Commit baafb410 authored by AnimeGitB's avatar AnimeGitB Committed by Melledy
Browse files

Remove GiveAll, GiveArt, GiveChar commands

parent 6fd1ce81
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<String> 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<GameItem> 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<Integer> testItemsList = Arrays.asList(testItemsIds);
}
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 <artifactId> <mainPropId> [<appendPropId>[,<times>]]... [level]", aliases = {"gart"}, permission = "player.giveart", permissionTargeted = "player.giveart.others", description = "commands.giveArtifact.description")
public final class GiveArtifactCommand implements CommandHandler {
private static final Map<String, Map<EquipType, Integer>> 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<String, String> 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<String> 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<Integer> 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())));
}
}
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 <avatarId> [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<String> 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())));
}
}
package emu.grasscutter.command.commands; package emu.grasscutter.command.commands;
import emu.grasscutter.GameConstants;
import emu.grasscutter.command.Command; import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler; import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.data.GameData; 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.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.GameItem;
import emu.grasscutter.game.inventory.ItemType; import emu.grasscutter.game.inventory.ItemType;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ActionReason; 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.List;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import static emu.grasscutter.utils.Language.translate; @Command(label = "give", usage = "give <itemId|avatarId|\"all\"|\"weapons\"|\"mats\"|\"avatars\"> [lv<level>] [r<refinement>] [x<amount>] | give <artifactId> [lv<level>] [x<amount>] [mainPropId] [<appendPropId>[,<times>]]...", aliases = {
@Command(label = "give", usage = "give <itemId|itemName> [amount] [level]", aliases = {
"g", "item", "giveitem"}, permission = "player.give", permissionTargeted = "player.give.others", description = "commands.give.description") "g", "item", "giveitem"}, permission = "player.give", permissionTargeted = "player.give.others", description = "commands.give.description")
public final class GiveCommand implements CommandHandler { 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 private static Pattern lvlRegex = Pattern.compile("l(?:vl?)?(\\d+)"); // Java doesn't have raw string literals :(
Pattern refineRegex = Pattern.compile("r(\\d+)"); private static Pattern refineRegex = Pattern.compile("r(\\d+)");
Pattern amountRegex = Pattern.compile("((?<=x)\\d+|\\d+(?=x)(?!x\\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); Matcher match = pattern.matcher(arg);
if (match.find()) { 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) 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 { ...@@ -31,27 +39,50 @@ public final class GiveCommand implements CommandHandler {
return -1; return -1;
} }
@Override private static enum GiveAllType {
public void execute(Player sender, Player targetPlayer, List<String> args) { NONE,
int item; ALL,
int lvl = 1; WEAPONS,
int amount = 1; MATS,
int refinement = 0; AVATARS
}
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<Integer> appendPropIdList;
public ItemData data;
public AvatarData avatarData;
public GiveAllType giveAllType = GiveAllType.NONE;
};
for (int i = args.size()-1; i>=0; i--) { // Reverse iteration as we are deleting elements private static GiveItemParameters parseArgs(Player sender, List<String> 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(); String arg = args.get(i).toLowerCase();
boolean deleteArg = false; boolean deleteArg = false;
int argNum; int argNum;
// Note that a single argument can actually match all of these, e.g. "lv90r5x100"
if ((argNum = matchIntOrNeg(lvlRegex, arg)) != -1) { if ((argNum = matchIntOrNeg(lvlRegex, arg)) != -1) {
lvl = argNum; param.lvl = argNum;
deleteArg = true; deleteArg = true;
} }
if ((argNum = matchIntOrNeg(refineRegex, arg)) != -1) { if ((argNum = matchIntOrNeg(refineRegex, arg)) != -1) {
refinement = argNum; param.refinement = argNum;
deleteArg = true;
}
if ((argNum = matchIntOrNeg(constellationRegex, arg)) != -1) {
param.constellation = argNum;
deleteArg = true; deleteArg = true;
} }
if ((argNum = matchIntOrNeg(amountRegex, arg)) != -1) { if ((argNum = matchIntOrNeg(amountRegex, arg)) != -1) {
amount = argNum; param.amount = argNum;
deleteArg = true; deleteArg = true;
} }
if (deleteArg) { if (deleteArg) {
...@@ -59,112 +90,387 @@ public final class GiveCommand implements CommandHandler { ...@@ -59,112 +90,387 @@ public final class GiveCommand implements CommandHandler {
} }
} }
switch (args.size()) { // At this point, first remaining argument MUST be itemId/avatarId
case 4: // <itemId|itemName> [amount] [level] [refinement] 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;
case "weapons":
param.giveAllType = GiveAllType.WEAPONS;
break;
case "mats":
param.giveAllType = GiveAllType.MATS;
break;
case "avatars":
param.giveAllType = GiveAllType.AVATARS;
break;
default:
try { try {
refinement = Integer.parseInt(args.get(3)); param.id = Integer.parseInt(id);
} catch (NumberFormatException ignored) { param.data = GameData.getItemDataMap().get(param.id);
CommandHandler.sendMessage(sender, translate(sender, "commands.generic.invalid.itemRefinement")); if ((param.id > 10_000_000) && (param.id < 12_000_000))
return; param.avatarData = GameData.getAvatarDataMap().get(param.id);
} // Fallthrough else if ((param.id > 1000) && (param.id < 1100))
case 3: // <itemId|itemName> [amount] [level] 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;
}
}
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 { try {
lvl = Integer.parseInt(args.get(2)); parseRelicArgs(param, args);
} catch (NumberFormatException ignored) { } catch (IllegalArgumentException e) {
CommandHandler.sendMessage(sender, translate(sender, "commands.generic.invalid.itemLevel")); 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<String> args) {
if (args.size() < 1) { // *No args*
CommandHandler.sendTranslatedMessage(sender, "commands.give.usage");
return; return;
} // Fallthrough }
case 2: // <itemId|itemName> [amount]
try { try {
amount = Integer.parseInt(args.get(1)); GiveItemParameters param = parseArgs(sender, args);
} catch (NumberFormatException ignored) {
CommandHandler.sendMessage(sender, translate(sender, "commands.generic.invalid.amount")); switch (param.giveAllType) {
case ALL:
giveAll(targetPlayer, param);
CommandHandler.sendTranslatedMessage(sender, "commands.give.giveall_success");
return; return;
} // Fallthrough case WEAPONS:
case 1: // <itemId|itemName> giveAllWeapons(targetPlayer, param);
try { CommandHandler.sendTranslatedMessage(sender, "commands.give.giveall_success");
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; return;
} case MATS:
break; giveAllMats(targetPlayer, param);
default: // *No args* CommandHandler.sendTranslatedMessage(sender, "commands.give.giveall_success");
CommandHandler.sendMessage(sender, translate(sender, "commands.give.usage"));
return; return;
case AVATARS:
giveAllAvatars(targetPlayer, param);
CommandHandler.sendTranslatedMessage(sender, "commands.give.giveall_success");
return;
case NONE:
break;
} }
ItemData itemData = GameData.getItemDataMap().get(item); // Check if this is an avatar
if (itemData == null) { if (param.avatarData != null) {
CommandHandler.sendMessage(sender, translate(sender, "commands.generic.invalid.itemId")); 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; return;
} }
if (refinement != 0) { // If it's not an avatar, it needs to be a valid item
if (itemData.getItemType() == ItemType.ITEM_WEAPON) { if (param.data == null) {
if (refinement < 1 || refinement > 5) { CommandHandler.sendTranslatedMessage(sender, "commands.generic.invalid.itemId");
CommandHandler.sendMessage(sender, translate(sender, "commands.give.refinement_must_between_1_and_5"));
return; return;
} }
} else {
CommandHandler.sendMessage(sender, translate(sender, "commands.give.refinement_only_applicable_weapons")); 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; 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;
}
} }
private static Avatar makeAvatar(GiveItemParameters param) {
return makeAvatar(param.avatarData, param.lvl, Avatar.getMinPromoteLevel(param.lvl), 0);
} }
this.item(targetPlayer, itemData, amount, lvl, refinement); 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);
if (!itemData.isEquip()) { // Add constellations.
CommandHandler.sendMessage(sender, translate(sender, "commands.give.given", Integer.toString(amount), Integer.toString(item), Integer.toString(targetPlayer.getUid()))); int talentBase = switch (avatar.getAvatarId()) {
} else if (itemData.getItemType() == ItemType.ITEM_WEAPON) { case 10000005 -> 70;
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()))); case 10000006 -> 40;
} else { default -> (avatar.getAvatarId() - 10000000) * 10;
CommandHandler.sendMessage(sender, translate(sender, "commands.give.given_level", Integer.toString(item), Integer.toString(lvl), Integer.toString(amount), Integer.toString(targetPlayer.getUid()))); };
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));
} }
private void item(Player player, ItemData itemData, int amount, int lvl, int refinement) { avatar.recalcStats();
if (itemData.isEquip()) {
List<GameItem> items = new LinkedList<>(); return avatar;
for (int i = 0; i < amount; i++) { }
GameItem item = new GameItem(itemData);
if (item.isEquipped()) { private static void giveAllAvatars(Player player, GiveItemParameters param) {
// check item max level int promoteLevel = Avatar.getMinPromoteLevel(param.lvl);
if (item.getItemType() == ItemType.ITEM_WEAPON) { if (param.constellation < 0) {
if (lvl > 90) lvl = 90; param.constellation = 6;
} else { }
if (lvl > 21) lvl = 21; for (AvatarData avatarData : GameData.getAvatarDataMap().values()) {
} // Exclude test avatars
} int id = avatarData.getId();
item.setCount(amount); if (id < 10000002 || id >= 11000000) continue;
item.setLevel(lvl);
if (lvl > 80) { // Don't try to add each avatar to the current team
item.setPromoteLevel(6); player.addAvatar(makeAvatar(avatarData, param.lvl, promoteLevel, param.constellation), false);
} else if (lvl > 70) { }
item.setPromoteLevel(5); }
} else if (lvl > 60) {
item.setPromoteLevel(4); private static List<GameItem> makeUnstackableItems(GiveItemParameters param) {
} else if (lvl > 50) { int promoteLevel = GameItem.getMinPromoteLevel(param.lvl);
item.setPromoteLevel(3); int totalExp = 0;
} else if (lvl > 40) { if (param.data.getItemType() == ItemType.ITEM_WEAPON) {
item.setPromoteLevel(2); int rankLevel = param.data.getRankLevel();
} else if (lvl > 20) { for (int i = 1; i < param.lvl; i++)
item.setPromoteLevel(1); totalExp += GameData.getWeaponExpRequired(rankLevel, i);
} }
List<GameItem> 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) { if (item.getItemType() == ItemType.ITEM_WEAPON) {
if (refinement > 0) { item.setPromoteLevel(promoteLevel);
item.setRefinement(refinement - 1); item.setTotalExp(totalExp);
} else { item.setRefinement(param.refinement - 1); // Actual refinement data is 0..4 not 1..5
item.setRefinement(0); }
items.add(item);
} }
return items;
} }
private static List<GameItem> 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<GameItem> 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); items.add(item);
} }
player.getInventory().addItems(items, ActionReason.SubfieldDrop); return items;
} else { }
GameItem item = new GameItem(itemData);
item.setCount(amount); private static int getArtifactMainProp(ItemData itemData, FightProperty prop) throws IllegalArgumentException {
player.getInventory().addItem(item, ActionReason.SubfieldDrop); 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<Integer> getArtifactAffixes(ItemData itemData, FightProperty prop) throws IllegalArgumentException {
if (prop == FightProperty.FIGHT_PROP_NONE) {
throw new IllegalArgumentException();
}
List<Integer> 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<Integer> 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<String> 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<GameItem> 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<GameItem> 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<GameItem> 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
""");
} }
...@@ -4,15 +4,12 @@ import java.util.HashMap; ...@@ -4,15 +4,12 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.command.Command; import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler; import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.entity.EntityAvatar; import emu.grasscutter.game.entity.EntityAvatar;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.FightProperty; import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify; import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify;
import emu.grasscutter.utils.Language;
import static emu.grasscutter.utils.Language.translate; import static emu.grasscutter.utils.Language.translate;
@Command(label = "setstats", usage = "setstats|stats <stat> <value>", aliases = {"stats"}, permission = "player.setstats", permissionTargeted = "player.setstats.others", description = "commands.setStats.description") @Command(label = "setstats", usage = "setstats|stats <stat> <value>", aliases = {"stats"}, permission = "player.setstats", permissionTargeted = "player.setstats.others", description = "commands.setStats.description")
...@@ -20,157 +17,50 @@ public final class SetStatsCommand implements CommandHandler { ...@@ -20,157 +17,50 @@ public final class SetStatsCommand implements CommandHandler {
static class Stat { static class Stat {
String name; String name;
FightProperty prop; 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.name = name;
this.prop = prop; this.prop = prop;
this.percent = percent;
} }
} }
Map<String, Stat> stats = new HashMap<>(); Map<String, Stat> stats;
public SetStatsCommand() { public SetStatsCommand() {
// Default stats this.stats = new HashMap<>();
stats.put("maxhp", new Stat(FightProperty.FIGHT_PROP_MAX_HP.toString(), FightProperty.FIGHT_PROP_MAX_HP, false)); for (String key : FightProperty.getShortNames()) {
stats.put("hp", new Stat(FightProperty.FIGHT_PROP_CUR_HP.toString(), FightProperty.FIGHT_PROP_CUR_HP, false)); this.stats.put(key, new Stat(FightProperty.getPropByShortName(key)));
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"));
// Full FightProperty enum that won't be advertised but can be used by devs // Full FightProperty enum that won't be advertised but can be used by devs
// They have a prefix to avoid the "hp" clash // They have a prefix to avoid the "hp" clash
stats.put("_none", new Stat("NONE", FightProperty.FIGHT_PROP_NONE, true)); for (FightProperty prop : FightProperty.values()) {
stats.put("_base_hp", new Stat("BASE_HP", FightProperty.FIGHT_PROP_BASE_HP, false)); String name = prop.toString().substring(10); // FIGHT_PROP_BASE_HP -> _BASE_HP
stats.put("_hp", new Stat("HP", FightProperty.FIGHT_PROP_HP, false)); String key = name.toLowerCase(); // _BASE_HP -> _base_hp
stats.put("_hp_percent", new Stat("HP_PERCENT", FightProperty.FIGHT_PROP_HP_PERCENT, true)); name = name.substring(1); // _BASE_HP -> BASE_HP
stats.put("_base_attack", new Stat("BASE_ATTACK", FightProperty.FIGHT_PROP_BASE_ATTACK, false)); this.stats.put(key, new Stat(name, prop));
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)); // Compatibility aliases
stats.put("_defense", new Stat("DEFENSE", FightProperty.FIGHT_PROP_DEFENSE, false)); this.stats.put("mhp", this.stats.get("maxhp"));
stats.put("_defense_percent", new Stat("DEFENSE_PERCENT", FightProperty.FIGHT_PROP_DEFENSE_PERCENT, true)); this.stats.put("hp", new Stat(FightProperty.FIGHT_PROP_CUR_HP)); // Overrides FIGHT_PROP_HP
stats.put("_base_speed", new Stat("BASE_SPEED", FightProperty.FIGHT_PROP_BASE_SPEED, true)); this.stats.put("atk", new Stat(FightProperty.FIGHT_PROP_CUR_ATTACK)); // Overrides FIGHT_PROP_ATTACK
stats.put("_speed_percent", new Stat("SPEED_PERCENT", FightProperty.FIGHT_PROP_SPEED_PERCENT, true)); 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.
stats.put("_hp_mp_percent", new Stat("HP_MP_PERCENT", FightProperty.FIGHT_PROP_HP_MP_PERCENT, true)); this.stats.put("eanemo", this.stats.get("anemo%"));
stats.put("_attack_mp_percent", new Stat("ATTACK_MP_PERCENT", FightProperty.FIGHT_PROP_ATTACK_MP_PERCENT, true)); this.stats.put("ecryo", this.stats.get("cryo%"));
stats.put("_critical", new Stat("CRITICAL", FightProperty.FIGHT_PROP_CRITICAL, true)); this.stats.put("edendro", this.stats.get("dendro%"));
stats.put("_anti_critical", new Stat("ANTI_CRITICAL", FightProperty.FIGHT_PROP_ANTI_CRITICAL, true)); this.stats.put("edend", this.stats.get("dendro%"));
stats.put("_critical_hurt", new Stat("CRITICAL_HURT", FightProperty.FIGHT_PROP_CRITICAL_HURT, true)); this.stats.put("eelectro", this.stats.get("electro%"));
stats.put("_charge_efficiency", new Stat("CHARGE_EFFICIENCY", FightProperty.FIGHT_PROP_CHARGE_EFFICIENCY, true)); this.stats.put("eelec", this.stats.get("electro%"));
stats.put("_add_hurt", new Stat("ADD_HURT", FightProperty.FIGHT_PROP_ADD_HURT, true)); this.stats.put("ethunder", this.stats.get("electro%"));
stats.put("_sub_hurt", new Stat("SUB_HURT", FightProperty.FIGHT_PROP_SUB_HURT, true)); this.stats.put("egeo", this.stats.get("geo%"));
stats.put("_heal_add", new Stat("HEAL_ADD", FightProperty.FIGHT_PROP_HEAL_ADD, true)); this.stats.put("ehydro", this.stats.get("hydro%"));
stats.put("_healed_add", new Stat("HEALED_ADD", FightProperty.FIGHT_PROP_HEALED_ADD, false)); this.stats.put("epyro", this.stats.get("pyro%"));
stats.put("_element_mastery", new Stat("ELEMENT_MASTERY", FightProperty.FIGHT_PROP_ELEMENT_MASTERY, true)); this.stats.put("ephys", this.stats.get("phys%"));
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));
} }
@Override @Override
...@@ -206,8 +96,8 @@ public final class SetStatsCommand implements CommandHandler { ...@@ -206,8 +96,8 @@ public final class SetStatsCommand implements CommandHandler {
Stat stat = stats.get(statStr); Stat stat = stats.get(statStr);
entity.setFightProperty(stat.prop, value); entity.setFightProperty(stat.prop, value);
entity.getWorld().broadcastPacket(new PacketEntityFightPropUpdateNotify(entity, stat.prop)); entity.getWorld().broadcastPacket(new PacketEntityFightPropUpdateNotify(entity, stat.prop));
if (stat.percent) { if (FightProperty.isPercentage(stat.prop)) {
valueStr = String.format("%.1f%%", value*100f); valueStr = String.format("%.1f%%", value * 100f);
} else { } else {
valueStr = String.format("%.0f", value); valueStr = String.format("%.0f", value);
} }
......
...@@ -20,7 +20,8 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectMap; ...@@ -20,7 +20,8 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
public class GameDepot { public class GameDepot {
private static Int2ObjectMap<WeightedList<ReliquaryMainPropData>> relicMainPropDepot = new Int2ObjectOpenHashMap<>(); private static Int2ObjectMap<WeightedList<ReliquaryMainPropData>> relicRandomMainPropDepot = new Int2ObjectOpenHashMap<>();
private static Int2ObjectMap<List<ReliquaryMainPropData>> relicMainPropDepot = new Int2ObjectOpenHashMap<>();
private static Int2ObjectMap<List<ReliquaryAffixData>> relicAffixDepot = new Int2ObjectOpenHashMap<>(); private static Int2ObjectMap<List<ReliquaryAffixData>> relicAffixDepot = new Int2ObjectOpenHashMap<>();
private static Map<String, AvatarConfig> playerAbilities = new HashMap<>(); private static Map<String, AvatarConfig> playerAbilities = new HashMap<>();
...@@ -31,8 +32,10 @@ public class GameDepot { ...@@ -31,8 +32,10 @@ public class GameDepot {
if (data.getWeight() <= 0 || data.getPropDepotId() <= 0) { if (data.getWeight() <= 0 || data.getPropDepotId() <= 0) {
continue; continue;
} }
WeightedList<ReliquaryMainPropData> list = relicMainPropDepot.computeIfAbsent(data.getPropDepotId(), k -> new WeightedList<>()); List<ReliquaryMainPropData> list = relicMainPropDepot.computeIfAbsent(data.getPropDepotId(), k -> new ArrayList<>());
list.add(data.getWeight(), data); list.add(data);
WeightedList<ReliquaryMainPropData> weightedList = relicRandomMainPropDepot.computeIfAbsent(data.getPropDepotId(), k -> new WeightedList<>());
weightedList.add(data.getWeight(), data);
} }
for (ReliquaryAffixData data : GameData.getReliquaryAffixDataMap().values()) { for (ReliquaryAffixData data : GameData.getReliquaryAffixDataMap().values()) {
if (data.getWeight() <= 0 || data.getDepotId() <= 0) { if (data.getWeight() <= 0 || data.getDepotId() <= 0) {
...@@ -48,14 +51,18 @@ public class GameDepot { ...@@ -48,14 +51,18 @@ public class GameDepot {
} }
public static ReliquaryMainPropData getRandomRelicMainProp(int depot) { public static ReliquaryMainPropData getRandomRelicMainProp(int depot) {
WeightedList<ReliquaryMainPropData> depotList = relicMainPropDepot.get(depot); WeightedList<ReliquaryMainPropData> depotList = relicRandomMainPropDepot.get(depot);
if (depotList == null) { if (depotList == null) {
return null; return null;
} }
return depotList.next(); return depotList.next();
} }
public static List<ReliquaryAffixData> getRandomRelicAffixList(int depot) { public static List<ReliquaryMainPropData> getRelicMainPropList(int depot) {
return relicMainPropDepot.get(depot);
}
public static List<ReliquaryAffixData> getRelicAffixList(int depot) {
return relicAffixDepot.get(depot); return relicAffixDepot.get(depot);
} }
......
...@@ -10,6 +10,7 @@ import emu.grasscutter.game.inventory.*; ...@@ -10,6 +10,7 @@ import emu.grasscutter.game.inventory.*;
import emu.grasscutter.game.props.FightProperty; import emu.grasscutter.game.props.FightProperty;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntSet; import it.unimi.dsi.fastutil.ints.IntSet;
import lombok.Getter;
@ResourceType(name = {"MaterialExcelConfigData.json", @ResourceType(name = {"MaterialExcelConfigData.json",
"WeaponExcelConfigData.json", "WeaponExcelConfigData.json",
...@@ -19,23 +20,23 @@ import it.unimi.dsi.fastutil.ints.IntSet; ...@@ -19,23 +20,23 @@ import it.unimi.dsi.fastutil.ints.IntSet;
public class ItemData extends GameResource { public class ItemData extends GameResource {
private int id; private int id;
private int stackLimit = 1; @Getter private int stackLimit = 1;
private int maxUseCount; @Getter private int maxUseCount;
private int rankLevel; @Getter private int rankLevel;
private String effectName; @Getter private String effectName;
private int[] satiationParams; @Getter private int[] satiationParams;
private int rank; @Getter private int rank;
private int weight; @Getter private int weight;
private int gadgetId; @Getter private int gadgetId;
private int[] destroyReturnMaterial; @Getter private int[] destroyReturnMaterial;
private int[] destroyReturnMaterialCount; @Getter private int[] destroyReturnMaterialCount;
private List<ItemUseData> itemUse; @Getter private List<ItemUseData> itemUse;
// Food // Food
private String foodQuality; @Getter private String foodQuality;
private String useTarget; @Getter private String useTarget;
private String[] iseParam; private String[] iseParam;
// String enums // String enums
...@@ -45,42 +46,42 @@ public class ItemData extends GameResource { ...@@ -45,42 +46,42 @@ public class ItemData extends GameResource {
private String effectType; private String effectType;
private String destroyRule; private String destroyRule;
// Post load enum forms of above
private transient MaterialType materialEnumType;
private transient ItemType itemEnumType;
private transient EquipType equipEnumType;
// Relic // Relic
private int mainPropDepotId; @Getter private int mainPropDepotId;
private int appendPropDepotId; @Getter private int appendPropDepotId;
private int appendPropNum; @Getter private int appendPropNum;
private int setId; @Getter private int setId;
private int[] addPropLevels; private int[] addPropLevels;
private int baseConvExp; @Getter private int baseConvExp;
private int maxLevel; @Getter private int maxLevel;
// Weapon // Weapon
private int weaponPromoteId; @Getter private int weaponPromoteId;
private int weaponBaseExp; @Getter private int weaponBaseExp;
private int storyId; @Getter private int storyId;
private int avatarPromoteId; @Getter private int avatarPromoteId;
private int awakenMaterial; @Getter private int awakenMaterial;
private int[] awakenCosts; @Getter private int[] awakenCosts;
private int[] skillAffix; @Getter private int[] skillAffix;
private WeaponProperty[] weaponProp; private WeaponProperty[] weaponProp;
// Hash // Hash
private String icon; @Getter private String icon;
private long nameTextMapHash; @Getter private long nameTextMapHash;
// Post load
private transient MaterialType materialEnumType;
private transient ItemType itemEnumType;
private transient EquipType equipEnumType;
private IntSet addPropLevelSet; @Getter private IntSet addPropLevelSet;
// Furniture // Furniture
private int comfort; @Getter private int comfort;
private List<Integer> furnType; @Getter private List<Integer> furnType;
private List<Integer> furnitureGadgetID; @Getter private List<Integer> furnitureGadgetID;
@SerializedName("JFDLJGDFIGL") @SerializedName("JFDLJGDFIGL")
private int roomSceneId; @Getter private int roomSceneId;
@Override @Override
public int getId(){ public int getId(){
...@@ -91,138 +92,18 @@ public class ItemData extends GameResource { ...@@ -91,138 +92,18 @@ public class ItemData extends GameResource {
return this.materialType; return this.materialType;
} }
public int getStackLimit(){
return this.stackLimit;
}
public int getMaxUseCount(){
return this.maxUseCount;
}
public String getUseTarget(){
return this.useTarget;
}
public String[] getUseParam(){ public String[] getUseParam(){
return this.iseParam; 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<ItemUseData> getItemUse() {
return itemUse;
}
public long getNameTextMapHash(){
return this.nameTextMapHash;
}
public String getIcon(){
return this.icon;
}
public String getItemTypeString(){ public String getItemTypeString(){
return this.itemType; 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() { public WeaponProperty[] getWeaponProperties() {
return weaponProp; return weaponProp;
} }
public int getMaxLevel() {
return maxLevel;
}
public int getComfort() {
return comfort;
}
public List<Integer> getFurnType() {
return furnType;
}
public List<Integer> getFurnitureGadgetID() {
return furnitureGadgetID;
}
public int getRoomSceneId() {
return roomSceneId;
}
public ItemType getItemType() { public ItemType getItemType() {
return this.itemEnumType; return this.itemEnumType;
} }
...@@ -274,26 +155,10 @@ public class ItemData extends GameResource { ...@@ -274,26 +155,10 @@ public class ItemData extends GameResource {
} }
public static class WeaponProperty { public static class WeaponProperty {
private FightProperty fightProp; @Getter private FightProperty fightProp;
private String propType; @Getter private String propType;
private float initValue; @Getter private float initValue;
private String type; @Getter 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;
}
public void onLoad() { public void onLoad() {
this.fightProp = FightProperty.getPropByName(propType); this.fightProp = FightProperty.getPropByName(propType);
......
...@@ -242,6 +242,23 @@ public class Avatar { ...@@ -242,6 +242,23 @@ public class Avatar {
this.promoteLevel = promoteLevel; 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<GameItem> getEquips() { public Int2ObjectMap<GameItem> getEquips() {
return equips; return equips;
} }
......
...@@ -33,34 +33,36 @@ import emu.grasscutter.net.proto.SceneReliquaryInfoOuterClass.SceneReliquaryInfo ...@@ -33,34 +33,36 @@ import emu.grasscutter.net.proto.SceneReliquaryInfoOuterClass.SceneReliquaryInfo
import emu.grasscutter.net.proto.SceneWeaponInfoOuterClass.SceneWeaponInfo; import emu.grasscutter.net.proto.SceneWeaponInfoOuterClass.SceneWeaponInfo;
import emu.grasscutter.net.proto.WeaponOuterClass.Weapon; import emu.grasscutter.net.proto.WeaponOuterClass.Weapon;
import emu.grasscutter.utils.WeightedList; import emu.grasscutter.utils.WeightedList;
import lombok.Getter;
import lombok.Setter;
@Entity(value = "items", useDiscriminator = false) @Entity(value = "items", useDiscriminator = false)
public class GameItem { public class GameItem {
@Id private ObjectId id; @Id private ObjectId id;
@Indexed private int ownerId; @Indexed private int ownerId;
private int itemId; @Getter @Setter private int itemId;
private int count; @Getter @Setter private int count;
@Transient private long guid; // Player unique id @Transient @Getter private long guid; // Player unique id
@Transient private ItemData itemData; @Transient @Getter @Setter private ItemData itemData;
// Equips // Equips
private int level; @Getter @Setter private int level;
private int exp; @Getter @Setter private int exp;
private int totalExp; @Getter @Setter private int totalExp;
private int promoteLevel; @Getter @Setter private int promoteLevel;
private boolean locked; @Getter @Setter private boolean locked;
// Weapon // Weapon
private List<Integer> affixes; @Getter private List<Integer> affixes;
private int refinement = 0; @Getter @Setter private int refinement = 0;
// Relic // Relic
private int mainPropId; @Getter @Setter private int mainPropId;
private List<Integer> appendPropIdList; @Getter private List<Integer> appendPropIdList;
private int equipCharacter; @Getter @Setter private int equipCharacter;
@Transient private int weaponEntityId; @Transient @Getter @Setter private int weaponEntityId;
public GameItem() { public GameItem() {
// Morphia only // Morphia only
...@@ -82,44 +84,39 @@ public class GameItem { ...@@ -82,44 +84,39 @@ public class GameItem {
this.itemId = data.getId(); this.itemId = data.getId();
this.itemData = data; this.itemData = data;
if (data.getItemType() == ItemType.ITEM_VIRTUAL) { switch (data.getItemType()) {
case ITEM_VIRTUAL:
this.count = count; this.count = count;
} else { break;
this.count = Math.min(count, data.getStackLimit()); case ITEM_WEAPON:
} this.count = 1;
this.level = Math.max(this.count, 1); // ??????????????????
// Equip data
if (getItemType() == ItemType.ITEM_WEAPON) {
this.level = Math.max(this.count, 1);
this.affixes = new ArrayList<>(2); this.affixes = new ArrayList<>(2);
if (getItemData().getSkillAffix() != null) { if (data.getSkillAffix() != null) {
for (int skillAffix : getItemData().getSkillAffix()) { for (int skillAffix : data.getSkillAffix()) {
if (skillAffix > 0) { if (skillAffix > 0) {
this.affixes.add(skillAffix); this.affixes.add(skillAffix);
} }
} }
} }
} else if (getItemType() == ItemType.ITEM_RELIQUARY) { break;
case ITEM_RELIQUARY:
this.count = 1;
this.level = 1; this.level = 1;
this.appendPropIdList = new ArrayList<>(); this.appendPropIdList = new ArrayList<>();
// Create main property // Create main property
ReliquaryMainPropData mainPropData = GameDepot.getRandomRelicMainProp(getItemData().getMainPropDepotId()); ReliquaryMainPropData mainPropData = GameDepot.getRandomRelicMainProp(data.getMainPropDepotId());
if (mainPropData != null) { if (mainPropData != null) {
this.mainPropId = mainPropData.getId(); this.mainPropId = mainPropData.getId();
} }
// Create extra stats // Create extra stats
if (getItemData().getAppendPropNum() > 0) { this.addAppendProps(data.getAppendPropNum());
for (int i = 0; i < getItemData().getAppendPropNum(); i++) { break;
this.addAppendProp(); default:
} this.count = Math.min(count, data.getStackLimit());
}
} }
} }
public ObjectId getObjectId() {
return id;
}
public int getOwnerId() { public int getOwnerId() {
return ownerId; return ownerId;
} }
...@@ -128,162 +125,88 @@ public class GameItem { ...@@ -128,162 +125,88 @@ public class GameItem {
this.ownerId = player.getUid(); this.ownerId = player.getUid();
this.guid = player.getNextGameGuid(); this.guid = player.getNextGameGuid();
} }
public int getItemId() {
return itemId;
}
public void setItemId(int itemId) { public ObjectId getObjectId() {
this.itemId = itemId; return id;
}
public long getGuid() {
return guid;
} }
public ItemType getItemType() { public ItemType getItemType() {
return this.itemData.getItemType(); return this.itemData.getItemType();
} }
public ItemData getItemData() { public static int getMinPromoteLevel(int level) {
return itemData; if (level > 80) {
} return 6;
} else if (level > 70) {
public void setItemData(ItemData materialData) { return 5;
this.itemData = materialData; } else if (level > 60) {
} return 4;
} else if (level > 50) {
public int getCount() { return 3;
return count; } else if (level > 40) {
} return 2;
} else if (level > 20) {
public void setCount(int count) { return 1;
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;
} }
return 0;
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 int getEquipSlot() { public int getEquipSlot() {
return this.getItemData().getEquipType().getValue(); return this.getItemData().getEquipType().getValue();
} }
public int getEquipCharacter() {
return equipCharacter;
}
public void setEquipCharacter(int equipCharacter) {
this.equipCharacter = equipCharacter;
}
public boolean isEquipped() { public boolean isEquipped() {
return this.getEquipCharacter() > 0; return this.getEquipCharacter() > 0;
} }
public boolean isLocked() {
return locked;
}
public void setLocked(boolean locked) {
this.locked = locked;
}
public boolean isDestroyable() { public boolean isDestroyable() {
return !this.isLocked() && !this.isEquipped(); return !this.isLocked() && !this.isEquipped();
} }
public int getWeaponEntityId() { public void addAppendProp() {
return weaponEntityId; if (this.appendPropIdList == null) {
} this.appendPropIdList = new ArrayList<>();
public void setWeaponEntityId(int weaponEntityId) {
this.weaponEntityId = weaponEntityId;
}
public List<Integer> getAffixes() {
return affixes;
}
public int getRefinement() {
return refinement;
} }
public void setRefinement(int refinement) { if (this.appendPropIdList.size() < 4) {
this.refinement = refinement; this.addNewAppendProp();
} else {
this.upgradeRandomAppendProp();
} }
public int getMainPropId() {
return mainPropId;
} }
public void setMainPropId(int mainPropId) { public void addAppendProps(int quantity) {
this.mainPropId = mainPropId; int num = Math.max(quantity, 0);
for (int i = 0; i < num; i++) {
this.addAppendProp();
} }
public List<Integer> getAppendPropIdList() {
return appendPropIdList;
} }
public void addAppendProp() { private Set<FightProperty> getAppendFightProperties() {
if (this.getAppendPropIdList() == null) { Set<FightProperty> props = new HashSet<>();
this.appendPropIdList = new ArrayList<>(); // 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());
} }
if (this.getAppendPropIdList().size() < 4) {
addNewAppendProp();
} else {
upgradeRandomAppendProp();
} }
return props;
} }
private void addNewAppendProp() { private void addNewAppendProp() {
List<ReliquaryAffixData> affixList = GameDepot.getRandomRelicAffixList(getItemData().getAppendPropDepotId()); List<ReliquaryAffixData> affixList = GameDepot.getRelicAffixList(this.itemData.getAppendPropDepotId());
if (affixList == null) { if (affixList == null) {
return; return;
} }
// Build blacklist - Dont add same stat as main/sub stat // Build blacklist - Dont add same stat as main/sub stat
Set<FightProperty> blacklist = new HashSet<>(); Set<FightProperty> blacklist = this.getAppendFightProperties();
ReliquaryMainPropData mainPropData = GameData.getReliquaryMainPropDataMap().get(this.getMainPropId()); ReliquaryMainPropData mainPropData = GameData.getReliquaryMainPropDataMap().get(this.mainPropId);
if (mainPropData != null) { if (mainPropData != null) {
blacklist.add(mainPropData.getFightProp()); 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 // Build random list
WeightedList<ReliquaryAffixData> randomList = new WeightedList<>(); WeightedList<ReliquaryAffixData> randomList = new WeightedList<>();
...@@ -299,25 +222,18 @@ public class GameItem { ...@@ -299,25 +222,18 @@ public class GameItem {
// Add random stat // Add random stat
ReliquaryAffixData affixData = randomList.next(); ReliquaryAffixData affixData = randomList.next();
this.getAppendPropIdList().add(affixData.getId()); this.appendPropIdList.add(affixData.getId());
} }
private void upgradeRandomAppendProp() { private void upgradeRandomAppendProp() {
List<ReliquaryAffixData> affixList = GameDepot.getRandomRelicAffixList(getItemData().getAppendPropDepotId()); List<ReliquaryAffixData> affixList = GameDepot.getRelicAffixList(this.itemData.getAppendPropDepotId());
if (affixList == null) { if (affixList == null) {
return; return;
} }
// Build whitelist // Build whitelist
Set<FightProperty> whitelist = new HashSet<>(); Set<FightProperty> whitelist = this.getAppendFightProperties();
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());
}
}
// Build random list // Build random list
WeightedList<ReliquaryAffixData> randomList = new WeightedList<>(); WeightedList<ReliquaryAffixData> randomList = new WeightedList<>();
...@@ -329,7 +245,7 @@ public class GameItem { ...@@ -329,7 +245,7 @@ public class GameItem {
// Add random stat // Add random stat
ReliquaryAffixData affixData = randomList.next(); ReliquaryAffixData affixData = randomList.next();
this.getAppendPropIdList().add(affixData.getId()); this.appendPropIdList.add(affixData.getId());
} }
@PostLoad @PostLoad
......
...@@ -147,7 +147,7 @@ public class InventoryManager { ...@@ -147,7 +147,7 @@ public class InventoryManager {
int totalExp = relic.getTotalExp(); int totalExp = relic.getTotalExp();
int reqExp = GameData.getRelicExpRequired(relic.getItemData().getRankLevel(), level); int reqExp = GameData.getRelicExpRequired(relic.getItemData().getRankLevel(), level);
int upgrades = 0; int upgrades = 0;
List<Integer> oldAppendPropIdList = relic.getAppendPropIdList(); List<Integer> oldAppendPropIdList = new ArrayList<>(relic.getAppendPropIdList());
while (expGain > 0 && reqExp > 0 && level < relic.getItemData().getMaxLevel()) { while (expGain > 0 && reqExp > 0 && level < relic.getItemData().getMaxLevel()) {
// Do calculations // Do calculations
...@@ -169,13 +169,7 @@ public class InventoryManager { ...@@ -169,13 +169,7 @@ public class InventoryManager {
} }
} }
if (upgrades > 0) { relic.addAppendProps(upgrades);
oldAppendPropIdList = new ArrayList<>(relic.getAppendPropIdList());
while (upgrades > 0) {
relic.addAppendProp();
upgrades -= 1;
}
}
// Save // Save
relic.setLevel(level); relic.setLevel(level);
......
package emu.grasscutter.game.props; package emu.grasscutter.game.props;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import static java.util.Map.entry;
import java.util.Arrays;
import java.util.stream.Stream; import java.util.stream.Stream;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
...@@ -133,4 +139,65 @@ public enum FightProperty { ...@@ -133,4 +139,65 @@ public enum FightProperty {
public static FightProperty getPropByName(String name) { public static FightProperty getPropByName(String name) {
return stringMap.getOrDefault(name, FIGHT_PROP_NONE); return stringMap.getOrDefault(name, FIGHT_PROP_NONE);
} }
public static FightProperty getPropByShortName(String name) {
return shortNameMap.getOrDefault(name, FIGHT_PROP_NONE);
}
public static Set<String> 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<String, FightProperty> 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<FightProperty> 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);
}
} }
...@@ -30,21 +30,7 @@ public class PacketGetMailItemRsp extends BasePacket { ...@@ -30,21 +30,7 @@ public class PacketGetMailItemRsp extends BasePacket {
if (!message.isAttachmentGot) {//No duplicated item if (!message.isAttachmentGot) {//No duplicated item
for (Mail.MailItem mailItem : message.itemList) { for (Mail.MailItem mailItem : message.itemList) {
EquipParamOuterClass.EquipParam.Builder item = EquipParamOuterClass.EquipParam.newBuilder(); EquipParamOuterClass.EquipParam.Builder item = EquipParamOuterClass.EquipParam.newBuilder();
int promoteLevel = 0; int promoteLevel = GameItem.getMinPromoteLevel(mailItem.itemLevel);
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;
}
item.setItemId(mailItem.itemId); item.setItemId(mailItem.itemId);
item.setItemNum(mailItem.itemCount); item.setItemNum(mailItem.itemCount);
......
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<Range> rangeEntries;
private final Set<Integer> 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
...@@ -132,35 +132,16 @@ ...@@ -132,35 +132,16 @@
"in_dungeon_error": "You are already in that dungeon.", "in_dungeon_error": "You are already in that dungeon.",
"description": "Enter a 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] <artifactID> <mainPropID> [<appendPropID>[,<times>]]... [level]",
"id_error": "Invalid artifact ID.",
"success": "Given %s to %s.",
"description": "Gives the player a specified artifact"
},
"giveChar": {
"usage": "Usage: givechar <player> <avatarID> [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": { "give": {
"usage": "Usage: give <player> <itemID|itemName> [amount] [level] [refinement]", "usage": "Usage: give <itemID|avatarID|\"all\"|\"weapons\"|\"mats\"|\"avatars\"> [x<amount>] [lv<level>] [r<refinement>]",
"refinement_only_applicable_weapons": "Refinement is only applicable to weapons.", "usage_relic": "Usage: give <artifactID> [mainPropID] [<appendPropID>[,<times>]]... [lv<level 0-20>]",
"refinement_must_between_1_and_5": "Refinement must be between 1 and 5.", "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": "Given %s of %s to %s.",
"given_with_level_and_refinement": "Given %s with level %s, refinement %s %s times 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.", "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": { "godmode": {
"success": "Godmode is now %s for %s.", "success": "Godmode is now %s for %s.",
...@@ -202,10 +183,6 @@ ...@@ -202,10 +183,6 @@
"success": "There are %s player(s) online:", "success": "There are %s player(s) online:",
"description": "List online players" "description": "List online players"
}, },
"nostamina": {
"success": "NoStamina is now %s for %s.",
"description": "Keep your endurance to the maximum."
},
"permission": { "permission": {
"usage": "Usage: permission <add|remove> <username> <permission>", "usage": "Usage: permission <add|remove> <username> <permission>",
"add": "Permission added.", "add": "Permission added.",
......
...@@ -146,21 +146,14 @@ ...@@ -146,21 +146,14 @@
"success": "%s a été donné à %s.", "success": "%s a été donné à %s.",
"description": "Donne au joueur l'artéfact spécifié." "description": "Donne au joueur l'artéfact spécifié."
}, },
"giveChar": {
"usage": "Usage: givechar <joueur> <avatarID> [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": { "give": {
"usage": "Usage: give <joueur> <itemID|itemName> [quantité] [niveau] [raffinement]", "usage": "Usage: give <joueur> <itemID|avatarID> [quantité] [niveau] [raffinement]",
"refinement_only_applicable_weapons": "Le raffinement est uniquement applicable aux armes.", "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.", "refinement_must_between_1_and_5": "Le raffinement doit être compris entre 1 et 5.",
"given": "Given %s of %s to %s.", "given": "Given %s of %s to %s.",
"given_with_level_and_refinement": "%s avec le niveau %s, raffinement %s %s fois à %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_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é" "description": "Donne un objet au joueur spécifié"
}, },
"godmode": { "godmode": {
......
...@@ -134,20 +134,14 @@ ...@@ -134,20 +134,14 @@
"id_error": "Błędne ID artefaktu.", "id_error": "Błędne ID artefaktu.",
"success": "Dano %s dla %s." "success": "Dano %s dla %s."
}, },
"giveChar": {
"usage": "Użycie: givechar <gracz> <avatarId> [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": { "give": {
"usage": "Użycie: give <gracz> <id przedmiotu | nazwa przedmiotu> [ilość] [poziom] [refinement]", "usage": "Użycie: give <gracz> <id przedmiotu | avatarID> [ilość] [poziom] [refinement]",
"refinement_only_applicable_weapons": "Ulepszenie można zastosować tylko dla broni.", "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.", "refinement_must_between_1_and_5": "Ulepszenie musi być pomiędzy 1, a 5.",
"given": "Dano %s %s dla %s.", "given": "Dano %s %s dla %s.",
"given_with_level_and_refinement": "Dano %s z poziomem %s, ulepszeniem %s %s razy 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": { "godmode": {
"success": "Godmode jest teraz %s dla %s." "success": "Godmode jest teraz %s dla %s."
......
...@@ -145,21 +145,14 @@ ...@@ -145,21 +145,14 @@
"success": "已将 %s 给予 %s。", "success": "已将 %s 给予 %s。",
"description": "给予指定圣遗物" "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": { "give": {
"usage": "用法:give <玩家> <物品ID|物品名> [数量] [等级] [精炼等级]", "usage": "用法:give <玩家> <物品ID|角色ID> [数量] [等级] [精炼等级]",
"refinement_only_applicable_weapons": "只有武器可以设置精炼等级。", "refinement_only_applicable_weapons": "只有武器可以设置精炼等级。",
"refinement_must_between_1_and_5": "精炼等级必须在 1-5 之间。", "refinement_must_between_1_and_5": "精炼等级必须在 1-5 之间。",
"given": "已将 %s 个 %s 给予 %s。", "given": "已将 %s 个 %s 给予 %s。",
"given_with_level_and_refinement": "已将 %s [等级 %s, 精炼 %s] %s 个给予 %s。", "given_with_level_and_refinement": "已将 %s [等级 %s, 精炼 %s] %s 个给予 %s。",
"given_level": "已将 %s [等级 %s] %s 个给予 %s。", "given_level": "已将 %s [等级 %s] %s 个给予 %s。",
"given_avatar": "已将角色 %s [等级 %s] 给与 %s。",
"description": "给予指定物品" "description": "给予指定物品"
}, },
"godmode": { "godmode": {
......
...@@ -144,21 +144,14 @@ ...@@ -144,21 +144,14 @@
"success": "已把 %s 給予 %s。", "success": "已把 %s 給予 %s。",
"description": "給予指定聖遺物。" "description": "給予指定聖遺物。"
}, },
"giveChar": {
"usage": "用法:givechar <player> <avatarId> [level]",
"given": "已將 %s 等級 %s 給予 %s。",
"invalid_avatar_id": "無效的角色ID。",
"invalid_avatar_level": "無效的角色等級。.",
"invalid_avatar_or_player_id": "無效的角色ID/玩家ID。",
"description": "給予指定角色。"
},
"give": { "give": {
"usage": "用法:give <player> <itemId|itemName> [amount] [level] [refinement]", "usage": "用法:give <player> <itemId|avatarId> [amount] [level] [refinement]",
"refinement_only_applicable_weapons": "精煉度只能施加在武器上面。", "refinement_only_applicable_weapons": "精煉度只能施加在武器上面。",
"refinement_must_between_1_and_5": "精煉度必需在 1 到 5 之間。", "refinement_must_between_1_and_5": "精煉度必需在 1 到 5 之間。",
"given": "已經將 %s 個 %s 給予 %s。", "given": "已經將 %s 個 %s 給予 %s。",
"given_with_level_and_refinement": "已將 %s [等級%s, 精煉%s] %s個給予 %s", "given_with_level_and_refinement": "已將 %s [等級%s, 精煉%s] %s個給予 %s",
"given_level": "已將 %s 等級 %s %s 個給予 %s", "given_level": "已將 %s 等級 %s %s 個給予 %s",
"given_avatar": "已將 %s 等級 %s 給予 %s。",
"description": "給予指定物品。" "description": "給予指定物品。"
}, },
"godmode": { "godmode": {
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment