Remove GiveAll, GiveArt, GiveChar commands

This commit is contained in:
AnimeGitB 2022-06-25 01:36:19 +09:30 committed by Melledy
parent 6fd1ce813c
commit baafb4104c
18 changed files with 766 additions and 1186 deletions

View File

@ -1,184 +0,0 @@
package emu.grasscutter.command.commands;
import emu.grasscutter.GameConstants;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.AvatarData;
import emu.grasscutter.data.excels.ItemData;
import emu.grasscutter.game.avatar.Avatar;
import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.inventory.ItemType;
import emu.grasscutter.game.player.Player;
import java.util.*;
import static emu.grasscutter.utils.Language.translate;
@Command(label = "giveall", usage = "giveall [amount]", aliases = {"givea"}, permission = "player.giveall", permissionTargeted = "player.giveall.others", threading = true, description = "commands.giveAll.description")
public final class GiveAllCommand implements CommandHandler {
@Override
public void execute(Player sender, Player targetPlayer, List<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);
}

View File

@ -1,208 +0,0 @@
package emu.grasscutter.command.commands;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.ItemData;
import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.inventory.ItemType;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.game.inventory.EquipType;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import static java.util.Map.entry;
import static emu.grasscutter.utils.Language.translate;
@Command(label = "giveart", usage = "giveart <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())));
}
}

View File

@ -1,86 +0,0 @@
package emu.grasscutter.command.commands;
import emu.grasscutter.GameConstants;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.AvatarData;
import emu.grasscutter.game.avatar.Avatar;
import emu.grasscutter.game.player.Player;
import java.util.List;
import static emu.grasscutter.utils.Language.translate;
@Command(label = "givechar", usage = "givechar <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())));
}
}

View File

