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

This commit is contained in:
KingRainbow44
2022-06-26 12:33:03 -04:00
83 changed files with 2687 additions and 2468 deletions

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 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.
public static final String[] DEFAULT_ABILITY_STRINGS = {
"Avatar_DefaultAbility_VisionReplaceDieInvincible", "Avatar_DefaultAbility_AvartarInShaderChange", "Avatar_SprintBS_Invincible",

View File

@@ -109,6 +109,79 @@ public final class CommandMap {
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.
*
@@ -129,39 +202,21 @@ public final class CommandMap {
String playerId = (player == null) ? consoleId : player.getAccount().getId();
// Check for special cases - currently only target command.
String targetUidStr = null;
if (label.startsWith("@")) { // @[UID]
targetUidStr = label.substring(1);
this.setPlayerTarget(playerId, player, label.substring(1));
return;
} else if (label.equalsIgnoreCase("target")) { // target [[@]UID]
if (args.size() > 0) {
targetUidStr = args.get(0);
String targetUidStr = args.get(0);
if (targetUidStr.startsWith("@")) {
targetUidStr = targetUidStr.substring(1);
}
} else {
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");
}
}
this.setPlayerTarget(playerId, player, targetUidStr);
return;
} else {
this.setPlayerTarget(playerId, player, "");
return;
}
}
// Get command handler.
@@ -179,38 +234,11 @@ public final class CommandMap {
// Get the command's annotation.
Command annotation = this.annotations.get(label);
// 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);
try {
int uid = Integer.parseInt(arg);
targetPlayer = Grasscutter.getGameServer().getPlayerByUid(uid, true);
if (targetPlayer == null) {
CommandHandler.sendTranslatedMessage(player, "commands.execution.player_exist_error");
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;
}
// Resolve targetPlayer
try{
targetPlayer = getTargetPlayer(playerId, player, targetPlayer, args);
} catch (IllegalArgumentException e) {
return;
}
// Check for permissions.

View File

@@ -2,36 +2,26 @@ package emu.grasscutter.command.commands;
import java.util.List;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.Account;
import emu.grasscutter.game.player.Player;
import static emu.grasscutter.utils.Language.translate;
import emu.grasscutter.server.game.GameSession;
@Command(
label = "ban",
usage = "ban <player> [time] [reason]",
usage = "ban <@player> [time] [reason]",
description = "commands.ban.description",
targetRequirement = Command.TargetRequirement.NONE
permission = "server.ban",
targetRequirement = Command.TargetRequirement.PLAYER
)
public final class BanCommand implements CommandHandler {
private boolean banAccount(int uid, int time, String reason) {
Player player = Grasscutter.getGameServer().getPlayerByUid(uid, true);
private boolean banAccount(Player targetPlayer, int time, String reason) {
Account account = targetPlayer.getAccount();
if (player == null) {
return false;
}
Account account = player.getAccount();
if (account == null) {
account = DatabaseHelper.getAccountByPlayerId(uid);
if (account == null) {
return false;
}
return false;
}
account.setBanReason(reason);
@@ -40,51 +30,36 @@ public final class BanCommand implements CommandHandler {
account.setBanned(true);
account.save();
Player banUser = Grasscutter.getGameServer().getPlayerByUid(uid);
if (banUser != null) {
banUser.getSession().close();
GameSession session = targetPlayer.getSession();
if (session != null) {
session.close();
}
return true;
}
@Override
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;
String reason = "Reason not specified.";
if (args.size() >= 1) {
try {
uid = Integer.parseInt(args.get(0));
} catch (NumberFormatException ignored) {
CommandHandler.sendMessage(sender, translate(sender, "commands.ban.invalid_player_id"));
return;
}
switch (args.size()) {
case 2:
reason = args.get(1); // Fall-through
case 1:
try {
time = Integer.parseInt(args.get(0));
} catch (NumberFormatException ignored) {
CommandHandler.sendTranslatedMessage(sender, "commands.ban.invalid_time");
return;
} // Fall-through, unimportant
default:
break;
}
if (args.size() >= 2) {
try {
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"));
if (banAccount(targetPlayer, time, reason)) {
CommandHandler.sendTranslatedMessage(sender, "commands.ban.success");
} 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;
import emu.grasscutter.GameConstants;
import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.GameDepot;
import emu.grasscutter.data.excels.AvatarData;
import emu.grasscutter.data.excels.ItemData;
import emu.grasscutter.data.excels.ReliquaryAffixData;
import emu.grasscutter.data.excels.ReliquaryMainPropData;
import emu.grasscutter.game.avatar.Avatar;
import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.inventory.ItemType;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.utils.SparseSet;
import java.util.LinkedList;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static emu.grasscutter.utils.Language.translate;
@Command(label = "give", usage = "give <itemId|itemName> [amount] [level]", aliases = {
@Command(label = "give", usage = "give <itemId|avatarId|\"all\"|\"weapons\"|\"mats\"|\"avatars\"> [lv<level>] [r<refinement>] [x<amount>] | give <artifactId> [lv<level>] [x<amount>] [mainPropId] [<appendPropId>[,<times>]]...", aliases = {
"g", "item", "giveitem"}, permission = "player.give", permissionTargeted = "player.give.others", description = "commands.give.description")
public final class GiveCommand implements CommandHandler {
Pattern lvlRegex = Pattern.compile("l(?:vl?)?(\\d+)"); // Java is a joke of a proglang that doesn't have raw string literals
Pattern refineRegex = Pattern.compile("r(\\d+)");
Pattern amountRegex = Pattern.compile("((?<=x)\\d+|\\d+(?=x)(?!x\\d))");
private static Pattern lvlRegex = Pattern.compile("l(?:vl?)?(\\d+)"); // Java doesn't have raw string literals :(
private static Pattern refineRegex = Pattern.compile("r(\\d+)");
private static Pattern constellationRegex = Pattern.compile("c(\\d+)");
private static Pattern amountRegex = Pattern.compile("((?<=x)\\d+|\\d+(?=x)(?!x\\d))");
private int matchIntOrNeg(Pattern pattern, String arg) {
private static int matchIntOrNeg(Pattern pattern, String arg) {
Matcher match = pattern.matcher(arg);
if (match.find()) {
return Integer.parseInt(match.group(1)); // This should be exception-safe as only \d+ can be passed to it (i.e. non-empty string of pure digits)
@@ -31,27 +39,50 @@ public final class GiveCommand implements CommandHandler {
return -1;
}
@Override
public void execute(Player sender, Player targetPlayer, List<String> args) {
int item;
int lvl = 1;
int amount = 1;
int refinement = 0;
private static enum GiveAllType {
NONE,
ALL,
WEAPONS,
MATS,
AVATARS
}
for (int i = args.size()-1; i>=0; i--) { // Reverse iteration as we are deleting elements
private static class GiveItemParameters {
public int id;
public int lvl = 0;
public int amount = 1;
public int refinement = 1;
public int constellation = -1;
public int mainPropId = -1;
public List<Integer> appendPropIdList;
public ItemData data;
public AvatarData avatarData;
public GiveAllType giveAllType = GiveAllType.NONE;
};
private static GiveItemParameters parseArgs(Player sender, List<String> args) throws IllegalArgumentException {
GiveItemParameters param = new GiveItemParameters();
// Extract any tagged arguments (e.g. "lv90", "x100", "r5")
for (int i = args.size() - 1; i >= 0; i--) { // Reverse iteration as we are deleting elements
String arg = args.get(i).toLowerCase();
boolean deleteArg = false;
int argNum;
// Note that a single argument can actually match all of these, e.g. "lv90r5x100"
if ((argNum = matchIntOrNeg(lvlRegex, arg)) != -1) {
lvl = argNum;
param.lvl = argNum;
deleteArg = true;
}
if ((argNum = matchIntOrNeg(refineRegex, arg)) != -1) {
refinement = argNum;
param.refinement = argNum;
deleteArg = true;
}
if ((argNum = matchIntOrNeg(constellationRegex, arg)) != -1) {
param.constellation = argNum;
deleteArg = true;
}
if ((argNum = matchIntOrNeg(amountRegex, arg)) != -1) {
amount = argNum;
param.amount = argNum;
deleteArg = true;
}
if (deleteArg) {
@@ -59,112 +90,387 @@ public final class GiveCommand implements CommandHandler {
}
}
switch (args.size()) {
case 4: // <itemId|itemName> [amount] [level] [refinement]
try {
refinement = Integer.parseInt(args.get(3));
} catch (NumberFormatException ignored) {
CommandHandler.sendMessage(sender, translate(sender, "commands.generic.invalid.itemRefinement"));
return;
} // Fallthrough
case 3: // <itemId|itemName> [amount] [level]
try {
lvl = Integer.parseInt(args.get(2));
} catch (NumberFormatException ignored) {
CommandHandler.sendMessage(sender, translate(sender, "commands.generic.invalid.itemLevel"));
return;
} // Fallthrough
case 2: // <itemId|itemName> [amount]
try {
amount = Integer.parseInt(args.get(1));
} catch (NumberFormatException ignored) {
CommandHandler.sendMessage(sender, translate(sender, "commands.generic.invalid.amount"));
return;
} // Fallthrough
case 1: // <itemId|itemName>
try {
item = Integer.parseInt(args.get(0));
} catch (NumberFormatException ignored) {
// TODO: Parse from item name using GM Handbook.
CommandHandler.sendMessage(sender, translate(sender, "commands.generic.invalid.itemId"));
return;
}
// At this point, first remaining argument MUST be itemId/avatarId
if (args.size() < 1) {
CommandHandler.sendTranslatedMessage(sender, "commands.give.usage"); // Reachable if someone does `/give lv90` or similar
throw new IllegalArgumentException();
}
String id = args.remove(0);
boolean isRelic = false;
switch (id) {
case "all":
param.giveAllType = GiveAllType.ALL;
break;
default: // *No args*
CommandHandler.sendMessage(sender, translate(sender, "commands.give.usage"));
return;
case "weapons":
param.giveAllType = GiveAllType.WEAPONS;
break;
case "mats":
param.giveAllType = GiveAllType.MATS;
break;
case "avatars":
param.giveAllType = GiveAllType.AVATARS;
break;
default:
try {
param.id = Integer.parseInt(id);
param.data = GameData.getItemDataMap().get(param.id);
if ((param.id > 10_000_000) && (param.id < 12_000_000))
param.avatarData = GameData.getAvatarDataMap().get(param.id);
else if ((param.id > 1000) && (param.id < 1100))
param.avatarData = GameData.getAvatarDataMap().get(param.id - 1000 + 10_000_000);
isRelic = ((param.data != null) && (param.data.getItemType() == ItemType.ITEM_RELIQUARY));
} catch (NumberFormatException e) {
// TODO: Parse from item name using GM Handbook.
CommandHandler.sendTranslatedMessage(sender, "commands.generic.invalid.itemId");
throw e;
}
}
ItemData itemData = GameData.getItemDataMap().get(item);
if (itemData == null) {
CommandHandler.sendMessage(sender, translate(sender, "commands.generic.invalid.itemId"));
if (param.amount < 1) param.amount = 1;
if (param.refinement < 1) param.refinement = 1;
if (param.refinement > 5) param.refinement = 5;
if (isRelic) {
// Input 0-20 to match game, instead of 1-21 which is the real level
if (param.lvl < 0) param.lvl = 0;
if (param.lvl > 20) param.lvl = 20;
param.lvl += 1;
if (illegalRelicIds.contains(param.id))
CommandHandler.sendTranslatedMessage(sender, "commands.give.illegal_relic");
} else {
// Suitable for Avatars and Weapons
if (param.lvl < 1) param.lvl = 1;
if (param.lvl > 90) param.lvl = 90;
}
if (isRelic && !args.isEmpty()) {
try {
parseRelicArgs(param, args);
} catch (IllegalArgumentException e) {
CommandHandler.sendTranslatedMessage(sender, "commands.execution.argument_error");
CommandHandler.sendTranslatedMessage(sender, "commands.give.usage_relic");
throw e;
}
}
return param;
}
@Override
public void execute(Player sender, Player targetPlayer, List<String> args) {
if (args.size() < 1) { // *No args*
CommandHandler.sendTranslatedMessage(sender, "commands.give.usage");
return;
}
if (refinement != 0) {
if (itemData.getItemType() == ItemType.ITEM_WEAPON) {
if (refinement < 1 || refinement > 5) {
CommandHandler.sendMessage(sender, translate(sender, "commands.give.refinement_must_between_1_and_5"));
try {
GiveItemParameters param = parseArgs(sender, args);
switch (param.giveAllType) {
case ALL:
giveAll(targetPlayer, param);
CommandHandler.sendTranslatedMessage(sender, "commands.give.giveall_success");
return;
}
} else {
CommandHandler.sendMessage(sender, translate(sender, "commands.give.refinement_only_applicable_weapons"));
case WEAPONS:
giveAllWeapons(targetPlayer, param);
CommandHandler.sendTranslatedMessage(sender, "commands.give.giveall_success");
return;
case MATS:
giveAllMats(targetPlayer, param);
CommandHandler.sendTranslatedMessage(sender, "commands.give.giveall_success");
return;
case AVATARS:
giveAllAvatars(targetPlayer, param);
CommandHandler.sendTranslatedMessage(sender, "commands.give.giveall_success");
return;
case NONE:
break;
}
// Check if this is an avatar
if (param.avatarData != null) {
Avatar avatar = makeAvatar(param);
targetPlayer.addAvatar(avatar);
CommandHandler.sendTranslatedMessage(sender, "commands.give.given_avatar", Integer.toString(param.id), Integer.toString(param.lvl), Integer.toString(targetPlayer.getUid()));
return;
}
// If it's not an avatar, it needs to be a valid item
if (param.data == null) {
CommandHandler.sendTranslatedMessage(sender, "commands.generic.invalid.itemId");
return;
}
switch (param.data.getItemType()) {
case ITEM_WEAPON:
targetPlayer.getInventory().addItems(makeUnstackableItems(param), ActionReason.SubfieldDrop);
CommandHandler.sendTranslatedMessage(sender, "commands.give.given_with_level_and_refinement", Integer.toString(param.id), Integer.toString(param.lvl), Integer.toString(param.refinement), Integer.toString(param.amount), Integer.toString(targetPlayer.getUid()));
return;
case ITEM_RELIQUARY:
targetPlayer.getInventory().addItems(makeArtifacts(param), ActionReason.SubfieldDrop);
CommandHandler.sendTranslatedMessage(sender, "commands.give.given_level", Integer.toString(param.id), Integer.toString(param.lvl), Integer.toString(param.amount), Integer.toString(targetPlayer.getUid()));
//CommandHandler.sendTranslatedMessage(sender, "commands.giveArtifact.success", Integer.toString(param.id), Integer.toString(targetPlayer.getUid()));
return;
default:
targetPlayer.getInventory().addItem(new GameItem(param.data, param.amount), ActionReason.SubfieldDrop);
CommandHandler.sendTranslatedMessage(sender, "commands.give.given", Integer.toString(param.amount), Integer.toString(param.id), Integer.toString(targetPlayer.getUid()));
return;
}
} catch (IllegalArgumentException ignored) {
return;
}
}
this.item(targetPlayer, itemData, amount, lvl, refinement);
private static Avatar makeAvatar(GiveItemParameters param) {
return makeAvatar(param.avatarData, param.lvl, Avatar.getMinPromoteLevel(param.lvl), 0);
}
if (!itemData.isEquip()) {
CommandHandler.sendMessage(sender, translate(sender, "commands.give.given", Integer.toString(amount), Integer.toString(item), Integer.toString(targetPlayer.getUid())));
} else if (itemData.getItemType() == ItemType.ITEM_WEAPON) {
CommandHandler.sendMessage(sender, translate(sender, "commands.give.given_with_level_and_refinement", Integer.toString(item), Integer.toString(lvl), Integer.toString(refinement), Integer.toString(amount), Integer.toString(targetPlayer.getUid())));
} else {
CommandHandler.sendMessage(sender, translate(sender, "commands.give.given_level", Integer.toString(item), Integer.toString(lvl), Integer.toString(amount), Integer.toString(targetPlayer.getUid())));
private static Avatar makeAvatar(AvatarData avatarData, int level, int promoteLevel, int constellation) {
// Calculate ascension level.
Avatar avatar = new Avatar(avatarData);
avatar.setLevel(level);
avatar.setPromoteLevel(promoteLevel);
// Add constellations.
int talentBase = switch (avatar.getAvatarId()) {
case 10000005 -> 70;
case 10000006 -> 40;
default -> (avatar.getAvatarId() - 10000000) * 10;
};
for (int i = 1; i <= constellation; i++) {
avatar.getTalentIdList().add(talentBase + i);
}
// Main character needs skill depot manually added.
if (avatar.getAvatarId() == GameConstants.MAIN_CHARACTER_MALE) {
avatar.setSkillDepotData(GameData.getAvatarSkillDepotDataMap().get(504));
}
else if(avatar.getAvatarId() == GameConstants.MAIN_CHARACTER_FEMALE) {
avatar.setSkillDepotData(GameData.getAvatarSkillDepotDataMap().get(704));
}
avatar.recalcStats();
return avatar;
}
private static void giveAllAvatars(Player player, GiveItemParameters param) {
int promoteLevel = Avatar.getMinPromoteLevel(param.lvl);
if (param.constellation < 0) {
param.constellation = 6;
}
for (AvatarData avatarData : GameData.getAvatarDataMap().values()) {
// Exclude test avatars
int id = avatarData.getId();
if (id < 10000002 || id >= 11000000) continue;
// Don't try to add each avatar to the current team
player.addAvatar(makeAvatar(avatarData, param.lvl, promoteLevel, param.constellation), false);
}
}
private void item(Player player, ItemData itemData, int amount, int lvl, int refinement) {
if (itemData.isEquip()) {
List<GameItem> items = new LinkedList<>();
for (int i = 0; i < amount; i++) {
GameItem item = new GameItem(itemData);
if (item.isEquipped()) {
// check item max level
if (item.getItemType() == ItemType.ITEM_WEAPON) {
if (lvl > 90) lvl = 90;
} else {
if (lvl > 21) lvl = 21;
}
}
item.setCount(amount);
item.setLevel(lvl);
if (lvl > 80) {
item.setPromoteLevel(6);
} else if (lvl > 70) {
item.setPromoteLevel(5);
} else if (lvl > 60) {
item.setPromoteLevel(4);
} else if (lvl > 50) {
item.setPromoteLevel(3);
} else if (lvl > 40) {
item.setPromoteLevel(2);
} else if (lvl > 20) {
item.setPromoteLevel(1);
}
if (item.getItemType() == ItemType.ITEM_WEAPON) {
if (refinement > 0) {
item.setRefinement(refinement - 1);
} else {
item.setRefinement(0);
}
}
items.add(item);
private static List<GameItem> makeUnstackableItems(GiveItemParameters param) {
int promoteLevel = GameItem.getMinPromoteLevel(param.lvl);
int totalExp = 0;
if (param.data.getItemType() == ItemType.ITEM_WEAPON) {
int rankLevel = param.data.getRankLevel();
for (int i = 1; i < param.lvl; i++)
totalExp += GameData.getWeaponExpRequired(rankLevel, i);
}
List<GameItem> items = new ArrayList<>(param.amount);
for (int i = 0; i < param.amount; i++) {
GameItem item = new GameItem(param.data);
item.setLevel(param.lvl);
if (item.getItemType() == ItemType.ITEM_WEAPON) {
item.setPromoteLevel(promoteLevel);
item.setTotalExp(totalExp);
item.setRefinement(param.refinement - 1); // Actual refinement data is 0..4 not 1..5
}
player.getInventory().addItems(items, ActionReason.SubfieldDrop);
} else {
GameItem item = new GameItem(itemData);
item.setCount(amount);
player.getInventory().addItem(item, ActionReason.SubfieldDrop);
items.add(item);
}
return items;
}
private static List<GameItem> makeArtifacts(GiveItemParameters param) {
param.lvl = Math.min(param.lvl, param.data.getMaxLevel());
int rank = param.data.getRankLevel();
int totalExp = 0;
for (int i = 1; i < param.lvl; i++)
totalExp += GameData.getRelicExpRequired(rank, i);
List<GameItem> items = new ArrayList<>(param.amount);
for (int i = 0; i < param.amount; i++) {
// Create item for the artifact.
GameItem item = new GameItem(param.data);
item.setLevel(param.lvl);
item.setTotalExp(totalExp);
int numAffixes = param.data.getAppendPropNum() + (param.lvl-1)/4;
if (param.mainPropId > 0) // Keep random mainProp if we didn't specify one
item.setMainPropId(param.mainPropId);
if (param.appendPropIdList != null) {
item.getAppendPropIdList().clear();
item.getAppendPropIdList().addAll(param.appendPropIdList);
}
// If we didn't include enough substats, top them up to the appropriate level at random
item.addAppendProps(numAffixes - item.getAppendPropIdList().size());
items.add(item);
}
return items;
}
private static int getArtifactMainProp(ItemData itemData, FightProperty prop) throws IllegalArgumentException {
if (prop != FightProperty.FIGHT_PROP_NONE)
for (ReliquaryMainPropData data : GameDepot.getRelicMainPropList(itemData.getMainPropDepotId()))
if (data.getWeight() > 0 && data.getFightProp() == prop)
return data.getId();
throw new IllegalArgumentException();
}
private static List<Integer> getArtifactAffixes(ItemData itemData, FightProperty prop) throws IllegalArgumentException {
if (prop == FightProperty.FIGHT_PROP_NONE) {
throw new IllegalArgumentException();
}
List<Integer> affixes = new ArrayList<>();
for (ReliquaryAffixData data : GameDepot.getRelicAffixList(itemData.getAppendPropDepotId())) {
if (data.getWeight() > 0 && data.getFightProp() == prop) {
affixes.add(data.getId());
}
}
return affixes;
}
private static int getAppendPropId(String substatText, ItemData itemData) throws IllegalArgumentException {
// If the given substat text is an integer, we just use that as the append prop ID.
try {
return Integer.parseInt(substatText);
} catch (NumberFormatException ignored) {
// If the argument was not an integer, we try to determine
// the append prop ID from the given text + artifact information.
// A substat string has the format `substat_tier`, with the
// `_tier` part being optional, defaulting to the maximum.
String[] substatArgs = substatText.split("_");
String substatType = substatArgs[0];
int substatTier = 4;
if (substatArgs.length > 1) {
substatTier = Integer.parseInt(substatArgs[1]);
}
List<Integer> substats = getArtifactAffixes(itemData, FightProperty.getPropByShortName(substatType));
if (substats.isEmpty()) {
throw new IllegalArgumentException();
}
substatTier -= 1; // 1-indexed to 0-indexed
substatTier = Math.min(Math.max(0, substatTier), substats.size() - 1);
return substats.get(substatTier);
}
}
private static void parseRelicArgs(GiveItemParameters param, List<String> args) throws IllegalArgumentException {
// Get the main stat from the arguments.
// If the given argument is an integer, we use that.
// If not, we check if the argument string is in the main prop map.
String mainPropIdString = args.remove(0);
try {
param.mainPropId = Integer.parseInt(mainPropIdString);
} catch (NumberFormatException ignored) {
// This can in turn throw an exception which we don't want to catch here.
param.mainPropId = getArtifactMainProp(param.data, FightProperty.getPropByShortName(mainPropIdString));
}
// Get substats.
param.appendPropIdList = new ArrayList<>();
// Every remaining argument is a substat.
for (String prop : args) {
// The substat syntax permits specifying a number of rolls for the given
// substat. Split the string into stat and number if that is the case here.
String[] arr = prop.split(",");
prop = arr[0];
int n = 1;
if (arr.length > 1) {
n = Math.min(Integer.parseInt(arr[1]), 200);
}
// Determine the substat ID.
int appendPropId = getAppendPropId(prop, param.data);
// Add the current substat.
for (int i = 0; i < n; i++) {
param.appendPropIdList.add(appendPropId);
}
};
}
private static void addItemsChunked(Player player, List<GameItem> items, int packetSize) {
// Send the items in multiple packets
int lastIdx = items.size() - 1;
for (int i = 0; i <= lastIdx; i += packetSize) {
player.getInventory().addItems(items.subList(i, Math.min(i + packetSize, lastIdx)));
}
}
private static void giveAllMats(Player player, GiveItemParameters param) {
List<GameItem> itemList = new ArrayList<>();
for (ItemData itemdata : GameData.getItemDataMap().values()) {
int id = itemdata.getId();
if (id < 100_000) continue; // Nothing meaningful below this
if (illegalItemIds.contains(id)) continue;
if (itemdata.isEquip()) continue;
GameItem item = new GameItem(itemdata);
item.setCount(param.amount);
itemList.add(item);
}
addItemsChunked(player, itemList, 100);
}
private static void giveAllWeapons(Player player, GiveItemParameters param) {
int promoteLevel = GameItem.getMinPromoteLevel(param.lvl);
int quantity = Math.min(param.amount, 5);
int refinement = param.refinement - 1;
List<GameItem> itemList = new ArrayList<>();
for (ItemData itemdata : GameData.getItemDataMap().values()) {
int id = itemdata.getId();
if (id < 11100 || id > 16000) continue; // All extant weapons are within this range
if (illegalWeaponIds.contains(id)) continue;
if (!itemdata.isEquip()) continue;
if (itemdata.getItemType() != ItemType.ITEM_WEAPON) continue;
for (int i = 0; i < quantity; i++) {
GameItem item = new GameItem(itemdata);
item.setLevel(param.lvl);
item.setPromoteLevel(promoteLevel);
item.setRefinement(refinement);
itemList.add(item);
}
}
addItemsChunked(player, itemList, 100);
}
private static void giveAll(Player player, GiveItemParameters param) {
giveAllAvatars(player, param);
giveAllMats(player, param);
giveAllWeapons(player, param);
}
private static final SparseSet illegalWeaponIds = new SparseSet("""
10000-10008, 11411, 11506-11508, 12505, 12506, 12508, 12509,
13503, 13506, 14411, 14503, 14505, 14508, 15504-15506
""");
private static final SparseSet illegalRelicIds = new SparseSet("""
20001, 23300-23340, 23383-23385, 78310-78554, 99310-99554
""");
private static final SparseSet illegalItemIds = new SparseSet("""
100086, 100087, 100100-101000, 101106-101110, 101306, 101500-104000,
105001, 105004, 106000-107000, 107011, 108000, 109000-110000,
115000-130000, 200200-200899, 220050, 220054
""");
}

View File

@@ -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,15 +8,15 @@ import java.util.List;
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 {
@Override
public void execute(Player sender, Player targetPlayer, List<String> args) {
if (sender != null) {
CommandHandler.sendMessage(sender, translate(sender, "commands.kick.player_kick_player",
Integer.toString(sender.getUid()), sender.getAccount().getUsername(),
Integer.toString(targetPlayer.getUid()), targetPlayer.getAccount().getUsername()));
CommandHandler.sendMessage(sender, translate(sender, "commands.kick.player_kick_player",
Integer.toString(sender.getUid()), sender.getAccount().getUsername(),
Integer.toString(targetPlayer.getUid()), targetPlayer.getAccount().getUsername()));
} else {
CommandHandler.sendMessage(null, translate(sender, "commands.kick.server_kick_player", Integer.toString(targetPlayer.getUid()), targetPlayer.getAccount().getUsername()));
}

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

View File

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

View File

@@ -2,37 +2,25 @@ package emu.grasscutter.command.commands;
import java.util.List;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.Account;
import emu.grasscutter.game.player.Player;
import static emu.grasscutter.utils.Language.translate;
@Command(
label = "unban",
usage = "unban <player>",
usage = "unban <@player>",
description = "commands.unban.description",
targetRequirement = Command.TargetRequirement.NONE
permission = "server.ban",
targetRequirement = Command.TargetRequirement.PLAYER
)
public final class UnBanCommand implements CommandHandler {
private boolean unBanAccount(int uid) {
Player player = Grasscutter.getGameServer().getPlayerByUid(uid, true);
if (player == null) {
return false;
}
Account account = player.getAccount();
private boolean unBanAccount(Player targetPlayer) {
Account account = targetPlayer.getAccount();
if (account == null) {
account = DatabaseHelper.getAccountByPlayerId(uid);
if (account == null) {
return false;
}
return false;
}
account.setBanReason(null);
@@ -46,24 +34,10 @@ public final class UnBanCommand implements CommandHandler {
@Override
public void execute(Player sender, Player targetPlayer, List<String> args) {
if (args.size() < 1) {
CommandHandler.sendMessage(sender, translate(sender, "commands.unban.command_usage"));
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"));
if (unBanAccount(targetPlayer)) {
CommandHandler.sendTranslatedMessage(sender, "commands.unban.success");
} 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<CityData> cityDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<WeatherData> weatherDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<BattlePassMissionExcelConfigData> battlePassMissionExcelConfigDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<BattlePassRewardExcelConfigData> battlePassRewardExcelConfigDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<BattlePassMissionData> battlePassMissionDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<BattlePassRewardData> battlePassRewardDataMap = new Int2ObjectOpenHashMap<>();
// Cache
private static Map<Integer, List<Integer>> fetters = new HashMap<>();
@@ -424,11 +424,11 @@ public class GameData {
return weatherDataMap;
}
public static Int2ObjectMap<BattlePassMissionExcelConfigData> getBattlePassMissionExcelConfigDataMap() {
return battlePassMissionExcelConfigDataMap;
public static Int2ObjectMap<BattlePassMissionData> getBattlePassMissionDataMap() {
return battlePassMissionDataMap;
}
public static Int2ObjectMap<BattlePassRewardExcelConfigData> getBattlePassRewardExcelConfigDataMap() {
return battlePassRewardExcelConfigDataMap;
public static Int2ObjectMap<BattlePassRewardData> getBattlePassRewardDataMap() {
return battlePassRewardDataMap;
}
}

View File

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

View File

@@ -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")
@Getter
@Setter
public class BattlePassRewardExcelConfigData extends GameResource {
public class BattlePassRewardData extends GameResource {
private int indexId;
private int level;
private List<Integer> freeRewardIdList;
@@ -23,5 +22,6 @@ public class BattlePassRewardExcelConfigData extends GameResource {
@Override
public void onLoad() {
}
}

View File

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

View File

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

View File

@@ -131,6 +131,7 @@ public final class DatabaseHelper {
DatabaseManager.getGameDatabase().getCollection("gachas").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("battlepass").deleteMany(eq("ownerUid", player.getUid()));
// Delete 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) {
this.player = player;
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(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(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(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(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() {
@@ -128,7 +129,7 @@ public class HealAbilityManager {
int fightPropertyType = 0;
float healAmount = 0;
float ratio = 0, base = 0;
float maxHP, curAttack, curDefense;
float maxHP, curHP, curAttack, curDefense;
Map<String, Float> map = sourceEntity.getMetaOverrideMap();
for(int i = 0 ; i < healDataAvatarList.size() ; i ++) {
@@ -139,7 +140,7 @@ public class HealAbilityManager {
for(int j = 0 ; j < healDataList.size(); j++) {
HealData healData = healDataList.get(j);
if(map.containsKey(healData.sRatio)) {
if(map.containsKey(healData.sRatio) || modifierString.equals(healData.sRatio)) {
if(healData.isString) {
ratio = map.get(healData.sRatio);
base = map.get(healData.sBase);
@@ -173,8 +174,15 @@ public class HealAbilityManager {
if(healActionAvatar != null) {
maxHP = healActionAvatar.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP);
curHP = healActionAvatar.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP);
curAttack = healActionAvatar.getFightProperty(FightProperty.FIGHT_PROP_CUR_ATTACK);
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) {
case 0:
healAmount = ratio * maxHP + base;

View File

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

View File

@@ -1,23 +1,53 @@
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 dev.morphia.annotations.Entity;
import dev.morphia.annotations.Id;
import dev.morphia.annotations.Indexed;
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.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.PacketBattlePassMissionUpdateNotify;
import emu.grasscutter.server.packet.send.PacketTakeBattlePassRewardRsp;
import lombok.Getter;
@Entity(value = "battlepass", useDiscriminator = false)
public class BattlePassManager {
@Id private ObjectId id;
@Transient private Player player;
@Id @Getter private ObjectId id;
@Transient @Getter private Player player;
@Indexed private int ownerUid;
private int point;
private int awardTakenLevel;
@Getter private int point;
@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
public BattlePassManager() {}
@@ -26,39 +56,226 @@ public class BattlePassManager {
this.setPlayer(player);
}
public ObjectId getId() {
return id;
}
public Player getPlayer() {
return this.player;
}
public void setPlayer(Player player) {
this.player = player;
this.ownerUid = player.getUid();
}
public int getPoint() {
return point;
public void updateViewed() {
this.viewed = true;
}
public int getAwardTakenLevel() {
return awardTakenLevel;
}
public boolean setLevel(int level) {
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){
this.point += point;
player.getSession().send(new PacketBattlePassCurScheduleUpdateNotify(player.getSession().getPlayer()));
public void addPoints(int points){
this.addPointsDirectly(points, false);
this.player.sendPacket(new PacketBattlePassCurScheduleUpdateNotify(player));
this.save();
}
public void updateAwardTakenLevel(int level){
this.awardTakenLevel = level;
player.getSession().send(new PacketBattlePassCurScheduleUpdateNotify(player.getSession().getPlayer()));
this.save();
public void addPointsDirectly(int points, boolean isWeekly) {
int amount = points;
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();
// 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() {
DatabaseHelper.saveBattlePass(this);
}

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.player.Player;
import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.game.props.WatcherTriggerType;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.net.proto.GadgetInteractReqOuterClass.GadgetInteractReq;
import emu.grasscutter.scripts.constants.EventType;
@@ -98,6 +99,8 @@ public class DungeonChallenge extends WorldChallenge {
getScene().getDungeonSettleObservers().forEach(o -> o.onDungeonSettle(getScene()));
getScene().getScriptManager().callEvent(EventType.EVENT_DUNGEON_SETTLE,
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.FightProperty;
import emu.grasscutter.game.props.PlayerProperty;
import emu.grasscutter.game.props.WatcherTriggerType;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.net.proto.AbilitySyncStateInfoOuterClass.AbilitySyncStateInfo;
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()));
}
}
// Battle Pass trigger
getScene().getPlayers().forEach(p -> p.getBattlePassManager().triggerMission(WatcherTriggerType.TRIGGER_MONSTER_DIE, this.getMonsterId(), 1));
}
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.MaterialType;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.WatcherTriggerType;
import emu.grasscutter.net.proto.GachaItemOuterClass.GachaItem;
import emu.grasscutter.net.proto.GachaTransferItemOuterClass.GachaTransferItem;
import emu.grasscutter.net.proto.GetGachaInfoRspOuterClass.GetGachaInfoRsp;
@@ -372,9 +373,12 @@ public class GachaManager {
if (starglitter > 0) {
inventory.addItem(starglitterId, starglitter);
}
// Packets
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) {

View File

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

View File

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

View File

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

View File

@@ -2,6 +2,7 @@ package emu.grasscutter.game.managers;
import emu.grasscutter.game.player.Player;
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.PacketResinChangeNotify;
import emu.grasscutter.utils.Utils;
@@ -43,9 +44,11 @@ public class ResinManager {
}
// Send packets.
this.player.sendPacket(new PacketPlayerPropNotify(this.player, PlayerProperty.PROP_PLAYER_RESIN));
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;
}
@@ -66,7 +69,6 @@ public class ResinManager {
}
// Send packets.
this.player.sendPacket(new PacketPlayerPropNotify(this.player, PlayerProperty.PROP_PLAYER_RESIN));
this.player.sendPacket(new PacketResinChangeNotify(this.player));
}
@@ -113,7 +115,6 @@ public class ResinManager {
}
// Send packets.
this.player.sendPacket(new PacketPlayerPropNotify(this.player, PlayerProperty.PROP_PLAYER_RESIN));
this.player.sendPacket(new PacketResinChangeNotify(this.player));
}
@@ -137,7 +138,6 @@ public class ResinManager {
}
// Send initial notifications on logon.
this.player.sendPacket(new PacketPlayerPropNotify(this.player, PlayerProperty.PROP_PLAYER_RESIN));
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.net.proto.ChangeHpReasonOuterClass.ChangeHpReason;
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.PacketEntityFightPropUpdateNotify;
@@ -26,7 +25,7 @@ public class SotSManager {
private Timer autoRecoverTimer;
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) {
this.player = player;

View File

@@ -446,5 +446,10 @@ public class EnergyManager {
public void setEnergyUsage(Boolean 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.Map;
import com.mongodb.QueryBuilder;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.common.ItemParamData;
import emu.grasscutter.data.excels.ForgeData;
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.net.proto.ForgeStartReqOuterClass;
import emu.grasscutter.game.props.WatcherTriggerType;
import emu.grasscutter.net.proto.ForgeQueueDataOuterClass.ForgeQueueData;
import emu.grasscutter.net.proto.ForgeQueueManipulateReqOuterClass.ForgeQueueManipulateReq;
import emu.grasscutter.net.proto.ForgeQueueManipulateTypeOuterClass.ForgeQueueManipulateType;
@@ -147,6 +144,13 @@ public class ForgingManager {
}
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.
List<ItemParamData> material = new ArrayList<>(forgeData.getMaterialItems());
@@ -158,6 +162,9 @@ public class ForgingManager {
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.
ActiveForgeData activeForge = new ActiveForgeData();
activeForge.setForgeId(req.getForgeId());
@@ -195,6 +202,9 @@ public class ForgingManager {
GameItem addItem = new GameItem(resultItemData, data.getResultItemCount() * finished);
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.
if (unfinished > 0) {
@@ -252,6 +262,12 @@ public class ForgingManager {
GameItem returnMora = new GameItem(moraItem, data.getScoinCost() * forge.getCount());
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.
this.player.getActiveForges().remove(queueId - 1);
this.sendForgeQueueDataNotify(true);

View File

@@ -2,8 +2,6 @@ package emu.grasscutter.game.managers.stamina;
import ch.qos.logback.classic.Logger;
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.entity.EntityAvatar;
import emu.grasscutter.game.entity.GameEntity;
@@ -112,8 +110,8 @@ public class StaminaManager {
}};
private final Logger logger = Grasscutter.getLogger();
public final static int GlobalCharacterMaximumStamina = 24000;
public final static int GlobalVehicleMaxStamina = 24000;
public final static int GlobalCharacterMaximumStamina = PlayerProperty.PROP_MAX_STAMINA.getMax();
public final static int GlobalVehicleMaxStamina = PlayerProperty.PROP_MAX_STAMINA.getMax();
private Position currentCoordinates = new Position(0, 0, 0);
private Position previousCoordinates = new Position(0, 0, 0);
private MotionState currentState = MotionState.MOTION_STATE_STANDBY;
@@ -285,14 +283,13 @@ public class StaminaManager {
// Returns new stamina and sends PlayerPropNotify or VehicleStaminaNotify
public int setStamina(GameSession session, String reason, int newStamina, boolean isCharacterStamina) {
// Target Player
if (!GAME_OPTIONS.staminaUsage || session.getPlayer().getStamina()) {
if (!GAME_OPTIONS.staminaUsage || session.getPlayer().getUnlimitedStamina()) {
newStamina = getMaxCharacterStamina();
}
// set stamina if is character stamina
if (isCharacterStamina) {
player.setProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA, newStamina);
session.send(new PacketPlayerPropNotify(player, PlayerProperty.PROP_CUR_PERSIST_STAMINA));
} else {
vehicleStamina = newStamina;
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.WeatherData;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.database.DatabaseManager;
import emu.grasscutter.game.Account;
import emu.grasscutter.game.CoopRequest;
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.PlayerProperty;
import emu.grasscutter.game.props.SceneType;
import emu.grasscutter.game.props.WatcherTriggerType;
import emu.grasscutter.game.quest.QuestManager;
import emu.grasscutter.game.shop.ShopLimit;
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 lombok.Getter;
import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.*;
import java.util.concurrent.LinkedBlockingQueue;
@@ -180,6 +185,7 @@ public class Player {
private long springLastUsed;
private HashMap<String, MapMark> mapMarks;
private int nextResinRefresh;
private int lastDailyReset;
@Deprecated
@SuppressWarnings({"rawtypes", "unchecked"}) // Morphia only!
@@ -253,14 +259,14 @@ public class Player {
this.teamManager = new TeamManager(this);
this.birthday = new PlayerBirthday();
this.codex = new PlayerCodex(this);
this.setProperty(PlayerProperty.PROP_PLAYER_LEVEL, 1);
this.setProperty(PlayerProperty.PROP_IS_SPRING_AUTO_USE, 1);
this.setProperty(PlayerProperty.PROP_SPRING_AUTO_USE_PERCENT, 50);
this.setProperty(PlayerProperty.PROP_IS_FLYABLE, 1);
this.setProperty(PlayerProperty.PROP_IS_TRANSFERABLE, 1);
this.setProperty(PlayerProperty.PROP_MAX_STAMINA, 24000);
this.setProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA, 24000);
this.setProperty(PlayerProperty.PROP_PLAYER_RESIN, 160);
this.setProperty(PlayerProperty.PROP_PLAYER_LEVEL, 1, false);
this.setProperty(PlayerProperty.PROP_IS_SPRING_AUTO_USE, 1, false);
this.setProperty(PlayerProperty.PROP_SPRING_AUTO_USE_PERCENT, 50, false);
this.setProperty(PlayerProperty.PROP_IS_FLYABLE, 1, false);
this.setProperty(PlayerProperty.PROP_IS_TRANSFERABLE, 1, false);
this.setProperty(PlayerProperty.PROP_MAX_STAMINA, 24000, false);
this.setProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA, 24000, false);
this.setProperty(PlayerProperty.PROP_PLAYER_RESIN, 160, false);
this.getFlyCloakList().add(140001);
this.getNameCardList().add(210001);
this.getPos().set(GameConstants.START_POSITION);
@@ -290,7 +296,9 @@ public class Player {
}
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) {
@@ -429,12 +437,13 @@ public class Player {
return this.getProperty(PlayerProperty.PROP_PLAYER_LEVEL);
}
public void setLevel(int level) {
this.setProperty(PlayerProperty.PROP_PLAYER_LEVEL, level);
this.sendPacket(new PacketPlayerPropNotify(this, PlayerProperty.PROP_PLAYER_LEVEL));
this.updateWorldLevel();
this.updateProfile();
public boolean setLevel(int level) {
if (this.setProperty(PlayerProperty.PROP_PLAYER_LEVEL, level)) {
this.updateWorldLevel();
this.updateProfile();
return true;
}
return false;
}
public int getExp() {
@@ -444,48 +453,59 @@ public class Player {
public int getWorldLevel() {
return this.getProperty(PlayerProperty.PROP_PLAYER_WORLD_LEVEL);
}
public boolean setWorldLevel(int level) {
if (this.setProperty(PlayerProperty.PROP_PLAYER_WORLD_LEVEL, 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();
return true;
}
return false;
}
public void setWorldLevel(int level) {
this.setProperty(PlayerProperty.PROP_PLAYER_WORLD_LEVEL, level);
this.sendPacket(new PacketPlayerPropNotify(this, PlayerProperty.PROP_PLAYER_WORLD_LEVEL));
public int getForgePoints() {
return this.getProperty(PlayerProperty.PROP_PLAYER_FORGE_POINT);
}
this.updateProfile();
public boolean setForgePoints(int value) {
if (value == this.getForgePoints()) {
return true;
}
return this.setProperty(PlayerProperty.PROP_PLAYER_FORGE_POINT, value);
}
public int getPrimogems() {
return this.getProperty(PlayerProperty.PROP_PLAYER_HCOIN);
}
public void setPrimogems(int primogem) {
this.setProperty(PlayerProperty.PROP_PLAYER_HCOIN, primogem);
this.sendPacket(new PacketPlayerPropNotify(this, PlayerProperty.PROP_PLAYER_HCOIN));
public boolean setPrimogems(int primogem) {
return this.setProperty(PlayerProperty.PROP_PLAYER_HCOIN, primogem);
}
public int getMora() {
return this.getProperty(PlayerProperty.PROP_PLAYER_SCOIN);
}
public void setMora(int mora) {
this.setProperty(PlayerProperty.PROP_PLAYER_SCOIN, mora);
this.sendPacket(new PacketPlayerPropNotify(this, PlayerProperty.PROP_PLAYER_SCOIN));
public boolean setMora(int mora) {
return this.setProperty(PlayerProperty.PROP_PLAYER_SCOIN, mora);
}
public int getCrystals() {
return this.getProperty(PlayerProperty.PROP_PLAYER_MCOIN);
}
public void setCrystals(int crystals) {
this.setProperty(PlayerProperty.PROP_PLAYER_MCOIN, crystals);
this.sendPacket(new PacketPlayerPropNotify(this, PlayerProperty.PROP_PLAYER_MCOIN));
public boolean setCrystals(int crystals) {
return this.setProperty(PlayerProperty.PROP_PLAYER_MCOIN, crystals);
}
public int getHomeCoin() {
return this.getProperty(PlayerProperty.PROP_PLAYER_HOME_COIN);
}
public void setHomeCoin(int coin) {
this.setProperty(PlayerProperty.PROP_PLAYER_HOME_COIN, coin);
this.sendPacket(new PacketPlayerPropNotify(this, PlayerProperty.PROP_PLAYER_HOME_COIN));
public boolean setHomeCoin(int coin) {
return this.setProperty(PlayerProperty.PROP_PLAYER_HOME_COIN, coin);
}
private int getExpRequired(int level) {
PlayerLevelData levelData = GameData.getPlayerLevelDataMap().get(level);
@@ -523,9 +543,6 @@ public class Player {
// Set exp
this.setProperty(PlayerProperty.PROP_PLAYER_EXP, exp);
// Update player with packet
this.sendPacket(new PacketPlayerPropNotify(this, PlayerProperty.PROP_PLAYER_EXP));
}
private void updateWorldLevel() {
@@ -544,7 +561,6 @@ public class Player {
0;
if (newWorldLevel != currentWorldLevel) {
this.getWorld().setWorldLevel(newWorldLevel);
this.setWorldLevel(newWorldLevel);
}
}
@@ -566,7 +582,7 @@ public class Player {
}
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
towerData = new TowerData();
}
@@ -599,7 +615,11 @@ public class Player {
}
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) {
@@ -794,6 +814,14 @@ public class Player {
return showAvatarList;
}
public int getLastDailyReset() {
return this.lastDailyReset;
}
public void setLastDailyReset(int value) {
this.lastDailyReset = value;
}
public boolean inMoonCard() {
return moonCard;
}
@@ -926,12 +954,10 @@ public class Player {
}
this.save();
}
public boolean getStamina() {
// Get Stamina
public boolean getUnlimitedStamina() {
return stamina;
}
public void setStamina(boolean stamina) {
// Set Stamina
public void setUnlimitedStamina(boolean stamina) {
this.stamina = stamina;
}
public boolean inGodmode() {
@@ -1268,6 +1294,7 @@ public class Player {
public void loadBattlePassManager() {
if (this.battlePassManager != null) return;
this.battlePassManager = DatabaseHelper.loadBattlePass(this);
this.battlePassManager.getMissions().values().removeIf(mission -> mission.getData() == null);
}
public AbilityManager getAbilityManager() {
@@ -1313,6 +1340,10 @@ public class Player {
this.resetSendPlayerLocTime();
}
}
// Handle daily reset.
this.doDailyReset();
// Expedition
var timeNow = Utils.getCurrentSeconds();
var needNotify = false;
@@ -1337,8 +1368,24 @@ public class Player {
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() {
this.nextSendPlayerLocTime = System.currentTimeMillis() + 5000;
@@ -1405,8 +1452,8 @@ public class Player {
world.addPlayer(this);
// Multiplayer setting
this.setProperty(PlayerProperty.PROP_PLAYER_MP_SETTING_TYPE, this.getMpSetting().getNumber());
this.setProperty(PlayerProperty.PROP_IS_MP_MODE_AVAILABLE, 1);
this.setProperty(PlayerProperty.PROP_PLAYER_MP_SETTING_TYPE, this.getMpSetting().getNumber(), false);
this.setProperty(PlayerProperty.PROP_IS_MP_MODE_AVAILABLE, 1, false);
// Packets
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.
// Battle Pass trigger
this.getBattlePassManager().triggerMission(WatcherTriggerType.TRIGGER_LOGIN);
this.furnitureManager.onLogin();
// Home
home = GameHome.getByUid(getUid());
@@ -1478,10 +1528,6 @@ public class Player {
// Call quit event.
PlayerQuitEvent event = new PlayerQuitEvent(this); event.call();
//reset wood
getDeforestationManager().resetWood();
}catch (Throwable e){
e.printStackTrace();
Grasscutter.getLogger().warn("Player (UID {}) save failure", getUid());
@@ -1515,101 +1561,41 @@ public class Player {
this.messageHandler = messageHandler;
}
private void saveSanityCheckedProperty(PlayerProperty prop, int value) {
getProperties().put(prop.getId(), value);
public int getPropertyMin(PlayerProperty prop) {
if (prop.getDynamicRange()) {
return switch (prop) {
default -> 0;
};
} else {
return prop.getMin();
}
}
private boolean setPropertyWithSanityCheck(PlayerProperty prop, int value) {
if (prop == PlayerProperty.PROP_EXP) { // 1001
if (!(value >= 0)) { return false; }
} else if (prop == PlayerProperty.PROP_BREAK_LEVEL) { // 1002
// TODO: implement sanity check
} else if (prop == PlayerProperty.PROP_SATIATION_VAL) { // 1003
// TODO: implement sanity check
} else if (prop == PlayerProperty.PROP_SATIATION_PENALTY_TIME) { // 1004
// TODO: implement sanity check
} 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
public int getPropertyMax(PlayerProperty prop) {
if (prop.getDynamicRange()) {
return switch (prop) {
case PROP_CUR_SPRING_VOLUME -> getProperty(PlayerProperty.PROP_MAX_SPRING_VOLUME);
case PROP_CUR_PERSIST_STAMINA -> getProperty(PlayerProperty.PROP_MAX_STAMINA);
default -> 0;
};
} else {
return prop.getMax();
}
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;
}
}
}

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;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static java.util.Map.entry;
import java.util.Arrays;
import java.util.stream.Stream;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
@@ -133,4 +139,65 @@ public enum FightProperty {
public static FightProperty getPropByName(String name) {
return stringMap.getOrDefault(name, FIGHT_PROP_NONE);
}
public static FightProperty getPropByShortName(String name) {
return shortNameMap.getOrDefault(name, FIGHT_PROP_NONE);
}
public static Set<String> getShortNames() {
return shortNameMap.keySet();
}
// This was originally for relic properties so some names might not be applicable for e.g. setstats
private static final Map<String, FightProperty> shortNameMap = Map.ofEntries(
// Normal relic stats
entry("hp", FIGHT_PROP_HP),
entry("atk", FIGHT_PROP_ATTACK),
entry("def", FIGHT_PROP_DEFENSE),
entry("hp%", FIGHT_PROP_HP_PERCENT),
entry("atk%", FIGHT_PROP_ATTACK_PERCENT),
entry("def%", FIGHT_PROP_DEFENSE_PERCENT),
entry("em", FIGHT_PROP_ELEMENT_MASTERY),
entry("er", FIGHT_PROP_CHARGE_EFFICIENCY),
entry("hb", FIGHT_PROP_HEAL_ADD),
entry("heal", FIGHT_PROP_HEAL_ADD),
entry("cd", FIGHT_PROP_CRITICAL_HURT),
entry("cdmg", FIGHT_PROP_CRITICAL_HURT),
entry("cr", FIGHT_PROP_CRITICAL),
entry("crate", FIGHT_PROP_CRITICAL),
entry("phys%", FIGHT_PROP_PHYSICAL_ADD_HURT),
entry("dendro%", FIGHT_PROP_GRASS_ADD_HURT),
entry("geo%", FIGHT_PROP_ROCK_ADD_HURT),
entry("anemo%", FIGHT_PROP_WIND_ADD_HURT),
entry("hydro%", FIGHT_PROP_WATER_ADD_HURT),
entry("cryo%", FIGHT_PROP_ICE_ADD_HURT),
entry("electro%", FIGHT_PROP_ELEC_ADD_HURT),
entry("pyro%", FIGHT_PROP_FIRE_ADD_HURT),
// Other stats
entry("maxhp", FIGHT_PROP_MAX_HP),
entry("dmg", FIGHT_PROP_ADD_HURT), // This seems to get reset after attacks
entry("cdr", FIGHT_PROP_SKILL_CD_MINUS_RATIO),
entry("heali", FIGHT_PROP_HEALED_ADD),
entry("shield", FIGHT_PROP_SHIELD_COST_MINUS_RATIO),
entry("defi", FIGHT_PROP_DEFENCE_IGNORE_RATIO),
entry("resall", FIGHT_PROP_SUB_HURT), // This seems to get reset after attacks
entry("resanemo", FIGHT_PROP_WIND_SUB_HURT),
entry("rescryo", FIGHT_PROP_ICE_SUB_HURT),
entry("resdendro", FIGHT_PROP_GRASS_SUB_HURT),
entry("reselectro", FIGHT_PROP_ELEC_SUB_HURT),
entry("resgeo", FIGHT_PROP_ROCK_SUB_HURT),
entry("reshydro", FIGHT_PROP_WATER_SUB_HURT),
entry("respyro", FIGHT_PROP_FIRE_SUB_HURT),
entry("resphys", FIGHT_PROP_PHYSICAL_SUB_HURT)
);
private static final List<FightProperty> flatProps = Arrays.asList(
FIGHT_PROP_BASE_HP, FIGHT_PROP_HP, FIGHT_PROP_BASE_ATTACK, FIGHT_PROP_ATTACK, FIGHT_PROP_BASE_DEFENSE,
FIGHT_PROP_DEFENSE, FIGHT_PROP_HEALED_ADD, FIGHT_PROP_CUR_FIRE_ENERGY, FIGHT_PROP_CUR_ELEC_ENERGY,
FIGHT_PROP_CUR_WATER_ENERGY, FIGHT_PROP_CUR_GRASS_ENERGY, FIGHT_PROP_CUR_WIND_ENERGY, FIGHT_PROP_CUR_ICE_ENERGY,
FIGHT_PROP_CUR_ROCK_ENERGY, FIGHT_PROP_CUR_HP, FIGHT_PROP_MAX_HP, FIGHT_PROP_CUR_ATTACK, FIGHT_PROP_CUR_DEFENSE);
public static boolean isPercentage(FightProperty prop) {
return !flatProps.contains(prop);
}
}

View File

@@ -6,41 +6,42 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
public enum PlayerProperty {
PROP_EXP (1001),
PROP_NONE (0),
PROP_EXP (1001, 0),
PROP_BREAK_LEVEL (1002),
PROP_SATIATION_VAL (1003),
PROP_SATIATION_PENALTY_TIME (1004),
PROP_LEVEL (4001),
PROP_LEVEL (4001, 0, 90),
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_CUR_SPRING_VOLUME (10003), // 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_SPRING_AUTO_USE_PERCENT (10005), // 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_WEATHER_LOCKED (10007),
PROP_IS_GAME_TIME_LOCKED (10008),
PROP_IS_TRANSFERABLE (10009),
PROP_MAX_STAMINA (10010), // Maximum stamina of the player (0 - 24000)
PROP_CUR_PERSIST_STAMINA (10011), // Used stamina of the player (0 - PROP_MAX_STAMINA)
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, true), // Current volume of the Statue of the Seven [0, PROP_MAX_SPRING_VOLUME]
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, 0, 100), // Auto HP recovery percentage [0, 100]
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, 0, 1),
PROP_IS_GAME_TIME_LOCKED (10008, 0, 1),
PROP_IS_TRANSFERABLE (10009, 0, 1),
PROP_MAX_STAMINA (10010, 0, 24_000), // Maximum stamina of the player (0 - 24000)
PROP_CUR_PERSIST_STAMINA (10011, true), // Used stamina of the player (0 - PROP_MAX_STAMINA)
PROP_CUR_TEMPORARY_STAMINA (10012),
PROP_PLAYER_LEVEL (10013),
PROP_PLAYER_LEVEL (10013, 1, 60),
PROP_PLAYER_EXP (10014),
PROP_PLAYER_HCOIN (10015), // Primogem (-inf, +inf)
// 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.
PROP_PLAYER_SCOIN (10016), // 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_IS_MP_MODE_AVAILABLE (10018), // 0 if in quest or something that disables MP [0, 1]
PROP_PLAYER_WORLD_LEVEL (10019), // [0, 8]
PROP_PLAYER_RESIN (10020), // Original Resin [0, +inf)
PROP_PLAYER_SCOIN (10016, 0), // Mora [0, +inf)
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, 1), // 0 if in quest or something that disables MP [0, 1]
PROP_PLAYER_WORLD_LEVEL (10019, 0, 8), // [0, 8]
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_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_WAIT_SUB_MCOIN (10026),
PROP_PLAYER_LEGENDARY_KEY (10027),
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_TYPE (10036),
PROP_CUR_CLIMATE_AREA_ID (10037),
@@ -48,22 +49,55 @@ public enum PlayerProperty {
PROP_PLAYER_WORLD_LEVEL_LIMIT (10039),
PROP_PLAYER_WORLD_LEVEL_ADJUST_CD (10040),
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);
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<>();
static {
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.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() {
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) {

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

View File

@@ -5,15 +5,19 @@ import emu.grasscutter.Grasscutter;
import emu.grasscutter.command.CommandMap;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.Account;
import emu.grasscutter.game.battlepass.BattlePassMissionManager;
import emu.grasscutter.game.combine.CombineManger;
import emu.grasscutter.game.drop.DropManager;
import emu.grasscutter.game.dungeons.DungeonManager;
import emu.grasscutter.game.dungeons.challenge.DungeonChallenge;
import emu.grasscutter.game.expedition.ExpeditionManager;
import emu.grasscutter.game.gacha.GachaManager;
import emu.grasscutter.game.managers.InventoryManager;
import emu.grasscutter.game.managers.MultiplayerManager;
import emu.grasscutter.game.managers.chat.ChatManager;
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.quest.ServerQuestHandler;
import emu.grasscutter.game.shop.ShopManager;
@@ -51,19 +55,19 @@ public final class GameServer extends KcpServer {
private final Set<World> worlds;
private ChatManagerHandler chatManager;
private final InventoryManager inventoryManager;
private final GachaManager gachaManager;
private final ShopManager shopManager;
private final MultiplayerManager multiplayerManager;
private final DungeonManager dungeonManager;
private final ExpeditionManager expeditionManager;
private final CommandMap commandMap;
private final TaskMap taskMap;
private final DropManager dropManager;
private final WorldDataManager worldDataManager;
private final CombineManger combineManger;
private final TowerScheduleManager towerScheduleManager;
@Getter private final InventoryManager inventoryManager;
@Getter private final GachaManager gachaManager;
@Getter private final ShopManager shopManager;
@Getter private final MultiplayerManager multiplayerManager;
@Getter private final DungeonManager dungeonManager;
@Getter private final ExpeditionManager expeditionManager;
@Getter private final CommandMap commandMap;
@Getter private final TaskMap taskMap;
@Getter private final DropManager dropManager;
@Getter private final WorldDataManager worldDataManager;
@Getter private final BattlePassMissionManager battlePassMissionManager;
@Getter private final CombineManger combineManger;
@Getter private final TowerScheduleManager towerScheduleManager;
public GameServer() {
this(getAdapterInetSocketAddress());
@@ -81,6 +85,10 @@ public final class GameServer extends KcpServer {
this.init(GameSessionManager.getListener(),channelConfig,address);
DungeonChallenge.initialize();
EnergyManager.initialize();
StaminaManager.initialize();
this.address = address;
this.packetHandler = new GameServerPacketHandler(PacketHandler.class);
this.questHandler = new ServerQuestHandler();
@@ -101,6 +109,8 @@ public final class GameServer extends KcpServer {
this.combineManger = new CombineManger(this);
this.towerScheduleManager = new TowerScheduleManager(this);
this.worldDataManager = new WorldDataManager(this);
this.battlePassMissionManager = new BattlePassMissionManager(this);
// Hook into shutdown event.
Runtime.getRuntime().addShutdownHook(new Thread(this::onServerShutdown));
}
@@ -129,53 +139,6 @@ public final class GameServer extends KcpServer {
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(){
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;
import emu.grasscutter.net.packet.BasePacket;
import emu.grasscutter.net.packet.Opcodes;
import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.packet.PacketHandler;
@@ -10,5 +11,18 @@ public class HandlerPlayerForceExitReq extends PacketHandler {
@Override
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
// 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.packet.send.PacketSetPlayerPropRsp;
import java.util.ArrayList;
import java.util.List;
@Opcodes(PacketOpcodes.SetPlayerPropReq)
public class HandlerSetPlayerPropReq extends PacketHandler {
@@ -21,11 +18,10 @@ public class HandlerSetPlayerPropReq extends PacketHandler {
// Auto template
SetPlayerPropReq req = SetPlayerPropReq.parseFrom(payload);
Player player = session.getPlayer();
List<PropValue> propList = req.getPropListList();
for (int i = 0; i < propList.size(); i++) {
PlayerProperty prop = PlayerProperty.getPropById(propList.get(i).getType());
for (PropValue p : req.getPropListList()) {
PlayerProperty prop = PlayerProperty.getPropById(p.getType());
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));
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;
import emu.grasscutter.net.packet.Opcodes;
import emu.grasscutter.net.packet.PacketHandler;
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.packet.send.PacketBattlePassCurScheduleUpdateNotify;
import emu.grasscutter.server.packet.send.PacketBattlePassMissionUpdateNotify;
import emu.grasscutter.server.packet.send.PacketTakeBattlePassMissionPointRsp;
@Opcodes(PacketOpcodes.TakeBattlePassMissionPointReq)
@@ -15,11 +12,10 @@ public class HandlerTakeBattlePassMissionPointReq extends PacketHandler {
@Override
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
var req
= TakeBattlePassMissionPointReqOuterClass.TakeBattlePassMissionPointReq.parseFrom(payload);
var req = 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());
}
}

View File

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

View File

@@ -5,6 +5,7 @@ import emu.grasscutter.game.player.Player;
import emu.grasscutter.net.packet.BasePacket;
import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.*;
import emu.grasscutter.net.proto.BattlePassAllDataNotifyOuterClass.BattlePassAllDataNotify;
import java.util.ArrayList;
import java.util.List;
@@ -13,50 +14,25 @@ public class PacketBattlePassAllDataNotify extends BasePacket {
public PacketBattlePassAllDataNotify(Player player) {
super(PacketOpcodes.BattlePassAllDataNotify);
var value = player.getBattlePassManager().getPoint();
var proto = BattlePassAllDataNotify.newBuilder();
proto
.setHaveCurSchedule(true)
.setCurSchedule(player.getBattlePassManager().getScheduleProto());
int level = (int) Math.floor(value / 1000d);
var point = value - level * 1000;
List<BattlePassRewardTagOuterClass.BattlePassRewardTag> rewardTags = new ArrayList<>();
for (int id = 1; id <= player.getBattlePassManager().getAwardTakenLevel(); id++)
rewardTags.add(BattlePassRewardTagOuterClass.BattlePassRewardTag.newBuilder()
.setLevel(id)
.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());
for (var missionData : GameData.getBattlePassMissionDataMap().values()) {
// Dont send invalid refresh types
if (!missionData.isValidRefreshType()) {
continue;
}
// 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())) {
proto.addMissionList(player.getBattlePassManager().loadMissionById(missionData.getId()).toProto());
} else {
proto.addMissionList(missionData.toProto());
}
}
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.PacketOpcodes;
import emu.grasscutter.net.proto.*;
import emu.grasscutter.net.proto.BattlePassCurScheduleUpdateNotifyOuterClass.BattlePassCurScheduleUpdateNotify;
import java.util.ArrayList;
import java.util.List;
public class PacketBattlePassCurScheduleUpdateNotify extends BasePacket {
public PacketBattlePassCurScheduleUpdateNotify(Player player) {
super(PacketOpcodes.BattlePassCurScheduleUpdateNotify);
var value = player.getBattlePassManager().getPoint();
int level = (int) Math.floor(value / 1000d);
var point = value - level * 1000;
var proto = BattlePassCurScheduleUpdateNotify.newBuilder();
List<BattlePassRewardTagOuterClass.BattlePassRewardTag> rewardTags = new ArrayList<>();
for (int id = 1; id <= player.getBattlePassManager().getAwardTakenLevel(); id++)
rewardTags.add(BattlePassRewardTagOuterClass.BattlePassRewardTag.newBuilder()
.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();
proto
.setHaveCurSchedule(true)
.setCurSchedule(player.getBattlePassManager().getScheduleProto())
.build();
setData(proto.build());

View File

@@ -1,39 +1,33 @@
package emu.grasscutter.server.packet.send;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.BattlePassMissionExcelConfigData;
import java.util.Collection;
import emu.grasscutter.game.battlepass.BattlePassMission;
import emu.grasscutter.net.packet.BasePacket;
import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.BattlePassMissionOuterClass;
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;
import emu.grasscutter.net.proto.BattlePassMissionUpdateNotifyOuterClass.BattlePassMissionUpdateNotify;
public class PacketBattlePassMissionUpdateNotify extends BasePacket {
public PacketBattlePassMissionUpdateNotify(List<Integer> missionIdList , GameSession session) {
public PacketBattlePassMissionUpdateNotify(BattlePassMission mission) {
super(PacketOpcodes.BattlePassMissionUpdateNotify);
var proto
= BattlePassMissionUpdateNotifyOuterClass.BattlePassMissionUpdateNotify.newBuilder();
var proto = BattlePassMissionUpdateNotify.newBuilder()
.addMissionList(mission.toProto())
.build();
Map<Integer, BattlePassMissionExcelConfigData> missionMap
= GameData.getBattlePassMissionExcelConfigDataMap();
this.setData(proto);
}
public PacketBattlePassMissionUpdateNotify(Collection<BattlePassMission> missions) {
super(PacketOpcodes.BattlePassMissionUpdateNotify);
missionIdList.forEach(missionId -> proto.addMissionList
(BattlePassMissionOuterClass.BattlePassMission.newBuilder().setMissionId(missionId).setMissionStatus
(BattlePassMissionOuterClass.BattlePassMission.MissionStatus.MISSION_STATUS_POINT_TAKEN)
.setTotalProgress(missionMap.get(missionId).getProgress()).setRewardBattlePassPoint(missionMap.get(missionId).getAddPoint()).build()));
var point = session.getPlayer().getBattlePassManager().getPoint();
missionIdList.forEach(missionId
-> session.getPlayer().getBattlePassManager().addPoint(missionMap.get(missionId).getAddPoint()));
Grasscutter.getLogger().info("[PacketBattlePassMissionUpdateNotify] addPoint: {}", session.getPlayer().getBattlePassManager().getPoint() - point);
var proto = BattlePassMissionUpdateNotify.newBuilder();
missions.forEach(mission -> {
proto.addMissionList(mission.toProto());
});
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
for (Mail.MailItem mailItem : message.itemList) {
EquipParamOuterClass.EquipParam.Builder item = EquipParamOuterClass.EquipParam.newBuilder();
int promoteLevel = 0;
if (mailItem.itemLevel > 80) { // 80/90
promoteLevel = 6;
} else if (mailItem.itemLevel > 70) { // 70/80
promoteLevel = 5;
} else if (mailItem.itemLevel > 60) { // 60/70
promoteLevel = 4;
} else if (mailItem.itemLevel > 50) { // 50/60
promoteLevel = 3;
} else if (mailItem.itemLevel > 40) { // 40/50
promoteLevel = 2;
} else if (mailItem.itemLevel > 20) { // 20/40
promoteLevel = 1;
}
int promoteLevel = GameItem.getMinPromoteLevel(mailItem.itemLevel);
item.setItemId(mailItem.itemId);
item.setItemNum(mailItem.itemCount);

View File

@@ -0,0 +1,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;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.BattlePassRewardExcelConfigData;
import emu.grasscutter.data.excels.RewardData;
import emu.grasscutter.data.common.ItemParamData;
import emu.grasscutter.net.packet.BasePacket;
import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.BattlePassRewardTakeOptionOuterClass;
import emu.grasscutter.net.proto.ItemParamOuterClass;
import emu.grasscutter.net.proto.TakeBattlePassRewardRspOuterClass;
import emu.grasscutter.net.proto.BattlePassRewardTakeOptionOuterClass.BattlePassRewardTakeOption;
import emu.grasscutter.net.proto.ItemParamOuterClass.ItemParam;
import emu.grasscutter.net.proto.TakeBattlePassRewardRspOuterClass.TakeBattlePassRewardRsp;
import emu.grasscutter.server.game.GameSession;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class PacketTakeBattlePassRewardRsp extends BasePacket {
public PacketTakeBattlePassRewardRsp(List<BattlePassRewardTakeOptionOuterClass.BattlePassRewardTakeOption> takeOptionList , GameSession session) {
public PacketTakeBattlePassRewardRsp(List<BattlePassRewardTakeOption> takeOptionList, List<ItemParamData> rewardItems) {
super(PacketOpcodes.TakeBattlePassRewardRsp);
var proto
= TakeBattlePassRewardRspOuterClass.TakeBattlePassRewardRsp.newBuilder();
Map<Integer , BattlePassRewardExcelConfigData> excelConfigDataMap = GameData.getBattlePassRewardExcelConfigDataMap();
Map<Integer , RewardData> rewardDataMap = GameData.getRewardDataMap();
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());
var proto = TakeBattlePassRewardRsp.newBuilder()
.addAllTakeOptionList(takeOptionList);
if (rewardItems != null) {
for (ItemParamData param : rewardItems) {
proto.addItemList(ItemParam.newBuilder().setItemId(param.getItemId()).setCount(param.getCount()));
}
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);
}
}

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