9
0
mirror of https://github.com/LeavesMC/Leaves.git synced 2025-12-31 04:46:29 +00:00

feat: neo command

This commit is contained in:
MC_XiaoHei
2025-08-22 09:54:53 +08:00
parent 3feea8e40a
commit 463e408b0d
8 changed files with 427 additions and 0 deletions

View File

@@ -0,0 +1,18 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: MC_XiaoHei <xor7xiaohei@gmail.com>
Date: Fri, 22 Aug 2025 09:42:16 +0800
Subject: [PATCH] Leaves Server Command
diff --git a/net/minecraft/commands/Commands.java b/net/minecraft/commands/Commands.java
index ec1cced129ef42be65d7b2b622638bfae8bd895e..b54b1b56c8df6c6e03c2f53423b273a7c975a498 100644
--- a/net/minecraft/commands/Commands.java
+++ b/net/minecraft/commands/Commands.java
@@ -182,6 +182,7 @@ public class Commands {
}
public Commands(Commands.CommandSelection selection, CommandBuildContext context, final boolean modern) {
// Paper end - Brigadier API - modern minecraft overloads that do not use redirects but are copies instead
+ org.leavesmc.leaves.neo_command.LeavesCommands.registerLeavesCommands(dispatcher); // Leaves Commands
AdvancementCommands.register(this.dispatcher);
AttributeCommand.register(this.dispatcher, context);
ExecuteCommand.register(this.dispatcher, context);

View File

@@ -0,0 +1,37 @@
package org.leavesmc.leaves.neo_command;
import com.mojang.brigadier.arguments.ArgumentType;
import com.mojang.brigadier.builder.ArgumentBuilder;
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.suggestion.Suggestions;
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
import java.util.concurrent.CompletableFuture;
public abstract class ArgumentNode<T> extends CommandNode {
private final ArgumentType<T> argumentType;
protected ArgumentNode(String name, ArgumentType<T> argumentType) {
super(name);
this.argumentType = argumentType;
}
@SuppressWarnings({"unused", "RedundantThrows"})
protected CompletableFuture<Suggestions> getSuggestions(final CommandContext context, final SuggestionsBuilder builder) throws CommandSyntaxException {
return Suggestions.empty();
}
@Override
protected ArgumentBuilder<CommandSourceStack, ?> compileBase() {
RequiredArgumentBuilder<CommandSourceStack, T> argumentBuilder = Commands.argument(name, argumentType);
if (isMethodOverridden("getSuggestions", ArgumentNode.class)) {
argumentBuilder.suggests(
(context, builder) -> getSuggestions(new CommandContext(context), builder)
);
}
return argumentBuilder;
}
}

View File

@@ -0,0 +1,105 @@
package org.leavesmc.leaves.neo_command;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.RedirectModifier;
import com.mojang.brigadier.context.ParsedCommandNode;
import com.mojang.brigadier.context.StringRange;
import com.mojang.brigadier.tree.CommandNode;
import net.minecraft.commands.CommandSourceStack;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
import java.util.List;
import static org.leavesmc.leaves.neo_command.CommandNode.*;
@SuppressWarnings({"ClassCanBeRecord", "unused"})
public class CommandContext {
private final com.mojang.brigadier.context.CommandContext<CommandSourceStack> source;
public CommandContext(com.mojang.brigadier.context.CommandContext<CommandSourceStack> source) {
this.source = source;
}
public com.mojang.brigadier.context.CommandContext<CommandSourceStack> getChild() {
return source.getChild();
}
public com.mojang.brigadier.context.CommandContext<CommandSourceStack> getLastChild() {
return source.getLastChild();
}
public Command<CommandSourceStack> getCommand() {
return source.getCommand();
}
public CommandSourceStack getSource() {
return source.getSource();
}
public CommandSender getSender() {
return source.getSource().getSender();
}
public <V> @NotNull V getArgument(final String name, final Class<V> clazz) {
return source.getArgument(name, clazz);
}
@SuppressWarnings("unchecked")
public <V> @NotNull V getArgument(final Class<? extends ArgumentNode<V>> nodeClass) {
String name = getNameForNode(nodeClass);
return (V) source.getArgument(name, Object.class);
}
public <V> @NotNull V getArgumentOrDefault(final Class<? extends ArgumentNode<V>> nodeClass, final V defaultValue) {
try {
return getArgument(nodeClass);
} catch (IllegalArgumentException e) {
return defaultValue;
}
}
public <V> V getArgumentOrDefault(final String name, final Class<V> clazz, final V defaultValue) {
try {
return source.getArgument(name, clazz);
} catch (IllegalArgumentException e) {
return defaultValue;
}
}
public String getStringOrDefault(final String name, final String defaultValue) {
return getArgumentOrDefault(name, String.class, defaultValue);
}
public int getIntegerOrDefault(final String name, final int defaultValue) {
return getArgumentOrDefault(name, Integer.class, defaultValue);
}
public RedirectModifier<CommandSourceStack> getRedirectModifier() {
return source.getRedirectModifier();
}
public StringRange getRange() {
return source.getRange();
}
public String getInput() {
return source.getInput();
}
public CommandNode<CommandSourceStack> getRootNode() {
return source.getRootNode();
}
public List<ParsedCommandNode<CommandSourceStack>> getNodes() {
return source.getNodes();
}
public boolean hasNodes() {
return source.hasNodes();
}
public boolean isForked() {
return source.isForked();
}
}

View File

@@ -0,0 +1,74 @@
package org.leavesmc.leaves.neo_command;
import com.mojang.brigadier.builder.ArgumentBuilder;
import net.minecraft.commands.CommandSourceStack;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
import java.util.stream.Stream;
public abstract class CommandNode {
private static final Map<Class<? extends CommandNode>, String> class2NameMap = new HashMap<>();
protected final String name;
protected final List<CommandNode> children = new ArrayList<>();
protected CommandNode(String name) {
this.name = name;
class2NameMap.put(getClass(), name);
}
@SafeVarargs
protected final void children(Supplier<? extends CommandNode>... childrenClasses) {
this.children.addAll(Stream.of(childrenClasses).map(Supplier::get).toList());
}
protected abstract ArgumentBuilder<CommandSourceStack, ?> compileBase();
protected boolean execute(CommandContext context) {
return true;
}
protected boolean requires(CommandSourceStack source) {
return true;
}
protected ArgumentBuilder<CommandSourceStack, ?> compile() {
ArgumentBuilder<CommandSourceStack, ?> builder = compileBase();
if (isMethodOverridden("requires", CommandNode.class)) {
builder = builder.requires(this::requires);
}
for (CommandNode child : children) {
builder = builder.then(child.compile());
}
if (isMethodOverridden("execute", CommandNode.class)) {
builder = builder.executes(mojangCtx -> {
CommandContext ctx = new CommandContext(mojangCtx);
return execute(ctx) ? 1 : 0;
});
}
return builder;
}
protected boolean isMethodOverridden(String methodName, @NotNull Class<?> baseClass) {
for (Method method : getClass().getDeclaredMethods()) {
if (method.getName().equals(methodName)) {
return method.getDeclaringClass() != baseClass;
}
}
return false;
}
public static String getNameForNode(Class<? extends CommandNode> nodeClass) {
return class2NameMap.get(nodeClass);
}
}

View File

@@ -0,0 +1,10 @@
package org.leavesmc.leaves.neo_command;
import org.leavesmc.leaves.neo_command.subcommands.ConfigCommand;
public class LeavesCommand extends LiteralNode {
public LeavesCommand() {
super("leaves_new");
children(ConfigCommand::new);
}
}

View File

@@ -0,0 +1,11 @@
package org.leavesmc.leaves.neo_command;
import com.mojang.brigadier.CommandDispatcher;
import net.minecraft.commands.CommandSourceStack;
import org.jetbrains.annotations.NotNull;
public class LeavesCommands {
public static void registerLeavesCommands(@NotNull CommandDispatcher<CommandSourceStack> dispatcher) {
new LeavesCommand().register(dispatcher);
}
}

View File

@@ -0,0 +1,25 @@
package org.leavesmc.leaves.neo_command;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.builder.ArgumentBuilder;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
import org.jetbrains.annotations.NotNull;
public class LiteralNode extends CommandNode {
protected LiteralNode(String name) {
super(name);
}
@Override
protected ArgumentBuilder<CommandSourceStack, ?> compileBase() {
return Commands.literal(name);
}
@SuppressWarnings("unchecked")
public void register(@NotNull CommandDispatcher<CommandSourceStack> dispatcher) {
dispatcher.register((LiteralArgumentBuilder<CommandSourceStack>) compile());
}
}

View File

@@ -0,0 +1,147 @@
package org.leavesmc.leaves.neo_command.subcommands;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.suggestion.Suggestions;
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.JoinConfiguration;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.Bukkit;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.leavesmc.leaves.command.LeavesCommandUtil;
import org.leavesmc.leaves.config.GlobalConfigManager;
import org.leavesmc.leaves.config.VerifiedConfig;
import org.leavesmc.leaves.neo_command.ArgumentNode;
import org.leavesmc.leaves.neo_command.CommandContext;
import org.leavesmc.leaves.neo_command.LiteralNode;
import java.util.concurrent.CompletableFuture;
import static net.kyori.adventure.text.Component.text;
public class ConfigCommand extends LiteralNode {
public ConfigCommand() {
super("config");
children(PathArgument::new);
}
private static class PathArgument extends ArgumentNode<String> {
public PathArgument() {
super("path", StringArgumentType.string());
children(ValueArgument::new);
}
@Override
protected CompletableFuture<Suggestions> getSuggestions(@NotNull CommandContext context, @NotNull SuggestionsBuilder builder) {
String path = context.getArgumentOrDefault(PathArgument.class, "");
int dotIndex = path.lastIndexOf(".");
builder = builder.createOffset(builder.getInput().lastIndexOf(' ') + dotIndex + 2);
LeavesCommandUtil.getListClosestMatchingLast(
context.getSender(),
path.substring(dotIndex + 1),
GlobalConfigManager.getVerifiedConfigSubPaths(path),
"bukkit.command.leaves.config"
)
.forEach(builder::suggest);
return builder.buildFuture();
}
@Override
protected boolean execute(@NotNull CommandContext context) {
String path = context.getArgument(PathArgument.class);
VerifiedConfig verifiedConfig = getVerifiedConfig(context);
if (verifiedConfig == null) {
return false;
}
context.getSender().sendMessage(Component.join(JoinConfiguration.spaces(),
text("Config", NamedTextColor.GRAY),
text(path, NamedTextColor.AQUA),
text("value is", NamedTextColor.GRAY),
text(verifiedConfig.getString(), NamedTextColor.AQUA)
));
return true;
}
private static @Nullable VerifiedConfig getVerifiedConfig(@NotNull CommandContext context) {
String path = context.getArgument(PathArgument.class);
VerifiedConfig verifiedConfig = GlobalConfigManager.getVerifiedConfig(path);
if (verifiedConfig == null) {
context.getSender().sendMessage(Component.join(JoinConfiguration.spaces(),
text("Config", NamedTextColor.GRAY),
text(path, NamedTextColor.RED),
text("is Not Found.", NamedTextColor.GRAY)
));
return null;
}
return verifiedConfig;
}
private static class ValueArgument extends ArgumentNode<String> {
public ValueArgument() {
super("value", StringArgumentType.greedyString());
}
@Override
protected CompletableFuture<Suggestions> getSuggestions(@NotNull CommandContext context, @NotNull SuggestionsBuilder builder) {
String path = context.getArgument(PathArgument.class);
String value = context.getArgumentOrDefault(ValueArgument.class, "");
VerifiedConfig verifiedConfig = GlobalConfigManager.getVerifiedConfig(path);
if (verifiedConfig == null) {
return builder
.suggest("<ERROR CONFIG>", net.minecraft.network.chat.Component.literal("This config path does not exist."))
.buildFuture();
}
LeavesCommandUtil.getListMatchingLast(
context.getSender(),
new String[]{value},
verifiedConfig.validator().valueSuggest()
).forEach(builder::suggest);
return builder.buildFuture();
}
@Override
protected boolean execute(@NotNull CommandContext context) {
VerifiedConfig verifiedConfig = getVerifiedConfig(context);
String path = context.getArgument(PathArgument.class);
String value = context.getArgument(ValueArgument.class);
if (verifiedConfig == null) {
return false;
}
try {
verifiedConfig.set(value);
context.getSender().sendMessage(Component.join(JoinConfiguration.spaces(),
text("Config", NamedTextColor.GRAY),
text(path, NamedTextColor.AQUA),
text("changed to", NamedTextColor.GRAY),
text(verifiedConfig.getString(), NamedTextColor.AQUA)
));
Bukkit.getOnlinePlayers()
.stream()
.filter(player -> player.hasPermission("leaves.command.config.notify") && player != context.getSender())
.forEach(
player -> player.sendMessage(Component.join(JoinConfiguration.spaces(),
text(context.getSender().getName() + ":", NamedTextColor.GRAY),
text("Config", NamedTextColor.GRAY),
text(path, NamedTextColor.AQUA),
text("changed to", NamedTextColor.GRAY),
text(verifiedConfig.getString(), NamedTextColor.AQUA)
))
);
return true;
} catch (IllegalArgumentException exception) {
context.getSender().sendMessage(Component.join(JoinConfiguration.spaces(),
text("Config", NamedTextColor.GRAY),
text(path, NamedTextColor.RED),
text("modify error by", NamedTextColor.GRAY),
text(exception.getMessage(), NamedTextColor.RED)
));
return false;
}
}
}
}
}