From baafb4104c81a18886edc290b47237893af43e1a Mon Sep 17 00:00:00 2001 From: AnimeGitB Date: Sat, 25 Jun 2022 01:36:19 +0930 Subject: [PATCH] Remove GiveAll, GiveArt, GiveChar commands --- .../command/commands/GiveAllCommand.java | 184 ------ .../command/commands/GiveArtifactCommand.java | 208 ------- .../command/commands/GiveCharCommand.java | 86 --- .../command/commands/GiveCommand.java | 524 ++++++++++++++---- .../command/commands/SetStatsCommand.java | 182 ++---- .../java/emu/grasscutter/data/GameDepot.java | 19 +- .../emu/grasscutter/data/excels/ItemData.java | 229 ++------ .../emu/grasscutter/game/avatar/Avatar.java | 17 + .../grasscutter/game/inventory/GameItem.java | 270 ++++----- .../game/managers/InventoryManager.java | 10 +- .../grasscutter/game/props/FightProperty.java | 67 +++ .../packet/send/PacketGetMailItemRsp.java | 16 +- .../java/emu/grasscutter/utils/SparseSet.java | 60 ++ src/main/resources/languages/en-US.json | 35 +- src/main/resources/languages/fr-FR.json | 11 +- src/main/resources/languages/pl-PL.json | 12 +- src/main/resources/languages/zh-CN.json | 11 +- src/main/resources/languages/zh-TW.json | 11 +- 18 files changed, 766 insertions(+), 1186 deletions(-) delete mode 100644 src/main/java/emu/grasscutter/command/commands/GiveAllCommand.java delete mode 100644 src/main/java/emu/grasscutter/command/commands/GiveArtifactCommand.java delete mode 100644 src/main/java/emu/grasscutter/command/commands/GiveCharCommand.java create mode 100644 src/main/java/emu/grasscutter/utils/SparseSet.java diff --git a/src/main/java/emu/grasscutter/command/commands/GiveAllCommand.java b/src/main/java/emu/grasscutter/command/commands/GiveAllCommand.java deleted file mode 100644 index 7e74c9230..000000000 --- a/src/main/java/emu/grasscutter/command/commands/GiveAllCommand.java +++ /dev/null @@ -1,184 +0,0 @@ -package emu.grasscutter.command.commands; - -import emu.grasscutter.GameConstants; -import emu.grasscutter.Grasscutter; -import emu.grasscutter.command.Command; -import emu.grasscutter.command.CommandHandler; -import emu.grasscutter.data.GameData; -import emu.grasscutter.data.excels.AvatarData; -import emu.grasscutter.data.excels.ItemData; -import emu.grasscutter.game.avatar.Avatar; -import emu.grasscutter.game.inventory.GameItem; -import emu.grasscutter.game.inventory.ItemType; -import emu.grasscutter.game.player.Player; - -import java.util.*; - -import static emu.grasscutter.utils.Language.translate; - -@Command(label = "giveall", usage = "giveall [amount]", aliases = {"givea"}, permission = "player.giveall", permissionTargeted = "player.giveall.others", threading = true, description = "commands.giveAll.description") -public final class GiveAllCommand implements CommandHandler { - - @Override - public void execute(Player sender, Player targetPlayer, List args) { - int amount = 99999; - - switch (args.size()) { - case 0: - break; - case 1: // [amount] - try { - amount = Integer.parseInt(args.get(0)); - } catch (NumberFormatException ignored) { - CommandHandler.sendMessage(sender, translate(sender, "commands.generic.invalid.amount")); - return; - } - break; - default: // invalid - CommandHandler.sendMessage(sender, translate(sender, "commands.giveAll.usage")); - return; - } - - this.giveAllItems(targetPlayer, amount); - CommandHandler.sendMessage(sender, translate(targetPlayer, "commands.giveAll.success", targetPlayer.getNickname())); - } - - public void giveAllItems(Player player, int amount) { - CommandHandler.sendMessage(player, translate(player, "commands.giveAll.started")); - - for (AvatarData avatarData: GameData.getAvatarDataMap().values()) { - //Exclude test avatar - if (isTestAvatar(avatarData.getId())) continue; - - Avatar avatar = new Avatar(avatarData); - avatar.setLevel(90); - avatar.setPromoteLevel(6); - - // Add constellations. - int talentBase = switch (avatar.getAvatarId()) { - case 10000005 -> 70; - case 10000006 -> 40; - default -> (avatar.getAvatarId()-10000000)*10; - }; - - for(int i = 1;i <= 6;++i){ - avatar.getTalentIdList().add(talentBase + i); - } - - // Handle skill depot for traveller. - if (avatar.getAvatarId() == GameConstants.MAIN_CHARACTER_MALE) { - avatar.setSkillDepotData(GameData.getAvatarSkillDepotDataMap().get(504)); - } - else if(avatar.getAvatarId() == GameConstants.MAIN_CHARACTER_FEMALE) { - avatar.setSkillDepotData(GameData.getAvatarSkillDepotDataMap().get(704)); - } - - // This will handle stats and talents - avatar.recalcStats(); - // Don't try to add each avatar to the current team - player.addAvatar(avatar, false); - } - - //some test items - List itemList = new ArrayList<>(); - for (ItemData itemdata: GameData.getItemDataMap().values()) { - //Exclude test item - if (isTestItem(itemdata.getId())) continue; - - if (itemdata.isEquip()) { - if (itemdata.getItemType() == ItemType.ITEM_WEAPON) { - for (int i = 0; i < 5; ++i) { - GameItem item = new GameItem(itemdata); - item.setLevel(90); - item.setPromoteLevel(6); - item.setRefinement(4); - itemList.add(item); - } - } - } - else { - GameItem item = new GameItem(itemdata); - item.setCount(amount); - itemList.add(item); - } - } - int packetNum = 10; - int itemLength = itemList.size(); - int number = itemLength / packetNum; - int remainder = itemLength % packetNum; - int offset = 0; - for (int i = 0; i < packetNum; ++i) { - if (remainder > 0) { - player.getInventory().addItems(itemList.subList(i * number + offset, (i + 1) * number + offset + 1)); - --remainder; - ++offset; - } - else { - player.getInventory().addItems(itemList.subList(i * number + offset, (i + 1) * number + offset)); - } - } - } - - public boolean isTestAvatar(int avatarId) { - return avatarId < 10000002 || avatarId >= 11000000; - } - - public boolean isTestItem(int itemId) { - for (Range range: testItemRanges) { - if (range.check(itemId)) { - return true; - } - } - - return testItemsList.contains(itemId); - } - - static class Range { - private final int min, max; - - public Range(int min, int max) { - if(min > max){ - min ^= max; - max ^= min; - min ^= max; - } - - this.min = min; - this.max = max; - } - - public boolean check(int value) { - return value >= this.min && value <= this.max; - } - } - - private static final Range[] testItemRanges = new Range[] { - new Range(106, 139), - new Range(1000, 1099), - new Range(2001, 3022), - new Range(23300, 23340), - new Range(23383, 23385), - new Range(78310, 78554), - new Range(99310, 99554), - new Range(100001, 100187), - new Range(100210, 100214), - new Range(100303, 100398), - new Range(100414, 100425), - new Range(100454, 103008), - new Range(109000, 109492), - new Range(115001, 118004), - new Range(141001, 141072), - new Range(220050, 221016), - }; - private static final Integer[] testItemsIds = new Integer[] { - 210, 211, 314, 315, 317, 1005, 1007, 1105, 1107, 1201, 1202,10366, - 101212, 11411, 11506, 11507, 11508, 12505, 12506, 12508, 12509, 13503, - 13506, 14411, 14503, 14505, 14508, 15504, 15505, 15506, - 20001, 10002, 10003, 10004, 10005, 10006, 10008,100231,100232,100431, - 101689,105001,105004, 106000,106001,108000,110000 - }; - - private static final Collection testItemsList = Arrays.asList(testItemsIds); - -} - diff --git a/src/main/java/emu/grasscutter/command/commands/GiveArtifactCommand.java b/src/main/java/emu/grasscutter/command/commands/GiveArtifactCommand.java deleted file mode 100644 index eaa0635dc..000000000 --- a/src/main/java/emu/grasscutter/command/commands/GiveArtifactCommand.java +++ /dev/null @@ -1,208 +0,0 @@ -package emu.grasscutter.command.commands; - -import emu.grasscutter.Grasscutter; -import emu.grasscutter.command.Command; -import emu.grasscutter.command.CommandHandler; -import emu.grasscutter.data.GameData; -import emu.grasscutter.data.excels.ItemData; -import emu.grasscutter.game.inventory.GameItem; -import emu.grasscutter.game.inventory.ItemType; -import emu.grasscutter.game.player.Player; -import emu.grasscutter.game.props.ActionReason; -import emu.grasscutter.game.inventory.EquipType; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import static java.util.Map.entry; - -import static emu.grasscutter.utils.Language.translate; - -@Command(label = "giveart", usage = "giveart [[,]]... [level]", aliases = {"gart"}, permission = "player.giveart", permissionTargeted = "player.giveart.others", description = "commands.giveArtifact.description") -public final class GiveArtifactCommand implements CommandHandler { - private static final Map> mainPropMap = Map.ofEntries( - entry("hp", Map.ofEntries(entry(EquipType.EQUIP_BRACER, 14001))), - entry("hp%", Map.ofEntries(entry(EquipType.EQUIP_SHOES, 10980), entry(EquipType.EQUIP_RING, 50980), entry(EquipType.EQUIP_DRESS, 30980))), - entry("atk", Map.ofEntries(entry(EquipType.EQUIP_NECKLACE, 12001))), - entry("atk%", Map.ofEntries(entry(EquipType.EQUIP_SHOES, 10990), entry(EquipType.EQUIP_RING, 50990), entry(EquipType.EQUIP_DRESS, 30990))), - entry("def%", Map.ofEntries(entry(EquipType.EQUIP_SHOES, 10970), entry(EquipType.EQUIP_RING, 50970), entry(EquipType.EQUIP_DRESS, 30970))), - entry("er", Map.ofEntries(entry(EquipType.EQUIP_SHOES, 10960))), - entry("em", Map.ofEntries(entry(EquipType.EQUIP_SHOES, 10950), entry(EquipType.EQUIP_RING, 50880), entry(EquipType.EQUIP_DRESS, 30930))), - entry("hb", Map.ofEntries(entry(EquipType.EQUIP_DRESS, 30940))), - entry("cdmg", Map.ofEntries(entry(EquipType.EQUIP_DRESS, 30950))), - entry("cr", Map.ofEntries(entry(EquipType.EQUIP_DRESS, 30960))), - entry("phys%", Map.ofEntries(entry(EquipType.EQUIP_RING, 50890))), - entry("dendro%", Map.ofEntries(entry(EquipType.EQUIP_RING, 50900))), - entry("geo%", Map.ofEntries(entry(EquipType.EQUIP_RING, 50910))), - entry("anemo%", Map.ofEntries(entry(EquipType.EQUIP_RING, 50920))), - entry("hydro%", Map.ofEntries(entry(EquipType.EQUIP_RING, 50930))), - entry("cryo%", Map.ofEntries(entry(EquipType.EQUIP_RING, 50940))), - entry("electro%", Map.ofEntries(entry(EquipType.EQUIP_RING, 50950))), - entry("pyro%", Map.ofEntries(entry(EquipType.EQUIP_RING, 50960))) - ); - private static final Map appendPropMap = Map.ofEntries( - entry("hp", "0102"), - entry("hp%", "0103"), - entry("atk", "0105"), - entry("atk%", "0106"), - entry("def", "0108"), - entry("def%", "0109"), - entry("er", "0123"), - entry("em", "0124"), - entry("cr", "0120"), - entry("cdmg", "0122") - ); - - private int getAppendPropId(String substatText, ItemData itemData) { - int res; - - // If the given substat text is an integer, we just use that - // as the append prop ID. - try { - res = Integer.parseInt(substatText); - return res; - } - catch (NumberFormatException ignores) { - // No need to handle this here. We just continue with the - // possibility of the argument being a substat string. - } - - // If the argument was not an integer, we try to determine - // the append prop ID from the given text + artifact information. - // A substat string has the format `substat_tier`, with the - // `_tier` part being optional. - String[] substatArgs = substatText.split("_"); - String substatType; - int substatTier; - - if (substatArgs.length == 1) { - substatType = substatArgs[0]; - substatTier = - itemData.getRankLevel() == 1 ? 2 - : itemData.getRankLevel() == 2 ? 3 - : 4; - } - else if (substatArgs.length == 2) { - substatType = substatArgs[0]; - substatTier = Integer.parseInt(substatArgs[1]); - } - else { - throw new IllegalArgumentException(); - } - - // Check if the specified tier is legal for the artifact rarity. - if (substatTier < 1 || substatTier > 4) { - throw new IllegalArgumentException(); - } - if (itemData.getRankLevel() == 1 && substatTier > 2) { - throw new IllegalArgumentException(); - } - if (itemData.getRankLevel() == 2 && substatTier > 3) { - throw new IllegalArgumentException(); - } - - // Check if the given substat type string is a legal stat. - if (!appendPropMap.containsKey(substatType)) { - throw new IllegalArgumentException(); - } - - // Build the append prop ID. - return Integer.parseInt(Integer.toString(itemData.getRankLevel()) + appendPropMap.get(substatType) + Integer.toString(substatTier)); - } - - @Override - public void execute(Player sender, Player targetPlayer, List args) { - // Sanity check - if (args.size() < 2) { - CommandHandler.sendMessage(sender, translate(sender, "commands.giveArtifact.usage")); - return; - } - - // Get the artifact piece ID from the arguments. - int itemId; - try { - itemId = Integer.parseInt(args.remove(0)); - } catch (NumberFormatException ignored) { - CommandHandler.sendMessage(sender, translate(sender, "commands.giveArtifact.id_error")); - return; - } - - ItemData itemData = GameData.getItemDataMap().get(itemId); - if (itemData.getItemType() != ItemType.ITEM_RELIQUARY) { - CommandHandler.sendMessage(sender, translate(sender, "commands.giveArtifact.id_error")); - return; - } - - // Get the main stat from the arguments. - // If the given argument is an integer, we use that. - // If not, we check if the argument string is in the main prop map. - String mainPropIdString = args.remove(0); - int mainPropId; - - try { - mainPropId = Integer.parseInt(mainPropIdString); - } catch (NumberFormatException ignored) { - mainPropId = -1; - } - - if (mainPropMap.containsKey(mainPropIdString) && mainPropMap.get(mainPropIdString).containsKey(itemData.getEquipType())) { - mainPropId = mainPropMap.get(mainPropIdString).get(itemData.getEquipType()); - } - - if (mainPropId == -1) { - CommandHandler.sendMessage(sender, translate(sender, "commands.execution.argument_error")); - return; - } - - // Get the level from the arguments. - int level = 1; - try { - int last = Integer.parseInt(args.get(args.size()-1)); - if (last > 0 && last < 22) { // Luckily appendPropIds aren't in the range of [1,21] - level = last; - args.remove(args.size()-1); - } - } catch (NumberFormatException ignored) { // Could be a stat,times string so no need to panic - } - - // Get substats. - ArrayList appendPropIdList = new ArrayList<>(); - try { - // Every remaining argument is a substat. - args.forEach(it -> { - // The substat syntax permits specifying a number of rolls for the given - // substat. Split the string into stat and number if that is the case here. - String[] arr; - int n = 1; - if ((arr = it.split(",")).length == 2) { - it = arr[0]; - n = Integer.parseInt(arr[1]); - if (n > 200) { - n = 200; - } - } - - // Determine the substat ID. - int appendPropId = getAppendPropId(it, itemData); - - // Add the current substat. - appendPropIdList.addAll(Collections.nCopies(n, appendPropId)); - }); - } catch (Exception ignored) { - CommandHandler.sendMessage(sender, translate(sender, "commands.execution.argument_error")); - return; - } - - // Create item for the artifact. - GameItem item = new GameItem(itemData); - item.setLevel(level); - item.setMainPropId(mainPropId); - item.getAppendPropIdList().clear(); - item.getAppendPropIdList().addAll(appendPropIdList); - targetPlayer.getInventory().addItem(item, ActionReason.SubfieldDrop); - - CommandHandler.sendMessage(sender, translate(sender, "commands.giveArtifact.success", Integer.toString(itemId), Integer.toString(targetPlayer.getUid()))); - } -} - diff --git a/src/main/java/emu/grasscutter/command/commands/GiveCharCommand.java b/src/main/java/emu/grasscutter/command/commands/GiveCharCommand.java deleted file mode 100644 index 587988b0a..000000000 --- a/src/main/java/emu/grasscutter/command/commands/GiveCharCommand.java +++ /dev/null @@ -1,86 +0,0 @@ -package emu.grasscutter.command.commands; - -import emu.grasscutter.GameConstants; -import emu.grasscutter.Grasscutter; -import emu.grasscutter.command.Command; -import emu.grasscutter.command.CommandHandler; -import emu.grasscutter.data.GameData; -import emu.grasscutter.data.excels.AvatarData; -import emu.grasscutter.game.avatar.Avatar; -import emu.grasscutter.game.player.Player; - -import java.util.List; - -import static emu.grasscutter.utils.Language.translate; - -@Command(label = "givechar", usage = "givechar [level]", aliases = {"givec"}, permission = "player.givechar", permissionTargeted = "player.givechar.others", description = "commands.giveChar.description") -public final class GiveCharCommand implements CommandHandler { - - @Override - public void execute(Player sender, Player targetPlayer, List args) { - int avatarId; - int level = 1; - - switch (args.size()) { - case 2: - try { - level = Integer.parseInt(args.get(1)); - } catch (NumberFormatException ignored) { - // TODO: Parse from avatar name using GM Handbook. - CommandHandler.sendMessage(sender, translate(sender, "commands.generic.invalid.avatarLevel")); - return; - } // Cheeky fall-through to parse first argument too - case 1: - try { - avatarId = Integer.parseInt(args.get(0)); - } catch (NumberFormatException ignored) { - // TODO: Parse from avatar name using GM Handbook. - CommandHandler.sendMessage(sender, translate(sender, "commands.generic.invalid.avatarId")); - return; - } - break; - default: - CommandHandler.sendMessage(sender, translate(sender, "commands.giveChar.usage")); - return; - } - - AvatarData avatarData = GameData.getAvatarDataMap().get(avatarId); - if (avatarData == null) { - CommandHandler.sendMessage(sender, translate(sender, "commands.generic.invalid.avatarId")); - return; - } - - // Check level. - if (level > 90) { - CommandHandler.sendMessage(sender, translate(sender, "commands.generic.invalid.avatarLevel")); - return; - } - - // Calculate ascension level. - int ascension; - if (level <= 40) { - ascension = (int) Math.ceil(level / 20f) - 1; - } else { - ascension = (int) Math.ceil(level / 10f) - 3; - ascension = Math.min(ascension, 6); - } - - Avatar avatar = new Avatar(avatarId); - avatar.setLevel(level); - avatar.setPromoteLevel(ascension); - - // Handle skill depot for traveller. - if (avatar.getAvatarId() == GameConstants.MAIN_CHARACTER_MALE) { - avatar.setSkillDepotData(GameData.getAvatarSkillDepotDataMap().get(504)); - } - else if(avatar.getAvatarId() == GameConstants.MAIN_CHARACTER_FEMALE) { - avatar.setSkillDepotData(GameData.getAvatarSkillDepotDataMap().get(704)); - } - - // This will handle stats and talents - avatar.recalcStats(); - - targetPlayer.addAvatar(avatar); - CommandHandler.sendMessage(sender, translate(sender, "commands.giveChar.given", Integer.toString(avatarId), Integer.toString(level), Integer.toString(targetPlayer.getUid()))); - } -} diff --git a/src/main/java/emu/grasscutter/command/commands/GiveCommand.java b/src/main/java/emu/grasscutter/command/commands/GiveCommand.java index dbe02a1cd..70ebd69b4 100644 --- a/src/main/java/emu/grasscutter/command/commands/GiveCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/GiveCommand.java @@ -1,29 +1,37 @@ package emu.grasscutter.command.commands; +import emu.grasscutter.GameConstants; import emu.grasscutter.command.Command; import emu.grasscutter.command.CommandHandler; import emu.grasscutter.data.GameData; +import emu.grasscutter.data.GameDepot; +import emu.grasscutter.data.excels.AvatarData; import emu.grasscutter.data.excels.ItemData; +import emu.grasscutter.data.excels.ReliquaryAffixData; +import emu.grasscutter.data.excels.ReliquaryMainPropData; +import emu.grasscutter.game.avatar.Avatar; import emu.grasscutter.game.inventory.GameItem; import emu.grasscutter.game.inventory.ItemType; import emu.grasscutter.game.player.Player; import emu.grasscutter.game.props.ActionReason; +import emu.grasscutter.game.props.FightProperty; +import emu.grasscutter.utils.SparseSet; -import java.util.LinkedList; +import java.util.ArrayList; import java.util.List; + import java.util.regex.Matcher; import java.util.regex.Pattern; -import static emu.grasscutter.utils.Language.translate; - -@Command(label = "give", usage = "give [amount] [level]", aliases = { +@Command(label = "give", usage = "give [lv] [r] [x] | give [lv] [x] [mainPropId] [[,]]...", aliases = { "g", "item", "giveitem"}, permission = "player.give", permissionTargeted = "player.give.others", description = "commands.give.description") public final class GiveCommand implements CommandHandler { - Pattern lvlRegex = Pattern.compile("l(?:vl?)?(\\d+)"); // Java is a joke of a proglang that doesn't have raw string literals - Pattern refineRegex = Pattern.compile("r(\\d+)"); - Pattern amountRegex = Pattern.compile("((?<=x)\\d+|\\d+(?=x)(?!x\\d))"); + private static Pattern lvlRegex = Pattern.compile("l(?:vl?)?(\\d+)"); // Java doesn't have raw string literals :( + private static Pattern refineRegex = Pattern.compile("r(\\d+)"); + private static Pattern constellationRegex = Pattern.compile("c(\\d+)"); + private static Pattern amountRegex = Pattern.compile("((?<=x)\\d+|\\d+(?=x)(?!x\\d))"); - private int matchIntOrNeg(Pattern pattern, String arg) { + private static int matchIntOrNeg(Pattern pattern, String arg) { Matcher match = pattern.matcher(arg); if (match.find()) { return Integer.parseInt(match.group(1)); // This should be exception-safe as only \d+ can be passed to it (i.e. non-empty string of pure digits) @@ -31,27 +39,50 @@ public final class GiveCommand implements CommandHandler { return -1; } - @Override - public void execute(Player sender, Player targetPlayer, List args) { - int item; - int lvl = 1; - int amount = 1; - int refinement = 0; + private static enum GiveAllType { + NONE, + ALL, + WEAPONS, + MATS, + AVATARS + } - for (int i = args.size()-1; i>=0; i--) { // Reverse iteration as we are deleting elements + private static class GiveItemParameters { + public int id; + public int lvl = 0; + public int amount = 1; + public int refinement = 1; + public int constellation = -1; + public int mainPropId = -1; + public List appendPropIdList; + public ItemData data; + public AvatarData avatarData; + public GiveAllType giveAllType = GiveAllType.NONE; + }; + + private static GiveItemParameters parseArgs(Player sender, List args) throws IllegalArgumentException { + GiveItemParameters param = new GiveItemParameters(); + + // Extract any tagged arguments (e.g. "lv90", "x100", "r5") + for (int i = args.size() - 1; i >= 0; i--) { // Reverse iteration as we are deleting elements String arg = args.get(i).toLowerCase(); boolean deleteArg = false; int argNum; + // Note that a single argument can actually match all of these, e.g. "lv90r5x100" if ((argNum = matchIntOrNeg(lvlRegex, arg)) != -1) { - lvl = argNum; + param.lvl = argNum; deleteArg = true; } if ((argNum = matchIntOrNeg(refineRegex, arg)) != -1) { - refinement = argNum; + param.refinement = argNum; + deleteArg = true; + } + if ((argNum = matchIntOrNeg(constellationRegex, arg)) != -1) { + param.constellation = argNum; deleteArg = true; } if ((argNum = matchIntOrNeg(amountRegex, arg)) != -1) { - amount = argNum; + param.amount = argNum; deleteArg = true; } if (deleteArg) { @@ -59,112 +90,387 @@ public final class GiveCommand implements CommandHandler { } } - switch (args.size()) { - case 4: // [amount] [level] [refinement] - try { - refinement = Integer.parseInt(args.get(3)); - } catch (NumberFormatException ignored) { - CommandHandler.sendMessage(sender, translate(sender, "commands.generic.invalid.itemRefinement")); - return; - } // Fallthrough - case 3: // [amount] [level] - try { - lvl = Integer.parseInt(args.get(2)); - } catch (NumberFormatException ignored) { - CommandHandler.sendMessage(sender, translate(sender, "commands.generic.invalid.itemLevel")); - return; - } // Fallthrough - case 2: // [amount] - try { - amount = Integer.parseInt(args.get(1)); - } catch (NumberFormatException ignored) { - CommandHandler.sendMessage(sender, translate(sender, "commands.generic.invalid.amount")); - return; - } // Fallthrough - case 1: // - try { - item = Integer.parseInt(args.get(0)); - } catch (NumberFormatException ignored) { - // TODO: Parse from item name using GM Handbook. - CommandHandler.sendMessage(sender, translate(sender, "commands.generic.invalid.itemId")); - return; - } + // At this point, first remaining argument MUST be itemId/avatarId + if (args.size() < 1) { + CommandHandler.sendTranslatedMessage(sender, "commands.give.usage"); // Reachable if someone does `/give lv90` or similar + throw new IllegalArgumentException(); + } + String id = args.remove(0); + boolean isRelic = false; + + switch (id) { + case "all": + param.giveAllType = GiveAllType.ALL; break; - default: // *No args* - CommandHandler.sendMessage(sender, translate(sender, "commands.give.usage")); - return; + case "weapons": + param.giveAllType = GiveAllType.WEAPONS; + break; + case "mats": + param.giveAllType = GiveAllType.MATS; + break; + case "avatars": + param.giveAllType = GiveAllType.AVATARS; + break; + default: + try { + param.id = Integer.parseInt(id); + param.data = GameData.getItemDataMap().get(param.id); + if ((param.id > 10_000_000) && (param.id < 12_000_000)) + param.avatarData = GameData.getAvatarDataMap().get(param.id); + else if ((param.id > 1000) && (param.id < 1100)) + param.avatarData = GameData.getAvatarDataMap().get(param.id - 1000 + 10_000_000); + isRelic = ((param.data != null) && (param.data.getItemType() == ItemType.ITEM_RELIQUARY)); + } catch (NumberFormatException e) { + // TODO: Parse from item name using GM Handbook. + CommandHandler.sendTranslatedMessage(sender, "commands.generic.invalid.itemId"); + throw e; + } } - ItemData itemData = GameData.getItemDataMap().get(item); - if (itemData == null) { - CommandHandler.sendMessage(sender, translate(sender, "commands.generic.invalid.itemId")); + if (param.amount < 1) param.amount = 1; + if (param.refinement < 1) param.refinement = 1; + if (param.refinement > 5) param.refinement = 5; + if (isRelic) { + // Input 0-20 to match game, instead of 1-21 which is the real level + if (param.lvl < 0) param.lvl = 0; + if (param.lvl > 20) param.lvl = 20; + param.lvl += 1; + if (illegalRelicIds.contains(param.id)) + CommandHandler.sendTranslatedMessage(sender, "commands.give.illegal_relic"); + } else { + // Suitable for Avatars and Weapons + if (param.lvl < 1) param.lvl = 1; + if (param.lvl > 90) param.lvl = 90; + } + + if (isRelic && !args.isEmpty()) { + try { + parseRelicArgs(param, args); + } catch (IllegalArgumentException e) { + CommandHandler.sendTranslatedMessage(sender, "commands.execution.argument_error"); + CommandHandler.sendTranslatedMessage(sender, "commands.give.usage_relic"); + throw e; + } + } + + return param; + } + + @Override + public void execute(Player sender, Player targetPlayer, List args) { + if (args.size() < 1) { // *No args* + CommandHandler.sendTranslatedMessage(sender, "commands.give.usage"); return; } - if (refinement != 0) { - if (itemData.getItemType() == ItemType.ITEM_WEAPON) { - if (refinement < 1 || refinement > 5) { - CommandHandler.sendMessage(sender, translate(sender, "commands.give.refinement_must_between_1_and_5")); + try { + GiveItemParameters param = parseArgs(sender, args); + + switch (param.giveAllType) { + case ALL: + giveAll(targetPlayer, param); + CommandHandler.sendTranslatedMessage(sender, "commands.give.giveall_success"); return; - } - } else { - CommandHandler.sendMessage(sender, translate(sender, "commands.give.refinement_only_applicable_weapons")); + case WEAPONS: + giveAllWeapons(targetPlayer, param); + CommandHandler.sendTranslatedMessage(sender, "commands.give.giveall_success"); + return; + case MATS: + giveAllMats(targetPlayer, param); + CommandHandler.sendTranslatedMessage(sender, "commands.give.giveall_success"); + return; + case AVATARS: + giveAllAvatars(targetPlayer, param); + CommandHandler.sendTranslatedMessage(sender, "commands.give.giveall_success"); + return; + case NONE: + break; + } + + // Check if this is an avatar + if (param.avatarData != null) { + Avatar avatar = makeAvatar(param); + targetPlayer.addAvatar(avatar); + CommandHandler.sendTranslatedMessage(sender, "commands.give.given_avatar", Integer.toString(param.id), Integer.toString(param.lvl), Integer.toString(targetPlayer.getUid())); + return; + } + // If it's not an avatar, it needs to be a valid item + if (param.data == null) { + CommandHandler.sendTranslatedMessage(sender, "commands.generic.invalid.itemId"); + return; + } + + switch (param.data.getItemType()) { + case ITEM_WEAPON: + targetPlayer.getInventory().addItems(makeUnstackableItems(param), ActionReason.SubfieldDrop); + CommandHandler.sendTranslatedMessage(sender, "commands.give.given_with_level_and_refinement", Integer.toString(param.id), Integer.toString(param.lvl), Integer.toString(param.refinement), Integer.toString(param.amount), Integer.toString(targetPlayer.getUid())); + return; + case ITEM_RELIQUARY: + targetPlayer.getInventory().addItems(makeArtifacts(param), ActionReason.SubfieldDrop); + CommandHandler.sendTranslatedMessage(sender, "commands.give.given_level", Integer.toString(param.id), Integer.toString(param.lvl), Integer.toString(param.amount), Integer.toString(targetPlayer.getUid())); + //CommandHandler.sendTranslatedMessage(sender, "commands.giveArtifact.success", Integer.toString(param.id), Integer.toString(targetPlayer.getUid())); + return; + default: + targetPlayer.getInventory().addItem(new GameItem(param.data, param.amount), ActionReason.SubfieldDrop); + CommandHandler.sendTranslatedMessage(sender, "commands.give.given", Integer.toString(param.amount), Integer.toString(param.id), Integer.toString(targetPlayer.getUid())); + return; + } + } catch (IllegalArgumentException ignored) { return; } } - this.item(targetPlayer, itemData, amount, lvl, refinement); + private static Avatar makeAvatar(GiveItemParameters param) { + return makeAvatar(param.avatarData, param.lvl, Avatar.getMinPromoteLevel(param.lvl), 0); + } - if (!itemData.isEquip()) { - CommandHandler.sendMessage(sender, translate(sender, "commands.give.given", Integer.toString(amount), Integer.toString(item), Integer.toString(targetPlayer.getUid()))); - } else if (itemData.getItemType() == ItemType.ITEM_WEAPON) { - CommandHandler.sendMessage(sender, translate(sender, "commands.give.given_with_level_and_refinement", Integer.toString(item), Integer.toString(lvl), Integer.toString(refinement), Integer.toString(amount), Integer.toString(targetPlayer.getUid()))); - } else { - CommandHandler.sendMessage(sender, translate(sender, "commands.give.given_level", Integer.toString(item), Integer.toString(lvl), Integer.toString(amount), Integer.toString(targetPlayer.getUid()))); + private static Avatar makeAvatar(AvatarData avatarData, int level, int promoteLevel, int constellation) { + // Calculate ascension level. + Avatar avatar = new Avatar(avatarData); + avatar.setLevel(level); + avatar.setPromoteLevel(promoteLevel); + + // Add constellations. + int talentBase = switch (avatar.getAvatarId()) { + case 10000005 -> 70; + case 10000006 -> 40; + default -> (avatar.getAvatarId() - 10000000) * 10; + }; + + for (int i = 1; i <= constellation; i++) { + avatar.getTalentIdList().add(talentBase + i); + } + + // Main character needs skill depot manually added. + if (avatar.getAvatarId() == GameConstants.MAIN_CHARACTER_MALE) { + avatar.setSkillDepotData(GameData.getAvatarSkillDepotDataMap().get(504)); + } + else if(avatar.getAvatarId() == GameConstants.MAIN_CHARACTER_FEMALE) { + avatar.setSkillDepotData(GameData.getAvatarSkillDepotDataMap().get(704)); + } + + avatar.recalcStats(); + + return avatar; + } + + private static void giveAllAvatars(Player player, GiveItemParameters param) { + int promoteLevel = Avatar.getMinPromoteLevel(param.lvl); + if (param.constellation < 0) { + param.constellation = 6; + } + for (AvatarData avatarData : GameData.getAvatarDataMap().values()) { + // Exclude test avatars + int id = avatarData.getId(); + if (id < 10000002 || id >= 11000000) continue; + + // Don't try to add each avatar to the current team + player.addAvatar(makeAvatar(avatarData, param.lvl, promoteLevel, param.constellation), false); } } - private void item(Player player, ItemData itemData, int amount, int lvl, int refinement) { - if (itemData.isEquip()) { - List items = new LinkedList<>(); - for (int i = 0; i < amount; i++) { - GameItem item = new GameItem(itemData); - if (item.isEquipped()) { - // check item max level - if (item.getItemType() == ItemType.ITEM_WEAPON) { - if (lvl > 90) lvl = 90; - } else { - if (lvl > 21) lvl = 21; - } - } - item.setCount(amount); - item.setLevel(lvl); - if (lvl > 80) { - item.setPromoteLevel(6); - } else if (lvl > 70) { - item.setPromoteLevel(5); - } else if (lvl > 60) { - item.setPromoteLevel(4); - } else if (lvl > 50) { - item.setPromoteLevel(3); - } else if (lvl > 40) { - item.setPromoteLevel(2); - } else if (lvl > 20) { - item.setPromoteLevel(1); - } - if (item.getItemType() == ItemType.ITEM_WEAPON) { - if (refinement > 0) { - item.setRefinement(refinement - 1); - } else { - item.setRefinement(0); - } - } - items.add(item); + private static List makeUnstackableItems(GiveItemParameters param) { + int promoteLevel = GameItem.getMinPromoteLevel(param.lvl); + int totalExp = 0; + if (param.data.getItemType() == ItemType.ITEM_WEAPON) { + int rankLevel = param.data.getRankLevel(); + for (int i = 1; i < param.lvl; i++) + totalExp += GameData.getWeaponExpRequired(rankLevel, i); + } + + List items = new ArrayList<>(param.amount); + for (int i = 0; i < param.amount; i++) { + GameItem item = new GameItem(param.data); + item.setLevel(param.lvl); + if (item.getItemType() == ItemType.ITEM_WEAPON) { + item.setPromoteLevel(promoteLevel); + item.setTotalExp(totalExp); + item.setRefinement(param.refinement - 1); // Actual refinement data is 0..4 not 1..5 } - player.getInventory().addItems(items, ActionReason.SubfieldDrop); - } else { - GameItem item = new GameItem(itemData); - item.setCount(amount); - player.getInventory().addItem(item, ActionReason.SubfieldDrop); + items.add(item); + } + return items; + } + + private static List makeArtifacts(GiveItemParameters param) { + param.lvl = Math.min(param.lvl, param.data.getMaxLevel()); + int rank = param.data.getRankLevel(); + int totalExp = 0; + for (int i = 1; i < param.lvl; i++) + totalExp += GameData.getRelicExpRequired(rank, i); + + List items = new ArrayList<>(param.amount); + for (int i = 0; i < param.amount; i++) { + // Create item for the artifact. + GameItem item = new GameItem(param.data); + item.setLevel(param.lvl); + item.setTotalExp(totalExp); + int numAffixes = param.data.getAppendPropNum() + (param.lvl-1)/4; + if (param.mainPropId > 0) // Keep random mainProp if we didn't specify one + item.setMainPropId(param.mainPropId); + if (param.appendPropIdList != null) { + item.getAppendPropIdList().clear(); + item.getAppendPropIdList().addAll(param.appendPropIdList); + } + // If we didn't include enough substats, top them up to the appropriate level at random + item.addAppendProps(numAffixes - item.getAppendPropIdList().size()); + items.add(item); + } + return items; + } + + private static int getArtifactMainProp(ItemData itemData, FightProperty prop) throws IllegalArgumentException { + if (prop != FightProperty.FIGHT_PROP_NONE) + for (ReliquaryMainPropData data : GameDepot.getRelicMainPropList(itemData.getMainPropDepotId())) + if (data.getWeight() > 0 && data.getFightProp() == prop) + return data.getId(); + throw new IllegalArgumentException(); + } + + private static List getArtifactAffixes(ItemData itemData, FightProperty prop) throws IllegalArgumentException { + if (prop == FightProperty.FIGHT_PROP_NONE) { + throw new IllegalArgumentException(); + } + List affixes = new ArrayList<>(); + for (ReliquaryAffixData data : GameDepot.getRelicAffixList(itemData.getAppendPropDepotId())) { + if (data.getWeight() > 0 && data.getFightProp() == prop) { + affixes.add(data.getId()); + } + } + return affixes; + } + + private static int getAppendPropId(String substatText, ItemData itemData) throws IllegalArgumentException { + // If the given substat text is an integer, we just use that as the append prop ID. + try { + return Integer.parseInt(substatText); + } catch (NumberFormatException ignored) { + // If the argument was not an integer, we try to determine + // the append prop ID from the given text + artifact information. + // A substat string has the format `substat_tier`, with the + // `_tier` part being optional, defaulting to the maximum. + String[] substatArgs = substatText.split("_"); + String substatType = substatArgs[0]; + + int substatTier = 4; + if (substatArgs.length > 1) { + substatTier = Integer.parseInt(substatArgs[1]); + } + + List substats = getArtifactAffixes(itemData, FightProperty.getPropByShortName(substatType)); + + if (substats.isEmpty()) { + throw new IllegalArgumentException(); + } + + substatTier -= 1; // 1-indexed to 0-indexed + substatTier = Math.min(Math.max(0, substatTier), substats.size() - 1); + return substats.get(substatTier); + } + } + + private static void parseRelicArgs(GiveItemParameters param, List args) throws IllegalArgumentException { + // Get the main stat from the arguments. + // If the given argument is an integer, we use that. + // If not, we check if the argument string is in the main prop map. + String mainPropIdString = args.remove(0); + + try { + param.mainPropId = Integer.parseInt(mainPropIdString); + } catch (NumberFormatException ignored) { + // This can in turn throw an exception which we don't want to catch here. + param.mainPropId = getArtifactMainProp(param.data, FightProperty.getPropByShortName(mainPropIdString)); + } + + // Get substats. + param.appendPropIdList = new ArrayList<>(); + // Every remaining argument is a substat. + for (String prop : args) { + // The substat syntax permits specifying a number of rolls for the given + // substat. Split the string into stat and number if that is the case here. + String[] arr = prop.split(","); + prop = arr[0]; + int n = 1; + if (arr.length > 1) { + n = Math.min(Integer.parseInt(arr[1]), 200); + } + + // Determine the substat ID. + int appendPropId = getAppendPropId(prop, param.data); + + // Add the current substat. + for (int i = 0; i < n; i++) { + param.appendPropIdList.add(appendPropId); + } + }; + } + + private static void addItemsChunked(Player player, List items, int packetSize) { + // Send the items in multiple packets + int lastIdx = items.size() - 1; + for (int i = 0; i <= lastIdx; i += packetSize) { + player.getInventory().addItems(items.subList(i, Math.min(i + packetSize, lastIdx))); } } + + private static void giveAllMats(Player player, GiveItemParameters param) { + List itemList = new ArrayList<>(); + for (ItemData itemdata : GameData.getItemDataMap().values()) { + int id = itemdata.getId(); + if (id < 100_000) continue; // Nothing meaningful below this + if (illegalItemIds.contains(id)) continue; + if (itemdata.isEquip()) continue; + + GameItem item = new GameItem(itemdata); + item.setCount(param.amount); + itemList.add(item); + } + + addItemsChunked(player, itemList, 100); + } + + private static void giveAllWeapons(Player player, GiveItemParameters param) { + int promoteLevel = GameItem.getMinPromoteLevel(param.lvl); + int quantity = Math.min(param.amount, 5); + int refinement = param.refinement - 1; + + List itemList = new ArrayList<>(); + for (ItemData itemdata : GameData.getItemDataMap().values()) { + int id = itemdata.getId(); + if (id < 11100 || id > 16000) continue; // All extant weapons are within this range + if (illegalWeaponIds.contains(id)) continue; + if (!itemdata.isEquip()) continue; + if (itemdata.getItemType() != ItemType.ITEM_WEAPON) continue; + + for (int i = 0; i < quantity; i++) { + GameItem item = new GameItem(itemdata); + item.setLevel(param.lvl); + item.setPromoteLevel(promoteLevel); + item.setRefinement(refinement); + itemList.add(item); + } + } + + addItemsChunked(player, itemList, 100); + } + + private static void giveAll(Player player, GiveItemParameters param) { + giveAllAvatars(player, param); + giveAllMats(player, param); + giveAllWeapons(player, param); + } + + private static final SparseSet illegalWeaponIds = new SparseSet(""" + 10000-10008, 11411, 11506-11508, 12505, 12506, 12508, 12509, + 13503, 13506, 14411, 14503, 14505, 14508, 15504-15506 + """); + + private static final SparseSet illegalRelicIds = new SparseSet(""" + 20001, 23300-23340, 23383-23385, 78310-78554, 99310-99554 + """); + + private static final SparseSet illegalItemIds = new SparseSet(""" + 100086, 100087, 100100-101000, 101106-101110, 101306, 101500-104000, + 105001, 105004, 106000-107000, 107011, 108000, 109000-110000, + 115000-130000, 200200-200899, 220050, 220054 + """); } diff --git a/src/main/java/emu/grasscutter/command/commands/SetStatsCommand.java b/src/main/java/emu/grasscutter/command/commands/SetStatsCommand.java index 643b313f8..3530929ee 100644 --- a/src/main/java/emu/grasscutter/command/commands/SetStatsCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/SetStatsCommand.java @@ -4,15 +4,12 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import emu.grasscutter.Grasscutter; import emu.grasscutter.command.Command; import emu.grasscutter.command.CommandHandler; 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; -import emu.grasscutter.utils.Language; - import static emu.grasscutter.utils.Language.translate; @Command(label = "setstats", usage = "setstats|stats ", aliases = {"stats"}, permission = "player.setstats", permissionTargeted = "player.setstats.others", description = "commands.setStats.description") @@ -20,157 +17,50 @@ public final class SetStatsCommand implements CommandHandler { static class Stat { String name; FightProperty prop; - boolean percent; - public Stat(String name, FightProperty prop, boolean percent) { + public Stat(FightProperty prop) { + this.name = prop.toString(); + this.prop = prop; + } + + public Stat(String name, FightProperty prop) { this.name = name; this.prop = prop; - this.percent = percent; } } - Map stats = new HashMap<>(); + Map stats; public SetStatsCommand() { - // Default stats - stats.put("maxhp", new Stat(FightProperty.FIGHT_PROP_MAX_HP.toString(), FightProperty.FIGHT_PROP_MAX_HP, false)); - stats.put("hp", new Stat(FightProperty.FIGHT_PROP_CUR_HP.toString(), FightProperty.FIGHT_PROP_CUR_HP, false)); - stats.put("atk", new Stat(FightProperty.FIGHT_PROP_CUR_ATTACK.toString(), FightProperty.FIGHT_PROP_CUR_ATTACK, false)); - stats.put("atkb", new Stat(FightProperty.FIGHT_PROP_BASE_ATTACK.toString(), FightProperty.FIGHT_PROP_BASE_ATTACK, false)); // This doesn't seem to get used to recalculate ATK, so it's only useful for stuff like Bennett's buff. - stats.put("def", new Stat(FightProperty.FIGHT_PROP_DEFENSE.toString(), FightProperty.FIGHT_PROP_DEFENSE, false)); - stats.put("em", new Stat(FightProperty.FIGHT_PROP_ELEMENT_MASTERY.toString(), FightProperty.FIGHT_PROP_ELEMENT_MASTERY, false)); - stats.put("er", new Stat(FightProperty.FIGHT_PROP_CHARGE_EFFICIENCY.toString(), FightProperty.FIGHT_PROP_CHARGE_EFFICIENCY, true)); - stats.put("crate", new Stat(FightProperty.FIGHT_PROP_CRITICAL.toString(), FightProperty.FIGHT_PROP_CRITICAL, true)); - stats.put("cdmg", new Stat(FightProperty.FIGHT_PROP_CRITICAL_HURT.toString(), FightProperty.FIGHT_PROP_CRITICAL_HURT, true)); - stats.put("dmg", new Stat(FightProperty.FIGHT_PROP_ADD_HURT.toString(), FightProperty.FIGHT_PROP_ADD_HURT, true)); // This seems to get reset after attacks - stats.put("eanemo", new Stat(FightProperty.FIGHT_PROP_WIND_ADD_HURT.toString(), FightProperty.FIGHT_PROP_WIND_ADD_HURT, true)); - stats.put("ecryo", new Stat(FightProperty.FIGHT_PROP_ICE_ADD_HURT.toString(), FightProperty.FIGHT_PROP_ICE_ADD_HURT, true)); - stats.put("edendro", new Stat(FightProperty.FIGHT_PROP_GRASS_ADD_HURT.toString(), FightProperty.FIGHT_PROP_GRASS_ADD_HURT, true)); - stats.put("eelectro", new Stat(FightProperty.FIGHT_PROP_ELEC_ADD_HURT.toString(), FightProperty.FIGHT_PROP_ELEC_ADD_HURT, true)); - stats.put("egeo", new Stat(FightProperty.FIGHT_PROP_ROCK_ADD_HURT.toString(), FightProperty.FIGHT_PROP_ROCK_ADD_HURT, true)); - stats.put("ehydro", new Stat(FightProperty.FIGHT_PROP_WATER_ADD_HURT.toString(), FightProperty.FIGHT_PROP_WATER_ADD_HURT, true)); - stats.put("epyro", new Stat(FightProperty.FIGHT_PROP_FIRE_ADD_HURT.toString(), FightProperty.FIGHT_PROP_FIRE_ADD_HURT, true)); - stats.put("ephys", new Stat(FightProperty.FIGHT_PROP_PHYSICAL_ADD_HURT.toString(), FightProperty.FIGHT_PROP_PHYSICAL_ADD_HURT, true)); - stats.put("resall", new Stat(FightProperty.FIGHT_PROP_SUB_HURT.toString(), FightProperty.FIGHT_PROP_SUB_HURT, true)); // This seems to get reset after attacks - stats.put("resanemo", new Stat(FightProperty.FIGHT_PROP_WIND_SUB_HURT.toString(), FightProperty.FIGHT_PROP_WIND_SUB_HURT, true)); - stats.put("rescryo", new Stat(FightProperty.FIGHT_PROP_ICE_SUB_HURT.toString(), FightProperty.FIGHT_PROP_ICE_SUB_HURT, true)); - stats.put("resdendro", new Stat(FightProperty.FIGHT_PROP_GRASS_SUB_HURT.toString(), FightProperty.FIGHT_PROP_GRASS_SUB_HURT, true)); - stats.put("reselectro", new Stat(FightProperty.FIGHT_PROP_ELEC_SUB_HURT.toString(), FightProperty.FIGHT_PROP_ELEC_SUB_HURT, true)); - stats.put("resgeo", new Stat(FightProperty.FIGHT_PROP_ROCK_SUB_HURT.toString(), FightProperty.FIGHT_PROP_ROCK_SUB_HURT, true)); - stats.put("reshydro", new Stat(FightProperty.FIGHT_PROP_WATER_SUB_HURT.toString(), FightProperty.FIGHT_PROP_WATER_SUB_HURT, true)); - stats.put("respyro", new Stat(FightProperty.FIGHT_PROP_FIRE_SUB_HURT.toString(), FightProperty.FIGHT_PROP_FIRE_SUB_HURT, true)); - stats.put("resphys", new Stat(FightProperty.FIGHT_PROP_PHYSICAL_SUB_HURT.toString(), FightProperty.FIGHT_PROP_PHYSICAL_SUB_HURT, true)); - stats.put("cdr", new Stat(FightProperty.FIGHT_PROP_SKILL_CD_MINUS_RATIO.toString(), FightProperty.FIGHT_PROP_SKILL_CD_MINUS_RATIO, true)); - stats.put("heal", new Stat(FightProperty.FIGHT_PROP_HEAL_ADD.toString(), FightProperty.FIGHT_PROP_HEAL_ADD, true)); - stats.put("heali", new Stat(FightProperty.FIGHT_PROP_HEALED_ADD.toString(), FightProperty.FIGHT_PROP_HEALED_ADD, true)); - stats.put("shield", new Stat(FightProperty.FIGHT_PROP_SHIELD_COST_MINUS_RATIO.toString(), FightProperty.FIGHT_PROP_SHIELD_COST_MINUS_RATIO, true)); - stats.put("defi", new Stat(FightProperty.FIGHT_PROP_DEFENCE_IGNORE_RATIO.toString(), FightProperty.FIGHT_PROP_DEFENCE_IGNORE_RATIO, true)); - // Compatibility aliases - stats.put("mhp", stats.get("maxhp")); - stats.put("cr", stats.get("crate")); - stats.put("cd", stats.get("cdmg")); - stats.put("edend", stats.get("edendro")); - stats.put("eelec", stats.get("eelectro")); - stats.put("ethunder", stats.get("eelectro")); - + this.stats = new HashMap<>(); + for (String key : FightProperty.getShortNames()) { + this.stats.put(key, new Stat(FightProperty.getPropByShortName(key))); + } // Full FightProperty enum that won't be advertised but can be used by devs // They have a prefix to avoid the "hp" clash - stats.put("_none", new Stat("NONE", FightProperty.FIGHT_PROP_NONE, true)); - stats.put("_base_hp", new Stat("BASE_HP", FightProperty.FIGHT_PROP_BASE_HP, false)); - stats.put("_hp", new Stat("HP", FightProperty.FIGHT_PROP_HP, false)); - stats.put("_hp_percent", new Stat("HP_PERCENT", FightProperty.FIGHT_PROP_HP_PERCENT, true)); - stats.put("_base_attack", new Stat("BASE_ATTACK", FightProperty.FIGHT_PROP_BASE_ATTACK, false)); - stats.put("_attack", new Stat("ATTACK", FightProperty.FIGHT_PROP_ATTACK, false)); - stats.put("_attack_percent", new Stat("ATTACK_PERCENT", FightProperty.FIGHT_PROP_ATTACK_PERCENT, true)); - stats.put("_base_defense", new Stat("BASE_DEFENSE", FightProperty.FIGHT_PROP_BASE_DEFENSE, false)); - stats.put("_defense", new Stat("DEFENSE", FightProperty.FIGHT_PROP_DEFENSE, false)); - stats.put("_defense_percent", new Stat("DEFENSE_PERCENT", FightProperty.FIGHT_PROP_DEFENSE_PERCENT, true)); - stats.put("_base_speed", new Stat("BASE_SPEED", FightProperty.FIGHT_PROP_BASE_SPEED, true)); - stats.put("_speed_percent", new Stat("SPEED_PERCENT", FightProperty.FIGHT_PROP_SPEED_PERCENT, true)); - stats.put("_hp_mp_percent", new Stat("HP_MP_PERCENT", FightProperty.FIGHT_PROP_HP_MP_PERCENT, true)); - stats.put("_attack_mp_percent", new Stat("ATTACK_MP_PERCENT", FightProperty.FIGHT_PROP_ATTACK_MP_PERCENT, true)); - stats.put("_critical", new Stat("CRITICAL", FightProperty.FIGHT_PROP_CRITICAL, true)); - stats.put("_anti_critical", new Stat("ANTI_CRITICAL", FightProperty.FIGHT_PROP_ANTI_CRITICAL, true)); - stats.put("_critical_hurt", new Stat("CRITICAL_HURT", FightProperty.FIGHT_PROP_CRITICAL_HURT, true)); - stats.put("_charge_efficiency", new Stat("CHARGE_EFFICIENCY", FightProperty.FIGHT_PROP_CHARGE_EFFICIENCY, true)); - stats.put("_add_hurt", new Stat("ADD_HURT", FightProperty.FIGHT_PROP_ADD_HURT, true)); - stats.put("_sub_hurt", new Stat("SUB_HURT", FightProperty.FIGHT_PROP_SUB_HURT, true)); - stats.put("_heal_add", new Stat("HEAL_ADD", FightProperty.FIGHT_PROP_HEAL_ADD, true)); - stats.put("_healed_add", new Stat("HEALED_ADD", FightProperty.FIGHT_PROP_HEALED_ADD, false)); - stats.put("_element_mastery", new Stat("ELEMENT_MASTERY", FightProperty.FIGHT_PROP_ELEMENT_MASTERY, true)); - stats.put("_physical_sub_hurt", new Stat("PHYSICAL_SUB_HURT", FightProperty.FIGHT_PROP_PHYSICAL_SUB_HURT, true)); - stats.put("_physical_add_hurt", new Stat("PHYSICAL_ADD_HURT", FightProperty.FIGHT_PROP_PHYSICAL_ADD_HURT, true)); - stats.put("_defence_ignore_ratio", new Stat("DEFENCE_IGNORE_RATIO", FightProperty.FIGHT_PROP_DEFENCE_IGNORE_RATIO, true)); - stats.put("_defence_ignore_delta", new Stat("DEFENCE_IGNORE_DELTA", FightProperty.FIGHT_PROP_DEFENCE_IGNORE_DELTA, true)); - stats.put("_fire_add_hurt", new Stat("FIRE_ADD_HURT", FightProperty.FIGHT_PROP_FIRE_ADD_HURT, true)); - stats.put("_elec_add_hurt", new Stat("ELEC_ADD_HURT", FightProperty.FIGHT_PROP_ELEC_ADD_HURT, true)); - stats.put("_water_add_hurt", new Stat("WATER_ADD_HURT", FightProperty.FIGHT_PROP_WATER_ADD_HURT, true)); - stats.put("_grass_add_hurt", new Stat("GRASS_ADD_HURT", FightProperty.FIGHT_PROP_GRASS_ADD_HURT, true)); - stats.put("_wind_add_hurt", new Stat("WIND_ADD_HURT", FightProperty.FIGHT_PROP_WIND_ADD_HURT, true)); - stats.put("_rock_add_hurt", new Stat("ROCK_ADD_HURT", FightProperty.FIGHT_PROP_ROCK_ADD_HURT, true)); - stats.put("_ice_add_hurt", new Stat("ICE_ADD_HURT", FightProperty.FIGHT_PROP_ICE_ADD_HURT, true)); - stats.put("_hit_head_add_hurt", new Stat("HIT_HEAD_ADD_HURT", FightProperty.FIGHT_PROP_HIT_HEAD_ADD_HURT, true)); - stats.put("_fire_sub_hurt", new Stat("FIRE_SUB_HURT", FightProperty.FIGHT_PROP_FIRE_SUB_HURT, true)); - stats.put("_elec_sub_hurt", new Stat("ELEC_SUB_HURT", FightProperty.FIGHT_PROP_ELEC_SUB_HURT, true)); - stats.put("_water_sub_hurt", new Stat("WATER_SUB_HURT", FightProperty.FIGHT_PROP_WATER_SUB_HURT, true)); - stats.put("_grass_sub_hurt", new Stat("GRASS_SUB_HURT", FightProperty.FIGHT_PROP_GRASS_SUB_HURT, true)); - stats.put("_wind_sub_hurt", new Stat("WIND_SUB_HURT", FightProperty.FIGHT_PROP_WIND_SUB_HURT, true)); - stats.put("_rock_sub_hurt", new Stat("ROCK_SUB_HURT", FightProperty.FIGHT_PROP_ROCK_SUB_HURT, true)); - stats.put("_ice_sub_hurt", new Stat("ICE_SUB_HURT", FightProperty.FIGHT_PROP_ICE_SUB_HURT, true)); - stats.put("_effect_hit", new Stat("EFFECT_HIT", FightProperty.FIGHT_PROP_EFFECT_HIT, true)); - stats.put("_effect_resist", new Stat("EFFECT_RESIST", FightProperty.FIGHT_PROP_EFFECT_RESIST, true)); - stats.put("_freeze_resist", new Stat("FREEZE_RESIST", FightProperty.FIGHT_PROP_FREEZE_RESIST, true)); - stats.put("_torpor_resist", new Stat("TORPOR_RESIST", FightProperty.FIGHT_PROP_TORPOR_RESIST, true)); - stats.put("_dizzy_resist", new Stat("DIZZY_RESIST", FightProperty.FIGHT_PROP_DIZZY_RESIST, true)); - stats.put("_freeze_shorten", new Stat("FREEZE_SHORTEN", FightProperty.FIGHT_PROP_FREEZE_SHORTEN, true)); - stats.put("_torpor_shorten", new Stat("TORPOR_SHORTEN", FightProperty.FIGHT_PROP_TORPOR_SHORTEN, true)); - stats.put("_dizzy_shorten", new Stat("DIZZY_SHORTEN", FightProperty.FIGHT_PROP_DIZZY_SHORTEN, true)); - stats.put("_max_fire_energy", new Stat("MAX_FIRE_ENERGY", FightProperty.FIGHT_PROP_MAX_FIRE_ENERGY, true)); - stats.put("_max_elec_energy", new Stat("MAX_ELEC_ENERGY", FightProperty.FIGHT_PROP_MAX_ELEC_ENERGY, true)); - stats.put("_max_water_energy", new Stat("MAX_WATER_ENERGY", FightProperty.FIGHT_PROP_MAX_WATER_ENERGY, true)); - stats.put("_max_grass_energy", new Stat("MAX_GRASS_ENERGY", FightProperty.FIGHT_PROP_MAX_GRASS_ENERGY, true)); - stats.put("_max_wind_energy", new Stat("MAX_WIND_ENERGY", FightProperty.FIGHT_PROP_MAX_WIND_ENERGY, true)); - stats.put("_max_ice_energy", new Stat("MAX_ICE_ENERGY", FightProperty.FIGHT_PROP_MAX_ICE_ENERGY, true)); - stats.put("_max_rock_energy", new Stat("MAX_ROCK_ENERGY", FightProperty.FIGHT_PROP_MAX_ROCK_ENERGY, true)); - stats.put("_skill_cd_minus_ratio", new Stat("SKILL_CD_MINUS_RATIO", FightProperty.FIGHT_PROP_SKILL_CD_MINUS_RATIO, true)); - stats.put("_shield_cost_minus_ratio", new Stat("SHIELD_COST_MINUS_RATIO", FightProperty.FIGHT_PROP_SHIELD_COST_MINUS_RATIO, true)); - stats.put("_cur_fire_energy", new Stat("CUR_FIRE_ENERGY", FightProperty.FIGHT_PROP_CUR_FIRE_ENERGY, false)); - stats.put("_cur_elec_energy", new Stat("CUR_ELEC_ENERGY", FightProperty.FIGHT_PROP_CUR_ELEC_ENERGY, false)); - stats.put("_cur_water_energy", new Stat("CUR_WATER_ENERGY", FightProperty.FIGHT_PROP_CUR_WATER_ENERGY, false)); - stats.put("_cur_grass_energy", new Stat("CUR_GRASS_ENERGY", FightProperty.FIGHT_PROP_CUR_GRASS_ENERGY, false)); - stats.put("_cur_wind_energy", new Stat("CUR_WIND_ENERGY", FightProperty.FIGHT_PROP_CUR_WIND_ENERGY, false)); - stats.put("_cur_ice_energy", new Stat("CUR_ICE_ENERGY", FightProperty.FIGHT_PROP_CUR_ICE_ENERGY, false)); - stats.put("_cur_rock_energy", new Stat("CUR_ROCK_ENERGY", FightProperty.FIGHT_PROP_CUR_ROCK_ENERGY, false)); - stats.put("_cur_hp", new Stat("CUR_HP", FightProperty.FIGHT_PROP_CUR_HP, false)); - stats.put("_max_hp", new Stat("MAX_HP", FightProperty.FIGHT_PROP_MAX_HP, false)); - stats.put("_cur_attack", new Stat("CUR_ATTACK", FightProperty.FIGHT_PROP_CUR_ATTACK, false)); - stats.put("_cur_defense", new Stat("CUR_DEFENSE", FightProperty.FIGHT_PROP_CUR_DEFENSE, false)); - stats.put("_cur_speed", new Stat("CUR_SPEED", FightProperty.FIGHT_PROP_CUR_SPEED, true)); - stats.put("_nonextra_attack", new Stat("NONEXTRA_ATTACK", FightProperty.FIGHT_PROP_NONEXTRA_ATTACK, true)); - stats.put("_nonextra_defense", new Stat("NONEXTRA_DEFENSE", FightProperty.FIGHT_PROP_NONEXTRA_DEFENSE, true)); - stats.put("_nonextra_critical", new Stat("NONEXTRA_CRITICAL", FightProperty.FIGHT_PROP_NONEXTRA_CRITICAL, true)); - stats.put("_nonextra_anti_critical", new Stat("NONEXTRA_ANTI_CRITICAL", FightProperty.FIGHT_PROP_NONEXTRA_ANTI_CRITICAL, true)); - stats.put("_nonextra_critical_hurt", new Stat("NONEXTRA_CRITICAL_HURT", FightProperty.FIGHT_PROP_NONEXTRA_CRITICAL_HURT, true)); - stats.put("_nonextra_charge_efficiency", new Stat("NONEXTRA_CHARGE_EFFICIENCY", FightProperty.FIGHT_PROP_NONEXTRA_CHARGE_EFFICIENCY, true)); - stats.put("_nonextra_element_mastery", new Stat("NONEXTRA_ELEMENT_MASTERY", FightProperty.FIGHT_PROP_NONEXTRA_ELEMENT_MASTERY, true)); - stats.put("_nonextra_physical_sub_hurt", new Stat("NONEXTRA_PHYSICAL_SUB_HURT", FightProperty.FIGHT_PROP_NONEXTRA_PHYSICAL_SUB_HURT, true)); - stats.put("_nonextra_fire_add_hurt", new Stat("NONEXTRA_FIRE_ADD_HURT", FightProperty.FIGHT_PROP_NONEXTRA_FIRE_ADD_HURT, true)); - stats.put("_nonextra_elec_add_hurt", new Stat("NONEXTRA_ELEC_ADD_HURT", FightProperty.FIGHT_PROP_NONEXTRA_ELEC_ADD_HURT, true)); - stats.put("_nonextra_water_add_hurt", new Stat("NONEXTRA_WATER_ADD_HURT", FightProperty.FIGHT_PROP_NONEXTRA_WATER_ADD_HURT, true)); - stats.put("_nonextra_grass_add_hurt", new Stat("NONEXTRA_GRASS_ADD_HURT", FightProperty.FIGHT_PROP_NONEXTRA_GRASS_ADD_HURT, true)); - stats.put("_nonextra_wind_add_hurt", new Stat("NONEXTRA_WIND_ADD_HURT", FightProperty.FIGHT_PROP_NONEXTRA_WIND_ADD_HURT, true)); - stats.put("_nonextra_rock_add_hurt", new Stat("NONEXTRA_ROCK_ADD_HURT", FightProperty.FIGHT_PROP_NONEXTRA_ROCK_ADD_HURT, true)); - stats.put("_nonextra_ice_add_hurt", new Stat("NONEXTRA_ICE_ADD_HURT", FightProperty.FIGHT_PROP_NONEXTRA_ICE_ADD_HURT, true)); - stats.put("_nonextra_fire_sub_hurt", new Stat("NONEXTRA_FIRE_SUB_HURT", FightProperty.FIGHT_PROP_NONEXTRA_FIRE_SUB_HURT, true)); - stats.put("_nonextra_elec_sub_hurt", new Stat("NONEXTRA_ELEC_SUB_HURT", FightProperty.FIGHT_PROP_NONEXTRA_ELEC_SUB_HURT, true)); - stats.put("_nonextra_water_sub_hurt", new Stat("NONEXTRA_WATER_SUB_HURT", FightProperty.FIGHT_PROP_NONEXTRA_WATER_SUB_HURT, true)); - stats.put("_nonextra_grass_sub_hurt", new Stat("NONEXTRA_GRASS_SUB_HURT", FightProperty.FIGHT_PROP_NONEXTRA_GRASS_SUB_HURT, true)); - stats.put("_nonextra_wind_sub_hurt", new Stat("NONEXTRA_WIND_SUB_HURT", FightProperty.FIGHT_PROP_NONEXTRA_WIND_SUB_HURT, true)); - stats.put("_nonextra_rock_sub_hurt", new Stat("NONEXTRA_ROCK_SUB_HURT", FightProperty.FIGHT_PROP_NONEXTRA_ROCK_SUB_HURT, true)); - stats.put("_nonextra_ice_sub_hurt", new Stat("NONEXTRA_ICE_SUB_HURT", FightProperty.FIGHT_PROP_NONEXTRA_ICE_SUB_HURT, true)); - stats.put("_nonextra_skill_cd_minus_ratio", new Stat("NONEXTRA_SKILL_CD_MINUS_RATIO", FightProperty.FIGHT_PROP_NONEXTRA_SKILL_CD_MINUS_RATIO, true)); - stats.put("_nonextra_shield_cost_minus_ratio", new Stat("NONEXTRA_SHIELD_COST_MINUS_RATIO", FightProperty.FIGHT_PROP_NONEXTRA_SHIELD_COST_MINUS_RATIO, true)); - stats.put("_nonextra_physical_add_hurt", new Stat("NONEXTRA_PHYSICAL_ADD_HURT", FightProperty.FIGHT_PROP_NONEXTRA_PHYSICAL_ADD_HURT, true)); + for (FightProperty prop : FightProperty.values()) { + String name = prop.toString().substring(10); // FIGHT_PROP_BASE_HP -> _BASE_HP + String key = name.toLowerCase(); // _BASE_HP -> _base_hp + name = name.substring(1); // _BASE_HP -> BASE_HP + this.stats.put(key, new Stat(name, prop)); + } + + // Compatibility aliases + this.stats.put("mhp", this.stats.get("maxhp")); + this.stats.put("hp", new Stat(FightProperty.FIGHT_PROP_CUR_HP)); // Overrides FIGHT_PROP_HP + this.stats.put("atk", new Stat(FightProperty.FIGHT_PROP_CUR_ATTACK)); // Overrides FIGHT_PROP_ATTACK + this.stats.put("atkb", new Stat(FightProperty.FIGHT_PROP_BASE_ATTACK)); // This doesn't seem to get used to recalculate ATK, so it's only useful for stuff like Bennett's buff. + this.stats.put("eanemo", this.stats.get("anemo%")); + this.stats.put("ecryo", this.stats.get("cryo%")); + this.stats.put("edendro", this.stats.get("dendro%")); + this.stats.put("edend", this.stats.get("dendro%")); + this.stats.put("eelectro", this.stats.get("electro%")); + this.stats.put("eelec", this.stats.get("electro%")); + this.stats.put("ethunder", this.stats.get("electro%")); + this.stats.put("egeo", this.stats.get("geo%")); + this.stats.put("ehydro", this.stats.get("hydro%")); + this.stats.put("epyro", this.stats.get("pyro%")); + this.stats.put("ephys", this.stats.get("phys%")); } @Override @@ -206,8 +96,8 @@ public final class SetStatsCommand implements CommandHandler { Stat stat = stats.get(statStr); entity.setFightProperty(stat.prop, value); entity.getWorld().broadcastPacket(new PacketEntityFightPropUpdateNotify(entity, stat.prop)); - if (stat.percent) { - valueStr = String.format("%.1f%%", value*100f); + if (FightProperty.isPercentage(stat.prop)) { + valueStr = String.format("%.1f%%", value * 100f); } else { valueStr = String.format("%.0f", value); } diff --git a/src/main/java/emu/grasscutter/data/GameDepot.java b/src/main/java/emu/grasscutter/data/GameDepot.java index d28b38960..0b44ac44a 100644 --- a/src/main/java/emu/grasscutter/data/GameDepot.java +++ b/src/main/java/emu/grasscutter/data/GameDepot.java @@ -20,8 +20,9 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; public class GameDepot { - private static Int2ObjectMap> relicMainPropDepot = new Int2ObjectOpenHashMap<>(); - private static Int2ObjectMap> relicAffixDepot = new Int2ObjectOpenHashMap<>(); + private static Int2ObjectMap> relicRandomMainPropDepot = new Int2ObjectOpenHashMap<>(); + private static Int2ObjectMap> relicMainPropDepot = new Int2ObjectOpenHashMap<>(); + private static Int2ObjectMap> relicAffixDepot = new Int2ObjectOpenHashMap<>(); private static Map playerAbilities = new HashMap<>(); private static Int2ObjectMap> spawnLists = new Int2ObjectOpenHashMap<>(); @@ -31,8 +32,10 @@ public class GameDepot { if (data.getWeight() <= 0 || data.getPropDepotId() <= 0) { continue; } - WeightedList list = relicMainPropDepot.computeIfAbsent(data.getPropDepotId(), k -> new WeightedList<>()); - list.add(data.getWeight(), data); + List list = relicMainPropDepot.computeIfAbsent(data.getPropDepotId(), k -> new ArrayList<>()); + list.add(data); + WeightedList weightedList = relicRandomMainPropDepot.computeIfAbsent(data.getPropDepotId(), k -> new WeightedList<>()); + weightedList.add(data.getWeight(), data); } for (ReliquaryAffixData data : GameData.getReliquaryAffixDataMap().values()) { if (data.getWeight() <= 0 || data.getDepotId() <= 0) { @@ -48,14 +51,18 @@ public class GameDepot { } public static ReliquaryMainPropData getRandomRelicMainProp(int depot) { - WeightedList depotList = relicMainPropDepot.get(depot); + WeightedList depotList = relicRandomMainPropDepot.get(depot); if (depotList == null) { return null; } return depotList.next(); } - public static List getRandomRelicAffixList(int depot) { + public static List getRelicMainPropList(int depot) { + return relicMainPropDepot.get(depot); + } + + public static List getRelicAffixList(int depot) { return relicAffixDepot.get(depot); } diff --git a/src/main/java/emu/grasscutter/data/excels/ItemData.java b/src/main/java/emu/grasscutter/data/excels/ItemData.java index c7496a67e..6dc2643d9 100644 --- a/src/main/java/emu/grasscutter/data/excels/ItemData.java +++ b/src/main/java/emu/grasscutter/data/excels/ItemData.java @@ -10,6 +10,7 @@ import emu.grasscutter.game.inventory.*; import emu.grasscutter.game.props.FightProperty; import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.ints.IntSet; +import lombok.Getter; @ResourceType(name = {"MaterialExcelConfigData.json", "WeaponExcelConfigData.json", @@ -19,23 +20,23 @@ import it.unimi.dsi.fastutil.ints.IntSet; public class ItemData extends GameResource { private int id; - private int stackLimit = 1; - private int maxUseCount; - private int rankLevel; - private String effectName; - private int[] satiationParams; - private int rank; - private int weight; - private int gadgetId; + @Getter private int stackLimit = 1; + @Getter private int maxUseCount; + @Getter private int rankLevel; + @Getter private String effectName; + @Getter private int[] satiationParams; + @Getter private int rank; + @Getter private int weight; + @Getter private int gadgetId; - private int[] destroyReturnMaterial; - private int[] destroyReturnMaterialCount; + @Getter private int[] destroyReturnMaterial; + @Getter private int[] destroyReturnMaterialCount; - private List itemUse; + @Getter private List itemUse; // Food - private String foodQuality; - private String useTarget; + @Getter private String foodQuality; + @Getter private String useTarget; private String[] iseParam; // String enums @@ -45,42 +46,42 @@ public class ItemData extends GameResource { private String effectType; private String destroyRule; - // Relic - private int mainPropDepotId; - private int appendPropDepotId; - private int appendPropNum; - private int setId; - private int[] addPropLevels; - private int baseConvExp; - private int maxLevel; - - // Weapon - private int weaponPromoteId; - private int weaponBaseExp; - private int storyId; - private int avatarPromoteId; - private int awakenMaterial; - private int[] awakenCosts; - private int[] skillAffix; - private WeaponProperty[] weaponProp; - - // Hash - private String icon; - private long nameTextMapHash; - - // Post load + // Post load enum forms of above private transient MaterialType materialEnumType; private transient ItemType itemEnumType; private transient EquipType equipEnumType; - private IntSet addPropLevelSet; + // Relic + @Getter private int mainPropDepotId; + @Getter private int appendPropDepotId; + @Getter private int appendPropNum; + @Getter private int setId; + private int[] addPropLevels; + @Getter private int baseConvExp; + @Getter private int maxLevel; + + // Weapon + @Getter private int weaponPromoteId; + @Getter private int weaponBaseExp; + @Getter private int storyId; + @Getter private int avatarPromoteId; + @Getter private int awakenMaterial; + @Getter private int[] awakenCosts; + @Getter private int[] skillAffix; + private WeaponProperty[] weaponProp; + + // Hash + @Getter private String icon; + @Getter private long nameTextMapHash; + + @Getter private IntSet addPropLevelSet; // Furniture - private int comfort; - private List furnType; - private List furnitureGadgetID; + @Getter private int comfort; + @Getter private List furnType; + @Getter private List furnitureGadgetID; @SerializedName("JFDLJGDFIGL") - private int roomSceneId; + @Getter private int roomSceneId; @Override public int getId(){ @@ -91,137 +92,17 @@ public class ItemData extends GameResource { return this.materialType; } - public int getStackLimit(){ - return this.stackLimit; - } - - public int getMaxUseCount(){ - return this.maxUseCount; - } - - public String getUseTarget(){ - return this.useTarget; - } - public String[] getUseParam(){ return this.iseParam; } - public int getRankLevel(){ - return this.rankLevel; - } - - public String getFoodQuality(){ - return this.foodQuality; - } - - public String getEffectName(){ - return this.effectName; - } - - public int[] getSatiationParams(){ - return this.satiationParams; - } - - public int[] getDestroyReturnMaterial(){ - return this.destroyReturnMaterial; - } - - public int[] getDestroyReturnMaterialCount(){ - return this.destroyReturnMaterialCount; - } - - public List getItemUse() { - return itemUse; - } - - public long getNameTextMapHash(){ - return this.nameTextMapHash; - } - - public String getIcon(){ - return this.icon; - } - public String getItemTypeString(){ return this.itemType; } - - public int getRank(){ - return this.rank; - } - - public int getGadgetId() { - return gadgetId; - } - - public int getBaseConvExp() { - return baseConvExp; - } - - public int getMainPropDepotId() { - return mainPropDepotId; - } - - public int getAppendPropDepotId() { - return appendPropDepotId; - } - - public int getAppendPropNum() { - return appendPropNum; - } - - public int getSetId() { - return setId; - } - - public int getWeaponPromoteId() { - return weaponPromoteId; - } - - public int getWeaponBaseExp() { - return weaponBaseExp; - } - - public int getAwakenMaterial() { - return awakenMaterial; - } - - public int[] getAwakenCosts() { - return awakenCosts; - } - - public IntSet getAddPropLevelSet() { - return addPropLevelSet; - } - - public int[] getSkillAffix() { - return skillAffix; - } public WeaponProperty[] getWeaponProperties() { return weaponProp; } - - public int getMaxLevel() { - return maxLevel; - } - - public int getComfort() { - return comfort; - } - - public List getFurnType() { - return furnType; - } - - public List getFurnitureGadgetID() { - return furnitureGadgetID; - } - - public int getRoomSceneId() { - return roomSceneId; - } public ItemType getItemType() { return this.itemEnumType; @@ -274,26 +155,10 @@ public class ItemData extends GameResource { } public static class WeaponProperty { - private FightProperty fightProp; - private String propType; - private float initValue; - private String type; - - public String getPropType(){ - return this.propType; - } - - public float getInitValue(){ - return this.initValue; - } - - public String getType(){ - return this.type; - } - - public FightProperty getFightProp() { - return fightProp; - } + @Getter private FightProperty fightProp; + @Getter private String propType; + @Getter private float initValue; + @Getter private String type; public void onLoad() { this.fightProp = FightProperty.getPropByName(propType); diff --git a/src/main/java/emu/grasscutter/game/avatar/Avatar.java b/src/main/java/emu/grasscutter/game/avatar/Avatar.java index 588052c42..92dd421ba 100644 --- a/src/main/java/emu/grasscutter/game/avatar/Avatar.java +++ b/src/main/java/emu/grasscutter/game/avatar/Avatar.java @@ -242,6 +242,23 @@ public class Avatar { this.promoteLevel = promoteLevel; } + static public int getMinPromoteLevel(int level) { + if (level > 80) { + return 6; + } else if (level > 70) { + return 5; + } else if (level > 60) { + return 4; + } else if (level > 50) { + return 3; + } else if (level > 40) { + return 2; + } else if (level > 20) { + return 1; + } + return 0; + } + public Int2ObjectMap getEquips() { return equips; } diff --git a/src/main/java/emu/grasscutter/game/inventory/GameItem.java b/src/main/java/emu/grasscutter/game/inventory/GameItem.java index 9aae598ff..6d5da5f34 100644 --- a/src/main/java/emu/grasscutter/game/inventory/GameItem.java +++ b/src/main/java/emu/grasscutter/game/inventory/GameItem.java @@ -33,34 +33,36 @@ import emu.grasscutter.net.proto.SceneReliquaryInfoOuterClass.SceneReliquaryInfo import emu.grasscutter.net.proto.SceneWeaponInfoOuterClass.SceneWeaponInfo; import emu.grasscutter.net.proto.WeaponOuterClass.Weapon; import emu.grasscutter.utils.WeightedList; +import lombok.Getter; +import lombok.Setter; @Entity(value = "items", useDiscriminator = false) public class GameItem { @Id private ObjectId id; @Indexed private int ownerId; - private int itemId; - private int count; + @Getter @Setter private int itemId; + @Getter @Setter private int count; - @Transient private long guid; // Player unique id - @Transient private ItemData itemData; + @Transient @Getter private long guid; // Player unique id + @Transient @Getter @Setter private ItemData itemData; // Equips - private int level; - private int exp; - private int totalExp; - private int promoteLevel; - private boolean locked; + @Getter @Setter private int level; + @Getter @Setter private int exp; + @Getter @Setter private int totalExp; + @Getter @Setter private int promoteLevel; + @Getter @Setter private boolean locked; // Weapon - private List affixes; - private int refinement = 0; + @Getter private List affixes; + @Getter @Setter private int refinement = 0; // Relic - private int mainPropId; - private List appendPropIdList; + @Getter @Setter private int mainPropId; + @Getter private List appendPropIdList; - private int equipCharacter; - @Transient private int weaponEntityId; + @Getter @Setter private int equipCharacter; + @Transient @Getter @Setter private int weaponEntityId; public GameItem() { // Morphia only @@ -82,42 +84,37 @@ public class GameItem { this.itemId = data.getId(); this.itemData = data; - if (data.getItemType() == ItemType.ITEM_VIRTUAL) { + switch (data.getItemType()) { + case ITEM_VIRTUAL: this.count = count; - } else { - this.count = Math.min(count, data.getStackLimit()); + break; + case ITEM_WEAPON: + this.count = 1; + this.level = Math.max(this.count, 1); // ?????????????????? + this.affixes = new ArrayList<>(2); + if (data.getSkillAffix() != null) { + for (int skillAffix : data.getSkillAffix()) { + if (skillAffix > 0) { + this.affixes.add(skillAffix); + } + } + } + break; + case ITEM_RELIQUARY: + this.count = 1; + this.level = 1; + this.appendPropIdList = new ArrayList<>(); + // Create main property + ReliquaryMainPropData mainPropData = GameDepot.getRandomRelicMainProp(data.getMainPropDepotId()); + if (mainPropData != null) { + this.mainPropId = mainPropData.getId(); + } + // Create extra stats + this.addAppendProps(data.getAppendPropNum()); + break; + default: + this.count = Math.min(count, data.getStackLimit()); } - - // Equip data - if (getItemType() == ItemType.ITEM_WEAPON) { - this.level = Math.max(this.count, 1); - this.affixes = new ArrayList<>(2); - if (getItemData().getSkillAffix() != null) { - for (int skillAffix : getItemData().getSkillAffix()) { - if (skillAffix > 0) { - this.affixes.add(skillAffix); - } - } - } - } else if (getItemType() == ItemType.ITEM_RELIQUARY) { - this.level = 1; - this.appendPropIdList = new ArrayList<>(); - // Create main property - ReliquaryMainPropData mainPropData = GameDepot.getRandomRelicMainProp(getItemData().getMainPropDepotId()); - if (mainPropData != null) { - this.mainPropId = mainPropData.getId(); - } - // Create extra stats - if (getItemData().getAppendPropNum() > 0) { - for (int i = 0; i < getItemData().getAppendPropNum(); i++) { - this.addAppendProp(); - } - } - } - } - - public ObjectId getObjectId() { - return id; } public int getOwnerId() { @@ -128,162 +125,88 @@ public class GameItem { this.ownerId = player.getUid(); this.guid = player.getNextGameGuid(); } - public int getItemId() { - return itemId; - } - public void setItemId(int itemId) { - this.itemId = itemId; - } - - public long getGuid() { - return guid; + public ObjectId getObjectId() { + return id; } public ItemType getItemType() { return this.itemData.getItemType(); } - public ItemData getItemData() { - return itemData; - } - - public void setItemData(ItemData materialData) { - this.itemData = materialData; - } - - public int getCount() { - return count; - } - - public void setCount(int count) { - this.count = count; - } - - public int getLevel() { - return level; - } - - public void setLevel(int level) { - this.level = level; - } - - public int getExp() { - return exp; - } - - public void setExp(int exp) { - this.exp = exp; - } - - public int getTotalExp() { - return totalExp; - } - - public void setTotalExp(int totalExp) { - this.totalExp = totalExp; - } - - public int getPromoteLevel() { - return promoteLevel; - } - - public void setPromoteLevel(int promoteLevel) { - this.promoteLevel = promoteLevel; + public static int getMinPromoteLevel(int level) { + if (level > 80) { + return 6; + } else if (level > 70) { + return 5; + } else if (level > 60) { + return 4; + } else if (level > 50) { + return 3; + } else if (level > 40) { + return 2; + } else if (level > 20) { + return 1; + } + return 0; } public int getEquipSlot() { return this.getItemData().getEquipType().getValue(); } - - public int getEquipCharacter() { - return equipCharacter; - } - - public void setEquipCharacter(int equipCharacter) { - this.equipCharacter = equipCharacter; - } public boolean isEquipped() { return this.getEquipCharacter() > 0; } - - public boolean isLocked() { - return locked; - } - - public void setLocked(boolean locked) { - this.locked = locked; - } public boolean isDestroyable() { return !this.isLocked() && !this.isEquipped(); } - - public int getWeaponEntityId() { - return weaponEntityId; - } - - public void setWeaponEntityId(int weaponEntityId) { - this.weaponEntityId = weaponEntityId; - } - - public List getAffixes() { - return affixes; - } - - public int getRefinement() { - return refinement; - } - - public void setRefinement(int refinement) { - this.refinement = refinement; - } - - public int getMainPropId() { - return mainPropId; - } - - public void setMainPropId(int mainPropId) { - this.mainPropId = mainPropId; - } - - public List getAppendPropIdList() { - return appendPropIdList; - } public void addAppendProp() { - if (this.getAppendPropIdList() == null) { + if (this.appendPropIdList == null) { this.appendPropIdList = new ArrayList<>(); } - if (this.getAppendPropIdList().size() < 4) { - addNewAppendProp(); + if (this.appendPropIdList.size() < 4) { + this.addNewAppendProp(); } else { - upgradeRandomAppendProp(); + this.upgradeRandomAppendProp(); } } + public void addAppendProps(int quantity) { + int num = Math.max(quantity, 0); + for (int i = 0; i < num; i++) { + this.addAppendProp(); + } + } + + private Set getAppendFightProperties() { + Set props = new HashSet<>(); + // Previously this would check no more than the first four affixes, however custom artifacts may not respect this order. + for (int appendPropId : this.appendPropIdList) { + ReliquaryAffixData affixData = GameData.getReliquaryAffixDataMap().get(appendPropId); + if (affixData != null) { + props.add(affixData.getFightProp()); + } + } + return props; + } + private void addNewAppendProp() { - List affixList = GameDepot.getRandomRelicAffixList(getItemData().getAppendPropDepotId()); + List affixList = GameDepot.getRelicAffixList(this.itemData.getAppendPropDepotId()); if (affixList == null) { return; } // Build blacklist - Dont add same stat as main/sub stat - Set blacklist = new HashSet<>(); - ReliquaryMainPropData mainPropData = GameData.getReliquaryMainPropDataMap().get(this.getMainPropId()); + Set blacklist = this.getAppendFightProperties(); + ReliquaryMainPropData mainPropData = GameData.getReliquaryMainPropDataMap().get(this.mainPropId); if (mainPropData != null) { blacklist.add(mainPropData.getFightProp()); } - int len = Math.min(4, this.getAppendPropIdList().size()); - for (int i = 0; i < len; i++) { - ReliquaryAffixData affixData = GameData.getReliquaryAffixDataMap().get((int) this.getAppendPropIdList().get(i)); - if (affixData != null) { - blacklist.add(affixData.getFightProp()); - } - } // Build random list WeightedList randomList = new WeightedList<>(); @@ -299,25 +222,18 @@ public class GameItem { // Add random stat ReliquaryAffixData affixData = randomList.next(); - this.getAppendPropIdList().add(affixData.getId()); + this.appendPropIdList.add(affixData.getId()); } private void upgradeRandomAppendProp() { - List affixList = GameDepot.getRandomRelicAffixList(getItemData().getAppendPropDepotId()); + List affixList = GameDepot.getRelicAffixList(this.itemData.getAppendPropDepotId()); if (affixList == null) { return; } // Build whitelist - Set whitelist = new HashSet<>(); - int len = Math.min(4, this.getAppendPropIdList().size()); - for (int i = 0; i < len; i++) { - ReliquaryAffixData affixData = GameData.getReliquaryAffixDataMap().get((int) this.getAppendPropIdList().get(i)); - if (affixData != null) { - whitelist.add(affixData.getFightProp()); - } - } + Set whitelist = this.getAppendFightProperties(); // Build random list WeightedList randomList = new WeightedList<>(); @@ -329,7 +245,7 @@ public class GameItem { // Add random stat ReliquaryAffixData affixData = randomList.next(); - this.getAppendPropIdList().add(affixData.getId()); + this.appendPropIdList.add(affixData.getId()); } @PostLoad diff --git a/src/main/java/emu/grasscutter/game/managers/InventoryManager.java b/src/main/java/emu/grasscutter/game/managers/InventoryManager.java index 361f54382..47fc2d8c1 100644 --- a/src/main/java/emu/grasscutter/game/managers/InventoryManager.java +++ b/src/main/java/emu/grasscutter/game/managers/InventoryManager.java @@ -147,7 +147,7 @@ public class InventoryManager { int totalExp = relic.getTotalExp(); int reqExp = GameData.getRelicExpRequired(relic.getItemData().getRankLevel(), level); int upgrades = 0; - List oldAppendPropIdList = relic.getAppendPropIdList(); + List oldAppendPropIdList = new ArrayList<>(relic.getAppendPropIdList()); while (expGain > 0 && reqExp > 0 && level < relic.getItemData().getMaxLevel()) { // Do calculations @@ -169,13 +169,7 @@ public class InventoryManager { } } - if (upgrades > 0) { - oldAppendPropIdList = new ArrayList<>(relic.getAppendPropIdList()); - while (upgrades > 0) { - relic.addAppendProp(); - upgrades -= 1; - } - } + relic.addAppendProps(upgrades); // Save relic.setLevel(level); diff --git a/src/main/java/emu/grasscutter/game/props/FightProperty.java b/src/main/java/emu/grasscutter/game/props/FightProperty.java index e36c432f5..a51ecdbad 100644 --- a/src/main/java/emu/grasscutter/game/props/FightProperty.java +++ b/src/main/java/emu/grasscutter/game/props/FightProperty.java @@ -1,7 +1,13 @@ package emu.grasscutter.game.props; import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.Set; + +import static java.util.Map.entry; + +import java.util.Arrays; import java.util.stream.Stream; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; @@ -133,4 +139,65 @@ public enum FightProperty { public static FightProperty getPropByName(String name) { return stringMap.getOrDefault(name, FIGHT_PROP_NONE); } + + public static FightProperty getPropByShortName(String name) { + return shortNameMap.getOrDefault(name, FIGHT_PROP_NONE); + } + + public static Set getShortNames() { + return shortNameMap.keySet(); + } + + // This was originally for relic properties so some names might not be applicable for e.g. setstats + private static final Map shortNameMap = Map.ofEntries( + // Normal relic stats + entry("hp", FIGHT_PROP_HP), + entry("atk", FIGHT_PROP_ATTACK), + entry("def", FIGHT_PROP_DEFENSE), + entry("hp%", FIGHT_PROP_HP_PERCENT), + entry("atk%", FIGHT_PROP_ATTACK_PERCENT), + entry("def%", FIGHT_PROP_DEFENSE_PERCENT), + entry("em", FIGHT_PROP_ELEMENT_MASTERY), + entry("er", FIGHT_PROP_CHARGE_EFFICIENCY), + entry("hb", FIGHT_PROP_HEAL_ADD), + entry("heal", FIGHT_PROP_HEAL_ADD), + entry("cd", FIGHT_PROP_CRITICAL_HURT), + entry("cdmg", FIGHT_PROP_CRITICAL_HURT), + entry("cr", FIGHT_PROP_CRITICAL), + entry("crate", FIGHT_PROP_CRITICAL), + entry("phys%", FIGHT_PROP_PHYSICAL_ADD_HURT), + entry("dendro%", FIGHT_PROP_GRASS_ADD_HURT), + entry("geo%", FIGHT_PROP_ROCK_ADD_HURT), + entry("anemo%", FIGHT_PROP_WIND_ADD_HURT), + entry("hydro%", FIGHT_PROP_WATER_ADD_HURT), + entry("cryo%", FIGHT_PROP_ICE_ADD_HURT), + entry("electro%", FIGHT_PROP_ELEC_ADD_HURT), + entry("pyro%", FIGHT_PROP_FIRE_ADD_HURT), + // Other stats + entry("maxhp", FIGHT_PROP_MAX_HP), + entry("dmg", FIGHT_PROP_ADD_HURT), // This seems to get reset after attacks + entry("cdr", FIGHT_PROP_SKILL_CD_MINUS_RATIO), + entry("heali", FIGHT_PROP_HEALED_ADD), + entry("shield", FIGHT_PROP_SHIELD_COST_MINUS_RATIO), + entry("defi", FIGHT_PROP_DEFENCE_IGNORE_RATIO), + entry("resall", FIGHT_PROP_SUB_HURT), // This seems to get reset after attacks + entry("resanemo", FIGHT_PROP_WIND_SUB_HURT), + entry("rescryo", FIGHT_PROP_ICE_SUB_HURT), + entry("resdendro", FIGHT_PROP_GRASS_SUB_HURT), + entry("reselectro", FIGHT_PROP_ELEC_SUB_HURT), + entry("resgeo", FIGHT_PROP_ROCK_SUB_HURT), + entry("reshydro", FIGHT_PROP_WATER_SUB_HURT), + entry("respyro", FIGHT_PROP_FIRE_SUB_HURT), + entry("resphys", FIGHT_PROP_PHYSICAL_SUB_HURT) + ); + + private static final List flatProps = Arrays.asList( + FIGHT_PROP_BASE_HP, FIGHT_PROP_HP, FIGHT_PROP_BASE_ATTACK, FIGHT_PROP_ATTACK, FIGHT_PROP_BASE_DEFENSE, + FIGHT_PROP_DEFENSE, FIGHT_PROP_HEALED_ADD, FIGHT_PROP_CUR_FIRE_ENERGY, FIGHT_PROP_CUR_ELEC_ENERGY, + FIGHT_PROP_CUR_WATER_ENERGY, FIGHT_PROP_CUR_GRASS_ENERGY, FIGHT_PROP_CUR_WIND_ENERGY, FIGHT_PROP_CUR_ICE_ENERGY, + FIGHT_PROP_CUR_ROCK_ENERGY, FIGHT_PROP_CUR_HP, FIGHT_PROP_MAX_HP, FIGHT_PROP_CUR_ATTACK, FIGHT_PROP_CUR_DEFENSE); + + public static boolean isPercentage(FightProperty prop) { + return !flatProps.contains(prop); + } } diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketGetMailItemRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketGetMailItemRsp.java index cdf2e2349..0cac39e88 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketGetMailItemRsp.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketGetMailItemRsp.java @@ -30,21 +30,7 @@ public class PacketGetMailItemRsp extends BasePacket { if (!message.isAttachmentGot) {//No duplicated item for (Mail.MailItem mailItem : message.itemList) { EquipParamOuterClass.EquipParam.Builder item = EquipParamOuterClass.EquipParam.newBuilder(); - int promoteLevel = 0; - - if (mailItem.itemLevel > 80) { // 80/90 - promoteLevel = 6; - } else if (mailItem.itemLevel > 70) { // 70/80 - promoteLevel = 5; - } else if (mailItem.itemLevel > 60) { // 60/70 - promoteLevel = 4; - } else if (mailItem.itemLevel > 50) { // 50/60 - promoteLevel = 3; - } else if (mailItem.itemLevel > 40) { // 40/50 - promoteLevel = 2; - } else if (mailItem.itemLevel > 20) { // 20/40 - promoteLevel = 1; - } + int promoteLevel = GameItem.getMinPromoteLevel(mailItem.itemLevel); item.setItemId(mailItem.itemId); item.setItemNum(mailItem.itemCount); diff --git a/src/main/java/emu/grasscutter/utils/SparseSet.java b/src/main/java/emu/grasscutter/utils/SparseSet.java new file mode 100644 index 000000000..b4913b5f5 --- /dev/null +++ b/src/main/java/emu/grasscutter/utils/SparseSet.java @@ -0,0 +1,60 @@ +package emu.grasscutter.utils; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; + +public final class SparseSet { + /* + * A convenience class for constructing integer sets out of large ranges + * Designed to be fed literal strings from this project only - + * can and will throw exceptions to tell you to fix your code if you feed it garbage. :) + */ + private static class Range { + private final int min, max; + + public Range(int min, int max) { + if (min > max) { + throw new IllegalArgumentException("Range passed minimum higher than maximum - " + Integer.toString(min) + " > " + Integer.toString(max)); + } + this.min = min; + this.max = max; + } + + public boolean check(int value) { + return value >= this.min && value <= this.max; + } + } + + private final List rangeEntries; + private final Set denseEntries; + + public SparseSet(String csv) { + this.rangeEntries = new ArrayList<>(); + this.denseEntries = new TreeSet<>(); + + for (String token : csv.replace("\n", "").replace(" ", "").split(",")) { + String[] tokens = token.split("-"); + switch (tokens.length) { + case 1: + this.denseEntries.add(Integer.parseInt(tokens[0])); + break; + case 2: + this.rangeEntries.add(new Range(Integer.parseInt(tokens[0]), Integer.parseInt(tokens[1]))); + break; + default: + throw new IllegalArgumentException("Invalid token passed to SparseSet initializer - " + token + " (split length " + Integer.toString(tokens.length) + ")"); + } + } + } + + public boolean contains(int i) { + for (Range range : this.rangeEntries) { + if (range.check(i)) { + return true; + } + } + return this.denseEntries.contains(i); + } +} \ No newline at end of file diff --git a/src/main/resources/languages/en-US.json b/src/main/resources/languages/en-US.json index dc31e518a..558b61844 100644 --- a/src/main/resources/languages/en-US.json +++ b/src/main/resources/languages/en-US.json @@ -132,35 +132,16 @@ "in_dungeon_error": "You are already in that dungeon.", "description": "Enter a dungeon" }, - "giveAll": { - "usage": "Usage: giveall [player] [amount]", - "started": "Receiving all items...", - "success": "Successfully gave all items to %s.", - "invalid_amount_or_playerId": "Invalid amount or player ID.", - "description": "Gives all items" - }, - "giveArtifact": { - "usage": "Usage: giveart|gart [player] [[,]]... [level]", - "id_error": "Invalid artifact ID.", - "success": "Given %s to %s.", - "description": "Gives the player a specified artifact" - }, - "giveChar": { - "usage": "Usage: givechar [level]", - "given": "Given %s with level %s to %s.", - "invalid_avatar_id": "Invalid avatar ID.", - "invalid_avatar_level": "Invalid avatar level.", - "invalid_avatar_or_player_id": "Invalid avatar or player ID.", - "description": "Gives the player a specified character" - }, "give": { - "usage": "Usage: give [amount] [level] [refinement]", - "refinement_only_applicable_weapons": "Refinement is only applicable to weapons.", - "refinement_must_between_1_and_5": "Refinement must be between 1 and 5.", + "usage": "Usage: give [x] [lv] [r]", + "usage_relic": "Usage: give [mainPropID] [[,]]... [lv]", + "illegal_relic": "This artifactID belongs to a blacklisted range, it may not be the one you wanted.", "given": "Given %s of %s to %s.", "given_with_level_and_refinement": "Given %s with level %s, refinement %s %s times to %s.", "given_level": "Given %s with level %s %s times to %s.", - "description": "Gives an item to you or the specified player" + "given_avatar": "Given %s with level %s to %s.", + "giveall_success": "Successfully gave all items.", + "description": "Gives an item to you or the specified player. Can also give all weapons, avatars and/or materials, and can construct custom artifacts." }, "godmode": { "success": "Godmode is now %s for %s.", @@ -202,10 +183,6 @@ "success": "There are %s player(s) online:", "description": "List online players" }, - "nostamina": { - "success": "NoStamina is now %s for %s.", - "description": "Keep your endurance to the maximum." - }, "permission": { "usage": "Usage: permission ", "add": "Permission added.", diff --git a/src/main/resources/languages/fr-FR.json b/src/main/resources/languages/fr-FR.json index d9b212325..b3b5583d3 100644 --- a/src/main/resources/languages/fr-FR.json +++ b/src/main/resources/languages/fr-FR.json @@ -146,21 +146,14 @@ "success": "%s a été donné à %s.", "description": "Donne au joueur l'artéfact spécifié." }, - "giveChar": { - "usage": "Usage: givechar [niveau]", - "given": "%s avec le niveau %s a été donné à %s.", - "invalid_avatar_id": "ID de l'avatar invalide.", - "invalid_avatar_level": "Niveau de l'avatar invalide.", - "invalid_avatar_or_player_id": "ID de l'avatar ou du joueur invalide.", - "description": "Donne au joueur le personnage spécifié" - }, "give": { - "usage": "Usage: give [quantité] [niveau] [raffinement]", + "usage": "Usage: give [quantité] [niveau] [raffinement]", "refinement_only_applicable_weapons": "Le raffinement est uniquement applicable aux armes.", "refinement_must_between_1_and_5": "Le raffinement doit être compris entre 1 et 5.", "given": "Given %s of %s to %s.", "given_with_level_and_refinement": "%s avec le niveau %s, raffinement %s %s fois à %s.", "given_level": "%s avec le niveau %s %s fois à %s.", + "given_avatar": "%s avec le niveau %s a été donné à %s.", "description": "Donne un objet au joueur spécifié" }, "godmode": { diff --git a/src/main/resources/languages/pl-PL.json b/src/main/resources/languages/pl-PL.json index 9848d5abc..0d8b5a29e 100644 --- a/src/main/resources/languages/pl-PL.json +++ b/src/main/resources/languages/pl-PL.json @@ -134,20 +134,14 @@ "id_error": "Błędne ID artefaktu.", "success": "Dano %s dla %s." }, - "giveChar": { - "usage": "Użycie: givechar [ilość]", - "given": "Dano %s z poziomem %s dla %s.", - "invalid_avatar_id": "Błędne ID postaci.", - "invalid_avatar_level": "Błędny poziom postaci.", - "invalid_avatar_or_player_id": "Błędne ID postaci lub gracza." - }, "give": { - "usage": "Użycie: give [ilość] [poziom] [refinement]", + "usage": "Użycie: give [ilość] [poziom] [refinement]", "refinement_only_applicable_weapons": "Ulepszenie można zastosować tylko dla broni.", "refinement_must_between_1_and_5": "Ulepszenie musi być pomiędzy 1, a 5.", "given": "Dano %s %s dla %s.", "given_with_level_and_refinement": "Dano %s z poziomem %s, ulepszeniem %s %s razy dla %s", - "given_level": "Dano %s z poziomem %s %s razy dla %s" + "given_level": "Dano %s z poziomem %s %s razy dla %s", + "given_avatar": "Dano %s z poziomem %s dla %s." }, "godmode": { "success": "Godmode jest teraz %s dla %s." diff --git a/src/main/resources/languages/zh-CN.json b/src/main/resources/languages/zh-CN.json index eed31bf0c..a2a8018ae 100644 --- a/src/main/resources/languages/zh-CN.json +++ b/src/main/resources/languages/zh-CN.json @@ -145,21 +145,14 @@ "success": "已将 %s 给予 %s。", "description": "给予指定圣遗物" }, - "giveChar": { - "usage": "用法:givechar <玩家> <角色ID> [等级]", - "given": "已将角色 %s [等级 %s] 给与 %s。", - "invalid_avatar_id": "无效的角色ID。", - "invalid_avatar_level": "无效的角色等级。", - "invalid_avatar_or_player_id": "无效的角色ID/玩家ID。", - "description": "给予指定角色" - }, "give": { - "usage": "用法:give <玩家> <物品ID|物品名> [数量] [等级] [精炼等级]", + "usage": "用法:give <玩家> <物品ID|角色ID> [数量] [等级] [精炼等级]", "refinement_only_applicable_weapons": "只有武器可以设置精炼等级。", "refinement_must_between_1_and_5": "精炼等级必须在 1-5 之间。", "given": "已将 %s 个 %s 给予 %s。", "given_with_level_and_refinement": "已将 %s [等级 %s, 精炼 %s] %s 个给予 %s。", "given_level": "已将 %s [等级 %s] %s 个给予 %s。", + "given_avatar": "已将角色 %s [等级 %s] 给与 %s。", "description": "给予指定物品" }, "godmode": { diff --git a/src/main/resources/languages/zh-TW.json b/src/main/resources/languages/zh-TW.json index a29005e32..129bcf4e8 100644 --- a/src/main/resources/languages/zh-TW.json +++ b/src/main/resources/languages/zh-TW.json @@ -144,21 +144,14 @@ "success": "已把 %s 給予 %s。", "description": "給予指定聖遺物。" }, - "giveChar": { - "usage": "用法:givechar [level]", - "given": "已將 %s 等級 %s 給予 %s。", - "invalid_avatar_id": "無效的角色ID。", - "invalid_avatar_level": "無效的角色等級。.", - "invalid_avatar_or_player_id": "無效的角色ID/玩家ID。", - "description": "給予指定角色。" - }, "give": { - "usage": "用法:give [amount] [level] [refinement]", + "usage": "用法:give [amount] [level] [refinement]", "refinement_only_applicable_weapons": "精煉度只能施加在武器上面。", "refinement_must_between_1_and_5": "精煉度必需在 1 到 5 之間。", "given": "已經將 %s 個 %s 給予 %s。", "given_with_level_and_refinement": "已將 %s [等級%s, 精煉%s] %s個給予 %s", "given_level": "已將 %s 等級 %s %s 個給予 %s", + "given_avatar": "已將 %s 等級 %s 給予 %s。", "description": "給予指定物品。" }, "godmode": {