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