Implement monolith vita plans

This commit is contained in:
Melledy committed 2026-10-21 05:58:19 -07:00
1 parent fbbfb20656
commit 9271c3431f
18 files changed
+636 -10

No files matched your search

@@ -49,6 +49,7 @@ public class GameConstants {
public static final int MAX_SHOWCASE_IDS = 5;
public static final int MAX_BUILDS = 100;
public static final int MAX_PRESETS = 50;
public static final int MAX_VITA_PROJECTS = 5;
public static final int MAX_FRIENDSHIPS = 50;
public static final int MAX_PENDING_FRIENDSHIPS = 30;
@@ -62,6 +63,14 @@ public class GameConstants {
public static final int[] TOWER_EVENTS_IDS = new int[] {
101, 102, 104, 105, 106, 107, 108, 114, 115, 116, 126, 127, 128
};
// Star Tower Vita (auto decision)
public static final int VITA_ENERGY_ITEM_ID = 42;
public static final int VITA_ENERGY_MAX = 10_000;
public static final int VITA_ENERGY_PURCHASE_CURRENCY_ID = STELLANITE_DUST_ITEM_ID;
public static final int VITA_ENERGY_PURCHASE_PRICE = 10;
public static final int VITA_ENERGY_PURCHASE_VALUE = 3_000;
public static final int VITA_ENERGY_COST = 15;
// Trace Hunt
public static final int TRACE_HUNT_TOKEN_ID = 37;
@@ -120,6 +120,7 @@ public class GameData {
@Getter private static DataTable<StarTowerQuestDef> StarTowerQuestDataTable = new DataTable<>();
@Getter private static DataTable<StarTowerBuildRankDef> StarTowerBuildRankDataTable = new DataTable<>();
@Getter private static DataTable<StarTowerBookPotentialRewardDef> StarTowerBookPotentialRewardDataTable = new DataTable<>();
@Getter private static DataTable<TowerVitaOptionDef> TowerVitaOptionDataTable = new DataTable<>();
@Getter private static DataTable<SubNoteSkillPromoteGroupDef> SubNoteSkillPromoteGroupDataTable = new DataTable<>();
@Getter private static DataTable<PotentialDef> PotentialDataTable = new DataTable<>();
@@ -0,0 +1,33 @@
package emu.nebula.data.resources;
import emu.nebula.data.BaseDef;
import emu.nebula.data.ResourceType;
import lombok.Getter;
@Getter
@ResourceType(name = "TowerVitaOption.json")
public class TowerVitaOptionDef extends BaseDef {
private int Id;
private int Categories;
private int Sort;
private int OptionType;
private int OptionGroup;
private String DefaultValue;
@Override
public int getId() {
return Id;
}
public boolean isSwitch() {
return this.OptionType == 1;
}
public boolean isInteger() {
return this.OptionType >= 2 && this.OptionType <= 4;
}
public boolean isList() {
return this.OptionType == 5;
}
}
@@ -366,10 +366,19 @@ public class Inventory extends PlayerManager implements GameDatabaseObject {
if (amount > 0) {
// Add resource
newAmount = Utils.safeAdd(oldAmount, amount);
// Limits
if (id == GameConstants.VITA_ENERGY_ITEM_ID) {
newAmount = Math.min(newAmount, GameConstants.VITA_ENERGY_MAX);
}
// Get diff
diff = newAmount - oldAmount;
// Set
this.resources.put(id, newAmount);
if (diff != 0) {
this.resources.put(id, newAmount);
}
} else {
// Remove resource
newAmount = Utils.safeSubtract(oldAmount, Math.abs(amount));
@@ -10,6 +10,7 @@ import emu.nebula.Nebula;
import emu.nebula.data.GameData;
import emu.nebula.data.resources.InfinityTowerLevelDef;
import emu.nebula.database.GameDatabaseObject;
import emu.nebula.game.tower.StarTowerVitaData;
import emu.nebula.game.tutorial.TutorialLevelLog;
import emu.nebula.game.vampire.VampireSurvivorLog;
import emu.nebula.proto.PlayerData.PlayerInfo;
@@ -36,6 +37,7 @@ public class PlayerProgress extends PlayerManager implements GameDatabaseObject
private IntSet starTowerLog;
private int[] starTowerGrowth;
private int towerTickets;
private StarTowerVitaData towerVitaData;
// Instances
private Int2IntMap dailyInstanceLog;
@@ -91,6 +93,9 @@ public class PlayerProgress extends PlayerManager implements GameDatabaseObject
// Tutorials
this.tutorialLog = new HashMap<>();
// Star Tower Vita
this.towerVitaData = new StarTowerVitaData(this);
// Save to database
this.save();
}
@@ -328,6 +333,14 @@ public class PlayerProgress extends PlayerManager implements GameDatabaseObject
shouldSave = true;
}
// Set star tower vita data
if (this.towerVitaData == null) {
this.towerVitaData = new StarTowerVitaData(this);
shouldSave = true;
} else {
this.towerVitaData.setPlayerProgress(this);
}
// Carry over infinity tower progress
if (this.infinityArenaLog != null) {
for (int levelId : this.infinityArenaLog.values()) {
@@ -1,6 +1,8 @@
package emu.nebula.game.tower;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import emu.nebula.GameConstants;
import emu.nebula.Nebula;
@@ -13,6 +15,7 @@ import emu.nebula.game.player.PlayerChangeInfo;
import emu.nebula.game.player.PlayerManager;
import emu.nebula.game.player.PlayerProgress;
import emu.nebula.game.quest.QuestCondition;
import emu.nebula.proto.Public.TowerVitaProject;
import emu.nebula.proto.PublicStarTower.StarTowerBookCharPotential;
import emu.nebula.proto.StarTowerApply.StarTowerApplyReq;
@@ -375,6 +378,96 @@ public class StarTowerManager extends PlayerManager {
return true;
}
// Vita projects (auto decision plans)
public StarTowerVitaData getVitaData() {
return this.getProgress().getTowerVitaData();
}
public List<StarTowerVitaProject> getVitaProjects() {
return this.getVitaData().getProjects().values().stream()
.sorted(Comparator.comparingInt(StarTowerVitaProject::getId))
.toList();
}
public StarTowerVitaProject getVitaProject(int id) {
return this.getVitaData().getProject(id);
}
public StarTowerVitaProject updateVitaProject(TowerVitaProject proto) {
// Sanity check
if (proto == null || proto.getId() < 1 || proto.getId() > GameConstants.MAX_VITA_PROJECTS) {
return null;
}
// Get or create the project
var data = this.getVitaData();
var project = data.getProject(proto.getId());
if (project == null) {
project = new StarTowerVitaProject(proto.getId());
}
// Update name
if (proto.hasName()) {
project.setName(proto.getName());
}
// The client only sends options that differ from the defaults, so replace the whole list
project.getOptions().clear();
for (var optionProto : proto.getOptions()) {
var option = StarTowerVitaOption.fromProto(optionProto);
if (option != null) {
project.putOption(option);
}
}
// Save project to database
data.putProject(project);
// Success
return project;
}
public StarTowerVitaProject renameVitaProject(int id, String name) {
// Sanity check
if (id < 1 || id > GameConstants.MAX_VITA_PROJECTS) {
return null;
}
// Get or create the project
var data = this.getVitaData();
var project = data.getProject(id);
if (project == null) {
project = new StarTowerVitaProject(id);
}
// Update name
project.setName(name);
// Save project to database
data.putProject(project);
// Success
return project;
}
public boolean selectVitaProject(int id) {
// Sanity check
if (id < 1 || id > GameConstants.MAX_VITA_PROJECTS) {
return false;
}
// Save selection to database
this.getVitaData().setSelectedProjectId(id);
// Success
return true;
}
// Game
public PlayerChangeInfo apply(StarTowerApplyReq req) {
@@ -0,0 +1,134 @@
package emu.nebula.game.tower;
import java.util.HashMap;
import java.util.Map;
import dev.morphia.annotations.Entity;
import emu.nebula.GameConstants;
import emu.nebula.Nebula;
import emu.nebula.game.player.PlayerChangeInfo;
import emu.nebula.game.player.PlayerProgress;
import lombok.Getter;
@Getter
@Entity(useDiscriminator = false)
public class StarTowerVitaData {
private int selectedProjectId;
private Map<Integer, StarTowerVitaProject> projects;
private transient PlayerProgress progress;
@Deprecated // Morphia only
public StarTowerVitaData() {
}
public StarTowerVitaData(PlayerProgress progress) {
this.setPlayerProgress(progress);
this.selectedProjectId = 1;
this.projects = new HashMap<>();
}
public void setPlayerProgress(PlayerProgress progress) {
this.progress = progress;
}
public StarTowerVitaProject getProject(int id) {
return this.getProjects().get(id);
}
public void putProject(StarTowerVitaProject project) {
this.getProjects().put(project.getId(), project);
// Save projects to the progress document
this.save("projects." + project.getId(), project);
}
public int getSelectedProjectId() {
return this.selectedProjectId > 0 ? this.selectedProjectId : 1;
}
public void setSelectedProjectId(int id) {
this.selectedProjectId = id;
// Save selection to the progress document
this.save("selectedProjectId", id);
}
// Vita energy
public int getEnergy() {
if (this.progress == null) {
return 0;
}
return this.progress.getPlayer().getInventory().getResourceCount(GameConstants.VITA_ENERGY_ITEM_ID);
}
public int addEnergy(int amount, PlayerChangeInfo change) {
// Sanity check
if (amount <= 0 || this.progress == null) {
return 0;
}
// Clamp to the energy cap
int gain = Math.min(amount, GameConstants.VITA_ENERGY_MAX - this.getEnergy());
if (gain <= 0) {
return 0;
}
// Add energy
this.progress.getPlayer().getInventory().addItem(GameConstants.VITA_ENERGY_ITEM_ID, gain, change);
// Return gained amount
return gain;
}
public PlayerChangeInfo buyEnergy(int count) {
// Sanity check
if (count <= 0) {
return null;
}
var inventory = this.getProgress().getPlayer().getInventory();
// Calculate cost
int cost = GameConstants.VITA_ENERGY_PURCHASE_PRICE * count;
// Make sure we have enough currency
if (!inventory.hasItem(GameConstants.VITA_ENERGY_PURCHASE_CURRENCY_ID, cost)) {
return null;
}
// Nothing to gain if we are already at the cap
if (this.getEnergy() >= GameConstants.VITA_ENERGY_MAX) {
return null;
}
// Buy energy
var change = inventory.removeItem(GameConstants.VITA_ENERGY_PURCHASE_CURRENCY_ID, cost);
// Add energy that we bought (addEnergy caps the amount at the max)
int amount = GameConstants.VITA_ENERGY_PURCHASE_VALUE * count;
inventory.addItem(GameConstants.VITA_ENERGY_ITEM_ID, amount, change);
// Success
return change.setSuccess(true);
}
// Database
private void save(String field, Object value) {
if (this.progress == null) {
return;
}
Nebula.getGameDatabase().update(
this.progress,
this.progress.getUid(),
"towerVitaData." + field,
value
);
}
}
@@ -0,0 +1,93 @@
package emu.nebula.game.tower;
import dev.morphia.annotations.Entity;
import emu.nebula.data.GameData;
import emu.nebula.proto.Public.TowerVitaOption;
import emu.nebula.proto.Public.UI32s;
import lombok.Getter;
@Getter
@Entity(useDiscriminator = false)
public class StarTowerVitaOption {
private int id;
private int type;
private Boolean booleanValue;
private Integer integerValue;
private int[] listValues;
@Deprecated // Morphia only
public StarTowerVitaOption() {
}
private StarTowerVitaOption(int id, int type) {
this.id = id;
this.type = type;
}
public static StarTowerVitaOption fromProto(TowerVitaOption proto) {
// Get option data so we know which value field to read
var data = GameData.getTowerVitaOptionDataTable().get(proto.getId());
if (data == null) {
return null;
}
var option = new StarTowerVitaOption(proto.getId(), data.getOptionType());
if (data.isSwitch()) {
if (!proto.hasBoolean()) {
return null;
}
option.booleanValue = proto.getBoolean();
} else if (data.isInteger()) {
if (!proto.hasInteger()) {
return null;
}
option.integerValue = proto.getInteger();
} else if (data.isList()) {
if (!proto.hasList()) {
return null;
}
option.listValues = proto.getList().getValues().toArray();
} else {
return null;
}
return option;
}
// Proto
public TowerVitaOption toProto() {
var proto = TowerVitaOption.newInstance()
.setId(this.getId());
if (this.getType() == 1) {
if (this.getBooleanValue() == null) {
return null;
}
proto.setBoolean(this.getBooleanValue());
} else if (this.getType() >= 2 && this.getType() <= 4) {
if (this.getIntegerValue() == null) {
return null;
}
proto.setInteger(this.getIntegerValue());
} else if (this.getType() == 5) {
if (this.getListValues() == null) {
return null;
}
proto.setList(UI32s.newInstance().addAllValues(this.getListValues()));
} else {
return null;
}
return proto;
}
}
@@ -0,0 +1,64 @@
package emu.nebula.game.tower;
import java.util.HashMap;
import java.util.Map;
import dev.morphia.annotations.Entity;
import emu.nebula.proto.Public.TowerVitaProject;
import lombok.Getter;
@Getter
@Entity(useDiscriminator = false)
public class StarTowerVitaProject {
private int id;
private String name;
private Map<Integer, StarTowerVitaOption> options;
@Deprecated // Morphia only
public StarTowerVitaProject() {
}
public StarTowerVitaProject(int id) {
this.id = id;
this.name = "Exploration Plan" + id;
this.options = new HashMap<>();
}
public void setName(String name) {
if (name == null) {
name = "";
}
// Clamp name length to prevent long names
if (name.length() > 32) {
name = name.substring(0, 31);
}
this.name = name;
}
public Map<Integer, StarTowerVitaOption> getOptions() {
return this.options;
}
public void putOption(StarTowerVitaOption option) {
this.getOptions().put(option.getId(), option);
}
public TowerVitaProject toProto() {
var proto = TowerVitaProject.newInstance()
.setId(this.getId())
.setName(this.getName() == null ? "" : this.getName());
// Sort option ids so the response is deterministic
this.getOptions().keySet().stream().sorted().forEach(id -> {
var option = this.getOptions().get(id).toProto();
if (option != null) {
proto.addOptions(option);
}
});
return proto;
}
}
@@ -1,5 +1,6 @@
package emu.nebula.game.tower.cases;
import emu.nebula.game.player.Player;
import emu.nebula.game.tower.StarTowerGame;
import emu.nebula.game.tower.StarTowerModifiers;
import emu.nebula.game.tower.room.StarTowerBaseRoom;
@@ -20,6 +21,10 @@ public abstract class StarTowerBaseCase {
}
public Player getPlayer() {
return this.getGame().getPlayer();
}
public StarTowerBaseRoom getRoom() {
return this.getGame().getRoom();
}
@@ -142,6 +142,12 @@ public class StarTowerHawkerCase extends StarTowerBaseCase {
this.buy(hawker.getSid(), rsp);
}
// Auto mode consumes vita energy
if (hawker.getAuto()) {
var change = this.getPlayer().getInventory().removeItem(GameConstants.VITA_ENERGY_ITEM_ID, GameConstants.VITA_ENERGY_COST);
rsp.setChange(change.toProto());
}
// Success
return rsp;
}
@@ -53,6 +53,11 @@ public class StarTowerNpcEventCase extends StarTowerBaseCase {
return CaseType.NpcEvent;
}
@Override
public boolean removeAfterInteract() {
return this.completed;
}
public int getOption(int index) {
if (index < 0 || index >= this.getOptions().size()) {
return 0;
@@ -68,15 +73,18 @@ public class StarTowerNpcEventCase extends StarTowerBaseCase {
return rsp;
}
// Get select request
var select = req.getSelectReq();
// Get option from selection index
int option = this.getOption(req.getSelectReq().getIndex());
int option = this.getOption(select.getIndex());
// Get select response proto
var selectRsp = rsp.getMutableSelectResp();
var success = selectRsp.getMutableResp();
var change = new PlayerChangeInfo();
// Completed event flag
// Pre set completed event flag to true
boolean completed = true;
// Handle option id
@@ -248,6 +256,11 @@ public class StarTowerNpcEventCase extends StarTowerBaseCase {
}
}
// Auto mode consumes vita energy
if (select.getAuto()) {
this.getPlayer().getInventory().removeItem(GameConstants.VITA_ENERGY_ITEM_ID, GameConstants.VITA_ENERGY_COST, change);
}
// Set change info
rsp.setChange(change.toProto());
@@ -66,15 +66,15 @@ public class StarTowerPotentialCase extends StarTowerBaseCase {
// Handle select option
if (select.hasReRoll()) {
this.reroll(rsp);
this.reroll(select.getAuto(), rsp);
} else {
this.select(select.getIndex(), rsp);
this.select(select.getIndex(), select.getAuto(), rsp);
}
return rsp;
}
private void reroll(StarTowerInteractResp rsp) {
private void reroll(boolean auto, StarTowerInteractResp rsp) {
// Check if we can reroll
if (!this.canReroll()) {
return;
@@ -116,13 +116,19 @@ public class StarTowerPotentialCase extends StarTowerBaseCase {
// Subtract coins
var change = this.getGame().addItem(GameConstants.TOWER_COIN_ITEM_ID, -price);
// Auto mode consumes vita energy
if (auto) {
this.getPlayer().getInventory().removeItem(GameConstants.VITA_ENERGY_ITEM_ID, GameConstants.VITA_ENERGY_COST, change);
}
// Set change proto in response
rsp.setChange(change.toProto());
// Achievement + Star Tower quests
this.getGame().getPlayer().trigger(QuestCondition.TowerSpecificPotentialReRollTotal, 1);
}
private void select(int index, StarTowerInteractResp rsp) {
private void select(int index, boolean auto, StarTowerInteractResp rsp) {
// Get selected potential
var potential = this.selectId(index);
if (potential == null) {
@@ -135,7 +141,12 @@ public class StarTowerPotentialCase extends StarTowerBaseCase {
// Add potential
var change = this.getGame().addItem(potential.getId(), potential.getLevel());
// Set change
// Auto mode consumes vita energy
if (auto) {
this.getPlayer().getInventory().removeItem(GameConstants.VITA_ENERGY_ITEM_ID, GameConstants.VITA_ENERGY_COST, change);
}
// Set change proto in response
rsp.setChange(change.toProto());
// Handle pending potential selectors
@@ -0,0 +1,37 @@
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.TowerVitaEnergyBuy.TowerVitaEnergyBuyResp;
import emu.nebula.net.HandlerId;
import emu.nebula.net.GameSession;
@HandlerId(NetMsgId.tower_vita_energy_buy_req)
public class HandlerTowerVitaEnergyBuyReq extends NetHandler {
@Override
public byte[] handle(GameSession session, byte[] message) throws Exception {
// Parse request
var req = UI32.parseFrom(message);
// Get vita data
var data = session.getPlayer().getStarTowerManager().getVitaData();
// Buy energy
var change = data.buyEnergy(req.getValue());
if (change == null) {
return session.encodeMsg(NetMsgId.tower_vita_energy_buy_failed_ack);
}
// Build response
var rsp = TowerVitaEnergyBuyResp.newInstance()
.setEnergy(data.getEnergy())
.setChange(change.toProto());
// Encode and send
return session.encodeMsg(NetMsgId.tower_vita_energy_buy_succeed_ack, rsp);
}
}
@@ -11,9 +11,18 @@ public class HandlerTowerVitaProjectListReq extends NetHandler {
@Override
public byte[] handle(GameSession session, byte[] message) throws Exception {
// Get manager
var manager = session.getPlayer().getStarTowerManager();
// Build response
var rsp = TowerVitaProjectListResp.newInstance();
var rsp = TowerVitaProjectListResp.newInstance()
.setUseId(manager.getVitaData().getSelectedProjectId());
// Add projects
for (var project : manager.getVitaProjects()) {
rsp.addProjects(project.toProto());
}
// Encode and send
return session.encodeMsg(NetMsgId.tower_vita_project_list_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.TowerVitaProjectNameUpdate.TowerVitaProjectNameUpdateRep;
import emu.nebula.net.HandlerId;
import emu.nebula.net.GameSession;
@HandlerId(NetMsgId.tower_vita_project_name_update_req)
public class HandlerTowerVitaProjectNameUpdateReq extends NetHandler {
@Override
public byte[] handle(GameSession session, byte[] message) throws Exception {
// Parse request
var req = TowerVitaProjectNameUpdateRep.parseFrom(message);
// Sanity
if (!req.hasId()) {
return session.encodeMsg(NetMsgId.tower_vita_project_name_update_failed_ack);
}
// Rename project
var project = session.getPlayer().getStarTowerManager().renameVitaProject(req.getId(), req.getName());
if (project == null) {
return session.encodeMsg(NetMsgId.tower_vita_project_name_update_failed_ack);
}
// Encode and send
return session.encodeMsg(NetMsgId.tower_vita_project_name_update_succeed_ack);
}
}
@@ -0,0 +1,26 @@
package emu.nebula.server.handlers;
import emu.nebula.net.NetHandler;
import emu.nebula.net.NetMsgId;
import emu.nebula.proto.TowerVitaProjectSet.TowerVitaProjectSetRep;
import emu.nebula.net.HandlerId;
import emu.nebula.net.GameSession;
@HandlerId(NetMsgId.tower_vita_project_set_req)
public class HandlerTowerVitaProjectSetReq extends NetHandler {
@Override
public byte[] handle(GameSession session, byte[] message) throws Exception {
// Parse request
var req = TowerVitaProjectSetRep.parseFrom(message);
// Sanity
if (!session.getPlayer().getStarTowerManager().selectVitaProject(req.getId())) {
return session.encodeMsg(NetMsgId.tower_vita_project_set_failed_ack);
}
// Encode and send
return session.encodeMsg(NetMsgId.tower_vita_project_set_succeed_ack);
}
}
@@ -0,0 +1,37 @@
package emu.nebula.server.handlers;
import emu.nebula.net.NetHandler;
import emu.nebula.net.NetMsgId;
import emu.nebula.proto.TowerVitaProjectUpdate.TowerVitaProjectUpdateRep;
import emu.nebula.net.HandlerId;
import emu.nebula.net.GameSession;
@HandlerId(NetMsgId.tower_vita_project_update_req)
public class HandlerTowerVitaProjectUpdateReq extends NetHandler {
@Override
public byte[] handle(GameSession session, byte[] message) throws Exception {
// Parse request
var req = TowerVitaProjectUpdateRep.parseFrom(message);
// Sanity
if (!req.hasProjects()) {
return session.encodeMsg(NetMsgId.tower_vita_project_update_failed_ack);
}
// Update project
var project = session.getPlayer().getStarTowerManager().updateVitaProject(req.getProjects());
if (project == null) {
return session.encodeMsg(NetMsgId.tower_vita_project_update_failed_ack);
}
// Build response
var rsp = TowerVitaProjectUpdateRep.newInstance()
.setProjects(project.toProto());
// Encode and send
return session.encodeMsg(NetMsgId.tower_vita_project_update_succeed_ack, rsp);
}
}