diff --git a/backend/app/converter/compatibility.py b/backend/app/converter/compatibility.py index 0ec67ac..7002e56 100644 --- a/backend/app/converter/compatibility.py +++ b/backend/app/converter/compatibility.py @@ -222,7 +222,13 @@ ENGINE_LABELS = {"main_menu_screen", "_main_menu_screen", "main_menu"} BLOCKING_BUILTINS = {"renpy.input", "renpy.say", "renpy.pause", "renpy.display_menu", "renpy.with_statement", "renpy.call_screen"} CALLS |= BLOCKING_BUILTINS | {"renpy.show", "renpy.hide", "renpy.scene", "Borders", "renpy.set_tag_attributes", - "eval", "exec", "Replay", "EndReplay", "renpy.end_replay"} + "eval", "exec", "Replay", "EndReplay", "renpy.end_replay", + "renpy.get_showing_tags", "renpy.run", "renpy.pop_call", + "renpy.curry", "_preferences.get_mute", "_preferences.get_volume", + "preferences.get_mute", "preferences.get_volume"} +CALLS |= {"math." + name for name in ( + "sin cos tan asin acos atan atan2 exp fabs trunc hypot log log10 fmod modf degrees radians isnan isinf " + "copysign").split()} def blocking_names(data) -> list[str]: diff --git a/frontend/src/player/engine.ts b/frontend/src/player/engine.ts index 3f58a47..278fea9 100644 --- a/frontend/src/player/engine.ts +++ b/frontend/src/player/engine.ts @@ -344,6 +344,20 @@ export class Engine { throw new JumpSignal("jump", "__quit__"); }, version: () => "pgvn", + get_showing_tags: () => this.renderer.snapshot().map((image) => image.tag), + run: (action: any) => { + this.screens.run(action, null); + return null; + }, + pop_call: () => { + this.stack.pop(); + return null; + }, + curry: (f: any) => { + const arity = f?.__def ? f.__def.params.filter((p: any) => p.kind === "p" && !p.d).length : f?.length ?? 0; + const curried = (...args: any[]): any => (args.length >= arity ? f(...args) : (...more: any[]) => curried(...args, ...more)); + return curried; + }, ...this.screens.renpyApi(), // presentation-only: nothing to roll back or replay, and the next transition is chosen by `with` end_replay: () => { @@ -391,7 +405,17 @@ export class Engine { set: (_t, k: string, v) => ((this.store[k] = v), true) }), audio: this.game.audio, random, - math: { floor: Math.floor, ceil: Math.ceil, sqrt: Math.sqrt, pi: Math.PI, pow: Math.pow }, + math: { + floor: Math.floor, ceil: Math.ceil, sqrt: Math.sqrt, pi: Math.PI, e: Math.E, pow: Math.pow, + sin: Math.sin, cos: Math.cos, tan: Math.tan, asin: Math.asin, acos: Math.acos, atan: Math.atan, + atan2: Math.atan2, exp: Math.exp, fabs: Math.abs, trunc: Math.trunc, hypot: Math.hypot, + log: (x: number, base?: number) => (base ? Math.log(x) / Math.log(base) : Math.log(x)), + log10: Math.log10, fmod: (a: number, b: number) => a % b, + modf: (x: number) => [x - Math.trunc(x), Math.trunc(x)], + degrees: (x: number) => (x * 180) / Math.PI, radians: (x: number) => (x * Math.PI) / 180, + isnan: Number.isNaN, isinf: (x: number) => x === Infinity || x === -Infinity, + copysign: (a: number, b: number) => Math.abs(a) * (b < 0 || Object.is(b, -0) ? -1 : 1), + }, 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)), @@ -450,6 +474,8 @@ export class Engine { get language() { return engine.tr.renpyName; }, + get_mute: () => false, + get_volume: () => 1, }; }