feat: Ren'Py screens (screen/call screen/show screen) with actions and styles
Converter - screens.py parses the screen language: containers (fixed, frame, vbox, hbox, grid, vpgrid, viewport, button, has), widgets (text, textbutton, imagebutton, add, bar ...), if/for/use/transclude, default, python, key, timer, on, inline ATL, conditional properties, and decompiled forms (unparenthesised operators, glued property values, comma separated actions) - style statements, call/show/hide screen operators and Python-level renpy.show_screen are translated; only screens the game shows are audited, unused stock screens no longer block an import; undefined screens do - ATL values may be computed at run time and transforms take parameters (at blink(0.2, 0.3)); blur property Player - screens.ts builds the DOM from the IR and rebuilds it on state changes; buttons with idle/hover/selected/insensitive states, tooltips, sounds, modal screens, timers and keys that survive re-rendering, saved with the save file - Actions: Jump, Call, Return, SetVariable, Toggle*, SetScreenVariable, Show, Hide, ToggleScreen, Function, Play, Notify, If, Language, ShowMenu, OpenURL ... - Crop/LiveCrop/Solid/Frame/Transform/DynamicDisplayable values, crop property, globals(), renpy.variant/change_language/seen_image, transition stubs Tests: screen parser and runtime (linkedom DOM), screens fixture compiled from tests/fixtures/screens.rpy. Docs: docs/SCREENS.md. Twisted-Thoughts now passes the strict compatibility check. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
47fc121e7d
commit
62ece25902
24 files changed
+2997
-60
No files matched your search
@@ -105,6 +105,16 @@ CALLS |= {f"renpy.{channel}.{method}" for channel in ("music", "sound")
|
||||
for method in ("set_volume", "set_pause", "set_pan")}
|
||||
CALLS |= {"renpy.end_replay", "renpy.block_rollback", "renpy.transition"}
|
||||
CALLS |= {"map", "filter", "hasattr", "getattr", "setattr", "callable", "super"}
|
||||
# Screen actions and values the player implements (see frontend/src/player/screens.ts)
|
||||
SCREEN_CALLS = set("""Jump Call Return SetVariable ToggleVariable SetDict SetField ToggleField
|
||||
SetScreenVariable ToggleScreenVariable Show Hide ShowTransient Function NullAction Play Stop Notify
|
||||
Quit MainMenu If GetTooltip StaticValue VariableValue AnimatedValue FieldValue SelectedIf
|
||||
SensitiveIf Queue ToggleScreen Language ShowMenu OpenURL Crop LiveCrop Solid Frame Null Transform
|
||||
DynamicDisplayable Color Dissolve Fade ImageDissolve MoveTransition CropMove PushMove Pixellate
|
||||
ComposeTransition With""".split())
|
||||
CALLS |= SCREEN_CALLS | {"globals", "renpy.variant", "renpy.change_language", "renpy.seen_image",
|
||||
"renpy.show_screen", "renpy.hide_screen", "renpy.restart_interaction",
|
||||
"renpy.get_screen", "renpy.call_screen"}
|
||||
METHODS = set("append extend insert pop remove index count reverse sort clear copy add discard get keys values items setdefault update lower upper capitalize title strip lstrip rstrip startswith endswith replace split join format find isdigit isalpha".split())
|
||||
|
||||
|
||||
@@ -139,6 +149,11 @@ def _bound_names(root) -> tuple[set[str], set[str]]:
|
||||
target(value.get("t"))
|
||||
elif kind == "lambda":
|
||||
names.update(p["n"] for p in value["params"])
|
||||
elif value.get("c") == "for":
|
||||
target(value.get("t"))
|
||||
elif "params" in value and "defaults" in value and "body" in value: # a screen
|
||||
names.update(p["n"] for p in value["params"])
|
||||
names.update(name for name, _ in value["defaults"])
|
||||
elif kind == "comp":
|
||||
target(value.get("t"))
|
||||
for gen in value.get("gens", []):
|
||||
@@ -152,9 +167,42 @@ def _bound_names(root) -> tuple[set[str], set[str]]:
|
||||
return names, methods
|
||||
|
||||
|
||||
SCREEN_OPENERS = {"renpy.show_screen", "renpy.call_screen", "renpy.hide_screen", "Show", "ShowTransient",
|
||||
"Hide"}
|
||||
|
||||
|
||||
def mark_screen_calls(data) -> None:
|
||||
"""`renpy.show_screen("x")` in game code uses screen x, just like `show screen x`."""
|
||||
def visit(value):
|
||||
if isinstance(value, list):
|
||||
for item in value:
|
||||
visit(item)
|
||||
elif isinstance(value, dict):
|
||||
if value.get("k") == "call" and value.get("a"):
|
||||
name = value["f"]["id"] if value["f"].get("k") == "name" else (
|
||||
"renpy." + value["f"]["a"] if value["f"].get("k") == "attr"
|
||||
and value["f"]["o"].get("k") == "name" and value["f"]["o"]["id"] == "renpy" else "")
|
||||
first = value["a"][0]
|
||||
if name in SCREEN_OPENERS and first.get("k") == "const" and isinstance(first["v"], str):
|
||||
if first["v"] in data.screens and first["v"] not in data.screen_used:
|
||||
data.use_screen(first["v"])
|
||||
changed.append(first["v"])
|
||||
for child in value.values():
|
||||
visit(child)
|
||||
|
||||
changed = [True]
|
||||
while changed:
|
||||
changed.clear()
|
||||
visit([data.modules, data.init_code, data.defaults,
|
||||
[data.screens[n] for n in data.screen_used if n in data.screens]])
|
||||
|
||||
|
||||
def audit_ir(data) -> None:
|
||||
"""Reject calls which the browser would otherwise silently replace by None."""
|
||||
user_names, user_methods = _bound_names([data.modules, data.init_code, data.defaults, data.images])
|
||||
mark_screen_calls(data)
|
||||
used_screens = {n: data.screens[n] for n in data.screen_used if n in data.screens}
|
||||
user_names, user_methods = _bound_names([data.modules, data.init_code, data.defaults, data.images,
|
||||
list(used_screens.values())])
|
||||
user_names |= {name for name, _ in data.defaults}
|
||||
|
||||
def dotted(value):
|
||||
@@ -189,5 +237,9 @@ def audit_ir(data) -> None:
|
||||
for path, module in data.modules.items():
|
||||
visit(module, path)
|
||||
visit(data.defaults, "default")
|
||||
for name, definition in used_screens.items():
|
||||
visit(definition, f"screen {name}")
|
||||
for name in sorted(data.screen_used - set(data.screens)):
|
||||
data.warn("Не найден screen", name)
|
||||
visit(data.init_code, "init")
|
||||
visit(data.images, "image")
|
||||
Reference in new issue
Block a user