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 { getPlayerProgress } from "./playerprogress.ts";
|
||||||
import { getStateManager } from "./statemanager.ts";
|
import { getStateManager } from "./statemanager.ts";
|
||||||
import { getCheckModal } from "./checkmodal.ts";
|
import { getCheckModal } from "./checkmodal.ts";
|
||||||
import { saveGame } from "./save.ts";
|
|
||||||
|
|
||||||
type Button = {
|
type Button = {
|
||||||
label: string;
|
label: string;
|
||||||
@ -113,7 +112,6 @@ export class Hotbar {
|
|||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
getStateManager().advance();
|
getStateManager().advance();
|
||||||
saveGame();
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -2,5 +2,5 @@ import { hostGame } from "./engine/internal/host.ts";
|
|||||||
import { game } from "./game.ts";
|
import { game } from "./game.ts";
|
||||||
import { getStateManager } from "./statemanager.ts";
|
import { getStateManager } from "./statemanager.ts";
|
||||||
|
|
||||||
getStateManager().startFirstGame();
|
getStateManager().startOrLoadFirstGame();
|
||||||
hostGame(game);
|
hostGame(game);
|
||||||
|
@ -27,7 +27,7 @@ export class PlayerProgress {
|
|||||||
#thrallsDeliveredItem: number[];
|
#thrallsDeliveredItem: number[];
|
||||||
|
|
||||||
constructor(args: NewRoundConfig | SaveFileV1) {
|
constructor(args: NewRoundConfig | SaveFileV1) {
|
||||||
if ("asSuccesor" in args) {
|
if ("asSuccessor" in args) {
|
||||||
//asSuccessor: SuccessorOption, withWish: Wish | null) {
|
//asSuccessor: SuccessorOption, withWish: Wish | null) {
|
||||||
const config = args as NewRoundConfig;
|
const config = args as NewRoundConfig;
|
||||||
const asSuccessor = config.asSuccessor;
|
const asSuccessor = config.asSuccessor;
|
||||||
@ -381,6 +381,10 @@ export function initPlayerProgress(
|
|||||||
active = new PlayerProgress({ asSuccessor: asSuccessor, withWish: withWish });
|
active = new PlayerProgress({ asSuccessor: asSuccessor, withWish: withWish });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function rehydratePlayerProgress(savefile: SaveFileV1) {
|
||||||
|
active = new PlayerProgress(savefile);
|
||||||
|
}
|
||||||
|
|
||||||
export function getPlayerProgress(): PlayerProgress {
|
export function getPlayerProgress(): PlayerProgress {
|
||||||
if (active == null) {
|
if (active == null) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
|
13
src/save.ts
13
src/save.ts
@ -2,6 +2,7 @@ import { getPlayerProgress } from "./playerprogress";
|
|||||||
import { getStateManager } from "./statemanager";
|
import { getStateManager } from "./statemanager";
|
||||||
import { getThralls } from "./thralls";
|
import { getThralls } from "./thralls";
|
||||||
import { SaveFileV1, mustBeSaveFileV1 } from "./saveformat";
|
import { SaveFileV1, mustBeSaveFileV1 } from "./saveformat";
|
||||||
|
import { getHuntMode } from "./huntmode.ts";
|
||||||
|
|
||||||
export interface SaveFile {
|
export interface SaveFile {
|
||||||
version: string;
|
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
|
/// 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
|
/// are present, `file` and `error` will both be absent. If an invalid save
|
||||||
/// file is discovered, `error` will refer to the issue(s) detected.
|
/// 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 from1 = readFromSlot("FLEDGLING_SLOT_1");
|
||||||
const from2 = readFromSlot("FLEDGLING_SLOT_2");
|
const from2 = readFromSlot("FLEDGLING_SLOT_2");
|
||||||
if (from1.file && from2.file) {
|
if (from1.file && from2.file) {
|
||||||
@ -96,6 +97,7 @@ export function saveGame() {
|
|||||||
function extractCurrentState(): SaveFileV1 {
|
function extractCurrentState(): SaveFileV1 {
|
||||||
const progress = getPlayerProgress();
|
const progress = getPlayerProgress();
|
||||||
const stateManager = getStateManager();
|
const stateManager = getStateManager();
|
||||||
|
const huntMode = getHuntMode();
|
||||||
var thrallDamage: number[] = [];
|
var thrallDamage: number[] = [];
|
||||||
const nThralls = getThralls().length;
|
const nThralls = getThralls().length;
|
||||||
for (let i = 0; i < nThralls; ++i) {
|
for (let i = 0; i < nThralls; ++i) {
|
||||||
@ -115,10 +117,10 @@ function extractCurrentState(): SaveFileV1 {
|
|||||||
psi: progress.getStat("PSI"),
|
psi: progress.getStat("PSI"),
|
||||||
},
|
},
|
||||||
talents: {
|
talents: {
|
||||||
agi: progress.getStat("AGI"),
|
agi: progress.getTalent("AGI"),
|
||||||
int: progress.getStat("INT"),
|
int: progress.getTalent("INT"),
|
||||||
cha: progress.getStat("CHA"),
|
cha: progress.getTalent("CHA"),
|
||||||
psi: progress.getStat("PSI"),
|
psi: progress.getTalent("PSI"),
|
||||||
},
|
},
|
||||||
isInPenance: progress.isInPenance,
|
isInPenance: progress.isInPenance,
|
||||||
wishId: progress.getWish()?.id ?? -1,
|
wishId: progress.getWish()?.id ?? -1,
|
||||||
@ -131,6 +133,7 @@ function extractCurrentState(): SaveFileV1 {
|
|||||||
thrallDamage: thrallDamage,
|
thrallDamage: thrallDamage,
|
||||||
thrallsObtainedItem: progress.getThrallObtainedItemIds(),
|
thrallsObtainedItem: progress.getThrallObtainedItemIds(),
|
||||||
thrallsDeliveredItem: progress.getThrallDeliveredItemIds(),
|
thrallsDeliveredItem: progress.getThrallDeliveredItemIds(),
|
||||||
|
depth: huntMode.getDepth(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,6 +27,7 @@ export interface SaveFileV1 {
|
|||||||
thrallDamage: number[]; // 0: thrall is absent or undamaged
|
thrallDamage: number[]; // 0: thrall is absent or undamaged
|
||||||
thrallsObtainedItem: number[];
|
thrallsObtainedItem: number[];
|
||||||
thrallsDeliveredItem: number[];
|
thrallsDeliveredItem: number[];
|
||||||
|
depth: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Checks whether obj is a valid save file, as far as we can tell, and returns
|
/// 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"),
|
thrallDamage: mustGetNumberArray(obj, "thrallDamage"),
|
||||||
thrallsObtainedItem: mustGetNumberArray(obj, "thrallsObtainedItem"),
|
thrallsObtainedItem: mustGetNumberArray(obj, "thrallsObtainedItem"),
|
||||||
thrallsDeliveredItem: mustGetNumberArray(obj, "thrallsDeliveredItem"),
|
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 { getHuntMode, HuntMode, initHuntMode } from "./huntmode.ts";
|
||||||
import { getVNModal } from "./vnmodal.ts";
|
import { getVNModal } from "./vnmodal.ts";
|
||||||
import { getScorer } from "./scorer.ts";
|
import { getScorer } from "./scorer.ts";
|
||||||
@ -11,6 +15,7 @@ import { generateName } from "./namegen.ts";
|
|||||||
import { photogenicThralls } from "./thralls.ts";
|
import { photogenicThralls } from "./thralls.ts";
|
||||||
import { choose } from "./utils.ts";
|
import { choose } from "./utils.ts";
|
||||||
import { SaveFileV1 } from "./saveformat.ts";
|
import { SaveFileV1 } from "./saveformat.ts";
|
||||||
|
import { readBestSave, saveGame } from "./save.ts";
|
||||||
|
|
||||||
const N_TURNS: number = 9;
|
const N_TURNS: number = 9;
|
||||||
|
|
||||||
@ -32,6 +37,23 @@ export class StateManager {
|
|||||||
return this.#revision;
|
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() {
|
startFirstGame() {
|
||||||
getVNModal().play([
|
getVNModal().play([
|
||||||
...openingScene,
|
...openingScene,
|
||||||
@ -65,7 +87,17 @@ export class StateManager {
|
|||||||
sndSleep.play({ bgm: true });
|
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() {
|
advance() {
|
||||||
|
saveGame();
|
||||||
if (this.#turn + 1 <= N_TURNS) {
|
if (this.#turn + 1 <= N_TURNS) {
|
||||||
this.#turn += 1;
|
this.#turn += 1;
|
||||||
getPlayerProgress().applyEndOfTurn();
|
getPlayerProgress().applyEndOfTurn();
|
||||||
|
@ -1,8 +1,13 @@
|
|||||||
import { D, I } from "./engine/public.ts";
|
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 { withCamera } from "./layout.ts";
|
||||||
import { VNScene, VNSceneMessage, VNScenePart } from "./vnscene.ts";
|
import { VNScene, VNSceneMessage, VNScenePart } from "./vnscene.ts";
|
||||||
import { C } from "./colors.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 WIDTH = 384;
|
||||||
const HEIGHT = 384;
|
const HEIGHT = 384;
|
||||||
@ -90,6 +95,8 @@ function createCathexis(part: VNScenePart): SceneCathexis {
|
|||||||
case "callback":
|
case "callback":
|
||||||
part?.callback();
|
part?.callback();
|
||||||
return new SkipCathexis();
|
return new SkipCathexis();
|
||||||
|
case "saveGameScreen":
|
||||||
|
return new SaveGameCathexis(part.file, part.error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,7 +119,6 @@ class SceneMessageCathexis {
|
|||||||
let firstFrame = !this.#gotOneFrame;
|
let firstFrame = !this.#gotOneFrame;
|
||||||
this.#gotOneFrame = true;
|
this.#gotOneFrame = true;
|
||||||
|
|
||||||
// TODO: SFX
|
|
||||||
if (!firstFrame && I.isMouseClicked("leftMouse")) {
|
if (!firstFrame && I.isMouseClicked("leftMouse")) {
|
||||||
this.#done = true;
|
this.#done = true;
|
||||||
}
|
}
|
||||||
@ -152,3 +158,87 @@ class SkipCathexis {
|
|||||||
throw new Error("shouldn't ever be drawn");
|
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 { Sound } from "./sound.ts";
|
||||||
|
import { SaveFileV1 } from "./saveformat.ts";
|
||||||
|
|
||||||
export type VNSceneMessage = {
|
export type VNSceneMessage = {
|
||||||
type: "message";
|
type: "message";
|
||||||
@ -11,9 +12,18 @@ export type VNSceneCallback = {
|
|||||||
callback: () => void;
|
callback: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type VNSceneSaveGameScreen = {
|
||||||
|
type: "saveGameScreen";
|
||||||
|
file: SaveFileV1 | null;
|
||||||
|
error: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
export type VNSceneBasisPart = string | VNSceneMessage | VNSceneCallback;
|
export type VNSceneBasisPart = string | VNSceneMessage | VNSceneCallback;
|
||||||
export type VNSceneBasis = VNSceneBasisPart[];
|
export type VNSceneBasis = VNSceneBasisPart[];
|
||||||
export type VNScenePart = VNSceneMessage | VNSceneCallback;
|
export type VNScenePart =
|
||||||
|
| VNSceneMessage
|
||||||
|
| VNSceneCallback
|
||||||
|
| VNSceneSaveGameScreen;
|
||||||
export type VNScene = VNScenePart[];
|
export type VNScene = VNScenePart[];
|
||||||
|
|
||||||
export function compile(basis: VNSceneBasis): VNScene {
|
export function compile(basis: VNSceneBasis): VNScene {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user