From c912b8d85710293961d1bc6ecb542237a7a6dac3 Mon Sep 17 00:00:00 2001 From: Akka <104902222+Akka0@users.noreply.github.com> Date: Fri, 6 May 2022 14:10:23 +0800 Subject: [PATCH 1/8] Choose Avatar & Enter Tower --- .../java/emu/grasscutter/data/GameData.java | 11 ++- .../grasscutter/data/def/TowerFloorData.java | 73 +++++++++++++++++++ .../grasscutter/data/def/TowerLevelData.java | 55 ++++++++++++++ .../game/dungeons/DungeonManager.java | 3 +- .../emu/grasscutter/game/player/Player.java | 11 +++ .../emu/grasscutter/game/player/TeamInfo.java | 5 ++ .../grasscutter/game/player/TeamManager.java | 66 ++++++++++++++--- .../grasscutter/game/tower/TowerManager.java | 40 ++++++++++ .../recv/HandlerTowerEnterLevelReq.java | 21 ++++++ .../recv/HandlerTowerTeamSelectReq.java | 26 +++++++ .../packet/send/PacketTowerAllDataRsp.java | 13 +++- .../packet/send/PacketTowerEnterLevelRsp.java | 22 ++++++ .../packet/send/PacketTowerTeamSelectRsp.java | 17 +++++ 13 files changed, 350 insertions(+), 13 deletions(-) create mode 100644 src/main/java/emu/grasscutter/data/def/TowerFloorData.java create mode 100644 src/main/java/emu/grasscutter/data/def/TowerLevelData.java create mode 100644 src/main/java/emu/grasscutter/game/tower/TowerManager.java create mode 100644 src/main/java/emu/grasscutter/server/packet/recv/HandlerTowerEnterLevelReq.java create mode 100644 src/main/java/emu/grasscutter/server/packet/recv/HandlerTowerTeamSelectReq.java create mode 100644 src/main/java/emu/grasscutter/server/packet/send/PacketTowerEnterLevelRsp.java create mode 100644 src/main/java/emu/grasscutter/server/packet/send/PacketTowerTeamSelectRsp.java diff --git a/src/main/java/emu/grasscutter/data/GameData.java b/src/main/java/emu/grasscutter/data/GameData.java index 692427496..76a7f1652 100644 --- a/src/main/java/emu/grasscutter/data/GameData.java +++ b/src/main/java/emu/grasscutter/data/GameData.java @@ -68,7 +68,9 @@ public class GameData { private static final Int2ObjectMap shopGoodsDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap combineDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap rewardPreviewDataMap = new Int2ObjectOpenHashMap<>(); - + private static final Int2ObjectMap towerFloorDataMap = new Int2ObjectOpenHashMap<>(); + private static final Int2ObjectMap towerLevelDataMap = new Int2ObjectOpenHashMap<>(); + // Cache private static Map> fetters = new HashMap<>(); private static Map> shopGoods = new HashMap<>(); @@ -311,4 +313,11 @@ public class GameData { public static Int2ObjectMap getCombineDataMap() { return combineDataMap; } + + public static Int2ObjectMap getTowerFloorDataMap(){ + return towerFloorDataMap; + } + public static Int2ObjectMap getTowerLevelDataMap(){ + return towerLevelDataMap; + } } diff --git a/src/main/java/emu/grasscutter/data/def/TowerFloorData.java b/src/main/java/emu/grasscutter/data/def/TowerFloorData.java new file mode 100644 index 000000000..d9d0082c7 --- /dev/null +++ b/src/main/java/emu/grasscutter/data/def/TowerFloorData.java @@ -0,0 +1,73 @@ +package emu.grasscutter.data.def; + +import emu.grasscutter.data.GameResource; +import emu.grasscutter.data.ResourceType; + +@ResourceType(name = "TowerFloorExcelConfigData.json") +public class TowerFloorData extends GameResource { + + private int FloorId; + private int FloorIndex; + private int LevelId; + private int OverrideMonsterLevel; + private int TeamNum; + private int FloorLevelConfigId; + + @Override + public int getId() { + return this.FloorId; + } + + @Override + public void onLoad() { + super.onLoad(); + } + + public int getFloorId() { + return FloorId; + } + + public void setFloorId(int floorId) { + FloorId = floorId; + } + + public int getFloorIndex() { + return FloorIndex; + } + + public void setFloorIndex(int floorIndex) { + FloorIndex = floorIndex; + } + + public int getLevelId() { + return LevelId; + } + + public void setLevelId(int levelId) { + LevelId = levelId; + } + + public int getOverrideMonsterLevel() { + return OverrideMonsterLevel; + } + + public void setOverrideMonsterLevel(int overrideMonsterLevel) { + OverrideMonsterLevel = overrideMonsterLevel; + } + + public int getTeamNum() { + return TeamNum; + } + + public void setTeamNum(int teamNum) { + TeamNum = teamNum; + } + + public int getFloorLevelConfigId() { + return FloorLevelConfigId; + } + + public void setFloorLevelConfigId(int floorLevelConfigId) { + FloorLevelConfigId = floorLevelConfigId; + } +} diff --git a/src/main/java/emu/grasscutter/data/def/TowerLevelData.java b/src/main/java/emu/grasscutter/data/def/TowerLevelData.java new file mode 100644 index 000000000..6cc45cc06 --- /dev/null +++ b/src/main/java/emu/grasscutter/data/def/TowerLevelData.java @@ -0,0 +1,55 @@ +package emu.grasscutter.data.def; + +import emu.grasscutter.data.GameResource; +import emu.grasscutter.data.ResourceType; + +@ResourceType(name = "TowerLevelExcelConfigData.json") +public class TowerLevelData extends GameResource { + + private int ID; + private int LevelId; + private int LevelIndex; + private int DungeonId; + + @Override + public int getId() { + return this.ID; + } + + @Override + public void onLoad() { + super.onLoad(); + } + + public int getID() { + return ID; + } + + public void setID(int ID) { + this.ID = ID; + } + + public int getLevelId() { + return LevelId; + } + + public void setLevelId(int levelId) { + LevelId = levelId; + } + + public int getLevelIndex() { + return LevelIndex; + } + + public void setLevelIndex(int levelIndex) { + LevelIndex = levelIndex; + } + + public int getDungeonId() { + return DungeonId; + } + + public void setDungeonId(int dungeonId) { + DungeonId = dungeonId; + } +} diff --git a/src/main/java/emu/grasscutter/game/dungeons/DungeonManager.java b/src/main/java/emu/grasscutter/game/dungeons/DungeonManager.java index c84ef8a22..e858decf8 100644 --- a/src/main/java/emu/grasscutter/game/dungeons/DungeonManager.java +++ b/src/main/java/emu/grasscutter/game/dungeons/DungeonManager.java @@ -75,7 +75,8 @@ public class DungeonManager { prevPos.set(entry.getPointData().getTranPos()); } } - + // clean temp team if it has + player.getTeamManager().cleanTemporaryTeam(); // Transfer player back to world player.getWorld().transferPlayerToScene(player, prevScene, prevPos); player.sendPacket(new BasePacket(PacketOpcodes.PlayerQuitDungeonRsp)); diff --git a/src/main/java/emu/grasscutter/game/player/Player.java b/src/main/java/emu/grasscutter/game/player/Player.java index f6ea68dc6..d864f9c34 100644 --- a/src/main/java/emu/grasscutter/game/player/Player.java +++ b/src/main/java/emu/grasscutter/game/player/Player.java @@ -27,6 +27,7 @@ import emu.grasscutter.game.props.EntityType; import emu.grasscutter.game.props.PlayerProperty; import emu.grasscutter.game.shop.ShopLimit; import emu.grasscutter.game.managers.MapMarkManager.*; +import emu.grasscutter.game.tower.TowerManager; import emu.grasscutter.game.world.Scene; import emu.grasscutter.game.world.World; import emu.grasscutter.net.packet.BasePacket; @@ -89,6 +90,8 @@ public class Player { @Transient private MessageHandler messageHandler; private TeamManager teamManager; + + private TowerManager towerManager; private PlayerGachaInfo gachaInfo; private PlayerProfile playerProfile; private boolean showAvatar; @@ -176,6 +179,7 @@ public class Player { this.nickname = "Traveler"; this.signature = ""; this.teamManager = new TeamManager(this); + this.towerManager = new TowerManager(this); this.birthday = new PlayerBirthday(); this.setProperty(PlayerProperty.PROP_PLAYER_LEVEL, 1); this.setProperty(PlayerProperty.PROP_IS_SPRING_AUTO_USE, 1); @@ -389,6 +393,10 @@ public class Player { return this.teamManager; } + public TowerManager getTowerManager() { + return towerManager; + } + public PlayerGachaInfo getGachaInfo() { return gachaInfo; } @@ -1030,6 +1038,9 @@ public class Player { if (this.getProfile().getUid() == 0) { this.getProfile().syncWithCharacter(this); } + if (this.getTowerManager() == null) { + this.towerManager = new TowerManager(this); + } // Check if player object exists in server // TODO - optimize diff --git a/src/main/java/emu/grasscutter/game/player/TeamInfo.java b/src/main/java/emu/grasscutter/game/player/TeamInfo.java index 5c66f1aaa..5794a7913 100644 --- a/src/main/java/emu/grasscutter/game/player/TeamInfo.java +++ b/src/main/java/emu/grasscutter/game/player/TeamInfo.java @@ -18,6 +18,11 @@ public class TeamInfo { this.avatars = new ArrayList<>(Grasscutter.getConfig().getGameServerOptions().MaxAvatarsInTeam); } + public TeamInfo(List avatars) { + this.name = ""; + this.avatars = avatars; + } + public String getName() { return name; } diff --git a/src/main/java/emu/grasscutter/game/player/TeamManager.java b/src/main/java/emu/grasscutter/game/player/TeamManager.java index ff9a3c68d..30418993e 100644 --- a/src/main/java/emu/grasscutter/game/player/TeamManager.java +++ b/src/main/java/emu/grasscutter/game/player/TeamManager.java @@ -1,12 +1,6 @@ package emu.grasscutter.game.player; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; import dev.morphia.annotations.Entity; import dev.morphia.annotations.Transient; @@ -59,7 +53,13 @@ public class TeamManager { @Transient private final Set gadgets; @Transient private final IntSet teamResonances; @Transient private final IntSet teamResonancesConfig; - + + private int useTemporarilyTeamIndex = -1; + /** + * Temporary Team for tower + */ + private List temporaryTeam; + public TeamManager() { this.mpTeam = new TeamInfo(); this.avatars = new ArrayList<>(); @@ -125,6 +125,10 @@ public class TeamManager { } public TeamInfo getCurrentTeamInfo() { + if (useTemporarilyTeamIndex >= 0 && + useTemporarilyTeamIndex < temporaryTeam.size()){ + return temporaryTeam.get(useTemporarilyTeamIndex); + } if (this.getPlayer().isInMultiplayer()) { return this.getMpTeam(); } @@ -352,7 +356,51 @@ public class TeamManager { // Packet this.updateTeamEntities(new PacketChangeMpTeamAvatarRsp(getPlayer(), teamInfo)); } - + + public void setupTemporaryTeam(List> guidList) { + var team = guidList.stream().map(list -> { + // Sanity checks + if (list.size() == 0 || list.size() > getMaxTeamSize()) { + return null; + } + + // Set team data + LinkedHashSet newTeam = new LinkedHashSet<>(); + for (int i = 0; i < list.size(); i++) { + Avatar avatar = getPlayer().getAvatars().getAvatarByGuid(list.get(i)); + if (avatar == null || newTeam.contains(avatar)) { + // Should never happen + return null; + } + newTeam.add(avatar); + } + + // convert to avatar ids + return newTeam.stream() + .map(Avatar::getAvatarId) + .toList(); + }) + .filter(Objects::nonNull) + .map(TeamInfo::new) + .toList(); + this.temporaryTeam = team; + } + + public void useTemporaryTeam(int index) { + this.useTemporarilyTeamIndex = index; + updateTeamEntities(null); + } + + public void cleanTemporaryTeam() { + // check if using temporary team + if(useTemporarilyTeamIndex < 0){ + return; + } + + this.useTemporarilyTeamIndex = -1; + this.temporaryTeam = null; + updateTeamEntities(null); + } public synchronized void setCurrentTeam(int teamId) { // if (getPlayer().isInMultiplayer()) { diff --git a/src/main/java/emu/grasscutter/game/tower/TowerManager.java b/src/main/java/emu/grasscutter/game/tower/TowerManager.java new file mode 100644 index 000000000..e49a15cc2 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/tower/TowerManager.java @@ -0,0 +1,40 @@ +package emu.grasscutter.game.tower; + +import dev.morphia.annotations.Entity; +import emu.grasscutter.data.GameData; +import emu.grasscutter.game.player.Player; +import emu.grasscutter.server.packet.send.PacketTowerEnterLevelRsp; + +import java.util.List; + +@Entity +public class TowerManager { + private final Player player; + + public TowerManager(Player player) { + this.player = player; + } + private int currentLevel; + private int currentFloor; + + public void teamSelect(int floor, List> towerTeams) { + var floorData = GameData.getTowerFloorDataMap().get(floor); + + this.currentFloor = floorData.getFloorId(); + this.currentLevel = floorData.getLevelId(); + + player.getTeamManager().setupTemporaryTeam(towerTeams); + } + + + public void enterLevel(int enterPointId) { + var levelData = GameData.getTowerLevelDataMap().get(currentLevel); + var id = levelData.getDungeonId(); + // use team user choose + player.getTeamManager().useTemporaryTeam(0); + player.getServer().getDungeonManager() + .enterDungeon(player, enterPointId, id); + + player.getSession().send(new PacketTowerEnterLevelRsp(currentFloor, currentLevel)); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerTowerEnterLevelReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerTowerEnterLevelReq.java new file mode 100644 index 000000000..163f101ed --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerTowerEnterLevelReq.java @@ -0,0 +1,21 @@ +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.TowerEnterLevelReqOuterClass.TowerEnterLevelReq; +import emu.grasscutter.server.game.GameSession; + +@Opcodes(PacketOpcodes.TowerEnterLevelReq) +public class HandlerTowerEnterLevelReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + TowerEnterLevelReq req = TowerEnterLevelReq.parseFrom(payload); + + //session.send(new PacketTowerCurLevelRecordChangeNotify()); + session.getPlayer().getTowerManager().enterLevel(req.getEnterPointId()); + + //session.send(new PacketTowerLevelStarCondNotify()); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerTowerTeamSelectReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerTowerTeamSelectReq.java new file mode 100644 index 000000000..6e6705379 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerTowerTeamSelectReq.java @@ -0,0 +1,26 @@ +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.TowerTeamOuterClass; +import emu.grasscutter.net.proto.TowerTeamSelectReqOuterClass.TowerTeamSelectReq; +import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.packet.send.PacketTowerTeamSelectRsp; + +@Opcodes(PacketOpcodes.TowerTeamSelectReq) +public class HandlerTowerTeamSelectReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + TowerTeamSelectReq req = TowerTeamSelectReq.parseFrom(payload); + + var towerTeams = req.getTowerTeamListList().stream() + .map(TowerTeamOuterClass.TowerTeam::getAvatarGuidListList) + .toList(); + + session.getPlayer().getTowerManager().teamSelect(req.getFloorId(), towerTeams); + + session.send(new PacketTowerTeamSelectRsp()); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketTowerAllDataRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketTowerAllDataRsp.java index 2bd1d0171..d2d2376e6 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketTowerAllDataRsp.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketTowerAllDataRsp.java @@ -1,19 +1,28 @@ package emu.grasscutter.server.packet.send; +import emu.grasscutter.data.GameData; +import emu.grasscutter.data.def.TowerFloorData; import emu.grasscutter.net.packet.BasePacket; import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.proto.TowerAllDataRspOuterClass.TowerAllDataRsp; import emu.grasscutter.net.proto.TowerCurLevelRecordOuterClass.TowerCurLevelRecord; import emu.grasscutter.net.proto.TowerFloorRecordOuterClass.TowerFloorRecord; +import java.util.stream.Collectors; + public class PacketTowerAllDataRsp extends BasePacket { public PacketTowerAllDataRsp() { super(PacketOpcodes.TowerAllDataRsp); - + + var list = GameData.getTowerFloorDataMap().values().stream() + .map(TowerFloorData::getFloorId) + .map(id -> TowerFloorRecord.newBuilder().setFloorId(id).build()) + .collect(Collectors.toList()); + TowerAllDataRsp proto = TowerAllDataRsp.newBuilder() .setTowerScheduleId(29) - .addTowerFloorRecordList(TowerFloorRecord.newBuilder().setFloorId(1001)) + .addAllTowerFloorRecordList(list) .setCurLevelRecord(TowerCurLevelRecord.newBuilder().setIsEmpty(true)) .setNextScheduleChangeTime(Integer.MAX_VALUE) .putFloorOpenTimeMap(1024, 1630486800) diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketTowerEnterLevelRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketTowerEnterLevelRsp.java new file mode 100644 index 000000000..ebb8fb2b2 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketTowerEnterLevelRsp.java @@ -0,0 +1,22 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.TowerEnterLevelRspOuterClass.TowerEnterLevelRsp; + +public class PacketTowerEnterLevelRsp extends BasePacket { + + public PacketTowerEnterLevelRsp(int floorId, int levelIndex) { + super(PacketOpcodes.TowerEnterLevelRsp); + + TowerEnterLevelRsp proto = TowerEnterLevelRsp.newBuilder() + .setFloorId(floorId) + .setLevelIndex(levelIndex) + .addTowerBuffIdList(4) + .addTowerBuffIdList(28) + .addTowerBuffIdList(18) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketTowerTeamSelectRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketTowerTeamSelectRsp.java new file mode 100644 index 000000000..445b707cd --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketTowerTeamSelectRsp.java @@ -0,0 +1,17 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.TowerTeamSelectRspOuterClass.TowerTeamSelectRsp; + +public class PacketTowerTeamSelectRsp extends BasePacket { + + public PacketTowerTeamSelectRsp() { + super(PacketOpcodes.TowerTeamSelectRsp); + + TowerTeamSelectRsp proto = TowerTeamSelectRsp.newBuilder() + .build(); + + this.setData(proto); + } +} From 2ad6f5934b89afc78d3f9d0c36d961027b9b1387 Mon Sep 17 00:00:00 2001 From: Akka <104902222+Akka0@users.noreply.github.com> Date: Fri, 6 May 2022 14:46:10 +0800 Subject: [PATCH 2/8] Add @Transient for temporary team --- src/main/java/emu/grasscutter/game/player/TeamManager.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/emu/grasscutter/game/player/TeamManager.java b/src/main/java/emu/grasscutter/game/player/TeamManager.java index 30418993e..16e8942ad 100644 --- a/src/main/java/emu/grasscutter/game/player/TeamManager.java +++ b/src/main/java/emu/grasscutter/game/player/TeamManager.java @@ -54,11 +54,11 @@ public class TeamManager { @Transient private final IntSet teamResonances; @Transient private final IntSet teamResonancesConfig; - private int useTemporarilyTeamIndex = -1; + @Transient private int useTemporarilyTeamIndex = -1; /** * Temporary Team for tower */ - private List temporaryTeam; + @Transient private List temporaryTeam; public TeamManager() { this.mpTeam = new TeamInfo(); From 198214ec53e56b7d4ae4749d39af6e941aeb4c6f Mon Sep 17 00:00:00 2001 From: gentlespoon Date: Fri, 6 May 2022 00:05:38 -0700 Subject: [PATCH 3/8] Implementes auto HP recovery at the statues. - Respects player setting. - SP + MP. - Statue has unlimited HP volume (to be updated) --- .../managers/SotSManager/SotSManager.java | 118 ++++++++++++++++++ .../emu/grasscutter/game/player/Player.java | 7 ++ .../HandlerEnterTransPointRegionNotify.java | 28 ++--- 3 files changed, 132 insertions(+), 21 deletions(-) create mode 100644 src/main/java/emu/grasscutter/game/managers/SotSManager/SotSManager.java diff --git a/src/main/java/emu/grasscutter/game/managers/SotSManager/SotSManager.java b/src/main/java/emu/grasscutter/game/managers/SotSManager/SotSManager.java new file mode 100644 index 000000000..b734f5908 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/managers/SotSManager/SotSManager.java @@ -0,0 +1,118 @@ +package emu.grasscutter.game.managers.SotSManager; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.game.avatar.Avatar; +import emu.grasscutter.game.player.Player; +import emu.grasscutter.game.props.FightProperty; +import emu.grasscutter.game.props.PlayerProperty; +import emu.grasscutter.net.proto.ChangeHpReasonOuterClass; +import emu.grasscutter.net.proto.PropChangeReasonOuterClass; +import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.packet.send.PacketAvatarFightPropUpdateNotify; +import emu.grasscutter.server.packet.send.PacketAvatarLifeStateChangeNotify; +import emu.grasscutter.server.packet.send.PacketEntityFightPropChangeReasonNotify; +import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify; + +import java.util.List; + +// Statue of the Seven Manager +public class SotSManager { + + private final Player player; + + public SotSManager(Player player) { + this.player = player; + } + + public boolean getIsAutoRecoveryEnabled() { + return player.getProperty(PlayerProperty.PROP_IS_SPRING_AUTO_USE) == 1; + } + + public void setIsAutoRecoveryEnabled(boolean enabled) { + player.setProperty(PlayerProperty.PROP_IS_SPRING_AUTO_USE, enabled ? 1 : 0); + } + + public int getAutoRecoveryPercentage() { + return player.getProperty(PlayerProperty.PROP_SPRING_AUTO_USE_PERCENT); + } + + public void setAutoRecoveryPercentage(int percentage) { + player.setProperty(PlayerProperty.PROP_SPRING_AUTO_USE_PERCENT, percentage); + } + + // autoRevive automatically revives all team members. + public void autoRevive(GameSession session) { + player.getTeamManager().getActiveTeam().forEach(entity -> { + boolean isAlive = entity.isAlive(); + if (!isAlive) { + float maxHP = entity.getAvatar().getFightProperty(FightProperty.FIGHT_PROP_MAX_HP); + entity.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, Math.min(150, maxHP)); + entity.getWorld().broadcastPacket(new PacketAvatarLifeStateChangeNotify(entity.getAvatar())); + } + }); + } + + public void scheduleAutoRecover(GameSession session) { + // TODO: play audio effects? possibly client side? - client automatically plays. + // delay 2.5 seconds + new Thread(() -> { + try { + Thread.sleep(2500); + autoRecover(session); + } catch (Exception e) { + Grasscutter.getLogger().error(e.getMessage()); + } + }).start(); + } + + // autoRecover checks player setting to see if auto recover is enabled, and refill HP to the predefined level. + public void autoRecover(GameSession session) { + // TODO: Implement SotS Spring volume refill over time. + // Placeholder: + player.setProperty(PlayerProperty.PROP_MAX_SPRING_VOLUME, 8500000); + player.setProperty(PlayerProperty.PROP_CUR_SPRING_VOLUME, 8500000); + + // TODO: In MP, respect SotS settings from the host. + boolean isAutoRecoveryEnabled = getIsAutoRecoveryEnabled(); + int autoRecoverPercentage = getAutoRecoveryPercentage(); + Grasscutter.getLogger().warn("isAutoRecoveryEnabled: " + isAutoRecoveryEnabled + "\tautoRecoverPercentage: " + autoRecoverPercentage); + + if (isAutoRecoveryEnabled) { + player.getTeamManager().getActiveTeam().forEach(entity -> { + float maxHP = entity.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP); + float currentHP = entity.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP); + if (currentHP == maxHP) { + return; + } + float targetHP = maxHP * autoRecoverPercentage / 100; + + if (targetHP > currentHP) { + float needHP = targetHP - currentHP; + + int sotsHPBalance = player.getProperty(PlayerProperty.PROP_CUR_SPRING_VOLUME); + if (sotsHPBalance >= needHP) { + sotsHPBalance -= needHP; + player.setProperty(PlayerProperty.PROP_CUR_SPRING_VOLUME, sotsHPBalance); + + float newHP = currentHP + needHP; + + session.send(new PacketEntityFightPropUpdateNotify(entity, FightProperty.FIGHT_PROP_MAX_HP)); + session.send(new PacketEntityFightPropChangeReasonNotify(entity, FightProperty.FIGHT_PROP_CUR_HP, + newHP, List.of(3), PropChangeReasonOuterClass.PropChangeReason.PROP_CHANGE_STATUE_RECOVER, + ChangeHpReasonOuterClass.ChangeHpReason.ChangeHpAddStatue)); + entity.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, newHP); + + Avatar avatar = entity.getAvatar(); + session.send(new PacketAvatarFightPropUpdateNotify(avatar, FightProperty.FIGHT_PROP_CUR_HP)); + avatar.setCurrentHp(newHP); + + player.save(); + } + + } + }); + } + } + + +} diff --git a/src/main/java/emu/grasscutter/game/player/Player.java b/src/main/java/emu/grasscutter/game/player/Player.java index d864f9c34..eb45f1390 100644 --- a/src/main/java/emu/grasscutter/game/player/Player.java +++ b/src/main/java/emu/grasscutter/game/player/Player.java @@ -22,6 +22,7 @@ import emu.grasscutter.game.inventory.Inventory; import emu.grasscutter.game.mail.Mail; import emu.grasscutter.game.mail.MailHandler; import emu.grasscutter.game.managers.MovementManager.MovementManager; +import emu.grasscutter.game.managers.SotSManager.SotSManager; import emu.grasscutter.game.props.ActionReason; import emu.grasscutter.game.props.EntityType; import emu.grasscutter.game.props.PlayerProperty; @@ -89,6 +90,8 @@ public class Player { @Transient private MailHandler mailHandler; @Transient private MessageHandler messageHandler; + @Transient private SotSManager sotsManager; + private TeamManager teamManager; private TowerManager towerManager; @@ -168,6 +171,7 @@ public class Player { this.messageHandler = null; this.mapMarksManager = new MapMarksManager(); this.movementManager = new MovementManager(this); + this.sotsManager = new SotSManager(this); } // On player creation @@ -196,6 +200,7 @@ public class Player { this.messageHandler = null; this.mapMarksManager = new MapMarksManager(); this.movementManager = new MovementManager(this); + this.sotsManager = new SotSManager(this); } public int getUid() { @@ -984,6 +989,8 @@ public class Player { public MovementManager getMovementManager() { return movementManager; } + public SotSManager getSotSManager() { return sotsManager; } + public synchronized void onTick() { // Check ping if (this.getLastPingTime() > System.currentTimeMillis() + 60000) { diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerEnterTransPointRegionNotify.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerEnterTransPointRegionNotify.java index 2c946e1fa..0ec0a844f 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerEnterTransPointRegionNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerEnterTransPointRegionNotify.java @@ -1,5 +1,7 @@ package emu.grasscutter.server.packet.recv; +import emu.grasscutter.game.managers.SotSManager.SotSManager; +import emu.grasscutter.game.player.Player; import emu.grasscutter.game.props.FightProperty; import emu.grasscutter.net.packet.Opcodes; import emu.grasscutter.net.packet.PacketHandler; @@ -18,26 +20,10 @@ import java.util.List; public class HandlerEnterTransPointRegionNotify extends PacketHandler { @Override public void handle(GameSession session, byte[] header, byte[] payload) throws Exception{ - session.getPlayer().getTeamManager().getActiveTeam().forEach(entity -> { - boolean isAlive = entity.isAlive(); - if(entity.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP) != entity.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP)){ - Float hp = entity.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP)-entity.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP); - - session.send(new PacketEntityFightPropUpdateNotify(entity,FightProperty.FIGHT_PROP_MAX_HP)); - - session.send(new PacketEntityFightPropChangeReasonNotify( - entity, FightProperty.FIGHT_PROP_CUR_HP, hp, List.of(3), - PropChangeReason.PROP_CHANGE_STATUE_RECOVER, ChangeHpReason.ChangeHpAddStatue)); - - entity.setFightProperty( - FightProperty.FIGHT_PROP_CUR_HP, - entity.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP) - ); - session.send(new PacketAvatarFightPropUpdateNotify(entity.getAvatar(), FightProperty.FIGHT_PROP_CUR_HP)); - if (!isAlive) { - entity.getWorld().broadcastPacket(new PacketAvatarLifeStateChangeNotify(entity.getAvatar())); - } - } - }); + Player player = session.getPlayer(); + SotSManager sotsManager = player.getSotSManager(); + sotsManager.autoRevive(session); + sotsManager.scheduleAutoRecover(session); + // TODO: allow interaction with the SotS? } } From 221668570e8482df90399f6a0fe9fa1e01d2751b Mon Sep 17 00:00:00 2001 From: gentlespoon Date: Fri, 6 May 2022 00:28:35 -0700 Subject: [PATCH 4/8] fix: lower logging level in SotSManager --- .../emu/grasscutter/game/managers/SotSManager/SotSManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/emu/grasscutter/game/managers/SotSManager/SotSManager.java b/src/main/java/emu/grasscutter/game/managers/SotSManager/SotSManager.java index b734f5908..dec54f686 100644 --- a/src/main/java/emu/grasscutter/game/managers/SotSManager/SotSManager.java +++ b/src/main/java/emu/grasscutter/game/managers/SotSManager/SotSManager.java @@ -75,7 +75,7 @@ public class SotSManager { // TODO: In MP, respect SotS settings from the host. boolean isAutoRecoveryEnabled = getIsAutoRecoveryEnabled(); int autoRecoverPercentage = getAutoRecoveryPercentage(); - Grasscutter.getLogger().warn("isAutoRecoveryEnabled: " + isAutoRecoveryEnabled + "\tautoRecoverPercentage: " + autoRecoverPercentage); + Grasscutter.getLogger().debug("isAutoRecoveryEnabled: " + isAutoRecoveryEnabled + "\tautoRecoverPercentage: " + autoRecoverPercentage); if (isAutoRecoveryEnabled) { player.getTeamManager().getActiveTeam().forEach(entity -> { From 796201e8b7d1fab07e8aed787f91d2e2d38b0e1c Mon Sep 17 00:00:00 2001 From: Melledy <52122272+Melledy@users.noreply.github.com> Date: Fri, 6 May 2022 01:17:16 -0700 Subject: [PATCH 5/8] Remove the red exclamation mark from achievements --- .../packet/send/PacketTakeAchievementRewardReq.java | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketTakeAchievementRewardReq.java b/src/main/java/emu/grasscutter/server/packet/send/PacketTakeAchievementRewardReq.java index 24135d52c..66049c64c 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketTakeAchievementRewardReq.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketTakeAchievementRewardReq.java @@ -15,14 +15,7 @@ public class PacketTakeAchievementRewardReq extends BasePacket { public PacketTakeAchievementRewardReq(GameSession session) { super(PacketOpcodes.TakeAchievementRewardReq); - List a_list = new ArrayList<>(); - a_list.add(AchievementInfo.newBuilder().setId(82044).setStatusValue(2).setCurrent(0).setGoal(1).build()); - a_list.add(AchievementInfo.newBuilder().setId(81205).setStatusValue(2).setCurrent(0).setGoal(1).build()); - - - TakeAchievementRewardReq proto = TakeAchievementRewardReq.newBuilder() - .addAllAList(a_list) - .build(); + TakeAchievementRewardReq proto = TakeAchievementRewardReq.newBuilder().build(); this.setData(proto); } From b760fb1b264f6ccfdbc4fd0e058361f435bd5e33 Mon Sep 17 00:00:00 2001 From: Melledy <52122272+Melledy@users.noreply.github.com> Date: Fri, 6 May 2022 01:19:39 -0700 Subject: [PATCH 6/8] Fix morphia error when saving player to db --- src/main/java/emu/grasscutter/game/tower/TowerManager.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/emu/grasscutter/game/tower/TowerManager.java b/src/main/java/emu/grasscutter/game/tower/TowerManager.java index e49a15cc2..3b45785dd 100644 --- a/src/main/java/emu/grasscutter/game/tower/TowerManager.java +++ b/src/main/java/emu/grasscutter/game/tower/TowerManager.java @@ -1,6 +1,7 @@ package emu.grasscutter.game.tower; import dev.morphia.annotations.Entity; +import dev.morphia.annotations.Transient; import emu.grasscutter.data.GameData; import emu.grasscutter.game.player.Player; import emu.grasscutter.server.packet.send.PacketTowerEnterLevelRsp; @@ -9,11 +10,12 @@ import java.util.List; @Entity public class TowerManager { - private final Player player; + @Transient private final Player player; public TowerManager(Player player) { this.player = player; } + private int currentLevel; private int currentFloor; From 2e68578525b98eb8c4878357cb89be113c5b7fce Mon Sep 17 00:00:00 2001 From: gentlespoon Date: Fri, 6 May 2022 02:23:10 -0700 Subject: [PATCH 7/8] The statues will now automatically regen their HP volume over time. Max is currently set to 85000 for everyone. Will update after implementing statue levels. --- .../managers/SotSManager/SotSManager.java | 79 +++++++++++++------ .../emu/grasscutter/game/player/Player.java | 10 +++ .../HandlerEnterTransPointRegionNotify.java | 2 + 3 files changed, 66 insertions(+), 25 deletions(-) diff --git a/src/main/java/emu/grasscutter/game/managers/SotSManager/SotSManager.java b/src/main/java/emu/grasscutter/game/managers/SotSManager/SotSManager.java index dec54f686..4edad231e 100644 --- a/src/main/java/emu/grasscutter/game/managers/SotSManager/SotSManager.java +++ b/src/main/java/emu/grasscutter/game/managers/SotSManager/SotSManager.java @@ -18,6 +18,8 @@ import java.util.List; // Statue of the Seven Manager public class SotSManager { + // NOTE: Spring volume balance *1 = fight prop HP *100 + private final Player player; public SotSManager(Player player) { @@ -46,7 +48,8 @@ public class SotSManager { boolean isAlive = entity.isAlive(); if (!isAlive) { float maxHP = entity.getAvatar().getFightProperty(FightProperty.FIGHT_PROP_MAX_HP); - entity.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, Math.min(150, maxHP)); + float newHP = (float)(maxHP * 0.3); + entity.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, newHP); entity.getWorld().broadcastPacket(new PacketAvatarLifeStateChangeNotify(entity.getAvatar())); } }); @@ -65,14 +68,31 @@ public class SotSManager { }).start(); } + public void refillSpringVolume() { + // TODO: max spring volume depends on level of the statues in Mondstadt and Liyue. + // https://genshin-impact.fandom.com/wiki/Statue_of_The_Seven#:~:text=region%20of%20Inazuma.-,Statue%20Levels,-Upon%20first%20unlocking + player.setProperty(PlayerProperty.PROP_MAX_SPRING_VOLUME, 8500000); + + long now = System.currentTimeMillis() / 1000; + long secondsSinceLastUsed = now - player.getSpringLastUsed(); + float percentageRefilled = (float)secondsSinceLastUsed / 15 / 100; // 15s = 1% max volume + int maxVolume = player.getProperty(PlayerProperty.PROP_MAX_SPRING_VOLUME); + int currentVolume = player.getProperty(PlayerProperty.PROP_CUR_SPRING_VOLUME); + if (currentVolume < maxVolume) { + int volumeRefilled = (int)(percentageRefilled * maxVolume); + int newVolume = currentVolume + volumeRefilled; + if (currentVolume + volumeRefilled > maxVolume) { + newVolume = maxVolume; + } + player.setProperty(PlayerProperty.PROP_CUR_SPRING_VOLUME, newVolume); + } + player.setSpringLastUsed(now); + player.save(); + } + // autoRecover checks player setting to see if auto recover is enabled, and refill HP to the predefined level. public void autoRecover(GameSession session) { - // TODO: Implement SotS Spring volume refill over time. - // Placeholder: - player.setProperty(PlayerProperty.PROP_MAX_SPRING_VOLUME, 8500000); - player.setProperty(PlayerProperty.PROP_CUR_SPRING_VOLUME, 8500000); - - // TODO: In MP, respect SotS settings from the host. + // TODO: In MP, respect SotS settings from the HOST. boolean isAutoRecoveryEnabled = getIsAutoRecoveryEnabled(); int autoRecoverPercentage = getAutoRecoveryPercentage(); Grasscutter.getLogger().debug("isAutoRecoveryEnabled: " + isAutoRecoveryEnabled + "\tautoRecoverPercentage: " + autoRecoverPercentage); @@ -88,27 +108,36 @@ public class SotSManager { if (targetHP > currentHP) { float needHP = targetHP - currentHP; + float needSV = needHP * 100; // convert HP needed to Spring Volume needed - int sotsHPBalance = player.getProperty(PlayerProperty.PROP_CUR_SPRING_VOLUME); - if (sotsHPBalance >= needHP) { - sotsHPBalance -= needHP; - player.setProperty(PlayerProperty.PROP_CUR_SPRING_VOLUME, sotsHPBalance); - - float newHP = currentHP + needHP; - - session.send(new PacketEntityFightPropUpdateNotify(entity, FightProperty.FIGHT_PROP_MAX_HP)); - session.send(new PacketEntityFightPropChangeReasonNotify(entity, FightProperty.FIGHT_PROP_CUR_HP, - newHP, List.of(3), PropChangeReasonOuterClass.PropChangeReason.PROP_CHANGE_STATUE_RECOVER, - ChangeHpReasonOuterClass.ChangeHpReason.ChangeHpAddStatue)); - entity.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, newHP); - - Avatar avatar = entity.getAvatar(); - session.send(new PacketAvatarFightPropUpdateNotify(avatar, FightProperty.FIGHT_PROP_CUR_HP)); - avatar.setCurrentHp(newHP); - - player.save(); + int sotsSVBalance = player.getProperty(PlayerProperty.PROP_CUR_SPRING_VOLUME); + if (sotsSVBalance >= needSV) { + // sufficient + sotsSVBalance -= needSV; + } else { + // insufficient balance + needSV = sotsSVBalance; + sotsSVBalance = 0; } + player.setProperty(PlayerProperty.PROP_CUR_SPRING_VOLUME, sotsSVBalance); + player.setSpringLastUsed(System.currentTimeMillis() / 1000); + float newHP = currentHP + needSV / 100; // convert SV to HP + + // TODO: Figure out why client shows current HP instead of added HP. + // Say an avatar had 12000 and now has 14000, it should show "2000". + // The client always show "+14000" which is incorrect. + + entity.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, newHP); + session.send(new PacketEntityFightPropChangeReasonNotify(entity, FightProperty.FIGHT_PROP_CUR_HP, + newHP, List.of(3), PropChangeReasonOuterClass.PropChangeReason.PROP_CHANGE_STATUE_RECOVER, + ChangeHpReasonOuterClass.ChangeHpReason.ChangeHpAddStatue)); + session.send(new PacketEntityFightPropUpdateNotify(entity, FightProperty.FIGHT_PROP_CUR_HP)); + + Avatar avatar = entity.getAvatar(); + avatar.setCurrentHp(newHP); + session.send(new PacketAvatarFightPropUpdateNotify(avatar, FightProperty.FIGHT_PROP_CUR_HP)); + player.save(); } }); } diff --git a/src/main/java/emu/grasscutter/game/player/Player.java b/src/main/java/emu/grasscutter/game/player/Player.java index eb45f1390..b93baccf8 100644 --- a/src/main/java/emu/grasscutter/game/player/Player.java +++ b/src/main/java/emu/grasscutter/game/player/Player.java @@ -130,6 +130,8 @@ public class Player { private MapMarksManager mapMarksManager; @Transient private MovementManager movementManager; + private long springLastUsed; + @Deprecated @SuppressWarnings({"rawtypes", "unchecked"}) // Morphia only! @@ -535,6 +537,14 @@ public class Player { } } + public long getSpringLastUsed() { + return springLastUsed; + } + + public void setSpringLastUsed(long val) { + springLastUsed = val; + } + public SceneLoadState getSceneLoadState() { return sceneState; } diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerEnterTransPointRegionNotify.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerEnterTransPointRegionNotify.java index 0ec0a844f..5591607fe 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerEnterTransPointRegionNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerEnterTransPointRegionNotify.java @@ -22,6 +22,8 @@ public class HandlerEnterTransPointRegionNotify extends PacketHandler { public void handle(GameSession session, byte[] header, byte[] payload) throws Exception{ Player player = session.getPlayer(); SotSManager sotsManager = player.getSotSManager(); + + sotsManager.refillSpringVolume(); sotsManager.autoRevive(session); sotsManager.scheduleAutoRecover(session); // TODO: allow interaction with the SotS? From 3ede7523b0aa20318e464255bcca14b77d564379 Mon Sep 17 00:00:00 2001 From: 4Benj_ Date: Fri, 6 May 2022 21:48:16 +0800 Subject: [PATCH 8/8] Stop WindSeedClientNotify and PlayerLuaShellNotify from being sent (#582) --- src/main/java/emu/grasscutter/net/packet/PacketOpcodes.java | 5 +++++ src/main/java/emu/grasscutter/server/game/GameSession.java | 6 ++++++ 2 files changed, 11 insertions(+) diff --git a/src/main/java/emu/grasscutter/net/packet/PacketOpcodes.java b/src/main/java/emu/grasscutter/net/packet/PacketOpcodes.java index 4d9eb57e8..8e77504d6 100644 --- a/src/main/java/emu/grasscutter/net/packet/PacketOpcodes.java +++ b/src/main/java/emu/grasscutter/net/packet/PacketOpcodes.java @@ -1,5 +1,8 @@ package emu.grasscutter.net.packet; +import java.util.Arrays; +import java.util.List; + public class PacketOpcodes { // Empty public static final int NONE = 0; @@ -1566,4 +1569,6 @@ public class PacketOpcodes { public static final int UNKNOWN_43 = 8877; public static final int UNKNOWN_44 = 8983; public static final int UNKNOWN_45 = 943; + + public static final List BANNED_PACKETS = Arrays.asList(PacketOpcodes.WindSeedClientNotify, PacketOpcodes.PlayerLuaShellNotify); } diff --git a/src/main/java/emu/grasscutter/server/game/GameSession.java b/src/main/java/emu/grasscutter/server/game/GameSession.java index ff024b03b..b984baa0e 100644 --- a/src/main/java/emu/grasscutter/server/game/GameSession.java +++ b/src/main/java/emu/grasscutter/server/game/GameSession.java @@ -157,6 +157,12 @@ public class GameSession extends KcpChannel { Grasscutter.getLogger().warn("Tried to send packet with missing cmd id!"); return; } + + // DO NOT REMOVE (unless we find a way to validate code before sending to client which I don't think we can) + // Stop WindSeedClientNotify from being sent for security purposes. + if(PacketOpcodes.BANNED_PACKETS.contains(packet.getOpcode())) { + return; + } // Header if (packet.shouldBuildHeader()) {