fledgling/src/vnmodal.ts
Nyeogmi 19b097a0bd 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>
2025-02-25 04:14:02 +00:00

246 lines
5.0 KiB
TypeScript

import { D, I } from "./engine/public.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;
export class VNModal {
#scene: VNScene | null;
#nextIndex = 0;
#cathexis: SceneCathexis | null;
constructor() {
this.#scene = null;
this.#nextIndex = 0;
this.#cathexis = null;
}
get blocksHud(): boolean {
return true;
}
get isShown(): boolean {
return this.#scene != null;
}
play(scene: VNScene) {
this.#scene = scene;
this.#nextIndex = 0;
this.#cathexis = null;
this.#fixCathexis();
}
#fixCathexis() {
while (true) {
if (this.#cathexis?.isDone()) {
this.#cathexis = null;
}
if (this.#cathexis != null) {
return;
}
if (this.#scene == null) {
return;
}
if (this.#cathexis == null) {
let ix = this.#nextIndex;
if (ix < this.#scene?.length) {
this.#cathexis = createCathexis(this.#scene[ix]);
this.#nextIndex += 1;
} else {
this.#scene = null;
}
}
}
}
update() {
this.#fixCathexis();
withCamera("FullscreenPopover", () => this.#update());
}
draw() {
withCamera("FullscreenPopover", () => this.#draw());
}
#update() {
this.#cathexis?.update();
}
#draw() {
this.#cathexis?.draw();
}
}
interface SceneCathexis {
isDone(): boolean;
update(): void;
draw(): void;
}
function createCathexis(part: VNScenePart): SceneCathexis {
switch (part.type) {
case "message":
part?.sfx?.play({ volume: 0.5 });
return new SceneMessageCathexis(part);
case "callback":
part?.callback();
return new SkipCathexis();
case "saveGameScreen":
return new SaveGameCathexis(part.file, part.error);
}
}
class SceneMessageCathexis {
#message: VNSceneMessage;
#done: boolean;
#gotOneFrame: boolean;
constructor(message: VNSceneMessage) {
this.#message = message;
this.#done = false;
this.#gotOneFrame = false;
}
isDone() {
return this.#done;
}
update() {
let firstFrame = !this.#gotOneFrame;
this.#gotOneFrame = true;
if (!firstFrame && I.isMouseClicked("leftMouse")) {
this.#done = true;
}
}
draw() {
D.drawText(
this.#message.text,
new Point(WIDTH / 2, HEIGHT / 2),
C.FG_BOLD,
{
alignX: AlignX.Center,
alignY: AlignY.Middle,
forceWidth: WIDTH,
},
);
}
}
let active: VNModal = new VNModal();
export function getVNModal() {
return active;
}
class SkipCathexis {
constructor() {}
isDone() {
return true;
}
update() {
throw new Error("shouldn't be updated");
}
draw() {
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;
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 && this.#file
? `A save was invalid. Continue from an alternate save?
${this.#error}`
: this.#error
? `Your save was invalid:
${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();
}
}