-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This commit adds proper brigadier support to Blade. In previous versions commands registered via Blade would sometimes not show up in-game, and some commands would not execute at all. This is a non-breaking change, and it should automatically start working once the dependency is updated.
- Loading branch information
Showing
13 changed files
with
347 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
plugins { | ||
id 'java-library' | ||
} | ||
|
||
repositories { | ||
maven { url 'https://repo.papermc.io/repository/maven-public/' } | ||
} | ||
|
||
dependencies { | ||
implementation project(":core") | ||
|
||
compileOnly 'io.papermc.paper:paper-api:1.20.4-R0.1-SNAPSHOT' | ||
compileOnly 'io.papermc.paper:paper-mojangapi:1.20.4-R0.1-SNAPSHOT' | ||
} |
183 changes: 183 additions & 0 deletions
183
bukkit-brigadier/src/main/java/me/vaperion/blade/bukkit/brigadier/BladeBrigadierSupport.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,183 @@ | ||
package me.vaperion.blade.bukkit.brigadier; | ||
|
||
import com.destroystokyo.paper.brigadier.BukkitBrigadierCommandSource; | ||
import com.destroystokyo.paper.event.brigadier.CommandRegisteredEvent; | ||
import com.mojang.brigadier.Command; | ||
import com.mojang.brigadier.arguments.*; | ||
import com.mojang.brigadier.builder.LiteralArgumentBuilder; | ||
import com.mojang.brigadier.builder.RequiredArgumentBuilder; | ||
import com.mojang.brigadier.suggestion.SuggestionProvider; | ||
import com.mojang.brigadier.tree.CommandNode; | ||
import com.mojang.brigadier.tree.LiteralCommandNode; | ||
import me.vaperion.blade.Blade; | ||
import me.vaperion.blade.command.Parameter; | ||
import me.vaperion.blade.context.Context; | ||
import me.vaperion.blade.context.WrappedSender; | ||
import org.bukkit.Bukkit; | ||
import org.bukkit.command.CommandSender; | ||
import org.bukkit.event.EventHandler; | ||
import org.bukkit.event.Listener; | ||
import org.bukkit.plugin.Plugin; | ||
import org.jetbrains.annotations.NotNull; | ||
|
||
import java.util.function.Function; | ||
import java.util.function.Predicate; | ||
|
||
@SuppressWarnings("UnstableApiUsage") | ||
public final class BladeBrigadierSupport implements Listener { | ||
|
||
private final Blade blade; | ||
private final BladeNodeDiscovery nodeDiscovery; | ||
private final Function<CommandSender, WrappedSender<?>> wrappedSenderFunction; | ||
|
||
public BladeBrigadierSupport(@NotNull Blade blade, | ||
@NotNull Function<CommandSender, WrappedSender<?>> wrappedSenderFunction) throws ClassNotFoundException { | ||
Class.forName("com.destroystokyo.paper.event.brigadier.CommandRegisteredEvent"); | ||
|
||
this.blade = blade; | ||
this.nodeDiscovery = new BladeNodeDiscovery(blade); | ||
this.wrappedSenderFunction = wrappedSenderFunction; | ||
|
||
Bukkit.getPluginManager().registerEvents(this, (Plugin) blade.getPlatform().getPluginInstance()); | ||
} | ||
|
||
@EventHandler | ||
public void onCommandRegistered(CommandRegisteredEvent<BukkitBrigadierCommandSource> event) { | ||
SimpleBladeNode node = nodeDiscovery.discoverCommand(event.getCommandLabel()); | ||
if (node == null) return; | ||
|
||
event.setLiteral(buildLiteral( | ||
node, | ||
event.getCommandLabel(), | ||
event.getBrigadierCommand(), | ||
event.getBrigadierCommand() | ||
)); | ||
} | ||
|
||
@NotNull | ||
private LiteralCommandNode<BukkitBrigadierCommandSource> buildLiteral( | ||
@NotNull SimpleBladeNode node, | ||
@NotNull String label, | ||
@NotNull SuggestionProvider<BukkitBrigadierCommandSource> suggestionProvider, | ||
@NotNull Command<BukkitBrigadierCommandSource> brigadierCommand) { | ||
LiteralArgumentBuilder<BukkitBrigadierCommandSource> builder = LiteralArgumentBuilder.<BukkitBrigadierCommandSource>literal(label) | ||
.requires(createPermissionPredicate(node)) | ||
.executes(brigadierCommand); | ||
|
||
LiteralCommandNode<BukkitBrigadierCommandSource> root = builder.build(); | ||
|
||
registerParams(node, root, suggestionProvider, brigadierCommand); | ||
|
||
for (SimpleBladeNode subCommand : node.getSubCommands()) { | ||
if (subCommand.isStub()) continue; | ||
|
||
String subLabel = subCommand.getCommand().getUsageAlias(); | ||
String[] parts = subLabel.split(" "); | ||
String rest = subLabel.substring(parts[0].length() + 1); | ||
|
||
registerSubCommand(subCommand, rest, suggestionProvider, brigadierCommand, root); | ||
} | ||
|
||
return root; | ||
} | ||
|
||
private void registerSubCommand(@NotNull SimpleBladeNode subCommand, | ||
@NotNull String label, | ||
@NotNull SuggestionProvider<BukkitBrigadierCommandSource> suggestionProvider, | ||
@NotNull Command<BukkitBrigadierCommandSource> brigadierCommand, | ||
@NotNull LiteralCommandNode<BukkitBrigadierCommandSource> root) { | ||
if (subCommand.isStub()) return; | ||
|
||
if (label.contains(" ")) { | ||
String[] parts = label.split(" "); | ||
|
||
String stubName = parts[0]; | ||
String rest = label.substring(stubName.length() + 1); | ||
|
||
CommandNode<BukkitBrigadierCommandSource> subCommandNode = root.getChild(stubName); | ||
|
||
if (subCommandNode == null) { | ||
subCommandNode = LiteralArgumentBuilder.<BukkitBrigadierCommandSource>literal(stubName) | ||
.requires(createPermissionPredicate(subCommand)) | ||
.executes(brigadierCommand) | ||
.build(); | ||
} | ||
|
||
root.addChild(subCommandNode); | ||
registerSubCommand(subCommand, rest, suggestionProvider, brigadierCommand, (LiteralCommandNode<BukkitBrigadierCommandSource>) subCommandNode); | ||
} else { | ||
CommandNode<BukkitBrigadierCommandSource> subCommandNode = root.getChild(label); | ||
|
||
if (subCommandNode == null) { | ||
subCommandNode = LiteralArgumentBuilder.<BukkitBrigadierCommandSource>literal(label) | ||
.requires(createPermissionPredicate(subCommand)) | ||
.executes(brigadierCommand) | ||
.build(); | ||
} | ||
|
||
root.addChild(subCommandNode); | ||
registerParams(subCommand, subCommandNode, suggestionProvider, brigadierCommand); | ||
} | ||
} | ||
|
||
private void registerParams(@NotNull SimpleBladeNode node, | ||
@NotNull CommandNode<BukkitBrigadierCommandSource> commandNode, | ||
@NotNull SuggestionProvider<BukkitBrigadierCommandSource> suggestionProvider, | ||
@NotNull Command<BukkitBrigadierCommandSource> brigadierCommand) { | ||
if (node.isStub()) return; | ||
|
||
for (Parameter.CommandParameter parameter : node.getCommand().getCommandParameters()) { | ||
RequiredArgumentBuilder<BukkitBrigadierCommandSource, Object> builder = RequiredArgumentBuilder | ||
.<BukkitBrigadierCommandSource, Object>argument(parameter.getName(), mapBrigadierType(parameter.getType())) | ||
.suggests(suggestionProvider) | ||
.requires(createPermissionPredicate(node)) | ||
.executes(brigadierCommand); | ||
|
||
CommandNode<BukkitBrigadierCommandSource> argument = builder.build(); | ||
commandNode.addChild(argument); | ||
commandNode = argument; | ||
} | ||
} | ||
|
||
// This is a bit weird. Brigadier on newer versions literally HIDES commands if you don't have permissions / enter invalid args | ||
// so instead of seeing the usage, you see unknown command. To fix this, we just tell brigadier that everyone has permission | ||
// to execute the command, which then properly delegates to Blade's handler. | ||
@NotNull | ||
private Predicate<BukkitBrigadierCommandSource> createPermissionPredicate(@NotNull SimpleBladeNode node) { | ||
return sender -> { | ||
WrappedSender<?> wrappedSender = wrappedSenderFunction.apply(sender.getBukkitSender()); | ||
Context context = new Context(blade, wrappedSender, "", new String[0]); | ||
|
||
if (node.getCommand() != null && node.getCommand().isHidden()) { | ||
boolean result = blade.getPermissionTester().testPermission(context, node.getCommand()); | ||
if (!result) return false; | ||
} | ||
|
||
for (SimpleBladeNode subCommand : node.getSubCommands()) { | ||
if (!subCommand.getCommand().isHidden()) continue; | ||
|
||
boolean result = blade.getPermissionTester().testPermission(context, subCommand.getCommand()); | ||
if (!result) return false; | ||
} | ||
|
||
return true; | ||
}; | ||
} | ||
|
||
@NotNull | ||
private ArgumentType<Object> mapBrigadierType(@NotNull Class<?> clazz) { | ||
if (clazz == String.class) return objectifyArgument(StringArgumentType.string()); | ||
if (clazz == int.class || clazz == Integer.class) return objectifyArgument(IntegerArgumentType.integer()); | ||
if (clazz == float.class || clazz == Float.class) return objectifyArgument(FloatArgumentType.floatArg()); | ||
if (clazz == double.class || clazz == Double.class) return objectifyArgument(DoubleArgumentType.doubleArg()); | ||
if (clazz == boolean.class || clazz == Boolean.class) return objectifyArgument(BoolArgumentType.bool()); | ||
if (clazz == long.class || clazz == Long.class) return objectifyArgument(LongArgumentType.longArg()); | ||
return objectifyArgument(StringArgumentType.string()); // Everything else becomes a string | ||
} | ||
|
||
@SuppressWarnings("unchecked") | ||
@NotNull | ||
private <T> ArgumentType<Object> objectifyArgument(@NotNull ArgumentType<T> type) { | ||
return (ArgumentType<Object>) type; | ||
} | ||
} |
56 changes: 56 additions & 0 deletions
56
bukkit-brigadier/src/main/java/me/vaperion/blade/bukkit/brigadier/BladeNodeDiscovery.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
package me.vaperion.blade.bukkit.brigadier; | ||
|
||
import me.vaperion.blade.Blade; | ||
import me.vaperion.blade.command.Command; | ||
import me.vaperion.blade.util.Tuple; | ||
import org.jetbrains.annotations.NotNull; | ||
import org.jetbrains.annotations.Nullable; | ||
|
||
import java.util.ArrayList; | ||
import java.util.List; | ||
|
||
public class BladeNodeDiscovery { | ||
|
||
private final Blade blade; | ||
|
||
public BladeNodeDiscovery(@NotNull Blade blade) { | ||
this.blade = blade; | ||
} | ||
|
||
@NotNull | ||
private String removeCommandQualifier(@NotNull String input) { | ||
String[] parts = input.split(" "); | ||
|
||
if (parts[0].contains(":")) | ||
parts[0] = parts[0].split(":")[1]; | ||
|
||
return String.join(" ", parts); | ||
} | ||
|
||
@Nullable | ||
public SimpleBladeNode discoverCommand(@NotNull String label) { | ||
label = removeCommandQualifier(label); | ||
Tuple<Command, String> bladeCommand = blade.getResolver().resolveCommand(new String[]{label}); | ||
|
||
if (bladeCommand != null) { | ||
// This is the simple case: if a command is registered with that exact label (e.g. "/hello"), we can just return it | ||
return new SimpleBladeNode(false, bladeCommand.getLeft(), List.of()); | ||
} | ||
|
||
// If no command was found, we have to search for "stub" parent commands, e.g. if you register "/hello world", "hello" would be a stub | ||
List<Command> commands = blade.getAliasToCommands().get(label); | ||
if (commands == null || commands.isEmpty()) return null; | ||
|
||
List<SimpleBladeNode> resolved = new ArrayList<>(); | ||
for (Command command : commands) { | ||
SimpleBladeNode subcommand = discoverCommand(command.getAliases()[0]); | ||
|
||
if (subcommand == null) | ||
resolved.add(new SimpleBladeNode(false, command, List.of())); | ||
else | ||
resolved.add(subcommand); | ||
} | ||
|
||
return new SimpleBladeNode(true, null, resolved); | ||
} | ||
} |
24 changes: 24 additions & 0 deletions
24
bukkit-brigadier/src/main/java/me/vaperion/blade/bukkit/brigadier/SimpleBladeNode.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package me.vaperion.blade.bukkit.brigadier; | ||
|
||
import lombok.Getter; | ||
import lombok.ToString; | ||
import me.vaperion.blade.command.Command; | ||
import org.jetbrains.annotations.NotNull; | ||
import org.jetbrains.annotations.Nullable; | ||
|
||
import java.util.List; | ||
|
||
@Getter | ||
@ToString | ||
public class SimpleBladeNode { | ||
|
||
private final boolean isStub; | ||
private final Command command; | ||
private final List<SimpleBladeNode> subCommands; | ||
|
||
public SimpleBladeNode(boolean isStub, @Nullable Command command, @NotNull List<SimpleBladeNode> subCommands) { | ||
this.isStub = isStub; | ||
this.command = command; | ||
this.subCommands = subCommands; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.