diff --git a/.gitignore b/.gitignore index aba1ffc..2075f58 100644 --- a/.gitignore +++ b/.gitignore @@ -42,8 +42,5 @@ bin/ .DS_Store .idea/ -scripts/impl/__pycache__/ - -scripts/ui/__pycache__/ - -scripts/utils/__pycache__/ +run/ +test-plugin.settings.gradle.kts \ No newline at end of file diff --git a/pineapple-core/src/main/java/sh/miles/pineapple/PineappleLib.java b/pineapple-core/src/main/java/sh/miles/pineapple/PineappleLib.java index 1ffa39e..bde690e 100644 --- a/pineapple-core/src/main/java/sh/miles/pineapple/PineappleLib.java +++ b/pineapple-core/src/main/java/sh/miles/pineapple/PineappleLib.java @@ -1,8 +1,19 @@ package sh.miles.pineapple; +import com.google.common.collect.HashBiMap; +import com.google.common.collect.ImmutableBiMap; +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 io.papermc.paper.command.brigadier.CommandSourceStack; +import io.papermc.paper.command.brigadier.Commands; +import io.papermc.paper.command.brigadier.argument.ArgumentTypes; import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents; import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.java.JavaPlugin; import org.jetbrains.annotations.NotNull; +import sh.miles.pineapple.command.AdvancedCommand; import sh.miles.pineapple.command.Command; import sh.miles.pineapple.command.CommandLabel; import sh.miles.pineapple.command.internal.PineappleCommandManager; @@ -19,10 +30,15 @@ import java.io.IOException; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; import java.util.logging.Logger; /** - * The main library class for PineappleLib. That should be loaded using {@link PineappleLib#initialize(Plugin)} + * The main library class for PineappleLib. That should be loaded using {@link PineappleLib#initialize(JavaPlugin)} *

* Provides ease of access to many features of PineappleLib and puts them all into one place. * @@ -32,7 +48,7 @@ public final class PineappleLib { private static PineappleLib instance; - private final Plugin plugin; + private final JavaPlugin plugin; private PineappleNMS nmsProvider; private final ConfigManager configurationManager; private final GuiManager guiManager; @@ -45,7 +61,7 @@ public final class PineappleLib { * @param plugin the plugin * @param useNms whether or not to use NMS */ - private PineappleLib(final Plugin plugin, final boolean useNms) { + private PineappleLib(final JavaPlugin plugin, final boolean useNms) { this.plugin = plugin; if (useNms) { NMSLoader.INSTANCE.activate(plugin.getLogger()); @@ -128,7 +144,7 @@ public static String getVersion() { * @param plugin the plugin * @since 1.0.0-SNAPSHOT */ - public static void initialize(@NotNull final Plugin plugin) { + public static void initialize(@NotNull final JavaPlugin plugin) { instance = new PineappleLib(plugin, false); } @@ -139,7 +155,7 @@ public static void initialize(@NotNull final Plugin plugin) { * @param useNms decides whether or not to use NMS * @since 1.0.0-SNAPSHOT */ - public static void initialize(@NotNull final Plugin plugin, final boolean useNms) { + public static void initialize(@NotNull final JavaPlugin plugin, final boolean useNms) { instance = new PineappleLib(plugin, useNms); } @@ -152,9 +168,52 @@ public static void initialize(@NotNull final Plugin plugin, final boolean useNms public static void registerCommand(@NotNull final Command command) { final CommandLabel label = command.getCommandLabel(); - instance.plugin.getLifecycleManager().registerEventHandler(LifecycleEvents.COMMANDS, commands -> commands.registrar() - .register(label.getName(), label.getDescription(), label.getAliases(), command) - ); + instance.plugin.registerCommand(label.getName(), label.getDescription(), label.getAliases(), command); + } + + public static void registerCommand(@NotNull final AdvancedCommand advancedCommand) { + instance.plugin.getLifecycleManager() + .registerEventHandler( + LifecycleEvents.COMMANDS, + commands -> commands.registrar().register(convert(advancedCommand).build()) + ); + } + + @SuppressWarnings({"UnstableApiUsage"}) + private static LiteralArgumentBuilder convert(AdvancedCommand advancedCommand) { + var command = Commands.literal(advancedCommand.getCommandLabel().getName()); + + List>> reversedEntries = new LinkedList<>(advancedCommand.getArguments().entrySet()); + Collections.reverse(reversedEntries); + + RequiredArgumentBuilder chainedArgument = null; + for (Map.Entry> entry : reversedEntries) { + if (chainedArgument == null) { + chainedArgument = Commands.argument(entry.getKey(), entry.getValue()).requires(sourceStack -> advancedCommand.canUse(sourceStack.getSender())).executes((context) -> { + advancedCommand.execute(context); + return com.mojang.brigadier.Command.SINGLE_SUCCESS; + }); + } else { + chainedArgument = Commands.argument(entry.getKey(), entry.getValue()).then(chainedArgument); + } + } + + + if (chainedArgument == null) { + command = command.requires(sourceStack -> advancedCommand.canUse(sourceStack.getSender())).executes((context) -> { + advancedCommand.execute(context); + return com.mojang.brigadier.Command.SINGLE_SUCCESS; + }); + } else { + command.then(chainedArgument); + } + + + for (AdvancedCommand subCommand : advancedCommand.getSubcommands()) { + command.then(convert(subCommand)); + } + + return command; } /** @@ -185,7 +244,7 @@ public static void cleanup() { private void loadVersion() { try (final BufferedReader reader = new BufferedReader( - new InputStreamReader(getClass().getResourceAsStream("/pineapple.version"), StandardCharsets.UTF_8) + new InputStreamReader(getClass().getResourceAsStream("/pineapple.version"), StandardCharsets.UTF_8) )) { this.version = reader.readLine(); } catch (IOException ignored) { diff --git a/pineapple-core/src/main/java/sh/miles/pineapple/command/AdvancedCommand.java b/pineapple-core/src/main/java/sh/miles/pineapple/command/AdvancedCommand.java new file mode 100644 index 0000000..e1fcffa --- /dev/null +++ b/pineapple-core/src/main/java/sh/miles/pineapple/command/AdvancedCommand.java @@ -0,0 +1,108 @@ +package sh.miles.pineapple.command; + +import com.google.common.base.Preconditions; +import com.mojang.brigadier.arguments.ArgumentType; +import com.mojang.brigadier.context.CommandContext; +import io.papermc.paper.command.brigadier.CommandSourceStack; +import org.bukkit.command.CommandSender; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +public class AdvancedCommand { + + private final CommandLabel label; + private final CommandSettings.Settings settings; + private final List subcommands; + private final Map> arguments; + + /** + * Creates Command + * + * @param label label + * @param settings settings + * @since 1.0.0-SNAPSHOT + */ + public AdvancedCommand(@NotNull final CommandLabel label, @NotNull final CommandSettings.Settings settings) { + Preconditions.checkNotNull(label); + Preconditions.checkNotNull(settings); + + this.label = label; + this.settings = settings; + this.subcommands = new ArrayList<>(); + this.arguments = new LinkedHashMap<>(); + } + + /** + * Creates Command + * + * @param label label + * @since 1.0.0-SNAPSHOT + */ + public AdvancedCommand(@NotNull final CommandLabel label) { + this(label, CommandSettings.DEFAULT_COMMAND_SETTINGS); + } + + public void execute(@NotNull CommandContext<@NotNull CommandSourceStack> context) { + } + + /** + * Register an argument for a command + * Use {@link io.papermc.paper.command.brigadier.argument.ArgumentTypes} for built-in or implement {@link ArgumentType} + * + * @param name argument name + * @param argumentType argument type + * @since 1.0.0-SNAPSHOT + */ + public void registerArgument(@NotNull String name, @NotNull ArgumentType argumentType) { + this.arguments.put(name, argumentType); + } + + /** + * Registers a command under this command. (sub-command) + * + * @param command the command to register + * @since 1.0.0-SNAPSHOT + */ + public void registerSubcommand(@NotNull AdvancedCommand command) { + this.subcommands.add(command); + } + + public boolean canUse(CommandSender sender) { + return sender.hasPermission(label.getPermission()); + } + + public CommandLabel getCommandLabel() { + return label; + } + + public CommandSettings.Settings getSettings() { + return settings; + } + + /** + * Get the subcommands + * + * @return the commands + * @since 1.0.0-SNAPSHOT + */ + @ApiStatus.Internal + public List getSubcommands() { + return new ArrayList<>(subcommands); + } + + /** + * Get the arguments + * + * @return the commands + * @since 1.0.0-SNAPSHOT + */ + @ApiStatus.Internal + public Map> getArguments() { + return new LinkedHashMap<>(arguments); + } +} diff --git a/pineapple-core/src/main/java/sh/miles/pineapple/command/Command.java b/pineapple-core/src/main/java/sh/miles/pineapple/command/Command.java index 25efee0..033fbc0 100644 --- a/pineapple-core/src/main/java/sh/miles/pineapple/command/Command.java +++ b/pineapple-core/src/main/java/sh/miles/pineapple/command/Command.java @@ -30,7 +30,7 @@ public class Command implements BasicCommand { protected BiConsumer noArgExecutor = (s, a) -> {}; /** - * Creates SCommand + * Creates Command * * @param label label * @param settings settings @@ -46,7 +46,7 @@ public Command(@NotNull final CommandLabel label, @NotNull final CommandSettings } /** - * Creates SCommand + * Creates Command * * @param label label * @since 1.0.0-SNAPSHOT diff --git a/settings.gradle.kts b/settings.gradle.kts index 1f5a1f6..6e60b5d 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -30,5 +30,9 @@ file("pineapple-apis").listFiles()?.forEach { project -> } } +// Inspired from PaperMC +apply(from = "test-plugin.settings.gradle.kts") +findProject(":test-plugin")?.projectDir = file("test-plugin") + // Features enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") \ No newline at end of file diff --git a/test-plugin.settings.gradle.kts b/test-plugin.settings.gradle.kts new file mode 100644 index 0000000..c83d520 --- /dev/null +++ b/test-plugin.settings.gradle.kts @@ -0,0 +1,2 @@ +// Uncomment to enable +// include(":test-plugin") \ No newline at end of file diff --git a/test-plugin/build.gradle.kts b/test-plugin/build.gradle.kts new file mode 100644 index 0000000..75a4478 --- /dev/null +++ b/test-plugin/build.gradle.kts @@ -0,0 +1,23 @@ +plugins { + kotlin("jvm") + id("com.gradleup.shadow") + id("xyz.jpenilla.run-paper") version "2.3.1" +} + +dependencies { + compileOnly(libs.paper.api) + implementation(project(":pineapple-core")) + implementation(project(":pineapple-common")) + implementation(project(":pineapple-nms:api")) +} + +tasks.runServer { + minecraftVersion("1.21.4") + + workingDir = file("run") + systemProperty("net.kyori.adventure.text.warnWhenLegacyFormattingDetected", true) + + doFirst { + workingDir.mkdirs() + } +} \ No newline at end of file diff --git a/test-plugin/src/main/kotlin/sh/miles/testplugin/TestPlugin.kt b/test-plugin/src/main/kotlin/sh/miles/testplugin/TestPlugin.kt new file mode 100644 index 0000000..2e22811 --- /dev/null +++ b/test-plugin/src/main/kotlin/sh/miles/testplugin/TestPlugin.kt @@ -0,0 +1,105 @@ +package sh.miles.testplugin + +import com.mojang.brigadier.arguments.StringArgumentType +import com.mojang.brigadier.builder.RequiredArgumentBuilder +import com.mojang.brigadier.context.CommandContext +import io.papermc.paper.command.brigadier.CommandSourceStack +import io.papermc.paper.command.brigadier.Commands +import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents +import org.bukkit.plugin.java.JavaPlugin +import sh.miles.pineapple.PineappleLib +import sh.miles.pineapple.command.AdvancedCommand +import sh.miles.pineapple.command.CommandLabel + +class TestPlugin: JavaPlugin() { + + @Suppress("UnstableApiUsage") + override fun onEnable() { + PineappleLib.initialize(this) + PineappleLib.registerCommand(TestCommand) + + val arguments = listOf( + Commands.argument("first", StringArgumentType.word()), + Commands.argument("second", StringArgumentType.word()), + ) + + + var chainedArguments: RequiredArgumentBuilder? = null + for (argument in arguments.reversed()) { + chainedArguments = if (chainedArguments == null) { + argument.requires { source -> source.sender.isOp }.executes { ctx -> + val first = StringArgumentType.getString(ctx, "first") + val second = StringArgumentType.getString(ctx, "second") + ctx.source.sender.sendRichMessage( + "Test $first $second" + ) + return@executes 1 + } + } else { + argument.then(chainedArguments) + } + } + + val finalCommand = Commands.literal("egg").then(chainedArguments) + + + + lifecycleManager.registerEventHandler(LifecycleEvents.COMMANDS) { commands -> + commands.registrar().register(finalCommand.build()) + commands.registrar().register( + Commands.literal("testcmd").then( + Commands.argument("first", StringArgumentType.word()).then( + Commands.argument("second", StringArgumentType.word()).requires { source -> source.sender.isOp } + .executes { ctx -> + val first = StringArgumentType.getString(ctx, "first") + val second = StringArgumentType.getString(ctx, "second") + ctx.source.executor.sendRichMessage( + "Test $first $second" + ) + return@executes 1 + }) + ).build() + ) + } + + + } +} + +object TestCommand: AdvancedCommand(CommandLabel("testcommand", "test-plugin.command.test")) { + + init { + registerSubcommand(FirstCommand) + registerSubcommand(SecondCommand) + } + + object FirstCommand: AdvancedCommand(CommandLabel("first", super.commandLabel.permission + ".first")) { + + init { + registerArgument("third", StringArgumentType.word()) + registerArgument("fourth", StringArgumentType.word()) + registerArgument("fifth", StringArgumentType.word()) + } + + override fun execute(context: CommandContext) { + val sender = context.source.executor!! + + sender.sendRichMessage( + "Executed first command! with ${ + StringArgumentType.getString( + context, "third" + ) + } ${StringArgumentType.getString(context, "fourth")}" + ) + } + + } + + object SecondCommand: AdvancedCommand(CommandLabel("second", super.commandLabel.permission + ".second")) { + override fun execute(context: CommandContext) { + val sender = context.source.executor!! + + sender.sendRichMessage("Executed second command!") + } + } +} diff --git a/test-plugin/src/main/resources/paper-plugin.yml b/test-plugin/src/main/resources/paper-plugin.yml new file mode 100644 index 0000000..31afacd --- /dev/null +++ b/test-plugin/src/main/resources/paper-plugin.yml @@ -0,0 +1,7 @@ +name: Test-Plugin +version: "1.0.0" +main: sh.miles.testplugin.TestPlugin +description: Test Plugin +author: Pineapple +api-version: 1.21.4 +defaultPerm: OP \ No newline at end of file