Implement vampire survivor records

This commit is contained in:
Melledy
2025-11-14 18:12:07 -08:00
parent 300d51aaf2
commit 30d0dfbaee
9 changed files with 165 additions and 17 deletions

View File

@@ -8,6 +8,8 @@ import lombok.Getter;
@ResourceType(name = "VampireSurvivor.json") @ResourceType(name = "VampireSurvivor.json")
public class VampireSurvivorDef extends BaseDef { public class VampireSurvivorDef extends BaseDef {
private int Id; private int Id;
private int Mode;
private int NeedWorldClass;
@Override @Override
public int getId() { public int getId() {

View File

@@ -1,10 +1,14 @@
package emu.nebula.game.player; package emu.nebula.game.player;
import java.util.HashMap;
import java.util.Map;
import dev.morphia.annotations.Entity; import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Id; import dev.morphia.annotations.Id;
import emu.nebula.Nebula; import emu.nebula.Nebula;
import emu.nebula.data.GameData; import emu.nebula.data.GameData;
import emu.nebula.database.GameDatabaseObject; import emu.nebula.database.GameDatabaseObject;
import emu.nebula.game.vampire.VampireSurvivorLog;
import emu.nebula.proto.PlayerData.PlayerInfo; import emu.nebula.proto.PlayerData.PlayerInfo;
import emu.nebula.proto.Public.CharGemInstance; import emu.nebula.proto.Public.CharGemInstance;
import emu.nebula.proto.Public.DailyInstance; 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.SkillInstance;
import emu.nebula.proto.Public.VampireSurvivorLevel; import emu.nebula.proto.Public.VampireSurvivorLevel;
import emu.nebula.proto.Public.WeekBossLevel; import emu.nebula.proto.Public.WeekBossLevel;
import emu.nebula.util.Bitset;
import it.unimi.dsi.fastutil.ints.Int2IntMap; import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
@@ -38,6 +43,9 @@ public class PlayerProgress extends PlayerManager implements GameDatabaseObject
private Int2IntMap infinityArenaLog; private Int2IntMap infinityArenaLog;
// Vampire Survivors TODO // Vampire Survivors TODO
private Map<Integer, VampireSurvivorLog> vampireLog;
private Bitset vampireTalents;
private IntSet vampireCards;
@Deprecated // Morphia only @Deprecated // Morphia only
public PlayerProgress() { public PlayerProgress() {
@@ -62,6 +70,9 @@ public class PlayerProgress extends PlayerManager implements GameDatabaseObject
this.infinityArenaLog = new Int2IntOpenHashMap(); this.infinityArenaLog = new Int2IntOpenHashMap();
// Vampire Survivor // Vampire Survivor
this.vampireLog = new HashMap<>();
this.vampireTalents = new Bitset();
this.vampireCards = new IntOpenHashSet();
// Save to database // Save to database
this.save(); this.save();
@@ -180,18 +191,31 @@ public class PlayerProgress extends PlayerManager implements GameDatabaseObject
proto.addWeekBossLevels(p); proto.addWeekBossLevels(p);
} }
// Force unlock all vampire survivor records // Vampire survivors
var vsProto = proto.getMutableVampireSurvivorRecord(); var vsProto = proto.getMutableVampireSurvivorRecord();
vsProto.getMutableSeason(); vsProto.getMutableSeason();
for (var vsData : GameData.getVampireSurvivorDataTable()) { if (unlockAll) {
var level = VampireSurvivorLevel.newInstance() // Force unlock all vampire survivor records if we dont have the records
.setId(vsData.getId()) for (var vsData : GameData.getVampireSurvivorDataTable()) {
.setScore(0) // Get existing record
.setPassed(true); var log = this.getVampireLog().get(vsData.getId());
vsProto.addRecords(level); 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());
}
} }
} }
} }

View File

