Implement tutorial levels

This commit is contained in:
Melledy
2025-11-15 00:52:54 -08:00
parent b19667315d
commit 66b2d84945
8 changed files with 197 additions and 0 deletions

View File

@@ -87,6 +87,9 @@ public class GameData {
@Getter private static DataTable<DailyQuestDef> DailyQuestDataTable = new DataTable<>();
@Getter private static DataTable<DailyQuestActiveDef> DailyQuestActiveDataTable = new DataTable<>();
// Tutorial
@Getter private static DataTable<TutorialLevelDef> TutorialLevelDataTable = new DataTable<>();
// Star tower
@Getter private static DataTable<StarTowerDef> StarTowerDataTable = new DataTable<>();
@Getter private static DataTable<StarTowerStageDef> StarTowerStageDataTable = new DataTable<>();

View File

@@ -0,0 +1,20 @@
package emu.nebula.data.resources;
import emu.nebula.data.BaseDef;
import emu.nebula.data.ResourceType;
import lombok.Getter;
@Getter
@ResourceType(name = "TutorialLevel.json")
public class TutorialLevelDef extends BaseDef {
private int Id;
private int WorldClass;
private int Item1;
private int Qty1;
@Override
public int getId() {
return Id;
}
}

View File

@@ -11,6 +11,7 @@ import emu.nebula.Nebula;
import emu.nebula.game.gacha.GachaModule;
import emu.nebula.game.player.PlayerModule;
import emu.nebula.game.scoreboss.ScoreBossModule;
import emu.nebula.game.tutorial.TutorialModule;
import emu.nebula.net.GameSession;
import it.unimi.dsi.fastutil.objects.Object2ObjectMap;
@@ -25,6 +26,7 @@ public class GameContext implements Runnable {
// Modules
private final PlayerModule playerModule;
private final GachaModule gachaModule;
private final TutorialModule tutorialModule;
private final ScoreBossModule scoreBossModule;
// Game loop
@@ -39,6 +41,7 @@ public class GameContext implements Runnable {
// Setup game modules
this.playerModule = new PlayerModule(this);
this.gachaModule = new GachaModule(this);
this.tutorialModule = new TutorialModule(this);
this.scoreBossModule = new ScoreBossModule(this);
// Run game loop

View File

@@ -8,6 +8,7 @@ import dev.morphia.annotations.Id;
import emu.nebula.Nebula;
import emu.nebula.data.GameData;
import emu.nebula.database.GameDatabaseObject;
import emu.nebula.game.tutorial.TutorialLevelLog;
import emu.nebula.game.vampire.VampireSurvivorLog;
import emu.nebula.proto.PlayerData.PlayerInfo;
import emu.nebula.proto.Public.CharGemInstance;
@@ -48,6 +49,9 @@ public class PlayerProgress extends PlayerManager implements GameDatabaseObject
// Fate cards
private IntSet fateCards;
// Tutorial
private Map<Integer, TutorialLevelLog> tutorialLog;
@Deprecated // Morphia only
public PlayerProgress() {
@@ -74,8 +78,13 @@ public class PlayerProgress extends PlayerManager implements GameDatabaseObject
// Vampire Survivor
this.vampireLog = new HashMap<>();
this.vampireTalents = new Bitset();
// Fate cards
this.fateCards = new IntOpenHashSet();
// Tutorials
this.tutorialLog = new HashMap<>();
// Save to database
this.save();
}
@@ -219,5 +228,10 @@ public class PlayerProgress extends PlayerManager implements GameDatabaseObject
vsProto.addRecords(log.toProto());
}
}
// Tutorials
for (var tutorial : this.getTutorialLog().values()) {
proto.addTutorialLevels(tutorial.toProto());
}
}
}

View File

@@ -0,0 +1,36 @@
package emu.nebula.game.tutorial;
import dev.morphia.annotations.Entity;
import emu.nebula.proto.Public.TutorialLevel;
import lombok.Getter;
@Getter
@Entity(useDiscriminator = false)
public class TutorialLevelLog {
private int id;
private boolean claimed;
@Deprecated // Morphia only
public TutorialLevelLog() {
}
public TutorialLevelLog(int id) {
this.id = id;
}
public void setClaimed(boolean value) {
this.claimed = value;
}
// Proto
public TutorialLevel toProto() {
var proto = TutorialLevel.newInstance()
.setLevelId(this.getId())
.setPassed(true)
.setRewardReceived(this.isClaimed());
return proto;
}
}

View File

@@ -0,0 +1,63 @@
package emu.nebula.game.tutorial;
import emu.nebula.Nebula;
import emu.nebula.data.GameData;
import emu.nebula.game.GameContext;
import emu.nebula.game.GameContextModule;
import emu.nebula.game.player.Player;
import emu.nebula.game.player.PlayerChangeInfo;
public class TutorialModule extends GameContextModule {
public TutorialModule(GameContext context) {
super(context);
}
public boolean settle(Player player, int id) {
// Check if the tutorial was completed
if (player.getProgress().getTutorialLog().containsKey(id)) {
return true;
}
// Get data
var data = GameData.getTutorialLevelDataTable().get(id);
if (data == null) {
return false;
}
// Create log
var log = new TutorialLevelLog(id);
// Add to progress tutorial map
player.getProgress().getTutorialLog().put(id, log);
// Save to database
Nebula.getGameDatabase().update(player.getProgress(), player.getUid(), "tutorialLog." + log.getId(), log);
// Success
return true;
}
public PlayerChangeInfo recvReward(Player player, int id) {
// Get tutorial log
var log = player.getProgress().getTutorialLog().get(id);
if (log == null || log.isClaimed()) {
return null;
}
// Get data
var data = GameData.getTutorialLevelDataTable().get(id);
if (data == null) {
return null;
}
// Set claim state
log.setClaimed(true);
// Save to database
Nebula.getGameDatabase().update(player.getProgress(), player.getUid(), "tutorialLog." + log.getId(), log);
// Add reward item
return player.getInventory().addItem(data.getItem1(), data.getQty1());
}
}

View File

@@ -0,0 +1,29 @@
package emu.nebula.server.handlers;
import emu.nebula.net.NetHandler;
import emu.nebula.net.NetMsgId;
import emu.nebula.proto.Public.UI32;
import emu.nebula.net.HandlerId;
import emu.nebula.Nebula;
import emu.nebula.net.GameSession;
@HandlerId(NetMsgId.tutorial_level_reward_receive_req)
public class HandlerTutorialLevelRewardReceiveReq extends NetHandler {
@Override
public byte[] handle(GameSession session, byte[] message) throws Exception {
// Parse request
var req = UI32.parseFrom(message);
// Get rewards
var change = Nebula.getGameContext().getTutorialModule().recvReward(session.getPlayer(), req.getValue());
if (change == null) {
return session.encodeMsg(NetMsgId.tutorial_level_reward_receive_failed_ack);
}
// Encode and send
return session.encodeMsg(NetMsgId.tutorial_level_reward_receive_succeed_ack, change.toProto());
}
}

View File

@@ -0,0 +1,29 @@
package emu.nebula.server.handlers;
import emu.nebula.net.NetHandler;
import emu.nebula.net.NetMsgId;
import emu.nebula.proto.Public.UI32;
import emu.nebula.net.HandlerId;
import emu.nebula.Nebula;
import emu.nebula.net.GameSession;
@HandlerId(NetMsgId.tutorial_level_settle_req)
public class HandlerTutorialLevelSettleReq extends NetHandler {
@Override
public byte[] handle(GameSession session, byte[] message) throws Exception {
// Parse request
var req = UI32.parseFrom(message);
// Settle
boolean success = Nebula.getGameContext().getTutorialModule().settle(session.getPlayer(), req.getValue());
if (success == false) {
return session.encodeMsg(NetMsgId.tutorial_level_settle_failed_ack);
}
// Encode and send
return session.encodeMsg(NetMsgId.tutorial_level_settle_succeed_ack);
}
}