diff --git a/README.md b/README.md index 07e4dbeb8..560e73b9a 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ ## Quick setup guide -**Note:** For support please join our [Discord](https://discord.gg/T5vZU6UyeG). +**Note**: For support please join our [Discord](https://discord.gg/T5vZU6UyeG). ### Requirements @@ -86,7 +86,7 @@ cd Grasscutter .\gradlew jar # Compile ``` -##### Linux +##### Linux (GNU) ```bash git clone https://github.com/Grasscutters/Grasscutter.git @@ -97,10 +97,6 @@ chmod +x gradlew You can find the output jar in the root of the project folder. -### Commands have moved to the [wiki](https://github.com/Grasscutters/Grasscutter/wiki/Commands)! +### Troubleshooting -# Quick Troubleshooting - -* If compiling wasn't successful, please check your JDK installation (Make sure its JDK 17 or higher and validated JDK's bin PATH variable). -* My client doesn't connect, doesn't login, 4206, etc... - Mostly your proxy daemon setup is *the issue*. If you're using Fiddler, change the default port to anything other than 8888. -* Startup sequence: MongoDB > Grasscutter > Proxy Daemon (mitmdump, fiddler, etc.) > Game +For a list of common issues and solutions and to ask for help, please join [our Discord server](https://discord.gg/T5vZU6UyeG) and go to the support channel. diff --git a/src/main/java/emu/grasscutter/command/commands/SetPropCommand.java b/src/main/java/emu/grasscutter/command/commands/SetPropCommand.java index 1eef5c47e..194a9a001 100644 --- a/src/main/java/emu/grasscutter/command/commands/SetPropCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/SetPropCommand.java @@ -1,279 +1,277 @@ -package emu.grasscutter.command.commands; - -import emu.grasscutter.command.Command; -import emu.grasscutter.command.CommandHandler; -import emu.grasscutter.data.GameData; -import emu.grasscutter.game.player.Player; -import emu.grasscutter.game.props.PlayerProperty; -import emu.grasscutter.game.tower.TowerLevelRecord; -import emu.grasscutter.server.packet.send.PacketOpenStateChangeNotify; -import emu.grasscutter.server.packet.send.PacketSceneAreaUnlockNotify; -import emu.grasscutter.server.packet.send.PacketScenePointUnlockNotify; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -@Command( - label = "setProp", - aliases = {"prop"}, - usage = {" "}, - permission = "player.setprop", - permissionTargeted = "player.setprop.others") -public final class SetPropCommand implements CommandHandler { - // List of map areas. Unfortunately, there is no readily available source for them in excels or - // bins. - private static final List sceneAreas = - List.of( - 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, - 28, 29, 32, 100, 101, 102, 103, 200, 210, 300, 400, 401, 402, 403); - Map props; - - public SetPropCommand() { - this.props = new HashMap<>(); - // Full PlayerProperty enum that won't be advertised but can be used by devs - for (PlayerProperty prop : PlayerProperty.values()) { - String name = prop.toString().substring(5); // PROP_EXP -> EXP - String key = name.toLowerCase(); // EXP -> exp - this.props.put(key, new Prop(name, prop)); - } - // Add special props - Prop worldlevel = - new Prop("World Level", PlayerProperty.PROP_PLAYER_WORLD_LEVEL, PseudoProp.WORLD_LEVEL); - this.props.put("worldlevel", worldlevel); - this.props.put("wl", worldlevel); - - Prop abyss = new Prop("Tower Level", PseudoProp.TOWER_LEVEL); - this.props.put("abyss", abyss); - this.props.put("abyssfloor", abyss); - this.props.put("ut", abyss); - this.props.put("tower", abyss); - this.props.put("towerlevel", abyss); - this.props.put("unlocktower", abyss); - - Prop bplevel = new Prop("BP Level", PseudoProp.BP_LEVEL); - this.props.put("bplevel", bplevel); - this.props.put("bp", bplevel); - this.props.put("battlepass", bplevel); - - Prop godmode = new Prop("GodMode", PseudoProp.GOD_MODE); - this.props.put("godmode", godmode); - this.props.put("god", godmode); - - Prop nostamina = new Prop("UnlimitedStamina", PseudoProp.UNLIMITED_STAMINA); - this.props.put("unlimitedstamina", nostamina); - this.props.put("us", nostamina); - this.props.put("nostamina", nostamina); - this.props.put("nostam", nostamina); - this.props.put("ns", nostamina); - - Prop unlimitedenergy = new Prop("UnlimitedEnergy", PseudoProp.UNLIMITED_ENERGY); - this.props.put("unlimitedenergy", unlimitedenergy); - this.props.put("ue", unlimitedenergy); - - Prop setopenstate = new Prop("SetOpenstate", PseudoProp.SET_OPENSTATE); - this.props.put("setopenstate", setopenstate); - this.props.put("so", setopenstate); - - Prop unsetopenstate = new Prop("UnsetOpenstate", PseudoProp.UNSET_OPENSTATE); - this.props.put("unsetopenstate", unsetopenstate); - this.props.put("uo", unsetopenstate); - - Prop unlockmap = new Prop("UnlockMap", PseudoProp.UNLOCK_MAP); - this.props.put("unlockmap", unlockmap); - this.props.put("um", unlockmap); - } - - @Override - public void execute(Player sender, Player targetPlayer, List args) { - if (args.size() != 2) { - sendUsageMessage(sender); - return; - } - String propStr = args.get(0).toLowerCase(); - String valueStr = args.get(1).toLowerCase(); - int value; - - if (!props.containsKey(propStr)) { - sendUsageMessage(sender); - return; - } - try { - value = - switch (valueStr.toLowerCase()) { - case "on", "true" -> 1; - case "off", "false" -> 0; - case "toggle" -> -1; - default -> Integer.parseInt(valueStr); - }; - } catch (NumberFormatException ignored) { - CommandHandler.sendTranslatedMessage(sender, "commands.execution.argument_error"); - return; - } - - boolean success = false; - Prop prop = props.get(propStr); - - success = - switch (prop.pseudoProp) { - case WORLD_LEVEL -> targetPlayer.setWorldLevel(value); - case BP_LEVEL -> targetPlayer.getBattlePassManager().setLevel(value); - case TOWER_LEVEL -> this.setTowerLevel(sender, targetPlayer, value); - case GOD_MODE, UNLIMITED_STAMINA, UNLIMITED_ENERGY -> this.setBool( - sender, targetPlayer, prop.pseudoProp, value); - case SET_OPENSTATE -> this.setOpenState(targetPlayer, value, 1); - case UNSET_OPENSTATE -> this.setOpenState(targetPlayer, value, 0); - case UNLOCK_MAP -> unlockMap(targetPlayer); - default -> targetPlayer.setProperty(prop.prop, value); - }; - - if (success) { - if (targetPlayer == sender) { - CommandHandler.sendTranslatedMessage( - sender, "commands.generic.set_to", prop.name, valueStr); - } else { - String uidStr = targetPlayer.getAccount().getId(); - CommandHandler.sendTranslatedMessage( - sender, "commands.generic.set_for_to", prop.name, uidStr, valueStr); - } - } else { - if (prop.prop - != PlayerProperty.PROP_NONE) { // PseudoProps need to do their own error messages - int min = targetPlayer.getPropertyMin(prop.prop); - int max = targetPlayer.getPropertyMax(prop.prop); - CommandHandler.sendTranslatedMessage( - sender, "commands.generic.invalid.value_between", prop.name, min, max); - } - } - } - - private boolean setTowerLevel(Player sender, Player targetPlayer, int topFloor) { - List floorIds = targetPlayer.getServer().getTowerSystem().getAllFloors(); - if (topFloor < 0 || topFloor > floorIds.size()) { - CommandHandler.sendTranslatedMessage( - sender, "commands.generic.invalid.value_between", "Tower Level", 0, floorIds.size()); - return false; - } - - Map recordMap = targetPlayer.getTowerManager().getRecordMap(); - // Add records for each unlocked floor - for (int floor : floorIds.subList(0, topFloor)) { - if (!recordMap.containsKey(floor)) { - recordMap.put(floor, new TowerLevelRecord(floor)); - } - } - // Remove records for each floor past our target - for (int floor : floorIds.subList(topFloor, floorIds.size())) { - recordMap.remove(floor); - } - // Six stars required on Floor 8 to unlock Floor 9+ - if (topFloor > 8) { - recordMap - .get(floorIds.get(7)) - .setLevelStars( - 0, - 6); // levelIds seem to start at 1 for Floor 1 Chamber 1, so this doesn't get shown at - // all - } - return true; - } - - private boolean setBool(Player sender, Player targetPlayer, PseudoProp pseudoProp, int value) { - boolean enabled = - switch (pseudoProp) { - case GOD_MODE -> targetPlayer.inGodmode(); - case UNLIMITED_STAMINA -> targetPlayer.getUnlimitedStamina(); - case UNLIMITED_ENERGY -> !targetPlayer.getEnergyManager().getEnergyUsage(); - default -> false; - }; - enabled = - switch (value) { - case -1 -> !enabled; - case 0 -> false; - default -> true; - }; - - switch (pseudoProp) { - case GOD_MODE: - targetPlayer.setGodmode(enabled); - break; - case UNLIMITED_STAMINA: - targetPlayer.setUnlimitedStamina(enabled); - break; - case UNLIMITED_ENERGY: - targetPlayer.getEnergyManager().setEnergyUsage(!enabled); - break; - default: - return false; - } - return true; - } - - private boolean setOpenState(Player targetPlayer, int state, int value) { - targetPlayer.sendPacket(new PacketOpenStateChangeNotify(state, value)); - return true; - } - - private boolean unlockMap(Player targetPlayer) { - // Unlock. - GameData.getScenePointsPerScene() - .forEach( - (sceneId, scenePoints) -> { - // Unlock trans points. - targetPlayer.getUnlockedScenePoints(sceneId).addAll(scenePoints); - - // Unlock map areas. - targetPlayer.getUnlockedSceneAreas(sceneId).addAll(sceneAreas); - }); - - // Send notify. - int playerScene = targetPlayer.getSceneId(); - targetPlayer.sendPacket( - new PacketScenePointUnlockNotify( - playerScene, targetPlayer.getUnlockedScenePoints(playerScene))); - targetPlayer.sendPacket( - new PacketSceneAreaUnlockNotify( - playerScene, targetPlayer.getUnlockedSceneAreas(playerScene))); - return true; - } - - enum PseudoProp { - NONE, - WORLD_LEVEL, - TOWER_LEVEL, - BP_LEVEL, - GOD_MODE, - UNLIMITED_STAMINA, - UNLIMITED_ENERGY, - SET_OPENSTATE, - UNSET_OPENSTATE, - UNLOCK_MAP - } - - static class Prop { - String name; - PlayerProperty prop; - PseudoProp pseudoProp; - - public Prop(PlayerProperty prop) { - this(prop.toString(), prop, PseudoProp.NONE); - } - - public Prop(String name) { - this(name, PlayerProperty.PROP_NONE, PseudoProp.NONE); - } - - public Prop(String name, PseudoProp pseudoProp) { - this(name, PlayerProperty.PROP_NONE, pseudoProp); - } - - public Prop(String name, PlayerProperty prop) { - this(name, prop, PseudoProp.NONE); - } - - public Prop(String name, PlayerProperty prop, PseudoProp pseudoProp) { - this.name = name; - this.prop = prop; - this.pseudoProp = pseudoProp; - } - } -} +package emu.grasscutter.command.commands; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.IntStream; + +import emu.grasscutter.command.Command; +import emu.grasscutter.command.CommandHandler; +import emu.grasscutter.data.GameData; +import emu.grasscutter.game.player.Player; +import emu.grasscutter.game.props.PlayerProperty; +import emu.grasscutter.game.tower.TowerLevelRecord; +import emu.grasscutter.server.packet.send.PacketOpenStateChangeNotify; +import emu.grasscutter.server.packet.send.PacketSceneAreaUnlockNotify; +import emu.grasscutter.server.packet.send.PacketScenePointUnlockNotify; + +@Command( + label = "setProp", + aliases = {"prop"}, + usage = {" "}, + permission = "player.setprop", + permissionTargeted = "player.setprop.others") +public final class SetPropCommand implements CommandHandler { + // List of map areas. Unfortunately, there is no readily available source for them in excels or + // bins. + private static final List sceneAreas = IntStream.range(1, 1000).boxed().toList();Map props; + + public SetPropCommand() { + this.props = new HashMap<>(); + // Full PlayerProperty enum that won't be advertised but can be used by devs + for (PlayerProperty prop : PlayerProperty.values()) { + String name = prop.toString().substring(5); // PROP_EXP -> EXP + String key = name.toLowerCase(); // EXP -> exp + this.props.put(key, new Prop(name, prop)); + } + // Add special props + Prop worldlevel = + new Prop("World Level", PlayerProperty.PROP_PLAYER_WORLD_LEVEL, PseudoProp.WORLD_LEVEL); + this.props.put("worldlevel", worldlevel); + this.props.put("wl", worldlevel); + + Prop abyss = new Prop("Tower Level", PseudoProp.TOWER_LEVEL); + this.props.put("abyss", abyss); + this.props.put("abyssfloor", abyss); + this.props.put("ut", abyss); + this.props.put("tower", abyss); + this.props.put("towerlevel", abyss); + this.props.put("unlocktower", abyss); + + Prop bplevel = new Prop("BP Level", PseudoProp.BP_LEVEL); + this.props.put("bplevel", bplevel); + this.props.put("bp", bplevel); + this.props.put("battlepass", bplevel); + + Prop godmode = new Prop("GodMode", PseudoProp.GOD_MODE); + this.props.put("godmode", godmode); + this.props.put("god", godmode); + + Prop nostamina = new Prop("UnlimitedStamina", PseudoProp.UNLIMITED_STAMINA); + this.props.put("unlimitedstamina", nostamina); + this.props.put("us", nostamina); + this.props.put("nostamina", nostamina); + this.props.put("nostam", nostamina); + this.props.put("ns", nostamina); + + Prop unlimitedenergy = new Prop("UnlimitedEnergy", PseudoProp.UNLIMITED_ENERGY); + this.props.put("unlimitedenergy", unlimitedenergy); + this.props.put("ue", unlimitedenergy); + + Prop setopenstate = new Prop("SetOpenstate", PseudoProp.SET_OPENSTATE); + this.props.put("setopenstate", setopenstate); + this.props.put("so", setopenstate); + + Prop unsetopenstate = new Prop("UnsetOpenstate", PseudoProp.UNSET_OPENSTATE); + this.props.put("unsetopenstate", unsetopenstate); + this.props.put("uo", unsetopenstate); + + Prop unlockmap = new Prop("UnlockMap", PseudoProp.UNLOCK_MAP); + this.props.put("unlockmap", unlockmap); + this.props.put("um", unlockmap); + } + + @Override + public void execute(Player sender, Player targetPlayer, List args) { + if (args.size() != 2) { + sendUsageMessage(sender); + return; + } + String propStr = args.get(0).toLowerCase(); + String valueStr = args.get(1).toLowerCase(); + int value; + + if (!props.containsKey(propStr)) { + sendUsageMessage(sender); + return; + } + try { + value = + switch (valueStr.toLowerCase()) { + case "on", "true" -> 1; + case "off", "false" -> 0; + case "toggle" -> -1; + default -> Integer.parseInt(valueStr); + }; + } catch (NumberFormatException ignored) { + CommandHandler.sendTranslatedMessage(sender, "commands.execution.argument_error"); + return; + } + + boolean success = false; + Prop prop = props.get(propStr); + + success = + switch (prop.pseudoProp) { + case WORLD_LEVEL -> targetPlayer.setWorldLevel(value); + case BP_LEVEL -> targetPlayer.getBattlePassManager().setLevel(value); + case TOWER_LEVEL -> this.setTowerLevel(sender, targetPlayer, value); + case GOD_MODE, UNLIMITED_STAMINA, UNLIMITED_ENERGY -> this.setBool( + sender, targetPlayer, prop.pseudoProp, value); + case SET_OPENSTATE -> this.setOpenState(targetPlayer, value, 1); + case UNSET_OPENSTATE -> this.setOpenState(targetPlayer, value, 0); + case UNLOCK_MAP -> unlockMap(targetPlayer); + default -> targetPlayer.setProperty(prop.prop, value); + }; + + if (success) { + if (targetPlayer == sender) { + CommandHandler.sendTranslatedMessage( + sender, "commands.generic.set_to", prop.name, valueStr); + } else { + String uidStr = targetPlayer.getAccount().getId(); + CommandHandler.sendTranslatedMessage( + sender, "commands.generic.set_for_to", prop.name, uidStr, valueStr); + } + } else { + if (prop.prop + != PlayerProperty.PROP_NONE) { // PseudoProps need to do their own error messages + int min = targetPlayer.getPropertyMin(prop.prop); + int max = targetPlayer.getPropertyMax(prop.prop); + CommandHandler.sendTranslatedMessage( + sender, "commands.generic.invalid.value_between", prop.name, min, max); + } + } + } + + private boolean setTowerLevel(Player sender, Player targetPlayer, int topFloor) { + List floorIds = targetPlayer.getServer().getTowerSystem().getAllFloors(); + if (topFloor < 0 || topFloor > floorIds.size()) { + CommandHandler.sendTranslatedMessage( + sender, "commands.generic.invalid.value_between", "Tower Level", 0, floorIds.size()); + return false; + } + + Map recordMap = targetPlayer.getTowerManager().getRecordMap(); + // Add records for each unlocked floor + for (int floor : floorIds.subList(0, topFloor)) { + if (!recordMap.containsKey(floor)) { + recordMap.put(floor, new TowerLevelRecord(floor)); + } + } + // Remove records for each floor past our target + for (int floor : floorIds.subList(topFloor, floorIds.size())) { + recordMap.remove(floor); + } + // Six stars required on Floor 8 to unlock Floor 9+ + if (topFloor > 8) { + recordMap + .get(floorIds.get(7)) + .setLevelStars( + 0, + 6); // levelIds seem to start at 1 for Floor 1 Chamber 1, so this doesn't get shown at + // all + } + return true; + } + + private boolean setBool(Player sender, Player targetPlayer, PseudoProp pseudoProp, int value) { + boolean enabled = + switch (pseudoProp) { + case GOD_MODE -> targetPlayer.inGodmode(); + case UNLIMITED_STAMINA -> targetPlayer.getUnlimitedStamina(); + case UNLIMITED_ENERGY -> !targetPlayer.getEnergyManager().getEnergyUsage(); + default -> false; + }; + enabled = + switch (value) { + case -1 -> !enabled; + case 0 -> false; + default -> true; + }; + + switch (pseudoProp) { + case GOD_MODE: + targetPlayer.setGodmode(enabled); + break; + case UNLIMITED_STAMINA: + targetPlayer.setUnlimitedStamina(enabled); + break; + case UNLIMITED_ENERGY: + targetPlayer.getEnergyManager().setEnergyUsage(!enabled); + break; + default: + return false; + } + return true; + } + + private boolean setOpenState(Player targetPlayer, int state, int value) { + targetPlayer.sendPacket(new PacketOpenStateChangeNotify(state, value)); + return true; + } + + private boolean unlockMap(Player targetPlayer) { + // Unlock. + GameData.getScenePointsPerScene() + .forEach( + (sceneId, scenePoints) -> { + // Unlock trans points. + targetPlayer.getUnlockedScenePoints(sceneId).addAll(scenePoints); + + // Unlock map areas. + targetPlayer.getUnlockedSceneAreas(sceneId).addAll(sceneAreas); + }); + + // Send notify. + int playerScene = targetPlayer.getSceneId(); + targetPlayer.sendPacket( + new PacketScenePointUnlockNotify( + playerScene, targetPlayer.getUnlockedScenePoints(playerScene))); + targetPlayer.sendPacket( + new PacketSceneAreaUnlockNotify( + playerScene, targetPlayer.getUnlockedSceneAreas(playerScene))); + return true; + } + + enum PseudoProp { + NONE, + WORLD_LEVEL, + TOWER_LEVEL, + BP_LEVEL, + GOD_MODE, + UNLIMITED_STAMINA, + UNLIMITED_ENERGY, + SET_OPENSTATE, + UNSET_OPENSTATE, + UNLOCK_MAP + } + + static class Prop { + String name; + PlayerProperty prop; + PseudoProp pseudoProp; + + public Prop(PlayerProperty prop) { + this(prop.toString(), prop, PseudoProp.NONE); + } + + public Prop(String name) { + this(name, PlayerProperty.PROP_NONE, PseudoProp.NONE); + } + + public Prop(String name, PseudoProp pseudoProp) { + this(name, PlayerProperty.PROP_NONE, pseudoProp); + } + + public Prop(String name, PlayerProperty prop) { + this(name, prop, PseudoProp.NONE); + } + + public Prop(String name, PlayerProperty prop, PseudoProp pseudoProp) { + this.name = name; + this.prop = prop; + this.pseudoProp = pseudoProp; + } + } +}