Compare commits

...

446 Commits

Author SHA1 Message Date
Asriel Senna 7d8644f2f8 Changelog and rework the section 2023-10-30 23:59:30 +01:00
Asriel Senna c7a284c1e4 Reimplement disable_on_enter based on new inputvalue implem 2023-10-30 23:55:21 +01:00
Tom Rothamel 8b79a3663c doc: Changelog the last few merges. 2023-10-30 17:56:30 -04:00
Elckarow fdf38adc83 sl2: copy_properties method for custom statements (#4839)
This takes the properties from another screen language statement.
2023-10-30 17:51:42 -04:00
Denys 921feb4ada Update Ukrainian translation. (#4996) 2023-10-30 17:50:14 -04:00
Gouvernathor f367e996aa Rework Inputvalues (#5080)
Add LocalVariableValue, factorize a bunch of code, use the same doc-generating model as with bar values.
2023-10-30 17:48:53 -04:00
Tom Rothamel c0c182d200 Merge pull request #5081 from renpy/language-basics
Rework the language basics page
2023-10-30 17:47:50 -04:00
Tom Rothamel a5fec5a7a5 rpy: Add a path if range queries are failing entirely. 2023-10-30 02:26:35 -04:00
Gouvernathor 0f668f2810 More tweaks 2023-10-30 00:57:00 +01:00
Asriel Senna 5cba186326 Chitespace, docstrings and unused ref anchors 2023-10-30 00:44:57 +01:00
Asriel Senna 74631fe701 Rework the language basics page 2023-10-30 00:44:26 +01:00
Tom Rothamel 13eeb9d0c0 update: Move prompt confirm to later in the update process.
Specifically, until after the update method has been selected, and
for rpu, after the files have been scanned and the size of the
download is known.

This will (shortly) let us give the user information about
download and disk size.
2023-10-29 14:55:39 -04:00
Tom Rothamel 1066157585 update: Call the old update format "zsync". 2023-10-29 12:25:22 -04:00
Tom Rothamel 7742627373 update: Add support for updating an empty directory.
That is, taking an empty alternate base directory and turning it
into a working game.
2023-10-28 22:34:27 -04:00
Tom Rothamel 95102a6b71 rpu: Add support for determining the download and disk size.
And pausing to prompt the user once that's known.
2023-10-28 22:30:14 -04:00
Tom Rothamel acac446c8b py2: Fix. 2023-10-28 13:35:41 -04:00
Tom Rothamel 9fc8e43f17 Merge branch 'fix' 2023-10-28 01:10:03 -04:00
Tom Rothamel 6d6003f4e0 sl2: Fix issue with transcludes and custom statements.
Fixes #5074.
2023-10-28 00:53:25 -04:00
Tom Rothamel 7ff1b3b5c5 Merge pull request #5057 from renpy/lint-toggles
Add lint toggles
2023-10-27 00:50:42 -04:00
Tom Rothamel 4fd318880e doc: LocalVariableValue 2023-10-27 00:48:56 -04:00
Gouvernathor 6cd57b192a Factorize and improve some Barvalue subclasses (#5064)
* factorize the most common BarValue subclasses, using the same recipe (but in a much simpler way) as in Action mixins #4473, which considerably shortens the code and avoids code duplication
* fix the kind parameter of FieldValue and VariableValue - it was saved but never passed to _get_field and _set_field
* fix some subclasses doc entries that weren't appropriately worded, some which set variables mentioned "fields"
* implement LocalVariableValue, supersede and fix add the LocalVariableValue action #5055, and allow it to have adequately-worded exceptions


Co-authored-by: Mal Graty <mal.graty@googlemail.com>
2023-10-27 00:47:21 -04:00
Tom Rothamel 8c6aab0bc6 Revert "add the LocalVariableValue action (#5055)"
This reverts commit 2451b3f0da.
2023-10-27 00:46:14 -04:00
Elckarow 2451b3f0da add the LocalVariableValue action (#5055) 2023-10-27 00:42:27 -04:00
Gouvernathor daea1375c3 Various tweaks and updates (#5069)
* Simplify currying in exports

* Unused import

* Docstrings, whitespace and exceptions

The docstrings were obsolete (not only buttons and hotspots take actions now, and they are now the core of screen language so nobody ignores what they are)
and the information that they can be subclassed is much more important in my view
Also these exceptions are better

* utflf
2023-10-27 00:41:32 -04:00
Tom Rothamel ada2b860f6 Merge pull request #5070 from qTich/fix/web-input-prompt
fix: translate `prompt` when used `web_input`
2023-10-27 00:41:09 -04:00
Tom Rothamel 6758447cad Merge pull request #5073 from renpy/pass-layeredimage
Re-add the pass keyword to the layeredimage language
2023-10-27 00:40:53 -04:00
Tom Rothamel 035d6e7b30 doc: Fetch. 2023-10-27 00:06:32 -04:00
Tom Rothamel 0675f6ce44 fetch: Fix fetching on the web platform. 2023-10-26 23:25:56 -04:00
Gouvernathor c7317c076b Re-add the pass keyword to the layeredimage language 2023-10-27 03:50:17 +02:00
Tom Rothamel 6bd420b421 fetch: Add the renpy.fetch function.
This is a single function that can fetch data from a web server,
optionally taking binary data or json as input, and returning its
result as bytes, text, or json-decoded data.
2023-10-26 00:59:19 -04:00
Tichq c1f2db72b5 fix: translate prompt when used web_input 2023-10-26 03:14:12 +03:00
Tom Rothamel f87917e399 Changes to android APK loading.
* Remove support for Expansion APKs. These have been obsolete for
  some time.

* Only use the APK for game files if an alternate base directory is
  not in use.
2023-10-25 19:52:33 -04:00
Gouvernathor 6bc50d6454 Make the doc generation of the mixined actions conditional 2023-10-25 19:44:55 +02:00
Tom Rothamel a2bfa02de1 doc: Fix caps on one line. 2023-10-25 09:38:10 -04:00
Gouvernathor a3ad875263 doc: fax the fox 2023-10-25 14:01:20 +02:00
Tom Rothamel 80d3075bf0 id: Force the director into lines mode after a reload.
Fixes #5028.
2023-10-25 01:08:44 -04:00
Tom Rothamel 0b213cc7db doc: Edits to the data accessor changelog. 2023-10-24 23:34:39 -04:00
Tom Rothamel 23fc2f66c9 Merge pull request #5067 from renpy/changelog-mixins
Changelog mixins
2023-10-24 20:40:20 -04:00
Ren'Py Bot 19498d5bce Merge branch 'fix' 2023-10-24 04:30:07 -04:00
Tom Rothamel 7f37382012 Avoid distributing web-icon.png in other formats. 2023-10-23 22:58:51 -04:00
Kassy 7fcad404b6 Web icon fix (#5018)
* Fix web icon name

The code looks for "web-icon.png" but the comment and function names
referred to "pwa_icon.png".

* Document the web icon customization
2023-10-23 22:58:20 -04:00
Tom Rothamel d8ad7d1672 Create checkpoints when hard skipping.
Fixes #5025.
2023-10-23 22:04:38 -04:00
Tom Rothamel e4b550b8c3 Create checkpoints when hard skipping.
Fixes #5025.
2023-10-23 22:04:01 -04:00
Gouvernathor 67bdb9ab03 Changelog mixins 2023-10-23 10:44:58 +02:00
Gouvernathor 6f44533bbe Mixin-Based Data Actions
This change replaces the implementation of the data actions with mixins, one for accessing the data and one for changing it. Doing so simplifies and regularizes the implementation, and makes it possible to add new types of data actions, like the Cycle and Increment actions added here.
2023-10-22 22:47:11 -04:00
Tom Rothamel d084b624b9 Merge branch 'fix' 2023-10-22 22:34:16 -04:00
Tom Rothamel 06d3a90bd7 Include checkpointing_suspended in the rollback objects.
It's possible (and had been for some time) to rollback into a spot
where checkpointing was suspended - but checkpointing would be re-enabled,
meaning that checkpoint_suspended would be forgotten.
2023-10-22 22:27:18 -04:00
Tom Rothamel 5e64403699 Fix issues with greedy rollback by default.
Fixes #5040, which was caused by the interaction of greedy rollbacl
and suspended rollback.

Fixes #5035, which was just never actually looking at the _greedy_rollback
variable.
2023-10-22 22:12:55 -04:00
Tom Rothamel d8954aa732 doc: Change links to Chinese documentation. 2023-10-21 01:52:15 -04:00
Tom Rothamel 669b07afef Add support for an alternate base directory.
This is a directory that is checked for update/version.json. If that
file exists and has versions that match the current engine, it is
used as the base directory instead of what the engine provides.

The main reason for this is to support Android games larger than
2GB, by letting those games download content.
2023-10-21 01:45:26 -04:00
Tom Rothamel e2bc3fb55c rpu: Write a version.json file into the update directory.
This file only exists if the update has finished successfully,
and contains the version information from updates.json.

It's intended to be used ot ensure that an alternate base
directory works with Ren'Py.
2023-10-20 23:24:07 -04:00
Tom Rothamel 088b1572ef rpy: Search for Ren'Py's update.pem in the base directory. 2023-10-20 00:31:30 -04:00
Gouvernathor a30f562d38 Resolve type issues 2023-10-19 21:23:42 +02:00
Ren'Py Bot 4768a012db Merge branch 'fix' 2023-10-19 04:30:07 -04:00
Tom Rothamel c7d8bd1dc9 rpu: Allow an update to be signed by multiple keys.
This provides a way to migrate old versions to a new key.
2023-10-19 01:27:07 -04:00
Tom Rothamel be226363e8 rpu: Include update information in all mutable formats. 2023-10-19 01:11:07 -04:00
Tom Rothamel 267a807146 rpu: Verify ECDSA signatures. 2023-10-19 00:53:17 -04:00
Tom Rothamel 34468cf57d Ignore update.pem. 2023-10-19 00:13:25 -04:00
Tom Rothamel 172f9f926e rpu: Create the updates.ecdsa file. 2023-10-19 00:06:14 -04:00
Tom Rothamel f6afced012 rpu: Include a verifying key with the built project. 2023-10-18 23:33:04 -04:00
Tom Rothamel 00cd7e4ada rpu: Generate update.pem when updates are enabled. 2023-10-18 23:13:52 -04:00
Tom Rothamel 9f4ab7d761 Always oversample SVGs.
Work to support placeholders lead to problems with SVG scaling
when the window scaled. This is resolved by scaling up the
placeholder, if required.

Fixes #5063.
2023-10-18 21:49:46 -04:00
Tom Rothamel 9ac420c259 id: Add lines explaining how to customize the director.
Fixes #5029.
2023-10-17 22:16:38 -04:00
Tom Rothamel 6c548d30ad Tweak the wording of the namespace error. 2023-10-17 00:02:26 -04:00
Tom Rothamel 615c6a728d Merge pull request #5059 from renpy/store.store
Prevent creation of a "store" substore
2023-10-17 00:01:12 -04:00
Gouvernathor bb8c508837 Prevent creation of a "store" substore 2023-10-16 18:45:43 +02:00
Tom Rothamel 30833f1f5f Allow renpy.utter_restart(True) to work on mobile. 2023-10-16 00:48:16 -04:00
Tom Rothamel 57bf18c705 rpu: Support for adding DLC. 2023-10-16 00:48:01 -04:00
Tom Rothamel e30855cb93 rpu: In the launcher, allow the website to pick rpu/zsync.
This makes it possible to deploy rpu, but have a fallback if
problems are reported.
2023-10-15 21:01:36 -04:00
Tom Rothamel ed0f536853 Merge branch 'fix' 2023-10-15 20:54:08 -04:00
Tom Rothamel e4f601310b Fix an issue with projects.txt.
Specifically, /path/to/mygame/ would produce a game with an
empty name, rather than mygame.
2023-10-15 20:54:00 -04:00
Tom Rothamel b92107dd02 rpu: Fix multi-part downloading. 2023-10-15 20:38:02 -04:00
Tom Rothamel 19c5e806b1 rpu: Add progress and logging to the updater. 2023-10-15 19:39:33 -04:00
Tom Rothamel f6d9762c5c rpu: Integrate rpu with the existing updater. 2023-10-15 15:58:01 -04:00
Ren'Py Bot 8b679cca71 Merge branch 'fix' 2023-10-15 04:30:07 -04:00
Tom Rothamel 34fe132e55 rpu: Use an environment variable to enable RPU. 2023-10-14 23:40:12 -04:00
Gouvernathor 0036bc3fe9 Optional plural 2023-10-15 03:52:22 +02:00
Gouvernathor d321d1ff51 Implement character char and word counts
fixes #5001
2023-10-15 03:46:44 +02:00
Gouvernathor a98d15eed4 Toggle the builtins-in-parameters check
disabled by default
2023-10-15 03:22:40 +02:00
Gouvernathor 57dba01448 Merge branch 'lint-parameter-names' into lint-toggles 2023-10-15 03:19:14 +02:00
Gouvernathor 3532b589d5 Implement lint options and make orphan tl check optional 2023-10-15 03:15:31 +02:00
Gouvernathor 51cc42a901 Base GUI entries for lint toggles 2023-10-15 02:33:51 +02:00
Gouvernathor e09b87dffe typos 2023-10-15 02:27:13 +02:00
Gouvernathor c5752398b9 doc: extend new im wording to the displayables page 2023-10-15 02:23:44 +02:00
Ren'Py Bot d5fe9c9a11 Merge branch 'fix' 2023-10-14 04:30:07 -04:00
Tom Rothamel b8ca91a62a bubble: Make available outside the overlay.
Fixes #5052.
2023-10-13 23:47:56 -04:00
Tom Rothamel 5aec41f9e1 bubble: Make available outside the overlay.
Fixes #5052.
2023-10-13 23:47:03 -04:00
Tom Rothamel b1d1db1075 doc: Finish documenting shaper. 2023-10-13 08:35:35 -04:00
Tom Rothamel 2708eda3d3 py2: Make generating rpus work with py2. 2023-10-13 08:35:35 -04:00
Ren'Py Bot ad83e0354e Merge branch 'fix' 2023-10-12 04:30:07 -04:00
Tom Rothamel 1c823a4e7c rpu: Support compressed segments. 2023-10-12 00:23:52 -04:00
Tom Rothamel 11186c86ae rpu: Allow rpus to be created in parallel. 2023-10-11 22:44:08 -04:00
Tom Rothamel 6014bfa007 Use the correct variable to configure the package formats. 2023-10-11 22:44:08 -04:00
Gouvernathor ef0d2db11d Fix translation 2023-10-11 18:23:50 +02:00
Ren'Py Bot 7dd1db50d2 Merge branch 'fix' 2023-10-11 04:30:07 -04:00
Tom Rothamel c8413912d2 Add a "Skip splashscreen" preference to the launcher.
This sets RENPY_SKIP_SPLASHSCREEN, which causes Ren'Py to
skip the splashscreen label.
2023-10-10 22:34:30 -04:00
Tom Rothamel 969b2ccecf SyntaxError: Invalid syntax. 2023-10-10 21:22:01 -04:00
Tom Rothamel 228c72f6c1 rpu: Create FileLists from the current.json file. 2023-10-10 20:40:44 -04:00
Tom Rothamel ad9af51510 rpu: Enable building rpus for the SDK. 2023-10-10 20:40:44 -04:00
Tom Rothamel 45faa71da8 Merge pull request #5045 from mal/layer-clip-docs
Clarify clipping behaviour as it relates to layers and Layer
2023-10-10 20:07:45 -04:00
Tom Rothamel e530c78cc0 Merge pull request #5050 from renpy/im-directives
Add warnings and deprecated sections for image manipulators
2023-10-10 20:07:27 -04:00
Gouvernathor c3a26654f7 Update the transform property 2023-10-11 00:52:20 +02:00
Gouvernathor 14710606ef doc: heavy-handed use of the deprecated directive 2023-10-11 00:41:13 +02:00
Gouvernathor 72cb9e3929 doc: Add warnings to the im page 2023-10-11 00:38:55 +02:00
Ren'Py Bot 9c3b082260 Merge branch 'fix' 2023-10-10 04:30:07 -04:00
Mal Graty 46cfe60086 doc: Signature for Layer displayable 2023-10-09 14:06:35 +01:00
Mal Graty 588d1e1137 doc: Warn of layer_clipping precedence in Layer 2023-10-09 14:04:14 +01:00
Mal Graty 15a12efd61 doc: Clarify behaviour there's no layer_clipping 2023-10-09 14:04:14 +01:00
Gouvernathor b0434ea1d9 doc: Update a link that was a teensy bit outdated 2023-10-09 14:58:47 +02:00
Tom Rothamel 96b44cdcb2 Add Show Filename and Line
A new option in the developer menu that shows the filename and line,
and lets you click to edit.
2023-10-08 21:58:44 -04:00
Tom Rothamel 1fde5f985a Add the EditFile action.
This opens the filename and line in a text editor,
if possible.
2023-10-08 21:54:43 -04:00
Tom Rothamel 34dcdc7f0e rpu: Include rpu update information in update.json. 2023-10-07 15:47:49 -04:00
Tom Rothamel 2238b460a7 rpu: Provide progress when making the update.
This was required because previously, only writing the files
made progress.
2023-10-07 10:37:47 -04:00
Tom Rothamel ff8ba2d10c rpu: Start integration with distribute.
This can now create the rpu files for a game.
2023-10-06 23:58:30 -04:00
Tom Rothamel 5e3495c333 rpu: Soft limit to 50MB.
While a big file will let the .rpy get bigger, this makes sure that
two files won't be downloaded when one will do.
2023-10-06 23:23:02 -04:00
Tom Rothamel 79945d9649 rpu: Prepare to generate updates.
* Add FileList.add_file and FileList.add_directory.
* Prepend the rpus with the name of the list that creates them.
* Delete obsolete rpus.
2023-10-06 22:57:23 -04:00
Tom Rothamel 254fa8c4ee Add missing encoding field to StdioRedirector.
Fixes a crash in compileall.
2023-10-06 22:56:32 -04:00
Tom Rothamel 14b596e15a Change the Sphinx search role.
Fixes #5037.
2023-10-05 21:59:56 -04:00
Tom Rothamel f33aef4711 Use premultiplied alpha for underline and strikethrough.
Fixes #5033.
2023-10-04 23:17:56 -04:00
Tom Rothamel 7d86e8509a rpu: Move to the renpy.update module. 2023-10-04 22:20:35 -04:00
Tom Rothamel dd26bbf859 rpu: Combine filetypes and util into a single common file. 2023-10-04 01:08:59 -04:00
Tom Rothamel cde1f301d2 rpu: Manage the executable bit.
Also, remove mtime from the json files.
2023-10-03 22:06:23 -04:00
Tom Rothamel 2d8e7d3a1a rpu: Create empty new files. 2023-10-02 23:24:08 -04:00
Tom Rothamel 1c2b3ed231 rpu: Delete files when not necessary, or at the end. 2023-10-02 23:14:43 -04:00
Tom Rothamel 021aa22a80 rpu: Add logging and deletion infrastructure. 2023-10-01 22:50:44 -04:00
Tom Rothamel e548ed13b2 rpu: Add support for reporting progress. 2023-10-01 01:21:03 -04:00
Tom Rothamel b1cf7eb64e Merge pull request #5020 from qTich/fix/pad_enabled
fix: `pad_enabled` instance type,, since can contains `all`
2023-09-30 19:42:55 -04:00
Tom Rothamel 86e9454b79 Merge pull request #5024 from Kassy2048/ipad-detect
New detection for iPad
2023-09-30 19:42:31 -04:00
Kassy f7d2434dff New detection for iPad
Since iOS 13, Safari uses the desktop mode by default on iPad which removes
the "iPad" string from the User-Agent header. The new detection looks for
an Apple product with touch enabled (there is no MacOS product with touch
screens AFAIK).
2023-10-01 01:36:00 +02:00
Tichq 07c96e8872 fix: pad_enabled instance type,, since can contains all 2023-09-30 11:50:27 +03:00
Tom Rothamel 0f51948f80 Search the interpolate namespace for variables during interpolation. 2023-09-29 22:04:43 -04:00
Ren'Py Bot 8448808c67 Merge branch 'fix' 2023-09-29 04:30:08 -04:00
Tom Rothamel 8da08dd187 Restore a (now simpler) render_movie.
Since this function is used to show cutscenes.

Fixes #5015.
2023-09-28 21:16:07 -04:00
Tom Rothamel eedc86f20e rpu: Use range downloads to get block files. 2023-09-28 01:25:10 -04:00
Tom Rothamel ccb978f454 rpu: Add a progress callback to the downloader. 2023-09-28 00:16:26 -04:00
Tom Rothamel 33830fd6b4 rpu: Start work on range download library. 2023-09-27 23:45:33 -04:00
Tom Rothamel 615c624a70 Revert "Call functions when interpolating."
This reverts commit 4d8d065c89.
2023-09-27 22:00:14 -04:00
Gouvernathor 66f17ae6dd Avoid using non-f-string interpolation syntax 2023-09-27 23:34:01 +02:00
Ren'Py Bot 7bf7303f85 Merge branch 'fix' 2023-09-27 04:30:08 -04:00
Tom Rothamel 4d8d065c89 Call functions when interpolating.
This will make it possible to have accessors when interpolating.

Fixes #5009.
2023-09-27 01:46:24 -04:00
Tom Rothamel fc111cc0ab Merge pull request #5007 from renpy/custom-text-tags
Custom text tags
2023-09-26 21:32:44 -04:00
Gouvernathor b535e2b63c Disown dynamic ctt, add warning markup, remove useless label 2023-09-27 00:28:39 +02:00
Tom Rothamel fb294567fe rpu: Execute the plan.
This involves copying data from the source files, including block
files that are on disk, into the target files, and then renaming
those files when done.
2023-09-26 02:26:00 -04:00
Tom Rothamel b2893b5ea8 rpu: Create plan.
The plan decides where to get data from, and write it to.
2023-09-25 23:58:33 -04:00
Tom Rothamel 5853d63076 Make sure the .rpa padding is taken fron a local file.
This will prevent a block file from being downloaded just to gain
access to the .rpa padding.
2023-09-25 22:39:23 -04:00
Tom Rothamel bd7cd2a0c6 rpu: Consider .new.rpu files as sources for update data.
So as to recover from a failed update.
2023-09-24 09:34:41 -04:00
Tom Rothamel 9d42e5c667 rpu: Log the initial evaluation of files. 2023-09-24 01:51:20 -04:00
Tom Rothamel ab9ee40e1a rpy: Add the ability to unserialize the file types. 2023-09-24 01:51:20 -04:00
Ren'Py Bot 0aa28b6804 Merge branch 'fix' 2023-09-23 04:30:07 -04:00
Tom Rothamel 0891fff309 rpu: Generate the block files and index.
The block files contain the deduplicated segments, while the
index stores where segments are in the destination files and
the block files.
2023-09-23 01:37:30 -04:00
Tom Rothamel ca37ebacd1 rpu: Add support for scanning a .rpa file. 2023-09-22 23:29:34 -04:00
Tom Rothamel 51aff5d94d rpu: Initial work, scanning directoriues to create files. 2023-09-22 22:22:36 -04:00
Gouvernathor 7ff6acc36c french translations 2023-09-23 01:44:00 +02:00
Denys cda1c18e03 Update Ukrainian translations (#4993) 2023-09-23 00:46:53 +02:00
Denys 7f615dcddc Update error.rpy (#4992) 2023-09-22 16:43:52 +02:00
Ren'Py Bot 646a7535b0 Merge branch 'fix' 2023-09-22 04:30:08 -04:00
Tom Rothamel 4600339cf7 Update the .gitignore. 2023-09-21 22:58:44 -04:00
Tom Rothamel 03fdf2801e Simplify how Movie's size argument works.
Previously it had been its own code path, but now the movie
is rendered normally and then resized. Fixes #4983.
2023-09-21 22:45:29 -04:00
Ren'Py Bot e427b4e910 Merge branch 'fix' 2023-09-21 04:30:08 -04:00
Gouvernathor 0826778a5e Add a warning when creating new projects in renpy 7
not disabled, because it still makes sense for test purposes or to update default files
2023-09-21 04:30:07 -04:00
Gouvernathor 5050ee20ee doc typo 2023-09-21 04:30:07 -04:00
Tom Rothamel b4cf19cb0c Filter out the _sustain tag when choosing live2d attributes.
Fixes #4984.
2023-09-20 23:06:25 -04:00
Tom Rothamel dee35cfc66 Allow the DPI of SVG images to be selected.
With a new DPI parameter to Image. Fixes #4832.
2023-09-19 22:53:58 -04:00
Gouvernathor 325175f3c3 Better detect when the console should expect a new line before executing the block (#4834)
* Make blocks with comments open a newline in the console

* Support decorators in the console

* Support ending lines with \ in the console
2023-09-19 21:59:28 -04:00
Tom Rothamel 787f09929b Merge pull request #4691 from renpy/warn-new-renpy7
Add a warning when creating new projects in renpy 7
2023-09-19 21:51:14 -04:00
Vladya c5f9ddeae8 "Backslash in filename" fix
Fixes "Backslash in filename" error, when using method from pkgutil.get_data and similar.
2023-09-19 21:49:11 -04:00
Tom Rothamel c43c34c943 Merge pull request #4966 from NyashniyVladya/patch-1
RenpyImporter.get_data fix
2023-09-19 21:47:35 -04:00
Gouvernathor ce07691bbc doc typo 2023-09-19 16:12:41 +02:00
brainos233 9e76a31da1 doc: add missing reference end-string (#4978) 2023-09-19 16:12:16 +02:00
Gouvernathor 90701d03c1 doc typo 2023-09-19 16:10:28 +02:00
Ren'Py Bot 0256f2bba7 Merge branch 'fix' 2023-09-19 04:30:56 -04:00
Tom Rothamel a12d9c544e Bump fix branch versions. 2023-09-19 00:12:40 -04:00
Tom Rothamel 8bb5291324 doc: Update sponsor doc. 2023-09-18 22:27:29 -04:00
Tom Rothamel 186b082e3c Finish changelog. 2023-09-18 22:24:59 -04:00
Tom Rothamel 4dd40736a1 live2d: Install 32-bit windows library.
Fixes #4953.
2023-09-18 22:22:08 -04:00
Tom Rothamel 8c3741e6f9 Fix a truly ancient translation bug. 2023-09-18 18:34:41 -04:00
Gouvernathor 6e7f28f574 Update the ShowMenu doc entry (#4967)
better apparent signature, better documenting how args and kwargs work
2023-09-18 18:22:46 -04:00
Gouvernathor fa24e47d29 Update the ShowMenu doc entry (#4967)
better apparent signature, better documenting how args and kwargs work
2023-09-18 18:22:12 -04:00
Ren'Py Bot 12acbfc963 Merge branch 'fix' 2023-09-18 04:30:45 -04:00
Tom Rothamel 8e1945a628 Mark a context as non interacting when it's copied for rollback.
This is an attempt at fixing a stack trace that ended wit:

  File "C:\renpy\renpy\ui.py", line 299, in interact
    rv = renpy.game.interface.interact(roll_forward=roll_forward, **kwargs)
  File "C:\renpy\renpy\display\core.py", line 3556, in interact
    raise Exception("Cannot start an interaction in the middle of an interaction, without creating a new context.")
Exception: Cannot start an interaction in the middle of an interaction, without creating a new context.

After an autoreload.
2023-09-17 22:57:02 -04:00
Ren'Py Bot fdae52f6ad Merge branch 'fix' 2023-09-17 04:30:56 -04:00
Tom Rothamel 4b47d59b3b steam: Make the steam deck keyboard a lot less complicated.
This disables:

* Automatic showing the keyboard if the player dismisses it.
* Shifting the screen to where the keyboard is,.

And it changes the keyboard area to the top of the screen.

The goal of this is to work around recently-introduced issues with
Steam Deck keyboard handling.
2023-09-16 23:45:59 -04:00
Tom Rothamel 9cde14fa6d steam: Make the steam deck keyboard a lot less complicated.
This disables:

* Automatic showing the keyboard if the player dismisses it.
* Shifting the screen to where the keyboard is,.

And it changes the keyboard area to the top of the screen.

The goal of this is to work around recently-introduced issues with
Steam Deck keyboard handling.
2023-09-16 23:41:57 -04:00
Tom Rothamel a98fa5f7fe hb: Changelog. 2023-09-16 20:07:15 -04:00
Tom Rothamel bf0bc1d837 Update to Emoji 15.1. 2023-09-16 18:44:36 -04:00
Tom Rothamel db24c1eb3a hb: Document variable fonts. 2023-09-16 00:59:20 -04:00
Tom Rothamel c8a88bb17e hb: Fix minor issue with variable fonts and text tags. 2023-09-16 00:41:33 -04:00
Tom Rothamel 9c73cff8ce hb: Add renpy.variable_font_info().
A function that returns information about a variable font, so
that the creator knows how Ren'Py understands their font.
2023-09-15 01:11:22 -04:00
Ren'Py Bot 5a4b14009e Merge branch 'fix' 2023-09-14 23:23:49 -04:00
Tom Rothamel a4cb1a123e Avoid calling interface.timeout when the interface isn't ready.
Fixes #4968.
2023-09-14 18:15:42 -04:00
Tom Rothamel 057f69602b Avoid nicing distribute. 2023-09-14 17:57:59 -04:00
Ren'Py Bot 2040da88db Merge branch 'fix' 2023-09-14 04:30:56 -04:00
Tom Rothamel 82de5b7af0 Revert "Enable or disable text input as required."
This reverts commit a65cecfc18.

Bug #4784 is implicated in this, but there's a new fix
in the form of an SDL patch.
2023-09-14 01:10:15 -04:00
Tom Rothamel a688409eb9 hb: Implement the axis: and instance text tags.
These let the user set an axis or an instance of a variable
font.
2023-09-13 09:08:15 -04:00
Tom Rothamel 2ec1cb0cdc hb: Make font variations case insensitive. 2023-09-13 08:20:37 -04:00
Tom Rothamel a73082df6f hb: Add styles to support variable fonts. 2023-09-13 01:21:17 -04:00
Tom Rothamel e33888949a hb: Construct the harfbuzz face after the variable font stuff is set.
This fixes issues where some data appears to remain cached.
2023-09-13 01:15:48 -04:00
Tom Rothamel 6422153ce1 hb: Allow instances and axis values of variable fonts to be specified. 2023-09-12 23:32:46 -04:00
Tom Rothamel 9d1263d7c3 hb: Add the first support for font instances and variations. 2023-09-12 00:38:35 -04:00
Tom Rothamel 608cbab10c Use the correct key. 2023-09-11 21:16:02 -04:00
Tom Rothamel 2b08c40a4a Add config.font_hinting.
This is a single variable that allows all uses of a font to
have the same hinting, which styles can override.
2023-09-10 22:06:56 -04:00
Vladya bd01358481 "Backslash in filename" fix
Fixes "Backslash in filename" error, when using method from pkgutil.get_data and similar.
2023-09-10 23:30:12 +03:00
Tom Rothamel 1e518437dd Revert to using the "auto" hinter.
With renpy/renpy-build@ebe6d6a01a,
the autohinter looks quite good on Latin and non-Latin languages,
so we should continue to prefer it.
2023-09-10 00:54:04 -04:00
Tom Rothamel 2096c691ce Implement the auto-light hinting mode.
This isn't going to be the default, but it is available if creators
want it.
2023-09-10 00:46:17 -04:00
Tom Rothamel 6b4eadf0fd Merge branch 'fix' 2023-09-09 21:52:51 -04:00
Tom Rothamel fc8ac8344d Allow a checkpoint to become a checkpoint.
A checkpoint is created when a failed rollback happens, and probably
also when saving. When that statement finishes, we'd want the
checkpoint to become hard.

Fixes #4963.
2023-09-09 20:47:12 -04:00
Ren'Py Bot f6d8afdad6 Merge branch 'fix' 2023-09-09 04:30:55 -04:00
Tom Rothamel 420564557f Force the gl2 renderer to be used on macOS.
This is because there are major macOS-relevant fixes to the renderer,
such as the fix for #4155, that don't make sense to backport to the
gl renderer.
2023-09-08 22:32:17 -04:00
Ren'Py Bot a8cab927bb Merge branch 'fix' 2023-09-08 04:30:56 -04:00
Tom Rothamel 81ee1d994e Fix "attempted relative import with no known parent package"
By setting __package__ correctly. Fixes #4961.
2023-09-08 01:29:30 -04:00
Tom Rothamel 52c1e64b6f steam: Force the window to be the top of screen.
The previous code could - and would - put the text onto the bottom
half of the screen, which popped the textbox up on the top half,
which looked terrible. By forcing the location, the end result
looks much nicer.

Fixes #4959.
2023-09-07 23:24:33 -04:00
Gouvernathor 3ef755d5d4 Merge branch 'fix' 2023-09-06 21:34:15 +02:00
Gouvernathor e719dd8d47 doc: fixes
fixes #4956 and something that was only fixed on master
2023-09-06 21:33:00 +02:00
Gouvernathor ab3f1242d7 Merge branch 'fix' 2023-09-06 11:21:04 +02:00
Gouvernathor 8df82cbac0 doc: fixes and improvements on the midnight commit 2023-09-06 11:20:34 +02:00
Gouvernathor 13c557b5a2 doc: remove duplication 2023-09-06 10:57:11 +02:00
Ren'Py Bot 44e32ddffb Merge branch 'fix' 2023-09-06 04:30:56 -04:00
Tom Rothamel 852b489529 Bump versions. 2023-09-06 00:46:03 -04:00
Tom Rothamel 3ff08b9c03 Produce a valid-ish version for pygame_sdl2. 2023-09-06 00:46:03 -04:00
Tom Rothamel 67c2469d37 doc: Changelog. 2023-09-06 00:46:03 -04:00
Tom Rothamel 60745892c1 Bump versions. 2023-09-06 00:44:45 -04:00
Ren'Py Bot 96c63ea9e2 Merge branch 'fix' 2023-09-05 04:30:56 -04:00
Ren'Py Bot 232eda7c98 Merge branch 'fix' 2023-09-03 20:24:29 -04:00
Ren'Py Bot 6c1f1c8138 Merge branch 'fix' 2023-09-02 18:08:22 -04:00
Ren'Py Bot 1102904380 Merge branch 'fix' 2023-09-02 04:30:37 -04:00
Ren'Py Bot d3d33f31bb Merge branch 'fix' 2023-08-31 19:10:28 -04:00
Ren'Py Bot 517e12d3e5 Merge branch 'fix' 2023-08-31 04:30:38 -04:00
Tom Rothamel 29e84de69c Merge pull request #4946 from the66F95/_InputValueAction
Update 00inputvalues.rpy
2023-08-31 00:41:17 -04:00
the66F95 e8de3df9c4 Update 00inputvalues.rpy
restores the previous behavior to be able to deactivate InputValue objects via "Enter".
for example, the current behavior can be seen in any project using the button that wraps the FilePageNameInputValue object.
2023-08-31 02:00:25 +02:00
Ren'Py Bot f0ba1ac5a1 Merge branch 'fix' 2023-08-30 04:30:38 -04:00
Ren'Py Bot 12dbf12f44 Merge branch 'fix' 2023-08-29 04:30:37 -04:00
Ren'Py Bot 18d3f48686 Merge branch 'fix' 2023-08-28 04:30:38 -04:00
Ren'Py Bot 86d0e1e275 Merge branch 'fix' 2023-08-27 04:30:37 -04:00
Tom Rothamel 935173fcf5 Create pax-format tar files.
These files support utf-8, so more files.
2023-08-27 00:34:46 -04:00
Tom Rothamel 3dd1d32ef4 hbfont: Eliminate type mixing.
And remove a warning on build.
2023-08-26 01:14:29 -04:00
Tom Rothamel 73725e2131 The new CopyToClipboard action copies text to the clipboard.
Fixes #4868.
2023-08-25 21:59:31 -04:00
Ren'Py Bot c29b180647 Merge branch 'fix' 2023-08-25 04:30:38 -04:00
Tichq 7045295eee fix: missed changes in beceb10166 (#4931) 2023-08-24 10:33:22 +02:00
Ren'Py Bot 3d1ab26448 Merge branch 'fix' 2023-08-23 04:30:37 -04:00
Tom Rothamel 80e2d1d3a9 Merge branch 'fix' 2023-08-22 22:18:21 -04:00
Tom Rothamel 2ccdb65cc5 Merge pull request #4927 from the66F95/autosave
respect "renpy.store._autosave"
2023-08-22 21:03:30 -04:00
the66F95 6135979a7c respect "renpy.store.__autosave"
respect the renpy.store._autosave value when calling renpy.loadsave.force_autosave() directly.
could be annoying if you have a choice menu in a splashscreen for instance.
2023-08-22 12:03:03 +02:00
Tom Rothamel 9c5b8f5ba4 Merge pull request #4914 from mithost/4872-fix
Revert removal of `loop_only` to fix `if_changed` loop seam issue
2023-08-20 22:27:14 -04:00
Tom Rothamel 3793a3f9ba Filter PYTHON* variables when launching a project.
While renpy.exe is set up to ignore the variables, renpython isn't.
By filtering them, we can be sure that system python variables
don't break the SDK.

Fixes #4878.
2023-08-19 21:18:53 -04:00
Tom Rothamel b3beae8ab5 Merge pull request #4875 from kyouryuukunn/revert_order_of_xyzrotate_and_zpos
revert the applyment order of poi,orientation,xyzrotate and zpos
2023-08-18 21:10:53 -04:00
Tom Rothamel c8d451838b Store runtime and ctime in the json.
Fixes #4873.
2023-08-18 00:39:38 -04:00
Tom Rothamel 5cc6b34f56 Merge pull request #4891 from michaelsstuff/reny_base_fix
Edgecase with OneDrive vault
2023-08-18 00:12:27 -04:00
Tom Rothamel 4606bcce76 Merge pull request #4900 from renpy/fix-args-calls
Fix occurrences of *args following a kwarg in calls
2023-08-18 00:11:35 -04:00
Tom Rothamel 20ecec1412 Merge pull request #4902 from shawna-p/lint-fix2
Use the say_menu_text_filter when linting
2023-08-18 00:09:18 -04:00
Tom Rothamel ebeed588fa Merge pull request #4876 from renpy/call-word
Parse argument keywords as words instead of names
2023-08-18 00:08:50 -04:00
Tom Rothamel ef08ad74b3 harfbuzz: Use advance as width.
The advance is pretty close to to glyph width, and with the
advance at the end of ruby, means ruby will be centered
properly.

Fixes #4917.
2023-08-17 21:41:35 -04:00
Ren'Py Bot 0b47bc2eea Merge branch 'fix' 2023-08-17 04:30:37 -04:00
Ren'Py Bot 37d216097d Merge branch 'fix' 2023-08-16 04:30:38 -04:00
Tom Rothamel 16c2455370 Make _side_image_attribute and _side_image_attributes_reset context-dynamic.
This prevents a reset from happening in a menu context, and then
causing the reset to not happen in the main context, thus causing
the image to be shown at the wrong times.

Fixes #4828.
2023-08-15 22:50:16 -04:00
Joseph Boyd d30d986a51 Revert removal of loop_only to fix if_changed loop seam issue
Fixes #4872 by restoring the `loop_only` parameter of `enqueue()` in audio.py.

After commit `b0b096b`, calling `play` on a music track with the `if_changed` parameter would *always* add it to the queue, even if the same track was already currently playing on loop. This newly queued 'loop' would always begin at the very start of the track, **ignoring any user-defined `loop` point parameters** that were added by the user to allow the track to seamlessly loop. This caused the looping track to abruptly 'restart' the next time it would normally loop whenever a player visited a label that wanted that track to play if it wasn't already looping (for example, the 'market' example in Ren'Py's audio documentation page for `if_changed`).

This change restores the `loop_only` parameter and functionality to `enqueue()` and `play()` to avoid a track from being added to the queue and potentially abruptly restarting the track if it is currently already playing on loop. To attempt to preserve the fix the change `b0b096b` accomplished for #4695, the position/logic of the `c.dequeue()` calls in `music.play()` has not been reverted to it's previous behaviour.

Documentation wise, the Audio documentation page mentions that `if_changed` queues 'an additional loop' of the currently playing track when this situation is reached, which is only an accurate description if the currently looping track does not have a user defined `loop` point (or if the loop point was 0.0). A more accurate description is that the track is queued to play again from the very beginning after the current loop is complete, discarding any user-defined `loop` points. The issue mentioned above is reproduceable using the code present in the `if_changed` section of the audio page, provided the 'market' track has a non-zero `loop` parameter.
2023-08-15 12:40:45 -04:00
Tom Rothamel b5288343fc Merge branch 'fix' 2023-08-15 00:30:45 -04:00
Tom Rothamel 589a510c33 Merge branch 'fix' 2023-08-15 00:28:56 -04:00
Tom Rothamel b729eaed7d harfbuzz: Support computing text bounds. 2023-08-13 19:39:12 -04:00
Tom Rothamel d06bcd0dd2 harfbuzz: Supply slant information. 2023-08-12 22:34:18 -04:00
Tom Rothamel 2e34102cf5 doc: The emoji and harfbuzz changes. 2023-08-11 22:32:07 -04:00
Ren'Py Bot 3018c21828 Merge branch 'fix' 2023-08-11 04:30:38 -04:00
Tom Rothamel 457d0c6cce Remove useless line.
Fixes #4905.
2023-08-11 01:51:02 -04:00
Tom Rothamel e1b37142c2 harfbuzz: Enable by default on py3.
Also, enable bytecode hinting everywhere.
2023-08-10 00:54:11 -04:00
Tom Rothamel 32923dd8f8 harfbuzz: Add support for vertical text. 2023-08-09 23:48:12 -04:00
shawna-p e4bef44f2c Filter text for the lint check 2023-08-09 22:49:58 -04:00
Gouvernathor 8a151ed1b4 Fix occurrences of *args following a kwarg in calls 2023-08-09 23:18:48 +02:00
Tom Rothamel 54b4406974 Support building without harfbuzz on Ren'Py 7 web. 2023-08-08 22:45:16 -04:00
Tom Rothamel 4b3561d821 harfbuzz: Build by default. 2023-08-07 23:32:22 -04:00
Ren'Py Bot fb197d90b9 Merge branch 'fix' 2023-08-07 04:30:37 -04:00
Tom Rothamel d32f5b10f5 Fix alpha premultiplication in text.
Fixes #4880.
2023-08-07 01:12:42 -04:00
Ren'Py Bot 8d3401f2ac Merge branch 'fix' 2023-08-05 04:30:37 -04:00
Michael df26c03087 OneDrive uses virtualized file system (vault) so
we use __file__ to get the real path of the file.
2023-08-04 13:18:20 +02:00
Gouvernathor db8a6f9f13 Fixes to the changelog
Duplicate entries and typos
2023-08-04 09:03:31 +02:00
Tom Rothamel 25986373d1 Merge branch 'fix' 2023-08-03 08:11:51 -04:00
Tom Rothamel 547bef2483 Merge branch 'fix' 2023-08-01 22:23:28 -04:00
Tom Rothamel ff177d0dae harfbuzz: Allow the text shaper to be set by style.
This allows harfbuzz and non-harfbuzz text to coexist in the same
game, which is useful for testing during development. It will also
provide backwards compatibility when we change the default.
2023-08-01 22:01:15 -04:00
Moshibit 1dd5ba512c Spanish translation update for master (#4885)
This is specifically for strings added in master, it should not be copied into fix (it would work but it's pointless)
2023-08-02 03:49:20 +02:00
Tom Rothamel 3906f99ebc harfbuzz: Move to one hb_font_t per FBFont, size.
This generates sized and kerned text.
2023-07-31 22:06:19 -04:00
Tom Rothamel f89c672c13 iap: Call the backend request_review. 2023-07-30 21:07:04 -04:00
Tom Rothamel 80ea555ad6 harfbuzz: Use for glyph creation and rendering.
This is enough to generate shaped emjoi, but the text placement and
spacing is all wonky, so there's still a bunch of work to be done.
2023-07-30 19:53:21 -04:00
Tom Rothamel af7a0baa2e vscode: Correct arch on Linux.
Microsoft calls it x64, not x86_64, so use that.
2023-07-30 19:52:44 -04:00
Gouvernathor 3fa7da1384 Parse argument keywords as words instead of names 2023-07-29 17:44:01 +02:00
kyouryuukunn 014de8a873 revert the applyment order of poi,orientation,xyzrotate and zpos 2023-07-29 18:45:58 +09:00
Moshibit 89cfd03516 Spanish translation update (#4856)
* Update launcher.rpy

* Fix Spansih translation.rpy

* Update common.rpy

* Update gui.rpy

* Update launcher.rpy

* Update launcher.rpy
2023-07-26 22:31:06 -04:00
Tom Rothamel 0b937fdea6 hb: Add ability to create a font and use it to shape text. 2023-07-26 01:37:42 -04:00
Tom Rothamel 802652d579 py2: Add an encoding for the emoji trie. 2023-07-25 11:10:11 -04:00
Tom Rothamel bcd4fd4d2c harfbuzz: New font rendering module.
This will support harfbuzz, eventually. It will not support Python 2.
2023-07-25 02:06:37 -04:00
Tom Rothamel 21016cca6c Merge pull request #4863 from JamiesonC/JamiesonC-musicroom-1
MusicRoom: have queue_if_playing consider whether the currently playing track is in the playlist
2023-07-25 00:07:06 -04:00
Ren'Py Bot 1921e7c985 Merge branch 'fix' 2023-07-24 04:30:37 -04:00
Tom Rothamel 6be8d94372 Make sure each trie entry has a default. 2023-07-24 01:05:00 -04:00
Tom Rothamel 9f286afbbb Merge pull request #4862 from brainos233/fix-emoji-font-name
Use right name of emoji font
2023-07-23 20:45:30 -04:00
Tom Rothamel e32cd6c124 Merge pull request #4866 from mal/devtools-translate-id-screen
Tool to view and copy translation identifiers
2023-07-23 20:44:32 -04:00
Mal Graty f523069468 dev: Tool to view and copy translation identifiers 2023-07-24 01:07:10 +01:00
Tom Rothamel 674d5d87dd emoji: Automatically enable the emoji font.
This divides characters into emjoi and non-emoji runs based on
the information in the emoji trie, based on the unicode standard.
2023-07-23 02:13:41 -04:00
Tom Rothamel 12b37de529 emoji: Make the emoji trie use full characters. 2023-07-22 23:19:33 -04:00
JamiesonC 562ce9734a MusicRoom: have queue_if_playing consider whether the currently playing track is in the playlist
This change ensures that a MusicRoom queues up from the first track if the currently playing track is not part of the playlist. Enables the interjection of the MusicRoom into more complex scenarios (such as hotswapping MusicRooms to effect dynamic playlist changes).
2023-07-22 13:02:31 -05:00
Brainos 148a14f939 Rename Copyright 2023-07-22 23:38:18 +08:00
Brainos fe2ad45dc0 Use right name of emoji font 2023-07-22 23:25:20 +08:00
Tom Rothamel 17fb5397c8 emoji: Check the default font, create style entries. 2023-07-21 23:20:09 -04:00
Tom Rothamel 8398c3081e emoji: Add a trie of emoji characters.
The intent of this is to provide the data required to segment a
string into emoji and non-emoji characters, with varying levels
of strictness.
2023-07-21 01:44:35 -04:00
Tom Rothamel 71e8bae6d2 doc: Changelog last. 2023-07-20 22:06:17 -04:00
Gouvernathor d312de84a4 Add the renpy.confirm function. (#4583)
This is a function that takes the place of layout.yesno_screen, but isn't quite as weird.
2023-07-20 21:51:16 -04:00
Ren'Py Bot c3d1c2cee7 Merge branch 'fix' 2023-07-20 04:30:37 -04:00
Tom Rothamel 49eab5ffa9 Fix pickling issue.
Python 2 had a weird type for staticmethods, one that
couldn't be pickled. So we wrap compute_raw on Python 2.
2023-07-19 02:22:14 -04:00
Tom Rothamel 8a7fcd95ce Fix incorrect property. 2023-07-19 02:21:59 -04:00
Tom Rothamel d1ad17256b Prefix Ren'Py Sync styles with "sync".
Fixes #4847.
2023-07-18 23:22:12 -04:00
Tom Rothamel 4035fc9621 launcher: Detect and abort distributing the tutorial.
The tutrorial expects to be part of Ren'Py - the example system
may not work if it's being run outside of Ren'Py. This detects
attempts to distribute the tutorial on its own, and aborts the
process.
2023-07-18 23:17:46 -04:00
Tom Rothamel 3dab93666d Add from to calls for the Ren'Py tutorial. 2023-07-18 23:15:25 -04:00
Tom Rothamel ff64ab49f1 Allow loading premultiplied textures.
This is to support the changes to text that produced premultiplied
surfaces, rather than non-premultiplied surfaces. This was necessary
to display emoji properly, and also speeds up the case of normal
text rendering, at least on gl2.

This also removes some old paths that worked with GL_BGRA, which
we aren't using.
2023-07-18 01:25:52 -04:00
Tom Rothamel 15657da6dc Generate premultiplied alpha when rendering text.
This will allow us to skip the alpha premultiply step.
2023-07-17 23:32:59 -04:00
Tom Rothamel 872d4694db emoji: Add support for displaying BGRA glyphs. 2023-07-17 22:39:47 -04:00
Tom Rothamel 617c7e5367 emoji: Expose the BRGA data to the rest of the font system. 2023-07-16 21:42:20 -04:00
Tom Rothamel e1aff07997 Merge pull request #4844 from renpy/absolute-compute
Absolute compute
2023-07-15 20:46:59 -04:00
Gouvernathor 02e365584e doc: move last to dedicated doc section 2023-07-15 05:45:24 +02:00
Tom Rothamel ec8104ae6b Add renpy.reset_all_contexts().
This replaces all contexts with a single context that continues at
the next statement, to allow a game to clean up after load and
error.

Fixes #4476.
2023-07-14 23:36:41 -04:00
Ren'Py Bot df114f794e Merge branch 'fix' 2023-07-14 04:30:37 -04:00
Tom Rothamel 3a897f0fee Merge pull request #4837 from AbdulGoodLooks/patch-1
Fixed Typo in dialogue.rst
2023-07-13 23:07:06 -04:00
Gouvernathor beceb10166 Use conpute_raw uniformly 2023-07-13 22:13:26 +02:00
Gouvernathor c687c175fe Add absolute.compute and absolute.compute_raw 2023-07-13 19:27:24 +02:00
Tom Rothamel e4bed1cbc2 Remove the androidhw and ioshw video playback methods, and config.hw_video.
These haven't been the default for many years and the players haven't
gottem any attention in that time. Removing them will simplify
the surface that needs to be maintained.
2023-07-12 23:37:13 -04:00
Gouvernathor bdcd6191bd Support position (absolute) everywhere (#4709)
This makes most places that support positions support absolute.
2023-07-12 21:30:22 -04:00
Tom Rothamel deadc4a466 Document that angle/anchorangle are clamped now. 2023-07-12 09:20:35 -04:00
Abdul 505303f51a Fixed Typo in dialogue.rst
Fixed minor typo in dialogue.rst
2023-07-12 14:08:22 +01:00
Tom Rothamel 0f84d898f2 Merge branch 'fix' 2023-07-11 23:28:33 -04:00
Tom Rothamel cb80aea906 transform: Clamp polar coordinates to 0 <= angle < 360.
This ensures that the angle computed by Ren'Py from cartesian
coordinates and the angle stored in last_angle are always in
the same range, rather than having potentially odd interactions.

The main use of a polar coordinate outside of the range would be-
to provide a movement through 0 (for example, moving from 350 to 370
degrees), this is now directly supported.

Fixes #4835.
2023-07-11 23:00:49 -04:00
Ren'Py Bot 850ad647cc Merge branch 'fix' 2023-07-11 04:30:37 -04:00
Tom Rothamel bdec32fa5b Merge branch 'fix' 2023-07-10 01:31:51 -04:00
Ren'Py Bot b349ab06fd Merge branch 'fix' 2023-07-09 04:30:37 -04:00
Tom Rothamel 09d119b2d4 Add bubble.properties_callback.
This makes it possible to limit the bubble properties based
on the character that is speaking.
2023-07-05 23:20:36 -04:00
Tom Rothamel 4588c15333 Merge pull request #4823 from qTich/feature/path-to-logdir
add: `path_to_logdir`
2023-07-05 21:50:23 -04:00
Tichq b9c5317556 add: path_to_logdir 2023-07-06 01:53:03 +03:00
Ren'Py Bot e051e1a120 Merge branch 'fix' 2023-07-05 04:30:37 -04:00
Tom Rothamel 5073076bb5 Merge branch 'fix' 2023-07-04 20:58:04 -04:00
Tom Rothamel f35e340c16 Merge pull request #4809 from renpy/last_say-tweaks
Tweaks to renpy.last_say
2023-07-04 19:13:42 -04:00
Tom Rothamel b4dc241e08 Merge pull request #4820 from renpy/layeredimage.predict_all
Update layeredimage.predict_all to None
2023-07-04 19:11:58 -04:00
Tom Rothamel d1c1c0f1ff Merge pull request #4818 from AbdulGoodLooks/patch-1
Fixed typo in documentation
2023-07-04 19:11:36 -04:00
Gouvernathor f45b13a101 Update layeredimage.predict_all to None 2023-07-04 18:07:58 +02:00
Abdul 3d26079aff Update screens.rst
Fixed minor typo
2023-07-04 14:53:51 +01:00
Tom Rothamel 4f152e93ab Move Displayable into its own module (renpy.display.displayable).
Mostly to make the file easier to manage as work begins on
clone.
2023-07-03 19:35:11 -04:00
Tom Rothamel 5fd0d17e0b Merge pull request #4813 from renpy/update-operators-list
Update the list of operators in the lexer
2023-07-03 17:56:42 -04:00
Gouvernathor 0dd9c81e8c Update the list of operators 2023-07-03 18:39:51 +02:00
Gouvernathor 045932fed6 Copy the warning to similar functions 2023-07-03 04:16:49 +02:00
Gouvernathor 4c1570fdae Tweak renpy.last_say and its doc
The character is evaluated at each query, so that if the LastSay object is saved, it will point to an up-to-date Character object
2023-07-03 04:16:31 +02:00
Tom Rothamel c7c499c6c8 Merge pull request #4745 from mal/multipart-translations
Detect multipart strings marked for translation
2023-07-02 21:47:04 -04:00
Mal Graty 7459dea76c Detect multipart strings marked for translation 2023-07-03 02:35:45 +01:00
Tom Rothamel 2880e63082 doc: Link and changeloy renpy.last_say. 2023-07-02 00:13:58 -04:00
Tom Rothamel 55d16830c9 iap: Add support for reviewing on the Apple App Store. 2023-07-02 00:06:45 -04:00
Tom Rothamel 5422467e02 iap: Add support for requesting a review on Google Play. 2023-07-01 23:18:37 -04:00
Gouvernathor 9189178a5f doc: fix attributes listing 2023-07-02 04:07:03 +02:00
Lezalith 494bb54620 Add renpy.last_say function. (#4749)
This is a function that returns information about the last dialogue line.
2023-07-01 22:03:56 -04:00
Gouvernathor d0e925156b Enable jump/call expression to local labels (#4797)
* Enable jump/call expression to local labels

* Use a separate slot to store the local global label

* Document passing .local_label, and changelog
2023-07-01 21:59:44 -04:00
Moshibit 7a96e8ad82 Spanish translation update (#4805)
* Update launcher.rpy

* Fix Spansih translation.rpy

* Update common.rpy

* Update gui.rpy

* Update launcher.rpy
2023-07-01 10:17:02 -04:00
Tom Rothamel 4d0d262a76 Report deferred errors in load_script.
Fixes #4798.
2023-07-01 01:21:19 -04:00
Tom Rothamel 3dad9048f0 Document the Ren'Py updater doesn't support mac apps.
Fixes #4803.
2023-06-30 22:33:54 -04:00
Tom Rothamel 648f2835ac doc: Remove a warning about macOS 10.9.
Which is no longer supported.
2023-06-29 23:30:05 -04:00
Tom Rothamel 23adb33f71 Merge pull request #4794 from renpy/doc-screen-properties
Add the screen property directive
2023-06-29 21:27:37 -04:00
Tom Rothamel d62b8b8dfc Merge pull request #4777 from renpy/itch_channels
Convert build.itch_channels to a true dict
2023-06-29 21:09:04 -04:00
Gouvernathor eef91dcda8 doc typo
what f976f31 was meant to do
2023-06-30 02:30:21 +02:00
Gouvernathor 29324f6092 Add the screen property directive 2023-06-30 02:08:11 +02:00
Gouvernathor b224df24dc doc: fix bullet point list 2023-06-30 01:50:15 +02:00
Gouvernathor 446a1d74e7 doc: follow-up last 2023-06-30 01:47:56 +02:00
Gouvernathor 5b7bd98219 doc: fix missing ref 2023-06-30 01:45:05 +02:00
Tom Rothamel 9207334808 Report empty ATL blocks as an error.
Fixes #4792. Deferring to a major release because it's a very
unlikely issue, and may require script changes.
2023-06-28 21:38:44 -04:00
Ren'Py Bot 34d4eb57a4 Merge branch 'fix' 2023-06-28 04:30:37 -04:00
Ren'Py Bot e8323f4cad Merge branch 'fix' 2023-06-27 01:08:00 -04:00
Tom Rothamel 9d9085d379 Reset the compile and warp arguments when reloading.
Fixes #4780.
2023-06-26 23:17:55 -04:00
Tom Rothamel cb0cf2bf5e Add the fact that we're reloading to renpy.session. 2023-06-26 23:07:38 -04:00
Tom Rothamel 2c4194e754 rapt: Remove missing JDK options.
Since we no longer search for the JDK on windows until we install
the SDK.
2023-06-26 23:04:26 -04:00
Ren'Py Bot ac2534a87f Merge branch 'fix' 2023-06-26 04:30:37 -04:00
Ren'Py Bot bb4e4da909 Merge branch 'fix' 2023-06-25 04:30:37 -04:00
Gouvernathor b467514c39 add incompat entry 2023-06-25 03:57:13 +02:00
Gouvernathor 8556d563c9 Convert build.itch_channels to a true dict, and light compat 2023-06-24 15:20:27 +02:00
Gouvernathor e56fbd90f1 fix Camera AST node 2023-06-23 21:56:03 +02:00
Gouvernathor 15ec88c900 Update exception text 2023-06-23 21:44:44 +02:00
Gouvernathor 7eeadcfde1 Typos in incompatible.rst
per previous commit
2023-06-23 13:13:08 +02:00
Gouvernathor 061a8c9194 Typos in changelog.rst
per https://github.com/renpy/renpy/pull/4564#issuecomment-1520347974
I didn't test the right-to-left/bottom-to-top thing
2023-06-23 13:10:31 +02:00
Ren'Py Bot 584b13ff67 Merge branch 'fix' 2023-06-23 04:30:37 -04:00
Tom Rothamel d309f3fb93 Add gui.history_spacing.
This variable allows the creator to customize the space between
history entries.

Fixes #4766.
2023-06-23 01:00:07 -04:00
Tom Rothamel 5505c01d0a Changes to box_reverse.
Per #4765.
2023-06-23 00:41:56 -04:00
Ren'Py Bot 86afb06388 Merge branch 'fix' 2023-06-22 23:27:45 -04:00
Tom Rothamel 0e74637c87 Merge pull request #4767 from dbrookman/doc_choice_button_text_hover
Correct var in GUI Customization Guide
2023-06-22 23:27:32 -04:00
Daniel Brookman 6710dbbde8 Correct var in GUI Customization Guide
"gui.choice_text_hover_color" doesn't point to choice_button, so
changing it won't do anything.
2023-06-22 14:55:41 -04:00
Gouvernathor fb03b82f4c doc: tweaks for timed nw 2023-06-22 13:12:30 +02:00
Tom Rothamel 360277f0fe Document that loadable doesn't search images.
Fixes #4712.
2023-06-22 01:14:39 -04:00
Tom Rothamel 14a09d2cc6 The {nw} text tag takes a number of seconds before the line is dismissed.
Fixes #4763.
2023-06-21 23:07:15 -04:00
Ren'Py Bot 822f1a4da6 Merge branch 'fix' 2023-06-21 04:30:37 -04:00
Ren'Py Bot bd3d5f9dde Merge branch 'fix' 2023-06-18 23:29:40 -04:00
Tom Rothamel 46b0a05849 Merge pull request #4753 from renpy/tweak-4750
Tweak 1a62894
2023-06-18 14:13:41 -04:00
Gouvernathor 6a663830f8 linkify, tweak phrasings and code 2023-06-18 17:45:03 +02:00
Ren'Py Bot 9a23172671 Merge branch 'fix' 2023-06-18 02:08:44 -04:00
Gouvernathor 06d6d4b1ef doc: typo 2023-06-17 19:52:33 +02:00
Tom Rothamel 8fe9b7ad89 Add the keep_last_frame parameter to Movie.
This keeps the last frame after the movie ends.

Fixes #4732.
2023-06-17 01:12:37 -04:00
Ren'Py Bot c6456618e4 Merge branch 'fix' 2023-06-16 23:47:53 -04:00
Tom Rothamel 9723a2b7f8 Merge pull request #4738 from renpy/movie-doc
Tweak the Movie callable documentation
2023-06-16 20:27:16 -04:00
Tom Rothamel 77b1ad7fb1 Merge pull request #4747 from Lezalith/master
Czech Translation of The Question
2023-06-16 20:10:48 -04:00
Lezalith b9bcff650b Czech Language button in Preferences screen. 2023-06-17 02:07:58 +02:00
Lezalith fcc3d2a1b6 The Question czech translation files. 2023-06-17 02:07:29 +02:00
Ren'Py Bot 728f217eb7 Merge branch 'fix' 2023-06-16 04:30:37 -04:00
Morgan Willcock ebb9358d87 Fix changelog entry for 'rpy python' (#4720) 2023-06-15 21:15:54 -04:00
Ren'Py Bot 3f5df973bf Merge branch 'fix' 2023-06-15 04:30:37 -04:00
Ren'Py Bot fb4264f3ee Merge branch 'fix' 2023-06-14 01:38:06 -04:00
Tom Rothamel a069545bcd retain: Document retained bubbles. 2023-06-13 23:32:08 -04:00
Asriel Senna 79d3951fad doc: reorder parameters to fit their description 2023-06-13 17:30:48 +02:00
Asriel Senna 97fc57d912 Undocument the fps parameter 2023-06-13 17:29:26 +02:00
Tom Rothamel 6ed04f3d5e Merge branch 'fix' 2023-06-13 00:54:35 -04:00
Tom Rothamel 1f05d93ea6 bubble, retain: Implement clearing retained bubbles.
This was implemented with a new button in the bubble editor, that
the user can click to clear out all buttons on the screen apart
from the current bubble.
2023-06-12 01:33:40 -04:00
Tom Rothamel c3a2d4745d retain: When retain is in use, each bubble has its own location. 2023-06-11 00:36:05 -04:00
Tom Rothamel afb9729ecf retain: Initial implementation of retained dialogue.
This is intended for used with speech bubbles, but might be useful
in other place. It's a mode where each say statement shows a new
screen, until the screens are cleared by the (to be added)
clear statement.
2023-06-09 20:36:30 -04:00
Tom Rothamel 6e7d650f51 doc: Match the fix branch. 2023-06-08 22:09:30 -04:00
Tom Rothamel a33f891d65 docgen: Set the search z-index to 1. 2023-06-07 19:55:48 -04:00
Gouvernathor 9b1f23fe4d Extend the system cursor preference to config.mouse_displayable (#4706)
* Make the system_cursor preference disable mouse_displayable

* doc: new "system_cursor" behavior

* changelog system_cursor
2023-06-07 19:55:32 -04:00
Ren'Py Bot bb6ca17bc3 Merge branch 'fix' 2023-06-07 02:17:36 -04:00
Ren'Py Bot f33ab6b095 Merge branch 'fix' 2023-06-07 01:06:44 -04:00
Ren'Py Bot 26d4b1af50 Merge branch 'fix' 2023-06-06 04:30:56 -04:00
Ren'Py Bot 25e2dba5b3 Merge branch 'fix' 2023-06-05 04:30:56 -04:00
Gouvernathor 1a375c525c Add a warning when creating new projects in renpy 7
not disabled, because it still makes sense for test purposes or to update default files
2023-06-04 11:45:22 +02:00
Ren'Py Bot dd1c162271 Merge branch 'fix' 2023-06-04 04:30:56 -04:00
Tom Rothamel dd2b26807d doc: Changelog e29fdf8cbf. 2023-06-02 23:29:53 -04:00
Ren'Py Bot e6d5b8eef6 Merge branch 'fix' 2023-06-02 23:28:42 -04:00
Ren'Py Bot f79c052ace Merge branch 'fix' 2023-06-01 22:52:41 -04:00
Tom Rothamel e29fdf8cbf Prevent autoreloading while the git index lock is present.
Fixes #3689.
2023-06-01 22:42:50 -04:00
Moshibit a88e2a3980 Fix Spanish tranlation (#4684)
* Update launcher.rpy

* Fix Spansih translation.rpy
2023-06-01 22:25:22 -04:00
Ren'Py Bot 1251ce7e36 Merge branch 'fix' 2023-06-01 04:30:56 -04:00
Gouvernathor c7083584df Make absolute a float again (#4680)
Multiple advantages over the wrapper-class implementation : lighter in memory, don't wrap methods that don't need too, don't spend time converting their parameters, avoid a two-version pickling and unpickling mechanism (with slots), shorter code with less warning silencing.
2023-05-31 01:31:02 -04:00
Ren'Py Bot b6f1be507b Merge branch 'fix' 2023-05-30 04:30:56 -04:00
Tom Rothamel 690f249481 Rewrite absolute to not inherit from float.
This replaces absolute with a class that contains a float, and
behaves like a float in most circumstances. However, when an
operation is performed on it that would create a float, an
absolute is returned instead.

This is meant to address #4666 and a class of potential issues
that could be similar to it, where absolutes that are operated
on could become a float, and cause layout issues.

I need to think if this is something that should got into the
fix branch, as it's a major change.
2023-05-30 02:01:39 -04:00
midgethetree dfc29bb646 fix fonts linting (#4679)
Fixes an issue where fonts located in the fonts/ directory and referenced in .rpy files with just their filename and not the fonts/ prefix would be falsely claimed to be unloadable by the linter despite working just fine in-game. Also fixes the converse issue, where fonts located in images/ directory and referenced without the images/ prefix weren't caught by the linter despite the game crashing when actually attempting to load them.
2023-05-30 00:31:11 -04:00
Ren'Py Bot d94c3ef043 Merge branch 'fix' 2023-05-29 12:27:46 -04:00
brainos233 d8322a6db7 fix: launcher\game\tl\schinese\options.rpy (#4676) 2023-05-29 18:14:36 +02:00
Gouvernathor f3f703b426 BOM and UTF-LF 2023-05-29 12:53:55 +02:00
zedraxlo ac39001a61 SChinese translation update (#4675) 2023-05-29 12:40:58 +02:00
Ren'Py Bot cfd1721d71 Merge branch 'fix' 2023-05-29 04:30:56 -04:00
Ren'Py Bot e33cc4c43c Merge branch 'fix' 2023-05-28 23:07:07 -04:00
Morgan Willcock 3762c42604 doc: Fix interpolation example block (#4674)
Corrects the indentation so that the code is valid.

One of the affected lines had a typo ('thje' instead of 'the'), which
is now corrected.
2023-05-28 23:25:59 +02:00
Ren'Py Bot 389e882505 Merge branch 'fix' 2023-05-27 00:38:16 -04:00
Ren'Py Bot fe2d76bdd3 Merge branch 'fix' 2023-05-25 01:40:53 -04:00
Ren'Py Bot b152e0b00a Merge branch 'fix' 2023-05-23 13:34:03 -04:00
Ren'Py Bot df982c1b4c Merge branch 'fix' 2023-05-23 04:30:46 -04:00
Ren'Py Bot a851c97c85 Merge branch 'fix' 2023-05-22 10:51:36 -04:00
Moshibit 09a29a941c Update launcher.rpy 2023-05-22 10:43:06 -04:00
Ren'Py Bot f9233742a2 Merge branch 'fix' 2023-05-22 01:51:15 -04:00
Tom Rothamel a2adf917aa The window icon is an image.
Fixes #4651.
2023-05-22 01:51:15 -04:00
Gouvernathor 41d0b41518 actual translation 2023-05-22 01:51:15 -04:00
Gouvernathor aded4dd05e french translations 2023-05-22 01:51:15 -04:00
Gouvernathor 15024f32a8 quicken PY3 case 2023-05-21 16:30:29 +02:00
Gouvernathor 553ada3b04 Lint parameters shadowing builtin names 2022-08-18 22:38:43 +02:00
172 changed files with 16731 additions and 3990 deletions
+6
View File
@@ -103,6 +103,9 @@ cache/
# Pygame_sdl2.
/pygame_sdl2
# Updater.
update.pem
# Steam.
steam_appid.txt
/steamapi.py
@@ -127,3 +130,6 @@ CubismSdkForNative-4-*.zip
# Works in progress, throwaway scripts, etc.
/scratch
/*-dists
/c
/cg
/.vscode/launch.json
+1 -1
View File
@@ -50,7 +50,7 @@ your system. On Ubuntu and Debian, these dependencies can be installed with
the command::
sudo apt install virtualenvwrapper python3-dev libavcodec-dev libavformat-dev \
libswresample-dev libswscale-dev libfreetype6-dev libfribidi-dev libsdl2-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
Ren'Py requires SDL_image 2.6 or greater. If your distribution doesn't include
+3
View File
@@ -344,6 +344,9 @@ define config.history_length = 250
## 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)
+6 -2
View File
@@ -412,7 +412,7 @@ style main_menu_version:
## This screen is intended to be used with one or more children, which are
## transcluded (placed) inside it.
screen game_menu(title, scroll=None, yinitial=0.0):
screen game_menu(title, scroll=None, yinitial=0.0, spacing=0):
style_prefix "game_menu"
@@ -445,6 +445,8 @@ screen game_menu(title, scroll=None, yinitial=0.0):
side_yfill True
vbox:
spacing spacing
transclude
elif scroll == "vpgrid":
@@ -460,6 +462,8 @@ screen game_menu(title, scroll=None, yinitial=0.0):
side_yfill True
spacing spacing
transclude
else:
@@ -888,7 +892,7 @@ screen history():
## Avoid predicting this screen, as it can be very large.
predict False
use game_menu(_("History"), scroll=("vpgrid" if gui.history_height else "viewport"), yinitial=1.0):
use game_menu(_("History"), scroll=("vpgrid" if gui.history_height else "viewport"), yinitial=1.0, spacing=gui.history_spacing):
style_prefix "history"
+4 -6
View File
@@ -31,10 +31,9 @@ init python:
ANDROID_NO_BUNDLE = 6
ANDROID_OK = 7
JDK_REQUIREMENT=8
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_JDK_TEXT = _("A 64-bit/x64 Java [JDK_REQUIREMENT] Development Kit is required to build Android packages on Windows. The JDK is different from the JRE, so it's possible you have Java without having the JDK.\n\nPlease {a=https://www.renpy.org/jdk/[JDK_REQUIREMENT]}download and install the JDK{/a}, 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.")
@@ -99,9 +98,12 @@ init python:
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.
@@ -109,8 +111,6 @@ init python:
if RAPT_PATH is None:
return ANDROID_NO_RAPT
if renpy.windows and not "JAVA_HOME" in os.environ:
return ANDROID_NO_JDK
if not os.path.exists(rapt.plat.adb):
return ANDROID_NO_SDK
if not rapt.keys.keys_exist(project.current.path):
@@ -133,8 +133,6 @@ init python:
if state == ANDROID_NO_RAPT:
return NO_RAPT_TEXT
if state == ANDROID_NO_JDK:
return NO_JDK_TEXT
if state == ANDROID_NO_SDK:
return NO_SDK_TEXT
if state == ANDROID_NO_KEY:
+105 -24
View File
@@ -528,6 +528,11 @@ change_renpy_executable()
self.reporter.info(_("Scanning project files..."))
project.update_dump(force=True, gui=False, compile=project.data['force_recompile'])
if project.data['tutorial']:
self.reporter.info(_("Building distributions failed:\n\nThe project is the Ren'Py Tutorial, which can't be distributed outside of Ren'Py. Consider using The Question as a test project."), pause=True)
self.log.close()
return
if project.data['force_recompile']:
import compileall
@@ -579,6 +584,9 @@ change_renpy_executable()
self.include_update = build['include_update']
self.build_update = self.include_update and build_update
if self.include_update:
self.make_key_pem()
# The various executables, which change names based on self.executable_name.
self.app = self.executable_name + ".app"
self.exe = self.executable_name + ".exe"
@@ -676,11 +684,12 @@ change_renpy_executable()
dlc=p["dlc"])
if self.build_update and p["update"]:
self.make_package(
p["name"],
"update",
p["file_lists"],
dlc=p["dlc"])
for update_format in self.list_update_formats():
self.make_package(
p["name"],
update_format,
p["file_lists"],
dlc=p["dlc"])
wait_parallel_threads()
@@ -699,6 +708,23 @@ change_renpy_executable()
if open_directory:
renpy.run(store.OpenDirectory(self.destination, absolute=True))
def list_update_formats(self):
"""
Returns a list of update formats to build.
"""
rv = [ ]
for update_format in self.build["update_formats"]:
if update_format == "rpu":
rv.append("rpu")
elif update_format == "zsync":
rv.append("update")
else:
raise Exception("Unknown update format: " + update_format)
return rv
def scan_and_classify(self, directory, patterns):
"""
Walks through the `directory`, finds files and directories that
@@ -1420,6 +1446,7 @@ change_renpy_executable()
FORMATS = {
"update" : (".update", False, False, False),
"rpu" : ("", False, False, False),
"tar.bz2" : (".tar.bz2", False, False, True),
"zip" : (".zip", False, False, True),
@@ -1469,7 +1496,7 @@ change_renpy_executable()
update_fn = os.path.join(self.destination, filename + ".update.json")
if self.include_update and (variant not in [ 'ios', 'android', 'source']) and (not format.startswith("app-")):
if self.include_update and not format.startswith("app-"):
with open(update_fn, "wb" if PY2 else "w") as f:
json.dump(update, f, indent=2)
@@ -1478,6 +1505,9 @@ change_renpy_executable()
fl.append(File("update", None, True, False))
fl.append(File("update/current.json", update_fn, False, False))
if not dlc:
fl.append(File("update/key.pem", self.temp_filename("key.pem"), False, False))
# If we're not an update file, prepend the directory.
if (not dlc) and prepend:
fl.prepend_directory(filename)
@@ -1488,6 +1518,10 @@ change_renpy_executable()
full_filename = filename + ext
path += ext
if format == "rpu":
full_filename = "rpu/" + variant + ".files.rpu"
path = self.destination + "/" + full_filename
if self.build['renpy']:
fl_hash = fl.hash(self)
else:
@@ -1524,6 +1558,8 @@ change_renpy_executable()
pkg = TarPackage(path, "w:bz2")
elif format == "update":
pkg = UpdatePackage(path, filename, self.destination)
elif format == "rpu":
pkg = RPUPackage(self.destination, variant)
elif format == "zip" or format == "app-zip" or format == "bare-zip":
if self.build['renpy']:
pkg = ExternalZipPackage(path)
@@ -1560,13 +1596,13 @@ change_renpy_executable()
self.reporter.progress_done()
if format == "update":
# Build the zsync file.
self.reporter.info(_("Making the [variant] update zsync file."), variant=variant)
pkg.close()
def close_progress(done, total):
self.reporter.progress(_("Finishing the [variant] [format] package."), done, total, variant=variant, format=format)
pkg.close(close_progress)
if done is not None:
done()
@@ -1587,33 +1623,78 @@ change_renpy_executable()
def add_variant(variant):
digest = self.build_cache[self.base_name + "-" + variant + ".update"][0]
sums_size = os.path.getsize(self.destination + "/" + self.base_name + "-" + variant + ".sums")
index[variant] = {
"version" : self.update_versions[variant],
"pretty_version" : self.pretty_version,
"digest" : digest,
"zsync_url" : self.base_name + "-" + variant + ".zsync",
"sums_url" : self.base_name + "-" + variant + ".sums",
"sums_size" : sums_size,
"json_url" : self.base_name + "-" + variant + ".update.json",
}
"renpy_version" : renpy.version_only,
}
fn = renpy.fsencode(os.path.join(self.destination, self.base_name + "-" + variant + ".update"))
if "update" in self.build["update_formats"]:
if os.path.exists(fn):
os.unlink(fn)
digest = self.build_cache[self.base_name + "-" + variant + ".update"][0]
sums_size = os.path.getsize(self.destination + "/" + self.base_name + "-" + variant + ".sums")
index[variant].update({
"digest" : digest,
"zsync_url" : self.base_name + "-" + variant + ".zsync",
"sums_url" : self.base_name + "-" + variant + ".sums",
"sums_size" : sums_size,
"json_url" : self.base_name + "-" + variant + ".update.json",
})
fn = renpy.fsencode(os.path.join(self.destination, self.base_name + "-" + variant + ".update"))
if os.path.exists(fn):
os.unlink(fn)
if "rpu" in self.build["update_formats"]:
rpu_size = 0
rpu_dir = os.path.join(self.destination, "rpu")
for i in os.listdir(rpu_dir):
rpu_size += os.path.getsize(os.path.join(rpu_dir, i))
index[variant]["rpu_url"] = "rpu/" + variant + ".files.rpu"
index[variant]["rpu_digest"] = self.build_cache["rpu/" + variant + ".files.rpu"][0]
index[variant]["rpu_size"] = rpu_size
for p in packages:
if p["update"]:
add_variant(p["name"])
update_data = json.dumps(index, indent=2)
fn = renpy.fsencode(os.path.join(self.destination, "updates.json"))
with open(fn, "wb" if PY2 else "w") as f:
json.dump(index, f, indent=2)
f.write(update_data)
# Write the signed file.
import ecdsa
with open(self.find_update_pem(), "rb") as f:
signing_key = ecdsa.SigningKey.from_pem(f.read())
fn = renpy.fsencode(os.path.join(self.destination, "updates.ecdsa"))
with open(fn, "wb") as f:
f.write(signing_key.sign(update_data.encode("utf-8")))
def find_update_pem(self):
if self.build['renpy']:
return os.path.join(config.renpy_base, "update.pem")
else:
return os.path.join(self.project.path, "update.pem")
def make_key_pem(self):
import ecdsa
with open(self.find_update_pem(), "rb") as f:
signing_key = ecdsa.SigningKey.from_pem(f.read())
key_pem = self.temp_filename("key.pem")
with open(key_pem, "wb") as f:
f.write(signing_key.verifying_key.to_pem())
def save_build_cache(self):
if not self.build['renpy']:
+16
View File
@@ -267,7 +267,23 @@ 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
+1 -1
View File
@@ -259,7 +259,7 @@ label lint:
interface.processing(_("Checking script for potential problems..."))
lint_fn = project.current.temp_filename("lint.txt")
project.current.launch([ 'lint', lint_fn ], wait=True)
project.current.launch([ 'lint', lint_fn, ] + list(persistent.lint_options), wait=True)
e = renpy.editor.editor
e.begin(True)
+5 -1
View File
@@ -81,7 +81,6 @@ init python:
label install_live2d:
python hide:
if PY2:
_prefix = r"lib/py2-"
else:
@@ -98,6 +97,11 @@ label install_live2d:
(r".*/Core/dll/android/(x86_64/libLive2DCubismCore.so)", r"rapt/prototype/renpyandroid/src/main/jniLibs/\1"),
]
if PY2:
patterns.extend([
(r".*/Core/dll/windows/x86/(Live2DCubismCore.dll)", _prefix + r"windows-i686/\1"),
])
install_from_zip("Live2D Cubism SDK for Native", "CubismSdkForNative-4-*.zip", patterns)
jump front_page
+1 -1
View File
@@ -492,7 +492,7 @@ init python in interface:
The amount of time to pause for after showing the message.
"""
common(title, store.INTERACTION_COLOR, message, submessage=None, pause=pause, show_screen=True, **kwargs)
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):
+1 -1
View File
@@ -98,7 +98,7 @@ label itch:
for fn in os.listdir(destination):
for pattern, channel in reversed(build['itch_channels']):
for pattern, channel in reversed(build['itch_channels'].items()):
if fnmatch.fnmatch(fn, pattern):
break
else:
+3
View File
@@ -62,6 +62,9 @@ label new_project:
if persistent.projects_directory is None:
$ interface.error(_("The projects directory could not be set. Giving up."))
if PY2:
$ interface.info(_("Warning : you are using Ren'Py 7. It is recommended to start new projects using Ren'Py 8 instead."))
python:
if persistent.legacy:
+3 -1
View File
@@ -242,10 +242,12 @@ init python:
# 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)
+34 -11
View File
@@ -33,7 +33,6 @@ init python in distribute:
from zipfile import crc32
# Since the long type doesn't exist on py3, define it here
if not PY2:
long = int
@@ -195,7 +194,7 @@ init python in distribute:
self.zipfile.write_with_info(zi, path)
def close(self):
def close(self, progress=None):
self.zipfile.close()
@@ -207,7 +206,7 @@ init python in distribute:
If true, times will be forced to the epoch.
"""
self.tarfile = tarfile.open(filename, mode)
self.tarfile = tarfile.open(filename, mode, format=tarfile.PAX_FORMAT)
self.tarfile.dereference = True
self.notime = notime
@@ -243,9 +242,10 @@ init python in distribute:
def add_directory(self, name, path):
self.add_file(name, path, True)
def close(self):
def close(self, progress=None):
self.tarfile.close()
class UpdatePackage(TarPackage):
def __init__(self, filename, basename, destination):
@@ -255,7 +255,7 @@ init python in distribute:
TarPackage.__init__(self, filename, "w", notime=True)
def close(self):
def close(self, progress=None):
TarPackage.close(self)
cmd = [
@@ -283,7 +283,6 @@ init python in distribute:
sums.write(struct.pack("<I", zlib.adler32(data) & 0xffffffff))
class DirectoryPackage(object):
def mkdir(self, path):
@@ -315,9 +314,10 @@ init python in distribute:
fn = os.path.join(self.path, name)
self.mkdir(fn)
def close(self):
def close(self, progress=None):
return
class ExternalZipPackage(object):
def __init__(self, path):
@@ -331,7 +331,7 @@ init python in distribute:
def add_directory(self, name, path):
self.dp.add_directory(name, path)
def close(self):
def close(self, progress=None):
self.dp.close()
if os.path.exists(self.path):
@@ -350,17 +350,40 @@ init python in distribute:
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):
def close(self, progress=None):
DirectoryPackage.close(self)
self.make_dmg()
class RPUPackage(object):
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
renpy.update.generate.BlockGenerator(self.variant, self.file_list, self.directory, progress)
parallel_threads = [ ]
class ParallelPackage(object):
@@ -378,7 +401,7 @@ init python in distribute:
def add_directory(self, name, path):
self.worklist.append((True, name, path, True))
def close(self):
def close(self, progress=None):
t = threading.Thread(target=self.run)
t.start()
+68 -20
View File
@@ -19,15 +19,15 @@
# 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 = { # the ones which should be enabled by default
"--orphan-tl",
}
init python:
from math import ceil
if persistent.show_edit_funcs is None:
persistent.show_edit_funcs = True
if persistent.windows_console is None:
persistent.windows_console = False
def scan_translations(piglatin=True):
languages = renpy.known_languages()
@@ -64,6 +64,9 @@ 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
init python:
if not persistent.daily_update_check_once:
persistent.daily_update_check_once = True
@@ -71,6 +74,14 @@ init python:
default preference_tab = "general"
define preference_tabs = {
"general" : _("General"),
"options" : _("Options"),
"theme" : _("Theme"),
"install" : _("Install Libraries"),
"actions" : _("Actions"),
"lint" : _("Lint Options"),
}
screen preferences():
@@ -98,17 +109,12 @@ screen preferences():
has vbox
# Projects directory selection.
add SEPARATOR2
add HALF_SPACER
textbutton _("General") action SetVariable("preference_tab", "general") style "l_list"
textbutton _("Options") action SetVariable("preference_tab", "options") style "l_list"
textbutton _("Theme") action SetVariable("preference_tab", "theme") style "l_list"
textbutton _("Install Libraries") action SetVariable("preference_tab", "install") style "l_list"
textbutton _("Actions") action SetVariable("preference_tab", "actions") style "l_list"
for i, l in preference_tabs.items():
textbutton l action SetVariable("preference_tab", i) style "l_list"
if preference_tab == "general":
@@ -119,7 +125,6 @@ screen preferences():
has vbox
# Projects directory selection.
add SEPARATOR2
@@ -207,6 +212,22 @@ screen preferences():
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
@@ -218,15 +239,11 @@ screen preferences():
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 renpy.windows:
textbutton _("Console output") style "l_checkbox" action ToggleField(persistent, "windows_console")
textbutton _("Sponsor message") style "l_checkbox" action ToggleField(persistent, "sponsor_message")
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
elif preference_tab == "theme":
frame:
@@ -236,7 +253,6 @@ screen preferences():
has vbox
# Projects directory selection.
add SEPARATOR2
frame:
@@ -276,7 +292,6 @@ screen preferences():
use install_preferences
elif preference_tab == "actions":
frame:
@@ -300,6 +315,39 @@ screen preferences():
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 _("Orphan translations"):
style "l_checkbox"
action ToggleSetMembership(persistent.lint_options, "--orphan-tl")
textbutton _("Parameters overriding builtin names"):
style "l_checkbox"
action ToggleSetMembership(persistent.lint_options, "--builtins-parameters")
textbutton _("Word count and character count for speaking characters"):
style "l_checkbox"
action ToggleSetMembership(persistent.lint_options, "--words-char-count")
add SPACER
textbutton _("Check Script (Lint)") action Jump("lint")
textbutton _("Return") action Jump("front_page") style "l_left_button"
+14 -4
View File
@@ -55,12 +55,12 @@ init python in project:
def __init__(self, path, name=None):
if name is None:
name = os.path.basename(path)
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))
@@ -128,6 +128,7 @@ init python in project:
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"]
@@ -259,6 +260,10 @@ init python in project:
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.
@@ -765,7 +770,12 @@ init python in project:
blurb = LAUNCH_BLURBS[persistent.blurb % len(LAUNCH_BLURBS)]
persistent.blurb += 1
interface.interaction(_("Launching"), blurb, pause=2.5)
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):
+21 -1
View File
@@ -1590,7 +1590,7 @@
# game/gui7.rpy:339
old "Custom. The GUI is optimized for a 16:9 aspect ratio."
new "Personnalisé. Le GUI est optimié pour un ratio de cadre 16:9."
new "Personnalisé. Le GUI est optimisé pour un ratio de cadre 16:9."
# game/gui7.rpy:355
old "WIDTH"
@@ -2147,3 +2147,23 @@
# game/androidstrings.rpy:69
old "I found a bundle.keystore file in the rapt directory. Do you want to use this file?"
new "J'ai trouvé un fichier bundle.keystore dans le dossier \"rapt\". Voulez-vous l'utiliser ?"
# game/choose_directory.rpy:72
old "No directory was selected, but one is required."
new "Un chemin d'accès est nécessaire, mais aucun n'a été fourni."
# game/choose_directory.rpy:80
old "The selected directory does not exist."
new "Le dossier sélectionné n'existe pas."
# game/choose_directory.rpy:82
old "The selected directory is not writable."
new "Le dossier sélectionné n'est pas ouvert en écriture."
# game/distribute.rpy:554
old "This may be derived from build.name and config.version or build.version."
new "Cette variable peut être dérivée de build.name, et de config.version ou build.version."
# game/new_project.rpy:66
old "Warning : you are using Ren'Py 7. It is recommended to start new projects using Ren'Py 8 instead."
new "Attention : vous utilisez Ren'Py 7. Il est recommandé d'utiliser Ren'Py 8 pour de nouveaux projets."
+5
View File
@@ -889,3 +889,8 @@ translate spanish strings:
# renpy/common/00sync.rpy:619
old "Sync Error"
new "Error de sincronización"
# renpy/common/00speechbubble.rpy:391
old "(clear retained bubbles)"
new "(borrar burbujas retenidas)"
+15
View File
@@ -228,3 +228,18 @@
old "Speech Bubble Editor (Shift+B)"
new "Editor de burbujas de diálogo (Mayús+B)"
# renpy/common/_developer/developer.rpym:69
old "Show Translation Identifiers"
new "Mostrar identificadores de traducción"
# renpy/common/_developer/developer.rpym:72
old "Hide Translation Identifiers"
new "Ocultar identificadores de traducción"
# renpy/common/_developer/developer.rpym:582
old "\n{color=#fff}Copied to clipboard.{/color}"
new "\n{color=#fff}Copiado en el portapapeles.{/color}"
# renpy/common/_developer/developer.rpym:588
old "\n{color=#fff}Click to copy.\nDrag to move.{/color}"
new "\n{color=#fff}Haz clic para copiar.\nArrastre para mover.{/color}"
+6
View File
@@ -435,3 +435,9 @@
# gui/game/gui.rpy:14
old "## Enable checks for invalid or unstable properties in screens or transforms"
new "## Habilitar comprobaciones de propiedades no válidas o inestables en pantallas o transformaciones."
# gui/game/gui.rpy:347
old "## Additional space to add between history screen entries."
new "## Espacio adicional para añadir entre las entradas de la pantalla de historial."
+20
View File
@@ -2130,3 +2130,23 @@
# game/updater.rpy:78
old "Nightly Fix (Ren'Py 7, Python 2)"
new "Correciones nocturnas (Ren'Py 7, Python 2)"
# game/choose_directory.rpy:72
old "No directory was selected, but one is required."
new "No se seleccionó un directorio, pero uno requerido."
# game/choose_directory.rpy:79
old "The selected directory does not exist."
new "El directorio seleccionado no existe."
# game/choose_directory.rpy:81
old "The selected directory is not writable."
new "No se puede escribir en el directorio seleccionado."
# game/distribute.rpy:554
old "This may be derived from build.name and config.version or build.version."
new "Puede derivarse de build.name y config.version o build.version."
# game/distribute.rpy:532
old "Building distributions failed:\n\nThe project is the Ren'Py Tutorial, which can't be distributed outside of Ren'Py. Consider using The Question as a test project."
new "La compilación de distribuciones falló:\n\nEl proyecto es el Tutorial de Ren'Py, que no puede ser distribuido fuera de Ren'Py. Considera usar The Question como proyecto de prueba."
+146 -145
View File
@@ -19,7 +19,7 @@ translate ukrainian strings:
# 00action_file.rpy:26
old "{#weekday}Friday"
new "{#weekday}П'ятниця"
new "{#weekday}Пятниця"
# 00action_file.rpy:26
old "{#weekday}Saturday"
@@ -59,51 +59,51 @@ translate ukrainian strings:
# 00action_file.rpy:47
old "{#month}January"
new "{#month}Січня"
new "{#month}січня"
# 00action_file.rpy:47
old "{#month}February"
new "{#month}Лютого"
new "{#month}лютого"
# 00action_file.rpy:47
old "{#month}March"
new "{#month}Березня"
new "{#month}березня"
# 00action_file.rpy:47
old "{#month}April"
new "{#month}Квітня"
new "{#month}квітня"
# 00action_file.rpy:47
old "{#month}May"
new "{#month}Травня"
new "{#month}травня"
# 00action_file.rpy:47
old "{#month}June"
new "{#month}Червня"
new "{#month}червня"
# 00action_file.rpy:47
old "{#month}July"
new "{#month}Липня"
new "{#month}липня"
# 00action_file.rpy:47
old "{#month}August"
new "{#month}Серпня"
new "{#month}серпня"
# 00action_file.rpy:47
old "{#month}September"
new "{#month}Вересня"
new "{#month}вересня"
# 00action_file.rpy:47
old "{#month}October"
new "{#month}Жовтня"
new "{#month}жовтня"
# 00action_file.rpy:47
old "{#month}November"
new "{#month}Листопада"
new "{#month}листопада"
# 00action_file.rpy:47
old "{#month}December"
new "{#month}Грудня"
new "{#month}грудня"
# 00action_file.rpy:63
old "{#month_short}Jan"
@@ -155,19 +155,19 @@ translate ukrainian strings:
# 00action_file.rpy:240
old "%b %d, %H:%M"
new "%d %b, %H:%M"
new "%b %d, %H:%M"
# 00action_file.rpy:353
old "Save slot %s: [text]"
new "Слот збереження %s: [text]"
new "Зберегти комірку %s: [text]"
# 00action_file.rpy:434
old "Load slot %s: [text]"
new "Слот завантаження %s: [text]"
new "Завантажити комірку %s: [text]"
# 00action_file.rpy:487
old "Delete slot [text]"
new "Видалити слот [text]"
new "Видалити комірку [text]"
# 00action_file.rpy:569
old "File page auto"
@@ -179,19 +179,19 @@ translate ukrainian strings:
# 00action_file.rpy:573
old "File page [text]"
new "Сторінка збережень [text]"
new "Сторінка [text]"
# 00action_file.rpy:763
old "Next file page."
new "Наступна сторінка збережень."
new "Наступна сторінка."
# 00action_file.rpy:827
old "Previous file page."
new "Минула сторінка збережень"
new "Попередня сторінка"
# 00action_file.rpy:888
old "Quick save complete."
new "Швидке збереження завершено."
new "Швидке збереження виконано."
# 00action_file.rpy:906
old "Quick save."
@@ -207,7 +207,7 @@ translate ukrainian strings:
# 00director.rpy:708
old "The interactive director is not enabled here."
new "Інтерактивний директор недоступний."
new "Інтерактивний режисер тут не ввімкнений."
# 00director.rpy:1481
old "⬆"
@@ -219,7 +219,7 @@ translate ukrainian strings:
# 00director.rpy:1551
old "Done"
new "Прийняти"
new "Готово"
# 00director.rpy:1561
old "(statement)"
@@ -231,7 +231,7 @@ translate ukrainian strings:
# 00director.rpy:1563
old "(attributes)"
new "(атрибут)"
new "(атрибути)"
# 00director.rpy:1564
old "(transform)"
@@ -247,7 +247,7 @@ translate ukrainian strings:
# 00director.rpy:1602
old "(filename)"
new "(ім'я файла)"
new "(імя файла)"
# 00director.rpy:1631
old "Change"
@@ -263,19 +263,19 @@ translate ukrainian strings:
# 00director.rpy:1639
old "Remove"
new "Видалити"
new "Вилучити"
# 00director.rpy:1674
old "Statement:"
new "Функції:"
new "Функція:"
# 00director.rpy:1695
old "Tag:"
new "Теги:"
new "Тег:"
# 00director.rpy:1711
old "Attributes:"
new "Атрибут:"
new "Атрибути:"
# 00director.rpy:1729
old "Transforms:"
@@ -287,15 +287,15 @@ translate ukrainian strings:
# 00director.rpy:1767
old "Transition:"
new "Переходи:"
new "Перехід:"
# 00director.rpy:1785
old "Channel:"
new "Канали:"
new "Канал:"
# 00director.rpy:1803
old "Audio Filename:"
new "Ім'я файла:"
new "Імя аудіофайлу::"
# 00gui.rpy:370
old "Are you sure?"
@@ -303,63 +303,63 @@ translate ukrainian strings:
# 00gui.rpy:371
old "Are you sure you want to delete this save?"
new "Ви впевнені що хочете видалити це збереження?"
new "Ви дійсно бажаєте видалити збереження?"
# 00gui.rpy:372
old "Are you sure you want to overwrite your save?"
new "Ви впевнені що хочете перезаписати ваше збереження?"
new "Ви дійсно бажаєте перезаписати збереження?"
# 00gui.rpy:373
old "Loading will lose unsaved progress.\nAre you sure you want to do this?"
new "Завантаження гри приведе до втрати незбереженого прогресу.\nВи впевнені що хочете це зробити?"
new "Завантаження гри приведе до втрати незбереженого прогресу.\nВи дійсно бажаєте це зробити?"
# 00gui.rpy:374
old "Are you sure you want to quit?"
new "Ви впевнені що бажаєте вийти?"
new "Ви дійсно бажаєте вийти?"
# 00gui.rpy:375
old "Are you sure you want to return to the main menu?\nThis will lose unsaved progress."
new "Ви впевнені, що хочете повернутися до головного меню?\nЦе призведе до втрати незбереженого прогресу."
new "Ви дійсно бажаєте повернутися до головного меню?\nЦе призведе до втрати незбереженого прогресу."
# 00gui.rpy:376
old "Are you sure you want to end the replay?"
new "Ви впевнені, що хочете завершити повтор?"
new "Ви дійсно бажаєте закінчити повтор?"
# 00gui.rpy:377
old "Are you sure you want to begin skipping?"
new "Ви впевнені, що бажаєте пропустити?"
new "Ви дійсно бажаєте почати пропускати?"
# 00gui.rpy:378
old "Are you sure you want to skip to the next choice?"
new "Ви точно хочете пропустити все до наступного вибору?"
new "Ви дійсно бажаєте пропустити все до наступного вибору?"
# 00gui.rpy:379
old "Are you sure you want to skip unseen dialogue to the next choice?"
new "Ви впевнені, що хочете пропустити непрочитані діалоги до наступного вибору?"
new "Ви дійсно бажаєте пропустити непрочитані діалоги до наступного вибору?"
# 00keymap.rpy:258
old "Failed to save screenshot as %s."
new "Провалено спробу зберегти скріншот як %s."
new "Не вдалося зберегти знімок екрана як %s."
# 00keymap.rpy:270
old "Saved screenshot as %s."
new "Скріншот збережений як %s."
new "Знімок екрана збережено як %s."
# 00library.rpy:146
old "Self-voicing disabled."
new "Синтезатор мови вимкнено."
new "Синтез мовлення вимкнено."
# 00library.rpy:147
old "Clipboard voicing enabled. "
new "Озвучення буфера обміну включено."
new "Озвучення буфера обміну увімкнено."
# 00library.rpy:148
old "Self-voicing enabled. "
new "Синтезатор мови включений."
new "Синтез мовлення увімкнено."
# 00library.rpy:150
old "bar"
new "Смуга налаштування"
new "смуга"
# 00library.rpy:151
old "selected"
@@ -371,39 +371,39 @@ translate ukrainian strings:
# 00library.rpy:153
old "horizontal scroll"
new "горизонтальна смуга прокручування"
new "горизонтальне прокручування"
# 00library.rpy:154
old "vertical scroll"
new "вертикальна смуга прокручування"
new "вертикальне прокручування"
# 00library.rpy:155
old "activate"
new "елемент активовано"
new "увімкнути"
# 00library.rpy:156
old "deactivate"
new "елемент деактивовано"
new "вимкнути"
# 00library.rpy:157
old "increase"
new "більше"
new "збільшити"
# 00library.rpy:158
old "decrease"
new "менше"
new "зменшити"
# 00library.rpy:193
old "Skip Mode"
new "Режим Пропуску"
new "Режим пропуску"
# 00library.rpy:279
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 "Ця програма містить вільне та відкрите програмне забезпечення під кількома ліцензіями, включаючи ліцензію MIT та GNU Lesser General Public. Повний список ліцензій, враховуючи посилання на повний вихідний код, можна знайти {a=https://www.renpy.org/l/license}тут{/a}."
new "Ця програма містить вільне програмне забезпечення під низкою ліцензій, зокрема ліцензією MIT та GNU Lesser General Public License. Повний список ПЗ, включно з посиланнями на повний вихідний код, можна знайти {a=https://www.renpy.org/l/license}тут{/a}."
# 00preferences.rpy:207
old "display"
new "режим екрана"
new "режим показу"
# 00preferences.rpy:219
old "transitions"
@@ -415,11 +415,11 @@ translate ukrainian strings:
# 00preferences.rpy:230
old "video sprites"
new "відео-спрайти"
new "відео спрайти"
# 00preferences.rpy:239
old "show empty window"
new "показувати порожнє вікно діалогу"
new "показати порожнє вікно"
# 00preferences.rpy:248
old "text speed"
@@ -443,11 +443,11 @@ translate ukrainian strings:
# 00preferences.rpy:271
old "skip unseen text"
new "пропускати весь текст"
new "пропускати увесь текст"
# 00preferences.rpy:273
old "begin skipping"
new "почати пропуск"
new "почати пропускати"
# 00preferences.rpy:277
old "after choices"
@@ -459,23 +459,23 @@ translate ukrainian strings:
# 00preferences.rpy:286
old "auto-forward time"
new "швидкість авточитання"
new "швидкість перемотки"
# 00preferences.rpy:300
old "auto-forward"
new "авточитання"
new "перемотка"
# 00preferences.rpy:307
old "Auto forward"
new "Авточитання"
new "Перемотка"
# 00preferences.rpy:310
old "auto-forward after click"
new "продовжувати авточитання після кліку"
new "перемотка після дотику"
# 00preferences.rpy:319
old "automatic move"
new "автоматично пересувати мишу до кнопки" ###
new "автоматичний рух"
# 00preferences.rpy:328
old "wait for voice"
@@ -483,23 +483,23 @@ translate ukrainian strings:
# 00preferences.rpy:337
old "voice sustain"
new "не зупиняти голос"
new "підтримка голосу"
# 00preferences.rpy:346
old "self voicing"
new "озвучка через синтезатор мови"
new "синтез мовлення"
# 00preferences.rpy:355
old "clipboard voicing"
new "синтез мови з буфера обміну"
new "озвучення буфера обміну"
# 00preferences.rpy:364
old "debug voicing"
new "режим дебагу синтезу мови"
new "режим налагодження синтезу мови"
# 00preferences.rpy:373
old "emphasize audio"
new "посилити гучність заздалегідь заданих звукових каналів рахунок приглушення інших каналів"
new "виділити звук"
# 00preferences.rpy:382
old "rollback side"
@@ -507,15 +507,15 @@ translate ukrainian strings:
# 00preferences.rpy:392
old "gl powersave"
new "налаштування графіки. Економія енергії"
new "економія енергії"
# 00preferences.rpy:398
old "gl framerate"
new "налаштування графіки. Частота кадрів"
new "частота кадрів"
# 00preferences.rpy:401
old "gl tearing"
new "налаштування графіки. Розривання кадрів"
new "розрив кадрів"
# 00preferences.rpy:413
old "music volume"
@@ -523,11 +523,11 @@ translate ukrainian strings:
# 00preferences.rpy:414
old "sound volume"
new "гучність звуків"
new "гучність звук. ефектів"
# 00preferences.rpy:415
old "voice volume"
new "гучність голосу"
new "гучність озвучення"
# 00preferences.rpy:416
old "mute music"
@@ -535,39 +535,39 @@ translate ukrainian strings:
# 00preferences.rpy:417
old "mute sound"
new "без звуків"
new "без звук. ефектів"
# 00preferences.rpy:418
old "mute voice"
new "без голосу"
new "без озвучення"
# 00preferences.rpy:419
old "mute all"
new "режим без звуку"
new "без звуку"
# 00preferences.rpy:500
old "Clipboard voicing enabled. Press 'shift+C' to disable."
new "Озвучення буфера обміну увімкнено. Натисніть 'shift+C', щоб вимкнути це."
new "Озвучення буфера обміну увімкнено. Натисніть 'shift+C', щоб вимкнути."
# 00preferences.rpy:502
old "Self-voicing would say \"[renpy.display.tts.last]\". Press 'alt+shift+V' to disable."
new "Синтезатор мови повинен сказати \"[renpy.display.tts.last]\". Натисніть 'alt+shift+V', щоб відключити його."
new "Синтез мовлення має сказати \"[renpy.display.tts.last]\". Натисніть 'alt+shift+V', щоб вимкнути."
# 00preferences.rpy:504
old "Self-voicing enabled. Press 'v' to disable."
new "Синтезатор мови включено. Натисніть 'v', щоб вимкнути його."
new "Синтез мовлення увімкнено. Натисніть 'v', щоб вимкнути його."
# _compat\gamemenu.rpym:198
old "Empty Slot."
new "Порожній слот"
new "Порожній комірка."
# _compat\gamemenu.rpym:355
old "Previous"
new "Назад"
new "Попередня"
# _compat\gamemenu.rpym:362
old "Next"
new "Далі"
new "Наступна"
# _compat\preferences.rpym:428
old "Joystick Mapping"
@@ -579,7 +579,7 @@ translate ukrainian strings:
# _developer\developer.rpym:43
old "Interactive Director (D)"
new "Інтерактивний Директор (D)"
new "Інтерактивний режисер (D)"
# _developer\developer.rpym:45
old "Reload Game (Shift+R)"
@@ -591,11 +591,11 @@ translate ukrainian strings:
# _developer\developer.rpym:49
old "Variable Viewer"
new "Перегляд змінних"
new "Переглядач змінних"
# _developer\developer.rpym:51
old "Image Location Picker"
new "Інструмент позиціонування на зображеннях"
new "Вибір розташування зображень"
# _developer\developer.rpym:53
old "Filename List"
@@ -603,11 +603,11 @@ translate ukrainian strings:
# _developer\developer.rpym:57
old "Show Image Load Log (F4)"
new "Показати лог завантаження зображень (F4)"
new "Показати журнал завант. зображень (F4)"
# _developer\developer.rpym:60
old "Hide Image Load Log (F4)"
new "Скрыть лог загрузки изображений (F4)"
new "Сховати журнал завант. зображень (F4)"
# _developer\developer.rpym:63
old "Image Attributes"
@@ -623,7 +623,7 @@ translate ukrainian strings:
# _developer\developer.rpym:137
old "Nothing to inspect."
new "Змінні не задано."
new "Нічого оглядати."
# _developer\developer.rpym:265
old "Return to the developer menu"
@@ -639,27 +639,27 @@ translate ukrainian strings:
# _developer\developer.rpym:435
old "Right-click or escape to quit."
new "Натисніть праву кнопку миші або ESC, щоб вийти."
new "Натисніть ПКМ або Escape, щоб вийти."
# _developer\developer.rpym:467
old "Rectangle copied to clipboard."
new "Координати прямокутника скопійовані в буфер обміну."
new "Координати прямокутника скопійовано в буфер обміну."
# _developer\developer.rpym:470
old "Position copied to clipboard."
new "Координати позиції скопійовані у буфер обміну."
new "Координати позиції скопійовано у буфер обміну."
# _developer\developer.rpym:489
old "Type to filter: "
new "Поточний фільтр: "
new "Фільтр: "
# _developer\developer.rpym:617
old "Textures: [tex_count] ([tex_size_mb:.1f] MB)"
new "Текстур: [tex_count] ([tex_size_mb:.1f] МБ)"
new "Текстури: [tex_count] ([tex_size_mb:.1f] Мб)"
# _developer\developer.rpym:621
old "Image cache: [cache_pct:.1f]% ([cache_size_mb:.1f] MB)"
new "Кеш зображень: [cache_pct:.1f]% ([cache_size_mb:.1f] МБ)"
new "Кеш зображень: [cache_pct:.1f]% ([cache_size_mb:.1f] Мб)"
# _developer\developer.rpym:631
old "✔ "
@@ -671,11 +671,11 @@ translate ukrainian strings:
# _developer\developer.rpym:639
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}✔ передбачене зображення (добре){/color}\n{color=#fcc}✘ раптове зображення (погано){/color}\n{color=#fff}Натисніть, щоб пересунути.{/color}"
new "\n{color=#cfc}✔ передбачене зображення (добре){/color}\n{color=#fcc}✘ раптове зображення (погано){/color}\n{color=#fff}Перетягніть, щоб перемістити.{/color}"
# _developer\inspector.rpym:38
old "Displayable Inspector"
new "Диспетчер об'єктів"
new "Оглядач обєктів"
# _developer\inspector.rpym:61
old "Size"
@@ -691,23 +691,23 @@ translate ukrainian strings:
# _developer\inspector.rpym:122
old "Inspecting Styles of [displayable_name!q]"
new "Інспектую стилі [displayable_name!q]"
new "Оглядання стилів [displayable_name!q]"
# _developer\inspector.rpym:139
old "displayable:"
new "об'єкт:"
new "обєкт:"
# _developer\inspector.rpym:145
old " (no properties affect the displayable)"
new " (на об'єкт не впливають жодні параметри)"
new " (на обєкт не впливають жодні параметри)"
# _developer\inspector.rpym:147
old " (default properties omitted)"
new " (налаштування за замовчуванням опущено)"
new " (стандартні властивості опущено)"
# _developer\inspector.rpym:185
old "<repr() failed>"
new "<repr() провалений>"
new "<repr() failed>"
# _layout\classic_load_save.rpym:170
old "a"
@@ -719,11 +719,11 @@ translate ukrainian strings:
# 00iap.rpy:217
old "Contacting App Store\nPlease Wait..."
new "Зв'язуюсь з App Store\nБудь ласка, чекайте..."
new "Звязок з App Store\nБудь ласка, зачекайте..."
# 00updater.rpy:375
old "The Ren'Py Updater is not supported on mobile devices."
new "Ren'Py Updater не підтримується на мобільних пристроях."
new "Оновлювач Ren'Py не підтримується на мобільних пристроях."
# 00updater.rpy:494
old "An error is being simulated."
@@ -731,35 +731,35 @@ translate ukrainian strings:
# 00updater.rpy:678
old "Either this project does not support updating, or the update status file was deleted."
new "Або цей проект не підтримує оновлення, або видалено файл статусу оновлення."
new "Або цей проєкт не підтримує оновлення, або файл стану оновлення було видалено."
# 00updater.rpy:692
old "This account does not have permission to perform an update."
new "Цей обліковий запис не має права проводити оновлення."
new "Цей обліковий запис не має дозволу на виконання оновлення."
# 00updater.rpy:695
old "This account does not have permission to write the update log."
new "Цей обліковий запис не має права писати лог оновлення."
new "Цей обліковий запис не має дозволу на запис журналу оновлень."
# 00updater.rpy:722
old "Could not verify update signature."
new "Не можу верифікувати підпис оновлення."
new "Не вдалося перевірити підпис оновлення."
# 00updater.rpy:997
old "The update file was not downloaded."
new "Файл поновлення не був завантажений."
new "Файл оновлення не було завантажено."
# 00updater.rpy:1015
old "The update file does not have the correct digest - it may have been corrupted."
new "Файл поновлення не містить коректного дайджесту — він може бути пошкоджений."
new "Файл оновлення не має правильного дайджесту — можливо, він був пошкоджений."
# 00updater.rpy:1071
old "While unpacking {}, unknown type {}."
new "При розпаковуванні {} виявлено невідомий тип {}."
new "Під час розпакування {}, невідомий тип {}."
# 00updater.rpy:1439
old "Updater"
new "Оновлення"
new "Оновлювач"
# 00updater.rpy:1450
old "This program is up to date."
@@ -767,7 +767,7 @@ translate ukrainian strings:
# 00updater.rpy:1452
old "[u.version] is available. Do you want to install it?"
new "[u.version] доступна. Ви бажаєте її встановити?"
new "[u.version] доступно. Бажаєте її встановити?"
# 00updater.rpy:1454
old "Preparing to download the updates."
@@ -799,11 +799,11 @@ translate ukrainian strings:
# 00gallery.rpy:605
old "prev"
new "мин"
new "попер."
# 00gallery.rpy:606
old "next"
new "наст"
new "наст."
# 00gallery.rpy:607
old "slideshow"
@@ -823,11 +823,11 @@ translate ukrainian strings:
# 00accessibility.rpy:76
old "Font Override"
new "Перевизначення шрифту"
new "Замінити шрифт"
# 00accessibility.rpy:80
old "Default"
new "За замовчуванням"
new "Стандратний"
# 00accessibility.rpy:84
old "DejaVu Sans"
@@ -839,7 +839,7 @@ translate ukrainian strings:
# 00accessibility.rpy:94
old "Text Size Scaling"
new "Масштабування розміру тексту"
new "Масштаб розміру тексту"
# 00accessibility.rpy:100
old "Reset"
@@ -847,11 +847,11 @@ translate ukrainian strings:
# 00accessibility.rpy:105
old "Line Spacing Scaling"
new "Масштабування міжрядкового інтервалу"
new "Масштаб міжрядкового інтервалу"
# 00accessibility.rpy:117
old "Self-Voicing"
new "Озвучка через синтезатор мовлення"
new "Синтез мовлення"
# 00accessibility.rpy:121
old "Off"
@@ -859,11 +859,11 @@ translate ukrainian strings:
# 00accessibility.rpy:125
old "Text-to-speech"
new "Перетворення тексту на мовлення"
new "Перетворення тексту в мовлення"
# 00accessibility.rpy:129
old "Clipboard"
new "З буфера обміну"
new "Буфер обміну"
# 00preferences.rpy:430
old "font transform"
@@ -875,22 +875,23 @@ translate ukrainian strings:
# 00preferences.rpy:441
old "font line spacing"
new "міжрядковий інтервал"
new "міжрядковий інтервал шрифту"
# renpy/common/00accessibility.rpy:191
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 "Установки цього меню призначені для збільшення доступності. Вони можуть працювати не з усіма іграми, а деякі комбінації опцій можуть зробити гру, що відображається некоректно. Це не проблема з грою чи движком. Для досягнення найкращих результатів при зміні шрифтів намагайтеся зберігати розмір тексту таким, яким він був спочатку."
new "Параметри цього меню призначені для покращення доступності. Вони можуть працювати не з усіма іграми, а деякі комбінації параметрів можуть зробити гру неможливою для гри. Це не є проблемою гри або рушія. Для досягнення найкращих результатів при зміні шрифтів намагайтеся зберігати розмір тексту таким, яким він був спочатку."
# renpy/common/00accessibility.rpy:193
old "Self-Voicing Volume Drop"
new "Зменшення гучності при озвученні через синтезатор мовлення"
new "Зменшити гучність синтезу мовлення"
# renpy/common/00preferences.rpy:384
old "self voicing volume drop"
new "зменшення гучності при озвученні через синтезатор мовлення"
new "зменшити гучність синтезу мовлення"
# renpy/common/00preferences.rpy:464
old "system cursor"
new "системний курсор"
new "системний вказівник"
# renpy/common/00accessibility.rpy:180
old "High Contrast Text"
@@ -898,11 +899,11 @@ translate ukrainian strings:
# renpy/common/00preferences.rpy:487
old "renderer menu"
new "меню рендерера"
new "меню візуалізації"
# renpy/common/00preferences.rpy:490
old "accessibility menu"
new "меню спеціальних можливостей"
new "меню доступності"
# renpy/common/00preferences.rpy:493
old "high contrast text"
@@ -910,15 +911,15 @@ translate ukrainian strings:
# renpy/common/00preferences.rpy:511
old "audio when minimized"
new "звук при згортанні вікна"
new "звук при згортанні"
# renpy/common/00preferences.rpy:531
old "main volume"
new "загальна гучність"
new "основна гучність"
# renpy/common/00preferences.rpy:535
old "mute main"
new "режим без звуку"
new "без основи"
translate ukrainian strings:
@@ -936,31 +937,31 @@ translate ukrainian strings:
# renpy/common/00director.rpy:1745
old "Click to toggle attribute, right click to toggle negative attribute."
new "Клацніть, аби увімкнути атрибут, клацніть ПКМ, аби увімкнути від’ємний атрибут."
new "Натисніть, аби увімкнути атрибут, натисніть ПКМ, аби увімкнути від’ємний атрибут."
# renpy/common/00director.rpy:1768
old "Click to set transform, right click to add to transform list."
new "Клацніть, щоб встановити трансформацію, клацніть ПКМ, щоб додати до списку трансформацій."
new "Натисніть, щоб встановити трансформацію, натисніть ПКМ, щоб додати до списку трансформацій."
# renpy/common/00director.rpy:1789
old "Click to set, right click to add to behind list."
new "Клацніть, щоб встановити, клацніть ПКМ, щоб додати до списку."
new "Натисніть, щоб встановити, натисніть ПКМ, щоб додати до списку."
# 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 "Це збереження було створено на іншому пристрої. Зловмисно створені файли збережень можуть завдати шкоди вашому комп'ютеру. Чи довіряєте ви творцю цього збереження і всім, хто міг змінити файл?"
new "Це збереження було створено на іншому пристрої. Зловмисно створені файли збережень можуть завдати шкоди вашому пристрою. Чи довіряєте ви творцю цього збереження і всім, хто міг змінити файл?"
# 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 "Чи довіряєте ви пристрою, на якому було створено збереження? Ви повинні вибрати \"Так\", тільки якщо ви є єдиним користувачем пристрою."
new "Чи довіряєте ви пристрою, на якому було створено збереження? Ви повинні вибрати «Так», тільки якщо ви є єдиним користувачем пристрою."
# renpy/common/00preferences.rpy:528
old "audio when unfocused"
new "звук при розфокусуванні"
new "звук при згортанні вікна"
# renpy/common/00preferences.rpy:537
old "web cache preload"
new "попереднє завантаження веб-кешу"
new "попер. завантаження вебкешу"
# renpy/common/00preferences.rpy:552
old "voice after game menu"
@@ -980,7 +981,7 @@ translate ukrainian strings:
# renpy/common/00sync.rpy:190
old "Could not connect to the Ren'Py Sync server."
new "Не вдалося підключитися до сервера Ren'Py Sync."
new "Не вдалося під’єднатися до сервера Ren'Py Sync."
# renpy/common/00sync.rpy:192
old "The Ren'Py Sync server timed out."
@@ -988,7 +989,7 @@ translate ukrainian strings:
# renpy/common/00sync.rpy:194
old "An unknown error occurred while connecting to the Ren'Py Sync server."
new "Під час підключення до сервера Ren'Py Sync виникла невідома помилка."
new "Під час з’єднання до сервера Ren'Py Sync виникла невідома помилка."
# 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."
@@ -1016,7 +1017,7 @@ translate ukrainian strings:
# renpy/common/00sync.rpy:529
old "This will upload your saves to the {a=https://sync.renpy.org}Ren'Py Sync Server{/a}.\nDo you want to continue?"
new "Це завантажить ваші збереження на сервер синхронізації {a=https://sync.renpy.org}Ren'Py Sync{/a}.\nБажаєте продовжити?"
new "Це завантажить ваші збереження на {a=https://sync.renpy.org}сервер Ren'Py Sync{/a}.\nБажаєте продовжити?"
# renpy/common/00sync.rpy:558
old "Enter Sync ID"
@@ -1024,7 +1025,7 @@ translate ukrainian strings:
# renpy/common/00sync.rpy:569
old "This will contact the {a=https://sync.renpy.org}Ren'Py Sync Server{/a}."
new "Зв’язок зі сервером синхронізації {a=https://sync.renpy.org}Ren'Py Sync{/a}."
new "Зв’язок зі {a=https://sync.renpy.org}сервером Ren'Py Sync{/a}."
# renpy/common/00sync.rpy:596
old "Sync Success"
@@ -1036,7 +1037,7 @@ translate ukrainian strings:
# renpy/common/00sync.rpy:605
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 "Ви можете використовувати цей ідентифікатор, щоб завантажити збереження на інший пристрій.\nЦя синхронізація закінчиться через годину.\nRen'Py Sync підтримується {a=https://www.renpy.org/sponsors.html}спонсорами Ren'Py{/a}."
new "Ви можете використати цей ідентифікатор, щоб завантажити збереження на інший пристрій.\nЦя синхронізація завершиться за годину.\nRen'Py Sync підтримується за підтримки {a=https://www.renpy.org/sponsors.html}спонсорів Ren'Py{/a}."
# renpy/common/00sync.rpy:631
old "Sync Error"
+23 -23
View File
@@ -3,19 +3,19 @@ translate ukrainian strings:
# 00console.rpy:255
old "Press <esc> to exit console. Type help for help.\n"
new "Натисніть <esc>, щоб вийти із консолі. Введіть <help> для допомоги.\n"
new "Натисніть <esc>, щоб вийти із консолі. Введіть help для довідки.\n"
# 00console.rpy:259
old "Ren'Py script enabled."
new "Ren'Py script активовано."
new "Скрипт Ren'Py увімкено."
# 00console.rpy:261
old "Ren'Py script disabled."
new "Ren'Py script деактивовано."
new "Скрипт Ren'Py вимкнуто."
# 00console.rpy:496
old "help: show this help"
new "help: показує допомогу"
new "help: показує цю довідку"
# 00console.rpy:501
old "commands:\n"
@@ -23,27 +23,27 @@ translate ukrainian strings:
# 00console.rpy:511
old " <renpy script statement>: run the statement\n"
new " <оператор renpy script>: запуск оператора\n"
new " <функція скрипту renpy>: запуск функції\n"
# 00console.rpy:513
old " <python expression or statement>: run the expression or statement"
new " <вираз або оператор python>: запустити вираз або оператор"
new " <вираз або функція python>: запуск виразу або функції"
# 00console.rpy:521
old "clear: clear the console history"
new "clear: очищення історії консолі"
new "clear: очистити історію консолі"
# 00console.rpy:525
old "exit: exit the console"
new "exit: вихід із консолі"
new "exit: вийти із консолі"
# 00console.rpy:533
old "load <slot>: loads the game from slot"
new "load <слот>: завантажує гру з вибраного слота"
new "load <комірка>: завантажує гру з вибраної комірки"
# 00console.rpy:546
old "save <slot>: saves the game in slot"
new "save <слот>: зберігає гру у вибраний слот"
new "save <комірка>: зберігає гру у вибраній комірці"
# 00console.rpy:557
old "reload: reloads the game, refreshing the scripts"
@@ -51,49 +51,49 @@ translate ukrainian strings:
# 00console.rpy:565
old "watch <expression>: watch a python expression"
new "watch <вираз>: спостерігати за виразом python"
new "watch <вираз>: дивитися вираз python"
# 00console.rpy:591
old "unwatch <expression>: stop watching an expression"
new "unwatch <вираз>: припинити спостерігати за виразом"
new "unwatch <вираз>: припинити дивитися вираз"
# 00console.rpy:622
old "unwatchall: stop watching all expressions"
new "unwatchall: глобальне припинення спостереження"
new "unwatchall: припинити дивитися за всіма виразами"
# 00console.rpy:639
old "jump <label>: jumps to label"
new "jump <мітка>: стрибок на мітку"
new "jump <мітка>: перейти на мітку"
# 00console.rpy:685
old "short: Shorten the representation of objects on the console (default)."
new "short: Скорочене представлення об'єктів (repr) у консолі (за замовчуванням)."
new "short: Скоротити представлення обєктів на консолі (за стандартом)."
# 00console.rpy:690
old "long: Print the full representation of objects on the console."
new "long: Виводити повне уявлення об'єктів (repr) у консолі."
new "long: Вивести повне представлення обєктів на консолі."
# renpy/common/00console.rpy:814
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 <вираз>: спостерігати за виразом python\n watch short: вкорочує відображення виразів, що відстежуються (за замовчуванням)\n watch long: робить відображення відстежуваних виразів як є"
new "watch <вираз>: дивитися вираз python\n watch short: робить представлення відстежених виразів коротким (за стандартом)\n watch long: робить представлення відстежених виразів як є"
# renpy/common/00console.rpy:925
old "escape: Enables escaping of unicode symbols in unicode strings."
new "escape: Включає екранування Unicode символів у рядках unicode."
new "escape: Вмикає екранування символів Unicode в рядках Unicode."
# renpy/common/00console.rpy:929
old "unescape: Disables escaping of unicode symbols in unicode strings and print it as is (default)."
new "unescape: Вимикає екранування Unicode символів у рядках unicode та виводить їх як є (за замовчуванням)."
new "unescape: Вимикає екранування символів Unicode в рядках Unicode і друкує їх як є (за стандартом)."
# renpy/common/00console.rpy:784
old "stack: print the return stack"
new "stack: виводить стек повернення (return stack)"
new "stack: вивести стек повернення"
translate ukrainian strings:
# renpy/common/_developer/developer.rpym:51
old "Persistent Viewer"
new "Переглядач постійних даних"
new "Переглядач даних"
# renpy/common/_developer/developer.rpym:70
old "Speech Bubble Editor (Shift+B)"
@@ -101,9 +101,9 @@ translate ukrainian strings:
# renpy/common/00console.rpy:789
old "help: show this help\n help <expr>: show signature and documentation of <expr>"
new "help: показати цю довідку\n help <expr>: показати підпис та документацію <expr>"
new "help: показує цю довідку\n help <вираз>: показує підпис та документацію <виразу>"
# renpy/common/00console.rpy:813
old "Help may display undocumented functions. Please check that the function or\nclass you want to use is documented.\n\n"
new "У довідці можуть відображатися недокументовані функції. Будь ласка, перевірте, чи функцію або\nклас, який ви хочете використати, задокументовано.\n\n"
new "У довідці можуть відображатися недокументовані функції. Будь ласка, перевірте, чи функцію або\nклас, який бажаєте використати, задокументовано.\n\n"
+27 -27
View File
@@ -7,23 +7,23 @@ translate ukrainian strings:
# 00gltest.rpy:74
old "Automatically Choose"
new "Вибрати автоматично"
new "Обрати автоматично"
# 00gltest.rpy:79
old "Force Angle/DirectX Renderer"
new "Примусова візуалізація Angle/DirectX"
new "Візуалізація Angle/DirectX"
# 00gltest.rpy:83
old "Force OpenGL Renderer"
new "Примусова візуалізація OpenGL"
new "Візуалізація OpenGL"
# 00gltest.rpy:87
old "Force Software Renderer"
new "Примусова програмна візуалізація"
new "Програмна візуалізація"
# 00gltest.rpy:93
old "NPOT"
new "NPOT (OpenGL 2+)"
new "NPOT"
# 00gltest.rpy:97
old "Enable"
@@ -51,7 +51,7 @@ translate ukrainian strings:
# 00gltest.rpy:163
old "Tearing"
new "Розривання кадрів"
new "Розрив кадрів"
# 00gltest.rpy:179
old "Changes will take effect the next time this program is run."
@@ -79,11 +79,11 @@ translate ukrainian strings:
# 00gltest.rpy:229
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 може вирішити цю проблему."
new "Графічні драйвери можуть бути застарілими або працювати неправильно. Це може призвести до повільного або некоректного відображення графіки. Оновлення DirectX може вирішити цю проблему."
# 00gltest.rpy:231
old "Its graphics drivers may be out of date or not operating correctly. This can lead to slow or incorrect graphics display."
new "Графічні драйвера застаріли чи працюють неправильно. Це може призвести до повільного або неправильного відображення графіки."
new "Графічні драйвери можуть бути застарілими або працювати неправильно. Це може призвести до повільного або некоректного відображення графіки."
# 00gltest.rpy:236
old "Update DirectX"
@@ -91,7 +91,7 @@ translate ukrainian strings:
# 00gltest.rpy:242
old "Continue, Show this warning again"
new "Продовжити, показати знову"
new "Продовжити, показувати знову"
# 00gltest.rpy:246
old "Continue, Don't show warning again"
@@ -103,11 +103,11 @@ translate ukrainian strings:
# 00gltest.rpy:268
old "DirectX web setup has been started. It may start minimized in the taskbar. Please follow the prompts to install DirectX."
new "Запущено веб-налаштування DirectX. Вона може згорнутися на панелі завдань. Будь ласка, дотримуйтесь підказок для встановлення DirectX."
new "Запущено вебналаштування DirectX. Вона може згорнутися на панелі завдань. Будь ласка, дотримуйтесь підказок для встановлення DirectX."
# 00gltest.rpy:272
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} Інсталятор DirectX за замовчуванням намагається встановити панель інструментів Bing. Якщо ви цього не хочете, зніміть відповідну галочку."
new "{b}Нотатка:{/b} Програма вебналаштування DirectX від Microsoft за стандартом встановлює панель інструментів Bing. Якщо вам не потрібна ця панель, зніміть позначку у відповідному полі."
# 00gltest.rpy:276
old "When setup finishes, please click below to restart this program."
@@ -119,11 +119,11 @@ translate ukrainian strings:
# 00gamepad.rpy:32
old "Select Gamepad to Calibrate"
new "Виберіть геймпад для калібрування"
new "Оберіть ґеймпад для калібрування"
# 00gamepad.rpy:35
old "No Gamepads Available"
new "Геймпадів не виявлено"
new "Ґеймпадів не виявлено"
# 00gamepad.rpy:54
old "Calibrating [name] ([i]/[total])"
@@ -131,7 +131,7 @@ translate ukrainian strings:
# 00gamepad.rpy:58
old "Press or move the [control!s] [kind]."
new "Натисніть або перемістіть [control!s] [kind]."
new "Натисніть або рухніть [control!s] [kind]."
# 00gamepad.rpy:66
old "Skip (A)"
@@ -167,7 +167,7 @@ translate ukrainian strings:
# _errorhandling.rpym:584
old "Attempts a roll back to a prior time, allowing you to save or choose a different choice."
new "Спроба відкоту до попереднього часу, що дозволяє зберегти або вибрати інший варіант."
new "Спроба відкату до попереднього часу, що дозволяє зберегти або вибрати інший варіант."
# _errorhandling.rpym:587
old "Ignore"
@@ -203,7 +203,7 @@ translate ukrainian strings:
# _errorhandling.rpym:638
old "Parsing the script failed."
new "Опрацювання скрипту завершилося невдало."
new "Розбір скрипта не вдався."
# _errorhandling.rpym:664
old "Opens the errors.txt file in a text editor."
@@ -219,7 +219,7 @@ translate ukrainian strings:
# _errorhandling.rpym:544
old "Copies the traceback.txt file to the clipboard as BBcode for forums like https://lemmasoft.renai.us/."
new "Копіює файл traceback.txt у буфер обміну як BBCode для форумів типу https://lemmasoft.renai.us/."
new "Копіює файл traceback.txt у буфер обміну як BBCode для форумів як https://lemmasoft.renai.us/."
# _errorhandling.rpym:546
old "Copy Markdown"
@@ -231,35 +231,35 @@ translate ukrainian strings:
# _errorhandling.rpym:683
old "Copies the errors.txt file to the clipboard as BBcode for forums like https://lemmasoft.renai.us/."
new "Копіює файл errors.txt у буфер обміну як BBCode для форумів типу https://lemmasoft.renai.us/."
new "Копіює файл errors.txt у буфер обміну як BBCode для форумів як https://lemmasoft.renai.us/."
# _errorhandling.rpym:687
old "Copies the errors.txt file to the clipboard as Markdown for Discord."
new "Копіює файл errors.txt у буфер обміну як Markdown для Дискорду."
new "Копіює файл errors.txt у буфер обміну як Markdown для Discord."
# renpy/common/00gltest.rpy:100
old "Force GL Renderer"
new "Примусова візуалізація GL"
new "Візуалізація GL"
# renpy/common/00gltest.rpy:105
old "Force ANGLE Renderer"
new "Примусова візуалізація ANGLE"
new "Візуалізація ANGLE"
# renpy/common/00gltest.rpy:110
old "Force GLES Renderer"
new "Примусова візуалізація GLES"
new "Візуалізація GLES"
# renpy/common/00gltest.rpy:116
old "Force GL2 Renderer"
new "Примусова візуалізація GL2"
new "Візуалізація GL2"
# renpy/common/00gltest.rpy:121
old "Force ANGLE2 Renderer"
new "Примусова візуалізація ANGLE2"
new "Візуалізація ANGLE2"
# renpy/common/00gltest.rpy:126
old "Force GLES2 Renderer"
new "Примусова візуалізація GLES2"
new "Візуалізація GLES2"
# renpy/common/00gltest.rpy:245
old "This game requires use of GL2 that can't be initialised."
@@ -271,11 +271,11 @@ translate ukrainian strings:
# renpy/common/00gltest.rpy:273
old "Change render options"
new "Змінити налаштування рендерингу"
new "Змінити параметри візцалізації"
# renpy/common/00gamepad.rpy:58
old "Press or move the '[control!s]' [kind]."
new "Натисніть або посуньте [kind] '[control!s]'"
new "Натисніть або рухніть '[control!s]' [kind]."
# renpy/common/00gltest.rpy:136
old "Enable (No Blocklist)"
File diff suppressed because it is too large Load Diff
+40 -44
View File
@@ -11,15 +11,15 @@ translate ukrainian strings:
# gui/game/screens.rpy:85
old "## Say screen"
new "## Екран розмови"
new "## Екран Say"
# gui/game/screens.rpy:87
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 "## Екран say використовується для відображення діалогу гравцеві. Він приймає два параметри, хто і що, ім'я персонажа, що говорить, і текст, який буде відображатися відповідно. (Параметр who може мати значення None, якщо ім’я не вказано.)"
new "## Екран Say використовується для відображення діалогу гравцеві. Він приймає два параметри, who і what, імя персонажа, що говорить, і текст, який буде відображатися відповідно. (Параметр who може мати значення None, якщо ім’я не вказано.)"
# gui/game/screens.rpy:92
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 "## Цей екран має створювати текст, який можна відображати з ідентифікатором \"what\", оскільки Ren'Py використовує це для керування відображенням тексту. Він також може створювати відображувані елементи з ідентифікаторами \"who\" та ідентифікаторами \"window\" для застосування властивостей стилю."
new "## Цей екран має створювати текст, який можна відображати з ідентифікатором «what», оскільки Ren'Py використовує це для керування відображенням тексту. Він також може створювати відображувані елементи з ідентифікаторами «who» та «window» для застосування властивостей стилю."
# gui/game/screens.rpy:96
old "## https://www.renpy.org/doc/html/screen_special.html#say"
@@ -27,11 +27,11 @@ translate ukrainian strings:
# gui/game/screens.rpy:114
old "## If there's a side image, display it above the text. Do not display on the phone variant - there's no room."
new "## Якщо є зображення збоку, відобразіть його над текстом. Не відображати на телефоні як варіант - немає місця."
new "Якщо є бічне зображення, показуйте його над текстом. Не виводьте на мобільний варіант немає місця."
# gui/game/screens.rpy:120
old "## Make the namebox available for styling through the Character object."
new "## Робить поле імен доступним для стилізації через об'єкт Character."
new "## Робить поле імені доступним для стилізації за допомогою обєкта Character."
# gui/game/screens.rpy:165
old "## Input screen"
@@ -43,7 +43,7 @@ translate ukrainian strings:
# gui/game/screens.rpy:170
old "## This screen must create an input displayable with id \"input\" to accept the various input parameters."
new "## Цей екран має створити відображуваний вхід з ідентифікатором \"input\" для прийняття різних вхідних параметрів."
new "## Цей екран має створити відображуваний вхід з ідентифікатором «input» для прийняття різних вхідних параметрів."
# gui/game/screens.rpy:173
old "## https://www.renpy.org/doc/html/screen_special.html#input"
@@ -55,7 +55,7 @@ translate ukrainian strings:
# gui/game/screens.rpy:202
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 "## Цей екран використовується для відображення вибору в грі, представленого командою 'menu'. Один параметр, 'items', — це список об’єктів, кожен із полями заголовка та дії."
new "## Цей екран використовується для відображення вибору в грі, представленого командою 'menu'. Один параметр, 'items' — це список об’єктів, кожен із полями заголовка та дії."
# gui/game/screens.rpy:206
old "## https://www.renpy.org/doc/html/screen_special.html#choice"
@@ -135,7 +135,7 @@ translate ukrainian strings:
# gui/game/screens.rpy:314
old "End Replay"
new "Закінчити повтору"
new "Закінчити повтор"
# gui/game/screens.rpy:318
old "Main Menu"
@@ -155,7 +155,7 @@ translate ukrainian strings:
# gui/game/screens.rpy:329
old "## The quit button is banned on iOS and unnecessary on Android and Web."
new "## Кнопка виходу заборонена на iOS і непотрібна на Android і в Веб."
new "## Кнопка виходу заборонена на iOS і непотрібна на Android і в Web."
# gui/game/screens.rpy:330
old "Quit"
@@ -191,11 +191,11 @@ translate ukrainian strings:
# gui/game/screens.rpy:408
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 "## Це описує базову структуру екрана меню гри. Він викликається за допомогою заголовка екрана та відображає фон, заголовок і навігацію."
new "## Це описує базову структуру екрана меню гри. Він викликається за допомогою заголовка екрана та відображає тло, заголовок і навігацію."
# gui/game/screens.rpy:411
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 "## Параметр прокручування може бути None або одним із \"viewport\" чи \"vpgrid\". Цей екран призначений для використання з одним або декількома об'єктами, які включені (розміщені) всередині нього."
new "## Параметр прокручування може бути None або одним із «viewport» чи «vpgrid». Цей екран призначений для використання з одним або декількома обєктами, які включені (розміщені) всередині нього."
# gui/game/screens.rpy:429
old "## Reserve space for the navigation section."
@@ -219,7 +219,7 @@ translate ukrainian strings:
# gui/game/screens.rpy:546
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 "## Цей оператор використання включає екран game_menu всередині цього. Потім дочірній елемент vbox включається в область перегляду всередині екрана game_menu."
new "## Цей оператор використання включає екран «game_menu» всередині цього. Потім дочірній елемент «vbox» включається в область перегляду всередині екрана «game_menu»."
# gui/game/screens.rpy:556
old "Version [config.version!t]\n"
@@ -227,7 +227,7 @@ translate ukrainian strings:
# gui/game/screens.rpy:558
old "## gui.about is usually set in options.rpy."
new "## gui.about зазвичай встановлюється в options.rpy."
new "## «gui.about» зазвичай встановлюється в «options.rpy»."
# gui/game/screens.rpy:562
old "Made with {a=https://www.renpy.org/}Ren'Py{/a} [renpy.version_only].\n\n[renpy.license!t]"
@@ -239,7 +239,7 @@ translate ukrainian strings:
# gui/game/screens.rpy:575
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 "## Ці екрани дозволяють гравцеві зберегти гру та завантажити її знову. Оскільки вони мають майже все спільне, обидва реалізовані в термінах третього екрана, file_slots."
new "## Ці екрани дозволяють гравцеві зберегти гру та завантажити її знову. Оскільки вони мають майже все спільне, обидва реалізовані в термінах третього екрана, «file_slots»."
# gui/game/screens.rpy:579
old "## https://www.renpy.org/doc/html/screen_special.html#save https://www.renpy.org/doc/html/screen_special.html#load"
@@ -251,7 +251,7 @@ translate ukrainian strings:
# gui/game/screens.rpy:598
old "Automatic saves"
new "Автозбереження"
new "Автоматичні збереження"
# gui/game/screens.rpy:598
old "Quick saves"
@@ -259,7 +259,7 @@ translate ukrainian strings:
# gui/game/screens.rpy:604
old "## This ensures the input will get the enter event before any of the buttons do."
new "## Це гарантує, що введення отримає подію enter раніше, ніж будь-яка кнопка."
new "## Це гарантує, що введення отримає подію «enter» раніше, ніж будь-яка кнопка."
# gui/game/screens.rpy:608
old "## The page name, which can be edited by clicking on a button."
@@ -287,7 +287,7 @@ translate ukrainian strings:
# gui/game/screens.rpy:660
old "{#auto_page}A"
new "{#auto_page}A"
new "{#auto_page}А"
# gui/game/screens.rpy:663
old "{#quick_page}Q"
@@ -295,7 +295,7 @@ translate ukrainian strings:
# gui/game/screens.rpy:665
old "## range(1, 10) gives the numbers from 1 to 9."
new "## діапазон(1, 10) дає числа від 1 до 9."
new "## range(1, 10) дає числа від 1 до 9."
# gui/game/screens.rpy:669
old ">"
@@ -339,7 +339,7 @@ translate ukrainian strings:
# gui/game/screens.rpy:737
old "## Additional vboxes of type \"radio_pref\" or \"check_pref\" can be added here, to add additional creator-defined preferences."
new "## Сюди можна долучити додаткові вікна vbox типу \"radio_pref\" або \"check_pref\", щоб долучити додаткові параметри, визначені творцем."
new "## Сюди можна долучити додаткові вікна vbox типу «radio_pref» або «check_pref», щоб долучити додаткові параметри, визначені творцем."
# gui/game/screens.rpy:748
old "Text Speed"
@@ -347,7 +347,7 @@ translate ukrainian strings:
# gui/game/screens.rpy:752
old "Auto-Forward Time"
new "Швидкість авточитання"
new "Швидкість перемотки"
# gui/game/screens.rpy:759
old "Music Volume"
@@ -355,7 +355,7 @@ translate ukrainian strings:
# gui/game/screens.rpy:766
old "Sound Volume"
new "Гучність ефектів"
new "Гучність звук. ефектів"
# gui/game/screens.rpy:772
old "Test"
@@ -363,7 +363,7 @@ translate ukrainian strings:
# gui/game/screens.rpy:776
old "Voice Volume"
new "Гучність голосу"
new "Гучність озвучення"
# gui/game/screens.rpy:787
old "Mute All"
@@ -383,15 +383,15 @@ translate ukrainian strings:
# gui/game/screens.rpy:875
old "## Avoid predicting this screen, as it can be very large."
new "## Уникайте прогнозування цього екрана, оскільки він може бути дуже великим."
new "## Не намагайтеся передбачити цей екран, оскільки він може бути дуже великим."
# gui/game/screens.rpy:886
old "## This lays things out properly if history_height is None."
new "## Це правильно розкладає речі, якщо history_height дорівнює None."
new "## Тут все буде правильно, якщо history_height дорівнює None."
# gui/game/screens.rpy:896
old "## Take the color of the who text from the Character, if set."
new "## Взяти колір тексту що від персонажа, якщо встановлено."
new "## Бере колір тексту who з Character, якщо він заданий."
# gui/game/screens.rpy:905
old "The dialogue history is empty."
@@ -419,7 +419,7 @@ translate ukrainian strings:
# gui/game/screens.rpy:978
old "Gamepad"
new "Геймпад"
new "Ґеймпад"
# gui/game/screens.rpy:991
old "Enter"
@@ -427,7 +427,7 @@ translate ukrainian strings:
# gui/game/screens.rpy:992
old "Advances dialogue and activates the interface."
new "Розвиває діалог і активує інтерфейс."
new "Просуває діалог і вмикає інтерфейс."
# gui/game/screens.rpy:995
old "Space"
@@ -439,7 +439,7 @@ translate ukrainian strings:
# gui/game/screens.rpy:999
old "Arrow Keys"
new "Стрілки"
new "Клавіші зі стрілками"
# gui/game/screens.rpy:1000
old "Navigate the interface."
@@ -495,11 +495,11 @@ translate ukrainian strings:
# gui/game/screens.rpy:1032
old "Toggles assistive {a=https://www.renpy.org/l/voicing}self-voicing{/a}."
new "Вмикає допоміжне {a=https://www.renpy.org/l/voicing}самоозвучення{/a}."
new "Вмикає допоміжний {a=https://www.renpy.org/l/voicing}синтез мовлення{/a}."
# gui/game/screens.rpy:1036
old "Opens the accessibility menu."
new "Відкриває меню спеціальних можливостей."
new "Відкриває меню доступності."
# gui/game/screens.rpy:1042
old "Left Click"
@@ -515,11 +515,11 @@ translate ukrainian strings:
# gui/game/screens.rpy:1054
old "Mouse Wheel Up\nClick Rollback Side"
new "Колесо миші вгору\nбік повернення назад"
new "Коліщатко миші вгору\nСторона відкату"
# gui/game/screens.rpy:1058
old "Mouse Wheel Down"
new "Колесо миші вниз"
new "Коліщатко миші вниз"
# gui/game/screens.rpy:1065
old "Right Trigger\nA/Bottom Button"
@@ -535,15 +535,15 @@ translate ukrainian strings:
# gui/game/screens.rpy:1078
old "D-Pad, Sticks"
new "D-Pad, Стіки"
new "Хрестовина, Стики"
# gui/game/screens.rpy:1082
old "Start, Guide"
new "Старт, Гайд"
new "Start, Guide"
# gui/game/screens.rpy:1086
old "Y/Top Button"
new "Кнопка Y/Top"
new "Y/Верхня кнопка"
# gui/game/screens.rpy:1089
old "Calibrate"
@@ -559,7 +559,7 @@ translate ukrainian strings:
# gui/game/screens.rpy:1123
old "## The confirm screen is called when Ren'Py wants to ask the player a yes or no question."
new "## Екран підтвердження викликається, коли Ren'Py хоче поставити гравцеві запитання «так» або «ні»."
new "## Екран підтвердження викликається, коли Ren'Py хоче поставити гравцеві запитання «Так» або «Ні»."
# gui/game/screens.rpy:1126
old "## https://www.renpy.org/doc/html/screen_special.html#confirm"
@@ -579,7 +579,7 @@ translate ukrainian strings:
# gui/game/screens.rpy:1157
old "## Right-click and escape answer \"no\"."
new "## Клацніть правою кнопкою миші та вийдіть із відповіді \"no\"."
new "## Клацніть ПКМ та вийдіть із відповіді «Ні»."
# gui/game/screens.rpy:1184
old "## Skip indicator screen"
@@ -623,7 +623,7 @@ translate ukrainian strings:
# gui/game/screens.rpy:1281
old "## This screen is used for NVL-mode dialogue and menus."
new "## Відображає діалог у vpgrid або vbox."
new "## Цей екран використовується для діалогу та меню режиму NVL."
# gui/game/screens.rpy:1283
old "## https://www.renpy.org/doc/html/screen_special.html#nvl"
@@ -635,7 +635,7 @@ translate ukrainian strings:
# gui/game/screens.rpy:1307
old "## Displays the menu, if given. The menu may be displayed incorrectly if config.narrator_menu is set to True."
new "## Відображає меню, якщо є. Меню може відображатися неправильно, якщо для config.narrator_menu встановлено значення True."
new "## Відображає меню, якщо є. Меню може відображатися неправильно, якщо для config.narrator_menu задано значення True."
# gui/game/screens.rpy:1337
old "## This controls the maximum number of NVL-mode entries that can be displayed at once."
@@ -658,23 +658,19 @@ translate ukrainian strings:
# gui/game/screens.rpy:676
old "Upload Sync"
new "Вивантажити синхронізацію"
# gui/game/screens.rpy:680
old "Download Sync"
new "Завантажити синхронізацію"
# gui/game/screens.rpy:1410
old "## Bubble screen"
new "## Екран з бульбашками"
# gui/game/screens.rpy:1412
old "## The bubble screen is used to display dialogue to the player when using speech bubbles. The bubble screen takes the same parameters as the say screen, must create a displayable with the id of \"what\", and can create displayables with the \"namebox\", \"who\", and \"window\" ids."
new "## Екран бульбашок використовується для показу діалогу гравцеві під час використання мовних бульбашок. Екран бульбашок має ті самі параметри, що й екран слів, має створювати елемент відображення з ідентифікатором \"what\", а також може створювати елементи відображення з ідентифікаторами \"namebox\", \"who\" і \"window\"."
new "## Екран бульбашок використовується для показу діалогу гравцеві під час використання мовних бульбашок. Екран бульбашок має ті самі параметри, що й екран слів, має створювати елемент відображення з ідентифікатором «what», а також може створювати елементи відображення з ідентифікаторами «namebox», «who» і «window»."
# gui/game/screens.rpy:1417
old "## https://www.renpy.org/doc/html/bubble.html#bubble-screen"
+5 -5
View File
@@ -1,7 +1,7 @@
# У цьому файлі міститься скрипт гри.
# Оголошення символів, які використовуються в цій грі. Колірний аргумент розфарбовує
# ім'я персонажа.
# Оголошення персонажів, які використовуються в цій грі. Колірний аргумент розфарбовує
# імя персонажа.
define e = Character("Ейлін")
@@ -10,14 +10,14 @@ define e = Character("Ейлін")
label start:
# Це показує фон. За замовчуванням використовується заповнювач, але ви можете
# додати файл (з назвою "bg room.png" або "bg room.jpg") до
# Це показує тло. За стандратом використовується заповнювач, але ви можете
# додати файл (з назвою «bg room.png» або «bg room.jpg») до
# теки images, щоб показати його.
scene bg room
# Це показує спрайт персонажа. Використовується заповнювач, але ви можете
# замінити його, додавши до зображень файл із назвою "eileen happy.png".
# замінити його, додавши до зображень файл із назвою «eileen happy.png».
# до теки.
show eileen happy
+4 -5
View File
@@ -115,7 +115,7 @@ screen update_channel(channels):
for c in channels:
if c["split_version"] != list(renpy.version_tuple) or allow_repair_update:
$ action = [SetField(persistent, "has_update", None), SetField(persistent, "last_update_check", None), updater.Update(c["url"], simulate=UPDATE_SIMULATE, public_key=PUBLIC_KEY, confirm=False, force=allow_repair_update)]
$ action = [SetField(persistent, "has_update", None), SetField(persistent, "last_update_check", None), updater.Update(c["url"], simulate=UPDATE_SIMULATE, public_key=PUBLIC_KEY, confirm=False, force=allow_repair_update, prefer_rpu=c.get("prefer_rpu", False))]
if c["channel"].startswith("Release"):
$ current = _("• {a=https://www.renpy.org/doc/html/changelog.html}View change log{/a}")
@@ -132,15 +132,14 @@ screen update_channel(channels):
hbox:
spacing 7
textbutton c["channel"] action action
textbutton c["channel"] action action
add HALF_SPACER
$ date = _strftime(__("%B %d, %Y"), time.localtime(c["timestamp"]))
$ pretty = c["pretty_version"]
text "[date] • [c[pretty_version]] [current!t]" style "l_small_text"
text "[date] • [pretty] [current!t]" style "l_small_text"
add HALF_SPACER
+5 -5
View File
@@ -228,11 +228,11 @@ init python:
return f_rule
return False
def generate_pwa_icons(p, destination):
def generate_web_icons(p, destination):
"""
Checks if the pwa_icon.png file exists in the game folder and generates
Checks if the web-icon.png file exists in the game folder and generates
required icons for PWA in subdirectory icons/ in the destination folder.
If no pwa_icon.png is found, the default Ren'Py icon is used instead in
If no web-icon.png is found, the default Ren'Py icon is used instead in
the web folder, if exists.
"""
# Check if there's a custom icon in the game directory
@@ -430,7 +430,7 @@ init python:
# Copy the files from WEB_PATH to destination.
for fn in os.listdir(WEB_PATH):
if fn in { "game.zip", "hash.txt", "index.html", "pwa_icon.png" }:
if fn in { "game.zip", "hash.txt", "index.html", "web-icon.png" }:
continue
shutil.copy(os.path.join(WEB_PATH, fn), os.path.join(destination, fn))
@@ -466,7 +466,7 @@ init python:
f.write(html)
if not PY2:
generate_pwa_icons(p, destination)
generate_web_icons(p, destination)
prepare_pwa_files(p, destination)
# Zip up the game.
+1
View File
@@ -41,3 +41,4 @@ renpy.gl2.live2dmodel gen/renpy.gl2.live2dmodel.c
renpy.text.textsupport gen/renpy.text.textsupport.c
renpy.text.texwrap gen/renpy.text.texwrap.c
renpy.text.ftfont gen/renpy.text.ftfont.c ftsupport.c ttgsubtable.c
renpy.text.hbfont gen/renpy.text.hbfont.c
File diff suppressed because it is too large Load Diff
+214
View File
@@ -0,0 +1,214 @@
#!/usr/bin/env python3
import collections
import graphlib
from pathlib import Path
import pprint
class trie_node(dict):
name = None
def __missing__(self, key):
self[key] = trie_node()
self[key][''] = 0
return self[key]
@property
def key(self):
return tuple(sorted(self.items()))
def __hash__(self):
return hash(self.key)
def __eq__(self, other):
return self.key == other.key
emoji = trie_node()
LEVEL = {
"unqualified" : 1,
"component" : 2,
"minimally-qualified" : 3,
"fully-qualified" : 4,
}
def load():
"""
Load the emoji.
"""
emoji_test_text = Path(__file__).parent / "emoji-test.txt"
lines = emoji_test_text.read_text().splitlines()
for l in lines:
l = l.partition("#")[0].strip()
if not l:
continue
codepoints, qualifier = l.split(";")
level = LEVEL[qualifier.strip()]
d = emoji
for i in codepoints.strip().split():
i = chr(int(i, 16))
d = d[i]
d[''] = level
emoji[''] = 0
def merge(d):
"""
Merge tries with identical subtrees.
"""
unique_tries = { }
def pass1(d):
unique_tries[d] = d
for v in d.values():
if type(v) is not int:
pass1(v)
def pass2(d):
d = unique_tries[d]
for k, v in d.items():
if type(v) is not int:
d[k] = pass2(v)
return d
pass1(d)
return pass2(d)
dict_by_name = { }
def assign_names(d):
"""
Assigns a unique name to each trie node.
"""
serial = 0
def assign(d):
if type(d) is int or type(d) is str:
return
if d.name is not None:
return
nonlocal serial
d.name = f"e{serial}"
dict_by_name[d.name] = d
serial += 1
for v in d.values():
assign(v)
assign(d)
d.name = "emoji"
dict_by_name[d.name] = d
order = [ ]
def sort():
"""
Sorts the dicts into dependency order.
"""
graph = { }
def add(d):
graph[d.name] = {v.name for v in d.values() if type(v) is not int}
for v in d.values():
if type(v) is not int:
add(v)
add(emoji)
ts = graphlib.TopologicalSorter(graph)
global order
order = list(ts.static_order())
def generate(f, d):
"""
Generates Python code for a trie node.
"""
l = [ f"{d.name} = {{" ]
for k in sorted(d.keys()):
v = d[k]
if type(v) is int:
l.append(f"'{k}': {v},")
else:
l.append(f"'{k}': {v.name},")
l.append("}")
print(" ".join(l), file=f)
def main():
dest = Path(__file__).parent.parent.parent / "renpy"/ "text"/ "emoji_trie.py"
with open(dest, "w") as f:
print("""\
# coding: utf-8
#
# This file is generated by module/emoji/make_emoji_trie.py. Do not
# edit it directly.
#
# Emoji trie data.
#
#
# It contains a data structure that can be used to determine if a
# sequence of codepoints is an emoji and if it should be rendered
# in an emoji font.
#
# Each trie node is a dictionary mapping codepoints to other dictionaries.
# If a codepoint is not present in the dictionarly, look up the '' key
# in the dictionary to find what it maps to. (It will be one of the constants
# below.
#
# The top-level dictionary is named emoji.
# The codepoint is not an emoji.
NOT_EMOJI = 0
# The codepoints are an emoji, but it may or may not be rendered in an emoji font.
UNQUALIFIED = 1
# The codepoints are part of a larger emoji sequence, and should be rendered
# in an emoji font when standing alone.
COMPONENT = 2
# The codepoints are an emoji, and should be rendered in an emoji font, but
# aren't fully qualified.
MINIMALLY_QUALIFIED = 3
# The codepoints are an emoji, and should be rendered in an emoji font.
QUALIFIED = 4
""", file=f)
global emoji
load()
emoji = merge(emoji)
assign_names(emoji)
sort()
for name in order:
generate(f, dict_by_name[name])
if __name__ == "__main__":
main()
+6 -1
View File
@@ -134,7 +134,7 @@ style_properties = sorted_dict(
alt=None,
altruby_style=None,
antialias=None,
vertical=None,
axis=None,
background='renpy.easy.displayable_or_none',
bar_invert=None,
bar_resizing=None,
@@ -155,6 +155,7 @@ style_properties = sorted_dict(
debug=None,
drop_shadow=None,
drop_shadow_color='renpy.easy.color',
emoji_font=None,
first_indent=None,
first_spacing=None,
fit_first=None,
@@ -168,6 +169,7 @@ style_properties = sorted_dict(
hover_sound=None,
hyperlink_functions=None,
italic=None,
instance=None,
justify=None,
kerning=None,
key_events=None,
@@ -187,10 +189,12 @@ style_properties = sorted_dict(
order_reverse=None,
outlines='expand_outlines',
outline_scaling=None,
prefer_emoji=None,
rest_indent=None,
right_margin=None,
right_padding=None,
ruby_style=None,
shaper=None,
size=None,
size_group=None,
slow_abortable=None,
@@ -209,6 +213,7 @@ style_properties = sorted_dict(
top_margin=None,
top_padding=None,
underline=None,
vertical=None,
xanchor='expand_anchor',
xfill=None,
xfit=None,
+48 -1
View File
@@ -88,6 +88,7 @@ cdef extern from "pyfreetype.h":
FT_PIXEL_MODE_GRAY4,
FT_PIXEL_MODE_LCD,
FT_PIXEL_MODE_LCD_V,
FT_PIXEL_MODE_BGRA,
FT_PIXEL_MODE_MAX
@@ -394,6 +395,13 @@ cdef extern from "pyfreetype.h":
FT_LOAD_LINEAR_DESIGN
FT_LOAD_SBITS_ONLY
FT_LOAD_NO_AUTOHINT
FT_LOAD_COLOR
FT_LOAD_TARGET_NORMAL
FT_LOAD_TARGET_LIGHT
FT_LOAD_TARGET_MONO
FT_LOAD_TARGET_LCD
FT_LOAD_TARGET_LCD_V
FT_Error FT_Load_Glyph(FT_Face face, FT_UInt glyph_index, FT_Int32 flags)
FT_Error FT_Load_Char(FT_Face face, FT_ULong char_code, FT_Int32 flags)
@@ -515,7 +523,7 @@ cdef extern from "pyfreetype.h":
FT_Error FT_Bitmap_Embolden(FT_Library lib, FT_Bitmap *bitmap, FT_Pos xStrength, FT_Pos yStrength)
FT_Error FT_Bitmap_Convert(FT_Library lib, FT_Bitmap *source, FT_Bitmap *target, FT_Int alignment)
# Additions by Tom Rothame.
# Additions by Tom Rothamel.
cdef struct FT_MemoryRec_
ctypedef FT_MemoryRec_ *FT_Memory
@@ -605,3 +613,42 @@ cdef extern from "pyfreetype.h":
cdef FT_Long FT_CEIL(FT_Long)
cdef FT_Long FT_FLOOR(FT_Long)
cdef FT_Long FT_ROUND(FT_Long)
# Multiple Masters
cdef struct FT_Var_Axis_:
FT_String* name
FT_Fixed minimum
FT_Fixed default "def"
FT_Fixed maximum
FT_ULong tag
FT_UInt strid
ctypedef FT_Var_Axis_ FT_Var_Axis
cdef struct FT_Var_Named_Style_:
FT_Fixed* coords
FT_UInt strid
FT_UInt psid
ctypedef FT_Var_Named_Style_ FT_Var_Named_Style
cdef struct FT_MM_Var_:
FT_UInt num_axis
FT_UInt num_designs
FT_UInt num_namedstyles
FT_Var_Axis* axis
FT_Var_Named_Style* namedstyle
ctypedef FT_MM_Var_ FT_MM_Var
FT_Error FT_Get_MM_Var(FT_Face face, FT_MM_Var **amaster)
FT_Error FT_Done_MM_Var(FT_Library library, FT_MM_Var *amaster)
FT_Error FT_Set_Named_Instance(FT_Face, FT_UInt instance_index)
FT_Error FT_Set_Var_Design_Coordinates( FT_Face face,
FT_UInt num_coords,
FT_Fixed* coords )
+1
View File
@@ -7,6 +7,7 @@
#include FT_OUTLINE_H
#include FT_BITMAP_H
#include FT_STROKER_H
#include FT_MULTIPLE_MASTERS_H
#define FT_FLOOR(X) ((X & -64) >> 6)
#define FT_CEIL(X) (((X + 63) & -64) >> 6)
+8
View File
@@ -86,6 +86,7 @@ include("libavcodec/avcodec.h", directory="ffmpeg", optional=True) or include("l
include("libswscale/swscale.h", directory="ffmpeg", optional=True) or include("libswscale/swscale.h") # type: ignore
include("GL/glew.h")
include("pygame_sdl2/pygame_sdl2.h", directory="python{}.{}".format(sys.version_info.major, sys.version_info.minor))
include("hb.h", directory="harfbuzz")
library("SDL2")
library("png")
@@ -197,6 +198,13 @@ cython(
[ "ftsupport.c", "ttgsubtable.c" ],
libs=sdl + [ 'freetype', 'z', 'm' ])
if not (PY2 and emscripten):
cython(
"renpy.text.hbfont",
[ "ftsupport.c" ],
libs=sdl + [ 'harfbuzz', 'freetype', 'z', 'm' ])
generate_all_cython()
find_unnecessary_gen()
+15 -1
View File
@@ -204,7 +204,7 @@ def path_to_saves(gamedir, save_directory=None): # type: (str, str|None) -> str
# the launcher, usually.)
def path_to_renpy_base():
"""
Returns the absolute path to thew Ren'Py base directory.
Returns the absolute path to the Ren'Py base directory.
"""
renpy_base = os.path.dirname(os.path.abspath(__file__))
@@ -212,6 +212,20 @@ def path_to_renpy_base():
return renpy_base
def path_to_logdir(basedir):
"""
Returns the absolute path to the log directory.
`basedir`
The base directory (config.basedir)
"""
import renpy # @UnresolvedImport
if renpy.android:
return os.environ['ANDROID_PUBLIC']
return basedir
##############################################################################
+18 -19
View File
@@ -278,9 +278,6 @@ class Backup(_object):
# A map from module to the set of names in that module.
self.names = { }
if mobile:
return
for m in sys.modules.values():
if m is None:
continue
@@ -442,7 +439,7 @@ def import_all():
global plog
plog = renpy.performance.log # type:ignore
import renpy.styledata # @UnresolvedImport
import renpy.styledata
import renpy.style
renpy.styledata.import_style_functions()
@@ -457,14 +454,15 @@ def import_all():
import renpy.translation.extract
import renpy.translation.merge
import renpy.display # @UnresolvedImport @Reimport
import renpy.display
import renpy.display.presplash
import renpy.display.pgrender
import renpy.display.scale
import renpy.display.module
import renpy.display.render # Most display stuff depends on this. @UnresolvedImport
import renpy.display.core # object @UnresolvedImport
import renpy.display.render
import renpy.display.displayable
import renpy.display.core
import renpy.display.swdraw
import renpy.text
@@ -484,13 +482,13 @@ def import_all():
import renpy.display.layout
import renpy.display.viewport
import renpy.display.transform
import renpy.display.motion # layout @UnresolvedImport
import renpy.display.behavior # layout @UnresolvedImport
import renpy.display.transition # core, layout @UnresolvedImport
import renpy.display.movetransition # core @UnresolvedImport
import renpy.display.motion
import renpy.display.behavior
import renpy.display.transition
import renpy.display.movetransition
import renpy.display.im
import renpy.display.imagelike
import renpy.display.image # core, behavior, im, imagelike @UnresolvedImport
import renpy.display.image
import renpy.display.video
import renpy.display.focus
import renpy.display.anim
@@ -536,7 +534,7 @@ def import_all():
import renpy.memory
import renpy.exports
import renpy.character # depends on exports. @UnresolvedImport
import renpy.character
import renpy.add_from
import renpy.dump
@@ -549,8 +547,8 @@ def import_all():
import renpy.gl2.gl2texture
import renpy.gl2.live2d
import renpy.minstore # depends on lots. @UnresolvedImport
import renpy.defaultstore # depends on everything. @UnresolvedImport
import renpy.minstore
import renpy.defaultstore
import renpy.test
import renpy.test.testmouse
@@ -570,8 +568,7 @@ def import_all():
global backup
if not mobile:
backup = Backup()
backup = Backup()
post_import()
@@ -617,8 +614,8 @@ def reload_all():
returned.
"""
if mobile:
raise Exception("Reloading is not supported on mobile platforms.")
# if mobile:
# raise Exception("Reloading is not supported on mobile platforms.")
import renpy
@@ -699,6 +696,7 @@ if 1 == 0:
from . import dump
from . import easy
from . import editor
from . import encryption
from . import error
from . import execution
from . import exports
@@ -743,5 +741,6 @@ if 1 == 0:
from . import ui
from . import util
from . import vc_version
from . import versions
from . import warp
from . import webloader
+4 -11
View File
@@ -151,20 +151,13 @@ class ArgumentParser(argparse.ArgumentParser):
else:
self.group.add_argument(*args, **kwargs)
def parse_args(self, *args, **kwargs):
rv = argparse.ArgumentParser.parse_args(self, *args, **kwargs)
if rv.command in compile_commands:
rv.compile = True
if renpy.session.get("compile", False):
rv.compile = True
return rv
def parse_known_args(self, *args, **kwargs):
args, rest = argparse.ArgumentParser.parse_known_args(self, *args, **kwargs)
if renpy.session.get("_reload", False):
args.compile = False
if args.command in compile_commands:
args.compile = True
@@ -195,7 +188,7 @@ def run():
args = renpy.game.args = ap.parse_args()
if args.warp:
if args.warp and not renpy.session.get("_reload", False):
renpy.warp.warp_spec = args.warp
if args.profile_display: # @UndefinedVariable
+25 -3
View File
@@ -1497,7 +1497,7 @@ class Camera(Node):
self.atl = atl
def diff_info(self):
return (ShowLayer, self.layer)
return (Camera, self.layer)
def execute(self):
next_node(self.next)
@@ -1697,19 +1697,22 @@ class Call(Node):
'label',
'arguments',
'expression',
'global_label',
]
def __new__(cls, *args, **kwargs):
self = Node.__new__(cls)
self.arguments = None
self.global_label = ""
return self
def __init__(self, loc, label, expression, arguments):
def __init__(self, loc, label, expression, arguments, global_label=""):
super(Call, self).__init__(loc)
self.label = label
self.expression = expression
self.arguments = arguments
self.global_label = global_label
def diff_info(self):
return (Call, self.label, self.expression)
@@ -1722,6 +1725,9 @@ class Call(Node):
if self.expression:
label = renpy.python.py_eval(label)
if isinstance(label, str) and label.startswith("."):
label = self.global_label + label
rv = renpy.game.context().call(label, return_site=self.next.name)
next_node(rv)
renpy.game.context().abnormal = True
@@ -1748,6 +1754,9 @@ class Call(Node):
except Exception:
return [ ]
if isinstance(label, str) and label.startswith("."):
label = self.global_label + label
if not renpy.game.script.has_label(label):
return [ ]
@@ -1966,13 +1975,20 @@ class Jump(Node):
__slots__ = [
'target',
'expression',
'global_label',
]
def __init__(self, loc, target, expression):
def __new__(cls, *args, **kwargs):
self = Node.__new__(cls)
self.global_label = ""
return self
def __init__(self, loc, target, expression, global_label=""):
super(Jump, self).__init__(loc)
self.target = target
self.expression = expression
self.global_label = global_label
def diff_info(self):
return (Jump, self.target, self.expression)
@@ -1989,6 +2005,9 @@ class Jump(Node):
if self.expression:
target = renpy.python.py_eval(target)
if isinstance(target, str) and target.startswith("."):
target = self.global_label + target
rv = renpy.game.script.lookup(target)
renpy.game.context().abnormal = True
@@ -2008,6 +2027,9 @@ class Jump(Node):
except Exception:
return [ ]
if isinstance(label, str) and label.startswith("."):
label = self.global_label + label
if not renpy.game.script.has_label(label):
return [ ]
+18 -7
View File
@@ -1294,20 +1294,17 @@ class Interpolation(Statement):
setattr(newts, k, v)
if k == "angle":
newts.last_angle = v
has_angle = True
elif k == "radius":
has_radius = True
elif k == "anchorangle":
newts.last_anchorangle = v
has_anchorangle = True
elif k == "anchorradius":
has_anchorradius = True
# Now, the things we change linearly are in the difference
# between the new and old states.
linear = trans.state.diff(newts)
@@ -1390,15 +1387,29 @@ class Interpolation(Statement):
else:
if has_angle:
last_angle = trans.state.angle or trans.state.last_angle
angle = (last_angle, newts.last_angle)
start = trans.state.angle
end = newts.last_angle
if end - start > 180:
start += 360
if end - start < -180:
start -= 360
angle = (start, end)
if has_radius:
radius = (trans.state.radius, newts.radius)
if has_anchorangle:
last_anchorangle = trans.state.anchorangle or trans.state.last_anchorangle
anchorangle = (last_anchorangle, newts.last_anchorangle)
start = trans.state.anchorangle
end = newts.last_anchorangle
if end - start > 180:
start += 360
if end - start < -180:
start -= 360
anchorangle = (start, end)
if has_anchorradius:
anchorradius = (trans.state.anchorradius, newts.anchorradius)
-217
View File
@@ -1,217 +0,0 @@
# Copyright 2004-2023 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 OTHER DEALINGS IN THE SOFTWARE.
from __future__ import division, absolute_import, with_statement, print_function, unicode_literals
from renpy.compat import PY2, basestring, bchr, bord, chr, open, pystr, range, round, str, tobytes, unicode # *
import renpy
import jnius # type: ignore
from renpy.audio.audio import MusicContext
VideoPlayer = jnius.autoclass("org.renpy.android.VideoPlayer")
class AndroidVideoChannel(object):
def __init__(self, name, file_prefix="", file_suffix="", default_loop=None):
# A list of queued filenames.
self.queue = [ ]
# The filename that's currently playing.
self.filename = None
# The videoplayer that's currently playing.
self.player = None
# The name assigned to this channel. This is used to look up
# information about the channel in the MusicContext object.
self.name = name
# The name of the mixer this channel uses. Set below, as there's
# no good default.
self.mixer = None
# The time the music in this channel was last changed.
self.last_changed = 0
# The callback that is called if the queue becomes empty.
self.callback = None
# Ignored.
self.synchro_start = False
self.wait_stop = False
self.loop = [ ]
# A prefix and suffix that are used to create the full filenames.
self.file_prefix = file_prefix
self.file_suffix = file_suffix
if default_loop is None:
# By default, should we loop the music?
self.default_loop = True
# Was this set explicitly?
self.default_loop_set = False
else:
self.default_loop = default_loop
self.default_loop_set = True
def get_context(self):
"""
Returns the MusicContext corresponding to this channel, taken from
the context object. Allocates a MusicContext if none exists.
"""
mcd = renpy.game.context().music
rv = mcd.get(self.name)
if rv is None:
rv = mcd[self.name] = MusicContext()
return rv
context = property(get_context)
def copy_context(self):
"""
Copies the MusicContext associated with this channel, updates the
ExecutionContext to point to the copy, and returns the copy.
"""
mcd = renpy.game.context().music
ctx = self.get_context().copy()
mcd[self.name] = ctx
return ctx
def start(self):
"""
Starts playing the first video in the queue.
"""
if not self.queue:
return
filename = self.queue.pop(0)
with renpy.loader.load(filename, directory="audio") as f:
real_fn = f.name
base = getattr(f, "base", -1)
length = getattr(f, "length", -1)
self.filename = filename
self.player = VideoPlayer(real_fn, base, length)
def stop(self):
if self.player is not None:
self.player.stop()
self.player = None
self.filename = None
def get_playing(self):
if self.player is None:
return None
if self.player.isPlaying():
return self.filename
def periodic(self):
# This should be set from something that checks to see if our
# mixer is muted.
force_stop = self.context.force_stop
if force_stop:
self.dequeue()
self.stop()
return
if self.get_playing():
return
if self.queue:
self.start()
def dequeue(self, even_tight=False):
"""
Clears the queued music, except for a first item that has
not been started.
"""
if self.get_playing():
self.queue = [ ]
else:
self.queue = self.queue[:1]
def interact(self):
"""
Called (mostly) once per interaction.
"""
self.periodic()
def fadeout(self, secs):
"""
Causes the playing music to be faded out for the given number
of seconds. Also clears any queued music.
"""
self.stop()
self.queue = [ ]
def enqueue(self, filenames, loop=True, synchro_start=False, fadein=0, tight=None, relative_volume=1.0):
self.queue.extend(filenames)
def set_volume(self, volume):
pass
def get_pos(self):
pass
def set_pan(self, pan, delay):
pass
def set_secondary_volume(self, volume, delay):
pass
def pause(self):
if self.player is not None:
self.player.pause()
def unpause(self):
if self.player is not None:
self.player.unpause()
def reload(self):
return
def read_video(self):
return None
def video_ready(self):
return 1
+1 -20
View File
@@ -792,20 +792,6 @@ class Channel(object):
return renpysound.video_ready(self.number)
# Use unconditional imports so these files get compiled during the build
# process.
try:
from renpy.audio.androidhw import AndroidVideoChannel
except Exception:
pass
try:
from renpy.audio.ioshw import IOSVideoChannel
except Exception:
pass
# A list of channels we know about.
all_channels = [ ]
@@ -882,12 +868,7 @@ def register_channel(name,
if not force and not renpy.game.context().init_phase and (" " not in name):
raise Exception("Can't register channel outside of init phase.")
if renpy.android and renpy.config.hw_video and name == "movie":
c = AndroidVideoChannel(name, default_loop=loop, file_prefix=file_prefix, file_suffix=file_suffix)
elif renpy.ios and renpy.config.hw_video and name == "movie":
c = IOSVideoChannel(name, default_loop=loop, file_prefix=file_prefix, file_suffix=file_suffix)
else:
c = Channel(name, loop, stop_on_mute, tight, file_prefix, file_suffix, buffer_queue, movie=movie, framedrop=framedrop)
c = Channel(name, loop, stop_on_mute, tight, file_prefix, file_suffix, buffer_queue, movie=movie, framedrop=framedrop)
c.mixer = mixer
-196
View File
@@ -1,196 +0,0 @@
from __future__ import division, absolute_import, with_statement, print_function, unicode_literals
from renpy.compat import PY2, basestring, bchr, bord, chr, open, pystr, range, round, str, tobytes, unicode # *
import renpy
import pyobjus # type: ignore
from renpy.audio.audio import MusicContext
VideoPlayer = pyobjus.autoclass("VideoPlayer")
class IOSVideoChannel(object):
def __init__(self, name, file_prefix="", file_suffix="", default_loop=None):
# A list of queued filenames.
self.queue = [ ]
# The filename that's currently playing.
self.filename = None
# The videoplayer that's currently playing.
self.player = None
# The name assigned to this channel. This is used to look up
# information about the channel in the MusicContext object.
self.name = name
# The name of the mixer this channel uses. Set below, as there's
# no good default.
self.mixer = None
# The time the music in this channel was last changed.
self.last_changed = 0
# The callback that is called if the queue becomes empty.
self.callback = None
# Ignored.
self.synchro_start = False
self.wait_stop = False
self.loop = [ ]
# A prefix and suffix that are used to create the full filenames.
self.file_prefix = file_prefix
self.file_suffix = file_suffix
if default_loop is None:
# By default, should we loop the music?
self.default_loop = True
# Was this set explicitly?
self.default_loop_set = False
else:
self.default_loop = default_loop
self.default_loop_set = True
def get_context(self):
"""
Returns the MusicContext corresponding to this channel, taken from
the context object. Allocates a MusicContext if none exists.
"""
mcd = renpy.game.context().music
rv = mcd.get(self.name)
if rv is None:
rv = mcd[self.name] = MusicContext()
return rv
context = property(get_context)
def copy_context(self):
"""
Copies the MusicContext associated with this channel, updates the
ExecutionContext to point to the copy, and returns the copy.
"""
mcd = renpy.game.context().music
ctx = self.get_context().copy()
mcd[self.name] = ctx
return ctx
def start(self):
"""
Starts playing the first video in the queue.
"""
if not self.queue:
return
filename = self.queue.pop(0)
with renpy.loader.load(filename, directory="audio") as f:
real_fn = f.name
self.filename = filename
self.player = VideoPlayer.alloc().initWithFile_(real_fn)
def stop(self):
if self.player is not None:
self.player.stop()
self.player = None
self.filename = None
def get_playing(self):
if self.player is None:
return None
if self.player.isPlaying():
return self.filename
def periodic(self):
# This should be set from something that checks to see if our
# mixer is muted.
force_stop = self.context.force_stop
if force_stop:
self.dequeue()
self.stop()
return
if self.player:
self.player.periodic()
if self.get_playing():
return
if self.queue:
self.start()
def dequeue(self, even_tight=False):
"""
Clears the queued music, except for a first item that has
not been started.
"""
if self.get_playing():
self.queue = [ ]
else:
self.queue = self.queue[:1]
def interact(self):
"""
Called (mostly) once per interaction.
"""
self.periodic()
def fadeout(self, secs):
"""
Causes the playing music to be faded out for the given number
of seconds. Also clears any queued music.
"""
self.stop()
self.queue = [ ]
def enqueue(self, filenames, loop=True, synchro_start=False, fadein=0, tight=None, relative_volume=1.0):
self.queue.extend(filenames)
def pause(self):
if self.player is not None:
self.player.pause()
def unpause(self):
if self.player is not None:
self.player.unpause()
def set_volume(self, volume):
pass
def get_pos(self):
pass
def set_pan(self, pan, delay):
pass
def set_secondary_volume(self, volume, delay):
pass
def reload(self):
return
def read_video(self):
return None
def video_ready(self):
return 1
+80 -5
View File
@@ -121,11 +121,82 @@ def mac_start(fn):
def popen_del(self, *args, **kwargs):
"""
Fix an issue where the __del__ method of popen doesn't wor,
Fix an issue where the __del__ method of popen doesn't work.
"""
return
def get_alternate_base(basedir, always=False):
"""
:undocumented:
Tries to find an alternate base directory. This exists in a writable
location, and is intended for use by a game that downloads its assets
to the device (generally for ios or android, where the assets may be
too big for the app store).
"""
# Determine the alternate base directory location.
if renpy.android:
altbase = os.path.join(os.environ["ANDROID_PRIVATE"], "base")
elif renpy.ios:
from pyobjus import autoclass # type: ignore
from pyobjus.objc_py_types import enum # type: ignore
NSSearchPathDirectory = enum("NSSearchPathDirectory", NSApplicationSupportDirectory=14)
NSSearchPathDomainMask = enum("NSSearchPathDomainMask", NSUserDomainMask=1)
NSFileManager = autoclass('NSFileManager')
manager = NSFileManager.defaultManager()
url = manager.URLsForDirectory_inDomains_(
NSSearchPathDirectory.NSApplicationSupportDirectory,
NSSearchPathDomainMask.NSUserDomainMask,
).lastObject()
# url.path seems to change type based on iOS version, for some reason.
try:
altbase = url.path().UTF8String()
except Exception:
altbase = url.path.UTF8String()
if isinstance(altbase, bytes):
altbase = altbase.decode("utf-8")
else:
altbase = os.path.join(basedir, "base")
if always:
return altbase
# Check to see if there's a game in there created with the
# current version of Ren'Py.
def ver(s):
"""
Returns the first three components of a version string.
"""
return tuple(int(i) for i in s.split(".")[:3])
import json
version_json = os.path.join(altbase, "update", "version.json")
if not os.path.exists(version_json):
return basedir
with open(version_json, "r") as f:
modules = json.load(f)
for v in modules.values():
if ver(v["renpy_version"]) != ver(renpy.version_only):
return basedir
return altbase
def bootstrap(renpy_base):
global renpy
@@ -189,6 +260,13 @@ def bootstrap(renpy_base):
sys.stderr.write("Base directory %r does not exist. Giving up.\n" % (basedir,))
sys.exit(1)
# Potentially use an alternate base directory.
try:
basedir = get_alternate_base(basedir)
except Exception:
import traceback
traceback.print_exc()
# Make game/ on Android.
if renpy.android:
if not os.path.exists(basedir + "/game"):
@@ -264,10 +342,7 @@ You may be using a system install of python. Please run {0}.sh,
renpy.config.gamedir = gamedir
renpy.config.args = [ ] # type: ignore
if renpy.android:
renpy.config.logdir = os.environ['ANDROID_PUBLIC']
else:
renpy.config.logdir = basedir
renpy.config.logdir = renpy.__main__.path_to_logdir(basedir)
if not os.path.exists(renpy.config.logdir):
os.makedirs(renpy.config.logdir, 0o777)
+65 -28
View File
@@ -95,6 +95,11 @@ class DialogueTextTags(object):
elif tag == "nw":
self.no_wait = True
if value is not None and not less_pauses:
self.pause_start.append(len(self.text))
self.pause_end.append(len(self.text))
self.pause_delay.append(value)
elif tag == "fast":
self.pause_start = [ len(self.text) ]
self.pause_end = [ ]
@@ -240,6 +245,7 @@ def show_display_say(who, what, who_args={}, what_args={}, window_args={},
layer=None,
properties={},
multiple=None,
retain=None,
**kwargs):
"""
This is called (by default) by renpy.display_say to add the
@@ -270,6 +276,11 @@ def show_display_say(who, what, who_args={}, what_args={}, window_args={},
@param kwargs: Additional keyword arguments should be ignored.
`retain`
If not None, the screen should be retained (not transient),
and the screen should be given the value of this argument as
its tag.
This function is required to return the ui.text() widget
displaying the what text.
"""
@@ -305,7 +316,10 @@ def show_display_say(who, what, who_args={}, what_args={}, window_args={},
tag = screen
if multiple:
if retain:
tag = retain
elif multiple:
if renpy.display.screen.has_screen("multiple_" + screen):
screen = "multiple_" + screen
@@ -325,7 +339,7 @@ def show_display_say(who, what, who_args={}, what_args={}, window_args={},
renpy.display.screen.show_screen(
screen,
_widget_properties=props,
_transient=True,
_transient=not retain,
_tag=tag,
who=who,
what=what,
@@ -439,7 +453,8 @@ def display_say(
ctc_force=False,
advance=True,
multiple=None,
dtt=None):
dtt=None,
retain=False):
global afm_text_queue
@@ -477,6 +492,9 @@ def display_say(
# Clears out transients.
renpy.exports.with_statement(None)
renpy.exports.checkpoint(True, hard=checkpoint)
return
# If we're not interacting, call the noniteractive_callbacks.
@@ -538,6 +556,19 @@ def display_say(
exception = None
retain_tag = "_retain_0"
retain_count = -1
if retain:
while True:
retain_count += 1
retain_tag = "_retain_{}".format(retain_count)
if not renpy.exports.get_screen(retain_tag):
break
if dtt.fast:
for i in renpy.config.say_sustain_callbacks:
i()
@@ -629,10 +660,16 @@ def display_say(
extend_text = "{done}" + extend_text
# Show the text.
show_args = { }
if multiple:
what_text = show_function(who, what_string, multiple=multiple)
else:
what_text = show_function(who, what_string)
show_args["multiple"] = multiple
if retain:
show_args["retain"] = retain_tag
what_text = show_function(who, what_string, **show_args)
if isinstance(what_text, tuple):
what_text = renpy.display.screen.get_widget(what_text[0], what_text[1], what_text[2])
@@ -859,6 +896,7 @@ class ADVCharacter(object):
callback=d('callback'),
type=d('type'),
advance=d('advance'),
retain=d('retain'),
)
self._statement_name = properties.pop("statement_name", None)
@@ -959,34 +997,27 @@ class ADVCharacter(object):
return screen, show_args, who_args, what_args, window_args, properties
def do_show(self, who, what, multiple=None, extra_properties=None):
def do_show(self, who, what, multiple=None, extra_properties=None, retain=None):
screen, show_args, who_args, what_args, window_args, properties = self.get_show_properties(extra_properties)
show_args = dict(show_args)
if multiple is not None:
show_args["multiple"] = multiple
return self.show_function(
who,
what,
who_args=who_args,
what_args=what_args,
window_args=window_args,
screen=screen,
properties=properties,
multiple=multiple,
**show_args)
if retain:
show_args["retain"] = retain
else:
return self.show_function(
who,
what,
who_args=who_args,
what_args=what_args,
window_args=window_args,
screen=screen,
properties=properties,
**show_args)
return self.show_function(
who,
what,
who_args=who_args,
what_args=what_args,
window_args=window_args,
screen=screen,
properties=properties,
**show_args)
# This is called after the last interaction is done.
@@ -1631,6 +1662,12 @@ def Character(name=NotSet, kind=None, **properties):
`screen`
The name of the screen that is used to display the dialogue.
`retain`
If not true, an unused tag is generated for each line of dialogue,
and the screens are shown non-transiently. Call :func:`renpy.clear_retain`
to remove all retaint screens. This is almost always used with
:doc:`bubble`.
Keyword arguments beginning with ``show_`` have the prefix
stripped off, and are passed to the screen as arguments. For
example, the value of ``show_myflag`` will become the value of
+1 -1
View File
@@ -196,7 +196,7 @@ init -1500 python:
:func:`renpy.show_screen` function.
"""
return Show(screen, transition, _transient=True, *args, **kwargs)
return Show(screen, transition, *args, _transient=True, **kwargs)
@renpy.pure
class Hide(Action, DictEquality):
+222 -274
View File
@@ -21,10 +21,7 @@
init -1600 python:
##########################################################################
# Functions that set variables or fields.
__FieldNotFound = object()
__Sentinel = object()
def _get_field(obj, name, kind):
@@ -34,372 +31,323 @@ init -1600 python:
rv = obj
for i in name.split("."):
rv = getattr(rv, i, __FieldNotFound)
if rv is __FieldNotFound:
raise NameError("The {} {} does not exist.".format(kind, name))
rv = getattr(rv, i, __Sentinel)
if rv is __Sentinel:
raise Exception("The {!r} {} does not exist.".format(name, kind))
return rv
def _set_field(obj, name, value, kind):
fields, _, attr = name.rpartition(".")
obj = _get_field(obj, fields, kind)
try:
obj = _get_field(obj, fields, kind)
setattr(obj, attr, value)
except Exception:
raise NameError("The {} {} does not exist.".format(kind, name))
raise Exception("The {!r} {} cannot be set.".format(name, kind))
@renpy.pure
class SetField(Action, FieldEquality):
init -1600 python hide:
Accessor = Manager = python_object
# clarify the role of each class without adding to the mro
# and without having ancestors placed between RevertableObject and python_object in the mro
# Accessor mixins : manage how the data is accessed
# defines a __call__ which gets the value to set from the value_to_set() method
# defines a current_value() method which returns the current value of the accessed data or raise an exception
# may define the `kind` class attribute, which is used in error messages
class Field(Accessor):
"""
:doc: data_action
:args: (object, field, value)
Causes the a field on an object to be set to a given value.
`object` is the object, `field` is a string giving the name of the
field to set, and `value` is the value to set it to.
An Accessor mixin class for Actions setting a field on an object.
"""
identity_fields = [ "object" ]
equality_fields = [ "field", "value" ]
identity_fields = ("object",)
equality_fields = ("field",)
kind = "field"
def __init__(self, object, field, value, kind="field"):
def __init__(self, object, field, *args, **kwargs):
super(Field, self).__init__(*args, **kwargs)
self.object = object
self.field = field
self.value = value
self.kind = kind
def __call__(self):
_set_field(self.object, self.field, self.value, self.kind)
_set_field(self.object, self.field, self.value_to_set(), self.kind)
renpy.restart_interaction()
def get_selected(self):
return _get_field(self.object, self.field, self.kind) == self.value
def current_value(self):
return _get_field(self.object, self.field, self.kind)
@renpy.pure
def SetVariable(name, value):
class Variable(Field):
"""
:doc: data_action
Causes the variable called `name` to be set to `value`.
The `name` argument must be a string, and can be a simple name like "strength", or
one with dots separating the variable from fields, like "hero.strength"
or "persistent.show_cutscenes".
An Accessor mixin class for Actions setting a global variable.
"""
return SetField(store, name, value, kind="variable")
kind = "global variable"
@renpy.pure
class SetDict(Action, FieldEquality):
def __init__(self, name, *args, **kwargs):
super(Variable, self).__init__(store, name, *args, **kwargs)
class Dict(Accessor):
"""
:doc: data_action
Causes the value of `key` in `dict` to be set to `value`.
This also works with lists, where `key` is the index at which
the value will be set.
An Accessor mixin class for Actions setting an index on a sequence or a key on a mapping.
"""
identity_fields = [ "dict" ]
equality_fields = [ "key", "value" ]
identity_fields = ("dict",)
equality_fields = ("key",)
kind = "key or index"
def __init__(self, dict, key, value):
def __init__(self, dict, key, *args, **kwargs):
super(Dict, self).__init__(*args, **kwargs)
self.dict = dict
self.key = key
self.value = value
def __call__(self):
self.dict[self.key] = self.value
self.dict[self.key] = self.value_to_set()
renpy.restart_interaction()
def get_selected(self):
if self.key not in self.dict:
return False
def current_value(self):
key = self.key
try:
return self.dict[self.key]
except LookupError as e:
raise Exception("The {!r} {} does not exist.".format(key, self.kind)) # from e # PY3 only
return self.dict[self.key] == self.value
@renpy.pure
class SetScreenVariable(Action, FieldEquality):
class ScreenVariable(Accessor):
"""
:doc: data_action
Causes the variable called `name` associated with the current screen
to be set to `value`.
In a ``use``\ d screen, this action sets the variable in the context
of the screen containing the ``use``\ d one(s).
To set variables within a ``use``\ d screen, and only in that
case, use :func:`SetLocalVariable` instead.
An Accessor mixin class for Actions setting a screen variable.
"""
identity_fields = [ "value" ]
equality_fields = [ "name" ]
identity_fields = ()
equality_fields = ("name",)
kind = "screen variable"
def __init__(self, name, value):
def __init__(self, name, *args, **kwargs):
super(ScreenVariable, self).__init__(*args, **kwargs)
self.name = name
self.value = value
def __call__(self):
cs = renpy.current_screen()
if cs is None:
return
cs.scope[self.name] = self.value
cs.scope[self.name] = self.value_to_set()
renpy.restart_interaction()
def get_selected(self):
def current_value(self):
cs = renpy.current_screen()
if cs is None:
return False
raise Exception("No current screen.")
if self.name not in cs.scope:
return False
return cs.scope[self.name] == self.value
# Not pure.
def SetLocalVariable(name, value):
"""
:doc: data_action
Causes the variable called `name` to be set to `value` in the current
local context.
This function is only useful in a screen that has been ``use``\ d by
another screen, as it provides a way of setting the value of a
variable inside the used screen. In all other cases,
:func:`SetScreenVariable` should be preferred, as it allows more
of the screen to be cached.
For more information, see :ref:`sl-use`.
This must be created in the context that the variable is set
in - it can't be passed in from somewhere else.
"""
return SetDict(sys._getframe(1).f_locals, name, value)
@renpy.pure
class ToggleField(Action, FieldEquality):
"""
:doc: data_action
:args: (object, field, true_value=None, false_value=None)
Toggles `field` on `object`. Toggling means to invert the boolean
value of that field when the action is performed.
`true_value`
If not None, then this is the true value we use.
`false_value`
If not None, then this is the false value we use.
"""
identity_fields = [ "object"]
equality_fields = [ "field", "true_value", "false_value" ]
kind = "field"
def __init__(self, object, field, true_value=None, false_value=None, kind="field"):
self.object = object
self.field = field
self.true_value = true_value
self.false_value = false_value
self.kind = kind
def __call__(self):
value = _get_field(self.object, self.field, self.kind)
if self.true_value is not None:
value = (value == self.true_value)
value = not value
if self.true_value is not None:
if value:
value = self.true_value
else:
value = self.false_value
_set_field(self.object, self.field, value, self.kind)
renpy.restart_interaction()
def get_selected(self):
rv = _get_field(self.object, self.field, self.kind)
if self.true_value is not None:
rv = (rv == self.true_value)
rv = cs.scope.get(self.name, __Sentinel)
if rv is __Sentinel:
raise Exception("The {!r} {} does not exist.".format(self.name, self.kind))
return rv
@renpy.pure
def ToggleVariable(variable, true_value=None, false_value=None):
class LocalVariable(Dict):
"""
:doc: data_action
Toggles the variable whose name is given in `variable`.
The `variable` argument must be a string, and can be a simple name like "strength", or
one with dots separating the variable from fields, like "hero.strength"
or "persistent.show_cutscenes".
`true_value`
If not None, then this is the true value used.
`false_value`
If not None, then this is the false value used.
An Accessor mixin class for Actions setting a local variable.
"""
return ToggleField(store, variable, true_value=true_value, false_value=false_value, kind="variable")
kind = "local variable"
def __init__(self, name, *args, **kwargs):
super(LocalVariable, self).__init__(sys._getframe(1).f_locals,
name,
*args, **kwargs)
@renpy.pure
class ToggleDict(Action, FieldEquality):
# Manager mixins : manage what value gets written
# defines a value_to_set() method which returns the value to be set
# may define get_selected and such methods
# both of these may call the current_value() method
class Set(Manager):
"""
:doc: data_action
Toggles the value of `key` in `dict`. It also works on
lists, in which case `key` is the index of the value to toggle.
Toggling means to invert the value when the action is performed.
`true_value`
If not None, then this is the true value used.
`false_value`
If not None, then this is the false value used.
A Manager mixin class for Actions setting a single value
"""
identity_fields = [ "dict", ]
equality_fields = [ "key", "true_value", "false_value" ]
identity_fields = ()
equality_fields = ("value",)
def __init__(self, dict, key, true_value=None, false_value=None):
self.dict = dict
self.key = key
self.true_value = true_value
self.false_value = false_value
def __init__(self, value, *args, **kwargs):
super(Set, self).__init__(*args, **kwargs)
self.value = value
def __call__(self):
value = self.dict[self.key]
if self.true_value is not None:
value = (value == self.true_value)
value = not value
if self.true_value is not None:
if value:
value = self.true_value
else:
value = self.false_value
self.dict[self.key] = value
renpy.restart_interaction()
def value_to_set(self):
return self.value
def get_selected(self):
try:
rv = self.dict[self.key]
except (KeyError, IndexError):
val = self.current_value()
except Exception:
return False
if self.true_value is not None:
rv = (rv == self.true_value)
return val == self.value
return rv
# Not pure.
def ToggleLocalVariable(name, true_value=None, false_value=None):
class Toggle(Manager):
"""
:doc: data_action
Toggles the value of the variable called `name` in the current local context.
This function is only useful in a screen that has been ``use``\ d by
another screen, as it provides a way of setting the value of a
variable inside the used screen. In all other cases,
:func:`ToggleScreenVariable` should be preferred, as it allows more
of the screen to be cached.
For more information, see :ref:`sl-use`.
This must be created in the context that the variable is set
in - it can't be passed in from somewhere else.
`true_value`
If not None, then this is the true value used.
`false_value`
If not None, then this is the false value used.
A Manager mixin class for Actions toggling between two values.
"""
return ToggleDict(sys._getframe(1).f_locals, name, true_value=true_value, false_value=false_value)
identity_fields = ()
equality_fields = ("true_value", "false_value")
@renpy.pure
class ToggleScreenVariable(Action, FieldEquality):
"""
:doc: data_action
Toggles the value of the variable called `name` in the current screen.
In a ``use``\ d screen, this action accesses and sets the given variable
in the context of the screen containing the ``use``\ d one(s).
To access and set variables within a ``use``\ d screen, and only in that
case, use :func:`ToggleLocalVariable` instead.
`true_value`
If not None, then this is the true value used.
`false_value`
If not None, then this is the false value used.
"""
equality_fields = [ "name", "true_value", "false_value" ]
def __init__(self, name, true_value=None, false_value=None):
self.name = name
def __init__(self, true_value=None, false_value=None, *args, **kwargs):
super(Toggle, self).__init__(*args, **kwargs)
self.true_value = true_value
self.false_value = false_value
def __call__(self):
cs = renpy.current_screen()
def value_to_set(self):
value = self.current_value()
if cs is None:
return
value = cs.scope[self.name]
if self.true_value is not None:
value = (value == self.true_value)
tv = self.true_value
if tv is not None:
value = (value == tv)
value = not value
if self.true_value is not None:
if tv is not None:
if value:
value = self.true_value
return tv
else:
value = self.false_value
return self.false_value
cs.scope[self.name] = value
renpy.restart_interaction()
return value
def get_selected(self):
cs = renpy.current_screen()
if cs is None:
try:
val = self.current_value()
except Exception:
return False
if self.name not in cs.scope:
tv = self.true_value
if tv is not None:
return val == tv
return bool(val)
class Cycle(Manager):
"""
A Manager mixin class for Actions cycling through a list of values.
Warning : the provided values must not be duplicated, otherwise some values can never be set.
"""
identity_fields = ()
equality_fields = ("values", "loop")
def __init__(self, values, *args, **kwargs):
if kwargs.pop("reverse", False):
values = values[::-1]
# the reversed builtin returns a non-indexable iterator
# and we only support sequences anyway - iterators are not picklable
self.loop = kwargs.pop("loop", True)
super(Cycle, self).__init__(*args, **kwargs)
self.values = renpy.easy.to_tuple(values)
def value_to_set(self):
values = self.values
value = self.current_value()
if value in values:
idx = values.index(value) + 1
lnv = len(values)
if self.loop and idx >= lnv:
idx -= lnv
else:
idx = 0
return values[idx]
def get_sensitive(self):
values = self.values
if not values:
return False
rv = cs.scope[self.name]
if self.loop:
return True
if self.true_value is not None:
rv = (rv == self.true_value)
try:
value = self.current_value()
except Exception:
return False
if value in values:
idx = values.index(value)+1
if idx >= len(values):
return False
return True
class Increment(Manager):
"""
A Manager mixin class for Actions incrementing a value by a set amount.
"""
identity_fields = ()
equality_fields = ("amount",)
def __init__(self, amount=1, *args, **kwargs):
super(Increment, self).__init__(*args, **kwargs)
self.amount = amount
def value_to_set(self):
return self.current_value() + self.amount
def get_sensitive(self):
try:
value = self.current_value()
except Exception:
return False
try:
value + self.amount
except Exception:
return False
return True
return rv
for accessor in (Field, Variable, Dict, ScreenVariable, LocalVariable):
for manager in (Set, Toggle, Cycle, Increment):
name = manager.__name__ + accessor.__name__
doc = ":doc: generated_data_action"
if config.generating_documentation:
from inspect import signature, Signature, Parameter
params = []
for bas in (accessor, manager):
sig = signature(bas.__init__)
for k, param in enumerate(sig.parameters.values()):
if k and (param.kind not in (param.VAR_POSITIONAL, param.VAR_KEYWORD)):
params.append(param)
if manager is Cycle:
params.append(Parameter("reverse", Parameter.KEYWORD_ONLY, default=False))
params.append(Parameter("loop", Parameter.KEYWORD_ONLY, default=True))
doc += "\n:args: " + str(Signature(parameters=params)) + "\n\nSee :ref:`data-actions`."
clsdict = python_dict(
identity_fields=manager.identity_fields+accessor.identity_fields,
equality_fields=manager.equality_fields+accessor.equality_fields,
__doc__=doc,
)
cls = type(name, (accessor, manager, Action, FieldEquality), clsdict)
if accessor is not LocalVariable:
renpy.pure(name)
setattr(store, name, cls)
init -1600 python:
@renpy.pure
class AddToSet(Action, FieldEquality):
+17
View File
@@ -291,6 +291,23 @@ init -1500 python:
Such Json data is added to a save slot by callbacks registered using
:var:`config.save_json_callbacks`.
By default, the following keys are defined:
`_save_name`
The value of :var:`save_name` when the game was saved.
`_renpy_version`
The version of Ren'Py the save was created with.
`_version`
The value of :var:`config.version` when the save was created.
`_game_runtime`
The result of calling :func:`renpy.get_game_runtime`.
`_ctime`
The time the save was created, in seconds since January 1, 1970, UTC.
"""
json = renpy.slot_json(__slotname(name, page, slot))
+5 -3
View File
@@ -36,13 +36,15 @@ init -1500 python:
class ShowMenu(Action, DictEquality):
"""
:doc: menu_action
:args: (screen=_game_menu_screen, *args, _transition=config.intra_transition, **kwargs)
Causes us to enter the game menu, if we're not there already. If we
are in the game menu, then this shows a screen or jumps to a label.
`screen` is usually the name of a screen, which is shown using
the screen mechanism. If the screen doesn't exist, then "_screen"
is appended to it, and that label is jumped to.
the screen mechanism with the ``*args`` and ``**kwargs`` passed to
the screen. If the screen doesn't exist, then "_screen" is appended
to it, and that label is jumped to, ignoring `args` and `kwargs`.
If the optional keyword argument `_transition` is given, the
menu will change screens using the provided transition.
@@ -97,7 +99,7 @@ init -1500 python:
if renpy.has_screen(screen):
renpy.transition(transition)
renpy.show_screen(screen, _transient=True, *self.args, **self.kwargs)
renpy.show_screen(screen, *self.args, _transient=True, **self.kwargs)
renpy.restart_interaction()
elif renpy.has_label(screen):
+52 -4
View File
@@ -609,7 +609,7 @@ init -1500 python:
The sensitivity and selectedness of this action match those
of the `yes` action.
See :func:`layout.yesno_screen` for a function version of this action.
See :func:`renpy.confirm` for a function version of this action.
"""
@@ -700,10 +700,8 @@ init -1500 python:
amount = delta * adjustment.step
elif self.amount == "page":
amount = delta * adjustment.page
elif isinstance(self.amount, float) and not isinstance(self.amount, absolute):
amount = delta * self.amount * adjustment.range
else:
amount = delta * self.amount
amount = absolute.compute_raw(delta*self.amount, adjustment.range)
if self.delay == 0.0:
adjustment.change(adjustment.value + amount)
@@ -878,6 +876,56 @@ init -1500 python:
return current.screen_name[0]
@renpy.pure
class CopyToClipboard(Action):
"""
:doc: other_action
Copies the string `s` to the system clipboard, if possible. This
should work on desktop and mobile platforms, but will not work
on the web.
"""
def __init__(self, s):
self.s = s
def __call__(self):
import pygame.scrap
pygame.scrap.put(pygame.SCRAP_TEXT, self.s.encode("utf-8"))
@renpy.pure
class EditFile(Action):
"""
:doc: other_action
Requests Ren'Py to open the given file in a text editor, if possible.
This will work on some platforms but not others.
`filename`
If given, the filename to open. If None, the current filename
and line number are used, with `line` being ignored.
`line`
The line number to open the file at.
"""
def __init__(self, filename=None, line=1):
self.filename = filename
self.line = line
def __call__(self):
filename = self.filename
line = self.line
if filename is None:
filename, line = renpy.get_filename_line()
try:
renpy.launch_editor([ filename ], line)
except Exception:
pass
init -1500:
transform _notify_transform:
+152 -216
View File
@@ -112,59 +112,26 @@ init -1500 python:
self.old_value = other.value
self.start_time = None
@renpy.pure
class DictValue(BarValue, FieldEquality):
"""
:doc: value
A value that allows the user to adjust the value of a key
in a dict.
`dict`
The dict.
`key`
The key.
`range`
The range to adjust over.
`max_is_zero`
If True, then when the value of a key is zero, the value of the
bar will be range, and all other values will be shifted down by 1.
This works both ways - when the bar is set to the maximum, the
value of a key is set to 0.
`style`
The styles of the bar created.
`offset`
An offset to add to the value.
`step`
The amount to change the bar by. If None, defaults to 1/10th of
the bar.
`action`
If not None, an action to call when the field has changed.
"""
class __GenericValue(BarValue, FieldEquality):
# pickle defaults
offset = 0
action = None
force_step = False
identity_fields = [ 'dict' ]
equality_fields = [ 'key', 'range', 'max_is_zero', 'style', 'offset', 'step', 'action', 'force_step' ]
identity_fields = ()
equality_fields = ('range', 'max_is_zero', 'style', 'offset', 'step', 'action', 'force_step')
def __init__(self, dict, key, range, max_is_zero=False, style="bar", offset=0, step=None, action=None, force_step=False):
self.dict = dict
self.key = key
def __init__(self, range, max_is_zero=False, style="bar", offset=0, step=None, action=None, force_step=False):
self.range = range
self.max_is_zero = max_is_zero
self.style = style
self.offset = offset
self.force_step = force_step
if step is None:
if isinstance(range, float):
step = range / 10.0
else:
step = max(range / 10, 1)
step = max(range // 10, 1)
self.step = step
self.action = action
@@ -178,14 +145,13 @@ init -1500 python:
value += self.offset
self.dict[self.key] = value
self.set_value(value)
renpy.restart_interaction()
return renpy.run(self.action)
def get_adjustment(self):
value = self.dict[self.key]
value = self.get_value()
value -= self.offset
@@ -207,10 +173,46 @@ init -1500 python:
return self.style, "v" + self.style
@renpy.pure
class FieldValue(BarValue, FieldEquality):
class DictValue(__GenericValue):
"""
:doc: value
:args: (self, object, field, range, max_is_zero=False, style="bar", offset=0, step=None, action=None, force_step=False)
:args: {args}
A bar value that allows the user to adjust the value of a key in
a dict, or of an element at a particular index in a list.
`dict`
The dict, or the list.
`key`
The key, or the index when using a list.
"""
kind = "key or index"
identity_fields = ('dict',)
equality_fields = __GenericValue.equality_fields + ('key',)
def __init__(self, dict, key, *args, **kwargs):
self.dict = dict
self.key = key
super(DictValue, self).__init__(*args, **kwargs)
def get_value(self):
value = self.dict[self.key]
return value
def set_value(self, value):
try:
self.dict[self.key] = value
except LookupError as e:
raise Exception("The {!r} {} does not exist".format(self.key, self.kind)) # from e # PY3 only
@renpy.pure
class FieldValue(__GenericValue):
"""
:doc: value
:args: {args}
A bar value that allows the user to adjust the value of a field
on an object.
@@ -218,223 +220,157 @@ init -1500 python:
`object`
The object.
`field`
The field, a string.
`range`
The range to adjust over.
`max_is_zero`
If True, then when the field is zero, the value of the
bar will be range, and all other values will be shifted
down by 1. This works both ways - when the bar is set to
the maximum, the field is set to 0.
This is used internally, for some preferences.
`style`
The styles of the bar created.
`offset`
An offset to add to the value.
`step`
The amount to change the bar by. If None, defaults to 1/10th of
the bar.
`action`
If not None, an action to call when the field has changed.
The field name, a string.
"""
offset = 0
action = None
force_step = False
kind = "field"
identity_fields = [ 'object', ]
equality_fields = [ 'range', 'max_is_zero', 'style', 'offset', 'step', 'action', 'force_step', 'field' ]
identity_fields = ('object',)
equality_fields = __GenericValue.equality_fields + ("field",)
def __init__(self, object, field, range, max_is_zero=False, style="bar", offset=0, step=None, action=None, force_step=False, kind="field"):
def __init__(self, object, field, *args, **kwargs):
self.object = object
self.field = field
self.range = range
self.max_is_zero = max_is_zero
self.style = style
self.offset = offset
self.force_step = force_step
self.kind = kind
super(FieldValue, self).__init__(*args, **kwargs)
if step is None:
if isinstance(range, float):
step = range / 10.0
else:
step = max(range / 10, 1)
def get_value(self):
value = _get_field(self.object, self.field, self.kind)
self.step = step
self.action = action
return value
def changed(self, value):
if self.max_is_zero:
if value == self.range:
value = 0
else:
value = value + 1
value += self.offset
_set_field(self.object, self.field, value, "field")
renpy.restart_interaction()
return renpy.run(self.action)
def get_adjustment(self):
value = _get_field(self.object, self.field, "field")
value -= self.offset
if self.max_is_zero:
if value == 0:
value = self.range
else:
value = value - 1
return ui.adjustment(
range=self.range,
value=value,
changed=self.changed,
step=self.step,
force_step=self.force_step,
)
def get_style(self):
return self.style, "v" + self.style
def set_value(self, value):
_set_field(self.object, self.field, value, self.kind)
@renpy.pure
def VariableValue(variable, range, max_is_zero=False, style="bar", offset=0, step=None, action=None, force_step=False):
class VariableValue(FieldValue):
"""
:doc: value
:args: {args}
A bar value that allows the user to adjust the value of a variable
in the default store.
A bar value that allows the user to adjust the value of a variable in
the default store.
`variable`
The `variable` parameter must be a string, and can be a simple name like "strength", or
one with dots separating the variable from fields, like "hero.strength"
or "persistent.show_cutscenes".
`range`
The range to adjust over.
`max_is_zero`
If True, then when the field is zero, the value of the
bar will be range, and all other values will be shifted
down by 1. This works both ways - when the bar is set to
the maximum, the field is set to 0.
This is used internally, for some preferences.
`style`
The styles of the bar created.
`offset`
An offset to add to the value.
`step`
The amount to change the bar by. If None, defaults to 1/10th of
the bar.
`action`
If not None, an action to call when the field has changed.
The `variable` parameter must be a string, and can be a simple
name like "strength", or one with dots separating the variable
from fields, like "hero.strength" or "persistent.show_cutscenes".
"""
return FieldValue(store, variable, range, max_is_zero=max_is_zero, style=style, offset=offset, step=step, action=action, force_step=force_step, kind="variable")
kind = "variable"
def __init__(self, variable, *args, **kwargs):
super(VariableValue, self).__init__(store, variable, *args, **kwargs)
@renpy.pure
class ScreenVariableValue(BarValue, FieldEquality):
class ScreenVariableValue(__GenericValue):
"""
:doc: value
:args: {args}
A bar value that adjusts the value of a variable in a screen.
In a ``use``\ d screen, this targets a variable in the context of
the screen containing the ``use``\ d one(s). To target variables
within a ``use``\ d screen, and only in that case, use
:func:`LocalVariableValue` instead.
`variable`
A string giving the name of the variable to adjust.
"""
kind = "screen variable" # not used in error messages, only in doc-gen
identity_fields = ()
equality_fields = __GenericValue.equality_fields + ('variable',)
def __init__(self, variable, *args, **kwargs):
self.variable = variable
super(ScreenVariableValue, self).__init__(*args, **kwargs)
def get_value(self):
cs = renpy.current_screen()
if cs is None:
raise Exception("No current screen.")
if self.variable not in cs.scope:
raise Exception("The {!r} variable is not defined in the {} screen.".format(self.variable, cs.screen_name))
value = cs.scope[self.variable]
return value
def set_value(self, value):
cs = renpy.current_screen()
cs.scope[self.variable] = value
# unpure
class LocalVariableValue(DictValue):
"""
:doc: value
:args: {args}
A bar value that adjusts the value of a variable in a ``use``\ d
screen.
To target a variable in a top-level screen, prefer using
:func:`ScreenVariableValue`.
For more information, see :ref:`sl-use`.
This must be created in the context that the variable is set in
- it can't be passed in from somewhere else.
`variable`
A string giving the name of the variable to adjust.
"""
kind = "local variable"
def __init__(self, variable, *args, **kwargs):
super(LocalVariableValue, self).__init__(sys._getframe(1).f_locals, variable, *args, **kwargs)
init -1500 python hide:
if config.generating_documentation:
import inspect
import itertools
generic_params = tuple(inspect.signature(__GenericValue.__init__).parameters.values())[1:]
suffix = inspect.cleandoc("""
`range`
The range to adjust over.
`max_is_zero`
If True, then when the field is zero, the value of the
bar will be range, and all other values will be shifted
down by 1. This works both ways - when the bar is set to
the maximum, the field is set to 0.
If True, then when the {kind}'s value is zero, the value of the
bar will be `range`, and all other values will be shifted down
by 1. This works both ways - when the bar is set to the maximum,
the value of the {kind} is set to 0.
This is used internally, for some preferences.
`style`
The styles of the bar created.
The styles of the created bar.
`offset`
An offset to add to the value.
`step`
The amount to change the bar by. If None, defaults to 1/10th of
the bar.
`action`
If not None, an action to call when the field has changed.
"""
If not None, an action to call when the {kind}'s value is changed.
""")
action = None
offset = 0
force_step = False
for value in (DictValue, FieldValue, VariableValue, ScreenVariableValue, LocalVariableValue):
docstr = inspect.cleandoc(value.__doc__)
identity_fields = [ ]
equality_fields = [ 'variable', 'max_is_zero', 'style', 'offset', 'step', 'action', 'force_step' ]
params = []
for param in itertools.islice(inspect.signature(value.__init__).parameters.values(), 1, None):
if param.kind not in (param.VAR_POSITIONAL, param.VAR_KEYWORD):
params.append(param)
def __init__(self, variable, range, max_is_zero=False, style="bar", offset=0, step=None, action=None, force_step=False):
self.variable = variable
self.range = range
self.max_is_zero = max_is_zero
self.style = style
self.offset = offset
self.force_step = force_step
params.extend(generic_params)
if step is None:
if isinstance(range, float):
step = range / 10.0
else:
step = max(range / 10, 1)
value.__doc__ = (docstr+"\n"+suffix).format(
args=inspect.Signature(parameters=params),
kind=value.kind)
self.step = step
self.action = action
def changed(self, value):
cs = renpy.current_screen()
if self.max_is_zero:
if value == self.range:
value = 0
else:
value = value + 1
value += self.offset
cs.scope[self.variable] = value
renpy.restart_interaction()
return renpy.run(self.action)
def get_adjustment(self):
cs = renpy.current_screen()
if (cs is None) or (self.variable not in cs.scope):
raise Exception("{} is not defined in the {} screen.".format(self.variable, cs.screen_name))
value = cs.scope[self.variable]
value -= self.offset
if self.max_is_zero:
if value == 0:
value = self.range
else:
value = value - 1
return ui.adjustment(
range=self.range,
value=value,
changed=self.changed,
step=self.step,
force_step=self.force_step,
)
def get_style(self):
return self.style, "v" + self.style
init -1500 python:
@renpy.pure
class MixerValue(BarValue, DictEquality):
+16 -9
View File
@@ -159,6 +159,7 @@ init -1500 python in build:
("*.dll", None),
("*.manifest", None),
("*.keystore", None),
("update.pem", None),
("lib/", None),
("renpy/", None),
@@ -207,6 +208,7 @@ init -1500 python in build:
("web-presplash.png", "web"),
("web-presplash.jpg", "web"),
("web-presplash.webp", "web"),
("web-icon.png", "web"),
("progressive_download.txt", "web"),
("steam_appid.txt", None),
@@ -460,15 +462,15 @@ init -1500 python in build:
itch_project = None
# Maps from files to itch.io channels.
itch_channels = [
( "*-all.zip", "win-osx-linux" ),
( "*-market.zip", "win-osx-linux" ),
( "*-pc.zip", "win-linux" ),
( "*-win.zip", "win" ),
( "*-mac.zip", "osx" ),
( "*-linux.tar.bz2", "linux" ),
( "*-release.apk", "android" ),
]
itch_channels = {
"*-all.zip" : "win-osx-linux",
"*-market.zip" : "win-osx-linux",
"*-pc.zip" : "win-linux",
"*-win.zip" : "win",
"*-mac.zip" : "osx",
"*-linux.tar.bz2" : "linux",
"*-release.apk" : "android",
}
# Should we include the old Ren'Py themes?
include_old_themes = True
@@ -506,6 +508,9 @@ init -1500 python in build:
# Should the sdk-fonts directory be renamed to game?
_sdk_fonts = False
# Which update formats should be built?
update_formats = [ "rpu" ]
# This function is called by the json_dump command to dump the build data
# into the json file.
def dump():
@@ -589,6 +594,8 @@ init -1500 python in build:
rv["_sdk_fonts"] = _sdk_fonts
rv["update_formats"] = update_formats
return rv
init 1500 python in build:
+6 -2
View File
@@ -208,7 +208,6 @@ init -1100 python:
if version <= (7, 3, 5):
config.side_image_requires_attributes = False
config.window_functions_set_auto = False
config.hw_video = True
config.who_what_sub_compat = 0
if version <= (7, 4, 0):
@@ -296,6 +295,11 @@ init -1100 python:
config.tts_front_to_back = False
_greedy_rollback = False
if _compat_versions(version, (7, 6, 99), (8, 1, 99)):
config.simple_box_reverse = True
build.itch_channels = list(build.itch_channels.items())
style.default.shaper = "freetype"
# The version of Ren'Py this script is intended for, or
# None if it's intended for the current version.
config.script_version = None
@@ -406,7 +410,7 @@ init 1100 python hide:
config.layers.append('screens')
if "Fullscreen" in config.translations:
fs = _("Fullscreen")
fs = __("Fullscreen")
config.translations.setdefault("Fullscreen 4:3", fs + " 4:3")
config.translations.setdefault("Fullscreen 16:9", fs + " 16:9")
config.translations.setdefault("Fullscreen 16:10", fs + " 16:10")
+9 -5
View File
@@ -593,7 +593,7 @@ init -1500 python in _console:
else:
break
if s.rstrip().endswith(":"):
if s.partition("#")[0].rstrip().endswith(":"):
rv += " "
if not s.rstrip():
@@ -612,7 +612,7 @@ init -1500 python in _console:
self.lines.append(line)
indent = get_indent(line)
if indent:
if indent or line.startswith("@") or line.endswith("\\"):
self.lines.append(indent)
return
@@ -717,7 +717,7 @@ init -1500 python in _console:
pass
else:
result = renpy.python.py_eval(code)
if persistent._console_short:
if persistent._console_short and not getattr(result, "_console_always_long", False):
he.result = aRepr.repr(result)
else:
he.result = repr(result)
@@ -1103,21 +1103,25 @@ screen _console:
has vbox
$ last_line = ""
for line in lines:
hbox:
spacing 4
if line[:1] != " ":
if (line[:1] != " ") and (last_line[:1] != "@") and (last_line[-1:] != "\\"):
text "> " style "_console_prompt"
else:
text "... " style "_console_prompt"
text "[line!q]" style "_console_input_text"
$ last_line = line
hbox:
spacing 4
if default[:1] != " ":
if (default[:1] != " ") and (last_line[:1] != "@") and (last_line[-1:] != "\\"):
text "> " style "_console_prompt"
else:
text "... " style "_console_prompt"
+17
View File
@@ -280,6 +280,10 @@ init python in director:
return show_director
def after_load():
state.mode = "lines"
config.after_load_callbacks.append(after_load)
def interact():
"""
@@ -1766,6 +1770,7 @@ screen director_transform(state):
null height 14
text _("Click to set transform, right click to add to transform list.")
text _("Customize director.transforms to add more transforms.")
use director_footer(state)
@@ -1786,6 +1791,8 @@ screen director_behind(state):
style "director_button"
ypadding 0
null height 14
text _("Click to set, right click to add to behind list.")
use director_footer(state)
@@ -1806,6 +1813,11 @@ screen director_with(state):
style "director_button"
ypadding 0
null height 14
text _("Click to set.")
text _("Customize director.transitions to add more transitions.")
use director_footer(state)
@@ -1824,6 +1836,11 @@ screen director_channel(state):
style "director_button"
ypadding 0
null height 14
text _("Click to set.")
text _("Customize director.audio_channels to add more channels.")
use director_footer(state)
+29
View File
@@ -111,6 +111,13 @@ init -1500 python in iap:
return
def request_review(self):
"""
Called to request that the user review the application.
"""
return False
class AndroidBackend(object):
"""
The IAP backend that is used when IAP is supported.
@@ -181,6 +188,9 @@ init -1500 python in iap:
identifier = self.identifier(p)
return self.store.getPrice(identifier)
def request_review(self):
return self.store.requestReview()
def init(self):
restore(False)
@@ -293,6 +303,10 @@ init -1500 python in iap:
return rv
def request_review(self):
self.helper.requestReview()
return True
def init(self):
self.helper.validateProductIdentifiersInBackground()
@@ -541,6 +555,21 @@ init -1500 python in iap:
return backend.get_store_name()
def request_review():
"""
:doc: iap
When called, the app store is asked to request a review from the user.
This returns true if the request was successful, and false if the
request was not. Note that a successful request does not mean that
the user will be asked to review the app, as app stores determine
if the user is actually asked.
This is supported on Google Play and the Apple App Store, only.
"""
return backend.request_review()
def missing_products():
"""
Determines if any products are missing from persistent._iap_purchases
+140 -87
View File
@@ -79,22 +79,27 @@ init -1510 python:
@renpy.pure
class InputValue(renpy.object.Object):
"""
Subclassable by creators, documented in Sphinx.
"""
default = True
editable = True
returnable = False
disable_on_enter = False
def get_text(self):
raise Exception("Not implemented.")
raise NotImplementedError
def set_text(self, s):
raise Exception("Not implemented.")
raise NotImplementedError
def enter(self):
if self.returnable:
return self.get_text()
else:
return None
elif self.disable_on_enter:
renpy.run(self.Disable())
raise renpy.IgnoreEvent
def Enable(self):
if self.editable:
@@ -114,70 +119,41 @@ init -1510 python:
else:
return None
@renpy.pure
class VariableInputValue(InputValue, FieldEquality):
class __GenericInputValue(InputValue, FieldEquality):
"""
Not subclassable by creators, not documented, meant to factorize
common features of the documented input value classes.
"""
equality_fields = ("default", "returnable", "disable_on_enter")
def __init__(self, default=True, returnable=False, disable_on_enter=False):
self.default = default
self.returnable = returnable
self.disable_on_enter = disable_on_enter
class ScreenVariableInputValue(__GenericInputValue):
"""
:doc: input_value
:args: {args}
An input value that updates `variable`.
An input value that updates a variable in a screen.
In a ``use``\ d screen, this targets a variable in the context of the
screen containing the ``use``\ d one(s). To target variables within a
``use``\ d screen, and only in that case, use
:func:`LocalVariableInputValue` instead.
`variable`
A string giving the name of the variable to update.
The `variable` parameter must be a string, and can be a simple name like "strength", or
one with dots separating the variable from fields, like "hero.strength"
or "persistent.show_cutscenes".
`default`
If true, this input can be editable by default.
`returnable`
If true, the value of this input will be returned when the
user presses enter.
"""
identity_fields = [ ]
equality_fields = [ "variable", "returnable" ]
identity_fields = ("screen",)
equality_fields = __GenericInputValue.equality_fields+("variable",)
def __init__(self, variable, default=True, returnable=False):
def __init__(self, variable, *args, **kwargs):
super(ScreenVariableInputValue, self).__init__(*args, **kwargs)
self.variable = variable
self.default = default
self.returnable = returnable
def get_text(self):
return _get_field(store, self.variable, "variable")
def set_text(self, s):
_set_field(store, self.variable, s, "variable")
renpy.restart_interaction()
class ScreenVariableInputValue(InputValue, FieldEquality):
"""
:doc: input_value
An input value that updates variable.
`variable`
A string giving the name of the variable to update.
`default`
If true, this input can be editable by default.
`returnable`
If true, the value of this input will be returned when the
user presses enter.
"""
identity_fields = [ 'screen' ]
equality_fields = [ "variable", "returnable" ]
def __init__(self, variable, default=True, returnable=False):
self.variable = variable
self.default = default
self.returnable = returnable
self.screen = renpy.current_screen()
def get_text(self):
@@ -190,68 +166,145 @@ init -1510 python:
renpy.restart_interaction()
@renpy.pure
class FieldInputValue(InputValue, FieldEquality):
class FieldInputValue(__GenericInputValue):
"""
:doc: input_value
:args: {args}
An input value that updates `field` on `object`.
`field`
A string giving the name of the field.
`default`
If true, this input can be editable by default.
`returnable`
If true, the value of this input will be returned when the
user presses enter.
"""
identity_fields = [ "object"]
equality_fields = [ "field", "returnable" ]
identity_fields = ("object",)
equality_fields = __GenericInputValue.equality_fields+("field",)
def __init__(self, object, field, default=True, returnable=False):
kind = "field"
def __init__(self, object, field, *args, **kwargs):
super(FieldInputValue, self).__init__(*args, **kwargs)
self.object = object
self.field = field
self.default = default
self.returnable = returnable
def get_text(self):
return _get_field(self.object, self.field, "field")
return _get_field(self.object, self.field, self.kind)
def set_text(self, s):
_set_field(self.object, self.field, s, "field")
_set_field(self.object, self.field, s, self.kind)
renpy.restart_interaction()
@renpy.pure
class DictInputValue(InputValue, FieldEquality):
class VariableInputValue(FieldInputValue):
"""
:doc: input_value
:args: {args}
An input value that updates `key` in `dict`.
An input value that updates `variable`.
`default`
If true, this input can be editable by default.
`variable`
A string giving the name of the variable to update.
`returnable`
If true, the value of this input will be returned when the
user presses enter.
The `variable` parameter must be a string, and can be a simple name like "strength", or
one with dots separating the variable from fields, like "hero.strength"
or "persistent.show_cutscenes".
"""
identity_fields = [ "dict", "key" ]
equality_fields = [ "returnable" ]
__version__ = 1
def __init__(self, dict, key, default=True, returnable=False):
kind = "variable"
def after_upgrade(self, version):
if version < 1:
self.field = self.variable
self.object = store
del self.variable
def __init__(self, variable, *args, **kwargs):
super(VariableInputValue, self).__init__(store, variable, *args, **kwargs)
@renpy.pure
class DictInputValue(__GenericInputValue):
"""
:doc: input_value
:args: {args}
An input value that updates ``dict[key]``.
`dict` may be a dict object or a list.
"""
identity_fields = ("dict",)
equality_fields = __GenericInputValue.equality_fields+("key",)
def __init__(self, dict, key, *args, **kwargs):
super(DictInputValue, self).__init__(*args, **kwargs)
self.dict = dict
self.key = key
self.default = default
self.returnable = returnable
def get_text(self):
return self.dict[self.key]
def set_text(self, s):
self.dict[self.key] = s
renpy.restart_interaction()
# not pure
class LocalVariableInputValue(DictInputValue):
"""
:doc: input_value
:args: {args}
An input value that updates a local variable in a ``use``\ d screen.
To target a variable in a top-level screen, prefer using
:func:`ScreenVariableInputValue`.
For more information, see :ref:`sl-use`.
This must be created in the context that the variable is set in - it
can't be passed in from somewhere else.
`variable`
A string giving the name of the variable to update.
"""
def __init__(self, variable, *args, **kwargs):
super(LocalVariableInputValue, self).__init__(sys._getframe(1).f_locals, variable, *args, **kwargs)
def get_text(self):
try:
return super(LocalVariableInputValue, self).get_text()
except LookupError:
raise Exception("The {!r} local variable does not exist.".format(self.key)) # from e # PY3 only
init -1510 python hide:
if config.generating_documentation:
import inspect
import itertools
generic_params = tuple(inspect.signature(__GenericInputValue.__init__).parameters.values())[1:]
suffix = inspect.cleandoc("""
`default`
If true, this input can be editable by default.
`returnable`
If true, the value of this input will be returned when the
user presses enter.
`disable_on_enter`
If true, and if `returnable` is not true, pressing enter
will disable this input.
""")
for ivalue in (ScreenVariableInputValue, FieldInputValue, VariableInputValue, DictInputValue, LocalVariableInputValue):
docstr = inspect.cleandoc(ivalue.__doc__)
params = []
for param in itertools.islice(inspect.signature(ivalue.__init__).parameters.values(), 1, None):
if param.kind not in (param.VAR_POSITIONAL, param.VAR_KEYWORD):
params.append(param)
params.extend(generic_params)
ivalue.__doc__ = (docstr+"\n"+suffix).format(
args=inspect.Signature(parameters=params),
)
+31 -6
View File
@@ -15,7 +15,7 @@ python early in layeredimage:
FIXED_PROPERTIES = renpy.sl2.slproperties.position_property_names + renpy.sl2.slproperties.box_property_names
# This is the default value for predict_all given to conditions.
predict_all = False
predict_all = None
def format_function(what, name, group, variant, attribute, image, image_format, **kwargs):
"""
@@ -423,7 +423,7 @@ python early in layeredimage:
args.append(None)
args.append(Null())
return ConditionSwitch(predict_all=predict_all, *args)
return ConditionSwitch(*args, predict_all=predict_all)
class RawConditionGroup(object):
@@ -883,6 +883,11 @@ python early in layeredimage:
ll = l.subblock_lexer()
while ll.advance():
if ll.keyword("pass"):
ll.expect_eol()
ll.expect_noblock("pass")
continue
line(ll)
ll.expect_eol()
ll.expect_noblock('attribute')
@@ -917,18 +922,23 @@ python early in layeredimage:
if not l.match(':'):
l.expect_eol()
l.expect_noblock('attribute')
l.expect_noblock('always')
return
l.expect_block('attribute')
l.expect_block('always')
l.expect_eol()
ll = l.subblock_lexer()
while ll.advance():
if ll.keyword("pass"):
ll.expect_eol()
ll.expect_noblock("pass")
continue
line(ll)
ll.expect_eol()
ll.expect_noblock('attribute')
ll.expect_noblock('always')
if a.image is None:
l.error("The always statement must have a displayable.")
@@ -954,6 +964,11 @@ python early in layeredimage:
ll = l.subblock_lexer()
while ll.advance():
if ll.keyword("pass"):
ll.expect_eol()
ll.expect_noblock("pass")
continue
if ll.keyword("attribute"):
parse_attribute(ll, rv)
continue
@@ -987,7 +1002,12 @@ python early in layeredimage:
rv = RawCondition(condition)
while ll.advance():
# not necessary : the if/elif/else blocks require a displayable,
# so they can't be empty in the first place anyway
# if ll.keyword("pass"):
# ll.expect_eol()
# ll.expect_noblock("pass")
# continue
while True:
@@ -1078,6 +1098,11 @@ python early in layeredimage:
parse_always(ll, rv)
ll.advance()
elif ll.keyword("pass"):
ll.expect_noblock("pass")
ll.expect_eol()
ll.advance()
else:
while parse_property(ll, rv, [ "image_format", "format_function", "attribute_function", "offer_screen", "at" ] +
+4 -15
View File
@@ -478,23 +478,12 @@ init -1400 python hide:
@layout
def yesno_screen(message, yes=None, no=None):
"""
:doc: other
:undocumented:
This causes the a yes/no prompt screen with the given message
to be displayed. The screen will be hidden when the user hits
yes or no.
`message`
The message that will be displayed.
`yes`
An action that is run when the user chooses yes.
`no`
An action that is run when the user chooses no.
See :func:`Confirm` for an equivalent Action.
renpy.confirm is a better alternative in a python context,
or the Confirm action in a screen context.
"""
if config.confirm_screen and renpy.has_screen('confirm'):
screen = "confirm"
elif renpy.has_screen("yesno_prompt"):
+1 -1
View File
@@ -409,7 +409,7 @@ init -1500 python:
if filename is None:
return
if self.single_track:
if self.single_track or filename not in self.filenames:
self.play(None, offset=0, queue=True)
else:
self.play(None, offset=1, queue=True)
+1 -1
View File
@@ -349,7 +349,7 @@ init -1500 python:
while config.nvl_list_length and (len(nvl_list) + 1 > config.nvl_list_length):
nvl_list.pop(0)
def do_display(self, who, what, multiple=None, **display_args):
def do_display(self, who, what, multiple=None, retain=None, **display_args):
renpy.translation.check_language()
+74 -12
View File
@@ -61,6 +61,10 @@ init -1150 python in bubble:
# The property group names, in order.
properties_order = [ ]
# If not None, a function that takes the character's image tag, and returns
# the list of property names that are allowed for that image tag.
properties_callback = None
# A map from property name to the (left, top, right, bottom) number of pixels
# areas with that property are expanded by. If a property is not in this
# map, None is tried.
@@ -75,6 +79,9 @@ init -1150 python in bubble:
frame = None
thoughtframe = None
# The layer that retained screens are placed on.
retain_layer = "screens"
class ToggleShown(Action):
def __call__(self):
if not active and not shown.value:
@@ -151,11 +158,19 @@ init -1150 python in bubble:
int(default_area[3] * ygrid)
]
return {
rv = {
"area" : default_area_rect,
"properties" : properties_order[0]
}
if properties_callback is not None:
rv["properties"] = properties_callback(image_tag)[0]
else:
rv["properties"] = properties_order[0]
return rv
def expand_area(self, area, properties_key):
"""
This is called to expand the area of a bubble. It is given the
@@ -178,8 +193,16 @@ init -1150 python in bubble:
return (x, y, w, h)
def do_add(self, who, what, multiple=False):
def do_show(self, who, what, multiple=None, extra_properties=None):
tlid = renpy.get_translation_identifier()
if tlid is not None:
if db[tlid].get("clear_retain", False):
renpy.clear_retain(layer=retain_layer)
def do_show(self, who, what, multiple=None, retain=None, extra_properties=None):
if extra_properties is None:
extra_properties = { }
@@ -188,7 +211,7 @@ init -1150 python in bubble:
image_tag = self.image_tag
if image_tag not in tag_properties:
if retain or (image_tag not in tag_properties):
tag_properties[image_tag] = self.bubble_default_properties(image_tag)
tlid = renpy.get_translation_identifier()
@@ -204,7 +227,7 @@ init -1150 python in bubble:
extra_properties.update(properties.get(properties_key, { }))
extra_properties[area_property] = self.expand_area(tag_properties[image_tag]["area"], properties_key)
return super(BubbleCharacter, self).do_show(who, what, multiple=multiple, extra_properties=extra_properties)
return super(BubbleCharacter, self).do_show(who, what, multiple=multiple, retain=retain, extra_properties=extra_properties)
class CycleBubbleProperty(Action):
"""
@@ -223,14 +246,19 @@ init -1150 python in bubble:
current = tag_properties[self.image_tag]["properties"]
if properties_callback is not None:
properties = properties_callback(self.image_tag)
else:
properties = properties_order
try:
idx = properties_order.index(current)
idx = properties.index(current)
except ValueError:
idx = 0
idx = (idx + 1) % len(properties_order)
idx = (idx + 1) % len(properties)
db[self.tlid]["properties"] = properties_order[idx]
db[self.tlid]["properties"] = properties[idx]
renpy.rollback(checkpoints=0, force=True, greedy=True)
def alternate(self):
@@ -238,6 +266,26 @@ init -1150 python in bubble:
del db[self.tlid]["properties"]
renpy.rollback(checkpoints=0, force=True, greedy=True)
class ToggleClearRetain(Action):
"""
This is an action that causes the clear_retain property to be toggled.
"""
def __init__(self, tlid):
self.tlid = tlid
def get_selected(self):
return db[self.tlid].get("clear_retain", False)
def __call__(self):
db[self.tlid]["clear_retain"] = not db[self.tlid].get("clear_retain", False)
renpy.rollback(checkpoints=0, force=True, greedy=True)
def alternate(self):
self()
class SetWindowArea(Action):
"""
An action that displays the area picker to select the window area.
@@ -315,14 +363,14 @@ init 1050 python hide:
raise Exception("bubble.properties[{!r}] contains a value that can't be serialized to JSON: {!r}".format(k, i))
if bubble.active:
config.overlay_screens.append("_bubble_editor")
config.always_shown_screens.append("_bubble_editor")
screen _bubble_editor():
zorder 1050
if bubble.shown.value:
if bubble.shown.value and not _menu:
drag:
draggable True
@@ -356,6 +404,18 @@ screen _bubble_editor():
null height gui._scale(5)
if bubble.current_dialogue and renpy.get_screen("_retain_0", layer=bubble.retain_layer):
textbutton _("(clear retained bubbles)"):
style "_default"
text_color "#ddd8"
text_selected_idle_color "#ddd"
text_hover_color "#fff"
text_size gui._scale(16)
action bubble.ToggleClearRetain(bubble.current_dialogue[0][1])
null height gui._scale(5)
for image_tag, properties in bubble.GetCurrentDialogue():
hbox:
@@ -364,7 +424,7 @@ screen _bubble_editor():
text "[image_tag!q]":
style "_default"
color "#fff"
size gui._scale(15)
size gui._scale(16)
for prop, action in properties:
textbutton "[prop!q]":
@@ -374,7 +434,9 @@ screen _bubble_editor():
text_color "#ddd8"
text_selected_idle_color "#ddd"
text_hover_color "#fff"
text_size gui._scale(15)
text_size gui._scale(16)
screen _bubble_window_area_editor(action):
+1 -7
View File
@@ -24,8 +24,6 @@ init -1500 python:
self.points = []
for p in points:
length = len(p)
if isinstance(p[-1], float):
length = len(p) - 1
point = [ p[-1] ]
@@ -62,11 +60,7 @@ init -1500 python:
self.initialized = None
def init_values(self, sizes):
def to_abs_(value, size):
if isinstance(value, float):
return value * size
else:
return value
to_abs_ = absolute.compute_raw
def coord_(c):
+19 -6
View File
@@ -560,7 +560,7 @@ init -1499 python in _renpysteam:
################################################################## Keyboard
# True to show the keyboard once, False otherwise.
keyboard_mode = "always"
keyboard_mode = "once"
# True if this is the start of a new interaction, and so the keyboard
# should be shown if a text box appears.
@@ -570,12 +570,16 @@ init -1499 python in _renpysteam:
keyboard_showing = None
# Should the layers be shifted so the baseline is in view?
keyboard_shift = True
keyboard_shift = False
# Where the basline is shifted to on the screen. This is a floating point number,
# Where the baseline is shifted to on the screen. This is a floating point number,
# with 0.0 being the top of the screen and 1.0 being the bottom.
keyboard_baseline = 0.5
# The textarea given to steam. This is scaled using the usual
# position rules.
keyboard_text_area = (0.0, 0.5, 1.0, 0.5)
def prime_keyboard():
global keyboard_primed
keyboard_primed = True
@@ -600,10 +604,19 @@ init -1499 python in _renpysteam:
_KeyboardShift.text_rect = keyboard_text_rect
if keyboard_primed and (keyboard_showing is None) and keyboard_text_rect:
x, y, w, h = (int(i) for i in keyboard_text_rect)
if keyboard_shift:
y = int(renpy.exports.get_physical_size()[1] * keyboard_baseline) - h
pw, ph = renpy.exports.get_physical_size()
def scale(n, available):
if type(n) == float:
n = n * available
return int(n)
x = scale(keyboard_text_area[0], pw)
y = scale(keyboard_text_area[1], ph)
w = scale(keyboard_text_area[2], pw)
h = scale(keyboard_text_area[3], ph)
steamapi.SteamUtils().ShowFloatingGamepadTextInput(
steamapi.k_EFloatingGamepadTextInputModeModeSingleLine,
+4 -1
View File
@@ -168,8 +168,11 @@ init -1800:
ruby_style style.ruby_text
altruby_style style.altruby_text
# hyperlink_functions is set in 00defaults.rpy
hinting "auto"
hinting True
adjust_spacing True
emoji_font "TwemojiCOLRv0.ttf"
prefer_emoji True
shaper ("harfbuzz" if (not PY2) else "freetype")
# Window properties
background None
+5 -1
View File
@@ -210,7 +210,7 @@ init -1100 python in _sync:
f.write(content)
fetch_id = emscripten.run_script_int(
"""fetchFile("PUT", "{url}", "/sync.data", null)""".format(url=url))
"""fetchFile("PUT", "{url}", "/sync.data", null, "application/octet-string")""".format(url=url))
status = "PENDING"
message = "Pending."
@@ -515,6 +515,7 @@ init -1100 python in _sync:
init -1100:
screen sync_confirm():
style_prefix "sync"
modal True
zorder 100
@@ -544,6 +545,7 @@ init -1100:
key "game_menu" action Return(False)
screen sync_prompt(prompt):
style_prefix "sync"
modal True
zorder 100
@@ -582,6 +584,7 @@ init -1100:
screen sync_success(sync_id):
style_prefix "sync"
modal True
zorder 100
@@ -617,6 +620,7 @@ init -1100:
key "game_menu" action Return(False)
screen sync_error(message):
style_prefix "sync"
modal True
zorder 100
+289 -33
View File
@@ -253,7 +253,7 @@ init -1500 python in updater:
# The update was cancelled.
CANCELLED = "CANCELLED"
def __init__(self, url, base=None, force=False, public_key=None, simulate=None, add=[], restart=True, check_only=False, confirm=True, patch=True):
def __init__(self, url, base=None, force=False, public_key=None, simulate=None, add=[], restart=True, check_only=False, confirm=True, patch=True, prefer_rpu=False, size_only=False, allow_empty=False):
"""
Takes the same arguments as update().
"""
@@ -312,6 +312,12 @@ init -1500 python in updater:
# Do we prompt for confirmation?
self.confirm = confirm
# Should rpu updates be preferred?
self.prefer_rpu = prefer_rpu
# Should the update be allowed even if current.json is empty?
self.allow_empty = allow_empty
# The base path of the game that we're updating, and the path to the update
# directory underneath it.
@@ -344,6 +350,9 @@ init -1500 python in updater:
# where each file is moved from <file>.new to <file>.
self.moves = [ ]
if self.allow_empty:
os.makedirs(self.updatedir, exist_ok=True)
if public_key is not None:
with renpy.open_file(public_key, False) as f:
self.public_key = rsa.PublicKey.load_pkcs1(f.read())
@@ -415,14 +424,11 @@ init -1500 python in updater:
Performs the update.
"""
if getattr(renpy, "mobile", False):
raise UpdateError(_("The Ren'Py Updater is not supported on mobile devices."))
self.load_state()
self.test_write()
self.check_updates()
pretty_version = self.check_versions()
self.pretty_version = self.check_versions()
if not self.modules:
self.can_cancel = False
@@ -432,12 +438,61 @@ init -1500 python in updater:
renpy.restart_interaction()
return
persistent._update_version[self.url] = pretty_version
persistent._update_version[self.url] = self.pretty_version
if self.check_only:
renpy.restart_interaction()
return
# Confirm goes here.
# Disable autoreload.
renpy.set_autoreload(False)
self.new_state = dict(self.current_state)
renpy.restart_interaction()
self.progress = 0.0
self.state = self.PREPARING
import os
has_rpu = False
has_zsync = False
prefer_rpu = self.prefer_rpu or "RPU_UPDATE" in os.environ
for i in self.modules:
for d in self.updates:
if "rpu_url" in self.updates[d]:
has_rpu = True
if "zsync_url" in self.updates[d]:
has_zsync = True
if has_rpu and has_zsync:
if prefer_rpu:
self.rpu_update()
else:
self.zsync_update()
elif has_rpu:
self.rpu_update()
elif has_zsync:
self.zsync_update()
else:
raise UpdateError(_("No update methods found."))
def prompt_confirm(self):
"""
Prompts the user to confirm the update. Returns if the update
should proceed, or raises UpdateCancelled if it should not.
"""
if self.confirm and (not self.add):
# Confirm with the user that the update is available.
@@ -445,7 +500,7 @@ init -1500 python in updater:
self.can_cancel = True
self.can_proceed = True
self.state = self.UPDATE_AVAILABLE
self.version = pretty_version
self.version = self.pretty_version
renpy.restart_interaction()
@@ -461,15 +516,131 @@ init -1500 python in updater:
self.can_cancel = True
self.can_proceed = False
# Disable autoreload.
renpy.set_autoreload(False)
def fetch_files_rpu(self, module):
"""
Fetches the rpu file list for the given module.
"""
import requests, zlib
url = urlparse.urljoin(self.url, self.updates[module]["rpu_url"])
try:
resp = requests.get(url)
resp.raise_for_status()
except Exception as e:
raise UpdateError(__("Could not download file list: ") + str(e))
if hashlib.sha256(resp.content).hexdigest() != self.updates[module]["rpu_digest"]:
raise UpdateError(__("File list digest does not match."))
data = zlib.decompress(resp.content)
from renpy.update.common import FileList
return FileList.from_json(json.loads(data))
def rpu_progress(self, state, progress):
"""
Called by the rpu code to update the progress.
"""
old_state = self.state
self.state = state
self.progress = progress
if state != old_state or progress == 1.0 or progress == 0.0:
renpy.restart_interaction()
def rpu_update(self):
"""
Perform an update using the .rpu files.
"""
from renpy.update.common import FileList
from renpy.update.update import Update
# 1. Load the current files.
target_file_lists = [ ]
for i in self.modules:
target_file_lists.append(FileList.from_current_json(self.current_state[i]))
# 2. Fetch the file lists.
source_file_lists = [ ]
module_lists = { }
for i in self.modules:
fl = self.fetch_files_rpu(i)
module_lists[i] = fl
source_file_lists.append(fl)
# 3. Compute the update, and confirm it.
u = Update(
urlparse.urljoin(self.url, "rpu"),
source_file_lists,
self.base,
target_file_lists,
progress_callback=self.rpu_progress,
logfile=self.log
)
self.prompt_confirm()
self.can_cancel = False
# 4. Remove the version.json file.
version_json = os.path.join(self.updatedir, "version.json")
if os.path.exists(version_json):
os.unlink(version_json)
# 5. Apply the update.
u.update()
# 6. Update the new state.
for i in self.modules:
d = module_lists[i].to_current_json()
d["version"] = self.updates[i]["version"]
d["renpy_version"] = self.updates[i]["renpy_version"]
d["pretty_version"] = self.updates[i]["pretty_version"]
self.new_state[i] = d
# 7. Finish up.
self.message = None
self.progress = None
self.can_proceed = True
self.can_cancel = False
persistent._update_version[self.url] = None
# 8. Write the version.json file.
version_state = { }
for i in self.modules:
version_state[i] = {
"version" : self.updates[i]["version"],
"renpy_version" : self.updates[i]["renpy_version"],
"pretty_version" : self.updates[i]["pretty_version"]
}
with open(os.path.join(self.updatedir, "version.json"), "w") as f:
json.dump(version_state, f)
if self.restart:
self.state = self.DONE
else:
self.state = self.DONE_NO_RESTART
# Perform the update.
self.new_state = dict(self.current_state)
renpy.restart_interaction()
self.progress = 0.0
self.state = self.PREPARING
def zsync_update(self):
self.prompt_confirm()
if self.patch:
for i in self.modules:
@@ -735,6 +906,10 @@ init -1500 python in updater:
fn = os.path.join(self.updatedir, "current.json")
if not os.path.exists(fn):
if self.allow_empty:
self.current_state = { }
return
raise UpdateError(_("Either this project does not support updating, or the update status file was deleted."))
with open(fn, "r") as f:
@@ -763,36 +938,106 @@ init -1500 python in updater:
fn = os.path.join(self.updatedir, "updates.json")
urlretrieve(self.url, fn)
with open(fn, "r") as f:
self.updates = json.load(f)
with open(fn, "rb") as f:
updates_json = f.read()
if self.public_key is not None:
fn = os.path.join(self.updatedir, "updates.json.sig")
urlretrieve(self.url + ".sig", fn)
# Was updates.json verified?
verified = False
with open(fn, "rb") as f:
import codecs
signature = codecs.decode(f.read(), "base64")
# Does updates.json need to be verified?
require_verified = False
# New-style ECDSA signature.
key = os.path.join(config.basedir, "update", "key.pem")
if not os.path.exists(key):
key = os.path.join(self.updatedir, "key.pem")
if os.path.exists(key):
require_verified = True
self.log.write("Verifying with ECDSA.\n")
try:
rsa.verify(updates_json, signature, self.public_key)
except Exception:
raise UpdateError(_("Could not verify update signature."))
if "monkeypatch" in self.updates:
future.utils.exec_(self.updates["monkeypatch"], globals(), globals())
import ecdsa
verifying_key = ecdsa.VerifyingKey.from_pem(open(key, "rb").read())
url = urlparse.urljoin(self.url, "updates.ecdsa")
f = urlopen(url)
while True:
signature = f.read(64)
if not signature:
break
if verifying_key.verify(signature, updates_json):
verified = True
self.log.write("Verified with ECDSA.\n")
except Exception:
if self.log:
import traceback
traceback.print_exc(None, self.log)
# Old-style RSA signature.
if self.public_key is not None:
require_verified = True
self.log.write("Verifying with RSA.\n")
try:
fn = os.path.join(self.updatedir, "updates.json.sig")
urlretrieve(self.url + ".sig", fn)
with open(fn, "rb") as f:
import codecs
signature = codecs.decode(f.read(), "base64")
rsa.verify(updates_json, signature, self.public_key)
verified = True
self.log.write("Verified with RSA.\n")
except Exception:
if self.log:
import traceback
traceback.print_exc(None, self.log)
if require_verified and not verified:
raise UpdateError(_("Could not verify update signature."))
self.updates = json.loads(updates_json)
if verified and "monkeypatch" in self.updates:
future.utils.exec_(self.updates["monkeypatch"], globals(), globals())
def add_dlc_state(self, name):
url = urlparse.urljoin(self.url, self.updates[name]["json_url"])
f = urlopen(url)
d = json.load(f)
has_rpu = "rpu_url" in self.updates[name]
has_zsync = "zsync_url" in self.updates[name]
prefer_rpu = self.prefer_rpu or "RPU_UPDATE" in os.environ
if has_rpu and has_zsync:
if prefer_rpu:
has_zsync = False
else:
has_rpu = False
if has_rpu:
fl = self.fetch_files_rpu(name)
d = { name : fl.to_current_json() }
else:
url = urlparse.urljoin(self.url, self.updates[name]["json_url"])
f = urlopen(url)
d = json.load(f)
d[name]["version"] = 0
self.current_state.update(d)
def check_versions(self):
"""
Decides what modules need to be updated, if any.
@@ -1448,7 +1693,7 @@ init -1500 python in updater:
return not not get_installed_packages(base)
def update(url, base=None, force=False, public_key=None, simulate=None, add=[], restart=True, confirm=True, patch=True):
def update(url, base=None, force=False, public_key=None, simulate=None, add=[], restart=True, confirm=True, patch=True, prefer_rpu=False, allow_empty=False):
"""
:doc: updater
@@ -1494,12 +1739,23 @@ init -1500 python in updater:
changed data. If false, Ren'Py will download a complete copy of
the game, and update from that. This is set to false automatically
when the url does not begin with "http:".
This is ignored if the RPU update format is being used.
`prefer_rpu`
If True, Ren'Py will prefer the RPU format for updates, if both
zsync and RPU are available.
`allow_empty`
If True, Ren'Py will allow the update to proceed even if the
base directory does not contain update information. (`add` must
be provided in this case.)
"""
global installed_packages_cache
installed_packages_cache = None
u = Updater(url=url, base=base, force=force, public_key=public_key, simulate=simulate, add=add, restart=restart, confirm=confirm, patch=patch)
u = Updater(url=url, base=base, force=force, public_key=public_key, simulate=simulate, add=add, restart=restart, confirm=confirm, patch=patch, prefer_rpu=prefer_rpu, allow_empty=allow_empty)
ui.timer(.1, repeat=True, action=renpy.restart_interaction)
renpy.call_screen("updater", u=u)
Binary file not shown.
+11
View File
@@ -0,0 +1,11 @@
Twiemoji
--------
https://github.com/twitter/twemoji
https://github.com/Emoji-COLRv0/Emoji-COLRv0
Copyright 2019 Twitter, Inc and other contributors
Code licensed under the MIT License: http://opensource.org/licenses/MIT
Graphics licensed under CC-BY 4.0: https://creativecommons.org/licenses/by/4.0/
+110
View File
@@ -65,11 +65,25 @@ screen _developer:
textbutton _("Image Attributes"):
action ui.callsinnewcontext("_image_attributes")
if not renpy.get_screen("_translation_identifier"):
textbutton _("Show Translation Identifiers"):
action Show("_translation_identifier")
else:
textbutton _("Hide Translation Identifiers"):
action Hide("_translation_identifier")
if bubble.active:
textbutton _("Speech Bubble Editor (Shift+B)"):
action [ SetField(bubble.shown, "value", True), Hide("_developer") ]
if not renpy.get_screen("_filename_and_line"):
textbutton _("Show Filename and Line"):
action Show("_filename_and_line")
else:
textbutton _("Hide Filename and Line"):
action Hide("_filename_and_line")
hbox:
spacing gui._scale(25)
@@ -532,6 +546,102 @@ screen _image_load_log():
timer 10.0 action SetScreenVariable("show_help", False)
screen _translation_identifier():
zorder 1500
default show_copy = False
default show_help = True
style_prefix ""
python:
identifier = renpy.get_translation_identifier()
copy = None
if identifier:
copy = (SetScreenVariable("show_copy", True),
Function(pygame_sdl2.scrap.put,
pygame_sdl2.scrap.SCRAP_TEXT,
identifier.encode("utf8")))
drag:
draggable True
focus_mask None
xpos 0
ypos 0
frame:
style "empty"
background "#0004"
xpadding 5
ypadding 5
has vbox
textbutton "[identifier]":
action copy
padding (0, 0)
text_color "#fff"
text_hover_color "#bdf"
text_size 14
if show_copy:
text _("\n{color=#fff}Copied to clipboard.{/color}"):
size 14
timer 2.0 action SetScreenVariable("show_copy", False)
elif show_help:
text _("\n{color=#fff}Click to copy.\nDrag to move.{/color}"):
size 14
timer 10.0 action SetScreenVariable("show_help", False)
screen _filename_and_line():
zorder 1500
style_prefix ""
# Should help be shown?
default help = True
# The filename and line to show.
$ filename, line = renpy.get_filename_line()
timer 3 action SetScreenVariable("help", False)
drag:
draggable True
focus_mask None
xpos 0
ypos 0
frame:
style "empty"
background "#0004"
xpadding 5
ypadding 5
xminimum 200
has vbox
textbutton "[filename]:[line]":
padding (0, 0)
text_color "#fff"
text_hover_color "#bdf"
text_size 14
action EditFile(filename, line)
if help:
null height 10
text _("Click to open in editor."):
size 14
color "#fff"
init 1000 python hide:
if config.transparent_tile:
tile = im.Tile("_transparent_tile.png", (config.screen_width, config.screen_height))
+4 -1
View File
@@ -85,8 +85,11 @@ init label _errorhandling:
slow_cps None
slow_cps_multiplier 1.0
slow_abortable False
hinting "auto"
hinting True
adjust_spacing True
emoji_font "TwemojiCOLRv0.ttf"
prefer_emoji True
shaper ("harfbuzz" if (not PY2) else "freetype")
# Window properties
background None
+4 -5
View File
@@ -142,11 +142,11 @@ from future.builtins import chr
def add_attribute(obj, name, value):
pass
try:
if PY2:
if PY2:
try:
from renpy.compat.dictviews import add_attribute
except ImportError:
print("Could not import renpy.compat.dictviews.", file=sys.stderr)
except ImportError:
print("Could not import renpy.compat.dictviews.", file=sys.stderr)
################################################################################
@@ -205,6 +205,5 @@ if PY2:
# Generated by scripts/relative_imports.py, do not edit below this line.
if 1 == 0:
from . import dictviews
from . import fixes
from . import pickle
+10
View File
@@ -1395,6 +1395,16 @@ linear_fades = False
# Classes that used to participate in rollback, but no longer do.
ex_rollback_classes = [ ]
# Should we revert to the old behavior of box_reverse?
simple_box_reverse = False
# A map from font name to the hinting for the font.
font_hinting = { None : "auto" }
# Should we execute costly tasks which are
# avoidable when not generating the documentation ?
generating_documentation = False
del os
del collections
+2 -1
View File
@@ -391,6 +391,7 @@ adv = ADVCharacter(None,
callback=None,
type='say',
advance=True,
retain=False,
who_style='say_label',
what_style='say_dialogue',
@@ -415,7 +416,7 @@ def predict_say(who, what):
def say(who, what, interact=True, *args, **kwargs):
who = Character(who, kind=adv)
who(what, interact=interact, *args, **kwargs)
who(what, *args, interact=interact, **kwargs)
# Used by renpy.reshow_say and extend.
_last_say_who = None
+2
View File
@@ -72,6 +72,7 @@ if 1 == 0:
from . import behavior
from . import controller
from . import core
from . import displayable
from . import dragdrop
from . import emulator
from . import error
@@ -93,6 +94,7 @@ if 1 == 0:
from . import pgrender
from . import predict
from . import presplash
from . import quaternion
from . import render
from . import scale
from . import screen
+12 -22
View File
@@ -94,7 +94,7 @@ def get_poi(state):
height = renpy.config.screen_height
placement = (d.xpos, d.ypos, d.xanchor, d.yanchor, d.xoffset, d.yoffset, True)
xplacement, yplacement = renpy.display.core.place(width, height, width, height, placement)
xplacement, yplacement = renpy.display.displayable.place(width, height, width, height, placement)
return (xplacement + width / 2, yplacement + height / 2, d.zpos + z11)
@@ -392,13 +392,13 @@ cdef class RenderTransform:
ysize = state.ysize
if xsize is not None:
if (type(xsize) is float) and renpy.config.relative_transform_size:
xsize *= self.widtho
if renpy.config.relative_transform_size:
xsize = absolute.compute_raw(xsize, self.widtho)
self.widtho = xsize
if ysize is not None:
if (type(ysize) is float) and renpy.config.relative_transform_size:
ysize *= self.heighto
if renpy.config.relative_transform_size:
ysize = absolute.compute_raw(ysize, self.heighto)
self.heighto = ysize
self.cr = render(child, self.widtho, self.heighto, st - self.transform.child_st_base, at)
@@ -644,7 +644,7 @@ cdef class RenderTransform:
zrotate = state.zrotate or 0
placement = (state.xpos, state.ypos, state.xanchor, state.yanchor, state.xoffset, state.yoffset, True)
xplacement, yplacement = renpy.display.core.place(width, height, width, height, placement)
xplacement, yplacement = renpy.display.displayable.place(width, height, width, height, placement)
if poi:
start_pos = (xplacement + width / 2, yplacement + height / 2, state.zpos + z11)
@@ -690,6 +690,9 @@ cdef class RenderTransform:
ypoi = math.degrees(ypoi)
zpoi = math.degrees(zpoi)
if xplacement or yplacement or state.zpos:
self.reverse = Matrix.offset(-xplacement, -yplacement, -state.zpos) * self.reverse
if poi or orientation or xyz_rotate:
m = Matrix.offset(-width / 2, -height / 2, -z11)
@@ -707,9 +710,6 @@ cdef class RenderTransform:
self.reverse = m * self.reverse
if xplacement or yplacement or state.zpos:
self.reverse = Matrix.offset(-xplacement, -yplacement, -state.zpos) * self.reverse
if state.rotate is not None:
m = Matrix.offset(-width / 2, -height / 2, 0.0)
m = Matrix.rotate(0, 0, -state.rotate) * m
@@ -753,18 +753,13 @@ cdef class RenderTransform:
manchory = height / 2.0
else:
manchorx, manchory = state.matrixanchor
if type(manchorx) is float:
manchorx *= width
if type(manchory) is float:
manchory *= height
manchorx, manchory = map(absolute.compute_raw, state.matrixanchor, (width, height))
m = Matrix.offset(-manchorx, -manchory, 0.0)
if poi:
placement = self.transform.get_placement()
xplacement, yplacement = renpy.display.core.place(self.widtho, self.heighto, width, height, placement)
xplacement, yplacement = renpy.display.displayable.place(self.widtho, self.heighto, width, height, placement)
start_pos = (xplacement + manchorx, yplacement + manchory, state.zpos)
a, b, c = ( float(e - s) for s, e in zip(start_pos, poi) )
@@ -833,12 +828,7 @@ cdef class RenderTransform:
manchory = self.height / 2.0
else:
manchorx, manchory = state.matrixanchor
if type(manchorx) is float:
manchorx *= self.width
if type(manchory) is float:
manchory *= self.height
manchorx, manchory = map(absolute.compute_raw, state.matrixanchor, (self.width, self.height))
m = Matrix.offset(-manchorx, -manchory, 0.0)
m = mt * m
+5 -5
View File
@@ -55,7 +55,7 @@ class State(object):
Position object, that modifies the position of the image.
"""
if image and not isinstance(image, renpy.display.core.Displayable):
if image and not isinstance(image, renpy.display.displayable.Displayable):
image = renpy.easy.displayable(image)
self.name = name
@@ -125,7 +125,7 @@ class Edge(object):
sma.edges.setdefault(self.old, []).append(self)
class SMAnimation(renpy.display.core.Displayable):
class SMAnimation(renpy.display.displayable.Displayable):
"""
This creates a state-machine animation. Such an animation is
created by randomly traversing the edges between states in a
@@ -308,7 +308,7 @@ class SMAnimation(renpy.display.core.Displayable):
for edges in self.edges.values():
args.extend(edges)
return SMAnimation(self.initial, delay=self.delay, *args, **self.properties)
return SMAnimation(self.initial, *args, delay=self.delay, **self.properties)
def Animation(*args, **kwargs):
@@ -322,7 +322,7 @@ def Animation(*args, **kwargs):
return TransitionAnimation(*newargs, **kwargs)
class TransitionAnimation(renpy.display.core.Displayable):
class TransitionAnimation(renpy.display.displayable.Displayable):
"""
A displayable that draws an animation with each frame separated
by a transition.
@@ -409,7 +409,7 @@ class TransitionAnimation(renpy.display.core.Displayable):
return self.images
class Blink(renpy.display.core.Displayable):
class Blink(renpy.display.displayable.Displayable):
def __init__(self, image, on=0.5, off=0.5, rise=0.5, set=0.5, # @ReservedAssignment
high=1.0, low=0.0, offset=0.0, anim_timebase=False, **properties):
+9 -7
View File
@@ -799,7 +799,7 @@ class SayBehavior(renpy.display.layout.Null):
return None
class DismissBehavior(renpy.display.core.Displayable):
class DismissBehavior(renpy.display.displayable.Displayable):
"""
This is used to implement the dismiss screen language statement.
"""
@@ -1313,7 +1313,7 @@ def input_post_per_interact():
i.caret_pos = len(content)
class CaretBlink(renpy.display.core.Displayable):
class CaretBlink(renpy.display.displayable.Displayable):
"""
A displayable that renders the caret.
"""
@@ -2075,7 +2075,7 @@ class Adjustment(renpy.object.Object):
return 0
class Bar(renpy.display.core.Displayable):
class Bar(renpy.display.displayable.Displayable):
"""
Implements a bar that can display an integer value, and respond
to clicks on that value.
@@ -2127,7 +2127,9 @@ class Bar(renpy.display.core.Displayable):
self.value = value
adjustment = value.get_adjustment()
renpy.game.interface.timeout(0)
if renpy.game.interface is not None:
renpy.game.interface.timeout(0)
tooltip = value.get_tooltip()
if tooltip is not None:
@@ -2595,7 +2597,7 @@ class Timer(renpy.display.layout.Null):
return run(self.function, *self.args, **self.kwargs)
class MouseArea(renpy.display.core.Displayable):
class MouseArea(renpy.display.displayable.Displayable):
# The offset between st and at.
at_st_offset = 0
@@ -2657,7 +2659,7 @@ class MouseArea(renpy.display.core.Displayable):
run(self.unhovered)
class OnEvent(renpy.display.core.Displayable):
class OnEvent(renpy.display.displayable.Displayable):
"""
This is a displayable that runs an action in response to a transform
event. It's used to implement the screen language on statement.
@@ -2849,7 +2851,7 @@ class AreaPicker(renpy.display.layout.Container):
class WebInput(renpy.display.core.Displayable):
class WebInput(renpy.display.displayable.Displayable):
"""
A displayable meant to pull input from an input tag in the web browser.
"""
+24 -608
View File
@@ -19,9 +19,6 @@
# 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 code for initializing and managing the display
# window.
from __future__ import division, absolute_import, with_statement, print_function, unicode_literals
from renpy.compat import PY2, basestring, bchr, bord, chr, open, pystr, range, round, str, tobytes, unicode # *
@@ -39,6 +36,8 @@ import atexit
import pygame_sdl2 as pygame
import renpy
from renpy.display.displayable import Displayable, DisplayableArguments, place
import_time = time.time()
try:
@@ -222,6 +221,25 @@ class absolute(float):
def __rdivmod__(self, value):
return value//self, value%self
@staticmethod
def compute_raw(value, room):
"""
Converts a position from one of the many supported position types
into an absolute number of pixels, without regard for the return type.
"""
if isinstance(value, (absolute, int)):
return value
elif isinstance(value, float):
return value * room
raise TypeError("Value {} of type {} not recognized as a position.".format(value, type(value)))
@staticmethod
def compute(value, room):
"""
Does the same, but converts the result to the absolute type.
"""
return absolute(absolute.compute_raw(value, room))
for fn in (
'__coerce__', # PY2
'__div__', # PY2
@@ -276,603 +294,6 @@ for fn in (
del absolute_wrap, fn, f # type: ignore
def place(width, height, sw, sh, placement):
"""
Performs the Ren'Py placement algorithm.
`width`, `height`
The width and height of the area the image will be
placed in.
`size`
The size of the image to be placed.
`placement`
The tuple returned by Displayable.get_placement().
"""
xpos, ypos, xanchor, yanchor, xoffset, yoffset, _subpixel = placement
if xpos is None:
xpos = 0
if ypos is None:
ypos = 0
if xanchor is None:
xanchor = 0
if yanchor is None:
yanchor = 0
if xoffset is None:
xoffset = 0
if yoffset is None:
yoffset = 0
# We need to use type, since isinstance(absolute(0), float).
if xpos.__class__ is float:
xpos *= width
if xanchor.__class__ is float:
xanchor *= sw
x = xpos + xoffset - xanchor
if ypos.__class__ is float:
ypos *= height
if yanchor.__class__ is float:
yanchor *= sh
y = ypos + yoffset - yanchor
return x, y
class DisplayableArguments(renpy.object.Object):
"""
Represents a set of arguments that can be passed to a duplicated
displayable.
"""
# The name of the displayable without any arguments.
name = () # type: Tuple
# Arguments supplied.
args = () # type: Tuple
# The style prefix in play. This is used by DynamicImage to figure
# out the prefix list to apply.
prefix = None # Optional[str]
# True if lint is in use.
lint = False
def copy(self, **kwargs):
"""
Returns a copy of this object with the various fields set to the
values they were given in kwargs.
"""
rv = DisplayableArguments()
rv.__dict__.update(self.__dict__)
rv.__dict__.update(kwargs)
return rv
def extraneous(self):
if renpy.config.developer and renpy.config.report_extraneous_attributes:
raise Exception("Image '{}' does not accept attributes '{}'.".format(
" ".join(self.name),
" ".join(self.args),
))
default_style = renpy.style.Style("default")
class Displayable(renpy.object.Object):
"""
The base class for every object in Ren'Py that can be
displayed to the screen.
Drawables will be serialized to a savegame file. Therefore, they
shouldn't store non-serializable things (like pygame surfaces) in
their fields.
"""
# Some invariants about method call order:
#
# per_interact is called before render.
# render is called before event.
#
# get_placement can be called at any time, so can't
# assume anything.
# If True this displayable can accept focus.
# If False, it can't, but it keeps its place in the focus order.
# If None, it does not have a place in the focus order.
focusable = None
# This is the focus named assigned by the focus code.
full_focus_name = None
# A role ('selected_' or '' that prefixes the style).
role = ''
# The event we'll pass on to our parent transform.
transform_event = None
# Can we change our look in response to transform_events?
transform_event_responder = False
# The main displayable, if this displayable is the root of a composite
# displayable. (This is used by SL to figure out where to add children
# to.) If None, it is itself.
_main = None
# A list of the children that make up this composite displayable.
_composite_parts = [ ]
# The location the displayable was created at, if known.
_location = None
# Does this displayable use the scope?
_uses_scope = False
# Arguments supplied to this displayable.
_args = DisplayableArguments()
# Set to true of the displayable is duplicatable (has a non-trivial
# duplicate method), or one of its children is.
_duplicatable = False
# Does this displayable require clipping?
_clipping = False
# Does this displayable have a tooltip?
_tooltip = None
# Should hbox and vbox skip this displayable?
_box_skip = False
# If not None, this should be a (width, height) tuple that overrides the
# amount of space offered to the displayable.
_offer_size = None
# If true, this displayable will be treated as draggable in its own right.
# This is used by viewport to decide if a drag is meant for the viewport
# or for its child.
_draggable = False
# Used by a transition (or transition-like object) to determine how long to
# delay for.
delay = None # type: float|None
def __ne__(self, o):
return not (self == o)
def __init__(self, focus=None, default=False, style='default', _args=None, tooltip=None, default_focus=False, **properties):
global default_style
if (style == "default") and (not properties):
self.style = default_style
else:
self.style = renpy.style.Style(style, properties) # @UndefinedVariable
self.focus_name = focus
self.default = default or default_focus
self._tooltip = tooltip
if _args is not None:
self._args = _args
def _copy(self, args=None):
"""
Makes a shallow copy of the displayable. If `args` is provided,
replaces the arguments with the stored copy.
"""
rv = copy.copy(self)
if args is not None:
rv._args = args
return rv
def _duplicate(self, args):
"""
Makes a duplicate copy of the following kids of displayables:
* Displayables that can accept arguments.
* Displayables that maintain state that should be reset before being
shown to the user.
* Containers that contain (including transitively) one of the other
kinds of displayables.
Displayables that contain state that can be manipulated by the user
are never copied.
This should call _unique on children that have been copied before
setting its own _duplicatable flag.
"""
if args and args.args:
args.extraneous()
return self
def _get_tooltip(self):
"""
Returns the tooltip of this displayable.
"""
return self._tooltip
def _in_current_store(self):
"""
Returns a version of this displayable that will not change as it is
rendered.
"""
return self
def _unique(self):
"""
This is called when a displayable is "unique", meaning there will
only be one reference to it, ever, from the tree of displayables.
"""
self._duplicatable = False
return
def parameterize(self, name, parameters):
"""
Obsolete alias for _duplicate.
"""
a = self._args.copy(name=name, args=parameters)
return self._duplicate(a)
def _equals(self, o):
"""
This is a utility method that can be called by a Displayable's
__eq__ method, to compare displayables for type and displayable
component equality.
"""
if type(self) is not type(o):
return False
if self.focus_name != o.focus_name:
return False
if self.style != o.style:
return False
if self.default != o.default:
return False
return True
def _repr_info(self):
return None
def __repr__(self):
rep = object.__repr__(self)
reprinfo = self._repr_info()
if reprinfo is None:
return rep
if reprinfo and not ((reprinfo[0] == '(') and (reprinfo[-1] == ')')):
reprinfo = "".join(("(", reprinfo, ")"))
parto = rep.rpartition(" at ")
return " ".join((parto[0],
reprinfo,
"at",
parto[2]))
def find_focusable(self, callback, focus_name):
focus_name = self.focus_name or focus_name
if self.focusable:
callback(self, focus_name)
elif self.focusable is not None:
callback(None, focus_name)
for i in self.visit():
if i is None:
continue
i.find_focusable(callback, focus_name)
def focus(self, default=False):
"""
Called to indicate that this widget has the focus.
"""
self.set_style_prefix(self.role + "hover_", True)
if not default:
renpy.exports.play(self.style.hover_sound)
def unfocus(self, default=False):
"""
Called to indicate that this widget has become unfocused.
"""
self.set_style_prefix(self.role + "idle_", True)
def is_focused(self):
if renpy.display.focus.grab and renpy.display.focus.grab is not self:
return
return renpy.game.context().scene_lists.focused is self
def set_style_prefix(self, prefix, root):
"""
Called to set the style prefix of this widget and its child
widgets, if any.
`root` - True if this is the root of a style tree, False if this
has been passed on to a child.
"""
if prefix == self.style.prefix:
return
self.style.set_prefix(prefix)
renpy.display.render.redraw(self, 0)
def render(self, width, height, st, at):
"""
Called to display this displayable. This is called with width
and height parameters, which give the largest width and height
that this drawable can be drawn to without overflowing some
bounding box. It's also given two times. It returns a Surface
that is the current image of this drawable.
@param st: The time since this widget was first shown, in seconds.
@param at: The time since a similarly named widget was first shown,
in seconds.
"""
raise Exception("Render not implemented.")
def event(self, ev, x, y, st):
"""
Called to report than an event has occured. Ev is the raw
pygame event object representing that event. If the event
involves the mouse, x and y are the translation of the event
into the coordinates of this displayable. st is the time this
widget has been shown for.
@returns A value that should be returned from Interact, or None if
no value is appropriate.
"""
return None
def get_placement(self):
"""
Returns a style object containing placement information for
this Displayable. Children are expected to overload this
to return something more sensible.
"""
return self.style.get_placement()
def visit_all(self, callback, seen=None):
"""
Calls the callback on this displayable, and then on all children
of this displayable.
"""
if seen is None:
seen = set()
for d in self.visit():
if d is None:
continue
id_d = id(d)
if id_d in seen:
continue
seen.add(id_d)
d.visit_all(callback, seen)
callback(self)
def visit(self):
"""
Called to ask the displayable to return a list of its children
(including children taken from styles). For convenience, this
list may also include None values.
"""
return [ ]
def per_interact(self):
"""
Called once per widget per interaction.
"""
return None
def predict_one(self):
"""
Called to ask this displayable to call the callback with all
the images it may want to load.
"""
return
def predict_one_action(self):
"""
Called to ask this displayable to cause image prediction
to occur for images that may be loaded by its actions.
"""
return
def place(self, dest, x, y, width, height, surf, main=True):
"""
This places a render (which must be of this displayable)
within a bounding area. Returns an (x, y) tuple giving the location
the displayable was placed at.
`dest`
If not None, the `surf` will be blitted to `dest` at the
computed coordinates.
`x`, `y`, `width`, `height`
The bounding area.
`surf`
The render to place.
`main`
This is passed to Render.blit().
"""
placement = self.get_placement()
subpixel = placement[6]
xpos, ypos = place(width, height, surf.width, surf.height, placement)
xpos += x
ypos += y
pos = (xpos, ypos)
if dest is not None:
if subpixel:
dest.subpixel_blit(surf, pos, main, main, None)
else:
dest.blit(surf, pos, main, main, None)
return pos
def set_transform_event(self, event):
"""
Sets the transform event of this displayable to event.
"""
if event == self.transform_event:
return
self.transform_event = event
if self.transform_event_responder:
renpy.display.render.redraw(self, 0)
def _handles_event(self, event):
"""
Returns True if the displayable handles event, False otherwise.
"""
return False
def _hide(self, st, at, kind):
"""
Returns None if this displayable is ready to be hidden, or
a replacement displayable if it doesn't want to be hidden
quite yet.
Kind may be "hide", "replace", or "cancel", with the latter
being called when the hide is being hidden itself because
another displayable is shown.
"""
return None
def _show(self):
"""
No longer used.
"""
def _target(self):
"""
If this displayable is part of a chain of one or more references,
returns the ultimate target of those references. Otherwise, returns
the displayable.
"""
return self
def _change_transform_child(self, child):
"""
If this is a transform, makes a copy of the transform and sets
the child of the innermost transform to this. Otherwise,
simply returns child.
"""
return child
def _clear(self):
"""
Clears out the children of this displayable, if any.
"""
return
def _tts_common(self, default_alt=None, reverse=False):
rv = [ ]
if reverse:
order = -1
else:
order = 1
speech = ""
for i in self.visit()[::order]:
if i is not None:
speech = i._tts()
if speech.strip():
if isinstance(speech, renpy.display.tts.TTSDone):
rv = [ speech ]
else:
rv.append(speech)
rv = ": ".join(rv)
rv = rv.replace("::", ":")
rv = rv.replace(": :", ":")
alt = self.style.alt
if alt is None:
alt = default_alt
if alt is not None:
rv = renpy.substitutions.substitute(alt, scope={ "text" : rv })[0]
rv = type(speech)(rv)
return rv
def _tts(self):
"""
Returns the self-voicing text of this displayable and all of its
children that cannot take focus. If the displayable can take focus,
returns the empty string.
"""
return self._tts_common()
def _tts_all(self):
"""
Returns the self-voicing text of this displayable and all of its
children that cannot take focus.
"""
return self._tts_common()
class SceneListEntry(renpy.object.Object):
"""
Represents a scene list entry. Since this was replacing a tuple,
@@ -2516,8 +1937,6 @@ class Interface(object):
pygame.event.set_blocked(i)
pygame.event.set_blocked(pygame.TEXTINPUT)
# Fix a problem with fullscreen and maximized.
if renpy.game.preferences.fullscreen:
renpy.game.preferences.maximized = False
@@ -2614,7 +2033,7 @@ class Interface(object):
if i in renderers:
gl2_renderers.append(i + "2")
if renpy.config.gl2:
if renpy.config.gl2 or renpy.macintosh:
renderers = gl2_renderers + renderers
# Prevent a performance warning if the renderer
@@ -2712,7 +2131,6 @@ class Interface(object):
# Stop the resizing.
pygame.key.stop_text_input() # @UndefinedVariable
pygame.key.set_text_input_rect(None) # @UndefinedVariable
pygame.event.set_blocked(pygame.TEXTINPUT)
self.text_rect = None
self.old_text_rect = None
self.display_reset = False
@@ -3498,7 +2916,6 @@ class Interface(object):
rect = (x0, y0, x1 - x0, y1 - y0)
pygame.key.set_text_input_rect(rect) # @UndefinedVariable
pygame.event.set_allowed(pygame.TEXTINPUT)
if not self.old_text_rect or not_shown:
pygame.key.start_text_input() # @UndefinedVariable
@@ -3515,7 +2932,6 @@ class Interface(object):
if self.old_text_rect:
pygame.key.stop_text_input() # @UndefinedVariable
pygame.key.set_text_input_rect(None) # @UndefinedVariable
pygame.event.set_blocked(pygame.TEXTINPUT)
if self.touch_keyboard:
renpy.exports.hide_screen('_touch_keyboard', layer='screens')
@@ -3929,7 +3345,7 @@ class Interface(object):
new_widget=new_d)
if not isinstance(trans, Displayable):
raise Exception("Expected transition to be a displayable, not a %r" % trans)
raise Exception("Expected transition to return a displayable, not a {!r}".format(trans))
if isinstance(trans, renpy.display.transform.Transform) and isinstance(old_trans, renpy.display.transform.Transform):
trans.take_state(old_trans)
@@ -3981,7 +3397,7 @@ class Interface(object):
trans = instantiate_transition(None, old_root, layers_root)
if not isinstance(trans, Displayable):
raise Exception("Expected transition to be a displayable, not a %r" % trans)
raise Exception("Expected transition to return a displayable, not a {!r}".format(trans))
transition_time = self.transition_time.get(None, None)
root_widget.add(trans, transition_time, transition_time)
+620
View File
@@ -0,0 +1,620 @@
# Copyright 2004-2023 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 division, absolute_import, with_statement, print_function, unicode_literals
from renpy.compat import PY2, basestring, bchr, bord, chr, open, pystr, range, round, str, tobytes, unicode # *
import copy
import renpy
def place(width, height, sw, sh, placement):
"""
Performs the Ren'Py placement algorithm.
`width`, `height`
The width and height of the area the image will be
placed in.
`sw`, `sh`
The size of the image to be placed.
`placement`
The tuple returned by Displayable.get_placement().
"""
xpos, ypos, xanchor, yanchor, xoffset, yoffset, _subpixel = placement
compute_raw = renpy.display.core.absolute.compute_raw
if xpos is None:
xpos = 0
if ypos is None:
ypos = 0
if xanchor is None:
xanchor = 0
if yanchor is None:
yanchor = 0
if xoffset is None:
xoffset = 0
if yoffset is None:
yoffset = 0
xpos = compute_raw(xpos, width)
xanchor = compute_raw(xanchor, sw)
x = xpos + xoffset - xanchor
ypos = compute_raw(ypos, height)
yanchor = compute_raw(yanchor, sh)
y = ypos + yoffset - yanchor
return x, y
class DisplayableArguments(renpy.object.Object):
"""
Represents a set of arguments that can be passed to a duplicated
displayable.
"""
# The name of the displayable without any arguments.
name = () # type: tuple
# Arguments supplied.
args = () # type: tuple
# The style prefix in play. This is used by DynamicImage to figure
# out the prefix list to apply.
prefix = None # Optional[str]
# True if lint is in use.
lint = False
def copy(self, **kwargs):
"""
Returns a copy of this object with the various fields set to the
values they were given in kwargs.
"""
rv = DisplayableArguments()
rv.__dict__.update(self.__dict__)
rv.__dict__.update(kwargs)
return rv
def extraneous(self):
if renpy.config.developer and renpy.config.report_extraneous_attributes:
raise Exception("Image '{}' does not accept attributes '{}'.".format(
" ".join(self.name),
" ".join(self.args),
))
default_style = renpy.style.Style("default")
class Displayable(renpy.object.Object):
"""
The base class for every object in Ren'Py that can be
displayed to the screen.
Drawables will be serialized to a savegame file. Therefore, they
shouldn't store non-serializable things (like pygame surfaces) in
their fields.
"""
# Some invariants about method call order:
#
# per_interact is called before render.
# render is called before event.
#
# get_placement can be called at any time, so can't
# assume anything.
# If True this displayable can accept focus.
# If False, it can't, but it keeps its place in the focus order.
# If None, it does not have a place in the focus order.
focusable = None
# This is the focus named assigned by the focus code.
full_focus_name = None
# A role ('selected_' or '' that prefixes the style).
role = ''
# The event we'll pass on to our parent transform.
transform_event = None
# Can we change our look in response to transform_events?
transform_event_responder = False
# The main displayable, if this displayable is the root of a composite
# displayable. (This is used by SL to figure out where to add children
# to.) If None, it is itself.
_main = None
# A list of the children that make up this composite displayable.
_composite_parts = [ ]
# The location the displayable was created at, if known.
_location = None
# Does this displayable use the scope?
_uses_scope = False
# Arguments supplied to this displayable.
_args = DisplayableArguments()
# Set to true of the displayable is duplicatable (has a non-trivial
# duplicate method), or one of its children is.
_duplicatable = False
# Does this displayable require clipping?
_clipping = False
# Does this displayable have a tooltip?
_tooltip = None
# Should hbox and vbox skip this displayable?
_box_skip = False
# If not None, this should be a (width, height) tuple that overrides the
# amount of space offered to the displayable.
_offer_size = None
# If true, this displayable will be treated as draggable in its own right.
# This is used by viewport to decide if a drag is meant for the viewport
# or for its child.
_draggable = False
# Used by a transition (or transition-like object) to determine how long to
# delay for.
delay = None # type: float|None
def __ne__(self, o):
return not (self == o)
def __init__(self, focus=None, default=False, style='default', _args=None, tooltip=None, default_focus=False, **properties):
global default_style
if (style == "default") and (not properties):
self.style = default_style
else:
self.style = renpy.style.Style(style, properties) # @UndefinedVariable
self.focus_name = focus
self.default = default or default_focus
self._tooltip = tooltip
if _args is not None:
self._args = _args
def _copy(self, args=None):
"""
Makes a shallow copy of the displayable. If `args` is provided,
replaces the arguments with the stored copy.
"""
rv = copy.copy(self)
if args is not None:
rv._args = args
return rv
def _duplicate(self, args):
"""
Makes a duplicate copy of the following kids of displayables:
* Displayables that can accept arguments.
* Displayables that maintain state that should be reset before being
shown to the user.
* Containers that contain (including transitively) one of the other
kinds of displayables.
Displayables that contain state that can be manipulated by the user
are never copied.
This should call _unique on children that have been copied before
setting its own _duplicatable flag.
"""
if args and args.args:
args.extraneous()
return self
def _get_tooltip(self):
"""
Returns the tooltip of this displayable.
"""
return self._tooltip
def _in_current_store(self):
"""
Returns a version of this displayable that will not change as it is
rendered.
"""
return self
def _unique(self):
"""
This is called when a displayable is "unique", meaning there will
only be one reference to it, ever, from the tree of displayables.
"""
self._duplicatable = False
return
def parameterize(self, name, parameters):
"""
Obsolete alias for _duplicate.
"""
a = self._args.copy(name=name, args=parameters)
return self._duplicate(a)
def _equals(self, o):
"""
This is a utility method that can be called by a Displayable's
__eq__ method, to compare displayables for type and displayable
component equality.
"""
if type(self) is not type(o):
return False
if self.focus_name != o.focus_name:
return False
if self.style != o.style:
return False
if self.default != o.default:
return False
return True
def _repr_info(self):
return None
def __repr__(self):
rep = object.__repr__(self)
reprinfo = self._repr_info()
if reprinfo is None:
return rep
if reprinfo and not ((reprinfo[0] == '(') and (reprinfo[-1] == ')')):
reprinfo = "".join(("(", reprinfo, ")"))
parto = rep.rpartition(" at ")
return " ".join((parto[0],
reprinfo,
"at",
parto[2]))
def find_focusable(self, callback, focus_name):
focus_name = self.focus_name or focus_name
if self.focusable:
callback(self, focus_name)
elif self.focusable is not None:
callback(None, focus_name)
for i in self.visit():
if i is None:
continue
i.find_focusable(callback, focus_name)
def focus(self, default=False):
"""
Called to indicate that this widget has the focus.
"""
self.set_style_prefix(self.role + "hover_", True)
if not default:
renpy.exports.play(self.style.hover_sound)
def unfocus(self, default=False):
"""
Called to indicate that this widget has become unfocused.
"""
self.set_style_prefix(self.role + "idle_", True)
def is_focused(self):
if renpy.display.focus.grab and renpy.display.focus.grab is not self:
return
return renpy.game.context().scene_lists.focused is self
def set_style_prefix(self, prefix, root):
"""
Called to set the style prefix of this widget and its child
widgets, if any.
`root` - True if this is the root of a style tree, False if this
has been passed on to a child.
"""
if prefix == self.style.prefix:
return
self.style.set_prefix(prefix)
renpy.display.render.redraw(self, 0)
def render(self, width, height, st, at):
"""
Called to display this displayable. This is called with width
and height parameters, which give the largest width and height
that this drawable can be drawn to without overflowing some
bounding box. It's also given two times. It returns a Surface
that is the current image of this drawable.
@param st: The time since this widget was first shown, in seconds.
@param at: The time since a similarly named widget was first shown,
in seconds.
"""
raise Exception("Render not implemented.")
def event(self, ev, x, y, st):
"""
Called to report than an event has occured. Ev is the raw
pygame event object representing that event. If the event
involves the mouse, x and y are the translation of the event
into the coordinates of this displayable. st is the time this
widget has been shown for.
@returns A value that should be returned from Interact, or None if
no value is appropriate.
"""
return None
def get_placement(self):
"""
Returns a style object containing placement information for
this Displayable. Children are expected to overload this
to return something more sensible.
"""
return self.style.get_placement()
def visit_all(self, callback, seen=None):
"""
Calls the callback on this displayable, and then on all children
of this displayable.
"""
if seen is None:
seen = set()
for d in self.visit():
if d is None:
continue
id_d = id(d)
if id_d in seen:
continue
seen.add(id_d)
d.visit_all(callback, seen)
callback(self)
def visit(self):
"""
Called to ask the displayable to return a list of its children
(including children taken from styles). For convenience, this
list may also include None values.
"""
return [ ]
def per_interact(self):
"""
Called once per widget per interaction.
"""
return None
def predict_one(self):
"""
Called to ask this displayable to call the callback with all
the images it may want to load.
"""
return
def predict_one_action(self):
"""
Called to ask this displayable to cause image prediction
to occur for images that may be loaded by its actions.
"""
return
def place(self, dest, x, y, width, height, surf, main=True):
"""
This places a render (which must be of this displayable)
within a bounding area. Returns an (x, y) tuple giving the location
the displayable was placed at.
`dest`
If not None, the `surf` will be blitted to `dest` at the
computed coordinates.
`x`, `y`, `width`, `height`
The bounding area.
`surf`
The render to place.
`main`
This is passed to Render.blit().
"""
placement = self.get_placement()
subpixel = placement[6]
xpos, ypos = place(width, height, surf.width, surf.height, placement)
xpos += x
ypos += y
pos = (xpos, ypos)
if dest is not None:
if subpixel:
dest.subpixel_blit(surf, pos, main, main, None)
else:
dest.blit(surf, pos, main, main, None)
return pos
def set_transform_event(self, event):
"""
Sets the transform event of this displayable to event.
"""
if event == self.transform_event:
return
self.transform_event = event
if self.transform_event_responder:
renpy.display.render.redraw(self, 0)
def _handles_event(self, event):
"""
Returns True if the displayable handles event, False otherwise.
"""
return False
def _hide(self, st, at, kind):
"""
Returns None if this displayable is ready to be hidden, or
a replacement displayable if it doesn't want to be hidden
quite yet.
Kind may be "hide", "replace", or "cancel", with the latter
being called when the hide is being hidden itself because
another displayable is shown.
"""
return None
def _show(self):
"""
No longer used.
"""
def _target(self):
"""
If this displayable is part of a chain of one or more references,
returns the ultimate target of those references. Otherwise, returns
the displayable.
"""
return self
def _change_transform_child(self, child):
"""
If this is a transform, makes a copy of the transform and sets
the child of the innermost transform to this. Otherwise,
simply returns child.
"""
return child
def _clear(self):
"""
Clears out the children of this displayable, if any.
"""
return
def _tts_common(self, default_alt=None, reverse=False):
rv = [ ]
if reverse:
order = -1
else:
order = 1
speech = ""
for i in self.visit()[::order]:
if i is not None:
speech = i._tts()
if speech.strip():
if isinstance(speech, renpy.display.tts.TTSDone):
rv = [ speech ]
else:
rv.append(speech)
rv = ": ".join(rv)
rv = rv.replace("::", ":")
rv = rv.replace(": :", ":")
alt = self.style.alt
if alt is None:
alt = default_alt
if alt is not None:
rv = renpy.substitutions.substitute(alt, scope={ "text" : rv })[0]
rv = type(speech)(rv)
return rv
def _tts(self):
"""
Returns the self-voicing text of this displayable and all of its
children that cannot take focus. If the displayable can take focus,
returns the empty string.
"""
return self._tts_common()
def _tts_all(self):
"""
Returns the self-voicing text of this displayable and all of its
children that cannot take focus.
"""
return self._tts_common()
+11 -21
View File
@@ -58,7 +58,7 @@ def default_drop_allowable(drop, drags):
return True
class Drag(renpy.display.core.Displayable, renpy.revertable.RevertableObject):
class Drag(renpy.display.displayable.Displayable, renpy.revertable.RevertableObject):
"""
:doc: drag_drop class
:args: (d=None, drag_name=None, draggable=True, droppable=True, drag_raise=True, dragging=None, dragged=None, dropped=None, drag_handle=(0.0, 0.0, 1.0, 1.0), drag_joined=..., clicked=None, hovered=None, unhovered=None, mouse_drop=False, **properties)
@@ -159,9 +159,7 @@ class Drag(renpy.display.core.Displayable, renpy.revertable.RevertableObject):
`drag_handle`
A (x, y, width, height) tuple, giving the position of the drag
handle within the child. In this tuple, integers are considered
to be a literal number of pixels, while floats are relative to
the size of the child.
handle within the child. This tuple takes :term:`positions <position>`.
`drag_joined`
This is called with the current Drag as an argument. It's
@@ -382,7 +380,7 @@ class Drag(renpy.display.core.Displayable, renpy.revertable.RevertableObject):
self.target_at_delay = 0
# The displayable we were last dropping on.
self.last_drop = None # type: renpy.display.core.Displayable|None
self.last_drop = None # type: renpy.display.displayable.Displayable|None
# Did we move over the course of this drag?
self.drag_moved = False
@@ -426,11 +424,11 @@ class Drag(renpy.display.core.Displayable, renpy.revertable.RevertableObject):
linear move.
"""
if (type(x) is float) and self.parent_width is not None:
x = int(x * self.parent_width)
if self.parent_width is not None:
x = int(absolute.compute_raw(x, self.parent_width))
if (type(y) is float) and self.parent_height is not None:
y = int(y * self.parent_height)
if self.parent_height is not None:
y = int(absolute.compute_raw(y, self.parent_height))
self.target_x = x
self.target_y = y
@@ -612,18 +610,10 @@ class Drag(renpy.display.core.Displayable, renpy.revertable.RevertableObject):
if self.draggable or self.clicked is not None:
fx, fy, fw, fh = self.drag_handle
if isinstance(fx, float):
fx = int(fx * cw)
if isinstance(fy, float):
fy = int(fy * ch)
if isinstance(fw, float):
fw = int(fw * cw)
if isinstance(fh, float):
fh = int(fh * ch)
fx = int(absolute.compute_raw(fx, cw))
fy = int(absolute.compute_raw(fy, ch))
fw = int(absolute.compute_raw(fw, cw))
fh = int(absolute.compute_raw(fh, ch))
mask = self.style.focus_mask
+1 -1
View File
@@ -422,7 +422,7 @@ def before_interact(roots):
grab = replaced_by.get(id(grab), None)
if override is not None:
d = renpy.exports.get_displayable(base=True, *override) # type: ignore
d = renpy.exports.get_displayable(*override, base=True) # type: ignore
if (d is not None) and (current is not d) and not grab:
current = d
+80 -45
View File
@@ -622,7 +622,7 @@ def free_memory():
cache.clear()
class ImageBase(renpy.display.core.Displayable):
class ImageBase(renpy.display.displayable.Displayable):
"""
This is the base class for all of the various kinds of images that
we can possibly have.
@@ -712,8 +712,9 @@ class Image(ImageBase):
"""
is_svg = False
dpi = 96
def __init__(self, filename, **properties):
def __init__(self, filename, dpi=96, **properties):
"""
@param filename: The filename that the image will be loaded from.
"""
@@ -731,7 +732,10 @@ class Image(ImageBase):
super(Image, self).__init__(filename, **properties)
self.filename = filename
self.dpi = dpi
self.is_svg = filename.lower().endswith(".svg")
self.pixel_perfect = self.is_svg
def _repr_info(self):
return repr(self.filename)
@@ -771,19 +775,23 @@ class Image(ImageBase):
# avoid size-related exceptions (e.g. Crop on a smaller placeholder)
surf = renpy.display.pgrender.transform_scale(surf, force_size)
self.is_svg = filename.lower().endswith(".svg")
self.pixel_perfect = self.is_svg
if self.is_svg:
width, height = surf.get_size()
width = int(width * renpy.display.draw.draw_per_virt)
height = int(height * renpy.display.draw.draw_per_virt)
width = int(width * renpy.display.draw.draw_per_virt * self.dpi / 96)
height = int(height * renpy.display.draw.draw_per_virt * self.dpi / 96)
filelike = renpy.loader.load(self.filename, directory="images")
if filename != self.filename:
with filelike as f:
surf = renpy.display.pgrender.load_image(filelike, filename, size=(width, height))
# This should only run for placeholder images.
surf = renpy.display.pgrender.transform_scale(surf, (width, height))
else:
filelike = renpy.loader.load(self.filename, directory="images")
with filelike as f:
surf = renpy.display.pgrender.load_image(filelike, filename, size=(width, height))
return surf
@@ -1012,8 +1020,9 @@ class FactorScale(ImageBase):
image logo doubled = im.FactorScale("logo.png", 1.5)
The same effect can now be achieved with the :tpref:`zoom` or the
:tpref:`xzoom` and :tpref:`yzoom` transform properties.
.. deprecated:: 7.4.0
Use the :tpref:`zoom`, or the
:tpref:`xzoom` and :tpref:`yzoom` transform properties.
"""
def __init__(self, im, width, height=None, bilinear=True, **properties):
@@ -1073,9 +1082,9 @@ class Flip(ImageBase):
image eileen flip = im.Flip("eileen_happy.png", vertical=True)
The same effect can now be achieved by setting
:tpref:`xzoom` (for horizontal flip)
or :tpref:`yzoom` (for vertical flip) to a negative value.
.. deprecated:: 7.4.0
Set :tpref:`xzoom` (for horizontal flip)
or :tpref:`yzoom` (for vertical flip) to a negative value.
"""
def __init__(self, im, horizontal=False, vertical=False, **properties):
@@ -1165,7 +1174,8 @@ class Crop(ImageBase):
image logo crop = im.Crop("logo.png", (0, 0, 100, 307))
The same effect can now be achieved by setting the :tpref:`crop` transform property.
.. deprecated:: 7.4.0
Use the :tpref:`crop` transform property.
"""
def __init__(self, im, x, y=None, w=None, h=None, **properties):
@@ -1365,7 +1375,8 @@ class Blur(ImageBase):
image logo blurred = im.Blur("logo.png", 1.5)
The same effect can now be achieved with the :tpref:`blur` transform property.
.. deprecated:: 7.4.0
Use the :tpref:`blur` transform property.
"""
def __init__(self, im, xrad, yrad=None, **properties):
@@ -1427,6 +1438,10 @@ class MatrixColor(ImageBase):
The components of the transformed color are clamped to the
range [0.0, 1.0].
.. deprecated:: 7.4.0
Use ``Transform(im, matrixcolor=matrix, **properties)``.
See :func:`Transform` and :tpref:`matrixcolor`.
"""
def __init__(self, im, matrix, **properties):
@@ -1475,6 +1490,9 @@ class matrix(tuple):
`matrix` is a 20 or 25 element list or tuple. If it is 20 elements
long, it is padded with (0, 0, 0, 0, 1) to make a 5x5 matrix,
suitable for multiplication.
.. deprecated:: 7.4.0
Use :class:`Matrix`.
"""
def __new__(cls, *args):
@@ -1567,8 +1585,9 @@ im.matrix(%f, %f, %f, %f, %f.
Returns an identity matrix, one that does not change color or
alpha.
A suitable equivalent for the :tpref:`matrixcolor` transform property
is IdentityMatrix().
.. deprecated:: 7.4.0
Use :func:`IdentityMatrix() <IdentityMatrix>`
with the :tpref:`matrixcolor` transform property.
"""
return matrix(1, 0, 0, 0, 0,
@@ -1598,8 +1617,9 @@ im.matrix(%f, %f, %f, %f, %f.
mostly sensitive to green, more of the green channel is
kept then the other two channels.
A suitable equivalent for the :tpref:`matrixcolor` transform property
is SaturationMatrix(value, desat).
.. deprecated:: 7.4.0
Use :func:`SaturationMatrix(value, desat) <SaturationMatrix>`
with the :tpref:`matrixcolor` transform property.
"""
r, g, b = desat
@@ -1622,8 +1642,9 @@ im.matrix(%f, %f, %f, %f, %f.
grayscale). This is equivalent to calling
im.matrix.saturation(0).
A suitable equivalent for the :tpref:`matrixcolor` transform property
is SaturationMatrix(0).
.. deprecated:: 7.4.0
Use :func:`SaturationMatrix(0) <SaturationMatrix>`
with the :tpref:`matrixcolor` transform property.
"""
return matrix.saturation(0.0)
@@ -1641,8 +1662,9 @@ im.matrix(%f, %f, %f, %f, %f.
the value of the red channel is 100, the transformed color
will have a red value of 50.)
A suitable equivalent for the :tpref:`matrixcolor` transform property
is TintMatrix(Color((r, g, b))).
.. deprecated:: 7.4.0
Use :func:`TintMatrix(Color((r, g, b))) <TintMatrix>`
with the :tpref:`matrixcolor` transform property.
"""
return matrix(r, 0, 0, 0, 0,
@@ -1659,8 +1681,9 @@ im.matrix(%f, %f, %f, %f, %f.
Returns an im.matrix that inverts the red, green, and blue
channels of the image without changing the alpha channel.
A suitable equivalent for the :tpref:`matrixcolor` transform property
is InvertMatrix(1.0).
.. deprecated:: 7.4.0
Use :func:`InvertMatrix(1.0) <InvertMatrix>`
with the :tpref:`matrixcolor` transform property.
"""
return matrix(-1, 0, 0, 0, 1,
@@ -1681,8 +1704,9 @@ im.matrix(%f, %f, %f, %f, %f.
a number between -1 and 1, with -1 the darkest possible
image and 1 the brightest.
A suitable equivalent for the :tpref:`matrixcolor` transform property
is BrightnessMatrix(b).
.. deprecated:: 7.4.0
Use :func:`BrightnessMatrix(b) <BrightnessMatrix>`
with the :tpref:`matrixcolor` transform property.
"""
return matrix(1, 0, 0, 0, b,
@@ -1699,8 +1723,9 @@ im.matrix(%f, %f, %f, %f, %f.
Returns an im.matrix that alters the opacity of an image. An
`o` of 0.0 is fully transparent, while 1.0 is fully opaque.
A suitable equivalent for the :tpref:`matrixcolor` transform property
is OpacityMatrix(o).
.. deprecated:: 7.4.0
Use :func:`OpacityMatrix(o) <OpacityMatrix>`
with the :tpref:`matrixcolor` transform property.
"""
return matrix(1, 0, 0, 0, 0,
@@ -1718,8 +1743,9 @@ im.matrix(%f, %f, %f, %f, %f.
be greater than 0.0, with values between 0.0 and 1.0 decreasing contrast, and
values greater than 1.0 increasing contrast.
A suitable equivalent for the :tpref:`matrixcolor` transform property
is ContrastMatrix(c).
.. deprecated:: 7.4.0
Use :func:`ContrastMatrix(c) <ContrastMatrix>`
with the :tpref:`matrixcolor` transform property.
"""
return matrix.brightness(-.5) * matrix.tint(c, c, c) * matrix.brightness(.5)
@@ -1734,8 +1760,9 @@ im.matrix(%f, %f, %f, %f, %f.
Returns an im.matrix that rotates the hue by `h` degrees, while
preserving luminosity.
A suitable equivalent for the :tpref:`matrixcolor` transform property
is HueMatrix(h).
.. deprecated:: 7.4.0
Use :func:`HueMatrix(h) <HueMatrix>`
with the :tpref:`matrixcolor` transform property.
"""
h = h * math.pi / 180
@@ -1769,8 +1796,9 @@ im.matrix(%f, %f, %f, %f, %f.
im.matrix.colorize("#f00", "#00f"))
A suitable equivalent for the :tpref:`matrixcolor` transform property
is ColorizeMatrix(black_color, white_color).
.. deprecated:: 7.4.0
Use :func:`ColorizeMatrix(black_color, white_color) <ColorizeMatrix>`
with the :tpref:`matrixcolor` transform property.
"""
(r0, g0, b0, _a0) = renpy.easy.color(black_color) # type: ignore
@@ -1797,8 +1825,9 @@ def Grayscale(im, desat=(0.2126, 0.7152, 0.0722), **properties):
An image manipulator that creates a desaturated version of the image
manipulator `im`.
The same effect can now be achieved by supplying SaturationMatrix(0)
to the :tpref:`matrixcolor` transform property.
.. deprecated:: 7.4.0
Set the :tpref:`matrixcolor` transform property to
:func:`SaturationMatrix(0) <SaturationMatrix>`.
"""
return MatrixColor(im, matrix.saturation(0.0, desat), **properties)
@@ -1812,8 +1841,9 @@ def Sepia(im, tint=(1.0, .94, .76), desat=(0.2126, 0.7152, 0.0722), **properties
An image manipulator that creates a sepia-toned version of the image
manipulator `im`.
The same effect can now be achieved by supplying SepiaMatrix()
to the :tpref:`matrixcolor` transform property.
.. deprecated:: 7.4.0
Set the :tpref:`matrixcolor` transform property to
:func:`SepiaMatrix() <SepiaMatrix>`
"""
return MatrixColor(im, matrix.saturation(0.0, desat) * matrix.tint(tint[0], tint[1], tint[2]), **properties)
@@ -1854,8 +1884,8 @@ class Tile(ImageBase):
If not None, a (width, height) tuple. If None, this defaults to
(:var:`config.screen_width`, :var:`config.screen_height`).
The same effect can now be achieved using the :func:`Tile`
displayable, with ``Tile(im, size=size)``.
.. deprecated:: 7.4.0
Use :func:`Tile(im, xysize=size, **properties) <Tile>`.
"""
def __init__(self, im, size=None, **properties):
@@ -1951,7 +1981,7 @@ def image(arg, loose=False, **properties):
"""
:doc: im_image
:name: Image
:args: (filename, *, optimize_bounds=True, oversample=1, **properties)
:args: (filename, *, optimize_bounds=True, oversample=1, dpi=96, **properties)
Loads an image from a file. `filename` is a
string giving the name of the file.
@@ -1970,6 +2000,11 @@ def image(arg, loose=False, **properties):
with more pixels than its logical size would imply. For example, if
an image file is 2048x2048 and oversample is 2, then the image will
be treated as a 1024x1024 image for the purpose of layout.
`dpi`
The DPI of an SVG image. This defaults to 96, but that can be
increased to render the SVG larger, and decreased to render
it smaller.
"""
"""
@@ -2004,7 +2039,7 @@ def image(arg, loose=False, **properties):
elif loose:
return arg
if isinstance(arg, renpy.display.core.Displayable):
if isinstance(arg, renpy.display.displayable.Displayable):
raise Exception("Expected an image, but got a general displayable.")
else:
raise Exception("Could not construct image from argument.")
+8 -8
View File
@@ -319,7 +319,7 @@ def wrap_render(child, w, h, st, at):
return rv
class ImageReference(renpy.display.core.Displayable):
class ImageReference(renpy.display.displayable.Displayable):
"""
ImageReference objects are used to reference images by their name,
which is a tuple of strings corresponding to the name used to define
@@ -348,7 +348,7 @@ class ImageReference(renpy.display.core.Displayable):
super(ImageReference, self).__init__(**properties)
self.name = name
self.target = None # type: renpy.display.core.Displayable|None
self.target = None # type: renpy.display.displayable.Displayable|None
def _repr_info(self):
return repr(self.name)
@@ -379,7 +379,7 @@ class ImageReference(renpy.display.core.Displayable):
name = self.name
if isinstance(name, renpy.display.core.Displayable):
if isinstance(name, renpy.display.displayable.Displayable):
self.target = name
return True
@@ -452,7 +452,7 @@ class ImageReference(renpy.display.core.Displayable):
rv = self._copy(args)
rv.target = None
if isinstance(rv.name, renpy.display.core.Displayable):
if isinstance(rv.name, renpy.display.displayable.Displayable):
if rv.name._duplicatable:
rv.name = rv.name._duplicate(args)
@@ -542,7 +542,7 @@ class ImageReference(renpy.display.core.Displayable):
return [ self.target ]
class DynamicImage(renpy.display.core.Displayable):
class DynamicImage(renpy.display.displayable.Displayable):
"""
:doc: disp_imagelike
:args: (name)
@@ -555,10 +555,10 @@ class DynamicImage(renpy.display.core.Displayable):
nosave = [ 'raw_target' ]
# The target that this image currently resolves to.
target = None # type: renpy.display.core.Displayable|None
target = None # type: renpy.display.displayable.Displayable|None
# The raw target that the image resolves to, before it has been parameterized.
raw_target = None # type: renpy.display.core.Displayable|None
raw_target = None # type: renpy.display.displayable.Displayable|None
# Have we been locked, so we never change?
locked = False
@@ -688,7 +688,7 @@ class DynamicImage(renpy.display.core.Displayable):
if not update:
return True
raw_target = target # type: renpy.display.core.Displayable
raw_target = target # type: renpy.display.displayable.Displayable
old_target = self.target
if raw_target._duplicatable:
+3 -3
View File
@@ -31,7 +31,7 @@ from renpy.display.render import render, Render
from renpy.display.matrix import Matrix2D
class Solid(renpy.display.core.Displayable):
class Solid(renpy.display.displayable.Displayable):
"""
:doc: disp_imagelike
@@ -145,7 +145,7 @@ class Borders(object):
)
class Frame(renpy.display.core.Displayable):
class Frame(renpy.display.displayable.Displayable):
"""
:doc: disp_imagelike
:args: (image, left=0, top=0, right=None, bottom=None, *, tile=False, **properties)
@@ -605,7 +605,7 @@ class Frame(renpy.display.core.Displayable):
return rv
class FileCurrentScreenshot(renpy.display.core.Displayable):
class FileCurrentScreenshot(renpy.display.displayable.Displayable):
"""
:doc: file_action_function
+2 -2
View File
@@ -36,7 +36,7 @@ from renpy.display.render import render
cached = set()
class ImageMapCrop(renpy.display.core.Displayable):
class ImageMapCrop(renpy.display.displayable.Displayable):
"""
This handles the cropping of uncached imagemap components.
"""
@@ -55,7 +55,7 @@ class ImageMapCrop(renpy.display.core.Displayable):
return cr.subsurface(self.rect)
class ImageCacheCrop(renpy.display.core.Displayable):
class ImageCacheCrop(renpy.display.displayable.Displayable):
"""
This handles the cropping of an imagemap component.
"""
+47 -40
View File
@@ -31,16 +31,11 @@ import renpy
from renpy.display.render import render, Render
def scale(num, base):
"""
If num is a float, multiplies it by base and returns that. Otherwise,
returns num unchanged.
"""
if type(num) is float:
return num * base
else:
return num
if PY2:
def compute_raw(value, room):
return renpy.display.core.absolute.compute_raw(value, room)
else:
compute_raw = renpy.display.core.absolute.compute_raw
def xyminimums(style, width, height):
@@ -58,7 +53,7 @@ def xyminimums(style, width, height):
if (type(xmaximum) is float) and xmaximum and renpy.config.adjust_minimums:
xminimum = xminimum / xmaximum
xminimum = xminimum * width
xminimum = compute_raw(xminimum, width)
if type(yminimum) is float:
ymaximum = style.ymaximum
@@ -66,12 +61,12 @@ def xyminimums(style, width, height):
if (type(ymaximum) is float) and ymaximum and renpy.config.adjust_minimums:
yminimum = yminimum / ymaximum
yminimum = yminimum * height
yminimum = compute_raw(yminimum, height)
return xminimum, yminimum
class Null(renpy.display.core.Displayable):
class Null(renpy.display.displayable.Displayable):
"""
:doc: disp_imagelike
:name: Null
@@ -101,7 +96,7 @@ class Null(renpy.display.core.Displayable):
return rv
class Container(renpy.display.core.Displayable):
class Container(renpy.display.displayable.Displayable):
"""
This is the base class for containers that can have one or more
children.
@@ -128,7 +123,7 @@ class Container(renpy.display.core.Displayable):
def __init__(self, *args, **properties):
self.children = self._list_type() # type: list
self.child = None # type: renpy.display.core.Displayable|None
self.child = None # type: renpy.display.displayable.Displayable|None
self.offsets = self._list_type() # type: list[tuple[int, int]]
for i in args:
@@ -469,13 +464,13 @@ class Grid(Container):
yspacing = self.style.spacing
if renpy.config.relative_spacing:
xspacing = renpy.display.layout.scale(xspacing, width)
yspacing = renpy.display.layout.scale(yspacing, height)
xspacing = compute_raw(xspacing, width)
yspacing = compute_raw(yspacing, height)
left_margin = scale(self.style.left_margin, width)
right_margin = scale(self.style.right_margin, width)
top_margin = scale(self.style.top_margin, height)
bottom_margin = scale(self.style.bottom_margin, height)
left_margin = compute_raw(self.style.left_margin, width)
right_margin = compute_raw(self.style.right_margin, width)
top_margin = compute_raw(self.style.top_margin, height)
bottom_margin = compute_raw(self.style.bottom_margin, height)
# For convenience and speed.
cols = self.cols
@@ -817,11 +812,11 @@ class MultiBox(Container):
minx = self.style.xminimum
if minx is not None:
width = max(width, scale(minx, width))
width = max(width, compute_raw(minx, width))
miny = self.style.yminimum
if miny is not None:
height = max(height, scale(miny, height))
height = max(height, compute_raw(miny, height))
if self.first and adjust_times:
self.update_times()
@@ -964,7 +959,7 @@ class MultiBox(Container):
# The children to layout.
children = list(self.children)
if self.style.box_reverse:
if self.style.box_reverse and renpy.config.simple_box_reverse:
children.reverse()
spacings.reverse()
@@ -1136,9 +1131,20 @@ class MultiBox(Container):
if not yfill:
height = max(yminimum, maxy)
if self.style.box_reverse and not renpy.config.simple_box_reverse:
new_placements = [ ]
for child, x, y, w, h, surf in placements:
if layout == "vertical":
new_placements.append((child, x, height - y - h, w, h, surf))
else:
new_placements.append((child, width - x - w, y, w, h, surf))
placements = new_placements
rv = renpy.display.render.Render(width, height)
if self.style.box_reverse ^ self.style.order_reverse:
if self.style.order_reverse ^ (self.style.box_reverse and renpy.config.simple_box_reverse):
placements.reverse()
for child, x, y, w, h, surf in placements:
@@ -1293,17 +1299,17 @@ class Window(Container):
width = max(xminimum, width)
height = max(yminimum, height)
left_margin = scale(style.left_margin, width)
left_padding = scale(style.left_padding, width)
left_margin = compute_raw(style.left_margin, width)
left_padding = compute_raw(style.left_padding, width)
right_margin = scale(style.right_margin, width)
right_padding = scale(style.right_padding, width)
right_margin = compute_raw(style.right_margin, width)
right_padding = compute_raw(style.right_padding, width)
top_margin = scale(style.top_margin, height)
top_padding = scale(style.top_padding, height)
top_margin = compute_raw(style.top_margin, height)
top_padding = compute_raw(style.top_padding, height)
bottom_margin = scale(style.bottom_margin, height)
bottom_padding = scale(style.bottom_padding, height)
bottom_margin = compute_raw(style.bottom_margin, height)
bottom_padding = compute_raw(style.bottom_padding, height)
# c for combined.
cxmargin = left_margin + right_margin
@@ -1417,7 +1423,7 @@ def dynamic_displayable_compat(st, at, expr):
return child, None
class DynamicDisplayable(renpy.display.core.Displayable):
class DynamicDisplayable(renpy.display.displayable.Displayable):
"""
:doc: disp_dynamic
@@ -1966,7 +1972,7 @@ class Side(Container):
return rv
class Alpha(renpy.display.core.Displayable):
class Alpha(renpy.display.displayable.Displayable):
def __init__(self, start, end, time, child=None, repeat=False, bounce=False,
anim_timebase=False, time_warp=None, **properties):
@@ -2381,11 +2387,8 @@ class NearRect(Container):
layout_y = py - ch
# Initial x positioning - using a variant of the layout algorithm.
if isinstance(xpos, float):
xpos = xpos * pw
if isinstance(xanchor, float):
xanchor = xanchor * cw
xpos = compute_raw(xpos, pw)
xanchor = compute_raw(xanchor, cw)
layout_x = px + xpos - xanchor
@@ -2421,6 +2424,7 @@ class NearRect(Container):
class Layer(AdjustTimes):
"""
:doc: disp_layer
:args: (layer, *, clipping=True, **properties)
This allows a layer to be shown as a displayable on another layer.
Intended for use with detached layers.
@@ -2433,6 +2437,9 @@ class Layer(AdjustTimes):
`clipping`
If False, the layer's contents may exceed its bounds, otherwise
anything exceeding the bounds will be trimmed.
An entry in config.layer_clipping will cause this option to be
ignored, and clipping to occur as specified by that config.
"""
# Used to store layer_transitions when processing this layer.
+1 -1
View File
@@ -47,7 +47,7 @@ class Texture(object):
return Texture(d, self.focus, self.main, self.fit)
class Model(renpy.display.core.Displayable):
class Model(renpy.display.displayable.Displayable):
"""
:doc: model_displayable class
+5 -7
View File
@@ -319,6 +319,7 @@ class Revolver(object):
self.child = child
def __call__(self, t, rect):
absolute = renpy.display.core.absolute
(w, h, cw, ch) = rect
@@ -328,10 +329,7 @@ class Revolver(object):
if x is None:
x = 0
if isinstance(x, float):
return int(x * r)
else:
return x
return absolute.compute_raw(x, r)
if self.pos is None:
pos = self.child.get_placement()
@@ -370,7 +368,7 @@ class Revolver(object):
nx = nx - xcor + xaround
ny = ny - ycor + yaround
return (renpy.display.core.absolute(nx), renpy.display.core.absolute(ny), 0, 0)
return (absolute(nx), absolute(ny), 0, 0)
def Revolve(start, end, time, child, around=(0.5, 0.5), cor=(0.5, 0.5), pos=None, **properties):
@@ -408,7 +406,7 @@ def zoom_render(crend, x, y, w, h, zw, zh, bilinear):
return rv
class ZoomCommon(renpy.display.core.Displayable):
class ZoomCommon(renpy.display.displayable.Displayable):
def __init__(self,
time, child,
@@ -577,7 +575,7 @@ class SizeZoom(ZoomCommon):
return 0, 0, width, height, zw, zh
class RotoZoom(renpy.display.core.Displayable):
class RotoZoom(renpy.display.displayable.Displayable):
transform = None
+8 -11
View File
@@ -345,7 +345,7 @@ def OldMoveTransition(delay, old_widget=None, new_widget=None, factory=None, ent
# New Move Transition (since 6.14)
class MoveInterpolate(renpy.display.core.Displayable):
class MoveInterpolate(renpy.display.displayable.Displayable):
"""
This displayable has two children. It interpolates between the positions
of its two children to place them on the screen.
@@ -456,9 +456,9 @@ def MoveTransition(delay, old_widget=None, new_widget=None, enter=None, leave=No
same tag) in the old and new scenes.
As only layers have tags, MoveTransitions can only be applied to a single
layer or all layers at once, using the with statement. It will not work
in other contexts, like ATL, :doc:`ComposeTransition`, or other ways of
applying transitions.
layer or all layers at once, using the :ref:`with statement <with-statement>`.
It will not work in other contexts such as :ref:`ATL <expression-atl-statement>`,
:func:`ComposeTransition`, or other ways of applying transitions.
`delay`
The time it takes for the interpolation to finish.
@@ -493,13 +493,10 @@ def MoveTransition(delay, old_widget=None, new_widget=None, enter=None, leave=No
A time warp function that's applied to images leaving the scene.
"""
if not (hasattr(old_widget, 'scene_list') or hasattr(old_widget, 'layers')):
if renpy.config.developer:
raise Exception("MoveTransition can only be applied to one or all layers, not %s." % type(old_widget).__name__)
if not (hasattr(new_widget, 'scene_list') or hasattr(new_widget, 'layers')):
if renpy.config.developer:
raise Exception("MoveTransition can only be applied to one or all layers, not %s." % type(new_widget).__name__)
if renpy.config.developer:
for widget in (old_widget, new_widget):
if not (hasattr(widget, 'scene_list') or hasattr(widget, 'layers')):
raise Exception("MoveTransition can only be applied to one or all layers, not %s." % type(widget).__name__)
use_old = old
+5 -5
View File
@@ -52,8 +52,8 @@ class SpriteCache(renpy.object.Object):
# If true, then the render is simple enough it can just be appended to
# the manager's render's children list.
child = None # type: renpy.display.core.Displayable|None
child_copy = None # type: renpy.display.core.Displayable|None
child = None # type: renpy.display.displayable.Displayable|None
child_copy = None # type: renpy.display.displayable.Displayable|None
st = 0.0 # type: float|None
render = None # type: renpy.display.render.Render|None
@@ -91,7 +91,7 @@ class Sprite(renpy.object.Object):
zorder = 0 # type: int|float
child = None # type: renpy.display.core.Displayable|None
child = None # type: renpy.display.displayable.Displayable|None
render = None # type: renpy.display.render.Render|None
live = True # type: bool
manager = None # type: SpriteManager|None
@@ -145,7 +145,7 @@ class Sprite(renpy.object.Object):
self.events = False
class SpriteManager(renpy.display.core.Displayable):
class SpriteManager(renpy.display.displayable.Displayable):
"""
:doc: sprites class
@@ -349,7 +349,7 @@ class SpriteManager(renpy.display.core.Displayable):
self.children = [ ]
class Particles(renpy.display.core.Displayable, renpy.rollback.NoRollback):
class Particles(renpy.display.displayable.Displayable, renpy.rollback.NoRollback):
"""
Supports particle motion, using the old API.
"""
+2 -8
View File
@@ -227,16 +227,10 @@ cpdef render(d, object widtho, object heighto, double st, double at):
width, height = d._offer_size
if xmaximum is not None:
if isinstance(xmaximum, float):
width = width * xmaximum
else:
width = min(xmaximum, width)
width = min(renpy.display.core.absolute.compute_raw(xmaximum, width), width)
if ymaximum is not None:
if isinstance(ymaximum, float):
height = height * ymaximum
else:
height = min(ymaximum, height)
height = min(renpy.display.core.absolute.compute_raw(ymaximum, height), height)
if width < 0:
width = 0
+6
View File
@@ -1105,6 +1105,12 @@ def get_screen(name, layer=None):
.. attribute:: zorder
The zorder the screen is being displayed at.
.. warning::
Like other similar functions, the object this returns is meant to be used
in the short term after the function is called. Including it in save data
or making it participate in rollback is not advised.
"""
if layer is None:
+34 -19
View File
@@ -76,6 +76,18 @@ def first_not_none(*args):
return args[-1]
def limit_angle(n):
"""
Limits an angle to the range 0 and 360 degrees.
"""
n = n % 360
if n < 0:
n += 360
return n
class TransformState(renpy.object.Object):
last_angle = 0.0
@@ -210,16 +222,14 @@ class TransformState(renpy.object.Object):
yalign = property(get_yalign, set_yalign)
def scale(self, value, available):
@staticmethod
def scale(value, available):
"""
Converts value to a float, scaled by the available area, if
required.
"""
if type(value) is float:
return value * available
return 1.0 * value
return float(absolute.compute_raw(value, available))
def cartesian_to_polar_pos(self, x, y):
"""
@@ -327,7 +337,11 @@ class TransformState(renpy.object.Object):
def get_angle(self):
xpos = first_not_none(self.xpos, self.inherited_xpos, 0)
ypos = first_not_none(self.ypos, self.inherited_ypos, 0)
angle, _radius = self.cartesian_to_polar_pos(xpos, ypos)
angle, radius = self.cartesian_to_polar_pos(xpos, ypos)
if radius == 0 and self.last_angle is not None:
angle = self.last_angle
return angle
def get_radius(self):
@@ -345,7 +359,7 @@ class TransformState(renpy.object.Object):
return self.radius_type(radius)
def set_angle(self, angle):
self.last_angle = angle
self.last_angle = limit_angle(angle)
xpos = first_not_none(self.xpos, self.inherited_xpos, 0)
ypos = first_not_none(self.ypos, self.inherited_ypos, 0)
@@ -355,8 +369,7 @@ class TransformState(renpy.object.Object):
def set_radius(self, radius):
self.radius_type = type(radius)
if type(radius) is float:
radius = self.scale(radius, min(self.available_width, self.available_height))
radius = self.scale(radius, min(self.available_width, self.available_height))
xpos = first_not_none(self.xpos, self.inherited_xpos, 0)
ypos = first_not_none(self.ypos, self.inherited_ypos, 0)
@@ -376,7 +389,11 @@ class TransformState(renpy.object.Object):
def get_anchorangle(self):
xanchor = first_not_none(self.xanchor, self.inherited_xanchor, 0)
yanchor = first_not_none(self.yanchor, self.inherited_yanchor, 0)
angle, _radius = self.cartesian_to_polar_anchor(xanchor, yanchor)
angle, radius = self.cartesian_to_polar_anchor(xanchor, yanchor)
if radius == 0 and self.last_anchorangle is not None:
angle = self.last_anchorangle
return angle
def get_anchorradius(self):
@@ -387,7 +404,7 @@ class TransformState(renpy.object.Object):
return self.radius_type(radius)
def set_anchorangle(self, angle):
self.last_anchorangle = angle
self.last_anchorangle = limit_angle(angle)
xanchor = first_not_none(self.xanchor, self.inherited_xanchor, 0)
yanchor = first_not_none(self.yanchor, self.inherited_yanchor, 0)
@@ -884,7 +901,7 @@ class Transform(Container):
self.active = True
if self.state.last_events != self.state.events:
if self.state.events:
if self.state.events and renpy.game.interface is not None:
renpy.game.interface.timeout(0)
self.state.last_events = self.state.events
@@ -1007,10 +1024,8 @@ class Transform(Container):
cw, ch = self.child_size
rw, rh = self.render_size
if xanchor.__class__ is float:
xanchor *= cw
if yanchor.__class__ is float:
yanchor *= ch
xanchor = absolute.compute_raw(xanchor, cw)
yanchor = absolute.compute_raw(yanchor, ch)
xanchor -= cw / 2.0
yanchor -= ch / 2.0
@@ -1020,8 +1035,8 @@ class Transform(Container):
xanchor += rw / 2.0
yanchor += rh / 2.0
xanchor = renpy.display.core.absolute(xanchor)
yanchor = renpy.display.core.absolute(yanchor)
xanchor = absolute(xanchor)
yanchor = absolute(yanchor)
rv = (xpos, ypos, xanchor, yanchor, xoffset, yoffset, subpixel)
@@ -1094,7 +1109,7 @@ class ATLTransform(renpy.atl.ATLTransformBase, Transform):
self.active = True
if self.state.last_events != self.state.events:
if self.state.events:
if self.state.events and renpy.game.interface is not None:
renpy.game.interface.timeout(0)
self.state.last_events = self.state.events
+4 -4
View File
@@ -33,14 +33,14 @@ import renpy
from renpy.display.render import render
class Transition(renpy.display.core.Displayable):
class Transition(renpy.display.displayable.Displayable):
"""
This is the base class of most transitions. It takes care of event
dispatching.
"""
new_widget = None # type:renpy.display.core.Displayable|None
old_widget = None # type:renpy.display.core.Displayable|None
new_widget = None # type:renpy.display.displayable.Displayable|None
old_widget = None # type:renpy.display.displayable.Displayable|None
def __init__(self, delay, **properties):
super(Transition, self).__init__(**properties)
@@ -158,7 +158,7 @@ class MultipleTransition(Transition):
self.events = False
def visit(self):
return [ i for i in self.screens if isinstance(i, renpy.display.core.Displayable)] + self.transitions
return [ i for i in self.screens if isinstance(i, renpy.display.displayable.Displayable)] + self.transitions
def event(self, ev, x, y, st):
+58 -55
View File
@@ -249,33 +249,43 @@ def get_movie_texture_web(channel, mask_channel, side_mask, mipmap):
return tex, new
def render_movie(channel, width, height, group=None):
tex, _new = get_movie_texture(channel)
def resize_movie(r, width, height):
"""
A utility function to resize a Render or texture to the given
dimensions.
"""
if group is not None:
if tex is None:
tex = group_texture.get(group, None)
else:
group_texture[group] = tex
if tex is None:
if r is None:
return None
sw, sh = tex.get_size()
rv = renpy.display.render.Render(width, height)
scale = min(1.0 * width / sw, 1.0 * height / sh)
sw, sh = r.get_size()
if not (sw and sh):
return rv
scale = min(1.0 * width / sw, 1.0 * height / sh) # type: float
dw = scale * sw
dh = scale * sh
rv = renpy.display.render.Render(width, height)
rv.forward = renpy.display.matrix.Matrix2D(1.0 / scale, 0.0, 0.0, 1.0 / scale)
rv.reverse = renpy.display.matrix.Matrix2D(scale, 0.0, 0.0, scale)
rv.blit(tex, (int((width - dw) / 2), int((height - dh) / 2)))
rv.blit(r, (int((width - dw) / 2), int((height - dh) / 2)))
return rv
def render_movie(channel, width, height):
"""
Called from the Draw objects to render and scale a fullscreen movie.
"""
tex, _new = get_movie_texture(channel)
return resize_movie(tex, width, height)
def default_play_callback(old, new): # @UnusedVariable
renpy.audio.music.play(new._play, channel=new.channel, loop=new.loop, synchro_start=True)
@@ -286,17 +296,13 @@ def default_play_callback(old, new): # @UnusedVariable
# A serial number that's used to generated movie channels.
movie_channel_serial = 0
class Movie(renpy.display.core.Displayable):
class Movie(renpy.display.displayable.Displayable):
"""
:doc: movie
:args: (*, size=None, channel="movie", play=None, side_mask=False, mask=None, mask_channel=None, start_image=None, image=None, play_callback=None, loop=True, group=None, **properties)
This is a displayable that shows the current movie.
`fps`
The framerate that the movie should be shown at. (This is currently
ignored, but the parameter is kept for backwards compatibility.
The framerate is auto-detected.)
`size`
This should be specified as either a tuple giving the width and
height of the movie, or None to automatically adjust to the size
@@ -386,6 +392,11 @@ class Movie(renpy.display.core.Displayable):
the previous frame, the last frame from that movie will be used for
this movie. This can prevent flashes of transparency when switching
between two movies.
`keep_last_frame`
If true, and the movie has ended, the last frame will be displayed,
rather than the movie being hidden. This only works if `loop` is
false. (This behavior will also occur if `group` is set.)
"""
fullscreen = False
@@ -456,7 +467,9 @@ class Movie(renpy.display.core.Displayable):
self.ensure_channel(self.channel)
self.ensure_channel(self.mask_channel)
def __init__(self, fps=24, size=None, channel="movie", play=None, mask=None, mask_channel=None, image=None, play_callback=None, side_mask=False, loop=True, start_image=None, group=None, **properties):
keep_last_frame_serial = 0
def __init__(self, fps=24, size=None, channel="movie", play=None, mask=None, mask_channel=None, image=None, play_callback=None, side_mask=False, loop=True, start_image=None, group=None, keep_last_frame=False, **properties):
global movie_channel_serial
@@ -497,10 +510,11 @@ class Movie(renpy.display.core.Displayable):
self.play_callback = play_callback
self.group = group
if group is None and keep_last_frame:
group = "_keep_last_frame_" + str(Movie.keep_last_frame_serial)
Movie.keep_last_frame_serial += 1
if (self.channel == "movie") and (renpy.config.hw_video) and renpy.mobile:
raise Exception("Movie(channel='movie') doesn't work on mobile when config.hw_video is true. (Use a different channel argument.)")
self.group = group
def _handles_event(self, event):
return event == "show"
@@ -535,42 +549,31 @@ class Movie(renpy.display.core.Displayable):
return rv
if self.size is None:
tex, _ = get_movie_texture(self.channel, self.mask_channel, self.side_mask, self.style.mipmap)
if self.group is not None:
if tex is None:
tex = group_texture.get(self.group, None)
else:
group_texture[self.group] = tex
if (not not_playing) and (tex is not None):
width, height = tex.get_size()
rv = renpy.display.render.Render(width, height)
rv.blit(tex, (0, 0))
elif (not not_playing) and (self.start_image is not None):
surf = renpy.display.render.render(self.start_image, width, height, st, at)
w, h = surf.get_size()
rv = renpy.display.render.Render(w, h)
rv.blit(surf, (0, 0))
tex, _ = get_movie_texture(self.channel, self.mask_channel, self.side_mask, self.style.mipmap)
if self.group is not None:
if tex is None:
tex = group_texture.get(self.group, None)
else:
rv = renpy.display.render.Render(0, 0)
group_texture[self.group] = tex
if (not not_playing) and (tex is not None):
width, height = tex.get_size()
rv = renpy.display.render.Render(width, height)
rv.blit(tex, (0, 0))
elif (not not_playing) and (self.start_image is not None):
surf = renpy.display.render.render(self.start_image, width, height, st, at)
w, h = surf.get_size()
rv = renpy.display.render.Render(w, h)
rv.blit(surf, (0, 0))
else:
rv = renpy.display.render.Render(0, 0)
w, h = self.size
if not playing:
rv = None
else:
rv = render_movie(self.channel, w, h, group=self.group)
if rv is None:
rv = renpy.display.render.Render(w, h)
if self.size is not None:
rv = resize_movie(rv, self.size[0], self.size[1])
# Usually we get redrawn when the frame is ready - but we want
# the movie to disappear if it's ended, or if it hasn't started
@@ -707,7 +710,7 @@ def frequent():
return False
elif fullscreen and not ((renpy.android or renpy.ios) and renpy.config.hw_video):
elif fullscreen:
c = renpy.audio.audio.get_channel("movie")
+6 -6
View File
@@ -660,13 +660,13 @@ class VPGrid(Viewport):
yspacing = self.style.spacing
if renpy.config.relative_spacing:
xspacing = renpy.display.layout.scale(xspacing, width)
yspacing = renpy.display.layout.scale(yspacing, height)
xspacing = renpy.display.layout.compute_raw(xspacing, width)
yspacing = renpy.display.layout.compute_raw(yspacing, height)
left_margin = renpy.display.layout.scale(self.style.left_margin, width)
right_margin = renpy.display.layout.scale(self.style.right_margin, width)
top_margin = renpy.display.layout.scale(self.style.top_margin, height)
bottom_margin = renpy.display.layout.scale(self.style.bottom_margin, height)
left_margin = renpy.display.layout.compute_raw(self.style.left_margin, width)
right_margin = renpy.display.layout.compute_raw(self.style.right_margin, width)
top_margin = renpy.display.layout.compute_raw(self.style.top_margin, height)
bottom_margin = renpy.display.layout.compute_raw(self.style.bottom_margin, height)
rend = renpy.display.render.render(self.children[0], child_width, child_height, st, at)
cw, ch = rend.get_size()
+5 -5
View File
@@ -58,9 +58,9 @@ def lookup_displayable_prefix(d):
return displayable(fn(arg))
def displayable_or_none(d, scope=None, dynamic=True): # type: (Any, dict|None, bool) -> renpy.display.core.Displayable|None
def displayable_or_none(d, scope=None, dynamic=True): # type: (Any, dict|None, bool) -> renpy.display.displayable.Displayable|None
if isinstance(d, renpy.display.core.Displayable):
if isinstance(d, renpy.display.displayable.Displayable):
return d
if d is None:
@@ -99,7 +99,7 @@ def displayable_or_none(d, scope=None, dynamic=True): # type: (Any, dict|None, b
raise Exception("Not a displayable: %r" % (d,))
def displayable(d, scope=None): # type(d, dict|None=None) -> renpy.display.core.Displayable|None
def displayable(d, scope=None): # type(d, dict|None=None) -> renpy.display.displayable.Displayable|None
"""
:doc: udd_utility
:name: renpy.displayable
@@ -109,7 +109,7 @@ def displayable(d, scope=None): # type(d, dict|None=None) -> renpy.display.core.
rules.
"""
if isinstance(d, renpy.display.core.Displayable):
if isinstance(d, renpy.display.displayable.Displayable):
return d
if isinstance(d, basestring):
@@ -145,7 +145,7 @@ def displayable(d, scope=None): # type(d, dict|None=None) -> renpy.display.core.
raise Exception("Not a displayable: %r" % (d,))
def dynamic_image(d, scope=None, prefix=None, search=None): # type: (Any, dict|None, str|None, list|None) -> renpy.display.core.Displayable|None
def dynamic_image(d, scope=None, prefix=None, search=None): # type: (Any, dict|None, str|None, list|None) -> renpy.display.displayable.Displayable|None
"""
Substitutes a scope into `d`, then returns a displayable.
+40
View File
@@ -777,6 +777,8 @@ class Context(renpy.object.Object):
rv.last_abnormal = self.last_abnormal
rv.abnormal_stack = list(self.abnormal_stack)
rv.interacting = False
return rv
def predict_call(self, label, return_site):
@@ -971,3 +973,41 @@ def run_context(top):
except Exception:
context.pop_all_dynamic()
raise
def reset_all_contexts():
"""
:doc: context
This pops all contexts off the context stack, resetting the dynamic variables
as it does so. When this is done, a new context is created, the current statement
ends, and the game continues from the next statement. This will put Ren'Py
into the state it was at startup, with the exception of data and the start
point.
This can be used to reset everything about the game - shown image, playing music,
etc, as if the game started from the beginning.
Because of how completely this resets Ren'Py, this function immediately ends the
current statement.
This is mainly intended for use in an after_load label, where it can bring the
game back to the state it was in when it started. It's then up to the game to
re-establish the scene, music, etc, and it can then jump to the label it wants
to continue at.
"""
old = renpy.game.context()
if old.next_node is None:
raise Exception("The renpy.reset_all_contexts function can only be called as the last thing in a python statement.")
while renpy.game.contexts:
c = renpy.game.contexts.pop()
c.pop_all_dynamic()
c = Context(True)
c.goto_label(old.next_node.name)
renpy.game.contexts.append(c)
raise renpy.game.RestartTopContext()

Some files were not shown because too many files have changed in this diff Show More