From 76a64b28f60b1a8921a3221e49462f96f4356c86 Mon Sep 17 00:00:00 2001 From: Apehum Date: Wed, 24 Dec 2025 02:18:53 +0800 Subject: [PATCH 01/36] feat: brigadier commands --- .github/workflows/build-pr.yml | 63 ++++++++ .../plo/slib/api/command/McCommandManager.kt | 66 ++++----- .../command/brigadier/McBrigadierContext.kt | 16 +++ .../su/plo/slib/api/server/McServerLib.kt | 8 +- .../command/brigadier/McArgumentResolver.kt | 68 +++++++++ .../command/brigadier/McArgumentTypes.kt | 53 +++++++ build.gradle.kts | 3 + bungee/build.gradle.kts | 28 ++++ .../bungee/command/BungeeCommandManager.kt | 23 ++- .../brigadier/BrigadierBungeeCommand.kt | 58 ++++++++ bungee/src/test/kotlin/BungeePlugin.kt | 20 --- .../su/plo/slib/bungee/TestBungeePlugin.kt | 12 ++ bungee/src/test/resources/bungee.yml | 3 + common-proxy/build.gradle.kts | 10 ++ .../kotlin/su/plo/slib/proxy/TestProxy.kt | 45 ++++++ .../kotlin/su/plo/slib/server/TestServer.kt | 87 +++++++++-- .../slib/command/AbstractCommandManager.kt | 69 +++++++++ gradle.properties | 2 +- gradle/libs.versions.toml | 14 +- minestom/build.gradle.kts | 20 +++ .../su/plo/slib/minestom/MinestomServerLib.kt | 9 +- .../command/MinestomCommandManager.kt | 136 +++++++++++++++++- .../command/brigadier/MinestomArgumentType.kt | 13 ++ .../brigadier/MinestomEntityArguments.kt | 81 +++++++++++ ...mand.brigadier.McArgumentResolver$Provider | 1 + ...command.brigadier.McArgumentTypes$Provider | 1 + .../plo/slib/minestom/TestMinestomServer.kt | 52 +++++++ minestom/src/test/resources/log4j.properties | 21 +++ .../plo/slib/mod/command/ModCommandManager.kt | 45 +++++- .../brigadier/ModBrigadierArguments.kt | 57 ++++++++ ...mand.brigadier.McArgumentResolver$Provider | 1 + ...command.brigadier.McArgumentTypes$Provider | 1 + scripts/smoke-test-proxy.sh | 3 + scripts/smoke-test-server.sh | 71 +-------- scripts/smoke-test.sh | 87 +++++++++++ settings.gradle.kts | 1 + spigot/build.gradle.kts | 18 ++- .../su/plo/slib/spigot/SpigotServerLib.kt | 15 +- .../spigot/command/SpigotCommandManager.kt | 37 ++++- .../brigadier/SpigotBrigadierArguments.kt | 72 ++++++++++ .../slib/spigot/entity/SpigotServerEntity.kt | 10 +- .../spigot/nms/CommandSourceStackProxy.kt | 20 +++ .../su/plo/slib/spigot/nms/CommandsProxy.kt | 16 +++ .../slib/spigot/nms/EntityArgumentProxy.kt | 35 +++++ .../su/plo/slib/spigot/nms/EntityProxy.kt | 14 ++ .../slib/spigot/nms/MinecraftServerProxy.kt | 13 ++ .../su/plo/slib/spigot/nms/Reflections.kt | 69 +++++++++ ...mand.brigadier.McArgumentResolver$Provider | 1 + ...command.brigadier.McArgumentTypes$Provider | 1 + .../src/main/resources/mappings/1.16.5.tiny | 17 +++ .../src/main/resources/mappings/1.17.1.tiny | 17 +++ .../src/main/resources/mappings/1.20.4.tiny | 17 +++ .../src/main/resources/mappings/1.21.4.tiny | 17 +++ .../src/main/resources/mappings/1.21.6.tiny | 17 +++ velocity/build.gradle.kts | 32 ++++- .../command/VelocityCommandManager.kt | 30 +++- velocity/src/test/kotlin/VelocityPlugin.kt | 34 ----- .../plo/slib/velocity/TestVelocityPlugin.kt | 24 ++++ 58 files changed, 1571 insertions(+), 203 deletions(-) create mode 100644 api/common/src/main/kotlin/su/plo/slib/api/command/brigadier/McBrigadierContext.kt create mode 100644 api/server/src/main/kotlin/su/plo/slib/api/server/command/brigadier/McArgumentResolver.kt create mode 100644 api/server/src/main/kotlin/su/plo/slib/api/server/command/brigadier/McArgumentTypes.kt create mode 100644 bungee/src/main/kotlin/su/plo/slib/bungee/command/brigadier/BrigadierBungeeCommand.kt delete mode 100644 bungee/src/test/kotlin/BungeePlugin.kt create mode 100644 bungee/src/test/kotlin/su/plo/slib/bungee/TestBungeePlugin.kt create mode 100644 bungee/src/test/resources/bungee.yml create mode 100644 common-proxy/build.gradle.kts create mode 100644 common-proxy/src/testFixtures/kotlin/su/plo/slib/proxy/TestProxy.kt create mode 100644 common/src/main/kotlin/su/plo/slib/command/AbstractCommandManager.kt create mode 100644 minestom/src/main/kotlin/su/plo/slib/minestom/command/brigadier/MinestomArgumentType.kt create mode 100644 minestom/src/main/kotlin/su/plo/slib/minestom/command/brigadier/MinestomEntityArguments.kt create mode 100644 minestom/src/main/resources/META-INF/services/su.plo.slib.api.server.command.brigadier.McArgumentResolver$Provider create mode 100644 minestom/src/main/resources/META-INF/services/su.plo.slib.api.server.command.brigadier.McArgumentTypes$Provider create mode 100644 minestom/src/test/kotlin/su/plo/slib/minestom/TestMinestomServer.kt create mode 100644 minestom/src/test/resources/log4j.properties create mode 100644 modded/src/main/kotlin/su/plo/slib/mod/command/brigadier/ModBrigadierArguments.kt create mode 100644 modded/src/main/resources/META-INF/services/su.plo.slib.api.server.command.brigadier.McArgumentResolver$Provider create mode 100644 modded/src/main/resources/META-INF/services/su.plo.slib.api.server.command.brigadier.McArgumentTypes$Provider create mode 100755 scripts/smoke-test-proxy.sh create mode 100755 scripts/smoke-test.sh create mode 100644 spigot/src/main/kotlin/su/plo/slib/spigot/command/brigadier/SpigotBrigadierArguments.kt create mode 100644 spigot/src/main/kotlin/su/plo/slib/spigot/nms/CommandSourceStackProxy.kt create mode 100644 spigot/src/main/kotlin/su/plo/slib/spigot/nms/CommandsProxy.kt create mode 100644 spigot/src/main/kotlin/su/plo/slib/spigot/nms/EntityArgumentProxy.kt create mode 100644 spigot/src/main/kotlin/su/plo/slib/spigot/nms/EntityProxy.kt create mode 100644 spigot/src/main/kotlin/su/plo/slib/spigot/nms/MinecraftServerProxy.kt create mode 100644 spigot/src/main/kotlin/su/plo/slib/spigot/nms/Reflections.kt create mode 100644 spigot/src/main/resources/META-INF/services/su.plo.slib.api.server.command.brigadier.McArgumentResolver$Provider create mode 100644 spigot/src/main/resources/META-INF/services/su.plo.slib.api.server.command.brigadier.McArgumentTypes$Provider create mode 100644 spigot/src/main/resources/mappings/1.16.5.tiny create mode 100644 spigot/src/main/resources/mappings/1.17.1.tiny create mode 100644 spigot/src/main/resources/mappings/1.20.4.tiny create mode 100644 spigot/src/main/resources/mappings/1.21.4.tiny create mode 100644 spigot/src/main/resources/mappings/1.21.6.tiny delete mode 100644 velocity/src/test/kotlin/VelocityPlugin.kt create mode 100644 velocity/src/test/kotlin/su/plo/slib/velocity/TestVelocityPlugin.kt diff --git a/.github/workflows/build-pr.yml b/.github/workflows/build-pr.yml index 1acb82a..cd64044 100644 --- a/.github/workflows/build-pr.yml +++ b/.github/workflows/build-pr.yml @@ -106,3 +106,66 @@ jobs: - name: Run smoke test run: ./scripts/smoke-test-server.sh spigot:runServer -Pspigot.run_minecraft_version=${{ matrix.version }} + + smoke-tests-minestom: + runs-on: ubuntu-latest + needs: build + + steps: + - uses: actions/checkout@v5 + + - name: Set up JDK + uses: actions/setup-java@v5 + with: + distribution: temurin + java-version: 21 + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v5 + with: + cache-read-only: false + + - name: Run smoke test + run: ./scripts/smoke-test-server.sh minestom:runServer -Pmodded.versions_mode=NONE + + smoke-tests-velocity: + runs-on: ubuntu-latest + needs: build + + steps: + - uses: actions/checkout@v5 + + - name: Set up JDK + uses: actions/setup-java@v5 + with: + distribution: temurin + java-version: 21 + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v5 + with: + cache-read-only: false + + - name: Run smoke test + run: ./scripts/smoke-test-proxy.sh velocity:runVelocity -Pmodded.versions_mode=NONE + + smoke-tests-bungee: + runs-on: ubuntu-latest + needs: build + + steps: + - uses: actions/checkout@v5 + + - name: Set up JDK + uses: actions/setup-java@v5 + with: + distribution: temurin + java-version: 21 + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v5 + with: + cache-read-only: false + + - name: Run smoke test + run: ./scripts/smoke-test-proxy.sh bungee:runWaterfall -Pmodded.versions_mode=NONE diff --git a/api/common/src/main/kotlin/su/plo/slib/api/command/McCommandManager.kt b/api/common/src/main/kotlin/su/plo/slib/api/command/McCommandManager.kt index e5475c8..06d65e8 100644 --- a/api/common/src/main/kotlin/su/plo/slib/api/command/McCommandManager.kt +++ b/api/common/src/main/kotlin/su/plo/slib/api/command/McCommandManager.kt @@ -1,7 +1,8 @@ package su.plo.slib.api.command -import com.google.common.collect.ImmutableMap -import com.google.common.collect.Maps +import com.mojang.brigadier.builder.LiteralArgumentBuilder +import com.mojang.brigadier.context.CommandContext +import su.plo.slib.api.command.brigadier.McBrigadierContext /** * Manages universal commands for multiple server implementations. @@ -11,11 +12,30 @@ import com.google.common.collect.Maps * * @param T The type of commands managed by this manager. */ -abstract class McCommandManager { +interface McCommandManager { - protected val commandByName: MutableMap = Maps.newHashMap() + /** + * Retrieves a read-only map of registered commands. + * + * @return A map containing the registered commands with their names as keys. + */ + val registeredCommands: Map - protected var registered = false + /** + * Retrieves a read-only map of registered brigadier commands. + * + * @return A list containing the registered commands with their names as keys. + */ + val registeredBrigadierCommands: List> + + /** + * Registers a brigadier command. + * + * @param command The instance of the command to register. + * @throws IllegalStateException If attempting to register commands after commands have already been registered. + * @throws IllegalArgumentException If a command with the same name or alias already exists. + */ + fun register(command: LiteralArgumentBuilder) /** * Registers a command with its name and optional aliases. @@ -26,38 +46,20 @@ abstract class McCommandManager { * @throws IllegalStateException If attempting to register commands after commands have already been registered. * @throws IllegalArgumentException If a command with the same name or alias already exists. */ - @Synchronized - fun register(name: String, command: T, vararg aliases: String) { - check(!registered) { "register after commands registration is not supported" } - require(!commandByName.containsKey(name)) { "Command with name '$name' already exist" } - - for (alias in aliases) { - require(!commandByName.containsKey(alias)) { "Command with name '$alias' already exist" } - } - - commandByName[name] = command - for (alias in aliases) { - commandByName[alias] = command - } - } + fun register(name: String, command: T, vararg aliases: String) /** - * Retrieves a read-only map of registered commands. - * - * @return A map containing the registered commands with their names as keys. + * Clears all registered commands and resets the registration state. */ - @get:Synchronized - val registeredCommands: Map - get() = ImmutableMap.copyOf(commandByName) + fun clear() /** - * Clears all registered commands and resets the registration state. + * Gets a brigadier context by server-specific instance. + * + * @param context The server-specific command context instance. + * @return A [McBrigadierContext] instance corresponding to the provided command source instance. */ - @Synchronized - fun clear() { - commandByName.clear() - registered = false - } + fun getBrigadierContext(context: CommandContext): McBrigadierContext /** * Gets a command source by server-specific instance. @@ -69,5 +71,5 @@ abstract class McCommandManager { * @param source The server-specific command source instance. * @return A [McCommandSource] instance corresponding to the provided command source instance. */ - abstract fun getCommandSource(source: Any): McCommandSource + fun getCommandSource(source: Any): McCommandSource } diff --git a/api/common/src/main/kotlin/su/plo/slib/api/command/brigadier/McBrigadierContext.kt b/api/common/src/main/kotlin/su/plo/slib/api/command/brigadier/McBrigadierContext.kt new file mode 100644 index 0000000..9c3736e --- /dev/null +++ b/api/common/src/main/kotlin/su/plo/slib/api/command/brigadier/McBrigadierContext.kt @@ -0,0 +1,16 @@ +package su.plo.slib.api.command.brigadier + +import su.plo.slib.api.command.McCommandSource +import su.plo.slib.api.entity.McEntity + +interface McBrigadierContext { + /** + * Gets the command source that initiated/triggered the execution of a command. + */ + val source: McCommandSource + + /** + * Gets the entity executing this command. + */ + val executor: McEntity? +} diff --git a/api/server/src/main/kotlin/su/plo/slib/api/server/McServerLib.kt b/api/server/src/main/kotlin/su/plo/slib/api/server/McServerLib.kt index 698292c..fe430d3 100644 --- a/api/server/src/main/kotlin/su/plo/slib/api/server/McServerLib.kt +++ b/api/server/src/main/kotlin/su/plo/slib/api/server/McServerLib.kt @@ -1,15 +1,15 @@ package su.plo.slib.api.server import su.plo.slib.api.McLib -import su.plo.slib.api.server.channel.McServerChannelManager import su.plo.slib.api.command.McCommand import su.plo.slib.api.command.McCommandManager -import su.plo.slib.api.server.entity.McServerEntity import su.plo.slib.api.entity.player.McGameProfile +import su.plo.slib.api.server.channel.McServerChannelManager +import su.plo.slib.api.server.entity.McServerEntity import su.plo.slib.api.server.entity.player.McServerPlayer import su.plo.slib.api.server.scheduler.McServerScheduler import su.plo.slib.api.server.world.McServerWorld -import java.util.* +import java.util.UUID interface McServerLib : McLib { @@ -113,7 +113,7 @@ interface McServerLib : McLib { * Creates a new [McServerEntity] instance of wrapped [instance]. * * The [instance] parameter represents the server-specific entity instance: - * - For Bukkit: [org.bukkit.entity.LivingEntity] + * - For Bukkit: [org.bukkit.entity.Entity] * - For modded servers (Fabric/Forge): [net.minecraft.world.entity.Entity] * * @return The entity. diff --git a/api/server/src/main/kotlin/su/plo/slib/api/server/command/brigadier/McArgumentResolver.kt b/api/server/src/main/kotlin/su/plo/slib/api/server/command/brigadier/McArgumentResolver.kt new file mode 100644 index 0000000..402b35b --- /dev/null +++ b/api/server/src/main/kotlin/su/plo/slib/api/server/command/brigadier/McArgumentResolver.kt @@ -0,0 +1,68 @@ +package su.plo.slib.api.server.command.brigadier + +import com.mojang.brigadier.context.CommandContext +import su.plo.slib.api.server.entity.McServerEntity +import su.plo.slib.api.server.entity.player.McServerPlayer +import java.util.ServiceLoader + +object McArgumentResolver { + + /** + * Extracts a single entity from a command context and converts it to [McServerEntity]. + * + * @param context The Brigadier command context. + * @param name The name of the argument. + * @return The selected entity. + */ + @JvmStatic + fun getEntity(context: CommandContext, name: String): McServerEntity = + provider.getEntity(context, name) + + /** + * Extracts multiple entities from a command context and converts them to [McServerEntity]. + * + * @param context The Brigadier command context. + * @param name The name of the argument. + * @return A collection of selected entities. + */ + @JvmStatic + fun getEntities(context: CommandContext, name: String): Collection = + provider.getEntities(context, name) + + /** + * Extracts a single player from a command context and converts it to [McServerPlayer]. + * + * @param context The Brigadier command context. + * @param name The name of the argument. + * @return The selected player. + */ + @JvmStatic + fun getPlayer(context: CommandContext, name: String): McServerPlayer = + provider.getPlayer(context, name) + + /** + * Extracts multiple players from a command context and converts them to [McServerPlayer]. + * + * @param context The Brigadier command context. + * @param name The name of the argument. + * @return A collection of selected players. + */ + @JvmStatic + fun getPlayers(context: CommandContext, name: String): Collection = + provider.getPlayers(context, name) + + private val provider: Provider = + ServiceLoader.load(Provider::class.java, Provider::class.java.getClassLoader()) + .first() + + interface Provider { + + fun getEntity(context: CommandContext, name: String): McServerEntity + + fun getEntities(context: CommandContext, name: String): Collection + + fun getPlayer(context: CommandContext, name: String): McServerPlayer + + fun getPlayers(context: CommandContext, name: String): Collection + } +} diff --git a/api/server/src/main/kotlin/su/plo/slib/api/server/command/brigadier/McArgumentTypes.kt b/api/server/src/main/kotlin/su/plo/slib/api/server/command/brigadier/McArgumentTypes.kt new file mode 100644 index 0000000..76be9dd --- /dev/null +++ b/api/server/src/main/kotlin/su/plo/slib/api/server/command/brigadier/McArgumentTypes.kt @@ -0,0 +1,53 @@ +package su.plo.slib.api.server.command.brigadier + +import com.mojang.brigadier.arguments.ArgumentType +import org.jetbrains.annotations.ApiStatus +import java.util.ServiceLoader + +/** + * Vanilla Minecraft argument types. + */ +object McArgumentTypes { + + /** + * Returns an argument type that selects a single entity. + */ + @JvmStatic + fun entity(): ArgumentType = provider.entity() + + /** + * Returns an argument type that selects multiple entities. + */ + @JvmStatic + fun entities(): ArgumentType = provider.entities() + + /** + * Returns an argument type that selects a single player. + */ + @JvmStatic + fun player(): ArgumentType = provider.player() + + /** + * Returns an argument type that selects multiple players. + */ + @JvmStatic + fun players(): ArgumentType = provider.players() + + private val provider: Provider = + // some loaders can't find service by class's classloader, + // some can't find it by context class loader + // so we're just trying both + ServiceLoader.load(Provider::class.java).firstOrNull() + ?: ServiceLoader.load(Provider::class.java, Provider::class.java.classLoader).first() + + @ApiStatus.Internal + interface Provider { + fun entity(): ArgumentType + + fun entities(): ArgumentType + + fun player(): ArgumentType + + fun players(): ArgumentType + } +} diff --git a/build.gradle.kts b/build.gradle.kts index 1fc0616..5322ae3 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -21,6 +21,7 @@ subprojects { implementation(rootProject.libs.kotlinx.coroutines.jdk8) implementation(rootProject.libs.guava) + api("com.mojang:brigadier:1.0.18") } tasks { @@ -105,6 +106,8 @@ allprojects { maven("https://oss.sonatype.org/content/repositories/snapshots") + maven("https://repo.papermc.io/repository/maven-public/") + maven("https://repo.plasmoverse.com/snapshots") maven("https://repo.plo.su") diff --git a/bungee/build.gradle.kts b/bungee/build.gradle.kts index 677ed72..e13b403 100644 --- a/bungee/build.gradle.kts +++ b/bungee/build.gradle.kts @@ -1,7 +1,13 @@ +import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar +import org.gradle.kotlin.dsl.register + plugins { id("su.plo.slib.shadow-platform") + alias(libs.plugins.run.waterfall) } +val testShadowBundle: Configuration by configurations.creating + repositories { maven("https://repo.codemc.org/repository/maven-public/") maven("https://repo.papermc.io/repository/maven-public/") @@ -12,6 +18,9 @@ dependencies { testCompileOnly(libs.bungee.api) compileOnly(libs.bungee.proxy) + testCompileOnly(testFixtures(project(":common-proxy"))) + testShadowBundle(testFixtures(project(":common-proxy"))) + compileOnly(project(":common")) compileOnly(project(":common-integration")) listOf( @@ -36,6 +45,10 @@ dependencies { } } +runWaterfallExtension { + disablePluginJarDetection() +} + tasks { shadowJar { archiveClassifier = "all" @@ -53,6 +66,21 @@ tasks { from(project(":common-integration").sourceSets.main.get().output) } + val testJar = + register("testJar", ShadowJar::class) { + configurations = listOf(testShadowBundle) + + archiveClassifier.set("test") + + from(zipTree(finalJar.get().archiveFile)) + from(sourceSets.test.get().output) + } + + runWaterfall { + waterfallVersion("1.21") + pluginJars.from(testJar) + } + build { dependsOn(finalJar) } diff --git a/bungee/src/main/kotlin/su/plo/slib/bungee/command/BungeeCommandManager.kt b/bungee/src/main/kotlin/su/plo/slib/bungee/command/BungeeCommandManager.kt index aeb0b99..920e1f9 100644 --- a/bungee/src/main/kotlin/su/plo/slib/bungee/command/BungeeCommandManager.kt +++ b/bungee/src/main/kotlin/su/plo/slib/bungee/command/BungeeCommandManager.kt @@ -1,5 +1,6 @@ package su.plo.slib.bungee.command +import com.mojang.brigadier.context.CommandContext import net.md_5.bungee.api.CommandSender import net.md_5.bungee.api.ProxyServer import net.md_5.bungee.api.connection.ProxiedPlayer @@ -7,15 +8,18 @@ import net.md_5.bungee.api.event.ChatEvent import net.md_5.bungee.api.plugin.Listener import net.md_5.bungee.api.plugin.Plugin import net.md_5.bungee.event.EventHandler -import su.plo.slib.api.command.McCommandManager import su.plo.slib.api.command.McCommandSource +import su.plo.slib.api.command.brigadier.McBrigadierContext +import su.plo.slib.api.entity.McEntity import su.plo.slib.api.proxy.command.McProxyCommand import su.plo.slib.api.proxy.event.command.McProxyCommandExecuteEvent import su.plo.slib.bungee.BungeeProxyLib +import su.plo.slib.bungee.command.brigadier.BrigadierBungeeCommand +import su.plo.slib.command.AbstractCommandManager class BungeeCommandManager( private val minecraftProxy: BungeeProxyLib -) : McCommandManager(), Listener { +) : AbstractCommandManager(), Listener { @EventHandler fun onChat(event: ChatEvent) { @@ -28,12 +32,20 @@ class BungeeCommandManager( @Synchronized fun registerCommands(plugin: Plugin, proxyServer: ProxyServer) { - commandByName.forEach { (name: String, command: McProxyCommand) -> + registerCommands { name, command -> proxyServer.pluginManager.registerCommand(plugin, BungeeCommand(minecraftProxy, this, command, name)) } + + registerBrigadierCommands { command -> + proxyServer.pluginManager.registerCommand(plugin, BrigadierBungeeCommand(this, command)) + } + registered = true } + override fun getBrigadierContext(context: CommandContext): McBrigadierContext = + context.source as BrigadierContext + override fun getCommandSource(source: Any): McCommandSource { require(source is CommandSender) { "source is not ${CommandSender::class.java}" } @@ -41,4 +53,9 @@ class BungeeCommandManager( minecraftProxy.getPlayerByInstance(source) } else BungeeDefaultCommandSource(minecraftProxy, source) } + + data class BrigadierContext( + override val source: McCommandSource, + override val executor: McEntity? = null, + ) : McBrigadierContext } diff --git a/bungee/src/main/kotlin/su/plo/slib/bungee/command/brigadier/BrigadierBungeeCommand.kt b/bungee/src/main/kotlin/su/plo/slib/bungee/command/brigadier/BrigadierBungeeCommand.kt new file mode 100644 index 0000000..b569c73 --- /dev/null +++ b/bungee/src/main/kotlin/su/plo/slib/bungee/command/brigadier/BrigadierBungeeCommand.kt @@ -0,0 +1,58 @@ +package su.plo.slib.bungee.command.brigadier + +import com.mojang.brigadier.CommandDispatcher +import com.mojang.brigadier.builder.LiteralArgumentBuilder +import com.mojang.brigadier.exceptions.CommandSyntaxException +import net.md_5.bungee.api.CommandSender +import net.md_5.bungee.api.plugin.Command +import net.md_5.bungee.api.plugin.TabExecutor +import su.plo.slib.api.chat.component.McTextComponent +import su.plo.slib.api.chat.style.McTextStyle +import su.plo.slib.bungee.command.BungeeCommandManager +import su.plo.slib.bungee.command.BungeeCommandManager.BrigadierContext + +class BrigadierBungeeCommand( + private val commandManager: BungeeCommandManager, + private val command: LiteralArgumentBuilder, +) : Command(command.literal), TabExecutor { + private val dispatcher = CommandDispatcher() + + init { + @Suppress("UNCHECKED_CAST") + dispatcher.register(command as LiteralArgumentBuilder) + } + + override fun execute(sender: CommandSender, arguments: Array) { + val context = BrigadierContext(commandManager.getCommandSource(sender)) + val input = listOf(command.literal, *arguments).joinToString(" ") + + try { + dispatcher.execute(input, context) + } catch (e: CommandSyntaxException) { + context.source.sendMessage( + McTextComponent.translatable( + "command.context.parse_error", + McTextComponent.literal(e.rawMessage.string), + McTextComponent.literal(e.cursor.toString()), + McTextComponent.literal(e.context), + ).withStyle(McTextStyle.RED) + ) + } catch (e: Exception) { + context.source.sendMessage( + McTextComponent.literal(e.message ?: "Unknown error").withStyle(McTextStyle.RED) + ) + } + } + + override fun onTabComplete(sender: CommandSender, arguments: Array): Iterable { + val context = BrigadierContext(commandManager.getCommandSource(sender)) + val input = listOf(command.literal, *arguments).joinToString(" ") + + return dispatcher.getCompletionSuggestions( + dispatcher.parse(input, context), + input.length, + ) + .thenApply { suggestions -> suggestions.list.map { it.text } } + .get() + } +} diff --git a/bungee/src/test/kotlin/BungeePlugin.kt b/bungee/src/test/kotlin/BungeePlugin.kt deleted file mode 100644 index e42ea1c..0000000 --- a/bungee/src/test/kotlin/BungeePlugin.kt +++ /dev/null @@ -1,20 +0,0 @@ -import net.md_5.bungee.api.plugin.Plugin -import su.plo.slib.api.proxy.event.command.McProxyCommandsRegisterEvent -import su.plo.slib.bungee.BungeeProxyLib - -class BungeePlugin : Plugin() { - - init { - McProxyCommandsRegisterEvent.registerListener { commandManager, minecraftServer -> - // register commands here - // commandManager.register("pepega", PepegaCommand()) - } - } - - private lateinit var minecraftServer: BungeeProxyLib - - override fun onEnable() { - // you need to initialize lib here - minecraftServer = BungeeProxyLib(this) - } -} diff --git a/bungee/src/test/kotlin/su/plo/slib/bungee/TestBungeePlugin.kt b/bungee/src/test/kotlin/su/plo/slib/bungee/TestBungeePlugin.kt new file mode 100644 index 0000000..90e62d8 --- /dev/null +++ b/bungee/src/test/kotlin/su/plo/slib/bungee/TestBungeePlugin.kt @@ -0,0 +1,12 @@ +package su.plo.slib.bungee + +import net.md_5.bungee.api.plugin.Plugin +import su.plo.slib.proxy.TestProxy + +class TestBungeePlugin : Plugin() { + private val testProxy = TestProxy() + + override fun onEnable() { + val minecraftServer = BungeeProxyLib(this) + } +} diff --git a/bungee/src/test/resources/bungee.yml b/bungee/src/test/resources/bungee.yml new file mode 100644 index 0000000..0ea24b3 --- /dev/null +++ b/bungee/src/test/resources/bungee.yml @@ -0,0 +1,3 @@ +name: slib-bungee-test +version: dev +main: su.plo.slib.bungee.TestBungeePlugin diff --git a/common-proxy/build.gradle.kts b/common-proxy/build.gradle.kts new file mode 100644 index 0000000..5a42ae1 --- /dev/null +++ b/common-proxy/build.gradle.kts @@ -0,0 +1,10 @@ +plugins { + `java-test-fixtures` +} + +dependencies { + testFixturesImplementation(kotlin("stdlib-jdk8")) + + testFixturesImplementation(project(":api:api-common")) + testFixturesImplementation(project(":api:api-proxy")) +} diff --git a/common-proxy/src/testFixtures/kotlin/su/plo/slib/proxy/TestProxy.kt b/common-proxy/src/testFixtures/kotlin/su/plo/slib/proxy/TestProxy.kt new file mode 100644 index 0000000..1d15ec1 --- /dev/null +++ b/common-proxy/src/testFixtures/kotlin/su/plo/slib/proxy/TestProxy.kt @@ -0,0 +1,45 @@ +package su.plo.slib.proxy + +import com.mojang.brigadier.Command +import com.mojang.brigadier.builder.LiteralArgumentBuilder +import su.plo.slib.api.command.McCommandSource +import su.plo.slib.api.event.player.McPlayerJoinEvent +import su.plo.slib.api.event.player.McPlayerQuitEvent +import su.plo.slib.api.logging.McLoggerFactory +import su.plo.slib.api.proxy.command.McProxyCommand +import su.plo.slib.api.proxy.event.command.McProxyCommandsRegisterEvent + +class TestProxy { + private var logger = McLoggerFactory.createLogger("TestProxy") + + init { + McPlayerJoinEvent.registerListener { player -> + logger.info("Player ${player.name} joined the server") + } + + McPlayerQuitEvent.registerListener { player -> + logger.info("Player ${player.name} quit the server") + } + + McProxyCommandsRegisterEvent.registerListener { commands, minecraftProxy -> + commands.register("ping", object : McProxyCommand { + override fun execute( + source: McCommandSource, + arguments: Array, + ) { + source.sendMessage("Pong") + } + }) + + commands.register( + LiteralArgumentBuilder.literal("brigadier-ping") + .executes { + val context = commands.getBrigadierContext(it) + context.source.sendMessage("Pong") + + Command.SINGLE_SUCCESS + } + ) + } + } +} diff --git a/common-server/src/testFixtures/kotlin/su/plo/slib/server/TestServer.kt b/common-server/src/testFixtures/kotlin/su/plo/slib/server/TestServer.kt index 1c16f7e..9bdc007 100644 --- a/common-server/src/testFixtures/kotlin/su/plo/slib/server/TestServer.kt +++ b/common-server/src/testFixtures/kotlin/su/plo/slib/server/TestServer.kt @@ -1,18 +1,23 @@ package su.plo.slib.server +import com.mojang.brigadier.Command +import com.mojang.brigadier.builder.LiteralArgumentBuilder +import com.mojang.brigadier.builder.RequiredArgumentBuilder import su.plo.slib.api.command.McCommand import su.plo.slib.api.command.McCommandSource import su.plo.slib.api.event.player.McPlayerJoinEvent import su.plo.slib.api.event.player.McPlayerQuitEvent import su.plo.slib.api.logging.McLoggerFactory import su.plo.slib.api.server.McServerLib +import su.plo.slib.api.server.command.brigadier.McArgumentResolver +import su.plo.slib.api.server.command.brigadier.McArgumentTypes import su.plo.slib.api.server.event.command.McServerCommandsRegisterEvent import su.plo.slib.api.server.event.player.McPlayerRegisterChannelsEvent class TestServer( val minecraftServer: McServerLib, ) { - private var logger = McLoggerFactory.createLogger("TestMod") + private var logger = McLoggerFactory.createLogger("TestServer") val channelKey = "slib:channels/test" @@ -30,15 +35,77 @@ class TestServer( } McServerCommandsRegisterEvent.registerListener { commands, _ -> - commands.register("ping", object : McCommand { - override fun execute( - source: McCommandSource, - arguments: Array, - ) { - source.sendMessage("Pong") - } - }) - logger.info("Command 'ping' registered") + commands.register( + "ping", + object : McCommand { + override fun execute( + source: McCommandSource, + arguments: Array, + ) { + source.sendMessage("Pong") + } + }, + ) + + commands.register( + LiteralArgumentBuilder.literal("brigadier-entity-selector") + .then( + LiteralArgumentBuilder.literal("entity") + .then( + RequiredArgumentBuilder.argument("target", McArgumentTypes.entity()) + .executes { + val entity = McArgumentResolver.getEntity(it, "target") + + val context = commands.getBrigadierContext(it) + context.source.sendMessage("Found entity: $entity; Source: ${context.source}; Executor: ${context.executor}") + + Command.SINGLE_SUCCESS + } + ) + ) + .then( + LiteralArgumentBuilder.literal("entities") + .then( + RequiredArgumentBuilder.argument("target", McArgumentTypes.entities()) + .executes { + val entities = McArgumentResolver.getEntities(it, "target") + + val context = commands.getBrigadierContext(it) + context.source.sendMessage("Found entities: $entities; Source: ${context.source}; Executor: ${context.executor}") + + Command.SINGLE_SUCCESS + } + ) + ) + .then( + LiteralArgumentBuilder.literal("player") + .then( + RequiredArgumentBuilder.argument("target", McArgumentTypes.player()) + .executes { + val player = McArgumentResolver.getPlayer(it, "target") + + val context = commands.getBrigadierContext(it) + context.source.sendMessage("Found player: $player; Source: ${context.source}; Executor: ${context.executor}") + + Command.SINGLE_SUCCESS + } + ) + ) + .then( + LiteralArgumentBuilder.literal("players") + .then( + RequiredArgumentBuilder.argument("target", McArgumentTypes.players()) + .executes { + val players = McArgumentResolver.getPlayers(it, "target") + + val context = commands.getBrigadierContext(it) + context.source.sendMessage("Found players: $players; Source: ${context.source}; Executor: ${context.executor}") + + Command.SINGLE_SUCCESS + } + ) + ) + ) } } diff --git a/common/src/main/kotlin/su/plo/slib/command/AbstractCommandManager.kt b/common/src/main/kotlin/su/plo/slib/command/AbstractCommandManager.kt new file mode 100644 index 0000000..b16ed72 --- /dev/null +++ b/common/src/main/kotlin/su/plo/slib/command/AbstractCommandManager.kt @@ -0,0 +1,69 @@ +package su.plo.slib.command + +import com.google.common.collect.ImmutableList +import com.google.common.collect.ImmutableMap +import com.google.common.collect.Maps +import com.mojang.brigadier.builder.LiteralArgumentBuilder +import su.plo.slib.api.command.McCommand +import su.plo.slib.api.command.McCommandManager +import su.plo.slib.api.logging.McLoggerFactory + +abstract class AbstractCommandManager : McCommandManager { + private val logger = McLoggerFactory.createLogger("CommandManager") + + protected val commandByName: MutableMap = Maps.newHashMap() + protected var brigadierCommands: MutableList> = mutableListOf() + + protected var registered = false + + @get:Synchronized + override val registeredCommands: Map + get() = ImmutableMap.copyOf(commandByName) + + @get:Synchronized + override val registeredBrigadierCommands: List> + get() = ImmutableList.copyOf(brigadierCommands) + + @Synchronized + override fun register(command: LiteralArgumentBuilder) { + check(!registered) { "register after commands registration is not supported" } + require(brigadierCommands.none { it.literal == command.literal }) { "Command with name '${command.literal}' already exist" } + + brigadierCommands.add(command) + } + + @Synchronized + override fun register(name: String, command: T, vararg aliases: String) { + check(!registered) { "register after commands registration is not supported" } + require(!commandByName.containsKey(name)) { "Command with name '$name' already exist" } + + for (alias in aliases) { + require(!commandByName.containsKey(alias)) { "Command with name '$alias' already exist" } + } + + commandByName[name] = command + for (alias in aliases) { + commandByName[alias] = command + } + } + + @Synchronized + override fun clear() { + commandByName.clear() + registered = false + } + + protected fun registerCommands(register: (String, T) -> Unit) { + commandByName.forEach { (name, command) -> + register(name, command) + logger.info("Command '$name' registered") + } + } + + protected fun registerBrigadierCommands(register: (LiteralArgumentBuilder) -> Unit) { + brigadierCommands.forEach { command -> + register(command) + logger.info("Command '${command.literal}' registered") + } + } +} diff --git a/gradle.properties b/gradle.properties index 15804ff..f8a7c9d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ # Version group=su.plo.slib -version=1.2.1 +version=1.3.0 # Gradle args org.gradle.jvmargs=-Xmx4G -Dkotlin.daemon.jvm.options=-Xmx512M diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 07dff40..834bf7a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,7 +2,7 @@ kotlin = "2.3.10" kotlinx-coroutines = "1.10.2" dokka = "2.1.0" -runpaper = "3.0.2" +run-task = "3.0.2" architectury = "1.15.48" shadow = "9.3.2" @@ -18,12 +18,14 @@ folia = "1.20.1-R0.1-SNAPSHOT" velocity = "3.1.1" bungee = "1.21-R0.4-SNAPSHOT" -minestom = "39d445482f" +minestom = "2025.10.05-1.21.8" adventure = "4.21.0" adventure-platform = "4.4.0" fabric-permissions = "0.3.1" +reflection-remapper = "0.1.2" + [libraries] kotlinx-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" } kotlinx-coroutines-jdk8 = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-jdk8", version.ref = "kotlinx-coroutines" } @@ -39,7 +41,7 @@ velocity = { module = "com.velocitypowered:velocity-api", version.ref = "velocit bungee-api = { module = "net.md-5:bungeecord-api", version.ref = "bungee" } bungee-proxy = { module = "net.md-5:bungeecord-proxy", version.ref = "bungee" } -minestom = { module = "net.minestom:minestom-snapshots", version.ref = "minestom" } +minestom = { module = "net.minestom:minestom", version.ref = "minestom" } adventure-api = { module = "net.kyori:adventure-api", version.ref = "adventure" } adventure-gson = { module = "net.kyori:adventure-text-serializer-gson", version.ref = "adventure" } @@ -49,10 +51,14 @@ adventure-bukkit = { module = "net.kyori:adventure-platform-bukkit", version.ref adventure-bungee = { module = "net.kyori:adventure-platform-bungeecord", version.ref = "adventure-platform" } fabric-permissions = { module = "me.lucko:fabric-permissions-api", version.ref = "fabric-permissions" } +reflectionremapper = { module = "xyz.jpenilla:reflection-remapper", version.ref = "reflection-remapper" } + shadow = { module = "com.gradleup.shadow:com.gradleup.shadow.gradle.plugin", version.ref = "shadow" } asm = { module = "org.ow2.asm:asm-commons", version.ref = "asm" } [plugins] architectury = { id = "gg.essential.loom", version.ref = "architectury" } dokka = { id = "org.jetbrains.dokka", version.ref = "dokka" } -runpaper = { id = "xyz.jpenilla.run-paper", version.ref = "runpaper"} +run-paper = { id = "xyz.jpenilla.run-paper", version.ref = "run-task"} +run-velocity = { id = "xyz.jpenilla.run-velocity", version.ref = "run-task"} +run-waterfall = { id = "xyz.jpenilla.run-waterfall", version.ref = "run-task"} diff --git a/minestom/build.gradle.kts b/minestom/build.gradle.kts index db1ad66..2c0e581 100644 --- a/minestom/build.gradle.kts +++ b/minestom/build.gradle.kts @@ -13,12 +13,32 @@ dependencies { project(":common", "shadow") ).forEach { api(it) + testImplementation(it) shadow(it) { isTransitive = false } } + + testImplementation(libs.minestom) + testImplementation(testFixtures(project(":common-server"))) + + testImplementation("org.apache.logging.log4j:log4j-core:2.25.3") + testImplementation("org.slf4j:slf4j-log4j12:2.0.17") } tasks { java { toolchain.languageVersion.set(JavaLanguageVersion.of(21)) } + + register("runServer") { + group = "application" + + workingDir = layout.projectDirectory.dir("run").asFile + + doFirst { + workingDir.mkdirs() + } + + classpath = sourceSets["test"].runtimeClasspath + mainClass.set("su.plo.slib.minestom.TestMinestomServerKt") + } } diff --git a/minestom/src/main/kotlin/su/plo/slib/minestom/MinestomServerLib.kt b/minestom/src/main/kotlin/su/plo/slib/minestom/MinestomServerLib.kt index b9073b3..68d7e49 100644 --- a/minestom/src/main/kotlin/su/plo/slib/minestom/MinestomServerLib.kt +++ b/minestom/src/main/kotlin/su/plo/slib/minestom/MinestomServerLib.kt @@ -23,8 +23,8 @@ import su.plo.slib.chat.AdventureComponentTextConverter import su.plo.slib.integration.IntegrationLoader import su.plo.slib.language.ServerTranslatorFactory import su.plo.slib.logging.Slf4jLogger -import su.plo.slib.minestom.channel.RegisterChannelHandler import su.plo.slib.minestom.channel.MinestomChannelManager +import su.plo.slib.minestom.channel.RegisterChannelHandler import su.plo.slib.minestom.command.MinestomCommandManager import su.plo.slib.minestom.entity.MinestomServerEntity import su.plo.slib.minestom.entity.MinestomServerPlayer @@ -34,7 +34,7 @@ import su.plo.slib.minestom.world.MinestomServerWorld import java.io.ByteArrayOutputStream import java.io.File import java.io.IOException -import java.util.* +import java.util.UUID import java.util.function.Consumer class MinestomServerLib( @@ -43,6 +43,7 @@ class MinestomServerLib( init { McLoggerFactory.supplier = McLoggerFactory.Supplier { name -> Slf4jLogger(name) } + instance = this } private val worldByInstance: MutableMap = Maps.newConcurrentMap() @@ -177,4 +178,8 @@ class MinestomServerLib( ) playerById.remove(event.player.uuid) } + + companion object { + lateinit var instance: MinestomServerLib + } } diff --git a/minestom/src/main/kotlin/su/plo/slib/minestom/command/MinestomCommandManager.kt b/minestom/src/main/kotlin/su/plo/slib/minestom/command/MinestomCommandManager.kt index c6d19fb..84dce22 100644 --- a/minestom/src/main/kotlin/su/plo/slib/minestom/command/MinestomCommandManager.kt +++ b/minestom/src/main/kotlin/su/plo/slib/minestom/command/MinestomCommandManager.kt @@ -1,24 +1,50 @@ package su.plo.slib.minestom.command +import com.mojang.brigadier.arguments.BoolArgumentType +import com.mojang.brigadier.arguments.DoubleArgumentType +import com.mojang.brigadier.arguments.FloatArgumentType +import com.mojang.brigadier.arguments.IntegerArgumentType +import com.mojang.brigadier.arguments.LongArgumentType +import com.mojang.brigadier.arguments.StringArgumentType +import com.mojang.brigadier.context.CommandContext +import com.mojang.brigadier.context.ParsedArgument +import com.mojang.brigadier.context.StringRange +import com.mojang.brigadier.exceptions.CommandSyntaxException +import com.mojang.brigadier.tree.ArgumentCommandNode +import com.mojang.brigadier.tree.LiteralCommandNode import net.minestom.server.MinecraftServer import net.minestom.server.command.CommandSender import net.minestom.server.command.builder.Command +import net.minestom.server.command.builder.CommandExecutor +import net.minestom.server.command.builder.arguments.Argument +import net.minestom.server.command.builder.arguments.ArgumentBoolean +import net.minestom.server.command.builder.arguments.ArgumentString +import net.minestom.server.command.builder.arguments.ArgumentWord +import net.minestom.server.command.builder.arguments.number.ArgumentDouble +import net.minestom.server.command.builder.arguments.number.ArgumentFloat +import net.minestom.server.command.builder.arguments.number.ArgumentInteger +import net.minestom.server.command.builder.arguments.number.ArgumentLong import net.minestom.server.entity.Player +import su.plo.slib.api.chat.component.McTextComponent +import su.plo.slib.api.chat.style.McTextStyle import su.plo.slib.api.command.McCommand -import su.plo.slib.api.command.McCommandManager import su.plo.slib.api.command.McCommandSource +import su.plo.slib.api.command.brigadier.McBrigadierContext +import su.plo.slib.api.entity.McEntity import su.plo.slib.api.server.McServerLib import su.plo.slib.api.server.event.command.McServerCommandsRegisterEvent +import su.plo.slib.command.AbstractCommandManager +import su.plo.slib.minestom.command.brigadier.MinestomArgumentType class MinestomCommandManager( private val minecraftServer: McServerLib -) : McCommandManager() { +) : AbstractCommandManager() { @Synchronized fun registerCommands() { McServerCommandsRegisterEvent.invoker.onCommandsRegister(this, minecraftServer) - commandByName.forEach { (name, command) -> + registerCommands { name, command -> val cmd = Command(name) cmd.setDefaultExecutor { sender, context -> val source = getCommandSource(sender) @@ -33,13 +59,117 @@ class MinestomCommandManager( MinecraftServer.getCommandManager().register(cmd) } + registerBrigadierCommands { command -> + MinecraftServer.getCommandManager().register(command.build().toMinestom()) + } + registered = true } + override fun getBrigadierContext(context: CommandContext): McBrigadierContext = + context.source as McBrigadierContext + override fun getCommandSource(source: Any): McCommandSource { require(source is CommandSender) { "source is not ${CommandSender::class.java}" } return if (source is Player) minecraftServer.getPlayerByInstance(source) else MinestomDefaultCommandSource(minecraftServer.textConverter, source) } + + private fun LiteralCommandNode<*>.toMinestom(): Command { + val minestomCommand = Command(name) + + val commands = children.filterIsInstance>() + .map { it.toMinestom() } + commands.forEach { minestomCommand.addSubcommand(it) } + + val executor = command?.toMinestom() ?: noopCommandExecutor() + + val arguments = children.filterIsInstance>() + .map { it.toMinestom() } + arguments.forEach {(argument, argumentExecutor) -> + minestomCommand.addSyntax(argumentExecutor ?: executor, argument) + } + + minestomCommand.defaultExecutor = executor + + return minestomCommand + } + + private fun noopCommandExecutor(): CommandExecutor = + object : CommandExecutor { + override fun apply( + sender: CommandSender, + context: net.minestom.server.command.builder.CommandContext, + ) { + } + } + + private fun ArgumentCommandNode<*, *>.toMinestom(): Pair, CommandExecutor?> { + val argumentType = type + val argument = when (argumentType) { + is BoolArgumentType -> ArgumentBoolean(name) + is DoubleArgumentType -> ArgumentDouble(name) + is FloatArgumentType -> ArgumentFloat(name) + is IntegerArgumentType -> ArgumentInteger(name) + is LongArgumentType -> ArgumentLong(name) + is StringArgumentType -> + when (argumentType.type) { + StringArgumentType.StringType.GREEDY_PHRASE -> ArgumentString(name) + StringArgumentType.StringType.SINGLE_WORD -> ArgumentWord(name) + StringArgumentType.StringType.QUOTABLE_PHRASE -> ArgumentString(name) + } + else -> (type as MinestomArgumentType<*>).argumentBuilder(name) + } + val executor = command?.toMinestom() + + return argument to executor + } + + private fun com.mojang.brigadier.Command<*>.toMinestom(): CommandExecutor = + object : CommandExecutor { + override fun apply( + sender: CommandSender, + context: net.minestom.server.command.builder.CommandContext, + ) { + val source = getCommandSource(sender) + + @Suppress("UNCHECKED_CAST") + val brigadierContext = CommandContext( + BrigadierContext(sender, source, source as? McEntity), + context.input, + context.map.mapValues { ParsedArgument(0, 0, it.value) }, + this@toMinestom as com.mojang.brigadier.Command, + null, + emptyList(), + StringRange(0, 0), + null, + null, + false, + ) + + try { + this@toMinestom.run(brigadierContext) + } catch (e: CommandSyntaxException) { + source.sendMessage( + McTextComponent.translatable( + "command.context.parse_error", + McTextComponent.literal(e.rawMessage.string), + McTextComponent.literal(e.cursor.toString()), + McTextComponent.literal(e.context), + ).withStyle(McTextStyle.RED) + ) + } catch (e: Exception) { + source.sendMessage( + McTextComponent.literal(e.message ?: "Unknown error").withStyle(McTextStyle.RED) + ) + } + } + } + + data class BrigadierContext( + val sender: CommandSender, + override val source: McCommandSource, + override val executor: McEntity? + ) : McBrigadierContext } diff --git a/minestom/src/main/kotlin/su/plo/slib/minestom/command/brigadier/MinestomArgumentType.kt b/minestom/src/main/kotlin/su/plo/slib/minestom/command/brigadier/MinestomArgumentType.kt new file mode 100644 index 0000000..97b1d21 --- /dev/null +++ b/minestom/src/main/kotlin/su/plo/slib/minestom/command/brigadier/MinestomArgumentType.kt @@ -0,0 +1,13 @@ +package su.plo.slib.minestom.command.brigadier + +import com.mojang.brigadier.StringReader +import com.mojang.brigadier.arguments.ArgumentType +import net.minestom.server.command.builder.arguments.Argument + +data class MinestomArgumentType( + val argumentBuilder: (String) -> Argument, +) : ArgumentType { + override fun parse(reader: StringReader): S { + throw UnsupportedOperationException() + } +} diff --git a/minestom/src/main/kotlin/su/plo/slib/minestom/command/brigadier/MinestomEntityArguments.kt b/minestom/src/main/kotlin/su/plo/slib/minestom/command/brigadier/MinestomEntityArguments.kt new file mode 100644 index 0000000..141a2e4 --- /dev/null +++ b/minestom/src/main/kotlin/su/plo/slib/minestom/command/brigadier/MinestomEntityArguments.kt @@ -0,0 +1,81 @@ +package su.plo.slib.minestom.command.brigadier + +import com.mojang.brigadier.arguments.ArgumentType +import com.mojang.brigadier.context.CommandContext +import net.minestom.server.command.builder.arguments.minecraft.ArgumentEntity +import net.minestom.server.entity.Player +import net.minestom.server.utils.entity.EntityFinder +import su.plo.slib.api.server.command.brigadier.McArgumentResolver +import su.plo.slib.api.server.command.brigadier.McArgumentTypes +import su.plo.slib.api.server.entity.McServerEntity +import su.plo.slib.api.server.entity.player.McServerPlayer +import su.plo.slib.minestom.MinestomServerLib +import su.plo.slib.minestom.command.MinestomCommandManager + +class MinestomEntityArguments : McArgumentTypes.Provider, McArgumentResolver.Provider { + + private val serverLib by lazy { + MinestomServerLib.instance + } + + @Suppress("UNCHECKED_CAST") + override fun entity(): ArgumentType = + MinestomArgumentType { name -> + ArgumentEntity(name).singleEntity(true).onlyPlayers(false) + } as ArgumentType + + @Suppress("UNCHECKED_CAST") + override fun entities(): ArgumentType = + MinestomArgumentType { name -> + ArgumentEntity(name).singleEntity(false).onlyPlayers(false) + } as ArgumentType + + @Suppress("UNCHECKED_CAST") + override fun player(): ArgumentType = + MinestomArgumentType { name -> + ArgumentEntity(name).singleEntity(true).onlyPlayers(true) + } as ArgumentType + + @Suppress("UNCHECKED_CAST") + override fun players(): ArgumentType = + MinestomArgumentType { name -> + ArgumentEntity(name).singleEntity(false).onlyPlayers(true) + } as ArgumentType + + override fun getEntity(context: CommandContext, name: String): McServerEntity { + val finder = context.getArgument(name, EntityFinder::class.java) + val brigadierContext = context.source as MinestomCommandManager.BrigadierContext + + val entity = finder.findFirstEntity(brigadierContext.sender) + ?: throw IllegalArgumentException("No entity found") + + return serverLib.getEntityByInstance(entity) + + } + + override fun getEntities(context: CommandContext, name: String): Collection { + val finder = context.getArgument(name, EntityFinder::class.java) + val brigadierContext = context.source as MinestomCommandManager.BrigadierContext + + return finder.find(brigadierContext.sender).map { serverLib.getEntityByInstance(it) } + } + + override fun getPlayer(context: CommandContext, name: String): McServerPlayer { + val finder = context.getArgument(name, EntityFinder::class.java) + val brigadierContext = context.source as MinestomCommandManager.BrigadierContext + + val player = finder.findFirstPlayer(brigadierContext.sender) + ?: throw IllegalArgumentException("No player found") + + return serverLib.getPlayerByInstance(player) + } + + override fun getPlayers(context: CommandContext, name: String): Collection { + val finder = context.getArgument(name, EntityFinder::class.java) + val brigadierContext = context.source as MinestomCommandManager.BrigadierContext + + return finder.find(brigadierContext.sender) + .filterIsInstance() + .map { serverLib.getPlayerByInstance(it) } + } +} diff --git a/minestom/src/main/resources/META-INF/services/su.plo.slib.api.server.command.brigadier.McArgumentResolver$Provider b/minestom/src/main/resources/META-INF/services/su.plo.slib.api.server.command.brigadier.McArgumentResolver$Provider new file mode 100644 index 0000000..4896f87 --- /dev/null +++ b/minestom/src/main/resources/META-INF/services/su.plo.slib.api.server.command.brigadier.McArgumentResolver$Provider @@ -0,0 +1 @@ +su.plo.slib.minestom.command.brigadier.MinestomEntityArguments diff --git a/minestom/src/main/resources/META-INF/services/su.plo.slib.api.server.command.brigadier.McArgumentTypes$Provider b/minestom/src/main/resources/META-INF/services/su.plo.slib.api.server.command.brigadier.McArgumentTypes$Provider new file mode 100644 index 0000000..4896f87 --- /dev/null +++ b/minestom/src/main/resources/META-INF/services/su.plo.slib.api.server.command.brigadier.McArgumentTypes$Provider @@ -0,0 +1 @@ +su.plo.slib.minestom.command.brigadier.MinestomEntityArguments diff --git a/minestom/src/test/kotlin/su/plo/slib/minestom/TestMinestomServer.kt b/minestom/src/test/kotlin/su/plo/slib/minestom/TestMinestomServer.kt new file mode 100644 index 0000000..aa7b22f --- /dev/null +++ b/minestom/src/test/kotlin/su/plo/slib/minestom/TestMinestomServer.kt @@ -0,0 +1,52 @@ +package su.plo.slib.minestom + +import net.minestom.server.MinecraftServer +import net.minestom.server.coordinate.Pos +import net.minestom.server.entity.GameMode +import net.minestom.server.event.Event +import net.minestom.server.event.GlobalEventHandler +import net.minestom.server.event.player.AsyncPlayerConfigurationEvent +import net.minestom.server.event.player.PlayerSpawnEvent +import su.plo.slib.api.logging.McLoggerFactory +import su.plo.slib.server.TestServer +import java.io.File +import kotlin.time.measureTime + +private val logger = McLoggerFactory.createLogger("Server") + +fun main() { + val startTime = measureTime { startServer() } + logger.info("Done (%.3fs)!".format(startTime.inWholeMilliseconds.toDouble() / 1000)) +} + +fun startServer() { + val minecraftServer = MinecraftServer.init() + + val minecraftServerLib = MinestomServerLib(File("slib-test")) + val testServer = TestServer(minecraftServerLib) + + minecraftServerLib.onInitialize() + testServer.registerChannels() + + val instanceManager = MinecraftServer.getInstanceManager() + val instanceContainer = instanceManager.createInstanceContainer() + + MinecraftServer.getGlobalEventHandler().apply { + addListener { event -> + val player = event.player + player.respawnPoint = Pos(0.0, 0.0, 0.0) + event.spawningInstance = instanceContainer + } + + addListener { event -> + val player = event.player + player.gameMode = GameMode.SPECTATOR + } + } + + minecraftServer.start("0.0.0.0", 25565) +} + +private inline fun GlobalEventHandler.addListener(crossinline listener: (T) -> Unit) { + addListener(T::class.java) { listener.invoke(it) } +} diff --git a/minestom/src/test/resources/log4j.properties b/minestom/src/test/resources/log4j.properties new file mode 100644 index 0000000..768df25 --- /dev/null +++ b/minestom/src/test/resources/log4j.properties @@ -0,0 +1,21 @@ +log4j.rootLogger=DEBUG, file, console + +log4j.appender.console=org.apache.log4j.ConsoleAppender +logrj.appender.console.Target=System.out +log4j.appender.console.layout=org.apache.log4j.PatternLayout +log4j.appender.console.layout.ConversionPattern=%-5p %c{1} - %m%n + +log4j.appender.file=org.apache.log4j.RollingFileAppender +log4j.appender.file.File=logs/latest.log +log4j.appender.file.Append=true +log4j.appender.file.ImmediateFlush=true +log4j.appender.file.MaxFileSize=10MB +log4j.appender.file.MaxBackupIndex=5 +log4j.appender.file.layout=org.apache.log4j.PatternLayout +log4j.appender.file.layout.ConversionPattern=%d %d{Z} [%t] %-5p (%F:%L) - %m%n + +log4j.logger.com.journaldev.log4j=WARN, file, console +log4j.logger.com.journaldev.log4j.logic=DEBUG, file, console + +log4j.additivity.com.journaldev.log4j=false +log4j.additivity.com.journaldev.log4j.logic=false diff --git a/modded/src/main/kotlin/su/plo/slib/mod/command/ModCommandManager.kt b/modded/src/main/kotlin/su/plo/slib/mod/command/ModCommandManager.kt index d0e241d..07182c8 100644 --- a/modded/src/main/kotlin/su/plo/slib/mod/command/ModCommandManager.kt +++ b/modded/src/main/kotlin/su/plo/slib/mod/command/ModCommandManager.kt @@ -1,26 +1,58 @@ package su.plo.slib.mod.command import com.mojang.brigadier.CommandDispatcher +import com.mojang.brigadier.builder.LiteralArgumentBuilder +import com.mojang.brigadier.context.CommandContext import net.minecraft.commands.CommandSourceStack import net.minecraft.world.entity.player.Player -import su.plo.slib.api.server.McServerLib import su.plo.slib.api.command.McCommand -import su.plo.slib.api.command.McCommandManager import su.plo.slib.api.command.McCommandSource +import su.plo.slib.api.command.brigadier.McBrigadierContext +import su.plo.slib.api.entity.McEntity +import su.plo.slib.api.server.McServerLib +import su.plo.slib.command.AbstractCommandManager class ModCommandManager( private val minecraftServer: McServerLib -) : McCommandManager() { +) : AbstractCommandManager() { @Synchronized fun registerCommands(dispatcher: CommandDispatcher) { - commandByName.forEach { (name, command) -> + registerCommands { name, command -> val modCommand = ModCommand(minecraftServer, this, command) modCommand.register(dispatcher, name) } + + @Suppress("UNCHECKED_CAST") + registerBrigadierCommands { command -> + dispatcher.register(command as LiteralArgumentBuilder) + } + this.registered = true } + override fun getBrigadierContext(context: CommandContext): McBrigadierContext { + val sourceStack = context.source + require(sourceStack is CommandSourceStack) { "source is not " + CommandSourceStack::class.java } + + val executor = sourceStack.entity?.let { + if (it is Player) { + minecraftServer.getPlayerByInstance(it) + } else { + minecraftServer.getEntityByInstance(it) + } + } + + // todo: sourceStack.source is not accessible +// val source = +// if (sourceStack.source is Player) { +// minecraftServer.getPlayerByInstance(entity) +// } else ModDefaultCommandSource(minecraftServer, sourceStack) + val source = getCommandSource(sourceStack) + + return BrigadierContext(getCommandSource(source), executor) + } + override fun getCommandSource(source: Any): McCommandSource { require(source is CommandSourceStack) { "source is not " + CommandSourceStack::class.java } @@ -30,4 +62,9 @@ class ModCommandManager( minecraftServer.getPlayerByInstance(entity) } else ModDefaultCommandSource(minecraftServer, source) } + + private data class BrigadierContext( + override val source: McCommandSource, + override val executor: McEntity? + ) : McBrigadierContext } diff --git a/modded/src/main/kotlin/su/plo/slib/mod/command/brigadier/ModBrigadierArguments.kt b/modded/src/main/kotlin/su/plo/slib/mod/command/brigadier/ModBrigadierArguments.kt new file mode 100644 index 0000000..942a3ed --- /dev/null +++ b/modded/src/main/kotlin/su/plo/slib/mod/command/brigadier/ModBrigadierArguments.kt @@ -0,0 +1,57 @@ +package su.plo.slib.mod.command.brigadier + +import com.mojang.brigadier.arguments.ArgumentType +import com.mojang.brigadier.context.CommandContext +import net.minecraft.commands.CommandSourceStack +import net.minecraft.commands.arguments.EntityArgument +import su.plo.slib.api.server.command.brigadier.McArgumentResolver +import su.plo.slib.api.server.command.brigadier.McArgumentTypes +import su.plo.slib.api.server.entity.McServerEntity +import su.plo.slib.api.server.entity.player.McServerPlayer +import su.plo.slib.mod.ModServerLib + +@Suppress("UNCHECKED_CAST") +class ModBrigadierArguments : McArgumentTypes.Provider, McArgumentResolver.Provider { + + private val serverLib by lazy { ModServerLib } + + override fun entity(): ArgumentType = EntityArgument.entity() as ArgumentType + + override fun entities(): ArgumentType = EntityArgument.entities() as ArgumentType + + override fun player(): ArgumentType = EntityArgument.player() as ArgumentType + + override fun players(): ArgumentType = EntityArgument.players() as ArgumentType + + override fun getEntity( + context: CommandContext, + name: String, + ): McServerEntity { + val entity = EntityArgument.getEntity(context as CommandContext, name) + return serverLib.getEntityByInstance(entity) + } + + override fun getEntities( + context: CommandContext, + name: String, + ): Collection { + val entities = EntityArgument.getEntities(context as CommandContext, name) + return entities.map { serverLib.getEntityByInstance(it) } + } + + override fun getPlayer( + context: CommandContext, + name: String, + ): McServerPlayer { + val player = EntityArgument.getPlayer(context as CommandContext, name) + return serverLib.getPlayerByInstance(player) + } + + override fun getPlayers( + context: CommandContext, + name: String, + ): Collection { + val players = EntityArgument.getPlayers(context as CommandContext, name) + return players.map { serverLib.getPlayerByInstance(it) } + } +} diff --git a/modded/src/main/resources/META-INF/services/su.plo.slib.api.server.command.brigadier.McArgumentResolver$Provider b/modded/src/main/resources/META-INF/services/su.plo.slib.api.server.command.brigadier.McArgumentResolver$Provider new file mode 100644 index 0000000..0ebbe72 --- /dev/null +++ b/modded/src/main/resources/META-INF/services/su.plo.slib.api.server.command.brigadier.McArgumentResolver$Provider @@ -0,0 +1 @@ +su.plo.slib.mod.command.brigadier.ModBrigadierArguments diff --git a/modded/src/main/resources/META-INF/services/su.plo.slib.api.server.command.brigadier.McArgumentTypes$Provider b/modded/src/main/resources/META-INF/services/su.plo.slib.api.server.command.brigadier.McArgumentTypes$Provider new file mode 100644 index 0000000..0ebbe72 --- /dev/null +++ b/modded/src/main/resources/META-INF/services/su.plo.slib.api.server.command.brigadier.McArgumentTypes$Provider @@ -0,0 +1 @@ +su.plo.slib.mod.command.brigadier.ModBrigadierArguments diff --git a/scripts/smoke-test-proxy.sh b/scripts/smoke-test-proxy.sh new file mode 100755 index 0000000..ea012d9 --- /dev/null +++ b/scripts/smoke-test-proxy.sh @@ -0,0 +1,3 @@ +#!/bin/bash +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +exec "$SCRIPT_DIR/smoke-test.sh" proxy "$@" diff --git a/scripts/smoke-test-server.sh b/scripts/smoke-test-server.sh index 7b34b2b..bb64def 100755 --- a/scripts/smoke-test-server.sh +++ b/scripts/smoke-test-server.sh @@ -1,70 +1,3 @@ #!/bin/bash -set -e - -if [[ -z "$1" ]]; then - echo "Usage: $0 " - echo "Example: $0 modded:1.21-neoforge:runServer -Pmodded.versions_dev=1.21-neoforge" - echo "Example: $0 spigot:runServer -Pspigot.run_minecraft_version=1.16.5 -Pspigot.run_java_version=21" - exit 1 -fi - -COMMAND="$*" -PATTERNS=("Done \(.*\)!" "Command 'ping' registered") -LOGFILE=$(mktemp) -FOUND_DIR=$(mktemp -d) -trap "rm -rf $FOUND_DIR $LOGFILE" EXIT - -TIMEOUT=${CI:+600} -TIMEOUT=${TIMEOUT:-180} -echo "Testing: $COMMAND" - -# Run server in background, write to file -START_TIME=$(date +%s) -timeout $TIMEOUT ./gradlew $COMMAND \ - --console=plain \ - --no-daemon > "$LOGFILE" 2>&1 & -PID=$! - -# Poll the log file -while kill -0 $PID 2>/dev/null; do - for i in "${!PATTERNS[@]}"; do - if [[ ! -f "$FOUND_DIR/$i" ]] && grep -Eq "${PATTERNS[$i]}" "$LOGFILE"; then - touch "$FOUND_DIR/$i" - MATCH=$(grep -Eo "${PATTERNS[$i]}" "$LOGFILE" | head -1) - [[ -z "$CI" ]] && printf "\r\033[K" - echo "Found '${PATTERNS[$i]}' -> $MATCH" - fi - done - - FOUND_COUNT=$(ls "$FOUND_DIR" 2>/dev/null | wc -l) - ELAPSED=$(($(date +%s) - START_TIME)) - - if [[ $FOUND_COUNT -eq ${#PATTERNS[@]} ]]; then - kill $PID 2>/dev/null - - echo "=== Server output ===" - cat "$LOGFILE" - - echo "=== Test result ===" - echo "All ${#PATTERNS[@]} patterns matched in ${ELAPSED}s" - - exit 0 - fi - - [[ -z "$CI" ]] && printf "\rWaiting... %ds/%ds (%d/%d patterns found)" "$ELAPSED" "$TIMEOUT" "$FOUND_COUNT" "${#PATTERNS[@]}" - sleep 1 -done -[[ -z "$CI" ]] && echo - -EXIT_CODE=0 -wait $PID || EXIT_CODE=$? - -echo "=== Server output ===" -cat "$LOGFILE" - -if [[ $EXIT_CODE -eq 124 ]]; then - echo "FAILED: Server startup timed out" -else - echo "FAILED: Server exited (code $EXIT_CODE) before all patterns found" -fi -exit 1 +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +exec "$SCRIPT_DIR/smoke-test.sh" server "$@" diff --git a/scripts/smoke-test.sh b/scripts/smoke-test.sh new file mode 100755 index 0000000..1a3372d --- /dev/null +++ b/scripts/smoke-test.sh @@ -0,0 +1,87 @@ +#!/bin/bash +set -e + +if [[ -z "$2" ]]; then + echo "Usage: $0 " + echo "Example: $0 server modded:1.21-neoforge:runServer -Pmodded.versions_dev=1.21-neoforge" + echo "Example: $0 server spigot:runServer -Pspigot.run_minecraft_version=1.16.5" + echo "Example: $0 proxy velocity:runVelocity" + echo "Example: $0 proxy bungee:runWaterfall" + exit 1 +fi + +ENV_TYPE="$1" +shift +COMMAND="$*" + +case "$ENV_TYPE" in + server) + PATTERNS=("Done \\(.*\\)!" "Command 'ping' registered" "Command 'brigadier-entity-selector' registered") + ;; + proxy) + PATTERNS=("Listening on" "Command 'ping' registered" "Command 'brigadier-ping' registered") + ;; + *) + echo "Error: Invalid environment type '$ENV_TYPE'. Must be 'server' or 'proxy'" + exit 1 + ;; +esac + +LOGFILE=$(mktemp) +FOUND_DIR=$(mktemp -d) +trap "rm -rf $FOUND_DIR $LOGFILE" EXIT + +TIMEOUT=${CI:+600} +TIMEOUT=${TIMEOUT:-180} +echo "Testing [$ENV_TYPE]: $COMMAND" + +# Run in background, write to file +START_TIME=$(date +%s) +timeout $TIMEOUT ./gradlew $COMMAND \ + --console=plain \ + --no-daemon > "$LOGFILE" 2>&1 & +PID=$! + +# Poll the log file +while kill -0 $PID 2>/dev/null; do + for i in "${!PATTERNS[@]}"; do + if [[ ! -f "$FOUND_DIR/$i" ]] && grep -Eq "${PATTERNS[$i]}" "$LOGFILE"; then + touch "$FOUND_DIR/$i" + MATCH=$(grep -Eo "${PATTERNS[$i]}" "$LOGFILE" | head -1) + [[ -z "$CI" ]] && printf "\r\033[K" + echo "Found '${PATTERNS[$i]}' -> $MATCH" + fi + done + + FOUND_COUNT=$(ls "$FOUND_DIR" 2>/dev/null | wc -l) + ELAPSED=$(($(date +%s) - START_TIME)) + + if [[ $FOUND_COUNT -eq ${#PATTERNS[@]} ]]; then + kill $PID 2>/dev/null + + echo "=== $ENV_TYPE output ===" + cat "$LOGFILE" + + echo "=== Test result ===" + echo "All ${#PATTERNS[@]} patterns matched in ${ELAPSED}s" + + exit 0 + fi + + [[ -z "$CI" ]] && printf "\rWaiting... %ds/%ds (%d/%d patterns found)" "$ELAPSED" "$TIMEOUT" "$FOUND_COUNT" "${#PATTERNS[@]}" + sleep 1 +done +[[ -z "$CI" ]] && echo + +EXIT_CODE=0 +wait $PID || EXIT_CODE=$? + +echo "=== $ENV_TYPE output ===" +cat "$LOGFILE" + +if [[ $EXIT_CODE -eq 124 ]]; then + echo "FAILED: Startup timed out" +else + echo "FAILED: Process exited (code $EXIT_CODE) before all patterns found" +fi +exit 1 diff --git a/settings.gradle.kts b/settings.gradle.kts index fa5386d..1171975 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -46,6 +46,7 @@ file("api").listFilesOrdered { include("common") include("common-integration") include("common-server") +include("common-proxy") include("spigot") include("minestom") include("velocity") diff --git a/spigot/build.gradle.kts b/spigot/build.gradle.kts index 0ce285b..41641f3 100644 --- a/spigot/build.gradle.kts +++ b/spigot/build.gradle.kts @@ -3,7 +3,7 @@ import org.semver4j.Semver plugins { id("su.plo.slib.shadow-platform") - alias(libs.plugins.runpaper) + alias(libs.plugins.run.paper) } val testShadowBundle: Configuration by configurations.creating @@ -12,6 +12,12 @@ dependencies { compileOnly(libs.spigot) testCompileOnly(libs.spigot) + compileOnly(libs.semver4j) + shadow(libs.semver4j) + + compileOnly(libs.reflectionremapper) + shadow(libs.reflectionremapper) + testCompileOnly(testFixtures(project(":common-server"))) testShadowBundle(testFixtures(project(":common-server"))) @@ -45,13 +51,17 @@ repositories { tasks { java { - toolchain.languageVersion.set(JavaLanguageVersion.of(8)) + toolchain.languageVersion.set(JavaLanguageVersion.of(17)) } shadowJar { archiveClassifier = "all" relocate("net.kyori", "su.plo.slib.libs.adventure") + relocate("xyz.jpenilla.reflectionremapper", "su.plo.slib.libs.reflectionremapper") + relocate("net.fabricmc.mappingio", "su.plo.slib.libs.mappingio") + relocate("org.jspecify", "su.plo.slib.libs.jspecify") + relocate("org.semver4j", "su.plo.slib.libs.semver4j") } val finalJar = register("finalJar") { @@ -90,9 +100,7 @@ tasks { val javaVersion = when { mcSemVersion.satisfies(">=1.20.5") -> 21 - mcSemVersion.satisfies(">=1.18") -> 17 - mcSemVersion.satisfies(">=1.17") -> 16 - else -> 8 + else -> 17 } minecraftVersion(mcVersion) diff --git a/spigot/src/main/kotlin/su/plo/slib/spigot/SpigotServerLib.kt b/spigot/src/main/kotlin/su/plo/slib/spigot/SpigotServerLib.kt index a841ad2..78ee889 100644 --- a/spigot/src/main/kotlin/su/plo/slib/spigot/SpigotServerLib.kt +++ b/spigot/src/main/kotlin/su/plo/slib/spigot/SpigotServerLib.kt @@ -6,6 +6,7 @@ import net.kyori.adventure.platform.bukkit.BukkitAudiences import org.bukkit.Bukkit import org.bukkit.OfflinePlayer import org.bukkit.World +import org.bukkit.entity.Entity import org.bukkit.entity.LivingEntity import org.bukkit.entity.Player import org.bukkit.event.EventHandler @@ -38,7 +39,8 @@ import su.plo.slib.spigot.scheduler.SpigotServerScheduler import su.plo.slib.spigot.util.SchedulerUtil import su.plo.slib.spigot.world.SpigotServerWorld import java.io.File -import java.util.* +import java.util.Optional +import java.util.UUID class SpigotServerLib( private val loader: JavaPlugin @@ -54,6 +56,7 @@ class SpigotServerLib( .apply { parent = loader.logger.parent } } } + instance = this } private val worldByInstance: MutableMap = Maps.newConcurrentMap() @@ -155,7 +158,11 @@ class SpigotServerLib( McGameProfile(offlinePlayer.uniqueId, offlinePlayer.name ?: "", ImmutableList.of()) override fun getEntityByInstance(instance: Any): McServerEntity { - require(instance is LivingEntity) { "instance is not ${LivingEntity::class.java}" } + require(instance is Entity) { "instance is not ${Entity::class.java}" } + + if (instance is Player) { + return getPlayerByInstance(instance) + } return SpigotServerEntity( this, @@ -186,4 +193,8 @@ class SpigotServerLib( ) playerById.remove(event.player.uniqueId) } + + companion object { + lateinit var instance: SpigotServerLib + } } diff --git a/spigot/src/main/kotlin/su/plo/slib/spigot/command/SpigotCommandManager.kt b/spigot/src/main/kotlin/su/plo/slib/spigot/command/SpigotCommandManager.kt index 20af439..f7c6ab4 100644 --- a/spigot/src/main/kotlin/su/plo/slib/spigot/command/SpigotCommandManager.kt +++ b/spigot/src/main/kotlin/su/plo/slib/spigot/command/SpigotCommandManager.kt @@ -1,20 +1,25 @@ package su.plo.slib.spigot.command +import com.mojang.brigadier.context.CommandContext import org.bukkit.command.Command import org.bukkit.command.CommandSender import org.bukkit.command.SimpleCommandMap import org.bukkit.entity.Player import org.bukkit.plugin.java.JavaPlugin import su.plo.slib.api.command.McCommand -import su.plo.slib.api.command.McCommandManager import su.plo.slib.api.command.McCommandSource +import su.plo.slib.api.command.brigadier.McBrigadierContext +import su.plo.slib.api.entity.McEntity import su.plo.slib.api.logging.McLoggerFactory import su.plo.slib.api.server.event.command.McServerCommandsRegisterEvent +import su.plo.slib.command.AbstractCommandManager import su.plo.slib.spigot.SpigotServerLib +import su.plo.slib.spigot.nms.ReflectionProxies +import su.plo.slib.spigot.nms.getCommandDispatcher class SpigotCommandManager( private val minecraftServer: SpigotServerLib -) : McCommandManager() { +) : AbstractCommandManager() { private val logger = McLoggerFactory.createLogger("SpigotCommandManager") @@ -22,13 +27,22 @@ class SpigotCommandManager( fun registerCommands(loader: JavaPlugin) { McServerCommandsRegisterEvent.invoker.onCommandsRegister(this, minecraftServer) - commandByName.forEach { (name, command) -> + registerCommands { name, command -> val spigotCommand = SpigotCommand(minecraftServer, this, command, name) val commandMap = loader.commandMap() commandMap.register("plasmovoice", spigotCommand) } + try { + val dispatcher = loader.server.getCommandDispatcher() + registerBrigadierCommands { command -> + dispatcher.register(command) + } + } catch (e: Exception) { + logger.warn("Failed to get Brigadier dispatcher: ${e.message}") + } + registered = true } @@ -43,6 +57,18 @@ class SpigotCommandManager( super.clear() } + override fun getBrigadierContext(context: CommandContext): McBrigadierContext { + val sourceStack = context.source as Any + + val source = ReflectionProxies.commandSourceStack.getBukkitSender(sourceStack) + .let { getCommandSource(it) } + val entity = ReflectionProxies.commandSourceStack.getEntity(sourceStack) + ?.let { ReflectionProxies.entity.getBukkitEntity(it) } + ?.let { minecraftServer.getEntityByInstance(it) } + + return BrigadierContext(source, entity) + } + override fun getCommandSource(source: Any): McCommandSource { require(source is CommandSender) { "source is not ${CommandSender::class.java}" } @@ -70,4 +96,9 @@ class SpigotCommandManager( .getDeclaredField("commandMap") .also { it.isAccessible = true } .get(server) as SimpleCommandMap + + private data class BrigadierContext( + override val source: McCommandSource, + override val executor: McEntity?, + ): McBrigadierContext } diff --git a/spigot/src/main/kotlin/su/plo/slib/spigot/command/brigadier/SpigotBrigadierArguments.kt b/spigot/src/main/kotlin/su/plo/slib/spigot/command/brigadier/SpigotBrigadierArguments.kt new file mode 100644 index 0000000..3bfae23 --- /dev/null +++ b/spigot/src/main/kotlin/su/plo/slib/spigot/command/brigadier/SpigotBrigadierArguments.kt @@ -0,0 +1,72 @@ +package su.plo.slib.spigot.command.brigadier + +import com.mojang.brigadier.arguments.ArgumentType +import com.mojang.brigadier.context.CommandContext +import su.plo.slib.api.server.command.brigadier.McArgumentResolver +import su.plo.slib.api.server.command.brigadier.McArgumentTypes +import su.plo.slib.api.server.entity.McServerEntity +import su.plo.slib.api.server.entity.player.McServerPlayer +import su.plo.slib.spigot.SpigotServerLib +import su.plo.slib.spigot.nms.ReflectionProxies +import java.lang.reflect.UndeclaredThrowableException + +class SpigotBrigadierArguments: McArgumentTypes.Provider, McArgumentResolver.Provider { + + private val serverLib by lazy { SpigotServerLib.instance } + + override fun entity(): ArgumentType = ReflectionProxies.entityArgument.entity() + + override fun entities(): ArgumentType = ReflectionProxies.entityArgument.entities() + + override fun player(): ArgumentType = ReflectionProxies.entityArgument.players() + + override fun players(): ArgumentType = ReflectionProxies.entityArgument.players() + + override fun getEntity(context: CommandContext, name: String): McServerEntity { + val entity = rethrowProxyException { + ReflectionProxies.entityArgument.getEntity(context, name) + } + + val bukkitEntity = ReflectionProxies.entity.getBukkitEntity(entity) + return serverLib.getEntityByInstance(bukkitEntity) + } + + override fun getEntities(context: CommandContext, name: String): Collection { + val entities = rethrowProxyException { + ReflectionProxies.entityArgument.getEntities(context, name) + } + + return entities.map { entity -> + val bukkitEntity = ReflectionProxies.entity.getBukkitEntity(entity) + serverLib.getEntityByInstance(bukkitEntity) + } + } + + override fun getPlayer(context: CommandContext, name: String): McServerPlayer { + val player = rethrowProxyException { + ReflectionProxies.entityArgument.getPlayer(context, name) + } + + val bukkitPlayer = ReflectionProxies.entity.getBukkitEntity(player) + return serverLib.getPlayerByInstance(bukkitPlayer) + } + + override fun getPlayers(context: CommandContext, name: String): Collection { + val players = rethrowProxyException { + ReflectionProxies.entityArgument.getPlayers(context, name) + } + + return players.map { player -> + val bukkitPlayer = ReflectionProxies.entity.getBukkitEntity(player) + serverLib.getPlayerByInstance(bukkitPlayer) + } + } + + private fun rethrowProxyException(block: () -> T): T { + try { + return block() + } catch (e: UndeclaredThrowableException) { + throw e.undeclaredThrowable + } + } +} diff --git a/spigot/src/main/kotlin/su/plo/slib/spigot/entity/SpigotServerEntity.kt b/spigot/src/main/kotlin/su/plo/slib/spigot/entity/SpigotServerEntity.kt index 802c770..6946a7e 100644 --- a/spigot/src/main/kotlin/su/plo/slib/spigot/entity/SpigotServerEntity.kt +++ b/spigot/src/main/kotlin/su/plo/slib/spigot/entity/SpigotServerEntity.kt @@ -1,6 +1,7 @@ package su.plo.slib.spigot.entity import org.bukkit.Location +import org.bukkit.entity.Entity import org.bukkit.entity.LivingEntity import su.plo.slib.api.position.Pos3d import su.plo.slib.api.server.entity.McServerEntity @@ -9,7 +10,7 @@ import su.plo.slib.api.server.world.McServerWorld import su.plo.slib.spigot.SpigotServerLib import java.util.* -open class SpigotServerEntity( +open class SpigotServerEntity( protected val minecraftServer: SpigotServerLib, protected val instance: E ) : McServerEntity { @@ -26,7 +27,12 @@ open class SpigotServerEntity( get() = instance.uniqueId override val eyeHeight: Double - get() = instance.eyeHeight + get() = + if (instance is LivingEntity) { + instance.eyeHeight + } else { + 0.0 + } override val hitBoxWidth: Float get() = instance.boundingBox.widthX.toFloat() diff --git a/spigot/src/main/kotlin/su/plo/slib/spigot/nms/CommandSourceStackProxy.kt b/spigot/src/main/kotlin/su/plo/slib/spigot/nms/CommandSourceStackProxy.kt new file mode 100644 index 0000000..71eb149 --- /dev/null +++ b/spigot/src/main/kotlin/su/plo/slib/spigot/nms/CommandSourceStackProxy.kt @@ -0,0 +1,20 @@ +package su.plo.slib.spigot.nms + +import org.bukkit.command.CommandSender +import xyz.jpenilla.reflectionremapper.proxy.annotation.FieldGetter +import xyz.jpenilla.reflectionremapper.proxy.annotation.Proxies +import xyz.jpenilla.reflectionremapper.proxy.annotation.Type + +@Proxies( + className = "net.minecraft.commands.CommandSourceStack" +) +interface CommandSourceStackProxy { + fun getBukkitSender( + @Type(className = "net.minecraft.commands.CommandSourceStack") instance: Any, + ): CommandSender + + @FieldGetter("entity") + fun getEntity( + @Type(className = "net.minecraft.commands.CommandSourceStack") instance: Any, + ): Any? +} diff --git a/spigot/src/main/kotlin/su/plo/slib/spigot/nms/CommandsProxy.kt b/spigot/src/main/kotlin/su/plo/slib/spigot/nms/CommandsProxy.kt new file mode 100644 index 0000000..a19c0c0 --- /dev/null +++ b/spigot/src/main/kotlin/su/plo/slib/spigot/nms/CommandsProxy.kt @@ -0,0 +1,16 @@ +package su.plo.slib.spigot.nms + +import com.mojang.brigadier.CommandDispatcher +import xyz.jpenilla.reflectionremapper.proxy.annotation.FieldGetter +import xyz.jpenilla.reflectionremapper.proxy.annotation.Proxies +import xyz.jpenilla.reflectionremapper.proxy.annotation.Type + +@Proxies( + className = "net.minecraft.commands.Commands" +) +interface CommandsProxy { + @FieldGetter("dispatcher") + fun getDispatcher( + @Type(className = "net.minecraft.commands.Commands") instance: Any, + ): CommandDispatcher +} diff --git a/spigot/src/main/kotlin/su/plo/slib/spigot/nms/EntityArgumentProxy.kt b/spigot/src/main/kotlin/su/plo/slib/spigot/nms/EntityArgumentProxy.kt new file mode 100644 index 0000000..6931979 --- /dev/null +++ b/spigot/src/main/kotlin/su/plo/slib/spigot/nms/EntityArgumentProxy.kt @@ -0,0 +1,35 @@ +package su.plo.slib.spigot.nms + +import com.mojang.brigadier.arguments.ArgumentType +import com.mojang.brigadier.context.CommandContext +import xyz.jpenilla.reflectionremapper.proxy.annotation.Proxies +import xyz.jpenilla.reflectionremapper.proxy.annotation.Static + +@Proxies( + className = "net.minecraft.commands.arguments.EntityArgument" +) +interface EntityArgumentProxy { + @Static + fun entity(): ArgumentType + + @Static + fun entities(): ArgumentType + + @Static + fun player(): ArgumentType + + @Static + fun players(): ArgumentType + + @Static + fun getEntity(context: CommandContext<*>, name: String): Any + + @Static + fun getEntities(context: CommandContext<*>, name: String): Collection + + @Static + fun getPlayer(context: CommandContext<*>, name: String): Any + + @Static + fun getPlayers(context: CommandContext<*>, name: String): Collection +} diff --git a/spigot/src/main/kotlin/su/plo/slib/spigot/nms/EntityProxy.kt b/spigot/src/main/kotlin/su/plo/slib/spigot/nms/EntityProxy.kt new file mode 100644 index 0000000..7144e0b --- /dev/null +++ b/spigot/src/main/kotlin/su/plo/slib/spigot/nms/EntityProxy.kt @@ -0,0 +1,14 @@ +package su.plo.slib.spigot.nms + +import org.bukkit.entity.Entity +import xyz.jpenilla.reflectionremapper.proxy.annotation.Proxies +import xyz.jpenilla.reflectionremapper.proxy.annotation.Type + +@Proxies( + className = "net.minecraft.world.entity.Entity" +) +interface EntityProxy { + fun getBukkitEntity( + @Type(className = "net.minecraft.world.entity.Entity") instance: Any, + ): Entity +} diff --git a/spigot/src/main/kotlin/su/plo/slib/spigot/nms/MinecraftServerProxy.kt b/spigot/src/main/kotlin/su/plo/slib/spigot/nms/MinecraftServerProxy.kt new file mode 100644 index 0000000..0123c56 --- /dev/null +++ b/spigot/src/main/kotlin/su/plo/slib/spigot/nms/MinecraftServerProxy.kt @@ -0,0 +1,13 @@ +package su.plo.slib.spigot.nms + +import xyz.jpenilla.reflectionremapper.proxy.annotation.Proxies +import xyz.jpenilla.reflectionremapper.proxy.annotation.Type + +@Proxies( + className = "net.minecraft.server.MinecraftServer" +) +interface MinecraftServerProxy { + fun getCommands( + @Type(className = "net.minecraft.server.MinecraftServer") instance: Any + ): Any +} diff --git a/spigot/src/main/kotlin/su/plo/slib/spigot/nms/Reflections.kt b/spigot/src/main/kotlin/su/plo/slib/spigot/nms/Reflections.kt new file mode 100644 index 0000000..f1ec86b --- /dev/null +++ b/spigot/src/main/kotlin/su/plo/slib/spigot/nms/Reflections.kt @@ -0,0 +1,69 @@ +package su.plo.slib.spigot.nms + +import com.mojang.brigadier.CommandDispatcher +import org.bukkit.Bukkit +import org.bukkit.Server +import org.semver4j.Semver +import xyz.jpenilla.reflectionremapper.ReflectionRemapper +import xyz.jpenilla.reflectionremapper.proxy.ReflectionProxyFactory + +object ReflectionProxies { + val minecraftServer: MinecraftServerProxy + val commands: CommandsProxy + val commandSourceStack: CommandSourceStackProxy + val entity: EntityProxy + val entityArgument: EntityArgumentProxy + + init { + val bukkitVersion = Bukkit.getVersion() + val minecraftVersionString = bukkitVersion.substring(bukkitVersion.lastIndexOf(" ") + 1, bukkitVersion.length - 1) + val minecraftVersion = Semver(minecraftVersionString) + + val remapper = + try { + ReflectionRemapper.forReobfMappingsInPaperJar() + } catch (_: Throwable) { + val mappingsVersion = listOf( + "1.21.6", + "1.21.4", + "1.20.4", + "1.17.1", + "1.16.5", + ) + .firstOrNull { minecraftVersion.satisfies(">=$it") } + ?: throw IllegalStateException("$minecraftVersionString is not supported") + + ReflectionRemapper.forMappings( + javaClass.classLoader.getResourceAsStream("mappings/$mappingsVersion.tiny")!!, + "source", + "target", + ) + } + + val proxyFactory = ReflectionProxyFactory.create(remapper, javaClass.classLoader) + + minecraftServer = proxyFactory.reflectionProxy() + commands = proxyFactory.reflectionProxy() + commandSourceStack = proxyFactory.reflectionProxy() + entity = proxyFactory.reflectionProxy() + entityArgument = proxyFactory.reflectionProxy() + } + + private inline fun ReflectionProxyFactory.reflectionProxy() = + reflectionProxy(T::class.java) +} + +fun Server.getCommandDispatcher(): CommandDispatcher { + val minecraftServer = getMinecraftServer() + val commands = ReflectionProxies.minecraftServer.getCommands(minecraftServer) + val dispatcher = ReflectionProxies.commands.getDispatcher(commands) + + return dispatcher +} + +fun Server.getMinecraftServer(): Any { + val getServerMethod = javaClass.getMethod("getServer") + val minecraftServer = getServerMethod.invoke(this) + + return minecraftServer +} diff --git a/spigot/src/main/resources/META-INF/services/su.plo.slib.api.server.command.brigadier.McArgumentResolver$Provider b/spigot/src/main/resources/META-INF/services/su.plo.slib.api.server.command.brigadier.McArgumentResolver$Provider new file mode 100644 index 0000000..6ea8415 --- /dev/null +++ b/spigot/src/main/resources/META-INF/services/su.plo.slib.api.server.command.brigadier.McArgumentResolver$Provider @@ -0,0 +1 @@ +su.plo.slib.spigot.command.brigadier.SpigotBrigadierArguments diff --git a/spigot/src/main/resources/META-INF/services/su.plo.slib.api.server.command.brigadier.McArgumentTypes$Provider b/spigot/src/main/resources/META-INF/services/su.plo.slib.api.server.command.brigadier.McArgumentTypes$Provider new file mode 100644 index 0000000..6ea8415 --- /dev/null +++ b/spigot/src/main/resources/META-INF/services/su.plo.slib.api.server.command.brigadier.McArgumentTypes$Provider @@ -0,0 +1 @@ +su.plo.slib.spigot.command.brigadier.SpigotBrigadierArguments diff --git a/spigot/src/main/resources/mappings/1.16.5.tiny b/spigot/src/main/resources/mappings/1.16.5.tiny new file mode 100644 index 0000000..292188b --- /dev/null +++ b/spigot/src/main/resources/mappings/1.16.5.tiny @@ -0,0 +1,17 @@ +tiny 2 0 source target +c net/minecraft/commands/CommandSourceStack net/minecraft/server/v1_16_R3/CommandListenerWrapper + f Lnet/minecraft/world/entity/Entity; entity k +c net/minecraft/commands/Commands net/minecraft/server/v1_16_R3/CommandDispatcher + f Lcom/mojang/brigadier/CommandDispatcher; dispatcher b +c net/minecraft/commands/arguments/EntityArgument net/minecraft/server/v1_16_R3/ArgumentEntity + m ()Lnet/minecraft/commands/arguments/EntityArgument; entity a + m (Lcom/mojang/brigadier/context/CommandContext;Ljava/lang/String;)Lnet/minecraft/world/entity/Entity; getEntity a + m ()Lnet/minecraft/commands/arguments/EntityArgument; entities multipleEntities + m (Lcom/mojang/brigadier/context/CommandContext;Ljava/lang/String;)Ljava/util/Collection; getEntities b + m ()Lnet/minecraft/commands/arguments/EntityArgument; player c + m (Lcom/mojang/brigadier/context/CommandContext;Ljava/lang/String;)Lnet/minecraft/server/level/ServerPlayer; getPlayer e + m ()Lnet/minecraft/commands/arguments/EntityArgument; players d + m (Lcom/mojang/brigadier/context/CommandContext;Ljava/lang/String;)Ljava/util/Collection; getPlayers f +c net/minecraft/server/MinecraftServer net/minecraft/server/v1_16_R3/MinecraftServer + m ()Lnet/minecraft/commands/Commands; getCommands getCommandDispatcher +c net/minecraft/world/entity/Entity net/minecraft/server/v1_16_R3/Entity diff --git a/spigot/src/main/resources/mappings/1.17.1.tiny b/spigot/src/main/resources/mappings/1.17.1.tiny new file mode 100644 index 0000000..fbde9a4 --- /dev/null +++ b/spigot/src/main/resources/mappings/1.17.1.tiny @@ -0,0 +1,17 @@ +tiny 2 0 source target +c net/minecraft/commands/CommandSourceStack net/minecraft/commands/CommandListenerWrapper + f Lnet/minecraft/world/entity/Entity; entity k +c net/minecraft/commands/Commands net/minecraft/commands/CommandDispatcher + f Lcom/mojang/brigadier/CommandDispatcher; dispatcher g +c net/minecraft/commands/arguments/EntityArgument net/minecraft/commands/arguments/ArgumentEntity + m ()Lnet/minecraft/commands/arguments/EntityArgument; entity a + m (Lcom/mojang/brigadier/context/CommandContext;Ljava/lang/String;)Lnet/minecraft/world/entity/Entity; getEntity a + m ()Lnet/minecraft/commands/arguments/EntityArgument; entities multipleEntities + m (Lcom/mojang/brigadier/context/CommandContext;Ljava/lang/String;)Ljava/util/Collection; getEntities b + m ()Lnet/minecraft/commands/arguments/EntityArgument; player c + m (Lcom/mojang/brigadier/context/CommandContext;Ljava/lang/String;)Lnet/minecraft/server/level/ServerPlayer; getPlayer e + m ()Lnet/minecraft/commands/arguments/EntityArgument; players d + m (Lcom/mojang/brigadier/context/CommandContext;Ljava/lang/String;)Ljava/util/Collection; getPlayers f +c net/minecraft/server/MinecraftServer net/minecraft/server/MinecraftServer + m ()Lnet/minecraft/commands/Commands; getCommands getCommandDispatcher +c net/minecraft/world/entity/Entity net/minecraft/world/entity/Entity diff --git a/spigot/src/main/resources/mappings/1.20.4.tiny b/spigot/src/main/resources/mappings/1.20.4.tiny new file mode 100644 index 0000000..aa6b87b --- /dev/null +++ b/spigot/src/main/resources/mappings/1.20.4.tiny @@ -0,0 +1,17 @@ +tiny 2 0 source target +c net/minecraft/commands/CommandSourceStack net/minecraft/commands/CommandListenerWrapper + f Lnet/minecraft/world/entity/Entity; entity k +c net/minecraft/commands/Commands net/minecraft/commands/CommandDispatcher + f Lcom/mojang/brigadier/CommandDispatcher; dispatcher h +c net/minecraft/commands/arguments/EntityArgument net/minecraft/commands/arguments/ArgumentEntity + m ()Lnet/minecraft/commands/arguments/EntityArgument; entity a + m (Lcom/mojang/brigadier/context/CommandContext;Ljava/lang/String;)Lnet/minecraft/world/entity/Entity; getEntity a + m ()Lnet/minecraft/commands/arguments/EntityArgument; entities multipleEntities + m (Lcom/mojang/brigadier/context/CommandContext;Ljava/lang/String;)Ljava/util/Collection; getEntities b + m ()Lnet/minecraft/commands/arguments/EntityArgument; player c + m (Lcom/mojang/brigadier/context/CommandContext;Ljava/lang/String;)Lnet/minecraft/server/level/ServerPlayer; getPlayer e + m ()Lnet/minecraft/commands/arguments/EntityArgument; players d + m (Lcom/mojang/brigadier/context/CommandContext;Ljava/lang/String;)Ljava/util/Collection; getPlayers f +c net/minecraft/server/MinecraftServer net/minecraft/server/MinecraftServer + m ()Lnet/minecraft/commands/Commands; getCommands getCommandDispatcher +c net/minecraft/world/entity/Entity net/minecraft/world/entity/Entity diff --git a/spigot/src/main/resources/mappings/1.21.4.tiny b/spigot/src/main/resources/mappings/1.21.4.tiny new file mode 100644 index 0000000..6aa247d --- /dev/null +++ b/spigot/src/main/resources/mappings/1.21.4.tiny @@ -0,0 +1,17 @@ +tiny 2 0 source target +c net/minecraft/commands/CommandSourceStack net/minecraft/commands/CommandListenerWrapper + f Lnet/minecraft/world/entity/Entity; entity l +c net/minecraft/commands/Commands net/minecraft/commands/CommandDispatcher + f Lcom/mojang/brigadier/CommandDispatcher; dispatcher h +c net/minecraft/commands/arguments/EntityArgument net/minecraft/commands/arguments/ArgumentEntity + m ()Lnet/minecraft/commands/arguments/EntityArgument; entity a + m (Lcom/mojang/brigadier/context/CommandContext;Ljava/lang/String;)Lnet/minecraft/world/entity/Entity; getEntity a + m ()Lnet/minecraft/commands/arguments/EntityArgument; entities multipleEntities + m (Lcom/mojang/brigadier/context/CommandContext;Ljava/lang/String;)Ljava/util/Collection; getEntities b + m ()Lnet/minecraft/commands/arguments/EntityArgument; player c + m (Lcom/mojang/brigadier/context/CommandContext;Ljava/lang/String;)Lnet/minecraft/server/level/ServerPlayer; getPlayer e + m ()Lnet/minecraft/commands/arguments/EntityArgument; players d + m (Lcom/mojang/brigadier/context/CommandContext;Ljava/lang/String;)Ljava/util/Collection; getPlayers f +c net/minecraft/server/MinecraftServer net/minecraft/server/MinecraftServer + m ()Lnet/minecraft/commands/Commands; getCommands getCommandDispatcher +c net/minecraft/world/entity/Entity net/minecraft/world/entity/Entity diff --git a/spigot/src/main/resources/mappings/1.21.6.tiny b/spigot/src/main/resources/mappings/1.21.6.tiny new file mode 100644 index 0000000..aaf1664 --- /dev/null +++ b/spigot/src/main/resources/mappings/1.21.6.tiny @@ -0,0 +1,17 @@ +tiny 2 0 source target +c net/minecraft/commands/CommandSourceStack net/minecraft/commands/CommandListenerWrapper + f Lnet/minecraft/world/entity/Entity; entity l +c net/minecraft/commands/Commands net/minecraft/commands/CommandDispatcher + f Lcom/mojang/brigadier/CommandDispatcher; dispatcher j +c net/minecraft/commands/arguments/EntityArgument net/minecraft/commands/arguments/ArgumentEntity + m ()Lnet/minecraft/commands/arguments/EntityArgument; entity a + m (Lcom/mojang/brigadier/context/CommandContext;Ljava/lang/String;)Lnet/minecraft/world/entity/Entity; getEntity a + m ()Lnet/minecraft/commands/arguments/EntityArgument; entities multipleEntities + m (Lcom/mojang/brigadier/context/CommandContext;Ljava/lang/String;)Ljava/util/Collection; getEntities b + m ()Lnet/minecraft/commands/arguments/EntityArgument; player c + m (Lcom/mojang/brigadier/context/CommandContext;Ljava/lang/String;)Lnet/minecraft/server/level/ServerPlayer; getPlayer e + m ()Lnet/minecraft/commands/arguments/EntityArgument; players d + m (Lcom/mojang/brigadier/context/CommandContext;Ljava/lang/String;)Ljava/util/Collection; getPlayers f +c net/minecraft/server/MinecraftServer net/minecraft/server/MinecraftServer + m ()Lnet/minecraft/commands/Commands; getCommands getCommandDispatcher +c net/minecraft/world/entity/Entity net/minecraft/world/entity/Entity diff --git a/velocity/build.gradle.kts b/velocity/build.gradle.kts index f2a074f..a24d12d 100644 --- a/velocity/build.gradle.kts +++ b/velocity/build.gradle.kts @@ -1,11 +1,22 @@ +import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar + plugins { id("su.plo.slib.shadow-platform") + alias(libs.plugins.run.velocity) + kotlin("kapt") } +val testShadowBundle: Configuration by configurations.creating + dependencies { compileOnly(libs.velocity) testCompileOnly(libs.velocity) + testCompileOnly(testFixtures(project(":common-proxy"))) + testShadowBundle(testFixtures(project(":common-proxy"))) + + kaptTest(libs.velocity) + compileOnly(project(":common")) listOf( project(":api:api-common"), @@ -22,8 +33,27 @@ repositories { maven("https://repo.papermc.io/repository/maven-public/") } +runVelocityExtension { + disablePluginJarDetection() +} + tasks { java { - toolchain.languageVersion.set(JavaLanguageVersion.of(11)) + toolchain.languageVersion.set(JavaLanguageVersion.of(17)) + } + + val testJar = + register("testJar", ShadowJar::class) { + configurations = listOf(testShadowBundle) + + archiveClassifier.set("test") + + from(zipTree(shadowJar.get().archiveFile)) + from(sourceSets.test.get().output) + } + + runVelocity { + velocityVersion("3.4.0-SNAPSHOT") + pluginJars.from(testJar) } } diff --git a/velocity/src/main/kotlin/su/plo/slib/velocity/command/VelocityCommandManager.kt b/velocity/src/main/kotlin/su/plo/slib/velocity/command/VelocityCommandManager.kt index 5331870..1433791 100644 --- a/velocity/src/main/kotlin/su/plo/slib/velocity/command/VelocityCommandManager.kt +++ b/velocity/src/main/kotlin/su/plo/slib/velocity/command/VelocityCommandManager.kt @@ -1,20 +1,25 @@ package su.plo.slib.velocity.command +import com.mojang.brigadier.builder.LiteralArgumentBuilder +import com.mojang.brigadier.context.CommandContext +import com.velocitypowered.api.command.BrigadierCommand import com.velocitypowered.api.command.CommandSource import com.velocitypowered.api.event.Subscribe import com.velocitypowered.api.event.command.CommandExecuteEvent import com.velocitypowered.api.proxy.Player import com.velocitypowered.api.proxy.ProxyServer -import su.plo.slib.api.command.McCommandManager import su.plo.slib.api.command.McCommandSource +import su.plo.slib.api.command.brigadier.McBrigadierContext +import su.plo.slib.api.entity.McEntity import su.plo.slib.api.proxy.McProxyLib import su.plo.slib.api.proxy.command.McProxyCommand import su.plo.slib.api.proxy.event.command.McProxyCommandExecuteEvent +import su.plo.slib.command.AbstractCommandManager import su.plo.slib.velocity.extension.textConverter class VelocityCommandManager( private val minecraftProxy: McProxyLib -) : McCommandManager() { +) : AbstractCommandManager() { @Subscribe fun onCommandExecute(event: CommandExecuteEvent) { @@ -40,6 +45,12 @@ class VelocityCommandManager( } } + override fun getBrigadierContext(context: CommandContext): McBrigadierContext { + val source = context.source as CommandSource + + return BrigadierContext(getCommandSource(source)) + } + override fun getCommandSource(source: Any): McCommandSource { if (source !is CommandSource) throw IllegalArgumentException("source is not " + CommandSource::class.java) @@ -51,11 +62,22 @@ class VelocityCommandManager( @Synchronized fun registerCommands(proxyServer: ProxyServer) { - commandByName.forEach { (name, command) -> - // todo: group commands and use aliases? + registerCommands { name, command -> val velocityCommand = VelocityCommand(minecraftProxy, this, command) proxyServer.commandManager.register(name, velocityCommand) } + + registerBrigadierCommands { command -> + @Suppress("UNCHECKED_CAST") + val brigadierCommand = BrigadierCommand(command as LiteralArgumentBuilder) + proxyServer.commandManager.register(brigadierCommand) + } + registered = true } + + private data class BrigadierContext( + override val source: McCommandSource, + override val executor: McEntity? = null, + ) : McBrigadierContext } diff --git a/velocity/src/test/kotlin/VelocityPlugin.kt b/velocity/src/test/kotlin/VelocityPlugin.kt deleted file mode 100644 index bd55f05..0000000 --- a/velocity/src/test/kotlin/VelocityPlugin.kt +++ /dev/null @@ -1,34 +0,0 @@ -import com.google.inject.Inject -import com.velocitypowered.api.event.Subscribe -import com.velocitypowered.api.event.proxy.ProxyInitializeEvent -import com.velocitypowered.api.plugin.Plugin -import com.velocitypowered.api.proxy.ProxyServer -import su.plo.slib.api.proxy.McProxyLib -import su.plo.slib.api.proxy.event.command.McProxyCommandsRegisterEvent -import su.plo.slib.velocity.VelocityProxyLib - -@Plugin( - id = "test", - name = "Test", - version = "0.0.1", - authors = ["Apehum"] -) -class VelocityPlugin @Inject constructor( - private val proxyServer: ProxyServer -) { - - init { - McProxyCommandsRegisterEvent.registerListener { commandManager, minecraftServer -> - // register commands here - // commandManager.register("pepega", PepegaCommand()) - } - } - - private lateinit var minecraftProxy: McProxyLib - - @Subscribe - fun onProxyInitialization(event: ProxyInitializeEvent) { - // you need to initialize lib here - minecraftProxy = VelocityProxyLib(proxyServer, this) - } -} diff --git a/velocity/src/test/kotlin/su/plo/slib/velocity/TestVelocityPlugin.kt b/velocity/src/test/kotlin/su/plo/slib/velocity/TestVelocityPlugin.kt new file mode 100644 index 0000000..7d0a2e2 --- /dev/null +++ b/velocity/src/test/kotlin/su/plo/slib/velocity/TestVelocityPlugin.kt @@ -0,0 +1,24 @@ +package su.plo.slib.velocity + +import com.google.inject.Inject +import com.velocitypowered.api.event.Subscribe +import com.velocitypowered.api.event.proxy.ProxyInitializeEvent +import com.velocitypowered.api.plugin.Plugin +import com.velocitypowered.api.proxy.ProxyServer +import su.plo.slib.proxy.TestProxy + +@Plugin( + id = "slib-velocity-test", + version = "0.0.1", + authors = ["Apehum"] +) +class TestVelocityPlugin @Inject constructor( + private val proxyServer: ProxyServer +) { + private val testProxy = TestProxy() + + @Subscribe + fun onProxyInitialization(event: ProxyInitializeEvent) { + val minecraftProxy = VelocityProxyLib(proxyServer, this) + } +} From a52f67e00aa3a594275e0f15a74b486b5aa8fdf3 Mon Sep 17 00:00:00 2001 From: Apehum Date: Wed, 24 Dec 2025 03:17:20 +0800 Subject: [PATCH 02/36] fix(spigot): remove reflection proxies for minecraft server and commands; resolve them by method/field list instead --- .../su/plo/slib/spigot/nms/CommandsProxy.kt | 16 ---------- .../slib/spigot/nms/MinecraftServerProxy.kt | 13 -------- .../su/plo/slib/spigot/nms/Reflections.kt | 32 +++++++++++++------ .../src/main/resources/mappings/1.16.5.tiny | 3 -- .../src/main/resources/mappings/1.17.1.tiny | 3 -- .../mappings/{1.20.4.tiny => 1.19.2.tiny} | 5 +-- .../src/main/resources/mappings/1.21.4.tiny | 17 ---------- .../src/main/resources/mappings/1.21.6.tiny | 5 +-- 8 files changed, 25 insertions(+), 69 deletions(-) delete mode 100644 spigot/src/main/kotlin/su/plo/slib/spigot/nms/CommandsProxy.kt delete mode 100644 spigot/src/main/kotlin/su/plo/slib/spigot/nms/MinecraftServerProxy.kt rename spigot/src/main/resources/mappings/{1.20.4.tiny => 1.19.2.tiny} (83%) delete mode 100644 spigot/src/main/resources/mappings/1.21.4.tiny diff --git a/spigot/src/main/kotlin/su/plo/slib/spigot/nms/CommandsProxy.kt b/spigot/src/main/kotlin/su/plo/slib/spigot/nms/CommandsProxy.kt deleted file mode 100644 index a19c0c0..0000000 --- a/spigot/src/main/kotlin/su/plo/slib/spigot/nms/CommandsProxy.kt +++ /dev/null @@ -1,16 +0,0 @@ -package su.plo.slib.spigot.nms - -import com.mojang.brigadier.CommandDispatcher -import xyz.jpenilla.reflectionremapper.proxy.annotation.FieldGetter -import xyz.jpenilla.reflectionremapper.proxy.annotation.Proxies -import xyz.jpenilla.reflectionremapper.proxy.annotation.Type - -@Proxies( - className = "net.minecraft.commands.Commands" -) -interface CommandsProxy { - @FieldGetter("dispatcher") - fun getDispatcher( - @Type(className = "net.minecraft.commands.Commands") instance: Any, - ): CommandDispatcher -} diff --git a/spigot/src/main/kotlin/su/plo/slib/spigot/nms/MinecraftServerProxy.kt b/spigot/src/main/kotlin/su/plo/slib/spigot/nms/MinecraftServerProxy.kt deleted file mode 100644 index 0123c56..0000000 --- a/spigot/src/main/kotlin/su/plo/slib/spigot/nms/MinecraftServerProxy.kt +++ /dev/null @@ -1,13 +0,0 @@ -package su.plo.slib.spigot.nms - -import xyz.jpenilla.reflectionremapper.proxy.annotation.Proxies -import xyz.jpenilla.reflectionremapper.proxy.annotation.Type - -@Proxies( - className = "net.minecraft.server.MinecraftServer" -) -interface MinecraftServerProxy { - fun getCommands( - @Type(className = "net.minecraft.server.MinecraftServer") instance: Any - ): Any -} diff --git a/spigot/src/main/kotlin/su/plo/slib/spigot/nms/Reflections.kt b/spigot/src/main/kotlin/su/plo/slib/spigot/nms/Reflections.kt index f1ec86b..aa994c2 100644 --- a/spigot/src/main/kotlin/su/plo/slib/spigot/nms/Reflections.kt +++ b/spigot/src/main/kotlin/su/plo/slib/spigot/nms/Reflections.kt @@ -4,12 +4,14 @@ import com.mojang.brigadier.CommandDispatcher import org.bukkit.Bukkit import org.bukkit.Server import org.semver4j.Semver +import su.plo.slib.api.logging.McLoggerFactory import xyz.jpenilla.reflectionremapper.ReflectionRemapper import xyz.jpenilla.reflectionremapper.proxy.ReflectionProxyFactory object ReflectionProxies { - val minecraftServer: MinecraftServerProxy - val commands: CommandsProxy + private val logger = McLoggerFactory.createLogger("ReflectionProxies") + + val commandsClass: Class<*> val commandSourceStack: CommandSourceStackProxy val entity: EntityProxy val entityArgument: EntityArgumentProxy @@ -22,11 +24,11 @@ object ReflectionProxies { val remapper = try { ReflectionRemapper.forReobfMappingsInPaperJar() + .also { logger.info("Using mappings from paper jar") } } catch (_: Throwable) { val mappingsVersion = listOf( "1.21.6", - "1.21.4", - "1.20.4", + "1.19.2", "1.17.1", "1.16.5", ) @@ -38,12 +40,14 @@ object ReflectionProxies { "source", "target", ) + .also { + logger.info("Using mappings from resources: \"mappings/$mappingsVersion.tiny\"") + } } val proxyFactory = ReflectionProxyFactory.create(remapper, javaClass.classLoader) - minecraftServer = proxyFactory.reflectionProxy() - commands = proxyFactory.reflectionProxy() + commandsClass = Class.forName(remapper.remapClassName("net.minecraft.commands.Commands")) commandSourceStack = proxyFactory.reflectionProxy() entity = proxyFactory.reflectionProxy() entityArgument = proxyFactory.reflectionProxy() @@ -53,12 +57,22 @@ object ReflectionProxies { reflectionProxy(T::class.java) } +@Suppress("UNCHECKED_CAST") fun Server.getCommandDispatcher(): CommandDispatcher { val minecraftServer = getMinecraftServer() - val commands = ReflectionProxies.minecraftServer.getCommands(minecraftServer) - val dispatcher = ReflectionProxies.commands.getDispatcher(commands) - return dispatcher + val getCommandsMethod = minecraftServer.javaClass.methods + .first { it.returnType == ReflectionProxies.commandsClass } + + val commands = getCommandsMethod.invoke(minecraftServer) + + val dispatcherField = commands.javaClass.declaredFields + .first { it.type == CommandDispatcher::class.java } + dispatcherField.isAccessible = true + + val dispatcher = dispatcherField.get(commands) + + return dispatcher as CommandDispatcher } fun Server.getMinecraftServer(): Any { diff --git a/spigot/src/main/resources/mappings/1.16.5.tiny b/spigot/src/main/resources/mappings/1.16.5.tiny index 292188b..89470f3 100644 --- a/spigot/src/main/resources/mappings/1.16.5.tiny +++ b/spigot/src/main/resources/mappings/1.16.5.tiny @@ -2,7 +2,6 @@ tiny 2 0 source target c net/minecraft/commands/CommandSourceStack net/minecraft/server/v1_16_R3/CommandListenerWrapper f Lnet/minecraft/world/entity/Entity; entity k c net/minecraft/commands/Commands net/minecraft/server/v1_16_R3/CommandDispatcher - f Lcom/mojang/brigadier/CommandDispatcher; dispatcher b c net/minecraft/commands/arguments/EntityArgument net/minecraft/server/v1_16_R3/ArgumentEntity m ()Lnet/minecraft/commands/arguments/EntityArgument; entity a m (Lcom/mojang/brigadier/context/CommandContext;Ljava/lang/String;)Lnet/minecraft/world/entity/Entity; getEntity a @@ -12,6 +11,4 @@ c net/minecraft/commands/arguments/EntityArgument net/minecraft/server/v1_16_R3/ m (Lcom/mojang/brigadier/context/CommandContext;Ljava/lang/String;)Lnet/minecraft/server/level/ServerPlayer; getPlayer e m ()Lnet/minecraft/commands/arguments/EntityArgument; players d m (Lcom/mojang/brigadier/context/CommandContext;Ljava/lang/String;)Ljava/util/Collection; getPlayers f -c net/minecraft/server/MinecraftServer net/minecraft/server/v1_16_R3/MinecraftServer - m ()Lnet/minecraft/commands/Commands; getCommands getCommandDispatcher c net/minecraft/world/entity/Entity net/minecraft/server/v1_16_R3/Entity diff --git a/spigot/src/main/resources/mappings/1.17.1.tiny b/spigot/src/main/resources/mappings/1.17.1.tiny index fbde9a4..9e16229 100644 --- a/spigot/src/main/resources/mappings/1.17.1.tiny +++ b/spigot/src/main/resources/mappings/1.17.1.tiny @@ -2,7 +2,6 @@ tiny 2 0 source target c net/minecraft/commands/CommandSourceStack net/minecraft/commands/CommandListenerWrapper f Lnet/minecraft/world/entity/Entity; entity k c net/minecraft/commands/Commands net/minecraft/commands/CommandDispatcher - f Lcom/mojang/brigadier/CommandDispatcher; dispatcher g c net/minecraft/commands/arguments/EntityArgument net/minecraft/commands/arguments/ArgumentEntity m ()Lnet/minecraft/commands/arguments/EntityArgument; entity a m (Lcom/mojang/brigadier/context/CommandContext;Ljava/lang/String;)Lnet/minecraft/world/entity/Entity; getEntity a @@ -12,6 +11,4 @@ c net/minecraft/commands/arguments/EntityArgument net/minecraft/commands/argumen m (Lcom/mojang/brigadier/context/CommandContext;Ljava/lang/String;)Lnet/minecraft/server/level/ServerPlayer; getPlayer e m ()Lnet/minecraft/commands/arguments/EntityArgument; players d m (Lcom/mojang/brigadier/context/CommandContext;Ljava/lang/String;)Ljava/util/Collection; getPlayers f -c net/minecraft/server/MinecraftServer net/minecraft/server/MinecraftServer - m ()Lnet/minecraft/commands/Commands; getCommands getCommandDispatcher c net/minecraft/world/entity/Entity net/minecraft/world/entity/Entity diff --git a/spigot/src/main/resources/mappings/1.20.4.tiny b/spigot/src/main/resources/mappings/1.19.2.tiny similarity index 83% rename from spigot/src/main/resources/mappings/1.20.4.tiny rename to spigot/src/main/resources/mappings/1.19.2.tiny index aa6b87b..834066c 100644 --- a/spigot/src/main/resources/mappings/1.20.4.tiny +++ b/spigot/src/main/resources/mappings/1.19.2.tiny @@ -2,16 +2,13 @@ tiny 2 0 source target c net/minecraft/commands/CommandSourceStack net/minecraft/commands/CommandListenerWrapper f Lnet/minecraft/world/entity/Entity; entity k c net/minecraft/commands/Commands net/minecraft/commands/CommandDispatcher - f Lcom/mojang/brigadier/CommandDispatcher; dispatcher h c net/minecraft/commands/arguments/EntityArgument net/minecraft/commands/arguments/ArgumentEntity m ()Lnet/minecraft/commands/arguments/EntityArgument; entity a m (Lcom/mojang/brigadier/context/CommandContext;Ljava/lang/String;)Lnet/minecraft/world/entity/Entity; getEntity a - m ()Lnet/minecraft/commands/arguments/EntityArgument; entities multipleEntities + m ()Lnet/minecraft/commands/arguments/EntityArgument; entities b m (Lcom/mojang/brigadier/context/CommandContext;Ljava/lang/String;)Ljava/util/Collection; getEntities b m ()Lnet/minecraft/commands/arguments/EntityArgument; player c m (Lcom/mojang/brigadier/context/CommandContext;Ljava/lang/String;)Lnet/minecraft/server/level/ServerPlayer; getPlayer e m ()Lnet/minecraft/commands/arguments/EntityArgument; players d m (Lcom/mojang/brigadier/context/CommandContext;Ljava/lang/String;)Ljava/util/Collection; getPlayers f -c net/minecraft/server/MinecraftServer net/minecraft/server/MinecraftServer - m ()Lnet/minecraft/commands/Commands; getCommands getCommandDispatcher c net/minecraft/world/entity/Entity net/minecraft/world/entity/Entity diff --git a/spigot/src/main/resources/mappings/1.21.4.tiny b/spigot/src/main/resources/mappings/1.21.4.tiny deleted file mode 100644 index 6aa247d..0000000 --- a/spigot/src/main/resources/mappings/1.21.4.tiny +++ /dev/null @@ -1,17 +0,0 @@ -tiny 2 0 source target -c net/minecraft/commands/CommandSourceStack net/minecraft/commands/CommandListenerWrapper - f Lnet/minecraft/world/entity/Entity; entity l -c net/minecraft/commands/Commands net/minecraft/commands/CommandDispatcher - f Lcom/mojang/brigadier/CommandDispatcher; dispatcher h -c net/minecraft/commands/arguments/EntityArgument net/minecraft/commands/arguments/ArgumentEntity - m ()Lnet/minecraft/commands/arguments/EntityArgument; entity a - m (Lcom/mojang/brigadier/context/CommandContext;Ljava/lang/String;)Lnet/minecraft/world/entity/Entity; getEntity a - m ()Lnet/minecraft/commands/arguments/EntityArgument; entities multipleEntities - m (Lcom/mojang/brigadier/context/CommandContext;Ljava/lang/String;)Ljava/util/Collection; getEntities b - m ()Lnet/minecraft/commands/arguments/EntityArgument; player c - m (Lcom/mojang/brigadier/context/CommandContext;Ljava/lang/String;)Lnet/minecraft/server/level/ServerPlayer; getPlayer e - m ()Lnet/minecraft/commands/arguments/EntityArgument; players d - m (Lcom/mojang/brigadier/context/CommandContext;Ljava/lang/String;)Ljava/util/Collection; getPlayers f -c net/minecraft/server/MinecraftServer net/minecraft/server/MinecraftServer - m ()Lnet/minecraft/commands/Commands; getCommands getCommandDispatcher -c net/minecraft/world/entity/Entity net/minecraft/world/entity/Entity diff --git a/spigot/src/main/resources/mappings/1.21.6.tiny b/spigot/src/main/resources/mappings/1.21.6.tiny index aaf1664..8015938 100644 --- a/spigot/src/main/resources/mappings/1.21.6.tiny +++ b/spigot/src/main/resources/mappings/1.21.6.tiny @@ -2,16 +2,13 @@ tiny 2 0 source target c net/minecraft/commands/CommandSourceStack net/minecraft/commands/CommandListenerWrapper f Lnet/minecraft/world/entity/Entity; entity l c net/minecraft/commands/Commands net/minecraft/commands/CommandDispatcher - f Lcom/mojang/brigadier/CommandDispatcher; dispatcher j c net/minecraft/commands/arguments/EntityArgument net/minecraft/commands/arguments/ArgumentEntity m ()Lnet/minecraft/commands/arguments/EntityArgument; entity a m (Lcom/mojang/brigadier/context/CommandContext;Ljava/lang/String;)Lnet/minecraft/world/entity/Entity; getEntity a - m ()Lnet/minecraft/commands/arguments/EntityArgument; entities multipleEntities + m ()Lnet/minecraft/commands/arguments/EntityArgument; entities b m (Lcom/mojang/brigadier/context/CommandContext;Ljava/lang/String;)Ljava/util/Collection; getEntities b m ()Lnet/minecraft/commands/arguments/EntityArgument; player c m (Lcom/mojang/brigadier/context/CommandContext;Ljava/lang/String;)Lnet/minecraft/server/level/ServerPlayer; getPlayer e m ()Lnet/minecraft/commands/arguments/EntityArgument; players d m (Lcom/mojang/brigadier/context/CommandContext;Ljava/lang/String;)Ljava/util/Collection; getPlayers f -c net/minecraft/server/MinecraftServer net/minecraft/server/MinecraftServer - m ()Lnet/minecraft/commands/Commands; getCommands getCommandDispatcher c net/minecraft/world/entity/Entity net/minecraft/world/entity/Entity From 0ddea5858f7b4aebb1abbbea8dcb20c0cbd856ef Mon Sep 17 00:00:00 2001 From: Apehum Date: Wed, 24 Dec 2025 19:16:30 +0800 Subject: [PATCH 03/36] feat: McBrigadierSource#from using service loaders --- .../plo/slib/api/command/McCommandManager.kt | 10 ----- .../command/brigadier/McBrigadierContext.kt | 16 -------- .../command/brigadier/McBrigadierSource.kt | 36 +++++++++++++++++ .../su/plo/slib/api/service/LazyService.kt | 14 +++++++ .../command/brigadier/McArgumentResolver.kt | 7 +--- .../command/brigadier/McArgumentTypes.kt | 9 +---- .../bungee/command/BungeeCommandManager.kt | 15 +------ ...eeCommand.kt => BungeeBrigadierCommand.kt} | 11 +++-- .../brigadier/BungeeBrigadierSource.kt | 16 ++++++++ ...mmand.brigadier.McBrigadierSource$Provider | 1 + .../kotlin/su/plo/slib/proxy/TestProxy.kt | 3 +- .../kotlin/su/plo/slib/server/TestServer.kt | 17 ++++---- .../command/MinestomCommandManager.kt | 13 +----- .../brigadier/MinestomBrigadierSource.kt | 18 +++++++++ .../brigadier/MinestomEntityArguments.kt | 8 ++-- ...mmand.brigadier.McBrigadierSource$Provider | 1 + .../plo/slib/mod/command/ModCommandManager.kt | 30 -------------- .../command/brigadier/ModBrigadierSource.kt | 40 +++++++++++++++++++ ...mmand.brigadier.McBrigadierSource$Provider | 1 + .../spigot/command/SpigotCommandManager.kt | 21 ---------- .../brigadier/SpigotBrigadierSource.kt | 29 ++++++++++++++ ...mmand.brigadier.McBrigadierSource$Provider | 1 + .../su/plo/slib/velocity/VelocityProxyLib.kt | 5 +++ .../command/VelocityCommandManager.kt | 14 ------- .../brigadier/VelocityBrigadierSource.kt | 23 +++++++++++ ...mmand.brigadier.McBrigadierSource$Provider | 1 + 26 files changed, 214 insertions(+), 146 deletions(-) delete mode 100644 api/common/src/main/kotlin/su/plo/slib/api/command/brigadier/McBrigadierContext.kt create mode 100644 api/common/src/main/kotlin/su/plo/slib/api/command/brigadier/McBrigadierSource.kt create mode 100644 api/common/src/main/kotlin/su/plo/slib/api/service/LazyService.kt rename bungee/src/main/kotlin/su/plo/slib/bungee/command/brigadier/{BrigadierBungeeCommand.kt => BungeeBrigadierCommand.kt} (82%) create mode 100644 bungee/src/main/kotlin/su/plo/slib/bungee/command/brigadier/BungeeBrigadierSource.kt create mode 100644 bungee/src/main/resources/META-INF/services/su.plo.slib.api.command.brigadier.McBrigadierSource$Provider create mode 100644 minestom/src/main/kotlin/su/plo/slib/minestom/command/brigadier/MinestomBrigadierSource.kt create mode 100644 minestom/src/main/resources/META-INF/services/su.plo.slib.api.command.brigadier.McBrigadierSource$Provider create mode 100644 modded/src/main/kotlin/su/plo/slib/mod/command/brigadier/ModBrigadierSource.kt create mode 100644 modded/src/main/resources/META-INF/services/su.plo.slib.api.command.brigadier.McBrigadierSource$Provider create mode 100644 spigot/src/main/kotlin/su/plo/slib/spigot/command/brigadier/SpigotBrigadierSource.kt create mode 100644 spigot/src/main/resources/META-INF/services/su.plo.slib.api.command.brigadier.McBrigadierSource$Provider create mode 100644 velocity/src/main/kotlin/su/plo/slib/velocity/command/brigadier/VelocityBrigadierSource.kt create mode 100644 velocity/src/main/resources/META-INF/services/su.plo.slib.api.command.brigadier.McBrigadierSource$Provider diff --git a/api/common/src/main/kotlin/su/plo/slib/api/command/McCommandManager.kt b/api/common/src/main/kotlin/su/plo/slib/api/command/McCommandManager.kt index 06d65e8..8fa960e 100644 --- a/api/common/src/main/kotlin/su/plo/slib/api/command/McCommandManager.kt +++ b/api/common/src/main/kotlin/su/plo/slib/api/command/McCommandManager.kt @@ -1,8 +1,6 @@ package su.plo.slib.api.command import com.mojang.brigadier.builder.LiteralArgumentBuilder -import com.mojang.brigadier.context.CommandContext -import su.plo.slib.api.command.brigadier.McBrigadierContext /** * Manages universal commands for multiple server implementations. @@ -53,14 +51,6 @@ interface McCommandManager { */ fun clear() - /** - * Gets a brigadier context by server-specific instance. - * - * @param context The server-specific command context instance. - * @return A [McBrigadierContext] instance corresponding to the provided command source instance. - */ - fun getBrigadierContext(context: CommandContext): McBrigadierContext - /** * Gets a command source by server-specific instance. * diff --git a/api/common/src/main/kotlin/su/plo/slib/api/command/brigadier/McBrigadierContext.kt b/api/common/src/main/kotlin/su/plo/slib/api/command/brigadier/McBrigadierContext.kt deleted file mode 100644 index 9c3736e..0000000 --- a/api/common/src/main/kotlin/su/plo/slib/api/command/brigadier/McBrigadierContext.kt +++ /dev/null @@ -1,16 +0,0 @@ -package su.plo.slib.api.command.brigadier - -import su.plo.slib.api.command.McCommandSource -import su.plo.slib.api.entity.McEntity - -interface McBrigadierContext { - /** - * Gets the command source that initiated/triggered the execution of a command. - */ - val source: McCommandSource - - /** - * Gets the entity executing this command. - */ - val executor: McEntity? -} diff --git a/api/common/src/main/kotlin/su/plo/slib/api/command/brigadier/McBrigadierSource.kt b/api/common/src/main/kotlin/su/plo/slib/api/command/brigadier/McBrigadierSource.kt new file mode 100644 index 0000000..1ce27e0 --- /dev/null +++ b/api/common/src/main/kotlin/su/plo/slib/api/command/brigadier/McBrigadierSource.kt @@ -0,0 +1,36 @@ +package su.plo.slib.api.command.brigadier + +import com.mojang.brigadier.context.CommandContext +import su.plo.slib.api.command.McCommandSource +import su.plo.slib.api.entity.McEntity +import su.plo.slib.api.service.lazyService + +interface McBrigadierSource { + /** + * Gets the command source that initiated/triggered the execution of a command. + */ + val source: McCommandSource + + /** + * Gets the entity executing this command. + */ + val executor: McEntity? + + companion object { + private val provider: Provider by lazyService() + + /** + * Gets a brigadier source by server-specific instance. + * + * @param context The server-specific command context instance. + * @return A [McBrigadierSource] instance corresponding to the provided command source instance. + */ + @JvmStatic + fun from(context: CommandContext): McBrigadierSource = + provider.getBrigadierSource(context) + } + + interface Provider { + fun getBrigadierSource(context: CommandContext): McBrigadierSource + } +} diff --git a/api/common/src/main/kotlin/su/plo/slib/api/service/LazyService.kt b/api/common/src/main/kotlin/su/plo/slib/api/service/LazyService.kt new file mode 100644 index 0000000..adb39d8 --- /dev/null +++ b/api/common/src/main/kotlin/su/plo/slib/api/service/LazyService.kt @@ -0,0 +1,14 @@ +package su.plo.slib.api.service + +import java.util.ServiceLoader + +inline fun lazyService(): Lazy = + lazy { + // some loaders can't find service by class's classloader, + // some can't find it by context class loader + // so we're just trying both + ServiceLoader.load(T::class.java).firstOrNull() + ?: ServiceLoader.load(T::class.java, T::class.java.classLoader).firstOrNull() + ?: throw IllegalStateException("${T::class.java} not found is classpath") + } + diff --git a/api/server/src/main/kotlin/su/plo/slib/api/server/command/brigadier/McArgumentResolver.kt b/api/server/src/main/kotlin/su/plo/slib/api/server/command/brigadier/McArgumentResolver.kt index 402b35b..bda0746 100644 --- a/api/server/src/main/kotlin/su/plo/slib/api/server/command/brigadier/McArgumentResolver.kt +++ b/api/server/src/main/kotlin/su/plo/slib/api/server/command/brigadier/McArgumentResolver.kt @@ -3,7 +3,7 @@ package su.plo.slib.api.server.command.brigadier import com.mojang.brigadier.context.CommandContext import su.plo.slib.api.server.entity.McServerEntity import su.plo.slib.api.server.entity.player.McServerPlayer -import java.util.ServiceLoader +import su.plo.slib.api.service.lazyService object McArgumentResolver { @@ -51,10 +51,7 @@ object McArgumentResolver { fun getPlayers(context: CommandContext, name: String): Collection = provider.getPlayers(context, name) - private val provider: Provider = - ServiceLoader.load(Provider::class.java, Provider::class.java.getClassLoader()) - .first() - + private val provider: Provider by lazyService() interface Provider { fun getEntity(context: CommandContext, name: String): McServerEntity diff --git a/api/server/src/main/kotlin/su/plo/slib/api/server/command/brigadier/McArgumentTypes.kt b/api/server/src/main/kotlin/su/plo/slib/api/server/command/brigadier/McArgumentTypes.kt index 76be9dd..9e58cd6 100644 --- a/api/server/src/main/kotlin/su/plo/slib/api/server/command/brigadier/McArgumentTypes.kt +++ b/api/server/src/main/kotlin/su/plo/slib/api/server/command/brigadier/McArgumentTypes.kt @@ -2,7 +2,7 @@ package su.plo.slib.api.server.command.brigadier import com.mojang.brigadier.arguments.ArgumentType import org.jetbrains.annotations.ApiStatus -import java.util.ServiceLoader +import su.plo.slib.api.service.lazyService /** * Vanilla Minecraft argument types. @@ -33,12 +33,7 @@ object McArgumentTypes { @JvmStatic fun players(): ArgumentType = provider.players() - private val provider: Provider = - // some loaders can't find service by class's classloader, - // some can't find it by context class loader - // so we're just trying both - ServiceLoader.load(Provider::class.java).firstOrNull() - ?: ServiceLoader.load(Provider::class.java, Provider::class.java.classLoader).first() + private val provider: Provider by lazyService() @ApiStatus.Internal interface Provider { diff --git a/bungee/src/main/kotlin/su/plo/slib/bungee/command/BungeeCommandManager.kt b/bungee/src/main/kotlin/su/plo/slib/bungee/command/BungeeCommandManager.kt index 920e1f9..1d7a505 100644 --- a/bungee/src/main/kotlin/su/plo/slib/bungee/command/BungeeCommandManager.kt +++ b/bungee/src/main/kotlin/su/plo/slib/bungee/command/BungeeCommandManager.kt @@ -1,6 +1,5 @@ package su.plo.slib.bungee.command -import com.mojang.brigadier.context.CommandContext import net.md_5.bungee.api.CommandSender import net.md_5.bungee.api.ProxyServer import net.md_5.bungee.api.connection.ProxiedPlayer @@ -9,12 +8,10 @@ import net.md_5.bungee.api.plugin.Listener import net.md_5.bungee.api.plugin.Plugin import net.md_5.bungee.event.EventHandler import su.plo.slib.api.command.McCommandSource -import su.plo.slib.api.command.brigadier.McBrigadierContext -import su.plo.slib.api.entity.McEntity import su.plo.slib.api.proxy.command.McProxyCommand import su.plo.slib.api.proxy.event.command.McProxyCommandExecuteEvent import su.plo.slib.bungee.BungeeProxyLib -import su.plo.slib.bungee.command.brigadier.BrigadierBungeeCommand +import su.plo.slib.bungee.command.brigadier.BungeeBrigadierCommand import su.plo.slib.command.AbstractCommandManager class BungeeCommandManager( @@ -37,15 +34,12 @@ class BungeeCommandManager( } registerBrigadierCommands { command -> - proxyServer.pluginManager.registerCommand(plugin, BrigadierBungeeCommand(this, command)) + proxyServer.pluginManager.registerCommand(plugin, BungeeBrigadierCommand(this, command)) } registered = true } - override fun getBrigadierContext(context: CommandContext): McBrigadierContext = - context.source as BrigadierContext - override fun getCommandSource(source: Any): McCommandSource { require(source is CommandSender) { "source is not ${CommandSender::class.java}" } @@ -53,9 +47,4 @@ class BungeeCommandManager( minecraftProxy.getPlayerByInstance(source) } else BungeeDefaultCommandSource(minecraftProxy, source) } - - data class BrigadierContext( - override val source: McCommandSource, - override val executor: McEntity? = null, - ) : McBrigadierContext } diff --git a/bungee/src/main/kotlin/su/plo/slib/bungee/command/brigadier/BrigadierBungeeCommand.kt b/bungee/src/main/kotlin/su/plo/slib/bungee/command/brigadier/BungeeBrigadierCommand.kt similarity index 82% rename from bungee/src/main/kotlin/su/plo/slib/bungee/command/brigadier/BrigadierBungeeCommand.kt rename to bungee/src/main/kotlin/su/plo/slib/bungee/command/brigadier/BungeeBrigadierCommand.kt index b569c73..882bc9e 100644 --- a/bungee/src/main/kotlin/su/plo/slib/bungee/command/brigadier/BrigadierBungeeCommand.kt +++ b/bungee/src/main/kotlin/su/plo/slib/bungee/command/brigadier/BungeeBrigadierCommand.kt @@ -9,21 +9,20 @@ import net.md_5.bungee.api.plugin.TabExecutor import su.plo.slib.api.chat.component.McTextComponent import su.plo.slib.api.chat.style.McTextStyle import su.plo.slib.bungee.command.BungeeCommandManager -import su.plo.slib.bungee.command.BungeeCommandManager.BrigadierContext -class BrigadierBungeeCommand( +class BungeeBrigadierCommand( private val commandManager: BungeeCommandManager, private val command: LiteralArgumentBuilder, ) : Command(command.literal), TabExecutor { - private val dispatcher = CommandDispatcher() + private val dispatcher = CommandDispatcher() init { @Suppress("UNCHECKED_CAST") - dispatcher.register(command as LiteralArgumentBuilder) + dispatcher.register(command as LiteralArgumentBuilder) } override fun execute(sender: CommandSender, arguments: Array) { - val context = BrigadierContext(commandManager.getCommandSource(sender)) + val context = BungeeBrigadierSource(commandManager.getCommandSource(sender)) val input = listOf(command.literal, *arguments).joinToString(" ") try { @@ -45,7 +44,7 @@ class BrigadierBungeeCommand( } override fun onTabComplete(sender: CommandSender, arguments: Array): Iterable { - val context = BrigadierContext(commandManager.getCommandSource(sender)) + val context = BungeeBrigadierSource(commandManager.getCommandSource(sender)) val input = listOf(command.literal, *arguments).joinToString(" ") return dispatcher.getCompletionSuggestions( diff --git a/bungee/src/main/kotlin/su/plo/slib/bungee/command/brigadier/BungeeBrigadierSource.kt b/bungee/src/main/kotlin/su/plo/slib/bungee/command/brigadier/BungeeBrigadierSource.kt new file mode 100644 index 0000000..65769c3 --- /dev/null +++ b/bungee/src/main/kotlin/su/plo/slib/bungee/command/brigadier/BungeeBrigadierSource.kt @@ -0,0 +1,16 @@ +package su.plo.slib.bungee.command.brigadier + +import com.mojang.brigadier.context.CommandContext +import su.plo.slib.api.command.McCommandSource +import su.plo.slib.api.command.brigadier.McBrigadierSource +import su.plo.slib.api.entity.McEntity + +data class BungeeBrigadierSource( + override val source: McCommandSource, + override val executor: McEntity? = null, +) : McBrigadierSource + +class BungeeBrigadierSourceProvider : McBrigadierSource.Provider { + override fun getBrigadierSource(context: CommandContext): McBrigadierSource = + context.source as BungeeBrigadierSource +} diff --git a/bungee/src/main/resources/META-INF/services/su.plo.slib.api.command.brigadier.McBrigadierSource$Provider b/bungee/src/main/resources/META-INF/services/su.plo.slib.api.command.brigadier.McBrigadierSource$Provider new file mode 100644 index 0000000..b7a32b7 --- /dev/null +++ b/bungee/src/main/resources/META-INF/services/su.plo.slib.api.command.brigadier.McBrigadierSource$Provider @@ -0,0 +1 @@ +su.plo.slib.bungee.command.brigadier.BungeeBrigadierSourceProvider diff --git a/common-proxy/src/testFixtures/kotlin/su/plo/slib/proxy/TestProxy.kt b/common-proxy/src/testFixtures/kotlin/su/plo/slib/proxy/TestProxy.kt index 1d15ec1..0057b25 100644 --- a/common-proxy/src/testFixtures/kotlin/su/plo/slib/proxy/TestProxy.kt +++ b/common-proxy/src/testFixtures/kotlin/su/plo/slib/proxy/TestProxy.kt @@ -3,6 +3,7 @@ package su.plo.slib.proxy import com.mojang.brigadier.Command import com.mojang.brigadier.builder.LiteralArgumentBuilder import su.plo.slib.api.command.McCommandSource +import su.plo.slib.api.command.brigadier.McBrigadierSource import su.plo.slib.api.event.player.McPlayerJoinEvent import su.plo.slib.api.event.player.McPlayerQuitEvent import su.plo.slib.api.logging.McLoggerFactory @@ -34,7 +35,7 @@ class TestProxy { commands.register( LiteralArgumentBuilder.literal("brigadier-ping") .executes { - val context = commands.getBrigadierContext(it) + val context = McBrigadierSource.from(it) context.source.sendMessage("Pong") Command.SINGLE_SUCCESS diff --git a/common-server/src/testFixtures/kotlin/su/plo/slib/server/TestServer.kt b/common-server/src/testFixtures/kotlin/su/plo/slib/server/TestServer.kt index 9bdc007..dd101d8 100644 --- a/common-server/src/testFixtures/kotlin/su/plo/slib/server/TestServer.kt +++ b/common-server/src/testFixtures/kotlin/su/plo/slib/server/TestServer.kt @@ -5,6 +5,7 @@ import com.mojang.brigadier.builder.LiteralArgumentBuilder import com.mojang.brigadier.builder.RequiredArgumentBuilder import su.plo.slib.api.command.McCommand import su.plo.slib.api.command.McCommandSource +import su.plo.slib.api.command.brigadier.McBrigadierSource import su.plo.slib.api.event.player.McPlayerJoinEvent import su.plo.slib.api.event.player.McPlayerQuitEvent import su.plo.slib.api.logging.McLoggerFactory @@ -56,8 +57,8 @@ class TestServer( .executes { val entity = McArgumentResolver.getEntity(it, "target") - val context = commands.getBrigadierContext(it) - context.source.sendMessage("Found entity: $entity; Source: ${context.source}; Executor: ${context.executor}") + val source = McBrigadierSource.from(it) + source.source.sendMessage("Found entity: $entity; Source: ${source.source}; Executor: ${source.executor}") Command.SINGLE_SUCCESS } @@ -70,8 +71,8 @@ class TestServer( .executes { val entities = McArgumentResolver.getEntities(it, "target") - val context = commands.getBrigadierContext(it) - context.source.sendMessage("Found entities: $entities; Source: ${context.source}; Executor: ${context.executor}") + val source = McBrigadierSource.from(it) + source.source.sendMessage("Found entities: $entities; Source: ${source.source}; Executor: ${source.executor}") Command.SINGLE_SUCCESS } @@ -84,8 +85,8 @@ class TestServer( .executes { val player = McArgumentResolver.getPlayer(it, "target") - val context = commands.getBrigadierContext(it) - context.source.sendMessage("Found player: $player; Source: ${context.source}; Executor: ${context.executor}") + val source = McBrigadierSource.from(it) + source.source.sendMessage("Found player: $player; Source: ${source.source}; Executor: ${source.executor}") Command.SINGLE_SUCCESS } @@ -98,8 +99,8 @@ class TestServer( .executes { val players = McArgumentResolver.getPlayers(it, "target") - val context = commands.getBrigadierContext(it) - context.source.sendMessage("Found players: $players; Source: ${context.source}; Executor: ${context.executor}") + val source = McBrigadierSource.from(it) + source.source.sendMessage("Found players: $players; Source: ${source.source}; Executor: ${source.executor}") Command.SINGLE_SUCCESS } diff --git a/minestom/src/main/kotlin/su/plo/slib/minestom/command/MinestomCommandManager.kt b/minestom/src/main/kotlin/su/plo/slib/minestom/command/MinestomCommandManager.kt index 84dce22..1f38417 100644 --- a/minestom/src/main/kotlin/su/plo/slib/minestom/command/MinestomCommandManager.kt +++ b/minestom/src/main/kotlin/su/plo/slib/minestom/command/MinestomCommandManager.kt @@ -29,12 +29,12 @@ import su.plo.slib.api.chat.component.McTextComponent import su.plo.slib.api.chat.style.McTextStyle import su.plo.slib.api.command.McCommand import su.plo.slib.api.command.McCommandSource -import su.plo.slib.api.command.brigadier.McBrigadierContext import su.plo.slib.api.entity.McEntity import su.plo.slib.api.server.McServerLib import su.plo.slib.api.server.event.command.McServerCommandsRegisterEvent import su.plo.slib.command.AbstractCommandManager import su.plo.slib.minestom.command.brigadier.MinestomArgumentType +import su.plo.slib.minestom.command.brigadier.MinestomBrigadierSource class MinestomCommandManager( private val minecraftServer: McServerLib @@ -66,9 +66,6 @@ class MinestomCommandManager( registered = true } - override fun getBrigadierContext(context: CommandContext): McBrigadierContext = - context.source as McBrigadierContext - override fun getCommandSource(source: Any): McCommandSource { require(source is CommandSender) { "source is not ${CommandSender::class.java}" } @@ -136,7 +133,7 @@ class MinestomCommandManager( @Suppress("UNCHECKED_CAST") val brigadierContext = CommandContext( - BrigadierContext(sender, source, source as? McEntity), + MinestomBrigadierSource(sender, source, source as? McEntity), context.input, context.map.mapValues { ParsedArgument(0, 0, it.value) }, this@toMinestom as com.mojang.brigadier.Command, @@ -166,10 +163,4 @@ class MinestomCommandManager( } } } - - data class BrigadierContext( - val sender: CommandSender, - override val source: McCommandSource, - override val executor: McEntity? - ) : McBrigadierContext } diff --git a/minestom/src/main/kotlin/su/plo/slib/minestom/command/brigadier/MinestomBrigadierSource.kt b/minestom/src/main/kotlin/su/plo/slib/minestom/command/brigadier/MinestomBrigadierSource.kt new file mode 100644 index 0000000..458bc0f --- /dev/null +++ b/minestom/src/main/kotlin/su/plo/slib/minestom/command/brigadier/MinestomBrigadierSource.kt @@ -0,0 +1,18 @@ +package su.plo.slib.minestom.command.brigadier + +import com.mojang.brigadier.context.CommandContext +import net.minestom.server.command.CommandSender +import su.plo.slib.api.command.McCommandSource +import su.plo.slib.api.command.brigadier.McBrigadierSource +import su.plo.slib.api.entity.McEntity + +data class MinestomBrigadierSource( + val sender: CommandSender, + override val source: McCommandSource, + override val executor: McEntity? +) : McBrigadierSource + +class MinestomBrigadierSourceProvider : McBrigadierSource.Provider { + override fun getBrigadierSource(context: CommandContext): McBrigadierSource = + context.source as MinestomBrigadierSource +} diff --git a/minestom/src/main/kotlin/su/plo/slib/minestom/command/brigadier/MinestomEntityArguments.kt b/minestom/src/main/kotlin/su/plo/slib/minestom/command/brigadier/MinestomEntityArguments.kt index 141a2e4..7b24411 100644 --- a/minestom/src/main/kotlin/su/plo/slib/minestom/command/brigadier/MinestomEntityArguments.kt +++ b/minestom/src/main/kotlin/su/plo/slib/minestom/command/brigadier/MinestomEntityArguments.kt @@ -44,7 +44,7 @@ class MinestomEntityArguments : McArgumentTypes.Provider, McArgumentResolver.Pro override fun getEntity(context: CommandContext, name: String): McServerEntity { val finder = context.getArgument(name, EntityFinder::class.java) - val brigadierContext = context.source as MinestomCommandManager.BrigadierContext + val brigadierContext = context.source as MinestomBrigadierSource val entity = finder.findFirstEntity(brigadierContext.sender) ?: throw IllegalArgumentException("No entity found") @@ -55,14 +55,14 @@ class MinestomEntityArguments : McArgumentTypes.Provider, McArgumentResolver.Pro override fun getEntities(context: CommandContext, name: String): Collection { val finder = context.getArgument(name, EntityFinder::class.java) - val brigadierContext = context.source as MinestomCommandManager.BrigadierContext + val brigadierContext = context.source as MinestomBrigadierSource return finder.find(brigadierContext.sender).map { serverLib.getEntityByInstance(it) } } override fun getPlayer(context: CommandContext, name: String): McServerPlayer { val finder = context.getArgument(name, EntityFinder::class.java) - val brigadierContext = context.source as MinestomCommandManager.BrigadierContext + val brigadierContext = context.source as MinestomBrigadierSource val player = finder.findFirstPlayer(brigadierContext.sender) ?: throw IllegalArgumentException("No player found") @@ -72,7 +72,7 @@ class MinestomEntityArguments : McArgumentTypes.Provider, McArgumentResolver.Pro override fun getPlayers(context: CommandContext, name: String): Collection { val finder = context.getArgument(name, EntityFinder::class.java) - val brigadierContext = context.source as MinestomCommandManager.BrigadierContext + val brigadierContext = context.source as MinestomBrigadierSource return finder.find(brigadierContext.sender) .filterIsInstance() diff --git a/minestom/src/main/resources/META-INF/services/su.plo.slib.api.command.brigadier.McBrigadierSource$Provider b/minestom/src/main/resources/META-INF/services/su.plo.slib.api.command.brigadier.McBrigadierSource$Provider new file mode 100644 index 0000000..76252ae --- /dev/null +++ b/minestom/src/main/resources/META-INF/services/su.plo.slib.api.command.brigadier.McBrigadierSource$Provider @@ -0,0 +1 @@ +su.plo.slib.minestom.command.brigadier.MinestomBrigadierSourceProvider diff --git a/modded/src/main/kotlin/su/plo/slib/mod/command/ModCommandManager.kt b/modded/src/main/kotlin/su/plo/slib/mod/command/ModCommandManager.kt index 07182c8..9f7162f 100644 --- a/modded/src/main/kotlin/su/plo/slib/mod/command/ModCommandManager.kt +++ b/modded/src/main/kotlin/su/plo/slib/mod/command/ModCommandManager.kt @@ -2,13 +2,10 @@ package su.plo.slib.mod.command import com.mojang.brigadier.CommandDispatcher import com.mojang.brigadier.builder.LiteralArgumentBuilder -import com.mojang.brigadier.context.CommandContext import net.minecraft.commands.CommandSourceStack import net.minecraft.world.entity.player.Player import su.plo.slib.api.command.McCommand import su.plo.slib.api.command.McCommandSource -import su.plo.slib.api.command.brigadier.McBrigadierContext -import su.plo.slib.api.entity.McEntity import su.plo.slib.api.server.McServerLib import su.plo.slib.command.AbstractCommandManager @@ -31,28 +28,6 @@ class ModCommandManager( this.registered = true } - override fun getBrigadierContext(context: CommandContext): McBrigadierContext { - val sourceStack = context.source - require(sourceStack is CommandSourceStack) { "source is not " + CommandSourceStack::class.java } - - val executor = sourceStack.entity?.let { - if (it is Player) { - minecraftServer.getPlayerByInstance(it) - } else { - minecraftServer.getEntityByInstance(it) - } - } - - // todo: sourceStack.source is not accessible -// val source = -// if (sourceStack.source is Player) { -// minecraftServer.getPlayerByInstance(entity) -// } else ModDefaultCommandSource(minecraftServer, sourceStack) - val source = getCommandSource(sourceStack) - - return BrigadierContext(getCommandSource(source), executor) - } - override fun getCommandSource(source: Any): McCommandSource { require(source is CommandSourceStack) { "source is not " + CommandSourceStack::class.java } @@ -62,9 +37,4 @@ class ModCommandManager( minecraftServer.getPlayerByInstance(entity) } else ModDefaultCommandSource(minecraftServer, source) } - - private data class BrigadierContext( - override val source: McCommandSource, - override val executor: McEntity? - ) : McBrigadierContext } diff --git a/modded/src/main/kotlin/su/plo/slib/mod/command/brigadier/ModBrigadierSource.kt b/modded/src/main/kotlin/su/plo/slib/mod/command/brigadier/ModBrigadierSource.kt new file mode 100644 index 0000000..2b6f4c3 --- /dev/null +++ b/modded/src/main/kotlin/su/plo/slib/mod/command/brigadier/ModBrigadierSource.kt @@ -0,0 +1,40 @@ +package su.plo.slib.mod.command.brigadier + +import com.mojang.brigadier.context.CommandContext +import net.minecraft.commands.CommandSourceStack +import net.minecraft.world.entity.player.Player +import su.plo.slib.api.command.McCommandSource +import su.plo.slib.api.command.brigadier.McBrigadierSource +import su.plo.slib.api.entity.McEntity +import su.plo.slib.mod.ModServerLib + +data class ModBrigadierSource( + override val source: McCommandSource, + override val executor: McEntity? +) : McBrigadierSource + +class ModBrigadierSourceProvider : McBrigadierSource.Provider { + private val minecraftServer by lazy { ModServerLib } + + override fun getBrigadierSource(context: CommandContext): McBrigadierSource { + val sourceStack = context.source + require(sourceStack is CommandSourceStack) { "source is not " + CommandSourceStack::class.java } + + val executor = sourceStack.entity?.let { + if (it is Player) { + minecraftServer.getPlayerByInstance(it) + } else { + minecraftServer.getEntityByInstance(it) + } + } + + // todo: sourceStack.source is not accessible +// val source = +// if (sourceStack.source is Player) { +// minecraftServer.getPlayerByInstance(entity) +// } else ModDefaultCommandSource(minecraftServer, sourceStack) + val source = minecraftServer.commandManager.getCommandSource(sourceStack) + + return ModBrigadierSource(minecraftServer.commandManager.getCommandSource(source), executor) + } +} diff --git a/modded/src/main/resources/META-INF/services/su.plo.slib.api.command.brigadier.McBrigadierSource$Provider b/modded/src/main/resources/META-INF/services/su.plo.slib.api.command.brigadier.McBrigadierSource$Provider new file mode 100644 index 0000000..b8cc0ef --- /dev/null +++ b/modded/src/main/resources/META-INF/services/su.plo.slib.api.command.brigadier.McBrigadierSource$Provider @@ -0,0 +1 @@ +su.plo.slib.mod.command.brigadier.ModBrigadierSourceProvider diff --git a/spigot/src/main/kotlin/su/plo/slib/spigot/command/SpigotCommandManager.kt b/spigot/src/main/kotlin/su/plo/slib/spigot/command/SpigotCommandManager.kt index f7c6ab4..2d7e63c 100644 --- a/spigot/src/main/kotlin/su/plo/slib/spigot/command/SpigotCommandManager.kt +++ b/spigot/src/main/kotlin/su/plo/slib/spigot/command/SpigotCommandManager.kt @@ -1,6 +1,5 @@ package su.plo.slib.spigot.command -import com.mojang.brigadier.context.CommandContext import org.bukkit.command.Command import org.bukkit.command.CommandSender import org.bukkit.command.SimpleCommandMap @@ -8,13 +7,10 @@ import org.bukkit.entity.Player import org.bukkit.plugin.java.JavaPlugin import su.plo.slib.api.command.McCommand import su.plo.slib.api.command.McCommandSource -import su.plo.slib.api.command.brigadier.McBrigadierContext -import su.plo.slib.api.entity.McEntity import su.plo.slib.api.logging.McLoggerFactory import su.plo.slib.api.server.event.command.McServerCommandsRegisterEvent import su.plo.slib.command.AbstractCommandManager import su.plo.slib.spigot.SpigotServerLib -import su.plo.slib.spigot.nms.ReflectionProxies import su.plo.slib.spigot.nms.getCommandDispatcher class SpigotCommandManager( @@ -57,18 +53,6 @@ class SpigotCommandManager( super.clear() } - override fun getBrigadierContext(context: CommandContext): McBrigadierContext { - val sourceStack = context.source as Any - - val source = ReflectionProxies.commandSourceStack.getBukkitSender(sourceStack) - .let { getCommandSource(it) } - val entity = ReflectionProxies.commandSourceStack.getEntity(sourceStack) - ?.let { ReflectionProxies.entity.getBukkitEntity(it) } - ?.let { minecraftServer.getEntityByInstance(it) } - - return BrigadierContext(source, entity) - } - override fun getCommandSource(source: Any): McCommandSource { require(source is CommandSender) { "source is not ${CommandSender::class.java}" } @@ -96,9 +80,4 @@ class SpigotCommandManager( .getDeclaredField("commandMap") .also { it.isAccessible = true } .get(server) as SimpleCommandMap - - private data class BrigadierContext( - override val source: McCommandSource, - override val executor: McEntity?, - ): McBrigadierContext } diff --git a/spigot/src/main/kotlin/su/plo/slib/spigot/command/brigadier/SpigotBrigadierSource.kt b/spigot/src/main/kotlin/su/plo/slib/spigot/command/brigadier/SpigotBrigadierSource.kt new file mode 100644 index 0000000..dfd3ea2 --- /dev/null +++ b/spigot/src/main/kotlin/su/plo/slib/spigot/command/brigadier/SpigotBrigadierSource.kt @@ -0,0 +1,29 @@ +package su.plo.slib.spigot.command.brigadier + +import com.mojang.brigadier.context.CommandContext +import su.plo.slib.api.command.McCommandSource +import su.plo.slib.api.command.brigadier.McBrigadierSource +import su.plo.slib.api.entity.McEntity +import su.plo.slib.spigot.SpigotServerLib +import su.plo.slib.spigot.nms.ReflectionProxies + +data class SpigotBrigadierSource( + override val source: McCommandSource, + override val executor: McEntity?, +): McBrigadierSource + +class SpigotBrigadierSourceProvider : McBrigadierSource.Provider { + private val minecraftServer by lazy { SpigotServerLib.instance } + + override fun getBrigadierSource(context: CommandContext): McBrigadierSource { + val sourceStack = context.source as Any + + val source = ReflectionProxies.commandSourceStack.getBukkitSender(sourceStack) + .let { minecraftServer.commandManager.getCommandSource(it) } + val entity = ReflectionProxies.commandSourceStack.getEntity(sourceStack) + ?.let { ReflectionProxies.entity.getBukkitEntity(it) } + ?.let { minecraftServer.getEntityByInstance(it) } + + return SpigotBrigadierSource(source, entity) + } +} diff --git a/spigot/src/main/resources/META-INF/services/su.plo.slib.api.command.brigadier.McBrigadierSource$Provider b/spigot/src/main/resources/META-INF/services/su.plo.slib.api.command.brigadier.McBrigadierSource$Provider new file mode 100644 index 0000000..f8bd081 --- /dev/null +++ b/spigot/src/main/resources/META-INF/services/su.plo.slib.api.command.brigadier.McBrigadierSource$Provider @@ -0,0 +1 @@ +su.plo.slib.spigot.command.brigadier.SpigotBrigadierSourceProvider diff --git a/velocity/src/main/kotlin/su/plo/slib/velocity/VelocityProxyLib.kt b/velocity/src/main/kotlin/su/plo/slib/velocity/VelocityProxyLib.kt index 8f567dd..11cca08 100644 --- a/velocity/src/main/kotlin/su/plo/slib/velocity/VelocityProxyLib.kt +++ b/velocity/src/main/kotlin/su/plo/slib/velocity/VelocityProxyLib.kt @@ -36,6 +36,7 @@ class VelocityProxyLib( init { McLoggerFactory.supplier = McLoggerFactory.Supplier { name -> Slf4jLogger(name) } + instance = this } private val playerById: MutableMap = Maps.newConcurrentMap() @@ -155,4 +156,8 @@ class VelocityProxyLib( getServerInfoByServerInstance(it) } } + + companion object { + lateinit var instance: VelocityProxyLib + } } diff --git a/velocity/src/main/kotlin/su/plo/slib/velocity/command/VelocityCommandManager.kt b/velocity/src/main/kotlin/su/plo/slib/velocity/command/VelocityCommandManager.kt index 1433791..532c4b4 100644 --- a/velocity/src/main/kotlin/su/plo/slib/velocity/command/VelocityCommandManager.kt +++ b/velocity/src/main/kotlin/su/plo/slib/velocity/command/VelocityCommandManager.kt @@ -1,7 +1,6 @@ package su.plo.slib.velocity.command import com.mojang.brigadier.builder.LiteralArgumentBuilder -import com.mojang.brigadier.context.CommandContext import com.velocitypowered.api.command.BrigadierCommand import com.velocitypowered.api.command.CommandSource import com.velocitypowered.api.event.Subscribe @@ -9,8 +8,6 @@ import com.velocitypowered.api.event.command.CommandExecuteEvent import com.velocitypowered.api.proxy.Player import com.velocitypowered.api.proxy.ProxyServer import su.plo.slib.api.command.McCommandSource -import su.plo.slib.api.command.brigadier.McBrigadierContext -import su.plo.slib.api.entity.McEntity import su.plo.slib.api.proxy.McProxyLib import su.plo.slib.api.proxy.command.McProxyCommand import su.plo.slib.api.proxy.event.command.McProxyCommandExecuteEvent @@ -45,12 +42,6 @@ class VelocityCommandManager( } } - override fun getBrigadierContext(context: CommandContext): McBrigadierContext { - val source = context.source as CommandSource - - return BrigadierContext(getCommandSource(source)) - } - override fun getCommandSource(source: Any): McCommandSource { if (source !is CommandSource) throw IllegalArgumentException("source is not " + CommandSource::class.java) @@ -75,9 +66,4 @@ class VelocityCommandManager( registered = true } - - private data class BrigadierContext( - override val source: McCommandSource, - override val executor: McEntity? = null, - ) : McBrigadierContext } diff --git a/velocity/src/main/kotlin/su/plo/slib/velocity/command/brigadier/VelocityBrigadierSource.kt b/velocity/src/main/kotlin/su/plo/slib/velocity/command/brigadier/VelocityBrigadierSource.kt new file mode 100644 index 0000000..e947bec --- /dev/null +++ b/velocity/src/main/kotlin/su/plo/slib/velocity/command/brigadier/VelocityBrigadierSource.kt @@ -0,0 +1,23 @@ +package su.plo.slib.velocity.command.brigadier + +import com.mojang.brigadier.context.CommandContext +import com.velocitypowered.api.command.CommandSource +import su.plo.slib.api.command.McCommandSource +import su.plo.slib.api.command.brigadier.McBrigadierSource +import su.plo.slib.api.entity.McEntity +import su.plo.slib.velocity.VelocityProxyLib + +data class VelocityBrigadierSource( + override val source: McCommandSource, + override val executor: McEntity? = null, +) : McBrigadierSource + +class VelocityBrigadierSourceProvider : McBrigadierSource.Provider { + private val minecraftProxy by lazy { VelocityProxyLib.instance } + + override fun getBrigadierSource(context: CommandContext): McBrigadierSource { + val source = context.source as CommandSource + + return VelocityBrigadierSource(minecraftProxy.commandManager.getCommandSource(source)) + } +} diff --git a/velocity/src/main/resources/META-INF/services/su.plo.slib.api.command.brigadier.McBrigadierSource$Provider b/velocity/src/main/resources/META-INF/services/su.plo.slib.api.command.brigadier.McBrigadierSource$Provider new file mode 100644 index 0000000..9fff795 --- /dev/null +++ b/velocity/src/main/resources/META-INF/services/su.plo.slib.api.command.brigadier.McBrigadierSource$Provider @@ -0,0 +1 @@ +su.plo.slib.velocity.command.brigadier.VelocityBrigadierSourceProvider From fef36efa8e2653697e344191e4c12b3cf35291e9 Mon Sep 17 00:00:00 2001 From: Apehum Date: Wed, 24 Dec 2025 19:27:29 +0800 Subject: [PATCH 04/36] fix(mod): use command source from CommandSourceStack as command source --- .../accessor/CommandSourceStackAccessor.java | 13 +++++++++++++ .../su/plo/slib/mod/command/ModCommandManager.kt | 14 ++++++++------ .../slib/mod/command/ModDefaultCommandSource.kt | 4 ++-- .../mod/command/brigadier/ModBrigadierSource.kt | 16 ++-------------- .../main/resources/slib-no-refmap.mixins.json | 3 ++- modded/src/main/resources/slib.mixins.json | 3 ++- 6 files changed, 29 insertions(+), 24 deletions(-) create mode 100644 modded/src/main/java/su/plo/slib/mod/mixin/accessor/CommandSourceStackAccessor.java diff --git a/modded/src/main/java/su/plo/slib/mod/mixin/accessor/CommandSourceStackAccessor.java b/modded/src/main/java/su/plo/slib/mod/mixin/accessor/CommandSourceStackAccessor.java new file mode 100644 index 0000000..37da0f9 --- /dev/null +++ b/modded/src/main/java/su/plo/slib/mod/mixin/accessor/CommandSourceStackAccessor.java @@ -0,0 +1,13 @@ +package su.plo.slib.mod.mixin.accessor; + +import net.minecraft.commands.CommandSource; +import net.minecraft.commands.CommandSourceStack; +import org.jetbrains.annotations.NotNull; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(CommandSourceStack.class) +public interface CommandSourceStackAccessor { + @Accessor("source") + @NotNull CommandSource slib_getSource(); +} diff --git a/modded/src/main/kotlin/su/plo/slib/mod/command/ModCommandManager.kt b/modded/src/main/kotlin/su/plo/slib/mod/command/ModCommandManager.kt index 9f7162f..c1bbdc5 100644 --- a/modded/src/main/kotlin/su/plo/slib/mod/command/ModCommandManager.kt +++ b/modded/src/main/kotlin/su/plo/slib/mod/command/ModCommandManager.kt @@ -8,6 +8,7 @@ import su.plo.slib.api.command.McCommand import su.plo.slib.api.command.McCommandSource import su.plo.slib.api.server.McServerLib import su.plo.slib.command.AbstractCommandManager +import su.plo.slib.mod.mixin.accessor.CommandSourceStackAccessor class ModCommandManager( private val minecraftServer: McServerLib @@ -28,13 +29,14 @@ class ModCommandManager( this.registered = true } - override fun getCommandSource(source: Any): McCommandSource { - require(source is CommandSourceStack) { "source is not " + CommandSourceStack::class.java } + override fun getCommandSource(sourceStack: Any): McCommandSource { + require(sourceStack is CommandSourceStack) { "source is not " + CommandSourceStack::class.java } + require(sourceStack is CommandSourceStackAccessor) { "source is not " + CommandSourceStack::class.java } - val entity = source.entity + val source = sourceStack.slib_getSource() - return if (entity is Player) { - minecraftServer.getPlayerByInstance(entity) - } else ModDefaultCommandSource(minecraftServer, source) + return if (source is Player) { + minecraftServer.getPlayerByInstance(source) + } else ModDefaultCommandSource(minecraftServer, sourceStack) } } diff --git a/modded/src/main/kotlin/su/plo/slib/mod/command/ModDefaultCommandSource.kt b/modded/src/main/kotlin/su/plo/slib/mod/command/ModDefaultCommandSource.kt index c71a0b2..6a94f4a 100644 --- a/modded/src/main/kotlin/su/plo/slib/mod/command/ModDefaultCommandSource.kt +++ b/modded/src/main/kotlin/su/plo/slib/mod/command/ModDefaultCommandSource.kt @@ -1,15 +1,15 @@ package su.plo.slib.mod.command import net.minecraft.commands.CommandSourceStack -import su.plo.slib.api.server.McServerLib import su.plo.slib.api.chat.component.McTextComponent import su.plo.slib.api.command.McCommandSource import su.plo.slib.api.permission.PermissionTristate +import su.plo.slib.api.server.McServerLib import su.plo.slib.mod.chat.ComponentTextConverter class ModDefaultCommandSource( private val minecraftServer: McServerLib, - private val source: CommandSourceStack + private val source: CommandSourceStack, ) : McCommandSource { override val language: String diff --git a/modded/src/main/kotlin/su/plo/slib/mod/command/brigadier/ModBrigadierSource.kt b/modded/src/main/kotlin/su/plo/slib/mod/command/brigadier/ModBrigadierSource.kt index 2b6f4c3..1b99ff3 100644 --- a/modded/src/main/kotlin/su/plo/slib/mod/command/brigadier/ModBrigadierSource.kt +++ b/modded/src/main/kotlin/su/plo/slib/mod/command/brigadier/ModBrigadierSource.kt @@ -2,7 +2,6 @@ package su.plo.slib.mod.command.brigadier import com.mojang.brigadier.context.CommandContext import net.minecraft.commands.CommandSourceStack -import net.minecraft.world.entity.player.Player import su.plo.slib.api.command.McCommandSource import su.plo.slib.api.command.brigadier.McBrigadierSource import su.plo.slib.api.entity.McEntity @@ -20,21 +19,10 @@ class ModBrigadierSourceProvider : McBrigadierSource.Provider { val sourceStack = context.source require(sourceStack is CommandSourceStack) { "source is not " + CommandSourceStack::class.java } - val executor = sourceStack.entity?.let { - if (it is Player) { - minecraftServer.getPlayerByInstance(it) - } else { - minecraftServer.getEntityByInstance(it) - } - } + val executor = sourceStack.entity?.let { minecraftServer.getEntityByInstance(it) } - // todo: sourceStack.source is not accessible -// val source = -// if (sourceStack.source is Player) { -// minecraftServer.getPlayerByInstance(entity) -// } else ModDefaultCommandSource(minecraftServer, sourceStack) val source = minecraftServer.commandManager.getCommandSource(sourceStack) - return ModBrigadierSource(minecraftServer.commandManager.getCommandSource(source), executor) + return ModBrigadierSource(source, executor) } } diff --git a/modded/src/main/resources/slib-no-refmap.mixins.json b/modded/src/main/resources/slib-no-refmap.mixins.json index 3213a8f..270ec9b 100644 --- a/modded/src/main/resources/slib-no-refmap.mixins.json +++ b/modded/src/main/resources/slib-no-refmap.mixins.json @@ -4,7 +4,8 @@ "package": "su.plo.slib.mod.mixin", "mixins": [ "MixinPlayerList", - "MixinServerPlayer" + "MixinServerPlayer", + "accessor.CommandSourceStackAccessor" ], "client": [ ], diff --git a/modded/src/main/resources/slib.mixins.json b/modded/src/main/resources/slib.mixins.json index 59bcd46..9047782 100644 --- a/modded/src/main/resources/slib.mixins.json +++ b/modded/src/main/resources/slib.mixins.json @@ -4,7 +4,8 @@ "package": "su.plo.slib.mod.mixin", "mixins": [ "MixinPlayerList", - "MixinServerPlayer" + "MixinServerPlayer", + "accessor.CommandSourceStackAccessor" ], "client": [ ], From 6a15cf4273563730501cb5f7a3d9f2220e61cd44 Mon Sep 17 00:00:00 2001 From: Apehum Date: Wed, 24 Dec 2025 19:28:56 +0800 Subject: [PATCH 05/36] fix(server): return getPlayerByInstance if entity is player in getEntityByInstance --- .../main/kotlin/su/plo/slib/minestom/MinestomServerLib.kt | 7 ++++++- .../su/plo/slib/minestom/entity/MinestomServerEntity.kt | 8 ++++---- modded/src/main/kotlin/su/plo/slib/mod/ModServerLib.kt | 4 ++++ 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/minestom/src/main/kotlin/su/plo/slib/minestom/MinestomServerLib.kt b/minestom/src/main/kotlin/su/plo/slib/minestom/MinestomServerLib.kt index 68d7e49..2324eed 100644 --- a/minestom/src/main/kotlin/su/plo/slib/minestom/MinestomServerLib.kt +++ b/minestom/src/main/kotlin/su/plo/slib/minestom/MinestomServerLib.kt @@ -2,6 +2,7 @@ package su.plo.slib.minestom import com.google.common.collect.Maps import net.minestom.server.MinecraftServer +import net.minestom.server.entity.Entity import net.minestom.server.entity.LivingEntity import net.minestom.server.entity.Player import net.minestom.server.event.instance.InstanceUnregisterEvent @@ -138,7 +139,11 @@ class MinestomServerLib( } override fun getEntityByInstance(instance: Any): McServerEntity { - require(instance is LivingEntity) { "instance is not ${LivingEntity::class.java}" } + require(instance is Entity) { "instance is not ${LivingEntity::class.java}" } + + if (instance is Player) { + return getPlayerByInstance(instance) + } return MinestomServerEntity( this, diff --git a/minestom/src/main/kotlin/su/plo/slib/minestom/entity/MinestomServerEntity.kt b/minestom/src/main/kotlin/su/plo/slib/minestom/entity/MinestomServerEntity.kt index 56e57cf..2535556 100644 --- a/minestom/src/main/kotlin/su/plo/slib/minestom/entity/MinestomServerEntity.kt +++ b/minestom/src/main/kotlin/su/plo/slib/minestom/entity/MinestomServerEntity.kt @@ -1,14 +1,14 @@ package su.plo.slib.minestom.entity -import net.minestom.server.entity.LivingEntity +import net.minestom.server.entity.Entity +import su.plo.slib.api.position.Pos3d import su.plo.slib.api.server.McServerLib import su.plo.slib.api.server.entity.McServerEntity -import su.plo.slib.api.position.Pos3d import su.plo.slib.api.server.position.ServerPos3d import su.plo.slib.api.server.world.McServerWorld -import java.util.* +import java.util.UUID -open class MinestomServerEntity( +open class MinestomServerEntity( protected val minecraftServer: McServerLib, protected val instance: E ) : McServerEntity { diff --git a/modded/src/main/kotlin/su/plo/slib/mod/ModServerLib.kt b/modded/src/main/kotlin/su/plo/slib/mod/ModServerLib.kt index 37c3e68..b2a7524 100644 --- a/modded/src/main/kotlin/su/plo/slib/mod/ModServerLib.kt +++ b/modded/src/main/kotlin/su/plo/slib/mod/ModServerLib.kt @@ -114,6 +114,10 @@ object ModServerLib : McServerLib { override fun getEntityByInstance(instance: Any): McServerEntity { require(instance is Entity) { "instance is not " + Entity::class.java } + if (instance is ServerPlayer) { + return getPlayerByInstance(instance) + } + return ModServerEntity( this, instance From 386eefafe80098ff16b74b75a4a3a4a3f3f6ebd7 Mon Sep 17 00:00:00 2001 From: Apehum Date: Wed, 24 Dec 2025 20:02:19 +0800 Subject: [PATCH 06/36] fix: convert McCommandManager back to interface for backwards-compat --- .../su/plo/slib/api/command/McCommandManager.kt | 14 +++++++------- .../su/plo/slib/command/AbstractCommandManager.kt | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/api/common/src/main/kotlin/su/plo/slib/api/command/McCommandManager.kt b/api/common/src/main/kotlin/su/plo/slib/api/command/McCommandManager.kt index 8fa960e..9da9a5f 100644 --- a/api/common/src/main/kotlin/su/plo/slib/api/command/McCommandManager.kt +++ b/api/common/src/main/kotlin/su/plo/slib/api/command/McCommandManager.kt @@ -10,21 +10,21 @@ import com.mojang.brigadier.builder.LiteralArgumentBuilder * * @param T The type of commands managed by this manager. */ -interface McCommandManager { +abstract class McCommandManager { /** * Retrieves a read-only map of registered commands. * * @return A map containing the registered commands with their names as keys. */ - val registeredCommands: Map + abstract val registeredCommands: Map /** * Retrieves a read-only map of registered brigadier commands. * * @return A list containing the registered commands with their names as keys. */ - val registeredBrigadierCommands: List> + abstract val registeredBrigadierCommands: List> /** * Registers a brigadier command. @@ -33,7 +33,7 @@ interface McCommandManager { * @throws IllegalStateException If attempting to register commands after commands have already been registered. * @throws IllegalArgumentException If a command with the same name or alias already exists. */ - fun register(command: LiteralArgumentBuilder) + abstract fun register(command: LiteralArgumentBuilder) /** * Registers a command with its name and optional aliases. @@ -44,12 +44,12 @@ interface McCommandManager { * @throws IllegalStateException If attempting to register commands after commands have already been registered. * @throws IllegalArgumentException If a command with the same name or alias already exists. */ - fun register(name: String, command: T, vararg aliases: String) + abstract fun register(name: String, command: T, vararg aliases: String) /** * Clears all registered commands and resets the registration state. */ - fun clear() + abstract fun clear() /** * Gets a command source by server-specific instance. @@ -61,5 +61,5 @@ interface McCommandManager { * @param source The server-specific command source instance. * @return A [McCommandSource] instance corresponding to the provided command source instance. */ - fun getCommandSource(source: Any): McCommandSource + abstract fun getCommandSource(source: Any): McCommandSource } diff --git a/common/src/main/kotlin/su/plo/slib/command/AbstractCommandManager.kt b/common/src/main/kotlin/su/plo/slib/command/AbstractCommandManager.kt index b16ed72..a8b0fbe 100644 --- a/common/src/main/kotlin/su/plo/slib/command/AbstractCommandManager.kt +++ b/common/src/main/kotlin/su/plo/slib/command/AbstractCommandManager.kt @@ -8,7 +8,7 @@ import su.plo.slib.api.command.McCommand import su.plo.slib.api.command.McCommandManager import su.plo.slib.api.logging.McLoggerFactory -abstract class AbstractCommandManager : McCommandManager { +abstract class AbstractCommandManager : McCommandManager() { private val logger = McLoggerFactory.createLogger("CommandManager") protected val commandByName: MutableMap = Maps.newHashMap() From bcc09b4648ca426de997316f4afa6809f3b45f10 Mon Sep 17 00:00:00 2001 From: Apehum Date: Wed, 24 Dec 2025 20:23:41 +0800 Subject: [PATCH 07/36] feat: add method to resolve directly from source instance in McBrigadierSource --- .../api/command/brigadier/McBrigadierSource.kt | 14 ++++++++++++-- .../command/brigadier/BungeeBrigadierSource.kt | 5 ++--- .../command/brigadier/MinestomBrigadierSource.kt | 5 ++--- .../mod/command/brigadier/ModBrigadierSource.kt | 4 +--- .../command/brigadier/SpigotBrigadierSource.kt | 5 ++--- .../command/brigadier/VelocityBrigadierSource.kt | 5 ++--- 6 files changed, 21 insertions(+), 17 deletions(-) diff --git a/api/common/src/main/kotlin/su/plo/slib/api/command/brigadier/McBrigadierSource.kt b/api/common/src/main/kotlin/su/plo/slib/api/command/brigadier/McBrigadierSource.kt index 1ce27e0..cfa88cd 100644 --- a/api/common/src/main/kotlin/su/plo/slib/api/command/brigadier/McBrigadierSource.kt +++ b/api/common/src/main/kotlin/su/plo/slib/api/command/brigadier/McBrigadierSource.kt @@ -23,14 +23,24 @@ interface McBrigadierSource { * Gets a brigadier source by server-specific instance. * * @param context The server-specific command context instance. - * @return A [McBrigadierSource] instance corresponding to the provided command source instance. + * @return A [McBrigadierSource] instance corresponding to the provided brigadier source instance. */ @JvmStatic fun from(context: CommandContext): McBrigadierSource = + from(context.source) + + /** + * Gets a brigadier source by server-specific instance. + * + * @param context The server-specific command context instance. + * @return A [McBrigadierSource] instance corresponding to the provided brigadier source instance. + */ + @JvmStatic + fun from(context: S): McBrigadierSource = provider.getBrigadierSource(context) } interface Provider { - fun getBrigadierSource(context: CommandContext): McBrigadierSource + fun getBrigadierSource(source: S): McBrigadierSource } } diff --git a/bungee/src/main/kotlin/su/plo/slib/bungee/command/brigadier/BungeeBrigadierSource.kt b/bungee/src/main/kotlin/su/plo/slib/bungee/command/brigadier/BungeeBrigadierSource.kt index 65769c3..774f620 100644 --- a/bungee/src/main/kotlin/su/plo/slib/bungee/command/brigadier/BungeeBrigadierSource.kt +++ b/bungee/src/main/kotlin/su/plo/slib/bungee/command/brigadier/BungeeBrigadierSource.kt @@ -1,6 +1,5 @@ package su.plo.slib.bungee.command.brigadier -import com.mojang.brigadier.context.CommandContext import su.plo.slib.api.command.McCommandSource import su.plo.slib.api.command.brigadier.McBrigadierSource import su.plo.slib.api.entity.McEntity @@ -11,6 +10,6 @@ data class BungeeBrigadierSource( ) : McBrigadierSource class BungeeBrigadierSourceProvider : McBrigadierSource.Provider { - override fun getBrigadierSource(context: CommandContext): McBrigadierSource = - context.source as BungeeBrigadierSource + override fun getBrigadierSource(source: S): McBrigadierSource = + source as BungeeBrigadierSource } diff --git a/minestom/src/main/kotlin/su/plo/slib/minestom/command/brigadier/MinestomBrigadierSource.kt b/minestom/src/main/kotlin/su/plo/slib/minestom/command/brigadier/MinestomBrigadierSource.kt index 458bc0f..1988dfa 100644 --- a/minestom/src/main/kotlin/su/plo/slib/minestom/command/brigadier/MinestomBrigadierSource.kt +++ b/minestom/src/main/kotlin/su/plo/slib/minestom/command/brigadier/MinestomBrigadierSource.kt @@ -1,6 +1,5 @@ package su.plo.slib.minestom.command.brigadier -import com.mojang.brigadier.context.CommandContext import net.minestom.server.command.CommandSender import su.plo.slib.api.command.McCommandSource import su.plo.slib.api.command.brigadier.McBrigadierSource @@ -13,6 +12,6 @@ data class MinestomBrigadierSource( ) : McBrigadierSource class MinestomBrigadierSourceProvider : McBrigadierSource.Provider { - override fun getBrigadierSource(context: CommandContext): McBrigadierSource = - context.source as MinestomBrigadierSource + override fun getBrigadierSource(source: S): McBrigadierSource = + source as MinestomBrigadierSource } diff --git a/modded/src/main/kotlin/su/plo/slib/mod/command/brigadier/ModBrigadierSource.kt b/modded/src/main/kotlin/su/plo/slib/mod/command/brigadier/ModBrigadierSource.kt index 1b99ff3..524a013 100644 --- a/modded/src/main/kotlin/su/plo/slib/mod/command/brigadier/ModBrigadierSource.kt +++ b/modded/src/main/kotlin/su/plo/slib/mod/command/brigadier/ModBrigadierSource.kt @@ -1,6 +1,5 @@ package su.plo.slib.mod.command.brigadier -import com.mojang.brigadier.context.CommandContext import net.minecraft.commands.CommandSourceStack import su.plo.slib.api.command.McCommandSource import su.plo.slib.api.command.brigadier.McBrigadierSource @@ -15,8 +14,7 @@ data class ModBrigadierSource( class ModBrigadierSourceProvider : McBrigadierSource.Provider { private val minecraftServer by lazy { ModServerLib } - override fun getBrigadierSource(context: CommandContext): McBrigadierSource { - val sourceStack = context.source + override fun getBrigadierSource(sourceStack: S): McBrigadierSource { require(sourceStack is CommandSourceStack) { "source is not " + CommandSourceStack::class.java } val executor = sourceStack.entity?.let { minecraftServer.getEntityByInstance(it) } diff --git a/spigot/src/main/kotlin/su/plo/slib/spigot/command/brigadier/SpigotBrigadierSource.kt b/spigot/src/main/kotlin/su/plo/slib/spigot/command/brigadier/SpigotBrigadierSource.kt index dfd3ea2..983883e 100644 --- a/spigot/src/main/kotlin/su/plo/slib/spigot/command/brigadier/SpigotBrigadierSource.kt +++ b/spigot/src/main/kotlin/su/plo/slib/spigot/command/brigadier/SpigotBrigadierSource.kt @@ -1,6 +1,5 @@ package su.plo.slib.spigot.command.brigadier -import com.mojang.brigadier.context.CommandContext import su.plo.slib.api.command.McCommandSource import su.plo.slib.api.command.brigadier.McBrigadierSource import su.plo.slib.api.entity.McEntity @@ -15,8 +14,8 @@ data class SpigotBrigadierSource( class SpigotBrigadierSourceProvider : McBrigadierSource.Provider { private val minecraftServer by lazy { SpigotServerLib.instance } - override fun getBrigadierSource(context: CommandContext): McBrigadierSource { - val sourceStack = context.source as Any + override fun getBrigadierSource(sourceStack: S): McBrigadierSource { + val sourceStack = sourceStack as Any val source = ReflectionProxies.commandSourceStack.getBukkitSender(sourceStack) .let { minecraftServer.commandManager.getCommandSource(it) } diff --git a/velocity/src/main/kotlin/su/plo/slib/velocity/command/brigadier/VelocityBrigadierSource.kt b/velocity/src/main/kotlin/su/plo/slib/velocity/command/brigadier/VelocityBrigadierSource.kt index e947bec..8f79631 100644 --- a/velocity/src/main/kotlin/su/plo/slib/velocity/command/brigadier/VelocityBrigadierSource.kt +++ b/velocity/src/main/kotlin/su/plo/slib/velocity/command/brigadier/VelocityBrigadierSource.kt @@ -1,6 +1,5 @@ package su.plo.slib.velocity.command.brigadier -import com.mojang.brigadier.context.CommandContext import com.velocitypowered.api.command.CommandSource import su.plo.slib.api.command.McCommandSource import su.plo.slib.api.command.brigadier.McBrigadierSource @@ -15,8 +14,8 @@ data class VelocityBrigadierSource( class VelocityBrigadierSourceProvider : McBrigadierSource.Provider { private val minecraftProxy by lazy { VelocityProxyLib.instance } - override fun getBrigadierSource(context: CommandContext): McBrigadierSource { - val source = context.source as CommandSource + override fun getBrigadierSource(source: S): McBrigadierSource { + require(source is CommandSource) return VelocityBrigadierSource(minecraftProxy.commandManager.getCommandSource(source)) } From 580153a142f1c0bf353f402265b50258acfbb1c7 Mon Sep 17 00:00:00 2001 From: Apehum Date: Sat, 27 Dec 2025 01:08:32 +0800 Subject: [PATCH 08/36] refactor: use McBrigadierSource as brigadier builder source --- .../plo/slib/api/command/McCommandManager.kt | 15 +++- .../command/brigadier/McBrigadierSource.kt | 42 +++------- .../command/brigadier/McArgumentResolver.kt | 17 ++-- .../brigadier/BungeeBrigadierCommand.kt | 12 +-- .../brigadier/BungeeBrigadierSource.kt | 11 +-- ...mmand.brigadier.McBrigadierSource$Provider | 1 - .../kotlin/su/plo/slib/proxy/TestProxy.kt | 7 +- .../kotlin/su/plo/slib/server/TestServer.kt | 30 ++++--- .../slib/command/AbstractCommandManager.kt | 82 ++++++++++++++++++- gradle/libs.versions.toml | 3 + .../command/MinestomCommandManager.kt | 2 +- .../brigadier/MinestomBrigadierSource.kt | 13 ++- .../brigadier/MinestomEntityArguments.kt | 18 ++-- ...mmand.brigadier.McBrigadierSource$Provider | 1 - modded/build.gradle.kts | 31 ++----- .../ServerPlayerCommandSourceAccessor.java | 14 ++++ .../plo/slib/mod/command/ModCommandManager.kt | 35 ++++++-- .../brigadier/ModBrigadierArguments.kt | 27 +++--- .../command/brigadier/ModBrigadierSource.kt | 22 +++-- ...mmand.brigadier.McBrigadierSource$Provider | 1 - .../src/main/resources/slib-forge.mixins.json | 14 ---- .../main/resources/slib-no-refmap.mixins.json | 15 ---- modded/src/main/resources/slib.mixins.json | 16 ---- modded/src/main/resources/slib.mixins.json5 | 24 ++++++ .../resources/META-INF/neoforge.mods.toml | 2 +- settings.gradle.kts | 2 + .../spigot/command/SpigotCommandManager.kt | 19 ++++- .../brigadier/SpigotBrigadierArguments.kt | 18 ++-- .../brigadier/SpigotBrigadierSource.kt | 26 +++--- ...mmand.brigadier.McBrigadierSource$Provider | 1 - .../command/VelocityCommandManager.kt | 17 +++- .../brigadier/VelocityBrigadierSource.kt | 16 ++-- ...mmand.brigadier.McBrigadierSource$Provider | 1 - 33 files changed, 337 insertions(+), 218 deletions(-) delete mode 100644 bungee/src/main/resources/META-INF/services/su.plo.slib.api.command.brigadier.McBrigadierSource$Provider delete mode 100644 minestom/src/main/resources/META-INF/services/su.plo.slib.api.command.brigadier.McBrigadierSource$Provider create mode 100644 modded/src/main/java/su/plo/slib/mod/mixin/accessor/ServerPlayerCommandSourceAccessor.java delete mode 100644 modded/src/main/resources/META-INF/services/su.plo.slib.api.command.brigadier.McBrigadierSource$Provider delete mode 100644 modded/src/main/resources/slib-forge.mixins.json delete mode 100644 modded/src/main/resources/slib-no-refmap.mixins.json delete mode 100644 modded/src/main/resources/slib.mixins.json create mode 100644 modded/src/main/resources/slib.mixins.json5 delete mode 100644 spigot/src/main/resources/META-INF/services/su.plo.slib.api.command.brigadier.McBrigadierSource$Provider delete mode 100644 velocity/src/main/resources/META-INF/services/su.plo.slib.api.command.brigadier.McBrigadierSource$Provider diff --git a/api/common/src/main/kotlin/su/plo/slib/api/command/McCommandManager.kt b/api/common/src/main/kotlin/su/plo/slib/api/command/McCommandManager.kt index 9da9a5f..5846e14 100644 --- a/api/common/src/main/kotlin/su/plo/slib/api/command/McCommandManager.kt +++ b/api/common/src/main/kotlin/su/plo/slib/api/command/McCommandManager.kt @@ -1,6 +1,9 @@ package su.plo.slib.api.command +import com.mojang.brigadier.arguments.ArgumentType import com.mojang.brigadier.builder.LiteralArgumentBuilder +import com.mojang.brigadier.builder.RequiredArgumentBuilder +import su.plo.slib.api.command.brigadier.McBrigadierSource /** * Manages universal commands for multiple server implementations. @@ -33,7 +36,7 @@ abstract class McCommandManager { * @throws IllegalStateException If attempting to register commands after commands have already been registered. * @throws IllegalArgumentException If a command with the same name or alias already exists. */ - abstract fun register(command: LiteralArgumentBuilder) + abstract fun register(command: LiteralArgumentBuilder) /** * Registers a command with its name and optional aliases. @@ -62,4 +65,14 @@ abstract class McCommandManager { * @return A [McCommandSource] instance corresponding to the provided command source instance. */ abstract fun getCommandSource(source: Any): McCommandSource + + companion object { + @JvmStatic + fun literal(name: String): LiteralArgumentBuilder = + LiteralArgumentBuilder.literal(name) + + @JvmStatic + fun argument(name: String, argument: ArgumentType): RequiredArgumentBuilder = + RequiredArgumentBuilder.argument(name, argument) + } } diff --git a/api/common/src/main/kotlin/su/plo/slib/api/command/brigadier/McBrigadierSource.kt b/api/common/src/main/kotlin/su/plo/slib/api/command/brigadier/McBrigadierSource.kt index cfa88cd..3b2dad9 100644 --- a/api/common/src/main/kotlin/su/plo/slib/api/command/brigadier/McBrigadierSource.kt +++ b/api/common/src/main/kotlin/su/plo/slib/api/command/brigadier/McBrigadierSource.kt @@ -1,9 +1,7 @@ package su.plo.slib.api.command.brigadier -import com.mojang.brigadier.context.CommandContext import su.plo.slib.api.command.McCommandSource import su.plo.slib.api.entity.McEntity -import su.plo.slib.api.service.lazyService interface McBrigadierSource { /** @@ -16,31 +14,17 @@ interface McBrigadierSource { */ val executor: McEntity? - companion object { - private val provider: Provider by lazyService() - - /** - * Gets a brigadier source by server-specific instance. - * - * @param context The server-specific command context instance. - * @return A [McBrigadierSource] instance corresponding to the provided brigadier source instance. - */ - @JvmStatic - fun from(context: CommandContext): McBrigadierSource = - from(context.source) - - /** - * Gets a brigadier source by server-specific instance. - * - * @param context The server-specific command context instance. - * @return A [McBrigadierSource] instance corresponding to the provided brigadier source instance. - */ - @JvmStatic - fun from(context: S): McBrigadierSource = - provider.getBrigadierSource(context) - } - - interface Provider { - fun getBrigadierSource(source: S): McBrigadierSource - } + /** + * Gets the server's implementation instance for this source. + * + * The return type may vary depending on the server platform: + * - For servers (Paper/Fabric/Forge/NeoForge): [net.minecraft.commands.CommandSourceStack] + * - For Minestom: [net.minestom.server.command.CommandSender] + * - For BungeeCord: [net.md_5.bungee.api.CommandSender] + * - For Velocity: [com.velocitypowered.api.command.CommandSource] + * + * @return The server's implementation object associated with this source. + * @param T The expected type of the server's implementation instance. + */ + fun getInstance(): T } diff --git a/api/server/src/main/kotlin/su/plo/slib/api/server/command/brigadier/McArgumentResolver.kt b/api/server/src/main/kotlin/su/plo/slib/api/server/command/brigadier/McArgumentResolver.kt index bda0746..7e75839 100644 --- a/api/server/src/main/kotlin/su/plo/slib/api/server/command/brigadier/McArgumentResolver.kt +++ b/api/server/src/main/kotlin/su/plo/slib/api/server/command/brigadier/McArgumentResolver.kt @@ -1,6 +1,7 @@ package su.plo.slib.api.server.command.brigadier import com.mojang.brigadier.context.CommandContext +import su.plo.slib.api.command.brigadier.McBrigadierSource import su.plo.slib.api.server.entity.McServerEntity import su.plo.slib.api.server.entity.player.McServerPlayer import su.plo.slib.api.service.lazyService @@ -15,7 +16,7 @@ object McArgumentResolver { * @return The selected entity. */ @JvmStatic - fun getEntity(context: CommandContext, name: String): McServerEntity = + fun getEntity(context: CommandContext, name: String): McServerEntity = provider.getEntity(context, name) /** @@ -26,7 +27,7 @@ object McArgumentResolver { * @return A collection of selected entities. */ @JvmStatic - fun getEntities(context: CommandContext, name: String): Collection = + fun getEntities(context: CommandContext, name: String): Collection = provider.getEntities(context, name) /** @@ -37,7 +38,7 @@ object McArgumentResolver { * @return The selected player. */ @JvmStatic - fun getPlayer(context: CommandContext, name: String): McServerPlayer = + fun getPlayer(context: CommandContext, name: String): McServerPlayer = provider.getPlayer(context, name) /** @@ -48,18 +49,18 @@ object McArgumentResolver { * @return A collection of selected players. */ @JvmStatic - fun getPlayers(context: CommandContext, name: String): Collection = + fun getPlayers(context: CommandContext, name: String): Collection = provider.getPlayers(context, name) private val provider: Provider by lazyService() interface Provider { - fun getEntity(context: CommandContext, name: String): McServerEntity + fun getEntity(context: CommandContext, name: String): McServerEntity - fun getEntities(context: CommandContext, name: String): Collection + fun getEntities(context: CommandContext, name: String): Collection - fun getPlayer(context: CommandContext, name: String): McServerPlayer + fun getPlayer(context: CommandContext, name: String): McServerPlayer - fun getPlayers(context: CommandContext, name: String): Collection + fun getPlayers(context: CommandContext, name: String): Collection } } diff --git a/bungee/src/main/kotlin/su/plo/slib/bungee/command/brigadier/BungeeBrigadierCommand.kt b/bungee/src/main/kotlin/su/plo/slib/bungee/command/brigadier/BungeeBrigadierCommand.kt index 882bc9e..71071ed 100644 --- a/bungee/src/main/kotlin/su/plo/slib/bungee/command/brigadier/BungeeBrigadierCommand.kt +++ b/bungee/src/main/kotlin/su/plo/slib/bungee/command/brigadier/BungeeBrigadierCommand.kt @@ -8,21 +8,21 @@ import net.md_5.bungee.api.plugin.Command import net.md_5.bungee.api.plugin.TabExecutor import su.plo.slib.api.chat.component.McTextComponent import su.plo.slib.api.chat.style.McTextStyle +import su.plo.slib.api.command.brigadier.McBrigadierSource import su.plo.slib.bungee.command.BungeeCommandManager class BungeeBrigadierCommand( private val commandManager: BungeeCommandManager, - private val command: LiteralArgumentBuilder, + private val command: LiteralArgumentBuilder, ) : Command(command.literal), TabExecutor { - private val dispatcher = CommandDispatcher() + private val dispatcher = CommandDispatcher() init { - @Suppress("UNCHECKED_CAST") - dispatcher.register(command as LiteralArgumentBuilder) + dispatcher.register(command) } override fun execute(sender: CommandSender, arguments: Array) { - val context = BungeeBrigadierSource(commandManager.getCommandSource(sender)) + val context = BungeeBrigadierSource(commandManager.getCommandSource(sender), instance = sender) val input = listOf(command.literal, *arguments).joinToString(" ") try { @@ -44,7 +44,7 @@ class BungeeBrigadierCommand( } override fun onTabComplete(sender: CommandSender, arguments: Array): Iterable { - val context = BungeeBrigadierSource(commandManager.getCommandSource(sender)) + val context = BungeeBrigadierSource(commandManager.getCommandSource(sender), instance = sender) val input = listOf(command.literal, *arguments).joinToString(" ") return dispatcher.getCompletionSuggestions( diff --git a/bungee/src/main/kotlin/su/plo/slib/bungee/command/brigadier/BungeeBrigadierSource.kt b/bungee/src/main/kotlin/su/plo/slib/bungee/command/brigadier/BungeeBrigadierSource.kt index 774f620..7e44eae 100644 --- a/bungee/src/main/kotlin/su/plo/slib/bungee/command/brigadier/BungeeBrigadierSource.kt +++ b/bungee/src/main/kotlin/su/plo/slib/bungee/command/brigadier/BungeeBrigadierSource.kt @@ -1,5 +1,6 @@ package su.plo.slib.bungee.command.brigadier +import net.md_5.bungee.api.CommandSender import su.plo.slib.api.command.McCommandSource import su.plo.slib.api.command.brigadier.McBrigadierSource import su.plo.slib.api.entity.McEntity @@ -7,9 +8,9 @@ import su.plo.slib.api.entity.McEntity data class BungeeBrigadierSource( override val source: McCommandSource, override val executor: McEntity? = null, -) : McBrigadierSource - -class BungeeBrigadierSourceProvider : McBrigadierSource.Provider { - override fun getBrigadierSource(source: S): McBrigadierSource = - source as BungeeBrigadierSource + private val instance: CommandSender, +) : McBrigadierSource { + @Suppress("UNCHECKED_CAST") + override fun getInstance(): T = + instance as T } diff --git a/bungee/src/main/resources/META-INF/services/su.plo.slib.api.command.brigadier.McBrigadierSource$Provider b/bungee/src/main/resources/META-INF/services/su.plo.slib.api.command.brigadier.McBrigadierSource$Provider deleted file mode 100644 index b7a32b7..0000000 --- a/bungee/src/main/resources/META-INF/services/su.plo.slib.api.command.brigadier.McBrigadierSource$Provider +++ /dev/null @@ -1 +0,0 @@ -su.plo.slib.bungee.command.brigadier.BungeeBrigadierSourceProvider diff --git a/common-proxy/src/testFixtures/kotlin/su/plo/slib/proxy/TestProxy.kt b/common-proxy/src/testFixtures/kotlin/su/plo/slib/proxy/TestProxy.kt index 0057b25..3ccc46b 100644 --- a/common-proxy/src/testFixtures/kotlin/su/plo/slib/proxy/TestProxy.kt +++ b/common-proxy/src/testFixtures/kotlin/su/plo/slib/proxy/TestProxy.kt @@ -2,6 +2,7 @@ package su.plo.slib.proxy import com.mojang.brigadier.Command import com.mojang.brigadier.builder.LiteralArgumentBuilder +import su.plo.slib.api.command.McCommandManager import su.plo.slib.api.command.McCommandSource import su.plo.slib.api.command.brigadier.McBrigadierSource import su.plo.slib.api.event.player.McPlayerJoinEvent @@ -33,10 +34,10 @@ class TestProxy { }) commands.register( - LiteralArgumentBuilder.literal("brigadier-ping") + McCommandManager.literal("brigadier-ping") .executes { - val context = McBrigadierSource.from(it) - context.source.sendMessage("Pong") + val source = it.source.source + source.sendMessage("Pong") Command.SINGLE_SUCCESS } diff --git a/common-server/src/testFixtures/kotlin/su/plo/slib/server/TestServer.kt b/common-server/src/testFixtures/kotlin/su/plo/slib/server/TestServer.kt index dd101d8..62ed148 100644 --- a/common-server/src/testFixtures/kotlin/su/plo/slib/server/TestServer.kt +++ b/common-server/src/testFixtures/kotlin/su/plo/slib/server/TestServer.kt @@ -1,11 +1,9 @@ package su.plo.slib.server import com.mojang.brigadier.Command -import com.mojang.brigadier.builder.LiteralArgumentBuilder -import com.mojang.brigadier.builder.RequiredArgumentBuilder import su.plo.slib.api.command.McCommand +import su.plo.slib.api.command.McCommandManager import su.plo.slib.api.command.McCommandSource -import su.plo.slib.api.command.brigadier.McBrigadierSource import su.plo.slib.api.event.player.McPlayerJoinEvent import su.plo.slib.api.event.player.McPlayerQuitEvent import su.plo.slib.api.logging.McLoggerFactory @@ -49,15 +47,15 @@ class TestServer( ) commands.register( - LiteralArgumentBuilder.literal("brigadier-entity-selector") + McCommandManager.literal("brigadier-entity-selector") .then( - LiteralArgumentBuilder.literal("entity") + McCommandManager.literal("entity") .then( - RequiredArgumentBuilder.argument("target", McArgumentTypes.entity()) + McCommandManager.argument("target", McArgumentTypes.entity()) .executes { val entity = McArgumentResolver.getEntity(it, "target") - val source = McBrigadierSource.from(it) + val source = it.source source.source.sendMessage("Found entity: $entity; Source: ${source.source}; Executor: ${source.executor}") Command.SINGLE_SUCCESS @@ -65,13 +63,13 @@ class TestServer( ) ) .then( - LiteralArgumentBuilder.literal("entities") + McCommandManager.literal("entities") .then( - RequiredArgumentBuilder.argument("target", McArgumentTypes.entities()) + McCommandManager.argument("target", McArgumentTypes.entities()) .executes { val entities = McArgumentResolver.getEntities(it, "target") - val source = McBrigadierSource.from(it) + val source = it.source source.source.sendMessage("Found entities: $entities; Source: ${source.source}; Executor: ${source.executor}") Command.SINGLE_SUCCESS @@ -79,13 +77,13 @@ class TestServer( ) ) .then( - LiteralArgumentBuilder.literal("player") + McCommandManager.literal("player") .then( - RequiredArgumentBuilder.argument("target", McArgumentTypes.player()) + McCommandManager.argument("target", McArgumentTypes.player()) .executes { val player = McArgumentResolver.getPlayer(it, "target") - val source = McBrigadierSource.from(it) + val source = it.source source.source.sendMessage("Found player: $player; Source: ${source.source}; Executor: ${source.executor}") Command.SINGLE_SUCCESS @@ -93,13 +91,13 @@ class TestServer( ) ) .then( - LiteralArgumentBuilder.literal("players") + McCommandManager.literal("players") .then( - RequiredArgumentBuilder.argument("target", McArgumentTypes.players()) + McCommandManager.argument("target", McArgumentTypes.players()) .executes { val players = McArgumentResolver.getPlayers(it, "target") - val source = McBrigadierSource.from(it) + val source = it.source source.source.sendMessage("Found players: $players; Source: ${source.source}; Executor: ${source.executor}") Command.SINGLE_SUCCESS diff --git a/common/src/main/kotlin/su/plo/slib/command/AbstractCommandManager.kt b/common/src/main/kotlin/su/plo/slib/command/AbstractCommandManager.kt index a8b0fbe..729c972 100644 --- a/common/src/main/kotlin/su/plo/slib/command/AbstractCommandManager.kt +++ b/common/src/main/kotlin/su/plo/slib/command/AbstractCommandManager.kt @@ -3,16 +3,25 @@ package su.plo.slib.command import com.google.common.collect.ImmutableList import com.google.common.collect.ImmutableMap import com.google.common.collect.Maps +import com.mojang.brigadier.RedirectModifier +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.context.CommandContext +import com.mojang.brigadier.tree.ArgumentCommandNode +import com.mojang.brigadier.tree.CommandNode +import com.mojang.brigadier.tree.LiteralCommandNode import su.plo.slib.api.command.McCommand import su.plo.slib.api.command.McCommandManager +import su.plo.slib.api.command.brigadier.McBrigadierSource import su.plo.slib.api.logging.McLoggerFactory abstract class AbstractCommandManager : McCommandManager() { private val logger = McLoggerFactory.createLogger("CommandManager") protected val commandByName: MutableMap = Maps.newHashMap() - protected var brigadierCommands: MutableList> = mutableListOf() + protected var brigadierCommands: MutableList> = mutableListOf() protected var registered = false @@ -21,11 +30,11 @@ abstract class AbstractCommandManager : McCommandManager() { get() = ImmutableMap.copyOf(commandByName) @get:Synchronized - override val registeredBrigadierCommands: List> + override val registeredBrigadierCommands: List> get() = ImmutableList.copyOf(brigadierCommands) @Synchronized - override fun register(command: LiteralArgumentBuilder) { + override fun register(command: LiteralArgumentBuilder) { check(!registered) { "register after commands registration is not supported" } require(brigadierCommands.none { it.literal == command.literal }) { "Command with name '${command.literal}' already exist" } @@ -60,10 +69,75 @@ abstract class AbstractCommandManager : McCommandManager() { } } - protected fun registerBrigadierCommands(register: (LiteralArgumentBuilder) -> Unit) { + protected fun registerBrigadierCommands(register: (LiteralArgumentBuilder) -> Unit) { brigadierCommands.forEach { command -> register(command) logger.info("Command '${command.literal}' registered") } } } + +@Suppress("UNCHECKED_CAST") +fun CommandContext.copyFor(source: T): CommandContext = + (this as CommandContext).copyFor(source) + +fun LiteralArgumentBuilder.proxied( + sourceFactory: (T) -> McBrigadierSource, + contextFactory: (CommandContext) -> CommandContext, +): LiteralArgumentBuilder = + build().toProxyNode(sourceFactory, contextFactory) as LiteralArgumentBuilder + +fun CommandNode.toProxyNode( + sourceFactory: (T) -> McBrigadierSource, + contextFactory: (CommandContext) -> CommandContext, +): ArgumentBuilder { + val node = + when (this) { + is LiteralCommandNode -> LiteralArgumentBuilder.literal(literal) + is ArgumentCommandNode -> + RequiredArgumentBuilder.argument(name, type as ArgumentType) + else -> throw IllegalArgumentException("Unsupported command node: $this") + } + + redirect?.let { redirect -> + val modifier = redirectModifier + + if (modifier == null) { + node.redirect(redirect.toProxyNode(sourceFactory, contextFactory).build()) + } else { + val proxiedModifier = object : RedirectModifier { + override fun apply(context: CommandContext): Collection { + val context = contextFactory(context) + + return modifier.apply(context).map { it.getInstance() } + } + } + + node.fork( + redirect.toProxyNode(sourceFactory, contextFactory).build(), + proxiedModifier, + ) + } + } + + children + .map { it.toProxyNode(sourceFactory, contextFactory) } + .forEach { node.then(it) } + + requirement?.let { requirement -> + node.requires { sourceStack -> + val source = sourceFactory(sourceStack) + requirement.test(source) + } + } + + command?.let { command -> + node.executes { context -> + val context = contextFactory(context) + + command.run(context) + } + } + + return node +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 834bf7a..595408e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -26,6 +26,8 @@ fabric-permissions = "0.3.1" reflection-remapper = "0.1.2" +fletching-table = "0.1.0-alpha.22" + [libraries] kotlinx-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" } kotlinx-coroutines-jdk8 = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-jdk8", version.ref = "kotlinx-coroutines" } @@ -62,3 +64,4 @@ dokka = { id = "org.jetbrains.dokka", version.ref = "dokka" } run-paper = { id = "xyz.jpenilla.run-paper", version.ref = "run-task"} run-velocity = { id = "xyz.jpenilla.run-velocity", version.ref = "run-task"} run-waterfall = { id = "xyz.jpenilla.run-waterfall", version.ref = "run-task"} +fletchingtable = { id = "dev.kikugie.fletching-table", version.ref = "fletching-table" } diff --git a/minestom/src/main/kotlin/su/plo/slib/minestom/command/MinestomCommandManager.kt b/minestom/src/main/kotlin/su/plo/slib/minestom/command/MinestomCommandManager.kt index 1f38417..c77c327 100644 --- a/minestom/src/main/kotlin/su/plo/slib/minestom/command/MinestomCommandManager.kt +++ b/minestom/src/main/kotlin/su/plo/slib/minestom/command/MinestomCommandManager.kt @@ -133,7 +133,7 @@ class MinestomCommandManager( @Suppress("UNCHECKED_CAST") val brigadierContext = CommandContext( - MinestomBrigadierSource(sender, source, source as? McEntity), + MinestomBrigadierSource(source, source as? McEntity, sender), context.input, context.map.mapValues { ParsedArgument(0, 0, it.value) }, this@toMinestom as com.mojang.brigadier.Command, diff --git a/minestom/src/main/kotlin/su/plo/slib/minestom/command/brigadier/MinestomBrigadierSource.kt b/minestom/src/main/kotlin/su/plo/slib/minestom/command/brigadier/MinestomBrigadierSource.kt index 1988dfa..7c761a6 100644 --- a/minestom/src/main/kotlin/su/plo/slib/minestom/command/brigadier/MinestomBrigadierSource.kt +++ b/minestom/src/main/kotlin/su/plo/slib/minestom/command/brigadier/MinestomBrigadierSource.kt @@ -6,12 +6,11 @@ import su.plo.slib.api.command.brigadier.McBrigadierSource import su.plo.slib.api.entity.McEntity data class MinestomBrigadierSource( - val sender: CommandSender, override val source: McCommandSource, - override val executor: McEntity? -) : McBrigadierSource - -class MinestomBrigadierSourceProvider : McBrigadierSource.Provider { - override fun getBrigadierSource(source: S): McBrigadierSource = - source as MinestomBrigadierSource + override val executor: McEntity?, + private val instance: CommandSender, +) : McBrigadierSource { + @Suppress("UNCHECKED_CAST") + override fun getInstance(): T = + instance as T } diff --git a/minestom/src/main/kotlin/su/plo/slib/minestom/command/brigadier/MinestomEntityArguments.kt b/minestom/src/main/kotlin/su/plo/slib/minestom/command/brigadier/MinestomEntityArguments.kt index 7b24411..ff5fb54 100644 --- a/minestom/src/main/kotlin/su/plo/slib/minestom/command/brigadier/MinestomEntityArguments.kt +++ b/minestom/src/main/kotlin/su/plo/slib/minestom/command/brigadier/MinestomEntityArguments.kt @@ -5,12 +5,12 @@ import com.mojang.brigadier.context.CommandContext import net.minestom.server.command.builder.arguments.minecraft.ArgumentEntity import net.minestom.server.entity.Player import net.minestom.server.utils.entity.EntityFinder +import su.plo.slib.api.command.brigadier.McBrigadierSource import su.plo.slib.api.server.command.brigadier.McArgumentResolver import su.plo.slib.api.server.command.brigadier.McArgumentTypes import su.plo.slib.api.server.entity.McServerEntity import su.plo.slib.api.server.entity.player.McServerPlayer import su.plo.slib.minestom.MinestomServerLib -import su.plo.slib.minestom.command.MinestomCommandManager class MinestomEntityArguments : McArgumentTypes.Provider, McArgumentResolver.Provider { @@ -42,39 +42,39 @@ class MinestomEntityArguments : McArgumentTypes.Provider, McArgumentResolver.Pro ArgumentEntity(name).singleEntity(false).onlyPlayers(true) } as ArgumentType - override fun getEntity(context: CommandContext, name: String): McServerEntity { + override fun getEntity(context: CommandContext, name: String): McServerEntity { val finder = context.getArgument(name, EntityFinder::class.java) val brigadierContext = context.source as MinestomBrigadierSource - val entity = finder.findFirstEntity(brigadierContext.sender) + val entity = finder.findFirstEntity(brigadierContext.getInstance()) ?: throw IllegalArgumentException("No entity found") return serverLib.getEntityByInstance(entity) } - override fun getEntities(context: CommandContext, name: String): Collection { + override fun getEntities(context: CommandContext, name: String): Collection { val finder = context.getArgument(name, EntityFinder::class.java) val brigadierContext = context.source as MinestomBrigadierSource - return finder.find(brigadierContext.sender).map { serverLib.getEntityByInstance(it) } + return finder.find(brigadierContext.getInstance()).map { serverLib.getEntityByInstance(it) } } - override fun getPlayer(context: CommandContext, name: String): McServerPlayer { + override fun getPlayer(context: CommandContext, name: String): McServerPlayer { val finder = context.getArgument(name, EntityFinder::class.java) val brigadierContext = context.source as MinestomBrigadierSource - val player = finder.findFirstPlayer(brigadierContext.sender) + val player = finder.findFirstPlayer(brigadierContext.getInstance()) ?: throw IllegalArgumentException("No player found") return serverLib.getPlayerByInstance(player) } - override fun getPlayers(context: CommandContext, name: String): Collection { + override fun getPlayers(context: CommandContext, name: String): Collection { val finder = context.getArgument(name, EntityFinder::class.java) val brigadierContext = context.source as MinestomBrigadierSource - return finder.find(brigadierContext.sender) + return finder.find(brigadierContext.getInstance()) .filterIsInstance() .map { serverLib.getPlayerByInstance(it) } } diff --git a/minestom/src/main/resources/META-INF/services/su.plo.slib.api.command.brigadier.McBrigadierSource$Provider b/minestom/src/main/resources/META-INF/services/su.plo.slib.api.command.brigadier.McBrigadierSource$Provider deleted file mode 100644 index 76252ae..0000000 --- a/minestom/src/main/resources/META-INF/services/su.plo.slib.api.command.brigadier.McBrigadierSource$Provider +++ /dev/null @@ -1 +0,0 @@ -su.plo.slib.minestom.command.brigadier.MinestomBrigadierSourceProvider diff --git a/modded/build.gradle.kts b/modded/build.gradle.kts index 3df1366..68e7e54 100644 --- a/modded/build.gradle.kts +++ b/modded/build.gradle.kts @@ -6,6 +6,7 @@ import net.fabricmc.loom.task.RemapJarTask plugins { id("com.gradleup.shadow") + alias(libs.plugins.fletchingtable) } val minecraftVersion = stonecutter.current.project.substringBefore('-') @@ -72,9 +73,9 @@ loom.mods.findByName("main")?.apply { mainResourceDirectory.set(sourceSets.test.get().output.resourcesDir) } -if (isForge && stonecutter.eval(minecraftVersion, "<1.20.2")) { +if (isForge) { loom.forge { - mixinConfig("slib-forge.mixins.json") + mixinConfig("slib.mixins.json") } } @@ -82,6 +83,12 @@ configurations { named("loomDevelopmentDependencies") { extendsFrom(configurations.getByName("implementation")) } } +fletchingTable { + j52j.register("main") { + extension("json", "*.json5") + } +} + dependencies { val minecraftVersionDep = (findProperty("deps.minecraft") as? String) ?: stonecutter.current.project.substringBefore('-') @@ -230,30 +237,10 @@ tasks { mergeServiceFiles() exclude("META-INF/*.kotlin_module") - // todo: neoforge should use its own mixin without refmap - if (isForge) { exclude("fabric.mod.json") - exclude("slib-no-refmap.mixins.json") - - if (stonecutter.eval(minecraftVersion, ">=1.20.2")) { - exclude("slib-forge.mixins.json") - } } else if (isNeoForge) { exclude("fabric.mod.json") - exclude("slib-forge.mixins.json") - exclude("slib.mixins.json") - - rename("slib-no-refmap.mixins.json", "slib.mixins.json") - } else { - exclude("slib-forge.mixins.json") - - if (stonecutter.eval(minecraftVersion, ">=26.1")) { - exclude("slib.mixins.json") - rename("slib-no-refmap.mixins.json", "slib.mixins.json") - } else { - exclude("slib-no-refmap.mixins.json") - } } } diff --git a/modded/src/main/java/su/plo/slib/mod/mixin/accessor/ServerPlayerCommandSourceAccessor.java b/modded/src/main/java/su/plo/slib/mod/mixin/accessor/ServerPlayerCommandSourceAccessor.java new file mode 100644 index 0000000..44def00 --- /dev/null +++ b/modded/src/main/java/su/plo/slib/mod/mixin/accessor/ServerPlayerCommandSourceAccessor.java @@ -0,0 +1,14 @@ +//? if >=1.21.10 { +/*package su.plo.slib.mod.mixin.accessor; + +import net.minecraft.server.level.ServerPlayer; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(targets = "net.minecraft.server.level.ServerPlayer$3") +public interface ServerPlayerCommandSourceAccessor { + + @Accessor("field_54403") + ServerPlayer slib_getServerPlayer(); +} +*///?} diff --git a/modded/src/main/kotlin/su/plo/slib/mod/command/ModCommandManager.kt b/modded/src/main/kotlin/su/plo/slib/mod/command/ModCommandManager.kt index c1bbdc5..39c523d 100644 --- a/modded/src/main/kotlin/su/plo/slib/mod/command/ModCommandManager.kt +++ b/modded/src/main/kotlin/su/plo/slib/mod/command/ModCommandManager.kt @@ -1,15 +1,23 @@ package su.plo.slib.mod.command import com.mojang.brigadier.CommandDispatcher -import com.mojang.brigadier.builder.LiteralArgumentBuilder +import com.mojang.brigadier.context.CommandContext import net.minecraft.commands.CommandSourceStack import net.minecraft.world.entity.player.Player import su.plo.slib.api.command.McCommand import su.plo.slib.api.command.McCommandSource +import su.plo.slib.api.command.brigadier.McBrigadierSource import su.plo.slib.api.server.McServerLib import su.plo.slib.command.AbstractCommandManager +import su.plo.slib.command.copyFor +import su.plo.slib.command.proxied +import su.plo.slib.mod.command.brigadier.ModBrigadierSource import su.plo.slib.mod.mixin.accessor.CommandSourceStackAccessor +//? if >=1.21.10 { +/*import su.plo.slib.mod.mixin.accessor.ServerPlayerCommandSourceAccessor +*///?} + class ModCommandManager( private val minecraftServer: McServerLib ) : AbstractCommandManager() { @@ -23,7 +31,12 @@ class ModCommandManager( @Suppress("UNCHECKED_CAST") registerBrigadierCommands { command -> - dispatcher.register(command as LiteralArgumentBuilder) + dispatcher.register( + command.proxied( + ModBrigadierSource::from, + { it.toMc() }, + ) + ) } this.registered = true @@ -35,8 +48,20 @@ class ModCommandManager( val source = sourceStack.slib_getSource() - return if (source is Player) { - minecraftServer.getPlayerByInstance(source) - } else ModDefaultCommandSource(minecraftServer, sourceStack) + //? if >=1.21.10 { + /*val player = (source as? ServerPlayerCommandSourceAccessor) + ?.let { minecraftServer.getPlayerByInstance(it.slib_getServerPlayer()) } + *///?} else { + val player = (source as? Player)?.let { minecraftServer.getPlayerByInstance(it) } + //?} + + return player ?: ModDefaultCommandSource(minecraftServer, sourceStack) } } + +fun CommandContext.toSourceStack(): CommandContext = + copyFor(source.getInstance() as CommandSourceStack) + +fun CommandContext.toMc(): CommandContext = + copyFor(ModBrigadierSource.from(source)) + diff --git a/modded/src/main/kotlin/su/plo/slib/mod/command/brigadier/ModBrigadierArguments.kt b/modded/src/main/kotlin/su/plo/slib/mod/command/brigadier/ModBrigadierArguments.kt index 942a3ed..352476b 100644 --- a/modded/src/main/kotlin/su/plo/slib/mod/command/brigadier/ModBrigadierArguments.kt +++ b/modded/src/main/kotlin/su/plo/slib/mod/command/brigadier/ModBrigadierArguments.kt @@ -2,13 +2,14 @@ package su.plo.slib.mod.command.brigadier import com.mojang.brigadier.arguments.ArgumentType import com.mojang.brigadier.context.CommandContext -import net.minecraft.commands.CommandSourceStack import net.minecraft.commands.arguments.EntityArgument +import su.plo.slib.api.command.brigadier.McBrigadierSource import su.plo.slib.api.server.command.brigadier.McArgumentResolver import su.plo.slib.api.server.command.brigadier.McArgumentTypes import su.plo.slib.api.server.entity.McServerEntity import su.plo.slib.api.server.entity.player.McServerPlayer import su.plo.slib.mod.ModServerLib +import su.plo.slib.mod.command.toSourceStack @Suppress("UNCHECKED_CAST") class ModBrigadierArguments : McArgumentTypes.Provider, McArgumentResolver.Provider { @@ -23,35 +24,35 @@ class ModBrigadierArguments : McArgumentTypes.Provider, McArgumentResolver.Provi override fun players(): ArgumentType = EntityArgument.players() as ArgumentType - override fun getEntity( - context: CommandContext, + override fun getEntity( + context: CommandContext, name: String, ): McServerEntity { - val entity = EntityArgument.getEntity(context as CommandContext, name) + val entity = EntityArgument.getEntity(context.toSourceStack(), name) return serverLib.getEntityByInstance(entity) } - override fun getEntities( - context: CommandContext, + override fun getEntities( + context: CommandContext, name: String, ): Collection { - val entities = EntityArgument.getEntities(context as CommandContext, name) + val entities = EntityArgument.getEntities(context.toSourceStack(), name) return entities.map { serverLib.getEntityByInstance(it) } } - override fun getPlayer( - context: CommandContext, + override fun getPlayer( + context: CommandContext, name: String, ): McServerPlayer { - val player = EntityArgument.getPlayer(context as CommandContext, name) + val player = EntityArgument.getPlayer(context.toSourceStack(), name) return serverLib.getPlayerByInstance(player) } - override fun getPlayers( - context: CommandContext, + override fun getPlayers( + context: CommandContext, name: String, ): Collection { - val players = EntityArgument.getPlayers(context as CommandContext, name) + val players = EntityArgument.getPlayers(context.toSourceStack(), name) return players.map { serverLib.getPlayerByInstance(it) } } } diff --git a/modded/src/main/kotlin/su/plo/slib/mod/command/brigadier/ModBrigadierSource.kt b/modded/src/main/kotlin/su/plo/slib/mod/command/brigadier/ModBrigadierSource.kt index 524a013..0ad8c67 100644 --- a/modded/src/main/kotlin/su/plo/slib/mod/command/brigadier/ModBrigadierSource.kt +++ b/modded/src/main/kotlin/su/plo/slib/mod/command/brigadier/ModBrigadierSource.kt @@ -8,19 +8,23 @@ import su.plo.slib.mod.ModServerLib data class ModBrigadierSource( override val source: McCommandSource, - override val executor: McEntity? -) : McBrigadierSource + override val executor: McEntity?, + private val instance: CommandSourceStack, +) : McBrigadierSource { -class ModBrigadierSourceProvider : McBrigadierSource.Provider { - private val minecraftServer by lazy { ModServerLib } + @Suppress("UNCHECKED_CAST") + override fun getInstance(): T = + instance as T - override fun getBrigadierSource(sourceStack: S): McBrigadierSource { - require(sourceStack is CommandSourceStack) { "source is not " + CommandSourceStack::class.java } + companion object { + private val minecraftServer by lazy { ModServerLib } - val executor = sourceStack.entity?.let { minecraftServer.getEntityByInstance(it) } + fun from(sourceStack: CommandSourceStack): ModBrigadierSource { + val executor = sourceStack.entity?.let { minecraftServer.getEntityByInstance(it) } - val source = minecraftServer.commandManager.getCommandSource(sourceStack) + val source = minecraftServer.commandManager.getCommandSource(sourceStack) - return ModBrigadierSource(source, executor) + return ModBrigadierSource(source, executor, sourceStack) + } } } diff --git a/modded/src/main/resources/META-INF/services/su.plo.slib.api.command.brigadier.McBrigadierSource$Provider b/modded/src/main/resources/META-INF/services/su.plo.slib.api.command.brigadier.McBrigadierSource$Provider deleted file mode 100644 index b8cc0ef..0000000 --- a/modded/src/main/resources/META-INF/services/su.plo.slib.api.command.brigadier.McBrigadierSource$Provider +++ /dev/null @@ -1 +0,0 @@ -su.plo.slib.mod.command.brigadier.ModBrigadierSourceProvider diff --git a/modded/src/main/resources/slib-forge.mixins.json b/modded/src/main/resources/slib-forge.mixins.json deleted file mode 100644 index cc0a5a2..0000000 --- a/modded/src/main/resources/slib-forge.mixins.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "required": true, - "minVersion": "0.7", - "package": "su.plo.slib.mod.mixin", - "mixins": [ - "MixinServerGamePacketListenerImpl" - ], - "client": [ - ], - "injectors": { - "defaultRequire": 1 - }, - "refmap": "slib-${loader}-${mcVersion}-modded_${mcVersion}-${loader}-refmap.json" -} diff --git a/modded/src/main/resources/slib-no-refmap.mixins.json b/modded/src/main/resources/slib-no-refmap.mixins.json deleted file mode 100644 index 270ec9b..0000000 --- a/modded/src/main/resources/slib-no-refmap.mixins.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "required": true, - "minVersion": "0.7", - "package": "su.plo.slib.mod.mixin", - "mixins": [ - "MixinPlayerList", - "MixinServerPlayer", - "accessor.CommandSourceStackAccessor" - ], - "client": [ - ], - "injectors": { - "defaultRequire": 1 - } -} diff --git a/modded/src/main/resources/slib.mixins.json b/modded/src/main/resources/slib.mixins.json deleted file mode 100644 index 9047782..0000000 --- a/modded/src/main/resources/slib.mixins.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "required": true, - "minVersion": "0.7", - "package": "su.plo.slib.mod.mixin", - "mixins": [ - "MixinPlayerList", - "MixinServerPlayer", - "accessor.CommandSourceStackAccessor" - ], - "client": [ - ], - "injectors": { - "defaultRequire": 1 - }, - "refmap": "slib-${loader}-${mcVersion}-modded_${mcVersion}-${loader}-refmap.json" -} diff --git a/modded/src/main/resources/slib.mixins.json5 b/modded/src/main/resources/slib.mixins.json5 new file mode 100644 index 0000000..1e10527 --- /dev/null +++ b/modded/src/main/resources/slib.mixins.json5 @@ -0,0 +1,24 @@ +{ + "required": true, + "minVersion": "0.7", + "package": "su.plo.slib.mod.mixin", + "mixins": [ + "MixinPlayerList", + "MixinServerPlayer", + "accessor.CommandSourceStackAccessor" + //? if forge && <1.20.2 { + /*,"MixinServerGamePacketListenerImpl" + *///?} + //? if >=1.21.10 { + /*,"accessor.ServerPlayerCommandSourceAccessor" + *///?} + ], + "client": [ + ], + "injectors": { + "defaultRequire": 1 + } + //? if !neoforge { + ,"refmap": "slib-${loader}-${mcVersion}-modded_${mcVersion}-${loader}-refmap.json", + //?} +} diff --git a/modded/src/test/resources/META-INF/neoforge.mods.toml b/modded/src/test/resources/META-INF/neoforge.mods.toml index f6039a8..b4a8d1c 100644 --- a/modded/src/test/resources/META-INF/neoforge.mods.toml +++ b/modded/src/test/resources/META-INF/neoforge.mods.toml @@ -3,7 +3,7 @@ loaderVersion = "[2,)" license = "MIT" [[mixins]] -config="slib-no-refmap.mixins.json" +config="slib.mixins.json" [[mods]] modId = "slib_test_mod" diff --git a/settings.gradle.kts b/settings.gradle.kts index 1171975..d3f0dd2 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -19,6 +19,8 @@ pluginManagement { maven("https://repo.plo.su") maven("https://repo.plasmoverse.com/snapshots") + maven("https://maven.kikugie.dev/snapshots") + maven("https://repo.essential.gg/repository/maven-public") } } diff --git a/spigot/src/main/kotlin/su/plo/slib/spigot/command/SpigotCommandManager.kt b/spigot/src/main/kotlin/su/plo/slib/spigot/command/SpigotCommandManager.kt index 2d7e63c..c02ace0 100644 --- a/spigot/src/main/kotlin/su/plo/slib/spigot/command/SpigotCommandManager.kt +++ b/spigot/src/main/kotlin/su/plo/slib/spigot/command/SpigotCommandManager.kt @@ -1,5 +1,6 @@ package su.plo.slib.spigot.command +import com.mojang.brigadier.context.CommandContext import org.bukkit.command.Command import org.bukkit.command.CommandSender import org.bukkit.command.SimpleCommandMap @@ -7,10 +8,14 @@ import org.bukkit.entity.Player import org.bukkit.plugin.java.JavaPlugin import su.plo.slib.api.command.McCommand import su.plo.slib.api.command.McCommandSource +import su.plo.slib.api.command.brigadier.McBrigadierSource import su.plo.slib.api.logging.McLoggerFactory import su.plo.slib.api.server.event.command.McServerCommandsRegisterEvent import su.plo.slib.command.AbstractCommandManager +import su.plo.slib.command.copyFor +import su.plo.slib.command.proxied import su.plo.slib.spigot.SpigotServerLib +import su.plo.slib.spigot.command.brigadier.SpigotBrigadierSource import su.plo.slib.spigot.nms.getCommandDispatcher class SpigotCommandManager( @@ -33,7 +38,12 @@ class SpigotCommandManager( try { val dispatcher = loader.server.getCommandDispatcher() registerBrigadierCommands { command -> - dispatcher.register(command) + dispatcher.register( + command.proxied( + SpigotBrigadierSource::from, + { it.toMc() }, + ) + ) } } catch (e: Exception) { logger.warn("Failed to get Brigadier dispatcher: ${e.message}") @@ -81,3 +91,10 @@ class SpigotCommandManager( .also { it.isAccessible = true } .get(server) as SimpleCommandMap } + +fun CommandContext.toSourceStack(): CommandContext = + copyFor(source.getInstance()) + +fun CommandContext.toMc(): CommandContext = + copyFor(SpigotBrigadierSource.from(source)) + diff --git a/spigot/src/main/kotlin/su/plo/slib/spigot/command/brigadier/SpigotBrigadierArguments.kt b/spigot/src/main/kotlin/su/plo/slib/spigot/command/brigadier/SpigotBrigadierArguments.kt index 3bfae23..e667128 100644 --- a/spigot/src/main/kotlin/su/plo/slib/spigot/command/brigadier/SpigotBrigadierArguments.kt +++ b/spigot/src/main/kotlin/su/plo/slib/spigot/command/brigadier/SpigotBrigadierArguments.kt @@ -2,11 +2,13 @@ package su.plo.slib.spigot.command.brigadier import com.mojang.brigadier.arguments.ArgumentType import com.mojang.brigadier.context.CommandContext +import su.plo.slib.api.command.brigadier.McBrigadierSource import su.plo.slib.api.server.command.brigadier.McArgumentResolver import su.plo.slib.api.server.command.brigadier.McArgumentTypes import su.plo.slib.api.server.entity.McServerEntity import su.plo.slib.api.server.entity.player.McServerPlayer import su.plo.slib.spigot.SpigotServerLib +import su.plo.slib.spigot.command.toSourceStack import su.plo.slib.spigot.nms.ReflectionProxies import java.lang.reflect.UndeclaredThrowableException @@ -22,18 +24,18 @@ class SpigotBrigadierArguments: McArgumentTypes.Provider, McArgumentResolver.Pro override fun players(): ArgumentType = ReflectionProxies.entityArgument.players() - override fun getEntity(context: CommandContext, name: String): McServerEntity { + override fun getEntity(context: CommandContext, name: String): McServerEntity { val entity = rethrowProxyException { - ReflectionProxies.entityArgument.getEntity(context, name) + ReflectionProxies.entityArgument.getEntity(context.toSourceStack(), name) } val bukkitEntity = ReflectionProxies.entity.getBukkitEntity(entity) return serverLib.getEntityByInstance(bukkitEntity) } - override fun getEntities(context: CommandContext, name: String): Collection { + override fun getEntities(context: CommandContext, name: String): Collection { val entities = rethrowProxyException { - ReflectionProxies.entityArgument.getEntities(context, name) + ReflectionProxies.entityArgument.getEntities(context.toSourceStack(), name) } return entities.map { entity -> @@ -42,18 +44,18 @@ class SpigotBrigadierArguments: McArgumentTypes.Provider, McArgumentResolver.Pro } } - override fun getPlayer(context: CommandContext, name: String): McServerPlayer { + override fun getPlayer(context: CommandContext, name: String): McServerPlayer { val player = rethrowProxyException { - ReflectionProxies.entityArgument.getPlayer(context, name) + ReflectionProxies.entityArgument.getPlayer(context.toSourceStack(), name) } val bukkitPlayer = ReflectionProxies.entity.getBukkitEntity(player) return serverLib.getPlayerByInstance(bukkitPlayer) } - override fun getPlayers(context: CommandContext, name: String): Collection { + override fun getPlayers(context: CommandContext, name: String): Collection { val players = rethrowProxyException { - ReflectionProxies.entityArgument.getPlayers(context, name) + ReflectionProxies.entityArgument.getPlayers(context.toSourceStack(), name) } return players.map { player -> diff --git a/spigot/src/main/kotlin/su/plo/slib/spigot/command/brigadier/SpigotBrigadierSource.kt b/spigot/src/main/kotlin/su/plo/slib/spigot/command/brigadier/SpigotBrigadierSource.kt index 983883e..8ec9cf4 100644 --- a/spigot/src/main/kotlin/su/plo/slib/spigot/command/brigadier/SpigotBrigadierSource.kt +++ b/spigot/src/main/kotlin/su/plo/slib/spigot/command/brigadier/SpigotBrigadierSource.kt @@ -9,20 +9,24 @@ import su.plo.slib.spigot.nms.ReflectionProxies data class SpigotBrigadierSource( override val source: McCommandSource, override val executor: McEntity?, -): McBrigadierSource + private val instance: Any, +): McBrigadierSource { -class SpigotBrigadierSourceProvider : McBrigadierSource.Provider { - private val minecraftServer by lazy { SpigotServerLib.instance } + @Suppress("UNCHECKED_CAST") + override fun getInstance(): T = + instance as T - override fun getBrigadierSource(sourceStack: S): McBrigadierSource { - val sourceStack = sourceStack as Any + companion object { + private val minecraftServer by lazy { SpigotServerLib.instance } - val source = ReflectionProxies.commandSourceStack.getBukkitSender(sourceStack) - .let { minecraftServer.commandManager.getCommandSource(it) } - val entity = ReflectionProxies.commandSourceStack.getEntity(sourceStack) - ?.let { ReflectionProxies.entity.getBukkitEntity(it) } - ?.let { minecraftServer.getEntityByInstance(it) } + fun from(sourceStack: Any): SpigotBrigadierSource { + val source = ReflectionProxies.commandSourceStack.getBukkitSender(sourceStack) + .let { minecraftServer.commandManager.getCommandSource(it) } + val entity = ReflectionProxies.commandSourceStack.getEntity(sourceStack) + ?.let { ReflectionProxies.entity.getBukkitEntity(it) } + ?.let { minecraftServer.getEntityByInstance(it) } - return SpigotBrigadierSource(source, entity) + return SpigotBrigadierSource(source, entity, sourceStack) + } } } diff --git a/spigot/src/main/resources/META-INF/services/su.plo.slib.api.command.brigadier.McBrigadierSource$Provider b/spigot/src/main/resources/META-INF/services/su.plo.slib.api.command.brigadier.McBrigadierSource$Provider deleted file mode 100644 index f8bd081..0000000 --- a/spigot/src/main/resources/META-INF/services/su.plo.slib.api.command.brigadier.McBrigadierSource$Provider +++ /dev/null @@ -1 +0,0 @@ -su.plo.slib.spigot.command.brigadier.SpigotBrigadierSourceProvider diff --git a/velocity/src/main/kotlin/su/plo/slib/velocity/command/VelocityCommandManager.kt b/velocity/src/main/kotlin/su/plo/slib/velocity/command/VelocityCommandManager.kt index 532c4b4..3d4f553 100644 --- a/velocity/src/main/kotlin/su/plo/slib/velocity/command/VelocityCommandManager.kt +++ b/velocity/src/main/kotlin/su/plo/slib/velocity/command/VelocityCommandManager.kt @@ -1,6 +1,6 @@ package su.plo.slib.velocity.command -import com.mojang.brigadier.builder.LiteralArgumentBuilder +import com.mojang.brigadier.context.CommandContext import com.velocitypowered.api.command.BrigadierCommand import com.velocitypowered.api.command.CommandSource import com.velocitypowered.api.event.Subscribe @@ -8,10 +8,14 @@ import com.velocitypowered.api.event.command.CommandExecuteEvent import com.velocitypowered.api.proxy.Player import com.velocitypowered.api.proxy.ProxyServer import su.plo.slib.api.command.McCommandSource +import su.plo.slib.api.command.brigadier.McBrigadierSource import su.plo.slib.api.proxy.McProxyLib import su.plo.slib.api.proxy.command.McProxyCommand import su.plo.slib.api.proxy.event.command.McProxyCommandExecuteEvent import su.plo.slib.command.AbstractCommandManager +import su.plo.slib.command.copyFor +import su.plo.slib.command.proxied +import su.plo.slib.velocity.command.brigadier.VelocityBrigadierSource import su.plo.slib.velocity.extension.textConverter class VelocityCommandManager( @@ -60,10 +64,19 @@ class VelocityCommandManager( registerBrigadierCommands { command -> @Suppress("UNCHECKED_CAST") - val brigadierCommand = BrigadierCommand(command as LiteralArgumentBuilder) + val brigadierCommand = BrigadierCommand( + command.proxied( + VelocityBrigadierSource::from, + { it.toMc() }, + ) + ) proxyServer.commandManager.register(brigadierCommand) } registered = true } } + +fun CommandContext.toMc(): CommandContext = + copyFor(VelocityBrigadierSource.from(source)) + diff --git a/velocity/src/main/kotlin/su/plo/slib/velocity/command/brigadier/VelocityBrigadierSource.kt b/velocity/src/main/kotlin/su/plo/slib/velocity/command/brigadier/VelocityBrigadierSource.kt index 8f79631..6e13750 100644 --- a/velocity/src/main/kotlin/su/plo/slib/velocity/command/brigadier/VelocityBrigadierSource.kt +++ b/velocity/src/main/kotlin/su/plo/slib/velocity/command/brigadier/VelocityBrigadierSource.kt @@ -9,14 +9,16 @@ import su.plo.slib.velocity.VelocityProxyLib data class VelocityBrigadierSource( override val source: McCommandSource, override val executor: McEntity? = null, -) : McBrigadierSource + private val instance: CommandSource, +) : McBrigadierSource { + @Suppress("UNCHECKED_CAST") + override fun getInstance(): T = + instance as T -class VelocityBrigadierSourceProvider : McBrigadierSource.Provider { - private val minecraftProxy by lazy { VelocityProxyLib.instance } + companion object { + private val minecraftProxy by lazy { VelocityProxyLib.instance } - override fun getBrigadierSource(source: S): McBrigadierSource { - require(source is CommandSource) - - return VelocityBrigadierSource(minecraftProxy.commandManager.getCommandSource(source)) + fun from(source: CommandSource): VelocityBrigadierSource = + VelocityBrigadierSource(minecraftProxy.commandManager.getCommandSource(source), instance = source) } } diff --git a/velocity/src/main/resources/META-INF/services/su.plo.slib.api.command.brigadier.McBrigadierSource$Provider b/velocity/src/main/resources/META-INF/services/su.plo.slib.api.command.brigadier.McBrigadierSource$Provider deleted file mode 100644 index 9fff795..0000000 --- a/velocity/src/main/resources/META-INF/services/su.plo.slib.api.command.brigadier.McBrigadierSource$Provider +++ /dev/null @@ -1 +0,0 @@ -su.plo.slib.velocity.command.brigadier.VelocityBrigadierSourceProvider From e65135a2083ad1dead3ea38b451dc8715065242d Mon Sep 17 00:00:00 2001 From: Apehum Date: Mon, 29 Dec 2025 05:00:17 +0800 Subject: [PATCH 09/36] feat: custom brigadier argument types --- .../api/command/brigadier/ArgumentResolver.kt | 19 +++ .../command/brigadier/CustomArgumentType.kt | 35 +++++ .../command/brigadier/McArgumentResolver.kt | 66 -------- .../command/brigadier/McArgumentTypes.kt | 16 +- .../brigadier/McEntitiesArgumentResolver.kt | 9 ++ .../brigadier/McEntityArgumentResolver.kt | 9 ++ .../brigadier/McPlayerArgumentResolver.kt | 9 ++ .../brigadier/McPlayersArgumentResolver.kt | 9 ++ .../bungee/command/BungeeCommandManager.kt | 6 +- .../brigadier/BungeeBrigadierCommand.kt | 6 +- .../kotlin/su/plo/slib/proxy/TestProxy.kt | 16 ++ .../slib/proxy/command/UuidArgumentType.kt | 40 +++++ .../kotlin/su/plo/slib/server/TestServer.kt | 45 +++++- .../slib/server/command/UuidArgumentType.kt | 39 +++++ .../slib/command/AbstractCommandManager.kt | 49 +++--- .../brigadier/CustomArgumentCommandNode.kt | 78 ++++++++++ minestom/build.gradle.kts | 7 + .../command/MinestomCommandManager.kt | 145 +++++++++++++----- .../brigadier/MinestomEntityArguments.kt | 126 +++++++-------- ...mand.brigadier.McArgumentResolver$Provider | 1 - .../plo/slib/minestom/TestMinestomServer.kt | 34 +++- .../plo/slib/mod/command/ModCommandManager.kt | 2 +- .../brigadier/ModBrigadierArguments.kt | 93 ++++++----- ...mand.brigadier.McArgumentResolver$Provider | 1 - scripts/smoke-test.sh | 4 +- .../spigot/command/SpigotCommandManager.kt | 5 +- .../brigadier/SpigotBrigadierArguments.kt | 98 ++++++------ .../slib/spigot/nms/EntityArgumentProxy.kt | 13 -- .../slib/spigot/nms/EntitySelectorProxy.kt | 29 ++++ .../su/plo/slib/spigot/nms/Reflections.kt | 4 +- ...mand.brigadier.McArgumentResolver$Provider | 1 - .../src/main/resources/mappings/1.16.5.tiny | 9 +- .../src/main/resources/mappings/1.17.1.tiny | 9 +- .../src/main/resources/mappings/1.19.2.tiny | 9 +- .../mappings/{1.21.6.tiny => 1.21.4.tiny} | 9 +- 35 files changed, 706 insertions(+), 344 deletions(-) create mode 100644 api/common/src/main/kotlin/su/plo/slib/api/command/brigadier/ArgumentResolver.kt create mode 100644 api/common/src/main/kotlin/su/plo/slib/api/command/brigadier/CustomArgumentType.kt delete mode 100644 api/server/src/main/kotlin/su/plo/slib/api/server/command/brigadier/McArgumentResolver.kt create mode 100644 api/server/src/main/kotlin/su/plo/slib/api/server/command/brigadier/McEntitiesArgumentResolver.kt create mode 100644 api/server/src/main/kotlin/su/plo/slib/api/server/command/brigadier/McEntityArgumentResolver.kt create mode 100644 api/server/src/main/kotlin/su/plo/slib/api/server/command/brigadier/McPlayerArgumentResolver.kt create mode 100644 api/server/src/main/kotlin/su/plo/slib/api/server/command/brigadier/McPlayersArgumentResolver.kt create mode 100644 common-proxy/src/testFixtures/kotlin/su/plo/slib/proxy/command/UuidArgumentType.kt create mode 100644 common-server/src/testFixtures/kotlin/su/plo/slib/server/command/UuidArgumentType.kt create mode 100644 common/src/main/kotlin/su/plo/slib/command/brigadier/CustomArgumentCommandNode.kt delete mode 100644 minestom/src/main/resources/META-INF/services/su.plo.slib.api.server.command.brigadier.McArgumentResolver$Provider delete mode 100644 modded/src/main/resources/META-INF/services/su.plo.slib.api.server.command.brigadier.McArgumentResolver$Provider create mode 100644 spigot/src/main/kotlin/su/plo/slib/spigot/nms/EntitySelectorProxy.kt delete mode 100644 spigot/src/main/resources/META-INF/services/su.plo.slib.api.server.command.brigadier.McArgumentResolver$Provider rename spigot/src/main/resources/mappings/{1.21.6.tiny => 1.21.4.tiny} (57%) diff --git a/api/common/src/main/kotlin/su/plo/slib/api/command/brigadier/ArgumentResolver.kt b/api/common/src/main/kotlin/su/plo/slib/api/command/brigadier/ArgumentResolver.kt new file mode 100644 index 0000000..e78f1ae --- /dev/null +++ b/api/common/src/main/kotlin/su/plo/slib/api/command/brigadier/ArgumentResolver.kt @@ -0,0 +1,19 @@ +package su.plo.slib.api.command.brigadier + +/** + * Defers argument resolution until command execution. + * + * Used with [CustomArgumentType] to parse arguments into selectors at parse time, + * then resolve them to actual objects at execution time using the command source. + * + * @param T the resolved type + */ +fun interface ArgumentResolver { + /** + * Resolves the argument using the command [source]. + * + * @param source the command source + * @return the resolved value + */ + fun resolve(source: McBrigadierSource): T +} diff --git a/api/common/src/main/kotlin/su/plo/slib/api/command/brigadier/CustomArgumentType.kt b/api/common/src/main/kotlin/su/plo/slib/api/command/brigadier/CustomArgumentType.kt new file mode 100644 index 0000000..f93ed0e --- /dev/null +++ b/api/common/src/main/kotlin/su/plo/slib/api/command/brigadier/CustomArgumentType.kt @@ -0,0 +1,35 @@ +package su.plo.slib.api.command.brigadier + +import com.mojang.brigadier.arguments.ArgumentType +import org.jetbrains.annotations.ApiStatus + +/** + * An argument type that wraps a native argument type. + * + * The native type is sent to the client for client-side completions and syntax validation, + * while the server uses custom parsing logic to produce the parsed type. + * + * @param PARSED The custom type produced by server-side parsing + * @param NATIVE The native type sent to the client + */ +interface CustomArgumentType : ArgumentType { + /** + * The native argument type sent to the client. + */ + val nativeType: ArgumentType + + /** + * Whether native suggestions should be used. + * + * Set to `false` is you want to implement custom [listSuggestions]. + */ + fun useNativeSuggestions(): Boolean = + true + + /** + * This is controlled client-side and can't be changed server-side. + */ + @ApiStatus.NonExtendable + override fun getExamples(): Collection = + nativeType.examples +} diff --git a/api/server/src/main/kotlin/su/plo/slib/api/server/command/brigadier/McArgumentResolver.kt b/api/server/src/main/kotlin/su/plo/slib/api/server/command/brigadier/McArgumentResolver.kt deleted file mode 100644 index 7e75839..0000000 --- a/api/server/src/main/kotlin/su/plo/slib/api/server/command/brigadier/McArgumentResolver.kt +++ /dev/null @@ -1,66 +0,0 @@ -package su.plo.slib.api.server.command.brigadier - -import com.mojang.brigadier.context.CommandContext -import su.plo.slib.api.command.brigadier.McBrigadierSource -import su.plo.slib.api.server.entity.McServerEntity -import su.plo.slib.api.server.entity.player.McServerPlayer -import su.plo.slib.api.service.lazyService - -object McArgumentResolver { - - /** - * Extracts a single entity from a command context and converts it to [McServerEntity]. - * - * @param context The Brigadier command context. - * @param name The name of the argument. - * @return The selected entity. - */ - @JvmStatic - fun getEntity(context: CommandContext, name: String): McServerEntity = - provider.getEntity(context, name) - - /** - * Extracts multiple entities from a command context and converts them to [McServerEntity]. - * - * @param context The Brigadier command context. - * @param name The name of the argument. - * @return A collection of selected entities. - */ - @JvmStatic - fun getEntities(context: CommandContext, name: String): Collection = - provider.getEntities(context, name) - - /** - * Extracts a single player from a command context and converts it to [McServerPlayer]. - * - * @param context The Brigadier command context. - * @param name The name of the argument. - * @return The selected player. - */ - @JvmStatic - fun getPlayer(context: CommandContext, name: String): McServerPlayer = - provider.getPlayer(context, name) - - /** - * Extracts multiple players from a command context and converts them to [McServerPlayer]. - * - * @param context The Brigadier command context. - * @param name The name of the argument. - * @return A collection of selected players. - */ - @JvmStatic - fun getPlayers(context: CommandContext, name: String): Collection = - provider.getPlayers(context, name) - - private val provider: Provider by lazyService() - interface Provider { - - fun getEntity(context: CommandContext, name: String): McServerEntity - - fun getEntities(context: CommandContext, name: String): Collection - - fun getPlayer(context: CommandContext, name: String): McServerPlayer - - fun getPlayers(context: CommandContext, name: String): Collection - } -} diff --git a/api/server/src/main/kotlin/su/plo/slib/api/server/command/brigadier/McArgumentTypes.kt b/api/server/src/main/kotlin/su/plo/slib/api/server/command/brigadier/McArgumentTypes.kt index 9e58cd6..a675972 100644 --- a/api/server/src/main/kotlin/su/plo/slib/api/server/command/brigadier/McArgumentTypes.kt +++ b/api/server/src/main/kotlin/su/plo/slib/api/server/command/brigadier/McArgumentTypes.kt @@ -13,36 +13,36 @@ object McArgumentTypes { * Returns an argument type that selects a single entity. */ @JvmStatic - fun entity(): ArgumentType = provider.entity() + fun entity(): ArgumentType = provider.entity() /** * Returns an argument type that selects multiple entities. */ @JvmStatic - fun entities(): ArgumentType = provider.entities() + fun entities(): ArgumentType = provider.entities() /** * Returns an argument type that selects a single player. */ @JvmStatic - fun player(): ArgumentType = provider.player() + fun player(): ArgumentType = provider.player() /** * Returns an argument type that selects multiple players. */ @JvmStatic - fun players(): ArgumentType = provider.players() + fun players(): ArgumentType = provider.players() private val provider: Provider by lazyService() @ApiStatus.Internal interface Provider { - fun entity(): ArgumentType + fun entity(): ArgumentType - fun entities(): ArgumentType + fun entities(): ArgumentType - fun player(): ArgumentType + fun player(): ArgumentType - fun players(): ArgumentType + fun players(): ArgumentType } } diff --git a/api/server/src/main/kotlin/su/plo/slib/api/server/command/brigadier/McEntitiesArgumentResolver.kt b/api/server/src/main/kotlin/su/plo/slib/api/server/command/brigadier/McEntitiesArgumentResolver.kt new file mode 100644 index 0000000..b5eea69 --- /dev/null +++ b/api/server/src/main/kotlin/su/plo/slib/api/server/command/brigadier/McEntitiesArgumentResolver.kt @@ -0,0 +1,9 @@ +package su.plo.slib.api.server.command.brigadier + +import su.plo.slib.api.command.brigadier.ArgumentResolver +import su.plo.slib.api.server.entity.McServerEntity + +/** + * An [ArgumentResolver] that resolves multiple entities. + */ +fun interface McEntitiesArgumentResolver : ArgumentResolver> diff --git a/api/server/src/main/kotlin/su/plo/slib/api/server/command/brigadier/McEntityArgumentResolver.kt b/api/server/src/main/kotlin/su/plo/slib/api/server/command/brigadier/McEntityArgumentResolver.kt new file mode 100644 index 0000000..fe25ff6 --- /dev/null +++ b/api/server/src/main/kotlin/su/plo/slib/api/server/command/brigadier/McEntityArgumentResolver.kt @@ -0,0 +1,9 @@ +package su.plo.slib.api.server.command.brigadier + +import su.plo.slib.api.command.brigadier.ArgumentResolver +import su.plo.slib.api.server.entity.McServerEntity + +/** + * An [ArgumentResolver] that resolves a single entity. + */ +fun interface McEntityArgumentResolver : ArgumentResolver diff --git a/api/server/src/main/kotlin/su/plo/slib/api/server/command/brigadier/McPlayerArgumentResolver.kt b/api/server/src/main/kotlin/su/plo/slib/api/server/command/brigadier/McPlayerArgumentResolver.kt new file mode 100644 index 0000000..32e3af1 --- /dev/null +++ b/api/server/src/main/kotlin/su/plo/slib/api/server/command/brigadier/McPlayerArgumentResolver.kt @@ -0,0 +1,9 @@ +package su.plo.slib.api.server.command.brigadier + +import su.plo.slib.api.command.brigadier.ArgumentResolver +import su.plo.slib.api.server.entity.player.McServerPlayer + +/** + * An [ArgumentResolver] that resolves a single player. + */ +fun interface McPlayerArgumentResolver : ArgumentResolver diff --git a/api/server/src/main/kotlin/su/plo/slib/api/server/command/brigadier/McPlayersArgumentResolver.kt b/api/server/src/main/kotlin/su/plo/slib/api/server/command/brigadier/McPlayersArgumentResolver.kt new file mode 100644 index 0000000..f770825 --- /dev/null +++ b/api/server/src/main/kotlin/su/plo/slib/api/server/command/brigadier/McPlayersArgumentResolver.kt @@ -0,0 +1,9 @@ +package su.plo.slib.api.server.command.brigadier + +import su.plo.slib.api.command.brigadier.ArgumentResolver +import su.plo.slib.api.server.entity.player.McServerPlayer + +/** + * An [ArgumentResolver] that resolves multiple players. + */ +fun interface McPlayersArgumentResolver : ArgumentResolver> diff --git a/bungee/src/main/kotlin/su/plo/slib/bungee/command/BungeeCommandManager.kt b/bungee/src/main/kotlin/su/plo/slib/bungee/command/BungeeCommandManager.kt index 1d7a505..37253af 100644 --- a/bungee/src/main/kotlin/su/plo/slib/bungee/command/BungeeCommandManager.kt +++ b/bungee/src/main/kotlin/su/plo/slib/bungee/command/BungeeCommandManager.kt @@ -13,6 +13,7 @@ import su.plo.slib.api.proxy.event.command.McProxyCommandExecuteEvent import su.plo.slib.bungee.BungeeProxyLib import su.plo.slib.bungee.command.brigadier.BungeeBrigadierCommand import su.plo.slib.command.AbstractCommandManager +import su.plo.slib.command.proxied class BungeeCommandManager( private val minecraftProxy: BungeeProxyLib @@ -34,7 +35,10 @@ class BungeeCommandManager( } registerBrigadierCommands { command -> - proxyServer.pluginManager.registerCommand(plugin, BungeeBrigadierCommand(this, command)) + proxyServer.pluginManager.registerCommand( + plugin, + BungeeBrigadierCommand(this, command.proxied({ it }, { it })), + ) } registered = true diff --git a/bungee/src/main/kotlin/su/plo/slib/bungee/command/brigadier/BungeeBrigadierCommand.kt b/bungee/src/main/kotlin/su/plo/slib/bungee/command/brigadier/BungeeBrigadierCommand.kt index 71071ed..be648c6 100644 --- a/bungee/src/main/kotlin/su/plo/slib/bungee/command/brigadier/BungeeBrigadierCommand.kt +++ b/bungee/src/main/kotlin/su/plo/slib/bungee/command/brigadier/BungeeBrigadierCommand.kt @@ -1,8 +1,8 @@ package su.plo.slib.bungee.command.brigadier import com.mojang.brigadier.CommandDispatcher -import com.mojang.brigadier.builder.LiteralArgumentBuilder import com.mojang.brigadier.exceptions.CommandSyntaxException +import com.mojang.brigadier.tree.LiteralCommandNode import net.md_5.bungee.api.CommandSender import net.md_5.bungee.api.plugin.Command import net.md_5.bungee.api.plugin.TabExecutor @@ -13,12 +13,12 @@ import su.plo.slib.bungee.command.BungeeCommandManager class BungeeBrigadierCommand( private val commandManager: BungeeCommandManager, - private val command: LiteralArgumentBuilder, + private val command: LiteralCommandNode, ) : Command(command.literal), TabExecutor { private val dispatcher = CommandDispatcher() init { - dispatcher.register(command) + dispatcher.root.addChild(command) } override fun execute(sender: CommandSender, arguments: Array) { diff --git a/common-proxy/src/testFixtures/kotlin/su/plo/slib/proxy/TestProxy.kt b/common-proxy/src/testFixtures/kotlin/su/plo/slib/proxy/TestProxy.kt index 3ccc46b..b668fcb 100644 --- a/common-proxy/src/testFixtures/kotlin/su/plo/slib/proxy/TestProxy.kt +++ b/common-proxy/src/testFixtures/kotlin/su/plo/slib/proxy/TestProxy.kt @@ -10,6 +10,8 @@ import su.plo.slib.api.event.player.McPlayerQuitEvent import su.plo.slib.api.logging.McLoggerFactory import su.plo.slib.api.proxy.command.McProxyCommand import su.plo.slib.api.proxy.event.command.McProxyCommandsRegisterEvent +import su.plo.slib.proxy.command.UuidArgumentType +import java.util.UUID class TestProxy { private var logger = McLoggerFactory.createLogger("TestProxy") @@ -42,6 +44,20 @@ class TestProxy { Command.SINGLE_SUCCESS } ) + + commands.register( + McCommandManager.literal("brigadier-custom-type") + .then( + McCommandManager.argument("uuid", UuidArgumentType()) + .executes { + val uuid = it.getArgument("uuid", UUID::class.java) + + it.source.source.sendMessage(uuid.toString()) + + Command.SINGLE_SUCCESS + } + ) + ) } } } diff --git a/common-proxy/src/testFixtures/kotlin/su/plo/slib/proxy/command/UuidArgumentType.kt b/common-proxy/src/testFixtures/kotlin/su/plo/slib/proxy/command/UuidArgumentType.kt new file mode 100644 index 0000000..89b919d --- /dev/null +++ b/common-proxy/src/testFixtures/kotlin/su/plo/slib/proxy/command/UuidArgumentType.kt @@ -0,0 +1,40 @@ +package su.plo.slib.proxy.command + +import com.mojang.brigadier.LiteralMessage +import com.mojang.brigadier.StringReader +import com.mojang.brigadier.arguments.ArgumentType +import com.mojang.brigadier.arguments.StringArgumentType +import com.mojang.brigadier.context.CommandContext +import com.mojang.brigadier.exceptions.CommandSyntaxException +import com.mojang.brigadier.exceptions.SimpleCommandExceptionType +import com.mojang.brigadier.suggestion.Suggestions +import com.mojang.brigadier.suggestion.SuggestionsBuilder +import su.plo.slib.api.command.brigadier.CustomArgumentType +import java.util.UUID +import java.util.concurrent.CompletableFuture + +class UuidArgumentType : CustomArgumentType { + override val nativeType: ArgumentType = StringArgumentType.string() + + private val invalidUuid = SimpleCommandExceptionType(LiteralMessage("Failed to parse UUID")) + + override fun parse(reader: StringReader): UUID? { + val input = reader.readString() + ?.takeIf { it.isNotBlank() } + ?: return null + + try { + return UUID.fromString(input) + } catch (e: IllegalArgumentException) { + throw CommandSyntaxException(invalidUuid, LiteralMessage(e.message), input, 0) + } + } + + override fun listSuggestions( + context: CommandContext, + builder: SuggestionsBuilder, + ): CompletableFuture = + builder + .suggest(UUID.randomUUID().toString()) + .buildFuture() +} diff --git a/common-server/src/testFixtures/kotlin/su/plo/slib/server/TestServer.kt b/common-server/src/testFixtures/kotlin/su/plo/slib/server/TestServer.kt index 62ed148..87ff3f8 100644 --- a/common-server/src/testFixtures/kotlin/su/plo/slib/server/TestServer.kt +++ b/common-server/src/testFixtures/kotlin/su/plo/slib/server/TestServer.kt @@ -8,10 +8,15 @@ import su.plo.slib.api.event.player.McPlayerJoinEvent import su.plo.slib.api.event.player.McPlayerQuitEvent import su.plo.slib.api.logging.McLoggerFactory import su.plo.slib.api.server.McServerLib -import su.plo.slib.api.server.command.brigadier.McArgumentResolver import su.plo.slib.api.server.command.brigadier.McArgumentTypes +import su.plo.slib.api.server.command.brigadier.McEntitiesArgumentResolver +import su.plo.slib.api.server.command.brigadier.McEntityArgumentResolver +import su.plo.slib.api.server.command.brigadier.McPlayerArgumentResolver +import su.plo.slib.api.server.command.brigadier.McPlayersArgumentResolver import su.plo.slib.api.server.event.command.McServerCommandsRegisterEvent import su.plo.slib.api.server.event.player.McPlayerRegisterChannelsEvent +import su.plo.slib.server.command.UuidArgumentType +import java.util.UUID class TestServer( val minecraftServer: McServerLib, @@ -46,6 +51,20 @@ class TestServer( }, ) + commands.register( + McCommandManager.literal("brigadier-custom-type") + .then( + McCommandManager.argument("uuid", UuidArgumentType()) + .executes { + val uuid = it.getArgument("uuid", UUID::class.java) + + it.source.source.sendMessage(uuid.toString()) + + Command.SINGLE_SUCCESS + } + ) + ) + commands.register( McCommandManager.literal("brigadier-entity-selector") .then( @@ -53,7 +72,11 @@ class TestServer( .then( McCommandManager.argument("target", McArgumentTypes.entity()) .executes { - val entity = McArgumentResolver.getEntity(it, "target") + val resolver = it.getArgument( + "target", + McEntityArgumentResolver::class.java, + ) + val entity = resolver.resolve(it.source) val source = it.source source.source.sendMessage("Found entity: $entity; Source: ${source.source}; Executor: ${source.executor}") @@ -67,7 +90,11 @@ class TestServer( .then( McCommandManager.argument("target", McArgumentTypes.entities()) .executes { - val entities = McArgumentResolver.getEntities(it, "target") + val resolver = it.getArgument( + "target", + McEntitiesArgumentResolver::class.java, + ) + val entities = resolver.resolve(it.source) val source = it.source source.source.sendMessage("Found entities: $entities; Source: ${source.source}; Executor: ${source.executor}") @@ -81,7 +108,11 @@ class TestServer( .then( McCommandManager.argument("target", McArgumentTypes.player()) .executes { - val player = McArgumentResolver.getPlayer(it, "target") + val resolver = it.getArgument( + "target", + McPlayerArgumentResolver::class.java, + ) + val player = resolver.resolve(it.source) val source = it.source source.source.sendMessage("Found player: $player; Source: ${source.source}; Executor: ${source.executor}") @@ -95,7 +126,11 @@ class TestServer( .then( McCommandManager.argument("target", McArgumentTypes.players()) .executes { - val players = McArgumentResolver.getPlayers(it, "target") + val resolver = it.getArgument( + "target", + McPlayersArgumentResolver::class.java, + ) + val players = resolver.resolve(it.source) val source = it.source source.source.sendMessage("Found players: $players; Source: ${source.source}; Executor: ${source.executor}") diff --git a/common-server/src/testFixtures/kotlin/su/plo/slib/server/command/UuidArgumentType.kt b/common-server/src/testFixtures/kotlin/su/plo/slib/server/command/UuidArgumentType.kt new file mode 100644 index 0000000..7b9e6f0 --- /dev/null +++ b/common-server/src/testFixtures/kotlin/su/plo/slib/server/command/UuidArgumentType.kt @@ -0,0 +1,39 @@ +package su.plo.slib.server.command + +import com.mojang.brigadier.LiteralMessage +import com.mojang.brigadier.StringReader +import com.mojang.brigadier.arguments.ArgumentType +import com.mojang.brigadier.arguments.StringArgumentType +import com.mojang.brigadier.context.CommandContext +import com.mojang.brigadier.exceptions.CommandSyntaxException +import com.mojang.brigadier.exceptions.SimpleCommandExceptionType +import com.mojang.brigadier.suggestion.Suggestions +import com.mojang.brigadier.suggestion.SuggestionsBuilder +import su.plo.slib.api.command.brigadier.CustomArgumentType +import java.util.UUID +import java.util.concurrent.CompletableFuture + +class UuidArgumentType : CustomArgumentType { + override val nativeType: ArgumentType = StringArgumentType.string() + + override fun useNativeSuggestions(): Boolean = false + + private val invalidUuid = SimpleCommandExceptionType(LiteralMessage("Failed to parse UUID")) + + override fun parse(reader: StringReader): UUID? { + val input = reader.readString() + ?.takeIf { it.isNotBlank() } + ?: return null + + try { + return UUID.fromString(input) + } catch (e: IllegalArgumentException) { + throw CommandSyntaxException(invalidUuid, LiteralMessage(e.message), input, 0) + } + } + + override fun listSuggestions( + context: CommandContext, + builder: SuggestionsBuilder, + ): CompletableFuture = Suggestions.empty() +} diff --git a/common/src/main/kotlin/su/plo/slib/command/AbstractCommandManager.kt b/common/src/main/kotlin/su/plo/slib/command/AbstractCommandManager.kt index 729c972..53bb045 100644 --- a/common/src/main/kotlin/su/plo/slib/command/AbstractCommandManager.kt +++ b/common/src/main/kotlin/su/plo/slib/command/AbstractCommandManager.kt @@ -5,7 +5,6 @@ import com.google.common.collect.ImmutableMap import com.google.common.collect.Maps import com.mojang.brigadier.RedirectModifier 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.context.CommandContext @@ -14,8 +13,10 @@ import com.mojang.brigadier.tree.CommandNode import com.mojang.brigadier.tree.LiteralCommandNode import su.plo.slib.api.command.McCommand import su.plo.slib.api.command.McCommandManager +import su.plo.slib.api.command.brigadier.CustomArgumentType import su.plo.slib.api.command.brigadier.McBrigadierSource import su.plo.slib.api.logging.McLoggerFactory +import su.plo.slib.command.brigadier.buildCustom abstract class AbstractCommandManager : McCommandManager() { private val logger = McLoggerFactory.createLogger("CommandManager") @@ -81,21 +82,21 @@ abstract class AbstractCommandManager : McCommandManager() { fun CommandContext.copyFor(source: T): CommandContext = (this as CommandContext).copyFor(source) -fun LiteralArgumentBuilder.proxied( - sourceFactory: (T) -> McBrigadierSource, - contextFactory: (CommandContext) -> CommandContext, -): LiteralArgumentBuilder = - build().toProxyNode(sourceFactory, contextFactory) as LiteralArgumentBuilder +fun LiteralArgumentBuilder.proxied( + sourceFactory: (S) -> McBrigadierSource, + contextFactory: (CommandContext) -> CommandContext, +): LiteralCommandNode = + build().toProxyNode(sourceFactory, contextFactory) as LiteralCommandNode -fun CommandNode.toProxyNode( - sourceFactory: (T) -> McBrigadierSource, - contextFactory: (CommandContext) -> CommandContext, -): ArgumentBuilder { +fun CommandNode.toProxyNode( + sourceFactory: (S) -> McBrigadierSource, + contextFactory: (CommandContext) -> CommandContext, +): CommandNode { val node = when (this) { - is LiteralCommandNode -> LiteralArgumentBuilder.literal(literal) + is LiteralCommandNode -> LiteralArgumentBuilder.literal(literal) is ArgumentCommandNode -> - RequiredArgumentBuilder.argument(name, type as ArgumentType) + RequiredArgumentBuilder.argument(name, type as ArgumentType) else -> throw IllegalArgumentException("Unsupported command node: $this") } @@ -103,10 +104,10 @@ fun CommandNode.toProxyNode( val modifier = redirectModifier if (modifier == null) { - node.redirect(redirect.toProxyNode(sourceFactory, contextFactory).build()) + node.redirect(redirect.toProxyNode(sourceFactory, contextFactory)) } else { - val proxiedModifier = object : RedirectModifier { - override fun apply(context: CommandContext): Collection { + val proxiedModifier = object : RedirectModifier { + override fun apply(context: CommandContext): Collection { val context = contextFactory(context) return modifier.apply(context).map { it.getInstance() } @@ -114,14 +115,14 @@ fun CommandNode.toProxyNode( } node.fork( - redirect.toProxyNode(sourceFactory, contextFactory).build(), + redirect.toProxyNode(sourceFactory, contextFactory), proxiedModifier, ) } } children - .map { it.toProxyNode(sourceFactory, contextFactory) } + .map { it.toProxyNode(sourceFactory, contextFactory) } .forEach { node.then(it) } requirement?.let { requirement -> @@ -135,9 +136,19 @@ fun CommandNode.toProxyNode( node.executes { context -> val context = contextFactory(context) - command.run(context) + try { + command.run(context) + } catch (e: Throwable) { + e.printStackTrace() + throw e + } } } - return node + if (node is RequiredArgumentBuilder && node.type is CustomArgumentType<*, *>) { + @Suppress("UNCHECKED_CAST") + return (node as RequiredArgumentBuilder).buildCustom() + } + + return node.build() } diff --git a/common/src/main/kotlin/su/plo/slib/command/brigadier/CustomArgumentCommandNode.kt b/common/src/main/kotlin/su/plo/slib/command/brigadier/CustomArgumentCommandNode.kt new file mode 100644 index 0000000..b2f463e --- /dev/null +++ b/common/src/main/kotlin/su/plo/slib/command/brigadier/CustomArgumentCommandNode.kt @@ -0,0 +1,78 @@ +package su.plo.slib.command.brigadier + +import com.mojang.brigadier.Command +import com.mojang.brigadier.RedirectModifier +import com.mojang.brigadier.StringReader +import com.mojang.brigadier.builder.RequiredArgumentBuilder +import com.mojang.brigadier.context.CommandContext +import com.mojang.brigadier.context.CommandContextBuilder +import com.mojang.brigadier.context.ParsedArgument +import com.mojang.brigadier.suggestion.SuggestionProvider +import com.mojang.brigadier.suggestion.Suggestions +import com.mojang.brigadier.suggestion.SuggestionsBuilder +import com.mojang.brigadier.tree.ArgumentCommandNode +import com.mojang.brigadier.tree.CommandNode +import su.plo.slib.api.command.brigadier.CustomArgumentType +import java.util.concurrent.CompletableFuture +import java.util.function.Predicate + +class CustomArgumentCommandNode( + name: String, + val customArgumentType: CustomArgumentType, + command: Command, + requirement: Predicate, + redirect: CommandNode?, + modifier: RedirectModifier?, + forks: Boolean, + customSuggestions: SuggestionProvider?, +) : ArgumentCommandNode( + name, + customArgumentType.nativeType, + command, + requirement, + redirect, + modifier, + forks, + customSuggestions, +) { + override fun parse(reader: StringReader, contextBuilder: CommandContextBuilder) { + val start = reader.cursor + val result = customArgumentType.parse(reader) + + val parsed = ParsedArgument(start, reader.cursor, result) + + contextBuilder.withArgument(name, parsed) + contextBuilder.withNode(this, parsed.range) + } +} + +fun RequiredArgumentBuilder.buildCustom(): CustomArgumentCommandNode { + @Suppress("UNCHECKED_CAST") + val type = type as CustomArgumentType + + val result = CustomArgumentCommandNode( + name, + type, + command, + requirement, + redirect, + redirectModifier, + isFork, + suggestionsProvider ?: + if (!type.useNativeSuggestions()) { + object : SuggestionProvider { + override fun getSuggestions( + context: CommandContext, + builder: SuggestionsBuilder, + ): CompletableFuture = + type.listSuggestions(context, builder) + } + } else { + null + }, + ) + + arguments.forEach { result.addChild(it) } + + return result +} diff --git a/minestom/build.gradle.kts b/minestom/build.gradle.kts index 2c0e581..a8b62f5 100644 --- a/minestom/build.gradle.kts +++ b/minestom/build.gradle.kts @@ -20,8 +20,13 @@ dependencies { testImplementation(libs.minestom) testImplementation(testFixtures(project(":common-server"))) + // todo: catalog testImplementation("org.apache.logging.log4j:log4j-core:2.25.3") testImplementation("org.slf4j:slf4j-log4j12:2.0.17") + + testImplementation("org.jline:jline-reader:3.21.0") + testImplementation("org.jline:jline-terminal:3.21.0") + testImplementation("net.minecrell:terminalconsoleappender:1.3.0") } tasks { @@ -32,6 +37,8 @@ tasks { register("runServer") { group = "application" + standardInput = System.`in` + workingDir = layout.projectDirectory.dir("run").asFile doFirst { diff --git a/minestom/src/main/kotlin/su/plo/slib/minestom/command/MinestomCommandManager.kt b/minestom/src/main/kotlin/su/plo/slib/minestom/command/MinestomCommandManager.kt index c77c327..537b5d6 100644 --- a/minestom/src/main/kotlin/su/plo/slib/minestom/command/MinestomCommandManager.kt +++ b/minestom/src/main/kotlin/su/plo/slib/minestom/command/MinestomCommandManager.kt @@ -1,5 +1,7 @@ package su.plo.slib.minestom.command +import com.mojang.brigadier.StringReader +import com.mojang.brigadier.arguments.ArgumentType import com.mojang.brigadier.arguments.BoolArgumentType import com.mojang.brigadier.arguments.DoubleArgumentType import com.mojang.brigadier.arguments.FloatArgumentType @@ -10,29 +12,35 @@ import com.mojang.brigadier.context.CommandContext import com.mojang.brigadier.context.ParsedArgument import com.mojang.brigadier.context.StringRange import com.mojang.brigadier.exceptions.CommandSyntaxException +import com.mojang.brigadier.suggestion.SuggestionsBuilder import com.mojang.brigadier.tree.ArgumentCommandNode import com.mojang.brigadier.tree.LiteralCommandNode +import net.kyori.adventure.text.Component import net.minestom.server.MinecraftServer +import net.minestom.server.command.ArgumentParserType import net.minestom.server.command.CommandSender import net.minestom.server.command.builder.Command import net.minestom.server.command.builder.CommandExecutor import net.minestom.server.command.builder.arguments.Argument -import net.minestom.server.command.builder.arguments.ArgumentBoolean -import net.minestom.server.command.builder.arguments.ArgumentString -import net.minestom.server.command.builder.arguments.ArgumentWord -import net.minestom.server.command.builder.arguments.number.ArgumentDouble -import net.minestom.server.command.builder.arguments.number.ArgumentFloat -import net.minestom.server.command.builder.arguments.number.ArgumentInteger -import net.minestom.server.command.builder.arguments.number.ArgumentLong +import net.minestom.server.command.builder.arguments.minecraft.SuggestionType +import net.minestom.server.command.builder.exception.ArgumentSyntaxException +import net.minestom.server.command.builder.suggestion.Suggestion +import net.minestom.server.command.builder.suggestion.SuggestionCallback +import net.minestom.server.command.builder.suggestion.SuggestionEntry import net.minestom.server.entity.Player +import net.minestom.server.network.NetworkBuffer import su.plo.slib.api.chat.component.McTextComponent import su.plo.slib.api.chat.style.McTextStyle import su.plo.slib.api.command.McCommand import su.plo.slib.api.command.McCommandSource +import su.plo.slib.api.command.brigadier.CustomArgumentType +import su.plo.slib.api.command.brigadier.McBrigadierSource import su.plo.slib.api.entity.McEntity import su.plo.slib.api.server.McServerLib import su.plo.slib.api.server.event.command.McServerCommandsRegisterEvent import su.plo.slib.command.AbstractCommandManager +import su.plo.slib.command.brigadier.CustomArgumentCommandNode +import su.plo.slib.command.proxied import su.plo.slib.minestom.command.brigadier.MinestomArgumentType import su.plo.slib.minestom.command.brigadier.MinestomBrigadierSource @@ -60,7 +68,12 @@ class MinestomCommandManager( } registerBrigadierCommands { command -> - MinecraftServer.getCommandManager().register(command.build().toMinestom()) + MinecraftServer.getCommandManager().register( + command.proxied( + { it }, + { it }, + ).toMinestom() + ) } registered = true @@ -73,16 +86,16 @@ class MinestomCommandManager( else MinestomDefaultCommandSource(minecraftServer.textConverter, source) } - private fun LiteralCommandNode<*>.toMinestom(): Command { + private fun LiteralCommandNode.toMinestom(): Command { val minestomCommand = Command(name) - val commands = children.filterIsInstance>() + val commands = children.filterIsInstance>() .map { it.toMinestom() } commands.forEach { minestomCommand.addSubcommand(it) } val executor = command?.toMinestom() ?: noopCommandExecutor() - val arguments = children.filterIsInstance>() + val arguments = children.filterIsInstance>() .map { it.toMinestom() } arguments.forEach {(argument, argumentExecutor) -> minestomCommand.addSyntax(argumentExecutor ?: executor, argument) @@ -102,28 +115,72 @@ class MinestomCommandManager( } } - private fun ArgumentCommandNode<*, *>.toMinestom(): Pair, CommandExecutor?> { + private fun ArgumentType<*>.toMinestomParserType(): ArgumentParserType = + when (this) { + is BoolArgumentType -> ArgumentParserType.BOOL + is DoubleArgumentType -> ArgumentParserType.DOUBLE + is FloatArgumentType -> ArgumentParserType.FLOAT + is IntegerArgumentType -> ArgumentParserType.INTEGER + is LongArgumentType -> ArgumentParserType.LONG + is StringArgumentType -> ArgumentParserType.STRING + is CustomArgumentType<*, *> -> nativeType.toMinestomParserType() + else -> throw IllegalArgumentException("Invalid argument type: $this") + } + + private fun ArgumentCommandNode.toMinestom(): Pair, CommandExecutor?> { val argumentType = type - val argument = when (argumentType) { - is BoolArgumentType -> ArgumentBoolean(name) - is DoubleArgumentType -> ArgumentDouble(name) - is FloatArgumentType -> ArgumentFloat(name) - is IntegerArgumentType -> ArgumentInteger(name) - is LongArgumentType -> ArgumentLong(name) - is StringArgumentType -> - when (argumentType.type) { - StringArgumentType.StringType.GREEDY_PHRASE -> ArgumentString(name) - StringArgumentType.StringType.SINGLE_WORD -> ArgumentWord(name) - StringArgumentType.StringType.QUOTABLE_PHRASE -> ArgumentString(name) + val executor = command?.toMinestom() + + if (argumentType is MinestomArgumentType) { + return argumentType.argumentBuilder.invoke(name) to executor + } + + val minestomArgument = object : Argument(name) { + @Suppress("UNCHECKED_CAST") + override fun parse(sender: CommandSender, input: String): T = + try { + if (this@toMinestom is CustomArgumentCommandNode<*, *, *>) { + (customArgumentType.parse(StringReader(input)) as T) + } else { + argumentType.parse(StringReader(input)) + } + } catch (e: CommandSyntaxException) { + throw ArgumentSyntaxException(e.message, input, -1) + } + + override fun parser(): ArgumentParserType = + argumentType.toMinestomParserType() + + override fun nodeProperties(): ByteArray? { + if (argumentType is StringArgumentType) { + return NetworkBuffer.makeArray(NetworkBuffer.VAR_INT, argumentType.type.ordinal) + } + + return super.nodeProperties() + } + } + + minestomArgument.suggestionCallback = object : SuggestionCallback { + override fun apply( + sender: CommandSender, + context: net.minestom.server.command.builder.CommandContext, + suggestion: Suggestion, + ) { + val brigadierContext = context.toBrigadier(sender, command) + val suggestions = listSuggestions(brigadierContext, SuggestionsBuilder(context.input, 0)).get() + + suggestions.list.forEach { + suggestion.addEntry( + SuggestionEntry(it.text, it.tooltip?.string?.let(Component::text)) + ) } - else -> (type as MinestomArgumentType<*>).argumentBuilder(name) + } } - val executor = command?.toMinestom() - return argument to executor + return minestomArgument to executor } - private fun com.mojang.brigadier.Command<*>.toMinestom(): CommandExecutor = + private fun com.mojang.brigadier.Command.toMinestom(): CommandExecutor = object : CommandExecutor { override fun apply( sender: CommandSender, @@ -131,19 +188,7 @@ class MinestomCommandManager( ) { val source = getCommandSource(sender) - @Suppress("UNCHECKED_CAST") - val brigadierContext = CommandContext( - MinestomBrigadierSource(source, source as? McEntity, sender), - context.input, - context.map.mapValues { ParsedArgument(0, 0, it.value) }, - this@toMinestom as com.mojang.brigadier.Command, - null, - emptyList(), - StringRange(0, 0), - null, - null, - false, - ) + val brigadierContext = context.toBrigadier(sender, this@toMinestom) try { this@toMinestom.run(brigadierContext) @@ -163,4 +208,24 @@ class MinestomCommandManager( } } } + + private fun net.minestom.server.command.builder.CommandContext.toBrigadier( + sender: CommandSender, + command: com.mojang.brigadier.Command?, + ): CommandContext { + val source = getCommandSource(sender) + + return CommandContext( + MinestomBrigadierSource(source, source as? McEntity, sender), + input, + map.mapValues { ParsedArgument(0, 0, it.value) }, + command, + null, + emptyList(), + StringRange(0, 0), + null, + null, + false, + ) + } } diff --git a/minestom/src/main/kotlin/su/plo/slib/minestom/command/brigadier/MinestomEntityArguments.kt b/minestom/src/main/kotlin/su/plo/slib/minestom/command/brigadier/MinestomEntityArguments.kt index ff5fb54..8445513 100644 --- a/minestom/src/main/kotlin/su/plo/slib/minestom/command/brigadier/MinestomEntityArguments.kt +++ b/minestom/src/main/kotlin/su/plo/slib/minestom/command/brigadier/MinestomEntityArguments.kt @@ -1,81 +1,67 @@ package su.plo.slib.minestom.command.brigadier import com.mojang.brigadier.arguments.ArgumentType -import com.mojang.brigadier.context.CommandContext import net.minestom.server.command.builder.arguments.minecraft.ArgumentEntity import net.minestom.server.entity.Player import net.minestom.server.utils.entity.EntityFinder -import su.plo.slib.api.command.brigadier.McBrigadierSource -import su.plo.slib.api.server.command.brigadier.McArgumentResolver import su.plo.slib.api.server.command.brigadier.McArgumentTypes -import su.plo.slib.api.server.entity.McServerEntity -import su.plo.slib.api.server.entity.player.McServerPlayer +import su.plo.slib.api.server.command.brigadier.McEntitiesArgumentResolver +import su.plo.slib.api.server.command.brigadier.McEntityArgumentResolver +import su.plo.slib.api.server.command.brigadier.McPlayerArgumentResolver +import su.plo.slib.api.server.command.brigadier.McPlayersArgumentResolver import su.plo.slib.minestom.MinestomServerLib -class MinestomEntityArguments : McArgumentTypes.Provider, McArgumentResolver.Provider { - - private val serverLib by lazy { - MinestomServerLib.instance - } - - @Suppress("UNCHECKED_CAST") - override fun entity(): ArgumentType = - MinestomArgumentType { name -> - ArgumentEntity(name).singleEntity(true).onlyPlayers(false) - } as ArgumentType - - @Suppress("UNCHECKED_CAST") - override fun entities(): ArgumentType = - MinestomArgumentType { name -> - ArgumentEntity(name).singleEntity(false).onlyPlayers(false) - } as ArgumentType - - @Suppress("UNCHECKED_CAST") - override fun player(): ArgumentType = - MinestomArgumentType { name -> - ArgumentEntity(name).singleEntity(true).onlyPlayers(true) - } as ArgumentType - - @Suppress("UNCHECKED_CAST") - override fun players(): ArgumentType = +class MinestomEntityArguments : McArgumentTypes.Provider { + private val serverLib by lazy { MinestomServerLib.instance } + + override fun entity(): ArgumentType = + argumentResolver( + MinestomArgumentType { name -> ArgumentEntity(name).singleEntity(true).onlyPlayers(false) } + ) { finder -> + McEntityArgumentResolver { source -> + val entity = finder.findFirstEntity((source as MinestomBrigadierSource).getInstance()) + ?: throw IllegalArgumentException("No entity found") + serverLib.getEntityByInstance(entity) + } + } + + override fun entities(): ArgumentType = + argumentResolver( + MinestomArgumentType { name -> ArgumentEntity(name).singleEntity(false).onlyPlayers(false) } + ) { finder -> + McEntitiesArgumentResolver { source -> + finder.find((source as MinestomBrigadierSource).getInstance()) + .map { serverLib.getEntityByInstance(it) } + } + } + + override fun player(): ArgumentType = + argumentResolver( + MinestomArgumentType { name -> ArgumentEntity(name).singleEntity(true).onlyPlayers(true) } + ) { finder -> + McPlayerArgumentResolver { source -> + val player = finder.findFirstPlayer((source as MinestomBrigadierSource).getInstance()) + ?: throw IllegalArgumentException("No player found") + serverLib.getPlayerByInstance(player) + } + } + + override fun players(): ArgumentType = + argumentResolver( + MinestomArgumentType { name -> ArgumentEntity(name).singleEntity(false).onlyPlayers(true) } + ) { finder -> + McPlayersArgumentResolver { source -> + finder.find((source as MinestomBrigadierSource).getInstance()) + .filterIsInstance() + .map { serverLib.getPlayerByInstance(it) } + } + } + + private fun argumentResolver( + finderArgumentType: MinestomArgumentType, + resolverFactory: (EntityFinder) -> T, + ): ArgumentType = MinestomArgumentType { name -> - ArgumentEntity(name).singleEntity(false).onlyPlayers(true) - } as ArgumentType - - override fun getEntity(context: CommandContext, name: String): McServerEntity { - val finder = context.getArgument(name, EntityFinder::class.java) - val brigadierContext = context.source as MinestomBrigadierSource - - val entity = finder.findFirstEntity(brigadierContext.getInstance()) - ?: throw IllegalArgumentException("No entity found") - - return serverLib.getEntityByInstance(entity) - - } - - override fun getEntities(context: CommandContext, name: String): Collection { - val finder = context.getArgument(name, EntityFinder::class.java) - val brigadierContext = context.source as MinestomBrigadierSource - - return finder.find(brigadierContext.getInstance()).map { serverLib.getEntityByInstance(it) } - } - - override fun getPlayer(context: CommandContext, name: String): McServerPlayer { - val finder = context.getArgument(name, EntityFinder::class.java) - val brigadierContext = context.source as MinestomBrigadierSource - - val player = finder.findFirstPlayer(brigadierContext.getInstance()) - ?: throw IllegalArgumentException("No player found") - - return serverLib.getPlayerByInstance(player) - } - - override fun getPlayers(context: CommandContext, name: String): Collection { - val finder = context.getArgument(name, EntityFinder::class.java) - val brigadierContext = context.source as MinestomBrigadierSource - - return finder.find(brigadierContext.getInstance()) - .filterIsInstance() - .map { serverLib.getPlayerByInstance(it) } - } + finderArgumentType.argumentBuilder(name).map { resolverFactory(it) } + } } diff --git a/minestom/src/main/resources/META-INF/services/su.plo.slib.api.server.command.brigadier.McArgumentResolver$Provider b/minestom/src/main/resources/META-INF/services/su.plo.slib.api.server.command.brigadier.McArgumentResolver$Provider deleted file mode 100644 index 4896f87..0000000 --- a/minestom/src/main/resources/META-INF/services/su.plo.slib.api.server.command.brigadier.McArgumentResolver$Provider +++ /dev/null @@ -1 +0,0 @@ -su.plo.slib.minestom.command.brigadier.MinestomEntityArguments diff --git a/minestom/src/test/kotlin/su/plo/slib/minestom/TestMinestomServer.kt b/minestom/src/test/kotlin/su/plo/slib/minestom/TestMinestomServer.kt index aa7b22f..cbf789d 100644 --- a/minestom/src/test/kotlin/su/plo/slib/minestom/TestMinestomServer.kt +++ b/minestom/src/test/kotlin/su/plo/slib/minestom/TestMinestomServer.kt @@ -1,5 +1,6 @@ package su.plo.slib.minestom +import net.minecrell.terminalconsole.SimpleTerminalConsole import net.minestom.server.MinecraftServer import net.minestom.server.coordinate.Pos import net.minestom.server.entity.GameMode @@ -7,12 +8,12 @@ import net.minestom.server.event.Event import net.minestom.server.event.GlobalEventHandler import net.minestom.server.event.player.AsyncPlayerConfigurationEvent import net.minestom.server.event.player.PlayerSpawnEvent -import su.plo.slib.api.logging.McLoggerFactory import su.plo.slib.server.TestServer import java.io.File +import kotlin.system.exitProcess import kotlin.time.measureTime -private val logger = McLoggerFactory.createLogger("Server") +private val logger = MinecraftServer.LOGGER fun main() { val startTime = measureTime { startServer() } @@ -45,6 +46,35 @@ fun startServer() { } minecraftServer.start("0.0.0.0", 25565) + + Console().start() +} + +class Console : SimpleTerminalConsole() { + private val commandManager = MinecraftServer.getCommandManager() + + override fun isRunning(): Boolean = + MinecraftServer.isStarted() && !MinecraftServer.isStopping() + + override fun runCommand(command: String) { + try { + commandManager.execute(commandManager.consoleSender, command) + } catch (e: Throwable) { + logger.error("Failed to execute command: /$command", e) + } + } + + override fun shutdown() { + logger.info("Shutting down...") + try { + MinecraftServer.stopCleanly() + exitProcess(0) + } catch (e: Throwable) { + logger.error("An error occurred while shutting down", e) + exitProcess(1) + } + } + } private inline fun GlobalEventHandler.addListener(crossinline listener: (T) -> Unit) { diff --git a/modded/src/main/kotlin/su/plo/slib/mod/command/ModCommandManager.kt b/modded/src/main/kotlin/su/plo/slib/mod/command/ModCommandManager.kt index 39c523d..001beeb 100644 --- a/modded/src/main/kotlin/su/plo/slib/mod/command/ModCommandManager.kt +++ b/modded/src/main/kotlin/su/plo/slib/mod/command/ModCommandManager.kt @@ -31,7 +31,7 @@ class ModCommandManager( @Suppress("UNCHECKED_CAST") registerBrigadierCommands { command -> - dispatcher.register( + dispatcher.root.addChild( command.proxied( ModBrigadierSource::from, { it.toMc() }, diff --git a/modded/src/main/kotlin/su/plo/slib/mod/command/brigadier/ModBrigadierArguments.kt b/modded/src/main/kotlin/su/plo/slib/mod/command/brigadier/ModBrigadierArguments.kt index 352476b..74f2641 100644 --- a/modded/src/main/kotlin/su/plo/slib/mod/command/brigadier/ModBrigadierArguments.kt +++ b/modded/src/main/kotlin/su/plo/slib/mod/command/brigadier/ModBrigadierArguments.kt @@ -1,58 +1,55 @@ package su.plo.slib.mod.command.brigadier +import com.mojang.brigadier.StringReader import com.mojang.brigadier.arguments.ArgumentType -import com.mojang.brigadier.context.CommandContext import net.minecraft.commands.arguments.EntityArgument -import su.plo.slib.api.command.brigadier.McBrigadierSource -import su.plo.slib.api.server.command.brigadier.McArgumentResolver +import net.minecraft.commands.arguments.selector.EntitySelector +import su.plo.slib.api.command.brigadier.CustomArgumentType import su.plo.slib.api.server.command.brigadier.McArgumentTypes -import su.plo.slib.api.server.entity.McServerEntity -import su.plo.slib.api.server.entity.player.McServerPlayer +import su.plo.slib.api.server.command.brigadier.McEntitiesArgumentResolver +import su.plo.slib.api.server.command.brigadier.McEntityArgumentResolver +import su.plo.slib.api.server.command.brigadier.McPlayerArgumentResolver +import su.plo.slib.api.server.command.brigadier.McPlayersArgumentResolver import su.plo.slib.mod.ModServerLib -import su.plo.slib.mod.command.toSourceStack - -@Suppress("UNCHECKED_CAST") -class ModBrigadierArguments : McArgumentTypes.Provider, McArgumentResolver.Provider { +class ModBrigadierArguments : McArgumentTypes.Provider { private val serverLib by lazy { ModServerLib } - override fun entity(): ArgumentType = EntityArgument.entity() as ArgumentType - - override fun entities(): ArgumentType = EntityArgument.entities() as ArgumentType - - override fun player(): ArgumentType = EntityArgument.player() as ArgumentType - - override fun players(): ArgumentType = EntityArgument.players() as ArgumentType - - override fun getEntity( - context: CommandContext, - name: String, - ): McServerEntity { - val entity = EntityArgument.getEntity(context.toSourceStack(), name) - return serverLib.getEntityByInstance(entity) - } - - override fun getEntities( - context: CommandContext, - name: String, - ): Collection { - val entities = EntityArgument.getEntities(context.toSourceStack(), name) - return entities.map { serverLib.getEntityByInstance(it) } - } - - override fun getPlayer( - context: CommandContext, - name: String, - ): McServerPlayer { - val player = EntityArgument.getPlayer(context.toSourceStack(), name) - return serverLib.getPlayerByInstance(player) - } - - override fun getPlayers( - context: CommandContext, - name: String, - ): Collection { - val players = EntityArgument.getPlayers(context.toSourceStack(), name) - return players.map { serverLib.getPlayerByInstance(it) } - } + override fun entity(): ArgumentType = + argumentResolver(EntityArgument.entity()) { selector -> + McEntityArgumentResolver { source -> + serverLib.getEntityByInstance(selector.findSingleEntity(source.getInstance())) + } + } + + override fun entities(): ArgumentType = + argumentResolver(EntityArgument.entities()) { selector -> + McEntitiesArgumentResolver { source -> + selector.findEntities(source.getInstance()).map { serverLib.getEntityByInstance(it) } + } + } + + override fun player(): ArgumentType = + argumentResolver(EntityArgument.player()) { selector -> + McPlayerArgumentResolver { source -> + serverLib.getPlayerByInstance(selector.findSinglePlayer(source.getInstance())) + } + } + + override fun players(): ArgumentType = + argumentResolver(EntityArgument.players()) { selector -> + McPlayersArgumentResolver { source -> + selector.findPlayers(source.getInstance()).map { serverLib.getPlayerByInstance(it) } + } + } + + private fun argumentResolver( + nativeType: ArgumentType, + resolverFactory: (EntitySelector) -> T, + ): CustomArgumentType = + object : CustomArgumentType { + override val nativeType = nativeType + + override fun parse(reader: StringReader) = resolverFactory(nativeType.parse(reader)) + } } diff --git a/modded/src/main/resources/META-INF/services/su.plo.slib.api.server.command.brigadier.McArgumentResolver$Provider b/modded/src/main/resources/META-INF/services/su.plo.slib.api.server.command.brigadier.McArgumentResolver$Provider deleted file mode 100644 index 0ebbe72..0000000 --- a/modded/src/main/resources/META-INF/services/su.plo.slib.api.server.command.brigadier.McArgumentResolver$Provider +++ /dev/null @@ -1 +0,0 @@ -su.plo.slib.mod.command.brigadier.ModBrigadierArguments diff --git a/scripts/smoke-test.sh b/scripts/smoke-test.sh index 1a3372d..a9d87b2 100755 --- a/scripts/smoke-test.sh +++ b/scripts/smoke-test.sh @@ -16,10 +16,10 @@ COMMAND="$*" case "$ENV_TYPE" in server) - PATTERNS=("Done \\(.*\\)!" "Command 'ping' registered" "Command 'brigadier-entity-selector' registered") + PATTERNS=("Done \\(.*\\)!" "Command 'ping' registered" "Command 'brigadier-entity-selector' registered" "Command 'brigadier-custom-type' registered") ;; proxy) - PATTERNS=("Listening on" "Command 'ping' registered" "Command 'brigadier-ping' registered") + PATTERNS=("Listening on" "Command 'ping' registered" "Command 'brigadier-ping' registered" "Command 'brigadier-custom-type' registered") ;; *) echo "Error: Invalid environment type '$ENV_TYPE'. Must be 'server' or 'proxy'" diff --git a/spigot/src/main/kotlin/su/plo/slib/spigot/command/SpigotCommandManager.kt b/spigot/src/main/kotlin/su/plo/slib/spigot/command/SpigotCommandManager.kt index c02ace0..c84fa68 100644 --- a/spigot/src/main/kotlin/su/plo/slib/spigot/command/SpigotCommandManager.kt +++ b/spigot/src/main/kotlin/su/plo/slib/spigot/command/SpigotCommandManager.kt @@ -38,7 +38,7 @@ class SpigotCommandManager( try { val dispatcher = loader.server.getCommandDispatcher() registerBrigadierCommands { command -> - dispatcher.register( + dispatcher.root.addChild( command.proxied( SpigotBrigadierSource::from, { it.toMc() }, @@ -46,7 +46,8 @@ class SpigotCommandManager( ) } } catch (e: Exception) { - logger.warn("Failed to get Brigadier dispatcher: ${e.message}") + logger.warn("Failed to get Brigadier dispatcher: {}", e) + e.printStackTrace() } registered = true diff --git a/spigot/src/main/kotlin/su/plo/slib/spigot/command/brigadier/SpigotBrigadierArguments.kt b/spigot/src/main/kotlin/su/plo/slib/spigot/command/brigadier/SpigotBrigadierArguments.kt index e667128..c77d3d3 100644 --- a/spigot/src/main/kotlin/su/plo/slib/spigot/command/brigadier/SpigotBrigadierArguments.kt +++ b/spigot/src/main/kotlin/su/plo/slib/spigot/command/brigadier/SpigotBrigadierArguments.kt @@ -1,69 +1,79 @@ package su.plo.slib.spigot.command.brigadier +import com.mojang.brigadier.StringReader import com.mojang.brigadier.arguments.ArgumentType -import com.mojang.brigadier.context.CommandContext -import su.plo.slib.api.command.brigadier.McBrigadierSource -import su.plo.slib.api.server.command.brigadier.McArgumentResolver +import su.plo.slib.api.command.brigadier.CustomArgumentType import su.plo.slib.api.server.command.brigadier.McArgumentTypes -import su.plo.slib.api.server.entity.McServerEntity -import su.plo.slib.api.server.entity.player.McServerPlayer +import su.plo.slib.api.server.command.brigadier.McEntitiesArgumentResolver +import su.plo.slib.api.server.command.brigadier.McEntityArgumentResolver +import su.plo.slib.api.server.command.brigadier.McPlayerArgumentResolver +import su.plo.slib.api.server.command.brigadier.McPlayersArgumentResolver import su.plo.slib.spigot.SpigotServerLib -import su.plo.slib.spigot.command.toSourceStack import su.plo.slib.spigot.nms.ReflectionProxies import java.lang.reflect.UndeclaredThrowableException -class SpigotBrigadierArguments: McArgumentTypes.Provider, McArgumentResolver.Provider { - +class SpigotBrigadierArguments: McArgumentTypes.Provider { private val serverLib by lazy { SpigotServerLib.instance } - override fun entity(): ArgumentType = ReflectionProxies.entityArgument.entity() - - override fun entities(): ArgumentType = ReflectionProxies.entityArgument.entities() - - override fun player(): ArgumentType = ReflectionProxies.entityArgument.players() + override fun entity(): ArgumentType = + argumentResolver(ReflectionProxies.entityArgument.entity()) { selector -> + McEntityArgumentResolver { source -> + val entity = rethrowProxyException { + ReflectionProxies.entitySelector.findSingleEntity(selector, source.getInstance()) + } + val bukkitEntity = ReflectionProxies.entity.getBukkitEntity(entity) - override fun players(): ArgumentType = ReflectionProxies.entityArgument.players() - - override fun getEntity(context: CommandContext, name: String): McServerEntity { - val entity = rethrowProxyException { - ReflectionProxies.entityArgument.getEntity(context.toSourceStack(), name) + serverLib.getEntityByInstance(bukkitEntity) + } } - val bukkitEntity = ReflectionProxies.entity.getBukkitEntity(entity) - return serverLib.getEntityByInstance(bukkitEntity) - } - - override fun getEntities(context: CommandContext, name: String): Collection { - val entities = rethrowProxyException { - ReflectionProxies.entityArgument.getEntities(context.toSourceStack(), name) + override fun entities(): ArgumentType = + argumentResolver(ReflectionProxies.entityArgument.entities()) { selector -> + McEntitiesArgumentResolver { source -> + val entities = rethrowProxyException { + ReflectionProxies.entitySelector.findEntities(selector, source.getInstance()) + } + entities.map { + val bukkitEntity = ReflectionProxies.entity.getBukkitEntity(it) + serverLib.getEntityByInstance(bukkitEntity) + } + } } - return entities.map { entity -> - val bukkitEntity = ReflectionProxies.entity.getBukkitEntity(entity) - serverLib.getEntityByInstance(bukkitEntity) + override fun player(): ArgumentType = + argumentResolver(ReflectionProxies.entityArgument.player()) { selector -> + McPlayerArgumentResolver { source -> + val player = rethrowProxyException { + ReflectionProxies.entitySelector.findSinglePlayer(selector, source.getInstance()) + } + val bukkitPlayer = ReflectionProxies.entity.getBukkitEntity(player) + serverLib.getPlayerByInstance(bukkitPlayer) + } } - } - override fun getPlayer(context: CommandContext, name: String): McServerPlayer { - val player = rethrowProxyException { - ReflectionProxies.entityArgument.getPlayer(context.toSourceStack(), name) + override fun players(): ArgumentType = + argumentResolver(ReflectionProxies.entityArgument.players()) { selector -> + McPlayersArgumentResolver { source -> + val players = rethrowProxyException { + ReflectionProxies.entitySelector.findPlayers(selector, source.getInstance()) + } + players.map { + val bukkitPlayer = ReflectionProxies.entity.getBukkitEntity(it) + serverLib.getPlayerByInstance(bukkitPlayer) + } + } } - val bukkitPlayer = ReflectionProxies.entity.getBukkitEntity(player) - return serverLib.getPlayerByInstance(bukkitPlayer) - } + private fun argumentResolver( + nativeType: ArgumentType, + resolverFactory: (Any) -> T, + ): CustomArgumentType = + object : CustomArgumentType { + override val nativeType = nativeType - override fun getPlayers(context: CommandContext, name: String): Collection { - val players = rethrowProxyException { - ReflectionProxies.entityArgument.getPlayers(context.toSourceStack(), name) + override fun parse(reader: StringReader) = resolverFactory(nativeType.parse(reader)) } - return players.map { player -> - val bukkitPlayer = ReflectionProxies.entity.getBukkitEntity(player) - serverLib.getPlayerByInstance(bukkitPlayer) - } - } - private fun rethrowProxyException(block: () -> T): T { try { return block() diff --git a/spigot/src/main/kotlin/su/plo/slib/spigot/nms/EntityArgumentProxy.kt b/spigot/src/main/kotlin/su/plo/slib/spigot/nms/EntityArgumentProxy.kt index 6931979..d5a9b37 100644 --- a/spigot/src/main/kotlin/su/plo/slib/spigot/nms/EntityArgumentProxy.kt +++ b/spigot/src/main/kotlin/su/plo/slib/spigot/nms/EntityArgumentProxy.kt @@ -1,7 +1,6 @@ package su.plo.slib.spigot.nms import com.mojang.brigadier.arguments.ArgumentType -import com.mojang.brigadier.context.CommandContext import xyz.jpenilla.reflectionremapper.proxy.annotation.Proxies import xyz.jpenilla.reflectionremapper.proxy.annotation.Static @@ -20,16 +19,4 @@ interface EntityArgumentProxy { @Static fun players(): ArgumentType - - @Static - fun getEntity(context: CommandContext<*>, name: String): Any - - @Static - fun getEntities(context: CommandContext<*>, name: String): Collection - - @Static - fun getPlayer(context: CommandContext<*>, name: String): Any - - @Static - fun getPlayers(context: CommandContext<*>, name: String): Collection } diff --git a/spigot/src/main/kotlin/su/plo/slib/spigot/nms/EntitySelectorProxy.kt b/spigot/src/main/kotlin/su/plo/slib/spigot/nms/EntitySelectorProxy.kt new file mode 100644 index 0000000..752d8bb --- /dev/null +++ b/spigot/src/main/kotlin/su/plo/slib/spigot/nms/EntitySelectorProxy.kt @@ -0,0 +1,29 @@ +package su.plo.slib.spigot.nms + +import xyz.jpenilla.reflectionremapper.proxy.annotation.Proxies +import xyz.jpenilla.reflectionremapper.proxy.annotation.Type + +@Proxies( + className = "net.minecraft.commands.arguments.selector.EntitySelector" +) +interface EntitySelectorProxy { + fun findSingleEntity( + @Type(className = "net.minecraft.commands.arguments.selector") instance: Any, + @Type(className = "net.minecraft.commands.CommandSourceStack") source: Any, + ): Any + + fun findEntities( + @Type(className = "net.minecraft.commands.arguments.selector") instance: Any, + @Type(className = "net.minecraft.commands.CommandSourceStack") source: Any, + ): List + + fun findSinglePlayer( + @Type(className = "net.minecraft.commands.arguments.selector") instance: Any, + @Type(className = "net.minecraft.commands.CommandSourceStack") source: Any, + ): Any + + fun findPlayers( + @Type(className = "net.minecraft.commands.arguments.selector") instance: Any, + @Type(className = "net.minecraft.commands.CommandSourceStack") source: Any, + ): List +} diff --git a/spigot/src/main/kotlin/su/plo/slib/spigot/nms/Reflections.kt b/spigot/src/main/kotlin/su/plo/slib/spigot/nms/Reflections.kt index aa994c2..2f46025 100644 --- a/spigot/src/main/kotlin/su/plo/slib/spigot/nms/Reflections.kt +++ b/spigot/src/main/kotlin/su/plo/slib/spigot/nms/Reflections.kt @@ -15,6 +15,7 @@ object ReflectionProxies { val commandSourceStack: CommandSourceStackProxy val entity: EntityProxy val entityArgument: EntityArgumentProxy + val entitySelector: EntitySelectorProxy init { val bukkitVersion = Bukkit.getVersion() @@ -27,7 +28,7 @@ object ReflectionProxies { .also { logger.info("Using mappings from paper jar") } } catch (_: Throwable) { val mappingsVersion = listOf( - "1.21.6", + "1.21.4", "1.19.2", "1.17.1", "1.16.5", @@ -51,6 +52,7 @@ object ReflectionProxies { commandSourceStack = proxyFactory.reflectionProxy() entity = proxyFactory.reflectionProxy() entityArgument = proxyFactory.reflectionProxy() + entitySelector = proxyFactory.reflectionProxy() } private inline fun ReflectionProxyFactory.reflectionProxy() = diff --git a/spigot/src/main/resources/META-INF/services/su.plo.slib.api.server.command.brigadier.McArgumentResolver$Provider b/spigot/src/main/resources/META-INF/services/su.plo.slib.api.server.command.brigadier.McArgumentResolver$Provider deleted file mode 100644 index 6ea8415..0000000 --- a/spigot/src/main/resources/META-INF/services/su.plo.slib.api.server.command.brigadier.McArgumentResolver$Provider +++ /dev/null @@ -1 +0,0 @@ -su.plo.slib.spigot.command.brigadier.SpigotBrigadierArguments diff --git a/spigot/src/main/resources/mappings/1.16.5.tiny b/spigot/src/main/resources/mappings/1.16.5.tiny index 89470f3..508a872 100644 --- a/spigot/src/main/resources/mappings/1.16.5.tiny +++ b/spigot/src/main/resources/mappings/1.16.5.tiny @@ -4,11 +4,12 @@ c net/minecraft/commands/CommandSourceStack net/minecraft/server/v1_16_R3/Comman c net/minecraft/commands/Commands net/minecraft/server/v1_16_R3/CommandDispatcher c net/minecraft/commands/arguments/EntityArgument net/minecraft/server/v1_16_R3/ArgumentEntity m ()Lnet/minecraft/commands/arguments/EntityArgument; entity a - m (Lcom/mojang/brigadier/context/CommandContext;Ljava/lang/String;)Lnet/minecraft/world/entity/Entity; getEntity a m ()Lnet/minecraft/commands/arguments/EntityArgument; entities multipleEntities - m (Lcom/mojang/brigadier/context/CommandContext;Ljava/lang/String;)Ljava/util/Collection; getEntities b m ()Lnet/minecraft/commands/arguments/EntityArgument; player c - m (Lcom/mojang/brigadier/context/CommandContext;Ljava/lang/String;)Lnet/minecraft/server/level/ServerPlayer; getPlayer e m ()Lnet/minecraft/commands/arguments/EntityArgument; players d - m (Lcom/mojang/brigadier/context/CommandContext;Ljava/lang/String;)Ljava/util/Collection; getPlayers f +c net/minecraft/commands/arguments/selector/EntitySelector net/minecraft/server/v1_16_R3/EntitySelector + m (Lnet/minecraft/commands/CommandSourceStack;)Lnet/minecraft/world/entity/Entity; findSingleEntity a + m (Lnet/minecraft/commands/CommandSourceStack;)Ljava/util/List; findEntities getEntities + m (Lnet/minecraft/commands/CommandSourceStack;)Lnet/minecraft/server/level/ServerPlayer; findSinglePlayer c + m (Lnet/minecraft/commands/CommandSourceStack;)Ljava/util/List; findPlayers d c net/minecraft/world/entity/Entity net/minecraft/server/v1_16_R3/Entity diff --git a/spigot/src/main/resources/mappings/1.17.1.tiny b/spigot/src/main/resources/mappings/1.17.1.tiny index 9e16229..60c5619 100644 --- a/spigot/src/main/resources/mappings/1.17.1.tiny +++ b/spigot/src/main/resources/mappings/1.17.1.tiny @@ -4,11 +4,12 @@ c net/minecraft/commands/CommandSourceStack net/minecraft/commands/CommandListen c net/minecraft/commands/Commands net/minecraft/commands/CommandDispatcher c net/minecraft/commands/arguments/EntityArgument net/minecraft/commands/arguments/ArgumentEntity m ()Lnet/minecraft/commands/arguments/EntityArgument; entity a - m (Lcom/mojang/brigadier/context/CommandContext;Ljava/lang/String;)Lnet/minecraft/world/entity/Entity; getEntity a m ()Lnet/minecraft/commands/arguments/EntityArgument; entities multipleEntities - m (Lcom/mojang/brigadier/context/CommandContext;Ljava/lang/String;)Ljava/util/Collection; getEntities b m ()Lnet/minecraft/commands/arguments/EntityArgument; player c - m (Lcom/mojang/brigadier/context/CommandContext;Ljava/lang/String;)Lnet/minecraft/server/level/ServerPlayer; getPlayer e m ()Lnet/minecraft/commands/arguments/EntityArgument; players d - m (Lcom/mojang/brigadier/context/CommandContext;Ljava/lang/String;)Ljava/util/Collection; getPlayers f +c net/minecraft/commands/arguments/selector/EntitySelector net/minecraft/commands/arguments/selector/EntitySelector + m (Lnet/minecraft/commands/CommandSourceStack;)Lnet/minecraft/world/entity/Entity; findSingleEntity a + m (Lnet/minecraft/commands/CommandSourceStack;)Ljava/util/List; findEntities getEntities + m (Lnet/minecraft/commands/CommandSourceStack;)Lnet/minecraft/server/level/ServerPlayer; findSinglePlayer c + m (Lnet/minecraft/commands/CommandSourceStack;)Ljava/util/List; findPlayers d c net/minecraft/world/entity/Entity net/minecraft/world/entity/Entity diff --git a/spigot/src/main/resources/mappings/1.19.2.tiny b/spigot/src/main/resources/mappings/1.19.2.tiny index 834066c..94ac9cb 100644 --- a/spigot/src/main/resources/mappings/1.19.2.tiny +++ b/spigot/src/main/resources/mappings/1.19.2.tiny @@ -4,11 +4,12 @@ c net/minecraft/commands/CommandSourceStack net/minecraft/commands/CommandListen c net/minecraft/commands/Commands net/minecraft/commands/CommandDispatcher c net/minecraft/commands/arguments/EntityArgument net/minecraft/commands/arguments/ArgumentEntity m ()Lnet/minecraft/commands/arguments/EntityArgument; entity a - m (Lcom/mojang/brigadier/context/CommandContext;Ljava/lang/String;)Lnet/minecraft/world/entity/Entity; getEntity a m ()Lnet/minecraft/commands/arguments/EntityArgument; entities b - m (Lcom/mojang/brigadier/context/CommandContext;Ljava/lang/String;)Ljava/util/Collection; getEntities b m ()Lnet/minecraft/commands/arguments/EntityArgument; player c - m (Lcom/mojang/brigadier/context/CommandContext;Ljava/lang/String;)Lnet/minecraft/server/level/ServerPlayer; getPlayer e m ()Lnet/minecraft/commands/arguments/EntityArgument; players d - m (Lcom/mojang/brigadier/context/CommandContext;Ljava/lang/String;)Ljava/util/Collection; getPlayers f +c net/minecraft/commands/arguments/selector/EntitySelector net/minecraft/commands/arguments/selector/EntitySelector + m (Lnet/minecraft/commands/CommandSourceStack;)Lnet/minecraft/world/entity/Entity; findSingleEntity a + m (Lnet/minecraft/commands/CommandSourceStack;)Ljava/util/List; findEntities b + m (Lnet/minecraft/commands/CommandSourceStack;)Lnet/minecraft/server/level/ServerPlayer; findSinglePlayer c + m (Lnet/minecraft/commands/CommandSourceStack;)Ljava/util/List; findPlayers d c net/minecraft/world/entity/Entity net/minecraft/world/entity/Entity diff --git a/spigot/src/main/resources/mappings/1.21.6.tiny b/spigot/src/main/resources/mappings/1.21.4.tiny similarity index 57% rename from spigot/src/main/resources/mappings/1.21.6.tiny rename to spigot/src/main/resources/mappings/1.21.4.tiny index 8015938..6c8f656 100644 --- a/spigot/src/main/resources/mappings/1.21.6.tiny +++ b/spigot/src/main/resources/mappings/1.21.4.tiny @@ -4,11 +4,12 @@ c net/minecraft/commands/CommandSourceStack net/minecraft/commands/CommandListen c net/minecraft/commands/Commands net/minecraft/commands/CommandDispatcher c net/minecraft/commands/arguments/EntityArgument net/minecraft/commands/arguments/ArgumentEntity m ()Lnet/minecraft/commands/arguments/EntityArgument; entity a - m (Lcom/mojang/brigadier/context/CommandContext;Ljava/lang/String;)Lnet/minecraft/world/entity/Entity; getEntity a m ()Lnet/minecraft/commands/arguments/EntityArgument; entities b - m (Lcom/mojang/brigadier/context/CommandContext;Ljava/lang/String;)Ljava/util/Collection; getEntities b m ()Lnet/minecraft/commands/arguments/EntityArgument; player c - m (Lcom/mojang/brigadier/context/CommandContext;Ljava/lang/String;)Lnet/minecraft/server/level/ServerPlayer; getPlayer e m ()Lnet/minecraft/commands/arguments/EntityArgument; players d - m (Lcom/mojang/brigadier/context/CommandContext;Ljava/lang/String;)Ljava/util/Collection; getPlayers f +c net/minecraft/commands/arguments/selector/EntitySelector net/minecraft/commands/arguments/selector/EntitySelector + m (Lnet/minecraft/commands/CommandSourceStack;)Lnet/minecraft/world/entity/Entity; findSingleEntity a + m (Lnet/minecraft/commands/CommandSourceStack;)Ljava/util/List; findEntities b + m (Lnet/minecraft/commands/CommandSourceStack;)Lnet/minecraft/server/level/ServerPlayer; findSinglePlayer c + m (Lnet/minecraft/commands/CommandSourceStack;)Ljava/util/List; findPlayers d c net/minecraft/world/entity/Entity net/minecraft/world/entity/Entity From ef984e3ca29a00b69a8793bce8f3a01ef83ac652 Mon Sep 17 00:00:00 2001 From: Apehum Date: Wed, 28 Jan 2026 09:51:11 +0800 Subject: [PATCH 10/36] feat(server): ServerPos3d brigadier argument --- .../command/brigadier/McArgumentTypes.kt | 8 +++++ .../command/brigadier/ServerPos3dResolver.kt | 9 +++++ .../slib/api/server/position/ServerPos3d.kt | 3 ++ .../kotlin/su/plo/slib/server/TestServer.kt | 20 +++++++++++ .../brigadier/MinestomEntityArguments.kt | 33 ++++++++++++++--- .../brigadier/ModBrigadierArguments.kt | 36 +++++++++++++++---- scripts/smoke-test.sh | 15 ++++++-- .../brigadier/SpigotBrigadierArguments.kt | 22 ++++++++++++ .../slib/spigot/nms/BlockPosArgumentProxy.kt | 13 +++++++ .../plo/slib/spigot/nms/CoordinatesProxy.kt | 19 ++++++++++ .../slib/spigot/nms/EntitySelectorProxy.kt | 8 ++--- .../su/plo/slib/spigot/nms/Reflections.kt | 8 +++++ .../su/plo/slib/spigot/nms/Vec2Proxy.kt | 20 +++++++++++ .../su/plo/slib/spigot/nms/Vec3Proxy.kt | 21 +++++++++++ .../src/main/resources/mappings/1.16.5.tiny | 12 +++++++ .../src/main/resources/mappings/1.17.1.tiny | 12 +++++++ .../src/main/resources/mappings/1.19.2.tiny | 12 +++++++ .../src/main/resources/mappings/1.21.4.tiny | 12 +++++++ .../src/main/resources/mappings/1.21.5.tiny | 27 ++++++++++++++ 19 files changed, 294 insertions(+), 16 deletions(-) create mode 100644 api/server/src/main/kotlin/su/plo/slib/api/server/command/brigadier/ServerPos3dResolver.kt create mode 100644 spigot/src/main/kotlin/su/plo/slib/spigot/nms/BlockPosArgumentProxy.kt create mode 100644 spigot/src/main/kotlin/su/plo/slib/spigot/nms/CoordinatesProxy.kt create mode 100644 spigot/src/main/kotlin/su/plo/slib/spigot/nms/Vec2Proxy.kt create mode 100644 spigot/src/main/kotlin/su/plo/slib/spigot/nms/Vec3Proxy.kt create mode 100644 spigot/src/main/resources/mappings/1.21.5.tiny diff --git a/api/server/src/main/kotlin/su/plo/slib/api/server/command/brigadier/McArgumentTypes.kt b/api/server/src/main/kotlin/su/plo/slib/api/server/command/brigadier/McArgumentTypes.kt index a675972..2449785 100644 --- a/api/server/src/main/kotlin/su/plo/slib/api/server/command/brigadier/McArgumentTypes.kt +++ b/api/server/src/main/kotlin/su/plo/slib/api/server/command/brigadier/McArgumentTypes.kt @@ -33,6 +33,12 @@ object McArgumentTypes { @JvmStatic fun players(): ArgumentType = provider.players() + /** + * Returns an argument type that resolves position. + */ + @JvmStatic + fun position(): ArgumentType = provider.position() + private val provider: Provider by lazyService() @ApiStatus.Internal @@ -44,5 +50,7 @@ object McArgumentTypes { fun player(): ArgumentType fun players(): ArgumentType + + fun position(): ArgumentType } } diff --git a/api/server/src/main/kotlin/su/plo/slib/api/server/command/brigadier/ServerPos3dResolver.kt b/api/server/src/main/kotlin/su/plo/slib/api/server/command/brigadier/ServerPos3dResolver.kt new file mode 100644 index 0000000..5b09e24 --- /dev/null +++ b/api/server/src/main/kotlin/su/plo/slib/api/server/command/brigadier/ServerPos3dResolver.kt @@ -0,0 +1,9 @@ +package su.plo.slib.api.server.command.brigadier + +import su.plo.slib.api.command.brigadier.ArgumentResolver +import su.plo.slib.api.server.position.ServerPos3d + +/** + * An [ArgumentResolver] that resolves [ServerPos3d]. + */ +fun interface ServerPos3dResolver : ArgumentResolver diff --git a/api/server/src/main/kotlin/su/plo/slib/api/server/position/ServerPos3d.kt b/api/server/src/main/kotlin/su/plo/slib/api/server/position/ServerPos3d.kt index b23becd..95d2603 100644 --- a/api/server/src/main/kotlin/su/plo/slib/api/server/position/ServerPos3d.kt +++ b/api/server/src/main/kotlin/su/plo/slib/api/server/position/ServerPos3d.kt @@ -87,4 +87,7 @@ class ServerPos3d @JvmOverloads constructor( return pos } + + override fun toString(): String = + "ServerPos3d(world=${worldReference.get()}, x=$x, y=$y, z=$z, yaw=$yaw, pitch=$pitch)" } diff --git a/common-server/src/testFixtures/kotlin/su/plo/slib/server/TestServer.kt b/common-server/src/testFixtures/kotlin/su/plo/slib/server/TestServer.kt index 87ff3f8..c64020b 100644 --- a/common-server/src/testFixtures/kotlin/su/plo/slib/server/TestServer.kt +++ b/common-server/src/testFixtures/kotlin/su/plo/slib/server/TestServer.kt @@ -13,6 +13,7 @@ import su.plo.slib.api.server.command.brigadier.McEntitiesArgumentResolver import su.plo.slib.api.server.command.brigadier.McEntityArgumentResolver import su.plo.slib.api.server.command.brigadier.McPlayerArgumentResolver import su.plo.slib.api.server.command.brigadier.McPlayersArgumentResolver +import su.plo.slib.api.server.command.brigadier.ServerPos3dResolver import su.plo.slib.api.server.event.command.McServerCommandsRegisterEvent import su.plo.slib.api.server.event.player.McPlayerRegisterChannelsEvent import su.plo.slib.server.command.UuidArgumentType @@ -140,6 +141,25 @@ class TestServer( ) ) ) + + commands.register( + McCommandManager.literal("brigadier-position-selector") + .then( + McCommandManager.argument("position", McArgumentTypes.position()) + .executes { + val resolver = it.getArgument( + "position", + ServerPos3dResolver::class.java, + ) + val position = resolver.resolve(it.source) + + val source = it.source + source.source.sendMessage("Position: $position; Source: ${source.source}; Executor: ${source.executor}") + + Command.SINGLE_SUCCESS + } + ) + ) } } diff --git a/minestom/src/main/kotlin/su/plo/slib/minestom/command/brigadier/MinestomEntityArguments.kt b/minestom/src/main/kotlin/su/plo/slib/minestom/command/brigadier/MinestomEntityArguments.kt index 8445513..06cfa5b 100644 --- a/minestom/src/main/kotlin/su/plo/slib/minestom/command/brigadier/MinestomEntityArguments.kt +++ b/minestom/src/main/kotlin/su/plo/slib/minestom/command/brigadier/MinestomEntityArguments.kt @@ -2,13 +2,17 @@ package su.plo.slib.minestom.command.brigadier import com.mojang.brigadier.arguments.ArgumentType import net.minestom.server.command.builder.arguments.minecraft.ArgumentEntity +import net.minestom.server.command.builder.arguments.relative.ArgumentRelativeBlockPosition +import net.minestom.server.entity.Entity import net.minestom.server.entity.Player -import net.minestom.server.utils.entity.EntityFinder import su.plo.slib.api.server.command.brigadier.McArgumentTypes import su.plo.slib.api.server.command.brigadier.McEntitiesArgumentResolver import su.plo.slib.api.server.command.brigadier.McEntityArgumentResolver import su.plo.slib.api.server.command.brigadier.McPlayerArgumentResolver import su.plo.slib.api.server.command.brigadier.McPlayersArgumentResolver +import su.plo.slib.api.server.command.brigadier.ServerPos3dResolver +import su.plo.slib.api.server.entity.McServerEntity +import su.plo.slib.api.server.position.ServerPos3d import su.plo.slib.minestom.MinestomServerLib class MinestomEntityArguments : McArgumentTypes.Provider { @@ -57,9 +61,30 @@ class MinestomEntityArguments : McArgumentTypes.Provider { } } - private fun argumentResolver( - finderArgumentType: MinestomArgumentType, - resolverFactory: (EntityFinder) -> T, + override fun position(): ArgumentType = + argumentResolver( + MinestomArgumentType { name -> ArgumentRelativeBlockPosition(name) } + ) { resolver -> + ServerPos3dResolver { source -> + val entity = source.executor as? McServerEntity + val entityPosition = entity?.getServerPosition() + + val position = resolver.from(entity?.getInstance()) + + ServerPos3d( + entity?.world, + position.x(), + position.y(), + position.z(), + entityPosition?.yaw ?: 0f, + entityPosition?.pitch ?: 0f, + ) + } + } + + private fun argumentResolver( + finderArgumentType: MinestomArgumentType, + resolverFactory: (S) -> T, ): ArgumentType = MinestomArgumentType { name -> finderArgumentType.argumentBuilder(name).map { resolverFactory(it) } diff --git a/modded/src/main/kotlin/su/plo/slib/mod/command/brigadier/ModBrigadierArguments.kt b/modded/src/main/kotlin/su/plo/slib/mod/command/brigadier/ModBrigadierArguments.kt index 74f2641..af1636d 100644 --- a/modded/src/main/kotlin/su/plo/slib/mod/command/brigadier/ModBrigadierArguments.kt +++ b/modded/src/main/kotlin/su/plo/slib/mod/command/brigadier/ModBrigadierArguments.kt @@ -2,14 +2,18 @@ package su.plo.slib.mod.command.brigadier import com.mojang.brigadier.StringReader import com.mojang.brigadier.arguments.ArgumentType +import net.minecraft.commands.CommandSourceStack import net.minecraft.commands.arguments.EntityArgument -import net.minecraft.commands.arguments.selector.EntitySelector +import net.minecraft.commands.arguments.coordinates.BlockPosArgument import su.plo.slib.api.command.brigadier.CustomArgumentType import su.plo.slib.api.server.command.brigadier.McArgumentTypes import su.plo.slib.api.server.command.brigadier.McEntitiesArgumentResolver import su.plo.slib.api.server.command.brigadier.McEntityArgumentResolver import su.plo.slib.api.server.command.brigadier.McPlayerArgumentResolver import su.plo.slib.api.server.command.brigadier.McPlayersArgumentResolver +import su.plo.slib.api.server.command.brigadier.ServerPos3dResolver +import su.plo.slib.api.server.entity.McServerEntity +import su.plo.slib.api.server.position.ServerPos3d import su.plo.slib.mod.ModServerLib class ModBrigadierArguments : McArgumentTypes.Provider { @@ -43,11 +47,31 @@ class ModBrigadierArguments : McArgumentTypes.Provider { } } - private fun argumentResolver( - nativeType: ArgumentType, - resolverFactory: (EntitySelector) -> T, - ): CustomArgumentType = - object : CustomArgumentType { + override fun position(): ArgumentType = + argumentResolver(BlockPosArgument.blockPos()) { coordinates -> + ServerPos3dResolver { source -> + val stack = source.getInstance() + val position = coordinates.getPosition(stack) + val rotation = coordinates.getRotation(stack) + + val world = (source.executor as? McServerEntity)?.world + + ServerPos3d( + world, + position.x(), + position.y(), + position.z(), + rotation.y, + rotation.x, + ) + } + } + + private fun argumentResolver( + nativeType: ArgumentType, + resolverFactory: (S) -> T, + ): CustomArgumentType = + object : CustomArgumentType { override val nativeType = nativeType override fun parse(reader: StringReader) = resolverFactory(nativeType.parse(reader)) diff --git a/scripts/smoke-test.sh b/scripts/smoke-test.sh index a9d87b2..71c38c5 100755 --- a/scripts/smoke-test.sh +++ b/scripts/smoke-test.sh @@ -16,10 +16,21 @@ COMMAND="$*" case "$ENV_TYPE" in server) - PATTERNS=("Done \\(.*\\)!" "Command 'ping' registered" "Command 'brigadier-entity-selector' registered" "Command 'brigadier-custom-type' registered") + PATTERNS=( + "Done \\(.*\\)!" + "Command 'ping' registered" + "Command 'brigadier-entity-selector' registered" + "Command 'brigadier-position-selector' registered" + "Command 'brigadier-custom-type' registered" + ) ;; proxy) - PATTERNS=("Listening on" "Command 'ping' registered" "Command 'brigadier-ping' registered" "Command 'brigadier-custom-type' registered") + PATTERNS=( + "Listening on" + "Command 'ping' registered" + "Command 'brigadier-ping' registered" + "Command 'brigadier-custom-type' registered" + ) ;; *) echo "Error: Invalid environment type '$ENV_TYPE'. Must be 'server' or 'proxy'" diff --git a/spigot/src/main/kotlin/su/plo/slib/spigot/command/brigadier/SpigotBrigadierArguments.kt b/spigot/src/main/kotlin/su/plo/slib/spigot/command/brigadier/SpigotBrigadierArguments.kt index c77d3d3..078c21c 100644 --- a/spigot/src/main/kotlin/su/plo/slib/spigot/command/brigadier/SpigotBrigadierArguments.kt +++ b/spigot/src/main/kotlin/su/plo/slib/spigot/command/brigadier/SpigotBrigadierArguments.kt @@ -8,6 +8,9 @@ import su.plo.slib.api.server.command.brigadier.McEntitiesArgumentResolver import su.plo.slib.api.server.command.brigadier.McEntityArgumentResolver import su.plo.slib.api.server.command.brigadier.McPlayerArgumentResolver import su.plo.slib.api.server.command.brigadier.McPlayersArgumentResolver +import su.plo.slib.api.server.command.brigadier.ServerPos3dResolver +import su.plo.slib.api.server.entity.McServerEntity +import su.plo.slib.api.server.position.ServerPos3d import su.plo.slib.spigot.SpigotServerLib import su.plo.slib.spigot.nms.ReflectionProxies import java.lang.reflect.UndeclaredThrowableException @@ -64,6 +67,25 @@ class SpigotBrigadierArguments: McArgumentTypes.Provider { } } + override fun position(): ArgumentType = + argumentResolver(ReflectionProxies.blockPosArgument.blockPos()) { coordinates -> + ServerPos3dResolver { source -> + val position = ReflectionProxies.coordinatesProxy.getPosition(coordinates, source.getInstance()) + val rotation = ReflectionProxies.coordinatesProxy.getRotation(coordinates, source.getInstance()) + + val world = (source.executor as? McServerEntity)?.world + + ServerPos3d( + world, + ReflectionProxies.vec3Proxy.x(position), + ReflectionProxies.vec3Proxy.y(position), + ReflectionProxies.vec3Proxy.z(position), + ReflectionProxies.vec2Proxy.y(rotation), + ReflectionProxies.vec2Proxy.x(rotation), + ) + } + } + private fun argumentResolver( nativeType: ArgumentType, resolverFactory: (Any) -> T, diff --git a/spigot/src/main/kotlin/su/plo/slib/spigot/nms/BlockPosArgumentProxy.kt b/spigot/src/main/kotlin/su/plo/slib/spigot/nms/BlockPosArgumentProxy.kt new file mode 100644 index 0000000..8a028f7 --- /dev/null +++ b/spigot/src/main/kotlin/su/plo/slib/spigot/nms/BlockPosArgumentProxy.kt @@ -0,0 +1,13 @@ +package su.plo.slib.spigot.nms + +import com.mojang.brigadier.arguments.ArgumentType +import xyz.jpenilla.reflectionremapper.proxy.annotation.Proxies +import xyz.jpenilla.reflectionremapper.proxy.annotation.Static + +@Proxies( + className = "net.minecraft.commands.arguments.coordinates.BlockPosArgument" +) +interface BlockPosArgumentProxy { + @Static + fun blockPos(): ArgumentType +} diff --git a/spigot/src/main/kotlin/su/plo/slib/spigot/nms/CoordinatesProxy.kt b/spigot/src/main/kotlin/su/plo/slib/spigot/nms/CoordinatesProxy.kt new file mode 100644 index 0000000..fc47cf3 --- /dev/null +++ b/spigot/src/main/kotlin/su/plo/slib/spigot/nms/CoordinatesProxy.kt @@ -0,0 +1,19 @@ +package su.plo.slib.spigot.nms + +import xyz.jpenilla.reflectionremapper.proxy.annotation.Proxies +import xyz.jpenilla.reflectionremapper.proxy.annotation.Type + +@Proxies( + className = "net.minecraft.commands.arguments.coordinates.Coordinates" +) +interface CoordinatesProxy { + fun getPosition( + @Type(className = "net.minecraft.commands.arguments.selector.EntitySelector") instance: Any, + @Type(className = "net.minecraft.commands.CommandSourceStack") source: Any, + ): Any + + fun getRotation( + @Type(className = "net.minecraft.commands.arguments.selector.EntitySelector") instance: Any, + @Type(className = "net.minecraft.commands.CommandSourceStack") source: Any, + ): Any +} diff --git a/spigot/src/main/kotlin/su/plo/slib/spigot/nms/EntitySelectorProxy.kt b/spigot/src/main/kotlin/su/plo/slib/spigot/nms/EntitySelectorProxy.kt index 752d8bb..183cd51 100644 --- a/spigot/src/main/kotlin/su/plo/slib/spigot/nms/EntitySelectorProxy.kt +++ b/spigot/src/main/kotlin/su/plo/slib/spigot/nms/EntitySelectorProxy.kt @@ -8,22 +8,22 @@ import xyz.jpenilla.reflectionremapper.proxy.annotation.Type ) interface EntitySelectorProxy { fun findSingleEntity( - @Type(className = "net.minecraft.commands.arguments.selector") instance: Any, + @Type(className = "net.minecraft.commands.arguments.selector.EntitySelector") instance: Any, @Type(className = "net.minecraft.commands.CommandSourceStack") source: Any, ): Any fun findEntities( - @Type(className = "net.minecraft.commands.arguments.selector") instance: Any, + @Type(className = "net.minecraft.commands.arguments.selector.EntitySelector") instance: Any, @Type(className = "net.minecraft.commands.CommandSourceStack") source: Any, ): List fun findSinglePlayer( - @Type(className = "net.minecraft.commands.arguments.selector") instance: Any, + @Type(className = "net.minecraft.commands.arguments.selector.EntitySelector") instance: Any, @Type(className = "net.minecraft.commands.CommandSourceStack") source: Any, ): Any fun findPlayers( - @Type(className = "net.minecraft.commands.arguments.selector") instance: Any, + @Type(className = "net.minecraft.commands.arguments.selector.EntitySelector") instance: Any, @Type(className = "net.minecraft.commands.CommandSourceStack") source: Any, ): List } diff --git a/spigot/src/main/kotlin/su/plo/slib/spigot/nms/Reflections.kt b/spigot/src/main/kotlin/su/plo/slib/spigot/nms/Reflections.kt index 2f46025..bae5eef 100644 --- a/spigot/src/main/kotlin/su/plo/slib/spigot/nms/Reflections.kt +++ b/spigot/src/main/kotlin/su/plo/slib/spigot/nms/Reflections.kt @@ -16,6 +16,10 @@ object ReflectionProxies { val entity: EntityProxy val entityArgument: EntityArgumentProxy val entitySelector: EntitySelectorProxy + val blockPosArgument: BlockPosArgumentProxy + val coordinatesProxy: CoordinatesProxy + val vec3Proxy: Vec3Proxy + val vec2Proxy: Vec2Proxy init { val bukkitVersion = Bukkit.getVersion() @@ -53,6 +57,10 @@ object ReflectionProxies { entity = proxyFactory.reflectionProxy() entityArgument = proxyFactory.reflectionProxy() entitySelector = proxyFactory.reflectionProxy() + blockPosArgument = proxyFactory.reflectionProxy() + coordinatesProxy = proxyFactory.reflectionProxy() + vec3Proxy = proxyFactory.reflectionProxy() + vec2Proxy = proxyFactory.reflectionProxy() } private inline fun ReflectionProxyFactory.reflectionProxy() = diff --git a/spigot/src/main/kotlin/su/plo/slib/spigot/nms/Vec2Proxy.kt b/spigot/src/main/kotlin/su/plo/slib/spigot/nms/Vec2Proxy.kt new file mode 100644 index 0000000..1a40777 --- /dev/null +++ b/spigot/src/main/kotlin/su/plo/slib/spigot/nms/Vec2Proxy.kt @@ -0,0 +1,20 @@ +package su.plo.slib.spigot.nms + +import xyz.jpenilla.reflectionremapper.proxy.annotation.FieldGetter +import xyz.jpenilla.reflectionremapper.proxy.annotation.Proxies +import xyz.jpenilla.reflectionremapper.proxy.annotation.Type + +@Proxies( + className = "net.minecraft.world.phys.Vec2" +) +interface Vec2Proxy { + @FieldGetter("x") + fun x( + @Type(className = "net.minecraft.world.phys.Vec2") instance: Any, + ): Float + + @FieldGetter("y") + fun y( + @Type(className = "net.minecraft.world.phys.Vec2") instance: Any, + ): Float +} diff --git a/spigot/src/main/kotlin/su/plo/slib/spigot/nms/Vec3Proxy.kt b/spigot/src/main/kotlin/su/plo/slib/spigot/nms/Vec3Proxy.kt new file mode 100644 index 0000000..f0647d2 --- /dev/null +++ b/spigot/src/main/kotlin/su/plo/slib/spigot/nms/Vec3Proxy.kt @@ -0,0 +1,21 @@ +package su.plo.slib.spigot.nms + +import xyz.jpenilla.reflectionremapper.proxy.annotation.Proxies +import xyz.jpenilla.reflectionremapper.proxy.annotation.Type + +@Proxies( + className = "net.minecraft.world.phys.Vec3" +) +interface Vec3Proxy { + fun x( + @Type(className = "net.minecraft.world.phys.Vec3") instance: Any, + ): Double + + fun y( + @Type(className = "net.minecraft.world.phys.Vec3") instance: Any, + ): Double + + fun z( + @Type(className = "net.minecraft.world.phys.Vec3") instance: Any, + ): Double +} diff --git a/spigot/src/main/resources/mappings/1.16.5.tiny b/spigot/src/main/resources/mappings/1.16.5.tiny index 508a872..9e5ba51 100644 --- a/spigot/src/main/resources/mappings/1.16.5.tiny +++ b/spigot/src/main/resources/mappings/1.16.5.tiny @@ -7,9 +7,21 @@ c net/minecraft/commands/arguments/EntityArgument net/minecraft/server/v1_16_R3/ m ()Lnet/minecraft/commands/arguments/EntityArgument; entities multipleEntities m ()Lnet/minecraft/commands/arguments/EntityArgument; player c m ()Lnet/minecraft/commands/arguments/EntityArgument; players d +c net/minecraft/commands/arguments/coordinates/BlockPosArgument net/minecraft/server/v1_16_R3/ArgumentPosition + m ()Lnet/minecraft/commands/arguments/coordinates/BlockPosArgument; blockPos a +c net/minecraft/commands/arguments/coordinates/Coordinates net/minecraft/server/v1_16_R3/IVectorPosition + m (Lnet/minecraft/commands/CommandSourceStack;)Lnet/minecraft/world/phys/Vec3; getPosition a + m (Lnet/minecraft/commands/CommandSourceStack;)Lnet/minecraft/world/phys/Vec2; getRotation b c net/minecraft/commands/arguments/selector/EntitySelector net/minecraft/server/v1_16_R3/EntitySelector m (Lnet/minecraft/commands/CommandSourceStack;)Lnet/minecraft/world/entity/Entity; findSingleEntity a m (Lnet/minecraft/commands/CommandSourceStack;)Ljava/util/List; findEntities getEntities m (Lnet/minecraft/commands/CommandSourceStack;)Lnet/minecraft/server/level/ServerPlayer; findSinglePlayer c m (Lnet/minecraft/commands/CommandSourceStack;)Ljava/util/List; findPlayers d c net/minecraft/world/entity/Entity net/minecraft/server/v1_16_R3/Entity +c net/minecraft/world/phys/Vec2 net/minecraft/server/v1_16_R3/Vec2F + f F x i + f F y j +c net/minecraft/world/phys/Vec3 net/minecraft/server/v1_16_R3/Vec3D + m ()D x getX + m ()D y getY + m ()D z getZ diff --git a/spigot/src/main/resources/mappings/1.17.1.tiny b/spigot/src/main/resources/mappings/1.17.1.tiny index 60c5619..bf78016 100644 --- a/spigot/src/main/resources/mappings/1.17.1.tiny +++ b/spigot/src/main/resources/mappings/1.17.1.tiny @@ -7,9 +7,21 @@ c net/minecraft/commands/arguments/EntityArgument net/minecraft/commands/argumen m ()Lnet/minecraft/commands/arguments/EntityArgument; entities multipleEntities m ()Lnet/minecraft/commands/arguments/EntityArgument; player c m ()Lnet/minecraft/commands/arguments/EntityArgument; players d +c net/minecraft/commands/arguments/coordinates/BlockPosArgument net/minecraft/commands/arguments/coordinates/ArgumentPosition + m ()Lnet/minecraft/commands/arguments/coordinates/BlockPosArgument; blockPos a +c net/minecraft/commands/arguments/coordinates/Coordinates net/minecraft/commands/arguments/coordinates/IVectorPosition + m (Lnet/minecraft/commands/CommandSourceStack;)Lnet/minecraft/world/phys/Vec3; getPosition a + m (Lnet/minecraft/commands/CommandSourceStack;)Lnet/minecraft/world/phys/Vec2; getRotation b c net/minecraft/commands/arguments/selector/EntitySelector net/minecraft/commands/arguments/selector/EntitySelector m (Lnet/minecraft/commands/CommandSourceStack;)Lnet/minecraft/world/entity/Entity; findSingleEntity a m (Lnet/minecraft/commands/CommandSourceStack;)Ljava/util/List; findEntities getEntities m (Lnet/minecraft/commands/CommandSourceStack;)Lnet/minecraft/server/level/ServerPlayer; findSinglePlayer c m (Lnet/minecraft/commands/CommandSourceStack;)Ljava/util/List; findPlayers d c net/minecraft/world/entity/Entity net/minecraft/world/entity/Entity +c net/minecraft/world/phys/Vec2 net/minecraft/world/phys/Vec2F + f F x i + f F y j +c net/minecraft/world/phys/Vec3 net/minecraft/world/phys/Vec3D + m ()D x getX + m ()D y getY + m ()D z getZ diff --git a/spigot/src/main/resources/mappings/1.19.2.tiny b/spigot/src/main/resources/mappings/1.19.2.tiny index 94ac9cb..b6870e4 100644 --- a/spigot/src/main/resources/mappings/1.19.2.tiny +++ b/spigot/src/main/resources/mappings/1.19.2.tiny @@ -7,9 +7,21 @@ c net/minecraft/commands/arguments/EntityArgument net/minecraft/commands/argumen m ()Lnet/minecraft/commands/arguments/EntityArgument; entities b m ()Lnet/minecraft/commands/arguments/EntityArgument; player c m ()Lnet/minecraft/commands/arguments/EntityArgument; players d +c net/minecraft/commands/arguments/coordinates/BlockPosArgument net/minecraft/commands/arguments/coordinates/ArgumentPosition + m ()Lnet/minecraft/commands/arguments/coordinates/BlockPosArgument; blockPos a +c net/minecraft/commands/arguments/coordinates/Coordinates net/minecraft/commands/arguments/coordinates/IVectorPosition + m (Lnet/minecraft/commands/CommandSourceStack;)Lnet/minecraft/world/phys/Vec3; getPosition a + m (Lnet/minecraft/commands/CommandSourceStack;)Lnet/minecraft/world/phys/Vec2; getRotation b c net/minecraft/commands/arguments/selector/EntitySelector net/minecraft/commands/arguments/selector/EntitySelector m (Lnet/minecraft/commands/CommandSourceStack;)Lnet/minecraft/world/entity/Entity; findSingleEntity a m (Lnet/minecraft/commands/CommandSourceStack;)Ljava/util/List; findEntities b m (Lnet/minecraft/commands/CommandSourceStack;)Lnet/minecraft/server/level/ServerPlayer; findSinglePlayer c m (Lnet/minecraft/commands/CommandSourceStack;)Ljava/util/List; findPlayers d c net/minecraft/world/entity/Entity net/minecraft/world/entity/Entity +c net/minecraft/world/phys/Vec2 net/minecraft/world/phys/Vec2F + f F x i + f F y j +c net/minecraft/world/phys/Vec3 net/minecraft/world/phys/Vec3D + m ()D x a + m ()D y b + m ()D z c diff --git a/spigot/src/main/resources/mappings/1.21.4.tiny b/spigot/src/main/resources/mappings/1.21.4.tiny index 6c8f656..e26ce45 100644 --- a/spigot/src/main/resources/mappings/1.21.4.tiny +++ b/spigot/src/main/resources/mappings/1.21.4.tiny @@ -7,9 +7,21 @@ c net/minecraft/commands/arguments/EntityArgument net/minecraft/commands/argumen m ()Lnet/minecraft/commands/arguments/EntityArgument; entities b m ()Lnet/minecraft/commands/arguments/EntityArgument; player c m ()Lnet/minecraft/commands/arguments/EntityArgument; players d +c net/minecraft/commands/arguments/coordinates/BlockPosArgument net/minecraft/commands/arguments/coordinates/ArgumentPosition + m ()Lnet/minecraft/commands/arguments/coordinates/BlockPosArgument; blockPos a +c net/minecraft/commands/arguments/coordinates/Coordinates net/minecraft/commands/arguments/coordinates/IVectorPosition + m (Lnet/minecraft/commands/CommandSourceStack;)Lnet/minecraft/world/phys/Vec3; getPosition a + m (Lnet/minecraft/commands/CommandSourceStack;)Lnet/minecraft/world/phys/Vec2; getRotation b c net/minecraft/commands/arguments/selector/EntitySelector net/minecraft/commands/arguments/selector/EntitySelector m (Lnet/minecraft/commands/CommandSourceStack;)Lnet/minecraft/world/entity/Entity; findSingleEntity a m (Lnet/minecraft/commands/CommandSourceStack;)Ljava/util/List; findEntities b m (Lnet/minecraft/commands/CommandSourceStack;)Lnet/minecraft/server/level/ServerPlayer; findSinglePlayer c m (Lnet/minecraft/commands/CommandSourceStack;)Ljava/util/List; findPlayers d c net/minecraft/world/entity/Entity net/minecraft/world/entity/Entity +c net/minecraft/world/phys/Vec2 net/minecraft/world/phys/Vec2F + f F x i + f F y j +c net/minecraft/world/phys/Vec3 net/minecraft/world/phys/Vec3D + m ()D x a + m ()D y b + m ()D z c diff --git a/spigot/src/main/resources/mappings/1.21.5.tiny b/spigot/src/main/resources/mappings/1.21.5.tiny new file mode 100644 index 0000000..8e6d511 --- /dev/null +++ b/spigot/src/main/resources/mappings/1.21.5.tiny @@ -0,0 +1,27 @@ +tiny 2 0 source target +c net/minecraft/commands/CommandSourceStack net/minecraft/commands/CommandListenerWrapper + f Lnet/minecraft/world/entity/Entity; entity l +c net/minecraft/commands/Commands net/minecraft/commands/CommandDispatcher +c net/minecraft/commands/arguments/EntityArgument net/minecraft/commands/arguments/ArgumentEntity + m ()Lnet/minecraft/commands/arguments/EntityArgument; entity a + m ()Lnet/minecraft/commands/arguments/EntityArgument; entities b + m ()Lnet/minecraft/commands/arguments/EntityArgument; player c + m ()Lnet/minecraft/commands/arguments/EntityArgument; players d +c net/minecraft/commands/arguments/coordinates/BlockPosArgument net/minecraft/commands/arguments/coordinates/ArgumentPosition + m ()Lnet/minecraft/commands/arguments/coordinates/BlockPosArgument; blockPos a +c net/minecraft/commands/arguments/coordinates/Coordinates net/minecraft/commands/arguments/coordinates/IVectorPosition + m (Lnet/minecraft/commands/CommandSourceStack;)Lnet/minecraft/world/phys/Vec3; getPosition a + m (Lnet/minecraft/commands/CommandSourceStack;)Lnet/minecraft/world/phys/Vec2; getRotation b +c net/minecraft/commands/arguments/selector/EntitySelector net/minecraft/commands/arguments/selector/EntitySelector + m (Lnet/minecraft/commands/CommandSourceStack;)Lnet/minecraft/world/entity/Entity; findSingleEntity a + m (Lnet/minecraft/commands/CommandSourceStack;)Ljava/util/List; findEntities b + m (Lnet/minecraft/commands/CommandSourceStack;)Lnet/minecraft/server/level/ServerPlayer; findSinglePlayer c + m (Lnet/minecraft/commands/CommandSourceStack;)Ljava/util/List; findPlayers d +c net/minecraft/world/entity/Entity net/minecraft/world/entity/Entity +c net/minecraft/world/phys/Vec2 net/minecraft/world/phys/Vec2F + f F x j + f F y k +c net/minecraft/world/phys/Vec3 net/minecraft/world/phys/Vec3D + m ()D x a + m ()D y b + m ()D z c From f82726768019cd43e99c5b5421b32247db33f1ba Mon Sep 17 00:00:00 2001 From: Apehum Date: Thu, 29 Jan 2026 18:26:57 +0800 Subject: [PATCH 11/36] feat(server): game profiles brigadier argument --- .../command/brigadier/McArgumentTypes.kt | 8 ++++ .../McGameProfilesArgumentResolver.kt | 9 +++++ .../kotlin/su/plo/slib/server/TestServer.kt | 20 ++++++++++ ...ments.kt => MinestomBrigadierArguments.kt} | 17 +++++++- ...command.brigadier.McArgumentTypes$Provider | 2 +- .../brigadier/ModBrigadierArguments.kt | 11 ++++++ .../su/plo/slib/mod/extension/GameProfile.kt | 13 +++++++ scripts/smoke-test.sh | 1 + .../brigadier/SpigotBrigadierArguments.kt | 39 ++++++++++++++++++- .../spigot/nms/GameProfileArgumentProxy.kt | 13 +++++++ .../su/plo/slib/spigot/nms/Reflections.kt | 6 ++- .../src/main/resources/mappings/1.16.5.tiny | 2 + .../src/main/resources/mappings/1.17.1.tiny | 2 + .../src/main/resources/mappings/1.18.2.tiny | 29 ++++++++++++++ .../src/main/resources/mappings/1.19.2.tiny | 2 + .../src/main/resources/mappings/1.21.4.tiny | 2 + .../src/main/resources/mappings/1.21.5.tiny | 2 + 17 files changed, 172 insertions(+), 6 deletions(-) create mode 100644 api/server/src/main/kotlin/su/plo/slib/api/server/command/brigadier/McGameProfilesArgumentResolver.kt rename minestom/src/main/kotlin/su/plo/slib/minestom/command/brigadier/{MinestomEntityArguments.kt => MinestomBrigadierArguments.kt} (83%) create mode 100644 spigot/src/main/kotlin/su/plo/slib/spigot/nms/GameProfileArgumentProxy.kt create mode 100644 spigot/src/main/resources/mappings/1.18.2.tiny diff --git a/api/server/src/main/kotlin/su/plo/slib/api/server/command/brigadier/McArgumentTypes.kt b/api/server/src/main/kotlin/su/plo/slib/api/server/command/brigadier/McArgumentTypes.kt index 2449785..6501195 100644 --- a/api/server/src/main/kotlin/su/plo/slib/api/server/command/brigadier/McArgumentTypes.kt +++ b/api/server/src/main/kotlin/su/plo/slib/api/server/command/brigadier/McArgumentTypes.kt @@ -33,6 +33,12 @@ object McArgumentTypes { @JvmStatic fun players(): ArgumentType = provider.players() + /** + * Returns an argument type that resolves multiple game profiles. + */ + @JvmStatic + fun gameProfiles(): ArgumentType = provider.gameProfiles() + /** * Returns an argument type that resolves position. */ @@ -51,6 +57,8 @@ object McArgumentTypes { fun players(): ArgumentType + fun gameProfiles(): ArgumentType + fun position(): ArgumentType } } diff --git a/api/server/src/main/kotlin/su/plo/slib/api/server/command/brigadier/McGameProfilesArgumentResolver.kt b/api/server/src/main/kotlin/su/plo/slib/api/server/command/brigadier/McGameProfilesArgumentResolver.kt new file mode 100644 index 0000000..a3fa9f8 --- /dev/null +++ b/api/server/src/main/kotlin/su/plo/slib/api/server/command/brigadier/McGameProfilesArgumentResolver.kt @@ -0,0 +1,9 @@ +package su.plo.slib.api.server.command.brigadier + +import su.plo.slib.api.command.brigadier.ArgumentResolver +import su.plo.slib.api.entity.player.McGameProfile + +/** + * An [ArgumentResolver] that resolves multiple game profiles. + */ +fun interface McGameProfilesArgumentResolver : ArgumentResolver> diff --git a/common-server/src/testFixtures/kotlin/su/plo/slib/server/TestServer.kt b/common-server/src/testFixtures/kotlin/su/plo/slib/server/TestServer.kt index c64020b..a2b060d 100644 --- a/common-server/src/testFixtures/kotlin/su/plo/slib/server/TestServer.kt +++ b/common-server/src/testFixtures/kotlin/su/plo/slib/server/TestServer.kt @@ -11,6 +11,7 @@ import su.plo.slib.api.server.McServerLib import su.plo.slib.api.server.command.brigadier.McArgumentTypes import su.plo.slib.api.server.command.brigadier.McEntitiesArgumentResolver import su.plo.slib.api.server.command.brigadier.McEntityArgumentResolver +import su.plo.slib.api.server.command.brigadier.McGameProfilesArgumentResolver import su.plo.slib.api.server.command.brigadier.McPlayerArgumentResolver import su.plo.slib.api.server.command.brigadier.McPlayersArgumentResolver import su.plo.slib.api.server.command.brigadier.ServerPos3dResolver @@ -142,6 +143,25 @@ class TestServer( ) ) + commands.register( + McCommandManager.literal("brigadier-game-profiles-selector") + .then( + McCommandManager.argument("targets", McArgumentTypes.gameProfiles()) + .executes { + val resolver = it.getArgument( + "targets", + McGameProfilesArgumentResolver::class.java, + ) + val gameProfiles = resolver.resolve(it.source) + + val source = it.source + source.source.sendMessage("Found game profiles: $gameProfiles; Source: ${source.source}; Executor: ${source.executor}") + + Command.SINGLE_SUCCESS + } + ) + ) + commands.register( McCommandManager.literal("brigadier-position-selector") .then( diff --git a/minestom/src/main/kotlin/su/plo/slib/minestom/command/brigadier/MinestomEntityArguments.kt b/minestom/src/main/kotlin/su/plo/slib/minestom/command/brigadier/MinestomBrigadierArguments.kt similarity index 83% rename from minestom/src/main/kotlin/su/plo/slib/minestom/command/brigadier/MinestomEntityArguments.kt rename to minestom/src/main/kotlin/su/plo/slib/minestom/command/brigadier/MinestomBrigadierArguments.kt index 06cfa5b..bb80510 100644 --- a/minestom/src/main/kotlin/su/plo/slib/minestom/command/brigadier/MinestomEntityArguments.kt +++ b/minestom/src/main/kotlin/su/plo/slib/minestom/command/brigadier/MinestomBrigadierArguments.kt @@ -1,13 +1,16 @@ package su.plo.slib.minestom.command.brigadier import com.mojang.brigadier.arguments.ArgumentType +import net.minestom.server.MinecraftServer import net.minestom.server.command.builder.arguments.minecraft.ArgumentEntity import net.minestom.server.command.builder.arguments.relative.ArgumentRelativeBlockPosition import net.minestom.server.entity.Entity import net.minestom.server.entity.Player +import su.plo.slib.api.entity.player.McGameProfile import su.plo.slib.api.server.command.brigadier.McArgumentTypes import su.plo.slib.api.server.command.brigadier.McEntitiesArgumentResolver import su.plo.slib.api.server.command.brigadier.McEntityArgumentResolver +import su.plo.slib.api.server.command.brigadier.McGameProfilesArgumentResolver import su.plo.slib.api.server.command.brigadier.McPlayerArgumentResolver import su.plo.slib.api.server.command.brigadier.McPlayersArgumentResolver import su.plo.slib.api.server.command.brigadier.ServerPos3dResolver @@ -15,7 +18,7 @@ import su.plo.slib.api.server.entity.McServerEntity import su.plo.slib.api.server.position.ServerPos3d import su.plo.slib.minestom.MinestomServerLib -class MinestomEntityArguments : McArgumentTypes.Provider { +class MinestomBrigadierArguments : McArgumentTypes.Provider { private val serverLib by lazy { MinestomServerLib.instance } override fun entity(): ArgumentType = @@ -61,6 +64,18 @@ class MinestomEntityArguments : McArgumentTypes.Provider { } } + // todo: there's no way to get cached game profiles in minestom + override fun gameProfiles(): ArgumentType = + argumentResolver( + MinestomArgumentType { name -> ArgumentEntity(name).singleEntity(false).onlyPlayers(true) } + ) { finder -> + McGameProfilesArgumentResolver { source -> + finder.find((source as MinestomBrigadierSource).getInstance()) + .filterIsInstance() + .map { McGameProfile(it.uuid, it.username, emptyList()) } + } + } + override fun position(): ArgumentType = argumentResolver( MinestomArgumentType { name -> ArgumentRelativeBlockPosition(name) } diff --git a/minestom/src/main/resources/META-INF/services/su.plo.slib.api.server.command.brigadier.McArgumentTypes$Provider b/minestom/src/main/resources/META-INF/services/su.plo.slib.api.server.command.brigadier.McArgumentTypes$Provider index 4896f87..f742ba6 100644 --- a/minestom/src/main/resources/META-INF/services/su.plo.slib.api.server.command.brigadier.McArgumentTypes$Provider +++ b/minestom/src/main/resources/META-INF/services/su.plo.slib.api.server.command.brigadier.McArgumentTypes$Provider @@ -1 +1 @@ -su.plo.slib.minestom.command.brigadier.MinestomEntityArguments +su.plo.slib.minestom.command.brigadier.MinestomBrigadierArguments diff --git a/modded/src/main/kotlin/su/plo/slib/mod/command/brigadier/ModBrigadierArguments.kt b/modded/src/main/kotlin/su/plo/slib/mod/command/brigadier/ModBrigadierArguments.kt index af1636d..b06a752 100644 --- a/modded/src/main/kotlin/su/plo/slib/mod/command/brigadier/ModBrigadierArguments.kt +++ b/modded/src/main/kotlin/su/plo/slib/mod/command/brigadier/ModBrigadierArguments.kt @@ -4,17 +4,20 @@ import com.mojang.brigadier.StringReader import com.mojang.brigadier.arguments.ArgumentType import net.minecraft.commands.CommandSourceStack import net.minecraft.commands.arguments.EntityArgument +import net.minecraft.commands.arguments.GameProfileArgument import net.minecraft.commands.arguments.coordinates.BlockPosArgument import su.plo.slib.api.command.brigadier.CustomArgumentType import su.plo.slib.api.server.command.brigadier.McArgumentTypes import su.plo.slib.api.server.command.brigadier.McEntitiesArgumentResolver import su.plo.slib.api.server.command.brigadier.McEntityArgumentResolver +import su.plo.slib.api.server.command.brigadier.McGameProfilesArgumentResolver import su.plo.slib.api.server.command.brigadier.McPlayerArgumentResolver import su.plo.slib.api.server.command.brigadier.McPlayersArgumentResolver import su.plo.slib.api.server.command.brigadier.ServerPos3dResolver import su.plo.slib.api.server.entity.McServerEntity import su.plo.slib.api.server.position.ServerPos3d import su.plo.slib.mod.ModServerLib +import su.plo.slib.mod.extension.toMcGameProfile class ModBrigadierArguments : McArgumentTypes.Provider { private val serverLib by lazy { ModServerLib } @@ -40,6 +43,14 @@ class ModBrigadierArguments : McArgumentTypes.Provider { } } + override fun gameProfiles(): ArgumentType = + argumentResolver(GameProfileArgument.gameProfile()) { selector -> + McGameProfilesArgumentResolver { source -> + selector.getNames(source.getInstance()) + .map { it.toMcGameProfile() } + } + } + override fun players(): ArgumentType = argumentResolver(EntityArgument.players()) { selector -> McPlayersArgumentResolver { source -> diff --git a/modded/src/main/kotlin/su/plo/slib/mod/extension/GameProfile.kt b/modded/src/main/kotlin/su/plo/slib/mod/extension/GameProfile.kt index 8b7d4ea..a98f743 100644 --- a/modded/src/main/kotlin/su/plo/slib/mod/extension/GameProfile.kt +++ b/modded/src/main/kotlin/su/plo/slib/mod/extension/GameProfile.kt @@ -3,6 +3,10 @@ package su.plo.slib.mod.extension import com.mojang.authlib.GameProfile import su.plo.slib.api.entity.player.McGameProfile +//? if >=1.21.9 { +/*import net.minecraft.server.players.NameAndId +*///?} + fun GameProfile.toMcGameProfile(): McGameProfile = McGameProfile( id, @@ -11,3 +15,12 @@ fun GameProfile.toMcGameProfile(): McGameProfile = McGameProfile.Property(it.name, it.value, it.signature) } ) + +//? if >=1.21.9 { +/*fun NameAndId.toMcGameProfile(): McGameProfile = + McGameProfile( + id, + name, + emptyList(), + ) +*///?} diff --git a/scripts/smoke-test.sh b/scripts/smoke-test.sh index 71c38c5..62de65b 100755 --- a/scripts/smoke-test.sh +++ b/scripts/smoke-test.sh @@ -21,6 +21,7 @@ case "$ENV_TYPE" in "Command 'ping' registered" "Command 'brigadier-entity-selector' registered" "Command 'brigadier-position-selector' registered" + "Command 'brigadier-game-profiles-selector' registered" "Command 'brigadier-custom-type' registered" ) ;; diff --git a/spigot/src/main/kotlin/su/plo/slib/spigot/command/brigadier/SpigotBrigadierArguments.kt b/spigot/src/main/kotlin/su/plo/slib/spigot/command/brigadier/SpigotBrigadierArguments.kt index 078c21c..a79cf2d 100644 --- a/spigot/src/main/kotlin/su/plo/slib/spigot/command/brigadier/SpigotBrigadierArguments.kt +++ b/spigot/src/main/kotlin/su/plo/slib/spigot/command/brigadier/SpigotBrigadierArguments.kt @@ -3,9 +3,11 @@ package su.plo.slib.spigot.command.brigadier import com.mojang.brigadier.StringReader import com.mojang.brigadier.arguments.ArgumentType import su.plo.slib.api.command.brigadier.CustomArgumentType +import su.plo.slib.api.entity.player.McGameProfile import su.plo.slib.api.server.command.brigadier.McArgumentTypes import su.plo.slib.api.server.command.brigadier.McEntitiesArgumentResolver import su.plo.slib.api.server.command.brigadier.McEntityArgumentResolver +import su.plo.slib.api.server.command.brigadier.McGameProfilesArgumentResolver import su.plo.slib.api.server.command.brigadier.McPlayerArgumentResolver import su.plo.slib.api.server.command.brigadier.McPlayersArgumentResolver import su.plo.slib.api.server.command.brigadier.ServerPos3dResolver @@ -13,7 +15,9 @@ import su.plo.slib.api.server.entity.McServerEntity import su.plo.slib.api.server.position.ServerPos3d import su.plo.slib.spigot.SpigotServerLib import su.plo.slib.spigot.nms.ReflectionProxies +import java.lang.reflect.InvocationTargetException import java.lang.reflect.UndeclaredThrowableException +import java.util.UUID class SpigotBrigadierArguments: McArgumentTypes.Provider { private val serverLib by lazy { SpigotServerLib.instance } @@ -67,11 +71,40 @@ class SpigotBrigadierArguments: McArgumentTypes.Provider { } } + override fun gameProfiles(): ArgumentType = + argumentResolver(ReflectionProxies.gameProfileArgument.gameProfile()) { selector -> + McGameProfilesArgumentResolver { source -> + val getNamesMethod = selector.javaClass.declaredMethods + .first() + .also { it.isAccessible = true } + + @Suppress("UNCHECKED_CAST") + val names = rethrowProxyException { + getNamesMethod.invoke(selector, source.getInstance()) as Collection + } + + names.map { + // 1.21.9+ uses NameAndId, so we can't just use GameProfile here + val uuidMethod = it.javaClass.declaredMethods + .first { it.returnType == UUID::class.java } + .also { it.isAccessible = true } + val nameMethod = it.javaClass.declaredMethods + .first { it.returnType == String::class.java } + .also { it.isAccessible = true } + + val uuid = uuidMethod.invoke(it) as UUID + val name = nameMethod.invoke(it) as String + + McGameProfile(uuid, name, emptyList()) + } + } + } + override fun position(): ArgumentType = argumentResolver(ReflectionProxies.blockPosArgument.blockPos()) { coordinates -> ServerPos3dResolver { source -> - val position = ReflectionProxies.coordinatesProxy.getPosition(coordinates, source.getInstance()) - val rotation = ReflectionProxies.coordinatesProxy.getRotation(coordinates, source.getInstance()) + val position = ReflectionProxies.coordinates.getPosition(coordinates, source.getInstance()) + val rotation = ReflectionProxies.coordinates.getRotation(coordinates, source.getInstance()) val world = (source.executor as? McServerEntity)?.world @@ -99,6 +132,8 @@ class SpigotBrigadierArguments: McArgumentTypes.Provider { private fun rethrowProxyException(block: () -> T): T { try { return block() + } catch (e: InvocationTargetException) { + throw e.targetException } catch (e: UndeclaredThrowableException) { throw e.undeclaredThrowable } diff --git a/spigot/src/main/kotlin/su/plo/slib/spigot/nms/GameProfileArgumentProxy.kt b/spigot/src/main/kotlin/su/plo/slib/spigot/nms/GameProfileArgumentProxy.kt new file mode 100644 index 0000000..b908e2d --- /dev/null +++ b/spigot/src/main/kotlin/su/plo/slib/spigot/nms/GameProfileArgumentProxy.kt @@ -0,0 +1,13 @@ +package su.plo.slib.spigot.nms + +import com.mojang.brigadier.arguments.ArgumentType +import xyz.jpenilla.reflectionremapper.proxy.annotation.Proxies +import xyz.jpenilla.reflectionremapper.proxy.annotation.Static + +@Proxies( + className = "net.minecraft.commands.arguments.GameProfileArgument" +) +interface GameProfileArgumentProxy { + @Static + fun gameProfile(): ArgumentType +} diff --git a/spigot/src/main/kotlin/su/plo/slib/spigot/nms/Reflections.kt b/spigot/src/main/kotlin/su/plo/slib/spigot/nms/Reflections.kt index bae5eef..70b6485 100644 --- a/spigot/src/main/kotlin/su/plo/slib/spigot/nms/Reflections.kt +++ b/spigot/src/main/kotlin/su/plo/slib/spigot/nms/Reflections.kt @@ -17,9 +17,10 @@ object ReflectionProxies { val entityArgument: EntityArgumentProxy val entitySelector: EntitySelectorProxy val blockPosArgument: BlockPosArgumentProxy - val coordinatesProxy: CoordinatesProxy + val coordinates: CoordinatesProxy val vec3Proxy: Vec3Proxy val vec2Proxy: Vec2Proxy + val gameProfileArgument: GameProfileArgumentProxy init { val bukkitVersion = Bukkit.getVersion() @@ -58,9 +59,10 @@ object ReflectionProxies { entityArgument = proxyFactory.reflectionProxy() entitySelector = proxyFactory.reflectionProxy() blockPosArgument = proxyFactory.reflectionProxy() - coordinatesProxy = proxyFactory.reflectionProxy() + coordinates = proxyFactory.reflectionProxy() vec3Proxy = proxyFactory.reflectionProxy() vec2Proxy = proxyFactory.reflectionProxy() + gameProfileArgument = proxyFactory.reflectionProxy() } private inline fun ReflectionProxyFactory.reflectionProxy() = diff --git a/spigot/src/main/resources/mappings/1.16.5.tiny b/spigot/src/main/resources/mappings/1.16.5.tiny index 9e5ba51..faa6b64 100644 --- a/spigot/src/main/resources/mappings/1.16.5.tiny +++ b/spigot/src/main/resources/mappings/1.16.5.tiny @@ -7,6 +7,8 @@ c net/minecraft/commands/arguments/EntityArgument net/minecraft/server/v1_16_R3/ m ()Lnet/minecraft/commands/arguments/EntityArgument; entities multipleEntities m ()Lnet/minecraft/commands/arguments/EntityArgument; player c m ()Lnet/minecraft/commands/arguments/EntityArgument; players d +c net/minecraft/commands/arguments/GameProfileArgument net/minecraft/server/v1_16_R3/ArgumentProfile + m ()Lnet/minecraft/commands/arguments/GameProfileArgument; gameProfile a c net/minecraft/commands/arguments/coordinates/BlockPosArgument net/minecraft/server/v1_16_R3/ArgumentPosition m ()Lnet/minecraft/commands/arguments/coordinates/BlockPosArgument; blockPos a c net/minecraft/commands/arguments/coordinates/Coordinates net/minecraft/server/v1_16_R3/IVectorPosition diff --git a/spigot/src/main/resources/mappings/1.17.1.tiny b/spigot/src/main/resources/mappings/1.17.1.tiny index bf78016..2dc7a7c 100644 --- a/spigot/src/main/resources/mappings/1.17.1.tiny +++ b/spigot/src/main/resources/mappings/1.17.1.tiny @@ -7,6 +7,8 @@ c net/minecraft/commands/arguments/EntityArgument net/minecraft/commands/argumen m ()Lnet/minecraft/commands/arguments/EntityArgument; entities multipleEntities m ()Lnet/minecraft/commands/arguments/EntityArgument; player c m ()Lnet/minecraft/commands/arguments/EntityArgument; players d +c net/minecraft/commands/arguments/GameProfileArgument net/minecraft/commands/arguments/ArgumentProfile + m ()Lnet/minecraft/commands/arguments/GameProfileArgument; gameProfile a c net/minecraft/commands/arguments/coordinates/BlockPosArgument net/minecraft/commands/arguments/coordinates/ArgumentPosition m ()Lnet/minecraft/commands/arguments/coordinates/BlockPosArgument; blockPos a c net/minecraft/commands/arguments/coordinates/Coordinates net/minecraft/commands/arguments/coordinates/IVectorPosition diff --git a/spigot/src/main/resources/mappings/1.18.2.tiny b/spigot/src/main/resources/mappings/1.18.2.tiny new file mode 100644 index 0000000..f66aee8 --- /dev/null +++ b/spigot/src/main/resources/mappings/1.18.2.tiny @@ -0,0 +1,29 @@ +tiny 2 0 source target +c net/minecraft/commands/CommandSourceStack net/minecraft/commands/CommandListenerWrapper + f Lnet/minecraft/world/entity/Entity; entity k +c net/minecraft/commands/Commands net/minecraft/commands/CommandDispatcher +c net/minecraft/commands/arguments/EntityArgument net/minecraft/commands/arguments/ArgumentEntity + m ()Lnet/minecraft/commands/arguments/EntityArgument; entity a + m ()Lnet/minecraft/commands/arguments/EntityArgument; entities multipleEntities + m ()Lnet/minecraft/commands/arguments/EntityArgument; player c + m ()Lnet/minecraft/commands/arguments/EntityArgument; players d +c net/minecraft/commands/arguments/GameProfileArgument net/minecraft/commands/arguments/ArgumentProfile + m ()Lnet/minecraft/commands/arguments/GameProfileArgument; gameProfile a +c net/minecraft/commands/arguments/coordinates/BlockPosArgument net/minecraft/commands/arguments/coordinates/ArgumentPosition + m ()Lnet/minecraft/commands/arguments/coordinates/BlockPosArgument; blockPos a +c net/minecraft/commands/arguments/coordinates/Coordinates net/minecraft/commands/arguments/coordinates/IVectorPosition + m (Lnet/minecraft/commands/CommandSourceStack;)Lnet/minecraft/world/phys/Vec3; getPosition a + m (Lnet/minecraft/commands/CommandSourceStack;)Lnet/minecraft/world/phys/Vec2; getRotation b +c net/minecraft/commands/arguments/selector/EntitySelector net/minecraft/commands/arguments/selector/EntitySelector + m (Lnet/minecraft/commands/CommandSourceStack;)Lnet/minecraft/world/entity/Entity; findSingleEntity a + m (Lnet/minecraft/commands/CommandSourceStack;)Ljava/util/List; findEntities getEntities + m (Lnet/minecraft/commands/CommandSourceStack;)Lnet/minecraft/server/level/ServerPlayer; findSinglePlayer c + m (Lnet/minecraft/commands/CommandSourceStack;)Ljava/util/List; findPlayers d +c net/minecraft/world/entity/Entity net/minecraft/world/entity/Entity +c net/minecraft/world/phys/Vec2 net/minecraft/world/phys/Vec2F + f F x i + f F y j +c net/minecraft/world/phys/Vec3 net/minecraft/world/phys/Vec3D + m ()D x a + m ()D y b + m ()D z c diff --git a/spigot/src/main/resources/mappings/1.19.2.tiny b/spigot/src/main/resources/mappings/1.19.2.tiny index b6870e4..2cc64db 100644 --- a/spigot/src/main/resources/mappings/1.19.2.tiny +++ b/spigot/src/main/resources/mappings/1.19.2.tiny @@ -7,6 +7,8 @@ c net/minecraft/commands/arguments/EntityArgument net/minecraft/commands/argumen m ()Lnet/minecraft/commands/arguments/EntityArgument; entities b m ()Lnet/minecraft/commands/arguments/EntityArgument; player c m ()Lnet/minecraft/commands/arguments/EntityArgument; players d +c net/minecraft/commands/arguments/GameProfileArgument net/minecraft/commands/arguments/ArgumentProfile + m ()Lnet/minecraft/commands/arguments/GameProfileArgument; gameProfile a c net/minecraft/commands/arguments/coordinates/BlockPosArgument net/minecraft/commands/arguments/coordinates/ArgumentPosition m ()Lnet/minecraft/commands/arguments/coordinates/BlockPosArgument; blockPos a c net/minecraft/commands/arguments/coordinates/Coordinates net/minecraft/commands/arguments/coordinates/IVectorPosition diff --git a/spigot/src/main/resources/mappings/1.21.4.tiny b/spigot/src/main/resources/mappings/1.21.4.tiny index e26ce45..8305cea 100644 --- a/spigot/src/main/resources/mappings/1.21.4.tiny +++ b/spigot/src/main/resources/mappings/1.21.4.tiny @@ -7,6 +7,8 @@ c net/minecraft/commands/arguments/EntityArgument net/minecraft/commands/argumen m ()Lnet/minecraft/commands/arguments/EntityArgument; entities b m ()Lnet/minecraft/commands/arguments/EntityArgument; player c m ()Lnet/minecraft/commands/arguments/EntityArgument; players d +c net/minecraft/commands/arguments/GameProfileArgument net/minecraft/commands/arguments/ArgumentProfile + m ()Lnet/minecraft/commands/arguments/GameProfileArgument; gameProfile a c net/minecraft/commands/arguments/coordinates/BlockPosArgument net/minecraft/commands/arguments/coordinates/ArgumentPosition m ()Lnet/minecraft/commands/arguments/coordinates/BlockPosArgument; blockPos a c net/minecraft/commands/arguments/coordinates/Coordinates net/minecraft/commands/arguments/coordinates/IVectorPosition diff --git a/spigot/src/main/resources/mappings/1.21.5.tiny b/spigot/src/main/resources/mappings/1.21.5.tiny index 8e6d511..c30e4e6 100644 --- a/spigot/src/main/resources/mappings/1.21.5.tiny +++ b/spigot/src/main/resources/mappings/1.21.5.tiny @@ -7,6 +7,8 @@ c net/minecraft/commands/arguments/EntityArgument net/minecraft/commands/argumen m ()Lnet/minecraft/commands/arguments/EntityArgument; entities b m ()Lnet/minecraft/commands/arguments/EntityArgument; player c m ()Lnet/minecraft/commands/arguments/EntityArgument; players d +c net/minecraft/commands/arguments/GameProfileArgument net/minecraft/commands/arguments/ArgumentProfile + m ()Lnet/minecraft/commands/arguments/GameProfileArgument; gameProfile a c net/minecraft/commands/arguments/coordinates/BlockPosArgument net/minecraft/commands/arguments/coordinates/ArgumentPosition m ()Lnet/minecraft/commands/arguments/coordinates/BlockPosArgument; blockPos a c net/minecraft/commands/arguments/coordinates/Coordinates net/minecraft/commands/arguments/coordinates/IVectorPosition From d33fc34e34d7e3aa085d400eaee08047f809d3bf Mon Sep 17 00:00:00 2001 From: Apehum Date: Fri, 30 Jan 2026 09:37:54 +0800 Subject: [PATCH 12/36] fix: proxy customSuggestions in brigadier commands --- .../su/plo/slib/command/AbstractCommandManager.kt | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/common/src/main/kotlin/su/plo/slib/command/AbstractCommandManager.kt b/common/src/main/kotlin/su/plo/slib/command/AbstractCommandManager.kt index 53bb045..bb8bf46 100644 --- a/common/src/main/kotlin/su/plo/slib/command/AbstractCommandManager.kt +++ b/common/src/main/kotlin/su/plo/slib/command/AbstractCommandManager.kt @@ -135,12 +135,16 @@ fun CommandNode.toProxyNode( command?.let { command -> node.executes { context -> val context = contextFactory(context) + command.run(context) + } + } - try { - command.run(context) - } catch (e: Throwable) { - e.printStackTrace() - throw e + if (this is ArgumentCommandNode) { + val node = node as RequiredArgumentBuilder + if (this.customSuggestions != null) { + node.suggests { context, builder -> + val context = contextFactory(context) + listSuggestions(context, builder) } } } From c5363f6675c5aac0323e45afdf8e0506d1723499 Mon Sep 17 00:00:00 2001 From: Apehum Date: Tue, 21 Apr 2026 06:53:28 +0800 Subject: [PATCH 13/36] fix(mod): disable refmap in mixin file on >=26.1 --- modded/src/main/resources/slib.mixins.json5 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modded/src/main/resources/slib.mixins.json5 b/modded/src/main/resources/slib.mixins.json5 index 1e10527..7960c13 100644 --- a/modded/src/main/resources/slib.mixins.json5 +++ b/modded/src/main/resources/slib.mixins.json5 @@ -18,7 +18,7 @@ "injectors": { "defaultRequire": 1 } - //? if !neoforge { + //? if !neoforge && <26.1 { ,"refmap": "slib-${loader}-${mcVersion}-modded_${mcVersion}-${loader}-refmap.json", //?} } From ccca826253a97bbfb538ca2f7b6d7f0b87d13ecd Mon Sep 17 00:00:00 2001 From: Apehum Date: Tue, 21 Apr 2026 07:15:15 +0800 Subject: [PATCH 14/36] ci: add 26.1.2 to paper matrix --- .github/workflows/build-pr.yml | 1 + spigot/build.gradle.kts | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/build-pr.yml b/.github/workflows/build-pr.yml index cd64044..c821e47 100644 --- a/.github/workflows/build-pr.yml +++ b/.github/workflows/build-pr.yml @@ -87,6 +87,7 @@ jobs: - 1.21.1 - 1.21.8 - 1.21.11 + - 26.1.2 runs-on: ubuntu-latest needs: build diff --git a/spigot/build.gradle.kts b/spigot/build.gradle.kts index 41641f3..2caa95e 100644 --- a/spigot/build.gradle.kts +++ b/spigot/build.gradle.kts @@ -99,6 +99,7 @@ tasks { val mcSemVersion = Semver(mcVersion) val javaVersion = when { + mcSemVersion.satisfies(">=26.1") -> 25 mcSemVersion.satisfies(">=1.20.5") -> 21 else -> 17 } From e1b90827746dd891939c495ff1ab932b4999be0a Mon Sep 17 00:00:00 2001 From: Apehum Date: Tue, 21 Apr 2026 07:15:48 +0800 Subject: [PATCH 15/36] fix(mod): add missing mapping for ServerPlayer$3 --- modded/1.21.11-26.1.txt | 2 ++ modded/versions/26.1-fabric/gradle.properties | 4 ++-- modded/versions/26.1-neoforge/gradle.properties | 4 ++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/modded/1.21.11-26.1.txt b/modded/1.21.11-26.1.txt index 8300c64..38f4861 100644 --- a/modded/1.21.11-26.1.txt +++ b/modded/1.21.11-26.1.txt @@ -3,3 +3,5 @@ playS2C clientboundPlay S2CPlayChannelEvents ClientboundPlayChannelEvents S2CConfigurationChannelEvents ClientboundConfigurationChannelEvents + +field_54403 this$0 diff --git a/modded/versions/26.1-fabric/gradle.properties b/modded/versions/26.1-fabric/gradle.properties index 32016a9..5cd97e0 100644 --- a/modded/versions/26.1-fabric/gradle.properties +++ b/modded/versions/26.1-fabric/gradle.properties @@ -1,4 +1,4 @@ loom.platform=fabric -deps.minecraft=26.1-rc-1 -deps.fabric_api=0.143.14+26.1 +deps.minecraft=26.1.2 +deps.fabric_api=0.146.1+26.1.2 diff --git a/modded/versions/26.1-neoforge/gradle.properties b/modded/versions/26.1-neoforge/gradle.properties index 1553930..5456c5a 100644 --- a/modded/versions/26.1-neoforge/gradle.properties +++ b/modded/versions/26.1-neoforge/gradle.properties @@ -1,4 +1,4 @@ loom.platform=neoforge -deps.minecraft=26.1-pre-3 -deps.neoforge=26.1.0.0-alpha.15+pre-3 +deps.minecraft=26.1.2 +deps.neoforge=26.1.2.22-beta From a7970f38a1368304316d3447f72c3b5eba780d54 Mon Sep 17 00:00:00 2001 From: Apehum Date: Wed, 22 Apr 2026 20:16:22 +0800 Subject: [PATCH 16/36] build: bump bungee java version to 11 --- bungee/build.gradle.kts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/bungee/build.gradle.kts b/bungee/build.gradle.kts index e13b403..579c769 100644 --- a/bungee/build.gradle.kts +++ b/bungee/build.gradle.kts @@ -49,6 +49,8 @@ runWaterfallExtension { disablePluginJarDetection() } +java.toolchain.languageVersion.set(JavaLanguageVersion.of(11)) + tasks { shadowJar { archiveClassifier = "all" @@ -79,6 +81,10 @@ tasks { runWaterfall { waterfallVersion("1.21") pluginJars.from(testJar) + + javaLauncher = project.javaToolchains.launcherFor { + languageVersion = JavaLanguageVersion.of(21) + } } build { From ea753551f7b73df24e5a58daa886ec9e39b6e335 Mon Sep 17 00:00:00 2001 From: Apehum Date: Wed, 22 Apr 2026 21:02:24 +0800 Subject: [PATCH 17/36] refactor: use LiteralCommandNode to register brigadier comamnds in McCommandManager --- .../plo/slib/api/command/McCommandManager.kt | 16 +++++++++-- .../slib/command/AbstractCommandManager.kt | 27 +++++++++---------- 2 files changed, 26 insertions(+), 17 deletions(-) diff --git a/api/common/src/main/kotlin/su/plo/slib/api/command/McCommandManager.kt b/api/common/src/main/kotlin/su/plo/slib/api/command/McCommandManager.kt index 5846e14..60078c2 100644 --- a/api/common/src/main/kotlin/su/plo/slib/api/command/McCommandManager.kt +++ b/api/common/src/main/kotlin/su/plo/slib/api/command/McCommandManager.kt @@ -3,6 +3,7 @@ package su.plo.slib.api.command import com.mojang.brigadier.arguments.ArgumentType import com.mojang.brigadier.builder.LiteralArgumentBuilder import com.mojang.brigadier.builder.RequiredArgumentBuilder +import com.mojang.brigadier.tree.LiteralCommandNode import su.plo.slib.api.command.brigadier.McBrigadierSource /** @@ -27,7 +28,7 @@ abstract class McCommandManager { * * @return A list containing the registered commands with their names as keys. */ - abstract val registeredBrigadierCommands: List> + abstract val registeredBrigadierCommands: List> /** * Registers a brigadier command. @@ -36,7 +37,18 @@ abstract class McCommandManager { * @throws IllegalStateException If attempting to register commands after commands have already been registered. * @throws IllegalArgumentException If a command with the same name or alias already exists. */ - abstract fun register(command: LiteralArgumentBuilder) + fun register(command: LiteralArgumentBuilder) { + register(command.build()) + } + + /** + * Registers a brigadier command. + * + * @param command The instance of the command to register. + * @throws IllegalStateException If attempting to register commands after commands have already been registered. + * @throws IllegalArgumentException If a command with the same name or alias already exists. + */ + abstract fun register(command: LiteralCommandNode) /** * Registers a command with its name and optional aliases. diff --git a/common/src/main/kotlin/su/plo/slib/command/AbstractCommandManager.kt b/common/src/main/kotlin/su/plo/slib/command/AbstractCommandManager.kt index bb8bf46..31d9293 100644 --- a/common/src/main/kotlin/su/plo/slib/command/AbstractCommandManager.kt +++ b/common/src/main/kotlin/su/plo/slib/command/AbstractCommandManager.kt @@ -22,7 +22,7 @@ abstract class AbstractCommandManager : McCommandManager() { private val logger = McLoggerFactory.createLogger("CommandManager") protected val commandByName: MutableMap = Maps.newHashMap() - protected var brigadierCommands: MutableList> = mutableListOf() + protected var brigadierCommands: MutableList> = mutableListOf() protected var registered = false @@ -31,11 +31,11 @@ abstract class AbstractCommandManager : McCommandManager() { get() = ImmutableMap.copyOf(commandByName) @get:Synchronized - override val registeredBrigadierCommands: List> + override val registeredBrigadierCommands: List> get() = ImmutableList.copyOf(brigadierCommands) @Synchronized - override fun register(command: LiteralArgumentBuilder) { + override fun register(command: LiteralCommandNode) { check(!registered) { "register after commands registration is not supported" } require(brigadierCommands.none { it.literal == command.literal }) { "Command with name '${command.literal}' already exist" } @@ -70,9 +70,9 @@ abstract class AbstractCommandManager : McCommandManager() { } } - protected fun registerBrigadierCommands(register: (LiteralArgumentBuilder) -> Unit) { + protected fun registerBrigadierCommands(registerCommand: (LiteralCommandNode) -> Unit) { brigadierCommands.forEach { command -> - register(command) + registerCommand(command) logger.info("Command '${command.literal}' registered") } } @@ -82,11 +82,11 @@ abstract class AbstractCommandManager : McCommandManager() { fun CommandContext.copyFor(source: T): CommandContext = (this as CommandContext).copyFor(source) -fun LiteralArgumentBuilder.proxied( +fun LiteralCommandNode.proxied( sourceFactory: (S) -> McBrigadierSource, contextFactory: (CommandContext) -> CommandContext, ): LiteralCommandNode = - build().toProxyNode(sourceFactory, contextFactory) as LiteralCommandNode + toProxyNode(sourceFactory, contextFactory) as LiteralCommandNode fun CommandNode.toProxyNode( sourceFactory: (S) -> McBrigadierSource, @@ -96,7 +96,7 @@ fun CommandNode.toProxyNode( when (this) { is LiteralCommandNode -> LiteralArgumentBuilder.literal(literal) is ArgumentCommandNode -> - RequiredArgumentBuilder.argument(name, type as ArgumentType) + RequiredArgumentBuilder.argument(name, type as ArgumentType) else -> throw IllegalArgumentException("Unsupported command node: $this") } @@ -106,12 +106,9 @@ fun CommandNode.toProxyNode( if (modifier == null) { node.redirect(redirect.toProxyNode(sourceFactory, contextFactory)) } else { - val proxiedModifier = object : RedirectModifier { - override fun apply(context: CommandContext): Collection { - val context = contextFactory(context) - - return modifier.apply(context).map { it.getInstance() } - } + val proxiedModifier = RedirectModifier { context -> + val context = contextFactory(context) + modifier.apply(context).map { it.getInstance() } } node.fork( @@ -122,7 +119,7 @@ fun CommandNode.toProxyNode( } children - .map { it.toProxyNode(sourceFactory, contextFactory) } + .map { it.toProxyNode(sourceFactory, contextFactory) } .forEach { node.then(it) } requirement?.let { requirement -> From 869501820331a874c4facf5169d7c3e9db823c46 Mon Sep 17 00:00:00 2001 From: Apehum Date: Thu, 23 Apr 2026 00:10:34 +0800 Subject: [PATCH 18/36] feat: add MessageTextConverter to support McTextComponent inside CommandSyntaxException --- .../chat/converter/MessageTextConverter.kt | 33 +++++ .../chat/ComponentToMessageConverter.kt | 10 ++ .../su/plo/slib/bungee/chat/McTextMessage.kt | 10 ++ .../brigadier/BungeeBrigadierCommand.kt | 8 +- ...ib.api.chat.converter.MessageTextConverter | 1 + .../slib/server/command/UuidArgumentType.kt | 16 ++- .../slib/command/AbstractCommandManager.kt | 1 + .../chat/ComponentToMessageConverter.kt | 10 ++ .../plo/slib/minestom/chat/McTextMessage.kt | 10 ++ .../command/MinestomCommandManager.kt | 113 ++++++++++-------- ...ib.api.chat.converter.MessageTextConverter | 1 + .../mod/chat/ComponentToMessageConverter.kt | 10 ++ ...ib.api.chat.converter.MessageTextConverter | 1 + .../chat/ComponentToMessageConverter.kt | 19 +++ ...ib.api.chat.converter.MessageTextConverter | 1 + .../chat/ComponentToMessageConverter.kt | 18 +++ ...ib.api.chat.converter.MessageTextConverter | 1 + 17 files changed, 206 insertions(+), 57 deletions(-) create mode 100644 api/common/src/main/kotlin/su/plo/slib/api/chat/converter/MessageTextConverter.kt create mode 100644 bungee/src/main/kotlin/su/plo/slib/bungee/chat/ComponentToMessageConverter.kt create mode 100644 bungee/src/main/kotlin/su/plo/slib/bungee/chat/McTextMessage.kt create mode 100644 bungee/src/main/resources/META-INF/services/su.plo.slib.api.chat.converter.MessageTextConverter create mode 100644 minestom/src/main/kotlin/su/plo/slib/minestom/chat/ComponentToMessageConverter.kt create mode 100644 minestom/src/main/kotlin/su/plo/slib/minestom/chat/McTextMessage.kt create mode 100644 minestom/src/main/resources/META-INF/services/su.plo.slib.api.chat.converter.MessageTextConverter create mode 100644 modded/src/main/kotlin/su/plo/slib/mod/chat/ComponentToMessageConverter.kt create mode 100644 modded/src/main/resources/META-INF/services/su.plo.slib.api.chat.converter.MessageTextConverter create mode 100644 spigot/src/main/kotlin/su/plo/slib/spigot/chat/ComponentToMessageConverter.kt create mode 100644 spigot/src/main/resources/META-INF/services/su.plo.slib.api.chat.converter.MessageTextConverter create mode 100644 velocity/src/main/kotlin/su/plo/slib/velocity/chat/ComponentToMessageConverter.kt create mode 100644 velocity/src/main/resources/META-INF/services/su.plo.slib.api.chat.converter.MessageTextConverter diff --git a/api/common/src/main/kotlin/su/plo/slib/api/chat/converter/MessageTextConverter.kt b/api/common/src/main/kotlin/su/plo/slib/api/chat/converter/MessageTextConverter.kt new file mode 100644 index 0000000..d3e32a2 --- /dev/null +++ b/api/common/src/main/kotlin/su/plo/slib/api/chat/converter/MessageTextConverter.kt @@ -0,0 +1,33 @@ +package su.plo.slib.api.chat.converter + +import com.mojang.brigadier.Message +import su.plo.slib.api.chat.component.McTextComponent +import su.plo.slib.api.service.lazyService + +/** + * Converts a [McTextComponent] into a platform-specific [Message] suitable for use with brigadier + * (e.g. as the message inside a `CommandSyntaxException`). + * + * Implementations preserve literal and translatable components where the platform allows — typically + * either by producing a native [Message] implementation (Vanilla `Component`, Velocity's + * `VelocityBrigadierMessage`) or by wrapping the component in a platform-specific [Message] that + * the command error-handling path recognizes and unwraps. + */ +interface MessageTextConverter { + + /** + * Converts a [McTextComponent] into a brigadier [Message]. + * + * @param text The [McTextComponent] to convert. + * @return A [Message] representing the converted text component. + */ + fun convert(text: McTextComponent): Message + + companion object { + private val provider: MessageTextConverter by lazyService() + + @JvmStatic + fun converter(): MessageTextConverter = + provider + } +} diff --git a/bungee/src/main/kotlin/su/plo/slib/bungee/chat/ComponentToMessageConverter.kt b/bungee/src/main/kotlin/su/plo/slib/bungee/chat/ComponentToMessageConverter.kt new file mode 100644 index 0000000..77b0044 --- /dev/null +++ b/bungee/src/main/kotlin/su/plo/slib/bungee/chat/ComponentToMessageConverter.kt @@ -0,0 +1,10 @@ +package su.plo.slib.bungee.chat + +import com.mojang.brigadier.Message +import su.plo.slib.api.chat.component.McTextComponent +import su.plo.slib.api.chat.converter.MessageTextConverter + +class ComponentToMessageConverter : MessageTextConverter { + override fun convert(text: McTextComponent): Message = + McTextMessage(text) +} diff --git a/bungee/src/main/kotlin/su/plo/slib/bungee/chat/McTextMessage.kt b/bungee/src/main/kotlin/su/plo/slib/bungee/chat/McTextMessage.kt new file mode 100644 index 0000000..7830e4d --- /dev/null +++ b/bungee/src/main/kotlin/su/plo/slib/bungee/chat/McTextMessage.kt @@ -0,0 +1,10 @@ +package su.plo.slib.bungee.chat + +import com.mojang.brigadier.Message +import su.plo.slib.api.chat.component.McTextComponent + +class McTextMessage( + val component: McTextComponent, +) : Message { + override fun getString(): String = component.toString() +} diff --git a/bungee/src/main/kotlin/su/plo/slib/bungee/command/brigadier/BungeeBrigadierCommand.kt b/bungee/src/main/kotlin/su/plo/slib/bungee/command/brigadier/BungeeBrigadierCommand.kt index be648c6..ee25a14 100644 --- a/bungee/src/main/kotlin/su/plo/slib/bungee/command/brigadier/BungeeBrigadierCommand.kt +++ b/bungee/src/main/kotlin/su/plo/slib/bungee/command/brigadier/BungeeBrigadierCommand.kt @@ -9,6 +9,7 @@ import net.md_5.bungee.api.plugin.TabExecutor import su.plo.slib.api.chat.component.McTextComponent import su.plo.slib.api.chat.style.McTextStyle import su.plo.slib.api.command.brigadier.McBrigadierSource +import su.plo.slib.bungee.chat.McTextMessage import su.plo.slib.bungee.command.BungeeCommandManager class BungeeBrigadierCommand( @@ -28,10 +29,15 @@ class BungeeBrigadierCommand( try { dispatcher.execute(input, context) } catch (e: CommandSyntaxException) { + val rawMessage = e.rawMessage + val messageArg = + if (rawMessage is McTextMessage) rawMessage.component + else McTextComponent.literal(rawMessage.string) + context.source.sendMessage( McTextComponent.translatable( "command.context.parse_error", - McTextComponent.literal(e.rawMessage.string), + messageArg, McTextComponent.literal(e.cursor.toString()), McTextComponent.literal(e.context), ).withStyle(McTextStyle.RED) diff --git a/bungee/src/main/resources/META-INF/services/su.plo.slib.api.chat.converter.MessageTextConverter b/bungee/src/main/resources/META-INF/services/su.plo.slib.api.chat.converter.MessageTextConverter new file mode 100644 index 0000000..8898785 --- /dev/null +++ b/bungee/src/main/resources/META-INF/services/su.plo.slib.api.chat.converter.MessageTextConverter @@ -0,0 +1 @@ +su.plo.slib.bungee.chat.ComponentToMessageConverter diff --git a/common-server/src/testFixtures/kotlin/su/plo/slib/server/command/UuidArgumentType.kt b/common-server/src/testFixtures/kotlin/su/plo/slib/server/command/UuidArgumentType.kt index 7b9e6f0..33bb0fa 100644 --- a/common-server/src/testFixtures/kotlin/su/plo/slib/server/command/UuidArgumentType.kt +++ b/common-server/src/testFixtures/kotlin/su/plo/slib/server/command/UuidArgumentType.kt @@ -1,14 +1,14 @@ package su.plo.slib.server.command -import com.mojang.brigadier.LiteralMessage import com.mojang.brigadier.StringReader import com.mojang.brigadier.arguments.ArgumentType import com.mojang.brigadier.arguments.StringArgumentType import com.mojang.brigadier.context.CommandContext -import com.mojang.brigadier.exceptions.CommandSyntaxException import com.mojang.brigadier.exceptions.SimpleCommandExceptionType import com.mojang.brigadier.suggestion.Suggestions import com.mojang.brigadier.suggestion.SuggestionsBuilder +import su.plo.slib.api.chat.component.McTextComponent +import su.plo.slib.api.chat.converter.MessageTextConverter import su.plo.slib.api.command.brigadier.CustomArgumentType import java.util.UUID import java.util.concurrent.CompletableFuture @@ -18,7 +18,13 @@ class UuidArgumentType : CustomArgumentType { override fun useNativeSuggestions(): Boolean = false - private val invalidUuid = SimpleCommandExceptionType(LiteralMessage("Failed to parse UUID")) + private val invalidUuid = SimpleCommandExceptionType( + MessageTextConverter.converter().convert( + McTextComponent.translatable( + "argument.uuid.invalid", + ) + ) + ) override fun parse(reader: StringReader): UUID? { val input = reader.readString() @@ -27,8 +33,8 @@ class UuidArgumentType : CustomArgumentType { try { return UUID.fromString(input) - } catch (e: IllegalArgumentException) { - throw CommandSyntaxException(invalidUuid, LiteralMessage(e.message), input, 0) + } catch (_: IllegalArgumentException) { + throw invalidUuid.createWithContext(reader) } } diff --git a/common/src/main/kotlin/su/plo/slib/command/AbstractCommandManager.kt b/common/src/main/kotlin/su/plo/slib/command/AbstractCommandManager.kt index 31d9293..19dcd70 100644 --- a/common/src/main/kotlin/su/plo/slib/command/AbstractCommandManager.kt +++ b/common/src/main/kotlin/su/plo/slib/command/AbstractCommandManager.kt @@ -8,6 +8,7 @@ import com.mojang.brigadier.arguments.ArgumentType import com.mojang.brigadier.builder.LiteralArgumentBuilder import com.mojang.brigadier.builder.RequiredArgumentBuilder import com.mojang.brigadier.context.CommandContext +import com.mojang.brigadier.exceptions.CommandSyntaxException import com.mojang.brigadier.tree.ArgumentCommandNode import com.mojang.brigadier.tree.CommandNode import com.mojang.brigadier.tree.LiteralCommandNode diff --git a/minestom/src/main/kotlin/su/plo/slib/minestom/chat/ComponentToMessageConverter.kt b/minestom/src/main/kotlin/su/plo/slib/minestom/chat/ComponentToMessageConverter.kt new file mode 100644 index 0000000..56c2872 --- /dev/null +++ b/minestom/src/main/kotlin/su/plo/slib/minestom/chat/ComponentToMessageConverter.kt @@ -0,0 +1,10 @@ +package su.plo.slib.minestom.chat + +import com.mojang.brigadier.Message +import su.plo.slib.api.chat.component.McTextComponent +import su.plo.slib.api.chat.converter.MessageTextConverter + +class ComponentToMessageConverter : MessageTextConverter { + override fun convert(text: McTextComponent): Message = + McTextMessage(text) +} diff --git a/minestom/src/main/kotlin/su/plo/slib/minestom/chat/McTextMessage.kt b/minestom/src/main/kotlin/su/plo/slib/minestom/chat/McTextMessage.kt new file mode 100644 index 0000000..26c6a07 --- /dev/null +++ b/minestom/src/main/kotlin/su/plo/slib/minestom/chat/McTextMessage.kt @@ -0,0 +1,10 @@ +package su.plo.slib.minestom.chat + +import com.mojang.brigadier.Message +import su.plo.slib.api.chat.component.McTextComponent + +class McTextMessage( + val component: McTextComponent, +) : Message { + override fun getString(): String = component.toString() +} diff --git a/minestom/src/main/kotlin/su/plo/slib/minestom/command/MinestomCommandManager.kt b/minestom/src/main/kotlin/su/plo/slib/minestom/command/MinestomCommandManager.kt index 537b5d6..68a01c7 100644 --- a/minestom/src/main/kotlin/su/plo/slib/minestom/command/MinestomCommandManager.kt +++ b/minestom/src/main/kotlin/su/plo/slib/minestom/command/MinestomCommandManager.kt @@ -22,9 +22,7 @@ import net.minestom.server.command.CommandSender import net.minestom.server.command.builder.Command import net.minestom.server.command.builder.CommandExecutor import net.minestom.server.command.builder.arguments.Argument -import net.minestom.server.command.builder.arguments.minecraft.SuggestionType import net.minestom.server.command.builder.exception.ArgumentSyntaxException -import net.minestom.server.command.builder.suggestion.Suggestion import net.minestom.server.command.builder.suggestion.SuggestionCallback import net.minestom.server.command.builder.suggestion.SuggestionEntry import net.minestom.server.entity.Player @@ -41,6 +39,7 @@ import su.plo.slib.api.server.event.command.McServerCommandsRegisterEvent import su.plo.slib.command.AbstractCommandManager import su.plo.slib.command.brigadier.CustomArgumentCommandNode import su.plo.slib.command.proxied +import su.plo.slib.minestom.chat.McTextMessage import su.plo.slib.minestom.command.brigadier.MinestomArgumentType import su.plo.slib.minestom.command.brigadier.MinestomBrigadierSource @@ -48,6 +47,12 @@ class MinestomCommandManager( private val minecraftServer: McServerLib ) : AbstractCommandManager() { + // Minestom discards the thrown ArgumentSyntaxException's rich info when building its + // InvalidCommand, and the per-argument ArgumentCallback is orphan API that never fires. + // We stash the original CommandSyntaxException here so the defaultExecutor (which does fire + // on parse errors) can render the translatable message. + private val pendingParseError = ThreadLocal() + @Synchronized fun registerCommands() { McServerCommandsRegisterEvent.invoker.onCommandsRegister(this, minecraftServer) @@ -93,26 +98,31 @@ class MinestomCommandManager( .map { it.toMinestom() } commands.forEach { minestomCommand.addSubcommand(it) } - val executor = command?.toMinestom() ?: noopCommandExecutor() + val literalExecutor = command?.toMinestom() val arguments = children.filterIsInstance>() .map { it.toMinestom() } arguments.forEach {(argument, argumentExecutor) -> - minestomCommand.addSyntax(argumentExecutor ?: executor, argument) + minestomCommand.addSyntax(argumentExecutor ?: literalExecutor ?: noopCommandExecutor(), argument) } - minestomCommand.defaultExecutor = executor + minestomCommand.defaultExecutor = defaultCommandExecutor(literalExecutor) return minestomCommand } private fun noopCommandExecutor(): CommandExecutor = - object : CommandExecutor { - override fun apply( - sender: CommandSender, - context: net.minestom.server.command.builder.CommandContext, - ) { + CommandExecutor { _, _ -> } + + private fun defaultCommandExecutor(fallback: CommandExecutor?): CommandExecutor = + CommandExecutor { sender, context -> + val error = pendingParseError.get() + if (error != null) { + pendingParseError.remove() + getCommandSource(sender).sendParseError(error) + return@CommandExecutor } + fallback?.apply(sender, context) } private fun ArgumentType<*>.toMinestomParserType(): ArgumentParserType = @@ -137,16 +147,19 @@ class MinestomCommandManager( val minestomArgument = object : Argument(name) { @Suppress("UNCHECKED_CAST") - override fun parse(sender: CommandSender, input: String): T = - try { + override fun parse(sender: CommandSender, input: String): T { + pendingParseError.remove() + return try { if (this@toMinestom is CustomArgumentCommandNode<*, *, *>) { (customArgumentType.parse(StringReader(input)) as T) } else { argumentType.parse(StringReader(input)) } } catch (e: CommandSyntaxException) { + pendingParseError.set(e) throw ArgumentSyntaxException(e.message, input, -1) } + } override fun parser(): ArgumentParserType = argumentType.toMinestomParserType() @@ -160,20 +173,14 @@ class MinestomCommandManager( } } - minestomArgument.suggestionCallback = object : SuggestionCallback { - override fun apply( - sender: CommandSender, - context: net.minestom.server.command.builder.CommandContext, - suggestion: Suggestion, - ) { - val brigadierContext = context.toBrigadier(sender, command) - val suggestions = listSuggestions(brigadierContext, SuggestionsBuilder(context.input, 0)).get() - - suggestions.list.forEach { - suggestion.addEntry( - SuggestionEntry(it.text, it.tooltip?.string?.let(Component::text)) - ) - } + minestomArgument.suggestionCallback = SuggestionCallback { sender, context, suggestion -> + val brigadierContext = context.toBrigadier(sender, command) + val suggestions = listSuggestions(brigadierContext, SuggestionsBuilder(context.input, 0)).get() + + suggestions.list.forEach { + suggestion.addEntry( + SuggestionEntry(it.text, it.tooltip?.string?.let(Component::text)) + ) } } @@ -181,31 +188,19 @@ class MinestomCommandManager( } private fun com.mojang.brigadier.Command.toMinestom(): CommandExecutor = - object : CommandExecutor { - override fun apply( - sender: CommandSender, - context: net.minestom.server.command.builder.CommandContext, - ) { - val source = getCommandSource(sender) - - val brigadierContext = context.toBrigadier(sender, this@toMinestom) - - try { - this@toMinestom.run(brigadierContext) - } catch (e: CommandSyntaxException) { - source.sendMessage( - McTextComponent.translatable( - "command.context.parse_error", - McTextComponent.literal(e.rawMessage.string), - McTextComponent.literal(e.cursor.toString()), - McTextComponent.literal(e.context), - ).withStyle(McTextStyle.RED) - ) - } catch (e: Exception) { - source.sendMessage( - McTextComponent.literal(e.message ?: "Unknown error").withStyle(McTextStyle.RED) - ) - } + CommandExecutor { sender, context -> + pendingParseError.remove() + val source = getCommandSource(sender) + val brigadierContext = context.toBrigadier(sender, this@toMinestom) + + try { + this@toMinestom.run(brigadierContext) + } catch (e: CommandSyntaxException) { + source.sendParseError(e) + } catch (e: Exception) { + source.sendMessage( + McTextComponent.literal(e.message ?: "Unknown error").withStyle(McTextStyle.RED) + ) } } @@ -228,4 +223,20 @@ class MinestomCommandManager( false, ) } + + private fun McCommandSource.sendParseError(e: CommandSyntaxException) { + val rawMessage = e.rawMessage + val messageArg = + if (rawMessage is McTextMessage) rawMessage.component + else McTextComponent.literal(rawMessage.string) + + sendMessage( + McTextComponent.translatable( + "command.context.parse_error", + messageArg, + McTextComponent.literal(e.cursor.toString()), + McTextComponent.literal(e.context), + ).withStyle(McTextStyle.RED) + ) + } } diff --git a/minestom/src/main/resources/META-INF/services/su.plo.slib.api.chat.converter.MessageTextConverter b/minestom/src/main/resources/META-INF/services/su.plo.slib.api.chat.converter.MessageTextConverter new file mode 100644 index 0000000..3e42941 --- /dev/null +++ b/minestom/src/main/resources/META-INF/services/su.plo.slib.api.chat.converter.MessageTextConverter @@ -0,0 +1 @@ +su.plo.slib.minestom.chat.ComponentToMessageConverter diff --git a/modded/src/main/kotlin/su/plo/slib/mod/chat/ComponentToMessageConverter.kt b/modded/src/main/kotlin/su/plo/slib/mod/chat/ComponentToMessageConverter.kt new file mode 100644 index 0000000..64f3d55 --- /dev/null +++ b/modded/src/main/kotlin/su/plo/slib/mod/chat/ComponentToMessageConverter.kt @@ -0,0 +1,10 @@ +package su.plo.slib.mod.chat + +import com.mojang.brigadier.Message +import su.plo.slib.api.chat.component.McTextComponent +import su.plo.slib.api.chat.converter.MessageTextConverter + +class ComponentToMessageConverter : MessageTextConverter { + override fun convert(text: McTextComponent): Message = + ComponentTextConverter.convert(text) +} diff --git a/modded/src/main/resources/META-INF/services/su.plo.slib.api.chat.converter.MessageTextConverter b/modded/src/main/resources/META-INF/services/su.plo.slib.api.chat.converter.MessageTextConverter new file mode 100644 index 0000000..e52000e --- /dev/null +++ b/modded/src/main/resources/META-INF/services/su.plo.slib.api.chat.converter.MessageTextConverter @@ -0,0 +1 @@ +su.plo.slib.mod.chat.ComponentToMessageConverter diff --git a/spigot/src/main/kotlin/su/plo/slib/spigot/chat/ComponentToMessageConverter.kt b/spigot/src/main/kotlin/su/plo/slib/spigot/chat/ComponentToMessageConverter.kt new file mode 100644 index 0000000..84d9d04 --- /dev/null +++ b/spigot/src/main/kotlin/su/plo/slib/spigot/chat/ComponentToMessageConverter.kt @@ -0,0 +1,19 @@ +package su.plo.slib.spigot.chat + +import com.mojang.brigadier.Message +import net.kyori.adventure.platform.bukkit.MinecraftComponentSerializer +import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer +import su.plo.slib.api.chat.component.McTextComponent +import su.plo.slib.api.chat.converter.MessageTextConverter +import su.plo.slib.chat.AdventureComponentTextConverter + +class ComponentToMessageConverter : MessageTextConverter { + private val textConverter = AdventureComponentTextConverter() + private val gson = GsonComponentSerializer.gson() + + override fun convert(text: McTextComponent): Message { + val json = textConverter.convertToJson(text) + val component = gson.deserialize(json) + return MinecraftComponentSerializer.get().serialize(component) as Message + } +} diff --git a/spigot/src/main/resources/META-INF/services/su.plo.slib.api.chat.converter.MessageTextConverter b/spigot/src/main/resources/META-INF/services/su.plo.slib.api.chat.converter.MessageTextConverter new file mode 100644 index 0000000..3313f66 --- /dev/null +++ b/spigot/src/main/resources/META-INF/services/su.plo.slib.api.chat.converter.MessageTextConverter @@ -0,0 +1 @@ +su.plo.slib.spigot.chat.ComponentToMessageConverter diff --git a/velocity/src/main/kotlin/su/plo/slib/velocity/chat/ComponentToMessageConverter.kt b/velocity/src/main/kotlin/su/plo/slib/velocity/chat/ComponentToMessageConverter.kt new file mode 100644 index 0000000..328aa47 --- /dev/null +++ b/velocity/src/main/kotlin/su/plo/slib/velocity/chat/ComponentToMessageConverter.kt @@ -0,0 +1,18 @@ +package su.plo.slib.velocity.chat + +import com.mojang.brigadier.Message +import com.velocitypowered.api.command.VelocityBrigadierMessage +import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer +import su.plo.slib.api.chat.component.McTextComponent +import su.plo.slib.api.chat.converter.MessageTextConverter +import su.plo.slib.chat.AdventureComponentTextConverter + +class ComponentToMessageConverter : MessageTextConverter { + private val textConverter = AdventureComponentTextConverter() + private val gson = GsonComponentSerializer.gson() + + override fun convert(text: McTextComponent): Message { + val json = textConverter.convertToJson(text) + return VelocityBrigadierMessage.tooltip(gson.deserialize(json)) + } +} diff --git a/velocity/src/main/resources/META-INF/services/su.plo.slib.api.chat.converter.MessageTextConverter b/velocity/src/main/resources/META-INF/services/su.plo.slib.api.chat.converter.MessageTextConverter new file mode 100644 index 0000000..79ec4bc --- /dev/null +++ b/velocity/src/main/resources/META-INF/services/su.plo.slib.api.chat.converter.MessageTextConverter @@ -0,0 +1 @@ +su.plo.slib.velocity.chat.ComponentToMessageConverter From 1c87c560e77b3e6b6def0f5bf4e6d46722597b9e Mon Sep 17 00:00:00 2001 From: Apehum Date: Thu, 23 Apr 2026 23:05:11 +0800 Subject: [PATCH 19/36] build: update adventure-platform --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 595408e..cde69c3 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -21,7 +21,7 @@ bungee = "1.21-R0.4-SNAPSHOT" minestom = "2025.10.05-1.21.8" adventure = "4.21.0" -adventure-platform = "4.4.0" +adventure-platform = "4.4.1" fabric-permissions = "0.3.1" reflection-remapper = "0.1.2" From 8e8c466c2c068558664d6c013ee935e4169711b4 Mon Sep 17 00:00:00 2001 From: Apehum Date: Fri, 24 Apr 2026 04:05:39 +0800 Subject: [PATCH 20/36] build: add commands to smoke tests --- bungee/build.gradle.kts | 3 + .../su/plo/slib/bungee/TestBungeePlugin.kt | 16 +++ .../command/MinestomDefaultCommandSource.kt | 17 ++- .../brigadier/MinestomBrigadierArguments.kt | 4 +- .../plo/slib/minestom/TestMinestomServer.kt | 22 +++- scripts/smoke-test.sh | 124 ++++++++++++++---- 6 files changed, 155 insertions(+), 31 deletions(-) diff --git a/bungee/build.gradle.kts b/bungee/build.gradle.kts index 579c769..8b26616 100644 --- a/bungee/build.gradle.kts +++ b/bungee/build.gradle.kts @@ -33,6 +33,7 @@ dependencies { } compileOnly(libs.adventure.bungee) + testCompileOnly(libs.adventure.bungee) shadow(libs.adventure.bungee) { exclude("org.jetbrains", "annotations") exclude("net.kyori", "adventure-api") @@ -74,6 +75,8 @@ tasks { archiveClassifier.set("test") + relocate("net.kyori", "su.plo.slib.libs.adventure") + from(zipTree(finalJar.get().archiveFile)) from(sourceSets.test.get().output) } diff --git a/bungee/src/test/kotlin/su/plo/slib/bungee/TestBungeePlugin.kt b/bungee/src/test/kotlin/su/plo/slib/bungee/TestBungeePlugin.kt index 90e62d8..b31e09e 100644 --- a/bungee/src/test/kotlin/su/plo/slib/bungee/TestBungeePlugin.kt +++ b/bungee/src/test/kotlin/su/plo/slib/bungee/TestBungeePlugin.kt @@ -1,12 +1,28 @@ package su.plo.slib.bungee +import net.kyori.adventure.key.Key +import net.kyori.adventure.translation.GlobalTranslator +import net.kyori.adventure.translation.TranslationStore import net.md_5.bungee.api.plugin.Plugin import su.plo.slib.proxy.TestProxy +import java.text.MessageFormat +import java.util.Locale class TestBungeePlugin : Plugin() { private val testProxy = TestProxy() override fun onEnable() { + registerVanillaTranslations() val minecraftServer = BungeeProxyLib(this) } + + // Bungee doesn't ship vanilla translations, so translation keys leak to the console as raw ids + // Register the minimum set the smoke tests need + private fun registerVanillaTranslations() { + val store = TranslationStore.messageFormat(Key.key("slib", "test")) + store.defaultLocale(Locale.US) + store.register("command.context.parse_error", Locale.US, MessageFormat("{0} at position {1}: {2}")) + store.register("argument.uuid.invalid", Locale.US, MessageFormat("Invalid UUID")) + GlobalTranslator.translator().addSource(store) + } } diff --git a/minestom/src/main/kotlin/su/plo/slib/minestom/command/MinestomDefaultCommandSource.kt b/minestom/src/main/kotlin/su/plo/slib/minestom/command/MinestomDefaultCommandSource.kt index 0ceb32a..5ead7b8 100644 --- a/minestom/src/main/kotlin/su/plo/slib/minestom/command/MinestomDefaultCommandSource.kt +++ b/minestom/src/main/kotlin/su/plo/slib/minestom/command/MinestomDefaultCommandSource.kt @@ -1,11 +1,15 @@ package su.plo.slib.minestom.command +import net.kyori.adventure.text.Component import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer +import net.kyori.adventure.translation.GlobalTranslator +import net.kyori.adventure.translation.Translator import net.minestom.server.command.CommandSender import su.plo.slib.api.chat.component.McTextComponent import su.plo.slib.api.chat.converter.ServerTextConverter import su.plo.slib.api.command.McCommandSource import su.plo.slib.api.permission.PermissionTristate +import java.util.Locale class MinestomDefaultCommandSource( private val textConverter: ServerTextConverter<*>, @@ -13,17 +17,18 @@ class MinestomDefaultCommandSource( ) : McCommandSource { override fun sendMessage(text: McTextComponent) { - val json = textConverter.convertToJson(this, text) - val component = GsonComponentSerializer.gson().deserialize(json) - - source.sendMessage(component) + source.sendMessage(render(text)) } override fun sendActionBar(text: McTextComponent) { + source.sendActionBar(render(text)) + } + + private fun render(text: McTextComponent): Component { val json = textConverter.convertToJson(this, text) val component = GsonComponentSerializer.gson().deserialize(json) - - source.sendActionBar(component) + val locale = Translator.parseLocale(language) ?: Locale.US + return GlobalTranslator.render(component, locale) } override val language: String diff --git a/minestom/src/main/kotlin/su/plo/slib/minestom/command/brigadier/MinestomBrigadierArguments.kt b/minestom/src/main/kotlin/su/plo/slib/minestom/command/brigadier/MinestomBrigadierArguments.kt index bb80510..2b3b535 100644 --- a/minestom/src/main/kotlin/su/plo/slib/minestom/command/brigadier/MinestomBrigadierArguments.kt +++ b/minestom/src/main/kotlin/su/plo/slib/minestom/command/brigadier/MinestomBrigadierArguments.kt @@ -91,8 +91,8 @@ class MinestomBrigadierArguments : McArgumentTypes.Provider { position.x(), position.y(), position.z(), - entityPosition?.yaw ?: 0f, - entityPosition?.pitch ?: 0f, + entityPosition?.yaw ?: position.x().toFloat(), + entityPosition?.pitch ?: position.y().toFloat(), ) } } diff --git a/minestom/src/test/kotlin/su/plo/slib/minestom/TestMinestomServer.kt b/minestom/src/test/kotlin/su/plo/slib/minestom/TestMinestomServer.kt index cbf789d..2ca5838 100644 --- a/minestom/src/test/kotlin/su/plo/slib/minestom/TestMinestomServer.kt +++ b/minestom/src/test/kotlin/su/plo/slib/minestom/TestMinestomServer.kt @@ -1,5 +1,8 @@ package su.plo.slib.minestom +import net.kyori.adventure.key.Key +import net.kyori.adventure.translation.GlobalTranslator +import net.kyori.adventure.translation.TranslationStore import net.minecrell.terminalconsole.SimpleTerminalConsole import net.minestom.server.MinecraftServer import net.minestom.server.coordinate.Pos @@ -10,6 +13,9 @@ import net.minestom.server.event.player.AsyncPlayerConfigurationEvent import net.minestom.server.event.player.PlayerSpawnEvent import su.plo.slib.server.TestServer import java.io.File +import java.text.MessageFormat +import java.util.Locale +import kotlin.concurrent.thread import kotlin.system.exitProcess import kotlin.time.measureTime @@ -23,6 +29,8 @@ fun main() { fun startServer() { val minecraftServer = MinecraftServer.init() + registerVanillaTranslations() + val minecraftServerLib = MinestomServerLib(File("slib-test")) val testServer = TestServer(minecraftServerLib) @@ -47,7 +55,9 @@ fun startServer() { minecraftServer.start("0.0.0.0", 25565) - Console().start() + thread(isDaemon = true) { + Console().start() + } } class Console : SimpleTerminalConsole() { @@ -80,3 +90,13 @@ class Console : SimpleTerminalConsole() { private inline fun GlobalEventHandler.addListener(crossinline listener: (T) -> Unit) { addListener(T::class.java) { listener.invoke(it) } } + +// Minestom doesn't ship vanilla translations, so translation keys leak to the console as raw ids +// Register the minimum set the smoke tests need +private fun registerVanillaTranslations() { + val store = TranslationStore.messageFormat(Key.key("slib", "test")) + store.defaultLocale(Locale.US) + store.register("command.context.parse_error", Locale.US, MessageFormat("{0} at position {1}: {2}")) + store.register("argument.uuid.invalid", Locale.US, MessageFormat("Invalid UUID")) + GlobalTranslator.translator().addSource(store) +} diff --git a/scripts/smoke-test.sh b/scripts/smoke-test.sh index 62de65b..a1a47d8 100755 --- a/scripts/smoke-test.sh +++ b/scripts/smoke-test.sh @@ -24,6 +24,20 @@ case "$ENV_TYPE" in "Command 'brigadier-game-profiles-selector' registered" "Command 'brigadier-custom-type' registered" ) + COMMAND_INPUTS=( + "brigadier-custom-type invalid-uuid" + "brigadier-entity-selector entities @e" + "brigadier-entity-selector players @a" + "brigadier-game-profiles-selector GNOME__" + "brigadier-position-selector 100 100 100" + ) + COMMAND_OUTPUT_PATTERNS=( + "Invalid UUID" + "Found entities:" + "Found players:" + "Found game profiles:" + "Position: ServerPos3d\\(world=null, x=100.0, y=100.0, z=100.0, yaw=100.0, pitch=100.0\\)" + ) ;; proxy) PATTERNS=( @@ -32,6 +46,12 @@ case "$ENV_TYPE" in "Command 'brigadier-ping' registered" "Command 'brigadier-custom-type' registered" ) + COMMAND_INPUTS=( + "brigadier-custom-type invalid-uuid" + ) + COMMAND_OUTPUT_PATTERNS=( + "Invalid UUID" + ) ;; *) echo "Error: Invalid environment type '$ENV_TYPE'. Must be 'server' or 'proxy'" @@ -39,22 +59,46 @@ case "$ENV_TYPE" in ;; esac +if [[ ${#COMMAND_INPUTS[@]} -ne ${#COMMAND_OUTPUT_PATTERNS[@]} ]]; then + echo "Error: COMMAND_INPUTS and COMMAND_OUTPUT_PATTERNS must have the same length" + exit 1 +fi + LOGFILE=$(mktemp) FOUND_DIR=$(mktemp -d) -trap "rm -rf $FOUND_DIR $LOGFILE" EXIT +STDIN_PIPE=$(mktemp -u) +mkfifo "$STDIN_PIPE" +trap "rm -rf $FOUND_DIR $LOGFILE $STDIN_PIPE" EXIT + +# Open read+write so this side doesn't block waiting for a peer, and so gradle +# doesn't see EOF before we send anything. +exec 3<>"$STDIN_PIPE" TIMEOUT=${CI:+600} TIMEOUT=${TIMEOUT:-180} +CMD_TIMEOUT=${CMD_TIMEOUT:-5} echo "Testing [$ENV_TYPE]: $COMMAND" # Run in background, write to file START_TIME=$(date +%s) timeout $TIMEOUT ./gradlew $COMMAND \ --console=plain \ - --no-daemon > "$LOGFILE" 2>&1 & + --no-daemon < "$STDIN_PIPE" > "$LOGFILE" 2>&1 & PID=$! -# Poll the log file +dump_and_fail() { + local msg="$1" + [[ -z "$CI" ]] && echo + echo "=== $ENV_TYPE output ===" + cat "$LOGFILE" + echo "=== Test result ===" + echo "FAILED: $msg" + kill $PID 2>/dev/null || true + exit 1 +} + +# Phase 1: wait for startup patterns. +STARTUP_OK=0 while kill -0 $PID 2>/dev/null; do for i in "${!PATTERNS[@]}"; do if [[ ! -f "$FOUND_DIR/$i" ]] && grep -Eq "${PATTERNS[$i]}" "$LOGFILE"; then @@ -69,31 +113,67 @@ while kill -0 $PID 2>/dev/null; do ELAPSED=$(($(date +%s) - START_TIME)) if [[ $FOUND_COUNT -eq ${#PATTERNS[@]} ]]; then - kill $PID 2>/dev/null - - echo "=== $ENV_TYPE output ===" - cat "$LOGFILE" - - echo "=== Test result ===" - echo "All ${#PATTERNS[@]} patterns matched in ${ELAPSED}s" - - exit 0 + [[ -z "$CI" ]] && printf "\r\033[K" + echo "All ${#PATTERNS[@]} startup patterns matched in ${ELAPSED}s" + STARTUP_OK=1 + break fi [[ -z "$CI" ]] && printf "\rWaiting... %ds/%ds (%d/%d patterns found)" "$ELAPSED" "$TIMEOUT" "$FOUND_COUNT" "${#PATTERNS[@]}" sleep 1 done -[[ -z "$CI" ]] && echo -EXIT_CODE=0 -wait $PID || EXIT_CODE=$? +if [[ $STARTUP_OK -eq 0 ]]; then + EXIT_CODE=0 + wait $PID || EXIT_CODE=$? + if [[ $EXIT_CODE -eq 124 ]]; then + dump_and_fail "Startup timed out" + else + dump_and_fail "Process exited (code $EXIT_CODE) before all startup patterns found" + fi +fi + +# Phase 2: send each command, wait for its expected output. +for i in "${!COMMAND_INPUTS[@]}"; do + INPUT="${COMMAND_INPUTS[$i]}" + PATTERN="${COMMAND_OUTPUT_PATTERNS[$i]}" + BEFORE=$(wc -l < "$LOGFILE") + + echo "Sending: $INPUT" + echo "$INPUT" >&3 + + CMD_START=$(date +%s) + MATCHED=0 + while kill -0 $PID 2>/dev/null; do + if tail -n +$((BEFORE + 1)) "$LOGFILE" | grep -Eq "$PATTERN"; then + MATCH=$(tail -n +$((BEFORE + 1)) "$LOGFILE" | grep -Eo "$PATTERN" | head -1) + [[ -z "$CI" ]] && printf "\r\033[K" + echo "Found '$PATTERN' -> $MATCH" + MATCHED=1 + break + fi + + CMD_ELAPSED=$(($(date +%s) - CMD_START)) + if [[ $CMD_ELAPSED -ge $CMD_TIMEOUT ]]; then + dump_and_fail "Command '$INPUT' did not produce pattern '$PATTERN' within ${CMD_TIMEOUT}s" + fi + + [[ -z "$CI" ]] && printf "\rWaiting for '%s'... %ds/%ds" "$PATTERN" "$CMD_ELAPSED" "$CMD_TIMEOUT" + sleep 1 + done + [[ -z "$CI" ]] && echo + + if [[ $MATCHED -eq 0 ]]; then + EXIT_CODE=0 + wait $PID || EXIT_CODE=$? + dump_and_fail "Process exited (code $EXIT_CODE) before pattern '$PATTERN' appeared for '$INPUT'" + fi +done echo "=== $ENV_TYPE output ===" cat "$LOGFILE" - -if [[ $EXIT_CODE -eq 124 ]]; then - echo "FAILED: Startup timed out" -else - echo "FAILED: Process exited (code $EXIT_CODE) before all patterns found" -fi -exit 1 +echo "=== Test result ===" +TOTAL_ELAPSED=$(($(date +%s) - START_TIME)) +echo "All ${#PATTERNS[@]} startup patterns and ${#COMMAND_INPUTS[@]} command patterns matched in ${TOTAL_ELAPSED}s" +kill $PID 2>/dev/null || true +exit 0 From ca1e1c4bb36e7b5f9c1b14fc3971ad95555c720b Mon Sep 17 00:00:00 2001 From: Apehum Date: Fri, 24 Apr 2026 04:55:12 +0800 Subject: [PATCH 21/36] build: remove brigadier-game-profiles-selector from tests, it's flaky --- scripts/smoke-test.sh | 2 -- 1 file changed, 2 deletions(-) diff --git a/scripts/smoke-test.sh b/scripts/smoke-test.sh index a1a47d8..d76d1dd 100755 --- a/scripts/smoke-test.sh +++ b/scripts/smoke-test.sh @@ -28,14 +28,12 @@ case "$ENV_TYPE" in "brigadier-custom-type invalid-uuid" "brigadier-entity-selector entities @e" "brigadier-entity-selector players @a" - "brigadier-game-profiles-selector GNOME__" "brigadier-position-selector 100 100 100" ) COMMAND_OUTPUT_PATTERNS=( "Invalid UUID" "Found entities:" "Found players:" - "Found game profiles:" "Position: ServerPos3d\\(world=null, x=100.0, y=100.0, z=100.0, yaw=100.0, pitch=100.0\\)" ) ;; From fe7a03ff506280d027b2d0ea7cd46ecd0cd32567 Mon Sep 17 00:00:00 2001 From: Apehum Date: Fri, 24 Apr 2026 06:22:49 +0800 Subject: [PATCH 22/36] ci: skip command checks in 1.19.4 and 1.20.1 paper --- .github/workflows/build-pr.yml | 22 +++++++++++++--------- gradle.properties | 2 +- scripts/smoke-test.sh | 9 +++++++++ 3 files changed, 23 insertions(+), 10 deletions(-) diff --git a/.github/workflows/build-pr.yml b/.github/workflows/build-pr.yml index c821e47..2553a2e 100644 --- a/.github/workflows/build-pr.yml +++ b/.github/workflows/build-pr.yml @@ -79,15 +79,17 @@ jobs: smoke-tests-paper: strategy: matrix: - version: - - 1.16.5 - - 1.19.2 - - 1.19.4 - - 1.20.1 - - 1.21.1 - - 1.21.8 - - 1.21.11 - - 26.1.2 + include: + - version: 1.16.5 + - version: 1.19.2 + - version: 1.19.4 + skip_command_io: true + - version: 1.20.1 + skip_command_io: true + - version: 1.21.1 + - version: 1.21.8 + - version: 1.21.11 + - version: 26.1.2 runs-on: ubuntu-latest needs: build @@ -106,6 +108,8 @@ jobs: cache-read-only: false - name: Run smoke test + env: + SKIP_COMMAND_IO: ${{ matrix.skip_command_io && '1' || '' }} run: ./scripts/smoke-test-server.sh spigot:runServer -Pspigot.run_minecraft_version=${{ matrix.version }} smoke-tests-minestom: diff --git a/gradle.properties b/gradle.properties index f8a7c9d..af415dc 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ # Version group=su.plo.slib -version=1.3.0 +version=1.3.0-SNAPSHOT # Gradle args org.gradle.jvmargs=-Xmx4G -Dkotlin.daemon.jvm.options=-Xmx512M diff --git a/scripts/smoke-test.sh b/scripts/smoke-test.sh index d76d1dd..bfb2486 100755 --- a/scripts/smoke-test.sh +++ b/scripts/smoke-test.sh @@ -132,6 +132,15 @@ if [[ $STARTUP_OK -eq 0 ]]; then fi # Phase 2: send each command, wait for its expected output. +# Paper 1.19.3 through 1.20.5 route stdin through CraftServer.dispatchCommand, +# which only hits the Bukkit command map (no brigadier fallback until 1.20.6). +# Set SKIP_COMMAND_IO=1 on affected versions to keep the startup checks while +# opting out of the stdin round-trip. +if [[ -n "$SKIP_COMMAND_IO" ]]; then + echo "Skipping command I/O phase (SKIP_COMMAND_IO=$SKIP_COMMAND_IO)" + COMMAND_INPUTS=() +fi + for i in "${!COMMAND_INPUTS[@]}"; do INPUT="${COMMAND_INPUTS[$i]}" PATTERN="${COMMAND_OUTPUT_PATTERNS[$i]}" From 5b0ec54c25bb918669d3908667a9c052b0f2a186 Mon Sep 17 00:00:00 2001 From: Apehum Date: Sat, 25 Apr 2026 05:25:43 +0800 Subject: [PATCH 23/36] fix: clear brigadier commands on AbstractCommandManager#clear --- .../main/kotlin/su/plo/slib/command/AbstractCommandManager.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/common/src/main/kotlin/su/plo/slib/command/AbstractCommandManager.kt b/common/src/main/kotlin/su/plo/slib/command/AbstractCommandManager.kt index 19dcd70..403c2d2 100644 --- a/common/src/main/kotlin/su/plo/slib/command/AbstractCommandManager.kt +++ b/common/src/main/kotlin/su/plo/slib/command/AbstractCommandManager.kt @@ -61,6 +61,7 @@ abstract class AbstractCommandManager : McCommandManager() { @Synchronized override fun clear() { commandByName.clear() + brigadierCommands.clear() registered = false } From fafe0ffe8d5761d76fbfc354a0f94917813b99db Mon Sep 17 00:00:00 2001 From: Apehum Date: Mon, 27 Apr 2026 09:55:35 +0800 Subject: [PATCH 24/36] chore(mod): remove wildcard import from ComponentTextConverter --- .../su/plo/slib/mod/chat/ComponentTextConverter.kt | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/modded/src/main/kotlin/su/plo/slib/mod/chat/ComponentTextConverter.kt b/modded/src/main/kotlin/su/plo/slib/mod/chat/ComponentTextConverter.kt index 4b98471..b9b9842 100644 --- a/modded/src/main/kotlin/su/plo/slib/mod/chat/ComponentTextConverter.kt +++ b/modded/src/main/kotlin/su/plo/slib/mod/chat/ComponentTextConverter.kt @@ -6,7 +6,10 @@ import su.plo.slib.api.chat.converter.McTextConverter import su.plo.slib.chat.AdventureComponentTextConverter //? if >=1.20.5 { -/*import com.google.gson.* +/*import com.google.gson.Gson +import com.google.gson.GsonBuilder +import com.google.gson.JsonParseException +import com.google.gson.JsonParser import com.mojang.serialization.JsonOps import net.minecraft.network.chat.ComponentSerialization *///?} @@ -28,7 +31,7 @@ object ComponentTextConverter : McTextConverter { ?.let { ComponentSerialization.CODEC.parse(JsonOps.INSTANCE, it).getOrThrow(::JsonParseException) } - ?: throw JsonParseException("JsonParser return null smh") + ?: throw JsonParseException("JsonParser returned null") *///?} else { Component.Serializer.fromJson(json)!! //?} @@ -41,4 +44,4 @@ object ComponentTextConverter : McTextConverter { *///?} else { Component.Serializer.toJson(text) //?} -} \ No newline at end of file +} From 2c565e79ffb7a4c74c3f33a3c88af2e54aa204c7 Mon Sep 17 00:00:00 2001 From: Apehum Date: Mon, 27 Apr 2026 09:59:03 +0800 Subject: [PATCH 25/36] fix(minestom): correct error message in getEntityByInstance --- .../src/main/kotlin/su/plo/slib/minestom/MinestomServerLib.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/minestom/src/main/kotlin/su/plo/slib/minestom/MinestomServerLib.kt b/minestom/src/main/kotlin/su/plo/slib/minestom/MinestomServerLib.kt index 2324eed..4011fb6 100644 --- a/minestom/src/main/kotlin/su/plo/slib/minestom/MinestomServerLib.kt +++ b/minestom/src/main/kotlin/su/plo/slib/minestom/MinestomServerLib.kt @@ -3,7 +3,6 @@ package su.plo.slib.minestom import com.google.common.collect.Maps import net.minestom.server.MinecraftServer import net.minestom.server.entity.Entity -import net.minestom.server.entity.LivingEntity import net.minestom.server.entity.Player import net.minestom.server.event.instance.InstanceUnregisterEvent import net.minestom.server.event.player.PlayerDisconnectEvent @@ -139,7 +138,7 @@ class MinestomServerLib( } override fun getEntityByInstance(instance: Any): McServerEntity { - require(instance is Entity) { "instance is not ${LivingEntity::class.java}" } + require(instance is Entity) { "instance is not ${Entity::class.java}" } if (instance is Player) { return getPlayerByInstance(instance) From f8bd0e3d6d76ea83b6c4c4fa0ca19f9c6c1a752a Mon Sep 17 00:00:00 2001 From: Apehum Date: Mon, 27 Apr 2026 10:00:55 +0800 Subject: [PATCH 26/36] fix(minestom): unwrap CustomArgumentType when emitting StringArgumentType node properties --- .../su/plo/slib/minestom/command/MinestomCommandManager.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/minestom/src/main/kotlin/su/plo/slib/minestom/command/MinestomCommandManager.kt b/minestom/src/main/kotlin/su/plo/slib/minestom/command/MinestomCommandManager.kt index 68a01c7..7ee6093 100644 --- a/minestom/src/main/kotlin/su/plo/slib/minestom/command/MinestomCommandManager.kt +++ b/minestom/src/main/kotlin/su/plo/slib/minestom/command/MinestomCommandManager.kt @@ -165,8 +165,9 @@ class MinestomCommandManager( argumentType.toMinestomParserType() override fun nodeProperties(): ByteArray? { - if (argumentType is StringArgumentType) { - return NetworkBuffer.makeArray(NetworkBuffer.VAR_INT, argumentType.type.ordinal) + val effectiveType = (argumentType as? CustomArgumentType<*, *>)?.nativeType ?: argumentType + if (effectiveType is StringArgumentType) { + return NetworkBuffer.makeArray(NetworkBuffer.VAR_INT, effectiveType.type.ordinal) } return super.nodeProperties() From 45a23287f946f6f013ea0231a703aa5ef9802326 Mon Sep 17 00:00:00 2001 From: Apehum Date: Mon, 27 Apr 2026 10:23:18 +0800 Subject: [PATCH 27/36] fix: reject null parse result in CustomArgumentCommandNode --- .../kotlin/su/plo/slib/server/command/UuidArgumentType.kt | 4 +--- .../plo/slib/command/brigadier/CustomArgumentCommandNode.kt | 1 + 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/common-server/src/testFixtures/kotlin/su/plo/slib/server/command/UuidArgumentType.kt b/common-server/src/testFixtures/kotlin/su/plo/slib/server/command/UuidArgumentType.kt index 33bb0fa..228a505 100644 --- a/common-server/src/testFixtures/kotlin/su/plo/slib/server/command/UuidArgumentType.kt +++ b/common-server/src/testFixtures/kotlin/su/plo/slib/server/command/UuidArgumentType.kt @@ -26,10 +26,8 @@ class UuidArgumentType : CustomArgumentType { ) ) - override fun parse(reader: StringReader): UUID? { + override fun parse(reader: StringReader): UUID { val input = reader.readString() - ?.takeIf { it.isNotBlank() } - ?: return null try { return UUID.fromString(input) diff --git a/common/src/main/kotlin/su/plo/slib/command/brigadier/CustomArgumentCommandNode.kt b/common/src/main/kotlin/su/plo/slib/command/brigadier/CustomArgumentCommandNode.kt index b2f463e..9715ce9 100644 --- a/common/src/main/kotlin/su/plo/slib/command/brigadier/CustomArgumentCommandNode.kt +++ b/common/src/main/kotlin/su/plo/slib/command/brigadier/CustomArgumentCommandNode.kt @@ -38,6 +38,7 @@ class CustomArgumentCommandNode( override fun parse(reader: StringReader, contextBuilder: CommandContextBuilder) { val start = reader.cursor val result = customArgumentType.parse(reader) + ?: error("CustomArgumentType ${customArgumentType::class.java.name} returned null from parse; throw a CommandSyntaxException to signal a parse failure") val parsed = ParsedArgument(start, reader.cursor, result) From 3065088b6f3bb5ce46d5d1fc31943a5a35fb3441 Mon Sep 17 00:00:00 2001 From: Apehum Date: Mon, 27 Apr 2026 10:24:37 +0800 Subject: [PATCH 28/36] chore(proxy): align UuidArgumentType with server fixture --- .../slib/proxy/command/UuidArgumentType.kt | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/common-proxy/src/testFixtures/kotlin/su/plo/slib/proxy/command/UuidArgumentType.kt b/common-proxy/src/testFixtures/kotlin/su/plo/slib/proxy/command/UuidArgumentType.kt index 89b919d..114a04c 100644 --- a/common-proxy/src/testFixtures/kotlin/su/plo/slib/proxy/command/UuidArgumentType.kt +++ b/common-proxy/src/testFixtures/kotlin/su/plo/slib/proxy/command/UuidArgumentType.kt @@ -1,14 +1,14 @@ package su.plo.slib.proxy.command -import com.mojang.brigadier.LiteralMessage import com.mojang.brigadier.StringReader import com.mojang.brigadier.arguments.ArgumentType import com.mojang.brigadier.arguments.StringArgumentType import com.mojang.brigadier.context.CommandContext -import com.mojang.brigadier.exceptions.CommandSyntaxException import com.mojang.brigadier.exceptions.SimpleCommandExceptionType import com.mojang.brigadier.suggestion.Suggestions import com.mojang.brigadier.suggestion.SuggestionsBuilder +import su.plo.slib.api.chat.component.McTextComponent +import su.plo.slib.api.chat.converter.MessageTextConverter import su.plo.slib.api.command.brigadier.CustomArgumentType import java.util.UUID import java.util.concurrent.CompletableFuture @@ -16,25 +16,28 @@ import java.util.concurrent.CompletableFuture class UuidArgumentType : CustomArgumentType { override val nativeType: ArgumentType = StringArgumentType.string() - private val invalidUuid = SimpleCommandExceptionType(LiteralMessage("Failed to parse UUID")) + override fun useNativeSuggestions(): Boolean = false - override fun parse(reader: StringReader): UUID? { + private val invalidUuid = SimpleCommandExceptionType( + MessageTextConverter.converter().convert( + McTextComponent.translatable( + "argument.uuid.invalid", + ) + ) + ) + + override fun parse(reader: StringReader): UUID { val input = reader.readString() - ?.takeIf { it.isNotBlank() } - ?: return null try { return UUID.fromString(input) - } catch (e: IllegalArgumentException) { - throw CommandSyntaxException(invalidUuid, LiteralMessage(e.message), input, 0) + } catch (_: IllegalArgumentException) { + throw invalidUuid.createWithContext(reader) } } override fun listSuggestions( context: CommandContext, builder: SuggestionsBuilder, - ): CompletableFuture = - builder - .suggest(UUID.randomUUID().toString()) - .buildFuture() + ): CompletableFuture = Suggestions.empty() } From 7c1ae2149f65e5c0fbb867372f90f296c21609b2 Mon Sep 17 00:00:00 2001 From: Apehum Date: Mon, 27 Apr 2026 10:25:15 +0800 Subject: [PATCH 29/36] docs: fix stale McCommandManager kdoc --- .../kotlin/su/plo/slib/api/command/McCommandManager.kt | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/api/common/src/main/kotlin/su/plo/slib/api/command/McCommandManager.kt b/api/common/src/main/kotlin/su/plo/slib/api/command/McCommandManager.kt index 60078c2..8d1f418 100644 --- a/api/common/src/main/kotlin/su/plo/slib/api/command/McCommandManager.kt +++ b/api/common/src/main/kotlin/su/plo/slib/api/command/McCommandManager.kt @@ -24,9 +24,9 @@ abstract class McCommandManager { abstract val registeredCommands: Map /** - * Retrieves a read-only map of registered brigadier commands. + * Retrieves a read-only list of registered brigadier command nodes. * - * @return A list containing the registered commands with their names as keys. + * @return A list of registered brigadier command nodes. */ abstract val registeredBrigadierCommands: List> @@ -71,7 +71,10 @@ abstract class McCommandManager { * * The [source] parameter represents the server-specific command source instance: * - For Velocity `com.velocitypowered.api.command.CommandSource` - * - For BungeeCord `// todo` + * - For BungeeCord `net.md_5.bungee.api.CommandSender` + * - For Spigot/Paper `org.bukkit.command.CommandSender` + * - For Minestom `net.minestom.server.command.CommandSender` + * - For modded servers (Fabric/Forge/NeoForge) `net.minecraft.commands.CommandSourceStack` * * @param source The server-specific command source instance. * @return A [McCommandSource] instance corresponding to the provided command source instance. From 69e1969b4266e942323b4b91422bd600336568af Mon Sep 17 00:00:00 2001 From: Apehum Date: Mon, 27 Apr 2026 10:32:16 +0800 Subject: [PATCH 30/36] fix(minestom): preserve argument chains when converting brigadier tree --- .../kotlin/su/plo/slib/server/TestServer.kt | 19 +++++++++++ .../command/MinestomCommandManager.kt | 34 ++++++++++++++----- scripts/smoke-test.sh | 3 ++ 3 files changed, 48 insertions(+), 8 deletions(-) diff --git a/common-server/src/testFixtures/kotlin/su/plo/slib/server/TestServer.kt b/common-server/src/testFixtures/kotlin/su/plo/slib/server/TestServer.kt index a2b060d..9cfb85d 100644 --- a/common-server/src/testFixtures/kotlin/su/plo/slib/server/TestServer.kt +++ b/common-server/src/testFixtures/kotlin/su/plo/slib/server/TestServer.kt @@ -1,6 +1,7 @@ package su.plo.slib.server import com.mojang.brigadier.Command +import com.mojang.brigadier.arguments.IntegerArgumentType import su.plo.slib.api.command.McCommand import su.plo.slib.api.command.McCommandManager import su.plo.slib.api.command.McCommandSource @@ -180,6 +181,24 @@ class TestServer( } ) ) + + commands.register( + McCommandManager.literal("brigadier-multi-arg") + .then( + McCommandManager.argument("a", IntegerArgumentType.integer()) + .then( + McCommandManager.argument("b", IntegerArgumentType.integer()) + .executes { + val a = it.getArgument("a", Integer::class.java) + val b = it.getArgument("b", Integer::class.java) + + it.source.source.sendMessage("Multi-arg: a=$a, b=$b") + + Command.SINGLE_SUCCESS + } + ) + ) + ) } } diff --git a/minestom/src/main/kotlin/su/plo/slib/minestom/command/MinestomCommandManager.kt b/minestom/src/main/kotlin/su/plo/slib/minestom/command/MinestomCommandManager.kt index 7ee6093..04a883d 100644 --- a/minestom/src/main/kotlin/su/plo/slib/minestom/command/MinestomCommandManager.kt +++ b/minestom/src/main/kotlin/su/plo/slib/minestom/command/MinestomCommandManager.kt @@ -94,23 +94,41 @@ class MinestomCommandManager( private fun LiteralCommandNode.toMinestom(): Command { val minestomCommand = Command(name) - val commands = children.filterIsInstance>() - .map { it.toMinestom() } - commands.forEach { minestomCommand.addSubcommand(it) } + children.filterIsInstance>() + .forEach { minestomCommand.addSubcommand(it.toMinestom()) } val literalExecutor = command?.toMinestom() - val arguments = children.filterIsInstance>() - .map { it.toMinestom() } - arguments.forEach {(argument, argumentExecutor) -> - minestomCommand.addSyntax(argumentExecutor ?: literalExecutor ?: noopCommandExecutor(), argument) - } + children.filterIsInstance>() + .forEach { registerArgumentSyntaxes(minestomCommand, it, emptyList(), literalExecutor) } minestomCommand.defaultExecutor = defaultCommandExecutor(literalExecutor) return minestomCommand } + private fun registerArgumentSyntaxes( + minestomCommand: Command, + node: ArgumentCommandNode, + prefix: List>, + fallbackExecutor: CommandExecutor?, + ) { + val (argument, executor) = node.toMinestom() + val pathSoFar = prefix + argument + val argDescendants = node.children.filterIsInstance>() + + if (executor != null || argDescendants.isEmpty()) { + minestomCommand.addSyntax( + executor ?: fallbackExecutor ?: noopCommandExecutor(), + *pathSoFar.toTypedArray(), + ) + } + + argDescendants.forEach { child -> + registerArgumentSyntaxes(minestomCommand, child, pathSoFar, fallbackExecutor) + } + } + private fun noopCommandExecutor(): CommandExecutor = CommandExecutor { _, _ -> } diff --git a/scripts/smoke-test.sh b/scripts/smoke-test.sh index bfb2486..3c0014a 100755 --- a/scripts/smoke-test.sh +++ b/scripts/smoke-test.sh @@ -23,18 +23,21 @@ case "$ENV_TYPE" in "Command 'brigadier-position-selector' registered" "Command 'brigadier-game-profiles-selector' registered" "Command 'brigadier-custom-type' registered" + "Command 'brigadier-multi-arg' registered" ) COMMAND_INPUTS=( "brigadier-custom-type invalid-uuid" "brigadier-entity-selector entities @e" "brigadier-entity-selector players @a" "brigadier-position-selector 100 100 100" + "brigadier-multi-arg 7 13" ) COMMAND_OUTPUT_PATTERNS=( "Invalid UUID" "Found entities:" "Found players:" "Position: ServerPos3d\\(world=null, x=100.0, y=100.0, z=100.0, yaw=100.0, pitch=100.0\\)" + "Multi-arg: a=7, b=13" ) ;; proxy) From 73265cdabebbff380cbef719b3740ee72983e0d1 Mon Sep 17 00:00:00 2001 From: Apehum Date: Tue, 28 Apr 2026 05:05:52 +0800 Subject: [PATCH 31/36] fix(proxy): move test translation key registration to common-proxy TestProxy --- .../su/plo/slib/bungee/TestBungeePlugin.kt | 16 ---------------- common-proxy/build.gradle.kts | 2 ++ .../kotlin/su/plo/slib/proxy/TestProxy.kt | 19 +++++++++++++++++-- 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/bungee/src/test/kotlin/su/plo/slib/bungee/TestBungeePlugin.kt b/bungee/src/test/kotlin/su/plo/slib/bungee/TestBungeePlugin.kt index b31e09e..90e62d8 100644 --- a/bungee/src/test/kotlin/su/plo/slib/bungee/TestBungeePlugin.kt +++ b/bungee/src/test/kotlin/su/plo/slib/bungee/TestBungeePlugin.kt @@ -1,28 +1,12 @@ package su.plo.slib.bungee -import net.kyori.adventure.key.Key -import net.kyori.adventure.translation.GlobalTranslator -import net.kyori.adventure.translation.TranslationStore import net.md_5.bungee.api.plugin.Plugin import su.plo.slib.proxy.TestProxy -import java.text.MessageFormat -import java.util.Locale class TestBungeePlugin : Plugin() { private val testProxy = TestProxy() override fun onEnable() { - registerVanillaTranslations() val minecraftServer = BungeeProxyLib(this) } - - // Bungee doesn't ship vanilla translations, so translation keys leak to the console as raw ids - // Register the minimum set the smoke tests need - private fun registerVanillaTranslations() { - val store = TranslationStore.messageFormat(Key.key("slib", "test")) - store.defaultLocale(Locale.US) - store.register("command.context.parse_error", Locale.US, MessageFormat("{0} at position {1}: {2}")) - store.register("argument.uuid.invalid", Locale.US, MessageFormat("Invalid UUID")) - GlobalTranslator.translator().addSource(store) - } } diff --git a/common-proxy/build.gradle.kts b/common-proxy/build.gradle.kts index 5a42ae1..c1f9156 100644 --- a/common-proxy/build.gradle.kts +++ b/common-proxy/build.gradle.kts @@ -7,4 +7,6 @@ dependencies { testFixturesImplementation(project(":api:api-common")) testFixturesImplementation(project(":api:api-proxy")) + + testFixturesImplementation(libs.adventure.api) } diff --git a/common-proxy/src/testFixtures/kotlin/su/plo/slib/proxy/TestProxy.kt b/common-proxy/src/testFixtures/kotlin/su/plo/slib/proxy/TestProxy.kt index b668fcb..2bbe458 100644 --- a/common-proxy/src/testFixtures/kotlin/su/plo/slib/proxy/TestProxy.kt +++ b/common-proxy/src/testFixtures/kotlin/su/plo/slib/proxy/TestProxy.kt @@ -1,16 +1,19 @@ package su.plo.slib.proxy import com.mojang.brigadier.Command -import com.mojang.brigadier.builder.LiteralArgumentBuilder +import net.kyori.adventure.key.Key +import net.kyori.adventure.translation.GlobalTranslator +import net.kyori.adventure.translation.TranslationStore import su.plo.slib.api.command.McCommandManager import su.plo.slib.api.command.McCommandSource -import su.plo.slib.api.command.brigadier.McBrigadierSource import su.plo.slib.api.event.player.McPlayerJoinEvent import su.plo.slib.api.event.player.McPlayerQuitEvent import su.plo.slib.api.logging.McLoggerFactory import su.plo.slib.api.proxy.command.McProxyCommand import su.plo.slib.api.proxy.event.command.McProxyCommandsRegisterEvent import su.plo.slib.proxy.command.UuidArgumentType +import java.text.MessageFormat +import java.util.Locale import java.util.UUID class TestProxy { @@ -59,5 +62,17 @@ class TestProxy { ) ) } + + registerVanillaTranslations() + } + + // Bungee/Velocity doesn't ship vanilla translations, so translation keys leak to the console as raw ids + // Register the minimum set the smoke tests need + private fun registerVanillaTranslations() { + val store = TranslationStore.messageFormat(Key.key("slib", "test")) + store.defaultLocale(Locale.US) + store.register("command.context.parse_error", Locale.US, MessageFormat("{0} at position {1}: {2}")) + store.register("argument.uuid.invalid", Locale.US, MessageFormat("Invalid UUID")) + GlobalTranslator.translator().addSource(store) } } From 06c428ac82cdf3bf6a14bf3906211af01f4ae698 Mon Sep 17 00:00:00 2001 From: Apehum Date: Thu, 30 Apr 2026 04:10:46 +0800 Subject: [PATCH 32/36] fix(mod): use sendSuccess in ModDefaultCommandSource#sendMessage --- gradle.properties | 2 +- .../su/plo/slib/mod/command/ModCommandManager.kt | 2 +- .../su/plo/slib/mod/command/ModDefaultCommandSource.kt | 10 +++++----- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/gradle.properties b/gradle.properties index af415dc..f8a7c9d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ # Version group=su.plo.slib -version=1.3.0-SNAPSHOT +version=1.3.0 # Gradle args org.gradle.jvmargs=-Xmx4G -Dkotlin.daemon.jvm.options=-Xmx512M diff --git a/modded/src/main/kotlin/su/plo/slib/mod/command/ModCommandManager.kt b/modded/src/main/kotlin/su/plo/slib/mod/command/ModCommandManager.kt index 001beeb..c0f3afc 100644 --- a/modded/src/main/kotlin/su/plo/slib/mod/command/ModCommandManager.kt +++ b/modded/src/main/kotlin/su/plo/slib/mod/command/ModCommandManager.kt @@ -44,7 +44,7 @@ class ModCommandManager( override fun getCommandSource(sourceStack: Any): McCommandSource { require(sourceStack is CommandSourceStack) { "source is not " + CommandSourceStack::class.java } - require(sourceStack is CommandSourceStackAccessor) { "source is not " + CommandSourceStack::class.java } + require(sourceStack is CommandSourceStackAccessor) { "source is not " + CommandSourceStackAccessor::class.java } val source = sourceStack.slib_getSource() diff --git a/modded/src/main/kotlin/su/plo/slib/mod/command/ModDefaultCommandSource.kt b/modded/src/main/kotlin/su/plo/slib/mod/command/ModDefaultCommandSource.kt index 6a94f4a..86e1858 100644 --- a/modded/src/main/kotlin/su/plo/slib/mod/command/ModDefaultCommandSource.kt +++ b/modded/src/main/kotlin/su/plo/slib/mod/command/ModDefaultCommandSource.kt @@ -19,11 +19,11 @@ class ModDefaultCommandSource( val json = minecraftServer.textConverter.convertToJson(this, text) val component = ComponentTextConverter.convertFromJson(json) - //? if >=1.19 { - source.sendSystemMessage(component) - //?} else { - /*source.sendSuccess(component, true); - *///?} + //? if >=1.20 { + /*source.sendSuccess({ component }, true) + *///?} else { + source.sendSuccess(component, true) + //?} } override fun sendActionBar(text: McTextComponent) = From 41de25e96d94c05f60e5063868cb5bd6ca57f63c Mon Sep 17 00:00:00 2001 From: Apehum Date: Sun, 10 May 2026 00:15:22 +0800 Subject: [PATCH 33/36] chore(spigot): remove unnecessary stack trace print --- .../kotlin/su/plo/slib/spigot/command/SpigotCommandManager.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/spigot/src/main/kotlin/su/plo/slib/spigot/command/SpigotCommandManager.kt b/spigot/src/main/kotlin/su/plo/slib/spigot/command/SpigotCommandManager.kt index c84fa68..55c33fe 100644 --- a/spigot/src/main/kotlin/su/plo/slib/spigot/command/SpigotCommandManager.kt +++ b/spigot/src/main/kotlin/su/plo/slib/spigot/command/SpigotCommandManager.kt @@ -46,8 +46,7 @@ class SpigotCommandManager( ) } } catch (e: Exception) { - logger.warn("Failed to get Brigadier dispatcher: {}", e) - e.printStackTrace() + logger.warn("Failed to get Brigadier dispatcher", e) } registered = true From ac9b5ceb1ba0d57c06e885bfda1901c1b0eebcfb Mon Sep 17 00:00:00 2001 From: Apehum Date: Sun, 10 May 2026 14:18:46 +0800 Subject: [PATCH 34/36] build: move deps to gradle catalog --- build.gradle.kts | 2 +- gradle/libs.versions.toml | 15 +++++++++++++++ minestom/build.gradle.kts | 11 +++++------ 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 5322ae3..039c189 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -21,7 +21,7 @@ subprojects { implementation(rootProject.libs.kotlinx.coroutines.jdk8) implementation(rootProject.libs.guava) - api("com.mojang:brigadier:1.0.18") + api(rootProject.libs.brigadier) } tasks { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index cde69c3..9490b9e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -21,6 +21,7 @@ bungee = "1.21-R0.4-SNAPSHOT" minestom = "2025.10.05-1.21.8" adventure = "4.21.0" +brigadier = "1.0.18" adventure-platform = "4.4.1" fabric-permissions = "0.3.1" @@ -28,6 +29,11 @@ reflection-remapper = "0.1.2" fletching-table = "0.1.0-alpha.22" +log4j = "2.25.3" +slf4j-log4j = "2.0.17" +jline = "3.21.0" +terminalconsoleappender = "1.3.0" + [libraries] kotlinx-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" } kotlinx-coroutines-jdk8 = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-jdk8", version.ref = "kotlinx-coroutines" } @@ -51,6 +57,9 @@ adventure-legacy = { module = "net.kyori:adventure-text-serializer-legacy", vers adventure-minimessage = { module = "net.kyori:adventure-text-minimessage", version.ref = "adventure" } adventure-bukkit = { module = "net.kyori:adventure-platform-bukkit", version.ref = "adventure-platform" } adventure-bungee = { module = "net.kyori:adventure-platform-bungeecord", version.ref = "adventure-platform" } + +brigadier = { module = "com.mojang:brigadier", version.ref = "brigadier" } + fabric-permissions = { module = "me.lucko:fabric-permissions-api", version.ref = "fabric-permissions" } reflectionremapper = { module = "xyz.jpenilla:reflection-remapper", version.ref = "reflection-remapper" } @@ -58,6 +67,12 @@ reflectionremapper = { module = "xyz.jpenilla:reflection-remapper", version.ref shadow = { module = "com.gradleup.shadow:com.gradleup.shadow.gradle.plugin", version.ref = "shadow" } asm = { module = "org.ow2.asm:asm-commons", version.ref = "asm" } +log4j-core = { module = "org.apache.logging.log4j:log4j-core", version.ref = "log4j" } +slf4j-log4j12 = { module = "org.slf4j:slf4j-log4j12", version.ref = "slf4j-log4j" } +jline-reader = { module = "org.jline:jline-reader", version.ref = "jline" } +jline-terminal = { module = "org.jline:jline-terminal", version.ref = "jline" } +terminalconsoleappender = { module = "net.minecrell:terminalconsoleappender", version.ref = "terminalconsoleappender" } + [plugins] architectury = { id = "gg.essential.loom", version.ref = "architectury" } dokka = { id = "org.jetbrains.dokka", version.ref = "dokka" } diff --git a/minestom/build.gradle.kts b/minestom/build.gradle.kts index a8b62f5..fe0b926 100644 --- a/minestom/build.gradle.kts +++ b/minestom/build.gradle.kts @@ -20,13 +20,12 @@ dependencies { testImplementation(libs.minestom) testImplementation(testFixtures(project(":common-server"))) - // todo: catalog - testImplementation("org.apache.logging.log4j:log4j-core:2.25.3") - testImplementation("org.slf4j:slf4j-log4j12:2.0.17") + testImplementation(libs.log4j.core) + testImplementation(libs.slf4j.log4j12) - testImplementation("org.jline:jline-reader:3.21.0") - testImplementation("org.jline:jline-terminal:3.21.0") - testImplementation("net.minecrell:terminalconsoleappender:1.3.0") + testImplementation(libs.jline.reader) + testImplementation(libs.jline.terminal) + testImplementation(libs.terminalconsoleappender) } tasks { From cf0d149caf2ef79e0640dc9689463fb91c522442 Mon Sep 17 00:00:00 2001 From: Apehum Date: Sun, 10 May 2026 14:29:24 +0800 Subject: [PATCH 35/36] build: group catalog entries --- gradle/libs.versions.toml | 93 +++++++++++++++++++++------------------ 1 file changed, 49 insertions(+), 44 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9490b9e..69f0f52 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,82 +1,87 @@ [versions] +# kotlin kotlin = "2.3.10" kotlinx-coroutines = "1.10.2" -dokka = "2.1.0" -run-task = "3.0.2" -architectury = "1.15.48" +# build tooling +dokka = "2.1.0" shadow = "9.3.2" asm = "9.9.1" +architectury = "1.15.48" +fletching-table = "0.1.0-alpha.22" +run-task = "3.0.2" +# common libraries annotations = "23.0.0" guava = "33.3.1-jre" slf4j = "1.7.30" semver4j = "6.0.0" +# platforms spigot = "1.16.5-R0.1-SNAPSHOT" folia = "1.20.1-R0.1-SNAPSHOT" velocity = "3.1.1" bungee = "1.21-R0.4-SNAPSHOT" - minestom = "2025.10.05-1.21.8" +# minecraft libraries adventure = "4.21.0" -brigadier = "1.0.18" adventure-platform = "4.4.1" +brigadier = "1.0.18" fabric-permissions = "0.3.1" - reflection-remapper = "0.1.2" -fletching-table = "0.1.0-alpha.22" - +# test runtime log4j = "2.25.3" slf4j-log4j = "2.0.17" jline = "3.21.0" terminalconsoleappender = "1.3.0" [libraries] -kotlinx-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" } +# kotlin +kotlinx-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" } kotlinx-coroutines-jdk8 = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-jdk8", version.ref = "kotlinx-coroutines" } -annotations = { module = "org.jetbrains:annotations", version.ref = "annotations" } -guava = { module = "com.google.guava:guava", version.ref = "guava" } -slf4j = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" } -semver4j = { module = "org.semver4j:semver4j", version.ref = "semver4j" } - -spigot = { module = "org.spigotmc:spigot-api", version.ref = "spigot" } -folia = { module = "dev.folia:folia-api", version.ref = "folia" } -velocity = { module = "com.velocitypowered:velocity-api", version.ref = "velocity" } -bungee-api = { module = "net.md-5:bungeecord-api", version.ref = "bungee" } +# builld tooling +shadow = { module = "com.gradleup.shadow:com.gradleup.shadow.gradle.plugin", version.ref = "shadow" } +asm = { module = "org.ow2.asm:asm-commons", version.ref = "asm" } + +# common libraries +annotations = { module = "org.jetbrains:annotations", version.ref = "annotations" } +guava = { module = "com.google.guava:guava", version.ref = "guava" } +slf4j = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" } +semver4j = { module = "org.semver4j:semver4j", version.ref = "semver4j" } + +# platforms +spigot = { module = "org.spigotmc:spigot-api", version.ref = "spigot" } +folia = { module = "dev.folia:folia-api", version.ref = "folia" } +velocity = { module = "com.velocitypowered:velocity-api", version.ref = "velocity" } +bungee-api = { module = "net.md-5:bungeecord-api", version.ref = "bungee" } bungee-proxy = { module = "net.md-5:bungeecord-proxy", version.ref = "bungee" } +minestom = { module = "net.minestom:minestom", version.ref = "minestom" } -minestom = { module = "net.minestom:minestom", version.ref = "minestom" } - -adventure-api = { module = "net.kyori:adventure-api", version.ref = "adventure" } -adventure-gson = { module = "net.kyori:adventure-text-serializer-gson", version.ref = "adventure" } -adventure-legacy = { module = "net.kyori:adventure-text-serializer-legacy", version.ref = "adventure" } +# minecraft libraries +adventure-api = { module = "net.kyori:adventure-api", version.ref = "adventure" } +adventure-gson = { module = "net.kyori:adventure-text-serializer-gson", version.ref = "adventure" } +adventure-legacy = { module = "net.kyori:adventure-text-serializer-legacy", version.ref = "adventure" } adventure-minimessage = { module = "net.kyori:adventure-text-minimessage", version.ref = "adventure" } -adventure-bukkit = { module = "net.kyori:adventure-platform-bukkit", version.ref = "adventure-platform" } -adventure-bungee = { module = "net.kyori:adventure-platform-bungeecord", version.ref = "adventure-platform" } - -brigadier = { module = "com.mojang:brigadier", version.ref = "brigadier" } - -fabric-permissions = { module = "me.lucko:fabric-permissions-api", version.ref = "fabric-permissions" } - -reflectionremapper = { module = "xyz.jpenilla:reflection-remapper", version.ref = "reflection-remapper" } - -shadow = { module = "com.gradleup.shadow:com.gradleup.shadow.gradle.plugin", version.ref = "shadow" } -asm = { module = "org.ow2.asm:asm-commons", version.ref = "asm" } - -log4j-core = { module = "org.apache.logging.log4j:log4j-core", version.ref = "log4j" } -slf4j-log4j12 = { module = "org.slf4j:slf4j-log4j12", version.ref = "slf4j-log4j" } -jline-reader = { module = "org.jline:jline-reader", version.ref = "jline" } -jline-terminal = { module = "org.jline:jline-terminal", version.ref = "jline" } +adventure-bukkit = { module = "net.kyori:adventure-platform-bukkit", version.ref = "adventure-platform" } +adventure-bungee = { module = "net.kyori:adventure-platform-bungeecord", version.ref = "adventure-platform" } +brigadier = { module = "com.mojang:brigadier", version.ref = "brigadier" } +fabric-permissions = { module = "me.lucko:fabric-permissions-api", version.ref = "fabric-permissions" } +reflectionremapper = { module = "xyz.jpenilla:reflection-remapper", version.ref = "reflection-remapper" } + +# test runtime +log4j-core = { module = "org.apache.logging.log4j:log4j-core", version.ref = "log4j" } +slf4j-log4j12 = { module = "org.slf4j:slf4j-log4j12", version.ref = "slf4j-log4j" } +jline-reader = { module = "org.jline:jline-reader", version.ref = "jline" } +jline-terminal = { module = "org.jline:jline-terminal", version.ref = "jline" } terminalconsoleappender = { module = "net.minecrell:terminalconsoleappender", version.ref = "terminalconsoleappender" } [plugins] -architectury = { id = "gg.essential.loom", version.ref = "architectury" } -dokka = { id = "org.jetbrains.dokka", version.ref = "dokka" } -run-paper = { id = "xyz.jpenilla.run-paper", version.ref = "run-task"} -run-velocity = { id = "xyz.jpenilla.run-velocity", version.ref = "run-task"} -run-waterfall = { id = "xyz.jpenilla.run-waterfall", version.ref = "run-task"} +dokka = { id = "org.jetbrains.dokka", version.ref = "dokka" } +architectury = { id = "gg.essential.loom", version.ref = "architectury" } fletchingtable = { id = "dev.kikugie.fletching-table", version.ref = "fletching-table" } +run-paper = { id = "xyz.jpenilla.run-paper", version.ref = "run-task" } +run-velocity = { id = "xyz.jpenilla.run-velocity", version.ref = "run-task" } +run-waterfall = { id = "xyz.jpenilla.run-waterfall", version.ref = "run-task" } From d2e1597f091680ccc06aa1dd123b55e85879c357 Mon Sep 17 00:00:00 2001 From: Apehum Date: Sun, 10 May 2026 14:55:13 +0800 Subject: [PATCH 36/36] ci: add concurrency for build-pr [ci skip] --- .github/workflows/build-pr.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/build-pr.yml b/.github/workflows/build-pr.yml index 2553a2e..0ced84d 100644 --- a/.github/workflows/build-pr.yml +++ b/.github/workflows/build-pr.yml @@ -13,6 +13,10 @@ on: - 'fix/**' workflow_dispatch: +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.ref_name }} + cancel-in-progress: true + jobs: build: runs-on: ubuntu-latest