diff --git a/src/main/java/emu/nebula/data/GameData.java b/src/main/java/emu/nebula/data/GameData.java index 992be53..b30b715 100644 --- a/src/main/java/emu/nebula/data/GameData.java +++ b/src/main/java/emu/nebula/data/GameData.java @@ -97,9 +97,11 @@ public class GameData { @Getter private static DataTable MainScreenCGDataTable = new DataTable<>(); - // ===== Daily Quests ===== + // ===== Daily/Weekly Quests ===== @Getter private static DataTable DailyQuestDataTable = new DataTable<>(); @Getter private static DataTable DailyQuestActiveDataTable = new DataTable<>(); + @Getter private static DataTable WeeklyQuestDataTable = new DataTable<>(); + @Getter private static DataTable WeeklyQuestActiveDataTable = new DataTable<>(); // ===== Achievements ===== @Getter private static DataTable AchievementDataTable = new DataTable<>(); diff --git a/src/main/java/emu/nebula/data/resources/BattlePassQuestDef.java b/src/main/java/emu/nebula/data/resources/BattlePassQuestDef.java index 5ed4175..25b0d78 100644 --- a/src/main/java/emu/nebula/data/resources/BattlePassQuestDef.java +++ b/src/main/java/emu/nebula/data/resources/BattlePassQuestDef.java @@ -23,6 +23,11 @@ public class BattlePassQuestDef extends BaseDef implements QuestData { return Id; } + @Override + public long getQuestKey() { + return Id; + } + public boolean isDaily() { return this.Type == 1; } diff --git a/src/main/java/emu/nebula/data/resources/WeeklyQuestActiveDef.java b/src/main/java/emu/nebula/data/resources/WeeklyQuestActiveDef.java new file mode 100644 index 0000000..3ae7e31 --- /dev/null +++ b/src/main/java/emu/nebula/data/resources/WeeklyQuestActiveDef.java @@ -0,0 +1,37 @@ +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 +@ResourceType(name = "WeeklyQuestActive.json") +public class WeeklyQuestActiveDef extends BaseDef { + private int Id; + private int Active; + + private int ItemTid1; + private int Number1; + private int ItemTid2; + private int Number2; + + private transient ItemParamMap rewards; + + @Override + public int getId() { + return Id; + } + + @Override + public void onLoad() { + this.rewards = new ItemParamMap(); + + if (this.ItemTid1 > 0 && this.Number1 > 0) { + this.rewards.add(this.ItemTid1, this.Number1); + } + if (this.ItemTid2 > 0 && this.Number2 > 0) { + this.rewards.add(this.ItemTid2, this.Number2); + } + } +} diff --git a/src/main/java/emu/nebula/data/resources/WeeklyQuestDef.java b/src/main/java/emu/nebula/data/resources/WeeklyQuestDef.java new file mode 100644 index 0000000..6a32cea --- /dev/null +++ b/src/main/java/emu/nebula/data/resources/WeeklyQuestDef.java @@ -0,0 +1,49 @@ +package emu.nebula.data.resources; + +import java.util.Arrays; + +import emu.nebula.data.BaseDef; +import emu.nebula.data.ResourceType; +import emu.nebula.game.quest.QuestData; +import emu.nebula.game.quest.QuestType; +import lombok.Getter; + +@Getter +@ResourceType(name = "WeeklyQuest.json") +public class WeeklyQuestDef extends BaseDef implements QuestData { + private int Id; + private boolean Apear; + private int Active; + private int ItemTid; + private int ItemQty; + + private int CompleteCond; + private int CompleteCondClient; + private String CompleteCondParams; + + private transient int[] condParams; + + @Override + public int getId() { + return Id; + } + + @Override + public int getQuestType() { + return QuestType.Weekly; + } + + public int[] getCompleteCondParams() { + return this.condParams; + } + + @Override + public void onLoad() { + this.condParams = Arrays.stream(this.CompleteCondParams.split("[\\[,\\]]")) + .filter(s -> !s.isEmpty()) + .mapToInt(Integer::parseInt) + .toArray(); + + this.CompleteCondParams = null; + } +} diff --git a/src/main/java/emu/nebula/game/player/Player.java b/src/main/java/emu/nebula/game/player/Player.java index c10714e..d10eba5 100644 --- a/src/main/java/emu/nebula/game/player/Player.java +++ b/src/main/java/emu/nebula/game/player/Player.java @@ -687,7 +687,7 @@ public class Player implements GameDatabaseObject { public void resetDailies(boolean resetWeekly, boolean resetMonthly) { // Reset daily quests - this.getQuestManager().resetDailyQuests(); + this.getQuestManager().resetDailyQuests(resetWeekly); this.getBattlePassManager().getBattlePass().resetDailyQuests(resetWeekly); // Check to reset weeklies @@ -740,6 +740,7 @@ public class Player implements GameDatabaseObject { if (manager != null) { manager.setPlayer(this); + manager.onLoad(); } else { try { manager = cls.getDeclaredConstructor(Player.class).newInstance(this); diff --git a/src/main/java/emu/nebula/game/player/PlayerManager.java b/src/main/java/emu/nebula/game/player/PlayerManager.java index 065d0b6..7707752 100644 --- a/src/main/java/emu/nebula/game/player/PlayerManager.java +++ b/src/main/java/emu/nebula/game/player/PlayerManager.java @@ -24,4 +24,11 @@ public abstract class PlayerManager { public int getPlayerUid() { return this.getPlayer().getUid(); } + + /** + * Called when the manager is loaded from the database + */ + public void onLoad() { + + } } diff --git a/src/main/java/emu/nebula/game/quest/GameQuest.java b/src/main/java/emu/nebula/game/quest/GameQuest.java index 0777b84..2830526 100644 --- a/src/main/java/emu/nebula/game/quest/GameQuest.java +++ b/src/main/java/emu/nebula/game/quest/GameQuest.java @@ -35,6 +35,10 @@ public class GameQuest { this.param = data.getCompleteCondParams()[1]; } } + + public long getKey() { + return ((long) this.getType() << 32) + this.getId(); + } public void resetProgress() { this.curProgress = 0; @@ -45,6 +49,10 @@ public class GameQuest { return this.curProgress >= this.maxProgress; } + public boolean canClaim() { + return this.isComplete() && !this.isClaimed(); + } + private int getStatus() { if (this.isClaimed()) { return 2; diff --git a/src/main/java/emu/nebula/game/quest/QuestData.java b/src/main/java/emu/nebula/game/quest/QuestData.java index d59e013..f7026a1 100644 --- a/src/main/java/emu/nebula/game/quest/QuestData.java +++ b/src/main/java/emu/nebula/game/quest/QuestData.java @@ -2,8 +2,18 @@ package emu.nebula.game.quest; public interface QuestData { + /** + * Quest id, used by the client + */ public int getId(); + /** + * This is the computed key for the quest in hashmaps + */ + public default long getQuestKey() { + return ((long) this.getQuestType() << 32) + this.getId(); + } + public int getQuestType(); public int getCompleteCond(); diff --git a/src/main/java/emu/nebula/game/quest/QuestManager.java b/src/main/java/emu/nebula/game/quest/QuestManager.java index 0f75656..4ccdc07 100644 --- a/src/main/java/emu/nebula/game/quest/QuestManager.java +++ b/src/main/java/emu/nebula/game/quest/QuestManager.java @@ -1,6 +1,7 @@ package emu.nebula.game.quest; import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; import java.util.Map; @@ -34,9 +35,10 @@ public class QuestManager extends PlayerManager implements GameDatabaseObject { // Daily activity missions private IntSet claimedActiveIds; + private IntSet claimedWeeklyIds; // Quests - private Map quests; + private Map list; // Level rewards private Bitset levelRewards; @@ -53,15 +55,30 @@ public class QuestManager extends PlayerManager implements GameDatabaseObject { super(player); this.uid = player.getUid(); this.claimedActiveIds = new IntOpenHashSet(); - this.quests = new HashMap<>(); + this.claimedWeeklyIds = new IntOpenHashSet(); + this.list = new HashMap<>(); this.levelRewards = new Bitset(); this.hasDailyReward = true; - this.resetDailyQuests(); + this.resetDailyQuests(true); this.save(); } + public synchronized GameQuest getQuest(int type, int questId) { + long key = ((long) type << 32) + questId; + return this.list.get(key); + } + + public synchronized GameQuest addQuest(GameQuest quest) { + this.list.put(quest.getKey(), quest); + return quest; + } + + public synchronized Collection getQuests() { + return this.getList().values(); + } + public boolean hasDailyReward() { return this.hasDailyReward; } @@ -70,34 +87,15 @@ public class QuestManager extends PlayerManager implements GameDatabaseObject { Nebula.getGameDatabase().update(this, this.getUid(), "levelRewards", this.levelRewards); } - public synchronized int getActivity() { - int activity = 0; - - for (var quest : getQuests().values()) { - if (quest.getType() != QuestType.Daily) { - continue; - } - - if (!quest.isClaimed()) { - continue; - } - - var data = GameData.getDailyQuestDataTable().get(quest.getId()); - if (data == null) { - continue; - } - - activity += data.getActive(); - } - - return activity; - } - - public synchronized void resetDailyQuests() { + public synchronized void resetDailyQuests(boolean resetWeekly) { // Reset daily quests for (var data : GameData.getDailyQuestDataTable()) { // Get quest - var quest = getQuests().computeIfAbsent(data.getId(), i -> new GameQuest(data)); + var quest = getList().get(data.getQuestKey()); + + if (quest == null) { + quest = this.addQuest(new GameQuest(data)); + } // Reset progress quest.resetProgress(); @@ -106,8 +104,31 @@ public class QuestManager extends PlayerManager implements GameDatabaseObject { this.syncQuest(quest); } + // Reset weekly quests + if (resetWeekly) { + for (var data : GameData.getWeeklyQuestDataTable()) { + // Get quest + var quest = getList().get(data.getQuestKey()); + + if (quest == null) { + quest = this.addQuest(new GameQuest(data)); + } + + // Reset progress + quest.resetProgress(); + + // Sync quest with player client + this.syncQuest(quest); + } + + // Reset weekly ids + this.claimedWeeklyIds.clear(); + } + // Reset activity this.claimedActiveIds.clear(); + + // Reset daily shop reward this.hasDailyReward = true; // Persist to database @@ -115,7 +136,11 @@ public class QuestManager extends PlayerManager implements GameDatabaseObject { } public synchronized void trigger(int condition, int progress, int param1, int param2) { - for (var quest : getQuests().values()) { + // TODO optimize so we dont loop through every quest + for (var entry : getList().entrySet()) { + // Get quest from entry + var quest = entry.getValue(); + // Try to trigger quest boolean result = quest.trigger(condition, progress, param1, param2); @@ -128,7 +153,7 @@ public class QuestManager extends PlayerManager implements GameDatabaseObject { this.syncQuest(quest); // Update in database - Nebula.getGameDatabase().update(this, this.getUid(), "quests." + quest.getId(), quest); + Nebula.getGameDatabase().update(this, this.getUid(), "list." + entry.getKey(), quest); } } @@ -146,21 +171,48 @@ public class QuestManager extends PlayerManager implements GameDatabaseObject { ); } - public PlayerChangeInfo receiveQuestReward(int questId) { + // Daily quests + + public synchronized int getDailyActivity() { + int activity = 0; + + for (var quest : this.getQuests()) { + if (quest.getType() != QuestType.Daily) { + continue; + } + + if (!quest.isClaimed()) { + continue; + } + + var data = GameData.getDailyQuestDataTable().get(quest.getId()); + if (data == null) continue; + + activity += data.getActive(); + } + + return activity; + } + + public PlayerChangeInfo receiveDailyQuestReward(int questId) { // Get received quests var claimList = new ArrayList(); if (questId > 0) { // Claim specific quest - var quest = this.getQuests().get(questId); + var quest = this.getQuest(QuestType.Daily, questId); if (quest != null && !quest.isClaimed()) { claimList.add(quest); } } else { // Claim all - for (var quest : this.getQuests().values()) { - if (!quest.isComplete() || quest.isClaimed()) { + for (var quest : this.getQuests()) { + if (quest.getType() != QuestType.Daily) { + continue; + } + + if (!quest.canClaim()) { continue; } @@ -189,7 +241,7 @@ public class QuestManager extends PlayerManager implements GameDatabaseObject { quest.setClaimed(true); // Update in database - Nebula.getGameDatabase().update(this, this.getUid(), "quests." + quest.getId(), quest); + Nebula.getGameDatabase().update(this, this.getUid(), "list." + quest.getKey(), quest); } // Trigger quest @@ -199,12 +251,12 @@ public class QuestManager extends PlayerManager implements GameDatabaseObject { return change.setSuccess(true); } - public PlayerChangeInfo claimActiveRewards() { + public PlayerChangeInfo claimDailyActiveRewards() { // Init var claimList = new IntArrayList(); var rewards = new ItemParamMap(); - int activity = this.getActivity(); + int activity = this.getDailyActivity(); // Get claimable for (var data : GameData.getDailyQuestActiveDataTable()) { @@ -240,6 +292,129 @@ public class QuestManager extends PlayerManager implements GameDatabaseObject { return change.setSuccess(true); } + // Weekly quests + + public synchronized int getWeeklyActivity() { + int activity = 0; + + for (var quest : getQuests()) { + if (quest.getType() != QuestType.Weekly) { + continue; + } + + if (!quest.isClaimed()) { + continue; + } + + var data = GameData.getWeeklyQuestDataTable().get(quest.getId()); + if (data == null) continue; + + activity += data.getActive(); + } + + return activity; + } + + public PlayerChangeInfo receiveWeeklyQuestReward(int questId) { + // Get received quests + var claimList = new ArrayList(); + + if (questId > 0) { + // Claim specific quest + var quest = this.getQuest(QuestType.Weekly, questId); + + if (quest != null && !quest.isClaimed()) { + claimList.add(quest); + } + } else { + // Claim all + for (var quest : this.getQuests()) { + if (quest.getType() != QuestType.Weekly) { + continue; + } + + if (!quest.canClaim()) { + continue; + } + + claimList.add(quest); + } + } + + // Sanity check + if (claimList.isEmpty()) { + return null; + } + + // Create change info + var change = new PlayerChangeInfo(); + + // Claim + for (var quest : claimList) { + // Get data + var data = GameData.getWeeklyQuestDataTable().get(quest.getId()); + if (data != null) { + // Add reward data + this.getPlayer().getInventory().addItem(data.getItemTid(), data.getItemQty(), change); + } + + // Set claimed + quest.setClaimed(true); + + // Update in database + Nebula.getGameDatabase().update(this, this.getUid(), "list." + quest.getKey(), quest); + } + + // Trigger quest + this.getPlayer().trigger(QuestCondition.QuestWithSpecificType, claimList.size(), QuestType.Weekly); + + // Success + return change.setSuccess(true); + } + + public PlayerChangeInfo claimWeeklyActiveRewards() { + // Init + var claimList = new IntArrayList(); + var rewards = new ItemParamMap(); + + int activity = this.getWeeklyActivity(); + + // Get claimable + for (var data : GameData.getWeeklyQuestActiveDataTable()) { + if (this.getClaimedWeeklyIds().contains(data.getId())) { + continue; + } + + if (activity >= data.getActive()) { + // Add rewards + rewards.add(data.getRewards()); + + // Add to claimed activity list + claimList.add(data.getId()); + } + } + + // Sanity check + if (claimList.isEmpty()) { + return null; + } + + // Add rewards + var change = this.getPlayer().getInventory().addItems(rewards); + + // Set claimed list + change.setExtraData(claimList); + + // Update in database + this.getClaimedWeeklyIds().addAll(claimList); + Nebula.getGameDatabase().update(this, this.getUid(), "claimedWeeklyIds", this.getClaimedWeeklyIds()); + + // Success + return change.setSuccess(true); + } + + // World level rewards + public PlayerChangeInfo receiveWorldClassReward(int id) { // Get rewards we want to claim var claimList = new ArrayList(); @@ -287,9 +462,9 @@ public class QuestManager extends PlayerManager implements GameDatabaseObject { return change.setSuccess(true); } - // Daily reward + // Daily shop reward - public PlayerChangeInfo claimDailyReward() { + public PlayerChangeInfo claimDailyShopGift() { // Sanity check if (!this.hasDailyReward) { return null; @@ -310,12 +485,40 @@ public class QuestManager extends PlayerManager implements GameDatabaseObject { return change.setSuccess(true); } + // Database + + @Override + public void onLoad() { + boolean shouldSave = false; + + // Fix missing weekly quests + if (this.claimedWeeklyIds == null) { + this.claimedWeeklyIds = new IntOpenHashSet(); + shouldSave = true; + } + + // Fix quest lists + if (this.getList() == null) { + this.list = new HashMap<>(); + this.resetDailyQuests(true); + + // QuestManager::resetDailyQuests will save to the database, so we dont need to set the shouldSave flag + shouldSave = false; + } + + // Save to database + if (shouldSave) { + this.save(); + } + } + // Serialization public void encodePlayerInfo(PlayerInfo proto) { + // Quests proto var quests = proto.getMutableQuests(); - for (var quest : this.getQuests().values()) { + for (var quest : this.getQuests()) { quests.addList(quest.toProto()); } @@ -324,6 +527,10 @@ public class QuestManager extends PlayerManager implements GameDatabaseObject { proto.addDailyActiveIds(id); } + for (int id : this.getClaimedWeeklyIds()) { + proto.addWeeklyActiveIds(id); + } + // Set world level rewards proto.getMutableState() .getMutableWorldClassReward() diff --git a/src/main/java/emu/nebula/game/quest/QuestType.java b/src/main/java/emu/nebula/game/quest/QuestType.java index 35dbf78..2f34453 100644 --- a/src/main/java/emu/nebula/game/quest/QuestType.java +++ b/src/main/java/emu/nebula/game/quest/QuestType.java @@ -1,5 +1,6 @@ package emu.nebula.game.quest; +// From public.proto public class QuestType { public static final int UnknownQuest = 0; public static final int TourGuide = 1; @@ -14,4 +15,6 @@ public class QuestType { public static final int Tower = 10; public static final int Demon = 11; public static final int TowerEvent = 12; + public static final int Weekly = 13; + public static final int Assist = 14; } diff --git a/src/main/java/emu/nebula/server/handlers/HandlerDailyShopRewardReceiveReq.java b/src/main/java/emu/nebula/server/handlers/HandlerDailyShopRewardReceiveReq.java index e1d7766..a8642a3 100644 --- a/src/main/java/emu/nebula/server/handlers/HandlerDailyShopRewardReceiveReq.java +++ b/src/main/java/emu/nebula/server/handlers/HandlerDailyShopRewardReceiveReq.java @@ -11,7 +11,7 @@ public class HandlerDailyShopRewardReceiveReq extends NetHandler { @Override public byte[] handle(GameSession session, byte[] message) throws Exception { // Claim daily reward - var change = session.getPlayer().getQuestManager().claimDailyReward(); + var change = session.getPlayer().getQuestManager().claimDailyShopGift(); if (change == null) { return session.encodeMsg(NetMsgId.daily_shop_reward_receive_failed_ack); diff --git a/src/main/java/emu/nebula/server/handlers/HandlerQuestDailyActiveRewardReceiveReq.java b/src/main/java/emu/nebula/server/handlers/HandlerQuestDailyActiveRewardReceiveReq.java index b359121..9532599 100644 --- a/src/main/java/emu/nebula/server/handlers/HandlerQuestDailyActiveRewardReceiveReq.java +++ b/src/main/java/emu/nebula/server/handlers/HandlerQuestDailyActiveRewardReceiveReq.java @@ -13,7 +13,7 @@ public class HandlerQuestDailyActiveRewardReceiveReq extends NetHandler { @Override public byte[] handle(GameSession session, byte[] message) throws Exception { // Receive rewards - var change = session.getPlayer().getQuestManager().claimActiveRewards(); + var change = session.getPlayer().getQuestManager().claimDailyActiveRewards(); if (change == null) { return session.encodeMsg(NetMsgId.quest_daily_active_reward_receive_failed_ack); diff --git a/src/main/java/emu/nebula/server/handlers/HandlerQuestDailyRewardReceiveReq.java b/src/main/java/emu/nebula/server/handlers/HandlerQuestDailyRewardReceiveReq.java index fb010ac..30ebd17 100644 --- a/src/main/java/emu/nebula/server/handlers/HandlerQuestDailyRewardReceiveReq.java +++ b/src/main/java/emu/nebula/server/handlers/HandlerQuestDailyRewardReceiveReq.java @@ -15,13 +15,13 @@ public class HandlerQuestDailyRewardReceiveReq extends NetHandler { var req = UI32.parseFrom(message); // Receive rewards - var change = session.getPlayer().getQuestManager().receiveQuestReward(req.getValue()); + var change = session.getPlayer().getQuestManager().receiveDailyQuestReward(req.getValue()); if (change == null) { return session.encodeMsg(NetMsgId.quest_daily_reward_receive_failed_ack); } - // Template + // Encode and send return session.encodeMsg(NetMsgId.quest_daily_reward_receive_succeed_ack, change.toProto()); } diff --git a/src/main/java/emu/nebula/server/handlers/HandlerQuestWeeklyActiveRewardReceiveReq.java b/src/main/java/emu/nebula/server/handlers/HandlerQuestWeeklyActiveRewardReceiveReq.java new file mode 100644 index 0000000..a217cec --- /dev/null +++ b/src/main/java/emu/nebula/server/handlers/HandlerQuestWeeklyActiveRewardReceiveReq.java @@ -0,0 +1,34 @@ +package emu.nebula.server.handlers; + +import emu.nebula.net.NetHandler; +import emu.nebula.net.NetMsgId; +import emu.nebula.proto.QuestDailyActiveRewardRecevie.QuestDailyActiveRewardReceiveResp; +import it.unimi.dsi.fastutil.ints.IntList; +import emu.nebula.net.HandlerId; +import emu.nebula.net.GameSession; + +@HandlerId(NetMsgId.quest_weekly_active_reward_receive_req) +public class HandlerQuestWeeklyActiveRewardReceiveReq extends NetHandler { + + @Override + public byte[] handle(GameSession session, byte[] message) throws Exception { + // Receive rewards + var change = session.getPlayer().getQuestManager().claimWeeklyActiveRewards(); + + if (change == null) { + return session.encodeMsg(NetMsgId.quest_weekly_active_reward_receive_failed_ack); + } + + // Build response + var rsp = QuestDailyActiveRewardReceiveResp.newInstance() + .setChange(change.toProto()); + + if (change.getExtraData() != null && change.getExtraData() instanceof IntList ids) { + ids.forEach(rsp::addActiveIds); + } + + // Encode and send + return session.encodeMsg(NetMsgId.quest_weekly_active_reward_receive_succeed_ack, rsp); + } + +} diff --git a/src/main/java/emu/nebula/server/handlers/HandlerQuestWeeklyRewardReceiveReq.java b/src/main/java/emu/nebula/server/handlers/HandlerQuestWeeklyRewardReceiveReq.java new file mode 100644 index 0000000..65a221f --- /dev/null +++ b/src/main/java/emu/nebula/server/handlers/HandlerQuestWeeklyRewardReceiveReq.java @@ -0,0 +1,28 @@ +package emu.nebula.server.handlers; + +import emu.nebula.net.NetHandler; +import emu.nebula.net.NetMsgId; +import emu.nebula.proto.Public.UI32; +import emu.nebula.net.HandlerId; +import emu.nebula.net.GameSession; + +@HandlerId(NetMsgId.quest_weekly_reward_receive_req) +public class HandlerQuestWeeklyRewardReceiveReq extends NetHandler { + + @Override + public byte[] handle(GameSession session, byte[] message) throws Exception { + // Parse request + var req = UI32.parseFrom(message); + + // Receive rewards + var change = session.getPlayer().getQuestManager().receiveWeeklyQuestReward(req.getValue()); + + if (change == null) { + return session.encodeMsg(NetMsgId.quest_weekly_reward_receive_failed_ack); + } + + // Encode and send + return session.encodeMsg(NetMsgId.quest_weekly_reward_receive_succeed_ack, change.toProto()); + } + +}