Merge remote-tracking branch 'origin/development' into development

This commit is contained in:
KingRainbow44 2022-06-26 12:33:03 -04:00
commit 18ecf3e41e
No known key found for this signature in database
GPG Key ID: FC2CB64B00D257BE
83 changed files with 2687 additions and 2468 deletions

1
.gitignore vendored
View File

@ -78,4 +78,5 @@ BuildConfig.java
# macOS # macOS
.DS_Store .DS_Store
.directory
data/hk4e/announcement/ data/hk4e/announcement/

View File

@ -93,6 +93,8 @@ dependencies {
compileOnly 'org.projectlombok:lombok:1.18.24' compileOnly 'org.projectlombok:lombok:1.18.24'
annotationProcessor 'org.projectlombok:lombok:1.18.24' annotationProcessor 'org.projectlombok:lombok:1.18.24'
testCompileOnly 'org.projectlombok:lombok:1.18.24'
testAnnotationProcessor 'org.projectlombok:lombok:1.18.24'
} }
configurations.all { configurations.all {

View File

@ -18,6 +18,11 @@ public final class GameConstants {
public static final int SERVER_CONSOLE_UID = 99; // The UID of the server console's "player". public static final int SERVER_CONSOLE_UID = 99; // The UID of the server console's "player".
public static final int BATTLE_PASS_MAX_LEVEL = 50;
public static final int BATTLE_PASS_POINT_PER_LEVEL = 1000;
public static final int BATTLE_PASS_POINT_PER_WEEK = 10000;
public static final int BATTLE_PASS_LEVEL_PRICE = 150;
// Default entity ability hashes. // Default entity ability hashes.
public static final String[] DEFAULT_ABILITY_STRINGS = { public static final String[] DEFAULT_ABILITY_STRINGS = {
"Avatar_DefaultAbility_VisionReplaceDieInvincible", "Avatar_DefaultAbility_AvartarInShaderChange", "Avatar_SprintBS_Invincible", "Avatar_DefaultAbility_VisionReplaceDieInvincible", "Avatar_DefaultAbility_AvartarInShaderChange", "Avatar_SprintBS_Invincible",

View File

@ -109,6 +109,79 @@ public final class CommandMap {
return this.commands.get(label); return this.commands.get(label);
} }
private Player getTargetPlayer(String playerId, Player player, Player targetPlayer, List<String> args) {
// Top priority: If any @UID argument is present, override targetPlayer with it.
for (int i = 0; i < args.size(); i++) {
String arg = args.get(i);
if (arg.startsWith("@")) {
arg = args.remove(i).substring(1);
if (arg.equals("")) {
// This is a special case to target nothing, distinct from failing to assign a target.
// This is specifically to allow in-game players to run a command without targeting themselves or anyone else.
return null;
}
try {
int uid = Integer.parseInt(arg);
targetPlayer = Grasscutter.getGameServer().getPlayerByUid(uid, true);
if (targetPlayer == null) {
CommandHandler.sendTranslatedMessage(player, "commands.execution.player_exist_error");
throw new IllegalArgumentException();
}
return targetPlayer;
} catch (NumberFormatException e) {
CommandHandler.sendTranslatedMessage(player, "commands.execution.uid_error");
throw new IllegalArgumentException();
}
}
}
// Next priority: If we invoked with a target, use that.
// By default, this only happens when you message another player in-game with a command.
if (targetPlayer != null) {
return targetPlayer;
}
// Next priority: Use previously-set target. (see /target [[@]UID])
if (targetPlayerIds.containsKey(playerId)) {
targetPlayer = Grasscutter.getGameServer().getPlayerByUid(targetPlayerIds.get(playerId), true);
// We check every time in case the target is deleted after being targeted
if (targetPlayer == null) {
CommandHandler.sendTranslatedMessage(player, "commands.execution.player_exist_error");
throw new IllegalArgumentException();
}
return targetPlayer;
}
// Lowest priority: Target the player invoking the command. In the case of the console, this will return null.
return player;
}
private boolean setPlayerTarget(String playerId, Player player, String targetUid) {
if (targetUid.equals("")) { // Clears the default targetPlayer.
targetPlayerIds.remove(playerId);
CommandHandler.sendTranslatedMessage(player, "commands.execution.clear_target");
return true;
}
// Sets default targetPlayer to the UID provided.
try {
int uid = Integer.parseInt(targetUid);
Player targetPlayer = Grasscutter.getGameServer().getPlayerByUid(uid, true);
if (targetPlayer == null) {
CommandHandler.sendTranslatedMessage(player, "commands.execution.player_exist_error");
return false;
}
targetPlayerIds.put(playerId, uid);
CommandHandler.sendTranslatedMessage(player, "commands.execution.set_target", targetUid);
CommandHandler.sendTranslatedMessage(player, targetPlayer.isOnline()? "commands.execution.set_target_online" : "commands.execution.set_target_offline", targetUid);
return true;
} catch (NumberFormatException e) {
CommandHandler.sendTranslatedMessage(player, "commands.execution.uid_error");
return false;
}
}
/** /**
* Invoke a command handler with the given arguments. * Invoke a command handler with the given arguments.
* *
@ -129,39 +202,21 @@ public final class CommandMap {
String playerId = (player == null) ? consoleId : player.getAccount().getId(); String playerId = (player == null) ? consoleId : player.getAccount().getId();
// Check for special cases - currently only target command. // Check for special cases - currently only target command.
String targetUidStr = null;
if (label.startsWith("@")) { // @[UID] if (label.startsWith("@")) { // @[UID]
targetUidStr = label.substring(1); this.setPlayerTarget(playerId, player, label.substring(1));
return;
} else if (label.equalsIgnoreCase("target")) { // target [[@]UID] } else if (label.equalsIgnoreCase("target")) { // target [[@]UID]
if (args.size() > 0) { if (args.size() > 0) {
targetUidStr = args.get(0); String targetUidStr = args.get(0);
if (targetUidStr.startsWith("@")) { if (targetUidStr.startsWith("@")) {
targetUidStr = targetUidStr.substring(1); targetUidStr = targetUidStr.substring(1);
} }
} else { this.setPlayerTarget(playerId, player, targetUidStr);
targetUidStr = "";
}
}
if (targetUidStr != null) {
if (targetUidStr.equals("")) { // Clears the default targetPlayer.
this.targetPlayerIds.remove(playerId);
CommandHandler.sendTranslatedMessage(player, "commands.execution.clear_target");
} else { // Sets default targetPlayer to the UID provided.
try {
int uid = Integer.parseInt(targetUidStr);
targetPlayer = Grasscutter.getGameServer().getPlayerByUid(uid, true);
if (targetPlayer == null) {
CommandHandler.sendTranslatedMessage(player, "commands.execution.player_exist_error");
} else {
this.targetPlayerIds.put(playerId, uid);
CommandHandler.sendTranslatedMessage(player, "commands.execution.set_target", targetUidStr);
CommandHandler.sendTranslatedMessage(player, targetPlayer.isOnline() ? "commands.execution.set_target_online" : "commands.execution.set_target_offline", targetUidStr);
}
} catch (NumberFormatException e) {
CommandHandler.sendTranslatedMessage(player, "commands.generic.invalid.uid");
}
}
return; return;
} else {
this.setPlayerTarget(playerId, player, "");
return;
}
} }
// Get command handler. // Get command handler.
@ -179,39 +234,12 @@ public final class CommandMap {
// Get the command's annotation. // Get the command's annotation.
Command annotation = this.annotations.get(label); Command annotation = this.annotations.get(label);
// If any @UID argument is present, override targetPlayer with it. // Resolve targetPlayer
for (int i = 0; i < args.size(); i++) { try{
String arg = args.get(i); targetPlayer = getTargetPlayer(playerId, player, targetPlayer, args);
if (arg.startsWith("@")) { } catch (IllegalArgumentException e) {
arg = args.remove(i).substring(1);
try {
int uid = Integer.parseInt(arg);
targetPlayer = Grasscutter.getGameServer().getPlayerByUid(uid, true);
if (targetPlayer == null) {
CommandHandler.sendTranslatedMessage(player, "commands.execution.player_exist_error");
return; return;
} }
break;
} catch (NumberFormatException e) {
CommandHandler.sendTranslatedMessage(player, "commands.generic.invalid.uid");
return;
}
}
}
// If there's still no targetPlayer at this point, use previously-set target
if (targetPlayer == null) {
if (this.targetPlayerIds.containsKey(playerId)) {
targetPlayer = Grasscutter.getGameServer().getPlayerByUid(this.targetPlayerIds.get(playerId), true); // We check every time in case the target is deleted after being targeted
if (targetPlayer == null) {
CommandHandler.sendTranslatedMessage(player, "commands.execution.player_exist_error");
return;
}
} else {
// If there's still no targetPlayer at this point, use executor.
targetPlayer = player;
}
}
// Check for permissions. // Check for permissions.
if (!Grasscutter.getPermissionHandler().checkPermission(player, targetPlayer, annotation.permission(), this.annotations.get(label).permissionTargeted())) { if (!Grasscutter.getPermissionHandler().checkPermission(player, targetPlayer, annotation.permission(), this.annotations.get(label).permissionTargeted())) {

View File

@ -2,37 +2,27 @@ package emu.grasscutter.command.commands;
import java.util.List; import java.util.List;
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.database.DatabaseHelper;
import emu.grasscutter.game.Account; import emu.grasscutter.game.Account;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
import emu.grasscutter.server.game.GameSession;
import static emu.grasscutter.utils.Language.translate;
@Command( @Command(
label = "ban", label = "ban",
usage = "ban <player> [time] [reason]", usage = "ban <@player> [time] [reason]",
description = "commands.ban.description", description = "commands.ban.description",
targetRequirement = Command.TargetRequirement.NONE permission = "server.ban",
targetRequirement = Command.TargetRequirement.PLAYER
) )
public final class BanCommand implements CommandHandler { public final class BanCommand implements CommandHandler {
private boolean banAccount(int uid, int time, String reason) { private boolean banAccount(Player targetPlayer, int time, String reason) {
Player player = Grasscutter.getGameServer().getPlayerByUid(uid, true); Account account = targetPlayer.getAccount();
if (player == null) {
return false;
}
Account account = player.getAccount();
if (account == null) {
account = DatabaseHelper.getAccountByPlayerId(uid);
if (account == null) { if (account == null) {
return false; return false;
} }
}
account.setBanReason(reason); account.setBanReason(reason);
account.setBanEndTime(time); account.setBanEndTime(time);
@ -40,51 +30,36 @@ public final class BanCommand implements CommandHandler {
account.setBanned(true); account.setBanned(true);
account.save(); account.save();
Player banUser = Grasscutter.getGameServer().getPlayerByUid(uid); GameSession session = targetPlayer.getSession();
if (session != null) {
if (banUser != null) { session.close();
banUser.getSession().close();
} }
return true; return true;
} }
@Override @Override
public void execute(Player sender, Player targetPlayer, List<String> args) { public void execute(Player sender, Player targetPlayer, List<String> args) {
if (args.size() < 1) {
CommandHandler.sendMessage(sender, translate(sender, "commands.ban.command_usage"));
return;
}
int uid = 0;
int time = 2051190000; int time = 2051190000;
String reason = "Reason not specified."; String reason = "Reason not specified.";
if (args.size() >= 1) { switch (args.size()) {
case 2:
reason = args.get(1); // Fall-through
case 1:
try { try {
uid = Integer.parseInt(args.get(0)); time = Integer.parseInt(args.get(0));
} catch (NumberFormatException ignored) { } catch (NumberFormatException ignored) {
CommandHandler.sendMessage(sender, translate(sender, "commands.ban.invalid_player_id")); CommandHandler.sendTranslatedMessage(sender, "commands.ban.invalid_time");
return; return;
} } // Fall-through, unimportant
default:
break;
} }
if (args.size() >= 2) { if (banAccount(targetPlayer, time, reason)) {
try { CommandHandler.sendTranslatedMessage(sender, "commands.ban.success");
time = Integer.parseInt(args.get(1));
} catch (NumberFormatException ignored) {
CommandHandler.sendMessage(sender, translate(sender, "commands.ban.invalid_time"));
return;
}
}
if (args.size() >= 3) {
reason = args.get(2);
}
if (banAccount(uid, time, reason)) {
CommandHandler.sendMessage(sender, translate(sender, "commands.ban.success"));
} else { } else {
CommandHandler.sendMessage(sender, translate(sender, "commands.ban.failure")); CommandHandler.sendTranslatedMessage(sender, "commands.ban.failure");
} }
} }
} }

View File

@ -1,30 +0,0 @@
package emu.grasscutter.command.commands;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.player.Player;
import java.util.List;
import static emu.grasscutter.utils.Language.translate;
@Command(label = "broadcast", usage = "broadcast <message>", aliases = {"b"}, permission = "server.broadcast", description = "commands.broadcast.description", targetRequirement = Command.TargetRequirement.NONE)
public final class BroadcastCommand implements CommandHandler {
@Override
public void execute(Player sender, Player targetPlayer, List<String> args) {
if (args.size() < 1) {
CommandHandler.sendMessage(sender, translate(sender, "commands.broadcast.command_usage"));
return;
}
String message = String.join(" ", args.subList(0, args.size()));
for (Player p : Grasscutter.getGameServer().getPlayers().values()) {
CommandHandler.sendMessage(p, message);
}
CommandHandler.sendMessage(sender, translate(sender, "commands.broadcast.message_sent"));
}
}

View File

@ -1,39 +0,0 @@
package emu.grasscutter.command.commands;
import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.player.Player;
import java.util.List;
import static emu.grasscutter.utils.Language.translate;
@Command(label = "changescene", usage = "changescene <sceneId>", aliases = {"scene"}, permission = "player.changescene", permissionTargeted = "player.changescene.others", description = "commands.changescene.description")
public final class ChangeSceneCommand implements CommandHandler {
@Override
public void execute(Player sender, Player targetPlayer, List<String> args) {
if (args.size() != 1) {
CommandHandler.sendMessage(sender, translate(sender, "commands.changescene.usage"));
return;
}
try {
int sceneId = Integer.parseInt(args.get(0));
if (sceneId == targetPlayer.getSceneId()) {
CommandHandler.sendMessage(sender, translate(sender, "commands.changescene.already_in_scene"));
return;
}
boolean result = targetPlayer.getWorld().transferPlayerToScene(targetPlayer, sceneId, targetPlayer.getPos());
if (!result) {
CommandHandler.sendMessage(sender, translate(sender, "commands.changescene.exists_error"));
return;
}
CommandHandler.sendMessage(sender, translate(sender, "commands.changescene.success", Integer.toString(sceneId)));
} catch (Exception e) {
CommandHandler.sendMessage(sender, translate(sender, "commands.execution.argument_error"));
}
}
}

View File

@ -1,63 +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.entity.EntityItem;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.utils.Position;
import java.util.List;
import static emu.grasscutter.utils.Language.translate;
@Command(label = "drop", usage = "drop <itemId|itemName> [amount]", aliases = {"d", "dropitem"}, permission = "server.drop", permissionTargeted = "server.drop.others", description = "commands.drop.description")
public final class DropCommand implements CommandHandler {
@Override
public void execute(Player sender, Player targetPlayer, List<String> args) {
int item = 0;
int amount = 1;
switch (args.size()) {
case 2:
try {
amount = Integer.parseInt(args.get(1));
} catch (NumberFormatException ignored) {
CommandHandler.sendMessage(sender, translate(sender, "commands.generic.invalid.amount"));
return;
} // Slightly cheeky here: no break, so it falls through to initialize the first argument too
case 1:
try {
item = Integer.parseInt(args.get(0));
} catch (NumberFormatException ignored) {
CommandHandler.sendMessage(sender, translate(sender, "commands.generic.invalid.itemId"));
return;
}
break;
default:
CommandHandler.sendMessage(sender, translate(sender, "commands.drop.command_usage"));
return;
}
ItemData itemData = GameData.getItemDataMap().get(item);
if (itemData == null) {
CommandHandler.sendMessage(sender, translate(sender, "commands.generic.invalid.itemId"));
return;
}
if (itemData.isEquip()) {
float range = (5f + (.1f * amount));
for (int i = 0; i < amount; i++) {
Position pos = targetPlayer.getPos().clone().addX((float) (Math.random() * range) - (range / 2)).addY(3f).addZ((float) (Math.random() * range) - (range / 2));
EntityItem entity = new EntityItem(targetPlayer.getScene(), targetPlayer, itemData, pos, 1);
targetPlayer.getScene().addEntity(entity);
}
} else {
EntityItem entity = new EntityItem(targetPlayer.getScene(), targetPlayer, itemData, targetPlayer.getPos().clone().addY(3f), amount);
targetPlayer.getScene().addEntity(entity);
}
CommandHandler.sendMessage(sender, translate(sender, "commands.drop.success", Integer.toString(amount), Integer.toString(item)));
}
}

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; 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 {
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
}
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(); 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 {
} }
} }
switch (args.size()) { // At this point, first remaining argument MUST be itemId/avatarId
case 4: // <itemId|itemName> [amount] [level] [refinement] if (args.size() < 1) {
try { CommandHandler.sendTranslatedMessage(sender, "commands.give.usage"); // Reachable if someone does `/give lv90` or similar
refinement = Integer.parseInt(args.get(3)); throw new IllegalArgumentException();
} 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;
} }
String id = args.remove(0);
boolean isRelic = false;
switch (id) {
case "all":
param.giveAllType = GiveAllType.ALL;
break; break;
default: // *No args* case "weapons":
CommandHandler.sendMessage(sender, translate(sender, "commands.give.usage")); param.giveAllType = GiveAllType.WEAPONS;
return; 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 (param.amount < 1) param.amount = 1;
if (itemData == null) { if (param.refinement < 1) param.refinement = 1;
CommandHandler.sendMessage(sender, translate(sender, "commands.generic.invalid.itemId")); if (param.refinement > 5) param.refinement = 5;
return; if (isRelic) {
} // Input 0-20 to match game, instead of 1-21 which is the real level
if (refinement != 0) { if (param.lvl < 0) param.lvl = 0;
if (itemData.getItemType() == ItemType.ITEM_WEAPON) { if (param.lvl > 20) param.lvl = 20;
if (refinement < 1 || refinement > 5) { param.lvl += 1;
CommandHandler.sendMessage(sender, translate(sender, "commands.give.refinement_must_between_1_and_5")); if (illegalRelicIds.contains(param.id))
return; CommandHandler.sendTranslatedMessage(sender, "commands.give.illegal_relic");
}
} else { } else {
CommandHandler.sendMessage(sender, translate(sender, "commands.give.refinement_only_applicable_weapons")); // 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;
}
try {
GiveItemParameters param = parseArgs(sender, args);
switch (param.giveAllType) {
case ALL:
giveAll(targetPlayer, param);
CommandHandler.sendTranslatedMessage(sender, "commands.give.giveall_success");
return;
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; 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()) { private static Avatar makeAvatar(AvatarData avatarData, int level, int promoteLevel, int constellation) {
CommandHandler.sendMessage(sender, translate(sender, "commands.give.given", Integer.toString(amount), Integer.toString(item), Integer.toString(targetPlayer.getUid()))); // Calculate ascension level.
} else if (itemData.getItemType() == ItemType.ITEM_WEAPON) { Avatar avatar = new Avatar(avatarData);
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()))); avatar.setLevel(level);
} else { avatar.setPromoteLevel(promoteLevel);
CommandHandler.sendMessage(sender, translate(sender, "commands.give.given_level", Integer.toString(item), Integer.toString(lvl), Integer.toString(amount), Integer.toString(targetPlayer.getUid())));
// 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) { private static List<GameItem> makeUnstackableItems(GiveItemParameters param) {
if (itemData.isEquip()) { int promoteLevel = GameItem.getMinPromoteLevel(param.lvl);
List<GameItem> items = new LinkedList<>(); int totalExp = 0;
for (int i = 0; i < amount; i++) { if (param.data.getItemType() == ItemType.ITEM_WEAPON) {
GameItem item = new GameItem(itemData); int rankLevel = param.data.getRankLevel();
if (item.isEquipped()) { for (int i = 1; i < param.lvl; i++)
// check item max level 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 (lvl > 90) lvl = 90; item.setPromoteLevel(promoteLevel);
} else { item.setTotalExp(totalExp);
if (lvl > 21) lvl = 21; item.setRefinement(param.refinement - 1); // Actual refinement data is 0..4 not 1..5
}
}
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); items.add(item);
} }
player.getInventory().addItems(items, ActionReason.SubfieldDrop); return items;
} else { }
GameItem item = new GameItem(itemData);
item.setCount(amount); private static List<GameItem> makeArtifacts(GiveItemParameters param) {
player.getInventory().addItem(item, ActionReason.SubfieldDrop); 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

@ -1,35 +0,0 @@
package emu.grasscutter.command.commands;
import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.player.Player;
import java.util.List;
import static emu.grasscutter.utils.Language.translate;
@Command(label = "godmode", usage = "godmode [on|off|toggle]", permission = "player.godmode", permissionTargeted = "player.godmode.others", description = "commands.godmode.description")
public final class GodModeCommand implements CommandHandler {
@Override
public void execute(Player sender, Player targetPlayer, List<String> args) {
boolean enabled = !targetPlayer.inGodmode();
if (args.size() == 1) {
switch (args.get(0).toLowerCase()) {
case "on":
enabled = true;
break;
case "off":
enabled = false;
break;
case "toggle":
break; // Already toggled
default:
break;
}
}
targetPlayer.setGodmode(enabled);
CommandHandler.sendMessage(sender, translate(sender, "commands.godmode.success", (enabled ? translate(sender, "commands.status.enabled") : translate(sender, "commands.status.disabled")), targetPlayer.getNickname()));
}
}

View File

@ -8,7 +8,7 @@ import java.util.List;
import static emu.grasscutter.utils.Language.translate; import static emu.grasscutter.utils.Language.translate;
@Command(label = "kick", usage = "kick", permission = "server.kick", description = "commands.kick.description") @Command(label = "kick", usage = "kick", aliases = {"restart"}, permissionTargeted = "server.kick", description = "commands.kick.description")
public final class KickCommand implements CommandHandler { public final class KickCommand implements CommandHandler {
@Override @Override

View File

@ -1,34 +0,0 @@
package emu.grasscutter.command.commands;
import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.player.Player;
import java.util.List;
import static emu.grasscutter.utils.Language.translate;
@Command(label = "nostamina", usage = "nostamina [on|off|toggle]", aliases = {"ns"}, permission = "player.nostamina", permissionTargeted = "player.nostamina.others", description = "commands.nostamina.description")
public final class NoStaminaCommand implements CommandHandler {
@Override
public void execute(Player sender, Player targetPlayer, List<String> args) {
boolean stamina = !targetPlayer.getStamina();
if (args.size() == 1) {
switch (args.get(0).toLowerCase()) {
case "on":
stamina = true;
break;
case "off":
stamina = false;
break;
default:
// toggled
break;
}
}
targetPlayer.setStamina(stamina); //Set
CommandHandler.sendMessage(sender, translate(sender, "commands.nostamina.success", (stamina ? translate(sender, "commands.status.enabled") : translate(sender, "commands.status.disabled")), targetPlayer.getNickname()));
}
}

View File

@ -3,6 +3,7 @@ package emu.grasscutter.command.commands;
import emu.grasscutter.Grasscutter; 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.command.Command.TargetRequirement;
import emu.grasscutter.game.Account; import emu.grasscutter.game.Account;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
@ -10,7 +11,7 @@ import java.util.List;
import static emu.grasscutter.utils.Language.translate; import static emu.grasscutter.utils.Language.translate;
@Command(label = "permission", usage = "permission <add|remove> <permission>", permission = "permission", description = "commands.permission.description") @Command(label = "permission", usage = "permission <add|remove> <permission>", permission = "permission", description = "commands.permission.description", targetRequirement = TargetRequirement.PLAYER)
public final class PermissionCommand implements CommandHandler { public final class PermissionCommand implements CommandHandler {
@Override @Override

View File

@ -1,21 +0,0 @@
package emu.grasscutter.command.commands;
import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.player.Player;
import java.util.List;
import static emu.grasscutter.utils.Language.translate;
@Command(label = "restart", usage = "restart", description = "commands.restart.description", targetRequirement = Command.TargetRequirement.NONE)
public final class RestartCommand implements CommandHandler {
@Override
public void execute(Player sender, Player targetPlayer, List<String> args) {
if (sender == null) {
return;
}
sender.getSession().close();
}
}

View File

@ -1,7 +1,9 @@
package emu.grasscutter.command.commands; package emu.grasscutter.command.commands;
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.command.Command.TargetRequirement;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
import java.util.List; import java.util.List;
@ -9,7 +11,7 @@ import java.util.List;
import static emu.grasscutter.utils.Language.translate; import static emu.grasscutter.utils.Language.translate;
@Command(label = "sendmessage", usage = "sendmessage <message>", @Command(label = "sendmessage", usage = "sendmessage <message>",
aliases = {"say", "sendservmsg", "sendservermessage"}, permission = "server.sendmessage", permissionTargeted = "server.sendmessage.others", description = "commands.sendMessage.description") aliases = {"say", "sendservmsg", "sendservermessage", "b", "broadcast"}, permission = "server.sendmessage", permissionTargeted = "server.sendmessage.others", description = "commands.sendMessage.description", targetRequirement = TargetRequirement.NONE)
public final class SendMessageCommand implements CommandHandler { public final class SendMessageCommand implements CommandHandler {
@Override @Override
@ -20,7 +22,14 @@ public final class SendMessageCommand implements CommandHandler {
} }
String message = String.join(" ", args); String message = String.join(" ", args);
if (targetPlayer == null) {
for (Player p : Grasscutter.getGameServer().getPlayers().values()) {
CommandHandler.sendMessage(p, message);
}
} else {
CommandHandler.sendMessage(targetPlayer, message); CommandHandler.sendMessage(targetPlayer, message);
}
CommandHandler.sendMessage(sender, translate(sender, "commands.sendMessage.success")); CommandHandler.sendMessage(sender, translate(sender, "commands.sendMessage.success"));
} }
} }

View File

@ -1,23 +0,0 @@
package emu.grasscutter.command.commands;
import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.player.Player;
import java.util.List;
@Command(label = "setbp", usage = "", aliases = "bp",permission = "player.setbp", description = "")
public final class SetBPLevelCommand implements CommandHandler {
@Override
public void execute(Player sender, Player targetPlayer, List<String> args) {
if (args.size() < 1) {
CommandHandler.sendMessage(sender , "Need a arg");
return;
}
int level = Integer.parseInt(args.get(0));
sender.getBattlePassManager().addPoint(level);
sender.getBattlePassManager().updateAwardTakenLevel(0);
}
}

View File

@ -0,0 +1,205 @@
package emu.grasscutter.command.commands;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.PlayerProperty;
import emu.grasscutter.game.tower.TowerLevelRecord;
@Command(label = "setprop", usage = "setprop|prop <prop> <value>", aliases = {"prop"}, permission = "player.setprop", permissionTargeted = "player.setprop.others", description = "commands.setProp.description")
public final class SetPropCommand implements CommandHandler {
static enum PseudoProp {
NONE,
WORLD_LEVEL,
TOWER_LEVEL,
BP_LEVEL,
GOD_MODE,
NO_STAMINA,
UNLIMITED_ENERGY
}
static class Prop {
String name;
PlayerProperty prop;
PseudoProp pseudoProp;
public Prop(PlayerProperty prop) {
this(prop.toString(), prop, PseudoProp.NONE);
}
public Prop(String name) {
this(name, PlayerProperty.PROP_NONE, PseudoProp.NONE);
}
public Prop(String name, PseudoProp pseudoProp) {
this(name, PlayerProperty.PROP_NONE, pseudoProp);
}
public Prop(String name, PlayerProperty prop) {
this(name, prop, PseudoProp.NONE);
}
public Prop(String name, PlayerProperty prop, PseudoProp pseudoProp) {
this.name = name;
this.prop = prop;
this.pseudoProp = pseudoProp;
}
}
Map<String, Prop> props;
public SetPropCommand() {
this.props = new HashMap<>();
// Full PlayerProperty enum that won't be advertised but can be used by devs
for (PlayerProperty prop : PlayerProperty.values()) {
String name = prop.toString().substring(5); // PROP_EXP -> EXP
String key = name.toLowerCase(); // EXP -> exp
this.props.put(key, new Prop(name, prop));
}
// Add special props
Prop worldlevel = new Prop("World Level", PlayerProperty.PROP_PLAYER_WORLD_LEVEL, PseudoProp.WORLD_LEVEL);
this.props.put("worldlevel", worldlevel);
this.props.put("wl", worldlevel);
Prop abyss = new Prop("Tower Level", PseudoProp.TOWER_LEVEL);
this.props.put("abyss", abyss);
this.props.put("abyssfloor", abyss);
this.props.put("ut", abyss);
this.props.put("tower", abyss);
this.props.put("towerlevel", abyss);
this.props.put("unlocktower", abyss);
Prop bplevel = new Prop("BP Level", PseudoProp.BP_LEVEL);
this.props.put("bplevel", bplevel);
this.props.put("bp", bplevel);
this.props.put("battlepass", bplevel);
Prop godmode = new Prop("godmode", PseudoProp.GOD_MODE);
this.props.put("godmode", godmode);
this.props.put("god", godmode);
Prop nostamina = new Prop("nostamina", PseudoProp.NO_STAMINA);
this.props.put("nostamina", nostamina);
this.props.put("nostam", nostamina);
this.props.put("ns", nostamina);
Prop unlimitedenergy = new Prop("unlimitedenergy", PseudoProp.UNLIMITED_ENERGY);
this.props.put("unlimitedenergy", unlimitedenergy);
this.props.put("ue", unlimitedenergy);
}
@Override
public void execute(Player sender, Player targetPlayer, List<String> args) {
if (args.size() != 2) {
CommandHandler.sendTranslatedMessage(sender, "commands.setProp.usage");
return;
}
String propStr = args.get(0).toLowerCase();
String valueStr = args.get(1).toLowerCase();
int value;
if (!props.containsKey(propStr)) {
CommandHandler.sendTranslatedMessage(sender, "commands.setProp.usage");
return;
}
try {
value = switch(valueStr.toLowerCase()) {
case "on", "true" -> 1;
case "off", "false" -> 0;
case "toggle" -> -1;
default -> Integer.parseInt(valueStr);
};
} catch (NumberFormatException ignored) {
CommandHandler.sendTranslatedMessage(sender, "commands.execution.argument_error");
return;
}
boolean success = false;
Prop prop = props.get(propStr);
success = switch (prop.pseudoProp) {
case WORLD_LEVEL -> targetPlayer.setWorldLevel(value);
case BP_LEVEL -> targetPlayer.getBattlePassManager().setLevel(value);
case TOWER_LEVEL -> this.setTowerLevel(sender, targetPlayer, value);
case GOD_MODE, NO_STAMINA, UNLIMITED_ENERGY -> this.setBool(sender, targetPlayer, prop.pseudoProp, value);
default -> targetPlayer.setProperty(prop.prop, value);
};
if (success) {
if (targetPlayer == sender) {
CommandHandler.sendTranslatedMessage(sender, "commands.generic.set_to", prop.name, valueStr);
} else {
String uidStr = targetPlayer.getAccount().getId();
CommandHandler.sendTranslatedMessage(sender, "commands.generic.set_for_to", prop.name, uidStr, valueStr);
}
} else {
if (prop.prop != PlayerProperty.PROP_NONE) { // PseudoProps need to do their own error messages
String min = Integer.toString(targetPlayer.getPropertyMin(prop.prop));
String max = Integer.toString(targetPlayer.getPropertyMax(prop.prop));
CommandHandler.sendTranslatedMessage(sender, "commands.generic.invalid.value_between", prop.name, min, max);
}
}
}
private boolean setTowerLevel(Player sender, Player targetPlayer, int topFloor) {
List<Integer> floorIds = targetPlayer.getServer().getTowerScheduleManager().getAllFloors();
if (topFloor < 0 || topFloor > floorIds.size()) {
String min = Integer.toString(0);
String max = Integer.toString(floorIds.size());
CommandHandler.sendTranslatedMessage(sender, "commands.generic.invalid.value_between", "Tower Level", min, max);
return false;
}
Map<Integer, TowerLevelRecord> recordMap = targetPlayer.getTowerManager().getRecordMap();
// Add records for each unlocked floor
for (int floor : floorIds.subList(0, topFloor)) {
if (!recordMap.containsKey(floor)) {
recordMap.put(floor, new TowerLevelRecord(floor));
}
}
// Remove records for each floor past our target
for (int floor : floorIds.subList(topFloor, floorIds.size())) {
if (recordMap.containsKey(floor)) {
recordMap.remove(floor);
}
}
// Six stars required on Floor 8 to unlock Floor 9+
if (topFloor > 8) {
recordMap.get(floorIds.get(7)).setLevelStars(0, 6); // levelIds seem to start at 1 for Floor 1 Chamber 1, so this doesn't get shown at all
}
return true;
}
private boolean setBool(Player sender, Player targetPlayer, PseudoProp pseudoProp, int value) {
boolean enabled = switch (pseudoProp) {
case GOD_MODE -> targetPlayer.inGodmode();
case NO_STAMINA -> targetPlayer.getUnlimitedStamina();
case UNLIMITED_ENERGY -> !targetPlayer.getEnergyManager().getEnergyUsage();
default -> false;
};
enabled = switch (value) {
case -1 -> !enabled;
case 0 -> false;
default -> true;
};
switch (pseudoProp) {
case GOD_MODE:
targetPlayer.setGodmode(enabled);
break;
case NO_STAMINA:
targetPlayer.setUnlimitedStamina(enabled);
break;
case UNLIMITED_ENERGY:
targetPlayer.getEnergyManager().setEnergyUsage(!enabled);
break;
default:
return false;
}
return true;
}
}

View File

@ -4,179 +4,66 @@ 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;
@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")
public final class SetStatsCommand implements CommandHandler { 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
public void execute(Player sender, Player targetPlayer, List<String> args) { public void execute(Player sender, Player targetPlayer, List<String> args) {
String syntax = sender == null ? translate(sender, "commands.setStats.usage_console") : translate(sender, "commands.setStats.usage_ingame");
String usage = syntax + translate(sender, "commands.setStats.help_message");
String statStr; String statStr;
String valueStr; String valueStr;
@ -184,7 +71,7 @@ public final class SetStatsCommand implements CommandHandler {
statStr = args.get(0).toLowerCase(); statStr = args.get(0).toLowerCase();
valueStr = args.get(1); valueStr = args.get(1);
} else { } else {
CommandHandler.sendMessage(sender, usage); CommandHandler.sendTranslatedMessage(sender, "commands.setStats.usage");
return; return;
} }
@ -198,7 +85,7 @@ public final class SetStatsCommand implements CommandHandler {
value = Float.parseFloat(valueStr); value = Float.parseFloat(valueStr);
} }
} catch (NumberFormatException ignored) { } catch (NumberFormatException ignored) {
CommandHandler.sendMessage(sender, translate(sender, "commands.setStats.value_error")); CommandHandler.sendTranslatedMessage(sender, "commands.generic.invalid.statValue");
return; return;
} }
@ -206,19 +93,19 @@ 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);
} }
if (targetPlayer == sender) { if (targetPlayer == sender) {
CommandHandler.sendMessage(sender, translate(sender, "commands.setStats.set_self", stat.name, valueStr)); CommandHandler.sendTranslatedMessage(sender, "commands.generic.set_to", stat.name, valueStr);
} else { } else {
String uidStr = targetPlayer.getAccount().getId(); String uidStr = targetPlayer.getAccount().getId();
CommandHandler.sendMessage(sender, translate(sender, "commands.setStats.set_for_uid", stat.name, uidStr, valueStr)); CommandHandler.sendTranslatedMessage(sender, "commands.generic.set_for_to", stat.name, uidStr, valueStr);
} }
} else { } else {
CommandHandler.sendMessage(sender, usage); CommandHandler.sendTranslatedMessage(sender, "commands.setStats.usage");
} }
return; return;
} }

View File

@ -1,39 +0,0 @@
package emu.grasscutter.command.commands;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.player.Player;
import java.util.List;
import static emu.grasscutter.utils.Language.translate;
@Command(label = "setworldlevel", usage = "setworldlevel <level>",
aliases = {"setworldlvl"}, permission = "player.setworldlevel", permissionTargeted = "player.setworldlevel.others", description = "commands.setWorldLevel.description")
public final class SetWorldLevelCommand implements CommandHandler {
@Override
public void execute(Player sender, Player targetPlayer, List<String> args) {
if (args.size() < 1) {
CommandHandler.sendMessage(sender, translate(sender, "commands.setWorldLevel.usage"));
return;
}
try {
int level = Integer.parseInt(args.get(0));
if (level > 8 || level < 0) {
CommandHandler.sendMessage(sender, translate(sender, "commands.setWorldLevel.value_error"));
return;
}
// Set in both world and player props
targetPlayer.getWorld().setWorldLevel(level);
targetPlayer.setWorldLevel(level);
CommandHandler.sendMessage(sender, translate(sender, "commands.setWorldLevel.success", Integer.toString(level)));
} catch (NumberFormatException ignored) {
CommandHandler.sendMessage(null, translate(sender, "commands.setWorldLevel.invalid_world_level"));
}
}
}

View File

@ -23,7 +23,7 @@ import java.util.Random;
import static emu.grasscutter.Configuration.*; import static emu.grasscutter.Configuration.*;
import static emu.grasscutter.utils.Language.translate; import static emu.grasscutter.utils.Language.translate;
@Command(label = "spawn", usage = "spawn <entityId> [amount] [level(monster only)] [<x> <y> <z>(monster only, optional)]", permission = "server.spawn", permissionTargeted = "server.spawn.others", description = "commands.spawn.description") @Command(label = "spawn", usage = "spawn <entityId> [amount] [level(monster only)] [<x> <y> <z>(monster only, optional)]", aliases = {"drop"}, permission = "server.spawn", permissionTargeted = "server.spawn.others", description = "commands.spawn.description")
public final class SpawnCommand implements CommandHandler { public final class SpawnCommand implements CommandHandler {
@Override @Override

View File

@ -56,7 +56,7 @@ public final class TeleportCommand implements CommandHandler {
Position target_pos = new Position(x, y, z); Position target_pos = new Position(x, y, z);
boolean result = targetPlayer.getWorld().transferPlayerToScene(targetPlayer, sceneId, target_pos); boolean result = targetPlayer.getWorld().transferPlayerToScene(targetPlayer, sceneId, target_pos);
if (!result) { if (!result) {
CommandHandler.sendMessage(sender, translate(sender, "commands.teleport.invalid_position")); CommandHandler.sendMessage(sender, translate(sender, "commands.teleport.exists_error"));
} else { } else {
CommandHandler.sendMessage(sender, translate(sender, "commands.teleport.success", CommandHandler.sendMessage(sender, translate(sender, "commands.teleport.success",
targetPlayer.getNickname(), Float.toString(x), Float.toString(y), targetPlayer.getNickname(), Float.toString(x), Float.toString(y),

View File

@ -2,38 +2,26 @@ package emu.grasscutter.command.commands;
import java.util.List; import java.util.List;
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.database.DatabaseHelper;
import emu.grasscutter.game.Account; import emu.grasscutter.game.Account;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
import static emu.grasscutter.utils.Language.translate;
@Command( @Command(
label = "unban", label = "unban",
usage = "unban <player>", usage = "unban <@player>",
description = "commands.unban.description", description = "commands.unban.description",
targetRequirement = Command.TargetRequirement.NONE permission = "server.ban",
targetRequirement = Command.TargetRequirement.PLAYER
) )
public final class UnBanCommand implements CommandHandler { public final class UnBanCommand implements CommandHandler {
private boolean unBanAccount(int uid) { private boolean unBanAccount(Player targetPlayer) {
Player player = Grasscutter.getGameServer().getPlayerByUid(uid, true); Account account = targetPlayer.getAccount();
if (player == null) {
return false;
}
Account account = player.getAccount();
if (account == null) {
account = DatabaseHelper.getAccountByPlayerId(uid);
if (account == null) { if (account == null) {
return false; return false;
} }
}
account.setBanReason(null); account.setBanReason(null);
account.setBanEndTime(0); account.setBanEndTime(0);
@ -46,24 +34,10 @@ public final class UnBanCommand implements CommandHandler {
@Override @Override
public void execute(Player sender, Player targetPlayer, List<String> args) { public void execute(Player sender, Player targetPlayer, List<String> args) {
if (args.size() < 1) { if (unBanAccount(targetPlayer)) {
CommandHandler.sendMessage(sender, translate(sender, "commands.unban.command_usage")); CommandHandler.sendTranslatedMessage(sender, "commands.unban.success");
return;
}
int uid = 0;
try {
uid = Integer.parseInt(args.get(0));
} catch (NumberFormatException ignored) {
CommandHandler.sendMessage(sender, translate(sender, "commands.unban.invalid_player_id"));
return;
}
if (unBanAccount(uid)) {
CommandHandler.sendMessage(sender, translate(sender, "commands.unban.success"));
} else { } else {
CommandHandler.sendMessage(sender, translate(sender, "commands.unban.failure")); CommandHandler.sendTranslatedMessage(sender, "commands.unban.failure");
} }
} }
} }

View File

@ -1,55 +0,0 @@
package emu.grasscutter.command.commands;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.avatar.Avatar;
import emu.grasscutter.game.entity.EntityAvatar;
import emu.grasscutter.game.managers.energy.EnergyManager;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.player.TeamManager;
import emu.grasscutter.game.props.ElementType;
import emu.grasscutter.net.proto.PropChangeReasonOuterClass;
import emu.grasscutter.utils.Position;
import java.util.List;
import static emu.grasscutter.Configuration.GAME_OPTIONS;
import static emu.grasscutter.utils.Language.translate;
@Command(label = "unlimitenergy", usage = "unlimitenergy [on|off|toggle]", aliases = {"ule"}, permission = "player.unlimitenergy", permissionTargeted = "player.unlimitenergy.others", description = "commands.unlimitenergy.description")
public final class UnlimitEnergyCommand implements CommandHandler {
@Override
public void execute(Player sender, Player targetPlayer, List<String> args) {
if(!GAME_OPTIONS.energyUsage){
CommandHandler.sendMessage(sender, translate(sender, "commands.unlimitenergy.config_error"));
return;
}
Boolean status = targetPlayer.getEnergyManager().getEnergyUsage();
if (args.size() == 1) {
switch (args.get(0).toLowerCase()) {
case "on":
status = true;
break;
case "off":
status = false;
break;
default:
status = !status;
break;
}
}
EnergyManager energyManager=targetPlayer.getEnergyManager();
energyManager.setEnergyUsage(!status);
// if unlimitEnergy is enable , make currentActiveTeam's Avatar full-energy
if (status) {
for (EntityAvatar entityAvatar : targetPlayer.getTeamManager().getActiveTeam()) {
entityAvatar.addEnergy(1000,
PropChangeReasonOuterClass.PropChangeReason.PROP_CHANGE_REASON_GM,true);
}
}
CommandHandler.sendMessage(sender, translate(sender, "commands.unlimitenergy.success", (status ? translate(sender, "commands.status.enabled") : translate(sender, "commands.status.disabled")), targetPlayer.getNickname()));
}
}

View File

@ -1,32 +0,0 @@
package emu.grasscutter.command.commands;
import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.tower.TowerLevelRecord;
import java.util.List;
import static emu.grasscutter.utils.Language.translate;
@Command(label = "unlocktower", usage = "unlocktower", aliases = {"ut"}, permission = "player.unlocktower", permissionTargeted = "player.unlocktower.others",
description = "commands.unlocktower.description")
public class UnlockTowerCommand implements CommandHandler {
@Override
public void execute(Player sender, Player targetPlayer, List<String> args) {
unlockFloor(targetPlayer, targetPlayer.getServer().getTowerScheduleManager()
.getCurrentTowerScheduleData().getEntranceFloorId());
unlockFloor(targetPlayer, targetPlayer.getServer().getTowerScheduleManager()
.getScheduleFloors());
CommandHandler.sendMessage(sender, translate(sender, "commands.unlocktower.success"));
}
public void unlockFloor(Player player, List<Integer> floors){
floors.stream()
.filter(id -> !player.getTowerManager().getRecordMap().containsKey(id))
.forEach(id -> player.getTowerManager().getRecordMap().put(id, new TowerLevelRecord(id)));
}
}

View File

@ -95,8 +95,8 @@ public class GameData {
private static final Int2ObjectMap<InvestigationMonsterData> investigationMonsterDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<InvestigationMonsterData> investigationMonsterDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<CityData> cityDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<CityData> cityDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<WeatherData> weatherDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<WeatherData> weatherDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<BattlePassMissionExcelConfigData> battlePassMissionExcelConfigDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<BattlePassMissionData> battlePassMissionDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<BattlePassRewardExcelConfigData> battlePassRewardExcelConfigDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<BattlePassRewardData> battlePassRewardDataMap = new Int2ObjectOpenHashMap<>();
// Cache // Cache
private static Map<Integer, List<Integer>> fetters = new HashMap<>(); private static Map<Integer, List<Integer>> fetters = new HashMap<>();
@ -424,11 +424,11 @@ public class GameData {
return weatherDataMap; return weatherDataMap;
} }
public static Int2ObjectMap<BattlePassMissionExcelConfigData> getBattlePassMissionExcelConfigDataMap() { public static Int2ObjectMap<BattlePassMissionData> getBattlePassMissionDataMap() {
return battlePassMissionExcelConfigDataMap; return battlePassMissionDataMap;
} }
public static Int2ObjectMap<BattlePassRewardExcelConfigData> getBattlePassRewardExcelConfigDataMap() { public static Int2ObjectMap<BattlePassRewardData> getBattlePassRewardDataMap() {
return battlePassRewardExcelConfigDataMap; return battlePassRewardDataMap;
} }
} }

View File

@ -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 {
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 {
} }
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);
} }

View File

@ -0,0 +1,73 @@
package emu.grasscutter.data.excels;
import java.util.Arrays;
import java.util.Set;
import java.util.stream.Collectors;
import emu.grasscutter.data.GameResource;
import emu.grasscutter.data.ResourceType;
import emu.grasscutter.game.props.BattlePassMissionRefreshType;
import emu.grasscutter.game.props.WatcherTriggerType;
import emu.grasscutter.net.proto.BattlePassMissionOuterClass.BattlePassMission.MissionStatus;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.FieldDefaults;
@ResourceType(name = {"BattlePassMissionExcelConfigData.json"})
@Getter
public class BattlePassMissionData extends GameResource {
private int addPoint;
private int id;
private int scheduleId;
private int progress;
private TriggerConfig triggerConfig;
private BattlePassMissionRefreshType refreshType;
private transient Set<Integer> mainParams;
@Override
public int getId() {
return this.id;
}
public WatcherTriggerType getTriggerType() {
return this.getTriggerConfig().getTriggerType();
}
public boolean isCycleRefresh() {
return getRefreshType() == null || getRefreshType() == BattlePassMissionRefreshType.BATTLE_PASS_MISSION_REFRESH_CYCLE_CROSS_SCHEDULE;
}
public boolean isValidRefreshType() {
return getRefreshType() == null ||
getRefreshType() == BattlePassMissionRefreshType.BATTLE_PASS_MISSION_REFRESH_CYCLE_CROSS_SCHEDULE ||
getScheduleId() == 2701;
}
@Override
public void onLoad() {
if (this.getTriggerConfig() != null && getTriggerConfig().getParamList()[0].length() > 0) {
this.mainParams = Arrays.stream(getTriggerConfig().getParamList()[0].split("[:;,]")).map(Integer::parseInt).collect(Collectors.toSet());
}
}
@Getter
public static class TriggerConfig {
private WatcherTriggerType triggerType;
private String[] paramList;
}
public emu.grasscutter.net.proto.BattlePassMissionOuterClass.BattlePassMission toProto() {
var protoBuilder = emu.grasscutter.net.proto.BattlePassMissionOuterClass.BattlePassMission.newBuilder();
protoBuilder
.setMissionId(getId())
.setTotalProgress(this.getProgress())
.setRewardBattlePassPoint(this.getAddPoint())
.setMissionStatus(MissionStatus.MISSION_STATUS_UNFINISHED)
.setMissionType(this.getRefreshType() == null ? 0 : this.getRefreshType().getValue());
return protoBuilder.build();
}
}

View File

@ -1,29 +0,0 @@
package emu.grasscutter.data.excels;
import emu.grasscutter.data.GameResource;
import emu.grasscutter.data.ResourceType;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.FieldDefaults;
@ResourceType(name = {"BattlePassMissionExcelConfigData.json"})
@FieldDefaults(level = AccessLevel.PRIVATE)
@Getter
@Setter
public class BattlePassMissionExcelConfigData extends GameResource {
private int addPoint;
private int id;
private int progress;
private String refreshType;
@Override
public void onLoad() {
}
@Override
public int getId() {
return this.id;
}
}

View File

@ -9,8 +9,7 @@ import java.util.List;
@ResourceType(name = "BattlePassRewardExcelConfigData.json") @ResourceType(name = "BattlePassRewardExcelConfigData.json")
@Getter @Getter
@Setter public class BattlePassRewardData extends GameResource {
public class BattlePassRewardExcelConfigData extends GameResource {
private int indexId; private int indexId;
private int level; private int level;
private List<Integer> freeRewardIdList; private List<Integer> freeRewardIdList;
@ -23,5 +22,6 @@ public class BattlePassRewardExcelConfigData extends GameResource {
@Override @Override
public void onLoad() { public void onLoad() {
} }
} }

View File

@ -6,7 +6,6 @@ import emu.grasscutter.data.GameResource;
import emu.grasscutter.data.ResourceType; import emu.grasscutter.data.ResourceType;
import emu.grasscutter.data.ResourceType.LoadPriority; import emu.grasscutter.data.ResourceType.LoadPriority;
import emu.grasscutter.data.common.ItemParamData; import emu.grasscutter.data.common.ItemParamData;
import emu.grasscutter.data.common.OpenCondData;
@ResourceType(name = {"ForgeExcelConfigData.json"}, loadPriority = LoadPriority.HIGHEST) @ResourceType(name = {"ForgeExcelConfigData.json"}, loadPriority = LoadPriority.HIGHEST)
public class ForgeData extends GameResource { public class ForgeData extends GameResource {
@ -19,6 +18,7 @@ public class ForgeData extends GameResource {
private int queueNum; private int queueNum;
private int scoinCost; private int scoinCost;
private int priority; private int priority;
private int forgePoint;
private List<ItemParamData> materialItems; private List<ItemParamData> materialItems;
@Override @Override
@ -58,6 +58,10 @@ public class ForgeData extends GameResource {
return priority; return priority;
} }
public int getForgePoint() {
return forgePoint;
}
public List<ItemParamData> getMaterialItems() { public List<ItemParamData> getMaterialItems() {
return materialItems; return materialItems;
} }

View File

@ -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;
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 {
private String effectType; private String effectType;
private String destroyRule; private String destroyRule;
// Relic // Post load enum forms of above
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
private transient MaterialType materialEnumType; private transient MaterialType materialEnumType;
private transient ItemType itemEnumType; private transient ItemType itemEnumType;
private transient EquipType equipEnumType; 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 // 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 {
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 {
} }
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);

View File

@ -131,6 +131,7 @@ public final class DatabaseHelper {
DatabaseManager.getGameDatabase().getCollection("gachas").deleteMany(eq("ownerId", player.getUid())); DatabaseManager.getGameDatabase().getCollection("gachas").deleteMany(eq("ownerId", player.getUid()));
DatabaseManager.getGameDatabase().getCollection("items").deleteMany(eq("ownerId", player.getUid())); DatabaseManager.getGameDatabase().getCollection("items").deleteMany(eq("ownerId", player.getUid()));
DatabaseManager.getGameDatabase().getCollection("quests").deleteMany(eq("ownerUid", player.getUid())); DatabaseManager.getGameDatabase().getCollection("quests").deleteMany(eq("ownerUid", player.getUid()));
DatabaseManager.getGameDatabase().getCollection("battlepass").deleteMany(eq("ownerUid", player.getUid()));
// Delete friendships. // Delete friendships.
// Here, we need to make sure to not only delete the deleted account's friendships, // Here, we need to make sure to not only delete the deleted account's friendships,

View File

@ -92,15 +92,16 @@ public class HealAbilityManager {
public HealAbilityManager (Player player) { public HealAbilityManager (Player player) {
this.player = player; this.player = player;
healDataAvatarList = new ArrayList(); healDataAvatarList = new ArrayList();
healDataAvatarList.add(new HealDataAvatar(10000054, "Kokomi", 0).addHealData("E", "ElementalArt_Heal_MaxHP_Base_Percentage", "ElementalArt_Heal_Base_Amount", false)); healDataAvatarList.add(new HealDataAvatar(10000054, "Kokomi", 0).addHealData("E", "ElementalArt_Heal_MaxHP_Base_Percentage", "ElementalArt_Heal_Base_Amount", false).addHealData("Q", "Avatar_Kokomi_ElementalBurst_Heal", 0.0172f, 212f, false));
healDataAvatarList.add(new HealDataAvatar(10000003, "Qin", 1).addHealData("Q", "Heal", "BurstHealConst", true)); healDataAvatarList.add(new HealDataAvatar(10000003, "Qin", 1).addHealData("Q", "Heal", "BurstHealConst", true));
healDataAvatarList.add(new HealDataAvatar(10000034, "Noel", 2).addHealData("E", "OnAttack_HealthRate", 0.452f, 282f, true)); healDataAvatarList.add(new HealDataAvatar(10000034, "Noel", 2).addHealData("E", "OnAttack_HealthRate", 0.452f, 282f, true));
healDataAvatarList.add(new HealDataAvatar(10000032, "Bennett", 0).addHealData("Q", "HealMaxHpRatio", "HealConst", false)); healDataAvatarList.add(new HealDataAvatar(10000032, "Bennett", 0).addHealData("Q", "HealMaxHpRatio", "HealConst", false));
healDataAvatarList.add(new HealDataAvatar(10000039, "Diona", 0).addHealData("Q", "HealHPRatio", "HealHP_Const", false)); healDataAvatarList.add(new HealDataAvatar(10000039, "Diona", 0).addHealData("Q", "HealHPRatio", "HealHP_Const", false));
healDataAvatarList.add(new HealDataAvatar(10000053, "Sayu", 1).addHealData("Q", "Constellation_6_Damage", "Heal_BaseAmount", true).addHealData("Q", "Heal_AttackRatio", "Constellation_6_Heal", true)); healDataAvatarList.add(new HealDataAvatar(10000053, "Sayu", 1).addHealData("Q", "Constellation_6_Damage", "Heal_BaseAmount", true).addHealData("Q", "Heal_AttackRatio", "Constellation_6_Heal", true));
healDataAvatarList.add(new HealDataAvatar(10000014, "Barbara", 0).addHealData("E", "HealHPOnAdded", "HealHPOnAdded_Const", true).addHealData("E", "HealHP_OnHittingOthers", "HealHP_Const_OnHittingOthers", true)); healDataAvatarList.add(new HealDataAvatar(10000014, "Barbara", 0).addHealData("E", "HealHPOnAdded", "HealHPOnAdded_Const", true).addHealData("E", "HealHP_OnHittingOthers", "HealHP_Const_OnHittingOthers", true).addHealData("Q", "Avatar_Barbara_IdolHeal", 0.374f, 4660f, true));
healDataAvatarList.add(new HealDataAvatar(10000065, "Shinobu", 0).addHealData("E", "ElementalArt_Heal_MaxHP_Percentage", 0.064f, 795f, false)); healDataAvatarList.add(new HealDataAvatar(10000065, "Shinobu", 0).addHealData("E", "ElementalArt_Heal_MaxHP_Percentage", 0.064f, 795f, false));
healDataAvatarList.add(new HealDataAvatar(10000035, "Qiqi", 1).addHealData("E", "HealHP_OnHittingOthers", "HealHP_Const_OnHittingOthers", true).addHealData("E", "ElementalArt_HealHp_Ratio", "ElementalArt_HealHp_Const", true)); healDataAvatarList.add(new HealDataAvatar(10000035, "Qiqi", 1).addHealData("E", "HealHP_OnHittingOthers", "HealHP_Const_OnHittingOthers", true).addHealData("E", "ElementalArt_HealHp_Ratio", "ElementalArt_HealHp_Const", true).addHealData("Q", "Avatar_Qiqi_ElementalBurst_ApplyModifier", 0.0191f, 1588f, false));
healDataAvatarList.add(new HealDataAvatar(10000046, "Hutao", 0).addHealData("Q", "Avatar_Hutao_VermilionBite_BakeMesh", 0.1166f, 0f, false));
} }
public Player getPlayer() { public Player getPlayer() {
@ -128,7 +129,7 @@ public class HealAbilityManager {
int fightPropertyType = 0; int fightPropertyType = 0;
float healAmount = 0; float healAmount = 0;
float ratio = 0, base = 0; float ratio = 0, base = 0;
float maxHP, curAttack, curDefense; float maxHP, curHP, curAttack, curDefense;
Map<String, Float> map = sourceEntity.getMetaOverrideMap(); Map<String, Float> map = sourceEntity.getMetaOverrideMap();
for(int i = 0 ; i < healDataAvatarList.size() ; i ++) { for(int i = 0 ; i < healDataAvatarList.size() ; i ++) {
@ -139,7 +140,7 @@ public class HealAbilityManager {
for(int j = 0 ; j < healDataList.size(); j++) { for(int j = 0 ; j < healDataList.size(); j++) {
HealData healData = healDataList.get(j); HealData healData = healDataList.get(j);
if(map.containsKey(healData.sRatio)) { if(map.containsKey(healData.sRatio) || modifierString.equals(healData.sRatio)) {
if(healData.isString) { if(healData.isString) {
ratio = map.get(healData.sRatio); ratio = map.get(healData.sRatio);
base = map.get(healData.sBase); base = map.get(healData.sBase);
@ -173,8 +174,15 @@ public class HealAbilityManager {
if(healActionAvatar != null) { if(healActionAvatar != null) {
maxHP = healActionAvatar.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP); maxHP = healActionAvatar.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP);
curHP = healActionAvatar.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP);
curAttack = healActionAvatar.getFightProperty(FightProperty.FIGHT_PROP_CUR_ATTACK); curAttack = healActionAvatar.getFightProperty(FightProperty.FIGHT_PROP_CUR_ATTACK);
curDefense = healActionAvatar.getFightProperty(FightProperty.FIGHT_PROP_CUR_DEFENSE); curDefense = healActionAvatar.getFightProperty(FightProperty.FIGHT_PROP_CUR_DEFENSE);
//Special case for Hu Tao:
if(healDataAvatar.avatarName.equals("Hutao") && curHP <= maxHP * 0.5 && ratio != 0) {
ratio = 0.1555f;
}
switch(fightPropertyType) { switch(fightPropertyType) {
case 0: case 0:
healAmount = ratio * maxHP + base; healAmount = ratio * maxHP + base;

View File

@ -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;
} }

View File

@ -1,23 +1,53 @@
package emu.grasscutter.game.battlepass; package emu.grasscutter.game.battlepass;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.bson.types.ObjectId; import org.bson.types.ObjectId;
import dev.morphia.annotations.Entity; import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Id; import dev.morphia.annotations.Id;
import dev.morphia.annotations.Indexed; import dev.morphia.annotations.Indexed;
import dev.morphia.annotations.Transient; import dev.morphia.annotations.Transient;
import emu.grasscutter.GameConstants;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.common.ItemParamData;
import emu.grasscutter.data.excels.BattlePassRewardData;
import emu.grasscutter.data.excels.RewardData;
import emu.grasscutter.database.DatabaseHelper; import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.game.props.BattlePassMissionRefreshType;
import emu.grasscutter.game.props.BattlePassMissionStatus;
import emu.grasscutter.game.props.WatcherTriggerType;
import emu.grasscutter.net.proto.BattlePassCycleOuterClass.BattlePassCycle;
import emu.grasscutter.net.proto.BattlePassProductOuterClass.BattlePassProduct;
import emu.grasscutter.net.proto.BattlePassRewardTagOuterClass.BattlePassRewardTag;
import emu.grasscutter.net.proto.BattlePassUnlockStatusOuterClass.BattlePassUnlockStatus;
import emu.grasscutter.net.proto.BattlePassRewardTakeOptionOuterClass.BattlePassRewardTakeOption;
import emu.grasscutter.net.proto.BattlePassScheduleOuterClass.BattlePassSchedule;
import emu.grasscutter.server.packet.send.PacketBattlePassCurScheduleUpdateNotify; import emu.grasscutter.server.packet.send.PacketBattlePassCurScheduleUpdateNotify;
import emu.grasscutter.server.packet.send.PacketBattlePassMissionUpdateNotify;
import emu.grasscutter.server.packet.send.PacketTakeBattlePassRewardRsp;
import lombok.Getter;
@Entity(value = "battlepass", useDiscriminator = false) @Entity(value = "battlepass", useDiscriminator = false)
public class BattlePassManager { public class BattlePassManager {
@Id private ObjectId id; @Id @Getter private ObjectId id;
@Transient private Player player; @Transient @Getter private Player player;
@Indexed private int ownerUid; @Indexed private int ownerUid;
private int point; @Getter private int point;
private int awardTakenLevel; @Getter private int cyclePoints; // Weekly maximum cap
@Getter private int level;
@Getter private boolean viewed;
@Getter private boolean paid;
private Map<Integer, BattlePassMission> missions;
private Map<Integer, BattlePassReward> takenRewards;
@Deprecated // Morphia only @Deprecated // Morphia only
public BattlePassManager() {} public BattlePassManager() {}
@ -26,37 +56,224 @@ public class BattlePassManager {
this.setPlayer(player); this.setPlayer(player);
} }
public ObjectId getId() {
return id;
}
public Player getPlayer() {
return this.player;
}
public void setPlayer(Player player) { public void setPlayer(Player player) {
this.player = player; this.player = player;
this.ownerUid = player.getUid(); this.ownerUid = player.getUid();
} }
public int getPoint() { public void updateViewed() {
return point; this.viewed = true;
} }
public int getAwardTakenLevel() { public boolean setLevel(int level) {
return awardTakenLevel; if (level >= 0 && level <= GameConstants.BATTLE_PASS_MAX_LEVEL) {
this.level = level;
this.point = 0;
this.player.sendPacket(new PacketBattlePassCurScheduleUpdateNotify(this.player));
return true;
}
return false;
} }
public void addPoint(int point){ public void addPoints(int points){
this.point += point; this.addPointsDirectly(points, false);
player.getSession().send(new PacketBattlePassCurScheduleUpdateNotify(player.getSession().getPlayer()));
this.player.sendPacket(new PacketBattlePassCurScheduleUpdateNotify(player));
this.save(); this.save();
} }
public void updateAwardTakenLevel(int level){ public void addPointsDirectly(int points, boolean isWeekly) {
this.awardTakenLevel = level; int amount = points;
player.getSession().send(new PacketBattlePassCurScheduleUpdateNotify(player.getSession().getPlayer()));
if (isWeekly) {
amount = Math.min(amount, GameConstants.BATTLE_PASS_POINT_PER_WEEK - this.cyclePoints);
}
if (amount <= 0) {
return;
}
this.point += amount;
this.cyclePoints += amount;
if (this.point >= GameConstants.BATTLE_PASS_POINT_PER_LEVEL && this.getLevel() < GameConstants.BATTLE_PASS_MAX_LEVEL) {
int levelups = Math.floorDiv(this.point, GameConstants.BATTLE_PASS_POINT_PER_LEVEL);
// Make sure player cant go above max BP level
levelups = Math.min(levelups, GameConstants.BATTLE_PASS_MAX_LEVEL - levelups);
// Set new points after level up
this.point = this.point - (levelups * GameConstants.BATTLE_PASS_POINT_PER_LEVEL);
this.level += levelups;
}
}
public Map<Integer, BattlePassMission> getMissions() {
if (this.missions == null) this.missions = new HashMap<>();
return this.missions;
}
// Will return a new empty mission if the mission id is not found
public BattlePassMission loadMissionById(int id) {
return getMissions().computeIfAbsent(id, i -> new BattlePassMission(i));
}
public boolean hasMission(int id) {
return getMissions().containsKey(id);
}
public Map<Integer, BattlePassReward> getTakenRewards() {
if (this.takenRewards == null) this.takenRewards = new HashMap<>();
return this.takenRewards;
}
// Mission triggers
public void triggerMission(WatcherTriggerType triggerType) {
getPlayer().getServer().getBattlePassMissionManager().triggerMission(getPlayer(), triggerType);
}
public void triggerMission(WatcherTriggerType triggerType, int param, int progress) {
getPlayer().getServer().getBattlePassMissionManager().triggerMission(getPlayer(), triggerType, param, progress);
}
// Handlers
public void takeMissionPoint(List<Integer> missionIdList) {
// Obvious exploit check
if (missionIdList.size() > GameData.getBattlePassMissionDataMap().size()) {
return;
}
List<BattlePassMission> updatedMissions = new ArrayList<>(missionIdList.size());
for (int id : missionIdList) {
// Skip if we dont have this mission
if (!this.hasMission(id)) {
continue;
}
BattlePassMission mission = this.loadMissionById(id);
if (mission.getData() == null) {
this.getMissions().remove(mission.getId());
continue;
}
// Take reward
if (mission.getStatus() == BattlePassMissionStatus.MISSION_STATUS_FINISHED) {
this.addPointsDirectly(mission.getData().getAddPoint(), mission.getData().isCycleRefresh());
mission.setStatus(BattlePassMissionStatus.MISSION_STATUS_POINT_TAKEN);
updatedMissions.add(mission);
}
}
if (updatedMissions.size() > 0) {
// Save to db
this.save(); this.save();
// Packet
getPlayer().sendPacket(new PacketBattlePassMissionUpdateNotify(updatedMissions));
getPlayer().sendPacket(new PacketBattlePassCurScheduleUpdateNotify(getPlayer()));
}
}
public void takeReward(List<BattlePassRewardTakeOption> takeOptionList) {
List<BattlePassRewardTag> rewardList = new ArrayList<>();
for (BattlePassRewardTakeOption option : takeOptionList) {
// Duplicate check
if (option.getTag().getRewardId() == 0 || getTakenRewards().containsKey(option.getTag().getRewardId())) {
continue;
}
// Level check
if (option.getTag().getLevel() > this.getLevel()) {
continue;
}
BattlePassRewardData rewardData = GameData.getBattlePassRewardDataMap().get(option.getTag().getLevel());
// Sanity check with excel data
if (rewardData.getFreeRewardIdList().contains(option.getTag().getRewardId())) {
rewardList.add(option.getTag());
} else if (this.isPaid() && rewardData.getPaidRewardIdList().contains(option.getTag().getRewardId())) {
rewardList.add(option.getTag());
}
}
// Get rewards
List<ItemParamData> rewardItems = null;
if (rewardList.size() > 0) {
rewardItems = new ArrayList<>();
for (BattlePassRewardTag tag : rewardList) {
RewardData reward = GameData.getRewardDataMap().get(tag.getRewardId());
if (reward == null) continue;
BattlePassReward bpReward = new BattlePassReward(tag.getLevel(), tag.getRewardId(), tag.getUnlockStatus() == BattlePassUnlockStatus.BATTLE_PASS_UNLOCK_STATUS_PAID);
this.getTakenRewards().put(bpReward.getRewardId(), bpReward);
rewardItems.addAll(reward.getRewardItemList());
}
// Save to db
this.save();
// Add items and send battle pass schedule packet
getPlayer().getInventory().addItemParamDatas(rewardItems);
getPlayer().sendPacket(new PacketBattlePassCurScheduleUpdateNotify(getPlayer()));
}
getPlayer().sendPacket(new PacketTakeBattlePassRewardRsp(takeOptionList, rewardItems));
}
public int buyLevels(int buyLevel) {
int boughtLevels = Math.min(buyLevel, GameConstants.BATTLE_PASS_MAX_LEVEL - buyLevel);
if (boughtLevels > 0) {
int price = GameConstants.BATTLE_PASS_LEVEL_PRICE * boughtLevels;
if (getPlayer().getPrimogems() < price) {
return 0;
}
this.level += boughtLevels;
this.save();
getPlayer().sendPacket(new PacketBattlePassCurScheduleUpdateNotify(getPlayer()));
}
return boughtLevels;
}
public void resetDailyMissions() {
// TODO
}
public void resetWeeklyMissions() {
// TODO
}
//
public BattlePassSchedule getScheduleProto() {
BattlePassSchedule.Builder schedule = BattlePassSchedule.newBuilder()
.setScheduleId(2700)
.setLevel(this.getLevel())
.setPoint(this.getPoint())
.setBeginTime(0)
.setEndTime(2059483200)
.setIsViewed(this.isViewed())
.setUnlockStatus(this.isPaid() ? BattlePassUnlockStatus.BATTLE_PASS_UNLOCK_STATUS_PAID : BattlePassUnlockStatus.BATTLE_PASS_UNLOCK_STATUS_FREE)
.setCurCyclePoints(this.getCyclePoints())
.setCurCycle(BattlePassCycle.newBuilder().setBeginTime(0).setEndTime(2059483200).setCycleIdx(3));
for (BattlePassReward reward : getTakenRewards().values()) {
schedule.addRewardTakenList(reward.toProto());
}
return schedule.build();
} }
public void save() { public void save() {

View File

@ -0,0 +1,70 @@
package emu.grasscutter.game.battlepass;
import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Transient;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.BattlePassMissionData;
import emu.grasscutter.game.props.BattlePassMissionStatus;
@Entity
public class BattlePassMission {
private int id;
private int progress;
private BattlePassMissionStatus status;
@Transient
private BattlePassMissionData data;
@Deprecated // Morphia only
public BattlePassMission() {}
public BattlePassMission(int id) {
this.id = id;
}
public int getId() {
return id;
}
public BattlePassMissionData getData() {
if (this.data == null) {
this.data = GameData.getBattlePassMissionDataMap().get(getId());
}
return this.data;
}
public int getProgress() {
return progress;
}
public void addProgress(int addProgress, int maxProgress) {
this.progress = Math.min(addProgress + this.progress, maxProgress);
}
public BattlePassMissionStatus getStatus() {
if (status == null) status = BattlePassMissionStatus.MISSION_STATUS_UNFINISHED;
return status;
}
public void setStatus(BattlePassMissionStatus status) {
this.status = status;
}
public boolean isFinshed() {
return getStatus().getValue() >= 2;
}
public emu.grasscutter.net.proto.BattlePassMissionOuterClass.BattlePassMission toProto() {
var protoBuilder = emu.grasscutter.net.proto.BattlePassMissionOuterClass.BattlePassMission.newBuilder();
protoBuilder
.setMissionId(getId())
.setCurProgress(getProgress())
.setTotalProgress(getData().getProgress())
.setRewardBattlePassPoint(getData().getAddPoint())
.setMissionStatus(getStatus().getMissionStatus())
.setMissionType(getData().getRefreshType() == null ? 0 : getData().getRefreshType().getValue());
return protoBuilder.build();
}
}

View File

@ -0,0 +1,78 @@
package emu.grasscutter.game.battlepass;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.BattlePassMissionData;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.BattlePassMissionRefreshType;
import emu.grasscutter.game.props.BattlePassMissionStatus;
import emu.grasscutter.game.props.WatcherTriggerType;
import emu.grasscutter.server.game.GameServer;
import emu.grasscutter.server.packet.send.PacketBattlePassMissionUpdateNotify;
public class BattlePassMissionManager {
private final GameServer server;
private final Map<WatcherTriggerType, List<BattlePassMissionData>> cachedTriggers;
// BP Mission manager for the server, contains cached triggers so we dont have to load it for each player
public BattlePassMissionManager(GameServer server) {
this.server = server;
this.cachedTriggers = new HashMap<>();
for (BattlePassMissionData missionData : GameData.getBattlePassMissionDataMap().values()) {
if (missionData.isValidRefreshType()) {
List<BattlePassMissionData> triggerList = getTriggers().computeIfAbsent(missionData.getTriggerType(), e -> new ArrayList<>());
triggerList.add(missionData);
}
}
}
public GameServer getServer() {
return server;
}
private Map<WatcherTriggerType, List<BattlePassMissionData>> getTriggers() {
return cachedTriggers;
}
public void triggerMission(Player player, WatcherTriggerType triggerType) {
triggerMission(player, triggerType, 0, 1);
}
public void triggerMission(Player player, WatcherTriggerType triggerType, int param, int progress) {
List<BattlePassMissionData> triggerList = getTriggers().get(triggerType);
if (triggerList == null || triggerList.isEmpty()) return;
for (BattlePassMissionData data : triggerList) {
// Skip params check if param == 0
if (param != 0) {
if (!data.getMainParams().contains(param)) {
continue;
}
}
// Get mission from player, if it doesnt exist, then we make one
BattlePassMission mission = player.getBattlePassManager().loadMissionById(data.getId());
if (mission.isFinshed()) continue;
// Add progress
mission.addProgress(progress, data.getProgress());
if (mission.getProgress() >= data.getProgress()) {
mission.setStatus(BattlePassMissionStatus.MISSION_STATUS_FINISHED);
}
// Save to db
player.getBattlePassManager().save();
// Packet
player.sendPacket(new PacketBattlePassMissionUpdateNotify(mission));
}
}
}

View File

@ -0,0 +1,52 @@
package emu.grasscutter.game.battlepass;
import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Transient;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.BattlePassMissionData;
import emu.grasscutter.data.excels.BattlePassRewardData;
import emu.grasscutter.game.props.BattlePassMissionStatus;
import emu.grasscutter.net.proto.BattlePassRewardTagOuterClass.BattlePassRewardTag;
import emu.grasscutter.net.proto.BattlePassUnlockStatusOuterClass.BattlePassUnlockStatus;
@Entity
public class BattlePassReward {
private int level;
private int rewardId;
private boolean paid;
@Transient
private BattlePassMissionData data;
@Deprecated // Morphia only
public BattlePassReward() {}
public BattlePassReward(int level, int rewardId, boolean paid) {
this.level = level;
this.rewardId = rewardId;
this.paid = paid;
}
public int getLevel() {
return level;
}
public int getRewardId() {
return rewardId;
}
public boolean isPaid() {
return paid;
}
public BattlePassRewardTag toProto() {
var protoBuilder = BattlePassRewardTag.newBuilder();
protoBuilder
.setLevel(this.getLevel())
.setRewardId(this.getRewardId())
.setUnlockStatus(this.isPaid() ? BattlePassUnlockStatus.BATTLE_PASS_UNLOCK_STATUS_PAID : BattlePassUnlockStatus.BATTLE_PASS_UNLOCK_STATUS_FREE);
return protoBuilder.build();
}
}

View File

@ -11,6 +11,7 @@ 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.WatcherTriggerType;
import emu.grasscutter.game.world.Scene; import emu.grasscutter.game.world.Scene;
import emu.grasscutter.net.proto.GadgetInteractReqOuterClass.GadgetInteractReq; import emu.grasscutter.net.proto.GadgetInteractReqOuterClass.GadgetInteractReq;
import emu.grasscutter.scripts.constants.EventType; import emu.grasscutter.scripts.constants.EventType;
@ -98,6 +99,8 @@ public class DungeonChallenge extends WorldChallenge {
getScene().getDungeonSettleObservers().forEach(o -> o.onDungeonSettle(getScene())); getScene().getDungeonSettleObservers().forEach(o -> o.onDungeonSettle(getScene()));
getScene().getScriptManager().callEvent(EventType.EVENT_DUNGEON_SETTLE, getScene().getScriptManager().callEvent(EventType.EVENT_DUNGEON_SETTLE,
new ScriptArgs(this.isSuccess() ? 1 : 0)); new ScriptArgs(this.isSuccess() ? 1 : 0));
// Battle pass trigger
this.getScene().getPlayers().forEach(p -> p.getBattlePassManager().triggerMission(WatcherTriggerType.TRIGGER_FINISH_DUNGEON));
} }
} }

View File

@ -8,6 +8,7 @@ import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.EntityIdType; import emu.grasscutter.game.props.EntityIdType;
import emu.grasscutter.game.props.FightProperty; import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.game.props.PlayerProperty; import emu.grasscutter.game.props.PlayerProperty;
import emu.grasscutter.game.props.WatcherTriggerType;
import emu.grasscutter.game.world.Scene; import emu.grasscutter.game.world.Scene;
import emu.grasscutter.net.proto.AbilitySyncStateInfoOuterClass.AbilitySyncStateInfo; import emu.grasscutter.net.proto.AbilitySyncStateInfoOuterClass.AbilitySyncStateInfo;
import emu.grasscutter.net.proto.AnimatorParameterValueInfoPairOuterClass.AnimatorParameterValueInfoPair; import emu.grasscutter.net.proto.AnimatorParameterValueInfoPairOuterClass.AnimatorParameterValueInfoPair;
@ -154,6 +155,8 @@ public class EntityMonster extends GameEntity {
getScene().getScriptManager().callEvent(EventType.EVENT_ANY_MONSTER_DIE, new ScriptArgs().setParam1(this.getConfigId())); getScene().getScriptManager().callEvent(EventType.EVENT_ANY_MONSTER_DIE, new ScriptArgs().setParam1(this.getConfigId()));
} }
} }
// Battle Pass trigger
getScene().getPlayers().forEach(p -> p.getBattlePassManager().triggerMission(WatcherTriggerType.TRIGGER_MONSTER_DIE, this.getMonsterId(), 1));
} }
public void recalcStats() { public void recalcStats() {

View File

@ -27,6 +27,7 @@ import emu.grasscutter.game.inventory.Inventory;
import emu.grasscutter.game.inventory.ItemType; import emu.grasscutter.game.inventory.ItemType;
import emu.grasscutter.game.inventory.MaterialType; import emu.grasscutter.game.inventory.MaterialType;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.WatcherTriggerType;
import emu.grasscutter.net.proto.GachaItemOuterClass.GachaItem; import emu.grasscutter.net.proto.GachaItemOuterClass.GachaItem;
import emu.grasscutter.net.proto.GachaTransferItemOuterClass.GachaTransferItem; import emu.grasscutter.net.proto.GachaTransferItemOuterClass.GachaTransferItem;
import emu.grasscutter.net.proto.GetGachaInfoRspOuterClass.GetGachaInfoRsp; import emu.grasscutter.net.proto.GetGachaInfoRspOuterClass.GetGachaInfoRsp;
@ -375,6 +376,9 @@ public class GachaManager {
// Packets // Packets
player.sendPacket(new PacketDoGachaRsp(banner, list, gachaInfo)); player.sendPacket(new PacketDoGachaRsp(banner, list, gachaInfo));
// Battle Pass trigger
player.getBattlePassManager().triggerMission(WatcherTriggerType.TRIGGER_GACHA_NUM, 0, times);
} }
private synchronized void startWatcher(GameServer server) { private synchronized void startWatcher(GameServer server) {

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.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,43 +84,38 @@ 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 {
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) {
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 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 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() {
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() { public void addAppendProp() {
if (this.getAppendPropIdList() == null) { if (this.appendPropIdList == null) {
this.appendPropIdList = new ArrayList<>(); this.appendPropIdList = new ArrayList<>();
} }
if (this.getAppendPropIdList().size() < 4) { if (this.appendPropIdList.size() < 4) {
addNewAppendProp(); this.addNewAppendProp();
} else { } 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() { 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 {
// 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 {
// 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

View File

@ -18,6 +18,7 @@ import emu.grasscutter.game.avatar.Avatar;
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.PlayerProperty; import emu.grasscutter.game.props.PlayerProperty;
import emu.grasscutter.game.props.WatcherTriggerType;
import emu.grasscutter.net.proto.ItemParamOuterClass.ItemParam; import emu.grasscutter.net.proto.ItemParamOuterClass.ItemParam;
import emu.grasscutter.server.packet.send.PacketAvatarEquipChangeNotify; import emu.grasscutter.server.packet.send.PacketAvatarEquipChangeNotify;
import emu.grasscutter.server.packet.send.PacketItemAddHintNotify; import emu.grasscutter.server.packet.send.PacketItemAddHintNotify;
@ -95,6 +96,7 @@ public class Inventory implements Iterable<GameItem> {
GameItem result = putItem(item); GameItem result = putItem(item);
if (result != null) { if (result != null) {
getPlayer().getBattlePassManager().triggerMission(WatcherTriggerType.TRIGGER_OBTAIN_MATERIAL_NUM, result.getItemId(), result.getCount());
getPlayer().sendPacket(new PacketStoreItemChangeNotify(result)); getPlayer().sendPacket(new PacketStoreItemChangeNotify(result));
return true; return true;
} }
@ -131,7 +133,9 @@ public class Inventory implements Iterable<GameItem> {
for (GameItem item : items) { for (GameItem item : items) {
GameItem result = putItem(item); GameItem result = putItem(item);
if (result != null) { if (result != null) {
getPlayer().getBattlePassManager().triggerMission(WatcherTriggerType.TRIGGER_OBTAIN_MATERIAL_NUM, result.getItemId(), result.getCount());
changedItems.add(result); changedItems.add(result);
} }
} }
@ -170,22 +174,29 @@ public class Inventory implements Iterable<GameItem> {
InventoryTab tab = getInventoryTab(type); InventoryTab tab = getInventoryTab(type);
// Add // Add
if (type == ItemType.ITEM_WEAPON || type == ItemType.ITEM_RELIQUARY) { switch (type) {
case ITEM_WEAPON:
case ITEM_RELIQUARY:
if (tab.getSize() >= tab.getMaxCapacity()) { if (tab.getSize() >= tab.getMaxCapacity()) {
return null; return null;
} }
// Duplicates cause problems // Duplicates cause problems
item.setCount(Math.max(item.getCount(), 1)); item.setCount(Math.max(item.getCount(), 1));
// Adds to inventory // Adds to inventory
putItem(item, tab); this.putItem(item, tab);
} else if (type == ItemType.ITEM_VIRTUAL) { // Set ownership and save to db
item.save();
return item;
case ITEM_VIRTUAL:
// Handle // Handle
this.addVirtualItem(item.getItemId(), item.getCount()); this.addVirtualItem(item.getItemId(), item.getCount());
return item; return item;
} else if (item.getItemData().getMaterialType() == MaterialType.MATERIAL_ADSORBATE) { default:
switch (item.getItemData().getMaterialType()) {
case MATERIAL_ADSORBATE:
this.player.getEnergyManager().handlePickupElemBall(item); this.player.getEnergyManager().handlePickupElemBall(item);
return null; return null;
} else if (item.getItemData().getMaterialType() == MaterialType.MATERIAL_AVATAR) { case MATERIAL_AVATAR:
// Get avatar id // Get avatar id
int avatarId = (item.getItemId() % 1000) + 10000000; int avatarId = (item.getItemId() % 1000) + 10000000;
// Dont let people give themselves extra main characters // Dont let people give themselves extra main characters
@ -194,56 +205,55 @@ public class Inventory implements Iterable<GameItem> {
} }
// Add avatar // Add avatar
AvatarData avatarData = GameData.getAvatarDataMap().get(avatarId); AvatarData avatarData = GameData.getAvatarDataMap().get(avatarId);
if (avatarData != null && !player.getAvatars().hasAvatar(avatarId)) { if (avatarData != null && !this.player.getAvatars().hasAvatar(avatarId)) {
this.getPlayer().addAvatar(new Avatar(avatarData)); this.player.addAvatar(new Avatar(avatarData));
} }
return null; return null;
} else if (item.getItemData().getMaterialType() == MaterialType.MATERIAL_FLYCLOAK) { case MATERIAL_FLYCLOAK:
AvatarFlycloakData flycloakData = GameData.getAvatarFlycloakDataMap().get(item.getItemId()); AvatarFlycloakData flycloakData = GameData.getAvatarFlycloakDataMap().get(item.getItemId());
if (flycloakData != null && !player.getFlyCloakList().contains(item.getItemId())) { if (flycloakData != null && !this.player.getFlyCloakList().contains(item.getItemId())) {
getPlayer().addFlycloak(item.getItemId()); this.player.addFlycloak(item.getItemId());
} }
return null; return null;
} else if (item.getItemData().getMaterialType() == MaterialType.MATERIAL_COSTUME) { case MATERIAL_COSTUME:
AvatarCostumeData costumeData = GameData.getAvatarCostumeDataItemIdMap().get(item.getItemId()); AvatarCostumeData costumeData = GameData.getAvatarCostumeDataItemIdMap().get(item.getItemId());
if (costumeData != null && !player.getCostumeList().contains(costumeData.getId())) { if (costumeData != null && !this.player.getCostumeList().contains(costumeData.getId())) {
getPlayer().addCostume(costumeData.getId()); this.player.addCostume(costumeData.getId());
} }
return null; return null;
} else if (item.getItemData().getMaterialType() == MaterialType.MATERIAL_NAMECARD) { case MATERIAL_NAMECARD:
if (!player.getNameCardList().contains(item.getItemId())) { if (!this.player.getNameCardList().contains(item.getItemId())) {
getPlayer().addNameCard(item.getItemId()); this.player.addNameCard(item.getItemId());
} }
return null; return null;
} else if (tab != null) { default:
if (tab == null) {
return null;
}
GameItem existingItem = tab.getItemById(item.getItemId()); GameItem existingItem = tab.getItemById(item.getItemId());
if (existingItem == null) { if (existingItem == null) {
// Item type didnt exist before, we will add it to main inventory map if there is enough space // Item type didnt exist before, we will add it to main inventory map if there is enough space
if (tab.getSize() >= tab.getMaxCapacity()) { if (tab.getSize() >= tab.getMaxCapacity()) {
return null; return null;
} }
putItem(item, tab); this.putItem(item, tab);
// Set ownership and save to db
item.save();
return item;
} else { } else {
// Add count // Add count
existingItem.setCount(Math.min(existingItem.getCount() + item.getCount(), item.getItemData().getStackLimit())); existingItem.setCount(Math.min(existingItem.getCount() + item.getCount(), item.getItemData().getStackLimit()));
existingItem.save(); existingItem.save();
return existingItem; return existingItem;
} }
} else {
return null;
} }
}
// Set ownership and save to db
if (item.getItemData().getItemType() != ItemType.ITEM_VIRTUAL)
item.save();
return item;
} }
private synchronized void putItem(GameItem item, InventoryTab tab) { private synchronized void putItem(GameItem item, InventoryTab tab) {
getPlayer().getCodex().checkAddedItem(item); this.player.getCodex().checkAddedItem(item);
// Set owner and guid FIRST! // Set owner and guid FIRST!
item.setOwner(getPlayer()); item.setOwner(this.player);
// Put in item store // Put in item store
getItems().put(item.getGuid(), item); getItems().put(item.getGuid(), item);
if (tab != null) { if (tab != null) {
@ -254,36 +264,36 @@ public class Inventory implements Iterable<GameItem> {
private void addVirtualItem(int itemId, int count) { private void addVirtualItem(int itemId, int count) {
switch (itemId) { switch (itemId) {
case 101 -> // Character exp case 101 -> // Character exp
getPlayer().getServer().getInventoryManager().upgradeAvatar(player, getPlayer().getTeamManager().getCurrentAvatarEntity().getAvatar(), count); this.player.getServer().getInventoryManager().upgradeAvatar(this.player, this.player.getTeamManager().getCurrentAvatarEntity().getAvatar(), count);
case 102 -> // Adventure exp case 102 -> // Adventure exp
getPlayer().addExpDirectly(count); this.player.addExpDirectly(count);
case 105 -> // Companionship exp case 105 -> // Companionship exp
getPlayer().getServer().getInventoryManager().upgradeAvatarFetterLevel(player, getPlayer().getTeamManager().getCurrentAvatarEntity().getAvatar(), count); this.player.getServer().getInventoryManager().upgradeAvatarFetterLevel(this.player, this.player.getTeamManager().getCurrentAvatarEntity().getAvatar(), count);
case 106 -> // Resin case 106 -> // Resin
getPlayer().getResinManager().addResin(count); this.player.getResinManager().addResin(count);
case 201 -> // Primogem case 201 -> // Primogem
getPlayer().setPrimogems(player.getPrimogems() + count); this.player.setPrimogems(this.player.getPrimogems() + count);
case 202 -> // Mora case 202 -> // Mora
getPlayer().setMora(player.getMora() + count); this.player.setMora(this.player.getMora() + count);
case 203 -> // Genesis Crystals case 203 -> // Genesis Crystals
getPlayer().setCrystals(player.getCrystals() + count); this.player.setCrystals(this.player.getCrystals() + count);
case 204 -> // Home Coin case 204 -> // Home Coin
getPlayer().setHomeCoin(player.getHomeCoin() + count); this.player.setHomeCoin(this.player.getHomeCoin() + count);
} }
} }
private int getVirtualItemCount(int itemId) { private int getVirtualItemCount(int itemId) {
switch (itemId) { switch (itemId) {
case 201: // Primogem case 201: // Primogem
return player.getPrimogems(); return this.player.getPrimogems();
case 202: // Mora case 202: // Mora
return player.getMora(); return this.player.getMora();
case 203: // Genesis Crystals case 203: // Genesis Crystals
return player.getCrystals(); return this.player.getCrystals();
case 106: // Resin case 106: // Resin
return player.getProperty(PlayerProperty.PROP_PLAYER_RESIN); return this.player.getProperty(PlayerProperty.PROP_PLAYER_RESIN);
case 204: // Home Coin case 204: // Home Coin
return player.getHomeCoin(); return this.player.getHomeCoin();
default: default:
GameItem item = getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(itemId); // What if we ever want to operate on weapons/relics/furniture? :S GameItem item = getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(itemId); // What if we ever want to operate on weapons/relics/furniture? :S
return (item == null) ? 0 : item.getCount(); return (item == null) ? 0 : item.getCount();
@ -389,6 +399,10 @@ public class Inventory implements Iterable<GameItem> {
getPlayer().sendPacket(new PacketStoreItemChangeNotify(item)); getPlayer().sendPacket(new PacketStoreItemChangeNotify(item));
} }
// Battle pass trigger
int removeCount = Math.min(count, item.getCount());
getPlayer().getBattlePassManager().triggerMission(WatcherTriggerType.TRIGGER_COST_MATERIAL, item.getItemId(), removeCount);
// Update in db // Update in db
item.save(); item.save();

View File

@ -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 {
} }
} }
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);

View File

@ -2,6 +2,7 @@ package emu.grasscutter.game.managers;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.PlayerProperty; import emu.grasscutter.game.props.PlayerProperty;
import emu.grasscutter.game.props.WatcherTriggerType;
import emu.grasscutter.server.packet.send.PacketPlayerPropNotify; import emu.grasscutter.server.packet.send.PacketPlayerPropNotify;
import emu.grasscutter.server.packet.send.PacketResinChangeNotify; import emu.grasscutter.server.packet.send.PacketResinChangeNotify;
import emu.grasscutter.utils.Utils; import emu.grasscutter.utils.Utils;
@ -43,9 +44,11 @@ public class ResinManager {
} }
// Send packets. // Send packets.
this.player.sendPacket(new PacketPlayerPropNotify(this.player, PlayerProperty.PROP_PLAYER_RESIN));
this.player.sendPacket(new PacketResinChangeNotify(this.player)); this.player.sendPacket(new PacketResinChangeNotify(this.player));
// Battle Pass trigger
this.player.getBattlePassManager().triggerMission(WatcherTriggerType.TRIGGER_COST_MATERIAL, 106, amount); // Resin item id = 106
return true; return true;
} }
@ -66,7 +69,6 @@ public class ResinManager {
} }
// Send packets. // Send packets.
this.player.sendPacket(new PacketPlayerPropNotify(this.player, PlayerProperty.PROP_PLAYER_RESIN));
this.player.sendPacket(new PacketResinChangeNotify(this.player)); this.player.sendPacket(new PacketResinChangeNotify(this.player));
} }
@ -113,7 +115,6 @@ public class ResinManager {
} }
// Send packets. // Send packets.
this.player.sendPacket(new PacketPlayerPropNotify(this.player, PlayerProperty.PROP_PLAYER_RESIN));
this.player.sendPacket(new PacketResinChangeNotify(this.player)); this.player.sendPacket(new PacketResinChangeNotify(this.player));
} }
@ -137,7 +138,6 @@ public class ResinManager {
} }
// Send initial notifications on logon. // Send initial notifications on logon.
this.player.sendPacket(new PacketPlayerPropNotify(this.player, PlayerProperty.PROP_PLAYER_RESIN));
this.player.sendPacket(new PacketResinChangeNotify(this.player)); this.player.sendPacket(new PacketResinChangeNotify(this.player));
} }
} }

View File

@ -8,7 +8,6 @@ import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.game.props.PlayerProperty; import emu.grasscutter.game.props.PlayerProperty;
import emu.grasscutter.net.proto.ChangeHpReasonOuterClass.ChangeHpReason; import emu.grasscutter.net.proto.ChangeHpReasonOuterClass.ChangeHpReason;
import emu.grasscutter.net.proto.PropChangeReasonOuterClass.PropChangeReason; import emu.grasscutter.net.proto.PropChangeReasonOuterClass.PropChangeReason;
import emu.grasscutter.server.game.GameSession;
import emu.grasscutter.server.packet.send.PacketEntityFightPropChangeReasonNotify; import emu.grasscutter.server.packet.send.PacketEntityFightPropChangeReasonNotify;
import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify; import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify;
@ -26,7 +25,7 @@ public class SotSManager {
private Timer autoRecoverTimer; private Timer autoRecoverTimer;
private final boolean enablePriorityHealing = false; private final boolean enablePriorityHealing = false;
public final static int GlobalMaximumSpringVolume = 8500000; public final static int GlobalMaximumSpringVolume = PlayerProperty.PROP_MAX_SPRING_VOLUME.getMax();
public SotSManager(Player player) { public SotSManager(Player player) {
this.player = player; this.player = player;

View File

@ -446,5 +446,10 @@ public class EnergyManager {
public void setEnergyUsage(Boolean energyUsage) { public void setEnergyUsage(Boolean energyUsage) {
this.energyUsage = energyUsage; this.energyUsage = energyUsage;
if (!energyUsage) { // Refill team energy if usage is disabled
for (EntityAvatar entityAvatar : player.getTeamManager().getActiveTeam()) {
entityAvatar.addEnergy(1000, PropChangeReason.PROP_CHANGE_REASON_GM,true);
}
}
} }
} }

View File

@ -5,18 +5,15 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import com.mongodb.QueryBuilder;
import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GameData; import emu.grasscutter.data.GameData;
import emu.grasscutter.data.common.ItemParamData; import emu.grasscutter.data.common.ItemParamData;
import emu.grasscutter.data.excels.ForgeData; import emu.grasscutter.data.excels.ForgeData;
import emu.grasscutter.data.excels.ItemData; import emu.grasscutter.data.excels.ItemData;
import emu.grasscutter.game.inventory.GameItem; import emu.grasscutter.game.inventory.GameItem;
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.net.proto.ForgeStartReqOuterClass; import emu.grasscutter.game.props.WatcherTriggerType;
import emu.grasscutter.net.proto.ForgeQueueDataOuterClass.ForgeQueueData; import emu.grasscutter.net.proto.ForgeQueueDataOuterClass.ForgeQueueData;
import emu.grasscutter.net.proto.ForgeQueueManipulateReqOuterClass.ForgeQueueManipulateReq; import emu.grasscutter.net.proto.ForgeQueueManipulateReqOuterClass.ForgeQueueManipulateReq;
import emu.grasscutter.net.proto.ForgeQueueManipulateTypeOuterClass.ForgeQueueManipulateType; import emu.grasscutter.net.proto.ForgeQueueManipulateTypeOuterClass.ForgeQueueManipulateType;
@ -148,6 +145,13 @@ public class ForgingManager {
ForgeData forgeData = GameData.getForgeDataMap().get(req.getForgeId()); ForgeData forgeData = GameData.getForgeDataMap().get(req.getForgeId());
//Check if the player has sufficient forge points.
int requiredPoints = forgeData.getForgePoint() * req.getForgeCount();
if (requiredPoints > this.player.getForgePoints()) {
this.player.sendPacket(new PacketForgeStartRsp(Retcode.RET_FORGE_POINT_NOT_ENOUGH));
return;
}
// Check if we have enough of each material and consume. // Check if we have enough of each material and consume.
List<ItemParamData> material = new ArrayList<>(forgeData.getMaterialItems()); List<ItemParamData> material = new ArrayList<>(forgeData.getMaterialItems());
material.add(new ItemParamData(202, forgeData.getScoinCost())); material.add(new ItemParamData(202, forgeData.getScoinCost()));
@ -158,6 +162,9 @@ public class ForgingManager {
this.player.sendPacket(new PacketForgeStartRsp(Retcode.RET_FORGE_POINT_NOT_ENOUGH)); //ToDo: Probably the wrong return code. this.player.sendPacket(new PacketForgeStartRsp(Retcode.RET_FORGE_POINT_NOT_ENOUGH)); //ToDo: Probably the wrong return code.
} }
// Consume forge points.
this.player.setForgePoints(this.player.getForgePoints() - requiredPoints);
// Create and add active forge. // Create and add active forge.
ActiveForgeData activeForge = new ActiveForgeData(); ActiveForgeData activeForge = new ActiveForgeData();
activeForge.setForgeId(req.getForgeId()); activeForge.setForgeId(req.getForgeId());
@ -196,6 +203,9 @@ public class ForgingManager {
GameItem addItem = new GameItem(resultItemData, data.getResultItemCount() * finished); GameItem addItem = new GameItem(resultItemData, data.getResultItemCount() * finished);
this.player.getInventory().addItem(addItem); this.player.getInventory().addItem(addItem);
// Battle pass trigger handler
this.player.getBattlePassManager().triggerMission(WatcherTriggerType.TRIGGER_DO_FORGE, 0, finished);
// Replace active forge with a new one for the unfinished items, if there are any. // Replace active forge with a new one for the unfinished items, if there are any.
if (unfinished > 0) { if (unfinished > 0) {
ActiveForgeData remainingForge = new ActiveForgeData(); ActiveForgeData remainingForge = new ActiveForgeData();
@ -252,6 +262,12 @@ public class ForgingManager {
GameItem returnMora = new GameItem(moraItem, data.getScoinCost() * forge.getCount()); GameItem returnMora = new GameItem(moraItem, data.getScoinCost() * forge.getCount());
returnItems.add(returnMora); returnItems.add(returnMora);
// Return forge points to the player.
int requiredPoints = data.getForgePoint() * forge.getCount();
int newPoints = Math.min(this.player.getForgePoints() + requiredPoints, 300_000);
this.player.setForgePoints(newPoints);
// Remove the forge queue. // Remove the forge queue.
this.player.getActiveForges().remove(queueId - 1); this.player.getActiveForges().remove(queueId - 1);
this.sendForgeQueueDataNotify(true); this.sendForgeQueueDataNotify(true);

View File

@ -2,8 +2,6 @@ package emu.grasscutter.game.managers.stamina;
import ch.qos.logback.classic.Logger; import ch.qos.logback.classic.Logger;
import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter;
import emu.grasscutter.command.commands.NoStaminaCommand;
import emu.grasscutter.data.GameData;
import emu.grasscutter.game.avatar.Avatar; import emu.grasscutter.game.avatar.Avatar;
import emu.grasscutter.game.entity.EntityAvatar; import emu.grasscutter.game.entity.EntityAvatar;
import emu.grasscutter.game.entity.GameEntity; import emu.grasscutter.game.entity.GameEntity;
@ -112,8 +110,8 @@ public class StaminaManager {
}}; }};
private final Logger logger = Grasscutter.getLogger(); private final Logger logger = Grasscutter.getLogger();
public final static int GlobalCharacterMaximumStamina = 24000; public final static int GlobalCharacterMaximumStamina = PlayerProperty.PROP_MAX_STAMINA.getMax();
public final static int GlobalVehicleMaxStamina = 24000; public final static int GlobalVehicleMaxStamina = PlayerProperty.PROP_MAX_STAMINA.getMax();
private Position currentCoordinates = new Position(0, 0, 0); private Position currentCoordinates = new Position(0, 0, 0);
private Position previousCoordinates = new Position(0, 0, 0); private Position previousCoordinates = new Position(0, 0, 0);
private MotionState currentState = MotionState.MOTION_STATE_STANDBY; private MotionState currentState = MotionState.MOTION_STATE_STANDBY;
@ -285,14 +283,13 @@ public class StaminaManager {
// Returns new stamina and sends PlayerPropNotify or VehicleStaminaNotify // Returns new stamina and sends PlayerPropNotify or VehicleStaminaNotify
public int setStamina(GameSession session, String reason, int newStamina, boolean isCharacterStamina) { public int setStamina(GameSession session, String reason, int newStamina, boolean isCharacterStamina) {
// Target Player // Target Player
if (!GAME_OPTIONS.staminaUsage || session.getPlayer().getStamina()) { if (!GAME_OPTIONS.staminaUsage || session.getPlayer().getUnlimitedStamina()) {
newStamina = getMaxCharacterStamina(); newStamina = getMaxCharacterStamina();
} }
// set stamina if is character stamina // set stamina if is character stamina
if (isCharacterStamina) { if (isCharacterStamina) {
player.setProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA, newStamina); player.setProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA, newStamina);
session.send(new PacketPlayerPropNotify(player, PlayerProperty.PROP_CUR_PERSIST_STAMINA));
} else { } else {
vehicleStamina = newStamina; vehicleStamina = newStamina;
session.send(new PacketVehicleStaminaNotify(vehicleId, ((float) newStamina) / 100)); session.send(new PacketVehicleStaminaNotify(vehicleId, ((float) newStamina) / 100));

View File

@ -7,6 +7,7 @@ import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.PlayerLevelData; import emu.grasscutter.data.excels.PlayerLevelData;
import emu.grasscutter.data.excels.WeatherData; import emu.grasscutter.data.excels.WeatherData;
import emu.grasscutter.database.DatabaseHelper; import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.database.DatabaseManager;
import emu.grasscutter.game.Account; import emu.grasscutter.game.Account;
import emu.grasscutter.game.CoopRequest; import emu.grasscutter.game.CoopRequest;
import emu.grasscutter.game.ability.AbilityManager; import emu.grasscutter.game.ability.AbilityManager;
@ -42,6 +43,7 @@ import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.game.props.ClimateType; import emu.grasscutter.game.props.ClimateType;
import emu.grasscutter.game.props.PlayerProperty; import emu.grasscutter.game.props.PlayerProperty;
import emu.grasscutter.game.props.SceneType; import emu.grasscutter.game.props.SceneType;
import emu.grasscutter.game.props.WatcherTriggerType;
import emu.grasscutter.game.quest.QuestManager; import emu.grasscutter.game.quest.QuestManager;
import emu.grasscutter.game.shop.ShopLimit; import emu.grasscutter.game.shop.ShopLimit;
import emu.grasscutter.game.tower.TowerData; import emu.grasscutter.game.tower.TowerData;
@ -75,6 +77,9 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import lombok.Getter; import lombok.Getter;
import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.*; import java.util.*;
import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.LinkedBlockingQueue;
@ -180,6 +185,7 @@ public class Player {
private long springLastUsed; private long springLastUsed;
private HashMap<String, MapMark> mapMarks; private HashMap<String, MapMark> mapMarks;
private int nextResinRefresh; private int nextResinRefresh;
private int lastDailyReset;
@Deprecated @Deprecated
@SuppressWarnings({"rawtypes", "unchecked"}) // Morphia only! @SuppressWarnings({"rawtypes", "unchecked"}) // Morphia only!
@ -253,14 +259,14 @@ public class Player {
this.teamManager = new TeamManager(this); this.teamManager = new TeamManager(this);
this.birthday = new PlayerBirthday(); this.birthday = new PlayerBirthday();
this.codex = new PlayerCodex(this); this.codex = new PlayerCodex(this);
this.setProperty(PlayerProperty.PROP_PLAYER_LEVEL, 1); this.setProperty(PlayerProperty.PROP_PLAYER_LEVEL, 1, false);
this.setProperty(PlayerProperty.PROP_IS_SPRING_AUTO_USE, 1); this.setProperty(PlayerProperty.PROP_IS_SPRING_AUTO_USE, 1, false);
this.setProperty(PlayerProperty.PROP_SPRING_AUTO_USE_PERCENT, 50); this.setProperty(PlayerProperty.PROP_SPRING_AUTO_USE_PERCENT, 50, false);
this.setProperty(PlayerProperty.PROP_IS_FLYABLE, 1); this.setProperty(PlayerProperty.PROP_IS_FLYABLE, 1, false);
this.setProperty(PlayerProperty.PROP_IS_TRANSFERABLE, 1); this.setProperty(PlayerProperty.PROP_IS_TRANSFERABLE, 1, false);
this.setProperty(PlayerProperty.PROP_MAX_STAMINA, 24000); this.setProperty(PlayerProperty.PROP_MAX_STAMINA, 24000, false);
this.setProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA, 24000); this.setProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA, 24000, false);
this.setProperty(PlayerProperty.PROP_PLAYER_RESIN, 160); this.setProperty(PlayerProperty.PROP_PLAYER_RESIN, 160, false);
this.getFlyCloakList().add(140001); this.getFlyCloakList().add(140001);
this.getNameCardList().add(210001); this.getNameCardList().add(210001);
this.getPos().set(GameConstants.START_POSITION); this.getPos().set(GameConstants.START_POSITION);
@ -290,7 +296,9 @@ public class Player {
} }
public Account getAccount() { public Account getAccount() {
return account; if (this.account == null)
this.account = DatabaseHelper.getAccountById(Integer.toString(this.id));
return this.account;
} }
public void setAccount(Account account) { public void setAccount(Account account) {
@ -429,12 +437,13 @@ public class Player {
return this.getProperty(PlayerProperty.PROP_PLAYER_LEVEL); return this.getProperty(PlayerProperty.PROP_PLAYER_LEVEL);
} }
public void setLevel(int level) { public boolean setLevel(int level) {
this.setProperty(PlayerProperty.PROP_PLAYER_LEVEL, level); if (this.setProperty(PlayerProperty.PROP_PLAYER_LEVEL, level)) {
this.sendPacket(new PacketPlayerPropNotify(this, PlayerProperty.PROP_PLAYER_LEVEL));
this.updateWorldLevel(); this.updateWorldLevel();
this.updateProfile(); this.updateProfile();
return true;
}
return false;
} }
public int getExp() { public int getExp() {
@ -445,47 +454,58 @@ public class Player {
return this.getProperty(PlayerProperty.PROP_PLAYER_WORLD_LEVEL); return this.getProperty(PlayerProperty.PROP_PLAYER_WORLD_LEVEL);
} }
public void setWorldLevel(int level) { public boolean setWorldLevel(int level) {
this.setProperty(PlayerProperty.PROP_PLAYER_WORLD_LEVEL, level); if (this.setProperty(PlayerProperty.PROP_PLAYER_WORLD_LEVEL, level)) {
this.sendPacket(new PacketPlayerPropNotify(this, PlayerProperty.PROP_PLAYER_WORLD_LEVEL)); if (this.world.getHost() == this) // Don't update World's WL if we are in someone else's world
this.world.setWorldLevel(level);
this.updateProfile(); this.updateProfile();
return true;
}
return false;
}
public int getForgePoints() {
return this.getProperty(PlayerProperty.PROP_PLAYER_FORGE_POINT);
}
public boolean setForgePoints(int value) {
if (value == this.getForgePoints()) {
return true;
}
return this.setProperty(PlayerProperty.PROP_PLAYER_FORGE_POINT, value);
} }
public int getPrimogems() { public int getPrimogems() {
return this.getProperty(PlayerProperty.PROP_PLAYER_HCOIN); return this.getProperty(PlayerProperty.PROP_PLAYER_HCOIN);
} }
public void setPrimogems(int primogem) { public boolean setPrimogems(int primogem) {
this.setProperty(PlayerProperty.PROP_PLAYER_HCOIN, primogem); return this.setProperty(PlayerProperty.PROP_PLAYER_HCOIN, primogem);
this.sendPacket(new PacketPlayerPropNotify(this, PlayerProperty.PROP_PLAYER_HCOIN));
} }
public int getMora() { public int getMora() {
return this.getProperty(PlayerProperty.PROP_PLAYER_SCOIN); return this.getProperty(PlayerProperty.PROP_PLAYER_SCOIN);
} }
public void setMora(int mora) { public boolean setMora(int mora) {
this.setProperty(PlayerProperty.PROP_PLAYER_SCOIN, mora); return this.setProperty(PlayerProperty.PROP_PLAYER_SCOIN, mora);
this.sendPacket(new PacketPlayerPropNotify(this, PlayerProperty.PROP_PLAYER_SCOIN));
} }
public int getCrystals() { public int getCrystals() {
return this.getProperty(PlayerProperty.PROP_PLAYER_MCOIN); return this.getProperty(PlayerProperty.PROP_PLAYER_MCOIN);
} }
public void setCrystals(int crystals) { public boolean setCrystals(int crystals) {
this.setProperty(PlayerProperty.PROP_PLAYER_MCOIN, crystals); return this.setProperty(PlayerProperty.PROP_PLAYER_MCOIN, crystals);
this.sendPacket(new PacketPlayerPropNotify(this, PlayerProperty.PROP_PLAYER_MCOIN));
} }
public int getHomeCoin() { public int getHomeCoin() {
return this.getProperty(PlayerProperty.PROP_PLAYER_HOME_COIN); return this.getProperty(PlayerProperty.PROP_PLAYER_HOME_COIN);
} }
public void setHomeCoin(int coin) { public boolean setHomeCoin(int coin) {
this.setProperty(PlayerProperty.PROP_PLAYER_HOME_COIN, coin); return this.setProperty(PlayerProperty.PROP_PLAYER_HOME_COIN, coin);
this.sendPacket(new PacketPlayerPropNotify(this, PlayerProperty.PROP_PLAYER_HOME_COIN));
} }
private int getExpRequired(int level) { private int getExpRequired(int level) {
PlayerLevelData levelData = GameData.getPlayerLevelDataMap().get(level); PlayerLevelData levelData = GameData.getPlayerLevelDataMap().get(level);
@ -523,9 +543,6 @@ public class Player {
// Set exp // Set exp
this.setProperty(PlayerProperty.PROP_PLAYER_EXP, exp); this.setProperty(PlayerProperty.PROP_PLAYER_EXP, exp);
// Update player with packet
this.sendPacket(new PacketPlayerPropNotify(this, PlayerProperty.PROP_PLAYER_EXP));
} }
private void updateWorldLevel() { private void updateWorldLevel() {
@ -544,7 +561,6 @@ public class Player {
0; 0;
if (newWorldLevel != currentWorldLevel) { if (newWorldLevel != currentWorldLevel) {
this.getWorld().setWorldLevel(newWorldLevel);
this.setWorldLevel(newWorldLevel); this.setWorldLevel(newWorldLevel);
} }
} }
@ -566,7 +582,7 @@ public class Player {
} }
public TowerData getTowerData() { public TowerData getTowerData() {
if(towerData==null){ if (towerData == null) {
// because of mistake, null may be saved as storage at some machine, this if can be removed in future // because of mistake, null may be saved as storage at some machine, this if can be removed in future
towerData = new TowerData(); towerData = new TowerData();
} }
@ -599,7 +615,11 @@ public class Player {
} }
public boolean setProperty(PlayerProperty prop, int value) { public boolean setProperty(PlayerProperty prop, int value) {
return setPropertyWithSanityCheck(prop, value); return setPropertyWithSanityCheck(prop, value, true);
}
public boolean setProperty(PlayerProperty prop, int value, boolean sendPacket) {
return setPropertyWithSanityCheck(prop, value, sendPacket);
} }
public int getProperty(PlayerProperty prop) { public int getProperty(PlayerProperty prop) {
@ -794,6 +814,14 @@ public class Player {
return showAvatarList; return showAvatarList;
} }
public int getLastDailyReset() {
return this.lastDailyReset;
}
public void setLastDailyReset(int value) {
this.lastDailyReset = value;
}
public boolean inMoonCard() { public boolean inMoonCard() {
return moonCard; return moonCard;
} }
@ -926,12 +954,10 @@ public class Player {
} }
this.save(); this.save();
} }
public boolean getStamina() { public boolean getUnlimitedStamina() {
// Get Stamina
return stamina; return stamina;
} }
public void setStamina(boolean stamina) { public void setUnlimitedStamina(boolean stamina) {
// Set Stamina
this.stamina = stamina; this.stamina = stamina;
} }
public boolean inGodmode() { public boolean inGodmode() {
@ -1268,6 +1294,7 @@ public class Player {
public void loadBattlePassManager() { public void loadBattlePassManager() {
if (this.battlePassManager != null) return; if (this.battlePassManager != null) return;
this.battlePassManager = DatabaseHelper.loadBattlePass(this); this.battlePassManager = DatabaseHelper.loadBattlePass(this);
this.battlePassManager.getMissions().values().removeIf(mission -> mission.getData() == null);
} }
public AbilityManager getAbilityManager() { public AbilityManager getAbilityManager() {
@ -1313,6 +1340,10 @@ public class Player {
this.resetSendPlayerLocTime(); this.resetSendPlayerLocTime();
} }
} }
// Handle daily reset.
this.doDailyReset();
// Expedition // Expedition
var timeNow = Utils.getCurrentSeconds(); var timeNow = Utils.getCurrentSeconds();
var needNotify = false; var needNotify = false;
@ -1337,8 +1368,24 @@ public class Player {
this.getResinManager().rechargeResin(); this.getResinManager().rechargeResin();
} }
private void doDailyReset() {
// Check if we should execute a daily reset on this tick.
int currentTime = Utils.getCurrentSeconds();
var currentDate = LocalDate.ofInstant(Instant.ofEpochSecond(currentTime), ZoneId.systemDefault());
var lastResetDate = LocalDate.ofInstant(Instant.ofEpochSecond(this.getLastDailyReset()), ZoneId.systemDefault());
if (!currentDate.isAfter(lastResetDate)) {
return;
}
// We should - now execute all the resetting logic we need.
// Reset forge points.
this.setForgePoints(300_000);
// Done. Update last reset time.
this.setLastDailyReset(currentTime);
}
public void resetSendPlayerLocTime() { public void resetSendPlayerLocTime() {
this.nextSendPlayerLocTime = System.currentTimeMillis() + 5000; this.nextSendPlayerLocTime = System.currentTimeMillis() + 5000;
@ -1405,8 +1452,8 @@ public class Player {
world.addPlayer(this); world.addPlayer(this);
// Multiplayer setting // Multiplayer setting
this.setProperty(PlayerProperty.PROP_PLAYER_MP_SETTING_TYPE, this.getMpSetting().getNumber()); this.setProperty(PlayerProperty.PROP_PLAYER_MP_SETTING_TYPE, this.getMpSetting().getNumber(), false);
this.setProperty(PlayerProperty.PROP_IS_MP_MODE_AVAILABLE, 1); this.setProperty(PlayerProperty.PROP_IS_MP_MODE_AVAILABLE, 1, false);
// Packets // Packets
session.send(new PacketPlayerDataNotify(this)); // Player data session.send(new PacketPlayerDataNotify(this)); // Player data
@ -1425,6 +1472,9 @@ public class Player {
getTodayMoonCard(); // The timer works at 0:0, some users log in after that, use this method to check if they have received a reward today or not. If not, send the reward. getTodayMoonCard(); // The timer works at 0:0, some users log in after that, use this method to check if they have received a reward today or not. If not, send the reward.
// Battle Pass trigger
this.getBattlePassManager().triggerMission(WatcherTriggerType.TRIGGER_LOGIN);
this.furnitureManager.onLogin(); this.furnitureManager.onLogin();
// Home // Home
home = GameHome.getByUid(getUid()); home = GameHome.getByUid(getUid());
@ -1478,10 +1528,6 @@ public class Player {
// Call quit event. // Call quit event.
PlayerQuitEvent event = new PlayerQuitEvent(this); event.call(); PlayerQuitEvent event = new PlayerQuitEvent(this); event.call();
//reset wood
getDeforestationManager().resetWood();
}catch (Throwable e){ }catch (Throwable e){
e.printStackTrace(); e.printStackTrace();
Grasscutter.getLogger().warn("Player (UID {}) save failure", getUid()); Grasscutter.getLogger().warn("Player (UID {}) save failure", getUid());
@ -1515,101 +1561,41 @@ public class Player {
this.messageHandler = messageHandler; this.messageHandler = messageHandler;
} }
private void saveSanityCheckedProperty(PlayerProperty prop, int value) { public int getPropertyMin(PlayerProperty prop) {
getProperties().put(prop.getId(), value); if (prop.getDynamicRange()) {
return switch (prop) {
default -> 0;
};
} else {
return prop.getMin();
}
} }
private boolean setPropertyWithSanityCheck(PlayerProperty prop, int value) { public int getPropertyMax(PlayerProperty prop) {
if (prop == PlayerProperty.PROP_EXP) { // 1001 if (prop.getDynamicRange()) {
if (!(value >= 0)) { return false; } return switch (prop) {
} else if (prop == PlayerProperty.PROP_BREAK_LEVEL) { // 1002 case PROP_CUR_SPRING_VOLUME -> getProperty(PlayerProperty.PROP_MAX_SPRING_VOLUME);
// TODO: implement sanity check case PROP_CUR_PERSIST_STAMINA -> getProperty(PlayerProperty.PROP_MAX_STAMINA);
} else if (prop == PlayerProperty.PROP_SATIATION_VAL) { // 1003 default -> 0;
// TODO: implement sanity check };
} else if (prop == PlayerProperty.PROP_SATIATION_PENALTY_TIME) { // 1004 } else {
// TODO: implement sanity check return prop.getMax();
} else if (prop == PlayerProperty.PROP_LEVEL) { // 4001
if (!(value >= 0 && value <= 90)) { return false; }
} else if (prop == PlayerProperty.PROP_LAST_CHANGE_AVATAR_TIME) { // 10001
// TODO: implement sanity check
} else if (prop == PlayerProperty.PROP_MAX_SPRING_VOLUME) { // 10002
if (!(value >= 0 && value <= SotSManager.GlobalMaximumSpringVolume)) { return false; }
} else if (prop == PlayerProperty.PROP_CUR_SPRING_VOLUME) { // 10003
int playerMaximumSpringVolume = getProperty(PlayerProperty.PROP_MAX_SPRING_VOLUME);
if (!(value >= 0 && value <= playerMaximumSpringVolume)) { return false; }
} else if (prop == PlayerProperty.PROP_IS_SPRING_AUTO_USE) { // 10004
if (!(value >= 0 && value <= 1)) { return false; }
} else if (prop == PlayerProperty.PROP_SPRING_AUTO_USE_PERCENT) { // 10005
if (!(value >= 0 && value <= 100)) { return false; }
} else if (prop == PlayerProperty.PROP_IS_FLYABLE) { // 10006
if (!(0 <= value && value <= 1)) { return false; }
} else if (prop == PlayerProperty.PROP_IS_WEATHER_LOCKED) { // 10007
if (!(0 <= value && value <= 1)) { return false; }
} else if (prop == PlayerProperty.PROP_IS_GAME_TIME_LOCKED) { // 10008
if (!(0 <= value && value <= 1)) { return false; }
} else if (prop == PlayerProperty.PROP_IS_TRANSFERABLE) { // 10009
if (!(0 <= value && value <= 1)) { return false; }
} else if (prop == PlayerProperty.PROP_MAX_STAMINA) { // 10010
if (!(value >= 0 && value <= StaminaManager.GlobalCharacterMaximumStamina)) { return false; }
} else if (prop == PlayerProperty.PROP_CUR_PERSIST_STAMINA) { // 10011
int playerMaximumStamina = getProperty(PlayerProperty.PROP_MAX_STAMINA);
if (!(value >= 0 && value <= playerMaximumStamina)) { return false; }
} else if (prop == PlayerProperty.PROP_CUR_TEMPORARY_STAMINA) { // 10012
// TODO: implement sanity check
} else if (prop == PlayerProperty.PROP_PLAYER_LEVEL) { // 10013
if (!(0 < value && value <= 90)) { return false; }
} else if (prop == PlayerProperty.PROP_PLAYER_EXP) { // 10014
if (!(0 <= value)) { return false; }
} else if (prop == PlayerProperty.PROP_PLAYER_HCOIN) { // 10015
// see PlayerProperty.PROP_PLAYER_HCOIN comments
} else if (prop == PlayerProperty.PROP_PLAYER_SCOIN) { // 10016
// See 10015
} else if (prop == PlayerProperty.PROP_PLAYER_MP_SETTING_TYPE) { // 10017
if (!(0 <= value && value <= 2)) { return false; }
} else if (prop == PlayerProperty.PROP_IS_MP_MODE_AVAILABLE) { // 10018
if (!(0 <= value && value <= 1)) { return false; }
} else if (prop == PlayerProperty.PROP_PLAYER_WORLD_LEVEL) { // 10019
if (!(0 <= value && value <= 8)) { return false; }
} else if (prop == PlayerProperty.PROP_PLAYER_RESIN) { // 10020
// Do not set 160 as a cap, because player can have more than 160 when they use fragile resin.
if (!(0 <= value)) { return false; }
} else if (prop == PlayerProperty.PROP_PLAYER_WAIT_SUB_HCOIN) { // 10022
// TODO: implement sanity check
} else if (prop == PlayerProperty.PROP_PLAYER_WAIT_SUB_SCOIN) { // 10023
// TODO: implement sanity check
} else if (prop == PlayerProperty.PROP_IS_ONLY_MP_WITH_PS_PLAYER) { // 10024
if (!(0 <= value && value <= 1)) { return false; }
} else if (prop == PlayerProperty.PROP_PLAYER_MCOIN) { // 10025
// see 10015
} else if (prop == PlayerProperty.PROP_PLAYER_WAIT_SUB_MCOIN) { // 10026
// TODO: implement sanity check
} else if (prop == PlayerProperty.PROP_PLAYER_LEGENDARY_KEY) { // 10027
// TODO: implement sanity check
} else if (prop == PlayerProperty.PROP_IS_HAS_FIRST_SHARE) { // 10028
// TODO: implement sanity check
} else if (prop == PlayerProperty.PROP_PLAYER_FORGE_POINT) { // 10029
// TODO: implement sanity check
} else if (prop == PlayerProperty.PROP_CUR_CLIMATE_METER) { // 10035
// TODO: implement sanity check
} else if (prop == PlayerProperty.PROP_CUR_CLIMATE_TYPE) { // 10036
// TODO: implement sanity check
} else if (prop == PlayerProperty.PROP_CUR_CLIMATE_AREA_ID) { // 10037
// TODO: implement sanity check
} else if (prop == PlayerProperty.PROP_CUR_CLIMATE_AREA_CLIMATE_TYPE) { // 10038
// TODO: implement sanity check
} else if (prop == PlayerProperty.PROP_PLAYER_WORLD_LEVEL_LIMIT) { // 10039
// TODO: implement sanity check
} else if (prop == PlayerProperty.PROP_PLAYER_WORLD_LEVEL_ADJUST_CD) { // 10040
// TODO: implement sanity check
} else if (prop == PlayerProperty.PROP_PLAYER_LEGENDARY_DAILY_TASK_NUM) { // 10041
// TODO: implement sanity check
} else if (prop == PlayerProperty.PROP_PLAYER_HOME_COIN) { // 10042
if (!(0 <= value)) { return false; }
} else if (prop == PlayerProperty.PROP_PLAYER_WAIT_SUB_HOME_COIN) { // 10043
// TODO: implement sanity check
} }
saveSanityCheckedProperty(prop, value); }
private boolean setPropertyWithSanityCheck(PlayerProperty prop, int value, boolean sendPacket) {
int min = this.getPropertyMin(prop);
int max = this.getPropertyMax(prop);
if (min <= value && value <= max) {
this.properties.put(prop.getId(), value);
if (sendPacket) {
// Update player with packet
this.sendPacket(new PacketPlayerPropNotify(this, prop));
}
return true;
} else {
return false; return false;
} }
}
} }

View File

@ -0,0 +1,18 @@
package emu.grasscutter.game.props;
public enum BattlePassMissionRefreshType {
BATTLE_PASS_MISSION_REFRESH_DAILY (0),
BATTLE_PASS_MISSION_REFRESH_CYCLE_CROSS_SCHEDULE (1), // Weekly
BATTLE_PASS_MISSION_REFRESH_SCHEDULE (2), // Per BP
BATTLE_PASS_MISSION_REFRESH_CYCLE (1); // Event?
private final int value;
BattlePassMissionRefreshType(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}

View File

@ -0,0 +1,26 @@
package emu.grasscutter.game.props;
import emu.grasscutter.net.proto.BattlePassMissionOuterClass.BattlePassMission.MissionStatus;
public enum BattlePassMissionStatus {
MISSION_STATUS_INVALID (0, MissionStatus.MISSION_STATUS_INVALID),
MISSION_STATUS_UNFINISHED (1, MissionStatus.MISSION_STATUS_UNFINISHED),
MISSION_STATUS_FINISHED (2, MissionStatus.MISSION_STATUS_FINISHED),
MISSION_STATUS_POINT_TAKEN (3, MissionStatus.MISSION_STATUS_POINT_TAKEN);
private final int value;
private final MissionStatus missionStatus;
BattlePassMissionStatus(int value, MissionStatus missionStatus) {
this.value = value;
this.missionStatus = missionStatus; // In case proto enum values change later
}
public int getValue() {
return value;
}
public MissionStatus getMissionStatus() {
return missionStatus;
}
}

View File

@ -1,7 +1,13 @@
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 {
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);
}
} }

View File

@ -6,41 +6,42 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
public enum PlayerProperty { public enum PlayerProperty {
PROP_EXP (1001), PROP_NONE (0),
PROP_EXP (1001, 0),
PROP_BREAK_LEVEL (1002), PROP_BREAK_LEVEL (1002),
PROP_SATIATION_VAL (1003), PROP_SATIATION_VAL (1003),
PROP_SATIATION_PENALTY_TIME (1004), PROP_SATIATION_PENALTY_TIME (1004),
PROP_LEVEL (4001), PROP_LEVEL (4001, 0, 90),
PROP_LAST_CHANGE_AVATAR_TIME (10001), PROP_LAST_CHANGE_AVATAR_TIME (10001),
PROP_MAX_SPRING_VOLUME (10002), // Maximum volume of the Statue of the Seven for the player [0, 8500000] PROP_MAX_SPRING_VOLUME (10002, 0, 8_500_000), // Maximum volume of the Statue of the Seven for the player [0, 8500000]
PROP_CUR_SPRING_VOLUME (10003), // Current volume of the Statue of the Seven [0, PROP_MAX_SPRING_VOLUME] PROP_CUR_SPRING_VOLUME (10003, true), // Current volume of the Statue of the Seven [0, PROP_MAX_SPRING_VOLUME]
PROP_IS_SPRING_AUTO_USE (10004), // Auto HP recovery when approaching the Statue of the Seven [0, 1] PROP_IS_SPRING_AUTO_USE (10004, 0, 1), // Auto HP recovery when approaching the Statue of the Seven [0, 1]
PROP_SPRING_AUTO_USE_PERCENT (10005), // Auto HP recovery percentage [0, 100] PROP_SPRING_AUTO_USE_PERCENT (10005, 0, 100), // Auto HP recovery percentage [0, 100]
PROP_IS_FLYABLE (10006), // Are you in a state that disables your flying ability? e.g. new player [0, 1] PROP_IS_FLYABLE (10006, 0, 1), // Are you in a state that disables your flying ability? e.g. new player [0, 1]
PROP_IS_WEATHER_LOCKED (10007), PROP_IS_WEATHER_LOCKED (10007, 0, 1),
PROP_IS_GAME_TIME_LOCKED (10008), PROP_IS_GAME_TIME_LOCKED (10008, 0, 1),
PROP_IS_TRANSFERABLE (10009), PROP_IS_TRANSFERABLE (10009, 0, 1),
PROP_MAX_STAMINA (10010), // Maximum stamina of the player (0 - 24000) PROP_MAX_STAMINA (10010, 0, 24_000), // Maximum stamina of the player (0 - 24000)
PROP_CUR_PERSIST_STAMINA (10011), // Used stamina of the player (0 - PROP_MAX_STAMINA) PROP_CUR_PERSIST_STAMINA (10011, true), // Used stamina of the player (0 - PROP_MAX_STAMINA)
PROP_CUR_TEMPORARY_STAMINA (10012), PROP_CUR_TEMPORARY_STAMINA (10012),
PROP_PLAYER_LEVEL (10013), PROP_PLAYER_LEVEL (10013, 1, 60),
PROP_PLAYER_EXP (10014), PROP_PLAYER_EXP (10014),
PROP_PLAYER_HCOIN (10015), // Primogem (-inf, +inf) PROP_PLAYER_HCOIN (10015), // Primogem (-inf, +inf)
// It is known that Mihoyo will make Primogem negative in the cases that a player spends // It is known that Mihoyo will make Primogem negative in the cases that a player spends
// his gems and then got a money refund, so negative is allowed. // his gems and then got a money refund, so negative is allowed.
PROP_PLAYER_SCOIN (10016), // Mora [0, +inf) PROP_PLAYER_SCOIN (10016, 0), // Mora [0, +inf)
PROP_PLAYER_MP_SETTING_TYPE (10017), // Do you allow other players to join your game? [0=no 1=direct 2=approval] PROP_PLAYER_MP_SETTING_TYPE (10017, 0, 2), // Do you allow other players to join your game? [0=no 1=direct 2=approval]
PROP_IS_MP_MODE_AVAILABLE (10018), // 0 if in quest or something that disables MP [0, 1] PROP_IS_MP_MODE_AVAILABLE (10018, 0, 1), // 0 if in quest or something that disables MP [0, 1]
PROP_PLAYER_WORLD_LEVEL (10019), // [0, 8] PROP_PLAYER_WORLD_LEVEL (10019, 0, 8), // [0, 8]
PROP_PLAYER_RESIN (10020), // Original Resin [0, +inf) PROP_PLAYER_RESIN (10020, 0, 2000), // Original Resin [0, 2000] - note that values above 160 require refills
PROP_PLAYER_WAIT_SUB_HCOIN (10022), PROP_PLAYER_WAIT_SUB_HCOIN (10022),
PROP_PLAYER_WAIT_SUB_SCOIN (10023), PROP_PLAYER_WAIT_SUB_SCOIN (10023),
PROP_IS_ONLY_MP_WITH_PS_PLAYER (10024), // Is only MP with PlayStation players? [0, 1] PROP_IS_ONLY_MP_WITH_PS_PLAYER (10024, 0, 1), // Is only MP with PlayStation players? [0, 1]
PROP_PLAYER_MCOIN (10025), // Genesis Crystal (-inf, +inf) see 10015 PROP_PLAYER_MCOIN (10025), // Genesis Crystal (-inf, +inf) see 10015
PROP_PLAYER_WAIT_SUB_MCOIN (10026), PROP_PLAYER_WAIT_SUB_MCOIN (10026),
PROP_PLAYER_LEGENDARY_KEY (10027), PROP_PLAYER_LEGENDARY_KEY (10027),
PROP_IS_HAS_FIRST_SHARE (10028), PROP_IS_HAS_FIRST_SHARE (10028),
PROP_PLAYER_FORGE_POINT (10029), PROP_PLAYER_FORGE_POINT (10029, 0, 300_000),
PROP_CUR_CLIMATE_METER (10035), PROP_CUR_CLIMATE_METER (10035),
PROP_CUR_CLIMATE_TYPE (10036), PROP_CUR_CLIMATE_TYPE (10036),
PROP_CUR_CLIMATE_AREA_ID (10037), PROP_CUR_CLIMATE_AREA_ID (10037),
@ -48,22 +49,55 @@ public enum PlayerProperty {
PROP_PLAYER_WORLD_LEVEL_LIMIT (10039), PROP_PLAYER_WORLD_LEVEL_LIMIT (10039),
PROP_PLAYER_WORLD_LEVEL_ADJUST_CD (10040), PROP_PLAYER_WORLD_LEVEL_ADJUST_CD (10040),
PROP_PLAYER_LEGENDARY_DAILY_TASK_NUM (10041), PROP_PLAYER_LEGENDARY_DAILY_TASK_NUM (10041),
PROP_PLAYER_HOME_COIN (10042), // Realm currency [0, +inf) PROP_PLAYER_HOME_COIN (10042, 0), // Realm currency [0, +inf)
PROP_PLAYER_WAIT_SUB_HOME_COIN (10043); PROP_PLAYER_WAIT_SUB_HOME_COIN (10043);
private final int id; private static final int inf = Integer.MAX_VALUE; // Maybe this should be something else?
private final int id, min, max;
private final boolean dynamicRange;
private static final Int2ObjectMap<PlayerProperty> map = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<PlayerProperty> map = new Int2ObjectOpenHashMap<>();
static { static {
Stream.of(values()).forEach(e -> map.put(e.getId(), e)); Stream.of(values()).forEach(e -> map.put(e.getId(), e));
} }
PlayerProperty(int id) { PlayerProperty(int id, int min, int max, boolean dynamicRange) {
this.id = id; this.id = id;
this.min = min;
this.max = max;
this.dynamicRange = dynamicRange;
}
PlayerProperty(int id, int min) {
this(id, min, inf, false);
}
PlayerProperty(int id, int min, int max) {
this(id, min, max, false);
}
PlayerProperty(int id) {
this(id, Integer.MIN_VALUE, inf, false);
}
PlayerProperty(int id, boolean dynamicRange) {
this(id, Integer.MIN_VALUE, inf, dynamicRange);
} }
public int getId() { public int getId() {
return id; return this.id;
}
public int getMin() {
return this.min;
}
public int getMax() {
return this.max;
}
public boolean getDynamicRange() {
return dynamicRange;
} }
public static PlayerProperty getPropById(int value) { public static PlayerProperty getPropById(int value) {

View File

@ -0,0 +1,337 @@
package emu.grasscutter.game.props;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
public enum WatcherTriggerType {
TRIGGER_NONE (0),
TRIGGER_COMBAT_CONFIG_COMMON (1),
TRIGGER_ELEMENT_VIEW (2),
TRIGGER_ENTER_AIRFLOW (5),
TRIGGER_NEW_MONSTER (6),
TRIGGER_NEW_AFFIX (8),
TRIGGER_CHANGE_INPUT_DEVICE_TYPE (9),
TRIGGER_PAIMON_ANGRY_VOICE_EASTER_EGG (10),
TRIGGER_WIND_CRYSTAL (11),
TRIGGER_ELEMENT_BALL (101),
TRIGGER_WORLD_LEVEL_UP (102),
TRIGGER_DUNGEON_ENTRY_TO_BE_EXPLORED (103),
TRIGGER_UNLOCK_GATE_TEMPLE (104),
TRIGGER_UNLOCK_AREA (105),
TRIGGER_UNLOCK_TRANS_POINT (106),
TRIGGER_OPEN_CHEST_WITH_GADGET_ID (107),
TRIGGER_CITY_LEVEL_UP (108),
TRIGGER_MONSTER_DIE (109),
TRIGGER_PLATFORM_START_MOVE (110),
TRIGGER_GROUP_NOTIFY (111),
TRIGGER_ELEMENT_TYPE_CHANGE (112),
TRIGGER_GADGET_INTERACTABLE (113),
TRIGGER_COLLECT_SET_OF_READINGS (114),
TRIGGER_TELEPORT_WITH_CERTAIN_PORTAL (115),
TRIGGER_WORLD_GATHER (116),
TRIGGER_TAKE_GENERAL_REWARD (117),
TRIGGER_BATTLE_FOR_MONSTER_DIE_OR (118),
TRIGGER_BATTLE_FOR_MONSTER_DIE_AND (119),
TRIGGER_OPEN_WORLD_CHEST (120),
TRIGGER_ENTER_CLIMATE_AREA (121),
TRIGGER_UNLOCK_SCENE_POINT (122),
TRIGGER_INTERACT_GADGET_WITH_INTERACT_ID (123),
TRIGGER_OBTAIN_AVATAR (201),
TRIGGER_PLAYER_LEVEL (202),
TRIGGER_AVATAR_UPGRADE (203),
TRIGGER_AVATAR_PROMOTE (204),
TRIGGER_WEAPON_UPGRADE (205),
TRIGGER_WEAPON_PROMOTE (206),
TRIGGER_RELIQUARY_UPGRADE (207),
TRIGGER_WEAR_RELIQUARY (208),
TRIGGER_UPGRADE_TALENT (209),
TRIGGER_UNLOCK_RECIPE (210),
TRIGGER_RELIQUARY_SET_NUM (211),
TRIGGER_OBTAIN_MATERIAL_NUM (212),
TRIGGER_OBTAIN_RELIQUARY_NUM (213),
TRIGGER_GACHA_NUM (214),
TRIGGER_ANY_RELIQUARY_UPGRADE (215),
TRIGGER_FETTER_LEVEL_AVATAR_NUM (216),
TRIGGER_SKILLED_AT_RECIPE (217),
TRIGGER_RELIQUARY_UPGRADE_EQUAL_RANK_LEVEL (218),
TRIGGER_SPECIFIED_WEAPON_UPGRADE (219),
TRIGGER_SPECIFIED_WEAPON_AWAKEN (220),
TRIGGER_UNLOCK_SPECIFIC_RECIPE_OR (221),
TRIGGER_POSSESS_MATERIAL_NUM (222),
TRIGGER_EXHIBITION_ACCUMULABLE_VALUE (223),
TRIGGER_EXHIBITION_REPLACEABLE_VALUE_SETTLE_NUM (224),
TRIGGER_ANY_WEAPON_UPGRADE_NUM (225),
TRIGGER_ANY_RELIQUARY_UPGRADE_NUM (226),
TRIGGER_ACTIVITY_SCORE_EXCEED_VALUE (227),
TRIGGER_UNLOCK_SPECIFIC_FORGE_OR (228),
TRIGGER_UNLOCK_SPECIFIC_ANIMAL_CODEX (229),
TRIGGER_OBTAIN_ITEM_NUM (230),
TRIGGER_CAPTURE_ANIMAL (231),
TRIGGER_DAILY_TASK (301),
TRIGGER_RAND_TASK (302),
TRIGGER_AVATAR_EXPEDITION (303),
TRIGGER_FINISH_TOWER_LEVEL (304),
TRIGGER_WORLD_BOSS_REWARD (306),
TRIGGER_FINISH_DUNGEON (307),
TRIGGER_START_AVATAR_EXPEDITION (308),
TRIGGER_OPEN_BLOSSOM_CHEST (309),
TRIGGER_FINISH_BLOSSOM_PROGRESS (310),
TRIGGER_DONE_TOWER_GADGET_UNHURT (311),
TRIGGER_DONE_TOWER_STARS (312),
TRIGGER_DONE_TOWER_UNHURT (313),
TRIGGER_STEAL_FOOD_TIMES (314),
TRIGGER_DONE_DUNGEON_WITH_SAME_ELEMENT_AVATARS (315),
TRIGGER_GROUP_FLIGHT_CHALLENGE_REACH_POINTS (316),
TRIGGER_FINISH_DAILY_DELIVERY_NUM (317),
TRIGGER_TOWER_STARS_NUM (318),
TRIGGER_FINISH_SPECIFED_TYPE_BLOSSOM_NUM (319),
TRIGGER_FINISH_SPECIFED_TYPE_BLOSSOM_CLIMATE_METER (320),
TRIGGER_FINISH_BLOSSOM_GROUP_VARIABLE_GT (321),
TRIGGER_EFFIGY_CHALLENGE_SCORE (322),
TRIGGER_FINISH_ROUTINE (323),
TRIGGER_ACTIVITY_EXPEDITION_FINISH (324),
TRIGGER_ACTIVITY_CHANNELLER_SLAB_FINISH_ALL_CAMP (325),
TRIGGER_ACTIVITY_CHANNELLER_SLAB_FINISH_ALL_ONEOFF_DUNGEON (326),
TRIGGER_ACTIVITY_CHANNELLER_SLAB_LOOP_DUNGEON_TOTAL_SCORE (327),
TRIGGER_GROUP_SUMMER_TIME_SPRINT_BOAT_REACH_POINTS (328),
TRIGGER_WEEKLY_BOSS_KILL (329),
TRIGGER_BOUNCE_CONJURING_FINISH_COUNT (330),
TRIGGER_BOUNCE_CONJURING_SCORE (331),
TRIGGER_GROUP_VARIABLE_SET_VALUE_TO (332),
TRIGGER_KILL_GADGETS_BY_SPECIFIC_SKILL (333),
TRIGGER_KILL_MONSTERS_WITHOUT_VEHICLE (334),
TRIGGER_KILL_MONSTER_IN_AREA (335),
TRIGGER_ENTER_VEHICLE (336),
TRIGGER_VEHICLE_DURATION (337),
TRIGGER_VEHICLE_FRIENDS (338),
TRIGGER_VEHICLE_KILLED_BY_MONSTER (339),
TRIGGER_VEHICLE_DASH (340),
TRIGGER_DO_COOK (401),
TRIGGER_DO_FORGE (402),
TRIGGER_DO_COMPOUND (403),
TRIGGER_DO_COMBINE (404),
TRIGGER_BUY_SHOP_GOODS (405),
TRIGGER_FORGE_WEAPON (406),
TRIGGER_MP_PLAY_BATTLE_WIN (421),
TRIGGER_KILL_GROUP_MONSTER (422),
TRIGGER_CRUCIBLE_ELEMENT_SCORE (423),
TRIGGER_MP_DUNGEON_TIMES (424),
TRIGGER_MP_KILL_MONSTER_NUM (425),
TRIGGER_CRUCIBLE_MAX_BALL (426),
TRIGGER_CRUCIBLE_MAX_SCORE (427),
TRIGGER_CRUCIBLE_SUBMIT_BALL (428),
TRIGGER_CRUCIBLE_WORLD_LEVEL_SCORE (429),
TRIGGER_MP_PLAY_GROUP_STATISTIC (430),
TRIGGER_KILL_GROUP_SPECIFIC_MONSTER (431),
TRIGGER_REACH_MP_PLAY_SCORE (432),
TRIGGER_REACH_MP_PLAY_RECORD (433),
TRIGGER_TREASURE_MAP_DONE_REGION (434),
TRIGGER_SEA_LAMP_MINI_QUEST (435),
TRIGGER_FINISH_FIND_HILICHURL_LEVEL (436),
TRIGGER_COMBINE_ITEM (437),
TRIGGER_FINISH_CHALLENGE_IN_DURATION (438),
TRIGGER_FINISH_CHALLENGE_LEFT_TIME (439),
TRIGGER_MP_KILL_MONSTER_ID_NUM (440),
TRIGGER_LOGIN (501),
TRIGGER_COST_MATERIAL (502),
TRIGGER_DELIVER_ITEM_TO_SALESMAN (503),
TRIGGER_USE_ITEM (504),
TRIGGER_ACCUMULATE_DAILY_LOGIN (505),
TRIGGER_FINISH_CHALLENGE (601),
TRIGGER_MECHANICUS_UNLOCK_GEAR (602),
TRIGGER_MECHANICUS_LEVELUP_GEAR (603),
TRIGGER_MECHANICUS_DIFFICULT (604),
TRIGGER_MECHANICUS_DIFFICULT_SCORE (605),
TRIGGER_MECHANICUS_KILL_MONSTER (606),
TRIGGER_MECHANICUS_BUILDING_POINT (607),
TRIGGER_MECHANICUS_DIFFICULT_EQ (608),
TRIGGER_MECHANICUS_BATTLE_END (609),
TRIGGER_MECHANICUS_BATTLE_END_EXCAPED_LESS_THAN (610),
TRIGGER_MECHANICUS_BATTLE_END_POINTS_MORE_THAN (611),
TRIGGER_MECHANICUS_BATTLE_END_GEAR_MORE_THAN (612),
TRIGGER_MECHANICUS_BATTLE_END_PURE_GEAR_DAMAGE (613),
TRIGGER_MECHANICUS_BATTLE_END_CARD_PICK_MORE_THAN (614),
TRIGGER_MECHANICUS_BATTLE_END_CARD_TARGET_MORE_THAN (615),
TRIGGER_MECHANICUS_BATTLE_END_BUILD_GEAR_MORE_THAN (616),
TRIGGER_MECHANICUS_BATTLE_END_GEAR_KILL_MORE_THAN (617),
TRIGGER_MECHANICUS_BATTLE_END_ROUND_MORE_THAN (618),
TRIGGER_MECHANICUS_BATTLE_END_ROUND (619),
TRIGGER_MECHANICUS_BATTLE_FIN_CHALLENGE_MORE_THAN (620),
TRIGGER_MECHANICUS_BATTLE_WATCHER_FINISH_COUNT (621),
TRIGGER_MECHANICUS_BATTLE_INTERACT_COUNT (622),
TRIGGER_MECHANICUS_BATTLE_DIFFICULT_ESCAPE (623),
TRIGGER_MECHANICUS_BATTLE_DIFFICULT_GEAR_NUM (624),
TRIGGER_MECHANICUS_BATTLE_DIFFICULT_GEAR_ID_NUM (625),
TRIGGER_FLEUR_FAIR_DUNGEON_FINISH_IN_LIMIT_TIME (626),
TRIGGER_FLEUR_FAIR_DUNGEON_FINISH_KEEP_ENERGY (627),
TRIGGER_FLEUR_FAIR_DUNGEON_FINISH_WITH_GROUP_VARIABLE (628),
TRIGGER_FLEUR_FAIR_DUNGEON_FINISH_WITH_BUFF_NUM (629),
TRIGGER_FLEUR_FAIR_DUNGEON_MISSION_FINISH (630),
TRIGGER_FINISH_DUNGEON_AND_CHALLENGE_REMAIN_TIME_GREATER_THAN (631),
TRIGGER_FINISH_DUNGEON_WITH_MIST_TRIAL_STAT (632),
TRIGGER_DUNGEON_MIST_TRIAL_STAT (633),
TRIGGER_DUNGEON_ELEMENT_REACTION_NUM (634),
TRIGGER_LEVEL_AVATAR_FINISH_DUNGEON_COUNT (635),
TRIGGER_CHESS_REACH_LEVEL (636),
TRIGGER_CHESS_DUNGEON_ADD_SCORE (637),
TRIGGER_CHESS_DUNGEON_SUCC_WITH_ESCAPED_MONSTERS_LESS_THAN (638),
TRIGGER_CHESS_DUNGEON_SUCC_WITH_TOWER_COUNT_LESS_OR_EQUAL (639),
TRIGGER_CHESS_DUNGEON_SUCC_WITH_CARD_COUNT_LESS_OR_EQUAL (640),
TRIGGER_CHESS_DUNGEON_SUCC_WITH_CARD_COUNT_GREATER_THAN (641),
TRIGGER_CHESS_KILL_MONSTERS (642),
TRIGGER_CHESS_COST_BUILDING_POINTS (643),
TRIGGER_SUMO_STAGE_SCORE_REACH (644),
TRIGGER_SUMO_TOTAL_MAX_SCORE_REACH (645),
TRIGGER_ROGUE_DESTROY_GADGET_NUM (646),
TRIGGER_ROGUE_KILL_MONSTER_NUM (647),
TRIGGER_ROGUE_FINISH_WITHOUT_USING_SPRING_CELL (649),
TRIGGER_ROGUE_FINISH_ALL_CHALLENGE_CELL (650),
TRIGGER_ROGUE_FINISH_WITH_AVATAR_ELEMENT_TYPE_NUM_LESS_THAN (651),
TRIGGER_ROGUE_FINISH_WITH_AVATAR_NUM_LESS_THAN (652),
TRIGGER_ROGUE_FINISH_NO_AVATAR_DEAD (653),
TRIGGER_ROGUE_SHIKIGAMI_UPGRADE (654),
TRIGGER_ROGUE_CURSE_NUM (655),
TRIGGER_ROGUE_SELECT_CARD_NUM (656),
TRIGGER_FINISH_QUEST_AND (700),
TRIGGER_FINISH_QUEST_OR (701),
TRIGGER_DAILY_TASK_VAR_EQUAL (702),
TRIGGER_QUEST_GLOBAL_VAR_EQUAL (703),
TRIGGER_TALK_NUM (704),
TRIGGER_FINISH_PARENT_QUEST_AND (705),
TRIGGER_FINISH_PARENT_QUEST_OR (706),
TRIGGER_ELEMENT_REACTION_TIMELIMIT_NUM (800),
TRIGGER_ELEMENT_REACTION_TIMELIMIT_KILL_NUM (801),
TRIGGER_ELEMENT_REACTION_TIMELIMIT_DAMAGE_NUM (802),
TRIGGER_ABILITY_STATE_PASS_TIME (803),
TRIGGER_MAX_CRITICAL_DAMAGE (804),
TRIGGER_FULL_SATIATION_TEAM_AVATAR_NUM (805),
TRIGGER_KILLED_BY_CERTAIN_MONSTER (806),
TRIGGER_CUR_AVATAR_HURT (807),
TRIGGER_CUR_AVATAR_ABILITY_STATE (808),
TRIGGER_USE_ENERGY_SKILL_NUM_TIMELIMIT (809),
TRIGGER_SHIELD_SOURCE_NUM (810),
TRIGGER_CUR_AVATAR_HURT_BY_SPECIFIC_ABILITY (811),
TRIGGER_KILLED_BY_SPECIFIC_ABILITY (812),
TRIGGER_MAX_DASH_TIME (900),
TRIGGER_MAX_FLY_TIME (901),
TRIGGER_MAX_FLY_MAP_DISTANCE (902),
TRIGGER_SIT_DOWN_IN_POINT (903),
TRIGGER_DASH (904),
TRIGGER_CLIMB (905),
TRIGGER_FLY (906),
TRIGGER_CITY_REPUTATION_LEVEL (930),
TRIGGER_CITY_REPUTATION_FINISH_REQUEST (931),
TRIGGER_HUNTING_FINISH_NUM (932),
TRIGGER_HUNTING_FAIL_NUM (933),
TRIGGER_OFFERING_LEVEL (934),
TRIGGER_MIRACLE_RING_DELIVER_ITEM (935),
TRIGGER_MIRACLE_RING_TAKE_REWARD (936),
TRIGGER_BLESSING_EXCHANGE_PIC_NUM (937),
TRIGGER_BLESSING_REDEEM_REWARD_NUM (938),
TRIGGER_GALLERY_BALLOON_REACH_SCORE (939),
TRIGGER_GALLERY_FALL_REACH_SCORE (940),
TRIGGER_FLEUR_FAIR_MUSIC_GAME_REACH_SCORE (941),
TRIGGER_MAIN_COOP_SAVE_POINT_AND (942),
TRIGGER_MAIN_COOP_SAVE_POINT_OR (943),
TRIGGER_MAIN_COOP_VAR_EQUAL (944),
TRIGGER_FINISH_ALL_ARENA_CHALLENGE_WATCHER_IN_SCHEDULE (945),
TRIGGER_GALLERY_BUOYANT_COMBAT_REACH_SCORE (946),
TRIGGER_BUOYANT_COMBAT_REACH_NEW_SCORE_LEVEL (947),
TRIGGER_PLACE_MIRACLE_RING (948),
TRIGGER_LUNA_RITE_SEARCH (949),
TRIGGER_GALLERY_FISH_REACH_SCORE (950),
TRIGGER_GALLERY_TRIATHLON_REACH_SCORE (951),
TRIGGER_WINTER_CAMP_SNOWMAN_COMPLEIET (952),
TRIGGER_CREATE_CUSTOM_DUNGEON (953),
TRIGGER_PUBLISH_CUSTOM_DUNGEON (954),
TRIGGER_PLAY_OTHER_CUSTOM_DUNGEON (955),
TRIGGER_FINISH_CUSTOM_DUNGEON_OFFICIAL (956),
TRIGGER_CUSTOM_DUNGEON_OFFICIAL_COIN (957),
TRIGGER_OBTAIN_WOOD_TYPE (1000),
TRIGGER_OBTAIN_WOOD_COUNT (1001),
TRIGGER_UNLOCK_FURNITURE_COUNT (1002),
TRIGGER_FURNITURE_MAKE (1003),
TRIGGER_HOME_LEVEL (1004),
TRIGGER_HOME_COIN (1005),
TRIGGER_HOME_COMFORT_LEVEL (1006),
TRIGGER_HOME_LIMITED_SHOP_BUY (1007),
TRIGGER_FURNITURE_SUITE_TYPE (1008),
TRIGGER_ARRANGEMENT_FURNITURE_COUNT (1009),
TRIGGER_ENTER_SELF_HOME (1010),
TRIGGER_HOME_MODULE_COMFORT_VALUE (1011),
TRIGGER_HOME_ENTER_ROOM (1012),
TRIGGER_HOME_AVATAR_IN (1013),
TRIGGER_HOME_AVATAR_REWARD_EVENT_COUNT (1014),
TRIGGER_HOME_AVATAR_TALK_FINISH_COUNT (1015),
TRIGGER_HOME_AVATAR_REWARD_EVENT_ALL_COUNT (1016),
TRIGGER_HOME_AVATAR_TALK_FINISH_ALL_COUNT (1017),
TRIGGER_HOME_AVATAR_FETTER_GET (1018),
TRIGGER_HOME_AVATAR_IN_COUNT (1019),
TRIGGER_HOME_DO_PLANT (1020),
TRIGGER_ARRANGEMENT_FURNITURE (1021),
TRIGGER_HOME_GATHER_COUNT (1022),
TRIGGER_HOME_FIELD_GATHER_COUNT (1023),
TRIGGER_HOME_UNLOCK_BGM_COUNT (1024),
TRIGGER_FISHING_SUCC_NUM (1100),
TRIGGER_FISHING_KEEP_BONUS (1101),
TRIGGER_EMPTY_FISH_POOL (1102),
TRIGGER_FISHING_FAIL_NUM (1103),
TRIGGER_SHOCK_FISH_NUM (1104),
TRIGGER_PLANT_FLOWER_SET_WISH (1105),
TRIGGER_PLANT_FLOWER_GIVE_FLOWER (1106),
TRIGGER_PLANT_FLOWER_OBTAIN_FLOWER_TYPE (1107),
TRIGGER_PLANT_FLOWER_COMMON_OBTAIN_FLOWER_TYPE (1108),
TRIGGER_FINISH_LANV2_PROJECTION_LEVEL (1111),
TRIGGER_GALLERY_SALVAGE_REACH_SCORE (1112),
TRIGGER_LANV2_FIREWORKS_CHALLENGE_REACH_SCORE (1113),
TRIGGER_POTION_STAGE_LEVEL_PASS_NUM (1115),
TRIGGER_POTION_STAGE_OBTAIN_MEDAL_NUM (1116),
TRIGGER_POTION_STAGE_REACH_TOTAL_SCORE (1117),
TRIGGER_BARTENDER_FINISH_STORY_MODULE (1120),
TRIGGER_BARTENDER_CHALLENGE_MODULE_LEVEL_SCORE (1121),
TRIGGER_BARTENDER_UNLOCK_FORMULA (1122),
TRIGGER_MICHIAE_MATSURI_UNLOCK_CRYSTAL_SKILL_REACH_NUM (1123),
TRIGGER_MICHIAE_MATSURI_FINISH_DARK_CHALLENGE_REACH_NUM (1124),
TRIGGER_CAPTURE_ENV_ANIMAL_REACH_NUM (1125),
TRIGGER_SPICE_MAKE_FORMULA_TIMES (1126),
TRIGGER_SPICE_GIVE_FOOD_TIMES (1127),
TRIGGER_SPICE_MAKE_FORMULA_SUCCESSFUL_TIMES (1128),
TRIGGER_IRODORI_FINISH_FLOWER_THEME (1131),
TRIGGER_IRODORI_FINISH_MASTER_STAGE (1132),
TRIGGER_IRODORI_CHESS_STAGE_REACH_SCORE (1133),
TRIGGER_IRODORI_FINISH_POETRY_THEME (1134),
TRIGGER_PHOTO_FINISH_POS_ID (1135),
TRIGGER_CRYSTAL_LINK_LEVEL_SCORE_REACH (1138),
TRIGGER_CRYSTAL_LINK_TOTAL_MAX_SCORE_REACH (1139);
private final int value;
private static final Int2ObjectMap<WatcherTriggerType> map = new Int2ObjectOpenHashMap<>();
private static final Map<String, WatcherTriggerType> stringMap = new HashMap<>();
static {
Stream.of(values()).forEach(e -> {
map.put(e.getValue(), e);
stringMap.put(e.name(), e);
});
}
private WatcherTriggerType(int value) {
this.value = value;
}
public int getValue() {
return value;
}
public static WatcherTriggerType getTypeByValue(int value) {
return map.getOrDefault(value, TRIGGER_NONE);
}
public static WatcherTriggerType getTypeByName(String name) {
return stringMap.getOrDefault(name, TRIGGER_NONE);
}
}

View File

@ -9,6 +9,7 @@ import emu.grasscutter.server.game.GameServer;
import java.io.FileReader; import java.io.FileReader;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.io.Reader; import java.io.Reader;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import static emu.grasscutter.Configuration.*; import static emu.grasscutter.Configuration.*;
@ -49,6 +50,12 @@ public class TowerScheduleManager {
return data; return data;
} }
public List<Integer> getAllFloors() {
List<Integer> floors = new ArrayList<>(this.getCurrentTowerScheduleData().getEntranceFloorId());
floors.addAll(this.getScheduleFloors());
return floors;
}
public List<Integer> getScheduleFloors() { public List<Integer> getScheduleFloors() {
return getCurrentTowerScheduleData().getSchedules().get(0).getFloorList(); return getCurrentTowerScheduleData().getSchedules().get(0).getFloorList();
} }

View File

@ -5,15 +5,19 @@ import emu.grasscutter.Grasscutter;
import emu.grasscutter.command.CommandMap; import emu.grasscutter.command.CommandMap;
import emu.grasscutter.database.DatabaseHelper; import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.Account; import emu.grasscutter.game.Account;
import emu.grasscutter.game.battlepass.BattlePassMissionManager;
import emu.grasscutter.game.combine.CombineManger; import emu.grasscutter.game.combine.CombineManger;
import emu.grasscutter.game.drop.DropManager; import emu.grasscutter.game.drop.DropManager;
import emu.grasscutter.game.dungeons.DungeonManager; import emu.grasscutter.game.dungeons.DungeonManager;
import emu.grasscutter.game.dungeons.challenge.DungeonChallenge;
import emu.grasscutter.game.expedition.ExpeditionManager; import emu.grasscutter.game.expedition.ExpeditionManager;
import emu.grasscutter.game.gacha.GachaManager; import emu.grasscutter.game.gacha.GachaManager;
import emu.grasscutter.game.managers.InventoryManager; import emu.grasscutter.game.managers.InventoryManager;
import emu.grasscutter.game.managers.MultiplayerManager; import emu.grasscutter.game.managers.MultiplayerManager;
import emu.grasscutter.game.managers.chat.ChatManager; import emu.grasscutter.game.managers.chat.ChatManager;
import emu.grasscutter.game.managers.chat.ChatManagerHandler; import emu.grasscutter.game.managers.chat.ChatManagerHandler;
import emu.grasscutter.game.managers.energy.EnergyManager;
import emu.grasscutter.game.managers.stamina.StaminaManager;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.quest.ServerQuestHandler; import emu.grasscutter.game.quest.ServerQuestHandler;
import emu.grasscutter.game.shop.ShopManager; import emu.grasscutter.game.shop.ShopManager;
@ -51,19 +55,19 @@ public final class GameServer extends KcpServer {
private final Set<World> worlds; private final Set<World> worlds;
private ChatManagerHandler chatManager; private ChatManagerHandler chatManager;
private final InventoryManager inventoryManager; @Getter private final InventoryManager inventoryManager;
private final GachaManager gachaManager; @Getter private final GachaManager gachaManager;
private final ShopManager shopManager; @Getter private final ShopManager shopManager;
private final MultiplayerManager multiplayerManager; @Getter private final MultiplayerManager multiplayerManager;
private final DungeonManager dungeonManager; @Getter private final DungeonManager dungeonManager;
private final ExpeditionManager expeditionManager; @Getter private final ExpeditionManager expeditionManager;
private final CommandMap commandMap; @Getter private final CommandMap commandMap;
private final TaskMap taskMap; @Getter private final TaskMap taskMap;
private final DropManager dropManager; @Getter private final DropManager dropManager;
private final WorldDataManager worldDataManager; @Getter private final WorldDataManager worldDataManager;
@Getter private final BattlePassMissionManager battlePassMissionManager;
private final CombineManger combineManger; @Getter private final CombineManger combineManger;
private final TowerScheduleManager towerScheduleManager; @Getter private final TowerScheduleManager towerScheduleManager;
public GameServer() { public GameServer() {
this(getAdapterInetSocketAddress()); this(getAdapterInetSocketAddress());
@ -81,6 +85,10 @@ public final class GameServer extends KcpServer {
this.init(GameSessionManager.getListener(),channelConfig,address); this.init(GameSessionManager.getListener(),channelConfig,address);
DungeonChallenge.initialize();
EnergyManager.initialize();
StaminaManager.initialize();
this.address = address; this.address = address;
this.packetHandler = new GameServerPacketHandler(PacketHandler.class); this.packetHandler = new GameServerPacketHandler(PacketHandler.class);
this.questHandler = new ServerQuestHandler(); this.questHandler = new ServerQuestHandler();
@ -101,6 +109,8 @@ public final class GameServer extends KcpServer {
this.combineManger = new CombineManger(this); this.combineManger = new CombineManger(this);
this.towerScheduleManager = new TowerScheduleManager(this); this.towerScheduleManager = new TowerScheduleManager(this);
this.worldDataManager = new WorldDataManager(this); this.worldDataManager = new WorldDataManager(this);
this.battlePassMissionManager = new BattlePassMissionManager(this);
// Hook into shutdown event. // Hook into shutdown event.
Runtime.getRuntime().addShutdownHook(new Thread(this::onServerShutdown)); Runtime.getRuntime().addShutdownHook(new Thread(this::onServerShutdown));
} }
@ -129,53 +139,6 @@ public final class GameServer extends KcpServer {
this.chatManager = chatManager; this.chatManager = chatManager;
} }
public InventoryManager getInventoryManager() {
return inventoryManager;
}
public GachaManager getGachaManager() {
return gachaManager;
}
public ShopManager getShopManager() {
return shopManager;
}
public MultiplayerManager getMultiplayerManager() {
return multiplayerManager;
}
public DropManager getDropManager() {
return dropManager;
}
public DungeonManager getDungeonManager() {
return dungeonManager;
}
public ExpeditionManager getExpeditionManager() {
return expeditionManager;
}
public CommandMap getCommandMap() {
return this.commandMap;
}
public CombineManger getCombineManger(){
return this.combineManger;
}
public TowerScheduleManager getTowerScheduleManager() {
return towerScheduleManager;
}
public WorldDataManager getWorldDataManager() {
return worldDataManager;
}
public TaskMap getTaskMap() {
return this.taskMap;
}
private static InetSocketAddress getAdapterInetSocketAddress(){ private static InetSocketAddress getAdapterInetSocketAddress(){
InetSocketAddress inetSocketAddress; InetSocketAddress inetSocketAddress;

View File

@ -0,0 +1,22 @@
package emu.grasscutter.server.packet.recv;
import emu.grasscutter.net.packet.Opcodes;
import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.BuyBattlePassLevelReqOuterClass.BuyBattlePassLevelReq;
import emu.grasscutter.net.packet.PacketHandler;
import emu.grasscutter.server.game.GameSession;
import emu.grasscutter.server.packet.send.PacketBuyBattlePassLevelRsp;
@Opcodes(PacketOpcodes.BuyBattlePassLevelReq)
public class HandlerBuyBattlePassLevelReq extends PacketHandler {
@Override
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
BuyBattlePassLevelReq req = BuyBattlePassLevelReq.parseFrom(payload);
int buyLevel = session.getPlayer().getBattlePassManager().buyLevels(req.getBuyLevel());
session.send(new PacketBuyBattlePassLevelRsp(buyLevel));
}
}

View File

@ -1,5 +1,6 @@
package emu.grasscutter.server.packet.recv; package emu.grasscutter.server.packet.recv;
import emu.grasscutter.net.packet.BasePacket;
import emu.grasscutter.net.packet.Opcodes; import emu.grasscutter.net.packet.Opcodes;
import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.packet.PacketHandler; import emu.grasscutter.net.packet.PacketHandler;
@ -10,5 +11,18 @@ public class HandlerPlayerForceExitReq extends PacketHandler {
@Override @Override
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
// Client should auto disconnect right now // Client should auto disconnect right now
session.send(new BasePacket(PacketOpcodes.PlayerForceExitRsp));
new Thread(){
@Override
public void run() {
try {
Thread.sleep(1000);// disconnect after 1 seconds
} catch (InterruptedException e) {
e.printStackTrace();
}
session.close();
super.run();
}
}.start();
} }
} }

View File

@ -0,0 +1,19 @@
package emu.grasscutter.server.packet.recv;
import emu.grasscutter.net.packet.Opcodes;
import emu.grasscutter.net.packet.PacketHandler;
import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.SetBattlePassViewedReqOuterClass.SetBattlePassViewedReq;
import emu.grasscutter.server.game.GameSession;
import emu.grasscutter.server.packet.send.PacketSetBattlePassViewedRsp;
@Opcodes(PacketOpcodes.SetBattlePassViewedReq)
public class HandlerSetBattlePassViewedReq extends PacketHandler {
@Override
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
var req = SetBattlePassViewedReq.parseFrom(payload);
session.getPlayer().getBattlePassManager().updateViewed();
session.send(new PacketSetBattlePassViewedRsp(req.getScheduleId()));
}
}

View File

@ -10,9 +10,6 @@ import emu.grasscutter.net.proto.SetPlayerPropReqOuterClass.SetPlayerPropReq;
import emu.grasscutter.server.game.GameSession; import emu.grasscutter.server.game.GameSession;
import emu.grasscutter.server.packet.send.PacketSetPlayerPropRsp; import emu.grasscutter.server.packet.send.PacketSetPlayerPropRsp;
import java.util.ArrayList;
import java.util.List;
@Opcodes(PacketOpcodes.SetPlayerPropReq) @Opcodes(PacketOpcodes.SetPlayerPropReq)
public class HandlerSetPlayerPropReq extends PacketHandler { public class HandlerSetPlayerPropReq extends PacketHandler {
@ -21,11 +18,10 @@ public class HandlerSetPlayerPropReq extends PacketHandler {
// Auto template // Auto template
SetPlayerPropReq req = SetPlayerPropReq.parseFrom(payload); SetPlayerPropReq req = SetPlayerPropReq.parseFrom(payload);
Player player = session.getPlayer(); Player player = session.getPlayer();
List<PropValue> propList = req.getPropListList(); for (PropValue p : req.getPropListList()) {
for (int i = 0; i < propList.size(); i++) { PlayerProperty prop = PlayerProperty.getPropById(p.getType());
PlayerProperty prop = PlayerProperty.getPropById(propList.get(i).getType());
if (prop == PlayerProperty.PROP_IS_MP_MODE_AVAILABLE) { if (prop == PlayerProperty.PROP_IS_MP_MODE_AVAILABLE) {
if (!player.setProperty(prop, (int)propList.get(i).getVal())) { if (!player.setProperty(prop, (int) p.getVal(), false)) {
session.send(new PacketSetPlayerPropRsp(1)); session.send(new PacketSetPlayerPropRsp(1));
return; return;
} }

View File

@ -0,0 +1,19 @@
package emu.grasscutter.server.packet.recv;
import emu.grasscutter.net.packet.Opcodes;
import emu.grasscutter.net.packet.PacketHandler;
import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.SetUpLunchBoxWidgetReqOuterClass;
import emu.grasscutter.server.game.GameSession;
import emu.grasscutter.server.packet.send.PacketSetUpLunchBoxWidgetRsp;
@Opcodes(PacketOpcodes.SetUpLunchBoxWidgetReq)
public class HandlerSetUpLunchBoxWidgetReq extends PacketHandler {
@Override
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
var req
= SetUpLunchBoxWidgetReqOuterClass.SetUpLunchBoxWidgetReq.parseFrom(payload);
session.send(new PacketSetUpLunchBoxWidgetRsp(req.getLunchBoxData()));
}
}

View File

@ -1,13 +1,10 @@
package emu.grasscutter.server.packet.recv; package emu.grasscutter.server.packet.recv;
import emu.grasscutter.net.packet.Opcodes; import emu.grasscutter.net.packet.Opcodes;
import emu.grasscutter.net.packet.PacketHandler; import emu.grasscutter.net.packet.PacketHandler;
import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.TakeBattlePassMissionPointReqOuterClass; import emu.grasscutter.net.proto.TakeBattlePassMissionPointReqOuterClass.TakeBattlePassMissionPointReq;
import emu.grasscutter.server.game.GameSession; import emu.grasscutter.server.game.GameSession;
import emu.grasscutter.server.packet.send.PacketBattlePassCurScheduleUpdateNotify;
import emu.grasscutter.server.packet.send.PacketBattlePassMissionUpdateNotify;
import emu.grasscutter.server.packet.send.PacketTakeBattlePassMissionPointRsp; import emu.grasscutter.server.packet.send.PacketTakeBattlePassMissionPointRsp;
@Opcodes(PacketOpcodes.TakeBattlePassMissionPointReq) @Opcodes(PacketOpcodes.TakeBattlePassMissionPointReq)
@ -15,11 +12,10 @@ public class HandlerTakeBattlePassMissionPointReq extends PacketHandler {
@Override @Override
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
var req var req = TakeBattlePassMissionPointReq.parseFrom(payload);
= TakeBattlePassMissionPointReqOuterClass.TakeBattlePassMissionPointReq.parseFrom(payload);
session.getPlayer().getBattlePassManager().takeMissionPoint(req.getMissionIdListList());
session.send(new PacketBattlePassMissionUpdateNotify(req.getMissionIdListList() , session));
session.send(new PacketBattlePassCurScheduleUpdateNotify(session.getPlayer()));
session.send(new PacketTakeBattlePassMissionPointRsp()); session.send(new PacketTakeBattlePassMissionPointRsp());
} }
} }

View File

@ -3,7 +3,7 @@ package emu.grasscutter.server.packet.recv;
import emu.grasscutter.net.packet.Opcodes; import emu.grasscutter.net.packet.Opcodes;
import emu.grasscutter.net.packet.PacketHandler; import emu.grasscutter.net.packet.PacketHandler;
import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.TakeBattlePassRewardReqOuterClass; import emu.grasscutter.net.proto.TakeBattlePassRewardReqOuterClass.TakeBattlePassRewardReq;
import emu.grasscutter.server.game.GameSession; import emu.grasscutter.server.game.GameSession;
import emu.grasscutter.server.packet.send.PacketTakeBattlePassRewardRsp; import emu.grasscutter.server.packet.send.PacketTakeBattlePassRewardRsp;
@ -11,14 +11,8 @@ import emu.grasscutter.server.packet.send.PacketTakeBattlePassRewardRsp;
public class HandlerTakeBattlePassRewardReq extends PacketHandler { public class HandlerTakeBattlePassRewardReq extends PacketHandler {
@Override @Override
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
var req var req = TakeBattlePassRewardReq.parseFrom(payload);
= TakeBattlePassRewardReqOuterClass.TakeBattlePassRewardReq.parseFrom(payload);
//due to the list only have one element, so we can use get(0) session.getPlayer().getBattlePassManager().takeReward(req.getTakeOptionListList());
session.send(new PacketTakeBattlePassRewardRsp(req.getTakeOptionListList() , session));
//update the awardTakenLevel
req.getTakeOptionListList().forEach(battlePassRewardTakeOption ->
session.getPlayer().getBattlePassManager().updateAwardTakenLevel(battlePassRewardTakeOption.getTag().getLevel()));
} }
} }

View File

@ -5,6 +5,7 @@ import emu.grasscutter.game.player.Player;
import emu.grasscutter.net.packet.BasePacket; import emu.grasscutter.net.packet.BasePacket;
import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.*; import emu.grasscutter.net.proto.*;
import emu.grasscutter.net.proto.BattlePassAllDataNotifyOuterClass.BattlePassAllDataNotify;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -13,50 +14,25 @@ public class PacketBattlePassAllDataNotify extends BasePacket {
public PacketBattlePassAllDataNotify(Player player) { public PacketBattlePassAllDataNotify(Player player) {
super(PacketOpcodes.BattlePassAllDataNotify); super(PacketOpcodes.BattlePassAllDataNotify);
var value = player.getBattlePassManager().getPoint(); var proto = BattlePassAllDataNotify.newBuilder();
int level = (int) Math.floor(value / 1000d); proto
.setHaveCurSchedule(true)
.setCurSchedule(player.getBattlePassManager().getScheduleProto());
var point = value - level * 1000; for (var missionData : GameData.getBattlePassMissionDataMap().values()) {
// Dont send invalid refresh types
if (!missionData.isValidRefreshType()) {
continue;
}
List<BattlePassRewardTagOuterClass.BattlePassRewardTag> rewardTags = new ArrayList<>(); // Check if player has mission in bp manager. If not, then add an empty proto from the mission data
if (player.getBattlePassManager().hasMission(missionData.getId())) {
for (int id = 1; id <= player.getBattlePassManager().getAwardTakenLevel(); id++) proto.addMissionList(player.getBattlePassManager().loadMissionById(missionData.getId()).toProto());
rewardTags.add(BattlePassRewardTagOuterClass.BattlePassRewardTag.newBuilder() } else {
.setLevel(id) proto.addMissionList(missionData.toProto());
.setUnlockStatus(BattlePassUnlockStatusOuterClass.BattlePassUnlockStatus.BATTLE_PASS_UNLOCK_STATUS_FREE) }
.setRewardId(1001000 + id) }
.build());
var proto
= BattlePassAllDataNotifyOuterClass.BattlePassAllDataNotify.newBuilder();
var missions
= GameData.getBattlePassMissionExcelConfigDataMap();
var curSchedule
= BattlePassScheduleOuterClass.BattlePassSchedule.newBuilder()
.setScheduleId(2700).setLevel(level).setPoint(point).setBeginTime(1653940800).setEndTime(2059483200).addAllRewardTakenList(rewardTags)
.setIsViewed(true).setUnlockStatus(BattlePassUnlockStatusOuterClass.BattlePassUnlockStatus.BATTLE_PASS_UNLOCK_STATUS_FREE).setCurCyclePoints(0)
.setCurCycle(BattlePassCycleOuterClass.BattlePassCycle.newBuilder().setBeginTime(1653940800).setEndTime(2059483200).setCycleIdx(3).build());
proto.setHaveCurSchedule(true).setCurSchedule(curSchedule);
//TODO: UNFINISHED YET / Need to add mission data --> Hard work
for (var mission : missions.values())
proto.addMissionList(BattlePassMissionOuterClass.BattlePassMission.newBuilder()
.setMissionId(mission.getId())
.setMissionStatus(BattlePassMissionOuterClass.BattlePassMission.MissionStatus.MISSION_STATUS_UNFINISHED)
.setTotalProgress(mission.getProgress())
.setMissionType(
mission.getRefreshType() == null ? 0 :
mission.getRefreshType().equals("BATTLE_PASS_MISSION_REFRESH_SCHEDULE") ? 2 : mission.getRefreshType().contains("CYCLE") ? 1 : 0)
.setRewardBattlePassPoint(mission.getAddPoint())
.build());
setData(proto.build()); setData(proto.build());
} }

View File

@ -4,36 +4,22 @@ import emu.grasscutter.game.player.Player;
import emu.grasscutter.net.packet.BasePacket; import emu.grasscutter.net.packet.BasePacket;
import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.*; import emu.grasscutter.net.proto.*;
import emu.grasscutter.net.proto.BattlePassCurScheduleUpdateNotifyOuterClass.BattlePassCurScheduleUpdateNotify;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
public class PacketBattlePassCurScheduleUpdateNotify extends BasePacket { public class PacketBattlePassCurScheduleUpdateNotify extends BasePacket {
public PacketBattlePassCurScheduleUpdateNotify(Player player) { public PacketBattlePassCurScheduleUpdateNotify(Player player) {
super(PacketOpcodes.BattlePassCurScheduleUpdateNotify); super(PacketOpcodes.BattlePassCurScheduleUpdateNotify);
var value = player.getBattlePassManager().getPoint(); var proto = BattlePassCurScheduleUpdateNotify.newBuilder();
int level = (int) Math.floor(value / 1000d);
var point = value - level * 1000;
List<BattlePassRewardTagOuterClass.BattlePassRewardTag> rewardTags = new ArrayList<>(); proto
.setHaveCurSchedule(true)
for (int id = 1; id <= player.getBattlePassManager().getAwardTakenLevel(); id++) .setCurSchedule(player.getBattlePassManager().getScheduleProto())
rewardTags.add(BattlePassRewardTagOuterClass.BattlePassRewardTag.newBuilder() .build();
.setLevel(id)
.setUnlockStatus(BattlePassUnlockStatusOuterClass.BattlePassUnlockStatus.BATTLE_PASS_UNLOCK_STATUS_FREE)
.setRewardId(1001000 + id)
.build());
var curSchedule
= BattlePassScheduleOuterClass.BattlePassSchedule.newBuilder()
.setScheduleId(2700).setLevel(level).setPoint(point).setBeginTime(1653940800).setEndTime(2059483200).addAllRewardTakenList(rewardTags)
.setIsViewed(true).setUnlockStatus(BattlePassUnlockStatusOuterClass.BattlePassUnlockStatus.BATTLE_PASS_UNLOCK_STATUS_FREE).setCurCyclePoints(0)
.setCurCycle(BattlePassCycleOuterClass.BattlePassCycle.newBuilder().setBeginTime(1653940800).setEndTime(2059483200).setCycleIdx(3).build());
var proto = BattlePassCurScheduleUpdateNotifyOuterClass.BattlePassCurScheduleUpdateNotify.newBuilder();
proto.setHaveCurSchedule(true).setCurSchedule(curSchedule).build();
setData(proto.build()); setData(proto.build());

View File

@ -1,38 +1,32 @@
package emu.grasscutter.server.packet.send; package emu.grasscutter.server.packet.send;
import emu.grasscutter.Grasscutter; import java.util.Collection;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.BattlePassMissionExcelConfigData; import emu.grasscutter.game.battlepass.BattlePassMission;
import emu.grasscutter.net.packet.BasePacket; import emu.grasscutter.net.packet.BasePacket;
import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.BattlePassMissionOuterClass; import emu.grasscutter.net.proto.BattlePassMissionUpdateNotifyOuterClass.BattlePassMissionUpdateNotify;
import emu.grasscutter.net.proto.BattlePassMissionUpdateNotifyOuterClass;
import emu.grasscutter.server.game.GameSession;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import java.util.List;
import java.util.Map;
public class PacketBattlePassMissionUpdateNotify extends BasePacket { public class PacketBattlePassMissionUpdateNotify extends BasePacket {
public PacketBattlePassMissionUpdateNotify(List<Integer> missionIdList , GameSession session) { public PacketBattlePassMissionUpdateNotify(BattlePassMission mission) {
super(PacketOpcodes.BattlePassMissionUpdateNotify); super(PacketOpcodes.BattlePassMissionUpdateNotify);
var proto var proto = BattlePassMissionUpdateNotify.newBuilder()
= BattlePassMissionUpdateNotifyOuterClass.BattlePassMissionUpdateNotify.newBuilder(); .addMissionList(mission.toProto())
.build();
Map<Integer, BattlePassMissionExcelConfigData> missionMap this.setData(proto);
= GameData.getBattlePassMissionExcelConfigDataMap(); }
missionIdList.forEach(missionId -> proto.addMissionList public PacketBattlePassMissionUpdateNotify(Collection<BattlePassMission> missions) {
(BattlePassMissionOuterClass.BattlePassMission.newBuilder().setMissionId(missionId).setMissionStatus super(PacketOpcodes.BattlePassMissionUpdateNotify);
(BattlePassMissionOuterClass.BattlePassMission.MissionStatus.MISSION_STATUS_POINT_TAKEN)
.setTotalProgress(missionMap.get(missionId).getProgress()).setRewardBattlePassPoint(missionMap.get(missionId).getAddPoint()).build()));
var point = session.getPlayer().getBattlePassManager().getPoint(); var proto = BattlePassMissionUpdateNotify.newBuilder();
missionIdList.forEach(missionId
-> session.getPlayer().getBattlePassManager().addPoint(missionMap.get(missionId).getAddPoint())); missions.forEach(mission -> {
Grasscutter.getLogger().info("[PacketBattlePassMissionUpdateNotify] addPoint: {}", session.getPlayer().getBattlePassManager().getPoint() - point); proto.addMissionList(mission.toProto());
});
this.setData(proto.build()); this.setData(proto.build());
} }

View File

@ -0,0 +1,18 @@
package emu.grasscutter.server.packet.send;
import emu.grasscutter.net.packet.BasePacket;
import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.BuyBattlePassLevelRspOuterClass.BuyBattlePassLevelRsp;
public class PacketBuyBattlePassLevelRsp extends BasePacket {
public PacketBuyBattlePassLevelRsp(int buyLevel) {
super(PacketOpcodes.BuyBattlePassLevelRsp);
BuyBattlePassLevelRsp proto = BuyBattlePassLevelRsp.newBuilder()
.setBuyLevel(buyLevel)
.build();
this.setData(proto);
}
}

View File

@ -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);

View File

@ -0,0 +1,18 @@
package emu.grasscutter.server.packet.send;
import emu.grasscutter.net.packet.BasePacket;
import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.SetBattlePassViewedRspOuterClass.SetBattlePassViewedRsp;
public class PacketSetBattlePassViewedRsp extends BasePacket {
public PacketSetBattlePassViewedRsp(int scheduleId) {
super(PacketOpcodes.SetBattlePassViewedRsp);
SetBattlePassViewedRsp proto = SetBattlePassViewedRsp.newBuilder()
.setScheduleId(scheduleId)
.build();
this.setData(proto);
}
}

View File

@ -0,0 +1,18 @@
package emu.grasscutter.server.packet.send;
import emu.grasscutter.net.packet.BasePacket;
import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.LunchBoxDataOuterClass;
import emu.grasscutter.net.proto.SetUpLunchBoxWidgetRspOuterClass;
public class PacketSetUpLunchBoxWidgetRsp extends BasePacket {
public PacketSetUpLunchBoxWidgetRsp(LunchBoxDataOuterClass.LunchBoxData lunchBoxData) {
super(PacketOpcodes.SetUpLunchBoxWidgetRsp);
var rsp
= SetUpLunchBoxWidgetRspOuterClass.SetUpLunchBoxWidgetRsp.newBuilder();
rsp.setLunchBoxData(lunchBoxData);
setData(rsp.build());
}
}

View File

@ -1,49 +1,28 @@
package emu.grasscutter.server.packet.send; package emu.grasscutter.server.packet.send;
import emu.grasscutter.Grasscutter; import emu.grasscutter.data.common.ItemParamData;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.BattlePassRewardExcelConfigData;
import emu.grasscutter.data.excels.RewardData;
import emu.grasscutter.net.packet.BasePacket; import emu.grasscutter.net.packet.BasePacket;
import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.BattlePassRewardTakeOptionOuterClass; import emu.grasscutter.net.proto.BattlePassRewardTakeOptionOuterClass.BattlePassRewardTakeOption;
import emu.grasscutter.net.proto.ItemParamOuterClass; import emu.grasscutter.net.proto.ItemParamOuterClass.ItemParam;
import emu.grasscutter.net.proto.TakeBattlePassRewardRspOuterClass; import emu.grasscutter.net.proto.TakeBattlePassRewardRspOuterClass.TakeBattlePassRewardRsp;
import emu.grasscutter.server.game.GameSession; import emu.grasscutter.server.game.GameSession;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map;
public class PacketTakeBattlePassRewardRsp extends BasePacket { public class PacketTakeBattlePassRewardRsp extends BasePacket {
public PacketTakeBattlePassRewardRsp(List<BattlePassRewardTakeOptionOuterClass.BattlePassRewardTakeOption> takeOptionList , GameSession session) { public PacketTakeBattlePassRewardRsp(List<BattlePassRewardTakeOption> takeOptionList, List<ItemParamData> rewardItems) {
super(PacketOpcodes.TakeBattlePassRewardRsp); super(PacketOpcodes.TakeBattlePassRewardRsp);
var proto var proto = TakeBattlePassRewardRsp.newBuilder()
= TakeBattlePassRewardRspOuterClass.TakeBattlePassRewardRsp.newBuilder(); .addAllTakeOptionList(takeOptionList);
Map<Integer , BattlePassRewardExcelConfigData> excelConfigDataMap = GameData.getBattlePassRewardExcelConfigDataMap(); if (rewardItems != null) {
Map<Integer , RewardData> rewardDataMap = GameData.getRewardDataMap(); for (ItemParamData param : rewardItems) {
proto.addItemList(ItemParam.newBuilder().setItemId(param.getItemId()).setCount(param.getCount()));
List<Integer> rewardItemList = new ArrayList<>();
for (var takeOption : takeOptionList) {
for (int level = session.getPlayer().getBattlePassManager().getAwardTakenLevel() + 1 ; level <= takeOption.getTag().getLevel() ; level++){
rewardItemList.addAll(excelConfigDataMap.get(level).getFreeRewardIdList());
rewardItemList.addAll(excelConfigDataMap.get(level).getPaidRewardIdList());
} }
for (var rewardItemId : rewardItemList) {
var rewardData = rewardDataMap.get(rewardItemId);
if (rewardData == null) continue;
rewardData.getRewardItemList().forEach(i ->
proto.addItemList(ItemParamOuterClass.ItemParam.newBuilder().setItemId(i.getId()).setCount(i.getCount()).build()));
} }
}
proto.addAllTakeOptionList(takeOptionList).build();
setData(proto); setData(proto);
} }
} }

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

@ -1,5 +1,5 @@
{ {
"scheduleId" : 45, "scheduleId" : 45,
"scheduleStartTime" : "2022-05-01T00:00:00+08:00", "scheduleStartTime" : "2022-06-01T00:00:00+08:00",
"nextScheduleChangeTime" : "2022-05-30T00:00:00+08:00" "nextScheduleChangeTime" : "2030-06-30T00:00:00+08:00"
} }

View File

@ -70,6 +70,8 @@
"command_exist_error": "No command found.", "command_exist_error": "No command found.",
"no_usage_specified": "No usage specified", "no_usage_specified": "No usage specified",
"no_description_specified": "No description specified", "no_description_specified": "No description specified",
"set_to": "%s set to %s.",
"set_for_to": "%s for %s set to %s.",
"invalid": { "invalid": {
"amount": "Invalid amount.", "amount": "Invalid amount.",
"artifactId": "Invalid artifact ID.", "artifactId": "Invalid artifact ID.",
@ -79,6 +81,8 @@
"itemId": "Invalid item ID.", "itemId": "Invalid item ID.",
"itemLevel": "Invalid itemLevel.", "itemLevel": "Invalid itemLevel.",
"itemRefinement": "Invalid itemRefinement.", "itemRefinement": "Invalid itemRefinement.",
"statValue": "Invalid stat value.",
"value_between": "Invalid value: %s must be between %s and %s.",
"playerId": "Invalid player ID.", "playerId": "Invalid player ID.",
"uid": "Invalid UID.", "uid": "Invalid UID.",
"id": "Invalid ID." "id": "Invalid ID."
@ -113,18 +117,6 @@
"no_account": "Account not found.", "no_account": "Account not found.",
"description": "Modify user accounts" "description": "Modify user accounts"
}, },
"broadcast": {
"command_usage": "Usage: broadcast <message>",
"message_sent": "Message sent.",
"description": "Sends a message to all the players"
},
"changescene": {
"usage": "Usage: changescene <sceneID>",
"already_in_scene": "You are already in that scene.",
"success": "Changed to scene %s.",
"exists_error": "The specified scene does not exist.",
"description": "Changes your scene"
},
"clear": { "clear": {
"command_usage": "Usage: clear <all|wp|art|mat>", "command_usage": "Usage: clear <all|wp|art|mat>",
"weapons": "Cleared weapons for %s.", "weapons": "Cleared weapons for %s.",
@ -141,11 +133,6 @@
"success": "Summoned %s to %s's world.", "success": "Summoned %s to %s's world.",
"description": "Forces someone to join the world of others. If no one is targeted, it sends you into co-op mode anyway." "description": "Forces someone to join the world of others. If no one is targeted, it sends you into co-op mode anyway."
}, },
"drop": {
"command_usage": "Usage: drop <itemID|itemName> [amount]",
"success": "Dropped %s of %s.",
"description": "Drops an item near you"
},
"enter_dungeon": { "enter_dungeon": {
"usage": "Usage: enterdungeon <dungeonID>", "usage": "Usage: enterdungeon <dungeonID>",
"changed": "Changed to dungeon %s.", "changed": "Changed to dungeon %s.",
@ -153,39 +140,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.",
"godmode": { "description": "Gives an item to you or the specified player. Can also give all weapons, avatars and/or materials, and can construct custom artifacts."
"success": "Godmode is now %s for %s.",
"description": "Prevents you from taking damage. Defaults to toggle."
}, },
"heal": { "heal": {
"success": "All characters have been healed.", "success": "All characters have been healed.",
@ -223,10 +187,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.",
@ -263,9 +223,6 @@
"success": "Reset complete.", "success": "Reset complete.",
"description": "Reset target player's shop refresh time" "description": "Reset target player's shop refresh time"
}, },
"restart": {
"description": "Restarts the current session"
},
"sendMail": { "sendMail": {
"usage": "Usage: sendmail <userID|all|help> [templateID]", "usage": "Usage: sendmail <userID|all|help> [templateID]",
"user_not_exist": "The user with an ID of '%s' does not exist.", "user_not_exist": "The user with an ID of '%s' does not exist.",
@ -290,9 +247,9 @@
"description": "Sends mail to the specified user. The usage of this command changes based on its composition state" "description": "Sends mail to the specified user. The usage of this command changes based on its composition state"
}, },
"sendMessage": { "sendMessage": {
"usage": "Usage: sendmessage <player> <message>", "usage": "Usage: sendmessage <message>",
"success": "Message sent.", "success": "Message sent.",
"description": "Sends a message to a player as the server" "description": "Sends a message to a player as the server. If used with no target, sends to all players on the server."
}, },
"setFetterLevel": { "setFetterLevel": {
"usage": "Usage: setfetterlevel <level>", "usage": "Usage: setfetterlevel <level>",
@ -301,24 +258,13 @@
"level_error": "Invalid fetter level.", "level_error": "Invalid fetter level.",
"description": "Sets your fetter level for your current active character" "description": "Sets your fetter level for your current active character"
}, },
"setStats": { "setProp": {
"usage_console": "Usage: setstats|stats @<UID> <stat> <value>", "usage": "Usage: setprop|prop <prop> <value>\n\tValues for <prop>: godmode | nostamina | unlimitedenergy | abyssfloor | worldlevel | bplevel\n\t(cont.) see PlayerProperty enum for other possible values, of form PROP_MAX_SPRING_VOLUME -> max_spring_volume",
"usage_ingame": "Usage: setstats|stats [@UID] <stat> <value>", "description": "Sets accountwide properties. Things like godmode can be enabled this way, as well as changing things like unlocked abyss floor and battle pass progress."
"help_message": "\n\tValues for <stat>: hp | maxhp | def | atk | em | er | crate | cdmg | cdr | heal | heali | shield | defi\n\t(cont.) Elemental DMG Bonus: epyro | ecryo | ehydro | egeo | edendro | eelectro | ephys\n\t(cont.) Elemental RES: respyro | rescryo | reshydro | resgeo | resdendro | reselectro | resphys\n",
"value_error": "Invalid stat value.",
"uid_error": "Invalid UID.",
"player_error": "Player not found or offline.",
"set_self": "%s set to %s.",
"set_for_uid": "%s for %s set to %s.",
"set_max_hp": "MAX HP set to %s.",
"description": "Sets fight property for your current active character"
}, },
"setWorldLevel": { "setStats": {
"usage": "Usage: setworldlevel <level>", "usage": "Usage: setstats|stats <stat> <value>\n\tValues for <stat>: hp | maxhp | def | atk | em | er | crate | cdmg | cdr | heal | heali | shield | defi\n\t(cont.) Elemental DMG Bonus: epyro | ecryo | ehydro | egeo | edendro | eelectro | ephys\n\t(cont.) Elemental RES: respyro | rescryo | reshydro | resgeo | resdendro | reselectro | resphys\n",
"value_error": "World level must be between 0-8.", "description": "Sets fight property for your current active character"
"success": "World level set to %s.",
"invalid_world_level": "Invalid world level.",
"description": "Sets your world level (Relog to see proper effects)"
}, },
"spawn": { "spawn": {
"usage": "Usage: spawn <entityID> [amount] [level(monster only)] [<x> <y> <z>(monster only, optional)]", "usage": "Usage: spawn <entityID> [amount] [level(monster only)] [<x> <y> <z>(monster only, optional)]",
@ -375,18 +321,10 @@
"usage": "Usage: tp [@<playerID>] <x> <y> <z> [sceneID]", "usage": "Usage: tp [@<playerID>] <x> <y> <z> [sceneID]",
"specify_player_id": "You must specify a player ID.", "specify_player_id": "You must specify a player ID.",
"invalid_position": "Invalid position.", "invalid_position": "Invalid position.",
"exists_error": "The specified scene does not exist.",
"success": "Teleported %s to %s, %s, %s in scene %s.", "success": "Teleported %s to %s, %s, %s in scene %s.",
"description": "Change the player's position" "description": "Change the player's position"
}, },
"unlimitenergy": {
"success": "UnlimitEnergy is now %s for %s.",
"config_error": "Command is disabled, because energyUsage is false in config.json.",
"description": "Use the element does not waste energy"
},
"unlocktower": {
"success": "Abyss Corridor's Floors are all unlocked now.",
"description": "Unlock all levels of tower"
},
"weather": { "weather": {
"usage": "Usage: weather [weatherId] [climateType]\nWeather IDs can be found in WeatherExcelConfigData.json.\nClimate types: sunny, cloudy, rain, thunderstorm, snow, mist", "usage": "Usage: weather [weatherId] [climateType]\nWeather IDs can be found in WeatherExcelConfigData.json.\nClimate types: sunny, cloudy, rain, thunderstorm, snow, mist",
"success": "Set weather ID to %s with climate type %s.", "success": "Set weather ID to %s with climate type %s.",
@ -394,18 +332,16 @@
"description": "Changes weather ID and climate type. Weather IDs can be found in WeatherExcelConfigData.json.\nClimate types: sunny, cloudy, rain, thunderstorm, snow, mist" "description": "Changes weather ID and climate type. Weather IDs can be found in WeatherExcelConfigData.json.\nClimate types: sunny, cloudy, rain, thunderstorm, snow, mist"
}, },
"ban": { "ban": {
"command_usage": "Usage: ban <playerId> [timestamp] [reason]", "command_usage": "Usage: ban <@playerId> [timestamp] [reason]",
"success": "Successful.", "success": "Successful.",
"failure": "Failed, player not found.", "failure": "Failed, player not found.",
"invalid_time": "Unable to parse timestamp.", "invalid_time": "Unable to parse timestamp.",
"invalid_player_id": "Unable to parse player ID.",
"description": "Ban a player" "description": "Ban a player"
}, },
"unban": { "unban": {
"command_usage": "Usage: unban <playerId>", "command_usage": "Usage: unban <@playerId>",
"success": "Successful.", "success": "Successful.",
"failure": "Failed, player not found.", "failure": "Failed, player not found.",
"invalid_player_id": "Unable to parse player ID.",
"description": "Unban a player" "description": "Unban a player"
} }
}, },

View File

@ -70,6 +70,8 @@
"command_exist_error": "Aucune commande trouvée.", "command_exist_error": "Aucune commande trouvée.",
"no_usage_specified": "Pas de description de l'utilisation spécifiée.", "no_usage_specified": "Pas de description de l'utilisation spécifiée.",
"no_description_specified": "Pas de description spécifiée", "no_description_specified": "Pas de description spécifiée",
"set_to": "%s a été défini a %s.",
"set_for_to": "%s de %s a été défini a %s.",
"invalid": { "invalid": {
"amount": "Montant invalide.", "amount": "Montant invalide.",
"artifactId": "ID de l'artéfact invalide.", "artifactId": "ID de l'artéfact invalide.",
@ -79,6 +81,8 @@
"itemId": "ID de l'objet invalide.", "itemId": "ID de l'objet invalide.",
"itemLevel": "Niveau de l'objet invalide.", "itemLevel": "Niveau de l'objet invalide.",
"itemRefinement": "Raffinement de l'objet invalide.", "itemRefinement": "Raffinement de l'objet invalide.",
"statValue": "Valeur de <stat> invalide.",
"value_between": "Invalid value: %s must be between %s and %s.",
"playerId": "ID du joueur invalide.", "playerId": "ID du joueur invalide.",
"uid": "UID invalide.", "uid": "UID invalide.",
"id": "ID invalide." "id": "ID invalide."
@ -114,18 +118,6 @@
"command_usage": "Utilisation: account <create|delete> <nom_d'utilisateur> [UID]", "command_usage": "Utilisation: account <create|delete> <nom_d'utilisateur> [UID]",
"description": "Modifie les comptes utilisateurs" "description": "Modifie les comptes utilisateurs"
}, },
"broadcast": {
"command_usage": "Usage: broadcast <message>",
"message_sent": "Message envoyé.",
"description": "Envoie un message a tous les joueurs"
},
"changescene": {
"usage": "Usage: changescene <sceneID>",
"already_in_scene": "Vous êtes déjà dans cette scène.",
"success": "Nouvelle scène : %s.",
"exists_error": "La scène spécifié n'existe pas.",
"description": "Change votre scène"
},
"clear": { "clear": {
"command_usage": "Usage: clear <all|wp|art|mat>", "command_usage": "Usage: clear <all|wp|art|mat>",
"weapons": "Les armes de %s ont été supprimés.", "weapons": "Les armes de %s ont été supprimés.",
@ -142,11 +134,6 @@
"success": "%s est apparu dans de monde de %s.", "success": "%s est apparu dans de monde de %s.",
"description": "Force quelqu'un a rejoindre le monde d'un autre. Si personne n'est ciblé, vous envoie quand même en mode multijoueur." "description": "Force quelqu'un a rejoindre le monde d'un autre. Si personne n'est ciblé, vous envoie quand même en mode multijoueur."
}, },
"drop": {
"command_usage": "Usage: drop <itemID|itemName> [quantité]",
"success": " %s %s ont été jetés.",
"description": "Jette un objet près de vous"
},
"enter_dungeon": { "enter_dungeon": {
"usage": "Usage: enterdungeon <dungeonID>", "usage": "Usage: enterdungeon <dungeonID>",
"changed": "Entré dans le donjon %s.", "changed": "Entré dans le donjon %s.",
@ -167,21 +154,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": {
@ -264,9 +244,6 @@
"success": "Réinitialisation terminée.", "success": "Réinitialisation terminée.",
"description": "Réinitialise le temps d'actualisation de la boutique du joueur spécifié" "description": "Réinitialise le temps d'actualisation de la boutique du joueur spécifié"
}, },
"restart": {
"description": "Redémare la session actuelle"
},
"sendMail": { "sendMail": {
"usage": "Usage: sendmail <userID|all|help> [templateID]", "usage": "Usage: sendmail <userID|all|help> [templateID]",
"user_not_exist": "L'utilisateur avec l'identifiant '%s' n'existe pas.", "user_not_exist": "L'utilisateur avec l'identifiant '%s' n'existe pas.",
@ -302,16 +279,12 @@
"level_error": "Niveau d'affinité invalide.", "level_error": "Niveau d'affinité invalide.",
"description": "Défini le niveau d'affinité de votre personnage actif" "description": "Défini le niveau d'affinité de votre personnage actif"
}, },
"setProp": {
"usage": "Usage: setprop|prop <prop> <value>\n\tValues for <prop>: godmode | nostamina | unlimitedenergy | abyssfloor | worldlevel | bplevel\n\t(cont.) see PlayerProperty enum for other possible values, of form PROP_MAX_SPRING_VOLUME -> max_spring_volume",
"description": "Sets accountwide properties. Things like godmode can be enabled this way, as well as changing things like unlocked abyss floor and battle pass progress."
},
"setStats": { "setStats": {
"usage_console": "Usage: setstats|stats @<UID> <stat> <valeur>", "usage": "Usage: setstats|stats <stat> <valeur>\n\tValeurs pour <stat>: hp | maxhp | def | atk | em | er | crate | cdmg | cdr | heal | heali | shield | defi\n\t(cont.) Bonus de dégât élémentaire: epyro | ecryo | ehydro | egeo | edendro | eelectro | ephys\n\t(cont.) Résistance élémentaire: respyro | rescryo | reshydro | resgeo | resdendro | reselectro | resphys\n",
"usage_ingame": "Usage: setstats|stats [@UID] <stat> <valeur>",
"help_message": "\n\tValeurs pour <stat>: hp | maxhp | def | atk | em | er | crate | cdmg | cdr | heal | heali | shield | defi\n\t(cont.) Bonus de dégât élémentaire: epyro | ecryo | ehydro | egeo | edendro | eelectro | ephys\n\t(cont.) Résistance élémentaire: respyro | rescryo | reshydro | resgeo | resdendro | reselectro | resphys\n",
"value_error": "Valeur de <stat> invalide.",
"uid_error": "UID invalide.",
"player_error": "Joueur introuvable ou hors ligne.",
"set_self": "%s a été défini a %s.",
"set_for_uid": "%s de %s a été défini a %s.",
"set_max_hp": "MAX HP a été défini a %s.",
"description": "Définit les propriétés de combat de votre personnage actif" "description": "Définit les propriétés de combat de votre personnage actif"
}, },
"setWorldLevel": { "setWorldLevel": {
@ -376,6 +349,7 @@
"usage": "Utilisation: tp [@<playerID>] <x> <y> <z> [sceneID]", "usage": "Utilisation: tp [@<playerID>] <x> <y> <z> [sceneID]",
"specify_player_id": "Vous devez spécifier un ID d'utilisateur.", "specify_player_id": "Vous devez spécifier un ID d'utilisateur.",
"invalid_position": "Position invalide.", "invalid_position": "Position invalide.",
"exists_error": "La scène spécifié n'existe pas.",
"success": "%s a été téléporté à %s, %s, %s dans la scène %s.", "success": "%s a été téléporté à %s, %s, %s dans la scène %s.",
"description": "Change la position du joueur" "description": "Change la position du joueur"
}, },
@ -399,15 +373,13 @@
"success": "Succès.", "success": "Succès.",
"failure": "Échec, joueur introuvable.", "failure": "Échec, joueur introuvable.",
"invalid_time": "Impossible d'analyser le timestamp.", "invalid_time": "Impossible d'analyser le timestamp.",
"invalid_player_id": "Impossible d'analyser le player ID.", "command_usage": "Usage: ban <@playerId> [timestamp] [raison]"
"command_usage": "Usage: ban <playerId> [timestamp] [raison]"
}, },
"unban": { "unban": {
"description": "Retire le bannissement d'un joueur", "description": "Retire le bannissement d'un joueur",
"success": "Succès.", "success": "Succès.",
"failure": "Échec, joueur introuvable.", "failure": "Échec, joueur introuvable.",
"invalid_player_id": "Imossible d'analyser player ID.", "command_usage": "Usage: unban <@playerId>"
"command_usage": "Usage: unban <playerId>"
} }
}, },
"gacha": { "gacha": {

View File

@ -64,6 +64,8 @@
"console_execute_error": "Tą komende można wywołać tylko z konsoli.", "console_execute_error": "Tą komende można wywołać tylko z konsoli.",
"player_execute_error": "Wywołaj tą komendę w grze.", "player_execute_error": "Wywołaj tą komendę w grze.",
"command_exist_error": "Nie znaleziono komendy.", "command_exist_error": "Nie znaleziono komendy.",
"set_to": "%s ustawiono na %s.",
"set_for_to": "%s dla %s ustawiono na %s.",
"invalid": { "invalid": {
"amount": "Błędna ilość.", "amount": "Błędna ilość.",
"artifactId": "Błędne ID artefaktu.", "artifactId": "Błędne ID artefaktu.",
@ -73,6 +75,7 @@
"id przedmiotu": "Błędne id przedmiotu.", "id przedmiotu": "Błędne id przedmiotu.",
"itemLevel": "Błędny poziom przedmiotu.", "itemLevel": "Błędny poziom przedmiotu.",
"itemRefinement": "Błędne ulepszenie.", "itemRefinement": "Błędne ulepszenie.",
"value_between": "Invalid value: %s must be between %s and %s.",
"playerId": "Błędne playerId.", "playerId": "Błędne playerId.",
"uid": "Błędne UID.", "uid": "Błędne UID.",
"id": "Błędne ID." "id": "Błędne ID."
@ -107,16 +110,6 @@
"no_account": "Nie znaleziono konta.", "no_account": "Nie znaleziono konta.",
"command_usage": "Użycie: account <create|delete> <nazwa> [uid]" "command_usage": "Użycie: account <create|delete> <nazwa> [uid]"
}, },
"broadcast": {
"command_usage": "Użycie: broadcast <wiadomość>",
"message_sent": "Wiadomość wysłana."
},
"changescene": {
"usage": "Użycie: changescene <id sceny>",
"already_in_scene": "Już jesteś na tej scenie.",
"success": "Zmieniono scene na %s.",
"exists_error": "Ta scena nie istenieje."
},
"clear": { "clear": {
"command_usage": "Użycie: clear <all|wp|art|mat>", "command_usage": "Użycie: clear <all|wp|art|mat>",
"weapons": "Wyczyszczono bronie dla %s.", "weapons": "Wyczyszczono bronie dla %s.",
@ -148,20 +141,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."
@ -241,16 +228,13 @@
"success": "Poziom przyjaźni ustawiono na: %s", "success": "Poziom przyjaźni ustawiono na: %s",
"level_error": "Błędny poziom przyjaźni." "level_error": "Błędny poziom przyjaźni."
}, },
"setProp": {
"usage": "Usage: setprop|prop <prop> <value>\n\tValues for <prop>: godmode | nostamina | unlimitedenergy | abyssfloor | worldlevel | bplevel\n\t(cont.) see PlayerProperty enum for other possible values, of form PROP_MAX_SPRING_VOLUME -> max_spring_volume",
"description": "Sets accountwide properties. Things like godmode can be enabled this way, as well as changing things like unlocked abyss floor and battle pass progress."
},
"setStats": { "setStats": {
"usage_console": "Użycie: setstats|stats @<UID> <statystyka> <wartość>", "usage": "Użycie: setstats|stats <statystyka> <wartość>\n\tWartości dla Statystyka: hp | maxhp | def | atk | em | er | crate | cdmg | cdr | heal | heali | shield | defi\n\t(cont.) Bonus DMG żywiołu: epyro | ecryo | ehydro | egeo | edendro | eelectro | ephys\n\t(cont.) RES na żywioł: respyro | rescryo | reshydro | resgeo | resdendro | reselectro | resphys\n",
"usage_ingame": "Użycie: setstats|stats [@UID] <statystyka> <wartość>", "description": "Sets fight property for your current active character"
"help_message": "\n\tWartości dla Statystyka: hp | maxhp | def | atk | em | er | crate | cdmg | cdr | heal | heali | shield | defi\n\t(cont.) Bonus DMG żywiołu: epyro | ecryo | ehydro | egeo | edendro | eelectro | ephys\n\t(cont.) RES na żywioł: respyro | rescryo | reshydro | resgeo | resdendro | reselectro | resphys\n",
"value_error": "Błędna wartość statystyki.",
"uid_error": "Błędne UID.",
"player_error": "Gracza nie znaleziono lub jest offline.",
"set_self": "%s ustawiono na %s.",
"set_for_uid": "%s dla %s ustawiono na %s.",
"set_max_hp": "Maksymalne HP ustawione na %s."
}, },
"setWorldLevel": { "setWorldLevel": {
"usage": "Użycie: setworldlevel <poziom>", "usage": "Użycie: setworldlevel <poziom>",
@ -290,6 +274,7 @@
"usage": "Użycie: /tp [@<ID gracza>] <x> <y> <z> [ID sceny]", "usage": "Użycie: /tp [@<ID gracza>] <x> <y> <z> [ID sceny]",
"specify_player_id": "Musisz określić ID gracza.", "specify_player_id": "Musisz określić ID gracza.",
"invalid_position": "Błędna pozycja.", "invalid_position": "Błędna pozycja.",
"exists_error": "Ta scena nie istenieje.",
"success": "Przeteleportowano %s do %s, %s, %s w scenie %s" "success": "Przeteleportowano %s do %s, %s, %s w scenie %s"
}, },
"weather": { "weather": {
@ -298,10 +283,6 @@
"success": "Set weather ID to %s with climate type %s.", "success": "Set weather ID to %s with climate type %s.",
"status": "Current weather ID is %s with climate type %s." "status": "Current weather ID is %s with climate type %s."
}, },
"drop": {
"command_usage": "Użycie: drop <ID przedmiotu|nazwa przedmiotu> [ilość]",
"success": "Wyrzucono %s of %s."
},
"help": { "help": {
"usage": "Użycie: ", "usage": "Użycie: ",
"aliases": "Aliasy: ", "aliases": "Aliasy: ",
@ -310,6 +291,19 @@
"unlocktower": { "unlocktower": {
"success": "odblokować gotowe", "success": "odblokować gotowe",
"description": "Odblokuj głęboką spiralę" "description": "Odblokuj głęboką spiralę"
},
"ban": {
"command_usage": "Usage: ban <@playerId> [timestamp] [reason]",
"success": "Successful.",
"failure": "Failed, player not found.",
"invalid_time": "Unable to parse timestamp.",
"description": "Ban a player"
},
"unban": {
"command_usage": "Usage: unban <@playerId>",
"success": "Successful.",
"failure": "Failed, player not found.",
"description": "Unban a player"
} }
}, },
"gacha": { "gacha": {

View File

@ -70,6 +70,8 @@
"command_exist_error": "Команда не найдена.", "command_exist_error": "Команда не найдена.",
"no_usage_specified": "Применение команды не указано", "no_usage_specified": "Применение команды не указано",
"no_description_specified": "Описание отсутствует", "no_description_specified": "Описание отсутствует",
"set_to": "Характеристика %s стала равной %s.",
"set_for_to": "Характеристика %s игрока %s стала равной %s.",
"invalid": { "invalid": {
"amount": "Некорректное количество.", "amount": "Некорректное количество.",
"artifactId": "Некорректный ID артефакта.", "artifactId": "Некорректный ID артефакта.",
@ -79,6 +81,8 @@
"itemId": "Некорректный ID предмета.", "itemId": "Некорректный ID предмета.",
"itemLevel": "Некорректный уровень предмета (itemLevel).", "itemLevel": "Некорректный уровень предмета (itemLevel).",
"itemRefinement": "Некорректный уровень пробуждения предмета (itemRefinement).", "itemRefinement": "Некорректный уровень пробуждения предмета (itemRefinement).",
"statValue": "Некорректное значение характеристики.",
"value_between": "Invalid value: %s must be between %s and %s.",
"playerId": "Некорректный ID игрока.", "playerId": "Некорректный ID игрока.",
"uid": "Некорректный UID.", "uid": "Некорректный UID.",
"id": "Некорректный ID." "id": "Некорректный ID."
@ -302,16 +306,12 @@
"level_error": "Некорректный уровень дружбы.", "level_error": "Некорректный уровень дружбы.",
"description": "Устанавливает уровень дружбы для активного персонажа" "description": "Устанавливает уровень дружбы для активного персонажа"
}, },
"setProp": {
"usage": "Usage: setprop|prop <prop> <value>\n\tValues for <prop>: godmode | nostamina | unlimitedenergy | abyssfloor | worldlevel | bplevel\n\t(cont.) see PlayerProperty enum for other possible values, of form PROP_MAX_SPRING_VOLUME -> max_spring_volume",
"description": "Sets accountwide properties. Things like godmode can be enabled this way, as well as changing things like unlocked abyss floor and battle pass progress."
},
"setStats": { "setStats": {
"usage_console": "Применение: setstats|stats @<UID> <хар-ка> <значение>", "usage": "Применение: setstats|stats <хар-ка> <значение>\n\tВозможные значения для <хар-ка>: hp | maxhp | def | atk | em | er | crate | cdmg | cdr | heal | heali | shield | defi\n\t(прод.) Бонус элементального урона: epyro | ecryo | ehydro | egeo | edendro | eelectro | ephys\n\t(cont.) Элементальное сопротивление: respyro | rescryo | reshydro | resgeo | resdendro | reselectro | resphys\n",
"usage_ingame": "Применение: setstats|stats [@UID] <хар-ка> <значение>",
"help_message": "\n\tВозможные значения для <хар-ка>: hp | maxhp | def | atk | em | er | crate | cdmg | cdr | heal | heali | shield | defi\n\t(прод.) Бонус элементального урона: epyro | ecryo | ehydro | egeo | edendro | eelectro | ephys\n\t(cont.) Элементальное сопротивление: respyro | rescryo | reshydro | resgeo | resdendro | reselectro | resphys\n",
"value_error": "Некорректное значение характеристики.",
"uid_error": "Некорректный UID.",
"player_error": "Игрок не найден или находится не в сети.",
"set_self": "Характеристика %s стала равной %s.",
"set_for_uid": "Характеристика %s игрока %s стала равной %s.",
"set_max_hp": "Максимальное значение здоровья стало равно %s.",
"description": "Задаёт боевые характеристики для активного персонажа" "description": "Задаёт боевые характеристики для активного персонажа"
}, },
"setWorldLevel": { "setWorldLevel": {
@ -399,15 +399,13 @@
"success": "Успех.", "success": "Успех.",
"failure": "Неудача, игрок не найден.", "failure": "Неудача, игрок не найден.",
"invalid_time": "Не удалось определить промежуток времени.", "invalid_time": "Не удалось определить промежуток времени.",
"invalid_player_id": "Не удалось определить ID игрока.", "command_usage": "Применение: ban <@Id игрока> [промежуток_времени] [причина]"
"command_usage": "Применение: ban <Id игрока> [промежуток_времени] [причина]"
}, },
"unban": { "unban": {
"description": "Разбанивает игрока", "description": "Разбанивает игрока",
"success": "Успех.", "success": "Успех.",
"failure": "Неудача, игрок не найден.", "failure": "Неудача, игрок не найден.",
"invalid_player_id": "Не удалось определить ID игрока.", "command_usage": "Применение: unban <@Id_игрока>"
"command_usage": "Применение: unban <Id_игрока>"
} }
}, },
"gacha": { "gacha": {

View File

@ -70,6 +70,8 @@
"command_exist_error": "未找到命令。", "command_exist_error": "未找到命令。",
"no_usage_specified": "未指定用法", "no_usage_specified": "未指定用法",
"no_description_specified": "未指定说明", "no_description_specified": "未指定说明",
"set_to": "%s 已设为 %s。",
"set_for_to": "%s [来自 %s] 已设为 %s。",
"invalid": { "invalid": {
"amount": "无效的数量。", "amount": "无效的数量。",
"artifactId": "无效的圣遗物ID。", "artifactId": "无效的圣遗物ID。",
@ -79,6 +81,8 @@
"itemId": "无效的物品ID。", "itemId": "无效的物品ID。",
"itemLevel": "无效的物品等级。", "itemLevel": "无效的物品等级。",
"itemRefinement": "无效的物品精炼等级。", "itemRefinement": "无效的物品精炼等级。",
"statValue": "无效的属性值。",
"value_between": "Invalid value: %s must be between %s and %s.",
"playerId": "无效的玩家ID。", "playerId": "无效的玩家ID。",
"uid": "无效的UID。", "uid": "无效的UID。",
"id": "无效的ID。" "id": "无效的ID。"
@ -113,18 +117,6 @@
"no_account": "账号不存在。", "no_account": "账号不存在。",
"description": "创建或删除账号" "description": "创建或删除账号"
}, },
"broadcast": {
"command_usage": "用法broadcast <消息>",
"message_sent": "公告已发送。",
"description": "向所有玩家发送公告"
},
"changescene": {
"usage": "用法changescene <场景ID>",
"already_in_scene": "你已经在这个场景中了。",
"success": "已切换至场景 %s。",
"exists_error": "场景不存在。",
"description": "切换指定场景"
},
"clear": { "clear": {
"command_usage": "用法clear <all|wp|art|mat>\nall: 所有, wp: 武器, art: 圣遗物, mat: 材料", "command_usage": "用法clear <all|wp|art|mat>\nall: 所有, wp: 武器, art: 圣遗物, mat: 材料",
"weapons": "已清除 %s 的武器。", "weapons": "已清除 %s 的武器。",
@ -141,11 +133,6 @@
"success": "已强制传送 %s 到 %s 的世界。", "success": "已强制传送 %s 到 %s 的世界。",
"description": "强制传送指定玩家到他人的世界。如果没有指定玩家,则会使你进入多人游戏状态" "description": "强制传送指定玩家到他人的世界。如果没有指定玩家,则会使你进入多人游戏状态"
}, },
"drop": {
"command_usage": "用法drop <物品ID|物品名称> [数量]",
"success": "已丢下 %s 个 %s。",
"description": "在你附近丢下物品"
},
"enter_dungeon": { "enter_dungeon": {
"usage": "用法enterdungeon <秘境ID>", "usage": "用法enterdungeon <秘境ID>",
"changed": "已进入秘境 %s。", "changed": "已进入秘境 %s。",
@ -166,21 +153,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": {
@ -263,9 +243,6 @@
"success": "重置完成。", "success": "重置完成。",
"description": "重置指定玩家的商店刷新时间" "description": "重置指定玩家的商店刷新时间"
}, },
"restart": {
"description": "重新启动服务器"
},
"sendMail": { "sendMail": {
"usage": "用法sendmail <用户ID|all|help> [模板ID]", "usage": "用法sendmail <用户ID|all|help> [模板ID]",
"user_not_exist": "用户 '%s' 不存在。", "user_not_exist": "用户 '%s' 不存在。",
@ -301,24 +278,13 @@
"level_error": "无效的好感度等级。", "level_error": "无效的好感度等级。",
"description": "设置当前角色的好感度等级" "description": "设置当前角色的好感度等级"
}, },
"setStats": { "setProp": {
"usage_console": "用法setstats|stats @<UID> <属性> <数值>", "usage": "Usage: setprop|prop <prop> <value>\n\tValues for <prop>: godmode | nostamina | unlimitedenergy | abyssfloor | worldlevel | bplevel\n\t(cont.) see PlayerProperty enum for other possible values, of form PROP_MAX_SPRING_VOLUME -> max_spring_volume",
"usage_ingame": "用法setstats|stats [@UID] <属性> <数值>", "description": "Sets accountwide properties. Things like godmode can be enabled this way, as well as changing things like unlocked abyss floor and battle pass progress."
"help_message": "\n可更改的属性列表hp(生命值)|maxhp(最大生命值)|def(防御力)|atk(攻击力)|em(元素精通)|er(元素充能效率)|crate(暴击率)|cdmg(暴击伤害)|cdr(冷却缩减)|heal(治疗加成)|heali(受治疗加成)|shield(护盾强效)|defi(无视防御)\n元素增伤epyro(火)|ecryo(冰)|ehydro(水)|egeo(岩)|edendro(草)|eelectro(雷)|ephys(物理)\n元素抗性respyro(火)|rescryo(冰)|reshydro(水)|resgeo(岩)|resdendro(草)|reselectro(雷)|resphys(物理)\n",
"value_error": "无效的属性值。",
"uid_error": "无效的UID。",
"player_error": "玩家不存在或已离线。",
"set_self": "%s 已设为 %s。",
"set_for_uid": "%s [来自 %s] 已设为 %s。",
"set_max_hp": "最大生命值已设为 %s。",
"description": "设置当前角色的属性"
}, },
"setWorldLevel": { "setStats": {
"usage": "用法setworldlevel <等级>", "usage": "用法setstats|stats <属性> <数值>\n可更改的属性列表hp(生命值)|maxhp(最大生命值)|def(防御力)|atk(攻击力)|em(元素精通)|er(元素充能效率)|crate(暴击率)|cdmg(暴击伤害)|cdr(冷却缩减)|heal(治疗加成)|heali(受治疗加成)|shield(护盾强效)|defi(无视防御)\n元素增伤epyro(火)|ecryo(冰)|ehydro(水)|egeo(岩)|edendro(草)|eelectro(雷)|ephys(物理)\n元素抗性respyro(火)|rescryo(冰)|reshydro(水)|resgeo(岩)|resdendro(草)|reselectro(雷)|resphys(物理)\n",
"value_error": "世界等级必须在 0-8 之间。", "description": "设置当前角色的属性"
"success": "世界等级已设为 %s。",
"invalid_world_level": "无效的世界等级。",
"description": "设置世界等级,执行命令后需重新登录以生效"
}, },
"spawn": { "spawn": {
"usage": "用法spawn <实体ID> [数量] [等级(仅怪物)] [<x> <y> <z>(仅怪物, 可选)]", "usage": "用法spawn <实体ID> [数量] [等级(仅怪物)] [<x> <y> <z>(仅怪物, 可选)]",
@ -375,18 +341,10 @@
"usage": "用法tp [@<玩家ID>] <x> <y> <z> [场景ID]", "usage": "用法tp [@<玩家ID>] <x> <y> <z> [场景ID]",
"specify_player_id": "你必须指定一个玩家ID。", "specify_player_id": "你必须指定一个玩家ID。",
"invalid_position": "无效的位置。", "invalid_position": "无效的位置。",
"exists_error": "此场景不存在。",
"success": "传送 %s 到坐标 %s, %s, %s场景为 %s。", "success": "传送 %s 到坐标 %s, %s, %s场景为 %s。",
"description": "改变指定玩家的位置" "description": "改变指定玩家的位置"
}, },
"unlimitenergy": {
"success": "UnlimitEnergy 已设为 %s。[用户:%s]",
"config_error": "命令不可用。因为 config.json 中 energyUsage 为 false。",
"description": "使用元素爆发而不消耗能量"
},
"unlocktower": {
"success": "现已解锁深境回廊(1-8层)。",
"description": "解锁深境螺旋"
},
"weather": { "weather": {
"usage": "用法weather [天气ID] [气候类型]\n天气ID可以在 WeatherExcelConfigData.json 中找到。\n气候类型sunny(晴天), cloudy(多云), rain(雨), thunderstorm(雷雨), snow(雪), mist(雾)", "usage": "用法weather [天气ID] [气候类型]\n天气ID可以在 WeatherExcelConfigData.json 中找到。\n气候类型sunny(晴天), cloudy(多云), rain(雨), thunderstorm(雷雨), snow(雪), mist(雾)",
"success": "已设置天气ID 为 %s气候类型为 %s。", "success": "已设置天气ID 为 %s气候类型为 %s。",
@ -394,18 +352,16 @@
"description": "更改天气ID和气候类型。天气ID可以在 WeatherExcelConfigData.json 中找到。\n气候类型sunny(晴天), cloudy(多云), rain(雨), thunderstorm(雷雨), snow(雪), mist(雾)" "description": "更改天气ID和气候类型。天气ID可以在 WeatherExcelConfigData.json 中找到。\n气候类型sunny(晴天), cloudy(多云), rain(雨), thunderstorm(雷雨), snow(雪), mist(雾)"
}, },
"ban": { "ban": {
"command_usage": "用法ban <玩家ID> [时间] [原因]", "command_usage": "用法ban <@玩家ID> [时间] [原因]",
"success": "成功封禁玩家。", "success": "成功封禁玩家。",
"failure": "封禁玩家失败,因为玩家不存在。", "failure": "封禁玩家失败,因为玩家不存在。",
"invalid_time": "无法解析时间戳。", "invalid_time": "无法解析时间戳。",
"invalid_player_id": "无法解析玩家ID。",
"description": "封禁玩家" "description": "封禁玩家"
}, },
"unban": { "unban": {
"command_usage": "用法unban <玩家ID>", "command_usage": "用法unban <@玩家ID>",
"success": "成功取消玩家的封禁。", "success": "成功取消玩家的封禁。",
"failure": "取消玩家的封禁失败,因为玩家不存在。", "failure": "取消玩家的封禁失败,因为玩家不存在。",
"invalid_player_id": "无法解析玩家ID。",
"description": "取消玩家的封禁" "description": "取消玩家的封禁"
} }
}, },

View File

@ -69,6 +69,8 @@
"player_execute_error": "請在遊戲裡使用這條指令。", "player_execute_error": "請在遊戲裡使用這條指令。",
"command_exist_error": "找不到指令。", "command_exist_error": "找不到指令。",
"no_description_specified": "没有指定說明。", "no_description_specified": "没有指定說明。",
"set_to": "%s 已經設為 %s。",
"set_for_to": "%s 的使用者 %s 更改為 %s。",
"invalid": { "invalid": {
"amount": "無效的數量。", "amount": "無效的數量。",
"artifactId": "無效的聖遺物ID。", "artifactId": "無效的聖遺物ID。",
@ -78,6 +80,8 @@
"itemId": "無效的物品ID。", "itemId": "無效的物品ID。",
"itemLevel": "無效的物品等級。", "itemLevel": "無效的物品等級。",
"itemRefinement": "無效的物品精煉度。", "itemRefinement": "無效的物品精煉度。",
"statValue": "無效的數據值。",
"value_between": "Invalid value: %s must be between %s and %s.",
"playerId": "無效的玩家ID。", "playerId": "無效的玩家ID。",
"uid": "無效的UID。", "uid": "無效的UID。",
"id": "無效的ID。" "id": "無效的ID。"
@ -112,18 +116,6 @@
"command_usage": "用法account <create|delete> <username> [uid]", "command_usage": "用法account <create|delete> <username> [uid]",
"description": "建立或刪除帳號。" "description": "建立或刪除帳號。"
}, },
"broadcast": {
"command_usage": "用法broadcast <message>",
"message_sent": "公告已發送。",
"description": "向所有玩家發送公告。"
},
"changescene": {
"usage": "用法changescene <scene id>",
"already_in_scene": "你已經在這個場景中了。",
"success": "已切換至場景 %s.",
"exists_error": "此場景不存在。",
"description": "切換指定場景。"
},
"clear": { "clear": {
"command_usage": "用法: clear <all|wp|art|mat>", "command_usage": "用法: clear <all|wp|art|mat>",
"weapons": "已將 %s 的武器清空。", "weapons": "已將 %s 的武器清空。",
@ -140,11 +132,6 @@
"success": "召喚了 %s 到 %s 的世界。", "success": "召喚了 %s 到 %s 的世界。",
"description": "強制傳送指定用戶到他人的世界。如果未指定玩家,則會將你設為多人遊戲狀態。" "description": "強制傳送指定用戶到他人的世界。如果未指定玩家,則會將你設為多人遊戲狀態。"
}, },
"drop": {
"command_usage": "用法drop <itemId|itemName> [amount]",
"success": "已將 %s x %s 丟在附近。",
"description": "在你附近丟下一個物品。"
},
"enter_dungeon": { "enter_dungeon": {
"usage": "用法enterdungeon <dungeon id>", "usage": "用法enterdungeon <dungeon id>",
"changed": "已進入祕境 %s", "changed": "已進入祕境 %s",
@ -165,21 +152,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": {
@ -267,9 +247,6 @@
"success": "重置完成。", "success": "重置完成。",
"description": "重置所選玩家的商店刷新時間。" "description": "重置所選玩家的商店刷新時間。"
}, },
"restart": {
"description": "重新啟動伺服器。"
},
"sendMail": { "sendMail": {
"usage": "用法sendmail <userId|all|help> [templateId]", "usage": "用法sendmail <userId|all|help> [templateId]",
"user_not_exist": "ID '%s' 的使用者不存在。", "user_not_exist": "ID '%s' 的使用者不存在。",
@ -305,16 +282,12 @@
"level_error": "無效的好感度。", "level_error": "無效的好感度。",
"description": "設定當前角色的好感度等級。" "description": "設定當前角色的好感度等級。"
}, },
"setProp": {
"usage": "Usage: setprop|prop <prop> <value>\n\tValues for <prop>: godmode | nostamina | unlimitedenergy | abyssfloor | worldlevel | bplevel\n\t(cont.) see PlayerProperty enum for other possible values, of form PROP_MAX_SPRING_VOLUME -> max_spring_volume",
"description": "Sets accountwide properties. Things like godmode can be enabled this way, as well as changing things like unlocked abyss floor and battle pass progress."
},
"setStats": { "setStats": {
"usage_console": "用法setstats|stats @<UID> <stat> <value>", "usage": "用法setstats|stats <stat> <value>\n\t可使用的數據類型hp (生命值)| maxhp (最大生命值) | def(防禦力) | atk (攻擊力)| em (元素精通) | er (元素充能效率) | crate(暴擊率) | cdmg (暴擊傷害)| cdr (冷卻縮減) | heal(治療加成)| heali (受治療加成)| shield (護盾強效)| defi (無視防禦)\n\t(cont.) 元素增傷類epyro (火傷) | ecryo (冰傷) | ehydro (水傷) | egeo (岩傷) | edendro (草傷) | eelectro (雷傷) | ephys (物傷)(cont.) 元素減傷類respyro (火抗) | rescryo (冰抗) | reshydro (水抗) | resgeo (岩抗) | resdendro (草抗) | reselectro (雷抗) | resphys (物抗)\n",
"usage_ingame": "用法setstats|stats [@UID] <stat> <value>",
"help_message": "\n\t可使用的數據類型hp (生命值)| maxhp (最大生命值) | def(防禦力) | atk (攻擊力)| em (元素精通) | er (元素充能效率) | crate(暴擊率) | cdmg (暴擊傷害)| cdr (冷卻縮減) | heal(治療加成)| heali (受治療加成)| shield (護盾強效)| defi (無視防禦)\n\t(cont.) 元素增傷類epyro (火傷) | ecryo (冰傷) | ehydro (水傷) | egeo (岩傷) | edendro (草傷) | eelectro (雷傷) | ephys (物傷)(cont.) 元素減傷類respyro (火抗) | rescryo (冰抗) | reshydro (水抗) | resgeo (岩抗) | resdendro (草抗) | reselectro (雷抗) | resphys (物抗)\n",
"value_error": "無效的數據值。",
"uid_error": "無效的UID。",
"player_error": "玩家不存在或已離線。",
"set_self": "%s 已經設為 %s。",
"set_for_uid": "%s 的使用者 %s 更改為 %s。",
"set_max_hp": "最大生命值更改為 %s。",
"description": "設定當前角色的數據類型。" "description": "設定當前角色的數據類型。"
}, },
"setWorldLevel": { "setWorldLevel": {
@ -378,6 +351,7 @@
"usage": "用法tp [@<playerId>] <x> <y> <z> [sceneId]", "usage": "用法tp [@<playerId>] <x> <y> <z> [sceneId]",
"specify_player_id": "你必須指定一個玩家ID。", "specify_player_id": "你必須指定一個玩家ID。",
"invalid_position": "無效的座標。", "invalid_position": "無效的座標。",
"exists_error": "此場景不存在。",
"success": "傳送 %s 到座標 %s,%s,%s ,場景為 %s 。", "success": "傳送 %s 到座標 %s,%s,%s ,場景為 %s 。",
"description": "將玩家的位置傳送到你所指定的座標。" "description": "將玩家的位置傳送到你所指定的座標。"
}, },
@ -401,15 +375,13 @@
"success": "停權成功。", "success": "停權成功。",
"failure": "停權失敗,玩家帳號不存在。", "failure": "停權失敗,玩家帳號不存在。",
"invalid_time": "無效的時間戳。", "invalid_time": "無效的時間戳。",
"invalid_player_id": "無效的玩家ID。", "command_usage": "用法ban <@playerId> [timestamp] [reason]"
"command_usage": "用法ban <playerId> [timestamp] [reason]"
}, },
"unban": { "unban": {
"description": "撤銷停權指定玩家。", "description": "撤銷停權指定玩家。",
"success": "撤銷停權成功。", "success": "撤銷停權成功。",
"failure": "撤銷停權失敗,玩家帳號不存在。", "failure": "撤銷停權失敗,玩家帳號不存在。",
"invalid_player_id": "無效的玩家ID。", "command_usage": "用法unban <@playerId>"
"command_usage": "用法unban <playerId>"
} }
}, },
"gacha": { "gacha": {