Improved satiation (#2055)

* Natural satiation decreasing
Graphic showing satiation when eating (usually)

* Reworking values to match original

* Little fixes

* Satiation bar matches real values
Revival correctly updates bar

* Greatly simplify handling
Some fixes

* Inline variables
Add TODO for bug

* Satiation works correctly
Finally it all works as intended

* Remove unnecessary packets

* Improve satiation reduction handling
This commit is contained in:
Thoronium
2023-02-15 17:32:59 -07:00
committed by GitHub
parent b4b8f1ec38
commit 1b2210f5a7
10 changed files with 1772 additions and 14 deletions

View File

@@ -84,8 +84,8 @@ public class Avatar {
@Getter @Setter private int level = 1;
@Getter @Setter private int exp;
@Getter @Setter private int promoteLevel;
@Getter @Setter private int satiation; // ?
@Getter @Setter private int satiationPenalty; // ?
@Getter @Setter private int satiation; // Fullness
@Getter @Setter private int satiationPenalty; // When eating too much
@Getter @Setter private float currentHp;
private float currentEnergy;
@@ -204,12 +204,30 @@ public class Avatar {
return 0;
}
public boolean addSatiation(float value) {
if (this.satiation >= 100) return false;
public boolean addSatiation(int value) {
if (this.satiation >= 10000) return false;
this.satiation += value;
return true;
}
public float reduceSatiation(int value) {
if (this.satiation == 0) return 0;
this.satiation -= value;
if(this.satiation < 0) {
this.satiation = 0;
}
return this.satiation;
}
public float reduceSatiationPenalty(int value) {
if (this.satiationPenalty == 0) return 0;
this.satiationPenalty -= value;
if(this.satiationPenalty < 0) {
this.satiationPenalty = 0;
}
return this.satiationPenalty;
}
public GameItem getEquipBySlot(EquipType slot) {
return this.getEquips().get(slot.getValue());
}
@@ -879,8 +897,8 @@ public class Avatar {
avatarInfo.putPropMap(PlayerProperty.PROP_LEVEL.getId(), ProtoHelper.newPropValue(PlayerProperty.PROP_LEVEL, this.getLevel()));
avatarInfo.putPropMap(PlayerProperty.PROP_EXP.getId(), ProtoHelper.newPropValue(PlayerProperty.PROP_EXP, this.getExp()));
avatarInfo.putPropMap(PlayerProperty.PROP_BREAK_LEVEL.getId(), ProtoHelper.newPropValue(PlayerProperty.PROP_BREAK_LEVEL, this.getPromoteLevel()));
avatarInfo.putPropMap(PlayerProperty.PROP_SATIATION_VAL.getId(), ProtoHelper.newPropValue(PlayerProperty.PROP_SATIATION_VAL, 0));
avatarInfo.putPropMap(PlayerProperty.PROP_SATIATION_PENALTY_TIME.getId(), ProtoHelper.newPropValue(PlayerProperty.PROP_SATIATION_PENALTY_TIME, 0));
avatarInfo.putPropMap(PlayerProperty.PROP_SATIATION_VAL.getId(), ProtoHelper.newPropValue(PlayerProperty.PROP_SATIATION_VAL, this.getSatiation()));
avatarInfo.putPropMap(PlayerProperty.PROP_SATIATION_PENALTY_TIME.getId(), ProtoHelper.newPropValue(PlayerProperty.PROP_SATIATION_PENALTY_TIME, this.getSatiationPenalty()));
return avatarInfo.build();
}

View File

@@ -0,0 +1,115 @@
package emu.grasscutter.game.managers;
import java.util.HashMap;
import java.util.Map;
import emu.grasscutter.game.avatar.Avatar;
import emu.grasscutter.game.player.BasePlayerManager;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.PlayerProperty;
import emu.grasscutter.server.packet.send.PacketAvatarSatiationDataNotify;
import emu.grasscutter.server.packet.send.PacketPlayerGameTimeNotify;
import emu.grasscutter.server.packet.send.PacketPlayerTimeNotify;
import emu.grasscutter.server.packet.send.PacketAvatarPropNotify;
public class SatiationManager extends BasePlayerManager {
public SatiationManager(Player player) {
super(player);
}
/********************
* Change satiation
********************/
public synchronized boolean addSatiation(Avatar avatar, float satiationIncrease, int itemId) {
// Satiation is max 10000 but can go over in the case of overeating
Map<Integer, Long> propMap = new HashMap<>();
int satiation = Math.round(satiationIncrease * 100);
float totalSatiation = ((satiationIncrease * 100) + avatar.getSatiation());
// Update client time
updateTime();
// Calculate times
var playerTime = (player.getClientTime() / 1000);
float finishTime = playerTime + (totalSatiation / 30);
// Penalty
long penaltyTime = playerTime;
long penaltyValue = avatar.getSatiationPenalty();
if(totalSatiation + avatar.getSatiation() > 10000 && penaltyValue == 0) {
// Penalty is always 30sec
penaltyTime += 30;
penaltyValue = 3000;
}
// Add satiation
if (!addSatiationDirectly(avatar, satiation)) return false;
propMap.put(PlayerProperty.PROP_SATIATION_VAL.getId(), Long.valueOf(satiation));
propMap.put(PlayerProperty.PROP_SATIATION_PENALTY_TIME.getId(), penaltyValue);
// Send packets
player.getSession().send(new PacketAvatarPropNotify(avatar, propMap));
player.getSession().send(new PacketAvatarSatiationDataNotify(avatar, finishTime, penaltyTime));
return true;
}
public synchronized boolean addSatiationDirectly(Avatar avatar, int value) {
if (!avatar.addSatiation(value))
return false;
// Update avatar
avatar.save();
return true;
}
public synchronized void removeSatiationDirectly(Avatar avatar, int value) {
avatar.reduceSatiation(value);
avatar.reduceSatiationPenalty(3000);
avatar.save();
// Update avatar to no satiation
updateSingleAvatar(avatar, 0);
}
public synchronized void reduceSatiation() {
/* Satiation may not reduce while paused on official but it will here */
// Get all avatars with satiation
player.getAvatars().forEach(avatar -> {
// Ensure avatar isn't stuck in penalty
if (avatar.getSatiationPenalty() > 0 && avatar.getSatiation() == 0) {
avatar.reduceSatiationPenalty(3000);
}
// Reduce satiation
if (avatar.getSatiation() > 0) {
// Reduce penalty first
if (avatar.getSatiationPenalty() > 0) {
// Penalty reduction rate is 1/s
avatar.reduceSatiationPenalty(100);
} else {
// Satiation reduction rate is 0.3/s
avatar.reduceSatiation(30);
// Update all packets every tick else it won't work
// Surely there is a better way to handle this
addSatiation(avatar, 0, 0);
}
}
});
}
/********************
* Player Updates
********************/
public synchronized void updateSingleAvatar(Avatar avatar, float givenTime) {
float time = (player.getClientTime() / 1000) + givenTime;
player.getSession().send(new PacketAvatarPropNotify(avatar));
player.getSession().send(new PacketAvatarSatiationDataNotify(time, avatar));
}
private void updateTime() {
player.getSession().send(new PacketPlayerGameTimeNotify(player));
player.getSession().send(new PacketPlayerTimeNotify(player));
}
}

View File

@@ -29,6 +29,7 @@ import emu.grasscutter.game.managers.cooking.CookingCompoundManager;
import emu.grasscutter.game.managers.cooking.CookingManager;
import emu.grasscutter.game.managers.FurnitureManager;
import emu.grasscutter.game.managers.ResinManager;
import emu.grasscutter.game.managers.SatiationManager;
import emu.grasscutter.game.managers.deforestation.DeforestationManager;
import emu.grasscutter.game.managers.energy.EnergyManager;
import emu.grasscutter.game.managers.forging.ActiveForgeData;
@@ -164,6 +165,7 @@ public class Player {
@Getter private transient ActivityManager activityManager;
@Getter private transient PlayerBuffManager buffManager;
@Getter private transient PlayerProgressManager progressManager;
@Getter private transient SatiationManager satiationManager;
// Manager data (Save-able to the database)
private PlayerProfile playerProfile; // Getter has null-check
@@ -268,6 +270,7 @@ public class Player {
this.furnitureManager = new FurnitureManager(this);
this.cookingManager = new CookingManager(this);
this.cookingCompoundManager=new CookingCompoundManager(this);
this.satiationManager = new SatiationManager(this);
}
// On player creation
@@ -303,6 +306,7 @@ public class Player {
this.furnitureManager = new FurnitureManager(this);
this.cookingManager = new CookingManager(this);
this.cookingCompoundManager=new CookingCompoundManager(this);
this.satiationManager = new SatiationManager(this);
}
public int getUid() {
@@ -1100,6 +1104,9 @@ public class Player {
// Recharge resin.
this.getResinManager().rechargeResin();
// Satiation
this.getSatiationManager().reduceSatiation();
}
private synchronized void doDailyReset() {

View File

@@ -26,6 +26,7 @@ import emu.grasscutter.server.packet.send.PacketAddBackupAvatarTeamRsp;
import emu.grasscutter.server.packet.send.PacketAvatarDieAnimationEndRsp;
import emu.grasscutter.server.packet.send.PacketAvatarFightPropUpdateNotify;
import emu.grasscutter.server.packet.send.PacketAvatarLifeStateChangeNotify;
import emu.grasscutter.server.packet.send.PacketAvatarSatiationDataNotify;
import emu.grasscutter.server.packet.send.PacketAvatarTeamAllDataNotify;
import emu.grasscutter.server.packet.send.PacketAvatarTeamUpdateNotify;
import emu.grasscutter.server.packet.send.PacketChangeAvatarRsp;
@@ -569,6 +570,8 @@ public class TeamManager extends BasePlayerDataManager {
}
entity.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, 1f);
// Satiation is reset when reviving an avatar
player.getSatiationManager().removeSatiationDirectly(entity.getAvatar(), 15000);
this.getPlayer().sendPacket(new PacketAvatarFightPropUpdateNotify(entity.getAvatar(), FightProperty.FIGHT_PROP_CUR_HP));
this.getPlayer().sendPacket(new PacketAvatarLifeStateChangeNotify(entity.getAvatar()));
return true;
@@ -618,6 +621,7 @@ public class TeamManager extends BasePlayerDataManager {
FightProperty.FIGHT_PROP_CUR_HP,
entity.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP) * .4f
);
player.getSatiationManager().removeSatiationDirectly(entity.getAvatar(), 15000);
this.getPlayer().sendPacket(new PacketAvatarFightPropUpdateNotify(entity.getAvatar(), FightProperty.FIGHT_PROP_CUR_HP));
this.getPlayer().sendPacket(new PacketAvatarLifeStateChangeNotify(entity.getAvatar()));
}

View File

@@ -785,7 +785,7 @@ public class InventorySystem extends BaseGameSystem {
if (event.isCanceled()) return false;
float satiationIncrease = satiationParams[0] + ((float)satiationParams[1])/params.targetAvatar.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP);
if (!params.targetAvatar.addSatiation(satiationIncrease)) { // Make sure avatar can eat
if (!params.player.getSatiationManager().addSatiation(params.targetAvatar, satiationIncrease, itemData.getId())) { // Make sure avatar can eat
return false;
}
}

View File

@@ -28,7 +28,13 @@ public class PacketOpcodesUtils {
PacketOpcodes.WorldPlayerRTTNotify,
PacketOpcodes.UnionCmdNotify,
PacketOpcodes.QueryPathReq,
PacketOpcodes.QueryPathRsp
PacketOpcodes.QueryPathRsp,
// Satiation sends these every tick
PacketOpcodes.PlayerTimeNotify,
PacketOpcodes.PlayerGameTimeNotify,
PacketOpcodes.AvatarPropNotify,
PacketOpcodes.AvatarSatiationDataNotify
);
static {

View File

@@ -1,5 +1,7 @@
package emu.grasscutter.server.packet.send;
import java.util.Map;
import emu.grasscutter.game.avatar.Avatar;
import emu.grasscutter.game.props.PlayerProperty;
import emu.grasscutter.net.packet.BasePacket;
@@ -9,19 +11,19 @@ import emu.grasscutter.net.proto.AvatarPropNotifyOuterClass.AvatarPropNotify;
public class PacketAvatarPropNotify extends BasePacket {
public PacketAvatarPropNotify(Avatar avatar) {
super(PacketOpcodes.AvatarPropNotify);
AvatarPropNotify proto = AvatarPropNotify.newBuilder()
.setAvatarGuid(avatar.getGuid())
.putPropMap(PlayerProperty.PROP_LEVEL.getId(), avatar.getLevel())
.putPropMap(PlayerProperty.PROP_EXP.getId(), avatar.getExp())
.putPropMap(PlayerProperty.PROP_BREAK_LEVEL.getId(), avatar.getPromoteLevel())
.putPropMap(PlayerProperty.PROP_SATIATION_VAL.getId(), 0)
.putPropMap(PlayerProperty.PROP_SATIATION_PENALTY_TIME.getId(), 0)
.putPropMap(PlayerProperty.PROP_SATIATION_VAL.getId(), avatar.getSatiation())
.putPropMap(PlayerProperty.PROP_SATIATION_PENALTY_TIME.getId(), avatar.getSatiationPenalty())
.build();
this.setData(proto);
}
public PacketAvatarPropNotify(Avatar avatar, PlayerProperty prop, int value) {
super(PacketOpcodes.AvatarPropNotify);
@@ -29,7 +31,18 @@ public class PacketAvatarPropNotify extends BasePacket {
.setAvatarGuid(avatar.getGuid())
.putPropMap(prop.getId(), value)
.build();
this.setData(proto);
}
public PacketAvatarPropNotify(Avatar avatar, Map<Integer, Long> propMap) {
super(PacketOpcodes.AvatarPropNotify);
AvatarPropNotify proto = AvatarPropNotify.newBuilder()
.setAvatarGuid(avatar.getGuid())
.putAllPropMap(propMap)
.build();
this.setData(proto);
}
}

View File

@@ -0,0 +1,48 @@
package emu.grasscutter.server.packet.send;
import emu.grasscutter.net.packet.BasePacket;
import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.game.avatar.Avatar;
import emu.grasscutter.net.proto.AvatarSatiationDataNotifyOuterClass.AvatarSatiationDataNotify;
import emu.grasscutter.net.proto.AvatarSatiationDataOuterClass.AvatarSatiationData;
public class PacketAvatarSatiationDataNotify extends BasePacket {
public PacketAvatarSatiationDataNotify(Avatar avatar, float finishTime, long penaltyTime) {
super(PacketOpcodes.AvatarSatiationDataNotify);
AvatarSatiationData.Builder avatarSatiation = AvatarSatiationData.newBuilder()
.setAvatarGuid(avatar.getGuid())
.setFinishTime(finishTime);
// Penalty for overeating
if (penaltyTime > 0) {
avatarSatiation.setPenaltyFinishTime(penaltyTime);
}
avatarSatiation.build();
AvatarSatiationDataNotify notify = AvatarSatiationDataNotify.newBuilder()
.addSatiationDataList(0, avatarSatiation)
.build();
this.setData(notify);
}
public PacketAvatarSatiationDataNotify(float time, Avatar avatar) {
super(PacketOpcodes.AvatarSatiationDataNotify);
var avatarSatiation = AvatarSatiationData.newBuilder()
.setAvatarGuid(avatar.getGuid())
.setFinishTime(time + (avatar.getSatiation() / 30f))
// Penalty time always ends before finish time
.setPenaltyFinishTime(time + (avatar.getSatiationPenalty() / 100f))
.build();
AvatarSatiationDataNotify notify = AvatarSatiationDataNotify.newBuilder()
.addSatiationDataList(0, avatarSatiation)
.build();
this.setData(notify);
}
}