Save system: ceremonial PR (#42)

prototype for writing a save

Merge branch 'main' into savesystem

violently read player from file

oops, missed revisions in StateManager

create StateManager from file

autoformat the world

oops, forgot to save the split-up of save.ts

Save on end-of-day, or after endgame.

Putting it here avoids a circular reference problem

Merge branch 'main' into savesystem

Integrate save system

Deal with save corruption correctly

Co-authored-by: Kistaro Windrider <kistaro@gmail.com>
Reviewed-on: #42
Co-authored-by: Nyeogmi <economicsbat@gmail.com>
Co-committed-by: Nyeogmi <economicsbat@gmail.com>
This commit is contained in:
2025-02-25 04:14:02 +00:00
committed by Pyrex
parent 897133f8de
commit 19b097a0bd
8 changed files with 578 additions and 27 deletions

View File

@ -1,6 +1,12 @@
import { ALL_STATS, Skill, Stat, SuccessorOption, Wish } from "./datatypes.ts";
import { getSkills } from "./skills.ts";
import { getThralls, ItemStage, LifeStage, Thrall } from "./thralls.ts";
import { SaveFileV1, mustBeSaveFileV1 } from "./saveformat.ts";
interface NewRoundConfig {
asSuccessor: SuccessorOption;
withWish: Wish | null;
}
export class PlayerProgress {
#name: string;
@ -20,25 +26,65 @@ export class PlayerProgress {
#thrallsObtainedItem: number[];
#thrallsDeliveredItem: number[];
constructor(asSuccessor: SuccessorOption, withWish: Wish | null) {
this.#name = asSuccessor.name;
this.#thrallTemplate = asSuccessor.template.id;
this.#nImprovements = asSuccessor.nImprovements;
this.#stats = { ...asSuccessor.stats };
this.#talents = { ...asSuccessor.talents };
this.#isInPenance = asSuccessor.inPenance;
this.#wish = withWish;
this.#exp = 0;
this.#blood = 0;
this.#itemsPurloined = 0;
this.#skillsLearned = [];
this.#untrimmedSkillsAvailable = [];
this.#thrallsUnlocked = [];
this.#thrallDamage = {};
this.#thrallsObtainedItem = [];
this.#thrallsDeliveredItem = [];
constructor(args: NewRoundConfig | SaveFileV1) {
if ("asSuccessor" in args) {
//asSuccessor: SuccessorOption, withWish: Wish | null) {
const config = args as NewRoundConfig;
const asSuccessor = config.asSuccessor;
this.#name = asSuccessor.name;
this.#thrallTemplate = asSuccessor.template.id;
this.#nImprovements = asSuccessor.nImprovements;
this.#stats = { ...asSuccessor.stats };
this.#talents = { ...asSuccessor.talents };
this.#isInPenance = asSuccessor.inPenance;
this.#wish = config.withWish;
this.#exp = 0;
this.#blood = 0;
this.#itemsPurloined = 0;
this.#skillsLearned = [];
this.#untrimmedSkillsAvailable = [];
this.#thrallsUnlocked = [];
this.#thrallDamage = {};
this.#thrallsObtainedItem = [];
this.#thrallsDeliveredItem = [];
this.refill();
this.refill();
} else {
const file = mustBeSaveFileV1(args);
this.#name = file.name;
this.#thrallTemplate = file.thrallTemplateId;
this.#nImprovements = file.nImprovements;
this.#stats = {
AGI: file.stats.agi,
INT: file.stats.int,
CHA: file.stats.cha,
PSI: file.stats.psi,
};
this.#talents = {
AGI: file.talents.agi,
INT: file.talents.int,
CHA: file.talents.cha,
PSI: file.talents.psi,
};
(this.#isInPenance = file.isInPenance),
(this.#wish = file.wishId >= 0 ? { id: file.wishId } : null);
this.#exp = file.exp;
this.#blood = file.blood;
this.#itemsPurloined = file.itemsPurloined;
this.#skillsLearned = file.skillsLearned;
this.#untrimmedSkillsAvailable = file.untrimmedSkillsAvailableIds.map(
(id) => {
return { id: id };
},
);
this.#thrallsUnlocked = file.thrallsUnlocked;
this.#thrallDamage = {};
for (let i = 0; i < file.thrallDamage.length; ++i) {
this.#thrallDamage[i] = file.thrallDamage[i];
}
this.#thrallsObtainedItem = file.thrallsObtainedItem;
this.#thrallsDeliveredItem = file.thrallsDeliveredItem;
}
}
applyEndOfTurn() {
@ -206,7 +252,11 @@ export class PlayerProgress {
return skillsAvailable.slice(0, 6);
}
getLearnedSkills() {
getUntrimmedAvailableSkillIds(): number[] {
return this.#untrimmedSkillsAvailable.map((s) => s.id);
}
getLearnedSkills(): Skill[] {
let learnedSkills = [];
for (let s of this.#skillsLearned.values()) {
learnedSkills.push({ id: s });
@ -214,6 +264,10 @@ export class PlayerProgress {
return learnedSkills;
}
getRawLearnedSkills(): number[] {
return [...this.#skillsLearned];
}
getStats() {
return { ...this.#stats };
}
@ -233,6 +287,10 @@ export class PlayerProgress {
return this.#thrallsUnlocked.indexOf(thrall.id) != -1;
}
getUnlockedThrallIds(): number[] {
return [...this.#thrallsUnlocked];
}
damageThrall(thrall: Thrall, amount: number) {
if (amount <= 0.0) {
throw new Error(`damage must be some positive amount, not ${amount}`);
@ -246,6 +304,10 @@ export class PlayerProgress {
(this.#thrallDamage[thrall.id] ?? 0.0) + amount;
}
getThrallDamage(thrall: Thrall): number {
return this.#thrallDamage[thrall.id] ?? 0.0;
}
getThrallLifeStage(thrall: Thrall): LifeStage {
let damage = this.#thrallDamage[thrall.id] ?? 0;
if (damage < 0.5) {
@ -270,6 +332,10 @@ export class PlayerProgress {
this.#thrallsObtainedItem.push(thrall.id);
}
getThrallObtainedItemIds(): number[] {
return [...this.#thrallsObtainedItem];
}
deliverThrallItem(thrall: Thrall) {
if (this.#thrallsDeliveredItem.indexOf(thrall.id) != -1) {
return;
@ -277,6 +343,10 @@ export class PlayerProgress {
this.#thrallsDeliveredItem.push(thrall.id);
}
getThrallDeliveredItemIds(): number[] {
return [...this.#thrallsDeliveredItem];
}
getThrallItemStage(thrall: Thrall): ItemStage {
if (this.#thrallsDeliveredItem.indexOf(thrall.id) != -1) {
return ItemStage.Delivered;
@ -308,7 +378,11 @@ export function initPlayerProgress(
asSuccessor: SuccessorOption,
withWish: Wish | null,
) {
active = new PlayerProgress(asSuccessor, withWish);
active = new PlayerProgress({ asSuccessor: asSuccessor, withWish: withWish });
}
export function rehydratePlayerProgress(savefile: SaveFileV1) {
active = new PlayerProgress(savefile);
}
export function getPlayerProgress(): PlayerProgress {