feat: generator interpreter for Python that waits for the player (Ren'Py)
Python containing renpy.input, narrator("..."), character calls, renpy.say,
renpy.pause, renpy.display_menu, renpy.with_statement or renpy.call_screen now
runs through loops, try, functions, methods and expressions.
Player
- python.ts: execG/evalG/callG run only the code that may wait (mayBlock);
everything else stays on the synchronous fast path. A waiting call returns a
BlockingCall; outside the generator interpreter it is an explicit error. The
interpreter scope is set aside while the player decides
- engine: drives the generator and answers requests (dialogue, text input with
length/allow/exclude, pause, choice menu, transition, call screen)
- renpy.show/hide/scene, transitions and transforms as Python values,
Character(...) objects, Borders, set_tag_attributes
Converter
- blocking_names() lists game functions that may wait, transitively, and ships
them as game.json "blocking"; character calls are allowed
- hide screen of an unknown screen is harmless; images defined by a parenthesised
expression are supported
Hooked now passes the strict compatibility check (with Tomie and Twisted-Thoughts).
Tests: waiting.py fixture run through the generator interpreter; blocking-name analysis.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
ea03cebf6a
commit
3923ee72fb
11 files changed
+698
-27
No files matched your search
@@ -717,26 +717,32 @@ class RpyParser:
|
||||
return bool(m and m.group(1) in _DISPLAYABLE_CALLS)
|
||||
|
||||
def expression_picture(self, line: Line) -> dict[str, Any] | None:
|
||||
"""``image x:`` whose first line is a call computing the picture (``get_face("base")``) followed by
|
||||
transform lines: the picture is evaluated when the image is shown, the transforms are ATL."""
|
||||
"""``image x:`` whose first line is an expression computing the picture (``get_face("base")`` or
|
||||
``("a_" + str(n))``), optionally followed by transform lines: it is evaluated when the image is shown."""
|
||||
if not line.block:
|
||||
return None
|
||||
first = line.block[0]
|
||||
match = _CALL_LINE.match(first.text)
|
||||
text = first.text.strip()
|
||||
match = _CALL_LINE.match(text)
|
||||
word = Cursor(first.text).peek_word() or ""
|
||||
if (not match or self.displayable_line(first) or match.group(1) in self.d.transforms
|
||||
or word in self.TIMELINE_WORDS or WARPER.match(word) or word in POSITION_PROPS):
|
||||
if not (match or text.startswith("(")):
|
||||
return None
|
||||
if match and (self.displayable_line(first) or match.group(1) in self.d.transforms
|
||||
or word in self.TIMELINE_WORDS or WARPER.match(word) or word in POSITION_PROPS):
|
||||
return None
|
||||
expr = self.expr(text, first)
|
||||
if expr is None:
|
||||
return None
|
||||
picture: dict[str, Any] = {"expr": expr}
|
||||
rest = line.block[1:]
|
||||
expr = self.expr(first.text, first)
|
||||
if expr is None or not rest:
|
||||
return None
|
||||
issues: list[tuple[str, str]] = []
|
||||
props, animation = self.atl_spec(rest, issues.append, set())
|
||||
if issues or not animation:
|
||||
return None
|
||||
if rest:
|
||||
issues: list[tuple[str, str]] = []
|
||||
props, animation = self.atl_spec(rest, issues.append, set())
|
||||
if issues or not animation:
|
||||
return None
|
||||
picture["at"] = [{"props": props, "atl": animation}]
|
||||
self.d.warn("Изображение задано выражением", self.where(first), blocking=False)
|
||||
return {"expr": expr, "at": [{"props": props, "atl": animation}]}
|
||||
return picture
|
||||
|
||||
def atl_image(self, line: Line) -> dict[str, Any] | None:
|
||||
if (picture := self.expression_picture(line)) is not None:
|
||||
@@ -1231,8 +1237,7 @@ class RpyParser:
|
||||
name = c.word()
|
||||
if not name:
|
||||
return []
|
||||
self.d.screen_used.add(name)
|
||||
return [{"op": "hide_screen", "name": name}]
|
||||
return [{"op": "hide_screen", "name": name}] # hiding a screen that does not exist is harmless
|
||||
spec = self.image_spec(c, line)
|
||||
node: Node = {"op": "hide", "tag": spec.get("name", [""])[0]}
|
||||
self.show_clauses(c, line, node)
|
||||
|
||||
Reference in new issue
Block a user