Implement monolith potential book

This commit is contained in:
Melledy committed 2026-10-18 23:35:41 -07:00
1 parent b7d6278b26
commit 465ccd769e
14 files changed
+403 -3

No files matched your search

@@ -118,6 +118,7 @@ public class GameData {
@Getter private static DataTable<StarTowerTeamExpDef> StarTowerTeamExpDataTable = new DataTable<>();
@Getter private static DataTable<StarTowerEventDef> StarTowerEventDataTable = new DataTable<>();
@Getter private static DataTable<StarTowerBuildRankDef> StarTowerBuildRankDataTable = new DataTable<>();
@Getter private static DataTable<StarTowerBookPotentialRewardDef> StarTowerBookPotentialRewardDataTable = new DataTable<>();
@Getter private static DataTable<SubNoteSkillPromoteGroupDef> SubNoteSkillPromoteGroupDataTable = new DataTable<>();
@Getter private static DataTable<PotentialDef> PotentialDataTable = new DataTable<>();
@@ -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<ChatDef> chats;
private transient Int2ObjectMap<StarTowerBookPotentialRewardDef> 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<>();
}
}
@@ -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);
}
}
}
@@ -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);
}
@@ -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<CharacterGemPreset> 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() {
@@ -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()
@@ -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) {
@@ -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
*/
@@ -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<GameCharacter>();
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<StarTowerPotentialPreset> 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);
}
}
}
}
@@ -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()) {
@@ -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()) {
@@ -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);
}
}
@@ -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);
}
}
@@ -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);
}
}