feat: random generators, frozenset and __hash__/__iter__/__getitem__/__contains__ in Ren'Py python
CI / frontend (push) Failing after 18s
CI / backend (push) Failing after 35s

renpy.random/random get choices, sample, randrange and Random(seed); frozenset, iter and hash
are builtins. User classes may define __hash__ (dict keys), __iter__, __getitem__ and
__contains__; the converter accepts them instead of rejecting the whole class, which
removes the Perk/Skill/Status wall in SaintlyCorruption.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
mrleo1nidandClaude Sonnet 5.5 committed 2026-10-26 02:55:44 +03:00
1 parent f9eb5c1212
commit e1c596288e
8 files changed
+165 -18

No files matched your search

+7 -4
View File
@@ -91,9 +91,10 @@ def require_supported_scripts(data, engine: str = "renpy") -> None:
raise CompatibilityError(engine, issues)
CALLS = set("len str int float bool abs round min max sum any all range list tuple set dict sorted reversed enumerate zip isinstance _ __".split())
CALLS = set("len str int float bool abs round min max sum any all range list tuple set frozenset dict sorted reversed enumerate zip isinstance _ __".split())
CALLS |= {prefix + method for prefix in ("random.", "renpy.random.")
for method in ("randint", "random", "choice", "shuffle", "uniform")}
for method in ("randint", "randrange", "random", "choice", "choices", "sample", "shuffle", "uniform",
"Random")}
CALLS |= {"math." + method for method in ("floor", "ceil", "sqrt", "pow")}
CALLS |= {"renpy." + method for method in ("play", "notify", "jump", "call", "input", "has_label",
"seen_label", "showing", "get_attributes", "full_restart", "quit", "version")}
@@ -119,7 +120,7 @@ CALLS |= {"renpy.start_predict", "renpy.stop_predict", "renpy.start_predict_scre
"renpy.ui.imagebutton", "renpy.return_statement"}
from app.games.renpy_native import NATIVE_CLASSES, NATIVE_METHODS # noqa: E402
CALLS |= set(NATIVE_CLASSES)
CALLS |= {"map", "filter", "hasattr", "getattr", "setattr", "callable", "super"}
CALLS |= {"map", "filter", "iter", "hash", "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
@@ -154,6 +155,8 @@ CALLS |= SCREEN_CALLS | {"globals", "renpy.variant", "renpy.change_language", "r
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 zfill ljust rjust center partition rpartition rsplit splitlines isupper islower isspace isalnum swapcase rfind has_key iteritems iterkeys itervalues strftime isoformat weekday group groups groupdict start end".split())
METHODS |= NATIVE_METHODS
# Methods of generators made by `renpy.random.Random()` / `random.Random()`
METHODS |= {"randint", "randrange", "random", "choice", "choices", "sample", "shuffle", "uniform"}
def _bound_names(root) -> tuple[set[str], set[str]]:
@@ -315,7 +318,7 @@ def mark_screen_calls(data) -> None:
visit([live, data.defaults, [data.screens[n] for n in data.screen_used if n in data.screens]])
IMPLICIT_METHODS = {"__init__", "__str__", "__repr__"}
IMPLICIT_METHODS = {"__init__", "__str__", "__repr__", "__hash__", "__eq__", "__iter__", "__getitem__", "__contains__"}
# Labels Ren'Py itself defines (renpy/common); jumping to them goes to the player's own menu
ENGINE_LABELS = {"main_menu_screen", "_main_menu_screen", "main_menu"}
+2 -1
View File
@@ -264,7 +264,8 @@ def stmt(node: ast.stmt) -> list[Stmt]:
# Methods the player can run implicitly. Any other dunder method would silently
# never be called, so the converter refuses it instead.
SUPPORTED_DUNDERS = {"__init__", "__str__", "__repr__", "__call__", "__eq__"}
SUPPORTED_DUNDERS = {"__init__", "__str__", "__repr__", "__call__", "__eq__", "__hash__", "__iter__", "__getitem__",
"__contains__"}
INACTIVE_VARIANTS = {"gui.variant", "renpy.variant"}
TRANSPARENT_DECORATORS = {"renpy.pure"} # marks a function as side-effect free; no runtime meaning
DECORATORS = {"staticmethod", "classmethod", "property"}