From 5eb1d34b14e22bd445545fa1f522b3508c764c02 Mon Sep 17 00:00:00 2001 From: Melledy <52122272+Melledy@users.noreply.github.com> Date: Fri, 24 Jun 2022 01:12:52 -0700 Subject: [PATCH 01/23] Implement battle pass triggers/rewards --- .../java/emu/grasscutter/GameConstants.java | 4 + .../command/commands/SetBPLevelCommand.java | 3 +- .../java/emu/grasscutter/data/GameData.java | 12 +- .../data/excels/BattlePassMissionData.java | 69 ++++ .../BattlePassMissionExcelConfigData.java | 29 -- ...figData.java => BattlePassRewardData.java} | 4 +- .../grasscutter/database/DatabaseHelper.java | 1 + .../game/battlepass/BattlePassManager.java | 252 ++++++++++++- .../game/battlepass/BattlePassMission.java | 70 ++++ .../battlepass/BattlePassMissionManager.java | 78 ++++ .../game/battlepass/BattlePassReward.java | 52 +++ .../dungeons/challenge/DungeonChallenge.java | 3 + .../game/entity/EntityMonster.java | 3 + .../grasscutter/game/gacha/GachaManager.java | 6 +- .../grasscutter/game/inventory/Inventory.java | 10 +- .../game/managers/ResinManager.java | 6 +- .../game/managers/forging/ForgingManager.java | 4 + .../emu/grasscutter/game/player/Player.java | 5 + .../props/BattlePassMissionRefreshType.java | 18 + .../game/props/BattlePassMissionStatus.java | 26 ++ .../game/props/WatcherTriggerType.java | 337 ++++++++++++++++++ .../grasscutter/server/game/GameServer.java | 8 + .../recv/HandlerBuyBattlePassLevelReq.java | 22 ++ .../recv/HandlerSetBattlePassViewedReq.java | 19 + .../HandlerTakeBattlePassMissionPointReq.java | 12 +- .../recv/HandlerTakeBattlePassRewardReq.java | 12 +- .../send/PacketBattlePassAllDataNotify.java | 62 +--- ...cketBattlePassCurScheduleUpdateNotify.java | 28 +- .../PacketBattlePassMissionUpdateNotify.java | 42 +-- .../send/PacketBuyBattlePassLevelRsp.java | 18 + .../send/PacketSetBattlePassViewedRsp.java | 18 + .../send/PacketTakeBattlePassRewardRsp.java | 43 +-- 32 files changed, 1079 insertions(+), 197 deletions(-) create mode 100644 src/main/java/emu/grasscutter/data/excels/BattlePassMissionData.java delete mode 100644 src/main/java/emu/grasscutter/data/excels/BattlePassMissionExcelConfigData.java rename src/main/java/emu/grasscutter/data/excels/{BattlePassRewardExcelConfigData.java => BattlePassRewardData.java} (87%) create mode 100644 src/main/java/emu/grasscutter/game/battlepass/BattlePassMission.java create mode 100644 src/main/java/emu/grasscutter/game/battlepass/BattlePassMissionManager.java create mode 100644 src/main/java/emu/grasscutter/game/battlepass/BattlePassReward.java create mode 100644 src/main/java/emu/grasscutter/game/props/BattlePassMissionRefreshType.java create mode 100644 src/main/java/emu/grasscutter/game/props/BattlePassMissionStatus.java create mode 100644 src/main/java/emu/grasscutter/game/props/WatcherTriggerType.java create mode 100644 src/main/java/emu/grasscutter/server/packet/recv/HandlerBuyBattlePassLevelReq.java create mode 100644 src/main/java/emu/grasscutter/server/packet/recv/HandlerSetBattlePassViewedReq.java create mode 100644 src/main/java/emu/grasscutter/server/packet/send/PacketBuyBattlePassLevelRsp.java create mode 100644 src/main/java/emu/grasscutter/server/packet/send/PacketSetBattlePassViewedRsp.java diff --git a/src/main/java/emu/grasscutter/GameConstants.java b/src/main/java/emu/grasscutter/GameConstants.java index 5e50e3fd6..088ad8555 100644 --- a/src/main/java/emu/grasscutter/GameConstants.java +++ b/src/main/java/emu/grasscutter/GameConstants.java @@ -18,6 +18,10 @@ 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_LEVEL_PRICE = 150; + // Default entity ability hashes. public static final String[] DEFAULT_ABILITY_STRINGS = { "Avatar_DefaultAbility_VisionReplaceDieInvincible", "Avatar_DefaultAbility_AvartarInShaderChange", "Avatar_SprintBS_Invincible", diff --git a/src/main/java/emu/grasscutter/command/commands/SetBPLevelCommand.java b/src/main/java/emu/grasscutter/command/commands/SetBPLevelCommand.java index 2da2567dd..47c9ee88e 100644 --- a/src/main/java/emu/grasscutter/command/commands/SetBPLevelCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/SetBPLevelCommand.java @@ -17,7 +17,6 @@ public final class SetBPLevelCommand implements CommandHandler { int level = Integer.parseInt(args.get(0)); - sender.getBattlePassManager().addPoint(level); - sender.getBattlePassManager().updateAwardTakenLevel(0); + sender.getBattlePassManager().addPoints(level); } } diff --git a/src/main/java/emu/grasscutter/data/GameData.java b/src/main/java/emu/grasscutter/data/GameData.java index 9ed0390cc..59b7c4944 100644 --- a/src/main/java/emu/grasscutter/data/GameData.java +++ b/src/main/java/emu/grasscutter/data/GameData.java @@ -95,8 +95,8 @@ public class GameData { private static final Int2ObjectMap investigationMonsterDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap cityDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap weatherDataMap = new Int2ObjectOpenHashMap<>(); - private static final Int2ObjectMap battlePassMissionExcelConfigDataMap = new Int2ObjectOpenHashMap<>(); - private static final Int2ObjectMap battlePassRewardExcelConfigDataMap = new Int2ObjectOpenHashMap<>(); + private static final Int2ObjectMap battlePassMissionDataMap = new Int2ObjectOpenHashMap<>(); + private static final Int2ObjectMap battlePassRewardDataMap = new Int2ObjectOpenHashMap<>(); // Cache private static Map> fetters = new HashMap<>(); @@ -424,11 +424,11 @@ public class GameData { return weatherDataMap; } - public static Int2ObjectMap getBattlePassMissionExcelConfigDataMap() { - return battlePassMissionExcelConfigDataMap; + public static Int2ObjectMap getBattlePassMissionDataMap() { + return battlePassMissionDataMap; } - public static Int2ObjectMap getBattlePassRewardExcelConfigDataMap() { - return battlePassRewardExcelConfigDataMap; + public static Int2ObjectMap getBattlePassRewardDataMap() { + return battlePassRewardDataMap; } } diff --git a/src/main/java/emu/grasscutter/data/excels/BattlePassMissionData.java b/src/main/java/emu/grasscutter/data/excels/BattlePassMissionData.java new file mode 100644 index 000000000..d51c2e6eb --- /dev/null +++ b/src/main/java/emu/grasscutter/data/excels/BattlePassMissionData.java @@ -0,0 +1,69 @@ +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 mainParams; + + @Override + public int getId() { + return this.id; + } + + public WatcherTriggerType getTriggerType() { + return this.getTriggerConfig().getTriggerType(); + } + + 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(); + } +} diff --git a/src/main/java/emu/grasscutter/data/excels/BattlePassMissionExcelConfigData.java b/src/main/java/emu/grasscutter/data/excels/BattlePassMissionExcelConfigData.java deleted file mode 100644 index 10ecd963b..000000000 --- a/src/main/java/emu/grasscutter/data/excels/BattlePassMissionExcelConfigData.java +++ /dev/null @@ -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; - } -} diff --git a/src/main/java/emu/grasscutter/data/excels/BattlePassRewardExcelConfigData.java b/src/main/java/emu/grasscutter/data/excels/BattlePassRewardData.java similarity index 87% rename from src/main/java/emu/grasscutter/data/excels/BattlePassRewardExcelConfigData.java rename to src/main/java/emu/grasscutter/data/excels/BattlePassRewardData.java index ca7f6277a..95ca24a80 100644 --- a/src/main/java/emu/grasscutter/data/excels/BattlePassRewardExcelConfigData.java +++ b/src/main/java/emu/grasscutter/data/excels/BattlePassRewardData.java @@ -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 freeRewardIdList; @@ -23,5 +22,6 @@ public class BattlePassRewardExcelConfigData extends GameResource { @Override public void onLoad() { + } } diff --git a/src/main/java/emu/grasscutter/database/DatabaseHelper.java b/src/main/java/emu/grasscutter/database/DatabaseHelper.java index 2696860c9..d4592b005 100644 --- a/src/main/java/emu/grasscutter/database/DatabaseHelper.java +++ b/src/main/java/emu/grasscutter/database/DatabaseHelper.java @@ -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, diff --git a/src/main/java/emu/grasscutter/game/battlepass/BattlePassManager.java b/src/main/java/emu/grasscutter/game/battlepass/BattlePassManager.java index 0bc22a71b..adee62b89 100644 --- a/src/main/java/emu/grasscutter/game/battlepass/BattlePassManager.java +++ b/src/main/java/emu/grasscutter/game/battlepass/BattlePassManager.java @@ -1,14 +1,34 @@ 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.BattlePassMissionStatus; +import emu.grasscutter.game.props.WatcherTriggerType; +import emu.grasscutter.net.proto.BattlePassCycleOuterClass.BattlePassCycle; +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; @Entity(value = "battlepass", useDiscriminator = false) public class BattlePassManager { @@ -17,7 +37,13 @@ public class BattlePassManager { @Indexed private int ownerUid; private int point; - private int awardTakenLevel; + private int level; + + private boolean viewed; + private boolean paid; + + private Map missions; + private Map takenRewards; @Deprecated // Morphia only public BattlePassManager() {} @@ -40,25 +66,215 @@ public class BattlePassManager { } public int getPoint() { - return point; - } - - public int getAwardTakenLevel() { - return awardTakenLevel; - } - - public void addPoint(int point){ - this.point += point; - player.getSession().send(new PacketBattlePassCurScheduleUpdateNotify(player.getSession().getPlayer())); - this.save(); - } - - public void updateAwardTakenLevel(int level){ - this.awardTakenLevel = level; - player.getSession().send(new PacketBattlePassCurScheduleUpdateNotify(player.getSession().getPlayer())); - this.save(); + return this.point; } + public int getLevel() { + return this.level; + } + + public boolean isViewed() { + return viewed; + } + + public void updateViewed() { + this.viewed = true; + } + + public boolean isPaid() { + return paid; + } + + public void addPoints(int point){ + this.addPointsDirectly(point); + + player.getSession().send(new PacketBattlePassCurScheduleUpdateNotify(player.getSession().getPlayer())); + this.save(); + } + + public void addPointsDirectly(int point) { + this.point += point; + + if (this.point >= GameConstants.BATTLE_PASS_POINT_PER_LEVEL && this.getLevel() < GameConstants.BATTLE_PASS_MAX_LEVEL) { + int levelups = (int) Math.floor((float) 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 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 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 missionIdList) { + // Obvious exploit check + if (missionIdList.size() > GameData.getBattlePassMissionDataMap().size()) { + return; + } + + List 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.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 takeOptionList) { + List 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 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(0) + .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); } diff --git a/src/main/java/emu/grasscutter/game/battlepass/BattlePassMission.java b/src/main/java/emu/grasscutter/game/battlepass/BattlePassMission.java new file mode 100644 index 000000000..be870b911 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/battlepass/BattlePassMission.java @@ -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(); + } +} diff --git a/src/main/java/emu/grasscutter/game/battlepass/BattlePassMissionManager.java b/src/main/java/emu/grasscutter/game/battlepass/BattlePassMissionManager.java new file mode 100644 index 000000000..d45c4d978 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/battlepass/BattlePassMissionManager.java @@ -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> 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 triggerList = getTriggers().computeIfAbsent(missionData.getTriggerType(), e -> new ArrayList<>()); + triggerList.add(missionData); + } + } + } + + public GameServer getServer() { + return server; + } + + private Map> 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 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)); + } + } +} diff --git a/src/main/java/emu/grasscutter/game/battlepass/BattlePassReward.java b/src/main/java/emu/grasscutter/game/battlepass/BattlePassReward.java new file mode 100644 index 000000000..5d05ab84b --- /dev/null +++ b/src/main/java/emu/grasscutter/game/battlepass/BattlePassReward.java @@ -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(); + } +} diff --git a/src/main/java/emu/grasscutter/game/dungeons/challenge/DungeonChallenge.java b/src/main/java/emu/grasscutter/game/dungeons/challenge/DungeonChallenge.java index 1333fc96e..6836f44c8 100644 --- a/src/main/java/emu/grasscutter/game/dungeons/challenge/DungeonChallenge.java +++ b/src/main/java/emu/grasscutter/game/dungeons/challenge/DungeonChallenge.java @@ -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)); } } diff --git a/src/main/java/emu/grasscutter/game/entity/EntityMonster.java b/src/main/java/emu/grasscutter/game/entity/EntityMonster.java index 99ab3ea0e..b8c47e3db 100644 --- a/src/main/java/emu/grasscutter/game/entity/EntityMonster.java +++ b/src/main/java/emu/grasscutter/game/entity/EntityMonster.java @@ -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() { diff --git a/src/main/java/emu/grasscutter/game/gacha/GachaManager.java b/src/main/java/emu/grasscutter/game/gacha/GachaManager.java index 10cc5dae3..c7fff96e5 100644 --- a/src/main/java/emu/grasscutter/game/gacha/GachaManager.java +++ b/src/main/java/emu/grasscutter/game/gacha/GachaManager.java @@ -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) { diff --git a/src/main/java/emu/grasscutter/game/inventory/Inventory.java b/src/main/java/emu/grasscutter/game/inventory/Inventory.java index b22217bd9..13f201c01 100644 --- a/src/main/java/emu/grasscutter/game/inventory/Inventory.java +++ b/src/main/java/emu/grasscutter/game/inventory/Inventory.java @@ -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 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 { 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); } } @@ -368,7 +372,7 @@ public class Inventory implements Iterable { if (count <= 0 || item == null) { return false; } - + if (item.getItemData().isEquip()) { item.setCount(0); } else { @@ -389,6 +393,10 @@ public class Inventory implements Iterable { 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(); diff --git a/src/main/java/emu/grasscutter/game/managers/ResinManager.java b/src/main/java/emu/grasscutter/game/managers/ResinManager.java index cefd0ede8..e55809f41 100644 --- a/src/main/java/emu/grasscutter/game/managers/ResinManager.java +++ b/src/main/java/emu/grasscutter/game/managers/ResinManager.java @@ -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; @@ -45,7 +46,10 @@ 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; } diff --git a/src/main/java/emu/grasscutter/game/managers/forging/ForgingManager.java b/src/main/java/emu/grasscutter/game/managers/forging/ForgingManager.java index b9908e248..4edb6bf33 100644 --- a/src/main/java/emu/grasscutter/game/managers/forging/ForgingManager.java +++ b/src/main/java/emu/grasscutter/game/managers/forging/ForgingManager.java @@ -16,6 +16,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.net.proto.ForgeStartReqOuterClass; import emu.grasscutter.net.proto.ForgeQueueDataOuterClass.ForgeQueueData; import emu.grasscutter.net.proto.ForgeQueueManipulateReqOuterClass.ForgeQueueManipulateReq; @@ -195,6 +196,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) { diff --git a/src/main/java/emu/grasscutter/game/player/Player.java b/src/main/java/emu/grasscutter/game/player/Player.java index f52f6d6be..f883d27a6 100644 --- a/src/main/java/emu/grasscutter/game/player/Player.java +++ b/src/main/java/emu/grasscutter/game/player/Player.java @@ -42,6 +42,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; @@ -1268,6 +1269,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() { @@ -1425,6 +1427,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()); diff --git a/src/main/java/emu/grasscutter/game/props/BattlePassMissionRefreshType.java b/src/main/java/emu/grasscutter/game/props/BattlePassMissionRefreshType.java new file mode 100644 index 000000000..b07f20ea2 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/props/BattlePassMissionRefreshType.java @@ -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; + } +} diff --git a/src/main/java/emu/grasscutter/game/props/BattlePassMissionStatus.java b/src/main/java/emu/grasscutter/game/props/BattlePassMissionStatus.java new file mode 100644 index 000000000..7f2dabc90 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/props/BattlePassMissionStatus.java @@ -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; + } +} diff --git a/src/main/java/emu/grasscutter/game/props/WatcherTriggerType.java b/src/main/java/emu/grasscutter/game/props/WatcherTriggerType.java new file mode 100644 index 000000000..d9be1d8e2 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/props/WatcherTriggerType.java @@ -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 map = new Int2ObjectOpenHashMap<>(); + private static final Map 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); + } +} diff --git a/src/main/java/emu/grasscutter/server/game/GameServer.java b/src/main/java/emu/grasscutter/server/game/GameServer.java index 76eedbc51..4ae5c8549 100644 --- a/src/main/java/emu/grasscutter/server/game/GameServer.java +++ b/src/main/java/emu/grasscutter/server/game/GameServer.java @@ -5,6 +5,7 @@ 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; @@ -61,6 +62,7 @@ public final class GameServer extends KcpServer { private final TaskMap taskMap; private final DropManager dropManager; private final WorldDataManager worldDataManager; + private final BattlePassMissionManager battlePassMissionManager; private final CombineManger combineManger; private final TowerScheduleManager towerScheduleManager; @@ -101,6 +103,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)); } @@ -173,6 +177,10 @@ public final class GameServer extends KcpServer { return worldDataManager; } + public BattlePassMissionManager getBattlePassMissionManager() { + return battlePassMissionManager; + } + public TaskMap getTaskMap() { return this.taskMap; } diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerBuyBattlePassLevelReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerBuyBattlePassLevelReq.java new file mode 100644 index 000000000..abe2aebd8 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerBuyBattlePassLevelReq.java @@ -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)); + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerSetBattlePassViewedReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerSetBattlePassViewedReq.java new file mode 100644 index 000000000..4b4723c77 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerSetBattlePassViewedReq.java @@ -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())); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerTakeBattlePassMissionPointReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerTakeBattlePassMissionPointReq.java index c7a7e68cd..0288f833e 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerTakeBattlePassMissionPointReq.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerTakeBattlePassMissionPointReq.java @@ -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()); } } diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerTakeBattlePassRewardReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerTakeBattlePassRewardReq.java index 6204d7489..926c08af0 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerTakeBattlePassRewardReq.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerTakeBattlePassRewardReq.java @@ -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()); } } diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketBattlePassAllDataNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketBattlePassAllDataNotify.java index dc0ce294c..01ab447b7 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketBattlePassAllDataNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketBattlePassAllDataNotify.java @@ -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 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()); } diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketBattlePassCurScheduleUpdateNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketBattlePassCurScheduleUpdateNotify.java index 78e22a05d..9e480c121 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketBattlePassCurScheduleUpdateNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketBattlePassCurScheduleUpdateNotify.java @@ -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 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()); diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketBattlePassMissionUpdateNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketBattlePassMissionUpdateNotify.java index 06714e27e..0d1404edb 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketBattlePassMissionUpdateNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketBattlePassMissionUpdateNotify.java @@ -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 missionIdList , GameSession session) { + public PacketBattlePassMissionUpdateNotify(BattlePassMission mission) { super(PacketOpcodes.BattlePassMissionUpdateNotify); - var proto - = BattlePassMissionUpdateNotifyOuterClass.BattlePassMissionUpdateNotify.newBuilder(); + var proto = BattlePassMissionUpdateNotify.newBuilder() + .addMissionList(mission.toProto()) + .build(); - Map missionMap - = GameData.getBattlePassMissionExcelConfigDataMap(); + this.setData(proto); + } + + public PacketBattlePassMissionUpdateNotify(Collection 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()); } diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketBuyBattlePassLevelRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketBuyBattlePassLevelRsp.java new file mode 100644 index 000000000..2649c7e4c --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketBuyBattlePassLevelRsp.java @@ -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); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketSetBattlePassViewedRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketSetBattlePassViewedRsp.java new file mode 100644 index 000000000..08abf4951 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketSetBattlePassViewedRsp.java @@ -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); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketTakeBattlePassRewardRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketTakeBattlePassRewardRsp.java index f38e3fa68..a8ac3a466 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketTakeBattlePassRewardRsp.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketTakeBattlePassRewardRsp.java @@ -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 takeOptionList , GameSession session) { + public PacketTakeBattlePassRewardRsp(List takeOptionList, List rewardItems) { super(PacketOpcodes.TakeBattlePassRewardRsp); - var proto - = TakeBattlePassRewardRspOuterClass.TakeBattlePassRewardRsp.newBuilder(); - - Map excelConfigDataMap = GameData.getBattlePassRewardExcelConfigDataMap(); - Map rewardDataMap = GameData.getRewardDataMap(); - - List 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); } } From a16bc721018f07081ac4607859015a405729b64c Mon Sep 17 00:00:00 2001 From: Melledy <52122272+Melledy@users.noreply.github.com> Date: Fri, 24 Jun 2022 07:34:24 -0700 Subject: [PATCH 02/23] Implement weekly points for the battle pass --- .../java/emu/grasscutter/GameConstants.java | 1 + .../data/excels/BattlePassMissionData.java | 4 +++ .../game/battlepass/BattlePassManager.java | 30 +++++++++++++++---- 3 files changed, 29 insertions(+), 6 deletions(-) diff --git a/src/main/java/emu/grasscutter/GameConstants.java b/src/main/java/emu/grasscutter/GameConstants.java index 088ad8555..ef723f81c 100644 --- a/src/main/java/emu/grasscutter/GameConstants.java +++ b/src/main/java/emu/grasscutter/GameConstants.java @@ -20,6 +20,7 @@ public final class GameConstants { 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. diff --git a/src/main/java/emu/grasscutter/data/excels/BattlePassMissionData.java b/src/main/java/emu/grasscutter/data/excels/BattlePassMissionData.java index d51c2e6eb..4c4913245 100644 --- a/src/main/java/emu/grasscutter/data/excels/BattlePassMissionData.java +++ b/src/main/java/emu/grasscutter/data/excels/BattlePassMissionData.java @@ -35,6 +35,10 @@ public class BattlePassMissionData extends GameResource { 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 || diff --git a/src/main/java/emu/grasscutter/game/battlepass/BattlePassManager.java b/src/main/java/emu/grasscutter/game/battlepass/BattlePassManager.java index adee62b89..fab94557d 100644 --- a/src/main/java/emu/grasscutter/game/battlepass/BattlePassManager.java +++ b/src/main/java/emu/grasscutter/game/battlepass/BattlePassManager.java @@ -19,9 +19,11 @@ 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; @@ -37,6 +39,7 @@ public class BattlePassManager { @Indexed private int ownerUid; private int point; + private int cyclePoints; // Weekly maximum cap private int level; private boolean viewed; @@ -69,7 +72,11 @@ public class BattlePassManager { return this.point; } - public int getLevel() { + public int getCyclePoints() { + return cyclePoints; + } + + public int getLevel() { return this.level; } @@ -86,14 +93,25 @@ public class BattlePassManager { } public void addPoints(int point){ - this.addPointsDirectly(point); + this.addPointsDirectly(point, false); player.getSession().send(new PacketBattlePassCurScheduleUpdateNotify(player.getSession().getPlayer())); this.save(); } - public void addPointsDirectly(int point) { - this.point += point; + public void addPointsDirectly(int point, boolean isWeekly) { + int amount = point; + + 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 = (int) Math.floor((float) this.point / GameConstants.BATTLE_PASS_POINT_PER_LEVEL); @@ -159,7 +177,7 @@ public class BattlePassManager { // Take reward if (mission.getStatus() == BattlePassMissionStatus.MISSION_STATUS_FINISHED) { - this.addPointsDirectly(mission.getData().getAddPoint()); + this.addPointsDirectly(mission.getData().getAddPoint(), mission.getData().getRefreshType() == BattlePassMissionRefreshType.BATTLE_PASS_MISSION_REFRESH_CYCLE_CROSS_SCHEDULE); mission.setStatus(BattlePassMissionStatus.MISSION_STATUS_POINT_TAKEN); updatedMissions.add(mission); @@ -265,7 +283,7 @@ public class BattlePassManager { .setEndTime(2059483200) .setIsViewed(this.isViewed()) .setUnlockStatus(this.isPaid() ? BattlePassUnlockStatus.BATTLE_PASS_UNLOCK_STATUS_PAID : BattlePassUnlockStatus.BATTLE_PASS_UNLOCK_STATUS_FREE) - .setCurCyclePoints(0) + .setCurCyclePoints(this.getCyclePoints()) .setCurCycle(BattlePassCycle.newBuilder().setBeginTime(0).setEndTime(2059483200).setCycleIdx(3)); for (BattlePassReward reward : getTakenRewards().values()) { From cdc23a0647e41ad9dea6619e31b79b0181f4b445 Mon Sep 17 00:00:00 2001 From: Melledy <52122272+Melledy@users.noreply.github.com> Date: Fri, 24 Jun 2022 07:43:43 -0700 Subject: [PATCH 03/23] Random cleanups in the gameserver and player --- .../game/battlepass/BattlePassManager.java | 2 +- .../emu/grasscutter/game/player/Player.java | 4 - .../grasscutter/server/game/GameServer.java | 78 ++++--------------- 3 files changed, 14 insertions(+), 70 deletions(-) diff --git a/src/main/java/emu/grasscutter/game/battlepass/BattlePassManager.java b/src/main/java/emu/grasscutter/game/battlepass/BattlePassManager.java index fab94557d..5a8a8390c 100644 --- a/src/main/java/emu/grasscutter/game/battlepass/BattlePassManager.java +++ b/src/main/java/emu/grasscutter/game/battlepass/BattlePassManager.java @@ -177,7 +177,7 @@ public class BattlePassManager { // Take reward if (mission.getStatus() == BattlePassMissionStatus.MISSION_STATUS_FINISHED) { - this.addPointsDirectly(mission.getData().getAddPoint(), mission.getData().getRefreshType() == BattlePassMissionRefreshType.BATTLE_PASS_MISSION_REFRESH_CYCLE_CROSS_SCHEDULE); + this.addPointsDirectly(mission.getData().getAddPoint(), mission.getData().isCycleRefresh()); mission.setStatus(BattlePassMissionStatus.MISSION_STATUS_POINT_TAKEN); updatedMissions.add(mission); diff --git a/src/main/java/emu/grasscutter/game/player/Player.java b/src/main/java/emu/grasscutter/game/player/Player.java index f883d27a6..932148c9d 100644 --- a/src/main/java/emu/grasscutter/game/player/Player.java +++ b/src/main/java/emu/grasscutter/game/player/Player.java @@ -1483,10 +1483,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()); diff --git a/src/main/java/emu/grasscutter/server/game/GameServer.java b/src/main/java/emu/grasscutter/server/game/GameServer.java index 4ae5c8549..2eeb00fca 100644 --- a/src/main/java/emu/grasscutter/server/game/GameServer.java +++ b/src/main/java/emu/grasscutter/server/game/GameServer.java @@ -52,20 +52,19 @@ public final class GameServer extends KcpServer { private final Set 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 BattlePassMissionManager battlePassMissionManager; - - 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()); @@ -133,57 +132,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 BattlePassMissionManager getBattlePassMissionManager() { - return battlePassMissionManager; - } - - public TaskMap getTaskMap() { - return this.taskMap; - } private static InetSocketAddress getAdapterInetSocketAddress(){ InetSocketAddress inetSocketAddress; From 088b3dbbcc5f8d66db9f217f79e67c879f2f6e2d Mon Sep 17 00:00:00 2001 From: AnimeGitB Date: Sat, 25 Jun 2022 00:58:09 +0930 Subject: [PATCH 04/23] Add initializers to GameServer --- src/main/java/emu/grasscutter/server/game/GameServer.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main/java/emu/grasscutter/server/game/GameServer.java b/src/main/java/emu/grasscutter/server/game/GameServer.java index 2eeb00fca..21ad5907f 100644 --- a/src/main/java/emu/grasscutter/server/game/GameServer.java +++ b/src/main/java/emu/grasscutter/server/game/GameServer.java @@ -9,12 +9,15 @@ 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; @@ -82,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(); From 2cdfea1fb2ffdc2573dc0e4e57e485eac782f2f4 Mon Sep 17 00:00:00 2001 From: GanyusLeftHorn <1244229+GanyusLeftHorn@users.noreply.github.com> Date: Fri, 24 Jun 2022 11:21:16 -0700 Subject: [PATCH 05/23] Move worldlevel for World to Player::setWorldLevel --- src/main/java/emu/grasscutter/game/player/Player.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/emu/grasscutter/game/player/Player.java b/src/main/java/emu/grasscutter/game/player/Player.java index 932148c9d..b2d13879c 100644 --- a/src/main/java/emu/grasscutter/game/player/Player.java +++ b/src/main/java/emu/grasscutter/game/player/Player.java @@ -447,6 +447,8 @@ public class Player { } public void setWorldLevel(int level) { + this.getWorld().setWorldLevel(newWorldLevel); + this.setProperty(PlayerProperty.PROP_PLAYER_WORLD_LEVEL, level); this.sendPacket(new PacketPlayerPropNotify(this, PlayerProperty.PROP_PLAYER_WORLD_LEVEL)); @@ -545,7 +547,6 @@ public class Player { 0; if (newWorldLevel != currentWorldLevel) { - this.getWorld().setWorldLevel(newWorldLevel); this.setWorldLevel(newWorldLevel); } } From 3ec2c4e21e38260a67f18f31c68035c1df731acd Mon Sep 17 00:00:00 2001 From: GanyusLeftHorn <1244229+GanyusLeftHorn@users.noreply.github.com> Date: Sat, 25 Jun 2022 00:06:37 -0700 Subject: [PATCH 06/23] Reset forge points every day to enable crafting of enhancement ores. --- .../grasscutter/data/excels/ForgeData.java | 6 ++- .../game/managers/forging/ForgingManager.java | 20 ++++++-- .../emu/grasscutter/game/player/Player.java | 51 ++++++++++++++++++- 3 files changed, 70 insertions(+), 7 deletions(-) diff --git a/src/main/java/emu/grasscutter/data/excels/ForgeData.java b/src/main/java/emu/grasscutter/data/excels/ForgeData.java index 6b6199197..554c9f446 100644 --- a/src/main/java/emu/grasscutter/data/excels/ForgeData.java +++ b/src/main/java/emu/grasscutter/data/excels/ForgeData.java @@ -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 materialItems; @Override @@ -58,6 +58,10 @@ public class ForgeData extends GameResource { return priority; } + public int getForgePoint() { + return forgePoint; + } + public List getMaterialItems() { return materialItems; } diff --git a/src/main/java/emu/grasscutter/game/managers/forging/ForgingManager.java b/src/main/java/emu/grasscutter/game/managers/forging/ForgingManager.java index 4edb6bf33..28257a685 100644 --- a/src/main/java/emu/grasscutter/game/managers/forging/ForgingManager.java +++ b/src/main/java/emu/grasscutter/game/managers/forging/ForgingManager.java @@ -5,19 +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.game.props.WatcherTriggerType; -import emu.grasscutter.net.proto.ForgeStartReqOuterClass; import emu.grasscutter.net.proto.ForgeQueueDataOuterClass.ForgeQueueData; import emu.grasscutter.net.proto.ForgeQueueManipulateReqOuterClass.ForgeQueueManipulateReq; import emu.grasscutter.net.proto.ForgeQueueManipulateTypeOuterClass.ForgeQueueManipulateType; @@ -148,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 material = new ArrayList<>(forgeData.getMaterialItems()); @@ -159,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()); @@ -256,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); diff --git a/src/main/java/emu/grasscutter/game/player/Player.java b/src/main/java/emu/grasscutter/game/player/Player.java index b2d13879c..0d4b4c096 100644 --- a/src/main/java/emu/grasscutter/game/player/Player.java +++ b/src/main/java/emu/grasscutter/game/player/Player.java @@ -76,6 +76,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; @@ -181,6 +184,7 @@ public class Player { private long springLastUsed; private HashMap mapMarks; private int nextResinRefresh; + private int lastDailyReset; @Deprecated @SuppressWarnings({"rawtypes", "unchecked"}) // Morphia only! @@ -447,14 +451,27 @@ public class Player { } public void setWorldLevel(int level) { - this.getWorld().setWorldLevel(newWorldLevel); - + this.getWorld().setWorldLevel(level); + this.setProperty(PlayerProperty.PROP_PLAYER_WORLD_LEVEL, level); this.sendPacket(new PacketPlayerPropNotify(this, PlayerProperty.PROP_PLAYER_WORLD_LEVEL)); this.updateProfile(); } + public int getForgePoints() { + return this.getProperty(PlayerProperty.PROP_PLAYER_FORGE_POINT); + } + + public void setForgePoints(int value) { + if (value == this.getForgePoints()) { + return; + } + + this.setProperty(PlayerProperty.PROP_PLAYER_FORGE_POINT, value); + this.sendPacket(new PacketPlayerPropNotify(this, PlayerProperty.PROP_PLAYER_FORGE_POINT)); + } + public int getPrimogems() { return this.getProperty(PlayerProperty.PROP_PLAYER_HCOIN); } @@ -796,6 +813,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; } @@ -1316,6 +1341,10 @@ public class Player { this.resetSendPlayerLocTime(); } } + + // Handle daily reset. + this.doDailyReset(); + // Expedition var timeNow = Utils.getCurrentSeconds(); var needNotify = false; @@ -1340,8 +1369,26 @@ 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; + } + + Grasscutter.getLogger().info("Executing daily reset logic ..."); + + // 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; From 1ba415c66a707f5a0ead8c15f1f37b48fbfbd673 Mon Sep 17 00:00:00 2001 From: GanyusLeftHorn <1244229+GanyusLeftHorn@users.noreply.github.com> Date: Sat, 25 Jun 2022 00:09:09 -0700 Subject: [PATCH 07/23] Remove logging. --- src/main/java/emu/grasscutter/game/player/Player.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/emu/grasscutter/game/player/Player.java b/src/main/java/emu/grasscutter/game/player/Player.java index 0d4b4c096..91606a5b4 100644 --- a/src/main/java/emu/grasscutter/game/player/Player.java +++ b/src/main/java/emu/grasscutter/game/player/Player.java @@ -1380,8 +1380,6 @@ public class Player { return; } - Grasscutter.getLogger().info("Executing daily reset logic ..."); - // We should - now execute all the resetting logic we need. // Reset forge points. this.setForgePoints(300_000); From a3b4a11863eaa5464fad7535a9266c08419c79fd Mon Sep 17 00:00:00 2001 From: AnimeGitB Date: Sat, 25 Jun 2022 16:12:23 +0930 Subject: [PATCH 08/23] Add permission to BanCommand, rework its targeting --- .../command/commands/BanCommand.java | 75 +++++++------------ .../command/commands/UnBanCommand.java | 44 +++-------- src/main/resources/languages/en-US.json | 6 +- src/main/resources/languages/fr-FR.json | 6 +- src/main/resources/languages/pl-PL.json | 13 ++++ src/main/resources/languages/ru-RU.json | 6 +- src/main/resources/languages/zh-CN.json | 6 +- src/main/resources/languages/zh-TW.json | 6 +- 8 files changed, 57 insertions(+), 105 deletions(-) diff --git a/src/main/java/emu/grasscutter/command/commands/BanCommand.java b/src/main/java/emu/grasscutter/command/commands/BanCommand.java index da3615b7d..6b4dd9766 100644 --- a/src/main/java/emu/grasscutter/command/commands/BanCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/BanCommand.java @@ -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 [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 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"); } } } \ No newline at end of file diff --git a/src/main/java/emu/grasscutter/command/commands/UnBanCommand.java b/src/main/java/emu/grasscutter/command/commands/UnBanCommand.java index 0b3dc7d8d..3dafa4efc 100644 --- a/src/main/java/emu/grasscutter/command/commands/UnBanCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/UnBanCommand.java @@ -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 ", + 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 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"); } } } \ No newline at end of file diff --git a/src/main/resources/languages/en-US.json b/src/main/resources/languages/en-US.json index a5a50ec8e..4c6b2e377 100644 --- a/src/main/resources/languages/en-US.json +++ b/src/main/resources/languages/en-US.json @@ -390,18 +390,16 @@ "description": "Changes weather ID and climate type. Weather IDs can be found in WeatherExcelConfigData.json.\nClimate types: sunny, cloudy, rain, thunderstorm, snow, mist" }, "ban": { - "command_usage": "Usage: ban [timestamp] [reason]", + "command_usage": "Usage: ban <@playerId> [timestamp] [reason]", "success": "Successful.", "failure": "Failed, player not found.", "invalid_time": "Unable to parse timestamp.", - "invalid_player_id": "Unable to parse player ID.", "description": "Ban a player" }, "unban": { - "command_usage": "Usage: unban ", + "command_usage": "Usage: unban <@playerId>", "success": "Successful.", "failure": "Failed, player not found.", - "invalid_player_id": "Unable to parse player ID.", "description": "Unban a player" } }, diff --git a/src/main/resources/languages/fr-FR.json b/src/main/resources/languages/fr-FR.json index 955bbe1b4..12f4c0072 100644 --- a/src/main/resources/languages/fr-FR.json +++ b/src/main/resources/languages/fr-FR.json @@ -395,15 +395,13 @@ "success": "Succès.", "failure": "Échec, joueur introuvable.", "invalid_time": "Impossible d'analyser le timestamp.", - "invalid_player_id": "Impossible d'analyser le player ID.", - "command_usage": "Usage: ban [timestamp] [raison]" + "command_usage": "Usage: ban <@playerId> [timestamp] [raison]" }, "unban": { "description": "Retire le bannissement d'un joueur", "success": "Succès.", "failure": "Échec, joueur introuvable.", - "invalid_player_id": "Imossible d'analyser player ID.", - "command_usage": "Usage: unban " + "command_usage": "Usage: unban <@playerId>" } }, "gacha": { diff --git a/src/main/resources/languages/pl-PL.json b/src/main/resources/languages/pl-PL.json index e2fcfbae8..b90755dcf 100644 --- a/src/main/resources/languages/pl-PL.json +++ b/src/main/resources/languages/pl-PL.json @@ -306,6 +306,19 @@ "unlocktower": { "success": "odblokować gotowe", "description": "Odblokuj głęboką spiralę" + }, + "ban": { + "command_usage": "Usage: ban <@playerId> [timestamp] [reason]", + "success": "Successful.", + "failure": "Failed, player not found.", + "invalid_time": "Unable to parse timestamp.", + "description": "Ban a player" + }, + "unban": { + "command_usage": "Usage: unban <@playerId>", + "success": "Successful.", + "failure": "Failed, player not found.", + "description": "Unban a player" } }, "gacha": { diff --git a/src/main/resources/languages/ru-RU.json b/src/main/resources/languages/ru-RU.json index eea174c89..f099396f2 100644 --- a/src/main/resources/languages/ru-RU.json +++ b/src/main/resources/languages/ru-RU.json @@ -395,15 +395,13 @@ "success": "Успех.", "failure": "Неудача, игрок не найден.", "invalid_time": "Не удалось определить промежуток времени.", - "invalid_player_id": "Не удалось определить ID игрока.", - "command_usage": "Применение: ban [промежуток_времени] [причина]" + "command_usage": "Применение: ban <@Id игрока> [промежуток_времени] [причина]" }, "unban": { "description": "Разбанивает игрока", "success": "Успех.", "failure": "Неудача, игрок не найден.", - "invalid_player_id": "Не удалось определить ID игрока.", - "command_usage": "Применение: unban " + "command_usage": "Применение: unban <@Id_игрока>" } }, "gacha": { diff --git a/src/main/resources/languages/zh-CN.json b/src/main/resources/languages/zh-CN.json index 608bd7c7f..2bfb93f56 100644 --- a/src/main/resources/languages/zh-CN.json +++ b/src/main/resources/languages/zh-CN.json @@ -390,18 +390,16 @@ "description": "更改天气ID和气候类型。天气ID可以在 WeatherExcelConfigData.json 中找到。\n气候类型:sunny(晴天), cloudy(多云), rain(雨), thunderstorm(雷雨), snow(雪), mist(雾)" }, "ban": { - "command_usage": "用法:ban <玩家ID> [时间] [原因]", + "command_usage": "用法:ban <@玩家ID> [时间] [原因]", "success": "成功封禁玩家。", "failure": "封禁玩家失败,因为玩家不存在。", "invalid_time": "无法解析时间戳。", - "invalid_player_id": "无法解析玩家ID。", "description": "封禁玩家" }, "unban": { - "command_usage": "用法:unban <玩家ID>", + "command_usage": "用法:unban <@玩家ID>", "success": "成功取消玩家的封禁。", "failure": "取消玩家的封禁失败,因为玩家不存在。", - "invalid_player_id": "无法解析玩家ID。", "description": "取消玩家的封禁" } }, diff --git a/src/main/resources/languages/zh-TW.json b/src/main/resources/languages/zh-TW.json index f834d1669..7b86c09b9 100644 --- a/src/main/resources/languages/zh-TW.json +++ b/src/main/resources/languages/zh-TW.json @@ -397,15 +397,13 @@ "success": "停權成功。", "failure": "停權失敗,玩家帳號不存在。", "invalid_time": "無效的時間戳。", - "invalid_player_id": "無效的玩家ID。", - "command_usage": "用法:ban [timestamp] [reason]" + "command_usage": "用法:ban <@playerId> [timestamp] [reason]" }, "unban": { "description": "撤銷停權指定玩家。", "success": "撤銷停權成功。", "failure": "撤銷停權失敗,玩家帳號不存在。", - "invalid_player_id": "無效的玩家ID。", - "command_usage": "用法:unban " + "command_usage": "用法:unban <@playerId>" } }, "gacha": { From f00db7b7b256fed3b68e3ca7addc03c29965a5c7 Mon Sep 17 00:00:00 2001 From: AnimeGitB Date: Sat, 25 Jun 2022 16:29:30 +0930 Subject: [PATCH 09/23] Allow offline Player objects to return their Account --- src/main/java/emu/grasscutter/game/player/Player.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/emu/grasscutter/game/player/Player.java b/src/main/java/emu/grasscutter/game/player/Player.java index 91606a5b4..945d5a3dc 100644 --- a/src/main/java/emu/grasscutter/game/player/Player.java +++ b/src/main/java/emu/grasscutter/game/player/Player.java @@ -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; @@ -295,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) { From bcc20c2d0311025bb6cd6f92efa93a5909fd0720 Mon Sep 17 00:00:00 2001 From: AnimeGitB Date: Sat, 25 Jun 2022 17:08:07 +0930 Subject: [PATCH 10/23] Allow PermissionCommand to target offline players again --- .../emu/grasscutter/command/commands/PermissionCommand.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/emu/grasscutter/command/commands/PermissionCommand.java b/src/main/java/emu/grasscutter/command/commands/PermissionCommand.java index fe945ce2c..96c8c33e2 100644 --- a/src/main/java/emu/grasscutter/command/commands/PermissionCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/PermissionCommand.java @@ -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 ", permission = "permission", description = "commands.permission.description") +@Command(label = "permission", usage = "permission ", permission = "permission", description = "commands.permission.description", targetRequirement = TargetRequirement.PLAYER) public final class PermissionCommand implements CommandHandler { @Override From bb3720110576833b72cc03fa588eb1749c02e049 Mon Sep 17 00:00:00 2001 From: AnimeGitB Date: Sat, 25 Jun 2022 01:18:41 +0930 Subject: [PATCH 11/23] Small putItem refactor --- .../grasscutter/game/inventory/Inventory.java | 182 +++++++++--------- 1 file changed, 94 insertions(+), 88 deletions(-) diff --git a/src/main/java/emu/grasscutter/game/inventory/Inventory.java b/src/main/java/emu/grasscutter/game/inventory/Inventory.java index 13f201c01..2f412fbad 100644 --- a/src/main/java/emu/grasscutter/game/inventory/Inventory.java +++ b/src/main/java/emu/grasscutter/game/inventory/Inventory.java @@ -174,80 +174,86 @@ public class Inventory implements Iterable { 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) { @@ -258,36 +264,36 @@ public class Inventory implements Iterable { 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(); From 6fd1ce813c1491d6f1d7f4d8306d806070dceaef Mon Sep 17 00:00:00 2001 From: AnimeGitB Date: Sat, 25 Jun 2022 01:36:17 +0930 Subject: [PATCH 12/23] Remove Drop, ChangeScene, Restart, Broadcast commands --- .../emu/grasscutter/command/CommandMap.java | 144 +++++++++++------- .../command/commands/BroadcastCommand.java | 30 ---- .../command/commands/ChangeSceneCommand.java | 39 ----- .../command/commands/DropCommand.java | 63 -------- .../command/commands/KickCommand.java | 8 +- .../command/commands/RestartCommand.java | 21 --- .../command/commands/SendMessageCommand.java | 13 +- .../command/commands/SpawnCommand.java | 2 +- .../command/commands/TeleportCommand.java | 2 +- src/main/resources/languages/en-US.json | 21 +-- src/main/resources/languages/fr-FR.json | 21 +-- src/main/resources/languages/pl-PL.json | 15 +- src/main/resources/languages/zh-CN.json | 21 +-- src/main/resources/languages/zh-TW.json | 21 +-- 14 files changed, 108 insertions(+), 313 deletions(-) delete mode 100644 src/main/java/emu/grasscutter/command/commands/BroadcastCommand.java delete mode 100644 src/main/java/emu/grasscutter/command/commands/ChangeSceneCommand.java delete mode 100644 src/main/java/emu/grasscutter/command/commands/DropCommand.java delete mode 100644 src/main/java/emu/grasscutter/command/commands/RestartCommand.java diff --git a/src/main/java/emu/grasscutter/command/CommandMap.java b/src/main/java/emu/grasscutter/command/CommandMap.java index b5159261a..bc15ec9ab 100644 --- a/src/main/java/emu/grasscutter/command/CommandMap.java +++ b/src/main/java/emu/grasscutter/command/CommandMap.java @@ -109,6 +109,79 @@ public final class CommandMap { return this.commands.get(label); } + private Player getTargetPlayer(String playerId, Player player, Player targetPlayer, List 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. diff --git a/src/main/java/emu/grasscutter/command/commands/BroadcastCommand.java b/src/main/java/emu/grasscutter/command/commands/BroadcastCommand.java deleted file mode 100644 index 163db3891..000000000 --- a/src/main/java/emu/grasscutter/command/commands/BroadcastCommand.java +++ /dev/null @@ -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 ", 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 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")); - } -} diff --git a/src/main/java/emu/grasscutter/command/commands/ChangeSceneCommand.java b/src/main/java/emu/grasscutter/command/commands/ChangeSceneCommand.java deleted file mode 100644 index 3d41205cc..000000000 --- a/src/main/java/emu/grasscutter/command/commands/ChangeSceneCommand.java +++ /dev/null @@ -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 ", 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 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")); - } - } -} diff --git a/src/main/java/emu/grasscutter/command/commands/DropCommand.java b/src/main/java/emu/grasscutter/command/commands/DropCommand.java deleted file mode 100644 index ce1a1761b..000000000 --- a/src/main/java/emu/grasscutter/command/commands/DropCommand.java +++ /dev/null @@ -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 [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 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))); - } -} diff --git a/src/main/java/emu/grasscutter/command/commands/KickCommand.java b/src/main/java/emu/grasscutter/command/commands/KickCommand.java index 546fe905d..3a36e2f97 100644 --- a/src/main/java/emu/grasscutter/command/commands/KickCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/KickCommand.java @@ -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 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())); } diff --git a/src/main/java/emu/grasscutter/command/commands/RestartCommand.java b/src/main/java/emu/grasscutter/command/commands/RestartCommand.java deleted file mode 100644 index 3f73b6f3f..000000000 --- a/src/main/java/emu/grasscutter/command/commands/RestartCommand.java +++ /dev/null @@ -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 args) { - if (sender == null) { - return; - } - sender.getSession().close(); - } -} diff --git a/src/main/java/emu/grasscutter/command/commands/SendMessageCommand.java b/src/main/java/emu/grasscutter/command/commands/SendMessageCommand.java index d8051f7c7..0df6141bc 100644 --- a/src/main/java/emu/grasscutter/command/commands/SendMessageCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/SendMessageCommand.java @@ -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 ", - 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")); } } diff --git a/src/main/java/emu/grasscutter/command/commands/SpawnCommand.java b/src/main/java/emu/grasscutter/command/commands/SpawnCommand.java index 7bf320383..311df8b56 100644 --- a/src/main/java/emu/grasscutter/command/commands/SpawnCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/SpawnCommand.java @@ -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 [amount] [level(monster only)] [ (monster only, optional)]", permission = "server.spawn", permissionTargeted = "server.spawn.others", description = "commands.spawn.description") +@Command(label = "spawn", usage = "spawn [amount] [level(monster only)] [ (monster only, optional)]", aliases = {"drop"}, permission = "server.spawn", permissionTargeted = "server.spawn.others", description = "commands.spawn.description") public final class SpawnCommand implements CommandHandler { @Override diff --git a/src/main/java/emu/grasscutter/command/commands/TeleportCommand.java b/src/main/java/emu/grasscutter/command/commands/TeleportCommand.java index 4f0e14040..0bc920ac7 100644 --- a/src/main/java/emu/grasscutter/command/commands/TeleportCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/TeleportCommand.java @@ -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), diff --git a/src/main/resources/languages/en-US.json b/src/main/resources/languages/en-US.json index 4c6b2e377..dc31e518a 100644 --- a/src/main/resources/languages/en-US.json +++ b/src/main/resources/languages/en-US.json @@ -109,18 +109,6 @@ "no_account": "Account not found.", "description": "Modify user accounts" }, - "broadcast": { - "command_usage": "Usage: broadcast ", - "message_sent": "Message sent.", - "description": "Sends a message to all the players" - }, - "changescene": { - "usage": "Usage: changescene ", - "already_in_scene": "You are already in that scene.", - "success": "Changed to scene %s.", - "exists_error": "The specified scene does not exist.", - "description": "Changes your scene" - }, "clear": { "command_usage": "Usage: clear ", "weapons": "Cleared weapons for %s.", @@ -137,11 +125,6 @@ "success": "Summoned %s to %s's world.", "description": "Forces someone to join the world of others. If no one is targeted, it sends you into co-op mode anyway." }, - "drop": { - "command_usage": "Usage: drop [amount]", - "success": "Dropped %s of %s.", - "description": "Drops an item near you" - }, "enter_dungeon": { "usage": "Usage: enterdungeon ", "changed": "Changed to dungeon %s.", @@ -259,9 +242,6 @@ "success": "Reset complete.", "description": "Reset target player's shop refresh time" }, - "restart": { - "description": "Restarts the current session" - }, "sendMail": { "usage": "Usage: sendmail [templateID]", "user_not_exist": "The user with an ID of '%s' does not exist.", @@ -371,6 +351,7 @@ "usage": "Usage: tp [@] [sceneID]", "specify_player_id": "You must specify a player ID.", "invalid_position": "Invalid position.", + "exists_error": "The specified scene does not exist.", "success": "Teleported %s to %s, %s, %s in scene %s.", "description": "Change the player's position" }, diff --git a/src/main/resources/languages/fr-FR.json b/src/main/resources/languages/fr-FR.json index 12f4c0072..d9b212325 100644 --- a/src/main/resources/languages/fr-FR.json +++ b/src/main/resources/languages/fr-FR.json @@ -110,18 +110,6 @@ "command_usage": "Utilisation: account [UID]", "description": "Modifie les comptes utilisateurs" }, - "broadcast": { - "command_usage": "Usage: broadcast ", - "message_sent": "Message envoyé.", - "description": "Envoie un message a tous les joueurs" - }, - "changescene": { - "usage": "Usage: changescene ", - "already_in_scene": "Vous êtes déjà dans cette scène.", - "success": "Nouvelle scène : %s.", - "exists_error": "La scène spécifié n'existe pas.", - "description": "Change votre scène" - }, "clear": { "command_usage": "Usage: clear ", "weapons": "Les armes de %s ont été supprimés.", @@ -138,11 +126,6 @@ "success": "%s est apparu dans de monde de %s.", "description": "Force quelqu'un a rejoindre le monde d'un autre. Si personne n'est ciblé, vous envoie quand même en mode multijoueur." }, - "drop": { - "command_usage": "Usage: drop [quantité]", - "success": " %s %s ont été jetés.", - "description": "Jette un objet près de vous" - }, "enter_dungeon": { "usage": "Usage: enterdungeon ", "changed": "Entré dans le donjon %s.", @@ -260,9 +243,6 @@ "success": "Réinitialisation terminée.", "description": "Réinitialise le temps d'actualisation de la boutique du joueur spécifié" }, - "restart": { - "description": "Redémare la session actuelle" - }, "sendMail": { "usage": "Usage: sendmail [templateID]", "user_not_exist": "L'utilisateur avec l'identifiant '%s' n'existe pas.", @@ -372,6 +352,7 @@ "usage": "Utilisation: tp [@] [sceneID]", "specify_player_id": "Vous devez spécifier un ID d'utilisateur.", "invalid_position": "Position invalide.", + "exists_error": "La scène spécifié n'existe pas.", "success": "%s a été téléporté à %s, %s, %s dans la scène %s.", "description": "Change la position du joueur" }, diff --git a/src/main/resources/languages/pl-PL.json b/src/main/resources/languages/pl-PL.json index b90755dcf..9848d5abc 100644 --- a/src/main/resources/languages/pl-PL.json +++ b/src/main/resources/languages/pl-PL.json @@ -103,16 +103,6 @@ "no_account": "Nie znaleziono konta.", "command_usage": "Użycie: account [uid]" }, - "broadcast": { - "command_usage": "Użycie: broadcast ", - "message_sent": "Wiadomość wysłana." - }, - "changescene": { - "usage": "Użycie: changescene ", - "already_in_scene": "Już jesteś na tej scenie.", - "success": "Zmieniono scene na %s.", - "exists_error": "Ta scena nie istenieje." - }, "clear": { "command_usage": "Użycie: clear ", "weapons": "Wyczyszczono bronie dla %s.", @@ -286,6 +276,7 @@ "usage": "Użycie: /tp [@] [ID sceny]", "specify_player_id": "Musisz określić ID gracza.", "invalid_position": "Błędna pozycja.", + "exists_error": "Ta scena nie istenieje.", "success": "Przeteleportowano %s do %s, %s, %s w scenie %s" }, "weather": { @@ -294,10 +285,6 @@ "success": "Set weather ID to %s with climate type %s.", "status": "Current weather ID is %s with climate type %s." }, - "drop": { - "command_usage": "Użycie: drop [ilość]", - "success": "Wyrzucono %s of %s." - }, "help": { "usage": "Użycie: ", "aliases": "Aliasy: ", diff --git a/src/main/resources/languages/zh-CN.json b/src/main/resources/languages/zh-CN.json index 2bfb93f56..eed31bf0c 100644 --- a/src/main/resources/languages/zh-CN.json +++ b/src/main/resources/languages/zh-CN.json @@ -109,18 +109,6 @@ "no_account": "账号不存在。", "description": "创建或删除账号" }, - "broadcast": { - "command_usage": "用法:broadcast <消息>", - "message_sent": "公告已发送。", - "description": "向所有玩家发送公告" - }, - "changescene": { - "usage": "用法:changescene <场景ID>", - "already_in_scene": "你已经在这个场景中了。", - "success": "已切换至场景 %s。", - "exists_error": "场景不存在。", - "description": "切换指定场景" - }, "clear": { "command_usage": "用法:clear \nall: 所有, wp: 武器, art: 圣遗物, mat: 材料", "weapons": "已清除 %s 的武器。", @@ -137,11 +125,6 @@ "success": "已强制传送 %s 到 %s 的世界。", "description": "强制传送指定玩家到他人的世界。如果没有指定玩家,则会使你进入多人游戏状态" }, - "drop": { - "command_usage": "用法:drop <物品ID|物品名称> [数量]", - "success": "已丢下 %s 个 %s。", - "description": "在你附近丢下物品" - }, "enter_dungeon": { "usage": "用法:enterdungeon <秘境ID>", "changed": "已进入秘境 %s。", @@ -259,9 +242,6 @@ "success": "重置完成。", "description": "重置指定玩家的商店刷新时间" }, - "restart": { - "description": "重新启动服务器" - }, "sendMail": { "usage": "用法:sendmail <用户ID|all|help> [模板ID]", "user_not_exist": "用户 '%s' 不存在。", @@ -371,6 +351,7 @@ "usage": "用法:tp [@<玩家ID>] [场景ID]", "specify_player_id": "你必须指定一个玩家ID。", "invalid_position": "无效的位置。", + "exists_error": "此场景不存在。", "success": "传送 %s 到坐标 %s, %s, %s,场景为 %s。", "description": "改变指定玩家的位置" }, diff --git a/src/main/resources/languages/zh-TW.json b/src/main/resources/languages/zh-TW.json index 7b86c09b9..a29005e32 100644 --- a/src/main/resources/languages/zh-TW.json +++ b/src/main/resources/languages/zh-TW.json @@ -108,18 +108,6 @@ "command_usage": "用法:account [uid]", "description": "建立或刪除帳號。" }, - "broadcast": { - "command_usage": "用法:broadcast ", - "message_sent": "公告已發送。", - "description": "向所有玩家發送公告。" - }, - "changescene": { - "usage": "用法:changescene ", - "already_in_scene": "你已經在這個場景中了。", - "success": "已切換至場景 %s.", - "exists_error": "此場景不存在。", - "description": "切換指定場景。" - }, "clear": { "command_usage": "用法: clear ", "weapons": "已將 %s 的武器清空。", @@ -136,11 +124,6 @@ "success": "召喚了 %s 到 %s 的世界。", "description": "強制傳送指定用戶到他人的世界。如果未指定玩家,則會將你設為多人遊戲狀態。" }, - "drop": { - "command_usage": "用法:drop [amount]", - "success": "已將 %s x %s 丟在附近。", - "description": "在你附近丟下一個物品。" - }, "enter_dungeon": { "usage": "用法:enterdungeon ", "changed": "已進入祕境 %s", @@ -263,9 +246,6 @@ "success": "重置完成。", "description": "重置所選玩家的商店刷新時間。" }, - "restart": { - "description": "重新啟動伺服器。" - }, "sendMail": { "usage": "用法:sendmail [templateId]", "user_not_exist": "ID '%s' 的使用者不存在。", @@ -374,6 +354,7 @@ "usage": "用法:tp [@] [sceneId]", "specify_player_id": "你必須指定一個玩家ID。", "invalid_position": "無效的座標。", + "exists_error": "此場景不存在。", "success": "傳送 %s 到座標 %s,%s,%s ,場景為 %s 。", "description": "將玩家的位置傳送到你所指定的座標。" }, From baafb4104c81a18886edc290b47237893af43e1a Mon Sep 17 00:00:00 2001 From: AnimeGitB Date: Sat, 25 Jun 2022 01:36:19 +0930 Subject: [PATCH 13/23] Remove GiveAll, GiveArt, GiveChar commands --- .../command/commands/GiveAllCommand.java | 184 ------ .../command/commands/GiveArtifactCommand.java | 208 ------- .../command/commands/GiveCharCommand.java | 86 --- .../command/commands/GiveCommand.java | 524 ++++++++++++++---- .../command/commands/SetStatsCommand.java | 182 ++---- .../java/emu/grasscutter/data/GameDepot.java | 19 +- .../emu/grasscutter/data/excels/ItemData.java | 229 ++------ .../emu/grasscutter/game/avatar/Avatar.java | 17 + .../grasscutter/game/inventory/GameItem.java | 270 ++++----- .../game/managers/InventoryManager.java | 10 +- .../grasscutter/game/props/FightProperty.java | 67 +++ .../packet/send/PacketGetMailItemRsp.java | 16 +- .../java/emu/grasscutter/utils/SparseSet.java | 60 ++ src/main/resources/languages/en-US.json | 35 +- src/main/resources/languages/fr-FR.json | 11 +- src/main/resources/languages/pl-PL.json | 12 +- src/main/resources/languages/zh-CN.json | 11 +- src/main/resources/languages/zh-TW.json | 11 +- 18 files changed, 766 insertions(+), 1186 deletions(-) delete mode 100644 src/main/java/emu/grasscutter/command/commands/GiveAllCommand.java delete mode 100644 src/main/java/emu/grasscutter/command/commands/GiveArtifactCommand.java delete mode 100644 src/main/java/emu/grasscutter/command/commands/GiveCharCommand.java create mode 100644 src/main/java/emu/grasscutter/utils/SparseSet.java diff --git a/src/main/java/emu/grasscutter/command/commands/GiveAllCommand.java b/src/main/java/emu/grasscutter/command/commands/GiveAllCommand.java deleted file mode 100644 index 7e74c9230..000000000 --- a/src/main/java/emu/grasscutter/command/commands/GiveAllCommand.java +++ /dev/null @@ -1,184 +0,0 @@ -package emu.grasscutter.command.commands; - -import emu.grasscutter.GameConstants; -import emu.grasscutter.Grasscutter; -import emu.grasscutter.command.Command; -import emu.grasscutter.command.CommandHandler; -import emu.grasscutter.data.GameData; -import emu.grasscutter.data.excels.AvatarData; -import emu.grasscutter.data.excels.ItemData; -import emu.grasscutter.game.avatar.Avatar; -import emu.grasscutter.game.inventory.GameItem; -import emu.grasscutter.game.inventory.ItemType; -import emu.grasscutter.game.player.Player; - -import java.util.*; - -import static emu.grasscutter.utils.Language.translate; - -@Command(label = "giveall", usage = "giveall [amount]", aliases = {"givea"}, permission = "player.giveall", permissionTargeted = "player.giveall.others", threading = true, description = "commands.giveAll.description") -public final class GiveAllCommand implements CommandHandler { - - @Override - public void execute(Player sender, Player targetPlayer, List args) { - int amount = 99999; - - switch (args.size()) { - case 0: - break; - case 1: // [amount] - try { - amount = Integer.parseInt(args.get(0)); - } catch (NumberFormatException ignored) { - CommandHandler.sendMessage(sender, translate(sender, "commands.generic.invalid.amount")); - return; - } - break; - default: // invalid - CommandHandler.sendMessage(sender, translate(sender, "commands.giveAll.usage")); - return; - } - - this.giveAllItems(targetPlayer, amount); - CommandHandler.sendMessage(sender, translate(targetPlayer, "commands.giveAll.success", targetPlayer.getNickname())); - } - - public void giveAllItems(Player player, int amount) { - CommandHandler.sendMessage(player, translate(player, "commands.giveAll.started")); - - for (AvatarData avatarData: GameData.getAvatarDataMap().values()) { - //Exclude test avatar - if (isTestAvatar(avatarData.getId())) continue; - - Avatar avatar = new Avatar(avatarData); - avatar.setLevel(90); - avatar.setPromoteLevel(6); - - // Add constellations. - int talentBase = switch (avatar.getAvatarId()) { - case 10000005 -> 70; - case 10000006 -> 40; - default -> (avatar.getAvatarId()-10000000)*10; - }; - - for(int i = 1;i <= 6;++i){ - avatar.getTalentIdList().add(talentBase + i); - } - - // Handle skill depot for traveller. - if (avatar.getAvatarId() == GameConstants.MAIN_CHARACTER_MALE) { - avatar.setSkillDepotData(GameData.getAvatarSkillDepotDataMap().get(504)); - } - else if(avatar.getAvatarId() == GameConstants.MAIN_CHARACTER_FEMALE) { - avatar.setSkillDepotData(GameData.getAvatarSkillDepotDataMap().get(704)); - } - - // This will handle stats and talents - avatar.recalcStats(); - // Don't try to add each avatar to the current team - player.addAvatar(avatar, false); - } - - //some test items - List itemList = new ArrayList<>(); - for (ItemData itemdata: GameData.getItemDataMap().values()) { - //Exclude test item - if (isTestItem(itemdata.getId())) continue; - - if (itemdata.isEquip()) { - if (itemdata.getItemType() == ItemType.ITEM_WEAPON) { - for (int i = 0; i < 5; ++i) { - GameItem item = new GameItem(itemdata); - item.setLevel(90); - item.setPromoteLevel(6); - item.setRefinement(4); - itemList.add(item); - } - } - } - else { - GameItem item = new GameItem(itemdata); - item.setCount(amount); - itemList.add(item); - } - } - int packetNum = 10; - int itemLength = itemList.size(); - int number = itemLength / packetNum; - int remainder = itemLength % packetNum; - int offset = 0; - for (int i = 0; i < packetNum; ++i) { - if (remainder > 0) { - player.getInventory().addItems(itemList.subList(i * number + offset, (i + 1) * number + offset + 1)); - --remainder; - ++offset; - } - else { - player.getInventory().addItems(itemList.subList(i * number + offset, (i + 1) * number + offset)); - } - } - } - - public boolean isTestAvatar(int avatarId) { - return avatarId < 10000002 || avatarId >= 11000000; - } - - public boolean isTestItem(int itemId) { - for (Range range: testItemRanges) { - if (range.check(itemId)) { - return true; - } - } - - return testItemsList.contains(itemId); - } - - static class Range { - private final int min, max; - - public Range(int min, int max) { - if(min > max){ - min ^= max; - max ^= min; - min ^= max; - } - - this.min = min; - this.max = max; - } - - public boolean check(int value) { - return value >= this.min && value <= this.max; - } - } - - private static final Range[] testItemRanges = new Range[] { - new Range(106, 139), - new Range(1000, 1099), - new Range(2001, 3022), - new Range(23300, 23340), - new Range(23383, 23385), - new Range(78310, 78554), - new Range(99310, 99554), - new Range(100001, 100187), - new Range(100210, 100214), - new Range(100303, 100398), - new Range(100414, 100425), - new Range(100454, 103008), - new Range(109000, 109492), - new Range(115001, 118004), - new Range(141001, 141072), - new Range(220050, 221016), - }; - private static final Integer[] testItemsIds = new Integer[] { - 210, 211, 314, 315, 317, 1005, 1007, 1105, 1107, 1201, 1202,10366, - 101212, 11411, 11506, 11507, 11508, 12505, 12506, 12508, 12509, 13503, - 13506, 14411, 14503, 14505, 14508, 15504, 15505, 15506, - 20001, 10002, 10003, 10004, 10005, 10006, 10008,100231,100232,100431, - 101689,105001,105004, 106000,106001,108000,110000 - }; - - private static final Collection testItemsList = Arrays.asList(testItemsIds); - -} - diff --git a/src/main/java/emu/grasscutter/command/commands/GiveArtifactCommand.java b/src/main/java/emu/grasscutter/command/commands/GiveArtifactCommand.java deleted file mode 100644 index eaa0635dc..000000000 --- a/src/main/java/emu/grasscutter/command/commands/GiveArtifactCommand.java +++ /dev/null @@ -1,208 +0,0 @@ -package emu.grasscutter.command.commands; - -import emu.grasscutter.Grasscutter; -import emu.grasscutter.command.Command; -import emu.grasscutter.command.CommandHandler; -import emu.grasscutter.data.GameData; -import emu.grasscutter.data.excels.ItemData; -import emu.grasscutter.game.inventory.GameItem; -import emu.grasscutter.game.inventory.ItemType; -import emu.grasscutter.game.player.Player; -import emu.grasscutter.game.props.ActionReason; -import emu.grasscutter.game.inventory.EquipType; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import static java.util.Map.entry; - -import static emu.grasscutter.utils.Language.translate; - -@Command(label = "giveart", usage = "giveart [[,]]... [level]", aliases = {"gart"}, permission = "player.giveart", permissionTargeted = "player.giveart.others", description = "commands.giveArtifact.description") -public final class GiveArtifactCommand implements CommandHandler { - private static final Map> mainPropMap = Map.ofEntries( - entry("hp", Map.ofEntries(entry(EquipType.EQUIP_BRACER, 14001))), - entry("hp%", Map.ofEntries(entry(EquipType.EQUIP_SHOES, 10980), entry(EquipType.EQUIP_RING, 50980), entry(EquipType.EQUIP_DRESS, 30980))), - entry("atk", Map.ofEntries(entry(EquipType.EQUIP_NECKLACE, 12001))), - entry("atk%", Map.ofEntries(entry(EquipType.EQUIP_SHOES, 10990), entry(EquipType.EQUIP_RING, 50990), entry(EquipType.EQUIP_DRESS, 30990))), - entry("def%", Map.ofEntries(entry(EquipType.EQUIP_SHOES, 10970), entry(EquipType.EQUIP_RING, 50970), entry(EquipType.EQUIP_DRESS, 30970))), - entry("er", Map.ofEntries(entry(EquipType.EQUIP_SHOES, 10960))), - entry("em", Map.ofEntries(entry(EquipType.EQUIP_SHOES, 10950), entry(EquipType.EQUIP_RING, 50880), entry(EquipType.EQUIP_DRESS, 30930))), - entry("hb", Map.ofEntries(entry(EquipType.EQUIP_DRESS, 30940))), - entry("cdmg", Map.ofEntries(entry(EquipType.EQUIP_DRESS, 30950))), - entry("cr", Map.ofEntries(entry(EquipType.EQUIP_DRESS, 30960))), - entry("phys%", Map.ofEntries(entry(EquipType.EQUIP_RING, 50890))), - entry("dendro%", Map.ofEntries(entry(EquipType.EQUIP_RING, 50900))), - entry("geo%", Map.ofEntries(entry(EquipType.EQUIP_RING, 50910))), - entry("anemo%", Map.ofEntries(entry(EquipType.EQUIP_RING, 50920))), - entry("hydro%", Map.ofEntries(entry(EquipType.EQUIP_RING, 50930))), - entry("cryo%", Map.ofEntries(entry(EquipType.EQUIP_RING, 50940))), - entry("electro%", Map.ofEntries(entry(EquipType.EQUIP_RING, 50950))), - entry("pyro%", Map.ofEntries(entry(EquipType.EQUIP_RING, 50960))) - ); - private static final Map appendPropMap = Map.ofEntries( - entry("hp", "0102"), - entry("hp%", "0103"), - entry("atk", "0105"), - entry("atk%", "0106"), - entry("def", "0108"), - entry("def%", "0109"), - entry("er", "0123"), - entry("em", "0124"), - entry("cr", "0120"), - entry("cdmg", "0122") - ); - - private int getAppendPropId(String substatText, ItemData itemData) { - int res; - - // If the given substat text is an integer, we just use that - // as the append prop ID. - try { - res = Integer.parseInt(substatText); - return res; - } - catch (NumberFormatException ignores) { - // No need to handle this here. We just continue with the - // possibility of the argument being a substat string. - } - - // If the argument was not an integer, we try to determine - // the append prop ID from the given text + artifact information. - // A substat string has the format `substat_tier`, with the - // `_tier` part being optional. - String[] substatArgs = substatText.split("_"); - String substatType; - int substatTier; - - if (substatArgs.length == 1) { - substatType = substatArgs[0]; - substatTier = - itemData.getRankLevel() == 1 ? 2 - : itemData.getRankLevel() == 2 ? 3 - : 4; - } - else if (substatArgs.length == 2) { - substatType = substatArgs[0]; - substatTier = Integer.parseInt(substatArgs[1]); - } - else { - throw new IllegalArgumentException(); - } - - // Check if the specified tier is legal for the artifact rarity. - if (substatTier < 1 || substatTier > 4) { - throw new IllegalArgumentException(); - } - if (itemData.getRankLevel() == 1 && substatTier > 2) { - throw new IllegalArgumentException(); - } - if (itemData.getRankLevel() == 2 && substatTier > 3) { - throw new IllegalArgumentException(); - } - - // Check if the given substat type string is a legal stat. - if (!appendPropMap.containsKey(substatType)) { - throw new IllegalArgumentException(); - } - - // Build the append prop ID. - return Integer.parseInt(Integer.toString(itemData.getRankLevel()) + appendPropMap.get(substatType) + Integer.toString(substatTier)); - } - - @Override - public void execute(Player sender, Player targetPlayer, List args) { - // Sanity check - if (args.size() < 2) { - CommandHandler.sendMessage(sender, translate(sender, "commands.giveArtifact.usage")); - return; - } - - // Get the artifact piece ID from the arguments. - int itemId; - try { - itemId = Integer.parseInt(args.remove(0)); - } catch (NumberFormatException ignored) { - CommandHandler.sendMessage(sender, translate(sender, "commands.giveArtifact.id_error")); - return; - } - - ItemData itemData = GameData.getItemDataMap().get(itemId); - if (itemData.getItemType() != ItemType.ITEM_RELIQUARY) { - CommandHandler.sendMessage(sender, translate(sender, "commands.giveArtifact.id_error")); - return; - } - - // Get the main stat from the arguments. - // If the given argument is an integer, we use that. - // If not, we check if the argument string is in the main prop map. - String mainPropIdString = args.remove(0); - int mainPropId; - - try { - mainPropId = Integer.parseInt(mainPropIdString); - } catch (NumberFormatException ignored) { - mainPropId = -1; - } - - if (mainPropMap.containsKey(mainPropIdString) && mainPropMap.get(mainPropIdString).containsKey(itemData.getEquipType())) { - mainPropId = mainPropMap.get(mainPropIdString).get(itemData.getEquipType()); - } - - if (mainPropId == -1) { - CommandHandler.sendMessage(sender, translate(sender, "commands.execution.argument_error")); - return; - } - - // Get the level from the arguments. - int level = 1; - try { - int last = Integer.parseInt(args.get(args.size()-1)); - if (last > 0 && last < 22) { // Luckily appendPropIds aren't in the range of [1,21] - level = last; - args.remove(args.size()-1); - } - } catch (NumberFormatException ignored) { // Could be a stat,times string so no need to panic - } - - // Get substats. - ArrayList appendPropIdList = new ArrayList<>(); - try { - // Every remaining argument is a substat. - args.forEach(it -> { - // The substat syntax permits specifying a number of rolls for the given - // substat. Split the string into stat and number if that is the case here. - String[] arr; - int n = 1; - if ((arr = it.split(",")).length == 2) { - it = arr[0]; - n = Integer.parseInt(arr[1]); - if (n > 200) { - n = 200; - } - } - - // Determine the substat ID. - int appendPropId = getAppendPropId(it, itemData); - - // Add the current substat. - appendPropIdList.addAll(Collections.nCopies(n, appendPropId)); - }); - } catch (Exception ignored) { - CommandHandler.sendMessage(sender, translate(sender, "commands.execution.argument_error")); - return; - } - - // Create item for the artifact. - GameItem item = new GameItem(itemData); - item.setLevel(level); - item.setMainPropId(mainPropId); - item.getAppendPropIdList().clear(); - item.getAppendPropIdList().addAll(appendPropIdList); - targetPlayer.getInventory().addItem(item, ActionReason.SubfieldDrop); - - CommandHandler.sendMessage(sender, translate(sender, "commands.giveArtifact.success", Integer.toString(itemId), Integer.toString(targetPlayer.getUid()))); - } -} - diff --git a/src/main/java/emu/grasscutter/command/commands/GiveCharCommand.java b/src/main/java/emu/grasscutter/command/commands/GiveCharCommand.java deleted file mode 100644 index 587988b0a..000000000 --- a/src/main/java/emu/grasscutter/command/commands/GiveCharCommand.java +++ /dev/null @@ -1,86 +0,0 @@ -package emu.grasscutter.command.commands; - -import emu.grasscutter.GameConstants; -import emu.grasscutter.Grasscutter; -import emu.grasscutter.command.Command; -import emu.grasscutter.command.CommandHandler; -import emu.grasscutter.data.GameData; -import emu.grasscutter.data.excels.AvatarData; -import emu.grasscutter.game.avatar.Avatar; -import emu.grasscutter.game.player.Player; - -import java.util.List; - -import static emu.grasscutter.utils.Language.translate; - -@Command(label = "givechar", usage = "givechar [level]", aliases = {"givec"}, permission = "player.givechar", permissionTargeted = "player.givechar.others", description = "commands.giveChar.description") -public final class GiveCharCommand implements CommandHandler { - - @Override - public void execute(Player sender, Player targetPlayer, List args) { - int avatarId; - int level = 1; - - switch (args.size()) { - case 2: - try { - level = Integer.parseInt(args.get(1)); - } catch (NumberFormatException ignored) { - // TODO: Parse from avatar name using GM Handbook. - CommandHandler.sendMessage(sender, translate(sender, "commands.generic.invalid.avatarLevel")); - return; - } // Cheeky fall-through to parse first argument too - case 1: - try { - avatarId = Integer.parseInt(args.get(0)); - } catch (NumberFormatException ignored) { - // TODO: Parse from avatar name using GM Handbook. - CommandHandler.sendMessage(sender, translate(sender, "commands.generic.invalid.avatarId")); - return; - } - break; - default: - CommandHandler.sendMessage(sender, translate(sender, "commands.giveChar.usage")); - return; - } - - AvatarData avatarData = GameData.getAvatarDataMap().get(avatarId); - if (avatarData == null) { - CommandHandler.sendMessage(sender, translate(sender, "commands.generic.invalid.avatarId")); - return; - } - - // Check level. - if (level > 90) { - CommandHandler.sendMessage(sender, translate(sender, "commands.generic.invalid.avatarLevel")); - return; - } - - // Calculate ascension level. - int ascension; - if (level <= 40) { - ascension = (int) Math.ceil(level / 20f) - 1; - } else { - ascension = (int) Math.ceil(level / 10f) - 3; - ascension = Math.min(ascension, 6); - } - - Avatar avatar = new Avatar(avatarId); - avatar.setLevel(level); - avatar.setPromoteLevel(ascension); - - // Handle skill depot for traveller. - if (avatar.getAvatarId() == GameConstants.MAIN_CHARACTER_MALE) { - avatar.setSkillDepotData(GameData.getAvatarSkillDepotDataMap().get(504)); - } - else if(avatar.getAvatarId() == GameConstants.MAIN_CHARACTER_FEMALE) { - avatar.setSkillDepotData(GameData.getAvatarSkillDepotDataMap().get(704)); - } - - // This will handle stats and talents - avatar.recalcStats(); - - targetPlayer.addAvatar(avatar); - CommandHandler.sendMessage(sender, translate(sender, "commands.giveChar.given", Integer.toString(avatarId), Integer.toString(level), Integer.toString(targetPlayer.getUid()))); - } -} diff --git a/src/main/java/emu/grasscutter/command/commands/GiveCommand.java b/src/main/java/emu/grasscutter/command/commands/GiveCommand.java index dbe02a1cd..70ebd69b4 100644 --- a/src/main/java/emu/grasscutter/command/commands/GiveCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/GiveCommand.java @@ -1,29 +1,37 @@ package emu.grasscutter.command.commands; +import emu.grasscutter.GameConstants; import emu.grasscutter.command.Command; import emu.grasscutter.command.CommandHandler; import emu.grasscutter.data.GameData; +import emu.grasscutter.data.GameDepot; +import emu.grasscutter.data.excels.AvatarData; import emu.grasscutter.data.excels.ItemData; +import emu.grasscutter.data.excels.ReliquaryAffixData; +import emu.grasscutter.data.excels.ReliquaryMainPropData; +import emu.grasscutter.game.avatar.Avatar; import emu.grasscutter.game.inventory.GameItem; import emu.grasscutter.game.inventory.ItemType; import emu.grasscutter.game.player.Player; import emu.grasscutter.game.props.ActionReason; +import emu.grasscutter.game.props.FightProperty; +import emu.grasscutter.utils.SparseSet; -import java.util.LinkedList; +import java.util.ArrayList; import java.util.List; + import java.util.regex.Matcher; import java.util.regex.Pattern; -import static emu.grasscutter.utils.Language.translate; - -@Command(label = "give", usage = "give [amount] [level]", aliases = { +@Command(label = "give", usage = "give [lv] [r] [x] | give [lv] [x] [mainPropId] [[,]]...", aliases = { "g", "item", "giveitem"}, permission = "player.give", permissionTargeted = "player.give.others", description = "commands.give.description") public final class GiveCommand implements CommandHandler { - Pattern lvlRegex = Pattern.compile("l(?:vl?)?(\\d+)"); // Java is a joke of a proglang that doesn't have raw string literals - Pattern refineRegex = Pattern.compile("r(\\d+)"); - Pattern amountRegex = Pattern.compile("((?<=x)\\d+|\\d+(?=x)(?!x\\d))"); + private static Pattern lvlRegex = Pattern.compile("l(?:vl?)?(\\d+)"); // Java doesn't have raw string literals :( + private static Pattern refineRegex = Pattern.compile("r(\\d+)"); + private static Pattern constellationRegex = Pattern.compile("c(\\d+)"); + private static Pattern amountRegex = Pattern.compile("((?<=x)\\d+|\\d+(?=x)(?!x\\d))"); - private int matchIntOrNeg(Pattern pattern, String arg) { + private static int matchIntOrNeg(Pattern pattern, String arg) { Matcher match = pattern.matcher(arg); if (match.find()) { return Integer.parseInt(match.group(1)); // This should be exception-safe as only \d+ can be passed to it (i.e. non-empty string of pure digits) @@ -31,27 +39,50 @@ public final class GiveCommand implements CommandHandler { return -1; } - @Override - public void execute(Player sender, Player targetPlayer, List args) { - int item; - int lvl = 1; - int amount = 1; - int refinement = 0; + private static enum GiveAllType { + NONE, + ALL, + WEAPONS, + MATS, + AVATARS + } - for (int i = args.size()-1; i>=0; i--) { // Reverse iteration as we are deleting elements + private static class GiveItemParameters { + public int id; + public int lvl = 0; + public int amount = 1; + public int refinement = 1; + public int constellation = -1; + public int mainPropId = -1; + public List appendPropIdList; + public ItemData data; + public AvatarData avatarData; + public GiveAllType giveAllType = GiveAllType.NONE; + }; + + private static GiveItemParameters parseArgs(Player sender, List args) throws IllegalArgumentException { + GiveItemParameters param = new GiveItemParameters(); + + // Extract any tagged arguments (e.g. "lv90", "x100", "r5") + for (int i = args.size() - 1; i >= 0; i--) { // Reverse iteration as we are deleting elements String arg = args.get(i).toLowerCase(); boolean deleteArg = false; int argNum; + // Note that a single argument can actually match all of these, e.g. "lv90r5x100" if ((argNum = matchIntOrNeg(lvlRegex, arg)) != -1) { - lvl = argNum; + param.lvl = argNum; deleteArg = true; } if ((argNum = matchIntOrNeg(refineRegex, arg)) != -1) { - refinement = argNum; + param.refinement = argNum; + deleteArg = true; + } + if ((argNum = matchIntOrNeg(constellationRegex, arg)) != -1) { + param.constellation = argNum; deleteArg = true; } if ((argNum = matchIntOrNeg(amountRegex, arg)) != -1) { - amount = argNum; + param.amount = argNum; deleteArg = true; } if (deleteArg) { @@ -59,112 +90,387 @@ public final class GiveCommand implements CommandHandler { } } - switch (args.size()) { - case 4: // [amount] [level] [refinement] - try { - refinement = Integer.parseInt(args.get(3)); - } catch (NumberFormatException ignored) { - CommandHandler.sendMessage(sender, translate(sender, "commands.generic.invalid.itemRefinement")); - return; - } // Fallthrough - case 3: // [amount] [level] - try { - lvl = Integer.parseInt(args.get(2)); - } catch (NumberFormatException ignored) { - CommandHandler.sendMessage(sender, translate(sender, "commands.generic.invalid.itemLevel")); - return; - } // Fallthrough - case 2: // [amount] - try { - amount = Integer.parseInt(args.get(1)); - } catch (NumberFormatException ignored) { - CommandHandler.sendMessage(sender, translate(sender, "commands.generic.invalid.amount")); - return; - } // Fallthrough - case 1: // - try { - item = Integer.parseInt(args.get(0)); - } catch (NumberFormatException ignored) { - // TODO: Parse from item name using GM Handbook. - CommandHandler.sendMessage(sender, translate(sender, "commands.generic.invalid.itemId")); - return; - } + // At this point, first remaining argument MUST be itemId/avatarId + if (args.size() < 1) { + CommandHandler.sendTranslatedMessage(sender, "commands.give.usage"); // Reachable if someone does `/give lv90` or similar + throw new IllegalArgumentException(); + } + String id = args.remove(0); + boolean isRelic = false; + + switch (id) { + case "all": + param.giveAllType = GiveAllType.ALL; break; - default: // *No args* - CommandHandler.sendMessage(sender, translate(sender, "commands.give.usage")); - return; + case "weapons": + param.giveAllType = GiveAllType.WEAPONS; + break; + case "mats": + param.giveAllType = GiveAllType.MATS; + break; + case "avatars": + param.giveAllType = GiveAllType.AVATARS; + break; + default: + try { + param.id = Integer.parseInt(id); + param.data = GameData.getItemDataMap().get(param.id); + if ((param.id > 10_000_000) && (param.id < 12_000_000)) + param.avatarData = GameData.getAvatarDataMap().get(param.id); + else if ((param.id > 1000) && (param.id < 1100)) + param.avatarData = GameData.getAvatarDataMap().get(param.id - 1000 + 10_000_000); + isRelic = ((param.data != null) && (param.data.getItemType() == ItemType.ITEM_RELIQUARY)); + } catch (NumberFormatException e) { + // TODO: Parse from item name using GM Handbook. + CommandHandler.sendTranslatedMessage(sender, "commands.generic.invalid.itemId"); + throw e; + } } - ItemData itemData = GameData.getItemDataMap().get(item); - if (itemData == null) { - CommandHandler.sendMessage(sender, translate(sender, "commands.generic.invalid.itemId")); + if (param.amount < 1) param.amount = 1; + if (param.refinement < 1) param.refinement = 1; + if (param.refinement > 5) param.refinement = 5; + if (isRelic) { + // Input 0-20 to match game, instead of 1-21 which is the real level + if (param.lvl < 0) param.lvl = 0; + if (param.lvl > 20) param.lvl = 20; + param.lvl += 1; + if (illegalRelicIds.contains(param.id)) + CommandHandler.sendTranslatedMessage(sender, "commands.give.illegal_relic"); + } else { + // Suitable for Avatars and Weapons + if (param.lvl < 1) param.lvl = 1; + if (param.lvl > 90) param.lvl = 90; + } + + if (isRelic && !args.isEmpty()) { + try { + parseRelicArgs(param, args); + } catch (IllegalArgumentException e) { + CommandHandler.sendTranslatedMessage(sender, "commands.execution.argument_error"); + CommandHandler.sendTranslatedMessage(sender, "commands.give.usage_relic"); + throw e; + } + } + + return param; + } + + @Override + public void execute(Player sender, Player targetPlayer, List args) { + if (args.size() < 1) { // *No args* + CommandHandler.sendTranslatedMessage(sender, "commands.give.usage"); return; } - if (refinement != 0) { - if (itemData.getItemType() == ItemType.ITEM_WEAPON) { - if (refinement < 1 || refinement > 5) { - CommandHandler.sendMessage(sender, translate(sender, "commands.give.refinement_must_between_1_and_5")); + try { + GiveItemParameters param = parseArgs(sender, args); + + switch (param.giveAllType) { + case ALL: + giveAll(targetPlayer, param); + CommandHandler.sendTranslatedMessage(sender, "commands.give.giveall_success"); return; - } - } else { - CommandHandler.sendMessage(sender, translate(sender, "commands.give.refinement_only_applicable_weapons")); + case WEAPONS: + giveAllWeapons(targetPlayer, param); + CommandHandler.sendTranslatedMessage(sender, "commands.give.giveall_success"); + return; + case MATS: + giveAllMats(targetPlayer, param); + CommandHandler.sendTranslatedMessage(sender, "commands.give.giveall_success"); + return; + case AVATARS: + giveAllAvatars(targetPlayer, param); + CommandHandler.sendTranslatedMessage(sender, "commands.give.giveall_success"); + return; + case NONE: + break; + } + + // Check if this is an avatar + if (param.avatarData != null) { + Avatar avatar = makeAvatar(param); + targetPlayer.addAvatar(avatar); + CommandHandler.sendTranslatedMessage(sender, "commands.give.given_avatar", Integer.toString(param.id), Integer.toString(param.lvl), Integer.toString(targetPlayer.getUid())); + return; + } + // If it's not an avatar, it needs to be a valid item + if (param.data == null) { + CommandHandler.sendTranslatedMessage(sender, "commands.generic.invalid.itemId"); + return; + } + + switch (param.data.getItemType()) { + case ITEM_WEAPON: + targetPlayer.getInventory().addItems(makeUnstackableItems(param), ActionReason.SubfieldDrop); + CommandHandler.sendTranslatedMessage(sender, "commands.give.given_with_level_and_refinement", Integer.toString(param.id), Integer.toString(param.lvl), Integer.toString(param.refinement), Integer.toString(param.amount), Integer.toString(targetPlayer.getUid())); + return; + case ITEM_RELIQUARY: + targetPlayer.getInventory().addItems(makeArtifacts(param), ActionReason.SubfieldDrop); + CommandHandler.sendTranslatedMessage(sender, "commands.give.given_level", Integer.toString(param.id), Integer.toString(param.lvl), Integer.toString(param.amount), Integer.toString(targetPlayer.getUid())); + //CommandHandler.sendTranslatedMessage(sender, "commands.giveArtifact.success", Integer.toString(param.id), Integer.toString(targetPlayer.getUid())); + return; + default: + targetPlayer.getInventory().addItem(new GameItem(param.data, param.amount), ActionReason.SubfieldDrop); + CommandHandler.sendTranslatedMessage(sender, "commands.give.given", Integer.toString(param.amount), Integer.toString(param.id), Integer.toString(targetPlayer.getUid())); + return; + } + } catch (IllegalArgumentException ignored) { return; } } - this.item(targetPlayer, itemData, amount, lvl, refinement); + private static Avatar makeAvatar(GiveItemParameters param) { + return makeAvatar(param.avatarData, param.lvl, Avatar.getMinPromoteLevel(param.lvl), 0); + } - if (!itemData.isEquip()) { - CommandHandler.sendMessage(sender, translate(sender, "commands.give.given", Integer.toString(amount), Integer.toString(item), Integer.toString(targetPlayer.getUid()))); - } else if (itemData.getItemType() == ItemType.ITEM_WEAPON) { - CommandHandler.sendMessage(sender, translate(sender, "commands.give.given_with_level_and_refinement", Integer.toString(item), Integer.toString(lvl), Integer.toString(refinement), Integer.toString(amount), Integer.toString(targetPlayer.getUid()))); - } else { - CommandHandler.sendMessage(sender, translate(sender, "commands.give.given_level", Integer.toString(item), Integer.toString(lvl), Integer.toString(amount), Integer.toString(targetPlayer.getUid()))); + private static Avatar makeAvatar(AvatarData avatarData, int level, int promoteLevel, int constellation) { + // Calculate ascension level. + Avatar avatar = new Avatar(avatarData); + avatar.setLevel(level); + avatar.setPromoteLevel(promoteLevel); + + // Add constellations. + int talentBase = switch (avatar.getAvatarId()) { + case 10000005 -> 70; + case 10000006 -> 40; + default -> (avatar.getAvatarId() - 10000000) * 10; + }; + + for (int i = 1; i <= constellation; i++) { + avatar.getTalentIdList().add(talentBase + i); + } + + // Main character needs skill depot manually added. + if (avatar.getAvatarId() == GameConstants.MAIN_CHARACTER_MALE) { + avatar.setSkillDepotData(GameData.getAvatarSkillDepotDataMap().get(504)); + } + else if(avatar.getAvatarId() == GameConstants.MAIN_CHARACTER_FEMALE) { + avatar.setSkillDepotData(GameData.getAvatarSkillDepotDataMap().get(704)); + } + + avatar.recalcStats(); + + return avatar; + } + + private static void giveAllAvatars(Player player, GiveItemParameters param) { + int promoteLevel = Avatar.getMinPromoteLevel(param.lvl); + if (param.constellation < 0) { + param.constellation = 6; + } + for (AvatarData avatarData : GameData.getAvatarDataMap().values()) { + // Exclude test avatars + int id = avatarData.getId(); + if (id < 10000002 || id >= 11000000) continue; + + // Don't try to add each avatar to the current team + player.addAvatar(makeAvatar(avatarData, param.lvl, promoteLevel, param.constellation), false); } } - private void item(Player player, ItemData itemData, int amount, int lvl, int refinement) { - if (itemData.isEquip()) { - List items = new LinkedList<>(); - for (int i = 0; i < amount; i++) { - GameItem item = new GameItem(itemData); - if (item.isEquipped()) { - // check item max level - if (item.getItemType() == ItemType.ITEM_WEAPON) { - if (lvl > 90) lvl = 90; - } else { - if (lvl > 21) lvl = 21; - } - } - item.setCount(amount); - item.setLevel(lvl); - if (lvl > 80) { - item.setPromoteLevel(6); - } else if (lvl > 70) { - item.setPromoteLevel(5); - } else if (lvl > 60) { - item.setPromoteLevel(4); - } else if (lvl > 50) { - item.setPromoteLevel(3); - } else if (lvl > 40) { - item.setPromoteLevel(2); - } else if (lvl > 20) { - item.setPromoteLevel(1); - } - if (item.getItemType() == ItemType.ITEM_WEAPON) { - if (refinement > 0) { - item.setRefinement(refinement - 1); - } else { - item.setRefinement(0); - } - } - items.add(item); + private static List makeUnstackableItems(GiveItemParameters param) { + int promoteLevel = GameItem.getMinPromoteLevel(param.lvl); + int totalExp = 0; + if (param.data.getItemType() == ItemType.ITEM_WEAPON) { + int rankLevel = param.data.getRankLevel(); + for (int i = 1; i < param.lvl; i++) + totalExp += GameData.getWeaponExpRequired(rankLevel, i); + } + + List items = new ArrayList<>(param.amount); + for (int i = 0; i < param.amount; i++) { + GameItem item = new GameItem(param.data); + item.setLevel(param.lvl); + if (item.getItemType() == ItemType.ITEM_WEAPON) { + item.setPromoteLevel(promoteLevel); + item.setTotalExp(totalExp); + item.setRefinement(param.refinement - 1); // Actual refinement data is 0..4 not 1..5 } - player.getInventory().addItems(items, ActionReason.SubfieldDrop); - } else { - GameItem item = new GameItem(itemData); - item.setCount(amount); - player.getInventory().addItem(item, ActionReason.SubfieldDrop); + items.add(item); + } + return items; + } + + private static List makeArtifacts(GiveItemParameters param) { + param.lvl = Math.min(param.lvl, param.data.getMaxLevel()); + int rank = param.data.getRankLevel(); + int totalExp = 0; + for (int i = 1; i < param.lvl; i++) + totalExp += GameData.getRelicExpRequired(rank, i); + + List items = new ArrayList<>(param.amount); + for (int i = 0; i < param.amount; i++) { + // Create item for the artifact. + GameItem item = new GameItem(param.data); + item.setLevel(param.lvl); + item.setTotalExp(totalExp); + int numAffixes = param.data.getAppendPropNum() + (param.lvl-1)/4; + if (param.mainPropId > 0) // Keep random mainProp if we didn't specify one + item.setMainPropId(param.mainPropId); + if (param.appendPropIdList != null) { + item.getAppendPropIdList().clear(); + item.getAppendPropIdList().addAll(param.appendPropIdList); + } + // If we didn't include enough substats, top them up to the appropriate level at random + item.addAppendProps(numAffixes - item.getAppendPropIdList().size()); + items.add(item); + } + return items; + } + + private static int getArtifactMainProp(ItemData itemData, FightProperty prop) throws IllegalArgumentException { + if (prop != FightProperty.FIGHT_PROP_NONE) + for (ReliquaryMainPropData data : GameDepot.getRelicMainPropList(itemData.getMainPropDepotId())) + if (data.getWeight() > 0 && data.getFightProp() == prop) + return data.getId(); + throw new IllegalArgumentException(); + } + + private static List getArtifactAffixes(ItemData itemData, FightProperty prop) throws IllegalArgumentException { + if (prop == FightProperty.FIGHT_PROP_NONE) { + throw new IllegalArgumentException(); + } + List affixes = new ArrayList<>(); + for (ReliquaryAffixData data : GameDepot.getRelicAffixList(itemData.getAppendPropDepotId())) { + if (data.getWeight() > 0 && data.getFightProp() == prop) { + affixes.add(data.getId()); + } + } + return affixes; + } + + private static int getAppendPropId(String substatText, ItemData itemData) throws IllegalArgumentException { + // If the given substat text is an integer, we just use that as the append prop ID. + try { + return Integer.parseInt(substatText); + } catch (NumberFormatException ignored) { + // If the argument was not an integer, we try to determine + // the append prop ID from the given text + artifact information. + // A substat string has the format `substat_tier`, with the + // `_tier` part being optional, defaulting to the maximum. + String[] substatArgs = substatText.split("_"); + String substatType = substatArgs[0]; + + int substatTier = 4; + if (substatArgs.length > 1) { + substatTier = Integer.parseInt(substatArgs[1]); + } + + List substats = getArtifactAffixes(itemData, FightProperty.getPropByShortName(substatType)); + + if (substats.isEmpty()) { + throw new IllegalArgumentException(); + } + + substatTier -= 1; // 1-indexed to 0-indexed + substatTier = Math.min(Math.max(0, substatTier), substats.size() - 1); + return substats.get(substatTier); + } + } + + private static void parseRelicArgs(GiveItemParameters param, List args) throws IllegalArgumentException { + // Get the main stat from the arguments. + // If the given argument is an integer, we use that. + // If not, we check if the argument string is in the main prop map. + String mainPropIdString = args.remove(0); + + try { + param.mainPropId = Integer.parseInt(mainPropIdString); + } catch (NumberFormatException ignored) { + // This can in turn throw an exception which we don't want to catch here. + param.mainPropId = getArtifactMainProp(param.data, FightProperty.getPropByShortName(mainPropIdString)); + } + + // Get substats. + param.appendPropIdList = new ArrayList<>(); + // Every remaining argument is a substat. + for (String prop : args) { + // The substat syntax permits specifying a number of rolls for the given + // substat. Split the string into stat and number if that is the case here. + String[] arr = prop.split(","); + prop = arr[0]; + int n = 1; + if (arr.length > 1) { + n = Math.min(Integer.parseInt(arr[1]), 200); + } + + // Determine the substat ID. + int appendPropId = getAppendPropId(prop, param.data); + + // Add the current substat. + for (int i = 0; i < n; i++) { + param.appendPropIdList.add(appendPropId); + } + }; + } + + private static void addItemsChunked(Player player, List items, int packetSize) { + // Send the items in multiple packets + int lastIdx = items.size() - 1; + for (int i = 0; i <= lastIdx; i += packetSize) { + player.getInventory().addItems(items.subList(i, Math.min(i + packetSize, lastIdx))); } } + + private static void giveAllMats(Player player, GiveItemParameters param) { + List itemList = new ArrayList<>(); + for (ItemData itemdata : GameData.getItemDataMap().values()) { + int id = itemdata.getId(); + if (id < 100_000) continue; // Nothing meaningful below this + if (illegalItemIds.contains(id)) continue; + if (itemdata.isEquip()) continue; + + GameItem item = new GameItem(itemdata); + item.setCount(param.amount); + itemList.add(item); + } + + addItemsChunked(player, itemList, 100); + } + + private static void giveAllWeapons(Player player, GiveItemParameters param) { + int promoteLevel = GameItem.getMinPromoteLevel(param.lvl); + int quantity = Math.min(param.amount, 5); + int refinement = param.refinement - 1; + + List itemList = new ArrayList<>(); + for (ItemData itemdata : GameData.getItemDataMap().values()) { + int id = itemdata.getId(); + if (id < 11100 || id > 16000) continue; // All extant weapons are within this range + if (illegalWeaponIds.contains(id)) continue; + if (!itemdata.isEquip()) continue; + if (itemdata.getItemType() != ItemType.ITEM_WEAPON) continue; + + for (int i = 0; i < quantity; i++) { + GameItem item = new GameItem(itemdata); + item.setLevel(param.lvl); + item.setPromoteLevel(promoteLevel); + item.setRefinement(refinement); + itemList.add(item); + } + } + + addItemsChunked(player, itemList, 100); + } + + private static void giveAll(Player player, GiveItemParameters param) { + giveAllAvatars(player, param); + giveAllMats(player, param); + giveAllWeapons(player, param); + } + + private static final SparseSet illegalWeaponIds = new SparseSet(""" + 10000-10008, 11411, 11506-11508, 12505, 12506, 12508, 12509, + 13503, 13506, 14411, 14503, 14505, 14508, 15504-15506 + """); + + private static final SparseSet illegalRelicIds = new SparseSet(""" + 20001, 23300-23340, 23383-23385, 78310-78554, 99310-99554 + """); + + private static final SparseSet illegalItemIds = new SparseSet(""" + 100086, 100087, 100100-101000, 101106-101110, 101306, 101500-104000, + 105001, 105004, 106000-107000, 107011, 108000, 109000-110000, + 115000-130000, 200200-200899, 220050, 220054 + """); } diff --git a/src/main/java/emu/grasscutter/command/commands/SetStatsCommand.java b/src/main/java/emu/grasscutter/command/commands/SetStatsCommand.java index 643b313f8..3530929ee 100644 --- a/src/main/java/emu/grasscutter/command/commands/SetStatsCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/SetStatsCommand.java @@ -4,15 +4,12 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import emu.grasscutter.Grasscutter; import emu.grasscutter.command.Command; import emu.grasscutter.command.CommandHandler; import emu.grasscutter.game.entity.EntityAvatar; import emu.grasscutter.game.player.Player; import emu.grasscutter.game.props.FightProperty; import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify; -import emu.grasscutter.utils.Language; - import static emu.grasscutter.utils.Language.translate; @Command(label = "setstats", usage = "setstats|stats ", aliases = {"stats"}, permission = "player.setstats", permissionTargeted = "player.setstats.others", description = "commands.setStats.description") @@ -20,157 +17,50 @@ public final class SetStatsCommand implements CommandHandler { static class Stat { String name; FightProperty prop; - boolean percent; - public Stat(String name, FightProperty prop, boolean percent) { + public Stat(FightProperty prop) { + this.name = prop.toString(); + this.prop = prop; + } + + public Stat(String name, FightProperty prop) { this.name = name; this.prop = prop; - this.percent = percent; } } - Map stats = new HashMap<>(); + Map stats; public SetStatsCommand() { - // Default stats - stats.put("maxhp", new Stat(FightProperty.FIGHT_PROP_MAX_HP.toString(), FightProperty.FIGHT_PROP_MAX_HP, false)); - stats.put("hp", new Stat(FightProperty.FIGHT_PROP_CUR_HP.toString(), FightProperty.FIGHT_PROP_CUR_HP, false)); - stats.put("atk", new Stat(FightProperty.FIGHT_PROP_CUR_ATTACK.toString(), FightProperty.FIGHT_PROP_CUR_ATTACK, false)); - stats.put("atkb", new Stat(FightProperty.FIGHT_PROP_BASE_ATTACK.toString(), FightProperty.FIGHT_PROP_BASE_ATTACK, false)); // This doesn't seem to get used to recalculate ATK, so it's only useful for stuff like Bennett's buff. - stats.put("def", new Stat(FightProperty.FIGHT_PROP_DEFENSE.toString(), FightProperty.FIGHT_PROP_DEFENSE, false)); - stats.put("em", new Stat(FightProperty.FIGHT_PROP_ELEMENT_MASTERY.toString(), FightProperty.FIGHT_PROP_ELEMENT_MASTERY, false)); - stats.put("er", new Stat(FightProperty.FIGHT_PROP_CHARGE_EFFICIENCY.toString(), FightProperty.FIGHT_PROP_CHARGE_EFFICIENCY, true)); - stats.put("crate", new Stat(FightProperty.FIGHT_PROP_CRITICAL.toString(), FightProperty.FIGHT_PROP_CRITICAL, true)); - stats.put("cdmg", new Stat(FightProperty.FIGHT_PROP_CRITICAL_HURT.toString(), FightProperty.FIGHT_PROP_CRITICAL_HURT, true)); - stats.put("dmg", new Stat(FightProperty.FIGHT_PROP_ADD_HURT.toString(), FightProperty.FIGHT_PROP_ADD_HURT, true)); // This seems to get reset after attacks - stats.put("eanemo", new Stat(FightProperty.FIGHT_PROP_WIND_ADD_HURT.toString(), FightProperty.FIGHT_PROP_WIND_ADD_HURT, true)); - stats.put("ecryo", new Stat(FightProperty.FIGHT_PROP_ICE_ADD_HURT.toString(), FightProperty.FIGHT_PROP_ICE_ADD_HURT, true)); - stats.put("edendro", new Stat(FightProperty.FIGHT_PROP_GRASS_ADD_HURT.toString(), FightProperty.FIGHT_PROP_GRASS_ADD_HURT, true)); - stats.put("eelectro", new Stat(FightProperty.FIGHT_PROP_ELEC_ADD_HURT.toString(), FightProperty.FIGHT_PROP_ELEC_ADD_HURT, true)); - stats.put("egeo", new Stat(FightProperty.FIGHT_PROP_ROCK_ADD_HURT.toString(), FightProperty.FIGHT_PROP_ROCK_ADD_HURT, true)); - stats.put("ehydro", new Stat(FightProperty.FIGHT_PROP_WATER_ADD_HURT.toString(), FightProperty.FIGHT_PROP_WATER_ADD_HURT, true)); - stats.put("epyro", new Stat(FightProperty.FIGHT_PROP_FIRE_ADD_HURT.toString(), FightProperty.FIGHT_PROP_FIRE_ADD_HURT, true)); - stats.put("ephys", new Stat(FightProperty.FIGHT_PROP_PHYSICAL_ADD_HURT.toString(), FightProperty.FIGHT_PROP_PHYSICAL_ADD_HURT, true)); - stats.put("resall", new Stat(FightProperty.FIGHT_PROP_SUB_HURT.toString(), FightProperty.FIGHT_PROP_SUB_HURT, true)); // This seems to get reset after attacks - stats.put("resanemo", new Stat(FightProperty.FIGHT_PROP_WIND_SUB_HURT.toString(), FightProperty.FIGHT_PROP_WIND_SUB_HURT, true)); - stats.put("rescryo", new Stat(FightProperty.FIGHT_PROP_ICE_SUB_HURT.toString(), FightProperty.FIGHT_PROP_ICE_SUB_HURT, true)); - stats.put("resdendro", new Stat(FightProperty.FIGHT_PROP_GRASS_SUB_HURT.toString(), FightProperty.FIGHT_PROP_GRASS_SUB_HURT, true)); - stats.put("reselectro", new Stat(FightProperty.FIGHT_PROP_ELEC_SUB_HURT.toString(), FightProperty.FIGHT_PROP_ELEC_SUB_HURT, true)); - stats.put("resgeo", new Stat(FightProperty.FIGHT_PROP_ROCK_SUB_HURT.toString(), FightProperty.FIGHT_PROP_ROCK_SUB_HURT, true)); - stats.put("reshydro", new Stat(FightProperty.FIGHT_PROP_WATER_SUB_HURT.toString(), FightProperty.FIGHT_PROP_WATER_SUB_HURT, true)); - stats.put("respyro", new Stat(FightProperty.FIGHT_PROP_FIRE_SUB_HURT.toString(), FightProperty.FIGHT_PROP_FIRE_SUB_HURT, true)); - stats.put("resphys", new Stat(FightProperty.FIGHT_PROP_PHYSICAL_SUB_HURT.toString(), FightProperty.FIGHT_PROP_PHYSICAL_SUB_HURT, true)); - stats.put("cdr", new Stat(FightProperty.FIGHT_PROP_SKILL_CD_MINUS_RATIO.toString(), FightProperty.FIGHT_PROP_SKILL_CD_MINUS_RATIO, true)); - stats.put("heal", new Stat(FightProperty.FIGHT_PROP_HEAL_ADD.toString(), FightProperty.FIGHT_PROP_HEAL_ADD, true)); - stats.put("heali", new Stat(FightProperty.FIGHT_PROP_HEALED_ADD.toString(), FightProperty.FIGHT_PROP_HEALED_ADD, true)); - stats.put("shield", new Stat(FightProperty.FIGHT_PROP_SHIELD_COST_MINUS_RATIO.toString(), FightProperty.FIGHT_PROP_SHIELD_COST_MINUS_RATIO, true)); - stats.put("defi", new Stat(FightProperty.FIGHT_PROP_DEFENCE_IGNORE_RATIO.toString(), FightProperty.FIGHT_PROP_DEFENCE_IGNORE_RATIO, true)); - // Compatibility aliases - stats.put("mhp", stats.get("maxhp")); - stats.put("cr", stats.get("crate")); - stats.put("cd", stats.get("cdmg")); - stats.put("edend", stats.get("edendro")); - stats.put("eelec", stats.get("eelectro")); - stats.put("ethunder", stats.get("eelectro")); - + this.stats = new HashMap<>(); + for (String key : FightProperty.getShortNames()) { + this.stats.put(key, new Stat(FightProperty.getPropByShortName(key))); + } // Full FightProperty enum that won't be advertised but can be used by devs // They have a prefix to avoid the "hp" clash - stats.put("_none", new Stat("NONE", FightProperty.FIGHT_PROP_NONE, true)); - stats.put("_base_hp", new Stat("BASE_HP", FightProperty.FIGHT_PROP_BASE_HP, false)); - stats.put("_hp", new Stat("HP", FightProperty.FIGHT_PROP_HP, false)); - stats.put("_hp_percent", new Stat("HP_PERCENT", FightProperty.FIGHT_PROP_HP_PERCENT, true)); - stats.put("_base_attack", new Stat("BASE_ATTACK", FightProperty.FIGHT_PROP_BASE_ATTACK, false)); - stats.put("_attack", new Stat("ATTACK", FightProperty.FIGHT_PROP_ATTACK, false)); - stats.put("_attack_percent", new Stat("ATTACK_PERCENT", FightProperty.FIGHT_PROP_ATTACK_PERCENT, true)); - stats.put("_base_defense", new Stat("BASE_DEFENSE", FightProperty.FIGHT_PROP_BASE_DEFENSE, false)); - stats.put("_defense", new Stat("DEFENSE", FightProperty.FIGHT_PROP_DEFENSE, false)); - stats.put("_defense_percent", new Stat("DEFENSE_PERCENT", FightProperty.FIGHT_PROP_DEFENSE_PERCENT, true)); - stats.put("_base_speed", new Stat("BASE_SPEED", FightProperty.FIGHT_PROP_BASE_SPEED, true)); - stats.put("_speed_percent", new Stat("SPEED_PERCENT", FightProperty.FIGHT_PROP_SPEED_PERCENT, true)); - stats.put("_hp_mp_percent", new Stat("HP_MP_PERCENT", FightProperty.FIGHT_PROP_HP_MP_PERCENT, true)); - stats.put("_attack_mp_percent", new Stat("ATTACK_MP_PERCENT", FightProperty.FIGHT_PROP_ATTACK_MP_PERCENT, true)); - stats.put("_critical", new Stat("CRITICAL", FightProperty.FIGHT_PROP_CRITICAL, true)); - stats.put("_anti_critical", new Stat("ANTI_CRITICAL", FightProperty.FIGHT_PROP_ANTI_CRITICAL, true)); - stats.put("_critical_hurt", new Stat("CRITICAL_HURT", FightProperty.FIGHT_PROP_CRITICAL_HURT, true)); - stats.put("_charge_efficiency", new Stat("CHARGE_EFFICIENCY", FightProperty.FIGHT_PROP_CHARGE_EFFICIENCY, true)); - stats.put("_add_hurt", new Stat("ADD_HURT", FightProperty.FIGHT_PROP_ADD_HURT, true)); - stats.put("_sub_hurt", new Stat("SUB_HURT", FightProperty.FIGHT_PROP_SUB_HURT, true)); - stats.put("_heal_add", new Stat("HEAL_ADD", FightProperty.FIGHT_PROP_HEAL_ADD, true)); - stats.put("_healed_add", new Stat("HEALED_ADD", FightProperty.FIGHT_PROP_HEALED_ADD, false)); - stats.put("_element_mastery", new Stat("ELEMENT_MASTERY", FightProperty.FIGHT_PROP_ELEMENT_MASTERY, true)); - stats.put("_physical_sub_hurt", new Stat("PHYSICAL_SUB_HURT", FightProperty.FIGHT_PROP_PHYSICAL_SUB_HURT, true)); - stats.put("_physical_add_hurt", new Stat("PHYSICAL_ADD_HURT", FightProperty.FIGHT_PROP_PHYSICAL_ADD_HURT, true)); - stats.put("_defence_ignore_ratio", new Stat("DEFENCE_IGNORE_RATIO", FightProperty.FIGHT_PROP_DEFENCE_IGNORE_RATIO, true)); - stats.put("_defence_ignore_delta", new Stat("DEFENCE_IGNORE_DELTA", FightProperty.FIGHT_PROP_DEFENCE_IGNORE_DELTA, true)); - stats.put("_fire_add_hurt", new Stat("FIRE_ADD_HURT", FightProperty.FIGHT_PROP_FIRE_ADD_HURT, true)); - stats.put("_elec_add_hurt", new Stat("ELEC_ADD_HURT", FightProperty.FIGHT_PROP_ELEC_ADD_HURT, true)); - stats.put("_water_add_hurt", new Stat("WATER_ADD_HURT", FightProperty.FIGHT_PROP_WATER_ADD_HURT, true)); - stats.put("_grass_add_hurt", new Stat("GRASS_ADD_HURT", FightProperty.FIGHT_PROP_GRASS_ADD_HURT, true)); - stats.put("_wind_add_hurt", new Stat("WIND_ADD_HURT", FightProperty.FIGHT_PROP_WIND_ADD_HURT, true)); - stats.put("_rock_add_hurt", new Stat("ROCK_ADD_HURT", FightProperty.FIGHT_PROP_ROCK_ADD_HURT, true)); - stats.put("_ice_add_hurt", new Stat("ICE_ADD_HURT", FightProperty.FIGHT_PROP_ICE_ADD_HURT, true)); - stats.put("_hit_head_add_hurt", new Stat("HIT_HEAD_ADD_HURT", FightProperty.FIGHT_PROP_HIT_HEAD_ADD_HURT, true)); - stats.put("_fire_sub_hurt", new Stat("FIRE_SUB_HURT", FightProperty.FIGHT_PROP_FIRE_SUB_HURT, true)); - stats.put("_elec_sub_hurt", new Stat("ELEC_SUB_HURT", FightProperty.FIGHT_PROP_ELEC_SUB_HURT, true)); - stats.put("_water_sub_hurt", new Stat("WATER_SUB_HURT", FightProperty.FIGHT_PROP_WATER_SUB_HURT, true)); - stats.put("_grass_sub_hurt", new Stat("GRASS_SUB_HURT", FightProperty.FIGHT_PROP_GRASS_SUB_HURT, true)); - stats.put("_wind_sub_hurt", new Stat("WIND_SUB_HURT", FightProperty.FIGHT_PROP_WIND_SUB_HURT, true)); - stats.put("_rock_sub_hurt", new Stat("ROCK_SUB_HURT", FightProperty.FIGHT_PROP_ROCK_SUB_HURT, true)); - stats.put("_ice_sub_hurt", new Stat("ICE_SUB_HURT", FightProperty.FIGHT_PROP_ICE_SUB_HURT, true)); - stats.put("_effect_hit", new Stat("EFFECT_HIT", FightProperty.FIGHT_PROP_EFFECT_HIT, true)); - stats.put("_effect_resist", new Stat("EFFECT_RESIST", FightProperty.FIGHT_PROP_EFFECT_RESIST, true)); - stats.put("_freeze_resist", new Stat("FREEZE_RESIST", FightProperty.FIGHT_PROP_FREEZE_RESIST, true)); - stats.put("_torpor_resist", new Stat("TORPOR_RESIST", FightProperty.FIGHT_PROP_TORPOR_RESIST, true)); - stats.put("_dizzy_resist", new Stat("DIZZY_RESIST", FightProperty.FIGHT_PROP_DIZZY_RESIST, true)); - stats.put("_freeze_shorten", new Stat("FREEZE_SHORTEN", FightProperty.FIGHT_PROP_FREEZE_SHORTEN, true)); - stats.put("_torpor_shorten", new Stat("TORPOR_SHORTEN", FightProperty.FIGHT_PROP_TORPOR_SHORTEN, true)); - stats.put("_dizzy_shorten", new Stat("DIZZY_SHORTEN", FightProperty.FIGHT_PROP_DIZZY_SHORTEN, true)); - stats.put("_max_fire_energy", new Stat("MAX_FIRE_ENERGY", FightProperty.FIGHT_PROP_MAX_FIRE_ENERGY, true)); - stats.put("_max_elec_energy", new Stat("MAX_ELEC_ENERGY", FightProperty.FIGHT_PROP_MAX_ELEC_ENERGY, true)); - stats.put("_max_water_energy", new Stat("MAX_WATER_ENERGY", FightProperty.FIGHT_PROP_MAX_WATER_ENERGY, true)); - stats.put("_max_grass_energy", new Stat("MAX_GRASS_ENERGY", FightProperty.FIGHT_PROP_MAX_GRASS_ENERGY, true)); - stats.put("_max_wind_energy", new Stat("MAX_WIND_ENERGY", FightProperty.FIGHT_PROP_MAX_WIND_ENERGY, true)); - stats.put("_max_ice_energy", new Stat("MAX_ICE_ENERGY", FightProperty.FIGHT_PROP_MAX_ICE_ENERGY, true)); - stats.put("_max_rock_energy", new Stat("MAX_ROCK_ENERGY", FightProperty.FIGHT_PROP_MAX_ROCK_ENERGY, true)); - stats.put("_skill_cd_minus_ratio", new Stat("SKILL_CD_MINUS_RATIO", FightProperty.FIGHT_PROP_SKILL_CD_MINUS_RATIO, true)); - stats.put("_shield_cost_minus_ratio", new Stat("SHIELD_COST_MINUS_RATIO", FightProperty.FIGHT_PROP_SHIELD_COST_MINUS_RATIO, true)); - stats.put("_cur_fire_energy", new Stat("CUR_FIRE_ENERGY", FightProperty.FIGHT_PROP_CUR_FIRE_ENERGY, false)); - stats.put("_cur_elec_energy", new Stat("CUR_ELEC_ENERGY", FightProperty.FIGHT_PROP_CUR_ELEC_ENERGY, false)); - stats.put("_cur_water_energy", new Stat("CUR_WATER_ENERGY", FightProperty.FIGHT_PROP_CUR_WATER_ENERGY, false)); - stats.put("_cur_grass_energy", new Stat("CUR_GRASS_ENERGY", FightProperty.FIGHT_PROP_CUR_GRASS_ENERGY, false)); - stats.put("_cur_wind_energy", new Stat("CUR_WIND_ENERGY", FightProperty.FIGHT_PROP_CUR_WIND_ENERGY, false)); - stats.put("_cur_ice_energy", new Stat("CUR_ICE_ENERGY", FightProperty.FIGHT_PROP_CUR_ICE_ENERGY, false)); - stats.put("_cur_rock_energy", new Stat("CUR_ROCK_ENERGY", FightProperty.FIGHT_PROP_CUR_ROCK_ENERGY, false)); - stats.put("_cur_hp", new Stat("CUR_HP", FightProperty.FIGHT_PROP_CUR_HP, false)); - stats.put("_max_hp", new Stat("MAX_HP", FightProperty.FIGHT_PROP_MAX_HP, false)); - stats.put("_cur_attack", new Stat("CUR_ATTACK", FightProperty.FIGHT_PROP_CUR_ATTACK, false)); - stats.put("_cur_defense", new Stat("CUR_DEFENSE", FightProperty.FIGHT_PROP_CUR_DEFENSE, false)); - stats.put("_cur_speed", new Stat("CUR_SPEED", FightProperty.FIGHT_PROP_CUR_SPEED, true)); - stats.put("_nonextra_attack", new Stat("NONEXTRA_ATTACK", FightProperty.FIGHT_PROP_NONEXTRA_ATTACK, true)); - stats.put("_nonextra_defense", new Stat("NONEXTRA_DEFENSE", FightProperty.FIGHT_PROP_NONEXTRA_DEFENSE, true)); - stats.put("_nonextra_critical", new Stat("NONEXTRA_CRITICAL", FightProperty.FIGHT_PROP_NONEXTRA_CRITICAL, true)); - stats.put("_nonextra_anti_critical", new Stat("NONEXTRA_ANTI_CRITICAL", FightProperty.FIGHT_PROP_NONEXTRA_ANTI_CRITICAL, true)); - stats.put("_nonextra_critical_hurt", new Stat("NONEXTRA_CRITICAL_HURT", FightProperty.FIGHT_PROP_NONEXTRA_CRITICAL_HURT, true)); - stats.put("_nonextra_charge_efficiency", new Stat("NONEXTRA_CHARGE_EFFICIENCY", FightProperty.FIGHT_PROP_NONEXTRA_CHARGE_EFFICIENCY, true)); - stats.put("_nonextra_element_mastery", new Stat("NONEXTRA_ELEMENT_MASTERY", FightProperty.FIGHT_PROP_NONEXTRA_ELEMENT_MASTERY, true)); - stats.put("_nonextra_physical_sub_hurt", new Stat("NONEXTRA_PHYSICAL_SUB_HURT", FightProperty.FIGHT_PROP_NONEXTRA_PHYSICAL_SUB_HURT, true)); - stats.put("_nonextra_fire_add_hurt", new Stat("NONEXTRA_FIRE_ADD_HURT", FightProperty.FIGHT_PROP_NONEXTRA_FIRE_ADD_HURT, true)); - stats.put("_nonextra_elec_add_hurt", new Stat("NONEXTRA_ELEC_ADD_HURT", FightProperty.FIGHT_PROP_NONEXTRA_ELEC_ADD_HURT, true)); - stats.put("_nonextra_water_add_hurt", new Stat("NONEXTRA_WATER_ADD_HURT", FightProperty.FIGHT_PROP_NONEXTRA_WATER_ADD_HURT, true)); - stats.put("_nonextra_grass_add_hurt", new Stat("NONEXTRA_GRASS_ADD_HURT", FightProperty.FIGHT_PROP_NONEXTRA_GRASS_ADD_HURT, true)); - stats.put("_nonextra_wind_add_hurt", new Stat("NONEXTRA_WIND_ADD_HURT", FightProperty.FIGHT_PROP_NONEXTRA_WIND_ADD_HURT, true)); - stats.put("_nonextra_rock_add_hurt", new Stat("NONEXTRA_ROCK_ADD_HURT", FightProperty.FIGHT_PROP_NONEXTRA_ROCK_ADD_HURT, true)); - stats.put("_nonextra_ice_add_hurt", new Stat("NONEXTRA_ICE_ADD_HURT", FightProperty.FIGHT_PROP_NONEXTRA_ICE_ADD_HURT, true)); - stats.put("_nonextra_fire_sub_hurt", new Stat("NONEXTRA_FIRE_SUB_HURT", FightProperty.FIGHT_PROP_NONEXTRA_FIRE_SUB_HURT, true)); - stats.put("_nonextra_elec_sub_hurt", new Stat("NONEXTRA_ELEC_SUB_HURT", FightProperty.FIGHT_PROP_NONEXTRA_ELEC_SUB_HURT, true)); - stats.put("_nonextra_water_sub_hurt", new Stat("NONEXTRA_WATER_SUB_HURT", FightProperty.FIGHT_PROP_NONEXTRA_WATER_SUB_HURT, true)); - stats.put("_nonextra_grass_sub_hurt", new Stat("NONEXTRA_GRASS_SUB_HURT", FightProperty.FIGHT_PROP_NONEXTRA_GRASS_SUB_HURT, true)); - stats.put("_nonextra_wind_sub_hurt", new Stat("NONEXTRA_WIND_SUB_HURT", FightProperty.FIGHT_PROP_NONEXTRA_WIND_SUB_HURT, true)); - stats.put("_nonextra_rock_sub_hurt", new Stat("NONEXTRA_ROCK_SUB_HURT", FightProperty.FIGHT_PROP_NONEXTRA_ROCK_SUB_HURT, true)); - stats.put("_nonextra_ice_sub_hurt", new Stat("NONEXTRA_ICE_SUB_HURT", FightProperty.FIGHT_PROP_NONEXTRA_ICE_SUB_HURT, true)); - stats.put("_nonextra_skill_cd_minus_ratio", new Stat("NONEXTRA_SKILL_CD_MINUS_RATIO", FightProperty.FIGHT_PROP_NONEXTRA_SKILL_CD_MINUS_RATIO, true)); - stats.put("_nonextra_shield_cost_minus_ratio", new Stat("NONEXTRA_SHIELD_COST_MINUS_RATIO", FightProperty.FIGHT_PROP_NONEXTRA_SHIELD_COST_MINUS_RATIO, true)); - stats.put("_nonextra_physical_add_hurt", new Stat("NONEXTRA_PHYSICAL_ADD_HURT", FightProperty.FIGHT_PROP_NONEXTRA_PHYSICAL_ADD_HURT, true)); + for (FightProperty prop : FightProperty.values()) { + String name = prop.toString().substring(10); // FIGHT_PROP_BASE_HP -> _BASE_HP + String key = name.toLowerCase(); // _BASE_HP -> _base_hp + name = name.substring(1); // _BASE_HP -> BASE_HP + this.stats.put(key, new Stat(name, prop)); + } + + // Compatibility aliases + this.stats.put("mhp", this.stats.get("maxhp")); + this.stats.put("hp", new Stat(FightProperty.FIGHT_PROP_CUR_HP)); // Overrides FIGHT_PROP_HP + this.stats.put("atk", new Stat(FightProperty.FIGHT_PROP_CUR_ATTACK)); // Overrides FIGHT_PROP_ATTACK + this.stats.put("atkb", new Stat(FightProperty.FIGHT_PROP_BASE_ATTACK)); // This doesn't seem to get used to recalculate ATK, so it's only useful for stuff like Bennett's buff. + this.stats.put("eanemo", this.stats.get("anemo%")); + this.stats.put("ecryo", this.stats.get("cryo%")); + this.stats.put("edendro", this.stats.get("dendro%")); + this.stats.put("edend", this.stats.get("dendro%")); + this.stats.put("eelectro", this.stats.get("electro%")); + this.stats.put("eelec", this.stats.get("electro%")); + this.stats.put("ethunder", this.stats.get("electro%")); + this.stats.put("egeo", this.stats.get("geo%")); + this.stats.put("ehydro", this.stats.get("hydro%")); + this.stats.put("epyro", this.stats.get("pyro%")); + this.stats.put("ephys", this.stats.get("phys%")); } @Override @@ -206,8 +96,8 @@ public final class SetStatsCommand implements CommandHandler { Stat stat = stats.get(statStr); entity.setFightProperty(stat.prop, value); entity.getWorld().broadcastPacket(new PacketEntityFightPropUpdateNotify(entity, stat.prop)); - if (stat.percent) { - valueStr = String.format("%.1f%%", value*100f); + if (FightProperty.isPercentage(stat.prop)) { + valueStr = String.format("%.1f%%", value * 100f); } else { valueStr = String.format("%.0f", value); } diff --git a/src/main/java/emu/grasscutter/data/GameDepot.java b/src/main/java/emu/grasscutter/data/GameDepot.java index d28b38960..0b44ac44a 100644 --- a/src/main/java/emu/grasscutter/data/GameDepot.java +++ b/src/main/java/emu/grasscutter/data/GameDepot.java @@ -20,8 +20,9 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; public class GameDepot { - private static Int2ObjectMap> relicMainPropDepot = new Int2ObjectOpenHashMap<>(); - private static Int2ObjectMap> relicAffixDepot = new Int2ObjectOpenHashMap<>(); + private static Int2ObjectMap> relicRandomMainPropDepot = new Int2ObjectOpenHashMap<>(); + private static Int2ObjectMap> relicMainPropDepot = new Int2ObjectOpenHashMap<>(); + private static Int2ObjectMap> relicAffixDepot = new Int2ObjectOpenHashMap<>(); private static Map playerAbilities = new HashMap<>(); private static Int2ObjectMap> spawnLists = new Int2ObjectOpenHashMap<>(); @@ -31,8 +32,10 @@ public class GameDepot { if (data.getWeight() <= 0 || data.getPropDepotId() <= 0) { continue; } - WeightedList list = relicMainPropDepot.computeIfAbsent(data.getPropDepotId(), k -> new WeightedList<>()); - list.add(data.getWeight(), data); + List list = relicMainPropDepot.computeIfAbsent(data.getPropDepotId(), k -> new ArrayList<>()); + list.add(data); + WeightedList weightedList = relicRandomMainPropDepot.computeIfAbsent(data.getPropDepotId(), k -> new WeightedList<>()); + weightedList.add(data.getWeight(), data); } for (ReliquaryAffixData data : GameData.getReliquaryAffixDataMap().values()) { if (data.getWeight() <= 0 || data.getDepotId() <= 0) { @@ -48,14 +51,18 @@ public class GameDepot { } public static ReliquaryMainPropData getRandomRelicMainProp(int depot) { - WeightedList depotList = relicMainPropDepot.get(depot); + WeightedList depotList = relicRandomMainPropDepot.get(depot); if (depotList == null) { return null; } return depotList.next(); } - public static List getRandomRelicAffixList(int depot) { + public static List getRelicMainPropList(int depot) { + return relicMainPropDepot.get(depot); + } + + public static List getRelicAffixList(int depot) { return relicAffixDepot.get(depot); } diff --git a/src/main/java/emu/grasscutter/data/excels/ItemData.java b/src/main/java/emu/grasscutter/data/excels/ItemData.java index c7496a67e..6dc2643d9 100644 --- a/src/main/java/emu/grasscutter/data/excels/ItemData.java +++ b/src/main/java/emu/grasscutter/data/excels/ItemData.java @@ -10,6 +10,7 @@ import emu.grasscutter.game.inventory.*; import emu.grasscutter.game.props.FightProperty; import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.ints.IntSet; +import lombok.Getter; @ResourceType(name = {"MaterialExcelConfigData.json", "WeaponExcelConfigData.json", @@ -19,23 +20,23 @@ import it.unimi.dsi.fastutil.ints.IntSet; public class ItemData extends GameResource { private int id; - private int stackLimit = 1; - private int maxUseCount; - private int rankLevel; - private String effectName; - private int[] satiationParams; - private int rank; - private int weight; - private int gadgetId; + @Getter private int stackLimit = 1; + @Getter private int maxUseCount; + @Getter private int rankLevel; + @Getter private String effectName; + @Getter private int[] satiationParams; + @Getter private int rank; + @Getter private int weight; + @Getter private int gadgetId; - private int[] destroyReturnMaterial; - private int[] destroyReturnMaterialCount; + @Getter private int[] destroyReturnMaterial; + @Getter private int[] destroyReturnMaterialCount; - private List itemUse; + @Getter private List itemUse; // Food - private String foodQuality; - private String useTarget; + @Getter private String foodQuality; + @Getter private String useTarget; private String[] iseParam; // String enums @@ -45,42 +46,42 @@ public class ItemData extends GameResource { private String effectType; private String destroyRule; - // Relic - private int mainPropDepotId; - private int appendPropDepotId; - private int appendPropNum; - private int setId; - private int[] addPropLevels; - private int baseConvExp; - private int maxLevel; - - // Weapon - private int weaponPromoteId; - private int weaponBaseExp; - private int storyId; - private int avatarPromoteId; - private int awakenMaterial; - private int[] awakenCosts; - private int[] skillAffix; - private WeaponProperty[] weaponProp; - - // Hash - private String icon; - private long nameTextMapHash; - - // Post load + // Post load enum forms of above private transient MaterialType materialEnumType; private transient ItemType itemEnumType; private transient EquipType equipEnumType; - private IntSet addPropLevelSet; + // Relic + @Getter private int mainPropDepotId; + @Getter private int appendPropDepotId; + @Getter private int appendPropNum; + @Getter private int setId; + private int[] addPropLevels; + @Getter private int baseConvExp; + @Getter private int maxLevel; + + // Weapon + @Getter private int weaponPromoteId; + @Getter private int weaponBaseExp; + @Getter private int storyId; + @Getter private int avatarPromoteId; + @Getter private int awakenMaterial; + @Getter private int[] awakenCosts; + @Getter private int[] skillAffix; + private WeaponProperty[] weaponProp; + + // Hash + @Getter private String icon; + @Getter private long nameTextMapHash; + + @Getter private IntSet addPropLevelSet; // Furniture - private int comfort; - private List furnType; - private List furnitureGadgetID; + @Getter private int comfort; + @Getter private List furnType; + @Getter private List furnitureGadgetID; @SerializedName("JFDLJGDFIGL") - private int roomSceneId; + @Getter private int roomSceneId; @Override public int getId(){ @@ -91,137 +92,17 @@ public class ItemData extends GameResource { return this.materialType; } - public int getStackLimit(){ - return this.stackLimit; - } - - public int getMaxUseCount(){ - return this.maxUseCount; - } - - public String getUseTarget(){ - return this.useTarget; - } - public String[] getUseParam(){ return this.iseParam; } - public int getRankLevel(){ - return this.rankLevel; - } - - public String getFoodQuality(){ - return this.foodQuality; - } - - public String getEffectName(){ - return this.effectName; - } - - public int[] getSatiationParams(){ - return this.satiationParams; - } - - public int[] getDestroyReturnMaterial(){ - return this.destroyReturnMaterial; - } - - public int[] getDestroyReturnMaterialCount(){ - return this.destroyReturnMaterialCount; - } - - public List getItemUse() { - return itemUse; - } - - public long getNameTextMapHash(){ - return this.nameTextMapHash; - } - - public String getIcon(){ - return this.icon; - } - public String getItemTypeString(){ return this.itemType; } - - public int getRank(){ - return this.rank; - } - - public int getGadgetId() { - return gadgetId; - } - - public int getBaseConvExp() { - return baseConvExp; - } - - public int getMainPropDepotId() { - return mainPropDepotId; - } - - public int getAppendPropDepotId() { - return appendPropDepotId; - } - - public int getAppendPropNum() { - return appendPropNum; - } - - public int getSetId() { - return setId; - } - - public int getWeaponPromoteId() { - return weaponPromoteId; - } - - public int getWeaponBaseExp() { - return weaponBaseExp; - } - - public int getAwakenMaterial() { - return awakenMaterial; - } - - public int[] getAwakenCosts() { - return awakenCosts; - } - - public IntSet getAddPropLevelSet() { - return addPropLevelSet; - } - - public int[] getSkillAffix() { - return skillAffix; - } public WeaponProperty[] getWeaponProperties() { return weaponProp; } - - public int getMaxLevel() { - return maxLevel; - } - - public int getComfort() { - return comfort; - } - - public List getFurnType() { - return furnType; - } - - public List getFurnitureGadgetID() { - return furnitureGadgetID; - } - - public int getRoomSceneId() { - return roomSceneId; - } public ItemType getItemType() { return this.itemEnumType; @@ -274,26 +155,10 @@ public class ItemData extends GameResource { } public static class WeaponProperty { - private FightProperty fightProp; - private String propType; - private float initValue; - private String type; - - public String getPropType(){ - return this.propType; - } - - public float getInitValue(){ - return this.initValue; - } - - public String getType(){ - return this.type; - } - - public FightProperty getFightProp() { - return fightProp; - } + @Getter private FightProperty fightProp; + @Getter private String propType; + @Getter private float initValue; + @Getter private String type; public void onLoad() { this.fightProp = FightProperty.getPropByName(propType); diff --git a/src/main/java/emu/grasscutter/game/avatar/Avatar.java b/src/main/java/emu/grasscutter/game/avatar/Avatar.java index 588052c42..92dd421ba 100644 --- a/src/main/java/emu/grasscutter/game/avatar/Avatar.java +++ b/src/main/java/emu/grasscutter/game/avatar/Avatar.java @@ -242,6 +242,23 @@ public class Avatar { this.promoteLevel = promoteLevel; } + static public int getMinPromoteLevel(int level) { + if (level > 80) { + return 6; + } else if (level > 70) { + return 5; + } else if (level > 60) { + return 4; + } else if (level > 50) { + return 3; + } else if (level > 40) { + return 2; + } else if (level > 20) { + return 1; + } + return 0; + } + public Int2ObjectMap getEquips() { return equips; } diff --git a/src/main/java/emu/grasscutter/game/inventory/GameItem.java b/src/main/java/emu/grasscutter/game/inventory/GameItem.java index 9aae598ff..6d5da5f34 100644 --- a/src/main/java/emu/grasscutter/game/inventory/GameItem.java +++ b/src/main/java/emu/grasscutter/game/inventory/GameItem.java @@ -33,34 +33,36 @@ import emu.grasscutter.net.proto.SceneReliquaryInfoOuterClass.SceneReliquaryInfo import emu.grasscutter.net.proto.SceneWeaponInfoOuterClass.SceneWeaponInfo; import emu.grasscutter.net.proto.WeaponOuterClass.Weapon; import emu.grasscutter.utils.WeightedList; +import lombok.Getter; +import lombok.Setter; @Entity(value = "items", useDiscriminator = false) public class GameItem { @Id private ObjectId id; @Indexed private int ownerId; - private int itemId; - private int count; + @Getter @Setter private int itemId; + @Getter @Setter private int count; - @Transient private long guid; // Player unique id - @Transient private ItemData itemData; + @Transient @Getter private long guid; // Player unique id + @Transient @Getter @Setter private ItemData itemData; // Equips - private int level; - private int exp; - private int totalExp; - private int promoteLevel; - private boolean locked; + @Getter @Setter private int level; + @Getter @Setter private int exp; + @Getter @Setter private int totalExp; + @Getter @Setter private int promoteLevel; + @Getter @Setter private boolean locked; // Weapon - private List affixes; - private int refinement = 0; + @Getter private List affixes; + @Getter @Setter private int refinement = 0; // Relic - private int mainPropId; - private List appendPropIdList; + @Getter @Setter private int mainPropId; + @Getter private List appendPropIdList; - private int equipCharacter; - @Transient private int weaponEntityId; + @Getter @Setter private int equipCharacter; + @Transient @Getter @Setter private int weaponEntityId; public GameItem() { // Morphia only @@ -82,42 +84,37 @@ public class GameItem { this.itemId = data.getId(); this.itemData = data; - if (data.getItemType() == ItemType.ITEM_VIRTUAL) { + switch (data.getItemType()) { + case ITEM_VIRTUAL: this.count = count; - } else { - this.count = Math.min(count, data.getStackLimit()); + break; + case ITEM_WEAPON: + this.count = 1; + this.level = Math.max(this.count, 1); // ?????????????????? + this.affixes = new ArrayList<>(2); + if (data.getSkillAffix() != null) { + for (int skillAffix : data.getSkillAffix()) { + if (skillAffix > 0) { + this.affixes.add(skillAffix); + } + } + } + break; + case ITEM_RELIQUARY: + this.count = 1; + this.level = 1; + this.appendPropIdList = new ArrayList<>(); + // Create main property + ReliquaryMainPropData mainPropData = GameDepot.getRandomRelicMainProp(data.getMainPropDepotId()); + if (mainPropData != null) { + this.mainPropId = mainPropData.getId(); + } + // Create extra stats + this.addAppendProps(data.getAppendPropNum()); + break; + default: + this.count = Math.min(count, data.getStackLimit()); } - - // Equip data - if (getItemType() == ItemType.ITEM_WEAPON) { - this.level = Math.max(this.count, 1); - this.affixes = new ArrayList<>(2); - if (getItemData().getSkillAffix() != null) { - for (int skillAffix : getItemData().getSkillAffix()) { - if (skillAffix > 0) { - this.affixes.add(skillAffix); - } - } - } - } else if (getItemType() == ItemType.ITEM_RELIQUARY) { - this.level = 1; - this.appendPropIdList = new ArrayList<>(); - // Create main property - ReliquaryMainPropData mainPropData = GameDepot.getRandomRelicMainProp(getItemData().getMainPropDepotId()); - if (mainPropData != null) { - this.mainPropId = mainPropData.getId(); - } - // Create extra stats - if (getItemData().getAppendPropNum() > 0) { - for (int i = 0; i < getItemData().getAppendPropNum(); i++) { - this.addAppendProp(); - } - } - } - } - - public ObjectId getObjectId() { - return id; } public int getOwnerId() { @@ -128,162 +125,88 @@ public class GameItem { this.ownerId = player.getUid(); this.guid = player.getNextGameGuid(); } - public int getItemId() { - return itemId; - } - public void setItemId(int itemId) { - this.itemId = itemId; - } - - public long getGuid() { - return guid; + public ObjectId getObjectId() { + return id; } public ItemType getItemType() { return this.itemData.getItemType(); } - public ItemData getItemData() { - return itemData; - } - - public void setItemData(ItemData materialData) { - this.itemData = materialData; - } - - public int getCount() { - return count; - } - - public void setCount(int count) { - this.count = count; - } - - public int getLevel() { - return level; - } - - public void setLevel(int level) { - this.level = level; - } - - public int getExp() { - return exp; - } - - public void setExp(int exp) { - this.exp = exp; - } - - public int getTotalExp() { - return totalExp; - } - - public void setTotalExp(int totalExp) { - this.totalExp = totalExp; - } - - public int getPromoteLevel() { - return promoteLevel; - } - - public void setPromoteLevel(int promoteLevel) { - this.promoteLevel = promoteLevel; + public static int getMinPromoteLevel(int level) { + if (level > 80) { + return 6; + } else if (level > 70) { + return 5; + } else if (level > 60) { + return 4; + } else if (level > 50) { + return 3; + } else if (level > 40) { + return 2; + } else if (level > 20) { + return 1; + } + return 0; } public int getEquipSlot() { return this.getItemData().getEquipType().getValue(); } - - public int getEquipCharacter() { - return equipCharacter; - } - - public void setEquipCharacter(int equipCharacter) { - this.equipCharacter = equipCharacter; - } public boolean isEquipped() { return this.getEquipCharacter() > 0; } - - public boolean isLocked() { - return locked; - } - - public void setLocked(boolean locked) { - this.locked = locked; - } public boolean isDestroyable() { return !this.isLocked() && !this.isEquipped(); } - - public int getWeaponEntityId() { - return weaponEntityId; - } - - public void setWeaponEntityId(int weaponEntityId) { - this.weaponEntityId = weaponEntityId; - } - - public List getAffixes() { - return affixes; - } - - public int getRefinement() { - return refinement; - } - - public void setRefinement(int refinement) { - this.refinement = refinement; - } - - public int getMainPropId() { - return mainPropId; - } - - public void setMainPropId(int mainPropId) { - this.mainPropId = mainPropId; - } - - public List getAppendPropIdList() { - return appendPropIdList; - } public void addAppendProp() { - if (this.getAppendPropIdList() == null) { + if (this.appendPropIdList == null) { this.appendPropIdList = new ArrayList<>(); } - if (this.getAppendPropIdList().size() < 4) { - addNewAppendProp(); + if (this.appendPropIdList.size() < 4) { + this.addNewAppendProp(); } else { - upgradeRandomAppendProp(); + this.upgradeRandomAppendProp(); } } + public void addAppendProps(int quantity) { + int num = Math.max(quantity, 0); + for (int i = 0; i < num; i++) { + this.addAppendProp(); + } + } + + private Set getAppendFightProperties() { + Set props = new HashSet<>(); + // Previously this would check no more than the first four affixes, however custom artifacts may not respect this order. + for (int appendPropId : this.appendPropIdList) { + ReliquaryAffixData affixData = GameData.getReliquaryAffixDataMap().get(appendPropId); + if (affixData != null) { + props.add(affixData.getFightProp()); + } + } + return props; + } + private void addNewAppendProp() { - List affixList = GameDepot.getRandomRelicAffixList(getItemData().getAppendPropDepotId()); + List affixList = GameDepot.getRelicAffixList(this.itemData.getAppendPropDepotId()); if (affixList == null) { return; } // Build blacklist - Dont add same stat as main/sub stat - Set blacklist = new HashSet<>(); - ReliquaryMainPropData mainPropData = GameData.getReliquaryMainPropDataMap().get(this.getMainPropId()); + Set blacklist = this.getAppendFightProperties(); + ReliquaryMainPropData mainPropData = GameData.getReliquaryMainPropDataMap().get(this.mainPropId); if (mainPropData != null) { blacklist.add(mainPropData.getFightProp()); } - int len = Math.min(4, this.getAppendPropIdList().size()); - for (int i = 0; i < len; i++) { - ReliquaryAffixData affixData = GameData.getReliquaryAffixDataMap().get((int) this.getAppendPropIdList().get(i)); - if (affixData != null) { - blacklist.add(affixData.getFightProp()); - } - } // Build random list WeightedList randomList = new WeightedList<>(); @@ -299,25 +222,18 @@ public class GameItem { // Add random stat ReliquaryAffixData affixData = randomList.next(); - this.getAppendPropIdList().add(affixData.getId()); + this.appendPropIdList.add(affixData.getId()); } private void upgradeRandomAppendProp() { - List affixList = GameDepot.getRandomRelicAffixList(getItemData().getAppendPropDepotId()); + List affixList = GameDepot.getRelicAffixList(this.itemData.getAppendPropDepotId()); if (affixList == null) { return; } // Build whitelist - Set whitelist = new HashSet<>(); - int len = Math.min(4, this.getAppendPropIdList().size()); - for (int i = 0; i < len; i++) { - ReliquaryAffixData affixData = GameData.getReliquaryAffixDataMap().get((int) this.getAppendPropIdList().get(i)); - if (affixData != null) { - whitelist.add(affixData.getFightProp()); - } - } + Set whitelist = this.getAppendFightProperties(); // Build random list WeightedList randomList = new WeightedList<>(); @@ -329,7 +245,7 @@ public class GameItem { // Add random stat ReliquaryAffixData affixData = randomList.next(); - this.getAppendPropIdList().add(affixData.getId()); + this.appendPropIdList.add(affixData.getId()); } @PostLoad diff --git a/src/main/java/emu/grasscutter/game/managers/InventoryManager.java b/src/main/java/emu/grasscutter/game/managers/InventoryManager.java index 361f54382..47fc2d8c1 100644 --- a/src/main/java/emu/grasscutter/game/managers/InventoryManager.java +++ b/src/main/java/emu/grasscutter/game/managers/InventoryManager.java @@ -147,7 +147,7 @@ public class InventoryManager { int totalExp = relic.getTotalExp(); int reqExp = GameData.getRelicExpRequired(relic.getItemData().getRankLevel(), level); int upgrades = 0; - List oldAppendPropIdList = relic.getAppendPropIdList(); + List oldAppendPropIdList = new ArrayList<>(relic.getAppendPropIdList()); while (expGain > 0 && reqExp > 0 && level < relic.getItemData().getMaxLevel()) { // Do calculations @@ -169,13 +169,7 @@ public class InventoryManager { } } - if (upgrades > 0) { - oldAppendPropIdList = new ArrayList<>(relic.getAppendPropIdList()); - while (upgrades > 0) { - relic.addAppendProp(); - upgrades -= 1; - } - } + relic.addAppendProps(upgrades); // Save relic.setLevel(level); diff --git a/src/main/java/emu/grasscutter/game/props/FightProperty.java b/src/main/java/emu/grasscutter/game/props/FightProperty.java index e36c432f5..a51ecdbad 100644 --- a/src/main/java/emu/grasscutter/game/props/FightProperty.java +++ b/src/main/java/emu/grasscutter/game/props/FightProperty.java @@ -1,7 +1,13 @@ package emu.grasscutter.game.props; import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.Set; + +import static java.util.Map.entry; + +import java.util.Arrays; import java.util.stream.Stream; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; @@ -133,4 +139,65 @@ public enum FightProperty { public static FightProperty getPropByName(String name) { return stringMap.getOrDefault(name, FIGHT_PROP_NONE); } + + public static FightProperty getPropByShortName(String name) { + return shortNameMap.getOrDefault(name, FIGHT_PROP_NONE); + } + + public static Set getShortNames() { + return shortNameMap.keySet(); + } + + // This was originally for relic properties so some names might not be applicable for e.g. setstats + private static final Map shortNameMap = Map.ofEntries( + // Normal relic stats + entry("hp", FIGHT_PROP_HP), + entry("atk", FIGHT_PROP_ATTACK), + entry("def", FIGHT_PROP_DEFENSE), + entry("hp%", FIGHT_PROP_HP_PERCENT), + entry("atk%", FIGHT_PROP_ATTACK_PERCENT), + entry("def%", FIGHT_PROP_DEFENSE_PERCENT), + entry("em", FIGHT_PROP_ELEMENT_MASTERY), + entry("er", FIGHT_PROP_CHARGE_EFFICIENCY), + entry("hb", FIGHT_PROP_HEAL_ADD), + entry("heal", FIGHT_PROP_HEAL_ADD), + entry("cd", FIGHT_PROP_CRITICAL_HURT), + entry("cdmg", FIGHT_PROP_CRITICAL_HURT), + entry("cr", FIGHT_PROP_CRITICAL), + entry("crate", FIGHT_PROP_CRITICAL), + entry("phys%", FIGHT_PROP_PHYSICAL_ADD_HURT), + entry("dendro%", FIGHT_PROP_GRASS_ADD_HURT), + entry("geo%", FIGHT_PROP_ROCK_ADD_HURT), + entry("anemo%", FIGHT_PROP_WIND_ADD_HURT), + entry("hydro%", FIGHT_PROP_WATER_ADD_HURT), + entry("cryo%", FIGHT_PROP_ICE_ADD_HURT), + entry("electro%", FIGHT_PROP_ELEC_ADD_HURT), + entry("pyro%", FIGHT_PROP_FIRE_ADD_HURT), + // Other stats + entry("maxhp", FIGHT_PROP_MAX_HP), + entry("dmg", FIGHT_PROP_ADD_HURT), // This seems to get reset after attacks + entry("cdr", FIGHT_PROP_SKILL_CD_MINUS_RATIO), + entry("heali", FIGHT_PROP_HEALED_ADD), + entry("shield", FIGHT_PROP_SHIELD_COST_MINUS_RATIO), + entry("defi", FIGHT_PROP_DEFENCE_IGNORE_RATIO), + entry("resall", FIGHT_PROP_SUB_HURT), // This seems to get reset after attacks + entry("resanemo", FIGHT_PROP_WIND_SUB_HURT), + entry("rescryo", FIGHT_PROP_ICE_SUB_HURT), + entry("resdendro", FIGHT_PROP_GRASS_SUB_HURT), + entry("reselectro", FIGHT_PROP_ELEC_SUB_HURT), + entry("resgeo", FIGHT_PROP_ROCK_SUB_HURT), + entry("reshydro", FIGHT_PROP_WATER_SUB_HURT), + entry("respyro", FIGHT_PROP_FIRE_SUB_HURT), + entry("resphys", FIGHT_PROP_PHYSICAL_SUB_HURT) + ); + + private static final List flatProps = Arrays.asList( + FIGHT_PROP_BASE_HP, FIGHT_PROP_HP, FIGHT_PROP_BASE_ATTACK, FIGHT_PROP_ATTACK, FIGHT_PROP_BASE_DEFENSE, + FIGHT_PROP_DEFENSE, FIGHT_PROP_HEALED_ADD, FIGHT_PROP_CUR_FIRE_ENERGY, FIGHT_PROP_CUR_ELEC_ENERGY, + FIGHT_PROP_CUR_WATER_ENERGY, FIGHT_PROP_CUR_GRASS_ENERGY, FIGHT_PROP_CUR_WIND_ENERGY, FIGHT_PROP_CUR_ICE_ENERGY, + FIGHT_PROP_CUR_ROCK_ENERGY, FIGHT_PROP_CUR_HP, FIGHT_PROP_MAX_HP, FIGHT_PROP_CUR_ATTACK, FIGHT_PROP_CUR_DEFENSE); + + public static boolean isPercentage(FightProperty prop) { + return !flatProps.contains(prop); + } } diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketGetMailItemRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketGetMailItemRsp.java index cdf2e2349..0cac39e88 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketGetMailItemRsp.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketGetMailItemRsp.java @@ -30,21 +30,7 @@ public class PacketGetMailItemRsp extends BasePacket { if (!message.isAttachmentGot) {//No duplicated item for (Mail.MailItem mailItem : message.itemList) { EquipParamOuterClass.EquipParam.Builder item = EquipParamOuterClass.EquipParam.newBuilder(); - int promoteLevel = 0; - - if (mailItem.itemLevel > 80) { // 80/90 - promoteLevel = 6; - } else if (mailItem.itemLevel > 70) { // 70/80 - promoteLevel = 5; - } else if (mailItem.itemLevel > 60) { // 60/70 - promoteLevel = 4; - } else if (mailItem.itemLevel > 50) { // 50/60 - promoteLevel = 3; - } else if (mailItem.itemLevel > 40) { // 40/50 - promoteLevel = 2; - } else if (mailItem.itemLevel > 20) { // 20/40 - promoteLevel = 1; - } + int promoteLevel = GameItem.getMinPromoteLevel(mailItem.itemLevel); item.setItemId(mailItem.itemId); item.setItemNum(mailItem.itemCount); diff --git a/src/main/java/emu/grasscutter/utils/SparseSet.java b/src/main/java/emu/grasscutter/utils/SparseSet.java new file mode 100644 index 000000000..b4913b5f5 --- /dev/null +++ b/src/main/java/emu/grasscutter/utils/SparseSet.java @@ -0,0 +1,60 @@ +package emu.grasscutter.utils; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; + +public final class SparseSet { + /* + * A convenience class for constructing integer sets out of large ranges + * Designed to be fed literal strings from this project only - + * can and will throw exceptions to tell you to fix your code if you feed it garbage. :) + */ + private static class Range { + private final int min, max; + + public Range(int min, int max) { + if (min > max) { + throw new IllegalArgumentException("Range passed minimum higher than maximum - " + Integer.toString(min) + " > " + Integer.toString(max)); + } + this.min = min; + this.max = max; + } + + public boolean check(int value) { + return value >= this.min && value <= this.max; + } + } + + private final List rangeEntries; + private final Set denseEntries; + + public SparseSet(String csv) { + this.rangeEntries = new ArrayList<>(); + this.denseEntries = new TreeSet<>(); + + for (String token : csv.replace("\n", "").replace(" ", "").split(",")) { + String[] tokens = token.split("-"); + switch (tokens.length) { + case 1: + this.denseEntries.add(Integer.parseInt(tokens[0])); + break; + case 2: + this.rangeEntries.add(new Range(Integer.parseInt(tokens[0]), Integer.parseInt(tokens[1]))); + break; + default: + throw new IllegalArgumentException("Invalid token passed to SparseSet initializer - " + token + " (split length " + Integer.toString(tokens.length) + ")"); + } + } + } + + public boolean contains(int i) { + for (Range range : this.rangeEntries) { + if (range.check(i)) { + return true; + } + } + return this.denseEntries.contains(i); + } +} \ No newline at end of file diff --git a/src/main/resources/languages/en-US.json b/src/main/resources/languages/en-US.json index dc31e518a..558b61844 100644 --- a/src/main/resources/languages/en-US.json +++ b/src/main/resources/languages/en-US.json @@ -132,35 +132,16 @@ "in_dungeon_error": "You are already in that dungeon.", "description": "Enter a dungeon" }, - "giveAll": { - "usage": "Usage: giveall [player] [amount]", - "started": "Receiving all items...", - "success": "Successfully gave all items to %s.", - "invalid_amount_or_playerId": "Invalid amount or player ID.", - "description": "Gives all items" - }, - "giveArtifact": { - "usage": "Usage: giveart|gart [player] [[,]]... [level]", - "id_error": "Invalid artifact ID.", - "success": "Given %s to %s.", - "description": "Gives the player a specified artifact" - }, - "giveChar": { - "usage": "Usage: givechar [level]", - "given": "Given %s with level %s to %s.", - "invalid_avatar_id": "Invalid avatar ID.", - "invalid_avatar_level": "Invalid avatar level.", - "invalid_avatar_or_player_id": "Invalid avatar or player ID.", - "description": "Gives the player a specified character" - }, "give": { - "usage": "Usage: give [amount] [level] [refinement]", - "refinement_only_applicable_weapons": "Refinement is only applicable to weapons.", - "refinement_must_between_1_and_5": "Refinement must be between 1 and 5.", + "usage": "Usage: give [x] [lv] [r]", + "usage_relic": "Usage: give [mainPropID] [[,]]... [lv]", + "illegal_relic": "This artifactID belongs to a blacklisted range, it may not be the one you wanted.", "given": "Given %s of %s to %s.", "given_with_level_and_refinement": "Given %s with level %s, refinement %s %s times to %s.", "given_level": "Given %s with level %s %s times to %s.", - "description": "Gives an item to you or the specified player" + "given_avatar": "Given %s with level %s to %s.", + "giveall_success": "Successfully gave all items.", + "description": "Gives an item to you or the specified player. Can also give all weapons, avatars and/or materials, and can construct custom artifacts." }, "godmode": { "success": "Godmode is now %s for %s.", @@ -202,10 +183,6 @@ "success": "There are %s player(s) online:", "description": "List online players" }, - "nostamina": { - "success": "NoStamina is now %s for %s.", - "description": "Keep your endurance to the maximum." - }, "permission": { "usage": "Usage: permission ", "add": "Permission added.", diff --git a/src/main/resources/languages/fr-FR.json b/src/main/resources/languages/fr-FR.json index d9b212325..b3b5583d3 100644 --- a/src/main/resources/languages/fr-FR.json +++ b/src/main/resources/languages/fr-FR.json @@ -146,21 +146,14 @@ "success": "%s a été donné à %s.", "description": "Donne au joueur l'artéfact spécifié." }, - "giveChar": { - "usage": "Usage: givechar [niveau]", - "given": "%s avec le niveau %s a été donné à %s.", - "invalid_avatar_id": "ID de l'avatar invalide.", - "invalid_avatar_level": "Niveau de l'avatar invalide.", - "invalid_avatar_or_player_id": "ID de l'avatar ou du joueur invalide.", - "description": "Donne au joueur le personnage spécifié" - }, "give": { - "usage": "Usage: give [quantité] [niveau] [raffinement]", + "usage": "Usage: give [quantité] [niveau] [raffinement]", "refinement_only_applicable_weapons": "Le raffinement est uniquement applicable aux armes.", "refinement_must_between_1_and_5": "Le raffinement doit être compris entre 1 et 5.", "given": "Given %s of %s to %s.", "given_with_level_and_refinement": "%s avec le niveau %s, raffinement %s %s fois à %s.", "given_level": "%s avec le niveau %s %s fois à %s.", + "given_avatar": "%s avec le niveau %s a été donné à %s.", "description": "Donne un objet au joueur spécifié" }, "godmode": { diff --git a/src/main/resources/languages/pl-PL.json b/src/main/resources/languages/pl-PL.json index 9848d5abc..0d8b5a29e 100644 --- a/src/main/resources/languages/pl-PL.json +++ b/src/main/resources/languages/pl-PL.json @@ -134,20 +134,14 @@ "id_error": "Błędne ID artefaktu.", "success": "Dano %s dla %s." }, - "giveChar": { - "usage": "Użycie: givechar [ilość]", - "given": "Dano %s z poziomem %s dla %s.", - "invalid_avatar_id": "Błędne ID postaci.", - "invalid_avatar_level": "Błędny poziom postaci.", - "invalid_avatar_or_player_id": "Błędne ID postaci lub gracza." - }, "give": { - "usage": "Użycie: give [ilość] [poziom] [refinement]", + "usage": "Użycie: give [ilość] [poziom] [refinement]", "refinement_only_applicable_weapons": "Ulepszenie można zastosować tylko dla broni.", "refinement_must_between_1_and_5": "Ulepszenie musi być pomiędzy 1, a 5.", "given": "Dano %s %s dla %s.", "given_with_level_and_refinement": "Dano %s z poziomem %s, ulepszeniem %s %s razy dla %s", - "given_level": "Dano %s z poziomem %s %s razy dla %s" + "given_level": "Dano %s z poziomem %s %s razy dla %s", + "given_avatar": "Dano %s z poziomem %s dla %s." }, "godmode": { "success": "Godmode jest teraz %s dla %s." diff --git a/src/main/resources/languages/zh-CN.json b/src/main/resources/languages/zh-CN.json index eed31bf0c..a2a8018ae 100644 --- a/src/main/resources/languages/zh-CN.json +++ b/src/main/resources/languages/zh-CN.json @@ -145,21 +145,14 @@ "success": "已将 %s 给予 %s。", "description": "给予指定圣遗物" }, - "giveChar": { - "usage": "用法:givechar <玩家> <角色ID> [等级]", - "given": "已将角色 %s [等级 %s] 给与 %s。", - "invalid_avatar_id": "无效的角色ID。", - "invalid_avatar_level": "无效的角色等级。", - "invalid_avatar_or_player_id": "无效的角色ID/玩家ID。", - "description": "给予指定角色" - }, "give": { - "usage": "用法:give <玩家> <物品ID|物品名> [数量] [等级] [精炼等级]", + "usage": "用法:give <玩家> <物品ID|角色ID> [数量] [等级] [精炼等级]", "refinement_only_applicable_weapons": "只有武器可以设置精炼等级。", "refinement_must_between_1_and_5": "精炼等级必须在 1-5 之间。", "given": "已将 %s 个 %s 给予 %s。", "given_with_level_and_refinement": "已将 %s [等级 %s, 精炼 %s] %s 个给予 %s。", "given_level": "已将 %s [等级 %s] %s 个给予 %s。", + "given_avatar": "已将角色 %s [等级 %s] 给与 %s。", "description": "给予指定物品" }, "godmode": { diff --git a/src/main/resources/languages/zh-TW.json b/src/main/resources/languages/zh-TW.json index a29005e32..129bcf4e8 100644 --- a/src/main/resources/languages/zh-TW.json +++ b/src/main/resources/languages/zh-TW.json @@ -144,21 +144,14 @@ "success": "已把 %s 給予 %s。", "description": "給予指定聖遺物。" }, - "giveChar": { - "usage": "用法:givechar [level]", - "given": "已將 %s 等級 %s 給予 %s。", - "invalid_avatar_id": "無效的角色ID。", - "invalid_avatar_level": "無效的角色等級。.", - "invalid_avatar_or_player_id": "無效的角色ID/玩家ID。", - "description": "給予指定角色。" - }, "give": { - "usage": "用法:give [amount] [level] [refinement]", + "usage": "用法:give [amount] [level] [refinement]", "refinement_only_applicable_weapons": "精煉度只能施加在武器上面。", "refinement_must_between_1_and_5": "精煉度必需在 1 到 5 之間。", "given": "已經將 %s 個 %s 給予 %s。", "given_with_level_and_refinement": "已將 %s [等級%s, 精煉%s] %s個給予 %s", "given_level": "已將 %s 等級 %s %s 個給予 %s", + "given_avatar": "已將 %s 等級 %s 給予 %s。", "description": "給予指定物品。" }, "godmode": { From fc9acf42c2dcdbbd797020ab54febc6ee38a7067 Mon Sep 17 00:00:00 2001 From: AnimeGitB Date: Sat, 25 Jun 2022 02:02:53 +0930 Subject: [PATCH 14/23] PlayerProperty Refactor --- .../game/managers/ResinManager.java | 4 - .../game/managers/SotSManager.java | 3 +- .../game/managers/stamina/StaminaManager.java | 7 +- .../emu/grasscutter/game/player/Player.java | 155 ++++++------------ .../game/props/PlayerProperty.java | 81 ++++++--- .../packet/recv/HandlerSetPlayerPropReq.java | 10 +- 6 files changed, 109 insertions(+), 151 deletions(-) diff --git a/src/main/java/emu/grasscutter/game/managers/ResinManager.java b/src/main/java/emu/grasscutter/game/managers/ResinManager.java index e55809f41..76393c7f3 100644 --- a/src/main/java/emu/grasscutter/game/managers/ResinManager.java +++ b/src/main/java/emu/grasscutter/game/managers/ResinManager.java @@ -44,7 +44,6 @@ 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 @@ -70,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)); } @@ -117,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)); } @@ -141,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)); } } diff --git a/src/main/java/emu/grasscutter/game/managers/SotSManager.java b/src/main/java/emu/grasscutter/game/managers/SotSManager.java index b80058142..5f394a800 100644 --- a/src/main/java/emu/grasscutter/game/managers/SotSManager.java +++ b/src/main/java/emu/grasscutter/game/managers/SotSManager.java @@ -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; diff --git a/src/main/java/emu/grasscutter/game/managers/stamina/StaminaManager.java b/src/main/java/emu/grasscutter/game/managers/stamina/StaminaManager.java index 85c8fb7b3..3c3fd5a02 100644 --- a/src/main/java/emu/grasscutter/game/managers/stamina/StaminaManager.java +++ b/src/main/java/emu/grasscutter/game/managers/stamina/StaminaManager.java @@ -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; @@ -292,7 +290,6 @@ public class StaminaManager { // 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)); diff --git a/src/main/java/emu/grasscutter/game/player/Player.java b/src/main/java/emu/grasscutter/game/player/Player.java index 945d5a3dc..2a13012f7 100644 --- a/src/main/java/emu/grasscutter/game/player/Player.java +++ b/src/main/java/emu/grasscutter/game/player/Player.java @@ -259,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); @@ -481,7 +481,6 @@ public class Player { public void setPrimogems(int primogem) { this.setProperty(PlayerProperty.PROP_PLAYER_HCOIN, primogem); - this.sendPacket(new PacketPlayerPropNotify(this, PlayerProperty.PROP_PLAYER_HCOIN)); } public int getMora() { @@ -490,7 +489,6 @@ public class Player { public void setMora(int mora) { this.setProperty(PlayerProperty.PROP_PLAYER_SCOIN, mora); - this.sendPacket(new PacketPlayerPropNotify(this, PlayerProperty.PROP_PLAYER_SCOIN)); } public int getCrystals() { @@ -499,7 +497,6 @@ public class Player { public void setCrystals(int crystals) { this.setProperty(PlayerProperty.PROP_PLAYER_MCOIN, crystals); - this.sendPacket(new PacketPlayerPropNotify(this, PlayerProperty.PROP_PLAYER_MCOIN)); } public int getHomeCoin() { @@ -508,7 +505,6 @@ public class Player { public void setHomeCoin(int coin) { this.setProperty(PlayerProperty.PROP_PLAYER_HOME_COIN, coin); - this.sendPacket(new PacketPlayerPropNotify(this, PlayerProperty.PROP_PLAYER_HOME_COIN)); } private int getExpRequired(int level) { PlayerLevelData levelData = GameData.getPlayerLevelDataMap().get(level); @@ -546,9 +542,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() { @@ -621,7 +614,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) { @@ -1456,8 +1453,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 @@ -1565,101 +1562,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; } + } } diff --git a/src/main/java/emu/grasscutter/game/props/PlayerProperty.java b/src/main/java/emu/grasscutter/game/props/PlayerProperty.java index 85a9456cf..ef3d0dbe3 100644 --- a/src/main/java/emu/grasscutter/game/props/PlayerProperty.java +++ b/src/main/java/emu/grasscutter/game/props/PlayerProperty.java @@ -6,41 +6,41 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; public enum PlayerProperty { - PROP_EXP (1001), + 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 +48,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 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) { diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerSetPlayerPropReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerSetPlayerPropReq.java index 7f01392ad..7775a237d 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerSetPlayerPropReq.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerSetPlayerPropReq.java @@ -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 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; } From 9425f672e0ab374eddcab962552575c5e3da8248 Mon Sep 17 00:00:00 2001 From: AnimeGitB Date: Sat, 25 Jun 2022 02:04:29 +0930 Subject: [PATCH 15/23] Remove BPLevel, GodMode, NoStamina, SetWorldLevel, UnlimitEnergy, UnlockTower commands --- .../command/commands/GodModeCommand.java | 35 --- .../command/commands/NoStaminaCommand.java | 34 --- .../command/commands/SetBPLevelCommand.java | 22 -- .../command/commands/SetPropCommand.java | 205 ++++++++++++++++++ .../command/commands/SetStatsCommand.java | 13 +- .../commands/SetWorldLevelCommand.java | 39 ---- .../commands/UnlimitEnergyCommand.java | 55 ----- .../command/commands/UnlockTowerCommand.java | 32 --- .../game/battlepass/BattlePassManager.java | 61 ++---- .../game/managers/energy/EnergyManager.java | 5 + .../game/managers/stamina/StaminaManager.java | 2 +- .../emu/grasscutter/game/player/Player.java | 61 +++--- .../game/props/PlayerProperty.java | 1 + .../game/tower/TowerScheduleManager.java | 7 + src/main/resources/languages/en-US.json | 44 +--- src/main/resources/languages/fr-FR.json | 18 +- src/main/resources/languages/pl-PL.json | 18 +- src/main/resources/languages/ru-RU.json | 18 +- src/main/resources/languages/zh-CN.json | 36 +-- src/main/resources/languages/zh-TW.json | 18 +- 20 files changed, 334 insertions(+), 390 deletions(-) delete mode 100644 src/main/java/emu/grasscutter/command/commands/GodModeCommand.java delete mode 100644 src/main/java/emu/grasscutter/command/commands/NoStaminaCommand.java delete mode 100644 src/main/java/emu/grasscutter/command/commands/SetBPLevelCommand.java create mode 100644 src/main/java/emu/grasscutter/command/commands/SetPropCommand.java delete mode 100644 src/main/java/emu/grasscutter/command/commands/SetWorldLevelCommand.java delete mode 100644 src/main/java/emu/grasscutter/command/commands/UnlimitEnergyCommand.java delete mode 100644 src/main/java/emu/grasscutter/command/commands/UnlockTowerCommand.java diff --git a/src/main/java/emu/grasscutter/command/commands/GodModeCommand.java b/src/main/java/emu/grasscutter/command/commands/GodModeCommand.java deleted file mode 100644 index 0b635b897..000000000 --- a/src/main/java/emu/grasscutter/command/commands/GodModeCommand.java +++ /dev/null @@ -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 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())); - } -} diff --git a/src/main/java/emu/grasscutter/command/commands/NoStaminaCommand.java b/src/main/java/emu/grasscutter/command/commands/NoStaminaCommand.java deleted file mode 100644 index 4a1ef8873..000000000 --- a/src/main/java/emu/grasscutter/command/commands/NoStaminaCommand.java +++ /dev/null @@ -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 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())); - } -} diff --git a/src/main/java/emu/grasscutter/command/commands/SetBPLevelCommand.java b/src/main/java/emu/grasscutter/command/commands/SetBPLevelCommand.java deleted file mode 100644 index 47c9ee88e..000000000 --- a/src/main/java/emu/grasscutter/command/commands/SetBPLevelCommand.java +++ /dev/null @@ -1,22 +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 args) { - if (args.size() < 1) { - CommandHandler.sendMessage(sender , "Need a arg"); - return; - } - - int level = Integer.parseInt(args.get(0)); - - sender.getBattlePassManager().addPoints(level); - } -} diff --git a/src/main/java/emu/grasscutter/command/commands/SetPropCommand.java b/src/main/java/emu/grasscutter/command/commands/SetPropCommand.java new file mode 100644 index 000000000..2248d2b1a --- /dev/null +++ b/src/main/java/emu/grasscutter/command/commands/SetPropCommand.java @@ -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 ", 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 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 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 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 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; + } +} diff --git a/src/main/java/emu/grasscutter/command/commands/SetStatsCommand.java b/src/main/java/emu/grasscutter/command/commands/SetStatsCommand.java index 3530929ee..d3c19953e 100644 --- a/src/main/java/emu/grasscutter/command/commands/SetStatsCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/SetStatsCommand.java @@ -10,7 +10,6 @@ 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 static emu.grasscutter.utils.Language.translate; @Command(label = "setstats", usage = "setstats|stats ", aliases = {"stats"}, permission = "player.setstats", permissionTargeted = "player.setstats.others", description = "commands.setStats.description") public final class SetStatsCommand implements CommandHandler { @@ -65,8 +64,6 @@ public final class SetStatsCommand implements CommandHandler { @Override public void execute(Player sender, Player targetPlayer, List 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; @@ -74,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; } @@ -88,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; } @@ -102,13 +99,13 @@ public final class SetStatsCommand implements CommandHandler { 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; } diff --git a/src/main/java/emu/grasscutter/command/commands/SetWorldLevelCommand.java b/src/main/java/emu/grasscutter/command/commands/SetWorldLevelCommand.java deleted file mode 100644 index c142dc01d..000000000 --- a/src/main/java/emu/grasscutter/command/commands/SetWorldLevelCommand.java +++ /dev/null @@ -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 ", - 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 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")); - } - } -} diff --git a/src/main/java/emu/grasscutter/command/commands/UnlimitEnergyCommand.java b/src/main/java/emu/grasscutter/command/commands/UnlimitEnergyCommand.java deleted file mode 100644 index e3f074979..000000000 --- a/src/main/java/emu/grasscutter/command/commands/UnlimitEnergyCommand.java +++ /dev/null @@ -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 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())); - } -} diff --git a/src/main/java/emu/grasscutter/command/commands/UnlockTowerCommand.java b/src/main/java/emu/grasscutter/command/commands/UnlockTowerCommand.java deleted file mode 100644 index 60194fa8e..000000000 --- a/src/main/java/emu/grasscutter/command/commands/UnlockTowerCommand.java +++ /dev/null @@ -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 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 floors){ - floors.stream() - .filter(id -> !player.getTowerManager().getRecordMap().containsKey(id)) - .forEach(id -> player.getTowerManager().getRecordMap().put(id, new TowerLevelRecord(id))); - } -} diff --git a/src/main/java/emu/grasscutter/game/battlepass/BattlePassManager.java b/src/main/java/emu/grasscutter/game/battlepass/BattlePassManager.java index 5a8a8390c..049441728 100644 --- a/src/main/java/emu/grasscutter/game/battlepass/BattlePassManager.java +++ b/src/main/java/emu/grasscutter/game/battlepass/BattlePassManager.java @@ -31,19 +31,20 @@ 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 cyclePoints; // Weekly maximum cap - private int level; + @Getter private int point; + @Getter private int cyclePoints; // Weekly maximum cap + @Getter private int level; - private boolean viewed; - private boolean paid; + @Getter private boolean viewed; + @Getter private boolean paid; private Map missions; private Map takenRewards; @@ -55,52 +56,34 @@ 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 this.point; - } - - public int getCyclePoints() { - return cyclePoints; - } - - public int getLevel() { - return this.level; - } - - public boolean isViewed() { - return viewed; - } - public void updateViewed() { this.viewed = true; } - public boolean isPaid() { - return paid; + 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 addPoints(int point){ - this.addPointsDirectly(point, false); + public void addPoints(int points){ + this.addPointsDirectly(points, false); - player.getSession().send(new PacketBattlePassCurScheduleUpdateNotify(player.getSession().getPlayer())); + this.player.sendPacket(new PacketBattlePassCurScheduleUpdateNotify(player)); this.save(); } - public void addPointsDirectly(int point, boolean isWeekly) { - int amount = point; + public void addPointsDirectly(int points, boolean isWeekly) { + int amount = points; if (isWeekly) { amount = Math.min(amount, GameConstants.BATTLE_PASS_POINT_PER_WEEK - this.cyclePoints); @@ -114,7 +97,7 @@ public class BattlePassManager { this.cyclePoints += amount; if (this.point >= GameConstants.BATTLE_PASS_POINT_PER_LEVEL && this.getLevel() < GameConstants.BATTLE_PASS_MAX_LEVEL) { - int levelups = (int) Math.floor((float) this.point / GameConstants.BATTLE_PASS_POINT_PER_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); diff --git a/src/main/java/emu/grasscutter/game/managers/energy/EnergyManager.java b/src/main/java/emu/grasscutter/game/managers/energy/EnergyManager.java index 82b075228..39c08b956 100644 --- a/src/main/java/emu/grasscutter/game/managers/energy/EnergyManager.java +++ b/src/main/java/emu/grasscutter/game/managers/energy/EnergyManager.java @@ -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); + } + } } } \ No newline at end of file diff --git a/src/main/java/emu/grasscutter/game/managers/stamina/StaminaManager.java b/src/main/java/emu/grasscutter/game/managers/stamina/StaminaManager.java index 3c3fd5a02..6b9b03022 100644 --- a/src/main/java/emu/grasscutter/game/managers/stamina/StaminaManager.java +++ b/src/main/java/emu/grasscutter/game/managers/stamina/StaminaManager.java @@ -283,7 +283,7 @@ 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(); } diff --git a/src/main/java/emu/grasscutter/game/player/Player.java b/src/main/java/emu/grasscutter/game/player/Player.java index 2a13012f7..17d35d103 100644 --- a/src/main/java/emu/grasscutter/game/player/Player.java +++ b/src/main/java/emu/grasscutter/game/player/Player.java @@ -437,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() { @@ -452,59 +453,59 @@ public class Player { public int getWorldLevel() { return this.getProperty(PlayerProperty.PROP_PLAYER_WORLD_LEVEL); } - - public void setWorldLevel(int level) { - this.getWorld().setWorldLevel(level); - - this.setProperty(PlayerProperty.PROP_PLAYER_WORLD_LEVEL, level); - this.sendPacket(new PacketPlayerPropNotify(this, PlayerProperty.PROP_PLAYER_WORLD_LEVEL)); - - this.updateProfile(); + + 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 int getForgePoints() { return this.getProperty(PlayerProperty.PROP_PLAYER_FORGE_POINT); } - public void setForgePoints(int value) { + public boolean setForgePoints(int value) { if (value == this.getForgePoints()) { - return; + return true; } - this.setProperty(PlayerProperty.PROP_PLAYER_FORGE_POINT, value); - this.sendPacket(new PacketPlayerPropNotify(this, PlayerProperty.PROP_PLAYER_FORGE_POINT)); + 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); + 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); + 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); + 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); + 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); @@ -581,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(); } @@ -953,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() { diff --git a/src/main/java/emu/grasscutter/game/props/PlayerProperty.java b/src/main/java/emu/grasscutter/game/props/PlayerProperty.java index ef3d0dbe3..30f3ce14f 100644 --- a/src/main/java/emu/grasscutter/game/props/PlayerProperty.java +++ b/src/main/java/emu/grasscutter/game/props/PlayerProperty.java @@ -6,6 +6,7 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; public enum PlayerProperty { + PROP_NONE (0), PROP_EXP (1001, 0), PROP_BREAK_LEVEL (1002), PROP_SATIATION_VAL (1003), diff --git a/src/main/java/emu/grasscutter/game/tower/TowerScheduleManager.java b/src/main/java/emu/grasscutter/game/tower/TowerScheduleManager.java index a2b8c0396..0455a9769 100644 --- a/src/main/java/emu/grasscutter/game/tower/TowerScheduleManager.java +++ b/src/main/java/emu/grasscutter/game/tower/TowerScheduleManager.java @@ -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 getAllFloors() { + List floors = new ArrayList<>(this.getCurrentTowerScheduleData().getEntranceFloorId()); + floors.addAll(this.getScheduleFloors()); + return floors; + } + public List getScheduleFloors() { return getCurrentTowerScheduleData().getSchedules().get(0).getFloorList(); } diff --git a/src/main/resources/languages/en-US.json b/src/main/resources/languages/en-US.json index 558b61844..26b9a871e 100644 --- a/src/main/resources/languages/en-US.json +++ b/src/main/resources/languages/en-US.json @@ -66,6 +66,8 @@ "command_exist_error": "No command found.", "no_usage_specified": "No usage specified", "no_description_specified": "No description specified", + "set_to": "%s set to %s.", + "set_for_to": "%s for %s set to %s.", "invalid": { "amount": "Invalid amount.", "artifactId": "Invalid artifact ID.", @@ -75,6 +77,8 @@ "itemId": "Invalid item ID.", "itemLevel": "Invalid itemLevel.", "itemRefinement": "Invalid itemRefinement.", + "statValue": "Invalid stat value.", + "value_between": "Invalid value: %s must be between %s and %s.", "playerId": "Invalid player ID.", "uid": "Invalid UID.", "id": "Invalid ID." @@ -143,10 +147,6 @@ "giveall_success": "Successfully gave all items.", "description": "Gives an item to you or the specified player. Can also give all weapons, avatars and/or materials, and can construct custom artifacts." }, - "godmode": { - "success": "Godmode is now %s for %s.", - "description": "Prevents you from taking damage. Defaults to toggle." - }, "heal": { "success": "All characters have been healed.", "description": "Heal all characters in your current team." @@ -243,9 +243,9 @@ "description": "Sends mail to the specified user. The usage of this command changes based on its composition state" }, "sendMessage": { - "usage": "Usage: sendmessage ", + "usage": "Usage: sendmessage ", "success": "Message sent.", - "description": "Sends a message to a player as the server" + "description": "Sends a message to a player as the server. If used with no target, sends to all players on the server." }, "setFetterLevel": { "usage": "Usage: setfetterlevel ", @@ -254,24 +254,13 @@ "level_error": "Invalid fetter level.", "description": "Sets your fetter level for your current active character" }, - "setStats": { - "usage_console": "Usage: setstats|stats @ ", - "usage_ingame": "Usage: setstats|stats [@UID] ", - "help_message": "\n\tValues for : hp | maxhp | def | atk | em | er | crate | cdmg | cdr | heal | heali | shield | defi\n\t(cont.) Elemental DMG Bonus: epyro | ecryo | ehydro | egeo | edendro | eelectro | ephys\n\t(cont.) Elemental RES: respyro | rescryo | reshydro | resgeo | resdendro | reselectro | resphys\n", - "value_error": "Invalid stat value.", - "uid_error": "Invalid UID.", - "player_error": "Player not found or offline.", - "set_self": "%s set to %s.", - "set_for_uid": "%s for %s set to %s.", - "set_max_hp": "MAX HP set to %s.", - "description": "Sets fight property for your current active character" + "setProp": { + "usage": "Usage: setprop|prop \n\tValues for : godmode | nostamina | unlimitedenergy | abyssfloor | worldlevel | bplevel\n\t(cont.) see PlayerProperty enum for other possible values, of form PROP_MAX_SPRING_VOLUME -> max_spring_volume", + "description": "Sets accountwide properties. Things like godmode can be enabled this way, as well as changing things like unlocked abyss floor and battle pass progress." }, - "setWorldLevel": { - "usage": "Usage: setworldlevel ", - "value_error": "World level must be between 0-8.", - "success": "World level set to %s.", - "invalid_world_level": "Invalid world level.", - "description": "Sets your world level (Relog to see proper effects)" + "setStats": { + "usage": "Usage: setstats|stats \n\tValues for : hp | maxhp | def | atk | em | er | crate | cdmg | cdr | heal | heali | shield | defi\n\t(cont.) Elemental DMG Bonus: epyro | ecryo | ehydro | egeo | edendro | eelectro | ephys\n\t(cont.) Elemental RES: respyro | rescryo | reshydro | resgeo | resdendro | reselectro | resphys\n", + "description": "Sets fight property for your current active character" }, "spawn": { "usage": "Usage: spawn [amount] [level(monster only)] [ (monster only, optional)]", @@ -332,15 +321,6 @@ "success": "Teleported %s to %s, %s, %s in scene %s.", "description": "Change the player's position" }, - "unlimitenergy": { - "success": "UnlimitEnergy is now %s for %s.", - "config_error": "Command is disabled, because energyUsage is false in config.json.", - "description": "Use the element does not waste energy" - }, - "unlocktower": { - "success": "Abyss Corridor's Floors are all unlocked now.", - "description": "Unlock all levels of tower" - }, "weather": { "usage": "Usage: weather [weatherId] [climateType]\nWeather IDs can be found in WeatherExcelConfigData.json.\nClimate types: sunny, cloudy, rain, thunderstorm, snow, mist", "success": "Set weather ID to %s with climate type %s.", diff --git a/src/main/resources/languages/fr-FR.json b/src/main/resources/languages/fr-FR.json index b3b5583d3..6932eed4b 100644 --- a/src/main/resources/languages/fr-FR.json +++ b/src/main/resources/languages/fr-FR.json @@ -66,6 +66,8 @@ "command_exist_error": "Aucune commande trouvée.", "no_usage_specified": "Pas de description de l'utilisation spécifiée.", "no_description_specified": "Pas de description spécifiée", + "set_to": "%s a été défini a %s.", + "set_for_to": "%s de %s a été défini a %s.", "invalid": { "amount": "Montant invalide.", "artifactId": "ID de l'artéfact invalide.", @@ -75,6 +77,8 @@ "itemId": "ID de l'objet invalide.", "itemLevel": "Niveau de l'objet invalide.", "itemRefinement": "Raffinement de l'objet invalide.", + "statValue": "Valeur de invalide.", + "value_between": "Invalid value: %s must be between %s and %s.", "playerId": "ID du joueur invalide.", "uid": "UID invalide.", "id": "ID invalide." @@ -271,16 +275,12 @@ "level_error": "Niveau d'affinité invalide.", "description": "Défini le niveau d'affinité de votre personnage actif" }, + "setProp": { + "usage": "Usage: setprop|prop \n\tValues for : godmode | nostamina | unlimitedenergy | abyssfloor | worldlevel | bplevel\n\t(cont.) see PlayerProperty enum for other possible values, of form PROP_MAX_SPRING_VOLUME -> max_spring_volume", + "description": "Sets accountwide properties. Things like godmode can be enabled this way, as well as changing things like unlocked abyss floor and battle pass progress." + }, "setStats": { - "usage_console": "Usage: setstats|stats @ ", - "usage_ingame": "Usage: setstats|stats [@UID] ", - "help_message": "\n\tValeurs pour : hp | maxhp | def | atk | em | er | crate | cdmg | cdr | heal | heali | shield | defi\n\t(cont.) Bonus de dégât élémentaire: epyro | ecryo | ehydro | egeo | edendro | eelectro | ephys\n\t(cont.) Résistance élémentaire: respyro | rescryo | reshydro | resgeo | resdendro | reselectro | resphys\n", - "value_error": "Valeur de invalide.", - "uid_error": "UID invalide.", - "player_error": "Joueur introuvable ou hors ligne.", - "set_self": "%s a été défini a %s.", - "set_for_uid": "%s de %s a été défini a %s.", - "set_max_hp": "MAX HP a été défini a %s.", + "usage": "Usage: setstats|stats \n\tValeurs pour : hp | maxhp | def | atk | em | er | crate | cdmg | cdr | heal | heali | shield | defi\n\t(cont.) Bonus de dégât élémentaire: epyro | ecryo | ehydro | egeo | edendro | eelectro | ephys\n\t(cont.) Résistance élémentaire: respyro | rescryo | reshydro | resgeo | resdendro | reselectro | resphys\n", "description": "Définit les propriétés de combat de votre personnage actif" }, "setWorldLevel": { diff --git a/src/main/resources/languages/pl-PL.json b/src/main/resources/languages/pl-PL.json index 0d8b5a29e..e4d006027 100644 --- a/src/main/resources/languages/pl-PL.json +++ b/src/main/resources/languages/pl-PL.json @@ -60,6 +60,8 @@ "console_execute_error": "Tą komende można wywołać tylko z konsoli.", "player_execute_error": "Wywołaj tą komendę w grze.", "command_exist_error": "Nie znaleziono komendy.", + "set_to": "%s ustawiono na %s.", + "set_for_to": "%s dla %s ustawiono na %s.", "invalid": { "amount": "Błędna ilość.", "artifactId": "Błędne ID artefaktu.", @@ -69,6 +71,7 @@ "id przedmiotu": "Błędne id przedmiotu.", "itemLevel": "Błędny poziom przedmiotu.", "itemRefinement": "Błędne ulepszenie.", + "value_between": "Invalid value: %s must be between %s and %s.", "playerId": "Błędne playerId.", "uid": "Błędne UID.", "id": "Błędne ID." @@ -221,16 +224,13 @@ "success": "Poziom przyjaźni ustawiono na: %s", "level_error": "Błędny poziom przyjaźni." }, + "setProp": { + "usage": "Usage: setprop|prop \n\tValues for : godmode | nostamina | unlimitedenergy | abyssfloor | worldlevel | bplevel\n\t(cont.) see PlayerProperty enum for other possible values, of form PROP_MAX_SPRING_VOLUME -> max_spring_volume", + "description": "Sets accountwide properties. Things like godmode can be enabled this way, as well as changing things like unlocked abyss floor and battle pass progress." + }, "setStats": { - "usage_console": "Użycie: setstats|stats @ ", - "usage_ingame": "Użycie: setstats|stats [@UID] ", - "help_message": "\n\tWartości dla Statystyka: hp | maxhp | def | atk | em | er | crate | cdmg | cdr | heal | heali | shield | defi\n\t(cont.) Bonus DMG żywiołu: epyro | ecryo | ehydro | egeo | edendro | eelectro | ephys\n\t(cont.) RES na żywioł: respyro | rescryo | reshydro | resgeo | resdendro | reselectro | resphys\n", - "value_error": "Błędna wartość statystyki.", - "uid_error": "Błędne UID.", - "player_error": "Gracza nie znaleziono lub jest offline.", - "set_self": "%s ustawiono na %s.", - "set_for_uid": "%s dla %s ustawiono na %s.", - "set_max_hp": "Maksymalne HP ustawione na %s." + "usage": "Użycie: setstats|stats \n\tWartości dla Statystyka: hp | maxhp | def | atk | em | er | crate | cdmg | cdr | heal | heali | shield | defi\n\t(cont.) Bonus DMG żywiołu: epyro | ecryo | ehydro | egeo | edendro | eelectro | ephys\n\t(cont.) RES na żywioł: respyro | rescryo | reshydro | resgeo | resdendro | reselectro | resphys\n", + "description": "Sets fight property for your current active character" }, "setWorldLevel": { "usage": "Użycie: setworldlevel ", diff --git a/src/main/resources/languages/ru-RU.json b/src/main/resources/languages/ru-RU.json index f099396f2..a2b8be991 100644 --- a/src/main/resources/languages/ru-RU.json +++ b/src/main/resources/languages/ru-RU.json @@ -66,6 +66,8 @@ "command_exist_error": "Команда не найдена.", "no_usage_specified": "Применение команды не указано", "no_description_specified": "Описание отсутствует", + "set_to": "Характеристика %s стала равной %s.", + "set_for_to": "Характеристика %s игрока %s стала равной %s.", "invalid": { "amount": "Некорректное количество.", "artifactId": "Некорректный ID артефакта.", @@ -75,6 +77,8 @@ "itemId": "Некорректный ID предмета.", "itemLevel": "Некорректный уровень предмета (itemLevel).", "itemRefinement": "Некорректный уровень пробуждения предмета (itemRefinement).", + "statValue": "Некорректное значение характеристики.", + "value_between": "Invalid value: %s must be between %s and %s.", "playerId": "Некорректный ID игрока.", "uid": "Некорректный UID.", "id": "Некорректный ID." @@ -298,16 +302,12 @@ "level_error": "Некорректный уровень дружбы.", "description": "Устанавливает уровень дружбы для активного персонажа" }, + "setProp": { + "usage": "Usage: setprop|prop \n\tValues for : godmode | nostamina | unlimitedenergy | abyssfloor | worldlevel | bplevel\n\t(cont.) see PlayerProperty enum for other possible values, of form PROP_MAX_SPRING_VOLUME -> max_spring_volume", + "description": "Sets accountwide properties. Things like godmode can be enabled this way, as well as changing things like unlocked abyss floor and battle pass progress." + }, "setStats": { - "usage_console": "Применение: setstats|stats @ <хар-ка> <значение>", - "usage_ingame": "Применение: setstats|stats [@UID] <хар-ка> <значение>", - "help_message": "\n\tВозможные значения для <хар-ка>: hp | maxhp | def | atk | em | er | crate | cdmg | cdr | heal | heali | shield | defi\n\t(прод.) Бонус элементального урона: epyro | ecryo | ehydro | egeo | edendro | eelectro | ephys\n\t(cont.) Элементальное сопротивление: respyro | rescryo | reshydro | resgeo | resdendro | reselectro | resphys\n", - "value_error": "Некорректное значение характеристики.", - "uid_error": "Некорректный UID.", - "player_error": "Игрок не найден или находится не в сети.", - "set_self": "Характеристика %s стала равной %s.", - "set_for_uid": "Характеристика %s игрока %s стала равной %s.", - "set_max_hp": "Максимальное значение здоровья стало равно %s.", + "usage": "Применение: setstats|stats <хар-ка> <значение>\n\tВозможные значения для <хар-ка>: hp | maxhp | def | atk | em | er | crate | cdmg | cdr | heal | heali | shield | defi\n\t(прод.) Бонус элементального урона: epyro | ecryo | ehydro | egeo | edendro | eelectro | ephys\n\t(cont.) Элементальное сопротивление: respyro | rescryo | reshydro | resgeo | resdendro | reselectro | resphys\n", "description": "Задаёт боевые характеристики для активного персонажа" }, "setWorldLevel": { diff --git a/src/main/resources/languages/zh-CN.json b/src/main/resources/languages/zh-CN.json index a2a8018ae..9896b0850 100644 --- a/src/main/resources/languages/zh-CN.json +++ b/src/main/resources/languages/zh-CN.json @@ -66,6 +66,8 @@ "command_exist_error": "未找到命令。", "no_usage_specified": "未指定用法", "no_description_specified": "未指定说明", + "set_to": "%s 已设为 %s。", + "set_for_to": "%s [来自 %s] 已设为 %s。", "invalid": { "amount": "无效的数量。", "artifactId": "无效的圣遗物ID。", @@ -75,6 +77,8 @@ "itemId": "无效的物品ID。", "itemLevel": "无效的物品等级。", "itemRefinement": "无效的物品精炼等级。", + "statValue": "无效的属性值。", + "value_between": "Invalid value: %s must be between %s and %s.", "playerId": "无效的玩家ID。", "uid": "无效的UID。", "id": "无效的ID。" @@ -270,24 +274,13 @@ "level_error": "无效的好感度等级。", "description": "设置当前角色的好感度等级" }, - "setStats": { - "usage_console": "用法:setstats|stats @ <属性> <数值>", - "usage_ingame": "用法:setstats|stats [@UID] <属性> <数值>", - "help_message": "\n可更改的属性列表:hp(生命值)|maxhp(最大生命值)|def(防御力)|atk(攻击力)|em(元素精通)|er(元素充能效率)|crate(暴击率)|cdmg(暴击伤害)|cdr(冷却缩减)|heal(治疗加成)|heali(受治疗加成)|shield(护盾强效)|defi(无视防御)\n元素增伤:epyro(火)|ecryo(冰)|ehydro(水)|egeo(岩)|edendro(草)|eelectro(雷)|ephys(物理)\n元素抗性:respyro(火)|rescryo(冰)|reshydro(水)|resgeo(岩)|resdendro(草)|reselectro(雷)|resphys(物理)\n", - "value_error": "无效的属性值。", - "uid_error": "无效的UID。", - "player_error": "玩家不存在或已离线。", - "set_self": "%s 已设为 %s。", - "set_for_uid": "%s [来自 %s] 已设为 %s。", - "set_max_hp": "最大生命值已设为 %s。", - "description": "设置当前角色的属性" + "setProp": { + "usage": "Usage: setprop|prop \n\tValues for : godmode | nostamina | unlimitedenergy | abyssfloor | worldlevel | bplevel\n\t(cont.) see PlayerProperty enum for other possible values, of form PROP_MAX_SPRING_VOLUME -> max_spring_volume", + "description": "Sets accountwide properties. Things like godmode can be enabled this way, as well as changing things like unlocked abyss floor and battle pass progress." }, - "setWorldLevel": { - "usage": "用法:setworldlevel <等级>", - "value_error": "世界等级必须在 0-8 之间。", - "success": "世界等级已设为 %s。", - "invalid_world_level": "无效的世界等级。", - "description": "设置世界等级,执行命令后需重新登录以生效" + "setStats": { + "usage": "用法:setstats|stats <属性> <数值>\n可更改的属性列表:hp(生命值)|maxhp(最大生命值)|def(防御力)|atk(攻击力)|em(元素精通)|er(元素充能效率)|crate(暴击率)|cdmg(暴击伤害)|cdr(冷却缩减)|heal(治疗加成)|heali(受治疗加成)|shield(护盾强效)|defi(无视防御)\n元素增伤:epyro(火)|ecryo(冰)|ehydro(水)|egeo(岩)|edendro(草)|eelectro(雷)|ephys(物理)\n元素抗性:respyro(火)|rescryo(冰)|reshydro(水)|resgeo(岩)|resdendro(草)|reselectro(雷)|resphys(物理)\n", + "description": "设置当前角色的属性" }, "spawn": { "usage": "用法:spawn <实体ID> [数量] [等级(仅怪物)] [ (仅怪物, 可选)]", @@ -348,15 +341,6 @@ "success": "传送 %s 到坐标 %s, %s, %s,场景为 %s。", "description": "改变指定玩家的位置" }, - "unlimitenergy": { - "success": "UnlimitEnergy 已设为 %s。[用户:%s]", - "config_error": "命令不可用。因为 config.json 中 energyUsage 为 false。", - "description": "使用元素爆发而不消耗能量" - }, - "unlocktower": { - "success": "现已解锁深境回廊(1-8层)。", - "description": "解锁深境螺旋" - }, "weather": { "usage": "用法:weather [天气ID] [气候类型]\n天气ID可以在 WeatherExcelConfigData.json 中找到。\n气候类型:sunny(晴天), cloudy(多云), rain(雨), thunderstorm(雷雨), snow(雪), mist(雾)", "success": "已设置天气ID 为 %s,气候类型为 %s。", diff --git a/src/main/resources/languages/zh-TW.json b/src/main/resources/languages/zh-TW.json index 129bcf4e8..350d041fd 100644 --- a/src/main/resources/languages/zh-TW.json +++ b/src/main/resources/languages/zh-TW.json @@ -65,6 +65,8 @@ "player_execute_error": "請在遊戲裡使用這條指令。", "command_exist_error": "找不到指令。", "no_description_specified": "没有指定說明。", + "set_to": "%s 已經設為 %s。", + "set_for_to": "%s 的使用者 %s 更改為 %s。", "invalid": { "amount": "無效的數量。", "artifactId": "無效的聖遺物ID。", @@ -74,6 +76,8 @@ "itemId": "無效的物品ID。", "itemLevel": "無效的物品等級。", "itemRefinement": "無效的物品精煉度。", + "statValue": "無效的數據值。", + "value_between": "Invalid value: %s must be between %s and %s.", "playerId": "無效的玩家ID。", "uid": "無效的UID。", "id": "無效的ID。" @@ -274,16 +278,12 @@ "level_error": "無效的好感度。", "description": "設定當前角色的好感度等級。" }, + "setProp": { + "usage": "Usage: setprop|prop \n\tValues for : godmode | nostamina | unlimitedenergy | abyssfloor | worldlevel | bplevel\n\t(cont.) see PlayerProperty enum for other possible values, of form PROP_MAX_SPRING_VOLUME -> max_spring_volume", + "description": "Sets accountwide properties. Things like godmode can be enabled this way, as well as changing things like unlocked abyss floor and battle pass progress." + }, "setStats": { - "usage_console": "用法:setstats|stats @ ", - "usage_ingame": "用法:setstats|stats [@UID] ", - "help_message": "\n\t可使用的數據類型:hp (生命值)| maxhp (最大生命值) | def(防禦力) | atk (攻擊力)| em (元素精通) | er (元素充能效率) | crate(暴擊率) | cdmg (暴擊傷害)| cdr (冷卻縮減) | heal(治療加成)| heali (受治療加成)| shield (護盾強效)| defi (無視防禦)\n\t(cont.) 元素增傷類:epyro (火傷) | ecryo (冰傷) | ehydro (水傷) | egeo (岩傷) | edendro (草傷) | eelectro (雷傷) | ephys (物傷)(cont.) 元素減傷類:respyro (火抗) | rescryo (冰抗) | reshydro (水抗) | resgeo (岩抗) | resdendro (草抗) | reselectro (雷抗) | resphys (物抗)\n", - "value_error": "無效的數據值。", - "uid_error": "無效的UID。", - "player_error": "玩家不存在或已離線。", - "set_self": "%s 已經設為 %s。", - "set_for_uid": "%s 的使用者 %s 更改為 %s。", - "set_max_hp": "最大生命值更改為 %s。", + "usage": "用法:setstats|stats \n\t可使用的數據類型:hp (生命值)| maxhp (最大生命值) | def(防禦力) | atk (攻擊力)| em (元素精通) | er (元素充能效率) | crate(暴擊率) | cdmg (暴擊傷害)| cdr (冷卻縮減) | heal(治療加成)| heali (受治療加成)| shield (護盾強效)| defi (無視防禦)\n\t(cont.) 元素增傷類:epyro (火傷) | ecryo (冰傷) | ehydro (水傷) | egeo (岩傷) | edendro (草傷) | eelectro (雷傷) | ephys (物傷)(cont.) 元素減傷類:respyro (火抗) | rescryo (冰抗) | reshydro (水抗) | resgeo (岩抗) | resdendro (草抗) | reselectro (雷抗) | resphys (物抗)\n", "description": "設定當前角色的數據類型。" }, "setWorldLevel": { From 40dcde66e46b8c03d21c7e6347963a4f7b472263 Mon Sep 17 00:00:00 2001 From: AnimeGitB Date: Sat, 25 Jun 2022 22:49:32 +0930 Subject: [PATCH 16/23] Add more lombok to keep VSCode happy --- .gitignore | 1 + build.gradle | 2 ++ 2 files changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index ede33151b..38232abf0 100644 --- a/.gitignore +++ b/.gitignore @@ -78,4 +78,5 @@ BuildConfig.java # macOS .DS_Store +.directory data/hk4e/announcement/ diff --git a/build.gradle b/build.gradle index 97e0d5056..d4535ceb9 100644 --- a/build.gradle +++ b/build.gradle @@ -93,6 +93,8 @@ dependencies { compileOnly 'org.projectlombok:lombok:1.18.24' annotationProcessor 'org.projectlombok:lombok:1.18.24' + testCompileOnly 'org.projectlombok:lombok:1.18.24' + testAnnotationProcessor 'org.projectlombok:lombok:1.18.24' } configurations.all { From 0144e55d3a7e4143181030c395d4b413f58352c5 Mon Sep 17 00:00:00 2001 From: zhaodice <63996691+zhaodice@users.noreply.github.com> Date: Sun, 26 Jun 2022 13:19:59 +0800 Subject: [PATCH 17/23] Lunch Support (#1373) * PacketSetUpLunchBoxWidgetRsp * HandlerSetUpLunchBoxWidgetReq --- .../recv/HandlerSetUpLunchBoxWidgetReq.java | 19 +++++++++++++++++++ .../send/PacketSetUpLunchBoxWidgetRsp.java | 18 ++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 src/main/java/emu/grasscutter/server/packet/recv/HandlerSetUpLunchBoxWidgetReq.java create mode 100644 src/main/java/emu/grasscutter/server/packet/send/PacketSetUpLunchBoxWidgetRsp.java diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerSetUpLunchBoxWidgetReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerSetUpLunchBoxWidgetReq.java new file mode 100644 index 000000000..a7d3e3c06 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerSetUpLunchBoxWidgetReq.java @@ -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())); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketSetUpLunchBoxWidgetRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketSetUpLunchBoxWidgetRsp.java new file mode 100644 index 000000000..c282aae5e --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketSetUpLunchBoxWidgetRsp.java @@ -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()); + } +} From 57e0f7ba40a3089db1af87a28d2ae6eddc99121e Mon Sep 17 00:00:00 2001 From: Paimon2022 Date: Sun, 26 Jun 2022 09:43:54 +0800 Subject: [PATCH 18/23] Fix healing abilities for Q skill of Kokomi, Qiqi and Barbara. --- .../emu/grasscutter/game/ability/HealAbilityManager.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/emu/grasscutter/game/ability/HealAbilityManager.java b/src/main/java/emu/grasscutter/game/ability/HealAbilityManager.java index f0ec12448..3c70025c4 100644 --- a/src/main/java/emu/grasscutter/game/ability/HealAbilityManager.java +++ b/src/main/java/emu/grasscutter/game/ability/HealAbilityManager.java @@ -92,15 +92,15 @@ 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, 4660, 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)); } public Player getPlayer() { @@ -139,7 +139,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); From 6e9006710178d4119652965e628c68f3bf61cab0 Mon Sep 17 00:00:00 2001 From: zhaodice <63996691+zhaodice@users.noreply.github.com> Date: Sun, 26 Jun 2022 12:42:33 +0800 Subject: [PATCH 19/23] Logout immediately --- .../server/packet/recv/HandlerPlayerForceExitReq.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerPlayerForceExitReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerPlayerForceExitReq.java index 1fe4c8d54..844fa195e 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerPlayerForceExitReq.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerPlayerForceExitReq.java @@ -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,7 @@ 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)); + session.getPlayer().onLogout(); } } From d5866dd36dc0cafb15b5e49283e87a91bd7aeba2 Mon Sep 17 00:00:00 2001 From: zhaodice <63996691+zhaodice@users.noreply.github.com> Date: Sun, 26 Jun 2022 13:24:02 +0800 Subject: [PATCH 20/23] Update HandlerPlayerForceExitReq.java --- .../server/packet/recv/HandlerPlayerForceExitReq.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerPlayerForceExitReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerPlayerForceExitReq.java index 844fa195e..84943757d 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerPlayerForceExitReq.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerPlayerForceExitReq.java @@ -12,6 +12,6 @@ public class HandlerPlayerForceExitReq extends PacketHandler { public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { // Client should auto disconnect right now session.send(new BasePacket(PacketOpcodes.PlayerForceExitRsp)); - session.getPlayer().onLogout(); + session.close(); } } From b5fd8c1acd26263f26307f91300f23836a8ba3b5 Mon Sep 17 00:00:00 2001 From: xingluo233 <54618947+xingluo233@users.noreply.github.com> Date: Sun, 26 Jun 2022 15:51:31 +0800 Subject: [PATCH 21/23] Update TowerSchedule.json --- src/main/resources/defaults/data/TowerSchedule.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/resources/defaults/data/TowerSchedule.json b/src/main/resources/defaults/data/TowerSchedule.json index e10416a20..cf8b2fe04 100644 --- a/src/main/resources/defaults/data/TowerSchedule.json +++ b/src/main/resources/defaults/data/TowerSchedule.json @@ -1,5 +1,5 @@ { "scheduleId" : 45, - "scheduleStartTime" : "2022-05-01T00:00:00+08:00", - "nextScheduleChangeTime" : "2022-05-30T00:00:00+08:00" -} \ No newline at end of file + "scheduleStartTime" : "2022-06-01T00:00:00+08:00", + "nextScheduleChangeTime" : "2030-06-30T00:00:00+08:00" +} From 541c38b74c56a2dce60bf3549c35f685a3c89f2d Mon Sep 17 00:00:00 2001 From: zhaodice <63996691+zhaodice@users.noreply.github.com> Date: Sun, 26 Jun 2022 15:08:06 +0800 Subject: [PATCH 22/23] Avoid disconnection notification --- .../packet/recv/HandlerPlayerForceExitReq.java | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerPlayerForceExitReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerPlayerForceExitReq.java index 84943757d..4f578021a 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerPlayerForceExitReq.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerPlayerForceExitReq.java @@ -12,6 +12,17 @@ public class HandlerPlayerForceExitReq extends PacketHandler { public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { // Client should auto disconnect right now session.send(new BasePacket(PacketOpcodes.PlayerForceExitRsp)); - session.close(); + new Thread(){ + @Override + public void run() { + try { + Thread.sleep(1000);// disconnect after 1 seconds + } catch (InterruptedException e) { + e.printStackTrace(); + } + session.close(); + super.run(); + } + }.start(); } } From 46916b82168c26098091bffa7150d0479b37b7e7 Mon Sep 17 00:00:00 2001 From: Paimon2022 Date: Sun, 26 Jun 2022 22:29:06 +0800 Subject: [PATCH 23/23] Fix healing abilities for Q skill of Hu Tao --- .../grasscutter/game/ability/HealAbilityManager.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/main/java/emu/grasscutter/game/ability/HealAbilityManager.java b/src/main/java/emu/grasscutter/game/ability/HealAbilityManager.java index 3c70025c4..5aa274ce3 100644 --- a/src/main/java/emu/grasscutter/game/ability/HealAbilityManager.java +++ b/src/main/java/emu/grasscutter/game/ability/HealAbilityManager.java @@ -98,9 +98,10 @@ public class HealAbilityManager { 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).addHealData("Q", "Avatar_Barbara_IdolHeal", 0.374f, 4660, 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).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 map = sourceEntity.getMetaOverrideMap(); for(int i = 0 ; i < healDataAvatarList.size() ; i ++) { @@ -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;