style: MessageUtils 도입 - 전체 채팅 스타일 개선

- MessageUtils 유틸리티 추가 (청록색 기본 + 흰색 강조)
- 모든 명령어 및 GUI에 통일된 메시지 스타일 적용
- 수정 파일: SpawnCommand, NicknameCommand, CoordinateCommand, HeadCommand, PlayerCommand, ProtectFarmlandCommand, ChatCommand, TeleportGui, CoordinateGui, ChatEvents
This commit is contained in:
Caadiq 2025-12-29 15:13:37 +09:00
parent d0d8d0650f
commit ee66dddd99
11 changed files with 395 additions and 415 deletions

View file

@ -2,11 +2,10 @@ package com.beemer.essentials.command
import com.beemer.essentials.config.ChatConfig
import com.beemer.essentials.util.ChatUtils
import com.beemer.essentials.util.MessageUtils
import com.mojang.brigadier.exceptions.CommandSyntaxException
import net.minecraft.ChatFormatting
import net.minecraft.commands.CommandSourceStack
import net.minecraft.commands.Commands
import net.minecraft.network.chat.Component
import net.minecraft.server.level.ServerPlayer
import net.neoforged.bus.api.SubscribeEvent
import net.neoforged.neoforge.event.RegisterCommandsEvent
@ -31,11 +30,7 @@ object ChatCommand {
private fun hasPermissionOrSend(player: ServerPlayer?, requiredLevel: Int = 2): Boolean {
if (player != null && !player.hasPermissions(requiredLevel)) {
player.sendSystemMessage(
Component.literal("해당 명령어를 실행할 권한이 없습니다.").withStyle {
it.withColor(ChatFormatting.RED)
}
)
MessageUtils.sendError(player, "해당 명령어를 실행할 권한이 없습니다.")
return false
}
return true
@ -51,14 +46,14 @@ object ChatCommand {
ChatConfig.loadConfig()
val success =
Component.literal("채팅 형식을 새로고침했습니다.").withStyle {
it.withColor(ChatFormatting.GOLD)
}
if (player == null)
context.source.sendSuccess({ success }, false)
else player.sendSystemMessage(success)
if (player != null) {
MessageUtils.sendSuccess(player, "채팅 형식을 새로고침했습니다.")
} else {
context.source.sendSuccess(
{ MessageUtils.success("채팅 형식을 새로고침했습니다.") },
false
)
}
1
}
@ -81,9 +76,7 @@ object ChatCommand {
ChatUtils.clearChatForAll(allPlayers)
server.playerList.broadcastSystemMessage(
Component.literal("\u00A0\u00A0채팅창을 비웠습니다.").withStyle {
it.withColor(ChatFormatting.AQUA)
},
MessageUtils.info("채팅창을 비웠습니다."),
false
)

View file

@ -7,8 +7,8 @@ import com.beemer.essentials.data.Location
import com.beemer.essentials.gui.Menu
import com.beemer.essentials.gui.createPageContainer
import com.beemer.essentials.util.DimensionUtils
import com.beemer.essentials.util.MessageUtils
import com.mojang.brigadier.arguments.StringArgumentType
import net.minecraft.ChatFormatting
import net.minecraft.commands.Commands
import net.minecraft.network.chat.Component
import net.minecraft.server.level.ServerPlayer
@ -19,7 +19,7 @@ import net.neoforged.neoforge.event.RegisterCommandsEvent
object CoordinateCommand {
@SubscribeEvent
fun onRegisterCommands(event: RegisterCommandsEvent) {
// /좌표 - GUI 열기 (페이지 0부터 시작)
// /좌표 - GUI 열기
event.dispatcher.register(
Commands.literal("좌표").executes { context ->
val player = context.source.playerOrException as ServerPlayer
@ -28,7 +28,7 @@ object CoordinateCommand {
}
)
// /좌표이동 <장소> - 바로 이동
// /좌표이동 <장소>
event.dispatcher.register(
Commands.literal("좌표이동")
.then(
@ -70,16 +70,9 @@ object CoordinateCommand {
)
if (CoordinateConfig.isExist(name)) {
player.sendSystemMessage(
Component.literal(
"$name 좌표가 이미 존재합니다."
)
.withStyle {
it.withColor(
ChatFormatting
.RED
)
}
MessageUtils.sendError(
player,
"{$name} 좌표가 이미 존재합니다."
)
return@executes 0
}
@ -116,16 +109,9 @@ object CoordinateCommand {
)
CoordinateConfig.addCoordinate(coordinate)
player.sendSystemMessage(
Component.literal(
"$name 좌표를 추가했습니다."
)
.withStyle {
it.withColor(
ChatFormatting
.GOLD
)
}
MessageUtils.sendSuccess(
player,
"{$name} 좌표를 추가했습니다."
)
1
}
@ -155,31 +141,17 @@ object CoordinateCommand {
)
if (!CoordinateConfig.isExist(name)) {
player.sendSystemMessage(
Component.literal(
"$name 좌표가 존재하지 않습니다."
)
.withStyle {
it.withColor(
ChatFormatting
.RED
)
}
MessageUtils.sendError(
player,
"{$name} 좌표가 존재하지 않습니다."
)
return@executes 0
}
CoordinateConfig.removeCoordinate(name)
player.sendSystemMessage(
Component.literal(
"$name 좌표를 제거했습니다."
)
.withStyle {
it.withColor(
ChatFormatting
.GOLD
)
}
MessageUtils.sendSuccess(
player,
"{$name} 좌표를 제거했습니다."
)
1
}
@ -207,21 +179,13 @@ object CoordinateCommand {
private fun teleportToCoordinate(player: ServerPlayer, name: String): Int {
val coord = CoordinateConfig.getCoordinate(name)
if (coord == null) {
player.sendSystemMessage(
Component.literal("$name 좌표가 존재하지 않습니다.").withStyle {
it.withColor(ChatFormatting.RED)
}
)
MessageUtils.sendError(player, "{$name} 좌표가 존재하지 않습니다.")
return 0
}
val level = DimensionUtils.getLevelById(player.server, coord.dimension)
if (level == null) {
player.sendSystemMessage(
Component.literal("존재하지 않는 차원입니다.").withStyle {
it.withColor(ChatFormatting.RED)
}
)
MessageUtils.sendError(player, "존재하지 않는 차원입니다.")
return 0
}
@ -244,11 +208,7 @@ object CoordinateCommand {
)
player.teleportTo(level, coord.x, coord.y, coord.z, player.yRot, player.xRot)
player.sendSystemMessage(
Component.literal("$name (으)로 이동했습니다.").withStyle {
it.withColor(ChatFormatting.GOLD)
}
)
MessageUtils.sendSuccess(player, "{$name}(으)로 이동했습니다.")
return 1
}
}

View file

@ -2,6 +2,7 @@ package com.beemer.essentials.command
import com.beemer.essentials.config.PlayerConfig
import com.beemer.essentials.nickname.NicknameDataStore
import com.beemer.essentials.util.MessageUtils
import com.mojang.brigadier.arguments.StringArgumentType
import java.util.Optional
import java.util.UUID
@ -30,8 +31,6 @@ object HeadCommand {
StringArgumentType.greedyString()
)
.suggests { _, builder ->
// 서버에 접속한 적 있는 플레이어만 제안
// 닉네임이 있으면 닉네임, 없으면 원래 이름
val suggestions =
mutableListOf<String>()
PlayerConfig.getAllPlayers()
@ -93,11 +92,7 @@ object HeadCommand {
// 3. 서버에 접속한 적 없는 플레이어면 거부
if (targetUuid == null || !PlayerConfig.isKnownPlayer(targetUuid)) {
player.sendSystemMessage(
Component.literal("해당 플레이어는 서버에 접속한 적이 없습니다.").withStyle {
it.withColor(ChatFormatting.RED)
}
)
MessageUtils.sendError(player, "해당 플레이어는 서버에 접속한 적이 없습니다.")
return 0
}
@ -130,21 +125,13 @@ object HeadCommand {
val added = player.inventory.add(headItem)
if (added) {
player.sendSystemMessage(
Component.literal(displayName)
.withStyle { it.withColor(ChatFormatting.GREEN) }
.append(
Component.literal("의 머리가 지급되었습니다.").withStyle {
it.withColor(ChatFormatting.GOLD)
}
)
)
MessageUtils.sendSuccess(player, "{$displayName}의 머리가 지급되었습니다.")
} else {
// 인벤토리가 가득 찬 경우 바닥에 드랍
player.drop(headItem, false)
player.sendSystemMessage(
Component.literal("인벤토리가 가득 차서 ${displayName}의 머리를 바닥에 떨어뜨렸습니다.")
.withStyle { it.withColor(ChatFormatting.YELLOW) }
MessageUtils.sendWarning(
player,
"인벤토리가 가득 차서 {$displayName}의 머리를 바닥에 떨어뜨렸습니다."
)
}

View file

@ -3,9 +3,8 @@ package com.beemer.essentials.command
import com.beemer.essentials.config.PlayerConfig
import com.beemer.essentials.util.CommandUtils
import com.beemer.essentials.util.DimensionUtils
import net.minecraft.ChatFormatting
import com.beemer.essentials.util.MessageUtils
import net.minecraft.commands.Commands
import net.minecraft.network.chat.Component
import net.neoforged.bus.api.SubscribeEvent
import net.neoforged.neoforge.event.RegisterCommandsEvent
@ -23,16 +22,9 @@ object PlayerCommand {
val lastLocation =
info?.lastLocation
?: run {
player.sendSystemMessage(
Component.literal(
"이전 위치가 존재하지 않습니다."
)
.withStyle {
it.withColor(
ChatFormatting
.RED
)
}
MessageUtils.sendError(
player,
"이전 위치가 존재하지 않습니다."
)
return@executes 0
}
@ -43,16 +35,9 @@ object PlayerCommand {
lastLocation.dimension
)
?: run {
player.sendSystemMessage(
Component.literal(
"존재하지 않는 차원입니다."
)
.withStyle {
it.withColor(
ChatFormatting
.RED
)
}
MessageUtils.sendError(
player,
"존재하지 않는 차원입니다."
)
return@executes 0
}
@ -65,11 +50,7 @@ object PlayerCommand {
player.yRot,
player.xRot
)
player.sendSystemMessage(
Component.literal("이전 위치로 이동했습니다.").withStyle {
it.withColor(ChatFormatting.GOLD)
}
)
MessageUtils.sendSuccess(player, "이전 위치로 이동했습니다.")
1
}
)

View file

@ -2,9 +2,8 @@ package com.beemer.essentials.command
import com.beemer.essentials.config.ProtectFarmlandConfig
import com.beemer.essentials.util.CommandUtils
import net.minecraft.ChatFormatting
import com.beemer.essentials.util.MessageUtils
import net.minecraft.commands.Commands
import net.minecraft.network.chat.Component
import net.neoforged.bus.api.SubscribeEvent
import net.neoforged.neoforge.event.RegisterCommandsEvent
@ -19,22 +18,9 @@ object ProtectFarmlandCommand {
?: return@executes 0
val enabled = ProtectFarmlandConfig.toggle()
val status = if (enabled) "활성화" else "비활성화"
player.sendSystemMessage(
Component.literal("밭 보호를 ")
.withStyle { it.withColor(ChatFormatting.GOLD) }
.append(
Component.literal(if (enabled) "활성화" else "비활성화")
.withStyle {
it.withColor(ChatFormatting.DARK_GREEN)
}
)
.append(
Component.literal("했습니다.").withStyle {
it.withColor(ChatFormatting.GOLD)
}
)
)
MessageUtils.sendSuccess(player, "밭 보호를 {$status}했습니다.")
1
}
)

View file

@ -5,116 +5,129 @@ import com.beemer.essentials.config.SpawnConfig
import com.beemer.essentials.data.Location
import com.beemer.essentials.util.CommandUtils
import com.beemer.essentials.util.DimensionUtils
import net.minecraft.ChatFormatting
import com.beemer.essentials.util.MessageUtils
import net.minecraft.commands.Commands
import net.minecraft.network.chat.Component
import net.neoforged.bus.api.SubscribeEvent
import net.neoforged.neoforge.event.RegisterCommandsEvent
object SpawnCommand {
@SubscribeEvent
fun onRegisterCommands(event: RegisterCommandsEvent) {
listOf("setspawn", "스폰설정").forEach { command ->
event.dispatcher.register(
Commands.literal(command).executes { context ->
val player =
CommandUtils.getPlayerOrSendFailure(context.source)
?: return@executes 0
@SubscribeEvent
fun onRegisterCommands(event: RegisterCommandsEvent) {
listOf("setspawn", "스폰설정").forEach { command ->
event.dispatcher.register(
Commands.literal(command).executes { context ->
val player =
CommandUtils.getPlayerOrSendFailure(context.source)
?: return@executes 0
val exactPos = player.position()
val blockPos = player.blockPosition()
val dimensionId = player.level().dimension().location().toString()
val biomeId =
player.level()
.getBiome(blockPos)
.unwrapKey()
.map { it.location().toString() }
.orElse("minecraft:plains")
val exactPos = player.position()
val blockPos = player.blockPosition()
val dimension =
player.level().dimension().location().toString()
val biome =
player.level()
.getBiome(blockPos)
.unwrapKey()
.map { it.location().toString() }
.orElse("minecraft:plains")
val location =
Location(
dimension = dimensionId,
biome = biomeId,
x = exactPos.x,
y = exactPos.y,
z = exactPos.z
)
val location =
Location(
dimension = dimension,
biome = biome,
x = exactPos.x,
y = exactPos.y,
z = exactPos.z
)
SpawnConfig.setCustomSpawn(location)
player.sendSystemMessage(
Component.literal("스폰 지점이 현재 위치로 설정되었습니다.").withStyle {
it.withColor(ChatFormatting.GOLD)
SpawnConfig.setCustomSpawn(location)
MessageUtils.sendSuccess(player, "스폰 지점이 현재 위치로 설정되었습니다.")
1
}
)
1
}
)
}
}
listOf("spawn", "스폰", "넴주").forEach { command ->
event.dispatcher.register(
Commands.literal(command).executes { context ->
val player =
CommandUtils.getPlayerOrSendFailure(context.source)
?: return@executes 0
listOf("spawn", "스폰", "넴주").forEach { command ->
event.dispatcher.register(
Commands.literal(command).executes { context ->
val player =
CommandUtils.getPlayerOrSendFailure(context.source)
?: return@executes 0
val target = SpawnConfig.getCustomSpawn() ?: SpawnConfig.getDefaultSpawn()
val target =
SpawnConfig.getCustomSpawn()
?: SpawnConfig.getDefaultSpawn()
target?.let { t ->
val level = DimensionUtils.getLevelById(player.server, t.dimension)
target?.let { t ->
val level =
DimensionUtils.getLevelById(
player.server,
t.dimension
)
level?.let { l ->
val exactPos = player.position()
val blockPos = player.blockPosition()
val currentDimension =
player.level().dimension().location().toString()
val currentBiome =
player.level()
.getBiome(blockPos)
.unwrapKey()
.map { it.location().toString() }
.orElse("minecraft:plains")
level?.let { l ->
val exactPos = player.position()
val blockPos = player.blockPosition()
val currentDimension =
player.level()
.dimension()
.location()
.toString()
val currentBiome =
player.level()
.getBiome(blockPos)
.unwrapKey()
.map {
it.location()
.toString()
}
.orElse("minecraft:plains")
val currentLocation =
Location(
dimension = currentDimension,
biome = currentBiome,
x = exactPos.x,
y = exactPos.y,
z = exactPos.z
)
val currentLocation =
Location(
dimension =
currentDimension,
biome = currentBiome,
x = exactPos.x,
y = exactPos.y,
z = exactPos.z
)
PlayerConfig.recordLastLocation(player, currentLocation)
player.teleportTo(l, t.x, t.y, t.z, player.yRot, player.xRot)
player.sendSystemMessage(
Component.literal("스폰으로 이동했습니다.").withStyle {
it.withColor(ChatFormatting.GOLD)
PlayerConfig.recordLastLocation(
player,
currentLocation
)
player.teleportTo(
l,
t.x,
t.y,
t.z,
player.yRot,
player.xRot
)
MessageUtils.sendSuccess(
player,
"스폰으로 이동했습니다."
)
}
}
)
}
}
1
}
)
}
listOf("delspawn", "스폰삭제").forEach { command ->
event.dispatcher.register(
Commands.literal(command).executes { context ->
val player =
CommandUtils.getPlayerOrSendFailure(context.source)
?: return@executes 0
SpawnConfig.removeCustomSpawn()
player.sendSystemMessage(
Component.literal("스폰 지점이 기본 스폰 지점으로 변경되었습니다.").withStyle {
it.withColor(ChatFormatting.GOLD)
1
}
)
1
}
)
}
listOf("delspawn", "스폰삭제").forEach { command ->
event.dispatcher.register(
Commands.literal(command).executes { context ->
val player =
CommandUtils.getPlayerOrSendFailure(context.source)
?: return@executes 0
SpawnConfig.removeCustomSpawn()
MessageUtils.sendInfo(player, "스폰 지점이 기본 스폰 지점으로 변경되었습니다.")
1
}
)
}
}
}
}

View file

@ -1,7 +1,7 @@
package com.beemer.essentials.event
import com.beemer.essentials.util.ChatUtils
import net.minecraft.network.chat.Component
import com.beemer.essentials.util.MessageUtils
import net.minecraft.server.level.ServerPlayer
import net.neoforged.bus.api.SubscribeEvent
import net.neoforged.neoforge.event.ServerChatEvent
@ -12,13 +12,11 @@ object ChatEvents {
val player = event.player as ServerPlayer
val raw = event.rawText
if (raw.startsWith("/"))
return
if (raw.startsWith("/")) return
if (ChatUtils.hasIllegalCharacter(raw)) {
event.isCanceled = true
player.sendSystemMessage(Component.literal("채팅에 허용되지 않는 문자가 포함되어 있습니다.")
.withStyle { it.withColor(net.minecraft.ChatFormatting.RED) })
MessageUtils.sendError(player, "채팅에 허용되지 않는 문자가 포함되어 있습니다.")
return
}
@ -26,4 +24,4 @@ object ChatEvents {
event.isCanceled = true
}
}
}

View file

@ -4,6 +4,7 @@ import com.beemer.essentials.config.CoordinateConfig
import com.beemer.essentials.config.PlayerConfig
import com.beemer.essentials.data.Location
import com.beemer.essentials.nickname.NicknameDataStore
import com.beemer.essentials.util.MessageUtils
import com.beemer.essentials.util.TranslationUtils.translateBiome
import com.beemer.essentials.util.TranslationUtils.translateDimension
import java.util.UUID
@ -109,6 +110,7 @@ class Menu(
val x = tag.getDouble("x")
val y = tag.getDouble("y")
val z = tag.getDouble("z")
val coordName = tag.getString("name")
val dimension =
ResourceLocation.tryParse(tag.getString("dimension"))?.let {
ResourceKey.create(Registries.DIMENSION, it)
@ -135,15 +137,7 @@ class Menu(
)
player.teleportTo(level, x, y, z, player.yRot, player.xRot)
player.sendSystemMessage(
Component.literal(tag.getString("name"))
.withStyle { it.withColor(ChatFormatting.DARK_GREEN) }
.append(
Component.literal("(으)로 이동했습니다.").withStyle {
it.withColor(ChatFormatting.GOLD)
}
)
)
MessageUtils.sendSuccess(player, "{$coordName}(으)로 이동했습니다.")
}
player.closeContainer()
}

View file

@ -4,8 +4,7 @@ import com.beemer.essentials.config.PlayerConfig
import com.beemer.essentials.data.Location
import com.beemer.essentials.data.Player
import com.beemer.essentials.nickname.NicknameDataStore
import net.minecraft.ChatFormatting
import net.minecraft.network.chat.Component
import com.beemer.essentials.util.MessageUtils
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.Container
import net.minecraft.world.entity.player.Inventory
@ -21,140 +20,127 @@ class TeleportGui(
val container: Container,
val viewer: ServerPlayer
) : AbstractContainerMenu(MenuType.GENERIC_9x3, syncId) {
companion object {
const val CONTAINER_COLUMNS = 9
const val CONTAINER_ROWS = 3
const val CONTAINER_SIZE = CONTAINER_COLUMNS * CONTAINER_ROWS
companion object {
const val CONTAINER_COLUMNS = 9
const val CONTAINER_ROWS = 3
const val CONTAINER_SIZE = CONTAINER_COLUMNS * CONTAINER_ROWS
const val SLOT_SIZE = 18
const val LEFT_PADDING = 8
const val TOP_PADDING = 18
const val SLOT_SIZE = 18
const val LEFT_PADDING = 8
const val TOP_PADDING = 18
const val PLAYER_INV_TOP = 86
const val HOTBAR_Y = 144
}
private val targetPlayers =
viewer.server
.playerList
.players
.filterIsInstance<ServerPlayer>()
.filter { it.uuid != viewer.uuid }
.map { it.uuid }
init {
for (i in 0 until CONTAINER_SIZE) {
val x = LEFT_PADDING + (i % CONTAINER_COLUMNS) * SLOT_SIZE
val y = TOP_PADDING + (i / CONTAINER_COLUMNS) * SLOT_SIZE
addSlot(
object : Slot(container, i, x, y) {
override fun mayPickup(
player: net.minecraft.world.entity.player.Player
): Boolean = false
override fun mayPlace(stack: ItemStack): Boolean = false
}
)
const val PLAYER_INV_TOP = 86
const val HOTBAR_Y = 144
}
for (row in 0 until 3) {
for (col in 0 until 9) {
val index = col + row * 9 + 9
val x = LEFT_PADDING + col * SLOT_SIZE
val y = PLAYER_INV_TOP + row * SLOT_SIZE
addSlot(Slot(playerInv, index, x, y))
}
}
private val targetPlayers =
viewer.server
.playerList
.players
.filterIsInstance<ServerPlayer>()
.filter { it.uuid != viewer.uuid }
.map { it.uuid }
for (col in 0 until 9) {
val x = LEFT_PADDING + col * SLOT_SIZE
val y = HOTBAR_Y
addSlot(Slot(playerInv, col, x, y))
}
}
override fun clicked(
slotId: Int,
button: Int,
clickType: ClickType,
player: net.minecraft.world.entity.player.Player
) {
if (slotId in 0 until CONTAINER_SIZE && player is ServerPlayer) {
val currentTargetPlayer =
targetPlayers.getOrNull(slotId)?.let { player.server.playerList.getPlayer(it) }
if (currentTargetPlayer != null && player.uuid != currentTargetPlayer.uuid) {
val prevPos = player.blockPosition()
val prevDimension = player.level().dimension().location().toString()
val prevBiome =
player.level()
.getBiome(prevPos)
.unwrapKey()
.map { it.location().toString() }
.orElse("minecraft:plains")
val previousLocation =
Location(
dimension = prevDimension,
biome = prevBiome,
x = prevPos.x.toDouble(),
y = prevPos.y.toDouble(),
z = prevPos.z.toDouble()
init {
for (i in 0 until CONTAINER_SIZE) {
val x = LEFT_PADDING + (i % CONTAINER_COLUMNS) * SLOT_SIZE
val y = TOP_PADDING + (i / CONTAINER_COLUMNS) * SLOT_SIZE
addSlot(
object : Slot(container, i, x, y) {
override fun mayPickup(
player: net.minecraft.world.entity.player.Player
): Boolean = false
override fun mayPlace(stack: ItemStack): Boolean = false
}
)
PlayerConfig.recordLastLocation(player, previousLocation)
}
val targetLevel = currentTargetPlayer.serverLevel()
player.teleportTo(
targetLevel,
currentTargetPlayer.x,
currentTargetPlayer.y,
currentTargetPlayer.z,
currentTargetPlayer.yRot,
currentTargetPlayer.xRot
)
// 닉네임이 있으면 닉네임 사용
val targetName =
NicknameDataStore.getNickname(currentTargetPlayer.uuid)
?: currentTargetPlayer.gameProfile.name
val playerName =
NicknameDataStore.getNickname(player.uuid) ?: player.gameProfile.name
player.sendSystemMessage(
Component.literal(targetName)
.withStyle { it.withColor(ChatFormatting.AQUA) }
.append(
Component.literal("님에게 텔레포트 했습니다.").withStyle {
it.withColor(ChatFormatting.GOLD)
}
)
)
currentTargetPlayer.sendSystemMessage(
Component.literal(playerName)
.withStyle { it.withColor(ChatFormatting.AQUA) }
.append(
Component.literal("님이 당신에게 텔레포트 했습니다.").withStyle {
it.withColor(ChatFormatting.GOLD)
}
)
)
} else {
player.sendSystemMessage(
Component.literal("해당 플레이어는 현재 오프라인입니다.").withStyle {
it.withColor(ChatFormatting.RED)
for (row in 0 until 3) {
for (col in 0 until 9) {
val index = col + row * 9 + 9
val x = LEFT_PADDING + col * SLOT_SIZE
val y = PLAYER_INV_TOP + row * SLOT_SIZE
addSlot(Slot(playerInv, index, x, y))
}
)
}
}
player.closeContainer()
return
for (col in 0 until 9) {
val x = LEFT_PADDING + col * SLOT_SIZE
val y = HOTBAR_Y
addSlot(Slot(playerInv, col, x, y))
}
}
super.clicked(slotId, button, clickType, player)
}
override fun stillValid(player: net.minecraft.world.entity.player.Player): Boolean = true
override fun clicked(
slotId: Int,
button: Int,
clickType: ClickType,
player: net.minecraft.world.entity.player.Player
) {
if (slotId in 0 until CONTAINER_SIZE && player is ServerPlayer) {
val currentTargetPlayer =
targetPlayers.getOrNull(slotId)?.let {
player.server.playerList.getPlayer(it)
}
override fun quickMoveStack(
player: net.minecraft.world.entity.player.Player,
index: Int
): ItemStack = ItemStack.EMPTY
if (currentTargetPlayer != null && player.uuid != currentTargetPlayer.uuid
) {
val prevPos = player.blockPosition()
val prevDimension = player.level().dimension().location().toString()
val prevBiome =
player.level()
.getBiome(prevPos)
.unwrapKey()
.map { it.location().toString() }
.orElse("minecraft:plains")
val previousLocation =
Location(
dimension = prevDimension,
biome = prevBiome,
x = prevPos.x.toDouble(),
y = prevPos.y.toDouble(),
z = prevPos.z.toDouble()
)
PlayerConfig.recordLastLocation(player, previousLocation)
val targetLevel = currentTargetPlayer.serverLevel()
player.teleportTo(
targetLevel,
currentTargetPlayer.x,
currentTargetPlayer.y,
currentTargetPlayer.z,
currentTargetPlayer.yRot,
currentTargetPlayer.xRot
)
// 닉네임이 있으면 닉네임 사용
val targetName =
NicknameDataStore.getNickname(currentTargetPlayer.uuid)
?: currentTargetPlayer.gameProfile.name
val playerName =
NicknameDataStore.getNickname(player.uuid)
?: player.gameProfile.name
MessageUtils.sendSuccess(player, "{$targetName}님에게 텔레포트 했습니다.")
MessageUtils.sendInfo(
currentTargetPlayer,
"{$playerName}님이 당신에게 텔레포트 했습니다."
)
} else {
MessageUtils.sendError(player, "해당 플레이어는 현재 오프라인입니다.")
}
player.closeContainer()
return
}
super.clicked(slotId, button, clickType, player)
}
override fun stillValid(player: net.minecraft.world.entity.player.Player): Boolean = true
override fun quickMoveStack(
player: net.minecraft.world.entity.player.Player,
index: Int
): ItemStack = ItemStack.EMPTY
}

View file

@ -1,9 +1,8 @@
package com.beemer.essentials.nickname
import com.beemer.essentials.util.MessageUtils
import com.mojang.brigadier.arguments.StringArgumentType
import net.minecraft.ChatFormatting
import net.minecraft.commands.Commands
import net.minecraft.network.chat.Component
import net.minecraft.server.level.ServerPlayer
import net.neoforged.bus.api.SubscribeEvent
import net.neoforged.neoforge.event.RegisterCommandsEvent
@ -29,7 +28,6 @@ object NicknameCommand {
.entity as?
ServerPlayer
?: return@executes 0
val nickname =
StringArgumentType
.getString(
@ -46,7 +44,6 @@ object NicknameCommand {
val player =
context.source.entity as? ServerPlayer
?: return@executes 0
executeReset(player)
}
)
@ -69,7 +66,6 @@ object NicknameCommand {
.entity as?
ServerPlayer
?: return@executes 0
val nickname =
StringArgumentType
.getString(
@ -86,7 +82,6 @@ object NicknameCommand {
val player =
context.source.entity as? ServerPlayer
?: return@executes 0
executeReset(player)
}
)
@ -96,65 +91,34 @@ object NicknameCommand {
private fun executeSet(player: ServerPlayer, nickname: String): Int {
// 유효성 검사: 길이
if (nickname.length < 2 || nickname.length > 16) {
player.sendSystemMessage(
Component.literal("닉네임은 2~16자 사이여야 합니다.").withStyle {
it.withColor(ChatFormatting.RED)
}
)
MessageUtils.sendError(player, "닉네임은 2~16자 사이여야 합니다.")
return 0
}
// 유효성 검사: 중복
if (NicknameDataStore.isNicknameTaken(nickname, player.uuid)) {
player.sendSystemMessage(
Component.literal("이미 사용 중인 닉네임입니다.").withStyle {
it.withColor(ChatFormatting.RED)
}
)
MessageUtils.sendError(player, "이미 사용 중인 닉네임입니다.")
return 0
}
// 닉네임 저장 및 적용 (gameProfile.name = 실제 마인크래프트 이름)
// 닉네임 저장 및 적용
NicknameDataStore.setNickname(player.uuid, player.gameProfile.name, nickname)
NicknameManager.applyNickname(player, nickname)
player.sendSystemMessage(
Component.literal("닉네임이 ")
.withStyle { it.withColor(ChatFormatting.GOLD) }
.append(
Component.literal(nickname).withStyle {
it.withColor(ChatFormatting.AQUA)
}
)
.append(
Component.literal("(으)로 변경되었습니다.").withStyle {
it.withColor(ChatFormatting.GOLD)
}
)
)
MessageUtils.sendSuccess(player, "닉네임이 {$nickname}(으)로 변경되었습니다.")
return 1
}
private fun executeReset(player: ServerPlayer): Int {
if (!NicknameDataStore.hasNickname(player.uuid)) {
player.sendSystemMessage(
Component.literal("설정된 닉네임이 없습니다.").withStyle {
it.withColor(ChatFormatting.RED)
}
)
MessageUtils.sendError(player, "설정된 닉네임이 없습니다.")
return 0
}
NicknameDataStore.removeNickname(player.uuid)
NicknameManager.removeNickname(player)
player.sendSystemMessage(
Component.literal("닉네임이 초기화되었습니다.").withStyle {
it.withColor(ChatFormatting.GOLD)
}
)
MessageUtils.sendSuccess(player, "닉네임이 초기화되었습니다.")
return 1
}
}

View file

@ -0,0 +1,118 @@
package com.beemer.essentials.util
import net.minecraft.network.chat.Component
import net.minecraft.network.chat.MutableComponent
import net.minecraft.network.chat.Style
import net.minecraft.network.chat.TextColor
import net.minecraft.server.level.ServerPlayer
/** 메시지 스타일 유틸리티 기본 텍스트: 밝은 청록색 (#A8D8D8) 강조 텍스트: 흰색 (#FFFFFF) */
object MessageUtils {
// 기본 색상 (더 밝게)
private const val BASE_COLOR = 0xA8D8D8 // 밝은 청록색 (기본 텍스트)
private const val ACCENT_COLOR = 0xFFFFFF // 흰색 (강조)
private const val ERROR_COLOR = 0xFF8888 // 밝은 빨간색 (오류)
private const val WARNING_COLOR = 0xFFE066 // 밝은 노란색 (경고)
/**
* 기본 스타일 메시지 생성
* @param text 텍스트 (강조 부분은 {중괄호} 감싸기) : "스폰으로 이동했습니다." 또는 "{비머}님에게 텔레포트 요청을 보냈습니다."
*/
fun styled(text: String): MutableComponent {
return parseStyledText(text, BASE_COLOR, ACCENT_COLOR)
}
/** 성공 메시지 (아이콘 없이 기본 스타일) */
fun success(text: String): MutableComponent {
return styled(text)
}
/** 정보 메시지 (아이콘 없이 기본 스타일) */
fun info(text: String): MutableComponent {
return styled(text)
}
/** 오류 메시지 (빨간색 스타일) */
fun error(text: String): MutableComponent {
return parseStyledText(text, ERROR_COLOR, ACCENT_COLOR)
}
/** 경고 메시지 (노란색 스타일) */
fun warning(text: String): MutableComponent {
return parseStyledText(text, WARNING_COLOR, ACCENT_COLOR)
}
// === 플레이어에게 직접 전송 ===
fun sendSuccess(player: ServerPlayer, text: String) {
player.sendSystemMessage(success(text))
}
fun sendInfo(player: ServerPlayer, text: String) {
player.sendSystemMessage(info(text))
}
fun sendError(player: ServerPlayer, text: String) {
player.sendSystemMessage(error(text))
}
fun sendWarning(player: ServerPlayer, text: String) {
player.sendSystemMessage(warning(text))
}
/** 스타일 텍스트 파싱 {중괄호} 안의 텍스트는 강조 색상으로 표시 */
private fun parseStyledText(text: String, baseColor: Int, accentColor: Int): MutableComponent {
val result: MutableComponent = Component.empty()
var i = 0
val sb = StringBuilder()
while (i < text.length) {
when {
text[i] == '{' -> {
// 이전 텍스트 추가 (기본 색상)
if (sb.isNotEmpty()) {
result.append(
Component.literal(sb.toString())
.withStyle(
Style.EMPTY.withColor(TextColor.fromRgb(baseColor))
)
)
sb.clear()
}
// 강조 텍스트 찾기
val endIdx = text.indexOf('}', i)
if (endIdx != -1) {
val accentText = text.substring(i + 1, endIdx)
result.append(
Component.literal(accentText)
.withStyle(
Style.EMPTY.withColor(
TextColor.fromRgb(accentColor)
)
)
)
i = endIdx + 1
} else {
sb.append(text[i])
i++
}
}
else -> {
sb.append(text[i])
i++
}
}
}
// 남은 텍스트 추가
if (sb.isNotEmpty()) {
result.append(
Component.literal(sb.toString())
.withStyle(Style.EMPTY.withColor(TextColor.fromRgb(baseColor)))
)
}
return result
}
}