From b80126fdda139a0b2c9ce1bda1b14bc0f5b3fb20 Mon Sep 17 00:00:00 2001 From: AnimeGitB Date: Thu, 28 Jul 2022 19:48:05 +0930 Subject: [PATCH] Add lock function to SetStatsCommand --- .../command/commands/SetStatsCommand.java | 130 +++++++++++++----- .../emu/grasscutter/game/avatar/Avatar.java | 6 + src/main/resources/languages/en-US.json | 6 +- 3 files changed, 110 insertions(+), 32 deletions(-) diff --git a/src/main/java/emu/grasscutter/command/commands/SetStatsCommand.java b/src/main/java/emu/grasscutter/command/commands/SetStatsCommand.java index 2d3c092bc..3e8456fd2 100644 --- a/src/main/java/emu/grasscutter/command/commands/SetStatsCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/SetStatsCommand.java @@ -6,14 +6,23 @@ import java.util.Map; import emu.grasscutter.command.Command; import emu.grasscutter.command.CommandHandler; +import emu.grasscutter.game.avatar.Avatar; import emu.grasscutter.game.entity.EntityAvatar; import emu.grasscutter.game.player.Player; import emu.grasscutter.game.props.FightProperty; import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify; -@Command(label = "setStats", aliases = {"stats", "stat"}, usage = {" "}, permission = "player.setstats", permissionTargeted = "player.setstats.others") +@Command( + label = "setStats", + aliases = {"stats", "stat"}, + usage = { + "[set] ", + "(lock|freeze) []", // Can lock to current value + "(unlock|unfreeze) "}, + permission = "player.setstats", + permissionTargeted = "player.setstats.others") public final class SetStatsCommand implements CommandHandler { - static class Stat { + private static class Stat { String name; FightProperty prop; @@ -27,9 +36,21 @@ public final class SetStatsCommand implements CommandHandler { this.prop = prop; } } - - Map stats; - + + private static enum Action { + ACTION_SET("commands.generic.set_to", "commands.generic.set_for_to"), + ACTION_LOCK("commands.setStats.locked_to", "commands.setStats.locked_for_to"), + ACTION_UNLOCK("commands.setStats.unlocked", "commands.setStats.unlocked_for"); + public final String messageKeySelf; + public final String messageKeyOther; + private Action(String messageKeySelf, String messageKeyOther) { + this.messageKeySelf = messageKeySelf; + this.messageKeyOther = messageKeyOther; + } + } + + private Map stats; + public SetStatsCommand() { this.stats = new HashMap<>(); for (String key : FightProperty.getShortNames()) { @@ -62,50 +83,97 @@ public final class SetStatsCommand implements CommandHandler { this.stats.put("ephys", this.stats.get("phys%")); } + public static float parsePercent(String input) throws NumberFormatException { + if (input.endsWith("%")) { + return Float.parseFloat(input.substring(0, input.length()-1))/100f; + } else { + return Float.parseFloat(input); + } + } + @Override public void execute(Player sender, Player targetPlayer, List args) { - String statStr; + String statStr = null; String valueStr; + float value = 0f; - if (args.size() == 2) { - statStr = args.get(0).toLowerCase(); - valueStr = args.get(1); - } else { + if (args.size() < 2) { sendUsageMessage(sender); return; } + // Get the action and stat + String arg0 = args.remove(0).toLowerCase(); + Action action = switch (arg0) { + default -> {statStr = arg0; yield Action.ACTION_SET;} // Implicit set command + case "set" -> Action.ACTION_SET; // Explicit set command + case "lock", "freeze" -> Action.ACTION_LOCK; + case "unlock", "unfreeze" -> Action.ACTION_UNLOCK; + }; + if (statStr == null) { + statStr = args.remove(0).toLowerCase(); + } + if (!stats.containsKey(statStr)) { + sendUsageMessage(sender); // Invalid stat or action + return; + } + Stat stat = stats.get(statStr); EntityAvatar entity = targetPlayer.getTeamManager().getCurrentAvatarEntity(); + Avatar avatar = entity.getAvatar(); - float value; + // Get the value if the action requires it try { - if (valueStr.endsWith("%")) { - value = Float.parseFloat(valueStr.substring(0, valueStr.length()-1))/100f; - } else { - value = Float.parseFloat(valueStr); + switch (action) { + case ACTION_LOCK: + if (args.isEmpty()) { // Lock to current value + value = avatar.getFightProperty(stat.prop); + break; + } // Else fall-through and lock to supplied value + case ACTION_SET: + value = parsePercent(args.remove(0)); + break; + case ACTION_UNLOCK: + break; } } catch (NumberFormatException ignored) { CommandHandler.sendTranslatedMessage(sender, "commands.generic.invalid.statValue"); return; + } catch (IndexOutOfBoundsException ignored) { + sendUsageMessage(sender); + return; } - if (stats.containsKey(statStr)) { - Stat stat = stats.get(statStr); - entity.setFightProperty(stat.prop, value); - entity.getWorld().broadcastPacket(new PacketEntityFightPropUpdateNotify(entity, stat.prop)); - if (FightProperty.isPercentage(stat.prop)) { - valueStr = String.format("%.1f%%", value * 100f); - } else { - valueStr = String.format("%.0f", value); - } - if (targetPlayer == sender) { - CommandHandler.sendTranslatedMessage(sender, "commands.generic.set_to", stat.name, valueStr); - } else { - String uidStr = targetPlayer.getAccount().getId(); - CommandHandler.sendTranslatedMessage(sender, "commands.generic.set_for_to", stat.name, uidStr, valueStr); - } - } else { + if (!args.isEmpty()) { // Leftover arguments! sendUsageMessage(sender); + return; + } + + switch (action) { + case ACTION_SET: + entity.setFightProperty(stat.prop, value); + entity.getWorld().broadcastPacket(new PacketEntityFightPropUpdateNotify(entity, stat.prop)); + break; + case ACTION_LOCK: + avatar.getFightPropOverrides().put(stat.prop.getId(), value); + avatar.recalcStats(); + break; + case ACTION_UNLOCK: + avatar.getFightPropOverrides().remove(stat.prop.getId()); + avatar.recalcStats(); + break; + } + + // Report action + if (FightProperty.isPercentage(stat.prop)) { + valueStr = String.format("%.1f%%", value * 100f); + } else { + valueStr = String.format("%.0f", value); + } + if (targetPlayer == sender) { + CommandHandler.sendTranslatedMessage(sender, action.messageKeySelf, stat.name, valueStr); + } else { + String uidStr = targetPlayer.getAccount().getId(); + CommandHandler.sendTranslatedMessage(sender, action.messageKeyOther, stat.name, uidStr, valueStr); } return; } diff --git a/src/main/java/emu/grasscutter/game/avatar/Avatar.java b/src/main/java/emu/grasscutter/game/avatar/Avatar.java index 5a42ce925..37f696f48 100644 --- a/src/main/java/emu/grasscutter/game/avatar/Avatar.java +++ b/src/main/java/emu/grasscutter/game/avatar/Avatar.java @@ -63,6 +63,7 @@ import it.unimi.dsi.fastutil.ints.Int2FloatOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import lombok.Getter; @Entity(value = "avatars", useDiscriminator = false) public class Avatar { @@ -85,6 +86,7 @@ public class Avatar { @Transient private final Int2ObjectMap equips; @Transient private final Int2FloatOpenHashMap fightProp; + @Transient @Getter private final Int2FloatOpenHashMap fightPropOverrides; @Transient private Set extraAbilityEmbryos; private List fetters; @@ -111,6 +113,7 @@ public class Avatar { public Avatar() { this.equips = new Int2ObjectOpenHashMap<>(); this.fightProp = new Int2FloatOpenHashMap(); + this.fightPropOverrides = new Int2FloatOpenHashMap(); this.extraAbilityEmbryos = new HashSet<>(); this.proudSkillBonusMap = new HashMap<>(); this.fetters = new ArrayList<>(); // TODO Move to avatar @@ -728,6 +731,9 @@ public class Avatar { (getFightProperty(FightProperty.FIGHT_PROP_BASE_DEFENSE) * (1f + getFightProperty(FightProperty.FIGHT_PROP_DEFENSE_PERCENT))) + getFightProperty(FightProperty.FIGHT_PROP_DEFENSE) ); + // Reapply all overrides + this.fightProp.putAll(this.fightPropOverrides); + // Set current hp this.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, this.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP) * hpPercent); diff --git a/src/main/resources/languages/en-US.json b/src/main/resources/languages/en-US.json index c1b9a6d24..c6cafc301 100644 --- a/src/main/resources/languages/en-US.json +++ b/src/main/resources/languages/en-US.json @@ -274,7 +274,11 @@ "description": "Sets accountwide properties. Things like godmode can be enabled this way, as well as changing things like unlocked abyss floor and battle pass progress.\n\tValues for : godmode | nostamina | unlimitedenergy | abyss | worldlevel | bplevel\n\t(cont.) see PlayerProperty enum for other possible values, of form PROP_MAX_SPRING_VOLUME -> max_spring_volume" }, "setStats": { - "description": "Sets fight property for your current active character\n\tValues for : hp | maxhp | def | atk | em | er | crate | cdmg | cdr | heal | heali | shield | defi\n\t(cont.) Elemental DMG Bonus: epyro | ecryo | ehydro | egeo | edendro | eelectro | ephys\n\t(cont.) Elemental RES: respyro | rescryo | reshydro | resgeo | resdendro | reselectro | resphys" + "description": "Sets fight property for your current active character\n\tValues for : hp | maxhp | def | atk | em | er | crate | cdmg | cdr | heal | heali | shield | defi\n\t(cont.) Elemental DMG Bonus: epyro | ecryo | ehydro | egeo | edendro | eelectro | ephys\n\t(cont.) Elemental RES: respyro | rescryo | reshydro | resgeo | resdendro | reselectro | resphys", + "locked_to": "%s locked to %s.", + "locked_for_to": "%s for %s locked to %s.", + "unlocked": "%s unlocked.", + "unlocked_for": "%s for %s unlocked." }, "spawn": { "success": "Spawned %s of %s.",