From 0f8b2cb192332478b0bf231f5320ef980ff7d266 Mon Sep 17 00:00:00 2001 From: gentlespoon Date: Wed, 11 May 2022 22:13:44 -0700 Subject: [PATCH] Slowly recover HP near statue --- .../game/managers/SotSManager.java | 223 +++++++++--------- .../HandlerEnterTransPointRegionNotify.java | 8 +- .../HandlerExitTransPointRegionNotify.java | 5 +- 3 files changed, 121 insertions(+), 115 deletions(-) diff --git a/src/main/java/emu/grasscutter/game/managers/SotSManager.java b/src/main/java/emu/grasscutter/game/managers/SotSManager.java index 564663662..0004e389a 100644 --- a/src/main/java/emu/grasscutter/game/managers/SotSManager.java +++ b/src/main/java/emu/grasscutter/game/managers/SotSManager.java @@ -1,16 +1,14 @@ package emu.grasscutter.game.managers; +import ch.qos.logback.classic.Logger; import emu.grasscutter.Grasscutter; -import emu.grasscutter.game.avatar.Avatar; import emu.grasscutter.game.entity.EntityAvatar; 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.net.proto.ChangeHpReasonOuterClass.ChangeHpReason; +import emu.grasscutter.net.proto.PropChangeReasonOuterClass.PropChangeReason; 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; @@ -24,7 +22,9 @@ public class SotSManager { // NOTE: Spring volume balance *1 = fight prop HP *100 private final Player player; + private final Logger logger = Grasscutter.getLogger(); private Timer autoRecoverTimer; + private final boolean enablePriorityHealing = false; public final static int GlobalMaximumSpringVolume = 8500000; @@ -38,6 +38,7 @@ public class SotSManager { public void setIsAutoRecoveryEnabled(boolean enabled) { player.setProperty(PlayerProperty.PROP_IS_SPRING_AUTO_USE, enabled ? 1 : 0); + player.save(); } public int getAutoRecoveryPercentage() { @@ -46,49 +47,122 @@ public class SotSManager { public void setAutoRecoveryPercentage(int percentage) { player.setProperty(PlayerProperty.PROP_SPRING_AUTO_USE_PERCENT, percentage); + player.save(); } - // autoRevive automatically revives all team members. - public void autoRevive(GameSession session) { - player.getTeamManager().getActiveTeam().forEach(entity -> { - boolean isAlive = entity.isAlive(); - float currentHP = entity.getAvatar().getFightProperty(FightProperty.FIGHT_PROP_CUR_HP); - float maxHP = entity.getAvatar().getFightProperty(FightProperty.FIGHT_PROP_MAX_HP); -// Grasscutter.getLogger().debug("" + entity.getAvatar().getAvatarData().getName() + "\t" + currentHP + "/" + maxHP + "\t" + (isAlive ? "ALIVE":"DEAD")); - float newHP = (float)(maxHP * 0.3); - if (currentHP < newHP) { - updateAvatarCurHP(session, entity, newHP); - } - if (!isAlive) { - entity.getWorld().broadcastPacket(new PacketAvatarLifeStateChangeNotify(entity.getAvatar())); - } - }); + public long getLastUsed() { + return player.getSpringLastUsed(); } - public void scheduleAutoRecover(GameSession session) { + public void setLastUsed() { + player.setSpringLastUsed(System.currentTimeMillis() / 1000); + player.save(); + } + + public int getMaxVolume() { + return player.getProperty(PlayerProperty.PROP_MAX_SPRING_VOLUME); + } + + public void setMaxVolume(int volume) { + player.setProperty(PlayerProperty.PROP_MAX_SPRING_VOLUME, volume); + player.save(); + } + + public int getCurrentVolume() { + return player.getProperty(PlayerProperty.PROP_CUR_SPRING_VOLUME); + } + + public void setCurrentVolume(int volume) { + player.setProperty(PlayerProperty.PROP_CUR_SPRING_VOLUME, volume); + setLastUsed(); + player.save(); + } + + public void handleEnterTransPointRegionNotify() { + logger.trace("Player entered statue region"); + autoRevive(); if (autoRecoverTimer == null) { autoRecoverTimer = new Timer(); - autoRecoverTimer.schedule(new AutoRecoverTimerTick(session), 2500); + autoRecoverTimer.schedule(new AutoRecoverTimerTick(), 2500, 15000); } } - public void cancelAutoRecover() { + public void handleExitTransPointRegionNotify() { + logger.trace("Player left statue region"); if (autoRecoverTimer != null) { autoRecoverTimer.cancel(); autoRecoverTimer = null; } } - private class AutoRecoverTimerTick extends TimerTask - { - private GameSession session; + // autoRevive automatically revives all team members. + public void autoRevive() { + player.getTeamManager().getActiveTeam().forEach(entity -> { + boolean isAlive = entity.isAlive(); + if (isAlive) { + return; + } + logger.trace("Reviving avatar " + entity.getAvatar().getAvatarData().getName()); + player.getTeamManager().reviveAvatar(entity.getAvatar()); + player.getTeamManager().healAvatar(entity.getAvatar(), 30, 0); + }); + } - public AutoRecoverTimerTick(GameSession session) { - this.session = session; - } + private class AutoRecoverTimerTick extends TimerTask { + // autoRecover checks player setting to see if auto recover is enabled, and refill HP to the predefined level. public void run() { - autoRecover(session); - cancelAutoRecover(); + refillSpringVolume(); + + logger.trace("isAutoRecoveryEnabled: " + getIsAutoRecoveryEnabled() + "\tautoRecoverPercentage: " + getAutoRecoveryPercentage()); + + if (getIsAutoRecoveryEnabled()) { + List activeTeam = player.getTeamManager().getActiveTeam(); + // When the statue does not have enough remaining volume: + // Enhanced experience: Enable priority healing + // The current active character will get healed first, then sequential. + // Vanilla experience: Disable priority healing + // Sequential healing based on character index. + int priorityIndex = enablePriorityHealing ? player.getTeamManager().getCurrentCharacterIndex() : -1; + if (priorityIndex >= 0) { + checkAndHealAvatar(activeTeam.get(priorityIndex)); + } + for (int i = 0; i < activeTeam.size(); i++) { + if (i != priorityIndex) { + checkAndHealAvatar(activeTeam.get(i)); + } + } + } + } + } + + public void checkAndHealAvatar(EntityAvatar entity) { + int maxHP = (int) (entity.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP) * 100); + int currentHP = (int) (entity.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP) * 100); + if (currentHP == maxHP) { + return; + } + int targetHP = maxHP * getAutoRecoveryPercentage() / 100; + + if (targetHP > currentHP) { + int needHP = targetHP - currentHP; + int currentVolume = getCurrentVolume(); + if (currentVolume >= needHP) { + // sufficient + setCurrentVolume(currentVolume - needHP); + } else { + // insufficient balance + needHP = currentVolume; + setCurrentVolume(0); + } + if (needHP > 0) { + logger.trace("Healing avatar " + entity.getAvatar().getAvatarData().getName() + " +" + needHP); + player.getTeamManager().healAvatar(entity.getAvatar(), 0, needHP); + player.getSession().send(new PacketEntityFightPropChangeReasonNotify(entity, FightProperty.FIGHT_PROP_CUR_HP, + ((float) needHP / 100), List.of(3), PropChangeReason.PROP_CHANGE_STATUE_RECOVER, + ChangeHpReason.ChangeHpAddStatue)); + player.getSession().send(new PacketEntityFightPropUpdateNotify(entity, FightProperty.FIGHT_PROP_CUR_HP)); + + } } } @@ -96,84 +170,23 @@ public class SotSManager { // Temporary: Max spring volume depends on level of the statues in Mondstadt and Liyue. Override until we have statue level. // TODO: remove // 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); + setMaxVolume(8500000); // Temporary: Auto enable 100% statue recovery until we can adjust statue settings in game // TODO: remove - player.setProperty(PlayerProperty.PROP_SPRING_AUTO_USE_PERCENT, 100); - player.setProperty(PlayerProperty.PROP_IS_SPRING_AUTO_USE, 1); + setAutoRecoveryPercentage(100); + setIsAutoRecoveryEnabled(true); - 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); + int maxVolume = getMaxVolume(); + int currentVolume = getCurrentVolume(); 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: In MP, respect SotS settings from the HOST. - boolean isAutoRecoveryEnabled = getIsAutoRecoveryEnabled(); - int autoRecoverPercentage = getAutoRecoveryPercentage(); - Grasscutter.getLogger().debug("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; - float needSV = needHP * 100; // convert HP needed to Spring Volume needed - - 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 - - updateAvatarCurHP(session, entity, newHP); - } - }); + long now = System.currentTimeMillis() / 1000; + int secondsSinceLastUsed = (int) (now - getLastUsed()); + // 15s = 1% max volume + int volumeRefilled = secondsSinceLastUsed * maxVolume / 15 / 100; + logger.trace("Statue has refilled HP volume: " + volumeRefilled); + currentVolume = Math.min(currentVolume + volumeRefilled, maxVolume); + logger.trace("Statue remaining HP volume: " + currentVolume); + setCurrentVolume(currentVolume); } } - - private void updateAvatarCurHP(GameSession session, EntityAvatar entity, float newHP) { - // 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/server/packet/recv/HandlerEnterTransPointRegionNotify.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerEnterTransPointRegionNotify.java index 94c9bfd8b..9a5872033 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerEnterTransPointRegionNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerEnterTransPointRegionNotify.java @@ -11,12 +11,6 @@ import emu.grasscutter.server.game.GameSession; public class HandlerEnterTransPointRegionNotify extends PacketHandler { @Override 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? + session.getPlayer().getSotSManager().handleEnterTransPointRegionNotify(); } } diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerExitTransPointRegionNotify.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerExitTransPointRegionNotify.java index 0d35c1762..03fcd4e3c 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerExitTransPointRegionNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerExitTransPointRegionNotify.java @@ -1,5 +1,6 @@ package emu.grasscutter.server.packet.recv; +import emu.grasscutter.Grasscutter; import emu.grasscutter.game.managers.SotSManager; import emu.grasscutter.game.player.Player; import emu.grasscutter.net.packet.Opcodes; @@ -11,8 +12,6 @@ import emu.grasscutter.server.game.GameSession; public class HandlerExitTransPointRegionNotify extends PacketHandler { @Override public void handle(GameSession session, byte[] header, byte[] payload) throws Exception{ - Player player = session.getPlayer(); - SotSManager sotsManager = player.getSotSManager(); - sotsManager.cancelAutoRecover(); + session.getPlayer().getSotSManager().handleExitTransPointRegionNotify(); } }