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 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(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) } ) ) }