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(HeadCommand)
|
||||
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.util.ChatUtils
|
||||
import com.beemer.essentials.util.MessageUtils
|
||||
import com.mojang.brigadier.exceptions.CommandSyntaxException
|
||||
import net.minecraft.ChatFormatting
|
||||
import net.minecraft.commands.CommandSourceStack
|
||||
import net.minecraft.commands.Commands
|
||||
import net.minecraft.network.chat.Component
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import net.neoforged.bus.api.SubscribeEvent
|
||||
import net.neoforged.neoforge.event.RegisterCommandsEvent
|
||||
|
|
@ -30,7 +31,11 @@ object ChatCommand {
|
|||
|
||||
private fun hasPermissionOrSend(player: ServerPlayer?, requiredLevel: Int = 2): Boolean {
|
||||
if (player != null && !player.hasPermissions(requiredLevel)) {
|
||||
MessageUtils.sendError(player, "해당 명령어를 실행할 권한이 없습니다.")
|
||||
player.sendSystemMessage(
|
||||
Component.literal("해당 명령어를 실행할 권한이 없습니다.").withStyle {
|
||||
it.withColor(ChatFormatting.RED)
|
||||
}
|
||||
)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
|
|
@ -46,14 +51,14 @@ object ChatCommand {
|
|||
|
||||
ChatConfig.loadConfig()
|
||||
|
||||
if (player != null) {
|
||||
MessageUtils.sendSuccess(player, "채팅 형식을 새로고침했습니다.")
|
||||
} else {
|
||||
context.source.sendSuccess(
|
||||
{ MessageUtils.success("채팅 형식을 새로고침했습니다.") },
|
||||
false
|
||||
)
|
||||
}
|
||||
val success =
|
||||
Component.literal("채팅 형식을 새로고침했습니다.").withStyle {
|
||||
it.withColor(ChatFormatting.GOLD)
|
||||
}
|
||||
|
||||
if (player == null)
|
||||
context.source.sendSuccess({ success }, false)
|
||||
else player.sendSystemMessage(success)
|
||||
|
||||
1
|
||||
}
|
||||
|
|
@ -76,7 +81,9 @@ object ChatCommand {
|
|||
ChatUtils.clearChatForAll(allPlayers)
|
||||
|
||||
server.playerList.broadcastSystemMessage(
|
||||
MessageUtils.info("채팅창을 비웠습니다."),
|
||||
Component.literal("\u00A0\u00A0채팅창을 비웠습니다.").withStyle {
|
||||
it.withColor(ChatFormatting.AQUA)
|
||||
},
|
||||
false
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -7,9 +7,8 @@ import com.beemer.essentials.data.Location
|
|||
import com.beemer.essentials.gui.Menu
|
||||
import com.beemer.essentials.gui.createPageContainer
|
||||
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 net.minecraft.ChatFormatting
|
||||
import net.minecraft.commands.Commands
|
||||
import net.minecraft.network.chat.Component
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
|
|
@ -20,7 +19,7 @@ import net.neoforged.neoforge.event.RegisterCommandsEvent
|
|||
object CoordinateCommand {
|
||||
@SubscribeEvent
|
||||
fun onRegisterCommands(event: RegisterCommandsEvent) {
|
||||
// /좌표 - GUI 열기
|
||||
// /좌표 - GUI 열기 (페이지 0부터 시작)
|
||||
event.dispatcher.register(
|
||||
Commands.literal("좌표").executes { context ->
|
||||
val player = context.source.playerOrException as ServerPlayer
|
||||
|
|
@ -29,7 +28,7 @@ object CoordinateCommand {
|
|||
}
|
||||
)
|
||||
|
||||
// /좌표이동 <장소>
|
||||
// /좌표이동 <장소> - 바로 이동
|
||||
event.dispatcher.register(
|
||||
Commands.literal("좌표이동")
|
||||
.then(
|
||||
|
|
@ -71,9 +70,16 @@ object CoordinateCommand {
|
|||
)
|
||||
|
||||
if (CoordinateConfig.isExist(name)) {
|
||||
MessageUtils.sendError(
|
||||
player,
|
||||
"{$name} 좌표가 이미 존재합니다."
|
||||
player.sendSystemMessage(
|
||||
Component.literal(
|
||||
"$name 좌표가 이미 존재합니다."
|
||||
)
|
||||
.withStyle {
|
||||
it.withColor(
|
||||
ChatFormatting
|
||||
.RED
|
||||
)
|
||||
}
|
||||
)
|
||||
return@executes 0
|
||||
}
|
||||
|
|
@ -110,9 +116,16 @@ object CoordinateCommand {
|
|||
)
|
||||
|
||||
CoordinateConfig.addCoordinate(coordinate)
|
||||
MessageUtils.sendSuccess(
|
||||
player,
|
||||
"{$name} 좌표를 추가했습니다."
|
||||
player.sendSystemMessage(
|
||||
Component.literal(
|
||||
"$name 좌표를 추가했습니다."
|
||||
)
|
||||
.withStyle {
|
||||
it.withColor(
|
||||
ChatFormatting
|
||||
.GOLD
|
||||
)
|
||||
}
|
||||
)
|
||||
1
|
||||
}
|
||||
|
|
@ -142,17 +155,31 @@ object CoordinateCommand {
|
|||
)
|
||||
|
||||
if (!CoordinateConfig.isExist(name)) {
|
||||
MessageUtils.sendError(
|
||||
player,
|
||||
"{$name} 좌표가 존재하지 않습니다."
|
||||
player.sendSystemMessage(
|
||||
Component.literal(
|
||||
"$name 좌표가 존재하지 않습니다."
|
||||
)
|
||||
.withStyle {
|
||||
it.withColor(
|
||||
ChatFormatting
|
||||
.RED
|
||||
)
|
||||
}
|
||||
)
|
||||
return@executes 0
|
||||
}
|
||||
|
||||
CoordinateConfig.removeCoordinate(name)
|
||||
MessageUtils.sendSuccess(
|
||||
player,
|
||||
"{$name} 좌표를 제거했습니다."
|
||||
player.sendSystemMessage(
|
||||
Component.literal(
|
||||
"$name 좌표를 제거했습니다."
|
||||
)
|
||||
.withStyle {
|
||||
it.withColor(
|
||||
ChatFormatting
|
||||
.GOLD
|
||||
)
|
||||
}
|
||||
)
|
||||
1
|
||||
}
|
||||
|
|
@ -167,22 +194,12 @@ object CoordinateCommand {
|
|||
else (coordinates.size + Menu.ITEMS_PER_PAGE - 1) / Menu.ITEMS_PER_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(
|
||||
SimpleMenuProvider(
|
||||
{ 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 {
|
||||
val coord = CoordinateConfig.getCoordinate(name)
|
||||
if (coord == null) {
|
||||
MessageUtils.sendError(player, "{$name} 좌표가 존재하지 않습니다.")
|
||||
player.sendSystemMessage(
|
||||
Component.literal("$name 좌표가 존재하지 않습니다.").withStyle {
|
||||
it.withColor(ChatFormatting.RED)
|
||||
}
|
||||
)
|
||||
return 0
|
||||
}
|
||||
|
||||
val level = DimensionUtils.getLevelById(player.server, coord.dimension)
|
||||
if (level == null) {
|
||||
MessageUtils.sendError(player, "존재하지 않는 차원입니다.")
|
||||
player.sendSystemMessage(
|
||||
Component.literal("존재하지 않는 차원입니다.").withStyle {
|
||||
it.withColor(ChatFormatting.RED)
|
||||
}
|
||||
)
|
||||
return 0
|
||||
}
|
||||
|
||||
|
|
@ -219,9 +244,11 @@ object CoordinateCommand {
|
|||
)
|
||||
|
||||
player.teleportTo(level, coord.x, coord.y, coord.z, player.yRot, player.xRot)
|
||||
// 텔레포트 사운드 재생
|
||||
SoundUtils.playTeleport(player)
|
||||
MessageUtils.sendSuccess(player, "{$name}(으)로 이동했습니다.")
|
||||
player.sendSystemMessage(
|
||||
Component.literal("$name (으)로 이동했습니다.").withStyle {
|
||||
it.withColor(ChatFormatting.GOLD)
|
||||
}
|
||||
)
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package com.beemer.essentials.command
|
|||
|
||||
import com.beemer.essentials.config.PlayerConfig
|
||||
import com.beemer.essentials.nickname.NicknameDataStore
|
||||
import com.beemer.essentials.util.MessageUtils
|
||||
import com.mojang.brigadier.arguments.StringArgumentType
|
||||
import java.util.Optional
|
||||
import java.util.UUID
|
||||
|
|
@ -31,6 +30,8 @@ object HeadCommand {
|
|||
StringArgumentType.greedyString()
|
||||
)
|
||||
.suggests { _, builder ->
|
||||
// 서버에 접속한 적 있는 플레이어만 제안
|
||||
// 닉네임이 있으면 닉네임, 없으면 원래 이름
|
||||
val suggestions =
|
||||
mutableListOf<String>()
|
||||
PlayerConfig.getAllPlayers()
|
||||
|
|
@ -92,7 +93,11 @@ object HeadCommand {
|
|||
|
||||
// 3. 서버에 접속한 적 없는 플레이어면 거부
|
||||
if (targetUuid == null || !PlayerConfig.isKnownPlayer(targetUuid)) {
|
||||
MessageUtils.sendError(player, "해당 플레이어는 서버에 접속한 적이 없습니다.")
|
||||
player.sendSystemMessage(
|
||||
Component.literal("해당 플레이어는 서버에 접속한 적이 없습니다.").withStyle {
|
||||
it.withColor(ChatFormatting.RED)
|
||||
}
|
||||
)
|
||||
return 0
|
||||
}
|
||||
|
||||
|
|
@ -125,13 +130,21 @@ object HeadCommand {
|
|||
val added = player.inventory.add(headItem)
|
||||
|
||||
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 {
|
||||
// 인벤토리가 가득 찬 경우 바닥에 드랍
|
||||
player.drop(headItem, false)
|
||||
MessageUtils.sendWarning(
|
||||
player,
|
||||
"인벤토리가 가득 차서 {$displayName}의 머리를 바닥에 떨어뜨렸습니다."
|
||||
player.sendSystemMessage(
|
||||
Component.literal("인벤토리가 가득 차서 ${displayName}의 머리를 바닥에 떨어뜨렸습니다.")
|
||||
.withStyle { it.withColor(ChatFormatting.YELLOW) }
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,29 +1,13 @@
|
|||
package com.beemer.essentials.command
|
||||
|
||||
import net.minecraft.ChatFormatting
|
||||
import net.minecraft.commands.Commands
|
||||
import net.minecraft.network.chat.Component
|
||||
import net.minecraft.network.chat.TextColor
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import net.neoforged.bus.api.SubscribeEvent
|
||||
import net.neoforged.neoforge.event.RegisterCommandsEvent
|
||||
|
||||
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
|
||||
fun onRegisterCommands(event: RegisterCommandsEvent) {
|
||||
listOf("도움말", "help", "essentials").forEach { command ->
|
||||
|
|
@ -40,11 +24,15 @@ object HelpCommand {
|
|||
private fun showHelp(player: ServerPlayer) {
|
||||
val header =
|
||||
Component.literal("══════════ ")
|
||||
.withStyle { it.withColor(COLOR_SEPARATOR) }
|
||||
.append(Component.literal("도움말").withStyle { it.withColor(COLOR_HEADER) })
|
||||
.withStyle { it.withColor(ChatFormatting.DARK_GRAY) }
|
||||
.append(
|
||||
Component.literal("도움말").withStyle {
|
||||
it.withColor(ChatFormatting.GOLD).withBold(true)
|
||||
}
|
||||
)
|
||||
.append(
|
||||
Component.literal(" ══════════").withStyle {
|
||||
it.withColor(COLOR_SEPARATOR)
|
||||
it.withColor(ChatFormatting.DARK_GRAY)
|
||||
}
|
||||
)
|
||||
|
||||
|
|
@ -52,7 +40,7 @@ object HelpCommand {
|
|||
player.sendSystemMessage(Component.empty())
|
||||
|
||||
// 좌표 관리
|
||||
sendCategory(player, "좌표 관리", COLOR_COORDINATE)
|
||||
sendCategory(player, "좌표 관리", ChatFormatting.YELLOW)
|
||||
sendCommand(player, "/좌표", "저장된 좌표 목록 (GUI)")
|
||||
sendCommand(player, "/좌표추가 <이름>", "현재 위치 저장")
|
||||
sendCommand(player, "/좌표이동 <이름>", "해당 좌표로 이동")
|
||||
|
|
@ -60,61 +48,66 @@ object HelpCommand {
|
|||
player.sendSystemMessage(Component.empty())
|
||||
|
||||
// 스폰
|
||||
sendCategory(player, "스폰", COLOR_SPAWN)
|
||||
sendCategory(player, "스폰", ChatFormatting.GREEN)
|
||||
sendCommand(player, "/스폰", "스폰으로 이동")
|
||||
sendCommand(player, "/스폰설정", "현재 위치를 스폰으로 설정")
|
||||
sendCommand(player, "/스폰삭제", "커스텀 스폰 삭제")
|
||||
player.sendSystemMessage(Component.empty())
|
||||
|
||||
// 텔레포트
|
||||
sendCategory(player, "텔레포트", COLOR_TELEPORT)
|
||||
sendCategory(player, "텔레포트", ChatFormatting.AQUA)
|
||||
sendCommand(player, "/tpa", "플레이어 선택 (GUI)")
|
||||
sendCommand(player, "/back", "이전 위치로 이동")
|
||||
player.sendSystemMessage(Component.empty())
|
||||
|
||||
// 닉네임
|
||||
sendCategory(player, "닉네임", COLOR_NICKNAME)
|
||||
sendCategory(player, "닉네임", ChatFormatting.LIGHT_PURPLE)
|
||||
sendCommand(player, "/닉네임 변경 <닉네임>", "닉네임 설정")
|
||||
sendCommand(player, "/닉네임 초기화", "닉네임 초기화")
|
||||
player.sendSystemMessage(Component.empty())
|
||||
|
||||
// 머리
|
||||
sendCategory(player, "머리", COLOR_HEAD)
|
||||
sendCategory(player, "머리", ChatFormatting.GOLD)
|
||||
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 =
|
||||
Component.literal("═══════════════════════════════").withStyle {
|
||||
it.withColor(COLOR_SEPARATOR)
|
||||
it.withColor(ChatFormatting.DARK_GRAY)
|
||||
}
|
||||
player.sendSystemMessage(footer)
|
||||
}
|
||||
|
||||
private fun sendCategory(player: ServerPlayer, name: String, color: TextColor) {
|
||||
private fun sendCategory(player: ServerPlayer, name: String, color: ChatFormatting) {
|
||||
val message =
|
||||
Component.literal("▸ ")
|
||||
.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)
|
||||
}
|
||||
|
||||
private fun sendCommand(player: ServerPlayer, cmd: String, desc: String) {
|
||||
val message =
|
||||
Component.literal(" ")
|
||||
.append(Component.literal(cmd).withStyle { it.withColor(COLOR_COMMAND) })
|
||||
.append(Component.literal(" - ").withStyle { it.withColor(COLOR_DASH) })
|
||||
.append(Component.literal(desc).withStyle { it.withColor(COLOR_DESC) })
|
||||
.append(
|
||||
Component.literal(cmd).withStyle {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.util.CommandUtils
|
||||
import com.beemer.essentials.util.DimensionUtils
|
||||
import com.beemer.essentials.util.MessageUtils
|
||||
import com.beemer.essentials.util.SoundUtils
|
||||
import net.minecraft.ChatFormatting
|
||||
import net.minecraft.commands.Commands
|
||||
import net.minecraft.network.chat.Component
|
||||
import net.neoforged.bus.api.SubscribeEvent
|
||||
import net.neoforged.neoforge.event.RegisterCommandsEvent
|
||||
|
||||
|
|
@ -23,9 +23,16 @@ object PlayerCommand {
|
|||
val lastLocation =
|
||||
info?.lastLocation
|
||||
?: run {
|
||||
MessageUtils.sendError(
|
||||
player,
|
||||
"이전 위치가 존재하지 않습니다."
|
||||
player.sendSystemMessage(
|
||||
Component.literal(
|
||||
"이전 위치가 존재하지 않습니다."
|
||||
)
|
||||
.withStyle {
|
||||
it.withColor(
|
||||
ChatFormatting
|
||||
.RED
|
||||
)
|
||||
}
|
||||
)
|
||||
return@executes 0
|
||||
}
|
||||
|
|
@ -36,9 +43,16 @@ object PlayerCommand {
|
|||
lastLocation.dimension
|
||||
)
|
||||
?: run {
|
||||
MessageUtils.sendError(
|
||||
player,
|
||||
"존재하지 않는 차원입니다."
|
||||
player.sendSystemMessage(
|
||||
Component.literal(
|
||||
"존재하지 않는 차원입니다."
|
||||
)
|
||||
.withStyle {
|
||||
it.withColor(
|
||||
ChatFormatting
|
||||
.RED
|
||||
)
|
||||
}
|
||||
)
|
||||
return@executes 0
|
||||
}
|
||||
|
|
@ -51,9 +65,11 @@ object PlayerCommand {
|
|||
player.yRot,
|
||||
player.xRot
|
||||
)
|
||||
// 텔레포트 사운드 재생
|
||||
SoundUtils.playTeleport(player)
|
||||
MessageUtils.sendSuccess(player, "이전 위치로 이동했습니다.")
|
||||
player.sendSystemMessage(
|
||||
Component.literal("이전 위치로 이동했습니다.").withStyle {
|
||||
it.withColor(ChatFormatting.GOLD)
|
||||
}
|
||||
)
|
||||
1
|
||||
}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2,8 +2,9 @@ package com.beemer.essentials.command
|
|||
|
||||
import com.beemer.essentials.config.ProtectFarmlandConfig
|
||||
import com.beemer.essentials.util.CommandUtils
|
||||
import com.beemer.essentials.util.MessageUtils
|
||||
import net.minecraft.ChatFormatting
|
||||
import net.minecraft.commands.Commands
|
||||
import net.minecraft.network.chat.Component
|
||||
import net.neoforged.bus.api.SubscribeEvent
|
||||
import net.neoforged.neoforge.event.RegisterCommandsEvent
|
||||
|
||||
|
|
@ -18,9 +19,22 @@ object ProtectFarmlandCommand {
|
|||
?: return@executes 0
|
||||
|
||||
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
|
||||
}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -5,132 +5,116 @@ import com.beemer.essentials.config.SpawnConfig
|
|||
import com.beemer.essentials.data.Location
|
||||
import com.beemer.essentials.util.CommandUtils
|
||||
import com.beemer.essentials.util.DimensionUtils
|
||||
import com.beemer.essentials.util.MessageUtils
|
||||
import com.beemer.essentials.util.SoundUtils
|
||||
import net.minecraft.ChatFormatting
|
||||
import net.minecraft.commands.Commands
|
||||
import net.minecraft.network.chat.Component
|
||||
import net.neoforged.bus.api.SubscribeEvent
|
||||
import net.neoforged.neoforge.event.RegisterCommandsEvent
|
||||
|
||||
object SpawnCommand {
|
||||
@SubscribeEvent
|
||||
fun onRegisterCommands(event: RegisterCommandsEvent) {
|
||||
listOf("setspawn", "스폰설정").forEach { command ->
|
||||
event.dispatcher.register(
|
||||
Commands.literal(command).executes { context ->
|
||||
val player =
|
||||
CommandUtils.getPlayerOrSendFailure(context.source)
|
||||
?: return@executes 0
|
||||
@SubscribeEvent
|
||||
fun onRegisterCommands(event: RegisterCommandsEvent) {
|
||||
listOf("setspawn", "스폰설정").forEach { command ->
|
||||
event.dispatcher.register(
|
||||
Commands.literal(command).executes { context ->
|
||||
val player =
|
||||
CommandUtils.getPlayerOrSendFailure(context.source)
|
||||
?: return@executes 0
|
||||
|
||||
val exactPos = player.position()
|
||||
val blockPos = player.blockPosition()
|
||||
val dimension =
|
||||
player.level().dimension().location().toString()
|
||||
val biome =
|
||||
player.level()
|
||||
.getBiome(blockPos)
|
||||
.unwrapKey()
|
||||
.map { it.location().toString() }
|
||||
.orElse("minecraft:plains")
|
||||
val exactPos = player.position()
|
||||
val blockPos = player.blockPosition()
|
||||
val dimensionId = player.level().dimension().location().toString()
|
||||
val biomeId =
|
||||
player.level()
|
||||
.getBiome(blockPos)
|
||||
.unwrapKey()
|
||||
.map { it.location().toString() }
|
||||
.orElse("minecraft:plains")
|
||||
|
||||
val location =
|
||||
Location(
|
||||
dimension = dimension,
|
||||
biome = biome,
|
||||
x = exactPos.x,
|
||||
y = exactPos.y,
|
||||
z = exactPos.z
|
||||
)
|
||||
val location =
|
||||
Location(
|
||||
dimension = dimensionId,
|
||||
biome = biomeId,
|
||||
x = exactPos.x,
|
||||
y = exactPos.y,
|
||||
z = exactPos.z
|
||||
)
|
||||
|
||||
SpawnConfig.setCustomSpawn(location)
|
||||
MessageUtils.sendSuccess(player, "스폰 지점이 현재 위치로 설정되었습니다.")
|
||||
1
|
||||
SpawnConfig.setCustomSpawn(location)
|
||||
|
||||
player.sendSystemMessage(
|
||||
Component.literal("스폰 지점이 현재 위치로 설정되었습니다.").withStyle {
|
||||
it.withColor(ChatFormatting.GOLD)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
)
|
||||
}
|
||||
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.nickname.NicknameDataStore
|
||||
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.mojang.authlib.GameProfile
|
||||
import net.minecraft.ChatFormatting
|
||||
|
|
@ -34,22 +32,13 @@ object TeleportCommand {
|
|||
CommandUtils.getPlayerOrSendFailure(context.source)
|
||||
?: return@executes 0
|
||||
|
||||
val container = SimpleContainer(9 * 3)
|
||||
|
||||
val targetPlayers =
|
||||
player.server.playerList.players.filter {
|
||||
it != player
|
||||
}
|
||||
|
||||
// 다른 플레이어가 없으면 에러 메시지 표시
|
||||
if (targetPlayers.isEmpty()) {
|
||||
MessageUtils.sendError(
|
||||
player,
|
||||
"현재 접속 중인 다른 플레이어가 없습니다."
|
||||
)
|
||||
return@executes 0
|
||||
}
|
||||
|
||||
val container = SimpleContainer(9 * 3)
|
||||
|
||||
targetPlayers.take(CONTAINER_SIZE).forEachIndexed {
|
||||
idx,
|
||||
target ->
|
||||
|
|
@ -57,9 +46,6 @@ object TeleportCommand {
|
|||
container.setItem(idx, head)
|
||||
}
|
||||
|
||||
// 플레이어가 있을 때만 클릭 사운드 재생
|
||||
SoundUtils.playClick(player)
|
||||
|
||||
player.openMenu(
|
||||
SimpleMenuProvider(
|
||||
{ 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
|
||||
|
||||
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.neoforged.bus.api.SubscribeEvent
|
||||
import net.neoforged.neoforge.event.ServerChatEvent
|
||||
|
|
@ -12,11 +12,13 @@ object ChatEvents {
|
|||
val player = event.player as ServerPlayer
|
||||
val raw = event.rawText
|
||||
|
||||
if (raw.startsWith("/")) return
|
||||
if (raw.startsWith("/"))
|
||||
return
|
||||
|
||||
if (ChatUtils.hasIllegalCharacter(raw)) {
|
||||
event.isCanceled = true
|
||||
MessageUtils.sendError(player, "채팅에 허용되지 않는 문자가 포함되어 있습니다.")
|
||||
player.sendSystemMessage(Component.literal("채팅에 허용되지 않는 문자가 포함되어 있습니다.")
|
||||
.withStyle { it.withColor(net.minecraft.ChatFormatting.RED) })
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -24,4 +26,4 @@ object ChatEvents {
|
|||
|
||||
event.isCanceled = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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.util.ChatUtils
|
||||
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.neoforged.bus.api.SubscribeEvent
|
||||
import net.neoforged.neoforge.event.entity.player.PlayerEvent
|
||||
|
|
@ -37,70 +35,9 @@ object PlayerEvents {
|
|||
val level = DimensionUtils.getLevelById(player.server, spawn.dimension) ?: return
|
||||
|
||||
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
|
||||
fun onPlayerLoggedOut(event: PlayerEvent.PlayerLoggedOutEvent) {
|
||||
val player = event.entity as? ServerPlayer ?: return
|
||||
|
|
@ -117,23 +54,19 @@ object PlayerEvents {
|
|||
|
||||
val pos = oldPlayer.blockPosition()
|
||||
val dimension = oldPlayer.level().dimension().location().toString()
|
||||
val biome =
|
||||
oldPlayer
|
||||
.level()
|
||||
.getBiome(pos)
|
||||
.unwrapKey()
|
||||
.map { it.location().toString() }
|
||||
.orElse("minecraft:plains")
|
||||
val biome = oldPlayer.level().getBiome(pos)
|
||||
.unwrapKey()
|
||||
.map { it.location().toString() }
|
||||
.orElse("minecraft:plains")
|
||||
|
||||
val lastLoc =
|
||||
Location(
|
||||
dimension = dimension,
|
||||
biome = biome,
|
||||
x = pos.x.toDouble(),
|
||||
y = pos.y.toDouble(),
|
||||
z = pos.z.toDouble()
|
||||
)
|
||||
val lastLoc = Location(
|
||||
dimension = dimension,
|
||||
biome = biome,
|
||||
x = pos.x.toDouble(),
|
||||
y = pos.y.toDouble(),
|
||||
z = pos.z.toDouble()
|
||||
)
|
||||
|
||||
PlayerConfig.recordLastLocation(oldPlayer, lastLoc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
package com.beemer.essentials.gui
|
||||
|
||||
import com.beemer.essentials.config.AntimobConfig
|
||||
import com.beemer.essentials.util.SoundUtils
|
||||
import net.minecraft.ChatFormatting
|
||||
import net.minecraft.core.component.DataComponents
|
||||
import net.minecraft.network.chat.Component
|
||||
|
|
@ -78,7 +77,6 @@ class AntimobGui(
|
|||
) {
|
||||
if (player is ServerPlayer && slotId in 0 until CONTAINER_SIZE) {
|
||||
if (glassSlotToMob.containsKey(slotId)) {
|
||||
playClickSound(player)
|
||||
val mob = glassSlotToMob[slotId] ?: return
|
||||
AntimobConfig.toggle(mob)
|
||||
container.setItem(slotId, makeToggleGlass(mob))
|
||||
|
|
@ -96,10 +94,6 @@ class AntimobGui(
|
|||
index: Int
|
||||
): ItemStack = ItemStack.EMPTY
|
||||
|
||||
private fun playClickSound(player: ServerPlayer) {
|
||||
SoundUtils.playClick(player)
|
||||
}
|
||||
|
||||
private fun makeMobHead(mob: String): ItemStack {
|
||||
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.data.Location
|
||||
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.translateDimension
|
||||
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.ItemLore
|
||||
|
||||
/** 좌표 GUI - 페이지 기능 포함 4줄(36개) 표시 + 6번째 줄에 이전/다음 버튼 */
|
||||
/** 좌표 GUI - 페이지 기능 포함 5줄(45개) 표시 + 6번째 줄에 이전/다음 버튼 */
|
||||
class Menu(
|
||||
syncId: Int,
|
||||
playerInv: Inventory,
|
||||
|
|
@ -40,9 +38,9 @@ class Menu(
|
|||
) : AbstractContainerMenu(MenuType.GENERIC_9x6, syncId) {
|
||||
|
||||
companion object {
|
||||
const val ITEMS_PER_PAGE = 36 // 4줄 * 9
|
||||
val PREV_BUTTON_SLOTS = listOf(45, 46) // 6번째 줄 이전 버튼 2칸
|
||||
val NEXT_BUTTON_SLOTS = listOf(52, 53) // 6번째 줄 다음 버튼 2칸
|
||||
const val ITEMS_PER_PAGE = 45 // 5줄 * 9
|
||||
const val PREV_BUTTON_SLOT = 45 // 6번째 줄 첫 번째
|
||||
const val NEXT_BUTTON_SLOT = 53 // 6번째 줄 마지막
|
||||
}
|
||||
|
||||
init {
|
||||
|
|
@ -83,26 +81,24 @@ class Menu(
|
|||
val stack = slots[slotId].item
|
||||
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.getString("action") == "prev"
|
||||
) {
|
||||
if (currentPage > 0) {
|
||||
playClickSound(player)
|
||||
openPage(player, currentPage - 1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 다음 페이지 버튼 (슬롯 52, 53)
|
||||
if (slotId in NEXT_BUTTON_SLOTS &&
|
||||
// 다음 페이지 버튼
|
||||
if (slotId == NEXT_BUTTON_SLOT &&
|
||||
tag.contains("action") &&
|
||||
tag.getString("action") == "next"
|
||||
) {
|
||||
val totalPages = getTotalPages()
|
||||
if (currentPage < totalPages - 1) {
|
||||
playClickSound(player)
|
||||
openPage(player, currentPage + 1)
|
||||
}
|
||||
return
|
||||
|
|
@ -113,7 +109,6 @@ class Menu(
|
|||
val x = tag.getDouble("x")
|
||||
val y = tag.getDouble("y")
|
||||
val z = tag.getDouble("z")
|
||||
val coordName = tag.getString("name")
|
||||
val dimension =
|
||||
ResourceLocation.tryParse(tag.getString("dimension"))?.let {
|
||||
ResourceKey.create(Registries.DIMENSION, it)
|
||||
|
|
@ -121,6 +116,7 @@ class Menu(
|
|||
val level = dimension?.let { player.server.getLevel(it) }
|
||||
|
||||
if (level != null) {
|
||||
// 이전 위치 저장
|
||||
val prevPos = player.blockPosition()
|
||||
PlayerConfig.recordLastLocation(
|
||||
player,
|
||||
|
|
@ -139,18 +135,20 @@ class Menu(
|
|||
)
|
||||
|
||||
player.teleportTo(level, x, y, z, player.yRot, player.xRot)
|
||||
// 텔레포트 사운드 재생
|
||||
SoundUtils.playTeleport(player)
|
||||
MessageUtils.sendSuccess(player, "{$coordName}(으)로 이동했습니다.")
|
||||
player.sendSystemMessage(
|
||||
Component.literal(tag.getString("name"))
|
||||
.withStyle { it.withColor(ChatFormatting.DARK_GREEN) }
|
||||
.append(
|
||||
Component.literal("(으)로 이동했습니다.").withStyle {
|
||||
it.withColor(ChatFormatting.GOLD)
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
player.closeContainer()
|
||||
}
|
||||
}
|
||||
|
||||
private fun playClickSound(player: ServerPlayer) {
|
||||
SoundUtils.playClick(player)
|
||||
}
|
||||
|
||||
private fun getTotalPages(): Int {
|
||||
val totalItems = CoordinateConfig.getCoordinates().size
|
||||
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) {
|
||||
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(
|
||||
SimpleMenuProvider(
|
||||
{ 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
|
||||
else (coordinates.size + Menu.ITEMS_PER_PAGE - 1) / Menu.ITEMS_PER_PAGE
|
||||
|
||||
// 좌표 아이템 추가 (2~5번째 줄, 슬롯 9-44)
|
||||
// 좌표 아이템 추가 (5줄, 45개)
|
||||
for (i in startIdx until endIdx) {
|
||||
val coord = coordinates[i]
|
||||
val item = ItemStack(Items.FILLED_MAP)
|
||||
val item = ItemStack(Items.PAPER)
|
||||
val tag =
|
||||
CompoundTag().apply {
|
||||
putString("name", coord.name)
|
||||
|
|
@ -221,131 +207,96 @@ fun createPageContainer(player: ServerPlayer, page: Int): SimpleContainer {
|
|||
val displayName =
|
||||
if (creatorName != null) {
|
||||
Component.literal(coord.name)
|
||||
.withStyle {
|
||||
it.withColor(ChatFormatting.GOLD).withBold(false).withItalic(false)
|
||||
}
|
||||
.withStyle { it.withColor(ChatFormatting.GOLD).withBold(true) }
|
||||
.append(
|
||||
Component.literal(" ($creatorName)").withStyle {
|
||||
it.withColor(ChatFormatting.AQUA)
|
||||
.withBold(false)
|
||||
.withItalic(false)
|
||||
it.withColor(ChatFormatting.AQUA).withBold(false)
|
||||
}
|
||||
)
|
||||
} else {
|
||||
Component.literal(coord.name).withStyle {
|
||||
it.withColor(ChatFormatting.GOLD).withBold(false).withItalic(false)
|
||||
it.withColor(ChatFormatting.GOLD).withBold(true)
|
||||
}
|
||||
}
|
||||
|
||||
val loreList: List<Component> =
|
||||
listOf(
|
||||
Component.literal("디멘션: ")
|
||||
.withStyle {
|
||||
it.withColor(ChatFormatting.DARK_GREEN).withItalic(false)
|
||||
}
|
||||
.withStyle { it.withColor(ChatFormatting.DARK_GREEN) }
|
||||
.append(
|
||||
Component.literal(translateDimension(coord.dimension))
|
||||
.withStyle {
|
||||
it.withColor(ChatFormatting.GRAY)
|
||||
.withItalic(false)
|
||||
}
|
||||
.withStyle { it.withColor(ChatFormatting.GRAY) }
|
||||
),
|
||||
Component.literal("바이옴: ")
|
||||
.withStyle {
|
||||
it.withColor(ChatFormatting.DARK_GREEN).withItalic(false)
|
||||
}
|
||||
.withStyle { it.withColor(ChatFormatting.DARK_GREEN) }
|
||||
.append(
|
||||
Component.literal(translateBiome(coord.biome)).withStyle {
|
||||
it.withColor(ChatFormatting.GRAY).withItalic(false)
|
||||
it.withColor(ChatFormatting.GRAY)
|
||||
}
|
||||
),
|
||||
Component.literal("좌표: ")
|
||||
.withStyle {
|
||||
it.withColor(ChatFormatting.DARK_GREEN).withItalic(false)
|
||||
}
|
||||
.withStyle { it.withColor(ChatFormatting.DARK_GREEN) }
|
||||
.append(
|
||||
Component.literal(
|
||||
"${coord.x.toInt()}, ${coord.y.toInt()}, ${coord.z.toInt()}"
|
||||
)
|
||||
.withStyle {
|
||||
it.withColor(ChatFormatting.GRAY)
|
||||
.withItalic(false)
|
||||
}
|
||||
.withStyle { it.withColor(ChatFormatting.GRAY) }
|
||||
)
|
||||
)
|
||||
|
||||
item.set(DataComponents.CUSTOM_NAME, displayName)
|
||||
item.set(DataComponents.LORE, ItemLore(loreList))
|
||||
item.set(DataComponents.CUSTOM_DATA, CustomData.of(tag))
|
||||
item.set(
|
||||
DataComponents.CUSTOM_MODEL_DATA,
|
||||
net.minecraft.world.item.component.CustomModelData(1)
|
||||
)
|
||||
container.setItem(9 + (i - startIdx), item) // 슬롯 9부터 시작 (1줄 건너뛰기)
|
||||
container.setItem(i - startIdx, item)
|
||||
}
|
||||
|
||||
// 1번째 줄 (슬롯 0-8) - 타이틀 영역 투명 처리
|
||||
val fillerItem = ItemStack(Items.GLASS_PANE)
|
||||
fillerItem.set(DataComponents.CUSTOM_NAME, Component.empty())
|
||||
fillerItem.set(DataComponents.LORE, ItemLore(listOf()))
|
||||
// 맨 아래줄 빈 공간을 회색 유리판으로 채우기 (슬롯 46-52)
|
||||
val fillerItem = ItemStack(Items.GRAY_STAINED_GLASS_PANE)
|
||||
fillerItem.set(
|
||||
DataComponents.CUSTOM_MODEL_DATA,
|
||||
net.minecraft.world.item.component.CustomModelData(1)
|
||||
DataComponents.CUSTOM_NAME,
|
||||
Component.literal(" ").withStyle { it.withColor(ChatFormatting.DARK_GRAY) }
|
||||
)
|
||||
fillerItem.set(DataComponents.HIDE_TOOLTIP, net.minecraft.util.Unit.INSTANCE)
|
||||
for (slot in 0..8) {
|
||||
for (slot in 46..52) {
|
||||
container.setItem(slot, fillerItem.copy())
|
||||
}
|
||||
|
||||
// 6번째 줄 가운데 슬롯 (47-51) - 투명 처리
|
||||
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 "") }
|
||||
// 이전 페이지 버튼 (슬롯 45)
|
||||
if (page > 0) {
|
||||
val prevItem = ItemStack(Items.LIME_STAINED_GLASS_PANE)
|
||||
val prevTag = CompoundTag().apply { putString("action", "prev") }
|
||||
prevItem.set(
|
||||
DataComponents.CUSTOM_NAME,
|
||||
Component.literal("이전 페이지 [${page + 1}/$totalPages]").withStyle {
|
||||
it.withColor(0xA8D8D8).withItalic(false)
|
||||
}
|
||||
Component.literal("이전 페이지").withStyle { it.withColor(ChatFormatting.YELLOW) }
|
||||
)
|
||||
prevItem.set(DataComponents.CUSTOM_DATA, CustomData.of(prevTag))
|
||||
container.setItem(Menu.PREV_BUTTON_SLOT, prevItem)
|
||||
} else {
|
||||
prevItem.set(DataComponents.CUSTOM_NAME, Component.empty())
|
||||
prevItem.set(DataComponents.HIDE_TOOLTIP, net.minecraft.util.Unit.INSTANCE)
|
||||
val disabledItem = ItemStack(Items.RED_STAINED_GLASS_PANE)
|
||||
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) - 페이지 정보 툴팁
|
||||
val nextItem = ItemStack(Items.GLASS_PANE)
|
||||
val nextTag =
|
||||
CompoundTag().apply { putString("action", if (page < totalPages - 1) "next" else "") }
|
||||
// 다음 페이지 버튼 (슬롯 53)
|
||||
if (page < totalPages - 1) {
|
||||
val nextItem = ItemStack(Items.LIME_STAINED_GLASS_PANE)
|
||||
val nextTag = CompoundTag().apply { putString("action", "next") }
|
||||
nextItem.set(
|
||||
DataComponents.CUSTOM_NAME,
|
||||
Component.literal("다음 페이지 [${page + 1}/$totalPages]").withStyle {
|
||||
it.withColor(0xA8D8D8).withItalic(false)
|
||||
}
|
||||
Component.literal("다음 페이지").withStyle { it.withColor(ChatFormatting.YELLOW) }
|
||||
)
|
||||
nextItem.set(DataComponents.CUSTOM_DATA, CustomData.of(nextTag))
|
||||
container.setItem(Menu.NEXT_BUTTON_SLOT, nextItem)
|
||||
} else {
|
||||
nextItem.set(DataComponents.CUSTOM_NAME, Component.empty())
|
||||
nextItem.set(DataComponents.HIDE_TOOLTIP, net.minecraft.util.Unit.INSTANCE)
|
||||
val disabledItem = ItemStack(Items.RED_STAINED_GLASS_PANE)
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.data.Location
|
||||
import com.beemer.essentials.data.Player
|
||||
import com.beemer.essentials.nickname.NicknameDataStore
|
||||
import com.beemer.essentials.util.MessageUtils
|
||||
import com.beemer.essentials.util.SoundUtils
|
||||
import net.minecraft.ChatFormatting
|
||||
import net.minecraft.network.chat.Component
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import net.minecraft.world.Container
|
||||
import net.minecraft.world.entity.player.Inventory
|
||||
|
|
@ -20,135 +21,140 @@ class TeleportGui(
|
|||
val container: Container,
|
||||
val viewer: ServerPlayer
|
||||
) : AbstractContainerMenu(MenuType.GENERIC_9x3, syncId) {
|
||||
companion object {
|
||||
const val CONTAINER_COLUMNS = 9
|
||||
const val CONTAINER_ROWS = 3
|
||||
const val CONTAINER_SIZE = CONTAINER_COLUMNS * CONTAINER_ROWS
|
||||
companion object {
|
||||
const val CONTAINER_COLUMNS = 9
|
||||
const val CONTAINER_ROWS = 3
|
||||
const val CONTAINER_SIZE = CONTAINER_COLUMNS * CONTAINER_ROWS
|
||||
|
||||
const val SLOT_SIZE = 18
|
||||
const val LEFT_PADDING = 8
|
||||
const val TOP_PADDING = 18
|
||||
const val SLOT_SIZE = 18
|
||||
const val LEFT_PADDING = 8
|
||||
const val TOP_PADDING = 18
|
||||
|
||||
const val PLAYER_INV_TOP = 86
|
||||
const val HOTBAR_Y = 144
|
||||
const val PLAYER_INV_TOP = 86
|
||||
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 =
|
||||
viewer.server
|
||||
.playerList
|
||||
.players
|
||||
.filterIsInstance<ServerPlayer>()
|
||||
.filter { it.uuid != viewer.uuid }
|
||||
.map { it.uuid }
|
||||
for (row in 0 until 3) {
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
for (col in 0 until 9) {
|
||||
val x = LEFT_PADDING + col * SLOT_SIZE
|
||||
val y = HOTBAR_Y
|
||||
addSlot(Slot(playerInv, col, x, y))
|
||||
}
|
||||
}
|
||||
|
||||
override fun clicked(
|
||||
slotId: Int,
|
||||
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) {
|
||||
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))
|
||||
}
|
||||
}
|
||||
val targetLevel = currentTargetPlayer.serverLevel()
|
||||
|
||||
for (col in 0 until 9) {
|
||||
val x = LEFT_PADDING + col * SLOT_SIZE
|
||||
val y = HOTBAR_Y
|
||||
addSlot(Slot(playerInv, col, x, y))
|
||||
}
|
||||
}
|
||||
player.teleportTo(
|
||||
targetLevel,
|
||||
currentTargetPlayer.x,
|
||||
currentTargetPlayer.y,
|
||||
currentTargetPlayer.z,
|
||||
currentTargetPlayer.yRot,
|
||||
currentTargetPlayer.xRot
|
||||
)
|
||||
|
||||
override fun clicked(
|
||||
slotId: Int,
|
||||
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)
|
||||
}
|
||||
// 닉네임이 있으면 닉네임 사용
|
||||
val targetName =
|
||||
NicknameDataStore.getNickname(currentTargetPlayer.uuid)
|
||||
?: currentTargetPlayer.gameProfile.name
|
||||
val playerName =
|
||||
NicknameDataStore.getNickname(player.uuid) ?: player.gameProfile.name
|
||||
|
||||
// 플레이어가 없으면 무시 (빈 공간 클릭)
|
||||
if (currentTargetPlayer == null) {
|
||||
return
|
||||
}
|
||||
|
||||
val prevPos = player.blockPosition()
|
||||
val prevDimension = player.level().dimension().location().toString()
|
||||
val prevBiome =
|
||||
player.level()
|
||||
.getBiome(prevPos)
|
||||
.unwrapKey()
|
||||
.map { it.location().toString() }
|
||||
.orElse("minecraft:plains")
|
||||
val previousLocation =
|
||||
Location(
|
||||
dimension = prevDimension,
|
||||
biome = prevBiome,
|
||||
x = prevPos.x.toDouble(),
|
||||
y = prevPos.y.toDouble(),
|
||||
z = prevPos.z.toDouble()
|
||||
player.sendSystemMessage(
|
||||
Component.literal(targetName)
|
||||
.withStyle { it.withColor(ChatFormatting.AQUA) }
|
||||
.append(
|
||||
Component.literal("님에게 텔레포트 했습니다.").withStyle {
|
||||
it.withColor(ChatFormatting.GOLD)
|
||||
}
|
||||
)
|
||||
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.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)
|
||||
player.closeContainer()
|
||||
return
|
||||
}
|
||||
super.clicked(slotId, button, clickType, player)
|
||||
}
|
||||
|
||||
private fun playClickSound(player: ServerPlayer) {
|
||||
SoundUtils.playTeleport(player)
|
||||
}
|
||||
override fun stillValid(player: net.minecraft.world.entity.player.Player): Boolean = true
|
||||
|
||||
override fun stillValid(player: net.minecraft.world.entity.player.Player): Boolean = true
|
||||
|
||||
override fun quickMoveStack(
|
||||
player: net.minecraft.world.entity.player.Player,
|
||||
index: Int
|
||||
): ItemStack = ItemStack.EMPTY
|
||||
override fun quickMoveStack(
|
||||
player: net.minecraft.world.entity.player.Player,
|
||||
index: Int
|
||||
): ItemStack = ItemStack.EMPTY
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
package com.beemer.essentials.nickname
|
||||
|
||||
import com.beemer.essentials.util.MessageUtils
|
||||
import com.mojang.brigadier.arguments.StringArgumentType
|
||||
import net.minecraft.ChatFormatting
|
||||
import net.minecraft.commands.Commands
|
||||
import net.minecraft.network.chat.Component
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import net.neoforged.bus.api.SubscribeEvent
|
||||
import net.neoforged.neoforge.event.RegisterCommandsEvent
|
||||
|
|
@ -28,6 +29,7 @@ object NicknameCommand {
|
|||
.entity as?
|
||||
ServerPlayer
|
||||
?: return@executes 0
|
||||
|
||||
val nickname =
|
||||
StringArgumentType
|
||||
.getString(
|
||||
|
|
@ -44,6 +46,7 @@ object NicknameCommand {
|
|||
val player =
|
||||
context.source.entity as? ServerPlayer
|
||||
?: return@executes 0
|
||||
|
||||
executeReset(player)
|
||||
}
|
||||
)
|
||||
|
|
@ -66,6 +69,7 @@ object NicknameCommand {
|
|||
.entity as?
|
||||
ServerPlayer
|
||||
?: return@executes 0
|
||||
|
||||
val nickname =
|
||||
StringArgumentType
|
||||
.getString(
|
||||
|
|
@ -82,6 +86,7 @@ object NicknameCommand {
|
|||
val player =
|
||||
context.source.entity as? ServerPlayer
|
||||
?: return@executes 0
|
||||
|
||||
executeReset(player)
|
||||
}
|
||||
)
|
||||
|
|
@ -91,41 +96,65 @@ object NicknameCommand {
|
|||
private fun executeSet(player: ServerPlayer, nickname: String): Int {
|
||||
// 유효성 검사: 길이
|
||||
if (nickname.length < 2 || nickname.length > 16) {
|
||||
MessageUtils.sendError(player, "닉네임은 2~16자 사이여야 합니다.")
|
||||
return 0
|
||||
}
|
||||
|
||||
// 유효성 검사: 현재 닉네임과 동일
|
||||
val currentNickname = NicknameDataStore.getNickname(player.uuid)
|
||||
if (currentNickname == nickname) {
|
||||
MessageUtils.sendError(player, "현재 사용 중인 닉네임과 동일합니다.")
|
||||
player.sendSystemMessage(
|
||||
Component.literal("닉네임은 2~16자 사이여야 합니다.").withStyle {
|
||||
it.withColor(ChatFormatting.RED)
|
||||
}
|
||||
)
|
||||
return 0
|
||||
}
|
||||
|
||||
// 유효성 검사: 중복
|
||||
if (NicknameDataStore.isNicknameTaken(nickname, player.uuid)) {
|
||||
MessageUtils.sendError(player, "이미 사용 중인 닉네임입니다.")
|
||||
player.sendSystemMessage(
|
||||
Component.literal("이미 사용 중인 닉네임입니다.").withStyle {
|
||||
it.withColor(ChatFormatting.RED)
|
||||
}
|
||||
)
|
||||
return 0
|
||||
}
|
||||
|
||||
// 닉네임 저장 및 적용
|
||||
// 닉네임 저장 및 적용 (gameProfile.name = 실제 마인크래프트 이름)
|
||||
NicknameDataStore.setNickname(player.uuid, player.gameProfile.name, 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
|
||||
}
|
||||
|
||||
private fun executeReset(player: ServerPlayer): Int {
|
||||
if (!NicknameDataStore.hasNickname(player.uuid)) {
|
||||
MessageUtils.sendError(player, "설정된 닉네임이 없습니다.")
|
||||
player.sendSystemMessage(
|
||||
Component.literal("설정된 닉네임이 없습니다.").withStyle {
|
||||
it.withColor(ChatFormatting.RED)
|
||||
}
|
||||
)
|
||||
return 0
|
||||
}
|
||||
|
||||
NicknameDataStore.removeNickname(player.uuid)
|
||||
NicknameManager.removeNickname(player)
|
||||
|
||||
MessageUtils.sendSuccess(player, "닉네임이 초기화되었습니다.")
|
||||
player.sendSystemMessage(
|
||||
Component.literal("닉네임이 초기화되었습니다.").withStyle {
|
||||
it.withColor(ChatFormatting.GOLD)
|
||||
}
|
||||
)
|
||||
|
||||
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",
|
||||
"PlayerListMixin",
|
||||
"PlayerMixin",
|
||||
"ServerGamePacketListenerImplMixin",
|
||||
"ServerPlayerMixin"
|
||||
],
|
||||
"client": [],
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue