minecraft-mod/Essentials/src/main/kotlin/com/beemer/essentials/gui/EssentialsMenuGui.kt
Caadiq 2bf161bf15 refactor: SoundUtils 유틸리티로 사운드 재생 로직 중앙화
- 새 파일: SoundUtils.kt - 모든 사운드 재생 함수 통합
- 새 파일: LocationUtils.kt - 플레이어 위치 변환 유틸리티
- 10개 파일에서 중복 사운드 재생 코드 제거 (약 150줄)
- GUI: EssentialsMenuGui, CoordinateGui, TeleportGui, AntimobGui
- Command: SpawnCommand, PlayerCommand, CoordinateCommand, TeleportCommand
- Event/Util: PlayerEvents, MessageUtils
2025-12-30 19:40:36 +09:00

268 lines
12 KiB
Kotlin

package com.beemer.essentials.gui
import com.beemer.essentials.util.SoundUtils
import net.minecraft.ChatFormatting
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.world.Container
import net.minecraft.world.SimpleContainer
import net.minecraft.world.SimpleMenuProvider
import net.minecraft.world.entity.player.Inventory
import net.minecraft.world.entity.player.Player
import net.minecraft.world.inventory.AbstractContainerMenu
import net.minecraft.world.inventory.ClickType
import net.minecraft.world.inventory.ContainerLevelAccess
import net.minecraft.world.inventory.CraftingMenu
import net.minecraft.world.inventory.MenuType
import net.minecraft.world.inventory.Slot
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.Items
import net.minecraft.world.item.component.CustomData
import net.minecraft.world.item.component.ItemLore
/** Essentials 메뉴 GUI - Shift+F로 열기 스폰, 좌표, 제작대 등 주요 기능 바로가기 */
class EssentialsMenuGui(
syncId: Int,
playerInv: Inventory,
val container: Container,
val viewer: ServerPlayer
) : AbstractContainerMenu(MenuType.GENERIC_9x6, syncId) {
companion object {
const val CONTAINER_SIZE = 54 // 9x6 = 54
// 메뉴 아이템 슬롯 위치 (9x6 기준)
const val SLOT_SPAWN = 19 // 스폰
const val SLOT_COORDINATES = 21 // 좌표
const val SLOT_WORKBENCH = 23 // 제작대
const val SLOT_TPA = 25 // 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
}
)
}
// 플레이어 인벤토리 슬롯
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) {
// 허용된 버튼 슬롯 목록
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)
return
}
val stack = slots[slotId].item
val tag = stack.get(DataComponents.CUSTOM_DATA)?.copyTag() ?: return
val action = tag.getString("action")
if (action.isEmpty()) return
when (action) {
"spawn" -> {
player.closeContainer()
player.server.commands.performPrefixedCommand(
player.createCommandSourceStack(),
"스폰"
)
// 스폰 명령어에서 이미 사운드 재생하므로 여기서는 제거
}
"coordinates" -> {
playClickSound(player)
player.closeContainer()
player.server.commands.performPrefixedCommand(
player.createCommandSourceStack(),
"좌표"
)
}
"workbench" -> {
player.closeContainer()
openWorkbench(player)
playClickSound(player)
}
"tpa" -> {
// TPA 명령어에서 사운드 재생하므로 여기서는 제거
player.closeContainer()
player.server.commands.performPrefixedCommand(
player.createCommandSourceStack(),
"tpa"
)
}
}
}
private fun playClickSound(player: ServerPlayer) {
SoundUtils.playClick(player)
}
private fun openWorkbench(player: ServerPlayer) {
val access =
object : ContainerLevelAccess {
override fun <T : Any> evaluate(
function:
java.util.function.BiFunction<
net.minecraft.world.level.Level,
net.minecraft.core.BlockPos,
T>
): java.util.Optional<T> {
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(54) // 9x6 = 54
// 버튼 생성 헬퍼 함수 (툴팁 포함)
fun createButton(action: String, name: String, description: String): ItemStack {
val item = ItemStack(Items.GLASS_PANE)
// 이름 설정 (하늘색)
item.set(
DataComponents.CUSTOM_NAME,
Component.literal(name).withStyle {
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 spawnSlots = listOf(18, 19, 20, 27, 28, 29)
val spawnItem = createButton("spawn", "스폰", "스폰 지점으로 이동합니다")
spawnSlots.forEach { container.setItem(it, spawnItem.copy()) }
// 제작대 버튼 (3x2 영역: 21,22,23,30,31,32)
val workbenchSlots = listOf(21, 22, 23, 30, 31, 32)
val workbenchItem = createButton("workbench", "제작대", "제작대를 엽니다")
workbenchSlots.forEach { container.setItem(it, workbenchItem.copy()) }
// 좌표 버튼 (3x2 영역: 24,25,26,33,34,35)
val coordSlots = listOf(24, 25, 26, 33, 34, 35)
val coordItem = createButton("coordinates", "좌표", "저장된 좌표 목록을 엽니다")
coordSlots.forEach { container.setItem(it, coordItem.copy()) }
// TPA 버튼 (3x2 영역: 36,37,38,45,46,47)
val tpaSlots = listOf(36, 37, 38, 45, 46, 47)
val tpaItem = createButton("tpa", "텔레포트", "다른 플레이어에게 이동합니다")
tpaSlots.forEach { container.setItem(it, tpaItem.copy()) }
return container
}
/** 메뉴 GUI 열기 */
fun openEssentialsMenu(player: ServerPlayer) {
val container = createEssentialsMenuContainer()
// 메뉴 오픈 사운드 재생
SoundUtils.playMenu(player)
// 커스텀 GUI 이미지를 위한 유니코드 문자
// \uF808 = -8 (왼쪽으로 8px 이동)
// \uE000 = 메뉴 GUI 이미지
val customGuiPrefix = "\uF808\uE000"
player.openMenu(
SimpleMenuProvider(
{ windowId, inv, _ -> EssentialsMenuGui(windowId, inv, container, player) },
Component.literal(customGuiPrefix).withStyle { it.withColor(0xFFFFFF) }
)
)
}