Slowly recover HP near statue

This commit is contained in:
gentlespoon 2022-05-11 22:13:44 -07:00 committed by Melledy
parent b14affd6ee
commit 0f8b2cb192
3 changed files with 121 additions and 115 deletions

View File

@ -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;
public AutoRecoverTimerTick(GameSession session) {
this.session = 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);
});
}
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<EntityAvatar> 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();
}
}

View File

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

View File

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