From 86b2933aa19f54f5699b2dce6e251ebfff31e034 Mon Sep 17 00:00:00 2001 From: Melledy <121644117+Melledy@users.noreply.github.com> Date: Fri, 7 Nov 2025 06:23:42 -0800 Subject: [PATCH] Implement level rewards --- .../nebula/data/resources/WorldClassDef.java | 12 ++++ .../java/emu/nebula/game/player/Player.java | 23 ++++++- .../emu/nebula/game/quest/QuestManager.java | 67 +++++++++++++++++-- ...ndlerPlayerWorldClassRewardReceiveReq.java | 28 ++++++++ .../HandlerQuestDailyRewardReceiveReq.java | 2 +- src/main/java/emu/nebula/util/Bitset.java | 28 ++++++++ 6 files changed, 153 insertions(+), 7 deletions(-) create mode 100644 src/main/java/emu/nebula/server/handlers/HandlerPlayerWorldClassRewardReceiveReq.java diff --git a/src/main/java/emu/nebula/data/resources/WorldClassDef.java b/src/main/java/emu/nebula/data/resources/WorldClassDef.java index c12c480..8e740e2 100644 --- a/src/main/java/emu/nebula/data/resources/WorldClassDef.java +++ b/src/main/java/emu/nebula/data/resources/WorldClassDef.java @@ -2,6 +2,7 @@ package emu.nebula.data.resources; import emu.nebula.data.BaseDef; import emu.nebula.data.ResourceType; +import emu.nebula.game.inventory.ItemParamMap; import lombok.Getter; @Getter @@ -11,8 +12,19 @@ public class WorldClassDef extends BaseDef { private int Exp; private String Reward; + private transient ItemParamMap rewards; + @Override public int getId() { return Id; } + + @Override + public void onLoad() { + if (this.Reward != null) { + this.rewards = ItemParamMap.fromJsonString(this.Reward); + } else { + this.rewards = new ItemParamMap(); + } + } } diff --git a/src/main/java/emu/nebula/game/player/Player.java b/src/main/java/emu/nebula/game/player/Player.java index 1b71ea8..fa9773a 100644 --- a/src/main/java/emu/nebula/game/player/Player.java +++ b/src/main/java/emu/nebula/game/player/Player.java @@ -23,6 +23,7 @@ import emu.nebula.game.scoreboss.ScoreBossManager; import emu.nebula.game.story.StoryManager; import emu.nebula.game.tower.StarTowerManager; import emu.nebula.net.GameSession; +import emu.nebula.net.NetMsgId; import emu.nebula.net.NetMsgPacket; import emu.nebula.proto.PlayerData.DictionaryEntry; import emu.nebula.proto.PlayerData.DictionaryTab; @@ -32,6 +33,7 @@ import emu.nebula.proto.Public.NewbieInfo; import emu.nebula.proto.Public.QuestType; import emu.nebula.proto.Public.Story; import emu.nebula.proto.Public.WorldClass; +import emu.nebula.proto.Public.WorldClassRewardState; import emu.nebula.proto.Public.Title; import lombok.Getter; @@ -305,10 +307,15 @@ public class Player implements GameDatabaseObject { // Check for level ups while (this.exp >= expRequired && expRequired > 0) { + // Add level this.level += 1; this.exp -= expRequired; + // Recalculate exp required expRequired = this.getMaxExp(); + + // Set level reward + this.getQuestManager().getLevelRewards().setBit(this.level); } // Save to database @@ -321,6 +328,17 @@ public class Player implements GameDatabaseObject { this.getExp() ); + // Save level rewards if we changed it + if (oldLevel != this.getLevel()) { + this.getQuestManager().saveLevelRewards(); + + this.addNextPackage( + NetMsgId.world_class_reward_state_notify, + WorldClassRewardState.newInstance() + .setFlag(getQuestManager().getLevelRewards().toBigEndianByteArray()) + ); + } + // Calculate changes var proto = WorldClass.newInstance() .setAddClass(this.getLevel() - oldLevel) @@ -514,7 +532,6 @@ public class Player implements GameDatabaseObject { state.getMutableMail(); state.getMutableBattlePass(); - state.getMutableWorldClassReward(); state.getMutableFriendEnergy(); state.getMutableMallPackage(); state.getMutableAchievement(); @@ -571,6 +588,10 @@ public class Player implements GameDatabaseObject { proto.addDailyActiveIds(id); } + + state.getMutableWorldClassReward() + .setFlag(this.getQuestManager().getLevelRewards().toBigEndianByteArray()); + // Add dictionary tabs for (var dictionaryData : GameData.getDictionaryTabDataTable()) { var dictionaryProto = DictionaryTab.newInstance() diff --git a/src/main/java/emu/nebula/game/quest/QuestManager.java b/src/main/java/emu/nebula/game/quest/QuestManager.java index 0bc35f8..abe4093 100644 --- a/src/main/java/emu/nebula/game/quest/QuestManager.java +++ b/src/main/java/emu/nebula/game/quest/QuestManager.java @@ -1,19 +1,21 @@ package emu.nebula.game.quest; +import java.util.ArrayList; import java.util.HashMap; -import java.util.HashSet; import java.util.Map; import dev.morphia.annotations.Entity; import dev.morphia.annotations.Id; import emu.nebula.Nebula; import emu.nebula.data.GameData; +import emu.nebula.data.resources.WorldClassDef; import emu.nebula.database.GameDatabaseObject; import emu.nebula.game.inventory.ItemParamMap; import emu.nebula.game.player.Player; import emu.nebula.game.player.PlayerChangeInfo; import emu.nebula.game.player.PlayerManager; import emu.nebula.net.NetMsgId; +import emu.nebula.util.Bitset; import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.ints.IntSet; @@ -32,6 +34,9 @@ public class QuestManager extends PlayerManager implements GameDatabaseObject { // Quests private Map quests; + // Level rewards + private Bitset levelRewards; + @Deprecated // Morphia only public QuestManager() { @@ -42,12 +47,17 @@ public class QuestManager extends PlayerManager implements GameDatabaseObject { this.uid = player.getUid(); this.claimedActiveIds = new IntOpenHashSet(); this.quests = new HashMap<>(); + this.levelRewards = new Bitset(); this.resetDailyQuests(); this.save(); } + public void saveLevelRewards() { + Nebula.getGameDatabase().update(this, this.getUid(), "levelRewards", this.levelRewards); + } + public synchronized void resetDailyQuests() { // Reset daily quests for (var data : GameData.getDailyQuestDataTable()) { @@ -87,9 +97,9 @@ public class QuestManager extends PlayerManager implements GameDatabaseObject { } } - public PlayerChangeInfo receiveReward(int questId) { + public PlayerChangeInfo receiveQuestReward(int questId) { // Get received quests - var claimList = new HashSet(); + var claimList = new ArrayList(); if (questId > 0) { // Claim specific quest @@ -140,7 +150,7 @@ public class QuestManager extends PlayerManager implements GameDatabaseObject { Nebula.getGameDatabase().update(this, this.getUid(), "activity", this.getActivity()); // Success - return change; + return change.setSuccess(true); } public PlayerChangeInfo claimActiveRewards() { @@ -179,7 +189,54 @@ public class QuestManager extends PlayerManager implements GameDatabaseObject { Nebula.getGameDatabase().update(this, this.getUid(), "claimedActiveIds", this.getClaimedActiveIds()); // Success - return change; + return change.setSuccess(true); + } + + public PlayerChangeInfo receiveWorldClassReward(int id) { + // Get rewards we want to claim + var claimList = new ArrayList(); + + if (id > 0) { + // Claim specific level reward + if (this.getLevelRewards().isSet(id)) { + var data = GameData.getWorldClassDataTable().get(id); + if (data != null) { + claimList.add(data); + } + } + } else { + // Claim all + for (var data : GameData.getWorldClassDataTable()) { + if (this.getLevelRewards().isSet(data.getId())) { + claimList.add(data); + } + } + } + + // Sanity check + if (claimList.isEmpty()) { + return null; + } + + // Claim + var rewards = new ItemParamMap(); + + for (var data : claimList) { + // Add rewards + rewards.add(data.getRewards()); + + // Unset level rewards + this.getLevelRewards().unsetBit(data.getId()); + } + + // Add to inventory + var change = this.getPlayer().getInventory().addItems(rewards); + + // Save to db + this.saveLevelRewards(); + + // Success + return change.setSuccess(true); } /** diff --git a/src/main/java/emu/nebula/server/handlers/HandlerPlayerWorldClassRewardReceiveReq.java b/src/main/java/emu/nebula/server/handlers/HandlerPlayerWorldClassRewardReceiveReq.java new file mode 100644 index 0000000..32b1dca --- /dev/null +++ b/src/main/java/emu/nebula/server/handlers/HandlerPlayerWorldClassRewardReceiveReq.java @@ -0,0 +1,28 @@ +package emu.nebula.server.handlers; + +import emu.nebula.net.NetHandler; +import emu.nebula.net.NetMsgId; +import emu.nebula.proto.PlayerWorldClassRewardReceive.PlayerWorldClassRewardReq; +import emu.nebula.net.HandlerId; +import emu.nebula.net.GameSession; + +@HandlerId(NetMsgId.player_world_class_reward_receive_req) +public class HandlerPlayerWorldClassRewardReceiveReq extends NetHandler { + + @Override + public byte[] handle(GameSession session, byte[] message) throws Exception { + // Parse request + var req = PlayerWorldClassRewardReq.parseFrom(message); + + // Receive rewards + var change = session.getPlayer().getQuestManager().receiveWorldClassReward(req.getClassX()); + + if (change == null) { + return session.encodeMsg(NetMsgId.player_world_class_reward_receive_failed_ack); + } + + // Template + return session.encodeMsg(NetMsgId.player_world_class_reward_receive_succeed_ack, change.toProto()); + } + +} diff --git a/src/main/java/emu/nebula/server/handlers/HandlerQuestDailyRewardReceiveReq.java b/src/main/java/emu/nebula/server/handlers/HandlerQuestDailyRewardReceiveReq.java index 2840c49..fb010ac 100644 --- a/src/main/java/emu/nebula/server/handlers/HandlerQuestDailyRewardReceiveReq.java +++ b/src/main/java/emu/nebula/server/handlers/HandlerQuestDailyRewardReceiveReq.java @@ -15,7 +15,7 @@ public class HandlerQuestDailyRewardReceiveReq extends NetHandler { var req = UI32.parseFrom(message); // Receive rewards - var change = session.getPlayer().getQuestManager().receiveReward(req.getValue()); + var change = session.getPlayer().getQuestManager().receiveQuestReward(req.getValue()); if (change == null) { return session.encodeMsg(NetMsgId.quest_daily_reward_receive_failed_ack); diff --git a/src/main/java/emu/nebula/util/Bitset.java b/src/main/java/emu/nebula/util/Bitset.java index ece0ee7..9502b16 100644 --- a/src/main/java/emu/nebula/util/Bitset.java +++ b/src/main/java/emu/nebula/util/Bitset.java @@ -39,6 +39,19 @@ public class Bitset { this.data[longArrayOffset] |= (1L << bytePosition); } + public void unsetBit(int index) { + int longArrayOffset = (int) Math.floor((index - 1) / 64D); + int bytePosition = ((index - 1) % 64); + + if (longArrayOffset >= this.data.length) { + var oldData = this.data; + this.data = new long[longArrayOffset + 1]; + System.arraycopy(oldData, 0, this.data, 0, oldData.length); + } + + this.data[longArrayOffset] &= (1L << bytePosition); + } + public byte[] toByteArray() { byte[] array = new byte[this.getData().length * 8]; @@ -53,4 +66,19 @@ public class Bitset { return array; } + + public byte[] toBigEndianByteArray() { + byte[] array = new byte[this.getData().length * 8]; + + for (int i = 0; i < this.getData().length; i++) { + long value = this.getData()[i]; + + for (int x = 0; x <= 7; x++) { + array[(i * 8) + x] = (byte) (value & 0xFF); + value >>= Byte.SIZE; + } + } + + return array; + } }