Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 2 additions & 5 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,5 @@ bin/
.DS_Store
.idea/

scripts/impl/__pycache__/

scripts/ui/__pycache__/

scripts/utils/__pycache__/
run/
test-plugin.settings.gradle.kts
77 changes: 68 additions & 9 deletions pineapple-core/src/main/java/sh/miles/pineapple/PineappleLib.java
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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)}
* <p>
* Provides ease of access to many features of PineappleLib and puts them all into one place.
*
Expand All @@ -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;
Expand All @@ -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());
Expand Down Expand Up @@ -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);
}

Expand All @@ -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);
}

Expand All @@ -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<CommandSourceStack> convert(AdvancedCommand advancedCommand) {
var command = Commands.literal(advancedCommand.getCommandLabel().getName());

List<Map.Entry<String, ArgumentType<?>>> reversedEntries = new LinkedList<>(advancedCommand.getArguments().entrySet());
Collections.reverse(reversedEntries);

RequiredArgumentBuilder<CommandSourceStack, ?> chainedArgument = null;
for (Map.Entry<String, ArgumentType<?>> 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;
}

/**
Expand Down Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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<AdvancedCommand> subcommands;
private final Map<String, ArgumentType<?>> 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) {
Copy link
Member

@Y2Kwastaken Y2Kwastaken Feb 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How would it feel to wrap CommandContext in this case? I feel like this API could be nicer if we served something "easier" Paper has a lot of these really annoying inbetween types, which if we wrap, we could resolve in a more "beautiful manner" Mainly what comes to my mind is any form of Registry entry or Location is absolutely annoying to resolve.

It's also nice because we know we'll always have a CommandSourceStack so we can chuck away that annoying generic too

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It depends on the info we actually end up needing from the context, we could technically use the arguments the command knows about to pass a CommandSourceStack with some form of argument collection, just depends on how we handle the generics on that collection

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It depends on the info we actually end up needing from the context, we could technically use the arguments the command knows about to pass a CommandSourceStack with some form of argument collection, just depends on how we handle the generics on that collection

Perhaps I'll try building something out this weekend and we see how we like it?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good to me, lmk if there's any ideas you have that I could attempt

}

/**
* 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<AdvancedCommand> getSubcommands() {
return new ArrayList<>(subcommands);
}

/**
* Get the arguments
*
* @return the commands
* @since 1.0.0-SNAPSHOT
*/
@ApiStatus.Internal
public Map<String, ArgumentType<?>> getArguments() {
return new LinkedHashMap<>(arguments);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public class Command implements BasicCommand {
protected BiConsumer<CommandSender, String[]> noArgExecutor = (s, a) -> {};

/**
* Creates SCommand
* Creates Command
*
* @param label label
* @param settings settings
Expand All @@ -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
Expand Down
4 changes: 4 additions & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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")
2 changes: 2 additions & 0 deletions test-plugin.settings.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Uncomment to enable
// include(":test-plugin")
23 changes: 23 additions & 0 deletions test-plugin/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -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()
}
}
Loading