- 모든 GUI에 bell 클릭 소리 추가 (playNotifySound로 해당 플레이어만 듣도록) - TeleportGui: 다른 플레이어 없으면 메시지만 표시, 빈 공간 클릭 무시 - EssentialsMenuGui: 아이템 이름 간소화 (스폰/좌표/제작대/텔레포트) - 설명 텍스트에서 '클릭하여' 제거, 좌표에 '저장된' 추가
283 lines
11 KiB
Kotlin
283 lines
11 KiB
Kotlin
package com.beemer.essentials.gui
|
|
|
|
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.sounds.SoundEvents
|
|
import net.minecraft.sounds.SoundSource
|
|
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_9x3, syncId) {
|
|
|
|
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
|
|
}
|
|
)
|
|
}
|
|
|
|
// 플레이어 인벤토리 슬롯
|
|
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(),
|
|
"스폰"
|
|
)
|
|
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 <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(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 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 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)
|
|
|
|
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) }
|
|
)
|
|
)
|
|
}
|