From 30d0dfbaeedcc9b6f219bada9f340e5793ca7f06 Mon Sep 17 00:00:00 2001 From: Melledy <121644117+Melledy@users.noreply.github.com> Date: Fri, 14 Nov 2025 18:12:07 -0800 Subject: [PATCH] Implement vampire survivor records --- .../data/resources/VampireSurvivorDef.java | 2 + .../nebula/game/player/PlayerProgress.java | 42 ++++++++--- .../nebula/game/tower/StarTowerManager.java | 4 + .../game/vampire/VampireSurvivorGame.java | 8 +- .../game/vampire/VampireSurvivorLog.java | 42 +++++++++++ .../game/vampire/VampireSurvivorManager.java | 73 ++++++++++++++++++- .../HandlerVampireSurvivorApplyReq.java | 2 +- .../HandlerVampireSurvivorSettleReq.java | 2 +- .../HandlerVampireTalentDetailReq.java | 7 +- 9 files changed, 165 insertions(+), 17 deletions(-) create mode 100644 src/main/java/emu/nebula/game/vampire/VampireSurvivorLog.java diff --git a/src/main/java/emu/nebula/data/resources/VampireSurvivorDef.java b/src/main/java/emu/nebula/data/resources/VampireSurvivorDef.java index 1cf6b40..614fd67 100644 --- a/src/main/java/emu/nebula/data/resources/VampireSurvivorDef.java +++ b/src/main/java/emu/nebula/data/resources/VampireSurvivorDef.java @@ -8,6 +8,8 @@ import lombok.Getter; @ResourceType(name = "VampireSurvivor.json") public class VampireSurvivorDef extends BaseDef { private int Id; + private int Mode; + private int NeedWorldClass; @Override public int getId() { diff --git a/src/main/java/emu/nebula/game/player/PlayerProgress.java b/src/main/java/emu/nebula/game/player/PlayerProgress.java index d8b765b..0880cb4 100644 --- a/src/main/java/emu/nebula/game/player/PlayerProgress.java +++ b/src/main/java/emu/nebula/game/player/PlayerProgress.java @@ -1,10 +1,14 @@ package emu.nebula.game.player; +import java.util.HashMap; +import java.util.Map; + import dev.morphia.annotations.Entity; import dev.morphia.annotations.Id; import emu.nebula.Nebula; import emu.nebula.data.GameData; import emu.nebula.database.GameDatabaseObject; +import emu.nebula.game.vampire.VampireSurvivorLog; import emu.nebula.proto.PlayerData.PlayerInfo; import emu.nebula.proto.Public.CharGemInstance; import emu.nebula.proto.Public.DailyInstance; @@ -12,6 +16,7 @@ import emu.nebula.proto.Public.RegionBossLevel; import emu.nebula.proto.Public.SkillInstance; import emu.nebula.proto.Public.VampireSurvivorLevel; import emu.nebula.proto.Public.WeekBossLevel; +import emu.nebula.util.Bitset; import it.unimi.dsi.fastutil.ints.Int2IntMap; import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; import it.unimi.dsi.fastutil.ints.IntOpenHashSet; @@ -38,6 +43,9 @@ public class PlayerProgress extends PlayerManager implements GameDatabaseObject private Int2IntMap infinityArenaLog; // Vampire Survivors TODO + private Map vampireLog; + private Bitset vampireTalents; + private IntSet vampireCards; @Deprecated // Morphia only public PlayerProgress() { @@ -62,6 +70,9 @@ public class PlayerProgress extends PlayerManager implements GameDatabaseObject this.infinityArenaLog = new Int2IntOpenHashMap(); // Vampire Survivor + this.vampireLog = new HashMap<>(); + this.vampireTalents = new Bitset(); + this.vampireCards = new IntOpenHashSet(); // Save to database this.save(); @@ -180,18 +191,31 @@ public class PlayerProgress extends PlayerManager implements GameDatabaseObject proto.addWeekBossLevels(p); } - // Force unlock all vampire survivor records + // Vampire survivors var vsProto = proto.getMutableVampireSurvivorRecord(); - vsProto.getMutableSeason(); - for (var vsData : GameData.getVampireSurvivorDataTable()) { - var level = VampireSurvivorLevel.newInstance() - .setId(vsData.getId()) - .setScore(0) - .setPassed(true); - - vsProto.addRecords(level); + if (unlockAll) { + // Force unlock all vampire survivor records if we dont have the records + for (var vsData : GameData.getVampireSurvivorDataTable()) { + // Get existing record + var log = this.getVampireLog().get(vsData.getId()); + + if (log == null) { + var level = VampireSurvivorLevel.newInstance() + .setId(vsData.getId()) + .setScore(0) + .setPassed(true); + + vsProto.addRecords(level); + } else { + vsProto.addRecords(log.toProto()); + } + } + } else { + for (var log : this.getVampireLog().values()) { + vsProto.addRecords(log.toProto()); + } } } } diff --git a/src/main/java/emu/nebula/game/tower/StarTowerManager.java b/src/main/java/emu/nebula/game/tower/StarTowerManager.java index 6607148..fa648b8 100644 --- a/src/main/java/emu/nebula/game/tower/StarTowerManager.java +++ b/src/main/java/emu/nebula/game/tower/StarTowerManager.java @@ -37,6 +37,10 @@ public class StarTowerManager extends PlayerManager { public StarTowerBuild getBuildById(long id) { return this.getBuilds().get(id); } + + public boolean hasBuild(long id) { + return this.getBuilds().containsKey(id); + } public StarTowerGame apply(StarTowerApplyReq req) { // Sanity checks diff --git a/src/main/java/emu/nebula/game/vampire/VampireSurvivorGame.java b/src/main/java/emu/nebula/game/vampire/VampireSurvivorGame.java index 3ba9dc9..0125be1 100644 --- a/src/main/java/emu/nebula/game/vampire/VampireSurvivorGame.java +++ b/src/main/java/emu/nebula/game/vampire/VampireSurvivorGame.java @@ -15,6 +15,7 @@ import lombok.Getter; public class VampireSurvivorGame { private final VampireSurvivorManager manager; private final VampireSurvivorDef data; + private long[] builds; private IntSet cards; @@ -22,9 +23,10 @@ public class VampireSurvivorGame { private int rewardLevel; private IntList rewards; - public VampireSurvivorGame(VampireSurvivorManager manager, VampireSurvivorDef data) { + public VampireSurvivorGame(VampireSurvivorManager manager, VampireSurvivorDef data, long[] builds) { this.manager = manager; this.data = data; + this.builds = builds; this.cards = new IntOpenHashSet(); this.rewards = new IntArrayList(); @@ -32,6 +34,10 @@ public class VampireSurvivorGame { this.calcRewards(); } + public int getId() { + return this.getData().getId(); + } + private WeightedList getRandom() { var random = new WeightedList(); diff --git a/src/main/java/emu/nebula/game/vampire/VampireSurvivorLog.java b/src/main/java/emu/nebula/game/vampire/VampireSurvivorLog.java new file mode 100644 index 0000000..60cd5a4 --- /dev/null +++ b/src/main/java/emu/nebula/game/vampire/VampireSurvivorLog.java @@ -0,0 +1,42 @@ +package emu.nebula.game.vampire; + +import dev.morphia.annotations.Entity; +import emu.nebula.proto.Public.VampireSurvivorLevel; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Entity(useDiscriminator = false) +public class VampireSurvivorLog { + private int id; + + @Setter private int score; + @Setter private boolean passed; + @Setter private long[] builds; + + @Deprecated // Morphia only + public VampireSurvivorLog() { + + } + + public VampireSurvivorLog(int id) { + this.id = id; + } + + // Proto + + public VampireSurvivorLevel toProto() { + var proto = VampireSurvivorLevel.newInstance() + .setId(this.getId()) + .setScore(this.getScore()) + .setPassed(this.isPassed()); + + if (this.builds != null) { + for (long buildId : this.builds) { + proto.addBuildIds(buildId); + } + } + + return proto; + } +} diff --git a/src/main/java/emu/nebula/game/vampire/VampireSurvivorManager.java b/src/main/java/emu/nebula/game/vampire/VampireSurvivorManager.java index f597148..e43a64b 100644 --- a/src/main/java/emu/nebula/game/vampire/VampireSurvivorManager.java +++ b/src/main/java/emu/nebula/game/vampire/VampireSurvivorManager.java @@ -1,35 +1,100 @@ package emu.nebula.game.vampire; +import emu.nebula.Nebula; import emu.nebula.data.GameData; import emu.nebula.game.player.Player; import emu.nebula.game.player.PlayerManager; - +import emu.nebula.game.player.PlayerProgress; +import emu.nebula.util.Bitset; import lombok.Getter; +import us.hebi.quickbuf.RepeatedLong; @Getter public class VampireSurvivorManager extends PlayerManager { // Game - private transient VampireSurvivorGame game; + private VampireSurvivorGame game; public VampireSurvivorManager(Player player) { super(player); } - public VampireSurvivorGame apply(int levelId) { + public PlayerProgress getProgress() { + return this.getPlayer().getProgress(); + } + + public Bitset getTalents() { + return this.getProgress().getVampireTalents(); + } + + public int getTalentPoints() { + return this.getProgress().getVampireCards().size() * 5; + } + + public VampireSurvivorGame apply(int levelId, RepeatedLong builds) { // Get data var data = GameData.getVampireSurvivorDataTable().get(levelId); if (data == null) { return null; } + // Check player level (world class) + if (this.getPlayer().getLevel() < data.getNeedWorldClass()) { + return null; + } + + // Check builds + if (builds.length() != data.getMode()) { + return null; + } + + // Make sure our builds exist + for (long buildId : builds) { + boolean hasBuild = this.getPlayer().getStarTowerManager().hasBuild(buildId); + if (hasBuild == false) { + return null; + } + } + // Create game - this.game = new VampireSurvivorGame(this, data); + this.game = new VampireSurvivorGame(this, data, builds.toArray()); // Success return this.game; } public void settle(boolean isWin, int score) { + // Sanity check + if (this.game == null) { + return; + } + + // Skip if we didn't win + if (!isWin) { + return; + } + + // Get log from database + var log = this.getProgress().getVampireLog().computeIfAbsent( + game.getId(), + id -> new VampireSurvivorLog(id) + ); + + // Check if we should update score + if (score >= log.getScore() || !log.isPassed()) { + // Set record info + log.setPassed(isWin); + log.setScore(score); + log.setBuilds(game.getBuilds()); + + // Save record to database if we set any data + Nebula.getGameDatabase().update( + this.getProgress(), + this.getPlayerUid(), + "vampireLog." + game.getId(), + log + ); + } + // Clear game this.game = null; } diff --git a/src/main/java/emu/nebula/server/handlers/HandlerVampireSurvivorApplyReq.java b/src/main/java/emu/nebula/server/handlers/HandlerVampireSurvivorApplyReq.java index 8b13c6b..a938576 100644 --- a/src/main/java/emu/nebula/server/handlers/HandlerVampireSurvivorApplyReq.java +++ b/src/main/java/emu/nebula/server/handlers/HandlerVampireSurvivorApplyReq.java @@ -16,7 +16,7 @@ public class HandlerVampireSurvivorApplyReq extends NetHandler { var req = VampireSurvivorApplyReq.parseFrom(message); // Apply - var game = session.getPlayer().getVampireSurvivorManager().apply(req.getId()); + var game = session.getPlayer().getVampireSurvivorManager().apply(req.getId(), req.getBuildIds()); if (game == null) { return session.encodeMsg(NetMsgId.vampire_survivor_apply_failed_ack); diff --git a/src/main/java/emu/nebula/server/handlers/HandlerVampireSurvivorSettleReq.java b/src/main/java/emu/nebula/server/handlers/HandlerVampireSurvivorSettleReq.java index ba0044b..4d6539d 100644 --- a/src/main/java/emu/nebula/server/handlers/HandlerVampireSurvivorSettleReq.java +++ b/src/main/java/emu/nebula/server/handlers/HandlerVampireSurvivorSettleReq.java @@ -25,7 +25,7 @@ public class HandlerVampireSurvivorSettleReq extends NetHandler { // Calculate victory + score boolean victory = !req.getDefeat(); - int score = 0; + int score = 1; // Settle session.getPlayer().getVampireSurvivorManager().settle(victory, score); diff --git a/src/main/java/emu/nebula/server/handlers/HandlerVampireTalentDetailReq.java b/src/main/java/emu/nebula/server/handlers/HandlerVampireTalentDetailReq.java index be5f2a7..18480ad 100644 --- a/src/main/java/emu/nebula/server/handlers/HandlerVampireTalentDetailReq.java +++ b/src/main/java/emu/nebula/server/handlers/HandlerVampireTalentDetailReq.java @@ -4,6 +4,7 @@ import emu.nebula.net.NetHandler; import emu.nebula.net.NetMsgId; import emu.nebula.proto.VampireTalentDetail.VampireTalentDetailResp; import emu.nebula.net.HandlerId; + import emu.nebula.net.GameSession; @HandlerId(NetMsgId.vampire_talent_detail_req) @@ -11,9 +12,13 @@ public class HandlerVampireTalentDetailReq extends NetHandler { @Override public byte[] handle(GameSession session, byte[] message) throws Exception { + // Get vampire surv manager + var manager = session.getPlayer().getVampireSurvivorManager(); + // Build response var rsp = VampireTalentDetailResp.newInstance() - .setNodes(new byte[8]); + .setNodes(manager.getTalents().toByteArray()) + .setActiveCount(manager.getTalentPoints()); // Encode and send return session.encodeMsg(NetMsgId.vampire_talent_detail_succeed_ack, rsp);