diff --git a/src/generated/main/emu/lunarcore/proto/ReserveStaminaExchangeCsReqOuterClass.java b/src/generated/main/emu/lunarcore/proto/ReserveStaminaExchangeCsReqOuterClass.java new file mode 100644 index 0000000..1c8b912 --- /dev/null +++ b/src/generated/main/emu/lunarcore/proto/ReserveStaminaExchangeCsReqOuterClass.java @@ -0,0 +1,260 @@ +// 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 ReserveStaminaExchangeCsReqOuterClass { + /** + * Protobuf type {@code ReserveStaminaExchangeCsReq} + */ + public static final class ReserveStaminaExchangeCsReq extends ProtoMessage implements Cloneable { + private static final long serialVersionUID = 0L; + + /** + * optional uint32 num = 2; + */ + private int num; + + private ReserveStaminaExchangeCsReq() { + } + + /** + * @return a new empty instance of {@code ReserveStaminaExchangeCsReq} + */ + public static ReserveStaminaExchangeCsReq newInstance() { + return new ReserveStaminaExchangeCsReq(); + } + + /** + * optional uint32 num = 2; + * @return whether the num field is set + */ + public boolean hasNum() { + return (bitField0_ & 0x00000001) != 0; + } + + /** + * optional uint32 num = 2; + * @return this + */ + public ReserveStaminaExchangeCsReq clearNum() { + bitField0_ &= ~0x00000001; + num = 0; + return this; + } + + /** + * optional uint32 num = 2; + * @return the num + */ + public int getNum() { + return num; + } + + /** + * optional uint32 num = 2; + * @param value the num to set + * @return this + */ + public ReserveStaminaExchangeCsReq setNum(final int value) { + bitField0_ |= 0x00000001; + num = value; + return this; + } + + @Override + public ReserveStaminaExchangeCsReq copyFrom(final ReserveStaminaExchangeCsReq other) { + cachedSize = other.cachedSize; + if ((bitField0_ | other.bitField0_) != 0) { + bitField0_ = other.bitField0_; + num = other.num; + } + return this; + } + + @Override + public ReserveStaminaExchangeCsReq mergeFrom(final ReserveStaminaExchangeCsReq other) { + if (other.isEmpty()) { + return this; + } + cachedSize = -1; + if (other.hasNum()) { + setNum(other.num); + } + return this; + } + + @Override + public ReserveStaminaExchangeCsReq clear() { + if (isEmpty()) { + return this; + } + cachedSize = -1; + bitField0_ = 0; + num = 0; + return this; + } + + @Override + public ReserveStaminaExchangeCsReq 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 ReserveStaminaExchangeCsReq)) { + return false; + } + ReserveStaminaExchangeCsReq other = (ReserveStaminaExchangeCsReq) o; + return bitField0_ == other.bitField0_ + && (!hasNum() || num == other.num); + } + + @Override + public void writeTo(final ProtoSink output) throws IOException { + if ((bitField0_ & 0x00000001) != 0) { + output.writeRawByte((byte) 16); + output.writeUInt32NoTag(num); + } + } + + @Override + protected int computeSerializedSize() { + int size = 0; + if ((bitField0_ & 0x00000001) != 0) { + size += 1 + ProtoSink.computeUInt32SizeNoTag(num); + } + return size; + } + + @Override + @SuppressWarnings("fallthrough") + public ReserveStaminaExchangeCsReq mergeFrom(final ProtoSource input) throws IOException { + // Enabled Fall-Through Optimization (QuickBuffers) + int tag = input.readTag(); + while (true) { + switch (tag) { + case 16: { + // num + num = input.readUInt32(); + bitField0_ |= 0x00000001; + 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.writeUInt32(FieldNames.num, num); + } + output.endObject(); + } + + @Override + public ReserveStaminaExchangeCsReq mergeFrom(final JsonSource input) throws IOException { + if (!input.beginObject()) { + return this; + } + while (!input.isAtEnd()) { + switch (input.readFieldHash()) { + case 109446: { + if (input.isAtField(FieldNames.num)) { + if (!input.trySkipNullValue()) { + num = input.readUInt32(); + bitField0_ |= 0x00000001; + } + } else { + input.skipUnknownField(); + } + break; + } + default: { + input.skipUnknownField(); + break; + } + } + } + input.endObject(); + return this; + } + + @Override + public ReserveStaminaExchangeCsReq clone() { + return new ReserveStaminaExchangeCsReq().copyFrom(this); + } + + @Override + public boolean isEmpty() { + return ((bitField0_) == 0); + } + + public static ReserveStaminaExchangeCsReq parseFrom(final byte[] data) throws + InvalidProtocolBufferException { + return ProtoMessage.mergeFrom(new ReserveStaminaExchangeCsReq(), data).checkInitialized(); + } + + public static ReserveStaminaExchangeCsReq parseFrom(final ProtoSource input) throws + IOException { + return ProtoMessage.mergeFrom(new ReserveStaminaExchangeCsReq(), input).checkInitialized(); + } + + public static ReserveStaminaExchangeCsReq parseFrom(final JsonSource input) throws IOException { + return ProtoMessage.mergeFrom(new ReserveStaminaExchangeCsReq(), input).checkInitialized(); + } + + /** + * @return factory for creating ReserveStaminaExchangeCsReq messages + */ + public static MessageFactory getFactory() { + return ReserveStaminaExchangeCsReqFactory.INSTANCE; + } + + private enum ReserveStaminaExchangeCsReqFactory implements MessageFactory { + INSTANCE; + + @Override + public ReserveStaminaExchangeCsReq create() { + return ReserveStaminaExchangeCsReq.newInstance(); + } + } + + /** + * Contains name constants used for serializing JSON + */ + static class FieldNames { + static final FieldName num = FieldName.forField("num"); + } + } +} diff --git a/src/generated/main/emu/lunarcore/proto/ReserveStaminaExchangeScRspOuterClass.java b/src/generated/main/emu/lunarcore/proto/ReserveStaminaExchangeScRspOuterClass.java new file mode 100644 index 0000000..8449e99 --- /dev/null +++ b/src/generated/main/emu/lunarcore/proto/ReserveStaminaExchangeScRspOuterClass.java @@ -0,0 +1,340 @@ +// 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 ReserveStaminaExchangeScRspOuterClass { + /** + * Protobuf type {@code ReserveStaminaExchangeScRsp} + */ + public static final class ReserveStaminaExchangeScRsp extends ProtoMessage implements Cloneable { + private static final long serialVersionUID = 0L; + + /** + * optional uint32 num = 9; + */ + private int num; + + /** + * optional uint32 retcode = 13; + */ + private int retcode; + + private ReserveStaminaExchangeScRsp() { + } + + /** + * @return a new empty instance of {@code ReserveStaminaExchangeScRsp} + */ + public static ReserveStaminaExchangeScRsp newInstance() { + return new ReserveStaminaExchangeScRsp(); + } + + /** + * optional uint32 num = 9; + * @return whether the num field is set + */ + public boolean hasNum() { + return (bitField0_ & 0x00000001) != 0; + } + + /** + * optional uint32 num = 9; + * @return this + */ + public ReserveStaminaExchangeScRsp clearNum() { + bitField0_ &= ~0x00000001; + num = 0; + return this; + } + + /** + * optional uint32 num = 9; + * @return the num + */ + public int getNum() { + return num; + } + + /** + * optional uint32 num = 9; + * @param value the num to set + * @return this + */ + public ReserveStaminaExchangeScRsp setNum(final int value) { + bitField0_ |= 0x00000001; + num = value; + return this; + } + + /** + * optional uint32 retcode = 13; + * @return whether the retcode field is set + */ + public boolean hasRetcode() { + return (bitField0_ & 0x00000002) != 0; + } + + /** + * optional uint32 retcode = 13; + * @return this + */ + public ReserveStaminaExchangeScRsp clearRetcode() { + bitField0_ &= ~0x00000002; + retcode = 0; + return this; + } + + /** + * optional uint32 retcode = 13; + * @return the retcode + */ + public int getRetcode() { + return retcode; + } + + /** + * optional uint32 retcode = 13; + * @param value the retcode to set + * @return this + */ + public ReserveStaminaExchangeScRsp setRetcode(final int value) { + bitField0_ |= 0x00000002; + retcode = value; + return this; + } + + @Override + public ReserveStaminaExchangeScRsp copyFrom(final ReserveStaminaExchangeScRsp other) { + cachedSize = other.cachedSize; + if ((bitField0_ | other.bitField0_) != 0) { + bitField0_ = other.bitField0_; + num = other.num; + retcode = other.retcode; + } + return this; + } + + @Override + public ReserveStaminaExchangeScRsp mergeFrom(final ReserveStaminaExchangeScRsp other) { + if (other.isEmpty()) { + return this; + } + cachedSize = -1; + if (other.hasNum()) { + setNum(other.num); + } + if (other.hasRetcode()) { + setRetcode(other.retcode); + } + return this; + } + + @Override + public ReserveStaminaExchangeScRsp clear() { + if (isEmpty()) { + return this; + } + cachedSize = -1; + bitField0_ = 0; + num = 0; + retcode = 0; + return this; + } + + @Override + public ReserveStaminaExchangeScRsp 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 ReserveStaminaExchangeScRsp)) { + return false; + } + ReserveStaminaExchangeScRsp other = (ReserveStaminaExchangeScRsp) o; + return bitField0_ == other.bitField0_ + && (!hasNum() || num == other.num) + && (!hasRetcode() || retcode == other.retcode); + } + + @Override + public void writeTo(final ProtoSink output) throws IOException { + if ((bitField0_ & 0x00000001) != 0) { + output.writeRawByte((byte) 72); + output.writeUInt32NoTag(num); + } + if ((bitField0_ & 0x00000002) != 0) { + output.writeRawByte((byte) 104); + output.writeUInt32NoTag(retcode); + } + } + + @Override + protected int computeSerializedSize() { + int size = 0; + if ((bitField0_ & 0x00000001) != 0) { + size += 1 + ProtoSink.computeUInt32SizeNoTag(num); + } + if ((bitField0_ & 0x00000002) != 0) { + size += 1 + ProtoSink.computeUInt32SizeNoTag(retcode); + } + return size; + } + + @Override + @SuppressWarnings("fallthrough") + public ReserveStaminaExchangeScRsp mergeFrom(final ProtoSource input) throws IOException { + // Enabled Fall-Through Optimization (QuickBuffers) + int tag = input.readTag(); + while (true) { + switch (tag) { + case 72: { + // num + num = input.readUInt32(); + bitField0_ |= 0x00000001; + tag = input.readTag(); + if (tag != 104) { + break; + } + } + case 104: { + // retcode + retcode = input.readUInt32(); + bitField0_ |= 0x00000002; + 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.writeUInt32(FieldNames.num, num); + } + if ((bitField0_ & 0x00000002) != 0) { + output.writeUInt32(FieldNames.retcode, retcode); + } + output.endObject(); + } + + @Override + public ReserveStaminaExchangeScRsp mergeFrom(final JsonSource input) throws IOException { + if (!input.beginObject()) { + return this; + } + while (!input.isAtEnd()) { + switch (input.readFieldHash()) { + case 109446: { + if (input.isAtField(FieldNames.num)) { + if (!input.trySkipNullValue()) { + num = input.readUInt32(); + bitField0_ |= 0x00000001; + } + } else { + input.skipUnknownField(); + } + break; + } + case 1097936398: { + if (input.isAtField(FieldNames.retcode)) { + if (!input.trySkipNullValue()) { + retcode = input.readUInt32(); + bitField0_ |= 0x00000002; + } + } else { + input.skipUnknownField(); + } + break; + } + default: { + input.skipUnknownField(); + break; + } + } + } + input.endObject(); + return this; + } + + @Override + public ReserveStaminaExchangeScRsp clone() { + return new ReserveStaminaExchangeScRsp().copyFrom(this); + } + + @Override + public boolean isEmpty() { + return ((bitField0_) == 0); + } + + public static ReserveStaminaExchangeScRsp parseFrom(final byte[] data) throws + InvalidProtocolBufferException { + return ProtoMessage.mergeFrom(new ReserveStaminaExchangeScRsp(), data).checkInitialized(); + } + + public static ReserveStaminaExchangeScRsp parseFrom(final ProtoSource input) throws + IOException { + return ProtoMessage.mergeFrom(new ReserveStaminaExchangeScRsp(), input).checkInitialized(); + } + + public static ReserveStaminaExchangeScRsp parseFrom(final JsonSource input) throws IOException { + return ProtoMessage.mergeFrom(new ReserveStaminaExchangeScRsp(), input).checkInitialized(); + } + + /** + * @return factory for creating ReserveStaminaExchangeScRsp messages + */ + public static MessageFactory getFactory() { + return ReserveStaminaExchangeScRspFactory.INSTANCE; + } + + private enum ReserveStaminaExchangeScRspFactory implements MessageFactory { + INSTANCE; + + @Override + public ReserveStaminaExchangeScRsp create() { + return ReserveStaminaExchangeScRsp.newInstance(); + } + } + + /** + * Contains name constants used for serializing JSON + */ + static class FieldNames { + static final FieldName num = FieldName.forField("num"); + + static final FieldName retcode = FieldName.forField("retcode"); + } + } +} diff --git a/src/main/java/emu/lunarcore/game/player/Player.java b/src/main/java/emu/lunarcore/game/player/Player.java index ceb163d..844836b 100644 --- a/src/main/java/emu/lunarcore/game/player/Player.java +++ b/src/main/java/emu/lunarcore/game/player/Player.java @@ -72,6 +72,7 @@ public class Player { private int talentPoints; // Rogue talent points private int stamina; + private double staminaReserve; private long nextStaminaRecover; private transient Battle battle; @@ -130,6 +131,7 @@ public class Player { this.headIcon = 200001; this.level = 1; this.stamina = GameConstants.MAX_STAMINA; + this.nextStaminaRecover = System.currentTimeMillis(); this.unlockedHeadIcons = new IntOpenHashSet(); this.lineupManager = new LineupManager(this); @@ -367,6 +369,20 @@ public class Player { this.sendPacket(new PacketStaminaInfoScNotify(this)); } + public int exchangeReserveStamina(int amount) { + // Sanity checks + if (amount <= 0 || this.staminaReserve < amount) { + return 0; + } + + this.staminaReserve -= amount; + this.stamina += amount; + + // Update to client + this.sendPacket(new PacketStaminaInfoScNotify(this)); + return amount; + } + private void updateStamina() { // Get current timestamp long time = System.currentTimeMillis(); @@ -378,12 +394,17 @@ public class Player { if (this.stamina < GameConstants.MAX_STAMINA) { this.stamina += 1; hasChanged = true; + } else if (this.stamina < GameConstants.MAX_STAMINA_RESERVE) { + double amount = (time - this.nextStaminaRecover) / (18D * 60D * 1000D); + this.staminaReserve = Math.min(this.staminaReserve + amount, GameConstants.MAX_STAMINA_RESERVE); + hasChanged = true; } // Calculate next stamina recover time if (this.stamina >= GameConstants.MAX_STAMINA) { this.nextStaminaRecover = time; } + this.nextStaminaRecover += 5 * 60 * 1000; } diff --git a/src/main/java/emu/lunarcore/server/packet/recv/HandlerReserveStaminaExchangeCsReq.java b/src/main/java/emu/lunarcore/server/packet/recv/HandlerReserveStaminaExchangeCsReq.java new file mode 100644 index 0000000..487f120 --- /dev/null +++ b/src/main/java/emu/lunarcore/server/packet/recv/HandlerReserveStaminaExchangeCsReq.java @@ -0,0 +1,21 @@ +package emu.lunarcore.server.packet.recv; + +import emu.lunarcore.proto.ReserveStaminaExchangeCsReqOuterClass.ReserveStaminaExchangeCsReq; +import emu.lunarcore.server.game.GameSession; +import emu.lunarcore.server.packet.CmdId; +import emu.lunarcore.server.packet.Opcodes; +import emu.lunarcore.server.packet.PacketHandler; +import emu.lunarcore.server.packet.send.PacketReserveStaminaExchangeScRsp; + +@Opcodes(CmdId.ReserveStaminaExchangeCsReq) +public class HandlerReserveStaminaExchangeCsReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] data) throws Exception { + var req = ReserveStaminaExchangeCsReq.parseFrom(data); + + int exchangedAmount = session.getPlayer().exchangeReserveStamina(req.getNum()); + session.send(new PacketReserveStaminaExchangeScRsp(exchangedAmount)); + } + +} diff --git a/src/main/java/emu/lunarcore/server/packet/send/PacketReserveStaminaExchangeScRsp.java b/src/main/java/emu/lunarcore/server/packet/send/PacketReserveStaminaExchangeScRsp.java new file mode 100644 index 0000000..e596ddb --- /dev/null +++ b/src/main/java/emu/lunarcore/server/packet/send/PacketReserveStaminaExchangeScRsp.java @@ -0,0 +1,22 @@ +package emu.lunarcore.server.packet.send; + +import emu.lunarcore.proto.ReserveStaminaExchangeScRspOuterClass.ReserveStaminaExchangeScRsp; +import emu.lunarcore.server.packet.BasePacket; +import emu.lunarcore.server.packet.CmdId; + +public class PacketReserveStaminaExchangeScRsp extends BasePacket { + + public PacketReserveStaminaExchangeScRsp(int amount) { + super(CmdId.ReserveStaminaExchangeScRsp); + + var data = ReserveStaminaExchangeScRsp.newInstance(); + + if (amount > 0) { + data.setNum(amount); + } else { + data.setRetcode(1); + } + + this.setData(data); + } +} diff --git a/src/main/java/emu/lunarcore/server/packet/send/PacketStaminaInfoScNotify.java b/src/main/java/emu/lunarcore/server/packet/send/PacketStaminaInfoScNotify.java index 7503b0b..d3722b0 100644 --- a/src/main/java/emu/lunarcore/server/packet/send/PacketStaminaInfoScNotify.java +++ b/src/main/java/emu/lunarcore/server/packet/send/PacketStaminaInfoScNotify.java @@ -12,7 +12,8 @@ public class PacketStaminaInfoScNotify extends BasePacket { var data = StaminaInfoScNotify.newInstance() .setNextRecoverTime(player.getNextStaminaRecover() / 1000) - .setStamina(player.getStamina()); + .setStamina(player.getStamina()) + .setReserveStamina((int) Math.floor(player.getStaminaReserve())); this.setData(data); }