Implement presets

This commit is contained in:
Melledy
2026-04-18 01:33:52 -07:00
parent 87c5dff006
commit a8c64e5a78
12 changed files with 459 additions and 9 deletions
@@ -41,6 +41,8 @@ public class GameConstants {
public static final int MAX_FORMATIONS = 6; public static final int MAX_FORMATIONS = 6;
public static final int MAX_SHOWCASE_IDS = 5; 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 BATTLE_PASS_ID = 1; public static final int BATTLE_PASS_ID = 1;
@@ -1,6 +1,8 @@
package emu.nebula.game.formation; package emu.nebula.game.formation;
import dev.morphia.annotations.Entity; import dev.morphia.annotations.Entity;
import emu.nebula.game.player.Player;
import emu.nebula.game.tower.StarTowerPotentialPreset;
import emu.nebula.proto.Public.FormationInfo; import emu.nebula.proto.Public.FormationInfo;
import lombok.Getter; import lombok.Getter;
@@ -10,6 +12,7 @@ public class Formation {
private int num; private int num;
private int[] charIds; private int[] charIds;
private int[] discIds; private int[] discIds;
private long presetId;
@Deprecated @Deprecated
public Formation() { public Formation() {
@@ -22,10 +25,16 @@ public class Formation {
this.discIds = new int[6]; this.discIds = new int[6];
} }
public Formation(FormationInfo formation) { public Formation(Player player, FormationInfo formation) {
this.num = formation.getNumber(); this.num = formation.getNumber();
this.charIds = formation.getCharIds().toArray(); this.charIds = formation.getCharIds().toArray();
this.discIds = formation.getDiscIds().toArray(); this.discIds = formation.getDiscIds().toArray();
// Validate presetId
var preset = player.getStarTowerManager().getPresetById(formation.getPreselectionId());
if (preset != null && isValidPreset(preset)) {
this.presetId = preset.getUid();
}
} }
public int getCharIdAt(int i) { public int getCharIdAt(int i) {
@@ -68,13 +77,26 @@ public class Formation {
return count; return count;
} }
private boolean isValidPreset(StarTowerPotentialPreset preset) {
// Returns true if all the character ids in this preset match our charIds
for (int charId : this.getCharIds()) {
if (!preset.getCharPotentials().containsKey(charId)) {
// Character id not found
return false;
}
}
return true;
}
// Proto // Proto
public FormationInfo toProto() { public FormationInfo toProto() {
var proto = FormationInfo.newInstance() var proto = FormationInfo.newInstance()
.setNumber(this.getNum()) .setNumber(this.getNum())
.addAllCharIds(this.getCharIds()) .addAllCharIds(this.getCharIds())
.addAllDiscIds(this.getDiscIds()); .addAllDiscIds(this.getDiscIds())
.setPreselectionId(this.getPresetId());
return proto; return proto;
} }
@@ -58,7 +58,7 @@ public class FormationManager extends PlayerManager implements GameDatabaseObjec
// TODO // TODO
// Create formation // Create formation
var formation = new Formation(info); var formation = new Formation(this.getPlayer(), info);
// Add to formations map // Add to formations map
this.formations.put(formation.getNum(), formation); this.formations.put(formation.getNum(), formation);
@@ -49,7 +49,7 @@ public class StarTowerBuild implements GameDatabaseObject {
@Deprecated @Deprecated
public StarTowerBuild() { public StarTowerBuild() {
// Morphia only
} }
public StarTowerBuild(Player player) { public StarTowerBuild(Player player) {
@@ -104,6 +104,7 @@ public class StarTowerBuild implements GameDatabaseObject {
} }
public void setName(String newName) { public void setName(String newName) {
// Clamp name length to prevent long names
if (newName.length() > 32) { if (newName.length() > 32) {
newName = newName.substring(0, 31); newName = newName.substring(0, 31);
} }
@@ -113,11 +114,23 @@ public class StarTowerBuild implements GameDatabaseObject {
} }
public void setLock(boolean state) { public void setLock(boolean state) {
// Skip if no change detected
if (this.lock == state) {
return;
}
// Set locked state
this.lock = state; this.lock = state;
Nebula.getGameDatabase().update(this, this.getUid(), "lock", this.isLock()); Nebula.getGameDatabase().update(this, this.getUid(), "lock", this.isLock());
} }
public void setPreference(boolean state) { public void setPreference(boolean state) {
// Skip if no change detected
if (this.preference == state) {
return;
}
// Set preference (favorite toggle)
this.preference = state; this.preference = state;
Nebula.getGameDatabase().update(this, this.getUid(), "preference", this.isPreference()); Nebula.getGameDatabase().update(this, this.getUid(), "preference", this.isPreference());
} }
@@ -1,5 +1,6 @@
package emu.nebula.game.tower; package emu.nebula.game.tower;
import emu.nebula.GameConstants;
import emu.nebula.Nebula; import emu.nebula.Nebula;
import emu.nebula.data.GameData; import emu.nebula.data.GameData;
import emu.nebula.data.resources.StarTowerGrowthNodeDef; import emu.nebula.data.resources.StarTowerGrowthNodeDef;
@@ -9,6 +10,7 @@ import emu.nebula.game.player.PlayerChangeInfo;
import emu.nebula.game.player.PlayerManager; import emu.nebula.game.player.PlayerManager;
import emu.nebula.game.player.PlayerProgress; import emu.nebula.game.player.PlayerProgress;
import emu.nebula.game.quest.QuestCondition; import emu.nebula.game.quest.QuestCondition;
import emu.nebula.proto.PublicStarTower.StarTowerBookCharPotential;
import emu.nebula.proto.StarTowerApply.StarTowerApplyReq; import emu.nebula.proto.StarTowerApply.StarTowerApplyReq;
import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.ints.IntArrayList;
@@ -20,13 +22,16 @@ import lombok.Getter;
@Getter @Getter
public class StarTowerManager extends PlayerManager { public class StarTowerManager extends PlayerManager {
// Tower game instance // Star tower game instance
private StarTowerGame game; private StarTowerGame game;
// Tower builds // Star tower records
private Long2ObjectMap<StarTowerBuild> builds; private Long2ObjectMap<StarTowerBuild> builds;
private StarTowerBuild lastBuild; private StarTowerBuild lastBuild;
// Potential presets
private Long2ObjectMap<StarTowerPotentialPreset> presets;
public StarTowerManager(Player player) { public StarTowerManager(Player player) {
super(player); super(player);
} }
@@ -155,7 +160,7 @@ public class StarTowerManager extends PlayerManager {
this.loadFromDatabase(); this.loadFromDatabase();
} }
return builds; return this.builds;
} }
public StarTowerBuild getBuildById(long id) { public StarTowerBuild getBuildById(long id) {
@@ -206,7 +211,7 @@ public class StarTowerManager extends PlayerManager {
} }
// Check limit // Check limit
if (this.getBuilds().size() >= 50) { if (this.getBuilds().size() >= GameConstants.MAX_BUILDS) {
return null; return null;
} }
@@ -258,6 +263,63 @@ public class StarTowerManager extends PlayerManager {
return change; return change;
} }
// Presets
public Long2ObjectMap<StarTowerPotentialPreset> getPresets() {
if (this.presets == null) {
this.loadFromDatabase();
}
return this.presets;
}
public StarTowerPotentialPreset getPresetById(long presetId) {
return this.getPresets().get(presetId);
}
public StarTowerPotentialPreset importPreset(String name, boolean preference, Iterable<StarTowerBookCharPotential> potentials) {
// Check max presets limit
if (this.getPresets().size() >= GameConstants.MAX_PRESETS) {
return null;
}
// Check name
if (name == null) {
name = "";
}
// Clamp name if it's too long
if (name.length() > 32) {
name = name.substring(0, 31);
}
// Create preset
var preset = new StarTowerPotentialPreset(this.getPlayer(), name, preference, potentials);
// Save to database
preset.save();
// Add to map
this.getPresets().put(preset.getUid(), preset);
// Return preset
return preset;
}
public boolean deletePreset(StarTowerPotentialPreset preset) {
// Get preset
var removed = this.getPresets().remove(preset.getUid());
if (removed == null) {
return false;
}
// Delete from database
removed.delete();
// Success
return true;
}
// Game // Game
public PlayerChangeInfo apply(StarTowerApplyReq req) { public PlayerChangeInfo apply(StarTowerApplyReq req) {
@@ -401,8 +463,9 @@ public class StarTowerManager extends PlayerManager {
// Database // Database
public void loadFromDatabase() { public void loadFromDatabase() {
// Init builds // Init variables
this.builds = new Long2ObjectOpenHashMap<>(); this.builds = new Long2ObjectOpenHashMap<>();
this.presets = new Long2ObjectOpenHashMap<>();
// Load builds with the current player's uid // Load builds with the current player's uid
Nebula.getGameDatabase().getObjects(StarTowerBuild.class, "playerUid", getPlayerUid()).forEach(build -> { Nebula.getGameDatabase().getObjects(StarTowerBuild.class, "playerUid", getPlayerUid()).forEach(build -> {
@@ -415,5 +478,11 @@ public class StarTowerManager extends PlayerManager {
// Add build // Add build
this.builds.put(build.getUid(), build); this.builds.put(build.getUid(), build);
}); });
// Load presets with the current player's uid
Nebula.getGameDatabase().getObjects(StarTowerPotentialPreset.class, "playerUid", getPlayerUid()).forEach(preset -> {
// Add preset
this.presets.put(preset.getUid(), preset);
});
} }
} }
@@ -0,0 +1,137 @@
package emu.nebula.game.tower;
import java.util.HashMap;
import java.util.LinkedHashMap;
import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Id;
import dev.morphia.annotations.Indexed;
import emu.nebula.Nebula;
import emu.nebula.data.GameData;
import emu.nebula.database.GameDatabaseObject;
import emu.nebula.game.player.Player;
import emu.nebula.proto.PublicStarTower.PotentialPreselection;
import emu.nebula.proto.PublicStarTower.StarTowerBookCharPotential;
import emu.nebula.proto.PublicStarTower.StarTowerBookPotential;
import emu.nebula.util.Snowflake;
import lombok.Getter;
@Getter
@Entity(value = "presets", useDiscriminator = false)
public class StarTowerPotentialPreset implements GameDatabaseObject {
@Id
private int uid;
@Indexed
private int playerUid;
private String name;
private boolean preference;
private long timestamp;
private LinkedHashMap<Integer, HashMap<Integer, Integer>> charPotentials;
@Deprecated
public StarTowerPotentialPreset() {
// Morphia only
}
public StarTowerPotentialPreset(Player player, String name, boolean preference, Iterable<StarTowerBookCharPotential> potentials) {
this.uid = Snowflake.newUid();
this.playerUid = player.getUid();
this.name = name;
this.preference = preference;
this.charPotentials = new LinkedHashMap<>(); // Order is important
this.timestamp = Nebula.getCurrentServerTime();
this.updatePotentials(potentials);
}
public void setName(String newName) {
// Clamp name length to prevent long names
if (newName.length() > 32) {
newName = newName.substring(0, 31);
}
this.name = newName;
Nebula.getGameDatabase().update(this, this.getUid(), "name", this.getName());
}
public void setPreference(boolean state) {
// Skip if no change detected
if (this.preference == state) {
return;
}
// Set preference (favorite toggle)
this.preference = state;
Nebula.getGameDatabase().update(this, this.getUid(), "preference", this.isPreference());
}
public void updatePotentials(Iterable<StarTowerBookCharPotential> charPotentials) {
// Clear character potentials first
this.getCharPotentials().clear();
// Parse character potentials
for (var charPotential : charPotentials) {
int charId = charPotential.getCharId();
var potentials = new HashMap<Integer, Integer>();
for (var potential : charPotential.getPotentials()) {
// Validate
var data = GameData.getPotentialDataTable().get(potential.getId());
if (data == null || data.getCharId() != data.getCharId()) {
return;
}
// Check level
int level = potential.getLevel();
level = Math.max(level, 0);
level = Math.min(level, data.getMaxLevel());
// Set potential
potentials.put(potential.getId(), potential.getLevel());
}
// Add character potential
this.getCharPotentials().put(charId, potentials);
}
}
// Proto
public PotentialPreselection toProto() {
var proto = PotentialPreselection.newInstance()
.setId(this.getUid())
.setName(this.getName())
.setPreference(this.isPreference());
for (var entry : this.getCharPotentials().entrySet()) {
int charId = entry.getKey();
var potentials = entry.getValue();
var info = StarTowerBookCharPotential.newInstance()
.setCharId(charId);
for (var potential : potentials.entrySet()) {
int potentialId = potential.getKey();
int level = potential.getValue();
var book = StarTowerBookPotential.newInstance()
.setId(potentialId)
.setLevel(level);
info.addPotentials(book);
}
proto.addCharPotentials(info);
}
return proto;
}
// Database
public void delete() {
Nebula.getGameDatabase().delete(this);
}
}
@@ -0,0 +1,44 @@
package emu.nebula.server.handlers;
import emu.nebula.net.NetHandler;
import emu.nebula.net.NetMsgId;
import emu.nebula.proto.PotentialPreselectionDelete.PotentialPreselectionDeleteReq;
import emu.nebula.net.HandlerId;
import java.util.HashSet;
import emu.nebula.game.tower.StarTowerPotentialPreset;
import emu.nebula.net.GameSession;
@HandlerId(NetMsgId.potential_preselection_delete_req)
public class HandlerPotentialPreselectionDeleteReq extends NetHandler {
@Override
public byte[] handle(GameSession session, byte[] message) throws Exception {
// Parse request
var req = PotentialPreselectionDeleteReq.parseFrom(message);
// Get list of presets
var presets = new HashSet<StarTowerPotentialPreset>();
// Get preset
for (long id : req.getIds()) {
var preset = session.getPlayer().getStarTowerManager().getPresetById(id);
if (preset == null) {
return session.encodeMsg(NetMsgId.potential_preselection_delete_failed_ack);
}
presets.add(preset);
}
// Delete presets
for (var preset : presets) {
session.getPlayer().getStarTowerManager().deletePreset(preset);
}
// Encode and send
return session.encodeMsg(NetMsgId.potential_preselection_delete_succeed_ack);
}
}
@@ -0,0 +1,33 @@
package emu.nebula.server.handlers;
import emu.nebula.net.NetHandler;
import emu.nebula.net.NetMsgId;
import emu.nebula.proto.PotentialPreselectionImport.PotentialPreselectionImportReq;
import emu.nebula.net.HandlerId;
import emu.nebula.net.GameSession;
@HandlerId(NetMsgId.potential_preselection_import_req)
public class HandlerPotentialPreselectionImportReq extends NetHandler {
@Override
public byte[] handle(GameSession session, byte[] message) throws Exception {
// Parse request
var req = PotentialPreselectionImportReq.parseFrom(message);
// Import preset
var preset = session.getPlayer().getStarTowerManager().importPreset(
req.getName(),
req.getPreference(),
req.getCharPotentials()
);
// Check if there was an error with importing the preset
if (preset == null) {
return session.encodeMsg(NetMsgId.potential_preselection_import_failed_ack);
}
// Encode and send
return session.encodeMsg(NetMsgId.potential_preselection_import_succeed_ack, preset.toProto());
}
}
@@ -14,6 +14,12 @@ public class HandlerPotentialPreselectionListReq extends NetHandler {
// Create base response // Create base response
var rsp = PotentialPreselectionList.newInstance(); var rsp = PotentialPreselectionList.newInstance();
// Add potential presets
var presets = session.getPlayer().getStarTowerManager().getPresets().values();
for (var preset : presets) {
rsp.addList(preset.toProto());
}
// Encode and send // Encode and send
return session.encodeMsg(NetMsgId.potential_preselection_list_succeed_ack, rsp); return session.encodeMsg(NetMsgId.potential_preselection_list_succeed_ack, rsp);
} }
@@ -0,0 +1,31 @@
package emu.nebula.server.handlers;
import emu.nebula.net.NetHandler;
import emu.nebula.net.NetMsgId;
import emu.nebula.proto.PotentialPreselectionNameSet.PotentialPreselectionNameSetReq;
import emu.nebula.net.HandlerId;
import emu.nebula.net.GameSession;
@HandlerId(NetMsgId.potential_preselection_name_set_req)
public class HandlerPotentialPreselectionNameSetReq extends NetHandler {
@Override
public byte[] handle(GameSession session, byte[] message) throws Exception {
// Parse request
var req = PotentialPreselectionNameSetReq.parseFrom(message);
// Get preset
var preset = session.getPlayer().getStarTowerManager().getPresetById(req.getId());
if (preset == null) {
return session.encodeMsg(NetMsgId.potential_preselection_name_set_failed_ack);
}
// Update name
preset.setName(req.getName());
// Encode and send
return session.encodeMsg(NetMsgId.potential_preselection_name_set_succeed_ack);
}
}
@@ -0,0 +1,61 @@
package emu.nebula.server.handlers;
import emu.nebula.net.NetHandler;
import emu.nebula.net.NetMsgId;
import emu.nebula.proto.PotentialPreselectionPreferenceSet.PotentialPreselectionPreferenceSetReq;
import emu.nebula.net.HandlerId;
import emu.nebula.net.GameSession;
import java.util.HashSet;
import emu.nebula.game.tower.StarTowerPotentialPreset;
@HandlerId(NetMsgId.potential_preselection_preference_set_req)
public class HandlerPotentialPreselectionPreferenceSetReq extends NetHandler {
@Override
public byte[] handle(GameSession session, byte[] message) throws Exception {
// Parse request
var req = PotentialPreselectionPreferenceSetReq.parseFrom(message);
// Get list of presets
var checkInPresets = new HashSet<StarTowerPotentialPreset>();
var checkOutPresets = new HashSet<StarTowerPotentialPreset>();
// Get check-in presets
for (long id : req.getCheckInIds()) {
var preset = session.getPlayer().getStarTowerManager().getPresetById(id);
if (preset == null) {
return session.encodeMsg(NetMsgId.potential_preselection_preference_set_failed_ack);
}
checkInPresets.add(preset);
}
// Get check-out presets
for (long id : req.getCheckOutIds()) {
var preset = session.getPlayer().getStarTowerManager().getPresetById(id);
if (preset == null) {
return session.encodeMsg(NetMsgId.potential_preselection_preference_set_failed_ack);
}
checkOutPresets.add(preset);
}
// Set preference to true for check-in presets
for (var preset : checkInPresets) {
preset.setPreference(true);
}
// Set preference to false for check-out presets
for (var preset : checkOutPresets) {
preset.setPreference(false);
}
// Encode and send
return session.encodeMsg(NetMsgId.potential_preselection_preference_set_succeed_ack);
}
}
@@ -0,0 +1,32 @@
package emu.nebula.server.handlers;
import emu.nebula.net.NetHandler;
import emu.nebula.net.NetMsgId;
import emu.nebula.proto.PotentialPreselectionUpdate.PotentialPreselectionUpdateReq;
import emu.nebula.net.HandlerId;
import emu.nebula.net.GameSession;
@HandlerId(NetMsgId.potential_preselection_update_req)
public class HandlerPotentialPreselectionUpdateReq extends NetHandler {
@Override
public byte[] handle(GameSession session, byte[] message) throws Exception {
// Parse request
var req = PotentialPreselectionUpdateReq.parseFrom(message);
// Get preset
var preset = session.getPlayer().getStarTowerManager().getPresetById(req.getId());
if (preset == null) {
return session.encodeMsg(NetMsgId.potential_preselection_update_failed_ack);
}
// Update potentials
preset.updatePotentials(req.getCharPotentials());
preset.save();
// Encode and send
return session.encodeMsg(NetMsgId.potential_preselection_update_succeed_ack, preset.toProto());
}
}