@ -1,29 +1,37 @@
package emu.grasscutter.command.commands;
import emu.grasscutter.GameConstants;
import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.GameDepot;
import emu.grasscutter.data.excels.AvatarData;
import emu.grasscutter.data.excels.ItemData;
import emu.grasscutter.data.excels.ReliquaryAffixData;
import emu.grasscutter.data.excels.ReliquaryMainPropData;
import emu.grasscutter.game.avatar.Avatar;
import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.inventory.ItemType;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.utils.SparseSet;
import java.util.LinkedList;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static emu.grasscutter.utils.Language.translate;
@Command(label = "give", usage = "give <itemId|itemName> [amount] [level]", aliases = {
@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 = {
"g", "item", "giveitem"}, permission = "player.give", permissionTargeted = "player.give.others", description = "commands.give.description")
public final class GiveCommand implements CommandHandler {
Pattern lvlRegex = Pattern.compile("l(?:vl?)?(\\d+)"); // Java is a joke of a proglang that doesn't have raw string literals
Pattern refineRegex = Pattern.compile("r(\\d+)");
Pattern amountRegex = Pattern.compile("((?<=x)\\d+|\\d+(?=x)(?!x\\d))");
private static Pattern lvlRegex = Pattern.compile("l(?:vl?)?(\\d+)"); // Java doesn't have raw string literals :(
private static Pattern refineRegex = Pattern.compile("r(\\d+)");
private static Pattern constellationRegex = Pattern.compile("c(\\d+)");
private static Pattern amountRegex = Pattern.compile("((?<=x)\\d+|\\d+(?=x)(?!x\\d))");
private int matchIntOrNeg(Pattern pattern, String arg) {
private static int matchIntOrNeg(Pattern pattern, String arg) {
Matcher match = pattern.matcher(arg);
if (match.find()) {
return Integer.parseInt(match.group(1)); // This should be exception-safe as only \d+ can be passed to it (i.e. non-empty string of pure digits)
@ -31,27 +39,50 @@ public final class GiveCommand implements CommandHandler {
return -1;
}
@Override
public void execute(Player sender, Player targetPlayer, List<String> args) {
int item;
int lvl = 1;
int amount = 1;
int refinement = 0;
private static enum GiveAllType {
NONE,
ALL,
WEAPONS,
MATS,
AVATARS
}
for (int i = args.size()-1; i>=0; i--) { // Reverse iteration as we are deleting elements
private static class GiveItemParameters {
public int id;
public int lvl = 0;
public int amount = 1;
public int refinement = 1;
public int constellation = -1;
public int mainPropId = -1;
public List<Integer> appendPropIdList;
public ItemData data;
public AvatarData avatarData;
public GiveAllType giveAllType = GiveAllType.NONE;
};
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();
boolean deleteArg = false;
int argNum;
// Note that a single argument can actually match all of these, e.g. "lv90r5x100"
if ((argNum = matchIntOrNeg(lvlRegex, arg)) != -1) {
lvl = argNum;
param.lvl = argNum;
deleteArg = true;
}
if ((argNum = matchIntOrNeg(refineRegex, arg)) != -1) {
refinement = argNum;
param.refinement = argNum;
deleteArg = true;
}
if ((argNum = matchIntOrNeg(constellationRegex, arg)) != -1) {
param.constellation = argNum;
deleteArg = true;
}
if ((argNum = matchIntOrNeg(amountRegex, arg)) != -1) {
amount = argNum;
param.amount = argNum;
deleteArg = true;
}
if (deleteArg) {
@ -59,112 +90,387 @@ public final class GiveCommand implements CommandHandler {
}
}
switch (args.size()) {
case 4: // <itemId|itemName> [amount] [level] [refinement]
try {
refinement = Integer.parseInt(args.get(3));
} catch (NumberFormatException ignored) {
CommandHandler.sendMessage(sender, translate(sender, "commands.generic.invalid.itemRefinement"));
return;
} // Fallthrough
case 3: // <itemId|itemName> [amount] [level]
try {
lvl = Integer.parseInt(args.get(2));
} catch (NumberFormatException ignored) {
CommandHandler.sendMessage(sender, translate(sender, "commands.generic.invalid.itemLevel"));
return;
} // Fallthrough
case 2: // <itemId|itemName> [amount]
try {
amount = Integer.parseInt(args.get(1));
} catch (NumberFormatException ignored) {
CommandHandler.sendMessage(sender, translate(sender, "commands.generic.invalid.amount"));
return;
} // Fallthrough
case 1: // <itemId|itemName>
try {
item = Integer.parseInt(args.get(0));
} catch (NumberFormatException ignored) {
// TODO: Parse from item name using GM Handbook.
CommandHandler.sendMessage(sender, translate(sender, "commands.generic.invalid.itemId"));
return;
}
// At this point, first remaining argument MUST be itemId/avatarId
if (args.size() < 1) {
CommandHandler.sendTranslatedMessage(sender, "commands.give.usage"); // Reachable if someone does `/give lv90` or similar
throw new IllegalArgumentException();
}
String id = args.remove(0);
boolean isRelic = false;
switch (id) {
case "all":
param.giveAllType = GiveAllType.ALL;
break;
default: // *No args*
CommandHandler.sendMessage(sender, translate(sender, "commands.give.usage"));
return;
case "weapons":
param.giveAllType = GiveAllType.WEAPONS;
break;
case "mats":
param.giveAllType = GiveAllType.MATS;
break;
case "avatars":
param.giveAllType = GiveAllType.AVATARS;
break;
default:
try {
param.id = Integer.parseInt(id);
param.data = GameData.getItemDataMap().get(param.id);
if ((param.id > 10_000_000) && (param.id < 12_000_000))
param.avatarData = GameData.getAvatarDataMap().get(param.id);
else if ((param.id > 1000) && (param.id < 1100))
param.avatarData = GameData.getAvatarDataMap().get(param.id - 1000 + 10_000_000);
isRelic = ((param.data != null) && (param.data.getItemType() == ItemType.ITEM_RELIQUARY));
} catch (NumberFormatException e) {
// TODO: Parse from item name using GM Handbook.
CommandHandler.sendTranslatedMessage(sender, "commands.generic.invalid.itemId");
throw e;
}
}
ItemData itemData = GameData.getItemDataMap().get(item);
if (itemData == null) {
CommandHandler.sendMessage(sender, translate(sender, "commands.generic.invalid.itemId"));
if (param.amount < 1) param.amount = 1;
if (param.refinement < 1) param.refinement = 1;
if (param.refinement > 5) param.refinement = 5;
if (isRelic) {
// Input 0-20 to match game, instead of 1-21 which is the real level
if (param.lvl < 0) param.lvl = 0;
if (param.lvl > 20) param.lvl = 20;
param.lvl += 1;
if (illegalRelicIds.contains(param.id))
CommandHandler.sendTranslatedMessage(sender, "commands.give.illegal_relic");
} else {
// Suitable for Avatars and Weapons
if (param.lvl < 1) param.lvl = 1;
if (param.lvl > 90) param.lvl = 90;
}
if (isRelic && !args.isEmpty()) {
try {
parseRelicArgs(param, args);
} catch (IllegalArgumentException e) {
CommandHandler.sendTranslatedMessage(sender, "commands.execution.argument_error");
CommandHandler.sendTranslatedMessage(sender, "commands.give.usage_relic");
throw e;
}
}
return param;
}
@Override
public void execute(Player sender, Player targetPlayer, List<String> args) {
if (args.size() < 1) { // *No args*
CommandHandler.sendTranslatedMessage(sender, "commands.give.usage");
return;
}
if (refinement != 0) {
if (itemData.getItemType() == ItemType.ITEM_WEAPON) {
if (refinement < 1 || refinement > 5) {
CommandHandler.sendMessage(sender, translate(sender, "commands.give.refinement_must_between_1_and_5"));
try {
GiveItemParameters param = parseArgs(sender, args);
switch (param.giveAllType) {
case ALL:
giveAll(targetPlayer, param);
CommandHandler.sendTranslatedMessage(sender, "commands.give.giveall_success");
return;
}
} else {
CommandHandler.sendMessage(sender, translate(sender, "commands.give.refinement_only_applicable_weapons"));
case WEAPONS:
giveAllWeapons(targetPlayer, param);
CommandHandler.sendTranslatedMessage(sender, "commands.give.giveall_success");
return;
case MATS:
giveAllMats(targetPlayer, param);
CommandHandler.sendTranslatedMessage(sender, "commands.give.giveall_success");
return;
case AVATARS:
giveAllAvatars(targetPlayer, param);
CommandHandler.sendTranslatedMessage(sender, "commands.give.giveall_success");
return;
case NONE:
break;
}
// Check if this is an avatar
if (param.avatarData != null) {
Avatar avatar = makeAvatar(param);
targetPlayer.addAvatar(avatar);
CommandHandler.sendTranslatedMessage(sender, "commands.give.given_avatar", Integer.toString(param.id), Integer.toString(param.lvl), Integer.toString(targetPlayer.getUid()));
return;
}
// If it's not an avatar, it needs to be a valid item
if (param.data == null) {
CommandHandler.sendTranslatedMessage(sender, "commands.generic.invalid.itemId");
return;
}
switch (param.data.getItemType()) {
case ITEM_WEAPON:
targetPlayer.getInventory().addItems(makeUnstackableItems(param), ActionReason.SubfieldDrop);
CommandHandler.sendTranslatedMessage(sender, "commands.give.given_with_level_and_refinement", Integer.toString(param.id), Integer.toString(param.lvl), Integer.toString(param.refinement), Integer.toString(param.amount), Integer.toString(targetPlayer.getUid()));
return;
case ITEM_RELIQUARY:
targetPlayer.getInventory().addItems(makeArtifacts(param), ActionReason.SubfieldDrop);
CommandHandler.sendTranslatedMessage(sender, "commands.give.given_level", Integer.toString(param.id), Integer.toString(param.lvl), Integer.toString(param.amount), Integer.toString(targetPlayer.getUid()));
//CommandHandler.sendTranslatedMessage(sender, "commands.giveArtifact.success", Integer.toString(param.id), Integer.toString(targetPlayer.getUid()));
return;
default:
targetPlayer.getInventory().addItem(new GameItem(param.data, param.amount), ActionReason.SubfieldDrop);
CommandHandler.sendTranslatedMessage(sender, "commands.give.given", Integer.toString(param.amount), Integer.toString(param.id), Integer.toString(targetPlayer.getUid()));
return;
}
} catch (IllegalArgumentException ignored) {
return;
}
}
this.item(targetPlayer, itemData, amount, lvl, refinement);
private static Avatar makeAvatar(GiveItemParameters param) {
return makeAvatar(param.avatarData, param.lvl, Avatar.getMinPromoteLevel(param.lvl), 0);
}
if (!itemData.isEquip()) {
CommandHandler.sendMessage(sender, translate(sender, "commands.give.given", Integer.toString(amount), Integer.toString(item), Integer.toString(targetPlayer.getUid())));
} else if (itemData.getItemType() == ItemType.ITEM_WEAPON) {
CommandHandler.sendMessage(sender, translate(sender, "commands.give.given_with_level_and_refinement", Integer.toString(item), Integer.toString(lvl), Integer.toString(refinement), Integer.toString(amount), Integer.toString(targetPlayer.getUid())));
} else {
CommandHandler.sendMessage(sender, translate(sender, "commands.give.given_level", Integer.toString(item), Integer.toString(lvl), Integer.toString(amount), Integer.toString(targetPlayer.getUid())));
private static Avatar makeAvatar(AvatarData avatarData, int level, int promoteLevel, int constellation) {
// Calculate ascension level.
Avatar avatar = new Avatar(avatarData);
avatar.setLevel(level);
avatar.setPromoteLevel(promoteLevel);
// Add constellations.
int talentBase = switch (avatar.getAvatarId()) {
case 10000005 -> 70;
case 10000006 -> 40;
default -> (avatar.getAvatarId() - 10000000) * 10;
};
for (int i = 1; i <= constellation; i++) {
avatar.getTalentIdList().add(talentBase + i);
}
// Main character needs skill depot manually added.
if (avatar.getAvatarId() == GameConstants.MAIN_CHARACTER_MALE) {
avatar.setSkillDepotData(GameData.getAvatarSkillDepotDataMap().get(504));
}
else if(avatar.getAvatarId() == GameConstants.MAIN_CHARACTER_FEMALE) {
avatar.setSkillDepotData(GameData.getAvatarSkillDepotDataMap().get(704));
}
avatar.recalcStats();
return avatar;
}
private static void giveAllAvatars(Player player, GiveItemParameters param) {
int promoteLevel = Avatar.getMinPromoteLevel(param.lvl);
if (param.constellation < 0) {
param.constellation = 6;
}
for (AvatarData avatarData : GameData.getAvatarDataMap().values()) {
// Exclude test avatars
int id = avatarData.getId();
if (id < 10000002 || id >= 11000000) continue;
// Don't try to add each avatar to the current team
player.addAvatar(makeAvatar(avatarData, param.lvl, promoteLevel, param.constellation), false);
}
}
private void item(Player player, ItemData itemData, int amount, int lvl, int refinement) {
if (itemData.isEquip()) {
List<GameItem> items = new LinkedList<>();
for (int i = 0; i < amount; i++) {
GameItem item = new GameItem(itemData);
if (item.isEquipped()) {
// check item max level
if (item.getItemType() == ItemType.ITEM_WEAPON) {
if (lvl > 90) lvl = 90;
} else {
if (lvl > 21) lvl = 21;
}
}
item.setCount(amount);
item.setLevel(lvl);
if (lvl > 80) {
item.setPromoteLevel(6);
} else if (lvl > 70) {
item.setPromoteLevel(5);
} else if (lvl > 60) {
item.setPromoteLevel(4);
} else if (lvl > 50) {
item.setPromoteLevel(3);
} else if (lvl > 40) {
item.setPromoteLevel(2);
} else if (lvl > 20) {
item.setPromoteLevel(1);
}
if (item.getItemType() == ItemType.ITEM_WEAPON) {
if (refinement > 0) {
item.setRefinement(refinement - 1);
} else {
item.setRefinement(0);
}
}
items.add(item);
private static List<GameItem> makeUnstackableItems(GiveItemParameters param) {
int promoteLevel = GameItem.getMinPromoteLevel(param.lvl);
int totalExp = 0;
if (param.data.getItemType() == ItemType.ITEM_WEAPON) {
int rankLevel = param.data.getRankLevel();
for (int i = 1; i < param.lvl; i++)
totalExp += GameData.getWeaponExpRequired(rankLevel, i);
}
List<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) {
item.setPromoteLevel(promoteLevel);
item.setTotalExp(totalExp);
item.setRefinement(param.refinement - 1); // Actual refinement data is 0..4 not 1..5
}
player.getInventory().addItems(items, ActionReason.SubfieldDrop);
} else {
GameItem item = new GameItem(itemData);
item.setCount(amount);
player.getInventory().addItem(item, ActionReason.SubfieldDrop);
items.add(item);
}
return items;
}
private static List<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);
}
return items;
}
private static int getArtifactMainProp(ItemData itemData, FightProperty prop) throws IllegalArgumentException {
if (prop != FightProperty.FIGHT_PROP_NONE)
for (ReliquaryMainPropData data : GameDepot.getRelicMainPropList(itemData.getMainPropDepotId()))
if (data.getWeight() > 0 && data.getFightProp() == prop)
return data.getId();
throw new IllegalArgumentException();
}
private static List<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
""");
}

View File

@ -4,15 +4,12 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.entity.EntityAvatar;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify;
import emu.grasscutter.utils.Language;
import static emu.grasscutter.utils.Language.translate;
@Command(label = "setstats", usage = "setstats|stats <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 {
static class Stat {
String name;
FightProperty prop;
boolean percent;
public Stat(String name, FightProperty prop, boolean percent) {
public Stat(FightProperty prop) {
this.name = prop.toString();
this.prop = prop;
}
public Stat(String name, FightProperty prop) {
this.name = name;
this.prop = prop;
this.percent = percent;
}
}
Map<String, Stat> stats = new HashMap<>();
Map<String, Stat> stats;
public SetStatsCommand() {
// Default stats
stats.put("maxhp", new Stat(FightProperty.FIGHT_PROP_MAX_HP.toString(), FightProperty.FIGHT_PROP_MAX_HP, false));
stats.put("hp", new Stat(FightProperty.FIGHT_PROP_CUR_HP.toString(), FightProperty.FIGHT_PROP_CUR_HP, false));
stats.put("atk", new Stat(FightProperty.FIGHT_PROP_CUR_ATTACK.toString(), FightProperty.FIGHT_PROP_CUR_ATTACK, false));
stats.put("atkb", new Stat(FightProperty.FIGHT_PROP_BASE_ATTACK.toString(), FightProperty.FIGHT_PROP_BASE_ATTACK, false)); // This doesn't seem to get used to recalculate ATK, so it's only useful for stuff like Bennett's buff.
stats.put("def", new Stat(FightProperty.FIGHT_PROP_DEFENSE.toString(), FightProperty.FIGHT_PROP_DEFENSE, false));
stats.put("em", new Stat(FightProperty.FIGHT_PROP_ELEMENT_MASTERY.toString(), FightProperty.FIGHT_PROP_ELEMENT_MASTERY, false));
stats.put("er", new Stat(FightProperty.FIGHT_PROP_CHARGE_EFFICIENCY.toString(), FightProperty.FIGHT_PROP_CHARGE_EFFICIENCY, true));
stats.put("crate", new Stat(FightProperty.FIGHT_PROP_CRITICAL.toString(), FightProperty.FIGHT_PROP_CRITICAL, true));
stats.put("cdmg", new Stat(FightProperty.FIGHT_PROP_CRITICAL_HURT.toString(), FightProperty.FIGHT_PROP_CRITICAL_HURT, true));
stats.put("dmg", new Stat(FightProperty.FIGHT_PROP_ADD_HURT.toString(), FightProperty.FIGHT_PROP_ADD_HURT, true)); // This seems to get reset after attacks
stats.put("eanemo", new Stat(FightProperty.FIGHT_PROP_WIND_ADD_HURT.toString(), FightProperty.FIGHT_PROP_WIND_ADD_HURT, true));
stats.put("ecryo", new Stat(FightProperty.FIGHT_PROP_ICE_ADD_HURT.toString(), FightProperty.FIGHT_PROP_ICE_ADD_HURT, true));
stats.put("edendro", new Stat(FightProperty.FIGHT_PROP_GRASS_ADD_HURT.toString(), FightProperty.FIGHT_PROP_GRASS_ADD_HURT, true));
stats.put("eelectro", new Stat(FightProperty.FIGHT_PROP_ELEC_ADD_HURT.toString(), FightProperty.FIGHT_PROP_ELEC_ADD_HURT, true));
stats.put("egeo", new Stat(FightProperty.FIGHT_PROP_ROCK_ADD_HURT.toString(), FightProperty.FIGHT_PROP_ROCK_ADD_HURT, true));
stats.put("ehydro", new Stat(FightProperty.FIGHT_PROP_WATER_ADD_HURT.toString(), FightProperty.FIGHT_PROP_WATER_ADD_HURT, true));
stats.put("epyro", new Stat(FightProperty.FIGHT_PROP_FIRE_ADD_HURT.toString(), FightProperty.FIGHT_PROP_FIRE_ADD_HURT, true));
stats.put("ephys", new Stat(FightProperty.FIGHT_PROP_PHYSICAL_ADD_HURT.toString(), FightProperty.FIGHT_PROP_PHYSICAL_ADD_HURT, true));
stats.put("resall", new Stat(FightProperty.FIGHT_PROP_SUB_HURT.toString(), FightProperty.FIGHT_PROP_SUB_HURT, true)); // This seems to get reset after attacks
stats.put("resanemo", new Stat(FightProperty.FIGHT_PROP_WIND_SUB_HURT.toString(), FightProperty.FIGHT_PROP_WIND_SUB_HURT, true));
stats.put("rescryo", new Stat(FightProperty.FIGHT_PROP_ICE_SUB_HURT.toString(), FightProperty.FIGHT_PROP_ICE_SUB_HURT, true));
stats.put("resdendro", new Stat(FightProperty.FIGHT_PROP_GRASS_SUB_HURT.toString(), FightProperty.FIGHT_PROP_GRASS_SUB_HURT, true));
stats.put("reselectro", new Stat(FightProperty.FIGHT_PROP_ELEC_SUB_HURT.toString(), FightProperty.FIGHT_PROP_ELEC_SUB_HURT, true));
stats.put("resgeo", new Stat(FightProperty.FIGHT_PROP_ROCK_SUB_HURT.toString(), FightProperty.FIGHT_PROP_ROCK_SUB_HURT, true));
stats.put("reshydro", new Stat(FightProperty.FIGHT_PROP_WATER_SUB_HURT.toString(), FightProperty.FIGHT_PROP_WATER_SUB_HURT, true));
stats.put("respyro", new Stat(FightProperty.FIGHT_PROP_FIRE_SUB_HURT.toString(), FightProperty.FIGHT_PROP_FIRE_SUB_HURT, true));
stats.put("resphys", new Stat(FightProperty.FIGHT_PROP_PHYSICAL_SUB_HURT.toString(), FightProperty.FIGHT_PROP_PHYSICAL_SUB_HURT, true));
stats.put("cdr", new Stat(FightProperty.FIGHT_PROP_SKILL_CD_MINUS_RATIO.toString(), FightProperty.FIGHT_PROP_SKILL_CD_MINUS_RATIO, true));
stats.put("heal", new Stat(FightProperty.FIGHT_PROP_HEAL_ADD.toString(), FightProperty.FIGHT_PROP_HEAL_ADD, true));
stats.put("heali", new Stat(FightProperty.FIGHT_PROP_HEALED_ADD.toString(), FightProperty.FIGHT_PROP_HEALED_ADD, true));
stats.put("shield", new Stat(FightProperty.FIGHT_PROP_SHIELD_COST_MINUS_RATIO.toString(), FightProperty.FIGHT_PROP_SHIELD_COST_MINUS_RATIO, true));
stats.put("defi", new Stat(FightProperty.FIGHT_PROP_DEFENCE_IGNORE_RATIO.toString(), FightProperty.FIGHT_PROP_DEFENCE_IGNORE_RATIO, true));
// Compatibility aliases
stats.put("mhp", stats.get("maxhp"));
stats.put("cr", stats.get("crate"));
stats.put("cd", stats.get("cdmg"));
stats.put("edend", stats.get("edendro"));
stats.put("eelec", stats.get("eelectro"));
stats.put("ethunder", stats.get("eelectro"));
this.stats = new HashMap<>();
for (String key : FightProperty.getShortNames()) {
this.stats.put(key, new Stat(FightProperty.getPropByShortName(key)));
}
// Full FightProperty enum that won't be advertised but can be used by devs
// They have a prefix to avoid the "hp" clash
stats.put("_none", new Stat("NONE", FightProperty.FIGHT_PROP_NONE, true));
stats.put("_base_hp", new Stat("BASE_HP", FightProperty.FIGHT_PROP_BASE_HP, false));
stats.put("_hp", new Stat("HP", FightProperty.FIGHT_PROP_HP, false));
stats.put("_hp_percent", new Stat("HP_PERCENT", FightProperty.FIGHT_PROP_HP_PERCENT, true));
stats.put("_base_attack", new Stat("BASE_ATTACK", FightProperty.FIGHT_PROP_BASE_ATTACK, false));
stats.put("_attack", new Stat("ATTACK", FightProperty.FIGHT_PROP_ATTACK, false));
stats.put("_attack_percent", new Stat("ATTACK_PERCENT", FightProperty.FIGHT_PROP_ATTACK_PERCENT, true));
stats.put("_base_defense", new Stat("BASE_DEFENSE", FightProperty.FIGHT_PROP_BASE_DEFENSE, false));
stats.put("_defense", new Stat("DEFENSE", FightProperty.FIGHT_PROP_DEFENSE, false));
stats.put("_defense_percent", new Stat("DEFENSE_PERCENT", FightProperty.FIGHT_PROP_DEFENSE_PERCENT, true));
stats.put("_base_speed", new Stat("BASE_SPEED", FightProperty.FIGHT_PROP_BASE_SPEED, true));
stats.put("_speed_percent", new Stat("SPEED_PERCENT", FightProperty.FIGHT_PROP_SPEED_PERCENT, true));
stats.put("_hp_mp_percent", new Stat("HP_MP_PERCENT", FightProperty.FIGHT_PROP_HP_MP_PERCENT, true));
stats.put("_attack_mp_percent", new Stat("ATTACK_MP_PERCENT", FightProperty.FIGHT_PROP_ATTACK_MP_PERCENT, true));
stats.put("_critical", new Stat("CRITICAL", FightProperty.FIGHT_PROP_CRITICAL, true));
stats.put("_anti_critical", new Stat("ANTI_CRITICAL", FightProperty.FIGHT_PROP_ANTI_CRITICAL, true));
stats.put("_critical_hurt", new Stat("CRITICAL_HURT", FightProperty.FIGHT_PROP_CRITICAL_HURT, true));
stats.put("_charge_efficiency", new Stat("CHARGE_EFFICIENCY", FightProperty.FIGHT_PROP_CHARGE_EFFICIENCY, true));
stats.put("_add_hurt", new Stat("ADD_HURT", FightProperty.FIGHT_PROP_ADD_HURT, true));
stats.put("_sub_hurt", new Stat("SUB_HURT", FightProperty.FIGHT_PROP_SUB_HURT, true));
stats.put("_heal_add", new Stat("HEAL_ADD", FightProperty.FIGHT_PROP_HEAL_ADD, true));
stats.put("_healed_add", new Stat("HEALED_ADD", FightProperty.FIGHT_PROP_HEALED_ADD, false));
stats.put("_element_mastery", new Stat("ELEMENT_MASTERY", FightProperty.FIGHT_PROP_ELEMENT_MASTERY, true));
stats.put("_physical_sub_hurt", new Stat("PHYSICAL_SUB_HURT", FightProperty.FIGHT_PROP_PHYSICAL_SUB_HURT, true));
stats.put("_physical_add_hurt", new Stat("PHYSICAL_ADD_HURT", FightProperty.FIGHT_PROP_PHYSICAL_ADD_HURT, true));
stats.put("_defence_ignore_ratio", new Stat("DEFENCE_IGNORE_RATIO", FightProperty.FIGHT_PROP_DEFENCE_IGNORE_RATIO, true));
stats.put("_defence_ignore_delta", new Stat("DEFENCE_IGNORE_DELTA", FightProperty.FIGHT_PROP_DEFENCE_IGNORE_DELTA, true));
stats.put("_fire_add_hurt", new Stat("FIRE_ADD_HURT", FightProperty.FIGHT_PROP_FIRE_ADD_HURT, true));
stats.put("_elec_add_hurt", new Stat("ELEC_ADD_HURT", FightProperty.FIGHT_PROP_ELEC_ADD_HURT, true));
stats.put("_water_add_hurt", new Stat("WATER_ADD_HURT", FightProperty.FIGHT_PROP_WATER_ADD_HURT, true));
stats.put("_grass_add_hurt", new Stat("GRASS_ADD_HURT", FightProperty.FIGHT_PROP_GRASS_ADD_HURT, true));
stats.put("_wind_add_hurt", new Stat("WIND_ADD_HURT", FightProperty.FIGHT_PROP_WIND_ADD_HURT, true));
stats.put("_rock_add_hurt", new Stat("ROCK_ADD_HURT", FightProperty.FIGHT_PROP_ROCK_ADD_HURT, true));
stats.put("_ice_add_hurt", new Stat("ICE_ADD_HURT", FightProperty.FIGHT_PROP_ICE_ADD_HURT, true));
stats.put("_hit_head_add_hurt", new Stat("HIT_HEAD_ADD_HURT", FightProperty.FIGHT_PROP_HIT_HEAD_ADD_HURT, true));
stats.put("_fire_sub_hurt", new Stat("FIRE_SUB_HURT", FightProperty.FIGHT_PROP_FIRE_SUB_HURT, true));
stats.put("_elec_sub_hurt", new Stat("ELEC_SUB_HURT", FightProperty.FIGHT_PROP_ELEC_SUB_HURT, true));
stats.put("_water_sub_hurt", new Stat("WATER_SUB_HURT", FightProperty.FIGHT_PROP_WATER_SUB_HURT, true));
stats.put("_grass_sub_hurt", new Stat("GRASS_SUB_HURT", FightProperty.FIGHT_PROP_GRASS_SUB_HURT, true));
stats.put("_wind_sub_hurt", new Stat("WIND_SUB_HURT", FightProperty.FIGHT_PROP_WIND_SUB_HURT, true));
stats.put("_rock_sub_hurt", new Stat("ROCK_SUB_HURT", FightProperty.FIGHT_PROP_ROCK_SUB_HURT, true));
stats.put("_ice_sub_hurt", new Stat("ICE_SUB_HURT", FightProperty.FIGHT_PROP_ICE_SUB_HURT, true));
stats.put("_effect_hit", new Stat("EFFECT_HIT", FightProperty.FIGHT_PROP_EFFECT_HIT, true));
stats.put("_effect_resist", new Stat("EFFECT_RESIST", FightProperty.FIGHT_PROP_EFFECT_RESIST, true));
stats.put("_freeze_resist", new Stat("FREEZE_RESIST", FightProperty.FIGHT_PROP_FREEZE_RESIST, true));
stats.put("_torpor_resist", new Stat("TORPOR_RESIST", FightProperty.FIGHT_PROP_TORPOR_RESIST, true));
stats.put("_dizzy_resist", new Stat("DIZZY_RESIST", FightProperty.FIGHT_PROP_DIZZY_RESIST, true));
stats.put("_freeze_shorten", new Stat("FREEZE_SHORTEN", FightProperty.FIGHT_PROP_FREEZE_SHORTEN, true));
stats.put("_torpor_shorten", new Stat("TORPOR_SHORTEN", FightProperty.FIGHT_PROP_TORPOR_SHORTEN, true));
stats.put("_dizzy_shorten", new Stat("DIZZY_SHORTEN", FightProperty.FIGHT_PROP_DIZZY_SHORTEN, true));
stats.put("_max_fire_energy", new Stat("MAX_FIRE_ENERGY", FightProperty.FIGHT_PROP_MAX_FIRE_ENERGY, true));
stats.put("_max_elec_energy", new Stat("MAX_ELEC_ENERGY", FightProperty.FIGHT_PROP_MAX_ELEC_ENERGY, true));
stats.put("_max_water_energy", new Stat("MAX_WATER_ENERGY", FightProperty.FIGHT_PROP_MAX_WATER_ENERGY, true));
stats.put("_max_grass_energy", new Stat("MAX_GRASS_ENERGY", FightProperty.FIGHT_PROP_MAX_GRASS_ENERGY, true));
stats.put("_max_wind_energy", new Stat("MAX_WIND_ENERGY", FightProperty.FIGHT_PROP_MAX_WIND_ENERGY, true));
stats.put("_max_ice_energy", new Stat("MAX_ICE_ENERGY", FightProperty.FIGHT_PROP_MAX_ICE_ENERGY, true));
stats.put("_max_rock_energy", new Stat("MAX_ROCK_ENERGY", FightProperty.FIGHT_PROP_MAX_ROCK_ENERGY, true));
stats.put("_skill_cd_minus_ratio", new Stat("SKILL_CD_MINUS_RATIO", FightProperty.FIGHT_PROP_SKILL_CD_MINUS_RATIO, true));
stats.put("_shield_cost_minus_ratio", new Stat("SHIELD_COST_MINUS_RATIO", FightProperty.FIGHT_PROP_SHIELD_COST_MINUS_RATIO, true));
stats.put("_cur_fire_energy", new Stat("CUR_FIRE_ENERGY", FightProperty.FIGHT_PROP_CUR_FIRE_ENERGY, false));
stats.put("_cur_elec_energy", new Stat("CUR_ELEC_ENERGY", FightProperty.FIGHT_PROP_CUR_ELEC_ENERGY, false));
stats.put("_cur_water_energy", new Stat("CUR_WATER_ENERGY", FightProperty.FIGHT_PROP_CUR_WATER_ENERGY, false));
stats.put("_cur_grass_energy", new Stat("CUR_GRASS_ENERGY", FightProperty.FIGHT_PROP_CUR_GRASS_ENERGY, false));
stats.put("_cur_wind_energy", new Stat("CUR_WIND_ENERGY", FightProperty.FIGHT_PROP_CUR_WIND_ENERGY, false));
stats.put("_cur_ice_energy", new Stat("CUR_ICE_ENERGY", FightProperty.FIGHT_PROP_CUR_ICE_ENERGY, false));
stats.put("_cur_rock_energy", new Stat("CUR_ROCK_ENERGY", FightProperty.FIGHT_PROP_CUR_ROCK_ENERGY, false));
stats.put("_cur_hp", new Stat("CUR_HP", FightProperty.FIGHT_PROP_CUR_HP, false));
stats.put("_max_hp", new Stat("MAX_HP", FightProperty.FIGHT_PROP_MAX_HP, false));
stats.put("_cur_attack", new Stat("CUR_ATTACK", FightProperty.FIGHT_PROP_CUR_ATTACK, false));
stats.put("_cur_defense", new Stat("CUR_DEFENSE", FightProperty.FIGHT_PROP_CUR_DEFENSE, false));
stats.put("_cur_speed", new Stat("CUR_SPEED", FightProperty.FIGHT_PROP_CUR_SPEED, true));
stats.put("_nonextra_attack", new Stat("NONEXTRA_ATTACK", FightProperty.FIGHT_PROP_NONEXTRA_ATTACK, true));
stats.put("_nonextra_defense", new Stat("NONEXTRA_DEFENSE", FightProperty.FIGHT_PROP_NONEXTRA_DEFENSE, true));
stats.put("_nonextra_critical", new Stat("NONEXTRA_CRITICAL", FightProperty.FIGHT_PROP_NONEXTRA_CRITICAL, true));
stats.put("_nonextra_anti_critical", new Stat("NONEXTRA_ANTI_CRITICAL", FightProperty.FIGHT_PROP_NONEXTRA_ANTI_CRITICAL, true));
stats.put("_nonextra_critical_hurt", new Stat("NONEXTRA_CRITICAL_HURT", FightProperty.FIGHT_PROP_NONEXTRA_CRITICAL_HURT, true));
stats.put("_nonextra_charge_efficiency", new Stat("NONEXTRA_CHARGE_EFFICIENCY", FightProperty.FIGHT_PROP_NONEXTRA_CHARGE_EFFICIENCY, true));
stats.put("_nonextra_element_mastery", new Stat("NONEXTRA_ELEMENT_MASTERY", FightProperty.FIGHT_PROP_NONEXTRA_ELEMENT_MASTERY, true));
stats.put("_nonextra_physical_sub_hurt", new Stat("NONEXTRA_PHYSICAL_SUB_HURT", FightProperty.FIGHT_PROP_NONEXTRA_PHYSICAL_SUB_HURT, true));
stats.put("_nonextra_fire_add_hurt", new Stat("NONEXTRA_FIRE_ADD_HURT", FightProperty.FIGHT_PROP_NONEXTRA_FIRE_ADD_HURT, true));
stats.put("_nonextra_elec_add_hurt", new Stat("NONEXTRA_ELEC_ADD_HURT", FightProperty.FIGHT_PROP_NONEXTRA_ELEC_ADD_HURT, true));
stats.put("_nonextra_water_add_hurt", new Stat("NONEXTRA_WATER_ADD_HURT", FightProperty.FIGHT_PROP_NONEXTRA_WATER_ADD_HURT, true));
stats.put("_nonextra_grass_add_hurt", new Stat("NONEXTRA_GRASS_ADD_HURT", FightProperty.FIGHT_PROP_NONEXTRA_GRASS_ADD_HURT, true));
stats.put("_nonextra_wind_add_hurt", new Stat("NONEXTRA_WIND_ADD_HURT", FightProperty.FIGHT_PROP_NONEXTRA_WIND_ADD_HURT, true));
stats.put("_nonextra_rock_add_hurt", new Stat("NONEXTRA_ROCK_ADD_HURT", FightProperty.FIGHT_PROP_NONEXTRA_ROCK_ADD_HURT, true));
stats.put("_nonextra_ice_add_hurt", new Stat("NONEXTRA_ICE_ADD_HURT", FightProperty.FIGHT_PROP_NONEXTRA_ICE_ADD_HURT, true));
stats.put("_nonextra_fire_sub_hurt", new Stat("NONEXTRA_FIRE_SUB_HURT", FightProperty.FIGHT_PROP_NONEXTRA_FIRE_SUB_HURT, true));
stats.put("_nonextra_elec_sub_hurt", new Stat("NONEXTRA_ELEC_SUB_HURT", FightProperty.FIGHT_PROP_NONEXTRA_ELEC_SUB_HURT, true));
stats.put("_nonextra_water_sub_hurt", new Stat("NONEXTRA_WATER_SUB_HURT", FightProperty.FIGHT_PROP_NONEXTRA_WATER_SUB_HURT, true));
stats.put("_nonextra_grass_sub_hurt", new Stat("NONEXTRA_GRASS_SUB_HURT", FightProperty.FIGHT_PROP_NONEXTRA_GRASS_SUB_HURT, true));
stats.put("_nonextra_wind_sub_hurt", new Stat("NONEXTRA_WIND_SUB_HURT", FightProperty.FIGHT_PROP_NONEXTRA_WIND_SUB_HURT, true));
stats.put("_nonextra_rock_sub_hurt", new Stat("NONEXTRA_ROCK_SUB_HURT", FightProperty.FIGHT_PROP_NONEXTRA_ROCK_SUB_HURT, true));
stats.put("_nonextra_ice_sub_hurt", new Stat("NONEXTRA_ICE_SUB_HURT", FightProperty.FIGHT_PROP_NONEXTRA_ICE_SUB_HURT, true));
stats.put("_nonextra_skill_cd_minus_ratio", new Stat("NONEXTRA_SKILL_CD_MINUS_RATIO", FightProperty.FIGHT_PROP_NONEXTRA_SKILL_CD_MINUS_RATIO, true));
stats.put("_nonextra_shield_cost_minus_ratio", new Stat("NONEXTRA_SHIELD_COST_MINUS_RATIO", FightProperty.FIGHT_PROP_NONEXTRA_SHIELD_COST_MINUS_RATIO, true));
stats.put("_nonextra_physical_add_hurt", new Stat("NONEXTRA_PHYSICAL_ADD_HURT", FightProperty.FIGHT_PROP_NONEXTRA_PHYSICAL_ADD_HURT, true));
for (FightProperty prop : FightProperty.values()) {
String name = prop.toString().substring(10); // FIGHT_PROP_BASE_HP -> _BASE_HP
String key = name.toLowerCase(); // _BASE_HP -> _base_hp
name = name.substring(1); // _BASE_HP -> BASE_HP
this.stats.put(key, new Stat(name, prop));
}
// Compatibility aliases
this.stats.put("mhp", this.stats.get("maxhp"));
this.stats.put("hp", new Stat(FightProperty.FIGHT_PROP_CUR_HP)); // Overrides FIGHT_PROP_HP
this.stats.put("atk", new Stat(FightProperty.FIGHT_PROP_CUR_ATTACK)); // Overrides FIGHT_PROP_ATTACK
this.stats.put("atkb", new Stat(FightProperty.FIGHT_PROP_BASE_ATTACK)); // This doesn't seem to get used to recalculate ATK, so it's only useful for stuff like Bennett's buff.
this.stats.put("eanemo", this.stats.get("anemo%"));
this.stats.put("ecryo", this.stats.get("cryo%"));
this.stats.put("edendro", this.stats.get("dendro%"));
this.stats.put("edend", this.stats.get("dendro%"));
this.stats.put("eelectro", this.stats.get("electro%"));
this.stats.put("eelec", this.stats.get("electro%"));
this.stats.put("ethunder", this.stats.get("electro%"));
this.stats.put("egeo", this.stats.get("geo%"));
this.stats.put("ehydro", this.stats.get("hydro%"));
this.stats.put("epyro", this.stats.get("pyro%"));
this.stats.put("ephys", this.stats.get("phys%"));
}
@Override
@ -206,8 +96,8 @@ public final class SetStatsCommand implements CommandHandler {
Stat stat = stats.get(statStr);
entity.setFightProperty(stat.prop, value);
entity.getWorld().broadcastPacket(new PacketEntityFightPropUpdateNotify(entity, stat.prop));
if (stat.percent) {
valueStr = String.format("%.1f%%", value*100f);
if (FightProperty.isPercentage(stat.prop)) {
valueStr = String.format("%.1f%%", value * 100f);
} else {
valueStr = String.format("%.0f", value);
}

View File

@ -20,8 +20,9 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
public class GameDepot {
private static Int2ObjectMap<WeightedList<ReliquaryMainPropData>> relicMainPropDepot = new Int2ObjectOpenHashMap<>();
private static Int2ObjectMap<List<ReliquaryAffixData>> relicAffixDepot = 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 Map<String, AvatarConfig> playerAbilities = new HashMap<>();
private static Int2ObjectMap<SpatialIndex<SpawnGroupEntry>> spawnLists = new Int2ObjectOpenHashMap<>();
@ -31,8 +32,10 @@ public class GameDepot {
if (data.getWeight() <= 0 || data.getPropDepotId() <= 0) {
continue;
}
WeightedList<ReliquaryMainPropData> list = relicMainPropDepot.computeIfAbsent(data.getPropDepotId(), k -> new WeightedList<>());
list.add(data.getWeight(), data);
List<ReliquaryMainPropData> list = relicMainPropDepot.computeIfAbsent(data.getPropDepotId(), k -> new ArrayList<>());
list.add(data);
WeightedList<ReliquaryMainPropData> weightedList = relicRandomMainPropDepot.computeIfAbsent(data.getPropDepotId(), k -> new WeightedList<>());
weightedList.add(data.getWeight(), data);
}
for (ReliquaryAffixData data : GameData.getReliquaryAffixDataMap().values()) {
if (data.getWeight() <= 0 || data.getDepotId() <= 0) {
@ -48,14 +51,18 @@ public class GameDepot {
}
public static ReliquaryMainPropData getRandomRelicMainProp(int depot) {
WeightedList<ReliquaryMainPropData> depotList = relicMainPropDepot.get(depot);
WeightedList<ReliquaryMainPropData> depotList = relicRandomMainPropDepot.get(depot);
if (depotList == null) {
return null;
}
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);
}

View File

@ -10,6 +10,7 @@ import emu.grasscutter.game.inventory.*;
import emu.grasscutter.game.props.FightProperty;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntSet;
import lombok.Getter;
@ResourceType(name = {"MaterialExcelConfigData.json",
"WeaponExcelConfigData.json",
@ -19,23 +20,23 @@ import it.unimi.dsi.fastutil.ints.IntSet;
public class ItemData extends GameResource {
private int id;
private int stackLimit = 1;
private int maxUseCount;
private int rankLevel;
private String effectName;
private int[] satiationParams;
private int rank;
private int weight;
private int gadgetId;
@Getter private int stackLimit = 1;
@Getter private int maxUseCount;
@Getter private int rankLevel;
@Getter private String effectName;
@Getter private int[] satiationParams;
@Getter private int rank;
@Getter private int weight;
@Getter private int gadgetId;
private int[] destroyReturnMaterial;
private int[] destroyReturnMaterialCount;
@Getter private int[] destroyReturnMaterial;
@Getter private int[] destroyReturnMaterialCount;
private List<ItemUseData> itemUse;
@Getter private List<ItemUseData> itemUse;
// Food
private String foodQuality;
private String useTarget;
@Getter private String foodQuality;
@Getter private String useTarget;
private String[] iseParam;
// String enums
@ -45,42 +46,42 @@ public class ItemData extends GameResource {
private String effectType;
private String destroyRule;
// Relic
private int mainPropDepotId;
private int appendPropDepotId;
private int appendPropNum;
private int setId;
private int[] addPropLevels;
private int baseConvExp;
private int maxLevel;
// Weapon
private int weaponPromoteId;
private int weaponBaseExp;
private int storyId;
private int avatarPromoteId;
private int awakenMaterial;
private int[] awakenCosts;
private int[] skillAffix;
private WeaponProperty[] weaponProp;
// Hash
private String icon;
private long nameTextMapHash;
// Post load
// Post load enum forms of above
private transient MaterialType materialEnumType;
private transient ItemType itemEnumType;
private transient EquipType equipEnumType;
private IntSet addPropLevelSet;
// Relic
@Getter private int mainPropDepotId;
@Getter private int appendPropDepotId;
@Getter private int appendPropNum;
@Getter private int setId;
private int[] addPropLevels;
@Getter private int baseConvExp;
@Getter private int maxLevel;
// Weapon
@Getter private int weaponPromoteId;
@Getter private int weaponBaseExp;
@Getter private int storyId;
@Getter private int avatarPromoteId;
@Getter private int awakenMaterial;
@Getter private int[] awakenCosts;
@Getter private int[] skillAffix;
private WeaponProperty[] weaponProp;
// Hash
@Getter private String icon;
@Getter private long nameTextMapHash;
@Getter private IntSet addPropLevelSet;
// Furniture
private int comfort;
private List<Integer> furnType;
private List<Integer> furnitureGadgetID;
@Getter private int comfort;
@Getter private List<Integer> furnType;
@Getter private List<Integer> furnitureGadgetID;
@SerializedName("JFDLJGDFIGL")
private int roomSceneId;
@Getter private int roomSceneId;
@Override
public int getId(){
@ -91,137 +92,17 @@ public class ItemData extends GameResource {
return this.materialType;
}
public int getStackLimit(){
return this.stackLimit;
}
public int getMaxUseCount(){
return this.maxUseCount;
}
public String getUseTarget(){
return this.useTarget;
}
public String[] getUseParam(){
return this.iseParam;
}
public int getRankLevel(){
return this.rankLevel;
}
public String getFoodQuality(){
return this.foodQuality;
}
public String getEffectName(){
return this.effectName;
}
public int[] getSatiationParams(){
return this.satiationParams;
}
public int[] getDestroyReturnMaterial(){
return this.destroyReturnMaterial;
}
public int[] getDestroyReturnMaterialCount(){
return this.destroyReturnMaterialCount;
}
public List<ItemUseData> getItemUse() {
return itemUse;
}
public long getNameTextMapHash(){
return this.nameTextMapHash;
}
public String getIcon(){
return this.icon;
}
public String getItemTypeString(){
return this.itemType;
}
public int getRank(){
return this.rank;
}
public int getGadgetId() {
return gadgetId;
}
public int getBaseConvExp() {
return baseConvExp;
}
public int getMainPropDepotId() {
return mainPropDepotId;
}
public int getAppendPropDepotId() {
return appendPropDepotId;
}
public int getAppendPropNum() {
return appendPropNum;
}
public int getSetId() {
return setId;
}
public int getWeaponPromoteId() {
return weaponPromoteId;
}
public int getWeaponBaseExp() {
return weaponBaseExp;
}
public int getAwakenMaterial() {
return awakenMaterial;
}
public int[] getAwakenCosts() {
return awakenCosts;
}
public IntSet getAddPropLevelSet() {
return addPropLevelSet;
}
public int[] getSkillAffix() {
return skillAffix;
}
public WeaponProperty[] getWeaponProperties() {
return weaponProp;
}
public int getMaxLevel() {
return maxLevel;
}
public int getComfort() {
return comfort;
}
public List<Integer> getFurnType() {
return furnType;
}
public List<Integer> getFurnitureGadgetID() {
return furnitureGadgetID;
}
public int getRoomSceneId() {
return roomSceneId;
}
public ItemType getItemType() {
return this.itemEnumType;
@ -274,26 +155,10 @@ public class ItemData extends GameResource {
}
public static class WeaponProperty {
private FightProperty fightProp;
private String propType;
private float initValue;
private String type;
public String getPropType(){
return this.propType;
}
public float getInitValue(){
return this.initValue;
}
public String getType(){
return this.type;
}
public FightProperty getFightProp() {
return fightProp;
}
@Getter private FightProperty fightProp;
@Getter private String propType;
@Getter private float initValue;
@Getter private String type;
public void onLoad() {
this.fightProp = FightProperty.getPropByName(propType);

View File

@ -242,6 +242,23 @@ public class Avatar {
this.promoteLevel = promoteLevel;
}
static public int getMinPromoteLevel(int level) {
if (level > 80) {
return 6;
} else if (level > 70) {
return 5;
} else if (level > 60) {
return 4;
} else if (level > 50) {
return 3;
} else if (level > 40) {
return 2;
} else if (level > 20) {
return 1;
}
return 0;
}
public Int2ObjectMap<GameItem> getEquips() {
return equips;
}

View File

@ -33,34 +33,36 @@ import emu.grasscutter.net.proto.SceneReliquaryInfoOuterClass.SceneReliquaryInfo
import emu.grasscutter.net.proto.SceneWeaponInfoOuterClass.SceneWeaponInfo;
import emu.grasscutter.net.proto.WeaponOuterClass.Weapon;
import emu.grasscutter.utils.WeightedList;
import lombok.Getter;
import lombok.Setter;
@Entity(value = "items", useDiscriminator = false)
public class GameItem {
@Id private ObjectId id;
@Indexed private int ownerId;
private int itemId;
private int count;
@Getter @Setter private int itemId;
@Getter @Setter private int count;
@Transient private long guid; // Player unique id
@Transient private ItemData itemData;
@Transient @Getter private long guid; // Player unique id
@Transient @Getter @Setter private ItemData itemData;
// Equips
private int level;
private int exp;
private int totalExp;
private int promoteLevel;
private boolean locked;
@Getter @Setter private int level;
@Getter @Setter private int exp;
@Getter @Setter private int totalExp;
@Getter @Setter private int promoteLevel;
@Getter @Setter private boolean locked;
// Weapon
private List<Integer> affixes;
private int refinement = 0;
@Getter private List<Integer> affixes;
@Getter @Setter private int refinement = 0;
// Relic
private int mainPropId;
private List<Integer> appendPropIdList;
@Getter @Setter private int mainPropId;
@Getter private List<Integer> appendPropIdList;
private int equipCharacter;
@Transient private int weaponEntityId;
@Getter @Setter private int equipCharacter;
@Transient @Getter @Setter private int weaponEntityId;
public GameItem() {
// Morphia only
@ -82,42 +84,37 @@ public class GameItem {
this.itemId = data.getId();
this.itemData = data;
if (data.getItemType() == ItemType.ITEM_VIRTUAL) {
switch (data.getItemType()) {
case ITEM_VIRTUAL:
this.count = count;
} else {
this.count = Math.min(count, data.getStackLimit());
break;
case ITEM_WEAPON:
this.count = 1;
this.level = Math.max(this.count, 1); // ??????????????????
this.affixes = new ArrayList<>(2);
if (data.getSkillAffix() != null) {
for (int skillAffix : data.getSkillAffix()) {
if (skillAffix > 0) {
this.affixes.add(skillAffix);
}
}
}
break;
case ITEM_RELIQUARY:
this.count = 1;
this.level = 1;
this.appendPropIdList = new ArrayList<>();
// Create main property
ReliquaryMainPropData mainPropData = GameDepot.getRandomRelicMainProp(data.getMainPropDepotId());
if (mainPropData != null) {
this.mainPropId = mainPropData.getId();
}
// Create extra stats
this.addAppendProps(data.getAppendPropNum());
break;
default:
this.count = Math.min(count, data.getStackLimit());
}
// Equip data
if (getItemType() == ItemType.ITEM_WEAPON) {
this.level = Math.max(this.count, 1);
this.affixes = new ArrayList<>(2);
if (getItemData().getSkillAffix() != null) {
for (int skillAffix : getItemData().getSkillAffix()) {
if (skillAffix > 0) {
this.affixes.add(skillAffix);
}
}
}
} else if (getItemType() == ItemType.ITEM_RELIQUARY) {
this.level = 1;
this.appendPropIdList = new ArrayList<>();
// Create main property
ReliquaryMainPropData mainPropData = GameDepot.getRandomRelicMainProp(getItemData().getMainPropDepotId());
if (mainPropData != null) {
this.mainPropId = mainPropData.getId();
}
// Create extra stats
if (getItemData().getAppendPropNum() > 0) {
for (int i = 0; i < getItemData().getAppendPropNum(); i++) {
this.addAppendProp();
}
}
}
}
public ObjectId getObjectId() {
return id;
}
public int getOwnerId() {
@ -128,162 +125,88 @@ public class GameItem {
this.ownerId = player.getUid();
this.guid = player.getNextGameGuid();
}
public int getItemId() {
return itemId;
}
public void setItemId(int itemId) {
this.itemId = itemId;
}
public long getGuid() {
return guid;
public ObjectId getObjectId() {
return id;
}
public ItemType getItemType() {
return this.itemData.getItemType();
}
public ItemData getItemData() {
return itemData;
}
public void setItemData(ItemData materialData) {
this.itemData = materialData;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
public int getLevel() {
return level;
}
public void setLevel(int level) {
this.level = level;
}
public int getExp() {
return exp;
}
public void setExp(int exp) {
this.exp = exp;
}
public int getTotalExp() {
return totalExp;
}
public void setTotalExp(int totalExp) {
this.totalExp = totalExp;
}
public int getPromoteLevel() {
return promoteLevel;
}
public void setPromoteLevel(int promoteLevel) {
this.promoteLevel = promoteLevel;
public static int getMinPromoteLevel(int level) {
if (level > 80) {
return 6;
} else if (level > 70) {
return 5;
} else if (level > 60) {
return 4;
} else if (level > 50) {
return 3;
} else if (level > 40) {
return 2;
} else if (level > 20) {
return 1;
}
return 0;
}
public int getEquipSlot() {
return this.getItemData().getEquipType().getValue();
}
public int getEquipCharacter() {
return equipCharacter;
}
public void setEquipCharacter(int equipCharacter) {
this.equipCharacter = equipCharacter;
}
public boolean isEquipped() {
return this.getEquipCharacter() > 0;
}
public boolean isLocked() {
return locked;
}
public void setLocked(boolean locked) {
this.locked = locked;
}
public boolean isDestroyable() {
return !this.isLocked() && !this.isEquipped();
}
public int getWeaponEntityId() {
return weaponEntityId;
}
public void setWeaponEntityId(int weaponEntityId) {
this.weaponEntityId = weaponEntityId;
}
public List<Integer> getAffixes() {
return affixes;
}
public int getRefinement() {
return refinement;
}
public void setRefinement(int refinement) {
this.refinement = refinement;
}
public int getMainPropId() {
return mainPropId;
}
public void setMainPropId(int mainPropId) {
this.mainPropId = mainPropId;
}
public List<Integer> getAppendPropIdList() {
return appendPropIdList;
}
public void addAppendProp() {
if (this.getAppendPropIdList() == null) {
if (this.appendPropIdList == null) {
this.appendPropIdList = new ArrayList<>();
}
if (this.getAppendPropIdList().size() < 4) {
addNewAppendProp();
if (this.appendPropIdList.size() < 4) {
this.addNewAppendProp();
} else {
upgradeRandomAppendProp();
this.upgradeRandomAppendProp();
}
}
public void addAppendProps(int quantity) {
int num = Math.max(quantity, 0);
for (int i = 0; i < num; i++) {
this.addAppendProp();
}
}
private Set<FightProperty> getAppendFightProperties() {
Set<FightProperty> props = new HashSet<>();
// Previously this would check no more than the first four affixes, however custom artifacts may not respect this order.
for (int appendPropId : this.appendPropIdList) {
ReliquaryAffixData affixData = GameData.getReliquaryAffixDataMap().get(appendPropId);
if (affixData != null) {
props.add(affixData.getFightProp());
}
}
return props;
}
private void addNewAppendProp() {
List<ReliquaryAffixData> affixList = GameDepot.getRandomRelicAffixList(getItemData().getAppendPropDepotId());
List<ReliquaryAffixData> affixList = GameDepot.getRelicAffixList(this.itemData.getAppendPropDepotId());
if (affixList == null) {
return;
}
// Build blacklist - Dont add same stat as main/sub stat
Set<FightProperty> blacklist = new HashSet<>();
ReliquaryMainPropData mainPropData = GameData.getReliquaryMainPropDataMap().get(this.getMainPropId());
Set<FightProperty> blacklist = this.getAppendFightProperties();
ReliquaryMainPropData mainPropData = GameData.getReliquaryMainPropDataMap().get(this.mainPropId);
if (mainPropData != null) {
blacklist.add(mainPropData.getFightProp());
}
int len = Math.min(4, this.getAppendPropIdList().size());
for (int i = 0; i < len; i++) {
ReliquaryAffixData affixData = GameData.getReliquaryAffixDataMap().get((int) this.getAppendPropIdList().get(i));
if (affixData != null) {
blacklist.add(affixData.getFightProp());
}
}
// Build random list
WeightedList<ReliquaryAffixData> randomList = new WeightedList<>();
@ -299,25 +222,18 @@ public class GameItem {
// Add random stat
ReliquaryAffixData affixData = randomList.next();
this.getAppendPropIdList().add(affixData.getId());
this.appendPropIdList.add(affixData.getId());
}
private void upgradeRandomAppendProp() {
List<ReliquaryAffixData> affixList = GameDepot.getRandomRelicAffixList(getItemData().getAppendPropDepotId());
List<ReliquaryAffixData> affixList = GameDepot.getRelicAffixList(this.itemData.getAppendPropDepotId());
if (affixList == null) {
return;
}
// Build whitelist
Set<FightProperty> whitelist = new HashSet<>();
int len = Math.min(4, this.getAppendPropIdList().size());
for (int i = 0; i < len; i++) {
ReliquaryAffixData affixData = GameData.getReliquaryAffixDataMap().get((int) this.getAppendPropIdList().get(i));
if (affixData != null) {
whitelist.add(affixData.getFightProp());
}
}
Set<FightProperty> whitelist = this.getAppendFightProperties();
// Build random list
WeightedList<ReliquaryAffixData> randomList = new WeightedList<>();
@ -329,7 +245,7 @@ public class GameItem {
// Add random stat
ReliquaryAffixData affixData = randomList.next();
this.getAppendPropIdList().add(affixData.getId());
this.appendPropIdList.add(affixData.getId());
}
@PostLoad

View File

@ -147,7 +147,7 @@ public class InventoryManager {
int totalExp = relic.getTotalExp();
int reqExp = GameData.getRelicExpRequired(relic.getItemData().getRankLevel(), level);
int upgrades = 0;
List<Integer> oldAppendPropIdList = relic.getAppendPropIdList();
List<Integer> oldAppendPropIdList = new ArrayList<>(relic.getAppendPropIdList());
while (expGain > 0 && reqExp > 0 && level < relic.getItemData().getMaxLevel()) {
// Do calculations
@ -169,13 +169,7 @@ public class InventoryManager {
}
}
if (upgrades > 0) {
oldAppendPropIdList = new ArrayList<>(relic.getAppendPropIdList());
while (upgrades > 0) {
relic.addAppendProp();
upgrades -= 1;
}
}
relic.addAppendProps(upgrades);
// Save
relic.setLevel(level);

View File

@ -1,7 +1,13 @@
package emu.grasscutter.game.props;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static java.util.Map.entry;
import java.util.Arrays;
import java.util.stream.Stream;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
@ -133,4 +139,65 @@ public enum FightProperty {
public static FightProperty getPropByName(String name) {
return stringMap.getOrDefault(name, FIGHT_PROP_NONE);
}
public static FightProperty getPropByShortName(String name) {
return shortNameMap.getOrDefault(name, FIGHT_PROP_NONE);
}
public static Set<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);
}
}

View File

@ -30,21 +30,7 @@ public class PacketGetMailItemRsp extends BasePacket {
if (!message.isAttachmentGot) {//No duplicated item
for (Mail.MailItem mailItem : message.itemList) {
EquipParamOuterClass.EquipParam.Builder item = EquipParamOuterClass.EquipParam.newBuilder();
int promoteLevel = 0;
if (mailItem.itemLevel > 80) { // 80/90
promoteLevel = 6;
} else if (mailItem.itemLevel > 70) { // 70/80
promoteLevel = 5;
} else if (mailItem.itemLevel > 60) { // 60/70
promoteLevel = 4;
} else if (mailItem.itemLevel > 50) { // 50/60
promoteLevel = 3;
} else if (mailItem.itemLevel > 40) { // 40/50
promoteLevel = 2;
} else if (mailItem.itemLevel > 20) { // 20/40
promoteLevel = 1;
}
int promoteLevel = GameItem.getMinPromoteLevel(mailItem.itemLevel);
item.setItemId(mailItem.itemId);
item.setItemNum(mailItem.itemCount);

View File

@ -0,0 +1,60 @@
package emu.grasscutter.utils;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
public final class SparseSet {
/*
* A convenience class for constructing integer sets out of large ranges
* Designed to be fed literal strings from this project only -
* can and will throw exceptions to tell you to fix your code if you feed it garbage. :)
*/
private static class Range {
private final int min, max;
public Range(int min, int max) {
if (min > max) {
throw new IllegalArgumentException("Range passed minimum higher than maximum - " + Integer.toString(min) + " > " + Integer.toString(max));
}
this.min = min;
this.max = max;
}
public boolean check(int value) {
return value >= this.min && value <= this.max;
}
}
private final List<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);
}
}

View File

@ -132,35 +132,16 @@
"in_dungeon_error": "You are already in that dungeon.",
"description": "Enter a dungeon"
},
"giveAll": {
"usage": "Usage: giveall [player] [amount]",
"started": "Receiving all items...",
"success": "Successfully gave all items to %s.",
"invalid_amount_or_playerId": "Invalid amount or player ID.",
"description": "Gives all items"
},
"giveArtifact": {
"usage": "Usage: giveart|gart [player] <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": {
"usage": "Usage: give <player> <itemID|itemName> [amount] [level] [refinement]",
"refinement_only_applicable_weapons": "Refinement is only applicable to weapons.",
"refinement_must_between_1_and_5": "Refinement must be between 1 and 5.",
"usage": "Usage: give <itemID|avatarID|\"all\"|\"weapons\"|\"mats\"|\"avatars\"> [x<amount>] [lv<level>] [r<refinement>]",
"usage_relic": "Usage: give <artifactID> [mainPropID] [<appendPropID>[,<times>]]... [lv<level 0-20>]",
"illegal_relic": "This artifactID belongs to a blacklisted range, it may not be the one you wanted.",
"given": "Given %s of %s to %s.",
"given_with_level_and_refinement": "Given %s with level %s, refinement %s %s times to %s.",
"given_level": "Given %s with level %s %s times to %s.",
"description": "Gives an item to you or the specified player"
"given_avatar": "Given %s with level %s to %s.",
"giveall_success": "Successfully gave all items.",
"description": "Gives an item to you or the specified player. Can also give all weapons, avatars and/or materials, and can construct custom artifacts."
},
"godmode": {
"success": "Godmode is now %s for %s.",
@ -202,10 +183,6 @@
"success": "There are %s player(s) online:",
"description": "List online players"
},
"nostamina": {
"success": "NoStamina is now %s for %s.",
"description": "Keep your endurance to the maximum."
},
"permission": {
"usage": "Usage: permission <add|remove> <username> <permission>",
"add": "Permission added.",

View File

@ -146,21 +146,14 @@
"success": "%s a été donné à %s.",
"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": {
"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_must_between_1_and_5": "Le raffinement doit être compris entre 1 et 5.",
"given": "Given %s of %s to %s.",
"given_with_level_and_refinement": "%s avec le niveau %s, raffinement %s %s fois à %s.",
"given_level": "%s avec le niveau %s %s fois à %s.",
"given_avatar": "%s avec le niveau %s a été donné à %s.",
"description": "Donne un objet au joueur spécifié"
},
"godmode": {

View File

@ -134,20 +134,14 @@
"id_error": "Błędne ID artefaktu.",
"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": {
"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_must_between_1_and_5": "Ulepszenie musi być pomiędzy 1, a 5.",
"given": "Dano %s %s dla %s.",
"given_with_level_and_refinement": "Dano %s z poziomem %s, ulepszeniem %s %s razy dla %s",
"given_level": "Dano %s z poziomem %s %s razy dla %s"
"given_level": "Dano %s z poziomem %s %s razy dla %s",
"given_avatar": "Dano %s z poziomem %s dla %s."
},
"godmode": {
"success": "Godmode jest teraz %s dla %s."

View File

@ -145,21 +145,14 @@
"success": "已将 %s 给予 %s。",
"description": "给予指定圣遗物"
},
"giveChar": {
"usage": "用法givechar <玩家> <角色ID> [等级]",
"given": "已将角色 %s [等级 %s] 给与 %s。",
"invalid_avatar_id": "无效的角色ID。",
"invalid_avatar_level": "无效的角色等级。",
"invalid_avatar_or_player_id": "无效的角色ID/玩家ID。",
"description": "给予指定角色"
},
"give": {
"usage": "用法give <玩家> <物品ID|物品名> [数量] [等级] [精炼等级]",
"usage": "用法give <玩家> <物品ID|角色ID> [数量] [等级] [精炼等级]",
"refinement_only_applicable_weapons": "只有武器可以设置精炼等级。",
"refinement_must_between_1_and_5": "精炼等级必须在 1-5 之间。",
"given": "已将 %s 个 %s 给予 %s。",
"given_with_level_and_refinement": "已将 %s [等级 %s, 精炼 %s] %s 个给予 %s。",
"given_level": "已将 %s [等级 %s] %s 个给予 %s。",
"given_avatar": "已将角色 %s [等级 %s] 给与 %s。",
"description": "给予指定物品"
},
"godmode": {

View File

@ -144,21 +144,14 @@
"success": "已把 %s 給予 %s。",
"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": {
"usage": "用法give <player> <itemId|itemName> [amount] [level] [refinement]",
"usage": "用法give <player> <itemId|avatarId> [amount] [level] [refinement]",
"refinement_only_applicable_weapons": "精煉度只能施加在武器上面。",
"refinement_must_between_1_and_5": "精煉度必需在 1 到 5 之間。",
"given": "已經將 %s 個 %s 給予 %s。",
"given_with_level_and_refinement": "已將 %s [等級%s, 精煉%s] %s個給予 %s",
"given_level": "已將 %s 等級 %s %s 個給予 %s",
"given_avatar": "已將 %s 等級 %s 給予 %s。",
"description": "給予指定物品。"
},
"godmode": {