@@ -37,6 +37,10 @@ public class StarTowerManager extends PlayerManager {
public StarTowerBuild getBuildById(long id) { public StarTowerBuild getBuildById(long id) {
return this.getBuilds().get(id); return this.getBuilds().get(id);
} }
public boolean hasBuild(long id) {
return this.getBuilds().containsKey(id);
}
public StarTowerGame apply(StarTowerApplyReq req) { public StarTowerGame apply(StarTowerApplyReq req) {
// Sanity checks // Sanity checks

View File

@@ -15,6 +15,7 @@ import lombok.Getter;
public class VampireSurvivorGame { public class VampireSurvivorGame {
private final VampireSurvivorManager manager; private final VampireSurvivorManager manager;
private final VampireSurvivorDef data; private final VampireSurvivorDef data;
private long[] builds;
private IntSet cards; private IntSet cards;
@@ -22,9 +23,10 @@ public class VampireSurvivorGame {
private int rewardLevel; private int rewardLevel;
private IntList rewards; private IntList rewards;
public VampireSurvivorGame(VampireSurvivorManager manager, VampireSurvivorDef data) { public VampireSurvivorGame(VampireSurvivorManager manager, VampireSurvivorDef data, long[] builds) {
this.manager = manager; this.manager = manager;
this.data = data; this.data = data;
this.builds = builds;
this.cards = new IntOpenHashSet(); this.cards = new IntOpenHashSet();
this.rewards = new IntArrayList(); this.rewards = new IntArrayList();
@@ -32,6 +34,10 @@ public class VampireSurvivorGame {
this.calcRewards(); this.calcRewards();
} }
public int getId() {
return this.getData().getId();
}
private WeightedList<Integer> getRandom() { private WeightedList<Integer> getRandom() {
var random = new WeightedList<Integer>(); var random = new WeightedList<Integer>();

View File

@@ -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;
}
}

View File

@@ -1,35 +1,100 @@
package emu.nebula.game.vampire; package emu.nebula.game.vampire;
import emu.nebula.Nebula;
import emu.nebula.data.GameData; import emu.nebula.data.GameData;
import emu.nebula.game.player.Player; import emu.nebula.game.player.Player;
import emu.nebula.game.player.PlayerManager; import emu.nebula.game.player.PlayerManager;
import emu.nebula.game.player.PlayerProgress;
import emu.nebula.util.Bitset;
import lombok.Getter; import lombok.Getter;
import us.hebi.quickbuf.RepeatedLong;
@Getter @Getter
public class VampireSurvivorManager extends PlayerManager { public class VampireSurvivorManager extends PlayerManager {
// Game // Game
private transient VampireSurvivorGame game; private VampireSurvivorGame game;
public VampireSurvivorManager(Player player) { public VampireSurvivorManager(Player player) {
super(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 // Get data
var data = GameData.getVampireSurvivorDataTable().get(levelId); var data = GameData.getVampireSurvivorDataTable().get(levelId);
if (data == null) { if (data == null) {
return 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 // Create game
this.game = new VampireSurvivorGame(this, data); this.game = new VampireSurvivorGame(this, data, builds.toArray());
// Success // Success
return this.game; return this.game;
} }
public void settle(boolean isWin, int score) { 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 // Clear game
this.game = null; this.game = null;
} }

View File

@@ -16,7 +16,7 @@ public class HandlerVampireSurvivorApplyReq extends NetHandler {
var req = VampireSurvivorApplyReq.parseFrom(message); var req = VampireSurvivorApplyReq.parseFrom(message);
// Apply // Apply
var game = session.getPlayer().getVampireSurvivorManager().apply(req.getId()); var game = session.getPlayer().getVampireSurvivorManager().apply(req.getId(), req.getBuildIds());
if (game == null) { if (game == null) {
return session.encodeMsg(NetMsgId.vampire_survivor_apply_failed_ack); return session.encodeMsg(NetMsgId.vampire_survivor_apply_failed_ack);

View File

@@ -25,7 +25,7 @@ public class HandlerVampireSurvivorSettleReq extends NetHandler {
// Calculate victory + score // Calculate victory + score
boolean victory = !req.getDefeat(); boolean victory = !req.getDefeat();
int score = 0; int score = 1;
// Settle // Settle
session.getPlayer().getVampireSurvivorManager().settle(victory, score); session.getPlayer().getVampireSurvivorManager().settle(victory, score);

View File

@@ -4,6 +4,7 @@ import emu.nebula.net.NetHandler;
import emu.nebula.net.NetMsgId; import emu.nebula.net.NetMsgId;
import emu.nebula.proto.VampireTalentDetail.VampireTalentDetailResp; import emu.nebula.proto.VampireTalentDetail.VampireTalentDetailResp;
import emu.nebula.net.HandlerId; import emu.nebula.net.HandlerId;
import emu.nebula.net.GameSession; import emu.nebula.net.GameSession;
@HandlerId(NetMsgId.vampire_talent_detail_req) @HandlerId(NetMsgId.vampire_talent_detail_req)
@@ -11,9 +12,13 @@ public class HandlerVampireTalentDetailReq extends NetHandler {
@Override @Override
public byte[] handle(GameSession session, byte[] message) throws Exception { public byte[] handle(GameSession session, byte[] message) throws Exception {
// Get vampire surv manager
var manager = session.getPlayer().getVampireSurvivorManager();
// Build response // Build response
var rsp = VampireTalentDetailResp.newInstance() var rsp = VampireTalentDetailResp.newInstance()
.setNodes(new byte[8]); .setNodes(manager.getTalents().toByteArray())
.setActiveCount(manager.getTalentPoints());
// Encode and send // Encode and send
return session.encodeMsg(NetMsgId.vampire_talent_detail_succeed_ack, rsp); return session.encodeMsg(NetMsgId.vampire_talent_detail_succeed_ack, rsp);