From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Martijn Muijsers Date: Sat, 26 Nov 2022 10:47:56 +0100 Subject: [PATCH] Gale commands License: GPL-3.0 (https://www.gnu.org/licenses/gpl-3.0.html) Gale - https://galemc.org This patch is based on the following patch: "Paper command" By: Zach Brown As part of: Paper (https://github.com/PaperMC/Paper) Licensed under: MIT (https://opensource.org/licenses/MIT) diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java index 661794310eb10d4b9fe29c9445abd57826214d41..d2b14c65e6a925ba4a29d48bcedc0d9504092052 100644 --- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java @@ -50,6 +50,7 @@ import net.minecraft.world.level.GameRules; import net.minecraft.world.level.GameType; import net.minecraft.world.level.block.entity.SkullBlockEntity; import net.minecraft.world.level.storage.LevelStorageSource; +import org.galemc.gale.command.GaleCommands; import org.slf4j.Logger; // CraftBukkit start @@ -221,6 +222,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface org.spigotmc.WatchdogThread.doStart(org.spigotmc.SpigotConfig.timeoutTime, org.spigotmc.SpigotConfig.restartOnCrash); thread.start(); // Paper - start console thread after MinecraftServer.console & PaperConfig are initialized io.papermc.paper.command.PaperCommands.registerCommands(this); + GaleCommands.registerCommands(this); // Gale - Gale commands - register commands com.destroystokyo.paper.Metrics.PaperMetrics.startMetrics(); com.destroystokyo.paper.VersionHistoryManager.INSTANCE.getClass(); // load version history now io.papermc.paper.brigadier.PaperBrigadierProviderImpl.INSTANCE.getClass(); // init PaperBrigadierProvider diff --git a/src/main/java/org/galemc/gale/command/GaleCommand.java b/src/main/java/org/galemc/gale/command/GaleCommand.java new file mode 100644 index 0000000000000000000000000000000000000000..87d3aed35341dfa9358af064dd54d7de95078269 --- /dev/null +++ b/src/main/java/org/galemc/gale/command/GaleCommand.java @@ -0,0 +1,179 @@ +// Gale - Gale commands - /gale command + +package org.galemc.gale.command; + +import io.papermc.paper.command.CommandUtil; +import it.unimi.dsi.fastutil.Pair; +import net.minecraft.Util; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.permissions.Permission; +import org.bukkit.permissions.PermissionDefault; +import org.bukkit.plugin.PluginManager; +import org.galemc.gale.command.subcommands.InfoCommand; +import org.galemc.gale.command.subcommands.ReloadCommand; +import org.galemc.gale.command.subcommands.VersionCommand; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +import static net.kyori.adventure.text.Component.newline; +import static net.kyori.adventure.text.Component.text; +import static net.kyori.adventure.text.format.NamedTextColor.RED; +import static net.kyori.adventure.text.format.NamedTextColor.GRAY; + +public final class GaleCommand extends Command { + public static final String COMMAND_LABEL = "gale"; + public static final String BASE_PERM = GaleCommands.COMMAND_BASE_PERM + "." + COMMAND_LABEL; + private static final Permission basePermission = new Permission(BASE_PERM, PermissionDefault.TRUE); + // subcommand label -> subcommand + private static final GaleSubcommand RELOAD_SUBCOMMAND = new ReloadCommand(); + private static final GaleSubcommand VERSION_SUBCOMMAND = new VersionCommand(); + private static final GaleSubcommand INFO_SUBCOMMAND = new InfoCommand(); + private static final Map SUBCOMMANDS = Util.make(() -> { + final Map, GaleSubcommand> commands = new HashMap<>(); + + commands.put(Set.of(ReloadCommand.LITERAL_ARGUMENT), RELOAD_SUBCOMMAND); + commands.put(Set.of(VersionCommand.LITERAL_ARGUMENT), VERSION_SUBCOMMAND); + commands.put(Set.of(InfoCommand.LITERAL_ARGUMENT), INFO_SUBCOMMAND); + + return commands.entrySet().stream() + .flatMap(entry -> entry.getKey().stream().map(s -> Map.entry(s, entry.getValue()))) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + }); + // alias -> subcommand label + private static final Map ALIASES = Util.make(() -> { + final Map> aliases = new HashMap<>(); + + aliases.put(VersionCommand.LITERAL_ARGUMENT, Set.of("ver")); + aliases.put(InfoCommand.LITERAL_ARGUMENT, Set.of("about")); + + return aliases.entrySet().stream() + .flatMap(entry -> entry.getValue().stream().map(s -> Map.entry(s, entry.getKey()))) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + }); + + private String createUsageMessage(Collection arguments) { + return "/" + COMMAND_LABEL + " [" + String.join(" | ", arguments) + "]"; + } + + public GaleCommand() { + super(COMMAND_LABEL); + this.description = "Gale related commands"; + this.usageMessage = this.createUsageMessage(SUBCOMMANDS.keySet()); + final List permissions = SUBCOMMANDS.values().stream().map(GaleSubcommand::getPermission).filter(Objects::nonNull).toList(); + this.setPermission(BASE_PERM); + final PluginManager pluginManager = Bukkit.getServer().getPluginManager(); + pluginManager.addPermission(basePermission); + for (final Permission permission : permissions) { + pluginManager.addPermission(permission); + } + } + + @Override + public List tabComplete( + final CommandSender sender, + final String alias, + final String[] args, + final @Nullable Location location + ) throws IllegalArgumentException { + if (args.length <= 1) { + List subCommandArguments = new ArrayList<>(SUBCOMMANDS.size()); + for (Map.Entry subCommandEntry : SUBCOMMANDS.entrySet()) { + if (subCommandEntry.getValue().testPermission(sender)) { + subCommandArguments.add(subCommandEntry.getKey()); + } + } + return CommandUtil.getListMatchingLast(sender, args, subCommandArguments); + } + + final @Nullable Pair subCommand = resolveCommand(args[0]); + if (subCommand != null && subCommand.second().testPermission(sender)) { + return subCommand.second().tabComplete(sender, subCommand.first(), Arrays.copyOfRange(args, 1, args.length)); + } + + return Collections.emptyList(); + } + + private boolean testHasOnePermission(CommandSender sender) { + for (Map.Entry subCommandEntry : SUBCOMMANDS.entrySet()) { + if (subCommandEntry.getValue().testPermission(sender)) { + return true; + } + } + return false; + } + + @Override + public boolean execute( + final CommandSender sender, + final String commandLabel, + final String[] args + ) { + + // Check if the sender has the base permission and at least one specific permission + if (!sender.hasPermission(basePermission) || !this.testHasOnePermission(sender)) { + sender.sendMessage(Bukkit.permissionMessage()); + return true; + } + + // Determine the usage message with the subcommands they can perform + List subCommandArguments = new ArrayList<>(SUBCOMMANDS.size()); + for (Map.Entry subCommandEntry : SUBCOMMANDS.entrySet()) { + if (subCommandEntry.getValue().testPermission(sender)) { + subCommandArguments.add(subCommandEntry.getKey()); + } + } + String specificUsageMessage = this.createUsageMessage(subCommandArguments); + + // If they did not give a subcommand + if (args.length == 0) { + INFO_SUBCOMMAND.execute(sender, InfoCommand.LITERAL_ARGUMENT, new String[0]); + sender.sendMessage(newline().append(text("Command usage: " + specificUsageMessage, GRAY))); + return false; + } + + // If they do not have permission for the subcommand they gave, or the argument is not a valid subcommand + final @Nullable Pair subCommand = resolveCommand(args[0]); + if (subCommand == null || !subCommand.second().testPermission(sender)) { + sender.sendMessage(text("Usage: " + specificUsageMessage, RED)); + return false; + } + + // Execute the subcommand + final String[] choppedArgs = Arrays.copyOfRange(args, 1, args.length); + return subCommand.second().execute(sender, subCommand.first(), choppedArgs); + + } + + private static @Nullable Pair resolveCommand(String label) { + label = label.toLowerCase(Locale.ENGLISH); + @Nullable GaleSubcommand subCommand = SUBCOMMANDS.get(label); + if (subCommand == null) { + final @Nullable String command = ALIASES.get(label); + if (command != null) { + label = command; + subCommand = SUBCOMMANDS.get(command); + } + } + + if (subCommand != null) { + return Pair.of(label, subCommand); + } + + return null; + } + +} diff --git a/src/main/java/org/galemc/gale/command/GaleCommands.java b/src/main/java/org/galemc/gale/command/GaleCommands.java new file mode 100644 index 0000000000000000000000000000000000000000..b729f2c778e6158f1cb3aecc7f0ed0a746ff6339 --- /dev/null +++ b/src/main/java/org/galemc/gale/command/GaleCommands.java @@ -0,0 +1,31 @@ +// Gale - Gale commands + +package org.galemc.gale.command; + +import net.minecraft.server.MinecraftServer; +import org.bukkit.command.Command; +import org.bukkit.craftbukkit.util.permissions.CraftDefaultPermissions; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.framework.qual.DefaultQualifier; + +import java.util.HashMap; +import java.util.Map; + +@DefaultQualifier(NonNull.class) +public final class GaleCommands { + + public static final String COMMAND_BASE_PERM = CraftDefaultPermissions.GALE_ROOT + ".command"; + + private GaleCommands() {} + + private static final Map COMMANDS = new HashMap<>(); + static { + COMMANDS.put(GaleCommand.COMMAND_LABEL, new GaleCommand()); + } + + public static void registerCommands(final MinecraftServer server) { + COMMANDS.forEach((s, command) -> { + server.server.getCommandMap().register(s, "Gale", command); + }); + } +} diff --git a/src/main/java/org/galemc/gale/command/GaleSubcommand.java b/src/main/java/org/galemc/gale/command/GaleSubcommand.java new file mode 100644 index 0000000000000000000000000000000000000000..7bcf875f91f5018daa06ef3280c4cdd7e8df4fd5 --- /dev/null +++ b/src/main/java/org/galemc/gale/command/GaleSubcommand.java @@ -0,0 +1,27 @@ +// Gale - Gale commands + +package org.galemc.gale.command; + +import org.bukkit.command.CommandSender; +import org.bukkit.permissions.Permission; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.framework.qual.DefaultQualifier; +import org.jetbrains.annotations.Nullable; + +import java.util.Collections; +import java.util.List; + +@DefaultQualifier(NonNull.class) +public interface GaleSubcommand { + + boolean execute(CommandSender sender, String subCommand, String[] args); + + default List tabComplete(final CommandSender sender, final String subCommand, final String[] args) { + return Collections.emptyList(); + } + + boolean testPermission(CommandSender sender); + + @Nullable Permission getPermission(); + +} diff --git a/src/main/java/org/galemc/gale/command/PermissionedGaleSubcommand.java b/src/main/java/org/galemc/gale/command/PermissionedGaleSubcommand.java new file mode 100644 index 0000000000000000000000000000000000000000..35ffa5f1bb682ac962ab9466e682a461efa65e7b --- /dev/null +++ b/src/main/java/org/galemc/gale/command/PermissionedGaleSubcommand.java @@ -0,0 +1,32 @@ +// Gale - Gale commands + +package org.galemc.gale.command; + +import org.bukkit.command.CommandSender; +import org.bukkit.permissions.Permission; +import org.bukkit.permissions.PermissionDefault; +import org.jetbrains.annotations.Nullable; + +public abstract class PermissionedGaleSubcommand implements GaleSubcommand { + + public final Permission permission; + + protected PermissionedGaleSubcommand(Permission permission) { + this.permission = permission; + } + + protected PermissionedGaleSubcommand(String permission, PermissionDefault permissionDefault) { + this(new Permission(permission, permissionDefault)); + } + + @Override + public boolean testPermission(CommandSender sender) { + return sender.hasPermission(this.permission); + } + + @Override + public @Nullable Permission getPermission() { + return this.permission; + } + +} diff --git a/src/main/java/org/galemc/gale/command/subcommands/InfoCommand.java b/src/main/java/org/galemc/gale/command/subcommands/InfoCommand.java new file mode 100644 index 0000000000000000000000000000000000000000..a781afaf6821a4b7bd770b33eaf65527bfbed04e --- /dev/null +++ b/src/main/java/org/galemc/gale/command/subcommands/InfoCommand.java @@ -0,0 +1,42 @@ +// Gale - Gale commands - /gale info command + +package org.galemc.gale.command.subcommands; + +import net.kyori.adventure.text.event.ClickEvent; +import net.kyori.adventure.text.format.TextDecoration; +import org.bukkit.command.CommandSender; +import org.bukkit.permissions.Permission; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.framework.qual.DefaultQualifier; +import org.galemc.gale.command.GaleSubcommand; +import org.jetbrains.annotations.Nullable; + +import static net.kyori.adventure.text.Component.text; + +@DefaultQualifier(NonNull.class) +public final class InfoCommand implements GaleSubcommand { + + public final static String LITERAL_ARGUMENT = "info"; + + @Override + public boolean execute(final CommandSender sender, final String subCommand, final String[] args) { + sender.sendMessage( + text("Gale is a performant Minecraft server system. Find us on: ") + .append(text("https://github.com/GaleMC/Gale") + .decorate(TextDecoration.UNDERLINED) + .clickEvent(ClickEvent.openUrl("https://github.com/GaleMC/Gale"))) + ); + return true; + } + + @Override + public boolean testPermission(CommandSender sender) { + return true; + } + + @Override + public @Nullable Permission getPermission() { + return null; + } + +} diff --git a/src/main/java/org/galemc/gale/command/subcommands/ReloadCommand.java b/src/main/java/org/galemc/gale/command/subcommands/ReloadCommand.java new file mode 100644 index 0000000000000000000000000000000000000000..c6ca5d3cf7e709f59f68ccd28db378e9e6303fdd --- /dev/null +++ b/src/main/java/org/galemc/gale/command/subcommands/ReloadCommand.java @@ -0,0 +1,46 @@ +// Gale - Gale commands - /gale reload command + +package org.galemc.gale.command.subcommands; + +import net.minecraft.server.MinecraftServer; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.permissions.PermissionDefault; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.framework.qual.DefaultQualifier; +import org.galemc.gale.command.GaleCommand; +import org.galemc.gale.command.PermissionedGaleSubcommand; + +import static net.kyori.adventure.text.Component.text; +import static net.kyori.adventure.text.format.NamedTextColor.GREEN; +import static net.kyori.adventure.text.format.NamedTextColor.RED; + +@DefaultQualifier(NonNull.class) +public final class ReloadCommand extends PermissionedGaleSubcommand { + + public final static String LITERAL_ARGUMENT = "reload"; + public static final String PERM = GaleCommand.BASE_PERM + "." + LITERAL_ARGUMENT; + + public ReloadCommand() { + super(PERM, PermissionDefault.OP); + } + + @Override + public boolean execute(final CommandSender sender, final String subCommand, final String[] args) { + this.doReload(sender); + return true; + } + + private void doReload(final CommandSender sender) { + Command.broadcastCommandMessage(sender, text("Please note that this command is not supported and may cause issues.", RED)); + Command.broadcastCommandMessage(sender, text("If you encounter any issues please use the /stop command to restart your server.", RED)); + + MinecraftServer server = ((CraftServer) sender.getServer()).getServer(); + server.galeConfigurations.reloadConfigs(server); + server.server.reloadCount++; + + Command.broadcastCommandMessage(sender, text("Gale config reload complete.", GREEN)); + } + +} diff --git a/src/main/java/org/galemc/gale/command/subcommands/VersionCommand.java b/src/main/java/org/galemc/gale/command/subcommands/VersionCommand.java new file mode 100644 index 0000000000000000000000000000000000000000..675cd4295dabfade1b9cc5473010b5b20dc32039 --- /dev/null +++ b/src/main/java/org/galemc/gale/command/subcommands/VersionCommand.java @@ -0,0 +1,39 @@ +// Gale - Gale commands - /gale version command + +package org.galemc.gale.command.subcommands; + +import net.minecraft.server.MinecraftServer; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.permissions.PermissionDefault; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.framework.qual.DefaultQualifier; +import org.galemc.gale.command.GaleCommand; +import org.galemc.gale.command.PermissionedGaleSubcommand; + +@DefaultQualifier(NonNull.class) +public final class VersionCommand extends PermissionedGaleSubcommand { + + public final static String LITERAL_ARGUMENT = "version"; + public static final String PERM = GaleCommand.BASE_PERM + "." + LITERAL_ARGUMENT; + + public VersionCommand() { + super(PERM, PermissionDefault.TRUE); + } + + @Override + public boolean execute(final CommandSender sender, final String subCommand, final String[] args) { + final @Nullable Command ver = MinecraftServer.getServer().server.getCommandMap().getCommand("version"); + if (ver != null) { + ver.execute(sender, GaleCommand.COMMAND_LABEL, new String[0]); + } + return true; + } + + @Override + public boolean testPermission(CommandSender sender) { + return super.testPermission(sender) && sender.hasPermission("bukkit.command.version"); + } + +}