diff --git a/.gitignore b/.gitignore index c04d0e49..5ecdbbc4 100644 --- a/.gitignore +++ b/.gitignore @@ -36,7 +36,9 @@ out/ # Vanilla-like server server.properties -world/ +survival/world/ /datapack-tests/mojang-data /mojang-data/1.20.4 /mojang-data/1.21.1 +/survival/world/ +/world/ \ No newline at end of file diff --git a/commands/src/main/java/net/minestom/vanilla/commands/DifficultyCommand.java b/commands/src/main/java/net/minestom/vanilla/commands/DifficultyCommand.java deleted file mode 100644 index 10f0d06a..00000000 --- a/commands/src/main/java/net/minestom/vanilla/commands/DifficultyCommand.java +++ /dev/null @@ -1,51 +0,0 @@ -package net.minestom.vanilla.commands; - -import net.minestom.server.MinecraftServer; -import net.minestom.server.command.CommandSender; -import net.minestom.server.command.builder.Command; -import net.minestom.server.command.builder.CommandContext; -import net.minestom.server.command.builder.arguments.Argument; -import net.minestom.server.command.builder.arguments.ArgumentType; -import net.minestom.server.command.builder.exception.ArgumentSyntaxException; -import net.minestom.server.world.Difficulty; -import org.jetbrains.annotations.NotNull; - -/** - * Command that make an instance change difficulty - */ -public class DifficultyCommand extends Command { - public DifficultyCommand() { - super("difficulty"); - - setCondition(this::isAllowed); - - setDefaultExecutor(this::usage); - - Argument difficulty = ArgumentType.Word("difficulty").from("peaceful", "easy", "normal", "hard"); - - - difficulty.setCallback(this::difficultyCallback); - - addSyntax(this::execute, difficulty); - } - - private void usage(CommandSender player, CommandContext arguments) { - player.sendMessage("Usage: /difficulty (peaceful|easy|normal|hard)"); - } - - private void execute(CommandSender player, CommandContext arguments) { - String difficultyName = arguments.get("difficulty"); - Difficulty difficulty = Difficulty.valueOf(difficultyName.toUpperCase()); - MinecraftServer.setDifficulty(difficulty); - player.sendMessage("You are now playing in " + difficultyName); - } - - private void difficultyCallback(@NotNull CommandSender sender, @NotNull ArgumentSyntaxException exception) { - sender.sendMessage("'" + exception.getInput() + "' is not a valid difficulty!"); - } - - private boolean isAllowed(CommandSender player, String commandName) { - return true; // TODO: permissions - } -} - diff --git a/commands/src/main/java/net/minestom/vanilla/commands/GamemodeCommand.java b/commands/src/main/java/net/minestom/vanilla/commands/GamemodeCommand.java deleted file mode 100644 index 38446d9a..00000000 --- a/commands/src/main/java/net/minestom/vanilla/commands/GamemodeCommand.java +++ /dev/null @@ -1,128 +0,0 @@ -package net.minestom.vanilla.commands; - -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.format.NamedTextColor; -import net.minestom.server.command.CommandSender; -import net.minestom.server.command.builder.Command; -import net.minestom.server.command.builder.arguments.ArgumentEnum; -import net.minestom.server.command.builder.arguments.ArgumentType; -import net.minestom.server.command.builder.arguments.minecraft.ArgumentEntity; -import net.minestom.server.entity.Entity; -import net.minestom.server.entity.GameMode; -import net.minestom.server.entity.Player; -import net.minestom.server.utils.entity.EntityFinder; - -import java.util.List; -import java.util.Locale; - -/** - * Command that make a player change gamemode, made in - * the style of the vanilla /gamemode command. - * - * @see ... - */ -public class GamemodeCommand extends Command { - - public GamemodeCommand() { - super("gamemode", "gm"); - - //GameMode parameter - ArgumentEnum gamemode = ArgumentType.Enum("gamemode", GameMode.class).setFormat(ArgumentEnum.Format.LOWER_CASED); - gamemode.setCallback((sender, exception) -> sender.sendMessage( - Component.text("Invalid gamemode ", NamedTextColor.RED) - .append(Component.text(exception.getInput(), NamedTextColor.WHITE)) - .append(Component.text("!")))); - - ArgumentEntity player = ArgumentType.Entity("targets").onlyPlayers(true); - - //Upon invalid usage, print the correct usage of the command to the sender - setDefaultExecutor((sender, context) -> { - String commandName = context.getCommandName(); - - sender.sendMessage(Component.text("Usage: /" + commandName + " [targets]", NamedTextColor.RED)); - }); - - //Command Syntax for /gamemode - addSyntax((sender, context) -> { - //Limit execution to players only - if (!(sender instanceof Player playerSender)) { - sender.sendMessage(Component.text("Please run this command in-game.", NamedTextColor.RED)); - return; - } - - //Check permission, this could be replaced with hasPermission - if (playerSender.getPermissionLevel() < 2) { - sender.sendMessage(Component.text("You don't have permission to use this command.", NamedTextColor.RED)); - return; - } - - GameMode mode = context.get(gamemode); - - //Set the gamemode for the sender - executeSelf(playerSender, mode); - }, gamemode); - - //Command Syntax for /gamemode [targets] - addSyntax((sender, context) -> { - //Check permission for players only - //This allows the console to use this syntax too - if ((sender instanceof Player playerSender) && playerSender.getPermissionLevel() < 2) { - sender.sendMessage(Component.text("You don't have permission to use this command.", NamedTextColor.RED)); - return; - } - - EntityFinder finder = context.get(player); - GameMode mode = context.get(gamemode); - - //Set the gamemode for the targets - executeOthers(sender, mode, finder.find(sender)); - }, gamemode, player); - } - - /** - * Sets the gamemode for the specified entities, and - * notifies them (and the sender) in the chat. - */ - private void executeOthers(CommandSender sender, GameMode mode, List entities) { - if (entities.isEmpty()) { - //If there are no players that could be modified, display an error message - if (sender instanceof Player playerSender) - sender.sendMessage(Component.translatable("argument.entity.notfound.player", NamedTextColor.RED)); - else sender.sendMessage(Component.text("No player was found", NamedTextColor.RED)); - } else for (Entity entity : entities) { - if (entity instanceof Player p) { - if (p == sender) { - //If the player is the same as the sender, call - //executeSelf to display one message instead of two - executeSelf(p, mode); - } else { - p.setGameMode(mode); - - String gamemodeString = "gameMode." + mode.name().toLowerCase(Locale.ROOT); - Component gamemodeComponent = Component.translatable(gamemodeString); - Component playerName = p.getDisplayName() == null ? p.getName() : p.getDisplayName(); - - //Send a message to the changed player and the sender - p.sendMessage(Component.translatable("gameMode.changed", gamemodeComponent)); - sender.sendMessage(Component.translatable("commands.gamemode.success.other", playerName, gamemodeComponent)); - } - } - } - } - - /** - * Sets the gamemode for the executing Player, and - * notifies them in the chat. - */ - private void executeSelf(Player sender, GameMode mode) { - sender.setGameMode(mode); - - //The translation keys 'gameMode.survival', 'gameMode.creative', etc. - //correspond to the translated game mode names. - String gamemodeString = "gameMode." + mode.name().toLowerCase(Locale.ROOT); - Component gamemodeComponent = Component.translatable(gamemodeString); - - //Send the translated message to the player. - sender.sendMessage(Component.translatable("commands.gamemode.success.self", gamemodeComponent)); - } -} diff --git a/commands/src/main/java/net/minestom/vanilla/commands/HelpCommand.java b/commands/src/main/java/net/minestom/vanilla/commands/HelpCommand.java deleted file mode 100644 index 8006356f..00000000 --- a/commands/src/main/java/net/minestom/vanilla/commands/HelpCommand.java +++ /dev/null @@ -1,38 +0,0 @@ -package net.minestom.vanilla.commands; - -import net.minestom.server.command.CommandSender; -import net.minestom.server.command.builder.Command; -import net.minestom.server.command.builder.CommandContext; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -/** - * Returns the list of all available commands - */ -public class HelpCommand extends Command { - public HelpCommand() { - super("help"); - - setDefaultExecutor(this::execute); - } - - private void execute(CommandSender sender, CommandContext context) { - sender.sendMessage("=== Help ==="); - - List commands = new ArrayList<>(); - - Collections.addAll(commands, VanillaCommands.values()); - - commands.sort(this::compareCommands); - - commands.forEach(command -> sender.sendMessage("/" + command.name().toLowerCase())); - - sender.sendMessage("============"); - } - - private int compareCommands(VanillaCommands a, VanillaCommands b) { - return a.name().compareTo(b.name()); - } -} diff --git a/commands/src/main/java/net/minestom/vanilla/commands/SaveAllCommand.java b/commands/src/main/java/net/minestom/vanilla/commands/SaveAllCommand.java deleted file mode 100644 index e49151cc..00000000 --- a/commands/src/main/java/net/minestom/vanilla/commands/SaveAllCommand.java +++ /dev/null @@ -1,29 +0,0 @@ -package net.minestom.vanilla.commands; - -import net.minestom.server.MinecraftServer; -import net.minestom.server.command.CommandSender; -import net.minestom.server.command.builder.Command; -import net.minestom.server.command.builder.CommandContext; -import net.minestom.vanilla.logging.Logger; - -/** - * Save the server - */ -public class SaveAllCommand extends Command { - public SaveAllCommand() { - super("save-all"); - setCondition(this::condition); - setDefaultExecutor(this::execute); - } - - private boolean condition(CommandSender player, String commandName) { - return true; // TODO: permissions - } - - private void execute(CommandSender player, CommandContext arguments) { - MinecraftServer.getInstanceManager().getInstances().forEach(i -> { - i.saveChunksToStorage(); - Logger.info("Saved dimension " + i.getDimensionType().name()); - }); - } -} diff --git a/commands/src/main/java/net/minestom/vanilla/commands/StopCommand.java b/commands/src/main/java/net/minestom/vanilla/commands/StopCommand.java deleted file mode 100644 index da66512c..00000000 --- a/commands/src/main/java/net/minestom/vanilla/commands/StopCommand.java +++ /dev/null @@ -1,25 +0,0 @@ -package net.minestom.vanilla.commands; - -import net.minestom.server.MinecraftServer; -import net.minestom.server.command.CommandSender; -import net.minestom.server.command.builder.Command; -import net.minestom.server.command.builder.CommandContext; - -/** - * Stops the server - */ -public class StopCommand extends Command { - public StopCommand() { - super("stop"); - setCondition(this::condition); - setDefaultExecutor(this::execute); - } - - private boolean condition(CommandSender player, String commandName) { - return true; // TODO: permissions - } - - private void execute(CommandSender player, CommandContext arguments) { - MinecraftServer.stopCleanly(); - } -} diff --git a/commands/src/main/java/net/minestom/vanilla/commands/VanillaCommand.java b/commands/src/main/java/net/minestom/vanilla/commands/VanillaCommand.java new file mode 100644 index 00000000..c087fb76 --- /dev/null +++ b/commands/src/main/java/net/minestom/vanilla/commands/VanillaCommand.java @@ -0,0 +1,68 @@ +package net.minestom.vanilla.commands; + +import net.kyori.adventure.text.Component; +import net.minestom.server.command.CommandSender; +import net.minestom.server.command.ConsoleSender; +import net.minestom.server.command.builder.Command; +import net.minestom.server.command.builder.CommandContext; +import net.minestom.server.command.builder.condition.CommandCondition; +import net.minestom.server.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Abstract base class for Vanilla commands. + * Includes permission level handling and shared utility methods. + */ +public abstract class VanillaCommand extends Command { + + public final int LEVEL_ALL = 0; + public final int LEVEL_MODERATOR = 1; + public final int LEVEL_GAMEMASTER = 2; + public final int LEVEL_ADMIN = 3; + public final int LEVEL_OWNER = 4; + + public VanillaCommand(@NotNull String name, @Nullable String... aliases) { + super(name, aliases); + } + + public VanillaCommand(@NotNull String name) { + super(name); + } + + /** + * Returns the usage message for this command, required and used for /help command. + * + * @param sender the command sender + * @param context the command context + * @return the usage message as a Component + */ + public abstract Component usage(CommandSender sender, CommandContext context); + + protected abstract void defaultor(CommandSender sender, CommandContext context); + + /** + * Creates a command condition that checks if the sender has the required permission level. + * Console senders are always allowed. + * + * @param level the required permission level + * @return the command condition + */ + public CommandCondition permission(int level) { + return (sender, commandName) -> { + if (sender instanceof ConsoleSender) return true; + if (sender instanceof Player player) return player.getPermissionLevel() >= level; + return false; + }; + } + + /** + * Checks whether the command was invoked without any arguments. + * + * @param context the command context + * @return true if no arguments were given, false otherwise + */ + public boolean hasNoArguments(CommandContext context) { + return context.getCommandName().equals(context.getInput()); + } +} diff --git a/commands/src/main/java/net/minestom/vanilla/commands/VanillaCommands.java b/commands/src/main/java/net/minestom/vanilla/commands/VanillaCommands.java deleted file mode 100644 index efee3a6a..00000000 --- a/commands/src/main/java/net/minestom/vanilla/commands/VanillaCommands.java +++ /dev/null @@ -1,40 +0,0 @@ -package net.minestom.vanilla.commands; - -import net.minestom.server.command.CommandManager; -import net.minestom.server.command.builder.Command; -import org.jetbrains.annotations.NotNull; - -import java.util.function.Supplier; - -/** - * All commands available in the vanilla reimplementation - */ -public enum VanillaCommands { - - FORCELOAD(ForceloadCommand::new), - GAMEMODE(GamemodeCommand::new), - DIFFICULTY(DifficultyCommand::new), - ME(MeCommand::new), - STOP(StopCommand::new), - HELP(HelpCommand::new), - SAVE_ALL(SaveAllCommand::new), - ; - - private final Supplier commandCreator; - - VanillaCommands(Supplier commandCreator) { - this.commandCreator = commandCreator; - } - - /** - * Register all vanilla commands into the given manager - * - * @param manager the command manager to register commands on - */ - public static void registerAll(@NotNull CommandManager manager) { - for (VanillaCommands vanillaCommand : values()) { - Command command = vanillaCommand.commandCreator.get(); - manager.register(command); - } - } -} diff --git a/commands/src/main/java/net/minestom/vanilla/commands/VanillaCommandsFeature.java b/commands/src/main/java/net/minestom/vanilla/commands/VanillaCommandsFeature.java index ddf8ed21..de536aac 100644 --- a/commands/src/main/java/net/minestom/vanilla/commands/VanillaCommandsFeature.java +++ b/commands/src/main/java/net/minestom/vanilla/commands/VanillaCommandsFeature.java @@ -1,36 +1,92 @@ package net.minestom.vanilla.commands; +import net.minestom.server.command.CommandManager; +import net.minestom.vanilla.commands.admin.OpCommand; +import net.minestom.vanilla.commands.all.HelpCommand; +import net.minestom.vanilla.commands.all.ListCommand; +import net.minestom.vanilla.commands.all.MeCommand; +import net.minestom.vanilla.commands.all.MsgCommand; +import net.minestom.vanilla.commands.gamemaster.DifficultyCommand; +import net.minestom.vanilla.commands.gamemaster.ForceloadCommand; +import net.minestom.vanilla.commands.gamemaster.GamemodeCommand; +import net.minestom.vanilla.commands.gamemaster.SayCommand; +import net.minestom.vanilla.commands.owner.SaveAllCommand; +import net.minestom.vanilla.commands.owner.StopCommand; +import net.minestom.vanilla.logging.Logger; -import net.kyori.adventure.key.Key; -import net.minestom.vanilla.VanillaReimplementation; -import net.minestom.vanilla.instancemeta.InstanceMetaFeature; -import org.jetbrains.annotations.NotNull; +import java.util.HashMap; +import java.util.List; +import java.util.Map; -import java.util.Set; +/** + * Handles the registration and management of all built-in (vanilla) commands. + * Allows for easy bulk or custom command registration into Minestom's {@link CommandManager}. + */ +public final class VanillaCommandsFeature { -public class VanillaCommandsFeature implements VanillaReimplementation.Feature { + /** + * A map of all registered vanilla commands by name. + */ + private static final Map registeredCommands = new HashMap<>(); - @Override - public void hook(@NotNull HookContext context) { - new Logic().hook(context.vri()); - } + /** + * List of all default vanilla command instances that should be registered by default. + */ + private static final List VANILLA_COMMANDS = List.of( + new HelpCommand(), + new ListCommand(), + new MeCommand(), + new DifficultyCommand(), + new ForceloadCommand(), + new GamemodeCommand(), + new SayCommand(), + new SaveAllCommand(), + new StopCommand(), + new OpCommand(), + new MsgCommand() + ); - @Override - public @NotNull Key key() { - return Key.key("vri:commands"); - } + /** + * Registers a single vanilla command to the command manager and stores it for reference. + * Logs if the command is not part of the default vanilla command list. + * + * @param manager the command manager to register with + * @param command the command to register + */ + public static void registerCommand(CommandManager manager, VanillaCommand command) { + manager.register(command); + registeredCommands.put(command.getName(), command); - private static class Logic { - private Logic() { + if (!getVanillaCommands().contains(command)) { + Logger.info("Registered custom vanilla command " + command.getName()); } + } - private void hook(@NotNull VanillaReimplementation vri) { - VanillaCommands.registerAll(vri.process().command()); - } + /** + * Registers all default vanilla commands to the given command manager. + * + * @param manager the command manager to register all commands to + */ + public static void registerAll(CommandManager manager) { + VANILLA_COMMANDS.forEach(command -> registerCommand(manager, command)); + Logger.info("Registered " + VANILLA_COMMANDS.size() + " vanilla commands"); + } + + /** + * Returns a map of all vanilla commands that have been registered, including custom ones. + * + * @return a map of command name to {@link VanillaCommand} instance + */ + public static Map getRegisteredCommands() { + return registeredCommands; } - @Override - public @NotNull Set> dependencies() { - return Set.of(InstanceMetaFeature.class); + /** + * Returns the list of default vanilla command instances (before registration). + * + * @return a list of default {@link VanillaCommand} instances + */ + public static List getVanillaCommands() { + return VANILLA_COMMANDS; } } diff --git a/commands/src/main/java/net/minestom/vanilla/commands/admin/OpCommand.java b/commands/src/main/java/net/minestom/vanilla/commands/admin/OpCommand.java new file mode 100644 index 00000000..3995a278 --- /dev/null +++ b/commands/src/main/java/net/minestom/vanilla/commands/admin/OpCommand.java @@ -0,0 +1,79 @@ +package net.minestom.vanilla.commands.admin; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.TextDecoration; +import net.minestom.server.MinecraftServer; +import net.minestom.server.command.CommandSender; +import net.minestom.server.command.ConsoleSender; +import net.minestom.server.command.builder.CommandContext; +import net.minestom.server.command.builder.arguments.ArgumentType; +import net.minestom.server.command.builder.arguments.minecraft.ArgumentEntity; +import net.minestom.server.entity.Entity; +import net.minestom.server.entity.Player; +import net.minestom.vanilla.commands.VanillaCommand; + +import java.util.List; + +public class OpCommand extends VanillaCommand { + public OpCommand() { + super("op"); + //TODO set correct permission Level. But due a lack of console to otherwise run op, we place it for all. + setCondition(permission(LEVEL_ALL)); + + ArgumentEntity target = ArgumentType.Entity("target").onlyPlayers(true); + setDefaultExecutor(this::defaultor); + + addSyntax((sender, context) -> { + List entities = context.get(target).find(sender); + + setOp(sender, context, entities); + }, target); + } + + private void setOp(CommandSender sender, CommandContext context, List entities) { + if (entities.isEmpty()) { + sender.sendMessage(Component.translatable("argument.entity.notfound.player",NamedTextColor.RED)); + return; + } + + for (Entity entity : entities) { + + final Player player = (Player) entity; + + if (player.getPermissionLevel() >= LEVEL_GAMEMASTER) { + player.sendMessage(Component.translatable("commands.op.failed").color(NamedTextColor.RED)); + return; + } + // Todo make so permission level is based on server config (https://minecraft.wiki/w/Permission_level#Java_Edition_2) + player.setPermissionLevel(LEVEL_OWNER); + player.refreshCommands(); + + if (sender instanceof ConsoleSender) { + MinecraftServer.getConnectionManager().getOnlinePlayers().stream() + .filter(p -> player.getPermissionLevel() >= LEVEL_GAMEMASTER) // Filter for players with operator permission + .forEach(p -> player.sendMessage(Component.text("[Server: ").append(Component.translatable("commands.op.success", Component.text(player.getUsername()))).color(NamedTextColor.GRAY).decoration(TextDecoration.ITALIC, true))); + return; + } + + MinecraftServer.getConnectionManager().getOnlinePlayers().stream() + .filter(p -> player.getPermissionLevel() >= LEVEL_GAMEMASTER) // Filter for players with operator permission + .forEach(p -> player.sendMessage(Component.translatable("commands.op.success", Component.text(player.getUsername())))); + + } + } + + + @Override + public Component usage(CommandSender sender, CommandContext context) { + return Component.text("/op []"); + } + + @Override + public void defaultor(CommandSender sender, CommandContext context) { + sender.sendMessage(Component.translatable("command.unknown.argument").color(NamedTextColor.RED) + .appendNewline() + .append(Component.text(context.getCommandName()).color(NamedTextColor.RED).decoration(TextDecoration.UNDERLINED, true)) + .append(Component.translatable("command.context.here", NamedTextColor.RED).decoration(TextDecoration.ITALIC,true))); + } +} diff --git a/commands/src/main/java/net/minestom/vanilla/commands/all/HelpCommand.java b/commands/src/main/java/net/minestom/vanilla/commands/all/HelpCommand.java new file mode 100644 index 00000000..fe235e1e --- /dev/null +++ b/commands/src/main/java/net/minestom/vanilla/commands/all/HelpCommand.java @@ -0,0 +1,90 @@ +package net.minestom.vanilla.commands.all; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.TextDecoration; +import net.minestom.server.command.CommandSender; +import net.minestom.server.command.builder.CommandContext; +import net.minestom.server.command.builder.arguments.ArgumentType; +import net.minestom.server.command.builder.arguments.ArgumentWord; +import net.minestom.vanilla.commands.VanillaCommand; +import net.minestom.vanilla.commands.VanillaCommandsFeature; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +// Not explicitly used but good to keep if needed +import java.util.List; +// For Objects.requireNonNullElse for better null handling if needed +// Good practice to include if using streams + + +/** + * Returns the list of all available commands + */ +public class HelpCommand extends VanillaCommand { + public HelpCommand() { + super("help"); + + setCondition(permission(LEVEL_ALL)); + + setDefaultExecutor(this::defaultor); + + ArgumentWord commandArg = ArgumentType.Word("command"); + + addSyntax((sender, context) -> { + String commandName = context.get(commandArg); + + // Attempt to retrieve the command instance + VanillaCommand targetCommand = VanillaCommandsFeature.getRegisteredCommands().get(commandName); + + if (targetCommand == null) { + sender.sendMessage(Component.translatable("commands.help.failed", NamedTextColor.RED)); + return; + } + + viewUsage(sender, context, targetCommand); + + }, commandArg); // Associate this executor with the 'commandArg' + } + + /** + * Displays the usage for a specific command. + * @param sender The command sender. + * @param context The command context. + * @param command The VanillaCommand instance whose usage is to be displayed. Can be null if not found. + */ + private void viewUsage(@NotNull CommandSender sender, CommandContext context, @Nullable VanillaCommand command) { + if (command == null) { + return; + } + + Component usage = command.usage(sender, context); + + if (usage == null) { + return; + } + sender.sendMessage(usage); + } + + private int compareCommands(VanillaCommand a, VanillaCommand b) { + return a.getName().compareTo(b.getName()); + } + + @Override + public Component usage(CommandSender sender, CommandContext context) { + return Component.text("/help []"); + } + + public void defaultor(CommandSender sender, CommandContext context) { + sender.sendMessage(Component.text("=== Help ===")); + + List commands = new ArrayList<>(VanillaCommandsFeature.getRegisteredCommands().values()); + + commands.sort(this::compareCommands); + + commands.forEach(command -> sender.sendMessage(Component.text("/" + command.getName().toLowerCase()))); + + sender.sendMessage(Component.text("============")); + } +} \ No newline at end of file diff --git a/commands/src/main/java/net/minestom/vanilla/commands/all/ListCommand.java b/commands/src/main/java/net/minestom/vanilla/commands/all/ListCommand.java new file mode 100644 index 00000000..a2121abc --- /dev/null +++ b/commands/src/main/java/net/minestom/vanilla/commands/all/ListCommand.java @@ -0,0 +1,72 @@ +package net.minestom.vanilla.commands.all; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.JoinConfiguration; +import net.minestom.server.MinecraftServer; +import net.minestom.server.command.CommandSender; +import net.minestom.server.command.builder.CommandContext; +import net.minestom.server.command.builder.arguments.ArgumentCommand; +import net.minestom.server.command.builder.arguments.ArgumentType; +import net.minestom.server.entity.Player; +import net.minestom.vanilla.commands.VanillaCommand; + +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + +public class ListCommand extends VanillaCommand { + public ListCommand() { + super("list"); + + setCondition(permission(LEVEL_ALL)); + + setDefaultExecutor(this::defaultor); + + ArgumentCommand arg = ArgumentType.Command("uuids"); + + addSyntax((sender, context) -> listPlayers(sender, context, true), arg); + + } + + @Override + public Component usage(CommandSender sender, CommandContext context) { + return Component.text("/list [uuids]"); + } + + @Override + public void defaultor(CommandSender sender, CommandContext context) { + listPlayers(sender, context, false); + } + + private void listPlayers(CommandSender sender, CommandContext context, boolean uuid) { + int onlinePlayersCount = MinecraftServer.getConnectionManager().getOnlinePlayerCount(); + String maxPlayersCount = "???"; + //TODO add maxPlayerCount thingy from sever config / properties i think + + Collection onlinePlayers = MinecraftServer.getConnectionManager().getOnlinePlayers(); + + List playerComponents = onlinePlayers.stream() + .map(player -> { + // Start with the player's username + Component playerUsername = Component.text(player.getUsername()); + Component playerUUID = Component.text(player.getUuid().toString()); + if (uuid) { + // If UUIDs are requested, append "(UUID)" to the username + playerUsername = Component.translatable("commands.list.nameAndId", playerUsername, playerUUID); + } + return playerUsername; + }) + .collect(Collectors.toList()); + + Component playerListComponent = Component.join( + JoinConfiguration.builder().separator(Component.text(", ")).build(), + playerComponents + ); + + Component headerComponent = Component.translatable("commands.list.players", Component.text(onlinePlayersCount), Component.text(maxPlayersCount), Component.text()); + + Component fullMessage = headerComponent.append(playerListComponent); + + sender.sendMessage(fullMessage); + } +} diff --git a/commands/src/main/java/net/minestom/vanilla/commands/MeCommand.java b/commands/src/main/java/net/minestom/vanilla/commands/all/MeCommand.java similarity index 55% rename from commands/src/main/java/net/minestom/vanilla/commands/MeCommand.java rename to commands/src/main/java/net/minestom/vanilla/commands/all/MeCommand.java index 05043ac0..bd299f3c 100644 --- a/commands/src/main/java/net/minestom/vanilla/commands/MeCommand.java +++ b/commands/src/main/java/net/minestom/vanilla/commands/all/MeCommand.java @@ -1,36 +1,49 @@ -package net.minestom.vanilla.commands; +package net.minestom.vanilla.commands.all; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.TextComponent; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.TextDecoration; import net.minestom.server.adventure.audience.Audiences; import net.minestom.server.command.CommandSender; -import net.minestom.server.command.builder.Command; import net.minestom.server.command.builder.CommandContext; import net.minestom.server.command.builder.arguments.ArgumentStringArray; import net.minestom.server.command.builder.arguments.ArgumentType; import net.minestom.server.entity.Player; +import net.minestom.vanilla.commands.VanillaCommand; /** * Command that displays a player action */ -public class MeCommand extends Command { +public class MeCommand extends VanillaCommand { public MeCommand() { super("me"); - setDefaultExecutor(this::usage); + setCondition(permission(LEVEL_ALL)); + + setDefaultExecutor(this::defaultor); ArgumentStringArray message = ArgumentType.StringArray("message"); addSyntax(this::execute, message); } - private void usage(CommandSender player, CommandContext arguments) { - player.sendMessage("Usage: /me "); + @Override + public Component usage(CommandSender sender, CommandContext context) { + return Component.text("/me "); + } + + @Override + public void defaultor(CommandSender sender, CommandContext context) { + sender.sendMessage(Component.translatable("command.unknown.command", NamedTextColor.RED) + .appendNewline() + .append(Component.text(context.getInput().replace(context.getCommandName() + " ",""), NamedTextColor.GRAY) + .append(Component.translatable("command.context.here", NamedTextColor.RED).decoration(TextDecoration.ITALIC,true)))); } private void execute(CommandSender sender, CommandContext arguments) { if (!(sender instanceof Player player)) { - sender.sendMessage("This command must be executed by a player!"); + sender.sendMessage(Component.translatable("permissions.requires.player", NamedTextColor.RED)); return; } String[] messageParts = arguments.get("message"); @@ -49,10 +62,5 @@ private void execute(CommandSender sender, CommandContext arguments) { Component message = builder.build(); Audiences.all().sendMessage(message); } - - @SuppressWarnings("unused") - private boolean isAllowed(CommandSender player) { - return player instanceof Player; // TODO: permissions - } } diff --git a/commands/src/main/java/net/minestom/vanilla/commands/all/MsgCommand.java b/commands/src/main/java/net/minestom/vanilla/commands/all/MsgCommand.java new file mode 100644 index 00000000..3391b892 --- /dev/null +++ b/commands/src/main/java/net/minestom/vanilla/commands/all/MsgCommand.java @@ -0,0 +1,79 @@ +package net.minestom.vanilla.commands.all; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.TextDecoration; +import net.minestom.server.command.CommandSender; +import net.minestom.server.command.ConsoleSender; +import net.minestom.server.command.builder.CommandContext; +import net.minestom.server.command.builder.arguments.ArgumentString; +import net.minestom.server.command.builder.arguments.ArgumentType; +import net.minestom.server.command.builder.arguments.minecraft.ArgumentEntity; +import net.minestom.server.entity.Entity; +import net.minestom.server.entity.Player; +import net.minestom.vanilla.commands.VanillaCommand; + +import java.util.List; + +public class MsgCommand extends VanillaCommand { + public MsgCommand() { + super("msg", "tell", "w"); + + setCondition(permission(LEVEL_ALL)); + + setDefaultExecutor(this::defaultor); + + ArgumentEntity targetArg = ArgumentType.Entity("targets").onlyPlayers(true); + ArgumentString messageArg = ArgumentType.String("message"); + + addSyntax((sender, context) -> { + List receivers = context.get(targetArg).find(sender); + String message = context.get(messageArg); + + whisperTo(sender, receivers, message); + + }, targetArg, messageArg); + + + } + + private void whisperTo(CommandSender sender, List receivers, String message) { + + if (receivers.isEmpty()) { + sender.sendMessage(Component.translatable("argument.entity.notfound.player", NamedTextColor.RED)); + return; + } + + receivers.forEach(entity -> { + if (!(entity instanceof Player player)) { + return; + } + + if (sender instanceof ConsoleSender) { + player.sendMessage(Component.translatable("commands.message.display.incoming", Component.text("Server"), Component.text(message).decorate(TextDecoration.ITALIC).color(NamedTextColor.GRAY))); + return; + } + + String senderName = ((Player) sender).getUsername(); + sender.sendMessage(Component.translatable("commands.message.display.outgoing", Component.text(player.getUsername()), Component.text(message)).decorate(TextDecoration.ITALIC).color(NamedTextColor.GRAY)); + player.sendMessage(Component.translatable("commands.message.display.incoming", Component.text(senderName), Component.text(message)).decorate(TextDecoration.ITALIC).color(NamedTextColor.GRAY)); + }); + + + + + } + + @Override + public Component usage(CommandSender sender, CommandContext context) { + return Component.text("/msg "); + } + + @Override + protected void defaultor(CommandSender sender, CommandContext context) { + sender.sendMessage(Component.translatable("command.unknown.command", NamedTextColor.RED) + .appendNewline() + .append(Component.text(context.getInput().replace(context.getCommandName() + " ",""), NamedTextColor.GRAY) + .append(Component.translatable("command.context.here", NamedTextColor.RED).decoration(TextDecoration.ITALIC,true)))); + } +} diff --git a/commands/src/main/java/net/minestom/vanilla/commands/gamemaster/DifficultyCommand.java b/commands/src/main/java/net/minestom/vanilla/commands/gamemaster/DifficultyCommand.java new file mode 100644 index 00000000..51f768fa --- /dev/null +++ b/commands/src/main/java/net/minestom/vanilla/commands/gamemaster/DifficultyCommand.java @@ -0,0 +1,71 @@ +package net.minestom.vanilla.commands.gamemaster; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.TextDecoration; +import net.minestom.server.MinecraftServer; +import net.minestom.server.command.CommandSender; +import net.minestom.server.command.builder.CommandContext; +import net.minestom.server.command.builder.arguments.ArgumentEnum; +import net.minestom.server.command.builder.arguments.ArgumentType; +import net.minestom.server.world.Difficulty; +import net.minestom.vanilla.commands.VanillaCommand; + +public class DifficultyCommand extends VanillaCommand { + + public DifficultyCommand() { + super("difficulty"); + + setCondition(permission(LEVEL_GAMEMASTER)); + + setDefaultExecutor(this::defaultor); + + ArgumentEnum difficulty = ArgumentType.Enum("difficulty", Difficulty.class).setFormat(ArgumentEnum.Format.LOWER_CASED); + + addSyntax((sender, context) -> { + Difficulty newDifficulty = context.get(difficulty); + setDifficulty(sender, newDifficulty); + }, difficulty); + } + + @Override + public Component usage(CommandSender sender, CommandContext context) { + return Component.text("/difficulty [peaceful]") + .appendNewline() + .append(Component.text("/difficulty [easy]")) + .appendNewline() + .append(Component.text("/difficulty [normal]")) + .appendNewline() + .append(Component.text("/difficulty [hard]")); + } + + @Override + public void defaultor(CommandSender sender, CommandContext context) { + if (hasNoArguments(context)) { + setDifficulty(sender, Difficulty.EASY); + return; + } + + sender.sendMessage(Component.translatable("command.unknown.argument").color(NamedTextColor.RED) + .appendNewline() + .append(Component.text("...ifficulty").color(NamedTextColor.GRAY)) + .appendSpace() + .append(Component.text(context.getInput().replace(context.getCommandName() + " ","")).color(NamedTextColor.RED).decoration(TextDecoration.UNDERLINED, true)) + .append(Component.translatable("command.context.here", NamedTextColor.RED).decoration(TextDecoration.ITALIC,true))); + + } + + + private void setDifficulty(CommandSender sender, Difficulty difficulty) { + Difficulty current = MinecraftServer.getDifficulty(); + + if (current == difficulty) { + sender.sendMessage(Component.translatable("commands.difficulty.failure", Component.translatable("options.difficulty." + difficulty.name().toLowerCase())).color(NamedTextColor.RED)); + return; + } + + MinecraftServer.setDifficulty(difficulty); + sender.sendMessage(Component.translatable("commands.difficulty.success", Component.translatable("options.difficulty." + difficulty.name().toLowerCase()))); + } +} + diff --git a/commands/src/main/java/net/minestom/vanilla/commands/ForceloadCommand.java b/commands/src/main/java/net/minestom/vanilla/commands/gamemaster/ForceloadCommand.java similarity index 77% rename from commands/src/main/java/net/minestom/vanilla/commands/ForceloadCommand.java rename to commands/src/main/java/net/minestom/vanilla/commands/gamemaster/ForceloadCommand.java index e8eaff53..eeae661a 100644 --- a/commands/src/main/java/net/minestom/vanilla/commands/ForceloadCommand.java +++ b/commands/src/main/java/net/minestom/vanilla/commands/gamemaster/ForceloadCommand.java @@ -1,7 +1,9 @@ -package net.minestom.vanilla.commands; +package net.minestom.vanilla.commands.gamemaster; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.TextDecoration; import net.minestom.server.command.CommandSender; -import net.minestom.server.command.builder.Command; import net.minestom.server.command.builder.CommandContext; import net.minestom.server.command.builder.arguments.ArgumentType; import net.minestom.server.coordinate.CoordConversion; @@ -9,26 +11,21 @@ import net.minestom.server.entity.Player; import net.minestom.server.instance.Instance; import net.minestom.server.utils.location.RelativeVec; +import net.minestom.vanilla.commands.VanillaCommand; import net.minestom.vanilla.instancemeta.tickets.TicketManager; import net.minestom.vanilla.instancemeta.tickets.TicketUtils; import java.util.List; -/** - * "forceload": - * Description: "Forces chunks to constantly be loaded or not. " - * BE: false - * EE: false - * JE: true - * OP_Level: 2 - * BE_EE_OP_Level: 0 - * MP_Only: false - */ -public class ForceloadCommand extends Command { +public class ForceloadCommand extends VanillaCommand { public ForceloadCommand() { super("forceload"); + setCondition(permission(LEVEL_GAMEMASTER)); + + setDefaultExecutor(this::defaultor); + // forceload add [] // Forces the chunk at the position (through to if set) in the dimension of the command's execution to be loaded constantly. this.addSyntax( @@ -79,7 +76,7 @@ private void removeForceLoad(Instance instance, long chunkIndex) { private void usageAddFrom(CommandSender sender, CommandContext context) { if (!(sender instanceof Player player)) { - sender.sendMessage("This command must be executed by a player!"); + sender.sendMessage(Component.translatable("permissions.requires.player", NamedTextColor.RED)); return; } RelativeVec fromVec = context.get("from"); @@ -96,7 +93,7 @@ private void usageAddFrom(CommandSender sender, CommandContext context) { private void usageAddFromTo(CommandSender sender, CommandContext context) { if (!(sender instanceof Player player)) { - sender.sendMessage("This command must be executed by a player!"); + sender.sendMessage(Component.translatable("permissions.requires.player", NamedTextColor.RED)); return; } RelativeVec fromVec = context.get("from"); @@ -123,7 +120,7 @@ private void usageAddFromTo(CommandSender sender, CommandContext context) { private void usageRemoveFrom(CommandSender sender, CommandContext context) { if (!(sender instanceof Player player)) { - sender.sendMessage("This command must be executed by a player!"); + sender.sendMessage(Component.translatable("permissions.requires.player", NamedTextColor.RED)); return; } RelativeVec fromVec = context.get("from"); @@ -141,7 +138,7 @@ private void usageRemoveFrom(CommandSender sender, CommandContext context) { private void usageRemoveFromTo(CommandSender sender, CommandContext context) { if (!(sender instanceof Player player)) { - sender.sendMessage("This command must be executed by a player!"); + sender.sendMessage(Component.translatable("permissions.requires.player", NamedTextColor.RED)); return; } RelativeVec fromVec = context.get("from"); @@ -167,4 +164,21 @@ private void usageRemoveFromTo(CommandSender sender, CommandContext context) { } } } + + @Override + public Component usage(CommandSender sender, CommandContext context) { + return Component.text("/forceload add []") + .appendNewline() + .append(Component.text("/forceload remove (|all)")) + .appendNewline() + .append(Component.text("/forceload query []")); + } + + @Override + public void defaultor(CommandSender sender, CommandContext context) { + sender.sendMessage(Component.translatable("command.unknown.command", NamedTextColor.RED) + .appendNewline() + .append(Component.text(context.getInput().replace(context.getCommandName() + " ",""), NamedTextColor.GRAY) + .append(Component.translatable("command.context.here", NamedTextColor.RED).decoration(TextDecoration.ITALIC,true)))); + } } diff --git a/commands/src/main/java/net/minestom/vanilla/commands/gamemaster/GamemodeCommand.java b/commands/src/main/java/net/minestom/vanilla/commands/gamemaster/GamemodeCommand.java new file mode 100644 index 00000000..2e161494 --- /dev/null +++ b/commands/src/main/java/net/minestom/vanilla/commands/gamemaster/GamemodeCommand.java @@ -0,0 +1,110 @@ +package net.minestom.vanilla.commands.gamemaster; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.TextDecoration; +import net.minestom.server.command.CommandSender; +import net.minestom.server.command.builder.CommandContext; +import net.minestom.server.command.builder.arguments.ArgumentEnum; +import net.minestom.server.command.builder.arguments.ArgumentType; +import net.minestom.server.command.builder.arguments.minecraft.ArgumentEntity; +import net.minestom.server.entity.Entity; +import net.minestom.server.entity.GameMode; +import net.minestom.server.entity.Player; +import net.minestom.vanilla.commands.VanillaCommand; + +import java.util.List; + +/** + * Command that make a player change gamemode, made in + * the style of the vanilla /gamemode command. + * + * @see ... + */ +public class GamemodeCommand extends VanillaCommand { + + public GamemodeCommand() { + super("gamemode"); + + setCondition(permission(LEVEL_GAMEMASTER)); + + ArgumentEnum gamemode = ArgumentType.Enum("gamemode", GameMode.class).setFormat(ArgumentEnum.Format.LOWER_CASED); + + ArgumentEntity target = ArgumentType.Entity("targets").onlyPlayers(true); + + setDefaultExecutor(this::defaultor); + + //Command Syntax for /gamemode + addSyntax((sender, context) -> { + GameMode mode = context.get(gamemode); + + setGamemodeSelf(sender, mode); + + }, gamemode); + + //Command Syntax for /gamemode [targets] + addSyntax((sender, context) -> { + GameMode mode = context.get(gamemode); + List entities = context.get(target).find(sender); + //Set the gamemode for the targets + setGamemodeOther(sender, mode, entities); + }, gamemode, target); + } + + + @Override + public Component usage(CommandSender sender, CommandContext context) { + return Component.text("/gamemode []"); + } + + @Override + public void defaultor(CommandSender sender, CommandContext context) { + + if (hasNoArguments(context)) { + sender.sendMessage(Component.translatable("command.unknown.command", NamedTextColor.RED) + .appendNewline() + .append(Component.text(context.getInput().replace(context.getCommandName() + " ",""), NamedTextColor.GRAY) + .append(Component.translatable("command.context.here", NamedTextColor.RED).decoration(TextDecoration.ITALIC,true)))); + return; + } + + // Get the part of the input that was supposed to be the gamemode + String invalidInput = context.getInput().replace(context.getCommandName() + " ", ""); + + // Correctly create a translatable component with the placeholder filled + sender.sendMessage(Component.translatable("argument.gamemode.invalid", + Component.text(invalidInput) + ).color(NamedTextColor.RED)); + } + + private void setGamemodeSelf(CommandSender sender, GameMode gameMode) { + if (!(sender instanceof final Player playerSender)) { + sender.sendMessage(Component.translatable("permissions.requires.player", NamedTextColor.RED)); + return; + } + setGamemode(playerSender, gameMode); + } + + private void setGamemodeOther(CommandSender sender, GameMode gameMode, List entities) { + if (entities.isEmpty()) { + sender.sendMessage(Component.translatable("argument.entity.notfound.player", NamedTextColor.RED)); + return; + } + + for (Entity entity : entities) { + + setGamemode(entity, gameMode); + } + } + + + private void setGamemode(Entity entity, GameMode gamemode) { + + final Player player = (Player) entity; + + if (player.getGameMode().equals(gamemode)) return; + + player.setGameMode(gamemode); + player.sendMessage(Component.translatable("gameMode.changed", Component.translatable("gameMode." + gamemode.name().toLowerCase()))); + } +} diff --git a/commands/src/main/java/net/minestom/vanilla/commands/gamemaster/SayCommand.java b/commands/src/main/java/net/minestom/vanilla/commands/gamemaster/SayCommand.java new file mode 100644 index 00000000..ee047d75 --- /dev/null +++ b/commands/src/main/java/net/minestom/vanilla/commands/gamemaster/SayCommand.java @@ -0,0 +1,65 @@ +package net.minestom.vanilla.commands.gamemaster; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.TextComponent; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.TextDecoration; +import net.minestom.server.adventure.audience.Audiences; +import net.minestom.server.command.CommandSender; +import net.minestom.server.command.builder.CommandContext; +import net.minestom.server.command.builder.arguments.ArgumentStringArray; +import net.minestom.server.command.builder.arguments.ArgumentType; +import net.minestom.server.entity.Player; +import net.minestom.vanilla.commands.VanillaCommand; + +/** + * Command that displays a player action + */ +public class SayCommand extends VanillaCommand { + public SayCommand() { + super("say"); + + setCondition(permission(LEVEL_GAMEMASTER)); + + setDefaultExecutor(this::defaultor); + + ArgumentStringArray message = ArgumentType.StringArray("message"); + + addSyntax(this::execute, message); + } + + @Override + public Component usage(CommandSender sender, CommandContext context) { + return Component.text("/me "); + } + + @Override + public void defaultor(CommandSender sender, CommandContext context) { + sender.sendMessage(Component.translatable("command.unknown.command", NamedTextColor.RED) + .appendNewline() + .append(Component.text(context.getInput().replace(context.getCommandName() + " ",""), NamedTextColor.GRAY) + .append(Component.translatable("command.context.here", NamedTextColor.RED).decoration(TextDecoration.ITALIC,true)))); + } + + private void execute(CommandSender sender, CommandContext arguments) { + if (!(sender instanceof Player player)) { + sender.sendMessage(Component.translatable("permissions.requires.player", NamedTextColor.RED)); + return; + } + String[] messageParts = arguments.get("message"); + + TextComponent.Builder builder = Component.text(); + + builder.append(Component.text("[" + player.getUsername() + "]")); + builder.append(Component.text(" ")); + builder.append(Component.text(messageParts[0])); + + for (int i = 1; i < messageParts.length; i++) { + builder.append(Component.text(messageParts[i])); + } + + Component message = builder.build(); + Audiences.all().sendMessage(message); + } +} + diff --git a/commands/src/main/java/net/minestom/vanilla/commands/owner/SaveAllCommand.java b/commands/src/main/java/net/minestom/vanilla/commands/owner/SaveAllCommand.java new file mode 100644 index 00000000..f42bae89 --- /dev/null +++ b/commands/src/main/java/net/minestom/vanilla/commands/owner/SaveAllCommand.java @@ -0,0 +1,36 @@ +package net.minestom.vanilla.commands.owner; + +import net.kyori.adventure.text.Component; +import net.minestom.server.MinecraftServer; +import net.minestom.server.command.CommandSender; +import net.minestom.server.command.builder.CommandContext; +import net.minestom.vanilla.commands.VanillaCommand; +import net.minestom.vanilla.logging.Logger; + +/** + * Save the server + */ +public class SaveAllCommand extends VanillaCommand { + public SaveAllCommand() { + super("save-all"); + setCondition(permission(LEVEL_OWNER)); + setDefaultExecutor(this::defaultor); + + //Todo add flush argument thiny + } + + @Override + public Component usage(CommandSender sender, CommandContext context) { + return Component.text("/save-all [flush]"); + } + + @Override + public void defaultor(CommandSender sender, CommandContext context) { + sender.sendMessage(Component.translatable("commands.save.saving")); + MinecraftServer.getInstanceManager().getInstances().forEach(i -> { + i.saveChunksToStorage(); + Logger.info("Saved dimension " + i.getDimensionType().name()); + }); + sender.sendMessage(Component.translatable("commands.save.success")); + } +} diff --git a/commands/src/main/java/net/minestom/vanilla/commands/owner/StopCommand.java b/commands/src/main/java/net/minestom/vanilla/commands/owner/StopCommand.java new file mode 100644 index 00000000..9b4c8188 --- /dev/null +++ b/commands/src/main/java/net/minestom/vanilla/commands/owner/StopCommand.java @@ -0,0 +1,28 @@ +package net.minestom.vanilla.commands.owner; + +import net.kyori.adventure.text.Component; +import net.minestom.server.MinecraftServer; +import net.minestom.server.command.CommandSender; +import net.minestom.server.command.builder.CommandContext; +import net.minestom.vanilla.commands.VanillaCommand; + +/** + * Stops the server + */ +public class StopCommand extends VanillaCommand { + public StopCommand() { + super("stop"); + setCondition(permission(LEVEL_OWNER)); + setDefaultExecutor(this::defaultor); + } + + @Override + public Component usage(CommandSender sender, CommandContext context) { + return null; + } + + @Override + public void defaultor(CommandSender sender, CommandContext context) { + MinecraftServer.stopCleanly(); + } +} diff --git a/mojang-data/client-1.21.5.jar b/mojang-data/client-1.21.5.jar new file mode 100644 index 00000000..1107a671 Binary files /dev/null and b/mojang-data/client-1.21.5.jar differ diff --git a/survival/build.gradle.kts b/survival/build.gradle.kts index baac46b4..655df64b 100644 --- a/survival/build.gradle.kts +++ b/survival/build.gradle.kts @@ -3,4 +3,5 @@ dependencies { api(project(":datapack")) api(project(":loot-table")) api(project(":crafting")) + api(project(":commands")) } \ No newline at end of file diff --git a/survival/src/main/java/net/minestom/vanilla/survival/Survival.java b/survival/src/main/java/net/minestom/vanilla/survival/Survival.java index 99a4a778..73a033a8 100644 --- a/survival/src/main/java/net/minestom/vanilla/survival/Survival.java +++ b/survival/src/main/java/net/minestom/vanilla/survival/Survival.java @@ -26,6 +26,7 @@ import net.minestom.server.item.enchant.Enchantment; import net.minestom.server.utils.time.TimeUnit; import net.minestom.server.world.DimensionType; +import net.minestom.vanilla.commands.VanillaCommandsFeature; import net.minestom.vanilla.crafting.CraftingFeature; import net.minestom.vanilla.crafting.Recipe; import net.minestom.vanilla.logging.Logger; @@ -74,6 +75,9 @@ public void initialize() { Map recipes = CraftingFeature.buildFromDatapack(process); process.eventHandler().addChild(CraftingFeature.createEventNode(recipes, process)); + + VanillaCommandsFeature.registerAll(process.command()); + process.eventHandler().addListener(AsyncPlayerConfigurationEvent.class, event -> { final Player player = event.getPlayer();