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>
246 lines
5.0 KiB
TypeScript
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();
|
|
}
|
|
}
|