diff --git a/src/main/java/emu/nebula/data/GameData.java b/src/main/java/emu/nebula/data/GameData.java index ef3d3d2..982156e 100644 --- a/src/main/java/emu/nebula/data/GameData.java +++ b/src/main/java/emu/nebula/data/GameData.java @@ -118,6 +118,7 @@ public class GameData { @Getter private static DataTable StarTowerTeamExpDataTable = new DataTable<>(); @Getter private static DataTable StarTowerEventDataTable = new DataTable<>(); @Getter private static DataTable StarTowerBuildRankDataTable = new DataTable<>(); + @Getter private static DataTable StarTowerBookPotentialRewardDataTable = new DataTable<>(); @Getter private static DataTable SubNoteSkillPromoteGroupDataTable = new DataTable<>(); @Getter private static DataTable PotentialDataTable = new DataTable<>(); diff --git a/src/main/java/emu/nebula/data/resources/CharacterDef.java b/src/main/java/emu/nebula/data/resources/CharacterDef.java index cfdb69f..d805069 100644 --- a/src/main/java/emu/nebula/data/resources/CharacterDef.java +++ b/src/main/java/emu/nebula/data/resources/CharacterDef.java @@ -6,7 +6,11 @@ import emu.nebula.data.BaseDef; import emu.nebula.data.GameData; import emu.nebula.data.ResourceType; import emu.nebula.game.character.ElementType; + +import it.unimi.dsi.fastutil.ints.Int2ObjectLinkedOpenHashMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.objects.ObjectArrayList; + import lombok.Getter; @Getter @@ -36,6 +40,7 @@ public class CharacterDef extends BaseDef { private transient HonorDef honor; private transient ElementType elementType; private transient List chats; + private transient Int2ObjectMap potentialBookRewards; @Override public int getId() { @@ -67,5 +72,6 @@ public class CharacterDef extends BaseDef { public void onLoad() { this.elementType = ElementType.getByValue(this.EET); this.chats = new ObjectArrayList<>(); + this.potentialBookRewards = new Int2ObjectLinkedOpenHashMap<>(); } } diff --git a/src/main/java/emu/nebula/data/resources/StarTowerBookPotentialRewardDef.java b/src/main/java/emu/nebula/data/resources/StarTowerBookPotentialRewardDef.java new file mode 100644 index 0000000..1e8df08 --- /dev/null +++ b/src/main/java/emu/nebula/data/resources/StarTowerBookPotentialRewardDef.java @@ -0,0 +1,50 @@ +package emu.nebula.data.resources; + +import java.util.Arrays; + +import emu.nebula.data.BaseDef; +import emu.nebula.data.GameData; +import emu.nebula.data.ResourceType; +import emu.nebula.data.ResourceType.LoadPriority; + +import lombok.Getter; + +@Getter +@ResourceType(name = "StarTowerBookPotentialReward.json", loadPriority = LoadPriority.LOW) +public class StarTowerBookPotentialRewardDef extends BaseDef { + private int Id; + private int CharId; + private int Sort; + private String Params; + private int ItemId; + private int ItemQty; + + // Cached data + private transient int[] conditions; + + @Override + public int getId() { + return Id; + } + + public int getRequiredPotentials() { + if (this.conditions == null || this.conditions.length < 2) { + return Integer.MAX_VALUE; + } + + return this.conditions[1]; + } + + @Override + public void onLoad() { + this.conditions = Arrays.stream(Params.replaceAll("[\\[\\] ]", "") + .split(",")) + .mapToInt(Integer::parseInt) + .toArray(); + + var characterDef = GameData.getCharacterDataTable().get(this.CharId); + if (characterDef != null) { + characterDef.getPotentialBookRewards().put(this.getSort(), this); + } + } +} diff --git a/src/main/java/emu/nebula/game/character/CharacterStorage.java b/src/main/java/emu/nebula/game/character/CharacterStorage.java index 149018b..973cef6 100644 --- a/src/main/java/emu/nebula/game/character/CharacterStorage.java +++ b/src/main/java/emu/nebula/game/character/CharacterStorage.java @@ -50,6 +50,11 @@ public class CharacterStorage extends PlayerManager { return this.characters.get(id); } + public GameCharacter getCharacterFromPotential(int potentialId) { + int charId = (potentialId / 100) % 1000; + return this.getCharacterById(charId); + } + public boolean hasCharacter(int id) { return this.characters.containsKey(id); } diff --git a/src/main/java/emu/nebula/game/character/GameCharacter.java b/src/main/java/emu/nebula/game/character/GameCharacter.java index 6b4886f..bcf49e6 100644 --- a/src/main/java/emu/nebula/game/character/GameCharacter.java +++ b/src/main/java/emu/nebula/game/character/GameCharacter.java @@ -39,6 +39,8 @@ import emu.nebula.server.error.ErrorCode; import emu.nebula.server.error.NebulaException; import emu.nebula.util.Bitset; import emu.nebula.util.ints.CustomIntArray; +import it.unimi.dsi.fastutil.ints.Int2IntMap; +import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.ints.IntSet; @@ -74,6 +76,10 @@ public class GameCharacter implements GameDatabaseObject { // Affinity stories (Memory recollection) private IntSet plots; + // Seen potentials + private Int2IntMap seenPotentials; + private IntSet receivedPotentialRewards; + // Gems private int gemPresetIndex; private List gemPresets; @@ -103,9 +109,12 @@ public class GameCharacter implements GameDatabaseObject { this.talents = new Bitset(); this.gemPresets = new ArrayList<>(); this.gemSlots = new CharacterGemSlot[GameConstants.CHARACTER_MAX_GEM_SLOTS]; - this.createTime = Nebula.getCurrentServerTime(); + this.seenPotentials = new Int2IntOpenHashMap(); + this.receivedPotentialRewards = new IntOpenHashSet(); this.contact = new CharacterContact(this); + + this.createTime = Nebula.getCurrentServerTime(); } public void setPlayer(Player player) { @@ -942,6 +951,113 @@ public class GameCharacter implements GameDatabaseObject { return new PlayerChangeInfo(); } + // Potential book + + public Int2IntMap getSeenPotentials() { + if (this.seenPotentials == null) { + this.seenPotentials = new Int2IntOpenHashMap(); + } + + return this.seenPotentials; + } + + public int getSeenPotentialLevel(int potentialId) { + return this.getSeenPotentials().getOrDefault(potentialId, 0); + } + + public boolean hasSeenPotential(int potentialId) { + return this.getSeenPotentials().containsKey(potentialId); + } + + public boolean setSeenPotential(int potentialId, int newLevel) { + // Make sure potential is owned by this character + int charId = (potentialId / 100) % 1000; + if (charId != this.getCharId()) { + return false; + } + + // Check if we already have an equal or higher level + int curLevel = this.getSeenPotentialLevel(potentialId); + + // Skip if new level is lower than current recorded level + if (newLevel <= curLevel) { + return false; + } + + // Set the max seen level + this.getSeenPotentials().put(potentialId, newLevel); + + // Success + return true; + } + + public int getTotalSeenPotentials() { + return this.getSeenPotentials().size(); + } + + public IntSet getReceivedPotentialRewards() { + if (this.receivedPotentialRewards == null) { + this.receivedPotentialRewards = new IntOpenHashSet(); + } + + return this.receivedPotentialRewards; + } + + public PlayerChangeInfo claimPotentialBookRewards() { + var rewards = new ItemParamMap(); + var received = new IntArrayList(); + int potentials = this.getTotalSeenPotentials(); + + for (var reward : this.getData().getPotentialBookRewards().values()) { + // Skip if reward is already claimed + if (this.getReceivedPotentialRewards().contains(reward.getId())) { + continue; + } + + // Check if we meet criteria for claiming the reward + if (potentials >= reward.getRequiredPotentials()) { + // Mark as received + this.getReceivedPotentialRewards().add(reward.getId()); + + // Add to lists + received.add(reward.getId()); + rewards.add(reward.getItemId(), reward.getItemQty()); + } + } + + if (rewards.isEmpty()) { + return null; + } + + // Add rewards + var change = this.getPlayer().getInventory().addItems(rewards); + change.setExtraData(received); + + // Save to db + this.save(); + + // Success + return change; + } + + public boolean hasPendingPotentialBookRewards() { + int potentials = this.getTotalSeenPotentials(); + + for (var reward : this.getData().getPotentialBookRewards().values()) { + // Skip if reward is already claimed + if (this.getReceivedPotentialRewards().contains(reward.getId())) { + continue; + } + + // Check if we meet criteria for claiming the reward + if (potentials >= reward.getRequiredPotentials()) { + return true; + } + } + + return false; + } + // Proto public Char toProto() { diff --git a/src/main/java/emu/nebula/game/player/Player.java b/src/main/java/emu/nebula/game/player/Player.java index c81e569..f834e0d 100644 --- a/src/main/java/emu/nebula/game/player/Player.java +++ b/src/main/java/emu/nebula/game/player/Player.java @@ -1254,11 +1254,19 @@ public class Player implements GameDatabaseObject { state.getMutableTravelerDuelQuest() .setType(QuestType.TravelerDuel); state.getMutableStarTower(); - state.getMutableStarTowerBook(); state.getMutableScoreBoss(); state.getMutableCharAffinityRewards(); state.getMutableTraceHunt().setBossRewardCanReceive(this.getTraceHuntManager().isHuntComplete()); + // Star tower potential book state + var book = state.getMutableStarTowerBook(); + for (var character : this.getCharacters().getCharacterCollection()) { + // Set red dot if character has pending rewards + if (character.hasPendingPotentialBookRewards()) { + book.addCharIds(character.getCharId()); + } + } + // Force complete tutorials for (var guide : GameData.getGuideGroupDataTable()) { var info = NewbieInfo.newInstance() diff --git a/src/main/java/emu/nebula/game/player/PlayerProgress.java b/src/main/java/emu/nebula/game/player/PlayerProgress.java index 4715035..ecb9320 100644 --- a/src/main/java/emu/nebula/game/player/PlayerProgress.java +++ b/src/main/java/emu/nebula/game/player/PlayerProgress.java @@ -191,6 +191,18 @@ public class PlayerProgress extends PlayerManager implements GameDatabaseObject Nebula.getGameDatabase().update(this, this.getUid(), logName + "." + id, newStar); } + public boolean isNewPotential(int id) { + // Get character that uses this potential + var character = this.getPlayer().getCharacters().getCharacterFromPotential(id); + + if (character == null) { + return true; + } + + // Checks if the character has seen the potential yet or not + return !character.hasSeenPotential(id); + } + // Proto public void encodePlayerInfo(PlayerInfo proto) { diff --git a/src/main/java/emu/nebula/game/tower/StarTowerGame.java b/src/main/java/emu/nebula/game/tower/StarTowerGame.java index 8c81e74..ca22f61 100644 --- a/src/main/java/emu/nebula/game/tower/StarTowerGame.java +++ b/src/main/java/emu/nebula/game/tower/StarTowerGame.java @@ -202,6 +202,14 @@ public class StarTowerGame { return this.getPotentials().values().intStream().reduce(0, Integer::sum); } + public boolean isNewPotential(int id) { + if (this.getPotentials().containsKey(id)) { + return false; + } + + return this.getPlayer().getProgress().isNewPotential(id); + } + /** * Gets the team element, if the team has 2+ or more elements, then returns null */ diff --git a/src/main/java/emu/nebula/game/tower/StarTowerManager.java b/src/main/java/emu/nebula/game/tower/StarTowerManager.java index edc5aee..7c63969 100644 --- a/src/main/java/emu/nebula/game/tower/StarTowerManager.java +++ b/src/main/java/emu/nebula/game/tower/StarTowerManager.java @@ -1,10 +1,13 @@ package emu.nebula.game.tower; +import java.util.HashSet; + import emu.nebula.GameConstants; import emu.nebula.Nebula; import emu.nebula.data.GameData; import emu.nebula.data.resources.StarTowerGrowthNodeDef; import emu.nebula.game.achievement.AchievementCondition; +import emu.nebula.game.character.GameCharacter; import emu.nebula.game.player.Player; import emu.nebula.game.player.PlayerChangeInfo; import emu.nebula.game.player.PlayerManager; @@ -215,7 +218,10 @@ public class StarTowerManager extends PlayerManager { return null; } - // Add build + // Update seen potentials + this.updateSeenPotentials(build); + + // Add build for player this.addBuild(build); // Success @@ -263,6 +269,32 @@ public class StarTowerManager extends PlayerManager { return change; } + // Seen potentials + + public void updateSeenPotentials(StarTowerBuild build) { + var updateChars = new HashSet(); + + for (var entry : build.getPotentials().int2IntEntrySet()) { + int potentialId = entry.getIntKey(); + int level = entry.getIntValue(); + + var character = this.getPlayer().getCharacters().getCharacterFromPotential(potentialId); + if (character == null) continue; + + boolean success = character.setSeenPotential(potentialId, level); + + if (success) { + updateChars.add(character); + } + } + + if (updateChars.size() > 0) { + for (var character : updateChars) { + character.save(); + } + } + } + // Presets public Long2ObjectMap getPresets() { @@ -484,5 +516,16 @@ public class StarTowerManager extends PlayerManager { // Add preset this.presets.put(preset.getUid(), preset); }); + + // Migration: Check if we need to update any seen potentials + boolean needsPotentialMigration = this.builds.size() > 0 && this.getPlayer().getCharacters().getCharacterCollection() + .stream() + .allMatch(c -> c.getSeenPotentials().isEmpty()); + + if (needsPotentialMigration) { + for (var build : this.getBuilds().values()) { + this.updateSeenPotentials(build); + } + } } } diff --git a/src/main/java/emu/nebula/game/tower/cases/StarTowerPotentialCase.java b/src/main/java/emu/nebula/game/tower/cases/StarTowerPotentialCase.java index 85f9ee6..ccf84ee 100644 --- a/src/main/java/emu/nebula/game/tower/cases/StarTowerPotentialCase.java +++ b/src/main/java/emu/nebula/game/tower/cases/StarTowerPotentialCase.java @@ -174,6 +174,10 @@ public class StarTowerPotentialCase extends StarTowerBaseCase { if (potential.getLevel() > 1) { select.addLuckyIds(potential.getId()); } + + if (this.getGame().isNewPotential(potential.getId())) { + select.addNewIds(potential.getId()); + } } if (this.canReroll()) { diff --git a/src/main/java/emu/nebula/game/tower/cases/StarTowerSelectSpecialPotentialCase.java b/src/main/java/emu/nebula/game/tower/cases/StarTowerSelectSpecialPotentialCase.java index 4775c5c..c4bbfc2 100644 --- a/src/main/java/emu/nebula/game/tower/cases/StarTowerSelectSpecialPotentialCase.java +++ b/src/main/java/emu/nebula/game/tower/cases/StarTowerSelectSpecialPotentialCase.java @@ -33,6 +33,10 @@ public class StarTowerSelectSpecialPotentialCase extends StarTowerPotentialCase for (var potential : this.getPotentials()) { select.addIds(potential.getId()); + + if (this.getGame().isNewPotential(potential.getId())) { + select.addNewIds(potential.getId()); + } } if (this.canReroll()) { diff --git a/src/main/java/emu/nebula/server/handlers/HandlerStarTowerBookCharPotentialGetReq.java b/src/main/java/emu/nebula/server/handlers/HandlerStarTowerBookCharPotentialGetReq.java new file mode 100644 index 0000000..6ec6cb6 --- /dev/null +++ b/src/main/java/emu/nebula/server/handlers/HandlerStarTowerBookCharPotentialGetReq.java @@ -0,0 +1,67 @@ +package emu.nebula.server.handlers; + +import emu.nebula.net.NetHandler; +import emu.nebula.net.NetMsgId; +import emu.nebula.proto.Public.UI32; +import emu.nebula.proto.PublicStarTower.StarTowerBookPotential; +import emu.nebula.proto.StarTowerBookCharPotentialGet.StarTowerBookPotentialGetResp; +import it.unimi.dsi.fastutil.ints.IntArrayList; +import emu.nebula.net.HandlerId; +import emu.nebula.data.GameData; +import emu.nebula.net.GameSession; + +@HandlerId(NetMsgId.star_tower_book_char_potential_get_req) +public class HandlerStarTowerBookCharPotentialGetReq extends NetHandler { + + @Override + public byte[] handle(GameSession session, byte[] message) throws Exception { + // Parse character id + var req = UI32.parseFrom(message); + var character = session.getPlayer().getCharacters().getCharacterById(req.getValue()); + + if (character == null) { + return session.encodeMsg(NetMsgId.star_tower_book_char_potential_get_failed_ack); + } + + // Get potentials + var potentialDef = GameData.getCharPotentialDataTable().get(character.getCharId()); + + if (potentialDef == null) { + return session.encodeMsg(NetMsgId.star_tower_book_char_potential_get_failed_ack); + } + + // Build response + var rsp = StarTowerBookPotentialGetResp.newInstance(); + + // Get count + var potentials = new IntArrayList(); + + potentials.addElements(potentials.size(), potentialDef.getMasterSpecificPotentialIds()); + potentials.addElements(potentials.size(), potentialDef.getAssistSpecificPotentialIds()); + potentials.addElements(potentials.size(), potentialDef.getCommonPotentialIds()); + potentials.addElements(potentials.size(), potentialDef.getMasterNormalPotentialIds()); + potentials.addElements(potentials.size(), potentialDef.getAssistNormalPotentialIds()); + + // Serialize potentials + for (var potentialId : potentials) { + int level = character.getSeenPotentialLevel(potentialId); + + if (level > 0) { + var info = StarTowerBookPotential.newInstance() + .setId(potentialId) + .setLevel(level); + + rsp.addPotentials(info); + } + } + + // Add received reward ids + for (int rewardId : character.getReceivedPotentialRewards()) { + rsp.addReceivedIds(rewardId); + } + + // Encode and send + return session.encodeMsg(NetMsgId.star_tower_book_char_potential_get_succeed_ack, rsp); + } + +} diff --git a/src/main/java/emu/nebula/server/handlers/HandlerStarTowerBookPotentialBriefListGetReq.java b/src/main/java/emu/nebula/server/handlers/HandlerStarTowerBookPotentialBriefListGetReq.java new file mode 100644 index 0000000..cb9ffba --- /dev/null +++ b/src/main/java/emu/nebula/server/handlers/HandlerStarTowerBookPotentialBriefListGetReq.java @@ -0,0 +1,33 @@ +package emu.nebula.server.handlers; + +import emu.nebula.net.NetHandler; +import emu.nebula.net.NetMsgId; +import emu.nebula.proto.PublicStarTower.StarTowerBookCharPotentialBrief; +import emu.nebula.proto.StarTowerBookPotentialBriefListGet.StarTowerBookPotentialBriefListResp; +import emu.nebula.net.HandlerId; +import emu.nebula.net.GameSession; + +@HandlerId(NetMsgId.star_tower_book_potential_brief_list_get_req) +public class HandlerStarTowerBookPotentialBriefListGetReq extends NetHandler { + + @Override + public byte[] handle(GameSession session, byte[] message) throws Exception { + // Build response + var rsp = StarTowerBookPotentialBriefListResp.newInstance(); + + // Get all characters + var characters = session.getPlayer().getCharacters().getCharacterCollection(); + + for (var character : characters) { + var info = StarTowerBookCharPotentialBrief.newInstance() + .setCharId(character.getCharId()) + .setCount(character.getTotalSeenPotentials()); + + rsp.addInfos(info); + } + + // Encode and send + return session.encodeMsg(NetMsgId.star_tower_book_potential_brief_list_get_succeed_ack, rsp); + } + +} diff --git a/src/main/java/emu/nebula/server/handlers/HandlerStarTowerBookPotentialRewardReceiveReq.java b/src/main/java/emu/nebula/server/handlers/HandlerStarTowerBookPotentialRewardReceiveReq.java new file mode 100644 index 0000000..b7c6e51 --- /dev/null +++ b/src/main/java/emu/nebula/server/handlers/HandlerStarTowerBookPotentialRewardReceiveReq.java @@ -0,0 +1,43 @@ +package emu.nebula.server.handlers; + +import emu.nebula.net.NetHandler; +import emu.nebula.net.NetMsgId; +import emu.nebula.proto.Public.UI32; +import emu.nebula.proto.StarTowerBookPotentialRewardReceive.StarTowerBookPotentialRewardReceiveResp; +import it.unimi.dsi.fastutil.ints.IntList; +import emu.nebula.net.HandlerId; +import emu.nebula.net.GameSession; + +@HandlerId(NetMsgId.star_tower_book_potential_reward_receive_req) +public class HandlerStarTowerBookPotentialRewardReceiveReq extends NetHandler { + + @Override + public byte[] handle(GameSession session, byte[] message) throws Exception { + // Parse character id + var req = UI32.parseFrom(message); + var character = session.getPlayer().getCharacters().getCharacterById(req.getValue()); + + if (character == null) { + return session.encodeMsg(NetMsgId.star_tower_book_potential_reward_receive_failed_ack); + } + + // Claim rewards + var change = character.claimPotentialBookRewards(); + + if (change == null) { + return session.encodeMsg(NetMsgId.star_tower_book_potential_reward_receive_failed_ack); + } + + // Build response + var rsp = StarTowerBookPotentialRewardReceiveResp.newInstance() + .setChange(change.toProto()); + + // Add list of received reward ids to response + var received = (IntList) change.getExtraData(); + received.forEach(rsp::addReceivedIds); + + // Encode and send + return session.encodeMsg(NetMsgId.star_tower_book_potential_reward_receive_succeed_ack, rsp); + } + +}