diff --git a/Essentials/src/main/kotlin/com/beemer/essentials/command/TeleportCommand.kt b/Essentials/src/main/kotlin/com/beemer/essentials/command/TeleportCommand.kt index 18a4b68..ce70c81 100644 --- a/Essentials/src/main/kotlin/com/beemer/essentials/command/TeleportCommand.kt +++ b/Essentials/src/main/kotlin/com/beemer/essentials/command/TeleportCommand.kt @@ -4,6 +4,7 @@ import com.beemer.essentials.gui.TeleportGui import com.beemer.essentials.gui.TeleportGui.Companion.CONTAINER_SIZE import com.beemer.essentials.nickname.NicknameDataStore import com.beemer.essentials.util.CommandUtils +import com.beemer.essentials.util.MessageUtils import com.beemer.essentials.util.TranslationUtils import com.mojang.authlib.GameProfile import net.minecraft.ChatFormatting @@ -32,13 +33,22 @@ object TeleportCommand { CommandUtils.getPlayerOrSendFailure(context.source) ?: return@executes 0 - val container = SimpleContainer(9 * 3) - val targetPlayers = player.server.playerList.players.filter { it != player } + // 다른 플레이어가 없으면 메시지만 표시 + if (targetPlayers.isEmpty()) { + MessageUtils.sendInfo( + player, + "현재 접속 중인 다른 플레이어가 없습니다." + ) + return@executes 0 + } + + val container = SimpleContainer(9 * 3) + targetPlayers.take(CONTAINER_SIZE).forEachIndexed { idx, target -> diff --git a/Essentials/src/main/kotlin/com/beemer/essentials/gui/AntimobGui.kt b/Essentials/src/main/kotlin/com/beemer/essentials/gui/AntimobGui.kt index 9adf8ea..2330703 100644 --- a/Essentials/src/main/kotlin/com/beemer/essentials/gui/AntimobGui.kt +++ b/Essentials/src/main/kotlin/com/beemer/essentials/gui/AntimobGui.kt @@ -5,6 +5,8 @@ import net.minecraft.ChatFormatting import net.minecraft.core.component.DataComponents import net.minecraft.network.chat.Component import net.minecraft.server.level.ServerPlayer +import net.minecraft.sounds.SoundEvents +import net.minecraft.sounds.SoundSource import net.minecraft.world.Container import net.minecraft.world.entity.player.Inventory import net.minecraft.world.inventory.AbstractContainerMenu @@ -77,6 +79,7 @@ class AntimobGui( ) { if (player is ServerPlayer && slotId in 0 until CONTAINER_SIZE) { if (glassSlotToMob.containsKey(slotId)) { + playClickSound(player) val mob = glassSlotToMob[slotId] ?: return AntimobConfig.toggle(mob) container.setItem(slotId, makeToggleGlass(mob)) @@ -94,6 +97,10 @@ class AntimobGui( index: Int ): ItemStack = ItemStack.EMPTY + private fun playClickSound(player: ServerPlayer) { + player.playNotifySound(SoundEvents.NOTE_BLOCK_BELL.value(), SoundSource.MASTER, 0.5f, 1.0f) + } + private fun makeMobHead(mob: String): ItemStack { val headItem = ItemStack(Items.PLAYER_HEAD) diff --git a/Essentials/src/main/kotlin/com/beemer/essentials/gui/CoordinateGui.kt b/Essentials/src/main/kotlin/com/beemer/essentials/gui/CoordinateGui.kt index 32389b1..6cd1a62 100644 --- a/Essentials/src/main/kotlin/com/beemer/essentials/gui/CoordinateGui.kt +++ b/Essentials/src/main/kotlin/com/beemer/essentials/gui/CoordinateGui.kt @@ -16,6 +16,8 @@ import net.minecraft.network.chat.Component import net.minecraft.resources.ResourceKey import net.minecraft.resources.ResourceLocation import net.minecraft.server.level.ServerPlayer +import net.minecraft.sounds.SoundEvents +import net.minecraft.sounds.SoundSource import net.minecraft.world.Container import net.minecraft.world.SimpleContainer import net.minecraft.world.SimpleMenuProvider @@ -88,6 +90,7 @@ class Menu( tag.getString("action") == "prev" ) { if (currentPage > 0) { + playClickSound(player) openPage(player, currentPage - 1) } return @@ -100,6 +103,7 @@ class Menu( ) { val totalPages = getTotalPages() if (currentPage < totalPages - 1) { + playClickSound(player) openPage(player, currentPage + 1) } return @@ -118,7 +122,6 @@ class Menu( val level = dimension?.let { player.server.getLevel(it) } if (level != null) { - // 이전 위치 저장 val prevPos = player.blockPosition() PlayerConfig.recordLastLocation( player, @@ -137,12 +140,17 @@ class Menu( ) player.teleportTo(level, x, y, z, player.yRot, player.xRot) + playClickSound(player) MessageUtils.sendSuccess(player, "{$coordName}(으)로 이동했습니다.") } player.closeContainer() } } + private fun playClickSound(player: ServerPlayer) { + player.playNotifySound(SoundEvents.NOTE_BLOCK_BELL.value(), SoundSource.MASTER, 0.5f, 1.0f) + } + private fun getTotalPages(): Int { val totalItems = CoordinateConfig.getCoordinates().size return if (totalItems == 0) 1 else (totalItems + ITEMS_PER_PAGE - 1) / ITEMS_PER_PAGE diff --git a/Essentials/src/main/kotlin/com/beemer/essentials/gui/EssentialsMenuGui.kt b/Essentials/src/main/kotlin/com/beemer/essentials/gui/EssentialsMenuGui.kt index d75ab8d..a9475dd 100644 --- a/Essentials/src/main/kotlin/com/beemer/essentials/gui/EssentialsMenuGui.kt +++ b/Essentials/src/main/kotlin/com/beemer/essentials/gui/EssentialsMenuGui.kt @@ -5,6 +5,8 @@ import net.minecraft.core.component.DataComponents import net.minecraft.nbt.CompoundTag import net.minecraft.network.chat.Component import net.minecraft.server.level.ServerPlayer +import net.minecraft.sounds.SoundEvents +import net.minecraft.sounds.SoundSource import net.minecraft.world.Container import net.minecraft.world.SimpleContainer import net.minecraft.world.SimpleMenuProvider @@ -29,240 +31,253 @@ class EssentialsMenuGui( val viewer: ServerPlayer ) : AbstractContainerMenu(MenuType.GENERIC_9x3, syncId) { - companion object { - const val CONTAINER_SIZE = 27 + companion object { + const val CONTAINER_SIZE = 27 - // 메뉴 아이템 슬롯 위치 - const val SLOT_SPAWN = 10 // 스폰 - const val SLOT_COORDINATES = 12 // 좌표 - const val SLOT_WORKBENCH = 14 // 제작대 - const val SLOT_TPA = 16 // TPA - } - - init { - // 컨테이너 슬롯 추가 - for (i in 0 until CONTAINER_SIZE) { - val x = 8 + (i % 9) * 18 - val y = 18 + (i / 9) * 18 - addSlot( - object : Slot(container, i, x, y) { - override fun mayPickup(player: Player): Boolean = false - override fun mayPlace(stack: ItemStack): Boolean = false - } - ) + // 메뉴 아이템 슬롯 위치 + const val SLOT_SPAWN = 10 // 스폰 + const val SLOT_COORDINATES = 12 // 좌표 + const val SLOT_WORKBENCH = 14 // 제작대 + const val SLOT_TPA = 16 // TPA } - // 플레이어 인벤토리 슬롯 - for (row in 0 until 3) { - for (col in 0 until 9) { - val index = col + row * 9 + 9 - val x = 8 + col * 18 - val y = 86 + row * 18 - addSlot(Slot(playerInv, index, x, y)) - } - } - for (col in 0 until 9) { - val x = 8 + col * 18 - val y = 144 - addSlot(Slot(playerInv, col, x, y)) - } - } - - override fun clicked(slotId: Int, button: Int, clickType: ClickType, player: Player) { - if (slotId !in 0 until CONTAINER_SIZE || player !is ServerPlayer) { - super.clicked(slotId, button, clickType, player) - return - } - - val stack = slots[slotId].item - val tag = stack.get(DataComponents.CUSTOM_DATA)?.copyTag() ?: return - val action = tag.getString("action") - - when (action) { - "spawn" -> { - player.closeContainer() - // 스폰 명령어 실행 - player.server.commands.performPrefixedCommand( - player.createCommandSourceStack(), - "스폰" - ) - } - "coordinates" -> { - player.closeContainer() - // 좌표 GUI 열기 - player.server.commands.performPrefixedCommand( - player.createCommandSourceStack(), - "좌표" - ) - } - "workbench" -> { - player.closeContainer() - // 제작대 열기 - openWorkbench(player) - } - "tpa" -> { - player.closeContainer() - // TPA GUI 열기 - player.server.commands.performPrefixedCommand( - player.createCommandSourceStack(), - "tpa" - ) - } - } - } - - private fun openWorkbench(player: ServerPlayer) { - val access = - object : ContainerLevelAccess { - override fun evaluate( - function: - java.util.function.BiFunction< - net.minecraft.world.level.Level, - net.minecraft.core.BlockPos, - T> - ): java.util.Optional { - return java.util.Optional.ofNullable( - function.apply(player.level(), player.blockPosition()) + init { + // 컨테이너 슬롯 추가 + for (i in 0 until CONTAINER_SIZE) { + val x = 8 + (i % 9) * 18 + val y = 18 + (i / 9) * 18 + addSlot( + object : Slot(container, i, x, y) { + override fun mayPickup(player: Player): Boolean = false + override fun mayPlace(stack: ItemStack): Boolean = false + } ) - } } - player.openMenu( - SimpleMenuProvider( - { containerId, inventory, _ -> - object : CraftingMenu(containerId, inventory, access) { - override fun stillValid(player: Player): Boolean = true - } - }, - Component.translatable("container.crafting") - ) - ) - } + // 플레이어 인벤토리 슬롯 + for (row in 0 until 3) { + for (col in 0 until 9) { + val index = col + row * 9 + 9 + val x = 8 + col * 18 + val y = 86 + row * 18 + addSlot(Slot(playerInv, index, x, y)) + } + } + for (col in 0 until 9) { + val x = 8 + col * 18 + val y = 144 + addSlot(Slot(playerInv, col, x, y)) + } + } - override fun stillValid(player: Player): Boolean = true - override fun quickMoveStack(player: Player, index: Int): ItemStack = ItemStack.EMPTY + override fun clicked(slotId: Int, button: Int, clickType: ClickType, player: Player) { + if (slotId !in 0 until CONTAINER_SIZE || player !is ServerPlayer) { + super.clicked(slotId, button, clickType, player) + return + } + + val stack = slots[slotId].item + val tag = stack.get(DataComponents.CUSTOM_DATA)?.copyTag() ?: return + val action = tag.getString("action") + + when (action) { + "spawn" -> { + player.closeContainer() + player.server.commands.performPrefixedCommand( + player.createCommandSourceStack(), + "스폰" + ) + playClickSound(player) + } + "coordinates" -> { + playClickSound(player) + player.closeContainer() + player.server.commands.performPrefixedCommand( + player.createCommandSourceStack(), + "좌표" + ) + } + "workbench" -> { + player.closeContainer() + openWorkbench(player) + playClickSound(player) + } + "tpa" -> { + playClickSound(player) + player.closeContainer() + player.server.commands.performPrefixedCommand( + player.createCommandSourceStack(), + "tpa" + ) + } + } + } + + private fun playClickSound(player: ServerPlayer) { + player.playNotifySound( + SoundEvents.NOTE_BLOCK_BELL.value(), + SoundSource.MASTER, + 0.5f, + 1.0f + ) + } + + private fun openWorkbench(player: ServerPlayer) { + val access = + object : ContainerLevelAccess { + override fun evaluate( + function: + java.util.function.BiFunction< + net.minecraft.world.level.Level, + net.minecraft.core.BlockPos, + T> + ): java.util.Optional { + return java.util.Optional.ofNullable( + function.apply( + player.level(), + player.blockPosition() + ) + ) + } + } + + player.openMenu( + SimpleMenuProvider( + { containerId, inventory, _ -> + object : CraftingMenu(containerId, inventory, access) { + override fun stillValid(player: Player): Boolean = + true + } + }, + Component.translatable("container.crafting") + ) + ) + } + + override fun stillValid(player: Player): Boolean = true + override fun quickMoveStack(player: Player, index: Int): ItemStack = ItemStack.EMPTY } /** 메뉴 컨테이너 생성 */ fun createEssentialsMenuContainer(): SimpleContainer { - val container = SimpleContainer(27) + val container = SimpleContainer(27) - // 배경을 회색 유리판으로 채우기 - val fillerItem = ItemStack(Items.GRAY_STAINED_GLASS_PANE) - fillerItem.set( - DataComponents.CUSTOM_NAME, - Component.literal(" ").withStyle { it.withColor(ChatFormatting.DARK_GRAY) } - ) - for (i in 0 until 27) { - container.setItem(i, fillerItem.copy()) - } + // 배경을 회색 유리판으로 채우기 + val fillerItem = ItemStack(Items.GRAY_STAINED_GLASS_PANE) + fillerItem.set( + DataComponents.CUSTOM_NAME, + Component.literal(" ").withStyle { it.withColor(ChatFormatting.DARK_GRAY) } + ) + for (i in 0 until 27) { + container.setItem(i, fillerItem.copy()) + } - // 스폰 버튼 - val spawnItem = ItemStack(Items.GRASS_BLOCK) - spawnItem.set( - DataComponents.CUSTOM_NAME, - 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) + // 스폰 버튼 + val spawnItem = ItemStack(Items.GRASS_BLOCK) + spawnItem.set( + DataComponents.CUSTOM_NAME, + 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) - // 좌표 버튼 - val coordItem = ItemStack(Items.COMPASS) - coordItem.set( - DataComponents.CUSTOM_NAME, - 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) + // 좌표 버튼 + val coordItem = ItemStack(Items.COMPASS) + coordItem.set( + DataComponents.CUSTOM_NAME, + 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) - // 제작대 버튼 - val workbenchItem = ItemStack(Items.CRAFTING_TABLE) - workbenchItem.set( - DataComponents.CUSTOM_NAME, - 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) + // 제작대 버튼 + val workbenchItem = ItemStack(Items.CRAFTING_TABLE) + workbenchItem.set( + DataComponents.CUSTOM_NAME, + 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 버튼 - val tpaItem = ItemStack(Items.ENDER_PEARL) - tpaItem.set( - DataComponents.CUSTOM_NAME, - 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) + // TPA 버튼 + val tpaItem = ItemStack(Items.ENDER_PEARL) + tpaItem.set( + DataComponents.CUSTOM_NAME, + 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 } /** 메뉴 GUI 열기 */ fun openEssentialsMenu(player: ServerPlayer) { - val container = createEssentialsMenuContainer() - player.openMenu( - SimpleMenuProvider( - { windowId, inv, _ -> EssentialsMenuGui(windowId, inv, container, player) }, - Component.literal("Essentials 메뉴").withStyle { it.withBold(true) } - ) - ) + val container = createEssentialsMenuContainer() + player.openMenu( + SimpleMenuProvider( + { windowId, inv, _ -> EssentialsMenuGui(windowId, inv, container, player) }, + Component.literal("Essentials 메뉴").withStyle { it.withBold(true) } + ) + ) } diff --git a/Essentials/src/main/kotlin/com/beemer/essentials/gui/TeleportGui.kt b/Essentials/src/main/kotlin/com/beemer/essentials/gui/TeleportGui.kt index f1feea1..0dfc1bb 100644 --- a/Essentials/src/main/kotlin/com/beemer/essentials/gui/TeleportGui.kt +++ b/Essentials/src/main/kotlin/com/beemer/essentials/gui/TeleportGui.kt @@ -2,10 +2,11 @@ package com.beemer.essentials.gui 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 com.beemer.essentials.util.MessageUtils import net.minecraft.server.level.ServerPlayer +import net.minecraft.sounds.SoundEvents +import net.minecraft.sounds.SoundSource import net.minecraft.world.Container import net.minecraft.world.entity.player.Inventory import net.minecraft.world.inventory.AbstractContainerMenu @@ -78,65 +79,78 @@ class TeleportGui( 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() - ) - 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, "해당 플레이어는 현재 오프라인입니다.") + // 플레이어가 없으면 무시 (빈 공간 클릭) + if (currentTargetPlayer == null) { + return } + 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 + ) + + // 소리 재생 (텔레포트 후) + playClickSound(player) + + // 닉네임이 있으면 닉네임 사용 + 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}님이 당신에게 텔레포트 했습니다." + ) + player.closeContainer() return } super.clicked(slotId, button, clickType, player) } + private fun playClickSound(player: ServerPlayer) { + player.playNotifySound( + SoundEvents.NOTE_BLOCK_BELL.value(), + SoundSource.MASTER, + 0.5f, + 1.0f + ) + } + override fun stillValid(player: net.minecraft.world.entity.player.Player): Boolean = true override fun quickMoveStack(