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();