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:
1 parent
ea351e44b3
commit
7652f7d010
2 files changed
+28
-2
No files matched your search
@@ -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");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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));
|
||||||
|
|||||||
Reference in new issue
Block a user