feat: Rollback steps back through the last 50 interactions (wheel up, PageUp, Rollback action)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
mrleo1nidandClaude Sonnet 5.5 committed 2026-10-27 01:48:56 +03:00
1 parent ea351e44b3
commit 7652f7d010
2 files changed
+28 -2

No files matched your search

+25 -1
View File
@@ -48,6 +48,9 @@ function fontGroup() {
return group; return group;
} }
/** How many interactions Rollback can step back through. */
const ROLLBACK_LIMIT = 50;
export class Engine { export class Engine {
private liveImages?: LiveImages; private liveImages?: LiveImages;
private imageScales = new Map<string, number>(); private imageScales = new Map<string, number>();
@@ -83,6 +86,8 @@ export class Engine {
private windowShown = true; private windowShown = true;
private lastSay: { name: string; text: string; color?: string; char?: CharacterDef } | null = null; private lastSay: { name: string; text: string; color?: string; char?: CharacterDef } | null = null;
private history: { name: string; text: string }[] = []; private history: { name: string; text: string }[] = [];
/** One state per interaction, newest last: what Rollback steps back through. */
private rollbackLog: { snap: SaveData; history: { name: string; text: string }[] }[] = [];
private nvlLines: HTMLElement[] = []; private nvlLines: HTMLElement[] = [];
private interactions = 0; private interactions = 0;
@@ -1061,6 +1066,7 @@ export class Engine {
quit: () => this.onExit(), quit: () => this.onExit(),
mainMenu: () => this.restart(() => { this.stack = []; this.cur = { label: "", path: [0] }; }), mainMenu: () => this.restart(() => { this.stack = []; this.cur = { label: "", path: [0] }; }),
openMenu: () => this.openGameMenu(), openMenu: () => this.openGameMenu(),
rollback: () => this.rollback(),
openUrl: (url) => { openUrl: (url) => {
if (/^https?:\/\//i.test(url)) window.open(url, "_blank", "noopener,noreferrer"); if (/^https?:\/\//i.test(url)) window.open(url, "_blank", "noopener,noreferrer");
}, },
@@ -1531,11 +1537,24 @@ export class Engine {
private interactionStart() { private interactionStart() {
this.renderer.commit(); this.renderer.commit();
this.rollbackLog.push({ snap: this.snapshotState(), history: this.history.slice() });
if (this.rollbackLog.length > ROLLBACK_LIMIT) this.rollbackLog.shift();
this.interactions++; this.interactions++;
if (this.interactions % 15 === 0) void this.save("auto", true); if (this.interactions % 15 === 0) void this.save("auto", true);
this.preloadAhead(); this.preloadAhead();
} }
/** Back to the previous interaction: the one being shown is dropped, the one before it is shown again. */
private rollback() {
if (!this.running || this.rollbackLog.length < 2) return;
this.rollbackLog.pop();
const entry = this.rollbackLog.pop()!;
this.restart(() => {
this.applySave(entry.snap);
this.history = entry.history;
});
}
private preloadAhead() { private preloadAhead() {
const block = this.labels.has(this.cur.label) ? this.blockFor(this.cur) : []; const block = this.labels.has(this.cur.label) ? this.blockFor(this.cur) : [];
const start = this.cur.path[this.cur.path.length - 1]; const start = this.cur.path[this.cur.path.length - 1];
@@ -1878,6 +1897,7 @@ export class Engine {
this.newStore(); this.newStore();
this.stack = []; this.stack = [];
this.history = []; this.history = [];
this.rollbackLog = [];
this.lastSay = null; this.lastSay = null;
this.textbox.classList.add("pg-hidden"); this.textbox.classList.add("pg-hidden");
this.cur = at ?? { label: this.game.entry, path: [0] }; this.cur = at ?? { label: this.game.entry, path: [0] };
@@ -1973,6 +1993,7 @@ export class Engine {
} }
if (!data.revision && !(await this.confirmAsk("Старое сохранение не содержит ревизию игры. После изменения скрипта его позиции могут быть неверны. Попробовать загрузить?", "Загрузить"))) return; if (!data.revision && !(await this.confirmAsk("Старое сохранение не содержит ревизию игры. После изменения скрипта его позиции могут быть неверны. Попробовать загрузить?", "Загрузить"))) return;
this.replayStash = null; this.replayStash = null;
this.rollbackLog = [];
this.restart(() => this.applySave(data)); this.restart(() => this.applySave(data));
} }
@@ -2123,7 +2144,7 @@ export class Engine {
this.advanceInput(); this.advanceInput();
}); });
stage.addEventListener("wheel", (ev) => { stage.addEventListener("wheel", (ev) => {
if (ev.deltaY < 0 && this.screen.classList.contains("pg-hidden") && this.running) this.openHistory(); if (ev.deltaY < 0 && this.screen.classList.contains("pg-hidden") && this.running) this.rollback();
}, { passive: true }); }, { passive: true });
} }
@@ -2203,6 +2224,9 @@ export class Engine {
this.setSkip(!this.skip); this.setSkip(!this.skip);
} else if (ev.key === "a" || ev.key === "ф") { } else if (ev.key === "a" || ev.key === "ф") {
this.setAuto(!this.auto); this.setAuto(!this.auto);
} else if (ev.key === "PageUp") {
ev.preventDefault();
this.rollback();
} else if (ev.key === "h" || ev.key === "р") { } else if (ev.key === "h" || ev.key === "р") {
this.textbox.classList.toggle("pg-ui-hidden"); this.textbox.classList.toggle("pg-ui-hidden");
} }
+3 -1
View File
@@ -43,6 +43,8 @@ export interface ScreenHost {
quit(): void; quit(): void;
mainMenu(): void; mainMenu(): void;
openMenu(): void; openMenu(): void;
/** The Rollback action: back to the previous interaction. */
rollback?(): void;
openUrl(url: string): void; openUrl(url: string): void;
quickSave(): void; quickSave(): void;
quickLoad(): void; quickLoad(): void;
@@ -1797,7 +1799,7 @@ export class ScreenManager {
case "UploadSync": case "UploadSync":
case "DownloadSync": this.host.notify("Синхронизация недоступна в браузере"); return; case "DownloadSync": this.host.notify("Синхронизация недоступна в браузере"); return;
case "Skip": this.host.setSkipping?.(true); return; case "Skip": this.host.setSkipping?.(true); return;
case "Rollback": return; // there is no rollback in the browser player case "Rollback": this.host.rollback?.(); return;
case "ToggleLocalVariable": { case "ToggleLocalVariable": {
const now = owner?.vars.get(a); const now = owner?.vars.get(a);
owner?.vars.set(a, !truthy(now)); owner?.vars.set(a, !truthy(now));