From ce51e9d6c358fd618ab3ea63d87af310e961a70b Mon Sep 17 00:00:00 2001 From: KingRainbow44 Date: Mon, 18 Apr 2022 18:24:08 -0400 Subject: [PATCH 01/15] Implement new command system --- .../grasscutter/commands/PlayerCommands.java | 307 ++++++++++++++++++ .../grasscutter/commands/ServerCommands.java | 0 .../java/emu/grasscutter/Grasscutter.java | 15 +- .../emu/grasscutter/commands/Command.java | 4 +- .../grasscutter/commands/CommandHandler.java | 28 ++ .../emu/grasscutter/commands/CommandMap.java | 87 +++++ .../game/managers/ChatManager.java | 8 +- .../grasscutter/server/game/GameServer.java | 24 +- 8 files changed, 453 insertions(+), 20 deletions(-) create mode 100644 src/deprecated/java/emu/grasscutter/commands/PlayerCommands.java rename src/{main => deprecated}/java/emu/grasscutter/commands/ServerCommands.java (100%) create mode 100644 src/main/java/emu/grasscutter/commands/CommandHandler.java create mode 100644 src/main/java/emu/grasscutter/commands/CommandMap.java diff --git a/src/deprecated/java/emu/grasscutter/commands/PlayerCommands.java b/src/deprecated/java/emu/grasscutter/commands/PlayerCommands.java new file mode 100644 index 000000000..2e8be354d --- /dev/null +++ b/src/deprecated/java/emu/grasscutter/commands/PlayerCommands.java @@ -0,0 +1,307 @@ +package emu.grasscutter.commands; + +import java.lang.reflect.Modifier; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; + +import emu.grasscutter.data.GenshinData; +import emu.grasscutter.data.def.ItemData; +import emu.grasscutter.data.def.MonsterData; +import emu.grasscutter.game.GenshinPlayer; +import emu.grasscutter.game.avatar.GenshinAvatar; +import emu.grasscutter.game.entity.EntityAvatar; +import emu.grasscutter.game.entity.EntityItem; +import emu.grasscutter.game.entity.EntityMonster; +import emu.grasscutter.game.entity.GenshinEntity; +import emu.grasscutter.game.inventory.GenshinItem; +import emu.grasscutter.game.inventory.ItemType; +import emu.grasscutter.game.props.ActionReason; +import emu.grasscutter.game.props.FightProperty; +import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify; +import emu.grasscutter.server.packet.send.PacketItemAddHintNotify; +import emu.grasscutter.utils.Position; + +public class PlayerCommands { + private static HashMap list = new HashMap<>(); + + static { + try { + // Look for classes + for (Class cls : PlayerCommands.class.getDeclaredClasses()) { + // Get non abstract classes + if (!Modifier.isAbstract(cls.getModifiers())) { + Command commandAnnotation = cls.getAnnotation(Command.class); + PlayerCommand command = (PlayerCommand) cls.newInstance(); + + if (commandAnnotation != null) { + command.setLevel(commandAnnotation.gmLevel()); + for (String alias : commandAnnotation.aliases()) { + if (alias.length() == 0) { + continue; + } + + String commandName = "!" + alias; + list.put(commandName, command); + commandName = "/" + alias; + list.put(commandName, command); + } + } + + String commandName = "!" + cls.getSimpleName().toLowerCase(); + list.put(commandName, command); + commandName = "/" + cls.getSimpleName().toLowerCase(); + list.put(commandName, command); + } + + } + } catch (Exception e) { + + } + } + + public static void handle(GenshinPlayer player, String msg) { + String[] split = msg.split(" "); + + // End if invalid + if (split.length == 0) { + return; + } + + // + String first = split[0].toLowerCase(); + PlayerCommand c = PlayerCommands.list.get(first); + + if (c != null) { + // Level check + if (player.getGmLevel() < c.getLevel()) { + return; + } + // Execute + int len = Math.min(first.length() + 1, msg.length()); + c.execute(player, msg.substring(len)); + } + } + + public static abstract class PlayerCommand { + // GM level required to use this command + private int level; + protected int getLevel() { return this.level; } + protected void setLevel(int minLevel) { this.level = minLevel; } + + // Main + public abstract void execute(GenshinPlayer player, String raw); + } + + // ================ Commands ================ + + @Command(aliases = {"g", "item", "additem"}, helpText = "/give [item id] [count] - Gives {count} amount of {item id}") + public static class Give extends PlayerCommand { + @Override + public void execute(GenshinPlayer player, String raw) { + String[] split = raw.split(" "); + int itemId = 0, count = 1; + + try { + itemId = Integer.parseInt(split[0]); + } catch (Exception e) { + itemId = 0; + } + + try { + count = Math.max(Math.min(Integer.parseInt(split[1]), Integer.MAX_VALUE), 1); + } catch (Exception e) { + count = 1; + } + + // Give + ItemData itemData = GenshinData.getItemDataMap().get(itemId); + GenshinItem item; + + if (itemData == null) { + player.dropMessage("Error: Item data not found"); + return; + } + + if (itemData.isEquip()) { + List items = new LinkedList<>(); + for (int i = 0; i < count; i++) { + item = new GenshinItem(itemData); + items.add(item); + } + player.getInventory().addItems(items); + player.sendPacket(new PacketItemAddHintNotify(items, ActionReason.SubfieldDrop)); + } else { + item = new GenshinItem(itemData, count); + player.getInventory().addItem(item); + player.sendPacket(new PacketItemAddHintNotify(item, ActionReason.SubfieldDrop)); + } + } + } + + @Command(aliases = {"d"}, helpText = "/drop [item id] [count] - Drops {count} amount of {item id}") + public static class Drop extends PlayerCommand { + @Override + public void execute(GenshinPlayer player, String raw) { + String[] split = raw.split(" "); + int itemId = 0, count = 1; + + try { + itemId = Integer.parseInt(split[0]); + } catch (Exception e) { + itemId = 0; + } + + try { + count = Math.max(Math.min(Integer.parseInt(split[1]), Integer.MAX_VALUE), 1); + } catch (Exception e) { + count = 1; + } + + // Give + ItemData itemData = GenshinData.getItemDataMap().get(itemId); + + if (itemData == null) { + player.dropMessage("Error: Item data not found"); + return; + } + + if (itemData.isEquip()) { + float range = (5f + (.1f * count)); + for (int i = 0; i < count; i++) { + Position pos = player.getPos().clone().addX((float) (Math.random() * range) - (range / 2)).addY(3f).addZ((float) (Math.random() * range) - (range / 2)); + EntityItem entity = new EntityItem(player.getWorld(), player, itemData, pos, 1); + player.getWorld().addEntity(entity); + } + } else { + EntityItem entity = new EntityItem(player.getWorld(), player, itemData, player.getPos().clone().addY(3f), count); + player.getWorld().addEntity(entity); + } + } + } + + @Command(helpText = "/spawn [monster id] [count] - Creates {count} amount of {item id}") + public static class Spawn extends PlayerCommand { + @Override + public void execute(GenshinPlayer player, String raw) { + String[] split = raw.split(" "); + int monsterId = 0, count = 1, level = 1; + + try { + monsterId = Integer.parseInt(split[0]); + } catch (Exception e) { + monsterId = 0; + } + + try { + level = Math.max(Math.min(Integer.parseInt(split[1]), 200), 1); + } catch (Exception e) { + level = 1; + } + + try { + count = Math.max(Math.min(Integer.parseInt(split[2]), 1000), 1); + } catch (Exception e) { + count = 1; + } + + // Give + MonsterData monsterData = GenshinData.getMonsterDataMap().get(monsterId); + + if (monsterData == null) { + player.dropMessage("Error: Monster data not found"); + return; + } + + float range = (5f + (.1f * count)); + for (int i = 0; i < count; i++) { + Position pos = player.getPos().clone().addX((float) (Math.random() * range) - (range / 2)).addY(3f).addZ((float) (Math.random() * range) - (range / 2)); + EntityMonster entity = new EntityMonster(player.getWorld(), monsterData, pos, level); + player.getWorld().addEntity(entity); + } + } + } + + @Command(helpText = "/killall") + public static class KillAll extends PlayerCommand { + @Override + public void execute(GenshinPlayer player, String raw) { + List toRemove = new LinkedList<>(); + for (GenshinEntity entity : player.getWorld().getEntities().values()) { + if (entity instanceof EntityMonster) { + toRemove.add(entity); + } + } + toRemove.forEach(e -> player.getWorld().killEntity(e, 0)); + } + } + + @Command(helpText = "/resetconst - Resets all constellations for the currently active character") + public static class ResetConst extends PlayerCommand { + @Override + public void execute(GenshinPlayer player, String raw) { + EntityAvatar entity = player.getTeamManager().getCurrentAvatarEntity(); + + if (entity == null) { + return; + } + + GenshinAvatar avatar = entity.getAvatar(); + + avatar.getTalentIdList().clear(); + avatar.setCoreProudSkillLevel(0); + avatar.recalcStats(); + avatar.save(); + + player.dropMessage("Constellations for " + entity.getAvatar().getAvatarData().getName() + " have been reset. Please relogin to see changes."); + } + } + + @Command(helpText = "/godmode - Prevents you from taking damage") + public static class Godmode extends PlayerCommand { + @Override + public void execute(GenshinPlayer player, String raw) { + player.setGodmode(!player.hasGodmode()); + player.dropMessage("Godmode is now " + (player.hasGodmode() ? "ON" : "OFF")); + } + } + + @Command(helpText = "/sethp [hp]") + public static class Sethp extends PlayerCommand { + @Override + public void execute(GenshinPlayer player, String raw) { + String[] split = raw.split(" "); + int hp = 0; + + try { + hp = Math.max(Integer.parseInt(split[0]), 1); + } catch (Exception e) { + hp = 1; + } + + EntityAvatar entity = player.getTeamManager().getCurrentAvatarEntity(); + + if (entity == null) { + return; + } + + entity.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, hp); + entity.getWorld().broadcastPacket(new PacketEntityFightPropUpdateNotify(entity, FightProperty.FIGHT_PROP_CUR_HP)); + } + } + + @Command(aliases = {"clearart"}, helpText = "/clearartifacts") + public static class ClearArtifacts extends PlayerCommand { + @Override + public void execute(GenshinPlayer player, String raw) { + List toRemove = new LinkedList<>(); + for (GenshinItem item : player.getInventory().getItems().values()) { + if (item.getItemType() == ItemType.ITEM_RELIQUARY && item.getLevel() == 1 && item.getExp() == 0 && !item.isLocked() && !item.isEquipped()) { + toRemove.add(item); + } + } + + player.getInventory().removeItems(toRemove); + } + } +} diff --git a/src/main/java/emu/grasscutter/commands/ServerCommands.java b/src/deprecated/java/emu/grasscutter/commands/ServerCommands.java similarity index 100% rename from src/main/java/emu/grasscutter/commands/ServerCommands.java rename to src/deprecated/java/emu/grasscutter/commands/ServerCommands.java diff --git a/src/main/java/emu/grasscutter/Grasscutter.java b/src/main/java/emu/grasscutter/Grasscutter.java index f0fa2cc89..2c247f8bb 100644 --- a/src/main/java/emu/grasscutter/Grasscutter.java +++ b/src/main/java/emu/grasscutter/Grasscutter.java @@ -7,7 +7,9 @@ import java.io.FileWriter; import java.io.InputStreamReader; import java.net.InetSocketAddress; +import emu.grasscutter.commands.CommandMap; import emu.grasscutter.utils.Utils; +import org.reflections.Reflections; import org.slf4j.LoggerFactory; import com.google.gson.Gson; @@ -23,10 +25,6 @@ import emu.grasscutter.tools.Tools; import emu.grasscutter.utils.Crypto; public final class Grasscutter { - static { - System.setProperty("logback.configurationFile", "src/main/resources/logback.xml"); - } - private static final Logger log = (Logger) LoggerFactory.getLogger(Grasscutter.class); private static Config config; @@ -37,8 +35,13 @@ public final class Grasscutter { private static DispatchServer dispatchServer; private static GameServer gameServer; + public static final Reflections reflector = new Reflections(); + static { - // Load configuration. + // Declare logback configuration. + System.setProperty("logback.configurationFile", "src/main/resources/logback.xml"); + + // Load server configuration. Grasscutter.loadConfig(); // Check server structure. Utils.startupCheck(); @@ -100,7 +103,7 @@ public final class Grasscutter { String input; try (BufferedReader br = new BufferedReader(new InputStreamReader(System.in))) { while ((input = br.readLine()) != null) { - ServerCommands.handle(input); + CommandMap.getInstance().invoke(null, input); } } catch (Exception e) { Grasscutter.getLogger().error("An error occurred.", e); diff --git a/src/main/java/emu/grasscutter/commands/Command.java b/src/main/java/emu/grasscutter/commands/Command.java index aa88826dd..7147e6b6b 100644 --- a/src/main/java/emu/grasscutter/commands/Command.java +++ b/src/main/java/emu/grasscutter/commands/Command.java @@ -5,9 +5,11 @@ import java.lang.annotation.RetentionPolicy; @Retention(RetentionPolicy.RUNTIME) public @interface Command { + String label() default ""; + String[] aliases() default ""; int gmLevel() default 1; - String helpText() default ""; + String usage() default ""; } diff --git a/src/main/java/emu/grasscutter/commands/CommandHandler.java b/src/main/java/emu/grasscutter/commands/CommandHandler.java new file mode 100644 index 000000000..a1f58a539 --- /dev/null +++ b/src/main/java/emu/grasscutter/commands/CommandHandler.java @@ -0,0 +1,28 @@ +package emu.grasscutter.commands; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.game.GenshinPlayer; + +import java.util.List; + +public interface CommandHandler { + /* Invoked on player execution. */ + void execute(GenshinPlayer player, List args); + /* Invoked on server execution. */ + void execute(List args); + + /* + * Utilities. + */ + + /** + * Send a message to the target. + * @param player The player to send the message to, or null for the server console. + * @param message The message to send. + */ + static void sendMessage(GenshinPlayer player, String message) { + if(player == null) { + Grasscutter.getLogger().info(message); + } else player.dropMessage(message); + } +} diff --git a/src/main/java/emu/grasscutter/commands/CommandMap.java b/src/main/java/emu/grasscutter/commands/CommandMap.java new file mode 100644 index 000000000..b139e2266 --- /dev/null +++ b/src/main/java/emu/grasscutter/commands/CommandMap.java @@ -0,0 +1,87 @@ +package emu.grasscutter.commands; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.game.GenshinPlayer; +import org.reflections.Reflections; + +import java.util.*; + +@SuppressWarnings("UnusedReturnValue") +public final class CommandMap { + public static CommandMap getInstance() { + return Grasscutter.getGameServer().getCommandMap(); + } + + private final Map commands = new HashMap<>(); + + /** + * Register a command handler. + * @param label The command label. + * @param command The command handler. + * @return Instance chaining. + */ + public CommandMap registerCommand(String label, CommandHandler command) { + this.commands.put(label, command); return this; + } + + /** + * Removes a registered command handler. + * @param label The command label. + * @return Instance chaining. + */ + public CommandMap unregisterCommand(String label) { + this.commands.remove(label); return this; + } + + /** + * Invoke a command handler with the given arguments. + * @param player The player invoking the command or null for the server console. + * @param rawMessage The messaged used to invoke the command. + */ + public void invoke(GenshinPlayer player, String rawMessage) { + // Remove prefix if present. + if(!Character.isLetter(rawMessage.charAt(0))) + rawMessage = rawMessage.substring(1); + + // Parse message. + String[] split = rawMessage.split(" "); + List args = Arrays.asList(split); + String label = args.remove(0); + + // Get command handler. + CommandHandler handler = this.commands.get(label); + if(handler == null) { + CommandHandler.sendMessage(player, "Unknown command: " + label); return; + } + + // Invoke execute method for handler. + if(player == null) + handler.execute(args); + else handler.execute(player, args); + } + + public CommandMap() { + this(false); + } + + public CommandMap(boolean scan) { + if(scan) this.scan(); + } + + /** + * Scans for all classes annotated with {@link Command} and registers them. + */ + private void scan() { + Reflections reflector = Grasscutter.reflector; + Set classes = reflector.getTypesAnnotatedWith(Command.class); + classes.forEach(annotated -> { + try { + Class cls = annotated.getClass(); + Command cmdData = cls.getAnnotation(Command.class); + Object object = cls.getDeclaredConstructors()[0].newInstance(); + if (object instanceof CommandHandler) + this.registerCommand(cmdData.label(), (CommandHandler) object); + } catch (Exception ignored) { } + }); + } +} diff --git a/src/main/java/emu/grasscutter/game/managers/ChatManager.java b/src/main/java/emu/grasscutter/game/managers/ChatManager.java index d2d1acac4..bd5a8dcc1 100644 --- a/src/main/java/emu/grasscutter/game/managers/ChatManager.java +++ b/src/main/java/emu/grasscutter/game/managers/ChatManager.java @@ -1,6 +1,6 @@ package emu.grasscutter.game.managers; -import emu.grasscutter.Grasscutter; +import emu.grasscutter.commands.CommandMap; import emu.grasscutter.commands.PlayerCommands; import emu.grasscutter.game.GenshinPlayer; import emu.grasscutter.net.packet.GenshinPacket; @@ -26,8 +26,8 @@ public class ChatManager { } // Check if command - if (message.charAt(0) == '!' || message.charAt(0) == '/') { - PlayerCommands.handle(player, message); + if (message.charAt(0) == '!') { + CommandMap.getInstance().invoke(player, message); return; } @@ -68,7 +68,7 @@ public class ChatManager { // Check if command if (message.charAt(0) == '!') { - PlayerCommands.handle(player, message); + CommandMap.getInstance().invoke(player, message); return; } diff --git a/src/main/java/emu/grasscutter/server/game/GameServer.java b/src/main/java/emu/grasscutter/server/game/GameServer.java index 1baf3253d..263c0cc72 100644 --- a/src/main/java/emu/grasscutter/server/game/GameServer.java +++ b/src/main/java/emu/grasscutter/server/game/GameServer.java @@ -11,6 +11,7 @@ import java.util.concurrent.ConcurrentHashMap; import emu.grasscutter.GenshinConstants; import emu.grasscutter.Grasscutter; +import emu.grasscutter.commands.CommandMap; import emu.grasscutter.database.DatabaseHelper; import emu.grasscutter.game.GenshinPlayer; import emu.grasscutter.game.dungeons.DungeonManager; @@ -23,11 +24,10 @@ import emu.grasscutter.net.packet.PacketHandler; import emu.grasscutter.net.proto.SocialDetailOuterClass.SocialDetail; import emu.grasscutter.netty.MihoyoKcpServer; -public class GameServer extends MihoyoKcpServer { +public final class GameServer extends MihoyoKcpServer { private final InetSocketAddress address; private final GameServerPacketHandler packetHandler; - private final Timer gameLoop; - + private final Map players; private final ChatManager chatManager; @@ -36,9 +36,11 @@ public class GameServer extends MihoyoKcpServer { private final ShopManager shopManager; private final MultiplayerManager multiplayerManager; private final DungeonManager dungeonManager; + private final CommandMap commandMap; public GameServer(InetSocketAddress address) { super(address); + this.setServerInitializer(new GameServerInitializer(this)); this.address = address; this.packetHandler = new GameServerPacketHandler(PacketHandler.class); @@ -50,22 +52,22 @@ public class GameServer extends MihoyoKcpServer { this.shopManager = new ShopManager(this); this.multiplayerManager = new MultiplayerManager(this); this.dungeonManager = new DungeonManager(this); + this.commandMap = new CommandMap(true); - // Ticker - this.gameLoop = new Timer(); - this.gameLoop.scheduleAtFixedRate(new TimerTask() { + // Schedule game loop. + Timer gameLoop = new Timer(); + gameLoop.scheduleAtFixedRate(new TimerTask() { @Override public void run() { try { onTick(); } catch (Exception e) { - // TODO Auto-generated catch block - e.printStackTrace(); + Grasscutter.getLogger().error("An error occurred during game update.", e); } } }, new Date(), 1000L); - // Shutdown hook + // Hook into shutdown event. Runtime.getRuntime().addShutdownHook(new Thread(this::onServerShutdown)); } @@ -101,6 +103,10 @@ public class GameServer extends MihoyoKcpServer { return dungeonManager; } + public CommandMap getCommandMap() { + return this.commandMap; + } + public void registerPlayer(GenshinPlayer player) { getPlayers().put(player.getId(), player); } From 4f7f3580db29f3a5c37da9a4451e02d73b329287 Mon Sep 17 00:00:00 2001 From: KingRainbow44 Date: Mon, 18 Apr 2022 20:11:23 -0400 Subject: [PATCH 02/15] Fix registering error & implement handling --- .../java/emu/grasscutter/Grasscutter.java | 7 +++- .../emu/grasscutter/commands/Command.java | 2 +- .../emu/grasscutter/commands/CommandMap.java | 41 +++++++++++++++---- .../game/managers/ChatManager.java | 1 - 4 files changed, 39 insertions(+), 12 deletions(-) diff --git a/src/main/java/emu/grasscutter/Grasscutter.java b/src/main/java/emu/grasscutter/Grasscutter.java index 2c247f8bb..83afe274c 100644 --- a/src/main/java/emu/grasscutter/Grasscutter.java +++ b/src/main/java/emu/grasscutter/Grasscutter.java @@ -16,7 +16,6 @@ import com.google.gson.Gson; import com.google.gson.GsonBuilder; import ch.qos.logback.classic.Logger; -import emu.grasscutter.commands.ServerCommands; import emu.grasscutter.data.ResourceLoader; import emu.grasscutter.database.DatabaseManager; import emu.grasscutter.server.dispatch.DispatchServer; @@ -103,7 +102,11 @@ public final class Grasscutter { String input; try (BufferedReader br = new BufferedReader(new InputStreamReader(System.in))) { while ((input = br.readLine()) != null) { - CommandMap.getInstance().invoke(null, input); + try { + CommandMap.getInstance().invoke(null, input); + } catch (Exception e) { + Grasscutter.getLogger().error("Command error: " + e.getMessage()); + } } } catch (Exception e) { Grasscutter.getLogger().error("An error occurred.", e); diff --git a/src/main/java/emu/grasscutter/commands/Command.java b/src/main/java/emu/grasscutter/commands/Command.java index 7147e6b6b..d33ed70d6 100644 --- a/src/main/java/emu/grasscutter/commands/Command.java +++ b/src/main/java/emu/grasscutter/commands/Command.java @@ -7,7 +7,7 @@ import java.lang.annotation.RetentionPolicy; public @interface Command { String label() default ""; - String[] aliases() default ""; + String[] aliases() default {""}; int gmLevel() default 1; diff --git a/src/main/java/emu/grasscutter/commands/CommandMap.java b/src/main/java/emu/grasscutter/commands/CommandMap.java index b139e2266..621419b04 100644 --- a/src/main/java/emu/grasscutter/commands/CommandMap.java +++ b/src/main/java/emu/grasscutter/commands/CommandMap.java @@ -21,7 +21,15 @@ public final class CommandMap { * @return Instance chaining. */ public CommandMap registerCommand(String label, CommandHandler command) { - this.commands.put(label, command); return this; + Grasscutter.getLogger().debug("Registered command: " + label); + + Command annotation = command.getClass().getAnnotation(Command.class); + if(annotation.aliases().length > 0) { + for (String alias : annotation.aliases()) + this.commands.put(alias, command); + } this.commands.put(label, command); + + return this; } /** @@ -30,7 +38,17 @@ public final class CommandMap { * @return Instance chaining. */ public CommandMap unregisterCommand(String label) { - this.commands.remove(label); return this; + Grasscutter.getLogger().debug("Unregistered command: " + label); + CommandHandler handler = this.commands.get(label); + if(handler == null) return this; + + Command annotation = handler.getClass().getAnnotation(Command.class); + if(annotation.aliases().length > 0) { + for (String alias : annotation.aliases()) + this.commands.remove(alias); + } this.commands.remove(label); + + return this; } /** @@ -39,13 +57,18 @@ public final class CommandMap { * @param rawMessage The messaged used to invoke the command. */ public void invoke(GenshinPlayer player, String rawMessage) { + rawMessage = rawMessage.trim(); + if(rawMessage.length() == 0) { + CommandHandler.sendMessage(player, "No command specified."); + } + // Remove prefix if present. if(!Character.isLetter(rawMessage.charAt(0))) rawMessage = rawMessage.substring(1); // Parse message. String[] split = rawMessage.split(" "); - List args = Arrays.asList(split); + List args = new LinkedList<>(Arrays.asList(split)); String label = args.remove(0); // Get command handler. @@ -73,15 +96,17 @@ public final class CommandMap { */ private void scan() { Reflections reflector = Grasscutter.reflector; - Set classes = reflector.getTypesAnnotatedWith(Command.class); + Set> classes = reflector.getTypesAnnotatedWith(Command.class); classes.forEach(annotated -> { try { - Class cls = annotated.getClass(); - Command cmdData = cls.getAnnotation(Command.class); - Object object = cls.getDeclaredConstructors()[0].newInstance(); + Command cmdData = annotated.getAnnotation(Command.class); + Object object = annotated.newInstance(); if (object instanceof CommandHandler) this.registerCommand(cmdData.label(), (CommandHandler) object); - } catch (Exception ignored) { } + else Grasscutter.getLogger().error("Class " + annotated.getName() + " is not a CommandHandler!"); + } catch (Exception exception) { + Grasscutter.getLogger().error("Failed to register command handler for " + annotated.getSimpleName(), exception); + } }); } } diff --git a/src/main/java/emu/grasscutter/game/managers/ChatManager.java b/src/main/java/emu/grasscutter/game/managers/ChatManager.java index bd5a8dcc1..64729cb37 100644 --- a/src/main/java/emu/grasscutter/game/managers/ChatManager.java +++ b/src/main/java/emu/grasscutter/game/managers/ChatManager.java @@ -1,7 +1,6 @@ package emu.grasscutter.game.managers; import emu.grasscutter.commands.CommandMap; -import emu.grasscutter.commands.PlayerCommands; import emu.grasscutter.game.GenshinPlayer; import emu.grasscutter.net.packet.GenshinPacket; import emu.grasscutter.server.game.GameServer; From 1028e48e4950947f07d1ae0a295277765e64e955 Mon Sep 17 00:00:00 2001 From: KingRainbow44 Date: Mon, 18 Apr 2022 22:09:51 -0400 Subject: [PATCH 03/15] Execution power --- .../emu/grasscutter/commands/Command.java | 10 ++++- .../grasscutter/commands/CommandHandler.java | 4 +- .../emu/grasscutter/commands/CommandMap.java | 37 ++++++++++++++----- 3 files changed, 39 insertions(+), 12 deletions(-) diff --git a/src/main/java/emu/grasscutter/commands/Command.java b/src/main/java/emu/grasscutter/commands/Command.java index d33ed70d6..3f739d78e 100644 --- a/src/main/java/emu/grasscutter/commands/Command.java +++ b/src/main/java/emu/grasscutter/commands/Command.java @@ -6,10 +6,18 @@ import java.lang.annotation.RetentionPolicy; @Retention(RetentionPolicy.RUNTIME) public @interface Command { String label() default ""; + + String usage() default ""; String[] aliases() default {""}; + Execution execution() default Execution.ALL; + int gmLevel() default 1; - String usage() default ""; + enum Execution { + ALL, + CONSOLE, + PLAYER + } } diff --git a/src/main/java/emu/grasscutter/commands/CommandHandler.java b/src/main/java/emu/grasscutter/commands/CommandHandler.java index a1f58a539..97bd8c81f 100644 --- a/src/main/java/emu/grasscutter/commands/CommandHandler.java +++ b/src/main/java/emu/grasscutter/commands/CommandHandler.java @@ -7,9 +7,9 @@ import java.util.List; public interface CommandHandler { /* Invoked on player execution. */ - void execute(GenshinPlayer player, List args); + default void execute(GenshinPlayer player, List args) { } /* Invoked on server execution. */ - void execute(List args); + default void execute(List args) { } /* * Utilities. diff --git a/src/main/java/emu/grasscutter/commands/CommandMap.java b/src/main/java/emu/grasscutter/commands/CommandMap.java index 621419b04..aa9f47f54 100644 --- a/src/main/java/emu/grasscutter/commands/CommandMap.java +++ b/src/main/java/emu/grasscutter/commands/CommandMap.java @@ -13,6 +13,7 @@ public final class CommandMap { } private final Map commands = new HashMap<>(); + private final Map executionPower = new HashMap<>(); /** * Register a command handler. @@ -23,13 +24,18 @@ public final class CommandMap { public CommandMap registerCommand(String label, CommandHandler command) { Grasscutter.getLogger().debug("Registered command: " + label); + // Get command data. Command annotation = command.getClass().getAnnotation(Command.class); - if(annotation.aliases().length > 0) { - for (String alias : annotation.aliases()) - this.commands.put(alias, command); - } this.commands.put(label, command); + this.executionPower.put(label, annotation.execution()); + this.commands.put(label, command); - return this; + // Register aliases. + if(annotation.aliases().length > 0) { + for (String alias : annotation.aliases()) { + this.commands.put(alias, command); + this.executionPower.put(alias, annotation.execution()); + } + } return this; } /** @@ -43,10 +49,16 @@ public final class CommandMap { if(handler == null) return this; Command annotation = handler.getClass().getAnnotation(Command.class); + this.executionPower.remove(label); + this.commands.remove(label); + + // Unregister aliases. if(annotation.aliases().length > 0) { - for (String alias : annotation.aliases()) + for (String alias : annotation.aliases()) { this.commands.remove(alias); - } this.commands.remove(label); + this.executionPower.remove(alias); + } + } return this; } @@ -77,9 +89,16 @@ public final class CommandMap { CommandHandler.sendMessage(player, "Unknown command: " + label); return; } + // Execution power check. + Command.Execution executionPower = this.executionPower.get(label); + if(player == null && executionPower == Command.Execution.PLAYER) { + CommandHandler.sendMessage(null, "Run this command in-game."); return; + } else if (player != null && executionPower == Command.Execution.CONSOLE) { + CommandHandler.sendMessage(player, "This command can only be run from the console."); return; + } + // Invoke execute method for handler. - if(player == null) - handler.execute(args); + if(player == null) handler.execute(args); else handler.execute(player, args); } From a0e26352a6f812a2d846a602f5e2fc3a9cf8c729 Mon Sep 17 00:00:00 2001 From: KingRainbow44 Date: Mon, 18 Apr 2022 22:10:41 -0400 Subject: [PATCH 04/15] Add existing commands --- .../grasscutter/commands/PlayerCommands.java | 586 +++++++++--------- .../grasscutter/commands/ServerCommands.java | 121 ++++ .../emu/grasscutter/game/GenshinPlayer.java | 11 +- src/main/java/emu/grasscutter/game/World.java | 2 +- .../java/emu/grasscutter/utils/Utils.java | 10 + 5 files changed, 431 insertions(+), 299 deletions(-) create mode 100644 src/main/java/emu/grasscutter/commands/ServerCommands.java diff --git a/src/main/java/emu/grasscutter/commands/PlayerCommands.java b/src/main/java/emu/grasscutter/commands/PlayerCommands.java index 9123cbd94..f427409c2 100644 --- a/src/main/java/emu/grasscutter/commands/PlayerCommands.java +++ b/src/main/java/emu/grasscutter/commands/PlayerCommands.java @@ -1,21 +1,17 @@ package emu.grasscutter.commands; -import java.lang.reflect.Modifier; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; - +import emu.grasscutter.Grasscutter; import emu.grasscutter.data.GenshinData; import emu.grasscutter.data.def.ItemData; import emu.grasscutter.data.def.MonsterData; import emu.grasscutter.game.GenshinPlayer; +import emu.grasscutter.game.World; import emu.grasscutter.game.avatar.GenshinAvatar; import emu.grasscutter.game.entity.EntityAvatar; import emu.grasscutter.game.entity.EntityItem; import emu.grasscutter.game.entity.EntityMonster; -import emu.grasscutter.game.entity.GenshinEntity; import emu.grasscutter.game.inventory.GenshinItem; +import emu.grasscutter.game.inventory.Inventory; import emu.grasscutter.game.inventory.ItemType; import emu.grasscutter.game.props.ActionReason; import emu.grasscutter.game.props.FightProperty; @@ -23,306 +19,302 @@ import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify; import emu.grasscutter.server.packet.send.PacketItemAddHintNotify; import emu.grasscutter.utils.Position; -public class PlayerCommands { - private static HashMap commandList = new HashMap(); - private static HashMap commandAliasList = new HashMap(); +import java.util.LinkedList; +import java.util.List; - - static { - try { - // Look for classes - for (Class cls : PlayerCommands.class.getDeclaredClasses()) { - // Get non abstract classes - if (!Modifier.isAbstract(cls.getModifiers())) { - Command commandAnnotation = cls.getAnnotation(Command.class); - PlayerCommand command = (PlayerCommand) cls.newInstance(); - - if (commandAnnotation != null) { - command.setLevel(commandAnnotation.gmLevel()); - command.setHelpText(commandAnnotation.helpText()); - for (String alias : commandAnnotation.aliases()) { - if (alias.length() == 0) { - continue; - } +/** + * A container for player-related commands. + */ +public final class PlayerCommands { + @Command(label = "give", aliases = {"g", "item", "giveitem"}, + usage = "Usage: give [player] [amount]") + public static class GiveCommand implements CommandHandler { - String commandName = alias; - commandAliasList.put(commandName, command); - } - } + @Override + public void execute(GenshinPlayer player, List args) { + int target, item, amount = 1; - String commandName = cls.getSimpleName().toLowerCase(); - commandList.put(commandName, command); - } - - } - } catch (Exception e) { - - } - } + switch(args.size()) { + default: + CommandHandler.sendMessage(player, "Usage: give [amount]"); + return; + case 1: + try { + item = Integer.parseInt(args.get(0)); + target = player.getId(); + } catch (NumberFormatException ignored) { + // TODO: Parse from item name using GM Handbook. + CommandHandler.sendMessage(player, "Invalid item id."); + return; + } + break; + case 2: + try { + target = Integer.parseInt(args.get(0)); + if(Grasscutter.getGameServer().getPlayerById(target) == null) { + target = player.getId(); amount = Integer.parseInt(args.get(1)); + item = Integer.parseInt(args.get(0)); + } else { + item = Integer.parseInt(args.get(1)); + } + } catch (NumberFormatException ignored) { + // TODO: Parse from item name using GM Handbook. + CommandHandler.sendMessage(player, "Invalid item or player ID."); + return; + } + break; + case 3: + try { + target = Integer.parseInt(args.get(0)); + if(Grasscutter.getGameServer().getPlayerById(target) == null) { + CommandHandler.sendMessage(player, "Invalid player ID."); return; + } - public static void handle(GenshinPlayer player, String msg) { - String[] split = msg.split(" "); - - // End if invalid - if (split.length == 0) { - return; - } + item = Integer.parseInt(args.get(1)); + amount = Integer.parseInt(args.get(2)); + } catch (NumberFormatException ignored) { + // TODO: Parse from item name using GM Handbook. + CommandHandler.sendMessage(player, "Invalid item or player ID."); + return; + } + break; + } - String first = split[0].toLowerCase().substring(1); - PlayerCommand c = PlayerCommands.commandList.get(first); - PlayerCommand a = PlayerCommands.commandAliasList.get(first); - - if (c != null || a != null) { - PlayerCommand cmd = c != null ? c : a; - // Level check - if (player.getGmLevel() < cmd.getLevel()) { - return; - } - // Execute - int len = Math.min(split[0].length() + 1, msg.length()); - cmd.execute(player, msg.substring(len)); - } - } - - public static abstract class PlayerCommand { - // GM level required to use this command - private int level; - private String helpText; + GenshinPlayer targetPlayer = Grasscutter.getGameServer().getPlayerById(target); + if(targetPlayer == null) { + CommandHandler.sendMessage(player, "Player not found."); return; + } - protected int getLevel() { return this.level; } - protected void setLevel(int minLevel) { this.level = minLevel; } + ItemData itemData = GenshinData.getItemDataMap().get(item); + if(itemData == null) { + CommandHandler.sendMessage(player, "Invalid item id."); return; + } + + this.item(targetPlayer, itemData, amount); + } - protected String getHelpText() { return this.helpText; } - protected void setHelpText(String helpText) { this.helpText = helpText; } + /** + * give [player] [itemId|itemName] [amount] + */ + @Override public void execute(List args) { + if(args.size() < 2) { + CommandHandler.sendMessage(null, "Usage: give [amount]"); + return; + } - // Main - public abstract void execute(GenshinPlayer player, String raw); - } - - // ================ Commands ================ + try { + int target = Integer.parseInt(args.get(0)); + int item = Integer.parseInt(args.get(1)); + int amount = 1; if(args.size() > 2) amount = Integer.parseInt(args.get(2)); + + GenshinPlayer targetPlayer = Grasscutter.getGameServer().getPlayerById(target); + if(targetPlayer == null) { + CommandHandler.sendMessage(null, "Player not found."); return; + } + + ItemData itemData = GenshinData.getItemDataMap().get(item); + if(itemData == null) { + CommandHandler.sendMessage(null, "Invalid item id."); return; + } + + this.item(targetPlayer, itemData, amount); + } catch (NumberFormatException ignored) { + CommandHandler.sendMessage(null, "Invalid item or player ID."); + } + } + + private void item(GenshinPlayer player, ItemData itemData, int amount) { + GenshinItem genshinItem = new GenshinItem(itemData); + if(itemData.isEquip()) { + List items = new LinkedList<>(); + for(int i = 0; i < amount; i++) { + items.add(genshinItem); + } player.getInventory().addItems(items); + player.sendPacket(new PacketItemAddHintNotify(items, ActionReason.SubfieldDrop)); + } else { + genshinItem.setCount(amount); + player.getInventory().addItem(genshinItem); + player.sendPacket(new PacketItemAddHintNotify(genshinItem, ActionReason.SubfieldDrop)); + } + } + } + + @Command(label = "drop", aliases = {"d", "dropitem"}, + usage = "Usage: drop [amount]", + execution = Command.Execution.PLAYER) + public static class DropCommand implements CommandHandler { - @Command(aliases = {"h"}, helpText = "Shows this command") - public static class Help extends PlayerCommand { + @Override + public void execute(GenshinPlayer player, List args) { + if(args.size() < 1) { + CommandHandler.sendMessage(null, "Usage: drop [amount]"); + return; + } - @Override - public void execute(GenshinPlayer player, String raw) { - String helpMessage = "Grasscutter Commands: "; - for (Map.Entry cmd : commandList.entrySet()) { + try { + int item = Integer.parseInt(args.get(0)); + int amount = 1; if(args.size() > 1) amount = Integer.parseInt(args.get(1)); - helpMessage += "\n" + cmd.getKey() + " - " + cmd.getValue().helpText; - } + ItemData itemData = GenshinData.getItemDataMap().get(item); + if(itemData == null) { + CommandHandler.sendMessage(null, "Invalid item id."); return; + } - player.dropMessage(helpMessage); - } - } - - @Command(aliases = {"g", "item", "additem"}, helpText = "/give [item id] [count] - Gives {count} amount of {item id}") - public static class Give extends PlayerCommand { - @Override - public void execute(GenshinPlayer player, String raw) { - String[] split = raw.split(" "); - int itemId = 0, count = 1; - - try { - itemId = Integer.parseInt(split[0]); - } catch (Exception e) { - itemId = 0; - } - - try { - count = Math.max(Math.min(Integer.parseInt(split[1]), Integer.MAX_VALUE), 1); - } catch (Exception e) { - count = 1; - } - - // Give - ItemData itemData = GenshinData.getItemDataMap().get(itemId); - GenshinItem item; - - if (itemData == null) { - player.dropMessage("Error: Item data not found"); - return; - } - - if (itemData.isEquip()) { - List items = new LinkedList<>(); - for (int i = 0; i < count; i++) { - item = new GenshinItem(itemData); - items.add(item); - } - player.getInventory().addItems(items); - player.sendPacket(new PacketItemAddHintNotify(items, ActionReason.SubfieldDrop)); - } else { - item = new GenshinItem(itemData, count); - player.getInventory().addItem(item); - player.sendPacket(new PacketItemAddHintNotify(item, ActionReason.SubfieldDrop)); - } - } - } - - @Command(aliases = {"d"}, helpText = "/drop [item id] [count] - Drops {count} amount of {item id}") - public static class Drop extends PlayerCommand { - @Override - public void execute(GenshinPlayer player, String raw) { - String[] split = raw.split(" "); - int itemId = 0, count = 1; - - try { - itemId = Integer.parseInt(split[0]); - } catch (Exception e) { - itemId = 0; - } - - try { - count = Math.max(Math.min(Integer.parseInt(split[1]), Integer.MAX_VALUE), 1); - } catch (Exception e) { - count = 1; - } - - // Give - ItemData itemData = GenshinData.getItemDataMap().get(itemId); - - if (itemData == null) { - player.dropMessage("Error: Item data not found"); - return; - } - - if (itemData.isEquip()) { - float range = (5f + (.1f * count)); - for (int i = 0; i < count; i++) { - Position pos = player.getPos().clone().addX((float) (Math.random() * range) - (range / 2)).addY(3f).addZ((float) (Math.random() * range) - (range / 2)); - EntityItem entity = new EntityItem(player.getWorld(), player, itemData, pos, 1); - player.getWorld().addEntity(entity); - } - } else { - EntityItem entity = new EntityItem(player.getWorld(), player, itemData, player.getPos().clone().addY(3f), count); - player.getWorld().addEntity(entity); - } - } - } - - @Command(helpText = "/spawn [monster id] [count] - Creates {count} amount of {item id}") - public static class Spawn extends PlayerCommand { - @Override - public void execute(GenshinPlayer player, String raw) { - String[] split = raw.split(" "); - int monsterId = 0, count = 1, level = 1; - - try { - monsterId = Integer.parseInt(split[0]); - } catch (Exception e) { - monsterId = 0; - } - - try { - level = Math.max(Math.min(Integer.parseInt(split[1]), 200), 1); - } catch (Exception e) { - level = 1; - } - - try { - count = Math.max(Math.min(Integer.parseInt(split[2]), 1000), 1); - } catch (Exception e) { - count = 1; - } - - // Give - MonsterData monsterData = GenshinData.getMonsterDataMap().get(monsterId); - - if (monsterData == null) { - player.dropMessage("Error: Monster data not found"); - return; - } - - float range = (5f + (.1f * count)); - for (int i = 0; i < count; i++) { - Position pos = player.getPos().clone().addX((float) (Math.random() * range) - (range / 2)).addY(3f).addZ((float) (Math.random() * range) - (range / 2)); - EntityMonster entity = new EntityMonster(player.getWorld(), monsterData, pos, level); - player.getWorld().addEntity(entity); - } - } - } - - @Command(helpText = "/killall") - public static class KillAll extends PlayerCommand { - @Override - public void execute(GenshinPlayer player, String raw) { - List toRemove = new LinkedList<>(); - for (GenshinEntity entity : player.getWorld().getEntities().values()) { - if (entity instanceof EntityMonster) { - toRemove.add(entity); - } - } - toRemove.forEach(e -> player.getWorld().killEntity(e, 0)); - } - } - - @Command(helpText = "/resetconst - Resets all constellations for the currently active character") - public static class ResetConst extends PlayerCommand { - @Override - public void execute(GenshinPlayer player, String raw) { - EntityAvatar entity = player.getTeamManager().getCurrentAvatarEntity(); - - if (entity == null) { - return; - } - - GenshinAvatar avatar = entity.getAvatar(); - - avatar.getTalentIdList().clear(); - avatar.setCoreProudSkillLevel(0); - avatar.recalcStats(); - avatar.save(); - - player.dropMessage("Constellations for " + entity.getAvatar().getAvatarData().getName() + " have been reset. Please relogin to see changes."); - } - } - - @Command(helpText = "/godmode - Prevents you from taking damage") - public static class Godmode extends PlayerCommand { - @Override - public void execute(GenshinPlayer player, String raw) { - player.setGodmode(!player.hasGodmode()); - player.dropMessage("Godmode is now " + (player.hasGodmode() ? "ON" : "OFF")); - } - } - - @Command(helpText = "/sethp [hp]") - public static class Sethp extends PlayerCommand { - @Override - public void execute(GenshinPlayer player, String raw) { - String[] split = raw.split(" "); - int hp = 0; - - try { - hp = Math.max(Integer.parseInt(split[0]), 1); - } catch (Exception e) { - hp = 1; - } - - EntityAvatar entity = player.getTeamManager().getCurrentAvatarEntity(); - - if (entity == null) { - return; - } - - entity.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, hp); - entity.getWorld().broadcastPacket(new PacketEntityFightPropUpdateNotify(entity, FightProperty.FIGHT_PROP_CUR_HP)); - } - } - - @Command(aliases = {"clearart"}, helpText = "/clearartifacts") - public static class ClearArtifacts extends PlayerCommand { - @Override - public void execute(GenshinPlayer player, String raw) { - List toRemove = new LinkedList<>(); - for (GenshinItem item : player.getInventory().getItems().values()) { - if (item.getItemType() == ItemType.ITEM_RELIQUARY && item.getLevel() == 1 && item.getExp() == 0 && !item.isLocked() && !item.isEquipped()) { - toRemove.add(item); - } - } - - player.getInventory().removeItems(toRemove); - } - } + if (itemData.isEquip()) { + float range = (5f + (.1f * amount)); + for (int i = 0; i < amount; i++) { + Position pos = player.getPos().clone().addX((float) (Math.random() * range) - (range / 2)).addY(3f).addZ((float) (Math.random() * range) - (range / 2)); + EntityItem entity = new EntityItem(player.getWorld(), player, itemData, pos, 1); + player.getWorld().addEntity(entity); + } + } else { + EntityItem entity = new EntityItem(player.getWorld(), player, itemData, player.getPos().clone().addY(3f), amount); + player.getWorld().addEntity(entity); + } + } catch (NumberFormatException ignored) { + CommandHandler.sendMessage(null, "Invalid item or player ID."); + } + } + } + + @Command(label = "spawn", execution = Command.Execution.PLAYER, + usage = "Usage: spawn [level] [amount]") + public static class SpawnCommand implements CommandHandler { + + @Override + public void execute(GenshinPlayer player, List args) { + if(args.size() < 1) { + CommandHandler.sendMessage(null, "Usage: spawn [amount]"); + return; + } + + try { + int entity = Integer.parseInt(args.get(0)); + int level = 1; if(args.size() > 1) level = Integer.parseInt(args.get(1)); + int amount = 1; if(args.size() > 2) amount = Integer.parseInt(args.get(2)); + + MonsterData entityData = GenshinData.getMonsterDataMap().get(entity); + if(entityData == null) { + CommandHandler.sendMessage(null, "Invalid entity id."); return; + } + + float range = (5f + (.1f * amount)); + for (int i = 0; i < amount; i++) { + Position pos = player.getPos().clone().addX((float) (Math.random() * range) - (range / 2)).addY(3f).addZ((float) (Math.random() * range) - (range / 2)); + EntityMonster monster = new EntityMonster(player.getWorld(), entityData, pos, level); + player.getWorld().addEntity(monster); + } + } catch (NumberFormatException ignored) { + CommandHandler.sendMessage(null, "Invalid item or player ID."); + } + } + } + + @Command(label = "killall", + usage = "Usage: killall [sceneId]") + public static class KillAllCommand implements CommandHandler { + + @Override + public void execute(GenshinPlayer player, List args) { + World world = player.getWorld(); + world.getEntities().values().stream() + .filter(entity -> entity instanceof EntityMonster) + .forEach(entity -> world.killEntity(entity, 0)); + } + + @Override + public void execute(List args) { + if(args.size() < 1) { + CommandHandler.sendMessage(null, "Usage: killall [sceneId]"); return; + } + + try { + int sceneId = Integer.parseInt(args.get(0)); + CommandHandler.sendMessage(null, "Killing all monsters in scene " + sceneId); + // TODO: Implement getting worlds by scene ID. + } catch (NumberFormatException ignored) { + CommandHandler.sendMessage(null, "Invalid scene id."); + } + } + } + + @Command(label = "resetconst", aliases = {"resetconstellation"}, + usage = "Usage: resetconst [all]", execution = Command.Execution.PLAYER) + public static class ResetConstellationCommand implements CommandHandler { + + @Override + public void execute(GenshinPlayer player, List args) { + if(args.size() > 0 && args.get(0).equalsIgnoreCase("all")) { + player.getAvatars().forEach(this::resetConstellation); + player.dropMessage("Reset all avatars' constellations."); + } else { + EntityAvatar entity = player.getTeamManager().getCurrentAvatarEntity(); + if(entity == null) + return; + + GenshinAvatar avatar = entity.getAvatar(); + this.resetConstellation(avatar); + + player.dropMessage("Constellations for " + avatar.getAvatarData().getName() + " have been reset. Please relog to see changes."); + } + } + + private void resetConstellation(GenshinAvatar avatar) { + avatar.getTalentIdList().clear(); + avatar.setCoreProudSkillLevel(0); + avatar.recalcStats(); + avatar.save(); + } + } + + @Command(label = "godmode", + usage = "Usage: godmode", execution = Command.Execution.PLAYER) + public static class GodModeCommand implements CommandHandler { + + @Override + public void execute(GenshinPlayer player, List args) { + player.setGodmode(!player.inGodmode()); + player.dropMessage("Godmode is now " + (player.inGodmode() ? "enabled" : "disabled") + "."); + } + } + + @Command(label = "sethealth", aliases = {"sethp"}, + usage = "Usage: sethealth ", execution = Command.Execution.PLAYER) + public static class SetHealthCommand implements CommandHandler { + + @Override + public void execute(GenshinPlayer player, List args) { + if(args.size() < 1) { + CommandHandler.sendMessage(null, "Usage: sethealth "); return; + } + + try { + int health = Integer.parseInt(args.get(0)); + EntityAvatar entity = player.getTeamManager().getCurrentAvatarEntity(); + if(entity == null) + return; + + entity.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, health); + entity.getWorld().broadcastPacket(new PacketEntityFightPropUpdateNotify(entity, FightProperty.FIGHT_PROP_CUR_HP)); + player.dropMessage("Health set to " + health + "."); + } catch (NumberFormatException ignored) { + CommandHandler.sendMessage(null, "Invalid health value."); + } + } + } + + @Command(label = "clearartifacts", aliases = {"clearart"}, + usage = "Usage: clearartifacts", execution = Command.Execution.PLAYER) + public static class ClearArtifactsCommand implements CommandHandler { + + @Override + public void execute(GenshinPlayer player, List args) { + Inventory playerInventory = player.getInventory(); + playerInventory.getItems().values().stream() + .filter(item -> item.getItemType() == ItemType.ITEM_RELIQUARY) + .filter(item -> item.getLevel() == 1 && item.getExp() == 0) + .filter(item -> !item.isLocked() && !item.isEquipped()) + .forEach(item -> playerInventory.removeItem(item, item.getCount())); + } + } } diff --git a/src/main/java/emu/grasscutter/commands/ServerCommands.java b/src/main/java/emu/grasscutter/commands/ServerCommands.java new file mode 100644 index 000000000..193ec2b50 --- /dev/null +++ b/src/main/java/emu/grasscutter/commands/ServerCommands.java @@ -0,0 +1,121 @@ +package emu.grasscutter.commands; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.database.DatabaseHelper; +import emu.grasscutter.game.Account; +import emu.grasscutter.game.GenshinPlayer; + +import java.util.List; + +/** + * A container for server-related commands. + */ +public final class ServerCommands { + @Command(label = "reload", usage = "Usage: reload") + public static class ReloadCommand implements CommandHandler { + + @Override + public void execute(List args) { + Grasscutter.getLogger().info("Reloading config."); + Grasscutter.loadConfig(); + Grasscutter.getDispatchServer().loadQueries(); + Grasscutter.getLogger().info("Reload complete."); + } + + @Override + public void execute(GenshinPlayer player, List args) { + this.execute(args); + } + } + + @Command(label = "sendmessage", aliases = {"sendmsg", "msg"}, + usage = "Usage: sendmessage ") + public static class SendMessageCommand implements CommandHandler { + + @Override + public void execute(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."); + } + } + + @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.sendMessage(player, message); + CommandHandler.sendMessage(null, "Message sent."); + } catch (NumberFormatException ignored) { + CommandHandler.sendMessage(null, "Invalid player ID."); + } + } + } + + @Command(label = "account", + usage = "Usage: account [uid]", + execution = Command.Execution.CONSOLE) + public static class AccountCommand implements CommandHandler { + + @Override + public void execute(List args) { + if(args.size() < 2) { + CommandHandler.sendMessage(null, "Usage: account [uid]"); return; + } + + String action = args.get(0); + String username = args.get(1); + + switch(action) { + default: + CommandHandler.sendMessage(null, "Usage: account [uid]"); + return; + case "create": + int uid = 0; + if(args.size() > 2) { + try { + uid = Integer.parseInt(args.get(2)); + } catch (NumberFormatException ignored) { + CommandHandler.sendMessage(null, "Invalid UID."); return; + } + } + + Account account = DatabaseHelper.createAccountWithId(username, uid); + if(account == null) { + CommandHandler.sendMessage(null, "Account already exists."); return; + } else CommandHandler.sendMessage(null, "Account created with UID " + account.getId() + "."); + return; + case "delete": + if(DatabaseHelper.deleteAccount(username)) { + CommandHandler.sendMessage(null, "Account deleted."); return; + } else CommandHandler.sendMessage(null, "Account not found."); + return; + } + } + } +} diff --git a/src/main/java/emu/grasscutter/game/GenshinPlayer.java b/src/main/java/emu/grasscutter/game/GenshinPlayer.java index 57a561289..f9f1521e1 100644 --- a/src/main/java/emu/grasscutter/game/GenshinPlayer.java +++ b/src/main/java/emu/grasscutter/game/GenshinPlayer.java @@ -485,7 +485,7 @@ public class GenshinPlayer { this.regionId = regionId; } - public boolean hasGodmode() { + public boolean inGodmode() { return godmode; } @@ -558,6 +558,15 @@ public class GenshinPlayer { public void dropMessage(Object message) { this.sendPacket(new PacketPrivateChatNotify(GenshinConstants.SERVER_CONSOLE_UID, getId(), message.toString())); } + + /** + * Sends a message to another player. + * @param sender The sender of the message. + * @param message The message to send. + */ + public void sendMessage(GenshinPlayer sender, Object message) { + this.sendPacket(new PacketPrivateChatNotify(sender.getId(), this.getId(), message.toString())); + } public void interactWith(int gadgetEntityId) { GenshinEntity entity = getWorld().getEntityById(gadgetEntityId); diff --git a/src/main/java/emu/grasscutter/game/World.java b/src/main/java/emu/grasscutter/game/World.java index a06b584f1..78744c0c1 100644 --- a/src/main/java/emu/grasscutter/game/World.java +++ b/src/main/java/emu/grasscutter/game/World.java @@ -344,7 +344,7 @@ public class World implements Iterable { // Godmode check if (target instanceof EntityAvatar) { - if (((EntityAvatar) target).getPlayer().hasGodmode()) { + if (((EntityAvatar) target).getPlayer().inGodmode()) { return; } } diff --git a/src/main/java/emu/grasscutter/utils/Utils.java b/src/main/java/emu/grasscutter/utils/Utils.java index 4293193d1..d4d1063a3 100644 --- a/src/main/java/emu/grasscutter/utils/Utils.java +++ b/src/main/java/emu/grasscutter/utils/Utils.java @@ -118,6 +118,16 @@ public final class Utils { } } + /** + * Get object with null fallback. + * @param nonNull The object to return if not null. + * @param fallback The object to return if null. + * @return One of the two provided objects. + */ + public static T requireNonNullElseGet(T nonNull, T fallback) { + return nonNull != null ? nonNull : fallback; + } + /** * Checks for required files and folders before startup. */ From 052d0dbf87e4b84593a7a9320baeed5ebc8970a1 Mon Sep 17 00:00:00 2001 From: KingRainbow44 Date: Mon, 18 Apr 2022 22:10:51 -0400 Subject: [PATCH 05/15] Add color to logback --- src/main/resources/logback.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index 66d630192..666b53b42 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -1,7 +1,7 @@ - [%d{HH:mm:ss}] [%level] %msg%n + [%d{HH:mm:ss}] [%highlight(%level)] %msg%n From 4a70653e885a951dc7a95fe386d4bac343969a1e Mon Sep 17 00:00:00 2001 From: KingRainbow44 Date: Mon, 18 Apr 2022 22:16:20 -0400 Subject: [PATCH 06/15] Refactor method names & add `/` prefix --- .../grasscutter/game/managers/ChatManager.java | 17 +++++++++++------ .../packet/recv/HandlerPlayerChatReq.java | 4 ++-- .../packet/recv/HandlerPrivateChatReq.java | 4 ++-- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/main/java/emu/grasscutter/game/managers/ChatManager.java b/src/main/java/emu/grasscutter/game/managers/ChatManager.java index 64729cb37..3856c0fd1 100644 --- a/src/main/java/emu/grasscutter/game/managers/ChatManager.java +++ b/src/main/java/emu/grasscutter/game/managers/ChatManager.java @@ -7,7 +7,12 @@ import emu.grasscutter.server.game.GameServer; import emu.grasscutter.server.packet.send.PacketPlayerChatNotify; import emu.grasscutter.server.packet.send.PacketPrivateChatNotify; +import java.util.Arrays; +import java.util.List; + public class ChatManager { + static final List PREFIXES = Arrays.asList('/', '!'); + private final GameServer server; public ChatManager(GameServer server) { @@ -18,14 +23,14 @@ public class ChatManager { return server; } - public void sendPrivChat(GenshinPlayer player, int targetUid, String message) { + public void sendPrivateMessage(GenshinPlayer player, int targetUid, String message) { // Sanity checks if (message == null || message.length() == 0) { return; } // Check if command - if (message.charAt(0) == '!') { + if (PREFIXES.contains(message.charAt(0))) { CommandMap.getInstance().invoke(player, message); return; } @@ -44,7 +49,7 @@ public class ChatManager { target.sendPacket(packet); } - public void sendPrivChat(GenshinPlayer player, int targetUid, int emote) { + public void sendPrivateMessage(GenshinPlayer player, int targetUid, int emote) { // Get target GenshinPlayer target = getServer().getPlayerById(targetUid); @@ -59,14 +64,14 @@ public class ChatManager { target.sendPacket(packet); } - public void sendTeamChat(GenshinPlayer player, int channel, String message) { + public void sendTeamMessage(GenshinPlayer player, int channel, String message) { // Sanity checks if (message == null || message.length() == 0) { return; } // Check if command - if (message.charAt(0) == '!') { + if (PREFIXES.contains(message.charAt(0))) { CommandMap.getInstance().invoke(player, message); return; } @@ -75,7 +80,7 @@ public class ChatManager { player.getWorld().broadcastPacket(new PacketPlayerChatNotify(player, channel, message)); } - public void sendTeamChat(GenshinPlayer player, int channel, int icon) { + public void sendTeamMessage(GenshinPlayer player, int channel, int icon) { // Create and send chat packet player.getWorld().broadcastPacket(new PacketPlayerChatNotify(player, channel, icon)); } diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerPlayerChatReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerPlayerChatReq.java index 9a807e991..db0704ed8 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerPlayerChatReq.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerPlayerChatReq.java @@ -17,9 +17,9 @@ public class HandlerPlayerChatReq extends PacketHandler { ChatInfo.ContentCase content = req.getChatInfo().getContentCase(); if (content == ChatInfo.ContentCase.TEXT) { - session.getServer().getChatManager().sendTeamChat(session.getPlayer(), req.getChannelId(), req.getChatInfo().getText()); + session.getServer().getChatManager().sendTeamMessage(session.getPlayer(), req.getChannelId(), req.getChatInfo().getText()); } else if (content == ChatInfo.ContentCase.ICON) { - session.getServer().getChatManager().sendTeamChat(session.getPlayer(), req.getChannelId(), req.getChatInfo().getIcon()); + session.getServer().getChatManager().sendTeamMessage(session.getPlayer(), req.getChannelId(), req.getChatInfo().getIcon()); } session.send(new PacketPlayerChatRsp()); diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerPrivateChatReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerPrivateChatReq.java index 081c0883a..b576bbb20 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerPrivateChatReq.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerPrivateChatReq.java @@ -15,9 +15,9 @@ public class HandlerPrivateChatReq extends PacketHandler { PrivateChatReq.ContentCase content = req.getContentCase(); if (content == PrivateChatReq.ContentCase.TEXT) { - session.getServer().getChatManager().sendPrivChat(session.getPlayer(), req.getTargetUid(), req.getText()); + session.getServer().getChatManager().sendPrivateMessage(session.getPlayer(), req.getTargetUid(), req.getText()); } else if (content == PrivateChatReq.ContentCase.ICON) { - session.getServer().getChatManager().sendPrivChat(session.getPlayer(), req.getTargetUid(), req.getIcon()); + session.getServer().getChatManager().sendPrivateMessage(session.getPlayer(), req.getTargetUid(), req.getIcon()); } //session.send(new GenshinPacket(PacketOpcodes.PrivateChatRsp)); // Unused by server From f8496a5ea5841930dc128eb1535954d71e7327d8 Mon Sep 17 00:00:00 2001 From: KingRainbow44 Date: Mon, 18 Apr 2022 22:23:22 -0400 Subject: [PATCH 07/15] Find by player's UID not IID --- .../emu/grasscutter/commands/PlayerCommands.java | 8 ++++---- .../emu/grasscutter/commands/ServerCommands.java | 6 +++--- .../emu/grasscutter/database/DatabaseHelper.java | 1 + .../emu/grasscutter/server/game/GameServer.java | 13 +++++++------ 4 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/main/java/emu/grasscutter/commands/PlayerCommands.java b/src/main/java/emu/grasscutter/commands/PlayerCommands.java index f427409c2..e8816a188 100644 --- a/src/main/java/emu/grasscutter/commands/PlayerCommands.java +++ b/src/main/java/emu/grasscutter/commands/PlayerCommands.java @@ -51,7 +51,7 @@ public final class PlayerCommands { case 2: try { target = Integer.parseInt(args.get(0)); - if(Grasscutter.getGameServer().getPlayerById(target) == null) { + if(Grasscutter.getGameServer().getPlayerByUid(target) == null) { target = player.getId(); amount = Integer.parseInt(args.get(1)); item = Integer.parseInt(args.get(0)); } else { @@ -66,7 +66,7 @@ public final class PlayerCommands { case 3: try { target = Integer.parseInt(args.get(0)); - if(Grasscutter.getGameServer().getPlayerById(target) == null) { + if(Grasscutter.getGameServer().getPlayerByUid(target) == null) { CommandHandler.sendMessage(player, "Invalid player ID."); return; } @@ -80,7 +80,7 @@ public final class PlayerCommands { break; } - GenshinPlayer targetPlayer = Grasscutter.getGameServer().getPlayerById(target); + GenshinPlayer targetPlayer = Grasscutter.getGameServer().getPlayerByUid(target); if(targetPlayer == null) { CommandHandler.sendMessage(player, "Player not found."); return; } @@ -107,7 +107,7 @@ public final class PlayerCommands { int item = Integer.parseInt(args.get(1)); int amount = 1; if(args.size() > 2) amount = Integer.parseInt(args.get(2)); - GenshinPlayer targetPlayer = Grasscutter.getGameServer().getPlayerById(target); + GenshinPlayer targetPlayer = Grasscutter.getGameServer().getPlayerByUid(target); if(targetPlayer == null) { CommandHandler.sendMessage(null, "Player not found."); return; } diff --git a/src/main/java/emu/grasscutter/commands/ServerCommands.java b/src/main/java/emu/grasscutter/commands/ServerCommands.java index 193ec2b50..5b2148b67 100644 --- a/src/main/java/emu/grasscutter/commands/ServerCommands.java +++ b/src/main/java/emu/grasscutter/commands/ServerCommands.java @@ -42,7 +42,7 @@ public final class ServerCommands { 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; } @@ -64,7 +64,7 @@ public final class ServerCommands { 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; } @@ -108,7 +108,7 @@ public final class ServerCommands { Account account = DatabaseHelper.createAccountWithId(username, uid); if(account == null) { CommandHandler.sendMessage(null, "Account already exists."); return; - } else CommandHandler.sendMessage(null, "Account created with UID " + account.getId() + "."); + } else CommandHandler.sendMessage(null, "Account created with UID " + account.getPlayerId() + "."); return; case "delete": if(DatabaseHelper.deleteAccount(username)) { diff --git a/src/main/java/emu/grasscutter/database/DatabaseHelper.java b/src/main/java/emu/grasscutter/database/DatabaseHelper.java index d27e47030..905c4ef64 100644 --- a/src/main/java/emu/grasscutter/database/DatabaseHelper.java +++ b/src/main/java/emu/grasscutter/database/DatabaseHelper.java @@ -36,6 +36,7 @@ public class DatabaseHelper { if (reservedId == GenshinConstants.SERVER_CONSOLE_UID) { return null; } + exists = DatabaseHelper.getAccountByPlayerId(reservedId); if (exists != null) { return null; diff --git a/src/main/java/emu/grasscutter/server/game/GameServer.java b/src/main/java/emu/grasscutter/server/game/GameServer.java index 263c0cc72..deb215f0d 100644 --- a/src/main/java/emu/grasscutter/server/game/GameServer.java +++ b/src/main/java/emu/grasscutter/server/game/GameServer.java @@ -1,12 +1,7 @@ package emu.grasscutter.server.game; import java.net.InetSocketAddress; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import java.util.Map; -import java.util.Timer; -import java.util.TimerTask; +import java.util.*; import java.util.concurrent.ConcurrentHashMap; import emu.grasscutter.GenshinConstants; @@ -115,6 +110,12 @@ public final class GameServer extends MihoyoKcpServer { return this.getPlayers().get(id); } + public GenshinPlayer getPlayerByUid(int uid) { + return this.getPlayers().values().stream() + .filter(player -> player.getAccount().getPlayerId() == uid) + .findFirst().orElse(this.getPlayerById(uid)); + } + public GenshinPlayer forceGetPlayerById(int id) { // Console check if (id == GenshinConstants.SERVER_CONSOLE_UID) { From ba4e727302c6a0cdb0d4ccbe820aa45c3475b07a Mon Sep 17 00:00:00 2001 From: KingRainbow44 Date: Mon, 18 Apr 2022 22:31:31 -0400 Subject: [PATCH 08/15] Fix bugs with commands --- .../java/emu/grasscutter/commands/PlayerCommands.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/emu/grasscutter/commands/PlayerCommands.java b/src/main/java/emu/grasscutter/commands/PlayerCommands.java index e8816a188..df927dbcc 100644 --- a/src/main/java/emu/grasscutter/commands/PlayerCommands.java +++ b/src/main/java/emu/grasscutter/commands/PlayerCommands.java @@ -41,7 +41,7 @@ public final class PlayerCommands { case 1: try { item = Integer.parseInt(args.get(0)); - target = player.getId(); + target = player.getAccount().getPlayerId(); } catch (NumberFormatException ignored) { // TODO: Parse from item name using GM Handbook. CommandHandler.sendMessage(player, "Invalid item id."); @@ -147,7 +147,7 @@ public final class PlayerCommands { @Override public void execute(GenshinPlayer player, List args) { if(args.size() < 1) { - CommandHandler.sendMessage(null, "Usage: drop [amount]"); + CommandHandler.sendMessage(player, "Usage: drop [amount]"); return; } @@ -157,7 +157,7 @@ public final class PlayerCommands { ItemData itemData = GenshinData.getItemDataMap().get(item); if(itemData == null) { - CommandHandler.sendMessage(null, "Invalid item id."); return; + CommandHandler.sendMessage(player, "Invalid item id."); return; } if (itemData.isEquip()) { @@ -172,7 +172,7 @@ public final class PlayerCommands { player.getWorld().addEntity(entity); } } catch (NumberFormatException ignored) { - CommandHandler.sendMessage(null, "Invalid item or player ID."); + CommandHandler.sendMessage(player, "Invalid item or player ID."); } } } From fa65f512caac83e5cb3296473bc2bc9547da2d81 Mon Sep 17 00:00:00 2001 From: KingRainbow44 Date: Mon, 18 Apr 2022 23:06:03 -0400 Subject: [PATCH 09/15] Generic help command --- .../emu/grasscutter/commands/CommandMap.java | 17 ++++++ .../grasscutter/commands/ServerCommands.java | 58 +++++++++++++++++-- 2 files changed, 71 insertions(+), 4 deletions(-) diff --git a/src/main/java/emu/grasscutter/commands/CommandMap.java b/src/main/java/emu/grasscutter/commands/CommandMap.java index aa9f47f54..5a70aeb45 100644 --- a/src/main/java/emu/grasscutter/commands/CommandMap.java +++ b/src/main/java/emu/grasscutter/commands/CommandMap.java @@ -63,6 +63,23 @@ public final class CommandMap { return this; } + /** + * Returns a list of all registered commands. + * @return All command handlers as a list. + */ + public List getHandlers() { + return new LinkedList<>(this.commands.values()); + } + + /** + * Returns a handler by label/alias. + * @param label The command label. + * @return The command handler. + */ + public CommandHandler getHandler(String label) { + return this.commands.get(label); + } + /** * Invoke a command handler with the given arguments. * @param player The player invoking the command or null for the server console. diff --git a/src/main/java/emu/grasscutter/commands/ServerCommands.java b/src/main/java/emu/grasscutter/commands/ServerCommands.java index 5b2148b67..fc1e9b142 100644 --- a/src/main/java/emu/grasscutter/commands/ServerCommands.java +++ b/src/main/java/emu/grasscutter/commands/ServerCommands.java @@ -6,6 +6,7 @@ import emu.grasscutter.game.Account; import emu.grasscutter.game.GenshinPlayer; import java.util.List; +import java.util.stream.Collectors; /** * A container for server-related commands. @@ -57,7 +58,7 @@ public final class ServerCommands { @Override public void execute(GenshinPlayer player, List args) { if(args.size() < 2) { - CommandHandler.sendMessage(null, "Usage: sendmessage "); return; + CommandHandler.sendMessage(player, "Usage: sendmessage "); return; } try { @@ -66,13 +67,13 @@ public final class ServerCommands { GenshinPlayer targetPlayer = Grasscutter.getGameServer().getPlayerByUid(target); if(targetPlayer == null) { - CommandHandler.sendMessage(null, "Player not found."); return; + CommandHandler.sendMessage(player, "Player not found."); return; } targetPlayer.sendMessage(player, message); - CommandHandler.sendMessage(null, "Message sent."); + CommandHandler.sendMessage(player, "Message sent."); } catch (NumberFormatException ignored) { - CommandHandler.sendMessage(null, "Invalid player ID."); + CommandHandler.sendMessage(player, "Invalid player ID."); } } } @@ -118,4 +119,53 @@ public final class ServerCommands { } } } + + @Command(label = "help", + usage = "Usage: help [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()); + } + } + + @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())); + } else { + String command = args.get(0); + CommandHandler handler = CommandMap.getInstance().getHandler(command); + if(handler == null) { + CommandHandler.sendMessage(player, "Command not found."); return; + } + + Command annotation = handler.getClass().getAnnotation(Command.class); + CommandHandler.sendMessage(player, annotation.usage()); + } + } + } } From 17163ff81c053f67c070140c7c9f148fd2735e77 Mon Sep 17 00:00:00 2001 From: KingRainbow44 Date: Mon, 18 Apr 2022 23:26:34 -0400 Subject: [PATCH 10/15] Use OS file separator --- .../emu/grasscutter/data/ResourceLoader.java | 47 +++++++++---------- .../java/emu/grasscutter/utils/Utils.java | 9 ++++ 2 files changed, 30 insertions(+), 26 deletions(-) diff --git a/src/main/java/emu/grasscutter/data/ResourceLoader.java b/src/main/java/emu/grasscutter/data/ResourceLoader.java index dea19e022..f5b9f4f72 100644 --- a/src/main/java/emu/grasscutter/data/ResourceLoader.java +++ b/src/main/java/emu/grasscutter/data/ResourceLoader.java @@ -2,20 +2,12 @@ package emu.grasscutter.data; import java.io.File; import java.io.FileReader; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.Collection; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.TreeMap; +import java.util.*; import java.util.Map.Entry; import java.util.regex.Matcher; import java.util.regex.Pattern; -import java.util.stream.Stream; +import emu.grasscutter.utils.Utils; import org.reflections.Reflections; import com.google.gson.reflect.TypeToken; @@ -39,19 +31,12 @@ public class ResourceLoader { } }); - classList.sort((a, b) -> { - return b.getAnnotation(ResourceType.class).loadPriority().value() - a.getAnnotation(ResourceType.class).loadPriority().value(); - }); + classList.sort((a, b) -> b.getAnnotation(ResourceType.class).loadPriority().value() - a.getAnnotation(ResourceType.class).loadPriority().value()); return classList; } public static void loadAll() { - // Create resource folder if it doesnt exist - File resFolder = new File(Grasscutter.getConfig().RESOURCE_FOLDER); - if (!resFolder.exists()) { - resFolder.mkdir(); - } // Load ability lists loadAbilityEmbryos(); loadOpenConfig(); @@ -110,7 +95,7 @@ public class ResourceLoader { try { loadFromResource(resourceDefinition, type, map); } catch (Exception e) { - Grasscutter.getLogger().error("Error loading resource file: " + type.name(), e); + Grasscutter.getLogger().error("Error loading resource file: " + Arrays.toString(type.name()), e); } } } @@ -153,10 +138,16 @@ public class ResourceLoader { Pattern pattern = Pattern.compile("(?<=ConfigAvatar_)(.*?)(?=.json)"); embryoList = new LinkedList<>(); - File folder = new File(Grasscutter.getConfig().RESOURCE_FOLDER + "BinOutput\\Avatar\\"); - for (File file : folder.listFiles()) { - AvatarConfig config = null; - String avatarName = null; + File folder = new File(Utils.toFilePath(Grasscutter.getConfig().RESOURCE_FOLDER + "BinOutput/Avatar/")); + File[] files = folder.listFiles(); + if(files == null) { + Grasscutter.getLogger().error("Error loading ability embryos: no files found in " + folder.getAbsolutePath()); + return; + } + + for (File file : files) { + AvatarConfig config; + String avatarName; Matcher matcher = pattern.matcher(file.getName()); if (matcher.find()) { @@ -209,14 +200,18 @@ public class ResourceLoader { String[] folderNames = {"BinOutput\\Talent\\EquipTalents\\", "BinOutput\\Talent\\AvatarTalents\\"}; for (String name : folderNames) { - File folder = new File(Grasscutter.getConfig().RESOURCE_FOLDER + name); + File folder = new File(Utils.toFilePath(Grasscutter.getConfig().RESOURCE_FOLDER + name)); + File[] files = folder.listFiles(); + if(files == null) { + Grasscutter.getLogger().error("Error loading open config: no files found in " + folder.getAbsolutePath()); return; + } - for (File file : folder.listFiles()) { + for (File file : files) { if (!file.getName().endsWith(".json")) { continue; } - Map config = null; + Map config; try (FileReader fileReader = new FileReader(file)) { config = Grasscutter.getGsonFactory().fromJson(fileReader, type); diff --git a/src/main/java/emu/grasscutter/utils/Utils.java b/src/main/java/emu/grasscutter/utils/Utils.java index 4293193d1..f23a54e15 100644 --- a/src/main/java/emu/grasscutter/utils/Utils.java +++ b/src/main/java/emu/grasscutter/utils/Utils.java @@ -79,6 +79,15 @@ public final class Utils { return v7; } + /** + * Creates a string with the path to a file. + * @param path The path to the file. + * @return A path using the operating system's file separator. + */ + public static String toFilePath(String path) { + return path.replace("/", File.separator); + } + /** * Checks if a file exists on the file system. * @param path The path to the file. From 87a9d0c5905b98e07795b0f6cc51d01f5c91e5cf Mon Sep 17 00:00:00 2001 From: KingRainbow44 Date: Mon, 18 Apr 2022 23:46:04 -0400 Subject: [PATCH 11/15] Implement basic permission system --- .../emu/grasscutter/commands/Command.java | 2 +- .../emu/grasscutter/commands/CommandMap.java | 23 +++++++--- .../grasscutter/commands/ServerCommands.java | 43 ++++++++++++++++++- .../java/emu/grasscutter/game/Account.java | 19 ++++++++ .../emu/grasscutter/game/GenshinPlayer.java | 7 +-- 5 files changed, 80 insertions(+), 14 deletions(-) diff --git a/src/main/java/emu/grasscutter/commands/Command.java b/src/main/java/emu/grasscutter/commands/Command.java index 3f739d78e..d1e181e20 100644 --- a/src/main/java/emu/grasscutter/commands/Command.java +++ b/src/main/java/emu/grasscutter/commands/Command.java @@ -13,7 +13,7 @@ public @interface Command { Execution execution() default Execution.ALL; - int gmLevel() default 1; + String permission() default ""; enum Execution { ALL, diff --git a/src/main/java/emu/grasscutter/commands/CommandMap.java b/src/main/java/emu/grasscutter/commands/CommandMap.java index 5a70aeb45..e9ca260e7 100644 --- a/src/main/java/emu/grasscutter/commands/CommandMap.java +++ b/src/main/java/emu/grasscutter/commands/CommandMap.java @@ -1,6 +1,7 @@ package emu.grasscutter.commands; import emu.grasscutter.Grasscutter; +import emu.grasscutter.game.Account; import emu.grasscutter.game.GenshinPlayer; import org.reflections.Reflections; @@ -13,7 +14,7 @@ public final class CommandMap { } private final Map commands = new HashMap<>(); - private final Map executionPower = new HashMap<>(); + private final Map annotations = new HashMap<>(); /** * Register a command handler. @@ -26,14 +27,14 @@ public final class CommandMap { // Get command data. Command annotation = command.getClass().getAnnotation(Command.class); - this.executionPower.put(label, annotation.execution()); + this.annotations.put(label, annotation); this.commands.put(label, command); // Register aliases. if(annotation.aliases().length > 0) { for (String alias : annotation.aliases()) { this.commands.put(alias, command); - this.executionPower.put(alias, annotation.execution()); + this.annotations.put(alias, annotation); } } return this; } @@ -49,14 +50,14 @@ public final class CommandMap { if(handler == null) return this; Command annotation = handler.getClass().getAnnotation(Command.class); - this.executionPower.remove(label); + this.annotations.remove(label); this.commands.remove(label); // Unregister aliases. if(annotation.aliases().length > 0) { for (String alias : annotation.aliases()) { this.commands.remove(alias); - this.executionPower.remove(alias); + this.annotations.remove(alias); } } @@ -106,8 +107,18 @@ public final class CommandMap { CommandHandler.sendMessage(player, "Unknown command: " + label); return; } + // Check for permission. + if(player != null) { + String permissionNode = this.annotations.get(label).permission(); + Account account = player.getAccount(); + List permissions = account.getPermissions(); + if(!permissions.contains("*") && !permissions.contains(permissionNode)) { + CommandHandler.sendMessage(player, "You do not have permission to run this command."); return; + } + } + // Execution power check. - Command.Execution executionPower = this.executionPower.get(label); + Command.Execution executionPower = this.annotations.get(label).execution(); if(player == null && executionPower == Command.Execution.PLAYER) { CommandHandler.sendMessage(null, "Run this command in-game."); return; } else if (player != null && executionPower == Command.Execution.CONSOLE) { diff --git a/src/main/java/emu/grasscutter/commands/ServerCommands.java b/src/main/java/emu/grasscutter/commands/ServerCommands.java index fc1e9b142..6ae0bb33c 100644 --- a/src/main/java/emu/grasscutter/commands/ServerCommands.java +++ b/src/main/java/emu/grasscutter/commands/ServerCommands.java @@ -109,7 +109,10 @@ public final class ServerCommands { Account account = DatabaseHelper.createAccountWithId(username, uid); if(account == null) { CommandHandler.sendMessage(null, "Account already exists."); return; - } else CommandHandler.sendMessage(null, "Account created with UID " + account.getPlayerId() + "."); + } else { + CommandHandler.sendMessage(null, "Account created with UID " + account.getPlayerId() + "."); + account.addPermission("*"); // Grant the player superuser permissions. + } return; case "delete": if(DatabaseHelper.deleteAccount(username)) { @@ -120,6 +123,44 @@ public final class ServerCommands { } } + @Command(label = "permission", + usage = "Usage: permission ", + execution = Command.Execution.CONSOLE) + public static class PermissionCommand implements CommandHandler { + + @Override + public void execute(List args) { + if(args.size() < 3) { + CommandHandler.sendMessage(null, "Usage: permission "); return; + } + + String action = args.get(0); + String username = args.get(1); + String permission = args.get(2); + + Account account = DatabaseHelper.getAccountByName(username); + if(account == null) { + CommandHandler.sendMessage(null, "Account not found."); return; + } + + switch(action) { + default: + CommandHandler.sendMessage(null, "Usage: permission "); + return; + case "add": + if(account.addPermission(permission)) { + CommandHandler.sendMessage(null, "Permission added."); return; + } else CommandHandler.sendMessage(null, "They already have this permission!"); + return; + case "remove": + if(account.removePermission(permission)) { + CommandHandler.sendMessage(null, "Permission removed."); return; + } else CommandHandler.sendMessage(null, "They don't have this permission!"); + return; + } + } + } + @Command(label = "help", usage = "Usage: help [command]") public static class HelpCommand implements CommandHandler { diff --git a/src/main/java/emu/grasscutter/game/Account.java b/src/main/java/emu/grasscutter/game/Account.java index 0bb2e9e0b..bba03916a 100644 --- a/src/main/java/emu/grasscutter/game/Account.java +++ b/src/main/java/emu/grasscutter/game/Account.java @@ -9,6 +9,8 @@ import emu.grasscutter.utils.Crypto; import emu.grasscutter.utils.Utils; import dev.morphia.annotations.IndexOptions; +import java.util.List; + @Entity(value = "accounts", noClassnameStored = true) public class Account { @Id private String id; @@ -23,6 +25,7 @@ public class Account { private String token; private String sessionKey; // Session token for dispatch server + private List permissions; @Deprecated public Account() {} @@ -84,6 +87,22 @@ public class Account { this.save(); return this.sessionKey; } + + /** + * The collection of a player's permissions. + */ + public List getPermissions() { + return this.permissions; + } + + public boolean addPermission(String permission) { + if(this.permissions.contains(permission)) return false; + this.permissions.add(permission); return true; + } + + public boolean removePermission(String permission) { + return this.permissions.remove(permission); + } // TODO make unique public String generateLoginToken() { diff --git a/src/main/java/emu/grasscutter/game/GenshinPlayer.java b/src/main/java/emu/grasscutter/game/GenshinPlayer.java index f9f1521e1..bbf80513e 100644 --- a/src/main/java/emu/grasscutter/game/GenshinPlayer.java +++ b/src/main/java/emu/grasscutter/game/GenshinPlayer.java @@ -1,11 +1,6 @@ package emu.grasscutter.game; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Map; -import java.util.Set; +import java.util.*; import dev.morphia.annotations.*; import emu.grasscutter.GenshinConstants; From 27c9545b0d74201d22b95654deed2f8077761b2f Mon Sep 17 00:00:00 2001 From: KingRainbow44 Date: Mon, 18 Apr 2022 23:53:05 -0400 Subject: [PATCH 12/15] `GameServer#getPlayerByUid` = `GameServer#getPlayerById` --- .../java/emu/grasscutter/commands/PlayerCommands.java | 8 ++++---- .../java/emu/grasscutter/commands/ServerCommands.java | 4 ++-- src/main/java/emu/grasscutter/server/game/GameServer.java | 6 ------ 3 files changed, 6 insertions(+), 12 deletions(-) diff --git a/src/main/java/emu/grasscutter/commands/PlayerCommands.java b/src/main/java/emu/grasscutter/commands/PlayerCommands.java index df927dbcc..ae80c5d25 100644 --- a/src/main/java/emu/grasscutter/commands/PlayerCommands.java +++ b/src/main/java/emu/grasscutter/commands/PlayerCommands.java @@ -51,7 +51,7 @@ public final class PlayerCommands { case 2: try { target = Integer.parseInt(args.get(0)); - if(Grasscutter.getGameServer().getPlayerByUid(target) == null) { + if(Grasscutter.getGameServer().getPlayerById(target) == null) { target = player.getId(); amount = Integer.parseInt(args.get(1)); item = Integer.parseInt(args.get(0)); } else { @@ -66,7 +66,7 @@ public final class PlayerCommands { case 3: try { target = Integer.parseInt(args.get(0)); - if(Grasscutter.getGameServer().getPlayerByUid(target) == null) { + if(Grasscutter.getGameServer().getPlayerById(target) == null) { CommandHandler.sendMessage(player, "Invalid player ID."); return; } @@ -80,7 +80,7 @@ public final class PlayerCommands { break; } - GenshinPlayer targetPlayer = Grasscutter.getGameServer().getPlayerByUid(target); + GenshinPlayer targetPlayer = Grasscutter.getGameServer().getPlayerById(target); if(targetPlayer == null) { CommandHandler.sendMessage(player, "Player not found."); return; } @@ -107,7 +107,7 @@ public final class PlayerCommands { int item = Integer.parseInt(args.get(1)); int amount = 1; if(args.size() > 2) amount = Integer.parseInt(args.get(2)); - GenshinPlayer targetPlayer = Grasscutter.getGameServer().getPlayerByUid(target); + GenshinPlayer targetPlayer = Grasscutter.getGameServer().getPlayerById(target); if(targetPlayer == null) { CommandHandler.sendMessage(null, "Player not found."); return; } diff --git a/src/main/java/emu/grasscutter/commands/ServerCommands.java b/src/main/java/emu/grasscutter/commands/ServerCommands.java index 6ae0bb33c..3accd3ce8 100644 --- a/src/main/java/emu/grasscutter/commands/ServerCommands.java +++ b/src/main/java/emu/grasscutter/commands/ServerCommands.java @@ -43,7 +43,7 @@ public final class ServerCommands { int target = Integer.parseInt(args.get(0)); String message = String.join(" ", args.subList(1, args.size())); - GenshinPlayer targetPlayer = Grasscutter.getGameServer().getPlayerByUid(target); + GenshinPlayer targetPlayer = Grasscutter.getGameServer().getPlayerById(target); if(targetPlayer == null) { CommandHandler.sendMessage(null, "Player not found."); return; } @@ -65,7 +65,7 @@ public final class ServerCommands { int target = Integer.parseInt(args.get(0)); String message = String.join(" ", args.subList(1, args.size())); - GenshinPlayer targetPlayer = Grasscutter.getGameServer().getPlayerByUid(target); + GenshinPlayer targetPlayer = Grasscutter.getGameServer().getPlayerById(target); if(targetPlayer == null) { CommandHandler.sendMessage(player, "Player not found."); return; } diff --git a/src/main/java/emu/grasscutter/server/game/GameServer.java b/src/main/java/emu/grasscutter/server/game/GameServer.java index deb215f0d..87a76d400 100644 --- a/src/main/java/emu/grasscutter/server/game/GameServer.java +++ b/src/main/java/emu/grasscutter/server/game/GameServer.java @@ -110,12 +110,6 @@ public final class GameServer extends MihoyoKcpServer { return this.getPlayers().get(id); } - public GenshinPlayer getPlayerByUid(int uid) { - return this.getPlayers().values().stream() - .filter(player -> player.getAccount().getPlayerId() == uid) - .findFirst().orElse(this.getPlayerById(uid)); - } - public GenshinPlayer forceGetPlayerById(int id) { // Console check if (id == GenshinConstants.SERVER_CONSOLE_UID) { From efe183ecf7c8297f9a8eb078d752aac180e4c735 Mon Sep 17 00:00:00 2001 From: Melledy <52122272+Melledy@users.noreply.github.com> Date: Mon, 18 Apr 2022 21:38:19 -0700 Subject: [PATCH 13/15] Fix permissions not saving --- .../emu/grasscutter/commands/ServerCommands.java | 14 ++++++++------ src/main/java/emu/grasscutter/game/Account.java | 5 ++++- .../emu/grasscutter/server/game/GameServer.java | 9 +++++++++ 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/main/java/emu/grasscutter/commands/ServerCommands.java b/src/main/java/emu/grasscutter/commands/ServerCommands.java index 3accd3ce8..1262fae97 100644 --- a/src/main/java/emu/grasscutter/commands/ServerCommands.java +++ b/src/main/java/emu/grasscutter/commands/ServerCommands.java @@ -138,7 +138,7 @@ public final class ServerCommands { String username = args.get(1); String permission = args.get(2); - Account account = DatabaseHelper.getAccountByName(username); + Account account = Grasscutter.getGameServer().getAccountByName(username); if(account == null) { CommandHandler.sendMessage(null, "Account not found."); return; } @@ -146,18 +146,20 @@ public final class ServerCommands { switch(action) { default: CommandHandler.sendMessage(null, "Usage: permission "); - return; + break; case "add": if(account.addPermission(permission)) { - CommandHandler.sendMessage(null, "Permission added."); return; + CommandHandler.sendMessage(null, "Permission added."); } else CommandHandler.sendMessage(null, "They already have this permission!"); - return; + break; case "remove": if(account.removePermission(permission)) { - CommandHandler.sendMessage(null, "Permission removed."); return; + CommandHandler.sendMessage(null, "Permission removed."); } else CommandHandler.sendMessage(null, "They don't have this permission!"); - return; + break; } + + account.save(); } } diff --git a/src/main/java/emu/grasscutter/game/Account.java b/src/main/java/emu/grasscutter/game/Account.java index bba03916a..0d206dda1 100644 --- a/src/main/java/emu/grasscutter/game/Account.java +++ b/src/main/java/emu/grasscutter/game/Account.java @@ -9,6 +9,7 @@ import emu.grasscutter.utils.Crypto; import emu.grasscutter.utils.Utils; import dev.morphia.annotations.IndexOptions; +import java.util.ArrayList; import java.util.List; @Entity(value = "accounts", noClassnameStored = true) @@ -28,7 +29,9 @@ public class Account { private List permissions; @Deprecated - public Account() {} + public Account() { + this.permissions = new ArrayList<>(); + } public String getId() { return id; diff --git a/src/main/java/emu/grasscutter/server/game/GameServer.java b/src/main/java/emu/grasscutter/server/game/GameServer.java index 87a76d400..329a28e61 100644 --- a/src/main/java/emu/grasscutter/server/game/GameServer.java +++ b/src/main/java/emu/grasscutter/server/game/GameServer.java @@ -8,6 +8,7 @@ import emu.grasscutter.GenshinConstants; import emu.grasscutter.Grasscutter; import emu.grasscutter.commands.CommandMap; import emu.grasscutter.database.DatabaseHelper; +import emu.grasscutter.game.Account; import emu.grasscutter.game.GenshinPlayer; import emu.grasscutter.game.dungeons.DungeonManager; import emu.grasscutter.game.gacha.GachaManager; @@ -138,6 +139,14 @@ public final class GameServer extends MihoyoKcpServer { return player.getSocialDetail(); } + public Account getAccountByName(String username) { + Optional playerOpt = getPlayers().values().stream().filter(player -> player.getAccount().getUsername().equals(username)).findFirst(); + if (playerOpt.get() != null) { + return playerOpt.get().getAccount(); + } + return DatabaseHelper.getAccountByName(username); + } + public void onTick() throws Exception { for (GenshinPlayer player : this.getPlayers().values()) { player.onTick(); From 9dc58d8b3b1502de3339a2cccf2589a9e5ba12d2 Mon Sep 17 00:00:00 2001 From: naufal Date: Tue, 19 Apr 2022 11:49:08 +0700 Subject: [PATCH 14/15] feat: added configuration for change upload port feat: added configuration for use non SSL --- src/main/java/emu/grasscutter/Config.java | 3 ++ .../server/dispatch/DispatchServer.java | 42 +++++++++++-------- 2 files changed, 27 insertions(+), 18 deletions(-) diff --git a/src/main/java/emu/grasscutter/Config.java b/src/main/java/emu/grasscutter/Config.java index b7a971122..0a3ff1a4a 100644 --- a/src/main/java/emu/grasscutter/Config.java +++ b/src/main/java/emu/grasscutter/Config.java @@ -6,12 +6,15 @@ public final class Config { public int DispatchServerPort = 443; public String DispatchServerKeystorePath = "./keystore.p12"; public String DispatchServerKeystorePassword = ""; + public Boolean UseSSL = true; public String GameServerName = "Test"; public String GameServerIp = "127.0.0.1"; public String GameServerPublicIp = ""; public int GameServerPort = 22102; + public int UploadLogPort = 80; + public String DatabaseUrl = "mongodb://localhost:27017"; public String DatabaseCollection = "grasscutter"; diff --git a/src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java b/src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java index b014e904d..da90caa42 100644 --- a/src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java +++ b/src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java @@ -139,22 +139,28 @@ public final class DispatchServer { } public void start() throws Exception { - HttpsServer server = HttpsServer.create(getAddress(), 0); - SSLContext sslContext = SSLContext.getInstance("TLS"); - - try (FileInputStream fis = new FileInputStream(Grasscutter.getConfig().DispatchServerKeystorePath)) { - char[] keystorePassword = Grasscutter.getConfig().DispatchServerKeystorePassword.toCharArray(); - KeyStore ks = KeyStore.getInstance("PKCS12"); - ks.load(fis, keystorePassword); - KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); - kmf.init(ks, keystorePassword); - - sslContext.init(kmf.getKeyManagers(), null, null); - - server.setHttpsConfigurator(new HttpsConfigurator(sslContext)); - } catch (Exception e) { - Grasscutter.getLogger().error("No SSL cert found!"); - return; + HttpServer server; + if(Grasscutter.getConfig().UseSSL) { + HttpsServer httpsServer; + httpsServer = HttpsServer.create(getAddress(), 0); + SSLContext sslContext = SSLContext.getInstance("TLS"); + try (FileInputStream fis = new FileInputStream(Grasscutter.getConfig().DispatchServerKeystorePath)) { + char[] keystorePassword = Grasscutter.getConfig().DispatchServerKeystorePassword.toCharArray(); + KeyStore ks = KeyStore.getInstance("PKCS12"); + ks.load(fis, keystorePassword); + KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); + kmf.init(ks, keystorePassword); + + sslContext.init(kmf.getKeyManagers(), null, null); + + httpsServer.setHttpsConfigurator(new HttpsConfigurator(sslContext)); + server = httpsServer; + } catch (Exception e) { + Grasscutter.getLogger().error("No SSL cert found!"); + return; + } + } else { + server = HttpServer.create(getAddress(), 0); } server.createContext("/", t -> { @@ -396,7 +402,7 @@ public final class DispatchServer { overseaLogServer.start(); Grasscutter.getLogger().info("Log server (overseauspider) started on port " + 8888); - HttpServer uploadLogServer = HttpServer.create(new InetSocketAddress(Grasscutter.getConfig().DispatchServerIp, 80), 0); + HttpServer uploadLogServer = HttpServer.create(new InetSocketAddress(Grasscutter.getConfig().DispatchServerIp, Grasscutter.getConfig().UploadLogPort), 0); uploadLogServer.createContext( // log-upload-os.mihoyo.com "/crash/dataUpload", new DispatchHttpJsonHandler("{\"code\":0}") @@ -413,7 +419,7 @@ public final class DispatchServer { os.close(); }); uploadLogServer.start(); - Grasscutter.getLogger().info("Log server (log-upload-os) started on port " + 80); + Grasscutter.getLogger().info("Log server (log-upload-os) started on port " + Grasscutter.getConfig().UploadLogPort); } private Map parseQueryString(String qs) { From 5c71c97108a4640f2cecd601ccb9179f8c096c61 Mon Sep 17 00:00:00 2001 From: Melledy <52122272+Melledy@users.noreply.github.com> Date: Mon, 18 Apr 2022 22:00:01 -0700 Subject: [PATCH 15/15] Grant superuser permissions to accounts created before the permissions update --- src/main/java/emu/grasscutter/game/Account.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/main/java/emu/grasscutter/game/Account.java b/src/main/java/emu/grasscutter/game/Account.java index 0d206dda1..dffbb0d86 100644 --- a/src/main/java/emu/grasscutter/game/Account.java +++ b/src/main/java/emu/grasscutter/game/Account.java @@ -4,6 +4,7 @@ import dev.morphia.annotations.Collation; import dev.morphia.annotations.Entity; import dev.morphia.annotations.Id; import dev.morphia.annotations.Indexed; +import dev.morphia.annotations.PreLoad; import emu.grasscutter.database.DatabaseHelper; import emu.grasscutter.utils.Crypto; import emu.grasscutter.utils.Utils; @@ -12,6 +13,8 @@ import dev.morphia.annotations.IndexOptions; import java.util.ArrayList; import java.util.List; +import com.mongodb.DBObject; + @Entity(value = "accounts", noClassnameStored = true) public class Account { @Id private String id; @@ -114,6 +117,14 @@ public class Account { return this.token; } + @PreLoad + public void onLoad(DBObject dbObj) { + // Grant the superuser permissions to accounts created before the permissions update + if (!dbObj.containsField("permissions")) { + this.addPermission("*"); + } + } + public void save() { DatabaseHelper.saveAccount(this); }