feat: add local image enhancement and lossless mod assets
CI / frontend (push) Failing after 26s
CI / backend (push) Failing after 1m37s

This commit is contained in:
mrleo1nid committed 2026-10-25 21:41:45 +03:00
1 parent cc71050362
commit f7f7f5b1b3
29 files changed
+1495 -10

No files matched your search

+29 -5
View File
@@ -2,6 +2,7 @@
import { AudioSystem } from "./audio";
import { VolumeStore } from "./volume-store";
import { LiveImages } from "../imager/live";
import { BlockingCall, JumpSignal, PyDict, Python, decodeValue, encodeValue, pyStr, truthy, userBuiltins, type Scope } from "./python";
import { Renderer, type ResolvedImage } from "./renderer";
import { fastPoint, interpolate, parseText, plainText, renderText, waitPoints } from "./text";
@@ -24,6 +25,8 @@ interface LabelRef {
}
export class Engine {
private liveImages?: LiveImages;
private imageScales = new Map<string, number>();
private labels = new Map<string, LabelRef>();
private anchors = new Map<string, { label: string; path: number[]; params?: [string, Expr | null][] | null }>();
private imagesByTag = new Map<string, { attrs: string[]; key: string }[]>();
@@ -136,6 +139,7 @@ export class Engine {
root.append(this.container);
this.renderer = new Renderer(this.container, w, h, {
url: (f) => this.fileUrl(f),
assetScale: (url) => this.imageScales.get(url) ?? 1,
resolveRef: (name) => this.resolveImage(name.split(" "))?.def ?? null,
evalCond: (c) => truthy(this.safeEval(c)),
transform: (name) => game.transforms[name] ?? null,
@@ -153,6 +157,7 @@ export class Engine {
});
this.audio = new AudioSystem(this.settings, (f) => this.fileUrl(f), game.channels ?? {});
this.buildUI();
if (manifest.versionId !== undefined) this.liveImages = new LiveImages(manifest.versionId, (message) => this.notify(`Улучшение графики: ${message}`));
this.resizeObserver = new ResizeObserver(() => this.renderer.fit(this.container));
this.resizeObserver.observe(this.container);
this.renderer.fit(this.container);
@@ -161,6 +166,7 @@ export class Engine {
}
destroy() {
this.liveImages?.destroy();
this.screens.destroy();
this.gen++;
this.wakeAll();
@@ -184,7 +190,13 @@ export class Engine {
}
}
const entry = logical ? this.manifest.files[logical] : undefined;
return entry ? `/api/versions/${this.manifest.versionId}/files/${entry.hash}` : null;
if (!entry || !logical) return null;
const source = `/api/versions/${this.manifest.versionId}/files/${entry.hash}`;
const improved = this.liveImages?.url(logical, entry.hash, source);
const url = improved || source;
const baseScale = this.game.assetScale?.[logical] ?? 1;
this.imageScales.set(url, baseScale * (improved ? this.liveImages!.scale(improved) : 1));
return url;
}
private resolveImage(name: string[]): ResolvedImage | null {
@@ -698,10 +710,7 @@ export class Engine {
if (resolved) return this.renderer.displayable(resolved.def);
const url = this.fileUrl(value);
if (!url) return null;
const img = new Image();
img.src = url;
img.draggable = false;
return img;
return this.renderer.displayable({ file: value });
}
private screenFont(file: string): string | null {
@@ -2008,6 +2017,21 @@ export class Engine {
row(reading, "Скорость текста", 0, 150, 5, s.cps, (v) => (v === 0 ? "Мгновенно" : `${v} симв/с`), (v) => (s.cps = v), "reading");
row(reading, "Задержка авто", 0.3, 4, 0.1, s.autoDelay, (v) => `${v.toFixed(1)} с`, (v) => (s.autoDelay = v), "reading");
const display = section("Игра", "Изменения сохраняются автоматически в этом браузере.");
const graphics = section("Графика", "Локальный генератор настраивается в меню профиля → Улучшение графики. Результат подхватывается при следующем показе картинки.");
const imageToggle = (label: string, checked: boolean, apply: (value: boolean) => void) => {
const row = el("label", "pg-check"); const box = el("input");
Object.assign(box, { type: "checkbox", checked });
box.onchange = () => apply(box.checked); row.append(box, el("span", "", label)); graphics.append(row);
};
imageToggle("Показывать улучшенные изображения", this.liveImages?.enabled ?? false, (value) => {
if (!this.liveImages) return;
this.liveImages.enabled = value;
this.liveImages.save();
// Refresh shown scene for immediate original/result comparison.
const shown = this.renderer.snapshot();
for (const image of shown) { const resolved = this.resolveImage(image.name); if (resolved) this.renderer.show(resolved, image.tag, image.at, { zorder: image.zorder }); }
});
imageToggle("Улучшать по ходу игры", this.liveImages?.automatic ?? false, (value) => { if (this.liveImages) { this.liveImages.automatic = value; this.liveImages.save(); } });
const langs = this.languages();
if (langs.length > 1) {
const r = el("label", "pg-setting");