Compare commits
233 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5534516253 | |||
| a445a7b000 | |||
| 601594e047 | |||
| cedff0527f | |||
| 2b1f812051 | |||
| c85c917d12 | |||
| f2040ea6d5 | |||
| 7602e726f2 | |||
| dce59141a7 | |||
| 8dfafd617b | |||
| a28829662c | |||
| 9665ee9226 | |||
| b79419ea2f | |||
| b2787cc61d | |||
| 8c45a9a1ec | |||
| fc45dca4c8 | |||
| be4d4290a8 | |||
| 59c49a6512 | |||
| 6fedb10d30 | |||
| 5eb650d3fb | |||
| 8c2d509b9c | |||
| 1eee09bb9e | |||
| bacc645355 | |||
| db83f8462d | |||
| 4ee3026cad | |||
| cc6594d068 | |||
| 3469504ffd | |||
| 7954d1d9cd | |||
| 8706d19545 | |||
| 666370ac7f | |||
| 88722cc03b | |||
| 64171052ac | |||
| 793a2b2250 | |||
| d16e1e71b8 | |||
| cf56ebc008 | |||
| 26166cfdd8 | |||
| c5f60655d3 | |||
| 0ef5e6650e | |||
| 036bdc4afe | |||
| 33d8c014ec | |||
| 1b193e6884 | |||
| d6b7786f60 | |||
| a1bcb09260 | |||
| d05704c7c7 | |||
| 68af2f3db2 | |||
| 9b6ab94970 | |||
| 51abcae5d4 | |||
| 79e9d81173 | |||
| 4e60a996dd | |||
| 15738d1dfb | |||
| c37c44d227 | |||
| 045b6cc227 | |||
| 56f86495fb | |||
| 576b02d124 | |||
| b5b7673e8b | |||
| 455274bbaa | |||
| 0b492357d9 | |||
| fb0149d0dc | |||
| e7184c3a11 | |||
| 963dba7cc6 | |||
| 087f618326 | |||
| 4c2be41c63 | |||
| d23d5d91e3 | |||
| 7f8a07a8a5 | |||
| 173eaf6029 | |||
| 8d0830f66f | |||
| 584f7a0be7 | |||
| e210147343 | |||
| e4b33e595a | |||
| 53d08e0f2d | |||
| 84c53d3d9c | |||
| 2962b5c398 | |||
| 0ebfae81c9 | |||
| 5dc0372228 | |||
| 82780c57ee | |||
| 305215823d | |||
| 38440bf843 | |||
| 3596a59a54 | |||
| 5aaf1f8ef4 | |||
| 398c8a3053 | |||
| cfa560d8fa | |||
| 46fe772cc5 | |||
| 9149a5c941 | |||
| ce50d6e3f1 | |||
| f015cda018 | |||
| 069ac8e6c3 | |||
| e2ad4d5a3c | |||
| 3239f0d5bb | |||
| 7ac80c1a4f | |||
| eda749e44a | |||
| 5da79e86ae | |||
| dc73065241 | |||
| 55992074c1 | |||
| ba79f7986c | |||
| 5a0e3da293 | |||
| d7a2bd7bdf | |||
| aa43d1828a | |||
| 21d90dbd55 | |||
| 4b8026fdd7 | |||
| 73b8629724 | |||
| 79f0037afc | |||
| 49c92600bf | |||
| f594f51ada | |||
| 254c5b6a2a | |||
| 7568ae3146 | |||
| 1d7f7353c8 | |||
| ad4756039d | |||
| dc30cc39aa | |||
| 2720ab3615 | |||
| d7a8c5fb96 | |||
| 49baa6e6cb | |||
| b5abd9f4c4 | |||
| 98d104b9ec | |||
| f5e001ac99 | |||
| fe2ccc43e2 | |||
| 030d9d2dbf | |||
| 011c3147dc | |||
| c0281a41de | |||
| 02ded280af | |||
| d6ba807878 | |||
| 8dc57697bf | |||
| 3c12d7ddb5 | |||
| c851968def | |||
| 626a5b03a1 | |||
| 7da19e215f | |||
| 7a11cb35eb | |||
| d2981c59cd | |||
| e4aa46e7fe | |||
| c56aea9dc8 | |||
| 86bab3977e | |||
| 2b03eb6c39 | |||
| 7f33118758 | |||
| 1bea3fc006 | |||
| 9608615542 | |||
| 9ddff5d7a5 | |||
| e623134867 | |||
| 77e428c9a1 | |||
| 03c951c4a4 | |||
| d9fb4a4234 | |||
| 571aff3eb1 | |||
| 7b1e96300f | |||
| 0f00705da7 | |||
| 9d9f2c2a75 | |||
| ce2fa71356 | |||
| e315006136 | |||
| a2bf4f3452 | |||
| f9a2d1153a | |||
| 33f9cef572 | |||
| c3a1c8d7ff | |||
| 565956e966 | |||
| 4eb84611fe | |||
| 69f3c01442 | |||
| f205b611b5 | |||
| fad22b3631 | |||
| 176309152f | |||
| 1bf0e75bc1 | |||
| 8266095feb | |||
| e2843c06ba | |||
| 28d5f5fc39 | |||
| a6b21ec78f | |||
| 62bac5aa0e | |||
| 843b10851f | |||
| c5f15b5c6a | |||
| ebd8baba00 | |||
| 464ae528b1 | |||
| 884de6f4c3 | |||
| 414b1a36c7 | |||
| 8b1168b621 | |||
| 8526cc1ba3 | |||
| 5870b6cd1f | |||
| 7b2f7d7e85 | |||
| cbff654273 | |||
| 7cad0e9e06 | |||
| a7471bba03 | |||
| 44c11a7803 | |||
| 210935c4c6 | |||
| 412cbda546 | |||
| 553e80d12e | |||
| 5dd3af6a0a | |||
| 879d97a93b | |||
| c5325bb61e | |||
| 5a708e86d0 | |||
| 3a52fd76e5 | |||
| 4e24239322 | |||
| d2bdc159bd | |||
| 49e804eb50 | |||
| 209564d2d2 | |||
| 56b42f7f8a | |||
| fb1a01c37f | |||
| 1d1c53e2d5 | |||
| 0604e61151 | |||
| 19f69717de | |||
| f8232ed24a | |||
| efafa4e9e6 | |||
| 5a7856cb26 | |||
| 48130761d7 | |||
| 95daa0251f | |||
| 31c3efa9bc | |||
| 34875f0011 | |||
| 78c61e45c5 | |||
| 4f78eeed28 | |||
| 8b80e916a7 | |||
| b810238038 | |||
| 7808826773 | |||
| 782f89510b | |||
| ec8147408b | |||
| 92d89ea508 | |||
| 1ebae688db | |||
| 927370ebfc | |||
| 7afe53fb81 | |||
| 9eb1c06659 | |||
| f6e7f67faa | |||
| f96d677d4a | |||
| dd115f186f | |||
| 344079de86 | |||
| 47c53e90b4 | |||
| bd07fa6abb | |||
| 8f1d3636d5 | |||
| 07886362c5 | |||
| 85c58d5220 | |||
| 203fdbcc84 | |||
| a47e9ae043 | |||
| 6c09b387a3 | |||
| 8969d18c91 | |||
| 828bc67064 | |||
| de01c76882 | |||
| 7be9a851c3 | |||
| 38e83e3401 | |||
| bcda13f128 | |||
| 121ba27204 | |||
| 572c915b06 | |||
| 520a4016db | |||
| 16b85e42ac |
@@ -54,6 +54,7 @@ def main():
|
||||
ap.add_argument("--variant", action="store")
|
||||
ap.add_argument("--sign", action="store_true", default=True)
|
||||
ap.add_argument("--nosign", action="store_false", dest="sign")
|
||||
ap.add_argument("--vc-version-only", action="store_true")
|
||||
|
||||
args = ap.parse_args()
|
||||
|
||||
@@ -86,6 +87,9 @@ def main():
|
||||
with open("renpy/vc_version.py", "w") as f:
|
||||
f.write("vc_version = {}".format(vc_version))
|
||||
|
||||
if args.vc_version_only:
|
||||
return
|
||||
|
||||
try:
|
||||
reload(sys.modules['renpy.vc_version']) # @UndefinedVariable
|
||||
except:
|
||||
|
||||
@@ -843,7 +843,7 @@ style radio_vbox:
|
||||
|
||||
style radio_button:
|
||||
properties gui.button_properties("radio_button")
|
||||
foreground "gui/button/check_[prefix_]foreground.png"
|
||||
foreground "gui/button/radio_[prefix_]foreground.png"
|
||||
|
||||
style radio_button_text:
|
||||
properties gui.button_text_properties("radio_button")
|
||||
@@ -1441,7 +1441,7 @@ style window:
|
||||
|
||||
style radio_button:
|
||||
variant "small"
|
||||
foreground "gui/phone/button/check_[prefix_]foreground.png"
|
||||
foreground "gui/phone/button/radio_[prefix_]foreground.png"
|
||||
|
||||
style check_button:
|
||||
variant "small"
|
||||
|
||||
@@ -373,7 +373,7 @@ init python in distribute:
|
||||
This manages the process of building distributions.
|
||||
"""
|
||||
|
||||
def __init__(self, project, destination=None, reporter=None, packages=None, build_update=True, open_directory=False, noarchive=False, packagedest=None, report_success=True):
|
||||
def __init__(self, project, destination=None, reporter=None, packages=None, build_update=True, open_directory=False, noarchive=False, packagedest=None, report_success=True, scan=True):
|
||||
"""
|
||||
Distributes `project`.
|
||||
|
||||
@@ -441,8 +441,9 @@ init python in distribute:
|
||||
# dictionaries.
|
||||
data = project.data
|
||||
|
||||
self.reporter.info(_("Scanning project files..."))
|
||||
project.update_dump(force=True, gui=False, compile=project.data['force_recompile'])
|
||||
if scan:
|
||||
self.reporter.info(_("Scanning project files..."))
|
||||
project.update_dump(force=True, gui=False, compile=project.data['force_recompile'])
|
||||
|
||||
if project.dump.get("error", False):
|
||||
raise Exception("Could not get build data from the project. Please ensure the project runs.")
|
||||
@@ -523,6 +524,10 @@ init python in distribute:
|
||||
# Build the mac app and windows exes.
|
||||
self.add_mac_files()
|
||||
self.add_windows_files()
|
||||
self.add_main_py()
|
||||
|
||||
# Add the main.py.
|
||||
self.add_main_py()
|
||||
|
||||
# Add generated/special files.
|
||||
if build['renpy']:
|
||||
@@ -805,6 +810,7 @@ init python in distribute:
|
||||
|
||||
self.add_file_list_hash("rapt")
|
||||
self.add_file_list_hash("renios")
|
||||
self.add_file_list_hash("web")
|
||||
|
||||
tmp_fn = self.temp_filename("renpy.py")
|
||||
|
||||
@@ -965,7 +971,7 @@ init python in distribute:
|
||||
old_exe_fn = os.path.join(config.renpy_base, "renpy.exe")
|
||||
old_main_fn = os.path.join(config.renpy_base, "lib/windows-i686/renpy.exe")
|
||||
|
||||
if os.path.exists(icon_fn):
|
||||
if os.path.exists(icon_fn) and os.path.exists(old_exe_fn):
|
||||
exe_fn = self.temp_filename("renpy.exe")
|
||||
main_fn = self.temp_filename("main.exe")
|
||||
|
||||
@@ -979,8 +985,16 @@ init python in distribute:
|
||||
exe_fn = old_exe_fn
|
||||
main_fn = old_main_fn
|
||||
|
||||
self.add_file(windows, self.exe, exe_fn)
|
||||
self.add_file(windows, "lib/windows-i686/" + self.exe, main_fn)
|
||||
if os.path.exists(exe_fn):
|
||||
self.add_file(windows, self.exe, exe_fn)
|
||||
self.add_file(windows, "lib/windows-i686/" + self.exe, main_fn)
|
||||
|
||||
def add_main_py(self):
|
||||
if self.build['renpy']:
|
||||
return
|
||||
|
||||
self.add_file("web", "main.py", os.path.join(config.renpy_base, "renpy.py"))
|
||||
|
||||
|
||||
def mark_executable(self):
|
||||
"""
|
||||
|
||||
@@ -249,6 +249,7 @@ screen front_page_project:
|
||||
|
||||
textbutton _("Android") action Jump("android")
|
||||
textbutton _("iOS") action Jump("ios")
|
||||
textbutton _("Web") + " " + _("(Beta)") action Jump("web")
|
||||
textbutton _("Generate Translations") action Jump("translate")
|
||||
textbutton _("Extract Dialogue") action Jump("extract_dialogue")
|
||||
|
||||
|
||||
+15
-1
@@ -58,6 +58,7 @@ init python:
|
||||
|
||||
if RENIOS_PATH:
|
||||
import renios.create
|
||||
import renios.image
|
||||
|
||||
def IOSState():
|
||||
if not RENIOS_PATH:
|
||||
@@ -119,6 +120,10 @@ init python:
|
||||
return os.path.join(persistent.xcode_projects_directory, xcode_name(p.name))
|
||||
|
||||
def ios_create(p=None, gui=True):
|
||||
project.current.update_dump(force=True)
|
||||
|
||||
name = project.current.dump.get("name", None)
|
||||
version = project.current.dump.get("version", None)
|
||||
|
||||
dest = xcode_project(p)
|
||||
|
||||
@@ -135,7 +140,7 @@ init python:
|
||||
os.rename(dest, backup)
|
||||
|
||||
iface = MobileInterface("ios")
|
||||
renios.create.create_project(iface, dest)
|
||||
renios.create.create_project(iface, dest, name, version)
|
||||
|
||||
ios_populate(p, gui=gui)
|
||||
|
||||
@@ -187,6 +192,15 @@ init python:
|
||||
|
||||
os.unlink(py_fn)
|
||||
|
||||
ios_image(p, "ios-icon.png", "Media.xcassets/AppIcon.appiconset", True)
|
||||
ios_image(p, "ios-launchimage.png", "Media.xcassets/LaunchImage.launchimage", False)
|
||||
|
||||
def ios_image(p, source, destination, scale):
|
||||
source = os.path.join(p.path, source)
|
||||
destination = os.path.join(xcode_project(p), destination)
|
||||
|
||||
renios.image.generate(source, destination, scale)
|
||||
|
||||
|
||||
def launch_xcode():
|
||||
dist = xcode_project(None)
|
||||
|
||||
+48
-14
@@ -23,23 +23,57 @@ init python:
|
||||
|
||||
def find_itch_butler():
|
||||
|
||||
import os
|
||||
|
||||
if renpy.windows:
|
||||
rv = os.path.join(os.environ.get("APPDATA", ""), "Roaming", "itch", "bin", "butler.exe")
|
||||
|
||||
if not os.path.exists(rv):
|
||||
rv = os.path.join(os.environ.get("APPDATA", ""), "itch", "bin", "butler.exe")
|
||||
|
||||
platform = "windows-amd64"
|
||||
exe = "butler.exe"
|
||||
elif renpy.macintosh:
|
||||
rv = os.path.join(os.environ.get("HOME", ""), "Library", "Application Support", "itch", "bin", "butler")
|
||||
plaform = "darwin-amd64"
|
||||
exe = "butler"
|
||||
else:
|
||||
rv = os.path.join(os.environ.get("HOME", ""), ".config", "itch", "bin", "butler")
|
||||
platform = "linux-amd64"
|
||||
exe = "butler"
|
||||
|
||||
if not os.path.exists(rv):
|
||||
return None
|
||||
dn = os.path.join(config.renpy_base, "tmp", "butler-" + platform)
|
||||
zip = os.path.join(dn, "butler.zip")
|
||||
exe = os.path.join(dn, exe)
|
||||
|
||||
if os.path.exists(exe):
|
||||
return exe
|
||||
|
||||
interface.processing(_("Downloading the itch.io butler."))
|
||||
|
||||
try:
|
||||
os.makedirs(dn)
|
||||
except:
|
||||
pass
|
||||
|
||||
import urllib2
|
||||
import ssl
|
||||
|
||||
with interface.error_handling("Downloading the itch.io butler."):
|
||||
context = ssl._create_unverified_context()
|
||||
response = urllib2.urlopen("https://broth.itch.ovh/butler/{}/LATEST/archive/default".format(platform), context=context)
|
||||
|
||||
with open(zip, "wb") as f:
|
||||
while True:
|
||||
data = response.read(1024 * 1024)
|
||||
if not data:
|
||||
break
|
||||
|
||||
f.write(data)
|
||||
|
||||
import zipfile
|
||||
|
||||
with zipfile.ZipFile(zip) as zf:
|
||||
zf.extractall(dn)
|
||||
|
||||
try:
|
||||
os.chmod(exe, 0o755)
|
||||
except:
|
||||
pass
|
||||
|
||||
return exe
|
||||
|
||||
return rv
|
||||
|
||||
label itch:
|
||||
|
||||
@@ -48,6 +82,8 @@ label itch:
|
||||
|
||||
python hide:
|
||||
|
||||
butler = find_itch_butler()
|
||||
|
||||
build = project.current.dump["build"]
|
||||
|
||||
destination = build["destination"]
|
||||
@@ -100,8 +136,6 @@ label itch:
|
||||
label="build_distributions"
|
||||
)
|
||||
|
||||
butler = find_itch_butler()
|
||||
|
||||
if not butler:
|
||||
interface.error(
|
||||
_("The butler program was not found."),
|
||||
|
||||
@@ -3744,7 +3744,7 @@ class PE:
|
||||
|
||||
offset = self.get_physical_by_rva(rva)
|
||||
if not offset:
|
||||
raise False
|
||||
return False
|
||||
|
||||
return self.set_bytes_at_offset(offset, data)
|
||||
|
||||
|
||||
@@ -109,10 +109,10 @@ init 1 python:
|
||||
SEPARATOR = Frame(PATTERN, 0, 0, tile=True, ymaximum=5, yalign=1.0)
|
||||
SEPARATOR2 = Frame(PATTERN, 0, 0, tile=True, ymaximum=10, yalign=1.0)
|
||||
|
||||
SPACER_HEIGHT = 12
|
||||
SPACER_HEIGHT = 8
|
||||
SPACER = Null(height=SPACER_HEIGHT)
|
||||
|
||||
HALF_SPACER_HEIGHT = 6
|
||||
HALF_SPACER_HEIGHT = 4
|
||||
HALF_SPACER = Null(height=HALF_SPACER_HEIGHT)
|
||||
|
||||
# DIVIDING THE SCREEN
|
||||
|
||||
@@ -647,4 +647,91 @@ translate spanish strings:
|
||||
|
||||
# 00gallery.rpy:596
|
||||
old "return"
|
||||
new "volver"
|
||||
new "volver"
|
||||
|
||||
translate spanish strings:
|
||||
|
||||
# 00accessibility.rpy:76
|
||||
old "Font Override"
|
||||
new "Invalidar Fuente"
|
||||
|
||||
# 00accessibility.rpy:80
|
||||
old "Default"
|
||||
new "por Defecto"
|
||||
|
||||
# 00accessibility.rpy:84
|
||||
old "DejaVu Sans"
|
||||
new "DejaVu Sans"
|
||||
|
||||
# 00accessibility.rpy:88
|
||||
old "Opendyslexic"
|
||||
new "Opendyslexic"
|
||||
|
||||
# 00accessibility.rpy:94
|
||||
old "Text Size Scaling"
|
||||
new "Tamaño de la Escala del Texto"
|
||||
|
||||
# 00accessibility.rpy:100
|
||||
old "Reset"
|
||||
new "Reiniciar"
|
||||
|
||||
# 00accessibility.rpy:105
|
||||
old "Line Spacing Scaling"
|
||||
new "Escalado del espaciado de línea"
|
||||
|
||||
# 00accessibility.rpy:117
|
||||
old "Self-Voicing"
|
||||
new "Voz-automática"
|
||||
|
||||
# 00accessibility.rpy:121
|
||||
old "Off"
|
||||
new "Apagado"
|
||||
|
||||
# 00accessibility.rpy:125
|
||||
old "Text-to-speech"
|
||||
new "Texto a voz"
|
||||
|
||||
# 00accessibility.rpy:129
|
||||
old "Clipboard"
|
||||
new "Potapapeles"
|
||||
|
||||
# 00accessibility.rpy:133
|
||||
old "Debug"
|
||||
new "Depurar"
|
||||
|
||||
# 00action_file.rpy:434
|
||||
old "Load slot %s: [text]"
|
||||
new "Cargar ranura %s: [text]"
|
||||
|
||||
# 00preferences.rpy:430
|
||||
old "font transform"
|
||||
new "transformación de fuente"
|
||||
|
||||
# 00preferences.rpy:433
|
||||
old "font size"
|
||||
new "tamaño de fuente"
|
||||
|
||||
# 00preferences.rpy:441
|
||||
old "font line spacing"
|
||||
new "fuente de espacio de líneas"
|
||||
|
||||
# _developer\developer.rpym:63
|
||||
old "Image Attributes"
|
||||
new "Atrubutos de Imagen"
|
||||
|
||||
# _developer\developer.rpym:90
|
||||
old "[name] [attributes] (hidden)"
|
||||
new "[name] [attributes] (hidden)"
|
||||
|
||||
# _developer\developer.rpym:94
|
||||
old "[name] [attributes]"
|
||||
new "[name] [attributes]"
|
||||
|
||||
# _developer\developer.rpym:154
|
||||
old "Hide deleted"
|
||||
new "Hide deleted"
|
||||
|
||||
# _developer\developer.rpym:154
|
||||
old "Show deleted"
|
||||
new "Show deleted"
|
||||
|
||||
|
||||
@@ -187,4 +187,15 @@ translate spanish strings:
|
||||
|
||||
# 00console.rpy:639
|
||||
old "jump <label>: jumps to label"
|
||||
new "jump <label>: salta a la etiqueta"
|
||||
new "jump <label>: salta a la etiqueta"
|
||||
|
||||
translate spanish strings:
|
||||
|
||||
# 00console.rpy:680
|
||||
old "short: Shorten the representation of objects on the console (default)."
|
||||
new "corto: Acorta la representación de los objetos en la consola (predeterminado)."
|
||||
|
||||
# 00console.rpy:685
|
||||
old "long: Print the full representation of objects on the console."
|
||||
new "largo: imprime la representación completa de los objetos en la consola."
|
||||
|
||||
|
||||
@@ -211,4 +211,31 @@ translate spanish strings:
|
||||
|
||||
# _errorhandling.rpym:667
|
||||
old "Copies the errors.txt file to the clipboard."
|
||||
new "Copia el archivo errors.txt al portapapeles."
|
||||
new "Copia el archivo errors.txt al portapapeles."
|
||||
|
||||
translate spanish strings:
|
||||
|
||||
# _errorhandling.rpym:542
|
||||
old "Copy BBCode"
|
||||
new "Copiar BBCode"
|
||||
|
||||
# _errorhandling.rpym:544
|
||||
old "Copies the traceback.txt file to the clipboard as BBcode for forums like https://lemmasoft.renai.us/."
|
||||
new "Copia el archivo traceback.txt en el portapapeles como BBcode para foros como https://lemmasoft.renai.us/."
|
||||
|
||||
# _errorhandling.rpym:546
|
||||
old "Copy Markdown"
|
||||
new "Copiar Markdown"
|
||||
|
||||
# _errorhandling.rpym:548
|
||||
old "Copies the traceback.txt file to the clipboard as Markdown for Discord."
|
||||
new "Copia el archivo traceback.txt al portapapeles como Markdown paara Discord."
|
||||
|
||||
# _errorhandling.rpym:683
|
||||
old "Copies the errors.txt file to the clipboard as BBcode for forums like https://lemmasoft.renai.us/."
|
||||
new "Copia el archivo errors.txt en el portapapeles como BBcode para foros como https://lemmasoft.renai.us/."
|
||||
|
||||
# _errorhandling.rpym:687
|
||||
old "Copies the errors.txt file to the clipboard as Markdown for Discord."
|
||||
new "Copia el archivo errors.txt al portapapeles como Markdown para Discord."
|
||||
|
||||
|
||||
@@ -0,0 +1,217 @@
|
||||
# Copyright 2004-2019 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 launcher support for creating RenPyWeb projects,
|
||||
# other than the logic in the standard distribute code.
|
||||
|
||||
|
||||
init python:
|
||||
|
||||
import shutil
|
||||
import webserver
|
||||
|
||||
WEB_PATH = None
|
||||
|
||||
def find_web():
|
||||
|
||||
global WEB_PATH
|
||||
|
||||
candidates = [ ]
|
||||
|
||||
WEB_PATH = os.path.join(config.renpy_base, "web")
|
||||
|
||||
if os.path.isdir(WEB_PATH) and check_hash_txt("web"):
|
||||
pass
|
||||
else:
|
||||
WEB_PATH = None
|
||||
|
||||
find_web()
|
||||
|
||||
def get_web_destination(p):
|
||||
"""
|
||||
Returns the path to the desint
|
||||
"""
|
||||
|
||||
build = p.dump['build']
|
||||
|
||||
base_name = build['directory_name']
|
||||
destination = build["destination"]
|
||||
|
||||
parent = os.path.dirname(p.path)
|
||||
destination = os.path.join(parent, destination, base_name + "-web")
|
||||
|
||||
return destination
|
||||
|
||||
def build_web(p, gui=True):
|
||||
|
||||
# Figure out the reporter to use.
|
||||
if gui:
|
||||
reporter = distribute.GuiReporter()
|
||||
else:
|
||||
reporter = distribute.TextReporter()
|
||||
|
||||
# Determine details.
|
||||
p.update_dump(True, gui=gui)
|
||||
|
||||
destination = get_web_destination(p)
|
||||
display_name = p.dump['build']['display_name']
|
||||
|
||||
# Clean the folder, then remale it.
|
||||
if os.path.exists(destination):
|
||||
shutil.rmtree(destination)
|
||||
|
||||
os.makedirs(destination, 0o777)
|
||||
|
||||
# Use the distributor to make game.zip.
|
||||
distribute.Distributor(p, packages=[ "web" ], packagedest=os.path.join(destination, "game"), reporter=reporter, noarchive=True, scan=False)
|
||||
|
||||
# Copy the files from WEB_PATH to destination.
|
||||
for fn in os.listdir(WEB_PATH):
|
||||
if fn in { "game.zip", "hash.txt", "index.html" }:
|
||||
continue
|
||||
|
||||
shutil.copy(os.path.join(WEB_PATH, fn), os.path.join(destination, fn))
|
||||
|
||||
# Copy over index.html.
|
||||
with open(os.path.join(WEB_PATH, "index.html")) as f:
|
||||
html = f.read()
|
||||
|
||||
html = html.replace("%%TITLE%%", display_name.encode("utf-8"))
|
||||
|
||||
with open(os.path.join(destination, "index.html"), "w") as f:
|
||||
f.write(html)
|
||||
|
||||
webserver.start(destination)
|
||||
|
||||
|
||||
def launch_web():
|
||||
renpy.run(OpenURL("http://127.0.0.1:8042/index.html"))
|
||||
|
||||
screen web():
|
||||
|
||||
frame:
|
||||
style_group "l"
|
||||
style "l_root"
|
||||
|
||||
window:
|
||||
|
||||
has vbox
|
||||
|
||||
label _("Web: [project.current.display_name!q]")
|
||||
|
||||
add HALF_SPACER
|
||||
|
||||
|
||||
hbox:
|
||||
|
||||
# Left side.
|
||||
frame:
|
||||
style "l_indent"
|
||||
xmaximum ONEHALF
|
||||
xfill True
|
||||
|
||||
has vbox
|
||||
|
||||
add SEPARATOR2
|
||||
add HALF_SPACER
|
||||
|
||||
frame:
|
||||
style "l_indent"
|
||||
has vbox
|
||||
|
||||
text _("Build:")
|
||||
|
||||
add HALF_SPACER
|
||||
|
||||
frame style "l_indent":
|
||||
|
||||
has vbox
|
||||
|
||||
textbutton _("Build Web Application") action Jump("web_build")
|
||||
textbutton _("Build and Open in Browser") action Jump("web_launch")
|
||||
textbutton _("Open without Build") action Jump("web_start")
|
||||
|
||||
add SPACER
|
||||
|
||||
text _("Support:")
|
||||
|
||||
add HALF_SPACER
|
||||
|
||||
frame style "l_indent":
|
||||
|
||||
has vbox
|
||||
|
||||
textbutton _("RenPyWeb Home") action OpenURL("https://renpy.beuc.net/")
|
||||
textbutton _("Beuc's Patreon") action OpenURL("https://www.beuc.net/donate/")
|
||||
|
||||
|
||||
# Right side.
|
||||
frame:
|
||||
style "l_indent"
|
||||
xmaximum ONEHALF
|
||||
xfill True
|
||||
|
||||
has vbox
|
||||
|
||||
add SEPARATOR2
|
||||
add HALF_SPACER
|
||||
|
||||
frame:
|
||||
style "l_indent"
|
||||
has vbox
|
||||
|
||||
text _("Ren'Py web applications require the entire game to be downloaded to the player's computer before it can start.")
|
||||
|
||||
add SPACER
|
||||
|
||||
text _("Current limitations in the web platform mean that loading large images, audio files, or movies may cause audio or framerate glitches, and lower performance in general.")
|
||||
|
||||
textbutton _("Return") action Jump("front_page") style "l_left_button"
|
||||
|
||||
|
||||
|
||||
label web:
|
||||
|
||||
if WEB_PATH is None:
|
||||
$ interface.yesno(_("Before packaging web apps, you'll need to download RenPyWeb, Ren'Py's web support. Would you like to download RenPyWeb now?"), no=Jump("front_page"))
|
||||
$ add_dlc("web", restart=True)
|
||||
|
||||
call screen web
|
||||
|
||||
jump front_page
|
||||
|
||||
|
||||
label web_build:
|
||||
$ build_web(project.current, gui=True)
|
||||
jump web
|
||||
|
||||
|
||||
label web_launch:
|
||||
$ build_web(project.current, gui=True)
|
||||
$ launch_web()
|
||||
jump web
|
||||
|
||||
|
||||
label web_start:
|
||||
$ project.current.update_dump(True, gui=True)
|
||||
$ webserver.start(get_web_destination(project.current))
|
||||
$ launch_web()
|
||||
jump web
|
||||
@@ -0,0 +1,253 @@
|
||||
import BaseHTTPServer
|
||||
import threading
|
||||
|
||||
import posixpath
|
||||
import urllib
|
||||
import os
|
||||
import os
|
||||
import posixpath
|
||||
import BaseHTTPServer
|
||||
import urllib
|
||||
import urlparse
|
||||
import cgi
|
||||
import sys
|
||||
import shutil
|
||||
import mimetypes
|
||||
|
||||
try:
|
||||
from cStringIO import StringIO
|
||||
except ImportError:
|
||||
from StringIO import StringIO
|
||||
|
||||
# The path to the root of the web server.
|
||||
root = ""
|
||||
|
||||
import renpy
|
||||
|
||||
|
||||
class WebHandler(BaseHTTPServer.BaseHTTPRequestHandler):
|
||||
|
||||
"""Simple HTTP request handler with GET and HEAD commands.
|
||||
|
||||
This serves files from the current directory and any of its
|
||||
subdirectories. The MIME type for files is determined by
|
||||
calling the .guess_type() method.
|
||||
|
||||
The GET and HEAD requests are identical except that the HEAD
|
||||
request omits the actual contents of the file.
|
||||
|
||||
"""
|
||||
|
||||
server_version = "Ren'Py/" + renpy.version_only # @UndefinedVariable
|
||||
|
||||
def do_GET(self):
|
||||
"""Serve a GET request."""
|
||||
f = self.send_head()
|
||||
if f:
|
||||
try:
|
||||
self.copyfile(f, self.wfile)
|
||||
finally:
|
||||
f.close()
|
||||
|
||||
def do_HEAD(self):
|
||||
"""Serve a HEAD request."""
|
||||
f = self.send_head()
|
||||
if f:
|
||||
f.close()
|
||||
|
||||
def send_head(self):
|
||||
"""Common code for GET and HEAD commands.
|
||||
|
||||
This sends the response code and MIME headers.
|
||||
|
||||
Return value is either a file object (which has to be copied
|
||||
to the outputfile by the caller unless the command was HEAD,
|
||||
and must be closed by the caller under all circumstances), or
|
||||
None, in which case the caller has nothing further to do.
|
||||
|
||||
"""
|
||||
path = self.translate_path(self.path)
|
||||
f = None
|
||||
if os.path.isdir(path):
|
||||
parts = urlparse.urlsplit(self.path)
|
||||
if not parts.path.endswith('/'):
|
||||
# redirect browser - doing basically what apache does
|
||||
self.send_response(301)
|
||||
new_parts = (parts[0], parts[1], parts[2] + '/',
|
||||
parts[3], parts[4])
|
||||
new_url = urlparse.urlunsplit(new_parts)
|
||||
self.send_header("Location", new_url)
|
||||
self.end_headers()
|
||||
return None
|
||||
for index in "index.html", "index.htm":
|
||||
index = os.path.join(path, index)
|
||||
if os.path.exists(index):
|
||||
path = index
|
||||
break
|
||||
else:
|
||||
return self.list_directory(path)
|
||||
ctype = self.guess_type(path)
|
||||
try:
|
||||
# Always read in binary mode. Opening files in text mode may cause
|
||||
# newline translations, making the actual size of the content
|
||||
# transmitted *less* than the content-length!
|
||||
f = open(path, 'rb')
|
||||
except IOError:
|
||||
self.send_error(404, "File not found")
|
||||
return None
|
||||
try:
|
||||
self.send_response(200)
|
||||
self.send_header("Content-type", ctype)
|
||||
fs = os.fstat(f.fileno())
|
||||
|
||||
# Ren'Py - bust cache.
|
||||
self.send_header("Pragma", "no-cache")
|
||||
|
||||
self.send_header("Content-Length", str(fs[6]))
|
||||
self.send_header("Last-Modified", self.date_time_string(fs.st_mtime))
|
||||
self.end_headers()
|
||||
return f
|
||||
except:
|
||||
f.close()
|
||||
raise
|
||||
|
||||
def list_directory(self, path):
|
||||
"""Helper to produce a directory listing (absent index.html).
|
||||
|
||||
Return value is either a file object, or None (indicating an
|
||||
error). In either case, the headers are sent, making the
|
||||
interface the same as for send_head().
|
||||
|
||||
"""
|
||||
try:
|
||||
list = os.listdir(path)
|
||||
except os.error:
|
||||
self.send_error(404, "No permission to list directory")
|
||||
return None
|
||||
list.sort(key=lambda a: a.lower())
|
||||
f = StringIO()
|
||||
displaypath = cgi.escape(urllib.unquote(self.path))
|
||||
f.write('<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">')
|
||||
f.write("<html>\n<title>Directory listing for %s</title>\n" % displaypath)
|
||||
f.write("<body>\n<h2>Directory listing for %s</h2>\n" % displaypath)
|
||||
f.write("<hr>\n<ul>\n")
|
||||
for name in list:
|
||||
fullname = os.path.join(path, name)
|
||||
displayname = linkname = name
|
||||
# Append / for directories or @ for symbolic links
|
||||
if os.path.isdir(fullname):
|
||||
displayname = name + "/"
|
||||
linkname = name + "/"
|
||||
if os.path.islink(fullname):
|
||||
displayname = name + "@"
|
||||
# Note: a link to a directory displays with @ and links with /
|
||||
f.write('<li><a href="%s">%s</a>\n'
|
||||
% (urllib.quote(linkname), cgi.escape(displayname)))
|
||||
f.write("</ul>\n<hr>\n</body>\n</html>\n")
|
||||
length = f.tell()
|
||||
f.seek(0)
|
||||
self.send_response(200)
|
||||
encoding = sys.getfilesystemencoding()
|
||||
self.send_header("Content-type", "text/html; charset=%s" % encoding)
|
||||
self.send_header("Content-Length", str(length))
|
||||
self.end_headers()
|
||||
return f
|
||||
|
||||
def translate_path(self, path):
|
||||
"""Translate a /-separated PATH to the local filename syntax.
|
||||
|
||||
Components that mean special things to the local file system
|
||||
(e.g. drive or directory names) are ignored. (XXX They should
|
||||
probably be diagnosed.)
|
||||
|
||||
"""
|
||||
# abandon query parameters
|
||||
path = path.split('?', 1)[0]
|
||||
path = path.split('#', 1)[0]
|
||||
# Don't forget explicit trailing slash when normalizing. Issue17324
|
||||
trailing_slash = path.rstrip().endswith('/')
|
||||
path = posixpath.normpath(urllib.unquote(path))
|
||||
words = path.split('/')
|
||||
words = filter(None, words)
|
||||
|
||||
# Ren'Py - use the root we were given.
|
||||
path = root
|
||||
|
||||
for word in words:
|
||||
if os.path.dirname(word) or word in (os.curdir, os.pardir):
|
||||
# Ignore components that are not a simple file/directory name
|
||||
continue
|
||||
path = os.path.join(path, word)
|
||||
if trailing_slash:
|
||||
path += '/'
|
||||
return path
|
||||
|
||||
def copyfile(self, source, outputfile):
|
||||
"""Copy all data between two file objects.
|
||||
|
||||
The SOURCE argument is a file object open for reading
|
||||
(or anything with a read() method) and the DESTINATION
|
||||
argument is a file object open for writing (or
|
||||
anything with a write() method).
|
||||
|
||||
The only reason for overriding this would be to change
|
||||
the block size or perhaps to replace newlines by CRLF
|
||||
-- note however that this the default server uses this
|
||||
to copy binary data as well.
|
||||
|
||||
"""
|
||||
shutil.copyfileobj(source, outputfile)
|
||||
|
||||
def guess_type(self, path):
|
||||
"""Guess the type of a file.
|
||||
|
||||
Argument is a PATH (a filename).
|
||||
|
||||
Return value is a string of the form type/subtype,
|
||||
usable for a MIME Content-type header.
|
||||
|
||||
The default implementation looks the file's extension
|
||||
up in the table self.extensions_map, using application/octet-stream
|
||||
as a default; however it would be permissible (if
|
||||
slow) to look inside the data to make a better guess.
|
||||
|
||||
"""
|
||||
|
||||
base, ext = posixpath.splitext(path)
|
||||
if ext in self.extensions_map:
|
||||
return self.extensions_map[ext]
|
||||
ext = ext.lower()
|
||||
if ext in self.extensions_map:
|
||||
return self.extensions_map[ext]
|
||||
else:
|
||||
return self.extensions_map['']
|
||||
|
||||
extensions_map = {
|
||||
'': 'application/octet-stream', # Default
|
||||
'.gz': 'application/gzip',
|
||||
'.htm': 'text/html',
|
||||
'.html': 'text/html',
|
||||
'.js': 'application/javascript',
|
||||
}
|
||||
|
||||
|
||||
def run():
|
||||
server = BaseHTTPServer.HTTPServer(("127.0.0.1", 8042), WebHandler)
|
||||
server.serve_forever()
|
||||
|
||||
|
||||
def start(root_):
|
||||
|
||||
print("----")
|
||||
|
||||
global root
|
||||
|
||||
started = root
|
||||
root = root_
|
||||
|
||||
if started:
|
||||
return
|
||||
|
||||
t = threading.Thread(target=run)
|
||||
t.daemon = True
|
||||
t.start()
|
||||
+182
-5
@@ -10,9 +10,18 @@
|
||||
|
||||
#if defined(__arm__) && !(__MACOS__ || __IPHONEOS__)
|
||||
#define USE_MEMALIGN
|
||||
#include <malloc.h>
|
||||
#include <stdlib.h>
|
||||
#endif
|
||||
|
||||
/* Should a mono channel be split into two equal stero channels (true) or
|
||||
* should the energy be split onto two stereo channels with 1/2 the energy
|
||||
* (false).
|
||||
*/
|
||||
static int audio_equal_mono = 1;
|
||||
|
||||
/* The weight of stereo channels when audio_equal_mono is true. */
|
||||
static float stereo_matrix[] = { 1.0, 1.0 };
|
||||
|
||||
/* The output audio sample rate. */
|
||||
static int audio_sample_rate = 44100;
|
||||
|
||||
@@ -564,6 +573,10 @@ static void decode_audio(MediaState *ms) {
|
||||
|
||||
if (!ms->audio_decode_frame->channel_layout) {
|
||||
ms->audio_decode_frame->channel_layout = av_get_default_channel_layout(ms->audio_decode_frame->channels);
|
||||
|
||||
if (audio_equal_mono && (ms->audio_decode_frame->channels == 1)) {
|
||||
swr_set_matrix(ms->swr, stereo_matrix, 1);
|
||||
}
|
||||
}
|
||||
|
||||
converted_frame = av_frame_alloc();
|
||||
@@ -723,7 +736,7 @@ static SurfaceQueueEntry *decode_video_frame(MediaState *ms) {
|
||||
|
||||
|
||||
#ifdef USE_MEMALIGN
|
||||
rv->pixels = memalign(16, rv->pitch * rv->h);
|
||||
posix_memalign(&rv->pixels, 16, rv->pitch * rv->h);
|
||||
memset(rv->pixels, 0, rv->pitch * rv->h);
|
||||
#else
|
||||
rv->pixels = SDL_calloc(1, rv->pitch * rv->h);
|
||||
@@ -788,10 +801,16 @@ static void decode_video(MediaState *ms) {
|
||||
}
|
||||
|
||||
|
||||
static int decode_sync_start(void *arg);
|
||||
void media_read_sync(struct MediaState *ms);
|
||||
void media_read_sync_finish(struct MediaState *ms);
|
||||
|
||||
|
||||
/**
|
||||
* Returns 1 if there is a video frame ready on this channel, or 0 otherwise.
|
||||
*/
|
||||
int media_video_ready(struct MediaState *ms) {
|
||||
|
||||
int consumed = 0;
|
||||
int rv = 0;
|
||||
|
||||
@@ -857,6 +876,7 @@ done:
|
||||
|
||||
|
||||
SDL_Surface *media_read_video(MediaState *ms) {
|
||||
|
||||
SDL_Surface *rv = NULL;
|
||||
SurfaceQueueEntry *sqe = NULL;
|
||||
|
||||
@@ -866,9 +886,11 @@ SDL_Surface *media_read_video(MediaState *ms) {
|
||||
|
||||
SDL_LockMutex(ms->lock);
|
||||
|
||||
#ifndef __EMSCRIPTEN__
|
||||
while (!ms->ready) {
|
||||
SDL_CondWait(ms->cond, ms->lock);
|
||||
SDL_CondWait(ms->cond, ms->lock);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!ms->surface_queue_size) {
|
||||
goto done;
|
||||
@@ -1042,7 +1064,148 @@ finish:
|
||||
}
|
||||
|
||||
|
||||
void media_read_sync_finish(struct MediaState *ms) {
|
||||
// copy/paste from end of decode_thread
|
||||
|
||||
/* Data used by the decoder should be freed here, while data shared with
|
||||
* the readers should be freed in media_close.
|
||||
*/
|
||||
|
||||
SDL_LockMutex(ms->lock);
|
||||
|
||||
/* Ensures that every stream becomes ready. */
|
||||
if (!ms->ready) {
|
||||
ms->ready = 1;
|
||||
SDL_CondBroadcast(ms->cond);
|
||||
}
|
||||
|
||||
while (!ms->quit) {
|
||||
/* SDL_CondWait(ms->cond, ms->lock); */
|
||||
}
|
||||
|
||||
SDL_UnlockMutex(ms->lock);
|
||||
|
||||
deallocate(ms);
|
||||
}
|
||||
|
||||
|
||||
static int decode_sync_start(void *arg) {
|
||||
// copy/paste from start of decode_thread
|
||||
MediaState *ms = (MediaState *) arg;
|
||||
|
||||
int err;
|
||||
|
||||
AVFormatContext *ctx = avformat_alloc_context();
|
||||
ms->ctx = ctx;
|
||||
|
||||
AVIOContext *io_context = rwops_open(ms->rwops);
|
||||
ctx->pb = io_context;
|
||||
|
||||
err = avformat_open_input(&ctx, ms->filename, NULL, NULL);
|
||||
if (err) {
|
||||
media_read_sync_finish(ms);
|
||||
}
|
||||
|
||||
err = avformat_find_stream_info(ctx, NULL);
|
||||
if (err) {
|
||||
media_read_sync_finish(ms);
|
||||
}
|
||||
|
||||
|
||||
ms->video_stream = -1;
|
||||
ms->audio_stream = -1;
|
||||
|
||||
for (int i = 0; i < ctx->nb_streams; i++) {
|
||||
if (ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
|
||||
if (ms->want_video && ms->video_stream == -1) {
|
||||
ms->video_stream = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO) {
|
||||
if (ms->audio_stream == -1) {
|
||||
ms->audio_stream = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ms->video_context = find_context(ctx, ms->video_stream);
|
||||
ms->audio_context = find_context(ctx, ms->audio_stream);
|
||||
|
||||
ms->swr = swr_alloc();
|
||||
|
||||
av_init_packet(&ms->video_pkt);
|
||||
|
||||
// Compute the number of samples we need to play back.
|
||||
if (ms->audio_duration < 0) {
|
||||
if (av_fmt_ctx_get_duration_estimation_method(ctx) != AVFMT_DURATION_FROM_BITRATE) {
|
||||
|
||||
long long duration = ((long long) ctx->duration) * audio_sample_rate;
|
||||
ms->audio_duration = (unsigned int) (duration / AV_TIME_BASE);
|
||||
|
||||
ms->total_duration = 1.0 * ctx->duration / AV_TIME_BASE;
|
||||
|
||||
// Check that the duration is reasonable (between 0s and 3600s). If not,
|
||||
// reject it.
|
||||
if (ms->audio_duration < 0 || ms->audio_duration > 3600 * audio_sample_rate) {
|
||||
ms->audio_duration = -1;
|
||||
}
|
||||
|
||||
ms->audio_duration -= (unsigned int) (ms->skip * audio_sample_rate);
|
||||
|
||||
|
||||
} else {
|
||||
ms->audio_duration = -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (ms->skip != 0.0) {
|
||||
av_seek_frame(ctx, -1, (int64_t) (ms->skip * AV_TIME_BASE), AVSEEK_FLAG_BACKWARD);
|
||||
}
|
||||
|
||||
// [snip!]
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
void media_read_sync(struct MediaState *ms) {
|
||||
// copy/paste from middle of decode_thread
|
||||
// printf("---* media_read_sync %p\n", ms);
|
||||
|
||||
//while (!ms->quit) {
|
||||
if (!ms->quit) {
|
||||
// printf(" audio_finished: %d, video_finished: %d\n", ms->audio_finished, ms->video_finished);
|
||||
if (! ms->audio_finished) {
|
||||
decode_audio(ms);
|
||||
}
|
||||
|
||||
if (! ms->video_finished) {
|
||||
decode_video(ms);
|
||||
}
|
||||
|
||||
SDL_LockMutex(ms->lock);
|
||||
|
||||
if (!ms->ready) {
|
||||
ms->ready = 1;
|
||||
SDL_CondBroadcast(ms->cond);
|
||||
}
|
||||
|
||||
if (!(ms->needs_decode || ms->quit)) {
|
||||
/* SDL_CondWait(ms->cond, ms->lock); */
|
||||
}
|
||||
|
||||
ms->needs_decode = 0;
|
||||
|
||||
SDL_UnlockMutex(ms->lock);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int media_read_audio(struct MediaState *ms, Uint8 *stream, int len) {
|
||||
#ifdef __EMSCRIPTEN__
|
||||
media_read_sync(ms);
|
||||
#endif
|
||||
|
||||
SDL_LockMutex(ms->lock);
|
||||
|
||||
@@ -1127,6 +1290,7 @@ int media_read_audio(struct MediaState *ms, Uint8 *stream, int len) {
|
||||
}
|
||||
|
||||
void media_wait_ready(struct MediaState *ms) {
|
||||
#ifndef __EMSCRIPTEN__
|
||||
SDL_LockMutex(ms->lock);
|
||||
|
||||
while (!ms->ready) {
|
||||
@@ -1134,6 +1298,7 @@ void media_wait_ready(struct MediaState *ms) {
|
||||
}
|
||||
|
||||
SDL_UnlockMutex(ms->lock);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -1142,7 +1307,12 @@ double media_duration(MediaState *ms) {
|
||||
}
|
||||
|
||||
void media_start(MediaState *ms) {
|
||||
char buf[1024];
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
decode_sync_start(ms);
|
||||
#else
|
||||
|
||||
char buf[1024];
|
||||
|
||||
snprintf(buf, 1024, "decode: %s", ms->filename);
|
||||
SDL_Thread *t = SDL_CreateThread(decode_thread, buf, (void *) ms);
|
||||
@@ -1151,6 +1321,7 @@ void media_start(MediaState *ms) {
|
||||
ms->started = 1;
|
||||
SDL_DetachThread(t);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -1210,6 +1381,11 @@ void media_close(MediaState *ms) {
|
||||
/* Tell the decoder to terminate. It will deallocate everything for us. */
|
||||
SDL_LockMutex(ms->lock);
|
||||
ms->quit = 1;
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
media_read_sync_finish(ms);
|
||||
#endif
|
||||
|
||||
SDL_CondBroadcast(ms->cond);
|
||||
SDL_UnlockMutex(ms->lock);
|
||||
|
||||
@@ -1224,9 +1400,10 @@ void media_sample_surfaces(SDL_Surface *rgb, SDL_Surface *rgba) {
|
||||
rgba_surface = rgba;
|
||||
}
|
||||
|
||||
void media_init(int rate, int status) {
|
||||
void media_init(int rate, int status, int equal_mono) {
|
||||
|
||||
audio_sample_rate = rate / SPEED;
|
||||
audio_equal_mono = equal_mono;
|
||||
|
||||
av_register_all();
|
||||
|
||||
|
||||
+33
-18
@@ -31,11 +31,33 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
#define MAXVOLUME 16384
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
|
||||
#define EVAL_LOCK() { }
|
||||
#define EVAL_UNLOCK() { }
|
||||
#define BEGIN() { }
|
||||
#define ENTER() { }
|
||||
#define EXIT() { }
|
||||
#define ALTENTER() { }
|
||||
#define ALTEXIT() { }
|
||||
|
||||
#else
|
||||
|
||||
#define EVAL_LOCK() { PyEval_AcquireLock(); }
|
||||
#define EVAL_UNLOCK() { PyEval_ReleaseLock(); }
|
||||
#define BEGIN() PyThreadState *_save;
|
||||
#define ENTER() { _save = PyEval_SaveThread(); SDL_LockAudio(); }
|
||||
#define EXIT() { SDL_UnlockAudio(); PyEval_RestoreThread(_save); }
|
||||
#define ALTENTER() { _save = PyEval_SaveThread(); }
|
||||
#define ALTEXIT() { PyEval_RestoreThread(_save); }
|
||||
|
||||
#endif
|
||||
|
||||
/* Declarations of ffdecode functions. */
|
||||
struct MediaState;
|
||||
typedef struct MediaState MediaState;
|
||||
|
||||
void media_init(int rate, int status);
|
||||
void media_init(int rate, int status, int equal_mono);
|
||||
|
||||
void media_advance_time(void);
|
||||
void media_sample_surfaces(SDL_Surface *rgb, SDL_Surface *rgba);
|
||||
@@ -61,21 +83,21 @@ PyThreadState* thread = NULL;
|
||||
static void incref(PyObject *ref) {
|
||||
PyThreadState *oldstate;
|
||||
|
||||
PyEval_AcquireLock();
|
||||
EVAL_LOCK();
|
||||
oldstate = PyThreadState_Swap(thread);
|
||||
Py_INCREF(ref);
|
||||
PyThreadState_Swap(oldstate);
|
||||
PyEval_ReleaseLock();
|
||||
EVAL_UNLOCK();
|
||||
}
|
||||
|
||||
static void decref(PyObject *ref) {
|
||||
PyThreadState *oldstate;
|
||||
|
||||
PyEval_AcquireLock();
|
||||
EVAL_LOCK();
|
||||
oldstate = PyThreadState_Swap(thread);
|
||||
Py_DECREF(ref);
|
||||
PyThreadState_Swap(oldstate);
|
||||
PyEval_ReleaseLock();
|
||||
EVAL_UNLOCK();
|
||||
}
|
||||
|
||||
/* A mutex that protects the shared data structures. */
|
||||
@@ -84,17 +106,6 @@ SDL_mutex *name_mutex;
|
||||
#define LOCK_NAME() { SDL_LockMutex(name_mutex); }
|
||||
#define UNLOCK_NAME() { SDL_UnlockMutex(name_mutex); }
|
||||
|
||||
/* Locking on entry from python... */
|
||||
// #define BEGIN() PyThreadState *_save;
|
||||
// #define ENTER() { printf("Locking by %s.\n", __FUNCTION__); _save = PyEval_SaveThread(); SDL_LockAudio(); printf("Lock by %s\n", __FUNCTION__); }
|
||||
// #define EXIT() { SDL_UnlockAudio(); PyEval_RestoreThread(_save); printf("Release by %s\n", __FUNCTION__); }
|
||||
|
||||
#define BEGIN() PyThreadState *_save;
|
||||
#define ENTER() { _save = PyEval_SaveThread(); SDL_LockAudio(); }
|
||||
#define EXIT() { SDL_UnlockAudio(); PyEval_RestoreThread(_save); }
|
||||
#define ALTENTER() { _save = PyEval_SaveThread(); }
|
||||
#define ALTEXIT() { PyEval_RestoreThread(_save); }
|
||||
|
||||
/* Min and Max */
|
||||
#define min(a, b) (((a) < (b)) ? (a) : (b))
|
||||
#define max(a, b) (((a) > (b)) ? (a) : (b))
|
||||
@@ -433,6 +444,7 @@ static void pan_audio(struct Channel *c, Uint8 *stream, int length) {
|
||||
}
|
||||
|
||||
static void callback(void *userdata, Uint8 *stream, int length) {
|
||||
|
||||
int channel = 0;
|
||||
|
||||
memset(stream, 0, length);
|
||||
@@ -1199,7 +1211,7 @@ void RPS_set_video(int channel, int video) {
|
||||
* Initializes the sound to the given frequencies, channels, and
|
||||
* sample buffer size.
|
||||
*/
|
||||
void RPS_init(int freq, int stereo, int samples, int status) {
|
||||
void RPS_init(int freq, int stereo, int samples, int status, int equal_mono) {
|
||||
|
||||
if (initialized) {
|
||||
return;
|
||||
@@ -1207,7 +1219,10 @@ void RPS_init(int freq, int stereo, int samples, int status) {
|
||||
|
||||
name_mutex = SDL_CreateMutex();
|
||||
|
||||
#ifndef __EMSCRIPTEN__
|
||||
PyEval_InitThreads();
|
||||
#endif
|
||||
|
||||
import_pygame_sdl2();
|
||||
|
||||
if (!thread) {
|
||||
@@ -1238,7 +1253,7 @@ void RPS_init(int freq, int stereo, int samples, int status) {
|
||||
return;
|
||||
}
|
||||
|
||||
media_init(audio_spec.freq, status);
|
||||
media_init(audio_spec.freq, status, equal_mono);
|
||||
|
||||
SDL_PauseAudio(0);
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ PyObject *RPS_read_video(int channel);
|
||||
void RPS_sample_surfaces(PyObject *rgb, PyObject *rgba);
|
||||
void RPS_set_video(int channel, int video);
|
||||
|
||||
void RPS_init(int freq, int stereo, int samples, int status);
|
||||
void RPS_init(int freq, int stereo, int samples, int status, int equal_mono);
|
||||
void RPS_quit(void);
|
||||
|
||||
void RPS_advance_time(void);
|
||||
|
||||
@@ -45,11 +45,19 @@ emscripten = "RENPY_EMSCRIPTEN" in os.environ
|
||||
# Is coverage enabled?
|
||||
coverage = "RENPY_COVERAGE" in os.environ
|
||||
|
||||
# Are we doing a static build?
|
||||
static = "RENPY_STATIC" in os.environ
|
||||
|
||||
if coverage:
|
||||
gen = "gen.coverage"
|
||||
else:
|
||||
gen = "gen"
|
||||
|
||||
|
||||
if static:
|
||||
gen += "-static"
|
||||
|
||||
|
||||
# The cython command.
|
||||
cython_command = os.environ.get("RENPY_CYTHON", "cython")
|
||||
|
||||
@@ -317,6 +325,18 @@ def cython(name, source=[], libs=[], includes=[], compile_if=True, define_macros
|
||||
"-o",
|
||||
c_fn])
|
||||
|
||||
# Fix-up source for static loading
|
||||
if static and (len(split_name) > 1):
|
||||
parent_module = '.'.join(split_name[:-1])
|
||||
parent_module_identifier = parent_module.replace('.', '_')
|
||||
with open(c_fn, 'r') as f:
|
||||
ccode = f.read()
|
||||
ccode = re.sub('Py_InitModule4\("([^"]+)"', 'Py_InitModule4("'+parent_module+'.\\1"', ccode)
|
||||
ccode = re.sub('^__Pyx_PyMODINIT_FUNC init', '__Pyx_PyMODINIT_FUNC init'+parent_module_identifier+'_', ccode, 0, re.MULTILINE) # Cython 0.28.2
|
||||
ccode = re.sub('^PyMODINIT_FUNC init', 'PyMODINIT_FUNC init'+parent_module_identifier+'_', ccode, 0, re.MULTILINE) # Cython 0.25.2
|
||||
with open(c_fn, 'w') as f:
|
||||
f.write(ccode)
|
||||
|
||||
except subprocess.CalledProcessError as e:
|
||||
print()
|
||||
print(str(e))
|
||||
|
||||
+15
-2
@@ -40,7 +40,7 @@ except ImportError:
|
||||
vc_version = 0
|
||||
|
||||
# The tuple giving the version number.
|
||||
version_tuple = (7, 2, 0, vc_version)
|
||||
version_tuple = (7, 2, 3, vc_version)
|
||||
|
||||
# The name of this version.
|
||||
version_name = "What's on the menu."
|
||||
@@ -551,6 +551,10 @@ def post_import():
|
||||
vars(renpy.exports).setdefault(k, v)
|
||||
|
||||
|
||||
def issubmodule(sub, module):
|
||||
return sub == module or sub.startswith(module + ".")
|
||||
|
||||
|
||||
def reload_all():
|
||||
"""
|
||||
Resets all modules to the state they were in right after import_all
|
||||
@@ -588,10 +592,11 @@ def reload_all():
|
||||
renpy.display.interface = None
|
||||
|
||||
py_compile_cache = renpy.python.py_compile_cache
|
||||
reload_modules = renpy.config.reload_modules
|
||||
|
||||
# Delete the store modules.
|
||||
for i in sys.modules.keys():
|
||||
if i.startswith("store") or i == "renpy.store":
|
||||
if issubmodule(i, "store") or i == "renpy.store":
|
||||
m = sys.modules[i]
|
||||
|
||||
if m is not None:
|
||||
@@ -599,6 +604,14 @@ def reload_all():
|
||||
|
||||
del sys.modules[i]
|
||||
|
||||
elif any(issubmodule(i, m) for m in reload_modules):
|
||||
m = sys.modules[i]
|
||||
|
||||
if m is not None:
|
||||
m.__dict__.clear()
|
||||
|
||||
del sys.modules[i]
|
||||
|
||||
# Restore the state of all modules from backup.
|
||||
backup.restore()
|
||||
|
||||
|
||||
+32
-4
@@ -249,6 +249,16 @@ class PyExpr(unicode):
|
||||
return (unicode(self), self.filename, self.linenumber) # E1101
|
||||
|
||||
|
||||
def probably_side_effect_free(expr):
|
||||
"""
|
||||
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)
|
||||
|
||||
|
||||
class PyCode(object):
|
||||
|
||||
__slots__ = [
|
||||
@@ -629,6 +639,10 @@ class Say(Node):
|
||||
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)
|
||||
@@ -1432,6 +1446,10 @@ class Call(Node):
|
||||
label = self.label
|
||||
|
||||
if self.expression:
|
||||
|
||||
if not probably_side_effect_free(label):
|
||||
return [ ]
|
||||
|
||||
label = renpy.python.py_eval(label)
|
||||
|
||||
return [ renpy.game.context().predict_call(label, self.next.name) ]
|
||||
@@ -1498,6 +1516,7 @@ class Return(Node):
|
||||
class Menu(Node):
|
||||
|
||||
translation_relevant = True
|
||||
rollback = "force"
|
||||
|
||||
__slots__ = [
|
||||
'items',
|
||||
@@ -1677,10 +1696,16 @@ class Jump(Node):
|
||||
|
||||
def predict(self):
|
||||
|
||||
label = self.target
|
||||
|
||||
if self.expression:
|
||||
return [ ]
|
||||
else:
|
||||
return [ renpy.game.script.lookup(self.target) ]
|
||||
|
||||
if not probably_side_effect_free(label):
|
||||
return [ ]
|
||||
|
||||
label = renpy.python.py_eval(label)
|
||||
|
||||
return [ renpy.game.script.lookup(label) ]
|
||||
|
||||
def scry(self):
|
||||
rv = Node.scry(self)
|
||||
@@ -1827,6 +1852,7 @@ class UserStatement(Node):
|
||||
'translatable',
|
||||
'code_block',
|
||||
'translation_relevant',
|
||||
'rollback',
|
||||
]
|
||||
|
||||
def __new__(cls, *args, **kwargs):
|
||||
@@ -1835,6 +1861,7 @@ class UserStatement(Node):
|
||||
self.code_block = None
|
||||
self.translatable = False
|
||||
self.translation_relevant = False
|
||||
self.rollback = "normal"
|
||||
return self
|
||||
|
||||
def __init__(self, loc, line, block):
|
||||
@@ -1846,6 +1873,7 @@ class UserStatement(Node):
|
||||
self.parsed = None
|
||||
|
||||
self.name = self.call("label")
|
||||
self.rollback = renpy.statements.get("rollback", self.parsed) or "normal"
|
||||
|
||||
# Do not store the parse.
|
||||
self.parsed = None
|
||||
@@ -1976,7 +2004,7 @@ def get_namespace(store):
|
||||
|
||||
# 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" }
|
||||
EARLY_CONFIG = { "save_directory", "allow_duplicate_labels", "keyword_after_python" }
|
||||
|
||||
define_statements = [ ]
|
||||
|
||||
|
||||
@@ -251,6 +251,9 @@ class Context(object):
|
||||
|
||||
return self.context == other.context
|
||||
|
||||
def __ne__(self, other):
|
||||
return not (self == other)
|
||||
|
||||
# This is intended to be subclassed by ATLTransform. It takes care of
|
||||
# managing ATL execution, which allows ATLTransform itself to not care
|
||||
# much about the contents of this file.
|
||||
|
||||
@@ -811,12 +811,16 @@ def init():
|
||||
|
||||
if pcm_ok is None and renpysound:
|
||||
bufsize = 2048
|
||||
if renpy.emscripten:
|
||||
# Large buffer (and latency) as compromise to avoid sound jittering
|
||||
bufsize = 8192 # works for me
|
||||
#bufsize = 16384 # jitter/silence right after starting a sound
|
||||
|
||||
if 'RENPY_SOUND_BUFSIZE' in os.environ:
|
||||
bufsize = int(os.environ['RENPY_SOUND_BUFSIZE'])
|
||||
|
||||
try:
|
||||
renpysound.init(renpy.config.sound_sample_rate, 2, bufsize, False)
|
||||
renpysound.init(renpy.config.sound_sample_rate, 2, bufsize, False, renpy.config.equal_mono)
|
||||
pcm_ok = True
|
||||
except:
|
||||
if renpy.config.debug_sound:
|
||||
|
||||
@@ -77,7 +77,7 @@ cdef extern from "renpysound_core.h":
|
||||
void RPS_set_video(int channel, int video)
|
||||
|
||||
void RPS_sample_surfaces(object, object)
|
||||
void RPS_init(int freq, int stereo, int samples, int status)
|
||||
void RPS_init(int freq, int stereo, int samples, int status, int equal_mono)
|
||||
void RPS_quit()
|
||||
|
||||
void RPS_periodic()
|
||||
@@ -354,7 +354,7 @@ def set_video(channel, video):
|
||||
else:
|
||||
RPS_set_video(channel, NO_VIDEO)
|
||||
|
||||
def init(freq, stereo, samples, status=False):
|
||||
def init(freq, stereo, samples, status=False, equal_mono=False):
|
||||
"""
|
||||
Initializes the audio system with the given parameters. The parameter are
|
||||
just informational - the audio system should be able to play all supported
|
||||
@@ -379,7 +379,7 @@ def init(freq, stereo, samples, status=False):
|
||||
else:
|
||||
status = 0
|
||||
|
||||
RPS_init(freq, stereo, samples, status)
|
||||
RPS_init(freq, stereo, samples, status, equal_mono)
|
||||
check_error()
|
||||
|
||||
def quit(): # @ReservedAssignment
|
||||
|
||||
+3
-2
@@ -86,6 +86,7 @@ def extra_imports():
|
||||
import cProfile; cProfile
|
||||
import pstats; pstats
|
||||
import _ssl; _ssl
|
||||
import SimpleHTTPServer; SimpleHTTPServer
|
||||
|
||||
# Used by requests.
|
||||
import cgi; cgi
|
||||
@@ -354,7 +355,7 @@ You may be using a system install of python. Please run {0}.sh,
|
||||
|
||||
renpy.audio.audio.quit()
|
||||
|
||||
|
||||
# Prevent subprocess from throwing errors while trying to run it's
|
||||
# __del__ method during shutdown.
|
||||
subprocess.Popen.__del__ = popen_del
|
||||
if not renpy.emscripten:
|
||||
subprocess.Popen.__del__ = popen_del
|
||||
|
||||
+13
-8
@@ -854,7 +854,7 @@ class ADVCharacter(object):
|
||||
properties=self.properties,
|
||||
**self.show_args)
|
||||
|
||||
def resolve_say_attributes(self, predict, attrs, wanted=[], remove=[], skip_trans=False):
|
||||
def resolve_say_attributes(self, mode, predict, attrs, wanted=[], remove=[], skip_trans=False):
|
||||
"""
|
||||
Deals with image attributes associated with the current say
|
||||
statement.
|
||||
@@ -893,12 +893,14 @@ class ADVCharacter(object):
|
||||
show_image = (self.image_tag,) + attrs + tuple(wanted) + tuple( "-" + i for i in remove)
|
||||
|
||||
if predict:
|
||||
images.predict_show(new_image)
|
||||
images.predict_show(show_image)
|
||||
|
||||
else:
|
||||
|
||||
trans = renpy.config.say_attribute_transition
|
||||
layer = renpy.config.say_attribute_transition_layer
|
||||
if renpy.config.say_attribute_transition_callback_attrs:
|
||||
trans, layer = renpy.config.say_attribute_transition_callback(self.image_tag, new_image, mode)
|
||||
else:
|
||||
trans, layer = renpy.config.say_attribute_transition_callback(self.image_tag, mode)
|
||||
|
||||
if not skip_trans:
|
||||
if (trans is not None) and (layer is not None):
|
||||
@@ -950,7 +952,7 @@ class ADVCharacter(object):
|
||||
if renpy.config.speaking_attribute is not None:
|
||||
temporary_attrs.insert(0, renpy.config.speaking_attribute)
|
||||
|
||||
self.resolve_say_attributes(predicting, attrs, skip_trans=temporary_attrs)
|
||||
self.resolve_say_attributes("permanent", predicting, attrs, skip_trans=temporary_attrs)
|
||||
|
||||
# This is so late to give resolve_say_attributes time to do some
|
||||
# error handling.
|
||||
@@ -963,7 +965,7 @@ class ADVCharacter(object):
|
||||
images = renpy.game.context().images
|
||||
attrs = images.get_attributes(None, self.image_tag)
|
||||
|
||||
self.resolve_say_attributes(predicting, temporary_attrs)
|
||||
self.resolve_say_attributes("temporary", predicting, temporary_attrs)
|
||||
|
||||
return (attrs, images)
|
||||
|
||||
@@ -991,9 +993,12 @@ class ADVCharacter(object):
|
||||
if images.showing(None, (self.image_tag,)):
|
||||
|
||||
if not predicting:
|
||||
new_image = images.apply_attributes(None, self.image_tag, image_with_attrs)
|
||||
|
||||
trans = renpy.config.say_attribute_transition
|
||||
layer = renpy.config.say_attribute_transition_layer
|
||||
if renpy.config.say_attribute_transition_callback_attrs:
|
||||
trans, layer = renpy.config.say_attribute_transition_callback(self.image_tag, new_image, "restore")
|
||||
else:
|
||||
trans, layer = renpy.config.say_attribute_transition_callback(self.image_tag, "restore")
|
||||
|
||||
if interact:
|
||||
if (trans is not None) and (layer is not None):
|
||||
|
||||
@@ -560,7 +560,8 @@ python early hide:
|
||||
parse=parse_show_call_screen,
|
||||
execute=execute_call_screen,
|
||||
predict=predict_screen,
|
||||
lint=lint_screen)
|
||||
lint=lint_screen,
|
||||
force_begin_rollback=True)
|
||||
|
||||
renpy.register_statement("hide screen",
|
||||
parse=parse_hide_screen,
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
# Copyright 2004-2019 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.
|
||||
|
||||
init -1500 python hide:
|
||||
|
||||
def opendyslexic(f):
|
||||
|
||||
# Generated with fc-query --format=%{charset} OpenDyslexic3-Regular.ttf
|
||||
charset = "20-7e a0-107 10a-113 116-11b 11e-123 126-12b 12e-133 136-137 139-148 14a-14d 150-15b 15e-16b 16e-17e 186 188-189 18e-192 194 197 19d 1a0-1a1 1a9 1ac 1af-1b4 1c0-1c3 1cd-1df 1e6-1e7 1fe-1ff 218-21b 228-22b 241-244 24b-24c 250-254 256-259 25b-25c 261-262 265 268 26a-26b 26f-270 272 274-276 279-27a 280-281 283 289-28a 28c-28f 294-295 298-299 29b-29c 29f 2a1-2a2 2c6-2c7 2d8-2dd 309 31b 323 326 32d 331 3b2 3b8 3c0 3c7 401 403 405-408 40c 40e 410 412 415 418 41c-41e 420-422 425 427 42f-430 432 435 437 439 43e 440-441 443 445 44f 451 453 455-458 45b-45c 45e 472 498-499 4ae 4b1 4c0 4d0 4d2-4df 4e2-4f5 4f8-4f9 1e04-1e05 1e0c-1e0f 1e12-1e13 1e24-1e25 1e2e-1e2f 1e36-1e37 1e3c-1e3f 1e44-1e47 1e4a-1e4d 1e50-1e53 1e62-1e63 1e6c-1e71 1e80-1e85 1e92-1e93 1ea0-1ef9 2013-2014 2018-201a 201c-201e 2020-2022 2026 2030 2039-203a 203d 2044 20ac 2122 2126 215b-215e 2202 2206 220f 2211-2212 2219-221a 221e 222b 2248 2260 2264-2265 25ca 2c64 2c6d 2c72-2c73 2e18 fb01-fb02"
|
||||
|
||||
rv = FontGroup()
|
||||
|
||||
for r in charset.split():
|
||||
start, _, end = r.partition("-")
|
||||
start = int(start, 16)
|
||||
end = int(end, 16) if end else start
|
||||
|
||||
rv.add("_OpenDyslexic3-Regular.ttf", start, end)
|
||||
|
||||
rv.add(f, None, None)
|
||||
|
||||
return rv
|
||||
|
||||
config.font_transforms["opendyslexic"] = opendyslexic
|
||||
|
||||
config.ftfont_scale["_OpenDyslexic3-Regular.ttf"] = .87
|
||||
config.ftfont_vertical_extent_scale["_OpenDyslexic3-Regular.ttf"] = .66
|
||||
|
||||
def dejavusans(f):
|
||||
return "DejaVuSans.ttf"
|
||||
|
||||
config.font_transforms["dejavusans"] = dejavusans
|
||||
|
||||
screen _accessibility():
|
||||
zorder 2000
|
||||
modal True
|
||||
|
||||
frame:
|
||||
style_group ""
|
||||
|
||||
has side "c b":
|
||||
spacing gui._scale(10)
|
||||
xfill True
|
||||
yfill True
|
||||
|
||||
fixed:
|
||||
|
||||
viewport:
|
||||
scrollbars "vertical"
|
||||
mousewheel True
|
||||
|
||||
has grid 2 1:
|
||||
xfill True
|
||||
spacing 20
|
||||
|
||||
vbox:
|
||||
|
||||
label _("Font Override")
|
||||
|
||||
null height 10
|
||||
|
||||
textbutton _("Default"):
|
||||
action Preference("font transform", None)
|
||||
style_suffix "radio_button"
|
||||
|
||||
textbutton _("DejaVu Sans"):
|
||||
action Preference("font transform", "dejavusans")
|
||||
style_suffix "radio_button"
|
||||
|
||||
textbutton _("Opendyslexic"):
|
||||
action Preference("font transform", "opendyslexic")
|
||||
style_suffix "radio_button"
|
||||
|
||||
null height 10
|
||||
|
||||
label _("Text Size Scaling")
|
||||
|
||||
null height 10
|
||||
|
||||
bar value Preference("font size")
|
||||
|
||||
textbutton _("Reset"):
|
||||
action Preference("font size", 1.0)
|
||||
|
||||
null height 10
|
||||
|
||||
label _("Line Spacing Scaling")
|
||||
|
||||
null height 10
|
||||
|
||||
bar value Preference("font line spacing")
|
||||
|
||||
textbutton _("Reset"):
|
||||
action Preference("font line spacing", 1.0)
|
||||
|
||||
|
||||
vbox:
|
||||
|
||||
label _("Self-Voicing")
|
||||
|
||||
null height 10
|
||||
|
||||
textbutton _("Off"):
|
||||
action Preference("self voicing", "disable")
|
||||
style_suffix "radio_button"
|
||||
|
||||
textbutton _("Text-to-speech"):
|
||||
action Preference("self voicing", "enable")
|
||||
style_suffix "radio_button"
|
||||
|
||||
textbutton _("Clipboard"):
|
||||
action Preference("clipboard voicing", "enable")
|
||||
style_suffix "radio_button"
|
||||
|
||||
textbutton _("Debug"):
|
||||
action Preference("debug voicing", "enable")
|
||||
style_suffix "radio_button"
|
||||
|
||||
vbox:
|
||||
|
||||
text ("The options on this menu are intended to improve accessibility. They may not work with all games, and some combinations of options may render the game unplayable. This is not an issue with the game or engine. For the best results when changing fonts, try to keep the text size the same as it originally was.")
|
||||
|
||||
hbox:
|
||||
spacing gui._scale(25)
|
||||
|
||||
textbutton _("Return"):
|
||||
action Hide("_accessibility")
|
||||
yalign 1.0
|
||||
|
||||
@@ -226,6 +226,7 @@ init -1500 python in achievement:
|
||||
backends.insert(0, SteamBackend())
|
||||
else:
|
||||
renpy.write_log("Failed to initialize steam.")
|
||||
steam = None
|
||||
|
||||
|
||||
def register(name, **kwargs):
|
||||
|
||||
@@ -139,14 +139,17 @@ init -1500 python:
|
||||
`step`
|
||||
The amount to change the bar by. If None, defaults to 1/10th of
|
||||
the bar.
|
||||
`action`
|
||||
If not None, an action to call when the field has changed.
|
||||
"""
|
||||
|
||||
offset = 0
|
||||
action = None
|
||||
|
||||
identity_fields = [ 'dict' ]
|
||||
equality_fields = [ 'key', 'range', 'max_is_zero', 'style', 'offset', 'step']
|
||||
equality_fields = [ 'key', 'range', 'max_is_zero', 'style', 'offset', 'step', 'action']
|
||||
|
||||
def __init__(self, dict, key, range, max_is_zero=False, style="bar", offset=0, step=None):
|
||||
def __init__(self, dict, key, range, max_is_zero=False, style="bar", offset=0, step=None, action=None):
|
||||
self.dict = dict
|
||||
self.key = key
|
||||
self.range = range
|
||||
@@ -161,6 +164,7 @@ init -1500 python:
|
||||
step = max(range / 10, 1)
|
||||
|
||||
self.step = step
|
||||
self.action = action
|
||||
|
||||
def changed(self, value):
|
||||
|
||||
@@ -175,6 +179,8 @@ init -1500 python:
|
||||
self.dict[self.key] = value
|
||||
renpy.restart_interaction()
|
||||
|
||||
renpy.run(self.action)
|
||||
|
||||
def get_adjustment(self):
|
||||
|
||||
value = self.dict[self.key]
|
||||
@@ -224,14 +230,17 @@ init -1500 python:
|
||||
`step`
|
||||
The amount to change the bar by. If None, defaults to 1/10th of
|
||||
the bar.
|
||||
`action`
|
||||
If not None, an action to call when the field has changed.
|
||||
"""
|
||||
|
||||
offset = 0
|
||||
action = None
|
||||
|
||||
identity_fields = [ 'object' ]
|
||||
equality_fields = [ 'range', 'max_is_zero', 'style', 'offset', 'step']
|
||||
identity_fields = [ 'object', ]
|
||||
equality_fields = [ 'range', 'max_is_zero', 'style', 'offset', 'step', 'action']
|
||||
|
||||
def __init__(self, object, field, range, max_is_zero=False, style="bar", offset=0, step=None):
|
||||
def __init__(self, object, field, range, max_is_zero=False, style="bar", offset=0, step=None, action=None):
|
||||
self.object = object
|
||||
self.field = field
|
||||
self.range = range
|
||||
@@ -246,6 +255,7 @@ init -1500 python:
|
||||
step = max(range / 10, 1)
|
||||
|
||||
self.step = step
|
||||
self.action = action
|
||||
|
||||
def changed(self, value):
|
||||
|
||||
@@ -260,6 +270,8 @@ init -1500 python:
|
||||
setattr(self.object, self.field, value)
|
||||
renpy.restart_interaction()
|
||||
|
||||
renpy.run(self.action)
|
||||
|
||||
def get_adjustment(self):
|
||||
|
||||
value = getattr(self.object, self.field)
|
||||
@@ -282,7 +294,7 @@ init -1500 python:
|
||||
return self.style, "v" + self.style
|
||||
|
||||
@renpy.pure
|
||||
def VariableValue(variable, range, max_is_zero=False, style="bar", offset=0, step=None):
|
||||
def VariableValue(variable, range, max_is_zero=False, style="bar", offset=0, step=None, action=None):
|
||||
"""
|
||||
:doc: value
|
||||
|
||||
@@ -307,9 +319,11 @@ init -1500 python:
|
||||
`step`
|
||||
The amount to change the bar by. If None, defaults to 1/10th of
|
||||
the bar.
|
||||
`action`
|
||||
If not None, an action to call when the field has changed.
|
||||
"""
|
||||
|
||||
return FieldValue(store, variable, range, max_is_zero=max_is_zero, style=style, offset=offset, step=step)
|
||||
return FieldValue(store, variable, range, max_is_zero=max_is_zero, style=style, offset=offset, step=step, action=action)
|
||||
|
||||
@renpy.pure
|
||||
class ScreenVariableValue(BarValue, FieldEquality):
|
||||
@@ -336,14 +350,17 @@ init -1500 python:
|
||||
`step`
|
||||
The amount to change the bar by. If None, defaults to 1/10th of
|
||||
the bar.
|
||||
`action`
|
||||
If not None, an action to call when the field has changed.
|
||||
"""
|
||||
|
||||
action = None
|
||||
offset = 0
|
||||
|
||||
identity_fields = [ ]
|
||||
equality_fields = [ 'variable', 'max_is_zero', 'style', 'offset', 'step']
|
||||
equality_fields = [ 'variable', 'max_is_zero', 'style', 'offset', 'step', 'action']
|
||||
|
||||
def __init__(self, variable, range, max_is_zero=False, style="bar", offset=0, step=None):
|
||||
def __init__(self, variable, range, max_is_zero=False, style="bar", offset=0, step=None, action=None):
|
||||
self.variable = variable
|
||||
self.range = range
|
||||
self.max_is_zero = max_is_zero
|
||||
@@ -357,6 +374,7 @@ init -1500 python:
|
||||
step = max(range / 10, 1)
|
||||
|
||||
self.step = step
|
||||
self.action = action
|
||||
|
||||
def changed(self, value):
|
||||
|
||||
@@ -373,6 +391,8 @@ init -1500 python:
|
||||
cs.scope[self.variable] = value
|
||||
renpy.restart_interaction()
|
||||
|
||||
renpy.run(self.action)
|
||||
|
||||
def get_adjustment(self):
|
||||
|
||||
cs = renpy.current_screen()
|
||||
|
||||
@@ -72,13 +72,17 @@ init -1500 python in build:
|
||||
( "renpy.py", "renpy"),
|
||||
|
||||
( "renpy/", "all"),
|
||||
( "renpy/**.py", "renpy"),
|
||||
( "renpy/**.pyx", "renpy"),
|
||||
( "renpy/**.pyd", "renpy"),
|
||||
( "renpy/**.pxi", "renpy"),
|
||||
( "renpy/common/", "all"),
|
||||
( "renpy/common/_compat/**", "renpy"),
|
||||
( "renpy/common/**.rpy", "renpy"),
|
||||
( "renpy/common/**.rpym", "renpy"),
|
||||
( "renpy/common/_compat/**", "renpy"),
|
||||
( "renpy/common/**", "all"),
|
||||
( "renpy/**", "renpy"),
|
||||
( "renpy/**", "all"),
|
||||
|
||||
# Ignore Ren'Py and renpy.exe.
|
||||
( "lib/*/renpy", None),
|
||||
@@ -148,6 +152,9 @@ init -1500 python in build:
|
||||
("ouya_icon.png", "android"),
|
||||
|
||||
("ios-presplash.*", "ios"),
|
||||
("ios-launchimage.png", None),
|
||||
("ios-icon.png", None),
|
||||
|
||||
])
|
||||
|
||||
base_patterns = [ ]
|
||||
@@ -326,8 +333,9 @@ init -1500 python in build:
|
||||
package("win", "zip", "windows renpy all", "Windows x86")
|
||||
package("market", "zip", "windows linux mac renpy all", "Windows, Mac, Linux for Markets")
|
||||
package("steam", "zip", "windows linux mac renpy all", hidden=True)
|
||||
package("android", "directory", "android renpy all", hidden=True, update=False, dlc=True)
|
||||
package("ios", "directory", "ios renpy all", hidden=True, update=False, dlc=True)
|
||||
package("android", "directory", "android all", hidden=True, update=False, dlc=True)
|
||||
package("ios", "directory", "ios all", hidden=True, update=False, dlc=True)
|
||||
package("web", "zip", "web all", update=False, dlc=True)
|
||||
|
||||
# Data that we expect the user to set.
|
||||
|
||||
@@ -424,7 +432,7 @@ init -1500 python in build:
|
||||
rv["renpy_patterns"] = excludes + renpy_patterns
|
||||
rv["xbit_patterns"] = xbit_patterns
|
||||
rv["version"] = version or directory_name
|
||||
rv["display_name"] = display_name or executable_name
|
||||
rv["display_name"] = display_name or config.name or executable_name
|
||||
|
||||
rv["exclude_empty_directories"] = exclude_empty_directories
|
||||
|
||||
|
||||
@@ -183,10 +183,26 @@ init -1900 python:
|
||||
if version <= (7, 1, 1):
|
||||
config.menu_actions = False
|
||||
|
||||
if version <= (7, 2, 2):
|
||||
config.say_attribute_transition_callback_attrs = False
|
||||
|
||||
# The version of Ren'Py this script is intended for, or
|
||||
# None if it's intended for the current version.
|
||||
config.script_version = None
|
||||
|
||||
python early hide:
|
||||
try:
|
||||
import ast
|
||||
script_version = renpy.file("script_version.txt").read()
|
||||
script_version = ast.literal_eval(script_version)
|
||||
|
||||
if script_version <= (7, 2, 2):
|
||||
config.keyword_after_python = True
|
||||
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
init -1000 python hide:
|
||||
try:
|
||||
import ast
|
||||
|
||||
@@ -140,6 +140,8 @@ init -1500 python in _console:
|
||||
aRepr.repr_RevertableList = aRepr.repr_list
|
||||
aRepr.repr_RevertableDict = aRepr.repr_dict
|
||||
aRepr.repr_RevertableSet = aRepr.repr_set
|
||||
aRepr.repr_defaultdict = aRepr.repr_dict
|
||||
aRepr.repr_OrderedDict = aRepr.repr_dict
|
||||
|
||||
# The list of traced expressions.
|
||||
class TracedExpressionsList(NoRollback, list):
|
||||
@@ -418,7 +420,7 @@ init -1500 python in _console:
|
||||
l.advance()
|
||||
|
||||
# Command can be None, but that's okay, since the lookup will fail.
|
||||
command = l.name()
|
||||
command = l.word()
|
||||
|
||||
command_fn = config.console_commands.get(command, None)
|
||||
|
||||
|
||||
@@ -373,7 +373,7 @@ init -1500 python:
|
||||
if not _preferences.performance_test and not performance_test:
|
||||
return
|
||||
|
||||
# Don't bother on android or ios - there's nothing the user can do.
|
||||
# Don't bother on android or ios or emscripten - there's nothing the user can do.
|
||||
if renpy.mobile:
|
||||
return
|
||||
|
||||
|
||||
@@ -42,6 +42,7 @@ init -1600 python:
|
||||
help = [ 'K_F1', 'meta_shift_/' ],
|
||||
choose_renderer = [ 'G', 'alt_shift_K_g' ],
|
||||
progress_screen = [ 'alt_shift_K_p', 'meta_shift_K_p', 'K_F2' ],
|
||||
accessibility = [ "K_a" ],
|
||||
|
||||
# Accessibility.
|
||||
self_voicing = [ 'v', 'V', 'alt_K_v' ],
|
||||
@@ -378,6 +379,7 @@ init -1100 python:
|
||||
progress_screen = _progress_screen,
|
||||
director = director.Start(),
|
||||
performance = ToggleScreen("_performance"),
|
||||
accessibility = ToggleScreen("_accessibility"),
|
||||
)
|
||||
|
||||
config.underlay = [ _default_keymap ]
|
||||
|
||||
@@ -55,6 +55,9 @@ init -1700 python:
|
||||
|
||||
return False
|
||||
|
||||
def __ne__(self, o):
|
||||
return not (self == o)
|
||||
|
||||
class FieldEquality(object):
|
||||
"""
|
||||
Declares two objects equal if their types are the same, and
|
||||
@@ -92,6 +95,9 @@ init -1700 python:
|
||||
|
||||
return False
|
||||
|
||||
def __ne__(self, o):
|
||||
return not (self == o)
|
||||
|
||||
|
||||
init -1700 python:
|
||||
|
||||
@@ -152,9 +158,9 @@ init -1700 python:
|
||||
what = _last_say_what + config.extend_interjection + _last_raw_what
|
||||
|
||||
args = args + _last_say_args
|
||||
kw = dict(kwargs)
|
||||
kw.update(_last_say_kwargs)
|
||||
kw.setdefault("interact", interact)
|
||||
kw = dict(_last_say_kwargs)
|
||||
kw.update(kwargs)
|
||||
kw["interact"] = interact and kw.get("interact", True)
|
||||
|
||||
renpy.exports.say(who, what, *args, **kw)
|
||||
store._last_say_what = what
|
||||
|
||||
@@ -71,6 +71,7 @@ init -1500 python:
|
||||
"""
|
||||
|
||||
def __call__(self):
|
||||
renpy.free_memory()
|
||||
renpy.display.interface.display_reset = True
|
||||
|
||||
|
||||
@@ -198,6 +199,13 @@ init -1500 python:
|
||||
* Preference("gl tearing", True) - Tears rather than skipping frames.
|
||||
* Preference("gl tearing", False) - Skips frames rather than tearing.
|
||||
|
||||
* Preference("font transform", "opendyslexic") - Sets the accessibility font transform to opendyslexic.
|
||||
* Preference("font transform", "dejavusans") - Sets the accessibility font trandform to deja vu sans.
|
||||
* Preference("font transform", None) - Disables the accessibility font transform.
|
||||
|
||||
* Preference("font size", 1.0) - Sets the accessibility font size scaling factor.
|
||||
* Preference("font vertical spacing", 1.0) - Sets the accessibility font vertical spacing scaling factor.
|
||||
|
||||
Values that can be used with bars are:
|
||||
|
||||
* Preference("text speed")
|
||||
@@ -206,6 +214,8 @@ init -1500 python:
|
||||
* Preference("sound volume")
|
||||
* Preference("voice volume")
|
||||
* Preference("mixer <mixer> volume")
|
||||
* Preference("font size")
|
||||
* Preference("font vertical spacing")
|
||||
|
||||
The `range` parameter can be given to give the range of certain bars.
|
||||
For "text speed", it defaults to 200 cps. For "auto-forward time", it
|
||||
@@ -417,6 +427,27 @@ init -1500 python:
|
||||
elif name == _("gl tearing"):
|
||||
return [ SetField(_preferences, "gl_tearing", value), _DisplayReset() ]
|
||||
|
||||
elif name == _("font transform"):
|
||||
return [ SetField(_preferences, "font_transform", value), _DisplayReset() ]
|
||||
|
||||
elif name == _("font size"):
|
||||
|
||||
if value is None:
|
||||
bar_range = range or 1.0
|
||||
return FieldValue(_preferences, "font_size", range=bar_range, style="slider", offset=.5, action=_DisplayReset())
|
||||
|
||||
return [ SetField(_preferences, "font_size", value), _DisplayReset() ]
|
||||
|
||||
elif name == _("font line spacing"):
|
||||
|
||||
if value is None:
|
||||
bar_range = range or 1.0
|
||||
return FieldValue(_preferences, "font_line_spacing", range=bar_range, style="slider", offset=.5, action=_DisplayReset())
|
||||
|
||||
return [ SetField(_preferences, "font_line_spacing", value), _DisplayReset() ]
|
||||
|
||||
|
||||
|
||||
mixer_names = {
|
||||
"music" : "music",
|
||||
"sound" : "sfx",
|
||||
|
||||
@@ -319,7 +319,7 @@ init -1500 python:
|
||||
else:
|
||||
fn = config.auto_voice(tlid)
|
||||
|
||||
_voice.auto_filename = fn
|
||||
self.auto_filename = fn
|
||||
|
||||
if fn and renpy.loadable(fn):
|
||||
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,92 @@
|
||||
Per https://www.opendyslexic.org/,
|
||||
|
||||
"OpenDyslexic 3 uses the SIL-OFL license, giving you freedom to use it for
|
||||
personal use, business use, education, commercial, books, ebook readers,
|
||||
applications, websites, and any other project or purpose you need."
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
@@ -203,28 +203,30 @@ init label _errorhandling:
|
||||
left_padding gui._scale(15)
|
||||
ypadding gui._scale(2)
|
||||
|
||||
style _radio_button_text is _button_text:
|
||||
size gui._scale(26)
|
||||
|
||||
style _label is _default:
|
||||
top_margin gui._scale(10)
|
||||
bottom_margin gui._scale(15)
|
||||
|
||||
style _label_text is _default:
|
||||
size gui._scale(40)
|
||||
size gui._scale(30)
|
||||
kerning -1
|
||||
|
||||
style _bar is _default:
|
||||
left_bar "#24C"
|
||||
left_bar "#468"
|
||||
hover_left_bar "#24C"
|
||||
right_bar "#b0b0b0"
|
||||
ysize gui._scale(20)
|
||||
|
||||
style _vbar is _default:
|
||||
bottom_bar "#24C"
|
||||
bottom_bar "#468"
|
||||
hover_bottom_bar "#24C"
|
||||
top_bar "#b0b0b0"
|
||||
xsize gui._scale(20)
|
||||
bar_vertical True
|
||||
|
||||
style _slider is _bar
|
||||
style _vslider is _vbar
|
||||
|
||||
style _scrollbar is _default:
|
||||
thumb "#808080"
|
||||
hover_thumb "#a0a0a0"
|
||||
@@ -572,7 +574,7 @@ screen _exception:
|
||||
|
||||
side "c r":
|
||||
xfill True
|
||||
label _("An exception has occurred.")
|
||||
label _("An exception has occurred.") text_size gui._scale(40)
|
||||
text "{size=-3}[config.version!q]\n[renpy.version_only!q]\n[renpy.platform!q]{/size}" text_align 1.0 yalign 0.5
|
||||
|
||||
viewport:
|
||||
@@ -648,7 +650,7 @@ screen _parse_errors:
|
||||
has side "t c b":
|
||||
spacing gui._scale(10)
|
||||
|
||||
label _("Parsing the script failed.")
|
||||
label _("Parsing the script failed.") text_size gui._scale(40)
|
||||
|
||||
viewport:
|
||||
id "viewport"
|
||||
|
||||
+47
-1
@@ -514,6 +514,9 @@ statement_callbacks = [ ]
|
||||
# A list of file extensions that are blacklisted by autoreload.
|
||||
autoreload_blacklist = [ ".rpyc", ".rpymc", ".rpyb", ".pyc", ".pyo" ]
|
||||
|
||||
# A list of python modules that should be reloaded when appropriate.
|
||||
reload_modules = [ ]
|
||||
|
||||
# The layer dialogue is shown on.
|
||||
say_layer = "screens"
|
||||
|
||||
@@ -939,6 +942,48 @@ auto_clear_screenshot = True
|
||||
# Should Ren'Py allow duplicate labels.
|
||||
allow_duplicate_labels = False
|
||||
|
||||
# A map of font transform name to font transform function.
|
||||
font_transforms = { }
|
||||
|
||||
# A scaling factor that is applied to a truetype font.
|
||||
ftfont_scale = { }
|
||||
|
||||
# This is used to scale the ascent and descent of a font.
|
||||
ftfont_vertical_extent_scale = { }
|
||||
|
||||
|
||||
def say_attribute_transition_callback(*args):
|
||||
"""
|
||||
:args: (tag, attrs, mode)
|
||||
|
||||
Returns the say attribute transition to use, and the layer the transition
|
||||
should be applied to (with None being a valid layer.
|
||||
|
||||
Attrs is the list of tags/attributes of the incoming image.
|
||||
|
||||
Mode is one of "permanent", "temporary", or "restore".
|
||||
"""
|
||||
|
||||
return renpy.config.say_attribute_transition, renpy.config.say_attribute_transition_layer
|
||||
|
||||
|
||||
# Should say_attribute_transition_callback take attrs?
|
||||
say_attribute_transition_callback_attrs = True
|
||||
|
||||
# The function used by renpy.notify
|
||||
notify = None
|
||||
|
||||
# Should Ren'Py support a SL2 keyword after a Python statement?
|
||||
keyword_after_python = False
|
||||
|
||||
# A label Ren'Py should jump to if a load fails.
|
||||
load_failed_label = None
|
||||
|
||||
# If true, Ren'Py distributes mono to both stereo channels. If false,
|
||||
# it splits it 50/50.
|
||||
equal_mono = True
|
||||
|
||||
|
||||
del os
|
||||
del collections
|
||||
|
||||
@@ -958,4 +1003,5 @@ def init():
|
||||
global tts_function
|
||||
tts_function = renpy.display.tts.default_tts_function
|
||||
|
||||
import os
|
||||
global notify
|
||||
notify = renpy.exports.display_notify
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
|
||||
class Curry(object):
|
||||
"""
|
||||
Stores a callable and some arguments. When called, calls the
|
||||
@@ -50,6 +51,9 @@ class Curry(object):
|
||||
self.args == other.args and
|
||||
self.kwargs == other.kwargs)
|
||||
|
||||
def __ne__(self, other):
|
||||
return not (self == other)
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.callable) ^ hash(self.args) ^ hash(self.kwargs)
|
||||
|
||||
|
||||
+17
-2
@@ -313,6 +313,9 @@ class Displayable(renpy.object.Object):
|
||||
# Does this displayable have a tooltip?
|
||||
_tooltip = None
|
||||
|
||||
def __ne__(self, o):
|
||||
return not (self == o)
|
||||
|
||||
def __init__(self, focus=None, default=False, style='default', _args=None, tooltip=None, **properties):
|
||||
|
||||
global default_style
|
||||
@@ -2115,7 +2118,7 @@ class Interface(object):
|
||||
if not self.started:
|
||||
return
|
||||
|
||||
if background:
|
||||
if background and not renpy.emscripten:
|
||||
self.bgscreenshot_event.clear()
|
||||
self.bgscreenshot_needed = True
|
||||
|
||||
@@ -2191,6 +2194,9 @@ class Interface(object):
|
||||
|
||||
try:
|
||||
renpy.display.scale.image_save_unscaled(window, filename)
|
||||
if renpy.emscripten:
|
||||
import emscripten
|
||||
emscripten.run_script(r'''FSDownload('%s')''' % filename)
|
||||
return True
|
||||
except:
|
||||
if renpy.config.debug:
|
||||
@@ -2296,6 +2302,7 @@ class Interface(object):
|
||||
ev = pygame.event.poll()
|
||||
|
||||
if ev.type == pygame.NOEVENT:
|
||||
self.check_background_screenshot()
|
||||
# Seems to prevent the CPU from speeding up.
|
||||
renpy.display.draw.event_peek_sleep()
|
||||
return None
|
||||
@@ -2799,9 +2806,17 @@ class Interface(object):
|
||||
if not expensive:
|
||||
step += 1
|
||||
|
||||
# Step 4: Autosave.
|
||||
# Step 4: Preload images (on emscripten)
|
||||
elif step == 4:
|
||||
|
||||
if expensive and renpy.emscripten:
|
||||
renpy.display.im.cache.preload_thread_pass()
|
||||
|
||||
step += 1
|
||||
|
||||
# Step 5: Autosave.
|
||||
elif step == 5:
|
||||
|
||||
if not self.did_autosave:
|
||||
renpy.loadsave.autosave()
|
||||
renpy.persistent.check_update()
|
||||
|
||||
+53
-51
@@ -442,69 +442,71 @@ class Cache(object):
|
||||
self.preload_lock.wait()
|
||||
self.preload_lock.release()
|
||||
|
||||
while self.preloads and self.keep_preloading:
|
||||
def preload_thread_pass(self):
|
||||
|
||||
# If the size of the current generation is bigger than the
|
||||
# total cache size, stop preloading.
|
||||
with self.lock:
|
||||
|
||||
# If the cache is overfull, clean it out.
|
||||
if not self.cleanout():
|
||||
|
||||
if renpy.config.debug_image_cache:
|
||||
for i in self.preloads:
|
||||
renpy.display.ic_log.write("Overfull %r", i)
|
||||
|
||||
self.preloads = [ ]
|
||||
|
||||
break
|
||||
|
||||
try:
|
||||
image = self.preloads.pop(0)
|
||||
|
||||
if image not in self.preload_blacklist:
|
||||
try:
|
||||
self.preload_texture(image)
|
||||
except:
|
||||
self.preload_blacklist.add(image)
|
||||
except:
|
||||
pass
|
||||
while self.preloads and self.keep_preloading:
|
||||
|
||||
# If the size of the current generation is bigger than the
|
||||
# total cache size, stop preloading.
|
||||
with self.lock:
|
||||
self.cleanout()
|
||||
|
||||
# If we have time, preload pinned images.
|
||||
if self.keep_preloading and not renpy.game.less_memory:
|
||||
# If the cache is overfull, clean it out.
|
||||
if not self.cleanout():
|
||||
|
||||
workset = set(renpy.store._cache_pin_set)
|
||||
if renpy.config.debug_image_cache:
|
||||
for i in self.preloads:
|
||||
renpy.display.ic_log.write("Overfull %r", i)
|
||||
|
||||
# Remove things that are not in the workset from the pin cache,
|
||||
# and remove things that are in the workset from pin cache.
|
||||
for i in self.pin_cache.keys():
|
||||
self.preloads = [ ]
|
||||
|
||||
if i in workset:
|
||||
workset.remove(i)
|
||||
else:
|
||||
surf = self.pin_cache[i]
|
||||
break
|
||||
|
||||
del self.pin_cache[i]
|
||||
|
||||
# For each image in the worklist...
|
||||
for image in workset:
|
||||
|
||||
if image in self.preload_blacklist:
|
||||
continue
|
||||
|
||||
# If we have normal preloads, break out.
|
||||
if self.preloads:
|
||||
break
|
||||
try:
|
||||
image = self.preloads.pop(0)
|
||||
|
||||
if image not in self.preload_blacklist:
|
||||
try:
|
||||
surf = image.load()
|
||||
self.pin_cache[image] = surf
|
||||
renpy.display.draw.load_texture(surf)
|
||||
self.preload_texture(image)
|
||||
except:
|
||||
self.preload_blacklist.add(image)
|
||||
except:
|
||||
pass
|
||||
|
||||
with self.lock:
|
||||
self.cleanout()
|
||||
|
||||
# If we have time, preload pinned images.
|
||||
if self.keep_preloading and not renpy.game.less_memory:
|
||||
|
||||
workset = set(renpy.store._cache_pin_set)
|
||||
|
||||
# Remove things that are not in the workset from the pin cache,
|
||||
# and remove things that are in the workset from pin cache.
|
||||
for i in self.pin_cache.keys():
|
||||
|
||||
if i in workset:
|
||||
workset.remove(i)
|
||||
else:
|
||||
surf = self.pin_cache[i]
|
||||
|
||||
del self.pin_cache[i]
|
||||
|
||||
# For each image in the worklist...
|
||||
for image in workset:
|
||||
|
||||
if image in self.preload_blacklist:
|
||||
continue
|
||||
|
||||
# If we have normal preloads, break out.
|
||||
if self.preloads:
|
||||
break
|
||||
|
||||
try:
|
||||
surf = image.load()
|
||||
self.pin_cache[image] = surf
|
||||
renpy.display.draw.load_texture(surf)
|
||||
except:
|
||||
self.preload_blacklist.add(image)
|
||||
|
||||
def add_load_log(self, filename):
|
||||
|
||||
|
||||
@@ -598,6 +598,9 @@ class DynamicImage(renpy.display.core.Displayable):
|
||||
if self.name != o.name:
|
||||
return False
|
||||
|
||||
if self._uses_scope and (self.target != o.target):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def _target(self):
|
||||
|
||||
@@ -1520,6 +1520,9 @@ class Side(Container):
|
||||
super(Side, self)._clear()
|
||||
self.sized = False
|
||||
|
||||
def per_interact(self):
|
||||
self.sized = False
|
||||
|
||||
def render(self, width, height, st, at):
|
||||
|
||||
pos_d = { }
|
||||
|
||||
@@ -866,6 +866,12 @@ cdef class Render:
|
||||
"""
|
||||
|
||||
(x, y, w, h) = rect
|
||||
|
||||
x = int(x)
|
||||
y = int(y)
|
||||
w = int(w)
|
||||
h = int(h)
|
||||
|
||||
rv = Render(w, h)
|
||||
|
||||
reverse = self.reverse
|
||||
|
||||
+13
-4
@@ -197,6 +197,8 @@ class Screen(renpy.object.Object):
|
||||
This class stores information about the screen.
|
||||
"""
|
||||
|
||||
sensitive = "True"
|
||||
|
||||
def __init__(self,
|
||||
name,
|
||||
function,
|
||||
@@ -207,7 +209,8 @@ class Screen(renpy.object.Object):
|
||||
variant=None,
|
||||
parameters=False,
|
||||
location=None,
|
||||
layer="screens"):
|
||||
layer="screens",
|
||||
sensitive="True"):
|
||||
|
||||
# The name of this screen.
|
||||
if isinstance(name, basestring):
|
||||
@@ -256,6 +259,9 @@ class Screen(renpy.object.Object):
|
||||
# The layer the screen will be shown on.
|
||||
self.layer = layer
|
||||
|
||||
# Is this screen sensitive? An expression.
|
||||
self.sensitive = sensitive
|
||||
|
||||
global prepared
|
||||
global analyzed
|
||||
|
||||
@@ -654,9 +660,6 @@ class ScreenDisplayable(renpy.display.layout.Container):
|
||||
if self.profile.debug:
|
||||
profile_log.write("\n")
|
||||
|
||||
if self.phase == SHOW:
|
||||
self.phase = UPDATE
|
||||
|
||||
return self.widgets
|
||||
|
||||
def render(self, w, h, st, at):
|
||||
@@ -664,6 +667,9 @@ class ScreenDisplayable(renpy.display.layout.Container):
|
||||
if not self.child:
|
||||
self.update()
|
||||
|
||||
if self.phase == SHOW:
|
||||
self.phase = UPDATE
|
||||
|
||||
try:
|
||||
push_current_screen(self)
|
||||
child = renpy.display.render.render(self.child, w, h, st, at)
|
||||
@@ -691,6 +697,9 @@ class ScreenDisplayable(renpy.display.layout.Container):
|
||||
if (self.phase == OLD) or (self.phase == HIDE):
|
||||
return
|
||||
|
||||
if not renpy.python.py_eval(self.screen.sensitive):
|
||||
return
|
||||
|
||||
try:
|
||||
push_current_screen(self)
|
||||
|
||||
|
||||
+4
-1
@@ -107,6 +107,9 @@ class LineLogEntry(object):
|
||||
|
||||
return (self.filename == other.filename) and (self.line == other.line) and (self.node is other.node)
|
||||
|
||||
def __ne__(self, other):
|
||||
return not (self == other)
|
||||
|
||||
|
||||
class Context(renpy.object.Object):
|
||||
"""
|
||||
@@ -530,7 +533,7 @@ class Context(renpy.object.Object):
|
||||
renpy.game.log.begin(force=force_rollback)
|
||||
|
||||
if self.rollback and self.force_checkpoint:
|
||||
renpy.game.log.checkpoint(hard=False)
|
||||
renpy.game.log.force_checkpoint = True
|
||||
self.force_checkpoint = False
|
||||
|
||||
self.seen = False
|
||||
|
||||
+39
-7
@@ -114,6 +114,8 @@ from renpy.ast import eval_who
|
||||
|
||||
from renpy.loader import add_python_directory
|
||||
|
||||
from renpy.lint import try_compile, try_eval
|
||||
|
||||
renpy_pure("ParameterizedText")
|
||||
renpy_pure("Keymap")
|
||||
renpy_pure("has_screen")
|
||||
@@ -182,6 +184,7 @@ def public_api():
|
||||
eval_who
|
||||
is_selected, is_sensitive
|
||||
add_python_directory
|
||||
try_compile, try_eval
|
||||
|
||||
|
||||
del public_api
|
||||
@@ -228,7 +231,7 @@ def in_rollback():
|
||||
Returns true if the game has been rolled back.
|
||||
"""
|
||||
|
||||
return renpy.game.log.in_rollback()
|
||||
return renpy.game.log.in_rollback() or renpy.game.after_rollback
|
||||
|
||||
|
||||
def can_rollback():
|
||||
@@ -1078,10 +1081,16 @@ def display_menu(items,
|
||||
renpy.exports.mode(type)
|
||||
choice_for_skipping()
|
||||
|
||||
# The possible choices in the menu.
|
||||
choices = [ val for label, val in items ]
|
||||
while None in choices:
|
||||
choices.remove(None)
|
||||
choices = [ ]
|
||||
|
||||
for _, val in items:
|
||||
if isinstance(val, renpy.ui.ChoiceReturn):
|
||||
val = val.value
|
||||
|
||||
if val is None:
|
||||
continue
|
||||
|
||||
choices.append(val)
|
||||
|
||||
# Roll forward.
|
||||
roll_forward = renpy.exports.roll_forward_info()
|
||||
@@ -1463,12 +1472,12 @@ def movie_cutscene(filename, delay=None, loops=0, stop_music=True):
|
||||
"""
|
||||
:doc: movie_cutscene
|
||||
|
||||
This displays an MPEG-1 cutscene for the specified number of
|
||||
This displays a movie cutscene for the specified number of
|
||||
seconds. The user can click to interrupt the cutscene.
|
||||
Overlays and Underlays are disabled for the duration of the cutscene.
|
||||
|
||||
`filename`
|
||||
The name of a file containing an MPEG-1 movie.
|
||||
The name of a file containing any movie playable by Ren'Py.
|
||||
|
||||
`delay`
|
||||
The number of seconds to wait before ending the cutscene.
|
||||
@@ -2305,8 +2314,10 @@ def free_memory():
|
||||
Attempts to free some memory. Useful before running a renpygame-based
|
||||
minigame.
|
||||
"""
|
||||
|
||||
force_full_redraw()
|
||||
renpy.display.interface.kill_textures_and_surfaces()
|
||||
renpy.text.font.free_memory()
|
||||
|
||||
|
||||
@renpy_pure
|
||||
@@ -2370,6 +2381,14 @@ def pop_error_handler():
|
||||
|
||||
|
||||
def error(msg):
|
||||
"""
|
||||
:doc: lint
|
||||
|
||||
Reports `msg`, a string, as as error for the user. This is logged as a
|
||||
parse or lint error when approprate, and otherwise it is raised as an
|
||||
exception.
|
||||
"""
|
||||
|
||||
_error_handlers[-1](msg)
|
||||
|
||||
|
||||
@@ -2990,6 +3009,19 @@ def notify(message):
|
||||
|
||||
Only one notification is displayed at a time. If a second notification
|
||||
is displayed, the first notification is replaced.
|
||||
|
||||
This function just calls :var:`config.notify`, allowing its implementation
|
||||
to be replaced by assigning a new function to that variable.
|
||||
"""
|
||||
|
||||
renpy.config.notify(message)
|
||||
|
||||
|
||||
def display_notify(message):
|
||||
"""
|
||||
:doc: other
|
||||
|
||||
The default implementation of :func:`renpy.notify`.
|
||||
"""
|
||||
|
||||
hide_screen('notify')
|
||||
|
||||
+1
-1
@@ -89,7 +89,7 @@ cdef class Environ:
|
||||
cdef void imageblend(self, double fraction, int ramp)
|
||||
cdef void set_vertex(self, float *vertices)
|
||||
cdef void set_texture(self, int unit, float *coords)
|
||||
cdef void set_color(self, float r, float g, float b, float a)
|
||||
cdef void set_color(self, double r, double g, double b, double a)
|
||||
cdef void set_clip(self, tuple clip_box, GLDraw draw)
|
||||
cdef void unset_clip(self, GLDraw draw)
|
||||
cdef void ortho(self, double left, double right, double bottom, double top, double near, double far)
|
||||
|
||||
+13
-2
@@ -528,6 +528,14 @@ cdef class GLDraw:
|
||||
self.always_opaque = True
|
||||
gltexture.use_gles()
|
||||
|
||||
elif renpy.emscripten:
|
||||
# give back control to browser regularly
|
||||
self.redraw_period = 0.1
|
||||
# True messes alpha imagemap/imagebutton mouseover
|
||||
self.always_opaque = False
|
||||
# WebGL is GLES
|
||||
gltexture.use_gles()
|
||||
|
||||
else:
|
||||
gltexture.use_gl()
|
||||
|
||||
@@ -637,7 +645,7 @@ cdef class GLDraw:
|
||||
# ANGLE from working with fbo on Windows.
|
||||
|
||||
use_fbo = (
|
||||
renpy.ios or renpy.android or (EGL and not ANGLE) or
|
||||
renpy.ios or renpy.android or renpy.emscripten or (EGL and not ANGLE) or
|
||||
use_subsystem(
|
||||
glrtt_fbo,
|
||||
"RENPY_GL_RTT",
|
||||
@@ -1206,6 +1214,9 @@ cdef class GLDraw:
|
||||
|
||||
what.kill()
|
||||
|
||||
if renpy.emscripten:
|
||||
renpy.display.interface.force_redraw = True
|
||||
|
||||
return a
|
||||
|
||||
|
||||
@@ -1492,7 +1503,7 @@ cdef class Environ(object):
|
||||
Sets the array of texture coordinates for unit `unit`.
|
||||
"""
|
||||
|
||||
cdef void set_color(self, float r, float g, float b, float a):
|
||||
cdef void set_color(self, double r, double g, double b, double a):
|
||||
"""
|
||||
Sets the color to be shown.
|
||||
"""
|
||||
|
||||
@@ -319,7 +319,7 @@ cdef class FixedFunctionEnviron(Environ):
|
||||
else:
|
||||
glDisableClientState(GL_TEXTURE_COORD_ARRAY)
|
||||
|
||||
cdef void set_color(self, float r, float g, float b, float a):
|
||||
cdef void set_color(self, double r, double g, double b, double a):
|
||||
glColor4f(r, g, b, a)
|
||||
|
||||
cdef void ortho(self, double left, double right, double bottom, double top, double near, double far):
|
||||
|
||||
@@ -182,7 +182,7 @@ cdef class LimitedEnviron(Environ):
|
||||
else:
|
||||
glDisableClientState(GL_TEXTURE_COORD_ARRAY)
|
||||
|
||||
cdef void set_color(self, float r, float g, float b, float a):
|
||||
cdef void set_color(self, double r, double g, double b, double a):
|
||||
glColor4f(r, g, b, a)
|
||||
|
||||
|
||||
|
||||
@@ -566,7 +566,7 @@ cdef class ShaderEnviron(Environ):
|
||||
else:
|
||||
glDisableVertexAttribArrayARB(tex)
|
||||
|
||||
cdef void set_color(self, float r, float g, float b, float a):
|
||||
cdef void set_color(self, double r, double g, double b, double a):
|
||||
glUniform4fARB(self.program.Color, r, g, b, a)
|
||||
|
||||
cdef void ortho(self, double left, double right, double bottom, double top, double near, double far):
|
||||
|
||||
+37
-1
@@ -77,8 +77,26 @@ def add(msg):
|
||||
print(unicode(msg).encode('utf-8'))
|
||||
|
||||
|
||||
# Trys to evaluate an expression, announcing an error if it fails.
|
||||
# Tries to evaluate an expression, announcing an error if it fails.
|
||||
def try_eval(where, expr, additional=None):
|
||||
"""
|
||||
:doc: lint
|
||||
|
||||
Tries to evaluate an expression, and writes an error to lint.txt if
|
||||
it fails.
|
||||
|
||||
`where`
|
||||
A string giving the location the expression is found. Used to
|
||||
generate an error message of the form "Could not evaluate `expr`
|
||||
in `where`."
|
||||
|
||||
`expr`
|
||||
The expression to try evaluating.
|
||||
|
||||
`additional`
|
||||
If given, an additional line of information that is addded to the
|
||||
error message.
|
||||
"""
|
||||
|
||||
# Make sure the expression compiles.
|
||||
try_compile(where, expr)
|
||||
@@ -105,6 +123,24 @@ def try_eval(where, expr, additional=None):
|
||||
|
||||
|
||||
def try_compile(where, expr, additional=None):
|
||||
"""
|
||||
:doc: lint
|
||||
|
||||
Tries to compile an expression, and writes an error to lint.txt if
|
||||
it fails.
|
||||
|
||||
`where`
|
||||
A string giving the location the expression is found. Used to
|
||||
generate an error message of the form "Could not evaluate `expr`
|
||||
in `where`."
|
||||
|
||||
`expr`
|
||||
The expression to try compiling.
|
||||
|
||||
`additional`
|
||||
If given, an additional line of information that is addded to the
|
||||
error message.
|
||||
"""
|
||||
|
||||
try:
|
||||
renpy.python.py_compile_eval_bytecode(expr)
|
||||
|
||||
+15
-14
@@ -465,6 +465,14 @@ def load_core(name):
|
||||
if rv is not None:
|
||||
return rv
|
||||
|
||||
# Look for the file directly.
|
||||
if not renpy.config.force_archives:
|
||||
try:
|
||||
fn = transfn(name)
|
||||
return open_file(fn, "rb")
|
||||
except:
|
||||
pass
|
||||
|
||||
# Look for the file in the apk.
|
||||
for apk in apks:
|
||||
prefixed_name = "/".join("x-" + i for i in name.split("/"))
|
||||
@@ -474,14 +482,6 @@ def load_core(name):
|
||||
except IOError:
|
||||
pass
|
||||
|
||||
# Look for the file directly.
|
||||
if not renpy.config.force_archives:
|
||||
try:
|
||||
fn = transfn(name)
|
||||
return open_file(fn, "rb")
|
||||
except:
|
||||
pass
|
||||
|
||||
# Look for it in archive files.
|
||||
for prefix, index in archives:
|
||||
if not name in index:
|
||||
@@ -586,11 +586,6 @@ def loadable_core(name):
|
||||
if name in loadable_cache:
|
||||
return loadable_cache[name]
|
||||
|
||||
for apk in apks:
|
||||
prefixed_name = "/".join("x-" + i for i in name.split("/"))
|
||||
if prefixed_name in apk.info:
|
||||
return True
|
||||
|
||||
try:
|
||||
transfn(name)
|
||||
loadable_cache[name] = True
|
||||
@@ -598,6 +593,12 @@ def loadable_core(name):
|
||||
except:
|
||||
pass
|
||||
|
||||
for apk in apks:
|
||||
prefixed_name = "/".join("x-" + i for i in name.split("/"))
|
||||
if prefixed_name in apk.info:
|
||||
loadable_cache[name] = True
|
||||
return True
|
||||
|
||||
for _prefix, index in archives:
|
||||
if name in index:
|
||||
loadable_cache[name] = True
|
||||
@@ -743,7 +744,7 @@ class RenpyImporter(object):
|
||||
code = compile(source, filename, 'exec', renpy.python.old_compile_flags, 1)
|
||||
exec code in mod.__dict__
|
||||
|
||||
return mod
|
||||
return sys.modules[fullname]
|
||||
|
||||
def get_data(self, filename):
|
||||
return load(filename).read()
|
||||
|
||||
+12
-4
@@ -179,7 +179,7 @@ def save_dump(roots, log):
|
||||
|
||||
return size
|
||||
|
||||
f = file("save_dump.txt", "w")
|
||||
f, _ = renpy.error.open_error_file("save_dump.txt", "w")
|
||||
|
||||
visit(roots, "roots")
|
||||
visit(log, "log")
|
||||
@@ -474,6 +474,9 @@ def autosave_thread(take_screenshot):
|
||||
|
||||
finally:
|
||||
autosave_not_running.set()
|
||||
if renpy.emscripten:
|
||||
import emscripten
|
||||
emscripten.syncfs()
|
||||
|
||||
|
||||
def autosave():
|
||||
@@ -550,9 +553,14 @@ def force_autosave(take_screenshot=False, block=False):
|
||||
return
|
||||
|
||||
autosave_not_running.clear()
|
||||
t = threading.Thread(target=autosave_thread, args=(take_screenshot,))
|
||||
t.daemon = True
|
||||
t.start()
|
||||
|
||||
if not renpy.emscripten:
|
||||
t = threading.Thread(target=autosave_thread, args=(take_screenshot,))
|
||||
t.daemon = True
|
||||
t.start()
|
||||
else:
|
||||
import emscripten
|
||||
emscripten.async_call(autosave_thread, take_screenshot, -1)
|
||||
|
||||
|
||||
################################################################################
|
||||
|
||||
+5
-1
@@ -148,7 +148,11 @@ class LogFile(object):
|
||||
if self.open():
|
||||
|
||||
if not self.raw_write:
|
||||
s = s % args
|
||||
try:
|
||||
s = s % args
|
||||
except:
|
||||
s = repr((s,) + args)
|
||||
|
||||
s += "\n"
|
||||
|
||||
if not isinstance(s, unicode):
|
||||
|
||||
+12
-7
@@ -258,13 +258,6 @@ def main():
|
||||
game.basepath = renpy.config.gamedir
|
||||
renpy.config.searchpath = [ renpy.config.gamedir ]
|
||||
|
||||
if renpy.android and ("ANDROID_PUBLIC" in os.environ):
|
||||
|
||||
android_game = os.path.join(os.environ["ANDROID_PUBLIC"], "game")
|
||||
|
||||
if os.path.exists(android_game):
|
||||
renpy.config.searchpath.insert(0, android_game)
|
||||
|
||||
# Find the common directory.
|
||||
commondir = __main__.path_to_common(renpy.config.renpy_base) # E1101 @UndefinedVariable
|
||||
|
||||
@@ -274,10 +267,22 @@ def main():
|
||||
else:
|
||||
renpy.config.commondir = None
|
||||
|
||||
# Add path from env variable, if any
|
||||
if "RENPY_SEARCHPATH" in os.environ:
|
||||
renpy.config.searchpath.extend(os.environ["RENPY_SEARCHPATH"].split("::"))
|
||||
|
||||
if renpy.android:
|
||||
renpy.config.searchpath = [ ]
|
||||
renpy.config.commondir = None
|
||||
|
||||
if "ANDROID_PUBLIC" in os.environ:
|
||||
android_game = os.path.join(os.environ["ANDROID_PUBLIC"], "game")
|
||||
|
||||
print("Android searchpath: ", android_game)
|
||||
|
||||
if os.path.exists(android_game):
|
||||
renpy.config.searchpath.insert(0, android_game)
|
||||
|
||||
# Load Ren'Py extensions.
|
||||
for dir in renpy.config.searchpath: # @ReservedAssignment
|
||||
for fn in os.listdir(dir):
|
||||
|
||||
@@ -51,6 +51,20 @@ from renpy.python import store_eval as eval
|
||||
|
||||
from renpy.display.core import absolute
|
||||
|
||||
_print = print
|
||||
|
||||
|
||||
def print(*args, **kwargs):
|
||||
"""
|
||||
:undocumented:
|
||||
|
||||
This is a variant of the print function that forces a checkpoint
|
||||
at the start of the next statement, so that it can't be rolled past.
|
||||
"""
|
||||
|
||||
renpy.game.context().force_checkpoint = True
|
||||
_print(*args, **kwargs)
|
||||
|
||||
|
||||
def _(s):
|
||||
"""
|
||||
|
||||
+6
-2
@@ -722,7 +722,7 @@ class Lexer(object):
|
||||
|
||||
def string(self):
|
||||
"""
|
||||
Lexes a string, and returns the string to the user, or none if
|
||||
Lexes a string, and returns the string to the user, or None if
|
||||
no string could be found. This also takes care of expanding
|
||||
escapes and collapsing whitespace.
|
||||
|
||||
@@ -1190,7 +1190,7 @@ class Lexer(object):
|
||||
if not text:
|
||||
return None
|
||||
|
||||
return renpy.ast.PyExpr(self.text[start:self.pos].strip(), self.filename, self.number)
|
||||
return renpy.ast.PyExpr(text, self.filename, self.number)
|
||||
|
||||
def comma_expression(self):
|
||||
"""
|
||||
@@ -1222,6 +1222,10 @@ class Lexer(object):
|
||||
|
||||
self.line, self.filename, self.number, self.text, self.subblock, self.pos = state
|
||||
self.word_cache_pos = -1
|
||||
if self.line < len(self.block):
|
||||
self.eob = False
|
||||
else:
|
||||
self.eob = True
|
||||
|
||||
def get_location(self):
|
||||
"""
|
||||
|
||||
@@ -108,7 +108,7 @@ def analyze():
|
||||
dt[1],
|
||||
dt[2],
|
||||
dt[3],
|
||||
event.format(*args),
|
||||
event.format(*args).replace("%", "%%"),
|
||||
)
|
||||
s = s.encode("utf-8")
|
||||
|
||||
|
||||
@@ -153,6 +153,15 @@ Preference("gl_framerate", None, (int, type(None)))
|
||||
# Do we allow tearing?
|
||||
Preference("gl_tearing", False)
|
||||
|
||||
# The font transformation used.
|
||||
Preference("font_transform", None, (type(None), basestring))
|
||||
|
||||
# An adjustment applied to font size.
|
||||
Preference("font_size", 1.0)
|
||||
|
||||
# An adjustment applied to font line spacing.
|
||||
Preference("font_line_spacing", 1.0)
|
||||
|
||||
|
||||
class Preferences(renpy.object.Object):
|
||||
"""
|
||||
@@ -233,6 +242,9 @@ class Preferences(renpy.object.Object):
|
||||
def __eq__(self, other):
|
||||
return vars(self) == vars(other)
|
||||
|
||||
def __ne__(self, other):
|
||||
return not (self == other)
|
||||
|
||||
|
||||
renpy.game.Preferences = Preferences
|
||||
renpy.game.preferences = Preferences()
|
||||
|
||||
+50
-20
@@ -1084,7 +1084,7 @@ class DetRandom(random.Random):
|
||||
Resets the RNG, removing all of the pushbacked numbers.
|
||||
"""
|
||||
|
||||
self.stack = [ ]
|
||||
del self.stack[:]
|
||||
|
||||
def Random(self, seed=None):
|
||||
"""
|
||||
@@ -1237,7 +1237,8 @@ class Rollback(renpy.object.Object):
|
||||
print("Removing unreachable:", o, file=renpy.log.real_stdout)
|
||||
pass
|
||||
|
||||
self.objects = new_objects
|
||||
del self.objects[:]
|
||||
self.objects.extend(new_objects)
|
||||
|
||||
return True
|
||||
|
||||
@@ -1308,6 +1309,7 @@ class RollbackLog(renpy.object.Object):
|
||||
|
||||
nosave = [ 'old_store', 'mutated', 'identifier_cache' ]
|
||||
identifier_cache = None
|
||||
force_checkpoint = False
|
||||
|
||||
def __init__(self):
|
||||
|
||||
@@ -1337,6 +1339,10 @@ class RollbackLog(renpy.object.Object):
|
||||
# reset?
|
||||
self.did_interaction = True
|
||||
|
||||
# Should we force a checkpoint before completing the current
|
||||
# statement.
|
||||
self.force_checkpoint = False
|
||||
|
||||
def after_setstate(self):
|
||||
self.mutated = { }
|
||||
self.rolled_forward = False
|
||||
@@ -1401,8 +1407,8 @@ class RollbackLog(renpy.object.Object):
|
||||
begin_stores()
|
||||
|
||||
# If the log is too long, prune it.
|
||||
if len(self.log) > renpy.config.rollback_length:
|
||||
self.log = self.log[-renpy.config.rollback_length:]
|
||||
while len(self.log) > renpy.config.rollback_length:
|
||||
self.log.pop(0)
|
||||
|
||||
# check for the end of fixed rollback
|
||||
if self.log and self.log[-1] == self.current:
|
||||
@@ -1420,7 +1426,7 @@ class RollbackLog(renpy.object.Object):
|
||||
|
||||
self.log.append(self.current)
|
||||
|
||||
self.mutated = { }
|
||||
self.mutated.clear()
|
||||
|
||||
# Flag a mutation as having happened. This is used by the
|
||||
# save code.
|
||||
@@ -1450,6 +1456,10 @@ class RollbackLog(renpy.object.Object):
|
||||
Should be true if called from begin().
|
||||
"""
|
||||
|
||||
if self.force_checkpoint:
|
||||
self.checkpoint(hard=False)
|
||||
self.force_checkpoint = False
|
||||
|
||||
# Update self.current.stores with the changes from each store.
|
||||
# Also updates .ever_been_changed.
|
||||
for name, sd in store_dicts.iteritems():
|
||||
@@ -1460,7 +1470,7 @@ class RollbackLog(renpy.object.Object):
|
||||
|
||||
for _i in xrange(4):
|
||||
|
||||
self.current.objects = [ ]
|
||||
del self.current.objects[:]
|
||||
|
||||
try:
|
||||
for _k, v in self.mutated.iteritems():
|
||||
@@ -1582,7 +1592,7 @@ class RollbackLog(renpy.object.Object):
|
||||
self.forward.pop(0)
|
||||
else:
|
||||
self.current.forward = data
|
||||
self.forward = [ ]
|
||||
del self.forward[:]
|
||||
|
||||
elif data is not None:
|
||||
if self.forward:
|
||||
@@ -1591,12 +1601,12 @@ class RollbackLog(renpy.object.Object):
|
||||
fwd_name, fwd_data = self.forward[0]
|
||||
|
||||
if (self.current.context.current == fwd_name
|
||||
and data == fwd_data
|
||||
and (keep_rollback or self.rolled_forward)
|
||||
):
|
||||
and data == fwd_data
|
||||
and (keep_rollback or self.rolled_forward)
|
||||
):
|
||||
self.forward.pop(0)
|
||||
else:
|
||||
self.forward = [ ]
|
||||
del self.forward[:]
|
||||
|
||||
# Log the data in case we roll back again.
|
||||
self.current.forward = data
|
||||
@@ -1619,7 +1629,7 @@ class RollbackLog(renpy.object.Object):
|
||||
renpy.game.context().force_checkpoint = True
|
||||
|
||||
if purge:
|
||||
self.log = [ ]
|
||||
del self.log[:]
|
||||
|
||||
def retain_after_load(self):
|
||||
"""
|
||||
@@ -1645,6 +1655,25 @@ class RollbackLog(renpy.object.Object):
|
||||
|
||||
return self.rollback_limit > 0
|
||||
|
||||
def load_failed(self):
|
||||
"""
|
||||
This is called to try to recover when rollback fails.
|
||||
"""
|
||||
|
||||
if not renpy.config.load_failed_label:
|
||||
raise Exception("Couldn't find a place to stop rolling back. Perhaps the script changed in an incompatible way?")
|
||||
|
||||
rb = self.log.pop()
|
||||
rb.rollback()
|
||||
|
||||
while renpy.exports.call_stack_depth():
|
||||
renpy.exports.pop_call()
|
||||
|
||||
renpy.game.contexts[0].force_checkpoint = True
|
||||
renpy.game.contexts[0].goto_label(renpy.config.load_failed_label)
|
||||
|
||||
raise renpy.game.RestartTopContext()
|
||||
|
||||
def rollback(self, checkpoints, force=False, label=None, greedy=True, on_load=False, abnormal=True, current_label=None):
|
||||
"""
|
||||
This rolls the system back to the first valid rollback point
|
||||
@@ -1671,7 +1700,7 @@ class RollbackLog(renpy.object.Object):
|
||||
load. Used to implement .retain_after_load()
|
||||
|
||||
`abnormal`
|
||||
If true, treats this as an abnormal event, suppresisng rollback
|
||||
If true, treats this as an abnormal event, suppressing transitions
|
||||
and so on.
|
||||
|
||||
`current_label`
|
||||
@@ -1708,15 +1737,16 @@ class RollbackLog(renpy.object.Object):
|
||||
break
|
||||
|
||||
else:
|
||||
if force:
|
||||
raise Exception("Couldn't find a place to stop rolling back. Perhaps the script changed in an incompatible way?")
|
||||
|
||||
# Otherwise, just give up.
|
||||
|
||||
print("Can't find a place to rollback to. Not rolling back.")
|
||||
|
||||
revlog.reverse()
|
||||
self.log = self.log + revlog
|
||||
self.log.extend(revlog)
|
||||
|
||||
if force:
|
||||
self.load_failed()
|
||||
else:
|
||||
print("Can't find a place to rollback to. Not rolling back.")
|
||||
|
||||
return
|
||||
|
||||
force_checkpoint = False
|
||||
@@ -1783,7 +1813,7 @@ class RollbackLog(renpy.object.Object):
|
||||
# If necessary, reset the RNG.
|
||||
if force:
|
||||
rng.reset()
|
||||
self.forward = [ ]
|
||||
del self.forward[:]
|
||||
|
||||
# Flag that we're in the transition immediately after a rollback.
|
||||
renpy.game.after_rollback = abnormal
|
||||
|
||||
@@ -370,6 +370,9 @@ class FileLocation(object):
|
||||
|
||||
return self.directory == other.directory
|
||||
|
||||
def __ne__(self, other):
|
||||
return not (self == other)
|
||||
|
||||
|
||||
class MultiLocation(object):
|
||||
"""
|
||||
@@ -504,6 +507,10 @@ class MultiLocation(object):
|
||||
|
||||
return self.locations == other.locations
|
||||
|
||||
def __ne__(self, other):
|
||||
return not (self == other)
|
||||
|
||||
|
||||
|
||||
# The thread that scans locations every few seconds.
|
||||
scan_thread = None
|
||||
|
||||
@@ -263,6 +263,11 @@ class Script(object):
|
||||
initcode = [ ]
|
||||
|
||||
for fn, dir in script_files: # @ReservedAssignment
|
||||
# Mitigate "busy script" warning from the browser
|
||||
if renpy.emscripten:
|
||||
import emscripten
|
||||
emscripten.sleep(0)
|
||||
|
||||
self.load_appropriate_file(".rpyc", ".rpy", dir, fn, initcode)
|
||||
|
||||
# Make the sort stable.
|
||||
|
||||
@@ -301,6 +301,13 @@ class SLNode(object):
|
||||
|
||||
return False
|
||||
|
||||
def has_python(self):
|
||||
"""
|
||||
Returns true if this node is Python or has a python node as a child.
|
||||
"""
|
||||
|
||||
return False
|
||||
|
||||
|
||||
# A sentinel used to indicate a keyword argument was not given.
|
||||
NotGiven = renpy.object.Sentinel("NotGiven")
|
||||
@@ -451,6 +458,9 @@ class SLBlock(SLNode):
|
||||
|
||||
return False
|
||||
|
||||
def has_python(self):
|
||||
return any(i.has_python() for i in self.children)
|
||||
|
||||
|
||||
list_or_tuple = (list, tuple)
|
||||
|
||||
@@ -1265,6 +1275,9 @@ class SLIf(SLNode):
|
||||
|
||||
return False
|
||||
|
||||
def has_python(self):
|
||||
return any(i[1].has_python() for i in self.entries)
|
||||
|
||||
|
||||
class SLShowIf(SLNode):
|
||||
"""
|
||||
@@ -1354,6 +1367,9 @@ class SLShowIf(SLNode):
|
||||
|
||||
return False
|
||||
|
||||
def has_python(self):
|
||||
return any(i[1].has_python() for i in self.entries)
|
||||
|
||||
|
||||
class SLFor(SLBlock):
|
||||
"""
|
||||
@@ -1514,6 +1530,9 @@ class SLPython(SLNode):
|
||||
self.constant = NOT_CONST
|
||||
self.last_keyword = True
|
||||
|
||||
def has_python(self):
|
||||
return True
|
||||
|
||||
|
||||
class SLPass(SLNode):
|
||||
|
||||
@@ -1559,6 +1578,9 @@ class SLDefault(SLNode):
|
||||
|
||||
scope[variable] = eval(self.expr, context.globals, scope)
|
||||
|
||||
def has_python(self):
|
||||
return True
|
||||
|
||||
|
||||
class SLUse(SLNode):
|
||||
|
||||
@@ -1862,6 +1884,7 @@ class SLScreen(SLBlock):
|
||||
analysis = None
|
||||
|
||||
layer = "'screens'"
|
||||
sensitive = "True"
|
||||
|
||||
def __init__(self, loc):
|
||||
|
||||
@@ -1885,6 +1908,9 @@ class SLScreen(SLBlock):
|
||||
# Should we predict this screen?
|
||||
self.predict = "None" # expr.
|
||||
|
||||
# Should this screen be sensitive.
|
||||
self.sensitive = "True"
|
||||
|
||||
# The parameters this screen takes.
|
||||
self.parameters = None
|
||||
|
||||
@@ -1905,6 +1931,7 @@ class SLScreen(SLBlock):
|
||||
rv.variant = self.variant
|
||||
rv.predict = self.predict
|
||||
rv.parameters = self.parameters
|
||||
rv.sensitive = self.sensitive
|
||||
|
||||
rv.prepared = False
|
||||
rv.analysis = None
|
||||
@@ -1928,6 +1955,7 @@ class SLScreen(SLBlock):
|
||||
parameters=self.parameters,
|
||||
location=self.location,
|
||||
layer=renpy.python.py_eval(self.layer),
|
||||
sensitive=self.sensitive,
|
||||
)
|
||||
|
||||
def analyze(self, analysis):
|
||||
|
||||
@@ -437,14 +437,14 @@ def sl2add(d, replaces=None, scope=None, **kwargs):
|
||||
|
||||
Transform = renpy.display.motion.Transform
|
||||
|
||||
if kwargs:
|
||||
rv = Transform(child=d, **kwargs)
|
||||
rv._main = d
|
||||
|
||||
if (replaces is not None) and isinstance(rv, Transform):
|
||||
rv.take_state(replaces)
|
||||
rv.take_execution_state(replaces)
|
||||
|
||||
if kwargs:
|
||||
rv = Transform(child=d, **kwargs)
|
||||
rv._main = d
|
||||
|
||||
return rv
|
||||
|
||||
|
||||
|
||||
+39
-22
@@ -25,6 +25,8 @@ import collections
|
||||
import renpy.sl2
|
||||
import renpy.sl2.slast as slast
|
||||
|
||||
from ast import literal_eval
|
||||
|
||||
# A tuple of style prefixes that we know of.
|
||||
STYLE_PREFIXES = [
|
||||
'',
|
||||
@@ -166,23 +168,23 @@ class Parser(object):
|
||||
elif isinstance(i, Parser):
|
||||
self.children[i.name] = i
|
||||
|
||||
def parse_statement(self, loc, l, layout_mode=False):
|
||||
def parse_statement(self, loc, l, layout_mode=False, keyword=True):
|
||||
word = l.word() or l.match(r'\$')
|
||||
|
||||
if word and word in self.children:
|
||||
if layout_mode:
|
||||
c = self.children[word].parse_layout(loc, l, self)
|
||||
c = self.children[word].parse_layout(loc, l, self, keyword)
|
||||
else:
|
||||
c = self.children[word].parse(loc, l, self)
|
||||
c = self.children[word].parse(loc, l, self, keyword)
|
||||
|
||||
return c
|
||||
else:
|
||||
return None
|
||||
|
||||
def parse_layout(self, loc, l, parent):
|
||||
def parse_layout(self, loc, l, parent, keyword):
|
||||
l.error("The %s statement cannot be used as a container for the has statement." % self.name)
|
||||
|
||||
def parse(self, loc, l, parent):
|
||||
def parse(self, loc, l, parent, keyword):
|
||||
"""
|
||||
This is expected to parse a function statement, and to return
|
||||
a list of python ast statements.
|
||||
@@ -199,7 +201,7 @@ class Parser(object):
|
||||
|
||||
raise Exception("Not Implemented")
|
||||
|
||||
def parse_contents(self, l, target, layout_mode=False, can_has=False, can_tag=False, block_only=False):
|
||||
def parse_contents(self, l, target, layout_mode=False, can_has=False, can_tag=False, block_only=False, keyword=True):
|
||||
"""
|
||||
Parses the remainder of the current line of `l`, and all of its subblock,
|
||||
looking for keywords and children.
|
||||
@@ -255,6 +257,12 @@ class Parser(object):
|
||||
|
||||
expr = l.comma_expression()
|
||||
|
||||
if (not keyword) and (not renpy.config.keyword_after_python):
|
||||
try:
|
||||
literal_eval(expr)
|
||||
except:
|
||||
l.error("a non-constant keyword argument like '%s %s' is not allowed after a python block." % (name, expr))
|
||||
|
||||
target.keyword.append((name, expr))
|
||||
|
||||
if block_only:
|
||||
@@ -309,13 +317,16 @@ class Parser(object):
|
||||
if child_index != 0:
|
||||
l.error("The has statement may not be given after a child has been supplied.")
|
||||
|
||||
c = self.parse_statement(loc, l, layout_mode=True)
|
||||
c = self.parse_statement(loc, l, layout_mode=True, keyword=keyword)
|
||||
|
||||
if c is None:
|
||||
l.error('Has expects a child statement.')
|
||||
|
||||
target.children.append(c)
|
||||
|
||||
if c.has_python():
|
||||
keyword = False
|
||||
|
||||
continue
|
||||
|
||||
c = self.parse_statement(loc, l)
|
||||
@@ -328,6 +339,10 @@ class Parser(object):
|
||||
if c is not None:
|
||||
target.children.append(c)
|
||||
child_index += 1
|
||||
|
||||
if c.has_python():
|
||||
keyword = False
|
||||
|
||||
continue
|
||||
|
||||
l.revert(state)
|
||||
@@ -553,10 +568,10 @@ class DisplayableParser(Parser):
|
||||
add(renpy.sl2.slproperties.ui_properties)
|
||||
add(renpy.sl2.slproperties.position_properties)
|
||||
|
||||
def parse_layout(self, loc, l, parent):
|
||||
return self.parse(loc, l, parent, True)
|
||||
def parse_layout(self, loc, l, parent, keyword):
|
||||
return self.parse(loc, l, parent, keyword, layout_mode=True)
|
||||
|
||||
def parse(self, loc, l, parent, layout_mode=False):
|
||||
def parse(self, loc, l, parent, keyword, layout_mode=False):
|
||||
|
||||
rv = slast.SLDisplayable(
|
||||
loc,
|
||||
@@ -612,7 +627,7 @@ class IfParser(Parser):
|
||||
if not parent_contents:
|
||||
childbearing_statements.add(self)
|
||||
|
||||
def parse(self, loc, l, parent):
|
||||
def parse(self, loc, l, parent, keyword):
|
||||
|
||||
if self.parent_contents:
|
||||
contents_from = parent
|
||||
@@ -642,7 +657,7 @@ class IfParser(Parser):
|
||||
l.require(':')
|
||||
|
||||
block = slast.SLBlock(loc)
|
||||
contents_from.parse_contents(l, block, block_only=True)
|
||||
contents_from.parse_contents(l, block, block_only=True, keyword=keyword)
|
||||
|
||||
rv.entries.append((condition, block))
|
||||
|
||||
@@ -654,7 +669,7 @@ class IfParser(Parser):
|
||||
l.require(':')
|
||||
|
||||
block = slast.SLBlock(loc)
|
||||
contents_from.parse_contents(l, block, block_only=True)
|
||||
contents_from.parse_contents(l, block, block_only=True, keyword=keyword)
|
||||
|
||||
rv.entries.append((condition, block))
|
||||
|
||||
@@ -713,7 +728,7 @@ class ForParser(Parser):
|
||||
|
||||
l.error("expected variable or tuple pattern.")
|
||||
|
||||
def parse(self, loc, l, parent):
|
||||
def parse(self, loc, l, parent, keyword):
|
||||
|
||||
l.skip_whitespace()
|
||||
|
||||
@@ -755,7 +770,7 @@ ForParser("for")
|
||||
|
||||
class OneLinePythonParser(Parser):
|
||||
|
||||
def parse(self, loc, l, parent):
|
||||
def parse(self, loc, l, parent, keyword):
|
||||
|
||||
loc = l.get_location()
|
||||
source = l.require(l.rest_statement)
|
||||
@@ -772,7 +787,7 @@ OneLinePythonParser("$")
|
||||
|
||||
class MultiLinePythonParser(Parser):
|
||||
|
||||
def parse(self, loc, l, parent):
|
||||
def parse(self, loc, l, parent, keyword):
|
||||
|
||||
loc = l.get_location()
|
||||
|
||||
@@ -792,7 +807,7 @@ MultiLinePythonParser("python")
|
||||
|
||||
class PassParser(Parser):
|
||||
|
||||
def parse(self, loc, l, parent):
|
||||
def parse(self, loc, l, parent, keyword):
|
||||
|
||||
l.expect_eol()
|
||||
|
||||
@@ -804,7 +819,7 @@ PassParser("pass")
|
||||
|
||||
class DefaultParser(Parser):
|
||||
|
||||
def parse(self, loc, l, parent):
|
||||
def parse(self, loc, l, parent, keyword):
|
||||
|
||||
name = l.require(l.word)
|
||||
l.require(r'=')
|
||||
@@ -825,7 +840,7 @@ class UseParser(Parser):
|
||||
super(UseParser, self).__init__(name)
|
||||
childbearing_statements.add(self)
|
||||
|
||||
def parse(self, loc, l, parent):
|
||||
def parse(self, loc, l, parent, keyword):
|
||||
|
||||
if l.keyword('expression'):
|
||||
target = l.require(l.simple_expression)
|
||||
@@ -863,7 +878,7 @@ Keyword("style_group")
|
||||
|
||||
class TranscludeParser(Parser):
|
||||
|
||||
def parse(self, loc, l, parent):
|
||||
def parse(self, loc, l, parent, keyword):
|
||||
l.expect_eol()
|
||||
return slast.SLTransclude(loc)
|
||||
|
||||
@@ -928,7 +943,7 @@ class CustomParser(Parser):
|
||||
# The number of positional parameters required.
|
||||
self.positional = positional
|
||||
|
||||
def parse(self, loc, l, parent):
|
||||
def parse(self, loc, l, parent, keyword):
|
||||
|
||||
arguments = [ ]
|
||||
|
||||
@@ -961,7 +976,7 @@ class ScreenParser(Parser):
|
||||
def __init__(self):
|
||||
super(ScreenParser, self).__init__("screen", statement=False)
|
||||
|
||||
def parse(self, loc, l, parent, name="_name"):
|
||||
def parse(self, loc, l, parent, name="_name", keyword=True):
|
||||
|
||||
screen = slast.SLScreen(loc)
|
||||
|
||||
@@ -977,6 +992,7 @@ class ScreenParser(Parser):
|
||||
screen.variant = keyword.get("variant", "None")
|
||||
screen.predict = keyword.get("predict", "None")
|
||||
screen.layer = keyword.get("layer", "'screens'")
|
||||
screen.sensitive = keyword.get("sensitive", "True")
|
||||
|
||||
return screen
|
||||
|
||||
@@ -989,6 +1005,7 @@ Keyword("predict")
|
||||
Keyword("style_group")
|
||||
Keyword("style_prefix")
|
||||
Keyword("layer")
|
||||
Keyword("sensitive")
|
||||
parser = None
|
||||
|
||||
|
||||
|
||||
@@ -48,6 +48,7 @@ def register(
|
||||
label=None,
|
||||
warp=None,
|
||||
translation_strings=None,
|
||||
force_begin_rollback=False,
|
||||
):
|
||||
"""
|
||||
:doc: statement_register
|
||||
@@ -128,9 +129,15 @@ def register(
|
||||
return a list of strings, which are then reported as being available
|
||||
to be translated.
|
||||
|
||||
`force_begin_rollback`
|
||||
This should be set to true on statements that are likely to cause the
|
||||
end of a fast skip, similar to ``menu`` or ``call screen``.
|
||||
"""
|
||||
name = tuple(name.split())
|
||||
|
||||
if label:
|
||||
force_begin_rollback = True
|
||||
|
||||
registry[name] = dict(
|
||||
parse=parse,
|
||||
lint=lint,
|
||||
@@ -142,6 +149,7 @@ def register(
|
||||
label=label,
|
||||
warp=warp,
|
||||
translation_strings=translation_strings,
|
||||
rollback="force" if force_begin_rollback else "normal",
|
||||
)
|
||||
|
||||
if block not in [True, False, "script", "possible"]:
|
||||
@@ -212,6 +220,11 @@ def call(method, parsed, *args, **kwargs):
|
||||
return method(parsed, *args, **kwargs)
|
||||
|
||||
|
||||
def get(key, parsed):
|
||||
name, parsed = parsed
|
||||
return registry[name].get(key, None)
|
||||
|
||||
|
||||
def get_name(parsed):
|
||||
name, _parsed = parsed
|
||||
return " ".join(name)
|
||||
|
||||
@@ -183,6 +183,15 @@ class Formatter(string.Formatter):
|
||||
|
||||
value = value.replace("{", "{{")
|
||||
|
||||
if "u" in conversion:
|
||||
value = value.upper()
|
||||
|
||||
if "l" in conversion:
|
||||
value = value.lower()
|
||||
|
||||
if "c" in conversion:
|
||||
value = value.capitalize()
|
||||
|
||||
return value
|
||||
|
||||
|
||||
|
||||
+36
-32
@@ -384,6 +384,10 @@ class BMFont(ImageFont):
|
||||
self.width[c] = w + xo
|
||||
self.advance[c] = xadvance
|
||||
self.offsets[c] = (xo, yo)
|
||||
elif kind == "kerning":
|
||||
first = unichr(int(args["first"]))
|
||||
second = unichr(int(args["second"]))
|
||||
self.kerns[first + second] = int(args["amount"])
|
||||
|
||||
f.close()
|
||||
|
||||
@@ -635,7 +639,7 @@ def load_face(fn):
|
||||
if font_file is None:
|
||||
raise Exception("Could not find font {0!r}.".format(orig_fn))
|
||||
|
||||
rv = ftfont.FTFace(font_file, index) # @UndefinedVariable
|
||||
rv = ftfont.FTFace(font_file, index, orig_fn) # @UndefinedVariable
|
||||
|
||||
face_cache[orig_fn] = rv
|
||||
|
||||
@@ -727,14 +731,9 @@ class FontGroup(object):
|
||||
|
||||
def __init__(self):
|
||||
|
||||
# A list of font names we know of.
|
||||
self.fonts = [ ]
|
||||
|
||||
# A map from character to the index of the font it's part of.
|
||||
self.cache = { }
|
||||
|
||||
# A list of (index, start, end) tuples.
|
||||
self.patterns = [ ]
|
||||
# A map from character index to font name. None is used for
|
||||
# the default font.
|
||||
self.map = { }
|
||||
|
||||
def add(self, font, start, end):
|
||||
"""
|
||||
@@ -744,7 +743,8 @@ class FontGroup(object):
|
||||
|
||||
`start`
|
||||
The start of the range. This may be a single-character string, or
|
||||
an integer giving a unicode code point.
|
||||
an integer giving a unicode code point. If start is None, then the
|
||||
font is used as the default.
|
||||
|
||||
`end`
|
||||
The end of the range. This may be a single-character string, or an
|
||||
@@ -757,6 +757,18 @@ class FontGroup(object):
|
||||
chained together.
|
||||
"""
|
||||
|
||||
if start is None:
|
||||
|
||||
if isinstance(font, FontGroup):
|
||||
for k, v in font.map.items():
|
||||
if k not in self.map:
|
||||
self.map[k] = v
|
||||
else:
|
||||
if None not in self.map:
|
||||
self.map[None] = font
|
||||
|
||||
return self
|
||||
|
||||
if not isinstance(start, int):
|
||||
start = ord(start)
|
||||
|
||||
@@ -766,12 +778,9 @@ class FontGroup(object):
|
||||
if end < start:
|
||||
raise Exception("In FontGroup.add, the start of a character range must be before the end of the range.")
|
||||
|
||||
if font not in self.fonts:
|
||||
self.fonts.append(font)
|
||||
|
||||
index = self.fonts.index(font)
|
||||
|
||||
self.patterns.append((index, start, end))
|
||||
for i in range(start, end+1):
|
||||
if i not in self.map:
|
||||
self.map[i] = font
|
||||
|
||||
return self
|
||||
|
||||
@@ -783,32 +792,27 @@ class FontGroup(object):
|
||||
mark = 0
|
||||
pos = 0
|
||||
|
||||
old_index = 0
|
||||
|
||||
cache = self.cache
|
||||
old_font = None
|
||||
|
||||
for c in s:
|
||||
|
||||
index = cache.get(c, None)
|
||||
n = ord(c)
|
||||
|
||||
if index is None:
|
||||
n = ord(c)
|
||||
font = self.map.get(ord(c), None)
|
||||
|
||||
for index, start, end in self.patterns:
|
||||
if start <= n <= end:
|
||||
break
|
||||
else:
|
||||
if font is None:
|
||||
font = self.map.get(None, None)
|
||||
|
||||
if font is None:
|
||||
raise Exception("Character U+{0:04x} not found in FontGroup".format(n))
|
||||
|
||||
cache[c] = index
|
||||
|
||||
if index != old_index:
|
||||
if font != old_font:
|
||||
if pos:
|
||||
yield self.fonts[old_index], s[mark:pos]
|
||||
yield old_font, s[mark:pos]
|
||||
|
||||
old_index = index
|
||||
old_font = font
|
||||
mark = pos
|
||||
|
||||
pos += 1
|
||||
|
||||
yield self.fonts[old_index], s[mark:]
|
||||
yield font, s[mark:]
|
||||
|
||||
+16
-4
@@ -31,6 +31,8 @@ from ttgsubtable cimport *
|
||||
from textsupport cimport Glyph, SPLIT_INSTEAD
|
||||
import traceback
|
||||
|
||||
import renpy.config
|
||||
|
||||
cdef extern from "ftsupport.h":
|
||||
char *freetype_error_to_string(int error)
|
||||
|
||||
@@ -147,11 +149,16 @@ cdef class FTFace:
|
||||
# The offset in that file.
|
||||
unsigned long offset
|
||||
|
||||
def __init__(self, f, index):
|
||||
public object fn
|
||||
|
||||
def __init__(self, f, index, fn):
|
||||
|
||||
cdef int error
|
||||
cdef unsigned long size
|
||||
|
||||
# The filename.
|
||||
self.fn = fn
|
||||
|
||||
# The file that the font is opened from.
|
||||
self.f = f
|
||||
|
||||
@@ -249,6 +256,8 @@ cdef class FTFont:
|
||||
if bold:
|
||||
antialias = True
|
||||
|
||||
size = size * renpy.config.ftfont_scale.get(face.fn, 1.0) * renpy.game.preferences.font_size
|
||||
|
||||
self.face_object = face
|
||||
self.face = self.face_object.face
|
||||
|
||||
@@ -287,6 +296,7 @@ cdef class FTFont:
|
||||
cdef int error
|
||||
cdef FT_Face face
|
||||
cdef FT_Fixed scale
|
||||
cdef float ascent_scale
|
||||
|
||||
face = self.face
|
||||
|
||||
@@ -303,8 +313,10 @@ cdef class FTFont:
|
||||
|
||||
scale = face.size.metrics.y_scale
|
||||
|
||||
self.ascent = FT_CEIL(face.size.metrics.ascender)
|
||||
self.descent = FT_FLOOR(face.size.metrics.descender)
|
||||
vextent_scale = renpy.config.ftfont_vertical_extent_scale.get(self.face_object.fn, 1.0)
|
||||
|
||||
self.ascent = FT_CEIL(int(face.size.metrics.ascender * vextent_scale))
|
||||
self.descent = FT_FLOOR(int(face.size.metrics.descender * vextent_scale))
|
||||
|
||||
if self.descent > 0:
|
||||
self.descent = -self.descent
|
||||
@@ -320,7 +332,7 @@ cdef class FTFont:
|
||||
# if self.height > self.lineskip:
|
||||
# self.lineskip = self.height
|
||||
|
||||
self.lineskip = self.height
|
||||
self.lineskip = <int> self.height * renpy.game.preferences.font_line_spacing
|
||||
|
||||
if self.vertical:
|
||||
self.underline_offset = FT_FLOOR(FT_MulFix(face.ascender + face.descender - face.underline_position, scale))
|
||||
|
||||
+10
-2
@@ -293,13 +293,21 @@ class TextSegment(object):
|
||||
on the font group.
|
||||
"""
|
||||
|
||||
if not isinstance(self.font, font.FontGroup):
|
||||
tf = self.font
|
||||
|
||||
font_transform = renpy.game.preferences.font_transform
|
||||
if font_transform is not None:
|
||||
font_func = renpy.config.font_transforms.get(font_transform, None)
|
||||
if font_func is not None:
|
||||
tf = font_func(tf)
|
||||
|
||||
if not isinstance(tf, font.FontGroup):
|
||||
yield (self, s)
|
||||
return
|
||||
|
||||
segs = { }
|
||||
|
||||
for f, ss in self.font.segment(s):
|
||||
for f, ss in tf.segment(s):
|
||||
|
||||
seg = segs.get(f, None)
|
||||
|
||||
|
||||
@@ -637,6 +637,8 @@ def change_language(language, force=False):
|
||||
else:
|
||||
old_change_language(tl, language)
|
||||
|
||||
renpy.store._history_list = [ ]
|
||||
|
||||
for i in renpy.config.change_language_callbacks:
|
||||
i()
|
||||
|
||||
|
||||
+1499
File diff suppressed because it is too large
Load Diff
Executable
+31
@@ -0,0 +1,31 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
if [ -z "$1" ]; then
|
||||
echo "$0 requires a version."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
RENPY="$(dirname $0)/.."
|
||||
|
||||
butler () {
|
||||
"$RENPY/tmp/butler-linux-amd64/butler" "$@"
|
||||
}
|
||||
|
||||
D="$RENPY/dl/$1"
|
||||
P=renpytom/renpy-mirror
|
||||
|
||||
butler push "$D/renpy-$1-sdk.zip" $P:sdk-windows-mac-linux
|
||||
|
||||
butler push "$D/renpy-$1-rapt.zip" $P:rapt
|
||||
butler push "$D/renpy-$1-raspi.tar.bz2" $P:raspi
|
||||
butler push "$D/renpy-$1-renios.zip" $P:renios
|
||||
|
||||
butler push "$D/renpy-$1-atom-linux.tar.bz2" $P:atom-linux
|
||||
butler push "$D/renpy-$1-atom-mac.zip" $P:atom-mac
|
||||
butler push "$D/renpy-$1-atom-windows.zip" $P:atom-windows
|
||||
|
||||
butler push "$D/renpy-$1-editra-linux.tar.bz2" $P:editra-linux
|
||||
butler push "$D/renpy-$1-editra-mac.zip" $P:editra-mac
|
||||
butler push "$D/renpy-$1-editra-windows.zip" $P:editra-windows
|
||||
@@ -849,6 +849,8 @@ both horizontal and vertical positions.
|
||||
Otherwise, the components are considered to be an absolute number
|
||||
of pixels.
|
||||
|
||||
If corners and crop are given, crop takes priority over corners.
|
||||
|
||||
.. transform-property:: crop_relative
|
||||
|
||||
:type: boolean
|
||||
@@ -862,16 +864,16 @@ both horizontal and vertical positions.
|
||||
:type: None or (int, int)
|
||||
:default: None
|
||||
|
||||
If not None, gives the upper-left corner of the crop box. This
|
||||
takes priority over crop.
|
||||
If not None, gives the upper-left corner of the crop box. Crop takes
|
||||
priority over corners.
|
||||
|
||||
.. transform-property:: corner2
|
||||
|
||||
:type: None or (int, int)
|
||||
:default: None
|
||||
|
||||
If not None, gives the lower right corner of the crop box. This
|
||||
takes priority over crop.
|
||||
If not None, gives the lower right corner of the crop box. Cropt takes
|
||||
priority over corners.
|
||||
|
||||
.. transform-property:: size
|
||||
|
||||
|
||||
+64
-10
@@ -3,9 +3,20 @@
|
||||
Creator-Defined Statements
|
||||
==========================
|
||||
|
||||
Creator-defined statements allow you to add your own statements to Ren'Py. This
|
||||
Creator-Defined Statements (CDS) allow you to add your own statements to Ren'Py. This
|
||||
makes it possible to add things that are not supported by the current syntax of
|
||||
Ren'Py.
|
||||
Ren'Py. CDS are more flexible than the direct Python code. Most often, CDS are used
|
||||
when you have a repeatable construction. For example, calling a function with one argument.
|
||||
Ren'Py does not know what this function does and how it should be executed,
|
||||
so Ren'Py does not do anything with it until execution and has an error if an exception occurs.
|
||||
Using the CDS allows you to check the correctness of the syntax using parse
|
||||
(for example, check that the argument is a valid string), to ignore incorrect data
|
||||
at execution (for non-critical functions, it is better to skip the execute than
|
||||
to throw an exception), predict displayables (if the function uses them),
|
||||
and give you addition information during lint (if at runtime it was ignored you
|
||||
can have a report here). The CDS does not guarantee that the execution will be successful,
|
||||
but the better you code your statement, the better Ren'Py can "understand" what
|
||||
you expect from it.
|
||||
|
||||
Creator-defined statements must be defined in a ``python early`` block. What's more,
|
||||
the filename containing the user-defined statement must be be loaded earlier
|
||||
@@ -13,7 +24,7 @@ than any file that uses it. Since Ren'Py loads files in Unicode sort order, it
|
||||
generally makes sense to prefix the name of any file containing a user-defined
|
||||
statement with 01, or some other small number.
|
||||
|
||||
A user-defined statement cannot be used in the file in which it is defined.
|
||||
A creator-defined statement cannot be used in the file in which it is defined.
|
||||
|
||||
Creator-defined statement are registered using the :func:`renpy.register_statement`
|
||||
function.
|
||||
@@ -24,10 +35,42 @@ The parse method takes a Lexer object:
|
||||
|
||||
.. class:: Lexer
|
||||
|
||||
.. method:: error(msg)
|
||||
|
||||
Adds a `msg` (with the current position) in the list of detected
|
||||
parsing errors. This interrupts the parsing of the current statement,
|
||||
but does not prevent further parsing.
|
||||
|
||||
.. method:: require(thing, name=None)
|
||||
|
||||
Tries to parse `thing`, and reports an error if it cannot be done.
|
||||
|
||||
If `thing` is a string, tries to parse it using :func:`match`.
|
||||
Otherwise, thing must be a other method on this lexer object,
|
||||
which is called without arguments. If `name` is not specified,
|
||||
the name of the method will be used in the message
|
||||
(or `thing` if it's a string), otherwise the `name` will be used.
|
||||
|
||||
.. method:: eol()
|
||||
|
||||
True if the lexer is at the end of the line.
|
||||
|
||||
.. method:: expect_eol()
|
||||
|
||||
If we are not at the end of the line, raise an error.
|
||||
|
||||
.. method:: expect_noblock(stmt)
|
||||
|
||||
Called to indicate this statement does not expect a block.
|
||||
If a block is found, raises an error. `stmt` should be a string,
|
||||
it will be added to the message with an error.
|
||||
|
||||
.. method:: expect_block(stmt)
|
||||
|
||||
Called to indicate that the statement requires that a non-empty
|
||||
block is present. `stmt` should be a string, it will be added
|
||||
to the message with an error.
|
||||
|
||||
.. method:: match(re)
|
||||
|
||||
Matches an arbitrary regexp string.
|
||||
@@ -35,7 +78,8 @@ The parse method takes a Lexer object:
|
||||
All of the statements in the lexer that match things are implemented
|
||||
in terms of this function. They first skip whitespace, then attempt
|
||||
to match against the line. If the match succeeds, the matched text
|
||||
is returned. Otherwise, None is returned.
|
||||
is returned. Otherwise, None is returned, and the state of the lexer
|
||||
is unchanged.
|
||||
|
||||
.. method:: keyword(s)
|
||||
|
||||
@@ -50,9 +94,9 @@ The parse method takes a Lexer object:
|
||||
Matches any word, including keywords. Returns the text of the
|
||||
matched word.
|
||||
|
||||
.. method:: image_name_component():
|
||||
.. method:: image_name_component()
|
||||
|
||||
Matches an image name component. Unlike a a word, and image name
|
||||
Matches an image name component. Unlike a word, a image name
|
||||
component can begin with a number.
|
||||
|
||||
.. method:: string()
|
||||
@@ -78,15 +122,23 @@ The parse method takes a Lexer object:
|
||||
.. method:: simple_expression()
|
||||
|
||||
Matches a simple Python expression, returns it as a string.
|
||||
This is often used when you expect a variable name.
|
||||
It is not recommended to change the result. The correct action is
|
||||
to evaluate the result in the future.
|
||||
|
||||
.. method:: delimited_python(delim)
|
||||
|
||||
Matches a pythio expression that ends in a delimiter, often
|
||||
':' or '='.
|
||||
Matches a Python expression that ends in a `delim`, for example ':'.
|
||||
This is often used when you expect a condition until the delimiter.
|
||||
It is not recommended to change the result. The correct action is
|
||||
to evaluate the result in the future. This raises an error if
|
||||
end of line is reached before the delimiter.
|
||||
|
||||
.. method:: arguments()
|
||||
|
||||
Returns an object representing the arguments to a function
|
||||
This must be called before the parentheses with the arguments list,
|
||||
if they are not specified returns None, otherwise
|
||||
returns an object representing the arguments to a function
|
||||
call. This object has an ``evaluate`` method on it that
|
||||
takes an optional `scope` dictionary, and returns a tuple
|
||||
in which the first component is a tuple of positional arguments,
|
||||
@@ -113,7 +165,9 @@ The parse method takes a Lexer object:
|
||||
.. method:: advance()
|
||||
|
||||
In a subblock lexer, advances to the next line. This must be called
|
||||
before the first line, so the first line can be parsed.
|
||||
before the first line, so the first line can be parsed. Returns True
|
||||
if we've successfully advanced to a line in the block, or False
|
||||
if we have advanced beyond all lines in the block.
|
||||
|
||||
|
||||
Lint Utility Functions
|
||||
|
||||
@@ -2,11 +2,105 @@
|
||||
Full Changelog
|
||||
==============
|
||||
|
||||
.. _renpy-7.2.2:
|
||||
|
||||
7.2.2
|
||||
=====
|
||||
|
||||
Ren'Py now supports a new Accessibility menu, which can be accessed
|
||||
by pressing the "a" key. This menu, which is intended to be used by
|
||||
players, let's the player override the game font, change the scaling
|
||||
and spacing of text, and enable self-voicing.
|
||||
|
||||
Ren'Py will now allow files in the public game directory
|
||||
(/mnt/sdcard/Android/`package`/files/game) to override those included with the
|
||||
game proper. This has been listed as a feature in 7.2.0, but didn't work
|
||||
right.
|
||||
|
||||
Ren'Py will now include temporary image attributes in the say statements
|
||||
generated as part of the translation process.
|
||||
|
||||
When uploading to itch.io, Ren'Py now downloads butler itself. This means
|
||||
there is no longer a dependence on the location or structure of the Itch
|
||||
app, as there was before.
|
||||
|
||||
The bar values :func:`DictValue`, :func:`FieldValue`, :func:`VariableValue`,
|
||||
and :func:`ScreenVariableValue` all take a new `action` parameter, which
|
||||
gives an action that is performed each time the value changes.
|
||||
|
||||
The rollback system has been optimized to reduce the amount of garbage
|
||||
that needs to be collected.
|
||||
|
||||
.. _renpy-7.2.1:
|
||||
|
||||
7.2.1
|
||||
=====
|
||||
|
||||
iOS Improvements
|
||||
----------------
|
||||
|
||||
Ren'Py will now set the version field for the iOS application when generating
|
||||
a project.
|
||||
|
||||
Ren'Py will now look for the files ios-icon.png and ios-launchimage.png,
|
||||
and use them to generate the icon and launch image in the sizes required
|
||||
for iOS.
|
||||
|
||||
Other Improvements
|
||||
-------------------
|
||||
|
||||
The :func:`renpy.in_rollback` function returns True when in the rollback that
|
||||
occurs immediately after a load. This makes it possible to use::
|
||||
|
||||
python:
|
||||
if not renpy.in_rollback():
|
||||
renpy.run(ShowMenu('save'))
|
||||
|
||||
To display a save menu during an initial playthough, but not during loading
|
||||
or rollback.
|
||||
|
||||
:var:`config.say_attribute_transition_callback` now exists, making it possible
|
||||
to select the transition to use on a per-say-statement basis.
|
||||
|
||||
The new ``RENPY_SEARCHPATH`` environment variable makes it possible to
|
||||
override :var:`config.searchpath` on launch.
|
||||
|
||||
Fixes
|
||||
-----
|
||||
|
||||
Ren'Py has been audited to make sure that the semantics of == and != match,
|
||||
whenever == was redefined.
|
||||
|
||||
There was a fix to problems that might occur when a transform is added
|
||||
to a screen using the ``add`` statement.
|
||||
|
||||
The way ``extend`` processes arguments was changed to ensure that newer
|
||||
arguments take priority over arguments given to the statement being
|
||||
extended.
|
||||
|
||||
Ren'Py now properly considers the scope when comparing dynamic images for
|
||||
equality. This fixes an issue that prevented some dynamic images from
|
||||
updating when part of a screen.
|
||||
|
||||
A crash when :var:`config.save_dump` is true on macOS has been fixed.
|
||||
|
||||
A crash when :var:`config.profile` is true has been fixed.
|
||||
|
||||
Ren'Py now explicitly asks for text (as opposed to email, password, phone number
|
||||
etc) input on Android when the keyboard is displayed.
|
||||
|
||||
An issue has been fixed that prevented roll-forward from working through a
|
||||
menu statement.
|
||||
|
||||
Fixes a bug that prevents roll-forward through a menu.
|
||||
|
||||
|
||||
.. _renpy-7.2.0:
|
||||
.. _renpy-7.2:
|
||||
.. _renpy-7.1.4:
|
||||
|
||||
7.2
|
||||
===
|
||||
7.2.0
|
||||
=====
|
||||
|
||||
Menu Arguments
|
||||
--------------
|
||||
@@ -143,7 +237,6 @@ Ren'Py now uses the Amazon payment APIs when sideloaded onto a device
|
||||
made by Amazon, allowing their payment system to be tested on a dual-store
|
||||
APK.
|
||||
|
||||
|
||||
Ren'Py will now allow files in the public game directory (/mnt/sdcard/Android/`package`/files/game)
|
||||
to override those included with the game proper.
|
||||
|
||||
@@ -161,7 +254,7 @@ interaction restart occurs.
|
||||
|
||||
|
||||
|
||||
Size-changing properties like :ref:`xysize` now apply to a Drag and not
|
||||
Size-changing properties like :propref:`xysize` now apply to a Drag and not
|
||||
the space it can move around in.
|
||||
|
||||
A bug that could cause a transparent, black, or gray line to appear on
|
||||
|
||||
@@ -160,6 +160,30 @@ These control transitions between various screens.
|
||||
If not None, a transition to use when the image is changed by a
|
||||
say statement with image attributes.
|
||||
|
||||
.. var:: config.say_attribute_transition_callback = ...
|
||||
|
||||
This is a function that return a transition to apply and a layer to
|
||||
apply it on
|
||||
|
||||
This should be a function that takes three arguments, the image tag
|
||||
being shown, a tuple of tags describing the image being shown, and a
|
||||
`mode` parameter that is one of:
|
||||
|
||||
* "permanent", for permanent attribute change (one that lasts longer
|
||||
than the current say statement).
|
||||
* "temporary", for a temporary attribute change (one that is restored
|
||||
at the end of the current say statement).
|
||||
* "restore", for when a temporary change is being restored.
|
||||
|
||||
This should return a 2-component tuple, consiting of:
|
||||
|
||||
* The transition to use, or None if no transition should occur.
|
||||
* The layer the transition should be on, either a string or None. This is
|
||||
almost always None.
|
||||
|
||||
The default implementation of this returns (config.say_attribute_transition,
|
||||
config.say_attribute_transition_layer).
|
||||
|
||||
.. var:: config.say_attribute_transition_layer = None
|
||||
|
||||
If not None, this must be a string giving the name of a layer. (Almost always
|
||||
@@ -526,6 +550,13 @@ Occasionally Used
|
||||
If not None, this should be a string giving the default language
|
||||
that the game is translated into by the translation framework.
|
||||
|
||||
.. var:: config.load_failed_label = None
|
||||
|
||||
If a string, this is a label that is jumped to when a load fails because
|
||||
the script has changed so much that Ren'Py can't recover.
|
||||
Before performing the load, Ren'Py will revert to the start of the
|
||||
last statement, then it will clear the call stack.
|
||||
|
||||
.. var:: config.locale_to_language_function = ...
|
||||
|
||||
A function that determines the language the game should use,
|
||||
@@ -633,6 +664,13 @@ Occasionally Used
|
||||
Uses nearest-neighbor filtering by default, to support pixel art or
|
||||
melting players' eyes.
|
||||
|
||||
.. var:: config.notify = ...
|
||||
|
||||
This is called by :func:`renpy.notify` or :func:`Notify` with a
|
||||
single `message` argument, to display the notification. The default
|
||||
implementation is :funct:`renpy.display_notify`. This is intended
|
||||
to allow creators to intercept notifications.
|
||||
|
||||
.. var:: config.optimize_texture_bounds = False
|
||||
|
||||
When True, Ren'Py will scan images to find the bounding box of the
|
||||
@@ -680,6 +718,12 @@ Occasionally Used
|
||||
a window. The default action prompts the user to see if he wants
|
||||
to quit the game.
|
||||
|
||||
.. var:: config.reload_modules = [ ]
|
||||
|
||||
A list of strings giving the names of python modules that should be
|
||||
reloaded along with the game. Any submodules of these modules
|
||||
will also be reloaded.
|
||||
|
||||
.. var:: config.replace_text = None
|
||||
|
||||
If not None, a function that is called with a single argument, a text to
|
||||
|
||||
@@ -58,6 +58,7 @@ the omission in future versions.
|
||||
* Evilantishad0w
|
||||
* Felix Lampe
|
||||
* Franck_v
|
||||
* Frédéric Chapoton
|
||||
* Gas
|
||||
* George Economidis
|
||||
* Giuseppe Bertone
|
||||
@@ -96,6 +97,7 @@ the omission in future versions.
|
||||
* Markus Koschany
|
||||
* Max le Fou
|
||||
* Maxwell Paul Brickner
|
||||
* Meithal
|
||||
* Merumelu
|
||||
* mikey (ATP Projects)
|
||||
* Morgan Willcock
|
||||
|
||||
@@ -163,7 +163,8 @@ the @ and the temporary ones coming after. ::
|
||||
e happy @ vhappy "Really! That changes everything."
|
||||
|
||||
To cause a transition to occur whenever the images are changed in this way, set
|
||||
:var:`config.say_attribute_transition` to a transition.
|
||||
:var:`config.say_attribute_transition` to a transition. For more control,
|
||||
use :var:`config.say_attribute_transition_callback`.
|
||||
|
||||
|
||||
Example Characters
|
||||
|
||||
@@ -58,6 +58,10 @@ The following environment variables control the behavior of Ren'Py:
|
||||
%d substitution in it. For example, setting this to "screenshot%04d.jpg" will
|
||||
cause Ren'Py to write out jpeg screenshots rather than the usual pngs.
|
||||
|
||||
``RENPY_SEARCHPATH``
|
||||
If set, a double-colon (\:\:) separated list of additional paths that
|
||||
are added to :var:`config.searchpath`.
|
||||
|
||||
``RENPY_SIMPLE_EXCEPTIONS``
|
||||
When set, this disables Ren'Py's graphical exception handling.
|
||||
|
||||
|
||||
@@ -12,6 +12,14 @@ features.
|
||||
Incompatible changes to the GUI are documented at :ref:`gui-changes`, as
|
||||
such changes only take effect when the GUI is regenerated.
|
||||
|
||||
.. _incompatible-7.2.2:
|
||||
|
||||
7.2.2
|
||||
-----
|
||||
|
||||
:var:`config.say_attribute_transition_callback` has been changed to
|
||||
accept a new argument, the image being displayed.
|
||||
|
||||
|
||||
.. _incompatible-7.1.1:
|
||||
|
||||
|
||||
@@ -128,4 +128,18 @@ to display the game's user interface.
|
||||
Once Ren'Py finishes initializing, it will either resume the current game,
|
||||
or run the splashscreen and main menu.
|
||||
|
||||
Generation
|
||||
-----------
|
||||
|
||||
To ease development, it's possible to have Ren'Py generate the iOS icon
|
||||
and launch image from files.
|
||||
|
||||
``ios-icon.png``
|
||||
If present, this file is scaled to the various sizes of png file
|
||||
needed to make an iOS icon.
|
||||
|
||||
``ios-launchimage.png``
|
||||
If present, this file is used to make an iOS Launch Image. This is done
|
||||
by creating an image of the required sized, filling it with the top-left
|
||||
pixel of this file, and then centering this file in the center of the
|
||||
Launch Image.
|
||||
|
||||
@@ -839,7 +839,7 @@ style radio_vbox:
|
||||
|
||||
style radio_button:
|
||||
properties gui.button_properties("radio_button")
|
||||
foreground "gui/button/check_[prefix_]foreground.png"
|
||||
foreground "gui/button/radio_[prefix_]foreground.png"
|
||||
|
||||
style radio_button_text:
|
||||
properties gui.button_text_properties("radio_button")
|
||||
|
||||
@@ -9,6 +9,8 @@ support can be used for many things, from setting a flag to creating
|
||||
new displayables. This chapter covers ways in which Ren'Py scripts can
|
||||
directly invoke Python, through the various Python statements.
|
||||
|
||||
Ren'Py currently supports Python 2.7, though we strongly recommend you write
|
||||
Python that runs in Python 2 and Python 3.
|
||||
|
||||
.. _python-statement:
|
||||
|
||||
|
||||
@@ -603,7 +603,8 @@ using a Python assignment statement. ::
|
||||
Lines beginning with a dollar-sign are interpreted as Python statements. The
|
||||
assignment statement here assigns a value to a variable. Ren'Py has support
|
||||
for other ways of including Python, such as a multi-line Python statement,
|
||||
that are discussed in other sections of this manual.
|
||||
that are discussed in other sections of this manual. Ren'Py supports Python 2.7,
|
||||
though we strongly recommend you write Python that runs in Python 2 and Python 3.
|
||||
|
||||
To check the flag, use the ``if`` statement::
|
||||
|
||||
|
||||
@@ -207,9 +207,8 @@ All user interface statements take the following common properties:
|
||||
By default, the ``id`` is automatically-generated.
|
||||
|
||||
`style`
|
||||
The name of the style applied to this displayable. This may be a
|
||||
string name, or a style object. The style gives default
|
||||
values for style properties.
|
||||
A string giving the name of the style applied to this displayable. The
|
||||
style gives default values for style properties.
|
||||
|
||||
`style_prefix`
|
||||
.. _style-prefix:
|
||||
@@ -259,6 +258,14 @@ All user interface statements take the following common properties:
|
||||
:func:`GetTooltip` function. See the :ref:`tooltips` section for
|
||||
more details.
|
||||
|
||||
`arguments`
|
||||
A tuple or list containing additional positional arguments that
|
||||
are given to the displayable.
|
||||
|
||||
`properties`
|
||||
A dictionary containing additional properties given to the
|
||||
displayable.
|
||||
|
||||
Many user interface statements take classes of style properties, or
|
||||
transform properties. These properties can have a style prefix
|
||||
associated with them, that determines when they apply. For example, if
|
||||
@@ -1713,7 +1720,7 @@ them from the expression.
|
||||
hbox:
|
||||
for i, member in enumerate(party):
|
||||
vbox:
|
||||
use member.screen pass (i+1)
|
||||
use expression member.screen pass (i + 1)
|
||||
|
||||
|
||||
Use and Transclude
|
||||
|
||||
+153
-150
@@ -1,208 +1,211 @@
|
||||
<ul class='sponsors'>
|
||||
<li> 017Bluefield (7.1.2–7.2)
|
||||
<li> __skwrl__ (6.99.14.3–7.2)
|
||||
<li> __skwrl__ (6.99.14.3–7.2.2)
|
||||
<li> Adia Alderson (6.99.13–7.1.2)
|
||||
<li> Aleema (6.99.13–7.2)
|
||||
<li> <a href="http://alexdodge.net/" rel="nofollow">Alex Dodge</a> (6.99.14–7.2)
|
||||
<li> <a href="https://rabidb.com/" rel="nofollow">AlexSSZ</a> (6.99.13–7.2)
|
||||
<li> <a href="https://alic-szecsei.com" rel="nofollow">Alic Szecsei</a> (6.99.14.3–7.2)
|
||||
<li> Almost Human (7.2)
|
||||
<li> Ana Raffali (7.2)
|
||||
<li> Andurin (7.1.2–7.2)
|
||||
<li> Anne Camlin (7.0–7.2)
|
||||
<li> Aleema (6.99.13–7.2.2)
|
||||
<li> <a href="http://alexdodge.net/" rel="nofollow">Alex Dodge</a> (6.99.14–7.2.2)
|
||||
<li> <a href="https://rabidb.com/" rel="nofollow">AlexSSZ</a> (6.99.13–7.2.2)
|
||||
<li> <a href="https://alic-szecsei.com" rel="nofollow">Alic Szecsei</a> (6.99.14.3–7.2.2)
|
||||
<li> Almost Human (7.2–7.2.2)
|
||||
<li> Ana Raffali (7.2–7.2.2)
|
||||
<li> Andurin (7.1.2–7.2.2)
|
||||
<li> Anne Camlin (7.0–7.2.2)
|
||||
<li> <a href="https://lemmasoft.renai.us/forums/viewtopic.php?f=51&t=34131" rel="nofollow">AR09FQF-AQ09FRF</a> (6.99.14.1–7.0)
|
||||
<li> Asatiir (6.99.13–7.0)
|
||||
<li> <a href="https://www.renpy.org/agegate?url=https://www.patreon.com/ASLPro3D" rel="nofollow">ASLPro3D</a> (7.0–7.2)
|
||||
<li> <a href="https://www.atwistedspirit.com/" rel="nofollow">ATwistedSpirit</a> (7.1–7.2)
|
||||
<li> <a href="https://www.renpy.org/agegate?url=https://www.patreon.com/ASLPro3D" rel="nofollow">ASLPro3D</a> (7.0–7.2.2)
|
||||
<li> <a href="https://www.atwistedspirit.com/" rel="nofollow">ATwistedSpirit</a> (7.1–7.2.2)
|
||||
<li> <a href="https://patchworkprincess.moe/" rel="nofollow">Azura (Vanessa Parker)</a> (7.0–7.1)
|
||||
<li> <a href="https://www.youtube.com/channel/UC3Xm0sk-rRCtD-yVp64WPWQ" rel="nofollow">BadGamer</a> (6.99.14.3–7.2)
|
||||
<li> <a href="https://www.youtube.com/channel/UC3Xm0sk-rRCtD-yVp64WPWQ" rel="nofollow">BadGamer</a> (6.99.14.3–7.2.2)
|
||||
<li> Belfort and Bastion (6.99.13–7.1)
|
||||
<li> Biotikos Development (6.99.13–7.2)
|
||||
<li> Biotikos Development (6.99.13–7.2.2)
|
||||
<li> Bob (6.99.13–7.0)
|
||||
<li> <a href="https://bobcgames.itch.io" rel="nofollow">Bob Conway</a> (6.99.14.1–7.2)
|
||||
<li> Bob Reus (7.1–7.2)
|
||||
<li> bob_xxii (7.2)
|
||||
<li> Booom313's tinker corner (6.99.14–7.2)
|
||||
<li> Boreas Bear (6.99.13–7.2)
|
||||
<li> <a href="http://boyslaughplus.moe" rel="nofollow">Boys Laugh +</a> (6.99.13–7.2)
|
||||
<li> <a href="https://bobcgames.itch.io" rel="nofollow">Bob Conway</a> (6.99.14.1–7.2.2)
|
||||
<li> Bob Reus (7.1–7.2.2)
|
||||
<li> bob_xxii (7.2–7.2.2)
|
||||
<li> Booom313's tinker corner (6.99.14–7.2.2)
|
||||
<li> Boreas Bear (6.99.13–7.2.2)
|
||||
<li> <a href="http://boyslaughplus.moe" rel="nofollow">Boys Laugh +</a> (6.99.13–7.2.2)
|
||||
<li> Brett Douglas (6.99.14–6.99.14.3)
|
||||
<li> Brimney (6.99.13–7.2)
|
||||
<li> <a href="https://www.renpy.org/agegate?url=https://www.brunimultimedia.com" rel="nofollow">Bruni Multimedia</a> (6.99.14–7.2)
|
||||
<li> Brimney (6.99.13–7.2.2)
|
||||
<li> <a href="https://www.renpy.org/agegate?url=https://www.brunimultimedia.com" rel="nofollow">Bruni Multimedia</a> (6.99.14–7.2.2)
|
||||
<li> <a href="http://www.bura.cl" rel="nofollow">BURA</a> (6.99.13)
|
||||
<li> Cameron Woodard (6.99.14.3–7.2)
|
||||
<li> Cara Hillstock (6.99.13–7.2)
|
||||
<li> <a href="https://www.celebrityhunter.com.br" rel="nofollow">Celebrity Hunter</a> (7.1–7.2)
|
||||
<li> Charlene Gilbert (6.99.13–7.2)
|
||||
<li> <a href="http://charliethegoldfish.com" rel="nofollow">Charlie Francis Cassidy</a> (6.99.13–7.2)
|
||||
<li> Cameron Woodard (6.99.14.3–7.2.2)
|
||||
<li> Cara Hillstock (6.99.13–7.2.2)
|
||||
<li> <a href="https://www.celebrityhunter.com.br" rel="nofollow">Celebrity Hunter</a> (7.1–7.2.2)
|
||||
<li> Charlene Gilbert (6.99.13–7.2.2)
|
||||
<li> <a href="http://charliethegoldfish.com" rel="nofollow">Charlie Francis Cassidy</a> (6.99.13–7.2.2)
|
||||
<li> Chekhov's Ghost (7.1–7.2)
|
||||
<li> Cherry Kiss Games (6.99.14.1–7.2)
|
||||
<li> Chrysoula Tzavelas (6.99.13–7.2)
|
||||
<li> <a href="https://cloverfirefly.itch.io" rel="nofollow">cloverfirefly</a> (6.99.13–7.2)
|
||||
<li> CobaltCore (7.1.1–7.2)
|
||||
<li> CobraPL (7.1–7.2)
|
||||
<li> <a href="https://www.renpy.org/agegate?url=https://obscurasoft.com/" rel="nofollow">Coming Out On Top</a> (6.99.13–7.2)
|
||||
<li> <a href="http://www.computerart.club/" rel="nofollow">computerart.club</a> (6.99.14–7.2)
|
||||
<li> CookieNomNom (7.1.1–7.2)
|
||||
<li> Crazy Cactus (6.99.14.3–7.2)
|
||||
<li> <a href="https://www.renpy.org/agegate?url=http://cricketbat.me" rel="nofollow">cricketbat_</a> (7.0–7.2)
|
||||
<li> Cherry Kiss Games (6.99.14.1–7.2.2)
|
||||
<li> Chrysoula Tzavelas (6.99.13–7.2.2)
|
||||
<li> <a href="https://cloverfirefly.itch.io" rel="nofollow">cloverfirefly</a> (6.99.13–7.2.2)
|
||||
<li> CobaltCore (7.1.1–7.2.2)
|
||||
<li> CobraPL (7.1–7.2.2)
|
||||
<li> <a href="https://www.renpy.org/agegate?url=https://obscurasoft.com/" rel="nofollow">Coming Out On Top</a> (6.99.13–7.2.2)
|
||||
<li> <a href="http://www.computerart.club/" rel="nofollow">computerart.club</a> (6.99.14–7.2.2)
|
||||
<li> CookieNomNom (7.1.1–7.2.2)
|
||||
<li> Crazy Cactus (6.99.14.3–7.2.2)
|
||||
<li> <a href="https://www.renpy.org/agegate?url=http://cricketbat.me" rel="nofollow">cricketbat_</a> (7.0–7.2.2)
|
||||
<li> <a href="http://cultofape.com" rel="nofollow">CultOfApe</a> (6.99.14–7.0)
|
||||
<li> Culture4Jam (6.99.13–7.2)
|
||||
<li> Culture4Jam (6.99.13–7.2.2)
|
||||
<li> Daniel Burns (7.1–7.2)
|
||||
<li> <a href="https://www.renpy.org/agegate?url=http://www.darkministry.net" rel="nofollow">Dark Ministry</a> (7.2)
|
||||
<li> <a href="https://www.renpy.org/agegate?url=http://www.darkministry.net" rel="nofollow">Dark Ministry</a> (7.2–7.2.2)
|
||||
<li> <a href="http://dark-aii.net" rel="nofollow">Dark-Aii</a> (6.99.14.1–6.99.14.3)
|
||||
<li> <a href="http://www.darksirengames.com" rel="nofollow">DarkSirenGames</a> (6.99.13–7.2)
|
||||
<li> Darryl Taylor (7.0–7.2)
|
||||
<li> David Koster (6.99.14–7.2)
|
||||
<li> David Yingling (6.99.14.1–7.2)
|
||||
<li> Dee (7.1–7.2)
|
||||
<li> Denny Cheng (6.99.14.2–7.2)
|
||||
<li> Dharker Studios Ltd (6.99.13–6.99.14, 7.1.1–7.2)
|
||||
<li> <a href="http://coriginate.com" rel="nofollow">Ds110</a> (6.99.13–7.2)
|
||||
<li> DuoDevelopers (7.1.1–7.2)
|
||||
<li> E. William Brown (6.99.13–7.2)
|
||||
<li> <a href="http://www.darksirengames.com" rel="nofollow">DarkSirenGames</a> (6.99.13–7.2.2)
|
||||
<li> Darryl Taylor (7.0–7.2.2)
|
||||
<li> David Koster (6.99.14–7.2.2)
|
||||
<li> David Yingling (6.99.14.1–7.2.2)
|
||||
<li> Dee (7.1–7.2.2)
|
||||
<li> Denny Cheng (6.99.14.2–7.2.2)
|
||||
<li> Dharker Studios Ltd (6.99.13–6.99.14, 7.1.1–7.2.2)
|
||||
<li> <a href="http://coriginate.com" rel="nofollow">Ds110</a> (6.99.13–7.2.2)
|
||||
<li> DuoDevelopers (7.1.1–7.2.2)
|
||||
<li> E. William Brown (6.99.13–7.2.2)
|
||||
<li> <a href="https://twitter.com/WC_EchoFrost" rel="nofollow">EchoFrost (Watercress)</a> (6.99.14.2–7.2)
|
||||
<li> <a href="https://www.youtube.com/c/ElaineDoesCoding" rel="nofollow">ElaineDoesCoding</a> (7.0–7.2)
|
||||
<li> eric chi hung ng (7.0–7.2)
|
||||
<li> Euan 'Xolf' Robertson (6.99.13–7.2)
|
||||
<li> <a href="https://www.youtube.com/c/ElaineDoesCoding" rel="nofollow">ElaineDoesCoding</a> (7.0–7.2.2)
|
||||
<li> eric chi hung ng (7.0–7.2.2)
|
||||
<li> Euan 'Xolf' Robertson (6.99.13–7.2.2)
|
||||
<li> Eve Hallows (6.99.13–6.99.14)
|
||||
<li> Exiscoming (6.99.14–7.2)
|
||||
<li> Exiscoming (6.99.14–7.2.2)
|
||||
<li> Fuseblower (7.0–7.2)
|
||||
<li> George (7.1–7.2)
|
||||
<li> George (7.1–7.2.2)
|
||||
<li> <a href="http://www.gtbono.com" rel="nofollow">Giovanni Tempobono</a> (7.1–7.1.2)
|
||||
<li> <a href="https://goldengamebarn.com/" rel="nofollow">Golden Game Barn</a> (6.99.13–7.2)
|
||||
<li> <a href="https://twitter.com/goodtalesvn" rel="nofollow">Good Tales</a> (7.1–7.2)
|
||||
<li> Great Chicken Studio (6.99.14–7.2)
|
||||
<li> Grimiku (7.0–7.2)
|
||||
<li> <a href="https://goldengamebarn.com/" rel="nofollow">Golden Game Barn</a> (6.99.13–7.2.2)
|
||||
<li> <a href="https://twitter.com/goodtalesvn" rel="nofollow">Good Tales</a> (7.1–7.2.2)
|
||||
<li> Great Chicken Studio (6.99.14–7.2.2)
|
||||
<li> Grimiku (7.0–7.2.2)
|
||||
<li> Gunso (6.99.13–7.2)
|
||||
<li> HAG Productions (7.1.1–7.2)
|
||||
<li> <a href="http://hanb.jp" rel="nofollow">HanbitGaram (MOEWORK Entertainment)</a> (7.1–7.2)
|
||||
<li> HAG Productions (7.1.1–7.2.2)
|
||||
<li> <a href="http://hanb.jp" rel="nofollow">HanbitGaram (MOEWORK Entertainment)</a> (7.1–7.2.2)
|
||||
<li> Happimochi (6.99.13–6.99.14)
|
||||
<li> HopesGaming (7.1.1–7.2)
|
||||
<li> HopesGaming (7.1.1–7.2.2)
|
||||
<li> <a href="https://ijemin.com" rel="nofollow">I_Jemin</a> (6.99.13–7.0)
|
||||
<li> <a href="https://irredeemable.net/" rel="nofollow">Irredeemable Games</a> (6.99.14–7.2)
|
||||
<li> J. C. Holder (6.99.13–7.2)
|
||||
<li> <a href="https://irredeemable.net/" rel="nofollow">Irredeemable Games</a> (6.99.14–7.2.2)
|
||||
<li> J. C. Holder (6.99.13–7.2.2)
|
||||
<li> James Tyner (6.99.13–7.1)
|
||||
<li> JeongPyo Lee (6.99.13–7.2)
|
||||
<li> Joanna B (6.99.13–7.2)
|
||||
<li> JoeBanks (7.1–7.2)
|
||||
<li> Jonas Kyratzes (7.1.1–7.2)
|
||||
<li> jonnymelabo (6.99.13–7.2)
|
||||
<li> Joseph Denicola (7.2)
|
||||
<li> JeongPyo Lee (6.99.13–7.2.2)
|
||||
<li> Joanna B (6.99.13–7.2.2)
|
||||
<li> JoeBanks (7.1–7.2.2)
|
||||
<li> Jonas Kyratzes (7.1.1–7.2.2)
|
||||
<li> jonnymelabo (6.99.13–7.2.2)
|
||||
<li> Joseph Denicola (7.2–7.2.2)
|
||||
<li> Josh Kaplan (6.99.13–6.99.14.2)
|
||||
<li> K. Starbuck (6.99.13–6.99.14)
|
||||
<li> Kampmichi (7.1.1–7.2)
|
||||
<li> Kayde Initials (6.99.13–7.2)
|
||||
<li> <a href="http://kexboy.com" rel="nofollow">KEXBOY</a> (6.99.13–7.2)
|
||||
<li> Kikai Digital (7.0–7.2)
|
||||
<li> <a href="http://www.gamesbykinmoku.com" rel="nofollow">Kinmoku</a> (6.99.13–7.2)
|
||||
<li> <a href="http://krsm94.web.fc2.com/" rel="nofollow">Klast Halc (crAsm)</a> (6.99.14.2–7.2)
|
||||
<li> Kosmos Games (6.99.14–7.2)
|
||||
<li> Kampmichi (7.1.1–7.2.2)
|
||||
<li> Katryna Starks (7.2.1–7.2.2)
|
||||
<li> Kayde Initials (6.99.13–7.2.2)
|
||||
<li> <a href="http://kexboy.com" rel="nofollow">KEXBOY</a> (6.99.13–7.2.2)
|
||||
<li> Kikai Digital (7.0–7.2.2)
|
||||
<li> <a href="http://www.gamesbykinmoku.com" rel="nofollow">Kinmoku</a> (6.99.13–7.2.2)
|
||||
<li> <a href="http://krsm94.web.fc2.com/" rel="nofollow">Klast Halc (crAsm)</a> (6.99.14.2–7.2.2)
|
||||
<li> Kosmos Games (6.99.14–7.2.2)
|
||||
<li> <a href="https://de.koubaibu.tech/" rel="nofollow">Koubaibu Technology Group</a> (7.1–7.1.2)
|
||||
<li> Kraig Halfpap (6.99.14.2–7.2)
|
||||
<li> L C (7.2)
|
||||
<li> <a href="https://www.renpy.org/agegate?url=https://la-cucaracha-studios.itch.io/" rel="nofollow">La Cucaracha Studios</a> (7.1.2–7.2)
|
||||
<li> lain105/EckoMars (6.99.13–7.2)
|
||||
<li> <a href="http://waltz.moe/portfolio/" rel="nofollow">Laiska</a> (7.2)
|
||||
<li> LateWhiteRabbit (6.99.14–7.2)
|
||||
<li> <a href="https://lernakow.itch.io" rel="nofollow">Lernakow</a> (6.99.14.3–7.2)
|
||||
<li> Kraig Halfpap (6.99.14.2–7.2.2)
|
||||
<li> L C (7.2–7.2.2)
|
||||
<li> <a href="https://www.renpy.org/agegate?url=https://la-cucaracha-studios.itch.io/" rel="nofollow">La Cucaracha Studios</a> (7.1.2–7.2.2)
|
||||
<li> lain105/EckoMars (6.99.13–7.2.2)
|
||||
<li> <a href="http://waltz.moe/portfolio/" rel="nofollow">Laiska</a> (7.2–7.2.2)
|
||||
<li> LateWhiteRabbit (6.99.14–7.2.2)
|
||||
<li> <a href="https://lernakow.itch.io" rel="nofollow">Lernakow</a> (6.99.14.3–7.2.2)
|
||||
<li> LewdKitty (7.0–7.2)
|
||||
<li> <a href="https://www.renpy.org/agegate?url=https://www.lewdlab.com/" rel="nofollow">Lewdlab</a> (6.99.14.1–7.2)
|
||||
<li> <a href="https://lemmasoft.renai.us/forums/viewtopic.php?f=66&t=48011&p=482643" rel="nofollow">Lezalith</a> (7.1–7.2)
|
||||
<li> LewdKitty (7.2.1–7.2.2)
|
||||
<li> <a href="https://www.renpy.org/agegate?url=https://www.lewdlab.com/" rel="nofollow">Lewdlab</a> (6.99.14.1–7.2.2)
|
||||
<li> <a href="https://lemmasoft.renai.us/forums/viewtopic.php?f=66&t=48011&p=482643" rel="nofollow">Lezalith</a> (7.1–7.2.2)
|
||||
<li> Lictor (6.99.14.2–7.1.2)
|
||||
<li> Little Napoleon (7.1–7.2)
|
||||
<li> <a href="http://trash.moe/" rel="nofollow">Lore</a> (6.99.13–7.2)
|
||||
<li> Luke Jackson (6.99.13–7.2)
|
||||
<li> <a href="http://luscious-spirit-studios.tumblr.com/" rel="nofollow">Luscious Spirit Studios</a> (6.99.13–7.2)
|
||||
<li> <a href="https://www.renpy.org/agegate?url=https://www.patreon.com/Luxee" rel="nofollow">Luxee</a> (6.99.14.3–7.2)
|
||||
<li> Maeve Bandruid (6.99.13–7.2)
|
||||
<li> <a href="https://www.renpy.org/agegate?url=https://www.patreon.com/manitu" rel="nofollow">MANITU Games CO.</a> (6.99.13–7.2)
|
||||
<li> <a href="https://manorstories.fr" rel="nofollow">ManorStories</a> (6.99.14–7.2)
|
||||
<li> <a href="http://www.marionettecode.com/" rel="nofollow">Marionette</a> (6.99.14–7.2)
|
||||
<li> Little Napoleon (7.1–7.2.2)
|
||||
<li> <a href="http://trash.moe/" rel="nofollow">Lore</a> (6.99.13–7.2.2)
|
||||
<li> Luke Jackson (6.99.13–7.2.2)
|
||||
<li> <a href="http://luscious-spirit-studios.tumblr.com/" rel="nofollow">Luscious Spirit Studios</a> (6.99.13–7.2.2)
|
||||
<li> <a href="https://www.renpy.org/agegate?url=https://www.patreon.com/Luxee" rel="nofollow">Luxee</a> (6.99.14.3–7.2.2)
|
||||
<li> Maeve Bandruid (6.99.13–7.2.2)
|
||||
<li> <a href="https://www.renpy.org/agegate?url=https://www.patreon.com/manitu" rel="nofollow">MANITU Games CO.</a> (6.99.13–7.2.2)
|
||||
<li> <a href="https://manorstories.fr" rel="nofollow">ManorStories</a> (6.99.14–7.2.2)
|
||||
<li> <a href="http://www.marionettecode.com/" rel="nofollow">Marionette</a> (6.99.14–7.2.2)
|
||||
<li> Matthew (7.2)
|
||||
<li> Matthew Preston (7.2)
|
||||
<li> Matthew Preston (7.2)
|
||||
<li> Matthew Preston (7.2–7.2.2)
|
||||
<li> MAW.3D.Art (6.99.13)
|
||||
<li> Max J Sher (6.99.13–7.2)
|
||||
<li> MCatter Dev (7.1–7.2)
|
||||
<li> <a href="https://meagantrott.com" rel="nofollow">Meagan Trott</a> (6.99.13–7.2)
|
||||
<li> <a href="http://metasepia.icecavern.net" rel="nofollow">Metasepia Games</a> (6.99.13–7.2)
|
||||
<li> Meyvol (6.99.14.1–7.2)
|
||||
<li> Michael Lewis (7.2)
|
||||
<li> Michael Simon (7.1.1–7.2)
|
||||
<li> Michaela Laws (6.99.13–7.2)
|
||||
<li> Max J Sher (6.99.13–7.2.2)
|
||||
<li> MCatter Dev (7.1–7.2.2)
|
||||
<li> <a href="https://meagantrott.com" rel="nofollow">Meagan Trott</a> (6.99.13–7.2.2)
|
||||
<li> <a href="http://metasepia.icecavern.net" rel="nofollow">Metasepia Games</a> (6.99.13–7.2.2)
|
||||
<li> Meyvol (6.99.14.1–7.2.2)
|
||||
<li> Michael Lewis (7.2–7.2.2)
|
||||
<li> Michael Simon (7.1.1–7.2.2)
|
||||
<li> Michaela Laws (6.99.13–7.2.2)
|
||||
<li> <a href="http://www.milkteestudios.com" rel="nofollow">Milktee Studios</a> (7.0–7.1)
|
||||
<li> Miss Skizzors (7.0–7.2)
|
||||
<li> Montse Latre (7.1.1–7.2)
|
||||
<li> <a href="https://moonlitworks.com/" rel="nofollow">Moonlit Works</a> (7.0–7.2)
|
||||
<li> <a href="https://mrvaldez.ph/" rel="nofollow">MrValdez</a> (7.0–7.2)
|
||||
<li> Miss Skizzors (7.0–7.2.2)
|
||||
<li> Montse Latre (7.1.1–7.2.2)
|
||||
<li> <a href="https://moonlitworks.com/" rel="nofollow">Moonlit Works</a> (7.0–7.2.2)
|
||||
<li> <a href="https://mrvaldez.ph/" rel="nofollow">MrValdez</a> (7.0–7.2.2)
|
||||
<li> <a href="https://ms45.itch.io" rel="nofollow">Ms .45</a> (6.99.13)
|
||||
<li> nasredin (6.99.14.3–7.2)
|
||||
<li> Natalie Van Sistine (7.0–7.2)
|
||||
<li> <a href="https://cafe.naver.com/vmo.cafe" rel="nofollow">Naver cafe VMO</a> (7.1–7.2)
|
||||
<li> nasredin (6.99.14.3–7.2.2)
|
||||
<li> Natalie Van Sistine (7.0–7.2.2)
|
||||
<li> <a href="https://cafe.naver.com/vmo.cafe" rel="nofollow">Naver cafe VMO</a> (7.1–7.2.2)
|
||||
<li> <a href="http://cafe.naver.com/vmo" rel="nofollow">NaverCafe VMO</a> (6.99.13–7.1)
|
||||
<li> Neigan (7.0)
|
||||
<li> Nicholas Swenson (6.99.14–7.2)
|
||||
<li> <a href="https://www.patreon.com/nikraria" rel="nofollow">nikraria</a> (6.99.13–7.2)
|
||||
<li> Nova Alamak (6.99.14.3–7.2)
|
||||
<li> Nicholas Swenson (6.99.14–7.2.2)
|
||||
<li> <a href="https://www.patreon.com/nikraria" rel="nofollow">nikraria</a> (6.99.13–7.2.2)
|
||||
<li> Nova Alamak (6.99.14.3–7.2.2)
|
||||
<li> <a href="http://devety.com" rel="nofollow">Oleksii olety Kyrylchuk</a> (6.99.14.1–6.99.14.2)
|
||||
<li> Panda Penguin Games (7.2)
|
||||
<li> Panda Penguin Games (7.2–7.2.2)
|
||||
<li> Panthera Morag (6.99.13–7.2)
|
||||
<li> Patchwork Princess (7.1–7.2)
|
||||
<li> Patchwork Princess (7.1–7.2.2)
|
||||
<li> Patricia (6.99.13–7.1.1)
|
||||
<li> <a href="http://peachpattch.com/" rel="nofollow">peachpattch</a> (6.99.14–7.2)
|
||||
<li> philat (6.99.14.1–7.2)
|
||||
<li> Pi3shark (7.2)
|
||||
<li> plasterbrain (6.99.13–7.2)
|
||||
<li> <a href="http://peachpattch.com/" rel="nofollow">peachpattch</a> (6.99.14–7.2.2)
|
||||
<li> philat (6.99.14.1–7.2.2)
|
||||
<li> Pi3shark (7.2–7.2.2)
|
||||
<li> plasterbrain (6.99.13–7.2.2)
|
||||
<li> Ploppertje (6.99.13–6.99.14.3)
|
||||
<li> Podge (6.99.13–7.2)
|
||||
<li> Podge (6.99.13–7.2.2)
|
||||
<li> Puriwit Sesupaponphong (6.99.13)
|
||||
<li> Puriwit Sesupaponphong (6.99.13–7.1)
|
||||
<li> Pólyí Konrád (7.1–7.1.1)
|
||||
<li> <a href="http://www.cottoncandycyanide.com/" rel="nofollow">Quantum Suicide</a> (7.1–7.2)
|
||||
<li> <a href="http://razzartvisual.com/" rel="nofollow">Razz</a> (6.99.13–7.2)
|
||||
<li> <a href="http://razzartvisual.com/" rel="nofollow">Razz</a> (6.99.13–7.2.2)
|
||||
<li> <a href="https://twitter.com/ragnarhomsar" rel="nofollow">Realga</a> (7.0–7.1)
|
||||
<li> René Dudfield (6.99.13–7.2)
|
||||
<li> Richard (6.99.14–7.2)
|
||||
<li> <a href="http://impqueen.com/" rel="nofollow">Rinmaru</a> (6.99.13–7.2)
|
||||
<li> Rizia Praja (6.99.14.1–7.2)
|
||||
<li> Robyn Rosenfeld (7.1, 7.2)
|
||||
<li> René Dudfield (6.99.13–7.2.2)
|
||||
<li> Richard (6.99.14–7.2.2)
|
||||
<li> <a href="http://impqueen.com/" rel="nofollow">Rinmaru</a> (6.99.13–7.2.2)
|
||||
<li> Rizia Praja (6.99.14.1–7.2.2)
|
||||
<li> Robyn Rosenfeld (7.1, 7.2–7.2.2)
|
||||
<li> <a href="http://sunkoiwish.com/" rel="nofollow">Royce A Xaiyeon</a> (6.99.13–7.2)
|
||||
<li> Ryan Balgos (6.99.14–7.2)
|
||||
<li> Ryan Balgos (6.99.14–7.2.2)
|
||||
<li> Ryan Whited (6.99.14–7.1)
|
||||
<li> <a href="https://saiffyros.wordpress.com/" rel="nofollow">Saiffyros</a> (6.99.14–7.2)
|
||||
<li> Sam Yang (6.99.13–7.2)
|
||||
<li> <a href="https://saiffyros.wordpress.com/" rel="nofollow">Saiffyros</a> (6.99.14–7.2.2)
|
||||
<li> Sam Yang (6.99.13–7.2.2)
|
||||
<li> Simply Aria (6.99.14–7.2)
|
||||
<li> <a href="http://sleepyagents.co.uk" rel="nofollow">Sleepy Agents</a> (6.99.13–7.2)
|
||||
<li> Snowlocke (7.0–7.2)
|
||||
<li> <a href="http://sleepyagents.co.uk" rel="nofollow">Sleepy Agents</a> (6.99.13–7.2.2)
|
||||
<li> Snowlocke (7.0–7.2.2)
|
||||
<li> <a href="http://store.steampowered.com/app/570350/Sorry_Entschuldigung__A_Psychological_Horror_Visual_Novel/" rel="nofollow">Sorry. (Entschuldigung)</a> (6.99.14.2–7.1)
|
||||
<li> Spyros Panagiotopoulos (6.99.14–7.1)
|
||||
<li> Steamgirl (6.99.13–6.99.14, 7.2)
|
||||
<li> Steamlynx (6.99.14.1–7.2)
|
||||
<li> Stefan Gagne (6.99.13–7.2)
|
||||
<li> <a href="https://www.renpy.org/agegate?url=http://www.patreon.com/errilhl" rel="nofollow">Studio Errilhl</a> (6.99.14.3–7.2)
|
||||
<li> Steamgirl (6.99.13–6.99.14, 7.2–7.2.2)
|
||||
<li> Steamlynx (6.99.14.1–7.2.2)
|
||||
<li> Stefan Gagne (6.99.13–7.2.2)
|
||||
<li> <a href="https://www.renpy.org/agegate?url=http://www.patreon.com/errilhl" rel="nofollow">Studio Errilhl</a> (6.99.14.3–7.2.2)
|
||||
<li> <a href="https://vnstudioelan.com/" rel="nofollow">Studio Élan</a> (7.2.1–7.2.2)
|
||||
<li> <a href="https://play.google.com/store/apps/developer?id=Suhaimizs&hl=en" rel="nofollow">Suhaimizs</a> (7.2)
|
||||
<li> Taosym (6.99.13–7.2)
|
||||
<li> Taosym (6.99.13–7.2.2)
|
||||
<li> Teofilo Hurtado (6.99.14–7.1.1)
|
||||
<li> the66 (7.2)
|
||||
<li> the66 (7.2–7.2.2)
|
||||
<li> Thomas Charlie (6.99.13–7.2)
|
||||
<li> Timothy Landreth (6.99.13–7.2)
|
||||
<li> Timothy Young (6.99.14.1–7.2)
|
||||
<li> TJ Huckabee (6.99.13–7.2)
|
||||
<li> Timothy Landreth (6.99.13–7.2.2)
|
||||
<li> Timothy Young (6.99.14.1–7.2.2)
|
||||
<li> TJ Huckabee (6.99.13–7.2.2)
|
||||
<li> ToastCrust (6.99.13–6.99.14)
|
||||
<li> Tobias Wüthrich (6.99.13–6.99.14.3)
|
||||
<li> <a href="http://tremmigames.tk/" rel="nofollow">tremmiGames</a> (7.0–7.2)
|
||||
<li> trooper6 (6.99.13–7.2)
|
||||
<li> <a href="https://www.youtube.com/tryinmorning" rel="nofollow">tryinmorning</a> (6.99.13–7.2)
|
||||
<li> <a href="https://studiomugenjohncel.wordpress.com/" rel="nofollow">Uncle Mugen (Mugenjohncel</a> (6.99.14.2–7.2)
|
||||
<li> <a href="http://www.visualsaga.com/" rel="nofollow">Visual Saga</a> (6.99.13–7.2)
|
||||
<li> <a href="http://tremmigames.tk/" rel="nofollow">tremmiGames</a> (7.0–7.2.2)
|
||||
<li> trooper6 (6.99.13–7.2.2)
|
||||
<li> <a href="https://www.youtube.com/tryinmorning" rel="nofollow">tryinmorning</a> (6.99.13–7.2.2)
|
||||
<li> <a href="https://studiomugenjohncel.wordpress.com/" rel="nofollow">Uncle Mugen (Mugenjohncel</a> (6.99.14.2–7.2.2)
|
||||
<li> <a href="http://www.visualsaga.com/" rel="nofollow">Visual Saga</a> (6.99.13–7.2.2)
|
||||
<li> Vollschauer (6.99.13–6.99.14)
|
||||
<li> <a href="http://vxlloyd.com" rel="nofollow">VX Lloyd</a> (6.99.14.3–7.0)
|
||||
<li> Wayne Wollesen (6.99.14.3–7.1.1)
|
||||
<li> <a href="https://www.facebook.com/weeabooriot" rel="nofollow">Weeabooriot Cosplay</a> (6.99.13–7.2)
|
||||
<li> <a href="https://arcaniinteractive.com/" rel="nofollow">William Lucht</a> (7.0–7.2)
|
||||
<li> <a href="https://www.facebook.com/weeabooriot" rel="nofollow">Weeabooriot Cosplay</a> (6.99.13–7.2.2)
|
||||
<li> <a href="https://arcaniinteractive.com/" rel="nofollow">William Lucht</a> (7.0–7.2.2)
|
||||
<li> William Tumeo (6.99.13–7.1.2)
|
||||
<li> <a href="http://winterwolves.com/ambersmagicshop.htm" rel="nofollow">Winter Wolves</a> (6.99.13–7.2)
|
||||
<li> <a href="https://www.wolfbiteinteractive.com/" rel="nofollow">WolfBite Interactive</a> (7.2)
|
||||
<li> Y Press Games (7.2)
|
||||
<li> <a href="http://astralore.com/" rel="nofollow">Yakmala!</a> (6.99.13–7.2)
|
||||
<li> <a href="http://winterwolves.com/ambersmagicshop.htm" rel="nofollow">Winter Wolves</a> (6.99.13–7.2.2)
|
||||
<li> <a href="https://www.wolfbiteinteractive.com/" rel="nofollow">WolfBite Interactive</a> (7.2–7.2.2)
|
||||
<li> Y Press Games (7.2–7.2.2)
|
||||
<li> <a href="http://astralore.com/" rel="nofollow">Yakmala!</a> (6.99.13–7.2.2)
|
||||
</ul>
|
||||
|
||||
@@ -96,7 +96,7 @@ formatting syntax. Ren'Py uses [ to introduce string formatting
|
||||
because { was taken by text tags.
|
||||
|
||||
Along with the ``!s`` and ``!r`` conversion flags supported by Python, Ren'Py
|
||||
supports ``!q`` and ``!t`` conversion flags. The ``!q`` conversion flag ensures that
|
||||
supports several more flags. The ``!q`` conversion flag ensures that
|
||||
text tags are properly quoted, so that displaying a string will not
|
||||
introduce unwanted formatting constructs. For example::
|
||||
|
||||
@@ -112,6 +112,10 @@ The ``!t`` flag will translate the interpolated string::
|
||||
g "I'm [mood!t] to see you."
|
||||
|
||||
|
||||
The ``!u`` flag forces the text to upper case, the ``!l`` flag forces the
|
||||
text to lower-case, and the ``!c`` flag capitalizes the text.
|
||||
|
||||
|
||||
Styling and Text Tags
|
||||
=====================
|
||||
|
||||
|
||||
@@ -1025,7 +1025,7 @@ translate french strings:
|
||||
|
||||
# _errorhandling.rpym:663
|
||||
old "Opens the errors.txt file in a text editor."
|
||||
new "Ouvrir le fihchier « errors.txt » dans un éditeur de texte."
|
||||
new "Ouvrir le fichier « errors.txt » dans un éditeur de texte."
|
||||
|
||||
# _errorhandling.rpym:667
|
||||
old "Copies the errors.txt file to the clipboard."
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 420 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 169 KiB |
@@ -1 +1 @@
|
||||
{"display_name": "The Question", "add_from": true, "renamed_all": true, "renamed_steam": true, "force_recompile": true, "build_update": true, "packages": ["mac", "pc", "linux", "win", "market"], "type": "hidden"}
|
||||
{"display_name": "The Question", "android_build": "Release", "type": "hidden", "renamed_all": true, "renamed_steam": true, "force_recompile": true, "build_update": true, "packages": ["mac", "pc"], "add_from": true}
|
||||
@@ -776,6 +776,7 @@ screen preferences():
|
||||
textbutton "English" text_font "DejaVuSans.ttf"action Language(None)
|
||||
textbutton "Français" text_font "DejaVuSans.ttf"action Language("french")
|
||||
textbutton "Русский" text_font "DejaVuSans.ttf" action Language("russian")
|
||||
textbutton "Español" text_font "DejaVuSans.ttf"action Language("spanish")
|
||||
|
||||
# This should be last.
|
||||
textbutton "Pig Latin" text_font "DejaVuSans.ttf" action Language("piglatin")
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
|
||||
translate spanish strings:
|
||||
|
||||
# 01example.rpy:473
|
||||
old "Copied the example to the clipboard."
|
||||
new "Se copió el ejemplo al portapapeles."
|
||||
|
||||
# 01example.rpy:546
|
||||
old "copy"
|
||||
new "copiar"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,211 @@
|
||||
|
||||
# game/indepth_character.rpy:11
|
||||
translate spanish demo_character_e7e1b1bb:
|
||||
|
||||
# e "We've already seen how to define a Character in Ren'Py. But I want to go into a bit more detail as to what a Character is."
|
||||
e "Ya hemos visto cómo definir Character en Ren'Py, pero quiero entrar en más detalles sobre qué es un Character."
|
||||
|
||||
# game/indepth_character.rpy:17
|
||||
translate spanish demo_character_d7908a94:
|
||||
|
||||
# e "Here are couple of additional characters."
|
||||
e "Aquí hay un par de personajes adicionales."
|
||||
|
||||
# game/indepth_character.rpy:19
|
||||
translate spanish demo_character_275ef8b9:
|
||||
|
||||
# e "Each statement creates a Character object, and gives it a single argument, a name. If the name is None, no name is displayed."
|
||||
e "Cada sentencia crea un objeto Character y le da un solo argumento, un nombre. Si el nombre es None(Ninguno), no se muestra ningún nombre."
|
||||
|
||||
# game/indepth_character.rpy:21
|
||||
translate spanish demo_character_a63aea0c:
|
||||
|
||||
# e "This can be followed by named arguments that set properties of the character. A named argument is a property name, an equals sign, and a value."
|
||||
e "Esto puede ser seguido por argumentos con nombre que establecen las propiedades del personaje. Un argumento con nombre es el nombre de propiedad, un signo igual y un valor."
|
||||
|
||||
# game/indepth_character.rpy:23
|
||||
translate spanish demo_character_636a502e:
|
||||
|
||||
# e "Multiple arguments should be separated with commas, like they are here. Let's see those characters in action."
|
||||
e "Los argumentos múltiples se deben separar con comas, como están aquí. Veamos esos personajes en acción."
|
||||
|
||||
# game/indepth_character.rpy:27
|
||||
translate spanish demo_character_44b54e1d:
|
||||
|
||||
# e_shout "I can shout!"
|
||||
e_shout "Puedo gritar"
|
||||
|
||||
# game/indepth_character.rpy:29
|
||||
translate spanish demo_character_a9646dd8:
|
||||
|
||||
# e_whisper "And I can speak in a whisper."
|
||||
e_whisper "Y puedo hablar susurrando."
|
||||
|
||||
# game/indepth_character.rpy:31
|
||||
translate spanish demo_character_79793208:
|
||||
|
||||
# e "This example shows how the name Character is a bit of a misnomer. Here, we have multiple Characters in use, but you see it as me speaking."
|
||||
e "Este ejemplo muestra cómo el nombre Character es un poco inapropiado. Aquí, tenemos varios Characters en uso, pero los ves como mi hablar."
|
||||
|
||||
# game/indepth_character.rpy:33
|
||||
translate spanish demo_character_5d5d7482:
|
||||
|
||||
# e "It's best to think of a Character as repesenting a name and style, rather than a single person."
|
||||
e "Es mejor pensar que un Character representa un nombre y un estilo, en lugar de una sola persona."
|
||||
|
||||
# game/indepth_character.rpy:37
|
||||
translate spanish demo_character_66d08d98:
|
||||
|
||||
# e "There are a lot of properties that can be given to Characters, most of them prefixed styles."
|
||||
e "Hay muchas propiedades que se pueden asignar a los Characters, la mayoría de ellos con estilos prefijados."
|
||||
|
||||
# game/indepth_character.rpy:39
|
||||
translate spanish demo_character_7e0d75aa:
|
||||
|
||||
# e "Properties beginning with window apply to the textbox, those with what apply to the the dialogue, and those with who to the name of Character speaking."
|
||||
e "Las propiedades que comienzan con window se aplican al cuadro de texto, aquellas con what se aplican al diálogo y aquellas con who al nombre del Character que habla."
|
||||
|
||||
# game/indepth_character.rpy:41
|
||||
translate spanish demo_character_56703784:
|
||||
|
||||
# e "If you leave a prefix out, the style customizes the name of the speaker."
|
||||
e "Si dejas un prefijo, el estilo personaliza el nombre del orador."
|
||||
|
||||
# game/indepth_character.rpy:43
|
||||
translate spanish demo_character_b456f0a9:
|
||||
|
||||
# e "There are quite a few different properties that can be set this way. Here are some of the most useful."
|
||||
e "Hay bastantes propiedades diferentes que se pueden establecer de esta manera. Éstos son algunos de los más útiles."
|
||||
|
||||
# game/indepth_character.rpy:54
|
||||
translate spanish demo_character_18ba073d:
|
||||
|
||||
# e1a "If it's set to None, the textbox has no background window."
|
||||
e1a "Si está configurado como None, el cuadro de texto no tiene ventana de fondo."
|
||||
|
||||
# game/indepth_character.rpy:59
|
||||
translate spanish demo_character_5a26445c:
|
||||
|
||||
# e2 "The who_color and what_color properties set the color of the character's name and dialogue text, respectively."
|
||||
e2 "Las propiedades who_color y what_color establecen el color del texto del nombre y diálogo del personaje, respectivamente."
|
||||
|
||||
# game/indepth_character.rpy:61
|
||||
translate spanish demo_character_88a18c32:
|
||||
|
||||
# e2 "The colors are strings containing rgb hex codes, the same sort of colors understood by a web browser."
|
||||
e2 "Los colores son cadenas que contienen códigos hexadecimales rgb, el mismo tipo de colores que entiende un navegador web."
|
||||
|
||||
# game/indepth_character.rpy:67
|
||||
translate spanish demo_character_ed690751:
|
||||
|
||||
# e3 "Similarly, the who_font and what_font properties set the font used by the different kinds of text."
|
||||
e3 "De manera similar, las propiedades who_font y what_font establecen la fuente utilizada por los diferentes tipos de texto."
|
||||
|
||||
# game/indepth_character.rpy:74
|
||||
translate spanish demo_character_8dfa6426:
|
||||
|
||||
# e4 "Setting the who_bold, what_italic, and what_size properties makes the name bold, and the dialogue text italic at a size of 20 pixels."
|
||||
e4 "La configuración de las propiedades who_bold, what_italic y what_size hace que el nombre aparezca en negrita y el texto del diálogo en cursiva a un tamaño de 20 píxeles."
|
||||
|
||||
# game/indepth_character.rpy:76
|
||||
translate spanish demo_character_20e83c32:
|
||||
|
||||
# e4 "Of course, the what_bold, who_italic and who_size properties also exist, even if they're not used here."
|
||||
e4 "Por supuesto, las propiedades what_bold, who_italic y who_size también existen, incluso si no se usan aquí."
|
||||
|
||||
# game/indepth_character.rpy:83
|
||||
translate spanish demo_character_e4cbb1f2:
|
||||
|
||||
# e5 "The what_outlines property puts an outline around the text."
|
||||
e5 "La propiedad what_outlines coloca un contorno alrededor del texto."
|
||||
|
||||
# game/indepth_character.rpy:85
|
||||
translate spanish demo_character_71535ecf:
|
||||
|
||||
# e5 "It's a little complicated since it takes a list with a tuple in it, with the tuple being four things in parenthesis, and the list the square brackets around them."
|
||||
e5 "Es un poco complicado, ya que toma una lista con una tupla, con la tupla cuatro cosas entre paréntesis, y los corchetes de la lista a su alrededor."
|
||||
|
||||
# game/indepth_character.rpy:87
|
||||
translate spanish demo_character_e9ac7482:
|
||||
|
||||
# e5 "The first number is the size of the outline, in pixels. That's followed by a string giving the hex-code of the color of the outline, and the x and y offsets."
|
||||
e5 "El primer número es el tamaño del contorno, en píxeles. A esto le sigue una cadena que proporciona el código hexadecimal del color del contorno y las compensaciones de x e y."
|
||||
|
||||
# game/indepth_character.rpy:93
|
||||
translate spanish demo_character_ea72d988:
|
||||
|
||||
# e6 "When the outline size is 0 and the offsets are given, what_outlines can also act as a drop-shadow behind the text."
|
||||
e6 "Cuando el tamaño del contorno es 0 y se dan las compensaciones, what_outlines también puede actuar como una sombra detrás del texto."
|
||||
|
||||
# game/indepth_character.rpy:99
|
||||
translate spanish demo_character_8d35ebcd:
|
||||
|
||||
# e7 "The what_xalign and what_textalign properties control the alignment of text, with 0.0 being left, 0.5 being center, and 1.0 being right."
|
||||
e7 "Las propiedades what_xalign y what_textalign controlan la alineación del texto, con 0.0 a la izquierda, 0,5 al centro y 1.0 a la derecha."
|
||||
|
||||
# game/indepth_character.rpy:101
|
||||
translate spanish demo_character_7c75906c:
|
||||
|
||||
# e7 "The what_xalign property controls where all the text itself is placed within the textbox, while what_textalign controls where rows of text are placed relative to each other."
|
||||
e7 "La propiedad what_xalign controla dónde se coloca todo el texto dentro del cuadro de texto, mientras que what_textalign controla dónde se colocan las filas de texto relativas entre sí."
|
||||
|
||||
# game/indepth_character.rpy:103
|
||||
translate spanish demo_character_e2811c1c:
|
||||
|
||||
# e7 "Generally you'll want to to set them both what_xalign and what_textalign to the same value."
|
||||
e7 "Por lo general, querrás establecerlos what_xalign y what_textalign en el mismo valor."
|
||||
|
||||
# game/indepth_character.rpy:105
|
||||
translate spanish demo_character_baa52234:
|
||||
|
||||
# e7 "Setting what_layout to 'subtitle' puts Ren'Py in subtitle mode, which tries to even out the length of every line of text in a block."
|
||||
e7 "Al establecer what_layout en 'subtitle' pones a Ren'Py en el modo de subtítulos, que intenta igualar la longitud de cada línea de texto en un bloque."
|
||||
|
||||
# game/indepth_character.rpy:110
|
||||
translate spanish demo_character_41190f01:
|
||||
|
||||
# e8 "These properties can be combined to achieve many different effects."
|
||||
e8 "Estas propiedades se pueden combinar para lograr muchos efectos diferentes."
|
||||
|
||||
# game/indepth_character.rpy:124
|
||||
translate spanish demo_character_aa12d9ca:
|
||||
|
||||
# e8 "This example hides the background and shows dialogue centered and outlined, as if the game is being subtitled."
|
||||
e8 "Este ejemplo oculta el fondo y muestra el diálogo centrado y delineado, como si el juego estuviera subtitulado."
|
||||
|
||||
# game/indepth_character.rpy:133
|
||||
translate spanish demo_character_a7f243e5:
|
||||
|
||||
# e9 "There are two interesting non-style properties, what_prefix and what_suffix. These can put text at the start and end of a line of dialogue."
|
||||
e9 "Hay dos propiedades interesantes que no son de estilo, what_prefix y what_suffix. Estos pueden poner texto al principio y al final de una línea de diálogo."
|
||||
|
||||
# game/indepth_character.rpy:139
|
||||
translate spanish demo_character_f9b0052f:
|
||||
|
||||
# e "By using kind, you can copy properties from one character to another, changing only what you need to."
|
||||
e "Al usar kind, puede copiar las propiedades de un personaje a otro, cambiando solo lo que necesitas."
|
||||
|
||||
# game/indepth_character.rpy:148
|
||||
translate spanish demo_character_6dfce4b7:
|
||||
|
||||
# l8 "Like this! Finally I get some more dialogue around here."
|
||||
l8 "¡Me gusta esto! Finalmente consigo un poco más de diálogo por aquí."
|
||||
|
||||
# game/indepth_character.rpy:157
|
||||
translate spanish demo_character_68d9e46c:
|
||||
|
||||
# e "The last thing you have to know is that there's a special character, narrator, that speaks narration. Got it?"
|
||||
e "Lo último que debes saber es que hay un personaje especial, narrador, que habla narración. ¿Entendido?"
|
||||
|
||||
# game/indepth_character.rpy:159
|
||||
translate spanish demo_character_0c8f314a:
|
||||
|
||||
# "I think I do."
|
||||
"Eso creo."
|
||||
|
||||
# game/indepth_character.rpy:48
|
||||
translate spanish demo_character_31ace18e:
|
||||
|
||||
# e1 "The window_background property sets the image that's used for the background of the textbox, which should be the same size as the default in gui/textbox.png."
|
||||
e1 "La propiedad window_background establece la imagen que se utiliza para el fondo del cuadro de texto, que debe tener el mismo tamaño que el predeterminado en gui/textbox.png."
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user