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? } }