Skip to content

Commit a0b1046

Browse files
committed
Delegate Bukkit command registering logic to CommandRegistrationStrategy
Just a rough example implementation `NMS#getResourceDispatcher` replaced by `NMS#createCommandRegistrationStrategy`, which creates a `SpigotCommandRegistration` implementation using the resources dispatcher Alternate fix for CommandAPI#554. If we're on Paper-1.20.6-65, a `PaperCommandRegistration` implementation is used instead. This can be used to properly handle the internal changes made by PaperMC/Paper#8235.
1 parent 5a75af8 commit a0b1046

File tree

21 files changed

+560
-452
lines changed

21 files changed

+560
-452
lines changed

commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandAPIBukkit.java

Lines changed: 12 additions & 305 deletions
Large diffs are not rendered by default.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package dev.jorel.commandapi;
2+
3+
import com.mojang.brigadier.CommandDispatcher;
4+
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
5+
import com.mojang.brigadier.tree.CommandNode;
6+
import com.mojang.brigadier.tree.LiteralCommandNode;
7+
import com.mojang.brigadier.tree.RootCommandNode;
8+
import dev.jorel.commandapi.preprocessor.RequireField;
9+
10+
import java.util.HashSet;
11+
import java.util.List;
12+
import java.util.Map;
13+
import java.util.function.Predicate;
14+
15+
@RequireField(in = CommandNode.class, name = "children", ofType = Map.class)
16+
@RequireField(in = CommandNode.class, name = "literals", ofType = Map.class)
17+
@RequireField(in = CommandNode.class, name = "arguments", ofType = Map.class)
18+
public abstract class CommandRegistrationStrategy<Source> {
19+
// Reflection
20+
// I'd like to make the Maps here `Map<String, CommandNode<Source>>`, but these static fields cannot use the type
21+
// parameter Source. We still need to cast to that signature for map, so Map is raw.
22+
static final SafeVarHandle<CommandNode<?>, Map> commandNodeChildren;
23+
private static final SafeVarHandle<CommandNode<?>, Map> commandNodeLiterals;
24+
private static final SafeVarHandle<CommandNode<?>, Map> commandNodeArguments;
25+
26+
// Compute all var handles all in one go so we don't do this during main server runtime
27+
static {
28+
commandNodeChildren = SafeVarHandle.ofOrNull(CommandNode.class, "children", "children", Map.class);
29+
commandNodeLiterals = SafeVarHandle.ofOrNull(CommandNode.class, "literals", "literals", Map.class);
30+
commandNodeArguments = SafeVarHandle.ofOrNull(CommandNode.class, "arguments", "arguments", Map.class);
31+
}
32+
33+
// Utility methods
34+
protected void removeBrigadierCommands(CommandDispatcher<Source> dispatcher, String commandName,
35+
boolean unregisterNamespaces, Predicate<CommandNode<Source>> extraCheck) {
36+
RootCommandNode<?> root = dispatcher.getRoot();
37+
Map<String, CommandNode<Source>> children = (Map<String, CommandNode<Source>>) commandNodeChildren.get(root);
38+
Map<String, CommandNode<Source>> literals = (Map<String, CommandNode<Source>>) commandNodeLiterals.get(root);
39+
Map<String, CommandNode<Source>> arguments = (Map<String, CommandNode<Source>>) commandNodeArguments.get(root);
40+
41+
removeCommandFromMapIfCheckPasses(children, commandName, extraCheck);
42+
removeCommandFromMapIfCheckPasses(literals, commandName, extraCheck);
43+
// Commands should really only be represented as literals, but it is technically possible
44+
// to put an ArgumentCommandNode in the root, so we'll check
45+
removeCommandFromMapIfCheckPasses(arguments, commandName, extraCheck);
46+
47+
if (unregisterNamespaces) {
48+
removeCommandNamespace(children, commandName, extraCheck);
49+
removeCommandNamespace(literals, commandName, extraCheck);
50+
removeCommandNamespace(arguments, commandName, extraCheck);
51+
}
52+
}
53+
54+
protected static <T> void removeCommandNamespace(Map<String, T> map, String commandName, Predicate<T> extraCheck) {
55+
for (String key : new HashSet<>(map.keySet())) {
56+
if (!isThisTheCommandButNamespaced(commandName, key)) continue;
57+
58+
removeCommandFromMapIfCheckPasses(map, key, extraCheck);
59+
}
60+
}
61+
62+
protected static <T> void removeCommandFromMapIfCheckPasses(Map<String, T> map, String key, Predicate<T> extraCheck) {
63+
T element = map.get(key);
64+
if (element == null) return;
65+
if (extraCheck.test(element)) map.remove(key);
66+
}
67+
68+
protected static boolean isThisTheCommandButNamespaced(String commandName, String key) {
69+
if(!key.contains(":")) return false;
70+
String[] split = key.split(":");
71+
if(split.length < 2) return false;
72+
return split[1].equalsIgnoreCase(commandName);
73+
}
74+
75+
// Behavior methods
76+
public abstract CommandDispatcher<Source> getBrigadierDispatcher();
77+
78+
public abstract void runTasksAfterServerStart();
79+
80+
public abstract void postCommandRegistration(RegisteredCommand registeredCommand, LiteralCommandNode<Source> resultantNode, List<LiteralCommandNode<Source>> aliasNodes);
81+
82+
public abstract LiteralCommandNode<Source> registerCommandNode(LiteralArgumentBuilder<Source> node, String namespace);
83+
84+
public abstract void unregister(String commandName, boolean unregisterNamespaces, boolean unregisterBukkit);
85+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package dev.jorel.commandapi;
2+
3+
import com.mojang.brigadier.CommandDispatcher;
4+
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
5+
import com.mojang.brigadier.tree.LiteralCommandNode;
6+
7+
import java.util.List;
8+
9+
/**
10+
* Handles logic for registering commands after Paper build 65, where https://github.com/PaperMC/Paper/pull/8235
11+
* changed a bunch of the behind-the-scenes logic.
12+
*/
13+
public class PaperCommandRegistration<Source> extends CommandRegistrationStrategy<Source> {
14+
// References to necessary objects
15+
private final CommandDispatcher<Source> brigadierDispatcher;
16+
17+
public PaperCommandRegistration(CommandDispatcher<Source> brigadierDispatcher) {
18+
this.brigadierDispatcher = brigadierDispatcher;
19+
}
20+
21+
@Override
22+
public CommandDispatcher<Source> getBrigadierDispatcher() {
23+
return brigadierDispatcher;
24+
}
25+
26+
@Override
27+
public void runTasksAfterServerStart() {
28+
// Nothing to do
29+
}
30+
31+
@Override
32+
public void postCommandRegistration(RegisteredCommand registeredCommand, LiteralCommandNode<Source> resultantNode, List<LiteralCommandNode<Source>> aliasNodes) {
33+
// Nothing to do
34+
}
35+
36+
@Override
37+
public LiteralCommandNode<Source> registerCommandNode(LiteralArgumentBuilder<Source> node, String namespace) {
38+
LiteralCommandNode<Source> builtNode = brigadierDispatcher.register(node);
39+
40+
// Namespace is not empty on Bukkit forks
41+
brigadierDispatcher.getRoot().addChild(CommandAPIHandler.getInstance().namespaceNode(builtNode, namespace));
42+
43+
return builtNode;
44+
}
45+
46+
@Override
47+
public void unregister(String commandName, boolean unregisterNamespaces, boolean unregisterBukkit) {
48+
// Remove nodes from the dispatcher
49+
removeBrigadierCommands(brigadierDispatcher, commandName, unregisterNamespaces, c -> true);
50+
51+
// Update the dispatcher file
52+
CommandAPIHandler.getInstance().writeDispatcherToFile();
53+
}
54+
}

0 commit comments

Comments
 (0)