From 5cea728eb750e6b3f29b1ada860f351347912ec7 Mon Sep 17 00:00:00 2001 From: Benjamin Elsdon Date: Tue, 19 Apr 2022 18:17:19 +0800 Subject: [PATCH 1/3] Admin cmds, perms and descriptions for all cmds + Additonal stuff: Fixed + refactored help command. Removed 'Usage: ' from all commands. Created 'player.hasPermission(permission)' function. Created default for Usage annotation. Created hashmap version of 'getHandlers' and renamed the original to getHandlersAsList() --- .../emu/grasscutter/commands/Command.java | 8 +- .../emu/grasscutter/commands/CommandMap.java | 7 +- .../grasscutter/commands/PlayerCommands.java | 48 ++++- .../grasscutter/commands/ServerCommands.java | 186 ++++++++++++++---- .../java/emu/grasscutter/game/Account.java | 4 + 5 files changed, 202 insertions(+), 51 deletions(-) diff --git a/src/main/java/emu/grasscutter/commands/Command.java b/src/main/java/emu/grasscutter/commands/Command.java index d1e181e20..d26d7fe29 100644 --- a/src/main/java/emu/grasscutter/commands/Command.java +++ b/src/main/java/emu/grasscutter/commands/Command.java @@ -7,14 +7,16 @@ import java.lang.annotation.RetentionPolicy; public @interface Command { String label() default ""; - String usage() default ""; + String usage() default "No usage specified"; + + String description() default "No description specified"; - String[] aliases() default {""}; + String[] aliases() default {}; Execution execution() default Execution.ALL; String permission() default ""; - + enum Execution { ALL, CONSOLE, diff --git a/src/main/java/emu/grasscutter/commands/CommandMap.java b/src/main/java/emu/grasscutter/commands/CommandMap.java index e9ca260e7..a802551ce 100644 --- a/src/main/java/emu/grasscutter/commands/CommandMap.java +++ b/src/main/java/emu/grasscutter/commands/CommandMap.java @@ -68,10 +68,12 @@ public final class CommandMap { * Returns a list of all registered commands. * @return All command handlers as a list. */ - public List getHandlers() { + public List getHandlersAsList() { return new LinkedList<>(this.commands.values()); } + public HashMap getHandlers() { return new LinkedHashMap<>(this.commands); } + /** * Returns a handler by label/alias. * @param label The command label. @@ -111,8 +113,7 @@ public final class CommandMap { if(player != null) { String permissionNode = this.annotations.get(label).permission(); Account account = player.getAccount(); - List permissions = account.getPermissions(); - if(!permissions.contains("*") && !permissions.contains(permissionNode)) { + if(permissionNode != "" && !account.hasPermission(permissionNode)) { CommandHandler.sendMessage(player, "You do not have permission to run this command."); return; } } diff --git a/src/main/java/emu/grasscutter/commands/PlayerCommands.java b/src/main/java/emu/grasscutter/commands/PlayerCommands.java index ae80c5d25..2ffcbae06 100644 --- a/src/main/java/emu/grasscutter/commands/PlayerCommands.java +++ b/src/main/java/emu/grasscutter/commands/PlayerCommands.java @@ -27,7 +27,7 @@ import java.util.List; */ public final class PlayerCommands { @Command(label = "give", aliases = {"g", "item", "giveitem"}, - usage = "Usage: give [player] [amount]") + usage = "give [player] [amount]", description = "Gives an item to you or the specified player", permission = "player.give") public static class GiveCommand implements CommandHandler { @Override @@ -140,8 +140,8 @@ public final class PlayerCommands { } @Command(label = "drop", aliases = {"d", "dropitem"}, - usage = "Usage: drop [amount]", - execution = Command.Execution.PLAYER) + usage = "drop [amount]", + execution = Command.Execution.PLAYER, description = "Drops an item near you", permission = "server.drop") public static class DropCommand implements CommandHandler { @Override @@ -178,7 +178,7 @@ public final class PlayerCommands { } @Command(label = "spawn", execution = Command.Execution.PLAYER, - usage = "Usage: spawn [level] [amount]") + usage = "spawn [level] [amount]", description = "Spawns an entity near you", permission = "server.spawn") public static class SpawnCommand implements CommandHandler { @Override @@ -211,7 +211,7 @@ public final class PlayerCommands { } @Command(label = "killall", - usage = "Usage: killall [sceneId]") + usage = "killall [sceneId]", description = "Kill all entities", permission = "server.killall") public static class KillAllCommand implements CommandHandler { @Override @@ -239,7 +239,8 @@ public final class PlayerCommands { } @Command(label = "resetconst", aliases = {"resetconstellation"}, - usage = "Usage: resetconst [all]", execution = Command.Execution.PLAYER) + usage = "resetconst [all]", execution = Command.Execution.PLAYER, permission = "player.resetconstellation", + description = "Resets the constellation level on your current active character, will need to relog after using the command to see any changes.") public static class ResetConstellationCommand implements CommandHandler { @Override @@ -268,7 +269,7 @@ public final class PlayerCommands { } @Command(label = "godmode", - usage = "Usage: godmode", execution = Command.Execution.PLAYER) + usage = "godmode", execution = Command.Execution.PLAYER, description = "Prevents you from taking damage", permission = "player.godmode") public static class GodModeCommand implements CommandHandler { @Override @@ -279,7 +280,8 @@ public final class PlayerCommands { } @Command(label = "sethealth", aliases = {"sethp"}, - usage = "Usage: sethealth ", execution = Command.Execution.PLAYER) + usage = "sethealth ", execution = Command.Execution.PLAYER, description = "Sets your health to the specified value", + permission = "player.sethealth") public static class SetHealthCommand implements CommandHandler { @Override @@ -304,7 +306,8 @@ public final class PlayerCommands { } @Command(label = "clearartifacts", aliases = {"clearart"}, - usage = "Usage: clearartifacts", execution = Command.Execution.PLAYER) + usage = "clearartifacts", execution = Command.Execution.PLAYER, permission = "player.clearartifacts", + description = "Deletes all unequipped and unlocked level 0 artifacts, including yellow rarity ones from your inventory") public static class ClearArtifactsCommand implements CommandHandler { @Override @@ -317,4 +320,31 @@ public final class PlayerCommands { .forEach(item -> playerInventory.removeItem(item, item.getCount())); } } + + @Command(label = "sendservermessage", aliases = {"sendservmsg"}, + usage = "sendservermessage ", description = "Sends a message to a player as the server", + execution = Command.Execution.PLAYER, permission = "server.sendmessage") + public static class SendServerMessageCommand implements CommandHandler { + @Override + public void execute(GenshinPlayer player, List args) { + if(args.size() < 2) { + CommandHandler.sendMessage(null, "Usage: sendmessage "); return; + } + + try { + int target = Integer.parseInt(args.get(0)); + String message = String.join(" ", args.subList(1, args.size())); + + GenshinPlayer targetPlayer = Grasscutter.getGameServer().getPlayerById(target); + if(targetPlayer == null) { + CommandHandler.sendMessage(null, "Player not found."); return; + } + + targetPlayer.dropMessage(message); + CommandHandler.sendMessage(null, "Message sent."); + } catch (NumberFormatException ignored) { + CommandHandler.sendMessage(null, "Invalid player ID."); + } + } + } } diff --git a/src/main/java/emu/grasscutter/commands/ServerCommands.java b/src/main/java/emu/grasscutter/commands/ServerCommands.java index 1262fae97..052bb149e 100644 --- a/src/main/java/emu/grasscutter/commands/ServerCommands.java +++ b/src/main/java/emu/grasscutter/commands/ServerCommands.java @@ -1,10 +1,15 @@ package emu.grasscutter.commands; +import com.mongodb.internal.connection.CommandHelper; import emu.grasscutter.Grasscutter; import emu.grasscutter.database.DatabaseHelper; import emu.grasscutter.game.Account; import emu.grasscutter.game.GenshinPlayer; +import emu.grasscutter.server.packet.send.PacketSceneKickPlayerRsp; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; import java.util.List; import java.util.stream.Collectors; @@ -12,7 +17,7 @@ import java.util.stream.Collectors; * A container for server-related commands. */ public final class ServerCommands { - @Command(label = "reload", usage = "Usage: reload") + @Command(label = "reload", usage = "reload", description = "Reload server config", permission = "server.reload") public static class ReloadCommand implements CommandHandler { @Override @@ -25,12 +30,78 @@ public final class ServerCommands { @Override public void execute(GenshinPlayer player, List args) { + CommandHandler.sendMessage(player, "Reloading config."); this.execute(args); + CommandHandler.sendMessage(player, "Reload complete."); } } - + + @Command(label = "kick", usage = "kick ", description = "Kicks the specified player from the server (WIP)", permission = "server.kick") + public static class KickCommand implements CommandHandler { + @Override + public void execute(List args) { + int target = Integer.parseInt(args.get(0)); + String message = String.join(" ", args.subList(1, args.size())); + + GenshinPlayer targetPlayer = Grasscutter.getGameServer().getPlayerById(target); + if(targetPlayer == null) { + CommandHandler.sendMessage(null, "Player not found."); return; + } + + targetPlayer.sendPacket(new PacketSceneKickPlayerRsp(targetPlayer.getId())); + //targetPlayer.getSession().close(); + } + } + + @Command(label = "stop", usage = "stop", description = "Stops the server", permission = "server.stop") + public static class StopCommand implements CommandHandler { + @Override + public void execute(List args) { + Grasscutter.getLogger().info("Server shutting down..."); + for (GenshinPlayer p : Grasscutter.getGameServer().getPlayers().values()) { + p.dropMessage("Server shutting down..."); + } + + System.exit(1); + } + } + + @Command(label = "broadcast", aliases = {"b"}, + usage = "broadcast ", description = "Sends a message to all the players", permission = "server.broadcast") + public static class BroadcastCommand implements CommandHandler { + @Override + public void execute(List args) { + if(args.size() < 1) { + CommandHandler.sendMessage(null, "Usage: broadcast "); return; + } + + String message = String.join(" ", args.subList(0, args.size())); + + for (GenshinPlayer p : Grasscutter.getGameServer().getPlayers().values()) { + p.dropMessage(message); + } + + CommandHandler.sendMessage(null, "Message sent."); + } + + @Override + public void execute(GenshinPlayer player, List args) { + if(args.size() < 1) { + CommandHandler.sendMessage(player, "Usage: broadcast "); return; + } + + String message = String.join(" ", args.subList(0, args.size())); + + for (GenshinPlayer p : Grasscutter.getGameServer().getPlayers().values()) { + p.dropMessage(message); + } + + CommandHandler.sendMessage(player, "Message sent."); + } + } + @Command(label = "sendmessage", aliases = {"sendmsg", "msg"}, - usage = "Usage: sendmessage ") + usage = "sendmessage ", description = "Sends a message to a player") public static class SendMessageCommand implements CommandHandler { @Override @@ -79,8 +150,8 @@ public final class ServerCommands { } @Command(label = "account", - usage = "Usage: account [uid]", - execution = Command.Execution.CONSOLE) + usage = "account [uid]", + description = "Modify user accounts", execution = Command.Execution.CONSOLE) public static class AccountCommand implements CommandHandler { @Override @@ -124,8 +195,8 @@ public final class ServerCommands { } @Command(label = "permission", - usage = "Usage: permission ", - execution = Command.Execution.CONSOLE) + usage = "permission ", + description = "Grants or removes a permission for a user", permission = "*") public static class PermissionCommand implements CommandHandler { @Override @@ -164,50 +235,93 @@ public final class ServerCommands { } @Command(label = "help", - usage = "Usage: help [command]") + usage = "help [command]", description = "Sends the help message or shows information about a specified command") public static class HelpCommand implements CommandHandler { @Override public void execute(List args) { - List handlers = CommandMap.getInstance().getHandlers(); - List annotations = handlers.stream() - .map(handler -> handler.getClass().getAnnotation(Command.class)) - .collect(Collectors.toList()); - - if(args.size() < 1) { - StringBuilder builder = new StringBuilder("Available commands:\n"); - annotations.forEach(annotation -> builder.append(annotation.usage()).append("\n")); - CommandHandler.sendMessage(null, builder.toString()); - } else { - String command = args.get(0); - CommandHandler handler = CommandMap.getInstance().getHandler(command); - if(handler == null) { - CommandHandler.sendMessage(null, "Command not found."); return; - } - - Command annotation = handler.getClass().getAnnotation(Command.class); - CommandHandler.sendMessage(null, annotation.usage()); - } + this.execute(null, args); } @Override public void execute(GenshinPlayer player, List args) { - List handlers = CommandMap.getInstance().getHandlers(); - List annotations = handlers.stream() - .map(handler -> handler.getClass().getAnnotation(Command.class)) - .collect(Collectors.toList()); - if(args.size() < 1) { - annotations.forEach(annotation -> player.dropMessage(annotation.usage())); + HashMap handlers = CommandMap.getInstance().getHandlers(); + List annotations = new ArrayList(); + for(String key : handlers.keySet()) { + Command annotation = handlers.get(key).getClass().getAnnotation(Command.class); + + if(!Arrays.asList(annotation.aliases()).contains(key)) { + if(player != null && annotation.permission() != "" && !player.getAccount().hasPermission(annotation.permission())) continue; + annotations.add(annotation); + } + } + + + SendAllHelpMessage(player, annotations); } else { String command = args.get(0); CommandHandler handler = CommandMap.getInstance().getHandler(command); + StringBuilder builder = new StringBuilder(player == null ? "\nHelp - " : "Help - ").append(command).append(": \n"); if(handler == null) { - CommandHandler.sendMessage(player, "Command not found."); return; + builder.append("No command found."); + } else { + Command annotation = handler.getClass().getAnnotation(Command.class); + + builder.append(" ").append(annotation.description()).append("\n"); + builder.append(" Usage: ").append(annotation.usage()); + if(annotation.aliases().length >= 1) { + builder.append("\n").append(" Aliases: "); + for (String alias : annotation.aliases()) { + builder.append(alias).append(" "); + } + } + if(player != null && annotation.permission() != "" && !player.getAccount().hasPermission(annotation.permission())) { + builder.append("\n Warning: You do not have permission to run this command."); + } } - Command annotation = handler.getClass().getAnnotation(Command.class); - CommandHandler.sendMessage(player, annotation.usage()); + CommandHandler.sendMessage(player, builder.toString()); + } + } + + void SendAllHelpMessage(GenshinPlayer player, List annotations) { + if(player == null) { + StringBuilder builder = new StringBuilder("\nAvailable commands:\n"); + annotations.forEach(annotation -> { + if (annotation.execution() != (player == null ? Command.Execution.PLAYER : Command.Execution.CONSOLE)) { + builder.append(annotation.label()).append("\n"); + builder.append(" ").append(annotation.description()).append("\n"); + builder.append(" Usage: ").append(annotation.usage()); + if (annotation.aliases().length >= 1) { + builder.append("\n").append(" Aliases: "); + for (String alias : annotation.aliases()) { + builder.append(alias).append(" "); + } + } + + builder.append("\n"); + } + }); + + CommandHandler.sendMessage(null, builder.toString()); + } else { + CommandHandler.sendMessage(player, "Available commands:"); + annotations.forEach(annotation -> { + if (annotation.execution() != (player == null ? Command.Execution.PLAYER : Command.Execution.CONSOLE)) { + StringBuilder builder = new StringBuilder(annotation.label()).append("\n"); + builder.append(" ").append(annotation.description()).append("\n"); + builder.append(" Usage: ").append(annotation.usage()); + if (annotation.aliases().length >= 1) { + builder.append("\n").append(" Aliases: "); + for (String alias : annotation.aliases()) { + builder.append(alias).append(" "); + } + } + + CommandHandler.sendMessage(player, builder.toString()); + } + }); } } } diff --git a/src/main/java/emu/grasscutter/game/Account.java b/src/main/java/emu/grasscutter/game/Account.java index dffbb0d86..2eeeed3b1 100644 --- a/src/main/java/emu/grasscutter/game/Account.java +++ b/src/main/java/emu/grasscutter/game/Account.java @@ -105,6 +105,10 @@ public class Account { if(this.permissions.contains(permission)) return false; this.permissions.add(permission); return true; } + + public boolean hasPermission(String permission) { + return this.permissions.contains(permission) || this.permissions.contains("*") ? true : false; + } public boolean removePermission(String permission) { return this.permissions.remove(permission); From dfc956a19f5424e46688a2e11fc05de2a79e63e1 Mon Sep 17 00:00:00 2001 From: Benjamin Elsdon Date: Tue, 19 Apr 2022 18:55:49 +0800 Subject: [PATCH 2/3] Modified the new commands to be compliant with my changes --- .../java/emu/grasscutter/commands/PlayerCommands.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/java/emu/grasscutter/commands/PlayerCommands.java b/src/main/java/emu/grasscutter/commands/PlayerCommands.java index ebe72e6ae..f7135dac9 100644 --- a/src/main/java/emu/grasscutter/commands/PlayerCommands.java +++ b/src/main/java/emu/grasscutter/commands/PlayerCommands.java @@ -180,7 +180,8 @@ public final class PlayerCommands { } } - @Command(label = "givechar", aliases = { "givec" }, usage = "Usage: givechar [level]") + @Command(label = "givechar", aliases = { "givec" }, usage = "givechar [level]", + description = "Gives the player a specified character", permission = "player.givechar") public static class GiveCharCommand implements CommandHandler { @Override public void execute(GenshinPlayer player, List args) { int target, avatarID, level = 1, ascension = 1; @@ -435,8 +436,9 @@ public final class PlayerCommands { } } - @Command(label = "setworldlevel", aliases = {"setworldlvl"}, - usage = "Usage: setworldlevel ", execution = Command.Execution.PLAYER) + @Command(label = "setworldlevel", aliases = {"setworldlvl"}, usage = "setworldlevel ", + description = "Sets your world level (Relog to see proper effects)", permission = "player.setworldlevel", + execution = Command.Execution.PLAYER) public static class SetWorldLevelCommand implements CommandHandler { @Override public void execute(GenshinPlayer player, List args) { From f54cfc6c7543d3a87e450e7e7c253b099ec09462 Mon Sep 17 00:00:00 2001 From: Benjamin Elsdon Date: Tue, 19 Apr 2022 20:36:15 +0800 Subject: [PATCH 3/3] Fixed an issue with kicking players (Still broken but now it actually disconnects the player) --- .../grasscutter/commands/ServerCommands.java | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/main/java/emu/grasscutter/commands/ServerCommands.java b/src/main/java/emu/grasscutter/commands/ServerCommands.java index 67c33338c..a4014be57 100644 --- a/src/main/java/emu/grasscutter/commands/ServerCommands.java +++ b/src/main/java/emu/grasscutter/commands/ServerCommands.java @@ -40,16 +40,26 @@ public final class ServerCommands { public static class KickCommand implements CommandHandler { @Override public void execute(List args) { + this.execute(null, args); + } + + @Override + public void execute(GenshinPlayer player, List args) { int target = Integer.parseInt(args.get(0)); String message = String.join(" ", args.subList(1, args.size())); - GenshinPlayer targetPlayer = Grasscutter.getGameServer().getPlayerById(target); + GenshinPlayer targetPlayer = Grasscutter.getGameServer().getPlayerByUid(target); if(targetPlayer == null) { - CommandHandler.sendMessage(null, "Player not found."); return; + CommandHandler.sendMessage(player, "Player not found."); + return; + } + if(player != null) { + CommandHandler.sendMessage(null, String.format("Player [%s:%s] has kicked player [%s:%s]", player.getAccount().getPlayerId(), player.getAccount().getUsername(), target, targetPlayer.getAccount().getUsername())); } - targetPlayer.sendPacket(new PacketSceneKickPlayerRsp(targetPlayer.getId())); - //targetPlayer.getSession().close(); + CommandHandler.sendMessage(player, String.format("Kicking player [%s:%s]", target, targetPlayer.getAccount().getUsername())); + + targetPlayer.getSession().close(); } }