머리 명령어 추가
This commit is contained in:
parent
727769a170
commit
e4337b9e12
7 changed files with 312 additions and 99 deletions
|
|
@ -31,5 +31,6 @@ class Essentials(modEventBus: IEventBus) {
|
|||
NeoForge.EVENT_BUS.register(AntimobCommand)
|
||||
NeoForge.EVENT_BUS.register(CoordinateCommand)
|
||||
NeoForge.EVENT_BUS.register(NicknameCommand)
|
||||
NeoForge.EVENT_BUS.register(HeadCommand)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,153 @@
|
|||
package com.beemer.essentials.command
|
||||
|
||||
import com.beemer.essentials.config.PlayerConfig
|
||||
import com.beemer.essentials.nickname.NicknameDataStore
|
||||
import com.mojang.brigadier.arguments.StringArgumentType
|
||||
import java.util.Optional
|
||||
import java.util.UUID
|
||||
import net.minecraft.ChatFormatting
|
||||
import net.minecraft.commands.Commands
|
||||
import net.minecraft.commands.SharedSuggestionProvider
|
||||
import net.minecraft.core.component.DataComponents
|
||||
import net.minecraft.network.chat.Component
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import net.minecraft.world.item.ItemStack
|
||||
import net.minecraft.world.item.Items
|
||||
import net.minecraft.world.item.component.ResolvableProfile
|
||||
import net.neoforged.bus.api.SubscribeEvent
|
||||
import net.neoforged.neoforge.event.RegisterCommandsEvent
|
||||
|
||||
object HeadCommand {
|
||||
@SubscribeEvent
|
||||
fun onRegisterCommands(event: RegisterCommandsEvent) {
|
||||
// /머리 <플레이어> - 해당 플레이어 머리 아이템 지급
|
||||
listOf("머리", "head").forEach { command ->
|
||||
event.dispatcher.register(
|
||||
Commands.literal(command)
|
||||
.then(
|
||||
Commands.argument(
|
||||
"플레이어",
|
||||
StringArgumentType.greedyString()
|
||||
)
|
||||
.suggests { _, builder ->
|
||||
// 서버에 접속한 적 있는 플레이어만 제안
|
||||
// 닉네임이 있으면 닉네임, 없으면 원래 이름
|
||||
val suggestions =
|
||||
mutableListOf<String>()
|
||||
PlayerConfig.getAllPlayers()
|
||||
.forEach { (uuid, name) ->
|
||||
val nickname =
|
||||
NicknameDataStore
|
||||
.getNickname(
|
||||
UUID.fromString(
|
||||
uuid
|
||||
)
|
||||
)
|
||||
suggestions.add(
|
||||
nickname
|
||||
?: name
|
||||
)
|
||||
}
|
||||
SharedSuggestionProvider.suggest(
|
||||
suggestions,
|
||||
builder
|
||||
)
|
||||
}
|
||||
.executes { context ->
|
||||
val player =
|
||||
context.source.entity as?
|
||||
ServerPlayer
|
||||
?: return@executes 0
|
||||
val targetName =
|
||||
StringArgumentType
|
||||
.getString(
|
||||
context,
|
||||
"플레이어"
|
||||
)
|
||||
givePlayerHead(player, targetName)
|
||||
}
|
||||
)
|
||||
.executes { context ->
|
||||
// 인자 없이 실행 시 자신의 머리
|
||||
val player =
|
||||
context.source.entity as? ServerPlayer
|
||||
?: return@executes 0
|
||||
givePlayerHead(player, player.gameProfile.name)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun givePlayerHead(player: ServerPlayer, targetName: String): Int {
|
||||
// 1. 원래 이름으로 UUID 찾기
|
||||
var targetUuid = PlayerConfig.getUuidByName(targetName)
|
||||
var realName = targetName
|
||||
|
||||
// 2. 못 찾으면 닉네임으로 UUID 찾기
|
||||
if (targetUuid == null) {
|
||||
val uuidByNickname = NicknameDataStore.getUuidByNickname(targetName)
|
||||
if (uuidByNickname != null) {
|
||||
targetUuid = uuidByNickname.toString()
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 서버에 접속한 적 없는 플레이어면 거부
|
||||
if (targetUuid == null || !PlayerConfig.isKnownPlayer(targetUuid)) {
|
||||
player.sendSystemMessage(
|
||||
Component.literal("해당 플레이어는 서버에 접속한 적이 없습니다.").withStyle {
|
||||
it.withColor(ChatFormatting.RED)
|
||||
}
|
||||
)
|
||||
return 0
|
||||
}
|
||||
|
||||
// 실제 마인크래프트 이름 가져오기
|
||||
val allPlayers = PlayerConfig.getAllPlayers()
|
||||
realName = allPlayers[targetUuid] ?: targetName
|
||||
|
||||
// 표시용 이름 (닉네임 우선)
|
||||
val displayName =
|
||||
NicknameDataStore.getNickname(UUID.fromString(targetUuid)) ?: realName
|
||||
|
||||
// 머리 아이템 생성
|
||||
val headItem = ItemStack(Items.PLAYER_HEAD)
|
||||
val resolvableProfile =
|
||||
ResolvableProfile(
|
||||
Optional.of(realName),
|
||||
Optional.empty<UUID>(),
|
||||
com.mojang.authlib.properties.PropertyMap()
|
||||
)
|
||||
|
||||
headItem.set(DataComponents.PROFILE, resolvableProfile)
|
||||
headItem.set(
|
||||
DataComponents.CUSTOM_NAME,
|
||||
Component.literal("${displayName}의 머리").withStyle {
|
||||
it.withColor(ChatFormatting.GOLD)
|
||||
}
|
||||
)
|
||||
|
||||
// 인벤토리에 추가
|
||||
val added = player.inventory.add(headItem)
|
||||
|
||||
if (added) {
|
||||
player.sendSystemMessage(
|
||||
Component.literal(displayName)
|
||||
.withStyle { it.withColor(ChatFormatting.GREEN) }
|
||||
.append(
|
||||
Component.literal("의 머리가 지급되었습니다.").withStyle {
|
||||
it.withColor(ChatFormatting.GOLD)
|
||||
}
|
||||
)
|
||||
)
|
||||
} else {
|
||||
// 인벤토리가 가득 찬 경우 바닥에 드랍
|
||||
player.drop(headItem, false)
|
||||
player.sendSystemMessage(
|
||||
Component.literal("인벤토리가 가득 차서 ${displayName}의 머리를 바닥에 떨어뜨렸습니다.")
|
||||
.withStyle { it.withColor(ChatFormatting.YELLOW) }
|
||||
)
|
||||
}
|
||||
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
|
@ -16,77 +16,104 @@ object SpawnCommand {
|
|||
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
|
||||
Commands.literal(command).executes { context ->
|
||||
val player =
|
||||
CommandUtils.getPlayerOrSendFailure(context.source)
|
||||
?: return@executes 0
|
||||
|
||||
val playerLocation = player.blockPosition()
|
||||
val dimensionId = player.level().dimension().location().toString()
|
||||
val biomeId = player.level().getBiome(playerLocation)
|
||||
.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 = dimensionId,
|
||||
biome = biomeId,
|
||||
x = playerLocation.x.toDouble(),
|
||||
y = playerLocation.y.toDouble(),
|
||||
z = playerLocation.z.toDouble()
|
||||
)
|
||||
val location =
|
||||
Location(
|
||||
dimension = dimensionId,
|
||||
biome = biomeId,
|
||||
x = exactPos.x,
|
||||
y = exactPos.y,
|
||||
z = exactPos.z
|
||||
)
|
||||
|
||||
SpawnConfig.setCustomSpawn(location)
|
||||
SpawnConfig.setCustomSpawn(location)
|
||||
|
||||
player.sendSystemMessage(Component.literal("스폰 지점이 현재 위치로 설정되었습니다.").withStyle { it.withColor(ChatFormatting.GOLD) })
|
||||
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
|
||||
Commands.literal(command).executes { context ->
|
||||
val player =
|
||||
CommandUtils.getPlayerOrSendFailure(context.source)
|
||||
?: return@executes 0
|
||||
|
||||
val target = SpawnConfig.getCustomSpawn() ?: SpawnConfig.getDefaultSpawn()
|
||||
val target = SpawnConfig.getCustomSpawn() ?: SpawnConfig.getDefaultSpawn()
|
||||
|
||||
target?.let { t ->
|
||||
val level = DimensionUtils.getLevelById(player.server, t.dimension)
|
||||
target?.let { t ->
|
||||
val level = DimensionUtils.getLevelById(player.server, t.dimension)
|
||||
|
||||
level?.let { l ->
|
||||
val currentPos = player.blockPosition()
|
||||
val currentDimension = player.level().dimension().location().toString()
|
||||
val currentBiome = player.level().getBiome(currentPos)
|
||||
.unwrapKey()
|
||||
.map { it.location().toString() }
|
||||
.orElse("minecraft:plains")
|
||||
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 = currentPos.x.toDouble(),
|
||||
y = currentPos.y.toDouble(),
|
||||
z = currentPos.z.toDouble()
|
||||
)
|
||||
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) })
|
||||
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
|
||||
}
|
||||
1
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
listOf("delspawn", "스폰삭제").forEach { command ->
|
||||
event.dispatcher.register(
|
||||
Commands.literal(command).executes { context ->
|
||||
val player = CommandUtils.getPlayerOrSendFailure(context.source) ?: return@executes 0
|
||||
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
|
||||
}
|
||||
SpawnConfig.removeCustomSpawn()
|
||||
player.sendSystemMessage(
|
||||
Component.literal("스폰 지점이 기본 스폰 지점으로 변경되었습니다.").withStyle {
|
||||
it.withColor(ChatFormatting.GOLD)
|
||||
}
|
||||
)
|
||||
1
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -71,16 +71,9 @@ object CoordinateConfig {
|
|||
val dimension = tag.getString("dimension")
|
||||
val biome = tag.getString("biome")
|
||||
|
||||
// 하위 호환: Int로 저장된 경우 Double로 변환
|
||||
val x =
|
||||
if (tag.contains("x", 6)) tag.getDouble("x")
|
||||
else tag.getInt("x").toDouble() + 0.5
|
||||
val y =
|
||||
if (tag.contains("y", 6)) tag.getDouble("y")
|
||||
else tag.getInt("y").toDouble()
|
||||
val z =
|
||||
if (tag.contains("z", 6)) tag.getDouble("z")
|
||||
else tag.getInt("z").toDouble() + 0.5
|
||||
val x = tag.getDouble("x")
|
||||
val y = tag.getDouble("y")
|
||||
val z = tag.getDouble("z")
|
||||
|
||||
val creatorUuid =
|
||||
if (tag.contains("creatorUuid")) tag.getString("creatorUuid")
|
||||
|
|
|
|||
|
|
@ -142,4 +142,19 @@ object PlayerConfig {
|
|||
val uuid = player.uuid.toString()
|
||||
return players[uuid]
|
||||
}
|
||||
|
||||
/** 모든 저장된 플레이어 목록 (uuid -> name) */
|
||||
fun getAllPlayers(): Map<String, String> {
|
||||
return players.mapValues { it.value.name }
|
||||
}
|
||||
|
||||
/** 이름으로 UUID 찾기 */
|
||||
fun getUuidByName(name: String): String? {
|
||||
return players.entries.find { it.value.name.equals(name, ignoreCase = true) }?.key
|
||||
}
|
||||
|
||||
/** UUID가 저장된 플레이어인지 확인 */
|
||||
fun isKnownPlayer(uuid: String): Boolean {
|
||||
return players.containsKey(uuid)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
package com.beemer.essentials.gui
|
||||
|
||||
import com.beemer.essentials.config.AntimobConfig
|
||||
import com.mojang.authlib.GameProfile
|
||||
import net.minecraft.ChatFormatting
|
||||
import net.minecraft.core.component.DataComponents
|
||||
import net.minecraft.network.chat.Component
|
||||
|
|
@ -15,9 +14,13 @@ import net.minecraft.world.inventory.Slot
|
|||
import net.minecraft.world.item.ItemStack
|
||||
import net.minecraft.world.item.Items
|
||||
import net.minecraft.world.item.component.ResolvableProfile
|
||||
import java.util.*
|
||||
|
||||
class AntimobGui(syncId: Int, playerInv: Inventory, val container: Container, private val viewer: ServerPlayer) : AbstractContainerMenu(MenuType.GENERIC_9x2, syncId) {
|
||||
class AntimobGui(
|
||||
syncId: Int,
|
||||
playerInv: Inventory,
|
||||
val container: Container,
|
||||
viewer: ServerPlayer
|
||||
) : AbstractContainerMenu(MenuType.GENERIC_9x2, syncId) {
|
||||
companion object {
|
||||
const val CONTAINER_COLUMNS = 9
|
||||
const val CONTAINER_ROWS = 2
|
||||
|
|
@ -38,10 +41,14 @@ class AntimobGui(syncId: Int, playerInv: Inventory, val container: Container, pr
|
|||
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
|
||||
})
|
||||
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 (row in 0 until 3) {
|
||||
|
|
@ -62,7 +69,12 @@ class AntimobGui(syncId: Int, playerInv: Inventory, val container: Container, pr
|
|||
refreshContainerContents()
|
||||
}
|
||||
|
||||
override fun clicked(slotId: Int, button: Int, clickType: ClickType, player: net.minecraft.world.entity.player.Player) {
|
||||
override fun clicked(
|
||||
slotId: Int,
|
||||
button: Int,
|
||||
clickType: ClickType,
|
||||
player: net.minecraft.world.entity.player.Player
|
||||
) {
|
||||
if (player is ServerPlayer && slotId in 0 until CONTAINER_SIZE) {
|
||||
if (glassSlotToMob.containsKey(slotId)) {
|
||||
val mob = glassSlotToMob[slotId] ?: return
|
||||
|
|
@ -77,51 +89,55 @@ class AntimobGui(syncId: Int, playerInv: Inventory, val container: Container, pr
|
|||
|
||||
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
|
||||
|
||||
private fun makeMobHead(mob: String, player: ServerPlayer): ItemStack {
|
||||
private fun makeMobHead(mob: String): ItemStack {
|
||||
val headItem = ItemStack(Items.PLAYER_HEAD)
|
||||
|
||||
val uuid = when (mob) {
|
||||
"크리퍼" -> UUID.fromString("057b1c47-1321-4863-a6fe-8887f9ec265f")
|
||||
"가스트" -> UUID.fromString("063085a6-797f-4785-be1a-21cd7580f752")
|
||||
"엔더맨" -> UUID.fromString("40ffb372-12f6-4678-b3f2-2176bf56dd4b")
|
||||
else -> UUID.fromString("c06f8906-4c8a-4911-9c29-ea1dbd1aab82")
|
||||
}
|
||||
// MHF_ 스킨 이름 사용 (네트워크 요청 없음)
|
||||
val skinName =
|
||||
when (mob) {
|
||||
"크리퍼" -> "MHF_Creeper"
|
||||
"가스트" -> "MHF_Ghast"
|
||||
"엔더맨" -> "MHF_Enderman"
|
||||
else -> "MHF_Steve"
|
||||
}
|
||||
|
||||
val filledProfile: GameProfile = try {
|
||||
player.server.sessionService.fetchProfile(uuid, true)?.profile ?: player.gameProfile
|
||||
} catch (_: Exception) {
|
||||
player.gameProfile
|
||||
}
|
||||
val resolvableProfile =
|
||||
ResolvableProfile(
|
||||
java.util.Optional.of(skinName),
|
||||
java.util.Optional.empty<java.util.UUID>(),
|
||||
com.mojang.authlib.properties.PropertyMap()
|
||||
)
|
||||
|
||||
val resolvableProfile = try {
|
||||
ResolvableProfile(filledProfile)
|
||||
} catch (_: NoSuchMethodError) {
|
||||
val ctor = ResolvableProfile::class.java.getDeclaredConstructor(GameProfile::class.java)
|
||||
ctor.isAccessible = true
|
||||
ctor.newInstance(filledProfile)
|
||||
}
|
||||
|
||||
headItem.set(DataComponents.CUSTOM_NAME, Component.literal(mob).withStyle { it.withColor(ChatFormatting.YELLOW) })
|
||||
headItem.set(
|
||||
DataComponents.CUSTOM_NAME,
|
||||
Component.literal(mob).withStyle { it.withColor(ChatFormatting.YELLOW) }
|
||||
)
|
||||
headItem.set(DataComponents.PROFILE, resolvableProfile)
|
||||
return headItem
|
||||
}
|
||||
|
||||
private fun makeToggleGlass(mob: String): ItemStack {
|
||||
val enabled = AntimobConfig.get(mob)
|
||||
val glass = if (enabled) ItemStack(Items.GREEN_STAINED_GLASS_PANE) else ItemStack(Items.RED_STAINED_GLASS_PANE)
|
||||
val glass =
|
||||
if (enabled) ItemStack(Items.GREEN_STAINED_GLASS_PANE)
|
||||
else ItemStack(Items.RED_STAINED_GLASS_PANE)
|
||||
val statusText = if (enabled) "활성화" else "비활성화"
|
||||
glass.set(DataComponents.CUSTOM_NAME, Component.literal(statusText).withStyle { it.withColor(if (enabled) ChatFormatting.GREEN else ChatFormatting.RED) })
|
||||
glass.set(
|
||||
DataComponents.CUSTOM_NAME,
|
||||
Component.literal(statusText).withStyle {
|
||||
it.withColor(if (enabled) ChatFormatting.GREEN else ChatFormatting.RED)
|
||||
}
|
||||
)
|
||||
return glass
|
||||
}
|
||||
|
||||
private fun refreshContainerContents() {
|
||||
headSlotToMob.forEach { (slot, mob) ->
|
||||
container.setItem(slot, makeMobHead(mob, viewer))
|
||||
}
|
||||
glassSlotToMob.forEach { (slot, mob) ->
|
||||
container.setItem(slot, makeToggleGlass(mob))
|
||||
}
|
||||
headSlotToMob.forEach { (slot, mob) -> container.setItem(slot, makeMobHead(mob)) }
|
||||
glassSlotToMob.forEach { (slot, mob) -> container.setItem(slot, makeToggleGlass(mob)) }
|
||||
}
|
||||
}
|
||||
|
|
@ -86,4 +86,12 @@ object NicknameDataStore {
|
|||
fun hasNickname(uuid: UUID): Boolean {
|
||||
return nicknames.containsKey(uuid.toString())
|
||||
}
|
||||
|
||||
/** 닉네임으로 UUID 찾기 */
|
||||
fun getUuidByNickname(nickname: String): UUID? {
|
||||
val target = nickname.trim()
|
||||
if (target.isEmpty()) return null
|
||||
val entry = nicknames.entries.find { it.value.equals(target, ignoreCase = true) }
|
||||
return entry?.key?.let { UUID.fromString(it) }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue