mirror of
https://github.com/Melledy/Nebula.git
synced 2025-12-24 10:14:48 +01:00
Rework how story is handled, fixes choices not saving
This commit is contained in:
@@ -93,6 +93,7 @@ public class GameData {
|
||||
// ===== Story =====
|
||||
@Getter private static DataTable<StoryDef> StoryDataTable = new DataTable<>();
|
||||
@Getter private static DataTable<StorySetSectionDef> StorySetSectionDataTable = new DataTable<>();
|
||||
@Getter private static DataTable<StoryEvidenceDef> StoryEvidenceDataTable = new DataTable<>();
|
||||
|
||||
// ===== Daily Quests =====
|
||||
@Getter private static DataTable<DailyQuestDef> DailyQuestDataTable = new DataTable<>();
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package emu.nebula.data.resources;
|
||||
|
||||
import emu.nebula.data.BaseDef;
|
||||
import emu.nebula.data.ResourceType;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
@ResourceType(name = "StoryEvidence.json")
|
||||
public class StoryEvidenceDef extends BaseDef {
|
||||
private int Id;
|
||||
|
||||
@Override
|
||||
public int getId() {
|
||||
return Id;
|
||||
}
|
||||
}
|
||||
@@ -45,7 +45,6 @@ import emu.nebula.proto.Public.Friend;
|
||||
import emu.nebula.proto.Public.HonorInfo;
|
||||
import emu.nebula.proto.Public.NewbieInfo;
|
||||
import emu.nebula.proto.Public.QuestType;
|
||||
import emu.nebula.proto.Public.Story;
|
||||
import emu.nebula.proto.Public.WorldClass;
|
||||
import emu.nebula.proto.Public.WorldClassRewardState;
|
||||
import emu.nebula.util.Utils;
|
||||
@@ -931,14 +930,7 @@ public class Player implements GameDatabaseObject {
|
||||
acc.addNewbies(NewbieInfo.newInstance().setGroupId(GameConstants.INTRO_GUIDE_ID).setStepId(-1));
|
||||
|
||||
// Story
|
||||
var story = proto.getMutableStory();
|
||||
|
||||
for (int storyId : this.getStoryManager().getCompletedStories()) {
|
||||
var storyProto = Story.newInstance()
|
||||
.setIdx(storyId);
|
||||
|
||||
story.addStories(storyProto);
|
||||
}
|
||||
this.getStoryManager().encodePlayerInfo(proto);
|
||||
|
||||
// Add titles
|
||||
for (int titleId : this.getInventory().getTitles()) {
|
||||
@@ -954,7 +946,7 @@ public class Player implements GameDatabaseObject {
|
||||
}
|
||||
|
||||
// Quests
|
||||
this.getQuestManager().encodeProto(proto);
|
||||
this.getQuestManager().encodePlayerInfo(proto);
|
||||
|
||||
// Add dictionary tabs
|
||||
for (var dictionaryData : GameData.getDictionaryTabDataTable()) {
|
||||
@@ -973,7 +965,7 @@ public class Player implements GameDatabaseObject {
|
||||
}
|
||||
|
||||
// Add progress
|
||||
this.getProgress().encodeProto(proto);
|
||||
this.getProgress().encodePlayerInfo(proto);
|
||||
|
||||
// Handbook
|
||||
proto.addHandbook(this.getCharacters().getCharacterHandbook());
|
||||
|
||||
@@ -186,7 +186,7 @@ public class PlayerProgress extends PlayerManager implements GameDatabaseObject
|
||||
|
||||
// Proto
|
||||
|
||||
public void encodeProto(PlayerInfo proto) {
|
||||
public void encodePlayerInfo(PlayerInfo proto) {
|
||||
// Check if we want to unlock all instances
|
||||
boolean unlockAll = Nebula.getConfig().getServerOptions().unlockInstances;
|
||||
|
||||
|
||||
@@ -312,7 +312,7 @@ public class QuestManager extends PlayerManager implements GameDatabaseObject {
|
||||
|
||||
// Serialization
|
||||
|
||||
public void encodeProto(PlayerInfo proto) {
|
||||
public void encodePlayerInfo(PlayerInfo proto) {
|
||||
var quests = proto.getMutableQuests();
|
||||
|
||||
for (var quest : this.getQuests().values()) {
|
||||
|
||||
32
src/main/java/emu/nebula/game/story/StoryChoiceInfo.java
Normal file
32
src/main/java/emu/nebula/game/story/StoryChoiceInfo.java
Normal file
@@ -0,0 +1,32 @@
|
||||
package emu.nebula.game.story;
|
||||
|
||||
import dev.morphia.annotations.Entity;
|
||||
import emu.nebula.proto.Public.StoryChoice;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
@Entity(useDiscriminator = false)
|
||||
public class StoryChoiceInfo {
|
||||
private int group;
|
||||
private int value;
|
||||
|
||||
@Deprecated
|
||||
public StoryChoiceInfo() {
|
||||
// Morphia only
|
||||
}
|
||||
|
||||
public StoryChoiceInfo(int group, int value) {
|
||||
this.group = group;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
// Proto
|
||||
|
||||
public StoryChoice toProto() {
|
||||
var proto = StoryChoice.newInstance()
|
||||
.setGroup(this.getGroup())
|
||||
.setValue(this.getValue());
|
||||
|
||||
return proto;
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,28 @@
|
||||
package emu.nebula.game.story;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import dev.morphia.annotations.Entity;
|
||||
import dev.morphia.annotations.Id;
|
||||
import dev.morphia.annotations.PostLoad;
|
||||
import emu.nebula.Nebula;
|
||||
import emu.nebula.data.GameData;
|
||||
import emu.nebula.database.GameDatabaseObject;
|
||||
import emu.nebula.game.player.Player;
|
||||
import emu.nebula.game.player.PlayerChangeInfo;
|
||||
import emu.nebula.game.player.PlayerManager;
|
||||
import emu.nebula.proto.PlayerData.PlayerInfo;
|
||||
import emu.nebula.proto.Public.Story;
|
||||
import emu.nebula.proto.StorySett.StorySettle;
|
||||
import it.unimi.dsi.fastutil.ints.Int2IntMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.ints.IntList;
|
||||
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
|
||||
import it.unimi.dsi.fastutil.ints.IntSet;
|
||||
|
||||
import lombok.Getter;
|
||||
import us.hebi.quickbuf.RepeatedInt;
|
||||
import us.hebi.quickbuf.RepeatedMessage;
|
||||
|
||||
@Getter
|
||||
@Entity(value = "story", useDiscriminator = false)
|
||||
@@ -24,6 +32,10 @@ public class StoryManager extends PlayerManager implements GameDatabaseObject {
|
||||
|
||||
private IntSet completedStories;
|
||||
private Int2IntMap completedSets;
|
||||
private IntSet evidences;
|
||||
|
||||
// Note: Story options are seperate from regular story ids to save database space, since most stories do not have options
|
||||
private Map<Integer, StoryOptionLog> options;
|
||||
|
||||
@Deprecated // Morphia only
|
||||
public StoryManager() {
|
||||
@@ -35,6 +47,8 @@ public class StoryManager extends PlayerManager implements GameDatabaseObject {
|
||||
this.uid = player.getUid();
|
||||
this.completedStories = new IntOpenHashSet();
|
||||
this.completedSets = new Int2IntOpenHashMap();
|
||||
this.evidences = new IntOpenHashSet();
|
||||
this.options = new HashMap<>();
|
||||
|
||||
this.save();
|
||||
}
|
||||
@@ -51,15 +65,22 @@ public class StoryManager extends PlayerManager implements GameDatabaseObject {
|
||||
return false;
|
||||
}
|
||||
|
||||
public PlayerChangeInfo settle(IntList list) {
|
||||
public PlayerChangeInfo settle(RepeatedMessage<StorySettle> list, RepeatedInt evidences) {
|
||||
// Player change info
|
||||
var changes = new PlayerChangeInfo();
|
||||
var change = new PlayerChangeInfo();
|
||||
|
||||
// Handle regular story
|
||||
for (var settle : list) {
|
||||
// Get id
|
||||
int id = settle.getIdx();
|
||||
|
||||
for (int id : list) {
|
||||
// Get story data
|
||||
var data = GameData.getStoryDataTable().get(id);
|
||||
if (data == null) continue;
|
||||
|
||||
// Settle options (Must be before the completion check as we need to do the same story multiple times to get all the endings)
|
||||
this.settleOptions(settle);
|
||||
|
||||
// Check if we already completed the story
|
||||
if (this.getCompletedStories().contains(id)) {
|
||||
continue;
|
||||
@@ -69,14 +90,61 @@ public class StoryManager extends PlayerManager implements GameDatabaseObject {
|
||||
this.getCompletedStories().add(id);
|
||||
|
||||
// Add rewards
|
||||
this.getPlayer().getInventory().addItems(data.getRewards(), changes);
|
||||
this.getPlayer().getInventory().addItems(data.getRewards(), change);
|
||||
|
||||
// Save to db
|
||||
Nebula.getGameDatabase().addToSet(this, this.getPlayerUid(), "completedStories", id);
|
||||
}
|
||||
|
||||
// Handle evidences
|
||||
for (int id : evidences) {
|
||||
// Verify that evidence id exists
|
||||
var data = GameData.getStoryEvidenceDataTable().get(id);
|
||||
if (data == null) continue;
|
||||
|
||||
// Sanity check
|
||||
if (this.getEvidences().contains(id)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Save to db
|
||||
Nebula.getGameDatabase().addToSet(this, this.getPlayerUid(), "evidences", id);
|
||||
}
|
||||
|
||||
// Complete
|
||||
return changes;
|
||||
return change;
|
||||
}
|
||||
|
||||
private void settleOptions(StorySettle settle) {
|
||||
// Init variables
|
||||
boolean changed = false;
|
||||
StoryOptionLog log = null;
|
||||
|
||||
// Update
|
||||
if (settle.hasMajor()) {
|
||||
if (log == null) {
|
||||
log = getOptions().computeIfAbsent(settle.getIdx(), idx -> new StoryOptionLog());
|
||||
}
|
||||
|
||||
if (log.settleMajor(settle.getMajor())) {
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (settle.hasPersonality()) {
|
||||
if (log == null) {
|
||||
log = getOptions().computeIfAbsent(settle.getIdx(), idx -> new StoryOptionLog());
|
||||
}
|
||||
|
||||
if (log.settlePersonality(settle.getPersonality())) {
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Save to database if we changed anything
|
||||
if (changed) {
|
||||
Nebula.getGameDatabase().update(this, this.getPlayerUid(), "options." + settle.getIdx(), log);
|
||||
}
|
||||
}
|
||||
|
||||
public PlayerChangeInfo settleSet(int chapterId, int sectionId) {
|
||||
@@ -106,4 +174,47 @@ public class StoryManager extends PlayerManager implements GameDatabaseObject {
|
||||
// Complete
|
||||
return changes;
|
||||
}
|
||||
|
||||
// Proto
|
||||
|
||||
public void encodePlayerInfo(PlayerInfo proto) {
|
||||
var story = proto.getMutableStory();
|
||||
|
||||
for (int storyId : this.getCompletedStories()) {
|
||||
var storyProto = Story.newInstance()
|
||||
.setIdx(storyId);
|
||||
|
||||
var storyOptions = this.getOptions().get(storyId);
|
||||
if (storyOptions != null) {
|
||||
storyOptions.encodeStoryProto(storyProto);
|
||||
}
|
||||
|
||||
story.addStories(storyProto);
|
||||
}
|
||||
|
||||
for (int id : this.getEvidences()) {
|
||||
story.addEvidences(id);
|
||||
}
|
||||
}
|
||||
|
||||
// Database fixes
|
||||
|
||||
@PostLoad
|
||||
public void onLoad() {
|
||||
boolean save = false;
|
||||
|
||||
if (this.evidences == null) {
|
||||
this.evidences = new IntOpenHashSet();
|
||||
save = true;
|
||||
}
|
||||
|
||||
if (this.options == null) {
|
||||
this.options = new HashMap<>();
|
||||
save = true;
|
||||
}
|
||||
|
||||
if (save) {
|
||||
this.save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
143
src/main/java/emu/nebula/game/story/StoryOptionLog.java
Normal file
143
src/main/java/emu/nebula/game/story/StoryOptionLog.java
Normal file
@@ -0,0 +1,143 @@
|
||||
package emu.nebula.game.story;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import dev.morphia.annotations.Entity;
|
||||
|
||||
import emu.nebula.proto.Public.Story;
|
||||
import emu.nebula.proto.StorySett.StoryOptions;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
import us.hebi.quickbuf.RepeatedMessage;
|
||||
|
||||
@Getter
|
||||
@Entity(useDiscriminator = false)
|
||||
public class StoryOptionLog {
|
||||
private List<StoryChoiceInfo> major;
|
||||
private List<StoryChoiceInfo> personality;
|
||||
|
||||
public StoryOptionLog() {
|
||||
|
||||
}
|
||||
|
||||
public int getMajorOptionSize() {
|
||||
if (this.major == null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return this.major.size();
|
||||
}
|
||||
|
||||
public boolean hasMajorOption(int group, int choice) {
|
||||
if (this.major == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.major.stream()
|
||||
.filter(c -> c.getGroup() == group && c.getValue() == choice)
|
||||
.findFirst()
|
||||
.isPresent();
|
||||
}
|
||||
|
||||
public boolean addMajorOption(int group, int choice) {
|
||||
if (this.major == null) {
|
||||
this.major = new ArrayList<>();
|
||||
}
|
||||
|
||||
return this.major.add(new StoryChoiceInfo(group, choice));
|
||||
}
|
||||
|
||||
public boolean settleMajor(RepeatedMessage<StoryOptions> options) {
|
||||
boolean success = false;
|
||||
|
||||
for (var option : options) {
|
||||
// Sanity check
|
||||
if (this.getMajorOptionSize() >= 5) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Skip if we already have this choice
|
||||
if (this.hasMajorOption(option.getGroup(), option.getChoice())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add
|
||||
this.addMajorOption(option.getGroup(), option.getChoice());
|
||||
|
||||
// Set success flag
|
||||
success = true;
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
public int getPersonalityOptionSize() {
|
||||
if (this.personality == null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return this.personality.size();
|
||||
}
|
||||
|
||||
public boolean hasPersonalityOption(int group, int choice) {
|
||||
if (this.personality == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.personality.stream()
|
||||
.filter(c -> c.getGroup() == group && c.getValue() == choice)
|
||||
.findFirst()
|
||||
.isPresent();
|
||||
}
|
||||
|
||||
public boolean addPersonalityOption(int group, int choice) {
|
||||
if (this.personality == null) {
|
||||
this.personality = new ArrayList<>();
|
||||
}
|
||||
|
||||
return this.personality.add(new StoryChoiceInfo(group, choice));
|
||||
}
|
||||
|
||||
public boolean settlePersonality(RepeatedMessage<StoryOptions> options) {
|
||||
boolean success = false;
|
||||
|
||||
for (var option : options) {
|
||||
// Sanity check
|
||||
if (this.getPersonalityOptionSize() >= 5) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Skip if we already have this choice
|
||||
if (this.hasPersonalityOption(option.getGroup(), option.getChoice())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add
|
||||
this.addPersonalityOption(option.getGroup(), option.getChoice());
|
||||
|
||||
// Set success flag
|
||||
success = true;
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
// Proto
|
||||
|
||||
public void encodeStoryProto(Story proto) {
|
||||
if (this.major != null) {
|
||||
for (var choice : this.major) {
|
||||
proto.addMajor(choice.toProto());
|
||||
}
|
||||
}
|
||||
|
||||
if (this.personality != null) {
|
||||
for (var choice : this.personality) {
|
||||
proto.addMajor(choice.toProto());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3,7 +3,6 @@ package emu.nebula.server.handlers;
|
||||
import emu.nebula.net.NetHandler;
|
||||
import emu.nebula.net.NetMsgId;
|
||||
import emu.nebula.proto.StorySett.StorySettleReq;
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
||||
import emu.nebula.net.HandlerId;
|
||||
import emu.nebula.net.GameSession;
|
||||
|
||||
@@ -15,18 +14,14 @@ public class HandlerStorySettleReq extends NetHandler {
|
||||
// Parse request
|
||||
var req = StorySettleReq.parseFrom(message);
|
||||
|
||||
// Get list of settled story ids
|
||||
var list = new IntArrayList();
|
||||
|
||||
for (var settle : req.getList()) {
|
||||
list.add(settle.getIdx());
|
||||
}
|
||||
|
||||
// Settle
|
||||
var changes = session.getPlayer().getStoryManager().settle(list);
|
||||
var change = session.getPlayer().getStoryManager().settle(req.getList(), req.getEvidences());
|
||||
|
||||
// Handle client events for achievements
|
||||
session.getPlayer().getAchievementManager().handleClientEvents(req.getEvents());
|
||||
|
||||
// Send response
|
||||
return session.encodeMsg(NetMsgId.story_settle_succeed_ack, changes.toProto());
|
||||
return session.encodeMsg(NetMsgId.story_settle_succeed_ack, change.toProto());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import java.net.ServerSocket;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.time.YearMonth;
|
||||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Base64;
|
||||
|
||||
Reference in New Issue
Block a user