Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 10b4064a53 | |||
| f1ea5289aa | |||
| a26ed9c328 |
@@ -1,176 +0,0 @@
|
||||
# macOS-related files
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
|
||||
# Windows-related files
|
||||
Thumbs.db
|
||||
Thumbs.db:encryptable
|
||||
ehthumbs.db
|
||||
ehthumbs_vista.db
|
||||
[Dd]esktop.ini
|
||||
|
||||
# Python related files.
|
||||
*.pyc
|
||||
*.pyo
|
||||
*.egg-info
|
||||
|
||||
# Editor/tool files.
|
||||
*~
|
||||
*.bak
|
||||
.gdb_history
|
||||
|
||||
# Compiled Ren'Py files.
|
||||
*.rpyc
|
||||
*.rpyb
|
||||
*.rpymc
|
||||
|
||||
# Ren'Py log files.
|
||||
log.txt
|
||||
errors.txt
|
||||
traceback.txt
|
||||
lint.txt
|
||||
|
||||
# Created directories.
|
||||
saves/
|
||||
tmp/
|
||||
cache/
|
||||
|
||||
# Executables.
|
||||
/renpy.exe
|
||||
/renpy2.exe
|
||||
/renpy3.exe
|
||||
/renpy-32.exe
|
||||
|
||||
/renpy.sh
|
||||
/renpy2.sh
|
||||
/renpy3.sh
|
||||
|
||||
/renpy.app
|
||||
/renpy2.app
|
||||
/renpy3.app
|
||||
|
||||
/7z.sfx
|
||||
|
||||
# Libraries.
|
||||
/lib
|
||||
|
||||
# Platforms.
|
||||
/rapt
|
||||
/rapt2
|
||||
/rapt3
|
||||
|
||||
/renios
|
||||
/renios2
|
||||
/renios3
|
||||
|
||||
/web
|
||||
/web2
|
||||
/web3
|
||||
|
||||
# Docs.
|
||||
/sphinx/source/inc
|
||||
/sphinx/source/thequestion.rst
|
||||
/doc
|
||||
/doc-web
|
||||
/LICENSE.txt
|
||||
|
||||
# Gui template.
|
||||
/gui/game/gui
|
||||
|
||||
# Editors.
|
||||
/jedit
|
||||
/atom
|
||||
/vscode
|
||||
|
||||
# Generated source.
|
||||
/renpy/vc_version.py
|
||||
/module/gen/
|
||||
/module/gen3/
|
||||
/module/gen-static/
|
||||
/module/gen3-static/
|
||||
/tutorial/game/tutorial_director.rpy
|
||||
|
||||
# Module build.
|
||||
/module/build
|
||||
/module/dist
|
||||
/module/emscripten-static
|
||||
|
||||
# Download target.
|
||||
/dl
|
||||
|
||||
# Pygame_sdl2.
|
||||
/pygame_sdl2
|
||||
|
||||
# Updater.
|
||||
update.pem
|
||||
|
||||
# Steam.
|
||||
steam_appid.txt
|
||||
/steamapi.py
|
||||
|
||||
# Android.
|
||||
*.keystore
|
||||
|
||||
# Type analysis.
|
||||
/typings
|
||||
|
||||
# Live2D
|
||||
cubism
|
||||
CubismSdkForNative-4-*.zip
|
||||
|
||||
# Created files.
|
||||
/screenshot*
|
||||
.android.json
|
||||
|
||||
# Old files.
|
||||
/0old
|
||||
|
||||
# Works in progress, throwaway scripts, etc.
|
||||
/scratch
|
||||
/*-dists
|
||||
/c
|
||||
/cg
|
||||
/.vscode/launch.json
|
||||
/.vscode/tasks.json
|
||||
|
||||
|
||||
# Pyi Files (generated by scripts/generate_pyi.py)
|
||||
renpy/audio/filter.pyi
|
||||
renpy/audio/renpysound.pyi
|
||||
renpy/display/accelerator.pyi
|
||||
renpy/display/matrix.pyi
|
||||
renpy/display/quaternion.pyi
|
||||
renpy/display/render.pyi
|
||||
renpy/encryption.pyi
|
||||
renpy/gl2/gl2draw.pyi
|
||||
renpy/gl2/gl2mesh.pyi
|
||||
renpy/gl2/gl2mesh2.pyi
|
||||
renpy/gl2/gl2mesh3.pyi
|
||||
renpy/gl2/gl2model.pyi
|
||||
renpy/gl2/gl2polygon.pyi
|
||||
renpy/gl2/gl2shader.pyi
|
||||
renpy/gl2/gl2texture.pyi
|
||||
renpy/gl2/live2dmodel.pyi
|
||||
renpy/lexersupport.pyi
|
||||
renpy/pydict.pyi
|
||||
renpy/style.pyi
|
||||
renpy/style.pyi
|
||||
renpy/styledata/style_activate_functions.pyi
|
||||
renpy/styledata/style_functions.pyi
|
||||
renpy/styledata/style_hover_functions.pyi
|
||||
renpy/styledata/style_idle_functions.pyi
|
||||
renpy/styledata/style_insensitive_functions.pyi
|
||||
renpy/styledata/style_selected_activate_functions.pyi
|
||||
renpy/styledata/style_selected_functions.pyi
|
||||
renpy/styledata/style_selected_hover_functions.pyi
|
||||
renpy/styledata/style_selected_idle_functions.pyi
|
||||
renpy/styledata/style_selected_insensitive_functions.pyi
|
||||
renpy/styledata/styleclass.pyi
|
||||
renpy/styledata/stylesets.pyi
|
||||
renpy/text/ftfont.pyi
|
||||
renpy/text/hbfont.pyi
|
||||
renpy/text/textsupport.pyi
|
||||
renpy/text/texwrap.pyi
|
||||
renpy/uguu/gl.pyi
|
||||
renpy/uguu/uguu.pyi
|
||||
@@ -1,284 +0,0 @@
|
||||
{
|
||||
"files.watcherExclude": {
|
||||
"**/renpy/0old/**": true,
|
||||
"**/renpy/atom/**": true,
|
||||
"**/renpy/cubism/**": true,
|
||||
"**/renpy/dists/**": true,
|
||||
"**/renpy/dl/**": true,
|
||||
"**/renpy/doc/**": true,
|
||||
"**/renpy/doc-web/**": true,
|
||||
"**/renpy/jedit/**": true,
|
||||
"**/renpy/renpy*.app/**": true,
|
||||
"**/renpy/tmp/**": true,
|
||||
"**/renpy/web/**": true
|
||||
},
|
||||
"files.exclude": {
|
||||
"0old": true,
|
||||
"**-dists": true,
|
||||
"**/*.pyc": true,
|
||||
"**/*.pyo": true,
|
||||
"**/*.rpmc": true,
|
||||
"**/*.rpyc": true,
|
||||
"**/__pycache__": true,
|
||||
"**/screenshot-*": true,
|
||||
"7z.sfx": true,
|
||||
"atom": true,
|
||||
"cubism": true,
|
||||
"dl": true,
|
||||
"doc": true,
|
||||
"doc-web": true,
|
||||
"jedit": true,
|
||||
"lib*": true,
|
||||
"live2d": true,
|
||||
"rapt*": true,
|
||||
"renios*": true,
|
||||
"renpy*.app": true,
|
||||
"renpy*.exe": true,
|
||||
"tmp": true,
|
||||
"vscode": true,
|
||||
"web": true,
|
||||
"web2": true,
|
||||
"web3": true,
|
||||
"**/*.rpa": true,
|
||||
"**/*.rpymc": true,
|
||||
"**/cache/": true,
|
||||
"renpy/audio/filter.pyi": true,
|
||||
"renpy/audio/renpysound.pyi": true,
|
||||
"renpy/display/accelerator.pyi": true,
|
||||
"renpy/display/matrix.pyi": true,
|
||||
"renpy/display/quaternion.pyi": true,
|
||||
"renpy/display/render.pyi": true,
|
||||
"renpy/encryption.pyi": true,
|
||||
"renpy/gl2/gl2draw.pyi": true,
|
||||
"renpy/gl2/gl2mesh.pyi": true,
|
||||
"renpy/gl2/gl2mesh2.pyi": true,
|
||||
"renpy/gl2/gl2mesh3.pyi": true,
|
||||
"renpy/gl2/gl2model.pyi": true,
|
||||
"renpy/gl2/gl2polygon.pyi": true,
|
||||
"renpy/gl2/gl2shader.pyi": true,
|
||||
"renpy/gl2/gl2texture.pyi": true,
|
||||
"renpy/gl2/live2dmodel.pyi": true,
|
||||
"renpy/lexersupport.pyi": true,
|
||||
"renpy/pydict.pyi": true,
|
||||
"renpy/style.pyi": true,
|
||||
"renpy/styledata/style_activate_functions.pyi": true,
|
||||
"renpy/styledata/style_functions.pyi": true,
|
||||
"renpy/styledata/style_hover_functions.pyi": true,
|
||||
"renpy/styledata/style_idle_functions.pyi": true,
|
||||
"renpy/styledata/style_insensitive_functions.pyi": true,
|
||||
"renpy/styledata/style_selected_activate_functions.pyi": true,
|
||||
"renpy/styledata/style_selected_functions.pyi": true,
|
||||
"renpy/styledata/style_selected_hover_functions.pyi": true,
|
||||
"renpy/styledata/style_selected_idle_functions.pyi": true,
|
||||
"renpy/styledata/style_selected_insensitive_functions.pyi": true,
|
||||
"renpy/styledata/styleclass.pyi": true,
|
||||
"renpy/styledata/stylesets.pyi": true,
|
||||
"renpy/text/ftfont.pyi": true,
|
||||
"renpy/text/hbfont.pyi": true,
|
||||
"renpy/text/textsupport.pyi": true,
|
||||
"renpy/text/texwrap.pyi": true,
|
||||
"renpy/uguu/gl.pyi": true,
|
||||
"renpy/uguu/uguu.pyi": true
|
||||
},
|
||||
"python.languageServer": "Pylance",
|
||||
"python.analysis.typeCheckingMode": "basic",
|
||||
"python.analysis.diagnosticMode": "workspace",
|
||||
"python.linting.pylintEnabled": false,
|
||||
"python.analysis.diagnosticSeverityOverrides": {
|
||||
"reportMissingModuleSource": "none"
|
||||
},
|
||||
"esbonio.server.enabled": true,
|
||||
"esbonio.sphinx.confDir": "",
|
||||
"renpy.excludeCompiledFilesFromWorkspace": true,
|
||||
"renpy.watchFoldersForChanges": false,
|
||||
"renpy.warnOnInvalidVariableNames": "Disabled",
|
||||
"renpy.warnOnInvalidFilenameIssues": "Disabled",
|
||||
"renpy.warnOnIndentationAndSpacingIssues": "Disabled",
|
||||
"editor.tokenColorCustomizations": {
|
||||
"[*Light*]": {
|
||||
"textMateRules": [
|
||||
{
|
||||
"scope": "ref.matchtext",
|
||||
"settings": {
|
||||
"foreground": "#000"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"[*Dark*]": {
|
||||
"textMateRules": [
|
||||
{
|
||||
"scope": "ref.matchtext",
|
||||
"settings": {
|
||||
"foreground": "#fff"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"textMateRules": [
|
||||
{
|
||||
"scope": "renpy.meta.plain",
|
||||
"settings": {
|
||||
"fontStyle": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "renpy.meta.i",
|
||||
"settings": {
|
||||
"fontStyle": "italic"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "renpy.meta.b",
|
||||
"settings": {
|
||||
"fontStyle": "bold"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"renpy.meta.u",
|
||||
"renpy.meta.a"
|
||||
],
|
||||
"settings": {
|
||||
"fontStyle": "underline"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "renpy.meta.s",
|
||||
"settings": {
|
||||
"fontStyle": "strikethrough"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "renpy.meta.i renpy.meta.b",
|
||||
"settings": {
|
||||
"fontStyle": "italic bold"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "renpy.meta.i renpy.meta.u",
|
||||
"settings": {
|
||||
"fontStyle": "italic underline"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "renpy.meta.i renpy.meta.s",
|
||||
"settings": {
|
||||
"fontStyle": "italic strikethrough"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "renpy.meta.b renpy.meta.u",
|
||||
"settings": {
|
||||
"fontStyle": "bold underline"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "renpy.meta.b renpy.meta.s",
|
||||
"settings": {
|
||||
"fontStyle": "bold strikethrough"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "renpy.meta.u renpy.meta.s",
|
||||
"settings": {
|
||||
"fontStyle": "underline strikethrough"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "renpy.meta.i renpy.meta.b renpy.meta.u",
|
||||
"settings": {
|
||||
"fontStyle": "italic bold underline"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "renpy.meta.i renpy.meta.b renpy.meta.s",
|
||||
"settings": {
|
||||
"fontStyle": "italic bold strikethrough"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "renpy.meta.i renpy.meta.u renpy.meta.s",
|
||||
"settings": {
|
||||
"fontStyle": "italic underline strikethrough"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "renpy.meta.b renpy.meta.u renpy.meta.s",
|
||||
"settings": {
|
||||
"fontStyle": "bold underline strikethrough"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "renpy.meta.i renpy.meta.b renpy.meta.u renpy.meta.s",
|
||||
"settings": {
|
||||
"fontStyle": "italic bold underline strikethrough"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "renpy.meta.color.text",
|
||||
"settings": {
|
||||
"foreground": "#ffffff"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "renpy.meta.color.#f00",
|
||||
"settings": {
|
||||
"foreground": "#f00"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "text.notes.info",
|
||||
"settings": {
|
||||
"foreground": "#17a2b8",
|
||||
"fontStyle": "bold"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "text.notes.success",
|
||||
"settings": {
|
||||
"foreground": "#28a745",
|
||||
"fontStyle": "bold"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "text.notes.warning",
|
||||
"settings": {
|
||||
"foreground": "#ffc107",
|
||||
"fontStyle": "bold"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "text.notes.danger",
|
||||
"settings": {
|
||||
"foreground": "#dc3545",
|
||||
"fontStyle": "bold"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "renpy.meta.color.#fff",
|
||||
"settings": {
|
||||
"foreground": "#fff"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "renpy.meta.color.#fcc",
|
||||
"settings": {
|
||||
"foreground": "#fcc"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "renpy.meta.color.#cfc",
|
||||
"settings": {
|
||||
"foreground": "#cfc"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "renpy.meta.color.#c00",
|
||||
"settings": {
|
||||
"foreground": "#c00"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
||||
// for the documentation about the tasks.json format
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "run.sh --build",
|
||||
"type": "shell",
|
||||
"command": "/home/tom/ab/renpy/run.sh --build",
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,203 +0,0 @@
|
||||
==============================
|
||||
The Ren'Py Visual Novel Engine
|
||||
==============================
|
||||
|
||||
https://www.renpy.org
|
||||
|
||||
|
||||
Branches
|
||||
========
|
||||
|
||||
The following branches are the most interesting.
|
||||
|
||||
``fix``
|
||||
The fix branch is used for fixes to the current version of Ren'Py that do
|
||||
not require dangerous changes. The fix branch is also the source of the
|
||||
documentation on https://www.renpy.org/. This branch is automatically
|
||||
merged into master on a regular basis.
|
||||
|
||||
Pull requests that contain fixes or documentation improvements should be
|
||||
made to the fix branch. When a release is made, the master branch is
|
||||
copied to the fix branch.
|
||||
|
||||
``master``
|
||||
The master branch is where the main focus of development is. This branch
|
||||
will eventually become the next release of Ren'Py.
|
||||
|
||||
Pull requests that contain new features, that require incompatible changes,
|
||||
or major changes to Ren'Py's internals should be targeted at the master
|
||||
branch.
|
||||
|
||||
|
||||
Getting Started
|
||||
===============
|
||||
|
||||
Ren'Py depends on a number of Python modules written in Cython and C. For
|
||||
changes to Ren'Py that only involve Python modules, you can use the modules
|
||||
found in the latest nightly build. Otherwise, you'll have to compile the
|
||||
modules yourself.
|
||||
|
||||
The development scripts assume a POSIX-like platform. The scripts should run
|
||||
on Linux or macOS, and can be made to run on Windows using an environment
|
||||
like MSYS.
|
||||
|
||||
Nightly Build
|
||||
-------------
|
||||
|
||||
Nightly builds can be downloaded from:
|
||||
|
||||
https://nightly.renpy.org
|
||||
|
||||
Note that the latest nightly build is at the bottom of the list. Once you've
|
||||
unpacked the nightly, change into this repository, and run::
|
||||
|
||||
./after_checkout.sh <path-to-nightly>
|
||||
|
||||
Once this script completes, you should be able to run Ren'Py using renpy.sh,
|
||||
renpy.app, or renpy.exe, as appropriate for your platform.
|
||||
|
||||
If the current nightly build doesn't work, please wait 24 hours for a new
|
||||
build to occur. If that build still doesn't work, contact Tom (`pytom at bishoujo.us`,
|
||||
or @renpytom on Twitter/X) to find out what's wrong.
|
||||
|
||||
The ``doc`` symlink will dangle until documentation is built, as described
|
||||
below.
|
||||
|
||||
Compiling the Modules
|
||||
----------------------
|
||||
|
||||
Building the modules requires you have the many dependencies installed on
|
||||
your system. On Ubuntu and Debian, these dependencies can be installed with
|
||||
the command::
|
||||
|
||||
sudo apt install virtualenvwrapper python3-dev libassimp-dev libavcodec-dev libavformat-dev \
|
||||
libswresample-dev libswscale-dev libharfbuzz-dev libfreetype6-dev libfribidi-dev libsdl2-dev \
|
||||
libsdl2-image-dev libsdl2-gfx-dev libsdl2-mixer-dev libsdl2-ttf-dev libjpeg-dev pkg-config
|
||||
|
||||
Ren'Py requires SDL_image 2.6 or greater. If your distribution doesn't include
|
||||
that version, you'll need to download it from:
|
||||
|
||||
https://github.com/libsdl-org/SDL_image/tree/SDL2
|
||||
|
||||
We strongly suggest installing the Ren'Py modules into a Python
|
||||
virtualenv. To create a new virtualenv, open a new terminal and run::
|
||||
|
||||
. /usr/share/virtualenvwrapper/virtualenvwrapper.sh
|
||||
mkvirtualenv renpy
|
||||
|
||||
To return to this virtualenv later, run::
|
||||
|
||||
. /usr/share/virtualenvwrapper/virtualenvwrapper.sh
|
||||
workon renpy
|
||||
|
||||
After activating the virtualenv, install additional dependencies::
|
||||
|
||||
pip install -U setuptools cython future six typing pefile requests ecdsa
|
||||
|
||||
Then, install pygame_sdl2 by running the following commands::
|
||||
|
||||
git clone https://www.github.com/renpy/pygame_sdl2
|
||||
pushd pygame_sdl2
|
||||
python setup.py install
|
||||
popd
|
||||
|
||||
Finally, use setup.py to compile extension modules that support Ren'Py::
|
||||
|
||||
python setup.py install
|
||||
|
||||
Ren'Py will be installed into the activated virtualenv. It can then be run
|
||||
using the command::
|
||||
|
||||
python renpy.py
|
||||
|
||||
|
||||
Other Platforms
|
||||
---------------
|
||||
|
||||
Where supported, Ren'Py will attempt to find include directories and library paths
|
||||
using pkg-config. If pkg-config is not present, include and library paths can be
|
||||
specified using CFLAGS and LDFLAGS.
|
||||
|
||||
If RENPY_CFLAGS is present in the environment and CFLAGS is not, setup.py
|
||||
will set CFLAGS to RENPY_CFLAGS. The same is true for RENPY_LDFLAGS,
|
||||
RENPY_CC, RENPY_CXX, and RENPY_LD.
|
||||
|
||||
Setup.py does not support cross-compiling. See https://github.com/renpy/renpy-build
|
||||
for software that cross-compiles Ren'Py for many platforms.
|
||||
|
||||
|
||||
Documentation
|
||||
=============
|
||||
|
||||
Building
|
||||
--------
|
||||
|
||||
Building the documentation requires Ren'Py to work. You'll either need to
|
||||
link in a nightly build, or compile the modules as described above. You'll
|
||||
also need the `Sphinx <https://www.sphinx-doc.org>`_ documentation generator.
|
||||
If you have pip working, install Sphinx using::
|
||||
|
||||
pip install -U sphinx sphinx_rtd_theme sphinx_rtd_dark_mode
|
||||
|
||||
Once Sphinx is installed, change into the ``sphinx`` directory inside the
|
||||
Ren'Py checkout and run::
|
||||
|
||||
./build.sh
|
||||
|
||||
Format
|
||||
------
|
||||
|
||||
Ren'Py's documentation consists of reStructuredText files found in sphinx/source, and
|
||||
generated documentation found in function docstrings scattered throughout the code. Do
|
||||
not edit the files in sphinx/source/inc directly, as they will be overwritten.
|
||||
|
||||
Docstrings may include tags on the first few lines:
|
||||
|
||||
\:doc: `section` `kind`
|
||||
Indicates that this function should be documented. `section` gives
|
||||
the name of the include file the function will be documented in, while
|
||||
`kind` indicates the kind of object to be documented (one of ``function``,
|
||||
``method`` or ``class``. If omitted, `kind` will be auto-detected.
|
||||
\:name: `name`
|
||||
The name of the function to be documented. Function names are usually
|
||||
detected, so this is only necessary when a function has multiple aliases.
|
||||
\:args: `args`
|
||||
This overrides the detected argument list. It can be used if some arguments
|
||||
to the function are deprecated.
|
||||
|
||||
For example::
|
||||
|
||||
def warp_speed(factor, transwarp=False):
|
||||
"""
|
||||
:doc: warp
|
||||
:name: renpy.warp_speed
|
||||
:args: (factor)
|
||||
|
||||
Exceeds the speed of light.
|
||||
"""
|
||||
|
||||
renpy.engine.warp_drive.engage(factor)
|
||||
|
||||
|
||||
Translating
|
||||
===========
|
||||
|
||||
For best practices when it comes to translating the launcher and template
|
||||
game, please read:
|
||||
|
||||
https://lemmasoft.renai.us/forums/viewtopic.php?p=321603#p321603
|
||||
|
||||
|
||||
Contributing
|
||||
============
|
||||
|
||||
For bug fixes, documentation improvements, and simple changes, just
|
||||
make a pull request. For more complex changes, it might make sense
|
||||
to file an issue first so we can discuss the design.
|
||||
|
||||
License
|
||||
=======
|
||||
|
||||
For the complete licensing terms, please read:
|
||||
|
||||
https://www.renpy.org/doc/html/license.html
|
||||
@@ -1,64 +0,0 @@
|
||||
This file contains the names, contact information, and notes about each
|
||||
language Ren'Py is translated in. When you're adding a translation or
|
||||
taking responsibility for a translation, please send a pull request updating
|
||||
this file.
|
||||
|
||||
|
||||
Pig Latin
|
||||
---------
|
||||
|
||||
This is an automatically-generated translation that lets me test various
|
||||
translation mechanisms.
|
||||
|
||||
* Tom Rothamel <pytom@bishoujo.us>
|
||||
|
||||
Russian
|
||||
---------
|
||||
|
||||
Translation to the Russian language. Most of translation provided by Project
|
||||
Gardares, especially Ria-kon.
|
||||
|
||||
* Project Gardares <guardares@gmail.com>
|
||||
|
||||
Malay
|
||||
---------
|
||||
|
||||
Translation to the Malay language. This will be translated to both writing
|
||||
systems used in Malay, namely the Latin-based Rumi script system and the
|
||||
Arabic-based Jawi script system, to provide compatibility in some areas where
|
||||
there is still exist Malay people who still cannot read their language in the
|
||||
newer Rumi script system, or refused to use it. The translation is being made
|
||||
as general and native as possible to cover the entire region that uses Malay
|
||||
language, this means native words take precedence over loanwords if and only if
|
||||
both native and loanwords exist and both are suitable in the context of use.
|
||||
|
||||
* Muhammad Nur Hidayat (MNH48) <translation@mnh48.moe>
|
||||
|
||||
Turkish
|
||||
---------
|
||||
|
||||
Translation to the Turkish language. Some terms and abbreviations (especially
|
||||
those related to coding) are deliberately left untranslated (or the original words
|
||||
are provided in brackets) to avoid confusion, relying on their common use in
|
||||
Turkish language and among Turkish developers.
|
||||
|
||||
* Arda Güler (WKFO) <ardaguler09@gmail.com>
|
||||
|
||||
Finnish
|
||||
---------
|
||||
|
||||
Translation to the Finnish language. Most terms have been translated,
|
||||
sometimes requiring some imagination to come up with suitable terms.
|
||||
The quality is not perfect, but it should make sense to any developer
|
||||
fluent in Finnish but not English. Adjustments will be made if better
|
||||
translations are suggested.
|
||||
|
||||
* Lari Liuhamo (Diapolo10) <lari.liuhamo+github116504@gmail.com>
|
||||
|
||||
Ukrainian
|
||||
---------
|
||||
|
||||
Translation to Ukrainian language. Most of translations provided by Veydzher.
|
||||
Feel free to report me any mistakes, fixes or improvements.
|
||||
|
||||
* Veydzher <veydzherdgswift008@gmail.com>
|
||||
@@ -1,161 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
from __future__ import print_function
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
from renpy import version_tuple # @UnresolvedImport
|
||||
|
||||
branch = os.popen("git branch --show-current").read().strip()
|
||||
|
||||
SOURCE = [
|
||||
"/home/tom/ab/renpy",
|
||||
"/home/tom/ab/renpy-build-" + branch,
|
||||
"/home/tom/ab/pygame_sdl2",
|
||||
]
|
||||
|
||||
|
||||
from renpy.versions import generate_vc_version
|
||||
|
||||
version = generate_vc_version()["version"]
|
||||
short_version = version.rpartition(".")[0]
|
||||
major = version.partition(".")[0]
|
||||
print("Version", version)
|
||||
|
||||
if major == '7':
|
||||
SOURCE.append("/home/tom/ab/renpy-build-fix/renpyweb")
|
||||
|
||||
ap = argparse.ArgumentParser()
|
||||
|
||||
ap.add_argument("--release", action="store_true")
|
||||
ap.add_argument("--prerelease", action="store_true")
|
||||
ap.add_argument("--experimental", action="store_true")
|
||||
ap.add_argument("--no-tag", "-n", action="store_true")
|
||||
ap.add_argument("--push-tags", action="store_true")
|
||||
ap.add_argument("--delete-tag")
|
||||
ap.add_argument("--github", action="store_true")
|
||||
ap.add_argument("--real", action="store_true")
|
||||
|
||||
args = ap.parse_args()
|
||||
|
||||
if not args.real:
|
||||
print("Did you want to use scripts/add_all.sh? If not, give --real.")
|
||||
sys.exit(1)
|
||||
|
||||
if args.github:
|
||||
subprocess.call([ "git", "push", "--tags" ])
|
||||
subprocess.call([ "gh", "release", "create", version, "--notes", "See https://www.renpy.org/release/" + short_version, "-t", "Ren'Py {}".format(short_version) ])
|
||||
|
||||
dn = "/home/tom/ab/renpy/dl/" + short_version
|
||||
|
||||
for fn in os.listdir(dn):
|
||||
|
||||
if fn == ".build_cache":
|
||||
continue
|
||||
|
||||
if fn.endswith(".update.gz"):
|
||||
continue
|
||||
|
||||
if fn.endswith(".update.json"):
|
||||
continue
|
||||
|
||||
if fn.startswith("updates.json"):
|
||||
continue
|
||||
|
||||
if fn.endswith(".zsync"):
|
||||
continue
|
||||
|
||||
if fn.endswith(".sums"):
|
||||
continue
|
||||
|
||||
subprocess.call([ "gh", "release", "upload", version, os.path.join(dn, fn) ])
|
||||
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
|
||||
if args.release:
|
||||
subprocess.check_call([ "/home/tom/ab/renpy/scripts/checksums.py", "/home/tom/ab/renpy/dl/" + short_version ])
|
||||
|
||||
if args.delete_tag:
|
||||
for i in SOURCE:
|
||||
|
||||
os.chdir(i)
|
||||
|
||||
if i == SOURCE[0]:
|
||||
tag = args.delete_tag
|
||||
else:
|
||||
tag = "renpy-" + args.delete_tag
|
||||
|
||||
subprocess.call([ "git", "tag", "-d", tag, ])
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
if args.push_tags:
|
||||
for i in SOURCE:
|
||||
os.chdir(i)
|
||||
|
||||
if subprocess.call([ "git", "push", "--tags" ]):
|
||||
print("Tags not pushed: {}".format(os.getcwd()))
|
||||
sys.exit(1)
|
||||
|
||||
print("Pushed tags.")
|
||||
sys.exit(0)
|
||||
|
||||
if args.release:
|
||||
links = [ "release", "prerelease", "experimental" ]
|
||||
tag = True
|
||||
elif args.prerelease:
|
||||
links = [ "prerelease", "experimental" ]
|
||||
tag = True
|
||||
elif args.experimental:
|
||||
links = [ "experimental" ]
|
||||
tag = False
|
||||
else:
|
||||
links = [ ]
|
||||
tag = False
|
||||
|
||||
if args.no_tag:
|
||||
tag = False
|
||||
|
||||
links = [ i + "-" + major for i in links ]
|
||||
|
||||
if tag:
|
||||
for i in SOURCE:
|
||||
os.chdir(i)
|
||||
|
||||
if subprocess.call([ "git", "diff", "--quiet", "HEAD" ]):
|
||||
print("Directory not checked in: {}".format(os.getcwd()))
|
||||
sys.exit(1)
|
||||
|
||||
for i in SOURCE:
|
||||
|
||||
os.chdir(i)
|
||||
|
||||
if i == SOURCE[0]:
|
||||
tag = version
|
||||
else:
|
||||
tag = "renpy-" + version
|
||||
|
||||
subprocess.check_call([ "git", "tag", "-a", tag, "-m", "Tagging Ren'Py + " + version + " release." ])
|
||||
|
||||
os.chdir("/home/tom/ab/renpy/dl")
|
||||
|
||||
for i in links:
|
||||
if os.path.exists(i):
|
||||
os.unlink(i)
|
||||
os.symlink(short_version, i)
|
||||
|
||||
os.chdir("/home/tom/ab/website")
|
||||
subprocess.check_call("./upload.sh")
|
||||
|
||||
os.chdir("/home/tom/ab/renpy/sphinx")
|
||||
if args.release:
|
||||
subprocess.check_call("./upload.sh")
|
||||
elif args.prerelease:
|
||||
subprocess.check_call("./upload_dev.sh")
|
||||
|
||||
print("Version", version)
|
||||
@@ -1,16 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
ROOT="$(dirname $(python -c "import os;print(os.path.realpath('$0'))"))"
|
||||
|
||||
ln -s "$ROOT/help.html" "$ROOT/tutorial/README.html"
|
||||
ln -s "$ROOT/help.html" "$ROOT/the_question/README.html"
|
||||
ln -s "$ROOT/help.html" "$ROOT/templates/english/README.html"
|
||||
|
||||
ln -s "$ROOT/sphinx/source/license.rst" "$ROOT/LICENSE.txt"
|
||||
|
||||
if [ "$1" != "" ]; then
|
||||
ln -s "$1/lib" "$ROOT/lib"
|
||||
ln -s "$1/renpy.app" "$ROOT"
|
||||
ln -s "$1/renpy.exe" "$ROOT"
|
||||
ln -s "$1/renpy.sh" "$ROOT"
|
||||
fi
|
||||
@@ -0,0 +1,9 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
from distutils.core import setup
|
||||
import py2exe
|
||||
|
||||
setup(name="RenPy",
|
||||
windows=[ "run_game.py" ],
|
||||
zipfile='lib/renpy.zip',
|
||||
)
|
||||
@@ -0,0 +1,44 @@
|
||||
# This file consists of renpy functions that aren't expected to be
|
||||
# touched by the user too much. We reserve the _ prefix for names
|
||||
# defined in the library.
|
||||
|
||||
# It's strongly reccomended that you don't edit this file, as future
|
||||
# releases of Ren'Py will probably change this file to include more
|
||||
# functionality.
|
||||
|
||||
# It's also strongly recommended that you leave this file in the
|
||||
# game directory, so its functionality is included in your game.
|
||||
|
||||
label _enable_overlay:
|
||||
|
||||
# Set up the default keymap.
|
||||
python hide:
|
||||
config.underlay = [ ]
|
||||
config.overlay = [ ]
|
||||
|
||||
# The default keymap.
|
||||
km = renpy.Keymap(
|
||||
K_PAGEUP = renpy.rollback,
|
||||
f = renpy.toggle_fullscreen,
|
||||
s = renpy.screenshot,
|
||||
)
|
||||
|
||||
config.underlay.append(km)
|
||||
|
||||
|
||||
# If the user gives an enable_overlay function, call that.
|
||||
if renpy.has_label('enable_overlay'):
|
||||
call enable_overlay
|
||||
|
||||
return
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# This is the true starting point of the program. Sssh... Don't
|
||||
# tell anyone.
|
||||
label _start:
|
||||
|
||||
call _enable_overlay
|
||||
jump start
|
||||
@@ -0,0 +1,205 @@
|
||||
init:
|
||||
# Set up the size of the screen.
|
||||
$ config.screen_width = 800
|
||||
$ config.screen_height = 600
|
||||
|
||||
# Positions of things on the screen.
|
||||
$ left = Position(xpos=0.0, xanchor='left')
|
||||
$ right = Position(xpos=1.0, xanchor='right')
|
||||
$ center = Position()
|
||||
|
||||
# Backgrounds.
|
||||
image whitehouse = Image("whitehouse.jpg")
|
||||
|
||||
# Character pictures.
|
||||
image eileen happy = Image("9a_happy.png")
|
||||
image eileen vhappy = Image("9a_vhappy.png")
|
||||
image eileen concerned = Image("9a_concerned.png")
|
||||
|
||||
# Character objects.
|
||||
$ e = Character('Eileen', color=(200, 255, 200, 255))
|
||||
|
||||
|
||||
# The actual game starts here.
|
||||
label start:
|
||||
|
||||
scene whitehouse
|
||||
show eileen vhappy
|
||||
|
||||
"Girl" "Welcome to Ren'Py 4 Preview Release 1."
|
||||
|
||||
show eileen happy
|
||||
|
||||
"Girl" "And welcome to American Bishoujo's southern base, just
|
||||
outside of Washington, D.C."
|
||||
|
||||
show eileen happy at left
|
||||
|
||||
"Girl" "This isn't the view from our home base, but it'll do for
|
||||
this demo."
|
||||
|
||||
show eileen happy
|
||||
|
||||
"Girl" "My name is Eileen, and while I plan to star in my own game
|
||||
one day, for now I'm helping to introduce you to Ren'Py."
|
||||
|
||||
e "Ren'Py is an engine that makes it easy to write visual novel
|
||||
games, by taking care of much of the hard work of writing a
|
||||
game."
|
||||
|
||||
e "For example, displaying a line of dialogue is a single
|
||||
statement. So is displaying a thought or narration."
|
||||
|
||||
"I understand."
|
||||
|
||||
e "Dialogue is easy to write. Just put a string on a line by
|
||||
itself, or to the right of an object name or label string."
|
||||
|
||||
e "Since dialogue makes up the bulk of these games, we thought it
|
||||
should be easy to write."
|
||||
|
||||
e "Ren'Py can also display menus that let you alter the flow of
|
||||
the story."
|
||||
|
||||
menu:
|
||||
"Why don't you try a menu out by picking a number?"
|
||||
|
||||
"1":
|
||||
show eileen concerned
|
||||
e "You picked one. I don't like odd numbers."
|
||||
|
||||
"2":
|
||||
show eileen vhappy
|
||||
e "You picked two. Even numbers are lucky!"
|
||||
|
||||
show eileen happy
|
||||
|
||||
e "There are a number of statements that control what's displayed
|
||||
on the screen. The image statement is used to introduce
|
||||
images with names."
|
||||
|
||||
scene
|
||||
|
||||
e "The scene statement can clear the screen..."
|
||||
|
||||
scene whitehouse
|
||||
|
||||
e "... or it can clear the screen and then show a background."
|
||||
|
||||
show eileen happy
|
||||
|
||||
e "The show statement is used to show pictures."
|
||||
|
||||
e "When the show statement is used on an image with the same first
|
||||
name (called a tag) as one already shown, it replaces that picture."
|
||||
|
||||
show eileen vhappy
|
||||
|
||||
e "This makes it easy for characters to change emotions."
|
||||
|
||||
hide eileen
|
||||
|
||||
e "The hide statement hides an image."
|
||||
|
||||
show eileen happy at center
|
||||
|
||||
e "A new feature in Ren'Py 4 is the at clause on images, which
|
||||
lets you say where you want to show the image at. I can go
|
||||
from the center of the screen..."
|
||||
|
||||
show eileen happy at left
|
||||
|
||||
e "... to the left of the screen ..."
|
||||
|
||||
show eileen happy at right
|
||||
|
||||
e "... to the right of the screen ..."
|
||||
|
||||
show eileen happy
|
||||
|
||||
e "... and back to the center."
|
||||
|
||||
e "Ren'Py supports a variety of control statements, such as jump,
|
||||
call, return, if, and while statements."
|
||||
|
||||
e "Rather than bore you with the details, i'll just tell you to
|
||||
check grab the Ren'Py tutorial from http://www.bishoujo.us/renpy/."
|
||||
|
||||
e "That's just about it for writing scripts. Let me show you some
|
||||
of the new engine features."
|
||||
|
||||
e "The first feature I can show off is the ability to go
|
||||
full-screen. Hit the 'f' key to try it out, and hit 'f'
|
||||
again to go back to a window."
|
||||
|
||||
show eileen vhappy
|
||||
|
||||
e "The next feature, rollback, is really neat."
|
||||
|
||||
show eileen happy
|
||||
|
||||
menu:
|
||||
"Would you like to see it?"
|
||||
|
||||
"Yes.":
|
||||
pass
|
||||
|
||||
"No.":
|
||||
jump after_rollback
|
||||
|
||||
e "Rollback lets you play the game backwards."
|
||||
|
||||
e "It lets you go back and reread a line of dialogue you missed,
|
||||
or even to go back to a menu and make a different choice if you
|
||||
made a mistake."
|
||||
|
||||
e "We do limit the number of steps someone can rollback."
|
||||
|
||||
e "Try it out now, by hitting page up until you get back to the
|
||||
menu, and then choose 'No' instead of 'Yes'."
|
||||
|
||||
show eileen concerned
|
||||
|
||||
e "Well, try it."
|
||||
|
||||
e "You want to hit page up."
|
||||
|
||||
e "Well, whatever, your loss. Moving on."
|
||||
|
||||
|
||||
label after_rollback:
|
||||
|
||||
show eileen happy
|
||||
|
||||
e "Another new feature works only on Windows. If a game crashes,
|
||||
a notepad is brought up showing the crash message, to help
|
||||
you debug what went wrong."
|
||||
|
||||
e "The biggest new feature, though, is reasonable
|
||||
documentation, which you can read at http://www.bishoujo.us/renpy/."
|
||||
|
||||
show eileen concerned
|
||||
|
||||
e "Since this is just a preview release, there are still a few
|
||||
things missing."
|
||||
|
||||
e "For example, there's no support for loading or saving."
|
||||
|
||||
e "We also left out music and animations."
|
||||
|
||||
show eileen happy
|
||||
|
||||
e "Don't worry, though. Those will be coming in the final release,
|
||||
which is due out in a few weeks."
|
||||
|
||||
e "You can begin making your own game by editing game/script.rpy. That's
|
||||
the script for the game you're playing now."
|
||||
|
||||
e "After you make a change, you have to re-run the game to see
|
||||
the change take effect."
|
||||
|
||||
show eileen vhappy
|
||||
|
||||
e "Good luck making your own games!"
|
||||
|
||||
return
|
||||
@@ -1,316 +0,0 @@
|
||||
#!/home/tom/ab/renpy/lib/py3-linux-x86_64/python
|
||||
|
||||
# Builds a distribution of Ren'Py.
|
||||
from __future__ import division, absolute_import, with_statement, print_function, unicode_literals
|
||||
|
||||
import future.standard_library
|
||||
import future.utils
|
||||
PY2 = future.utils.PY2
|
||||
|
||||
import sys
|
||||
import os
|
||||
import compileall
|
||||
import shutil
|
||||
import subprocess
|
||||
import argparse
|
||||
import time
|
||||
import collections
|
||||
|
||||
try:
|
||||
from importlib import reload
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
ROOT = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
|
||||
def zip_rapt_symbols(destination):
|
||||
"""
|
||||
Zips up the rapt symbols.
|
||||
"""
|
||||
|
||||
import zipfile
|
||||
|
||||
if PY2:
|
||||
zf = zipfile.ZipFile(destination + "/android-native-symbols.zip", "w", zipfile.ZIP_DEFLATED)
|
||||
else:
|
||||
zf = zipfile.ZipFile(destination + "/android-native-symbols.zip", "w", zipfile.ZIP_DEFLATED, compresslevel=3)
|
||||
|
||||
for dn, dirs, files in os.walk("rapt/symbols"):
|
||||
for fn in dirs + files:
|
||||
fn = os.path.join(dn, fn)
|
||||
arcname = os.path.relpath(fn, "rapt/symbols")
|
||||
zf.write(fn, arcname)
|
||||
|
||||
zf.close()
|
||||
|
||||
def copy_tutorial_file(src, dest):
|
||||
"""
|
||||
Copies a file from src to dst. Lines between "# tutorial-only" and
|
||||
"# end-tutorial-only" comments are omitted from the copy.
|
||||
"""
|
||||
|
||||
# True if we want to copy the line.
|
||||
copy = True
|
||||
|
||||
with open(src, "r") as sf, open(dest, "w") as df:
|
||||
for l in sf:
|
||||
if "# tutorial-only" in l:
|
||||
copy = False
|
||||
elif "# end-tutorial-only" in l:
|
||||
copy = True
|
||||
else:
|
||||
if copy:
|
||||
df.write(l)
|
||||
|
||||
def link_directory(dirname):
|
||||
dn = os.path.join(ROOT, dirname)
|
||||
|
||||
if os.path.exists(dn):
|
||||
os.unlink(dn)
|
||||
|
||||
if PY2:
|
||||
source = dn + "2"
|
||||
else:
|
||||
source = dn + "3"
|
||||
|
||||
if os.path.exists(source):
|
||||
os.symlink(source, dn)
|
||||
|
||||
def main():
|
||||
|
||||
start = time.time()
|
||||
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("version", nargs="?")
|
||||
ap.add_argument("--fast", action="store_true")
|
||||
ap.add_argument("--pygame", action="store", default=None)
|
||||
ap.add_argument("--no-rapt", action="store_true")
|
||||
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("--notarized", action="store_true", dest="notarized")
|
||||
ap.add_argument("--vc-version-only", action="store_true")
|
||||
ap.add_argument("--link-directories", action="store_true")
|
||||
ap.add_argument("--append-version", action="store_true")
|
||||
ap.add_argument("--nightly", action="store_true")
|
||||
ap.add_argument("--print-version", action="store_true")
|
||||
|
||||
args = ap.parse_args()
|
||||
|
||||
link_directory("rapt")
|
||||
link_directory("renios")
|
||||
link_directory("web")
|
||||
|
||||
import renpy.versions
|
||||
renpy.versions.generate_vc_version(nightly=args.nightly)
|
||||
|
||||
if args.link_directories or args.vc_version_only:
|
||||
return
|
||||
|
||||
if not os.path.abspath(sys.executable).startswith(ROOT + "/lib"):
|
||||
raise Exception("Distribute must be run with the python in lib/.")
|
||||
|
||||
if args.sign:
|
||||
os.environ["RENPY_MAC_IDENTITY"] = "Developer ID Application: Tom Rothamel (XHTE5H7Z79)"
|
||||
|
||||
if PY2 and not sys.flags.optimize:
|
||||
raise Exception("Not running with python optimization.")
|
||||
|
||||
try:
|
||||
vc_version_base = os.path.splitext(renpy.vc_version.__file__)[0]
|
||||
|
||||
# Delete the .pyc and .pyo files, as reload can choose one of them instead
|
||||
# of the new .py file.
|
||||
for fn in [ vc_version_base + ".pyc", vc_version_base + ".pyo" ]:
|
||||
if os.path.exists(fn):
|
||||
os.unlink(fn)
|
||||
|
||||
reload(renpy.vc_version)
|
||||
except Exception:
|
||||
import renpy.vc_version
|
||||
|
||||
# A normal reload is fine, as renpy/__init__.py won't change.
|
||||
reload(renpy)
|
||||
|
||||
if args.print_version:
|
||||
print(renpy.version_only)
|
||||
return
|
||||
|
||||
if args.version is None:
|
||||
if args.nightly:
|
||||
args.version = renpy.version_only
|
||||
else:
|
||||
args.version = ".".join(str(i) for i in renpy.version_tuple[:-1])
|
||||
|
||||
if args.append_version:
|
||||
args.version += "-" + renpy.version_only
|
||||
|
||||
# Check that the versions match.
|
||||
full_version = renpy.version_only # @UndefinedVariable
|
||||
|
||||
if "-" not in args.version \
|
||||
and not full_version.startswith(args.version):
|
||||
raise Exception("The command-line and Ren'Py versions do not match.")
|
||||
|
||||
os.environ['RENPY_BUILD_VERSION'] = args.version
|
||||
|
||||
# The destination directory.
|
||||
destination = os.path.join("dl", args.version)
|
||||
|
||||
if args.variant:
|
||||
destination += "-" + args.variant
|
||||
|
||||
if os.path.exists(os.path.join(destination, "checksums.txt")):
|
||||
raise Exception("The checksums.txt file exists.")
|
||||
|
||||
print("Version {} ({})".format(args.version, full_version))
|
||||
|
||||
if sys.version_info[0] >= 3:
|
||||
renpy_sh = "./renpy3.sh"
|
||||
else:
|
||||
renpy_sh = "./renpy2.sh"
|
||||
|
||||
# Compile all the python files.
|
||||
compileall.compile_dir("renpy/", ddir="renpy/", force=True, quiet=1)
|
||||
|
||||
# Compile the various games.
|
||||
if not args.fast:
|
||||
for i in [ 'tutorial', 'launcher', 'the_question' ]:
|
||||
print("Compiling", i)
|
||||
subprocess.check_call([renpy_sh, i, "compile" ])
|
||||
|
||||
# Kick off the rapt build.
|
||||
if not args.fast:
|
||||
|
||||
print("Cleaning RAPT.")
|
||||
|
||||
sys.path.insert(0, os.path.join(ROOT, "rapt", "buildlib"))
|
||||
|
||||
import rapt.interface # type: ignore
|
||||
import rapt.build # type: ignore
|
||||
|
||||
interface = rapt.interface.Interface()
|
||||
rapt.build.distclean(interface)
|
||||
|
||||
print("Compiling RAPT and renios.")
|
||||
|
||||
compileall.compile_dir("rapt/buildlib/", ddir="rapt/buildlib/", quiet=1)
|
||||
compileall.compile_dir("renios/buildlib/", ddir="renios/buildlib/", quiet=1)
|
||||
|
||||
if not os.path.exists(destination):
|
||||
os.makedirs(destination)
|
||||
|
||||
zip_rapt_symbols(destination)
|
||||
|
||||
if args.fast:
|
||||
|
||||
cmd = [
|
||||
renpy_sh,
|
||||
"launcher",
|
||||
"distribute",
|
||||
"launcher",
|
||||
"--package",
|
||||
"sdk",
|
||||
"--destination",
|
||||
destination,
|
||||
"--no-update",
|
||||
]
|
||||
|
||||
else:
|
||||
cmd = [
|
||||
renpy_sh,
|
||||
"launcher",
|
||||
"distribute",
|
||||
"launcher",
|
||||
"--destination",
|
||||
destination,
|
||||
]
|
||||
|
||||
if args.notarized:
|
||||
cmd.extend([
|
||||
"--macapp",
|
||||
"notarized/out",
|
||||
])
|
||||
|
||||
print()
|
||||
subprocess.check_call(cmd)
|
||||
|
||||
# Sign the update.
|
||||
if not args.fast:
|
||||
subprocess.check_call([
|
||||
"scripts/sign_update.py",
|
||||
"/home/tom/ab/keys/renpy_private.pem",
|
||||
os.path.join(destination, "updates.json"),
|
||||
])
|
||||
|
||||
# Package pygame_sdl2.
|
||||
if not args.fast:
|
||||
subprocess.check_call([
|
||||
"pygame_sdl2/setup.py",
|
||||
"-q",
|
||||
"egg_info",
|
||||
"--tag-build",
|
||||
"+renpy" + args.version.replace("+", "-"),
|
||||
"sdist",
|
||||
"-d",
|
||||
os.path.abspath(destination)
|
||||
])
|
||||
|
||||
# Write 7z.exe.
|
||||
sdk = "renpy-{}-sdk".format(args.version)
|
||||
|
||||
if not args.fast:
|
||||
|
||||
# shutil.copy("renpy-ppc.zip", os.path.join(destination, "renpy-ppc.zip"))
|
||||
|
||||
with open("7z.sfx", "rb") as f:
|
||||
sfx = f.read()
|
||||
|
||||
os.chdir(destination)
|
||||
|
||||
if os.path.exists(sdk):
|
||||
shutil.rmtree(sdk)
|
||||
|
||||
subprocess.check_call([ "unzip", "-q", sdk + ".zip" ])
|
||||
|
||||
if os.path.exists(sdk + ".7z"):
|
||||
os.unlink(sdk + ".7z")
|
||||
|
||||
sys.stdout.write("Creating -sdk.7z")
|
||||
|
||||
p = subprocess.Popen([ "7z", "a", sdk + ".7z", sdk], stdout=subprocess.PIPE)
|
||||
for i, _l in enumerate(p.stdout): # type: ignore
|
||||
if i % 10 != 0:
|
||||
continue
|
||||
|
||||
sys.stdout.write(".")
|
||||
sys.stdout.flush()
|
||||
|
||||
if p.wait() != 0:
|
||||
raise Exception("7z failed")
|
||||
|
||||
with open(sdk + ".7z", "rb") as f:
|
||||
data = f.read()
|
||||
|
||||
with open(sdk + ".7z.exe", "wb") as f:
|
||||
f.write(sfx)
|
||||
f.write(data)
|
||||
|
||||
os.unlink(sdk + ".7z")
|
||||
shutil.rmtree(sdk)
|
||||
|
||||
else:
|
||||
os.chdir(destination)
|
||||
|
||||
if os.path.exists(sdk + ".7z.exe"):
|
||||
os.unlink(sdk + ".7z.exe")
|
||||
|
||||
print()
|
||||
|
||||
print("Distribute took {:.0f} seconds.".format(time.time() - start))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,3 @@
|
||||
tutorial.html: tutorial.xml
|
||||
python highlight.py tutorial.xml > tutorial.hi.xml
|
||||
xsltproc stylesheet.xslt tutorial.hi.xml > tutorial.html
|
||||
@@ -0,0 +1,80 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
import sys
|
||||
import re
|
||||
|
||||
keywords = [
|
||||
r'\bimage\b',
|
||||
r'\bscene\b',
|
||||
r'\bshow\b',
|
||||
r'\bhide\b',
|
||||
r'\binit\b',
|
||||
r'\$',
|
||||
r'\blabel\b',
|
||||
r'\bmenu\b',
|
||||
r'\bset\b',
|
||||
r'\bif\b',
|
||||
r'\bwhile\b',
|
||||
r'\bjump\b',
|
||||
r'\blabel\b',
|
||||
r'\bcall\b',
|
||||
r'\breturn\b',
|
||||
r'\bfrom\b',
|
||||
r'\belif\b',
|
||||
r'\belse\b',
|
||||
r'\bpass\b',
|
||||
]
|
||||
|
||||
kwre = '|'.join(keywords)
|
||||
|
||||
def example(m):
|
||||
s = m.group(1)
|
||||
rv = ""
|
||||
pos = 0
|
||||
|
||||
while pos < len(s):
|
||||
|
||||
m = re.compile(r'(?s)"(([^"]|\\.)*)"').match(s, pos)
|
||||
if m:
|
||||
rv += '"<span class="string">%s</span>"' % m.group(1)
|
||||
pos = m.end()
|
||||
continue
|
||||
|
||||
m = re.compile(r"(?s)'(([^']|\\.)*)'").match(s, pos)
|
||||
if m:
|
||||
rv += '\'<span class="string">%s</span>\'' % m.group(1)
|
||||
pos = m.end()
|
||||
continue
|
||||
|
||||
m = re.compile(r"(?s)(#[^\n]+)").match(s, pos)
|
||||
if m:
|
||||
rv += '<span class="comment">%s</span>' % m.group(1)
|
||||
pos = m.end()
|
||||
continue
|
||||
|
||||
|
||||
m = re.compile(kwre).match(s, pos)
|
||||
if m:
|
||||
rv += '<span class="keyword">%s</span>' % m.group(0)
|
||||
pos = m.end()
|
||||
continue
|
||||
|
||||
|
||||
rv += s[pos]
|
||||
pos += 1
|
||||
|
||||
|
||||
return "<example>" + rv + "</example>"
|
||||
|
||||
def main():
|
||||
|
||||
f = file(sys.argv[1])
|
||||
s = f.read()
|
||||
f.close()
|
||||
|
||||
s = re.sub(r"(?s)<example>(.*?)</example>", example, s)
|
||||
|
||||
print s
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,46 @@
|
||||
BODY {
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
H1 {
|
||||
text-align: center;
|
||||
margin-top: 2em;
|
||||
margin-bottom: 2em;
|
||||
}
|
||||
|
||||
H2 {
|
||||
text-align: center;
|
||||
margin-top: 1em;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
PRE.example {
|
||||
border: 2px black solid;
|
||||
margin-left: 10%;
|
||||
margin-right: 10%;
|
||||
background: #f0f0f0;
|
||||
padding: 1em;
|
||||
padding-bottom: 0em;
|
||||
}
|
||||
|
||||
DIV.rule {
|
||||
border: 2px black solid;
|
||||
margin-left: 10%;
|
||||
margin-right: 10%;
|
||||
margin-bottom: 1em;
|
||||
|
||||
background: #f0f0ff;
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
SPAN.comment {
|
||||
color: #800000;
|
||||
}
|
||||
|
||||
SPAN.string {
|
||||
color: #006000;
|
||||
}
|
||||
|
||||
SPAN.keyword {
|
||||
color: #804000;
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
|
||||
|
||||
<!-- First, copy the element. -->
|
||||
<xsl:template match="*" priority="-1">
|
||||
<xsl:copy>
|
||||
<xsl:copy-of select="@*" />
|
||||
<xsl:apply-templates />
|
||||
</xsl:copy>
|
||||
</xsl:template>
|
||||
|
||||
<!-- Now, handle some elements specially. -->
|
||||
|
||||
<xsl:template match="doc">
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" type="text/css" href="style.css" />
|
||||
<title><xsl:value-of select="title" /></title>
|
||||
</head>
|
||||
<body>
|
||||
<xsl:apply-templates />
|
||||
</body>
|
||||
</html>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="title">
|
||||
<h1><xsl:apply-templates /></h1>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="subtitle">
|
||||
<h2><xsl:apply-templates /></h2>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="def">
|
||||
<b><i><xsl:apply-templates /></i></b>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="example">
|
||||
<pre class="example"><xsl:apply-templates /></pre>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="rule">
|
||||
<div class="rule"><xsl:apply-templates /></div>
|
||||
</xsl:template>
|
||||
|
||||
</xsl:stylesheet>
|
||||
@@ -0,0 +1,895 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<doc>
|
||||
<title>Writing Visual Novels with Ren'Py</title>
|
||||
<subtitle>The Ren'Py Tutorial</subtitle>
|
||||
|
||||
|
||||
<p>
|
||||
Ren'Py is a programming language and runtime, intended to ease
|
||||
the creation of visual-novel type games. It contains features
|
||||
that make it easy to display thoughts, dialogue, and menus; to
|
||||
display images to the user; to write game logic; and to support
|
||||
the saving and loading of games.
|
||||
|
||||
</p><p>
|
||||
|
||||
Ren'Py is implemented on top of python, and that python heritage
|
||||
shows through in many places. Many Ren'Py statements allow
|
||||
python expressions to be used, and there are also Ren'Py
|
||||
statements that allow for the execution of arbitrary python
|
||||
code. Many of the less-used features of Ren'Py are exposed to
|
||||
the user by way of python. By only requiring use of the simplest
|
||||
features of python, it's hoped that Ren'Py will be usable by
|
||||
all game authors.
|
||||
|
||||
</p>
|
||||
|
||||
<h3>A Simple Ren'Py Script</h3>
|
||||
|
||||
<p>
|
||||
|
||||
The following is a simple but complete Ren'Py script. The colors
|
||||
are added to make it easier to read, and aren't part of the
|
||||
script proper.
|
||||
|
||||
</p>
|
||||
|
||||
<example>
|
||||
init:
|
||||
image whitehouse = Image("whitehouse.jpg")
|
||||
image eileen happy = Image("eileen_happy.png")
|
||||
image eileen upset = Image("eileen_upset.png")
|
||||
|
||||
label start:
|
||||
$ e = Character('Eileen')
|
||||
|
||||
scene whitehouse
|
||||
show eileen happy
|
||||
|
||||
e "I'm standing in front of the White House."
|
||||
|
||||
show eileen upset
|
||||
|
||||
e "I once wanted to go on a tour of the West Wing, but you have to
|
||||
know somebody to get in."
|
||||
|
||||
"For some reason, she really seems upset about this."
|
||||
|
||||
e "I considered sneaking in, but that probably isn't a good idea."
|
||||
</example>
|
||||
|
||||
<p>
|
||||
This example, shows many aspects of a Ren'Py script. The first
|
||||
four lines of the script serve to load in three images. After
|
||||
the label indicating the start of the game, a character is
|
||||
declared. The script then proceeds to display a picture of a
|
||||
character on top of a background image, and to have the
|
||||
character say two lines of dialogue, changing her picture in
|
||||
between. The POV character then thinks a line of dialogue,
|
||||
before the character says her final line.
|
||||
|
||||
</p><p>
|
||||
|
||||
We'll go into detail into what each of the statements here does
|
||||
over the course of this tutorial. For now, however let me just
|
||||
point out that the first 6 statements inialize the game, while
|
||||
the last 7 (starting with "scene") show images and display
|
||||
dialogue. As such, the bulk of a game is more like the last 7
|
||||
then the first 6.
|
||||
|
||||
</p><p>
|
||||
|
||||
Of particular note is that a keyword isn't required to introduce
|
||||
dialogue. This allows visual novels consisting mostly of
|
||||
dialogue to be expressed in a concise form.
|
||||
|
||||
</p>
|
||||
|
||||
<h3>The Structure of a Ren'Py Script</h3>
|
||||
|
||||
<p>
|
||||
|
||||
The largest division of a Ren'Py script is into files. By
|
||||
default, Ren'Py reads the script from all files ending in .rpy
|
||||
found in the game underneath the directory in which Ren'Py is
|
||||
installed. These script files may be read in any order, and all
|
||||
of them together make up a Ren'Py script.
|
||||
|
||||
</p><p>
|
||||
|
||||
Each of these files is divided into a series of <def>logical
|
||||
line</def>s. The first logical line of a file begins at the start of a
|
||||
file, and another logical line begins after each logical line
|
||||
ends, until the end of the file is reached. By default, a
|
||||
logical line is terminated by the first newline
|
||||
encountered. However, a line will not terminate if any of the
|
||||
following are true:
|
||||
|
||||
</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
The newline is immediately preceded by a backslash. In this
|
||||
case, the backslash is removed, and the newline is
|
||||
ignored.
|
||||
</li>
|
||||
|
||||
<li>
|
||||
An opening parenthesis, bracket, or brace has been encountered
|
||||
without encountering the corresponding closing character.
|
||||
</li>
|
||||
|
||||
<li>
|
||||
The newline occurs within a string.
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
|
||||
These rules should be the same as for Python.
|
||||
|
||||
</p><p>
|
||||
|
||||
Ren'Py also supports <def>comment</def>s. A comment begins with a hash mark
|
||||
that is not contained within a string, and continues to, but
|
||||
does not include, the next newline character. Some examples are:
|
||||
|
||||
</p>
|
||||
|
||||
<example>
|
||||
# This line contains only a comment.
|
||||
scene whitehouse # This line contains a statement as well.
|
||||
</example>
|
||||
|
||||
|
||||
<p>
|
||||
|
||||
If, after eliminating comments, a logical line is empty, that
|
||||
logical line is ignored.
|
||||
|
||||
</p><p>
|
||||
|
||||
Logical lines are then combined into <def>block</def>s. Two
|
||||
logical lines are in the same block if the lines have the same
|
||||
indentation preceding them, and no logical line with a lesser
|
||||
amount of indentation occurs between the two lines. In the
|
||||
following example:
|
||||
</p>
|
||||
|
||||
<example>
|
||||
line 1
|
||||
line a
|
||||
line b
|
||||
line 2
|
||||
line c
|
||||
line d
|
||||
</example>
|
||||
|
||||
<p>
|
||||
|
||||
There are three blocks. One block contains lines 1 and 2,
|
||||
another lines a and b, and the third contains lines c and
|
||||
d. This example can also serve to illustrate the concept of a
|
||||
<def>block associated with a line</def>. A block is associated
|
||||
with a line if the block starts on the next logical line
|
||||
following the line. For example, the block containing lines a
|
||||
and b is associated with line 1.
|
||||
|
||||
|
||||
</p><p>
|
||||
|
||||
There are three kinds of blocks in an Ren'Py program. The most
|
||||
common is a block containing Ren'Py statements. Other blocks may
|
||||
contain menu entries or python code. The top-level block (the
|
||||
one that contains the first line of a file) is always a block of
|
||||
Ren'Py statements.
|
||||
|
||||
</p>
|
||||
|
||||
<h3>Lexical Structure of Statements</h3>
|
||||
|
||||
<p>
|
||||
Before we can discuss statements, however, we must first discuss
|
||||
the tokens statements are built up out of. So here's a short
|
||||
list of all the tokens we use.
|
||||
|
||||
</p><p>
|
||||
|
||||
<def>Keyword</def>s are words that appear in the source
|
||||
code. They're used to introduce a statement, or to delimit parts
|
||||
of a statement. You'll see keywords throughout the descriptions
|
||||
of statements. In grammar rules, keywords are in quotes.
|
||||
|
||||
</p><p>
|
||||
|
||||
A <def>name</def>s consist of an alphabetic character or number,
|
||||
followed by zero or more alphabetic characters or underscores,
|
||||
so long as the string isn't a keyword.
|
||||
|
||||
</p><p>
|
||||
|
||||
An <def>image_name</def> is a list of one or more names,
|
||||
separated by a space.
|
||||
|
||||
</p><p>
|
||||
|
||||
A <def>string</def> begins with a " or a ', and continues until
|
||||
a matching unescaped " or ' is reached. Runs of whitespace
|
||||
inside a string are collapsed into a single space character,
|
||||
allowing strings to span multiple lines. The \ character is used
|
||||
inside the string to escape special characters, such as
|
||||
whitespace, quotes, and (as \n) to include a newline.
|
||||
|
||||
</p><p>
|
||||
|
||||
A <def>simple_expression</def> is a python expression that
|
||||
starts with a name, a string, or any python expression in
|
||||
parenthesis. This may be followed by any number of the
|
||||
following:
|
||||
|
||||
</p>
|
||||
|
||||
<ul>
|
||||
<li>A dot followed by a name.</li>
|
||||
<li>A parenthesized python expression.</li>
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
|
||||
In general, simple expressions are strings, names, or method
|
||||
calls. They are not expected to contain operators.
|
||||
|
||||
</p><p>
|
||||
|
||||
A <def>python_expression</def> is an arbitrary python
|
||||
expression that may not include a colon. These expressions are
|
||||
generally used to express the conditions in the if and while
|
||||
statements.
|
||||
|
||||
</p>
|
||||
|
||||
<h3>Grammar Rules</h3>
|
||||
|
||||
<p>
|
||||
We will be giving grammar rules for some of the statements. In
|
||||
these rules, a word in quotes means that that word is literally
|
||||
expected. Parenthesis are used to group things together, but
|
||||
they don't correspond to anything in the source code. Star,
|
||||
question mark, and plus are used to indicate that the token or
|
||||
group they are to the right of can occur zero or more, zero or
|
||||
one, or one or more times, respectively.
|
||||
|
||||
</p><p>
|
||||
|
||||
If we give a name for the rule, it will be separated from the
|
||||
body of the rule with a crude ascii-art arrow (->).
|
||||
|
||||
</p>
|
||||
|
||||
|
||||
<h3>Dialogue: The Say Statement</h3>
|
||||
|
||||
<p>
|
||||
As the bulk of the content of a visual novel is presented to the
|
||||
user in the form of dialogue or thoughts, it's important that
|
||||
the ability to display text to the user be as convenient as
|
||||
possible. In Ren'Py, both actions are done through the say
|
||||
statement. The say statement doesn't require a keyword to
|
||||
introduce it. Instead, it consists of either a single string, or
|
||||
a simple_expression followed by a string.
|
||||
</p>
|
||||
|
||||
<rule>say_statement -> ( simple_expression )? string</rule>
|
||||
|
||||
<p>
|
||||
We can distinguish two forms of the say statement, depending on
|
||||
if the simple_expression is provided. The single-argument form
|
||||
of say consists only of a single string. This form causes the
|
||||
string to be displayed to the user without any label as to who
|
||||
is saying it. Conventionally, this is used to indicate to the
|
||||
user thoughts or narration.
|
||||
</p>
|
||||
|
||||
<example>
|
||||
"I moved to my left, and she moved to her right."
|
||||
|
||||
"So we were still blocking each other's path."
|
||||
|
||||
"I then moved to my right, and at the same time she moved to her
|
||||
left."
|
||||
|
||||
"We could be at this all day."
|
||||
</example>
|
||||
|
||||
<p>
|
||||
The two-argument form of the say statement first evaluates the
|
||||
expression to see what its value is. If the expression returns a
|
||||
string, that string is used as a character name to indicate who
|
||||
is saying the dialogue. If it returns an object, that object is
|
||||
responsible for displaying the dialogue to the user.
|
||||
|
||||
</p><p>
|
||||
|
||||
The most common type of object used in a dialogue statement is a
|
||||
Character object. Character objects have associated with them a
|
||||
name and a color. When a character object is asked to display a
|
||||
line of dialogue, it labels it with the character name in the
|
||||
character's signature color. In general, strings are used to
|
||||
indicate the names of lesser characters or ones who we have not
|
||||
discovered the name of yet, while character objects are used to
|
||||
indicate important characters.
|
||||
|
||||
</p>
|
||||
|
||||
<example>
|
||||
"Girl" "Hi, my name is Eileen."
|
||||
|
||||
e "Starting today, I'll be living here."
|
||||
</example>
|
||||
|
||||
<p>
|
||||
Finally, the string in a dialogue is subject to interpolation of
|
||||
variables. A string variable can be interpolated with %(name)s,
|
||||
while a number requires %(name)d. For example:
|
||||
</p>
|
||||
|
||||
<example>
|
||||
e "I know all about you."
|
||||
|
||||
e "I know that you're %(player_age)d years old, and your zodiac
|
||||
sign is %(player_sign)s."
|
||||
</example>
|
||||
|
||||
<h3>Menus</h3>
|
||||
|
||||
<p>
|
||||
Menus present a user with a list of choices that can be made. In a
|
||||
visual novel game, menus are the primary means by which the user
|
||||
can influence the game's story.
|
||||
</p>
|
||||
|
||||
<rule>menu_statement -> "menu" ( name )? ":"</rule>
|
||||
|
||||
<p>
|
||||
A menu statement consists simply of the word menu, an optional
|
||||
name, and a colon. If the name is supplied it's treated as a
|
||||
label for this menu statement, as if the menu statement was
|
||||
preceded by a label statement. (See the section on control flow
|
||||
for details about the label statement.)
|
||||
|
||||
</p><p>
|
||||
|
||||
The menu statement must have a block associated with it. This
|
||||
block must contain one or more menuitems in it. There are three
|
||||
kinds of menuitems that can be contained in a menu block.
|
||||
|
||||
</p>
|
||||
|
||||
<rule>caption_menuitem -> string</rule>
|
||||
|
||||
<p>
|
||||
|
||||
The first kind of menuitem is simply a string. This string is
|
||||
placed into a menu as a caption that cannot be selected. In
|
||||
general, captions are used to indicate what the menu is for,
|
||||
especially when it is not clear from the choices.
|
||||
|
||||
</p>
|
||||
|
||||
<rule>choice_menuitem -> string ( "if" python_expression )? ":"</rule>
|
||||
|
||||
<p>
|
||||
The second kind of menuitem gives a choice the user can
|
||||
make. Each choice must have a block of Ren'Py statements
|
||||
associated with the choice. If the choice is selected by the
|
||||
user, then block of statements associated with the choice
|
||||
is executed. A choice may also have an optional if clause that
|
||||
includes a python expression. This clause gives a condition that
|
||||
must be satisfied for the choice to be presented to the
|
||||
user. The terminating colon is what indicates that this menuitem
|
||||
is a choice.
|
||||
</p>
|
||||
|
||||
<rule>set_menuitem -> "set" simple_expression</rule>
|
||||
|
||||
<p>
|
||||
The third kind of menuitem gives an expression that yields a
|
||||
set. There may only be one of this kind of menuitem per menu. If
|
||||
present, it's used to filter the list of choices shown to the
|
||||
user.
|
||||
|
||||
</p><p>
|
||||
|
||||
When a menu is to be shown to the user, the first thing that
|
||||
happens is that a list of captions and choices is built up from
|
||||
the menuitems associated with the menu. Each of the choices that
|
||||
has an expression associated with it has that expression
|
||||
evaluated, and if it evaluates to false, that choice is removed
|
||||
from the list. Finally, if a set is present, it is checked to
|
||||
see if the text of a choice is in the set. If the text is found,
|
||||
the choice is removed from the list.
|
||||
|
||||
</p><p>
|
||||
|
||||
If no choices survive this process, the menu is not displayed
|
||||
and execution continues with the next statement. Otherwise, the
|
||||
menu is presented to the user. When the user makes a choice, the
|
||||
text of that choice is added to the set (if one is present), and
|
||||
execution continues with the block associated with the
|
||||
choice. When that block finishes, execution continues with the
|
||||
statement after the menu.
|
||||
|
||||
</p><p>
|
||||
|
||||
Here's a fairly complicated menu that uses all three kinds of
|
||||
menuitems. Most menus in actual games will not be this
|
||||
complicated.
|
||||
|
||||
</p>
|
||||
|
||||
<example>
|
||||
menu what_to_do:
|
||||
|
||||
# Ensure that we can only do a given thing once.
|
||||
set what_to_do_set
|
||||
|
||||
"What should we do today?"
|
||||
|
||||
"Go to the movies.":
|
||||
"We went to the movies."
|
||||
|
||||
"Go shopping.":
|
||||
"We went shopping, and the girls bought swimsuits."
|
||||
$ have_swimsuits = True
|
||||
|
||||
"Go to the beach." if have_swimsuits:
|
||||
"We went to the beach together. I got to see the girls in their
|
||||
new swimsuits."
|
||||
</example>
|
||||
|
||||
<p>
|
||||
This menu will only allow a given activity to be chosen once,
|
||||
and will allow the user to chose to go to the beach only if
|
||||
the user has chosen to go shopping.
|
||||
</p>
|
||||
|
||||
<h3>Displaying Images</h3>
|
||||
|
||||
<p>
|
||||
Without the ability to display images to the user, a visual
|
||||
novel would be a text adventure. Ren'Py controls image display
|
||||
by using a <def>scene list</def>, a list of things to be
|
||||
displayed to the user. Every time an interaction starts (that
|
||||
is, a line of dialogue or a menu is displayed), the things in
|
||||
the scene list are drawn to the screen, with the first being in
|
||||
the back and the last being in the front. A number of statements
|
||||
manipulate the scene list. Before we can explain them, however,
|
||||
we should first define a few terms.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
An <def>image_name</def> is a space-separated list of names
|
||||
that's used to refer to an image. This list of names may not
|
||||
include keywords in it. The first element of the image name is
|
||||
known as the <def>image tag</def>, and is treated specially.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
A <def>Displayable</def> is a python object implementing an
|
||||
interface that allows it to be displayed to the screen. A
|
||||
<def>transform</def> is a function that, when applied to a
|
||||
Displayable, returns a new Displayable. Transforms are used to
|
||||
change the way an image is displayed to the user. A
|
||||
<def>transform_list</def> is a comma-separated list of
|
||||
transforms. Display lists are applied from left to right.
|
||||
</p>
|
||||
|
||||
<rule>image_spec -> image_name ( "at" transform_list )? ( "with" transform_list )?</rule>
|
||||
|
||||
<p>
|
||||
An <def>image_spec</def> is an image name, an optional at list of
|
||||
transformers, and an optional with list of transformers. The
|
||||
at list is used to contain transforms that apply for the life of
|
||||
the image, such as transforms that change the placement of the
|
||||
image on the screen. The with list, on the other hand, contains
|
||||
transforms that should only be applied the first time the image
|
||||
is displayed, such as transitions. When the user finishes the
|
||||
next interaction, the with clause is removed from the image.
|
||||
</p>
|
||||
|
||||
<rule>image_statement -> "image" image_name "=" python_expression</rule>
|
||||
|
||||
<p>
|
||||
The first display statement is the image statement, which does
|
||||
binds an image name with a displayable defining that
|
||||
image. As the list of name bindings, is never saved, the image
|
||||
statement can only appear inside of an init block. The most
|
||||
popular python expression to use here is Image, which takes as
|
||||
an argument an image filename to load. Another popular choice is
|
||||
Animation, which is defined elsewhere in this document.
|
||||
|
||||
</p><p>
|
||||
|
||||
An example of image in use is:
|
||||
|
||||
</p>
|
||||
|
||||
<example>
|
||||
init:
|
||||
image eileen happy = Image("eileen/happy.png")
|
||||
image eileen upset = Image("eileen/upset.png")
|
||||
</example>
|
||||
|
||||
<rule>show_statement -> "show" image_spec</rule>
|
||||
|
||||
<p>
|
||||
|
||||
The next display statement is the show statement, which takes an
|
||||
image specifier and displays it on the screen. If an image with
|
||||
the same tag as the image given in the spec already exists on
|
||||
the scene list, it is replaced with the newly displayed
|
||||
Displayable. Otherwise, the new one is added to the end of the
|
||||
scene list (that is, closest to the user).
|
||||
|
||||
</p><p>
|
||||
|
||||
If a with list is present, the image, transformed by the at
|
||||
and with lists is displayed to the user for the next
|
||||
interaction. At the end of that interaction, it is replaced by
|
||||
the image transformed with only the with clause. If no with
|
||||
list exists, the image transformed with the at list is
|
||||
immediately displayed.
|
||||
|
||||
</p><p>
|
||||
|
||||
Automatically replacing an image with the same tag is a useful
|
||||
feature that allows characters to change expression without
|
||||
having to explicitly hide the old image.
|
||||
|
||||
</p>
|
||||
|
||||
|
||||
<rule>scene_statement -> "scene" ( image_spec )?</rule>
|
||||
|
||||
<p>
|
||||
The scene statement first clears the scene_list. If the optional
|
||||
image_spec is present, it is shown as if it was shown with the
|
||||
show statement. The best use for the image_spec on a scene
|
||||
command is show a background for the scene.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
We can put together the scene and show statements to get the
|
||||
following example:
|
||||
</p>
|
||||
|
||||
<example>
|
||||
scene living_room
|
||||
show eileen happy at left
|
||||
|
||||
e "I'm feeling happy right now."
|
||||
|
||||
show eileen upset at left
|
||||
|
||||
e "But sometimes, I can get upset for no good reason."
|
||||
</example>
|
||||
|
||||
<rule>hide_statement -> "hide" image_spec</rule>
|
||||
|
||||
<p>
|
||||
The hide statement is used to remove an image from the scene
|
||||
list. If the image_spec does not contain a with list, an image
|
||||
matching the tag of the image_spec is removed from the scene
|
||||
list immediately. (In this form, the image name doesn't need to
|
||||
actually exist, so long as the tag is recognizable.) If the
|
||||
image_spec does have a with clause, the image_spec is shown
|
||||
until the end of the next interaction, and only then removed
|
||||
from the scene_list. This allows the hiding of an image to
|
||||
include a transition effect.
|
||||
|
||||
</p><p>
|
||||
|
||||
Hide is a rarely used display statement. The show statement
|
||||
automatically replaces an old image when a character changes
|
||||
emotion, and the scene image removes all images when the scene
|
||||
changes. Hide is generally only used for when a character leaves
|
||||
in the middle of a scene.
|
||||
|
||||
</p>
|
||||
|
||||
<example>
|
||||
e "Well, I'll be going now."
|
||||
|
||||
hide eileen
|
||||
|
||||
"And with that, she left."
|
||||
</example>
|
||||
|
||||
|
||||
<p>
|
||||
These four statements, along with the library of Displayables
|
||||
and transforms provided with Ren'Py, should be enough to render
|
||||
most scenes needed in a visual novel type game.
|
||||
</p>
|
||||
|
||||
|
||||
<h3>Control Statements</h3>
|
||||
|
||||
<p>
|
||||
Control statements change the order in which statements in a
|
||||
Ren'Py script execute. These statements allow for control
|
||||
transfers, conditional execution, and procedure calls.
|
||||
</p>
|
||||
|
||||
<rule>label_statement -> "label" name ":"</rule>
|
||||
|
||||
<p>
|
||||
The label statements assigns a name to a point in the program,
|
||||
allowing control to be transfered to this point by the jump or
|
||||
call statements. The label statement may have a block associated
|
||||
with it. If it does, the statement executed after the label is
|
||||
the first statement in the block. Otherwise, the next statement
|
||||
to be executed is the first statement after the label.
|
||||
</p>
|
||||
|
||||
<rule>jump statement -> "jump" name</rule>
|
||||
|
||||
<p>
|
||||
The jump statement unconditionally transfers control to the
|
||||
statement with the given name. If the name does not exist, an
|
||||
error is raised.
|
||||
</p>
|
||||
|
||||
<example>
|
||||
label loop_start:
|
||||
|
||||
e "Oh no! It looks like we're trapped in an infinite loop."
|
||||
|
||||
jump loop_start
|
||||
</example>
|
||||
|
||||
<rule>call_statement -> "call" name ( "from" name )?</rule>
|
||||
|
||||
<p>
|
||||
The call statement transfers control to the location given. It
|
||||
also pushes the name of the return site onto the return stack,
|
||||
allowing the return statement to return to the statement after
|
||||
the call site.
|
||||
|
||||
</p><p>
|
||||
|
||||
If the optional from clause is present, it has the effect of
|
||||
including a label statement with the given name as the statement
|
||||
immediately following the call statement. An explicit label is
|
||||
required here to ensure that saved games with return stacks can
|
||||
return to the proper place when loaded on a changed script. On
|
||||
the other hand, from clauses may be distracting when a game is
|
||||
still under development. A script will be provided to add from
|
||||
clauses to call statements right before a game is released.
|
||||
|
||||
</p>
|
||||
|
||||
<rule>return_statement -> "return"</rule>
|
||||
|
||||
<p>
|
||||
|
||||
If the return stack is not empty, the return statement pops the
|
||||
top return site off of it and transfers control
|
||||
there. Otherwise, it terminates execution without raising an
|
||||
error.
|
||||
|
||||
</p>
|
||||
|
||||
<example>
|
||||
e "First, we will call a subroutine."
|
||||
|
||||
call subroutine from _call_site_1
|
||||
|
||||
e "Finally, we will exit the program."
|
||||
|
||||
return
|
||||
|
||||
label subroutine:
|
||||
|
||||
e "Next, we will return from the subroutine."
|
||||
|
||||
return
|
||||
</example>
|
||||
|
||||
<rule>if_statement -> "if" python_expression ":"</rule>
|
||||
<rule>elif_clause -> "elif" python_expression ":"</rule>
|
||||
<rule>else_clause -> "else" ":"</rule>
|
||||
|
||||
<p>
|
||||
The if statement is used to conditionally execute a block of
|
||||
statements. It is the only statement that consists of more than
|
||||
one logical line in the same block. The initial if statement may
|
||||
be followed by zero or more elif clauses, concluded with an
|
||||
optional else clause. The expression is evaluated for each
|
||||
clause in turn, and if it evaluates to a true value, then the
|
||||
block associated with that clause is executed. If no expression
|
||||
evaluates to true, then the block associated with the else
|
||||
clause is executed. (If else clause exists, execution
|
||||
immediately continues with the next statement.) In any case, at
|
||||
the end of the block, control is transferred to the statement
|
||||
following the if statement.
|
||||
</p>
|
||||
|
||||
<example>
|
||||
if points >= 10:
|
||||
|
||||
e "Congratulations! You're getting the best ending!"
|
||||
|
||||
elif points >= 5:
|
||||
|
||||
e "It's the good ending for you."
|
||||
|
||||
else:
|
||||
|
||||
e "Sorry, you're about to get the bad ending."
|
||||
</example>
|
||||
|
||||
<rule>while_statement -> "while" python_expression ":"</rule>
|
||||
|
||||
<p>
|
||||
|
||||
The while statement executes its block while the expression is
|
||||
true. Specifically, each time the while statement executes, it
|
||||
evaluates the expression. If the expression is true, control is
|
||||
transferred to the first statement of the block associated with
|
||||
the while loop. If it is false, then control is transferred to
|
||||
the next statement. The while statement is the statement that
|
||||
normally executes after the last statement in the block, causing
|
||||
the condition to be evaluated again and the loop to repeat.
|
||||
|
||||
</p><p>
|
||||
|
||||
This definition of a while loop means that it would be hard to
|
||||
implement statements like python's "continue" or "break". These
|
||||
statements can be easily faked with labels in the right places
|
||||
and jumps to those labels. This definition also means that it's
|
||||
possible to jump into the middle of the block associated with
|
||||
a while loop and, if at the end of the block the condition
|
||||
is true, have the while loop repeat the block.
|
||||
|
||||
</p>
|
||||
|
||||
<example>
|
||||
while not endgame:
|
||||
|
||||
"It's now morning. Time to get up and seize the day."
|
||||
|
||||
call morning
|
||||
call afternoon
|
||||
call evening
|
||||
|
||||
"Well, time to call it a night."
|
||||
|
||||
"Now it's time to wake up and face the endgame."
|
||||
</example>
|
||||
|
||||
|
||||
<rule>pass_statement -> "pass"</rule>
|
||||
|
||||
<p>
|
||||
|
||||
The pass statement can be used where a block is required, but
|
||||
there's no statement that can be placed in that block. When
|
||||
executed, pass has no effect.
|
||||
|
||||
</p><p>
|
||||
|
||||
For example, pass can be used in a menu if we don't want to take
|
||||
any action when a choice is selected.
|
||||
|
||||
</p>
|
||||
|
||||
<example>
|
||||
menu:
|
||||
"Should I go to the movies?"
|
||||
|
||||
"Yes":
|
||||
call go_see_movie
|
||||
|
||||
"No":
|
||||
pass
|
||||
|
||||
"Now it's getting close to dinner time, and I'm starving."
|
||||
</example>
|
||||
|
||||
<rule>init_statement -> "init" ":"</rule>
|
||||
|
||||
<p>
|
||||
|
||||
The init statement is used to introduce a block of code that
|
||||
should be run when the game first starts. When the game is first
|
||||
loaded, the script is scanned for init blocks, and code in init
|
||||
blocks is run in an arbitrary order. An init statement
|
||||
encountered during execution, however, is treated as a pass
|
||||
statement, and the block is not executed.
|
||||
|
||||
</p>
|
||||
|
||||
<h3>Python Statements</h3>
|
||||
|
||||
<rule>python_statement -> "$" python_code</rule>
|
||||
|
||||
<p>
|
||||
There are two Ren'Py statements that allow python statements to be
|
||||
mixed with Ren'Py code. Any statement beginning with a
|
||||
dollar-sign ('$') will be interpreted as python code extending
|
||||
to the end of the logical line. This form can only include a
|
||||
python statement containing a single logical line. (So python
|
||||
control constructs cannot be used.)
|
||||
</p>
|
||||
|
||||
<rule>python_block_statement -> "python" ( "hide" )? ":"</rule>
|
||||
|
||||
<p>
|
||||
The other way to introduce python statements is with a python
|
||||
block statement. The block associated with this statement, along
|
||||
with any block inside those blocks, is interpreted as python
|
||||
code that is passed to the python interpreter. The block nesting
|
||||
structure is reflected in the python code that is interpreted,
|
||||
so that python control structures will work as advertised.
|
||||
|
||||
</p><p>
|
||||
|
||||
If the optional hide keyword is added to a python block
|
||||
statements, local variables created in the block will not
|
||||
be added to the store. The variables in the store can be
|
||||
accessed as attributes of the store, however.
|
||||
</p>
|
||||
|
||||
<example>
|
||||
# Toggle fullscreen mode.
|
||||
$ config.fullscreen = not config.fullscreen
|
||||
|
||||
# Pointless python that uses a loop.
|
||||
python:
|
||||
for i in ('e', 'l'):
|
||||
globals()[i].points = 0
|
||||
</example>
|
||||
|
||||
<p>
|
||||
In general, if a Ren'Py construct exists that does what you want
|
||||
(like while or if), it should be used in preference to a python
|
||||
block, unless a large amount of code is to be executed with
|
||||
no user interaction.
|
||||
</p>
|
||||
|
||||
|
||||
<h3>Starting a Game</h3>
|
||||
|
||||
<p>
|
||||
When Ren'Py is first invoked, it first tries to parse all the
|
||||
.rpy files in the game directory. If at least one .rpy file
|
||||
exists, it is loaded, and the script is then written out in a
|
||||
serialized form. If no .rpy files exist, but the serialized
|
||||
script does exist, the serialized script is read back in from
|
||||
disk.
|
||||
|
||||
</p><p>
|
||||
|
||||
Once the script is loaded, the first thing that occurs is that
|
||||
it is scanned for init blocks. These init blocks are then run
|
||||
immediately, in no particular order. The init blocks should do
|
||||
things like loading images and changing Ren'Py configuration. On
|
||||
no account should an init block try to display an image or
|
||||
interact with the user, as the display system is not yet
|
||||
initialized, and so such interaction will not work.
|
||||
|
||||
</p><p>
|
||||
|
||||
After the last init block has finished running, the display is
|
||||
initialized, and the actual game can begin. It's expected that
|
||||
each game will have a label named "start". The game is begun by
|
||||
jumping to this start label. Execution proceeds from there,
|
||||
terminating when the end of a file or a return statement is
|
||||
reached.
|
||||
|
||||
</p>
|
||||
|
||||
</doc>
|
||||
|
||||
<!-- (define-key xml-mode-map [(control return)] 'tompy-xml-ctrlret) -->
|
||||
@@ -1,5 +0,0 @@
|
||||
*.c
|
||||
*.so
|
||||
*.egg-info
|
||||
*.html
|
||||
build
|
||||
@@ -1 +0,0 @@
|
||||
../../renpy/cslots.pyx
|
||||
@@ -1,8 +0,0 @@
|
||||
from setuptools import setup, Extension
|
||||
|
||||
setup(
|
||||
name="cslots",
|
||||
version="1.0.0",
|
||||
ext_modules=[
|
||||
Extension("cslots", [ "cslots.pyx" ]),
|
||||
])
|
||||
@@ -1,7 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
cython -a cslots.pyx
|
||||
pip install -e .
|
||||
python3 -m pytest
|
||||
@@ -1,262 +0,0 @@
|
||||
import sys
|
||||
import pickle
|
||||
|
||||
from cslots import Object, Slot, IntegerSlot
|
||||
|
||||
class C1(Object):
|
||||
str1 : str|None = None
|
||||
int1 : int = 0
|
||||
|
||||
def __init__(self, str1=None, int1=1):
|
||||
self.str1 = str1
|
||||
self.int1 = int1
|
||||
|
||||
class C2(C1):
|
||||
str2 : str|None = None
|
||||
int2 : int = 0
|
||||
|
||||
def __init__(self, str1=None, int1=0, str2=None, int2=0):
|
||||
self.str1 = str1
|
||||
self.int1 = int1
|
||||
self.str2 = str2
|
||||
self.int2 = int2
|
||||
|
||||
|
||||
def test_slot_count():
|
||||
assert C1._cslot_count == 2
|
||||
assert C2._cslot_count == 4
|
||||
|
||||
def test_slots():
|
||||
|
||||
o = C2()
|
||||
|
||||
assert o.str1 is None
|
||||
assert o.int1 == 0
|
||||
assert o.str2 is None
|
||||
assert o.int2 == 0
|
||||
|
||||
o.str1 = "hello"
|
||||
o.int1 = 42
|
||||
o.str2 = "world"
|
||||
o.int2 = 43
|
||||
|
||||
assert o.str1 == "hello"
|
||||
assert o.int1 == 42
|
||||
assert o.str2 == "world"
|
||||
assert o.int2 == 43
|
||||
|
||||
o.str1 = None
|
||||
o.int1 = 0
|
||||
o.str2 = None
|
||||
o.int2 = 0
|
||||
|
||||
assert o.str1 is None
|
||||
assert o.int1 == 0
|
||||
assert o.str2 is None
|
||||
assert o.int2 == 0
|
||||
|
||||
|
||||
def test_compress():
|
||||
|
||||
def expect_size(o, values, indexes):
|
||||
assert sys.getsizeof(o) == 32 + 8 * values + indexes
|
||||
|
||||
o = C2()
|
||||
|
||||
o.str1 = "hello"
|
||||
o.int1 = 42
|
||||
o.str2 = "world"
|
||||
o.int2 = 43
|
||||
expect_size(o, 4, 4)
|
||||
|
||||
o._compress()
|
||||
|
||||
assert o.str1 == "hello"
|
||||
assert o.int1 == 42
|
||||
assert o.str2 == "world"
|
||||
assert o.int2 == 43
|
||||
expect_size(o, 4, 4)
|
||||
|
||||
o = C2()
|
||||
|
||||
o.str2 = "world"
|
||||
o.int2 = 43
|
||||
|
||||
o._compress()
|
||||
|
||||
assert o.str1 is None
|
||||
assert o.int1 == 0
|
||||
assert o.str2 == "world"
|
||||
assert o.int2 == 43
|
||||
expect_size(o, 2, 4)
|
||||
|
||||
o = C2()
|
||||
o.str1 = "hello"
|
||||
|
||||
o._compress()
|
||||
|
||||
assert o.str1 == "hello"
|
||||
assert o.int1 == 0
|
||||
assert o.str2 is None
|
||||
assert o.int2 == 0
|
||||
expect_size(o, 1, 1)
|
||||
|
||||
|
||||
def test_decompress():
|
||||
|
||||
def expect_size(o, values, indexes):
|
||||
assert sys.getsizeof(o) == 32 + 8 * values + indexes
|
||||
|
||||
o = C2()
|
||||
|
||||
o.str1 = "hello"
|
||||
o.int1 = 42
|
||||
o.str2 = "world"
|
||||
o.int2 = 43
|
||||
expect_size(o, 4, 4)
|
||||
|
||||
o._compress()
|
||||
o._decompress()
|
||||
|
||||
assert o.str1 == "hello"
|
||||
assert o.int1 == 42
|
||||
assert o.str2 == "world"
|
||||
assert o.int2 == 43
|
||||
expect_size(o, 4, 4)
|
||||
|
||||
o = C2()
|
||||
|
||||
o.str2 = "world"
|
||||
o.int2 = 43
|
||||
|
||||
o._compress()
|
||||
o._decompress()
|
||||
|
||||
assert o.str1 is None
|
||||
assert o.int1 == 0
|
||||
assert o.str2 == "world"
|
||||
assert o.int2 == 43
|
||||
expect_size(o, 4, 4)
|
||||
|
||||
o = C2()
|
||||
o.str1 = "hello"
|
||||
|
||||
o._compress()
|
||||
o._decompress()
|
||||
|
||||
assert o.str1 == "hello"
|
||||
assert o.int1 == 0
|
||||
assert o.str2 is None
|
||||
assert o.int2 == 0
|
||||
expect_size(o, 4, 4)
|
||||
|
||||
|
||||
def test_kill():
|
||||
|
||||
o = C2()
|
||||
|
||||
o.str1 = "hello"
|
||||
o.int1 = 42
|
||||
o.str2 = "world"
|
||||
o.int2 = 43
|
||||
|
||||
o._kill()
|
||||
|
||||
assert o.str1 is None
|
||||
assert o.int1 == 0
|
||||
assert o.str2 is None
|
||||
assert o.int2 == 0
|
||||
|
||||
o = C2()
|
||||
|
||||
o.str2 = "world"
|
||||
o.int2 = 43
|
||||
|
||||
o._compress()
|
||||
o._kill()
|
||||
|
||||
assert o.str1 is None
|
||||
assert o.int1 == 0
|
||||
assert o.str2 is None
|
||||
assert o.int2 == 0
|
||||
|
||||
|
||||
def test_pickle():
|
||||
o = C2()
|
||||
|
||||
o.str1 = "hello"
|
||||
o.int1 = 42
|
||||
o.str2 = "world"
|
||||
o.int2 = 43
|
||||
|
||||
o = pickle.loads(pickle.dumps(o))
|
||||
|
||||
assert o.str1 == "hello"
|
||||
assert o.int1 == 42
|
||||
assert o.str2 == "world"
|
||||
assert o.int2 == 43
|
||||
|
||||
o = C2()
|
||||
|
||||
o.str2 = "world"
|
||||
o.int2 = 43
|
||||
|
||||
o = pickle.loads(pickle.dumps(o))
|
||||
|
||||
assert o.str1 is None
|
||||
assert o.int1 == 0
|
||||
assert o.str2 == "world"
|
||||
assert o.int2 == 43
|
||||
|
||||
o = C2()
|
||||
o.str1 = "hello"
|
||||
|
||||
o = pickle.loads(pickle.dumps(o))
|
||||
|
||||
assert o.str1 == "hello"
|
||||
assert o.int1 == 0
|
||||
assert o.str2 is None
|
||||
assert o.int2 == 0
|
||||
|
||||
|
||||
|
||||
def test_pickle_compressed():
|
||||
o = C2()
|
||||
|
||||
o.str1 = "hello"
|
||||
o.int1 = 42
|
||||
o.str2 = "world"
|
||||
o.int2 = 43
|
||||
|
||||
o._compress()
|
||||
|
||||
o = pickle.loads(pickle.dumps(o))
|
||||
|
||||
assert o.str1 == "hello"
|
||||
assert o.int1 == 42
|
||||
assert o.str2 == "world"
|
||||
assert o.int2 == 43
|
||||
|
||||
o = C2()
|
||||
|
||||
o.str2 = "world"
|
||||
o.int2 = 43
|
||||
|
||||
o._compress()
|
||||
o = pickle.loads(pickle.dumps(o))
|
||||
|
||||
assert o.str1 is None
|
||||
assert o.int1 == 0
|
||||
assert o.str2 == "world"
|
||||
assert o.int2 == 43
|
||||
|
||||
o = C2()
|
||||
o.str1 = "hello"
|
||||
|
||||
o._compress()
|
||||
o = pickle.loads(pickle.dumps(o))
|
||||
|
||||
assert o.str1 == "hello"
|
||||
assert o.int1 == 0
|
||||
assert o.str2 is None
|
||||
assert o.int2 == 0
|
||||
@@ -1,481 +0,0 @@
|
||||
################################################################################
|
||||
## Initialization
|
||||
################################################################################
|
||||
|
||||
## The init offset statement causes the initialization statements in this file
|
||||
## to run before init statements in any other file.
|
||||
init offset = -2
|
||||
|
||||
## Calling gui.init resets the styles to sensible default values, and sets the
|
||||
## width and height of the game.
|
||||
init python:
|
||||
gui.init(1280, 720)
|
||||
|
||||
## Enable checks for invalid or unstable properties in screens or transforms
|
||||
define config.check_conflicting_properties = True
|
||||
|
||||
|
||||
################################################################################
|
||||
## GUI Configuration Variables
|
||||
################################################################################
|
||||
|
||||
|
||||
## Colors ######################################################################
|
||||
##
|
||||
## The colors of text in the interface.
|
||||
|
||||
## An accent color used throughout the interface to label and highlight
|
||||
## text.
|
||||
define gui.accent_color = "#00b8c3"
|
||||
|
||||
## The color used for a text button when it is neither selected nor hovered.
|
||||
define gui.idle_color = "#888888"
|
||||
|
||||
## The small color is used for small text, which needs to be
|
||||
## brighter/darker to achieve the same effect.
|
||||
define gui.idle_small_color = "#aaaaaa"
|
||||
|
||||
## The color that is used for buttons and bars that are hovered.
|
||||
define gui.hover_color = Color(gui.accent_color).tint(.6)
|
||||
|
||||
## The color used for a text button when it is selected but not focused.
|
||||
## A button is selected if it is the current screen or preference value.
|
||||
define gui.selected_color = "#ffffff"
|
||||
|
||||
## The color used for a text button when it cannot be selected.
|
||||
define gui.insensitive_color = "#55555580"
|
||||
|
||||
## Colors used for the portions of bars that are not filled in. These are
|
||||
## not used directly, but are used when re-generating bar image files.
|
||||
define gui.muted_color = "#004e49"
|
||||
define gui.hover_muted_color = "#006e75"
|
||||
|
||||
## The colors used for dialogue and menu choice text.
|
||||
define gui.text_color = "#ffffff"
|
||||
define gui.interface_text_color = "#ffffff"
|
||||
|
||||
|
||||
## Fonts and Font Sizes ########################################################
|
||||
|
||||
## The font used for in-game text.
|
||||
define gui.text_font = "DejaVuSans.ttf"
|
||||
|
||||
## The font used for character names.
|
||||
define gui.name_text_font = "DejaVuSans.ttf"
|
||||
|
||||
## The font used for out-of-game text.
|
||||
define gui.interface_text_font = "DejaVuSans.ttf"
|
||||
|
||||
## The size of normal dialogue text.
|
||||
define gui.text_size = gui.scale(22)
|
||||
|
||||
## The size of character names.
|
||||
define gui.name_text_size = gui.scale(30)
|
||||
|
||||
## The size of text in the game's user interface.
|
||||
define gui.interface_text_size = gui.scale(22)
|
||||
|
||||
## The size of labels in the game's user interface.
|
||||
define gui.label_text_size = gui.scale(24)
|
||||
|
||||
## The size of text on the notify screen.
|
||||
define gui.notify_text_size = gui.scale(16)
|
||||
|
||||
## The size of the game's title.
|
||||
define gui.title_text_size = gui.scale(50)
|
||||
|
||||
|
||||
## Main and Game Menus #########################################################
|
||||
|
||||
## The images used for the main and game menus.
|
||||
define gui.main_menu_background = "gui/main_menu.png"
|
||||
define gui.game_menu_background = "gui/game_menu.png"
|
||||
|
||||
|
||||
## Dialogue ####################################################################
|
||||
##
|
||||
## These variables control how dialogue is displayed on the screen one line
|
||||
## at a time.
|
||||
|
||||
## The height of the textbox containing dialogue.
|
||||
define gui.textbox_height = gui.scale(185)
|
||||
|
||||
## The placement of the textbox vertically on the screen. 0.0 is the top, 0.5 is
|
||||
## center, and 1.0 is the bottom.
|
||||
define gui.textbox_yalign = 1.0
|
||||
|
||||
|
||||
## The placement of the speaking character's name, relative to the textbox.
|
||||
## These can be a whole number of pixels from the left or top, or 0.5 to center.
|
||||
define gui.name_xpos = gui.scale(240)
|
||||
define gui.name_ypos = gui.scale(0)
|
||||
|
||||
## The horizontal alignment of the character's name. This can be 0.0 for
|
||||
## left-aligned, 0.5 for centered, and 1.0 for right-aligned.
|
||||
define gui.name_xalign = 0.0
|
||||
|
||||
## The width, height, and borders of the box containing the character's
|
||||
## name, or None to automatically size it.
|
||||
define gui.namebox_width = None
|
||||
define gui.namebox_height = None
|
||||
|
||||
## The borders of the box containing the character's name, in left, top,
|
||||
## right, bottom order.
|
||||
define gui.namebox_borders = Borders(5, 5, 5, 5)
|
||||
|
||||
## If True, the background of the namebox will be tiled, if False, the background
|
||||
## of the namebox will be scaled.
|
||||
define gui.namebox_tile = False
|
||||
|
||||
|
||||
## The placement of dialogue relative to the textbox. These can be a whole
|
||||
## number of pixels relative to the left or top side of the textbox, or 0.5
|
||||
## to center.
|
||||
define gui.dialogue_xpos = gui.scale(268)
|
||||
define gui.dialogue_ypos = gui.scale(50)
|
||||
|
||||
## The maximum width of dialogue text, in pixels.
|
||||
define gui.dialogue_width = gui.scale(744)
|
||||
|
||||
## The horizontal alignment of the dialogue text. This can be 0.0 for
|
||||
## left-aligned, 0.5 for centered, and 1.0 for right-aligned.
|
||||
define gui.dialogue_text_xalign = 0.0
|
||||
|
||||
|
||||
## Buttons #####################################################################
|
||||
##
|
||||
## These variables, along with the image files in gui/button, control aspects
|
||||
## of how buttons are displayed.
|
||||
|
||||
## The width and height of a button, in pixels. If None, Ren'Py computes a size.
|
||||
define gui.button_width = None
|
||||
define gui.button_height = None
|
||||
|
||||
## The borders on each side of the button, in left, top, right, bottom order.
|
||||
define gui.button_borders = Borders(gui.scale(4), gui.scale(4), gui.scale(4), gui.scale(4))
|
||||
|
||||
## If True, the background image will be tiled. If False, the background image
|
||||
## will be linearly scaled.
|
||||
define gui.button_tile = False
|
||||
|
||||
## The font used by the button.
|
||||
define gui.button_text_font = gui.interface_text_font
|
||||
|
||||
## The size of the text used by the button.
|
||||
define gui.button_text_size = gui.interface_text_size
|
||||
|
||||
## The color of button text in various states.
|
||||
define gui.button_text_idle_color = gui.idle_color
|
||||
define gui.button_text_hover_color = gui.hover_color
|
||||
define gui.button_text_selected_color = gui.selected_color
|
||||
define gui.button_text_insensitive_color = gui.insensitive_color
|
||||
|
||||
## The horizontal alignment of the button text. (0.0 is left, 0.5 is center,
|
||||
## 1.0 is right).
|
||||
define gui.button_text_xalign = 0.0
|
||||
|
||||
|
||||
## These variables override settings for different kinds of buttons. Please
|
||||
## see the gui documentation for the kinds of buttons available, and what
|
||||
## each is used for.
|
||||
##
|
||||
## These customizations are used by the default interface:
|
||||
|
||||
define gui.radio_button_borders = Borders(gui.scale(18), gui.scale(4), gui.scale(4), gui.scale(4))
|
||||
|
||||
define gui.check_button_borders = Borders(gui.scale(18), gui.scale(4), gui.scale(4), gui.scale(4))
|
||||
|
||||
define gui.confirm_button_text_xalign = 0.5
|
||||
|
||||
define gui.page_button_borders = Borders(gui.scale(10), gui.scale(4), gui.scale(10), gui.scale(4))
|
||||
|
||||
define gui.quick_button_borders = Borders(gui.scale(10), gui.scale(4), gui.scale(10), gui.scale(0))
|
||||
define gui.quick_button_text_size = gui.scale(14)
|
||||
define gui.quick_button_text_idle_color = gui.idle_small_color
|
||||
define gui.quick_button_text_selected_color = gui.accent_color
|
||||
|
||||
## You can also add your own customizations, by adding properly-named variables.
|
||||
## For example, you can uncomment the following line to set the width of a
|
||||
## navigation button.
|
||||
|
||||
# define gui.navigation_button_width = 250
|
||||
|
||||
|
||||
## Choice Buttons ##############################################################
|
||||
##
|
||||
## Choice buttons are used in the in-game menus.
|
||||
|
||||
define gui.choice_button_width = gui.scale(790)
|
||||
define gui.choice_button_height = None
|
||||
define gui.choice_button_tile = False
|
||||
define gui.choice_button_borders = Borders(gui.scale(100), gui.scale(5), gui.scale(100), gui.scale(5))
|
||||
define gui.choice_button_text_font = gui.text_font
|
||||
define gui.choice_button_text_size = gui.text_size
|
||||
define gui.choice_button_text_xalign = 0.5
|
||||
define gui.choice_button_text_idle_color = "#cccccc"
|
||||
define gui.choice_button_text_hover_color = "#ffffff"
|
||||
define gui.choice_button_text_insensitive_color = "#444444"
|
||||
|
||||
|
||||
## File Slot Buttons ###########################################################
|
||||
##
|
||||
## A file slot button is a special kind of button. It contains a thumbnail
|
||||
## image, and text describing the contents of the save slot. A save slot
|
||||
## uses image files in gui/button, like the other kinds of buttons.
|
||||
|
||||
## The save slot button.
|
||||
define gui.slot_button_width = gui.scale(276)
|
||||
define gui.slot_button_height = gui.scale(206)
|
||||
define gui.slot_button_borders = Borders(gui.scale(10), gui.scale(10), gui.scale(10), gui.scale(10))
|
||||
define gui.slot_button_text_size = gui.scale(14)
|
||||
define gui.slot_button_text_xalign = 0.5
|
||||
define gui.slot_button_text_idle_color = gui.idle_small_color
|
||||
define gui.slot_button_text_selected_idle_color = gui.selected_color
|
||||
define gui.slot_button_text_selected_hover_color = gui.hover_color
|
||||
|
||||
## The width and height of thumbnails used by the save slots.
|
||||
define config.thumbnail_width = gui.scale(256)
|
||||
define config.thumbnail_height = gui.scale(144)
|
||||
|
||||
## The number of columns and rows in the grid of save slots.
|
||||
define gui.file_slot_cols = 3
|
||||
define gui.file_slot_rows = 2
|
||||
|
||||
|
||||
## Positioning and Spacing #####################################################
|
||||
##
|
||||
## These variables control the positioning and spacing of various user interface
|
||||
## elements.
|
||||
|
||||
## The position of the left side of the navigation buttons, relative
|
||||
## to the left side of the screen.
|
||||
define gui.navigation_xpos = gui.scale(40)
|
||||
|
||||
## The vertical position of the skip indicator.
|
||||
define gui.skip_ypos = gui.scale(10)
|
||||
|
||||
## The vertical position of the notify screen.
|
||||
define gui.notify_ypos = gui.scale(45)
|
||||
|
||||
## The spacing between menu choices.
|
||||
define gui.choice_spacing = gui.scale(22)
|
||||
|
||||
## Buttons in the navigation section of the main and game menus.
|
||||
define gui.navigation_spacing = gui.scale(4)
|
||||
|
||||
## Controls the amount of spacing between preferences.
|
||||
define gui.pref_spacing = gui.scale(10)
|
||||
|
||||
## Controls the amount of spacing between preference buttons.
|
||||
define gui.pref_button_spacing = gui.scale(0)
|
||||
|
||||
## The spacing between file page buttons.
|
||||
define gui.page_spacing = gui.scale(0)
|
||||
|
||||
## The spacing between file slots.
|
||||
define gui.slot_spacing = gui.scale(10)
|
||||
|
||||
## The position of the main menu text.
|
||||
define gui.main_menu_text_xalign = 1.0
|
||||
|
||||
|
||||
## Frames ######################################################################
|
||||
##
|
||||
## These variables control the look of frames that can contain user interface
|
||||
## components when an overlay or window is not present.
|
||||
|
||||
## Generic frames.
|
||||
define gui.frame_borders = Borders(gui.scale(4), gui.scale(4), gui.scale(4), gui.scale(4))
|
||||
|
||||
## The frame that is used as part of the confirm screen.
|
||||
define gui.confirm_frame_borders = Borders(gui.scale(40), gui.scale(40), gui.scale(40), gui.scale(40))
|
||||
|
||||
## The frame that is used as part of the skip screen.
|
||||
define gui.skip_frame_borders = Borders(gui.scale(16), gui.scale(5), gui.scale(50), gui.scale(5))
|
||||
|
||||
## The frame that is used as part of the notify screen.
|
||||
define gui.notify_frame_borders = Borders(gui.scale(16), gui.scale(5), gui.scale(40), gui.scale(5))
|
||||
|
||||
## Should frame backgrounds be tiled?
|
||||
define gui.frame_tile = False
|
||||
|
||||
|
||||
## Bars, Scrollbars, and Sliders ###############################################
|
||||
##
|
||||
## These control the look and size of bars, scrollbars, and sliders.
|
||||
##
|
||||
## The default GUI only uses sliders and vertical scrollbars.
|
||||
## All of the other bars are only used in creator-written screens.
|
||||
|
||||
## The height of horizontal bars, scrollbars, and sliders. The width of
|
||||
## vertical bars, scrollbars, and sliders.
|
||||
define gui.bar_size = gui.scale(25)
|
||||
define gui.scrollbar_size = gui.scale(12)
|
||||
define gui.slider_size = gui.scale(25)
|
||||
|
||||
## True if bar images should be tiled. False if they should be linearly scaled.
|
||||
define gui.bar_tile = False
|
||||
define gui.scrollbar_tile = False
|
||||
define gui.slider_tile = False
|
||||
|
||||
## Horizontal borders.
|
||||
define gui.bar_borders = Borders(gui.scale(4), gui.scale(4), gui.scale(4), gui.scale(4))
|
||||
define gui.scrollbar_borders = Borders(gui.scale(4), gui.scale(4), gui.scale(4), gui.scale(4))
|
||||
define gui.slider_borders = Borders(gui.scale(4), gui.scale(4), gui.scale(4), gui.scale(4))
|
||||
|
||||
## Vertical borders.
|
||||
define gui.vbar_borders = Borders(gui.scale(4), gui.scale(4), gui.scale(4), gui.scale(4))
|
||||
define gui.vscrollbar_borders = Borders(gui.scale(4), gui.scale(4), gui.scale(4), gui.scale(4))
|
||||
define gui.vslider_borders = Borders(gui.scale(4), gui.scale(4), gui.scale(4), gui.scale(4))
|
||||
|
||||
## What to do with unscrollable scrollbars in the game menu. "hide" hides them, while
|
||||
## None shows them.
|
||||
define gui.unscrollable = "hide"
|
||||
|
||||
|
||||
## History #####################################################################
|
||||
##
|
||||
## The history screen displays dialogue that the player has already dismissed.
|
||||
|
||||
## The number of blocks of dialogue history Ren'Py will keep.
|
||||
define config.history_length = 250
|
||||
|
||||
## The height of a history screen entry, or None to make the height variable
|
||||
## at the cost of performance.
|
||||
define gui.history_height = gui.scale(140)
|
||||
|
||||
## Additional space to add between history screen entries.
|
||||
define gui.history_spacing = 0
|
||||
|
||||
## The position, width, and alignment of the label giving the name of the
|
||||
## speaking character.
|
||||
define gui.history_name_xpos = gui.scale(155)
|
||||
define gui.history_name_ypos = 0
|
||||
define gui.history_name_width = gui.scale(155)
|
||||
define gui.history_name_xalign = 1.0
|
||||
|
||||
## The position, width, and alignment of the dialogue text.
|
||||
define gui.history_text_xpos = gui.scale(170)
|
||||
define gui.history_text_ypos = gui.scale(2)
|
||||
define gui.history_text_width = gui.scale(740)
|
||||
define gui.history_text_xalign = 0.0
|
||||
|
||||
|
||||
## NVL-Mode ###################################################################
|
||||
##
|
||||
## The NVL-mode screen displays the dialogue spoken by NVL-mode characters.
|
||||
|
||||
## The borders of the background of the NVL-mode background window.
|
||||
define gui.nvl_borders = Borders(0, gui.scale(10), 0, gui.scale(20))
|
||||
|
||||
## The maximum number of NVL-mode entries Ren'Py will display. When more
|
||||
## entries than this are to be show, the oldest entry will be removed.
|
||||
define gui.nvl_list_length = 6
|
||||
|
||||
## The height of an NVL-mode entry. Set this to None to have the entries
|
||||
## dynamically adjust height.
|
||||
define gui.nvl_height = gui.scale(115)
|
||||
|
||||
## The spacing between NVL-mode entries when gui.nvl_height is None, and
|
||||
## between NVL-mode entries and an NVL-mode menu.
|
||||
define gui.nvl_spacing = gui.scale(10)
|
||||
|
||||
## The position, width, and alignment of the label giving the name of the
|
||||
## speaking character.
|
||||
define gui.nvl_name_xpos = gui.scale(430)
|
||||
define gui.nvl_name_ypos = 0
|
||||
define gui.nvl_name_width = gui.scale(150)
|
||||
define gui.nvl_name_xalign = 1.0
|
||||
|
||||
## The position, width, and alignment of the dialogue text.
|
||||
define gui.nvl_text_xpos = gui.scale(450)
|
||||
define gui.nvl_text_ypos = gui.scale(8)
|
||||
define gui.nvl_text_width = gui.scale(590)
|
||||
define gui.nvl_text_xalign = 0.0
|
||||
|
||||
## The position, width, and alignment of nvl_thought text (the text said by
|
||||
## the nvl_narrator character.)
|
||||
define gui.nvl_thought_xpos = gui.scale(240)
|
||||
define gui.nvl_thought_ypos = gui.scale(0)
|
||||
define gui.nvl_thought_width = gui.scale(780)
|
||||
define gui.nvl_thought_xalign = 0.0
|
||||
|
||||
## The position of nvl menu_buttons.
|
||||
define gui.nvl_button_xpos = gui.scale(450)
|
||||
define gui.nvl_button_xalign = 0.0
|
||||
|
||||
|
||||
## Localization ################################################################
|
||||
|
||||
## This controls where a line break is permitted. The default is suitable for
|
||||
## most languages. A list of available values can be found at
|
||||
## https://www.renpy.org/doc/html/style_properties.html#style-property-language
|
||||
|
||||
define gui.language = "unicode"
|
||||
|
||||
|
||||
################################################################################
|
||||
## Mobile devices
|
||||
################################################################################
|
||||
|
||||
init python:
|
||||
|
||||
## This increases the size of the quick buttons to make them easier to
|
||||
## touch on tablets and phones.
|
||||
@gui.variant
|
||||
def touch():
|
||||
|
||||
gui.quick_button_borders = Borders(gui.scale(40), gui.scale(14), gui.scale(40), gui.scale(0))
|
||||
|
||||
## This changes the size and spacing of various GUI elements to ensure
|
||||
## they are easily visible on phones.
|
||||
@gui.variant
|
||||
def small():
|
||||
|
||||
## Font sizes.
|
||||
gui.text_size = gui.scale(30)
|
||||
gui.name_text_size = gui.scale(36)
|
||||
gui.notify_text_size = gui.scale(25)
|
||||
gui.interface_text_size = gui.scale(30)
|
||||
gui.button_text_size = gui.scale(30)
|
||||
gui.label_text_size = gui.scale(34)
|
||||
|
||||
## Adjust the location of the textbox.
|
||||
gui.textbox_height = gui.scale(240)
|
||||
gui.name_xpos = gui.scale(80)
|
||||
gui.dialogue_xpos = gui.scale(90)
|
||||
gui.dialogue_width = gui.scale(1100)
|
||||
|
||||
## Change the size and spacing of various things.
|
||||
gui.slider_size = gui.scale(36)
|
||||
|
||||
gui.choice_button_width = gui.scale(1240)
|
||||
gui.choice_button_text_size = gui.scale(30)
|
||||
|
||||
gui.navigation_spacing = gui.scale(20)
|
||||
gui.pref_button_spacing = gui.scale(10)
|
||||
|
||||
gui.history_height = gui.scale(190)
|
||||
gui.history_text_width = gui.scale(690)
|
||||
|
||||
gui.quick_button_text_size = gui.scale(20)
|
||||
|
||||
## File button layout.
|
||||
gui.file_slot_cols = 2
|
||||
gui.file_slot_rows = 2
|
||||
|
||||
## NVL-mode.
|
||||
gui.nvl_height = gui.scale(170)
|
||||
|
||||
gui.nvl_name_width = gui.scale(305)
|
||||
gui.nvl_name_xpos = gui.scale(325)
|
||||
|
||||
gui.nvl_text_width = gui.scale(915)
|
||||
gui.nvl_text_xpos = gui.scale(345)
|
||||
gui.nvl_text_ypos = gui.scale(5)
|
||||
|
||||
gui.nvl_thought_width = gui.scale(1240)
|
||||
gui.nvl_thought_xpos = gui.scale(20)
|
||||
|
||||
gui.nvl_button_width = gui.scale(1240)
|
||||
gui.nvl_button_xpos = gui.scale(20)
|
||||
|
Before Width: | Height: | Size: 6.7 KiB |
|
Before Width: | Height: | Size: 4.1 KiB |
@@ -1,30 +0,0 @@
|
||||
init -100 python in gui:
|
||||
|
||||
def scale(n):
|
||||
return int(n)
|
||||
|
||||
init 100 python in gui:
|
||||
|
||||
if not renpy.mobile:
|
||||
from store import config
|
||||
|
||||
import sys
|
||||
import os
|
||||
sys.path.insert(0, os.path.join(config.renpy_base, "launcher", "game"))
|
||||
|
||||
from gui7.parameters import GuiParameters
|
||||
from gui7.images import ImageGenerator
|
||||
from gui7 import generate_gui
|
||||
|
||||
p = GuiParameters(
|
||||
config.gamedir, config.gamedir,
|
||||
config.screen_width, config.screen_height,
|
||||
accent_color, "#000000", False, None,
|
||||
True, False, False, "gui")
|
||||
|
||||
p.skip_backup = True
|
||||
|
||||
generate_gui(p)
|
||||
|
||||
_skip_backup = True
|
||||
_gui_images()
|
||||
@@ -1 +0,0 @@
|
||||
## When this file exists, Ren'Py will load files in game/libs before other files in game.
|
||||
@@ -1,213 +0,0 @@
|
||||
## This file contains options that can be changed to customize your
|
||||
## game.
|
||||
##
|
||||
## Lines beginning with two '#' marks are comments, and you shouldn't
|
||||
## uncomment them. Lines beginning with a single '#' mark are
|
||||
## commented-out code, and you may want to uncomment them when
|
||||
## appropriate.
|
||||
|
||||
|
||||
## Basics ######################################################################
|
||||
|
||||
## A human-readable name of the game. This is used to set the default
|
||||
## window title, and shows up in the interface and error reports.
|
||||
##
|
||||
## The _() surrounding the string marks it as eligible for translation.
|
||||
|
||||
define config.name = _("Ren'Py 7 Default GUI")
|
||||
|
||||
|
||||
## Determines if the title given above is shown on the main menu
|
||||
## screen. Set this to False to hide the title.
|
||||
|
||||
define gui.show_name = True
|
||||
|
||||
|
||||
## The version of the game.
|
||||
|
||||
define config.version = "1.0"
|
||||
|
||||
|
||||
## Text that is placed on the game's about screen. Place the text between
|
||||
## the triple-quotes, and leave a blank line between paragraphs.
|
||||
|
||||
define gui.about = _p("""
|
||||
""")
|
||||
|
||||
|
||||
## A short name for the game used for executables and directories in the
|
||||
## built distribution. This must be ASCII-only, and must not contain
|
||||
## spaces, colons, or semicolons.
|
||||
|
||||
define build.name = "gui"
|
||||
|
||||
|
||||
## Sounds and music ############################################################
|
||||
|
||||
## These three variables control, among other things, which mixers are
|
||||
## shown to the player by default. Setting one of these to False will
|
||||
## hide the appropriate mixer.
|
||||
|
||||
define config.has_sound = True
|
||||
define config.has_music = True
|
||||
define config.has_voice = True
|
||||
|
||||
|
||||
## To allow the user to play a test sound on the sound or voice channel,
|
||||
## uncomment a line below and use it to set a sample sound to play.
|
||||
|
||||
# define config.sample_sound = "sample-sound.ogg"
|
||||
# define config.sample_voice = "sample-voice.ogg"
|
||||
|
||||
|
||||
## Uncomment the following line to set an audio file that will be played
|
||||
## while the player is at the main menu. This file will continue playing
|
||||
## into the game, until it is stopped or another file is played.
|
||||
|
||||
# define config.main_menu_music = "main-menu-theme.ogg"
|
||||
|
||||
|
||||
## Transitions #################################################################
|
||||
##
|
||||
## These variables set transitions that are used when certain events occur.
|
||||
## Each variable should be set to a transition, or None to indicate that
|
||||
## no transition should be used.
|
||||
|
||||
## Entering or exiting the game menu.
|
||||
|
||||
define config.enter_transition = dissolve
|
||||
define config.exit_transition = dissolve
|
||||
|
||||
|
||||
## Between screens of the game menu.
|
||||
|
||||
define config.intra_transition = dissolve
|
||||
|
||||
|
||||
## A transition that is used after a game has been loaded.
|
||||
|
||||
define config.after_load_transition = None
|
||||
|
||||
|
||||
## Used when entering the main menu after the game has ended.
|
||||
|
||||
define config.end_game_transition = None
|
||||
|
||||
|
||||
## A variable to set the transition used when the game starts
|
||||
## does not exist. Instead, use a with statement after showing
|
||||
## the initial scene.
|
||||
|
||||
|
||||
## Window management ###########################################################
|
||||
##
|
||||
## This controls when the dialogue window is displayed. If "show", it is always
|
||||
## displayed. If "hide", it is only displayed when dialogue is present. If
|
||||
## "auto", the window is hidden before scene statements and shown again
|
||||
## once dialogue is displayed.
|
||||
##
|
||||
## After the game has started, this can be changed with the "window show",
|
||||
## "window hide", and "window auto" statements.
|
||||
|
||||
define config.window = "auto"
|
||||
|
||||
|
||||
## Transitions used to show and hide the dialogue window
|
||||
|
||||
define config.window_show_transition = Dissolve(.2)
|
||||
define config.window_hide_transition = Dissolve(.2)
|
||||
|
||||
|
||||
## Preference defaults #########################################################
|
||||
|
||||
## Controls the default text speed. The default, 0, is infinite, while any
|
||||
## other number is the number of characters per second to type out.
|
||||
|
||||
default preferences.text_cps = 0
|
||||
|
||||
|
||||
## The default auto-forward delay. Larger numbers lead to longer waits,
|
||||
## with 0 to 30 being the valid range.
|
||||
|
||||
default preferences.afm_time = 15
|
||||
|
||||
|
||||
## Save directory ##############################################################
|
||||
##
|
||||
## Controls the platform-specific place Ren'Py will place the save
|
||||
## files for this game. The save files will be placed in:
|
||||
##
|
||||
## Windows: %APPDATA\RenPy\<config.save_directory>
|
||||
##
|
||||
## Macintosh: $HOME/Library/RenPy/<config.save_directory>
|
||||
##
|
||||
## Linux: $HOME/.renpy/<config.save_directory>
|
||||
##
|
||||
## This generally should not be changed, and if it is, should always be a
|
||||
## literal string, not an expression.
|
||||
|
||||
define config.save_directory = "gui-7"
|
||||
|
||||
|
||||
## Icon ########################################################################
|
||||
##
|
||||
## The icon displayed on the taskbar or dock.
|
||||
|
||||
define config.window_icon = "gui/window_icon.png"
|
||||
|
||||
|
||||
## Build configuration #########################################################
|
||||
##
|
||||
## This section controls how Ren'Py turns your project into distribution
|
||||
## files.
|
||||
|
||||
init python:
|
||||
|
||||
## The following functions take file patterns. File patterns are case-
|
||||
## insensitive, and matched against the path relative to the base
|
||||
## directory, with and without a leading /. If multiple patterns match,
|
||||
## the first is used.
|
||||
##
|
||||
## In a pattern:
|
||||
##
|
||||
## / is the directory separator.
|
||||
##
|
||||
## * matches all characters, except the directory separator.
|
||||
##
|
||||
## ** matches all characters, including the directory separator.
|
||||
##
|
||||
## For example, "*.txt" matches txt files in the base directory, "game/**.ogg"
|
||||
## matches ogg files in the game directory or any of its subdirectories, and
|
||||
## "**.psd" matches psd files anywhere in the project.
|
||||
|
||||
## Classify files as None to exclude them from the built distributions.
|
||||
|
||||
build.classify('**~', None)
|
||||
build.classify('**.bak', None)
|
||||
build.classify('**/.**', None)
|
||||
build.classify('**/#**', None)
|
||||
build.classify('**/thumbs.db', None)
|
||||
|
||||
## To archive files, classify them as 'archive'.
|
||||
|
||||
# build.classify('game/**.png', 'archive')
|
||||
# build.classify('game/**.jpg', 'archive')
|
||||
|
||||
## Files matching documentation patterns are duplicated in a mac app
|
||||
## build, so they appear in both the app and the zip file.
|
||||
|
||||
build.documentation('*.html')
|
||||
build.documentation('*.txt')
|
||||
|
||||
|
||||
## A Google Play license key is required to perform in-app purchases.
|
||||
## It can be found in the Google Play developer console,
|
||||
## under "Monetize" > "Monetization Setup" > "Licensing".
|
||||
|
||||
# define build.google_play_key = "..."
|
||||
|
||||
|
||||
## The username and project name associated with an itch.io project,
|
||||
## separated by a slash.
|
||||
|
||||
# define build.itch_project = "renpytom/test-project"
|
||||
@@ -1,33 +0,0 @@
|
||||
# The script of the game goes in this file.
|
||||
|
||||
# Declare characters used by this game. The color argument colorizes the
|
||||
# name of the character.
|
||||
|
||||
define e = Character("Eileen")
|
||||
|
||||
|
||||
# The game starts here.
|
||||
|
||||
label start:
|
||||
|
||||
# Show a background. This uses a placeholder by default, but you can
|
||||
# add a file (named either "bg room.png" or "bg room.jpg") to the
|
||||
# images directory to show it.
|
||||
|
||||
scene bg room
|
||||
|
||||
# This shows a character sprite. A placeholder is used, but you can
|
||||
# replace it by adding a file named "eileen happy.png" to the images
|
||||
# directory.
|
||||
|
||||
show eileen happy
|
||||
|
||||
# These display lines of dialogue.
|
||||
|
||||
e "You've created a new Ren'Py game."
|
||||
|
||||
e "Once you add a story, pictures, and music, you can release it to the world!"
|
||||
|
||||
# This ends the game.
|
||||
|
||||
return
|
||||
@@ -1,160 +0,0 @@
|
||||
testcase default:
|
||||
|
||||
"Start"
|
||||
pause .6
|
||||
|
||||
# Test rollback
|
||||
"Hello, World."
|
||||
"Back"
|
||||
|
||||
# Test history.
|
||||
click
|
||||
click
|
||||
"History"
|
||||
pause .6
|
||||
|
||||
# Test Help.
|
||||
"Help"
|
||||
"Keyboard"
|
||||
|
||||
# Test About
|
||||
"About"
|
||||
|
||||
# Test Preferences
|
||||
"Preferences"
|
||||
|
||||
"Left"
|
||||
"Right"
|
||||
"Disable"
|
||||
|
||||
"Unseen Text"
|
||||
"Unseen Text"
|
||||
|
||||
"After Choices"
|
||||
"After Choices"
|
||||
|
||||
"Transitions"
|
||||
"Transitions"
|
||||
|
||||
"Mute All"
|
||||
"Mute All"
|
||||
|
||||
"Save"
|
||||
"Load"
|
||||
|
||||
"Return"
|
||||
pause .6
|
||||
|
||||
run Jump("test")
|
||||
|
||||
"In testcase code."
|
||||
|
||||
# menu_1
|
||||
click until "Choice A"
|
||||
|
||||
|
||||
# input
|
||||
type "Test User\n"
|
||||
"Name: Test User"
|
||||
|
||||
# Nvl
|
||||
click
|
||||
click
|
||||
click
|
||||
|
||||
click
|
||||
click
|
||||
click until "NVL 6"
|
||||
|
||||
# NVL Menu.
|
||||
"Choice B"
|
||||
|
||||
$ renpy.unlink_save("1-1")
|
||||
"Save"
|
||||
|
||||
pause .6
|
||||
|
||||
"Save slot 1"
|
||||
|
||||
pause .6
|
||||
|
||||
"Save slot 1"
|
||||
"Yes"
|
||||
|
||||
"Main Menu"
|
||||
"Yes"
|
||||
|
||||
"Load"
|
||||
"Load slot 1"
|
||||
|
||||
pause .5
|
||||
|
||||
"Save"
|
||||
|
||||
pause .5
|
||||
|
||||
|
||||
"Load"
|
||||
"Load slot 1"
|
||||
"No"
|
||||
|
||||
"Return"
|
||||
|
||||
# Done.
|
||||
pause .5
|
||||
|
||||
"Done."
|
||||
"Quit"
|
||||
|
||||
|
||||
define nvle = Character("Eileen", kind=nvl)
|
||||
|
||||
|
||||
label test:
|
||||
|
||||
"In testcase code."
|
||||
|
||||
|
||||
menu menu_1:
|
||||
"This is a menu."
|
||||
|
||||
"Choice A":
|
||||
pass
|
||||
|
||||
"Choice B":
|
||||
pass
|
||||
|
||||
label after_menu_1:
|
||||
|
||||
|
||||
$ name = renpy.input("What is your name?")
|
||||
"Name: [name]"
|
||||
|
||||
nvle "NVL 1"
|
||||
nvle "NVL 2"
|
||||
nvle "NVL 3"
|
||||
|
||||
nvl clear
|
||||
|
||||
nvle "NVL 4"
|
||||
nvle "NVL 5"
|
||||
nvle "NVL 6"
|
||||
|
||||
|
||||
$ menu = nvl_menu
|
||||
|
||||
|
||||
menu menu_2:
|
||||
"This is a menu."
|
||||
|
||||
"Choice A":
|
||||
pass
|
||||
|
||||
"Choice B":
|
||||
pass
|
||||
|
||||
label after_menu_2:
|
||||
|
||||
"Done."
|
||||
|
||||
return
|
||||
@@ -1 +0,0 @@
|
||||
{ "type" : "hidden" }
|
||||
@@ -1,86 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# The path to a copy of Atom that's been installed globally, if one exists.
|
||||
# This is overidden by RENPY_ATOM, if set. If either is given, that is used
|
||||
# and no special handing of the .atom directory is performed.
|
||||
ATOM = None
|
||||
|
||||
import sys
|
||||
import os
|
||||
import subprocess
|
||||
import platform
|
||||
import shutil
|
||||
|
||||
import renpy
|
||||
|
||||
|
||||
class Editor(renpy.editor.Editor):
|
||||
|
||||
has_projects = True
|
||||
|
||||
def get_atom(self):
|
||||
"""
|
||||
Returns the path to the atom executable, if None. Also takes care
|
||||
of setting up the .atom directory if it's not available.
|
||||
"""
|
||||
|
||||
atom = os.environ.get("RENPY_ATOM", ATOM)
|
||||
|
||||
if atom is not None:
|
||||
return atom
|
||||
|
||||
DIR = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
|
||||
DIR = os.path.join(renpy.exports.fsdecode(DIR), "atom")
|
||||
|
||||
if renpy.windows:
|
||||
atom = os.path.join(DIR, "atom-windows", "atom.exe")
|
||||
elif renpy.macintosh:
|
||||
atom = os.path.join(DIR, "Atom.app", "Contents", "Resources", "app", "atom.sh")
|
||||
else:
|
||||
atom = os.path.join(DIR, "atom-linux-" + platform.machine(), "atom")
|
||||
|
||||
default_dot_atom = os.path.join(DIR, "default-dot-atom")
|
||||
dot_atom = os.path.join(DIR, ".atom")
|
||||
|
||||
if not os.path.exists(dot_atom) and os.path.exists(default_dot_atom):
|
||||
shutil.copytree(default_dot_atom, dot_atom)
|
||||
|
||||
return atom
|
||||
|
||||
def begin(self, new_window=False, **kwargs):
|
||||
self.args = [ ]
|
||||
|
||||
def open(self, filename, line=None, **kwargs):
|
||||
|
||||
if line:
|
||||
filename = "{}:{}".format(filename, line)
|
||||
|
||||
self.args.append(filename)
|
||||
|
||||
def open_project(self, project):
|
||||
self.args.append(project)
|
||||
|
||||
def end(self, **kwargs):
|
||||
|
||||
atom = self.get_atom()
|
||||
|
||||
self.args.reverse()
|
||||
|
||||
args = [ atom ] + self.args
|
||||
args = [ renpy.exports.fsencode(i) for i in args ]
|
||||
|
||||
subprocess.Popen(args)
|
||||
|
||||
|
||||
def main():
|
||||
e = Editor()
|
||||
e.begin()
|
||||
|
||||
for i in sys.argv[1:]:
|
||||
e.open(i)
|
||||
|
||||
e.end()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,4 +0,0 @@
|
||||
import renpy
|
||||
|
||||
# Do nothing when the editor is invoked.
|
||||
Editor = renpy.editor.Editor
|
||||
@@ -1,4 +0,0 @@
|
||||
import renpy
|
||||
|
||||
# Pass the file off to the system editor (as determined by file associations).
|
||||
Editor = renpy.editor.SystemEditor
|
||||
@@ -1,93 +0,0 @@
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
import renpy
|
||||
|
||||
class Editor(renpy.editor.Editor):
|
||||
|
||||
has_projects = True
|
||||
|
||||
system = __file__.endswith(" (System).edit.py")
|
||||
|
||||
def get_code(self):
|
||||
"""
|
||||
Returns the path to the code executable.
|
||||
"""
|
||||
|
||||
if self.system:
|
||||
|
||||
if "RENPY_VSCODE" in os.environ:
|
||||
return os.environ["RENPY_VSCODE"]
|
||||
|
||||
if renpy.windows:
|
||||
return "code.cmd"
|
||||
|
||||
if renpy.macintosh and os.path.exists("/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code"):
|
||||
return "/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code"
|
||||
|
||||
return "code"
|
||||
|
||||
else:
|
||||
|
||||
RENPY_VSCODE = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "vscode"))
|
||||
|
||||
if renpy.windows:
|
||||
code = os.path.join(RENPY_VSCODE, "VSCode-win32-x64", "bin", "code.cmd")
|
||||
elif renpy.macintosh:
|
||||
code = os.path.join(RENPY_VSCODE, "Visual Studio Code.app", "Contents", "Resources", "app", "bin", "code")
|
||||
elif renpy.linux:
|
||||
if renpy.arch == "aarch64":
|
||||
arch = "arm64"
|
||||
elif renpy.arch == "armv7l":
|
||||
arch = "arm"
|
||||
else:
|
||||
arch = "x64"
|
||||
|
||||
code = os.path.join(RENPY_VSCODE, "VSCode-linux-" + arch, "bin", "code")
|
||||
else:
|
||||
code = "code"
|
||||
|
||||
return code
|
||||
|
||||
def open(self, filename, line=None, **kwargs):
|
||||
if line:
|
||||
filename = "{}:{}".format(filename, line)
|
||||
self.args.append(filename)
|
||||
|
||||
def open_project(self, project):
|
||||
self.args.append(project)
|
||||
|
||||
def begin(self, new_window=False, **kwargs):
|
||||
self.args = [ ]
|
||||
|
||||
def end(self, **kwargs):
|
||||
self.args.reverse()
|
||||
|
||||
code = self.get_code()
|
||||
if self.system or not renpy.linux:
|
||||
args = [ code, "-g" ] + self.args
|
||||
else:
|
||||
args = [ code, "--no-sandbox", "-g" ] + self.args
|
||||
|
||||
args = [ renpy.exports.fsencode(i) for i in args ]
|
||||
|
||||
if renpy.windows:
|
||||
CREATE_NO_WINDOW = 0x08000000
|
||||
subprocess.Popen(args, creationflags=CREATE_NO_WINDOW)
|
||||
else:
|
||||
subprocess.Popen(args)
|
||||
|
||||
|
||||
|
||||
def main():
|
||||
e = Editor()
|
||||
e.begin()
|
||||
|
||||
for i in sys.argv[1:]:
|
||||
e.open(i)
|
||||
|
||||
e.end()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,93 +0,0 @@
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
import renpy
|
||||
|
||||
class Editor(renpy.editor.Editor):
|
||||
|
||||
has_projects = True
|
||||
|
||||
system = __file__.endswith(" (System).edit.py")
|
||||
|
||||
def get_code(self):
|
||||
"""
|
||||
Returns the path to the code executable.
|
||||
"""
|
||||
|
||||
if self.system:
|
||||
|
||||
if "RENPY_VSCODE" in os.environ:
|
||||
return os.environ["RENPY_VSCODE"]
|
||||
|
||||
if renpy.windows:
|
||||
return "code.cmd"
|
||||
|
||||
if renpy.macintosh and os.path.exists("/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code"):
|
||||
return "/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code"
|
||||
|
||||
return "code"
|
||||
|
||||
else:
|
||||
|
||||
RENPY_VSCODE = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "vscode"))
|
||||
|
||||
if renpy.windows:
|
||||
code = os.path.join(RENPY_VSCODE, "VSCode-win32-x64", "bin", "code.cmd")
|
||||
elif renpy.macintosh:
|
||||
code = os.path.join(RENPY_VSCODE, "Visual Studio Code.app", "Contents", "Resources", "app", "bin", "code")
|
||||
elif renpy.linux:
|
||||
if renpy.arch == "aarch64":
|
||||
arch = "arm64"
|
||||
elif renpy.arch == "armv7l":
|
||||
arch = "arm"
|
||||
else:
|
||||
arch = "x64"
|
||||
|
||||
code = os.path.join(RENPY_VSCODE, "VSCode-linux-" + arch, "bin", "code")
|
||||
else:
|
||||
code = "code"
|
||||
|
||||
return code
|
||||
|
||||
def open(self, filename, line=None, **kwargs):
|
||||
if line:
|
||||
filename = "{}:{}".format(filename, line)
|
||||
self.args.append(filename)
|
||||
|
||||
def open_project(self, project):
|
||||
self.args.append(project)
|
||||
|
||||
def begin(self, new_window=False, **kwargs):
|
||||
self.args = [ ]
|
||||
|
||||
def end(self, **kwargs):
|
||||
self.args.reverse()
|
||||
|
||||
code = self.get_code()
|
||||
if self.system or not renpy.linux:
|
||||
args = [ code, "-g" ] + self.args
|
||||
else:
|
||||
args = [ code, "--no-sandbox", "-g" ] + self.args
|
||||
|
||||
args = [ renpy.exports.fsencode(i) for i in args ]
|
||||
|
||||
if renpy.windows:
|
||||
CREATE_NO_WINDOW = 0x08000000
|
||||
subprocess.Popen(args, creationflags=CREATE_NO_WINDOW)
|
||||
else:
|
||||
subprocess.Popen(args)
|
||||
|
||||
|
||||
|
||||
def main():
|
||||
e = Editor()
|
||||
e.begin()
|
||||
|
||||
for i in sys.argv[1:]:
|
||||
e.open(i)
|
||||
|
||||
e.end()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,45 +0,0 @@
|
||||
# Copyright 2004-2025 Tom Rothamel <pytom@bishoujo.us>
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person
|
||||
# obtaining a copy of this software and associated documentation files
|
||||
# (the "Software"), to deal in the Software without restriction,
|
||||
# including without limitation the rights to use, copy, modify, merge,
|
||||
# publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
# and to permit persons to whom the Software is furnished to do so,
|
||||
# subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
# Checks for various abilities that might be taken away from us by
|
||||
# redistributors.
|
||||
|
||||
init 1 python in ability:
|
||||
|
||||
from store import config
|
||||
import store
|
||||
import store.updater as updater
|
||||
|
||||
import os
|
||||
|
||||
EXECUTABLES = [ "renpy.exe", "renpy.app", "renpy.sh" ]
|
||||
|
||||
# can_distribute - True if we can distribute
|
||||
for i in EXECUTABLES:
|
||||
if not os.path.exists(os.path.join(config.renpy_base, i)):
|
||||
can_distribute = False
|
||||
else:
|
||||
can_distribute = True
|
||||
|
||||
|
||||
# can_update - True if we can update.
|
||||
can_update = updater.can_update() or (store.UPDATE_SIMULATE is not None)
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
# Copyright 2004-2025 Tom Rothamel <pytom@bishoujo.us>
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person
|
||||
# obtaining a copy of this software and associated documentation files
|
||||
# (the "Software"), to deal in the Software without restriction,
|
||||
# including without limitation the rights to use, copy, modify, merge,
|
||||
# publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
# and to permit persons to whom the Software is furnished to do so,
|
||||
# subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
screen about:
|
||||
|
||||
$ version = renpy.version()
|
||||
|
||||
frame:
|
||||
style_group "l"
|
||||
style "l_root"
|
||||
|
||||
window:
|
||||
xfill True
|
||||
|
||||
has vbox xfill True
|
||||
|
||||
add "images/logo.png" xalign 0.5 yoffset -5
|
||||
|
||||
null height 15
|
||||
|
||||
text _("[version!q]") xalign 0.5 bold True
|
||||
|
||||
null height 20
|
||||
|
||||
textbutton _("View license") action interface.OpenLicense() xalign 0.5
|
||||
|
||||
textbutton _("Return") action Jump("front_page") style "l_left_button"
|
||||
|
||||
label about:
|
||||
call screen about
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
# Copyright 2004-2025 Tom Rothamel <pytom@bishoujo.us>
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person
|
||||
# obtaining a copy of this software and associated documentation files
|
||||
# (the "Software"), to deal in the Software without restriction,
|
||||
# including without limitation the rights to use, copy, modify, merge,
|
||||
# publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
# and to permit persons to whom the Software is furnished to do so,
|
||||
# subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
label add_file:
|
||||
|
||||
python hide:
|
||||
import os
|
||||
filename = ""
|
||||
while True:
|
||||
filename = interface.input(
|
||||
_("FILENAME"),
|
||||
_("Enter the name of the script file to create."),
|
||||
allow=interface.FILENAME_LETTERS,
|
||||
cancel=Jump("navigation"),
|
||||
default=filename,
|
||||
)
|
||||
filename = filename.strip()
|
||||
if not filename:
|
||||
interface.error(_("The file name may not be empty."), label=None)
|
||||
continue
|
||||
|
||||
if "." in filename and not filename.endswith(".rpy"):
|
||||
interface.error(_("The filename must have the .rpy extension."), label=None)
|
||||
continue
|
||||
elif "." not in filename:
|
||||
filename += ".rpy"
|
||||
|
||||
path = os.path.join(project.current.gamedir, filename)
|
||||
dir = os.path.dirname(path)
|
||||
|
||||
if os.path.exists(path):
|
||||
interface.error(_("The file already exists."), label=None)
|
||||
continue
|
||||
|
||||
break
|
||||
|
||||
try:
|
||||
os.makedirs(dir)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
contents = u"\uFEFF"
|
||||
contents += _("# Ren'Py automatically loads all script files ending with .rpy. To use this\n# file, define a label and jump to it from another file.\n")
|
||||
|
||||
with open(path, "wb") as f:
|
||||
f.write(contents.encode("utf-8"))
|
||||
|
||||
jump navigation_refresh
|
||||
@@ -1,735 +0,0 @@
|
||||
# Copyright 2004-2025 Tom Rothamel <pytom@bishoujo.us>
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person
|
||||
# obtaining a copy of this software and associated documentation files
|
||||
# (the "Software"), to deal in the Software without restriction,
|
||||
# including without limitation the rights to use, copy, modify, merge,
|
||||
# publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
# and to permit persons to whom the Software is furnished to do so,
|
||||
# subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
default persistent.android_bundle = False
|
||||
|
||||
init python:
|
||||
ANDROID_NO_RAPT = 0
|
||||
ANDROID_NO_JDK = 1
|
||||
ANDROID_NO_SDK = 2
|
||||
ANDROID_NO_KEY = 3
|
||||
ANDROID_NO_BUNDLE_KEY = 4
|
||||
ANDROID_NO_CONFIG = 5
|
||||
ANDROID_NO_BUNDLE = 6
|
||||
ANDROID_OK = 7
|
||||
|
||||
JDK_REQUIREMENT = 42
|
||||
|
||||
NO_RAPT_TEXT = _("To build Android packages, please download RAPT, unzip it, and place it into the Ren'Py directory. Then restart the Ren'Py launcher.")
|
||||
NO_SDK_TEXT = _("RAPT has been installed, but you'll need to install the Android SDK before you can build Android packages. Choose Install SDK to do this.")
|
||||
NO_KEY_TEXT = _("RAPT has been installed, but a key hasn't been configured. Please generate new keys, or copy android.keystore and bundle.keystore to the base directory.")
|
||||
NO_CONFIG_TEXT = _("The current project has not been configured. Use \"Configure\" to configure it before building.")
|
||||
NO_BUNDLE_TEXT = _("Please select if you want a Play Bundle (for Google Play), or a Universal APK (for sideloading and other app stores).")
|
||||
OK_TEXT = _("Choose \"Build\" to build the current project, or attach an Android device and choose \"Build & Install\" to build and install it on the device.")
|
||||
|
||||
PHONE_TEXT = _("Attempts to emulate an Android phone.\n\nTouch input is emulated through the mouse, but only when the button is held down. Escape is mapped to the menu button, and PageUp is mapped to the back button.")
|
||||
TABLET_TEXT = _("Attempts to emulate an Android tablet.\n\nTouch input is emulated through the mouse, but only when the button is held down. Escape is mapped to the menu button, and PageUp is mapped to the back button.")
|
||||
OUYA_TEXT = _("Attempts to emulate a televison-based Android console.\n\nController input is mapped to the arrow keys, Enter is mapped to the select button, Escape is mapped to the menu button, and PageUp is mapped to the back button.")
|
||||
|
||||
INSTALL_SDK_TEXT = _("Downloads and installs the Android SDK and supporting packages.")
|
||||
GENERATE_KEYS_TEXT = _("Generates the keys required to sign the package.")
|
||||
CONFIGURE_TEXT = _("Configures the package name, version, and other information about this project.")
|
||||
BUILD_TEXT = _("Builds the Android package.")
|
||||
BUILD_AND_INSTALL_TEXT = _("Builds the Android package, and installs it on an Android device connected to your computer.")
|
||||
BUILD_INSTALL_AND_LAUNCH_TEXT = _("Builds the Android package, installs it on an Android device connected to your computer, then launches the app on your device.")
|
||||
|
||||
LOGCAT_TEXT = _("Retrieves the log from the Android device and writes it to a file.")
|
||||
LIST_DEVICES_TEXT = _("Lists the connected devices.")
|
||||
PAIR_TEXT = _("Pairs with a device over Wi-Fi, on Android 11+.")
|
||||
CONNECT_TEXT = _("Connects to a device over Wi-Fi, on Android 11+.")
|
||||
DISCONNECT_TEXT = _("Disconnects a device connected over Wi-Fi.")
|
||||
|
||||
CLEAN_TEXT = _("Removes Android temporary files.")
|
||||
|
||||
PLAY_BUNDLE_TEXT = _("Builds an Android App Bundle (ABB), intended to be uploaded to Google Play. This can include up to 2GB of data.")
|
||||
UNIVERSAL_APK_TEXT = _("Builds a Universal APK package, intended for sideloading and stores other than Google Play. This can include up to 2GB of data.")
|
||||
|
||||
import subprocess
|
||||
import re
|
||||
import os
|
||||
import json
|
||||
import glob
|
||||
|
||||
def find_rapt():
|
||||
|
||||
global RAPT_PATH
|
||||
|
||||
candidates = [ ]
|
||||
|
||||
RAPT_PATH = os.path.join(config.renpy_base, "rapt")
|
||||
|
||||
if os.path.isdir(RAPT_PATH) and check_hash_txt("rapt"):
|
||||
import sys
|
||||
sys.path.insert(0, os.path.join(RAPT_PATH, "buildlib"))
|
||||
else:
|
||||
RAPT_PATH = None
|
||||
|
||||
find_rapt()
|
||||
|
||||
import threading
|
||||
|
||||
if RAPT_PATH:
|
||||
import rapt
|
||||
import rapt.build
|
||||
import rapt.configure
|
||||
import rapt.install_sdk
|
||||
import rapt.plat
|
||||
import rapt.interface
|
||||
import rapt.keys
|
||||
|
||||
rapt.plat.renpy = True
|
||||
rapt.plat.translate = __
|
||||
|
||||
JDK_REQUIREMENT = rapt.plat.jdk_requirement
|
||||
|
||||
else:
|
||||
rapt = None
|
||||
|
||||
|
||||
def AndroidState():
|
||||
"""
|
||||
Determines the state of the android install, and returns it.
|
||||
"""
|
||||
|
||||
if RAPT_PATH is None:
|
||||
return ANDROID_NO_RAPT
|
||||
if not os.path.exists(rapt.plat.adb):
|
||||
return ANDROID_NO_SDK
|
||||
if not rapt.keys.keys_exist(project.current.path):
|
||||
return ANDROID_NO_KEY
|
||||
if not any([
|
||||
os.path.exists(os.path.join(project.current.path, "android.json")),
|
||||
os.path.exists(os.path.join(project.current.path, ".android.json"))
|
||||
]):
|
||||
return ANDROID_NO_CONFIG
|
||||
if persistent.android_bundle is None:
|
||||
return ANDROID_NO_BUNDLE
|
||||
|
||||
return ANDROID_OK
|
||||
|
||||
|
||||
def AndroidStateText(state):
|
||||
"""
|
||||
Returns text corresponding to the state.
|
||||
"""
|
||||
|
||||
if state == ANDROID_NO_RAPT:
|
||||
return NO_RAPT_TEXT
|
||||
if state == ANDROID_NO_SDK:
|
||||
return NO_SDK_TEXT
|
||||
if state == ANDROID_NO_KEY:
|
||||
return NO_KEY_TEXT
|
||||
if state == ANDROID_NO_CONFIG:
|
||||
return NO_CONFIG_TEXT
|
||||
if state == ANDROID_NO_BUNDLE:
|
||||
return NO_BUNDLE_TEXT
|
||||
if state == ANDROID_OK:
|
||||
return OK_TEXT
|
||||
|
||||
def AndroidIfState(state, needed, action):
|
||||
"""
|
||||
If `state` is `needed` or better, `action` is returned. Otherwise,
|
||||
returns None, disabling the button.
|
||||
"""
|
||||
|
||||
if state >= needed:
|
||||
return action
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
class AndroidBuild(Action):
|
||||
"""
|
||||
Activates an Android build process.
|
||||
"""
|
||||
|
||||
def __init__(self, label):
|
||||
self.label = label
|
||||
|
||||
def __call__(self):
|
||||
renpy.jump(self.label)
|
||||
|
||||
def update_android_json(p, gui):
|
||||
"""
|
||||
Updates .android.json to include the google play information.
|
||||
|
||||
`p`
|
||||
The project to update json for.
|
||||
"""
|
||||
|
||||
p.update_dump(True, gui=gui)
|
||||
|
||||
build = p.dump["build"]
|
||||
|
||||
c = rapt.configure.Configuration(p.path)
|
||||
|
||||
if "google_play_key" in build:
|
||||
c.google_play_key = build["google_play_key"]
|
||||
else:
|
||||
c.google_play_key = None
|
||||
|
||||
if "google_play_salt" in build:
|
||||
|
||||
if len(build["google_play_salt"]) != 20:
|
||||
raise Exception("build.google_play_salt must be exactly 20 bytes long.")
|
||||
|
||||
c.google_play_salt = ", ".join(str(i) for i in build["google_play_salt"])
|
||||
else:
|
||||
c.google_play_salt = None
|
||||
|
||||
c.save(p.path)
|
||||
|
||||
|
||||
def android_build(p=None, gui=True, bundle=False, install=False, launch=False, destination=None, opendir=False, packages=None):
|
||||
"""
|
||||
This actually builds the package.
|
||||
"""
|
||||
|
||||
if p is None:
|
||||
p = project.current
|
||||
|
||||
update_android_json(p, gui)
|
||||
|
||||
dist = p.temp_filename("android.dist")
|
||||
|
||||
if os.path.exists(dist):
|
||||
shutil.rmtree(dist)
|
||||
|
||||
if gui:
|
||||
reporter = distribute.GuiReporter()
|
||||
rapt_interface = MobileInterface("android")
|
||||
else:
|
||||
reporter = distribute.TextReporter()
|
||||
rapt_interface = rapt.interface.Interface()
|
||||
|
||||
if not packages:
|
||||
packages = ['android']
|
||||
|
||||
distribute.Distributor(p,
|
||||
reporter=reporter,
|
||||
packages=packages,
|
||||
build_update=False,
|
||||
noarchive=True,
|
||||
packagedest=dist,
|
||||
report_success=False,
|
||||
)
|
||||
|
||||
def finished(files, destination=destination):
|
||||
|
||||
source_dir = rapt.plat.path("bin")
|
||||
|
||||
try:
|
||||
|
||||
destination_dir = destination
|
||||
|
||||
# Use default destination if not configured
|
||||
if gui and destination is None:
|
||||
build = p.dump['build']
|
||||
destination = build["destination"]
|
||||
|
||||
if destination != "-dists":
|
||||
parent = os.path.dirname(p.path)
|
||||
destination_dir = os.path.join(parent, destination)
|
||||
|
||||
except Exception:
|
||||
destination_dir = None
|
||||
|
||||
dir_to_open = source_dir
|
||||
|
||||
if destination_dir is not None:
|
||||
|
||||
reporter.info(_("Copying Android files to distributions directory."))
|
||||
|
||||
try:
|
||||
os.makedirs(destination_dir)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
|
||||
for i in files:
|
||||
shutil.copy(i, renpy.fsencode(destination_dir))
|
||||
|
||||
dir_to_open = destination_dir
|
||||
|
||||
except Exception:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
pass
|
||||
|
||||
if opendir:
|
||||
dir_to_open = os.path.join(p.path, dir_to_open)
|
||||
renpy.run(store.OpenDirectory(dir_to_open, absolute=True))
|
||||
|
||||
|
||||
with interface.nolinks():
|
||||
rapt.build.build(
|
||||
rapt_interface,
|
||||
dist,
|
||||
p.path,
|
||||
bundle=bundle,
|
||||
install=install,
|
||||
launch=launch,
|
||||
finished=finished,
|
||||
permissions=p.dump['build']['android_permissions'],
|
||||
version=p.dump['build']['version'])
|
||||
|
||||
|
||||
def android_build_argument(cmd):
|
||||
return cmd + project.current.data["android_build"]
|
||||
|
||||
|
||||
|
||||
# The android support can stick unicode into os.environ. Fix that.
|
||||
init 100 python:
|
||||
for k, v in list(os.environ.items()):
|
||||
if not isinstance(v, str):
|
||||
os.environ[k] = renpy.fsencode(v)
|
||||
|
||||
screen android_process(interface):
|
||||
|
||||
zorder 100
|
||||
|
||||
default ft = FileTail(interface.filename)
|
||||
|
||||
text "[ft.text!q]":
|
||||
size 14
|
||||
color TEXT
|
||||
font gui.LIGHT_FONT
|
||||
xpos 75
|
||||
ypos 350
|
||||
|
||||
timer .1 action interface.check_process repeat True
|
||||
timer .2 action ft.update repeat True
|
||||
|
||||
|
||||
screen android:
|
||||
|
||||
default tt = Tooltip(None)
|
||||
$ state = AndroidState()
|
||||
|
||||
frame:
|
||||
style_group "l"
|
||||
style "l_root"
|
||||
|
||||
window:
|
||||
|
||||
has vbox
|
||||
|
||||
label _("Android: [project.current.display_name!q]")
|
||||
|
||||
add HALF_SPACER
|
||||
|
||||
hbox:
|
||||
|
||||
# Left side.
|
||||
frame:
|
||||
style "l_indent"
|
||||
xmaximum ONEHALF
|
||||
xfill True
|
||||
|
||||
has vbox
|
||||
|
||||
add SEPARATOR2
|
||||
|
||||
frame:
|
||||
style "l_indent"
|
||||
has vbox
|
||||
|
||||
text _("Emulation:")
|
||||
|
||||
add HALF_SPACER
|
||||
|
||||
frame style "l_indent":
|
||||
|
||||
has hbox:
|
||||
spacing 15
|
||||
|
||||
textbutton _("Phone"):
|
||||
action LaunchEmulator("touch", "small phone touch android mobile")
|
||||
hovered tt.Action(PHONE_TEXT)
|
||||
|
||||
textbutton _("Tablet"):
|
||||
action LaunchEmulator("touch", "medium tablet touch android mobile")
|
||||
hovered tt.Action(TABLET_TEXT)
|
||||
|
||||
textbutton _("Television"):
|
||||
action LaunchEmulator("tv", "small tv android mobile")
|
||||
hovered tt.Action(OUYA_TEXT)
|
||||
|
||||
|
||||
add SPACER
|
||||
add SEPARATOR2
|
||||
|
||||
frame:
|
||||
style "l_indent"
|
||||
has vbox
|
||||
|
||||
text _("Build:")
|
||||
|
||||
add HALF_SPACER
|
||||
|
||||
frame style "l_indent":
|
||||
|
||||
has vbox
|
||||
textbutton _("Install SDK"):
|
||||
action AndroidIfState(state, ANDROID_NO_SDK, Jump("android_installsdk"))
|
||||
hovered tt.Action(INSTALL_SDK_TEXT)
|
||||
|
||||
textbutton _("Generate Keys"):
|
||||
action AndroidIfState(state, ANDROID_NO_KEY, Jump("android_keys"))
|
||||
hovered tt.Action(GENERATE_KEYS_TEXT)
|
||||
|
||||
textbutton _("Configure"):
|
||||
action AndroidIfState(state, ANDROID_NO_KEY, Jump("android_configure"))
|
||||
hovered tt.Action(CONFIGURE_TEXT)
|
||||
|
||||
add SPACER
|
||||
|
||||
textbutton _("Play Bundle"):
|
||||
action SetField(persistent, "android_bundle", True)
|
||||
hovered tt.Action(PLAY_BUNDLE_TEXT)
|
||||
style "l_checkbox"
|
||||
|
||||
textbutton _("Universal APK"):
|
||||
action SetField(persistent, "android_bundle", False)
|
||||
hovered tt.Action(UNIVERSAL_APK_TEXT)
|
||||
style "l_checkbox"
|
||||
|
||||
add SPACER
|
||||
|
||||
textbutton _("Build Package"):
|
||||
action AndroidIfState(state, ANDROID_OK, AndroidBuild("android_build"))
|
||||
hovered tt.Action(BUILD_TEXT)
|
||||
|
||||
textbutton _("Build & Install"):
|
||||
action AndroidIfState(state, ANDROID_OK, AndroidBuild("android_build_and_install"))
|
||||
hovered tt.Action(BUILD_AND_INSTALL_TEXT)
|
||||
|
||||
textbutton _("Build, Install & Launch"):
|
||||
action AndroidIfState(state, ANDROID_OK, AndroidBuild("android_build_install_and_launch"))
|
||||
hovered tt.Action(BUILD_INSTALL_AND_LAUNCH_TEXT)
|
||||
|
||||
add SPACER
|
||||
|
||||
textbutton _("Force Recompile") action DataToggle("force_recompile") style "l_checkbox"
|
||||
|
||||
|
||||
# Right side.
|
||||
frame:
|
||||
style "l_indent"
|
||||
xmaximum ONEHALF
|
||||
xfill True
|
||||
|
||||
has vbox
|
||||
|
||||
add SEPARATOR2
|
||||
|
||||
frame:
|
||||
style "l_indent"
|
||||
has vbox
|
||||
|
||||
text _("Other:")
|
||||
|
||||
add HALF_SPACER
|
||||
|
||||
frame style "l_indent":
|
||||
|
||||
has vbox
|
||||
|
||||
textbutton _("Logcat"):
|
||||
action AndroidIfState(state, ANDROID_NO_KEY, Jump("logcat"))
|
||||
hovered tt.Action(LOGCAT_TEXT)
|
||||
|
||||
textbutton _("List Devices"):
|
||||
action AndroidIfState(state, ANDROID_NO_KEY, Jump("android_list_devices"))
|
||||
hovered tt.Action(LIST_DEVICES_TEXT)
|
||||
|
||||
textbutton _("Wi-Fi Debugging Pair"):
|
||||
action AndroidIfState(state, ANDROID_NO_KEY, Jump("android_pair"))
|
||||
hovered tt.Action(PAIR_TEXT)
|
||||
|
||||
textbutton _("Wi-Fi Debugging Connect"):
|
||||
action AndroidIfState(state, ANDROID_NO_KEY, Jump("android_connect"))
|
||||
hovered tt.Action(CONNECT_TEXT)
|
||||
|
||||
textbutton _("Wi-Fi Debugging Disconnect"):
|
||||
action AndroidIfState(state, ANDROID_NO_KEY, Jump("android_disconnect"))
|
||||
hovered tt.Action(DISCONNECT_TEXT)
|
||||
|
||||
textbutton _("Clean"):
|
||||
action AndroidIfState(state, ANDROID_NO_KEY, Jump("android_clean"))
|
||||
hovered tt.Action(CLEAN_TEXT)
|
||||
|
||||
add SPACER
|
||||
add SEPARATOR2
|
||||
|
||||
frame:
|
||||
style "l_indent"
|
||||
has vbox
|
||||
|
||||
add SPACER
|
||||
|
||||
if tt.value:
|
||||
text tt.value
|
||||
else:
|
||||
text AndroidStateText(state)
|
||||
|
||||
|
||||
textbutton _("Return") action Jump("front_page") style "l_left_button"
|
||||
|
||||
|
||||
label android:
|
||||
|
||||
if RAPT_PATH is None:
|
||||
$ interface.yesno(_("Before packaging Android apps, you'll need to download RAPT, the Ren'Py Android Packaging Tool. Would you like to download RAPT now?"), no=Jump("front_page"))
|
||||
$ add_dlc("rapt", restart=True)
|
||||
|
||||
call screen android
|
||||
|
||||
|
||||
label android_installsdk:
|
||||
|
||||
python:
|
||||
with interface.nolinks():
|
||||
rapt.install_sdk.install_sdk(MobileInterface("android"))
|
||||
|
||||
jump android
|
||||
|
||||
|
||||
label android_keys:
|
||||
|
||||
python:
|
||||
rapt.keys.generate_keys(MobileInterface("android"), project.current.path)
|
||||
|
||||
jump android
|
||||
|
||||
|
||||
label android_configure:
|
||||
|
||||
python:
|
||||
project.current.update_dump(force=True)
|
||||
|
||||
rapt.configure.configure(
|
||||
MobileInterface("android", edit=False),
|
||||
project.current.path,
|
||||
default_name=project.current.dump.get("name", None),
|
||||
default_version=project.current.dump.get("version", None))
|
||||
|
||||
jump android
|
||||
|
||||
|
||||
label android_build:
|
||||
|
||||
$ android_build(bundle=persistent.android_bundle, opendir=True)
|
||||
|
||||
jump android
|
||||
|
||||
|
||||
label android_build_and_install:
|
||||
|
||||
$ android_build(bundle=persistent.android_bundle, install=True, opendir=True)
|
||||
|
||||
jump android
|
||||
|
||||
label android_build_install_and_launch:
|
||||
|
||||
$ android_build(bundle=persistent.android_bundle, install=True, launch=True, opendir=True)
|
||||
|
||||
jump android
|
||||
|
||||
label logcat:
|
||||
|
||||
python hide:
|
||||
|
||||
interface = MobileInterface("android", filename="logcat.txt")
|
||||
interface.info(_("Retrieving logcat information from device."))
|
||||
interface.call([ rapt.plat.adb, "logcat", "-d" ], cancel=True)
|
||||
interface.open_editor()
|
||||
|
||||
jump android
|
||||
|
||||
label android_list_devices:
|
||||
|
||||
python hide:
|
||||
cc = ConsoleCommand()
|
||||
cc.add(rapt.plat.adb, "devices")
|
||||
cc.run()
|
||||
|
||||
jump android
|
||||
|
||||
|
||||
label android_pair:
|
||||
|
||||
python hide:
|
||||
pairing_code = interface.input(
|
||||
_("Wi-Fi Pairing Code"),
|
||||
_("If supported, this can be found in 'Developer options', 'Wireless debugging', 'Pair device with pairing code'."),
|
||||
sanitize=False,
|
||||
cancel=Jump("android"),
|
||||
)
|
||||
|
||||
host = interface.input(
|
||||
_("Pairing Host & Port"),
|
||||
_("If supported, this can be found in 'Developer options', 'Wireless debugging', 'Pair device with pairing code'."),
|
||||
sanitize=False,
|
||||
cancel=Jump("android"),
|
||||
)
|
||||
|
||||
cc = ConsoleCommand()
|
||||
cc.add(rapt.plat.adb, "pair", host, pairing_code)
|
||||
cc.run()
|
||||
|
||||
jump android
|
||||
|
||||
label android_connect:
|
||||
|
||||
python hide:
|
||||
host = interface.input(
|
||||
_("IP Address & Port"),
|
||||
_("If supported, this can be found in 'Developer options', 'Wireless debugging'."),
|
||||
sanitize=False,
|
||||
cancel=Jump("android"),
|
||||
)
|
||||
|
||||
cc = ConsoleCommand()
|
||||
cc.add(rapt.plat.adb, "connect", host)
|
||||
cc.run()
|
||||
|
||||
jump android
|
||||
|
||||
label android_disconnect:
|
||||
|
||||
python hide:
|
||||
host = interface.input(
|
||||
_("IP Address & Port"),
|
||||
_("This can be found in 'List Devices'."),
|
||||
sanitize=False,
|
||||
cancel=Jump("android"),
|
||||
)
|
||||
|
||||
cc = ConsoleCommand()
|
||||
cc.add(rapt.plat.adb, "disconnect", host)
|
||||
cc.run()
|
||||
|
||||
jump android
|
||||
|
||||
label android_clean:
|
||||
|
||||
python hide:
|
||||
import shutil
|
||||
import time
|
||||
|
||||
interface = MobileInterface("android")
|
||||
interface.info(_("Cleaning up Android project."))
|
||||
|
||||
# Get the android json file, for the update_always key.
|
||||
try:
|
||||
filename = os.path.join(project.current.path, ".android.json")
|
||||
with open(filename, "rb") as f:
|
||||
android_json = json.load(f)
|
||||
except Exception:
|
||||
android_json = {}
|
||||
|
||||
# Clean up the files.
|
||||
def clean(path):
|
||||
if os.path.exists(path):
|
||||
shutil.rmtree(path)
|
||||
|
||||
if android_json.get("update_always", True):
|
||||
|
||||
try:
|
||||
with open(rapt.plat.path("project/local.properties"), "r") as f:
|
||||
local_properties = f.read()
|
||||
except Exception:
|
||||
local_properties = None
|
||||
|
||||
try:
|
||||
with open(rapt.plat.path("project/bundle.properties"), "r") as f:
|
||||
bundle_properties = f.read()
|
||||
except Exception:
|
||||
bundle_properties = None
|
||||
|
||||
try:
|
||||
with open(rapt.plat.path("project/gradle.properties"), "r") as f:
|
||||
gradle_properties = f.read()
|
||||
except Exception:
|
||||
gradle_properties = None
|
||||
|
||||
clean(rapt.plat.path("project"))
|
||||
|
||||
if local_properties or bundle_properties or gradle_properties:
|
||||
|
||||
os.mkdir(rapt.plat.path("project"))
|
||||
|
||||
if local_properties:
|
||||
|
||||
with open(rapt.plat.path("project/local.properties"), "w") as f:
|
||||
f.write(local_properties)
|
||||
|
||||
if bundle_properties:
|
||||
|
||||
with open(rapt.plat.path("project/bundle.properties"), "w") as f:
|
||||
f.write(bundle_properties)
|
||||
|
||||
if gradle_properties:
|
||||
|
||||
with open(rapt.plat.path("project/gradle.properties"), "w") as f:
|
||||
f.write(gradle_properties)
|
||||
|
||||
clean(rapt.plat.path("bin"))
|
||||
clean(project.current.temp_filename("android.dist"))
|
||||
|
||||
# This can go really fast, so pause so it looks like something is happening.
|
||||
time.sleep(.5)
|
||||
|
||||
|
||||
jump android
|
||||
|
||||
|
||||
init python:
|
||||
|
||||
def android_build_command():
|
||||
ap = renpy.arguments.ArgumentParser()
|
||||
ap.add_argument("android_project", help="The path to the project directory.")
|
||||
ap.add_argument("--bundle", action="store_true", help="Builds an android app bundle.")
|
||||
ap.add_argument("--install", action="store_true", help="Installs the app on a device.")
|
||||
ap.add_argument("--launch", action="store_true", help="Launches the app after build and install complete. Implies --install.")
|
||||
ap.add_argument("--destination", "--dest", default=None, action="store", help="The directory where the packaged files should be placed.")
|
||||
ap.add_argument("--package", action="append", help="If given, a package to build. Defaults to building the 'android' package.")
|
||||
|
||||
args = ap.parse_args()
|
||||
|
||||
if args.launch:
|
||||
args.install = True
|
||||
|
||||
p = project.Project(args.android_project)
|
||||
|
||||
if args.package:
|
||||
packages = args.package
|
||||
else:
|
||||
packages = None
|
||||
|
||||
android_build(p=p, gui=False, bundle=args.bundle, install=args.install, launch=args.launch, destination=args.destination, packages=packages)
|
||||
|
||||
return False
|
||||
|
||||
renpy.arguments.register_command("android_build", android_build_command)
|
||||
@@ -1,67 +0,0 @@
|
||||
|
||||
# This file contains strings used by RAPT, so the Ren'Py translation framework
|
||||
# can find them. It's automatically generated by rapt/update_translations.py, and
|
||||
# hence should not be changed by hand.
|
||||
|
||||
init python hide:
|
||||
__("{} is not a directory.")
|
||||
__("{} does not contain a Ren'Py game.")
|
||||
__("{} does not contain a Ren'Py game.")
|
||||
__("Run configure before attempting to build the app.")
|
||||
__("Updating project.")
|
||||
__("Creating assets directory.")
|
||||
__("Packaging internal data.")
|
||||
__("I'm using Gradle to build the package.")
|
||||
__("The build seems to have failed.")
|
||||
__("I'm installing the bundle.")
|
||||
__("Installing the bundle appears to have failed.")
|
||||
__("Launching app.")
|
||||
__("Launching the app appears to have failed.")
|
||||
__("The build seems to have succeeded.")
|
||||
__("What is the full name of your application? This name will appear in the list of installed applications.")
|
||||
__("What is the short name of your application? This name will be used in the launcher, and for application shortcuts.")
|
||||
__("What is the name of the package?\n\nThis is usually of the form com.domain.program or com.domain.email.program. It may only contain ASCII letters and dots. It must contain at least one dot.")
|
||||
__("The package name may not be empty.")
|
||||
__("The package name may not contain spaces.")
|
||||
__("The package name must contain at least one dot.")
|
||||
__("The package name may not contain two dots in a row, or begin or end with a dot.")
|
||||
__("Each part of the package name must start with a letter, and contain only letters, numbers, and underscores.")
|
||||
__("{} is a Java keyword, and can't be used as part of a package name.")
|
||||
__("How much RAM (in GB) do you want to allocate to Gradle?\nThis must be a positive integer number.")
|
||||
__("The RAM size must contain only numbers and be positive.")
|
||||
__("How would you like your application to be displayed?")
|
||||
__("In landscape orientation.")
|
||||
__("In portrait orientation.")
|
||||
__("In the user's preferred orientation.")
|
||||
__("Which app store would you like to support in-app purchasing through?")
|
||||
__("Google Play.")
|
||||
__("Amazon App Store.")
|
||||
__("Both, in one app.")
|
||||
__("Neither.")
|
||||
__("Do you want to automatically update the Java source code?")
|
||||
__("Yes. This is the best choice for most projects.")
|
||||
__("No. This may require manual updates when Ren'Py or the project configuration changes.")
|
||||
__("Unknown configuration variable: {}")
|
||||
__("I'm compiling a short test program, to see if you have a working JDK on your system.")
|
||||
__("I was unable to use javac to compile a test file. If you haven't installed the Java Development Kit yet, please download it from:\n\n{a=https://adoptium.net}https://adoptium.net/{/a}\n\nThe JDK is different from the JRE, so it's possible you have Java without having the JDK. Please install JDK [JDK_REQUIREMENT], and add it to your PATH.\n\nWithout a working JDK, I can't continue.")
|
||||
__("The version of Java on your computer does not appear to be JDK [JDK_REQUIREMENT], which is required to build Android apps. If you need to install a newer JDK, you can download it from:\n\n{a=https://adoptium.net/}https://adoptium.net/{/a}, and add it to your PATH.\n\nYou can also set the JAVA_HOME environment variable to use a different version of Java.")
|
||||
__("The JDK is present and working. Good!")
|
||||
__("The Android SDK has already been unpacked.")
|
||||
__("Do you accept the Android SDK Terms and Conditions?")
|
||||
__("I'm downloading the Android SDK. This might take a while.")
|
||||
__("I'm extracting the Android SDK.")
|
||||
__("I've finished unpacking the Android SDK.")
|
||||
__("I'm about to download and install the required Android packages. This might take a while.")
|
||||
__("I was unable to accept the Android licenses.")
|
||||
__("I was unable to accept the Android licenses.")
|
||||
__("I was unable to install the required Android packages.")
|
||||
__("I've finished installing the required Android packages.")
|
||||
__("It looks like you're ready to start packaging games.")
|
||||
__("Please enter your name or the name of your organization.")
|
||||
__("I found an android.keystore file in the rapt directory. Do you want to use this file?")
|
||||
__("I will create the key in the android.keystore file.\n\nYou need to back this file up. If you lose it, you will not be able to upgrade your application.\n\nYou also need to keep the key safe. If evil people get this file, they could make fake versions of your application, and potentially steal your users' data.\n\nWill you make a backup of android.keystore, and keep it in a safe place?")
|
||||
__("\n\nSaying 'No' will prevent key creation.")
|
||||
__("Could not create android.keystore. Is keytool in your path?")
|
||||
__("I've finished creating android.keystore. Please back it up, and keep it in a safe place.")
|
||||
__("I found a bundle.keystore file in the rapt directory. Do you want to use this file?")
|
||||
__("I've opened the directory containing android.keystore and bundle.keystore. Please back them up, and keep them in a safe place.")
|
||||
@@ -1,86 +0,0 @@
|
||||
# Copyright 2004-2025 Tom Rothamel <pytom@bishoujo.us>
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person
|
||||
# obtaining a copy of this software and associated documentation files
|
||||
# (the "Software"), to deal in the Software without restriction,
|
||||
# including without limitation the rights to use, copy, modify, merge,
|
||||
# publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
# and to permit persons to whom the Software is furnished to do so,
|
||||
# subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
# Ren'Py archiver. This builds a Ren'Py archive file, and the
|
||||
# associated index file. These files are really easy to
|
||||
# reverse-engineer, but are probably better than nothing.
|
||||
|
||||
init python in archiver:
|
||||
|
||||
import sys
|
||||
import random
|
||||
import glob
|
||||
import zlib
|
||||
|
||||
from pickle import dumps, HIGHEST_PROTOCOL
|
||||
|
||||
|
||||
class Archive(object):
|
||||
"""
|
||||
Adds files from disk to a rpa archive.
|
||||
"""
|
||||
|
||||
def __init__(self, filename):
|
||||
|
||||
# The archive file.
|
||||
self.f = open(filename, "wb")
|
||||
|
||||
# The index to the file.
|
||||
self.index = _dict()
|
||||
|
||||
# A fixed key minimizes difference between archive versions.
|
||||
self.key = 0x42424242
|
||||
|
||||
padding = b"RPA-3.0 XXXXXXXXXXXXXXXX XXXXXXXX\n"
|
||||
self.f.write(padding)
|
||||
|
||||
def add(self, name, path):
|
||||
"""
|
||||
Adds a file to the archive.
|
||||
"""
|
||||
|
||||
self.index[name] = _list()
|
||||
|
||||
with open(path, "rb") as df:
|
||||
data = df.read()
|
||||
dlen = len(data)
|
||||
|
||||
# Pad.
|
||||
padding = b"Made with Ren'Py."
|
||||
self.f.write(padding)
|
||||
|
||||
offset = self.f.tell()
|
||||
|
||||
self.f.write(data)
|
||||
|
||||
self.index[name].append((offset ^ self.key, dlen ^ self.key, b""))
|
||||
|
||||
def close(self):
|
||||
|
||||
indexoff = self.f.tell()
|
||||
|
||||
self.f.write(zlib.compress(dumps(self.index, HIGHEST_PROTOCOL)))
|
||||
|
||||
self.f.seek(0)
|
||||
self.f.write(b"RPA-3.0 %016x %08x\n" % (indexoff, self.key))
|
||||
|
||||
self.f.close()
|
||||
|
||||
@@ -1,418 +0,0 @@
|
||||
# Copyright 2004-2025 Tom Rothamel <pytom@bishoujo.us>
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person
|
||||
# obtaining a copy of this software and associated documentation files
|
||||
# (the "Software"), to deal in the Software without restriction,
|
||||
# including without limitation the rights to use, copy, modify, merge,
|
||||
# publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
# and to permit persons to whom the Software is furnished to do so,
|
||||
# subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
# http://www.csn.ul.ie/~caolan/publink/winresdump/winresdump/doc/pefile.html
|
||||
# Contains a reasonable description of the format.
|
||||
|
||||
from __future__ import print_function
|
||||
from renpy.compat import bchr, chr, str, PY2
|
||||
|
||||
import struct
|
||||
import sys
|
||||
import array
|
||||
import pefile
|
||||
|
||||
|
||||
# This class performs various operations on memory-loaded binary files,
|
||||
# including modifications.
|
||||
|
||||
class BinFile(object):
|
||||
|
||||
def set_u32(self, addr, value):
|
||||
self.a[addr + 0] = (value >> 0) & 0xff
|
||||
self.a[addr + 1] = (value >> 8) & 0xff
|
||||
self.a[addr + 2] = (value >> 16) & 0xff
|
||||
self.a[addr + 3] = (value >> 24) & 0xff
|
||||
|
||||
def u32(self):
|
||||
addr = self.addr
|
||||
rv = self.a[addr]
|
||||
rv |= self.a[addr + 1] << 8
|
||||
rv |= self.a[addr + 2] << 16
|
||||
rv |= self.a[addr + 3] << 24
|
||||
self.addr += 4
|
||||
return rv
|
||||
|
||||
def u16(self):
|
||||
addr = self.addr
|
||||
rv = self.a[addr]
|
||||
rv |= self.a[addr + 1] << 8
|
||||
self.addr += 2
|
||||
return rv
|
||||
|
||||
def u8(self):
|
||||
rv = self.a[self.addr]
|
||||
self.addr += 1
|
||||
return rv
|
||||
|
||||
def name(self):
|
||||
c = self.u16()
|
||||
|
||||
rv = u""
|
||||
for _i in range(c):
|
||||
rv += chr(self.u16())
|
||||
|
||||
return rv
|
||||
|
||||
def seek(self, addr):
|
||||
self.addr = addr
|
||||
|
||||
def tostring(self):
|
||||
return self.a.tobytes()
|
||||
|
||||
def substring(self, start, len): # @ReservedAssignment
|
||||
return self.a[start:start + len].tobytes()
|
||||
|
||||
def __init__(self, data):
|
||||
self.a = array.array('B')
|
||||
self.a.frombytes(data)
|
||||
|
||||
##############################################################################
|
||||
# These functions parse data out of the file. In these functions, offset is
|
||||
# relative to the start of the file.
|
||||
|
||||
|
||||
# The virtual address of the resource segment.
|
||||
resource_virtual = 0
|
||||
|
||||
# This parses a data block out of the resources.
|
||||
|
||||
|
||||
def parse_data(bf, offset):
|
||||
bf.seek(offset)
|
||||
data_offset = bf.u32()
|
||||
data_len = bf.u32()
|
||||
code_page = bf.u32()
|
||||
bf.u32()
|
||||
|
||||
l = [ ]
|
||||
|
||||
bf.seek(data_offset - resource_virtual)
|
||||
for _i in range(data_len):
|
||||
l.append(bchr(bf.u8()))
|
||||
|
||||
return (code_page, b"".join(l))
|
||||
|
||||
# This parses a resource directory.
|
||||
|
||||
|
||||
def parse_directory(bf, offset):
|
||||
|
||||
bf.seek(offset)
|
||||
char = bf.u32() # @UnusedVariable
|
||||
timedate = bf.u32() # @UnusedVariable
|
||||
major = bf.u16() # @UnusedVariable
|
||||
minor = bf.u16() # @UnusedVariable
|
||||
n_named = bf.u16()
|
||||
n_id = bf.u16()
|
||||
|
||||
entries = [ ]
|
||||
|
||||
for _i in range(n_named + n_id):
|
||||
entries.append((bf.u32(), bf.u32()))
|
||||
|
||||
rv = { }
|
||||
|
||||
for name, value in entries:
|
||||
|
||||
if name & 0x80000000:
|
||||
bf.seek((name & 0x7fffffff))
|
||||
name = bf.name()
|
||||
|
||||
if value & 0x80000000:
|
||||
value = parse_directory(bf, value & 0x7fffffff)
|
||||
else:
|
||||
value = parse_data(bf, value)
|
||||
|
||||
rv[name] = value
|
||||
|
||||
return rv
|
||||
|
||||
|
||||
##############################################################################
|
||||
# This utility function displays the tree of resources that have been loaded.
|
||||
def show_resources(d, prefix):
|
||||
|
||||
if not isinstance(d, dict):
|
||||
print(prefix, "Codepage", d[0], "length", len(d[1]))
|
||||
return
|
||||
|
||||
for k in d:
|
||||
print(prefix, k)
|
||||
show_resources(d[k], prefix + " ")
|
||||
|
||||
##############################################################################
|
||||
# These functions repack the resources into a new resource segment. Here,
|
||||
# the offset is relative to the start of the resource segment.
|
||||
|
||||
|
||||
class Packer(object):
|
||||
|
||||
def pack(self, d):
|
||||
self.data = b""
|
||||
self.data_offset = 0
|
||||
|
||||
self.entries = b""
|
||||
self.entries_offset = 0
|
||||
|
||||
head = self.pack_dict(d, 0)
|
||||
|
||||
self.data = b""
|
||||
self.data_offset = len(head) + len(self.entries)
|
||||
|
||||
self.entries = b""
|
||||
self.entries_offset = len(head)
|
||||
|
||||
return self.pack_dict(d, 0) + self.entries + self.data
|
||||
|
||||
def pack_name(self, s):
|
||||
rv = self.data_offset + len(self.data)
|
||||
|
||||
l = len(s)
|
||||
s = s.encode("utf-16le")
|
||||
self.data += struct.pack("<H", l) + s + b"\0\0"
|
||||
|
||||
return rv
|
||||
|
||||
def pack_tuple(self, t):
|
||||
codepage, data = t
|
||||
|
||||
rv = len(self.entries) + self.entries_offset
|
||||
|
||||
if len(self.data) % 2:
|
||||
self.data += b"P"
|
||||
|
||||
daddr = len(self.data) + self.data_offset
|
||||
|
||||
self.entries += struct.pack("<IIII", daddr + resource_virtual, len(data), codepage, 0)
|
||||
self.data += data
|
||||
|
||||
# if len(self.data) % 1 == 1:
|
||||
# self.data += 'P'
|
||||
|
||||
return rv
|
||||
|
||||
def pack_dict(self, d, offset):
|
||||
name_entries = sorted((a, b) for a, b in d.items() if isinstance(a, str))
|
||||
id_entries = sorted((a, b) for a, b in d.items() if isinstance(a, int))
|
||||
|
||||
rv = struct.pack("<IIHHHH", 0, 0, 4, 0, len(name_entries), len(id_entries))
|
||||
|
||||
offset += len(rv) + (len(name_entries) + len(id_entries)) * 8
|
||||
|
||||
rest = b""
|
||||
|
||||
for (name, value) in name_entries + id_entries:
|
||||
if isinstance(name, str):
|
||||
name = 0x80000000 | self.pack_name(name)
|
||||
|
||||
if isinstance(value, dict):
|
||||
addr = offset | 0x80000000
|
||||
packed = self.pack_dict(value, offset)
|
||||
offset += len(packed)
|
||||
rest += packed
|
||||
else:
|
||||
addr = self.pack_tuple(value)
|
||||
|
||||
rv += struct.pack("<II", name, addr)
|
||||
|
||||
return rv + rest
|
||||
|
||||
##############################################################################
|
||||
# This loads in an icon file, and returns a dictionary that is suitable for
|
||||
# use in the resources of an exe file.
|
||||
|
||||
|
||||
def load_icon(fn):
|
||||
f = BinFile(open(fn, "rb").read())
|
||||
|
||||
f.seek(0)
|
||||
f.u16()
|
||||
f.u16()
|
||||
count = f.u16()
|
||||
|
||||
rv = { }
|
||||
rv[3] = { }
|
||||
|
||||
group = struct.pack("HHH", 0, 1, count)
|
||||
|
||||
for i in range(count):
|
||||
width = f.u8()
|
||||
height = f.u8()
|
||||
colors = f.u8()
|
||||
reserved = f.u8()
|
||||
planes = f.u16()
|
||||
bpp = f.u16()
|
||||
size = f.u32()
|
||||
offset = f.u32()
|
||||
|
||||
addr = f.addr
|
||||
f.seek(offset + 16)
|
||||
if not f.u32():
|
||||
f.set_u32(offset + 20, 0)
|
||||
|
||||
#rv[3][i + 1] = { 0 : (1252, f.substring(offset, size)) }
|
||||
#I do not have justification for the 0x0409 entry ID, other than copying what other files do. Same for codepage of 0
|
||||
rv[3][i + 1] = { 1033 : (0, f.substring(offset, size)) }
|
||||
|
||||
group += struct.pack("BBBBHHIH", width, height, colors, reserved,
|
||||
planes, bpp, size, i + 1)
|
||||
|
||||
f.seek(addr)
|
||||
|
||||
#rv[14] = { 1 : { 0 : (1252, group) } }
|
||||
rv[14] = { 1 : { 1033 : (0, group) } }
|
||||
|
||||
return rv
|
||||
|
||||
|
||||
##############################################################################
|
||||
# This is the main function that should be called externally, that copies over
|
||||
# the icons.
|
||||
def change_icons(oldexe, icofn):
|
||||
global resource_virtual
|
||||
|
||||
pe = pefile.PE(oldexe)
|
||||
|
||||
for s in pe.sections:
|
||||
if s.Name == b".rsrc\0\0\0":
|
||||
rsrc_section = s
|
||||
break
|
||||
else:
|
||||
raise Exception("Couldn't find resource section.")
|
||||
|
||||
base = rsrc_section.PointerToRawData
|
||||
resource_virtual = rsrc_section.VirtualAddress
|
||||
|
||||
physize = rsrc_section.SizeOfRawData
|
||||
virsize = rsrc_section.Misc_VirtualSize
|
||||
|
||||
f = open(oldexe, "rb")
|
||||
basedata = f.read(base)
|
||||
data = f.read(physize)
|
||||
|
||||
#Symbol table, I could not understand so well
|
||||
# Some analysis and the little I could find on how the symbol tables were layed out show that the PE table is in two sections. One is
|
||||
# a list of NumberOfSymbols*(18 bytes) symbol entries, followed immediately by a string table who's length is specified in the 4 bytes following the symbol structure list
|
||||
#Again, the information *seems* to indicate that this will always follow the entire block of all sections (so be at the end of the file)
|
||||
|
||||
|
||||
######
|
||||
# As a related note, apparently this flag has been deprecated, but appears to be set (I think ..) in the renpy.exe that is used to build from..
|
||||
# no idea of a consequence, just an observation
|
||||
# https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#characteristics , see "IMAGE_FILE_LINE_NUMS_STRIPPED" :
|
||||
# COFF line numbers have been removed. This flag is deprecated and should be zero.
|
||||
# In renpy.exe, this 2-byte flag is @ 0x096 and 0x097 in little-endian
|
||||
######
|
||||
|
||||
f.close()
|
||||
|
||||
bf = BinFile(data)
|
||||
|
||||
resources = parse_directory(bf, 0)
|
||||
# show_resources(resources, "")
|
||||
resources.update(load_icon(icofn))
|
||||
# show_resources(resources, "")
|
||||
|
||||
rsrc = Packer().pack(resources)
|
||||
|
||||
newExactSize = len(rsrc)
|
||||
|
||||
#From note about flags above regarding image flags. These two should be 0
|
||||
pe.FILE_HEADER.Characteristics = pe.FILE_HEADER.Characteristics & (~pefile.IMAGE_CHARACTERISTICS["IMAGE_FILE_LINE_NUMS_STRIPPED"])
|
||||
pe.FILE_HEADER.Characteristics = pe.FILE_HEADER.Characteristics & (~pefile.IMAGE_CHARACTERISTICS["IMAGE_FILE_LOCAL_SYMS_STRIPPED"])
|
||||
|
||||
#Per docs, symbol table can just be removed
|
||||
#https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#coff-file-header-object-and-image see "PointerToSymbolTable" and "NumberOfSymbols "
|
||||
|
||||
pe.FILE_HEADER.PointerToSymbolTable = 0
|
||||
pe.FILE_HEADER.NumberOfSymbols = 0
|
||||
|
||||
#####
|
||||
# Alignment requirements as I understand them
|
||||
#
|
||||
# SectionAlignment (misnomer.. 'memory' alignment) = 4096
|
||||
# FileAlignment = 512
|
||||
#
|
||||
# Section, SizeOfRawData = multiple of FileAlignment (On Disk size)
|
||||
# Scetion, VirtualSize is NOT aligned to any specific number, and rwdata may be > than this due to its alignment requirement (In memory size)
|
||||
#
|
||||
# the resource section should be padded out to meet the alignment requirement of SizeOfRawData = multiple of FileAlignment (So, in the file, since on disk)
|
||||
#
|
||||
#####
|
||||
# RVA table virt address and size = that in the section header
|
||||
#
|
||||
#####
|
||||
# SizeOfImage must be aligned to SectionAlignment (memory alignment)
|
||||
# There is NO padding needed, the image size number simply needs to be aligned to >= the
|
||||
|
||||
memoryalignment = pe.OPTIONAL_HEADER.SectionAlignment
|
||||
filealignment = pe.OPTIONAL_HEADER.FileAlignment
|
||||
|
||||
rsrc_section.Misc_VirtualSize = newExactSize
|
||||
rsrc_section.Misc_PhysicalAddress = newExactSize
|
||||
rsrc_section.Misc = newExactSize
|
||||
|
||||
#Size of resource section (and pointer, but we're not changing anything 'above' resource) is the same as virtual address in the section table
|
||||
# https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#optional-header-data-directories-image-only
|
||||
pe.OPTIONAL_HEADER.DATA_DIRECTORY[2].Size = rsrc_section.Misc_VirtualSize
|
||||
|
||||
|
||||
#Alignment for raw data
|
||||
if len(rsrc) % filealignment:
|
||||
pad = filealignment - (len(rsrc) % filealignment)
|
||||
padding = b"\0" * (pad)
|
||||
padding = padding[:pad]
|
||||
rsrc += padding
|
||||
rsrc_section.SizeOfRawData = len(rsrc)
|
||||
|
||||
|
||||
#Image size is the memory size of all sections + all headers, padded to memory alignment.
|
||||
#There's already a header size, and this isn't changing headers, so just add.
|
||||
#imageSize = pe.OPTIONAL_HEADER.ImageBase + pe.OPTIONAL_HEADER.SizeOfHeaders
|
||||
imageSize = pe.OPTIONAL_HEADER.SizeOfHeaders
|
||||
for s in pe.sections:
|
||||
sectionSize = s.Misc_VirtualSize
|
||||
#Align every section, so add padding to size
|
||||
pad = 0
|
||||
if sectionSize % memoryalignment:
|
||||
pad = memoryalignment - (sectionSize % memoryalignment)
|
||||
imageSize += (sectionSize + pad)
|
||||
pad = 0
|
||||
if imageSize % memoryalignment:
|
||||
pad = memoryalignment - (imageSize % memoryalignment)
|
||||
pe.OPTIONAL_HEADER.SizeOfImage = imageSize + pad
|
||||
|
||||
|
||||
#The symbol table is simply left off. Its size DOES factor into ImageSize, but we didn't calculate it above, so fine
|
||||
#Correctly checksum the file. The entire file is involved in the calculation, so a new PE object must be generated for the calculation to work against
|
||||
newFile = pe.write()[:base] + rsrc
|
||||
newpe = pefile.PE(data=bytes(newFile))
|
||||
newpe.OPTIONAL_HEADER.CheckSum = newpe.generate_checksum()
|
||||
|
||||
return bytes(newpe.write())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
f = open(sys.argv[3], "wb")
|
||||
f.write(change_icons(sys.argv[1], sys.argv[2]))
|
||||
f.close()
|
||||
@@ -1,84 +0,0 @@
|
||||
# Copyright 2004-2025 Tom Rothamel <pytom@bishoujo.us>
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person
|
||||
# obtaining a copy of this software and associated documentation files
|
||||
# (the "Software"), to deal in the Software without restriction,
|
||||
# including without limitation the rights to use, copy, modify, merge,
|
||||
# publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
# and to permit persons to whom the Software is furnished to do so,
|
||||
# subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
init python:
|
||||
|
||||
try:
|
||||
import _renpytfd
|
||||
except Exception:
|
||||
_renpytfd = None
|
||||
|
||||
def directory_is_writable(path):
|
||||
test = os.path.join(path, "renpy test do not use")
|
||||
|
||||
try:
|
||||
if os.path.isdir(test):
|
||||
os.rmdir(test)
|
||||
|
||||
os.mkdir(test)
|
||||
os.rmdir(test)
|
||||
|
||||
return True
|
||||
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def choose_directory(default_path):
|
||||
"""
|
||||
Pops up a directory chooser.
|
||||
|
||||
`path`
|
||||
The directory that is selected by default. If None, config.renpy_base
|
||||
is selected.
|
||||
|
||||
Returns a (path, is_default) tuple, where path is the chosen directory,
|
||||
and is_default is true if and only if it was chosen by default mechanism
|
||||
rather than user choice.
|
||||
"""
|
||||
|
||||
if _renpytfd:
|
||||
path = _renpytfd.selectFolderDialog(__("Select Projects Directory"), default_path)
|
||||
else:
|
||||
path = None
|
||||
|
||||
if default_path is None:
|
||||
try:
|
||||
default_path = os.path.dirname(os.path.abspath(config.renpy_base))
|
||||
except Exception:
|
||||
default_path = os.path.abspath(config.renpy_base)
|
||||
|
||||
# Path being None or "" means nothing was selected.
|
||||
if not path:
|
||||
|
||||
if default_path is None or not os.path.isdir(default_path) or not directory_is_writable(default_path):
|
||||
interface.error(_("No directory was selected, but one is required."))
|
||||
|
||||
return default_path, True
|
||||
|
||||
# Apply more thorough checks to an explicit path.
|
||||
path = renpy.fsdecode(path)
|
||||
|
||||
if not os.path.isdir(path):
|
||||
interface.error(_("The selected directory does not exist."))
|
||||
elif not directory_is_writable(path):
|
||||
interface.error(_("The selected directory is not writable."))
|
||||
|
||||
return path, False
|
||||
@@ -1,514 +0,0 @@
|
||||
# Copyright 2004-2025 Tom Rothamel <pytom@bishoujo.us>
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person
|
||||
# obtaining a copy of this software and associated documentation files
|
||||
# (the "Software"), to deal in the Software without restriction,
|
||||
# including without limitation the rights to use, copy, modify, merge,
|
||||
# publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
# and to permit persons to whom the Software is furnished to do so,
|
||||
# subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
init python:
|
||||
import random
|
||||
import codecs
|
||||
import re
|
||||
import sys
|
||||
|
||||
def theme_names():
|
||||
"""
|
||||
Gets a list of all of the theme names we know about.
|
||||
"""
|
||||
|
||||
names = list(theme_data.THEME.keys())
|
||||
names.sort(key=lambda a : a.lower())
|
||||
|
||||
return names
|
||||
|
||||
def scheme_names(theme):
|
||||
"""
|
||||
Gets a list of the color scheme names corresponding to the given
|
||||
theme.
|
||||
"""
|
||||
|
||||
names = list(theme_data.THEME[theme].keys())
|
||||
names.sort(key=lambda a : a.lower())
|
||||
|
||||
return names
|
||||
|
||||
def theme_yinitial():
|
||||
names = theme_names()
|
||||
|
||||
if len(names) < 2:
|
||||
return 0
|
||||
|
||||
return 1.0 * names.index(current_theme) / (len(names) - 1)
|
||||
|
||||
def scheme_yinitial():
|
||||
names = scheme_names(current_theme)
|
||||
|
||||
if len(names) < 2:
|
||||
return 0
|
||||
|
||||
return 1.0 * names.index(current_scheme) / (len(names) - 1)
|
||||
|
||||
def pick_theme(theme, scheme):
|
||||
"""
|
||||
Returns a theme and scheme that are similar to `theme` and `scheme`.
|
||||
|
||||
If the theme is known, picks it, otherwise picks a random theme. If
|
||||
the scheme is known for that theme, picks it, otherwise picks a
|
||||
random scheme that is known for the current theme.
|
||||
"""
|
||||
|
||||
if theme not in theme_data.THEME:
|
||||
theme = random.choice(list(theme_data.THEME))
|
||||
|
||||
schemes = theme_data.THEME[theme]
|
||||
|
||||
if scheme not in schemes:
|
||||
if theme in schemes:
|
||||
scheme = theme
|
||||
else:
|
||||
scheme = random.choice(list(schemes))
|
||||
|
||||
return theme, scheme
|
||||
|
||||
def implement_theme(theme, scheme):
|
||||
"""
|
||||
Implement the current theme.
|
||||
|
||||
This function uses non-public APIs.
|
||||
"""
|
||||
|
||||
global showing_theme, showing_scheme
|
||||
|
||||
if theme == showing_theme and scheme == showing_scheme:
|
||||
return
|
||||
|
||||
renpy.style.restore(style_backup)
|
||||
exec(theme_data.THEME[theme][scheme], globals(), globals())
|
||||
|
||||
# Rebuild the style cache.
|
||||
renpy.style.rebuild(False)
|
||||
|
||||
# Bust the render cache, so we re-evaluate the styles.
|
||||
renpy.display.interface.kill_textures()
|
||||
|
||||
showing_theme = theme
|
||||
showing_scheme = scheme
|
||||
|
||||
renpy.restart_interaction()
|
||||
|
||||
showing_theme = None
|
||||
showing_scheme = None
|
||||
|
||||
class SetTheme(Action):
|
||||
def __init__(self, theme):
|
||||
self.theme = theme
|
||||
|
||||
def __call__(self):
|
||||
global current_theme
|
||||
global current_scheme
|
||||
|
||||
current_theme, current_scheme = pick_theme(self.theme, current_scheme)
|
||||
|
||||
implement_theme(current_theme, current_scheme)
|
||||
renpy.restart_interaction()
|
||||
|
||||
def get_selected(self):
|
||||
return current_theme == self.theme
|
||||
|
||||
class SetScheme(Action):
|
||||
def __init__(self, scheme):
|
||||
self.scheme = scheme
|
||||
|
||||
def __call__(self):
|
||||
global current_theme
|
||||
global current_scheme
|
||||
|
||||
current_theme, current_scheme = pick_theme(current_theme, self.scheme)
|
||||
|
||||
implement_theme(current_theme, current_scheme)
|
||||
renpy.restart_interaction()
|
||||
|
||||
def get_selected(self):
|
||||
return current_scheme == self.scheme
|
||||
|
||||
class PreviewTheme(Action):
|
||||
|
||||
def __init__(self, theme, scheme):
|
||||
self.theme = theme
|
||||
self.scheme = scheme
|
||||
|
||||
def __call__(self):
|
||||
theme, scheme = pick_theme(self.theme, self.scheme)
|
||||
implement_theme(theme, scheme)
|
||||
|
||||
def unhovered(self):
|
||||
if (showing_theme == self.theme and showing_scheme == self.scheme):
|
||||
implement_theme(current_theme, current_scheme)
|
||||
|
||||
def value_changed(value):
|
||||
return None
|
||||
|
||||
##########################################################################
|
||||
# Code to update options.rpy
|
||||
|
||||
def list_logical_lines(filename):
|
||||
"""
|
||||
This reads in filename, and turns it into a list of logical
|
||||
lines.
|
||||
"""
|
||||
|
||||
f = codecs.open(filename, "rb", "utf-8")
|
||||
data = f.read()
|
||||
f.close()
|
||||
|
||||
# The result.
|
||||
rv = [ ]
|
||||
|
||||
# The current position we're looking at in the buffer.
|
||||
pos = 0
|
||||
|
||||
# Looping over the lines in the file.
|
||||
while pos < len(data):
|
||||
|
||||
# The line that we're building up.
|
||||
line = ""
|
||||
|
||||
# The number of open parenthesis there are right now.
|
||||
parendepth = 0
|
||||
|
||||
# Looping over the characters in a single logical line.
|
||||
while pos < len(data):
|
||||
|
||||
c = data[pos]
|
||||
|
||||
if c == '\n' and not parendepth:
|
||||
rv.append(line)
|
||||
|
||||
pos += 1
|
||||
# This helps out error checking.
|
||||
line = ""
|
||||
break
|
||||
|
||||
# Backslash/newline.
|
||||
if c == "\\" and data[pos+1] == "\n":
|
||||
pos += 2
|
||||
line += "\\\n"
|
||||
continue
|
||||
|
||||
# Parenthesis.
|
||||
if c in ('(', '[', '{'):
|
||||
parendepth += 1
|
||||
|
||||
if c in ('}', ']', ')') and parendepth:
|
||||
parendepth -= 1
|
||||
|
||||
# Comments.
|
||||
if c == '#':
|
||||
while data[pos] != '\n':
|
||||
line += data[pos]
|
||||
pos += 1
|
||||
|
||||
continue
|
||||
|
||||
# Strings.
|
||||
if c in ('"', "'", "`"):
|
||||
delim = c
|
||||
line += c
|
||||
pos += 1
|
||||
|
||||
escape = False
|
||||
|
||||
while pos < len(data):
|
||||
|
||||
c = data[pos]
|
||||
|
||||
if escape:
|
||||
escape = False
|
||||
pos += 1
|
||||
line += c
|
||||
continue
|
||||
|
||||
if c == delim:
|
||||
pos += 1
|
||||
line += c
|
||||
break
|
||||
|
||||
if c == '\\':
|
||||
escape = True
|
||||
|
||||
line += c
|
||||
pos += 1
|
||||
|
||||
continue
|
||||
|
||||
continue
|
||||
|
||||
line += c
|
||||
pos += 1
|
||||
|
||||
if line:
|
||||
rv.append(line)
|
||||
|
||||
return rv
|
||||
|
||||
|
||||
def switch_theme():
|
||||
"""
|
||||
Switches the theme of the current project to the current theme
|
||||
and color scheme. (As set in current_theme and current_scheme.)
|
||||
"""
|
||||
|
||||
theme_code = theme_data.THEME[current_theme][current_scheme]
|
||||
|
||||
# Did we change the file at all?
|
||||
changed = False
|
||||
|
||||
filename = os.path.join(project.current.path, "game/options.rpy")
|
||||
|
||||
with codecs.open(filename + ".new", "wb", "utf-8") as out:
|
||||
|
||||
for l in list_logical_lines(filename):
|
||||
|
||||
m = re.match(r' theme.(\w+)\(', l)
|
||||
if (not changed) and m and (m.group(1) in theme_data.THEME_FUNCTIONS):
|
||||
l = " " + theme_code
|
||||
changed = True
|
||||
|
||||
out.write(l + "\n")
|
||||
|
||||
if changed:
|
||||
try:
|
||||
os.unlink(filename + ".bak")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
os.rename(filename, filename + ".bak")
|
||||
os.rename(filename + ".new", filename)
|
||||
else:
|
||||
os.unlink(filename + ".new")
|
||||
interface.error(_("Could not change the theme. Perhaps options.rpy was changed too much."))
|
||||
|
||||
|
||||
# Now give the theme's screen-ops function a chance to make any
|
||||
# necessary changes to the screens.rpy file
|
||||
filename = os.path.join(project.current.path, "game/screens.rpy")
|
||||
changed = False
|
||||
|
||||
try:
|
||||
with codecs.open(filename + ".new", "wb", "utf-8") as out:
|
||||
lines = list_logical_lines(filename)
|
||||
|
||||
lines = theme_data.THEME_SCREEN_OPERATIONS[current_theme](lines)
|
||||
|
||||
if lines != None:
|
||||
for l in lines:
|
||||
out.write(l + "\n")
|
||||
changed = True
|
||||
|
||||
if changed:
|
||||
try:
|
||||
os.unlink(filename + ".bak")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
os.rename(filename, filename + ".bak")
|
||||
os.rename(filename + ".new", filename)
|
||||
|
||||
except Exception as inst:
|
||||
try:
|
||||
# just in case
|
||||
os.unlink(filename + ".new")
|
||||
except Exception:
|
||||
pass
|
||||
pass
|
||||
|
||||
def make_style_backup():
|
||||
"""
|
||||
Call this to back up the styles. This should be called in a
|
||||
translate python block in each translation.
|
||||
"""
|
||||
|
||||
global style_backup
|
||||
style_backup = renpy.style.backup()
|
||||
|
||||
config.change_language_callbacks.append(make_style_backup)
|
||||
|
||||
screen theme_demo:
|
||||
|
||||
window:
|
||||
style "gm_root"
|
||||
xpadding 5
|
||||
ypadding 5
|
||||
|
||||
grid 1 1:
|
||||
xfill True
|
||||
style_group "prefs"
|
||||
|
||||
vbox:
|
||||
|
||||
frame:
|
||||
style_group "pref"
|
||||
has vbox
|
||||
|
||||
label _("Display")
|
||||
textbutton _("Window") action SelectedIf(True)
|
||||
textbutton _("Fullscreen") action ui.returns(None)
|
||||
textbutton _("Planetarium") action None
|
||||
|
||||
|
||||
frame:
|
||||
style_group "pref"
|
||||
has vbox
|
||||
|
||||
label _("Sound Volume")
|
||||
bar style "slider" value .75 range 1.0 changed value_changed
|
||||
|
||||
textbutton "Test":
|
||||
action ui.returns(None)
|
||||
style "soundtest_button"
|
||||
|
||||
|
||||
init -2 python:
|
||||
style.pref_frame.xfill = True
|
||||
style.pref_frame.xmargin = 5
|
||||
style.pref_frame.top_margin = 5
|
||||
|
||||
style.pref_vbox.xfill = True
|
||||
|
||||
style.pref_button.size_group = "pref"
|
||||
style.pref_button.xalign = 1.0
|
||||
|
||||
style.pref_slider.xmaximum = 192
|
||||
style.pref_slider.xalign = 1.0
|
||||
|
||||
style.soundtest_button.xalign = 1.0
|
||||
|
||||
screen choose_theme:
|
||||
|
||||
default scheme_yadjustment = ui.adjustment()
|
||||
default theme_yadjustment = ui.adjustment()
|
||||
|
||||
default first = True
|
||||
|
||||
python:
|
||||
if first:
|
||||
theme_yinitial_value = theme_yinitial()
|
||||
scheme_yinitial_value = scheme_yinitial()
|
||||
else:
|
||||
theme_yinitial_value = None
|
||||
scheme_yinitial_value = None
|
||||
|
||||
first = False
|
||||
|
||||
frame:
|
||||
style_group "l"
|
||||
style "l_root"
|
||||
|
||||
window:
|
||||
|
||||
has vbox
|
||||
|
||||
label _("Choose Theme")
|
||||
|
||||
hbox:
|
||||
yfill True
|
||||
|
||||
# Theme selector.
|
||||
frame:
|
||||
style "l_indent"
|
||||
bottom_margin HALF_SPACER_HEIGHT
|
||||
xmaximum 225
|
||||
|
||||
has vbox
|
||||
|
||||
label _("Theme") style "l_label_small"
|
||||
|
||||
viewport:
|
||||
scrollbars "vertical"
|
||||
mousewheel True
|
||||
yadjustment theme_yadjustment
|
||||
yinitial theme_yinitial_value
|
||||
|
||||
has vbox
|
||||
|
||||
for i in theme_names():
|
||||
textbutton "[i]":
|
||||
action SetTheme(i)
|
||||
hovered PreviewTheme(i, current_scheme)
|
||||
style "l_list2"
|
||||
|
||||
|
||||
# Color scheme selector.
|
||||
frame:
|
||||
style "l_indent"
|
||||
bottom_margin HALF_SPACER_HEIGHT
|
||||
xmaximum 225
|
||||
|
||||
has vbox
|
||||
|
||||
label _("Color Scheme") style "l_label_small"
|
||||
|
||||
viewport:
|
||||
scrollbars "vertical"
|
||||
mousewheel True
|
||||
yadjustment scheme_yadjustment
|
||||
yinitial scheme_yinitial_value
|
||||
|
||||
has vbox
|
||||
|
||||
for i in scheme_names(current_theme):
|
||||
textbutton "[i]":
|
||||
action SetScheme(i)
|
||||
hovered PreviewTheme(current_theme, i)
|
||||
style "l_list2"
|
||||
|
||||
|
||||
# Preview
|
||||
frame:
|
||||
style "l_default"
|
||||
background Frame(PATTERN, 0, 0, tile=True)
|
||||
xpadding 5
|
||||
ypadding 5
|
||||
|
||||
xfill True
|
||||
yfill True
|
||||
xmargin 20
|
||||
bottom_margin 6
|
||||
|
||||
use theme_demo
|
||||
|
||||
textbutton _("Return") action Jump("front_page") style "l_left_button"
|
||||
textbutton _("Continue") action Return(True) style "l_right_button"
|
||||
|
||||
|
||||
label choose_theme_callable:
|
||||
|
||||
python:
|
||||
current_theme, current_scheme = pick_theme(None, None)
|
||||
implement_theme(current_theme, current_scheme)
|
||||
|
||||
call screen choose_theme
|
||||
|
||||
python hide:
|
||||
with interface.error_handling(_("changing the theme")):
|
||||
switch_theme()
|
||||
|
||||
return
|
||||
|
||||
label choose_theme:
|
||||
call choose_theme_callable
|
||||
jump front_page
|
||||
@@ -1,91 +0,0 @@
|
||||
# Copyright 2004-2025 Tom Rothamel <pytom@bishoujo.us>
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person
|
||||
# obtaining a copy of this software and associated documentation files
|
||||
# (the "Software"), to deal in the Software without restriction,
|
||||
# including without limitation the rights to use, copy, modify, merge,
|
||||
# publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
# and to permit persons to whom the Software is furnished to do so,
|
||||
# subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
init python:
|
||||
|
||||
class ConsoleCommand():
|
||||
"""
|
||||
This runs a series of console commands in an OS-provided
|
||||
console window. This is done by creating a shell script
|
||||
or batch file as appropriate, then using an OS-approved
|
||||
method to run that file.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
|
||||
if renpy.macintosh:
|
||||
fn = "console.command"
|
||||
nl = b"\n"
|
||||
prefix = "#!/bin/sh"
|
||||
elif renpy.windows:
|
||||
fn = "console.bat"
|
||||
nl = b"\r\n"
|
||||
prefix = "@echo off"
|
||||
else:
|
||||
fn = "console.sh"
|
||||
nl = b"\n"
|
||||
prefix = "#!/bin/bash"
|
||||
|
||||
|
||||
self.fn = project.current.temp_filename(fn)
|
||||
self.f = open(self.fn, "wb")
|
||||
self.nl = nl
|
||||
|
||||
self.f.write(renpy.fsencode(prefix, force=True) + nl)
|
||||
|
||||
def add(self, *args):
|
||||
"""
|
||||
Adds a command to be run.
|
||||
"""
|
||||
|
||||
args = [ b'"' + renpy.fsencode(i, force=True) + b'"' for i in args]
|
||||
self.f.write(b" ".join(args) + self.nl)
|
||||
|
||||
def write(self, *args):
|
||||
"""
|
||||
Adds a command to be run.
|
||||
"""
|
||||
|
||||
args = [ renpy.fsencode(i, force=True) for i in args]
|
||||
self.f.write(b" ".join(args) + self.nl)
|
||||
|
||||
def run(self):
|
||||
"""
|
||||
Runs the queued up commands.
|
||||
"""
|
||||
|
||||
if renpy.windows:
|
||||
self.write("pause")
|
||||
elif renpy.linux:
|
||||
self.add("echo", "Press enter to close this window...")
|
||||
self.write("read")
|
||||
|
||||
self.f.close()
|
||||
os.chmod(self.fn, 0o755)
|
||||
|
||||
if renpy.linux:
|
||||
command = renpy.fsencode('"{}"'.format(self.fn.replace("\"", "\\\"")))
|
||||
subprocess.Popen([ "x-terminal-emulator", "-e", command ])
|
||||
else:
|
||||
command = renpy.fsencode(self.fn)
|
||||
os.startfile(command)
|
||||
|
||||
interface.interaction(_("INFORMATION"), _("The command is being run in a new operating system console window."), pause=2.5)
|
||||
@@ -1,328 +0,0 @@
|
||||
# Copyright 2004-2025 Tom Rothamel <pytom@bishoujo.us>
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person
|
||||
# obtaining a copy of this software and associated documentation files
|
||||
# (the "Software"), to deal in the Software without restriction,
|
||||
# including without limitation the rights to use, copy, modify, merge,
|
||||
# publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
# and to permit persons to whom the Software is furnished to do so,
|
||||
# subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
init python:
|
||||
class PackageToggle(Action):
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
|
||||
def get_selected(self):
|
||||
return self.name in project.current.data['packages']
|
||||
|
||||
def __call__(self):
|
||||
packages = project.current.data['packages']
|
||||
|
||||
if self.name in packages:
|
||||
packages.remove(self.name)
|
||||
else:
|
||||
packages.append(self.name)
|
||||
|
||||
project.current.save_data()
|
||||
renpy.restart_interaction()
|
||||
|
||||
class DataToggle(Action):
|
||||
def __init__(self, field):
|
||||
self.field = field
|
||||
|
||||
def get_selected(self):
|
||||
return project.current.data[self.field]
|
||||
|
||||
def __call__(self):
|
||||
project.current.data[self.field] = not project.current.data[self.field]
|
||||
|
||||
project.current.save_data()
|
||||
renpy.restart_interaction()
|
||||
|
||||
|
||||
DEFAULT_BUILD_INFO = """
|
||||
|
||||
## This section contains information about how to build your project into
|
||||
## distribution files.
|
||||
init python:
|
||||
|
||||
## The name that's used for directories and archive files. For example, if
|
||||
## this is 'mygame-1.0', the windows distribution will be in the
|
||||
## directory 'mygame-1.0-win', in the 'mygame-1.0-win.zip' file.
|
||||
build.directory_name = "PROJECTNAME-1.0"
|
||||
|
||||
## The name that's uses for executables - the program that users will run
|
||||
## to start the game. For example, if this is 'mygame', then on Windows,
|
||||
## users can click 'mygame.exe' to start the game.
|
||||
build.executable_name = "PROJECTNAME"
|
||||
|
||||
## If True, Ren'Py will include update information into packages. This
|
||||
## allows the updater to run.
|
||||
build.include_update = False
|
||||
|
||||
## File patterns:
|
||||
##
|
||||
## The following functions take file patterns. File patterns are case-
|
||||
## insensitive, and matched against the path relative to the base
|
||||
## directory, with and without a leading /. If multiple patterns match,
|
||||
## the first is used.
|
||||
##
|
||||
##
|
||||
## In a pattern:
|
||||
##
|
||||
## /
|
||||
## Is the directory separator.
|
||||
## *
|
||||
## Matches all characters, except the directory separator.
|
||||
## **
|
||||
## Matches all characters, including the directory separator.
|
||||
##
|
||||
## For example:
|
||||
##
|
||||
## *.txt
|
||||
## Matches txt files in the base directory.
|
||||
## game/**.ogg
|
||||
## Matches ogg files in the game directory or any of its subdirectories.
|
||||
## **.psd
|
||||
## Matches psd files anywhere in the project.
|
||||
|
||||
## Classify files as None to exclude them from the built distributions.
|
||||
|
||||
build.classify('**~', None)
|
||||
build.classify('**.bak', None)
|
||||
build.classify('**/.**', None)
|
||||
build.classify('**/#**', None)
|
||||
build.classify('**/thumbs.db', None)
|
||||
|
||||
## To archive files, classify them as 'archive'.
|
||||
|
||||
# build.classify('game/**.png', 'archive')
|
||||
# build.classify('game/**.jpg', 'archive')
|
||||
|
||||
## Files matching documentation patterns are duplicated in a mac app
|
||||
## build, so they appear in both the app and the zip file.
|
||||
|
||||
build.documentation('*.html')
|
||||
build.documentation('*.txt')
|
||||
"""
|
||||
|
||||
# A screen that displays a file or directory name, and
|
||||
# lets the user change it,
|
||||
#
|
||||
# title
|
||||
# The title of the link.
|
||||
# value
|
||||
# The value of the field.
|
||||
screen distribute_name:
|
||||
|
||||
add SEPARATOR2
|
||||
|
||||
frame:
|
||||
style "l_indent"
|
||||
has vbox
|
||||
|
||||
text title
|
||||
|
||||
add HALF_SPACER
|
||||
|
||||
frame:
|
||||
style "l_indent"
|
||||
text "[value!q]"
|
||||
|
||||
add SPACER
|
||||
|
||||
|
||||
screen build_distributions:
|
||||
|
||||
frame:
|
||||
style_group "l"
|
||||
style "l_root"
|
||||
|
||||
window:
|
||||
|
||||
has vbox
|
||||
|
||||
label _("Build Distributions: [project.current.display_name!q]")
|
||||
|
||||
add HALF_SPACER
|
||||
|
||||
hbox:
|
||||
|
||||
# Left side.
|
||||
frame:
|
||||
style "l_indent"
|
||||
xmaximum ONEHALF
|
||||
xfill True
|
||||
|
||||
has vbox
|
||||
|
||||
use distribute_name(
|
||||
title=_("Directory Name:"),
|
||||
value=project.current.dump["build"]["directory_name"])
|
||||
|
||||
use distribute_name(
|
||||
title=_("Executable Name:"),
|
||||
value=project.current.dump["build"]["executable_name"])
|
||||
|
||||
add SEPARATOR2
|
||||
|
||||
frame:
|
||||
style "l_indent"
|
||||
has vbox
|
||||
|
||||
text _("Actions:")
|
||||
|
||||
add HALF_SPACER
|
||||
|
||||
frame style "l_indent":
|
||||
|
||||
has vbox
|
||||
|
||||
textbutton _("Edit options.rpy") action editor.Edit("game/options.rpy", check=True)
|
||||
textbutton _("Add from clauses to calls, once") action Jump("add_from")
|
||||
textbutton _("Update old-game") action Jump("start_update_old_game")
|
||||
textbutton _("Refresh") action Jump("build_distributions")
|
||||
|
||||
add HALF_SPACER
|
||||
|
||||
textbutton _("Upload to itch.io") action Jump("itch")
|
||||
|
||||
# Right side.
|
||||
frame:
|
||||
style "l_indent"
|
||||
xmaximum ONEHALF
|
||||
xfill True
|
||||
|
||||
has vbox
|
||||
|
||||
add SEPARATOR2
|
||||
|
||||
frame:
|
||||
style "l_indent"
|
||||
has vbox
|
||||
|
||||
text _("Build Packages:")
|
||||
|
||||
add HALF_SPACER
|
||||
|
||||
$ packages = project.current.dump["build"]["packages"]
|
||||
|
||||
for pkg in packages:
|
||||
if not pkg["hidden"]:
|
||||
$ description = pkg["description"]
|
||||
button:
|
||||
action PackageToggle(pkg["name"]) style "l_checkbox"
|
||||
hbox:
|
||||
spacing 3
|
||||
text "[description!q]"
|
||||
if pkg["dlc"]:
|
||||
text _("(DLC)")
|
||||
|
||||
add SPACER
|
||||
add HALF_SPACER
|
||||
add SEPARATOR2
|
||||
|
||||
frame:
|
||||
style "l_indent"
|
||||
has vbox
|
||||
|
||||
text _("Options:")
|
||||
|
||||
add HALF_SPACER
|
||||
|
||||
if project.current.dump["build"]["include_update"]:
|
||||
textbutton _("Build Updates") action DataToggle("build_update") style "l_checkbox"
|
||||
|
||||
textbutton _("Add from clauses to calls") action DataToggle("add_from") style "l_checkbox"
|
||||
textbutton _("Force Recompile") action DataToggle("force_recompile") style "l_checkbox"
|
||||
|
||||
|
||||
textbutton _("Return") action Jump("front_page") style "l_left_button"
|
||||
textbutton _("Build") action Jump("start_distribute") style "l_right_button"
|
||||
|
||||
label add_from_common:
|
||||
python:
|
||||
interface.processing(_("Adding from clauses to call statements that do not have them."))
|
||||
project.current.launch([ "add_from" ], wait=True)
|
||||
|
||||
return
|
||||
|
||||
label add_from:
|
||||
call add_from_common
|
||||
jump build_distributions
|
||||
|
||||
label start_update_old_game:
|
||||
call update_old_game
|
||||
jump build_distributions
|
||||
|
||||
label add_update_pem:
|
||||
|
||||
python hide:
|
||||
interface.info("You're trying to build an update, but an update.pem file doesn't exist.\n\nThis file is used to sign updates, and will be automatically created in your projects's base directory.\n\nYou'll need to back up update.pem and keep it safe.", cancel=Jump("build_distributions"))
|
||||
|
||||
import ecdsa
|
||||
key = ecdsa.SigningKey.generate(curve=ecdsa.NIST256p).to_pem()
|
||||
|
||||
with open(os.path.join(project.current.path, "update.pem"), "wb") as f:
|
||||
f.write(key)
|
||||
|
||||
return
|
||||
|
||||
label start_distribute:
|
||||
if project.current.dump["build"]["include_update"] and not os.path.exists(os.path.join(project.current.path, "update.pem")):
|
||||
call add_update_pem
|
||||
|
||||
if project.current.data["add_from"]:
|
||||
call add_from_common
|
||||
|
||||
jump distribute
|
||||
|
||||
label build_update_dump:
|
||||
python:
|
||||
project.current.update_dump(True)
|
||||
|
||||
if project.current.dump.get("error", False):
|
||||
interface.error(_("Errors were detected when running the project. Please ensure the project runs without errors before building distributions."))
|
||||
|
||||
return
|
||||
|
||||
label build_distributions:
|
||||
|
||||
call build_update_dump
|
||||
|
||||
label post_build:
|
||||
|
||||
if not project.current.dump["build"]["directory_name"]:
|
||||
jump build_missing
|
||||
|
||||
call screen build_distributions
|
||||
|
||||
label build_missing:
|
||||
|
||||
python hide:
|
||||
|
||||
interface.yesno(_("Your project does not contain build information. Would you like to add build information to the end of options.rpy?"), yes=Return(True), no=Jump("front_page"))
|
||||
|
||||
project_name = project.current.name
|
||||
project_name = project_name.replace(" ", "_")
|
||||
project_name = project_name.replace(":", "")
|
||||
project_name = project_name.replace(";", "")
|
||||
|
||||
build_info = DEFAULT_BUILD_INFO.replace("PROJECTNAME", project_name)
|
||||
|
||||
with open(os.path.join(project.current.path, "game", "options.rpy"), "a") as f:
|
||||
f.write(build_info)
|
||||
|
||||
jump build_distributions
|
||||
@@ -1,53 +0,0 @@
|
||||
# Copyright 2004-2025 Tom Rothamel <pytom@bishoujo.us>
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person
|
||||
# obtaining a copy of this software and associated documentation files
|
||||
# (the "Software"), to deal in the Software without restriction,
|
||||
# including without limitation the rights to use, copy, modify, merge,
|
||||
# publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
# and to permit persons to whom the Software is furnished to do so,
|
||||
# subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
# This file checks to see if we're inside a dmg, and displays an error
|
||||
# message to the player if we are.
|
||||
|
||||
|
||||
init python:
|
||||
|
||||
def dmgcheck():
|
||||
|
||||
if not renpy.macintosh:
|
||||
return
|
||||
|
||||
fn = os.path.join(config.renpy_base, "dmgcheck.txt")
|
||||
|
||||
try:
|
||||
if os.path.exists(fn):
|
||||
os.unlink(fn)
|
||||
|
||||
with open(fn, "w") as f:
|
||||
f.write("Testing to see if we're in a DMG.\r\n")
|
||||
|
||||
os.unlink(fn)
|
||||
return
|
||||
|
||||
except Exception:
|
||||
|
||||
# If we're here, we didn't make it through the checks safely. So
|
||||
# put up a warning message.
|
||||
|
||||
interface.info(
|
||||
message=_("Ren'Py is running from a read only folder. Some functionality will not work."),
|
||||
submessage=_("This is probably because Ren'Py is running directly from a Macintosh drive image. To fix this, quit this launcher, copy the entire %s folder somewhere else on your computer, and run Ren'Py again.") % (os.path.basename(config.renpy_base)),
|
||||
)
|
||||
@@ -1,149 +0,0 @@
|
||||
# Copyright 2004-2025 Tom Rothamel <pytom@bishoujo.us>
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person
|
||||
# obtaining a copy of this software and associated documentation files
|
||||
# (the "Software"), to deal in the Software without restriction,
|
||||
# including without limitation the rights to use, copy, modify, merge,
|
||||
# publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
# and to permit persons to whom the Software is furnished to do so,
|
||||
# subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
init python:
|
||||
|
||||
import requests
|
||||
|
||||
import urllib.request
|
||||
import os
|
||||
import threading
|
||||
import time
|
||||
|
||||
class Downloader(object):
|
||||
|
||||
def __init__(self, url, dest):
|
||||
"""
|
||||
Downloads `url` to `dest`, providing progress reports
|
||||
as necessary.
|
||||
"""
|
||||
|
||||
self.url = url
|
||||
|
||||
# The destination file, and the destination temp file.
|
||||
self.dest = dest
|
||||
self.tmp = dest + ".tmp"
|
||||
|
||||
# Open the tmpfile.
|
||||
self.safe_unlink(self.tmp)
|
||||
self.tmpfile = open(self.tmp, "wb")
|
||||
|
||||
# Set by the thread to indicate progress (ranges from 0.0 to 1.0).
|
||||
self.progress = 0.0
|
||||
|
||||
# This is set to true by cancel() to indicate the download should be cancelled.
|
||||
self.cancelled = False
|
||||
|
||||
# Set on success or failure.
|
||||
self.success = False
|
||||
self.failure = None
|
||||
|
||||
try:
|
||||
# Open the URL.
|
||||
|
||||
self.urlfile = requests.get(url, stream=True, proxies=renpy.proxies, timeout=15)
|
||||
|
||||
t = threading.Thread(target=self.thread)
|
||||
t.daemon = True
|
||||
t.start()
|
||||
|
||||
except Exception as e:
|
||||
self.failure = str(e)
|
||||
|
||||
def thread(self):
|
||||
|
||||
try:
|
||||
count = 0
|
||||
|
||||
if "content-length" in self.urlfile.headers:
|
||||
length = int(self.urlfile.headers["content-length"])
|
||||
else:
|
||||
length = 0
|
||||
|
||||
for data in self.urlfile.iter_content(1024 * 1024):
|
||||
|
||||
count += len(data)
|
||||
self.tmpfile.write(data)
|
||||
|
||||
if length > 0:
|
||||
self.progress = 1.0 * count / length
|
||||
|
||||
if self.cancelled:
|
||||
break
|
||||
|
||||
self.tmpfile.close()
|
||||
|
||||
if self.cancelled:
|
||||
return
|
||||
|
||||
if length and count != length:
|
||||
self.failure = "Download length does not match content length."
|
||||
return
|
||||
|
||||
self.safe_unlink(self.dest)
|
||||
os.rename(self.tmp, self.dest)
|
||||
|
||||
self.success = True
|
||||
|
||||
except Exception as e:
|
||||
self.failure = str(e)
|
||||
|
||||
def safe_unlink(self, fn):
|
||||
if os.path.exists(fn):
|
||||
os.unlink(fn)
|
||||
|
||||
def cancel(self):
|
||||
"""
|
||||
Cancels the download.
|
||||
"""
|
||||
|
||||
self.cancelled = True
|
||||
|
||||
def check(self):
|
||||
"""
|
||||
Returns True if the download is finished, False if it was cancelled,
|
||||
None if it's ongoing, and raises an Exception if the download has failed.
|
||||
"""
|
||||
|
||||
if self.success:
|
||||
return True
|
||||
if self.cancelled:
|
||||
return False
|
||||
if self.failure:
|
||||
raise Exception("Downloading {} to {} failed: {}".format(self.url, self.dest, self.failure))
|
||||
|
||||
return None
|
||||
|
||||
class DownloaderValue(BarValue):
|
||||
"""
|
||||
A BarValue that reports the progress of a background download.
|
||||
"""
|
||||
|
||||
def __init__(self, d):
|
||||
self.downloader = d
|
||||
|
||||
def get_adjustment(self):
|
||||
self.adjustment = ui.adjustment(value=0.0, range=1.0, adjustable=False)
|
||||
return self.adjustment
|
||||
|
||||
def periodic(self, st):
|
||||
self.adjustment.change(self.downloader.progress)
|
||||
return .25
|
||||
@@ -1,681 +0,0 @@
|
||||
# Copyright 2004-2025 Tom Rothamel <pytom@bishoujo.us>
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person
|
||||
# obtaining a copy of this software and associated documentation files
|
||||
# (the "Software"), to deal in the Software without restriction,
|
||||
# including without limitation the rights to use, copy, modify, merge,
|
||||
# publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
# and to permit persons to whom the Software is furnished to do so,
|
||||
# subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
# Editor Support.
|
||||
#
|
||||
# This contains code for scanning for editors, and for allowing the user to
|
||||
# select an editor.
|
||||
|
||||
init 1 python in editor:
|
||||
|
||||
from store import Action, renpy, config, persistent
|
||||
import store.project as project
|
||||
import store.updater as updater
|
||||
import store.interface as interface
|
||||
import store.util as util
|
||||
import store
|
||||
|
||||
import glob
|
||||
import re
|
||||
import traceback
|
||||
import os
|
||||
import os.path
|
||||
|
||||
# Should we set up the editor?
|
||||
set_editor = "RENPY_EDIT_PY" not in os.environ
|
||||
|
||||
# A map from editor name to EditorInfo object.
|
||||
editors = { }
|
||||
|
||||
class EditorInfo(object):
|
||||
def __init__(self, filename):
|
||||
# The path to the editor info file.
|
||||
self.filename = filename
|
||||
|
||||
# The name of the editor.
|
||||
self.name = os.path.basename(filename)[:-len(".edit.py")]
|
||||
|
||||
# The time the editor file was last modified. We use this
|
||||
# to decide if we should update the editors mat when we
|
||||
# have multiple versions of an editor in contention.
|
||||
self.mtime = os.path.getmtime(filename)
|
||||
|
||||
def scan_editor(filename):
|
||||
"""
|
||||
Inserts an editor into editors if there isn't a newer
|
||||
editor there already.
|
||||
"""
|
||||
|
||||
ei = EditorInfo(filename)
|
||||
|
||||
if ei.name in editors:
|
||||
if editors[ei.name].mtime >= ei.mtime:
|
||||
return
|
||||
|
||||
editors[ei.name] = ei
|
||||
|
||||
def scan_all():
|
||||
"""
|
||||
Finds all *.edit.py files, and uses them to populate the list
|
||||
of editors.
|
||||
"""
|
||||
|
||||
editors.clear()
|
||||
|
||||
for d in [ config.renpy_base, persistent.projects_directory ]:
|
||||
if d is None:
|
||||
continue
|
||||
|
||||
if not os.path.isdir(d):
|
||||
continue
|
||||
|
||||
for i in util.listdir(d):
|
||||
i = os.path.join(d, i)
|
||||
if not os.path.isdir(d):
|
||||
continue
|
||||
|
||||
for j in util.listdir(i):
|
||||
j = os.path.join(i, j)
|
||||
|
||||
if j.endswith(".edit.py"):
|
||||
scan_editor(j)
|
||||
|
||||
########################################################################
|
||||
|
||||
# A list of fancy_editor_info objects.
|
||||
fancy_editors = [ ]
|
||||
|
||||
# The error message to display if an editor failed to start.
|
||||
error_message = None
|
||||
|
||||
class FancyEditorInfo(object):
|
||||
"""
|
||||
Represents an editor in the selection screen. A FEI knows if the
|
||||
editor is installed or not.
|
||||
"""
|
||||
|
||||
def __init__(self, priority, name, description=None, dlc=None, dldescription=None, error_message=None, deprecated=False):
|
||||
# The priority of the editor. Lower priorities will come later
|
||||
# in the list.
|
||||
self.priority = priority
|
||||
|
||||
# The name of the editor.
|
||||
self.name = name
|
||||
|
||||
# Is the editor installed?
|
||||
self.installed = name in editors
|
||||
|
||||
# The dlc needed to install the editor.
|
||||
self.dlc = dlc
|
||||
|
||||
# A description of the editor.
|
||||
self.description = description
|
||||
|
||||
# A description of the download.
|
||||
self.dldescription = dldescription
|
||||
|
||||
# An error message to display if the editor failed to start.
|
||||
self.error_message = error_message
|
||||
|
||||
# If the editor is considered deprecated for use with Ren'Py.
|
||||
self.deprecated = deprecated
|
||||
|
||||
|
||||
def vscode_path():
|
||||
"""
|
||||
Gets the path to VS Code for this platform.
|
||||
"""
|
||||
|
||||
if renpy.windows:
|
||||
return os.path.join(config.renpy_base, "vscode/VSCode-win32-x64")
|
||||
elif renpy.macintosh:
|
||||
return os.path.join(config.renpy_base, "vscode/Visual Studio Code.app")
|
||||
else:
|
||||
if renpy.arch == "aarch64":
|
||||
arch = "arm64"
|
||||
elif renpy.arch == "armv7l":
|
||||
arch = "arm"
|
||||
else:
|
||||
arch = "x64"
|
||||
|
||||
return os.path.join(config.renpy_base, "vscode/VSCode-linux-" + arch)
|
||||
|
||||
def fancy_scan_editors():
|
||||
"""
|
||||
Creates the list of FancyEditorInfo objects.
|
||||
"""
|
||||
|
||||
import platform
|
||||
|
||||
global fancy_editors
|
||||
|
||||
scan_all()
|
||||
|
||||
fei = fancy_editors = [ ]
|
||||
|
||||
# Visual Studio Code
|
||||
AD1 = _("A modern editor with many extensions including advanced Ren'Py integration.")
|
||||
AD2 = _("A modern editor with many extensions including advanced Ren'Py integration.\n{a=jump:reinstall_vscode}Upgrade Visual Studio Code to the latest version.{/a}")
|
||||
|
||||
installed = os.path.exists(vscode_path())
|
||||
|
||||
e = FancyEditorInfo(
|
||||
0,
|
||||
_("Visual Studio Code"),
|
||||
AD2 if installed else AD2,
|
||||
"extension:vscode",
|
||||
_("Up to 110 MB download required."),
|
||||
None)
|
||||
|
||||
e.installed = e.installed and installed
|
||||
|
||||
fei.append(e)
|
||||
|
||||
# Atom.
|
||||
AD = _("Atom is deprecated and its bugs are known for corrupting games, using another editor is recommended.")
|
||||
|
||||
if renpy.windows:
|
||||
dlc = "atom-windows"
|
||||
installed = os.path.exists(os.path.join(config.renpy_base, "atom/atom-windows"))
|
||||
elif renpy.macintosh:
|
||||
dlc = "atom-mac"
|
||||
installed = os.path.exists(os.path.join(config.renpy_base, "atom/Atom.app"))
|
||||
else:
|
||||
dlc = "atom-linux"
|
||||
installed = os.path.exists(os.path.join(config.renpy_base, "atom/atom-linux-" + platform.machine()))
|
||||
|
||||
if not (renpy.arch in [ "aarch64", "armv7l" ]):
|
||||
|
||||
e = FancyEditorInfo(
|
||||
1,
|
||||
_("Atom"),
|
||||
AD,
|
||||
"extension:atom",
|
||||
_("Up to 150 MB download required."),
|
||||
None,
|
||||
deprecated=True,
|
||||
)
|
||||
|
||||
e.installed = e.installed and (installed or 'RENPY_ATOM' in os.environ)
|
||||
|
||||
fei.append(e)
|
||||
|
||||
# jEdit - Only present if it exists on system.
|
||||
e = FancyEditorInfo(
|
||||
2,
|
||||
_("jEdit"),
|
||||
_("JEdit is deprecated, using another editor is recommended."),
|
||||
"jedit",
|
||||
_("1.8 MB download required."),
|
||||
_("This may have occurred because Java is not installed on this system."),
|
||||
deprecated=True,
|
||||
)
|
||||
|
||||
e.installed = e.installed and os.path.exists(os.path.join(config.renpy_base, "jedit"))
|
||||
|
||||
fei.append(e)
|
||||
|
||||
fei.append(FancyEditorInfo(
|
||||
3,
|
||||
_("Visual Studio Code (System)"),
|
||||
_("Uses a copy of Visual Studio Code that you have installed outside of Ren'Py. It's recommended you install the language-renpy extension to add support for Ren'Py files."),
|
||||
))
|
||||
|
||||
fei.append(FancyEditorInfo(
|
||||
3,
|
||||
_("System Editor"),
|
||||
_("Invokes the editor your operating system has associated with .rpy files."),
|
||||
None))
|
||||
|
||||
for k in editors:
|
||||
if k in [ "Visual Studio Code", "Visual Studio Code (System)", "Atom", "jEdit", "System Editor", "None" ]:
|
||||
continue
|
||||
|
||||
fei.append(FancyEditorInfo(
|
||||
4,
|
||||
k,
|
||||
None,
|
||||
None))
|
||||
|
||||
fei.append(FancyEditorInfo(
|
||||
5,
|
||||
_("None"),
|
||||
_("Prevents Ren'Py from opening a text editor."),
|
||||
None))
|
||||
|
||||
fei.sort(key=lambda e : (e.priority, e.name.lower()))
|
||||
|
||||
# If we're in a linux distro or something, assume all editors work.
|
||||
if not updater.can_update():
|
||||
for i in fei:
|
||||
if i.dlc and not i.dlc.startswith("extension:") and not i.deprecated:
|
||||
i.installed = True
|
||||
|
||||
def fancy_activate_editor(default=False):
|
||||
"""
|
||||
Activates the editor in persistent.editor, if it's installed.
|
||||
|
||||
`default`
|
||||
|
||||
"""
|
||||
|
||||
global error_message
|
||||
|
||||
fancy_scan_editors()
|
||||
|
||||
if default and not set_editor:
|
||||
renpy.editor.init()
|
||||
return
|
||||
|
||||
for i in fancy_editors:
|
||||
|
||||
if i.name == persistent.editor:
|
||||
if i.installed and i.name in editors:
|
||||
ei = editors[i.name]
|
||||
os.environ["RENPY_EDIT_PY"] = renpy.fsencode(os.path.abspath(ei.filename))
|
||||
error_message = i.error_message
|
||||
break
|
||||
|
||||
else:
|
||||
persistent.editor = None
|
||||
os.environ.pop("RENPY_EDIT_PY", None)
|
||||
|
||||
renpy.editor.init()
|
||||
|
||||
def fancy_select_editor(name):
|
||||
"""
|
||||
Selects the editor with the given name, installing it if it
|
||||
doesn't already exist.
|
||||
"""
|
||||
|
||||
for fe in fancy_editors:
|
||||
if fe.name == name:
|
||||
break
|
||||
else:
|
||||
return
|
||||
|
||||
if not fe.installed:
|
||||
|
||||
if fe.dlc.startswith("extension:"):
|
||||
import installer
|
||||
manifest = fe.dlc.partition(":")[2]
|
||||
renpy.invoke_in_new_context(installer.manifest, "https://www.renpy.org/extensions/{}/{}.py".format(manifest, manifest), renpy=True)
|
||||
else:
|
||||
store.add_dlc(fe.dlc)
|
||||
|
||||
persistent.editor = fe.name
|
||||
fancy_activate_editor()
|
||||
|
||||
return persistent.editor is not None
|
||||
|
||||
# Call fancy_activate_editor on startup.
|
||||
fancy_activate_editor(True)
|
||||
|
||||
class SelectEditor(Action):
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
|
||||
def get_selected(self):
|
||||
return persistent.editor == self.name
|
||||
|
||||
def __call__(self):
|
||||
return fancy_select_editor(self.name)
|
||||
|
||||
|
||||
def check_editor():
|
||||
"""
|
||||
Checks to see if an editor is set. If one isn't asks the user to
|
||||
select one.
|
||||
|
||||
Returns True if the editor is set and editing can proceed, and
|
||||
False otherwise.
|
||||
"""
|
||||
|
||||
if not set_editor:
|
||||
return True
|
||||
|
||||
if persistent.editor and persistent.editor != "None":
|
||||
return True
|
||||
|
||||
return renpy.invoke_in_new_context(renpy.call_screen, "editor")
|
||||
|
||||
##########################################################################
|
||||
# Editing actions.
|
||||
|
||||
|
||||
class Edit(Action):
|
||||
alt = _("Edit [text].")
|
||||
|
||||
def __init__(self, filename, line=None, check=False):
|
||||
"""
|
||||
An action that opens the given line of the given file in a
|
||||
text editor.
|
||||
|
||||
`filename`
|
||||
The filename to open.
|
||||
|
||||
`line`
|
||||
The line in the file to jump to.
|
||||
|
||||
`check`
|
||||
If true, we will check to see if the file exists, and gray
|
||||
out the box if it does not.
|
||||
"""
|
||||
|
||||
self.filename = filename
|
||||
self.line = line
|
||||
self.check = check
|
||||
|
||||
def get_sensitive(self):
|
||||
if not self.check:
|
||||
return True
|
||||
|
||||
fn = project.current.unelide_filename(self.filename)
|
||||
return os.path.exists(renpy.fsencode(fn))
|
||||
|
||||
def __call__(self):
|
||||
|
||||
if not self.get_sensitive():
|
||||
return
|
||||
|
||||
if not check_editor():
|
||||
return
|
||||
|
||||
fn = project.current.unelide_filename(self.filename)
|
||||
|
||||
try:
|
||||
|
||||
e = renpy.editor.editor
|
||||
|
||||
e.begin()
|
||||
e.open(fn, line=self.line)
|
||||
e.end()
|
||||
|
||||
except Exception as e:
|
||||
exception = traceback.format_exception_only(type(e), e)[-1][:-1]
|
||||
renpy.invoke_in_new_context(interface.error, _("An exception occurred while launching the text editor:\n[exception!q]"), error_message, exception=exception)
|
||||
|
||||
class EditAbsolute(Action):
|
||||
def __init__(self, filename, line=None, check=False):
|
||||
"""
|
||||
An action that lets us edit an absolutely-specified filename.
|
||||
|
||||
`filename`
|
||||
The filename to open.
|
||||
|
||||
`line`
|
||||
The line in the file to jump to.
|
||||
|
||||
`check`
|
||||
If true, we will check to see if the file exists, and gray
|
||||
out the box if it does not.
|
||||
"""
|
||||
|
||||
self.filename = filename
|
||||
self.line = line
|
||||
self.check = check
|
||||
|
||||
def get_sensitive(self):
|
||||
if not self.check:
|
||||
return True
|
||||
|
||||
return os.path.exists(renpy.fsencode(self.filename))
|
||||
|
||||
def __call__(self):
|
||||
|
||||
if not self.get_sensitive():
|
||||
return
|
||||
|
||||
if not check_editor():
|
||||
return
|
||||
|
||||
try:
|
||||
|
||||
e = renpy.editor.editor
|
||||
|
||||
e.begin()
|
||||
e.open(self.filename, line=self.line)
|
||||
e.end()
|
||||
|
||||
except Exception as e:
|
||||
exception = traceback.format_exception_only(type(e), e)[-1][:-1]
|
||||
renpy.invoke_in_new_context(interface.error, _("An exception occurred while launching the text editor:\n[exception!q]"), error_message, exception=exception)
|
||||
|
||||
|
||||
class EditAll(Action):
|
||||
"""
|
||||
Opens all scripts that are part of the current project in a web browser.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
return
|
||||
|
||||
def __call__(self):
|
||||
|
||||
if not check_editor():
|
||||
return
|
||||
|
||||
scripts = project.current.script_files()
|
||||
scripts = [ i for i in scripts if not i.startswith("game/tl/") ]
|
||||
scripts.sort(key=lambda fn : fn.lower())
|
||||
|
||||
for fn in [ "game/screens.rpy", "game/options.rpy", "game/script.rpy" ]:
|
||||
if fn in scripts:
|
||||
scripts.remove(fn)
|
||||
scripts.insert(0, fn)
|
||||
|
||||
try:
|
||||
|
||||
e = renpy.editor.editor
|
||||
e.begin()
|
||||
|
||||
for fn in scripts:
|
||||
fn = project.current.unelide_filename(fn)
|
||||
e.open(fn)
|
||||
|
||||
e.end()
|
||||
|
||||
except Exception as e:
|
||||
exception = traceback.format_exception_only(type(e), e)[-1][:-1]
|
||||
renpy.invoke_in_new_context(interface.error, _("An exception occurred while launching the text editor:\n[exception!q]"), error_message, exception=exception)
|
||||
|
||||
|
||||
class EditProject(Action):
|
||||
"""
|
||||
Opens the project's base directory in an editor.
|
||||
"""
|
||||
|
||||
def __call__(self):
|
||||
|
||||
if not check_editor():
|
||||
return
|
||||
|
||||
try:
|
||||
|
||||
e = renpy.editor.editor
|
||||
|
||||
e.begin()
|
||||
e.open_project(project.current.path)
|
||||
e.end()
|
||||
|
||||
except Exception as e:
|
||||
exception = traceback.format_exception_only(type(e), e)[-1][:-1]
|
||||
renpy.invoke_in_new_context(interface.error, _("An exception occurred while launching the text editor:\n[exception!q]"), error_message, exception=exception)
|
||||
|
||||
|
||||
def CanEditProject():
|
||||
"""
|
||||
Returns True if EditProject can be used.
|
||||
"""
|
||||
|
||||
try:
|
||||
e = renpy.editor.editor
|
||||
return e.has_projects
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def check_old_vscode_extension():
|
||||
"""
|
||||
Check to see if the old version of the Ren'Py vscode extension is installed.
|
||||
"""
|
||||
|
||||
extensions = os.path.join(vscode_path(), "data", "extensions")
|
||||
|
||||
if not os.path.isdir(extensions):
|
||||
return False
|
||||
|
||||
for i in util.listdir(extensions):
|
||||
if i.lower().startswith("renpy.language-renpy-"):
|
||||
return False
|
||||
|
||||
for i in util.listdir(extensions):
|
||||
if i.lower().startswith("luquedaniel.languague-renpy-"):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
screen editor:
|
||||
|
||||
frame:
|
||||
style_group "l"
|
||||
style "l_root"
|
||||
|
||||
window:
|
||||
|
||||
has vbox
|
||||
|
||||
label _("Select Editor")
|
||||
|
||||
add HALF_SPACER
|
||||
|
||||
hbox:
|
||||
frame:
|
||||
style "l_indent"
|
||||
xfill True
|
||||
|
||||
viewport:
|
||||
scrollbars "vertical"
|
||||
mousewheel True
|
||||
|
||||
has vbox
|
||||
|
||||
text _("A text editor is the program you'll use to edit Ren'Py script files. Here, you can select the editor Ren'Py will use. If not already present, the editor will be automatically downloaded and installed.") style "l_small_text"
|
||||
|
||||
for fe in editor.fancy_editors:
|
||||
|
||||
if fe.deprecated and not fe.installed:
|
||||
continue
|
||||
|
||||
add SPACER
|
||||
|
||||
textbutton fe.name action editor.SelectEditor(fe.name)
|
||||
|
||||
add HALF_SPACER
|
||||
|
||||
frame:
|
||||
style "l_indent"
|
||||
has vbox
|
||||
|
||||
if fe.description:
|
||||
text fe.description:
|
||||
style "l_small_text"
|
||||
if fe.deprecated:
|
||||
color ERROR_COLOR
|
||||
|
||||
if not fe.installed:
|
||||
add HALF_SPACER
|
||||
text fe.dldescription style "l_small_text"
|
||||
|
||||
|
||||
textbutton _("Cancel") action Return(False) style "l_left_button"
|
||||
|
||||
label reinstall_vscode:
|
||||
python hide:
|
||||
manifest = "vscode"
|
||||
renpy.invoke_in_new_context(installer.manifest, "https://www.renpy.org/extensions/{}/{}.py".format(manifest, manifest), renpy=True)
|
||||
|
||||
jump editor_preference
|
||||
|
||||
label upgrade_vscode_extension:
|
||||
python hide:
|
||||
manifest = "vscode"
|
||||
renpy.invoke_in_new_context(installer.manifest, "https://www.renpy.org/extensions/vscode/upgrade_extension.py", renpy=True)
|
||||
|
||||
jump post_extension_check
|
||||
|
||||
label editor_preference:
|
||||
call screen editor
|
||||
jump preferences
|
||||
|
||||
default persistent.ignore_obsolete_editor = set()
|
||||
|
||||
# This label is called when the launcher starts, to check if the editor
|
||||
# is obsolete, and let them change it.
|
||||
label editor_check:
|
||||
|
||||
if persistent.editor in persistent.ignore_obsolete_editor:
|
||||
jump post_editor_check
|
||||
|
||||
if persistent.editor == "Atom":
|
||||
$ result = interface.choice(
|
||||
_("The Atom text editor is no longer supported by its developers. We suggest switching to Visual Studio Code or another editor."), [
|
||||
( "select", _("Select editor now.")),
|
||||
( "ignore", _("Ignore until next launch.")),
|
||||
( "block", _("Do not ask again.")),
|
||||
], "select")
|
||||
|
||||
else:
|
||||
jump post_atom_check
|
||||
|
||||
if result == "select":
|
||||
$ renpy.pop_call()
|
||||
jump editor_preference
|
||||
|
||||
elif result == "block":
|
||||
$ persistent.ignore_obsolete_editor.add(persistent.editor)
|
||||
|
||||
|
||||
label post_atom_check:
|
||||
|
||||
if "luquedaniel.languague-renpy" in persistent.ignore_obsolete_editor:
|
||||
jump post_extension_check
|
||||
|
||||
if persistent.editor != "Visual Studio Code":
|
||||
jump post_extension_check
|
||||
|
||||
if editor.check_old_vscode_extension():
|
||||
$ result = interface.choice(
|
||||
_("You are using an old version of the Ren'Py Language support for Visual Studio Code. Would you like to upgrade?"), [
|
||||
( "upgrade", _("Upgrade.")),
|
||||
( "ignore", _("Ignore until next launch.")),
|
||||
( "block", _("Do not ask again.")),
|
||||
], "select")
|
||||
|
||||
if result == "upgrade":
|
||||
jump upgrade_vscode_extension
|
||||
|
||||
elif result == "block":
|
||||
$ persistent.ignore_obsolete_editor.add("luquedaniel.languague-renpy")
|
||||
|
||||
label post_extension_check:
|
||||
jump post_editor_check
|
||||
@@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -1,289 +0,0 @@
|
||||
# Copyright 2004-2025 Tom Rothamel <pytom@bishoujo.us>
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person
|
||||
# obtaining a copy of this software and associated documentation files
|
||||
# (the "Software"), to deal in the Software without restriction,
|
||||
# including without limitation the rights to use, copy, modify, merge,
|
||||
# publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
# and to permit persons to whom the Software is furnished to do so,
|
||||
# subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
define PROJECT_ADJUSTMENT = ui.adjustment()
|
||||
|
||||
init python:
|
||||
|
||||
import datetime
|
||||
|
||||
# Used for testing.
|
||||
def Relaunch():
|
||||
renpy.quit(relaunch=True)
|
||||
|
||||
screen front_page:
|
||||
frame:
|
||||
alt ""
|
||||
|
||||
style_group "l"
|
||||
style "l_root"
|
||||
|
||||
has hbox
|
||||
|
||||
# Projects list section - on left.
|
||||
|
||||
frame:
|
||||
style "l_projects"
|
||||
xmaximum 300
|
||||
right_margin 2
|
||||
|
||||
top_padding 20
|
||||
bottom_padding 13
|
||||
|
||||
side "t c b":
|
||||
|
||||
window style "l_label":
|
||||
|
||||
has hbox:
|
||||
xfill True
|
||||
|
||||
text _("PROJECTS:") style "l_label_text" size 36 yoffset 10
|
||||
|
||||
textbutton _("refresh"):
|
||||
xalign 1.0
|
||||
yalign 1.0
|
||||
yoffset 5
|
||||
style "l_small_button"
|
||||
action project.Rescan()
|
||||
right_margin HALF_INDENT
|
||||
|
||||
side "c r":
|
||||
|
||||
viewport:
|
||||
yadjustment PROJECT_ADJUSTMENT
|
||||
mousewheel True
|
||||
use front_page_project_list
|
||||
|
||||
vbar:
|
||||
style "l_vscrollbar"
|
||||
adjustment PROJECT_ADJUSTMENT
|
||||
|
||||
vbox:
|
||||
add HALF_SPACER
|
||||
add SEPARATOR
|
||||
add HALF_SPACER
|
||||
|
||||
hbox:
|
||||
xfill True
|
||||
|
||||
textbutton _("+ Create New Project"):
|
||||
left_margin (HALF_INDENT)
|
||||
action Jump("new_project")
|
||||
|
||||
# Project section - on right.
|
||||
|
||||
if project.current is not None:
|
||||
use front_page_project
|
||||
|
||||
if project.current is not None:
|
||||
textbutton _("Launch Project") action project.Launch() style "l_right_button"
|
||||
key "K_F5" action project.Launch()
|
||||
|
||||
|
||||
|
||||
# This is used by front_page to display the list of known projects on the screen.
|
||||
screen front_page_project_list:
|
||||
|
||||
$ projects = project.manager.projects
|
||||
$ templates = project.manager.templates
|
||||
|
||||
vbox:
|
||||
|
||||
if templates and persistent.show_templates:
|
||||
|
||||
for p in templates:
|
||||
|
||||
textbutton _("[p.name!q] (template)"):
|
||||
action project.Select(p)
|
||||
alt _("Select project [text].")
|
||||
style "l_list"
|
||||
|
||||
null height 12
|
||||
|
||||
if projects:
|
||||
|
||||
for p in projects:
|
||||
|
||||
textbutton "[p.name!q]":
|
||||
action project.Select(p)
|
||||
alt _("Select project [text].")
|
||||
style "l_list"
|
||||
|
||||
null height 12
|
||||
|
||||
textbutton _("Tutorial") action project.SelectTutorial() style "l_list" alt _("Select project [text].")
|
||||
textbutton _("The Question") action project.Select("the_question") style "l_list" alt _("Select project [text].")
|
||||
|
||||
|
||||
# This is used for the right side of the screen, which is where the project-specific
|
||||
# buttons are.
|
||||
screen front_page_project:
|
||||
|
||||
$ p = project.current
|
||||
|
||||
window:
|
||||
|
||||
has vbox
|
||||
|
||||
frame style "l_label":
|
||||
has hbox xfill True
|
||||
text "[p.display_name!q]" style "l_label_text"
|
||||
label _("Active Project") style "l_alternate"
|
||||
|
||||
grid 2 1:
|
||||
xfill True
|
||||
spacing HALF_INDENT
|
||||
|
||||
vbox:
|
||||
|
||||
label _("Open Directory") style "l_label_small"
|
||||
|
||||
frame style "l_indent":
|
||||
has grid 2 max(5, (len(p.get_renpy_launcher()["open_directory"]) + 1) // 2):
|
||||
transpose True xfill True
|
||||
|
||||
for button_name, path in p.get_renpy_launcher()["open_directory"].items():
|
||||
textbutton button_name action OpenDirectory(os.path.join(p.path, path), absolute=True)
|
||||
|
||||
vbox:
|
||||
if persistent.show_edit_funcs:
|
||||
|
||||
label _("Edit File") style "l_label_small"
|
||||
|
||||
frame style "l_indent":
|
||||
has vbox
|
||||
|
||||
for button_name, path in p.get_renpy_launcher()["edit_file"].items():
|
||||
textbutton button_name action editor.Edit(path, check=True)
|
||||
|
||||
if editor.CanEditProject():
|
||||
textbutton _("Open project") action editor.EditProject()
|
||||
else:
|
||||
textbutton _("All script files") action editor.EditAll()
|
||||
|
||||
add SPACER
|
||||
|
||||
label _("Actions") style "l_label_small"
|
||||
|
||||
grid 2 1:
|
||||
xfill True
|
||||
spacing HALF_INDENT
|
||||
|
||||
frame style "l_indent":
|
||||
has vbox
|
||||
|
||||
textbutton _("Navigate Script") action Jump("navigation")
|
||||
textbutton _("Check Script (Lint)") action Call("lint")
|
||||
|
||||
if project.current.exists("game/gui.rpy"):
|
||||
textbutton _("Change/Update GUI") action Jump("change_gui")
|
||||
else:
|
||||
textbutton _("Change Theme") action Jump("choose_theme")
|
||||
|
||||
|
||||
textbutton _("Delete Persistent") action Jump("rmpersistent")
|
||||
textbutton _("Force Recompile") action Jump("force_recompile")
|
||||
|
||||
# textbutton "Relaunch" action Relaunch
|
||||
|
||||
frame style "l_indent":
|
||||
has vbox
|
||||
|
||||
if ability.can_distribute:
|
||||
textbutton _("Build Distributions") action Jump("build_distributions")
|
||||
|
||||
textbutton _("Android") action Jump("android")
|
||||
textbutton _("iOS") action Jump("ios")
|
||||
textbutton _("Web") action Jump("web")
|
||||
textbutton _("Generate Translations") action Jump("translate")
|
||||
textbutton _("Extract Dialogue") action Jump("extract_dialogue")
|
||||
|
||||
label main_menu:
|
||||
return
|
||||
|
||||
label start:
|
||||
show screen bottom_info
|
||||
$ dmgcheck()
|
||||
|
||||
jump expression renpy.session.pop("launcher_start_label", "before_front_page")
|
||||
|
||||
default persistent.has_chosen_language = False
|
||||
default persistent.has_update = False
|
||||
|
||||
label before_front_page:
|
||||
|
||||
if (not persistent.has_chosen_language) or ("RENPY_CHOOSE_LANGUAGE" in os.environ):
|
||||
|
||||
if (_preferences.language is None) or ("RENPY_CHOOSE_LANGUAGE" in os.environ):
|
||||
hide screen bottom_info
|
||||
call choose_language
|
||||
show screen bottom_info
|
||||
|
||||
$ persistent.has_chosen_language = True
|
||||
|
||||
call editor_check
|
||||
|
||||
label post_editor_check:
|
||||
label front_page:
|
||||
|
||||
if persistent.daily_update_check and ((not persistent.last_update_check) or (datetime.date.today() > persistent.last_update_check)):
|
||||
python hide:
|
||||
persistent.last_update_check = datetime.date.today()
|
||||
renpy.invoke_in_thread(fetch_update_channels)
|
||||
|
||||
call screen front_page
|
||||
jump front_page
|
||||
|
||||
|
||||
label lint:
|
||||
python hide:
|
||||
|
||||
interface.processing(_("Checking script for potential problems..."))
|
||||
lint_fn = project.current.temp_filename("lint.txt")
|
||||
|
||||
persistent.lint_options.discard("--orphan-tl") # compat
|
||||
persistent.lint_options.discard("--builtins-parameters") # compat
|
||||
persistent.lint_options.discard("--words-char-count") # compat
|
||||
|
||||
project.current.launch([ 'lint', lint_fn, ] + list(persistent.lint_options), wait=True)
|
||||
|
||||
e = renpy.editor.editor
|
||||
e.begin(True)
|
||||
e.open(lint_fn)
|
||||
e.end()
|
||||
|
||||
return
|
||||
|
||||
label rmpersistent:
|
||||
|
||||
python hide:
|
||||
interface.processing(_("Deleting persistent data..."))
|
||||
project.current.launch([ 'rmpersistent' ], wait=True)
|
||||
|
||||
jump front_page
|
||||
|
||||
label force_recompile:
|
||||
|
||||
python hide:
|
||||
interface.processing(_("Recompiling all rpy files into rpyc files..."))
|
||||
project.current.launch([ 'compile' ], wait=True)
|
||||
|
||||
jump front_page
|
||||
@@ -1,512 +0,0 @@
|
||||
# Copyright 2004-2025 Tom Rothamel <pytom@bishoujo.us>
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person
|
||||
# obtaining a copy of this software and associated documentation files
|
||||
# (the "Software"), to deal in the Software without restriction,
|
||||
# including without limitation the rights to use, copy, modify, merge,
|
||||
# publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
# and to permit persons to whom the Software is furnished to do so,
|
||||
# subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
define gui.project_system_font = None
|
||||
define gui.asian = False
|
||||
|
||||
# This is set in new_project before new_template_project is called,
|
||||
# and provides the full path to the template project.
|
||||
default gui_template_path = None
|
||||
|
||||
define LIBS_TXT = _("""\
|
||||
The game/libs/ directory is meant for third-party libraries that ask to be
|
||||
put in game/libs.
|
||||
|
||||
When this file (game/libs/libs.txt) is present, Ren'Py will load all files
|
||||
in libs before any other file in game/, and will not consider directory names
|
||||
when loading these files.
|
||||
""")
|
||||
|
||||
init -1 python:
|
||||
|
||||
import gui7
|
||||
from gui7 import translate_define, translate_copy, translate_code
|
||||
|
||||
import os
|
||||
|
||||
from store import config
|
||||
|
||||
def translate_font(language, font):
|
||||
"""
|
||||
Selects the font file that is used when translating `language`.
|
||||
|
||||
`font`
|
||||
Is the name of the font file used for both the launcher and
|
||||
the new GUI template. This should be a string giving the name
|
||||
of the font file.
|
||||
"""
|
||||
|
||||
def callback():
|
||||
gui.REGULAR_FONT = font
|
||||
gui.LIGHT_FONT = font
|
||||
gui.REGULAR_BOLD = True
|
||||
|
||||
style._default.font = font
|
||||
style.default.font = font
|
||||
|
||||
gui.system_font = font
|
||||
gui.project_system_font = font
|
||||
|
||||
config.language_callbacks[language].append(callback)
|
||||
|
||||
def s(s):
|
||||
return '"' + s + '"'
|
||||
|
||||
gui7.translate_copy(language, "sdk-fonts/" + font, font)
|
||||
gui7.translate_define(language, "gui.text_font", s(font))
|
||||
gui7.translate_define(language, "gui.name_text_font", s(font))
|
||||
gui7.translate_define(language, "gui.interface_text_font", s(font))
|
||||
|
||||
|
||||
for fn in [ "gui.rpy", "options.rpy", "screens.rpy" ]:
|
||||
fn = os.path.join(config.renpy_base, "gui", "game", fn)
|
||||
if os.path.exists(fn):
|
||||
config.translate_files.append(fn)
|
||||
|
||||
config.translate_comments = config.translate_files
|
||||
|
||||
|
||||
DARK_COLORS = [
|
||||
"#0099cc",
|
||||
"#99ccff",
|
||||
"#66cc00",
|
||||
"#cccc00",
|
||||
"#cc6600",
|
||||
# "#cc3300",
|
||||
|
||||
"#0066cc",
|
||||
"#9933ff",
|
||||
"#00cc99",
|
||||
"#cc0066",
|
||||
"#cc0000",
|
||||
]
|
||||
|
||||
LIGHT_COLORS = [
|
||||
"#003366", # Dark Blue
|
||||
"#0099ff", # Light Blue
|
||||
"#336600", # Green
|
||||
"#000000", # Black
|
||||
"#cc6600", # Orange
|
||||
|
||||
"#000066", # Darkest Blue
|
||||
"#660066", # Purple
|
||||
"#006666", # Dark Green
|
||||
"#cc0066", # Pinkish
|
||||
"#990000", # Red
|
||||
]
|
||||
|
||||
|
||||
# LIGHT_COLORS = DARK_COLORS
|
||||
|
||||
COLOR_OPTIONS = [
|
||||
(i, "#000000", False) for i in DARK_COLORS
|
||||
] + [
|
||||
(i, "#ffffff", True) for i in LIGHT_COLORS
|
||||
]
|
||||
|
||||
|
||||
screen gui_swatches():
|
||||
|
||||
grid 5 4:
|
||||
|
||||
for accent, bg, light in COLOR_OPTIONS:
|
||||
|
||||
frame:
|
||||
style "empty"
|
||||
xysize (85, 60)
|
||||
|
||||
add Color(accent).replace_hsv_saturation(.25).replace_value(.5)
|
||||
|
||||
if light:
|
||||
add Color(bg).opacity(.9)
|
||||
else:
|
||||
add Color(bg).opacity(.8)
|
||||
|
||||
button:
|
||||
style "empty"
|
||||
|
||||
if light:
|
||||
selected_background "#000"
|
||||
else:
|
||||
selected_background "#fff"
|
||||
|
||||
xpadding 3
|
||||
ypadding 3
|
||||
xmargin 10
|
||||
ymargin 10
|
||||
|
||||
action SetVariable("gui_color", (accent, bg, light))
|
||||
|
||||
idle_child Solid(accent)
|
||||
hover_child Solid(Color(accent).tint(.6))
|
||||
|
||||
screen gui_demo(accent, boring, light, display):
|
||||
|
||||
$ p = gui7.GuiParameters(
|
||||
"-",
|
||||
"-",
|
||||
1280,
|
||||
720,
|
||||
accent,
|
||||
boring,
|
||||
light,
|
||||
None,
|
||||
False,
|
||||
False,
|
||||
False,
|
||||
"-"
|
||||
)
|
||||
|
||||
frame:
|
||||
style "empty"
|
||||
|
||||
add p.menu_color
|
||||
|
||||
if light:
|
||||
add Solid(p.boring_color.opacity(.9))
|
||||
else:
|
||||
add Solid(p.boring_color.opacity(.8))
|
||||
|
||||
|
||||
frame:
|
||||
style "empty"
|
||||
|
||||
xpadding 10
|
||||
ypadding 10
|
||||
|
||||
has vbox
|
||||
|
||||
text _("Display"):
|
||||
style "empty"
|
||||
font (gui.system_font or "DejaVuSans.ttf")
|
||||
color p.accent_color
|
||||
size 24
|
||||
|
||||
for i in [ _("Window"), _("Fullscreen"), _("Planetarium") ]:
|
||||
|
||||
textbutton i:
|
||||
action (None if i == "Planetarium" else SetScreenVariable("display", i))
|
||||
style "empty"
|
||||
|
||||
text_style "empty"
|
||||
text_font (gui.system_font or "DejaVuSans.ttf")
|
||||
text_size 24
|
||||
|
||||
text_color p.idle_color
|
||||
text_hover_color p.hover_color
|
||||
text_selected_color p.selected_color
|
||||
text_insensitive_color p.insensitive_color
|
||||
|
||||
xmargin 4
|
||||
ymargin 4
|
||||
left_padding 21
|
||||
|
||||
selected_background Solid(p.accent_color, xsize=5)
|
||||
|
||||
null height 30
|
||||
|
||||
text _("Text Speed"):
|
||||
style "empty"
|
||||
color p.accent_color
|
||||
size 24
|
||||
font (gui.system_font or "DejaVuSans.ttf")
|
||||
|
||||
bar:
|
||||
value ScreenVariableValue("value", 1.0)
|
||||
style "empty"
|
||||
base_bar Solid(p.muted_color)
|
||||
hover_base_bar Solid(p.hover_muted_color)
|
||||
|
||||
thumb Solid(p.accent_color, xsize=10)
|
||||
hover_thumb Solid(p.hover_color, xsize=10)
|
||||
|
||||
ysize 30
|
||||
|
||||
|
||||
screen choose_gui_color():
|
||||
|
||||
default display = "Window"
|
||||
default value = 0.5
|
||||
|
||||
frame:
|
||||
style_group "l"
|
||||
style "l_root"
|
||||
|
||||
window:
|
||||
has vbox
|
||||
|
||||
label _("Select Accent and Background Colors")
|
||||
|
||||
frame:
|
||||
style "l_indent"
|
||||
|
||||
has hbox:
|
||||
yfill True
|
||||
|
||||
frame:
|
||||
style "l_default"
|
||||
xsize 425
|
||||
|
||||
has vbox
|
||||
|
||||
text _("Please click on the color scheme you wish to use, then click Continue. These colors can be changed and customized later.")
|
||||
|
||||
add SPACER
|
||||
|
||||
use gui_swatches()
|
||||
|
||||
|
||||
# Preview
|
||||
frame:
|
||||
xsize 350
|
||||
|
||||
style "l_default"
|
||||
background Frame(PATTERN, 0, 0, tile=True)
|
||||
xpadding 5
|
||||
ypadding 5
|
||||
|
||||
xfill True
|
||||
yfill True
|
||||
xmargin 20
|
||||
bottom_margin 6
|
||||
|
||||
use gui_demo(gui_color[0], gui_color[1], gui_color[2], display)
|
||||
|
||||
textbutton _("Return") action Jump("front_page") style "l_left_button"
|
||||
|
||||
if gui_color:
|
||||
textbutton _("Continue") action Return(True) style "l_right_button"
|
||||
key "input_enter" action Return(True)
|
||||
|
||||
|
||||
label change_gui:
|
||||
|
||||
python:
|
||||
|
||||
gui_new = False
|
||||
gui_replace_images = True
|
||||
gui_update_code = True
|
||||
gui_replace_code = False
|
||||
|
||||
project.current.update_dump(True)
|
||||
gui_size = tuple(project.current.dump["size"])
|
||||
|
||||
project_dir = project.current.path
|
||||
project_name = project.current.name
|
||||
|
||||
mode = interface.choice(
|
||||
__("{b}Warning{/b}\nContinuing will overwrite customized bar, button, save slot, scrollbar, and slider images.\n\nWhat would you like to do?") +
|
||||
__("{size=-4}\n\nThis will not overwrite gui/main_menu.png, gui/game_menu.png, and gui/window_icon.png, but will create files that do not exist.{/size}"),
|
||||
[
|
||||
("change", _("Choose new colors, then regenerate image files.")),
|
||||
("regenerate", _("Regenerate the image files using the colors in gui.rpy.")),
|
||||
],
|
||||
None,
|
||||
cancel=Jump("front_page"),
|
||||
)
|
||||
|
||||
if mode == "change":
|
||||
jump gui_project_common
|
||||
else:
|
||||
jump gui_generate_images
|
||||
|
||||
label new_gui_project:
|
||||
|
||||
python:
|
||||
gui_new = True
|
||||
gui_replace_images = True
|
||||
gui_replace_code = True
|
||||
gui_update_code = True
|
||||
|
||||
|
||||
label gui_project_size:
|
||||
|
||||
python:
|
||||
|
||||
default_size = (1920, 1080)
|
||||
|
||||
gui_size = interface.choice(
|
||||
_("What resolution should the project use? Although Ren'Py can scale the window up and down, this is the initial size of the window, the size at which assets should be drawn, and the size at which the assets will be at their sharpest.\n\nThe default of [default_size[0]]x[default_size[1]] is a reasonable compromise."),
|
||||
[
|
||||
((1280, 720), "1280x720"),
|
||||
((1920, 1080), "1920x1080"),
|
||||
((2560, 1440), "2560x1440"),
|
||||
((3840, 2160), "3840x2160"),
|
||||
("custom", _("Custom. The GUI is optimized for a 16:9 aspect ratio.")),
|
||||
],
|
||||
default_size,
|
||||
cancel=Jump("front_page"),
|
||||
)
|
||||
|
||||
if gui_size == "custom":
|
||||
|
||||
gui_width = ""
|
||||
while True:
|
||||
gui_width = interface.input(
|
||||
_("WIDTH"),
|
||||
_("Please enter the width of your game, in pixels."),
|
||||
cancel=Jump("front_page"),
|
||||
allow=interface.DIGITS_LETTERS,
|
||||
)
|
||||
|
||||
try:
|
||||
gui_width = int(gui_width)
|
||||
except Exception:
|
||||
interface.error(_("The width must be a number."), label=None)
|
||||
continue
|
||||
break
|
||||
|
||||
gui_height = ""
|
||||
while True:
|
||||
gui_height = interface.input(
|
||||
_("HEIGHT"),
|
||||
_("Please enter the height of your game, in pixels."),
|
||||
cancel=Jump("front_page"),
|
||||
allow=interface.DIGITS_LETTERS,
|
||||
)
|
||||
|
||||
try:
|
||||
gui_height = int(gui_height)
|
||||
except Exception:
|
||||
interface.error(_("The height must be a number."), label=None)
|
||||
continue
|
||||
break
|
||||
|
||||
gui_size = (gui_width, gui_height)
|
||||
|
||||
|
||||
label gui_project_common:
|
||||
|
||||
$ gui_color = (DARK_COLORS[0], "#000000", False)
|
||||
|
||||
call screen choose_gui_color
|
||||
|
||||
python hide:
|
||||
|
||||
width, height = gui_size
|
||||
accent, boring, light = gui_color
|
||||
|
||||
prefix = os.path.join(project_dir, "game")
|
||||
|
||||
if not os.path.isdir(prefix) and not gui_new:
|
||||
interface.error("{} does not appear to be a Ren'Py game.".format(prefix))
|
||||
|
||||
template = os.path.join(config.renpy_base, "gui", "game")
|
||||
|
||||
if not os.path.isdir(template):
|
||||
interface.error("{} does not appear to be a Ren'Py game.".format(template))
|
||||
|
||||
p = gui7.GuiParameters(
|
||||
prefix,
|
||||
template,
|
||||
width,
|
||||
height,
|
||||
accent,
|
||||
boring,
|
||||
light,
|
||||
_preferences.language,
|
||||
gui_replace_images,
|
||||
gui_replace_code,
|
||||
gui_update_code,
|
||||
project_name,
|
||||
)
|
||||
|
||||
if gui_new:
|
||||
interface.processing(_("Creating the new project..."))
|
||||
else:
|
||||
interface.processing(_("Updating the project..."))
|
||||
|
||||
with interface.error_handling(_("creating a new project")):
|
||||
gui7.generate_gui(p)
|
||||
|
||||
# Activate the project.
|
||||
with interface.error_handling(_("activating the new project")):
|
||||
project.manager.scan()
|
||||
project.Select(project.manager.get(project_name))()
|
||||
|
||||
if gui_new:
|
||||
|
||||
call update_renpy_strings_common
|
||||
|
||||
python hide:
|
||||
if gui.project_system_font:
|
||||
with open(os.path.join(project.current.gamedir, "tl/None/common.rpym"), "ab") as f:
|
||||
f.write("define gui.system_font = {!r}\r\n".format(gui.project_system_font).encode("utf-8"))
|
||||
|
||||
|
||||
label gui_generate_images:
|
||||
|
||||
python:
|
||||
|
||||
interface.processing(_("Updating the project..."))
|
||||
project.current.launch([ 'gui_images' ], env={ "RENPY_VARIANT" : "small phone" }, wait=True)
|
||||
project.current.launch([ 'gui_images' ], wait=True)
|
||||
|
||||
jump front_page
|
||||
|
||||
label new_template_project:
|
||||
|
||||
# Unused in new_template_project.
|
||||
$ gui_size = (1920, 1080)
|
||||
$ gui_color = (DARK_COLORS[0], "#000000", False)
|
||||
|
||||
python hide:
|
||||
|
||||
width, height = gui_size
|
||||
accent, boring, light = gui_color
|
||||
|
||||
prefix = os.path.join(project_dir, "game")
|
||||
template = os.path.join(gui_template_path, "game")
|
||||
|
||||
# Most of this isn't actually used.
|
||||
p = gui7.GuiParameters(
|
||||
prefix,
|
||||
template,
|
||||
width,
|
||||
height,
|
||||
accent,
|
||||
boring,
|
||||
light,
|
||||
_preferences.language,
|
||||
False,
|
||||
True,
|
||||
True,
|
||||
project_name,
|
||||
)
|
||||
|
||||
interface.processing(_("Creating the new project..."))
|
||||
|
||||
with interface.error_handling(_("creating a new project")):
|
||||
gui7.generate_minimal(p)
|
||||
|
||||
# Activate the project.
|
||||
with interface.error_handling(_("activating the new project")):
|
||||
project.manager.scan()
|
||||
project.Select(project.manager.get(project_name))()
|
||||
|
||||
call update_renpy_strings_common
|
||||
|
||||
python hide:
|
||||
if gui.project_system_font:
|
||||
with open(os.path.join(project.current.gamedir, "tl/None/common.rpym"), "ab") as f:
|
||||
f.write("define gui.system_font = {!r}\r\n".format(gui.project_system_font).encode("utf-8"))
|
||||
|
||||
jump front_page
|
||||
@@ -1,152 +0,0 @@
|
||||
# Copyright 2004-2025 Tom Rothamel <pytom@bishoujo.us>
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person
|
||||
# obtaining a copy of this software and associated documentation files
|
||||
# (the "Software"), to deal in the Software without restriction,
|
||||
# including without limitation the rights to use, copy, modify, merge,
|
||||
# publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
# and to permit persons to whom the Software is furnished to do so,
|
||||
# subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
from gui7.code import CodeGenerator, translate_define, translate_copy, translate_code
|
||||
from gui7.images import ImageGenerator
|
||||
from gui7.parameters import GuiParameters
|
||||
|
||||
import renpy.arguments
|
||||
import os
|
||||
import shutil
|
||||
|
||||
|
||||
|
||||
def finish(p):
|
||||
|
||||
for dn in [ "images", "audio", "libs" ]:
|
||||
|
||||
fulldn = os.path.join(p.prefix, dn)
|
||||
|
||||
if not os.path.exists(fulldn):
|
||||
os.mkdir(fulldn)
|
||||
|
||||
with open(os.path.join(p.prefix, "libs", "libs.txt"), "w") as f:
|
||||
f.write(renpy.translation.translate_string(renpy.store.LIBS_TXT, language=p.language))
|
||||
|
||||
def generate_gui(p):
|
||||
|
||||
ImageGenerator(p).generate_all()
|
||||
CodeGenerator(p).generate_gui("gui.rpy", defines=True)
|
||||
CodeGenerator(p).generate_gui("screens.rpy")
|
||||
CodeGenerator(p).generate_code("options.rpy")
|
||||
CodeGenerator(p).copy_script("script.rpy")
|
||||
CodeGenerator(p).copy_files()
|
||||
|
||||
finish(p)
|
||||
|
||||
|
||||
def generate_minimal(p):
|
||||
|
||||
# Copy the template over.
|
||||
os.makedirs(os.path.dirname(p.prefix), 0o777)
|
||||
shutil.copytree(p.template, p.prefix)
|
||||
|
||||
def delete(fn):
|
||||
fn = os.path.join(p.prefix, fn)
|
||||
|
||||
if os.path.isdir(fn):
|
||||
shutil.rmtree(fn)
|
||||
elif os.path.exists(fn):
|
||||
os.unlink(fn)
|
||||
|
||||
# Prune directories.
|
||||
delete("cache")
|
||||
delete("saves")
|
||||
delete("tl")
|
||||
|
||||
# Prune files to be regenerated.
|
||||
delete("gui.rpy")
|
||||
delete("screens.rpy")
|
||||
delete("options.rpy")
|
||||
|
||||
# Generate files.
|
||||
CodeGenerator(p).generate_code("gui.rpy")
|
||||
CodeGenerator(p).generate_code("screens.rpy")
|
||||
CodeGenerator(p).generate_code("options.rpy")
|
||||
CodeGenerator(p).copy_files()
|
||||
|
||||
finish(p)
|
||||
|
||||
|
||||
def generate_gui_command():
|
||||
|
||||
ap = renpy.arguments.ArgumentParser()
|
||||
|
||||
ap.add_argument("target", action="store", help="The game into which the generated gui should be placed.")
|
||||
ap.add_argument("--width", default=1280, action="store", type=int, help="The width of the generated gui.")
|
||||
ap.add_argument("--height", default=720, action="store", type=int, help="The height of the generated gui.")
|
||||
ap.add_argument("--accent", default="#00b8c3", action="store", help="The accent color used throughout the gui.")
|
||||
ap.add_argument("--boring", default="#000000", action="store", help="The boring color used for the gui background.")
|
||||
ap.add_argument("--light", default=False, action="store_true", help="True if this is considered a light theme.")
|
||||
ap.add_argument("--template", default="gui", action="store", help="The template directory containing source code.")
|
||||
ap.add_argument("--language", default=None, action="store", help="The language to translate strings and comments to.")
|
||||
|
||||
ap.add_argument("--start", default=False, action="store_true", help="Starts a new project, replacing images and code.")
|
||||
ap.add_argument("--replace-images", default=False, action="store_true", help="True if existing images should be overwritten.")
|
||||
ap.add_argument("--replace-code", default=False, action="store_true", help="True if an existing gui.rpy file should be overwritten.")
|
||||
ap.add_argument("--update-code", default=False, action="store_true", help="True if an existing gui.rpy file should be updated.")
|
||||
|
||||
ap.add_argument("--minimal", default=False, action="store_true", help="Only update option.rpy and translations.")
|
||||
|
||||
args = ap.parse_args()
|
||||
|
||||
if args.start:
|
||||
args.replace_images = True
|
||||
args.replace_code = True
|
||||
args.update_code = True
|
||||
|
||||
if args.minimal:
|
||||
args.replace_code = True
|
||||
args.update_code = True
|
||||
|
||||
prefix = os.path.join(args.target, "game")
|
||||
|
||||
if os.path.exists(args.target):
|
||||
|
||||
if not os.path.isdir(prefix):
|
||||
ap.error("{} does not appear to be a Ren'Py game.".format(prefix))
|
||||
|
||||
template = os.path.join(args.template, "game")
|
||||
|
||||
if not os.path.isdir(template):
|
||||
ap.error("{} does not appear to be a Ren'Py game.".format(template))
|
||||
|
||||
p = GuiParameters(
|
||||
prefix,
|
||||
template,
|
||||
args.width,
|
||||
args.height,
|
||||
args.accent,
|
||||
args.boring,
|
||||
args.light,
|
||||
args.language,
|
||||
args.replace_images,
|
||||
args.replace_code,
|
||||
args.update_code,
|
||||
os.path.basename(args.target),
|
||||
)
|
||||
|
||||
if args.minimal:
|
||||
generate_minimal(p)
|
||||
else:
|
||||
generate_gui(p)
|
||||
|
||||
renpy.arguments.register_command("generate_gui", generate_gui_command)
|
||||
@@ -1,442 +0,0 @@
|
||||
# Copyright 2004-2025 Tom Rothamel <pytom@bishoujo.us>
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person
|
||||
# obtaining a copy of this software and associated documentation files
|
||||
# (the "Software"), to deal in the Software without restriction,
|
||||
# including without limitation the rights to use, copy, modify, merge,
|
||||
# publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
# and to permit persons to whom the Software is furnished to do so,
|
||||
# subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import os
|
||||
import codecs
|
||||
import re
|
||||
import math
|
||||
import textwrap
|
||||
import collections
|
||||
import shutil
|
||||
|
||||
import renpy
|
||||
|
||||
|
||||
class Define(object):
|
||||
|
||||
def __init__(self, name, value, comment):
|
||||
self.name = name
|
||||
self.value = value
|
||||
self.comment = comment
|
||||
|
||||
|
||||
# A map from language name to a list of defines.
|
||||
language_defines = collections.defaultdict(list)
|
||||
|
||||
|
||||
def translate_define(language, name, value, comment=None):
|
||||
"""
|
||||
This function should be called to register the value of a define that is
|
||||
set when generating code in `language`.
|
||||
|
||||
`name`
|
||||
A string giving the name of the define.
|
||||
|
||||
`value`
|
||||
A string giving the value of the define. Be sure to quote it properly,
|
||||
or use repr().
|
||||
|
||||
`comment`
|
||||
If not None, a comment that will be generated before the define. The
|
||||
comment will only be generated if the define does not exist in
|
||||
gui.rpy. There is no need to use "## ", as the comment will be
|
||||
added and wrapped automatically.
|
||||
"""
|
||||
|
||||
language_defines[language].append(Define(name, value, comment))
|
||||
|
||||
|
||||
# A map from a language name to a list of (src, dst) pairs. Each represents a
|
||||
# file that is copied into place.
|
||||
language_copies = collections.defaultdict(list)
|
||||
|
||||
|
||||
def translate_copy(language, src, dst):
|
||||
"""
|
||||
This function should be called to copy a file from `src` to `dst`
|
||||
when generating code in `language`.
|
||||
|
||||
`src`
|
||||
A path, relative to the launcher game directory.
|
||||
|
||||
`dst`
|
||||
A path, relative to the game directory of the new game.
|
||||
"""
|
||||
|
||||
language_copies[language].append((src, dst))
|
||||
|
||||
|
||||
# A map from language name and filename to code that should be added to the
|
||||
# end of a newly-generated file.
|
||||
language_code = collections.defaultdict(list)
|
||||
|
||||
|
||||
def translate_code(language, filename, code):
|
||||
"""
|
||||
This function can be called to include a block of code verbatim
|
||||
into `file` when a game is generated in `language`.
|
||||
"""
|
||||
|
||||
language_code[language, filename].extend([''] + code.split("\n"))
|
||||
|
||||
|
||||
class CodeGenerator(object):
|
||||
"""
|
||||
This is used to generate and update the GUI code.
|
||||
"""
|
||||
|
||||
def __init__(self, parameters):
|
||||
"""
|
||||
Generates or updates gui.rpy.
|
||||
"""
|
||||
|
||||
self.p = parameters
|
||||
|
||||
def load_template(self, filename):
|
||||
|
||||
target = os.path.join(self.p.prefix, filename)
|
||||
|
||||
if os.path.exists(target) and not self.p.replace_code:
|
||||
template = target
|
||||
else:
|
||||
template = os.path.join(self.p.template, filename)
|
||||
|
||||
if not os.path.exists(template):
|
||||
return False
|
||||
else:
|
||||
with codecs.open(template, "r", "utf-8") as f:
|
||||
self.lines = [ i.rstrip().replace(u"\ufeff", "") for i in f ]
|
||||
|
||||
return True
|
||||
|
||||
def remove_scale(self):
|
||||
|
||||
def scale(m):
|
||||
original = int(m.group(1))
|
||||
scaled = int(math.ceil(original * self.p.scale))
|
||||
return str(scaled)
|
||||
|
||||
lines = [ ]
|
||||
|
||||
for l in self.lines:
|
||||
l = re.sub(r'gui.scale\((.*?)\)', scale, l)
|
||||
lines.append(l)
|
||||
|
||||
self.lines = lines
|
||||
|
||||
def update_size(self):
|
||||
|
||||
gui_init = "gui.init({}, {})".format(self.p.width, self.p.height)
|
||||
|
||||
lines = [ ]
|
||||
|
||||
for l in self.lines:
|
||||
l = re.sub(r'gui.init\(.*?\)', gui_init, l)
|
||||
lines.append(l)
|
||||
|
||||
self.lines = lines
|
||||
|
||||
def update_defines(self, replacements, additions=[]):
|
||||
"""
|
||||
Replaces define statements in gui.rpy.
|
||||
"""
|
||||
|
||||
replacements = dict(replacements)
|
||||
|
||||
for d in additions:
|
||||
replacements[d.name] = d.value
|
||||
|
||||
seen = set()
|
||||
|
||||
lines = [ ]
|
||||
|
||||
for l in self.lines:
|
||||
|
||||
m = re.match(r'^(\s*)define (.*?) =', l)
|
||||
|
||||
if m:
|
||||
indent = m.group(1)
|
||||
variable = m.group(2)
|
||||
|
||||
if variable in replacements:
|
||||
l = "{}define {} = {}".format(indent, variable, replacements[variable])
|
||||
|
||||
seen.add(variable)
|
||||
|
||||
lines.append(l)
|
||||
|
||||
for d in additions:
|
||||
|
||||
if d.name in seen:
|
||||
continue
|
||||
|
||||
seen.add(d.name)
|
||||
|
||||
lines.append("")
|
||||
|
||||
if d.comment:
|
||||
for s in textwrap.wrap(d.comment):
|
||||
lines.append("## " + s)
|
||||
|
||||
lines.append("define {} = {}".format(d.name, d.value))
|
||||
|
||||
self.lines = lines
|
||||
|
||||
def update_gui_defines(self):
|
||||
"""
|
||||
Replaces define statements in gui.rpy.
|
||||
"""
|
||||
|
||||
replacements = {
|
||||
'gui.accent_color' : repr(self.p.accent_color.hexcode),
|
||||
'gui.selected_color' : repr(self.p.selected_color.hexcode),
|
||||
'gui.hover_color' : repr(self.p.hover_color.hexcode),
|
||||
'gui.muted_color' : repr(self.p.muted_color.hexcode),
|
||||
'gui.hover_muted_color' : repr(self.p.hover_muted_color.hexcode),
|
||||
'gui.title_color' : repr(self.p.title_color.hexcode),
|
||||
'gui.idle_color' : repr(self.p.idle_color.hexcode),
|
||||
'gui.idle_small_color' : repr(self.p.idle_small_color.hexcode),
|
||||
'gui.insensitive_color' : repr(self.p.insensitive_color.hexcode),
|
||||
'gui.text_color' : repr(self.p.text_color.hexcode),
|
||||
'gui.interface_text_color' : repr(self.p.text_color.hexcode),
|
||||
'gui.choice_button_text_idle_color' : repr(self.p.idle_color.hexcode),
|
||||
'gui.choice_button_text_insensitive_color' : repr(self.p.insensitive_color.hexcode),
|
||||
}
|
||||
|
||||
self.update_defines(replacements, language_defines[self.p.language])
|
||||
|
||||
def update_options_defines(self):
|
||||
"""
|
||||
Replaces define statements in options.rpy.
|
||||
"""
|
||||
|
||||
def quote(s):
|
||||
s = s.replace("\\", "\\\\")
|
||||
s = s.replace("\"", "\\\"")
|
||||
return '"' + s + '"'
|
||||
|
||||
replacements = {
|
||||
'config.name' : "_({})".format(quote(self.p.name)),
|
||||
'build.name' : quote(self.p.simple_name),
|
||||
'config.save_directory' : quote(self.p.savedir),
|
||||
}
|
||||
|
||||
self.update_defines(replacements)
|
||||
|
||||
def write_target(self, filename):
|
||||
|
||||
target = os.path.join(self.p.prefix, filename)
|
||||
|
||||
if os.path.exists(target):
|
||||
|
||||
backup = 1
|
||||
|
||||
while True:
|
||||
|
||||
bfn = "{}.{}.bak".format(target, backup)
|
||||
|
||||
if not os.path.exists(bfn):
|
||||
break
|
||||
|
||||
backup += 1
|
||||
|
||||
if not self.p.skip_backup:
|
||||
os.rename(target, bfn)
|
||||
|
||||
with codecs.open(target, "w", "utf-8") as f:
|
||||
f.write(u"\ufeff")
|
||||
|
||||
for l in self.lines:
|
||||
f.write(l + "\r\n")
|
||||
|
||||
def translate_strings(self):
|
||||
|
||||
def replace(m):
|
||||
s = eval(m.group(1))
|
||||
s = renpy.translation.translate_string(s, language=self.p.language)
|
||||
s = renpy.translation.quote_unicode(s)
|
||||
|
||||
quote = m.group(1)[0]
|
||||
|
||||
s = u"_({}{}{})".format(quote, s, quote)
|
||||
|
||||
return s
|
||||
|
||||
lines = [ ]
|
||||
|
||||
for l in self.lines:
|
||||
|
||||
l = re.sub(r'_\((\".*?\")\)', replace, l)
|
||||
l = re.sub(r'_\((\'.*?\')\)', replace, l)
|
||||
|
||||
lines.append(l)
|
||||
|
||||
self.lines = lines
|
||||
|
||||
def translate_comments(self):
|
||||
|
||||
lines = [ ]
|
||||
|
||||
comment = [ ]
|
||||
indent = ""
|
||||
|
||||
for l in self.lines:
|
||||
|
||||
m = re.match(r'^(\s*## )(.*)', l.rstrip())
|
||||
|
||||
if m:
|
||||
|
||||
indent = m.group(1)
|
||||
c = m.group(2)
|
||||
|
||||
if comment:
|
||||
c = c.strip()
|
||||
|
||||
comment.append(c)
|
||||
|
||||
else:
|
||||
|
||||
if comment:
|
||||
s = "## " + ' '.join(comment)
|
||||
|
||||
if s.endswith("#"):
|
||||
hashpad = True
|
||||
s = s.rstrip('# ')
|
||||
else:
|
||||
hashpad = False
|
||||
|
||||
s = renpy.translation.translate_string(s, language=self.p.language)
|
||||
|
||||
m = re.match(r'## ?([ *]*)(.*)', s)
|
||||
|
||||
if m is None:
|
||||
raise Exception("Comment translation doesn't start with '## ': {}".format(s))
|
||||
|
||||
prefix = m.group(1)
|
||||
empty = ' ' * len(prefix)
|
||||
rest = m.group(2)
|
||||
|
||||
len_prefix = len(indent) + len(prefix)
|
||||
len_wrap = 80 - len_prefix
|
||||
|
||||
import store.gui
|
||||
|
||||
for i, s in enumerate(renpy.text.extras.textwrap(rest, len_wrap, store.gui.asian)):
|
||||
|
||||
if i == 0:
|
||||
s = indent + prefix + s
|
||||
else:
|
||||
s = indent + empty + s
|
||||
|
||||
if hashpad and len(s) < 79:
|
||||
s = s + ' ' + "#" * (79 - len(s))
|
||||
|
||||
lines.append(s)
|
||||
|
||||
comment = [ ]
|
||||
|
||||
lines.append(l)
|
||||
|
||||
self.lines = lines
|
||||
|
||||
def copy_files(self):
|
||||
|
||||
for src, dst in language_copies[self.p.language]:
|
||||
src = os.path.join(renpy.config.renpy_base, src)
|
||||
dst = os.path.join(self.p.prefix, dst)
|
||||
|
||||
if os.path.exists(dst):
|
||||
continue
|
||||
|
||||
dstdir = os.path.dirname(dst)
|
||||
|
||||
if not os.path.exists(dstdir):
|
||||
os.makedirs(dstdir, 0o777)
|
||||
|
||||
shutil.copy(src, dst)
|
||||
|
||||
def copy_script(self, name):
|
||||
dst = os.path.join(self.p.prefix, name)
|
||||
|
||||
if os.path.exists(dst):
|
||||
return
|
||||
|
||||
language = renpy.store._preferences.language # @UndefinedVariable
|
||||
|
||||
if language is None:
|
||||
language = "None"
|
||||
|
||||
src = os.path.join(renpy.config.gamedir, "tl", language, name + "m")
|
||||
|
||||
if not os.path.exists(src):
|
||||
src = os.path.join(self.p.template, name)
|
||||
|
||||
if not self.load_template(src):
|
||||
return
|
||||
|
||||
self.remove_scale()
|
||||
self.write_target(dst)
|
||||
|
||||
def add_code(self, fn):
|
||||
|
||||
if not self.p.replace_code:
|
||||
return
|
||||
|
||||
self.lines.extend(language_code[self.p.language, fn])
|
||||
|
||||
def generate_gui(self, fn, defines=False):
|
||||
if not self.p.update_code:
|
||||
return
|
||||
|
||||
if not self.load_template(fn):
|
||||
return
|
||||
|
||||
if defines:
|
||||
self.update_gui_defines()
|
||||
|
||||
if self.p.replace_code:
|
||||
self.remove_scale()
|
||||
self.update_size()
|
||||
self.translate_strings()
|
||||
self.translate_comments()
|
||||
self.add_code(fn)
|
||||
|
||||
self.write_target(fn)
|
||||
|
||||
def generate_code(self, fn):
|
||||
|
||||
target = os.path.join(self.p.prefix, fn)
|
||||
|
||||
if os.path.exists(target):
|
||||
return
|
||||
|
||||
if not self.load_template(fn):
|
||||
return
|
||||
|
||||
self.translate_strings()
|
||||
self.translate_comments()
|
||||
self.update_options_defines()
|
||||
|
||||
self.add_code(fn)
|
||||
|
||||
self.write_target(fn)
|
||||
|
Before Width: | Height: | Size: 14 KiB |
@@ -1,447 +0,0 @@
|
||||
# Copyright 2004-2025 Tom Rothamel <pytom@bishoujo.us>
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person
|
||||
# obtaining a copy of this software and associated documentation files
|
||||
# (the "Software"), to deal in the Software without restriction,
|
||||
# including without limitation the rights to use, copy, modify, merge,
|
||||
# publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
# and to permit persons to whom the Software is furnished to do so,
|
||||
# subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
import pygame_sdl2
|
||||
import os
|
||||
|
||||
from renpy.store import config
|
||||
import renpy.display
|
||||
|
||||
|
||||
class ImageGenerator(object):
|
||||
|
||||
def __init__(self, parameters):
|
||||
|
||||
pygame_sdl2.image.init()
|
||||
|
||||
self.p = p = parameters
|
||||
|
||||
self.width = p.width
|
||||
self.height = p.height
|
||||
|
||||
self.scale = p.scale
|
||||
|
||||
self.accent_color = p.accent_color
|
||||
self.boring_color = p.boring_color
|
||||
|
||||
self.hover_color = p.hover_color
|
||||
self.muted_color = p.muted_color
|
||||
self.hover_muted_color = p.hover_muted_color
|
||||
|
||||
self.menu_color = p.menu_color
|
||||
|
||||
self.prefix = os.path.join(p.prefix, "gui", "")
|
||||
|
||||
try:
|
||||
os.mkdir(self.prefix, 0o777)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
self.full_width = self.width / self.scale
|
||||
self.full_height = self.height / self.scale
|
||||
|
||||
self.overwrite = p.replace_images
|
||||
|
||||
def scale_int(self, n):
|
||||
rv = int(n * self.scale)
|
||||
|
||||
if rv < 1:
|
||||
rv = 1
|
||||
|
||||
return rv
|
||||
|
||||
def rescale_template(self, t):
|
||||
|
||||
rv = [ ]
|
||||
|
||||
for pos, opacity in t:
|
||||
rv.append((pos * self.scale, opacity))
|
||||
|
||||
return rv
|
||||
|
||||
def generate_line(self, template):
|
||||
|
||||
size = int(max(i[0] for i in template))
|
||||
|
||||
rv = [ ]
|
||||
|
||||
right_pos, right_value = template[0]
|
||||
|
||||
for i in range(size):
|
||||
|
||||
if i == right_pos:
|
||||
rv.append(right_value)
|
||||
continue
|
||||
|
||||
while i >= right_pos:
|
||||
left_pos = right_pos
|
||||
left_value = right_value
|
||||
|
||||
right_pos, right_value = template.pop(0)
|
||||
|
||||
done = 1.0 * (i - left_pos) / (right_pos - left_pos)
|
||||
rv.append(left_value + done * (right_value - left_value))
|
||||
|
||||
return rv
|
||||
|
||||
def crop_line(self, line, size):
|
||||
"""
|
||||
Crops the center `size` pixels out of `line`.
|
||||
"""
|
||||
|
||||
if len(line) <= size:
|
||||
return line
|
||||
|
||||
start = (len(line) - size) // 2
|
||||
|
||||
return line[start:start + size ]
|
||||
|
||||
def save(self, s, filename, overwrite=True):
|
||||
|
||||
fn = self.prefix + filename + ".png"
|
||||
dn = os.path.dirname(fn)
|
||||
|
||||
try:
|
||||
os.makedirs(dn, 0o777)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if os.path.exists(fn):
|
||||
|
||||
if not overwrite:
|
||||
return
|
||||
|
||||
if not self.overwrite:
|
||||
return
|
||||
|
||||
index = 1
|
||||
|
||||
while True:
|
||||
bfn = u"{}.{}.bak".format(fn, index)
|
||||
|
||||
if not os.path.exists(bfn):
|
||||
break
|
||||
|
||||
index += 1
|
||||
|
||||
if not self.p.skip_backup:
|
||||
os.rename(fn, bfn)
|
||||
|
||||
pygame_sdl2.image.save(s, fn, 3)
|
||||
|
||||
def make_surface(self, width, height):
|
||||
return pygame_sdl2.Surface((width, height), pygame_sdl2.SRCALPHA)
|
||||
|
||||
def generate_image(self, filename, xtmpl, ytmpl, color=(0, 0, 0, 255)):
|
||||
|
||||
r, g, b, a = color
|
||||
|
||||
xtmpl = self.rescale_template(xtmpl)
|
||||
ytmpl = self.rescale_template(ytmpl)
|
||||
|
||||
xline = self.generate_line(xtmpl)
|
||||
yline = self.generate_line(ytmpl)
|
||||
|
||||
xline = self.crop_line(xline, self.width)
|
||||
yline = self.crop_line(yline, self.height)
|
||||
|
||||
s = self.make_surface(len(xline), len(yline))
|
||||
|
||||
for x, xv in enumerate(xline):
|
||||
for y, yv in enumerate(yline):
|
||||
v = xv * yv
|
||||
s.set_at((x, y), (r, g, b, int(a * v)))
|
||||
|
||||
self.save(s, filename)
|
||||
|
||||
def generate_textbox(self):
|
||||
|
||||
XSIZE = self.full_width
|
||||
XINSIDE = (XSIZE - 744) // 2
|
||||
|
||||
YSIZE = 185
|
||||
YBORDER = 5
|
||||
|
||||
X = [
|
||||
(0, 0.0),
|
||||
(XINSIDE, 1.0),
|
||||
(XSIZE - XINSIDE, 1.0),
|
||||
(XSIZE, 0.0),
|
||||
]
|
||||
|
||||
Y = [
|
||||
(0, 0.0),
|
||||
(YBORDER, 1.0),
|
||||
(YSIZE, 1.0),
|
||||
]
|
||||
|
||||
self.generate_image("textbox", X, Y, self.boring_color.opacity(.8))
|
||||
|
||||
YSIZE = 240
|
||||
YBORDER = 5
|
||||
|
||||
X = [ (0, 1.0), (self.full_width, 1.0) ]
|
||||
|
||||
Y = [
|
||||
(0, 0.0),
|
||||
(YBORDER, 1.0),
|
||||
(YSIZE, 1.0),
|
||||
]
|
||||
|
||||
self.generate_image("phone/textbox", X, Y, self.boring_color.opacity(.8))
|
||||
|
||||
width = self.scale_int(300)
|
||||
height = self.scale_int(36)
|
||||
|
||||
s = self.make_surface(width, height)
|
||||
self.save(s, "namebox")
|
||||
|
||||
def generate_nvl(self):
|
||||
XSIZE = self.full_width
|
||||
XINSIDE = (XSIZE - 800) // 2
|
||||
|
||||
YSIZE = self.full_height
|
||||
|
||||
X = [
|
||||
(0, 0.0),
|
||||
(XINSIDE, 1.0),
|
||||
(XSIZE - XINSIDE, 1.0),
|
||||
(XSIZE, 0.0),
|
||||
]
|
||||
|
||||
Y = [
|
||||
(0, 1.0),
|
||||
(YSIZE, 1.0),
|
||||
]
|
||||
|
||||
self.generate_image("nvl", X, Y, self.boring_color.opacity(.8))
|
||||
|
||||
X = [
|
||||
(0, 1.0),
|
||||
(XSIZE, 1.0),
|
||||
]
|
||||
|
||||
Y = [
|
||||
(0, 1.0),
|
||||
(YSIZE, 1.0),
|
||||
]
|
||||
|
||||
self.generate_image("phone/nvl", X, Y, self.boring_color.opacity(.8))
|
||||
|
||||
def generate_choice_button(self):
|
||||
XSIZE = 790
|
||||
XINSIDE = 100
|
||||
|
||||
YSIZE = 35
|
||||
YBORDER = 3
|
||||
|
||||
X = [
|
||||
(0, 0.0),
|
||||
(XINSIDE, 1.0),
|
||||
(XSIZE - XINSIDE, 1.0),
|
||||
(XSIZE, 0.0),
|
||||
]
|
||||
|
||||
Y = [
|
||||
(0, 0.0),
|
||||
(YBORDER, 1.0),
|
||||
(YSIZE - YBORDER, 1.0),
|
||||
(YSIZE, 0.0),
|
||||
]
|
||||
|
||||
self.generate_image("button/choice_idle_background", X, Y, self.boring_color.opacity(.8))
|
||||
self.generate_image("button/choice_hover_background", X, Y, self.accent_color.opacity(.95))
|
||||
self.generate_image("phone/button/choice_idle_background", X, Y, self.boring_color.opacity(.8))
|
||||
self.generate_image("phone/button/choice_hover_background", X, Y, self.accent_color.opacity(.95))
|
||||
|
||||
def generate_overlay(self):
|
||||
|
||||
width = self.scale_int(280)
|
||||
phone_width = self.scale_int(340)
|
||||
|
||||
game_y = self.scale_int(120)
|
||||
game_height = self.scale_int(570)
|
||||
|
||||
line_width = self.scale_int(3)
|
||||
|
||||
if self.p.light:
|
||||
opacity = 0.9
|
||||
else:
|
||||
opacity = 0.8
|
||||
|
||||
# Main menu.
|
||||
mm = self.make_surface(self.width, self.height)
|
||||
mm.subsurface((0, 0, width, self.height)).fill(self.boring_color.opacity(opacity))
|
||||
mm.subsurface((width - line_width, 0, line_width, self.height)).fill(self.accent_color)
|
||||
self.save(mm, "overlay/main_menu")
|
||||
|
||||
mm = self.make_surface(self.width, self.height)
|
||||
mm.subsurface((0, 0, phone_width, self.height)).fill(self.boring_color.opacity(opacity))
|
||||
mm.subsurface((phone_width - line_width, 0, line_width, self.height)).fill(self.accent_color)
|
||||
self.save(mm, "phone/overlay/main_menu")
|
||||
|
||||
# Game menu.
|
||||
gm = self.make_surface(self.width, self.height)
|
||||
gm.fill(self.boring_color.opacity(opacity))
|
||||
gm.subsurface((width - line_width, game_y, line_width, game_height)).fill(self.accent_color)
|
||||
self.save(gm, "overlay/game_menu")
|
||||
|
||||
gm = self.make_surface(self.width, self.height)
|
||||
gm.fill(self.boring_color.opacity(opacity))
|
||||
gm.subsurface((phone_width - line_width, game_y, line_width, game_height)).fill(self.accent_color)
|
||||
self.save(gm, "phone/overlay/game_menu")
|
||||
|
||||
# Confirm.
|
||||
gm = self.make_surface(self.width, self.height)
|
||||
gm.fill(self.boring_color.opacity(.6))
|
||||
self.save(gm, "overlay/confirm")
|
||||
|
||||
def generate_frame(self):
|
||||
width = self.scale_int(600)
|
||||
height = self.scale_int(250)
|
||||
|
||||
border = self.scale_int(3)
|
||||
|
||||
s = self.make_surface(width, height)
|
||||
s.fill(self.accent_color)
|
||||
s.subsurface((border, border, width - 2 * border, height - 2 * border)).fill(self.boring_color)
|
||||
self.save(s, "frame")
|
||||
|
||||
def generate_quick_buttons(self):
|
||||
width = self.scale_int(100)
|
||||
height = self.scale_int(30)
|
||||
|
||||
s = self.make_surface(width, height)
|
||||
self.save(s, "button/quick_idle_background")
|
||||
self.save(s, "button/quick_hover_background")
|
||||
|
||||
def generate_skip(self):
|
||||
XSIZE = 240
|
||||
XRIGHT = 50
|
||||
|
||||
YSIZE = 43
|
||||
|
||||
X = [
|
||||
(0, 1.0),
|
||||
(XSIZE - XRIGHT, 1.0),
|
||||
(XSIZE, 0.0),
|
||||
]
|
||||
|
||||
Y = [
|
||||
(0, 1.0),
|
||||
(YSIZE, 1.0),
|
||||
]
|
||||
|
||||
self.generate_image("skip", X, Y, self.boring_color.opacity(.8))
|
||||
|
||||
def generate_notify(self):
|
||||
XSIZE = 922
|
||||
XRIGHT = 50
|
||||
|
||||
YSIZE = 43
|
||||
|
||||
X = [
|
||||
(0, 1.0),
|
||||
(XSIZE - XRIGHT, 1.0),
|
||||
(XSIZE, 0.0),
|
||||
]
|
||||
|
||||
Y = [
|
||||
(0, 1.0),
|
||||
(YSIZE, 1.0),
|
||||
]
|
||||
|
||||
self.generate_image("notify", X, Y, self.boring_color.opacity(.8))
|
||||
|
||||
def generate_icon(self):
|
||||
|
||||
icon_fn = os.path.join(config.renpy_base, "launcher", "game", "gui7", "icon.png")
|
||||
icon = pygame_sdl2.image.load(icon_fn)
|
||||
|
||||
width, height = icon.get_size()
|
||||
surf = pygame_sdl2.Surface((width, height), pygame_sdl2.SRCALPHA)
|
||||
|
||||
ro, go, bo, _ao = tuple(self.accent_color)
|
||||
|
||||
ro -= 23
|
||||
go -= 23
|
||||
bo -= 23
|
||||
|
||||
for y in range(height):
|
||||
for x in range(width):
|
||||
r, g, b, a = icon.get_at((x, y))
|
||||
|
||||
r = max(0, min(r + ro, 255))
|
||||
g = max(0, min(g + go, 255))
|
||||
b = max(0, min(b + bo, 255))
|
||||
|
||||
surf.set_at((x, y), (r, g, b, a))
|
||||
|
||||
self.save(surf, "window_icon", overwrite=False)
|
||||
|
||||
def generate_menus(self):
|
||||
s = self.make_surface(self.width, self.height)
|
||||
s.fill(self.menu_color)
|
||||
|
||||
self.save(s, "main_menu", overwrite=False)
|
||||
self.save(s, "game_menu", overwrite=False)
|
||||
|
||||
def generate_bubble(self):
|
||||
|
||||
import shutil
|
||||
|
||||
for fn in [ "bubble.png", "thoughtbubble.png" ]:
|
||||
|
||||
source = os.path.join(config.renpy_base, "gui", "game", "gui", fn)
|
||||
dest = os.path.join(self.prefix, fn)
|
||||
|
||||
if source == dest:
|
||||
return
|
||||
|
||||
shutil.copyfile(source, dest)
|
||||
|
||||
|
||||
def generate_all(self):
|
||||
self.generate_textbox()
|
||||
self.generate_choice_button()
|
||||
self.generate_overlay()
|
||||
self.generate_frame()
|
||||
self.generate_nvl()
|
||||
self.generate_quick_buttons()
|
||||
self.generate_skip()
|
||||
self.generate_notify()
|
||||
self.generate_menus()
|
||||
self.generate_icon()
|
||||
self.generate_bubble()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
|
||||
ap = argparse.ArgumentParser()
|
||||
|
||||
ap.add_argument("prefix")
|
||||
ap.add_argument("width", type=int)
|
||||
ap.add_argument("height", type=int)
|
||||
|
||||
args = ap.parse_args()
|
||||
|
||||
ImageGenerator(args.prefix, args.width, args.height).generate_all()
|
||||
@@ -1,113 +0,0 @@
|
||||
# Copyright 2004-2025 Tom Rothamel <pytom@bishoujo.us>
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person
|
||||
# obtaining a copy of this software and associated documentation files
|
||||
# (the "Software"), to deal in the Software without restriction,
|
||||
# including without limitation the rights to use, copy, modify, merge,
|
||||
# publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
# and to permit persons to whom the Software is furnished to do so,
|
||||
# subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
from renpy.store import Color
|
||||
import time
|
||||
|
||||
# The target width used in templates.
|
||||
WIDTH = 1280
|
||||
HEIGHT = 720
|
||||
|
||||
|
||||
class GuiParameters(object):
|
||||
"""
|
||||
This represents the parameters to the gui. This is used to initialize
|
||||
the ImageGenerator and CodeGenerator objects to a consistent set of
|
||||
parameters.
|
||||
"""
|
||||
|
||||
def __init__(self, prefix, template, width, height, accent, boring, light, language, replace_images, replace_code, update_code, name=None):
|
||||
|
||||
self.prefix = prefix
|
||||
self.template = template
|
||||
|
||||
self.width = width
|
||||
self.height = height
|
||||
|
||||
self.scale = min(1.0 * width / WIDTH, 1.0 * height / HEIGHT)
|
||||
|
||||
self.accent_color = Color(accent)
|
||||
self.boring_color = Color(boring)
|
||||
|
||||
# tint = n * color + (1-n) * white
|
||||
# shade = n * color + (1-n) * black
|
||||
|
||||
self.light = light
|
||||
|
||||
if light:
|
||||
self.hover_color = self.accent_color # .tint(.95)
|
||||
self.muted_color = self.accent_color.tint(.6)
|
||||
self.hover_muted_color = self.accent_color.tint(.4)
|
||||
else:
|
||||
self.hover_color = self.accent_color.tint(.6)
|
||||
self.muted_color = self.accent_color.shade(.4)
|
||||
self.hover_muted_color = self.accent_color.shade(.6)
|
||||
|
||||
if light:
|
||||
self.menu_color = self.accent_color.replace_hsv_saturation(.25).replace_value(.75)
|
||||
else:
|
||||
self.menu_color = self.accent_color.replace_hsv_saturation(.25).replace_value(.25)
|
||||
|
||||
self.title_color = self.accent_color.replace_hsv_saturation(.5).replace_value(1.0)
|
||||
|
||||
if light:
|
||||
|
||||
self.selected_color = Color("#555555")
|
||||
self.idle_color = Color("#707070")
|
||||
self.idle_small_color = Color("#606060")
|
||||
self.text_color = Color("#404040")
|
||||
self.choice_color = Color("#cccccc")
|
||||
|
||||
else:
|
||||
|
||||
self.selected_color = Color("#ffffff")
|
||||
self.idle_color = Color("#888888")
|
||||
self.idle_small_color = Color("#aaaaaa")
|
||||
self.text_color = Color("#ffffff")
|
||||
self.choice_color = Color("#cccccc")
|
||||
|
||||
self.insensitive_color = self.idle_color.replace_opacity(.5)
|
||||
|
||||
self.language = language
|
||||
|
||||
if replace_code:
|
||||
update_code = True
|
||||
|
||||
self.replace_images = replace_images
|
||||
self.replace_code = replace_code
|
||||
self.update_code = update_code
|
||||
|
||||
self.skip_backup = False
|
||||
|
||||
name = name or ''
|
||||
|
||||
self.name = name
|
||||
|
||||
GOOD_CHARACTERS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_"
|
||||
|
||||
simple_name = "".join(i for i in name if i in GOOD_CHARACTERS)
|
||||
|
||||
if not simple_name:
|
||||
simple_name = "game"
|
||||
|
||||
self.simple_name = simple_name
|
||||
|
||||
self.savedir = self.simple_name + "-" + str(int(time.time()))
|
||||
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 57 KiB |
|
Before Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 433 B |
|
Before Width: | Height: | Size: 160 KiB |
|
Before Width: | Height: | Size: 71 KiB |
|
Before Width: | Height: | Size: 2.8 KiB |
@@ -1,197 +0,0 @@
|
||||
# Copyright 2004-2025 Tom Rothamel <pytom@bishoujo.us>
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person
|
||||
# obtaining a copy of this software and associated documentation files
|
||||
# (the "Software"), to deal in the Software without restriction,
|
||||
# including without limitation the rights to use, copy, modify, merge,
|
||||
# publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
# and to permit persons to whom the Software is furnished to do so,
|
||||
# subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
init python:
|
||||
import fnmatch
|
||||
import re
|
||||
import zipfile
|
||||
|
||||
def install_from_zip(name, zipglob, patterns):
|
||||
|
||||
# Determine the filename matching the zipglob, and put it into filename.
|
||||
filenames = [ i for i in os.listdir(config.renpy_base) if fnmatch.fnmatch(i.lower(), zipglob.lower()) ]
|
||||
|
||||
if not filenames:
|
||||
interface.error(
|
||||
_("Could not install [name!t], as a file matching [zipglob] was not found in the Ren'Py SDK directory."),
|
||||
label="install",
|
||||
name=name,
|
||||
zipglob=zipglob,
|
||||
)
|
||||
|
||||
filenames.sort(key=lambda a : a.lower())
|
||||
filename = filenames[-1]
|
||||
|
||||
# The zipfile.
|
||||
zf = zipfile.ZipFile(os.path.join(config.renpy_base, filename))
|
||||
|
||||
for fn in zf.namelist():
|
||||
matchfn = fn.replace("\\", "/")
|
||||
dstfn = None
|
||||
|
||||
renpy.write_log(fn)
|
||||
|
||||
for src, dst in patterns:
|
||||
if re.match(src, matchfn):
|
||||
dstfn = re.sub(src, dst, matchfn)
|
||||
break
|
||||
|
||||
if not dstfn:
|
||||
continue
|
||||
|
||||
dstfn = os.path.join(config.renpy_base, dstfn)
|
||||
|
||||
if not os.path.exists(os.path.dirname(dstfn)):
|
||||
try:
|
||||
os.makedirs(os.path.dirname(dstfn))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
renpy.write_log(fn + " -> " + dstfn)
|
||||
|
||||
data = zf.read(fn)
|
||||
with open(dstfn, "wb") as f:
|
||||
f.write(data)
|
||||
|
||||
try:
|
||||
os.chmod(dstfn, 0o755)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
interface.info(_("Successfully installed [name!t]."), name=name)
|
||||
|
||||
|
||||
label install_live2d:
|
||||
python hide:
|
||||
_prefix = r"lib/py3-"
|
||||
|
||||
patterns = [
|
||||
(r".*/Core/dll/linux/x86_64/(libLive2DCubismCore.so)", _prefix + r"linux-x86_64/\1"),
|
||||
(r".*/Core/dll/windows/x86_64/(Live2DCubismCore.dll)", _prefix + r"windows-x86_64/\1"),
|
||||
(r".*/Core/dll/macos/(libLive2DCubismCore.dylib)", _prefix + r"mac-universal/\1"),
|
||||
(r".*/Core/dll/experimental/rpi/(libLive2DCubismCore.so)", _prefix + r"linux-armv7l/\1"),
|
||||
|
||||
(r".*/Core/dll/android/(armeabi-v7a/libLive2DCubismCore.so)", r"rapt/prototype/renpyandroid/src/main/jniLibs/\1"),
|
||||
(r".*/Core/dll/android/(arm64-v8a/libLive2DCubismCore.so)", r"rapt/prototype/renpyandroid/src/main/jniLibs/\1"),
|
||||
(r".*/Core/dll/android/(x86_64/libLive2DCubismCore.so)", r"rapt/prototype/renpyandroid/src/main/jniLibs/\1"),
|
||||
]
|
||||
|
||||
install_from_zip("Live2D Cubism SDK for Native", "CubismSdkForNative-[45]-*.zip", patterns)
|
||||
|
||||
jump front_page
|
||||
|
||||
screen install_preferences():
|
||||
|
||||
frame:
|
||||
style "l_indent"
|
||||
has vbox
|
||||
|
||||
text _("This screen allows you to install libraries that can't be distributed with Ren'Py. Some of these libraries may require you to agree to a third-party license before being used or distributed.")
|
||||
|
||||
add SPACER
|
||||
|
||||
if not achievement.has_steam:
|
||||
|
||||
textbutton _("Install Steam Support"):
|
||||
action Jump("install_steam")
|
||||
|
||||
add HALF_SPACER
|
||||
|
||||
frame:
|
||||
style "l_indent"
|
||||
has vbox
|
||||
|
||||
text _("Before installing Steam support, please make sure you are a {a=https://partner.steamgames.com/}Steam partner{/a}.")
|
||||
|
||||
else:
|
||||
|
||||
textbutton _("Install Steam Support")
|
||||
|
||||
add HALF_SPACER
|
||||
|
||||
frame:
|
||||
style "l_indent"
|
||||
has vbox
|
||||
|
||||
text _("Steam support has already been installed.")
|
||||
|
||||
add SPACER
|
||||
|
||||
textbutton _("Install Live2D Cubism SDK for Native"):
|
||||
action Jump("prompt_live2d")
|
||||
|
||||
|
||||
screen install_live2d():
|
||||
|
||||
frame:
|
||||
style_group "l"
|
||||
style "l_root"
|
||||
|
||||
window:
|
||||
|
||||
has vbox
|
||||
|
||||
label _("Install Libraries")
|
||||
|
||||
add HALF_SPACER
|
||||
|
||||
hbox:
|
||||
frame:
|
||||
style "l_indent"
|
||||
xfill True
|
||||
|
||||
viewport:
|
||||
scrollbars "vertical"
|
||||
mousewheel True
|
||||
|
||||
has vbox
|
||||
|
||||
add SPACER
|
||||
|
||||
textbutton _("Install Live2D Cubism SDK for Native"):
|
||||
action Jump("install_live2d")
|
||||
|
||||
add HALF_SPACER
|
||||
|
||||
frame:
|
||||
style "l_indent"
|
||||
has vbox
|
||||
|
||||
text _("The {a=https://www.live2d.com/en/download/cubism-sdk/download-native/}Cubism SDK for Native{/a} adds support for displaying Live2D models. Place CubismSdkForNative-{i}version{/i}.zip in the Ren'Py SDK directory, and then click Install. Distributing a game with Live2D requires you to accept a license from Live2D, Inc.")
|
||||
|
||||
add SPACER
|
||||
|
||||
text _("Live2D in Ren'Py doesn't support the Web, Android x86_64 (including emulators and Chrome OS), and must be added to iOS projects manually. Live2D must be reinstalled after upgrading Ren'Py or installing Android support.")
|
||||
|
||||
|
||||
|
||||
textbutton _("Cancel") action Return(False) style "l_left_button"
|
||||
textbutton _("Open Ren'Py SDK Directory") action OpenDirectory(config.renpy_base, absolute=True) style "l_right_button"
|
||||
|
||||
timer 2.0 action renpy.restart_interaction repeat True
|
||||
|
||||
label prompt_live2d:
|
||||
call screen install_live2d
|
||||
jump preferences
|
||||
|
||||
label install_steam:
|
||||
$ add_dlc("steam", restart=True)
|
||||
jump install
|
||||
@@ -1,503 +0,0 @@
|
||||
# Copyright 2004-2025 Tom Rothamel <pytom@bishoujo.us>
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person
|
||||
# obtaining a copy of this software and associated documentation files
|
||||
# (the "Software"), to deal in the Software without restriction,
|
||||
# including without limitation the rights to use, copy, modify, merge,
|
||||
# publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
# and to permit persons to whom the Software is furnished to do so,
|
||||
# subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
import hashlib
|
||||
import os
|
||||
import time
|
||||
import requests
|
||||
import zipfile
|
||||
import tarfile
|
||||
import shutil
|
||||
import subprocess
|
||||
import renpy
|
||||
import stat
|
||||
|
||||
VERSION = 1
|
||||
|
||||
# True if the installer should run in quiet mode.
|
||||
quiet = False
|
||||
|
||||
from store import _, config, interface, project, Jump # type: ignore
|
||||
|
||||
temp_exists = False
|
||||
|
||||
def _ensure_temp():
|
||||
"""
|
||||
Ensures that the directories needed by the extension API are present.
|
||||
"""
|
||||
|
||||
global temp_exists
|
||||
|
||||
if temp_exists:
|
||||
return
|
||||
|
||||
backups = os.path.join(config.renpy_base, "tmp", "installer", "backups")
|
||||
|
||||
try:
|
||||
if not os.path.exists(backups):
|
||||
os.makedirs(os.path.dirname(backups))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
temp_exists = True
|
||||
|
||||
# The target directory that the extensions API operates on.
|
||||
target = None
|
||||
|
||||
def set_target(directory):
|
||||
"""
|
||||
This sets the directory that the extension API targets. This is where
|
||||
packages are unpacked to and the default working directory where the
|
||||
programs are run.
|
||||
"""
|
||||
|
||||
global target
|
||||
target = directory
|
||||
|
||||
_clean("temp:", 3)
|
||||
|
||||
|
||||
def _path(filename):
|
||||
"""
|
||||
Returns the full path to `filename`. If `filename` starts with the
|
||||
prefix temp:, it's placed in the temp directory. If the filename
|
||||
starts with backup, a backup filename is returned. Otherwise,
|
||||
the path is interpreted relative to the target directory.
|
||||
"""
|
||||
|
||||
_ensure_temp()
|
||||
|
||||
tempdir = os.path.join(config.renpy_base, "tmp", "installer")
|
||||
backups = os.path.join(config.renpy_base, "tmp", "installer", "backups")
|
||||
|
||||
prefix, _, rest = filename.partition(":")
|
||||
|
||||
if prefix == "temp":
|
||||
return os.path.join(tempdir, rest)
|
||||
|
||||
if prefix == "backup":
|
||||
base = os.path.basename(rest.rpartition(":")[2])
|
||||
return os.path.join(backups, base + "." + str(time.time()))
|
||||
|
||||
if prefix == "renpy":
|
||||
return os.path.join(config.renpy_base, rest)
|
||||
|
||||
if target is None:
|
||||
raise Exception("The target directory has not been set.")
|
||||
|
||||
return os.path.join(target, filename)
|
||||
|
||||
|
||||
def _clean(directory, age=3):
|
||||
"""
|
||||
Removes files from `directory` that are older than `age` days.
|
||||
"""
|
||||
|
||||
directory = _path(directory)
|
||||
|
||||
for root, dirs, files in os.walk(directory, topdown=False):
|
||||
for f in files:
|
||||
filename = os.path.join(root, f)
|
||||
mtime = os.stat(filename).st_mtime
|
||||
if time.time() - mtime > age * 86400:
|
||||
try:
|
||||
os.unlink(filename)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
if root != directory:
|
||||
|
||||
try:
|
||||
os.rmdir(root)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def _friendly(filename):
|
||||
"""
|
||||
Returns a version of the filename without any leading prefix.
|
||||
"""
|
||||
|
||||
return filename.rpartition(":")[2]
|
||||
|
||||
|
||||
def _check_hash(filename, hashj):
|
||||
"""
|
||||
Returns a cryptographic hash of `filename`. `filename` should
|
||||
be a full path, one returned by temp or path.
|
||||
"""
|
||||
|
||||
try:
|
||||
|
||||
sha = hashlib.sha256()
|
||||
|
||||
with open(filename, "rb") as f:
|
||||
while True:
|
||||
data = f.read(1024 * 1024)
|
||||
if not data:
|
||||
break
|
||||
|
||||
sha.update(data)
|
||||
|
||||
return sha.hexdigest() == hash
|
||||
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
# The name and url of the file that is currently being downloaded. This is meant to
|
||||
# to be used by the interface screens to show the user what files are being
|
||||
# downloaded.
|
||||
download_file = ""
|
||||
download_url = ""
|
||||
|
||||
def download(url, filename, hash=None, headers=None, requests_kwargs={}):
|
||||
"""
|
||||
Downloads `url` to `filename`, a tempfile.
|
||||
"""
|
||||
global download_url
|
||||
|
||||
global download_file
|
||||
|
||||
download_url = url
|
||||
download_file = _friendly(filename)
|
||||
|
||||
filename = _path(filename)
|
||||
|
||||
if hash is not None:
|
||||
if _check_hash(filename, hash):
|
||||
return
|
||||
|
||||
progress_time = time.time()
|
||||
|
||||
all_headers = {"User-Agent": 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36 renpy/8'}
|
||||
if headers:
|
||||
all_headers.update(headers)
|
||||
|
||||
try:
|
||||
|
||||
response = requests.get(url, stream=True, proxies=renpy.exports.proxies, timeout=15, headers=all_headers, **requests_kwargs)
|
||||
response.raise_for_status()
|
||||
|
||||
total_size = int(response.headers.get('content-length', 1))
|
||||
|
||||
downloaded = 0
|
||||
|
||||
with open(filename, "wb") as f:
|
||||
|
||||
for i in response.iter_content(65536):
|
||||
|
||||
f.write(i)
|
||||
downloaded += len(i)
|
||||
|
||||
if time.time() - progress_time > 0.1:
|
||||
progress_time = time.time()
|
||||
if not quiet:
|
||||
interface.processing(
|
||||
_("Downloading [installer.download_file]..."),
|
||||
complete=downloaded, total=total_size)
|
||||
|
||||
except requests.HTTPError as e:
|
||||
if not quiet:
|
||||
raise
|
||||
|
||||
interface.error(_("Could not download [installer.download_file] from [installer.download_url]:\n{b}[installer.download_error]"))
|
||||
|
||||
if hash is not None:
|
||||
if not quiet:
|
||||
raise Exception("Hash check failed.")
|
||||
if not _check_hash(filename, hash):
|
||||
interface.error(_("The downloaded file [installer.download_file] from [installer.download_url] is not correct."))
|
||||
|
||||
class _FixedZipFile(zipfile.ZipFile):
|
||||
"""
|
||||
A patched version of zipfile.ZipFile that adds support for:
|
||||
|
||||
* Unix permissions bits.
|
||||
* Unix symbolic links.
|
||||
"""
|
||||
|
||||
def _extract_member(self, member, targetpath, pwd):
|
||||
|
||||
if not isinstance(member, zipfile.ZipInfo):
|
||||
member = self.getinfo(member)
|
||||
|
||||
# build the destination pathname, replacing
|
||||
# forward slashes to platform specific separators.
|
||||
arcname = member.filename.replace('/', os.path.sep)
|
||||
|
||||
if os.path.altsep:
|
||||
arcname = arcname.replace(os.path.altsep, os.path.sep)
|
||||
# interpret absolute pathname as relative, remove drive letter or
|
||||
# UNC path, redundant separators, "." and ".." components.
|
||||
arcname = os.path.splitdrive(arcname)[1]
|
||||
invalid_path_parts = ('', os.path.curdir, os.path.pardir)
|
||||
arcname = os.path.sep.join(x for x in arcname.split(os.path.sep) if x not in invalid_path_parts)
|
||||
|
||||
targetpath = os.path.join(targetpath, arcname)
|
||||
targetpath = os.path.normpath(targetpath)
|
||||
|
||||
# Create all upper directories if necessary.
|
||||
upperdirs = os.path.dirname(targetpath)
|
||||
if upperdirs and not os.path.exists(upperdirs):
|
||||
os.makedirs(upperdirs)
|
||||
|
||||
if member.filename.endswith("/"):
|
||||
if not os.path.isdir(targetpath):
|
||||
os.mkdir(targetpath)
|
||||
return targetpath
|
||||
|
||||
attr = member.external_attr >> 16
|
||||
|
||||
if stat.S_ISLNK(attr):
|
||||
|
||||
with self.open(member, pwd=pwd) as source:
|
||||
linkto = source.read()
|
||||
|
||||
os.symlink(linkto, targetpath)
|
||||
|
||||
else:
|
||||
|
||||
with self.open(member, pwd=pwd) as source, open(targetpath, "wb") as target:
|
||||
shutil.copyfileobj(source, target)
|
||||
|
||||
if attr:
|
||||
os.chmod(targetpath, attr)
|
||||
|
||||
return targetpath
|
||||
|
||||
|
||||
# The name of the archive being unpacked.
|
||||
unpack_archive = ""
|
||||
|
||||
|
||||
def unpack(archive, destination):
|
||||
"""
|
||||
Unpacks `archive` to `destination`. `archive` should be the name of
|
||||
a zip or (perhaps compressed) tar file. `destination` should be a
|
||||
directory that the contents are unpacked into.
|
||||
"""
|
||||
|
||||
global unpack_archive
|
||||
unpack_archive = _friendly(archive)
|
||||
|
||||
if not quiet:
|
||||
interface.processing(_("Unpacking [installer.unpack_archive]..."))
|
||||
|
||||
archive = _path(archive)
|
||||
destination = _path(destination)
|
||||
|
||||
if not os.path.exists(destination):
|
||||
os.makedirs(destination)
|
||||
|
||||
old_cwd = os.getcwd()
|
||||
|
||||
try:
|
||||
|
||||
os.chdir(destination)
|
||||
|
||||
if tarfile.is_tarfile(archive):
|
||||
tar = tarfile.open(archive)
|
||||
tar.extractall(".")
|
||||
tar.close()
|
||||
|
||||
elif zipfile.is_zipfile(archive):
|
||||
zip = _FixedZipFile(archive)
|
||||
zip.extractall(".")
|
||||
zip.close()
|
||||
|
||||
else:
|
||||
raise Exception("Unknown file type.")
|
||||
|
||||
finally:
|
||||
os.chdir(old_cwd)
|
||||
|
||||
|
||||
def exists(filename):
|
||||
"""
|
||||
Returns true if `filename` exists.
|
||||
"""
|
||||
|
||||
return os.path.exists(_path(filename))
|
||||
|
||||
|
||||
def remove(filename):
|
||||
"""
|
||||
Removes a file or directory from the target directory, backing it up
|
||||
the temporary directory.
|
||||
"""
|
||||
|
||||
if not exists(filename):
|
||||
return
|
||||
|
||||
backup = _path("backup:" + filename)
|
||||
shutil.move(_path(filename), backup)
|
||||
|
||||
# Now, touch everything so _cleanup doesn't get it too quickly.
|
||||
|
||||
if os.path.isdir(backup):
|
||||
for root, dirs, files in os.walk(backup):
|
||||
for f in files:
|
||||
try:
|
||||
os.utime(os.path.join(root, f), None)
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
try:
|
||||
os.utime(backup, None)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def move(old_filename, new_filename):
|
||||
"""
|
||||
Moves a filename from `old_filename` to `new_filename`.
|
||||
"""
|
||||
|
||||
remove(new_filename)
|
||||
shutil.move(_path(old_filename), _path(new_filename))
|
||||
|
||||
|
||||
def mkdir(dirname):
|
||||
"""
|
||||
Makes the named directory.
|
||||
"""
|
||||
|
||||
if not os.path.exists(_path(dirname)):
|
||||
os.makedirs(_path(dirname))
|
||||
|
||||
|
||||
def info(message, **kwargs):
|
||||
"""
|
||||
Displays `message` to the user, asking them to click through or
|
||||
cancel.
|
||||
"""
|
||||
|
||||
interface.info(message, cancel=Jump("front_page"), **kwargs)
|
||||
|
||||
|
||||
def processing(message, **kwargs):
|
||||
"""
|
||||
Displays `message` to the user, without waiting.
|
||||
"""
|
||||
|
||||
interface.processing(message, **kwargs)
|
||||
|
||||
|
||||
def error(message, **kwargs):
|
||||
"""
|
||||
Displays `message` to the user, as an error.
|
||||
"""
|
||||
|
||||
interface.error(message)
|
||||
|
||||
|
||||
install_args = [ ]
|
||||
install_error = ""
|
||||
|
||||
def run(*args, **kwargs):
|
||||
"""
|
||||
Runs a program with the given arguments, in the target directory.
|
||||
"""
|
||||
|
||||
environ = { renpy.exports.fsencode(k) : renpy.exports.fsencode(v) for k, v in os.environ.items() }
|
||||
|
||||
for k, v in kwargs.pop("environ", {}).items():
|
||||
environ[renpy.exports.fsencode(k)] = renpy.exports.fsencode(v)
|
||||
|
||||
global install_args
|
||||
global install_error
|
||||
|
||||
args = [ renpy.exports.fsencode(i) for i in args ]
|
||||
|
||||
try:
|
||||
subprocess.check_call(args, cwd=target, env=environ) # type: ignore
|
||||
except Exception as e:
|
||||
install_args = args
|
||||
install_error = str(e)
|
||||
|
||||
interface.error(_("Could not run [installer.install_args!r]:\n[installer.install_error]"))
|
||||
|
||||
|
||||
_renpy = renpy
|
||||
|
||||
def manifest(url, renpy=False, insecure=False):
|
||||
"""
|
||||
Executes the manifest at `url`.
|
||||
|
||||
`renpy`
|
||||
If true, the manifest applies to Ren'Py. If False, the manifest applies
|
||||
to the current project.
|
||||
|
||||
`insecure`
|
||||
If true, verificaiton is disabled.
|
||||
"""
|
||||
|
||||
import ecdsa
|
||||
|
||||
download(url, "temp:manifest.py")
|
||||
|
||||
with open(_path("temp:manifest.py"), "rb") as f:
|
||||
manifest = f.read()
|
||||
|
||||
if not insecure:
|
||||
download(url + ".sig", "temp:manifest.py.sig")
|
||||
|
||||
with open(_path("temp:manifest.py.sig"), "rb") as f:
|
||||
sig = f.read()
|
||||
|
||||
key = ecdsa.VerifyingKey.from_pem(_renpy.exports.open_file("renpy_ecdsa_public.pem").read())
|
||||
|
||||
if not key.verify(sig, manifest):
|
||||
error(_("The manifest signature is not valid."))
|
||||
return
|
||||
|
||||
if renpy:
|
||||
set_target(config.renpy_base)
|
||||
else:
|
||||
if project.current is None:
|
||||
error(_("No project has been selected."))
|
||||
return
|
||||
|
||||
set_target(project.current.path)
|
||||
|
||||
exec(manifest.decode("utf-8"), {}, {})
|
||||
|
||||
|
||||
def local_manifest(filename, renpy=False):
|
||||
"""
|
||||
Executes the manifest in `filename`.
|
||||
|
||||
`renpy`
|
||||
If true, the manifest applies to Ren'Py. If False, the manifest applies
|
||||
to the current project.
|
||||
"""
|
||||
|
||||
if renpy:
|
||||
set_target(config.renpy_base)
|
||||
else:
|
||||
if project.current is None:
|
||||
error(_("No project has been selected."))
|
||||
return
|
||||
|
||||
set_target(project.current.path)
|
||||
|
||||
with open(filename, "r") as f:
|
||||
exec(f.read(), {}, {})
|
||||
@@ -1,12 +0,0 @@
|
||||
|
||||
# This file imports the extensions API into the default store, and makes it
|
||||
# also contains the strings used by the extensions API, so the Ren'Py translation
|
||||
# framework can find them.
|
||||
|
||||
init python:
|
||||
import installer
|
||||
|
||||
init python hide:
|
||||
_("Downloading [extension.download_file].")
|
||||
_("Could not download [extension.download_file] from [extension.download_url]:\n{b}[extension.download_error]")
|
||||
_("The downloaded file [extension.download_file] from [extension.download_url] is not correct.")
|
||||
@@ -1,553 +0,0 @@
|
||||
# Copyright 2004-2025 Tom Rothamel <pytom@bishoujo.us>
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person
|
||||
# obtaining a copy of this software and associated documentation files
|
||||
# (the "Software"), to deal in the Software without restriction,
|
||||
# including without limitation the rights to use, copy, modify, merge,
|
||||
# publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
# and to permit persons to whom the Software is furnished to do so,
|
||||
# subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
################################################################################
|
||||
# Interface actions.
|
||||
init python in interface:
|
||||
from store import OpenURL, config, Return, _preferences, persistent
|
||||
import store
|
||||
|
||||
import os.path
|
||||
import contextlib
|
||||
|
||||
RENPY_URL = "http://www.renpy.org"
|
||||
DOC_PATH = os.path.join(config.renpy_base, "doc/")
|
||||
DOC_URL = "http://www.renpy.org/doc/html/"
|
||||
DOC_LOCAL_URL = "file:///" + DOC_PATH
|
||||
|
||||
# If true, the launcher should not link back to renpy.org.
|
||||
NO_LAUNCHER_LINKS = os.path.exists(os.path.join(config.renpy_base, "no_launcher_links.txt"))
|
||||
|
||||
|
||||
local_doc_exists = os.path.exists(DOC_PATH)
|
||||
|
||||
def get_doc_url(page):
|
||||
"""
|
||||
Returns the URL to the documentation page.
|
||||
"""
|
||||
|
||||
if local_doc_exists and (NO_LAUNCHER_LINKS or not persistent.use_web_doc):
|
||||
from urllib.parse import urljoin
|
||||
from urllib.request import pathname2url
|
||||
|
||||
return urljoin('file:', pathname2url(DOC_PATH + page))
|
||||
else:
|
||||
return DOC_URL + page
|
||||
|
||||
def OpenDocumentation(page="index.html"):
|
||||
"""
|
||||
An action that opens the documentation.
|
||||
"""
|
||||
|
||||
return OpenURL(get_doc_url(page))
|
||||
|
||||
def OpenLicense():
|
||||
"""
|
||||
An action that opens the license.
|
||||
"""
|
||||
|
||||
return OpenDocumentation("license.html")
|
||||
|
||||
def get_sponsor_url():
|
||||
"""
|
||||
Returns the URL to the sponsors page.
|
||||
"""
|
||||
|
||||
return "https://www.renpy.org/sponsors.html?version={}&language={}".format(
|
||||
renpy.version_only,
|
||||
_preferences.language or "english"
|
||||
)
|
||||
|
||||
# Should we display the bottom links?
|
||||
links = True
|
||||
|
||||
@contextlib.contextmanager
|
||||
def nolinks():
|
||||
global links
|
||||
links = False
|
||||
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
links = True
|
||||
|
||||
# Version.
|
||||
import re
|
||||
version = re.sub(r'\.\d+(\w*)$', r'\1', renpy.version())
|
||||
|
||||
# This displays the bottom of the screen. If the tooltip is not None, this displays the
|
||||
# tooltip. Otherwise, it displays a list of links (to various websites, and to the
|
||||
# preferences and update screen), or is just blank.
|
||||
screen bottom_info:
|
||||
|
||||
zorder 100
|
||||
|
||||
if interface.links:
|
||||
|
||||
frame:
|
||||
style_group "l"
|
||||
style "l_default"
|
||||
|
||||
left_margin (10 + INDENT)
|
||||
right_margin (10 + INDENT)
|
||||
xfill True
|
||||
ypos 536
|
||||
yanchor 0.0
|
||||
|
||||
has vbox:
|
||||
spacing 20
|
||||
|
||||
hbox:
|
||||
xfill True
|
||||
|
||||
hbox:
|
||||
spacing INDENT
|
||||
textbutton _("Documentation") style "l_link" action interface.OpenDocumentation()
|
||||
|
||||
if not interface.NO_LAUNCHER_LINKS:
|
||||
textbutton _("Ren'Py Website") style "l_link" action OpenURL(interface.RENPY_URL)
|
||||
|
||||
textbutton _("[interface.version]") style "l_link" action Jump("about")
|
||||
|
||||
hbox:
|
||||
spacing INDENT
|
||||
xalign 1.0
|
||||
|
||||
if ability.can_update:
|
||||
textbutton _("update") action Jump("update") style "l_link":
|
||||
if persistent.has_update:
|
||||
text_color "#F96854"
|
||||
text_hover_color Color("#F96854").tint(.8)
|
||||
|
||||
textbutton _("preferences") style "l_link" action Jump("preferences")
|
||||
textbutton _("quit") style "l_link" action Quit(confirm=False)
|
||||
|
||||
if persistent.sponsor_message and not interface.NO_LAUNCHER_LINKS:
|
||||
|
||||
textbutton _("Ren'Py Sponsor Information"):
|
||||
style "l_link"
|
||||
text_color "#F96854"
|
||||
text_hover_color Color("#F96854").tint(.8)
|
||||
|
||||
xalign 0.0
|
||||
yalign 1.0
|
||||
yoffset -10
|
||||
|
||||
action OpenURL(interface.get_sponsor_url())
|
||||
|
||||
|
||||
|
||||
screen common:
|
||||
|
||||
default complete = None
|
||||
default total = None
|
||||
default yes = None
|
||||
default no = None
|
||||
default choices = None
|
||||
default cancel = None
|
||||
default bar_value = None
|
||||
|
||||
frame:
|
||||
style "l_root"
|
||||
|
||||
frame:
|
||||
style_group "l_info"
|
||||
|
||||
has vbox
|
||||
|
||||
text message:
|
||||
textalign 0.5
|
||||
xalign 0.5
|
||||
layout "subtitle"
|
||||
|
||||
if complete is not None:
|
||||
add SPACER
|
||||
|
||||
frame:
|
||||
style "l_progress_frame"
|
||||
|
||||
bar:
|
||||
range total
|
||||
value complete
|
||||
style "l_progress_bar"
|
||||
|
||||
if bar_value is not None:
|
||||
add SPACER
|
||||
|
||||
frame:
|
||||
style "l_progress_frame"
|
||||
|
||||
bar:
|
||||
value bar_value
|
||||
style "l_progress_bar"
|
||||
|
||||
|
||||
if choices:
|
||||
add SPACER
|
||||
|
||||
for v, l in choices:
|
||||
textbutton l:
|
||||
action SetScreenVariable("selected", v)
|
||||
selected_background REVERSE_IDLE
|
||||
selected_hover_background REVERSE_HOVER
|
||||
xpadding 20
|
||||
size_group "choice"
|
||||
text_selected_idle_color REVERSE_TEXT
|
||||
text_selected_hover_color REVERSE_TEXT
|
||||
text_xalign 0.5
|
||||
|
||||
if selected is not None:
|
||||
$ continue_ = Return(selected)
|
||||
else:
|
||||
$ continue_ = None
|
||||
|
||||
if submessage:
|
||||
add SPACER
|
||||
|
||||
text submessage:
|
||||
textalign 0.5
|
||||
xalign 0.5
|
||||
layout "subtitle"
|
||||
|
||||
if yes:
|
||||
add SPACER
|
||||
|
||||
hbox:
|
||||
xalign 0.5
|
||||
textbutton _("Yes") style "l_button" action yes
|
||||
null width 160
|
||||
textbutton _("No") style "l_button" action no
|
||||
|
||||
|
||||
label title text_color title_color style "l_info_label"
|
||||
|
||||
if back:
|
||||
textbutton _("Return") action back style "l_left_button"
|
||||
elif cancel:
|
||||
textbutton _("Cancel") action cancel style "l_left_button"
|
||||
|
||||
if continue_:
|
||||
textbutton _("Continue") action continue_ style "l_right_button"
|
||||
key "input_enter" action continue_
|
||||
|
||||
|
||||
screen launcher_input:
|
||||
|
||||
default value = default
|
||||
|
||||
frame:
|
||||
style "l_root"
|
||||
|
||||
frame:
|
||||
style_group "l_info"
|
||||
|
||||
has vbox
|
||||
|
||||
text message:
|
||||
textalign 0.5
|
||||
xalign 0.5
|
||||
layout "subtitle"
|
||||
|
||||
add SPACER
|
||||
|
||||
input style "l_default":
|
||||
value ScreenVariableInputValue("value", returnable=True)
|
||||
size 24
|
||||
xalign 0.5
|
||||
color INPUT_COLOR
|
||||
allow allow
|
||||
copypaste True
|
||||
|
||||
if filename:
|
||||
add SPACER
|
||||
text _("Due to package format limitations, non-ASCII file and directory names are not allowed.")
|
||||
|
||||
label title style "l_info_label" text_color QUESTION_COLOR
|
||||
|
||||
if cancel:
|
||||
textbutton _("Cancel") action cancel style "l_left_button"
|
||||
|
||||
textbutton _("Continue") action Return(value) style "l_right_button"
|
||||
|
||||
|
||||
init python in interface:
|
||||
|
||||
import traceback
|
||||
from store import Jump
|
||||
import store._errorhandling as _errorhandling
|
||||
|
||||
def common(title, title_color, message, submessage=None, back=None, continue_=None, pause0=False, show_screen=False, **kwargs):
|
||||
"""
|
||||
Displays the info, interaction, and processing screens.
|
||||
|
||||
`title`
|
||||
The title of the screen.
|
||||
|
||||
`message`
|
||||
The main message that is displayed when the screen is.
|
||||
|
||||
`submessage`
|
||||
If not None, a message that is displayed below the main message.
|
||||
|
||||
`back`
|
||||
If not None, a back button will be present. `back` is the action that
|
||||
is called when the button is clicked.
|
||||
|
||||
`cancel`
|
||||
If not None, a cancel button will be present. `cancel` is the action
|
||||
that is called when the button is clicked.
|
||||
|
||||
`continue_`
|
||||
If True, a continue button will be present. `continue_` gives the action
|
||||
that is called when that button is clicked.
|
||||
|
||||
`pause0`
|
||||
If True, a zero-length pause will be inserted before calling the
|
||||
screen. This will display it to the user and then immediately
|
||||
return.
|
||||
|
||||
`show_screen`
|
||||
If True, the screen will be show, and will return immediately. if False,
|
||||
the screen will be called, and interaction will pause.
|
||||
|
||||
Other keyword arguments are passed to the screen itself.
|
||||
"""
|
||||
|
||||
|
||||
if show_screen:
|
||||
screen_func = renpy.show_screen
|
||||
else:
|
||||
screen_func = renpy.call_screen
|
||||
|
||||
if pause0:
|
||||
ui.pausebehavior(0)
|
||||
|
||||
return screen_func("common", title=title, title_color=title_color, message=message, submessage=submessage, back=back, continue_=continue_, **kwargs)
|
||||
|
||||
def hide_screen():
|
||||
"""
|
||||
Hides a screen that was shown with show_screen=True.
|
||||
"""
|
||||
|
||||
renpy.hide_screen("common")
|
||||
|
||||
|
||||
def error(message, submessage=None, label="front_page", **kwargs):
|
||||
"""
|
||||
Indicates to the user that an error has occurred.
|
||||
|
||||
`message`
|
||||
The message to display.
|
||||
|
||||
`submessage`
|
||||
Optional secondary message information. For example, this may be
|
||||
used to display an exception string.
|
||||
|
||||
`label`
|
||||
The label to redirect to when the user finishes displaying the error. None
|
||||
to just return False.
|
||||
|
||||
Keyword arguments are passed into the screen so that they can be substituted into
|
||||
the message.
|
||||
"""
|
||||
|
||||
if label is None:
|
||||
action = Return(False)
|
||||
else:
|
||||
action = Jump(label)
|
||||
|
||||
common(_("ERROR"), store.ERROR_COLOR, message=message, submessage=submessage, back=action, **kwargs)
|
||||
|
||||
|
||||
store._ignore_action = Jump("front_page")
|
||||
|
||||
_errorhandling.rollback = False
|
||||
_errorhandling.ignore = True
|
||||
_errorhandling.reload = False
|
||||
_errorhandling.console = False
|
||||
|
||||
@contextlib.contextmanager
|
||||
def error_handling(what, label="front_page"):
|
||||
"""
|
||||
This is a context manager that catches exceptions and displays them using
|
||||
interface.error.
|
||||
|
||||
`what`
|
||||
What we're doing when the error occurs. This is usually written using
|
||||
the present participle.
|
||||
|
||||
`label`
|
||||
The label to jump to when error handling finishes.
|
||||
|
||||
As an example of usage::
|
||||
|
||||
with interface.error_handling(_("opening the log file")):
|
||||
f = open("log.txt", "w")
|
||||
"""
|
||||
|
||||
try:
|
||||
yield
|
||||
except Exception as e:
|
||||
renpy.renpy.error.report_exception(e, editor=False)
|
||||
|
||||
error(_("While [what!qt], an error occurred:"),
|
||||
_("[exception!q]"),
|
||||
what=what,
|
||||
label=label,
|
||||
exception=traceback.format_exception_only(type(e), e)[-1][:-1])
|
||||
|
||||
import string
|
||||
DIGITS_LETTERS = string.digits
|
||||
PROJECT_LETTERS = string.digits + string.ascii_letters + " _"
|
||||
FILENAME_LETTERS = PROJECT_LETTERS + "\\/"
|
||||
TRANSLATE_LETTERS = string.ascii_letters + string.digits + "_"
|
||||
|
||||
def input(title, message, filename=False, sanitize=True, cancel=None, allow=None, default=""):
|
||||
"""
|
||||
Requests typewritten input from the user.
|
||||
"""
|
||||
|
||||
rv = default
|
||||
|
||||
while True:
|
||||
|
||||
rv = renpy.call_screen(
|
||||
"launcher_input",
|
||||
title=title,
|
||||
message=message,
|
||||
filename=filename or (allow in [PROJECT_LETTERS, FILENAME_LETTERS]),
|
||||
allow=allow,
|
||||
cancel=cancel,
|
||||
default=rv
|
||||
)
|
||||
|
||||
if sanitize:
|
||||
if ("[" in rv) or ("{" in rv):
|
||||
error(_("Text input may not contain the {{ or [[ characters."), label=None)
|
||||
continue
|
||||
|
||||
if filename:
|
||||
if filename and (filename != "withslash") and (("\\" in rv) or ("/" in rv)):
|
||||
error(_("File and directory names may not contain / or \\."), label=None)
|
||||
continue
|
||||
|
||||
try:
|
||||
rv.encode("ascii")
|
||||
except Exception:
|
||||
error(_("File and directory names must consist of ASCII characters."), label=None)
|
||||
continue
|
||||
|
||||
return rv
|
||||
|
||||
def info(message, submessage=None, pause=True, **kwargs):
|
||||
"""
|
||||
Displays an informational message to the user. The user will be asked to click to
|
||||
confirm that he has read the message.
|
||||
|
||||
`message`
|
||||
The message to display.
|
||||
|
||||
`pause`
|
||||
True if we should pause while showing the info.
|
||||
|
||||
Keyword arguments are passed into the screen so that they can be substituted into
|
||||
the message.
|
||||
"""
|
||||
|
||||
if pause:
|
||||
common(_("INFORMATION"), store.INFO_COLOR, message, submessage, continue_=Return(True), **kwargs)
|
||||
else:
|
||||
common(_("INFORMATION"), store.INFO_COLOR, message, submessage, pause0=True, **kwargs)
|
||||
|
||||
|
||||
def interaction(title, message, submessage=None, pause=0, **kwargs):
|
||||
"""
|
||||
Put up on the screen while an interaction with an external program occurs.
|
||||
This shows the message, then immediately returns.
|
||||
|
||||
`title`
|
||||
The title of the interaction.
|
||||
|
||||
`message`
|
||||
The message itself.
|
||||
|
||||
`submessage`
|
||||
An optional sub message.
|
||||
|
||||
`pause`
|
||||
The amount of time to pause for after showing the message.
|
||||
"""
|
||||
|
||||
common(title, store.INTERACTION_COLOR, message, submessage=submessage, pause=pause, show_screen=True, **kwargs)
|
||||
renpy.pause(pause)
|
||||
|
||||
def processing(message, submessage=None, complete=None, total=None, **kwargs):
|
||||
"""
|
||||
Indicates to the user that processing is taking place. This should be used when
|
||||
there is an indefinite amount of work to be done.
|
||||
|
||||
`message`
|
||||
The message to display.
|
||||
|
||||
`submessage`
|
||||
An additional message to display.
|
||||
|
||||
`complete`
|
||||
The fraction complete the step is.
|
||||
|
||||
`total`
|
||||
The total amount of work to do in this step.
|
||||
|
||||
Keyword arguments are passed into the screen so that they can be substituted into
|
||||
the message.
|
||||
"""
|
||||
|
||||
common(_("PROCESSING"), store.INTERACTION_COLOR, message, submessage, pause0=True, complete=complete, total=total, **kwargs)
|
||||
|
||||
|
||||
def yesno(message, yes=Return(True), no=Return(False), **kwargs):
|
||||
"""
|
||||
Asks the user a yes or no question.
|
||||
|
||||
`message`
|
||||
The question to ask.
|
||||
|
||||
`yes`
|
||||
The action to perform if the user answers yes.
|
||||
|
||||
`no`
|
||||
The action to perform if the user answer no.
|
||||
"""
|
||||
|
||||
return common(_("QUESTION"), store.QUESTION_COLOR, message, yes=yes, no=no, **kwargs)
|
||||
|
||||
def choice(message, choices, selected, **kwargs):
|
||||
"""
|
||||
Asks the user to pick a choice from a menu.
|
||||
|
||||
`choices`
|
||||
A list of (value, label) tuples, giving the choices.
|
||||
|
||||
`selected`
|
||||
The default choice that we mark as selected.
|
||||
"""
|
||||
|
||||
return common(_("CHOICE"), store.QUESTION_COLOR, message, choices=choices, selected=selected, **kwargs)
|
||||
@@ -1,457 +0,0 @@
|
||||
# Copyright 2004-2025 Tom Rothamel <pytom@bishoujo.us>
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person
|
||||
# obtaining a copy of this software and associated documentation files
|
||||
# (the "Software"), to deal in the Software without restriction,
|
||||
# including without limitation the rights to use, copy, modify, merge,
|
||||
# publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
# and to permit persons to whom the Software is furnished to do so,
|
||||
# subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
init python:
|
||||
IOS_NO_RENIOS = 0
|
||||
IOS_NO_DIRECTORY = 1
|
||||
IOS_NO_PROJECT = 2
|
||||
IOS_OK = 3
|
||||
|
||||
IOS_NO_RENIOS_TEXT = _("To build iOS packages, please download renios, unzip it, and place it into the Ren'Py directory. Then restart the Ren'Py launcher.")
|
||||
IOS_NO_DIRECTORY_TEXT = _("The directory in where Xcode projects will be placed has not been selected. Choose 'Select Directory' to select it.")
|
||||
IOS_NO_PROJECT_TEXT = _("There is no Xcode project corresponding to the current Ren'Py project. Choose 'Create Xcode Project' to create one.")
|
||||
IOS_OK_TEXT = _("An Xcode project exists. Choose 'Update Xcode Project' to update it with the latest game files, or use Xcode to build and install it.")
|
||||
|
||||
IPHONE_TEXT = _("Attempts to emulate an iPhone.\n\nTouch input is emulated through the mouse, but only when the button is held down.")
|
||||
IPAD_TEXT = _("Attempts to emulate an iPad.\n\nTouch input is emulated through the mouse, but only when the button is held down.")
|
||||
|
||||
IOS_SELECT_DIRECTORY_TEXT = _("Selects the directory where Xcode projects will be placed.")
|
||||
IOS_CREATE_PROJECT_TEXT = _("Creates an Xcode project corresponding to the current Ren'Py project.")
|
||||
IOS_UPDATE_PROJECT_TEXT = _("Updates the Xcode project with the latest game files. This must be done each time the Ren'Py project changes.")
|
||||
IOS_XCODE_TEXT = _("Opens the Xcode project in Xcode.")
|
||||
|
||||
IOS_OPEN_DIRECTORY_TEXT = _("Opens the directory containing Xcode projects.")
|
||||
|
||||
def find_renios():
|
||||
|
||||
global RENIOS_PATH
|
||||
|
||||
candidates = [ ]
|
||||
|
||||
RENIOS_PATH = os.path.join(config.renpy_base, "renios")
|
||||
|
||||
if os.path.isdir(RENIOS_PATH) and check_hash_txt("renios"):
|
||||
import sys
|
||||
sys.path.insert(0, os.path.join(RENIOS_PATH, "buildlib"))
|
||||
else:
|
||||
RENIOS_PATH = None
|
||||
|
||||
find_renios()
|
||||
|
||||
if RENIOS_PATH:
|
||||
import renios.create
|
||||
import renios.image
|
||||
import renios.interface
|
||||
|
||||
def IOSState():
|
||||
if not RENIOS_PATH:
|
||||
return IOS_NO_RENIOS
|
||||
elif not persistent.xcode_projects_directory:
|
||||
return IOS_NO_DIRECTORY
|
||||
elif not os.path.exists(xcode_project()):
|
||||
return IOS_NO_PROJECT
|
||||
else:
|
||||
return IOS_OK
|
||||
|
||||
def IOSStateText(state):
|
||||
if state == IOS_NO_RENIOS:
|
||||
return IOS_NO_RENIOS_TEXT
|
||||
elif state == IOS_NO_DIRECTORY:
|
||||
return IOS_NO_DIRECTORY_TEXT
|
||||
elif state == IOS_NO_PROJECT:
|
||||
return IOS_NO_PROJECT_TEXT
|
||||
else:
|
||||
return IOS_OK_TEXT
|
||||
|
||||
def IOSIfState(state, needed, action):
|
||||
"""
|
||||
If `state` is `needed` or better, `action` is returned. Otherwise,
|
||||
returns None, disabling the button.
|
||||
"""
|
||||
|
||||
if state >= needed:
|
||||
return action
|
||||
else:
|
||||
return None
|
||||
|
||||
xcode_name_cache = { }
|
||||
|
||||
def xcode_name(s):
|
||||
"""
|
||||
Returns a version of `s` that's safe to use in Xcode.
|
||||
"""
|
||||
|
||||
if s in xcode_name_cache:
|
||||
return xcode_name_cache[s]
|
||||
|
||||
s = re.sub(r'[^\w\-\.]', '', s)
|
||||
xcode_name_cache[s] = s
|
||||
return s
|
||||
|
||||
def xcode_project(p=None, target=None):
|
||||
"""
|
||||
Return the path to the Xcode project corresponding to `p`, or the current
|
||||
project if `p` is None
|
||||
"""
|
||||
|
||||
if target is not None:
|
||||
return target
|
||||
|
||||
if p is None:
|
||||
p = project.current
|
||||
|
||||
if persistent.xcode_projects_directory is None:
|
||||
raise Exception("The Xcode projects directory has not been set.")
|
||||
|
||||
return os.path.join(persistent.xcode_projects_directory, xcode_name(p.name))
|
||||
|
||||
def ios_create(p=None, gui=True, target=None):
|
||||
if p is None:
|
||||
p = project.current
|
||||
p.update_dump(force=True, gui=gui)
|
||||
|
||||
name = p.dump.get("name", None)
|
||||
version = p.dump.get("version", None)
|
||||
|
||||
dest = xcode_project(p, target)
|
||||
|
||||
if gui:
|
||||
iface = MobileInterface("ios")
|
||||
else:
|
||||
iface = renios.interface.Interface()
|
||||
|
||||
if os.path.exists(dest):
|
||||
if not iface.yesno(_("The Xcode project already exists. Would you like to rename the old project, and replace it with a new one?")):
|
||||
return
|
||||
|
||||
i = 0
|
||||
while True:
|
||||
i += 1
|
||||
backup = dest + "." + str(i)
|
||||
if not os.path.exists(backup):
|
||||
break
|
||||
|
||||
os.rename(dest, backup)
|
||||
|
||||
renios.create.create_project(iface, dest, name, version)
|
||||
|
||||
ios_populate(p, gui=gui, target=target)
|
||||
|
||||
|
||||
def eliminate_pycache(directory):
|
||||
"""
|
||||
Eliminates the __pycache__ directory, and moves the files in it up a level,
|
||||
renaming them to remove the cache tag.
|
||||
"""
|
||||
|
||||
print("Eliminating __pycache__...")
|
||||
|
||||
import pathlib
|
||||
import sys
|
||||
|
||||
paths = list(pathlib.Path(directory).glob("**/__pycache__/*.pyc"))
|
||||
|
||||
for p in paths:
|
||||
name = p.stem.partition(".")[0]
|
||||
p.rename(p.parent.parent / (name + ".pyc"))
|
||||
|
||||
paths = list(pathlib.Path(directory).glob("**/__pycache__"))
|
||||
|
||||
for p in paths:
|
||||
p.rmdir()
|
||||
|
||||
|
||||
def ios_populate(p=None, gui=True, target=None):
|
||||
"""
|
||||
This actually builds the package.
|
||||
"""
|
||||
|
||||
import shutil
|
||||
|
||||
if p is None:
|
||||
p = project.current
|
||||
|
||||
dist = os.path.join(xcode_project(p, target), "base")
|
||||
|
||||
if os.path.exists(dist):
|
||||
shutil.rmtree(dist)
|
||||
|
||||
if gui:
|
||||
reporter = distribute.GuiReporter()
|
||||
else:
|
||||
reporter = distribute.TextReporter()
|
||||
|
||||
distribute.Distributor(p,
|
||||
reporter=reporter,
|
||||
packages=[ 'ios' ],
|
||||
build_update=False,
|
||||
noarchive=True,
|
||||
packagedest=dist,
|
||||
report_success=False,
|
||||
)
|
||||
|
||||
eliminate_pycache(dist)
|
||||
|
||||
main_fn = os.path.join(dist, "main.py")
|
||||
|
||||
for fn in os.listdir(dist):
|
||||
if fn.endswith(".py"):
|
||||
py_fn = os.path.join(dist, fn)
|
||||
break
|
||||
else:
|
||||
raise Exception("Could not find a .py file.")
|
||||
|
||||
with open(py_fn, "r") as py_f:
|
||||
with open(main_fn, "w") as main_f:
|
||||
for l in py_f:
|
||||
if l.startswith("#!"):
|
||||
continue
|
||||
|
||||
main_f.write(l)
|
||||
|
||||
os.unlink(py_fn)
|
||||
|
||||
ios_image(p, "ios-icon.png", "Media.xcassets/AppIcon.appiconset", True, target)
|
||||
# ios_image(p, "ios-launchimage.png", "Media.xcassets/LaunchImage.launchimage", False, target)
|
||||
|
||||
def ios_image(p, source, destination, scale, target):
|
||||
source = os.path.join(p.path, source)
|
||||
destination = os.path.join(xcode_project(p, target), destination)
|
||||
|
||||
renios.image.generate(source, destination, scale)
|
||||
|
||||
|
||||
def launch_xcode():
|
||||
dist = xcode_project(None)
|
||||
|
||||
if not os.path.exists(dist):
|
||||
return
|
||||
|
||||
for fn in os.listdir(dist):
|
||||
if fn.endswith(".xcodeproj"):
|
||||
xcodeproj = os.path.join(dist, fn)
|
||||
subprocess.call([ 'open', renpy.fsencode(xcodeproj) ])
|
||||
|
||||
break
|
||||
|
||||
|
||||
screen ios:
|
||||
|
||||
default tt = Tooltip(None)
|
||||
$ state = IOSState()
|
||||
|
||||
frame:
|
||||
style_group "l"
|
||||
style "l_root"
|
||||
|
||||
window:
|
||||
|
||||
has vbox
|
||||
|
||||
label _("iOS: [project.current.display_name!q]")
|
||||
|
||||
add HALF_SPACER
|
||||
|
||||
hbox:
|
||||
|
||||
# Left side.
|
||||
frame:
|
||||
style "l_indent"
|
||||
xmaximum ONEHALF
|
||||
xfill True
|
||||
|
||||
has vbox
|
||||
|
||||
add SEPARATOR2
|
||||
|
||||
frame:
|
||||
style "l_indent"
|
||||
has vbox
|
||||
|
||||
text _("Emulation:")
|
||||
|
||||
add HALF_SPACER
|
||||
|
||||
frame style "l_indent":
|
||||
|
||||
has hbox:
|
||||
spacing 15
|
||||
|
||||
textbutton _("iPhone"):
|
||||
action LaunchEmulator("ios-touch", "small phone touch ios mobile")
|
||||
hovered tt.Action(IPHONE_TEXT)
|
||||
|
||||
textbutton _("iPad"):
|
||||
action LaunchEmulator("ios-touch", "medium tablet touch ios mobile")
|
||||
hovered tt.Action(IPAD_TEXT)
|
||||
|
||||
|
||||
add SPACER
|
||||
add SEPARATOR2
|
||||
|
||||
frame:
|
||||
style "l_indent"
|
||||
has vbox
|
||||
|
||||
text _("Build:")
|
||||
|
||||
add HALF_SPACER
|
||||
|
||||
frame style "l_indent":
|
||||
|
||||
has vbox
|
||||
|
||||
textbutton _("Select Xcode Projects Directory"):
|
||||
action IOSIfState(state, IOS_NO_DIRECTORY, Jump("select_xcode_projects_directory"))
|
||||
hovered tt.Action(IOS_SELECT_DIRECTORY_TEXT)
|
||||
|
||||
textbutton _("Create Xcode Project"):
|
||||
action IOSIfState(state, IOS_NO_PROJECT, Jump("create_xcode_project"))
|
||||
hovered tt.Action(IOS_CREATE_PROJECT_TEXT)
|
||||
|
||||
textbutton _("Update Xcode Project"):
|
||||
action IOSIfState(state, IOS_NO_PROJECT, Jump("update_xcode_project"))
|
||||
hovered tt.Action(IOS_UPDATE_PROJECT_TEXT)
|
||||
|
||||
if renpy.macintosh:
|
||||
textbutton _("Launch Xcode"):
|
||||
action IOSIfState(state, IOS_OK, launch_xcode)
|
||||
hovered tt.Action(IOS_XCODE_TEXT)
|
||||
|
||||
add SPACER
|
||||
|
||||
textbutton _("Force Recompile") action DataToggle("force_recompile") style "l_checkbox"
|
||||
|
||||
add SPACER
|
||||
add SEPARATOR2
|
||||
|
||||
frame:
|
||||
style "l_indent"
|
||||
has vbox
|
||||
|
||||
text _("Other:")
|
||||
|
||||
add HALF_SPACER
|
||||
|
||||
frame style "l_indent":
|
||||
|
||||
has vbox
|
||||
|
||||
textbutton _("Open Xcode Projects Directory"):
|
||||
action IOSIfState(state, IOS_NO_PROJECT, OpenDirectory(persistent.xcode_projects_directory, absolute=True))
|
||||
hovered tt.Action(IOS_OPEN_DIRECTORY_TEXT)
|
||||
|
||||
|
||||
# Right side.
|
||||
frame:
|
||||
style "l_indent"
|
||||
xmaximum ONEHALF
|
||||
xfill True
|
||||
|
||||
has vbox
|
||||
|
||||
add SEPARATOR2
|
||||
|
||||
frame:
|
||||
style "l_indent"
|
||||
has vbox
|
||||
|
||||
add SPACER
|
||||
|
||||
text _("There are known issues with the iOS simulator on Apple Silicon. Please test on x86_64 or iOS devices.")
|
||||
|
||||
add SPACER
|
||||
|
||||
if tt.value:
|
||||
text tt.value
|
||||
else:
|
||||
text IOSStateText(state)
|
||||
|
||||
|
||||
textbutton _("Return") action Jump("front_page") style "l_left_button"
|
||||
|
||||
|
||||
label ios:
|
||||
|
||||
if RENIOS_PATH is None:
|
||||
$ interface.yesno(_("Before packaging iOS apps, you'll need to download renios, Ren'Py's iOS support. Would you like to download renios now?"), no=Jump("front_page"))
|
||||
$ add_dlc("renios", restart=True)
|
||||
|
||||
call screen ios
|
||||
|
||||
label select_xcode_projects_directory:
|
||||
|
||||
python hide:
|
||||
|
||||
interface.interaction(_("XCODE PROJECTS DIRECTORY"), _("Please choose the Xcode Projects Directory using the directory chooser.\n{b}The directory chooser may have opened behind this window.{/b}"))
|
||||
|
||||
path, is_default = choose_directory(persistent.xcode_projects_directory)
|
||||
|
||||
if is_default:
|
||||
interface.info(_("Ren'Py has set the Xcode Projects Directory to:"), "[path!q]", path=path)
|
||||
|
||||
persistent.xcode_projects_directory = path
|
||||
|
||||
jump ios
|
||||
|
||||
label create_xcode_project:
|
||||
|
||||
$ ios_create(None, True)
|
||||
|
||||
jump ios
|
||||
|
||||
label update_xcode_project:
|
||||
$ ios_populate(None, True)
|
||||
|
||||
jump ios
|
||||
|
||||
init python:
|
||||
|
||||
def ios_create_command():
|
||||
ap = renpy.arguments.ArgumentParser()
|
||||
ap.add_argument("project", help="The path to the Ren'Py project.")
|
||||
ap.add_argument("destination", help="The path the iOS project that will be created.")
|
||||
|
||||
args = ap.parse_args()
|
||||
|
||||
p = project.Project(args.project)
|
||||
|
||||
ios_create(p, False, args.destination)
|
||||
|
||||
return False
|
||||
|
||||
renpy.arguments.register_command("ios_create", ios_create_command)
|
||||
|
||||
|
||||
def ios_populate_command():
|
||||
ap = renpy.arguments.ArgumentParser()
|
||||
ap.add_argument("project", help="The path to the Ren'Py project.")
|
||||
ap.add_argument("destination", help="The path the iOS project that will be created.")
|
||||
|
||||
args = ap.parse_args()
|
||||
|
||||
p = project.Project(args.project)
|
||||
|
||||
ios_populate(p, False, args.destination)
|
||||
|
||||
return False
|
||||
|
||||
renpy.arguments.register_command("ios_populate", ios_populate_command)
|
||||
@@ -1,150 +0,0 @@
|
||||
# Copyright 2004-2025 Tom Rothamel <pytom@bishoujo.us>
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person
|
||||
# obtaining a copy of this software and associated documentation files
|
||||
# (the "Software"), to deal in the Software without restriction,
|
||||
# including without limitation the rights to use, copy, modify, merge,
|
||||
# publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
# and to permit persons to whom the Software is furnished to do so,
|
||||
# subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
init python:
|
||||
|
||||
def find_itch_butler():
|
||||
|
||||
import requests
|
||||
|
||||
if renpy.windows:
|
||||
platform = "windows-amd64"
|
||||
exe = "butler.exe"
|
||||
elif renpy.macintosh:
|
||||
platform = "darwin-amd64"
|
||||
exe = "butler"
|
||||
else:
|
||||
platform = "linux-amd64"
|
||||
exe = "butler"
|
||||
|
||||
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 Exception:
|
||||
pass
|
||||
|
||||
with interface.error_handling(_("Downloading the itch.io butler.")):
|
||||
url = "https://broth.itch.zone/butler/{}/LATEST/archive/default".format(platform)
|
||||
|
||||
response = requests.get(url, headers={'User-Agent' : "Renpy"}, proxies=renpy.proxies)
|
||||
|
||||
with open(zip, "wb") as f:
|
||||
f.write(response.content)
|
||||
|
||||
import zipfile
|
||||
|
||||
with zipfile.ZipFile(zip) as zf:
|
||||
zf.extractall(dn)
|
||||
|
||||
try:
|
||||
os.chmod(exe, 0o755)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return exe
|
||||
|
||||
|
||||
label itch:
|
||||
|
||||
call build_update_dump
|
||||
|
||||
|
||||
python hide:
|
||||
|
||||
butler = find_itch_butler()
|
||||
|
||||
build = project.current.dump["build"]
|
||||
|
||||
destination = build["destination"]
|
||||
parent = os.path.dirname(project.current.path)
|
||||
destination = os.path.join(parent, destination)
|
||||
|
||||
version = build["version"]
|
||||
|
||||
if not os.path.exists(destination):
|
||||
interface.error(
|
||||
_("The built distributions could not be found. Please choose 'Build' and try again."),
|
||||
label="build_distributions"
|
||||
)
|
||||
|
||||
# A list of full filename, channel tuples.
|
||||
files = [ ]
|
||||
|
||||
for fn in os.listdir(destination):
|
||||
|
||||
for pattern, channel in reversed(build['itch_channels'].items()):
|
||||
if fnmatch.fnmatch(fn, pattern):
|
||||
break
|
||||
else:
|
||||
continue
|
||||
|
||||
fn = os.path.join(destination, fn)
|
||||
files.append((fn, channel))
|
||||
|
||||
if not os.path.exists(destination):
|
||||
interface.error(
|
||||
_("No uploadable files were found. Please choose 'Build' and try again."),
|
||||
label="build_distributions"
|
||||
)
|
||||
|
||||
if not butler:
|
||||
interface.error(
|
||||
_("The butler program was not found."),
|
||||
_("Please install the itch.io app, which includes butler, and try again."),
|
||||
label="build_distributions"
|
||||
)
|
||||
|
||||
itch_project = project.current.dump["build"].get("itch_project", None)
|
||||
|
||||
if itch_project is None or ("/" not in itch_project):
|
||||
interface.error(
|
||||
_("The name of the itch project has not been set."),
|
||||
_("Please {a=https://itch.io/game/new}create your project{/a}, then add a line like \n{vspace=5}define build.itch_project = \"user-name/game-name\"\n{vspace=5} to options.rpy."),
|
||||
label="build_distributions"
|
||||
)
|
||||
|
||||
cc = ConsoleCommand()
|
||||
|
||||
for filename, channel in files:
|
||||
|
||||
cc.add(
|
||||
butler,
|
||||
"push",
|
||||
filename,
|
||||
itch_project + ":" + channel,
|
||||
"--userversion",
|
||||
build["version"],
|
||||
)
|
||||
|
||||
cc.run()
|
||||
|
||||
|
||||
|
||||
|
||||
jump build_distributions
|
||||
@@ -1,55 +0,0 @@
|
||||
# Copyright 2004-2025 Tom Rothamel <pytom@bishoujo.us>
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person
|
||||
# obtaining a copy of this software and associated documentation files
|
||||
# (the "Software"), to deal in the Software without restriction,
|
||||
# including without limitation the rights to use, copy, modify, merge,
|
||||
# publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
# and to permit persons to whom the Software is furnished to do so,
|
||||
# subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
init python hide:
|
||||
import os
|
||||
|
||||
macapp = os.path.join(config.renpy_base, "renpy.app/Contents/MacOS")
|
||||
maclib = os.path.join(config.renpy_base, distribute.py("lib/py{major}-mac-universal"))
|
||||
|
||||
if os.path.exists(maclib):
|
||||
|
||||
for fn in os.listdir(maclib):
|
||||
try:
|
||||
|
||||
fn = os.path.join(maclib, fn)
|
||||
|
||||
if fn.endswith(".macho"):
|
||||
nfn = fn.rpartition(".")[0]
|
||||
|
||||
with open(fn, "rb") as f:
|
||||
f.read(5)
|
||||
data = f.read()
|
||||
|
||||
with open(nfn, "wb") as f:
|
||||
f.write(data)
|
||||
|
||||
os.chmod(nfn, 0o755)
|
||||
os.unlink(fn)
|
||||
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
macpython = os.path.join(maclib, "python")
|
||||
|
||||
if sys.executable.startswith(macapp) and os.path.exists(macpython):
|
||||
sys.executable = macpython
|
||||
@@ -1,271 +0,0 @@
|
||||
# Copyright 2004-2025 Tom Rothamel <pytom@bishoujo.us>
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person
|
||||
# obtaining a copy of this software and associated documentation files
|
||||
# (the "Software"), to deal in the Software without restriction,
|
||||
# including without limitation the rights to use, copy, modify, merge,
|
||||
# publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
# and to permit persons to whom the Software is furnished to do so,
|
||||
# subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
# This file contains mobile build things that are shared by the android
|
||||
# and iOS builds.
|
||||
|
||||
init -1 python:
|
||||
|
||||
def check_hash_txt(module):
|
||||
fn1 = module + "_hash.txt"
|
||||
fn2 = renpy.fsencode(os.path.join(config.renpy_base, module, "hash.txt"))
|
||||
|
||||
# No hash file? We're in dev mode - ignore.
|
||||
if not renpy.loadable(fn1):
|
||||
return True
|
||||
|
||||
hash1 = renpy.open_file(fn1, encoding="utf-8").read()
|
||||
|
||||
if not os.path.exists(fn2):
|
||||
return False
|
||||
|
||||
hash2 = open(fn2).read()
|
||||
|
||||
return hash1.strip() == hash2.strip()
|
||||
|
||||
class LaunchEmulator(Action):
|
||||
|
||||
def __init__(self, emulator, variants):
|
||||
self.emulator = emulator
|
||||
self.variants = variants
|
||||
|
||||
def __call__(self):
|
||||
|
||||
env = {
|
||||
"RENPY_EMULATOR" : self.emulator,
|
||||
"RENPY_VARIANT" : self.variants,
|
||||
}
|
||||
|
||||
p = project.current
|
||||
p.launch(env=env)
|
||||
|
||||
class MobileInterface(object):
|
||||
"""
|
||||
This is used to interface between the launcher and RAPT/RENIOS.
|
||||
"""
|
||||
|
||||
def __init__(self, platform, edit=True, filename=None):
|
||||
"""
|
||||
`platform`
|
||||
The name of the platform we're using for. Used for libraries,
|
||||
cancel labels, and logfiles.
|
||||
|
||||
`edit`
|
||||
If true, we launch the log file in the editor on failure.
|
||||
"""
|
||||
|
||||
self.platform = platform
|
||||
self.edit = edit
|
||||
|
||||
self.process = None
|
||||
|
||||
if filename is None:
|
||||
filename = platform + ".txt"
|
||||
|
||||
self.filename = project.current.temp_filename(filename)
|
||||
|
||||
self.info_msg = ""
|
||||
|
||||
with open(self.filename, "w") as f:
|
||||
f.write(renpy.version() + "\n")
|
||||
pass
|
||||
|
||||
def log(self, msg):
|
||||
with open(self.filename, "a") as f:
|
||||
f.write("\n")
|
||||
f.write(msg)
|
||||
f.write("\n")
|
||||
|
||||
def info(self, prompt):
|
||||
self.info_msg = prompt
|
||||
interface.processing(prompt, pause=False)
|
||||
self.log(prompt)
|
||||
|
||||
def open_directory(self, directory, prompt):
|
||||
renpy.run(store.OpenDirectory(directory, absolute=True))
|
||||
interface.info(prompt)
|
||||
|
||||
def yesno(self, prompt, submessage=None):
|
||||
return interface.yesno(prompt, submessage=submessage)
|
||||
|
||||
def yesno_choice(self, prompt, default=None):
|
||||
choices = [ (True, "Yes"), (False, "No") ]
|
||||
return interface.choice(prompt, choices, default)
|
||||
|
||||
def terms(self, url, prompt):
|
||||
submessage = _("{a=%s}%s{/a}") % (url, url)
|
||||
|
||||
if not interface.yesno(prompt, submessage=submessage):
|
||||
self.fail("You must accept the terms and conditions to proceed.", edit=False)
|
||||
|
||||
|
||||
def input(self, prompt, empty=None):
|
||||
|
||||
if empty is None:
|
||||
empty = ''
|
||||
|
||||
while True:
|
||||
rv = interface.input(_("QUESTION"), prompt, default=empty, cancel=Jump("android"))
|
||||
|
||||
rv = rv.strip()
|
||||
|
||||
if rv:
|
||||
return rv
|
||||
|
||||
def choice(self, prompt, choices, default):
|
||||
return interface.choice(prompt, choices, default, cancel=Jump("android"))
|
||||
|
||||
def fail(self, prompt, edit=True):
|
||||
self.log(prompt)
|
||||
prompt = re.sub(r'(http://\S+)', r'{a=\1}\1{/a}', prompt)
|
||||
|
||||
# Open android.txt in the editor.
|
||||
if edit and self.edit:
|
||||
self.open_editor()
|
||||
|
||||
interface.error(prompt, label="android")
|
||||
|
||||
def open_editor(self):
|
||||
editor.EditAbsolute(self.filename)()
|
||||
|
||||
def success(self, prompt):
|
||||
self.log(prompt)
|
||||
interface.info(prompt, pause=False)
|
||||
|
||||
def final_success(self, prompt):
|
||||
self.log(prompt)
|
||||
interface.info(prompt, label="android")
|
||||
|
||||
def run_yes_thread(self):
|
||||
import time
|
||||
|
||||
try:
|
||||
while self.run_yes:
|
||||
self.process.stdin.write(b'y\n')
|
||||
self.process.stdin.flush()
|
||||
time.sleep(.2)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def call(self, cmd, cancel=False, use_path=False, yes=False):
|
||||
|
||||
renpy.not_infinite_loop(30)
|
||||
|
||||
cmd = [ renpy.fsencode(i) for i in cmd ]
|
||||
|
||||
self.cmd = cmd
|
||||
|
||||
f = open(self.filename, "a")
|
||||
|
||||
f.write("\n\n\n")
|
||||
|
||||
if cancel:
|
||||
cancel_action = self.cancel
|
||||
else:
|
||||
cancel_action = None
|
||||
|
||||
startupinfo = None
|
||||
if renpy.windows:
|
||||
startupinfo = subprocess.STARTUPINFO()
|
||||
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
|
||||
|
||||
self.yes_thread = None
|
||||
|
||||
try:
|
||||
interface.processing(self.info_msg, show_screen=True, cancel=cancel_action)
|
||||
|
||||
kwargs = { }
|
||||
|
||||
try:
|
||||
self.process = subprocess.Popen(cmd, cwd=renpy.fsencode(RAPT_PATH), stdout=f, stderr=f, stdin=subprocess.PIPE, startupinfo=startupinfo, **kwargs)
|
||||
# avoid SIGTTIN caused by e.g. gradle doing empty read on terminal stdin
|
||||
if not yes:
|
||||
self.process.stdin.close()
|
||||
except Exception:
|
||||
import traceback
|
||||
traceback.print_exc(file=f)
|
||||
raise
|
||||
|
||||
if yes:
|
||||
import threading
|
||||
self.run_yes = True
|
||||
self.yes_thread = threading.Thread(target=self.run_yes_thread)
|
||||
self.yes_thread.daemon = True
|
||||
self.yes_thread.start()
|
||||
|
||||
renpy.call_screen("android_process", interface=self)
|
||||
finally:
|
||||
f.close()
|
||||
interface.hide_screen()
|
||||
|
||||
if yes and self.yes_thread:
|
||||
self.run_yes = False
|
||||
self.yes_thread.join()
|
||||
|
||||
try:
|
||||
self.process.stdin.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
self.process = None
|
||||
self.yes_thread = None
|
||||
|
||||
def check_process(self):
|
||||
rv = self.process.poll()
|
||||
|
||||
if rv is not None:
|
||||
renpy.not_infinite_loop(30)
|
||||
|
||||
if rv:
|
||||
raise subprocess.CalledProcessError(rv, self.cmd)
|
||||
else:
|
||||
return True
|
||||
|
||||
def download(self, url, dest):
|
||||
try:
|
||||
d = Downloader(url, dest)
|
||||
cancel_action = [ d.cancel, Jump(self.platform) ]
|
||||
interface.processing(self.info_msg, show_screen=True, cancel=cancel_action, bar_value=DownloaderValue(d))
|
||||
ui.timer(.1, action=d.check, repeat=True)
|
||||
ui.interact()
|
||||
finally:
|
||||
interface.hide_screen()
|
||||
|
||||
def background(self, f):
|
||||
try:
|
||||
t = threading.Thread(target=f)
|
||||
t.start()
|
||||
|
||||
interface.processing(self.info_msg, show_screen=True)
|
||||
|
||||
while t.is_alive():
|
||||
renpy.pause(0)
|
||||
t.join(0.25)
|
||||
|
||||
finally:
|
||||
interface.hide_screen()
|
||||
|
||||
|
||||
def cancel(self):
|
||||
if self.process:
|
||||
self.process.terminate()
|
||||
|
||||
renpy.jump(self.platform)
|
||||
@@ -1,293 +0,0 @@
|
||||
# Copyright 2004-2025 Tom Rothamel <pytom@bishoujo.us>
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person
|
||||
# obtaining a copy of this software and associated documentation files
|
||||
# (the "Software"), to deal in the Software without restriction,
|
||||
# including without limitation the rights to use, copy, modify, merge,
|
||||
# publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
# and to permit persons to whom the Software is furnished to do so,
|
||||
# subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
# Should translation files be shown in the launcher file navigation?
|
||||
default persistent.show_translation_files = True
|
||||
|
||||
init python in navigation:
|
||||
import store.interface as interface
|
||||
import store.project as project
|
||||
import store.editor as editor
|
||||
from store import persistent, Action
|
||||
|
||||
|
||||
# The last navigation screen we've seen. This is the scree we try to go
|
||||
# to the next time we enter navigation. (We may not be able to go there,
|
||||
# if the screen is empty.)
|
||||
if persistent.navigation is None:
|
||||
persistent.navigation = "label"
|
||||
|
||||
# A map from a kind of information, to how we should sort it. Possible
|
||||
# sorts are alphabetical, by-file, natural.
|
||||
if persistent.navigation_sort is None:
|
||||
persistent.navigation_sort = { }
|
||||
|
||||
if persistent.navigate_private is None:
|
||||
persistent.navigate_private = False
|
||||
|
||||
if persistent.navigate_library is None:
|
||||
persistent.navigate_library = False
|
||||
|
||||
# A list of kinds of navigation we support.
|
||||
KINDS = [ "file", "label", "define", "transform", "screen", "callable", "todo" ]
|
||||
|
||||
# A map from kind name to adjustment.
|
||||
adjustments = { }
|
||||
|
||||
for i in KINDS:
|
||||
persistent.navigation_sort.setdefault(i, "by-file")
|
||||
adjustments[i] = ui.adjustment()
|
||||
|
||||
def group_and_sort(kind):
|
||||
"""
|
||||
This is responsible for pulling navigation information of the
|
||||
appropriate kind out of project.current.dump, grouping it,
|
||||
and sorting it.
|
||||
|
||||
This returns a list of (group, list of (name, filename, line)). The
|
||||
group may be a string or None.
|
||||
"""
|
||||
|
||||
project.current.update_dump()
|
||||
|
||||
sort = persistent.navigation_sort[kind]
|
||||
|
||||
name_map = project.current.dump.get("location", {}).get(kind, { })
|
||||
|
||||
groups = { }
|
||||
|
||||
for name, loc in name_map.items():
|
||||
filename, line = loc
|
||||
filename = filename.replace("\\", "/")
|
||||
|
||||
if sort == "alphabetical":
|
||||
group = None
|
||||
else:
|
||||
group = filename
|
||||
if group.startswith("game/"):
|
||||
group = group[5:]
|
||||
|
||||
g = groups.get(group, None)
|
||||
if g is None:
|
||||
groups[group] = g = [ ]
|
||||
|
||||
g.append((name, filename, line))
|
||||
|
||||
for g in groups.values():
|
||||
if sort == "natural":
|
||||
g.sort(key=lambda a : a[2])
|
||||
else:
|
||||
g.sort(key=lambda a : a[0].lower())
|
||||
|
||||
rv = list(groups.items())
|
||||
rv.sort()
|
||||
|
||||
return rv
|
||||
|
||||
def group_and_sort_files():
|
||||
|
||||
rv = [ ]
|
||||
|
||||
for fn in project.current.script_files():
|
||||
shortfn = fn
|
||||
shortfn = shortfn.replace("\\", "/")
|
||||
|
||||
if shortfn.startswith("game/"):
|
||||
shortfn = fn[5:]
|
||||
|
||||
if shortfn.startswith("tl/") and not persistent.show_translation_files:
|
||||
continue
|
||||
|
||||
rv.append((shortfn, fn, None))
|
||||
|
||||
rv.sort()
|
||||
|
||||
return [ (None, rv) ]
|
||||
|
||||
class ChangeKind(Action):
|
||||
"""
|
||||
Changes the kind of thing we're navigating over.
|
||||
"""
|
||||
|
||||
def __init__(self, kind):
|
||||
self.kind = kind
|
||||
|
||||
def get_selected(self):
|
||||
return persistent.navigation == self.kind
|
||||
|
||||
def __call__(self):
|
||||
if persistent.navigation == self.kind:
|
||||
return
|
||||
|
||||
persistent.navigation = self.kind
|
||||
renpy.jump("navigation_loop")
|
||||
|
||||
class ChangeSort(Action):
|
||||
"""
|
||||
Changes the sort order.
|
||||
"""
|
||||
|
||||
def __init__(self, sort):
|
||||
self.sort = sort
|
||||
|
||||
def get_selected(self):
|
||||
return persistent.navigation_sort[persistent.navigation] == self.sort
|
||||
|
||||
def __call__(self):
|
||||
if self.get_selected():
|
||||
return
|
||||
|
||||
persistent.navigation_sort[persistent.navigation] = self.sort
|
||||
renpy.jump("navigation_loop")
|
||||
|
||||
|
||||
screen navigation:
|
||||
|
||||
$ todo_count = len(project.current.dump.get("location", {}).get("todo", []))
|
||||
|
||||
frame:
|
||||
style_group "l"
|
||||
style "l_root"
|
||||
|
||||
window:
|
||||
|
||||
has vbox
|
||||
|
||||
frame style "l_label":
|
||||
has hbox xfill True
|
||||
text _("Navigate: [project.current.display_name!q]") style "l_label_text"
|
||||
alt _("Navigate Script")
|
||||
|
||||
frame:
|
||||
style "l_alternate"
|
||||
style_group "l_small"
|
||||
|
||||
has hbox
|
||||
|
||||
if persistent.navigation != "file":
|
||||
text _("Order: ")
|
||||
textbutton _("alphabetical") action navigation.ChangeSort("alphabetical")
|
||||
text " | "
|
||||
textbutton _("by-file") action navigation.ChangeSort("by-file")
|
||||
text " | "
|
||||
textbutton _("natural") action navigation.ChangeSort("natural")
|
||||
|
||||
null width HALF_INDENT
|
||||
|
||||
textbutton _("refresh") action Jump("navigation_refresh")
|
||||
|
||||
|
||||
add HALF_SPACER
|
||||
|
||||
frame style "l_indent":
|
||||
hbox:
|
||||
spacing HALF_INDENT
|
||||
text _("Category:")
|
||||
alt ""
|
||||
|
||||
textbutton _("files") action navigation.ChangeKind("file")
|
||||
textbutton _("labels") action navigation.ChangeKind("label")
|
||||
textbutton _("defines") action navigation.ChangeKind("define")
|
||||
textbutton _("transforms") action navigation.ChangeKind("transform")
|
||||
textbutton _("screens") action navigation.ChangeKind("screen")
|
||||
textbutton _("callables") action navigation.ChangeKind("callable")
|
||||
textbutton (__("TODOs") + " (" + str(todo_count) + ")") action navigation.ChangeKind("todo")
|
||||
|
||||
add SPACER
|
||||
add SEPARATOR
|
||||
|
||||
frame style "l_indent_margin":
|
||||
|
||||
if groups:
|
||||
|
||||
viewport:
|
||||
mousewheel True
|
||||
scrollbars "vertical"
|
||||
yadjustment navigation.adjustments[persistent.navigation]
|
||||
|
||||
vbox:
|
||||
style_group "l_navigation"
|
||||
|
||||
if persistent.navigation == "file":
|
||||
textbutton _("Show translation files") style "l_checkbox" action [ ToggleField(persistent, "show_translation_files"), Jump("navigation_loop") ]
|
||||
add SPACER
|
||||
|
||||
for group_name, group in groups:
|
||||
|
||||
if group_name is not None:
|
||||
text "[group_name!q]"
|
||||
|
||||
if persistent.navigation == "todo":
|
||||
vbox:
|
||||
for name, filename, line in group:
|
||||
textbutton "[name!q]" action editor.Edit(filename, line)
|
||||
|
||||
else:
|
||||
hbox:
|
||||
box_wrap True
|
||||
|
||||
for name, filename, line in group:
|
||||
textbutton "[name!q]" action editor.Edit(filename, line)
|
||||
|
||||
if group_name is not None:
|
||||
add SPACER
|
||||
|
||||
if persistent.navigation == "file":
|
||||
add SPACER
|
||||
textbutton _("+ Add script file") action Jump("add_file") style "l_button"
|
||||
|
||||
else:
|
||||
|
||||
fixed:
|
||||
|
||||
if persistent.navigation == "todo":
|
||||
|
||||
text _("No TODO comments found.\n\nTo create one, include \"# TODO\" in your script."):
|
||||
textalign 0.5
|
||||
xalign 0.5
|
||||
yalign 0.5
|
||||
|
||||
else:
|
||||
|
||||
text _("The list of names is empty."):
|
||||
xalign 0.5
|
||||
yalign 0.5
|
||||
|
||||
textbutton _("Return") action Jump("front_page") style "l_left_button"
|
||||
textbutton _("Launch Project") action project.Launch() style "l_right_button"
|
||||
|
||||
label navigation:
|
||||
label navigation_loop:
|
||||
|
||||
python in navigation:
|
||||
|
||||
kind = persistent.navigation
|
||||
|
||||
if kind == "file":
|
||||
groups = group_and_sort_files()
|
||||
else:
|
||||
groups = group_and_sort(kind)
|
||||
|
||||
renpy.call_screen("navigation", groups=groups)
|
||||
|
||||
label navigation_refresh:
|
||||
$ project.current.update_dump(True)
|
||||
jump navigation_loop
|
||||
@@ -1,81 +0,0 @@
|
||||
# Copyright 2004-2025 Tom Rothamel <pytom@bishoujo.us>
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person
|
||||
# obtaining a copy of this software and associated documentation files
|
||||
# (the "Software"), to deal in the Software without restriction,
|
||||
# including without limitation the rights to use, copy, modify, merge,
|
||||
# publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
# and to permit persons to whom the Software is furnished to do so,
|
||||
# subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
init python:
|
||||
import shutil
|
||||
import os
|
||||
import time
|
||||
import re
|
||||
|
||||
|
||||
label new_project:
|
||||
|
||||
if persistent.projects_directory is None:
|
||||
call choose_projects_directory
|
||||
|
||||
if persistent.projects_directory is None:
|
||||
$ interface.error(_("The projects directory could not be set. Giving up."))
|
||||
|
||||
python:
|
||||
new_project_language = __("{#language name and font}")
|
||||
gui_kind = "new_gui_project"
|
||||
|
||||
# When translating this, feel free to replace [new_project_language] with the translation of your language.
|
||||
interface.info(_("You will be creating an [new_project_language]{#this substitution may be localized} language project. Change the launcher language in preferences to create a project in another language."), cancel=Jump("front_page"))
|
||||
|
||||
python:
|
||||
if project.manager.templates:
|
||||
gui_kind, gui_template_path = interface.choice(
|
||||
_("Please select a template project to use."),
|
||||
[ ( ("new_gui_project", None), _("Do not use a template project.") ) ] +
|
||||
[ ( ("new_template_project", p.path ), p.name) for p in project.manager.templates ],
|
||||
("new_gui_project", None),
|
||||
cancel=Jump("front_page"),
|
||||
)
|
||||
|
||||
python:
|
||||
project_name = ""
|
||||
while True:
|
||||
project_name = interface.input(
|
||||
_("PROJECT NAME"),
|
||||
_("Please enter the name of your project:"),
|
||||
allow=interface.PROJECT_LETTERS,
|
||||
cancel=Jump("front_page"),
|
||||
default=project_name,
|
||||
)
|
||||
|
||||
project_name = project_name.strip()
|
||||
if not project_name:
|
||||
interface.error(_("The project name may not be empty."), label=None)
|
||||
continue
|
||||
|
||||
project_dir = os.path.join(persistent.projects_directory, project_name)
|
||||
|
||||
if project.manager.get(project_name) is not None:
|
||||
interface.error(_("[project_name!q] already exists. Please choose a different project name."), project_name=project_name, label=None)
|
||||
continue
|
||||
|
||||
if os.path.exists(project_dir):
|
||||
interface.error(_("[project_dir!q] already exists. Please choose a different project name."), project_dir=project_dir, label=None)
|
||||
continue
|
||||
break
|
||||
|
||||
jump expression gui_kind
|
||||
@@ -1,462 +0,0 @@
|
||||
# Copyright 2004-2025 Tom Rothamel <pytom@bishoujo.us>
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person
|
||||
# obtaining a copy of this software and associated documentation files
|
||||
# (the "Software"), to deal in the Software without restriction,
|
||||
# including without limitation the rights to use, copy, modify, merge,
|
||||
# publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
# and to permit persons to whom the Software is furnished to do so,
|
||||
# subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
# This file contains some of the options that can be changed to customize
|
||||
# your Ren'Py game. It only contains the most common options... there
|
||||
# is quite a bit more customization you can do.
|
||||
#
|
||||
# Lines beginning with two '#' marks are comments, and you shouldn't
|
||||
# uncomment them. Lines beginning with a single '#' mark are
|
||||
# commented-out code, and you may want to uncomment them when
|
||||
# appropriate.
|
||||
|
||||
init -1 python hide:
|
||||
|
||||
# Update the searchpath to find additional fonts.
|
||||
config.searchpath.append(config.renpy_base + "/sdk-fonts")
|
||||
|
||||
# Should we enable the use of developer tools? This should be
|
||||
# set to False before the game is released, so the user can't
|
||||
# cheat using developer tools.
|
||||
|
||||
config.developer = True
|
||||
|
||||
# These control the width and height of the screen.
|
||||
|
||||
config.screen_width = 800
|
||||
config.screen_height = 600
|
||||
|
||||
# This controls the title of the window, when Ren'Py is
|
||||
# running in a window.
|
||||
|
||||
config.window_title = u"Ren'Py Launcher"
|
||||
|
||||
# These control the name and version of the game, that are reported
|
||||
# with tracebacks and other debugging logs.
|
||||
config.name = "Ren'Py Launcher"
|
||||
config.version = renpy.version().split()[1]
|
||||
|
||||
#####################
|
||||
# Themes
|
||||
|
||||
# We then want to call a theme function. themes.roundrect is
|
||||
# a theme that features the use of rounded rectangles. It's
|
||||
# the only theme we currently support.
|
||||
#
|
||||
# The theme function takes a number of parameters that can
|
||||
# customize the color scheme.
|
||||
|
||||
theme.roundrect(
|
||||
# Theme: Roundrect
|
||||
# Color scheme: Basic Blue
|
||||
|
||||
# The color of an idle widget face.
|
||||
widget = "#003c78",
|
||||
|
||||
# The color of a focused widget face.
|
||||
widget_hover = "#0050a0",
|
||||
|
||||
# The color of the text in a widget.
|
||||
widget_text = "#c8ffff",
|
||||
|
||||
# The color of the text in a selected widget. (For
|
||||
# example, the current value of a preference.)
|
||||
widget_selected = "#ffffc8",
|
||||
|
||||
# The color of a disabled widget face.
|
||||
disabled = "#404040",
|
||||
|
||||
# The color of disabled widget text.
|
||||
disabled_text = "#c8c8c8",
|
||||
|
||||
# The color of informational labels.
|
||||
label = "#ffffff",
|
||||
|
||||
# The color of a frame containing widgets.
|
||||
frame = "#6496c8",
|
||||
|
||||
# The background of the main menu. This can be a color
|
||||
# beginning with '#', or an image filename. The latter
|
||||
# should take up the full height and width of the screen.
|
||||
mm_root = "#dcebff",
|
||||
|
||||
# The background of the game menu. This can be a color
|
||||
# beginning with '#', or an image filename. The latter
|
||||
# should take up the full height and width of the screen.
|
||||
gm_root = "#dcebff",
|
||||
|
||||
# If this is True, the in-game window is rounded. If False,
|
||||
# the in-game window is square.
|
||||
rounded_window = False,
|
||||
|
||||
# And we're done with the theme. The theme will customize
|
||||
# various styles, so if we want to change them, we should
|
||||
# do so below.
|
||||
)
|
||||
|
||||
#####################
|
||||
# Help.
|
||||
|
||||
# This lets you configure the help option on the Ren'Py menus.
|
||||
# It may be:
|
||||
# - A label in the script, in which case that label is called to
|
||||
# show help to the user.
|
||||
# - A file name relative to the base directory, which is opened in a
|
||||
# web browser.
|
||||
# - None, to disable help.
|
||||
config.help = "README.html"
|
||||
|
||||
#####################
|
||||
# Transitions.
|
||||
|
||||
# Used when entering the game menu from the game.
|
||||
config.enter_transition = None
|
||||
|
||||
# Used when exiting the game menu to the game.
|
||||
config.exit_transition = None
|
||||
|
||||
# Used between screens of the game menu.
|
||||
config.intra_transition = None
|
||||
|
||||
# Used when entering the game menu from the main menu.
|
||||
config.main_game_transition = None
|
||||
|
||||
# Used when returning to the main menu from the game.
|
||||
config.game_main_transition = None
|
||||
|
||||
# Used when entering the main menu from the splashscreen.
|
||||
config.end_splash_transition = None
|
||||
|
||||
# Used when entering the main menu after the game has ended.
|
||||
config.end_game_transition = None
|
||||
|
||||
# Used when a game is loaded.
|
||||
config.after_load_transition = None
|
||||
|
||||
# Used when the window is shown.
|
||||
config.window_show_transition = None
|
||||
|
||||
# Used when the window is hidden.
|
||||
config.window_hide_transition = None
|
||||
|
||||
|
||||
#####################
|
||||
# This is the name of the directory where the game's data is
|
||||
# stored. (It needs to be set early, before any other init code
|
||||
# is run, so the persistent information can be found by the init code.)
|
||||
python early:
|
||||
config.save_directory = "launcher-4"
|
||||
|
||||
init -1 python hide:
|
||||
#####################
|
||||
# Default values of Preferences.
|
||||
|
||||
# Note: These options are only evaluated the first time a
|
||||
# game is run. To have them run a second time, delete
|
||||
# game/saves/persistent
|
||||
|
||||
# Should we start in fullscreen mode?
|
||||
|
||||
config.default_fullscreen = False
|
||||
|
||||
# The default text speed in characters per second. 0 is infinite.
|
||||
|
||||
config.default_text_cps = 0
|
||||
|
||||
#####################
|
||||
# More customizations can go here.
|
||||
|
||||
config.has_sound = False
|
||||
config.has_music = False
|
||||
config.has_voice = False
|
||||
config.force_sound = False
|
||||
config.quit_action = Quit(confirm=False)
|
||||
|
||||
if renpy.macintosh:
|
||||
config.window_icon = "images/window-icon-mac.png"
|
||||
else:
|
||||
config.window_icon = "images/window-icon.png"
|
||||
|
||||
config.has_autosave = False
|
||||
config.log_enable = False
|
||||
config.mouse_hide_time = None
|
||||
|
||||
_game_menu_screen = None
|
||||
|
||||
config.underlay = [
|
||||
renpy.Keymap(
|
||||
screenshot = _screenshot,
|
||||
reload_game = _reload_game,
|
||||
developer = _developer,
|
||||
quit = renpy.quit_event,
|
||||
iconify = renpy.iconify,
|
||||
help = _help,
|
||||
choose_renderer = renpy.curried_call_in_new_context("_choose_renderer"),
|
||||
console = _console.enter,
|
||||
profile_once = _profile_once,
|
||||
memory_profile = _memory_profile,
|
||||
self_voicing = Preference("self voicing", "toggle"),
|
||||
clipboard_voicing = Preference("clipboard voicing", "toggle"),
|
||||
debug_voicing = Preference("debug voicing", "toggle"),
|
||||
progress_screen = _progress_screen,
|
||||
),
|
||||
]
|
||||
|
||||
config.rollback_enabled = False
|
||||
|
||||
# This section controls how to build Ren'Py. (Building the launcher is how
|
||||
# we build Ren'Py distributions.)
|
||||
init python:
|
||||
|
||||
# We're building Ren'Py tonight.
|
||||
build.renpy = True
|
||||
|
||||
# The version number that's supplied to the updater.
|
||||
build.version = "Ren'Py {}".format(config.version)
|
||||
|
||||
# The name that's used for directories and archive files. For example, if
|
||||
# this is 'mygame-1.0', the windows distribution will be in the
|
||||
# directory 'mygame-1.0-win', in the 'mygame-1.0-win.zip' file.
|
||||
|
||||
if 'RENPY_BUILD_VERSION' in os.environ:
|
||||
build.directory_name = "renpy-" + os.environ['RENPY_BUILD_VERSION']
|
||||
else:
|
||||
build.directory_name = "renpy-" + config.version.rsplit('.', 1)[0]
|
||||
|
||||
# The name that's uses for executables - the program that users will run
|
||||
# to start the game. For example, if this is 'mygame', then on Windows,
|
||||
# users can click 'mygame.exe' to start the game.
|
||||
build.executable_name = "renpy"
|
||||
|
||||
# If True, Ren'Py will include update information into packages. This
|
||||
# allows the updater to run.
|
||||
build.include_update = True
|
||||
|
||||
# Build both kinds of updates.
|
||||
build.update_formats = [ "zsync", "rpu" ]
|
||||
|
||||
# Allow empty directories, so we can distribute the images directory.
|
||||
build.exclude_empty_directories = False
|
||||
|
||||
# Mac signing options.
|
||||
import os
|
||||
build.mac_identity = os.environ.get("RENPY_MAC_IDENTITY", None)
|
||||
build.mac_codesign_command = [ config.renpy_base + "/scripts/mac/mac_sign_client.sh", "{identity}", "{app}" ]
|
||||
build.mac_create_dmg_command = [ config.renpy_base + "/scripts/mac/mac_dmg_client.sh", "{identity}", "{volname}", "{sourcedir}", "{dmg}" ]
|
||||
build.mac_codesign_dmg_command = [ "/bin/true" ]
|
||||
|
||||
|
||||
# Clear out various file patterns.
|
||||
build.renpy_patterns = [ ]
|
||||
build.early_base_patterns = [ ]
|
||||
build.base_patterns = [ ]
|
||||
build.late_base_patterns = [ ]
|
||||
|
||||
# We don't need to clear out the executable patterns, since they're
|
||||
# correct for Ren'Py.
|
||||
|
||||
# Now, add the Ren'Py distribution in using classify_renpy.
|
||||
|
||||
build.classify_renpy("**~", None)
|
||||
build.classify_renpy("**/#*", None)
|
||||
build.classify_renpy("**/thumbs.db", None)
|
||||
build.classify_renpy("**/.*", None)
|
||||
|
||||
build.classify_renpy("rapt/**/libLive2DCubismCore.so", None)
|
||||
build.classify_renpy("rapt/symbols/", None)
|
||||
build.classify_renpy("rapt/**", "rapt")
|
||||
build.executable("rapt/prototype/gradlew")
|
||||
|
||||
build.classify_renpy("renios/prototype/base/", None)
|
||||
build.classify_renpy("renios/prototype/prototype.xcodeproj/*.xcworkspace/", None)
|
||||
build.classify_renpy("renios/prototype/prototype.xcodeproj/xcuserdata/", None)
|
||||
build.classify_renpy("renios/prototype/**", "renios")
|
||||
build.classify_renpy("renios/buildlib/**", "renios")
|
||||
build.classify_renpy("renios/ios.py", "renios")
|
||||
build.classify_renpy("renios/version.txt", "renios")
|
||||
build.classify_renpy("renios/", "renios")
|
||||
|
||||
build.classify_renpy("web/game.zip", None)
|
||||
build.classify_renpy("web/**", "web")
|
||||
|
||||
build.classify_renpy("**.old", None)
|
||||
build.classify_renpy("**.new", None)
|
||||
build.classify_renpy("**.bak", None)
|
||||
|
||||
build.classify_renpy("**.keystore", None)
|
||||
|
||||
build.classify_renpy("**/log.txt", None)
|
||||
build.classify_renpy("**/traceback.txt", None)
|
||||
build.classify_renpy("**/errors.txt", None)
|
||||
build.classify_renpy("**/steam_appid.txt", None)
|
||||
build.classify_renpy("**/saves/", None)
|
||||
build.classify_renpy("**/tmp/", None)
|
||||
build.classify_renpy("**/.Editra", None)
|
||||
|
||||
|
||||
# main source.
|
||||
|
||||
def source_and_binary(pattern, source="source", binary="binary", py=True):
|
||||
"""
|
||||
Classifies source and binary files beginning with `pattern`.
|
||||
.pyo, .rpyc, .rpycm, and .rpyb go into binary, everything
|
||||
else but .pyi files go into source.
|
||||
"""
|
||||
|
||||
if py is True:
|
||||
py = 'pycache'
|
||||
|
||||
if py == 'pycache':
|
||||
build.classify_renpy(pattern + "/**__pycache__/", binary)
|
||||
build.classify_renpy(pattern + "/**__pycache__/*.{}.pyc".format(sys.implementation.cache_tag), binary)
|
||||
build.classify_renpy(pattern + "/**.pyc", None)
|
||||
build.classify_renpy(pattern + "/**.pyo", None)
|
||||
|
||||
elif py == 'pyc':
|
||||
build.classify_renpy(pattern + "/**__pycache__/", None)
|
||||
build.classify_renpy(pattern + "/**.pyc", binary)
|
||||
build.classify_renpy(pattern + "/**.pyo", None)
|
||||
|
||||
elif py == 'pyo':
|
||||
build.classify_renpy(pattern + "/**__pycache__/", None)
|
||||
build.classify_renpy(pattern + "/**.pyc", None)
|
||||
build.classify_renpy(pattern + "/**.pyo", binary)
|
||||
|
||||
else:
|
||||
build.classify_renpy(pattern + "/**__pycache__/", None)
|
||||
build.classify_renpy(pattern + "/**.pyc", None)
|
||||
build.classify_renpy(pattern + "/**.pyo", None)
|
||||
|
||||
build.classify_renpy(pattern + "/**.pyi", None)
|
||||
build.classify_renpy(pattern + "/**.pyx", "source_only")
|
||||
build.classify_renpy(pattern + "/**.pxd", "source_only")
|
||||
|
||||
build.classify_renpy(pattern + "/**.rpyc", binary)
|
||||
build.classify_renpy(pattern + "/**.rpymc", binary)
|
||||
|
||||
build.classify_renpy(pattern + "/**/" + renpy.script.BYTECODE_FILE, binary)
|
||||
build.classify_renpy(pattern + "/**/cache/bytecode-*.rpyb", None)
|
||||
|
||||
build.classify_renpy(pattern + "/**/cache/*", binary)
|
||||
|
||||
build.classify_renpy(pattern + "/**", source)
|
||||
|
||||
build.classify_renpy("renpy/gl/", None)
|
||||
|
||||
build.classify_renpy("renpy.py", "binary")
|
||||
source_and_binary("renpy")
|
||||
|
||||
# games.
|
||||
build.classify_renpy("launcher/game/theme/", None)
|
||||
|
||||
build.classify_renpy("gui/game/gui/", "source")
|
||||
build.classify_renpy("gui/game/gui/bubble.png", "source")
|
||||
build.classify_renpy("gui/game/gui/thoughtbubble.png", "source")
|
||||
build.classify_renpy("gui/game/gui/*", None)
|
||||
|
||||
source_and_binary("launcher", py=False)
|
||||
source_and_binary("gui", binary=None, py=False)
|
||||
|
||||
source_and_binary("the_question")
|
||||
source_and_binary("tutorial")
|
||||
|
||||
# extra fonts.
|
||||
build.classify_renpy("sdk-fonts/**", "source")
|
||||
|
||||
# docs.
|
||||
build.classify_renpy("doc/", "source")
|
||||
build.classify_renpy("doc/.doctrees/", None)
|
||||
build.classify_renpy("doc/_sources/", None)
|
||||
build.classify_renpy("doc/**", "source")
|
||||
build.classify_renpy("LICENSE.txt", "source")
|
||||
|
||||
build.classify_renpy("sphinx/", "source_only")
|
||||
build.classify_renpy("sphinx/build.sh", "source_only")
|
||||
build.classify_renpy("sphinx/checks.py", "source_only")
|
||||
build.classify_renpy("sphinx/game/**", "source_only")
|
||||
build.classify_renpy("sphinx/source/inc/", None)
|
||||
build.classify_renpy("sphinx/source/**", "source_only")
|
||||
|
||||
# Build System.
|
||||
build.classify_renpy("setup.py", "source_only")
|
||||
build.classify_renpy("src/**", "source_only")
|
||||
build.classify_renpy("scripts/", "source_only")
|
||||
build.classify_renpy("scripts/setuplib.py", "source_only")
|
||||
build.classify_renpy("scripts/generate_styles.py", "source_only")
|
||||
|
||||
# all-platforms binary.
|
||||
build.classify_renpy("lib/**/libpython{}.{}.dll".format(sys.version_info.major, sys.version_info.minor), "binary")
|
||||
build.classify_renpy("lib/**/libpython*.dll", None)
|
||||
|
||||
build.classify_renpy("lib/**/*steam_api*", "steam")
|
||||
build.classify_renpy("lib/**/*Live2D*", None)
|
||||
|
||||
build.classify_renpy("lib/py3-linux-armv7l", None)
|
||||
build.classify_renpy("lib/py3-linux-aarch64/**", "linux_arm")
|
||||
source_and_binary("lib/py3-**", "binary", "binary")
|
||||
|
||||
libpython = "lib/python{}.{}".format(sys.version_info.major, sys.version_info.minor)
|
||||
source_and_binary(libpython, "binary", "binary", py='pyc')
|
||||
|
||||
build.classify_renpy("lib/python**", None)
|
||||
build.classify_renpy("lib/py2-**", None)
|
||||
|
||||
build.classify_renpy("renpy3.sh", "binary")
|
||||
|
||||
build.classify_renpy("lib/", "binary")
|
||||
|
||||
# Packages.
|
||||
build.packages = [ ]
|
||||
|
||||
build.package("sdk", "zip tar.bz2 dmg", "source binary")
|
||||
build.package("sdkarm", "tar.bz2", "source binary linux_arm")
|
||||
build.package("source", "tar.bz2", "source source_only", update=False)
|
||||
build.package("steam", "zip", "steam", dlc=True)
|
||||
|
||||
build.package("rapt", "zip", "rapt", dlc=True)
|
||||
build.package("renios", "zip", "renios", dlc=True)
|
||||
build.package("web", "zip", "web", dlc=True)
|
||||
|
||||
# The identifier for the SDK.
|
||||
define build.mac_info_plist["CFBundleIdentifier"] = "org.renpy.sdk"
|
||||
|
||||
# Enable the special launcher translation mode.
|
||||
define config.translate_launcher = True
|
||||
|
||||
# Allow clicks that focus the window to be processed.
|
||||
define config.mouse_focus_clickthrough = True
|
||||
|
||||
# Reduce the rate of screen updates.
|
||||
default preferences.gl_powersave = True
|
||||
|
||||
# Enable rtl.
|
||||
define config.rtl = True
|
||||
|
||||
# Disable steam.
|
||||
python early:
|
||||
config.enable_steam = False
|
||||
|
||||
# Since the launcher can be run directly or can be run from the SDK directory,
|
||||
# uneliding files needs to be handled slightly differently.
|
||||
define config.alternate_unelide_path = os.path.join(config.basedir, "launcher")
|
||||
|
||||
# Disable skipping.
|
||||
define config.allow_skipping = False
|
||||
@@ -1,388 +0,0 @@
|
||||
# Copyright 2004-2025 Tom Rothamel <pytom@bishoujo.us>
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person
|
||||
# obtaining a copy of this software and associated documentation files
|
||||
# (the "Software"), to deal in the Software without restriction,
|
||||
# including without limitation the rights to use, copy, modify, merge,
|
||||
# publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
# and to permit persons to whom the Software is furnished to do so,
|
||||
# subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
init python in distribute:
|
||||
|
||||
import time
|
||||
import zipfile
|
||||
import tarfile
|
||||
import zlib
|
||||
import struct
|
||||
import stat
|
||||
import shutil
|
||||
import sys
|
||||
import threading
|
||||
|
||||
from zipfile import crc32
|
||||
|
||||
# Since the long type doesn't exist on py3, define it here
|
||||
long = int
|
||||
|
||||
zlib.Z_DEFAULT_COMPRESSION = 5
|
||||
|
||||
class ZipFile(zipfile.ZipFile):
|
||||
|
||||
def write_with_info(self, zinfo, filename):
|
||||
|
||||
if zinfo.filename.endswith("/"):
|
||||
data = b''
|
||||
else:
|
||||
with open(filename, "rb") as f:
|
||||
data = f.read()
|
||||
|
||||
self.writestr(zinfo, data)
|
||||
|
||||
|
||||
class ZipPackage(object):
|
||||
"""
|
||||
A class that creates a zip file.
|
||||
"""
|
||||
|
||||
def __init__(self, filename):
|
||||
self.zipfile = ZipFile(filename, "w", zipfile.ZIP_DEFLATED, True)
|
||||
|
||||
def get_date_time(self, path):
|
||||
"""
|
||||
Gets the datetime for a file. If the time doesn't exist or is
|
||||
weird, use the current time instead.
|
||||
"""
|
||||
|
||||
try:
|
||||
s = os.stat(path)
|
||||
rv = time.gmtime(s.st_mtime)[:6]
|
||||
|
||||
# Check that the time is sensible.
|
||||
if rv[0] < 2000:
|
||||
rv = None
|
||||
except Exception:
|
||||
rv = None
|
||||
|
||||
if rv is None:
|
||||
rv = time.gmtime()[:6]
|
||||
|
||||
return rv
|
||||
|
||||
def add_file(self, name, path, xbit):
|
||||
|
||||
if path is None:
|
||||
raise Exception("path for " + name + " must not be None.")
|
||||
|
||||
zi = zipfile.ZipInfo(name)
|
||||
zi.date_time = self.get_date_time(path)
|
||||
zi.compress_type = zipfile.ZIP_DEFLATED
|
||||
zi.create_system = 3
|
||||
|
||||
if xbit:
|
||||
zi.external_attr = long(0o100755) << 16
|
||||
else:
|
||||
zi.external_attr = long(0o100644) << 16
|
||||
|
||||
self.zipfile.write_with_info(zi, path)
|
||||
|
||||
def add_directory(self, name, path):
|
||||
if path is None:
|
||||
return
|
||||
|
||||
zi = zipfile.ZipInfo(name + "/")
|
||||
zi.date_time = self.get_date_time(path)
|
||||
zi.compress_type = zipfile.ZIP_STORED
|
||||
zi.create_system = 3
|
||||
zi.external_attr = (long(0o040755) << 16) | 0x10
|
||||
|
||||
self.zipfile.write_with_info(zi, path)
|
||||
|
||||
def close(self, progress=None):
|
||||
self.zipfile.close()
|
||||
|
||||
|
||||
class TarPackage(object):
|
||||
|
||||
def __init__(self, filename, mode, notime=False):
|
||||
"""
|
||||
notime
|
||||
If true, times will be forced to the epoch.
|
||||
"""
|
||||
|
||||
self.tarfile = tarfile.open(filename, mode, format=tarfile.PAX_FORMAT)
|
||||
self.tarfile.dereference = True
|
||||
self.notime = notime
|
||||
|
||||
def add_file(self, name, path, xbit):
|
||||
|
||||
if path is not None:
|
||||
info = self.tarfile.gettarinfo(path, name)
|
||||
else:
|
||||
info = tarfile.TarInfo(name)
|
||||
info.size = 0
|
||||
info.mtime = int(time.time())
|
||||
info.type = tarfile.DIRTYPE
|
||||
|
||||
if xbit:
|
||||
info.mode = 0o755
|
||||
else:
|
||||
info.mode = 0o644
|
||||
|
||||
info.uid = 1000
|
||||
info.gid = 1000
|
||||
info.uname = "renpy"
|
||||
info.gname = "renpy"
|
||||
|
||||
if self.notime:
|
||||
info.mtime = 0
|
||||
|
||||
if info.isreg():
|
||||
with open(path, "rb") as f:
|
||||
self.tarfile.addfile(info, f)
|
||||
else:
|
||||
self.tarfile.addfile(info)
|
||||
|
||||
def add_directory(self, name, path):
|
||||
self.add_file(name, path, True)
|
||||
|
||||
def close(self, progress=None):
|
||||
self.tarfile.close()
|
||||
|
||||
|
||||
class UpdatePackage(TarPackage):
|
||||
|
||||
def __init__(self, filename, basename, destination):
|
||||
self.path = filename
|
||||
self.basename = basename
|
||||
self.destination = destination
|
||||
|
||||
TarPackage.__init__(self, filename, "w", notime=True)
|
||||
|
||||
def close(self, progress=None):
|
||||
TarPackage.close(self)
|
||||
|
||||
cmd = [
|
||||
updater.zsync_path("zsyncmake"),
|
||||
"-z",
|
||||
# -u url to gzipped data - not a local filename!
|
||||
"-u", self.basename + ".update.gz",
|
||||
"-o", os.path.join(self.destination, self.basename + ".zsync"),
|
||||
os.path.abspath(self.path),
|
||||
]
|
||||
|
||||
subprocess.check_call([ renpy.fsencode(i) for i in cmd ])
|
||||
|
||||
# Build the sums file. This is a file with an adler32 hash of each 64k block
|
||||
# of the zsync file. It's used to help us determine how much of the file is
|
||||
# downloaded.
|
||||
with open(self.path, "rb") as src:
|
||||
with open(renpy.fsencode(os.path.join(self.destination, self.basename + ".sums")), "wb") as sums:
|
||||
while True:
|
||||
data = src.read(65536)
|
||||
|
||||
if not data:
|
||||
break
|
||||
|
||||
sums.write(struct.pack("<I", zlib.adler32(data) & 0xffffffff))
|
||||
|
||||
|
||||
class DirectoryPackage(object):
|
||||
|
||||
def mkdir(self, path):
|
||||
if not os.path.isdir(path):
|
||||
os.makedirs(path, 0o755)
|
||||
|
||||
def __init__(self, path):
|
||||
self.path = path
|
||||
|
||||
if os.path.isdir(path):
|
||||
shutil.rmtree(path)
|
||||
|
||||
self.mkdir(path)
|
||||
|
||||
def add_file(self, name, path, xbit):
|
||||
fn = os.path.join(self.path, name)
|
||||
|
||||
# If this is not a directory, ensure all parent directories
|
||||
# have been created
|
||||
self.mkdir(os.path.dirname(fn))
|
||||
shutil.copy(path, fn)
|
||||
|
||||
if xbit:
|
||||
os.chmod(fn, 0o755)
|
||||
else:
|
||||
os.chmod(fn, 0o644)
|
||||
|
||||
def add_directory(self, name, path):
|
||||
fn = os.path.join(self.path, name)
|
||||
self.mkdir(fn)
|
||||
|
||||
def close(self, progress=None):
|
||||
return
|
||||
|
||||
|
||||
class ExternalZipPackage(object):
|
||||
|
||||
def __init__(self, path):
|
||||
self.path = path
|
||||
self.directory = path + ".zd"
|
||||
self.dp = DirectoryPackage(self.directory)
|
||||
|
||||
def add_file(self, name, path, xbit):
|
||||
self.dp.add_file(name, path, xbit)
|
||||
|
||||
def add_directory(self, name, path):
|
||||
self.dp.add_directory(name, path)
|
||||
|
||||
def close(self, progress=None):
|
||||
self.dp.close()
|
||||
|
||||
if os.path.exists(self.path):
|
||||
os.unlink(self.path)
|
||||
|
||||
p = subprocess.Popen([
|
||||
"zip",
|
||||
"-9rq",
|
||||
os.path.abspath(self.path),
|
||||
] + os.listdir(self.directory),
|
||||
cwd=os.path.abspath(self.directory)
|
||||
)
|
||||
|
||||
p.wait()
|
||||
|
||||
shutil.rmtree(self.directory)
|
||||
|
||||
|
||||
class DMGPackage(DirectoryPackage):
|
||||
def __init__(self, path, make_dmg):
|
||||
self.make_dmg = make_dmg
|
||||
DirectoryPackage.__init__(self, path)
|
||||
|
||||
def close(self, progress=None):
|
||||
DirectoryPackage.close(self)
|
||||
self.make_dmg()
|
||||
|
||||
|
||||
class RPUPackage(object):
|
||||
|
||||
generator = None
|
||||
|
||||
def __init__(self, directory, variant):
|
||||
import renpy.update.common
|
||||
|
||||
self.directory = directory + "/rpu"
|
||||
self.variant = variant
|
||||
self.file_list = renpy.update.common.FileList()
|
||||
|
||||
if not os.path.exists(self.directory):
|
||||
os.mkdir(self.directory)
|
||||
|
||||
def add_file(self, name, path, xbit):
|
||||
self.file_list.add_file(name, path, xbit)
|
||||
|
||||
def add_directory(self, name, _path):
|
||||
self.file_list.add_directory(name)
|
||||
|
||||
def close(self, progress=None):
|
||||
import renpy.update.generate
|
||||
|
||||
if RPUPackage.generator is None:
|
||||
RPUPackage.generator = renpy.update.generate.BlockGenerator(self.directory)
|
||||
|
||||
RPUPackage.generator.generate(self.variant, self.file_list, progress)
|
||||
|
||||
@staticmethod
|
||||
def reset():
|
||||
RPUPackage.generator = None
|
||||
|
||||
|
||||
class NullPackage(object):
|
||||
"""
|
||||
A package format that doesn't create an output file,
|
||||
only updates.
|
||||
"""
|
||||
|
||||
def add_file(self, name, path, xbit):
|
||||
return
|
||||
|
||||
def add_directory(self, name, _path):
|
||||
return
|
||||
|
||||
def close(self, progress=None):
|
||||
return
|
||||
|
||||
|
||||
parallel_threads = [ ]
|
||||
|
||||
class ParallelPackage(object):
|
||||
|
||||
def __init__(self, package, done, what):
|
||||
self.package = package
|
||||
self.done = done
|
||||
self.what = what
|
||||
|
||||
self.worklist = [ ]
|
||||
|
||||
def add_file(self, name, path, xbit):
|
||||
self.worklist.append((False, name, path, xbit))
|
||||
|
||||
def add_directory(self, name, path):
|
||||
self.worklist.append((True, name, path, True))
|
||||
|
||||
def close(self, progress=None):
|
||||
t = threading.Thread(target=self.run)
|
||||
t.start()
|
||||
|
||||
self.thread = t
|
||||
parallel_threads.append(self)
|
||||
|
||||
def run(self):
|
||||
|
||||
for i in self.worklist:
|
||||
directory, name, path, xbit = i
|
||||
if directory:
|
||||
self.package.add_directory(name, path)
|
||||
else:
|
||||
self.package.add_file(name, path, xbit)
|
||||
|
||||
self.package.close()
|
||||
|
||||
|
||||
def wait_parallel_threads():
|
||||
|
||||
t = 0
|
||||
|
||||
while parallel_threads:
|
||||
|
||||
alive = [ ]
|
||||
|
||||
for i in parallel_threads:
|
||||
i.thread.join(0)
|
||||
if i.thread.is_alive():
|
||||
alive.append(i)
|
||||
|
||||
if not alive:
|
||||
break
|
||||
|
||||
t += 1
|
||||
print("\r " + str(t) + " " + " ".join(sorted([ i.what for i in alive ])))
|
||||
|
||||
time.sleep(1)
|
||||
|
||||
for i in parallel_threads:
|
||||
if i.done:
|
||||
i.done()
|
||||
|
||||
parallel_threads.clear()
|
||||
@@ -1,473 +0,0 @@
|
||||
# Copyright 2004-2025 Tom Rothamel <pytom@bishoujo.us>
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person
|
||||
# obtaining a copy of this software and associated documentation files
|
||||
# (the "Software"), to deal in the Software without restriction,
|
||||
# including without limitation the rights to use, copy, modify, merge,
|
||||
# publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
# and to permit persons to whom the Software is furnished to do so,
|
||||
# subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
default persistent.show_edit_funcs = True
|
||||
default persistent.windows_console = False
|
||||
default persistent.lint_options = set()
|
||||
default persistent.use_web_doc = False
|
||||
|
||||
init python:
|
||||
from math import ceil
|
||||
|
||||
def scan_translations(piglatin=True):
|
||||
|
||||
languages = renpy.known_languages()
|
||||
|
||||
if not languages:
|
||||
return None
|
||||
|
||||
languages.remove("piglatin")
|
||||
|
||||
rv = [(i, renpy.translate_string("{#language name and font}", i)) for i in languages ]
|
||||
rv.sort(key=lambda a : renpy.filter_text_tags(a[1], allow=[]).lower())
|
||||
|
||||
rv.insert(0, (None, "English"))
|
||||
|
||||
if piglatin:
|
||||
rv.append(("piglatin", "Igpay Atinlay"))
|
||||
|
||||
bound = ceil(len(rv)/3.)
|
||||
return (rv[:bound], rv[bound:2*bound], rv[2*bound:])
|
||||
|
||||
class RestartAtPreferences(Action):
|
||||
def __call__(self):
|
||||
renpy.session["launcher_start_label"] = "preferences"
|
||||
renpy.utter_restart()
|
||||
|
||||
class EnsureProjectsTxt(Action):
|
||||
"""
|
||||
Ensures the projects.txt file exists before it's opened.
|
||||
"""
|
||||
|
||||
def __call__(self):
|
||||
fn = os.path.join(project.manager.projects_directory, "projects.txt")
|
||||
|
||||
if os.path.exists(fn):
|
||||
return
|
||||
|
||||
with open(fn, "w") as f:
|
||||
f.write("""\
|
||||
# This file can be used to add projects not in the projects directory
|
||||
# by listing the full path to each project, one per line.
|
||||
|
||||
""")
|
||||
|
||||
|
||||
default persistent.force_new_tutorial = False
|
||||
default persistent.sponsor_message = True
|
||||
default persistent.daily_update_check = True
|
||||
default persistent.daily_update_check_once = False
|
||||
|
||||
# Keep the default update check from triggering until tomorrow.
|
||||
default persistent.last_update_check = datetime.date.today()
|
||||
|
||||
# Should we try to skip the splashscreen?
|
||||
default persistent.skip_splashscreen = False
|
||||
|
||||
# Should we prefer rpu updates?
|
||||
default persistent.prefer_rpu = True
|
||||
|
||||
init python:
|
||||
if not persistent.daily_update_check_once:
|
||||
persistent.daily_update_check_once = True
|
||||
persistent.daily_update_check = True
|
||||
|
||||
|
||||
default preference_tab = "general"
|
||||
define preference_tabs = (
|
||||
("general", _("General")),
|
||||
("options", _("Options")),
|
||||
("theme", _("Theme")),
|
||||
("install", _("Install Libraries")),
|
||||
("actions", _("Actions")),
|
||||
("lint", _("Lint")),
|
||||
)
|
||||
|
||||
screen preferences():
|
||||
|
||||
default translations = scan_translations()
|
||||
|
||||
frame:
|
||||
style_group "l"
|
||||
style "l_root"
|
||||
alt "Preferences"
|
||||
|
||||
window:
|
||||
|
||||
has vbox
|
||||
|
||||
label _("Launcher Preferences")
|
||||
|
||||
add HALF_SPACER
|
||||
|
||||
hbox:
|
||||
|
||||
frame:
|
||||
style "l_indent"
|
||||
xmaximum ONETHIRD
|
||||
xfill True
|
||||
|
||||
has vbox
|
||||
|
||||
add SEPARATOR2
|
||||
|
||||
add HALF_SPACER
|
||||
|
||||
for i, l in preference_tabs:
|
||||
textbutton l action SetVariable("preference_tab", i) style "l_list"
|
||||
|
||||
if preference_tab == "general":
|
||||
|
||||
frame:
|
||||
style "l_indent"
|
||||
xmaximum TWOTHIRDS
|
||||
xfill True
|
||||
|
||||
has vbox
|
||||
|
||||
add SEPARATOR2
|
||||
|
||||
|
||||
frame:
|
||||
style "l_indent"
|
||||
|
||||
has vbox
|
||||
|
||||
text _("Projects Directory:")
|
||||
|
||||
add HALF_SPACER
|
||||
|
||||
|
||||
frame style "l_indent":
|
||||
if persistent.projects_directory:
|
||||
textbutton _("[persistent.projects_directory!q]"):
|
||||
action Jump("projects_directory_preference")
|
||||
alt _("Projects directory: [text]")
|
||||
else:
|
||||
textbutton _("Not Set"):
|
||||
action Jump("projects_directory_preference")
|
||||
alt _("Projects directory: [text]")
|
||||
|
||||
add SPACER
|
||||
|
||||
# Text editor selection.
|
||||
add SEPARATOR2
|
||||
|
||||
frame:
|
||||
style "l_indent"
|
||||
has vbox
|
||||
|
||||
text _("Text Editor:")
|
||||
|
||||
add HALF_SPACER
|
||||
|
||||
frame style "l_indent":
|
||||
textbutton (persistent.editor or _("Not Set")) action Jump("editor_preference") alt _("Text editor: [text]")
|
||||
|
||||
add SPACER
|
||||
|
||||
add SEPARATOR2
|
||||
|
||||
frame:
|
||||
style "l_indent"
|
||||
has vbox
|
||||
|
||||
text _("Language:")
|
||||
|
||||
add HALF_SPACER
|
||||
|
||||
hbox:
|
||||
for tran in translations:
|
||||
vbox:
|
||||
for tlid, tlname in tran:
|
||||
textbutton tlname:
|
||||
xmaximum (TWOTHIRDS//3)
|
||||
action [Language(tlid), project.SelectTutorial(True)]
|
||||
style "l_list"
|
||||
|
||||
elif preference_tab == "options":
|
||||
|
||||
frame:
|
||||
style "l_indent"
|
||||
xmaximum TWOTHIRDS
|
||||
xfill True
|
||||
|
||||
has vbox
|
||||
add SEPARATOR2
|
||||
|
||||
frame:
|
||||
style "l_indent"
|
||||
has vbox
|
||||
|
||||
text _("Navigation Options:")
|
||||
|
||||
add HALF_SPACER
|
||||
|
||||
textbutton _("Include private names") style "l_checkbox" action ToggleField(persistent, "navigate_private")
|
||||
textbutton _("Include library names") style "l_checkbox" action ToggleField(persistent, "navigate_library")
|
||||
|
||||
add SPACER
|
||||
add SEPARATOR2
|
||||
|
||||
frame:
|
||||
style "l_indent"
|
||||
has vbox
|
||||
|
||||
text _("Game Options:")
|
||||
|
||||
add HALF_SPACER
|
||||
|
||||
if renpy.windows:
|
||||
textbutton _("Console output") style "l_checkbox" action ToggleField(persistent, "windows_console")
|
||||
|
||||
textbutton _("Skip splashscreen") style "l_checkbox" action ToggleField(persistent, "skip_splashscreen")
|
||||
|
||||
add SPACER
|
||||
add SEPARATOR2
|
||||
|
||||
frame:
|
||||
style "l_indent"
|
||||
has vbox
|
||||
|
||||
text _("Launcher Options:")
|
||||
|
||||
add HALF_SPACER
|
||||
|
||||
textbutton _("Show edit file section") style "l_checkbox" action ToggleField(persistent, "show_edit_funcs")
|
||||
textbutton _("Large fonts") style "l_checkbox" action [ ToggleField(persistent, "large_print"), renpy.utter_restart ]
|
||||
|
||||
if interface.local_doc_exists:
|
||||
textbutton _("Prefer the web documentation") style "l_checkbox" action ToggleField(persistent, "use_web_doc")
|
||||
|
||||
textbutton _("Sponsor message") style "l_checkbox" action ToggleField(persistent, "sponsor_message")
|
||||
|
||||
textbutton _("Restore window position") style "l_checkbox" action Preference("restore window position", "toggle")
|
||||
|
||||
if ability.can_update:
|
||||
textbutton _("Daily check for update") style "l_checkbox" action [ToggleField(persistent, "daily_update_check"), SetField(persistent, "last_update_check", None)] selected persistent.daily_update_check
|
||||
textbutton _("Prefer RPU updates") style "l_checkbox" action ToggleField(persistent, "prefer_rpu")
|
||||
|
||||
elif preference_tab == "theme":
|
||||
|
||||
frame:
|
||||
style "l_indent"
|
||||
xmaximum TWOTHIRDS
|
||||
xfill True
|
||||
|
||||
has vbox
|
||||
|
||||
add SEPARATOR2
|
||||
|
||||
frame:
|
||||
style "l_indent"
|
||||
has vbox
|
||||
|
||||
text _("Launcher Theme:")
|
||||
|
||||
add HALF_SPACER
|
||||
|
||||
textbutton _("Default theme") style "l_checkbox" action [SetField(persistent, "theme", None), RestartAtPreferences() ]
|
||||
textbutton _("Dark theme") style "l_checkbox" action [SetField(persistent, "theme", "dark"), RestartAtPreferences()]
|
||||
textbutton _("Custom theme") style "l_checkbox" action [SetField(persistent, "theme", "custom"), RestartAtPreferences()]
|
||||
|
||||
add SPACER
|
||||
|
||||
$ skins_url = interface.get_doc_url("skins.html")
|
||||
|
||||
text _("Information about creating a custom theme can be found {a=[skins_url]}in the Ren'Py Documentation{/a}.")
|
||||
|
||||
elif preference_tab == "install":
|
||||
|
||||
frame:
|
||||
style "l_indent"
|
||||
xmaximum TWOTHIRDS
|
||||
xfill True
|
||||
|
||||
has vbox
|
||||
|
||||
add SEPARATOR2
|
||||
|
||||
frame:
|
||||
style "l_indent"
|
||||
has vbox
|
||||
|
||||
text _("Install Libraries:")
|
||||
|
||||
add HALF_SPACER
|
||||
|
||||
use install_preferences
|
||||
|
||||
elif preference_tab == "actions":
|
||||
|
||||
frame:
|
||||
style "l_indent"
|
||||
xmaximum TWOTHIRDS
|
||||
xfill True
|
||||
|
||||
has vbox
|
||||
|
||||
add SEPARATOR2
|
||||
|
||||
frame:
|
||||
style "l_indent"
|
||||
has vbox
|
||||
|
||||
text _("Actions:")
|
||||
|
||||
add HALF_SPACER
|
||||
|
||||
textbutton _("Open launcher project") style "l_nonbox" action [ project.Select("launcher"), Jump("front_page") ]
|
||||
textbutton _("Open projects.txt"):
|
||||
style "l_nonbox"
|
||||
if project.manager.projects_directory:
|
||||
action [
|
||||
EnsureProjectsTxt(),
|
||||
editor.EditAbsolute(os.path.join(project.manager.projects_directory, "projects.txt"))
|
||||
]
|
||||
|
||||
textbutton _("Reset window size") style "l_nonbox" action Preference("display", 1.0)
|
||||
textbutton _("Clean temporary files") style "l_nonbox" action Jump("clean_tmp")
|
||||
|
||||
elif preference_tab == "lint":
|
||||
|
||||
frame:
|
||||
style "l_indent"
|
||||
xmaximum TWOTHIRDS
|
||||
xfill True
|
||||
|
||||
has vbox
|
||||
|
||||
add SEPARATOR2
|
||||
|
||||
frame:
|
||||
style "l_indent"
|
||||
has vbox
|
||||
|
||||
text _("Lint toggles:")
|
||||
|
||||
add HALF_SPACER
|
||||
|
||||
textbutton _("Check for orphan/obsolete translations"):
|
||||
style "l_checkbox"
|
||||
action InvertSelected(ToggleSetMembership(persistent.lint_options, "--no-orphan-tl"))
|
||||
textbutton _("Check parameters shadowing reserved names"):
|
||||
style "l_checkbox"
|
||||
action ToggleSetMembership(persistent.lint_options, "--reserved-parameters")
|
||||
textbutton _("Print block, word, and character counts by speaking character."):
|
||||
style "l_checkbox"
|
||||
action ToggleSetMembership(persistent.lint_options, "--by-character")
|
||||
textbutton _("Unclosed text tags"):
|
||||
style "l_checkbox"
|
||||
action ToggleSetMembership(persistent.lint_options, "--check-unclosed-tags")
|
||||
textbutton _("Show all unreachable blocks and orphaned translations."):
|
||||
style "l_checkbox"
|
||||
action ToggleSetMembership(persistent.lint_options, "--all-problems")
|
||||
|
||||
|
||||
textbutton _("Return") action Jump("front_page") style "l_left_button"
|
||||
|
||||
label clean_tmp:
|
||||
python hide:
|
||||
installer.processing(_("Cleaning temporary files..."))
|
||||
installer._clean("renpy:tmp", 0)
|
||||
time.sleep(0.5)
|
||||
|
||||
jump preferences
|
||||
|
||||
label projects_directory_preference:
|
||||
call choose_projects_directory
|
||||
jump preferences
|
||||
|
||||
|
||||
label preferences:
|
||||
call screen preferences
|
||||
jump preferences
|
||||
|
||||
|
||||
screen choose_language():
|
||||
default local_lang = _preferences.language
|
||||
default chosen_lang = _preferences.language
|
||||
default translations = scan_translations(piglatin=False)
|
||||
|
||||
add BACKGROUND
|
||||
|
||||
vbox:
|
||||
xalign .5
|
||||
yalign .5
|
||||
|
||||
fixed:
|
||||
ysize 0
|
||||
|
||||
text renpy.translate_string(_("{#in language font}Welcome! Please choose a language"), local_lang):
|
||||
xalign .5
|
||||
yanchor 1.0
|
||||
ypos 1.0
|
||||
|
||||
style "l_label_text"
|
||||
|
||||
size 36
|
||||
textalign .5
|
||||
layout "subtitle"
|
||||
|
||||
add SPACER
|
||||
add SPACER
|
||||
|
||||
hbox:
|
||||
xalign .5
|
||||
for tran in translations:
|
||||
vbox:
|
||||
for tlid, tlname in tran:
|
||||
textbutton tlname:
|
||||
xmaximum (TWOTHIRDS//3)
|
||||
action [ SetScreenVariable("chosen_lang", tlid), Language(tlid), project.SelectTutorial(True), Return() ]
|
||||
hovered SetScreenVariable("local_lang", tlid)
|
||||
unhovered SetScreenVariable("local_lang", chosen_lang)
|
||||
style "l_list"
|
||||
text_xalign .5
|
||||
|
||||
add SPACER
|
||||
add SPACER
|
||||
|
||||
$ lang_name = renpy.translate_string("{#language name and font}", local_lang)
|
||||
|
||||
fixed:
|
||||
ysize 0
|
||||
|
||||
textbutton renpy.translate_string(_("{#in language font}Start using Ren'Py in [lang_name]"), local_lang):
|
||||
xalign .5
|
||||
action [Language(chosen_lang), project.SelectTutorial(True), Return()]
|
||||
style "l_default"
|
||||
text_style "l_default"
|
||||
|
||||
text_size 30
|
||||
text_textalign .5
|
||||
text_layout "subtitle"
|
||||
|
||||
|
||||
label choose_language:
|
||||
call screen choose_language
|
||||
return
|
||||
|
||||
|
||||
translate None strings:
|
||||
# game/new_project.rpy:77
|
||||
old "{#language name and font}"
|
||||
new "English"
|
||||
@@ -1,919 +0,0 @@
|
||||
# Copyright 2004-2025 Tom Rothamel <pytom@bishoujo.us>
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person
|
||||
# obtaining a copy of this software and associated documentation files
|
||||
# (the "Software"), to deal in the Software without restriction,
|
||||
# including without limitation the rights to use, copy, modify, merge,
|
||||
# publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
# and to permit persons to whom the Software is furnished to do so,
|
||||
# subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
# Code that manages projects.
|
||||
|
||||
init python:
|
||||
import os
|
||||
|
||||
init python in project:
|
||||
from store import persistent, config, Action, renpy, _preferences, MultiPersistent
|
||||
import store.util as util
|
||||
import store.interface as interface
|
||||
|
||||
import sys
|
||||
import os.path
|
||||
import json
|
||||
import subprocess
|
||||
import re
|
||||
import tempfile
|
||||
|
||||
multipersistent = MultiPersistent("launcher.renpy.org")
|
||||
|
||||
if persistent.blurb is None:
|
||||
persistent.blurb = 0
|
||||
|
||||
project_filter = [ i.strip() for i in os.environ.get("RENPY_PROJECT_FILTER", "").split(":") if i.strip() ]
|
||||
|
||||
LAUNCH_BLURBS = [
|
||||
_("After making changes to the script, press shift+R to reload your game."),
|
||||
_("Press shift+O (the letter) to access the console."),
|
||||
_("Press shift+D to access the developer menu."),
|
||||
_("Have you backed up your projects recently?"),
|
||||
_("Lint checks your game for potential mistakes, and gives you statistics."),
|
||||
]
|
||||
|
||||
class Project(object):
|
||||
|
||||
def __init__(self, path, name=None):
|
||||
|
||||
while path.endswith("/"):
|
||||
path = path[:-1]
|
||||
|
||||
if name is None:
|
||||
name = os.path.basename(path)
|
||||
|
||||
if not os.path.exists(path):
|
||||
raise Exception("{} does not exist.".format(path))
|
||||
|
||||
self.name = name
|
||||
|
||||
# The path to the project.
|
||||
self.path = path
|
||||
|
||||
# The path to the game directory.
|
||||
gamedir = os.path.join(path, "game")
|
||||
if os.path.isdir(gamedir):
|
||||
self.gamedir = gamedir
|
||||
else:
|
||||
self.gamedir = path
|
||||
|
||||
# Load the data.
|
||||
self.load_data()
|
||||
|
||||
# A name to display the project.
|
||||
self.display_name = self.data.get("display_name", self.name)
|
||||
|
||||
# The project's temporary directory.
|
||||
self.tmp = None
|
||||
|
||||
# This contains the result of dumping information about the game
|
||||
# to disk.
|
||||
self.dump = { }
|
||||
|
||||
# The mtime of the last dump file loaded.
|
||||
self.dump_mtime = 0
|
||||
|
||||
# A processed version of data['renpy_launcher'] with missing files
|
||||
# and directories removed.
|
||||
self.renpy_launcher = None
|
||||
|
||||
def get_dump_filename(self):
|
||||
|
||||
if os.path.exists(os.path.join(self.gamedir, "saves")):
|
||||
return os.path.join(self.gamedir, "saves", "navigation.json")
|
||||
|
||||
self.make_tmp()
|
||||
return os.path.join(self.tmp, "navigation.json")
|
||||
|
||||
def load_data(self):
|
||||
try:
|
||||
with open(os.path.join(self.path, "project.json"), "r") as f:
|
||||
self.data = json.load(f)
|
||||
except Exception:
|
||||
self.data = { }
|
||||
|
||||
self.update_data()
|
||||
|
||||
def save_data(self):
|
||||
"""
|
||||
Saves the project data.
|
||||
"""
|
||||
|
||||
try:
|
||||
with open(os.path.join(self.path, "project.json"), "w") as f:
|
||||
json.dump(self.data, f, indent=2)
|
||||
except Exception:
|
||||
self.load_data()
|
||||
|
||||
def update_data(self):
|
||||
data = self.data
|
||||
|
||||
data.setdefault("build_update", False)
|
||||
data.setdefault("packages", [ "pc", "mac" ])
|
||||
data.setdefault("add_from", True)
|
||||
data.setdefault("force_recompile", True)
|
||||
data.setdefault("android_build", "Release")
|
||||
data.setdefault("tutorial", False)
|
||||
|
||||
if "renamed_all" not in data:
|
||||
dp = data["packages"]
|
||||
|
||||
if "all" in dp:
|
||||
dp.remove("all")
|
||||
|
||||
if "pc" not in dp:
|
||||
dp.append("pc")
|
||||
|
||||
if "mac" not in dp:
|
||||
dp.append("mac")
|
||||
|
||||
data["renamed_all"] = True
|
||||
|
||||
if "renamed_steam" not in data:
|
||||
dp = data["packages"]
|
||||
|
||||
if "steam" in dp:
|
||||
dp.remove("steam")
|
||||
|
||||
if "market" not in dp:
|
||||
dp.append("market")
|
||||
|
||||
data["renamed_steam"] = True
|
||||
|
||||
|
||||
def get_renpy_launcher(self):
|
||||
|
||||
if self.renpy_launcher is not None:
|
||||
return self.renpy_launcher
|
||||
|
||||
rv = { }
|
||||
|
||||
default_values = {
|
||||
"open_directory":
|
||||
{
|
||||
"game": "game",
|
||||
"base": ".",
|
||||
"images": "game/images",
|
||||
"audio": "game/audio",
|
||||
"gui": "game/gui",
|
||||
"libs": "game/libs",
|
||||
"mods": "game/mods",
|
||||
},
|
||||
"edit_file":
|
||||
{
|
||||
"script.rpy": "game/script.rpy",
|
||||
"options.rpy": "game/options.rpy",
|
||||
"gui.rpy": "game/gui.rpy",
|
||||
"screens.rpy": "game/screens.rpy"
|
||||
}
|
||||
}
|
||||
|
||||
for k, default_d in default_values.items():
|
||||
d = self.data.get("renpy_launcher", {}).get(k, default_d)
|
||||
rv[k] = { name : path for name, path in d.items() if os.path.exists(os.path.join(self.path, path)) }
|
||||
|
||||
self.renpy_launcher = rv
|
||||
|
||||
return rv
|
||||
|
||||
|
||||
|
||||
def make_tmp(self):
|
||||
"""
|
||||
Makes the project's temporary directory, if it doesn't exist
|
||||
yet.
|
||||
"""
|
||||
|
||||
if self.tmp and os.path.isdir(self.tmp):
|
||||
return
|
||||
|
||||
tmp = os.path.join(config.renpy_base, "tmp", self.name)
|
||||
|
||||
try:
|
||||
os.makedirs(tmp)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if os.path.isdir(tmp):
|
||||
try:
|
||||
|
||||
fn = os.path.join(tmp, "write_test.txt")
|
||||
|
||||
if os.path.exists(fn):
|
||||
os.unlink(fn)
|
||||
|
||||
with open(fn, "w") as f:
|
||||
f.write("Test")
|
||||
|
||||
os.unlink(fn)
|
||||
|
||||
self.tmp = tmp
|
||||
return
|
||||
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
self.tmp = tempfile.mkdtemp()
|
||||
|
||||
def temp_filename(self, filename):
|
||||
"""
|
||||
Returns a filename in the temporary directory.
|
||||
"""
|
||||
|
||||
self.make_tmp()
|
||||
return os.path.join(self.tmp, filename)
|
||||
|
||||
def launch(self, args=[], wait=False, env={}):
|
||||
"""
|
||||
Launches the project.
|
||||
|
||||
`args`
|
||||
Additional arguments to give to the project.
|
||||
|
||||
`wait`
|
||||
If true, waits for the launched project to terminate before
|
||||
continuing.
|
||||
|
||||
`env`
|
||||
Additional variables to include in the environment.
|
||||
"""
|
||||
|
||||
self.make_tmp()
|
||||
|
||||
# Find the python executable to run.
|
||||
executable_path = os.path.dirname(renpy.fsdecode(sys.executable))
|
||||
|
||||
if renpy.renpy.windows:
|
||||
extension = ".exe"
|
||||
else:
|
||||
extension = ""
|
||||
|
||||
if persistent.windows_console:
|
||||
executables = [ "python" + extension ]
|
||||
else:
|
||||
executables = [ "pythonw" + extension ]
|
||||
|
||||
executables.append(sys.executable)
|
||||
|
||||
for i in executables:
|
||||
executable = os.path.join(executable_path, i)
|
||||
if os.path.exists(executable):
|
||||
break
|
||||
else:
|
||||
raise Exception("Python interpreter not found: %r", executables)
|
||||
|
||||
# Put together the basic command line.
|
||||
cmd = [ executable, sys.argv[0] ]
|
||||
|
||||
cmd.append(self.path)
|
||||
cmd.extend(args)
|
||||
|
||||
# Add flags to dump game info.
|
||||
cmd.append("--json-dump")
|
||||
cmd.append(self.get_dump_filename())
|
||||
|
||||
if persistent.navigate_private:
|
||||
cmd.append("--json-dump-private")
|
||||
|
||||
if persistent.navigate_library:
|
||||
cmd.append("--json-dump-common")
|
||||
|
||||
cmd.append("--errors-in-editor")
|
||||
|
||||
environ = dict(os.environ)
|
||||
environ["RENPY_LAUNCHER_LANGUAGE"] = _preferences.language or "english"
|
||||
|
||||
if persistent.skip_splashscreen:
|
||||
environ["RENPY_SKIP_SPLASHSCREEN"] = "1"
|
||||
|
||||
environ.update(env)
|
||||
|
||||
# Filter out system PYTHON* environment variables.
|
||||
if hasattr(sys, "renpy_executable"):
|
||||
environ = { k : v for k, v in environ.items() if not k.startswith("PYTHON") }
|
||||
|
||||
encoded_environ = { }
|
||||
|
||||
for k, v in environ.items():
|
||||
if v is None:
|
||||
continue
|
||||
|
||||
encoded_environ[renpy.fsencode(k)] = renpy.fsencode(v)
|
||||
|
||||
# Launch the project.
|
||||
cmd = [ renpy.fsencode(i) for i in cmd ]
|
||||
|
||||
p = subprocess.Popen(cmd, env=encoded_environ)
|
||||
|
||||
if wait:
|
||||
if p.wait():
|
||||
|
||||
print("Launch failed. command={!r}, returncode={!r}".format(cmd, p.returncode))
|
||||
|
||||
if args and not self.is_writeable():
|
||||
interface.error(_("Launching the project failed."), _("This may be because the project is not writeable."))
|
||||
else:
|
||||
interface.error(_("Launching the project failed."), _("Please ensure that your project launches normally before running this command."))
|
||||
|
||||
renpy.not_infinite_loop(30)
|
||||
|
||||
|
||||
def update_dump(self, force=False, gui=True, compile=False):
|
||||
"""
|
||||
If the dumpfile does not exist, runs Ren'Py to create it. Otherwise,
|
||||
loads it in iff it's newer than the one that's already loaded.
|
||||
"""
|
||||
|
||||
dump_filename = self.get_dump_filename()
|
||||
|
||||
if force or not os.path.exists(dump_filename):
|
||||
|
||||
if gui:
|
||||
interface.processing(_("Ren'Py is scanning the project..."))
|
||||
|
||||
if compile:
|
||||
self.launch(["compile", "--keep-orphan-rpyc" ], wait=True)
|
||||
else:
|
||||
self.launch(["quit"], wait=True)
|
||||
|
||||
if not os.path.exists(dump_filename):
|
||||
self.dump["error"] = True
|
||||
return
|
||||
|
||||
file_mtime = os.path.getmtime(dump_filename)
|
||||
if file_mtime == self.dump_mtime:
|
||||
return
|
||||
|
||||
self.dump_mtime = file_mtime
|
||||
|
||||
try:
|
||||
with open(dump_filename, "r") as f:
|
||||
self.dump = json.load(f)
|
||||
# add todo list to dump data
|
||||
self.update_todos()
|
||||
|
||||
except Exception:
|
||||
self.dump["error"] = True
|
||||
|
||||
def update_todos(self):
|
||||
"""
|
||||
Scans the scriptfiles for lines TODO comments and add them to
|
||||
the dump data.
|
||||
"""
|
||||
|
||||
todos = self.dump.setdefault("location", {})["todo"] = {}
|
||||
|
||||
files = self.script_files()
|
||||
|
||||
for f in files:
|
||||
|
||||
data = open(self.unelide_filename(f), encoding="utf-8")
|
||||
|
||||
for l, line in enumerate(data):
|
||||
l += 1
|
||||
|
||||
line = line[:1024]
|
||||
|
||||
m = re.search(r"#\s*TODO(\s*:\s*|\s+)(.*)", line, re.I)
|
||||
|
||||
if m is None:
|
||||
continue
|
||||
|
||||
raw_todo_text = m.group(2).strip()
|
||||
todo_text = raw_todo_text
|
||||
|
||||
index = 0
|
||||
|
||||
while not todo_text or todo_text in todos:
|
||||
index += 1
|
||||
todo_text = u"{0} ({1})".format(raw_todo_text, index)
|
||||
|
||||
todos[todo_text] = [f, l]
|
||||
|
||||
|
||||
def unelide_filename(self, fn):
|
||||
"""
|
||||
Unelides the filename relative to the project base.
|
||||
"""
|
||||
|
||||
fn = os.path.normpath(fn)
|
||||
|
||||
fn1 = os.path.join(self.path, fn)
|
||||
if os.path.exists(fn1):
|
||||
return fn1
|
||||
|
||||
fn2 = os.path.join(config.renpy_base, fn)
|
||||
if os.path.exists(fn2):
|
||||
return fn2
|
||||
|
||||
return fn
|
||||
|
||||
def script_files(self):
|
||||
"""
|
||||
Return a list of the script files that make up the project. These
|
||||
are elided, and so need to be passed to unelide_filename before they
|
||||
can be included in the project.
|
||||
"""
|
||||
|
||||
def is_script(fn):
|
||||
fn = fn.lower()
|
||||
|
||||
for i in [ ".rpy", ".rpym", "_ren.py" ]:
|
||||
if fn.endswith(i):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
rv = [ ]
|
||||
rv.extend(i for i, isdir in util.walk(self.path)
|
||||
if (not isdir) and is_script(i) and (not i.startswith("tmp/")) )
|
||||
|
||||
return rv
|
||||
|
||||
def exists(self, fn):
|
||||
"""
|
||||
Returns true if the file exists in the game.
|
||||
"""
|
||||
|
||||
return os.path.exists(os.path.join(self.path, fn))
|
||||
|
||||
def is_writeable(self):
|
||||
"""
|
||||
Returns true if it's possible to write a file in the projects
|
||||
directory.
|
||||
"""
|
||||
|
||||
return os.access(self.path, os.W_OK)
|
||||
|
||||
|
||||
class ProjectManager(object):
|
||||
"""
|
||||
This maintains a list of the various types of projects that
|
||||
we know about.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
|
||||
# The projects directory.
|
||||
self.projects_directory = ""
|
||||
|
||||
# Normal projects, in alphabetical order by lowercase name.
|
||||
self.projects = [ ]
|
||||
|
||||
# Template projects.
|
||||
self.templates = [ ]
|
||||
|
||||
# All projects - normal, template, and hidden.
|
||||
self.all_projects = [ ]
|
||||
|
||||
# Directories that have been scanned.
|
||||
self.scanned = set()
|
||||
|
||||
# The tutorial game, and the language it's for.
|
||||
self.tutoral = None
|
||||
self.tutorial_language = "the meowing of a cat"
|
||||
|
||||
self.scan()
|
||||
|
||||
def scan(self):
|
||||
"""
|
||||
Scans for projects.
|
||||
"""
|
||||
|
||||
global current
|
||||
|
||||
if persistent.projects_directory is None:
|
||||
if multipersistent.projects_directory is not None:
|
||||
persistent.projects_directory = multipersistent.projects_directory
|
||||
|
||||
if (persistent.projects_directory is not None) and not os.path.isdir(persistent.projects_directory):
|
||||
persistent.projects_directory = None
|
||||
|
||||
if persistent.projects_directory is not None:
|
||||
if multipersistent.projects_directory is None:
|
||||
multipersistent.projects_directory = persistent.projects_directory
|
||||
multipersistent.save()
|
||||
|
||||
self.projects_directory = persistent.projects_directory
|
||||
|
||||
self.projects = [ ]
|
||||
self.templates = [ ]
|
||||
self.all_projects = [ ]
|
||||
self.scanned = set()
|
||||
|
||||
if self.projects_directory is not None:
|
||||
self.scan_directory(self.projects_directory)
|
||||
|
||||
self.scan_directory(config.renpy_base)
|
||||
|
||||
self.projects.sort(key=lambda p : p.name.lower())
|
||||
self.templates.sort(key=lambda p : p.name.lower())
|
||||
|
||||
# Select the default project.
|
||||
if persistent.active_project is not None:
|
||||
p = self.get(persistent.active_project)
|
||||
|
||||
if (p is not None) and (p.name not in [ "tutorial", "tutorial_7" ]):
|
||||
current = p
|
||||
return
|
||||
|
||||
current = self.get_tutorial()
|
||||
|
||||
|
||||
def find_basedir(self, d):
|
||||
"""
|
||||
Try to find a project basedir in d.
|
||||
"""
|
||||
|
||||
def has_game(dn):
|
||||
return os.path.isdir(os.path.join(dn, "game"))
|
||||
|
||||
if has_game(d):
|
||||
return d
|
||||
|
||||
dn = os.path.join(d, "Contents", "Resources", "autorun")
|
||||
if has_game(dn):
|
||||
return dn
|
||||
|
||||
for dn in os.listdir(d):
|
||||
if not dn.endswith(".app"):
|
||||
continue
|
||||
|
||||
dn = os.path.join(d, dn, "Contents", "Resources", "autorun")
|
||||
|
||||
if has_game(dn):
|
||||
return dn
|
||||
|
||||
return None
|
||||
|
||||
def scan_directory(self, d):
|
||||
"""
|
||||
Scans for projects in directories directly underneath `d`.
|
||||
"""
|
||||
|
||||
global current
|
||||
|
||||
d = os.path.abspath(d)
|
||||
|
||||
if not os.path.isdir(d):
|
||||
return
|
||||
|
||||
for pdir in util.listdir(d):
|
||||
|
||||
ppath = os.path.join(d, pdir)
|
||||
self.scan_directory_direct(ppath, pdir)
|
||||
|
||||
# If a file called "projects.txt" exists, include any projects listed in it.
|
||||
extra_projects_fn = os.path.join(d, "projects.txt")
|
||||
|
||||
if os.path.exists(extra_projects_fn):
|
||||
|
||||
with open(extra_projects_fn, "r") as f:
|
||||
|
||||
for path in f:
|
||||
path = path.strip()
|
||||
|
||||
if path.startswith("#"):
|
||||
continue
|
||||
|
||||
if len(path) > 0:
|
||||
self.scan_directory_direct(path)
|
||||
|
||||
|
||||
def scan_directory_direct(self, ppath, name=None):
|
||||
"""
|
||||
Checks if there is a project in `ppath` and creates a project
|
||||
object with the name `name` if so.
|
||||
"""
|
||||
|
||||
# A project must be a directory.
|
||||
if not os.path.isdir(ppath):
|
||||
return
|
||||
|
||||
try:
|
||||
ppath = self.find_basedir(ppath)
|
||||
except Exception:
|
||||
return
|
||||
|
||||
if ppath is None:
|
||||
return
|
||||
|
||||
if ppath in self.scanned:
|
||||
return
|
||||
|
||||
self.scanned.add(ppath)
|
||||
|
||||
# We have a project directory, so create a Project.
|
||||
p = Project(ppath, name)
|
||||
|
||||
if project_filter and (p.name not in project_filter):
|
||||
return
|
||||
|
||||
project_type = p.data.get("type", "normal")
|
||||
|
||||
if project_type == "hidden":
|
||||
pass
|
||||
elif project_type == "template":
|
||||
self.projects.append(p)
|
||||
self.templates.append(p)
|
||||
else:
|
||||
self.projects.append(p)
|
||||
|
||||
self.all_projects.append(p)
|
||||
|
||||
|
||||
def get(self, name):
|
||||
"""
|
||||
Gets the project with the given name.
|
||||
|
||||
Returns None if the project doesn't exist.
|
||||
"""
|
||||
|
||||
for p in self.all_projects:
|
||||
if p.name == name:
|
||||
return p
|
||||
|
||||
return None
|
||||
|
||||
def get_tutorial(self):
|
||||
|
||||
language = _preferences.language
|
||||
if persistent.force_new_tutorial:
|
||||
language = None
|
||||
|
||||
if language == self.tutorial_language:
|
||||
return self.tutorial
|
||||
|
||||
rv = self.get("oldtutorial")
|
||||
p = self.get("tutorial")
|
||||
|
||||
if p is not None:
|
||||
|
||||
if language is None:
|
||||
rv = p
|
||||
|
||||
elif rv is None:
|
||||
rv = p
|
||||
|
||||
elif os.path.exists(os.path.join(p.path, "game", "tl", _preferences.language)):
|
||||
rv = p
|
||||
|
||||
elif not os.path.exists(os.path.join(rv.path, "game", "tl", _preferences.language)):
|
||||
rv = p
|
||||
|
||||
self.tutorial_language = language
|
||||
self.tutorial = rv
|
||||
|
||||
return rv
|
||||
|
||||
manager = ProjectManager()
|
||||
|
||||
# The current project.
|
||||
current = None
|
||||
|
||||
# Actions
|
||||
class Select(Action):
|
||||
"""
|
||||
An action that causes p to become the selected project when it was
|
||||
clicked. If label is not None, jumps to the given label.
|
||||
"""
|
||||
|
||||
def __init__(self, p, label=None):
|
||||
"""
|
||||
`p`
|
||||
Either a project object, or a string giving the name of a
|
||||
project.
|
||||
|
||||
`label`
|
||||
The label to jump to when clicked.
|
||||
"""
|
||||
|
||||
if isinstance(p, str):
|
||||
p = manager.get(p)
|
||||
|
||||
self.project = p
|
||||
self.label = label
|
||||
|
||||
def get_selected(self):
|
||||
if self.project is None:
|
||||
return False
|
||||
|
||||
if current is None:
|
||||
return False
|
||||
|
||||
return current.path == self.project.path
|
||||
|
||||
def get_sensitive(self):
|
||||
return self.project is not None
|
||||
|
||||
def __call__(self):
|
||||
global current
|
||||
|
||||
current = self.project
|
||||
persistent.active_project = self.project.name
|
||||
|
||||
renpy.restart_interaction()
|
||||
|
||||
if self.label is not None:
|
||||
renpy.jump(self.label)
|
||||
|
||||
class SelectTutorial(Action):
|
||||
"""
|
||||
Selects the tutorial.
|
||||
"""
|
||||
|
||||
def __init__(self, if_tutorial=False):
|
||||
"""
|
||||
Only selects if we're already in a tutorial.
|
||||
"""
|
||||
|
||||
self.if_tutorial = if_tutorial
|
||||
|
||||
def __call__(self):
|
||||
|
||||
p = manager.get_tutorial()
|
||||
|
||||
if p is None:
|
||||
return
|
||||
|
||||
global current
|
||||
|
||||
if self.if_tutorial:
|
||||
if (current is not None) and current.name not in [ "tutorial", "oldtutorial" ]:
|
||||
return None
|
||||
|
||||
current = p
|
||||
persistent.active_project = p.name
|
||||
|
||||
renpy.restart_interaction()
|
||||
|
||||
def get_sensitive(self):
|
||||
if self.if_tutorial:
|
||||
return True
|
||||
|
||||
return (manager.get_tutorial() is not None)
|
||||
|
||||
def get_selected(self):
|
||||
if self.if_tutorial:
|
||||
return False
|
||||
|
||||
p = manager.get_tutorial()
|
||||
|
||||
if p is None:
|
||||
return False
|
||||
|
||||
if current is None:
|
||||
return False
|
||||
|
||||
return current.path == p.path
|
||||
|
||||
class Launch(Action):
|
||||
"""
|
||||
An action that launches the supplied project, or the current
|
||||
project if no project is supplied.
|
||||
"""
|
||||
|
||||
def __init__(self, p=None):
|
||||
if p is None:
|
||||
self.project = current
|
||||
elif isinstance(p, str):
|
||||
self.project = manager.get(p)
|
||||
else:
|
||||
self.project = p
|
||||
|
||||
def get_sensitive(self):
|
||||
return self.project is not None
|
||||
|
||||
def post_launch(self):
|
||||
blurb = LAUNCH_BLURBS[persistent.blurb % len(LAUNCH_BLURBS)]
|
||||
persistent.blurb += 1
|
||||
|
||||
if persistent.skip_splashscreen:
|
||||
submessage = _("Splashscreen skipped in launcher preferences.")
|
||||
else:
|
||||
submessage = None
|
||||
|
||||
interface.interaction(_("Launching"), blurb, submessage=submessage, pause=2.5)
|
||||
|
||||
|
||||
def __call__(self):
|
||||
self.project.launch()
|
||||
renpy.invoke_in_new_context(self.post_launch)
|
||||
|
||||
class Rescan(Action):
|
||||
def __call__(self):
|
||||
"""
|
||||
Rescans the projects directory.
|
||||
"""
|
||||
|
||||
manager.scan()
|
||||
renpy.restart_interaction()
|
||||
|
||||
|
||||
manager.scan()
|
||||
|
||||
if isinstance(persistent.projects_directory, str):
|
||||
persistent.projects_directory = renpy.fsdecode(persistent.projects_directory)
|
||||
|
||||
init 10 python:
|
||||
if persistent.projects_directory is not None:
|
||||
if not directory_is_writable(persistent.projects_directory):
|
||||
persistent.projects_directory = None
|
||||
|
||||
label after_load:
|
||||
python:
|
||||
if project.current is not None:
|
||||
project.current.update_dump()
|
||||
|
||||
return
|
||||
|
||||
|
||||
###############################################################################
|
||||
# Code to choose the projects directory.
|
||||
|
||||
label choose_projects_directory:
|
||||
|
||||
python hide:
|
||||
|
||||
interface.interaction(_("PROJECTS DIRECTORY"), _("Please choose the projects directory using the directory chooser.\n{b}The directory chooser may have opened behind this window.{/b}"), _("This launcher will scan for projects in this directory, will create new projects in this directory, and will place built projects into this directory."),)
|
||||
|
||||
path, is_default = choose_directory(persistent.projects_directory)
|
||||
|
||||
if is_default:
|
||||
interface.info(_("Ren'Py has set the projects directory to:"), "[path!q]", path=path)
|
||||
|
||||
persistent.projects_directory = path
|
||||
project.multipersistent.projects_directory = path
|
||||
project.multipersistent.save()
|
||||
|
||||
project.manager.scan()
|
||||
|
||||
return
|
||||
|
||||
init python:
|
||||
|
||||
def set_projects_directory_command():
|
||||
ap = renpy.arguments.ArgumentParser()
|
||||
ap.add_argument("projects", help="The path to the projects directory.")
|
||||
|
||||
args = ap.parse_args()
|
||||
|
||||
persistent.projects_directory = renpy.fsdecode(args.projects)
|
||||
project.multipersistent.projects_directory = persistent.projects_directory
|
||||
project.multipersistent.save()
|
||||
renpy.save_persistent()
|
||||
|
||||
return False
|
||||
|
||||
renpy.arguments.register_command("set_projects_directory", set_projects_directory_command)
|
||||
|
||||
def get_projects_directory_command():
|
||||
ap = renpy.arguments.ArgumentParser()
|
||||
args = ap.parse_args()
|
||||
|
||||
if persistent.projects_directory is not None:
|
||||
print(persistent.projects_directory)
|
||||
|
||||
return False
|
||||
|
||||
renpy.arguments.register_command("get_projects_directory", get_projects_directory_command)
|
||||
|
||||
def set_project_command():
|
||||
ap = renpy.arguments.ArgumentParser()
|
||||
ap.add_argument("project", help="The full path to the project to select.")
|
||||
|
||||
args = ap.parse_args()
|
||||
|
||||
projects = os.path.dirname(os.path.abspath(args.project))
|
||||
name = os.path.basename(args.project)
|
||||
|
||||
persistent.projects_directory = renpy.fsdecode(projects)
|
||||
project.multipersistent.projects_directory = persistent.projects_directory
|
||||
|
||||
persistent.active_project = name
|
||||
|
||||
project.multipersistent.save()
|
||||
renpy.save_persistent()
|
||||
|
||||
return False
|
||||
|
||||
renpy.arguments.register_command("set_project", set_project_command)
|
||||
@@ -1,4 +0,0 @@
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEmkuXOrZ2viRe4fRCrlqUFGdgRqy9IcWt
|
||||
5lJvsUqRN619pWZIefz/bau9Sd8vcJt6VKwexOeBmMhzXwZ/1Lm3OQ==
|
||||
-----END PUBLIC KEY-----
|
||||
@@ -1,8 +0,0 @@
|
||||
-----BEGIN RSA PUBLIC KEY-----
|
||||
MIIBCgKCAQEAnEdzfwV6FFnmjkbJHJKJ59jqLTGkPSES5LBFbxwweGuC3LU2sNs+
|
||||
tRlWx+2+kbW6azb5yteP/O05/hzHdtIa4slao7s8A/wf0zA6HbLX4H5iqSosDvuw
|
||||
FP9DAF46vH0Qn1a3k97JFV2eXoGAbwHZICKhbIPCm7qR39G8FvUrw7grCS+5Scb+
|
||||
LzqkBv6TnwRDB4agcwhlkA432s6BTU8p9RIFmyldCEgq0SXeK07lkuDmOplp6IdL
|
||||
bVwLoRWg6pngIEoPo/Qxt8ZnLdBthN+TAsMRiquz17GaAvERbkUBwYtlt7R8qx01
|
||||
F3vzweKXSCguzr9XQBQflkVu2y6cmIEwPQIDAQAB
|
||||
-----END RSA PUBLIC KEY-----
|
||||
@@ -1,550 +0,0 @@
|
||||
# Copyright 2004-2025 Tom Rothamel <pytom@bishoujo.us>
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person
|
||||
# obtaining a copy of this software and associated documentation files
|
||||
# (the "Software"), to deal in the Software without restriction,
|
||||
# including without limitation the rights to use, copy, modify, merge,
|
||||
# publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
# and to permit persons to whom the Software is furnished to do so,
|
||||
# subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
# Defaults for custom theme properties.
|
||||
init -99 python:
|
||||
# The color of non-interactive text.
|
||||
custom_text = "#545454"
|
||||
|
||||
# Colors for buttons in various states.
|
||||
custom_idle = "#42637b"
|
||||
custom_hover = "#d86b45"
|
||||
custom_disable = "#808080"
|
||||
|
||||
# Colors for reversed text buttons (selected list entries).
|
||||
custom_reverse_idle = "#78a5c5"
|
||||
reverse_hover = "#d86b45"
|
||||
custom_reverse_text = "#ffffff"
|
||||
|
||||
# Colors for the scrollbar thumb.
|
||||
custom_scrollbar_idle = "#dfdfdf"
|
||||
custom_scrollbar_hover = "#d86b45"
|
||||
# An image used as a separator pattern.
|
||||
custom_pattern = "images/pattern.png"
|
||||
|
||||
# A displayable used for the background of everything.
|
||||
custom_background = "images/background.png"
|
||||
|
||||
# A displayable used for the background of the projects list.
|
||||
custom_projects_window = Null()
|
||||
|
||||
# A displayable used the background of information boxes.
|
||||
custom_info_window = "#f9f9f9c0"
|
||||
|
||||
# Colors for the titles of information boxes.
|
||||
custom_error_color = "#d15353"
|
||||
custom_info_color = "#545454"
|
||||
custom_interaction_color = "#d19753"
|
||||
custom_question_color = "#d19753"
|
||||
|
||||
# The color of input text.
|
||||
custom_input_color = "#d86b45"
|
||||
|
||||
# A displayable used for the background of windows
|
||||
# containing commands, preferences, and navigation info.
|
||||
custom_window = Frame(Fixed(Solid(custom_reverse_idle, xsize=4, xalign=0), Solid(custom_info_window, xsize=794, xalign=1.0), xsize=800, ysize=600), 0, 0, tile=True)
|
||||
|
||||
|
||||
init -1:
|
||||
|
||||
# Fonts.
|
||||
define gui.LIGHT_FONT = "fonts/Roboto-Light.ttf"
|
||||
define gui.REGULAR_FONT = "fonts/Roboto-Regular.ttf"
|
||||
|
||||
# Used to scale the size of fonts.
|
||||
define gui.FONT_SCALE = 1.0
|
||||
|
||||
# Should places where the regular font used be bolded?
|
||||
define gui.REGULAR_BOLD = False
|
||||
|
||||
init -1 python:
|
||||
|
||||
config.defer_styles = True
|
||||
|
||||
# The color of non-interactive text.
|
||||
TEXT = "#545454"
|
||||
|
||||
# Colors for buttons in various states.
|
||||
IDLE = "#42637b"
|
||||
HOVER = "#d86b45"
|
||||
DISABLED = "#808080"
|
||||
|
||||
# Colors for reversed text buttons (selected list entries).
|
||||
REVERSE_IDLE = "#78a5c5"
|
||||
REVERSE_HOVER = "#d86b45"
|
||||
REVERSE_TEXT = "#ffffff"
|
||||
|
||||
# Colors for the scrollbar thumb.
|
||||
SCROLLBAR_IDLE = "#dfdfdf"
|
||||
SCROLLBAR_HOVER = "#d86b45"
|
||||
|
||||
# An image used as a separator pattern.
|
||||
PATTERN = "images/pattern.png"
|
||||
|
||||
# A displayable used for the background of everything.
|
||||
BACKGROUND = "background"
|
||||
|
||||
# A displayable used for the background of windows
|
||||
# containing commands, preferences, and navigation info.
|
||||
WINDOW = Frame("window", 0, 0, tile=True)
|
||||
|
||||
# A displayable used for the background of the projects list.
|
||||
PROJECTS_WINDOW = Null()
|
||||
|
||||
# A displayable used the background of information boxes.
|
||||
INFO_WINDOW = "#f9f9f9"
|
||||
|
||||
# Colors for the titles of information boxes.
|
||||
ERROR_COLOR = "#d15353"
|
||||
INFO_COLOR = "#545454"
|
||||
INTERACTION_COLOR = "#d19753"
|
||||
QUESTION_COLOR = "#d19753"
|
||||
|
||||
# The color of input text.
|
||||
INPUT_COLOR = "#d86b45"
|
||||
|
||||
if persistent.theme == 'dark':
|
||||
# The color of non-interactive text.
|
||||
TEXT = "#ababab"
|
||||
|
||||
# Colors for buttons in various states.
|
||||
IDLE = "#709cbe"
|
||||
HOVER = "#d86b45"
|
||||
DISABLED = "#7f7f7f"
|
||||
|
||||
# Colors for reversed text buttons (selected list entries).
|
||||
REVERSE_IDLE = "#78a5c5"
|
||||
REVERSE_HOVER = "#d86b45"
|
||||
REVERSE_TEXT = "#0a0a0a"
|
||||
|
||||
# Colors for the scrollbar thumb.
|
||||
SCROLLBAR_IDLE = "#282828"
|
||||
SCROLLBAR_HOVER = "#d86b45"
|
||||
|
||||
# An image used as a separator pattern.
|
||||
PATTERN = "images/pattern.png"
|
||||
|
||||
# A displayable used for the background of everything.
|
||||
BACKGROUND = Fixed(Solid(REVERSE_TEXT), xsize=800, ysize=600)
|
||||
|
||||
# A displayable used for the background of the projects list.
|
||||
PROJECTS_WINDOW = Null()
|
||||
|
||||
# A displayable used the background of information boxes.
|
||||
INFO_WINDOW = "#101010"
|
||||
|
||||
# Colors for the titles of information boxes.
|
||||
ERROR_COLOR = "#d15353"
|
||||
INFO_COLOR = "#ababab"
|
||||
INTERACTION_COLOR = "#d19753"
|
||||
QUESTION_COLOR = "#d19753"
|
||||
|
||||
# The color of input text.
|
||||
INPUT_COLOR = "#d86b45"
|
||||
|
||||
# A displayable used for the background of windows
|
||||
# containing commands, preferences, and navigation info.
|
||||
WINDOW = Frame(Fixed(Solid(REVERSE_IDLE, xsize=4, xalign=0), Solid(INFO_WINDOW, xsize=794, xalign=1.0), xsize=800, ysize=600), 0, 0, tile=True)
|
||||
|
||||
elif persistent.theme == 'clear':
|
||||
|
||||
# The color of non-interactive text.
|
||||
TEXT = "#545454"
|
||||
|
||||
# Colors for buttons in various states.
|
||||
IDLE = "#42637b"
|
||||
HOVER = "#d86b45"
|
||||
DISABLED = "#808080"
|
||||
|
||||
# Colors for reversed text buttons (selected list entries).
|
||||
REVERSE_IDLE = "#78a5c5"
|
||||
REVERSE_HOVER = "#d86b45"
|
||||
REVERSE_TEXT = "#ffffff"
|
||||
|
||||
# Colors for the scrollbar thumb.
|
||||
SCROLLBAR_IDLE = "#dfdfdf"
|
||||
SCROLLBAR_HOVER = "#d86b45"
|
||||
|
||||
# An image used as a separator pattern.
|
||||
PATTERN = "images/pattern.png"
|
||||
|
||||
# A displayable used for the background of everything.
|
||||
BACKGROUND = Fixed(Solid(REVERSE_TEXT), xsize=800, ysize=600)
|
||||
|
||||
# A displayable used for the background of the projects list.
|
||||
PROJECTS_WINDOW = Null()
|
||||
|
||||
# A displayable used the background of information boxes.
|
||||
INFO_WINDOW = "#f9f9f9"
|
||||
|
||||
# Colors for the titles of information boxes.
|
||||
ERROR_COLOR = "#d15353"
|
||||
INFO_COLOR = "#545454"
|
||||
INTERACTION_COLOR = "#d19753"
|
||||
QUESTION_COLOR = "#d19753"
|
||||
|
||||
# The color of input text.
|
||||
INPUT_COLOR = "#d86b45"
|
||||
|
||||
# A displayable used for the background of windows
|
||||
# containing commands, preferences, and navigation info.
|
||||
WINDOW = Frame(Fixed(Solid(REVERSE_IDLE, xsize=4, xalign=0), Solid(INFO_WINDOW, xsize=794, xalign=1.0), xsize=800, ysize=600), 0, 0, tile=True)
|
||||
|
||||
elif persistent.theme == 'custom':
|
||||
|
||||
# The color of non-interactive text.
|
||||
TEXT = custom_text
|
||||
|
||||
# Colors for buttons in various states.
|
||||
IDLE = custom_idle
|
||||
HOVER = custom_hover
|
||||
DISABLED = custom_disable
|
||||
|
||||
# Colors for reversed text buttons (selected list entries).
|
||||
REVERSE_IDLE = custom_reverse_idle
|
||||
REVERSE_HOVER = reverse_hover
|
||||
REVERSE_TEXT = custom_reverse_text
|
||||
|
||||
# Colors for the scrollbar thumb.
|
||||
SCROLLBAR_IDLE = custom_scrollbar_idle
|
||||
SCROLLBAR_HOVER = custom_scrollbar_hover
|
||||
|
||||
# An image used as a separator pattern.
|
||||
PATTERN = custom_pattern
|
||||
|
||||
# A displayable used for the background of everything.
|
||||
BACKGROUND = custom_background
|
||||
|
||||
# A displayable used for the background of the projects list.
|
||||
PROJECTS_WINDOW = custom_projects_window
|
||||
|
||||
# A displayable used the background of information boxes.
|
||||
INFO_WINDOW = custom_info_window
|
||||
|
||||
# Colors for the titles of information boxes.
|
||||
ERROR_COLOR = custom_error_color
|
||||
INFO_COLOR = custom_info_color
|
||||
INTERACTION_COLOR = custom_interaction_color
|
||||
QUESTION_COLOR = custom_question_color
|
||||
|
||||
# The color of input text.
|
||||
INPUT_COLOR = custom_input_color
|
||||
|
||||
# A displayable used for the background of windows
|
||||
# containing commands, preferences, and navigation info.
|
||||
WINDOW = custom_window
|
||||
|
||||
|
||||
|
||||
init 1 python:
|
||||
|
||||
if not renpy.has_image('scrollbar_center'):
|
||||
renpy.image('scrollbar_center', Fixed(Solid(REVERSE_TEXT, area=(2, 2, 2, 4)), Solid(REVERSE_TEXT, area=(8, 2, 2, 4)), Solid(REVERSE_TEXT, area=(14, 2, 2, 4)), xysize=(18, 8)))
|
||||
if not renpy.has_image('vscrollbar_center'):
|
||||
renpy.image('vscrollbar_center', Fixed(Solid(REVERSE_TEXT, area=(2, 2, 4, 2)), Solid(REVERSE_TEXT, area=(2, 8, 4, 2)), Solid(REVERSE_TEXT, area=(2, 14, 4, 2)), xysize=(8, 18)))
|
||||
|
||||
def size(n):
|
||||
"""
|
||||
Adjusts the font size if we're in large-print mode.
|
||||
"""
|
||||
|
||||
if persistent.large_print and n < 18:
|
||||
n = 18
|
||||
|
||||
n = int(n * gui.FONT_SCALE)
|
||||
|
||||
return n
|
||||
|
||||
def light_font():
|
||||
if persistent.large_print:
|
||||
return gui.REGULAR_FONT
|
||||
|
||||
return gui.LIGHT_FONT
|
||||
|
||||
def regular_font():
|
||||
return gui.REGULAR_FONT
|
||||
|
||||
INDENT = 20
|
||||
HALF_INDENT = 10
|
||||
|
||||
SCROLLBAR_SIZE = 16
|
||||
|
||||
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 = 8
|
||||
SPACER = Null(height=SPACER_HEIGHT)
|
||||
|
||||
HALF_SPACER_HEIGHT = 4
|
||||
HALF_SPACER = Null(height=HALF_SPACER_HEIGHT)
|
||||
|
||||
# DIVIDING THE SCREEN
|
||||
ONETHIRD = 258
|
||||
TWOTHIRDS = 496
|
||||
ONEHALF = 377
|
||||
|
||||
def checkbox(full, color):
|
||||
if renpy.has_image('checkbox_full'):
|
||||
check = im.Twocolor(''.join(["images/checkbox_", "full" if full else "empty", ".png"]), color, color)
|
||||
else:
|
||||
check = Fixed(Solid(color, xsize=1, xalign=.0), Solid(color, xsize=1, xalign=1.0), Solid(color, ysize=1, yalign=.0), Solid(color, ysize=1, yalign=1.0), xsize=10, ysize=10)
|
||||
if full:
|
||||
check = Fixed(check, Solid(color, xsize=6, ysize=6, align=(.5, .5)), xsize=10, ysize=10)
|
||||
return At(check, l_checkbox_box)
|
||||
|
||||
|
||||
|
||||
# The default style.
|
||||
style l_default is default:
|
||||
font light_font()
|
||||
color TEXT
|
||||
idle_color IDLE
|
||||
hover_color HOVER
|
||||
size size(18)
|
||||
|
||||
style l_text is l_default
|
||||
|
||||
style l_button is l_default
|
||||
style l_button_text is l_default:
|
||||
insensitive_color DISABLED
|
||||
selected_font regular_font()
|
||||
selected_bold gui.REGULAR_BOLD
|
||||
|
||||
# A small button, used at the bottom of the screen.
|
||||
style l_link is l_default
|
||||
style l_link_text is l_default:
|
||||
size size(14)
|
||||
font light_font()
|
||||
|
||||
# Action buttons on the bottom of the screen.
|
||||
style l_right_button is l_default:
|
||||
xalign 1.0
|
||||
ypos 600 - 128 + 12
|
||||
left_margin 8 + INDENT
|
||||
right_margin 10 + INDENT
|
||||
|
||||
style l_right_button_text is l_default:
|
||||
size size(30)
|
||||
|
||||
style l_left_button is l_right_button:
|
||||
xalign 0.0
|
||||
|
||||
style l_left_button_text is l_right_button_text
|
||||
|
||||
|
||||
# The root frame. This contains everything but the bottom navigation,
|
||||
# and buttons.
|
||||
style l_root is l_default:
|
||||
background BACKGROUND
|
||||
xpadding 10
|
||||
top_padding 32
|
||||
bottom_padding 120
|
||||
|
||||
# An inner window.
|
||||
style l_window is l_default:
|
||||
background WINDOW
|
||||
left_padding 6
|
||||
xfill True
|
||||
yfill True
|
||||
|
||||
# Normal size labels.
|
||||
style l_label is l_default:
|
||||
xfill True
|
||||
top_padding 10
|
||||
bottom_padding 8
|
||||
bottom_margin 12
|
||||
background SEPARATOR
|
||||
|
||||
style l_label_text is l_default:
|
||||
size size(24)
|
||||
xpos INDENT
|
||||
yoffset 6
|
||||
|
||||
style l_label_small is l_default:
|
||||
xfill True
|
||||
bottom_padding 8
|
||||
bottom_margin HALF_SPACER_HEIGHT
|
||||
background SEPARATOR
|
||||
|
||||
# Small labels.
|
||||
style l_label_small_text is l_default:
|
||||
xpos INDENT
|
||||
yoffset 6
|
||||
size size(20)
|
||||
|
||||
# Alternate labels. This nests inside an l_label, and gives a button
|
||||
# or label that's nested inside another label.
|
||||
|
||||
style l_alternate is l_default:
|
||||
xalign 1.0
|
||||
yalign 1.0
|
||||
yoffset 4
|
||||
right_margin INDENT
|
||||
|
||||
style l_alternate_text is l_default:
|
||||
size size(14)
|
||||
font light_font()
|
||||
textalign 1.0
|
||||
|
||||
style l_small_button is l_button
|
||||
|
||||
style l_small_button_text is l_button_text:
|
||||
size size(14)
|
||||
|
||||
style l_small_text is l_text:
|
||||
size size(14)
|
||||
|
||||
# Indents its contents.
|
||||
style l_indent is l_default:
|
||||
left_margin INDENT
|
||||
|
||||
# Indents its contents and pads vertically.
|
||||
style l_indent_margin is l_indent:
|
||||
ymargin 6
|
||||
|
||||
# Lists.
|
||||
style l_list is l_default:
|
||||
left_padding HALF_INDENT
|
||||
xfill True
|
||||
selected_background REVERSE_IDLE
|
||||
selected_hover_background REVERSE_HOVER
|
||||
|
||||
style l_list_text is l_default:
|
||||
idle_color IDLE
|
||||
hover_color HOVER
|
||||
selected_idle_color REVERSE_TEXT
|
||||
selected_hover_color REVERSE_TEXT
|
||||
insensitive_color DISABLED
|
||||
|
||||
style l_list2 is l_list:
|
||||
left_padding (HALF_INDENT + INDENT)
|
||||
|
||||
style l_list2_text is l_list_text
|
||||
|
||||
# Scrollbar.
|
||||
style l_vscrollbar is l_default:
|
||||
thumb Fixed(
|
||||
Solid(SCROLLBAR_IDLE, xmaximum=8, xalign=0.5),
|
||||
Transform('vscrollbar_center', xalign=0.5, yalign=0.5),
|
||||
xmaximum = SCROLLBAR_SIZE)
|
||||
hover_thumb Fixed(
|
||||
Solid(SCROLLBAR_HOVER, xmaximum=8, xalign=0.5),
|
||||
Transform('vscrollbar_center', xalign=0.5, yalign=0.5),
|
||||
xmaximum = SCROLLBAR_SIZE)
|
||||
xmaximum SCROLLBAR_SIZE
|
||||
bar_vertical True
|
||||
bar_invert True
|
||||
unscrollable "hide"
|
||||
|
||||
# Information window.
|
||||
style l_info_vbox is vbox:
|
||||
yalign 0.5
|
||||
xalign 0.5
|
||||
xfill True
|
||||
|
||||
style l_info_frame is l_default:
|
||||
ypadding 21
|
||||
xfill True
|
||||
background Fixed(
|
||||
INFO_WINDOW,
|
||||
Frame(PATTERN, 0, 0, tile=True, ymaximum=5, yalign=0.0, yoffset=8),
|
||||
Frame(PATTERN, 0, 0, tile=True, ymaximum=5, yalign=1.0, yoffset=-8),
|
||||
)
|
||||
yminimum 180
|
||||
ypos 75
|
||||
|
||||
style l_info_label is l_default:
|
||||
xalign 0.5
|
||||
ypos 75
|
||||
yanchor 1.0
|
||||
yoffset 12
|
||||
|
||||
style l_info_label_text is l_default:
|
||||
size size(36)
|
||||
|
||||
style l_info_text is l_default:
|
||||
xalign 0.5
|
||||
|
||||
style l_info_button is l_button:
|
||||
xalign 0.5
|
||||
xmargin 50
|
||||
|
||||
style l_info_button_text is l_button_text:
|
||||
textalign 0.5
|
||||
layout "subtitle"
|
||||
|
||||
# Progress bar.
|
||||
style l_progress_frame is l_default:
|
||||
background Frame(PATTERN, 0, 0, tile=True)
|
||||
ypadding 5
|
||||
|
||||
style l_progress_bar is l_default:
|
||||
left_bar REVERSE_IDLE
|
||||
right_bar Null()
|
||||
ymaximum 24
|
||||
|
||||
# Navigation.
|
||||
style l_navigation_button is l_button:
|
||||
size_group "navigation"
|
||||
right_margin INDENT
|
||||
top_margin 3
|
||||
|
||||
style l_navigation_button_text is l_button_text:
|
||||
size size(14)
|
||||
font regular_font()
|
||||
|
||||
style l_navigation_text is l_text:
|
||||
size size(14)
|
||||
font light_font()
|
||||
color TEXT
|
||||
|
||||
# Checkboxes.
|
||||
style l_checkbox is l_button:
|
||||
left_padding INDENT
|
||||
background checkbox(False, IDLE)
|
||||
hover_background checkbox(False, HOVER)
|
||||
selected_idle_background checkbox(True, IDLE)
|
||||
selected_hover_background checkbox(True, HOVER)
|
||||
insensitive_background checkbox(False, DISABLED)
|
||||
|
||||
transform l_checkbox_box:
|
||||
ycenter 11
|
||||
|
||||
style l_checkbox_text is l_button_text:
|
||||
selected_font light_font()
|
||||
selected_bold False
|
||||
|
||||
# Lines up with a checkbox.
|
||||
style l_nonbox is l_button:
|
||||
xpadding INDENT
|
||||
|
||||
style l_nonbox_text is l_button_text:
|
||||
selected_font light_font()
|
||||
|
||||
# Projects list.
|
||||
style l_projects is l_default:
|
||||
background PROJECTS_WINDOW
|
||||
|
||||
style hyperlink_text:
|
||||
size size(18)
|
||||
font light_font()
|
||||
color IDLE
|
||||
hover_color HOVER
|
||||
@@ -1,74 +0,0 @@
|
||||
# Copyright 2004-2025 Tom Rothamel <pytom@bishoujo.us>
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person
|
||||
# obtaining a copy of this software and associated documentation files
|
||||
# (the "Software"), to deal in the Software without restriction,
|
||||
# including without limitation the rights to use, copy, modify, merge,
|
||||
# publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
# and to permit persons to whom the Software is furnished to do so,
|
||||
# subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
|
||||
init python:
|
||||
|
||||
|
||||
class FileTail(object):
|
||||
|
||||
def __init__(self, filename, lines=8):
|
||||
self.filename = filename
|
||||
self.text = ""
|
||||
self.lines = lines
|
||||
|
||||
def update(self):
|
||||
|
||||
def filter_text(s):
|
||||
if "Unknown chunk type '200'" in s:
|
||||
return False
|
||||
return True
|
||||
|
||||
try:
|
||||
with open(self.filename) as f:
|
||||
text = f.read()
|
||||
|
||||
try:
|
||||
text = renpy.fsdecode(text)
|
||||
except Exception:
|
||||
text = text.decode("latin-1")
|
||||
|
||||
text = text.strip()
|
||||
text = text.split("\n")
|
||||
|
||||
newtext = [ ]
|
||||
for l in text:
|
||||
|
||||
if "\r" in l:
|
||||
_head, _sep, l = l.rpartition("\r")
|
||||
|
||||
if not filter_text(l):
|
||||
continue
|
||||
|
||||
while l:
|
||||
newtext.append(l[:100])
|
||||
l = l[100:]
|
||||
|
||||
text = newtext
|
||||
text = text[-self.lines:]
|
||||
text = "\n".join(text)
|
||||
|
||||
if text != self.text:
|
||||
self.text = text
|
||||
renpy.restart_interaction()
|
||||
|
||||
except Exception:
|
||||
pass
|
||||
@@ -1,191 +0,0 @@
|
||||
init python:
|
||||
TEST_PROJECTS = u"/tmp/renpy-moé"
|
||||
import shutil
|
||||
|
||||
|
||||
testcase default:
|
||||
|
||||
call new_project
|
||||
call translate_project
|
||||
call extract_dialogue
|
||||
|
||||
"Delete Persistent"
|
||||
"Force Recompile"
|
||||
|
||||
call build_project
|
||||
|
||||
"quit"
|
||||
|
||||
|
||||
testcase new_project:
|
||||
python:
|
||||
if os.path.exists(TEST_PROJECTS):
|
||||
shutil.rmtree(TEST_PROJECTS)
|
||||
|
||||
os.mkdir(TEST_PROJECTS, 0o777)
|
||||
|
||||
persistent.projects_directory = TEST_PROJECTS
|
||||
|
||||
"refresh"
|
||||
"Create New Project"
|
||||
|
||||
"Continue"
|
||||
|
||||
# Name
|
||||
type "Test Project"
|
||||
"Continue"
|
||||
|
||||
# Size
|
||||
"1280x720"
|
||||
"Continue"
|
||||
|
||||
# Color Selection
|
||||
"Continue"
|
||||
|
||||
|
||||
testcase choose_colors:
|
||||
"Change/Update GUI"
|
||||
"Choose new colors"
|
||||
"Continue"
|
||||
"Continue"
|
||||
|
||||
"Change/Update GUI"
|
||||
"Regenerate the"
|
||||
"Continue"
|
||||
|
||||
|
||||
testcase delete10:
|
||||
type BACKSPACE
|
||||
type BACKSPACE
|
||||
type BACKSPACE
|
||||
type BACKSPACE
|
||||
type BACKSPACE
|
||||
type BACKSPACE
|
||||
type BACKSPACE
|
||||
type BACKSPACE
|
||||
type BACKSPACE
|
||||
type BACKSPACE
|
||||
|
||||
|
||||
testcase delete30:
|
||||
call delete10
|
||||
call delete10
|
||||
call delete10
|
||||
|
||||
|
||||
testcase translate_project:
|
||||
"Generate Translations"
|
||||
|
||||
call delete30
|
||||
|
||||
type "piglatin"
|
||||
|
||||
"Generate Translations"
|
||||
"Continue"
|
||||
|
||||
"Generate Translations"
|
||||
"Extract String Translations"
|
||||
"Continue"
|
||||
|
||||
"Generate Translations"
|
||||
"Merge String Translations"
|
||||
"Continue"
|
||||
|
||||
"Generate Translations"
|
||||
"Update Default"
|
||||
|
||||
|
||||
testcase build_project:
|
||||
"Build Distributions"
|
||||
"Build"
|
||||
|
||||
|
||||
testcase extract_dialogue_common:
|
||||
"Extract Dialogue"
|
||||
"Strip text tags"
|
||||
"Escape quotes"
|
||||
"Extract all"
|
||||
|
||||
testcase extract_dialogue:
|
||||
call extract_dialogue_common
|
||||
"Tab-delimited"
|
||||
"Continue"
|
||||
"Continue"
|
||||
|
||||
call extract_dialogue_common
|
||||
"Text Only"
|
||||
"Continue"
|
||||
"Continue"
|
||||
|
||||
|
||||
testcase android:
|
||||
|
||||
$ _test.timeout = 60.0
|
||||
$ _test.maximum_framerate = False
|
||||
|
||||
"Tutorial"
|
||||
"Android"
|
||||
|
||||
# Download and install RAPT.
|
||||
if "Yes":
|
||||
|
||||
"Yes"
|
||||
"Proceed"
|
||||
|
||||
"Install SDK"
|
||||
"Yes" until "Continue"
|
||||
|
||||
# We have to create the key.
|
||||
if "Cancel":
|
||||
type "Test Key"
|
||||
"Continue"
|
||||
"Continue"
|
||||
|
||||
# Configure the application.
|
||||
"Configure"
|
||||
|
||||
$ _test.maximum_framerate = True
|
||||
|
||||
call delete30
|
||||
type "Ren'Py Tutorial"
|
||||
"Continue"
|
||||
|
||||
call delete30
|
||||
type "Ren'Py Tutorial"
|
||||
"Continue"
|
||||
|
||||
call delete30
|
||||
type "org.renpy.tutorial"
|
||||
"Continue"
|
||||
|
||||
call delete30
|
||||
type "1.2.3"
|
||||
"Continue"
|
||||
|
||||
call delete30
|
||||
type "10203"
|
||||
"Continue"
|
||||
|
||||
$ _test.maximum_framerate = False
|
||||
|
||||
"In landscape"
|
||||
"Continue"
|
||||
|
||||
"Neither"
|
||||
"Continue"
|
||||
|
||||
"No."
|
||||
"Continue"
|
||||
|
||||
"Android 4.0"
|
||||
"Continue"
|
||||
|
||||
# Access the internet.
|
||||
"No"
|
||||
"Continue"
|
||||
|
||||
# Build the package.
|
||||
"Build Package"
|
||||
"Continue"
|
||||
|
||||
"quit"
|
||||
@@ -1,609 +0,0 @@
|
||||
# Copyright 2004-2025 Tom Rothamel <pytom@bishoujo.us>
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person
|
||||
# obtaining a copy of this software and associated documentation files
|
||||
# (the "Software"), to deal in the Software without restriction,
|
||||
# including without limitation the rights to use, copy, modify, merge,
|
||||
# publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
# and to permit persons to whom the Software is furnished to do so,
|
||||
# subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
init python in theme_data:
|
||||
|
||||
def __PerformScreenOperations(lines, file_picker_rows=5, file_picker_cols=2):
|
||||
|
||||
import re
|
||||
|
||||
x = 0
|
||||
correct_section = False
|
||||
|
||||
while x < len(lines):
|
||||
l = lines[x]
|
||||
|
||||
m = re.match(r'^screen\s+\w+\s*:\s*$', l)
|
||||
if m:
|
||||
correct_section = False
|
||||
|
||||
m = re.match(r'^screen\s+file_picker\s*:\s*$', l)
|
||||
if m:
|
||||
correct_section = True
|
||||
|
||||
if correct_section:
|
||||
m = re.match(r'^\s+\$\s*columns\s*=\s*(\d+)\s*$', l)
|
||||
if m:
|
||||
l = l.replace(str(m.group(1)), str(file_picker_cols))
|
||||
m = re.match(r'^\s+\$\s*rows\s*=\s*(\d+)\s*$', l)
|
||||
if m:
|
||||
l = l.replace(str(m.group(1)), str(file_picker_rows))
|
||||
|
||||
lines[x] = l
|
||||
|
||||
x = x + 1
|
||||
|
||||
return lines
|
||||
|
||||
def roundrect_screen_ops(lines):
|
||||
return __PerformScreenOperations(lines)
|
||||
|
||||
def awt_screen_ops(lines):
|
||||
return __PerformScreenOperations(lines, file_picker_rows=3)
|
||||
|
||||
|
||||
|
||||
|
||||
# theme name -> (color scheme name -> code that implements it)
|
||||
THEME = { }
|
||||
|
||||
# The set of theme functions.
|
||||
THEME_FUNCTIONS = set()
|
||||
|
||||
# theme name -> function name to call to munge screens.rpy as appropriate
|
||||
# for that theme.
|
||||
THEME_SCREEN_OPERATIONS = { }
|
||||
|
||||
# Color schemes that work (technically - some are eye-melting) with the
|
||||
# roundrect-style themes.
|
||||
ROUNDRECT_SCHEMES = {
|
||||
'Basic Blue': {'disabled': '#404040',
|
||||
'disabled_text': '#c8c8c8',
|
||||
'frame': '#6496c8',
|
||||
'gm_root': '#dcebff',
|
||||
'label': '#ffffff',
|
||||
'mm_root': '#dcebff',
|
||||
'widget': '#003c78',
|
||||
'widget_hover': '#0050a0',
|
||||
'widget_selected': '#ffffc8',
|
||||
'widget_text': '#c8ffff'},
|
||||
'Bloody Mary': {'disabled': '#400000',
|
||||
'disabled_text': '#260000',
|
||||
'frame': '#400808',
|
||||
'gm_root': '#000000',
|
||||
'label': '#ffffff',
|
||||
'mm_root': '#000000',
|
||||
'widget': '#000000',
|
||||
'widget_hover': '#830000',
|
||||
'widget_selected': '#ffffff',
|
||||
'widget_text': '#C2C2C2'},
|
||||
'Colorblind': {'disabled': '#898989',
|
||||
'disabled_text': '#666666',
|
||||
'frame': '#252525',
|
||||
'gm_root': '#393939',
|
||||
'label': '#c2c2c2',
|
||||
'mm_root': '#393939',
|
||||
'widget': '#898989',
|
||||
'widget_hover': '#464646',
|
||||
'widget_selected': '#F2F2F2',
|
||||
'widget_text': '#CCCCCC'},
|
||||
'Cotton Candy': {'disabled': '#C8AFA1',
|
||||
'disabled_text': '#E1D4C9',
|
||||
'frame': '#FCF5F2',
|
||||
'gm_root': '#D0B4BA',
|
||||
'label': '#805C40',
|
||||
'mm_root': '#D0B4BA',
|
||||
'widget': '#ECC7D0',
|
||||
'widget_hover': '#E1D4C9',
|
||||
'widget_selected': '#805C40',
|
||||
'widget_text': '#805C40'},
|
||||
'Creamsicle': {'disabled': '#FFECBF',
|
||||
'disabled_text': '#ffffff',
|
||||
'frame': '#FFECBF',
|
||||
'gm_root': '#FDF5E3',
|
||||
'label': '#502F13',
|
||||
'mm_root': '#FDF5E3',
|
||||
'widget': '#D96B00',
|
||||
'widget_hover': '#FD9B1C',
|
||||
'widget_selected': '#ffffff',
|
||||
'widget_text': '#FCE6B1'},
|
||||
'Dramatic Flesh': {'disabled': '#ab6038',
|
||||
'disabled_text': '#BF7C51',
|
||||
'frame': '#49271b',
|
||||
'gm_root': '#2a201f',
|
||||
'label': '#ffffff',
|
||||
'mm_root': '#2a201f',
|
||||
'widget': '#BF7C51',
|
||||
'widget_hover': '#dda570',
|
||||
'widget_selected': '#ffffff',
|
||||
'widget_text': '#E5DFDF'},
|
||||
'Easter Baby': {'disabled': '#DDE9FF',
|
||||
'disabled_text': '#A6AFBF',
|
||||
'frame': '#CCF8DC',
|
||||
'gm_root': '#FBF9DF',
|
||||
'label': '#698071',
|
||||
'mm_root': '#FBF9DF',
|
||||
'widget': '#F5D4EE',
|
||||
'widget_hover': '#F0DDFF',
|
||||
'widget_selected': '#000000',
|
||||
'widget_text': '#698071'},
|
||||
'Favorite Jeans': {'disabled': '#919994',
|
||||
'disabled_text': '#B6BFB9',
|
||||
'frame': '#6f7571',
|
||||
'gm_root': '#b0b8ba',
|
||||
'label': '#ffffff',
|
||||
'mm_root': '#b0b8ba',
|
||||
'widget': '#8699a7',
|
||||
'widget_hover': '#9eb1ad',
|
||||
'widget_selected': '#ffffff',
|
||||
'widget_text': '#dcdfd6'},
|
||||
'Fine China': {'disabled': '#ADB9CC',
|
||||
'disabled_text': '#DFBA14',
|
||||
'frame': '#ADB9CC',
|
||||
'gm_root': '#F7F7FA',
|
||||
'label': '#39435E',
|
||||
'mm_root': '#F7F7FA',
|
||||
'widget': '#6A7183',
|
||||
'widget_hover': '#1A2B47',
|
||||
'widget_selected': '#E3E3E4',
|
||||
'widget_text': '#C9C9CB'},
|
||||
'First Valentines': {'disabled': '#F8F2D0',
|
||||
'disabled_text': '#BFA1A1',
|
||||
'frame': '#F8F2D0',
|
||||
'gm_root': '#D98989',
|
||||
'label': '#5D1010',
|
||||
'mm_root': '#D98989',
|
||||
'widget': '#F09898',
|
||||
'widget_hover': '#D6C5BB',
|
||||
'widget_selected': '#B31E1E',
|
||||
'widget_text': '#593131'},
|
||||
'Ice Queen': {'disabled': '#F0F2F2',
|
||||
'disabled_text': '#FBFBFB',
|
||||
'frame': '#ffffff',
|
||||
'gm_root': '#E6E6E6',
|
||||
'label': '#D9D9D9',
|
||||
'mm_root': '#E6E6E6',
|
||||
'widget': '#D9D9D9',
|
||||
'widget_hover': '#F0F2F2',
|
||||
'widget_selected': '#737373',
|
||||
'widget_text': '#ffffff'},
|
||||
'Mocha Latte': {'disabled': '#614D3A',
|
||||
'disabled_text': '#80654D',
|
||||
'frame': '#926841',
|
||||
'gm_root': '#1A140E',
|
||||
'label': '#F1EBE5',
|
||||
'mm_root': '#1A140E',
|
||||
'widget': '#4D3B29',
|
||||
'widget_hover': '#996E45',
|
||||
'widget_selected': '#ffffff',
|
||||
'widget_text': '#B99D83'},
|
||||
'Muted Horror': {'disabled': '#73735C',
|
||||
'disabled_text': '#8C8C70',
|
||||
'frame': '#555544',
|
||||
'gm_root': '#1A0001',
|
||||
'label': '#1A0001',
|
||||
'mm_root': '#1A0001',
|
||||
'widget': '#777777',
|
||||
'widget_hover': '#73735C',
|
||||
'widget_selected': '#000000',
|
||||
'widget_text': '#404033'},
|
||||
'Old Polaroid': {'disabled': '#A89E7D',
|
||||
'disabled_text': '#CCC097',
|
||||
'frame': '#49403E',
|
||||
'gm_root': '#A84A3E',
|
||||
'label': '#ffffff',
|
||||
'mm_root': '#A84A3E',
|
||||
'widget': '#A89E7D',
|
||||
'widget_hover': '#8DB6B9',
|
||||
'widget_selected': '#ffffff',
|
||||
'widget_text': '#49403E'},
|
||||
'Really Red': {'disabled': '#404040',
|
||||
'disabled_text': '#c8c8c8',
|
||||
'frame': '#e17373',
|
||||
'gm_root': '#ffd0d0',
|
||||
'label': '#ffffff',
|
||||
'mm_root': '#ffd0d0',
|
||||
'widget': '#963232',
|
||||
'widget_hover': '#c83232',
|
||||
'widget_selected': '#ffffc8',
|
||||
'widget_text': '#ffffff'},
|
||||
'Summer Sky': {'disabled': '#6074BF',
|
||||
'disabled_text': '#7383BF',
|
||||
'frame': '#6074BF',
|
||||
'gm_root': '#B4CDD4',
|
||||
'label': '#94C7D4',
|
||||
'mm_root': '#B4CDD4',
|
||||
'widget': '#F2E6AA',
|
||||
'widget_hover': '#FCFCA4',
|
||||
'widget_selected': '#1A5766',
|
||||
'widget_text': '#7DA8B3'},
|
||||
'Swamp Critter': {'disabled': '#A2521D',
|
||||
'disabled_text': '#753D00',
|
||||
'frame': '#797C1C',
|
||||
'gm_root': '#B09D5A',
|
||||
'label': '#ffffff',
|
||||
'mm_root': '#B09B4F',
|
||||
'widget': '#753D00',
|
||||
'widget_hover': '#B19A48',
|
||||
'widget_selected': '#ffffff',
|
||||
'widget_text': '#CCCAC2'},
|
||||
'Urban Sprawl': {'disabled': '#8F0000',
|
||||
'disabled_text': '#333333',
|
||||
'frame': '#8F0000',
|
||||
'gm_root': '#000000',
|
||||
'label': '#ffffff',
|
||||
'mm_root': '#000000',
|
||||
'widget': '#333333',
|
||||
'widget_hover': '#000000',
|
||||
'widget_selected': '#ffffff',
|
||||
'widget_text': '#6C8A2F'},
|
||||
'Victorian Gingerbread': {'disabled': '#7A674F',
|
||||
'disabled_text': '#664F33',
|
||||
'frame': '#BF8A73',
|
||||
'gm_root': '#695640',
|
||||
'label': '#F2EDC4',
|
||||
'mm_root': '#695640',
|
||||
'widget': '#7A674F',
|
||||
'widget_hover': '#BDA77D',
|
||||
'widget_selected': '#FDFBEE',
|
||||
'widget_text': '#F2EDC4'},
|
||||
'Watermelon Pie': {'disabled': '#FABF46',
|
||||
'disabled_text': '#FFE06D',
|
||||
'frame': '#C3CD91',
|
||||
'gm_root': '#F7F7C5',
|
||||
'label': '#FCFCD7',
|
||||
'mm_root': '#F7F7C5',
|
||||
'widget': '#FFE06D',
|
||||
'widget_hover': '#E38A4F',
|
||||
'widget_selected': '#996600',
|
||||
'widget_text': '#FAA700'},
|
||||
'White Chocolate': {'disabled': '#614D3A',
|
||||
'disabled_text': '#80654D',
|
||||
'frame': '#926841',
|
||||
'gm_root': '#FBF9EA',
|
||||
'label': '#F1EBE5',
|
||||
'mm_root': '#FBF9EA',
|
||||
'widget': '#33271C',
|
||||
'widget_hover': '#ECE7C4',
|
||||
'widget_selected': '#ffffff',
|
||||
'widget_text': '#B99D83'},
|
||||
'Winter Mint': {'disabled': '#426143',
|
||||
'disabled_text': '#819981',
|
||||
'frame': '#245536',
|
||||
'gm_root': '#e5f1e5',
|
||||
'label': '#ffffff',
|
||||
'mm_root': '#e5f1e5',
|
||||
'widget': '#7AA27B',
|
||||
'widget_hover': '#A3C7A3',
|
||||
'widget_selected': '#ffffff',
|
||||
'widget_text': '#CDE0CE'},
|
||||
'Mint Chocolate': {'disabled': '#ffe69c',
|
||||
'disabled_text': '#ddbc7e',
|
||||
'frame': '#8ab395',
|
||||
'gm_root': '#7a4229',
|
||||
'label': '#7a4229',
|
||||
'mm_root': '#7a4229',
|
||||
'widget': '#ffe69c',
|
||||
'widget_hover': '#f5c153',
|
||||
'widget_selected': '#7a4229',
|
||||
'widget_text': '#b5743a'},
|
||||
'Parachute Pants': {'disabled': '#53c7bb',
|
||||
'disabled_text': '#97d7bd',
|
||||
'frame': '#457b9f',
|
||||
'gm_root': '#37397f',
|
||||
'label': '#cce2ae',
|
||||
'mm_root': '#37397f',
|
||||
'widget': '#53c7bb',
|
||||
'widget_hover': '#97d7bd',
|
||||
'widget_selected': '#37397f',
|
||||
'widget_text': '#457b9f'},
|
||||
'Strawberry Orchard': {'disabled': '#b3c292',
|
||||
'disabled_text': '#525748',
|
||||
'frame': '#d8ebae',
|
||||
'gm_root': '#e7f3cb',
|
||||
'label': '#525748',
|
||||
'mm_root': '#e7f3cb',
|
||||
'widget': '#525748',
|
||||
'widget_hover': '#f45c73',
|
||||
'widget_selected': '#ffce95',
|
||||
'widget_text': '#e7f3cb'},
|
||||
'Grape Jelly': {'disabled': '#81859a',
|
||||
'disabled_text': '#5e2862',
|
||||
'frame': '#8ea9b0',
|
||||
'gm_root': '#5e2862',
|
||||
'label': '#ffffff',
|
||||
'mm_root': '#5e2862',
|
||||
'widget': '#c45693',
|
||||
'widget_hover': '#5e2862',
|
||||
'widget_selected': '#e59eae',
|
||||
'widget_text': '#ffffff'},
|
||||
'Dreamscape': {'disabled': '#966077',
|
||||
'disabled_text': '#c75f77',
|
||||
'frame': '#836177',
|
||||
'gm_root': '#c75f77',
|
||||
'label': '#fefab6',
|
||||
'mm_root': '#c75f77',
|
||||
'widget': '#77a493',
|
||||
'widget_hover': '#8accb3',
|
||||
'widget_selected': '#c75f77',
|
||||
'widget_text': '#ffffff'},
|
||||
'Unrequited Love': {'disabled': '#dbe4dd',
|
||||
'disabled_text': '#bd9ca9',
|
||||
'frame': '#fffeed',
|
||||
'gm_root': '#b38698',
|
||||
'label': '#23000e',
|
||||
'mm_root': '#b38698',
|
||||
'widget': '#7fa1b3',
|
||||
'widget_hover': '#b38698',
|
||||
'widget_selected': '#fffeed',
|
||||
'widget_text': '#ffffff'},
|
||||
|
||||
'Watermelon': {'disabled': '#f9cdad',
|
||||
'disabled_text': '#fc9d9a',
|
||||
'frame': '#f9cdad',
|
||||
'gm_root': '#83af9b',
|
||||
'label': '#fe4365',
|
||||
'mm_root': '#83af9b',
|
||||
'widget': '#fc9d9a',
|
||||
'widget_hover': '#fe4365',
|
||||
'widget_selected': '#e5fcc2',
|
||||
'widget_text': '#ffffff'},
|
||||
'City Lights': {'disabled': '#638e89',
|
||||
'disabled_text': '#594f4f',
|
||||
'frame': '#547980',
|
||||
'gm_root': '#594f4f',
|
||||
'label': '#e5fcc2',
|
||||
'mm_root': '#594f4f',
|
||||
'widget': '#45ada8',
|
||||
'widget_hover': '#2e5860',
|
||||
'widget_selected': '#e5fcc2',
|
||||
'widget_text': '#9de0ad'},
|
||||
'Vampire Bite': {'disabled': '#971140',
|
||||
'disabled_text': '#bd4b40',
|
||||
'frame': '#bd1550',
|
||||
'gm_root': '#490a3d',
|
||||
'label': '#f8ca00',
|
||||
'mm_root': '#490a3d',
|
||||
'widget': '#e97f02',
|
||||
'widget_hover': '#f5a240',
|
||||
'widget_selected': '#490a3d',
|
||||
'widget_text': '#bd1550'},
|
||||
'Underground Rave': {'disabled': '#57cdff',
|
||||
'disabled_text': '#717be5',
|
||||
'frame': '#04b4ff',
|
||||
'gm_root': '#cd249b',
|
||||
'label': '#000000',
|
||||
'mm_root': '#cd249b',
|
||||
'widget': '#8833ce',
|
||||
'widget_hover': '#cd249b',
|
||||
'widget_selected': '#b6d754',
|
||||
'widget_text': '#000000'},
|
||||
'Tree Frog': {'disabled': '#ffffff',
|
||||
'disabled_text': '#1c140d',
|
||||
'frame': '#cbe86b',
|
||||
'gm_root': '#ffffff',
|
||||
'label': '#1c140d',
|
||||
'mm_root': '#ffffff',
|
||||
'widget': '#1c140d',
|
||||
'widget_hover': '#86827e',
|
||||
'widget_selected': '#f2e9e1',
|
||||
'widget_text': '#cbe86b'},
|
||||
'Sun Kissed': {'disabled': '#ffffff',
|
||||
'disabled_text': '#ec4c51',
|
||||
'frame': '#f0874d',
|
||||
'gm_root': '#f8c821',
|
||||
'label': '#ffffff',
|
||||
'mm_root': '#f8c821',
|
||||
'widget': '#ec4c51',
|
||||
'widget_hover': '#dc454a',
|
||||
'widget_selected': '#f8c821',
|
||||
'widget_text': '#ffffff'},
|
||||
'Vintage Faded': {'disabled': '#b17d6f',
|
||||
'disabled_text': '#8c4e3d',
|
||||
'frame': '#ab7464',
|
||||
'gm_root': '#935844',
|
||||
'label': '#f7d3c8',
|
||||
'mm_root': '#935844',
|
||||
'widget': '#8c4e3d',
|
||||
'widget_hover': '#734032',
|
||||
'widget_selected': '#fcf5ed',
|
||||
'widget_text': '#e2b9ad'},
|
||||
'Vintage': {'disabled': '#883e35',
|
||||
'disabled_text': '#b86258',
|
||||
'frame': '#b86258',
|
||||
'gm_root': '#a24637',
|
||||
'label': '#ffffff',
|
||||
'mm_root': '#a24637',
|
||||
'widget': '#6c2921',
|
||||
'widget_hover': '#832f26',
|
||||
'widget_selected': '#ffc7c0',
|
||||
'widget_text': '#fff4eb'},
|
||||
'Earth Tones': {'disabled': '#12612f',
|
||||
'disabled_text': '#2c6e44',
|
||||
'frame': '#00551f',
|
||||
'gm_root': '#6b4a27',
|
||||
'label': '#ffffff',
|
||||
'mm_root': '#568153',
|
||||
'widget': '#ad8c31',
|
||||
'widget_hover': '#568153',
|
||||
'widget_selected': '#f2edc4',
|
||||
'widget_text': '#ffffff'},
|
||||
'Kindergarten': {'disabled': '#1ca4b2',
|
||||
'disabled_text': '#22d5b3',
|
||||
'frame': '#22d5b3',
|
||||
'gm_root': '#ffeca6',
|
||||
'label': '#ffffff',
|
||||
'mm_root': '#ffeca6',
|
||||
'widget': '#1781b1',
|
||||
'widget_hover': '#12678e',
|
||||
'widget_selected': '#f2edc4',
|
||||
'widget_text': '#fdfbee'},
|
||||
'Phone Operator': {'disabled': '#929292',
|
||||
'disabled_text': '#ababab',
|
||||
'frame': '#d2d2d2',
|
||||
'gm_root': '#59667a',
|
||||
'label': '#343e4d',
|
||||
'mm_root': '#59667a',
|
||||
'widget': '#59667a',
|
||||
'widget_hover': '#343e4d',
|
||||
'widget_selected': '#bed4f6',
|
||||
'widget_text': '#ffffff'},
|
||||
}
|
||||
|
||||
ROUNDRECT_VARIANTS = [
|
||||
("Roundrect", "roundrect"),
|
||||
("Bordered", "bordered"),
|
||||
("Diamond", "diamond"),
|
||||
("Regal", "regal"),
|
||||
("Austen", "austen"),
|
||||
("TV", "tv"),
|
||||
("3D", "threeD"),
|
||||
("Glow", "glow"),
|
||||
("Marker", "marker"),
|
||||
("Crayon", "crayon"),
|
||||
]
|
||||
|
||||
ROUNDRECT_CODE = """theme.%(function)s(
|
||||
## Theme: %(theme)s
|
||||
## Color scheme: %(scheme)s
|
||||
|
||||
## The color of an idle widget face.
|
||||
widget = "%(widget)s",
|
||||
|
||||
## The color of a focused widget face.
|
||||
widget_hover = "%(widget_hover)s",
|
||||
|
||||
## The color of the text in a widget.
|
||||
widget_text = "%(widget_text)s",
|
||||
|
||||
## The color of the text in a selected widget. (For
|
||||
## example, the current value of a preference.)
|
||||
widget_selected = "%(widget_selected)s",
|
||||
|
||||
## The color of a disabled widget face.
|
||||
disabled = "%(disabled)s",
|
||||
|
||||
## The color of disabled widget text.
|
||||
disabled_text = "%(disabled_text)s",
|
||||
|
||||
## The color of informational labels.
|
||||
label = "%(label)s",
|
||||
|
||||
## The color of a frame containing widgets.
|
||||
frame = "%(frame)s",
|
||||
|
||||
## The background of the main menu. This can be a color
|
||||
## beginning with '#', or an image filename. The latter
|
||||
## should take up the full height and width of the screen.
|
||||
mm_root = "%(mm_root)s",
|
||||
|
||||
## The background of the game menu. This can be a color
|
||||
## beginning with '#', or an image filename. The latter
|
||||
## should take up the full height and width of the screen.
|
||||
gm_root = "%(gm_root)s",
|
||||
|
||||
## If this is True, the in-game window is rounded. If False,
|
||||
## the in-game window is square.
|
||||
rounded_window = False,
|
||||
|
||||
## And we're done with the theme. The theme will customize
|
||||
## various styles, so if we want to change them, we should
|
||||
## do so below.
|
||||
)"""
|
||||
|
||||
for theme, function in ROUNDRECT_VARIANTS:
|
||||
THEME[theme] = { }
|
||||
THEME_FUNCTIONS.add(function)
|
||||
THEME_SCREEN_OPERATIONS[theme] = roundrect_screen_ops
|
||||
|
||||
for scheme, colors in ROUNDRECT_SCHEMES.items():
|
||||
subs = dict(colors)
|
||||
subs["function"] = function
|
||||
subs["theme"] = theme
|
||||
subs["scheme"] = scheme
|
||||
|
||||
THEME[theme][scheme] = ROUNDRECT_CODE % (subs)
|
||||
|
||||
AWT_CODE = """theme.a_white_tulip(
|
||||
## Theme: A White Tulip
|
||||
## Scheme %(scheme)s
|
||||
|
||||
## The color of an idle widget face.
|
||||
widget = "%(widget)s",
|
||||
|
||||
## The color of a focused widget face.
|
||||
widget_hover = "%(widget_hover)s",
|
||||
|
||||
## The color of the text in a selected widget. (For
|
||||
## example, the current value of a preference.)
|
||||
widget_selected = "%(widget_selected)s",
|
||||
|
||||
## The color of a disabled widget face.
|
||||
disabled = "%(disabled)s",
|
||||
|
||||
## The color of a frame containing widgets.
|
||||
frame = "%(frame)s",
|
||||
|
||||
## The background of the main menu. This can be a color
|
||||
## beginning with '#', or an image filename. The latter
|
||||
## should take up the full height and width of the screen.
|
||||
mm_root = "%(mm_root)s",
|
||||
|
||||
## The background of the game menu. This can be a color
|
||||
## beginning with '#', or an image filename. The latter
|
||||
## should take up the full height and width of the screen.
|
||||
gm_root = "%(gm_root)s",
|
||||
|
||||
## The fonts used by this theme. The default fonts may not be
|
||||
## suitable for non-English languages.
|
||||
regular_font = "_theme_awt/Quicksand-Regular.ttf",
|
||||
bold_font = "_theme_awt/Quicksand-Bold.ttf",
|
||||
|
||||
## And we're done with the theme. The theme will customize
|
||||
## various styles, so if we want to change them, we should
|
||||
## do so below.
|
||||
)"""
|
||||
|
||||
THEME["A White Tulip"] = { }
|
||||
THEME_FUNCTIONS.add("a_white_tulip")
|
||||
THEME_SCREEN_OPERATIONS["A White Tulip"] = awt_screen_ops
|
||||
|
||||
|
||||
for scheme, colors in ROUNDRECT_SCHEMES.items():
|
||||
subs = dict(colors)
|
||||
subs["scheme"] = scheme
|
||||
|
||||
THEME["A White Tulip"][scheme] = AWT_CODE % (subs)
|
||||
|
||||
THEME["A White Tulip"]["A White Tulip"] = AWT_CODE % dict(
|
||||
scheme = "A White Tulip",
|
||||
widget = "#c1c6d3",
|
||||
widget_hover = "#d7dbe5",
|
||||
widget_text = "#6b6b6b",
|
||||
widget_selected = "#c1c6d3",
|
||||
disabled = "#b4b4b4",
|
||||
disabled_text = "#6b6b6b",
|
||||
label = "#6b6b6b",
|
||||
frame = "#9391c9",
|
||||
mm_root = "#ffffff",
|
||||
gm_root = "#ffffff",
|
||||
)
|
||||
|
||||
@@ -1,335 +0,0 @@
|
||||
|
||||
translate arabic strings:
|
||||
|
||||
# 00action_file.rpy:26
|
||||
old "{#weekday}Monday"
|
||||
new "{#weekday}الأثنين"
|
||||
|
||||
# 00action_file.rpy:26
|
||||
old "{#weekday}Tuesday"
|
||||
new "{#weekday}الثلاثاء"
|
||||
|
||||
# 00action_file.rpy:26
|
||||
old "{#weekday}Wednesday"
|
||||
new "{#weekday}الأربعاء"
|
||||
|
||||
# 00action_file.rpy:26
|
||||
old "{#weekday}Thursday"
|
||||
new "{#weekday}الخميس"
|
||||
|
||||
# 00action_file.rpy:26
|
||||
old "{#weekday}Friday"
|
||||
new "{#weekday}الجمعة"
|
||||
|
||||
# 00action_file.rpy:26
|
||||
old "{#weekday}Saturday"
|
||||
new "{#weekday}السبت"
|
||||
|
||||
# 00action_file.rpy:26
|
||||
old "{#weekday}Sunday"
|
||||
new "{#weekday}الأحد"
|
||||
|
||||
# 00action_file.rpy:37
|
||||
old "{#weekday_short}Mon"
|
||||
new "{#weekday_short}اثن"
|
||||
|
||||
# 00action_file.rpy:37
|
||||
old "{#weekday_short}Tue"
|
||||
new "{#weekday_short}ثلا"
|
||||
|
||||
# 00action_file.rpy:37
|
||||
old "{#weekday_short}Wed"
|
||||
new "{#weekday_short}أرب"
|
||||
|
||||
# 00action_file.rpy:37
|
||||
old "{#weekday_short}Thu"
|
||||
new "{#weekday_short}خمي"
|
||||
|
||||
# 00action_file.rpy:37
|
||||
old "{#weekday_short}Fri"
|
||||
new "{#weekday_short}جمع"
|
||||
|
||||
# 00action_file.rpy:37
|
||||
old "{#weekday_short}Sat"
|
||||
new "{#weekday_short}سبت"
|
||||
|
||||
# 00action_file.rpy:37
|
||||
old "{#weekday_short}Sun"
|
||||
new "{#weekday_short}أحد"
|
||||
|
||||
# 00action_file.rpy:47
|
||||
old "{#month}January"
|
||||
new "{#month}يناير"
|
||||
|
||||
# 00action_file.rpy:47
|
||||
old "{#month}February"
|
||||
new "{#month}فبراير"
|
||||
|
||||
# 00action_file.rpy:47
|
||||
old "{#month}March"
|
||||
new "{#month}مارس"
|
||||
|
||||
# 00action_file.rpy:47
|
||||
old "{#month}April"
|
||||
new "{#month}أبريل"
|
||||
|
||||
# 00action_file.rpy:47
|
||||
old "{#month}May"
|
||||
new "{#month}مايو"
|
||||
|
||||
# 00action_file.rpy:47
|
||||
old "{#month}June"
|
||||
new "{#month}يونيو"
|
||||
|
||||
# 00action_file.rpy:47
|
||||
old "{#month}July"
|
||||
new "{#month}يوليو"
|
||||
|
||||
# 00action_file.rpy:47
|
||||
old "{#month}August"
|
||||
new "{#month}أغسطس"
|
||||
|
||||
# 00action_file.rpy:47
|
||||
old "{#month}September"
|
||||
new "{#month}سبتمبر"
|
||||
|
||||
# 00action_file.rpy:47
|
||||
old "{#month}October"
|
||||
new "{#month}أكتوبر"
|
||||
|
||||
# 00action_file.rpy:47
|
||||
old "{#month}November"
|
||||
new "{#month}نوفمبر"
|
||||
|
||||
# 00action_file.rpy:47
|
||||
old "{#month}December"
|
||||
new "{#month}ديسمبر"
|
||||
|
||||
# 00action_file.rpy:63
|
||||
old "{#month_short}Jan"
|
||||
new "{#month_short}ينا"
|
||||
|
||||
# 00action_file.rpy:63
|
||||
old "{#month_short}Feb"
|
||||
new "{#month_short}فبر"
|
||||
|
||||
# 00action_file.rpy:63
|
||||
old "{#month_short}Mar"
|
||||
new "{#month_short}مار"
|
||||
|
||||
# 00action_file.rpy:63
|
||||
old "{#month_short}Apr"
|
||||
new "{#month_short}ابر"
|
||||
|
||||
# 00action_file.rpy:63
|
||||
old "{#month_short}May"
|
||||
new "{#month_short}ماي"
|
||||
|
||||
# 00action_file.rpy:63
|
||||
old "{#month_short}Jun"
|
||||
new "{#month_short}يون"
|
||||
|
||||
# 00action_file.rpy:63
|
||||
old "{#month_short}Jul"
|
||||
new "{#month_short}يول"
|
||||
|
||||
# 00action_file.rpy:63
|
||||
old "{#month_short}Aug"
|
||||
new "{#month_short}أغس"
|
||||
|
||||
# 00action_file.rpy:63
|
||||
old "{#month_short}Sep"
|
||||
new "{#month_short}سبتم"
|
||||
|
||||
# 00action_file.rpy:63
|
||||
old "{#month_short}Oct"
|
||||
new "{#month_short}أكت"
|
||||
|
||||
# 00action_file.rpy:63
|
||||
old "{#month_short}Nov"
|
||||
new "{#month_short}نوف"
|
||||
|
||||
# 00action_file.rpy:63
|
||||
old "{#month_short}Dec"
|
||||
new "{#month_short}ديس"
|
||||
|
||||
# 00action_file.rpy:235
|
||||
old "%b %d, %H:%M"
|
||||
new "%b %d, %H:%M"
|
||||
|
||||
# 00action_file.rpy:820
|
||||
old "Quick save complete."
|
||||
new "تم الحفظ السريع"
|
||||
|
||||
# 00gui.rpy:227
|
||||
old "Are you sure?"
|
||||
new "هل انت متأكد؟"
|
||||
|
||||
# 00gui.rpy:228
|
||||
old "Are you sure you want to delete this save?"
|
||||
new "هل انت متأكد من رغبتك في حذف خانة الحفظ هذه؟"
|
||||
|
||||
# 00gui.rpy:229
|
||||
old "Are you sure you want to overwrite your save?"
|
||||
new "هل انت متأكد من رغبتك في الحفظ على هذه الخانة؟"
|
||||
|
||||
# 00gui.rpy:230
|
||||
old "Loading will lose unsaved progress.\nAre you sure you want to do this?"
|
||||
new "الاسترجاع سيضيع كل ما فعلته منذ خانة الحفظ السابقة. \n هل انت متأكد من رغبتك في الاسترجاع إلى هذه النقطة؟"
|
||||
|
||||
# 00gui.rpy:231
|
||||
old "Are you sure you want to quit?"
|
||||
new "هل انت متأكد من رغبتك في الخروج؟"
|
||||
|
||||
# 00gui.rpy:232
|
||||
old "Are you sure you want to return to the main menu?\nThis will lose unsaved progress."
|
||||
new "هل انت متأكد من رغبتك في العودة للقائمة الرئيسية؟ \n كل ما لم تقم بحفظة سيضيع."
|
||||
|
||||
# 00gui.rpy:233
|
||||
old "Are you sure you want to end the replay?"
|
||||
new "هل أنت متأكد من رغبتك في الخروج من الإعادة؟"
|
||||
|
||||
# 00gui.rpy:234
|
||||
old "Are you sure you want to begin skipping?"
|
||||
new "هل أنت متأكد من رغبتك في البدء بالتسريع؟"
|
||||
|
||||
# 00gui.rpy:235
|
||||
old "Are you sure you want to skip to the next choice?"
|
||||
new "هل انت متأكد من رغبتك في التسريع حتى الخيار التالي؟"
|
||||
|
||||
# 00gui.rpy:236
|
||||
old "Are you sure you want to skip unseen dialogue to the next choice?"
|
||||
new "هل أنت متأكد من رغبتك في تسريع كل الحوارات حتى التي لم تسبق رؤيتها؟"
|
||||
|
||||
# 00keymap.rpy:250
|
||||
old "Saved screenshot as %s."
|
||||
new "تم حفظ الصورة كـ %s"
|
||||
|
||||
# 00library.rpy:142
|
||||
old "Self-voicing disabled."
|
||||
new "تم الغاء تفعيل النطق التلقائي."
|
||||
|
||||
# 00library.rpy:143
|
||||
old "Clipboard voicing enabled. "
|
||||
new "تم تفعيل النطق التلقائي من لوح الحفظ. "
|
||||
|
||||
# 00library.rpy:144
|
||||
old "Self-voicing enabled. "
|
||||
new "تم تفعيل النطق التلقائي."
|
||||
|
||||
# 00library.rpy:179
|
||||
old "Skip Mode"
|
||||
new "وضع التسريع"
|
||||
|
||||
# 00library.rpy:262
|
||||
old "This program contains free software under a number of licenses, including the MIT License and GNU Lesser General Public License. A complete list of software, including links to full source code, can be found {a=https://www.renpy.org/l/license}here{/a}."
|
||||
new "This program contains free software under a number of licenses, including the MIT License and GNU Lesser General Public License. A complete list of software, including links to full source code, can be found {a=https://www.renpy.org/l/license}here{/a}."
|
||||
|
||||
# 00preferences.rpy:422
|
||||
old "Clipboard voicing enabled. Press 'shift+C' to disable."
|
||||
new "تشغيل الأصوات من الحافظة مفعّل. إضغط لإيقافه زريّ: Shift+C"
|
||||
|
||||
# 00preferences.rpy:424
|
||||
old "Self-voicing would say \"[renpy.display.tts.last]\". Press 'alt+shift+V' to disable."
|
||||
new "Self-voicing would say \"[renpy.display.tts.last]\". Press 'alt+shift+V' to disable."
|
||||
|
||||
# 00preferences.rpy:426
|
||||
old "Self-voicing enabled. Press 'v' to disable."
|
||||
new "النطق التلقائي مفعَل. لإيقافه إضغط زر V"
|
||||
|
||||
# 00iap.rpy:217
|
||||
old "Contacting App Store\nPlease Wait..."
|
||||
new "Contacting App Store\nPlease Wait..."
|
||||
|
||||
# 00updater.rpy:367
|
||||
old "The Ren'Py Updater is not supported on mobile devices."
|
||||
new "تحديث برنامج رينباي غير مدعوم على الأجهزة المحمولة."
|
||||
|
||||
# 00updater.rpy:486
|
||||
old "An error is being simulated."
|
||||
new "يتم محاكاة خطأ الآن."
|
||||
|
||||
# 00updater.rpy:662
|
||||
old "Either this project does not support updating, or the update status file was deleted."
|
||||
new "هذا المشروع لا يدعم التحديث، أو أن ملف حالة نسخة البرنامج محذوفة."
|
||||
|
||||
# 00updater.rpy:676
|
||||
old "This account does not have permission to perform an update."
|
||||
new "هذا الحساب ليست لديه الصلاحية ليقوم بالتحديث."
|
||||
|
||||
# 00updater.rpy:679
|
||||
old "This account does not have permission to write the update log."
|
||||
new " هذا الحساب ليست لديه الصلاحية ليكتب ملف التحديث."
|
||||
|
||||
# 00updater.rpy:704
|
||||
old "Could not verify update signature."
|
||||
new "لم نتمكن من التحقق من توقيع التحديث update signature."
|
||||
|
||||
# 00updater.rpy:975
|
||||
old "The update file was not downloaded."
|
||||
new "لم يتم تحميل ملف التحديث."
|
||||
|
||||
# 00updater.rpy:993
|
||||
old "The update file does not have the correct digest - it may have been corrupted."
|
||||
new "ملف التحديث لا يحتوي على المختصرات الصحيحة - قد يكون معطوباً."
|
||||
|
||||
# 00updater.rpy:1049
|
||||
old "While unpacking {}, unknown type {}."
|
||||
new "عند فك الحزمة {}، نوع مجهول {}."
|
||||
|
||||
# 00updater.rpy:1393
|
||||
old "Updater"
|
||||
new "برنامج التحديث"
|
||||
|
||||
# 00updater.rpy:1404
|
||||
old "This program is up to date."
|
||||
new "هذه النسخة هي الأحدث"
|
||||
|
||||
# 00updater.rpy:1406
|
||||
old "[u.version] is available. Do you want to install it?"
|
||||
new "النسخة [u.version] متوفرة, هل ترغب في تنصيبها؟"
|
||||
|
||||
# 00updater.rpy:1408
|
||||
old "Preparing to download the updates."
|
||||
new "يتم التجهيز لتحميل البرنامج من الانترنت"
|
||||
|
||||
# 00updater.rpy:1410
|
||||
old "Downloading the updates."
|
||||
new "يتم تنزيل التحديثات"
|
||||
|
||||
# 00updater.rpy:1412
|
||||
old "Unpacking the updates."
|
||||
new "يتم فك الضغط عن التحديثات"
|
||||
|
||||
# 00updater.rpy:1416
|
||||
old "The updates have been installed. The program will restart."
|
||||
new "تم التحديث.. سيتم إعادة تشغيل البرنامج."
|
||||
|
||||
# 00updater.rpy:1418
|
||||
old "The updates have been installed."
|
||||
new "تم التحديث."
|
||||
|
||||
# 00updater.rpy:1420
|
||||
old "The updates were cancelled."
|
||||
new "تم إلغاء التحديث."
|
||||
|
||||
# 00gallery.rpy:563
|
||||
old "Image [index] of [count] locked."
|
||||
new "يوجد عدد صور [index] من أصل [count] مقفل"
|
||||
|
||||
# 00gallery.rpy:583
|
||||
old "prev"
|
||||
new "السابق"
|
||||
|
||||
# 00gallery.rpy:584
|
||||
old "next"
|
||||
new "التالي"
|
||||
|
||||
# 00gallery.rpy:585
|
||||
old "slideshow"
|
||||
new "عرض الشرائح"
|
||||
|
||||
# 00gallery.rpy:586
|
||||
old "return"
|
||||
new "العودة"
|
||||
|
||||
@@ -1,179 +0,0 @@
|
||||
|
||||
translate arabic strings:
|
||||
|
||||
# _developer/developer.rpym:38
|
||||
old "Developer Menu"
|
||||
new "قائمة المبرمج"
|
||||
|
||||
# _developer/developer.rpym:43
|
||||
old "Reload Game (Shift+R)"
|
||||
new "إعادة تشغيل اللعبة (Shift+R)"
|
||||
|
||||
# _developer/developer.rpym:45
|
||||
old "Console (Shift+O)"
|
||||
new "لوحة التحكم (Shift+O)"
|
||||
|
||||
# _developer/developer.rpym:47
|
||||
old "Variable Viewer"
|
||||
new "مستعرض الأوامر"
|
||||
|
||||
# _developer/developer.rpym:49
|
||||
old "Theme Test"
|
||||
new "اختبار القوالب"
|
||||
|
||||
# _developer/developer.rpym:51
|
||||
old "Image Location Picker"
|
||||
new "مكان الصور المطلوبة"
|
||||
|
||||
# _developer/developer.rpym:53
|
||||
old "Filename List"
|
||||
new "قائمة اسماء الملفات"
|
||||
|
||||
# _developer/developer.rpym:57
|
||||
old "Show Image Load Log"
|
||||
new "عرض قائمة الصور "
|
||||
|
||||
# _developer/developer.rpym:60
|
||||
old "Hide Image Load Log"
|
||||
new "إخفاء قائمة الصور"
|
||||
|
||||
# _developer/developer.rpym:95
|
||||
old "Nothing to inspect."
|
||||
new "لا يوجد شيء ليتم اختباره"
|
||||
|
||||
# _developer/developer.rpym:217
|
||||
old "Return to the developer menu"
|
||||
new "العودة للوحة المبرمج"
|
||||
|
||||
# _developer/developer.rpym:373
|
||||
old "Rectangle: %r"
|
||||
new "مثلث: %r"
|
||||
|
||||
# _developer/developer.rpym:378
|
||||
old "Mouse position: %r"
|
||||
new "مكان المؤشر: %r"
|
||||
|
||||
# _developer/developer.rpym:383
|
||||
old "Right-click or escape to quit."
|
||||
new "إضغط بالزر الايمن او إضغط زر الخروح للإغلاق"
|
||||
|
||||
# _developer/developer.rpym:412
|
||||
old "Rectangle copied to clipboard."
|
||||
new "تم نسخ المربع إلى الحافظة."
|
||||
|
||||
# _developer/developer.rpym:415
|
||||
old "Position copied to clipboard."
|
||||
new "تم نسخ الموضع إلى الحافظة."
|
||||
|
||||
# _developer/developer.rpym:524
|
||||
old "✔ "
|
||||
new "✔ "
|
||||
|
||||
# _developer/developer.rpym:527
|
||||
old "✘ "
|
||||
new "✘ "
|
||||
|
||||
# _developer/developer.rpym:532
|
||||
old "\n{color=#cfc}✔ predicted image (good){/color}\n{color=#fcc}✘ unpredicted image (bad){/color}\n{color=#fff}Drag to move.{/color}"
|
||||
new "\n{color=#cfc}✔ predicted image (good){/color}\n{color=#fcc}✘ unpredicted image (bad){/color}\n{color=#fff}Drag to move.{/color}"
|
||||
|
||||
# _developer/inspector.rpym:38
|
||||
old "Displayable Inspector"
|
||||
new "اختبار المستعرضات"
|
||||
|
||||
# _developer/inspector.rpym:61
|
||||
old "Size"
|
||||
new "حجم"
|
||||
|
||||
# _developer/inspector.rpym:65
|
||||
old "Style"
|
||||
new "مظهر"
|
||||
|
||||
# _developer/inspector.rpym:71
|
||||
old "Location"
|
||||
new "الموقع"
|
||||
|
||||
# _developer/inspector.rpym:122
|
||||
old "Inspecting Styles of [displayable_name!q]"
|
||||
new "يتم اختبار المظهر الخاص بـ [displayable_name!q]"
|
||||
|
||||
# _developer/inspector.rpym:139
|
||||
old "displayable:"
|
||||
new "مستعرضات"
|
||||
|
||||
# _developer/inspector.rpym:145
|
||||
old " (no properties affect the displayable)"
|
||||
new " (لا توجد اي مؤثرات على هذا المستعرض)"
|
||||
|
||||
# _developer/inspector.rpym:147
|
||||
old " (default properties omitted)"
|
||||
new " (تم استبعاد تأثير المؤثرات القياسية)"
|
||||
|
||||
# _developer/inspector.rpym:185
|
||||
old "<repr() failed>"
|
||||
new "<repr() failed>"
|
||||
|
||||
# 00console.rpy:182
|
||||
old "Press <esc> to exit console. Type help for help.\n"
|
||||
new "إضغط زر الخروج لإغلاق لوحة التحكم, اكتب كلمة help للمساعدة"
|
||||
|
||||
# 00console.rpy:186
|
||||
old "Ren'Py script enabled."
|
||||
new "لغة برمحة رينباي متاحه"
|
||||
|
||||
# 00console.rpy:188
|
||||
old "Ren'Py script disabled."
|
||||
new "لغة برمجة رينباي غير متاحه"
|
||||
|
||||
# 00console.rpy:398
|
||||
old "help: show this help"
|
||||
new "مساعده: عرض هذه المساعده"
|
||||
|
||||
# 00console.rpy:403
|
||||
old "commands:\n"
|
||||
new "أوامر: \n"
|
||||
|
||||
# 00console.rpy:413
|
||||
old " <renpy script statement>: run the statement\n"
|
||||
new "<renpy script statement>: عرض الأوامر\n"
|
||||
|
||||
# 00console.rpy:415
|
||||
old " <python expression or statement>: run the expression or statement"
|
||||
new " <python expression or statement>: عرض التعبير او الأوامر"
|
||||
|
||||
# 00console.rpy:423
|
||||
old "clear: clear the console history"
|
||||
new "clear: مسح تاريخ لوحة التحكم"
|
||||
|
||||
# 00console.rpy:427
|
||||
old "exit: exit the console"
|
||||
new "exit: الخروج من لوحة التحكم"
|
||||
|
||||
# 00console.rpy:435
|
||||
old "load <slot>: loads the game from slot"
|
||||
new "استرجاع <slot>: يقوم باسترجاع اللعب من نقطة الحفظ"
|
||||
|
||||
# 00console.rpy:448
|
||||
old "save <slot>: saves the game in slot"
|
||||
new "حفظ <slot>: يقوم بحفظ اللعب في نقطة الحفظ"
|
||||
|
||||
# 00console.rpy:459
|
||||
old "reload: reloads the game, refreshing the scripts"
|
||||
new "reload: يعيد تشغيل اللعبة مع عرض التغييرات في النص"
|
||||
|
||||
# 00console.rpy:467
|
||||
old "watch <expression>: watch a python expression"
|
||||
new "مشاهده <expression>: يقوم بعرض تعبيرات بايثون"
|
||||
|
||||
# 00console.rpy:493
|
||||
old "unwatch <expression>: stop watching an expression"
|
||||
new "unwatch <expression>: يقوم بإيقاف تعبير بايثون"
|
||||
|
||||
# 00console.rpy:519
|
||||
old "unwatchall: stop watching all expressions"
|
||||
new "unwatchall: يقوم بإيقاف كل تعبيرات بايثون"
|
||||
|
||||
# 00console.rpy:536
|
||||
old "jump <label>: jumps to label"
|
||||
new "jump <label>: يقفز للعنوان"
|
||||
|
||||
@@ -1,179 +0,0 @@
|
||||
|
||||
translate arabic strings:
|
||||
|
||||
# 00gltest.rpy:64
|
||||
old "Graphics Acceleration"
|
||||
new "تسريع الرسومات"
|
||||
|
||||
# 00gltest.rpy:70
|
||||
old "Automatically Choose"
|
||||
new "إختيار اوتوماتيكي"
|
||||
|
||||
# 00gltest.rpy:75
|
||||
old "Force Angle/DirectX Renderer"
|
||||
new "فرض الزاوية/دايركت × ومحركاته"
|
||||
|
||||
# 00gltest.rpy:79
|
||||
old "Force OpenGL Renderer"
|
||||
new "فرض محرك OpenGL"
|
||||
|
||||
# 00gltest.rpy:83
|
||||
old "Force Software Renderer"
|
||||
new "فرض محركات البرامج"
|
||||
|
||||
# 00gltest.rpy:93
|
||||
old "Enable"
|
||||
new "تفعيل"
|
||||
|
||||
# 00gltest.rpy:109
|
||||
old "Changes will take effect the next time this program is run."
|
||||
new "التغييرات سيتم تطبيقها في المرة القادمة تفتح البرنامج"
|
||||
|
||||
# 00gltest.rpy:141
|
||||
old "Performance Warning"
|
||||
new "تحذير تشغيلي"
|
||||
|
||||
# 00gltest.rpy:146
|
||||
old "This computer is using software rendering."
|
||||
new "هذا الحاسوب يستعمل محركات البرامج software rendering"
|
||||
|
||||
# 00gltest.rpy:148
|
||||
old "This computer is not using shaders."
|
||||
new "هذا الحاسوب لا يستعمل اي shaders"
|
||||
|
||||
# 00gltest.rpy:150
|
||||
old "This computer is displaying graphics slowly."
|
||||
new "هذا الحاسوب يستعرض الرسوميات بشكل بطيء"
|
||||
|
||||
# 00gltest.rpy:152
|
||||
old "This computer has a problem displaying graphics: [problem]."
|
||||
new "هذا الحاسوب يواجه مشكلة في عرض الرسوميات [problem]"
|
||||
|
||||
# 00gltest.rpy:157
|
||||
old "Its graphics drivers may be out of date or not operating correctly. This can lead to slow or incorrect graphics display. Updating DirectX could fix this problem."
|
||||
new "محركات الرسوميات قد تكون قديمة او لا تعمل بشكل صحيح. قد يسبب ذلك بطء او اخطاء في الاستعراض, القيام بتحديث directX قد يساعد في حل المشكلة."
|
||||
|
||||
# 00gltest.rpy:159
|
||||
old "Its graphics drivers may be out of date or not operating correctly. This can lead to slow or incorrect graphics display."
|
||||
new " محركات الرسوميات قد تكون قديمة او لا تعمل بشكل صحيح. قد يسبب ذلك بطء او اخطاء في الاستعراض."
|
||||
|
||||
# 00gltest.rpy:164
|
||||
old "Update DirectX"
|
||||
new "تحديث DirectX"
|
||||
|
||||
# 00gltest.rpy:170
|
||||
old "Continue, Show this warning again"
|
||||
new "استمرار, الرجاء عرض هذا التحذير في المرة الثادمة ايضاً"
|
||||
|
||||
# 00gltest.rpy:174
|
||||
old "Continue, Don't show warning again"
|
||||
new "استمرار, لا تعرض هذا التحذير مرة اخرى"
|
||||
|
||||
# 00gltest.rpy:192
|
||||
old "Updating DirectX."
|
||||
new "يتم تحديث DirectX"
|
||||
|
||||
# 00gltest.rpy:196
|
||||
old "DirectX web setup has been started. It may start minimized in the taskbar. Please follow the prompts to install DirectX."
|
||||
new "يتم الآن تنصيب DirectX, قد يبدأ ذلك بشكل مصغر في شريط المهام. الرجاء اتباع التعليمات لاكمال التنصيب."
|
||||
|
||||
# 00gltest.rpy:200
|
||||
old "{b}Note:{/b} Microsoft's DirectX web setup program will, by default, install the Bing toolbar. If you do not want this toolbar, uncheck the appropriate box."
|
||||
new "{b}ملاحظة:{/b} مايكروسوفت دايركت أكس يقوم تلقائياً بتنصيب شريط بينق Bing toolbar. إذا لم ترغب بذلك الرجاء القيام بإلغاء تحديد خانة الاختيار"
|
||||
|
||||
# 00gltest.rpy:204
|
||||
old "When setup finishes, please click below to restart this program."
|
||||
new "حيثن ينتهي التنصيب, الرجاء الضغط ادناه لإعادة تشغيل البرنامج"
|
||||
|
||||
# 00gltest.rpy:206
|
||||
old "Restart"
|
||||
new "إعادة تشغيل"
|
||||
|
||||
# 00gamepad.rpy:32
|
||||
old "Select Gamepad to Calibrate"
|
||||
new "الرجاء اختيار يد التحكم التي ترغب في تهيئتها"
|
||||
|
||||
# 00gamepad.rpy:35
|
||||
old "No Gamepads Available"
|
||||
new "لا توجد يد تحكم"
|
||||
|
||||
# 00gamepad.rpy:54
|
||||
old "Calibrating [name] ([i]/[total])"
|
||||
new "يتم تهيئة [name] ([i]/[total])"
|
||||
|
||||
# 00gamepad.rpy:58
|
||||
old "Press or move the [control!s] [kind]."
|
||||
new "الرجاء ضغط الزر او تحريك [control!s] [kind]."
|
||||
|
||||
# 00gamepad.rpy:66
|
||||
old "Skip (A)"
|
||||
new "تخطي (A)"
|
||||
|
||||
# 00gamepad.rpy:69
|
||||
old "Back (B)"
|
||||
new "عودة (B)"
|
||||
|
||||
# _errorhandling.rpym:495
|
||||
old "Open Traceback"
|
||||
new "قراءة التقرير"
|
||||
|
||||
# _errorhandling.rpym:497
|
||||
old "Opens the traceback.txt file in a text editor."
|
||||
new "يفتح تقرير الخطأ في برنامج الملفات النصية."
|
||||
|
||||
# _errorhandling.rpym:499
|
||||
old "Copy to Clipboard"
|
||||
new "نسخ إلى الحافظة"
|
||||
|
||||
# _errorhandling.rpym:501
|
||||
old "Copies the traceback.txt file to the clipboard."
|
||||
new "ينسخ ملف trackback.txt إلى الحافظة."
|
||||
|
||||
# _errorhandling.rpym:519
|
||||
old "An exception has occurred."
|
||||
new "حصل استثناء"
|
||||
|
||||
# _errorhandling.rpym:538
|
||||
old "Rollback"
|
||||
new "تراجع"
|
||||
|
||||
# _errorhandling.rpym:540
|
||||
old "Attempts a roll back to a prior time, allowing you to save or choose a different choice."
|
||||
new "يقوم بالتراجع لنقطة سابقة لكي تستطيع اختيار شيء آخر"
|
||||
|
||||
# _errorhandling.rpym:543
|
||||
old "Ignore"
|
||||
new "تجاهل"
|
||||
|
||||
# _errorhandling.rpym:545
|
||||
old "Ignores the exception, allowing you to continue. This often leads to additional errors."
|
||||
new "يتجاهل الاستثناء مما يستمح لك بالاستمرار. قد يسبب هذا المزيد من الاخطاء."
|
||||
|
||||
# _errorhandling.rpym:548
|
||||
old "Reload"
|
||||
new "إعادة المحاولة"
|
||||
|
||||
# _errorhandling.rpym:550
|
||||
old "Reloads the game from disk, saving and restoring game state if possible."
|
||||
new "يعيد تشغيل اللعبة من القرص الصلب, مع محاولة استعمادة آخر نقطة وحفظها عند الإستطاعة."
|
||||
|
||||
# _errorhandling.rpym:560
|
||||
old "Quits the game."
|
||||
new "يخرج من اللعبة."
|
||||
|
||||
# _errorhandling.rpym:582
|
||||
old "Parsing the script failed."
|
||||
new "حصل خطأ أثناء تشغيل النص."
|
||||
|
||||
# _errorhandling.rpym:606
|
||||
old "Open Parse Errors"
|
||||
new "يفتح قائمة اخطاء التشغيل."
|
||||
|
||||
# _errorhandling.rpym:608
|
||||
old "Opens the errors.txt file in a text editor."
|
||||
new "يفتح ملف errors.txt في برنامج الملفات النصية"
|
||||
|
||||
# _errorhandling.rpym:612
|
||||
old "Copies the errors.txt file to the clipboard."
|
||||
new "Copies the errors.txt file to the clipboard."
|
||||
|
||||
@@ -1,411 +0,0 @@
|
||||
|
||||
translate arabic strings:
|
||||
|
||||
# gui.rpy:2
|
||||
old "## Initialization"
|
||||
new "## تتم التهيئة الآن"
|
||||
|
||||
# gui.rpy:5
|
||||
old "## The init offset statement causes the init code in this file to run before init code in any other file."
|
||||
new "## The init offset statement causes the init code in this file to run before init code in any other file."
|
||||
|
||||
# gui.rpy:9
|
||||
old "## Calling gui.init resets the styles to sensible default values, and sets the width and height of the game."
|
||||
new "## Calling gui.init resets the styles to sensible default values, and sets the width and height of the game."
|
||||
|
||||
# gui.rpy:21
|
||||
old "## Colors"
|
||||
new "## الألوان"
|
||||
|
||||
# gui.rpy:23
|
||||
old "## The colors of text in the interface."
|
||||
new "## ألوان الخطوط الظاهرة في الواجهة."
|
||||
|
||||
# gui.rpy:25
|
||||
old "## An accent color used throughout the interface to label and highlight text."
|
||||
new "## لون إضافي ينطبق على الوسوم و العناوين الرئيسية في اماكن مختلفة من الواجهة."
|
||||
|
||||
# gui.rpy:29
|
||||
old "## The color used for a text button when it is neither selected nor hovered."
|
||||
new "## اللون المطلوب للأزرار حين لا تكون قيد الاستعمال ولا مظللة."
|
||||
|
||||
# gui.rpy:32
|
||||
old "## The small color is used for small text, which needs to be brighter/darker to achieve the same effect."
|
||||
new "## اللون الصغير هو اللون الذي ينطبق على الخطوط الصغيرة, والتي تحتاج ان تكون ذات لون فاتح او داكن اكثر مما حولها لتظهر بشكل واضح."
|
||||
|
||||
# gui.rpy:36
|
||||
old "## The color that is used for buttons and bars that are hovered."
|
||||
new "## اللون الذي ينطبق على الازرار و اشرطة التمرير عند تظليلها بالمؤشر."
|
||||
|
||||
# gui.rpy:39
|
||||
old "## The color used for a text button when it is selected but not focused. A button is selected if it is the current screen or preference value."
|
||||
new "## اللون الذي ينطبق على الزر المفعّل الآن لكن ليس حين يكون مظللا. تفعيل الزر هو ما يظهر عندما تكون في الصفحه الخاصة به او الصفحة التي يؤدي إليها."
|
||||
|
||||
# gui.rpy:43
|
||||
old "## The color used for a text button when it cannot be selected."
|
||||
new "## اللون الخاص بالزر حين يكون غير قابل للضغط."
|
||||
|
||||
# gui.rpy:46
|
||||
old "## Colors used for the portions of bars that are not filled in. These are not used directly, but are used when re-generating bar image files."
|
||||
new "## اللون الذي يتم استعماله لملء الفراغات في اشرطة التمرير. هذا اللون لا يظهر فعليا, لكن يتم استعماله عند اعادة تكوين الصور الخاصة باشرطة التمرير."
|
||||
|
||||
# gui.rpy:51
|
||||
old "## The colors used for dialogue and menu choice text."
|
||||
new "## اللون الخاص بالنص عند الحوار و في ازرار القائمة الرئيسية."
|
||||
|
||||
# gui.rpy:56
|
||||
old "## Fonts and Font Sizes"
|
||||
new "##نوع الخط و حجمه."
|
||||
|
||||
# gui.rpy:58
|
||||
old "## The font used for in-game text."
|
||||
new "##نوع الخط المستخدم داخل نص اللعبة."
|
||||
|
||||
# gui.rpy:61
|
||||
old "## The font used for character names."
|
||||
new "##نوع الخط المستخدم في أسماء الشخصيات."
|
||||
|
||||
# gui.rpy:64
|
||||
old "## The font used for out-of-game text."
|
||||
new "##نوع الخط المستخدم في النصوص خارج الحوار في اللعبة."
|
||||
|
||||
# gui.rpy:67
|
||||
old "## The size of normal dialogue text."
|
||||
new "##حجم الخط المستخدم في الحوار."
|
||||
|
||||
# gui.rpy:70
|
||||
old "## The size of character names."
|
||||
new "##حجم الخط المستخدم لأسماء الشخصيات."
|
||||
|
||||
# gui.rpy:73
|
||||
old "## The size of text in the game's user interface."
|
||||
new "##حجم الخط المستخدم في واجهة اللعبة التشغيلية."
|
||||
|
||||
# gui.rpy:76
|
||||
old "## The size of labels in the game's user interface."
|
||||
new "## حجم العناوين الرئيسية في واجهة اللعبة التشغيلية."
|
||||
|
||||
# gui.rpy:79
|
||||
old "## The size of text on the notify screen."
|
||||
new "##حجم الخط المستخدم في صفحة الملاحظات."
|
||||
|
||||
# gui.rpy:82
|
||||
old "## The size of the game's title."
|
||||
new "## حجم الخط المستخدم لعنوان اللعبة الرئيسي."
|
||||
|
||||
# gui.rpy:86
|
||||
old "## Main and Game Menus"
|
||||
new "## القائمة الرئيسية و قائمة اللعبة."
|
||||
|
||||
# gui.rpy:88
|
||||
old "## The images used for the main and game menus."
|
||||
new "## الصور المستخدمة في القائمة الرئيسية و قوائم اللعبة."
|
||||
|
||||
# gui.rpy:92
|
||||
old "## Should we show the name and version of the game?"
|
||||
new "## هل سنقوم بعرض اسم و نسخه اللعبة الحالية؟"
|
||||
|
||||
# gui.rpy:96
|
||||
old "## Dialogue"
|
||||
new "## الحوار"
|
||||
|
||||
# gui.rpy:98
|
||||
old "## These variables control how dialogue is displayed on the screen one line at a time."
|
||||
new "## هذه المتغيرات تقوم بتحديد طريقة عرض الحوار الظاهر على الشاشة سطراً سطراً."
|
||||
|
||||
# gui.rpy:101
|
||||
old "## The height of the textbox containing dialogue."
|
||||
new "## ارتفاع الصندوق الذي يحتوي على النصوص."
|
||||
|
||||
# gui.rpy:104
|
||||
old "## The placement of the textbox vertically on the screen. 0.0 is the top, 0.5 is center, and 1.0 is the bottom."
|
||||
new "## مكان صندوق الحوار عموديا على الشاشة. 0.0 يضعه في الأعلى, 0.5 في المنتصف, و 1.0 يضع الصندوق في أسفل الشاشة."
|
||||
|
||||
# gui.rpy:109
|
||||
old "## The placement of the speaking character's name, relative to the textbox. These can be a whole number of pixels from the left or top, or 0.5 to center."
|
||||
new "##مكان الخانة الخاصة بعرض اسم الشخصية و تناسبها مع صندوق الحوار الرئيسي. يمكن وضع ارقام تعبر عن عدد البكسلات بينها و بين الجزء العلوي الأيسر من الشاشة, او استعمال نسبة مثل 0.5 لوضعها في المنتصف."
|
||||
|
||||
# gui.rpy:114
|
||||
old "## The horizontal alignment of the character's name. This can be 0.0 for left-aligned, 0.5 for centered, and 1.0 for right-aligned."
|
||||
new "##هذا يحدد مكان ظهور اسم الشخصية, يمكن ان يكون 0.0 ليقارب اليسار, 0.5 ليكون في المنتصف, و 1.0 ليقارب اليمين."
|
||||
|
||||
# gui.rpy:118
|
||||
old "## The width, height, and borders of the box containing the character's name, or None to automatically size it."
|
||||
new "## طول و عرض و ارتفاع الصندوق الذي يحتوي على اسم الشخصية, او None ليتم استعمال حجم قياسي."
|
||||
|
||||
# gui.rpy:123
|
||||
old "## The borders of the box containing the character's name, in left, top, right, bottom order."
|
||||
new "## حجم الخط الذي يحيط بصندوق اسم الشخصية, الاحجام بالترتيب هي اليسار, الأعلى, اليمين, و الأسفل."
|
||||
|
||||
# gui.rpy:127
|
||||
old "## If True, the background of the namebox will be tiled, if False, the background of the namebox will be scaled."
|
||||
new "## عندما يتم اختيار True سيتم تكرار الخلفية حتى تغطي الصندوق, عند اختيار False سيتم تمديد الصورة لتغطي الصندوق."
|
||||
|
||||
# gui.rpy:132
|
||||
old "## The placement of dialogue relative to the textbox. These can be a whole number of pixels relative to the left or top side of the textbox, or 0.5 to center."
|
||||
new "## مكان نص الحوار و تناسبه مع صندوق الحوار الرئيسي. يمكن وضع ارقام تعبر عن عدد البكسلات بينها و بين الجزء العلوي الأيسر من الشاشة, او استعمال نسبة مثل 0.5 لوضعه في المنتصف."
|
||||
|
||||
# gui.rpy:138
|
||||
old "## The maximum width of dialogue text, in pixels."
|
||||
new "## عدد البكسلات القصوى التي يمكن ان يتمدد إليها صندوق الحوار بالعرض."
|
||||
|
||||
# gui.rpy:141
|
||||
old "## The horizontal alignment of the dialogue text. This can be 0.0 for left-aligned, 0.5 for centered, and 1.0 for right-aligned."
|
||||
new "## اتجاه النص عرضياً: يمكن استعمال 0.0 ليقارب اليسار, 0.5 ليكون في المنتصف, و 1.0 ليقارب اليمين."
|
||||
|
||||
# gui.rpy:146
|
||||
old "## Buttons"
|
||||
new "## أزرار"
|
||||
|
||||
# gui.rpy:148
|
||||
old "## These variables, along with the image files in gui/button, control aspects of how buttons are displayed."
|
||||
new "## هذه المتغيرات تتشارك مع ملفات الصور في gui/button في تحديد مظهر الأزرار."
|
||||
|
||||
# gui.rpy:151
|
||||
old "## The width and height of a button, in pixels. If None, Ren'Py computes a size."
|
||||
new "## طول و عرض الزر بالبكسل. لو تم استعمال None سيقوم رينباي باختيار حجم مناسب."
|
||||
|
||||
# gui.rpy:155
|
||||
old "## The borders on each side of the button, in left, top, right, bottom order."
|
||||
new "## حجم الخط الذي يحيط بصندوق الزر, الاحجام بالترتيب هي اليسار, الأعلى, اليمين, و الأسفل."
|
||||
|
||||
# gui.rpy:158
|
||||
old "## If True, the background image will be tiled. If False, the background image will be linearly scaled."
|
||||
new "## عندما يتم اختيار True سيتم تكرار الخلفية حتى تغطي المساحه بأكملها, عند اختيار False سيتم تمديد الصورة."
|
||||
|
||||
# gui.rpy:162
|
||||
old "## The font used by the button."
|
||||
new "## الخط المستخدم في الزر."
|
||||
|
||||
# gui.rpy:165
|
||||
old "## The size of the text used by the button."
|
||||
new "## حجم الخط المستخدم في الزر."
|
||||
|
||||
# gui.rpy:179
|
||||
old "## These variables override settings for different kinds of buttons. Please see the gui documentation for the kinds of buttons available, and what each is used for."
|
||||
new "## هذه المتغيرات تقوم بتجاهل الخيارات التي تنطبق على انواع الازرار الأخرى. الرجاء قراءة دليل المستخدم الخاص بالواجهة للتعرف على انواع الازرار المتاحه و كيفية استعمالها."
|
||||
|
||||
# gui.rpy:183
|
||||
old "## These customizations are used by the default interface:"
|
||||
new "## هذه الخيارات تنطبق على الواجهة القياسية:"
|
||||
|
||||
# gui.rpy:198
|
||||
old "## You can also add your own customizations, by adding properly-named variables. For example, you can uncomment the following line to set the width of a navigation button."
|
||||
new "## يمكنك اضافة تغييرات يدويا عن طريق اضافة متغيرات بأسماء مميزة. مثلا, يمكنك ازالة اقتباسات الحماية ## عن السطر التالي لتغيير طول و عرض الازرار الخاصة بالقائمة."
|
||||
|
||||
# gui.rpy:205
|
||||
old "## Choice Buttons"
|
||||
new "## أزرار الخيارات"
|
||||
|
||||
# gui.rpy:207
|
||||
old "## Choice buttons are used in the in-game menus."
|
||||
new "## أزرار الخيارات يتم استعمالها داخل قوائم اللعبة."
|
||||
|
||||
# gui.rpy:220
|
||||
old "## File Slot Buttons"
|
||||
new "## أزرار قائمة الحفظ."
|
||||
|
||||
# gui.rpy:222
|
||||
old "## A file slot button is a special kind of button. It contains a thumbnail image, and text describing the contents of the save slot. A save slot uses image files in gui/button, like the other kinds of buttons."
|
||||
new "## زر خانة الحفظ هو زر مميز عن غيره لانه يعرض صورة رمزية و نص يشرح محتويات الخانة. زر الحفظ يستعمل صور من مجلد الواجهة مثل باقي الأزرار في gui/button."
|
||||
|
||||
# gui.rpy:226
|
||||
old "## The save slot button."
|
||||
new "## زر خانة الحفظ."
|
||||
|
||||
# gui.rpy:234
|
||||
old "## The width and height of thumbnails used by the save slots."
|
||||
new "## طول و عرض الصورة الرمزية لخانة الحفظ."
|
||||
|
||||
# gui.rpy:238
|
||||
old "## The number of columns and rows in the grid of save slots."
|
||||
new "## عدد الخانات الطولية و العرضية التي يمكن الحفظ فيها."
|
||||
|
||||
# gui.rpy:243
|
||||
old "## Positioning and Spacing"
|
||||
new "## مكان الخانات و المساحه بينها."
|
||||
|
||||
# gui.rpy:245
|
||||
old "## These variables control the positioning and spacing of various user interface elements."
|
||||
new "## هذه المتغيرات تحدد المساحة و مكان عرض عدة عناصر خاصة بالواجهة."
|
||||
|
||||
# gui.rpy:248
|
||||
old "## The position of the left side of the navigation buttons, relative to the left side of the screen."
|
||||
new "## مكان الجزء الأيسر من ازرار القوائم وتناسبه مع الحد الأيسر من الشاشة."
|
||||
|
||||
# gui.rpy:252
|
||||
old "## The vertical position of the skip indicator."
|
||||
new "## موقع مؤشر استمرار الحوار رأسياً."
|
||||
|
||||
# gui.rpy:255
|
||||
old "## The vertical position of the notify screen."
|
||||
new "## موقع شاشة التنبيهات رأسياً."
|
||||
|
||||
# gui.rpy:258
|
||||
old "## The spacing between menu choices."
|
||||
new "## الفراغات بين خيارات القوائم."
|
||||
|
||||
# gui.rpy:261
|
||||
old "## Buttons in the navigation section of the main and game menus."
|
||||
new "## الأزرار في صفحات الخيارات و القائمة الرئيسية."
|
||||
|
||||
# gui.rpy:264
|
||||
old "## Controls the amount of spacing between preferences."
|
||||
new "## يتحكم في المساحه بين الخيارات."
|
||||
|
||||
# gui.rpy:267
|
||||
old "## Controls the amount of spacing between preference buttons."
|
||||
new "## يتحكم في المساحه بين ازرار الخيارات."
|
||||
|
||||
# gui.rpy:270
|
||||
old "## The spacing between file page buttons."
|
||||
new "## المساحه بين ازرار صفحات الحفظ."
|
||||
|
||||
# gui.rpy:273
|
||||
old "## The spacing between file slots."
|
||||
new "## المساحه بين خانات الحفظ."
|
||||
|
||||
# gui.rpy:277
|
||||
old "## Frames"
|
||||
new "## الإطارات"
|
||||
|
||||
# gui.rpy:279
|
||||
old "## These variables control the look of frames that can contain user interface components when an overlay or window is not present."
|
||||
new "## هذه المتغيرات تحدد شكل الإطارات الخاصة بعناصر الواجهة عند انعدام نافذة تتحكم بظهورها."
|
||||
|
||||
# gui.rpy:282
|
||||
old "## Generic frames that are introduced by player code."
|
||||
new "## إطارات قياسية يتم تقديمها حسب برمجة اللاعب."
|
||||
|
||||
# gui.rpy:285
|
||||
old "## The frame that is used as part of the confirm screen."
|
||||
new "## الإطار الخاص بصفحة التأكيد."
|
||||
|
||||
# gui.rpy:288
|
||||
old "## The frame that is used as part of the skip screen."
|
||||
new "## الإطار المستخدم في صفحة التسريع."
|
||||
|
||||
# gui.rpy:291
|
||||
old "## The frame that is used as part of the notify screen."
|
||||
new "## الإطار المستخدم في صفحة التنبيهات."
|
||||
|
||||
# gui.rpy:294
|
||||
old "## Should frame backgrounds be tiled?"
|
||||
new "## هل تريد ان تكون خلفية الإطار متكررة؟"
|
||||
|
||||
# gui.rpy:298
|
||||
old "## Bars, Scrollbars, and Sliders"
|
||||
new "## أشرطة التمرير, الأشرطة, و العناصر المنزلقة."
|
||||
|
||||
# gui.rpy:300
|
||||
old "## These control the look and size of bars, scrollbars, and sliders."
|
||||
new "## هذه تتحكم بشكل و حجم الأشرطة والعناصر المنزلقة."
|
||||
|
||||
# gui.rpy:302
|
||||
old "## The default GUI only uses sliders and vertical scrollbars. All of the other bars are only used in creator-written code."
|
||||
new "## الواجهة القياسية لا تستخدم سوى العناصر المنزلقة و الأشرطة رأسياً, الأشرطة الباقية لا يتم استعمالها إلا عندما يقرر ذلك صانع اللعبة."
|
||||
|
||||
# gui.rpy:305
|
||||
old "## The height of horizontal bars, scrollbars, and sliders. The width of vertical bars, scrollbars, and sliders."
|
||||
new "## ارتفاع الأشرطة و العناصر المنزلقة الأفقية. سماكة الأشرطة و العناصر المنزلقة الرأسية."
|
||||
|
||||
# gui.rpy:311
|
||||
old "## True if bar images should be tiled. False if they should be linearly scaled."
|
||||
new "## استعمل True لتكون الصورة المستخدمة في الأشرطة المنزلقة متكررة, False ليتم تمديدها على حجم الشريط."
|
||||
|
||||
# gui.rpy:316
|
||||
old "## Horizontal borders."
|
||||
new "## الحاشية بالعرض."
|
||||
|
||||
# gui.rpy:321
|
||||
old "## Vertical borders."
|
||||
new "## الحاضية بالطول."
|
||||
|
||||
# gui.rpy:326
|
||||
old "## What to do with unscrollable scrollbars in the gui. \"hide\" hides them, while None shows them."
|
||||
new "## ماذا سنفعل بالأشرطة التي لا يمكن سحبها؟ يمكن استعمال hide لإخفائها, او None لتكون ظاهره في كل الاوقات."
|
||||
|
||||
# gui.rpy:331
|
||||
old "## History"
|
||||
new "## الحوار السابق"
|
||||
|
||||
# gui.rpy:333
|
||||
old "## The history screen displays dialogue that the player has already dismissed."
|
||||
new "## صفحة الحوار السابق تعرض الحوارات التي سبق للاعب قرائتها."
|
||||
|
||||
# gui.rpy:335
|
||||
old "## The number of blocks of dialogue history Ren'Py will keep."
|
||||
new "## عدد فقاعات الحوار التي سيحتفظ بها رينباي في صفحة الحوار السابق."
|
||||
|
||||
# gui.rpy:338
|
||||
old "## The height of a history screen entry, or None to make the height variable at the cost of performance."
|
||||
new "## ارتفاع صفحة عرض الحوار السابق, او None ليكون الإرتفاع متغير مع ملاحظة انه يؤثر في الأداء العام."
|
||||
|
||||
# gui.rpy:342
|
||||
old "## The position, width, and alignment of the label giving the name of the speaking character."
|
||||
new "## الموقع, العرض, و اتجاه النص الخاص باسم الشخصية المعروض."
|
||||
|
||||
# gui.rpy:349
|
||||
old "## The position, width, and alignment of the dialogue text."
|
||||
new "## الموقع, العرض و اتجاه النص الخاص بالحوار المعروض."
|
||||
|
||||
# gui.rpy:356
|
||||
old "## NVL-Mode"
|
||||
new "## NVL-Mode أسلوب عرض الحوار الروائي."
|
||||
|
||||
# gui.rpy:358
|
||||
old "## The NVL-mode screen displays the dialogue spoken by NVL-mode characters."
|
||||
new "## شاشة عرض الأسلوب الروائي تعرض ما قالته شخصيات تم تخصيصها للحوار الروائي."
|
||||
|
||||
# gui.rpy:360
|
||||
old "## The borders of the background of the NVL-mode background window."
|
||||
new "## حاشية الخلفية المستخدمة في نافذة الحوار الخاصة بالحوار الروائي."
|
||||
|
||||
# gui.rpy:363
|
||||
old "## The height of an NVL-mode entry. Set this to None to have the entries dynamically adjust height."
|
||||
new "## إرتفاع النافذة الخاصة بعرض الحوار الروائي. استعمل None لتناسب النافذة الحوار المعروض لكل فقاعه حوار."
|
||||
|
||||
# gui.rpy:367
|
||||
old "## The spacing between NVL-mode entries when gui.nvl_height is None, and between NVL-mode entries and an NVL-mode menu."
|
||||
new "## المساحه بين العناصر عندما يكون النظام الروائي يستعمل متغير None في سطر gui.nvl_height و المساحه بين اجزاء الحوار والقائمة في الحوار الروائي."
|
||||
|
||||
# gui.rpy:384
|
||||
old "## The position, width, and alignment of nvl_thought text (the text said by the nvl_narrator character.)"
|
||||
new "## موقع, عرض و اتجاه النص الخاص بشخصية التفكير nvl_thought, الحوار المنطوق من قبل شخصية nvl_narrator تم تحديدها في ملف حوار اللعبة."
|
||||
|
||||
# gui.rpy:391
|
||||
old "## The position of nvl menu_buttons."
|
||||
new "## موقع ازرار القائمة في nvl menu_buttons."
|
||||
|
||||
# gui.rpy:403
|
||||
old "## This increases the size of the quick buttons to make them easier to touch on tablets and phones."
|
||||
new "## يقوم بتكبير المساحه الخاصة بازرار الاختصارات ليكون استعمالها اسهل على شاشات الاجهزة المحمولة و التابلت."
|
||||
|
||||
# gui.rpy:409
|
||||
old "## This changes the size and spacing of various GUI elements to ensure they are easily visible on phones."
|
||||
new "## يقوم بتغيير المساحات بين العناصر في اماكن مختلفة من الواجهة لتكون اسهل في الرؤية و الاستعمال على شاشات المحمول و التابلت."
|
||||
|
||||
# gui.rpy:413
|
||||
old "## Font sizes."
|
||||
new "## حجم النص."
|
||||
|
||||
# gui.rpy:421
|
||||
old "## Adjust the location of the textbox."
|
||||
new "## تحديد مكان مربع الحوار."
|
||||
|
||||
# gui.rpy:427
|
||||
old "## Change the size and spacing of items in the game menu."
|
||||
new "## تغيير الحجم و المساحه بين العناصر في قائمة اللعبة."
|
||||
|
||||
# gui.rpy:436
|
||||
old "## File button layout."
|
||||
new "## ترتيب ازرار الملفات."
|
||||
|
||||
# gui.rpy:440
|
||||
old "## NVL-mode."
|
||||
new "## NVL-mode نظام الحوار الروائي."
|
||||
|
||||
# gui.rpy:456
|
||||
old "## Quick buttons."
|
||||
new "## إختصارات."
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
|
||||
translate arabic strings:
|
||||
|
||||
# _layout/classic_joystick_preferences.rpym:94
|
||||
old "Joystick Mapping"
|
||||
new "خيارات عصى التحكم"
|
||||
|
||||
# _layout/classic_load_save.rpym:138
|
||||
old "Empty Slot."
|
||||
new "خانة فارغة"
|
||||
|
||||
# _layout/classic_load_save.rpym:170
|
||||
old "a"
|
||||
new "a"
|
||||
|
||||
# _layout/classic_load_save.rpym:179
|
||||
old "q"
|
||||
new "q"
|
||||
|
||||
# _compat/gamemenu.rpym:355
|
||||
old "Previous"
|
||||
new "السابق"
|
||||
|
||||
# _compat/gamemenu.rpym:362
|
||||
old "Next"
|
||||
new "التالي"
|
||||
|
||||
@@ -1,195 +0,0 @@
|
||||
|
||||
translate arabic strings:
|
||||
|
||||
# options.rpy:1
|
||||
old "## This file contains options that can be changed to customize your game."
|
||||
new "## هذا الملف يحتوي على الخيارات التي تستطيع تعديلها لتغيير محتويات لعبتك."
|
||||
|
||||
# options.rpy:4
|
||||
old "## Lines beginning with two '#' marks are comments, and you shouldn't uncomment them. Lines beginning with a single '#' mark are commented-out code, and you may want to uncomment them when appropriate."
|
||||
new "## الأسطر التي تبدأ بهذا الشعار مرتين'#' هي أسطر مقتبسه و لا يفترض عليك تغييرها. الأسطر التي تحتوي على '#' واحده هي أسطر برمجة يمكنك الغاء الاقتباس عندها لتفعيلها."
|
||||
|
||||
# options.rpy:10
|
||||
old "## Basics"
|
||||
new "## الأساسيات"
|
||||
|
||||
# options.rpy:12
|
||||
old "## A human-readable name of the game. This is used to set the default window title, and shows up in the interface and error reports."
|
||||
new "## الإسم الرسمي للعبه. يتم استعمال هذا السطر كإسم النافذة و يظهرفي واجهة النظام التشغيلية و ملفات حصر الأخطاء."
|
||||
|
||||
# options.rpy:15
|
||||
old "## The _() surrounding the string marks it as eligible for translation."
|
||||
new "## رمز _() حول النصوص يجعلها قابلة للترجمة."
|
||||
|
||||
# options.rpy:17
|
||||
old "Ren'Py 7 Default GUI"
|
||||
new "الواجهة التشغيلية القياسية لـ رينباي7"
|
||||
|
||||
# options.rpy:20
|
||||
old "## Determines if the title given above is shown on the main menu screen. Set this to False to hide the title."
|
||||
new "## يحدد إذا ما تم إظهار العنوان في القائمة الرئيسية. لإخفاء العنوان اجعله False."
|
||||
|
||||
# options.rpy:26
|
||||
old "## The version of the game."
|
||||
new "## نسخه اللعبة."
|
||||
|
||||
# options.rpy:31
|
||||
old "## Text that is placed on the game's about screen. To insert a blank line between paragraphs, write \\n\\n."
|
||||
new "## النص الظاهر على شاشة اللعبه. لإظهار سطر فارغ بين اجزاء النص اكتب \\n\\n."
|
||||
|
||||
# options.rpy:37
|
||||
old "## A short name for the game used for executables and directories in the built distribution. This must be ASCII-only, and must not contain spaces, colons, or semicolons."
|
||||
new "## اسم مختصر للعبه يتم استعماله في ملفات التشغيل و نسخ النشر. يجب ان يحتوي على احرف انجليزية فقط, دون فراغات ولا فواصل ولا فواصل منقوطة."
|
||||
|
||||
# options.rpy:44
|
||||
old "## Sounds and music"
|
||||
new "## الصوت و الموسيقى"
|
||||
|
||||
# options.rpy:46
|
||||
old "## These three variables control which mixers are shown to the player by default. Setting one of these to False will hide the appropriate mixer."
|
||||
new "## هذه المتغيرات الثلاثة تحدد معالجات الأصوات الظاهره بشكل قياسي للاعب. إلغاء أحدها يجعله يختفي من الواجهة عن طريق اختيار False."
|
||||
|
||||
# options.rpy:55
|
||||
old "## To allow the user to play a test sound on the sound or voice channel, uncomment a line below and use it to set a sample sound to play."
|
||||
new "## يسمح للاعب ان يسمع صوتا لاختبار ارتفاع الصوت او النطق, إمسح الاقتباس عن السطر التالي ليتم تفعيل زر العينة الصوتية."
|
||||
|
||||
# options.rpy:62
|
||||
old "## Uncomment the following line to set an audio file that will be played while the player is at the main menu. This file will continue playing into the game, until it is stopped or another file is played."
|
||||
new "## Uncomment the following line to set an audio file that will be played while the player is at the main menu. This file will continue playing into the game, until it is stopped or another file is played."
|
||||
|
||||
# options.rpy:69
|
||||
old "## Transitions"
|
||||
new "## الإنتقال"
|
||||
|
||||
# options.rpy:71
|
||||
old "## These variables set transitions that are used when certain events occur. Each variable should be set to a transition, or None to indicate that no transition should be used."
|
||||
new "## These variables set transitions that are used when certain events occur. Each variable should be set to a transition, or None to indicate that no transition should be used."
|
||||
|
||||
# options.rpy:75
|
||||
old "## Entering or exiting the game menu."
|
||||
new "## الدخول او الخروج من قائمة اللعبة."
|
||||
|
||||
# options.rpy:81
|
||||
old "## A transition that is used after a game has been loaded."
|
||||
new "## الإنتقال الذي يحصل بعد ان تنتهي اللعبة من الإقلاع."
|
||||
|
||||
# options.rpy:86
|
||||
old "## Used when entering the main menu after the game has ended."
|
||||
new "## يظهر عند الدخول إلى القائمة الرئيسية بعد انتهاء اللعبة."
|
||||
|
||||
# options.rpy:91
|
||||
old "## A variable to set the transition used when the game starts does not exist. Instead, use a with statement after showing the initial scene."
|
||||
new "## A variable to set the transition used when the game starts does not exist. Instead, use a with statement after showing the initial scene."
|
||||
|
||||
# options.rpy:96
|
||||
old "## Window management"
|
||||
new "## خيارات النافذة"
|
||||
|
||||
# options.rpy:98
|
||||
old "## This controls when the dialogue window is displayed. If \"show\", it is always displayed. If \"hide\", it is only displayed when dialogue is present. If \"auto\", the window is hidden before scene statements and shown again once dialogue is displayed."
|
||||
new "## This controls when the dialogue window is displayed. If \"show\", it is always displayed. If \"hide\", it is only displayed when dialogue is present. If \"auto\", the window is hidden before scene statements and shown again once dialogue is displayed."
|
||||
|
||||
# options.rpy:103
|
||||
old "## After the game has started, this can be changed with the \"window show\", \"window hide\", and \"window auto\" statements."
|
||||
new "## After the game has started, this can be changed with the \"window show\", \"window hide\", and \"window auto\" statements."
|
||||
|
||||
# options.rpy:109
|
||||
old "## Transitions used to show and hide the dialogue window"
|
||||
new "## الإنتقالات البصرية المستخدمه عند إظهار و إخفاء مربع الحوار"
|
||||
|
||||
# options.rpy:115
|
||||
old "## Preference defaults"
|
||||
new "## الخيارات القياسية"
|
||||
|
||||
# options.rpy:117
|
||||
old "## Controls the default text speed. The default, 0, is infinite, while any other number is the number of characters per second to type out."
|
||||
new "## Controls the default text speed. The default, 0, is infinite, while any other number is the number of characters per second to type out."
|
||||
|
||||
# options.rpy:123
|
||||
old "## The default auto-forward delay. Larger numbers lead to longer waits, with 0 to 30 being the valid range."
|
||||
new "## The default auto-forward delay. Larger numbers lead to longer waits, with 0 to 30 being the valid range."
|
||||
|
||||
# options.rpy:129
|
||||
old "## Save directory"
|
||||
new "## مجلد الحفظ"
|
||||
|
||||
# options.rpy:131
|
||||
old "## Controls the platform-specific place Ren'Py will place the save files for this game. The save files will be placed in:"
|
||||
new "## Controls the platform-specific place Ren'Py will place the save files for this game. The save files will be placed in:"
|
||||
|
||||
# options.rpy:134
|
||||
old "## Windows: %APPDATA\\RenPy\\<config.save_directory>"
|
||||
new "## Windows: %APPDATA\\RenPy\\<config.save_directory>"
|
||||
|
||||
# options.rpy:136
|
||||
old "## Macintosh: $HOME/Library/RenPy/<config.save_directory>"
|
||||
new "## Macintosh: $HOME/Library/RenPy/<config.save_directory>"
|
||||
|
||||
# options.rpy:138
|
||||
old "## Linux: $HOME/.renpy/<config.save_directory>"
|
||||
new "## Linux: $HOME/.renpy/<config.save_directory>"
|
||||
|
||||
# options.rpy:140
|
||||
old "## This generally should not be changed, and if it is, should always be a literal string, not an expression."
|
||||
new "## This generally should not be changed, and if it is, should always be a literal string, not an expression."
|
||||
|
||||
# options.rpy:146
|
||||
old "## Icon ########################################################################'"
|
||||
new "## أيقونة ########################################################################'"
|
||||
|
||||
# options.rpy:148
|
||||
old "## The icon displayed on the taskbar or dock."
|
||||
new "## الأيقونة الظاهرة في شريط البرامج."
|
||||
|
||||
# options.rpy:153
|
||||
old "## Build configuration"
|
||||
new "## معلومات ملف النشر"
|
||||
|
||||
# options.rpy:155
|
||||
old "## This section controls how Ren'Py turns your project into distribution files."
|
||||
new "## هذا الجزء يتحكم بالمعلومات التي يستعملها رينباي لتحويل مشروعك إلى ملفات يمكن نشرها."
|
||||
|
||||
# options.rpy:160
|
||||
old "## The following functions take file patterns. File patterns are case- insensitive, and matched against the path relative to the base directory, with and without a leading /. If multiple patterns match, the first is used."
|
||||
new "## The following functions take file patterns. File patterns are case- insensitive, and matched against the path relative to the base directory, with and without a leading /. If multiple patterns match, the first is used."
|
||||
|
||||
# options.rpy:165
|
||||
old "## In a pattern:"
|
||||
new "## In a pattern:"
|
||||
|
||||
# options.rpy:167
|
||||
old "## / is the directory separator."
|
||||
new "## / is the directory separator."
|
||||
|
||||
# options.rpy:169
|
||||
old "## * matches all characters, except the directory separator."
|
||||
new "## * matches all characters, except the directory separator."
|
||||
|
||||
# options.rpy:171
|
||||
old "## ** matches all characters, including the directory separator."
|
||||
new "## ** matches all characters, including the directory separator."
|
||||
|
||||
# options.rpy:173
|
||||
old "## For example, \"*.txt\" matches txt files in the base directory, \"game/**.ogg\" matches ogg files in the game directory or any of its subdirectories, and \"**.psd\" matches psd files anywhere in the project."
|
||||
new "## For example, \"*.txt\" matches txt files in the base directory, \"game/**.ogg\" matches ogg files in the game directory or any of its subdirectories, and \"**.psd\" matches psd files anywhere in the project."
|
||||
|
||||
# options.rpy:177
|
||||
old "## Classify files as None to exclude them from the built distributions."
|
||||
new "## Classify files as None to exclude them from the built distributions."
|
||||
|
||||
# options.rpy:185
|
||||
old "## To archive files, classify them as 'archive'."
|
||||
new "## To archive files, classify them as 'archive'."
|
||||
|
||||
# options.rpy:190
|
||||
old "## Files matching documentation patterns are duplicated in a mac app build, so they appear in both the app and the zip file."
|
||||
new "## Files matching documentation patterns are duplicated in a mac app build, so they appear in both the app and the zip file."
|
||||
|
||||
# options.rpy:196
|
||||
old "## A Google Play license key is required to download expansion files and perform in-app purchases. It can be found on the \"Services & APIs\" page of the Google Play developer console."
|
||||
new "## A Google Play license key is required to download expansion files and perform in-app purchases. It can be found on the \"Services & APIs\" page of the Google Play developer console."
|
||||
|
||||
# options.rpy:203
|
||||
old "## The username and project name associated with an itch.io project, separated by a slash."
|
||||
new "## The username and project name associated with an itch.io project, separated by a slash."
|
||||
|
||||
@@ -1,642 +0,0 @@
|
||||
|
||||
translate arabic strings:
|
||||
|
||||
# screens.rpy:9
|
||||
old "## Styles"
|
||||
new "## Styles"
|
||||
|
||||
# screens.rpy:87
|
||||
old "## In-game screens"
|
||||
new "## In-game screens"
|
||||
|
||||
# screens.rpy:91
|
||||
old "## Say screen"
|
||||
new "## Say screen"
|
||||
|
||||
# screens.rpy:93
|
||||
old "## The say screen is used to display dialogue to the player. It takes two parameters, who and what, which are the name of the speaking character and the text to be displayed, respectively. (The who parameter can be None if no name is given.)"
|
||||
new "## The say screen is used to display dialogue to the player. It takes two parameters, who and what, which are the name of the speaking character and the text to be displayed, respectively. (The who parameter can be None if no name is given.)"
|
||||
|
||||
# screens.rpy:98
|
||||
old "## This screen must create a text displayable with id \"what\", as Ren'Py uses this to manage text display. It can also create displayables with id \"who\" and id \"window\" to apply style properties."
|
||||
new "## This screen must create a text displayable with id \"what\", as Ren'Py uses this to manage text display. It can also create displayables with id \"who\" and id \"window\" to apply style properties."
|
||||
|
||||
# screens.rpy:102
|
||||
old "## https://www.renpy.org/doc/html/screen_special.html#say"
|
||||
new "## https://www.renpy.org/doc/html/screen_special.html#say"
|
||||
|
||||
# screens.rpy:169
|
||||
old "## Input screen"
|
||||
new "## Input screen"
|
||||
|
||||
# screens.rpy:171
|
||||
old "## This screen is used to display renpy.input. The prompt parameter is used to pass a text prompt in."
|
||||
new "## This screen is used to display renpy.input. The prompt parameter is used to pass a text prompt in."
|
||||
|
||||
# screens.rpy:174
|
||||
old "## This screen must create an input displayable with id \"input\" to accept the various input parameters."
|
||||
new "## This screen must create an input displayable with id \"input\" to accept the various input parameters."
|
||||
|
||||
# screens.rpy:177
|
||||
old "## http://www.renpy.org/doc/html/screen_special.html#input"
|
||||
new "## http://www.renpy.org/doc/html/screen_special.html#input"
|
||||
|
||||
# screens.rpy:205
|
||||
old "## Choice screen"
|
||||
new "## Choice screen"
|
||||
|
||||
# screens.rpy:207
|
||||
old "## This screen is used to display the in-game choices presented by the menu statement. The one parameter, items, is a list of objects, each with caption and action fields."
|
||||
new "## This screen is used to display the in-game choices presented by the menu statement. The one parameter, items, is a list of objects, each with caption and action fields."
|
||||
|
||||
# screens.rpy:211
|
||||
old "## http://www.renpy.org/doc/html/screen_special.html#choice"
|
||||
new "## http://www.renpy.org/doc/html/screen_special.html#choice"
|
||||
|
||||
# screens.rpy:221
|
||||
old "## When this is true, menu captions will be spoken by the narrator. When false, menu captions will be displayed as empty buttons."
|
||||
new "## When this is true, menu captions will be spoken by the narrator. When false, menu captions will be displayed as empty buttons."
|
||||
|
||||
# screens.rpy:244
|
||||
old "## Quick Menu screen"
|
||||
new "## Quick Menu screen"
|
||||
|
||||
# screens.rpy:246
|
||||
old "## The quick menu is displayed in-game to provide easy access to the out-of-game menus."
|
||||
new "## The quick menu is displayed in-game to provide easy access to the out-of-game menus."
|
||||
|
||||
# screens.rpy:261
|
||||
old "Back"
|
||||
new "عودة"
|
||||
|
||||
# screens.rpy:262
|
||||
old "History"
|
||||
new "سجل الحوار"
|
||||
|
||||
# screens.rpy:263
|
||||
old "Skip"
|
||||
new "تخطي"
|
||||
|
||||
# screens.rpy:264
|
||||
old "Auto"
|
||||
new "تلقائي"
|
||||
|
||||
# screens.rpy:265
|
||||
old "Save"
|
||||
new "حفظ"
|
||||
|
||||
# screens.rpy:266
|
||||
old "Q.Save"
|
||||
new "حفظ سريع"
|
||||
|
||||
# screens.rpy:267
|
||||
old "Q.Load"
|
||||
new "استرجاع سريع"
|
||||
|
||||
# screens.rpy:268
|
||||
old "Prefs"
|
||||
new "خيارات"
|
||||
|
||||
# screens.rpy:271
|
||||
old "## This code ensures that the quick_menu screen is displayed in-game, whenever the player has not explicitly hidden the interface."
|
||||
new "## This code ensures that the quick_menu screen is displayed in-game, whenever the player has not explicitly hidden the interface."
|
||||
|
||||
# screens.rpy:291
|
||||
old "## Navigation screen"
|
||||
new "## Navigation screen"
|
||||
|
||||
# screens.rpy:293
|
||||
old "## This screen is included in the main and game menus, and provides navigation to other menus, and to start the game."
|
||||
new "## This screen is included in the main and game menus, and provides navigation to other menus, and to start the game."
|
||||
|
||||
# screens.rpy:308
|
||||
old "Start"
|
||||
new "إبدأ"
|
||||
|
||||
# screens.rpy:316
|
||||
old "Load"
|
||||
new "استرجاع"
|
||||
|
||||
# screens.rpy:318
|
||||
old "Preferences"
|
||||
new "خيارات"
|
||||
|
||||
# screens.rpy:322
|
||||
old "End Replay"
|
||||
new "End Replay"
|
||||
|
||||
# screens.rpy:326
|
||||
old "Main Menu"
|
||||
new "القائمة الرئيسية"
|
||||
|
||||
# screens.rpy:328
|
||||
old "About"
|
||||
new "معلومات"
|
||||
|
||||
# screens.rpy:332
|
||||
old "## Help isn't necessary or relevant to mobile devices."
|
||||
new "## Help isn't necessary or relevant to mobile devices."
|
||||
|
||||
# screens.rpy:333
|
||||
old "Help"
|
||||
new "مساعدة"
|
||||
|
||||
# screens.rpy:335
|
||||
old "## The quit button is banned on iOS and unnecessary on Android."
|
||||
new "## The quit button is banned on iOS and unnecessary on Android."
|
||||
|
||||
# screens.rpy:336
|
||||
old "Quit"
|
||||
new "خروج"
|
||||
|
||||
# screens.rpy:350
|
||||
old "## Main Menu screen"
|
||||
new "## Main Menu screen"
|
||||
|
||||
# screens.rpy:352
|
||||
old "## Used to display the main menu when Ren'Py starts."
|
||||
new "## Used to display the main menu when Ren'Py starts."
|
||||
|
||||
# screens.rpy:354
|
||||
old "## http://www.renpy.org/doc/html/screen_special.html#main-menu"
|
||||
new "## http://www.renpy.org/doc/html/screen_special.html#main-menu"
|
||||
|
||||
# screens.rpy:369
|
||||
old "## The use statement includes another screen inside this one. The actual contents of the main menu are in the navigation screen."
|
||||
new "## The use statement includes another screen inside this one. The actual contents of the main menu are in the navigation screen."
|
||||
|
||||
# screens.rpy:413
|
||||
old "## Game Menu screen"
|
||||
new "## Game Menu screen"
|
||||
|
||||
# screens.rpy:415
|
||||
old "## This lays out the basic common structure of a game menu screen. It's called with the screen title, and displays the background, title, and navigation."
|
||||
new "## This lays out the basic common structure of a game menu screen. It's called with the screen title, and displays the background, title, and navigation."
|
||||
|
||||
# screens.rpy:418
|
||||
old "## The scroll parameter can be None, or one of \"viewport\" or \"vpgrid\". This screen is intended to be used with one or more children, which are transcluded (placed) inside it."
|
||||
new "## The scroll parameter can be None, or one of \"viewport\" or \"vpgrid\". This screen is intended to be used with one or more children, which are transcluded (placed) inside it."
|
||||
|
||||
# screens.rpy:476
|
||||
old "Return"
|
||||
new "عودة"
|
||||
|
||||
# screens.rpy:539
|
||||
old "## About screen"
|
||||
new "## About screen"
|
||||
|
||||
# screens.rpy:541
|
||||
old "## This screen gives credit and copyright information about the game and Ren'Py."
|
||||
new "## This screen gives credit and copyright information about the game and Ren'Py."
|
||||
|
||||
# screens.rpy:544
|
||||
old "## There's nothing special about this screen, and hence it also serves as an example of how to make a custom screen."
|
||||
new "## There's nothing special about this screen, and hence it also serves as an example of how to make a custom screen."
|
||||
|
||||
# screens.rpy:551
|
||||
old "## This use statement includes the game_menu screen inside this one. The vbox child is then included inside the viewport inside the game_menu screen."
|
||||
new "## This use statement includes the game_menu screen inside this one. The vbox child is then included inside the viewport inside the game_menu screen."
|
||||
|
||||
# screens.rpy:561
|
||||
old "Version [config.version!t]\n"
|
||||
new "Version [config.version!t]\n"
|
||||
|
||||
# screens.rpy:563
|
||||
old "## gui.about is usually set in options.rpy."
|
||||
new "## gui.about is usually set in options.rpy."
|
||||
|
||||
# screens.rpy:567
|
||||
old "Made with {a=https://www.renpy.org/}Ren'Py{/a} [renpy.version_only].\n\n[renpy.license!t]"
|
||||
new "Made with {a=https://www.renpy.org/}Ren'Py{/a} [renpy.version_only].\n\n[renpy.license!t]"
|
||||
|
||||
# screens.rpy:570
|
||||
old "## This is redefined in options.rpy to add text to the about screen."
|
||||
new "## This is redefined in options.rpy to add text to the about screen."
|
||||
|
||||
# screens.rpy:582
|
||||
old "## Load and Save screens"
|
||||
new "## Load and Save screens"
|
||||
|
||||
# screens.rpy:584
|
||||
old "## These screens are responsible for letting the player save the game and load it again. Since they share nearly everything in common, both are implemented in terms of a third screen, file_slots."
|
||||
new "## These screens are responsible for letting the player save the game and load it again. Since they share nearly everything in common, both are implemented in terms of a third screen, file_slots."
|
||||
|
||||
# screens.rpy:588
|
||||
old "## https://www.renpy.org/doc/html/screen_special.html#save https://www.renpy.org/doc/html/screen_special.html#load"
|
||||
new "## https://www.renpy.org/doc/html/screen_special.html#save https://www.renpy.org/doc/html/screen_special.html#load"
|
||||
|
||||
# screens.rpy:607
|
||||
old "Page {}"
|
||||
new "صفحة {}"
|
||||
|
||||
# screens.rpy:607
|
||||
old "Automatic saves"
|
||||
new "الحفظ التلقائي"
|
||||
|
||||
# screens.rpy:607
|
||||
old "Quick saves"
|
||||
new "الحفظ السريع"
|
||||
|
||||
# screens.rpy:613
|
||||
old "## This ensures the input will get the enter event before any of the buttons do."
|
||||
new "## This ensures the input will get the enter event before any of the buttons do."
|
||||
|
||||
# screens.rpy:629
|
||||
old "## The grid of file slots."
|
||||
new "## The grid of file slots."
|
||||
|
||||
# screens.rpy:649
|
||||
old "{#file_time}%A, %B %d %Y, %H:%M"
|
||||
new "{#file_time}%A, %B %d %Y, %H:%M"
|
||||
|
||||
# screens.rpy:649
|
||||
old "empty slot"
|
||||
new "خانة فارغة"
|
||||
|
||||
# screens.rpy:657
|
||||
old "## Buttons to access other pages."
|
||||
new "## Buttons to access other pages."
|
||||
|
||||
# screens.rpy:666
|
||||
old "<"
|
||||
new "<"
|
||||
|
||||
# screens.rpy:668
|
||||
old "{#auto_page}A"
|
||||
new "{#auto_page}ت"
|
||||
|
||||
# screens.rpy:670
|
||||
old "{#quick_page}Q"
|
||||
new "{#quick_page}س"
|
||||
|
||||
# screens.rpy:676
|
||||
old ">"
|
||||
new ">"
|
||||
|
||||
# screens.rpy:711
|
||||
old "## Preferences screen"
|
||||
new "## Preferences screen"
|
||||
|
||||
# screens.rpy:713
|
||||
old "## The preferences screen allows the player to configure the game to better suit themselves."
|
||||
new "## The preferences screen allows the player to configure the game to better suit themselves."
|
||||
|
||||
# screens.rpy:716
|
||||
old "## https://www.renpy.org/doc/html/screen_special.html#preferences"
|
||||
new "## https://www.renpy.org/doc/html/screen_special.html#preferences"
|
||||
|
||||
# screens.rpy:738
|
||||
old "Display"
|
||||
new "عرض"
|
||||
|
||||
# screens.rpy:739
|
||||
old "Window"
|
||||
new "نافذة"
|
||||
|
||||
# screens.rpy:740
|
||||
old "Fullscreen"
|
||||
new "ملء الشاشة"
|
||||
|
||||
# screens.rpy:744
|
||||
old "Rollback Side"
|
||||
new "Rollback Side"
|
||||
|
||||
# screens.rpy:745
|
||||
old "Disable"
|
||||
new "غير مفعّل"
|
||||
|
||||
# screens.rpy:746
|
||||
old "Left"
|
||||
new "يسار"
|
||||
|
||||
# screens.rpy:747
|
||||
old "Right"
|
||||
new "يمين"
|
||||
|
||||
# screens.rpy:752
|
||||
old "Unseen Text"
|
||||
new "النصوص التي لم تُشاهَد بعد"
|
||||
|
||||
# screens.rpy:753
|
||||
old "After Choices"
|
||||
new "بعد الإختيار"
|
||||
|
||||
# screens.rpy:754
|
||||
old "Transitions"
|
||||
new "التأثيرات البصرية"
|
||||
|
||||
# screens.rpy:756
|
||||
old "## Additional vboxes of type \"radio_pref\" or \"check_pref\" can be added here, to add additional creator-defined preferences."
|
||||
new "## Additional vboxes of type \"radio_pref\" or \"check_pref\" can be added here, to add additional creator-defined preferences."
|
||||
|
||||
# screens.rpy:767
|
||||
old "Text Speed"
|
||||
new "سرعة النص"
|
||||
|
||||
# screens.rpy:771
|
||||
old "Auto-Forward Time"
|
||||
new "وقت التسريع التلقائي"
|
||||
|
||||
# screens.rpy:778
|
||||
old "Music Volume"
|
||||
new "مستوى الموسيقى"
|
||||
|
||||
# screens.rpy:785
|
||||
old "Sound Volume"
|
||||
new "مستوى الأصوات"
|
||||
|
||||
# screens.rpy:791
|
||||
old "Test"
|
||||
new "تجربة"
|
||||
|
||||
# screens.rpy:795
|
||||
old "Voice Volume"
|
||||
new "مستوى صوت الكلام"
|
||||
|
||||
# screens.rpy:806
|
||||
old "Mute All"
|
||||
new "كتم الكل"
|
||||
|
||||
# screens.rpy:882
|
||||
old "## History screen"
|
||||
new "## History screen"
|
||||
|
||||
# screens.rpy:884
|
||||
old "## This is a screen that displays the dialogue history to the player. While there isn't anything special about this screen, it does have to access the dialogue history stored in _history_list."
|
||||
new "## This is a screen that displays the dialogue history to the player. While there isn't anything special about this screen, it does have to access the dialogue history stored in _history_list."
|
||||
|
||||
# screens.rpy:888
|
||||
old "## https://www.renpy.org/doc/html/history.html"
|
||||
new "## https://www.renpy.org/doc/html/history.html"
|
||||
|
||||
# screens.rpy:894
|
||||
old "## Avoid predicting this screen, as it can be very large."
|
||||
new "## Avoid predicting this screen, as it can be very large."
|
||||
|
||||
# screens.rpy:905
|
||||
old "## This lays things out properly if history_height is None."
|
||||
new "## This lays things out properly if history_height is None."
|
||||
|
||||
# screens.rpy:914
|
||||
old "## Take the color of the who text from the Character, if set."
|
||||
new "## Take the color of the who text from the Character, if set."
|
||||
|
||||
# screens.rpy:921
|
||||
old "The dialogue history is empty."
|
||||
new "The dialogue history is empty."
|
||||
|
||||
# screens.rpy:965
|
||||
old "## Help screen"
|
||||
new "## Help screen"
|
||||
|
||||
# screens.rpy:967
|
||||
old "## A screen that gives information about key and mouse bindings. It uses other screens (keyboard_help, mouse_help, and gamepad_help) to display the actual help."
|
||||
new "## A screen that gives information about key and mouse bindings. It uses other screens (keyboard_help, mouse_help, and gamepad_help) to display the actual help."
|
||||
|
||||
# screens.rpy:986
|
||||
old "Keyboard"
|
||||
new "لوحة المفاتيح"
|
||||
|
||||
# screens.rpy:987
|
||||
old "Mouse"
|
||||
new "الفأرة"
|
||||
|
||||
# screens.rpy:990
|
||||
old "Gamepad"
|
||||
new "عصا اللعب"
|
||||
|
||||
# screens.rpy:1003
|
||||
old "Enter"
|
||||
new "Enter"
|
||||
|
||||
# screens.rpy:1004
|
||||
old "Advances dialogue and activates the interface."
|
||||
new "التقدم في الحوار والتفاعل مع الواجهة."
|
||||
|
||||
# screens.rpy:1007
|
||||
old "Space"
|
||||
new "Space"
|
||||
|
||||
# screens.rpy:1008
|
||||
old "Advances dialogue without selecting choices."
|
||||
new "التقدم في الحوار دون تحديد الخيارات."
|
||||
|
||||
# screens.rpy:1011
|
||||
old "Arrow Keys"
|
||||
new "مفاتيح الأسهم"
|
||||
|
||||
# screens.rpy:1012
|
||||
old "Navigate the interface."
|
||||
new "لتصفح الواجهة."
|
||||
|
||||
# screens.rpy:1015
|
||||
old "Escape"
|
||||
new "Escape"
|
||||
|
||||
# screens.rpy:1016
|
||||
old "Accesses the game menu."
|
||||
new "للوصول لقائمة اللعبة."
|
||||
|
||||
# screens.rpy:1019
|
||||
old "Ctrl"
|
||||
new "Ctrl"
|
||||
|
||||
# screens.rpy:1020
|
||||
old "Skips dialogue while held down."
|
||||
new "لتخطي الحوار عند الإستمرار بالضغط."
|
||||
|
||||
# screens.rpy:1023
|
||||
old "Tab"
|
||||
new "Tab"
|
||||
|
||||
# screens.rpy:1024
|
||||
old "Toggles dialogue skipping."
|
||||
new "للتبديل بين أنظمة تخطي الحوار."
|
||||
|
||||
# screens.rpy:1027
|
||||
old "Page Up"
|
||||
new "Page Up"
|
||||
|
||||
# screens.rpy:1028
|
||||
old "Rolls back to earlier dialogue."
|
||||
new "للعودة للنص السابق."
|
||||
|
||||
# screens.rpy:1031
|
||||
old "Page Down"
|
||||
new "Page Down"
|
||||
|
||||
# screens.rpy:1032
|
||||
old "Rolls forward to later dialogue."
|
||||
new "للإستمرار للنص اللاحق."
|
||||
|
||||
# screens.rpy:1036
|
||||
old "Hides the user interface."
|
||||
new "لإخفاء الواجهة."
|
||||
|
||||
# screens.rpy:1040
|
||||
old "Takes a screenshot."
|
||||
new "إلتقاط صورة من الشاشة."
|
||||
|
||||
# screens.rpy:1044
|
||||
old "Toggles assistive {a=https://www.renpy.org/l/voicing}self-voicing{/a}."
|
||||
new "تفعيل مساعدة {a=https://www.renpy.org/l/voicing}الصوت التلقائي{/a}."
|
||||
|
||||
# screens.rpy:1050
|
||||
old "Left Click"
|
||||
new "النقر بالزر الأيسر"
|
||||
|
||||
# screens.rpy:1054
|
||||
old "Middle Click"
|
||||
new "النقر بالزر الأوسط"
|
||||
|
||||
# screens.rpy:1058
|
||||
old "Right Click"
|
||||
new "النقر بالزر الأيمن"
|
||||
|
||||
# screens.rpy:1062
|
||||
old "Mouse Wheel Up"
|
||||
new "تحريك عجلة الفأرة لأعلى"
|
||||
|
||||
# screens.rpy:1066
|
||||
old "Mouse Wheel Down"
|
||||
new "تحريك عجلة الفأرة لأسفل"
|
||||
|
||||
# screens.rpy:1073
|
||||
old "Right Trigger\nA/Bottom Button"
|
||||
new "Right Trigger\nA/Bottom Button"
|
||||
|
||||
# screens.rpy:1074
|
||||
old "Advance dialogue and activates the interface."
|
||||
new "التقدم في الحوار والتفاعل مع الواجهة."
|
||||
|
||||
# screens.rpy:1078
|
||||
old "Roll back to earlier dialogue."
|
||||
new "للعودة للنص السابق."
|
||||
|
||||
# screens.rpy:1081
|
||||
old "Right Shoulder"
|
||||
new "Right Shoulder"
|
||||
|
||||
# screens.rpy:1082
|
||||
old "Roll forward to later dialogue."
|
||||
new "للإستمرار للنص اللاحق."
|
||||
|
||||
# screens.rpy:1085
|
||||
old "D-Pad, Sticks"
|
||||
new "D-Pad, Sticks"
|
||||
|
||||
# screens.rpy:1089
|
||||
old "Start, Guide"
|
||||
new "Start, Guide"
|
||||
|
||||
# screens.rpy:1090
|
||||
old "Access the game menu."
|
||||
new "Access the game menu."
|
||||
|
||||
# screens.rpy:1093
|
||||
old "Y/Top Button"
|
||||
new "Y/Top Button"
|
||||
|
||||
# screens.rpy:1096
|
||||
old "Calibrate"
|
||||
new "Calibrate"
|
||||
|
||||
# screens.rpy:1124
|
||||
old "## Additional screens"
|
||||
new "## Additional screens"
|
||||
|
||||
# screens.rpy:1128
|
||||
old "## Confirm screen"
|
||||
new "## Confirm screen"
|
||||
|
||||
# screens.rpy:1130
|
||||
old "## The confirm screen is called when Ren'Py wants to ask the player a yes or no question."
|
||||
new "## The confirm screen is called when Ren'Py wants to ask the player a yes or no question."
|
||||
|
||||
# screens.rpy:1133
|
||||
old "## http://www.renpy.org/doc/html/screen_special.html#confirm"
|
||||
new "## http://www.renpy.org/doc/html/screen_special.html#confirm"
|
||||
|
||||
# screens.rpy:1137
|
||||
old "## Ensure other screens do not get input while this screen is displayed."
|
||||
new "## Ensure other screens do not get input while this screen is displayed."
|
||||
|
||||
# screens.rpy:1161
|
||||
old "Yes"
|
||||
new "نعم"
|
||||
|
||||
# screens.rpy:1162
|
||||
old "No"
|
||||
new "لا"
|
||||
|
||||
# screens.rpy:1164
|
||||
old "## Right-click and escape answer \"no\"."
|
||||
new "## Right-click and escape answer \"no\"."
|
||||
|
||||
# screens.rpy:1191
|
||||
old "## Skip indicator screen"
|
||||
new "## Skip indicator screen"
|
||||
|
||||
# screens.rpy:1193
|
||||
old "## The skip_indicator screen is displayed to indicate that skipping is in progress."
|
||||
new "## The skip_indicator screen is displayed to indicate that skipping is in progress."
|
||||
|
||||
# screens.rpy:1196
|
||||
old "## https://www.renpy.org/doc/html/screen_special.html#skip-indicator"
|
||||
new "## https://www.renpy.org/doc/html/screen_special.html#skip-indicator"
|
||||
|
||||
# screens.rpy:1208
|
||||
old "Skipping"
|
||||
new "جاري التخطي"
|
||||
|
||||
# screens.rpy:1215
|
||||
old "## This transform is used to blink the arrows one after another."
|
||||
new "## This transform is used to blink the arrows one after another."
|
||||
|
||||
# screens.rpy:1247
|
||||
old "## Notify screen"
|
||||
new "## Notify screen"
|
||||
|
||||
# screens.rpy:1249
|
||||
old "## The notify screen is used to show the player a message. (For example, when the game is quicksaved or a screenshot has been taken.)"
|
||||
new "## The notify screen is used to show the player a message. (For example, when the game is quicksaved or a screenshot has been taken.)"
|
||||
|
||||
# screens.rpy:1252
|
||||
old "## https://www.renpy.org/doc/html/screen_special.html#notify-screen"
|
||||
new "## https://www.renpy.org/doc/html/screen_special.html#notify-screen"
|
||||
|
||||
# screens.rpy:1286
|
||||
old "## NVL screen"
|
||||
new "## NVL screen"
|
||||
|
||||
# screens.rpy:1288
|
||||
old "## This screen is used for NVL-mode dialogue and menus."
|
||||
new "## This screen is used for NVL-mode dialogue and menus."
|
||||
|
||||
# screens.rpy:1290
|
||||
old "## http://www.renpy.org/doc/html/screen_special.html#nvl"
|
||||
new "## http://www.renpy.org/doc/html/screen_special.html#nvl"
|
||||
|
||||
# screens.rpy:1301
|
||||
old "## Displays dialogue in either a vpgrid or the vbox."
|
||||
new "## Displays dialogue in either a vpgrid or the vbox."
|
||||
|
||||
# screens.rpy:1314
|
||||
old "## Displays the menu, if given. The menu may be displayed incorrectly if config.narrator_menu is set to True, as it is above."
|
||||
new "## Displays the menu, if given. The menu may be displayed incorrectly if config.narrator_menu is set to True, as it is above."
|
||||
|
||||
# screens.rpy:1344
|
||||
old "## This controls the maximum number of NVL-mode entries that can be displayed at once."
|
||||
new "## This controls the maximum number of NVL-mode entries that can be displayed at once."
|
||||
|
||||
# screens.rpy:1406
|
||||
old "## Mobile Variants"
|
||||
new "## Mobile Variants"
|
||||
|
||||
# screens.rpy:1413
|
||||
old "## Since a mouse may not be present, we replace the quick menu with a version that uses fewer and bigger buttons that are easier to touch."
|
||||
new "## Since a mouse may not be present, we replace the quick menu with a version that uses fewer and bigger buttons that are easier to touch."
|
||||
|
||||
# screens.rpy:1429
|
||||
old "Menu"
|
||||
new "Menu"
|
||||
@@ -1,85 +0,0 @@
|
||||
# هذا الملف يحتوي على كل نصوص اللعبه
|
||||
|
||||
# استعمل السطر التالي لإظهار الصور في شاشة اللعب
|
||||
# image eileen happy = "eileen_happy.png"
|
||||
|
||||
# من هي شخصيات هذه اللعبه؟
|
||||
define e = Character('إيلين', color="#c8ffc8")
|
||||
|
||||
# اللعبه تبدأ هنا
|
||||
label start:
|
||||
|
||||
e "لقد بدأت حكاية رينباي جديدة."
|
||||
|
||||
e "كل ما عليك فعله هو إضافة نصوص و صور و إرسالها للعالم!"
|
||||
|
||||
return
|
||||
|
||||
|
||||
########################################################### من اليمين الى اليسار
|
||||
|
||||
define config.rtl = True
|
||||
|
||||
define gui.textalign = 1.0
|
||||
define gui.button_text_xalign = 1.0
|
||||
define gui.prompt_text_xalign = 1.0
|
||||
define gui.navigation_xpos = 240
|
||||
|
||||
define gui.name_xalign = 1.0
|
||||
define gui.dialogue_text_xalign = 1.0
|
||||
define gui.button_text_xalign = 1.0
|
||||
define gui.main_menu_text_xalign = 1.0
|
||||
define gui.history_name_xalign = 0.0
|
||||
define gui.history_text_xalign = 1.0
|
||||
define gui.nvl_name_xalign = 0.0
|
||||
define gui.nvl_text_xalign = 1.0
|
||||
define gui.nvl_thought_xalign = 1.0
|
||||
define gui.nvl_button_xalign = 1.0
|
||||
|
||||
define gui.help_label_text_xalign = 0.0
|
||||
|
||||
define gui.name_xpos = gui.scale(1040)
|
||||
define gui.dialogue_xpos = gui.scale(1012)
|
||||
define gui.history_name_xpos = gui.scale(763)
|
||||
define gui.history_text_xpos = gui.scale(748)
|
||||
define gui.nvl_name_xpos = gui.scale(850)
|
||||
define gui.nvl_text_xpos = gui.scale(830)
|
||||
define gui.nvl_thought_xpos = gui.scale(1040)
|
||||
define gui.nvl_button_xpos = gui.scale(830)
|
||||
|
||||
style game_menu_label:
|
||||
xalign 1.0
|
||||
xoffset gui.scale(-50)
|
||||
|
||||
style navigation_vbox:
|
||||
xanchor 1.0
|
||||
|
||||
style return_button:
|
||||
xanchor 1.0
|
||||
|
||||
style radio_button:
|
||||
xalign 1.0
|
||||
|
||||
style radio_label:
|
||||
xalign 1.0
|
||||
|
||||
style check_button:
|
||||
xalign 1.0
|
||||
|
||||
style check_label:
|
||||
xalign 1.0
|
||||
|
||||
style slider_label:
|
||||
xpos gui.scale(350)
|
||||
xalign 1.0
|
||||
|
||||
style help_hbox:
|
||||
box_reverse True
|
||||
xalign 1.0
|
||||
spacing gui.scale(20)
|
||||
|
||||
style help_label_text:
|
||||
xalign 0.0
|
||||
|
||||
style mute_all_button:
|
||||
xalign 0.0
|
||||
@@ -1,7 +0,0 @@
|
||||
translate arabic python:
|
||||
gui.REGULAR_FONT = "DejaVuSans.ttf"
|
||||
gui.LIGHT_FONT = "DejaVuSans.ttf"
|
||||
gui.FONT_SCALE = .9
|
||||
gui.REGULAR_BOLD = True
|
||||
|
||||
config.rtl = True
|
||||
@@ -1,978 +0,0 @@
|
||||
translate danish strings:
|
||||
|
||||
# renpy/common/00accessibility.rpy:28
|
||||
old "Self-voicing disabled."
|
||||
new "Automatisk oplæsning deaktiveret."
|
||||
|
||||
# renpy/common/00accessibility.rpy:29
|
||||
old "Clipboard voicing enabled. "
|
||||
new "Oplæsning af udklipsholder aktiveret."
|
||||
|
||||
# renpy/common/00accessibility.rpy:30
|
||||
old "Self-voicing enabled. "
|
||||
new "Automatisk oplæsning aktiveret."
|
||||
|
||||
# renpy/common/00accessibility.rpy:32
|
||||
old "bar"
|
||||
new "bjælke"
|
||||
|
||||
# renpy/common/00accessibility.rpy:33
|
||||
old "selected"
|
||||
new "valgt"
|
||||
|
||||
# renpy/common/00accessibility.rpy:34
|
||||
old "viewport"
|
||||
new "visningsport"
|
||||
|
||||
# renpy/common/00accessibility.rpy:35
|
||||
old "horizontal scroll"
|
||||
new "vandret rulning"
|
||||
|
||||
# renpy/common/00accessibility.rpy:36
|
||||
old "vertical scroll"
|
||||
new "lodret rulning"
|
||||
|
||||
# renpy/common/00accessibility.rpy:37
|
||||
old "activate"
|
||||
new "aktivér"
|
||||
|
||||
# renpy/common/00accessibility.rpy:38
|
||||
old "deactivate"
|
||||
new "deaktiver"
|
||||
|
||||
# renpy/common/00accessibility.rpy:39
|
||||
old "increase"
|
||||
new "øg"
|
||||
|
||||
# renpy/common/00accessibility.rpy:40
|
||||
old "decrease"
|
||||
new "sænk"
|
||||
|
||||
# renpy/common/00accessibility.rpy:138
|
||||
old "Font Override"
|
||||
new "Tilsidesættelse af skrifttype"
|
||||
|
||||
# renpy/common/00accessibility.rpy:142
|
||||
old "Default"
|
||||
new "Standard"
|
||||
|
||||
# renpy/common/00accessibility.rpy:146
|
||||
old "DejaVu Sans"
|
||||
new "DejaVu Sans"
|
||||
|
||||
# renpy/common/00accessibility.rpy:150
|
||||
old "Opendyslexic"
|
||||
new "Opendyslexic"
|
||||
|
||||
# renpy/common/00accessibility.rpy:156
|
||||
old "Text Size Scaling"
|
||||
new "Skalering af skriftstørrelse"
|
||||
|
||||
# renpy/common/00accessibility.rpy:162
|
||||
old "Reset"
|
||||
new "Nulstil"
|
||||
|
||||
# renpy/common/00accessibility.rpy:168
|
||||
old "Line Spacing Scaling"
|
||||
new "Skalering af linjeafstand"
|
||||
|
||||
# renpy/common/00accessibility.rpy:180
|
||||
old "High Contrast Text"
|
||||
new "Tekst med høj kontrast"
|
||||
|
||||
# renpy/common/00accessibility.rpy:182
|
||||
old "Enable"
|
||||
new "Slå til"
|
||||
|
||||
# renpy/common/00accessibility.rpy:186
|
||||
old "Disable"
|
||||
new "Slå fra"
|
||||
|
||||
# renpy/common/00accessibility.rpy:193
|
||||
old "Self-Voicing"
|
||||
new "Automatisk oplæsning"
|
||||
|
||||
# renpy/common/00accessibility.rpy:197
|
||||
old "Off"
|
||||
new "Fra"
|
||||
|
||||
# renpy/common/00accessibility.rpy:201
|
||||
old "Text-to-speech"
|
||||
new "Tekst til tale"
|
||||
|
||||
# renpy/common/00accessibility.rpy:205
|
||||
old "Clipboard"
|
||||
new "Udklipsholder"
|
||||
|
||||
# renpy/common/00accessibility.rpy:209
|
||||
old "Debug"
|
||||
new "Fejlsøgning"
|
||||
|
||||
# renpy/common/00accessibility.rpy:223
|
||||
old "Self-Voicing Volume Drop"
|
||||
new "Lydstyrkesænkning ved automatisk oplæsning"
|
||||
|
||||
# renpy/common/00accessibility.rpy:234
|
||||
old "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."
|
||||
new "Indstillingerne i denne menu er tiltænkt at forbedre tilgængelighed. De virker muligvis ikke med alle spil, og visse kombinationer af indstillinger gør muligvis spillet uspilleligt. Dette er ikke et problem med spillet eller spilmotoren. Prøv at bevare den originale skriftstørrelse for at opnå det bedste resultat væd ændring af skrifttype."
|
||||
|
||||
# renpy/common/00action_file.rpy:26
|
||||
old "{#weekday}Monday"
|
||||
new "{#weekday}mandag"
|
||||
|
||||
# renpy/common/00action_file.rpy:26
|
||||
old "{#weekday}Tuesday"
|
||||
new "{#weekday}tirsdag"
|
||||
|
||||
# renpy/common/00action_file.rpy:26
|
||||
old "{#weekday}Wednesday"
|
||||
new "{#weekday}onsdag"
|
||||
|
||||
# renpy/common/00action_file.rpy:26
|
||||
old "{#weekday}Thursday"
|
||||
new "{#weekday}torsdag"
|
||||
|
||||
# renpy/common/00action_file.rpy:26
|
||||
old "{#weekday}Friday"
|
||||
new "{#weekday}fredag"
|
||||
|
||||
# renpy/common/00action_file.rpy:26
|
||||
old "{#weekday}Saturday"
|
||||
new "{#weekday}lørdag"
|
||||
|
||||
# renpy/common/00action_file.rpy:26
|
||||
old "{#weekday}Sunday"
|
||||
new "{#weekday}søndag"
|
||||
|
||||
# renpy/common/00action_file.rpy:37
|
||||
old "{#weekday_short}Mon"
|
||||
new "{#weekday_short}man"
|
||||
|
||||
# renpy/common/00action_file.rpy:37
|
||||
old "{#weekday_short}Tue"
|
||||
new "{#weekday_short}tir"
|
||||
|
||||
# renpy/common/00action_file.rpy:37
|
||||
old "{#weekday_short}Wed"
|
||||
new "{#weekday_short}ons"
|
||||
|
||||
# renpy/common/00action_file.rpy:37
|
||||
old "{#weekday_short}Thu"
|
||||
new "{#weekday_short}tor"
|
||||
|
||||
# renpy/common/00action_file.rpy:37
|
||||
old "{#weekday_short}Fri"
|
||||
new "{#weekday_short}fre"
|
||||
|
||||
# renpy/common/00action_file.rpy:37
|
||||
old "{#weekday_short}Sat"
|
||||
new "{#weekday_short}lør"
|
||||
|
||||
# renpy/common/00action_file.rpy:37
|
||||
old "{#weekday_short}Sun"
|
||||
new "{#weekday_short}søn"
|
||||
|
||||
# renpy/common/00action_file.rpy:47
|
||||
old "{#month}January"
|
||||
new "{#month}januar"
|
||||
|
||||
# renpy/common/00action_file.rpy:47
|
||||
old "{#month}February"
|
||||
new "{#month}februar"
|
||||
|
||||
# renpy/common/00action_file.rpy:47
|
||||
old "{#month}March"
|
||||
new "{#month}marts"
|
||||
|
||||
# renpy/common/00action_file.rpy:47
|
||||
old "{#month}April"
|
||||
new "{#month}april"
|
||||
|
||||
# renpy/common/00action_file.rpy:47
|
||||
old "{#month}May"
|
||||
new "{#month}maj"
|
||||
|
||||
# renpy/common/00action_file.rpy:47
|
||||
old "{#month}June"
|
||||
new "{#month}juni"
|
||||
|
||||
# renpy/common/00action_file.rpy:47
|
||||
old "{#month}July"
|
||||
new "{#month}juli"
|
||||
|
||||
# renpy/common/00action_file.rpy:47
|
||||
old "{#month}August"
|
||||
new "{#month}august"
|
||||
|
||||
# renpy/common/00action_file.rpy:47
|
||||
old "{#month}September"
|
||||
new "{#month}september"
|
||||
|
||||
# renpy/common/00action_file.rpy:47
|
||||
old "{#month}October"
|
||||
new "{#month}oktober"
|
||||
|
||||
# renpy/common/00action_file.rpy:47
|
||||
old "{#month}November"
|
||||
new "{#month}november"
|
||||
|
||||
# renpy/common/00action_file.rpy:47
|
||||
old "{#month}December"
|
||||
new "{#month}december"
|
||||
|
||||
# renpy/common/00action_file.rpy:63
|
||||
old "{#month_short}Jan"
|
||||
new "{#month_short}jan"
|
||||
|
||||
# renpy/common/00action_file.rpy:63
|
||||
old "{#month_short}Feb"
|
||||
new "{#month_short}feb"
|
||||
|
||||
# renpy/common/00action_file.rpy:63
|
||||
old "{#month_short}Mar"
|
||||
new "{#month_short}mar"
|
||||
|
||||
# renpy/common/00action_file.rpy:63
|
||||
old "{#month_short}Apr"
|
||||
new "{#month_short}apr"
|
||||
|
||||
# renpy/common/00action_file.rpy:63
|
||||
old "{#month_short}May"
|
||||
new "{#month_short}maj"
|
||||
|
||||
# renpy/common/00action_file.rpy:63
|
||||
old "{#month_short}Jun"
|
||||
new "{#month_short}jun"
|
||||
|
||||
# renpy/common/00action_file.rpy:63
|
||||
old "{#month_short}Jul"
|
||||
new "{#month_short}jul"
|
||||
|
||||
# renpy/common/00action_file.rpy:63
|
||||
old "{#month_short}Aug"
|
||||
new "{#month_short}aug"
|
||||
|
||||
# renpy/common/00action_file.rpy:63
|
||||
old "{#month_short}Sep"
|
||||
new "{#month_short}sep"
|
||||
|
||||
# renpy/common/00action_file.rpy:63
|
||||
old "{#month_short}Oct"
|
||||
new "{#month_short}okt"
|
||||
|
||||
# renpy/common/00action_file.rpy:63
|
||||
old "{#month_short}Nov"
|
||||
new "{#month_short}nov"
|
||||
|
||||
# renpy/common/00action_file.rpy:63
|
||||
old "{#month_short}Dec"
|
||||
new "{#month_short}dec"
|
||||
|
||||
# renpy/common/00action_file.rpy:258
|
||||
old "%b %d, %H:%M"
|
||||
new "%d. %b, %H:%M"
|
||||
|
||||
# renpy/common/00action_file.rpy:378
|
||||
old "Save slot %s: [text]"
|
||||
new "Gem plads %s: [text]"
|
||||
|
||||
# renpy/common/00action_file.rpy:461
|
||||
old "Load slot %s: [text]"
|
||||
new "Indlæs plads %s: [text]"
|
||||
|
||||
# renpy/common/00action_file.rpy:514
|
||||
old "Delete slot [text]"
|
||||
new "Slet plads [text]"
|
||||
|
||||
# renpy/common/00action_file.rpy:593
|
||||
old "File page auto"
|
||||
new "Filside auto"
|
||||
|
||||
# renpy/common/00action_file.rpy:595
|
||||
old "File page quick"
|
||||
new "Filside hurtig"
|
||||
|
||||
# renpy/common/00action_file.rpy:597
|
||||
old "File page [text]"
|
||||
new "Filside [text]"
|
||||
|
||||
# renpy/common/00action_file.rpy:796
|
||||
old "Next file page."
|
||||
new "Næste filside."
|
||||
|
||||
# renpy/common/00action_file.rpy:868
|
||||
old "Previous file page."
|
||||
new "Forrige filside."
|
||||
|
||||
# renpy/common/00action_file.rpy:929
|
||||
old "Quick save complete."
|
||||
new "Hurtiggemning fuldført."
|
||||
|
||||
# renpy/common/00action_file.rpy:944
|
||||
old "Quick save."
|
||||
new "Hurtiggem."
|
||||
|
||||
# renpy/common/00action_file.rpy:963
|
||||
old "Quick load."
|
||||
new "Hurtigindlæs."
|
||||
|
||||
# renpy/common/00action_other.rpy:383
|
||||
old "Language [text]"
|
||||
new "Sprog [text]"
|
||||
|
||||
# renpy/common/00action_other.rpy:724
|
||||
old "Open [text] directory."
|
||||
new "Åbn mappen [text]."
|
||||
|
||||
# renpy/common/00director.rpy:705
|
||||
old "The interactive director is not enabled here."
|
||||
new "Det interaktive instruktionsværktøj er ikke aktiveret her."
|
||||
|
||||
# renpy/common/00director.rpy:1504
|
||||
old "⬆"
|
||||
new "⬆"
|
||||
|
||||
# renpy/common/00director.rpy:1510
|
||||
old "⬇"
|
||||
new "⬇"
|
||||
|
||||
# renpy/common/00director.rpy:1574
|
||||
old "Done"
|
||||
new "Færdig"
|
||||
|
||||
# renpy/common/00director.rpy:1584
|
||||
old "(statement)"
|
||||
new "(sætning)"
|
||||
|
||||
# renpy/common/00director.rpy:1585
|
||||
old "(tag)"
|
||||
new "(mærke)"
|
||||
|
||||
# renpy/common/00director.rpy:1586
|
||||
old "(attributes)"
|
||||
new "(attributter)"
|
||||
|
||||
# renpy/common/00director.rpy:1587
|
||||
old "(transform)"
|
||||
new "(transformation)"
|
||||
|
||||
# renpy/common/00director.rpy:1612
|
||||
old "(transition)"
|
||||
new "(overgang)"
|
||||
|
||||
# renpy/common/00director.rpy:1624
|
||||
old "(channel)"
|
||||
new "(kanal)"
|
||||
|
||||
# renpy/common/00director.rpy:1625
|
||||
old "(filename)"
|
||||
new "(filnavn)"
|
||||
|
||||
# renpy/common/00director.rpy:1654
|
||||
old "Change"
|
||||
new "Ændr"
|
||||
|
||||
# renpy/common/00director.rpy:1656
|
||||
old "Add"
|
||||
new "Tilføj"
|
||||
|
||||
# renpy/common/00director.rpy:1659
|
||||
old "Cancel"
|
||||
new "Annuller"
|
||||
|
||||
# renpy/common/00director.rpy:1662
|
||||
old "Remove"
|
||||
new "Fjern"
|
||||
|
||||
# renpy/common/00director.rpy:1697
|
||||
old "Statement:"
|
||||
new "Sætning:"
|
||||
|
||||
# renpy/common/00director.rpy:1718
|
||||
old "Tag:"
|
||||
new "Mærke:"
|
||||
|
||||
# renpy/common/00director.rpy:1734
|
||||
old "Attributes:"
|
||||
new "Attributter:"
|
||||
|
||||
# renpy/common/00director.rpy:1745
|
||||
old "Click to toggle attribute, right click to toggle negative attribute."
|
||||
new "Klik for at slå attributter til/fra, højreklik for at slå negative attributter til/fra."
|
||||
|
||||
# renpy/common/00director.rpy:1757
|
||||
old "Transforms:"
|
||||
new "Transformationer:"
|
||||
|
||||
# renpy/common/00director.rpy:1768
|
||||
old "Click to set transform, right click to add to transform list."
|
||||
new "Klik for at stille transformation, højreklik for at tilføje til transformationsliste."
|
||||
|
||||
# renpy/common/00director.rpy:1780
|
||||
old "Behind:"
|
||||
new "Bagved:"
|
||||
|
||||
# renpy/common/00director.rpy:1789
|
||||
old "Click to set, right click to add to behind list."
|
||||
new "Klik for at stille, højreklik for at tilføje til bagvedliste."
|
||||
|
||||
# renpy/common/00director.rpy:1801
|
||||
old "Transition:"
|
||||
new "Overgang:"
|
||||
|
||||
# renpy/common/00director.rpy:1819
|
||||
old "Channel:"
|
||||
new "Kanal:"
|
||||
|
||||
# renpy/common/00director.rpy:1837
|
||||
old "Audio Filename:"
|
||||
new "Lydfilnavn:"
|
||||
|
||||
# renpy/common/00gui.rpy:446
|
||||
old "Are you sure?"
|
||||
new "Er du sikker?"
|
||||
|
||||
# renpy/common/00gui.rpy:447
|
||||
old "Are you sure you want to delete this save?"
|
||||
new "Vil du gerne slette denne gemmefil?"
|
||||
|
||||
# renpy/common/00gui.rpy:448
|
||||
old "Are you sure you want to overwrite your save?"
|
||||
new "Vil du gerne overskrive din gemmefil?"
|
||||
|
||||
# renpy/common/00gui.rpy:449
|
||||
old "Loading will lose unsaved progress.\nAre you sure you want to do this?"
|
||||
new "Ved indlæsning vil ugemte fremskridt gå tabt.\nVil du gerne gøre dette?"
|
||||
|
||||
# renpy/common/00gui.rpy:450
|
||||
old "Are you sure you want to quit?"
|
||||
new "Vil du gerne afslutte?"
|
||||
|
||||
# renpy/common/00gui.rpy:451
|
||||
old "Are you sure you want to return to the main menu?\nThis will lose unsaved progress."
|
||||
new "Vil du gerne vende tilbage til hovedmenuen?\nUgemte fremskridt vil gå tabt."
|
||||
|
||||
# renpy/common/00gui.rpy:452
|
||||
old "Are you sure you want to end the replay?"
|
||||
new "Vil du gerne afslutte genafspilningnen?"
|
||||
|
||||
# renpy/common/00gui.rpy:453
|
||||
old "Are you sure you want to begin skipping?"
|
||||
new "Vil du gerne påbegynde overspringning?"
|
||||
|
||||
# renpy/common/00gui.rpy:454
|
||||
old "Are you sure you want to skip to the next choice?"
|
||||
new "Vil du gerne springe videre til det næste valg?"
|
||||
|
||||
# renpy/common/00gui.rpy:455
|
||||
old "Are you sure you want to skip unseen dialogue to the next choice?"
|
||||
new "Vil du gerne springe over uset dialog til det næste valg?"
|
||||
|
||||
# renpy/common/00gui.rpy:456
|
||||
old "This save was created on a different device. Maliciously constructed save files can harm your computer. Do you trust this save's creator and everyone who could have changed the file?"
|
||||
new "Denne gemmefil blev oprettet på en anden enhed. Gemmefiler konstrueret med ondsindet hensigt kan skade din computer. Stoler du på den, der har oprettet gemmefilen, og på alle, der kan have ændret filen?"
|
||||
|
||||
# renpy/common/00gui.rpy:457
|
||||
old "Do you trust the device the save was created on? You should only choose yes if you are the device's sole user."
|
||||
new "Stoler du på enheden, som gemmefilen oprettedes på? Du bør kun vælge ja, hvis du er enhedens eneste bruger."
|
||||
|
||||
# renpy/common/00keymap.rpy:322
|
||||
old "Failed to save screenshot as %s."
|
||||
new "Kunne ikke gemme skærmbillede som %s."
|
||||
|
||||
# renpy/common/00keymap.rpy:334
|
||||
old "Saved screenshot as %s."
|
||||
new "Gemte skærmbillede som %s."
|
||||
|
||||
# renpy/common/00library.rpy:248
|
||||
old "Skip Mode"
|
||||
new "Overspringstilstand"
|
||||
|
||||
# renpy/common/00library.rpy:335
|
||||
old "This program contains free software under a number of licenses, including the MIT License and GNU Lesser General Public License. A complete list of software, including links to full source code, can be found {a=https://www.renpy.org/l/license}here{/a}."
|
||||
new "Dette program indeholder gratis software under forskellige licenser, herunder MIT-licensen og GNU Lesser General Public License. En komplet liste over software, inklusiv links til fulde kildekoder, kan findes {a=https://www.renpy.org/l/license}her{/a}."
|
||||
|
||||
# renpy/common/00preferences.rpy:271
|
||||
old "display"
|
||||
new "skærmvisning"
|
||||
|
||||
# renpy/common/00preferences.rpy:287
|
||||
old "transitions"
|
||||
new "overgange"
|
||||
|
||||
# renpy/common/00preferences.rpy:296
|
||||
old "skip transitions"
|
||||
new "spring overgange over"
|
||||
|
||||
# renpy/common/00preferences.rpy:298
|
||||
old "video sprites"
|
||||
new "videosprites"
|
||||
|
||||
# renpy/common/00preferences.rpy:307
|
||||
old "show empty window"
|
||||
new "vis tomt vindue"
|
||||
|
||||
# renpy/common/00preferences.rpy:316
|
||||
old "text speed"
|
||||
new "teksthastighed"
|
||||
|
||||
# renpy/common/00preferences.rpy:324
|
||||
old "joystick"
|
||||
new "styrepind"
|
||||
|
||||
# renpy/common/00preferences.rpy:324
|
||||
old "joystick..."
|
||||
new "styrepind..."
|
||||
|
||||
# renpy/common/00preferences.rpy:331
|
||||
old "skip"
|
||||
new "spring over"
|
||||
|
||||
# renpy/common/00preferences.rpy:334
|
||||
old "skip unseen [text]"
|
||||
new "spring over uset [text]"
|
||||
|
||||
# renpy/common/00preferences.rpy:339
|
||||
old "skip unseen text"
|
||||
new "spring over uset tekst"
|
||||
|
||||
# renpy/common/00preferences.rpy:341
|
||||
old "begin skipping"
|
||||
new "påbegynd overspringning"
|
||||
|
||||
# renpy/common/00preferences.rpy:345
|
||||
old "after choices"
|
||||
new "efter valg"
|
||||
|
||||
# renpy/common/00preferences.rpy:352
|
||||
old "skip after choices"
|
||||
new "spring over efter valg"
|
||||
|
||||
# renpy/common/00preferences.rpy:354
|
||||
old "auto-forward time"
|
||||
new "auto-fremad-tid"
|
||||
|
||||
# renpy/common/00preferences.rpy:368
|
||||
old "auto-forward"
|
||||
new "auto-fremad"
|
||||
|
||||
# renpy/common/00preferences.rpy:375
|
||||
old "Auto forward"
|
||||
new "auto-fremad"
|
||||
|
||||
# renpy/common/00preferences.rpy:378
|
||||
old "auto-forward after click"
|
||||
new "auto-fremad efter klik"
|
||||
|
||||
# renpy/common/00preferences.rpy:387
|
||||
old "automatic move"
|
||||
new "automatisk bevægelse"
|
||||
|
||||
# renpy/common/00preferences.rpy:396
|
||||
old "wait for voice"
|
||||
new "vent på stemme"
|
||||
|
||||
# renpy/common/00preferences.rpy:405
|
||||
old "voice sustain"
|
||||
new "stemmebevarelse"
|
||||
|
||||
# renpy/common/00preferences.rpy:414
|
||||
old "self voicing"
|
||||
new "automatisk oplæsning"
|
||||
|
||||
# renpy/common/00preferences.rpy:423
|
||||
old "self voicing volume drop"
|
||||
new "lydstyrkesænkning ved automatisk oplæsning"
|
||||
|
||||
# renpy/common/00preferences.rpy:431
|
||||
old "clipboard voicing"
|
||||
new "oplæsning af udklipsholder"
|
||||
|
||||
# renpy/common/00preferences.rpy:440
|
||||
old "debug voicing"
|
||||
new "oplæsning af fejlsøgning"
|
||||
|
||||
# renpy/common/00preferences.rpy:449
|
||||
old "emphasize audio"
|
||||
new "fremhæv lyd"
|
||||
|
||||
# renpy/common/00preferences.rpy:458
|
||||
old "rollback side"
|
||||
new "tilbagerulningsside"
|
||||
|
||||
# renpy/common/00preferences.rpy:468
|
||||
old "gl powersave"
|
||||
new "gl-strømbesparelse"
|
||||
|
||||
# renpy/common/00preferences.rpy:474
|
||||
old "gl framerate"
|
||||
new "gl-billedhastighed"
|
||||
|
||||
# renpy/common/00preferences.rpy:477
|
||||
old "gl tearing"
|
||||
new "gl-overrivning"
|
||||
|
||||
# renpy/common/00preferences.rpy:480
|
||||
old "font transform"
|
||||
new "skrifttransformation"
|
||||
|
||||
# renpy/common/00preferences.rpy:483
|
||||
old "font size"
|
||||
new "skriftstørrelse"
|
||||
|
||||
# renpy/common/00preferences.rpy:491
|
||||
old "font line spacing"
|
||||
new "linjeafstand for skrift"
|
||||
|
||||
# renpy/common/00preferences.rpy:499
|
||||
old "system cursor"
|
||||
new "systemmarkør"
|
||||
|
||||
# renpy/common/00preferences.rpy:508
|
||||
old "renderer menu"
|
||||
new "optegningsmenu"
|
||||
|
||||
# renpy/common/00preferences.rpy:511
|
||||
old "accessibility menu"
|
||||
new "tilgængelighedsmenu"
|
||||
|
||||
# renpy/common/00preferences.rpy:514
|
||||
old "high contrast text"
|
||||
new "tekst med høj kontrast"
|
||||
|
||||
# renpy/common/00preferences.rpy:523
|
||||
old "audio when minimized"
|
||||
new "lyd ved minimeret tilstand"
|
||||
|
||||
# renpy/common/00preferences.rpy:532
|
||||
old "audio when unfocused"
|
||||
new "lyd ved ufokuseret tilstand"
|
||||
|
||||
# renpy/common/00preferences.rpy:541
|
||||
old "web cache preload"
|
||||
new "forudindlæsning af webcache"
|
||||
|
||||
# renpy/common/00preferences.rpy:556
|
||||
old "voice after game menu"
|
||||
new "stemme efter spilmenu"
|
||||
|
||||
# renpy/common/00preferences.rpy:575
|
||||
old "main volume"
|
||||
new "primær lydstyrke"
|
||||
|
||||
# renpy/common/00preferences.rpy:576
|
||||
old "music volume"
|
||||
new "musiklydstyrke"
|
||||
|
||||
# renpy/common/00preferences.rpy:577
|
||||
old "sound volume"
|
||||
new "lydeffektlydstyrke"
|
||||
|
||||
# renpy/common/00preferences.rpy:578
|
||||
old "voice volume"
|
||||
new "stemmelydstyrke"
|
||||
|
||||
# renpy/common/00preferences.rpy:579
|
||||
old "mute main"
|
||||
new "slå primær lyd fra"
|
||||
|
||||
# renpy/common/00preferences.rpy:580
|
||||
old "mute music"
|
||||
new "slå musik fra"
|
||||
|
||||
# renpy/common/00preferences.rpy:581
|
||||
old "mute sound"
|
||||
new "slå lydeffekter fra"
|
||||
|
||||
# renpy/common/00preferences.rpy:582
|
||||
old "mute voice"
|
||||
new "slå stemmer fra"
|
||||
|
||||
# renpy/common/00preferences.rpy:583
|
||||
old "mute all"
|
||||
new "slå alle fra"
|
||||
|
||||
# renpy/common/00preferences.rpy:657
|
||||
old "Clipboard voicing enabled. Press 'shift+C' to disable."
|
||||
new "Oplæsning af udklipsholder aktiveret. Tryk på 'shift+C' for at deaktivere."
|
||||
|
||||
# renpy/common/00preferences.rpy:659
|
||||
old "Self-voicing would say \"[renpy.display.tts.last]\". Press 'alt+shift+V' to disable."
|
||||
new "Automatisk oplæsning ville sige \"[renpy.display.tts.last]\". Tryk på 'alt+shift+V' for at deaktivere."
|
||||
|
||||
# renpy/common/00preferences.rpy:661
|
||||
old "Self-voicing enabled. Press 'v' to disable."
|
||||
new "Automatisk oplæsning aktiveret. Tryk på 'v' for at deaktivere."
|
||||
|
||||
# renpy/common/00speechbubble.rpy:344
|
||||
old "Speech Bubble Editor"
|
||||
new "Talebobleredigering"
|
||||
|
||||
# renpy/common/00speechbubble.rpy:349
|
||||
old "(hide)"
|
||||
new "(skjul)"
|
||||
|
||||
# renpy/common/00sync.rpy:70
|
||||
old "Sync downloaded."
|
||||
new "Synkronisering hentet."
|
||||
|
||||
# renpy/common/00sync.rpy:190
|
||||
old "Could not connect to the Ren'Py Sync server."
|
||||
new "Kunne ikke oprette forbindelse til Ren'Py Sync-serveren."
|
||||
|
||||
# renpy/common/00sync.rpy:192
|
||||
old "The Ren'Py Sync server timed out."
|
||||
new "Der opstod et forbindelsestimeout til Ren'Py Sync-serveren."
|
||||
|
||||
# renpy/common/00sync.rpy:194
|
||||
old "An unknown error occurred while connecting to the Ren'Py Sync server."
|
||||
new "En ukendt fejl opstod under forbindelse til Ren'Py Sync-serveren."
|
||||
|
||||
# renpy/common/00sync.rpy:267
|
||||
old "The Ren'Py Sync server does not have a copy of this sync. The sync ID may be invalid, or it may have timed out."
|
||||
new "Ren'Py Sync-serveren har ikke en kopi af denne synkronisering. Synkroniserings-id'er er muligvis ugyldig eller udløbet."
|
||||
|
||||
# renpy/common/00sync.rpy:412
|
||||
old "Please enter the sync ID you generated.\nNever enter a sync ID you didn't create yourself."
|
||||
new "Indtast venligst dit genererede synkroniserings-id.\nIndtast aldrig et synkroniserings-id, som du ikke selv har oprettet."
|
||||
|
||||
# renpy/common/00sync.rpy:431
|
||||
old "The sync ID is not in the correct format."
|
||||
new "Synkroniserings-id'et er ikke i det korrekte format."
|
||||
|
||||
# renpy/common/00sync.rpy:451
|
||||
old "The sync could not be decrypted."
|
||||
new "Synkroniseringen kunne ikke afkodes."
|
||||
|
||||
# renpy/common/00sync.rpy:474
|
||||
old "The sync belongs to a different game."
|
||||
new "Synkroniseringen tilhører et andet spil."
|
||||
|
||||
# renpy/common/00sync.rpy:479
|
||||
old "The sync contains a file with an invalid name."
|
||||
new "Synkroniseringen indeholder en fil med et ugyldigt navn."
|
||||
|
||||
# renpy/common/00sync.rpy:532
|
||||
old "This will upload your saves to the {a=https://sync.renpy.org}Ren'Py Sync Server{/a}.\nDo you want to continue?"
|
||||
new "Dette sender dine gemmefiler til {a=https://sync.renpy.org}Ren'Py Sync-serveren{/a}.\nVil du gerne fortsætte?"
|
||||
|
||||
# renpy/common/00sync.rpy:561
|
||||
old "Enter Sync ID"
|
||||
new "Indtast Synkroniserings-id"
|
||||
|
||||
# renpy/common/00sync.rpy:572
|
||||
old "This will contact the {a=https://sync.renpy.org}Ren'Py Sync Server{/a}."
|
||||
new "Dette kontakter {a=https://sync.renpy.org}Ren'Py Sync-serveren{/a}."
|
||||
|
||||
# renpy/common/00sync.rpy:599
|
||||
old "Sync Success"
|
||||
new "Synkronisering lykkedes"
|
||||
|
||||
# renpy/common/00sync.rpy:602
|
||||
old "The Sync ID is:"
|
||||
new "Synkroniserings-id'et er:"
|
||||
|
||||
# renpy/common/00sync.rpy:608
|
||||
old "You can use this ID to download your save on another device.\nThis sync will expire in an hour.\nRen'Py Sync is supported by {a=https://www.renpy.org/sponsors.html}Ren'Py's Sponsors{/a}."
|
||||
new "Du kan bruge dette id til at hente din gemmefil på en anden enhed.\nDenne synkronisering udløber om en time.\nRen'Py Sync støttes af {a=https://www.renpy.org/sponsors.html}Ren'Pys sponsorer{/a}."
|
||||
|
||||
# renpy/common/00sync.rpy:612
|
||||
old "Continue"
|
||||
new "Fortsæt"
|
||||
|
||||
# renpy/common/00sync.rpy:634
|
||||
old "Sync Error"
|
||||
new "Synkroniseringsfejl"
|
||||
|
||||
# renpy/common/00iap.rpy:219
|
||||
old "Contacting App Store\nPlease Wait..."
|
||||
new "Kontakter App Store\nVent venligst..."
|
||||
|
||||
# renpy/common/00updater.rpy:419
|
||||
old "The Ren'Py Updater is not supported on mobile devices."
|
||||
new "Ren'Py-opdateringsprogrammet understøttes ikke på mobilenheder."
|
||||
|
||||
# renpy/common/00updater.rpy:548
|
||||
old "An error is being simulated."
|
||||
new "En fejl simuleres."
|
||||
|
||||
# renpy/common/00updater.rpy:738
|
||||
old "Either this project does not support updating, or the update status file was deleted."
|
||||
new "Enten understøtter dette projekt ikke opdatering, eller også er opdateringsstatusfilen blevet slettet."
|
||||
|
||||
# renpy/common/00updater.rpy:752
|
||||
old "This account does not have permission to perform an update."
|
||||
new "Denne bruger har ikke tilladelse til at udføre en opdatering."
|
||||
|
||||
# renpy/common/00updater.rpy:755
|
||||
old "This account does not have permission to write the update log."
|
||||
new "Denne bruger har ikke tilladelse til at skrive opdateringsloggen."
|
||||
|
||||
# renpy/common/00updater.rpy:783
|
||||
old "Could not verify update signature."
|
||||
new "Kunne ikke efterprøve opdateringssignaturen."
|
||||
|
||||
# renpy/common/00updater.rpy:1084
|
||||
old "The update file was not downloaded."
|
||||
new "Opdateringsfilen blev ikke hentet."
|
||||
|
||||
# renpy/common/00updater.rpy:1102
|
||||
old "The update file does not have the correct digest - it may have been corrupted."
|
||||
new "Opdateringsfilen har ikke det korrekte fingeraftryk - den er muligvis blevet korrupteret."
|
||||
|
||||
# renpy/common/00updater.rpy:1252
|
||||
old "While unpacking {}, unknown type {}."
|
||||
new "Under udpakning {}, ukendt type {}"
|
||||
|
||||
# renpy/common/00updater.rpy:1624
|
||||
old "Updater"
|
||||
new "Opdateringsprogram"
|
||||
|
||||
# renpy/common/00updater.rpy:1635
|
||||
old "This program is up to date."
|
||||
new "Dette program er ajour."
|
||||
|
||||
# renpy/common/00updater.rpy:1637
|
||||
old "[u.version] is available. Do you want to install it?"
|
||||
new "[u.version] er tilgængelig. Vil du gerne installere denne?"
|
||||
|
||||
# renpy/common/00updater.rpy:1639
|
||||
old "Preparing to download the updates."
|
||||
new "Forbereder hentning af opdateringerne."
|
||||
|
||||
# renpy/common/00updater.rpy:1641
|
||||
old "Downloading the updates."
|
||||
new "Henter opdateringerne."
|
||||
|
||||
# renpy/common/00updater.rpy:1643
|
||||
old "Unpacking the updates."
|
||||
new "Udpakker opdateringerne."
|
||||
|
||||
# renpy/common/00updater.rpy:1647
|
||||
old "The updates have been installed. The program will restart."
|
||||
new "Opdateringer er blevet installeret. Programmet genstarter."
|
||||
|
||||
# renpy/common/00updater.rpy:1649
|
||||
old "The updates have been installed."
|
||||
new "Opdateringerne er blevet installeret."
|
||||
|
||||
# renpy/common/00updater.rpy:1651
|
||||
old "The updates were cancelled."
|
||||
new "Opdateringerne blev annulleret."
|
||||
|
||||
# renpy/common/00gallery.rpy:627
|
||||
old "Image [index] of [count] locked."
|
||||
new "Billede [index] af [count] låst."
|
||||
|
||||
# renpy/common/00gallery.rpy:647
|
||||
old "prev"
|
||||
new "forrige"
|
||||
|
||||
# renpy/common/00gallery.rpy:648
|
||||
old "next"
|
||||
new "næste"
|
||||
|
||||
# renpy/common/00gallery.rpy:649
|
||||
old "slideshow"
|
||||
new "diasshow"
|
||||
|
||||
# renpy/common/00gallery.rpy:650
|
||||
old "return"
|
||||
new "tilbage"
|
||||
|
||||
# renpy/common/00director.rpy:1776
|
||||
old "Customize director.transforms to add more transforms."
|
||||
new "Tilpas director.transforms for at tilføje flere transformationer."
|
||||
|
||||
# renpy/common/00director.rpy:1821
|
||||
old "Click to set."
|
||||
new "Klik for at stille."
|
||||
|
||||
# renpy/common/00director.rpy:1822
|
||||
old "Customize director.transitions to add more transitions."
|
||||
new "Tilpas director.transitions for at tilføje flere overgange."
|
||||
|
||||
# renpy/common/00director.rpy:1845
|
||||
old "Customize director.audio_channels to add more channels."
|
||||
new "Tilpas director.audio_channels for at tilføje flere kanaler."
|
||||
|
||||
# renpy/common/00preferences.rpy:573
|
||||
old "restore window position"
|
||||
new "gendan vinduesplacering"
|
||||
|
||||
# renpy/common/00speechbubble.rpy:408
|
||||
old "(clear retained bubbles)"
|
||||
new "(opryd bibeholdte bobler)"
|
||||
|
||||
# renpy/common/00updater.rpy:504
|
||||
old "No update methods found."
|
||||
new "Ingen opdateringsmetoder fundet."
|
||||
|
||||
# renpy/common/00updater.rpy:551
|
||||
old "Could not download file list: "
|
||||
new "Kunne ikke hente filliste:"
|
||||
|
||||
# renpy/common/00updater.rpy:554
|
||||
old "File list digest does not match."
|
||||
new "Fillistens fingeraftryk matcher ikke."
|
||||
|
||||
# renpy/common/00updater.rpy:2072
|
||||
old "Preparing to download the game data."
|
||||
new "Forbereder hentning af spildataene."
|
||||
|
||||
# renpy/common/00updater.rpy:2074
|
||||
old "Downloading the game data."
|
||||
new "Henter spildataene."
|
||||
|
||||
# renpy/common/00updater.rpy:2076
|
||||
old "The game data has been downloaded."
|
||||
new "Spildataene er blevet hentet."
|
||||
|
||||
# renpy/common/00updater.rpy:2078
|
||||
old "An error occurred when trying to download game data:"
|
||||
new "En fejl opstod under forsøg på hentning af spildata:"
|
||||
|
||||
# renpy/common/00updater.rpy:2083
|
||||
old "This game cannot be run until the game data has been downloaded."
|
||||
new "Dette spil kan ikke køres, indtil spildataene er blevet hentet."
|
||||
|
||||
# renpy/common/00updater.rpy:2090
|
||||
old "Retry"
|
||||
new "Prøv igen"
|
||||
|
||||
# renpy/common/00accessibility.rpy:120
|
||||
old "Accessibility Menu. Use up and down arrows to navigate, and enter to activate buttons and bars."
|
||||
new "Tilgængelighedsmenu. Brug op- og ned-piletasterne til at navigere og retur til at aktivere knapper og bjælker."
|
||||
|
||||
# renpy/common/00gui.rpy:454
|
||||
old "Are you sure you want to continue where you left off?"
|
||||
new "Vil du gerne fortsætte fra hvor du slap?"
|
||||
|
||||
# renpy/common/00preferences.rpy:439
|
||||
old "self voicing enable"
|
||||
new "aktivér automatisk oplæsning"
|
||||
|
||||
# renpy/common/00preferences.rpy:441
|
||||
old "self voicing disable"
|
||||
new "deaktiver automatisk oplæsning"
|
||||
|
||||
# renpy/common/00preferences.rpy:456
|
||||
old "clipboard voicing enable"
|
||||
new "aktivér oplæsning af udklipsholder"
|
||||
|
||||
# renpy/common/00preferences.rpy:458
|
||||
old "clipboard voicing disable"
|
||||
new "deaktiver oplæsning af udklipsholder"
|
||||
|
||||
# renpy/common/00preferences.rpy:465
|
||||
old "debug voicing enable"
|
||||
new "aktivér oplæsning af fejlsøgning"
|
||||
|
||||
# renpy/common/00preferences.rpy:467
|
||||
old "debug voicing disable"
|
||||
new "deaktiver oplæsning af fejlsøgning"
|
||||
|
||||
# renpy/common/00preferences.rpy:596
|
||||
old "reset"
|
||||
new "nulstil"
|
||||
|
||||
@@ -1,278 +0,0 @@
|
||||
translate danish strings:
|
||||
|
||||
# renpy/common/_developer/developer.rpym:38
|
||||
old "Developer Menu"
|
||||
new "Udviklermenu"
|
||||
|
||||
# renpy/common/_developer/developer.rpym:43
|
||||
old "Interactive Director (D)"
|
||||
new "Interaktivt instruktionsværktøj (D)"
|
||||
|
||||
# renpy/common/_developer/developer.rpym:45
|
||||
old "Reload Game (Shift+R)"
|
||||
new "Genindlæs spil (Shift+R)"
|
||||
|
||||
# renpy/common/_developer/developer.rpym:47
|
||||
old "Console (Shift+O)"
|
||||
new "Konsol (Shift+O)"
|
||||
|
||||
# renpy/common/_developer/developer.rpym:49
|
||||
old "Variable Viewer"
|
||||
new "Variabelviser"
|
||||
|
||||
# renpy/common/_developer/developer.rpym:51
|
||||
old "Persistent Viewer"
|
||||
new "Vedvarende data-viser"
|
||||
|
||||
# renpy/common/_developer/developer.rpym:53
|
||||
old "Image Location Picker"
|
||||
new "Billedplaceringsvælger"
|
||||
|
||||
# renpy/common/_developer/developer.rpym:55
|
||||
old "Filename List"
|
||||
new "Filnavneliste"
|
||||
|
||||
# renpy/common/_developer/developer.rpym:59
|
||||
old "Show Image Load Log (F4)"
|
||||
new "Vis billedindlæsningslog (F4)"
|
||||
|
||||
# renpy/common/_developer/developer.rpym:62
|
||||
old "Hide Image Load Log (F4)"
|
||||
new "Skjul billedindlæsningslog (F4)"
|
||||
|
||||
# renpy/common/_developer/developer.rpym:65
|
||||
old "Image Attributes"
|
||||
new "Billedattributter"
|
||||
|
||||
# renpy/common/_developer/developer.rpym:70
|
||||
old "Speech Bubble Editor (Shift+B)"
|
||||
new "Talebobleredigering (Shift+B)"
|
||||
|
||||
# renpy/common/_developer/developer.rpym:97
|
||||
old "[name] [attributes] (hidden)"
|
||||
new "[name] [attributes] (skjult)"
|
||||
|
||||
# renpy/common/_developer/developer.rpym:101
|
||||
old "[name] [attributes]"
|
||||
new "[name] [attributes]"
|
||||
|
||||
# renpy/common/_developer/developer.rpym:151
|
||||
old "Nothing to inspect."
|
||||
new "Intet at inspicere."
|
||||
|
||||
# renpy/common/_developer/developer.rpym:162
|
||||
old "Hide deleted"
|
||||
new "Skjul slettede"
|
||||
|
||||
# renpy/common/_developer/developer.rpym:162
|
||||
old "Show deleted"
|
||||
new "Vis slettede"
|
||||
|
||||
# renpy/common/_developer/developer.rpym:318
|
||||
old "Rectangle copied to clipboard."
|
||||
new "Rektangel kopieret til udklipsholder."
|
||||
|
||||
# renpy/common/_developer/developer.rpym:321
|
||||
old "Position copied to clipboard."
|
||||
new "Position kopieret til udklipsholder."
|
||||
|
||||
# renpy/common/_developer/developer.rpym:333
|
||||
old "Rectangle: %r"
|
||||
new "Rektangel: %r"
|
||||
|
||||
# renpy/common/_developer/developer.rpym:336
|
||||
old "Mouse position: %r"
|
||||
new "Museposition: %r"
|
||||
|
||||
# renpy/common/_developer/developer.rpym:341
|
||||
old "Right-click or escape to quit."
|
||||
new "Højreklik eller escape for at afslutte."
|
||||
|
||||
# renpy/common/_developer/developer.rpym:389
|
||||
old "Type to filter: "
|
||||
new "Skriv for at filtrere: "
|
||||
|
||||
# renpy/common/_developer/developer.rpym:507
|
||||
old "Textures: [tex_count] ([tex_size_mb:.1f] MB)"
|
||||
new "Teksturer: [tex_count] ([tex_size_mb:.1f] MB)"
|
||||
|
||||
# renpy/common/_developer/developer.rpym:511
|
||||
old "Image cache: [cache_pct:.1f]% ([cache_size_mb:.1f] MB)"
|
||||
new "Billedcache: [cache_pct:.1f]% ([cache_size_mb:.1f] MB)"
|
||||
|
||||
# renpy/common/_developer/developer.rpym:521
|
||||
old "✔ "
|
||||
new "✔ "
|
||||
|
||||
# renpy/common/_developer/developer.rpym:524
|
||||
old "✘ "
|
||||
new "✘ "
|
||||
|
||||
# renpy/common/_developer/developer.rpym:529
|
||||
old "\n{color=#cfc}✔ predicted image (good){/color}\n{color=#fcc}✘ unpredicted image (bad){/color}\n{color=#fff}Drag to move.{/color}"
|
||||
new "\n{color=#cfc}✔ forudset billede (godt){/color}\n{color=#fcc}✘ uforudset billede (dårligt){/color}\n{color=#fff}Træk for at flytte.{/color}"
|
||||
|
||||
# renpy/common/_developer/inspector.rpym:38
|
||||
old "Displayable Inspector"
|
||||
new "Inspektion af visbare elementer"
|
||||
|
||||
# renpy/common/_developer/inspector.rpym:61
|
||||
old "Size"
|
||||
new "Størrelse"
|
||||
|
||||
# renpy/common/_developer/inspector.rpym:65
|
||||
old "Style"
|
||||
new "Stil"
|
||||
|
||||
# renpy/common/_developer/inspector.rpym:71
|
||||
old "Location"
|
||||
new "Placering"
|
||||
|
||||
# renpy/common/_developer/inspector.rpym:122
|
||||
old "Inspecting Styles of [displayable_name!q]"
|
||||
new "Inspicerer stile for [displayable_name!q]"
|
||||
|
||||
# renpy/common/_developer/inspector.rpym:139
|
||||
old "displayable:"
|
||||
new "visbart element:"
|
||||
|
||||
# renpy/common/_developer/inspector.rpym:145
|
||||
old " (no properties affect the displayable)"
|
||||
new " (ingen egenskaber påvirker det visbare element)"
|
||||
|
||||
# renpy/common/_developer/inspector.rpym:147
|
||||
old " (default properties omitted)"
|
||||
new " (standardegenskaber udeladt)"
|
||||
|
||||
# renpy/common/_developer/inspector.rpym:185
|
||||
old "<repr() failed>"
|
||||
new "<repr() mislykkedes>"
|
||||
|
||||
# renpy/common/00console.rpy:537
|
||||
old "Press <esc> to exit console. Type help for help.\n"
|
||||
new "Tryk <esc> for at gå ud af konsol. Skriv help for hjælp.\n"
|
||||
|
||||
# renpy/common/00console.rpy:541
|
||||
old "Ren'Py script enabled."
|
||||
new "Ren'Py-script aktiveret" #manuskripterne eller scripting?
|
||||
|
||||
# renpy/common/00console.rpy:543
|
||||
old "Ren'Py script disabled."
|
||||
new "Ren'Py-script deaktiveret." #manuskripterne eller scripting?
|
||||
|
||||
# renpy/common/00console.rpy:793
|
||||
old "help: show this help\n help <expr>: show signature and documentation of <expr>"
|
||||
new "help: vis denne hjælp\n help <udtryk>: vis signaturen og dokumentationen for <udtryk>"
|
||||
|
||||
# renpy/common/00console.rpy:817
|
||||
old "Help may display undocumented functions. Please check that the function or\nclass you want to use is documented.\n\n"
|
||||
new "Hjælp viser muligvis udokumenterede funktioner. Tjek venligst, om den funktion\neller klasse, du gerne vil bruge, er dokumenteret.\n\n"
|
||||
|
||||
# renpy/common/00console.rpy:826
|
||||
old "commands:\n"
|
||||
new "kommandoer:\n"
|
||||
|
||||
# renpy/common/00console.rpy:836
|
||||
old " <renpy script statement>: run the statement\n"
|
||||
new " <renpy-script-ordre>: kør ordren\n"
|
||||
|
||||
# renpy/common/00console.rpy:838
|
||||
old " <python expression or statement>: run the expression or statement"
|
||||
new " <python-udtryk eller -ordre>: kør udtrykket eller ordren"
|
||||
|
||||
# renpy/common/00console.rpy:846
|
||||
old "clear: clear the console history"
|
||||
new "clear: ryd konsolhistorikken"
|
||||
|
||||
# renpy/common/00console.rpy:850
|
||||
old "exit: exit the console"
|
||||
new "exit: gå ud af konsollen"
|
||||
|
||||
# renpy/common/00console.rpy:858
|
||||
old "stack: print the return stack"
|
||||
new "stack: udskriv returstakken"
|
||||
|
||||
# renpy/common/00console.rpy:880
|
||||
old "load <slot>: loads the game from slot"
|
||||
new "load <plads>: indlæser spillet fra plads"
|
||||
|
||||
# renpy/common/00console.rpy:893
|
||||
old "save <slot>: saves the game in slot"
|
||||
new "save <plads>: gemmer spillet på plads"
|
||||
|
||||
# renpy/common/00console.rpy:904
|
||||
old "reload: reloads the game, refreshing the scripts"
|
||||
new "reload: genindlæser spiller og genopfrisker manuskripterne"
|
||||
|
||||
# renpy/common/00console.rpy:912
|
||||
old "watch <expression>: watch a python expression\n watch short: makes the representation of traced expressions short (default)\n watch long: makes the representation of traced expressions as is"
|
||||
new "watch <udtryk>: hold øje med et python-udtryk\n watch short: gør repræsentationen af sporede udtryk kort (standard)\n watch long: gør repræsentationen af sporede udtryk, som den er"
|
||||
|
||||
# renpy/common/00console.rpy:949
|
||||
old "unwatch <expression>: stop watching an expression"
|
||||
new "unwatch <udtryk>: stop med at holde øje med et udtryk"
|
||||
|
||||
# renpy/common/00console.rpy:995
|
||||
old "unwatchall: stop watching all expressions"
|
||||
new "unwatchall: stop med at holde øje med alle udtryk"
|
||||
|
||||
# renpy/common/00console.rpy:1016
|
||||
old "jump <label>: jumps to label"
|
||||
new "jump <label>: springer til mærkat"
|
||||
|
||||
# renpy/common/00console.rpy:1032
|
||||
old "short: Shorten the representation of objects on the console (default)."
|
||||
new "short: Forkort repræsentationen af objekter i konsollen (standard)."
|
||||
|
||||
# renpy/common/00console.rpy:1036
|
||||
old "long: Print the full representation of objects on the console."
|
||||
new "long: Skriv den fulde repræsentationen af objekter til konsollen."
|
||||
|
||||
# renpy/common/00console.rpy:1040
|
||||
old "escape: Enables escaping of unicode symbols in unicode strings."
|
||||
new "escape: Aktiverer omdannelsen af unicode-tegn til undvigesekvenser i unicode-strenge."
|
||||
|
||||
# renpy/common/00console.rpy:1044
|
||||
old "unescape: Disables escaping of unicode symbols in unicode strings and print it as is (default)."
|
||||
new "unescape: Deaktiverer omdannelsen af unicode-tegn til undvigesekvenser i unicode-strenge og skriver dem, som de er (standard)."
|
||||
|
||||
# renpy/common/_developer/developer.rpym:69
|
||||
old "Show Translation Identifiers"
|
||||
new "Vis oversættelsesidentifikatorer"
|
||||
|
||||
# renpy/common/_developer/developer.rpym:72
|
||||
old "Hide Translation Identifiers"
|
||||
new "Skjul oversættelsesidentifikatorer"
|
||||
|
||||
# renpy/common/_developer/developer.rpym:81
|
||||
old "Show Filename and Line"
|
||||
new "Vil filnavn og linje"
|
||||
|
||||
# renpy/common/_developer/developer.rpym:84
|
||||
old "Hide Filename and Line"
|
||||
new "Skjul filnavn og linje"
|
||||
|
||||
# renpy/common/_developer/developer.rpym:127
|
||||
old "Layer [l]:"
|
||||
new "Lag [l]:"
|
||||
|
||||
# renpy/common/_developer/developer.rpym:131
|
||||
old " [name] [attributes] (hidden)"
|
||||
new " [name] [attributes] (skjult)"
|
||||
|
||||
# renpy/common/_developer/developer.rpym:135
|
||||
old " [name] [attributes]"
|
||||
new " [name] [attributes]"
|
||||
|
||||
# renpy/common/_developer/developer.rpym:606
|
||||
old "\n{color=#fff}Copied to clipboard.{/color}"
|
||||
new "\n{color=#fff}Kopieret til udklipsholder.{/color}"
|
||||
|
||||
# renpy/common/_developer/developer.rpym:612
|
||||
old "\n{color=#fff}Click to copy.\nDrag to move.{/color}"
|
||||
new "\n{color=#fff}Klik for at kopiere.\nTræk for at flytte.{/color}"
|
||||
|
||||
# renpy/common/_developer/developer.rpym:657
|
||||
old "Click to open in editor."
|
||||
new "Klik for at åbne i tekstbehandler."
|
||||
|
||||