diff --git a/src/main/java/emu/nebula/GameConstants.java b/src/main/java/emu/nebula/GameConstants.java index 53bf7d6..3097bfe 100644 --- a/src/main/java/emu/nebula/GameConstants.java +++ b/src/main/java/emu/nebula/GameConstants.java @@ -41,6 +41,8 @@ public class GameConstants { public static final int MAX_FORMATIONS = 6; 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; diff --git a/src/main/java/emu/nebula/game/formation/Formation.java b/src/main/java/emu/nebula/game/formation/Formation.java index 30eb69a..79e3b36 100644 --- a/src/main/java/emu/nebula/game/formation/Formation.java +++ b/src/main/java/emu/nebula/game/formation/Formation.java @@ -1,6 +1,8 @@ package emu.nebula.game.formation; import dev.morphia.annotations.Entity; +import emu.nebula.game.player.Player; +import emu.nebula.game.tower.StarTowerPotentialPreset; import emu.nebula.proto.Public.FormationInfo; import lombok.Getter; @@ -10,6 +12,7 @@ public class Formation { private int num; private int[] charIds; private int[] discIds; + private long presetId; @Deprecated public Formation() { @@ -22,10 +25,16 @@ public class Formation { this.discIds = new int[6]; } - public Formation(FormationInfo formation) { + public Formation(Player player, FormationInfo formation) { this.num = formation.getNumber(); this.charIds = formation.getCharIds().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) { @@ -67,6 +76,18 @@ public class Formation { 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 @@ -74,7 +95,8 @@ public class Formation { var proto = FormationInfo.newInstance() .setNumber(this.getNum()) .addAllCharIds(this.getCharIds()) - .addAllDiscIds(this.getDiscIds()); + .addAllDiscIds(this.getDiscIds()) + .setPreselectionId(this.getPresetId()); return proto; } diff --git a/src/main/java/emu/nebula/game/formation/FormationManager.java b/src/main/java/emu/nebula/game/formation/FormationManager.java index f35a2ae..d044104 100644 --- a/src/main/java/emu/nebula/game/formation/FormationManager.java +++ b/src/main/java/emu/nebula/game/formation/FormationManager.java @@ -58,7 +58,7 @@ public class FormationManager extends PlayerManager implements GameDatabaseObjec // TODO // Create formation - var formation = new Formation(info); + var formation = new Formation(this.getPlayer(), info); // Add to formations map this.formations.put(formation.getNum(), formation); diff --git a/src/main/java/emu/nebula/game/tower/StarTowerBuild.java b/src/main/java/emu/nebula/game/tower/StarTowerBuild.java index c4d28d2..df7245c 100644 --- a/src/main/java/emu/nebula/game/tower/StarTowerBuild.java +++ b/src/main/java/emu/nebula/game/tower/StarTowerBuild.java @@ -49,7 +49,7 @@ public class StarTowerBuild implements GameDatabaseObject { @Deprecated public StarTowerBuild() { - + // Morphia only } public StarTowerBuild(Player player) { @@ -104,6 +104,7 @@ public class StarTowerBuild implements GameDatabaseObject { } public void setName(String newName) { + // Clamp name length to prevent long names if (newName.length() > 32) { newName = newName.substring(0, 31); } @@ -113,11 +114,23 @@ public class StarTowerBuild implements GameDatabaseObject { } public void setLock(boolean state) { + // Skip if no change detected + if (this.lock == state) { + return; + } + + // Set locked state this.lock = state; Nebula.getGameDatabase().update(this, this.getUid(), "lock", this.isLock()); } 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()); } diff --git a/src/main/java/emu/nebula/game/tower/StarTowerManager.java b/src/main/java/emu/nebula/game/tower/StarTowerManager.java index 97c5d37..edc5aee 100644 --- a/src/main/java/emu/nebula/game/tower/StarTowerManager.java +++ b/src/main/java/emu/nebula/game/tower/StarTowerManager.java @@ -1,5 +1,6 @@ package emu.nebula.game.tower; +import emu.nebula.GameConstants; import emu.nebula.Nebula; import emu.nebula.data.GameData; 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.PlayerProgress; import emu.nebula.game.quest.QuestCondition; +import emu.nebula.proto.PublicStarTower.StarTowerBookCharPotential; import emu.nebula.proto.StarTowerApply.StarTowerApplyReq; import it.unimi.dsi.fastutil.ints.IntArrayList; @@ -20,13 +22,16 @@ import lombok.Getter; @Getter public class StarTowerManager extends PlayerManager { - // Tower game instance + // Star tower game instance private StarTowerGame game; - // Tower builds + // Star tower records private Long2ObjectMap builds; private StarTowerBuild lastBuild; + // Potential presets + private Long2ObjectMap presets; + public StarTowerManager(Player player) { super(player); } @@ -155,7 +160,7 @@ public class StarTowerManager extends PlayerManager { this.loadFromDatabase(); } - return builds; + return this.builds; } public StarTowerBuild getBuildById(long id) { @@ -206,7 +211,7 @@ public class StarTowerManager extends PlayerManager { } // Check limit - if (this.getBuilds().size() >= 50) { + if (this.getBuilds().size() >= GameConstants.MAX_BUILDS) { return null; } @@ -258,6 +263,63 @@ public class StarTowerManager extends PlayerManager { return change; } + // Presets + + public Long2ObjectMap 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 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 public PlayerChangeInfo apply(StarTowerApplyReq req) { @@ -401,8 +463,9 @@ public class StarTowerManager extends PlayerManager { // Database public void loadFromDatabase() { - // Init builds + // Init variables this.builds = new Long2ObjectOpenHashMap<>(); + this.presets = new Long2ObjectOpenHashMap<>(); // Load builds with the current player's uid Nebula.getGameDatabase().getObjects(StarTowerBuild.class, "playerUid", getPlayerUid()).forEach(build -> { @@ -415,5 +478,11 @@ public class StarTowerManager extends PlayerManager { // Add 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); + }); } } diff --git a/src/main/java/emu/nebula/game/tower/StarTowerPotentialPreset.java b/src/main/java/emu/nebula/game/tower/StarTowerPotentialPreset.java new file mode 100644 index 0000000..4c8337b --- /dev/null +++ b/src/main/java/emu/nebula/game/tower/StarTowerPotentialPreset.java @@ -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> charPotentials; + + @Deprecated + public StarTowerPotentialPreset() { + // Morphia only + } + + public StarTowerPotentialPreset(Player player, String name, boolean preference, Iterable 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 charPotentials) { + // Clear character potentials first + this.getCharPotentials().clear(); + + // Parse character potentials + for (var charPotential : charPotentials) { + int charId = charPotential.getCharId(); + var potentials = new HashMap(); + + 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); + } +} diff --git a/src/main/java/emu/nebula/server/handlers/HandlerPotentialPreselectionDeleteReq.java b/src/main/java/emu/nebula/server/handlers/HandlerPotentialPreselectionDeleteReq.java new file mode 100644 index 0000000..914927b --- /dev/null +++ b/src/main/java/emu/nebula/server/handlers/HandlerPotentialPreselectionDeleteReq.java @@ -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(); + + // 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); + } + +} diff --git a/src/main/java/emu/nebula/server/handlers/HandlerPotentialPreselectionImportReq.java b/src/main/java/emu/nebula/server/handlers/HandlerPotentialPreselectionImportReq.java new file mode 100644 index 0000000..788616e --- /dev/null +++ b/src/main/java/emu/nebula/server/handlers/HandlerPotentialPreselectionImportReq.java @@ -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()); + } + +} diff --git a/src/main/java/emu/nebula/server/handlers/HandlerPotentialPreselectionListReq.java b/src/main/java/emu/nebula/server/handlers/HandlerPotentialPreselectionListReq.java index 31a4795..7bc256c 100644 --- a/src/main/java/emu/nebula/server/handlers/HandlerPotentialPreselectionListReq.java +++ b/src/main/java/emu/nebula/server/handlers/HandlerPotentialPreselectionListReq.java @@ -14,6 +14,12 @@ public class HandlerPotentialPreselectionListReq extends NetHandler { // Create base response 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 return session.encodeMsg(NetMsgId.potential_preselection_list_succeed_ack, rsp); } diff --git a/src/main/java/emu/nebula/server/handlers/HandlerPotentialPreselectionNameSetReq.java b/src/main/java/emu/nebula/server/handlers/HandlerPotentialPreselectionNameSetReq.java new file mode 100644 index 0000000..76bb4e2 --- /dev/null +++ b/src/main/java/emu/nebula/server/handlers/HandlerPotentialPreselectionNameSetReq.java @@ -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); + } + +} diff --git a/src/main/java/emu/nebula/server/handlers/HandlerPotentialPreselectionPreferenceSetReq.java b/src/main/java/emu/nebula/server/handlers/HandlerPotentialPreselectionPreferenceSetReq.java new file mode 100644 index 0000000..f913086 --- /dev/null +++ b/src/main/java/emu/nebula/server/handlers/HandlerPotentialPreselectionPreferenceSetReq.java @@ -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(); + var checkOutPresets = new HashSet(); + + // 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); + } + +} diff --git a/src/main/java/emu/nebula/server/handlers/HandlerPotentialPreselectionUpdateReq.java b/src/main/java/emu/nebula/server/handlers/HandlerPotentialPreselectionUpdateReq.java new file mode 100644 index 0000000..b0a7f19 --- /dev/null +++ b/src/main/java/emu/nebula/server/handlers/HandlerPotentialPreselectionUpdateReq.java @@ -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()); + } + +}