From 24e67b030cbf4f202018f30a7c378c4c834821d2 Mon Sep 17 00:00:00 2001 From: NOOBNUBY Date: Mon, 22 Jan 2024 02:13:43 +0900 Subject: [PATCH] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20Support=201.20.4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../internal/compat/v1_20_3/NMSKommand.kt | 136 +++++ .../compat/v1_20_3/NMSKommandArgument.kt | 539 ++++++++++++++++++ .../compat/v1_20_3/NMSKommandContext.kt | 69 +++ .../compat/v1_20_3/NMSKommandSource.kt | 91 +++ .../compat/v1_20_3/NMSKommandSuggestion.kt | 94 +++ .../compat/v1_20_3/wrapper/NMSEntityAnchor.kt | 41 ++ .../v1_20_3/wrapper/NMSWrapperSupport.kt | 32 ++ .../internal/compat/v1_20_4/NMSKommand.kt | 136 +++++ .../compat/v1_20_4/NMSKommandArgument.kt | 539 ++++++++++++++++++ .../compat/v1_20_4/NMSKommandContext.kt | 69 +++ .../compat/v1_20_4/NMSKommandSource.kt | 91 +++ .../compat/v1_20_4/NMSKommandSuggestion.kt | 94 +++ .../compat/v1_20_4/wrapper/NMSEntityAnchor.kt | 41 ++ .../v1_20_4/wrapper/NMSWrapperSupport.kt | 32 ++ 14 files changed, 2004 insertions(+) create mode 100644 kommand-core/v1.20.3/src/main/kotlin/io/github/monun/kommand/internal/compat/v1_20_3/NMSKommand.kt create mode 100644 kommand-core/v1.20.3/src/main/kotlin/io/github/monun/kommand/internal/compat/v1_20_3/NMSKommandArgument.kt create mode 100644 kommand-core/v1.20.3/src/main/kotlin/io/github/monun/kommand/internal/compat/v1_20_3/NMSKommandContext.kt create mode 100644 kommand-core/v1.20.3/src/main/kotlin/io/github/monun/kommand/internal/compat/v1_20_3/NMSKommandSource.kt create mode 100644 kommand-core/v1.20.3/src/main/kotlin/io/github/monun/kommand/internal/compat/v1_20_3/NMSKommandSuggestion.kt create mode 100644 kommand-core/v1.20.3/src/main/kotlin/io/github/monun/kommand/internal/compat/v1_20_3/wrapper/NMSEntityAnchor.kt create mode 100644 kommand-core/v1.20.3/src/main/kotlin/io/github/monun/kommand/internal/compat/v1_20_3/wrapper/NMSWrapperSupport.kt create mode 100644 kommand-core/v1.20.4/src/main/kotlin/io/github/monun/kommand/internal/compat/v1_20_4/NMSKommand.kt create mode 100644 kommand-core/v1.20.4/src/main/kotlin/io/github/monun/kommand/internal/compat/v1_20_4/NMSKommandArgument.kt create mode 100644 kommand-core/v1.20.4/src/main/kotlin/io/github/monun/kommand/internal/compat/v1_20_4/NMSKommandContext.kt create mode 100644 kommand-core/v1.20.4/src/main/kotlin/io/github/monun/kommand/internal/compat/v1_20_4/NMSKommandSource.kt create mode 100644 kommand-core/v1.20.4/src/main/kotlin/io/github/monun/kommand/internal/compat/v1_20_4/NMSKommandSuggestion.kt create mode 100644 kommand-core/v1.20.4/src/main/kotlin/io/github/monun/kommand/internal/compat/v1_20_4/wrapper/NMSEntityAnchor.kt create mode 100644 kommand-core/v1.20.4/src/main/kotlin/io/github/monun/kommand/internal/compat/v1_20_4/wrapper/NMSWrapperSupport.kt 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 00000000..ab3042a1 --- /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 00000000..4283b50d --- /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 00000000..7737206b --- /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 00000000..0fc2d85c --- /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 00000000..7da53ccf --- /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 00000000..f6f84d42 --- /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 00000000..d2f087b8 --- /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 00000000..0b8915b4 --- /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 00000000..98c429b0 --- /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 00000000..600c6b24 --- /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 00000000..bb8a0df1 --- /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 00000000..39735695 --- /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 00000000..e4ed9276 --- /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 00000000..5551224c --- /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