diff --git a/kommand-core/v1.20.3/src/main/kotlin/io/github/monun/kommand/internal/compat/v1_20_3/NMSKommand.kt b/kommand-core/v1.20.3/src/main/kotlin/io/github/monun/kommand/internal/compat/v1_20_3/NMSKommand.kt
new file mode 100644
index 0000000..ab3042a
--- /dev/null
+++ b/kommand-core/v1.20.3/src/main/kotlin/io/github/monun/kommand/internal/compat/v1_20_3/NMSKommand.kt
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2023 Monun
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package io.github.monun.kommand.internal.compat.v1_20_3
+
+import com.mojang.brigadier.CommandDispatcher
+import com.mojang.brigadier.arguments.ArgumentType
+import com.mojang.brigadier.builder.ArgumentBuilder
+import com.mojang.brigadier.builder.LiteralArgumentBuilder
+import com.mojang.brigadier.builder.RequiredArgumentBuilder
+import com.mojang.brigadier.exceptions.CommandSyntaxException
+import com.mojang.brigadier.tree.CommandNode
+import com.mojang.brigadier.tree.LiteralCommandNode
+import com.mojang.brigadier.tree.RootCommandNode
+import io.github.monun.kommand.internal.*
+import io.github.monun.kommand.internal.compat.v1_20_3.NMSKommandContext.Companion.wrapContext
+import io.github.monun.kommand.internal.compat.v1_20_3.NMSKommandSource.Companion.wrapSource
+import net.minecraft.commands.CommandSourceStack
+import net.minecraft.commands.Commands
+import net.minecraft.server.MinecraftServer
+import org.bukkit.Bukkit
+import org.bukkit.craftbukkit.v1_20_R3.CraftServer
+import org.bukkit.craftbukkit.v1_20_R3.command.VanillaCommandWrapper
+import org.bukkit.craftbukkit.v1_20_R3.entity.CraftPlayer
+import org.bukkit.entity.Player
+
+
+class NMSKommand : AbstractKommand() {
+ private val server: MinecraftServer = (Bukkit.getServer() as CraftServer).server
+ private val vanillaCommands: Commands = server.vanillaCommandDispatcher
+ private val dispatcher: CommandDispatcher = vanillaCommands.dispatcher
+ private val root: RootCommandNode = dispatcher.root
+
+ private val children: MutableMap> = root["children"]
+ private val literals: MutableMap> = root["literals"]
+
+ private val commandMap = Bukkit.getCommandMap()
+
+ override fun test(name: String, aliases: Array): Boolean {
+ return literals[name] == null && aliases.all { literals[it] == null }
+ }
+
+ override fun register(dispatcher: KommandDispatcherImpl, aliases: List) {
+ val node = this.dispatcher.register(dispatcher.root.convert() as LiteralArgumentBuilder)
+ aliases.forEach { this.dispatcher.register(literal(it).redirect(node)) }
+
+ val root = dispatcher.root
+ commandMap.register(
+ root.fallbackPrefix,
+ VanillaCommandWrapper(vanillaCommands, node).apply {
+ description = root.description
+ usage = root.usage
+ permission = null
+
+ setAliases(aliases.toList())
+ }
+ )
+ }
+
+ override fun unregister(name: String) {
+ children.remove(name)
+ literals.remove(name)
+ }
+
+ override fun sendCommandsPacket(player: Player) {
+ vanillaCommands.sendCommands((player as CraftPlayer).handle)
+ }
+}
+
+@Suppress("UNCHECKED_CAST")
+private operator fun CommandNode<*>.get(name: String): T {
+ val field = CommandNode::class.java.getDeclaredField(name).apply { isAccessible = true }
+ return field.get(this) as T
+}
+
+private fun AbstractKommandNode.convert(): ArgumentBuilder {
+ return when (this) {
+ is RootNodeImpl, is LiteralNodeImpl -> literal(name)
+ is ArgumentNodeImpl -> {
+ val kommandArgument = argument as NMSKommandArgument<*>
+ val type = kommandArgument.type
+ argument(name, type).apply {
+ suggests { context, suggestionsBuilder ->
+ kommandArgument.listSuggestions(wrapContext(context), suggestionsBuilder)
+ }
+ }
+ }
+
+ else -> error("Unknown node type ${javaClass.name}")
+ }.apply {
+ requires { source ->
+ kotlin.runCatching {
+ requires(wrapSource(source))
+ }.onFailure {
+ if (it !is CommandSyntaxException) it.printStackTrace()
+ }.getOrThrow()
+ }
+
+ executes?.let { executes ->
+ executes { context ->
+ wrapSource(context.source).runCatching {
+ executes(this@convert.wrapContext(context))
+ }.onFailure {
+ if (it !is CommandSyntaxException) it.printStackTrace()
+ }.getOrThrow()
+ 1
+ }
+ }
+
+ nodes.forEach { node ->
+ then(node.convert())
+ }
+ }
+}
+
+private fun literal(name: String): LiteralArgumentBuilder {
+ return LiteralArgumentBuilder.literal(name)
+}
+
+private fun argument(name: String, argumentType: ArgumentType<*>): RequiredArgumentBuilder {
+ return RequiredArgumentBuilder.argument(name, argumentType)
+}
\ No newline at end of file
diff --git a/kommand-core/v1.20.3/src/main/kotlin/io/github/monun/kommand/internal/compat/v1_20_3/NMSKommandArgument.kt b/kommand-core/v1.20.3/src/main/kotlin/io/github/monun/kommand/internal/compat/v1_20_3/NMSKommandArgument.kt
new file mode 100644
index 0000000..4283b50
--- /dev/null
+++ b/kommand-core/v1.20.3/src/main/kotlin/io/github/monun/kommand/internal/compat/v1_20_3/NMSKommandArgument.kt
@@ -0,0 +1,539 @@
+/*
+ * Copyright (C) 2023 Monun
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package io.github.monun.kommand.internal.compat.v1_20_3
+
+import com.destroystokyo.paper.profile.CraftPlayerProfile
+import com.destroystokyo.paper.profile.PlayerProfile
+import com.google.gson.JsonObject
+import com.google.gson.JsonParser
+import com.mojang.brigadier.StringReader
+import com.mojang.brigadier.arguments.*
+import com.mojang.brigadier.context.CommandContext
+import com.mojang.brigadier.exceptions.DynamicCommandExceptionType
+import com.mojang.brigadier.exceptions.SimpleCommandExceptionType
+import com.mojang.brigadier.suggestion.SuggestionProvider
+import com.mojang.brigadier.suggestion.Suggestions
+import com.mojang.brigadier.suggestion.SuggestionsBuilder
+import io.github.monun.kommand.*
+import io.github.monun.kommand.internal.AbstractKommandArgument
+import io.github.monun.kommand.internal.ReflectionSupport
+import io.github.monun.kommand.wrapper.*
+import io.github.monun.kommand.wrapper.Rotation
+import io.papermc.paper.brigadier.PaperBrigadier
+import net.kyori.adventure.text.Component
+import net.kyori.adventure.text.format.NamedTextColor
+import net.kyori.adventure.text.format.TextColor
+import net.minecraft.commands.CommandBuildContext
+import net.minecraft.commands.CommandSourceStack
+import net.minecraft.commands.arguments.*
+import net.minecraft.commands.arguments.blocks.BlockPredicateArgument
+import net.minecraft.commands.arguments.blocks.BlockStateArgument
+import net.minecraft.commands.arguments.coordinates.*
+import net.minecraft.commands.arguments.item.FunctionArgument
+import net.minecraft.commands.arguments.item.ItemArgument
+import net.minecraft.commands.arguments.item.ItemPredicateArgument
+import net.minecraft.commands.synchronization.SuggestionProviders
+import net.minecraft.core.Vec3i
+import net.minecraft.core.registries.Registries
+import net.minecraft.server.MinecraftServer
+import net.minecraft.server.level.ColumnPos
+import net.minecraft.world.level.block.state.pattern.BlockInWorld
+import org.bukkit.*
+import org.bukkit.advancement.Advancement
+import org.bukkit.block.Block
+import org.bukkit.block.data.BlockData
+import org.bukkit.craftbukkit.v1_20_R3.CraftParticle
+import org.bukkit.craftbukkit.v1_20_R3.block.CraftBlock
+import org.bukkit.craftbukkit.v1_20_R3.block.data.CraftBlockData
+import org.bukkit.craftbukkit.v1_20_R3.enchantments.CraftEnchantment
+import org.bukkit.craftbukkit.v1_20_R3.inventory.CraftItemStack
+import org.bukkit.craftbukkit.v1_20_R3.potion.CraftPotionEffectType
+import org.bukkit.craftbukkit.v1_20_R3.util.CraftNamespacedKey
+import org.bukkit.enchantments.Enchantment
+import org.bukkit.entity.Entity
+import org.bukkit.entity.Player
+import org.bukkit.inventory.ItemStack
+import org.bukkit.inventory.Recipe
+import org.bukkit.potion.PotionEffectType
+import org.bukkit.scoreboard.DisplaySlot
+import org.bukkit.scoreboard.Objective
+import org.bukkit.scoreboard.Team
+import java.lang.reflect.Method
+import java.util.*
+import java.util.concurrent.CompletableFuture
+
+open class NMSKommandArgument(
+ val type: ArgumentType<*>,
+ private val provider: (NMSKommandContext, name: String) -> T,
+ private val defaultSuggestionProvider: SuggestionProvider? = null
+) : AbstractKommandArgument() {
+ private companion object {
+ private val originalMethod: Method = ArgumentType::class.java.declaredMethods.find { method ->
+ val parameterTypes = method.parameterTypes
+
+ parameterTypes.count() == 2
+ && parameterTypes[0] == CommandContext::class.java
+ && parameterTypes[1] == SuggestionsBuilder::class.java
+ } ?: error("Not found listSuggestion")
+
+ private val overrideSuggestions = hashMapOf, Boolean>()
+
+ private fun checkOverrideSuggestions(type: Class<*>): Boolean = overrideSuggestions.computeIfAbsent(type) {
+ originalMethod.declaringClass != type.getMethod(
+ originalMethod.name,
+ *originalMethod.parameterTypes
+ ).declaringClass
+ }
+ }
+
+ private val hasOverrideSuggestion: Boolean by lazy {
+ checkOverrideSuggestions(type.javaClass)
+ }
+
+ fun from(context: NMSKommandContext, name: String): T {
+ return provider(context, name)
+ }
+
+ fun listSuggestions(
+ context: NMSKommandContext,
+ builder: SuggestionsBuilder
+ ): CompletableFuture {
+ this.suggestionProvider?.let {
+ val suggestion = NMSKommandSuggestion(builder)
+ it(suggestion, context)
+ if (!suggestion.suggestsDefault) return builder.buildFuture()
+ }
+
+ defaultSuggestionProvider?.let { return it.getSuggestions(context.handle, builder) }
+ if (hasOverrideSuggestion) return type.listSuggestions(context.handle, builder)
+ return builder.buildFuture()
+ }
+}
+
+infix fun ArgumentType<*>.provideDynamic(
+ provider: (context: NMSKommandContext, name: String) -> T
+): NMSKommandArgument {
+ return NMSKommandArgument(this, provider)
+}
+
+infix fun ArgumentType<*>.provide(
+ provider: (context: CommandContext, name: String) -> T
+): NMSKommandArgument {
+ return NMSKommandArgument(this, { context, name ->
+ provider(context.handle, name)
+ })
+}
+
+infix fun Pair, SuggestionProvider>.provide(
+ provider: (context: CommandContext, name: String) -> T
+): NMSKommandArgument {
+ return NMSKommandArgument(first, { context, name ->
+ provider(context.handle, name)
+ }, defaultSuggestionProvider = second)
+}
+
+class NMSKommandArgumentSupport : KommandArgumentSupport {
+ // com.mojang.brigadier.arguments
+
+ override fun bool(): KommandArgument {
+ return BoolArgumentType.bool() provide BoolArgumentType::getBool
+ }
+
+ override fun int(minimum: Int, maximum: Int): KommandArgument {
+ return IntegerArgumentType.integer(minimum, maximum) provide IntegerArgumentType::getInteger
+ }
+
+ override fun float(minimum: Float, maximum: Float): KommandArgument {
+ return FloatArgumentType.floatArg(minimum, maximum) provide FloatArgumentType::getFloat
+ }
+
+ override fun double(minimum: Double, maximum: Double): KommandArgument {
+ return DoubleArgumentType.doubleArg(minimum, maximum) provide DoubleArgumentType::getDouble
+ }
+
+ override fun long(minimum: Long, maximum: Long): KommandArgument {
+ return LongArgumentType.longArg(minimum, maximum) provide LongArgumentType::getLong
+ }
+
+ override fun string(type: StringType): KommandArgument {
+ return type.createType() provide StringArgumentType::getString
+ }
+
+ // net.minecraft.commands.arguments
+
+ override fun angle(): KommandArgument {
+ return AngleArgument.angle() provide AngleArgument::getAngle
+ }
+
+ override fun color(): KommandArgument {
+ return ColorArgument.color() provide { context, name ->
+ ColorArgument.getColor(context, name).color?.let { color ->
+ NamedTextColor.namedColor(color) ?: TextColor.color(color)
+ } ?: NamedTextColor.WHITE
+ }
+ }
+
+ override fun component(): KommandArgument {
+ return ComponentArgument.textComponent() provide { context, name ->
+ val nmsComponent = ComponentArgument.getComponent(context, name)
+ PaperBrigadier.componentFromMessage(nmsComponent)
+ }
+ }
+
+ override fun compoundTag(): KommandArgument {
+ return CompoundTagArgument.compoundTag() provide { context, name ->
+ val compoundTag = CompoundTagArgument.getCompoundTag(context, name)
+ JsonParser.parseString(compoundTag.toString()) as JsonObject
+ }
+ }
+
+ override fun dimension(): KommandArgument {
+ return DimensionArgument.dimension() provide { context, name ->
+ DimensionArgument.getDimension(context, name).world
+ }
+ }
+
+ override fun entityAnchor(): KommandArgument {
+ return EntityAnchorArgument.anchor() provide { context, name ->
+ when (EntityAnchorArgument.getAnchor(context, name) ?: error("Unknown entity anchor")) {
+ EntityAnchorArgument.Anchor.FEET -> EntityAnchor.FEET
+ EntityAnchorArgument.Anchor.EYES -> EntityAnchor.EYES
+ }
+ }
+ }
+
+ override fun entity(): KommandArgument {
+ return EntityArgument.entity() provide { context, name ->
+ EntityArgument.getEntity(context, name).bukkitEntity
+ }
+ }
+
+ override fun entities(): KommandArgument> {
+ return EntityArgument.entities() provide { context, name ->
+ EntityArgument.getEntities(context, name).map { it.bukkitEntity }
+ }
+ }
+
+ override fun player(): KommandArgument {
+ return EntityArgument.player() provide { context, name ->
+ EntityArgument.getPlayer(context, name).bukkitEntity
+ }
+ }
+
+ override fun players(): KommandArgument> {
+ return EntityArgument.players() provide { context, name ->
+ EntityArgument.getPlayers(context, name).map { it.bukkitEntity }
+ }
+ }
+
+ override fun summonableEntity(): KommandArgument {
+ return ResourceArgument.resource(
+ commandBuildContext,
+ Registries.ENTITY_TYPE
+ ) to SuggestionProviders.SUMMONABLE_ENTITIES provide { context, name ->
+ CraftNamespacedKey.fromMinecraft(ResourceArgument.getSummonableEntityType(context, name).key().location())
+ }
+ }
+
+ override fun profile(): KommandArgument> {
+ return GameProfileArgument.gameProfile() provide { context, name ->
+ val nms = GameProfileArgument.getGameProfiles(context, name)
+ nms.map { CraftPlayerProfile.asBukkitMirror(it) }
+ }
+ }
+
+ private val enchantmentMap = Enchantment.values().map { it as CraftEnchantment }.associateBy { it.handle }
+
+ override fun enchantment(): KommandArgument {
+ return ResourceArgument.resource(commandBuildContext, Registries.ENCHANTMENT) provide { context, name ->
+ val key = ResourceArgument.getEnchantment(context, name)
+ val value = key.value()
+
+ enchantmentMap[value] ?: error("Not found enchantment ${value.getFullname(0)}")
+ }
+ }
+
+ override fun message(): KommandArgument {
+ return MessageArgument.message() provide { context, name ->
+ PaperBrigadier.componentFromMessage(MessageArgument.getMessage(context, name))
+ }
+ }
+
+ private val mobEffectMap = PotionEffectType.values().map { it as CraftPotionEffectType }.associateBy { it.handle }
+
+ override fun mobEffect(): KommandArgument {
+ return ResourceArgument.resource(commandBuildContext, Registries.MOB_EFFECT) provide { context, name ->
+ val key = ResourceArgument.getMobEffect(context, name)
+ val value = key.value()
+
+ mobEffectMap[value] ?: error("Not found mob effect ${value.displayName}")
+ }
+ }
+
+ override fun objective(): KommandArgument {
+ return ObjectiveArgument.objective() provide { context, name ->
+ val nms = ObjectiveArgument.getObjective(context, name)
+ Bukkit.getScoreboardManager().mainScoreboard.getObjective(nms.name) ?: error("Objective error!")
+ }
+ }
+
+ override fun objectiveCriteria(): KommandArgument {
+ return ObjectiveCriteriaArgument.criteria() provide { context, name ->
+ ObjectiveCriteriaArgument.getCriteria(context, name).name
+ }
+ }
+
+ override fun particle(): KommandArgument {
+ return ParticleArgument.particle(commandBuildContext) provide { context, name ->
+ CraftParticle.minecraftToBukkit(ParticleArgument.getParticle(context, name).type)
+ }
+ }
+
+ override fun intRange(): KommandArgument {
+ return RangeArgument.intRange() provide { context, name ->
+ val nms = RangeArgument.Ints.getRange(context, name)
+ val min = nms.min.orElse(Int.MIN_VALUE)
+ val max = nms.max.orElse(Int.MAX_VALUE)
+ min..max
+ }
+ }
+
+ //float
+ override fun doubleRange(): KommandArgument> {
+ return RangeArgument.floatRange() provide { context, name ->
+ val nms = RangeArgument.Floats.getRange(context, name)
+ val min = nms.min.orElse(-Double.MAX_VALUE)
+ val max = nms.max.orElse(Double.MAX_VALUE)
+ min..max
+ }
+ }
+
+ override fun advancement(): KommandArgument {
+ return ResourceLocationArgument.id() provide { context, name ->
+ val nms = ResourceLocationArgument.getAdvancement(context, name)
+ nms.toBukkit()
+ }
+ }
+
+ override fun recipe(): KommandArgument {
+ return ResourceLocationArgument.id() to SuggestionProviders.ALL_RECIPES provide { context, name ->
+ val nms = ResourceLocationArgument.getRecipe(context, name)
+ nms.toBukkitRecipe()
+ }
+ }
+
+ private val displaySlots = DisplaySlot.values().associateBy { it.id }
+
+ override fun displaySlot(): KommandArgument {
+ return ScoreboardSlotArgument.displaySlot() provide { context, name ->
+ val slotName = ScoreboardSlotArgument.getDisplaySlot(context, name).serializedName
+ displaySlots[slotName] ?: error("Not found display slot $slotName")
+ }
+ }
+
+ override fun score(): KommandArgument {
+ return ScoreHolderArgument.scoreHolder() provide { context, name ->
+ ScoreHolderArgument.getName(context, name).toString()
+ }
+ }
+
+ override fun scores(): KommandArgument> {
+ return ScoreHolderArgument.scoreHolders() provide { context, name ->
+ ScoreHolderArgument.getNames(context, name).map { it.toString() }
+ }
+ }
+
+ override fun slot(): KommandArgument {
+ return SlotArgument.slot() provide { context, name ->
+ SlotArgument.getSlot(context, name)
+ }
+ }
+
+// new SimpleCommandExceptionType(Component.translatable("commands.team.option.seeFriendlyInvisibles.alreadyEnabled"));
+
+ /**
+ * TeamArgument의 error
+ */
+ private val errorTeamNotFound: DynamicCommandExceptionType = DynamicCommandExceptionType { name: Any? ->
+ net.minecraft.network.chat.Component.translatable(
+ "team.notFound",
+ name
+ )
+ }
+
+ override fun team(): KommandArgument {
+ return TeamArgument.team() provide { context, name ->
+ /**
+ * CraftTeam이 패키지 접근만 허용하여 생성불가
+ * PlayerTeam(nms) -> name -> BukkitTeam 순으로 가져와야함
+ * 하지만...
+ *
+ * val team: PlayerTeam = TeamArgument.getTeam(context, name)
+ * val teamName = team.name <-- spigot mapping에서 오류가 있음
+ * java.lang.NoSuchMethodError: 'java.lang.String net.minecraft.world.scores.ScoreboardTeam.b()'
+ */
+ val teamName: String = context.getArgument(name, String::class.java)
+ Bukkit.getScoreboardManager().mainScoreboard.getTeam(teamName) ?: throw errorTeamNotFound.create(teamName)
+ }
+ }
+
+ override fun time(): KommandArgument {
+ val argument = TimeArgument.time()
+ return argument provide { context, name ->
+ val time = context.getArgument(name, String::class.java)
+ argument.parse(StringReader(time))
+ }
+ }
+
+ override fun uuid(): KommandArgument {
+ return UuidArgument.uuid() provide UuidArgument::getUuid
+ }
+
+ companion object {
+ private val commandBuildContext: CommandBuildContext = ReflectionSupport.getFieldInstance(
+ MinecraftServer.getServer().resources.managers,
+ "commandBuildContext",
+ "c"
+ )
+ }
+
+ // net.minecraft.commands.arguments.blocks
+
+ override fun blockPredicate(): KommandArgument<(Block) -> Boolean> {
+ return BlockPredicateArgument.blockPredicate(commandBuildContext) provide { context, name ->
+ { block ->
+ BlockPredicateArgument.getBlockPredicate(context, name)
+ .test(BlockInWorld(context.source.level, (block as CraftBlock).position, true))
+ }
+ }
+ }
+
+ override fun blockState(): KommandArgument {
+ return BlockStateArgument.block(commandBuildContext) provide { context, name ->
+ CraftBlockData.fromData(BlockStateArgument.getBlock(context, name).state)
+ }
+ }
+
+ // net.minecraft.commands.arguments.coordinates
+
+ override fun blockPosition(type: PositionLoadType): KommandArgument {
+ /**
+ * Issue [https://github.com/monun/kommand/issues/18]
+ *
+ * mojang mapping -> spigot mapping 변환시 상속된 타입의 메서드 이름 or 필드 이름을 잘 감지하지 못함
+ * 변수의 타입을 메서드, 필드가 선언된 타입으로 정확히 선언해야함
+ * [net.minecraft.core.BlockPos]의 경우 [net.minecraft.core.Vec3i]를 상속받아 상위의 메서드를 가지고 있음
+ * BlockPos#getX <- 실패
+ * Vec3i#getX <- 성공
+ *
+ * 아마 remapping 작업이 다음과 같은 바이트 코드만 감지하는듯
+ * invokeVirtual 'Vec3i#getX'
+ */
+ return BlockPosArgument.blockPos() provide { context, name ->
+ val blockPosition: Vec3i = when (type) {
+ PositionLoadType.LOADED -> BlockPosArgument.getLoadedBlockPos(context, name)
+ PositionLoadType.SPAWNABLE -> BlockPosArgument.getSpawnablePos(context, name)
+ }
+
+ BlockPosition3D(blockPosition.x, blockPosition.y, blockPosition.z)
+ }
+ }
+
+ override fun blockPosition2D(): KommandArgument {
+ return ColumnPosArgument.columnPos() provide { context, name ->
+ val columnPosition: ColumnPos = ColumnPosArgument.getColumnPos(context, name)
+ BlockPosition2D(columnPosition.x, columnPosition.z)
+ }
+ }
+
+ override fun position(): KommandArgument {
+ return Vec3Argument.vec3() provide { context, name ->
+ val vec3 = Vec3Argument.getVec3(context, name)
+ Position3D(vec3.x, vec3.y, vec3.z)
+ }
+ }
+
+ override fun position2D(): KommandArgument {
+ return Vec2Argument.vec2() provide { context, name ->
+ val vec2 = Vec2Argument.getVec2(context, name)
+ Position2D(vec2.x.toDouble(), vec2.y.toDouble())
+ }
+ }
+
+ override fun rotation(): KommandArgument {
+ return RotationArgument.rotation() provide { context, name ->
+ val rotation = RotationArgument.getRotation(context, name).getRotation(context.source)
+ Rotation(rotation.x, rotation.y)
+ }
+ }
+
+ override fun swizzle(): KommandArgument> {
+ return SwizzleArgument.swizzle() provide { context, name ->
+ EnumSet.copyOf(SwizzleArgument.getSwizzle(context, name).map { axis ->
+ Axis.valueOf(axis.getName().uppercase())
+ })
+ }
+ }
+
+ // net.minecraft.commands.arguments.item
+
+ override fun function(): KommandArgument<() -> Unit> {
+ return FunctionArgument.functions() provide { context, name ->
+ {
+ FunctionArgument.getFunctions(context, name).map { function ->
+ context.source.server.functions.execute(function, context.source)
+ }
+ }
+ }
+ }
+
+ override fun item(): KommandArgument {
+ return ItemArgument.item(commandBuildContext) provide { context, name ->
+ CraftItemStack.asBukkitCopy(ItemArgument.getItem(context, name).createItemStack(1, false))
+ }
+ }
+
+ override fun itemPredicate(): KommandArgument<(ItemStack) -> Boolean> {
+ return ItemPredicateArgument.itemPredicate(commandBuildContext) provide { context, name ->
+ { itemStack ->
+ ItemPredicateArgument.getItemPredicate(context, name).test(CraftItemStack.asNMSCopy(itemStack))
+ }
+ }
+ }
+
+ private val unknownArgument =
+ SimpleCommandExceptionType(net.minecraft.network.chat.Component.translatable("command.unknown.argument"))
+
+ override fun dynamic(
+ type: StringType,
+ function: KommandSource.(context: KommandContext, input: String) -> T?
+ ): KommandArgument {
+ return type.createType() provideDynamic { context, name ->
+ context.source.function(context, StringArgumentType.getString(context.handle, name))
+ ?: throw unknownArgument.create()
+ }
+ }
+}
+
+fun StringType.createType(): StringArgumentType {
+ return when (this) {
+ StringType.SINGLE_WORD -> StringArgumentType.word()
+ StringType.QUOTABLE_PHRASE -> StringArgumentType.string()
+ StringType.GREEDY_PHRASE -> StringArgumentType.greedyString()
+ }
+}
diff --git a/kommand-core/v1.20.3/src/main/kotlin/io/github/monun/kommand/internal/compat/v1_20_3/NMSKommandContext.kt b/kommand-core/v1.20.3/src/main/kotlin/io/github/monun/kommand/internal/compat/v1_20_3/NMSKommandContext.kt
new file mode 100644
index 0000000..7737206
--- /dev/null
+++ b/kommand-core/v1.20.3/src/main/kotlin/io/github/monun/kommand/internal/compat/v1_20_3/NMSKommandContext.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2023 Monun
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package io.github.monun.kommand.internal.compat.v1_20_3
+
+import com.mojang.brigadier.context.CommandContext
+import io.github.monun.kommand.KommandContext
+import io.github.monun.kommand.KommandSource
+import io.github.monun.kommand.internal.AbstractKommandNode
+import io.github.monun.kommand.internal.ArgumentNodeImpl
+import io.github.monun.kommand.ref.getValue
+import io.github.monun.kommand.ref.weak
+import io.github.monun.kommand.internal.compat.v1_20_3.NMSKommandSource.Companion.wrapSource
+import net.minecraft.commands.CommandSourceStack
+import java.util.*
+
+class NMSKommandContext private constructor(
+ private val node: AbstractKommandNode,
+ handle: CommandContext
+) : KommandContext {
+ companion object {
+ private val refs = WeakHashMap, NMSKommandContext>()
+
+ fun AbstractKommandNode.wrapContext(context: CommandContext): NMSKommandContext =
+ refs.computeIfAbsent(context) {
+ NMSKommandContext(this, context)
+ }
+ }
+
+ internal val handle by weak(handle)
+
+ override val source: KommandSource by lazy { wrapSource(handle.source) }
+
+ override val input: String
+ get() = handle.input
+
+ @Suppress("UNCHECKED_CAST")
+ override fun get(name: String): T {
+ val argumentNode = node.findArgumentNode(name) ?: error("Not found argument node $name")
+ val argument = argumentNode.argument as NMSKommandArgument<*>
+
+ return argument.from(this, name) as T
+ }
+}
+
+private fun AbstractKommandNode.findArgumentNode(name: String): ArgumentNodeImpl? {
+ var node: AbstractKommandNode? = this
+
+ while (node != null) {
+ if (node is ArgumentNodeImpl && node.name == name) return node
+ node = node.parent
+ }
+
+ return null
+}
\ No newline at end of file
diff --git a/kommand-core/v1.20.3/src/main/kotlin/io/github/monun/kommand/internal/compat/v1_20_3/NMSKommandSource.kt b/kommand-core/v1.20.3/src/main/kotlin/io/github/monun/kommand/internal/compat/v1_20_3/NMSKommandSource.kt
new file mode 100644
index 0000000..0fc2d85
--- /dev/null
+++ b/kommand-core/v1.20.3/src/main/kotlin/io/github/monun/kommand/internal/compat/v1_20_3/NMSKommandSource.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2023 Monun
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package io.github.monun.kommand.internal.compat.v1_20_3
+
+import io.github.monun.kommand.KommandSource
+import io.github.monun.kommand.ref.getValue
+import io.github.monun.kommand.ref.weak
+import io.github.monun.kommand.internal.compat.v1_20_3.wrapper.NMSEntityAnchor
+import io.github.monun.kommand.wrapper.EntityAnchor
+import io.github.monun.kommand.wrapper.Position3D
+import io.github.monun.kommand.wrapper.Rotation
+import io.papermc.paper.brigadier.PaperBrigadier
+import net.kyori.adventure.text.Component
+import net.minecraft.commands.CommandSourceStack
+import org.bukkit.Location
+import org.bukkit.World
+import org.bukkit.command.CommandSender
+import org.bukkit.entity.Entity
+import org.bukkit.entity.Player
+import java.util.*
+
+class NMSKommandSource private constructor(
+ handle: CommandSourceStack
+) : KommandSource {
+ companion object {
+ private val refs = WeakHashMap()
+
+ fun wrapSource(source: CommandSourceStack): NMSKommandSource =
+ refs.computeIfAbsent(source) {
+ NMSKommandSource(source)
+ }
+ }
+
+ private val handle by weak(handle)
+
+ override val displayName: Component
+ get() = PaperBrigadier.componentFromMessage(handle.displayName)
+
+ override val sender: CommandSender
+ get() = handle.bukkitSender
+
+ override val entity: Entity
+ get() = handle.entityOrException.bukkitEntity
+
+ override val entityOrNull: Entity?
+ get() = handle.entity?.bukkitEntity
+
+ override val player: Player
+ get() = handle.playerOrException.bukkitEntity
+
+ override val playerOrNull: Player?
+ get() = handle.entity?.bukkitEntity?.takeIf { it is Player } as Player?
+
+ override val position: Position3D
+ get() = handle.position.run { Position3D(x, y, z) }
+
+ override val rotation: Rotation
+ get() = handle.rotation.run { Rotation(x, y) }
+
+ override val anchor: EntityAnchor
+ get() = NMSEntityAnchor(handle.anchor)
+
+ override val world: World
+ get() = handle.level.world
+
+ override val location: Location
+ get() = position.toLocation(handle.level.world, rotation)
+
+ override fun hasPermission(level: Int): Boolean {
+ return handle.hasPermission(level)
+ }
+
+ override fun hasPermission(level: Int, bukkitPermission: String): Boolean {
+ return handle.hasPermission(level, bukkitPermission)
+ }
+}
\ No newline at end of file
diff --git a/kommand-core/v1.20.3/src/main/kotlin/io/github/monun/kommand/internal/compat/v1_20_3/NMSKommandSuggestion.kt b/kommand-core/v1.20.3/src/main/kotlin/io/github/monun/kommand/internal/compat/v1_20_3/NMSKommandSuggestion.kt
new file mode 100644
index 0000000..7da53cc
--- /dev/null
+++ b/kommand-core/v1.20.3/src/main/kotlin/io/github/monun/kommand/internal/compat/v1_20_3/NMSKommandSuggestion.kt
@@ -0,0 +1,94 @@
+/*
+ * Kommand
+ * Copyright (C) 2021 Monun
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package io.github.monun.kommand.internal.compat.v1_20_3
+
+import com.mojang.brigadier.suggestion.SuggestionsBuilder
+import io.github.monun.kommand.AbstractKommandSuggestion
+import io.github.monun.kommand.ref.getValue
+import io.github.monun.kommand.ref.weak
+import io.papermc.paper.brigadier.PaperBrigadier
+import net.kyori.adventure.text.ComponentLike
+import net.minecraft.commands.SharedSuggestionProvider
+import java.util.*
+
+class NMSKommandSuggestion(
+ handle: SuggestionsBuilder
+) : AbstractKommandSuggestion() {
+ private val handle by weak(handle)
+
+ override fun suggest(value: Int, tooltip: (() -> ComponentLike)?) {
+ if (tooltip == null) handle.suggest(value)
+ else handle.suggest(value, PaperBrigadier.message(tooltip()))
+ }
+
+ override fun suggest(text: String, tooltip: (() -> ComponentLike)?) {
+ if (tooltip == null) handle.suggest(text)
+ else handle.suggest(text, PaperBrigadier.message(tooltip()))
+ }
+
+ override fun suggest(candidates: Iterable, tooltip: ((String) -> ComponentLike)?) {
+ val handle = handle
+ val input: String = handle.remaining.lowercase(Locale.ROOT)
+
+ candidates.forEach { candidate ->
+ val lowerCandidate = candidate.lowercase(Locale.ROOT)
+
+ if (SharedSuggestionProvider.matchesSubStr(input, lowerCandidate)) {
+ if (tooltip == null) handle.suggest(candidate)
+ else handle.suggest(candidate, PaperBrigadier.message(tooltip(candidate)))
+ }
+ }
+ }
+
+ override fun suggest(
+ candidates: Iterable,
+ transform: (T) -> String,
+ tooltip: ((T) -> ComponentLike)?
+ ) {
+ val handle = handle
+ val input: String = handle.remaining.lowercase(Locale.ROOT)
+
+ candidates.forEach {
+ val candidate = transform(it)
+ val lowerCandidate = transform(it).lowercase(Locale.ROOT)
+
+ if (SharedSuggestionProvider.matchesSubStr(input, lowerCandidate)) {
+ if (tooltip == null) handle.suggest(candidate)
+ else handle.suggest(candidate, PaperBrigadier.message(tooltip(it)))
+ }
+ }
+ }
+
+ override fun suggest(
+ candidates: Map,
+ tooltip: ((T) -> ComponentLike)?
+ ) {
+ val handle = handle
+ val input: String = handle.remaining.lowercase(Locale.ROOT)
+
+ candidates.forEach { (key, value) ->
+ val lowerCandidate = key.lowercase(Locale.ROOT)
+
+ if (SharedSuggestionProvider.matchesSubStr(input, lowerCandidate)) {
+ if (tooltip == null) handle.suggest(key)
+ else handle.suggest(key, PaperBrigadier.message(tooltip(value)))
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/kommand-core/v1.20.3/src/main/kotlin/io/github/monun/kommand/internal/compat/v1_20_3/wrapper/NMSEntityAnchor.kt b/kommand-core/v1.20.3/src/main/kotlin/io/github/monun/kommand/internal/compat/v1_20_3/wrapper/NMSEntityAnchor.kt
new file mode 100644
index 0000000..f6f84d4
--- /dev/null
+++ b/kommand-core/v1.20.3/src/main/kotlin/io/github/monun/kommand/internal/compat/v1_20_3/wrapper/NMSEntityAnchor.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2023 Monun
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package io.github.monun.kommand.internal.compat.v1_20_3.wrapper
+
+import io.github.monun.kommand.KommandSource
+import io.github.monun.kommand.wrapper.EntityAnchor
+import net.minecraft.commands.arguments.EntityAnchorArgument
+import net.minecraft.world.entity.Entity
+import org.bukkit.craftbukkit.v1_20_R3.entity.CraftEntity
+import org.bukkit.util.Vector
+
+class NMSEntityAnchor(
+ private val handle: EntityAnchorArgument.Anchor
+) : EntityAnchor {
+ override val name: String
+ get() = handle.name
+
+ override fun applyTo(entity: org.bukkit.entity.Entity): Vector {
+ val nmsEntity: Entity = (entity as CraftEntity).handle
+ return handle.apply(nmsEntity).run { Vector(x, y, z) }
+ }
+
+ override fun applyTo(source: KommandSource): Vector {
+ return applyTo(source.entity)
+ }
+}
\ No newline at end of file
diff --git a/kommand-core/v1.20.3/src/main/kotlin/io/github/monun/kommand/internal/compat/v1_20_3/wrapper/NMSWrapperSupport.kt b/kommand-core/v1.20.3/src/main/kotlin/io/github/monun/kommand/internal/compat/v1_20_3/wrapper/NMSWrapperSupport.kt
new file mode 100644
index 0000000..d2f087b
--- /dev/null
+++ b/kommand-core/v1.20.3/src/main/kotlin/io/github/monun/kommand/internal/compat/v1_20_3/wrapper/NMSWrapperSupport.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 Monun
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package io.github.monun.kommand.internal.compat.v1_20_3.wrapper
+
+import io.github.monun.kommand.wrapper.EntityAnchor
+import io.github.monun.kommand.wrapper.WrapperSupport
+import net.minecraft.commands.arguments.EntityAnchorArgument
+
+class NMSWrapperSupport : WrapperSupport {
+ override fun entityAnchorFeet(): EntityAnchor {
+ return NMSEntityAnchor(EntityAnchorArgument.Anchor.FEET)
+ }
+
+ override fun entityAnchorEyes(): EntityAnchor {
+ return NMSEntityAnchor(EntityAnchorArgument.Anchor.EYES)
+ }
+}
\ No newline at end of file
diff --git a/kommand-core/v1.20.4/src/main/kotlin/io/github/monun/kommand/internal/compat/v1_20_4/NMSKommand.kt b/kommand-core/v1.20.4/src/main/kotlin/io/github/monun/kommand/internal/compat/v1_20_4/NMSKommand.kt
new file mode 100644
index 0000000..0b8915b
--- /dev/null
+++ b/kommand-core/v1.20.4/src/main/kotlin/io/github/monun/kommand/internal/compat/v1_20_4/NMSKommand.kt
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2023 Monun
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package io.github.monun.kommand.internal.compat.v1_20_4
+
+import com.mojang.brigadier.CommandDispatcher
+import com.mojang.brigadier.arguments.ArgumentType
+import com.mojang.brigadier.builder.ArgumentBuilder
+import com.mojang.brigadier.builder.LiteralArgumentBuilder
+import com.mojang.brigadier.builder.RequiredArgumentBuilder
+import com.mojang.brigadier.exceptions.CommandSyntaxException
+import com.mojang.brigadier.tree.CommandNode
+import com.mojang.brigadier.tree.LiteralCommandNode
+import com.mojang.brigadier.tree.RootCommandNode
+import io.github.monun.kommand.internal.*
+import io.github.monun.kommand.internal.compat.v1_20_4.NMSKommandContext.Companion.wrapContext
+import io.github.monun.kommand.internal.compat.v1_20_4.NMSKommandSource.Companion.wrapSource
+import net.minecraft.commands.CommandSourceStack
+import net.minecraft.commands.Commands
+import net.minecraft.server.MinecraftServer
+import org.bukkit.Bukkit
+import org.bukkit.craftbukkit.v1_20_R3.CraftServer
+import org.bukkit.craftbukkit.v1_20_R3.command.VanillaCommandWrapper
+import org.bukkit.craftbukkit.v1_20_R3.entity.CraftPlayer
+import org.bukkit.entity.Player
+
+
+class NMSKommand : AbstractKommand() {
+ private val server: MinecraftServer = (Bukkit.getServer() as CraftServer).server
+ private val vanillaCommands: Commands = server.vanillaCommandDispatcher
+ private val dispatcher: CommandDispatcher = vanillaCommands.dispatcher
+ private val root: RootCommandNode = dispatcher.root
+
+ private val children: MutableMap> = root["children"]
+ private val literals: MutableMap> = root["literals"]
+
+ private val commandMap = Bukkit.getCommandMap()
+
+ override fun test(name: String, aliases: Array): Boolean {
+ return literals[name] == null && aliases.all { literals[it] == null }
+ }
+
+ override fun register(dispatcher: KommandDispatcherImpl, aliases: List) {
+ val node = this.dispatcher.register(dispatcher.root.convert() as LiteralArgumentBuilder)
+ aliases.forEach { this.dispatcher.register(literal(it).redirect(node)) }
+
+ val root = dispatcher.root
+ commandMap.register(
+ root.fallbackPrefix,
+ VanillaCommandWrapper(vanillaCommands, node).apply {
+ description = root.description
+ usage = root.usage
+ permission = null
+
+ setAliases(aliases.toList())
+ }
+ )
+ }
+
+ override fun unregister(name: String) {
+ children.remove(name)
+ literals.remove(name)
+ }
+
+ override fun sendCommandsPacket(player: Player) {
+ vanillaCommands.sendCommands((player as CraftPlayer).handle)
+ }
+}
+
+@Suppress("UNCHECKED_CAST")
+private operator fun CommandNode<*>.get(name: String): T {
+ val field = CommandNode::class.java.getDeclaredField(name).apply { isAccessible = true }
+ return field.get(this) as T
+}
+
+private fun AbstractKommandNode.convert(): ArgumentBuilder {
+ return when (this) {
+ is RootNodeImpl, is LiteralNodeImpl -> literal(name)
+ is ArgumentNodeImpl -> {
+ val kommandArgument = argument as NMSKommandArgument<*>
+ val type = kommandArgument.type
+ argument(name, type).apply {
+ suggests { context, suggestionsBuilder ->
+ kommandArgument.listSuggestions(wrapContext(context), suggestionsBuilder)
+ }
+ }
+ }
+
+ else -> error("Unknown node type ${javaClass.name}")
+ }.apply {
+ requires { source ->
+ kotlin.runCatching {
+ requires(wrapSource(source))
+ }.onFailure {
+ if (it !is CommandSyntaxException) it.printStackTrace()
+ }.getOrThrow()
+ }
+
+ executes?.let { executes ->
+ executes { context ->
+ wrapSource(context.source).runCatching {
+ executes(this@convert.wrapContext(context))
+ }.onFailure {
+ if (it !is CommandSyntaxException) it.printStackTrace()
+ }.getOrThrow()
+ 1
+ }
+ }
+
+ nodes.forEach { node ->
+ then(node.convert())
+ }
+ }
+}
+
+private fun literal(name: String): LiteralArgumentBuilder {
+ return LiteralArgumentBuilder.literal(name)
+}
+
+private fun argument(name: String, argumentType: ArgumentType<*>): RequiredArgumentBuilder {
+ return RequiredArgumentBuilder.argument(name, argumentType)
+}
\ No newline at end of file
diff --git a/kommand-core/v1.20.4/src/main/kotlin/io/github/monun/kommand/internal/compat/v1_20_4/NMSKommandArgument.kt b/kommand-core/v1.20.4/src/main/kotlin/io/github/monun/kommand/internal/compat/v1_20_4/NMSKommandArgument.kt
new file mode 100644
index 0000000..98c429b
--- /dev/null
+++ b/kommand-core/v1.20.4/src/main/kotlin/io/github/monun/kommand/internal/compat/v1_20_4/NMSKommandArgument.kt
@@ -0,0 +1,539 @@
+/*
+ * Copyright (C) 2023 Monun
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package io.github.monun.kommand.internal.compat.v1_20_4
+
+import com.destroystokyo.paper.profile.CraftPlayerProfile
+import com.destroystokyo.paper.profile.PlayerProfile
+import com.google.gson.JsonObject
+import com.google.gson.JsonParser
+import com.mojang.brigadier.StringReader
+import com.mojang.brigadier.arguments.*
+import com.mojang.brigadier.context.CommandContext
+import com.mojang.brigadier.exceptions.DynamicCommandExceptionType
+import com.mojang.brigadier.exceptions.SimpleCommandExceptionType
+import com.mojang.brigadier.suggestion.SuggestionProvider
+import com.mojang.brigadier.suggestion.Suggestions
+import com.mojang.brigadier.suggestion.SuggestionsBuilder
+import io.github.monun.kommand.*
+import io.github.monun.kommand.internal.AbstractKommandArgument
+import io.github.monun.kommand.internal.ReflectionSupport
+import io.github.monun.kommand.wrapper.*
+import io.github.monun.kommand.wrapper.Rotation
+import io.papermc.paper.brigadier.PaperBrigadier
+import net.kyori.adventure.text.Component
+import net.kyori.adventure.text.format.NamedTextColor
+import net.kyori.adventure.text.format.TextColor
+import net.minecraft.commands.CommandBuildContext
+import net.minecraft.commands.CommandSourceStack
+import net.minecraft.commands.arguments.*
+import net.minecraft.commands.arguments.blocks.BlockPredicateArgument
+import net.minecraft.commands.arguments.blocks.BlockStateArgument
+import net.minecraft.commands.arguments.coordinates.*
+import net.minecraft.commands.arguments.item.FunctionArgument
+import net.minecraft.commands.arguments.item.ItemArgument
+import net.minecraft.commands.arguments.item.ItemPredicateArgument
+import net.minecraft.commands.synchronization.SuggestionProviders
+import net.minecraft.core.Vec3i
+import net.minecraft.core.registries.Registries
+import net.minecraft.server.MinecraftServer
+import net.minecraft.server.level.ColumnPos
+import net.minecraft.world.level.block.state.pattern.BlockInWorld
+import org.bukkit.*
+import org.bukkit.advancement.Advancement
+import org.bukkit.block.Block
+import org.bukkit.block.data.BlockData
+import org.bukkit.craftbukkit.v1_20_R3.CraftParticle
+import org.bukkit.craftbukkit.v1_20_R3.block.CraftBlock
+import org.bukkit.craftbukkit.v1_20_R3.block.data.CraftBlockData
+import org.bukkit.craftbukkit.v1_20_R3.enchantments.CraftEnchantment
+import org.bukkit.craftbukkit.v1_20_R3.inventory.CraftItemStack
+import org.bukkit.craftbukkit.v1_20_R3.potion.CraftPotionEffectType
+import org.bukkit.craftbukkit.v1_20_R3.util.CraftNamespacedKey
+import org.bukkit.enchantments.Enchantment
+import org.bukkit.entity.Entity
+import org.bukkit.entity.Player
+import org.bukkit.inventory.ItemStack
+import org.bukkit.inventory.Recipe
+import org.bukkit.potion.PotionEffectType
+import org.bukkit.scoreboard.DisplaySlot
+import org.bukkit.scoreboard.Objective
+import org.bukkit.scoreboard.Team
+import java.lang.reflect.Method
+import java.util.*
+import java.util.concurrent.CompletableFuture
+
+open class NMSKommandArgument(
+ val type: ArgumentType<*>,
+ private val provider: (NMSKommandContext, name: String) -> T,
+ private val defaultSuggestionProvider: SuggestionProvider? = null
+) : AbstractKommandArgument() {
+ private companion object {
+ private val originalMethod: Method = ArgumentType::class.java.declaredMethods.find { method ->
+ val parameterTypes = method.parameterTypes
+
+ parameterTypes.count() == 2
+ && parameterTypes[0] == CommandContext::class.java
+ && parameterTypes[1] == SuggestionsBuilder::class.java
+ } ?: error("Not found listSuggestion")
+
+ private val overrideSuggestions = hashMapOf, Boolean>()
+
+ private fun checkOverrideSuggestions(type: Class<*>): Boolean = overrideSuggestions.computeIfAbsent(type) {
+ originalMethod.declaringClass != type.getMethod(
+ originalMethod.name,
+ *originalMethod.parameterTypes
+ ).declaringClass
+ }
+ }
+
+ private val hasOverrideSuggestion: Boolean by lazy {
+ checkOverrideSuggestions(type.javaClass)
+ }
+
+ fun from(context: NMSKommandContext, name: String): T {
+ return provider(context, name)
+ }
+
+ fun listSuggestions(
+ context: NMSKommandContext,
+ builder: SuggestionsBuilder
+ ): CompletableFuture {
+ this.suggestionProvider?.let {
+ val suggestion = NMSKommandSuggestion(builder)
+ it(suggestion, context)
+ if (!suggestion.suggestsDefault) return builder.buildFuture()
+ }
+
+ defaultSuggestionProvider?.let { return it.getSuggestions(context.handle, builder) }
+ if (hasOverrideSuggestion) return type.listSuggestions(context.handle, builder)
+ return builder.buildFuture()
+ }
+}
+
+infix fun ArgumentType<*>.provideDynamic(
+ provider: (context: NMSKommandContext, name: String) -> T
+): NMSKommandArgument {
+ return NMSKommandArgument(this, provider)
+}
+
+infix fun ArgumentType<*>.provide(
+ provider: (context: CommandContext, name: String) -> T
+): NMSKommandArgument {
+ return NMSKommandArgument(this, { context, name ->
+ provider(context.handle, name)
+ })
+}
+
+infix fun Pair, SuggestionProvider>.provide(
+ provider: (context: CommandContext, name: String) -> T
+): NMSKommandArgument {
+ return NMSKommandArgument(first, { context, name ->
+ provider(context.handle, name)
+ }, defaultSuggestionProvider = second)
+}
+
+class NMSKommandArgumentSupport : KommandArgumentSupport {
+ // com.mojang.brigadier.arguments
+
+ override fun bool(): KommandArgument {
+ return BoolArgumentType.bool() provide BoolArgumentType::getBool
+ }
+
+ override fun int(minimum: Int, maximum: Int): KommandArgument {
+ return IntegerArgumentType.integer(minimum, maximum) provide IntegerArgumentType::getInteger
+ }
+
+ override fun float(minimum: Float, maximum: Float): KommandArgument {
+ return FloatArgumentType.floatArg(minimum, maximum) provide FloatArgumentType::getFloat
+ }
+
+ override fun double(minimum: Double, maximum: Double): KommandArgument {
+ return DoubleArgumentType.doubleArg(minimum, maximum) provide DoubleArgumentType::getDouble
+ }
+
+ override fun long(minimum: Long, maximum: Long): KommandArgument {
+ return LongArgumentType.longArg(minimum, maximum) provide LongArgumentType::getLong
+ }
+
+ override fun string(type: StringType): KommandArgument {
+ return type.createType() provide StringArgumentType::getString
+ }
+
+ // net.minecraft.commands.arguments
+
+ override fun angle(): KommandArgument {
+ return AngleArgument.angle() provide AngleArgument::getAngle
+ }
+
+ override fun color(): KommandArgument {
+ return ColorArgument.color() provide { context, name ->
+ ColorArgument.getColor(context, name).color?.let { color ->
+ NamedTextColor.namedColor(color) ?: TextColor.color(color)
+ } ?: NamedTextColor.WHITE
+ }
+ }
+
+ override fun component(): KommandArgument {
+ return ComponentArgument.textComponent() provide { context, name ->
+ val nmsComponent = ComponentArgument.getComponent(context, name)
+ PaperBrigadier.componentFromMessage(nmsComponent)
+ }
+ }
+
+ override fun compoundTag(): KommandArgument {
+ return CompoundTagArgument.compoundTag() provide { context, name ->
+ val compoundTag = CompoundTagArgument.getCompoundTag(context, name)
+ JsonParser.parseString(compoundTag.toString()) as JsonObject
+ }
+ }
+
+ override fun dimension(): KommandArgument {
+ return DimensionArgument.dimension() provide { context, name ->
+ DimensionArgument.getDimension(context, name).world
+ }
+ }
+
+ override fun entityAnchor(): KommandArgument {
+ return EntityAnchorArgument.anchor() provide { context, name ->
+ when (EntityAnchorArgument.getAnchor(context, name) ?: error("Unknown entity anchor")) {
+ EntityAnchorArgument.Anchor.FEET -> EntityAnchor.FEET
+ EntityAnchorArgument.Anchor.EYES -> EntityAnchor.EYES
+ }
+ }
+ }
+
+ override fun entity(): KommandArgument {
+ return EntityArgument.entity() provide { context, name ->
+ EntityArgument.getEntity(context, name).bukkitEntity
+ }
+ }
+
+ override fun entities(): KommandArgument> {
+ return EntityArgument.entities() provide { context, name ->
+ EntityArgument.getEntities(context, name).map { it.bukkitEntity }
+ }
+ }
+
+ override fun player(): KommandArgument {
+ return EntityArgument.player() provide { context, name ->
+ EntityArgument.getPlayer(context, name).bukkitEntity
+ }
+ }
+
+ override fun players(): KommandArgument> {
+ return EntityArgument.players() provide { context, name ->
+ EntityArgument.getPlayers(context, name).map { it.bukkitEntity }
+ }
+ }
+
+ override fun summonableEntity(): KommandArgument {
+ return ResourceArgument.resource(
+ commandBuildContext,
+ Registries.ENTITY_TYPE
+ ) to SuggestionProviders.SUMMONABLE_ENTITIES provide { context, name ->
+ CraftNamespacedKey.fromMinecraft(ResourceArgument.getSummonableEntityType(context, name).key().location())
+ }
+ }
+
+ override fun profile(): KommandArgument> {
+ return GameProfileArgument.gameProfile() provide { context, name ->
+ val nms = GameProfileArgument.getGameProfiles(context, name)
+ nms.map { CraftPlayerProfile.asBukkitMirror(it) }
+ }
+ }
+
+ private val enchantmentMap = Enchantment.values().map { it as CraftEnchantment }.associateBy { it.handle }
+
+ override fun enchantment(): KommandArgument {
+ return ResourceArgument.resource(commandBuildContext, Registries.ENCHANTMENT) provide { context, name ->
+ val key = ResourceArgument.getEnchantment(context, name)
+ val value = key.value()
+
+ enchantmentMap[value] ?: error("Not found enchantment ${value.getFullname(0)}")
+ }
+ }
+
+ override fun message(): KommandArgument {
+ return MessageArgument.message() provide { context, name ->
+ PaperBrigadier.componentFromMessage(MessageArgument.getMessage(context, name))
+ }
+ }
+
+ private val mobEffectMap = PotionEffectType.values().map { it as CraftPotionEffectType }.associateBy { it.handle }
+
+ override fun mobEffect(): KommandArgument {
+ return ResourceArgument.resource(commandBuildContext, Registries.MOB_EFFECT) provide { context, name ->
+ val key = ResourceArgument.getMobEffect(context, name)
+ val value = key.value()
+
+ mobEffectMap[value] ?: error("Not found mob effect ${value.displayName}")
+ }
+ }
+
+ override fun objective(): KommandArgument {
+ return ObjectiveArgument.objective() provide { context, name ->
+ val nms = ObjectiveArgument.getObjective(context, name)
+ Bukkit.getScoreboardManager().mainScoreboard.getObjective(nms.name) ?: error("Objective error!")
+ }
+ }
+
+ override fun objectiveCriteria(): KommandArgument {
+ return ObjectiveCriteriaArgument.criteria() provide { context, name ->
+ ObjectiveCriteriaArgument.getCriteria(context, name).name
+ }
+ }
+
+ override fun particle(): KommandArgument {
+ return ParticleArgument.particle(commandBuildContext) provide { context, name ->
+ CraftParticle.minecraftToBukkit(ParticleArgument.getParticle(context, name).type)
+ }
+ }
+
+ override fun intRange(): KommandArgument {
+ return RangeArgument.intRange() provide { context, name ->
+ val nms = RangeArgument.Ints.getRange(context, name)
+ val min = nms.min.orElse(Int.MIN_VALUE)
+ val max = nms.max.orElse(Int.MAX_VALUE)
+ min..max
+ }
+ }
+
+ //float
+ override fun doubleRange(): KommandArgument> {
+ return RangeArgument.floatRange() provide { context, name ->
+ val nms = RangeArgument.Floats.getRange(context, name)
+ val min = nms.min.orElse(-Double.MAX_VALUE)
+ val max = nms.max.orElse(Double.MAX_VALUE)
+ min..max
+ }
+ }
+
+ override fun advancement(): KommandArgument {
+ return ResourceLocationArgument.id() provide { context, name ->
+ val nms = ResourceLocationArgument.getAdvancement(context, name)
+ nms.toBukkit()
+ }
+ }
+
+ override fun recipe(): KommandArgument {
+ return ResourceLocationArgument.id() to SuggestionProviders.ALL_RECIPES provide { context, name ->
+ val nms = ResourceLocationArgument.getRecipe(context, name)
+ nms.toBukkitRecipe()
+ }
+ }
+
+ private val displaySlots = DisplaySlot.values().associateBy { it.id }
+
+ override fun displaySlot(): KommandArgument {
+ return ScoreboardSlotArgument.displaySlot() provide { context, name ->
+ val slotName = ScoreboardSlotArgument.getDisplaySlot(context, name).serializedName
+ displaySlots[slotName] ?: error("Not found display slot $slotName")
+ }
+ }
+
+ override fun score(): KommandArgument {
+ return ScoreHolderArgument.scoreHolder() provide { context, name ->
+ ScoreHolderArgument.getName(context, name).toString()
+ }
+ }
+
+ override fun scores(): KommandArgument> {
+ return ScoreHolderArgument.scoreHolders() provide { context, name ->
+ ScoreHolderArgument.getNames(context, name).map { it.toString() }
+ }
+ }
+
+ override fun slot(): KommandArgument {
+ return SlotArgument.slot() provide { context, name ->
+ SlotArgument.getSlot(context, name)
+ }
+ }
+
+// new SimpleCommandExceptionType(Component.translatable("commands.team.option.seeFriendlyInvisibles.alreadyEnabled"));
+
+ /**
+ * TeamArgument의 error
+ */
+ private val errorTeamNotFound: DynamicCommandExceptionType = DynamicCommandExceptionType { name: Any? ->
+ net.minecraft.network.chat.Component.translatable(
+ "team.notFound",
+ name
+ )
+ }
+
+ override fun team(): KommandArgument {
+ return TeamArgument.team() provide { context, name ->
+ /**
+ * CraftTeam이 패키지 접근만 허용하여 생성불가
+ * PlayerTeam(nms) -> name -> BukkitTeam 순으로 가져와야함
+ * 하지만...
+ *
+ * val team: PlayerTeam = TeamArgument.getTeam(context, name)
+ * val teamName = team.name <-- spigot mapping에서 오류가 있음
+ * java.lang.NoSuchMethodError: 'java.lang.String net.minecraft.world.scores.ScoreboardTeam.b()'
+ */
+ val teamName: String = context.getArgument(name, String::class.java)
+ Bukkit.getScoreboardManager().mainScoreboard.getTeam(teamName) ?: throw errorTeamNotFound.create(teamName)
+ }
+ }
+
+ override fun time(): KommandArgument {
+ val argument = TimeArgument.time()
+ return argument provide { context, name ->
+ val time = context.getArgument(name, String::class.java)
+ argument.parse(StringReader(time))
+ }
+ }
+
+ override fun uuid(): KommandArgument {
+ return UuidArgument.uuid() provide UuidArgument::getUuid
+ }
+
+ companion object {
+ private val commandBuildContext: CommandBuildContext = ReflectionSupport.getFieldInstance(
+ MinecraftServer.getServer().resources.managers,
+ "commandBuildContext",
+ "c"
+ )
+ }
+
+ // net.minecraft.commands.arguments.blocks
+
+ override fun blockPredicate(): KommandArgument<(Block) -> Boolean> {
+ return BlockPredicateArgument.blockPredicate(commandBuildContext) provide { context, name ->
+ { block ->
+ BlockPredicateArgument.getBlockPredicate(context, name)
+ .test(BlockInWorld(context.source.level, (block as CraftBlock).position, true))
+ }
+ }
+ }
+
+ override fun blockState(): KommandArgument {
+ return BlockStateArgument.block(commandBuildContext) provide { context, name ->
+ CraftBlockData.fromData(BlockStateArgument.getBlock(context, name).state)
+ }
+ }
+
+ // net.minecraft.commands.arguments.coordinates
+
+ override fun blockPosition(type: PositionLoadType): KommandArgument {
+ /**
+ * Issue [https://github.com/monun/kommand/issues/18]
+ *
+ * mojang mapping -> spigot mapping 변환시 상속된 타입의 메서드 이름 or 필드 이름을 잘 감지하지 못함
+ * 변수의 타입을 메서드, 필드가 선언된 타입으로 정확히 선언해야함
+ * [net.minecraft.core.BlockPos]의 경우 [net.minecraft.core.Vec3i]를 상속받아 상위의 메서드를 가지고 있음
+ * BlockPos#getX <- 실패
+ * Vec3i#getX <- 성공
+ *
+ * 아마 remapping 작업이 다음과 같은 바이트 코드만 감지하는듯
+ * invokeVirtual 'Vec3i#getX'
+ */
+ return BlockPosArgument.blockPos() provide { context, name ->
+ val blockPosition: Vec3i = when (type) {
+ PositionLoadType.LOADED -> BlockPosArgument.getLoadedBlockPos(context, name)
+ PositionLoadType.SPAWNABLE -> BlockPosArgument.getSpawnablePos(context, name)
+ }
+
+ BlockPosition3D(blockPosition.x, blockPosition.y, blockPosition.z)
+ }
+ }
+
+ override fun blockPosition2D(): KommandArgument {
+ return ColumnPosArgument.columnPos() provide { context, name ->
+ val columnPosition: ColumnPos = ColumnPosArgument.getColumnPos(context, name)
+ BlockPosition2D(columnPosition.x, columnPosition.z)
+ }
+ }
+
+ override fun position(): KommandArgument {
+ return Vec3Argument.vec3() provide { context, name ->
+ val vec3 = Vec3Argument.getVec3(context, name)
+ Position3D(vec3.x, vec3.y, vec3.z)
+ }
+ }
+
+ override fun position2D(): KommandArgument {
+ return Vec2Argument.vec2() provide { context, name ->
+ val vec2 = Vec2Argument.getVec2(context, name)
+ Position2D(vec2.x.toDouble(), vec2.y.toDouble())
+ }
+ }
+
+ override fun rotation(): KommandArgument {
+ return RotationArgument.rotation() provide { context, name ->
+ val rotation = RotationArgument.getRotation(context, name).getRotation(context.source)
+ Rotation(rotation.x, rotation.y)
+ }
+ }
+
+ override fun swizzle(): KommandArgument> {
+ return SwizzleArgument.swizzle() provide { context, name ->
+ EnumSet.copyOf(SwizzleArgument.getSwizzle(context, name).map { axis ->
+ Axis.valueOf(axis.getName().uppercase())
+ })
+ }
+ }
+
+ // net.minecraft.commands.arguments.item
+
+ override fun function(): KommandArgument<() -> Unit> {
+ return FunctionArgument.functions() provide { context, name ->
+ {
+ FunctionArgument.getFunctions(context, name).map { function ->
+ context.source.server.functions.execute(function, context.source)
+ }
+ }
+ }
+ }
+
+ override fun item(): KommandArgument {
+ return ItemArgument.item(commandBuildContext) provide { context, name ->
+ CraftItemStack.asBukkitCopy(ItemArgument.getItem(context, name).createItemStack(1, false))
+ }
+ }
+
+ override fun itemPredicate(): KommandArgument<(ItemStack) -> Boolean> {
+ return ItemPredicateArgument.itemPredicate(commandBuildContext) provide { context, name ->
+ { itemStack ->
+ ItemPredicateArgument.getItemPredicate(context, name).test(CraftItemStack.asNMSCopy(itemStack))
+ }
+ }
+ }
+
+ private val unknownArgument =
+ SimpleCommandExceptionType(net.minecraft.network.chat.Component.translatable("command.unknown.argument"))
+
+ override fun dynamic(
+ type: StringType,
+ function: KommandSource.(context: KommandContext, input: String) -> T?
+ ): KommandArgument {
+ return type.createType() provideDynamic { context, name ->
+ context.source.function(context, StringArgumentType.getString(context.handle, name))
+ ?: throw unknownArgument.create()
+ }
+ }
+}
+
+fun StringType.createType(): StringArgumentType {
+ return when (this) {
+ StringType.SINGLE_WORD -> StringArgumentType.word()
+ StringType.QUOTABLE_PHRASE -> StringArgumentType.string()
+ StringType.GREEDY_PHRASE -> StringArgumentType.greedyString()
+ }
+}
diff --git a/kommand-core/v1.20.4/src/main/kotlin/io/github/monun/kommand/internal/compat/v1_20_4/NMSKommandContext.kt b/kommand-core/v1.20.4/src/main/kotlin/io/github/monun/kommand/internal/compat/v1_20_4/NMSKommandContext.kt
new file mode 100644
index 0000000..600c6b2
--- /dev/null
+++ b/kommand-core/v1.20.4/src/main/kotlin/io/github/monun/kommand/internal/compat/v1_20_4/NMSKommandContext.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2023 Monun
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package io.github.monun.kommand.internal.compat.v1_20_4
+
+import com.mojang.brigadier.context.CommandContext
+import io.github.monun.kommand.KommandContext
+import io.github.monun.kommand.KommandSource
+import io.github.monun.kommand.internal.AbstractKommandNode
+import io.github.monun.kommand.internal.ArgumentNodeImpl
+import io.github.monun.kommand.ref.getValue
+import io.github.monun.kommand.ref.weak
+import io.github.monun.kommand.internal.compat.v1_20_4.NMSKommandSource.Companion.wrapSource
+import net.minecraft.commands.CommandSourceStack
+import java.util.*
+
+class NMSKommandContext private constructor(
+ private val node: AbstractKommandNode,
+ handle: CommandContext
+) : KommandContext {
+ companion object {
+ private val refs = WeakHashMap, NMSKommandContext>()
+
+ fun AbstractKommandNode.wrapContext(context: CommandContext): NMSKommandContext =
+ refs.computeIfAbsent(context) {
+ NMSKommandContext(this, context)
+ }
+ }
+
+ internal val handle by weak(handle)
+
+ override val source: KommandSource by lazy { wrapSource(handle.source) }
+
+ override val input: String
+ get() = handle.input
+
+ @Suppress("UNCHECKED_CAST")
+ override fun get(name: String): T {
+ val argumentNode = node.findArgumentNode(name) ?: error("Not found argument node $name")
+ val argument = argumentNode.argument as NMSKommandArgument<*>
+
+ return argument.from(this, name) as T
+ }
+}
+
+private fun AbstractKommandNode.findArgumentNode(name: String): ArgumentNodeImpl? {
+ var node: AbstractKommandNode? = this
+
+ while (node != null) {
+ if (node is ArgumentNodeImpl && node.name == name) return node
+ node = node.parent
+ }
+
+ return null
+}
\ No newline at end of file
diff --git a/kommand-core/v1.20.4/src/main/kotlin/io/github/monun/kommand/internal/compat/v1_20_4/NMSKommandSource.kt b/kommand-core/v1.20.4/src/main/kotlin/io/github/monun/kommand/internal/compat/v1_20_4/NMSKommandSource.kt
new file mode 100644
index 0000000..bb8a0df
--- /dev/null
+++ b/kommand-core/v1.20.4/src/main/kotlin/io/github/monun/kommand/internal/compat/v1_20_4/NMSKommandSource.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2023 Monun
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package io.github.monun.kommand.internal.compat.v1_20_4
+
+import io.github.monun.kommand.KommandSource
+import io.github.monun.kommand.ref.getValue
+import io.github.monun.kommand.ref.weak
+import io.github.monun.kommand.internal.compat.v1_20_4.wrapper.NMSEntityAnchor
+import io.github.monun.kommand.wrapper.EntityAnchor
+import io.github.monun.kommand.wrapper.Position3D
+import io.github.monun.kommand.wrapper.Rotation
+import io.papermc.paper.brigadier.PaperBrigadier
+import net.kyori.adventure.text.Component
+import net.minecraft.commands.CommandSourceStack
+import org.bukkit.Location
+import org.bukkit.World
+import org.bukkit.command.CommandSender
+import org.bukkit.entity.Entity
+import org.bukkit.entity.Player
+import java.util.*
+
+class NMSKommandSource private constructor(
+ handle: CommandSourceStack
+) : KommandSource {
+ companion object {
+ private val refs = WeakHashMap()
+
+ fun wrapSource(source: CommandSourceStack): NMSKommandSource =
+ refs.computeIfAbsent(source) {
+ NMSKommandSource(source)
+ }
+ }
+
+ private val handle by weak(handle)
+
+ override val displayName: Component
+ get() = PaperBrigadier.componentFromMessage(handle.displayName)
+
+ override val sender: CommandSender
+ get() = handle.bukkitSender
+
+ override val entity: Entity
+ get() = handle.entityOrException.bukkitEntity
+
+ override val entityOrNull: Entity?
+ get() = handle.entity?.bukkitEntity
+
+ override val player: Player
+ get() = handle.playerOrException.bukkitEntity
+
+ override val playerOrNull: Player?
+ get() = handle.entity?.bukkitEntity?.takeIf { it is Player } as Player?
+
+ override val position: Position3D
+ get() = handle.position.run { Position3D(x, y, z) }
+
+ override val rotation: Rotation
+ get() = handle.rotation.run { Rotation(x, y) }
+
+ override val anchor: EntityAnchor
+ get() = NMSEntityAnchor(handle.anchor)
+
+ override val world: World
+ get() = handle.level.world
+
+ override val location: Location
+ get() = position.toLocation(handle.level.world, rotation)
+
+ override fun hasPermission(level: Int): Boolean {
+ return handle.hasPermission(level)
+ }
+
+ override fun hasPermission(level: Int, bukkitPermission: String): Boolean {
+ return handle.hasPermission(level, bukkitPermission)
+ }
+}
\ No newline at end of file
diff --git a/kommand-core/v1.20.4/src/main/kotlin/io/github/monun/kommand/internal/compat/v1_20_4/NMSKommandSuggestion.kt b/kommand-core/v1.20.4/src/main/kotlin/io/github/monun/kommand/internal/compat/v1_20_4/NMSKommandSuggestion.kt
new file mode 100644
index 0000000..3973569
--- /dev/null
+++ b/kommand-core/v1.20.4/src/main/kotlin/io/github/monun/kommand/internal/compat/v1_20_4/NMSKommandSuggestion.kt
@@ -0,0 +1,94 @@
+/*
+ * Kommand
+ * Copyright (C) 2021 Monun
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package io.github.monun.kommand.internal.compat.v1_20_4
+
+import com.mojang.brigadier.suggestion.SuggestionsBuilder
+import io.github.monun.kommand.AbstractKommandSuggestion
+import io.github.monun.kommand.ref.getValue
+import io.github.monun.kommand.ref.weak
+import io.papermc.paper.brigadier.PaperBrigadier
+import net.kyori.adventure.text.ComponentLike
+import net.minecraft.commands.SharedSuggestionProvider
+import java.util.*
+
+class NMSKommandSuggestion(
+ handle: SuggestionsBuilder
+) : AbstractKommandSuggestion() {
+ private val handle by weak(handle)
+
+ override fun suggest(value: Int, tooltip: (() -> ComponentLike)?) {
+ if (tooltip == null) handle.suggest(value)
+ else handle.suggest(value, PaperBrigadier.message(tooltip()))
+ }
+
+ override fun suggest(text: String, tooltip: (() -> ComponentLike)?) {
+ if (tooltip == null) handle.suggest(text)
+ else handle.suggest(text, PaperBrigadier.message(tooltip()))
+ }
+
+ override fun suggest(candidates: Iterable, tooltip: ((String) -> ComponentLike)?) {
+ val handle = handle
+ val input: String = handle.remaining.lowercase(Locale.ROOT)
+
+ candidates.forEach { candidate ->
+ val lowerCandidate = candidate.lowercase(Locale.ROOT)
+
+ if (SharedSuggestionProvider.matchesSubStr(input, lowerCandidate)) {
+ if (tooltip == null) handle.suggest(candidate)
+ else handle.suggest(candidate, PaperBrigadier.message(tooltip(candidate)))
+ }
+ }
+ }
+
+ override fun suggest(
+ candidates: Iterable,
+ transform: (T) -> String,
+ tooltip: ((T) -> ComponentLike)?
+ ) {
+ val handle = handle
+ val input: String = handle.remaining.lowercase(Locale.ROOT)
+
+ candidates.forEach {
+ val candidate = transform(it)
+ val lowerCandidate = transform(it).lowercase(Locale.ROOT)
+
+ if (SharedSuggestionProvider.matchesSubStr(input, lowerCandidate)) {
+ if (tooltip == null) handle.suggest(candidate)
+ else handle.suggest(candidate, PaperBrigadier.message(tooltip(it)))
+ }
+ }
+ }
+
+ override fun suggest(
+ candidates: Map,
+ tooltip: ((T) -> ComponentLike)?
+ ) {
+ val handle = handle
+ val input: String = handle.remaining.lowercase(Locale.ROOT)
+
+ candidates.forEach { (key, value) ->
+ val lowerCandidate = key.lowercase(Locale.ROOT)
+
+ if (SharedSuggestionProvider.matchesSubStr(input, lowerCandidate)) {
+ if (tooltip == null) handle.suggest(key)
+ else handle.suggest(key, PaperBrigadier.message(tooltip(value)))
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/kommand-core/v1.20.4/src/main/kotlin/io/github/monun/kommand/internal/compat/v1_20_4/wrapper/NMSEntityAnchor.kt b/kommand-core/v1.20.4/src/main/kotlin/io/github/monun/kommand/internal/compat/v1_20_4/wrapper/NMSEntityAnchor.kt
new file mode 100644
index 0000000..e4ed927
--- /dev/null
+++ b/kommand-core/v1.20.4/src/main/kotlin/io/github/monun/kommand/internal/compat/v1_20_4/wrapper/NMSEntityAnchor.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2023 Monun
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package io.github.monun.kommand.internal.compat.v1_20_4.wrapper
+
+import io.github.monun.kommand.KommandSource
+import io.github.monun.kommand.wrapper.EntityAnchor
+import net.minecraft.commands.arguments.EntityAnchorArgument
+import net.minecraft.world.entity.Entity
+import org.bukkit.craftbukkit.v1_20_R3.entity.CraftEntity
+import org.bukkit.util.Vector
+
+class NMSEntityAnchor(
+ private val handle: EntityAnchorArgument.Anchor
+) : EntityAnchor {
+ override val name: String
+ get() = handle.name
+
+ override fun applyTo(entity: org.bukkit.entity.Entity): Vector {
+ val nmsEntity: Entity = (entity as CraftEntity).handle
+ return handle.apply(nmsEntity).run { Vector(x, y, z) }
+ }
+
+ override fun applyTo(source: KommandSource): Vector {
+ return applyTo(source.entity)
+ }
+}
\ No newline at end of file
diff --git a/kommand-core/v1.20.4/src/main/kotlin/io/github/monun/kommand/internal/compat/v1_20_4/wrapper/NMSWrapperSupport.kt b/kommand-core/v1.20.4/src/main/kotlin/io/github/monun/kommand/internal/compat/v1_20_4/wrapper/NMSWrapperSupport.kt
new file mode 100644
index 0000000..5551224
--- /dev/null
+++ b/kommand-core/v1.20.4/src/main/kotlin/io/github/monun/kommand/internal/compat/v1_20_4/wrapper/NMSWrapperSupport.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 Monun
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package io.github.monun.kommand.internal.compat.v1_20_4.wrapper
+
+import io.github.monun.kommand.wrapper.EntityAnchor
+import io.github.monun.kommand.wrapper.WrapperSupport
+import net.minecraft.commands.arguments.EntityAnchorArgument
+
+class NMSWrapperSupport : WrapperSupport {
+ override fun entityAnchorFeet(): EntityAnchor {
+ return NMSEntityAnchor(EntityAnchorArgument.Anchor.FEET)
+ }
+
+ override fun entityAnchorEyes(): EntityAnchor {
+ return NMSEntityAnchor(EntityAnchorArgument.Anchor.EYES)
+ }
+}
\ No newline at end of file