From 541ae7cf778a09daa740e681c871eee92efaae86 Mon Sep 17 00:00:00 2001 From: Melledy <121644117+Melledy@users.noreply.github.com> Date: Sun, 12 Nov 2023 17:59:20 -0800 Subject: [PATCH] Implement basic stamina system (Everyone disliked that) --- .../proto/StaminaInfoScNotifyOuterClass.java | 421 ++++++++++++++++++ .../java/emu/lunarcore/GameConstants.java | 1 + .../emu/lunarcore/game/battle/Battle.java | 1 + .../lunarcore/game/battle/BattleService.java | 12 +- .../emu/lunarcore/game/player/Player.java | 57 ++- .../packet/recv/HandlerPlayerLoginCsReq.java | 4 +- .../packet/send/PacketGetBasicInfoScRsp.java | 2 +- .../send/PacketStaminaInfoScNotify.java | 19 + 8 files changed, 502 insertions(+), 15 deletions(-) create mode 100644 src/generated/main/emu/lunarcore/proto/StaminaInfoScNotifyOuterClass.java create mode 100644 src/main/java/emu/lunarcore/server/packet/send/PacketStaminaInfoScNotify.java diff --git a/src/generated/main/emu/lunarcore/proto/StaminaInfoScNotifyOuterClass.java b/src/generated/main/emu/lunarcore/proto/StaminaInfoScNotifyOuterClass.java new file mode 100644 index 0000000..8c0f950 --- /dev/null +++ b/src/generated/main/emu/lunarcore/proto/StaminaInfoScNotifyOuterClass.java @@ -0,0 +1,421 @@ +// Code generated by protocol buffer compiler. Do not edit! +package emu.lunarcore.proto; + +import java.io.IOException; +import us.hebi.quickbuf.FieldName; +import us.hebi.quickbuf.InvalidProtocolBufferException; +import us.hebi.quickbuf.JsonSink; +import us.hebi.quickbuf.JsonSource; +import us.hebi.quickbuf.MessageFactory; +import us.hebi.quickbuf.ProtoMessage; +import us.hebi.quickbuf.ProtoSink; +import us.hebi.quickbuf.ProtoSource; + +public final class StaminaInfoScNotifyOuterClass { + /** + * Protobuf type {@code StaminaInfoScNotify} + */ + public static final class StaminaInfoScNotify extends ProtoMessage implements Cloneable { + private static final long serialVersionUID = 0L; + + /** + * optional int64 next_recover_time = 3; + */ + private long nextRecoverTime; + + /** + * optional uint32 stamina = 5; + */ + private int stamina; + + /** + * optional uint32 reserve_stamina = 11; + */ + private int reserveStamina; + + private StaminaInfoScNotify() { + } + + /** + * @return a new empty instance of {@code StaminaInfoScNotify} + */ + public static StaminaInfoScNotify newInstance() { + return new StaminaInfoScNotify(); + } + + /** + * optional int64 next_recover_time = 3; + * @return whether the nextRecoverTime field is set + */ + public boolean hasNextRecoverTime() { + return (bitField0_ & 0x00000001) != 0; + } + + /** + * optional int64 next_recover_time = 3; + * @return this + */ + public StaminaInfoScNotify clearNextRecoverTime() { + bitField0_ &= ~0x00000001; + nextRecoverTime = 0L; + return this; + } + + /** + * optional int64 next_recover_time = 3; + * @return the nextRecoverTime + */ + public long getNextRecoverTime() { + return nextRecoverTime; + } + + /** + * optional int64 next_recover_time = 3; + * @param value the nextRecoverTime to set + * @return this + */ + public StaminaInfoScNotify setNextRecoverTime(final long value) { + bitField0_ |= 0x00000001; + nextRecoverTime = value; + return this; + } + + /** + * optional uint32 stamina = 5; + * @return whether the stamina field is set + */ + public boolean hasStamina() { + return (bitField0_ & 0x00000002) != 0; + } + + /** + * optional uint32 stamina = 5; + * @return this + */ + public StaminaInfoScNotify clearStamina() { + bitField0_ &= ~0x00000002; + stamina = 0; + return this; + } + + /** + * optional uint32 stamina = 5; + * @return the stamina + */ + public int getStamina() { + return stamina; + } + + /** + * optional uint32 stamina = 5; + * @param value the stamina to set + * @return this + */ + public StaminaInfoScNotify setStamina(final int value) { + bitField0_ |= 0x00000002; + stamina = value; + return this; + } + + /** + * optional uint32 reserve_stamina = 11; + * @return whether the reserveStamina field is set + */ + public boolean hasReserveStamina() { + return (bitField0_ & 0x00000004) != 0; + } + + /** + * optional uint32 reserve_stamina = 11; + * @return this + */ + public StaminaInfoScNotify clearReserveStamina() { + bitField0_ &= ~0x00000004; + reserveStamina = 0; + return this; + } + + /** + * optional uint32 reserve_stamina = 11; + * @return the reserveStamina + */ + public int getReserveStamina() { + return reserveStamina; + } + + /** + * optional uint32 reserve_stamina = 11; + * @param value the reserveStamina to set + * @return this + */ + public StaminaInfoScNotify setReserveStamina(final int value) { + bitField0_ |= 0x00000004; + reserveStamina = value; + return this; + } + + @Override + public StaminaInfoScNotify copyFrom(final StaminaInfoScNotify other) { + cachedSize = other.cachedSize; + if ((bitField0_ | other.bitField0_) != 0) { + bitField0_ = other.bitField0_; + nextRecoverTime = other.nextRecoverTime; + stamina = other.stamina; + reserveStamina = other.reserveStamina; + } + return this; + } + + @Override + public StaminaInfoScNotify mergeFrom(final StaminaInfoScNotify other) { + if (other.isEmpty()) { + return this; + } + cachedSize = -1; + if (other.hasNextRecoverTime()) { + setNextRecoverTime(other.nextRecoverTime); + } + if (other.hasStamina()) { + setStamina(other.stamina); + } + if (other.hasReserveStamina()) { + setReserveStamina(other.reserveStamina); + } + return this; + } + + @Override + public StaminaInfoScNotify clear() { + if (isEmpty()) { + return this; + } + cachedSize = -1; + bitField0_ = 0; + nextRecoverTime = 0L; + stamina = 0; + reserveStamina = 0; + return this; + } + + @Override + public StaminaInfoScNotify clearQuick() { + if (isEmpty()) { + return this; + } + cachedSize = -1; + bitField0_ = 0; + return this; + } + + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } + if (!(o instanceof StaminaInfoScNotify)) { + return false; + } + StaminaInfoScNotify other = (StaminaInfoScNotify) o; + return bitField0_ == other.bitField0_ + && (!hasNextRecoverTime() || nextRecoverTime == other.nextRecoverTime) + && (!hasStamina() || stamina == other.stamina) + && (!hasReserveStamina() || reserveStamina == other.reserveStamina); + } + + @Override + public void writeTo(final ProtoSink output) throws IOException { + if ((bitField0_ & 0x00000001) != 0) { + output.writeRawByte((byte) 24); + output.writeInt64NoTag(nextRecoverTime); + } + if ((bitField0_ & 0x00000002) != 0) { + output.writeRawByte((byte) 40); + output.writeUInt32NoTag(stamina); + } + if ((bitField0_ & 0x00000004) != 0) { + output.writeRawByte((byte) 88); + output.writeUInt32NoTag(reserveStamina); + } + } + + @Override + protected int computeSerializedSize() { + int size = 0; + if ((bitField0_ & 0x00000001) != 0) { + size += 1 + ProtoSink.computeInt64SizeNoTag(nextRecoverTime); + } + if ((bitField0_ & 0x00000002) != 0) { + size += 1 + ProtoSink.computeUInt32SizeNoTag(stamina); + } + if ((bitField0_ & 0x00000004) != 0) { + size += 1 + ProtoSink.computeUInt32SizeNoTag(reserveStamina); + } + return size; + } + + @Override + @SuppressWarnings("fallthrough") + public StaminaInfoScNotify mergeFrom(final ProtoSource input) throws IOException { + // Enabled Fall-Through Optimization (QuickBuffers) + int tag = input.readTag(); + while (true) { + switch (tag) { + case 24: { + // nextRecoverTime + nextRecoverTime = input.readInt64(); + bitField0_ |= 0x00000001; + tag = input.readTag(); + if (tag != 40) { + break; + } + } + case 40: { + // stamina + stamina = input.readUInt32(); + bitField0_ |= 0x00000002; + tag = input.readTag(); + if (tag != 88) { + break; + } + } + case 88: { + // reserveStamina + reserveStamina = input.readUInt32(); + bitField0_ |= 0x00000004; + tag = input.readTag(); + if (tag != 0) { + break; + } + } + case 0: { + return this; + } + default: { + if (!input.skipField(tag)) { + return this; + } + tag = input.readTag(); + break; + } + } + } + } + + @Override + public void writeTo(final JsonSink output) throws IOException { + output.beginObject(); + if ((bitField0_ & 0x00000001) != 0) { + output.writeInt64(FieldNames.nextRecoverTime, nextRecoverTime); + } + if ((bitField0_ & 0x00000002) != 0) { + output.writeUInt32(FieldNames.stamina, stamina); + } + if ((bitField0_ & 0x00000004) != 0) { + output.writeUInt32(FieldNames.reserveStamina, reserveStamina); + } + output.endObject(); + } + + @Override + public StaminaInfoScNotify mergeFrom(final JsonSource input) throws IOException { + if (!input.beginObject()) { + return this; + } + while (!input.isAtEnd()) { + switch (input.readFieldHash()) { + case -1964148386: + case 394600084: { + if (input.isAtField(FieldNames.nextRecoverTime)) { + if (!input.trySkipNullValue()) { + nextRecoverTime = input.readInt64(); + bitField0_ |= 0x00000001; + } + } else { + input.skipUnknownField(); + } + break; + } + case -1897344401: { + if (input.isAtField(FieldNames.stamina)) { + if (!input.trySkipNullValue()) { + stamina = input.readUInt32(); + bitField0_ |= 0x00000002; + } + } else { + input.skipUnknownField(); + } + break; + } + case -273362413: + case -799929876: { + if (input.isAtField(FieldNames.reserveStamina)) { + if (!input.trySkipNullValue()) { + reserveStamina = input.readUInt32(); + bitField0_ |= 0x00000004; + } + } else { + input.skipUnknownField(); + } + break; + } + default: { + input.skipUnknownField(); + break; + } + } + } + input.endObject(); + return this; + } + + @Override + public StaminaInfoScNotify clone() { + return new StaminaInfoScNotify().copyFrom(this); + } + + @Override + public boolean isEmpty() { + return ((bitField0_) == 0); + } + + public static StaminaInfoScNotify parseFrom(final byte[] data) throws + InvalidProtocolBufferException { + return ProtoMessage.mergeFrom(new StaminaInfoScNotify(), data).checkInitialized(); + } + + public static StaminaInfoScNotify parseFrom(final ProtoSource input) throws IOException { + return ProtoMessage.mergeFrom(new StaminaInfoScNotify(), input).checkInitialized(); + } + + public static StaminaInfoScNotify parseFrom(final JsonSource input) throws IOException { + return ProtoMessage.mergeFrom(new StaminaInfoScNotify(), input).checkInitialized(); + } + + /** + * @return factory for creating StaminaInfoScNotify messages + */ + public static MessageFactory getFactory() { + return StaminaInfoScNotifyFactory.INSTANCE; + } + + private enum StaminaInfoScNotifyFactory implements MessageFactory { + INSTANCE; + + @Override + public StaminaInfoScNotify create() { + return StaminaInfoScNotify.newInstance(); + } + } + + /** + * Contains name constants used for serializing JSON + */ + static class FieldNames { + static final FieldName nextRecoverTime = FieldName.forField("nextRecoverTime", "next_recover_time"); + + static final FieldName stamina = FieldName.forField("stamina"); + + static final FieldName reserveStamina = FieldName.forField("reserveStamina", "reserve_stamina"); + } + } +} diff --git a/src/main/java/emu/lunarcore/GameConstants.java b/src/main/java/emu/lunarcore/GameConstants.java index 006dadb..f165004 100644 --- a/src/main/java/emu/lunarcore/GameConstants.java +++ b/src/main/java/emu/lunarcore/GameConstants.java @@ -18,6 +18,7 @@ public class GameConstants { public static final int MATERIAL_HCOIN_ID = 1; // Material id for jades. DO NOT CHANGE public static final int MATERIAL_COIN_ID = 2; // Material id for credits. DO NOT CHANGE public static final int MAX_STAMINA = 240; + public static final int MAX_STAMINA_RESERVE = 2400; public static final int MAX_AVATARS_IN_TEAM = 4; public static final int DEFAULT_TEAMS = 6; public static final int MAX_MP = 5; // Client doesnt like more than 5 diff --git a/src/main/java/emu/lunarcore/game/battle/Battle.java b/src/main/java/emu/lunarcore/game/battle/Battle.java index edb318a..6c6f2de 100644 --- a/src/main/java/emu/lunarcore/game/battle/Battle.java +++ b/src/main/java/emu/lunarcore/game/battle/Battle.java @@ -33,6 +33,7 @@ public class Battle { private final List drops; private final long timestamp; + @Setter private int staminaCost; @Setter private int levelOverride; @Setter private int roundsLimit; diff --git a/src/main/java/emu/lunarcore/game/battle/BattleService.java b/src/main/java/emu/lunarcore/game/battle/BattleService.java index b2e7963..8e771d5 100644 --- a/src/main/java/emu/lunarcore/game/battle/BattleService.java +++ b/src/main/java/emu/lunarcore/game/battle/BattleService.java @@ -160,7 +160,11 @@ public class BattleService extends BaseGameService { // Get waves wave = Math.min(Math.max(1, wave), cocoonExcel.getMaxWave()); - // TODO sanity check stamina + // Sanity check stamina + int cost = cocoonExcel.getStaminaCost() * wave; + if (player.getStamina() < cost) { + return; + } // Get stages from cocoon List stages = new ArrayList<>(); @@ -181,6 +185,8 @@ public class BattleService extends BaseGameService { // Build battle from cocoon data Battle battle = new Battle(player, player.getLineupManager().getCurrentLineup(), stages); + battle.setStaminaCost(cost); + player.setBattle(battle); // Send packet @@ -212,6 +218,10 @@ public class BattleService extends BaseGameService { } // Drops getServer().getDropService().calculateDrops(battle); + // Spend stamina + if (battle.getStaminaCost() > 0) { + player.spendStamina(battle.getStaminaCost()); + } } case BATTLE_END_LOSE -> { // Set avatar hp to 20% if the player's party is downed diff --git a/src/main/java/emu/lunarcore/game/player/Player.java b/src/main/java/emu/lunarcore/game/player/Player.java index 4b1ca26..348943d 100644 --- a/src/main/java/emu/lunarcore/game/player/Player.java +++ b/src/main/java/emu/lunarcore/game/player/Player.java @@ -44,10 +44,7 @@ import emu.lunarcore.server.game.GameServer; import emu.lunarcore.server.game.GameSession; import emu.lunarcore.server.packet.BasePacket; import emu.lunarcore.server.packet.SessionState; -import emu.lunarcore.server.packet.send.PacketEnterSceneByServerScNotify; -import emu.lunarcore.server.packet.send.PacketPlayerSyncScNotify; -import emu.lunarcore.server.packet.send.PacketSceneEntityMoveScNotify; -import emu.lunarcore.server.packet.send.PacketSyncRogueVirtualItemInfoScNotify; +import emu.lunarcore.server.packet.send.*; import emu.lunarcore.util.Position; import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.ints.IntSet; @@ -69,11 +66,13 @@ public class Player { private int level; private int exp; private int worldLevel; - private int stamina; private int scoin; // Credits private int hcoin; // Jade private int mcoin; // Crystals - private int talentPoints; + private int talentPoints; // Rogue talent points + + private int stamina; + private long nextStaminaRecover; private transient Battle battle; private transient Scene scene; @@ -307,11 +306,6 @@ public class Player { this.sendPacket(new PacketSyncRogueVirtualItemInfoScNotify(this)); } - public void addStamina(int amount) { - this.stamina = Math.min(this.stamina + amount, GameConstants.MAX_STAMINA); - this.sendPacket(new PacketPlayerSyncScNotify(this)); - } - public void addExp(int amount) { // Required exp int reqExp = GameData.getPlayerExpRequired(level + 1); @@ -363,6 +357,42 @@ public class Player { this.battle = battle; } + public void addStamina(int amount) { + this.stamina = Math.min(this.stamina + amount, GameConstants.MAX_STAMINA); + this.sendPacket(new PacketStaminaInfoScNotify(this)); + } + + public void spendStamina(int amount) { + this.stamina = Math.max(this.stamina - amount, 0); + this.sendPacket(new PacketStaminaInfoScNotify(this)); + } + + private void updateStamina() { + // Get current timestamp + long time = System.currentTimeMillis(); + boolean hasChanged = false; + + // Check if we can add stamina + while (time >= this.nextStaminaRecover) { + // Add stamina + if (this.stamina < GameConstants.MAX_STAMINA) { + this.stamina += 1; + hasChanged = true; + } + + // Calculate next stamina recover time + if (this.stamina >= GameConstants.MAX_STAMINA) { + this.nextStaminaRecover = time; + } + this.nextStaminaRecover += 5 * 60 * 1000; + } + + // Send packet + if (hasChanged && this.hasLoggedIn()) { + this.getSession().send(new PacketStaminaInfoScNotify(this)); + } + } + public EntityProp interactWithProp(int propEntityId) { // Sanity if (this.getScene() == null) return null; @@ -532,7 +562,7 @@ public class Player { } public void onTick() { - + this.updateStamina(); } // Database @@ -561,6 +591,9 @@ public class Player { // Post database load this.getAvatars().setupHeroPaths(); + // Update stamina + this.updateStamina(); + // Check instances if (this.getChallengeInstance() != null && !this.getChallengeInstance().validate(this)) { // Delete instance if it failed to validate (example: missing an excel) diff --git a/src/main/java/emu/lunarcore/server/packet/recv/HandlerPlayerLoginCsReq.java b/src/main/java/emu/lunarcore/server/packet/recv/HandlerPlayerLoginCsReq.java index eb57e45..3841b66 100644 --- a/src/main/java/emu/lunarcore/server/packet/recv/HandlerPlayerLoginCsReq.java +++ b/src/main/java/emu/lunarcore/server/packet/recv/HandlerPlayerLoginCsReq.java @@ -6,6 +6,7 @@ import emu.lunarcore.server.packet.Opcodes; import emu.lunarcore.server.packet.PacketHandler; import emu.lunarcore.server.packet.SessionState; import emu.lunarcore.server.packet.send.PacketPlayerLoginScRsp; +import emu.lunarcore.server.packet.send.PacketStaminaInfoScNotify; @Opcodes(CmdId.PlayerLoginCsReq) public class HandlerPlayerLoginCsReq extends PacketHandler { @@ -15,8 +16,9 @@ public class HandlerPlayerLoginCsReq extends PacketHandler { // Set session flag session.setState(SessionState.ACTIVE); - // Send + // Send packets session.send(new PacketPlayerLoginScRsp(session)); + session.send(new PacketStaminaInfoScNotify(session.getPlayer())); } } diff --git a/src/main/java/emu/lunarcore/server/packet/send/PacketGetBasicInfoScRsp.java b/src/main/java/emu/lunarcore/server/packet/send/PacketGetBasicInfoScRsp.java index c7fcc00..97bc722 100644 --- a/src/main/java/emu/lunarcore/server/packet/send/PacketGetBasicInfoScRsp.java +++ b/src/main/java/emu/lunarcore/server/packet/send/PacketGetBasicInfoScRsp.java @@ -13,7 +13,7 @@ public class PacketGetBasicInfoScRsp extends BasePacket { var data = GetBasicInfoScRsp.newInstance() .setCurDay(1) - .setNextRecoverTime(0) + .setNextRecoverTime(session.getPlayer().getNextStaminaRecover() / 1000) .setGameplayBirthday(session.getPlayer().getBirthday()) .setPlayerSettingInfo(PlayerSettingInfo.newInstance()); diff --git a/src/main/java/emu/lunarcore/server/packet/send/PacketStaminaInfoScNotify.java b/src/main/java/emu/lunarcore/server/packet/send/PacketStaminaInfoScNotify.java new file mode 100644 index 0000000..7503b0b --- /dev/null +++ b/src/main/java/emu/lunarcore/server/packet/send/PacketStaminaInfoScNotify.java @@ -0,0 +1,19 @@ +package emu.lunarcore.server.packet.send; + +import emu.lunarcore.game.player.Player; +import emu.lunarcore.proto.StaminaInfoScNotifyOuterClass.StaminaInfoScNotify; +import emu.lunarcore.server.packet.BasePacket; +import emu.lunarcore.server.packet.CmdId; + +public class PacketStaminaInfoScNotify extends BasePacket { + + public PacketStaminaInfoScNotify(Player player) { + super(CmdId.StaminaInfoScNotify); + + var data = StaminaInfoScNotify.newInstance() + .setNextRecoverTime(player.getNextStaminaRecover() / 1000) + .setStamina(player.getStamina()); + + this.setData(data); + } +}