Integrate save system
This commit is contained in:
parent
32b6bf0b53
commit
2c121f0c8a
@ -6,7 +6,6 @@ import { addButton } from "./button.ts";
|
||||
import { getPlayerProgress } from "./playerprogress.ts";
|
||||
import { getStateManager } from "./statemanager.ts";
|
||||
import { getCheckModal } from "./checkmodal.ts";
|
||||
import { saveGame } from "./save.ts";
|
||||
|
||||
type Button = {
|
||||
label: string;
|
||||
@ -113,7 +112,6 @@ export class Hotbar {
|
||||
},
|
||||
() => {
|
||||
getStateManager().advance();
|
||||
saveGame();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
@ -2,5 +2,5 @@ import { hostGame } from "./engine/internal/host.ts";
|
||||
import { game } from "./game.ts";
|
||||
import { getStateManager } from "./statemanager.ts";
|
||||
|
||||
getStateManager().startFirstGame();
|
||||
getStateManager().startOrLoadFirstGame();
|
||||
hostGame(game);
|
||||
|
@ -27,7 +27,7 @@ export class PlayerProgress {
|
||||
#thrallsDeliveredItem: number[];
|
||||
|
||||
constructor(args: NewRoundConfig | SaveFileV1) {
|
||||
if ("asSuccesor" in args) {
|
||||
if ("asSuccessor" in args) {
|
||||
//asSuccessor: SuccessorOption, withWish: Wish | null) {
|
||||
const config = args as NewRoundConfig;
|
||||
const asSuccessor = config.asSuccessor;
|
||||
@ -381,6 +381,10 @@ export function initPlayerProgress(
|
||||
active = new PlayerProgress({ asSuccessor: asSuccessor, withWish: withWish });
|
||||
}
|
||||
|
||||
export function rehydratePlayerProgress(savefile: SaveFileV1) {
|
||||
active = new PlayerProgress(savefile);
|
||||
}
|
||||
|
||||
export function getPlayerProgress(): PlayerProgress {
|
||||
if (active == null) {
|
||||
throw new Error(
|
||||
|
13
src/save.ts
13
src/save.ts
@ -2,6 +2,7 @@ import { getPlayerProgress } from "./playerprogress";
|
||||
import { getStateManager } from "./statemanager";
|
||||
import { getThralls } from "./thralls";
|
||||
import { SaveFileV1, mustBeSaveFileV1 } from "./saveformat";
|
||||
import { getHuntMode } from "./huntmode.ts";
|
||||
|
||||
export interface SaveFile {
|
||||
version: string;
|
||||
@ -56,7 +57,7 @@ function readFromSlot(slot: SaveSlot): SaveFileV1LoadResult {
|
||||
/// Reads the more recent valid save file, if either is valid. If no save files
|
||||
/// are present, `file` and `error` will both be absent. If an invalid save
|
||||
/// file is discovered, `error` will refer to the issue(s) detected.
|
||||
function readBestSave(): SaveFileV1LoadResult {
|
||||
export function readBestSave(): SaveFileV1LoadResult {
|
||||
const from1 = readFromSlot("FLEDGLING_SLOT_1");
|
||||
const from2 = readFromSlot("FLEDGLING_SLOT_2");
|
||||
if (from1.file && from2.file) {
|
||||
@ -96,6 +97,7 @@ export function saveGame() {
|
||||
function extractCurrentState(): SaveFileV1 {
|
||||
const progress = getPlayerProgress();
|
||||
const stateManager = getStateManager();
|
||||
const huntMode = getHuntMode();
|
||||
var thrallDamage: number[] = [];
|
||||
const nThralls = getThralls().length;
|
||||
for (let i = 0; i < nThralls; ++i) {
|
||||
@ -115,10 +117,10 @@ function extractCurrentState(): SaveFileV1 {
|
||||
psi: progress.getStat("PSI"),
|
||||
},
|
||||
talents: {
|
||||
agi: progress.getStat("AGI"),
|
||||
int: progress.getStat("INT"),
|
||||
cha: progress.getStat("CHA"),
|
||||
psi: progress.getStat("PSI"),
|
||||
agi: progress.getTalent("AGI"),
|
||||
int: progress.getTalent("INT"),
|
||||
cha: progress.getTalent("CHA"),
|
||||
psi: progress.getTalent("PSI"),
|
||||
},
|
||||
isInPenance: progress.isInPenance,
|
||||
wishId: progress.getWish()?.id ?? -1,
|
||||
@ -131,6 +133,7 @@ function extractCurrentState(): SaveFileV1 {
|
||||
thrallDamage: thrallDamage,
|
||||
thrallsObtainedItem: progress.getThrallObtainedItemIds(),
|
||||
thrallsDeliveredItem: progress.getThrallDeliveredItemIds(),
|
||||
depth: huntMode.getDepth(),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -27,6 +27,7 @@ export interface SaveFileV1 {
|
||||
thrallDamage: number[]; // 0: thrall is absent or undamaged
|
||||
thrallsObtainedItem: number[];
|
||||
thrallsDeliveredItem: number[];
|
||||
depth: number;
|
||||
}
|
||||
|
||||
/// Checks whether obj is a valid save file, as far as we can tell, and returns
|
||||
@ -69,6 +70,7 @@ export function mustBeSaveFileV1(obj: unknown): SaveFileV1 {
|
||||
thrallDamage: mustGetNumberArray(obj, "thrallDamage"),
|
||||
thrallsObtainedItem: mustGetNumberArray(obj, "thrallsObtainedItem"),
|
||||
thrallsDeliveredItem: mustGetNumberArray(obj, "thrallsDeliveredItem"),
|
||||
depth: mustGetNumber(obj, "depth"),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,8 @@
|
||||
import { getPlayerProgress, initPlayerProgress } from "./playerprogress.ts";
|
||||
import {
|
||||
getPlayerProgress,
|
||||
initPlayerProgress,
|
||||
rehydratePlayerProgress,
|
||||
} from "./playerprogress.ts";
|
||||
import { getHuntMode, HuntMode, initHuntMode } from "./huntmode.ts";
|
||||
import { getVNModal } from "./vnmodal.ts";
|
||||
import { getScorer } from "./scorer.ts";
|
||||
@ -11,6 +15,7 @@ import { generateName } from "./namegen.ts";
|
||||
import { photogenicThralls } from "./thralls.ts";
|
||||
import { choose } from "./utils.ts";
|
||||
import { SaveFileV1 } from "./saveformat.ts";
|
||||
import { readBestSave, saveGame } from "./save.ts";
|
||||
|
||||
const N_TURNS: number = 9;
|
||||
|
||||
@ -32,6 +37,23 @@ export class StateManager {
|
||||
return this.#revision;
|
||||
}
|
||||
|
||||
startOrLoadFirstGame() {
|
||||
let save = readBestSave();
|
||||
if (save.file != null || save.error != null) {
|
||||
const file = save.file;
|
||||
const error = save.error;
|
||||
getVNModal().play([
|
||||
{
|
||||
type: "saveGameScreen",
|
||||
file: file,
|
||||
error: error,
|
||||
},
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
this.startFirstGame();
|
||||
}
|
||||
startFirstGame() {
|
||||
getVNModal().play([
|
||||
...openingScene,
|
||||
@ -65,7 +87,17 @@ export class StateManager {
|
||||
sndSleep.play({ bgm: true });
|
||||
}
|
||||
|
||||
resumeGame(saveFile: SaveFileV1) {
|
||||
// hack: prepare depth which advance() uses
|
||||
this.#turn = saveFile.turn;
|
||||
this.#revision = saveFile.revision;
|
||||
rehydratePlayerProgress(saveFile);
|
||||
initHuntMode(new HuntMode(saveFile.depth, generateManor()));
|
||||
this.advance();
|
||||
}
|
||||
|
||||
advance() {
|
||||
saveGame();
|
||||
if (this.#turn + 1 <= N_TURNS) {
|
||||
this.#turn += 1;
|
||||
getPlayerProgress().applyEndOfTurn();
|
||||
|
@ -1,8 +1,13 @@
|
||||
import { D, I } from "./engine/public.ts";
|
||||
import { AlignX, AlignY, Point } from "./engine/datatypes.ts";
|
||||
import { AlignX, AlignY, Point, Rect, Size } from "./engine/datatypes.ts";
|
||||
import { withCamera } from "./layout.ts";
|
||||
import { VNScene, VNSceneMessage, VNScenePart } from "./vnscene.ts";
|
||||
import { C } from "./colors.ts";
|
||||
import { DrawPile } from "./drawpile.ts";
|
||||
import { wipeSaves } from "./save.ts";
|
||||
import { SaveFileV1 } from "./saveformat.ts";
|
||||
import { addButton } from "./button.ts";
|
||||
import { getStateManager } from "./statemanager.ts";
|
||||
|
||||
const WIDTH = 384;
|
||||
const HEIGHT = 384;
|
||||
@ -90,6 +95,8 @@ function createCathexis(part: VNScenePart): SceneCathexis {
|
||||
case "callback":
|
||||
part?.callback();
|
||||
return new SkipCathexis();
|
||||
case "saveGameScreen":
|
||||
return new SaveGameCathexis(part.file, part.error);
|
||||
}
|
||||
}
|
||||
|
||||
@ -112,7 +119,6 @@ class SceneMessageCathexis {
|
||||
let firstFrame = !this.#gotOneFrame;
|
||||
this.#gotOneFrame = true;
|
||||
|
||||
// TODO: SFX
|
||||
if (!firstFrame && I.isMouseClicked("leftMouse")) {
|
||||
this.#done = true;
|
||||
}
|
||||
@ -152,3 +158,87 @@ class SkipCathexis {
|
||||
throw new Error("shouldn't ever be drawn");
|
||||
}
|
||||
}
|
||||
|
||||
class SaveGameCathexis {
|
||||
#drawpile: DrawPile;
|
||||
#file: SaveFileV1 | null;
|
||||
#error: string | null;
|
||||
#done: boolean;
|
||||
|
||||
constructor(file: SaveFileV1 | null, error: string | null) {
|
||||
this.#drawpile = new DrawPile();
|
||||
this.#file = file;
|
||||
this.#error = error;
|
||||
if (this.#error != null && this.#file != null) {
|
||||
throw new Error("can't have a savefile _and_ an error");
|
||||
}
|
||||
this.#done = false;
|
||||
}
|
||||
|
||||
isDone() {
|
||||
return this.#done;
|
||||
}
|
||||
|
||||
update() {
|
||||
let name = this.#file?.name;
|
||||
let turn = this.#file?.turn ?? 0;
|
||||
let turnText = turn < 9 ? `${name}, Turn ${turn + 1}` : "Sentence of Fate";
|
||||
this.#drawpile.clear();
|
||||
this.#drawpile.add(0, () => {
|
||||
D.drawText(
|
||||
this.#error
|
||||
? `Your save file was invalid and could not be loaded:
|
||||
|
||||
${this.#error}`
|
||||
: "Resume from save?",
|
||||
new Point(WIDTH / 2, HEIGHT / 2),
|
||||
C.FG_BOLD,
|
||||
{
|
||||
alignX: AlignX.Center,
|
||||
alignY: AlignY.Middle,
|
||||
forceWidth: WIDTH,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
addButton(
|
||||
this.#drawpile,
|
||||
"Clear Save",
|
||||
new Rect(new Point(0, HEIGHT - 32), new Size(128, 32)),
|
||||
this.#file != null,
|
||||
() => {
|
||||
wipeSaves();
|
||||
this.#file = null;
|
||||
},
|
||||
);
|
||||
if (this.#file) {
|
||||
let file = this.#file;
|
||||
addButton(
|
||||
this.#drawpile,
|
||||
`Continue (${turnText})`,
|
||||
new Rect(new Point(128, HEIGHT - 32), new Size(WIDTH - 128, 32)),
|
||||
true,
|
||||
() => {
|
||||
getStateManager().resumeGame(file);
|
||||
this.#done = true;
|
||||
},
|
||||
);
|
||||
} else {
|
||||
addButton(
|
||||
this.#drawpile,
|
||||
`Start New Game`,
|
||||
new Rect(new Point(128, HEIGHT - 32), new Size(WIDTH - 128, 32)),
|
||||
true,
|
||||
() => {
|
||||
getStateManager().startFirstGame();
|
||||
this.#done = true;
|
||||
},
|
||||
);
|
||||
}
|
||||
this.#drawpile.executeOnClick();
|
||||
}
|
||||
|
||||
draw() {
|
||||
this.#drawpile.draw();
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { Sound } from "./sound.ts";
|
||||
import { SaveFileV1 } from "./saveformat.ts";
|
||||
|
||||
export type VNSceneMessage = {
|
||||
type: "message";
|
||||
@ -11,9 +12,18 @@ export type VNSceneCallback = {
|
||||
callback: () => void;
|
||||
};
|
||||
|
||||
export type VNSceneSaveGameScreen = {
|
||||
type: "saveGameScreen";
|
||||
file: SaveFileV1 | null;
|
||||
error: string | null;
|
||||
};
|
||||
|
||||
export type VNSceneBasisPart = string | VNSceneMessage | VNSceneCallback;
|
||||
export type VNSceneBasis = VNSceneBasisPart[];
|
||||
export type VNScenePart = VNSceneMessage | VNSceneCallback;
|
||||
export type VNScenePart =
|
||||
| VNSceneMessage
|
||||
| VNSceneCallback
|
||||
| VNSceneSaveGameScreen;
|
||||
export type VNScene = VNScenePart[];
|
||||
|
||||
export function compile(basis: VNSceneBasis): VNScene {
|
||||
|
Loading…
x
Reference in New Issue
Block a user