mirror of
https://github.com/Melledy/Grasscutter.git
synced 2024-11-23 19:26:49 +00:00
Slowly recover HP near statue
This commit is contained in:
parent
b14affd6ee
commit
0f8b2cb192
@ -1,16 +1,14 @@
|
|||||||
package emu.grasscutter.game.managers;
|
package emu.grasscutter.game.managers;
|
||||||
|
|
||||||
|
import ch.qos.logback.classic.Logger;
|
||||||
import emu.grasscutter.Grasscutter;
|
import emu.grasscutter.Grasscutter;
|
||||||
import emu.grasscutter.game.avatar.Avatar;
|
|
||||||
import emu.grasscutter.game.entity.EntityAvatar;
|
import emu.grasscutter.game.entity.EntityAvatar;
|
||||||
import emu.grasscutter.game.player.Player;
|
import emu.grasscutter.game.player.Player;
|
||||||
import emu.grasscutter.game.props.FightProperty;
|
import emu.grasscutter.game.props.FightProperty;
|
||||||
import emu.grasscutter.game.props.PlayerProperty;
|
import emu.grasscutter.game.props.PlayerProperty;
|
||||||
import emu.grasscutter.net.proto.ChangeHpReasonOuterClass;
|
import emu.grasscutter.net.proto.ChangeHpReasonOuterClass.ChangeHpReason;
|
||||||
import emu.grasscutter.net.proto.PropChangeReasonOuterClass;
|
import emu.grasscutter.net.proto.PropChangeReasonOuterClass.PropChangeReason;
|
||||||
import emu.grasscutter.server.game.GameSession;
|
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.PacketEntityFightPropChangeReasonNotify;
|
||||||
import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify;
|
import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify;
|
||||||
|
|
||||||
@ -24,7 +22,9 @@ public class SotSManager {
|
|||||||
// NOTE: Spring volume balance *1 = fight prop HP *100
|
// NOTE: Spring volume balance *1 = fight prop HP *100
|
||||||
|
|
||||||
private final Player player;
|
private final Player player;
|
||||||
|
private final Logger logger = Grasscutter.getLogger();
|
||||||
private Timer autoRecoverTimer;
|
private Timer autoRecoverTimer;
|
||||||
|
private final boolean enablePriorityHealing = false;
|
||||||
|
|
||||||
public final static int GlobalMaximumSpringVolume = 8500000;
|
public final static int GlobalMaximumSpringVolume = 8500000;
|
||||||
|
|
||||||
@ -38,6 +38,7 @@ public class SotSManager {
|
|||||||
|
|
||||||
public void setIsAutoRecoveryEnabled(boolean enabled) {
|
public void setIsAutoRecoveryEnabled(boolean enabled) {
|
||||||
player.setProperty(PlayerProperty.PROP_IS_SPRING_AUTO_USE, enabled ? 1 : 0);
|
player.setProperty(PlayerProperty.PROP_IS_SPRING_AUTO_USE, enabled ? 1 : 0);
|
||||||
|
player.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getAutoRecoveryPercentage() {
|
public int getAutoRecoveryPercentage() {
|
||||||
@ -46,49 +47,122 @@ public class SotSManager {
|
|||||||
|
|
||||||
public void setAutoRecoveryPercentage(int percentage) {
|
public void setAutoRecoveryPercentage(int percentage) {
|
||||||
player.setProperty(PlayerProperty.PROP_SPRING_AUTO_USE_PERCENT, percentage);
|
player.setProperty(PlayerProperty.PROP_SPRING_AUTO_USE_PERCENT, percentage);
|
||||||
|
player.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
// autoRevive automatically revives all team members.
|
public long getLastUsed() {
|
||||||
public void autoRevive(GameSession session) {
|
return player.getSpringLastUsed();
|
||||||
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 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) {
|
if (autoRecoverTimer == null) {
|
||||||
autoRecoverTimer = new Timer();
|
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) {
|
if (autoRecoverTimer != null) {
|
||||||
autoRecoverTimer.cancel();
|
autoRecoverTimer.cancel();
|
||||||
autoRecoverTimer = null;
|
autoRecoverTimer = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class AutoRecoverTimerTick extends TimerTask
|
// autoRevive automatically revives all team members.
|
||||||
{
|
public void autoRevive() {
|
||||||
private GameSession session;
|
player.getTeamManager().getActiveTeam().forEach(entity -> {
|
||||||
|
boolean isAlive = entity.isAlive();
|
||||||
public AutoRecoverTimerTick(GameSession session) {
|
if (isAlive) {
|
||||||
this.session = session;
|
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() {
|
public void run() {
|
||||||
autoRecover(session);
|
refillSpringVolume();
|
||||||
cancelAutoRecover();
|
|
||||||
|
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.
|
// Temporary: Max spring volume depends on level of the statues in Mondstadt and Liyue. Override until we have statue level.
|
||||||
// TODO: remove
|
// TODO: remove
|
||||||
// https://genshin-impact.fandom.com/wiki/Statue_of_The_Seven#:~:text=region%20of%20Inazuma.-,Statue%20Levels,-Upon%20first%20unlocking
|
// 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
|
// Temporary: Auto enable 100% statue recovery until we can adjust statue settings in game
|
||||||
// TODO: remove
|
// TODO: remove
|
||||||
player.setProperty(PlayerProperty.PROP_SPRING_AUTO_USE_PERCENT, 100);
|
setAutoRecoveryPercentage(100);
|
||||||
player.setProperty(PlayerProperty.PROP_IS_SPRING_AUTO_USE, 1);
|
setIsAutoRecoveryEnabled(true);
|
||||||
|
|
||||||
long now = System.currentTimeMillis() / 1000;
|
int maxVolume = getMaxVolume();
|
||||||
long secondsSinceLastUsed = now - player.getSpringLastUsed();
|
int currentVolume = getCurrentVolume();
|
||||||
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) {
|
if (currentVolume < maxVolume) {
|
||||||
int volumeRefilled = (int)(percentageRefilled * maxVolume);
|
long now = System.currentTimeMillis() / 1000;
|
||||||
int newVolume = currentVolume + volumeRefilled;
|
int secondsSinceLastUsed = (int) (now - getLastUsed());
|
||||||
if (currentVolume + volumeRefilled > maxVolume) {
|
// 15s = 1% max volume
|
||||||
newVolume = maxVolume;
|
int volumeRefilled = secondsSinceLastUsed * maxVolume / 15 / 100;
|
||||||
}
|
logger.trace("Statue has refilled HP volume: " + volumeRefilled);
|
||||||
player.setProperty(PlayerProperty.PROP_CUR_SPRING_VOLUME, newVolume);
|
currentVolume = Math.min(currentVolume + volumeRefilled, maxVolume);
|
||||||
}
|
logger.trace("Statue remaining HP volume: " + currentVolume);
|
||||||
player.setSpringLastUsed(now);
|
setCurrentVolume(currentVolume);
|
||||||
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);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -11,12 +11,6 @@ import emu.grasscutter.server.game.GameSession;
|
|||||||
public class HandlerEnterTransPointRegionNotify extends PacketHandler {
|
public class HandlerEnterTransPointRegionNotify extends PacketHandler {
|
||||||
@Override
|
@Override
|
||||||
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception{
|
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception{
|
||||||
Player player = session.getPlayer();
|
session.getPlayer().getSotSManager().handleEnterTransPointRegionNotify();
|
||||||
SotSManager sotsManager = player.getSotSManager();
|
|
||||||
|
|
||||||
sotsManager.refillSpringVolume();
|
|
||||||
sotsManager.autoRevive(session);
|
|
||||||
sotsManager.scheduleAutoRecover(session);
|
|
||||||
// TODO: allow interaction with the SotS?
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package emu.grasscutter.server.packet.recv;
|
package emu.grasscutter.server.packet.recv;
|
||||||
|
|
||||||
|
import emu.grasscutter.Grasscutter;
|
||||||
import emu.grasscutter.game.managers.SotSManager;
|
import emu.grasscutter.game.managers.SotSManager;
|
||||||
import emu.grasscutter.game.player.Player;
|
import emu.grasscutter.game.player.Player;
|
||||||
import emu.grasscutter.net.packet.Opcodes;
|
import emu.grasscutter.net.packet.Opcodes;
|
||||||
@ -11,8 +12,6 @@ import emu.grasscutter.server.game.GameSession;
|
|||||||
public class HandlerExitTransPointRegionNotify extends PacketHandler {
|
public class HandlerExitTransPointRegionNotify extends PacketHandler {
|
||||||
@Override
|
@Override
|
||||||
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception{
|
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception{
|
||||||
Player player = session.getPlayer();
|
session.getPlayer().getSotSManager().handleExitTransPointRegionNotify();
|
||||||
SotSManager sotsManager = player.getSotSManager();
|
|
||||||
sotsManager.cancelAutoRecover();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user