Compare commits
No commits in common. "2d42669545df936579427410efc1651ea06b618a" and "695bc79ca43f9d2594d489b16b518f68fba2555e" have entirely different histories.
2d42669545
...
695bc79ca4
25 changed files with 527 additions and 1208 deletions
|
|
@ -1,37 +0,0 @@
|
||||||
package com.beemer.essentials.mixin;
|
|
||||||
|
|
||||||
import com.beemer.essentials.event.MenuKeyHandler;
|
|
||||||
import net.minecraft.network.protocol.game.ServerboundPlayerActionPacket;
|
|
||||||
import net.minecraft.server.level.ServerPlayer;
|
|
||||||
import net.minecraft.server.network.ServerGamePacketListenerImpl;
|
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
|
||||||
import org.spongepowered.asm.mixin.Shadow;
|
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
|
||||||
import org.spongepowered.asm.mixin.injection.Inject;
|
|
||||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 손 바꾸기 패킷 가로채기
|
|
||||||
* Shift + F 조합으로 메뉴 GUI 열기
|
|
||||||
*/
|
|
||||||
@Mixin(ServerGamePacketListenerImpl.class)
|
|
||||||
public class ServerGamePacketListenerImplMixin {
|
|
||||||
|
|
||||||
@Shadow
|
|
||||||
public ServerPlayer player;
|
|
||||||
|
|
||||||
@Inject(method = "handlePlayerAction", at = @At("HEAD"), cancellable = true)
|
|
||||||
private void onHandlePlayerAction(ServerboundPlayerActionPacket packet, CallbackInfo ci) {
|
|
||||||
// 손 바꾸기 액션인지 확인
|
|
||||||
if (packet.getAction() == ServerboundPlayerActionPacket.Action.SWAP_ITEM_WITH_OFFHAND) {
|
|
||||||
// Shift를 누르고 있으면 메뉴 열기
|
|
||||||
if (player.isShiftKeyDown()) {
|
|
||||||
// 패킷 처리 취소 (실제 손 바꾸기 방지)
|
|
||||||
ci.cancel();
|
|
||||||
|
|
||||||
// 메뉴 GUI 열기
|
|
||||||
MenuKeyHandler.INSTANCE.openMenuFromPacket(player);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -33,7 +33,5 @@ class Essentials(modEventBus: IEventBus) {
|
||||||
NeoForge.EVENT_BUS.register(NicknameCommand)
|
NeoForge.EVENT_BUS.register(NicknameCommand)
|
||||||
NeoForge.EVENT_BUS.register(HeadCommand)
|
NeoForge.EVENT_BUS.register(HeadCommand)
|
||||||
NeoForge.EVENT_BUS.register(HelpCommand)
|
NeoForge.EVENT_BUS.register(HelpCommand)
|
||||||
NeoForge.EVENT_BUS.register(WorkbenchCommand)
|
|
||||||
NeoForge.EVENT_BUS.register(MenuCommand)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,11 @@ package com.beemer.essentials.command
|
||||||
|
|
||||||
import com.beemer.essentials.config.ChatConfig
|
import com.beemer.essentials.config.ChatConfig
|
||||||
import com.beemer.essentials.util.ChatUtils
|
import com.beemer.essentials.util.ChatUtils
|
||||||
import com.beemer.essentials.util.MessageUtils
|
|
||||||
import com.mojang.brigadier.exceptions.CommandSyntaxException
|
import com.mojang.brigadier.exceptions.CommandSyntaxException
|
||||||
|
import net.minecraft.ChatFormatting
|
||||||
import net.minecraft.commands.CommandSourceStack
|
import net.minecraft.commands.CommandSourceStack
|
||||||
import net.minecraft.commands.Commands
|
import net.minecraft.commands.Commands
|
||||||
|
import net.minecraft.network.chat.Component
|
||||||
import net.minecraft.server.level.ServerPlayer
|
import net.minecraft.server.level.ServerPlayer
|
||||||
import net.neoforged.bus.api.SubscribeEvent
|
import net.neoforged.bus.api.SubscribeEvent
|
||||||
import net.neoforged.neoforge.event.RegisterCommandsEvent
|
import net.neoforged.neoforge.event.RegisterCommandsEvent
|
||||||
|
|
@ -30,7 +31,11 @@ object ChatCommand {
|
||||||
|
|
||||||
private fun hasPermissionOrSend(player: ServerPlayer?, requiredLevel: Int = 2): Boolean {
|
private fun hasPermissionOrSend(player: ServerPlayer?, requiredLevel: Int = 2): Boolean {
|
||||||
if (player != null && !player.hasPermissions(requiredLevel)) {
|
if (player != null && !player.hasPermissions(requiredLevel)) {
|
||||||
MessageUtils.sendError(player, "해당 명령어를 실행할 권한이 없습니다.")
|
player.sendSystemMessage(
|
||||||
|
Component.literal("해당 명령어를 실행할 권한이 없습니다.").withStyle {
|
||||||
|
it.withColor(ChatFormatting.RED)
|
||||||
|
}
|
||||||
|
)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
|
|
@ -46,14 +51,14 @@ object ChatCommand {
|
||||||
|
|
||||||
ChatConfig.loadConfig()
|
ChatConfig.loadConfig()
|
||||||
|
|
||||||
if (player != null) {
|
val success =
|
||||||
MessageUtils.sendSuccess(player, "채팅 형식을 새로고침했습니다.")
|
Component.literal("채팅 형식을 새로고침했습니다.").withStyle {
|
||||||
} else {
|
it.withColor(ChatFormatting.GOLD)
|
||||||
context.source.sendSuccess(
|
}
|
||||||
{ MessageUtils.success("채팅 형식을 새로고침했습니다.") },
|
|
||||||
false
|
if (player == null)
|
||||||
)
|
context.source.sendSuccess({ success }, false)
|
||||||
}
|
else player.sendSystemMessage(success)
|
||||||
|
|
||||||
1
|
1
|
||||||
}
|
}
|
||||||
|
|
@ -76,7 +81,9 @@ object ChatCommand {
|
||||||
ChatUtils.clearChatForAll(allPlayers)
|
ChatUtils.clearChatForAll(allPlayers)
|
||||||
|
|
||||||
server.playerList.broadcastSystemMessage(
|
server.playerList.broadcastSystemMessage(
|
||||||
MessageUtils.info("채팅창을 비웠습니다."),
|
Component.literal("\u00A0\u00A0채팅창을 비웠습니다.").withStyle {
|
||||||
|
it.withColor(ChatFormatting.AQUA)
|
||||||
|
},
|
||||||
false
|
false
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,8 @@ import com.beemer.essentials.data.Location
|
||||||
import com.beemer.essentials.gui.Menu
|
import com.beemer.essentials.gui.Menu
|
||||||
import com.beemer.essentials.gui.createPageContainer
|
import com.beemer.essentials.gui.createPageContainer
|
||||||
import com.beemer.essentials.util.DimensionUtils
|
import com.beemer.essentials.util.DimensionUtils
|
||||||
import com.beemer.essentials.util.MessageUtils
|
|
||||||
import com.beemer.essentials.util.SoundUtils
|
|
||||||
import com.mojang.brigadier.arguments.StringArgumentType
|
import com.mojang.brigadier.arguments.StringArgumentType
|
||||||
|
import net.minecraft.ChatFormatting
|
||||||
import net.minecraft.commands.Commands
|
import net.minecraft.commands.Commands
|
||||||
import net.minecraft.network.chat.Component
|
import net.minecraft.network.chat.Component
|
||||||
import net.minecraft.server.level.ServerPlayer
|
import net.minecraft.server.level.ServerPlayer
|
||||||
|
|
@ -20,7 +19,7 @@ import net.neoforged.neoforge.event.RegisterCommandsEvent
|
||||||
object CoordinateCommand {
|
object CoordinateCommand {
|
||||||
@SubscribeEvent
|
@SubscribeEvent
|
||||||
fun onRegisterCommands(event: RegisterCommandsEvent) {
|
fun onRegisterCommands(event: RegisterCommandsEvent) {
|
||||||
// /좌표 - GUI 열기
|
// /좌표 - GUI 열기 (페이지 0부터 시작)
|
||||||
event.dispatcher.register(
|
event.dispatcher.register(
|
||||||
Commands.literal("좌표").executes { context ->
|
Commands.literal("좌표").executes { context ->
|
||||||
val player = context.source.playerOrException as ServerPlayer
|
val player = context.source.playerOrException as ServerPlayer
|
||||||
|
|
@ -29,7 +28,7 @@ object CoordinateCommand {
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// /좌표이동 <장소>
|
// /좌표이동 <장소> - 바로 이동
|
||||||
event.dispatcher.register(
|
event.dispatcher.register(
|
||||||
Commands.literal("좌표이동")
|
Commands.literal("좌표이동")
|
||||||
.then(
|
.then(
|
||||||
|
|
@ -71,9 +70,16 @@ object CoordinateCommand {
|
||||||
)
|
)
|
||||||
|
|
||||||
if (CoordinateConfig.isExist(name)) {
|
if (CoordinateConfig.isExist(name)) {
|
||||||
MessageUtils.sendError(
|
player.sendSystemMessage(
|
||||||
player,
|
Component.literal(
|
||||||
"{$name} 좌표가 이미 존재합니다."
|
"$name 좌표가 이미 존재합니다."
|
||||||
|
)
|
||||||
|
.withStyle {
|
||||||
|
it.withColor(
|
||||||
|
ChatFormatting
|
||||||
|
.RED
|
||||||
|
)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
return@executes 0
|
return@executes 0
|
||||||
}
|
}
|
||||||
|
|
@ -110,9 +116,16 @@ object CoordinateCommand {
|
||||||
)
|
)
|
||||||
|
|
||||||
CoordinateConfig.addCoordinate(coordinate)
|
CoordinateConfig.addCoordinate(coordinate)
|
||||||
MessageUtils.sendSuccess(
|
player.sendSystemMessage(
|
||||||
player,
|
Component.literal(
|
||||||
"{$name} 좌표를 추가했습니다."
|
"$name 좌표를 추가했습니다."
|
||||||
|
)
|
||||||
|
.withStyle {
|
||||||
|
it.withColor(
|
||||||
|
ChatFormatting
|
||||||
|
.GOLD
|
||||||
|
)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
1
|
1
|
||||||
}
|
}
|
||||||
|
|
@ -142,17 +155,31 @@ object CoordinateCommand {
|
||||||
)
|
)
|
||||||
|
|
||||||
if (!CoordinateConfig.isExist(name)) {
|
if (!CoordinateConfig.isExist(name)) {
|
||||||
MessageUtils.sendError(
|
player.sendSystemMessage(
|
||||||
player,
|
Component.literal(
|
||||||
"{$name} 좌표가 존재하지 않습니다."
|
"$name 좌표가 존재하지 않습니다."
|
||||||
|
)
|
||||||
|
.withStyle {
|
||||||
|
it.withColor(
|
||||||
|
ChatFormatting
|
||||||
|
.RED
|
||||||
|
)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
return@executes 0
|
return@executes 0
|
||||||
}
|
}
|
||||||
|
|
||||||
CoordinateConfig.removeCoordinate(name)
|
CoordinateConfig.removeCoordinate(name)
|
||||||
MessageUtils.sendSuccess(
|
player.sendSystemMessage(
|
||||||
player,
|
Component.literal(
|
||||||
"{$name} 좌표를 제거했습니다."
|
"$name 좌표를 제거했습니다."
|
||||||
|
)
|
||||||
|
.withStyle {
|
||||||
|
it.withColor(
|
||||||
|
ChatFormatting
|
||||||
|
.GOLD
|
||||||
|
)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
1
|
1
|
||||||
}
|
}
|
||||||
|
|
@ -167,22 +194,12 @@ 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(customTitle).withStyle { it.withColor(0xFFFFFF) }
|
Component.literal("저장된 좌표 (${page + 1}/$totalPages)").withStyle {
|
||||||
|
it.withBold(true)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -190,13 +207,21 @@ object CoordinateCommand {
|
||||||
private fun teleportToCoordinate(player: ServerPlayer, name: String): Int {
|
private fun teleportToCoordinate(player: ServerPlayer, name: String): Int {
|
||||||
val coord = CoordinateConfig.getCoordinate(name)
|
val coord = CoordinateConfig.getCoordinate(name)
|
||||||
if (coord == null) {
|
if (coord == null) {
|
||||||
MessageUtils.sendError(player, "{$name} 좌표가 존재하지 않습니다.")
|
player.sendSystemMessage(
|
||||||
|
Component.literal("$name 좌표가 존재하지 않습니다.").withStyle {
|
||||||
|
it.withColor(ChatFormatting.RED)
|
||||||
|
}
|
||||||
|
)
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
val level = DimensionUtils.getLevelById(player.server, coord.dimension)
|
val level = DimensionUtils.getLevelById(player.server, coord.dimension)
|
||||||
if (level == null) {
|
if (level == null) {
|
||||||
MessageUtils.sendError(player, "존재하지 않는 차원입니다.")
|
player.sendSystemMessage(
|
||||||
|
Component.literal("존재하지 않는 차원입니다.").withStyle {
|
||||||
|
it.withColor(ChatFormatting.RED)
|
||||||
|
}
|
||||||
|
)
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -219,9 +244,11 @@ 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)
|
||||||
// 텔레포트 사운드 재생
|
player.sendSystemMessage(
|
||||||
SoundUtils.playTeleport(player)
|
Component.literal("$name (으)로 이동했습니다.").withStyle {
|
||||||
MessageUtils.sendSuccess(player, "{$name}(으)로 이동했습니다.")
|
it.withColor(ChatFormatting.GOLD)
|
||||||
|
}
|
||||||
|
)
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ package com.beemer.essentials.command
|
||||||
|
|
||||||
import com.beemer.essentials.config.PlayerConfig
|
import com.beemer.essentials.config.PlayerConfig
|
||||||
import com.beemer.essentials.nickname.NicknameDataStore
|
import com.beemer.essentials.nickname.NicknameDataStore
|
||||||
import com.beemer.essentials.util.MessageUtils
|
|
||||||
import com.mojang.brigadier.arguments.StringArgumentType
|
import com.mojang.brigadier.arguments.StringArgumentType
|
||||||
import java.util.Optional
|
import java.util.Optional
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
@ -31,6 +30,8 @@ object HeadCommand {
|
||||||
StringArgumentType.greedyString()
|
StringArgumentType.greedyString()
|
||||||
)
|
)
|
||||||
.suggests { _, builder ->
|
.suggests { _, builder ->
|
||||||
|
// 서버에 접속한 적 있는 플레이어만 제안
|
||||||
|
// 닉네임이 있으면 닉네임, 없으면 원래 이름
|
||||||
val suggestions =
|
val suggestions =
|
||||||
mutableListOf<String>()
|
mutableListOf<String>()
|
||||||
PlayerConfig.getAllPlayers()
|
PlayerConfig.getAllPlayers()
|
||||||
|
|
@ -92,7 +93,11 @@ object HeadCommand {
|
||||||
|
|
||||||
// 3. 서버에 접속한 적 없는 플레이어면 거부
|
// 3. 서버에 접속한 적 없는 플레이어면 거부
|
||||||
if (targetUuid == null || !PlayerConfig.isKnownPlayer(targetUuid)) {
|
if (targetUuid == null || !PlayerConfig.isKnownPlayer(targetUuid)) {
|
||||||
MessageUtils.sendError(player, "해당 플레이어는 서버에 접속한 적이 없습니다.")
|
player.sendSystemMessage(
|
||||||
|
Component.literal("해당 플레이어는 서버에 접속한 적이 없습니다.").withStyle {
|
||||||
|
it.withColor(ChatFormatting.RED)
|
||||||
|
}
|
||||||
|
)
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -125,13 +130,21 @@ object HeadCommand {
|
||||||
val added = player.inventory.add(headItem)
|
val added = player.inventory.add(headItem)
|
||||||
|
|
||||||
if (added) {
|
if (added) {
|
||||||
MessageUtils.sendSuccess(player, "{$displayName}의 머리가 지급되었습니다.")
|
player.sendSystemMessage(
|
||||||
|
Component.literal(displayName)
|
||||||
|
.withStyle { it.withColor(ChatFormatting.GREEN) }
|
||||||
|
.append(
|
||||||
|
Component.literal("의 머리가 지급되었습니다.").withStyle {
|
||||||
|
it.withColor(ChatFormatting.GOLD)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
// 인벤토리가 가득 찬 경우 바닥에 드랍
|
// 인벤토리가 가득 찬 경우 바닥에 드랍
|
||||||
player.drop(headItem, false)
|
player.drop(headItem, false)
|
||||||
MessageUtils.sendWarning(
|
player.sendSystemMessage(
|
||||||
player,
|
Component.literal("인벤토리가 가득 차서 ${displayName}의 머리를 바닥에 떨어뜨렸습니다.")
|
||||||
"인벤토리가 가득 차서 {$displayName}의 머리를 바닥에 떨어뜨렸습니다."
|
.withStyle { it.withColor(ChatFormatting.YELLOW) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,29 +1,13 @@
|
||||||
package com.beemer.essentials.command
|
package com.beemer.essentials.command
|
||||||
|
|
||||||
|
import net.minecraft.ChatFormatting
|
||||||
import net.minecraft.commands.Commands
|
import net.minecraft.commands.Commands
|
||||||
import net.minecraft.network.chat.Component
|
import net.minecraft.network.chat.Component
|
||||||
import net.minecraft.network.chat.TextColor
|
|
||||||
import net.minecraft.server.level.ServerPlayer
|
import net.minecraft.server.level.ServerPlayer
|
||||||
import net.neoforged.bus.api.SubscribeEvent
|
import net.neoforged.bus.api.SubscribeEvent
|
||||||
import net.neoforged.neoforge.event.RegisterCommandsEvent
|
import net.neoforged.neoforge.event.RegisterCommandsEvent
|
||||||
|
|
||||||
object HelpCommand {
|
object HelpCommand {
|
||||||
// 커스텀 색상 정의
|
|
||||||
private val COLOR_HEADER = TextColor.fromRgb(0xFFD700) // 골드
|
|
||||||
private val COLOR_SEPARATOR = TextColor.fromRgb(0x555555) // 어두운 회색
|
|
||||||
private val COLOR_COMMAND = TextColor.fromRgb(0xFFFFFF) // 흰색
|
|
||||||
private val COLOR_DESC = TextColor.fromRgb(0xAAAAAA) // 밝은 회색
|
|
||||||
private val COLOR_DASH = TextColor.fromRgb(0x666666) // 회색
|
|
||||||
|
|
||||||
// 카테고리별 색상
|
|
||||||
private val COLOR_COORDINATE = TextColor.fromRgb(0xFFE066) // 밝은 노랑
|
|
||||||
private val COLOR_SPAWN = TextColor.fromRgb(0x7FD88C) // 연두색
|
|
||||||
private val COLOR_TELEPORT = TextColor.fromRgb(0x7DD3FC) // 하늘색
|
|
||||||
private val COLOR_NICKNAME = TextColor.fromRgb(0xE879F9) // 보라색
|
|
||||||
private val COLOR_HEAD = TextColor.fromRgb(0xFBBF24) // 주황색
|
|
||||||
private val COLOR_WORKBENCH = TextColor.fromRgb(0xA3E635) // 라임색
|
|
||||||
private val COLOR_MENU = TextColor.fromRgb(0xF472B6) // 핑크색
|
|
||||||
|
|
||||||
@SubscribeEvent
|
@SubscribeEvent
|
||||||
fun onRegisterCommands(event: RegisterCommandsEvent) {
|
fun onRegisterCommands(event: RegisterCommandsEvent) {
|
||||||
listOf("도움말", "help", "essentials").forEach { command ->
|
listOf("도움말", "help", "essentials").forEach { command ->
|
||||||
|
|
@ -40,11 +24,15 @@ object HelpCommand {
|
||||||
private fun showHelp(player: ServerPlayer) {
|
private fun showHelp(player: ServerPlayer) {
|
||||||
val header =
|
val header =
|
||||||
Component.literal("══════════ ")
|
Component.literal("══════════ ")
|
||||||
.withStyle { it.withColor(COLOR_SEPARATOR) }
|
.withStyle { it.withColor(ChatFormatting.DARK_GRAY) }
|
||||||
.append(Component.literal("도움말").withStyle { it.withColor(COLOR_HEADER) })
|
.append(
|
||||||
|
Component.literal("도움말").withStyle {
|
||||||
|
it.withColor(ChatFormatting.GOLD).withBold(true)
|
||||||
|
}
|
||||||
|
)
|
||||||
.append(
|
.append(
|
||||||
Component.literal(" ══════════").withStyle {
|
Component.literal(" ══════════").withStyle {
|
||||||
it.withColor(COLOR_SEPARATOR)
|
it.withColor(ChatFormatting.DARK_GRAY)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -52,7 +40,7 @@ object HelpCommand {
|
||||||
player.sendSystemMessage(Component.empty())
|
player.sendSystemMessage(Component.empty())
|
||||||
|
|
||||||
// 좌표 관리
|
// 좌표 관리
|
||||||
sendCategory(player, "좌표 관리", COLOR_COORDINATE)
|
sendCategory(player, "좌표 관리", ChatFormatting.YELLOW)
|
||||||
sendCommand(player, "/좌표", "저장된 좌표 목록 (GUI)")
|
sendCommand(player, "/좌표", "저장된 좌표 목록 (GUI)")
|
||||||
sendCommand(player, "/좌표추가 <이름>", "현재 위치 저장")
|
sendCommand(player, "/좌표추가 <이름>", "현재 위치 저장")
|
||||||
sendCommand(player, "/좌표이동 <이름>", "해당 좌표로 이동")
|
sendCommand(player, "/좌표이동 <이름>", "해당 좌표로 이동")
|
||||||
|
|
@ -60,61 +48,66 @@ object HelpCommand {
|
||||||
player.sendSystemMessage(Component.empty())
|
player.sendSystemMessage(Component.empty())
|
||||||
|
|
||||||
// 스폰
|
// 스폰
|
||||||
sendCategory(player, "스폰", COLOR_SPAWN)
|
sendCategory(player, "스폰", ChatFormatting.GREEN)
|
||||||
sendCommand(player, "/스폰", "스폰으로 이동")
|
sendCommand(player, "/스폰", "스폰으로 이동")
|
||||||
sendCommand(player, "/스폰설정", "현재 위치를 스폰으로 설정")
|
sendCommand(player, "/스폰설정", "현재 위치를 스폰으로 설정")
|
||||||
sendCommand(player, "/스폰삭제", "커스텀 스폰 삭제")
|
sendCommand(player, "/스폰삭제", "커스텀 스폰 삭제")
|
||||||
player.sendSystemMessage(Component.empty())
|
player.sendSystemMessage(Component.empty())
|
||||||
|
|
||||||
// 텔레포트
|
// 텔레포트
|
||||||
sendCategory(player, "텔레포트", COLOR_TELEPORT)
|
sendCategory(player, "텔레포트", ChatFormatting.AQUA)
|
||||||
sendCommand(player, "/tpa", "플레이어 선택 (GUI)")
|
sendCommand(player, "/tpa", "플레이어 선택 (GUI)")
|
||||||
sendCommand(player, "/back", "이전 위치로 이동")
|
sendCommand(player, "/back", "이전 위치로 이동")
|
||||||
player.sendSystemMessage(Component.empty())
|
player.sendSystemMessage(Component.empty())
|
||||||
|
|
||||||
// 닉네임
|
// 닉네임
|
||||||
sendCategory(player, "닉네임", COLOR_NICKNAME)
|
sendCategory(player, "닉네임", ChatFormatting.LIGHT_PURPLE)
|
||||||
sendCommand(player, "/닉네임 변경 <닉네임>", "닉네임 설정")
|
sendCommand(player, "/닉네임 변경 <닉네임>", "닉네임 설정")
|
||||||
sendCommand(player, "/닉네임 초기화", "닉네임 초기화")
|
sendCommand(player, "/닉네임 초기화", "닉네임 초기화")
|
||||||
player.sendSystemMessage(Component.empty())
|
player.sendSystemMessage(Component.empty())
|
||||||
|
|
||||||
// 머리
|
// 머리
|
||||||
sendCategory(player, "머리", COLOR_HEAD)
|
sendCategory(player, "머리", ChatFormatting.GOLD)
|
||||||
sendCommand(player, "/머리", "내 머리 아이템 받기")
|
sendCommand(player, "/머리", "내 머리 아이템 받기")
|
||||||
sendCommand(player, "/머리 <닉네임>", "해당 플레이어 머리 받기")
|
sendCommand(player, "/머리 <닉네임>", "해당 플레이어 머리 받기")
|
||||||
player.sendSystemMessage(Component.empty())
|
|
||||||
|
|
||||||
// 제작대
|
|
||||||
sendCategory(player, "제작대", COLOR_WORKBENCH)
|
|
||||||
sendCommand(player, "/제작대", "제작대 열기")
|
|
||||||
player.sendSystemMessage(Component.empty())
|
|
||||||
|
|
||||||
// 메뉴
|
|
||||||
sendCategory(player, "메뉴", COLOR_MENU)
|
|
||||||
sendCommand(player, "/메뉴", "메뉴 GUI 열기")
|
|
||||||
sendCommand(player, "Shift + F", "메뉴 GUI 열기 (단축키)")
|
|
||||||
|
|
||||||
val footer =
|
val footer =
|
||||||
Component.literal("═══════════════════════════════").withStyle {
|
Component.literal("═══════════════════════════════").withStyle {
|
||||||
it.withColor(COLOR_SEPARATOR)
|
it.withColor(ChatFormatting.DARK_GRAY)
|
||||||
}
|
}
|
||||||
player.sendSystemMessage(footer)
|
player.sendSystemMessage(footer)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun sendCategory(player: ServerPlayer, name: String, color: TextColor) {
|
private fun sendCategory(player: ServerPlayer, name: String, color: ChatFormatting) {
|
||||||
val message =
|
val message =
|
||||||
Component.literal("▸ ")
|
Component.literal("▸ ")
|
||||||
.withStyle { it.withColor(color) }
|
.withStyle { it.withColor(color) }
|
||||||
.append(Component.literal(name).withStyle { it.withColor(color) })
|
.append(
|
||||||
|
Component.literal(name).withStyle {
|
||||||
|
it.withColor(color).withBold(true)
|
||||||
|
}
|
||||||
|
)
|
||||||
player.sendSystemMessage(message)
|
player.sendSystemMessage(message)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun sendCommand(player: ServerPlayer, cmd: String, desc: String) {
|
private fun sendCommand(player: ServerPlayer, cmd: String, desc: String) {
|
||||||
val message =
|
val message =
|
||||||
Component.literal(" ")
|
Component.literal(" ")
|
||||||
.append(Component.literal(cmd).withStyle { it.withColor(COLOR_COMMAND) })
|
.append(
|
||||||
.append(Component.literal(" - ").withStyle { it.withColor(COLOR_DASH) })
|
Component.literal(cmd).withStyle {
|
||||||
.append(Component.literal(desc).withStyle { it.withColor(COLOR_DESC) })
|
it.withColor(ChatFormatting.WHITE)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.append(
|
||||||
|
Component.literal(" - ").withStyle {
|
||||||
|
it.withColor(ChatFormatting.DARK_GRAY)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.append(
|
||||||
|
Component.literal(desc).withStyle {
|
||||||
|
it.withColor(ChatFormatting.GRAY)
|
||||||
|
}
|
||||||
|
)
|
||||||
player.sendSystemMessage(message)
|
player.sendSystemMessage(message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
package com.beemer.essentials.command
|
|
||||||
|
|
||||||
import com.beemer.essentials.gui.openEssentialsMenu
|
|
||||||
import net.minecraft.commands.Commands
|
|
||||||
import net.minecraft.server.level.ServerPlayer
|
|
||||||
import net.neoforged.bus.api.SubscribeEvent
|
|
||||||
import net.neoforged.neoforge.event.RegisterCommandsEvent
|
|
||||||
|
|
||||||
object MenuCommand {
|
|
||||||
@SubscribeEvent
|
|
||||||
fun onRegisterCommands(event: RegisterCommandsEvent) {
|
|
||||||
// /메뉴, /menu - 메뉴 GUI 열기
|
|
||||||
listOf("메뉴", "menu").forEach { command ->
|
|
||||||
event.dispatcher.register(
|
|
||||||
Commands.literal(command).executes { context ->
|
|
||||||
val player = context.source.entity as? ServerPlayer ?: return@executes 0
|
|
||||||
openEssentialsMenu(player)
|
|
||||||
1
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -3,9 +3,9 @@ package com.beemer.essentials.command
|
||||||
import com.beemer.essentials.config.PlayerConfig
|
import com.beemer.essentials.config.PlayerConfig
|
||||||
import com.beemer.essentials.util.CommandUtils
|
import com.beemer.essentials.util.CommandUtils
|
||||||
import com.beemer.essentials.util.DimensionUtils
|
import com.beemer.essentials.util.DimensionUtils
|
||||||
import com.beemer.essentials.util.MessageUtils
|
import net.minecraft.ChatFormatting
|
||||||
import com.beemer.essentials.util.SoundUtils
|
|
||||||
import net.minecraft.commands.Commands
|
import net.minecraft.commands.Commands
|
||||||
|
import net.minecraft.network.chat.Component
|
||||||
import net.neoforged.bus.api.SubscribeEvent
|
import net.neoforged.bus.api.SubscribeEvent
|
||||||
import net.neoforged.neoforge.event.RegisterCommandsEvent
|
import net.neoforged.neoforge.event.RegisterCommandsEvent
|
||||||
|
|
||||||
|
|
@ -23,9 +23,16 @@ object PlayerCommand {
|
||||||
val lastLocation =
|
val lastLocation =
|
||||||
info?.lastLocation
|
info?.lastLocation
|
||||||
?: run {
|
?: run {
|
||||||
MessageUtils.sendError(
|
player.sendSystemMessage(
|
||||||
player,
|
Component.literal(
|
||||||
"이전 위치가 존재하지 않습니다."
|
"이전 위치가 존재하지 않습니다."
|
||||||
|
)
|
||||||
|
.withStyle {
|
||||||
|
it.withColor(
|
||||||
|
ChatFormatting
|
||||||
|
.RED
|
||||||
|
)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
return@executes 0
|
return@executes 0
|
||||||
}
|
}
|
||||||
|
|
@ -36,9 +43,16 @@ object PlayerCommand {
|
||||||
lastLocation.dimension
|
lastLocation.dimension
|
||||||
)
|
)
|
||||||
?: run {
|
?: run {
|
||||||
MessageUtils.sendError(
|
player.sendSystemMessage(
|
||||||
player,
|
Component.literal(
|
||||||
"존재하지 않는 차원입니다."
|
"존재하지 않는 차원입니다."
|
||||||
|
)
|
||||||
|
.withStyle {
|
||||||
|
it.withColor(
|
||||||
|
ChatFormatting
|
||||||
|
.RED
|
||||||
|
)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
return@executes 0
|
return@executes 0
|
||||||
}
|
}
|
||||||
|
|
@ -51,9 +65,11 @@ object PlayerCommand {
|
||||||
player.yRot,
|
player.yRot,
|
||||||
player.xRot
|
player.xRot
|
||||||
)
|
)
|
||||||
// 텔레포트 사운드 재생
|
player.sendSystemMessage(
|
||||||
SoundUtils.playTeleport(player)
|
Component.literal("이전 위치로 이동했습니다.").withStyle {
|
||||||
MessageUtils.sendSuccess(player, "이전 위치로 이동했습니다.")
|
it.withColor(ChatFormatting.GOLD)
|
||||||
|
}
|
||||||
|
)
|
||||||
1
|
1
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,9 @@ package com.beemer.essentials.command
|
||||||
|
|
||||||
import com.beemer.essentials.config.ProtectFarmlandConfig
|
import com.beemer.essentials.config.ProtectFarmlandConfig
|
||||||
import com.beemer.essentials.util.CommandUtils
|
import com.beemer.essentials.util.CommandUtils
|
||||||
import com.beemer.essentials.util.MessageUtils
|
import net.minecraft.ChatFormatting
|
||||||
import net.minecraft.commands.Commands
|
import net.minecraft.commands.Commands
|
||||||
|
import net.minecraft.network.chat.Component
|
||||||
import net.neoforged.bus.api.SubscribeEvent
|
import net.neoforged.bus.api.SubscribeEvent
|
||||||
import net.neoforged.neoforge.event.RegisterCommandsEvent
|
import net.neoforged.neoforge.event.RegisterCommandsEvent
|
||||||
|
|
||||||
|
|
@ -18,9 +19,22 @@ object ProtectFarmlandCommand {
|
||||||
?: return@executes 0
|
?: return@executes 0
|
||||||
|
|
||||||
val enabled = ProtectFarmlandConfig.toggle()
|
val enabled = ProtectFarmlandConfig.toggle()
|
||||||
val status = if (enabled) "활성화" else "비활성화"
|
|
||||||
|
|
||||||
MessageUtils.sendSuccess(player, "밭 보호를 {$status}했습니다.")
|
player.sendSystemMessage(
|
||||||
|
Component.literal("밭 보호를 ")
|
||||||
|
.withStyle { it.withColor(ChatFormatting.GOLD) }
|
||||||
|
.append(
|
||||||
|
Component.literal(if (enabled) "활성화" else "비활성화")
|
||||||
|
.withStyle {
|
||||||
|
it.withColor(ChatFormatting.DARK_GREEN)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.append(
|
||||||
|
Component.literal("했습니다.").withStyle {
|
||||||
|
it.withColor(ChatFormatting.GOLD)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
1
|
1
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -5,132 +5,116 @@ import com.beemer.essentials.config.SpawnConfig
|
||||||
import com.beemer.essentials.data.Location
|
import com.beemer.essentials.data.Location
|
||||||
import com.beemer.essentials.util.CommandUtils
|
import com.beemer.essentials.util.CommandUtils
|
||||||
import com.beemer.essentials.util.DimensionUtils
|
import com.beemer.essentials.util.DimensionUtils
|
||||||
import com.beemer.essentials.util.MessageUtils
|
import net.minecraft.ChatFormatting
|
||||||
import com.beemer.essentials.util.SoundUtils
|
|
||||||
import net.minecraft.commands.Commands
|
import net.minecraft.commands.Commands
|
||||||
|
import net.minecraft.network.chat.Component
|
||||||
import net.neoforged.bus.api.SubscribeEvent
|
import net.neoforged.bus.api.SubscribeEvent
|
||||||
import net.neoforged.neoforge.event.RegisterCommandsEvent
|
import net.neoforged.neoforge.event.RegisterCommandsEvent
|
||||||
|
|
||||||
object SpawnCommand {
|
object SpawnCommand {
|
||||||
@SubscribeEvent
|
@SubscribeEvent
|
||||||
fun onRegisterCommands(event: RegisterCommandsEvent) {
|
fun onRegisterCommands(event: RegisterCommandsEvent) {
|
||||||
listOf("setspawn", "스폰설정").forEach { command ->
|
listOf("setspawn", "스폰설정").forEach { command ->
|
||||||
event.dispatcher.register(
|
event.dispatcher.register(
|
||||||
Commands.literal(command).executes { context ->
|
Commands.literal(command).executes { context ->
|
||||||
val player =
|
val player =
|
||||||
CommandUtils.getPlayerOrSendFailure(context.source)
|
CommandUtils.getPlayerOrSendFailure(context.source)
|
||||||
?: return@executes 0
|
?: return@executes 0
|
||||||
|
|
||||||
val exactPos = player.position()
|
val exactPos = player.position()
|
||||||
val blockPos = player.blockPosition()
|
val blockPos = player.blockPosition()
|
||||||
val dimension =
|
val dimensionId = player.level().dimension().location().toString()
|
||||||
player.level().dimension().location().toString()
|
val biomeId =
|
||||||
val biome =
|
player.level()
|
||||||
player.level()
|
.getBiome(blockPos)
|
||||||
.getBiome(blockPos)
|
.unwrapKey()
|
||||||
.unwrapKey()
|
.map { it.location().toString() }
|
||||||
.map { it.location().toString() }
|
.orElse("minecraft:plains")
|
||||||
.orElse("minecraft:plains")
|
|
||||||
|
|
||||||
val location =
|
val location =
|
||||||
Location(
|
Location(
|
||||||
dimension = dimension,
|
dimension = dimensionId,
|
||||||
biome = biome,
|
biome = biomeId,
|
||||||
x = exactPos.x,
|
x = exactPos.x,
|
||||||
y = exactPos.y,
|
y = exactPos.y,
|
||||||
z = exactPos.z
|
z = exactPos.z
|
||||||
)
|
)
|
||||||
|
|
||||||
SpawnConfig.setCustomSpawn(location)
|
SpawnConfig.setCustomSpawn(location)
|
||||||
MessageUtils.sendSuccess(player, "스폰 지점이 현재 위치로 설정되었습니다.")
|
|
||||||
1
|
player.sendSystemMessage(
|
||||||
|
Component.literal("스폰 지점이 현재 위치로 설정되었습니다.").withStyle {
|
||||||
|
it.withColor(ChatFormatting.GOLD)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
1
|
||||||
|
}
|
||||||
listOf("spawn", "스폰", "넴주").forEach { command ->
|
)
|
||||||
event.dispatcher.register(
|
|
||||||
Commands.literal(command).executes { context ->
|
|
||||||
val player =
|
|
||||||
CommandUtils.getPlayerOrSendFailure(context.source)
|
|
||||||
?: return@executes 0
|
|
||||||
|
|
||||||
val target =
|
|
||||||
SpawnConfig.getCustomSpawn()
|
|
||||||
?: SpawnConfig.getDefaultSpawn()
|
|
||||||
|
|
||||||
target?.let { t ->
|
|
||||||
val level =
|
|
||||||
DimensionUtils.getLevelById(
|
|
||||||
player.server,
|
|
||||||
t.dimension
|
|
||||||
)
|
|
||||||
|
|
||||||
level?.let { l ->
|
|
||||||
val exactPos = player.position()
|
|
||||||
val blockPos = player.blockPosition()
|
|
||||||
val currentDimension =
|
|
||||||
player.level()
|
|
||||||
.dimension()
|
|
||||||
.location()
|
|
||||||
.toString()
|
|
||||||
val currentBiome =
|
|
||||||
player.level()
|
|
||||||
.getBiome(blockPos)
|
|
||||||
.unwrapKey()
|
|
||||||
.map {
|
|
||||||
it.location()
|
|
||||||
.toString()
|
|
||||||
}
|
|
||||||
.orElse("minecraft:plains")
|
|
||||||
|
|
||||||
val currentLocation =
|
|
||||||
Location(
|
|
||||||
dimension =
|
|
||||||
currentDimension,
|
|
||||||
biome = currentBiome,
|
|
||||||
x = exactPos.x,
|
|
||||||
y = exactPos.y,
|
|
||||||
z = exactPos.z
|
|
||||||
)
|
|
||||||
|
|
||||||
PlayerConfig.recordLastLocation(
|
|
||||||
player,
|
|
||||||
currentLocation
|
|
||||||
)
|
|
||||||
player.teleportTo(
|
|
||||||
l,
|
|
||||||
t.x,
|
|
||||||
t.y,
|
|
||||||
t.z,
|
|
||||||
player.yRot,
|
|
||||||
player.xRot
|
|
||||||
)
|
|
||||||
// 텔레포트 사운드 재생
|
|
||||||
SoundUtils.playTeleport(player)
|
|
||||||
MessageUtils.sendSuccess(
|
|
||||||
player,
|
|
||||||
"스폰으로 이동했습니다."
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
1
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
listOf("delspawn", "스폰삭제").forEach { command ->
|
|
||||||
event.dispatcher.register(
|
|
||||||
Commands.literal(command).executes { context ->
|
|
||||||
val player =
|
|
||||||
CommandUtils.getPlayerOrSendFailure(context.source)
|
|
||||||
?: return@executes 0
|
|
||||||
|
|
||||||
SpawnConfig.removeCustomSpawn()
|
|
||||||
MessageUtils.sendInfo(player, "스폰 지점이 기본 스폰 지점으로 변경되었습니다.")
|
|
||||||
1
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
listOf("spawn", "스폰", "넴주").forEach { command ->
|
||||||
|
event.dispatcher.register(
|
||||||
|
Commands.literal(command).executes { context ->
|
||||||
|
val player =
|
||||||
|
CommandUtils.getPlayerOrSendFailure(context.source)
|
||||||
|
?: return@executes 0
|
||||||
|
|
||||||
|
val target = SpawnConfig.getCustomSpawn() ?: SpawnConfig.getDefaultSpawn()
|
||||||
|
|
||||||
|
target?.let { t ->
|
||||||
|
val level = DimensionUtils.getLevelById(player.server, t.dimension)
|
||||||
|
|
||||||
|
level?.let { l ->
|
||||||
|
val exactPos = player.position()
|
||||||
|
val blockPos = player.blockPosition()
|
||||||
|
val currentDimension =
|
||||||
|
player.level().dimension().location().toString()
|
||||||
|
val currentBiome =
|
||||||
|
player.level()
|
||||||
|
.getBiome(blockPos)
|
||||||
|
.unwrapKey()
|
||||||
|
.map { it.location().toString() }
|
||||||
|
.orElse("minecraft:plains")
|
||||||
|
|
||||||
|
val currentLocation =
|
||||||
|
Location(
|
||||||
|
dimension = currentDimension,
|
||||||
|
biome = currentBiome,
|
||||||
|
x = exactPos.x,
|
||||||
|
y = exactPos.y,
|
||||||
|
z = exactPos.z
|
||||||
|
)
|
||||||
|
|
||||||
|
PlayerConfig.recordLastLocation(player, currentLocation)
|
||||||
|
player.teleportTo(l, t.x, t.y, t.z, player.yRot, player.xRot)
|
||||||
|
player.sendSystemMessage(
|
||||||
|
Component.literal("스폰으로 이동했습니다.").withStyle {
|
||||||
|
it.withColor(ChatFormatting.GOLD)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
1
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
listOf("delspawn", "스폰삭제").forEach { command ->
|
||||||
|
event.dispatcher.register(
|
||||||
|
Commands.literal(command).executes { context ->
|
||||||
|
val player =
|
||||||
|
CommandUtils.getPlayerOrSendFailure(context.source)
|
||||||
|
?: return@executes 0
|
||||||
|
|
||||||
|
SpawnConfig.removeCustomSpawn()
|
||||||
|
player.sendSystemMessage(
|
||||||
|
Component.literal("스폰 지점이 기본 스폰 지점으로 변경되었습니다.").withStyle {
|
||||||
|
it.withColor(ChatFormatting.GOLD)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
1
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,6 @@ import com.beemer.essentials.gui.TeleportGui
|
||||||
import com.beemer.essentials.gui.TeleportGui.Companion.CONTAINER_SIZE
|
import com.beemer.essentials.gui.TeleportGui.Companion.CONTAINER_SIZE
|
||||||
import com.beemer.essentials.nickname.NicknameDataStore
|
import com.beemer.essentials.nickname.NicknameDataStore
|
||||||
import com.beemer.essentials.util.CommandUtils
|
import com.beemer.essentials.util.CommandUtils
|
||||||
import com.beemer.essentials.util.MessageUtils
|
|
||||||
import com.beemer.essentials.util.SoundUtils
|
|
||||||
import com.beemer.essentials.util.TranslationUtils
|
import com.beemer.essentials.util.TranslationUtils
|
||||||
import com.mojang.authlib.GameProfile
|
import com.mojang.authlib.GameProfile
|
||||||
import net.minecraft.ChatFormatting
|
import net.minecraft.ChatFormatting
|
||||||
|
|
@ -34,22 +32,13 @@ object TeleportCommand {
|
||||||
CommandUtils.getPlayerOrSendFailure(context.source)
|
CommandUtils.getPlayerOrSendFailure(context.source)
|
||||||
?: return@executes 0
|
?: return@executes 0
|
||||||
|
|
||||||
|
val container = SimpleContainer(9 * 3)
|
||||||
|
|
||||||
val targetPlayers =
|
val targetPlayers =
|
||||||
player.server.playerList.players.filter {
|
player.server.playerList.players.filter {
|
||||||
it != player
|
it != player
|
||||||
}
|
}
|
||||||
|
|
||||||
// 다른 플레이어가 없으면 에러 메시지 표시
|
|
||||||
if (targetPlayers.isEmpty()) {
|
|
||||||
MessageUtils.sendError(
|
|
||||||
player,
|
|
||||||
"현재 접속 중인 다른 플레이어가 없습니다."
|
|
||||||
)
|
|
||||||
return@executes 0
|
|
||||||
}
|
|
||||||
|
|
||||||
val container = SimpleContainer(9 * 3)
|
|
||||||
|
|
||||||
targetPlayers.take(CONTAINER_SIZE).forEachIndexed {
|
targetPlayers.take(CONTAINER_SIZE).forEachIndexed {
|
||||||
idx,
|
idx,
|
||||||
target ->
|
target ->
|
||||||
|
|
@ -57,9 +46,6 @@ object TeleportCommand {
|
||||||
container.setItem(idx, head)
|
container.setItem(idx, head)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 플레이어가 있을 때만 클릭 사운드 재생
|
|
||||||
SoundUtils.playClick(player)
|
|
||||||
|
|
||||||
player.openMenu(
|
player.openMenu(
|
||||||
SimpleMenuProvider(
|
SimpleMenuProvider(
|
||||||
{ windowId, inv, _ ->
|
{ windowId, inv, _ ->
|
||||||
|
|
|
||||||
|
|
@ -1,66 +0,0 @@
|
||||||
package com.beemer.essentials.command
|
|
||||||
|
|
||||||
import java.util.Optional
|
|
||||||
import java.util.function.BiFunction
|
|
||||||
import net.minecraft.ChatFormatting
|
|
||||||
import net.minecraft.commands.Commands
|
|
||||||
import net.minecraft.core.BlockPos
|
|
||||||
import net.minecraft.network.chat.Component
|
|
||||||
import net.minecraft.server.level.ServerPlayer
|
|
||||||
import net.minecraft.world.SimpleMenuProvider
|
|
||||||
import net.minecraft.world.entity.player.Player
|
|
||||||
import net.minecraft.world.inventory.ContainerLevelAccess
|
|
||||||
import net.minecraft.world.inventory.CraftingMenu
|
|
||||||
import net.minecraft.world.level.Level
|
|
||||||
import net.neoforged.bus.api.SubscribeEvent
|
|
||||||
import net.neoforged.neoforge.event.RegisterCommandsEvent
|
|
||||||
|
|
||||||
object WorkbenchCommand {
|
|
||||||
@SubscribeEvent
|
|
||||||
fun onRegisterCommands(event: RegisterCommandsEvent) {
|
|
||||||
// /제작대, /workbench - 제작대 GUI 열기
|
|
||||||
listOf("제작대", "workbench").forEach { command ->
|
|
||||||
event.dispatcher.register(
|
|
||||||
Commands.literal(command).executes { context ->
|
|
||||||
val player = context.source.entity as? ServerPlayer
|
|
||||||
if (player == null) {
|
|
||||||
context.source.sendFailure(
|
|
||||||
Component.literal("플레이어만 사용할 수 있는 명령어입니다.")
|
|
||||||
.withStyle(ChatFormatting.RED)
|
|
||||||
)
|
|
||||||
return@executes 0
|
|
||||||
}
|
|
||||||
|
|
||||||
openWorkbench(player)
|
|
||||||
1
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 항상 유효한 ContainerLevelAccess 생성 */
|
|
||||||
private fun createAlwaysValidAccess(level: Level, pos: BlockPos): ContainerLevelAccess {
|
|
||||||
return object : ContainerLevelAccess {
|
|
||||||
override fun <T : Any> evaluate(function: BiFunction<Level, BlockPos, T>): Optional<T> {
|
|
||||||
return Optional.ofNullable(function.apply(level, pos))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 플레이어에게 제작대 GUI 열기 */
|
|
||||||
private fun openWorkbench(player: ServerPlayer) {
|
|
||||||
val access = createAlwaysValidAccess(player.level(), player.blockPosition())
|
|
||||||
|
|
||||||
player.openMenu(
|
|
||||||
SimpleMenuProvider(
|
|
||||||
{ containerId, inventory, _ ->
|
|
||||||
object : CraftingMenu(containerId, inventory, access) {
|
|
||||||
// stillValid를 오버라이드하여 항상 true 반환 (거리 제한 없음)
|
|
||||||
override fun stillValid(player: Player): Boolean = true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Component.translatable("container.crafting")
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
package com.beemer.essentials.event
|
package com.beemer.essentials.event
|
||||||
|
|
||||||
import com.beemer.essentials.util.ChatUtils
|
import com.beemer.essentials.util.ChatUtils
|
||||||
import com.beemer.essentials.util.MessageUtils
|
import net.minecraft.network.chat.Component
|
||||||
import net.minecraft.server.level.ServerPlayer
|
import net.minecraft.server.level.ServerPlayer
|
||||||
import net.neoforged.bus.api.SubscribeEvent
|
import net.neoforged.bus.api.SubscribeEvent
|
||||||
import net.neoforged.neoforge.event.ServerChatEvent
|
import net.neoforged.neoforge.event.ServerChatEvent
|
||||||
|
|
@ -12,11 +12,13 @@ object ChatEvents {
|
||||||
val player = event.player as ServerPlayer
|
val player = event.player as ServerPlayer
|
||||||
val raw = event.rawText
|
val raw = event.rawText
|
||||||
|
|
||||||
if (raw.startsWith("/")) return
|
if (raw.startsWith("/"))
|
||||||
|
return
|
||||||
|
|
||||||
if (ChatUtils.hasIllegalCharacter(raw)) {
|
if (ChatUtils.hasIllegalCharacter(raw)) {
|
||||||
event.isCanceled = true
|
event.isCanceled = true
|
||||||
MessageUtils.sendError(player, "채팅에 허용되지 않는 문자가 포함되어 있습니다.")
|
player.sendSystemMessage(Component.literal("채팅에 허용되지 않는 문자가 포함되어 있습니다.")
|
||||||
|
.withStyle { it.withColor(net.minecraft.ChatFormatting.RED) })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,44 +0,0 @@
|
||||||
package com.beemer.essentials.event
|
|
||||||
|
|
||||||
import com.beemer.essentials.gui.openEssentialsMenu
|
|
||||||
import java.util.UUID
|
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
|
||||||
import net.minecraft.server.level.ServerPlayer
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shift + F (손 바꾸기) 패킷 감지하여 메뉴 GUI 열기
|
|
||||||
*
|
|
||||||
* 동작 방식:
|
|
||||||
* 1. 플레이어가 Shift를 누르고 있는 상태에서
|
|
||||||
* 2. F키(손 바꾸기)를 누르면
|
|
||||||
* 3. 손 바꾸기 패킷을 취소하고 메뉴 GUI를 엽니다
|
|
||||||
*
|
|
||||||
* Mixin으로 패킷을 가로채기 때문에 빈 손에서도 작동합니다.
|
|
||||||
*/
|
|
||||||
object MenuKeyHandler {
|
|
||||||
// 메뉴 열기 쿨다운 (밀리초 단위, 더블 감지 방지)
|
|
||||||
private val cooldowns = ConcurrentHashMap<UUID, Long>()
|
|
||||||
private const val COOLDOWN_MS = 500L
|
|
||||||
|
|
||||||
/** Mixin에서 호출 - 패킷 감지 시 메뉴 열기 */
|
|
||||||
fun openMenuFromPacket(player: ServerPlayer) {
|
|
||||||
if (isOnCooldown(player.uuid)) return
|
|
||||||
|
|
||||||
setCooldown(player.uuid)
|
|
||||||
openEssentialsMenu(player)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun isOnCooldown(uuid: UUID): Boolean {
|
|
||||||
val lastTime = cooldowns[uuid] ?: return false
|
|
||||||
return System.currentTimeMillis() - lastTime < COOLDOWN_MS
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setCooldown(uuid: UUID) {
|
|
||||||
cooldowns[uuid] = System.currentTimeMillis()
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 플레이어 로그아웃 시 상태 정리 */
|
|
||||||
fun cleanup(uuid: UUID) {
|
|
||||||
cooldowns.remove(uuid)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -5,8 +5,6 @@ import com.beemer.essentials.config.SpawnConfig
|
||||||
import com.beemer.essentials.data.Location
|
import com.beemer.essentials.data.Location
|
||||||
import com.beemer.essentials.util.ChatUtils
|
import com.beemer.essentials.util.ChatUtils
|
||||||
import com.beemer.essentials.util.DimensionUtils
|
import com.beemer.essentials.util.DimensionUtils
|
||||||
import com.beemer.essentials.util.SoundUtils
|
|
||||||
import net.minecraft.network.chat.Component
|
|
||||||
import net.minecraft.server.level.ServerPlayer
|
import net.minecraft.server.level.ServerPlayer
|
||||||
import net.neoforged.bus.api.SubscribeEvent
|
import net.neoforged.bus.api.SubscribeEvent
|
||||||
import net.neoforged.neoforge.event.entity.player.PlayerEvent
|
import net.neoforged.neoforge.event.entity.player.PlayerEvent
|
||||||
|
|
@ -37,70 +35,9 @@ object PlayerEvents {
|
||||||
val level = DimensionUtils.getLevelById(player.server, spawn.dimension) ?: return
|
val level = DimensionUtils.getLevelById(player.server, spawn.dimension) ?: return
|
||||||
|
|
||||||
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)
|
|
||||||
// 알림 사운드 재생
|
|
||||||
SoundUtils.playNotification(player)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 신규 플레이어 도움말 */
|
|
||||||
private fun sendWelcomeGuide(player: ServerPlayer) {
|
|
||||||
// 커스텀 색상 정의
|
|
||||||
val colorSeparator = net.minecraft.network.chat.TextColor.fromRgb(0x555555)
|
|
||||||
val colorWelcome = net.minecraft.network.chat.TextColor.fromRgb(0x7DD3FC)
|
|
||||||
val colorCategory = net.minecraft.network.chat.TextColor.fromRgb(0xFFE066)
|
|
||||||
val colorCommand = net.minecraft.network.chat.TextColor.fromRgb(0xFFFFFF)
|
|
||||||
val colorDesc = net.minecraft.network.chat.TextColor.fromRgb(0xAAAAAA)
|
|
||||||
|
|
||||||
val separator =
|
|
||||||
Component.literal("═══════════════════════════════").withStyle {
|
|
||||||
it.withColor(colorSeparator)
|
|
||||||
}
|
|
||||||
|
|
||||||
player.sendSystemMessage(Component.empty())
|
|
||||||
player.sendSystemMessage(separator)
|
|
||||||
player.sendSystemMessage(
|
|
||||||
Component.literal("🎉 서버에 오신 것을 환영합니다!").withStyle { it.withColor(colorWelcome) }
|
|
||||||
)
|
|
||||||
player.sendSystemMessage(Component.empty())
|
|
||||||
|
|
||||||
// 기본 명령어 안내
|
|
||||||
player.sendSystemMessage(
|
|
||||||
Component.literal("▸ 기본 명령어").withStyle { it.withColor(colorCategory) }
|
|
||||||
)
|
|
||||||
sendGuideItem(player, "/도움말", "명령어 목록 보기", colorCommand, colorDesc)
|
|
||||||
sendGuideItem(player, "/메뉴", "메뉴 GUI 열기", colorCommand, colorDesc)
|
|
||||||
sendGuideItem(player, "/스폰", "스폰으로 이동", colorCommand, colorDesc)
|
|
||||||
player.sendSystemMessage(Component.empty())
|
|
||||||
|
|
||||||
// 단축키 안내
|
|
||||||
player.sendSystemMessage(
|
|
||||||
Component.literal("▸ 단축키").withStyle { it.withColor(colorCategory) }
|
|
||||||
)
|
|
||||||
sendGuideItem(player, "Shift + F", "메뉴 GUI 열기", colorCommand, colorDesc)
|
|
||||||
|
|
||||||
player.sendSystemMessage(separator)
|
|
||||||
player.sendSystemMessage(Component.empty())
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun sendGuideItem(
|
|
||||||
player: ServerPlayer,
|
|
||||||
key: String,
|
|
||||||
desc: String,
|
|
||||||
colorKey: net.minecraft.network.chat.TextColor,
|
|
||||||
colorDesc: net.minecraft.network.chat.TextColor
|
|
||||||
) {
|
|
||||||
player.sendSystemMessage(
|
|
||||||
Component.literal(" ")
|
|
||||||
.append(Component.literal(key).withStyle { it.withColor(colorKey) })
|
|
||||||
.append(Component.literal(" - $desc").withStyle { it.withColor(colorDesc) })
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@SubscribeEvent
|
@SubscribeEvent
|
||||||
fun onPlayerLoggedOut(event: PlayerEvent.PlayerLoggedOutEvent) {
|
fun onPlayerLoggedOut(event: PlayerEvent.PlayerLoggedOutEvent) {
|
||||||
val player = event.entity as? ServerPlayer ?: return
|
val player = event.entity as? ServerPlayer ?: return
|
||||||
|
|
@ -117,22 +54,18 @@ object PlayerEvents {
|
||||||
|
|
||||||
val pos = oldPlayer.blockPosition()
|
val pos = oldPlayer.blockPosition()
|
||||||
val dimension = oldPlayer.level().dimension().location().toString()
|
val dimension = oldPlayer.level().dimension().location().toString()
|
||||||
val biome =
|
val biome = oldPlayer.level().getBiome(pos)
|
||||||
oldPlayer
|
.unwrapKey()
|
||||||
.level()
|
.map { it.location().toString() }
|
||||||
.getBiome(pos)
|
.orElse("minecraft:plains")
|
||||||
.unwrapKey()
|
|
||||||
.map { it.location().toString() }
|
|
||||||
.orElse("minecraft:plains")
|
|
||||||
|
|
||||||
val lastLoc =
|
val lastLoc = Location(
|
||||||
Location(
|
dimension = dimension,
|
||||||
dimension = dimension,
|
biome = biome,
|
||||||
biome = biome,
|
x = pos.x.toDouble(),
|
||||||
x = pos.x.toDouble(),
|
y = pos.y.toDouble(),
|
||||||
y = pos.y.toDouble(),
|
z = pos.z.toDouble()
|
||||||
z = pos.z.toDouble()
|
)
|
||||||
)
|
|
||||||
|
|
||||||
PlayerConfig.recordLastLocation(oldPlayer, lastLoc)
|
PlayerConfig.recordLastLocation(oldPlayer, lastLoc)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
package com.beemer.essentials.gui
|
package com.beemer.essentials.gui
|
||||||
|
|
||||||
import com.beemer.essentials.config.AntimobConfig
|
import com.beemer.essentials.config.AntimobConfig
|
||||||
import com.beemer.essentials.util.SoundUtils
|
|
||||||
import net.minecraft.ChatFormatting
|
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
|
||||||
|
|
@ -78,7 +77,6 @@ class AntimobGui(
|
||||||
) {
|
) {
|
||||||
if (player is ServerPlayer && slotId in 0 until CONTAINER_SIZE) {
|
if (player is ServerPlayer && slotId in 0 until CONTAINER_SIZE) {
|
||||||
if (glassSlotToMob.containsKey(slotId)) {
|
if (glassSlotToMob.containsKey(slotId)) {
|
||||||
playClickSound(player)
|
|
||||||
val mob = glassSlotToMob[slotId] ?: return
|
val mob = glassSlotToMob[slotId] ?: return
|
||||||
AntimobConfig.toggle(mob)
|
AntimobConfig.toggle(mob)
|
||||||
container.setItem(slotId, makeToggleGlass(mob))
|
container.setItem(slotId, makeToggleGlass(mob))
|
||||||
|
|
@ -96,10 +94,6 @@ class AntimobGui(
|
||||||
index: Int
|
index: Int
|
||||||
): ItemStack = ItemStack.EMPTY
|
): ItemStack = ItemStack.EMPTY
|
||||||
|
|
||||||
private fun playClickSound(player: ServerPlayer) {
|
|
||||||
SoundUtils.playClick(player)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun makeMobHead(mob: String): ItemStack {
|
private fun makeMobHead(mob: String): ItemStack {
|
||||||
val headItem = ItemStack(Items.PLAYER_HEAD)
|
val headItem = ItemStack(Items.PLAYER_HEAD)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,6 @@ import com.beemer.essentials.config.CoordinateConfig
|
||||||
import com.beemer.essentials.config.PlayerConfig
|
import com.beemer.essentials.config.PlayerConfig
|
||||||
import com.beemer.essentials.data.Location
|
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.SoundUtils
|
|
||||||
import com.beemer.essentials.util.TranslationUtils.translateBiome
|
import com.beemer.essentials.util.TranslationUtils.translateBiome
|
||||||
import com.beemer.essentials.util.TranslationUtils.translateDimension
|
import com.beemer.essentials.util.TranslationUtils.translateDimension
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
@ -31,7 +29,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 - 페이지 기능 포함 4줄(36개) 표시 + 6번째 줄에 이전/다음 버튼 */
|
/** 좌표 GUI - 페이지 기능 포함 5줄(45개) 표시 + 6번째 줄에 이전/다음 버튼 */
|
||||||
class Menu(
|
class Menu(
|
||||||
syncId: Int,
|
syncId: Int,
|
||||||
playerInv: Inventory,
|
playerInv: Inventory,
|
||||||
|
|
@ -40,9 +38,9 @@ class Menu(
|
||||||
) : AbstractContainerMenu(MenuType.GENERIC_9x6, syncId) {
|
) : AbstractContainerMenu(MenuType.GENERIC_9x6, syncId) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val ITEMS_PER_PAGE = 36 // 4줄 * 9
|
const val ITEMS_PER_PAGE = 45 // 5줄 * 9
|
||||||
val PREV_BUTTON_SLOTS = listOf(45, 46) // 6번째 줄 이전 버튼 2칸
|
const val PREV_BUTTON_SLOT = 45 // 6번째 줄 첫 번째
|
||||||
val NEXT_BUTTON_SLOTS = listOf(52, 53) // 6번째 줄 다음 버튼 2칸
|
const val NEXT_BUTTON_SLOT = 53 // 6번째 줄 마지막
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
|
@ -83,26 +81,24 @@ 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 in PREV_BUTTON_SLOTS &&
|
if (slotId == PREV_BUTTON_SLOT &&
|
||||||
tag.contains("action") &&
|
tag.contains("action") &&
|
||||||
tag.getString("action") == "prev"
|
tag.getString("action") == "prev"
|
||||||
) {
|
) {
|
||||||
if (currentPage > 0) {
|
if (currentPage > 0) {
|
||||||
playClickSound(player)
|
|
||||||
openPage(player, currentPage - 1)
|
openPage(player, currentPage - 1)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 다음 페이지 버튼 (슬롯 52, 53)
|
// 다음 페이지 버튼
|
||||||
if (slotId in NEXT_BUTTON_SLOTS &&
|
if (slotId == NEXT_BUTTON_SLOT &&
|
||||||
tag.contains("action") &&
|
tag.contains("action") &&
|
||||||
tag.getString("action") == "next"
|
tag.getString("action") == "next"
|
||||||
) {
|
) {
|
||||||
val totalPages = getTotalPages()
|
val totalPages = getTotalPages()
|
||||||
if (currentPage < totalPages - 1) {
|
if (currentPage < totalPages - 1) {
|
||||||
playClickSound(player)
|
|
||||||
openPage(player, currentPage + 1)
|
openPage(player, currentPage + 1)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
|
@ -113,7 +109,6 @@ class Menu(
|
||||||
val x = tag.getDouble("x")
|
val x = tag.getDouble("x")
|
||||||
val y = tag.getDouble("y")
|
val y = tag.getDouble("y")
|
||||||
val z = tag.getDouble("z")
|
val z = tag.getDouble("z")
|
||||||
val coordName = tag.getString("name")
|
|
||||||
val dimension =
|
val dimension =
|
||||||
ResourceLocation.tryParse(tag.getString("dimension"))?.let {
|
ResourceLocation.tryParse(tag.getString("dimension"))?.let {
|
||||||
ResourceKey.create(Registries.DIMENSION, it)
|
ResourceKey.create(Registries.DIMENSION, it)
|
||||||
|
|
@ -121,6 +116,7 @@ class Menu(
|
||||||
val level = dimension?.let { player.server.getLevel(it) }
|
val level = dimension?.let { player.server.getLevel(it) }
|
||||||
|
|
||||||
if (level != null) {
|
if (level != null) {
|
||||||
|
// 이전 위치 저장
|
||||||
val prevPos = player.blockPosition()
|
val prevPos = player.blockPosition()
|
||||||
PlayerConfig.recordLastLocation(
|
PlayerConfig.recordLastLocation(
|
||||||
player,
|
player,
|
||||||
|
|
@ -139,18 +135,20 @@ class Menu(
|
||||||
)
|
)
|
||||||
|
|
||||||
player.teleportTo(level, x, y, z, player.yRot, player.xRot)
|
player.teleportTo(level, x, y, z, player.yRot, player.xRot)
|
||||||
// 텔레포트 사운드 재생
|
player.sendSystemMessage(
|
||||||
SoundUtils.playTeleport(player)
|
Component.literal(tag.getString("name"))
|
||||||
MessageUtils.sendSuccess(player, "{$coordName}(으)로 이동했습니다.")
|
.withStyle { it.withColor(ChatFormatting.DARK_GREEN) }
|
||||||
|
.append(
|
||||||
|
Component.literal("(으)로 이동했습니다.").withStyle {
|
||||||
|
it.withColor(ChatFormatting.GOLD)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
player.closeContainer()
|
player.closeContainer()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun playClickSound(player: ServerPlayer) {
|
|
||||||
SoundUtils.playClick(player)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getTotalPages(): Int {
|
private fun getTotalPages(): Int {
|
||||||
val totalItems = CoordinateConfig.getCoordinates().size
|
val totalItems = CoordinateConfig.getCoordinates().size
|
||||||
return if (totalItems == 0) 1 else (totalItems + ITEMS_PER_PAGE - 1) / ITEMS_PER_PAGE
|
return if (totalItems == 0) 1 else (totalItems + ITEMS_PER_PAGE - 1) / ITEMS_PER_PAGE
|
||||||
|
|
@ -158,24 +156,12 @@ 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(customTitle).withStyle { it.withColor(0xFFFFFF) }
|
Component.literal("저장된 좌표 (${page + 1}/${getTotalPages()})").withStyle {
|
||||||
|
it.withBold(true)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -194,10 +180,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
|
||||||
|
|
||||||
// 좌표 아이템 추가 (2~5번째 줄, 슬롯 9-44)
|
// 좌표 아이템 추가 (5줄, 45개)
|
||||||
for (i in startIdx until endIdx) {
|
for (i in startIdx until endIdx) {
|
||||||
val coord = coordinates[i]
|
val coord = coordinates[i]
|
||||||
val item = ItemStack(Items.FILLED_MAP)
|
val item = ItemStack(Items.PAPER)
|
||||||
val tag =
|
val tag =
|
||||||
CompoundTag().apply {
|
CompoundTag().apply {
|
||||||
putString("name", coord.name)
|
putString("name", coord.name)
|
||||||
|
|
@ -221,131 +207,96 @@ 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 {
|
.withStyle { it.withColor(ChatFormatting.GOLD).withBold(true) }
|
||||||
it.withColor(ChatFormatting.GOLD).withBold(false).withItalic(false)
|
|
||||||
}
|
|
||||||
.append(
|
.append(
|
||||||
Component.literal(" ($creatorName)").withStyle {
|
Component.literal(" ($creatorName)").withStyle {
|
||||||
it.withColor(ChatFormatting.AQUA)
|
it.withColor(ChatFormatting.AQUA).withBold(false)
|
||||||
.withBold(false)
|
|
||||||
.withItalic(false)
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
Component.literal(coord.name).withStyle {
|
Component.literal(coord.name).withStyle {
|
||||||
it.withColor(ChatFormatting.GOLD).withBold(false).withItalic(false)
|
it.withColor(ChatFormatting.GOLD).withBold(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val loreList: List<Component> =
|
val loreList: List<Component> =
|
||||||
listOf(
|
listOf(
|
||||||
Component.literal("디멘션: ")
|
Component.literal("디멘션: ")
|
||||||
.withStyle {
|
.withStyle { it.withColor(ChatFormatting.DARK_GREEN) }
|
||||||
it.withColor(ChatFormatting.DARK_GREEN).withItalic(false)
|
|
||||||
}
|
|
||||||
.append(
|
.append(
|
||||||
Component.literal(translateDimension(coord.dimension))
|
Component.literal(translateDimension(coord.dimension))
|
||||||
.withStyle {
|
.withStyle { it.withColor(ChatFormatting.GRAY) }
|
||||||
it.withColor(ChatFormatting.GRAY)
|
|
||||||
.withItalic(false)
|
|
||||||
}
|
|
||||||
),
|
),
|
||||||
Component.literal("바이옴: ")
|
Component.literal("바이옴: ")
|
||||||
.withStyle {
|
.withStyle { it.withColor(ChatFormatting.DARK_GREEN) }
|
||||||
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).withItalic(false)
|
it.withColor(ChatFormatting.GRAY)
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
Component.literal("좌표: ")
|
Component.literal("좌표: ")
|
||||||
.withStyle {
|
.withStyle { it.withColor(ChatFormatting.DARK_GREEN) }
|
||||||
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 {
|
.withStyle { it.withColor(ChatFormatting.GRAY) }
|
||||||
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))
|
||||||
item.set(
|
container.setItem(i - startIdx, item)
|
||||||
DataComponents.CUSTOM_MODEL_DATA,
|
|
||||||
net.minecraft.world.item.component.CustomModelData(1)
|
|
||||||
)
|
|
||||||
container.setItem(9 + (i - startIdx), item) // 슬롯 9부터 시작 (1줄 건너뛰기)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1번째 줄 (슬롯 0-8) - 타이틀 영역 투명 처리
|
// 맨 아래줄 빈 공간을 회색 유리판으로 채우기 (슬롯 46-52)
|
||||||
val fillerItem = ItemStack(Items.GLASS_PANE)
|
val fillerItem = ItemStack(Items.GRAY_STAINED_GLASS_PANE)
|
||||||
fillerItem.set(DataComponents.CUSTOM_NAME, Component.empty())
|
|
||||||
fillerItem.set(DataComponents.LORE, ItemLore(listOf()))
|
|
||||||
fillerItem.set(
|
fillerItem.set(
|
||||||
DataComponents.CUSTOM_MODEL_DATA,
|
DataComponents.CUSTOM_NAME,
|
||||||
net.minecraft.world.item.component.CustomModelData(1)
|
Component.literal(" ").withStyle { it.withColor(ChatFormatting.DARK_GRAY) }
|
||||||
)
|
)
|
||||||
fillerItem.set(DataComponents.HIDE_TOOLTIP, net.minecraft.util.Unit.INSTANCE)
|
for (slot in 46..52) {
|
||||||
for (slot in 0..8) {
|
|
||||||
container.setItem(slot, fillerItem.copy())
|
container.setItem(slot, fillerItem.copy())
|
||||||
}
|
}
|
||||||
|
|
||||||
// 6번째 줄 가운데 슬롯 (47-51) - 투명 처리
|
// 이전 페이지 버튼 (슬롯 45)
|
||||||
for (slot in 47..51) {
|
|
||||||
container.setItem(slot, fillerItem.copy())
|
|
||||||
}
|
|
||||||
|
|
||||||
// 이전 페이지 버튼 (슬롯 45, 46) - 페이지 정보 툴팁
|
|
||||||
val prevItem = ItemStack(Items.GLASS_PANE)
|
|
||||||
val prevTag = CompoundTag().apply { putString("action", if (page > 0) "prev" else "") }
|
|
||||||
if (page > 0) {
|
if (page > 0) {
|
||||||
|
val prevItem = ItemStack(Items.LIME_STAINED_GLASS_PANE)
|
||||||
|
val prevTag = CompoundTag().apply { putString("action", "prev") }
|
||||||
prevItem.set(
|
prevItem.set(
|
||||||
DataComponents.CUSTOM_NAME,
|
DataComponents.CUSTOM_NAME,
|
||||||
Component.literal("이전 페이지 [${page + 1}/$totalPages]").withStyle {
|
Component.literal("이전 페이지").withStyle { it.withColor(ChatFormatting.YELLOW) }
|
||||||
it.withColor(0xA8D8D8).withItalic(false)
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
prevItem.set(DataComponents.CUSTOM_DATA, CustomData.of(prevTag))
|
||||||
|
container.setItem(Menu.PREV_BUTTON_SLOT, prevItem)
|
||||||
} else {
|
} else {
|
||||||
prevItem.set(DataComponents.CUSTOM_NAME, Component.empty())
|
val disabledItem = ItemStack(Items.RED_STAINED_GLASS_PANE)
|
||||||
prevItem.set(DataComponents.HIDE_TOOLTIP, net.minecraft.util.Unit.INSTANCE)
|
disabledItem.set(
|
||||||
|
DataComponents.CUSTOM_NAME,
|
||||||
|
Component.literal("이전 페이지").withStyle { it.withColor(ChatFormatting.YELLOW) }
|
||||||
|
)
|
||||||
|
container.setItem(Menu.PREV_BUTTON_SLOT, disabledItem)
|
||||||
}
|
}
|
||||||
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) - 페이지 정보 툴팁
|
// 다음 페이지 버튼 (슬롯 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("다음 페이지 [${page + 1}/$totalPages]").withStyle {
|
Component.literal("다음 페이지").withStyle { it.withColor(ChatFormatting.YELLOW) }
|
||||||
it.withColor(0xA8D8D8).withItalic(false)
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
nextItem.set(DataComponents.CUSTOM_DATA, CustomData.of(nextTag))
|
||||||
|
container.setItem(Menu.NEXT_BUTTON_SLOT, nextItem)
|
||||||
} else {
|
} else {
|
||||||
nextItem.set(DataComponents.CUSTOM_NAME, Component.empty())
|
val disabledItem = ItemStack(Items.RED_STAINED_GLASS_PANE)
|
||||||
nextItem.set(DataComponents.HIDE_TOOLTIP, net.minecraft.util.Unit.INSTANCE)
|
disabledItem.set(
|
||||||
|
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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,268 +0,0 @@
|
||||||
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) }
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
@ -2,9 +2,10 @@ package com.beemer.essentials.gui
|
||||||
|
|
||||||
import com.beemer.essentials.config.PlayerConfig
|
import com.beemer.essentials.config.PlayerConfig
|
||||||
import com.beemer.essentials.data.Location
|
import com.beemer.essentials.data.Location
|
||||||
|
import com.beemer.essentials.data.Player
|
||||||
import com.beemer.essentials.nickname.NicknameDataStore
|
import com.beemer.essentials.nickname.NicknameDataStore
|
||||||
import com.beemer.essentials.util.MessageUtils
|
import net.minecraft.ChatFormatting
|
||||||
import com.beemer.essentials.util.SoundUtils
|
import net.minecraft.network.chat.Component
|
||||||
import net.minecraft.server.level.ServerPlayer
|
import net.minecraft.server.level.ServerPlayer
|
||||||
import net.minecraft.world.Container
|
import net.minecraft.world.Container
|
||||||
import net.minecraft.world.entity.player.Inventory
|
import net.minecraft.world.entity.player.Inventory
|
||||||
|
|
@ -20,135 +21,140 @@ class TeleportGui(
|
||||||
val container: Container,
|
val container: Container,
|
||||||
val viewer: ServerPlayer
|
val viewer: ServerPlayer
|
||||||
) : AbstractContainerMenu(MenuType.GENERIC_9x3, syncId) {
|
) : AbstractContainerMenu(MenuType.GENERIC_9x3, syncId) {
|
||||||
companion object {
|
companion object {
|
||||||
const val CONTAINER_COLUMNS = 9
|
const val CONTAINER_COLUMNS = 9
|
||||||
const val CONTAINER_ROWS = 3
|
const val CONTAINER_ROWS = 3
|
||||||
const val CONTAINER_SIZE = CONTAINER_COLUMNS * CONTAINER_ROWS
|
const val CONTAINER_SIZE = CONTAINER_COLUMNS * CONTAINER_ROWS
|
||||||
|
|
||||||
const val SLOT_SIZE = 18
|
const val SLOT_SIZE = 18
|
||||||
const val LEFT_PADDING = 8
|
const val LEFT_PADDING = 8
|
||||||
const val TOP_PADDING = 18
|
const val TOP_PADDING = 18
|
||||||
|
|
||||||
const val PLAYER_INV_TOP = 86
|
const val PLAYER_INV_TOP = 86
|
||||||
const val HOTBAR_Y = 144
|
const val HOTBAR_Y = 144
|
||||||
|
}
|
||||||
|
|
||||||
|
private val targetPlayers =
|
||||||
|
viewer.server
|
||||||
|
.playerList
|
||||||
|
.players
|
||||||
|
.filterIsInstance<ServerPlayer>()
|
||||||
|
.filter { it.uuid != viewer.uuid }
|
||||||
|
.map { it.uuid }
|
||||||
|
|
||||||
|
init {
|
||||||
|
for (i in 0 until CONTAINER_SIZE) {
|
||||||
|
val x = LEFT_PADDING + (i % CONTAINER_COLUMNS) * SLOT_SIZE
|
||||||
|
val y = TOP_PADDING + (i / CONTAINER_COLUMNS) * SLOT_SIZE
|
||||||
|
addSlot(
|
||||||
|
object : Slot(container, i, x, y) {
|
||||||
|
override fun mayPickup(
|
||||||
|
player: net.minecraft.world.entity.player.Player
|
||||||
|
): Boolean = false
|
||||||
|
override fun mayPlace(stack: ItemStack): Boolean = false
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val targetPlayers =
|
for (row in 0 until 3) {
|
||||||
viewer.server
|
for (col in 0 until 9) {
|
||||||
.playerList
|
val index = col + row * 9 + 9
|
||||||
.players
|
val x = LEFT_PADDING + col * SLOT_SIZE
|
||||||
.filterIsInstance<ServerPlayer>()
|
val y = PLAYER_INV_TOP + row * SLOT_SIZE
|
||||||
.filter { it.uuid != viewer.uuid }
|
addSlot(Slot(playerInv, index, x, y))
|
||||||
.map { it.uuid }
|
}
|
||||||
|
}
|
||||||
|
|
||||||
init {
|
for (col in 0 until 9) {
|
||||||
for (i in 0 until CONTAINER_SIZE) {
|
val x = LEFT_PADDING + col * SLOT_SIZE
|
||||||
val x = LEFT_PADDING + (i % CONTAINER_COLUMNS) * SLOT_SIZE
|
val y = HOTBAR_Y
|
||||||
val y = TOP_PADDING + (i / CONTAINER_COLUMNS) * SLOT_SIZE
|
addSlot(Slot(playerInv, col, x, y))
|
||||||
addSlot(
|
}
|
||||||
object : Slot(container, i, x, y) {
|
}
|
||||||
override fun mayPickup(
|
|
||||||
player: net.minecraft.world.entity.player.Player
|
override fun clicked(
|
||||||
): Boolean = false
|
slotId: Int,
|
||||||
override fun mayPlace(stack: ItemStack): Boolean = false
|
button: Int,
|
||||||
}
|
clickType: ClickType,
|
||||||
|
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)
|
||||||
|
|
||||||
for (row in 0 until 3) {
|
val targetLevel = currentTargetPlayer.serverLevel()
|
||||||
for (col in 0 until 9) {
|
|
||||||
val index = col + row * 9 + 9
|
|
||||||
val x = LEFT_PADDING + col * SLOT_SIZE
|
|
||||||
val y = PLAYER_INV_TOP + row * SLOT_SIZE
|
|
||||||
addSlot(Slot(playerInv, index, x, y))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (col in 0 until 9) {
|
player.teleportTo(
|
||||||
val x = LEFT_PADDING + col * SLOT_SIZE
|
targetLevel,
|
||||||
val y = HOTBAR_Y
|
currentTargetPlayer.x,
|
||||||
addSlot(Slot(playerInv, col, x, y))
|
currentTargetPlayer.y,
|
||||||
}
|
currentTargetPlayer.z,
|
||||||
}
|
currentTargetPlayer.yRot,
|
||||||
|
currentTargetPlayer.xRot
|
||||||
|
)
|
||||||
|
|
||||||
override fun clicked(
|
// 닉네임이 있으면 닉네임 사용
|
||||||
slotId: Int,
|
val targetName =
|
||||||
button: Int,
|
NicknameDataStore.getNickname(currentTargetPlayer.uuid)
|
||||||
clickType: ClickType,
|
?: currentTargetPlayer.gameProfile.name
|
||||||
player: net.minecraft.world.entity.player.Player
|
val playerName =
|
||||||
) {
|
NicknameDataStore.getNickname(player.uuid) ?: player.gameProfile.name
|
||||||
if (slotId in 0 until CONTAINER_SIZE && player is ServerPlayer) {
|
|
||||||
// 해당 슬롯에 플레이어가 있는지 확인
|
|
||||||
val currentTargetPlayer =
|
|
||||||
targetPlayers.getOrNull(slotId)?.let {
|
|
||||||
player.server.playerList.getPlayer(it)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 플레이어가 없으면 무시 (빈 공간 클릭)
|
player.sendSystemMessage(
|
||||||
if (currentTargetPlayer == null) {
|
Component.literal(targetName)
|
||||||
return
|
.withStyle { it.withColor(ChatFormatting.AQUA) }
|
||||||
}
|
.append(
|
||||||
|
Component.literal("님에게 텔레포트 했습니다.").withStyle {
|
||||||
val prevPos = player.blockPosition()
|
it.withColor(ChatFormatting.GOLD)
|
||||||
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)
|
)
|
||||||
|
currentTargetPlayer.sendSystemMessage(
|
||||||
|
Component.literal(playerName)
|
||||||
|
.withStyle { it.withColor(ChatFormatting.AQUA) }
|
||||||
|
.append(
|
||||||
|
Component.literal("님이 당신에게 텔레포트 했습니다.").withStyle {
|
||||||
|
it.withColor(ChatFormatting.GOLD)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
player.sendSystemMessage(
|
||||||
|
Component.literal("해당 플레이어는 현재 오프라인입니다.").withStyle {
|
||||||
|
it.withColor(ChatFormatting.RED)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
val targetLevel = currentTargetPlayer.serverLevel()
|
player.closeContainer()
|
||||||
|
return
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
super.clicked(slotId, button, clickType, player)
|
||||||
|
}
|
||||||
|
|
||||||
private fun playClickSound(player: ServerPlayer) {
|
override fun stillValid(player: net.minecraft.world.entity.player.Player): Boolean = true
|
||||||
SoundUtils.playTeleport(player)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun stillValid(player: net.minecraft.world.entity.player.Player): Boolean = true
|
override fun quickMoveStack(
|
||||||
|
player: net.minecraft.world.entity.player.Player,
|
||||||
override fun quickMoveStack(
|
index: Int
|
||||||
player: net.minecraft.world.entity.player.Player,
|
): ItemStack = ItemStack.EMPTY
|
||||||
index: Int
|
|
||||||
): ItemStack = ItemStack.EMPTY
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
package com.beemer.essentials.nickname
|
package com.beemer.essentials.nickname
|
||||||
|
|
||||||
import com.beemer.essentials.util.MessageUtils
|
|
||||||
import com.mojang.brigadier.arguments.StringArgumentType
|
import com.mojang.brigadier.arguments.StringArgumentType
|
||||||
|
import net.minecraft.ChatFormatting
|
||||||
import net.minecraft.commands.Commands
|
import net.minecraft.commands.Commands
|
||||||
|
import net.minecraft.network.chat.Component
|
||||||
import net.minecraft.server.level.ServerPlayer
|
import net.minecraft.server.level.ServerPlayer
|
||||||
import net.neoforged.bus.api.SubscribeEvent
|
import net.neoforged.bus.api.SubscribeEvent
|
||||||
import net.neoforged.neoforge.event.RegisterCommandsEvent
|
import net.neoforged.neoforge.event.RegisterCommandsEvent
|
||||||
|
|
@ -28,6 +29,7 @@ object NicknameCommand {
|
||||||
.entity as?
|
.entity as?
|
||||||
ServerPlayer
|
ServerPlayer
|
||||||
?: return@executes 0
|
?: return@executes 0
|
||||||
|
|
||||||
val nickname =
|
val nickname =
|
||||||
StringArgumentType
|
StringArgumentType
|
||||||
.getString(
|
.getString(
|
||||||
|
|
@ -44,6 +46,7 @@ object NicknameCommand {
|
||||||
val player =
|
val player =
|
||||||
context.source.entity as? ServerPlayer
|
context.source.entity as? ServerPlayer
|
||||||
?: return@executes 0
|
?: return@executes 0
|
||||||
|
|
||||||
executeReset(player)
|
executeReset(player)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
@ -66,6 +69,7 @@ object NicknameCommand {
|
||||||
.entity as?
|
.entity as?
|
||||||
ServerPlayer
|
ServerPlayer
|
||||||
?: return@executes 0
|
?: return@executes 0
|
||||||
|
|
||||||
val nickname =
|
val nickname =
|
||||||
StringArgumentType
|
StringArgumentType
|
||||||
.getString(
|
.getString(
|
||||||
|
|
@ -82,6 +86,7 @@ object NicknameCommand {
|
||||||
val player =
|
val player =
|
||||||
context.source.entity as? ServerPlayer
|
context.source.entity as? ServerPlayer
|
||||||
?: return@executes 0
|
?: return@executes 0
|
||||||
|
|
||||||
executeReset(player)
|
executeReset(player)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
@ -91,41 +96,65 @@ object NicknameCommand {
|
||||||
private fun executeSet(player: ServerPlayer, nickname: String): Int {
|
private fun executeSet(player: ServerPlayer, nickname: String): Int {
|
||||||
// 유효성 검사: 길이
|
// 유효성 검사: 길이
|
||||||
if (nickname.length < 2 || nickname.length > 16) {
|
if (nickname.length < 2 || nickname.length > 16) {
|
||||||
MessageUtils.sendError(player, "닉네임은 2~16자 사이여야 합니다.")
|
player.sendSystemMessage(
|
||||||
return 0
|
Component.literal("닉네임은 2~16자 사이여야 합니다.").withStyle {
|
||||||
}
|
it.withColor(ChatFormatting.RED)
|
||||||
|
}
|
||||||
// 유효성 검사: 현재 닉네임과 동일
|
)
|
||||||
val currentNickname = NicknameDataStore.getNickname(player.uuid)
|
|
||||||
if (currentNickname == nickname) {
|
|
||||||
MessageUtils.sendError(player, "현재 사용 중인 닉네임과 동일합니다.")
|
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// 유효성 검사: 중복
|
// 유효성 검사: 중복
|
||||||
if (NicknameDataStore.isNicknameTaken(nickname, player.uuid)) {
|
if (NicknameDataStore.isNicknameTaken(nickname, player.uuid)) {
|
||||||
MessageUtils.sendError(player, "이미 사용 중인 닉네임입니다.")
|
player.sendSystemMessage(
|
||||||
|
Component.literal("이미 사용 중인 닉네임입니다.").withStyle {
|
||||||
|
it.withColor(ChatFormatting.RED)
|
||||||
|
}
|
||||||
|
)
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// 닉네임 저장 및 적용
|
// 닉네임 저장 및 적용 (gameProfile.name = 실제 마인크래프트 이름)
|
||||||
NicknameDataStore.setNickname(player.uuid, player.gameProfile.name, nickname)
|
NicknameDataStore.setNickname(player.uuid, player.gameProfile.name, nickname)
|
||||||
NicknameManager.applyNickname(player, nickname)
|
NicknameManager.applyNickname(player, nickname)
|
||||||
|
|
||||||
MessageUtils.sendSuccess(player, "닉네임이 {$nickname}(으)로 변경되었습니다.")
|
player.sendSystemMessage(
|
||||||
|
Component.literal("닉네임이 ")
|
||||||
|
.withStyle { it.withColor(ChatFormatting.GOLD) }
|
||||||
|
.append(
|
||||||
|
Component.literal(nickname).withStyle {
|
||||||
|
it.withColor(ChatFormatting.AQUA)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.append(
|
||||||
|
Component.literal("(으)로 변경되었습니다.").withStyle {
|
||||||
|
it.withColor(ChatFormatting.GOLD)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun executeReset(player: ServerPlayer): Int {
|
private fun executeReset(player: ServerPlayer): Int {
|
||||||
if (!NicknameDataStore.hasNickname(player.uuid)) {
|
if (!NicknameDataStore.hasNickname(player.uuid)) {
|
||||||
MessageUtils.sendError(player, "설정된 닉네임이 없습니다.")
|
player.sendSystemMessage(
|
||||||
|
Component.literal("설정된 닉네임이 없습니다.").withStyle {
|
||||||
|
it.withColor(ChatFormatting.RED)
|
||||||
|
}
|
||||||
|
)
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
NicknameDataStore.removeNickname(player.uuid)
|
NicknameDataStore.removeNickname(player.uuid)
|
||||||
NicknameManager.removeNickname(player)
|
NicknameManager.removeNickname(player)
|
||||||
|
|
||||||
MessageUtils.sendSuccess(player, "닉네임이 초기화되었습니다.")
|
player.sendSystemMessage(
|
||||||
|
Component.literal("닉네임이 초기화되었습니다.").withStyle {
|
||||||
|
it.withColor(ChatFormatting.GOLD)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
package com.beemer.essentials.util
|
|
||||||
|
|
||||||
import com.beemer.essentials.data.Location
|
|
||||||
import net.minecraft.server.level.ServerPlayer
|
|
||||||
|
|
||||||
/** 위치 관련 유틸리티 플레이어 위치를 Location 객체로 변환하는 공통 로직 */
|
|
||||||
object LocationUtils {
|
|
||||||
/** 플레이어의 현재 위치를 Location 객체로 변환 */
|
|
||||||
fun fromPlayer(player: ServerPlayer): Location {
|
|
||||||
val pos = player.position()
|
|
||||||
val blockPos = player.blockPosition()
|
|
||||||
val level = player.level()
|
|
||||||
|
|
||||||
val dimension = level.dimension().location().toString()
|
|
||||||
val biome =
|
|
||||||
level.getBiome(blockPos)
|
|
||||||
.unwrapKey()
|
|
||||||
.map { it.location().toString() }
|
|
||||||
.orElse("minecraft:plains")
|
|
||||||
|
|
||||||
return Location(dimension = dimension, biome = biome, x = pos.x, y = pos.y, z = pos.z)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,125 +0,0 @@
|
||||||
package com.beemer.essentials.util
|
|
||||||
|
|
||||||
import net.minecraft.network.chat.Component
|
|
||||||
import net.minecraft.network.chat.MutableComponent
|
|
||||||
import net.minecraft.network.chat.Style
|
|
||||||
import net.minecraft.network.chat.TextColor
|
|
||||||
import net.minecraft.server.level.ServerPlayer
|
|
||||||
|
|
||||||
/** 메시지 스타일 유틸리티 기본 텍스트: 밝은 청록색 (#A8D8D8) 강조 텍스트: 흰색 (#FFFFFF) */
|
|
||||||
object MessageUtils {
|
|
||||||
|
|
||||||
// 기본 색상 (더 밝게)
|
|
||||||
private const val BASE_COLOR = 0xA8D8D8 // 밝은 청록색 (기본 텍스트)
|
|
||||||
private const val ACCENT_COLOR = 0xFFFFFF // 흰색 (강조)
|
|
||||||
private const val ERROR_COLOR = 0xFF8888 // 밝은 빨간색 (오류)
|
|
||||||
private const val WARNING_COLOR = 0xFFE066 // 밝은 노란색 (경고)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 기본 스타일 메시지 생성
|
|
||||||
* @param text 텍스트 (강조 부분은 {중괄호}로 감싸기) 예: "스폰으로 이동했습니다." 또는 "{비머}님에게 텔레포트 요청을 보냈습니다."
|
|
||||||
*/
|
|
||||||
fun styled(text: String): MutableComponent {
|
|
||||||
return parseStyledText(text, BASE_COLOR, ACCENT_COLOR)
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 성공 메시지 (아이콘 없이 기본 스타일) */
|
|
||||||
fun success(text: String): MutableComponent {
|
|
||||||
return styled(text)
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 정보 메시지 (아이콘 없이 기본 스타일) */
|
|
||||||
fun info(text: String): MutableComponent {
|
|
||||||
return styled(text)
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 오류 메시지 (빨간색 스타일) */
|
|
||||||
fun error(text: String): MutableComponent {
|
|
||||||
return parseStyledText(text, ERROR_COLOR, ACCENT_COLOR)
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 경고 메시지 (노란색 스타일) */
|
|
||||||
fun warning(text: String): MutableComponent {
|
|
||||||
return parseStyledText(text, WARNING_COLOR, ACCENT_COLOR)
|
|
||||||
}
|
|
||||||
|
|
||||||
// === 플레이어에게 직접 전송 ===
|
|
||||||
|
|
||||||
fun sendSuccess(player: ServerPlayer, text: String) {
|
|
||||||
player.sendSystemMessage(success(text))
|
|
||||||
}
|
|
||||||
|
|
||||||
fun sendInfo(player: ServerPlayer, text: String) {
|
|
||||||
player.sendSystemMessage(info(text))
|
|
||||||
}
|
|
||||||
|
|
||||||
fun sendError(player: ServerPlayer, text: String) {
|
|
||||||
player.sendSystemMessage(error(text))
|
|
||||||
SoundUtils.playError(player)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun sendWarning(player: ServerPlayer, text: String) {
|
|
||||||
player.sendSystemMessage(warning(text))
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 알림 메시지 (사운드 포함) - 서버 접속 시 등 */
|
|
||||||
fun sendNotification(player: ServerPlayer, text: String) {
|
|
||||||
player.sendSystemMessage(info(text))
|
|
||||||
SoundUtils.playNotification(player)
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 스타일 텍스트 파싱 {중괄호} 안의 텍스트는 강조 색상으로 표시 */
|
|
||||||
private fun parseStyledText(text: String, baseColor: Int, accentColor: Int): MutableComponent {
|
|
||||||
val result: MutableComponent = Component.empty()
|
|
||||||
var i = 0
|
|
||||||
val sb = StringBuilder()
|
|
||||||
|
|
||||||
while (i < text.length) {
|
|
||||||
when {
|
|
||||||
text[i] == '{' -> {
|
|
||||||
// 이전 텍스트 추가 (기본 색상)
|
|
||||||
if (sb.isNotEmpty()) {
|
|
||||||
result.append(
|
|
||||||
Component.literal(sb.toString())
|
|
||||||
.withStyle(
|
|
||||||
Style.EMPTY.withColor(TextColor.fromRgb(baseColor))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
sb.clear()
|
|
||||||
}
|
|
||||||
// 강조 텍스트 찾기
|
|
||||||
val endIdx = text.indexOf('}', i)
|
|
||||||
if (endIdx != -1) {
|
|
||||||
val accentText = text.substring(i + 1, endIdx)
|
|
||||||
result.append(
|
|
||||||
Component.literal(accentText)
|
|
||||||
.withStyle(
|
|
||||||
Style.EMPTY.withColor(
|
|
||||||
TextColor.fromRgb(accentColor)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
i = endIdx + 1
|
|
||||||
} else {
|
|
||||||
sb.append(text[i])
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
sb.append(text[i])
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 남은 텍스트 추가
|
|
||||||
if (sb.isNotEmpty()) {
|
|
||||||
result.append(
|
|
||||||
Component.literal(sb.toString())
|
|
||||||
.withStyle(Style.EMPTY.withColor(TextColor.fromRgb(baseColor)))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,44 +0,0 @@
|
||||||
package com.beemer.essentials.util
|
|
||||||
|
|
||||||
import net.minecraft.resources.ResourceLocation
|
|
||||||
import net.minecraft.server.level.ServerPlayer
|
|
||||||
import net.minecraft.sounds.SoundEvent
|
|
||||||
import net.minecraft.sounds.SoundSource
|
|
||||||
|
|
||||||
/** 사운드 재생 유틸리티 모든 커스텀 사운드 재생 로직을 중앙화 */
|
|
||||||
object SoundUtils {
|
|
||||||
// 기본 볼륨 (20%)
|
|
||||||
private const val DEFAULT_VOLUME = 0.2f
|
|
||||||
private const val DEFAULT_PITCH = 1.0f
|
|
||||||
|
|
||||||
/** 클릭 사운드 - GUI 버튼 클릭 시 */
|
|
||||||
fun playClick(player: ServerPlayer) {
|
|
||||||
playSound(player, "minecraft:custom.click")
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 텔레포트 사운드 - 스폰, 좌표이동, TPA, back 시 */
|
|
||||||
fun playTeleport(player: ServerPlayer) {
|
|
||||||
playSound(player, "minecraft:custom.teleport")
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 에러 사운드 - 오류 메시지 시 */
|
|
||||||
fun playError(player: ServerPlayer) {
|
|
||||||
playSound(player, "minecraft:custom.error")
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 알림 사운드 - 서버 접속 시 */
|
|
||||||
fun playNotification(player: ServerPlayer) {
|
|
||||||
playSound(player, "minecraft:custom.notification")
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 메뉴 오픈 사운드 - 메뉴 GUI 열기 시 */
|
|
||||||
fun playMenu(player: ServerPlayer) {
|
|
||||||
playSound(player, "minecraft:custom.menu")
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 공통 사운드 재생 */
|
|
||||||
private fun playSound(player: ServerPlayer, soundId: String) {
|
|
||||||
val sound = SoundEvent.createVariableRangeEvent(ResourceLocation.parse(soundId))
|
|
||||||
player.playNotifySound(sound, SoundSource.MASTER, DEFAULT_VOLUME, DEFAULT_PITCH)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
# Essentials Access Transformer
|
|
||||||
# ServerGamePacketListenerImpl의 player 필드에 접근하기 위한 설정은 필요 없음
|
|
||||||
# NeoForge에서 ServerPlayer 접근 가능
|
|
||||||
|
|
@ -10,7 +10,6 @@
|
||||||
"PlayerInfoPacketMixin",
|
"PlayerInfoPacketMixin",
|
||||||
"PlayerListMixin",
|
"PlayerListMixin",
|
||||||
"PlayerMixin",
|
"PlayerMixin",
|
||||||
"ServerGamePacketListenerImplMixin",
|
|
||||||
"ServerPlayerMixin"
|
"ServerPlayerMixin"
|
||||||
],
|
],
|
||||||
"client": [],
|
"client": [],
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue