From 28afb10691d8a90f21dd8fdaaf5291eecf8e6bda Mon Sep 17 00:00:00 2001 From: Melledy <121644117+Melledy@users.noreply.github.com> Date: Mon, 17 Aug 2026 21:23:39 -0700 Subject: [PATCH] Implement character archive story rewards --- src/main/java/emu/nebula/data/GameData.java | 1 + .../data/resources/CharacterArchiveDef.java | 23 ++++++++++ .../nebula/game/character/GameCharacter.java | 45 ++++++++++++++++++- .../HandlerCharArchiveRewardReceiveReq.java | 45 +++++++++++++++++++ 4 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 src/main/java/emu/nebula/data/resources/CharacterArchiveDef.java create mode 100644 src/main/java/emu/nebula/server/handlers/HandlerCharArchiveRewardReceiveReq.java diff --git a/src/main/java/emu/nebula/data/GameData.java b/src/main/java/emu/nebula/data/GameData.java index 976738d..2d06f78 100644 --- a/src/main/java/emu/nebula/data/GameData.java +++ b/src/main/java/emu/nebula/data/GameData.java @@ -13,6 +13,7 @@ public class GameData { @Getter private static DataTable CharacterUpgradeDataTable = new DataTable<>(); @Getter private static DataTable CharItemExpDataTable = new DataTable<>(); @Getter private static DataTable CharacterSkinDataTable = new DataTable<>(); + @Getter private static DataTable CharacterArchiveDataTable = new DataTable<>(); @Getter private static DataTable TalentGroupDataTable = new DataTable<>(); @Getter private static DataTable TalentDataTable = new DataTable<>(); diff --git a/src/main/java/emu/nebula/data/resources/CharacterArchiveDef.java b/src/main/java/emu/nebula/data/resources/CharacterArchiveDef.java new file mode 100644 index 0000000..775b5ab --- /dev/null +++ b/src/main/java/emu/nebula/data/resources/CharacterArchiveDef.java @@ -0,0 +1,23 @@ +package emu.nebula.data.resources; + +import emu.nebula.data.BaseDef; +import emu.nebula.data.ResourceType; + +import lombok.Getter; + +@Getter +@ResourceType(name = "CharacterArchive.json") +public class CharacterArchiveDef extends BaseDef { + private int Id; + private int ArchType; + private int CharacterId; + private int UnlockAffinityLevel; + private int ArchReward; + private int ArchRewardQuantity; + + @Override + public int getId() { + return Id; + } + +} diff --git a/src/main/java/emu/nebula/game/character/GameCharacter.java b/src/main/java/emu/nebula/game/character/GameCharacter.java index e1b5253..6b4886f 100644 --- a/src/main/java/emu/nebula/game/character/GameCharacter.java +++ b/src/main/java/emu/nebula/game/character/GameCharacter.java @@ -15,6 +15,7 @@ import dev.morphia.annotations.PreLoad; import emu.nebula.GameConstants; import emu.nebula.Nebula; import emu.nebula.data.GameData; +import emu.nebula.data.resources.CharacterArchiveDef; import emu.nebula.data.resources.CharacterDef; import emu.nebula.data.resources.TalentGroupDef; import emu.nebula.database.GameDatabaseObject; @@ -65,13 +66,20 @@ public class GameCharacter implements GameDatabaseObject { private int skin; private int[] skills; private Bitset talents; - private IntSet plots; private long createTime; + // Archive stories + private IntSet archives; + + // Affinity stories (Memory recollection) + private IntSet plots; + + // Gems private int gemPresetIndex; private List gemPresets; private CharacterGemSlot[] gemSlots; + // Phone/Heartlink private CharacterContact contact; @Deprecated // Morphia only! @@ -532,6 +540,36 @@ public class GameCharacter implements GameDatabaseObject { return change; } + public PlayerChangeInfo recvArchiveReward(CharacterArchiveDef archive) { + // Sanity check to prevent players from recving rewards over and over again from the same archive + if (this.getArchives() != null && this.getArchives().contains(archive.getId())) { + return null; + } + + // Sanity check to make sure we can complete this archive + if (archive.getUnlockAffinityLevel() > this.getAffinityLevel()) { + return null; + } + + // Make sure archive has rewards + if (archive.getArchReward() == 0) { + return null; + } + + // Complete archive + if (this.archives == null) { + this.archives = new IntOpenHashSet(); + } + + this.getArchives().add(archive.getId()); + + // Update to database + this.save(); + + // Add items + return this.getPlayer().getInventory().addItem(archive.getArchReward(), archive.getArchRewardQuantity()); + } + // Gems public boolean hasGemPreset(int index) { @@ -955,6 +993,11 @@ public class GameCharacter implements GameDatabaseObject { this.getPlots().forEach(proto::addPlots); } + // Encode archives + if (this.getArchives() != null) { + this.getArchives().forEach(proto::addArchiveRewardIds); + } + // Finish return proto; } diff --git a/src/main/java/emu/nebula/server/handlers/HandlerCharArchiveRewardReceiveReq.java b/src/main/java/emu/nebula/server/handlers/HandlerCharArchiveRewardReceiveReq.java new file mode 100644 index 0000000..3fa8a16 --- /dev/null +++ b/src/main/java/emu/nebula/server/handlers/HandlerCharArchiveRewardReceiveReq.java @@ -0,0 +1,45 @@ +package emu.nebula.server.handlers; + +import emu.nebula.net.NetHandler; +import emu.nebula.net.NetMsgId; +import emu.nebula.proto.CharArchive.CharArchiveRewardReceiveReq; +import emu.nebula.net.HandlerId; +import emu.nebula.data.GameData; +import emu.nebula.net.GameSession; + +@HandlerId(NetMsgId.char_archive_reward_receive_req) +public class HandlerCharArchiveRewardReceiveReq extends NetHandler { + + @Override + public byte[] handle(GameSession session, byte[] message) throws Exception { + // Parse request + var req = CharArchiveRewardReceiveReq.parseFrom(message); + + // Get archive + var archive = GameData.getCharacterArchiveDataTable().get(req.getArchiveId()); + + if (archive == null) { + // Archive not found + return session.encodeMsg(NetMsgId.char_archive_reward_receive_failed_ack); + } + + // Get character + var character = session.getPlayer().getCharacters().getCharacterById(archive.getCharacterId()); + + if (character == null) { + // Character not found + return session.encodeMsg(NetMsgId.char_archive_reward_receive_failed_ack); + } + + // Get archive reward + var change = character.recvArchiveReward(archive); + + if (change == null) { + return session.encodeMsg(NetMsgId.char_archive_reward_receive_failed_ack); + } + + // Encode reward and send + return session.encodeMsg(NetMsgId.char_archive_reward_receive_succeed_ack, change.toProto()); + } + +}