feat: ATL pictures/contains/function/time, colour matrices and more Python for Ren'Py
ATL
- Image blocks become timelines: picture steps with dissolve, parallel, contains,
finite repeat, time, function f(trans, st, at), calls of other transforms with
arguments, on hover/idle events for buttons. Animations stop when their element
leaves the page
- Pictures defined by an expression (get_face_path("base")) are drawn at show time
- Purely graphical hints (shader, mesh, gl_*, u_*) are accepted and dropped
Colour
- matrixcolor and im.matrix.* as 5x5 matrices combined with `*`, applied through SVG
filters; im.MatrixColor/Scale/Crop/Blur/Composite and Text/Image as values
Python
- match statement, re/copy/time/datetime/defaultdict/functools.partial, more
str/dict methods, Character(...) created by code
- narrator("..."), Character calls, renpy.say and renpy.show/hide with expressions in
Python blocks become script nodes
- Audit follows reachability: functions and classes nothing refers to (custom
statement lexers, unused tools) no longer block an import
Docs: README and ARCHITECTURE now describe a multi-engine library (GameMaker and
QSP planned). Tests for the new runtime pieces.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
62ece25902
commit
ea03cebf6a
21 files changed
+1264
-93
No files matched your search
@@ -7,6 +7,7 @@ import { fastPoint, interpolate, parseText, plainText, renderText, waitPoints }
|
||||
import { languageName, rememberLanguage, type Translator } from "./i18n";
|
||||
import { naniText } from "./naninovel";
|
||||
import { ScreenManager, type ScreenHost } from "./screens";
|
||||
import { pythonModules } from "./pymodules";
|
||||
import { DEFAULT_SETTINGS, SLOTS, Restart, el, loadJSON, saveJSON, type SaveApi, type SaveMeta, type Settings } from "./engine-util";
|
||||
import type {
|
||||
CharacterDef, Expr, Frame, GameData, ImageDef, Label, Layer, Manifest, Node, SaveData, ScriptModule,
|
||||
@@ -321,6 +322,20 @@ export class Engine {
|
||||
...this.screens.renpyApi(),
|
||||
// presentation-only: nothing to roll back or replay, and the next transition is chosen by `with`
|
||||
end_replay: () => null,
|
||||
partial: (f: any, ...bound: any[]) => (...args: any[]) => f(...bound, ...args),
|
||||
loadable: (path: any) => this.fileUrl(String(path)) !== null,
|
||||
is_init_phase: () => false,
|
||||
call_stack_depth: () => this.stack.length,
|
||||
const: (value: any) => value,
|
||||
screenshot: () => null,
|
||||
Keymap: () => null,
|
||||
register_shader: () => null,
|
||||
register_statement: () => null,
|
||||
redraw: () => null,
|
||||
stop_predict: () => null,
|
||||
map_event: () => null,
|
||||
count_displayables_in_layer: () => 0,
|
||||
displayable: (value: any) => value,
|
||||
variant: (name: string) => ["pc", "large", "web", "medium"].includes(String(name)),
|
||||
change_language: (code: any) => void this.setLanguage(code === null || code === undefined ? this.game.sourceLang ?? "und" : String(code)),
|
||||
seen_image: (name: any) => this.seenImages.has(String(name)),
|
||||
@@ -351,6 +366,10 @@ export class Engine {
|
||||
len: (x: any) => (x instanceof PyDict ? x.map.size : x?.length ?? 0),
|
||||
// a read-only view: `"name" in globals()` works, writes do not reach the game
|
||||
globals: () => new PyDict(Object.entries(this.store)),
|
||||
print: () => null,
|
||||
...pythonModules(),
|
||||
_window_hide: () => { this.windowShown = false; this.textbox.classList.add("pg-hidden"); return null; },
|
||||
_window_show: () => { this.windowShown = true; return null; },
|
||||
...Object.fromEntries(["Dissolve", "Fade", "ImageDissolve", "MoveTransition", "CropMove", "PushMove",
|
||||
"Pixellate", "ComposeTransition", "With"].map((n) => [n, () => null])),
|
||||
str: (x: any) => pyStr(x),
|
||||
@@ -429,6 +448,8 @@ export class Engine {
|
||||
if (/^https?:\/\//i.test(url)) window.open(url, "_blank", "noopener,noreferrer");
|
||||
},
|
||||
setLanguage: (code) => void this.setLanguage(code ?? this.game.sourceLang ?? "und"),
|
||||
quickSave: () => void this.save("quick"),
|
||||
quickLoad: () => void this.load("quick"),
|
||||
transform: (name) => this.game.transforms[name] ?? null,
|
||||
atl: (name) => this.game.atl?.[name] ?? null,
|
||||
};
|
||||
@@ -729,7 +750,7 @@ export class Engine {
|
||||
this.renderer.scene();
|
||||
this.clearNvl();
|
||||
}
|
||||
if (node.op === "hide") this.renderer.hide(node.tag);
|
||||
if (node.op === "hide") this.renderer.hide(node.tagExpr ? pyStr(this.safeEval(node.tagExpr)).split(" ")[0] : node.tag);
|
||||
else if (node.img) this.showNode(node);
|
||||
if (node.with) await this.renderer.transition(this.transition(node.with));
|
||||
this.advance();
|
||||
@@ -878,7 +899,11 @@ export class Engine {
|
||||
}
|
||||
|
||||
private characterFor(node: Node): { char: CharacterDef; name: string; mode: string } {
|
||||
const who: string | undefined = node.who;
|
||||
let who: string | undefined = node.who;
|
||||
if (node.whoExpr) {
|
||||
const value = this.safeEval(node.whoExpr);
|
||||
who = typeof value === "string" ? value : undefined;
|
||||
}
|
||||
if (this.game.engine === "naninovel" && node.whoStr !== undefined) {
|
||||
const entry = this.game.naniResources?.CharacterNames?.[node.whoStr]?.[0];
|
||||
return { char: {}, name: this.interp(entry ? this.tr.line(entry.tid, entry.text) : this.tr.str(node.whoStr)), mode: "adv" };
|
||||
@@ -891,6 +916,9 @@ export class Engine {
|
||||
const char = this.game.characters[who];
|
||||
if (!char) {
|
||||
const v = this.store[who];
|
||||
if (v && typeof v === "object" && v.__character) {
|
||||
return { char: { color: v.color ?? undefined }, name: this.interp(this.tr.str(v.name ?? who)), mode: "adv" };
|
||||
}
|
||||
return { char: {}, name: typeof v === "string" ? v : who, mode: "adv" };
|
||||
}
|
||||
let name = this.tr.str(char.name ?? "");
|
||||
@@ -927,7 +955,9 @@ export class Engine {
|
||||
if (!inMenu) this.interactionStart();
|
||||
if (node.with) await this.renderer.transition(this.transition(node.with));
|
||||
|
||||
let raw = this.interp(this.tr.line(node.tid, node.what));
|
||||
let raw = node.whatExpr
|
||||
? this.interp(pyStr(this.safeEval(node.whatExpr)))
|
||||
: this.interp(this.tr.line(node.tid, node.what));
|
||||
raw = `${char.whatPrefix ?? ""}${raw}${char.whatSuffix ?? ""}`;
|
||||
let prefixLen = 0;
|
||||
if (mode === "extend" && this.lastSay) {
|
||||
|
||||
Reference in new issue
Block a user