feat: 커스텀 사운드 시스템 추가
- 클릭 사운드 (custom.click) - GUI 버튼 클릭 시 - 에러 사운드 (custom.error) - 오류 메시지 시 자동 재생 - 알림 사운드 (custom.notification) - 신규 플레이어 환영 시 - 텔레포트 사운드 (custom.teleport) - 스폰, 좌표이동, TPA, back - 메뉴 사운드 (custom.menu) - 메뉴 GUI 열기 시 기타 변경: - 닉네임 변경 시 동일 닉네임 예외처리 추가 - TPA 오류 메시지 빨간색으로 변경
This commit is contained in:
parent
4dba2a4803
commit
fd4540c9e7
11 changed files with 384 additions and 194 deletions
|
|
@ -166,12 +166,22 @@ object CoordinateCommand {
|
||||||
else (coordinates.size + Menu.ITEMS_PER_PAGE - 1) / Menu.ITEMS_PER_PAGE
|
else (coordinates.size + Menu.ITEMS_PER_PAGE - 1) / Menu.ITEMS_PER_PAGE
|
||||||
val container = createPageContainer(player, page)
|
val container = createPageContainer(player, page)
|
||||||
|
|
||||||
|
// 버튼 상태에 따른 이미지 선택
|
||||||
|
val buttonImage =
|
||||||
|
when {
|
||||||
|
page == 0 && page >= totalPages - 1 -> "\uE010" // 둘 다 비활성
|
||||||
|
page == 0 -> "\uE012" // 이전만 비활성
|
||||||
|
page >= totalPages - 1 -> "\uE011" // 다음만 비활성
|
||||||
|
else -> "\uE013" // 둘 다 활성
|
||||||
|
}
|
||||||
|
|
||||||
|
// 커스텀 버튼 이미지만 타이틀에 표시 (타이틀 텍스트는 이미지에 포함)
|
||||||
|
val customTitle = "\uF808$buttonImage"
|
||||||
|
|
||||||
player.openMenu(
|
player.openMenu(
|
||||||
SimpleMenuProvider(
|
SimpleMenuProvider(
|
||||||
{ windowId, inv, _ -> Menu(windowId, inv, container, page) },
|
{ windowId, inv, _ -> Menu(windowId, inv, container, page) },
|
||||||
Component.literal("저장된 좌표 (${page + 1}/$totalPages)").withStyle {
|
Component.literal(customTitle).withStyle { it.withColor(0xFFFFFF) }
|
||||||
it.withBold(true)
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -208,6 +218,19 @@ object CoordinateCommand {
|
||||||
)
|
)
|
||||||
|
|
||||||
player.teleportTo(level, coord.x, coord.y, coord.z, player.yRot, player.xRot)
|
player.teleportTo(level, coord.x, coord.y, coord.z, player.yRot, player.xRot)
|
||||||
|
// 텔레포트 사운드 재생
|
||||||
|
val teleportSound =
|
||||||
|
net.minecraft.sounds.SoundEvent.createVariableRangeEvent(
|
||||||
|
net.minecraft.resources.ResourceLocation.parse(
|
||||||
|
"minecraft:custom.teleport"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
player.playNotifySound(
|
||||||
|
teleportSound,
|
||||||
|
net.minecraft.sounds.SoundSource.MASTER,
|
||||||
|
0.2f,
|
||||||
|
1.0f
|
||||||
|
)
|
||||||
MessageUtils.sendSuccess(player, "{$name}(으)로 이동했습니다.")
|
MessageUtils.sendSuccess(player, "{$name}(으)로 이동했습니다.")
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,21 @@ object PlayerCommand {
|
||||||
player.yRot,
|
player.yRot,
|
||||||
player.xRot
|
player.xRot
|
||||||
)
|
)
|
||||||
|
// 텔레포트 사운드 재생
|
||||||
|
val teleportSound =
|
||||||
|
net.minecraft.sounds.SoundEvent
|
||||||
|
.createVariableRangeEvent(
|
||||||
|
net.minecraft.resources
|
||||||
|
.ResourceLocation.parse(
|
||||||
|
"minecraft:custom.teleport"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
player.playNotifySound(
|
||||||
|
teleportSound,
|
||||||
|
net.minecraft.sounds.SoundSource.MASTER,
|
||||||
|
0.2f,
|
||||||
|
1.0f
|
||||||
|
)
|
||||||
MessageUtils.sendSuccess(player, "이전 위치로 이동했습니다.")
|
MessageUtils.sendSuccess(player, "이전 위치로 이동했습니다.")
|
||||||
1
|
1
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -105,6 +105,24 @@ object SpawnCommand {
|
||||||
player.yRot,
|
player.yRot,
|
||||||
player.xRot
|
player.xRot
|
||||||
)
|
)
|
||||||
|
// 텔레포트 사운드 재생
|
||||||
|
val teleportSound =
|
||||||
|
net.minecraft.sounds.SoundEvent
|
||||||
|
.createVariableRangeEvent(
|
||||||
|
net.minecraft
|
||||||
|
.resources
|
||||||
|
.ResourceLocation
|
||||||
|
.parse(
|
||||||
|
"minecraft:custom.teleport"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
player.playNotifySound(
|
||||||
|
teleportSound,
|
||||||
|
net.minecraft.sounds.SoundSource
|
||||||
|
.MASTER,
|
||||||
|
0.2f,
|
||||||
|
1.0f
|
||||||
|
)
|
||||||
MessageUtils.sendSuccess(
|
MessageUtils.sendSuccess(
|
||||||
player,
|
player,
|
||||||
"스폰으로 이동했습니다."
|
"스폰으로 이동했습니다."
|
||||||
|
|
|
||||||
|
|
@ -38,9 +38,9 @@ object TeleportCommand {
|
||||||
it != player
|
it != player
|
||||||
}
|
}
|
||||||
|
|
||||||
// 다른 플레이어가 없으면 메시지만 표시
|
// 다른 플레이어가 없으면 에러 메시지 표시
|
||||||
if (targetPlayers.isEmpty()) {
|
if (targetPlayers.isEmpty()) {
|
||||||
MessageUtils.sendInfo(
|
MessageUtils.sendError(
|
||||||
player,
|
player,
|
||||||
"현재 접속 중인 다른 플레이어가 없습니다."
|
"현재 접속 중인 다른 플레이어가 없습니다."
|
||||||
)
|
)
|
||||||
|
|
@ -56,6 +56,22 @@ object TeleportCommand {
|
||||||
container.setItem(idx, head)
|
container.setItem(idx, head)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 플레이어가 있을 때만 클릭 사운드 재생
|
||||||
|
val clickSound =
|
||||||
|
net.minecraft.sounds.SoundEvent
|
||||||
|
.createVariableRangeEvent(
|
||||||
|
net.minecraft.resources
|
||||||
|
.ResourceLocation.parse(
|
||||||
|
"minecraft:custom.click"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
player.playNotifySound(
|
||||||
|
clickSound,
|
||||||
|
net.minecraft.sounds.SoundSource.MASTER,
|
||||||
|
0.2f,
|
||||||
|
1.0f
|
||||||
|
)
|
||||||
|
|
||||||
player.openMenu(
|
player.openMenu(
|
||||||
SimpleMenuProvider(
|
SimpleMenuProvider(
|
||||||
{ windowId, inv, _ ->
|
{ windowId, inv, _ ->
|
||||||
|
|
|
||||||
|
|
@ -39,10 +39,30 @@ object PlayerEvents {
|
||||||
player.teleportTo(level, spawn.x, spawn.y, spawn.z, player.yRot, player.xRot)
|
player.teleportTo(level, spawn.x, spawn.y, spawn.z, player.yRot, player.xRot)
|
||||||
|
|
||||||
// 신규 플레이어 도움말 (약간의 딜레이 후 표시)
|
// 신규 플레이어 도움말 (약간의 딜레이 후 표시)
|
||||||
player.server.execute { sendWelcomeGuide(player) }
|
player.server.execute {
|
||||||
|
sendWelcomeGuide(player)
|
||||||
|
// 알림 사운드 재생
|
||||||
|
playNotificationSound(player)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 알림 사운드 재생 */
|
||||||
|
private fun playNotificationSound(player: ServerPlayer) {
|
||||||
|
val notificationSound =
|
||||||
|
net.minecraft.sounds.SoundEvent.createVariableRangeEvent(
|
||||||
|
net.minecraft.resources.ResourceLocation.parse(
|
||||||
|
"minecraft:custom.notification"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
player.playNotifySound(
|
||||||
|
notificationSound,
|
||||||
|
net.minecraft.sounds.SoundSource.MASTER,
|
||||||
|
0.5f,
|
||||||
|
1.0f
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/** 신규 플레이어 도움말 */
|
/** 신규 플레이어 도움말 */
|
||||||
private fun sendWelcomeGuide(player: ServerPlayer) {
|
private fun sendWelcomeGuide(player: ServerPlayer) {
|
||||||
val separator =
|
val separator =
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ import net.minecraft.ChatFormatting
|
||||||
import net.minecraft.core.component.DataComponents
|
import net.minecraft.core.component.DataComponents
|
||||||
import net.minecraft.network.chat.Component
|
import net.minecraft.network.chat.Component
|
||||||
import net.minecraft.server.level.ServerPlayer
|
import net.minecraft.server.level.ServerPlayer
|
||||||
import net.minecraft.sounds.SoundEvents
|
|
||||||
import net.minecraft.sounds.SoundSource
|
import net.minecraft.sounds.SoundSource
|
||||||
import net.minecraft.world.Container
|
import net.minecraft.world.Container
|
||||||
import net.minecraft.world.entity.player.Inventory
|
import net.minecraft.world.entity.player.Inventory
|
||||||
|
|
@ -98,7 +97,12 @@ class AntimobGui(
|
||||||
): ItemStack = ItemStack.EMPTY
|
): ItemStack = ItemStack.EMPTY
|
||||||
|
|
||||||
private fun playClickSound(player: ServerPlayer) {
|
private fun playClickSound(player: ServerPlayer) {
|
||||||
player.playNotifySound(SoundEvents.NOTE_BLOCK_BELL.value(), SoundSource.MASTER, 0.5f, 1.0f)
|
// 커스텀 클릭 사운드 (리소스팩: custom.click)
|
||||||
|
val customClick =
|
||||||
|
net.minecraft.sounds.SoundEvent.createVariableRangeEvent(
|
||||||
|
net.minecraft.resources.ResourceLocation.parse("minecraft:custom.click")
|
||||||
|
)
|
||||||
|
player.playNotifySound(customClick, SoundSource.MASTER, 0.2f, 1.0f)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun makeMobHead(mob: String): ItemStack {
|
private fun makeMobHead(mob: String): ItemStack {
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,6 @@ import net.minecraft.network.chat.Component
|
||||||
import net.minecraft.resources.ResourceKey
|
import net.minecraft.resources.ResourceKey
|
||||||
import net.minecraft.resources.ResourceLocation
|
import net.minecraft.resources.ResourceLocation
|
||||||
import net.minecraft.server.level.ServerPlayer
|
import net.minecraft.server.level.ServerPlayer
|
||||||
import net.minecraft.sounds.SoundEvents
|
|
||||||
import net.minecraft.sounds.SoundSource
|
import net.minecraft.sounds.SoundSource
|
||||||
import net.minecraft.world.Container
|
import net.minecraft.world.Container
|
||||||
import net.minecraft.world.SimpleContainer
|
import net.minecraft.world.SimpleContainer
|
||||||
|
|
@ -32,7 +31,7 @@ import net.minecraft.world.item.Items
|
||||||
import net.minecraft.world.item.component.CustomData
|
import net.minecraft.world.item.component.CustomData
|
||||||
import net.minecraft.world.item.component.ItemLore
|
import net.minecraft.world.item.component.ItemLore
|
||||||
|
|
||||||
/** 좌표 GUI - 페이지 기능 포함 5줄(45개) 표시 + 6번째 줄에 이전/다음 버튼 */
|
/** 좌표 GUI - 페이지 기능 포함 4줄(36개) 표시 + 6번째 줄에 이전/다음 버튼 */
|
||||||
class Menu(
|
class Menu(
|
||||||
syncId: Int,
|
syncId: Int,
|
||||||
playerInv: Inventory,
|
playerInv: Inventory,
|
||||||
|
|
@ -41,9 +40,9 @@ class Menu(
|
||||||
) : AbstractContainerMenu(MenuType.GENERIC_9x6, syncId) {
|
) : AbstractContainerMenu(MenuType.GENERIC_9x6, syncId) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val ITEMS_PER_PAGE = 45 // 5줄 * 9
|
const val ITEMS_PER_PAGE = 36 // 4줄 * 9
|
||||||
const val PREV_BUTTON_SLOT = 45 // 6번째 줄 첫 번째
|
val PREV_BUTTON_SLOTS = listOf(45, 46) // 6번째 줄 이전 버튼 2칸
|
||||||
const val NEXT_BUTTON_SLOT = 53 // 6번째 줄 마지막
|
val NEXT_BUTTON_SLOTS = listOf(52, 53) // 6번째 줄 다음 버튼 2칸
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
|
@ -84,8 +83,8 @@ class Menu(
|
||||||
val stack = slots[slotId].item
|
val stack = slots[slotId].item
|
||||||
val tag = stack.get(DataComponents.CUSTOM_DATA)?.copyTag() ?: return
|
val tag = stack.get(DataComponents.CUSTOM_DATA)?.copyTag() ?: return
|
||||||
|
|
||||||
// 이전 페이지 버튼
|
// 이전 페이지 버튼 (슬롯 45, 46)
|
||||||
if (slotId == PREV_BUTTON_SLOT &&
|
if (slotId in PREV_BUTTON_SLOTS &&
|
||||||
tag.contains("action") &&
|
tag.contains("action") &&
|
||||||
tag.getString("action") == "prev"
|
tag.getString("action") == "prev"
|
||||||
) {
|
) {
|
||||||
|
|
@ -96,8 +95,8 @@ class Menu(
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 다음 페이지 버튼
|
// 다음 페이지 버튼 (슬롯 52, 53)
|
||||||
if (slotId == NEXT_BUTTON_SLOT &&
|
if (slotId in NEXT_BUTTON_SLOTS &&
|
||||||
tag.contains("action") &&
|
tag.contains("action") &&
|
||||||
tag.getString("action") == "next"
|
tag.getString("action") == "next"
|
||||||
) {
|
) {
|
||||||
|
|
@ -140,7 +139,14 @@ class Menu(
|
||||||
)
|
)
|
||||||
|
|
||||||
player.teleportTo(level, x, y, z, player.yRot, player.xRot)
|
player.teleportTo(level, x, y, z, player.yRot, player.xRot)
|
||||||
playClickSound(player)
|
// 텔레포트 사운드 재생
|
||||||
|
val teleportSound =
|
||||||
|
net.minecraft.sounds.SoundEvent.createVariableRangeEvent(
|
||||||
|
net.minecraft.resources.ResourceLocation.parse(
|
||||||
|
"minecraft:custom.teleport"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
player.playNotifySound(teleportSound, SoundSource.MASTER, 0.2f, 1.0f)
|
||||||
MessageUtils.sendSuccess(player, "{$coordName}(으)로 이동했습니다.")
|
MessageUtils.sendSuccess(player, "{$coordName}(으)로 이동했습니다.")
|
||||||
}
|
}
|
||||||
player.closeContainer()
|
player.closeContainer()
|
||||||
|
|
@ -148,7 +154,12 @@ class Menu(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun playClickSound(player: ServerPlayer) {
|
private fun playClickSound(player: ServerPlayer) {
|
||||||
player.playNotifySound(SoundEvents.NOTE_BLOCK_BELL.value(), SoundSource.MASTER, 0.5f, 1.0f)
|
// 커스텀 클릭 사운드 (리소스팩: custom.click)
|
||||||
|
val customClick =
|
||||||
|
net.minecraft.sounds.SoundEvent.createVariableRangeEvent(
|
||||||
|
net.minecraft.resources.ResourceLocation.parse("minecraft:custom.click")
|
||||||
|
)
|
||||||
|
player.playNotifySound(customClick, SoundSource.MASTER, 0.2f, 1.0f)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getTotalPages(): Int {
|
private fun getTotalPages(): Int {
|
||||||
|
|
@ -158,12 +169,24 @@ class Menu(
|
||||||
|
|
||||||
private fun openPage(player: ServerPlayer, page: Int) {
|
private fun openPage(player: ServerPlayer, page: Int) {
|
||||||
val container = createPageContainer(player, page)
|
val container = createPageContainer(player, page)
|
||||||
|
val totalPages = getTotalPages()
|
||||||
|
|
||||||
|
// 버튼 상태에 따른 이미지 선택
|
||||||
|
val buttonImage =
|
||||||
|
when {
|
||||||
|
page == 0 && page >= totalPages - 1 -> "\uE010" // 둘 다 비활성
|
||||||
|
page == 0 -> "\uE012" // 이전만 비활성
|
||||||
|
page >= totalPages - 1 -> "\uE011" // 다음만 비활성
|
||||||
|
else -> "\uE013" // 둘 다 활성
|
||||||
|
}
|
||||||
|
|
||||||
|
// 커스텀 버튼 이미지만 타이틀에 표시 (타이틀 텍스트는 이미지에 포함)
|
||||||
|
val customTitle = "\uF808$buttonImage"
|
||||||
|
|
||||||
player.openMenu(
|
player.openMenu(
|
||||||
SimpleMenuProvider(
|
SimpleMenuProvider(
|
||||||
{ windowId, inv, _ -> Menu(windowId, inv, container, page) },
|
{ windowId, inv, _ -> Menu(windowId, inv, container, page) },
|
||||||
Component.literal("저장된 좌표 (${page + 1}/${getTotalPages()})").withStyle {
|
Component.literal(customTitle).withStyle { it.withColor(0xFFFFFF) }
|
||||||
it.withBold(true)
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -182,10 +205,10 @@ fun createPageContainer(player: ServerPlayer, page: Int): SimpleContainer {
|
||||||
if (coordinates.isEmpty()) 1
|
if (coordinates.isEmpty()) 1
|
||||||
else (coordinates.size + Menu.ITEMS_PER_PAGE - 1) / Menu.ITEMS_PER_PAGE
|
else (coordinates.size + Menu.ITEMS_PER_PAGE - 1) / Menu.ITEMS_PER_PAGE
|
||||||
|
|
||||||
// 좌표 아이템 추가 (5줄, 45개)
|
// 좌표 아이템 추가 (2~5번째 줄, 슬롯 9-44)
|
||||||
for (i in startIdx until endIdx) {
|
for (i in startIdx until endIdx) {
|
||||||
val coord = coordinates[i]
|
val coord = coordinates[i]
|
||||||
val item = ItemStack(Items.PAPER)
|
val item = ItemStack(Items.FILLED_MAP)
|
||||||
val tag =
|
val tag =
|
||||||
CompoundTag().apply {
|
CompoundTag().apply {
|
||||||
putString("name", coord.name)
|
putString("name", coord.name)
|
||||||
|
|
@ -209,96 +232,131 @@ fun createPageContainer(player: ServerPlayer, page: Int): SimpleContainer {
|
||||||
val displayName =
|
val displayName =
|
||||||
if (creatorName != null) {
|
if (creatorName != null) {
|
||||||
Component.literal(coord.name)
|
Component.literal(coord.name)
|
||||||
.withStyle { it.withColor(ChatFormatting.GOLD).withBold(true) }
|
.withStyle {
|
||||||
|
it.withColor(ChatFormatting.GOLD).withBold(false).withItalic(false)
|
||||||
|
}
|
||||||
.append(
|
.append(
|
||||||
Component.literal(" ($creatorName)").withStyle {
|
Component.literal(" ($creatorName)").withStyle {
|
||||||
it.withColor(ChatFormatting.AQUA).withBold(false)
|
it.withColor(ChatFormatting.AQUA)
|
||||||
|
.withBold(false)
|
||||||
|
.withItalic(false)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
Component.literal(coord.name).withStyle {
|
Component.literal(coord.name).withStyle {
|
||||||
it.withColor(ChatFormatting.GOLD).withBold(true)
|
it.withColor(ChatFormatting.GOLD).withBold(false).withItalic(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val loreList: List<Component> =
|
val loreList: List<Component> =
|
||||||
listOf(
|
listOf(
|
||||||
Component.literal("디멘션: ")
|
Component.literal("디멘션: ")
|
||||||
.withStyle { it.withColor(ChatFormatting.DARK_GREEN) }
|
.withStyle {
|
||||||
|
it.withColor(ChatFormatting.DARK_GREEN).withItalic(false)
|
||||||
|
}
|
||||||
.append(
|
.append(
|
||||||
Component.literal(translateDimension(coord.dimension))
|
Component.literal(translateDimension(coord.dimension))
|
||||||
.withStyle { it.withColor(ChatFormatting.GRAY) }
|
.withStyle {
|
||||||
|
it.withColor(ChatFormatting.GRAY)
|
||||||
|
.withItalic(false)
|
||||||
|
}
|
||||||
),
|
),
|
||||||
Component.literal("바이옴: ")
|
Component.literal("바이옴: ")
|
||||||
.withStyle { it.withColor(ChatFormatting.DARK_GREEN) }
|
.withStyle {
|
||||||
|
it.withColor(ChatFormatting.DARK_GREEN).withItalic(false)
|
||||||
|
}
|
||||||
.append(
|
.append(
|
||||||
Component.literal(translateBiome(coord.biome)).withStyle {
|
Component.literal(translateBiome(coord.biome)).withStyle {
|
||||||
it.withColor(ChatFormatting.GRAY)
|
it.withColor(ChatFormatting.GRAY).withItalic(false)
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
Component.literal("좌표: ")
|
Component.literal("좌표: ")
|
||||||
.withStyle { it.withColor(ChatFormatting.DARK_GREEN) }
|
.withStyle {
|
||||||
|
it.withColor(ChatFormatting.DARK_GREEN).withItalic(false)
|
||||||
|
}
|
||||||
.append(
|
.append(
|
||||||
Component.literal(
|
Component.literal(
|
||||||
"${coord.x.toInt()}, ${coord.y.toInt()}, ${coord.z.toInt()}"
|
"${coord.x.toInt()}, ${coord.y.toInt()}, ${coord.z.toInt()}"
|
||||||
)
|
)
|
||||||
.withStyle { it.withColor(ChatFormatting.GRAY) }
|
.withStyle {
|
||||||
|
it.withColor(ChatFormatting.GRAY)
|
||||||
|
.withItalic(false)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
item.set(DataComponents.CUSTOM_NAME, displayName)
|
item.set(DataComponents.CUSTOM_NAME, displayName)
|
||||||
item.set(DataComponents.LORE, ItemLore(loreList))
|
item.set(DataComponents.LORE, ItemLore(loreList))
|
||||||
item.set(DataComponents.CUSTOM_DATA, CustomData.of(tag))
|
item.set(DataComponents.CUSTOM_DATA, CustomData.of(tag))
|
||||||
container.setItem(i - startIdx, item)
|
item.set(
|
||||||
|
DataComponents.CUSTOM_MODEL_DATA,
|
||||||
|
net.minecraft.world.item.component.CustomModelData(1)
|
||||||
|
)
|
||||||
|
container.setItem(9 + (i - startIdx), item) // 슬롯 9부터 시작 (1줄 건너뛰기)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 맨 아래줄 빈 공간을 회색 유리판으로 채우기 (슬롯 46-52)
|
// 1번째 줄 (슬롯 0-8) - 타이틀 영역 투명 처리
|
||||||
val fillerItem = ItemStack(Items.GRAY_STAINED_GLASS_PANE)
|
val fillerItem = ItemStack(Items.GLASS_PANE)
|
||||||
|
fillerItem.set(DataComponents.CUSTOM_NAME, Component.empty())
|
||||||
|
fillerItem.set(DataComponents.LORE, ItemLore(listOf()))
|
||||||
fillerItem.set(
|
fillerItem.set(
|
||||||
DataComponents.CUSTOM_NAME,
|
DataComponents.CUSTOM_MODEL_DATA,
|
||||||
Component.literal(" ").withStyle { it.withColor(ChatFormatting.DARK_GRAY) }
|
net.minecraft.world.item.component.CustomModelData(1)
|
||||||
)
|
)
|
||||||
for (slot in 46..52) {
|
fillerItem.set(DataComponents.HIDE_TOOLTIP, net.minecraft.util.Unit.INSTANCE)
|
||||||
|
for (slot in 0..8) {
|
||||||
container.setItem(slot, fillerItem.copy())
|
container.setItem(slot, fillerItem.copy())
|
||||||
}
|
}
|
||||||
|
|
||||||
// 이전 페이지 버튼 (슬롯 45)
|
// 6번째 줄 가운데 슬롯 (47-51) - 투명 처리
|
||||||
if (page > 0) {
|
for (slot in 47..51) {
|
||||||
val prevItem = ItemStack(Items.LIME_STAINED_GLASS_PANE)
|
container.setItem(slot, fillerItem.copy())
|
||||||
val prevTag = CompoundTag().apply { putString("action", "prev") }
|
|
||||||
prevItem.set(
|
|
||||||
DataComponents.CUSTOM_NAME,
|
|
||||||
Component.literal("이전 페이지").withStyle { it.withColor(ChatFormatting.YELLOW) }
|
|
||||||
)
|
|
||||||
prevItem.set(DataComponents.CUSTOM_DATA, CustomData.of(prevTag))
|
|
||||||
container.setItem(Menu.PREV_BUTTON_SLOT, prevItem)
|
|
||||||
} else {
|
|
||||||
val disabledItem = ItemStack(Items.RED_STAINED_GLASS_PANE)
|
|
||||||
disabledItem.set(
|
|
||||||
DataComponents.CUSTOM_NAME,
|
|
||||||
Component.literal("이전 페이지").withStyle { it.withColor(ChatFormatting.YELLOW) }
|
|
||||||
)
|
|
||||||
container.setItem(Menu.PREV_BUTTON_SLOT, disabledItem)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 다음 페이지 버튼 (슬롯 53)
|
// 이전 페이지 버튼 (슬롯 45, 46) - 페이지 정보 툴팁
|
||||||
|
val prevItem = ItemStack(Items.GLASS_PANE)
|
||||||
|
val prevTag = CompoundTag().apply { putString("action", if (page > 0) "prev" else "") }
|
||||||
|
if (page > 0) {
|
||||||
|
prevItem.set(
|
||||||
|
DataComponents.CUSTOM_NAME,
|
||||||
|
Component.literal("이전 페이지 [${page + 1}/$totalPages]").withStyle {
|
||||||
|
it.withColor(0xA8D8D8).withItalic(false)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
prevItem.set(DataComponents.CUSTOM_NAME, Component.empty())
|
||||||
|
prevItem.set(DataComponents.HIDE_TOOLTIP, net.minecraft.util.Unit.INSTANCE)
|
||||||
|
}
|
||||||
|
prevItem.set(DataComponents.LORE, ItemLore(listOf()))
|
||||||
|
prevItem.set(DataComponents.CUSTOM_DATA, CustomData.of(prevTag))
|
||||||
|
prevItem.set(
|
||||||
|
DataComponents.CUSTOM_MODEL_DATA,
|
||||||
|
net.minecraft.world.item.component.CustomModelData(1)
|
||||||
|
)
|
||||||
|
Menu.PREV_BUTTON_SLOTS.forEach { container.setItem(it, prevItem.copy()) }
|
||||||
|
|
||||||
|
// 다음 페이지 버튼 (슬롯 52, 53) - 페이지 정보 툴팁
|
||||||
|
val nextItem = ItemStack(Items.GLASS_PANE)
|
||||||
|
val nextTag =
|
||||||
|
CompoundTag().apply { putString("action", if (page < totalPages - 1) "next" else "") }
|
||||||
if (page < totalPages - 1) {
|
if (page < totalPages - 1) {
|
||||||
val nextItem = ItemStack(Items.LIME_STAINED_GLASS_PANE)
|
|
||||||
val nextTag = CompoundTag().apply { putString("action", "next") }
|
|
||||||
nextItem.set(
|
nextItem.set(
|
||||||
DataComponents.CUSTOM_NAME,
|
DataComponents.CUSTOM_NAME,
|
||||||
Component.literal("다음 페이지").withStyle { it.withColor(ChatFormatting.YELLOW) }
|
Component.literal("다음 페이지 [${page + 1}/$totalPages]").withStyle {
|
||||||
|
it.withColor(0xA8D8D8).withItalic(false)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
nextItem.set(DataComponents.CUSTOM_DATA, CustomData.of(nextTag))
|
|
||||||
container.setItem(Menu.NEXT_BUTTON_SLOT, nextItem)
|
|
||||||
} else {
|
} else {
|
||||||
val disabledItem = ItemStack(Items.RED_STAINED_GLASS_PANE)
|
nextItem.set(DataComponents.CUSTOM_NAME, Component.empty())
|
||||||
disabledItem.set(
|
nextItem.set(DataComponents.HIDE_TOOLTIP, net.minecraft.util.Unit.INSTANCE)
|
||||||
DataComponents.CUSTOM_NAME,
|
|
||||||
Component.literal("다음 페이지").withStyle { it.withColor(ChatFormatting.YELLOW) }
|
|
||||||
)
|
|
||||||
container.setItem(Menu.NEXT_BUTTON_SLOT, disabledItem)
|
|
||||||
}
|
}
|
||||||
|
nextItem.set(DataComponents.LORE, ItemLore(listOf()))
|
||||||
|
nextItem.set(DataComponents.CUSTOM_DATA, CustomData.of(nextTag))
|
||||||
|
nextItem.set(
|
||||||
|
DataComponents.CUSTOM_MODEL_DATA,
|
||||||
|
net.minecraft.world.item.component.CustomModelData(1)
|
||||||
|
)
|
||||||
|
Menu.NEXT_BUTTON_SLOTS.forEach { container.setItem(it, nextItem.copy()) }
|
||||||
|
|
||||||
return container
|
return container
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ import net.minecraft.core.component.DataComponents
|
||||||
import net.minecraft.nbt.CompoundTag
|
import net.minecraft.nbt.CompoundTag
|
||||||
import net.minecraft.network.chat.Component
|
import net.minecraft.network.chat.Component
|
||||||
import net.minecraft.server.level.ServerPlayer
|
import net.minecraft.server.level.ServerPlayer
|
||||||
import net.minecraft.sounds.SoundEvents
|
|
||||||
import net.minecraft.sounds.SoundSource
|
import net.minecraft.sounds.SoundSource
|
||||||
import net.minecraft.world.Container
|
import net.minecraft.world.Container
|
||||||
import net.minecraft.world.SimpleContainer
|
import net.minecraft.world.SimpleContainer
|
||||||
|
|
@ -29,16 +28,16 @@ class EssentialsMenuGui(
|
||||||
playerInv: Inventory,
|
playerInv: Inventory,
|
||||||
val container: Container,
|
val container: Container,
|
||||||
val viewer: ServerPlayer
|
val viewer: ServerPlayer
|
||||||
) : AbstractContainerMenu(MenuType.GENERIC_9x3, syncId) {
|
) : AbstractContainerMenu(MenuType.GENERIC_9x6, syncId) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val CONTAINER_SIZE = 27
|
const val CONTAINER_SIZE = 54 // 9x6 = 54
|
||||||
|
|
||||||
// 메뉴 아이템 슬롯 위치
|
// 메뉴 아이템 슬롯 위치 (9x6 기준)
|
||||||
const val SLOT_SPAWN = 10 // 스폰
|
const val SLOT_SPAWN = 19 // 스폰
|
||||||
const val SLOT_COORDINATES = 12 // 좌표
|
const val SLOT_COORDINATES = 21 // 좌표
|
||||||
const val SLOT_WORKBENCH = 14 // 제작대
|
const val SLOT_WORKBENCH = 23 // 제작대
|
||||||
const val SLOT_TPA = 16 // TPA
|
const val SLOT_TPA = 25 // TPA
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
|
@ -71,7 +70,41 @@ class EssentialsMenuGui(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun clicked(slotId: Int, button: Int, clickType: ClickType, player: Player) {
|
override fun clicked(slotId: Int, button: Int, clickType: ClickType, player: Player) {
|
||||||
if (slotId !in 0 until CONTAINER_SIZE || player !is ServerPlayer) {
|
// 허용된 버튼 슬롯 목록
|
||||||
|
val buttonSlots =
|
||||||
|
setOf(
|
||||||
|
// 스폰
|
||||||
|
18,
|
||||||
|
19,
|
||||||
|
20,
|
||||||
|
27,
|
||||||
|
28,
|
||||||
|
29,
|
||||||
|
// 제작대 (순서 변경)
|
||||||
|
21,
|
||||||
|
22,
|
||||||
|
23,
|
||||||
|
30,
|
||||||
|
31,
|
||||||
|
32,
|
||||||
|
// 좌표 (순서 변경)
|
||||||
|
24,
|
||||||
|
25,
|
||||||
|
26,
|
||||||
|
33,
|
||||||
|
34,
|
||||||
|
35,
|
||||||
|
// TPA
|
||||||
|
36,
|
||||||
|
37,
|
||||||
|
38,
|
||||||
|
45,
|
||||||
|
46,
|
||||||
|
47
|
||||||
|
)
|
||||||
|
|
||||||
|
// 버튼 슬롯이 아니면 기본 처리
|
||||||
|
if (slotId !in buttonSlots || player !is ServerPlayer) {
|
||||||
super.clicked(slotId, button, clickType, player)
|
super.clicked(slotId, button, clickType, player)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -79,6 +112,7 @@ class EssentialsMenuGui(
|
||||||
val stack = slots[slotId].item
|
val stack = slots[slotId].item
|
||||||
val tag = stack.get(DataComponents.CUSTOM_DATA)?.copyTag() ?: return
|
val tag = stack.get(DataComponents.CUSTOM_DATA)?.copyTag() ?: return
|
||||||
val action = tag.getString("action")
|
val action = tag.getString("action")
|
||||||
|
if (action.isEmpty()) return
|
||||||
|
|
||||||
when (action) {
|
when (action) {
|
||||||
"spawn" -> {
|
"spawn" -> {
|
||||||
|
|
@ -87,7 +121,7 @@ class EssentialsMenuGui(
|
||||||
player.createCommandSourceStack(),
|
player.createCommandSourceStack(),
|
||||||
"스폰"
|
"스폰"
|
||||||
)
|
)
|
||||||
playClickSound(player)
|
// 스폰 명령어에서 이미 사운드 재생하므로 여기서는 제거
|
||||||
}
|
}
|
||||||
"coordinates" -> {
|
"coordinates" -> {
|
||||||
playClickSound(player)
|
playClickSound(player)
|
||||||
|
|
@ -103,7 +137,7 @@ class EssentialsMenuGui(
|
||||||
playClickSound(player)
|
playClickSound(player)
|
||||||
}
|
}
|
||||||
"tpa" -> {
|
"tpa" -> {
|
||||||
playClickSound(player)
|
// TPA 명령어에서 사운드 재생하므로 여기서는 제거
|
||||||
player.closeContainer()
|
player.closeContainer()
|
||||||
player.server.commands.performPrefixedCommand(
|
player.server.commands.performPrefixedCommand(
|
||||||
player.createCommandSourceStack(),
|
player.createCommandSourceStack(),
|
||||||
|
|
@ -114,12 +148,14 @@ class EssentialsMenuGui(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun playClickSound(player: ServerPlayer) {
|
private fun playClickSound(player: ServerPlayer) {
|
||||||
player.playNotifySound(
|
// 커스텀 클릭 사운드 (리소스팩: custom.click)
|
||||||
SoundEvents.NOTE_BLOCK_BELL.value(),
|
val customClick =
|
||||||
SoundSource.MASTER,
|
net.minecraft.sounds.SoundEvent.createVariableRangeEvent(
|
||||||
0.5f,
|
net.minecraft.resources.ResourceLocation.parse(
|
||||||
1.0f
|
"minecraft:custom.click"
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
player.playNotifySound(customClick, SoundSource.MASTER, 0.2f, 1.0f)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun openWorkbench(player: ServerPlayer) {
|
private fun openWorkbench(player: ServerPlayer) {
|
||||||
|
|
@ -160,113 +196,60 @@ class EssentialsMenuGui(
|
||||||
|
|
||||||
/** 메뉴 컨테이너 생성 */
|
/** 메뉴 컨테이너 생성 */
|
||||||
fun createEssentialsMenuContainer(): SimpleContainer {
|
fun createEssentialsMenuContainer(): SimpleContainer {
|
||||||
val container = SimpleContainer(27)
|
val container = SimpleContainer(54) // 9x6 = 54
|
||||||
|
|
||||||
// 배경을 회색 유리판으로 채우기
|
// 버튼 생성 헬퍼 함수 (툴팁 포함)
|
||||||
val fillerItem = ItemStack(Items.GRAY_STAINED_GLASS_PANE)
|
fun createButton(action: String, name: String, description: String): ItemStack {
|
||||||
fillerItem.set(
|
val item = ItemStack(Items.GLASS_PANE)
|
||||||
DataComponents.CUSTOM_NAME,
|
// 이름 설정 (하늘색)
|
||||||
Component.literal(" ").withStyle { it.withColor(ChatFormatting.DARK_GRAY) }
|
item.set(
|
||||||
)
|
DataComponents.CUSTOM_NAME,
|
||||||
for (i in 0 until 27) {
|
Component.literal(name).withStyle {
|
||||||
container.setItem(i, fillerItem.copy())
|
it.withColor(0xA8D8D8).withItalic(false)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
// 설명 설정 (회색)
|
||||||
|
item.set(
|
||||||
|
DataComponents.LORE,
|
||||||
|
ItemLore(
|
||||||
|
listOf(
|
||||||
|
Component.literal(description).withStyle {
|
||||||
|
it.withColor(ChatFormatting.GRAY).withItalic(false)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
item.set(
|
||||||
|
DataComponents.CUSTOM_DATA,
|
||||||
|
CustomData.of(CompoundTag().apply { putString("action", action) })
|
||||||
|
)
|
||||||
|
// CustomModelData를 1로 설정하여 텍스처 제거
|
||||||
|
item.set(
|
||||||
|
DataComponents.CUSTOM_MODEL_DATA,
|
||||||
|
net.minecraft.world.item.component.CustomModelData(1)
|
||||||
|
)
|
||||||
|
return item
|
||||||
}
|
}
|
||||||
|
|
||||||
// 스폰 버튼
|
// 스폰 버튼 (3x2 영역: 18,19,20,27,28,29)
|
||||||
val spawnItem = ItemStack(Items.GRASS_BLOCK)
|
val spawnSlots = listOf(18, 19, 20, 27, 28, 29)
|
||||||
spawnItem.set(
|
val spawnItem = createButton("spawn", "스폰", "스폰 지점으로 이동합니다")
|
||||||
DataComponents.CUSTOM_NAME,
|
spawnSlots.forEach { container.setItem(it, spawnItem.copy()) }
|
||||||
Component.literal("스폰").withStyle {
|
|
||||||
it.withColor(ChatFormatting.GREEN).withBold(true)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
spawnItem.set(
|
|
||||||
DataComponents.LORE,
|
|
||||||
ItemLore(
|
|
||||||
listOf(
|
|
||||||
Component.literal("스폰으로 이동합니다.").withStyle {
|
|
||||||
it.withColor(ChatFormatting.GRAY)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
spawnItem.set(
|
|
||||||
DataComponents.CUSTOM_DATA,
|
|
||||||
CustomData.of(CompoundTag().apply { putString("action", "spawn") })
|
|
||||||
)
|
|
||||||
container.setItem(EssentialsMenuGui.SLOT_SPAWN, spawnItem)
|
|
||||||
|
|
||||||
// 좌표 버튼
|
// 제작대 버튼 (3x2 영역: 21,22,23,30,31,32)
|
||||||
val coordItem = ItemStack(Items.COMPASS)
|
val workbenchSlots = listOf(21, 22, 23, 30, 31, 32)
|
||||||
coordItem.set(
|
val workbenchItem = createButton("workbench", "제작대", "제작대를 엽니다")
|
||||||
DataComponents.CUSTOM_NAME,
|
workbenchSlots.forEach { container.setItem(it, workbenchItem.copy()) }
|
||||||
Component.literal("좌표").withStyle {
|
|
||||||
it.withColor(ChatFormatting.YELLOW).withBold(true)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
coordItem.set(
|
|
||||||
DataComponents.LORE,
|
|
||||||
ItemLore(
|
|
||||||
listOf(
|
|
||||||
Component.literal("저장된 좌표 목록을 엽니다.").withStyle {
|
|
||||||
it.withColor(ChatFormatting.GRAY)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
coordItem.set(
|
|
||||||
DataComponents.CUSTOM_DATA,
|
|
||||||
CustomData.of(CompoundTag().apply { putString("action", "coordinates") })
|
|
||||||
)
|
|
||||||
container.setItem(EssentialsMenuGui.SLOT_COORDINATES, coordItem)
|
|
||||||
|
|
||||||
// 제작대 버튼
|
// 좌표 버튼 (3x2 영역: 24,25,26,33,34,35)
|
||||||
val workbenchItem = ItemStack(Items.CRAFTING_TABLE)
|
val coordSlots = listOf(24, 25, 26, 33, 34, 35)
|
||||||
workbenchItem.set(
|
val coordItem = createButton("coordinates", "좌표", "저장된 좌표 목록을 엽니다")
|
||||||
DataComponents.CUSTOM_NAME,
|
coordSlots.forEach { container.setItem(it, coordItem.copy()) }
|
||||||
Component.literal("제작대").withStyle {
|
|
||||||
it.withColor(ChatFormatting.GOLD).withBold(true)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
workbenchItem.set(
|
|
||||||
DataComponents.LORE,
|
|
||||||
ItemLore(
|
|
||||||
listOf(
|
|
||||||
Component.literal("제작대를 엽니다.").withStyle {
|
|
||||||
it.withColor(ChatFormatting.GRAY)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
workbenchItem.set(
|
|
||||||
DataComponents.CUSTOM_DATA,
|
|
||||||
CustomData.of(CompoundTag().apply { putString("action", "workbench") })
|
|
||||||
)
|
|
||||||
container.setItem(EssentialsMenuGui.SLOT_WORKBENCH, workbenchItem)
|
|
||||||
|
|
||||||
// TPA 버튼
|
// TPA 버튼 (3x2 영역: 36,37,38,45,46,47)
|
||||||
val tpaItem = ItemStack(Items.ENDER_PEARL)
|
val tpaSlots = listOf(36, 37, 38, 45, 46, 47)
|
||||||
tpaItem.set(
|
val tpaItem = createButton("tpa", "텔레포트", "다른 플레이어에게 이동합니다")
|
||||||
DataComponents.CUSTOM_NAME,
|
tpaSlots.forEach { container.setItem(it, tpaItem.copy()) }
|
||||||
Component.literal("텔레포트").withStyle {
|
|
||||||
it.withColor(ChatFormatting.AQUA).withBold(true)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
tpaItem.set(
|
|
||||||
DataComponents.LORE,
|
|
||||||
ItemLore(
|
|
||||||
listOf(
|
|
||||||
Component.literal("다른 플레이어에게 이동합니다.").withStyle {
|
|
||||||
it.withColor(ChatFormatting.GRAY)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
tpaItem.set(
|
|
||||||
DataComponents.CUSTOM_DATA,
|
|
||||||
CustomData.of(CompoundTag().apply { putString("action", "tpa") })
|
|
||||||
)
|
|
||||||
container.setItem(EssentialsMenuGui.SLOT_TPA, tpaItem)
|
|
||||||
|
|
||||||
return container
|
return container
|
||||||
}
|
}
|
||||||
|
|
@ -274,10 +257,23 @@ fun createEssentialsMenuContainer(): SimpleContainer {
|
||||||
/** 메뉴 GUI 열기 */
|
/** 메뉴 GUI 열기 */
|
||||||
fun openEssentialsMenu(player: ServerPlayer) {
|
fun openEssentialsMenu(player: ServerPlayer) {
|
||||||
val container = createEssentialsMenuContainer()
|
val container = createEssentialsMenuContainer()
|
||||||
|
|
||||||
|
// 메뉴 오픈 사운드 재생
|
||||||
|
val menuSound =
|
||||||
|
net.minecraft.sounds.SoundEvent.createVariableRangeEvent(
|
||||||
|
net.minecraft.resources.ResourceLocation.parse("minecraft:custom.menu")
|
||||||
|
)
|
||||||
|
player.playNotifySound(menuSound, net.minecraft.sounds.SoundSource.MASTER, 0.2f, 1.0f)
|
||||||
|
|
||||||
|
// 커스텀 GUI 이미지를 위한 유니코드 문자
|
||||||
|
// \uF808 = -8 (왼쪽으로 8px 이동)
|
||||||
|
// \uE000 = 메뉴 GUI 이미지
|
||||||
|
val customGuiPrefix = "\uF808\uE000"
|
||||||
|
|
||||||
player.openMenu(
|
player.openMenu(
|
||||||
SimpleMenuProvider(
|
SimpleMenuProvider(
|
||||||
{ windowId, inv, _ -> EssentialsMenuGui(windowId, inv, container, player) },
|
{ windowId, inv, _ -> EssentialsMenuGui(windowId, inv, container, player) },
|
||||||
Component.literal("Essentials 메뉴").withStyle { it.withBold(true) }
|
Component.literal(customGuiPrefix).withStyle { it.withColor(0xFFFFFF) }
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ import com.beemer.essentials.data.Location
|
||||||
import com.beemer.essentials.nickname.NicknameDataStore
|
import com.beemer.essentials.nickname.NicknameDataStore
|
||||||
import com.beemer.essentials.util.MessageUtils
|
import com.beemer.essentials.util.MessageUtils
|
||||||
import net.minecraft.server.level.ServerPlayer
|
import net.minecraft.server.level.ServerPlayer
|
||||||
import net.minecraft.sounds.SoundEvents
|
|
||||||
import net.minecraft.sounds.SoundSource
|
import net.minecraft.sounds.SoundSource
|
||||||
import net.minecraft.world.Container
|
import net.minecraft.world.Container
|
||||||
import net.minecraft.world.entity.player.Inventory
|
import net.minecraft.world.entity.player.Inventory
|
||||||
|
|
@ -143,12 +142,14 @@ class TeleportGui(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun playClickSound(player: ServerPlayer) {
|
private fun playClickSound(player: ServerPlayer) {
|
||||||
player.playNotifySound(
|
// 커스텀 텔레포트 사운드 (리소스팩: custom.teleport)
|
||||||
SoundEvents.NOTE_BLOCK_BELL.value(),
|
val teleportSound =
|
||||||
SoundSource.MASTER,
|
net.minecraft.sounds.SoundEvent.createVariableRangeEvent(
|
||||||
0.5f,
|
net.minecraft.resources.ResourceLocation.parse(
|
||||||
1.0f
|
"minecraft:custom.teleport"
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
player.playNotifySound(teleportSound, SoundSource.MASTER, 0.2f, 1.0f)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun stillValid(player: net.minecraft.world.entity.player.Player): Boolean = true
|
override fun stillValid(player: net.minecraft.world.entity.player.Player): Boolean = true
|
||||||
|
|
|
||||||
|
|
@ -95,6 +95,13 @@ object NicknameCommand {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 유효성 검사: 현재 닉네임과 동일
|
||||||
|
val currentNickname = NicknameDataStore.getNickname(player.uuid)
|
||||||
|
if (currentNickname == nickname) {
|
||||||
|
MessageUtils.sendError(player, "현재 사용 중인 닉네임과 동일합니다.")
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
// 유효성 검사: 중복
|
// 유효성 검사: 중복
|
||||||
if (NicknameDataStore.isNicknameTaken(nickname, player.uuid)) {
|
if (NicknameDataStore.isNicknameTaken(nickname, player.uuid)) {
|
||||||
MessageUtils.sendError(player, "이미 사용 중인 닉네임입니다.")
|
MessageUtils.sendError(player, "이미 사용 중인 닉네임입니다.")
|
||||||
|
|
|
||||||
|
|
@ -55,12 +55,44 @@ object MessageUtils {
|
||||||
|
|
||||||
fun sendError(player: ServerPlayer, text: String) {
|
fun sendError(player: ServerPlayer, text: String) {
|
||||||
player.sendSystemMessage(error(text))
|
player.sendSystemMessage(error(text))
|
||||||
|
playErrorSound(player)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun sendWarning(player: ServerPlayer, text: String) {
|
fun sendWarning(player: ServerPlayer, text: String) {
|
||||||
player.sendSystemMessage(warning(text))
|
player.sendSystemMessage(warning(text))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 알림 메시지 (사운드 포함) - 서버 접속 시 등 */
|
||||||
|
fun sendNotification(player: ServerPlayer, text: String) {
|
||||||
|
player.sendSystemMessage(info(text))
|
||||||
|
playNotificationSound(player)
|
||||||
|
}
|
||||||
|
|
||||||
|
// === 사운드 재생 ===
|
||||||
|
|
||||||
|
private fun playErrorSound(player: ServerPlayer) {
|
||||||
|
val errorSound =
|
||||||
|
net.minecraft.sounds.SoundEvent.createVariableRangeEvent(
|
||||||
|
net.minecraft.resources.ResourceLocation.parse("minecraft:custom.error")
|
||||||
|
)
|
||||||
|
player.playNotifySound(errorSound, net.minecraft.sounds.SoundSource.MASTER, 0.2f, 1.0f)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun playNotificationSound(player: ServerPlayer) {
|
||||||
|
val notificationSound =
|
||||||
|
net.minecraft.sounds.SoundEvent.createVariableRangeEvent(
|
||||||
|
net.minecraft.resources.ResourceLocation.parse(
|
||||||
|
"minecraft:custom.notification"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
player.playNotifySound(
|
||||||
|
notificationSound,
|
||||||
|
net.minecraft.sounds.SoundSource.MASTER,
|
||||||
|
0.5f,
|
||||||
|
1.0f
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/** 스타일 텍스트 파싱 {중괄호} 안의 텍스트는 강조 색상으로 표시 */
|
/** 스타일 텍스트 파싱 {중괄호} 안의 텍스트는 강조 색상으로 표시 */
|
||||||
private fun parseStyledText(text: String, baseColor: Int, accentColor: Int): MutableComponent {
|
private fun parseStyledText(text: String, baseColor: Int, accentColor: Int): MutableComponent {
|
||||||
val result: MutableComponent = Component.empty()
|
val result: MutableComponent = Component.empty()
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue