py16+游戏源码存档
2026-08-22 14:22:51
发布于:广东
施工现场🚧
#
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation files
# (the "Software"), to deal in the Software without restriction,
# including without limitation the rights to use, copy, modify, merge,
# publish, distribute, sublicense, and/or sell copies of the Software,
# and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
from __future__ import print_function, absolute_import
import os
import sys
import warnings
# Functions to be customized by distributors. ################################
def path_to_gamedir(basedir, name):
"""
Returns the absolute path to the directory containing the game
scripts an assets. (This becomes config.gamedir.)
`basedir`
The base directory (config.basedir)
`name`
The basename of the executable, with the extension removed.
"""
# A list of candidate game directory names.
candidates = [ name ]
# Add candidate names that are based on the name of the executable,
# split at spaces and underscores.
game_name = name
while game_name:
prefix = game_name[0]
game_name = game_name[1:]
if prefix == ' ' or prefix == '_':
candidates.append(game_name)
# Add default candidates.
candidates.extend([ 'game', 'data', 'launcher/game' ])
# Take the first candidate that exists.
for i in candidates:
if i == "renpy":
continue
gamedir = os.path.join(basedir, i)
if os.path.isdir(gamedir):
break
else:
gamedir = basedir
return gamedir
def path_to_common(renpy_base):
"""
Returns the absolute path to the Ren'Py common directory.
`renpy_base`
The absolute path to the Ren'Py base directory, the directory
containing this file.
"""
path = renpy_base + "/renpy/common"
if os.path.isdir(path):
return path
return None
def path_to_saves(gamedir, save_directory=None): # type: (str, str|None) -> str
"""
Given the path to a Ren'Py game directory, and the value of config.
save_directory, returns absolute path to the directory where save files
will be placed.
`gamedir`
The absolute path to the game directory.
`save_directory`
The value of config.save_directory.
"""
import renpy # @UnresolvedImport
if save_directory is None:
save_directory = renpy.config.save_directory
save_directory = renpy.exports.fsencode(save_directory) # type: ignore
# Makes sure the permissions are right on the save directory.
def test_writable(d):
try:
fn = os.path.join(d, "test.txt")
open(fn, "w").close()
open(fn, "r").close()
os.unlink(fn)
return True
except Exception:
return False
# Android.
if renpy.android:
paths = [
os.path.join(os.environ["ANDROID_OLD_PUBLIC"], "game/saves"),
os.path.join(os.environ["ANDROID_PRIVATE"], "saves"),
os.path.join(os.environ["ANDROID_PUBLIC"], "saves"),
]
for rv in paths:
if os.path.isdir(rv) and test_writable(rv):
break
else:
rv = paths[-1]
print("Saving to", rv)
return rv
if renpy.ios:
from pyobjus import autoclass # type: ignore
from pyobjus.objc_py_types import enum # type: ignore
NSSearchPathDirectory = enum("NSSearchPathDirectory", NSDocumentDirectory=9)
NSSearchPathDomainMask = enum("NSSearchPathDomainMask", NSUserDomainMask=1)
NSFileManager = autoclass('NSFileManager')
manager = NSFileManager.defaultManager()
url = manager.URLsForDirectory_inDomains_(
NSSearchPathDirectory.NSDocumentDirectory,
NSSearchPathDomainMask.NSUserDomainMask,
).lastObject()
# url.path seems to change type based on iOS version, for some reason.
try:
rv = url.path().UTF8String()
except Exception:
rv = url.path.UTF8String()
if isinstance(rv, bytes):
rv = rv.decode("utf-8")
print("Saving to", rv)
return rv
# No save directory given.
if not save_directory:
return os.path.join(gamedir, "saves")
if "RENPY_PATH_TO_SAVES" in os.environ:
return os.environ["RENPY_PATH_TO_SAVES"] + "/" + save_directory
# Search the path above Ren'Py for a directory named "Ren'Py Data".
# If it exists, then use that for our save directory.
path = renpy.config.renpy_base
while True:
if os.path.isdir(path + "/Ren'Py Data"):
return path + "/Ren'Py Data/" + save_directory
newpath = os.path.dirname(path)
if path == newpath:
break
path = newpath
# Otherwise, put the saves in a platform-specific location.
if renpy.macintosh:
rv = "~/Library/RenPy/" + save_directory
return os.path.expanduser(rv)
elif renpy.windows:
if 'APPDATA' in os.environ:
return os.environ['APPDATA'] + "/RenPy/" + save_directory
else:
rv = "~/RenPy/" + renpy.config.save_directory # type: ignore
return os.path.expanduser(rv)
else:
rv = "~/.renpy/" + save_directory
return os.path.expanduser(rv)
# Returns the path to the Ren'Py base directory (containing common and
# the launcher, usually.)
def path_to_renpy_base():
"""
Returns the absolute path to the Ren'Py base directory.
"""
renpy_base = os.path.dirname(os.path.abspath(__file__))
renpy_base = os.path.abspath(renpy_base)
return renpy_base
def path_to_logdir(basedir):
"""
Returns the absolute path to the log directory.
`basedir`
The base directory (config.basedir)
"""
import renpy # @UnresolvedImport
if renpy.android:
return os.environ['ANDROID_PUBLIC']
return basedir
def predefined_searchpath(commondir):
import renpy # @UnresolvedImport
# The default gamedir, in private.
searchpath = [ renpy.config.gamedir ]
if renpy.android:
# The public android directory.
if "ANDROID_PUBLIC" in os.environ:
android_game = os.path.join(os.environ["ANDROID_PUBLIC"], "game")
if os.path.exists(android_game):
searchpath.insert(0, android_game)
# Asset packs.
packs = [
"ANDROID_PACK_FF1", "ANDROID_PACK_FF2",
"ANDROID_PACK_FF3", "ANDROID_PACK_FF4",
]
for i in packs:
if i not in os.environ:
continue
assets = os.environ[i]
for i in [ "renpy/common", "game" ]:
dn = os.path.join(assets, i)
if os.path.isdir(dn):
searchpath.append(dn)
else:
# Add path from env variable, if any
if "RENPY_SEARCHPATH" in os.environ:
searchpath.extend(os.environ["RENPY_SEARCHPATH"].split("::"))
if commondir and os.path.isdir(commondir):
searchpath.append(commondir)
if renpy.android or renpy.ios:
print("Mobile search paths:" , " ".join(searchpath))
return searchpath
##############################################################################
android = ("ANDROID_PRIVATE" in os.environ)
def main():
renpy_base = path_to_renpy_base()
sys.path.append(renpy_base)
# Ignore warnings.
warnings.simplefilter("ignore", DeprecationWarning)
# Start Ren'Py proper.
try:
import renpy.bootstrap
except ImportError:
print("Could not import renpy.bootstrap. Please ensure you decompressed Ren'Py", file=sys.stderr)
print("correctly, preserving the directory structure.", file=sys.stderr)
raise
# Set renpy.__main__ to this module.
renpy.__main__ = sys.modules[__name__] # type: ignore
renpy.bootstrap.bootstrap(renpy_base)
if __name__ == "__main__":
main()
# Copyright 2004-2025 Tom Rothamel <pytom@bishoujo.us>
#
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation files
# (the "Software"), to deal in the Software without restriction,
# including without limitation the rights to use, copy, modify, merge,
# publish, distribute, sublicense, and/or sell copies of the Software,
# and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
# This file contains the AST for the Ren'Py script language. Each class
# here corresponds to a statement in the script language.
# NOTE:
# When updating this file, consider if lint.py or warp.py also need
# updating.
from typing import Any, Callable, ClassVar, Literal, Never
import time
import hashlib
import ast
import re
import sys
import zlib
import renpy
from renpy.cslots import Object, Slot, IntegerSlot
from renpy.astsupport import hash32, PyExpr
from renpy.parameter import (
ParameterInfo,
ArgumentInfo,
apply_arguments,
EMPTY_PARAMETERS,
)
# For pickle compatibility.
if True:
from renpy.parameter import (
Parameter,
Signature,
EMPTY_ARGUMENTS,
)
# Config variables that are set twice - once when the rpy is first loaded,
# and then again at init time.
EARLY_CONFIG = {
"save_directory",
"allow_duplicate_labels",
"keyword_after_python",
"steam_appid",
"name",
"version",
"save_token_keys",
"check_conflicting_properties",
"check_translate_none",
"defer_tl_scripts",
"munge_in_strings",
"interface_layer",
"persistent_callback",
}
class PyCode(Object):
_cslot_linenumbers = True
filename: str
source: str
location: tuple[Any, ...]
mode: Literal["eval", "exec", "hide"] = "eval"
bytecode: bytes | None
py: int = 3
hashcode: int
def __getstate__(self):
return (1, self.source, (self.filename, self.linenumber), self.mode, self.py, self.hashcode, self.col_offset)
def __setstate__(self, state):
col_offset = 0
py = 2
hashcode = None
match state:
case (_, source, location, mode, py, hashcode, col_offset):
pass
case (_, source, location, mode, py, hashcode):
pass
case (_, source, location, mode, py):
pass
case (_, source, location, mode):
pass
case _:
raise Exception("Invalid state:", state)
self.py = py
self.source = source
self.filename = location[0]
self.linenumber = location[1]
self.col_offset = col_offset
self.mode = mode
if hashcode is None:
if isinstance(source, PyExpr):
hashcode = source.hashcode
else:
hashcode = hash32(source)
self.hashcode = hashcode
self.bytecode = None
if renpy.game.script.record_pycode:
renpy.game.script.all_pycode.append(self)
def __init__(
self, source: str, loc: tuple[str, int] = ("<none>", 1), mode: Literal["eval", "exec", "hide"] = "exec"
):
self.py = 3
if isinstance(source, PyExpr):
self.filename = source.filename
self.linenumber = source.linenumber
self.hashcode = source.hashcode
self.col_offset = source.column
else:
self.filename = loc[0]
self.linenumber = loc[1]
self.hashcode = hash32(source)
self.col_offset = 0
# The source code.
if mode != "eval":
self.source, self.col_offset = PyCode.dedent(source, self.col_offset)
else:
self.source = source
self.col_offset = 0
self.mode = mode
# This will be initialized later on, after we are serialized.
self.bytecode = None
if renpy.game.script.record_pycode:
renpy.game.script.all_pycode.append(self)
_leading_whitespace_re = re.compile("(^[ ]*)(?:[^ ])", re.MULTILINE)
@staticmethod
def dedent(text: str, col_offset: int) -> tuple[str, int]:
"""
Removes leading whitespace from a block of text. Entirely blank lines
are normalized to a newline character.
This returns the dedented text, and the amount of whitespace removed,
"""
# Look for the longest leading string of spaces and tabs common to
# all lines.
margin = None
indents = PyCode._leading_whitespace_re.findall(text)
for indent in indents:
if margin is None:
margin = indent
# Current line more deeply indented than previous winner:
# no change (previous winner is still on top).
elif indent.startswith(margin):
pass
# Current line consistent with and no deeper than previous winner:
# it's the new winner.
elif margin.startswith(indent):
margin = indent
# Find the largest common whitespace between current line and previous
# winner.
else:
for i, (x, y) in enumerate(zip(margin, indent)):
if x != y:
margin = margin[:i]
break
if margin:
text = re.sub(r"(?m)^" + margin, "", text)
if margin:
return text, len(margin) + col_offset
else:
return text, col_offset
DoesNotExtend = renpy.object.Sentinel("DoesNotExtend")
class Scry(object):
"""
This is used to store information about the future, if we know it. Unlike
predict, this tries to only get things we _know_ will happen.
"""
_next: "Node | None" = None
interacts: bool = False
say: bool = False
menu_with_caption: bool = False
who: str | None = None
extend_text: str | None | renpy.object.Sentinel = None
"""
Text that will be added to the current say statement by a call to
extend.
"""
multiple: int | None = None
"When the next say statement has a multiple argument, this is the value of that argument."
# By default, all attributes are None.
def __getattr__(self, name: str) -> Any:
return None
def __reduce__(self):
raise Exception("Cannot pickle Scry.")
def next(self) -> "Scry | None":
if self._next is None:
return None
else:
try:
return self._next.scry()
except Exception:
return None
type NodeName = "str | tuple[Any, ...] | None"
type RollbackType = Literal["normal", "never", "force"]
# Workaround that IntegerSlot accept only unsigned int.
# By using type alias SignedInt slot will fail 'type is int' check.
type SignedInt = int
class Node(Object):
"""
A node in the abstract syntax tree of the program.
"""
_cslot_linenumbers = True
filename: str
"Elided string file name of this node."
_name: NodeName
"""
Unique name of the node of all nodes in the abstract syntax tree.
This is used when the node name is either a string, or doesn't fit
into the usual filename, version, serial format.
"""
name_version: int
"""
When the name is a three-argument tuple, stores the version number.
"""
name_serial: int
"""
When the name is a three-argument tuple, stores the serial number.
"""
next: "Node | None"
"""
Node that unconditionally follows this one in the abstract syntax tree,
or None if this node is the last one in the block.
"""
translatable: ClassVar[bool] = False
"""
True if this node is translatable, False otherwise.
(This can be set on the class or the instance.)
"""
translation_relevant: ClassVar[bool] = False
"""
True if the node is relevant to translation, and has to be processed by
take_translations.
"""
rollback: ClassVar[RollbackType] = "normal"
"""
How does the node participate in rollback?
* "normal" in normal mode.
* "never" generally never.
* "force" force it to start.
"""
warp: ClassVar[bool] = False
"""
True if this statement should be run while warping, False otherwise.
"""
@property
def name(self) -> NodeName:
"""
The name property stores and retreives the name for the node.
This is one of:
* A string, when the node is a label.
* A tuple, in (filename, version, serial) format. This is stored efficently,
as it makes up most nodes in Ren'Py.
* Longer tuples, like (filename, version, serial, ...) are rare, but used.
* None, when the name is not known.
"""
if self._name:
return self._name
elif self.name_version:
return (self.filename, self.name_version, self.name_serial)
else:
return None
@name.setter
def name(self, value: NodeName):
match value:
case (self.filename, int(version), int(serial)):
self._name = None
self.name_version = version
self.name_serial = serial
case _:
self._name = value
# An ast node is equal to its name, allowing it to be used as the key in renpy.script.Script.namemap.
def __hash__(self):
return hash(self.name)
def __eq__(self, other):
return self.name == other
# Statement_start used to be a property on all nodes.
@property
def statement_start(self) -> "Node":
return self
@statement_start.setter
def statement_start(self, value: Never):
pass
def __init__(self, loc: tuple[str, int]):
"""
Initializes this Node object.
`loc`
A (filename, physical line number) tuple giving the
logical line on which this Node node starts.
"""
self.filename = loc[0]
self.linenumber = loc[1]
self.name = None
self.next = None
def diff_info(self) -> tuple[Any, ...]:
"""
Returns a tuple of diff info about ourself. This is used to
compare Nodes to see if they should be considered the same node. The
tuple returned must be hashable.
"""
return (id(self),)
def get_children(self, f: Callable[["Node"], Any]) -> None:
"""
Calls `f` with this node and its children.
"""
f(self)
def execute_init(self):
"""
Called at init time (that is, before the normal start of the script.),
at init priority returned by `Node.get_init` to execute init code of
this statement.
"""
def get_init(self) -> int | None:
"""
Return an integer priority for this node, or None if this node doesn't
care to suggest one.
"""
return None
def chain(self, next: "Node | None") -> None:
"""
This is called with the Node node that should be followed after
executing this node, and all nodes that this node
executes. (For example, if this node is a block label, the
next is the node that should be executed after all nodes in
the block.)
"""
self.next = next
def replace_next(self, old: "Node", new: "Node") -> None:
"""
Replaces instances of the `old` node with `new` when it is the next
node.
"""
if self.next is old:
self.next = new
def execute(self) -> None:
"""
Causes this node to execute, and any action it entails to be
performed. The node should call next_node with the node to
be executed after this one.
"""
raise Exception("Node subclass forgot to define execute.")
def early_execute(self) -> None:
"""
Called when the module is loaded.
"""
def predict(self) -> list["Node | None"]:
"""
This is called to predictively load images from this node. It
should cause renpy.display.predict.displayable and
renpy.display.predict.screen to be called as necessary.
Returns a list of nodes that may follow this one, where more
likely to be executed first is earlier in the list.
"""
if self.next is not None:
return [self.next]
else:
return []
def scry(self) -> Scry:
"""
Called to return an object with some general, user-definable
information about the future.
"""
rv = Scry()
rv._next = self.next
return rv
def restructure(self, callback: Callable[[list["Node"]], Any]):
"""
Called to restructure the AST.
When this method is called, callback is called once for each child
block of the node. The block, a list, can be updated by the callback
using slice assignment to the list.
"""
# Does nothing for nodes that do not contain child blocks.
return
def get_code(self, dialogue_filter: Callable[[str], str] | None = None) -> str:
"""
Returns the canonical form of the code corresponding to this statement.
This only needs to be defined if the statement is translatable.
`dialogue_filter`
If present, a filter that should be applied to human-readable
text in the statement.
"""
raise Exception("Not Implemented")
def analyze(self) -> None:
"""
Called on all code after the init phase, to analyze it.
"""
# Does nothing by default.
return
def can_warp(self) -> bool:
"""
Returns true if this should be run while warping, False otherwise.
"""
return self.warp
def get_reachable(self) -> list["Node"]:
"""
Return a possibly empty list of nodes that are directly reachable via
this node.
Basically, this should return all nodes that can be set as next node in
`execute`, but unlike predict, it should not guess nodes, and return
information that is statically defined in the node.
"""
if self.next is None:
return []
else:
return [self.next]
def get_translation_strings(self) -> list[tuple[int, str]]:
"""
Return a possibly empty list of linenumber, string pairs of strings
that are additional translation strings for this node.
"""
return []
################################################################################
# Utility functions
################################################################################
# The name of the current statement.
current_statement_name: str = "init"
def statement_name(name: str):
"""
Reports the name of this statement to systems like window auto.
"""
global current_statement_name
current_statement_name = name
for i in renpy.config.statement_callbacks:
i(name)
def next_node(n: Node | None):
"""
Indicates the next node that should be executed. When a statement
can crash, this should be set as early as possible, so that ignore
can bring us there.
"""
renpy.game.context().next_node = n
def probably_side_effect_free(expr: str) -> bool:
"""
Returns true if an expr probably does not have side effects, and should
be predicted. Basically, this just whitelists a set of characters that
doesn't allow for a function call.
"""
return not ("(" in expr)
def chain_block(block: list[Node], next: Node | None) -> None:
"""
This is called to chain together all of the nodes in a block. Node
n is chained with node n+1, while the last node is chained with
next.
"""
if not block:
return
for a, b in zip(block, block[1:]):
a.chain(b)
block[-1].chain(next)
def say_menu_with(expression: str | None, callback: Callable[[Any], Any]):
"""
This handles the with clause of a say or menu statement.
"""
if expression is not None:
what = renpy.python.py_eval(expression)
elif renpy.store.default_transition and renpy.game.preferences.transitions == 2:
what = renpy.store.default_transition
else:
return
if not what:
return
if renpy.game.preferences.transitions:
callback(what)
def eval_who(who: str | None, fast: bool | None = None) -> Any | None:
"""
Evaluates the `who` parameter to a say statement.
"""
if who is None:
return None
if "store.character" in renpy.python.store_dicts:
rv = renpy.python.store_dicts["store.character"].get(who, None)
else:
rv = None
if rv is None:
rv = renpy.python.store_dicts["store"].get(who, None)
if rv is not None:
return rv
try:
return renpy.python.py_eval(who)
except Exception:
raise Exception("Sayer '%s' is not defined." % who)
type ImspecType = """
tuple[tuple[str, ...], str | None, str | None, list[str], str | None, str | None, list[str]] |
tuple[tuple[str, ...], str | None, str | None, list[str], str | None, str | None] |
tuple[tuple[str, ...], list[str], str | None]
"""
def predict_imspec(imspec: ImspecType, scene=False, atl: "renpy.atl.RawBlock | None" = None):
"""
Call this to use the given callback to predict the image named
in imspec.
"""
if len(imspec) == 7:
name, expression, tag, at_expr_list, layer, _zorder, _behind = imspec
elif len(imspec) == 6:
name, expression, tag, at_expr_list, layer, _zorder = imspec
else:
name, at_expr_list, layer = imspec
tag = None
expression = None
if expression:
try:
img = renpy.python.py_eval(expression)
img = renpy.easy.displayable(img)
except Exception:
return
else:
img = None
at_list = []
for i in at_expr_list:
try:
at_list.append(renpy.python.py_eval(i))
except Exception:
pass
if atl is not None:
try:
at_list.append(renpy.display.transform.ATLTransform(atl))
except Exception:
pass
layer = renpy.exports.default_layer(layer, tag or name, bool(expression))
if scene:
renpy.game.context().images.predict_scene(layer)
renpy.exports.predict_show(name, layer, what=img, tag=tag, at_list=at_list)
def show_imspec(imspec: ImspecType, atl: "renpy.atl.RawBlock | None" = None):
if len(imspec) == 7:
name, expression, tag, at_list, layer, zorder, behind = imspec
elif len(imspec) == 6:
name, expression, tag, at_list, layer, zorder = imspec
behind = []
else:
name, at_list, layer = imspec
expression = None
tag = None
zorder = None
behind = []
if zorder is not None:
zorder = renpy.python.py_eval(zorder)
else:
zorder = None
if expression is not None:
expression = renpy.python.py_eval(expression)
if not renpy.config.old_show_expression:
if isinstance(expression, str):
name = expression
else:
if tag is None:
counter = 0
while True:
tag = "_show_expression_%d" % counter
if not renpy.exports.showing(tag, layer):
break
counter += 1
name = tag
expression = renpy.easy.displayable(expression)
at_list = [renpy.python.py_eval(i) for i in at_list]
layer = renpy.exports.default_layer(layer, tag or name, bool(expression) and (tag is None))
renpy.config.show(
name, at_list=at_list, layer=layer, what=expression, zorder=zorder, tag=tag, behind=behind, atl=atl
)
def create_store(name: str):
if name in renpy.config.special_namespaces:
return
# Take first two components of dot-joined name
maybe_special = ".".join(name.split(".")[:2])
if maybe_special in renpy.config.special_namespaces:
if not renpy.config.special_namespaces[maybe_special].allow_child_namespaces:
raise Exception("Creating stores within the {} namespace is not supported.".format(maybe_special[6:]))
renpy.python.create_store(name)
class StoreNamespace:
pure = True
repeat_at_default_time = False
def __init__(self, store):
self.store = store
def set(self, name: str, value: Any):
renpy.python.store_dicts[self.store][name] = value
def set_default(self, name: str, value: Any):
renpy.python.store_dicts[self.store][name] = value
def get(self, name: str) -> Any:
return renpy.python.store_dicts[self.store][name]
def get_namespace(store: str) -> tuple[StoreNamespace, bool]:
"""
Returns the namespace object for `store`, and a flag that is true if the
namespace is special, and false if it is a normal store.
"""
if store in renpy.config.special_namespaces:
return renpy.config.special_namespaces[store], True
return StoreNamespace(store), False
def redefine(stores: list[str]):
"""
Re-runs the define statements in the given stores.
"""
for i in define_statements:
i.redefine(stores)
def _reach_any(source: Node, target: Node) -> bool:
return True
def get_reachable_nodes(
entry_nodes: list[Node], node_validator: Callable[[Node, Node], bool] = _reach_any, seen: set[Node] | None = None
) -> list[Node]:
"""
Starting with `entry_nodes`, tries to reach new nodes by calling
`node_validator` on each new node that is reachable from the node in set.
`node_validator`
A function that takes two nodes as arguments, the node from which
reachability is being checked, and the node that is reachable from
that node.
If it returns True if the node should be considered
reachable for that source node, otherwise it is skipped.
By default allow any pairs of nodes.
`seen`
If not None, a set of nodes that have already been seen.
This can be used to avoid redundant work for multiple calls to this
function.
Ends when no new nodes are found and returns the left of all reached nodes.
Order of result nodes is all entry nodes, than nodes reachable from them,
and so on.
If all creator defined statements define their `reachable` function properly,
this function should return the same result for all calls with the same
arguments.
"""
from collections import deque
result = []
worklist = deque(entry_nodes)
if seen is None:
seen = set()
while worklist:
node = worklist.popleft()
if node in seen:
continue
seen.add(node)
result.append(node)
for n in node.get_reachable():
if n in seen:
continue
if not node_validator(node, n):
continue
worklist.append(n)
return result
################################################################################
# Basic statements
################################################################################
class Say(Node):
who: str | None
who_fast: bool
what: str
with_: str | None
interact: bool = True
attributes: tuple[str, ...] | None = None
arguments: ArgumentInfo | None = None
temporary_attributes: tuple[str, ...] | None = None
rollback: RollbackType = "normal" # type: ignore
identifier: str | None = None
explicit_identifier: bool = False
def diff_info(self):
return (Say, self.who, self.what)
def __init__(
self,
loc,
who,
what,
with_,
interact=True,
attributes=None,
arguments=None,
temporary_attributes=None,
identifier=None,
):
super(Say, self).__init__(loc)
if who is not None:
# True if who is a simple enough expression we can just look it up.
if re.match(renpy.lexer.word_regexp + r"\s*$", who):
self.who_fast = True
self.who = sys.intern(who.strip())
else:
self.who_fast = False
self.who = who
else:
self.who = None
self.who_fast = False
self.what = what
self.with_ = with_
self.interact = interact
self.arguments = arguments
# A tuple of attributes that are applied to the character that's
# speaking, or None to disable this behavior.
self.attributes = attributes
# Ditto for temporary attributes.
self.temporary_attributes = temporary_attributes
# If given, write in the identifier.
if identifier is not None:
self.identifier = identifier
self.explicit_identifier = True
def get_code(self, dialogue_filter=None):
rv = []
if self.who:
rv.append(self.who)
if self.attributes is not None:
rv.extend(self.attributes)
if self.temporary_attributes:
rv.append("@")
rv.extend(self.temporary_attributes)
what = self.what
if dialogue_filter is not None:
what = dialogue_filter(what)
rv.append(renpy.translation.encode_say_string(what))
if not self.interact:
rv.append("nointeract")
if getattr(self, "identifier", None) and self.explicit_identifier:
rv.append("id")
rv.append(getattr(self, "identifier", None))
if self.arguments:
rv.append(self.arguments.get_code())
# This has to be at the end.
if self.with_:
rv.append("with")
rv.append(self.with_)
return " ".join(rv)
def execute(self):
next_node(self.next)
try:
renpy.game.context().say_attributes = self.attributes
renpy.game.context().temporary_attributes = self.temporary_attributes
who = eval_who(self.who, self.who_fast)
stmt_name: str = "say"
if who is not None:
stmt_name = getattr(who, "statement_name", "say")
if callable(stmt_name):
stmt_name = stmt_name()
statement_name(stmt_name)
if not ((who is None) or callable(who) or isinstance(who, str)):
raise Exception(f"Sayer {self.who!r} is not a function or string.")
what = self.what
if renpy.config.say_menu_text_filter:
what = renpy.config.say_menu_text_filter(what)
renpy.store._last_raw_what = what
if self.arguments is not None:
args, kwargs = self.arguments.evaluate()
else:
args = ()
kwargs = {}
kwargs.setdefault("interact", self.interact)
if getattr(who, "record_say", True):
renpy.store._last_say_who = self.who
renpy.store._last_say_what = what
renpy.store._last_say_args = args
renpy.store._last_say_kwargs = kwargs
say_menu_with(self.with_, renpy.game.interface.set_transition)
renpy.exports.say(who, what, *args, **kwargs)
finally:
renpy.game.context().say_attributes = None
renpy.game.context().temporary_attributes = None
renpy.store._last_raw_what = ""
def predict(self):
old_attributes = renpy.game.context().say_attributes
old_temporary_attributes = renpy.game.context().temporary_attributes
try:
renpy.game.context().say_attributes = self.attributes
renpy.game.context().temporary_attributes = self.temporary_attributes
who = eval_who(self.who, self.who_fast)
def predict_with(trans):
renpy.display.predict.displayable(trans(old_widget=None, new_widget=None))
try:
say_menu_with(self.with_, predict_with)
except Exception:
pass
what = self.what
if renpy.config.say_menu_text_filter:
what = renpy.config.say_menu_text_filter(what)
renpy.exports.predict_say(who, what)
finally:
renpy.game.context().say_attributes = old_attributes
renpy.game.context().temporary_attributes = old_temporary_attributes
return [self.next]
def scry(self):
rv = super().scry()
who = eval_who(self.who, self.who_fast)
rv.who = who
rv.say = True
try:
rv.multiple = self.arguments.evaluate()[1]["multiple"]
except Exception:
pass
if self.interact:
renpy.exports.scry_say(who, self.what, rv)
else:
rv.interacts = False
rv.extend_text = DoesNotExtend
return rv
# Copy the descriptor.
setattr(Say, "with", Say.with_)
class Init(Node):
block: list[Node]
priority: SignedInt
def __init__(self, loc, block, priority):
super(Init, self).__init__(loc)
self.block = block
self.priority = priority
def get_children(self, f):
f(self)
for i in self.block:
i.get_children(f)
def execute_init(self):
renpy.execution.not_infinite_loop(60)
renpy.game.context().run(self.block[0])
def get_init(self):
return self.priority
# We handle chaining specially. We want to chain together the nodes in
# the block, but we want that chain to end in None, and we also want
# this node to just continue on to the next node in normal execution.
def chain(self, next):
self.next = next
chain_block(self.block, None)
def execute(self):
next_node(self.next)
renpy.execution.not_infinite_loop(60)
statement_name("init")
def restructure(self, callback):
callback(self.block)
class Label(Node):
translation_relevant = True
block: list[Node]
parameters: ParameterInfo | None = None
hide: bool = False
def __init__(self, loc, name, block, parameters, hide=False):
"""
Constructs a new Label node.
@param name: The name of this label.
@param block: A (potentially empty) list of nodes making up the
block associated with this label.
"""
super(Label, self).__init__(loc)
self.name = name # type: ignore
self.block = block
self.parameters = parameters
self.hide = hide
def diff_info(self):
return (Label, self.name)
def get_children(self, f):
f(self)
for i in self.block:
i.get_children(f)
def chain(self, next):
if self.block:
self.next = self.block[0]
chain_block(self.block, next)
else:
self.next = next
def execute(self):
next_node(self.next)
statement_name("label")
renpy.game.context().mark_seen()
values = apply_arguments(self.parameters, renpy.store._args, renpy.store._kwargs)
renpy.exports.dynamic(**values)
renpy.store._args = None
renpy.store._kwargs = None
renpy.easy.run_callbacks(renpy.config.label_callback, self.name, renpy.game.context().last_abnormal)
renpy.easy.run_callbacks(renpy.config.label_callbacks, self.name, renpy.game.context().last_abnormal)
def restructure(self, callback):
callback(self.block)
class Python(Node):
code: PyCode
store: str = "store"
hide: bool = False
def __init__(self, loc, python_code, hide=False, store="store"):
"""
@param code: A PyCode object.
@param hide: If True, the code will be executed with its
own local dictionary.
"""
super(Python, self).__init__(loc)
self.hide = hide
if hide:
self.code = PyCode(python_code, loc=loc, mode="hide")
else:
self.code = PyCode(python_code, loc=loc, mode="exec")
self.store = store
def diff_info(self):
return (Python, self.code.source)
def early_execute(self):
renpy.python.create_store(self.store)
def execute(self):
next_node(self.next)
statement_name("python")
try:
renpy.python.py_exec_bytecode(self.code.bytecode, self.hide, store=self.store)
finally:
if not renpy.game.context().init_phase:
for i in renpy.config.python_callbacks:
i()
def scry(self):
rv = super().scry()
rv.interacts = True
return rv
class EarlyPython(Node):
code: PyCode
store: str = "store"
hide: bool = False
def __init__(self, loc, python_code, hide=False, store="store"):
"""
@param code: A PyCode object.
@param hide: If True, the code will be executed with its
own local dictionary.
"""
super(EarlyPython, self).__init__(loc)
self.hide = hide
if hide:
self.code = PyCode(python_code, loc=loc, mode="hide")
else:
self.code = PyCode(python_code, loc=loc, mode="exec")
self.store = store
def diff_info(self):
return (EarlyPython, self.code.source)
def execute(self):
next_node(self.next)
renpy.execution.not_infinite_loop(60)
statement_name("python early")
def early_execute(self):
renpy.python.create_store(self.store)
if self.code.bytecode:
renpy.python.py_exec_bytecode(self.code.bytecode, self.hide, store=self.store)
class Image(Node):
imgname: tuple[str, ...]
code: PyCode | None
atl: "renpy.atl.RawBlock | None"
def __init__(self, loc, name, expr=None, atl=None):
"""
@param name: The name of the image being defined.
@param expr: An expression yielding a Displayable that is
assigned to the image.
"""
super(Image, self).__init__(loc)
self.imgname = name
if expr:
self.code = PyCode(expr, loc=loc, mode="eval")
self.atl = None
else:
self.code = None
self.atl = atl
def diff_info(self):
return (Image, tuple(self.imgname))
def execute(self):
# Note: We should always check that self.code is None before
# accessing self.atl, as self.atl may not always exist.
next_node(self.next)
statement_name("image")
if self.code is not None:
img = renpy.python.py_eval_bytecode(self.code.bytecode)
else:
img = renpy.display.motion.ATLTransform(self.atl)
renpy.exports.image(self.imgname, img)
def analyze(self):
if getattr(self, "atl", None) is not None:
# ATL images must participate with the game defined
# constant names. So, we pass empty parameters to enable it.
self.atl.analyze(EMPTY_PARAMETERS)
class Transform(Node):
varname: str
atl: "renpy.atl.RawBlock"
parameters: ParameterInfo | None = None
store: str = "store"
default_parameters = EMPTY_PARAMETERS
def __init__(self, loc, store, name, atl, parameters=default_parameters):
super(Transform, self).__init__(loc)
self.store = store
self.varname = name
self.atl = atl
self.parameters = parameters
def diff_info(self):
return (Transform, self.store, self.varname)
def early_execute(self):
create_store(self.store)
def execute(self):
next_node(self.next)
statement_name("transform")
parameters = getattr(self, "parameters", None)
if parameters is None:
parameters = Transform.default_parameters
trans = renpy.display.motion.ATLTransform(self.atl, parameters=parameters)
renpy.dump.transforms.append((self.varname, self.filename, self.linenumber))
renpy.exports.pure(self.varname)
ns, _special = get_namespace(self.store)
ns.set(self.varname, trans)
def analyze(self):
parameters = getattr(self, "parameters", None)
if parameters is None:
parameters = Transform.default_parameters
self.atl.analyze(parameters)
class Show(Node):
imspec: ImspecType
atl: "renpy.atl.RawBlock | None" = None
warp = True
def __init__(self, loc, imspec, atl=None):
"""
@param imspec: A triple consisting of an image name (itself a
tuple of strings), a list of at expressions, and a layer.
"""
super(Show, self).__init__(loc)
self.imspec = imspec
self.atl = atl
def diff_info(self):
return (Show, tuple(self.imspec[0]))
def execute(self):
next_node(self.next)
statement_name("show")
show_imspec(self.imspec, atl=getattr(self, "atl", None))
def predict(self):
predict_imspec(self.imspec, atl=getattr(self, "atl", None))
return [self.next]
def analyze(self):
if getattr(self, "atl", None) is not None:
# ATL block defined for show, scene or show layer statements
# must participate with the game defined constant names.
# So, we pass empty parameters to enable it.
self.atl.analyze(EMPTY_PARAMETERS)
class ShowLayer(Node):
warp = True
at_list: list[str]
atl: "renpy.atl.RawBlock | None" = None
layer: str = "master"
def __init__(self, loc, layer, at_list, atl):
super(ShowLayer, self).__init__(loc)
self.layer = layer
self.at_list = at_list
self.atl = atl
def diff_info(self):
return (ShowLayer, self.layer)
def execute(self):
next_node(self.next)
statement_name("show layer")
at_list = [renpy.python.py_eval(i) for i in self.at_list]
if self.atl is not None:
atl = renpy.display.motion.ATLTransform(self.atl)
at_list.append(atl)
renpy.exports.layer_at_list(at_list, layer=self.layer)
def predict(self):
return [self.next]
def analyze(self):
if self.atl is not None:
self.atl.analyze(EMPTY_PARAMETERS)
class Camera(Node):
warp = True
at_list: list[str]
atl: "renpy.atl.RawBlock | None"
layer: str = "master"
def __init__(self, loc, layer, at_list, atl):
super(Camera, self).__init__(loc)
self.layer = layer
self.at_list = at_list
self.atl = atl
def diff_info(self):
return (Camera, self.layer)
def execute(self):
next_node(self.next)
statement_name("show layer")
at_list = [renpy.python.py_eval(i) for i in self.at_list]
if self.atl is not None:
atl = renpy.display.motion.ATLTransform(self.atl)
at_list.append(atl)
renpy.exports.layer_at_list(at_list, layer=self.layer, camera=True)
def predict(self):
return [self.next]
def analyze(self):
if self.atl is not None:
self.atl.analyze(EMPTY_PARAMETERS)
class Scene(Node):
imspec: ImspecType
atl: "renpy.atl.RawBlock | None" = None
layer: str = "master"
warp = True
def __init__(self, loc, imgspec, layer, atl=None):
"""
@param imspec: A triple consisting of an image name (itself a
tuple of strings), a list of at expressions, and a layer, or
None to not have this scene statement also display an image.
"""
super(Scene, self).__init__(loc)
self.imspec = imgspec
self.layer = layer
self.atl = atl
def diff_info(self):
if self.imspec:
data = tuple(self.imspec[0])
else:
data = None
return (Scene, data)
def execute(self):
next_node(self.next)
statement_name("scene")
renpy.config.scene(self.layer)

全部评论 5
- 置顶
反编译结果
未完成3天前 来自 广东
0 难绷,ACGO 还有玩这个的
3天前 来自 湖北
1刚出的,看看能不能解了
3天前 来自 广东
0现在反编译了两个
3天前 来自 广东
0
1.2.3
3天前 来自 广东
0# Copyright 2004-2025 Tom Rothamel <pytom@bishoujo.us> # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation files # (the "Software"), to deal in the Software without restriction, # including without limitation the rights to use, copy, modify, merge, # publish, distribute, sublicense, and/or sell copies of the Software, # and to permit persons to whom the Software is furnished to do so, # subject to the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # This file contains the AST for the Ren'Py script language. Each class # here corresponds to a statement in the script language. # NOTE: # When updating this file, consider if lint.py or warp.py also need # updating. from typing import Any, Callable, ClassVar, Literal, Never import time import hashlib import ast import re import sys import zlib import renpy from renpy.cslots import Object, Slot, IntegerSlot from renpy.astsupport import hash32, PyExpr from renpy.parameter import ( ParameterInfo, ArgumentInfo, apply_arguments, EMPTY_PARAMETERS, ) # For pickle compatibility. if True: from renpy.parameter import ( Parameter, Signature, EMPTY_ARGUMENTS, ) # Config variables that are set twice - once when the rpy is first loaded, # and then again at init time. EARLY_CONFIG = { "save_directory", "allow3天前 来自 广东
01
3天前 来自 广东
0























有帮助,赞一个