Compare commits

...

409 Commits

Author SHA1 Message Date
Tom Rothamel 5085b1300b Fsencode the path to the update directory.
This should help make the updater work on non-ASCII systems.
2013-11-09 22:12:41 -05:00
Tom Rothamel 88275fa716 Use correct variable to access preferences.
Fixes a bug when config.default_language is set.
2013-11-09 14:53:53 -05:00
Tom Rothamel ce752343ca Document that video playback is supported on Android. 2013-11-09 12:36:39 -05:00
Tom Rothamel f74fb6ab29 Merge pull request #224 from DragoonHP/patch-1
Fix Doc
2013-11-09 06:36:28 -08:00
DragoonHP 7b185e022a Fix Doc 2013-11-09 10:47:39 +05:30
Tom Rothamel f9523a63f3 Use renpy.sh to generate documentation includes. 2013-11-08 20:51:36 -05:00
Tom Rothamel 524d9a77a2 Change the command line we use to run Ren'Py on Linux/Mac.
We remove one -O from the python command line. This prevents python
from removing docstrings, which means we can now use renpy.sh to
generate the documentation includes.
2013-11-08 20:49:22 -05:00
Tom Rothamel 7362261469 Ensure the environment contains only encoded strings.
Fixes #222.
2013-11-08 20:42:46 -05:00
Tom Rothamel f95ff022d4 Allow the title of the input prompt to be translated.
Fixes #221.
2013-11-07 18:02:25 -05:00
Tom Rothamel 25661cf69e Bump version to 6.16.1. 2013-11-07 17:41:45 -05:00
Tom Rothamel c2cb614750 Flag a string as translatable.
This doesn't have any actual effect, but makes the code read
better. That's because "Test" is already translated in a bunch
of other places.
2013-11-06 21:50:10 -05:00
Tom Rothamel e326c8d66e Document rare problems that can be worked around. 2013-11-06 21:30:22 -05:00
Tom Rothamel 030edbd5d8 Check in generated file. 2013-11-05 23:04:55 -05:00
Tom Rothamel 4bab0874b7 Document some functions. 2013-11-05 23:04:24 -05:00
Tom Rothamel 26b5b3a315 Update generated documentation. 2013-11-04 22:33:56 -05:00
Tom Rothamel bc94ee0cfa Use the build mechanism to build android distributions.
This allows creators to use build.classify to exclude files from
an Android distribution, or specify that files are Android-only.
2013-11-04 22:13:12 -05:00
Tom Rothamel a7aba0642b Do not fail if a module import can't be decoded as utf-8. 2013-11-03 21:04:26 -05:00
Tom Rothamel 956b9aff4a Merge pull request #218 from kyouryuukunn/fix_doc4
Fix doc4
2013-11-03 16:23:38 -08:00
kyouryuukunn 25bea3444f fix doc 2013-11-04 08:55:01 +09:00
kyouryuukunn 71ea4caeb9 fix doc 2013-11-04 08:46:33 +09:00
Tom Rothamel 9d354bfcf9 Merge pull request #216 from kyouryuukunn/fix_doc4
fix doc
2013-11-03 15:41:20 -08:00
kyouryuukunn b548b851ce fix doc 2013-11-04 08:37:20 +09:00
Tom Rothamel 6d2faf0d02 Fix typo.
Thanks to xavimat for the report.
2013-11-03 10:19:59 -05:00
Tom Rothamel 154c9f9d4a Merge pull request #214 from vbkaisetsu/fix-typo
fix typo
2013-11-03 07:11:15 -08:00
Koichi Akabe 579701cd30 fix typo 2013-11-03 23:53:33 +09:00
Tom Rothamel aacbd640e0 Support for building an android-suitable distribution.
This is the first step in using the build system to select the files
that will be included in an Android package.
2013-11-03 09:12:51 -05:00
Tom Rothamel 01e8296661 Add android package build information. 2013-11-02 23:45:19 -04:00
Tom Rothamel 32f2fc8126 Properly generate options.rpy for the japanese template.
The japanese template includes the build stuff, which also needs
to be updated when it's used to create a game.
2013-11-02 22:46:10 -04:00
Tom Rothamel 076e342f65 Fix a deadlock.
When the auto-save thread kicked off at the end of the game, it could
sit there waiting for a screenshot that would never be taken. This
fixes that, both by taking the screenshot and adding timeouts
that should prevent eternal pauses.
2013-11-02 21:59:24 -04:00
Tom Rothamel b186a703e2 Deal with symlinks leading to renpy.sh. 2013-11-01 18:11:08 -04:00
Tom Rothamel c9bfd1e377 Merge pull request #212 from vbkaisetsu/fix-typo-n-style
fix typos and style
2013-11-01 11:33:12 -07:00
Koichi Akabe 3365a073cb fix typos and style 2013-11-01 14:10:29 +09:00
Tom Rothamel 33e615bbf2 More use of templates directory. 2013-10-31 23:22:57 -04:00
Tom Rothamel a1adafa600 Merge pull request #211 from kyouryuukunn/update_translation2
update translation
2013-10-31 20:16:39 -07:00
kyouryuukunn 153d435571 update translation 2013-11-01 12:11:10 +09:00
Tom Rothamel 6a8ad2e80c Update the changelog to reflect the new translations. 2013-10-31 23:05:34 -04:00
Tom Rothamel 1395aba079 Minor editing of Japanese template. 2013-10-31 22:51:03 -04:00
Tom Rothamel 54e89492f3 Change the template selection message. 2013-10-31 22:32:22 -04:00
Tom Rothamel a69bb2edfa Fix after_checkout.sh. 2013-10-31 22:17:58 -04:00
Tom Rothamel b4b5a5fa59 Update console.bat, which provides a quick hacky way to run Ren'Py
in the windows console.
2013-10-31 22:15:17 -04:00
Tom Rothamel 389f3de5f4 Update .gitignore. 2013-10-31 22:14:59 -04:00
Tom Rothamel d457b345d7 Remove unused code from distribute.py. 2013-10-31 22:11:08 -04:00
Tom Rothamel ace21dca93 Generate the correct file. 2013-10-31 22:07:48 -04:00
Tom Rothamel 6d72e96bc2 Move the templates into the templates directory. 2013-10-31 22:03:40 -04:00
Tom Rothamel f4f6cd1215 Merge pull request #209 from kyouryuukunn/add_template-jp
add template-jp
2013-10-31 17:56:32 -07:00
Tom Rothamel 7de6e764a5 Merge pull request #208 from kyouryuukunn/update_translation
Update japanese translation
2013-10-31 17:56:15 -07:00
kyouryuukunn 4c9a56de23 Also translate screens.rpy 2013-11-01 09:27:43 +09:00
kyouryuukunn e54a18e4ad fix Japanese translation 2013-11-01 08:24:37 +09:00
kyouryuukunn 4183e60fc7 fix Japanese translation 2013-11-01 08:22:07 +09:00
kyouryuukunn b25d19509c use None language 2013-10-31 23:29:58 +09:00
kyouryuukunn d6c1444432 fix README.html in template-jp 2013-10-31 23:23:17 +09:00
kyouryuukunn edd1222e0e Update japanese translation 2013-10-31 22:08:59 +09:00
kyouryuukunn c0106e8bee add template-jp 2013-10-31 21:50:03 +09:00
Tom Rothamel 99be725ede Copy MTLc3m into the launcher directory.
It's possible to have the launcher without the tutorial or vice versa,
and some Linux distros package Ren'Py in this way. Copying the font
file ensures we work in this case.

This will increase the size of the zip file, but probably not of
the 7z or tar.bz2.
2013-10-31 07:54:44 -04:00
Tom Rothamel b0d04c3eac Remove executable bit from non-executable files. 2013-10-31 07:42:16 -04:00
Tom Rothamel b463732c42 Merge pull request #206 from kyouryuukunn/allow_translate_lancher
add the japanese translation for launcher
2013-10-31 04:39:41 -07:00
kyouryuukunn 5827121db5 fix 2013-10-31 17:04:19 +09:00
kyouryuukunn 7994a28363 remove #TODO from translation files 2013-10-31 16:56:04 +09:00
kyouryuukunn f6a9cb92b2 add make_style_backup in japanese/style.rpy 2013-10-31 13:10:01 +09:00
Tom Rothamel 446a6a640f Add the make_style_backup function, which needs to be
called from translations.

This ensures that we preserve the translation styles when
updating the theme.
2013-10-30 23:04:11 -04:00
Tom Rothamel 570a85e472 Change the default default_afm_time.
This makes the default behavior the one that's been used in all
quick_menu based games.
2013-10-30 21:53:32 -04:00
Tom Rothamel f1f6c84758 Merge pull request #204 from kyouryuukunn/change_options
Change options.rpy.
2013-10-30 17:44:51 -07:00
Tom Rothamel 3acf232631 Merge pull request #205 from vbkaisetsu/free-font
Replace mikachan with Motoya L cedar.

Fixes #186.
2013-10-30 16:43:07 -07:00
Tom Rothamel 0a2a2b35fe Merge pull request #200 from kyouryuukunn/enable_translation
Enable translation of  editor information in the launcher.
2013-10-29 19:51:36 -07:00
Tom Rothamel 0eedc523bb Allow the None language to have translations generated.
When we get "None" as the name of the language to generate translation
for, we generate it for the None language. We only generate string
translations, as that's the only thing that makes sense to translate
in the None language.

Fixes #203.
2013-10-29 22:37:42 -04:00
Koichi Akabe 5f59f4e126 remove mikachan license 2013-10-29 20:56:59 +09:00
kyouryuukunn 9d6f1e5110 add some transition configs to options.rpy 2013-10-29 20:39:59 +09:00
kyouryuukunn fc653f265c move default_afm_time and default_afm_enable from screens.rpy to options.rpy 2013-10-29 20:33:46 +09:00
Koichi Akabe 0f32739675 replace mikachan and vl-gothic with motoya 2013-10-29 19:11:05 +09:00
kyouryuukunn a333bcdc44 add the japanese translation for lancher 2013-10-29 18:14:59 +09:00
Tom Rothamel 3c3be23f1f Fix typo. 2013-10-28 23:52:33 -04:00
Tom Rothamel 6b91fb9847 Fix incorrect variable name. 2013-10-28 10:58:27 -04:00
Tom Rothamel 2219e4aebf Add missing comma. 2013-10-27 22:19:01 -04:00
Tom Rothamel 97b9c02aa8 Explicity document translate None.
This can be used to translate the interface when a game is
not originally written in English.
2013-10-27 22:07:39 -04:00
Tom Rothamel 864c959b4d List the new documentation pages, so people can find them. 2013-10-27 21:56:48 -04:00
Tom Rothamel 1a873b2d6a Re-organize file access functions. 2013-10-27 21:48:05 -04:00
Tom Rothamel e3d20823ae Document character callbacks.
Hey, that gets rid of the last long-standing warning in the
documentation build process. Bully!
2013-10-27 21:35:18 -04:00
Tom Rothamel 069b679e7d Move some things around in documentation. 2013-10-27 21:09:43 -04:00
Tom Rothamel 82b28baf87 Document conditional statements. 2013-10-27 21:03:52 -04:00
Tom Rothamel ffc150e569 Document the python statements. 2013-10-27 20:22:59 -04:00
Tom Rothamel 7bb1b85b66 Actually fix the doc warnings. 2013-10-27 20:22:21 -04:00
Tom Rothamel 827f597e07 Fix various doc errors/warnings. 2013-10-27 19:01:45 -04:00
Tom Rothamel b3f5e0c964 Generate an index of reserved names. 2013-10-27 18:45:10 -04:00
Tom Rothamel 3dec76ecf4 Remove linux-isms from doc build scripts. 2013-10-27 18:03:36 -04:00
Tom Rothamel 897caf7abc Edit environment variable documentation. 2013-10-27 18:00:12 -04:00
Tom Rothamel 0cac58e4a0 Edit keymap documentation. 2013-10-27 17:48:06 -04:00
Tom Rothamel cce422709b Check in source.
* Check in generated inc files.
* Fix permissions on .rst files.
2013-10-27 17:24:46 -04:00
Tom Rothamel b4d68fdb85 Document creator-defined statements.
Starting with the previous documentation, edited it, and
moved the various functions used into the public Ren'Py
API.
2013-10-27 17:18:29 -04:00
Tom Rothamel 1acf4e1243 Merge pull request #189 from kyouryuukunn/add_doc3
Add doc3
2013-10-27 08:22:23 -07:00
Tom Rothamel bb53094f66 Merge pull request #193 from kyouryuukunn/fix_message
Fix missing wxPython message.
2013-10-26 17:01:39 -07:00
Tom Rothamel 3d840f5c6d Properly deliver the error message when no build packages are selected.
Fixes #197.
2013-10-26 19:59:53 -04:00
Tom Rothamel db332a1f0f Merge pull request #191 from kyouryuukunn/allow_translate_voice_sustain
Allow the translation of "Voice Sustain".
2013-10-26 09:50:22 -07:00
Tom Rothamel 443a9001f5 Merge pull request #198 from vbkaisetsu/fix-doc
fix doc that doesn't contain needed line break
2013-10-26 09:49:06 -07:00
Tom Rothamel b7f9ad1808 Merge pull request #196 from xiongchiamiov/port-after-checkout
Port after_checkout.sh to non-Linux Unixes
2013-10-26 09:47:55 -07:00
Koichi Akabe 5aa7658cdf fix doc that doesn't contain needed line break 2013-10-26 22:21:01 +09:00
James Pearson 5dd2fae7a9 Port after_checkout.sh to non-Linux Unixes
`after_checkout.sh` has been using `readlink(1)` to get an absolute path (with
no symlinks) to the renpy root directory.  Unfortunately, `readlink -f` is a
GNU extension, and thus unavailable on the BSDs, including OS X.

There are[0] a number of options for those systems.  While an exec out to
Python seems like overkill (and is, in fact, a bit slow on my system), the
delay is small enough to not be a problem in a one-time script like this.
Additionally, it seemed silly to include a dozen-line shell function just for
this one usage.  Plus, we know for certain the user has Python, because
otherwise they couldn't run Ren'Py. ;)

I included the parens around `print` just to make sure it works with both
Python 2.x and 3.x.  I doubt this matters, but I figured I might as well.

If we stop caring about resolving symlinks, this entire line can be replaced
with `$PWD##*`. [1]

[0]: http://stackoverflow.com/q/1055671/120999
[1]: http://stackoverflow.com/a/1371283/120999
2013-10-25 22:51:48 -07:00
kyouryuukunn 3a5e571aea add doc about showing a window during a transition 2013-10-26 13:19:45 +09:00
Tom Rothamel 5ee03ed977 Reset _voice.tlid at each interaction.
This prevents voice sustain from carrying over through restarts,
as well as other potential minor problems.
2013-10-25 22:53:23 -04:00
Tom Rothamel 6710463318 Make installing the SDK conditional on RAPT being installed. 2013-10-25 21:15:41 -04:00
Tom Rothamel 893d36d4a3 Build RAPT as part of the Ren'Py release process.
This helps prevent me from accidentally forgetting to make a
RAPT release when Ren'Py code is updated.
2013-10-25 20:54:45 -04:00
Tom Rothamel 8f8b7e1fd9 Lock disk access. 2013-10-24 22:05:16 -04:00
Tom Rothamel e46c78fb36 Allow config.auto_voice to be a function.
This allows the patter to be changed when a translation is
being used, for example.
2013-10-24 21:39:08 -04:00
kyouryuukunn 5fd25efe0b allow_translate_lancher 2013-10-25 08:24:44 +09:00
kyouryuukunn 4d2f567bc4 enable translation of lancher 2013-10-24 22:01:23 +09:00
kyouryuukunn 0aa5016758 fix message 2013-10-24 20:25:19 +09:00
kyouryuukunn aac6291147 fix 2013-10-24 15:16:53 +09:00
Tom Rothamel 3cd4c18d6a Document text rendering change. 2013-10-23 23:10:19 -04:00
Tom Rothamel fe4bad7c11 Add mikachan license. 2013-10-23 22:55:55 -04:00
Tom Rothamel 251dbb2dee Mention the Japanese translation in the changelog. 2013-10-23 22:42:40 -04:00
Tom Rothamel 2ed6b14dc2 Remove files that are no longer needed. 2013-10-23 22:42:17 -04:00
Tom Rothamel a5162ac666 Fix question when overwriting a save. 2013-10-23 22:37:24 -04:00
Tom Rothamel 2a0adb3dbf Deal with {#comment} text tags. 2013-10-23 22:03:08 -04:00
kyouryuukunn 7edfe2cca8 add doc some statements equivalents 2013-10-24 01:13:02 +09:00
kyouryuukunn 757ecb37b4 add doc some pages 2013-10-24 01:12:37 +09:00
kyouryuukunn 0ebcd4a7f3 add doc about after_warp and some functions 2013-10-24 01:12:11 +09:00
kyouryuukunn ef1e6b0676 add the doc the page about creator defined statements 2013-10-24 01:10:10 +09:00
kyouryuukunn db94d03b64 move the window statement to displaying_images 2013-10-24 01:09:10 +09:00
kyouryuukunn 87b994b67a add the doc the page about developer tools and some functions 2013-10-24 01:08:40 +09:00
kyouryuukunn e5064f0794 add the doc the page about key maps 2013-10-24 00:55:22 +09:00
kyouryuukunn 6751172cfe add the doc the page about environment variables 2013-10-24 00:54:56 +09:00
Tom Rothamel 6c0d7802d9 Fix bad rst.x 2013-10-22 22:47:35 -04:00
Tom Rothamel ab7fd52fbd Fix syntax highlighting. 2013-10-22 22:45:44 -04:00
Tom Rothamel c6cb66dc61 Check in the generated docs. 2013-10-22 22:29:48 -04:00
Tom Rothamel 4b5ff75412 Document MultiPersistent. 2013-10-22 22:27:53 -04:00
Tom Rothamel 6dd5724be1 Split persistent out into its own rst file. 2013-10-22 22:11:13 -04:00
Tom Rothamel 32f3bbb67d Edit the label documentation. 2013-10-22 19:58:39 -04:00
Tom Rothamel 51cca16196 Merge pull request #188 from kyouryuukunn/add_doc3
More documentation from kyouryuukunn
2013-10-22 16:31:37 -07:00
kyouryuukunn 1fa87f753e add doc about init statement 2013-10-23 01:12:22 +09:00
kyouryuukunn 48cc2ae4cf add doc about special labels 2013-10-23 01:07:26 +09:00
kyouryuukunn e463c886cc add the doc about pause statement 2013-10-23 00:04:30 +09:00
kyouryuukunn 2efb7058f8 fix doc 2013-10-22 23:22:04 +09:00
kyouryuukunn 1c582ab21a add the doc persistent data 2013-10-22 23:14:33 +09:00
kyouryuukunn 4f30e7ac85 fix movie 2013-10-22 22:59:01 +09:00
kyouryuukunn 0e223e4c7a move renpy.call from other to statement_equivalents 2013-10-22 22:52:12 +09:00
kyouryuukunn b431d32825 move renpy.rollback from other to rollback 2013-10-22 22:49:15 +09:00
kyouryuukunn 8f41a8be99 add the doc the page about label 2013-10-22 22:47:27 +09:00
Tom Rothamel e9b260aae9 Merge pull request #187 from vbkaisetsu/fix-doc
fix reverted documentation errors
2013-10-22 06:41:39 -07:00
Koichi Akabe 2505af9985 fix reverted documentation errors
3fde29f reverted b074038 and 835fe5d because prior commits fixed auto generated files.
This commit fixes original rpy files.
2013-10-22 22:36:54 +09:00
kyouryuukunn 18fff449f3 add the doc the define statement 2013-10-22 21:51:43 +09:00
kyouryuukunn cc437eb47c add doc about base and game directorys 2013-10-22 20:45:51 +09:00
kyouryuukunn 14cd7e71b4 fix doc 2013-10-22 20:15:58 +09:00
kyouryuukunn 03499adffc add the doc the movie page 2013-10-22 20:12:26 +09:00
kyouryuukunn c738e39268 fix doc 2013-10-22 18:25:41 +09:00
Tom Rothamel 922af0d651 Testcase for vertical text.
This includes a pattern showing the center of the screen, and
vertical text starting at the center of the screen.
2013-10-22 00:50:13 -04:00
Tom Rothamel bfe91ab358 Center input prompts. 2013-10-22 00:47:12 -04:00
Tom Rothamel 0cfe13e214 Fix detection of RAPT when a .zip file exists in the Ren'Py directory. 2013-10-22 00:12:35 -04:00
Tom Rothamel 1246258f73 Use new screens for template. 2013-10-22 00:12:22 -04:00
Tom Rothamel 9655ef6a0d Merge pull request #184 from kyouryuukunn/fix_doc
fix doc
2013-10-21 20:22:17 -07:00
Tom Rothamel 3fde29fecd Edit audio.rst, and generate docs. 2013-10-21 23:09:42 -04:00
Tom Rothamel 04d5f374ef Merge pull request #185 from kyouryuukunn/add_doc2
Add documentation for audio statements.
2013-10-21 18:34:35 -07:00
Tom Rothamel acf0207f12 Re-init importer after a shift+R reload. 2013-10-21 21:09:25 -04:00
kyouryuukunn 2ebc6fb26e add index.rst audio page 2013-10-21 23:26:11 +09:00
kyouryuukunn 1c1c70e951 add doc audio page 2013-10-21 23:23:10 +09:00
kyouryuukunn 3ff3054f5d add doc about extend 2013-10-21 21:53:01 +09:00
kyouryuukunn eee003c184 fix doc 2013-10-21 21:49:42 +09:00
Tom Rothamel 661a884566 Encode the environment before using it.
This allows us to launch on windows.
2013-10-20 20:20:26 -04:00
Tom Rothamel e35860c235 Merge pull request #181 from kyouryuukunn/add_doc
Add doc about centered, vcentered, the play statement and the window statement
2013-10-20 13:38:37 -07:00
Tom Rothamel abfbebe996 Add disclaimer about vertical text. 2013-10-20 16:38:11 -04:00
Tom Rothamel 075555c294 Merge branch 'devel' of github.com:renpy/renpy into devel 2013-10-20 16:22:23 -04:00
Tom Rothamel 30eb973810 Add the renpy.is_seen function. 2013-10-20 16:15:36 -04:00
Tom Rothamel e60d332de6 Merge pull request #180 from kyouryuukunn/enable_translate
enable translate about screenshot
2013-10-20 13:12:05 -07:00
Tom Rothamel 835fe5db03 Merge pull request #182 from kyouryuukunn/fix_doc3
fix document
2013-10-20 13:11:34 -07:00
Tom Rothamel 9705ff7837 Merge pull request #183 from vbkaisetsu/tutorial-translation
update Japanese translation for tutorial game
2013-10-19 22:42:40 -07:00
Koichi Akabe 56aea76e25 update Japanese translation for tutorial game 2013-10-20 14:38:00 +09:00
Tom Rothamel f90ce46577 Use the (horizontal) lineskip as the advance value for vertical text.
This is an attempt at improving #179.
2013-10-20 00:51:46 -04:00
Tom Rothamel 5355f87e1d Deal with fonts with odd bounding-boxes.
Some truetype fonts have bounding boxes that contain the origin. (This
may be becuase of font design, auto-hinthing, or anti-aliasing.) We
deal with these fonts by rendering all the available pixels, even
those to the left of the origin.

This may make the actual area drawn to the screen bigger than the
displayable itself, so we deal with that when necessary.

Fixes #34.
2013-10-19 23:50:51 -04:00
kyouryuukunn fd3d21229b fix document 2013-10-20 03:03:31 +09:00
kyouryuukunn 1c819fec4d fix document 2013-10-20 02:44:44 +09:00
kyouryuukunn 1256909852 add doc about say statement with transition 2013-10-20 02:32:53 +09:00
kyouryuukunn cf933f2b37 add doc about window show hide 2013-10-20 02:14:55 +09:00
kyouryuukunn f3031a3cab add doc about the play statement 2013-10-20 02:13:18 +09:00
kyouryuukunn ddf5e82bd5 enable translate about screenshot 2013-10-19 11:04:38 +09:00
Tom Rothamel 95184394a4 Bump version to 6.16.0, and give it the name "In the Arena". 2013-10-18 21:27:23 -04:00
kyouryuukunn 8fbea7d329 add doc about centered and vcentered 2013-10-19 10:22:08 +09:00
Tom Rothamel 7fd9de354f Merge pull request #178 from kyouryuukunn/fix_document2
Fix documentation.
2013-10-18 17:27:37 -07:00
Tom Rothamel 70f5d97aef Document some bugfixes.
(I don't think it's worth going through and including them all.)
2013-10-17 23:35:12 -04:00
Tom Rothamel 1e8813e668 Document non-bugfix changes. 2013-10-17 23:23:21 -04:00
kyouryuukunn b07403855c fix document 2013-10-17 16:37:17 +09:00
Tom Rothamel 3ca12ea1d6 Start work on changelog. 2013-10-16 22:54:34 -04:00
Tom Rothamel 5ce0347e54 Merge pull request #176 from kyouryuukunn/fix_document
fix documentation about register_style_preference
2013-10-16 07:59:35 -07:00
kyouryuukunn ec2104d656 fix document about register_style_preference 2013-10-16 16:42:54 +09:00
Tom Rothamel e686b1228a Stop a playing movie while returning to the main menu. 2013-10-15 22:51:51 -04:00
Tom Rothamel 2667268f0d Merge pull request #173 from vbkaisetsu/rm-doc-ja-info-in-ja
remove doc-ja info in Japanese documentation
2013-10-15 19:48:32 -07:00
Tom Rothamel d3e5341c2d Fix problems with shift+R reloading. 2013-10-15 22:45:40 -04:00
Tom Rothamel 878aae6686 Use subtitle layout for the yes/no label text.
This prevents us from having widows when it's placed on two lines.
2013-10-15 22:36:54 -04:00
Tom Rothamel 2df74bb052 Make the rollback action sensitive iff Ren'Py can rollback. 2013-10-15 22:36:23 -04:00
Tom Rothamel 66f73ab014 Rearrange quick menu.
* Make the order more consistent.
* Add rollback ("Back")
* We don't need to make F.Skip so easy to disable - it's no worse
  than normal skip when skip unseen is off.
2013-10-15 22:24:30 -04:00
Tom Rothamel 8b7ccada49 Fast skip's get_sensitive should respect skip_unseen. 2013-10-15 22:22:16 -04:00
Tom Rothamel 27845de14b Require confirmation for fast skipping. Add to quick menu. 2013-10-15 22:16:19 -04:00
Tom Rothamel d59b550e04 Add the ability to ask for confirmation beofre skipping. 2013-10-15 20:29:42 -04:00
Tom Rothamel 4f26fd98a0 Tweak the test for slow skipping.
(config.skipping = True can also be used to turn on slow skipping.)
2013-10-15 20:05:03 -04:00
kyouryuukunn e3fe09a1ae distinguish fast and skip about get_selected() 2013-10-15 21:25:22 +09:00
Koichi Akabe 657b072ffa remove doc-ja info in Japanese documentation
Japanese doc information shouldn't be shown in Japanese, and it should be untranslatable text.
This commit adds two tags: language_LANGUAGE and gettext to support it.
2013-10-15 19:47:57 +09:00
Tom Rothamel def9865849 Store st properly in musicroom.
This prevents us from constantly resetting the last_playing
value, which caused many problems.
2013-10-14 21:55:38 -04:00
Tom Rothamel 98cff70eaf Fix typo. 2013-10-14 00:14:31 -04:00
Tom Rothamel 0c4eb467fe Make run.sh build in a different directory.
This prevents run.sh from conflicting with the official build.
2013-10-14 00:08:02 -04:00
Tom Rothamel 3e6ed73769 Add build.destination.
This lets us select the directory Ren'Py will build distributions
into.
2013-10-13 23:54:07 -04:00
Tom Rothamel 6c29701b0d Add a stop_action to the music room. 2013-10-13 20:25:08 -04:00
Tom Rothamel 273dbf780e If we can't get the date of a file, use now. 2013-10-13 20:20:02 -04:00
Tom Rothamel 35831b0ba7 MusicRoom support single track, loop, and shuffle options.
Also, add some testcases for MusicRoom.
2013-10-13 15:47:55 -04:00
Tom Rothamel 275f56a384 Yet another fix. 2013-10-13 01:43:39 -04:00
Tom Rothamel dbb6551d20 Today is not my night. (Fix previous commit.)0 2013-10-13 01:38:21 -04:00
Tom Rothamel 6d74eecc5c Also handle bad times. 2013-10-13 01:33:27 -04:00
Tom Rothamel eae4fd3f1b Add an error message if we can't stat a file going into a zip package. 2013-10-13 01:20:29 -04:00
Tom Rothamel 687b3ad1cd Merge pull request #163 from kyouryuukunn/hide_voice_preferences
Hide voice preferences if has_voice is false.
2013-10-12 08:45:57 -07:00
Tom Rothamel def04ec41c Right-click (the game_menu keys) answers no to a yes/no question
See the discussion at #166.
2013-10-11 23:04:13 -04:00
Tom Rothamel cdbc67f3d5 Do not save when renpy.store.main_menu is true.
We're using renpy.store.main_menu as the flag to tell us we're in
the main menu.
2013-10-11 00:43:08 -04:00
Tom Rothamel 2fd4f04bd8 Remove now-obsolete code. 2013-10-11 00:42:53 -04:00
Tom Rothamel 2edbd375fd Preserver store.main_menu when we enter a sub-menu from the main menu. 2013-10-11 00:42:14 -04:00
Tom Rothamel d685c3963a Fix code analysis problems. 2013-10-11 00:41:25 -04:00
Tom Rothamel 4218e2d950 Replace g. with gallery. in the _gallery screen.
Caught by kyouryuukunn here: https://github.com/renpy/renpy/pull/169#issuecomment-26032735
2013-10-10 23:14:50 -04:00
Tom Rothamel f96a6eee04 Regenerate documentation; Add link to Japanese documentation. 2013-10-10 10:42:09 -04:00
Tom Rothamel 12650ae9f7 Rewrite the gallery to allow us to advance forwards and backwards,
including advancing between buttons.
2013-10-10 00:37:55 -04:00
Tom Rothamel 9dee75e243 Merge pull request #168 from kyouryuukunn/gallery2
add gallery Previous,Next,Close,EnablePreview actions
2013-10-08 15:08:30 -07:00
Tom Rothamel e9a2579a64 Document Expansion APKs. 2013-10-08 17:57:10 -04:00
kyouryuukunn 7ca20972af change design of "_gallery" 2013-10-08 18:57:57 +09:00
Tom Rothamel 3b6c187ad1 Add support for placing google play info into the game script,
so it can be edited into the android code.

Also, various minor improvements.
2013-10-07 23:59:50 -04:00
Tom Rothamel c4e50aa700 More android documentation. 2013-10-07 21:55:07 -04:00
kyouryuukunn 4736b75cc7 change EnablePreview to ToggleSlideshow 2013-10-08 00:01:17 +09:00
kyouryuukunn 775a007a41 use "gallery" screen in place of "_gallery" if exist 2013-10-07 23:56:28 +09:00
kyouryuukunn ae64c83d01 add gallery Previous,Next,Close,EnablePreview actions 2013-10-07 22:06:00 +09:00
Tom Rothamel 723622b7f5 Expand LGPL correctly.
Fixes #167.
2013-10-07 01:27:18 -04:00
Tom Rothamel 7903ee1235 Update the Android documentation. 2013-10-07 01:16:20 -04:00
Tom Rothamel da6629c210 Move default preferences to their own section; document new defaults. 2013-10-06 22:12:51 -04:00
Tom Rothamel a994adfed7 Bars switch to the selected_hover when they're activated. 2013-10-06 10:55:57 -04:00
kyouryuukunn 66fc4eb85e also hide voice preferences in tutorial 2013-10-06 12:13:24 +09:00
Tom Rothamel 04d4a113ac Merge branch 'fix_and_unify_autosave' of github.com:kyouryuukunn/renpy into devel
This puts the checks as to if we can autosave into loadsave, rather than
scattering them in various places in the code.
2013-10-05 22:59:41 -04:00
kyouryuukunn fd6417bc3c hide voice preferences if has_voice is False 2013-10-06 11:49:59 +09:00
Tom Rothamel 74eee18415 Merge pull request #162 from vbkaisetsu/credit-indent
take care of indents in credits
2013-10-05 19:49:29 -07:00
Tom Rothamel d5d27062d7 Merge branch 'common_quit_process' of github.com:kyouryuukunn/renpy into devel
This always calls _quit (and quit if it exists) on quit, and it calls
_confirm_quit when the close button is clicked.
2013-10-05 21:44:59 -04:00
Tom Rothamel 4983e581ba Set config.has_voice to False by default.
At least until more than a few games have voices.
2013-10-05 21:02:53 -04:00
Tom Rothamel ab37f35cf8 Change the defaults for wait_for_voice and voice_sustain. 2013-10-05 20:54:29 -04:00
Tom Rothamel 4a4fbb9b3f Merge branch 'preferences.voice_sustain' of github.com:kyouryuukunn/renpy into devel 2013-10-05 20:37:24 -04:00
Tom Rothamel 9b2617e48c Merge pull request #161 from kyouryuukunn/rename_wait_voice
rename preferences("wait voice") to preferences("wait for voice")
2013-10-05 14:23:42 -07:00
kyouryuukunn 80c2d4cee2 rename preferences("wait voice") to preferences("wait for voice") 2013-10-06 03:03:38 +09:00
Tom Rothamel 6255511d02 Change the English name of wait_voice to "Wait for Voice." 2013-10-02 20:38:02 -04:00
Tom Rothamel e61ccaa6c8 Merge branch 'preferences.wait_voice' of github.com:kyouryuukunn/renpy into devel
Addds the wait_voice preference, which lets the user decide if auto-forward
mode will wait for voice to finish before advancing.
2013-10-02 20:31:11 -04:00
Tom Rothamel a2f31d5798 Use the relauch code to relaunch Ren'Py after a directx upgrade.
Fixes #159.
2013-10-01 23:19:03 -04:00
Tom Rothamel 29f22d1e79 Check in generated documentation. 2013-10-01 23:15:17 -04:00
Koichi Akabe fe78160055 take care for indents in credits 2013-10-02 10:20:41 +09:00
Tom Rothamel ba5d433dd1 Tweak the format of the translation documentation. 2013-10-01 19:59:04 -04:00
Tom Rothamel 3516bfc3c5 Merge pull request #158 from vbkaisetsu/doc-credits
add credits for documentation
2013-10-01 16:41:07 -07:00
Tom Rothamel 17fa9b6a14 Merge pull request #160 from kyouryuukunn/fix_document_slideaway
Fix documentation.
2013-10-01 16:36:55 -07:00
kyouryuukunn d0a8ab1976 fix_document 2013-10-01 19:29:09 +09:00
Tom Rothamel 051ca25fe7 Changes to persistence.
Automatically saving persistent data may be too slow (and might cause
a laptop's hard drive to spin up), so we now save it at shutdown or
when renpy.save_persistent is called.

We also allow creators to register a merge function that's called when
loading updated persistence data from disk.
2013-10-01 00:32:31 -04:00
Tom Rothamel 3e097905e3 Merge persistent data.
If a persistent updates while this code is running, we merge in
the data from that file, and restart the interaction.
2013-09-30 01:20:21 -04:00
Koichi Akabe d516731d24 set ??? as translator if translator-credits is not set 2013-09-29 14:30:34 +09:00
Koichi Akabe 2ddd430484 add credits for documentation 2013-09-29 13:57:54 +09:00
Tom Rothamel 099c8a53d3 Add NullAction.
This is used to make a button responsive to hover and unhover,
even if it doesn't have an action.
2013-09-28 23:43:58 -04:00
Tom Rothamel dcbf0d05e5 Merge pull request #156 from kyouryuukunn/fix_music_room
Fix music room.
2013-09-27 19:42:31 -07:00
kyouryuukunn dd70a48e3f fix_music_room 2013-09-28 10:16:06 +09:00
Tom Rothamel fe954a9e33 Export renpy.display.behavior.run as run_action, and make the music
room use it.
2013-09-27 18:05:58 -04:00
Tom Rothamel 5f5791a8e3 Add an action argument to MusicRoom.add, giving a list of actions
to be run when the music plays.
2013-09-27 18:00:14 -04:00
Tom Rothamel 1c90ce4dea Defer persistent loads. 2013-09-26 23:57:45 -04:00
kyouryuukunn 8bbd3a0000 add MusicRoom.add action keyword argument 2013-09-27 00:00:40 +09:00
Tom Rothamel 72e430ef49 Add persistent data to save locations; use for saving persistent.
Also, safely save normal save data.
2013-09-26 00:27:29 -04:00
Tom Rothamel 1c017a7589 Minor changes. 2013-09-24 23:20:53 -04:00
Tom Rothamel b9836c3eaa Move the preferences object into preferences.py. 2013-09-24 22:07:01 -04:00
kyouryuukunn 479f45f46f add preferences.voice_sustain 2013-09-25 10:53:36 +09:00
kyouryuukunn e3204944f6 add preferences.wait_voice 2013-09-25 10:41:57 +09:00
Tom Rothamel debcd2043b Merge branch 'master' into devel 2013-09-23 23:50:02 -04:00
Tom Rothamel b57ceed621 Deal with VIDEORESIZE events after mode changes on windows. 2013-09-23 23:48:43 -04:00
Tom Rothamel e6e0e80527 Deal better with text strings of insane lengths.
We now only display the first 8192 pixels of the text, but that's
better than crashing.

Fixes #141.
2013-09-23 21:24:47 -04:00
Tom Rothamel 3c0b350a4f Allow fast skipping to begin in the main menu.
Merges branch 'fast_skip_by_preference' of github.com:kyouryuukunn/renpy into devel
2013-09-23 19:43:43 -04:00
Tom Rothamel b9a065d48a Add a default for Transform.child_size.
Fixes #112.
2013-09-23 19:36:20 -04:00
kyouryuukunn c00e5abf80 set quit_action to ui.gamemenu(_quit_prompt) if more than v6.15.7 2013-09-23 11:56:00 +09:00
kyouryuukunn 2b3e06bf6d add quit label as the common quit process 2013-09-23 11:54:25 +09:00
Tom Rothamel 293b165aef After_rollback isn't a function. 2013-09-22 20:51:59 -04:00
Tom Rothamel 3dbbb3129c Merge branch 'master' into devel 2013-09-22 19:46:19 -04:00
Tom Rothamel 7b5378fcf1 Merge branch 'fix_screenshot' of github.com:kyouryuukunn/renpy into devel 2013-09-22 19:44:15 -04:00
Tom Rothamel 0e9d90e761 Merge pull request #131 from vbkaisetsu/fix-doc-errors
Fix documentation and jp translation errors.
2013-09-22 16:43:05 -07:00
Tom Rothamel d6acab6bd4 Merge pull request #149 from antoinentx/patch-1
Allow accent in the PATH of Ren'Py directory
2013-09-22 16:20:30 -07:00
Tom Rothamel b6096f6e95 Merge pull request #122 from kyouryuukunn/do_autosave_in_menu
Force autosave on menus and input.
2013-09-22 16:19:34 -07:00
Tom Rothamel 84f2da4a27 Add FileNewest, a function that is True for the newest file slot. 2013-09-21 23:32:03 -04:00
Tom Rothamel 3f6dfcb08e Rewrite the file actions.
The file actions now use the new save slot api, and there's a new
SaveJson function that can returns JSON information from a save
file.
2013-09-21 15:27:31 -04:00
Tom Rothamel f68e38b011 Add a new slot-based API over the new save slot stuff. 2013-09-21 14:35:53 -04:00
Tom Rothamel 7bb92310b5 Write json into save files.
This provides a way to associate arbitrary information with Ren'Py
save files. (For example, we could store which character's route
we're on, or scoring information.)

This introduces config.save_json_callbacks, which contains callback
functions that add information to the json callbacks.
2013-09-21 13:34:57 -04:00
Tom Rothamel ebab397625 Merge branch 'master' of github.com:renpy/renpy into devel 2013-09-20 23:20:43 -04:00
Tom Rothamel 776c0c2eb0 Merge pull request #136 from kyouryuukunn/has_quicksave
Change the default for config.has_quicksave to True.
2013-09-20 20:18:38 -07:00
kyouryuukunn c4bf9c07ca do autosave in menu and input 2013-09-20 18:17:48 +09:00
kyouryuukunn 8d8703bec6 set has_quicksave to False for old vertion 2013-09-20 11:42:53 +09:00
kyouryuukunn cdfb2a162e config.has_quicksave defaults to True 2013-09-20 11:42:53 +09:00
kyouryuukunn 842116e037 don't take_screenshot menu 2013-09-20 11:42:49 +09:00
kyouryuukunn 12403dab26 don't autosave during replay 2013-09-20 11:42:46 +09:00
kyouryuukunn 0dcb0e9fad unify checks for autosave about if in main menu or not 2013-09-20 11:42:46 +09:00
kyouryuukunn 450740318c fix autosave_frequency 2013-09-20 11:42:44 +09:00
kyouryuukunn 455940abda can also fast_skip by menu. 2013-09-20 11:42:41 +09:00
Tom Rothamel 38e2d1aaa9 Merge branch 'master' into devel 2013-09-19 21:35:36 -04:00
Tom Rothamel 8b640426a2 Merge pull request #137 from kyouryuukunn/toggle_page
add FilePageNext and FilePagePrevious a toggle function
2013-09-19 18:34:07 -07:00
Tom Rothamel 19a8c5512e Remove spurious variable from the namespace. 2013-09-18 23:12:12 -04:00
Tom Rothamel 9c50bce587 Remove os from the global namespace. 2013-09-18 23:06:01 -04:00
Tom Rothamel 3f06c505ce Document automatic voice. 2013-09-18 00:08:15 -04:00
Tom Rothamel 04e5b93f91 Normalize spacing. 2013-09-17 23:45:07 -04:00
Tom Rothamel 052ed5c8e3 Add the _auto_voice screen, which displays the file that the auto
voice system is trying to play.
2013-09-17 23:42:30 -04:00
Tom Rothamel 881a1a684f Implement config.auto_voice.
This uses the translation ids to look for a voice file, and
if found, plays the appropriate voice file.
2013-09-17 23:22:56 -04:00
Tom Rothamel 5bb41aa035 Add config.default_language, a non-sticky way to change the language
the game uses.
2013-09-16 23:34:49 -04:00
Tom Rothamel 62c69c8357 Fast skipping skips over renpy.pause.
Also, modified Skip(fast=True) to be enabled even if we haven't
read through the current scene.
2013-09-16 23:23:53 -04:00
Tom Rothamel 93c4da6216 Restart the interaction when a save cache slot changes.
This means that the load/save screens will live update when
a savefile appears or disappears.
2013-09-16 22:32:33 -04:00
Tom Rothamel 40cedc59cb Implement the scan thread for FileLocation.
The scan thread will scan the directory, getting mtimes. If a file
has changed its mtime (or appeared or disappeared), we clear out
the cached slot.
2013-09-16 22:15:54 -04:00
Tom Rothamel 3e44420023 Add a thread for scanning save locations. 2013-09-16 00:11:45 -04:00
Tom Rothamel 16aacb8fef Move the savelocation stuff into its own file. 2013-09-15 19:22:29 -04:00
Tom Rothamel 96a6d8cf3f unlink, rename, and cycle_saves now use the new location system. 2013-09-15 01:03:11 -04:00
Tom Rothamel 4e2d68bb4a Use the location system for load and can_load. 2013-09-15 00:09:24 -04:00
Tom Rothamel b3c96949d9 Make save scanning use locations.
This involves routing save slot listing and mtime/extra_info/screenshot
creation through the location system.
2013-09-14 23:45:22 -04:00
Tom Rothamel ac421dd971 Properly deal with percent-subtitutions in extend.
Fixes #150.
2013-09-13 23:27:58 -04:00
Tom Rothamel 97d7cf4f91 Test if a save location is active.
If we're installed to Program Files on Windows, the game-specific save
location is disabled since we can't write to it.
2013-09-12 23:51:30 -04:00
antoinentx cdfe0a5e9a Allow accent in the PATH of Ren'Py directory
Before the change, you get an uncaught exception when there is an accent in the Path. With that change, it works.
2013-09-12 18:11:09 +02:00
Tom Rothamel 170286d6ce Deal with python compile errors that contain non-ascii characters. 2013-09-11 23:28:11 -04:00
Tom Rothamel 5efa00294c No longer require the key exist for SetDict. 2013-09-11 21:02:13 -04:00
Tom Rothamel 1b47b4b420 Merge branch 'devel' of github.com:renpy/renpy into devel 2013-09-11 19:20:29 -04:00
Tom Rothamel 2868b5c12e Fix analysis problems caused by Persistent move. 2013-09-10 21:14:32 -04:00
Tom Rothamel 4cd7fbf7be Save to multiple locations.
The new MultiLocation lets us save to multiple locations. We now save
both to the user's save directory and a game-local save directory.

Also, refactored the save location in FileLocation into its own
method.
2013-09-10 18:24:42 -04:00
Tom Rothamel 1ed8555b4e Introduce new save location system.
While only used for saves at the moment, this system will eventually
let us use multiple locations.

This also introduces the SaveRecord object, which is where save data
is stored before we write it to disk.
2013-09-10 01:19:21 -04:00
Tom Rothamel a7655a6809 Remove the .pot files from source control.
These are generated files, so we probably don't want to put them
in git.
2013-09-08 23:13:19 -04:00
Tom Rothamel cd64333364 Allow the auto-save thread to free-run.
Now that we're almost always running on a multi-core device, there's
little reason not to just run the auto-save thread whenever we want.

This also will make some of the new save stuff simpler.
2013-09-08 23:11:40 -04:00
Tom Rothamel 4de7b7c504 Ensure Persistent exists in game, for backwards compatibility. 2013-09-08 02:46:50 -04:00
Tom Rothamel df4634bb6a Unicode-safety for extracted text. 2013-09-08 01:50:25 -04:00
Tom Rothamel fa338784d9 Add dialogue extraction GUI. 2013-09-08 01:25:14 -04:00
Tom Rothamel ca501350e1 Exclude extracted dialogue from builds. 2013-09-08 01:11:27 -04:00
Tom Rothamel db597d3a2b Add the dialogue command, which uses the translation system to
dump the game's dialogue to a file.
2013-09-08 01:07:46 -04:00
Tom Rothamel 49b3ba9e34 Add the _clear method to Persistent, providing a way to clear the
persistent data from within the game.

Also, move Persistent into persistent.py.
2013-09-07 23:48:48 -04:00
Tom Rothamel d7bdbe0420 Move things over to persistent.py
I'll be moving the Persistent class in there shortly.
2013-09-07 23:12:37 -04:00
Tom Rothamel 86f9c2e0fb Merge pull request #145 from vbkaisetsu/sphinx-gettext
Add documentation translation support.
2013-09-06 06:36:35 -07:00
Tom Rothamel c7d4b3e45a Merge pull request #143 from kyouryuukunn/fix_document2
Fix documentation.
2013-09-05 22:21:22 -07:00
Tom Rothamel 1bbe821474 Move MultiPersistent into its own file.
This cleans up loadsave for the new load/save system.
2013-09-06 00:10:11 -04:00
Tom Rothamel f9d46b15c8 Hotbars should be replaced.
Fix by Elmiwisa, from http://lemmasoft.renai.us/forums/viewtopic.php?f=32&t=23081.
2013-09-04 21:39:48 -04:00
kyouryuukunn 8d80e6399a fix document 2013-09-04 14:49:11 +09:00
kyouryuukunn e2ec6a57ff fix document 2013-09-04 14:39:26 +09:00
kyouryuukunn 661466df50 change 'toggle' to 'wrap' 2013-08-30 17:19:59 +09:00
kyouryuukunn 2d45112fc4 add FilePageNext and FilePagePrevious a toggle function 2013-08-30 17:19:58 +09:00
Tom Rothamel bf4b6c6dcd Merge pull request #134 from kyouryuukunn/fix_spell_miss
Use exit_yesno_prompt when exiting a yesno_prompt layout.
2013-08-29 20:42:49 -07:00
Tom Rothamel 170aa7fd79 Merge pull request #138 from kyouryuukunn/fix_spellmiss2
Fix duplicate code for Preference("skip", ...)
2013-08-29 20:41:58 -07:00
Tom Rothamel 825ad67c6e Merge pull request #140 from kyouryuukunn/fix_document
Fix documentation of Stop action.

It was the same as Play.
2013-08-29 20:40:32 -07:00
kyouryuukunn cfd2c66ea4 fix the document of Stop action 2013-08-30 00:40:29 +09:00
Tom Rothamel 323258443f Remove debug print. 2013-08-28 23:34:22 -04:00
Tom Rothamel e7c45601de Various android-related changes.
Get things running on OS X, and deal with missing java.
2013-08-28 23:06:47 -04:00
kyouryuukunn 61a18e8a0d fix spellmiss 2013-08-28 19:18:43 +09:00
Koichi Akabe 2c5b292888 update translation template and add missing files 2013-08-27 22:28:34 +09:00
Koichi Akabe 8a7538338f merge with trunk 2013-08-27 22:23:46 +09:00
Koichi Akabe c088bd776e fix spelling errors and invalid line breaking 2013-08-27 21:56:25 +09:00
Tom Rothamel 7a2232ee46 Remove debugging prints. 2013-08-27 01:39:05 -04:00
Tom Rothamel d52067a8d2 Run expensive operations (unpacks, file copies, and tars) in a
background thread.
2013-08-27 01:33:27 -04:00
Tom Rothamel 821344d0f2 Deal with empty strings, remove print. 2013-08-26 23:51:38 -04:00
Tom Rothamel 2536f989c7 Fixes to get working on Windows. 2013-08-26 23:15:27 -04:00
Tom Rothamel 828cb7dc3b Lexer.advance should return True and False.
This is documented in 000statements.rpy.
2013-08-24 10:35:32 -04:00
Tom Rothamel eac8b86fcf Use the text color for console output. 2013-08-24 00:51:41 -04:00
Tom Rothamel ef13f95cc2 Android improvements.
* Status feedback.
* Tooltips.
* Easy way to edit DownloaderService.java.
2013-08-23 00:02:10 -04:00
Tom Rothamel 3fa528cca1 Deal with \r when tailing output. 2013-08-22 01:07:17 -04:00
Tom Rothamel 585d2185bc The android install_sdk command works. 2013-08-22 00:59:39 -04:00
kyouryuukunn 817c43ed75 fix the spell miss in involke_yesno_prompt 2013-08-21 17:59:11 +09:00
Tom Rothamel ba75191d37 Downloader progress & android integration. 2013-08-21 00:12:32 -04:00
Tom Rothamel df8dfa0ddb Add a threaded downloader class.
This class supports getting progress and cancellation.
2013-08-20 23:55:36 -04:00
Tom Rothamel 093269ebcd Let the user cancel expensive operations. 2013-08-20 00:54:06 -04:00
Tom Rothamel 556ab591f9 Add a refresh button. 2013-08-19 23:50:14 -04:00
Tom Rothamel a91727c886 Minor code cleanups. 2013-08-19 22:58:16 -04:00
Tom Rothamel ff567918b1 Documentation inc changes (mostly whitespace-related). 2013-08-19 00:15:37 -04:00
Tom Rothamel e4f16f0d4e Documentation changes. 2013-08-19 00:15:24 -04:00
Tom Rothamel 98f3488520 Display text output while running android build commands. 2013-08-19 00:14:59 -04:00
Koichi Akabe ac7830ff50 update translation template 2013-08-09 20:43:31 +09:00
Koichi Akabe 12d60b81b8 merge with trunk 2013-08-09 20:36:48 +09:00
Koichi Akabe 8744787348 fix documentation errors
fix Japanese translation
2013-08-09 07:43:37 +09:00
Tom Rothamel fb37b423bd Add the ability to tail a file to the launcher.
Start work on running android processes.
2013-08-06 23:45:21 -04:00
Tom Rothamel 5e171a3ac9 Add _with_none to renpy.call_screen. 2013-08-04 20:44:23 -04:00
Tom Rothamel f8ffb1641f Create a GUI interface for Android.
Right now, we're able to configure an android build through the
launcher.
2013-07-29 10:56:52 -04:00
Tom Rothamel eb86123a47 Add interface.choice, which allow the launcher to ask a question. 2013-07-28 23:25:39 -04:00
Tom Rothamel cd1e6e7766 Ensure _main_menu is always set on the context. 2013-07-23 23:39:15 -04:00
Tom Rothamel 2fa5056ffd Cache compiled conditions for ConditionSwitch.
Previously, were re-compiling the expressions each time the
ConditionSwitch ran, which was leading to performance problem
when there was an absurdly large number of conditions.
2013-07-22 09:06:40 -04:00
Tom Rothamel ebe6b092a7 Allow _Config to be loaded.
This prevents SetVariable(config, "something", True) from causing a
problem. (This code is still not a good idea.)
2013-07-21 23:56:54 -04:00
Tom Rothamel 78a37292cc Add tooltips to describe the emulators. 2013-07-21 14:55:19 -04:00
Tom Rothamel 749a83d28b Basic android launcher screen, including the ability to launch
"emulators".
2013-07-21 14:41:44 -04:00
Tom Rothamel 91a8aa22b7 Fix autosave. 2013-07-20 12:40:10 -04:00
Tom Rothamel f7084cc07b Merge pull request #113 from kyouryuukunn/improve_autosave
Improve autosave
2013-07-18 18:56:00 -07:00
kyouryuukunn fa900941d5 Also check autosave() 2013-07-17 17:22:37 +09:00
kyouryuukunn 4da5cea54c Don't check when return the main menu 2013-07-17 17:11:35 +09:00
kyouryuukunn d83ab22d45 don't do the autosave in the main menu 2013-07-17 17:11:35 +09:00
kyouryuukunn c1229f65a1 Also do the autosave when return to the main menu 2013-07-17 17:11:34 +09:00
Tom Rothamel 8b8bf8d6aa Merge pull request #117 from kyouryuukunn/sensitive_of_skip
disable Skip if skip_unseen is false and in unseen
2013-07-16 18:21:05 -07:00
Tom Rothamel 08200cb5e8 Merge pull request #119 from kyouryuukunn/improve_fast_skip
Make fast-skipping stop when encountering unseen text, if skip_unseen is False.
2013-07-16 18:20:36 -07:00
Tom Rothamel bce5bb7d14 Display an indicator showing the TV-unsafe area. 2013-07-14 21:52:47 -04:00
kyouryuukunn f07fed4804 disable Skip if skip_unseen is false and in unseen 2013-07-15 10:39:43 +09:00
Tom Rothamel 8112bd6af4 Code to emulate a TV-based device.
Right now, this tries to emulate an OUYA console.
2013-07-14 20:27:06 -04:00
Tom Rothamel e0443512e6 Implement the touch-screen emulator.
This more-or-less emulates an android device.
2013-07-14 19:55:28 -04:00
Tom Rothamel cbfe972ace Start work on interface emulator. 2013-07-14 19:03:33 -04:00
kyouryuukunn e078700af9 stop fastskip in unseen 2013-07-15 03:08:08 +09:00
Tom Rothamel 0c74863be9 Remove spurious whitespace. 2013-07-14 12:00:15 -04:00
Tom Rothamel 2779280b83 Make ADVCharacter.__str__ handle dynamic characters. 2013-07-14 11:36:16 -04:00
Tom Rothamel 176c86a73e Merge pull request #120 from ckoepp/master
Added __str__ to ADVCharacter class
2013-07-14 08:32:57 -07:00
Christian Koepp 694c2d9e85 added __str__ to ADVCharacter class 2013-07-14 09:57:26 +02:00
Tom Rothamel 7fd7dba118 Fix rollback in Android movie playback. 2013-07-14 01:29:58 -04:00
Tom Rothamel b998b4c967 Add video playback for Android. 2013-07-14 01:06:29 -04:00
Tom Rothamel 206eb4d2cb Update docs. 2013-07-12 22:59:26 -04:00
Tom Rothamel bbee178ff9 Merge pull request #115 from kyouryuukunn/add_funcs_to_MusicRoom
Add funcs to music room
2013-07-12 19:54:27 -07:00
Tom Rothamel 67775552b5 Improve the gallery get_fraction function. 2013-07-12 22:50:23 -04:00
Tom Rothamel 54f02890cb Merge pull request #114 from kyouryuukunn/add_gallery_fraction
add Gallery get_fraction
2013-07-12 19:35:03 -07:00
Tom Rothamel b7bce5efd2 OUYA detection, and a new set of variants, based on Android screen sizes. 2013-07-07 19:47:49 -04:00
Tom Rothamel 8f13c8e37a Fix renpy.list_files when accessing an android APK.
Fixes #116.
2013-07-07 15:49:50 -04:00
kyouryuukunn eccc452d80 add MusicRoom TogglePlay 2013-06-29 12:43:48 +09:00
kyouryuukunn 7a1c36f8db add MusicRoom random play 2013-06-29 12:42:23 +09:00
kyouryuukunn 654095786e add Gallery get_fraction 2013-06-29 10:52:57 +09:00
Koichi Akabe 73d36d34c8 update translation templates 2013-04-21 18:31:44 +09:00
Koichi Akabe 4126b0574a merge with trunk 2013-04-21 18:15:26 +09:00
Koichi Akabe e2bc9aa6d0 update translation templates 2013-03-08 20:05:53 +09:00
Koichi Akabe 670721ac52 merge with trunk 2013-03-08 20:02:42 +09:00
Koichi Akabe e195ea5221 update translation template 2013-02-28 22:41:41 +09:00
Koichi Akabe ea1ebb4bba merge with trunk 2013-02-28 22:40:44 +09:00
Koichi Akabe 6a60039aac update translation template 2013-02-25 09:07:56 +09:00
Koichi Akabe 96485ec8d0 merge with trunk 2013-02-25 09:05:30 +09:00
Koichi Akabe 2b9621e7cf add translation template 2013-02-21 10:01:56 +09:00
Koichi Akabe 747bf2c3c1 add gettext target to support documentation translation 2013-02-21 09:56:19 +09:00
400 changed files with 22229 additions and 12749 deletions
+5 -6
View File
@@ -14,8 +14,10 @@ cache
log.txt
errors.txt
traceback.txt
styles.txt
/android
/build
/dist
/dists
@@ -23,12 +25,8 @@ styles.txt
/jedit
/lint.txt
/renpy.code
/testing*
/*testing*
/screenshot*
/Microsoft.VC90.CRT.manifest
/console.exe
/msvcr90.dll
/python27.dll
/renpy.exe
/lib
/lib.old
@@ -38,7 +36,7 @@ styles.txt
/.project
/.settings
/LICENSE.txt
/template/README.html
/templates/english/README.html
/the_question/README.html
/tutorial/README.html
/renpy/angle/*.pyx
@@ -52,3 +50,4 @@ styles.txt
dl
renpy/vc_version.py
.externalToolBuilders
rapt-*
+22 -2
View File
@@ -3,6 +3,7 @@
import argparse
import os
import subprocess
import sys
from renpy import version_tuple #@UnresolvedImport
@@ -31,7 +32,26 @@ elif args.experimental:
else:
links = [ ]
tag = False
def check_dirty():
if args.no_tag:
return
if subprocess.check_call([ "git", "diff", "--quiet", "HEAD" ]):
print "Directory not checked in: {}".format(os.getcwd())
sys.exit(1)
os.chdir("/home/tom/ab/renpy")
check_dirty()
os.chdir("/home/tom/ab/renpy/android")
check_dirty()
subprocess.check_call([ "./add_renpy.sh", short_version ])
if not args.no_tag:
subprocess.check_call([ "git", "tag", "-a", "rapt-" + version, "-m", "Tagging RAPT release." ])
os.chdir("/home/tom/ab/renpy/dl")
for i in links:
@@ -41,7 +61,7 @@ for i in links:
os.chdir("/home/tom/ab/renpy")
if tag and not args.no_tag:
if tag and not args.no_tag:
cmd = [ "git", "tag", "-a", version, "-m", "Ren'Py " + version ]
subprocess.check_call(cmd)
+3 -3
View File
@@ -1,10 +1,10 @@
#!/bin/bash
ROOT="$(dirname $(readlink -f $0))"
ROOT="$(dirname $(python -c "import os;print(os.path.realpath('$0'))"))"
ln -s "$ROOT/help.html" "$ROOT/tutorial/README.html"
ln -s "$ROOT/help.html" "$ROOT/the_question/README.html"
ln -s "$ROOT/help.html" "$ROOT/template/README.html"
ln -s "$ROOT/help.html" "$ROOT/templates/english/README.html"
ln -s "$ROOT/sphinx/source/license.rst" "$ROOT/LICENSE.txt"
ln -s "$ROOT/sphinx/build/html" "$ROOT/doc"
@@ -13,4 +13,4 @@ if [ "$1" != "" ]; then
ln -s "$1/lib" "$ROOT/lib"
ln -s "$1/renpy.app" "$ROOT"
ln -s "$1/renpy.exe" "$ROOT"
fi
fi
+1 -1
View File
@@ -1,4 +1,4 @@
# This file sets up the normal python modules. It's used to help eclipse
# This file sets up the normal python modules. It's used to help eclipse
# do type detection, by providing an importable version of the cython code.
from distutils.core import setup
+2
View File
@@ -0,0 +1,2 @@
lib\windows-i686\python.exe -OO renpy.py
pause
+79 -146
View File
@@ -1,84 +1,16 @@
#!/home/tom/bin/renpython -OO
# Builds a distributions of Ren'Py.
# Builds a distribution of Ren'Py.
import sys
import os
import zipfile
import tarfile
import zlib
import compileall
import shutil
import subprocess
import time
import argparse
CWD = os.getcwdu()
zlib.Z_DEFAULT_COMPRESSION = 9
# Gets the data for the given file.
def data(fn):
rv = file(fn, "rb").read()
if fn.startswith("renpy.app"):
return rv
if fn.endswith(".rpy") or fn.endswith(".rpym") or fn.endswith(".py") or fn.endswith(".txt"):
rv = rv.replace("\n", "\r\n")
rv = rv.replace("\r\r\n", "\r\n")
return rv
def tarup(filename, prefix, files):
tf = tarfile.open(filename, "w:bz2")
tf.dereference = True
sys.stdout.write(filename)
sys.stdout.flush()
for fn in files:
sys.stdout.write(".")
sys.stdout.flush()
tf.add(fn, prefix + "/" + fn, False)
sys.stdout.write("\n")
tf.close()
# Creates a zip file.
def zipup(filename, prefix, files):
zf = zipfile.ZipFile(filename, "w")
sys.stdout.write(filename)
sys.stdout.flush()
for fn in files:
sys.stdout.write(".")
sys.stdout.flush()
zi = zipfile.ZipInfo(prefix + "/" + fn)
st = os.stat(fn)
zi.date_time = time.gmtime(st.st_mtime)[:6]
zi.compress_type = zipfile.ZIP_DEFLATED
zi.create_system = 3
zi.external_attr = long(st.st_mode) << 16
zf.writestr(zi, data(fn))
zf.close()
sys.stdout.write("\n")
sys.stdout.flush()
def copy_tutorial_file(src, dest):
"""
Copies a file from src to dst. Lines between "# tutorial-only" and
@@ -87,10 +19,10 @@ def copy_tutorial_file(src, dest):
sf = open(src, "rb")
df = open(dest, "wb")
# True if we want to copy the line.
copy = True
for l in sf:
if "# tutorial-only" in l:
copy = False
@@ -99,49 +31,20 @@ def copy_tutorial_file(src, dest):
else:
if copy:
df.write(l)
sf.close()
df.close()
def tree(root):
rv = [ ]
for dirname, dirs, filenames in os.walk(root):
if "saves" in dirs:
dirs.remove("saves")
if ".svn" in dirs:
dirs.remove(".svn")
if ".doctrees" in dirs:
dirs.remove(".doctrees")
for f in filenames:
if f[-1] == '~' or f[0] == '.':
continue
if f.endswith(".bak") or f.endswith(".pyc"):
continue
if f == "semantic.cache":
continue
if "libSDL_mixer" in f or "mixer_music" in f:
continue
rv.append(dirname + "/" + f)
return rv
def main():
if not sys.flags.optimize:
raise Exception("Not running with python optimization.")
ap = argparse.ArgumentParser()
ap.add_argument("version")
ap.add_argument("--fast", action="store_true")
args = ap.parse_args()
# Revision updating is done early, so we can do it even if the rest
@@ -157,10 +60,10 @@ def main():
s = subprocess.check_output([ "git", "describe", "--tags", "--dirty", "--match", match_version ])
parts = s.strip().split("-")
vc_version = int(parts[1])
if parts[-1] == "dirty":
vc_version += 1
with open("renpy/vc_version.py", "w") as f:
f.write("vc_version = {}".format(vc_version))
@@ -169,62 +72,83 @@ def main():
# Check that the versions match.
full_version = ".".join(str(i) for i in renpy.version_tuple) #@UndefinedVariable
if args.version != "experimental" and not full_version.startswith(args.version):
raise Exception("The command-line and Ren'Py versions do not match.")
if args.version != "experimental" and not full_version.startswith(args.version):
raise Exception("The command-line and Ren'Py versions do not match.")
print "Version {} ({})".format(args.version, full_version)
print "Version {} ({})".format(args.version, full_version)
# Copy over the screens, to keep them up to date.
copy_tutorial_file("tutorial/game/screens.rpy", "template/game/screens.rpy")
copy_tutorial_file("tutorial/game/screens.rpy", "templates/english/game/screens.rpy")
# Compile all the python files.
compileall.compile_dir("renpy/", ddir="renpy/", force=1, quiet=1)
# Compile the various games
if not args.fast:
for i in [ 'tutorial', 'launcher', 'template', 'the_question' ]:
for i in [ 'tutorial', 'launcher', 'templates/english', 'templates/japanese', 'the_question' ]:
print "Compiling", i
subprocess.check_call(["./renpy.sh", i, "compile" ])
subprocess.check_call(["./renpy.sh", i, "quit" ])
# The destination directory.
destination = os.path.join("dl", args.version)
if not os.path.exists(destination):
os.makedirs(destination)
if args.fast:
cmd = [
"./renpy.sh",
"launcher",
"distribute",
"launcher",
"./renpy.sh",
"launcher",
"distribute",
"launcher",
"--package",
"sdk",
"--destination",
"--destination",
destination,
"--no-update",
]
else:
cmd = [
"./renpy.sh",
"launcher",
"distribute",
"launcher",
"--destination",
"./renpy.sh",
"launcher",
"distribute",
"launcher",
"--destination",
destination,
]
print
subprocess.check_call(cmd)
# Kick off the rapt build.
if not args.fast:
out = open("/tmp/rapt_build.txt", "wb")
print os.getcwd()
print("Building RAPT in the background.")
android = os.path.abspath("android")
rapt_build = subprocess.Popen([
os.path.join(android, "build_renpy.sh"),
zip_version
],
cwd = android,
stdout=out,
stderr=out)
else:
rapt_build = None
# Sign the update.
if not args.fast:
subprocess.check_call([
"scripts/sign_update.py",
"/home/tom/ab/keys/renpy_private.pem",
subprocess.check_call([
"scripts/sign_update.py",
"/home/tom/ab/keys/renpy_private.pem",
os.path.join(destination, "updates.json"),
])
@@ -238,49 +162,58 @@ def main():
with open("7z.sfx", "rb") as f:
sfx = f.read()
os.chdir(destination)
if os.path.exists(sdk):
shutil.rmtree(sdk)
subprocess.check_call([ "unzip", "-q", sdk + ".zip" ])
if os.path.exists(sdk + ".7z"):
os.unlink(sdk + ".7z")
sys.stdout.write("Creating -sdk.7z")
p = subprocess.Popen([ "7z", "a", sdk +".7z", sdk], stdout=subprocess.PIPE)
for i, _l in enumerate(p.stdout):
if i % 10 != 0:
continue
sys.stdout.write(".")
sys.stdout.flush()
if p.wait() != 0:
raise Exception("7z failed")
with open(sdk + ".7z", "rb") as f:
data = f.read()
with open(sdk + ".7z.exe", "wb") as f:
f.write(sfx)
f.write(data)
os.unlink(sdk + ".7z")
shutil.rmtree(sdk)
else:
os.chdir(destination)
if os.path.exists(sdk + ".7z.exe"):
os.unlink(sdk + ".7z.exe")
print
print
if rapt_build:
code = rapt_build.wait()
if code:
print "RAPT build failed. The output is in /tmp/rapt_build.txt"
else:
print "RAPT build succeeded."
print
print "Did you run me with renpython -OO?"
print "Did you update renpy.py and launcher/script_version.rpy?"
if __name__ == "__main__":
main()
+9 -9
View File
@@ -1,27 +1,27 @@
# Copyright 2004-2012 Tom Rothamel <pytom@bishoujo.us>
# See LICENSE.txt for license details.
# Checks for various abilities that might be taken away from us by
# Checks for various abilities that might be taken away from us by
# redistributors.
init 1 python in ability:
from store import config
import store
import store.updater as updater
import os
EXECUTABLES = [ "renpy.exe", "renpy.app", "renpy.sh" ]
# can_distribute - True if we can distribute
# can_distribute - True if we can distribute
for i in EXECUTABLES:
if not os.path.exists(os.path.join(config.renpy_base, i)):
can_distribute = False
else:
can_distribute = True
# can_update - True if we can update.
can_update = updater.can_update() or (store.UPDATE_SIMULATE is not None)
+8 -8
View File
@@ -2,30 +2,30 @@
# See LICENSE.txt for license details.
screen about:
$ version = renpy.version()
frame:
style_group "l"
style "l_root"
window:
xfill True
has vbox xfill True
add "logo.png" xalign 0.5 yoffset -5
null height 15
text _("[version!q]") xalign 0.5 bold True
null height 20
textbutton _("View license") action interface.OpenLicense() xalign 0.5
textbutton _("Back") action Jump("front_page") style "l_left_button"
label about:
call screen about
+9 -9
View File
@@ -3,34 +3,34 @@ label add_file:
python hide:
import os
import codecs
filename = interface.input(_("FILENAME"), _("Enter the name of the script file to create."), filename="withslash", cancel=Jump("navigation"))
if "." in filename and not filename.endswith(".rpy"):
interface.error(_("The filename must have the .rpy extension."), label="navigation")
elif "." not in filename:
filename += ".rpy"
path = os.path.join(project.current.gamedir, filename)
dir = os.path.dirname(path)
if os.path.exists(path):
interface.error(_("The file already exists."), label="navigation")
contents = u"\uFEFF"
contents += _("# Ren'Py automatically loads all script files ending with .rpy. To use this\n# file, define a label and jump to it from another file.\n")
contents += "\n"
try:
os.makedirs(dir)
except:
pass
contents = u"\uFEFF"
contents += _("# Ren'Py automatically loads all script files ending with .rpy. To use this\n# file, define a label and jump to it from another file.\n")
with open(path, "wb") as f:
f.write(contents.encode("utf-8"))
jump navigation_refresh
+492
View File
@@ -0,0 +1,492 @@
# Copyright 2004-2013 Tom Rothamel <pytom@bishoujo.us>
# See LICENSE.txt for license details.
init python:
ANDROID_NO_RAPT = 0
ANDROID_NO_SDK = 1
ANDROID_NO_KEY = 2
ANDROID_NO_CONFIG = 3
ANDROID_OK = 4
NO_RAPT_TEXT = _("To build Android packages, please download RAPT (from {a=http://www.renpy.org/dl/android}here{/a}), unzip it, and place it into the Ren'Py directory. Then restart the Ren'Py launcher.")
NO_SDK_TEXT = _("RAPT has been installed, but you'll need to install the Android SDK before you can build Android packages. Choose Install SDK to do this.")
NO_KEY_TEXT = _("RAPT has been installed, but a key hasn't been configured. Please create a new key, or restore android.keystore.")
NO_CONFIG_TEXT = _("The current project has not been configured. Use \"Configure\" to configure it before building.")
OK_TEXT = _("Choose \"Build\" to build the current project, or attach an Android device and choose \"Build & Install\" to build and install it on the device.")
PHONE_TEXT = _("Attempts to emulate an Android phone.\n\nTouch input is emulated through the mouse, but only when the button is held down. Escape is mapped to the menu button, and PageUp is mapped to the back button.")
TABLET_TEXT = _("Attempts to emulate an Android tablet.\n\nTouch input is emulated through the mouse, but only when the button is held down. Escape is mapped to the menu button, and PageUp is mapped to the back button.")
OUYA_TEXT = _("Attempts to emulate an OUYA console.\n\nController input is mapped to the arrow keys, Enter is mapped to the select button, Escape is mapped to the menu button, and PageUp is mapped to the back button.")
INSTALL_SDK_TEXT = _("Downloads and installs the Android SDK and supporting packages. Optionally, generates the keys required to sign the package.")
CONFIGURE_TEXT = _("Configures the package name, version, and other information about this project.")
PLAY_KEYS_TEXT = _("Opens the file containing the Google Play keys in the editor.\n\nThis is only needed if the application is using an expansion APK. Read the documentation for more details.")
BUILD_TEXT = _("Builds the Android package.")
BUILD_AND_INSTALL_TEXT = _("Builds the Android package, and installs it on an Android device connected to your computer.")
import subprocess
import re
import os
import json
def find_rapt():
global RAPT_PATH
candidates = [ ]
for fn in os.listdir(config.renpy_base):
if not fn.startswith("rapt-"):
continue
if not os.path.isdir(os.path.join(config.renpy_base, fn)):
continue
try:
version = fn[5:]
version = tuple(int(i) for i in version.split('.'))
except ValueError:
continue
candidates.append((version, fn))
if not candidates:
RAPT_PATH = None
else:
RAPT_PATH = os.path.join(config.renpy_base, candidates[-1][1])
import sys
sys.path.insert(0, os.path.join(RAPT_PATH, "buildlib"))
find_rapt()
import threading
if RAPT_PATH:
import rapt
import rapt.build
import rapt.configure
import rapt.install_sdk
import rapt.plat
else:
rapt = None
def AndroidState():
"""
Determines the state of the android install, and returns it.
"""
if RAPT_PATH is None:
return ANDROID_NO_RAPT
if not os.path.exists(os.path.join(RAPT_PATH, "android-sdk/extras/google/play_licensing")):
return ANDROID_NO_SDK
if not os.path.exists(os.path.join(RAPT_PATH, "android.keystore")):
return ANDROID_NO_KEY
if not os.path.exists(os.path.join(project.current.path, ".android.json")):
return ANDROID_NO_CONFIG
return ANDROID_OK
def AndroidStateText(state):
"""
Returns text corresponding to the state.
"""
if state == ANDROID_NO_RAPT:
return NO_RAPT_TEXT
if state == ANDROID_NO_SDK:
return NO_SDK_TEXT
if state == ANDROID_NO_KEY:
return NO_KEY_TEXT
if state == ANDROID_NO_CONFIG:
return NO_CONFIG_TEXT
if state == ANDROID_OK:
return OK_TEXT
def AndroidIfState(state, needed, action):
"""
If `state` is `needed` or better, `action` is returned. Otherwise,
returns None, disabling the button.
"""
if state >= needed:
return action
else:
return None
class AndroidInterface(object):
def __init__(self):
self.process = None
self.filename = project.current.temp_filename("android.txt")
self.info_msg = ""
with open(self.filename, "w"):
pass
def info(self, prompt):
self.info_msg = prompt
interface.processing(prompt, pause=False)
def yesno(self, prompt, submessage=None):
return interface.yesno(prompt, submessage=submessage)
def yesno_choice(self, prompt, default=None):
choices = [ (True, "Yes"), (False, "No") ]
return interface.choice(prompt, choices, default)
def terms(self, url, prompt):
submessage = _("{a=%s}%s{/a}") % (url, url)
return interface.yesno(prompt, submessage=submessage)
def input(self, prompt, empty=None):
if empty is None:
empty = ''
while True:
rv = interface.input(_("QUESTION"), prompt, default=empty, cancel=Jump("android"))
rv = rv.strip()
if rv:
return rv
def choice(self, prompt, choices, default):
return interface.choice(prompt, choices, default, cancel=Jump("android"))
def fail(self, prompt):
prompt = re.sub(r'(http://\S+)', r'{a=\1}\1{/a}', prompt)
interface.error(prompt, label="android")
def success(self, prompt):
interface.info(prompt, pause=False)
def final_success(self, prompt):
interface.info(prompt, label="android")
def call(self, cmd, cancel=False, use_path=False):
if not use_path:
cmd = [ rapt.plat.path(cmd[0]) ] + list(cmd[1:])
self.cmd = cmd
f = open(self.filename, "a")
f.write("\n\n\n")
if cancel:
cancel_action = self.cancel
else:
cancel_action = None
startupinfo = None
if renpy.windows:
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
try:
interface.processing(self.info_msg, show_screen=True, cancel=cancel_action)
self.process = subprocess.Popen(cmd, cwd=RAPT_PATH, stdout=f, stderr=f, startupinfo=startupinfo)
renpy.call_screen("android_process", interface=self)
finally:
f.close()
interface.hide_screen()
self.process = None
def check_process(self):
rv = self.process.poll()
if rv is not None:
if rv:
raise subprocess.CalledProcessError(rv, self.cmd)
else:
return True
def download(self, url, dest):
try:
d = Downloader(url, dest)
cancel_action = [ d.cancel, Jump("android") ]
interface.processing(self.info_msg, show_screen=True, cancel=cancel_action, bar_value=DownloaderValue(d))
ui.timer(.1, action=d.check, repeat=True)
ui.interact()
finally:
interface.hide_screen()
def background(self, f):
try:
t = threading.Thread(target=f)
t.start()
interface.processing(self.info_msg, show_screen=True)
while t.is_alive():
renpy.pause(0)
t.join(0.25)
finally:
interface.hide_screen()
def cancel(self):
if self.process:
self.process.terminate()
renpy.jump("android")
class AndroidBuild(Action):
"""
Activates an Android build process.
"""
def __init__(self, label):
self.label = label
def __call__(self):
renpy.jump(self.label)
class LaunchEmulator(Action):
def __init__(self, emulator, variants):
self.emulator = emulator
self.variants = variants
def __call__(self):
env = {
"RENPY_EMULATOR" : self.emulator,
"RENPY_VARIANT" : self.variants,
}
p = project.current
p.launch(env=env)
def update_android_json():
"""
Updates .android.json to include the google play information.
"""
project.current.update_dump(True)
build = project.current.dump["build"]
filename = os.path.join(project.current.path, ".android.json")
with open(filename, "r") as f:
android_json = json.load(f)
if "google_play_key" in build:
android_json["google_play_key"] = build["google_play_key"]
else:
android_json.pop("google_play_key", None)
if "google_play_salt" in build:
if len(build["google_play_salt"]) != 20:
raise Exception("build.google_play_salt must be exactly 20 bytes long.")
android_json["google_play_salt"] = ", ".join(str(i) for i in build["google_play_salt"])
else:
android_json.pop("google_play_salt", None)
with open(filename, "w") as f:
json.dump(android_json, f)
def android_build(command):
"""
This actually builds the package.
"""
update_android_json()
dist = project.current.temp_filename("android.dist")
if os.path.exists(dist):
shutil.rmtree(dist)
distribute.Distributor(project.current,
reporter=distribute.GuiReporter(),
packages=[ 'android' ],
build_update=False,
noarchive=True,
packagedest=dist,
report_success=False,
)
with interface.nolinks():
rapt.build.build(AndroidInterface(), dist, command)
# The android support can stick unicode into os.environ. Fix that.
init 100 python:
for k, v in list(os.environ.items()):
if isinstance(v, str):
os.environ[k] = renpy.fsencode(v)
screen android_process(interface):
zorder 100
default ft = FileTail(interface.filename)
text "[ft.text!q]":
size 14
color TEXT
font "Roboto-Light.ttf"
xpos 75
ypos 350
timer .1 action interface.check_process repeat True
timer .2 action ft.update repeat True
screen android:
default tt = Tooltip(None)
$ state = AndroidState()
frame:
style_group "l"
style "l_root"
window:
has vbox
label _("Android: [project.current.name!q]")
add HALF_SPACER
hbox:
# Left side.
frame:
style "l_indent"
xmaximum ONEHALF
xfill True
has vbox
add SEPARATOR2
frame:
style "l_indent"
has vbox
text _("Emulation:")
add HALF_SPACER
frame style "l_indent":
has vbox
textbutton _("Phone"):
action LaunchEmulator("touch", "small phone touch android")
hovered tt.Action(PHONE_TEXT)
textbutton _("Tablet"):
action LaunchEmulator("touch", "medium tablet touch android")
hovered tt.Action(TABLET_TEXT)
textbutton _("Television / OUYA"):
action LaunchEmulator("tv", "small tv ouya android")
hovered tt.Action(OUYA_TEXT)
add SPACER
add SEPARATOR2
frame:
style "l_indent"
has vbox
text _("Build:")
add HALF_SPACER
frame style "l_indent":
has vbox
textbutton _("Install SDK & Create Keys"):
action AndroidIfState(state, ANDROID_NO_SDK, Jump("android_installsdk"))
hovered tt.Action(INSTALL_SDK_TEXT)
textbutton _("Configure"):
action AndroidIfState(state, ANDROID_NO_CONFIG, Jump("android_configure"))
hovered tt.Action(CONFIGURE_TEXT)
textbutton _("Build Package"):
action AndroidIfState(state, ANDROID_OK, AndroidBuild("android_build"))
hovered tt.Action(BUILD_TEXT)
textbutton _("Build & Install"):
action AndroidIfState(state, ANDROID_OK, AndroidBuild("android_build_and_install"))
hovered tt.Action(BUILD_AND_INSTALL_TEXT)
# Right side.
frame:
style "l_indent"
xmaximum ONEHALF
xfill True
has vbox
add SEPARATOR2
frame:
style "l_indent"
has vbox
add SPACER
if tt.value:
text tt.value
else:
text AndroidStateText(state)
textbutton _("Back") action Jump("front_page") style "l_left_button"
label android:
call screen android
label android_installsdk:
python:
with interface.nolinks():
rapt.install_sdk.install_sdk(AndroidInterface())
jump android
label android_configure:
python:
rapt.configure.configure(AndroidInterface(), project.current.path)
jump android
label android_build:
$ android_build([ 'release' ])
jump android
label android_build_and_install:
$ android_build([ 'release', 'install' ])
jump android
+6 -6
View File
@@ -2,7 +2,7 @@
# See LICENSE.txt for license details.
# Ren'Py archiver. This builds a Ren'Py archive file, and the
# associated index file. These files are really easy to
# associated index file. These files are really easy to
# reverse-engineer, but are probably better than nothing.
init python in archiver:
@@ -23,16 +23,16 @@ init python in archiver:
# The archive file.
self.f = open(filename, "wb")
# The index to the file.
self.index = _dict()
# A fixed key minimizes difference between archive versions.
self.key = 0x42424242
padding = "RPA-3.0 XXXXXXXXXXXXXXXX XXXXXXXX\n"
self.f.write(padding)
def add(self, name, path):
"""
Adds a file to the archive.
@@ -55,7 +55,7 @@ init python in archiver:
self.index[name].append((offset ^ self.key, dlen ^ self.key, ""))
def close(self):
indexoff = self.f.tell()
self.f.write(dumps(self.index, HIGHEST_PROTOCOL).encode("zlib"))
@@ -64,4 +64,4 @@ init python in archiver:
self.f.write("RPA-3.0 %016x %08x\n" % (indexoff, self.key))
self.f.close()
+39 -39
View File
@@ -16,7 +16,7 @@ class BinFile(object):
self.a[addr+1] = (value >> 8) & 0xff
self.a[addr+2] = (value >> 16) & 0xff
self.a[addr+3] = (value >> 24) & 0xff
def u32(self):
addr = self.addr
rv = self.a[addr]
@@ -46,7 +46,7 @@ class BinFile(object):
rv += unichr(self.u16())
return rv
def seek(self, addr):
self.addr = addr
@@ -55,7 +55,7 @@ class BinFile(object):
def substring(self, start, len): #@ReservedAssignment
return self.a[start:start+len].tostring()
def __init__(self, data):
self.a = array.array('B')
self.a.fromstring(data)
@@ -66,7 +66,7 @@ class BinFile(object):
# The virtual address of the resource segment.
resource_virtual = 0
# This parses a data block out of the resources.
def parse_data(bf, offset):
bf.seek(offset)
@@ -95,7 +95,7 @@ def parse_directory(bf, offset):
n_id = bf.u16()
entries = [ ]
for _i in range(n_named + n_id):
entries.append((bf.u32(), bf.u32()))
@@ -113,8 +113,8 @@ def parse_directory(bf, offset):
value = parse_data(bf, value)
rv[name] = value
return rv
return rv
##############################################################################
@@ -124,7 +124,7 @@ def show_resources(d, prefix):
if not isinstance(d, dict):
print prefix, "Codepage", d[0], "length", len(d[1])
return
for k in d:
print prefix, k
show_resources(d[k], prefix + " ")
@@ -141,7 +141,7 @@ class Packer(object):
self.entries = ""
self.entries_offset = 0
head = self.pack_dict(d, 0)
self.data = ""
@@ -151,44 +151,44 @@ class Packer(object):
self.entries_offset = len(head)
return self.pack_dict(d, 0) + self.entries + self.data
def pack_name(self, s):
rv = self.data_offset + len(self.data)
l = len(s)
s = s.encode("utf-16le")
self.data += struct.pack("<H", l) + s + "\0\0"
return rv
def pack_tuple(self, t):
codepage, data = t
rv = len(self.entries) + self.entries_offset
rv = len(self.entries) + self.entries_offset
if len(self.data) % 2:
self.data += "P"
daddr = len(self.data) + self.data_offset
self.entries += struct.pack("<IIII", daddr + resource_virtual, len(data), codepage, 0)
self.data += data
# if len(self.data) % 1 == 1:
# self.data += 'P'
return rv
def pack_dict(self, d, offset):
name_entries = sorted((a, b) for a, b in d.iteritems() if isinstance(a, unicode))
id_entries = sorted((a, b) for a, b in d.iteritems() if isinstance(a, int))
rv = struct.pack("<IIHHHH", 0, 0, 4, 0, len(name_entries), len(id_entries))
offset += len(rv) + (len(name_entries) + len(id_entries)) * 8
rest = ""
for (name, value) in name_entries + id_entries:
if isinstance(name, unicode):
name = 0x80000000 | self.pack_name(name)
@@ -200,11 +200,11 @@ class Packer(object):
rest += packed
else:
addr = self.pack_tuple(value)
rv += struct.pack("<II", name, addr)
return rv + rest
##############################################################################
# This loads in an icon file, and returns a dictionary that is suitable for
# use in the resources of an exe file.
@@ -219,8 +219,8 @@ def load_icon(fn):
rv = { }
rv[3] = { }
group = struct.pack("HHH", 0, 1, count)
group = struct.pack("HHH", 0, 1, count)
for i in range(count):
width = f.u8()
height = f.u8()
@@ -235,17 +235,17 @@ def load_icon(fn):
f.seek(offset + 16)
if not f.u32():
f.set_u32(offset + 20, 0)
rv[3][i + 1] = { 0 : (1252, f.substring(offset, size)) }
group += struct.pack("BBBBHHIH", width, height, colors, reserved,
planes, bpp, size, i + 1)
f.seek(addr)
rv[14] = { 1 : { 0 : (1252, group) } }
return rv
@@ -269,25 +269,25 @@ def change_icons(oldexe, icofn):
physize = rsrc_section.SizeOfRawData
virsize = rsrc_section.Misc_VirtualSize
f = file(oldexe, "rb")
f.seek(base)
data = f.read(physize)
f.close()
bf = BinFile(data)
resources = parse_directory(bf, 0)
# show_resources(resources, "")
resources.update(load_icon(icofn))
# show_resources(resources, "")
rsrc = Packer().pack(resources)
alignment = pe.OPTIONAL_HEADER.SectionAlignment
# print "Alignment is", alignment
if len(rsrc) % alignment:
pad = alignment - (len(rsrc) % alignment)
padding = "RENPYVNE" * (pad / 8 + 1)
@@ -295,34 +295,34 @@ def change_icons(oldexe, icofn):
rsrc += padding
newsize = len(rsrc)
rsrc_section.Misc_VirtualSize += newsize - virsize
rsrc_section.Misc_PhysicalAddress += newsize - virsize
rsrc_section.Misc += newsize - virsize
rsrc_section.SizeOfRawData += newsize - physize
pe.OPTIONAL_HEADER.SizeOfInitializedData += newsize - physize
pe.OPTIONAL_HEADER.SizeOfInitializedData += newsize - physize
# Resource size.
pe.OPTIONAL_HEADER.DATA_DIRECTORY[2].Size += newsize - virsize
# Compute the total size of the image.
total_size = 0
for i in pe.sections:
sec_size = i.Misc_VirtualSize
sec_size = sec_size - (sec_size % alignment) + alignment
total_size += sec_size
pe.OPTIONAL_HEADER.SizeOfImage = total_size
return pe.write()[:base] + rsrc
return pe.write()[:base] + rsrc
if __name__ == "__main__":
f = file(sys.argv[3], "wb")
f.write(change_icons(sys.argv[1], sys.argv[2]))
f.close()
+75 -70
View File
@@ -11,64 +11,64 @@ init python:
"""
Gets a list of all of the theme names we know about.
"""
names = list(theme_data.THEME.keys())
names.sort(key=lambda a : a.lower())
return names
def scheme_names(theme):
"""
Gets a list of the color scheme names corresponding to the given
theme.
"""
names = list(theme_data.THEME[theme].keys())
names.sort(key=lambda a : a.lower())
return names
def theme_yinitial():
names = theme_names()
if len(names) < 2:
return 0
return 1.0 * names.index(current_theme) / (len(names) - 1)
def scheme_yinitial():
names = scheme_names(current_theme)
if len(names) < 2:
return 0
return 1.0 * names.index(current_scheme) / (len(names) - 1)
def pick_theme(theme, scheme):
"""
Returns a theme and scheme that are similar to `theme` and `scheme`.
Returns a theme and scheme that are similar to `theme` and `scheme`.
If the theme is known, picks it, otherwise picks a random theme. If
the scheme is known for that theme, picks it, otherwise picks a
the scheme is known for that theme, picks it, otherwise picks a
random scheme that is known for the current theme.
"""
if theme not in theme_data.THEME:
theme = random.choice(list(theme_data.THEME))
schemes = theme_data.THEME[theme]
if scheme not in schemes:
if theme in schemes:
scheme = theme
else:
scheme = random.choice(list(schemes))
return theme, scheme
def implement_theme(theme, scheme):
global showing_theme, showing_scheme
if theme == showing_theme and scheme == showing_scheme:
return
@@ -80,52 +80,52 @@ init python:
showing_scheme = scheme
renpy.restart_interaction()
showing_theme = None
showing_scheme = None
class SetTheme(Action):
def __init__(self, theme):
self.theme = theme
def __call__(self):
global current_theme
global current_scheme
global current_scheme
current_theme, current_scheme = pick_theme(self.theme, current_scheme)
implement_theme(current_theme, current_scheme)
renpy.restart_interaction()
def get_selected(self):
return current_theme == self.theme
class SetScheme(Action):
def __init__(self, scheme):
self.scheme = scheme
def __call__(self):
global current_theme
global current_scheme
global current_scheme
current_theme, current_scheme = pick_theme(current_theme, self.scheme)
implement_theme(current_theme, current_scheme)
renpy.restart_interaction()
def get_selected(self):
return current_scheme == self.scheme
class PreviewTheme(Action):
def __init__(self, theme, scheme):
self.theme = theme
self.scheme = scheme
def __call__(self):
theme, scheme = pick_theme(self.theme, self.scheme)
implement_theme(theme, scheme)
def unhovered(self):
if (showing_theme == self.theme and showing_scheme == self.scheme):
implement_theme(current_theme, current_scheme)
@@ -135,11 +135,11 @@ init python:
##########################################################################
# Code to update options.rpy
def list_logical_lines(filename):
"""
This reads in filename, and turns it into a list of logical
lines.
lines.
"""
f = codecs.open(filename, "rb", "utf-8")
@@ -227,7 +227,7 @@ init python:
continue
continue
line += c
pos += 1
@@ -239,7 +239,7 @@ init python:
def switch_theme():
"""
Switches the theme of the current project to the current theme
Switches the theme of the current project to the current theme
and color scheme. (As set in current_theme and current_scheme.)
"""
@@ -258,7 +258,7 @@ init python:
if (not changed) and m and (m.group(1) in theme_data.THEME_FUNCTIONS):
l = " " + theme_code
changed = True
out.write(l + "\n")
if changed:
@@ -272,7 +272,7 @@ init python:
else:
os.unlink(filename + ".new")
interface.error(_("Could not change the theme. Perhaps options.rpy was changed too much."))
# Now give the theme's screen-ops function a chance to make any
# necessary changes to the screens.rpy file
@@ -289,13 +289,13 @@ init python:
for l in lines:
out.write(l + "\n")
changed = True
if changed:
try:
os.unlink(filename + ".bak")
except:
pass
os.rename(filename, filename + ".bak")
os.rename(filename + ".new", filename)
@@ -307,13 +307,20 @@ init python:
pass
pass
def make_style_backup():
"""
Call this to back up the styles. This should be called in a
translate python block in each translation.
"""
init 100 python:
style_backup = renpy.style.backup()
global style_backup
style_backup = renpy.style.backup()
translate None python:
make_style_backup()
screen theme_demo:
window:
style "gm_root"
xpadding 5
@@ -324,16 +331,16 @@ screen theme_demo:
style_group "prefs"
vbox:
frame:
style_group "pref"
has vbox
label _("Display")
textbutton _("Window") action SelectedIf(True)
textbutton _("Fullscreen") action ui.returns(None)
textbutton _("Planetarium") action None
frame:
style_group "pref"
@@ -368,16 +375,16 @@ screen choose_theme:
frame:
style_group "l"
style "l_root"
window:
has vbox
label _("Choose Theme")
hbox:
yfill True
# Theme selector.
frame:
style "l_indent"
@@ -385,47 +392,47 @@ screen choose_theme:
xmaximum 225
has vbox
label _("Theme") style "l_label_small"
viewport:
scrollbars "vertical"
yinitial theme_yinitial()
mousewheel True
has vbox
for i in theme_names():
textbutton "[i]":
action SetTheme(i)
hovered PreviewTheme(i, current_scheme)
style "l_list2"
style "l_list2"
# Color scheme selector.
frame:
style "l_indent"
bottom_margin HALF_SPACER_HEIGHT
xmaximum 225
has vbox
label _("Color Scheme") style "l_label_small"
viewport:
scrollbars "vertical"
mousewheel True
yinitial scheme_yinitial()
has vbox
for i in scheme_names(current_theme):
textbutton "[i]":
textbutton "[i]":
action SetScheme(i)
hovered PreviewTheme(current_theme, i)
style "l_list2"
style "l_list2"
# Preview
frame:
style "l_default"
@@ -437,19 +444,19 @@ screen choose_theme:
yfill True
xmargin 20
bottom_margin 6
use theme_demo
textbutton _("Back") action Jump("front_page") style "l_left_button"
textbutton _("Continue") action Return(True) style "l_right_button"
label choose_theme_callable:
python:
current_theme, current_scheme = pick_theme(None, None)
implement_theme(current_theme, current_scheme)
implement_theme(current_theme, current_scheme)
call screen choose_theme
python hide:
@@ -457,9 +464,7 @@ label choose_theme_callable:
switch_theme()
return
label choose_theme:
call choose_theme_callable
jump front_page
+245 -190
View File
@@ -1,7 +1,7 @@
# Copyright 2004-2012 Tom Rothamel <pytom@bishoujo.us>
# See LICENSE.txt for license details.
# This file contains code that manages the distribution of Ren'Py games
# This file contains code that manages the distribution of Ren'Py games
# and Ren'Py proper.
#
# In this module, all files and paths are stored in unicode. Full paths
@@ -34,14 +34,14 @@ init python in distribute:
import time
match_cache = { }
def compile_match(pattern):
"""
Compiles a pattern for use with match.
"""
regexp = ""
while pattern:
if pattern.startswith("**"):
regexp += r'.*'
@@ -52,20 +52,20 @@ init python in distribute:
elif pattern[0] == '[':
regexp += r'['
pattern = pattern[1:]
while pattern and pattern[0] != ']':
regexp += pattern[0]
pattern = pattern[1:]
pattern = pattern[1:]
regexp += ']'
else:
regexp += re.escape(pattern[0])
pattern = pattern[1:]
regexp += "$"
return re.compile(regexp, re.I)
def match(s, pattern):
@@ -79,15 +79,15 @@ init python in distribute:
Things are matched case-insensitively.
"""
regexp = match_cache.get(pattern, None)
if regexp is None:
regexp = compile_match(pattern)
match_cache[pattern] = regexp
if regexp.match(s):
return True
if regexp.match("/" + s):
return True
@@ -96,28 +96,28 @@ init python in distribute:
class File(object):
"""
Represents a file that we can distribute.
self.name
The name of the file as it will be stored in the archives.
self.path
The path to the file on disk. None if it won't be stored
on disk.
self.directory
True if this is a directory.
self.executable
True if this is an executable that should be distributed
with the xbit set.
"""
def __init__(self, name, path, directory, executable):
self.name = name
self.path = path
self.directory = directory
self.executable = executable
def __repr__(self):
if self.directory:
extra = "dir"
@@ -125,72 +125,72 @@ init python in distribute:
extra = "x-bit"
else:
extra = ""
return "<File {!r} {!r} {}>".format(self.name, self.path, extra)
def copy(self):
return File(self.name, self.path, self.directory, self.executable)
class FileList(list):
"""
This represents a list of files that we know about.
"""
def sort(self):
list.sort(self, key=lambda a : a.name)
def copy(self):
"""
Makes a deep copy of this file list.
"""
rv = FileList()
for i in self:
rv.append(i.copy())
return rv
def filter_empty(self):
"""
Makes a deep copy of this file list with empty directories
omitted.
"""
rv = FileList()
needed_dirs = set()
for i in reversed(self):
if (not i.directory) or (i.name in needed_dirs):
if (not i.directory) or (i.name in needed_dirs):
rv.insert(0, i.copy())
directory, _sep, _filename = i.name.rpartition("/")
needed_dirs.add(directory)
return rv
@staticmethod
def merge(l):
"""
Merges a list of file lists into a single file list with no
duplicate entries.
"""
rv = FileList()
seen = set()
for fl in l:
for f in fl:
if f.name in seen:
continue
rv.append(f)
seen.add(f.name)
return rv
def prepend_directory(self, directory):
@@ -198,32 +198,32 @@ init python in distribute:
Modifies this file list such that every file in it has `directory`
prepended.
"""
for i in self:
i.name = directory + "/" + i.name
self.insert(0, File(directory, None, True, False))
def mac_transform(self, app, documentation):
"""
Creates a new file list that has the mac transform applied to it.
The mac transform places all files that aren't already in <app> in
<app>/Contents/Resources/autorun. If it matches one of the documentation
patterns, then it appears both inside and outside of the app.
"""
rv = FileList()
for f in self:
# Already in the app.
if f.name == app or f.name.startswith(app + "/"):
rv.append(f)
continue
# If it's documentation, keep the file. (But also make
# If it's documentation, keep the file. (But also make
# a copy.)
for pattern in documentation:
if match(f.name, pattern):
@@ -231,16 +231,16 @@ init python in distribute:
if match("/" + f.name, pattern):
rv.append(f)
# Make a copy.
f = f.copy()
f.name = app + "/Contents/Resources/autorun/" + f.name
rv.append(f)
rv.append(File(app + "/Contents/Resources/autorun", None, True, False))
rv.sort()
return rv
@@ -248,28 +248,45 @@ init python in distribute:
"""
This manages the process of building distributions.
"""
def __init__(self, project, destination=None, reporter=None, packages=None, build_update=True):
def __init__(self, project, destination=None, reporter=None, packages=None, build_update=True, open_directory=False, noarchive=False, packagedest=None, report_success=True):
"""
Distributes `project`.
`destination`
The destination in which the distribution will be placed. If None,
uses a default location.
`reporter`
An object that's used to report status and progress to the user.
`packages`
If not None, a list of packages to distributed. If None, all
packages are distributed.
`build_update`
Will updates be built?
`open_directory`
If true, the directory containing the built files will be opened
if the build succeeds.
`noarchive`
If true, files will not be placed into archives.
`packagedest`
If given, gives the full path to the single package (without any
extensions).
`report_success`
If true, we report that the build succeeded.
"""
# Safety - prevents us frome releasing a launcher that won't update.
if packagedest is not None:
if packages is None or len(packages) != 1:
raise Exception("Packagedest requires a single package be given.")
# Safety - prevents us from releasing a launcher that won't update.
if store.UPDATE_SIMULATE:
raise Exception("Cannot build distributions when UPDATE_SIMULATE is True.")
@@ -282,7 +299,7 @@ init python in distribute:
# Start by scanning the project, to get the data and build
# dictionaries.
data = project.data
project.update_dump(force=True, gui=False)
if project.dump.get("error", False):
raise Exception("Could not get build data from the project. Please ensure the project runs.")
@@ -295,17 +312,22 @@ init python in distribute:
self.base_name = build['directory_name']
self.executable_name = build['executable_name']
self.pretty_version = build['version']
# The destination directory.
if destination is None:
destination = build["destination"]
parent = os.path.dirname(project.path)
self.destination = os.path.join(parent, self.base_name + "-dists")
self.destination = os.path.join(parent, destination)
else:
self.destination = destination
if not packagedest:
try:
os.makedirs(self.destination)
except:
pass
else:
self.destination = destination
self.packagedest = packagedest
# Status reporter.
self.reporter = reporter
@@ -322,15 +344,19 @@ init python in distribute:
self.documentation_patterns = build['documentation_patterns']
build_packages = [ ]
for i in build['packages']:
name = i['name']
if (packages is None) or (name in packages):
if packages is None:
if not i['hidden']:
build_packages.append(i)
elif name in packages:
build_packages.append(i)
if not build_packages:
self.reporter.info(_("Nothing to do."), pause=True)
self.reporter.info(_("No packages are selected, so there's nothing to do."), pause=True)
self.log.close()
return
# add the game.
@@ -338,7 +364,10 @@ init python in distribute:
self.scan_and_classify(project.path, build["base_patterns"])
self.archive_files(build["archives"])
if noarchive:
self.ignore_archives(build['archives'])
else:
self.archive_files(build["archives"])
# Add Ren'Py.
self.reporter.info(_("Scanning Ren'Py files..."))
@@ -366,43 +395,51 @@ init python in distribute:
self.update_version = int(time.time())
for p in build_packages:
for f in p["formats"]:
self.make_package(
p["name"],
f,
p["file_lists"],
dlc=p["dlc"])
if self.build_update and p["update"]:
self.make_package(
p["name"],
"update",
p["file_lists"],
dlc=False)
if self.build_update:
self.finish_updates(build_packages)
# Finish up.
self.log.close()
if report_success:
self.reporter.info(_("All packages have been built.\n\nDue to the presence of permission information, unpacking and repacking the Linux and Macintosh distributions on Windows is not supported."))
if open_directory:
store.OpenDirectory(self.destination)()
def scan_and_classify(self, directory, patterns):
"""
Walks through the `directory`, finds files and directories that
match the pattern, and assds them to the appropriate file list.
match the pattern, and assigns them to the appropriate file list.
`patterns`
A list of pattern, file_list tuples. The pattern is a string
that is matched using match. File_list is either
a space-separated list of file lists to add the file to,
a space-separated list of file lists to add the file to,
or None to ignore it.
Directories are matched with a trailing /, but added to the
Directories are matched with a trailing /, but added to the
file list with the trailing / removed.
"""
def walk(name, path):
is_dir = os.path.isdir(path)
@@ -426,9 +463,9 @@ init python in distribute:
return
for fl in file_list:
f = File(name, path, is_dir, False)
f = File(name, path, is_dir, False)
self.file_lists[fl].append(f)
if is_dir:
for fn in os.listdir(path):
@@ -436,7 +473,7 @@ init python in distribute:
name + "/" + fn,
os.path.join(path, fn),
)
for fn in os.listdir(directory):
walk(fn, os.path.join(directory, fn))
@@ -447,45 +484,59 @@ init python in distribute:
def add_file(self, file_list, name, path, executable=False):
"""
Adds a file to the file lists.
`file_list`
A space-separated list of file list names.
`name`
The name of the file to be added.
`path`
The path to that file on disk.
"""
if not os.path.exists(path):
raise Exception("{} does not exist.".format(path))
if isinstance(file_list, basestring):
file_list = file_list.split()
f = File(name, path, False, executable)
for fl in file_list:
self.file_lists[fl].append(f)
def ignore_archives(self, archives):
"""
Ignore archiving commands by adding the files that would be in
archives into packages instead.
"""
for arcname, file_lists in archives:
if not self.file_lists[arcname]:
continue
for f in self.file_lists[arcname]:
for fl in file_lists:
self.file_lists[fl].append(f)
def archive_files(self, archives):
"""
Add files to archives.
"""
for arcname, file_list in archives:
if not self.file_lists[arcname]:
continue
arcfn = arcname + ".rpa"
arcpath = self.temp_filename(arcfn)
af = archiver.Archive(arcpath)
fll = len(self.file_lists[arcname])
for i, entry in enumerate(self.file_lists[arcname]):
if entry.directory:
@@ -495,11 +546,11 @@ init python in distribute:
name = "/".join(entry.name.split("/")[1:])
af.add(name, entry.path)
self.reporter.progress_done()
af.close()
self.add_file(file_list, "game/" + arcfn, arcpath)
def add_renpy_files(self):
@@ -509,11 +560,11 @@ init python in distribute:
if not os.path.exists(os.path.join(self.project.path, "game", "script_version.rpy")):
self.add_file("all", "game/script_version.rpy", os.path.join(config.gamedir, "script_version.rpy"))
if not os.path.exists(os.path.join(self.project.path, "game", "script_version.rpyc")):
self.add_file("all", "game/script_version.rpyc", os.path.join(config.gamedir, "script_version.rpyc"))
self.add_file("all", "renpy/LICENSE.txt", os.path.join(config.renpy_base, "LICENSE.txt"))
self.add_file("renpy", "renpy/LICENSE.txt", os.path.join(config.renpy_base, "LICENSE.txt"))
def write_plist(self):
@@ -532,14 +583,14 @@ init python in distribute:
CFBundlePackageType="APPL",
CFBundleShortVersionString=version,
CFBundleVersion="1.0.{0}".format(int(time.time())),
CFBundleDocumentTypes = [
CFBundleDocumentTypes = [
{
"CFBundleTypeOSTypes" : [ "****", "fold", "disk" ],
"CFBundleTypeRole" : "Viewer",
},
},
],
UTExportedTypeDeclarations = [
{
UTExportedTypeDeclarations = [
{
"UTTypeConformsTo" : [ "public.python-script" ],
"UTTypeDescription" : "Ren'Py Script",
"UTTypeIdentifier" : "org.renpy.rpy",
@@ -547,7 +598,7 @@ init python in distribute:
},
],
)
rv = self.temp_filename("Info.plist")
plistlib.writePlist(plist, rv)
return rv
@@ -562,27 +613,27 @@ init python in distribute:
windows = 'windows'
linux = 'linux'
mac = 'mac'
self.add_file(
linux,
linux,
"lib/linux-i686/" + self.executable_name,
os.path.join(config.renpy_base, "lib/linux-i686/pythonw"),
True)
self.add_file(
linux,
linux,
"lib/linux-x86_64/" + self.executable_name,
os.path.join(config.renpy_base, "lib/linux-x86_64/pythonw"),
True)
self.add_file(
mac,
mac,
"lib/darwin-x86_64/" + self.executable_name,
os.path.join(config.renpy_base, "lib/darwin-x86_64/pythonw"),
True)
self.add_file(
windows,
windows,
"lib/windows-i686/" + self.executable_name + ".exe",
os.path.join(config.renpy_base, "lib/windows-i686/pythonw.exe"))
@@ -599,7 +650,7 @@ init python in distribute:
contents = self.app + "/Contents"
plist_fn = self.write_plist()
self.add_file(filelist, contents + "/Info.plist", plist_fn)
self.add_file(filelist, contents + "/Info.plist", plist_fn)
self.add_file(filelist, contents + "/MacOS/" + self.executable_name, os.path.join(config.renpy_base, "renpy.sh"))
custom_fn = os.path.join(self.project.path, "icon.icns")
@@ -609,7 +660,7 @@ init python in distribute:
icon_fn = custom_fn
else:
icon_fn = default_fn
self.add_file(filelist, contents + "/Resources/icon.icns", icon_fn)
@@ -617,80 +668,83 @@ init python in distribute:
"""
Adds windows-specific files.
"""
icon_fn = os.path.join(self.project.path, "icon.ico")
old_exe_fn = os.path.join(config.renpy_base, "renpy.exe")
if os.path.exists(icon_fn):
exe_fn = self.temp_filename("renpy.exe")
with open(exe_fn, "wb") as f:
with open(exe_fn, "wb") as f:
f.write(change_icons(old_exe_fn, icon_fn))
else:
exe_fn = old_exe_fn
self.add_file("windows", "renpy.exe", exe_fn)
self.add_file("windows", "renpy.exe", exe_fn)
def mark_executable(self):
"""
Marks files as executable.
"""
for l in self.file_lists.values():
for f in l:
for pat in self.build['xbit_patterns']:
if match(f.name, pat):
for pat in self.build['xbit_patterns']:
if match(f.name, pat):
f.executable = True
if match("/" + f.name, pat):
if match("/" + f.name, pat):
f.executable = True
def rename(self):
"""
Rename files in all lists to match the executable names.
"""
def rename_one(fn):
parts = fn.split('/')
p = parts[0]
if p == "renpy.exe":
p = self.exe
elif p == "renpy.sh":
p = self.sh
elif p == "renpy.py":
p = self.py
parts[0] = p
return "/".join(parts)
for l in self.file_lists.values():
for f in l:
f.name = rename_one(f.name)
def make_package(self, variant, format, file_lists, dlc=False):
"""
Creates a package file in the projects directory.
Creates a package file in the projects directory.
`variant`
The name of the variant to package. This is appended to the base name to become
part of the file and directory names.
`format`
The format things will be packaged in. This should be one of "zip", "tar.bz2", or
"update".
The format things will be packaged in. This should be one of "zip", "tar.bz2", or
"update".
`file_lists`
A string containing a space-separated list of file_lists to include in this
A string containing a space-separated list of file_lists to include in this
package.
`dlc`
True if we want to build a non-update file in DLC mode.
"""
filename = self.base_name + "-" + variant
path = os.path.join(self.destination, filename)
if self.packagedest:
path = self.packagedest
fl = FileList.merge([ self.file_lists[i] for i in file_lists ])
fl = fl.copy()
fl.sort()
@@ -708,10 +762,10 @@ init python in distribute:
update_files.append(i.name)
else:
update_directories.append(i.name)
if i.executable:
update_xbit.append(i.name)
update = { variant : { "version" : self.update_version, "pretty_version" : self.pretty_version, "files" : update_files, "directories" : update_directories, "xbit" : update_xbit } }
if self.include_update and not dlc:
@@ -726,11 +780,11 @@ init python in distribute:
# The mac transform.
if format == "app-zip":
fl = fl.mac_transform(self.app, self.documentation_patterns)
# If we're not an update file, prepend the directory.
if (not dlc) and format != "update":
if (not dlc) and format != "update" and format != "directory":
fl.prepend_directory(filename)
if format == "tar.bz2":
path += ".tar.bz2"
pkg = TarPackage(path, "w:bz2")
@@ -740,10 +794,12 @@ init python in distribute:
elif format == "zip" or format == "app-zip":
path += ".zip"
pkg = ZipPackage(path)
elif format == "directory":
pkg = DirectoryPackage(path)
for i, f in enumerate(fl):
self.reporter.progress(_("Writing the [variant] [format] package."), i, len(fl), variant=variant, format=format)
if f.directory:
pkg.add_directory(f.name, f.path)
else:
@@ -754,31 +810,31 @@ init python in distribute:
if format == "update":
# Build the zsync file.
self.reporter.info(_("Making the [variant] update zsync file."), variant=variant)
cmd = [
updater.zsync_path("zsyncmake"),
cmd = [
updater.zsync_path("zsyncmake"),
"-z",
# -u url to gzipped data - not a local filename!
"-u", filename + ".update.gz",
"-u", filename + ".update.gz",
"-o", os.path.join(self.destination, filename + ".zsync"),
os.path.abspath(path),
os.path.abspath(path),
]
subprocess.check_call([ renpy.fsencode(i) for i in cmd ])
# Build the sums file. This is a file with an adler32 hash of each 64k block
# of the zsync file. It's used to help us determine how much of the file is
# of the zsync file. It's used to help us determine how much of the file is
# downloaded.
with open(path, "rb") as src:
with open(renpy.fsencode(os.path.join(self.destination, filename + ".sums")), "wb") as sums:
while True:
data = src.read(65536)
if not data:
break
sums.write(struct.pack("I", zlib.adler32(data) & 0xffffffff))
if self.include_update and not self.build_update and not dlc:
@@ -794,28 +850,28 @@ init python in distribute:
return
index = { }
def add_variant(variant):
fn = renpy.fsencode(os.path.join(self.destination, self.base_name + "-" + variant + ".update"))
with open(fn, "rb") as f:
digest = hashlib.sha256(f.read()).hexdigest()
index[variant] = {
"version" : self.update_version,
"pretty_version" : self.pretty_version,
"digest" : digest,
"zsync_url" : self.base_name + "-" + variant + ".zsync",
"sums_url" : self.base_name + "-" + variant + ".sums",
index[variant] = {
"version" : self.update_version,
"pretty_version" : self.pretty_version,
"digest" : digest,
"zsync_url" : self.base_name + "-" + variant + ".zsync",
"sums_url" : self.base_name + "-" + variant + ".sums",
"json_url" : self.base_name + "-" + variant + ".update.json",
}
os.unlink(fn)
for p in packages:
if p["update"]:
add_variant(p["name"])
fn = renpy.fsencode(os.path.join(self.destination, "updates.json"))
with open(fn, "wb") as f:
json.dump(index, f)
@@ -835,24 +891,24 @@ init python in distribute:
"""
Displays progress using the gui.
"""
def __init__(self):
# The time at which we should next report progress.
self.next_progress = 0
def info(self, what, pause=False, **kwargs):
if pause:
interface.information(what, **kwargs)
interface.info(what, **kwargs)
else:
interface.processing(what, **kwargs)
def progress(self, what, complete, total, **kwargs):
if (complete > 0) and (time.time() < self.next_progress):
return
interface.processing(what, _("Processed {b}[complete]{/b} of {b}[total]{/b} files."), complete=complete, total=total, **kwargs)
self.next_progress = time.time() + .05
def progress_done(self):
@@ -863,21 +919,21 @@ init python in distribute:
"""
Displays progress on the command line.
"""
def info(self, what, pause=False, **kwargs):
what = what.replace("[", "{")
what = what.replace("]", "}")
what = what.format(**kwargs)
print what
def progress(self, what, done, total, **kwargs):
what = what.replace("[", "{")
what = what.replace("]", "}")
what = what.format(**kwargs)
sys.stdout.write("\r{} - {} of {}".format(what, done + 1, total))
sys.stdout.flush()
def progress_done(self):
sys.stdout.write("\n")
@@ -885,39 +941,38 @@ init python in distribute:
def distribute_command():
ap = renpy.arguments.ArgumentParser()
ap.add_argument("--destination", "--dest", default=None, action="store", help="The directory where the packaged files should be placed.")
ap.add_argument("--packagedest", default=None, action="store", help="If given, gives the full path to the package file, without extensions." )
ap.add_argument("--no-update", default=True, action="store_false", dest="build_update", help="Prevents updates from being built.")
ap.add_argument("--package", action="append", help="If given, a package to build. Defaults to building all packages.")
ap.add_argument("--no-archive", action="store_true", help="If given, files will not be added to archives.")
ap.add_argument("project", help="The path to the project directory.")
ap.add_argument("--package", action="append", help="If given, a package to build. Defaults to building all packages.")
args = ap.parse_args()
p = project.Project(args.project)
if args.package:
packages = args.package
else:
packages = None
Distributor(p, destination=args.destination, reporter=TextReporter(), packages=packages, build_update=args.build_update)
Distributor(p, destination=args.destination, reporter=TextReporter(), packages=packages, build_update=args.build_update, noarchive=args.no_archive, packagedest=args.packagedest)
return False
renpy.arguments.register_command("distribute", distribute_command)
label distribute:
python hide:
data = project.current.data
d = distribute.Distributor(project.current, reporter=distribute.GuiReporter(), packages=data['packages'], build_update=data['build_update'])
OpenDirectory(d.destination)()
interface.info(_("All packages have been built.\n\nDue to the presence of permission information, unpacking and repacking the Linux and Macintosh distributions on Windows is not supported."))
d = distribute.Distributor(project.current,
reporter=distribute.GuiReporter(),
packages=data['packages'],
build_update=data['build_update'],
open_directory=True,
)
jump front_page
+51 -56
View File
@@ -5,66 +5,66 @@ init python:
class PackageToggle(Action):
def __init__(self, name):
self.name = name
def get_selected(self):
return self.name in project.current.data['packages']
return self.name in project.current.data['packages']
def __call__(self):
packages = project.current.data['packages']
if self.name in packages:
packages.remove(self.name)
else:
packages.append(self.name)
project.current.save_data()
renpy.restart_interaction()
class DataToggle(Action):
def __init__(self, field):
self.field = field
def get_selected(self):
return project.current.data[self.field]
def __call__(self):
project.current.data[self.field] = not project.current.data[self.field]
project.current.save_data()
renpy.restart_interaction()
DEFAULT_BUILD_INFO = """
## This section contains information about how to build your project into
## This section contains information about how to build your project into
## distribution files.
init python:
## The name that's used for directories and archive files. For example, if
## this is 'mygame-1.0', the windows distribution will be in the
## directory 'mygame-1.0-win', in the 'mygame-1.0-win.zip' file.
build.directory_name = "PROJECTNAME-1.0"
## The name that's uses for executables - the program that users will run
## to start the game. For example, if this is 'mygame', then on Windows,
## users can click 'mygame.exe' to start the game.
build.executable_name = "PROJECTNAME"
## If True, Ren'Py will include update information into packages. This
## allows the updater to run.
build.include_update = False
## File patterns:
##
##
## The following functions take file patterns. File patterns are case-
## insensitive, and matched against the path relative to the base
## directory, with and without a leading /. If multiple patterns match,
## the first is used.
##
##
##
## In a pattern:
##
## /
## /
## Is the directory separator.
## *
## Matches all characters, except the directory separator.
@@ -87,9 +87,9 @@ init python:
build.classify('**/.**', None)
build.classify('**/#**', None)
build.classify('**/thumbs.db', None)
## To archive files, classify them as 'archive'.
# build.classify('game/**.png', 'archive')
# build.classify('game/**.jpg', 'archive')
@@ -100,7 +100,7 @@ init python:
build.documentation('*.txt')
"""
# A screen that displays a file or directory name, and
# A screen that displays a file or directory name, and
# lets the user change it,
#
# title
@@ -108,17 +108,17 @@ init python:
# value
# The value of the field.
screen distribute_name:
add SEPARATOR2
frame:
style "l_indent"
has vbox
text title
add HALF_SPACER
frame:
style "l_indent"
text "[value!q]"
@@ -127,27 +127,27 @@ screen distribute_name:
screen build_distributions:
frame:
style_group "l"
style "l_root"
window:
has vbox
label _("Build Distributions: [project.current.name!q]")
add HALF_SPACER
hbox:
# Left side.
frame:
style "l_indent"
xmaximum ONEHALF
xfill True
has vbox
use distribute_name(
@@ -159,23 +159,23 @@ screen build_distributions:
value=project.current.dump["build"]["executable_name"])
add SEPARATOR2
frame:
style "l_indent"
has vbox
text _("Actions:")
add HALF_SPACER
frame style "l_indent":
has vbox
textbutton _("Edit options.rpy") action editor.Edit("game/options.rpy", check=True)
textbutton _("Refresh") action Jump("build_distributions")
# Right side.
frame:
style "l_indent"
@@ -185,23 +185,22 @@ screen build_distributions:
has vbox
add SEPARATOR2
frame:
style "l_indent"
has vbox
text _("Build Packages:")
add HALF_SPACER
$ packages = project.current.dump["build"]["packages"]
for pkg in packages:
$ description = pkg["description"]
textbutton "[description!q]" action PackageToggle(pkg["name"]) style "l_checkbox"
if not pkg["hidden"]:
$ description = pkg["description"]
textbutton "[description!q]" action PackageToggle(pkg["name"]) style "l_checkbox"
add SPACER
if project.current.dump["build"]["include_update"]:
@@ -214,34 +213,30 @@ screen build_distributions:
label build_update_dump:
python:
project.current.update_dump(True)
if project.current.dump.get("error", False):
interface.error(_("Errors were detected when running the project. Please ensure the project runs without errors before building distributions."))
return
label build_distributions:
call build_update_dump
if not project.current.dump["build"]["directory_name"]:
jump build_missing
call screen build_distributions
label build_missing:
python hide:
interface.yesno(_("Your project does not contain build information. Would you like to add build information to the end of options.rpy?"), yes=Return(True), no=Jump("front_page"))
build_info = DEFAULT_BUILD_INFO.replace("PROJECTNAME", project.current.name)
with open(os.path.join(project.current.path, "game", "options.rpy"), "a") as f:
f.write(build_info)
jump build_distributions
+129
View File
@@ -0,0 +1,129 @@
init python:
import urllib2
import os
import threading
import time
class Downloader(object):
def __init__(self, url, dest):
"""
Downloads `url` to `dest`, providing progress reports
as necessary.
"""
self.url = url
# The destination file, and the destination temp file.
self.dest = dest
self.tmp = dest + ".tmp"
# Open the tmpfile.
self.safe_unlink(self.tmp)
self.tmpfile = open(self.tmp, "wb")
# Set by the thread to indicate progress (ranges from 0.0 to 1.0).
self.progress = 0.0
# This is set to true by cancel() to indicate the download should be cancelled.
self.cancelled = False
# Set on succes or failure.
self.success = False
self.failure = None
try:
# Open the URL.
self.urlfile = urllib2.urlopen(url)
t = threading.Thread(target=self.thread)
t.daemon = True
t.start()
except Exception as e:
self.failure = str(e)
def thread(self):
try:
count = 0
if "content-length" in self.urlfile.headers:
length = int(self.urlfile.headers["content-length"])
else:
length = 0
while not self.cancelled:
data = self.urlfile.read(65536)
if not data:
break
count += len(data)
self.tmpfile.write(data)
if length > 0:
self.progress = 1.0 * count / length
self.tmpfile.close()
if self.cancelled:
return
if length and count != length:
self.failure = "Download length does not match content length."
return
self.safe_unlink(self.dest)
os.rename(self.tmp, self.dest)
self.success = True
except Exception as e:
self.failure = str(e)
def safe_unlink(self, fn):
if os.path.exists(fn):
os.unlink(fn)
def cancel(self):
"""
Cancels the download.
"""
self.cancelled = True
def check(self):
"""
Returns True if the download is finished, False if it was cancelled,
None if it's ongoing, and raises an Exception if the download has failed.
"""
if self.success:
return True
if self.cancelled:
return False
if self.failure:
raise Exception("Downloading {} to {} failed: {}".format(self.url, self.dest, self.failure))
return None
class DownloaderValue(BarValue):
"""
A BarValue that reports the progress of a background download.
"""
def __init__(self, d):
self.downloader = d
def get_adjustment(self):
self.adjustment = ui.adjustment(value=0.0, range=1.0, adjustable=False)
return self.adjustment
def periodic(self, st):
self.adjustment.change(self.downloader.progress)
return .25
+139 -96
View File
@@ -13,29 +13,29 @@ init python in editor:
import store.updater as updater
import store.interface as interface
import store
import glob
import re
import traceback
import os
import os.path
# Should we set up the editor?
set_editor = "RENPY_EDIT_PY" not in os.environ
# A map from editor name to EditorInfo object.
editors = { }
class EditorInfo(object):
def __init__(self, filename):
# The path to the editor info file.
self.filename = filename
# The name of the editor.
self.name = os.path.basename(filename)[:-len(".edit.py")]
# The time the editor file was last modified. We use this
# to decide if we should update the editors mat when we
# to decide if we should update the editors mat when we
# have multiple versions of an editor in contention.
self.mtime = os.path.getmtime(filename)
@@ -46,13 +46,13 @@ init python in editor:
"""
ei = EditorInfo(filename)
if ei.name in editors:
if editors[ei.name].mtime >= ei.mtime:
return
editors[ei.name] = ei
def scan_all():
"""
Finds all *.edit.py files, and uses them to populate the list
@@ -60,11 +60,11 @@ init python in editor:
"""
editors.clear()
for d in [ config.renpy_base, persistent.projects_directory ]:
if d is None:
continue
for filename in glob.glob(d + "/*/*.edit.py"):
scan_editor(filename)
@@ -78,42 +78,42 @@ init python in editor:
class FancyEditorInfo(object):
"""
Represents an editor in the selection screen. A FEI knows if the
Represents an editor in the selection screen. A FEI knows if the
editor is installed or not.
"""
def __init__(self, priority, name, description=None, dlc=None, dldescription=None, error_message=None):
# The priority of the editor. Lower priorities will come later
# in the list.
self.priority = priority
# The name of the editor.
self.name = name
# Is the editor installed?
self.installed = name in editors
# The dlc needed to install the editor.
self.dlc = dlc
# A description of the editor.
self.description = description
# A description of the download.
self.dldescription = dldescription
# An error message to display if the editor failed to start.
self.error_message = error_message
def fancy_scan_editors():
"""
Creates the list of FancyEditorInfo objects.
"""
global fancy_editors
scan_all()
fei = fancy_editors = [ ]
# Editra.
@@ -134,36 +134,36 @@ init python in editor:
dlc = "editra-linux"
installed = os.path.exists(os.path.join(config.basedir, "editra/Editra"))
description = EDL
error_message = _("The may have occured because wxPython is not installed on this system.")
error_message = _("This may have occured because wxPython is not installed on this system.")
e = FancyEditorInfo(
1,
"Editra",
"Editra",
description,
dlc,
_("Up to 22 MB download required."),
error_message)
e.installed = e.installed or installed
fei.append(e)
# jEdit
fei.append(FancyEditorInfo(
2,
"jEdit",
"A mature editor that requires Java.",
_("A mature editor that requires Java."),
"jedit",
_("1.8 MB download required."),
_("This may have occured because Java is not installed on this system."),
))
fei.append(FancyEditorInfo(
3,
"System Editor",
"Invokes the editor your operating system has associated with .rpy files.",
_("Invokes the editor your operating system has associated with .rpy files."),
None))
for k in editors:
if k in [ "Editra", "jEdit", "System Editor", "None" ]:
continue
@@ -173,13 +173,13 @@ init python in editor:
k,
None,
None))
fei.append(FancyEditorInfo(
5,
"None",
"Prevents Ren'Py from opening a text editor.",
_("Prevents Ren'Py from opening a text editor."),
None))
fei.sort(key=lambda e : (e.priority, e.name.lower()))
# If we're in a linux distro or something, assume all editors work.
@@ -190,40 +190,40 @@ init python in editor:
def fancy_activate_editor(default=False):
"""
Activates the editor in persistent.editor, if it's installed.
`default`
"""
global error_message
fancy_scan_editors()
if default and not set_editor:
renpy.editor.init()
return
for i in fancy_editors:
if i.name == persistent.editor:
if i.installed and i.name in editors:
ei = editors[i.name]
os.environ["RENPY_EDIT_PY"] = renpy.fsencode(os.path.abspath(ei.filename))
error_message = i.error_message
break
else:
persistent.editor = None
os.environ.pop("RENPY_EDIT_PY", None)
renpy.editor.init()
def fancy_select_editor(name):
"""
Selects the editor with the given name, installing it if it
doesn't already exist.
doesn't already exist.
"""
for fe in fancy_editors:
if fe.name == name:
break
@@ -231,47 +231,47 @@ init python in editor:
return
if not fe.installed:
# We don't check the status of this because fancy_activate_editor
# will fail if the editor is not installed.
store.add_dlc(fe.dlc)
persistent.editor = fe.name
fancy_activate_editor()
return persistent.editor is not None
# Call fancy_activate_editor on startup.
fancy_activate_editor(True)
class SelectEditor(Action):
def __init__(self, name):
self.name = name
def get_selected(self):
return persistent.editor == self.name
def __call__(self):
return fancy_select_editor(self.name)
def check_editor():
"""
Checks to see if an editor is set. If one isn't asks the user to
select one.
Returns True if the editor is set and editing can proceed, and
Checks to see if an editor is set. If one isn't asks the user to
select one.
Returns True if the editor is set and editing can proceed, and
False otherwise.
"""
if not set_editor:
return True
if persistent.editor:
return True
return renpy.invoke_in_new_context(renpy.call_screen, "editor")
##########################################################################
# Editing actions.
@@ -280,42 +280,42 @@ init python in editor:
def __init__(self, filename, line=None, check=False):
"""
An action that opens the given line of the given file in a
text editor.
text editor.
`filename`
The filename to open.
`line`
The line in the file to jump to.
`check`
If true, we will check to see if the file exists, and gray
out the box if it does not.
"""
self.filename = filename
self.line = line
self.check = check
def get_sensitive(self):
if not self.check:
return True
fn = project.current.unelide_filename(self.filename)
return os.path.exists(fn)
def __call__(self):
if not self.get_sensitive():
return
if not check_editor():
return
fn = project.current.unelide_filename(self.filename)
try:
e = renpy.editor.editor
e.begin()
@@ -325,23 +325,68 @@ init python in editor:
except Exception, e:
exception = traceback.format_exception_only(type(e), e)[-1][:-1]
renpy.invoke_in_new_context(interface.error, _("An exception occured while launching the text editor:\n[exception!q]"), error_message, exception=exception)
class EditAbsolute(Action):
def __init__(self, filename, line=None, check=False):
"""
An action that lets us edit an absolutely-specified filename.
`filename`
The filename to open.
`line`
The line in the file to jump to.
`check`
If true, we will check to see if the file exists, and gray
out the box if it does not.
"""
self.filename = filename
self.line = line
self.check = check
def get_sensitive(self):
if not self.check:
return True
return os.path.exists(self.filename)
def __call__(self):
if not self.get_sensitive():
return
if not check_editor():
return
try:
e = renpy.editor.editor
e.begin()
e.open(self.filename, line=self.line)
e.end()
except Exception, e:
exception = traceback.format_exception_only(type(e), e)[-1][:-1]
renpy.invoke_in_new_context(interface.error, _("An exception occured while launching the text editor:\n[exception!q]"), error_message, exception=exception)
class EditAll(Action):
"""
Opens all scripts that are part of the current project in a web browser.
"""
def __init__(self):
return
def __call__(self):
if not check_editor():
return
scripts = project.current.script_files()
scripts = project.current.script_files()
scripts = [ i for i in scripts if not i.startswith("game/tl/") ]
scripts.sort(key=lambda fn : fn.lower())
@@ -349,12 +394,12 @@ init python in editor:
if fn in scripts:
scripts.remove(fn)
scripts.insert(0, fn)
try:
e = renpy.editor.editor
e.begin()
for fn in scripts:
fn = project.current.unelide_filename(fn)
e.open(fn)
@@ -366,56 +411,54 @@ init python in editor:
renpy.invoke_in_new_context(interface.error, _("An exception occured while launching the text editor:\n[exception!q]"), error_message, exception=exception)
screen editor:
frame:
style_group "l"
style "l_root"
window:
has vbox
label _("Select Editor")
add HALF_SPACER
hbox:
frame:
style "l_indent"
xfill True
viewport:
scrollbars "vertical"
mousewheel True
has vbox
text _("A text editor is the program you'll use to edit Ren'Py script files. Here, you can select the editor Ren'Py will use. If not already present, the editor will be automatically downloaded and installed.") style "l_small_text"
for fe in editor.fancy_editors:
add SPACER
textbutton fe.name action editor.SelectEditor(fe.name)
add HALF_SPACER
frame:
style "l_indent"
has vbox
if fe.description:
text fe.description style "l_small_text"
if not fe.installed:
add HALF_SPACER
text fe.dldescription style "l_small_text"
textbutton _("Cancel") action Return(False) style "l_left_button"
label editor_preference:
call screen editor
jump preferences
+80 -62
View File
@@ -2,9 +2,9 @@
# See LICENSE.txt for license details.
define PROJECT_ADJUSTMENT = ui.adjustment()
init python:
import os
import subprocess
@@ -13,113 +13,129 @@ init python:
Opens `directory` in a file browser. `directory` is relative to
the project root.
"""
def __init__(self, directory, absolute=False):
if absolute:
self.directory = directory
else:
self.directory = os.path.join(project.current.path, directory)
def get_sensitive(self):
return os.path.exists(self.directory)
def __call__(self):
directory = renpy.fsencode(self.directory)
if renpy.windows:
os.startfile(directory)
elif renpy.macintosh:
subprocess.Popen([ "open", directory ])
else:
subprocess.Popen([ "xdg-open", directory ])
# Used for testing.
def Relaunch():
renpy.quit(relaunch=True)
screen front_page:
frame:
style_group "l"
style "l_root"
has hbox
# Projects list section - on left.
frame:
style "l_projects"
xmaximum 300
right_margin 2
top_padding 20
bottom_padding 26
side "t c b":
window style "l_label":
text "PROJECTS:" style "l_label_text" size 36 yoffset 10
has hbox:
xfill True
text "PROJECTS:" style "l_label_text" size 36 yoffset 10
textbutton _("refresh"):
xalign 1.0
yalign 1.0
yoffset 5
style "l_small_button"
action project.Rescan()
right_margin HALF_INDENT
side "c r":
viewport:
yadjustment PROJECT_ADJUSTMENT
mousewheel True
use front_page_project_list
vbar:
style "l_vscrollbar"
adjustment PROJECT_ADJUSTMENT
vbox:
add HALF_SPACER
add SEPARATOR
add HALF_SPACER
textbutton _("+ Create New Project"):
left_margin (HALF_INDENT)
action Jump("new_project")
hbox:
xfill True
textbutton _("+ Create New Project"):
left_margin (HALF_INDENT)
action Jump("new_project")
# Project section - on right.
if project.current is not None:
use front_page_project
if project.current is not None:
textbutton _("Launch Project") action project.Launch() style "l_right_button"
# This is used by front_page to display the list of known projects on the screen.
screen front_page_project_list:
$ projects = project.manager.projects
vbox:
if projects:
for p in projects:
textbutton "[p.name!q]":
action project.Select(p)
style "l_list"
null height 12
textbutton _("Tutorial") action project.Select("tutorial") style "l_list"
textbutton _("The Question") action project.Select("the_question") style "l_list"
# This is used for the right side of the screen, which is where the project-specific
# This is used for the right side of the screen, which is where the project-specific
# buttons are.
screen front_page_project:
$ p = project.current
window:
has vbox
frame style "l_label":
@@ -130,10 +146,10 @@ screen front_page_project:
grid 2 1:
xfill True
spacing HALF_INDENT
vbox:
label _("Open Directory") style "l_label_small"
label _("Open Directory") style "l_label_small"
frame style "l_indent":
has vbox
@@ -142,30 +158,30 @@ screen front_page_project:
textbutton _("base") action OpenDirectory(".")
# textbutton _("images") action OpenDirectory("game/images") style "l_list"
# textbutton _("save") action None style "l_list"
vbox:
label _("Edit File") style "l_label_small"
frame style "l_indent":
has vbox
textbutton "script.rpy" action editor.Edit("game/script.rpy", check=True)
textbutton "options.rpy" action editor.Edit("game/options.rpy", check=True)
textbutton "screens.rpy" action editor.Edit("game/screens.rpy", check=True)
textbutton _("All script files") action editor.EditAll()
add SPACER
add SEPARATOR
add SPACER
frame style "l_indent":
has vbox
textbutton _("Navigate Script") text_size 30 action Jump("navigation")
add SPACER
grid 2 1:
xfill True
spacing HALF_INDENT
@@ -176,7 +192,7 @@ screen front_page_project:
textbutton _("Check Script (Lint)") action Jump("lint")
textbutton _("Change Theme") action Jump("choose_theme")
textbutton _("Delete Persistent") action Jump("rmpersistent")
# textbutton "Relaunch" action Relaunch
frame style "l_indent":
@@ -185,27 +201,29 @@ screen front_page_project:
if ability.can_distribute:
textbutton _("Build Distributions") action Jump("build_distributions")
textbutton _("Android") action Jump("android")
textbutton _("Generate Translations") action Jump("translate")
textbutton _("Extract Dialogue") action Jump("extract_dialogue")
label main_menu:
return
label start:
show screen bottom_info
label front_page:
call screen front_page
call screen front_page
jump front_page
label lint:
python hide:
interface.processing(_("Checking script for potential problems..."))
lint_fn = project.current.temp_filename("lint.txt")
project.current.launch([ 'lint', lint_fn ], wait=True)
e = renpy.editor.editor
e.begin(True)
e.open(lint_fn)
@@ -214,10 +232,10 @@ label lint:
jump front_page
label rmpersistent:
python hide:
interface.processing(_("Deleting persistent data..."))
project.current.launch([ 'rmpersistent' ], wait=True)
jump front_page
+177 -114
View File
@@ -9,15 +9,15 @@ init python in interface:
import os.path
import contextlib
RENPY_URL = "http://www.renpy.org"
RENPY_GAMES_URL = "http://games.renpy.org"
RENPY_GAMES_URL = "http://games.renpy.org"
DOC_PATH = os.path.join(config.renpy_base, "doc/index.html")
DOC_URL = "http://www.renpy.org/doc/html/"
LICENSE_PATH = os.path.join(config.renpy_base, "doc/license.html")
LICENSE_URL = "http://www.renpy.org/doc/html/license.html"
if os.path.exists(DOC_PATH):
DOC_LOCAL_URL = "file:///" + DOC_PATH
else:
@@ -27,12 +27,12 @@ init python in interface:
LICENSE_LOCAL_URL = "file:///" + LICENSE_PATH
else:
LICENSE_LOCAL_URL = None
def OpenDocumentation():
"""
An action that opens the documentation.
"""
if DOC_LOCAL_URL is not None:
return OpenURL(DOC_LOCAL_URL)
else:
@@ -42,97 +42,122 @@ init python in interface:
"""
An action that opens the license.
"""
if LICENSE_LOCAL_URL is not None:
return OpenURL(LICENSE_LOCAL_URL)
else:
return OpenURL(LICENSE_URL)
# Should we display the bottom links?
links = True
@contextlib.contextmanager
def nolinks():
global links
links = False
try:
yield
finally:
links = True
# This displays the bottom of the screen. If the tooltip is not None, this displays the
# tooltip. Otherwise, it displays a list of links (to various websites, and to the
# tooltip. Otherwise, it displays a list of links (to various websites, and to the
# preferences and update screen), or is just blank.
screen bottom_info:
zorder 100
if interface.links:
frame:
style_group "l"
style "l_default"
left_margin (10 + INDENT)
right_margin (10 + INDENT)
xfill True
ypos 536
yanchor 0.0
hbox:
xfill True
hbox:
spacing INDENT
textbutton _("Documentation") style "l_link" action interface.OpenDocumentation()
textbutton _("Ren'Py Website") style "l_link" action OpenURL(interface.RENPY_URL)
textbutton _("Ren'Py Games List") style "l_link" action OpenURL(interface.RENPY_GAMES_URL)
textbutton _("About") style "l_link" action Jump("about")
hbox:
spacing INDENT
xalign 1.0
if ability.can_update:
textbutton _("update") action Jump("update") style "l_link"
textbutton _("preferences") style "l_link" action Jump("preferences")
textbutton _("quit") style "l_link" action Quit(confirm=False)
screen common:
default complete = None
default total = None
default yes = None
default no = None
default choices = None
default cancel = None
default bar_value = None
frame:
style "l_root"
frame:
style_group "l_info"
has vbox
text message:
text_align 0.5
xalign 0.5
layout "subtitle"
if complete is not None:
add SPACER
frame:
style "l_progress_frame"
bar:
range total
value complete
style "l_progress_bar"
if bar_value is not None:
add SPACER
frame:
style "l_progress_frame"
bar:
value bar_value
style "l_progress_bar"
if choices:
add SPACER
for v, l in choices:
textbutton l action SetScreenVariable("selected", v)
if selected is not None:
$ continue_ = Return(selected)
else:
$ continue_ = None
if submessage:
add SPACER
@@ -143,21 +168,23 @@ screen common:
if yes:
add SPACER
hbox:
xalign 0.5
textbutton _("Yes") style "l_button" action yes
null width 160
textbutton _("No") style "l_button" action no
label title text_color title_color style "l_info_label"
if back:
textbutton _("Back") action Return(False) style "l_left_button"
textbutton _("Back") action back style "l_left_button"
elif cancel:
textbutton _("Cancel") action cancel style "l_left_button"
if continue_:
textbutton _("Continue") action Return(True) style "l_right_button"
textbutton _("Continue") action continue_ style "l_right_button"
screen launcher_input:
@@ -167,142 +194,163 @@ screen launcher_input:
frame:
style_group "l_info"
has vbox
text message
text message:
text_align 0.5
xalign 0.5
layout "subtitle"
add SPACER
input style "l_default" size 24 xalign 0.5 default default color INPUT_COLOR
input style "l_default" size 24 xalign 0.5 default default color INPUT_COLOR
if filename:
add SPACER
text _("Due to package format limitations, non-ASCII file and directory names are not allowed.")
label _("[title]") style "l_info_label" text_color QUESTION_COLOR
label title style "l_info_label" text_color QUESTION_COLOR
if cancel:
textbutton _("Cancel") action cancel style "l_left_button"
init python in interface:
import traceback
def common(title, title_color, message, submessage=None, back=False, continue_=False, pause0=False, **kwargs):
from store import Jump
def common(title, title_color, message, submessage=None, back=None, continue_=None, pause0=False, show_screen=False, **kwargs):
"""
Displays the info, interaction, and processing screens.
`title`
The title of the screen.
`message`
The main message that is displayed when the screen is.
`submessage`
If not None, a message that is displayed below the main message.
`back`
If True, a back button will be present. If it's clicked, False will
be returned.
If not None, a back button will be present. `back` is the action that
is called when the button is clicked.
`cancel`
If not None, a cancel button will be present. `cancel` is the action
that is called when the button is clicked.
`continue_`
If True, a continue button will be present. If it's clicked, True
will be returned.
If True, a continue button will be present. `continue_` gives the action
that is called when that button is clicked.
`pause0`
If True, a zero-length pause will be inserted before calling the
screen. This will display it to the user and then immediately
If True, a zero-length pause will be inserted before calling the
screen. This will display it to the user and then immediately
return.
`show_screen`
If True, the screen will be show, and will return immediately. if False,
the screen will be called, and interaction will pause.
Other keyword arguments are passed to the screen itself.
"""
if pause0:
ui.pausebehavior(0)
return renpy.call_screen("common", title=title, title_color=title_color, message=message, submessage=submessage, back=back, continue_=continue_, **kwargs)
if show_screen:
screen_func = renpy.show_screen
else:
screen_func = renpy.call_screen
if pause0:
ui.pausebehavior(0)
return screen_func("common", title=title, title_color=title_color, message=message, submessage=submessage, back=back, continue_=continue_, **kwargs)
def hide_screen():
"""
Hides a screen that was shown with show_screen=True.
"""
renpy.hide_screen("common")
def error(message, submessage=None, label="front_page", **kwargs):
"""
Indicates to the user that an error has occured.
`message`
The message to display.
The message to display.
`submessage`
Optional secondary message information. For example, this may be
Optional secondary message information. For example, this may be
used to display an exception string.
`label`
The label to redirect to when the user finishes displaying the error. None
to just return.
Keyword arguments are passed into the screen so that they can be substituted into
the message.
"""
common(_("ERROR"), store.ERROR_COLOR, message=message, submessage=submessage, back=True, **kwargs)
common(_("ERROR"), store.ERROR_COLOR, message=message, submessage=submessage, back=Jump(label), **kwargs)
if label:
renpy.jump(label)
@contextlib.contextmanager
def error_handling(what, label="front_page"):
"""
This is a context manager that catches exceptions and displays them using
This is a context manager that catches exceptions and displays them using
interface.error.
`what`
What we're doing when the error occurs. This is usually written using
What we're doing when the error occurs. This is usually written using
the present participle.
`label`
The label to jump to when error handling finishes.
As an example of usage::
with interface.error_handling("opening the log file"):
f = open("log.txt", "w")
f = open("log.txt", "w")
"""
try:
yield
except Exception, e:
import traceback
traceback.print_exc()
error(_("While [what!q], an error occured:"),
_("[exception!q]"),
what=what,
error(_("While [what!q], an error occured:"),
_("[exception!q]"),
what=what,
label=label,
exception=traceback.format_exception_only(type(e), e)[-1][:-1])
def input(title, message, filename=False, sanitize=True, cancel=None, default=""):
"""
Requests typewritten input from the user.
"""
rv = default
while True:
rv = renpy.call_screen("launcher_input", title=title, message=message, filename=filename, cancel=cancel, default=rv)
if sanitize:
if ("[" in rv) or ("{" in rv):
error(_("Text input may not contain the {{ or [[ characters."), label=None)
continue
if filename:
if filename and (filename != "withslash") and (("\\" in rv) or ("/" in rv)):
error(_("File and directory names may not contain / or \\."), label=None)
continue
try:
rv.encode("ascii")
except:
@@ -313,25 +361,25 @@ init python in interface:
def info(message, submessage=None, pause=True, **kwargs):
"""
Displays an informational message to the user. The user will be asked to click to
Displays an informational message to the user. The user will be asked to click to
confirm that he has read the message.
`message`
The message to display.
The message to display.
`pause`
True if we should pause while showing the info.
Keyword arguments are passed into the screen so that they can be substituted into
the message.
"""
if pause:
common(_("INFORMATION"), store.INFO_COLOR, message, submessage, continue_=True, **kwargs)
common(_("INFORMATION"), store.INFO_COLOR, message, submessage, continue_=Return(True), **kwargs)
else:
common(_("INFORMATION"), store.INFO_COLOR, message, submessage, pause=0, **kwargs)
common(_("INFORMATION"), store.INFO_COLOR, message, submessage, pause0=True, **kwargs)
def interaction(title, message, submessage=None, **kwargs):
"""
Put up on the screen while an interaction with an external program occurs.
@@ -339,53 +387,68 @@ init python in interface:
`title`
The title of the interaction.
`message`
The message itself.
`submessage`
An optional sub message.
"""
common(title, store.INTERACTION_COLOR, message, submessage, pause0=True, **kwargs)
def processing(message, submessage=None, complete=None, total=None, **kwargs):
"""
Indicates to the user that processing is taking place. This should be used when
there is an indefinite amount of work to be done.
`message`
The message to display.
The message to display.
`submessage`
An additional message to display.
`complete`
The fraction complete the step is.
`total`
The total amount of work to do in this step.
Keyword arguments are passed into the screen so that they can be substituted into
the message.
"""
common(_("PROCESSING"), store.INTERACTION_COLOR, message, submessage, pause0=True, complete=complete, total=total, **kwargs)
common(_("PROCESSING"), store.INTERACTION_COLOR, message, submessage, pause0=True, complete=complete, total=total, **kwargs)
def yesno(message, yes=Return(True), no=Return(False), **kwargs):
"""
Asks the user a yes or no question.
`message`
The question to ask.
`yes`
The action to perform if the user answers yes.
`no`
The action to perform if the user answer no.
"""
return common(_("QUESTION"), store.QUESTION_COLOR, message, yes=yes, no=no, **kwargs)
def choice(message, choices, selected, **kwargs):
"""
Asks the user to pick a choice from a menu.
`choices`
A list of (value, label) tuples, giving the choices.
`selected`
The default choice that we mark as selected.
"""
return common(_("CHOICE"), store.QUESTION_COLOR, message, choices=choices, selected=selected, **kwargs)
+53 -53
View File
@@ -6,25 +6,25 @@ init python in navigation:
import store.project as project
import store.editor as editor
from store import persistent, Action
# The last navigation screen we've seen. This is the scree we try to go
# to the next time we enter navigation. (We may not be able to go there,
# if the screen is empty.)
if persistent.navigation is None:
persistent.navigation = "label"
# A map from a kind of information, to how we should sort it. Possible
# A map from a kind of information, to how we should sort it. Possible
# sorts are alphabetical, by-file, natural.
if persistent.navigation_sort is None:
persistent.navigation_sort = { }
if persistent.navigate_private is None:
persistent.navigate_private = False
if persistent.navigate_library is None:
persistent.navigate_library = False
# A list of kinds of navigation we support.
KINDS = [ "file", "label", "define", "transform", "screen", "callable", "todo" ]
@@ -37,22 +37,22 @@ init python in navigation:
def group_and_sort(kind):
"""
This is responsible for pulling navigation information of the
appropriate kind out of project.current.dump, grouping it,
This is responsible for pulling navigation information of the
appropriate kind out of project.current.dump, grouping it,
and sorting it.
This returns a list of (group, list of (name, filename, line)). The
This returns a list of (group, list of (name, filename, line)). The
group may be a string or None.
"""
project.current.update_dump()
sort = persistent.navigation_sort[kind]
name_map = project.current.dump.get("location", {}).get(kind, { })
groups = { }
for name, loc in name_map.items():
filename, line = loc
filename = filename.replace("\\", "/")
@@ -63,56 +63,56 @@ init python in navigation:
group = filename
if group.startswith("game/"):
group = group[5:]
g = groups.get(group, None)
if g is None:
groups[group] = g = [ ]
g.append((name, filename, line))
for g in groups.values():
if sort == "natural":
g.sort(key=lambda a : a[2])
else:
g.sort(key=lambda a : a[0].lower())
rv = list(groups.items())
rv.sort()
return rv
def group_and_sort_files():
rv = [ ]
for fn in project.current.script_files():
shortfn = fn
shortfn = shortfn.replace("\\", "/")
if shortfn.startswith("game/"):
shortfn = fn[5:]
rv.append((shortfn, fn, None))
rv.sort()
return [ (None, rv) ]
class ChangeKind(Action):
"""
Changes the kind of thing we're navigating over.
"""
def __init__(self, kind):
self.kind = kind
def get_selected(self):
return persistent.navigation == self.kind
def __call__(self):
if persistent.navigation == self.kind:
return
persistent.navigation = self.kind
renpy.jump("navigation_loop")
@@ -123,38 +123,38 @@ init python in navigation:
def __init__(self, sort):
self.sort = sort
def get_selected(self):
return persistent.navigation_sort[persistent.navigation] == self.sort
def __call__(self):
if self.get_selected():
return
persistent.navigation_sort[persistent.navigation] = self.sort
renpy.jump("navigation_loop")
screen navigation:
frame:
style_group "l"
style "l_root"
window:
has vbox
frame style "l_label":
has hbox xfill True
text _("Navigate: [project.current.name]") style "l_label_text"
frame:
style "l_alternate"
style_group "l_small"
has hbox
if persistent.navigation != "file":
text _("Order: ")
textbutton _("alphabetical") action navigation.ChangeSort("alphabetical")
@@ -164,17 +164,17 @@ screen navigation:
textbutton _("natural") action navigation.ChangeSort("natural")
null width HALF_INDENT
textbutton _("refresh") action Jump("navigation_refresh")
add HALF_SPACER
frame style "l_indent":
hbox:
spacing HALF_INDENT
text _("Category:")
textbutton _("files") action navigation.ChangeKind("file")
textbutton _("labels") action navigation.ChangeKind("label")
textbutton _("defines") action navigation.ChangeKind("define")
@@ -199,19 +199,19 @@ screen navigation:
style_group "l_navigation"
for group_name, group in groups:
if group_name is not None:
text "[group_name!q]"
if persistent.navigation == "todo":
if persistent.navigation == "todo":
vbox:
for name, filename, line in group:
textbutton "[name!q]" action editor.Edit(filename, line)
else:
hbox:
box_wrap True
for name, filename, line in group:
textbutton "[name!q]" action editor.Edit(filename, line)
@@ -223,11 +223,11 @@ screen navigation:
textbutton _("+ Add script file") action Jump("add_file") style "l_button"
else:
fixed:
if persistent.navigation == "todo":
text _("No TODO comments found.\n\nTo create one, include \"# TODO\" in your script."):
text_align 0.5
xalign 0.5
@@ -244,18 +244,18 @@ screen navigation:
label navigation:
label navigation_loop:
python in navigation:
kind = persistent.navigation
kind = persistent.navigation
if kind == "file":
groups = group_and_sort_files()
else:
groups = group_and_sort(kind)
renpy.call_screen("navigation", groups=groups)
label navigation_refresh:
$ project.current.update_dump(True)
jump navigation_loop
+21 -23
View File
@@ -14,9 +14,9 @@ screen select_template:
frame:
style_group "l"
style "l_root"
window:
has vbox
label _("Choose Project Template")
@@ -26,18 +26,18 @@ screen select_template:
frame:
style "l_indent"
xmaximum ONETHIRD
viewport:
scrollbars "vertical"
vbox:
for p in project.manager.templates:
textbutton "[p.name!q]" action SetScreenVariable("result", p) style "l_list"
frame:
style "l_indent"
xmaximum TWOTHIRDS
text _("Please select a template to use for your new project. Ren'Py ships with a default template that creates an English-language game with standard screens.")
text _("Please select a template to use for your new project. The template sets the default font and the user interface language. If your language is not supported, choose 'english'.")
textbutton _("Back") action Jump("front_page") style "l_left_button"
@@ -45,27 +45,27 @@ screen select_template:
label new_project:
if persistent.projects_directory is None:
call choose_projects_directory
python hide:
project_name = interface.input(
_("PROJECT NAME"),
_("PROJECT NAME"),
_("Please enter the name of your project:"),
filename=True,
cancel=Jump("front_page"))
project_name = project_name.strip()
if not project_name:
interface.error(_("The project name may not be empty."))
project_dir = os.path.join(persistent.projects_directory, project_name)
if project.manager.get(project_name) is not None:
interface.error(_("[project_name!q] already exists. Please choose a different project name."), project_name=project_name)
if os.path.exists(project_dir):
interface.error(_("[project_dir!q] already exists. Please choose a different project name."), project_dir=project_dir)
@@ -75,25 +75,25 @@ label new_project:
template = renpy.call_screen("select_template")
template_path = template.path
with interface.error_handling("creating a new project"):
shutil.copytree(template_path, project_dir)
# Delete the tmp directory, if it exists.
if os.path.isdir(os.path.join(project_dir, "tmp")):
os.path.rmtree(os.path.join(project_dir, "tmp"))
# Delete project.json, which must exist.
os.unlink(os.path.join(project_dir, "project.json"))
# Change the save directory in options.rpy
fn = os.path.join(project_dir, "game/options.rpy")
with open(fn, "rb") as f:
options = f.read().decode("utf-8")
save_dir = project_name + "-" + str(int(time.time()))
options = re.sub(r'template-\d+', save_dir, options)
options = options.replace("PROJECT_NAME", project_name)
options = options.replace("UNIQUE", str(int(time.time())))
with open(fn, "wb") as f:
f.write(options.encode("utf-8"))
@@ -101,9 +101,7 @@ label new_project:
with interface.error_handling("activating the new project"):
project.manager.scan()
project.Select(project.manager.get(project_name))()
call choose_theme_callable
jump front_page
+23 -23
View File
@@ -35,7 +35,7 @@ init -1 python hide:
#########################################
# Themes
## We then want to call a theme function. themes.roundrect is
## a theme that features the use of rounded rectangles. It's
## the only theme we currently support.
@@ -46,7 +46,7 @@ init -1 python hide:
theme.roundrect(
## Theme: Roundrect
## Color scheme: Basic Blue
## The color of an idle widget face.
widget = "#003c78",
@@ -60,7 +60,7 @@ init -1 python hide:
## example, the current value of a preference.)
widget_selected = "#ffffc8",
## The color of a disabled widget face.
## The color of a disabled widget face.
disabled = "#404040",
## The color of disabled widget text.
@@ -88,7 +88,7 @@ init -1 python hide:
## And we're done with the theme. The theme will customize
## various styles, so if we want to change them, we should
## do so below.
## do so below.
)
#########################################
@@ -100,7 +100,7 @@ init -1 python hide:
## show help to the user.
## - A file name relative to the base directory, which is opened in a
## web browser.
## - None, to disable help.
## - None, to disable help.
config.help = "README.html"
#########################################
@@ -171,9 +171,9 @@ init -1 python hide:
config.has_autosave = False
config.log_enable = False
config.mouse_hide_time = 86400 * 366
_game_menu_screen = None
config.underlay = [
renpy.Keymap(
quit = renpy.quit_event,
@@ -188,37 +188,37 @@ init -1 python hide:
## This section controls how to build Ren'Py. (Building the launcher is how
## we build Ren'Py distributions.)
init python:
## We're building Ren'Py tonight.
build.renpy = True
## The version number that's supplied to the updater.
build.version = "Ren'Py {}".format(config.version)
## The name that's used for directories and archive files. For example, if
## this is 'mygame-1.0', the windows distribution will be in the
## directory 'mygame-1.0-win', in the 'mygame-1.0-win.zip' file.
build.directory_name = "renpy-" + config.version.rsplit('.', 1)[0]
## The name that's uses for executables - the program that users will run
## to start the game. For example, if this is 'mygame', then on Windows,
## users can click 'mygame.exe' to start the game.
build.executable_name = "renpy"
## If True, Ren'Py will include update information into packages. This
## allows the updater to run.
build.include_update = True
## Clear out various file patterns.
build.renpy_patterns = [ ]
build.early_base_patterns = [ ]
build.base_patterns = [ ]
build.late_base_patterns = [ ]
# We don't need to clear out the executable patterns, since they're
# We don't need to clear out the executable patterns, since they're
# correct for Ren'Py.
## Now, add the Ren'Py distribution in using classify_renpy.
## Now, add the Ren'Py distribution in using classify_renpy.
build.classify_renpy("**~", None)
build.classify_renpy("**/#*", None)
build.classify_renpy("**/thumbs.db", None)
@@ -241,18 +241,18 @@ init python:
# games.
build.classify_renpy("launcher/game/theme/", None)
build.classify_renpy("launcher/**", "source")
build.classify_renpy("template/**", "source")
build.classify_renpy("templates/**", "source")
build.classify_renpy("the_question/**", "source")
build.classify_renpy("tutorial/**", "source")
# docs.
# docs.
build.classify_renpy("doc/", "source")
build.classify_renpy("doc/.doctrees/", None)
build.classify_renpy("doc/_sources/", None)
build.classify_renpy("doc/**", "source")
build.classify_renpy("LICENSE.txt", "source")
# module.
# module.
build.classify_renpy("module/", "source")
build.classify_renpy("module/*.c", "source")
build.classify_renpy("module/gen/", "source")
@@ -283,8 +283,8 @@ init python:
build.classify_renpy("editra/Editra-mac.app/**", "editra-mac")
build.classify_renpy("editra/lib/**", "editra-windows")
build.classify_renpy("editra/editra.exe", "editra-windows")
# Executable rules.
build.executable("editra/Editra/Editra")
+46 -13
View File
@@ -2,18 +2,19 @@
# See LICENSE.txt for license details.
init python in distribute:
import time
import zipfile
import tarfile
import zlib
import struct
import stat
import shutil
from zipfile import crc32
zlib.Z_DEFAULT_COMPRESSION = 9
class ZipFile(zipfile.ZipFile):
def write_with_info(self, zinfo, filename):
@@ -80,25 +81,29 @@ init python in distribute:
self.fp.seek(position, 0)
self.filelist.append(zinfo)
self.NameToInfo[zinfo.filename] = zinfo
class ZipPackage(object):
"""
A class that creates a zip file.
"""
def __init__(self, filename):
self.zipfile = ZipFile(filename, "w", zipfile.ZIP_DEFLATED)
def add_file(self, name, path, xbit):
if path is None:
raise Exception("path for " + name + " must not be None.")
zi = zipfile.ZipInfo(name)
s = os.stat(path)
zi.date_time = time.gmtime(s.st_mtime)[:6]
try:
s = os.stat(path)
zi.date_time = time.gmtime(s.st_mtime)[:6]
except:
zi.date_time = time.gmtime()[:6]
zi.compress_type = zipfile.ZIP_DEFLATED
zi.create_system = 3
@@ -120,7 +125,7 @@ init python in distribute:
zi.compress_type = zipfile.ZIP_STORED
zi.create_system = 3
zi.external_attr = (long(0040755) << 16) | 0x10
self.zipfile.write_with_info(zi, path)
def close(self):
@@ -128,15 +133,15 @@ init python in distribute:
class TarPackage(object):
def __init__(self, filename, mode, notime=False):
"""
notime
If true, times will be forced to the epoch.
"""
self.tarfile = tarfile.open(filename, mode)
self.tarfile.dereference = True
self.tarfile.dereference = True
self.notime = notime
def add_file(self, name, path, xbit):
@@ -173,3 +178,31 @@ init python in distribute:
def close(self):
self.tarfile.close()
class DirectoryPackage(object):
def mkdir(self, path):
if not os.path.isdir(path):
os.mkdir(path, 0755)
def __init__(self, path):
self.path = path
self.mkdir(path)
def add_file(self, name, path, xbit):
fn = os.path.join(self.path, name)
shutil.copy2(path, fn)
if xbit:
os.chmod(fn, 0755)
else:
os.chmod(fn, 0644)
def add_directory(self, name, path):
fn = os.path.join(self.path, name)
self.mkdir(fn)
def close(self):
return
+887 -887
View File
File diff suppressed because it is too large Load Diff
+56 -56
View File
@@ -4,93 +4,93 @@
init python:
if persistent.gl_enable is None:
persistent.gl_enable = True
config.gl_enable = persistent.gl_enable
if persistent.windows_console is None:
persistent.windows_console = False
def scan_translations():
languages = renpy.known_languages()
if not languages:
return None
rv = [ ( "English", None) ]
for i in languages:
rv.append((i.title(), i))
return rv
screen preferences:
$ translations = scan_translations()
frame:
style_group "l"
style "l_root"
window:
has vbox
label _("Launcher Preferences")
add HALF_SPACER
hbox:
frame:
style "l_indent"
xmaximum ONETHIRD
xfill True
has vbox
# Projects directory selection.
add SEPARATOR2
frame:
style "l_indent"
yminimum 75
has vbox
text _("Projects Directory:")
add HALF_SPACER
frame style "l_indent":
frame style "l_indent":
if persistent.projects_directory:
textbutton _("[persistent.projects_directory!q]") action Jump("projects_directory_preference")
else:
textbutton _("Not Set") action Jump("projects_directory_preference")
add SPACER
# Text editor selection.
add SEPARATOR2
frame:
style "l_indent"
yminimum 75
has vbox
text _("Text Editor:")
add HALF_SPACER
frame style "l_indent":
frame style "l_indent":
if persistent.editor:
textbutton persistent.editor action Jump("editor_preference")
else:
textbutton _("Not Set") action Jump("editor_preference")
add SPACER
if ability.can_update:
@@ -102,48 +102,48 @@ screen preferences:
style "l_indent"
yminimum 75
has vbox
text _("Update Channel:")
add HALF_SPACER
frame style "l_indent":
frame style "l_indent":
textbutton persistent.update_channel action Jump("update_preference")
frame:
style "l_indent"
xmaximum ONETHIRD
xfill True
has vbox
add SEPARATOR2
frame:
style "l_indent"
yminimum 75
has vbox
text _("Navigation Options:")
add HALF_SPACER
textbutton _("Include private names") style "l_checkbox" action ToggleField(persistent, "navigate_private")
textbutton _("Include library names") style "l_checkbox" action ToggleField(persistent, "navigate_library")
add SPACER
add SEPARATOR2
frame:
style "l_indent"
yminimum 75
has vbox
text _("Launcher Options:")
add HALF_SPACER
textbutton _("Hardware rendering") style "l_checkbox" action ToggleField(persistent, "gl_enable")
if renpy.windows:
textbutton _("Console output") style "l_checkbox" action ToggleField(persistent, "windows_console")
@@ -156,39 +156,39 @@ screen preferences:
has vbox
add SEPARATOR2
frame:
style "l_indent"
yminimum 75
has vbox
text _("Actions:")
add HALF_SPACER
textbutton _("Open launcher project") style "l_nonbox" action [ project.Select("launcher"), Jump("front_page") ]
if translations:
add SPACER
# Text editor selection.
add SEPARATOR2
frame:
style "l_indent"
yminimum 75
has vbox
text _("Language:")
add HALF_SPACER
# frame style "l_indent":
# frame style "l_indent":
for tlname, tlvalue in translations:
textbutton tlname action Language(tlvalue) style "l_list"
textbutton _("Back") action Jump("front_page") style "l_left_button"
+136 -118
View File
@@ -22,9 +22,9 @@ init python in project:
import subprocess
import re
import tempfile
class Project(object):
def __init__(self, path):
while path.endswith("/"):
@@ -32,30 +32,30 @@ init python in project:
if not os.path.exists(path):
raise Exception("{} does not exist.".format(path))
# The name of the project.
self.name = os.path.basename(path)
# The path to the project.
self.path = path
# The path to the game directory.
gamedir = os.path.join(path, "game")
if os.path.isdir(gamedir):
self.gamedir = gamedir
else:
self.gamedir = path
# Load the data.
self.load_data()
# The project's temporary directory.
self.tmp = None
self.tmp = None
# This contains the result of dumping information about the game
# to disk.
self.dump = { }
# The mtime of the last dump file loaded.
self.dump_mtime = 0
@@ -87,57 +87,60 @@ init python in project:
def update_data(self):
data = self.data
data.setdefault("build_update", False)
data.setdefault("packages", [ "all" ])
def make_tmp(self):
"""
Makes the project's temporary directory, if it doesn't exist
Makes the project's temporary directory, if it doesn't exist
yet.
"""
if self.tmp and os.path.isdir(self.tmp):
return
tmp = os.path.join(self.path, "tmp")
try:
os.mkdir(tmp)
except:
pass
if os.path.isdir(tmp):
self.tmp = tmp
return
self.tmp = tempfile.mkdtemp()
def temp_filename(self, filename):
"""
Returns a filename in the temporary directory.
"""
self.make_tmp()
return os.path.join(self.tmp, filename)
def launch(self, args=[], wait=False):
def launch(self, args=[], wait=False, env={}):
"""
Launches the project.
Launches the project.
`args`
Additional arguments to give to the project.
`wait`
If true, waits for the launched project to terminate before
continuing.
`env`
Additional variables to include in the environment.
"""
self.make_tmp()
# Find the python executable to run.
executable_path = os.path.dirname(sys.executable)
if renpy.renpy.windows:
extension = ".exe"
else:
@@ -147,7 +150,7 @@ init python in project:
executables = [ "python" + extension ]
else:
executables = [ "pythonw" + extension ]
executables.append(sys.executable)
for i in executables:
@@ -156,51 +159,56 @@ init python in project:
break
else:
raise Exception("Python interpreter not found: %r", executables)
# Put together the basic command line.
cmd = [ executable, "-EOO", sys.argv[0] ]
cmd = [ executable, "-EO", sys.argv[0] ]
cmd.append(self.path)
cmd.extend(args)
# Add flags to dump game info.
cmd.append("--json-dump")
cmd.append(self.get_dump_filename())
if persistent.navigate_private:
cmd.append("--json-dump-private")
if persistent.navigate_library:
cmd.append("--json-dump-common")
environ = dict(os.environ)
environ.update(env)
for k in environ:
environ[k] = renpy.fsencode(environ[k])
# Launch the project.
with interface.error_handling("launching the project"):
cmd = [ renpy.fsencode(i) for i in cmd ]
p = subprocess.Popen(cmd)
if wait:
p.wait()
cmd = [ renpy.fsencode(i) for i in cmd ]
p = subprocess.Popen(cmd, env=environ)
if wait:
p.wait()
def update_dump(self, force=False, gui=True):
"""
If the dumpfile does not exist, runs Ren'Py to create it. Otherwise,
If the dumpfile does not exist, runs Ren'Py to create it. Otherwise,
loads it in iff it's newer than the one that's already loaded.
"""
dump_filename = self.get_dump_filename()
if force or not os.path.exists(dump_filename):
if gui:
interface.processing(_("Ren'Py is scanning the project..."))
self.launch(["quit"], wait=True)
if not os.path.exists(dump_filename):
self.dump["error"] = True
return
file_mtime = os.path.getmtime(dump_filename)
if file_mtime == self.dump_mtime:
return
@@ -212,7 +220,7 @@ init python in project:
self.dump = json.load(f)
# add todo list to dump data
self.update_todos()
except:
self.dump["error"] = True
@@ -223,7 +231,7 @@ init python in project:
"""
todos = self.dump.setdefault("location", {})["todo"] = {}
files = self.script_files()
for f in files:
@@ -232,12 +240,12 @@ init python in project:
for l, line in enumerate(data):
l += 1
m = re.search(r".*#\s*TODO(\s*:\s*|\s+)(.*)", line, re.I)
if m is None:
continue
raw_todo_text = m.group(2).strip()
todo_text = raw_todo_text
@@ -246,7 +254,7 @@ init python in project:
while not todo_text or todo_text in todos:
index += 1
todo_text = "{0} ({1})".format(raw_todo_text, index)
todos[todo_text] = [f, l]
@@ -254,7 +262,7 @@ init python in project:
"""
Unelides the filename relative to the project base.
"""
fn1 = os.path.join(self.path, fn)
if os.path.exists(fn1):
return fn1
@@ -262,90 +270,90 @@ init python in project:
fn2 = os.path.join(config.renpy_base, fn)
if os.path.exists(fn2):
return fn2
return fn
def script_files(self):
"""
Return a list of the script files that make up the project. These
are elided, and so need to be passed to unelide_filename before they
can be included in the project.
"""
rv = [ ]
rv.extend(i for i, isdir in util.walk(self.path) if (not isdir) and (i.endswith(".rpy") or i.endswith(".rpym")) )
return rv
class ProjectManager(object):
"""
This maintains a list of the various types of projects that
we know about.
"""
def __init__(self):
# The projects directory.
self.projects_directory = ""
# Normal projects, in alphabetical order by lowercase name.
self.projects = [ ]
# Template projects.
self.templates = [ ]
# All projects - normal, template, and hidden.
self.all_projects = [ ]
# Directories that have been scanned.
self.scanned = set()
self.scan()
def scan(self):
"""
Scans for projects.
"""
if (persistent.projects_directory is not None) and not os.path.isdir(persistent.projects_directory):
persistent.projects_directory = None
self.projects_directory = persistent.projects_directory
self.projects = [ ]
self.templates = [ ]
self.all_projects = [ ]
self.scanned = set()
if self.projects_directory is not None:
self.scan_directory(self.projects_directory)
self.scan_directory(config.renpy_base)
self.scan_directory(os.path.join(config.renpy_base, "templates"))
self.projects.sort(key=lambda p : p.name.lower())
self.templates.sort(key=lambda p : p.name.lower())
self.projects.sort(key=lambda p : p.name.lower())
self.templates.sort(key=lambda p : p.name.lower())
def scan_directory(self, d):
"""
Scans for projects in directories directly underneath `d`.
"""
global current
d = os.path.abspath(d)
if not os.path.isdir(d):
return
for pdir in os.listdir(d):
ppath = os.path.join(d, pdir)
# A project must be a directory.
if not os.path.isdir(ppath):
continue
# A project has either a game/ directory, or a project.json
# file.
if (not os.path.isdir(os.path.join(ppath, "game"))) and (not os.path.exists(os.path.join(ppath, "project.json"))):
@@ -354,52 +362,52 @@ init python in project:
if ppath in self.scanned:
continue
self.scanned.add(ppath)
# We have a project directory, so create a Project.
p = Project(ppath)
project_type = p.data.get("type", "normal")
if project_type == "hidden":
pass
elif project_type == "template":
self.templates.append(p)
else:
self.projects.append(p)
self.projects.append(p)
self.all_projects.append(p)
# Select the default project.
if persistent.active_project is not None:
p = self.get(persistent.active_project)
if p is not None:
current = p
return
p = self.get("tutorial")
if p is not None:
current = p
return
current = None
def get(self, name):
"""
Gets the project with the given name.
Returns None if the project doesn't exist.
"""
for p in self.all_projects:
if p.name == name:
return p
return None
manager = ProjectManager()
manager = ProjectManager()
# The current project.
current = None
@@ -409,38 +417,38 @@ init python in project:
An action that causes p to become the selected project when it was
clicked. If label is not None, jumps to the given label.
"""
def __init__(self, p, label=None):
"""
`p`
Either a project object, or a string giving the name of a
Either a project object, or a string giving the name of a
project.
`label`
The label to jump to when clicked.
"""
if isinstance(p, basestring):
p = manager.get(p)
self.project = p
self.label = label
def get_selected(self):
if self.project is None:
return False
if current is None:
return False
return current.path == self.project.path
def get_sensitive(self):
return self.project is not None
def __call__(self):
global current
current = self.project
persistent.active_project = self.project.name
@@ -448,14 +456,14 @@ init python in project:
if self.label is not None:
renpy.jump(self.label)
class Launch(Action):
"""
An action that launches the supplied project, or the current
project if no project is supplied.
"""
def __init__(self, p=None):
if p is None:
self.project = current
@@ -463,13 +471,23 @@ init python in project:
self.project = manager.get(p)
else:
self.project = p
def get_sensitive(self):
return self.project is not None
def __call__(self):
self.project.launch()
class Rescan(Action):
def __call__(self):
"""
Rescans the projects directory.
"""
manager.scan()
renpy.restart_interaction()
manager.scan()
if isinstance(persistent.projects_directory, str):
@@ -477,13 +495,13 @@ init python in project:
###############################################################################
# Code to choose the projects directory.
label choose_projects_directory:
python hide:
interface.interaction(_("PROJECTS DIRECTORY"), _("Please choose the projects directory using the directory chooser.\n{b}The directory chooser may have opened behind this window.{/b}"), _("This launcher will scan for projects in this directory, will create new projects in this directory, and will place built projects into this directory."),)
path = persistent.projects_directory
if path:
@@ -493,34 +511,34 @@ label choose_projects_directory:
default_path = os.path.dirname(os.path.abspath(config.renpy_base))
except:
default_path = os.path.abspath(config.renpy_base)
if EasyDialogs:
choice = EasyDialogs.AskFolder(defaultLocation=default_path, wanted=unicode)
if choice is not None:
path = choice
path = choice
else:
path = None
else:
try:
cmd = [ "/usr/bin/python", os.path.join(config.gamedir, "tkaskdir.py"), renpy.fsencode(default_path) ]
p = subprocess.Popen(cmd, stdout=subprocess.PIPE)
choice = p.stdout.read()
choice = p.stdout.read()
p.wait()
if choice:
path = renpy.fsdecode(choice)
except:
import traceback
traceback.print_exc()
path = None
interface.error(_("Ren'Py was unable to run python with tkinter to choose the projects directory."), label=None)
@@ -530,7 +548,7 @@ label choose_projects_directory:
path = renpy.fsdecode(path)
persistent.projects_directory = path
persistent.projects_directory = path
project.manager.scan()
return
+1 -3
View File
@@ -1,4 +1,2 @@
init -999:
$ config.script_version = (6, 15, 7)
$ config.script_version = (6, 16, 1)
+46 -38
View File
@@ -19,14 +19,14 @@ init -1 python:
# Colors for the scrollbar thumb.
SCROLLBAR_IDLE = "#dfdfdf"
SCROLLBAR_HOVER = "#d86b45"
# An image used as a separator pattern.
PATTERN = "pattern.png"
# A displayable used for the background of everything.
BACKGROUND = "background.png"
# A displayable used for the background of windows
BACKGROUND = "background.png"
# A displayable used for the background of windows
# containing commands, preferences, and navigation info.
WINDOW = Frame("window.png", 0, 0, tile=True)
@@ -35,7 +35,7 @@ init -1 python:
# A displayable used the background of information boxes.
INFO_WINDOW = "#f9f9f9"
# Colors for the titles of information boxes.
ERROR_COLOR = "#d15353"
INFO_COLOR = "#545454"
@@ -65,7 +65,7 @@ init 1 python:
LIGHT = "Roboto-Light.ttf"
REGULAR = "Roboto-Regular.ttf"
DARK = "Roboto-Medium.ttf"
# DIVIDING THE SCREEN
ONETHIRD = 258
TWOTHIRDS = 496
@@ -78,7 +78,7 @@ init 1 python:
style.l_default.idle_color = IDLE
style.l_default.hover_color = HOVER
style.l_default.size = 18
style.l_text = Style(style.l_default)
style.l_button = Style(style.l_default)
@@ -104,8 +104,8 @@ init 1 python:
style.l_left_button = Style(style.l_right_button)
style.l_left_button.xalign = 0.0
style.l_left_button_text = Style(style.l_right_button_text)
# The root frame. This contains everything but the bottom navigation, back
# button, and tooltip button.
style.l_root = Style(style.l_default)
@@ -113,24 +113,24 @@ init 1 python:
style.l_root.xpadding = 10
style.l_root.top_padding = 64
style.l_root.bottom_padding = 128
# An inner window.
style.l_window = Style(style.l_default)
style.l_window.background = WINDOW
style.l_window.background = WINDOW
style.l_window.left_padding = 6
style.l_window.xfill = True
style.l_window.yfill = True
# Normal-sized labels.
style.l_label = Style(style.l_default)
style.l_label.xfill = True
style.l_label.top_padding = 10
style.l_label.bottom_padding = 8
style.l_label.bottom_margin = 12
style.l_label.background = SEPARATOR
style.l_label.background = SEPARATOR
style.l_label_text = Style(style.l_default)
style.l_label_text.size = 24
style.l_label_text.xpos = INDENT
@@ -141,12 +141,12 @@ init 1 python:
style.l_label_small.xfill = True
style.l_label_small.bottom_padding = 8
style.l_label_small.bottom_margin = HALF_SPACER_HEIGHT
style.l_label_small.background = SEPARATOR
style.l_label_small.background = SEPARATOR
style.l_label_small_text = Style(style.l_default)
style.l_label_small_text.xpos = INDENT
style.l_label_small_text.yoffset = 6
style.l_label_small_text.size = 20
# Alternate labels. This nests inside an l_label, and gives a button
# or label that's nested inside another label.
style.l_alternate = Style(style.l_default)
@@ -158,21 +158,21 @@ init 1 python:
style.l_alternate_text.size = 14
style.l_alternate_text.font = LIGHT
style.l_alternate_text.text_align = 1.0
style.l_small_button = Style(style.l_button)
style.l_small_button_text = Style(style.l_button_text)
style.l_small_button_text.size = 14
style.l_small_text = Style(style.l_text)
style.l_small_text.size = 14
# Indents its contents.
style.l_indent = Style(style.l_default)
style.l_indent.left_margin = INDENT
# Indents its contents and pads them vertically.
style.l_indent_margin = Style(style.l_indent)
style.l_indent_margin.ymargin = 6
style.l_indent_margin.ymargin = 6
# List button.
style.l_list = Style(style.l_default)
style.l_list.left_padding = HALF_INDENT
@@ -205,14 +205,14 @@ init 1 python:
style.l_vscrollbar.bar_vertical = True
style.l_vscrollbar.bar_invert = True
style.l_vscrollbar.unscrollable = "hide"
# Information window.
style.l_info_vbox = Style(style.vbox)
style.l_info_vbox.yalign = 0.5
style.l_info_vbox.xalign = 0.5
style.l_info_vbox.xfill = True
style.l_info_frame = Style(style.l_default)
style.l_info_frame.ypadding = 21
style.l_info_frame.xfill = True
@@ -221,25 +221,28 @@ init 1 python:
INFO_WINDOW,
Frame(PATTERN, 0, 0, tile=True, ymaximum=5, yalign=0.0, yoffset=8),
Frame(PATTERN, 0, 0, tile=True, ymaximum=5, yalign=1.0, yoffset=-8),
)
)
style.l_info_frame.yminimum = 180
style.l_info_frame.ypos = 100
style.l_info_frame.ypos = 75
style.l_info_label = Style(style.l_default)
style.l_info_label.xalign = 0.5
style.l_info_label.ypos = 100
style.l_info_label.ypos = 75
style.l_info_label.yanchor = 1.0
style.l_info_label.yoffset = 12
style.l_info_label_text = Style(style.l_default)
style.l_info_label_text.size = 36
style.l_info_text = Style(style.l_default)
style.l_info_text.xalign = 0.5
style.l_info_button = Style(style.l_button)
style.l_info_button.xalign = 0.5
style.l_info_button.xmargin = 50
style.l_info_button_text = Style(style.l_button_text)
style.l_info_button_text.text_align = 0.5
style.l_info_button_text.layout = "subtitle"
# Code navigation
style.l_navigation_button = Style(style.l_button)
@@ -258,33 +261,33 @@ init 1 python:
# Check boxes
style.l_checkbox = Style(style.l_button)
style.l_checkbox.left_padding = INDENT
def checkbox(full, color):
if full:
return im.Twocolor("checkbox_full.png", color, color, yalign=0.5)
else:
return im.Twocolor("checkbox_empty.png", color, color, yalign=0.5)
style.l_checkbox.background = checkbox(False, IDLE)
style.l_checkbox.hover_background = checkbox(False, HOVER)
style.l_checkbox.selected_idle_background = checkbox(True, IDLE)
style.l_checkbox.selected_hover_background = checkbox(True, HOVER)
style.l_checkbox.insensitive_background = checkbox(False, DISABLED)
style.l_checkbox_text = Style(style.l_button_text)
style.l_checkbox_text.selected_font = LIGHT
# A normal button that lines up with checkboxes.
style.l_nonbox = Style(style.l_button)
style.l_nonbox.xpadding = INDENT
style.l_nonbox_text = Style(style.l_button_text)
style.l_nonbox_text.selected_font = LIGHT
# A progress bar and its frame.
style.l_progress_frame = Style(style.l_default)
style.l_progress_frame.background = Frame(PATTERN, 0, 0, tile=True)
style.l_progress_frame.ypadding = 5
style.l_progress_bar = Style(style.l_default)
style.l_progress_bar.left_bar = REVERSE_IDLE
style.l_progress_bar.right_bar = Null()
@@ -293,3 +296,8 @@ init 1 python:
# The projects window.
style.l_projects = Style(style.l_default)
style.l_projects.background = PROJECTS_WINDOW
style.hyperlink_text.size = 18
style.hyperlink_text.font = LIGHT
style.hyperlink_text.color = IDLE
style.hyperlink_text.hover_color = HOVER
+44
View File
@@ -0,0 +1,44 @@
# Copyright 2004-2013 Tom Rothamel <pytom@bishoujo.us>
# See LICENSE.txt for license details.
init python:
class FileTail(object):
def __init__(self, filename, lines=8):
self.filename = filename
self.text = ""
self.lines = lines
def update(self):
try:
with open(self.filename) as f:
text = f.read()
text = text.strip()
text = text.split("\n")
newtext = [ ]
for l in text:
if "\r" in l:
_head, _sep, l = l.rpartition("\r")
while l:
newtext.append(l[:100])
l = l[100:]
text = newtext
text = text[-self.lines:]
text = "\n".join(text)
if text != self.text:
self.text = text
renpy.restart_interaction()
except:
pass
+20 -20
View File
@@ -9,18 +9,18 @@ init python in theme_data:
x = 0
correct_section = False
while x < len(lines):
l = lines[x]
m = re.match(r'^screen\s+\w+\s*:\s*$', l)
if m:
correct_section = False
m = re.match(r'^screen\s+file_picker\s*:\s*$', l)
if m:
correct_section = True
if correct_section:
m = re.match(r'^\s+\$\s*columns\s*=\s*(\d+)\s*$', l)
if m:
@@ -28,7 +28,7 @@ init python in theme_data:
m = re.match(r'^\s+\$\s*rows\s*=\s*(\d+)\s*$', l)
if m:
l = l.replace(str(m.group(1)), str(file_picker_rows))
lines[x] = l
x = x + 1
@@ -37,7 +37,7 @@ init python in theme_data:
def roundrect_screen_ops(lines):
return __PerformScreenOperations(lines)
def awt_screen_ops(lines):
return __PerformScreenOperations(lines, file_picker_rows=3)
@@ -49,12 +49,12 @@ init python in theme_data:
# The set of theme functions.
THEME_FUNCTIONS = set()
# theme name -> function name to call to munge screens.rpy as appropriate
# for that theme.
THEME_SCREEN_OPERATIONS = { }
# Color schemes that work (technically - some are eye-melting) with the
# Color schemes that work (technically - some are eye-melting) with the
# roundrect-style themes.
ROUNDRECT_SCHEMES = {
'Basic Blue': {'disabled': '#404040',
@@ -337,7 +337,7 @@ init python in theme_data:
'widget_hover': '#b38698',
'widget_selected': '#fffeed',
'widget_text': '#ffffff'},
'Watermelon': {'disabled': '#f9cdad',
'disabled_text': '#fc9d9a',
'frame': '#f9cdad',
@@ -462,11 +462,11 @@ init python in theme_data:
("Marker", "marker"),
("Crayon", "crayon"),
]
ROUNDRECT_CODE = """theme.%(function)s(
## Theme: %(theme)s
## Color scheme: %(scheme)s
## The color of an idle widget face.
widget = "%(widget)s",
@@ -480,7 +480,7 @@ init python in theme_data:
## example, the current value of a preference.)
widget_selected = "%(widget_selected)s",
## The color of a disabled widget face.
## The color of a disabled widget face.
disabled = "%(disabled)s",
## The color of disabled widget text.
@@ -508,9 +508,9 @@ init python in theme_data:
## And we're done with the theme. The theme will customize
## various styles, so if we want to change them, we should
## do so below.
## do so below.
)"""
for theme, function in ROUNDRECT_VARIANTS:
THEME[theme] = { }
THEME_FUNCTIONS.add(function)
@@ -521,13 +521,13 @@ init python in theme_data:
subs["function"] = function
subs["theme"] = theme
subs["scheme"] = scheme
THEME[theme][scheme] = ROUNDRECT_CODE % (subs)
AWT_CODE = """theme.a_white_tulip(
## Theme: A White Tulip
## Scheme %(scheme)s
## The color of an idle widget face.
widget = "%(widget)s",
@@ -538,7 +538,7 @@ init python in theme_data:
## example, the current value of a preference.)
widget_selected = "%(widget_selected)s",
## The color of a disabled widget face.
## The color of a disabled widget face.
disabled = "%(disabled)s",
## The color of a frame containing widgets.
@@ -556,9 +556,9 @@ init python in theme_data:
## And we're done with the theme. The theme will customize
## various styles, so if we want to change them, we should
## do so below.
## do so below.
)"""
THEME["A White Tulip"] = { }
THEME_FUNCTIONS.add("a_white_tulip")
THEME_SCREEN_OPERATIONS["A White Tulip"] = awt_screen_ops
@@ -567,7 +567,7 @@ init python in theme_data:
for scheme, colors in ROUNDRECT_SCHEMES.iteritems():
subs = dict(colors)
subs["scheme"] = scheme
THEME["A White Tulip"][scheme] = AWT_CODE % (subs)
THEME["A White Tulip"]["A White Tulip"] = AWT_CODE % dict(
+3 -3
View File
@@ -3,7 +3,7 @@
# Copyright 2004-2012 Tom Rothamel <pytom@bishoujo.us>
# See LICENSE.txt for license details.
# This is used on Linux and Mac to prompt the user for the projects
# This is used on Linux and Mac to prompt the user for the projects
# directory.
import sys
@@ -18,9 +18,9 @@ except ImportError:
# Create the TK canvas.
if __name__ == "__main__":
if __name__ == "__main__":
root = Tk()
root.withdraw()
result = askdirectory(initialdir=sys.argv[1], parent=root, title="Select Ren'Py Projects Directory")
sys.stdout.write(result)
sys.stdout.write(result)
+17
View File
@@ -0,0 +1,17 @@
MTLc3m.ttf is destributed under Apache License 2.0.
--------------------------------
Copyright (C) 2008 The Android Open Source Project
Copyright (C) 2010 MOTOYA CO.,LTD.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Binary file not shown.
+16
View File
@@ -0,0 +1,16 @@
# TODO: Translation updated at 2013-10-24 18:29
translate japanese strings:
# game/about.rpy:21
old "[version!q]"
new "[version!q]"
# game/about.rpy:25
old "View license"
new "ライセンス"
# game/about.rpy:27
old "Back"
new "戻る"
+23
View File
@@ -0,0 +1,23 @@
translate japanese strings:
# game/add_file.rpy:7
old "FILENAME"
new "ファイル名"
# game/add_file.rpy:7
old "Enter the name of the script file to create."
new "作成するスクリプトファイル名を入力してください。"
# game/add_file.rpy:10
old "The filename must have the .rpy extension."
new "ファイル名は拡張子 .rpy を持つ必要があります。"
# game/add_file.rpy:18
old "The file already exists."
new "ファイルは既に存在します。"
# game/add_file.rpy:21
old "# Ren'Py automatically loads all script files ending with .rpy. To use this\n# file, define a label and jump to it from another file.\n"
new "# Ren'Pyは .rpy で終わるすべてのスクリプトファイルを自動的にロードします。このファイルを使用するためにはラベルを定義し、他のファイルからそこにジャンプしてください。\n"
+103
View File
@@ -0,0 +1,103 @@
translate japanese strings:
# game/android.rpy:12
old "To build Android packages, please download RAPT (from {a=http://www.renpy.org/dl/android}here{/a}), unzip it, and place it into the Ren'Py directory. Then restart the Ren'Py launcher."
new "Androidパッケージをビルドするためには{a=http://www.renpy.org/dl/android}RAPT{/a}をダウンロードして解凍し、Ren'Py ディレクトリーに配置してからランチャーを再起動してください。"
# game/android.rpy:13
old "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."
new "RAPT はインストールされましたが、Androidパッケージをビルドする前に Android SDK もインストールする必要があります。このためにSDKをインストールしてください。"
# game/android.rpy:14
old "RAPT has been installed, but a key hasn't been configured. Please create a new key, or restore android.keystore."
new "RAPT はインストールされましたが、キーが設定されていません。新しいキーを作成するか、android.keystore を復帰させてください。"
# game/android.rpy:15
old "The current project has not been configured. Use \"Configure\" to configure it before building."
new "現在のプロジェクトは設定されていません。「設定」を利用してビルド前に設定してください。"
# game/android.rpy:16
old "Choose \"Build\" to build the current project, or attach an Android device and choose \"Build & Install\" to build and install it on the device."
new "「ビルド」を選択して現在のプロジェクトをビルドするか、Android デバイスを接続して「ビルド & インストール」を選択し、ビルド後そのデバイスにインストールしてください。"
# game/android.rpy:18
old "Attempts to emulate an Android phone.\n\nTouch input is emulated through the mouse, but only when the button is held down. Escape is mapped to the menu button, and PageUp is mapped to the back button."
new "Android Phoneをエミュレートします。\n\nタッチ入力はマウスを利用してエミュレートされますが、ボタン押下時のみです。Escapeはメニューボタン、PageUpはバックボタンにマップされています。"
# game/android.rpy:19
old "Attempts to emulate an Android tablet.\n\nTouch input is emulated through the mouse, but only when the button is held down. Escape is mapped to the menu button, and PageUp is mapped to the back button."
new "Android tabletをエミュレートします。\n\nタッチ入力はマウスを利用してエミュレートされますが、ボタン押下時のみです。Escapeはメニューボタン、PageUpはバックボタンにマップされています。"
# game/android.rpy:20
old "Attempts to emulate an OUYA console.\n\nController input is mapped to the arrow keys, Enter is mapped to the select button, Escape is mapped to the menu button, and PageUp is mapped to the back button."
new "Android OUYAをエミュレートします。\n\nコントローラーの入力は矢印キー、Enterはセレクトボタン、Escapeはメニューボタン、PageUpはバックボタンにマップされています。"
# game/android.rpy:22
old "Downloads and installs the Android SDK and supporting packages. Optionally, generates the keys required to sign the package."
new "Android SDKをダウンロード後インストールします。任意でサインに必要なキーを生成します。"
# game/android.rpy:23
old "Configures the package name, version, and other information about this project."
new "このプロジェクトについてパッケージ名、バージョン、その他の情報を設定してください。"
# game/android.rpy:24
old "Opens the file containing the Google Play keys in the editor.\n\nThis is only needed if the application is using an expansion APK. Read the documentation for more details."
new "Google Play key を含むファイルをエディターで開いてください。\n\nこれはアプリが拡張APKを使用する場合のみ必要となります。詳細はドキュメントを参照してください。"
# game/android.rpy:25
old "Builds the Android package."
new "Androidパッケージをビルドします。"
# game/android.rpy:26
old "Builds the Android package, and installs it on an Android device connected to your computer."
new "Androidパッケージをビルドし、コンピューターに接続されたAndroidデバイスにインストールします。"
# game/android.rpy:142
old "{a=%s}%s{/a}"
new "{a=%s}%s{/a}"
# game/android.rpy:151
old "QUESTION"
new "質問"
# game/android.rpy:351
old "Android: [project.current.name!q]"
new "Android: [project.current.name!q]"
# game/android.rpy:371
old "Emulation:"
new "エミュレーター:"
# game/android.rpy:379
old "Phone"
new "Phone"
# game/android.rpy:383
old "Tablet"
new "Tablet"
# game/android.rpy:387
old "Television / OUYA"
new "Television / OUYA"
# game/android.rpy:399
old "Build:"
new "ビルド:"
# game/android.rpy:407
old "Install SDK & Create Keys"
new "SDKのインストール & キーの作成"
# game/android.rpy:411
old "Configure"
new "設定"
# game/android.rpy:415
old "Build Package"
new "パッケージのビルド"
# game/android.rpy:419
old "Build & Install"
new "ビルド & インストール"
@@ -0,0 +1,43 @@
translate japanese strings:
# game/choose_theme.rpy:274
old "Could not change the theme. Perhaps options.rpy was changed too much."
new "テーマを変更出来ません。 options.rpy が変更されすぎているかもしれません。"
# game/choose_theme.rpy:332
old "Display"
new "Display"
# game/choose_theme.rpy:333
old "Window"
new "Window"
# game/choose_theme.rpy:334
old "Fullscreen"
new "Fullscreen"
# game/choose_theme.rpy:335
old "Planetarium"
new "Planetarium"
# game/choose_theme.rpy:342
old "Sound Volume"
new "Sound Volume"
# game/choose_theme.rpy:376
old "Choose Theme"
new "Choose Theme"
# game/choose_theme.rpy:389
old "Theme"
new "テーマ"
# game/choose_theme.rpy:413
old "Color Scheme"
new "カラースキーム"
# game/choose_theme.rpy:444
old "Continue"
new "続行"
+312
View File
@@ -0,0 +1,312 @@
translate japanese strings:
# renpy/common/00action_file.rpy:124
old "%b %d, %H:%M"
new "%m月%d日, %H:%M"
# renpy/common/00action_file.rpy:587
old "Quick save complete."
new "クイックセーブ完了。"
translate japanese strings:
# renpy/common/00gallery.rpy:521
old "Image [index] of [count] locked."
new "画像 [index] / [count] はロックされています。"
# renpy/common/00gallery.rpy:539
old "prev"
new "前へ"
# renpy/common/00gallery.rpy:540
old "next"
new "次へ"
# renpy/common/00gallery.rpy:541
old "slideshow"
new "スライドショー"
# renpy/common/00gallery.rpy:542
old "return"
new "戻る"
translate japanese strings:
# renpy/common/00gltest.rpy:50
old "Graphics Acceleration"
new "グラフィック・アクセラレーション"
# renpy/common/00gltest.rpy:54
old "Automatically Choose"
new "自動選択"
# renpy/common/00gltest.rpy:59
old "Force Angle/DirectX Renderer"
new "Angle/DirectX による描画を強制"
# renpy/common/00gltest.rpy:63
old "Force OpenGL Renderer"
new "OpenGL による描画を強制"
# renpy/common/00gltest.rpy:67
old "Force Software Renderer"
new "ソフトウェアによる描画を強制"
# renpy/common/00gltest.rpy:73
old "Changes will take effect the next time this program is run."
new "変更は、次にプログラムを起動した時に反映されます。"
# renpy/common/00gltest.rpy:77
old "Quit"
new "終了"
# renpy/common/00gltest.rpy:82
old "Return"
new "戻る"
# renpy/common/00gltest.rpy:112
old "Performance Warning"
new "パフォーマンスに関する注意"
# renpy/common/00gltest.rpy:117
old "This computer is using software rendering."
new "このコンピュータはソフトウェアレンダリングを使用しています。"
# renpy/common/00gltest.rpy:119
old "This computer is not using shaders."
new "このコンピューターはシェーダーを使用していません。"
# renpy/common/00gltest.rpy:121
old "This computer is displaying graphics slowly."
new "このコンピューターは描画速度が遅いです。"
# renpy/common/00gltest.rpy:123
old "This computer has a problem displaying graphics: [problem]."
new "このコンピューターはグラフィックスの表示に問題があります: [problem]。"
# renpy/common/00gltest.rpy:128
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をアップデートするとこの問題を解決できます。"
# renpy/common/00gltest.rpy:130
old "Its graphics drivers may be out of date or not operating correctly. This can lead to slow or incorrect graphics display."
new "グラフィックドライバーは古いか、あるいは正常に動作していません。これはグラフィックスの表示が遅くなったり、正常に表示されない原因となります。"
# renpy/common/00gltest.rpy:135
old "Update DirectX"
new "DirectXのアップデート"
# renpy/common/00gltest.rpy:141
old "Continue, Show this warning again"
new "今後も、この注意を表示する"
# renpy/common/00gltest.rpy:145
old "Continue, Don't show warning again"
new "今後は、この注意を表示しない"
# renpy/common/00gltest.rpy:171
old "Updating DirectX."
new "DirectXのアップデート中"
# renpy/common/00gltest.rpy:175
old "DirectX web setup has been started. It may start minimized in the taskbar. Please follow the prompts to install DirectX."
new "DirectXウェブセットアップが開始されました。タスクバーに最小化された状態で開始したので、画面に従ってDirectXをインストールして下さい。"
# renpy/common/00gltest.rpy:179
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} MicrosoftのDirectXウェブセットアッププログラムは、デフォルトでBingツールバーをインストールします。このツールバーが不要な場合は、対応したチェックボックスを解除して下さい。"
# renpy/common/00gltest.rpy:183
old "When setup finishes, please click below to restart this program."
new "セットアップが完了したら、以下をクリックしてプログラムを再起動して下さい。"
# renpy/common/00gltest.rpy:185
old "Restart"
new "再起動"
translate japanese strings:
# renpy/common/00keymap.rpy:168
old "Saved screenshot as %s."
new "スクリーンショットを %s に保存しました。"
translate japanese strings:
# renpy/common/00layout.rpy:421
old "Are you sure?"
new "本当にいいですか?"
# renpy/common/00layout.rpy:422
old "Are you sure you want to delete this save?"
new "この保存データを本当に削除しますか?"
# renpy/common/00layout.rpy:423
old "Are you sure you want to overwrite your save?"
new "本当に保存データを上書きしますか?"
# renpy/common/00layout.rpy:424
old "Loading will lose unsaved progress.\nAre you sure you want to do this?"
new "読み込みによって未保存のゲームが失われます。\n本当に読み込みますか?"
# renpy/common/00layout.rpy:425
old "Are you sure you want to quit?"
new "本当にゲームを終了しますか?"
# renpy/common/00layout.rpy:426
old "Are you sure you want to return to the main menu?\nThis will lose unsaved progress."
new "本当にメインメニューに戻りますか?\nこれによって未保存のゲームが失われます。"
# renpy/common/00layout.rpy:427
old "Are you sure you want to begin skipping?"
new "本当にスキップモードを開始しますか?"
# renpy/common/00layout.rpy:428
old "Are you sure you want to skip to the next choice?"
new "本当に次の選択肢までスキップしますか?"
# renpy/common/00layout.rpy:429
old "Are you sure you want to skip to unseen dialogue or the next choice?"
new "本当に未読の会話、もしくは次の選択肢までスキップしますか?"
translate japanese strings:
# renpy/common/00library.rpy:77
old "Skip Mode"
new "スキップモード"
# renpy/common/00library.rpy:80
old "Fast Skip Mode"
new "高速スキップモード"
translate japanese strings:
# renpy/common/00updater.rpy:1258
old "Updater"
new "アップデーター"
# renpy/common/00updater.rpy:1267
old "This program is up to date."
new "このプログラムは最新版です。"
# renpy/common/00updater.rpy:1269
old "[u.version] is available. Do you want to install it?"
new "[u.version] が利用可能です。インストールしますか?"
# renpy/common/00updater.rpy:1271
old "Preparing to download the updates."
new "アップデートをダウンロードする準備をしています。"
# renpy/common/00updater.rpy:1273
old "Downloading the updates."
new "アップデートをダウンロードしています。"
# renpy/common/00updater.rpy:1275
old "Unpacking the updates."
new "アップデートを展開しています。"
# renpy/common/00updater.rpy:1279
old "The updates have been installed. The program will restart."
new "アップデートがインストールされました。プログラムを再起動します。"
# renpy/common/00updater.rpy:1281
old "The updates have been installed."
new "アップデートがインストールされました。"
# renpy/common/00updater.rpy:1283
old "The updates were cancelled."
new "アップデートがキャンセルされました。"
translate japanese strings:
# renpy/common/_compat/gamemenu.rpym:180
old "Empty Slot."
new "空のスロット"
# renpy/common/_compat/gamemenu.rpym:337
old "Previous"
new "前へ"
# renpy/common/_compat/gamemenu.rpym:344
old "Next"
new "次へ"
translate japanese strings:
# renpy/common/_compat/preferences.rpym:411
old "Joystick Mapping"
new "ジョイスティックのマッピング"
translate japanese strings:
# renpy/common/_errorhandling.rpym:408
old "An exception has occurred."
new "例外が発生しました。"
# renpy/common/_errorhandling.rpym:434
old "Rollback"
new "ロールバック"
# renpy/common/_errorhandling.rpym:436
old "Attempts a roll back to a prior time, allowing you to save or choose a different choice."
new "一つ前へのロールバックを試み、セーブまたは他の選択肢を選ぶことを許可します。"
# renpy/common/_errorhandling.rpym:439
old "Ignore"
new "無視"
# renpy/common/_errorhandling.rpym:441
old "Ignores the exception, allowing you to continue. This often leads to additional errors."
new "例外を無視し、ゲームの継続を許可します。この場合、付加的なエラーが発生する原因となることがあります。"
# renpy/common/_errorhandling.rpym:444
old "Reload"
new "再読み込み"
# renpy/common/_errorhandling.rpym:446
old "Reloads the game from disk, saving and restoring game state if possible."
new "ディスクからゲームを再読み込みし、可能であればゲームの保存と復元をします。"
# renpy/common/_errorhandling.rpym:448
old "Open Traceback"
new "トレースバックを開く"
# renpy/common/_errorhandling.rpym:450
old "Opens the traceback.txt file in a text editor."
new "テキストエディターで traceback.txt を開きます。"
# renpy/common/_errorhandling.rpym:456
old "Quits the game."
new "ゲームを終了します。"
# renpy/common/_errorhandling.rpym:483
old "Parsing the script failed."
new "スクリプトの変換に失敗しました。"
# renpy/common/_errorhandling.rpym:510
old "Open Parse Errors"
new "変換エラーを開く"
# renpy/common/_errorhandling.rpym:512
old "Opens the errors.txt file in a text editor."
new "テキストエディターで errors.txt を開きます。"
translate japanese strings:
# renpy/common/_layout/classic_load_save.rpym:152
old "a"
new "a"
# renpy/common/_layout/classic_load_save.rpym:161
old "q"
new "q"
+35
View File
@@ -0,0 +1,35 @@
translate japanese strings:
# game/distribute.rpy:335
old "Nothing to do."
new "何もしません。"
# game/distribute.rpy:339
old "Scanning project files..."
new "プロジェクトをスキャンしています…"
# game/distribute.rpy:346
old "Scanning Ren'Py files..."
new "Ren'Pyファイルをスキャンしています…"
# game/distribute.rpy:496
old "Archiving files..."
new "ファイルをアーカイブしています…"
# game/distribute.rpy:747
old "Writing the [variant] [format] package."
new "[variant] [format] パッケージを書き出しています。."
# game/distribute.rpy:760
old "Making the [variant] update zsync file."
new "[variant] のアップデート用 zsync ファイルを書き出しています。"
# game/distribute.rpy:856
old "Processed {b}[complete]{/b} of {b}[total]{/b} files."
new "{b}[total]{/b} 中 {b}[complete]{/b} ファイルが処理されました。"
# game/distribute.rpy:917
old "All packages have been built.\n\nDue to the presence of permission information, unpacking and repacking the Linux and Macintosh distributions on Windows is not supported."
new "すべてのパッケージがビルドされました。\n\nパーミッション情報のために、Windows上でのLinuxやMacintosh用の配布物の解凍、再圧縮はサポートされません。"
@@ -0,0 +1,47 @@
translate japanese strings:
# game/distribute_gui.rpy:139
old "Build Distributions: [project.current.name!q]"
new "配布物のビルド: [project.current.name!q]"
# game/distribute_gui.rpy:154
old "Directory Name:"
new "ディレクトリー名:"
# game/distribute_gui.rpy:158
old "Executable Name:"
new "実行ファイル名:"
# game/distribute_gui.rpy:167
old "Actions:"
new "アクション:"
# game/distribute_gui.rpy:175
old "Edit options.rpy"
new "options.rpy を編集"
# game/distribute_gui.rpy:176
old "Refresh"
new "更新"
# game/distribute_gui.rpy:193
old "Build Packages:"
new "パッケージをビルド"
# game/distribute_gui.rpy:208
old "Build Updates"
new "アップデートをビルド"
# game/distribute_gui.rpy:212
old "Build"
new "ビルド"
# game/distribute_gui.rpy:219
old "Errors were detected when running the project. Please ensure the project runs without errors before building distributions."
new "プロジェクトの実行時にエラーが検出されました。配布物をビルドする前にエラーなしでプロジェクトが実行するようにしてください。"
# game/distribute_gui.rpy:236
old "Your project does not contain build information. Would you like to add build information to the end of options.rpy?"
new "プロジェクトにビルド情報がありません。options.rpy の末尾にビルド情報を追加しますか?"
+58
View File
@@ -0,0 +1,58 @@
translate japanese strings:
# game/editor.rpy:120
old "{b}Recommended.{/b} A beta editor with an easy to use interface and features that aid in development, such as spell-checking. Editra currently lacks the IME support required for Chinese, Japanese, and Korean text input."
new "{b}推奨{/b} スペルチュックのような開発目的のインターフェースや機能を簡単に使用可能なベータエディターです。Editraは現在中国、韓国、日本語の入力に必要な IME のサポートに欠陥があります。"
# game/editor.rpy:121
old "{b}Recommended.{/b} A beta editor with an easy to use interface and features that aid in development, such as spell-checking. Editra currently lacks the IME support required for Chinese, Japanese, and Korean text input. On Linux, Editra requires wxPython."
new "{b}推奨{/b} スペルチェックのような開発目的のインターフェースや機能を簡単に使用可能なベータエディターです。Editraは現在中国、韓国、日本語の入力に必要な IME のサポートに欠陥があります。LinuxではEditraはwxpythonを必要とします。"
# game/editor.rpy:137
old "This may have occured because wxPython is not installed on this system."
new "これはおそらくwxPythonがこのシステムにインストールされていないため発生しました。"
# game/editor.rpy:144
old "Up to 22 MB download required."
new "22MB までのダウンロードが必要です。"
# game/editor.rpy:157
old "1.8 MB download required."
new "1.8 MB のダウンロードが必要です。"
# game/editor.rpy:158
old "This may have occured because Java is not installed on this system."
new "これはおそらくJavaがこのシステムにインストールされていないため発生しました。"
# game/editor.rpy:327
old "An exception occured while launching the text editor:\n[exception!q]"
new "テキストエディターの選択中にエラーが発生しました:\n[exception!q]"
# game/editor.rpy:423
old "Select Editor"
new "エディターを選択"
# game/editor.rpy:438
old "A text editor is the program you'll use to edit Ren'Py script files. Here, you can select the editor Ren'Py will use. If not already present, the editor will be automatically downloaded and installed."
new "テキストエディタはRen'Pyスクリプトファイルを編集するためのプログラムです。ここではRen'Pyが使用するエディターを選択可能です。存在しなければエディターは自動的にダウンロードされインストールされます。"
# game/editor.rpy:460
old "Cancel"
new "キャンセル"
translate japanese strings:
# game/editor.rpy:155
old "A mature editor that requires Java."
new "Javaを必要とする成熟したエディターです。"
# game/editor.rpy:164
old "Invokes the editor your operating system has associated with .rpy files."
new "オペレーティングシステムで .rpy ファイルに関連づけたエディターを実行します。"
# game/editor.rpy:180
old "Prevents Ren'Py from opening a text editor."
new "Ren'Pyのテキストエディターの実行を停止します。"
+87
View File
@@ -0,0 +1,87 @@
translate japanese strings:
# game/front_page.rpy:67
old "refresh"
new "更新"
# game/front_page.rpy:94
old "+ Create New Project"
new "+ 新規プロジェクトの作成"
# game/front_page.rpy:106
old "Launch Project"
new "プロジェクトの起動"
# game/front_page.rpy:127
old "Tutorial"
new "Tutorial"
# game/front_page.rpy:128
old "The Question"
new "The Question"
# game/front_page.rpy:144
old "Active Project"
new "アクティブなプロジェクト"
# game/front_page.rpy:152
old "Open Directory"
new "ディレクトリーを開く"
# game/front_page.rpy:157
old "game"
new "game"
# game/front_page.rpy:158
old "base"
new "base"
# game/front_page.rpy:164
old "Edit File"
new "ファイル編集"
# game/front_page.rpy:172
old "All script files"
new "すべてのスクリプト"
# game/front_page.rpy:181
old "Navigate Script"
new "スクリプトのナビゲート"
# game/front_page.rpy:192
old "Check Script (Lint)"
new "スクリプトチェック(Lint)"
# game/front_page.rpy:193
old "Change Theme"
new "テーマ変更"
# game/front_page.rpy:194
old "Delete Persistent"
new "永続データ削除"
# game/front_page.rpy:202
old "Build Distributions"
new "配布物のビルド"
# game/front_page.rpy:204
old "Android"
new "Android"
# game/front_page.rpy:205
old "Generate Translations"
new "翻訳の生成"
# game/front_page.rpy:206
old "Extract Dialogue"
new "台詞の抽出"
# game/front_page.rpy:222
old "Checking script for potential problems..."
new "潜在的な問題のためにスクリプトをチェックしています…"
# game/front_page.rpy:237
old "Deleting persistent data..."
new "永続データを削除しています…"
+83
View File
@@ -0,0 +1,83 @@
translate japanese strings:
# game/interface.rpy:89
old "Documentation"
new "ドキュメント"
# game/interface.rpy:90
old "Ren'Py Website"
new "Ren'Pyサイト"
# game/interface.rpy:91
old "Ren'Py Games List"
new "Ren'Pyゲームリスト"
# game/interface.rpy:92
old "About"
new "About"
# game/interface.rpy:99
old "update"
new "アップデート"
# game/interface.rpy:101
old "preferences"
new "設定"
# game/interface.rpy:102
old "quit"
new "終了"
# game/interface.rpy:174
old "Yes"
new "Yes"
# game/interface.rpy:176
old "No"
new "No"
# game/interface.rpy:211
old "Due to package format limitations, non-ASCII file and directory names are not allowed."
new "パッケージフォーマットの制限により、アスキーコード以外のファイル、ディレクトリー名は許可されません。"
# game/interface.rpy:213
old "[title]"
new "[title]"
# game/interface.rpy:299
old "ERROR"
new "エラー"
# game/interface.rpy:328
old "While [what!q], an error occured:"
new "[what!q] 中にエラーが発生しました。:"
# game/interface.rpy:329
old "[exception!q]"
new "[exception!q]"
# game/interface.rpy:347
old "Text input may not contain the {{ or [[ characters."
new "{{ または [[ はテキスト入力出来ません。"
# game/interface.rpy:352
old "File and directory names may not contain / or \\."
new "ファイル、ディレクトリーが / または \\. を含んでいるかもしれません。"
# game/interface.rpy:358
old "File and directory names must consist of ASCII characters."
new "ファイル、ディレクトリー名はアスキーコードの文字列で構成される必要があります。"
# game/interface.rpy:379
old "INFORMATION"
new "情報"
# game/interface.rpy:422
old "PROCESSING"
new "処理中"
# game/interface.rpy:452
old "CHOICE"
new "選択"
+67
View File
@@ -0,0 +1,67 @@
translate japanese strings:
# game/navigation.rpy:150
old "Navigate: [project.current.name]"
new "ナビゲーション: [project.current.name]"
# game/navigation.rpy:159
old "Order: "
new "並び順:"
# game/navigation.rpy:160
old "alphabetical"
new "アルファベット順"
# game/navigation.rpy:162
old "by-file"
new "ファイル順"
# game/navigation.rpy:164
old "natural"
new "natural"
# game/navigation.rpy:176
old "Category:"
new "カテゴリー:"
# game/navigation.rpy:178
old "files"
new "ファイル"
# game/navigation.rpy:179
old "labels"
new "ラベル"
# game/navigation.rpy:180
old "defines"
new "定義"
# game/navigation.rpy:181
old "transforms"
new "変換"
# game/navigation.rpy:182
old "screens"
new "スクリーン"
# game/navigation.rpy:183
old "callables"
new "関数"
# game/navigation.rpy:184
old "TODOs"
new "TODO"
# game/navigation.rpy:223
old "+ Add script file"
new "+ スクリプトファイルを追加する。"
# game/navigation.rpy:231
old "No TODO comments found.\n\nTo create one, include \"# TODO\" in your script."
new "TODOコメントは見つかりません。\n\n作成には \"# TODO\" をスクリプトに加えてください。"
# game/navigation.rpy:238
old "The list of names is empty."
new "名前のリストは空です。"
+31
View File
@@ -0,0 +1,31 @@
translate japanese strings:
# game/new_project.rpy:22
old "Choose Project Template"
new "テンプレートを選んでください"
# game/new_project.rpy:40
old "Please select a template to use for your new project. The template sets the default font and the user interface language. If your language is not supported, choose 'english'."
new "新しいプロジェクトで使用するテンプレートを選択してください。Ren'Pyはデフォルトのフォントとユーザーインターフェイスの言語を設定します。あなたの言語がサポートされていないなら 'english' を選択してください。"
# game/new_project.rpy:55
old "PROJECT NAME"
new "プロジェクト名"
# game/new_project.rpy:56
old "Please enter the name of your project:"
new "プロジェクト名を入力してください:"
# game/new_project.rpy:62
old "The project name may not be empty."
new "プロジェクト名が空です。"
# game/new_project.rpy:67
old "[project_name!q] already exists. Please choose a different project name."
new "[project_name!q] は既に存在します。違う名前を選択してください。"
# game/new_project.rpy:70
old "[project_dir!q] already exists. Please choose a different project name."
new "[project_dir!q] は既に存在します。違う名前を選択してください。"
+59
View File
@@ -0,0 +1,59 @@
translate japanese strings:
# game/preferences.rpy:40
old "Launcher Preferences"
new "ランチャー設定"
# game/preferences.rpy:61
old "Projects Directory:"
new "プロジェクトディレクトリー:"
# game/preferences.rpy:68
old "[persistent.projects_directory!q]"
new "[persistent.projects_directory!q]"
# game/preferences.rpy:70
old "Not Set"
new "未設定"
# game/preferences.rpy:84
old "Text Editor:"
new "テキストエディター:"
# game/preferences.rpy:106
old "Update Channel:"
new "アップデートチャンネル:"
# game/preferences.rpy:126
old "Navigation Options:"
new "ナビゲートオプション:"
# game/preferences.rpy:130
old "Include private names"
new "内部使用の名前を含める"
# game/preferences.rpy:131
old "Include library names"
new "本体の名前を含める"
# game/preferences.rpy:141
old "Launcher Options:"
new "ランチャー設定:"
# game/preferences.rpy:145
old "Hardware rendering"
new "ハードウェアレンダリング"
# game/preferences.rpy:148
old "Console output"
new "コンソール出力"
# game/preferences.rpy:169
old "Open launcher project"
new "ランチャーのプロジェクトを開く"
# game/preferences.rpy:183
old "Language:"
new "言語:"
+27
View File
@@ -0,0 +1,27 @@
translate japanese strings:
# game/project.rpy:204
old "Ren'Py is scanning the project..."
new "Ren'Py プロジェクトをスキャンしています…"
# game/project.rpy:503
old "PROJECTS DIRECTORY"
new "プロジェクトディレクトリー"
# game/project.rpy:503
old "Please choose the projects directory using the directory chooser.\n{b}The directory chooser may have opened behind this window.{/b}"
new "プロジェクトディレクトリーを選択してください。\n{b}ディレクトリー選択画面がこのウィンドウの下に隠れている場合があります。{/b}"
# game/project.rpy:503
old "This launcher will scan for projects in this directory, will create new projects in this directory, and will place built projects into this directory."
new "ランチャーはこのディレクトリーでプロジェクトをスキャンし、新しいプロジェクトを作成し、ビルドしたプロジェクトを出力します。"
# game/project.rpy:543
old "Ren'Py was unable to run python with tkinter to choose the projects directory."
new "Ren'Py はプロジェクトディレクトリーを選択するために tkinter でpythonを実行することは出来ません。"
# game/project.rpy:547
old "Ren'Py has set the projects directory to:"
new "Ren'Py は以下にプロジェクトディレクトリーを設定します:"
+15
View File
@@ -0,0 +1,15 @@
translate japanese python:
MTLC = "tl/japanese/MTLc3m.ttf"
style.l_default.font = MTLC
style.l_default.size = 16
style.l_button_text.selected_font = MTLC
style.l_button_text.selected_bold = True
style.l_link_text.font = MTLC
style.l_alternate_text.font = MTLC
style.l_navigation_button_text.font = MTLC
style.l_navigation_text.font = MTLC
style.l_navigation_text.bold = True
style.l_checkbox_text.selected_font = MTLC
style.l_nonbox_text.selected_font = MTLC
style.hyperlink_text.font = MTLC
make_style_backup()
@@ -0,0 +1,35 @@
translate japanese strings:
# game/translations.rpy:10
old "Create or Update Translations"
new "翻訳の作成または更新"
# game/translations.rpy:10
old "Please enter the name of the language for which you want to create or update translations."
new "翻訳を作成または更新したい言語名を入力してください。"
# game/translations.rpy:15
old "The language name can not be the empty string."
new "言語名は空の文字列であってはなりません。"
# game/translations.rpy:26
old "Ren'Py is generating translations...."
new "Ren'Pyは翻訳を生成しています…"
# game/translations.rpy:30
old "Ren'Py has finished generating [language] translations."
new "Ren'Pyは [language] の翻訳ファイルを生成しました。"
# game/translations.rpy:44
old "What format would you like for the extracted dialogue?"
new "どのフォーマットで台詞を抽出しますか?"
# game/translations.rpy:56
old "Ren'Py is extracting dialogue...."
new "Ren'Pyは台詞を抽出しています…"
# game/translations.rpy:60
old "Ren'Py has finished extracting dialogue. The extracted dialogue can be found in dialogue.[format] in the base directory."
new "Ren'Pyは台詞を抽出し終えました。抽出した台詞はベースディレクトリーの dialogue.[format] にあります。"
+87
View File
@@ -0,0 +1,87 @@
translate japanese strings:
# game/updater.rpy:54
old "Select Update Channel"
new "アップデートチャンネルを選択してください"
# game/updater.rpy:65
old "The update channel controls the version of Ren'Py the updater will download. Please select an update channel:"
new "アップデートチャンネルはアップデーターがダウンロードするRen'Pyのバージョンを制御します。アップデートチャンネルを選択してください。"
# game/updater.rpy:70
old "Release"
new "Release"
# game/updater.rpy:76
old "{b}Recommended.{/b} The version of Ren'Py that should be used in all newly-released games."
new "{b}推奨{/b} 全ての新しく配布されるゲームで使用されるべきRen'Pyのバージョンです。"
# game/updater.rpy:81
old "Prerelease"
new "Prerelease"
# game/updater.rpy:87
old "A preview of the next version of Ren'Py that can be used for testing and taking advantage of new features, but not for final releases of games."
new "テストや新機能利用のために使用されるRen'Pyの次期バージョンのプレビューですが、\nゲームの最終リリースには向きません。"
# game/updater.rpy:93
old "Experimental"
new "Experimental"
# game/updater.rpy:99
old "Experimental versions of Ren'Py. You shouldn't select this channel unless asked by a Ren'Py developer."
new "Ren'Pyの試験的なバージョンです。開発者に頼まれない限りこのバージョンを使用するべきではありません。"
# game/updater.rpy:119
old "An error has occured:"
new "エラーが発生しました:"
# game/updater.rpy:121
old "Checking for updates."
new "アップデートをチェックしています。"
# game/updater.rpy:123
old "Ren'Py is up to date."
new "Ren'Pyは最新です。"
# game/updater.rpy:125
old "[u.version] is now available. Do you want to install it?"
new "[u.version] が利用可能です。インストールしますか?"
# game/updater.rpy:127
old "Preparing to download the update."
new "アップデートのダウンロード準備をしています。"
# game/updater.rpy:129
old "Downloading the update."
new "アップデートをダウンロードしています。"
# game/updater.rpy:131
old "Unpacking the update."
new "アップデートを解凍しています。"
# game/updater.rpy:133
old "Finishing up."
new "完了しました。"
# game/updater.rpy:135
old "The update has been installed. Ren'Py will restart."
new "アップデートはインストールされました。Ren'Pyは再起動します。"
# game/updater.rpy:137
old "The update has been installed."
new "アップデートはインストールされました。"
# game/updater.rpy:139
old "The update was cancelled."
new "アップデートはキャンセルされました。"
# game/updater.rpy:156
old "Ren'Py Update"
new "Ren'Py アップデート"
# game/updater.rpy:162
old "Proceed"
new "続行"
+37 -8
View File
@@ -8,27 +8,56 @@ label translate:
python:
language = interface.input(_("Create or Update Translations"), _("Please enter the name of the language for which you want to create or update translations."), filename=True, default=persistent.translate_language, cancel=Jump("front_page"))
language = language.strip()
if not language:
interface.error(_("The language name can not be the empty string."))
persistent.translate_language = language
args = [ "translate", language ]
if language == "rot13":
args.append("--rot13")
else:
args.append("--empty")
interface.processing(_("Ren'Py is generating translations...."))
project.current.launch(args, wait=True)
project.current.update_dump(force=True)
interface.info(_("Ren'Py has finished generating [language] translations."))
jump front_page
label extract_dialogue:
python:
CHOICES = [
("tab", "Tab-delimited Spreadsheet (dialogue.tab)"),
("txt", "Dialogue Text Only (dialogue.txt)"),
]
format = interface.choice(
_("What format would you like for the extracted dialogue?"),
CHOICES,
persistent.dialogue_format,
cancel=Jump("front_page"))
persistent.dialogue_format = format
args = [ "dialogue" ]
if format == "txt":
args.append("--text")
interface.processing(_("Ren'Py is extracting dialogue...."))
project.current.launch(args, wait=True)
project.current.update_dump(force=True)
interface.info(_("Ren'Py has finished extracting dialogue. The extracted dialogue can be found in dialogue.[format] in the base directory."))
jump front_page
+32 -32
View File
@@ -6,80 +6,80 @@ init python:
#
# It must be None for a release.
UPDATE_SIMULATE = None
PUBLIC_KEY = "renpy_public.pem"
UPDATE_URLS = {
"Release" : "http://update.renpy.org/release/updates.json",
"Prerelease" : "http://update.renpy.org/prerelease/updates.json",
"Experimental" : "http://update.renpy.org/experimental/updates.json"
"Experimental" : "http://update.renpy.org/experimental/updates.json"
}
version_tuple = renpy.version(tuple=True)
DLC_URL = "http://update.renpy.org/{0}.{1}.{2}/updates.json".format(version_tuple[0], version_tuple[1], version_tuple[2])
if persistent.update_channel not in UPDATE_URLS:
persistent.update_channel = "Release"
def check_dlc(name):
def check_dlc(name):
"""
Returns true if the named dlc package is present.
"""
return name in updater.get_installed_packages()
def add_dlc(name):
"""
Adds the DLC package, if it doesn't already exist.
Returns True if the DLC is installed, False otherwise.
"""
if check_dlc(name):
return True
return renpy.invoke_in_new_context(updater.update, DLC_URL, add=[name], public_key=PUBLIC_KEY, simulate=UPDATE_SIMULATE, restart=False)
screen update_channel:
frame:
style_group "l"
style "l_root"
window:
has vbox
label _("Select Update Channel")
add HALF_SPACER
hbox:
frame:
style "l_indent"
xfill True
has vbox
text _("The update channel controls the version of Ren'Py the updater will download. Please select an update channel:") style "l_small_text"
# Release
add SPACER
textbutton _("Release") action [ SetField(persistent, "update_channel", "Release"), Jump("preferences") ]
add HALF_SPACER
frame:
style "l_indent"
text _("{b}Recommended.{/b} The version of Ren'Py that should be used in all newly-released games.") style "l_small_text"
# Prerelease
add SPACER
textbutton _("Prerelease") action [ SetField(persistent, "update_channel", "Prerelease"), Jump("preferences") ]
add HALF_SPACER
frame:
@@ -91,14 +91,14 @@ screen update_channel:
add SPACER
textbutton _("Experimental") action [ SetField(persistent, "update_channel", "Experimental"), Jump("preferences") ]
add HALF_SPACER
frame:
style "l_indent"
text _("Experimental versions of Ren'Py. You shouldn't select this channel unless asked by a Ren'Py developer.") style "l_small_text"
textbutton _("Cancel") action Jump("preferences") style "l_left_button"
label update_preference:
@@ -106,13 +106,13 @@ label update_preference:
return
screen updater:
frame:
style "l_root"
frame:
style_group "l_info"
has vbox
if u.state == u.ERROR:
@@ -144,10 +144,10 @@ screen updater:
if u.progress is not None:
add SPACER
frame:
style "l_progress_frame"
bar:
range 1.0
value u.progress
@@ -157,15 +157,15 @@ screen updater:
if u.can_cancel:
textbutton _("Cancel") action u.cancel style "l_left_button"
if u.can_proceed:
textbutton _("Proceed") action u.proceed style "l_right_button"
label update:
python:
updater.update(UPDATE_URLS[persistent.update_channel], simulate=UPDATE_SIMULATE, public_key=PUBLIC_KEY)
# This should never happen.
jump front_page
+9 -9
View File
@@ -3,28 +3,28 @@
init python in util:
import os
def walk(directory, base=None):
"""
Walks through the directories and files underneath `directory`,
yielding (name, isdir) tuples. The names are given relative to
Walks through the directories and files underneath `directory`,
yielding (name, isdir) tuples. The names are given relative to
`base`, which defaults to `directory` if None.
"""
directory = renpy.fsdecode(directory)
if base is None:
base = directory
else:
base = renpy.fsdecode(base)
for subdir, directories, files in os.walk(directory):
for fn in directories:
fullfn = os.path.join(subdir, fn)
relfn = os.path.relpath(fullfn, base)
relfn = relfn.replace("\\", "/")
yield relfn, True
for fn in files:
@@ -32,6 +32,6 @@ init python in util:
relfn = os.path.relpath(fullfn, base)
relfn = relfn.replace("\\", "/")
yield relfn, False
+23 -23
View File
@@ -36,34 +36,34 @@ cdef extern from "renpy.h":
void core_init()
void save_png_core(object, SDL_RWops *, int)
void pixellate32_core(object, object, int, int, int, int)
void pixellate24_core(object, object, int, int, int, int)
void map32_core(object, object,
char *,
char *,
char *,
char *,
char *,
char *)
void map24_core(object, object,
char *,
char *,
char *,
char *)
void linmap32_core(object, object,
int,
int,
int,
int,
int,
int)
void linmap24_core(object, object,
int,
int,
int,
int)
void xblur32_core(object, object, int)
void alphamunge_core(object, object, int, int, int, char *)
void scale32_core(object, object,
@@ -93,9 +93,9 @@ cdef extern from "renpy.h":
int, int, int, int, int, char *)
int subpixel32(object, object, float, float, int)
void PyErr_Clear()
import pygame
@@ -108,7 +108,7 @@ def save_png(surf, file, compress=-1):
save_png_core(surf, RWopsFromPython(file), compress)
def pixellate(pysrc, pydst, avgwidth, avgheight, outwidth, outheight):
if not isinstance(pysrc, PygameSurface):
@@ -231,7 +231,7 @@ def alpha_munge(pysrc, pydst, srcchan, dstchan, amap):
# pydst.unlock()
# pysrc.unlock()
# def xblur(pysrc, pydst, radius):
@@ -283,7 +283,7 @@ def alpha_munge(pysrc, pydst, srcchan, dstchan, amap):
# w, h = pysrc.get_size()
# return stretch_core(pysrc, pydst, x, y, w, h)
def bilinear(pysrc, pydst,
source_xoff=0.0, source_yoff=0.0, source_width=None, source_height=None,
@@ -298,7 +298,7 @@ def bilinear(pysrc, pydst,
if pysrc.get_bitsize() not in (24, 32):
raise Exception("bilinear requires a 24 or 32 bit surface.")
if pydst.get_bitsize() != pysrc.get_bitsize():
raise Exception("bilinear requires both surfaces have the same bitsize.")
@@ -332,15 +332,15 @@ def check(surf):
if surf.get_bitsize() != 32:
raise Exception("Surface must be 32-bit.")
def transform(pysrc, pydst,
corner_x, corner_y,
xdx, ydx, xdy, ydy, a=1.0, precise=0):
check(pysrc)
check(pydst)
# pysrc.lock()
# pydst.lock()
@@ -359,7 +359,7 @@ def blend(pysrca, pysrcb, pydst, alpha):
check(pysrca)
check(pysrcb)
check(pydst)
# pysrca.lock()
# pysrcb.lock()
# pydst.lock()
@@ -375,14 +375,14 @@ def imageblend(pysrca, pysrcb, pydst, pyimg, aoff, amap):
check(pysrcb)
check(pydst)
check(pyimg)
# pysrca.lock()
# pysrcb.lock()
# pydst.lock()
# pyimg.lock()
imageblend32_core(pysrca, pysrcb, pydst, pyimg, aoff, amap)
# pyimg.unlock()
# pydst.unlock()
# pysrcb.unlock()
@@ -399,7 +399,7 @@ def colormatrix(pysrc, pydst,
# pysrc.lock()
# pydst.lock()
colormatrix32_core(pysrc, pydst,
c00, c01, c02, c03, c04,
c10, c11, c12, c13, c14,
@@ -421,8 +421,8 @@ def subpixel(pysrc, pydst, xoffset, yoffset, shift):
return
pydst.blit(pysrc, (int(xoffset), int(yoffset)))
# Be sure to update scale.py when adding something new here!
import_pygame_rwobject()
+1 -1
View File
@@ -16,7 +16,7 @@ RTL = FRIBIDI_TYPE_RTL
WRTL = FRIBIDI_TYPE_WR
def log2vis(s, int direction=FRIBIDI_TYPE_ON):
s = s.encode("utf8")
s = renpybidi_log2vis(s, &direction)
return s.decode("utf8"), direction
+36 -36
View File
@@ -57,13 +57,13 @@ print "CLASSES = {"
for i, j in enumerate((lines[0] + other_classes).split()):
print " \"{}\" : {},".format(j, i)
cl[j] = i
print "}"
rules = [ ]
for l in lines[1:]:
for c in l.split()[1:]:
for c in l.split()[1:]:
rules.append(c)
print
@@ -76,46 +76,46 @@ for l in file("LineBreak.txt"):
if m:
start = int(m.group(1), 16)
end = int(m.group(2), 16)
if start > 65535:
continue
if end > 65535:
end = 65535
for i in range(start, end + 1):
cc[i] = m.group(3)
continue
m = re.match("(\w+);(\w\w)", l)
if m:
start = int(m.group(1), 16)
if start > 65535:
continue
cc[start] = m.group(2)
if end > 65535:
end = 65535
for i in range(start, end + 1):
cc[i] = m.group(3)
continue
m = re.match("(\w+);(\w\w)", l)
if m:
start = int(m.group(1), 16)
if start > 65535:
continue
cc[start] = m.group(2)
continue
def generate(name, func):
ncc = [ ]
for i, ccl in enumerate(cc):
ncc.append(func(i, ccl))
assert "CJ" not in ncc
assert "AI" not in ncc
print "cdef char *break_" + name + " = \"" + "".join("\\x%02x" % cl[i] for i in ncc) + "\""
def western(i, cl):
if cl == "CJ":
return "ID"
elif cl == "AI":
return "AL"
return cl
hyphens = [ 0x2010, 0x2013, 0x301c, 0x30a0 ]
@@ -128,28 +128,28 @@ postfixes = [ 0x0025, 0x00A2, 0x00B0, 0x2030, 0x2032, 0x2033, 0x2103, 0xff05, 0x
prefixes = [ 0x0024, 0x00a3, 0x00a5, 0x20ac, 0x2116, 0xff04, 0xffe1, 0xffe5 ]
def cjk_strict(i, cl):
if cl == "CJ":
return "NS"
if cl == "AI":
return "ID"
return cl
def cjk_normal(i, cl):
if i in hyphens:
return "ID"
if cl == "CJ":
return "ID"
if cl == "AI":
return "ID"
return cl
def cjk_loose(i, cl):
if i in hyphens:
return "ID"
if i in iteration:
@@ -162,14 +162,14 @@ def cjk_loose(i, cl):
return "ID"
if i in prefixes:
return "ID"
if cl == "CJ":
return "ID"
if cl == "AI":
return "ID"
return cl
generate("western", western)
generate("cjk_strict", cjk_strict)
generate("cjk_normal", cjk_normal)
+35 -35
View File
@@ -10,7 +10,7 @@ cdef extern from "pyfreetype.h":
#Some tweaking may be needed on a platform-by-platform basis
DEF FT_SIZEOF_INT = 4
DEF FT_SIZEOF_LONG = 4
ctypedef signed short FT_Int16
ctypedef unsigned short FT_UInt16
IF FT_SIZEOF_INT == 4:
@@ -26,9 +26,9 @@ cdef extern from "pyfreetype.h":
ELIF FT_SIZEOF_LONG >= 4:
ctypedef long FT_Fast
ctypedef unsigned long FT_UFast
#fttypes.h
ctypedef unsigned char FT_Bool
ctypedef signed short FT_FWord
@@ -140,15 +140,15 @@ cdef extern from "pyfreetype.h":
(<unsigned long>c'l' << 16) |
(<unsigned long>c'o' << 8) |
(<unsigned long>c't'))
#freetype.h
ctypedef struct FT_Glyph_Metrics:
FT_Pos width, height
FT_Pos horiBearingX, horiBearingY, horiAdvance
FT_Pos vertBearingX, vertBearingY, vertAdvance
ctypedef struct FT_Bitmap_Size:
FT_Short height, width
FT_Pos size
@@ -322,7 +322,7 @@ cdef extern from "pyfreetype.h":
FT_Library library
FT_Face face
FT_GlyphSlot next
FT_UInt reserved
FT_UInt reserved
FT_Generic generic
FT_Glyph_Metrics metrics
@@ -361,7 +361,7 @@ cdef extern from "pyfreetype.h":
FT_SIZE_REQUEST_TYPE_SCALES,
FT_SIZE_REQUEST_TYPE_MAX
ctypedef struct FT_Size_RequestRec:
FT_Size_Request_Type type
FT_Long width, height
@@ -397,7 +397,7 @@ cdef extern from "pyfreetype.h":
void FT_Set_Transform(FT_Face face, FT_Matrix *matrix, FT_Vector *delta)
ctypedef enum FT_Render_Mode:
FT_RENDER_MODE_NORMAL = 0,
FT_RENDER_MODE_LIGHT,
@@ -438,7 +438,7 @@ cdef extern from "pyfreetype.h":
void FT_Library_Version(FT_Library, FT_Int *major, FT_Int *minor, FT_Int *patch)
FT_Bool FT_Face_CheckTrueTypePatents(FT_Face face)
FT_Bool FT_Face_SetUnpatentedHinting(FT_Face face, FT_Bool value)
#ftglyph.h
@@ -447,7 +447,7 @@ cdef extern from "pyfreetype.h":
FT_Glyph_Format format
FT_Vector advance
ctypedef FT_GlyphRec* FT_Glyph
ctypedef struct FT_BitmapGlyphRec:
FT_GlyphRec root
FT_Int left, top
@@ -475,7 +475,7 @@ cdef extern from "pyfreetype.h":
void FT_Done_Glyph(FT_Glyph glyph)
void FT_Matrix_Multiply(FT_Matrix *a, FT_Matrix *b)
FT_Error FT_Matrix_Invert(FT_Matrix *matrix)
FT_Error FT_Matrix_Invert(FT_Matrix *matrix)
@@ -485,7 +485,7 @@ cdef extern from "pyfreetype.h":
FT_Error FT_Outline_Done(FT_Library lib, FT_Outline *outline)
FT_Error FT_Outline_Copy(FT_Outline *source, FT_Outline *target)
FT_Error FT_Outline_Check(FT_Outline *outline)
void FT_Outline_Get_CBox(FT_Outline *outline, FT_BBox *cbox)
void FT_Outline_Translate(FT_Outline *outline, FT_Pos xOffset, FT_Pos yOffset)
void FT_Outline_Transform(FT_Outline *outline, FT_Matrix *matrix)
@@ -509,32 +509,32 @@ cdef extern from "pyfreetype.h":
FT_Error FT_Bitmap_Copy(FT_Library lib, FT_Bitmap *source, FT_Bitmap *target)
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.
cdef struct FT_MemoryRec_
cdef struct FT_MemoryRec_
ctypedef FT_MemoryRec_ *FT_Memory
ctypedef void * (*FT_AllocFunc)(FT_Memory, long)
ctypedef void (*FT_FreeFunc)(FT_Memory, void *)
ctypedef void * (*FT_Realloc_Func)(FT_Memory, long, long, void*)
cdef struct FT_StreamRec_
ctypedef FT_StreamRec_* FT_Stream
cdef union FT_StreamDesc:
long value
void *pointer
ctypedef unsigned long (*FT_Stream_IoFunc)(
FT_Stream,
unsigned long,
unsigned char *,
unsigned long)
ctypedef void (*FT_Stream_CloseFunc)(
FT_Stream stream)
cdef struct FT_StreamRec_:
unsigned char *base
unsigned long size
@@ -543,36 +543,36 @@ cdef extern from "pyfreetype.h":
FT_StreamDesc pathname
FT_Stream_IoFunc read
FT_Stream_CloseFunc close
FT_Memory memory
unsigned char *cursor
unsigned char *limit
ctypedef FT_StreamRec_ FT_StreamRec
cdef struct FT_ModuleRec_
ctypedef FT_ModuleRec_ *FT_Module
cdef struct FT_ParameterRec_
cdef struct FT_ParameterRec_
ctypedef struct FT_Parameter
ctypedef struct FT_Open_Args:
FT_UInt flags
FT_Byte *memory_base
FT_Long memory_size
FT_String *pathname
FT_Stream stream
FT_Module driver
FT_Int num_params
FT_Parameter *params
cdef enum:
FT_OPEN_MEMORY
FT_OPEN_STREAM
FT_OPEN_PATHNAME
FT_OPEN_DRIVER
FT_OPEN_PARAMS
FT_Error FT_Open_Face(
FT_Library library,
FT_Open_Args *args,
@@ -581,10 +581,10 @@ cdef extern from "pyfreetype.h":
cdef struct FT_Stroker_Rec_
ctypedef FT_Stroker_Rec_ *FT_Stroker
FT_Error FT_Stroker_New( FT_Library library,
FT_Stroker *astroker )
cdef enum FT_Stroker_LineCap:
FT_STROKER_LINECAP_ROUND
cdef enum FT_Stroker_LineJoin:
@@ -595,11 +595,11 @@ cdef extern from "pyfreetype.h":
FT_Stroker_LineCap line_cap,
FT_Stroker_LineJoin line_join,
FT_Fixed miter_limit )
void FT_Glyph_StrokeBorder(FT_Glyph *, FT_Stroker, FT_Bool, FT_Bool)
void FT_Stroker_Done(FT_Stroker)
cdef FT_Long FT_MulFix(FT_Long, FT_Long)
cdef FT_Long FT_CEIL(FT_Long)
cdef FT_Long FT_FLOOR(FT_Long)
+4 -6
View File
@@ -5,19 +5,17 @@ cdef extern from "pygame/pygame.h":
int pitch
int flags
void *pixels
struct SDL_Rect:
int x
int y
int w
int h
SDL_Surface *PySurface_AsSurface(object)
int SDL_SetAlpha(SDL_Surface *surface, unsigned int flag, char alpha)
enum:
SDL_SRCALPHA
cdef extern int SDL_BlitSurface(SDL_Surface *src, SDL_Rect *srcrect, SDL_Surface *dst, SDL_Rect *dstrect) nogil
+10 -10
View File
@@ -1805,7 +1805,7 @@ def generate_tegl():
constant("DOT3_RGB")
constant("DOT3_RGBA")
constant("CLAMP_TO_BORDER")
if GL_ARB_MULTITEXTURE:
constant("TEXTURE0_ARB")
constant("TEXTURE1_ARB")
@@ -2537,7 +2537,7 @@ class gltype(object):
def __init__(self, s):
self.type = s
def __getitem__(self, n):
return gltype(self.type + " *")
@@ -2546,7 +2546,7 @@ class gltype(object):
def __str__(self):
return self.type
GLenum = gltype("GLenum")
GLboolean = gltype("GLboolean")
GLbitfield = gltype("GLbitfield")
@@ -2573,7 +2573,7 @@ GLcharARB = gltype("GLchar")
def declare(*args):
global is_enum
is_enum = False
args = list(args)
if isinstance(args[0], gltype):
@@ -2587,7 +2587,7 @@ def declare(*args):
type,
name,
", ".join(str(i) for i in args))
constants = [ ]
def constant(name):
@@ -2628,24 +2628,24 @@ cdef inline gl_check(where):
renpy.display.log.write("GL error 0x%X at %s", error, where)
"""
def main():
out = sys.stdout
out.write(HEADER)
generate_tegl()
constant("BGRA")
constants.sort()
constants.append("RENPY_THIRD_TEXTURE")
print
print
print " enum:"
for i in constants:
print " %s" % i
print
print FOOTER
if __name__ == '__main__':
main()
+1
View File
@@ -1,3 +1,4 @@
#@PydevCodeAnalysisIgnore
from ossaudiodev import * #@UnusedWildImport
mixer = openmixer()
+6 -6
View File
@@ -1,4 +1,4 @@
# -*- python -*-
# -*- python -*-
# Copyright 2004-2012 Tom Rothamel <pytom@bishoujo.us>
# Permission is hereby granted, free of charge, to any person
@@ -50,7 +50,7 @@ cdef extern from "pss.h":
void PSS_periodic()
void PSS_alloc_event(object)
int PSS_refresh_event()
char *PSS_get_error()
int ffpy_needs_alloc
@@ -120,7 +120,7 @@ def unpause(channel):
def unpause_all():
PSS_unpause_all()
def fadeout(channel, ms):
PSS_fadeout(channel, ms)
check_error()
@@ -132,7 +132,7 @@ def get_pos(channel):
return PSS_get_pos(channel)
def set_volume(channel, volume):
if volume == 0:
if volume == 0:
PSS_set_volume(channel, 0)
else:
PSS_set_volume(channel, 10 ** volume / 10 )
@@ -180,9 +180,9 @@ def needs_alloc():
def movie_size():
return ffpy_movie_width, ffpy_movie_height
def check_version(version):
if version < 2 or version > 4:
raise Exception("pysdlsound version mismatch.")
import_pygame_rwobject()
import_pygame_rwobject()
+8 -8
View File
@@ -13,7 +13,7 @@ from setuplib import android, include, library, cython, cmodule, pymodule, copyf
# These control the level of optimization versus debugging.
setuplib.extra_compile_args = [ "-Wno-unused-function" ]
setuplib.extra_link_args = [ ]
# Detect win32.
if platform.win32_ver()[0]:
windows = True
@@ -40,7 +40,7 @@ library("avutil")
has_avresample = library("avresample", optional=True)
has_swscale = library("swscale", optional=True)
library("freetype")
has_fribidi = library("fribidi", optional=True)
has_fribidi = library("fribidi", optional=True)
library("z")
has_libglew = library("GLEW", optional=True)
has_libglew32 = library("glew32", optional=True)
@@ -54,13 +54,13 @@ else:
# Modules directory.
cython(
"_renpy",
"_renpy",
[ "IMG_savepng.c", "core.c", "subpixel.c"],
sdl + [ 'png', 'z', 'm' ])
if has_fribidi and not android:
cython(
"_renpybidi",
"_renpybidi",
[ "renpybidicore.c" ],
['fribidi'], define_macros=[ ("FRIBIDI_ENTRY", "") ])
@@ -78,7 +78,7 @@ if not android:
if has_swscale:
sound.insert(0, "swscale")
cython(
"pysdlsound.sound",
[ "pss.c", "ffdecode.c" ],
@@ -110,9 +110,9 @@ cython("renpy.gl.glrtt_fbo", libs=glew_libs)
def anglecopy(fn):
if android:
return
copyfile("renpy/gl/" + fn, "renpy/angle/" + fn, "DEF ANGLE = False", "DEF ANGLE = True")
anglecopy("glblacklist.py")
anglecopy("gldraw.pxd")
anglecopy("gldraw.pyx")
@@ -139,7 +139,7 @@ cython("renpy.text.textsupport")
cython("renpy.text.texwrap")
cython(
"renpy.text.ftfont",
"renpy.text.ftfont",
[ "ftsupport.c", "ttgsubtable.c" ],
libs = sdl + [ 'freetype', 'z', 'm' ])
+62 -67
View File
@@ -14,7 +14,7 @@ android = "RENPY_ANDROID" in os.environ
cython_command = os.environ.get("RENPY_CYTHON", None)
# Note that the android build sets up CFLAGS for us, and ensures
# that necessary libraries are present. So autoconfiguration is
# that necessary libraries are present. So autoconfiguration is
# unnecessary on that platform.
# The install variable is a list of directories that have Ren'Py
@@ -25,7 +25,7 @@ if not android:
install = [ os.path.abspath(i) for i in install ]
else:
install = [ ]
# The include and library dirs that we compile against.
include_dirs = [ "." ]
library_dirs = [ ]
@@ -33,78 +33,78 @@ library_dirs = [ ]
# Extra arguments that will be given to the compiler.
extra_compile_args = [ ]
extra_link_args = [ ]
def include(header, directory=None, optional=True):
"""
Searches the install paths for `header`. If `directory` is given, we
will append that to each of the install paths when trying to find
Searches the install paths for `header`. If `directory` is given, we
will append that to each of the install paths when trying to find
the header. The directory the header is found in is added to include_dirs
if it's not present already.
if it's not present already.
`optional`
If given, returns False rather than abandoning the process.
"""
if android:
return True
for i in install:
if directory is not None:
idir = os.path.join(i, "include", directory)
else:
idir = os.path.join(i, "include")
fn = os.path.join(idir, header)
if os.path.exists(fn):
if idir not in include_dirs:
include_dirs.append(idir)
return True
if optional:
return False
if directory is None:
print "Could not find required header {0}.".format(header)
else:
print "Could not find required header {0}/{1}.".format(directory, header)
sys.exit(-1)
def library(name, optional=False):
"""
Searches for `library`.
Searches for `library`.
`optional`
If true, this function will return False if a library is not found,
rather than reporting an error.
"""
if android:
return True
for i in install:
for ldir in [i, os.path.join(i, "lib") ]:
for suffix in ( ".so", ".a", ".dll.a", ".dylib" ):
fn = os.path.join(ldir, "lib" + name + suffix)
if os.path.exists(fn):
if ldir not in library_dirs:
library_dirs.append(ldir)
return True
if optional:
return False
print "Could not find required library {0}.".format(name)
sys.exit(-1)
@@ -113,10 +113,10 @@ extensions = [ ]
def cmodule(name, source, libs=[], define_macros=[]):
"""
Compiles the python module `name` from the files given in
Compiles the python module `name` from the files given in
`source`, and the libraries in `libs`.
"""
extensions.append(distutils.core.Extension(
name,
source,
@@ -136,12 +136,12 @@ def cython(name, source=[], libs=[], compile_if=True, define_macros=[]):
Compiles a cython module. This takes care of regenerating it as necessary
when it, or any of the files it depends on, changes.
"""
# Find the pyx file.
split_name = name.split(".")
fn = "/".join(split_name) + ".pyx"
if os.path.exists(os.path.join("..", fn)):
fn = os.path.join("..", fn)
elif os.path.exists(fn):
@@ -151,18 +151,18 @@ def cython(name, source=[], libs=[], compile_if=True, define_macros=[]):
sys.exit(-1)
module_dir = os.path.dirname(fn)
# Figure out what it depends on.
deps = [ fn ]
f = file(fn)
for l in f:
m = re.search(r'from\s*([\w.]+)\s*cimport', l)
if m:
deps.append(m.group(1).replace(".", "/") + ".pxd")
continue
m = re.search(r'cimport\s*([\w.]+)', l)
if m:
deps.append(m.group(1).replace(".", "/") + ".pxd")
@@ -171,27 +171,27 @@ def cython(name, source=[], libs=[], compile_if=True, define_macros=[]):
m = re.search(r'include\s*"(.*?)"', l)
if m:
deps.append(m.group(1))
continue
continue
f.close()
# Filter out cython stdlib dependencies.
deps = [ i for i in deps if (not i.startswith("cpython/")) and (not i.startswith("libc/")) ]
# Determine if any of the dependencies are newer than the c file.
c_fn = os.path.join("gen", name + ".c")
necessary_gen.append(name + ".c")
if os.path.exists(c_fn):
c_mtime = os.path.getmtime(c_fn)
else:
c_mtime = 0
out_of_date = False
# print c_fn, "depends on", deps
for dep_fn in deps:
if os.path.exists(os.path.join(module_dir, dep_fn)):
dep_fn = os.path.join(module_dir, dep_fn)
elif os.path.exists(os.path.join("..", dep_fn)):
@@ -215,8 +215,8 @@ def cython(name, source=[], libs=[], compile_if=True, define_macros=[]):
if out_of_date:
print name, "is out of date."
try:
import subprocess
try:
import subprocess
subprocess.check_call([
cython_command,
"-Iinclude",
@@ -230,22 +230,22 @@ def cython(name, source=[], libs=[], compile_if=True, define_macros=[]):
print str(e)
print
sys.exit(-1)
# Build the module normally once we have the c file.
if compile_if:
if compile_if:
cmodule(name, [ c_fn ] + source, libs=libs, define_macros=define_macros)
def find_unnecessary_gen():
for i in os.listdir("gen"):
if not i.endswith(".c"):
continue
if i in necessary_gen:
continue
print "Unnecessary file", os.path.join("gen", i)
py_modules = [ ]
@@ -253,33 +253,33 @@ def pymodule(name):
"""
Causes a python module to be included in the build.
"""
py_modules.append(name)
def copyfile(source, dest, replace=None, replace_with=None):
"""
Copy `source` to `dest`, preserving the modification time.
If `replace` is given, instances of `replace` in the file contents are
replaced with `replace_with`.
"""
sfn = os.path.join("..", source)
dfn = os.path.join("..", dest)
sf = file(sfn, "rb")
data = sf.read()
sf.close()
if replace:
data = data.replace(replace, replace_with)
df = file(dfn, "wb")
df.write("# This file was automatically generated from " + source + "\n")
df.write("# Modifications will be automatically overwritten.\n\n")
df.write("# Modifications will be automatically overwritten.\n\n")
df.write(data)
df.close()
import shutil
shutil.copystat(sfn, dfn)
@@ -290,16 +290,11 @@ def setup(name, version):
distutils.core.setup(
name = name,
version = version,
version = version,
ext_modules = extensions,
py_modules = py_modules,
)
# Ensure the gen directory exists.
if not os.path.exists("gen"):
os.mkdir("gen")
+12 -14
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
#@PydevCodeAnalysisIgnore
# This file is part of Ren'Py. The license below applies to Ren'Py only.
# This file is part of Ren'Py. The license below applies to Ren'Py only.
# Games and other projects that use Ren'Py may use a different license.
# Copyright 2004-2012 Tom Rothamel <pytom@bishoujo.us>
@@ -40,7 +40,7 @@ def path_to_common(renpy_base):
# the path to a directory that will hold save files.
def path_to_saves(gamedir):
import renpy #@UnresolvedImport
# Android.
if renpy.android:
paths = [
@@ -48,18 +48,18 @@ def path_to_saves(gamedir):
os.path.join(os.environ["ANDROID_PRIVATE"], "saves"),
os.path.join(os.environ["ANDROID_PUBLIC"], "saves"),
]
for rv in paths:
if os.path.isdir(rv):
break
print "Using savedir", rv
# We return the last path as the default.
return rv
# No save directory given.
if not renpy.config.save_directory:
return gamedir + "/saves"
@@ -93,7 +93,7 @@ def path_to_saves(gamedir):
rv = "~/.renpy/" + renpy.config.save_directory
return os.path.expanduser(rv)
# Returns the path to the Ren'Py base directory (containing common and
# the launcher, usually.)
def path_to_renpy_base():
@@ -128,9 +128,9 @@ if android:
__main__.path_to_common = path_to_common
__main__.path_to_saves = path_to_saves
os.environ["RENPY_RENDERER"] = "gl"
def main():
renpy_base = path_to_renpy_base()
# Add paths.
@@ -145,7 +145,7 @@ def main():
# Ignore warnings that happen.
warnings.simplefilter("ignore", DeprecationWarning)
# Start Ren'Py proper.
try:
import renpy.bootstrap
@@ -157,10 +157,8 @@ def main():
if android:
renpy.linux = False
renpy.android = True
renpy.bootstrap.bootstrap(renpy_base)
if __name__ == "__main__":
main()
+21 -5
View File
@@ -1,11 +1,27 @@
#!/bin/sh
SCRIPT="$0"
# Resolve the chain of symlinks leading to this script.
while [ -L "$SCRIPT" ] ; do
LINK=$(readlink "$SCRIPT")
case "$LINK" in
/*)
SCRIPT="$LINK"
;;
*)
SCRIPT="$(dirname "$SCRIPT")/$LINK"
;;
esac
done
# The directory containing this shell script - an absolute path.
ROOT=$(dirname "$0")
ROOT=$(dirname "$SCRIPT")
ROOT=$(cd "$ROOT"; pwd)
# The name of this shell script without the .sh on the end.
BASEFILE=$(basename "$0" .sh)
BASEFILE=$(basename "$SCRIPT" .sh)
if [ -z "$RENPY_PLATFORM" ] ; then
case "$(uname -s)-$(uname -m)" in
@@ -13,7 +29,7 @@ if [ -z "$RENPY_PLATFORM" ] ; then
RENPY_PLATFORM="darwin-x86_64"
ROOT1="$ROOT/../Resources/autorun"
ROOT2="$ROOT/../../.."
;;
;;
*-x86_64|amd64)
RENPY_PLATFORM="linux-x86_64"
ROOT1="$ROOT"
@@ -34,11 +50,11 @@ if [ -z "$RENPY_PLATFORM" ] ; then
fi
for BASE in "$ROOT" "$ROOT1" "$ROOT2"; do
for BASE in "$ROOT" "$ROOT1" "$ROOT2"; do
LIB="$BASE/lib/$RENPY_PLATFORM"
if test -d "$LIB"; then
break
fi
done
exec $RENPY_GDB "$LIB/$BASEFILE" $RENPY_PYARGS -EOO "$BASE/$BASEFILE.py" "$@"
exec $RENPY_GDB "$LIB/$BASEFILE" $RENPY_PYARGS -EO "$BASE/$BASEFILE.py" "$@"
+35 -28
View File
@@ -32,11 +32,11 @@ except ImportError:
vc_version = 0
# The tuple giving the version. This needs to be updated when
# we bump the version.
# we bump the version.
#
# Be sure to change script_version in launcher/game/script_version.rpy.
# Be sure to change config.version in tutorial/game/options.rpy.
version_tuple = (6, 15, 7, vc_version)
version_tuple = (6, 16, 1, vc_version)
# A verbose string computed from that version.
version = "Ren'Py " + ".".join(str(i) for i in version_tuple)
@@ -51,12 +51,12 @@ bytecode_version = 1
first_utter_start = True
def setup_modulefinder(modulefinder):
import _renpy
import _renpy
libexec = os.path.dirname(_renpy.__file__)
for i in [ "display", "gl", "angle", "text" ]:
displaypath = os.path.join(libexec, "renpy", i)
if os.path.exists(displaypath):
@@ -86,18 +86,19 @@ def import_cython():
import renpy.angle.glrtt_copy #@UnresolvedImport
import renpy.angle.glrtt_fbo #@UnresolvedImport
import renpy.angle.gltexture #@UnresolvedImport
def import_all():
import renpy.log #@UnresolvedImport
import renpy.display #@UnresolvedImport
# Should probably be early, as we will add it as a base to serialized things.
import renpy.object #@UnresolvedImport
import renpy.game #@UnresolvedImport
import renpy.preferences #@UnresolvedImport
# Adds in the Ren'Py loader.
import renpy.loader #@UnresolvedImport
@@ -108,6 +109,8 @@ def import_all():
import renpy.easy #@UnresolvedImport
import renpy.execution #@UnresolvedImport
import renpy.loadsave #@UnresolvedImport
import renpy.savelocation # @UnresolvedImport
import renpy.persistent #@UnresolvedImport
import renpy.parser #@UnresolvedImport
import renpy.python #@UnresolvedImport
import renpy.script #@UnresolvedImport
@@ -126,41 +129,41 @@ def import_all():
Update the __path__ of package, to import binary modules from a libexec
directory.
"""
name = package.__name__.split(".")
import _renpy #@UnresolvedImport
libexec = os.path.dirname(_renpy.__file__)
package.__path__.append(os.path.join(libexec, *name))
# Also find encodings, to deal with the way py2exe lays things out.
import encodings
libexec = os.path.dirname(encodings.__path__[0])
package.__path__.append(os.path.join(libexec, *name))
update_path(renpy.display)
import renpy.display.render # Most display stuff depends on this. @UnresolvedImport
import renpy.display.core # object @UnresolvedImport
import renpy.text #@UnresolvedImport
update_path(renpy.text)
import renpy.text.ftfont #@UnresolvedImport
import renpy.text.font #@UnresolvedImport
import renpy.text.textsupport #@UnresolvedImport
import renpy.text.texwrap #@UnresolvedImport
import renpy.text.text #@UnresolvedImport
import renpy.text.extras #@UnresolvedImport
sys.modules['renpy.display.text'] = renpy.text.text
import renpy.gl #@UnresolvedImport
update_path(renpy.gl)
import renpy.angle #@UnresolvedImport
update_path(renpy.angle)
import renpy.display.layout # core @UnresolvedImport
import renpy.display.motion # layout @UnresolvedImport
import renpy.display.behavior # layout @UnresolvedImport
@@ -179,11 +182,12 @@ def import_all():
import renpy.display.dragdrop #@UnresolvedImport
import renpy.display.imagemap #@UnresolvedImport
import renpy.display.predict #@UnresolvedImport
import renpy.display.emulator # @UnresolvedImport
import renpy.display.error #@UnresolvedImport
# Note: For windows to work, renpy.audio.audio needs to be after
# renpy.display.module.
# renpy.display.module.
import renpy.audio.audio #@UnresolvedImport
import renpy.audio.music #@UnresolvedImport
import renpy.audio.sound #@UnresolvedImport
@@ -208,14 +212,14 @@ def import_all():
# Create the store.
renpy.python.create_store("store")
# Import the contents of renpy.defaultstore into renpy.store, and set
# Import the contents of renpy.defaultstore into renpy.store, and set
# up an alias as we do.
renpy.store = sys.modules['store']
sys.modules['renpy.store'] = sys.modules['store']
import subprocess
sys.modules['renpy.subprocess'] = subprocess
for k, v in renpy.defaultstore.__dict__.iteritems():
renpy.store.__dict__.setdefault(k, v)
@@ -230,23 +234,26 @@ if False:
# This reloads all modules.
def reload_all():
import renpy #@UnresolvedImport
# Shut down the cache thread.
renpy.display.im.cache.quit()
# Shut down the importer.
renpy.loader.quit_importer()
blacklist = [ "renpy",
"renpy.log",
"renpy.bootstrap",
"renpy.display",
"renpy.display.pgrender",
"renpy.display.scale" ]
for i in sys.modules.keys():
if i.startswith("renpy") and i not in blacklist:
del sys.modules[i]
if i.startswith("store"):
del sys.modules[i]
@@ -254,9 +261,11 @@ def reload_all():
gc.collect()
renpy.display.draw = None
import_all()
renpy.loader.init_importer()
# Information about the platform we're running on. We break the platforms
# up into 4 groups - windows-like, mac-like, linux-like, and android-like.
windows = False
@@ -272,7 +281,5 @@ elif platform.mac_ver()[0]:
macintosh = True
else:
linux = True
# The android init code in renpy.py will set linux=False and android=True.
+6 -6
View File
@@ -29,21 +29,21 @@
# - True to allow shader rendering.
# - True to allow fixed-function rendering.
# If both of the last two entries are false, GL refuses to
# If both of the last two entries are false, GL refuses to
# start.
BLACKLIST = [
# Crashes for Mugenjohncel.
("S3 Graphics DeltaChrome", "1.4 20.00", False, False),
# A bug in Mesa 7.9 and 7.10 (before 7.10.3) causes the system to
# fail to initialize the GLSL compiler.
# https://bugs.freedesktop.org/show_bug.cgi?id=35603
# fail to initialize the GLSL compiler.
# https://bugs.freedesktop.org/show_bug.cgi?id=35603
("Mesa", "Mesa 7.9", False, True),
("Mesa", "Mesa 7.10.3", True, True),
("Mesa", "Mesa 7.10.3", True, True),
("Mesa", "Mesa 7.10", False, True),
# Default to allowing everything.
# Default to allowing everything.
("", "", True, True),
]
+38 -43
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.
# This file handles argument parsing. Argument parsing takes place in
# This file handles argument parsing. Argument parsing takes place in
# two phases. In the first phase, we only parse the arguments that are
# necessary to load the game, and run the init phase. The init phase
# can register commands and arguments. These arguments are parsed at
# the end of the init phase, before the game begins running, and can
# can register commands and arguments. These arguments are parsed at
# the end of the init phase, before the game begins running, and can
# decide if the game runs or some other action occurs.
import os
import argparse
import argparse
import renpy
try:
@@ -38,50 +38,50 @@ except:
# A map from command name to a (function, flag) tuple. The flag is true if the
# function will parse command line arguments, and false otherwise.
# function will parse command line arguments, and false otherwise.
commands = { }
class ArgumentParser(argparse.ArgumentParser):
"""
Creates an argument parser that is capable of parsing the standard Ren'Py
arguments, as well as arguments that are specific to a sub-command.
arguments, as well as arguments that are specific to a sub-command.
"""
def __init__(self, second_pass=True, description=None, require_command=True):
"""
Creates an argument parser.
`second_pass`
True if this is the second pass through argument parsing. (The pass
that parses sub-commands.)
`description`
If supplied, this will be used as a description of the subcommand
to run.
"""
self.group = self
argparse.ArgumentParser.__init__(self, description="The Ren'Py visual novel engine.", add_help=False)
self.add_argument(
"basedir", default=None,
help="The base directory containing of the project to run. This defaults to the directory containing the Ren'Py executable.",
nargs=1 if require_command else '?'
)
command_names = ", ".join(sorted(commands))
self.add_argument(
"command",
help="The command to execute. Available commands are: " + command_names + ". Defaults to 'run'.",
default="run",
nargs=1 if require_command else '?')
self.add_argument(
"--savedir", dest='savedir', default=None, metavar="DIRECTORY",
help="The directory where saves and persistent data are placed.")
self.add_argument(
'--trace', dest='trace', action='store', default=0, type=int, metavar="LEVEL",
help="The level of trace Ren'Py will log to trace.txt. (1=per-call, 2=per-line)")
@@ -89,7 +89,7 @@ class ArgumentParser(argparse.ArgumentParser):
self.add_argument(
"--version", action='version', version=renpy.version,
help="Displays the version of Ren'Py in use.")
self.add_argument("--lint", action="store_const", dest="command", const="lint", help=argparse.SUPPRESS)
dump = self.add_argument_group("JSON Dump Arguments", description="Ren'Py can dump information about the game to a JSON file. These options let you select the file, and choose what is dumped.")
@@ -102,22 +102,22 @@ class ArgumentParser(argparse.ArgumentParser):
command = renpy.game.args.command #@UndefinedVariable
self.group = self.add_argument_group("{0} command".format(command), description)
def add_argument(self, *args, **kwargs):
if self.group is self:
argparse.ArgumentParser.add_argument(self, *args, **kwargs)
else:
self.group.add_argument(*args, **kwargs)
def run():
"""
The default command, that (when called) leads to normal game startup.
"""
ap = ArgumentParser(description="Runs the current project normally.", require_command=False)
ap.add_argument(
'--profile-display', dest='profile_display', action='store_true', default=False,
help="If present, Ren'Py will report the amount of time it takes to draw the screen.")
@@ -149,7 +149,7 @@ def compile(): #@ReservedAssignment
"""
takes_no_arguments("Recompiles the game script.")
return False
@@ -159,7 +159,7 @@ def quit(): #@ReservedAssignment
"""
takes_no_arguments("Recompiles the game script.")
return False
def rmpersistent():
@@ -169,10 +169,7 @@ def rmpersistent():
takes_no_arguments("Deletes the persistent data.")
try:
os.unlink(renpy.config.savedir + "/persistent")
except:
pass
renpy.loadsave.location.unlink_persistent()
return False
@@ -181,15 +178,15 @@ def register_command(name, function):
"""
Registers a command that can be invoked when Ren'Py is run on the command
line. When the command is run, `function` is called with no arguments.
If `function` needs to take additional command-line arguments, it should
instantiate a renpy.arguments.ArgumentParser(), and then call parse_args
on it. Otherwise, it should call renpy.arguments.takes_no_arguments().
If `function` returns true, Ren'Py startup proceeds normally. Otherwise,
Ren'Py will terminate when function() returns.
"""
commands[name] = function
def bootstrap():
@@ -197,9 +194,9 @@ def bootstrap():
Called during bootstrap to perform an initial parse of the arguments, ignoring
unknown arguments. Returns the parsed arguments, and a list of unknown arguments.
"""
global rest
ap = ArgumentParser(False, require_command=False)
args, _rest = ap.parse_known_args()
return args
@@ -208,35 +205,33 @@ def pre_init():
"""
Called before init, to set up argument parsing.
"""
global subparsers
global subparsers
register_command("run", run)
register_command("lint", renpy.lint.lint)
register_command("compile", compile)
register_command("rmpersistent", rmpersistent)
register_command("quit", quit)
def post_init():
"""
Called after init, but before the game starts. This parses a command
and its arguments. It then runs the command function, and returns True
if execution should continue and False otherwise.
if execution should continue and False otherwise.
"""
command = renpy.game.args.command #@UndefinedVariable
if command not in commands:
ArgumentParser().error("Command {0} is unknown.".format(command))
return commands[command]()
def takes_no_arguments(description=None):
"""
Used to report that a command takes no arguments.
Used to report that a command takes no arguments.
"""
ArgumentParser(description=description).parse_args()
+170 -170
View File
@@ -34,7 +34,7 @@ import md5
def next_node(n):
"""
Indicates the next node that should be executed. When a statement
Indicates the next node that should be executed. When a statement
can crash, this should be set as early as possible, so that ignore
can bring us there.
"""
@@ -81,12 +81,12 @@ class ArgumentInfo(object):
def __newobj__(cls, *args):
return cls.__new__(cls, *args)
return cls.__new__(cls, *args)
# This represents a string containing python code.
class PyExpr(unicode):
__slots__ = [
__slots__ = [
'filename',
'linenumber',
]
@@ -99,7 +99,7 @@ class PyExpr(unicode):
def __getnewargs__(self):
return (unicode(self), self.filename, self.linenumber) # E1101
class PyCode(object):
__slots__ = [
@@ -151,11 +151,11 @@ class PyCode(object):
code = self.source
if isinstance(code, renpy.python.ast.AST): #@UndefinedVariable
code = renpy.python.ast.dump(code) #@UndefinedVariable
self.hash = chr(renpy.bytecode_version) + md5.md5(repr(self.location) + code.encode("utf-8")).digest()
return self.hash
def chain_block(block, next): #@ReservedAssignment
"""
This is called to chain together all of the nodes in a block. Node
@@ -188,7 +188,7 @@ class Scry(object):
else:
return self._next.scry()
class Node(object):
"""
A node in the abstract syntax tree of the program.
@@ -198,7 +198,7 @@ class Node(object):
@ivar filename: The filename where this node comes from.
@ivar linenumber: The line number of the line on which this node is defined.
"""
__slots__ = [
'name',
'filename',
@@ -213,12 +213,12 @@ class Node(object):
# Called to set the state of a Node, when necessary.
def __setstate__(self, state):
for k, v in state[1].iteritems():
try:
try:
setattr(self, k, v)
except AttributeError:
pass
def __init__(self, loc):
"""
Initializes this Node object.
@@ -230,7 +230,7 @@ class Node(object):
self.filename, self.linenumber = loc
self.name = None
self.next = None
def diff_info(self):
"""
Returns a tuple of diff info about ourself. This is used to
@@ -259,14 +259,14 @@ class Node(object):
"""
return None
def chain(self, next): #@ReservedAssignment
"""
This is called with the Node node that should be followed after
executing this node, and all nodes that this node
executes. (For example, if this node is a block label, the
next is the node that should be executed after all nodes in
the block.)
the block.)
"""
self.next = next
@@ -274,7 +274,7 @@ class Node(object):
def execute(self):
"""
Causes this node to execute, and any action it entails to be
performed. The node should call next_node with the node to
performed. The node should call next_node with the node to
be executed after this one.
"""
@@ -311,11 +311,11 @@ class Node(object):
"""
Called to restructure the AST.
When this method is called, callback is called once for each child
When this method is called, callback is called once for each child
block of the node. The block, a list, can be updated by the callback
using slice assignment to the list.
"""
# Does nothing for nodes that do not contain child blocks.
return
@@ -323,7 +323,7 @@ class Node(object):
"""
Returns the canonical form of the code corresponding to this statement.
This only needs to be defined if the statement is translatable.
`filter`
If present, a filter that should be applied to human-readable
text in the statement.
@@ -349,7 +349,7 @@ def say_menu_with(expression, callback):
if renpy.game.preferences.transitions:
# renpy.game.interface.set_transition(what)
callback(what)
class Say(Node):
__slots__ = [
@@ -381,9 +381,9 @@ class Say(Node):
else:
self.who_fast = False
else:
self.who = None
self.who = None
self.who_fast = False
self.what = what
self.with_ = with_
self.interact = interact
@@ -394,36 +394,36 @@ class Say(Node):
def get_code(self, dialogue_filter=None):
rv = [ ]
if self.who:
rv.append(self.who)
if self.attributes is not None:
rv.extend(self.attributes)
what = self.what
if dialogue_filter is not None:
what = dialogue_filter(what)
rv.append(renpy.translation.encode_say_string(what))
if not self.interact:
rv.append("nointeract")
if self.with_:
if self.with_:
rv.append("with")
rv.append(self.with_)
return " ".join(rv)
def execute(self):
next_node(self.next)
try:
renpy.exports.say_attributes = self.attributes
if self.who is not None:
if self.who_fast:
who = getattr(renpy.store, self.who, None)
@@ -433,37 +433,39 @@ class Say(Node):
who = renpy.python.py_eval(self.who)
else:
who = None
if not (
(who is None) or
callable(who) or
isinstance(who, basestring) ):
raise Exception("Sayer %s is not a function or string." % self.who.encode("utf-8"))
what = self.what
if renpy.config.say_menu_text_filter:
what = renpy.config.say_menu_text_filter(what) # E1102
renpy.store._last_raw_what = what
if getattr(who, "record_say", True):
renpy.store._last_say_who = self.who
renpy.store._last_say_what = what
say_menu_with(self.with_, renpy.game.interface.set_transition)
renpy.exports.say(who, what, interact=self.interact)
finally:
renpy.exports.say_attributes = None
def predict(self):
old_attributes = renpy.exports.say_attributes
try:
renpy.exports.say_attributes = self.attributes
if self.who is not None:
if self.who_fast:
who = getattr(renpy.store, self.who)
@@ -471,21 +473,21 @@ class Say(Node):
who = renpy.python.py_eval(self.who)
else:
who = None
def predict_with(trans):
renpy.display.predict.displayable(trans(old_widget=None, new_widget=None))
say_menu_with(self.with_, predict_with)
what = self.what
if renpy.config.say_menu_text_filter:
what = renpy.config.say_menu_text_filter(what)
renpy.exports.predict_say(who, what)
finally:
renpy.exports.say_attributes = old_attributes
return [ self.next ]
def scry(self):
@@ -499,13 +501,13 @@ class Say(Node):
else:
who = None
if self.interact:
if self.interact:
renpy.exports.scry_say(who, rv)
else:
rv.interacts = False
return rv
# Copy the descriptor.
setattr(Say, "with", Say.with_) # E1101
@@ -539,27 +541,27 @@ class Init(Node):
def execute(self):
next_node(self.next)
def restructure(self, callback):
callback(self.block)
def apply_arguments(params, args, kwargs):
"""
Applies arguments to parameters to update scope.
`scope`
A dict.
`params`
The parameters object.
`args`, `kwargs`
Positional and keyword arguments.
"""
values = { }
rv = { }
if args is None:
args = ()
@@ -571,7 +573,7 @@ def apply_arguments(params, args, kwargs):
raise Exception("Arguments supplied, but parameter list not present")
else:
return rv
for name, value in zip(params.positional, args):
if name in values:
raise Exception("Parameter %s has two values." % name)
@@ -593,11 +595,11 @@ def apply_arguments(params, args, kwargs):
raise Exception("Required parameter %s has no value." % name)
else:
rv[name] = renpy.python.py_eval(default)
else:
rv[name] = values[name]
del values[name]
# Now, values has the left-over keyword arguments, and extrapos
# has the left-over positional arguments.
@@ -613,7 +615,7 @@ def apply_arguments(params, args, kwargs):
raise Exception("Unknown keyword arguments: %s" % ( ", ".join(values.keys())))
return rv
class Label(Node):
__slots__ = [
@@ -627,7 +629,7 @@ class Label(Node):
self.parameters = None
self.hide = False
Node.__setstate__(self, state)
def __init__(self, loc, name, block, parameters, hide=False):
"""
Constructs a new Label node.
@@ -648,7 +650,7 @@ class Label(Node):
return (Label, self.name)
def get_children(self):
return self.block
return self.block
def chain(self, next): #@ReservedAssignment
@@ -657,12 +659,12 @@ class Label(Node):
chain_block(self.block, next)
else:
self.next = next
def execute(self):
next_node(self.next)
renpy.game.context().mark_seen()
values = apply_arguments(self.parameters, renpy.store._args, renpy.store._kwargs)
for k, v in values.iteritems():
@@ -698,7 +700,7 @@ class Python(Node):
@param hide: If True, the code will be executed with its
own local dictionary.
"""
super(Python, self).__init__(loc)
self.hide = hide
@@ -713,11 +715,11 @@ class Python(Node):
def execute(self):
next_node(self.next)
try:
try:
renpy.python.py_exec_bytecode(self.code.bytecode, self.hide, store=self.store)
finally:
if not renpy.game.context().init_phase:
for i in renpy.config.python_callbacks:
i()
@@ -746,7 +748,7 @@ class EarlyPython(Node):
@param hide: If True, the code will be executed with its
own local dictionary.
"""
super(EarlyPython, self).__init__(loc)
self.hide = hide
@@ -761,7 +763,7 @@ class EarlyPython(Node):
def early_execute(self):
renpy.python.create_store(self.store)
if self.code.bytecode:
renpy.python.py_exec_bytecode(self.code.bytecode, self.hide, store=self.store)
@@ -782,7 +784,7 @@ class Image(Node):
"""
super(Image, self).__init__(loc)
self.imgname = name
if expr:
@@ -791,8 +793,8 @@ class Image(Node):
else:
self.code = None
self.atl = atl
def diff_info(self):
def diff_info(self):
return (Image, tuple(self.imgname))
def execute(self):
@@ -810,7 +812,7 @@ class Image(Node):
renpy.exports.image(self.imgname, img)
class Transform(Node):
__slots__ = [
@@ -822,20 +824,20 @@ class Transform(Node):
'atl',
# The parameters associated with the transform, if any.
'parameters',
'parameters',
]
default_parameters = ParameterInfo([ ], [ ], None, None)
def __init__(self, loc, name, atl=None, parameters=default_parameters):
super(Transform, self).__init__(loc)
self.varname = name
self.atl = atl
self.parameters = parameters
def diff_info(self):
def diff_info(self):
return (Transform, self.varname)
def execute(self):
@@ -851,7 +853,7 @@ class Transform(Node):
renpy.dump.transforms.append((self.varname, self.filename, self.linenumber))
setattr(renpy.store, self.varname, trans)
def predict_imspec(imspec, scene=False, atl=None):
"""
Call this to use the given callback to predict the image named
@@ -866,8 +868,8 @@ def predict_imspec(imspec, scene=False, atl=None):
elif len(imspec) == 3:
name, _at_list, layer = imspec
if expression:
try:
img = renpy.python.py_eval(expression)
@@ -886,36 +888,36 @@ def predict_imspec(imspec, scene=False, atl=None):
if scene:
renpy.game.context().images.predict_scene(layer)
renpy.game.context().images.predict_show(tag or name, layer)
if atl is not None:
try:
img = renpy.display.motion.ATLTransform(atl, child=img)
except:
import traceback
traceback.print_exc()
renpy.display.predict.displayable(img)
def show_imspec(imspec, atl=None):
if len(imspec) == 7:
name, expression, tag, at_list, layer, zorder, behind = imspec
elif len(imspec) == 6:
name, expression, tag, at_list, layer, zorder = imspec
behind = [ ]
elif len(imspec) == 3:
name, at_list, layer = imspec
expression = None
tag = None
zorder = None
behind = [ ]
if zorder is not None:
zorder = renpy.python.py_eval(zorder)
else:
@@ -953,19 +955,19 @@ class Show(Node):
self.imspec = imspec
self.atl = atl
def diff_info(self):
def diff_info(self):
return (Show, tuple(self.imspec[0]))
def execute(self):
next_node(self.next)
show_imspec(self.imspec, atl=getattr(self, "atl", None))
def predict(self):
predict_imspec(self.imspec, atl=getattr(self, "atl", None))
return [ self.next ]
class Scene(Node):
@@ -988,7 +990,7 @@ class Scene(Node):
self.layer = layer
self.atl = atl
def diff_info(self):
def diff_info(self):
if self.imspec:
data = tuple(self.imspec[0])
@@ -1005,15 +1007,15 @@ class Scene(Node):
if self.imspec:
show_imspec(self.imspec, atl=getattr(self, "atl", None))
def predict(self):
if self.imspec:
predict_imspec(self.imspec, atl=getattr(self, "atl", None), scene=True)
return [ self.next ]
class Hide(Node):
__slots__ = [
@@ -1031,7 +1033,7 @@ class Hide(Node):
self.imspec = imgspec
def diff_info(self):
def diff_info(self):
return (Hide, tuple(self.imspec[0]))
def predict(self):
@@ -1051,11 +1053,11 @@ class Hide(Node):
if tag is None:
tag = name[0]
renpy.game.context().images.predict_hide(tag, layer)
return [ self.next ]
def execute(self):
next_node(self.next)
@@ -1069,10 +1071,10 @@ class Hide(Node):
name, _expression, tag, _at_list, layer, _zorder = self.imspec
elif len(self.imspec) == 7:
name, _expression, tag, _at_list, layer, _zorder, _behind = self.imspec
renpy.config.hide(tag or name, layer)
class With(Node):
__slots__ = [
@@ -1083,7 +1085,7 @@ class With(Node):
def __setstate__(self, state):
self.paired = None
Node.__setstate__(self, state)
def __init__(self, loc, expr, paired=None):
"""
@param expr: An expression giving a transition or None.
@@ -1095,7 +1097,7 @@ class With(Node):
def diff_info(self):
return (With, self.expr)
def execute(self):
next_node(self.next)
@@ -1105,7 +1107,7 @@ class With(Node):
if self.paired is not None:
paired = renpy.python.py_eval(self.paired)
else:
paired = None
paired = None
renpy.exports.with_statement(trans, paired)
@@ -1120,10 +1122,10 @@ class With(Node):
except:
pass
return [ self.next ]
class Call(Node):
__slots__ = [
@@ -1135,7 +1137,7 @@ class Call(Node):
def __setstate__(self, state):
self.arguments = None
Node.__setstate__(self, state)
def __init__(self, loc, label, expression, arguments):
super(Call, self).__init__(loc)
@@ -1186,7 +1188,7 @@ class Call(Node):
renpy.store._args = tuple(args)
renpy.store._kwargs = kwargs
def predict(self):
if self.expression:
return [ ]
@@ -1206,11 +1208,11 @@ class Return(Node):
def __setstate__(self, state):
self.expression = None
Node.__setstate__(self, state)
def __init__(self, loc, expression):
super(Return, self).__init__(loc)
self.expression = expression
def diff_info(self):
return (Return, )
@@ -1227,7 +1229,7 @@ class Return(Node):
renpy.store._return = None
renpy.game.context().pop_dynamic()
next_node(renpy.game.context().lookup_return(pop=True))
def predict(self):
@@ -1242,7 +1244,7 @@ class Return(Node):
rv._next = None
return rv
class Menu(Node):
__slots__ = [
@@ -1284,8 +1286,8 @@ class Menu(Node):
next_node(self.next)
choices = [ ]
narration = [ ]
narration = [ ]
for i, (label, condition, block) in enumerate(self.items):
if renpy.config.say_menu_text_filter:
@@ -1302,7 +1304,7 @@ class Menu(Node):
if narration:
renpy.exports.say(None, "\n".join(narration), interact=False)
say_menu_with(self.with_, renpy.game.interface.set_transition)
choice = renpy.exports.menu(choices, self.set)
@@ -1310,7 +1312,7 @@ class Menu(Node):
next_node(self.items[choice][2][0])
else:
next_node(self.next)
def predict(self):
rv = [ ]
@@ -1321,7 +1323,7 @@ class Menu(Node):
say_menu_with(self.with_, predict_with)
renpy.store.predict_menu()
for _label, _condition, block in self.items:
if block:
rv.append(block[0])
@@ -1333,17 +1335,17 @@ class Menu(Node):
rv._next = None
rv.interacts = True
return rv
def restructure(self, callback):
for _label, _condition, block in self.items:
if block is not None:
callback(block)
setattr(Menu, "with", Menu.with_) # E1101
# Goto is considered harmful. So we decided to name it "jump"
# instead.
# instead.
class Jump(Node):
__slots__ = [
@@ -1389,7 +1391,7 @@ class Jump(Node):
rv._next = None
else:
rv._next = renpy.game.script.lookup(self.target)
return rv
@@ -1437,7 +1439,7 @@ class While(Node):
def predict(self):
return [ self.block[0], self.next ]
def scry(self):
rv = Node.scry(self)
rv._next = None
@@ -1501,17 +1503,17 @@ class If(Node):
class UserStatement(Node):
__slots__ = [
'line',
'parsed',
'block',
__slots__ = [
'line',
'parsed',
'block',
'translatable' ]
def __setstate__(self, state):
self.block = [ ]
self.translatable = False
Node.__setstate__(self, state)
def __init__(self, loc, line, block):
super(UserStatement, self).__init__(loc)
@@ -1521,22 +1523,22 @@ class UserStatement(Node):
# Do not store the parse quite yet.
_parse_info = renpy.statements.parse(self, self.line, self.block)
def diff_info(self):
return (UserStatement, self.line)
def execute(self):
next_node(self.get_next())
self.call("execute")
def predict(self):
self.call("predict")
self.call("predict")
return [ self.get_next() ]
def call(self, method, *args, **kwargs):
parsed = self.parsed
parsed = self.parsed
if parsed is None:
parsed = renpy.statements.parse(self, self.line, self.block)
self.parsed = parsed
@@ -1549,17 +1551,17 @@ class UserStatement(Node):
return renpy.game.script.lookup(rv)
else:
return self.next
def scry(self):
rv = Node.scry(self)
rv._next = self.get_next()
self.call("scry", rv)
return rv
def get_code(self, dialogue_filter=None):
return self.line
class Define(Node):
__slots__ = [
@@ -1576,11 +1578,11 @@ class Define(Node):
"""
super(Define, self).__init__(loc)
self.varname = name
self.code = PyCode(expr, loc=loc, mode='eval')
def diff_info(self):
def diff_info(self):
return (Define, tuple(self.varname))
def execute(self):
@@ -1589,7 +1591,7 @@ class Define(Node):
value = renpy.python.py_eval_bytecode(self.code.bytecode)
renpy.dump.definitions.append((self.varname, self.filename, self.linenumber))
setattr(renpy.store, self.varname, value)
setattr(renpy.store, self.varname, value)
class Screen(Node):
@@ -1607,10 +1609,10 @@ class Screen(Node):
"""
super(Screen, self).__init__(loc)
self.screen = screen
def diff_info(self):
def diff_info(self):
return (Screen, self.screen.name)
def execute(self):
@@ -1625,31 +1627,31 @@ class Screen(Node):
class Translate(Node):
"""
A translation block, produced either by explicit translation statements
A translation block, produced either by explicit translation statements
or implicit translation blocs.
If language is None, when executed this transfers control to the translate
If language is None, when executed this transfers control to the translate
statement in the current language, if any, and otherwise runs the block.
If language is not None, causes an error to occur if control reaches this
If language is not None, causes an error to occur if control reaches this
statement.
When control normally leaves a translate statement, in any language, it
When control normally leaves a translate statement, in any language, it
goes to the end of the translate statement in the None language.
"""
__slots__ = [
"identifier",
"language",
"block",
]
def __init__(self, loc, identifier, language, block):
super(Translate, self).__init__(loc)
self.identifier = identifier
self.language = language
self.block = block
def diff_info(self):
return (Translate, self.identifier, self.language)
@@ -1662,63 +1664,63 @@ class Translate(Node):
if self.language is not None:
next_node(self.next)
raise Exception("Translation nodes cannot be run directly.")
next_node(renpy.game.script.translator.lookup_translate(self.identifier))
renpy.game.context().translate_identifier = self.identifier
def predict(self):
node = renpy.game.script.translator.lookup_translate(self.identifier)
return [ node ]
def scry(self):
rv = Scry()
rv._next = renpy.game.script.translator.lookup_translate(self.identifier)
return rv
def get_children(self):
return self.block
def restructure(self, callback):
return callback(self.block)
class EndTranslate(Node):
"""
A node added implicitly after each translate block. It's responsible for
resetting the translation identifier.
"""
def __init__(self, loc):
super(EndTranslate, self).__init__(loc)
def diff_info(self):
return (EndTranslate,)
def execute(self):
next_node(self.next)
renpy.game.context().translate_identifier = None
class TranslateString(Node):
"""
A node used for translated strings.
"""
__slots__ = [
__slots__ = [
"language",
"old",
"new"
]
def __init__(self, loc, language, old, new):
super(TranslateString, self).__init__(loc)
self.language = language
self.old = old
self.new = new
def diff_info(self):
return (TranslateString,)
def execute(self):
next_node(self.next)
renpy.translation.add_string_translation(self.language, self.old, self.new)
@@ -1737,7 +1739,7 @@ class TranslatePython(Node):
@param hide: If True, the code will be executed with its
own local dictionary.
"""
super(TranslatePython, self).__init__(loc)
self.language = language
@@ -1752,5 +1754,3 @@ class TranslatePython(Node):
# def early_execute(self):
# renpy.python.create_store(self.store)
# renpy.python.py_exec_bytecode(self.code.bytecode, self.hide, store=self.store)
+130 -130
View File
@@ -29,7 +29,7 @@ def compiling(loc):
def executing(loc):
file, number = loc #@ReservedAssignment
renpy.game.exception_info = "Executing ATL code at %s:%d" % (file, number)
@@ -40,7 +40,7 @@ def atl_warper(f):
name = f.func_name
warpers[name] = f
return f
# The pause warper is used internally when no other warper is
# specified.
@atl_warper
@@ -51,7 +51,7 @@ def pause(t):
return 1.0
position = object()
# A dictionary giving property names and the corresponding default
# values.
PROPERTIES = {
@@ -110,17 +110,17 @@ def correct_type(v, b, ty):
def interpolate(t, a, b, type): #@ReservedAssignment
"""
Linearly interpolate the arguments.
Linearly interpolate the arguments.
"""
if t >= 1.0:
return b
# Recurse into tuples.
if isinstance(b, tuple):
if isinstance(b, tuple):
if a is None:
a = [ None ] * len(b)
return tuple(interpolate(t, i, j, ty) for i, j, ty in zip(a, b, type))
# Deal with booleans, nones, etc.
@@ -134,7 +134,7 @@ def interpolate(t, a, b, type): #@ReservedAssignment
else:
if a is None:
a = 0
return correct_type(a + t * (b - a), b, type)
# Interpolate the value of a spline. This code is based on Aenakume's code,
@@ -146,9 +146,9 @@ def interpolate_spline(t, spline):
if spline[0] is None:
return spline[-1]
if len(spline) == 2:
t_p = 1.0 - t
t_p = 1.0 - t
rv = t_p * spline[0] + t * spline[-1]
@@ -156,7 +156,7 @@ def interpolate_spline(t, spline):
t_pp = (1.0 - t)**2
t_p = 2 * t * (1.0 - t)
t2 = t**2
rv = t_pp * spline[0] + t_p * spline[1] + t2 * spline[2]
elif len(spline) == 4:
@@ -165,14 +165,14 @@ def interpolate_spline(t, spline):
t_pp = 3 * t * (1.0 - t)**2
t_p = 3 * t**2 * (1.0 - t)
t3 = t**3
rv = t_ppp * spline[0] + t_pp * spline[1] + t_p * spline[2] + t3 * spline[3]
else:
raise Exception("ATL can't interpolate splines of length %d." % len(spline))
return correct_type(rv, spline[-1], position)
# This is the context used when compiling an ATL statement. It stores the
# scopes that are used to evaluate the various expressions in the statement,
@@ -184,15 +184,15 @@ class Context(object):
def eval(self, expr): #@ReservedAssignment
expr = renpy.python.escape_unicode(expr)
return eval(expr, renpy.store.__dict__, self.context) #@UndefinedVariable
# This is intended to be subclassed by ATLTransform. It takes care of
# managing ATL execution, which allows ATLTransform itself to not care
# much about the contents of this file.
class ATLTransformBase(renpy.object.Object):
# Compatibility with older saves.
parameters = renpy.ast.ParameterInfo([ ], [ ], None, None)
parameters = renpy.ast.ParameterInfo([ ], [ ], None, None)
def __init__(self, atl, context, parameters):
# The constructor will be called by atltransform.
@@ -202,13 +202,13 @@ class ATLTransformBase(renpy.object.Object):
# The parameters that we take.
self.parameters = parameters
# The raw code that makes up this ATL statement.
self.atl = atl
# The context in which execution occurs.
self.context = Context(context)
# The code after it has been compiled into a block.
self.block = None
@@ -240,7 +240,7 @@ class ATLTransformBase(renpy.object.Object):
"""
super(ATLTransformBase, self).take_execution_state(t)
if t.atl is not self.atl:
return
@@ -258,7 +258,7 @@ class ATLTransformBase(renpy.object.Object):
if self.child is renpy.display.motion.null:
self.child = t.child
def __call__(self, *args, **kwargs):
@@ -275,7 +275,7 @@ class ATLTransformBase(renpy.object.Object):
if not positional and args:
child = args.pop(0)
# Handle positional arguments.
while positional and args:
name = positional.pop(0)
@@ -304,7 +304,7 @@ class ATLTransformBase(renpy.object.Object):
if child is None:
child = self.child
if child is None:
child = renpy.display.motion.get_null()
@@ -319,10 +319,10 @@ class ATLTransformBase(renpy.object.Object):
parameters=parameters)
rv.take_state(self)
return rv
def compile(self): #@ReservedAssignment
"""
Compiles the ATL code into a block. As necessary, updates the
@@ -331,9 +331,9 @@ class ATLTransformBase(renpy.object.Object):
if self.parameters.positional and self.parameters.positional[0][1] is None:
raise Exception("Cannot compile ATL Transform, as it's missing positional parameter %s." % self.parameters.positional[0])
old_exception_info = renpy.game.exception_info
self.block = self.atl.compile(self.context)
if len(self.block.statements) == 1 \
@@ -346,7 +346,7 @@ class ATLTransformBase(renpy.object.Object):
renpy.game.exception_info = old_exception_info
def execute(self, trans, st, at):
if self.done:
@@ -360,21 +360,21 @@ class ATLTransformBase(renpy.object.Object):
if self.child.transform_event != self.last_child_transform_event:
self.last_child_transform_event = self.child.transform_event
self.transform_event = self.child.transform_event
# Hide request.
if trans.hide_request:
self.transform_event = "hide"
if trans.replaced_request:
self.transform_event = "replaced"
# Notice transform events.
if self.transform_event != self.last_transform_event:
event = self.transform_event
self.last_transform_event = self.transform_event
else:
event = None
old_exception_info = renpy.game.exception_info
if self.atl.animation:
@@ -387,31 +387,31 @@ class ATLTransformBase(renpy.object.Object):
renpy.game.exception_info = old_exception_info
# print "Executing", self, self.state, self.xpos, self.ypos
if action == "continue":
self.atl_state = arg
else:
self.done = True
return pause
def predict_one(self):
self.atl.predict(self.context)
def visit(self):
if not self.block:
self.compile()
return self.children + self.block.visit()
# The base class for raw ATL statements.
class RawStatement(renpy.object.Object):
def __init__(self, loc):
super(RawStatement, self).__init__()
self.loc = loc
# Compiles this RawStatement into a Statement, by using ctx to
# evaluate expressions as necessary.
def compile(self, ctx): #@ReservedAssignment
@@ -437,7 +437,7 @@ class Statement(renpy.object.Object):
# "continue", state, pause - Causes this statement to execute
# again, with the given state passed in the second time around.
#
#
#
# "next", timeleft, pause - Causes the next statement to execute,
# with timeleft being the amount of time left after this statement
# finished.
@@ -469,12 +469,12 @@ class RawBlock(RawStatement):
def __init__(self, loc, statements, animation):
super(RawBlock, self).__init__(loc)
# A list of RawStatements in this block.
self.statements = statements
self.animation = animation
def compile(self, ctx): #@ReservedAssignment
compiling(self.loc)
@@ -485,30 +485,30 @@ class RawBlock(RawStatement):
def predict(self, ctx):
for i in self.statements:
i.predict(ctx)
# A compiled ATL block.
# A compiled ATL block.
class Block(Statement):
def __init__(self, loc, statements):
super(Block, self).__init__(loc)
# A list of statements in the block.
self.statements = statements
# The start times of various statements.
self.times = [ ]
for i, s in enumerate(statements):
if isinstance(s, Time):
self.times.append((s.time, i + 1))
self.times.sort()
def execute(self, trans, st, state, event):
executing(self.loc)
# Unpack the state.
if state is not None:
index, start, loop_start, repeats, times, child_state = state
@@ -519,7 +519,7 @@ class Block(Statement):
action = "continue"
arg = None
pause = None
while action == "continue":
# Target is the time we're willing to execute to.
@@ -597,7 +597,7 @@ class Block(Statement):
time, tindex = times[0]
if time <= target:
times.pop(0)
index = tindex
start = time
child_state = None
@@ -608,7 +608,7 @@ class Block(Statement):
def visit(self):
return [ j for i in self.statements for j in i.visit() ]
# This can become one of four things:
#
# - A pause.
@@ -623,11 +623,11 @@ class Block(Statement):
class RawMultipurpose(RawStatement):
warp_function = None
def __init__(self, loc):
super(RawMultipurpose, self).__init__(loc)
self.warper = None
self.duration = None
self.properties = [ ]
@@ -635,12 +635,12 @@ class RawMultipurpose(RawStatement):
self.splines = [ ]
self.revolution = None
self.circles = "0"
def add_warper(self, name, duration, warp_function):
self.warper = name
self.duration = duration
self.warp_function = warp_function
def add_property(self, name, exprs):
self.properties.append((name, exprs))
@@ -649,17 +649,17 @@ class RawMultipurpose(RawStatement):
def add_revolution(self, revolution):
self.revolution = revolution
def add_circles(self, circles):
self.circles = circles
def add_spline(self, name, exprs):
self.splines.append((name, exprs))
def compile(self, ctx): #@ReservedAssignment
compiling(self.loc)
# Figure out what kind of statement we have. If there's no
# interpolator, and no properties, than we have either a
# call, or a child statement.
@@ -679,7 +679,7 @@ class RawMultipurpose(RawStatement):
if isinstance(child, (int, float)):
return Interpolation(self.loc, "pause", child, [ ], None, 0, [ ])
if isinstance(child, ATLTransformBase):
child.compile()
return child.block
@@ -693,7 +693,7 @@ class RawMultipurpose(RawStatement):
if self.warp_function:
warper = ctx.eval(self.warp_function)
else:
else:
warper = self.warper or "pause"
if warper not in warpers:
@@ -709,7 +709,7 @@ class RawMultipurpose(RawStatement):
properties.append((name, value))
splines = [ ]
for name, exprs in self.splines:
if name not in PROPERTIES:
raise Exception("ATL Property %s is unknown at runtime." % property)
@@ -717,7 +717,7 @@ class RawMultipurpose(RawStatement):
values = [ ctx.eval(i) for i in exprs ]
splines.append((name, values))
for expr, _with in self.expressions:
try:
value = ctx.eval(expr)
@@ -739,11 +739,11 @@ class RawMultipurpose(RawStatement):
circles = ctx.eval(self.circles)
return Interpolation(self.loc, warper, duration, properties, self.revolution, circles, splines)
def predict(self, ctx):
for i, _j in self.expressions:
try:
i = ctx.eval(i)
except:
@@ -757,7 +757,7 @@ class RawMultipurpose(RawStatement):
renpy.easy.predict(i)
except:
continue
# This lets us have an ATL transform as our child.
class RawContainsExpr(RawStatement):
@@ -790,21 +790,21 @@ class RawChild(RawStatement):
return Child(self.loc, box, None)
# This changes the child of this statement, optionally with a transition.
class Child(Statement):
def __init__(self, loc, child, transition):
super(Child, self).__init__(loc)
self.child = renpy.easy.displayable(child)
self.transition = transition
def execute(self, trans, st, state, event):
executing(self.loc)
old_child = trans.raw_child
if (old_child is not None) and (old_child is not renpy.display.motion.null) and (self.transition is not None):
@@ -820,8 +820,8 @@ class Child(Statement):
def visit(self):
return [ self.child ]
# This causes interpolation to occur.
class Interpolation(Statement):
@@ -833,19 +833,19 @@ class Interpolation(Statement):
self.duration = duration
self.properties = properties
self.splines = splines
# The direction we revolve in: cw, ccw, or None.
self.revolution = revolution
# The number of complete circles we make.
self.circles = circles
def execute(self, trans, st, state, event):
executing(self.loc)
warper = warpers.get(self.warper, self.warper)
if self.duration:
complete = min(1.0, st / self.duration)
else:
@@ -866,7 +866,7 @@ class Interpolation(Statement):
# Now, the things we change linearly are in the difference
# between the new and old states.
linear = trans.state.diff(newts)
revolution = None
splines = [ ]
@@ -910,14 +910,14 @@ class Interpolation(Statement):
startangle += 360
startangle += self.circles * 360
# Store the revolution.
revolution = (startangle, endangle, startradius, endradius)
# Figure out the splines.
for name, values in self.splines:
splines.append((name, [ getattr(trans.state, name) ] + values))
state = (linear, revolution, splines)
# Ensure that we set things, even if they don't actually
@@ -933,26 +933,26 @@ class Interpolation(Statement):
for k, (old, new) in linear.iteritems():
value = interpolate(complete, old, new, PROPERTIES[k])
setattr(trans.state, k, value)
setattr(trans.state, k, value)
# Handle the revolution.
if revolution is not None:
startangle, endangle, startradius, endradius = revolution
trans.state.angle = interpolate(complete, startangle, endangle, float)
trans.state.radius = interpolate(complete, startradius, endradius, float)
# Handle any splines we might have.
for name, values in splines:
value = interpolate_spline(complete, values)
setattr(trans.state, name, value)
if st >= self.duration:
return "next", st - self.duration, None
else:
if not self.properties and not self.revolution and not self.splines:
return "continue", state, self.duration - st
else:
else:
return "continue", state, 0
@@ -973,7 +973,7 @@ class RawRepeat(RawStatement):
if repeats is not None:
repeats = ctx.eval(repeats)
return Repeat(self.loc, repeats)
class Repeat(Statement):
@@ -1003,10 +1003,10 @@ class RawParallel(RawStatement):
def predict(self, ctx):
for i in self.blocks:
i.predict(ctx)
class Parallel(Statement):
def __init__(self, loc, blocks):
super(Parallel, self).__init__(loc)
self.blocks = blocks
@@ -1014,7 +1014,7 @@ class Parallel(Statement):
def execute(self, trans, st, state, event):
executing(self.loc)
if state is None:
state = [ (i, None) for i in self.blocks ]
@@ -1026,9 +1026,9 @@ class Parallel(Statement):
# The new state structure.
newstate = [ ]
for i, istate in state:
action, arg, pause = i.execute(trans, st, istate, event)
if pause is not None:
@@ -1040,7 +1040,7 @@ class Parallel(Statement):
left.append(arg)
elif action == "event":
return action, arg, pause
if newstate:
return "continue", newstate, min(pauses)
else:
@@ -1078,7 +1078,7 @@ class Choice(Statement):
def execute(self, trans, st, state, event):
executing(self.loc)
if state is None:
total = 0
@@ -1107,7 +1107,7 @@ class Choice(Statement):
def visit(self):
return [ j for i in self.choices for j in i[1].visit() ]
# The Time statement.
class RawTime(RawStatement):
@@ -1130,7 +1130,7 @@ class Time(Statement):
def execute(self, trans, st, state, event):
return "continue", None, None
# The On statement.
@@ -1162,7 +1162,7 @@ class On(Statement):
super(On, self).__init__(loc)
self.handlers = handlers
def execute(self, trans, st, state, event):
executing(self.loc)
@@ -1184,23 +1184,23 @@ class On(Statement):
start = st
cstate = None
while True:
# If we don't have a handler, return until we change event.
if name not in self.handlers:
return "continue", (name, start, cstate), None
action, arg, pause = self.handlers[name].execute(trans, st - start, cstate, event)
# If we get a continue, save our state.
if action == "continue":
# If it comes from a hide block, indicate that.
if name == "hide" or name == "replaced":
trans.hide_response = False
trans.replaced_response = False
return "continue", (name, start, arg), pause
# If we get a next, then try going to the default
@@ -1235,7 +1235,7 @@ class On(Statement):
# Event statement.
class RawEvent(RawStatement):
def __init__(self, loc, name):
@@ -1246,7 +1246,7 @@ class RawEvent(RawStatement):
def compile(self, ctx): #@ReservedAssignment
return Event(self.loc, self.name)
class Event(Statement):
def __init__(self, loc, name):
@@ -1256,7 +1256,7 @@ class Event(Statement):
def execute(self, trans, st, state, event):
return "event", (self.name, st), None
class RawFunction(RawStatement):
@@ -1269,9 +1269,9 @@ class RawFunction(RawStatement):
compiling(self.loc)
return Function(self.loc, ctx.eval(self.expr))
class Function(Statement):
def __init__(self, loc, function):
super(Function, self).__init__(loc)
@@ -1279,13 +1279,13 @@ class Function(Statement):
def execute(self, trans, st, state, event):
fr = self.function(trans, st, trans.at)
if fr is not None:
return "continue", None, fr
else:
return "next", 0, None
# This parses an ATL block.
def parse_atl(l):
@@ -1295,11 +1295,11 @@ def parse_atl(l):
statements = [ ]
animation = False
while not l.eob:
loc = l.get_location()
if l.keyword('repeat'):
repeats = l.simple_expression()
@@ -1310,7 +1310,7 @@ def parse_atl(l):
l.expect_eol()
l.expect_block('block')
block = parse_atl(l.subblock_lexer())
block = parse_atl(l.subblock_lexer())
statements.append(block)
elif l.keyword('contains'):
@@ -1328,14 +1328,14 @@ def parse_atl(l):
l.expect_eol()
l.expect_block('contains')
block = parse_atl(l.subblock_lexer())
block = parse_atl(l.subblock_lexer())
statements.append(RawChild(loc, block))
elif l.keyword('parallel'):
l.require(':')
l.expect_eol()
l.expect_block('parallel')
block = parse_atl(l.subblock_lexer())
statements.append(RawParallel(loc, block))
@@ -1348,7 +1348,7 @@ def parse_atl(l):
l.require(':')
l.expect_eol()
l.expect_block('choice')
block = parse_atl(l.subblock_lexer())
statements.append(RawChoice(loc, chance, block))
@@ -1359,7 +1359,7 @@ def parse_atl(l):
l.require(':')
l.expect_eol()
l.expect_block('on')
block = parse_atl(l.subblock_lexer())
statements.append(RawOn(loc, name, block))
@@ -1388,13 +1388,13 @@ def parse_atl(l):
elif l.keyword('animation'):
l.expect_noblock('animation')
animation = True
else:
# If we can't assign it it a statement more specifically,
# we try to parse it into a RawMultipurpose. That will
# then be turned into another statement, as appropriate.
# The RawMultipurpose we add things to.
rm = renpy.atl.RawMultipurpose(loc)
@@ -1403,29 +1403,29 @@ def parse_atl(l):
# Is this clause an expression?
this_expression = False
# First, look for a warper.
cp = l.checkpoint()
warper = l.name()
if warper in warpers:
duration = l.require(l.simple_expression)
warp_function = None
elif warper == "warp":
warper = None
warp_function = l.require(l.simple_expression)
duration = l.require(l.simple_expression)
else:
l.revert(cp)
warper = None
warp_function = None
duration = "0"
rm.add_warper(warper, duration, warp_function)
# Now, look for properties and simple_expressions.
@@ -1437,7 +1437,7 @@ def parse_atl(l):
if l.keyword('pass'):
continue
# Parse revolution keywords.
if l.keyword('clockwise'):
rm.add_revolution('clockwise')
@@ -1451,9 +1451,9 @@ def parse_atl(l):
expr = l.require(l.simple_expression)
rm.add_circles(expr)
# Try to parse a property.
# Try to parse a property.
cp = l.checkpoint()
prop = l.name()
if prop in PROPERTIES:
@@ -1464,7 +1464,7 @@ def parse_atl(l):
# presence of knots that determine which one it is.
knots = [ ]
while l.keyword('knot'):
knots.append(l.require(l.simple_expression))
@@ -1475,7 +1475,7 @@ def parse_atl(l):
rm.add_property(prop, expr)
continue
# Otherwise, try to parse it as a simple expressoon,
# with an optional with clause.
@@ -1490,7 +1490,7 @@ def parse_atl(l):
l.error('ATL statement contains two expressions in a row; is one of them a misspelled property? If not, separate them with pass.')
this_expression = True
if l.keyword("with"):
with_expr = l.require(l.simple_expression)
else:
@@ -1499,17 +1499,17 @@ def parse_atl(l):
rm.add_expression(expr, with_expr)
l.expect_noblock('ATL')
statements.append(rm)
if l.eol():
l.advance()
continue
l.require(",", "comma or end of line")
# Merge together statements that need to be merged together.
merged = [ ]
@@ -1538,7 +1538,7 @@ def parse_atl(l):
elif new is None:
old = new
continue
merged.append(new)
old = new
+241 -67
View File
@@ -24,11 +24,11 @@
# Invariants: The periodic callback assumes pcm_ok. If we don't have
# at least pcm_ok, we have no sound whatsoever.
import renpy.audio
import renpy.display
import renpy.audio # @UnusedImport
import renpy.display # @UnusedImport
import time
import pygame
import pygame # @UnusedImport
import os
import atexit
@@ -41,8 +41,8 @@ pss = None
if 'pss' not in disable:
try:
import pysdlsound as pss
pss.check_version(4)
atexit.register(pss.quit)
pss.check_version(4) # @UndefinedVariable
atexit.register(pss.quit) # @UndefinedVariable
except:
pass
@@ -53,12 +53,12 @@ if 'pss' not in disable:
except:
pass
# Save the mixer, and restore it at exit.
old_wave = None
old_midi = None
# This is True if we were able to sucessfully enable the pcm audio.
pcm_ok = None
@@ -69,7 +69,7 @@ def get_serial():
"""
Gets a globally unique serial number for each music change.
"""
global serial
serial += 1
return (unique, serial)
@@ -107,7 +107,7 @@ class MusicContext(renpy.python.RevertableObject):
def __init__(self):
super(MusicContext, self).__init__()
# The time this channel was last ordered panned.
self.pan_time = None
@@ -116,10 +116,10 @@ class MusicContext(renpy.python.RevertableObject):
# The time the secondary volume was last ordered changed.
self.secondary_volume_time = None
# The secondary volume.
self.secondary_volume = 1.0
# The time the channel was ordered last changed.
self.last_changed = 0
@@ -131,7 +131,7 @@ class MusicContext(renpy.python.RevertableObject):
# Should we force stop this channel?
self.force_stop = False
def copy(self):
"""
Returns a shallow copy of this context.
@@ -141,21 +141,21 @@ class MusicContext(renpy.python.RevertableObject):
rv.__dict__.update(self.__dict__)
return rv
# The next channel number to be assigned.
next_channel_number = 0
class Channel(object):
"""
This stores information about the currently-playing music.
"""
def __init__(self, name, default_loop, stop_on_mute, tight, file_prefix, file_suffix, buffer_queue):
# The name assigned to this channel. This is used to look up
# information about the channel in the MusicContext object.
self.name = name
# The number this channel has been assigned, or None if we've yet
# to assign a number to the channel. We only assign a channel
# number when there's an operation on the channel other than
@@ -220,7 +220,7 @@ class Channel(object):
# Should we buffer upcoming music/video in the queue?
self.buffer_queue = buffer_queue
if default_loop is None:
# By default, should we loop the music?
self.default_loop = True
@@ -230,7 +230,7 @@ class Channel(object):
else:
self.default_loop = default_loop
self.default_loop_set = True
def get_number(self):
"""
@@ -238,12 +238,12 @@ class Channel(object):
proves necessary.
"""
global next_channel_number
rv = self._number
if rv is None:
rv = self._number = next_channel_number
next_channel_number += 1
return rv
number = property(get_number)
@@ -263,14 +263,22 @@ class Channel(object):
return rv
context = property(get_context)
def periodic(self):
"""
This is the periodic call that causes this channel to load new stuff
into its queues, if necessary.
"""
# Update the channel volume.
vol = self.chan_volume * renpy.game.preferences.volumes[self.mixer]
if vol != self.actual_volume:
pss.set_volume(self.number, vol)
self.actual_volume = vol
# This should be set from something that checks to see if our
# mixer is muted.
force_stop = self.context.force_stop or (renpy.game.preferences.mute[self.mixer] and self.stop_on_mute)
@@ -286,7 +294,7 @@ class Channel(object):
else:
self.queue = [ ]
return
# Should we do the callback?
do_callback = False
@@ -314,7 +322,7 @@ class Channel(object):
# give up here.
if not self.buffer_queue and depth >= 1:
break
# We can't queue anything if the depth is > 0 and we're
# waiting for a synchro_start.
if self.synchro_start and depth:
@@ -336,7 +344,7 @@ class Channel(object):
if not topq:
continue
try:
topf = load(self.file_prefix + topq.filename + self.file_suffix)
@@ -363,11 +371,11 @@ class Channel(object):
if self.loop and not self.queue:
for i in self.loop:
newq = QueueEntry(i, 0, topq.tight)
newq = QueueEntry(i, 0, topq.tight)
self.queue.append(newq)
else:
do_callback = True
# Queue empty callback.
if do_callback and self.callback:
self.callback() # E1102
@@ -382,7 +390,7 @@ class Channel(object):
self.queue = self.queue[:self.keep_queue]
self.loop = [ ]
if not pcm_ok:
return
@@ -395,7 +403,7 @@ class Channel(object):
"""
self.keep_queue = 0
if pcm_ok:
if self.pan_time != self.context.pan_time:
@@ -410,7 +418,7 @@ class Channel(object):
pss.set_secondary_volume(self.number,
self.context.secondary_volume,
0)
if not self.queue and self.callback:
self.callback() # E1102
@@ -429,14 +437,14 @@ class Channel(object):
if secs == 0:
pss.stop(self.number)
else:
else:
pss.fadeout(self.number, int(secs * 1000))
def enqueue(self, filenames, loop=True, synchro_start=False, fadein=0, tight=None):
for filename in filenames:
renpy.game.persistent._seen_audio[filename] = True
renpy.game.persistent._seen_audio[filename] = True # @UndefinedVariable
if not pcm_ok:
return
@@ -444,7 +452,7 @@ class Channel(object):
tight = self.tight
self.keep_queue += 1
for filename in filenames:
qe = QueueEntry(filename, int(fadein * 1000), tight)
self.queue.append(qe)
@@ -472,17 +480,17 @@ class Channel(object):
if rv is None and self.loop:
rv = self.loop[0]
return rv
def set_volume(self, volume):
self.chan_volume = volume
def get_pos(self):
if not pcm_ok:
return -1
return pss.get_pos(self.number)
def set_pan(self, pan, delay):
@@ -503,6 +511,179 @@ class Channel(object):
self.secondary_volume_time = self.context.secondary_volume_time
pss.set_secondary_volume(self.number, self.context.secondary_volume, delay)
################################################################################
# Android VideoPlayer Channel
################################################################################
if renpy.android:
import jnius # @UnresolvedImport
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 start(self):
"""
Starts playing the first video in the queue.
"""
if not self.queue:
return
filename = self.queue.pop(0)
f = renpy.loader.load(filename)
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):
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
# A list of channels we know about.
all_channels = [ ]
@@ -512,7 +693,7 @@ channels = { }
def register_channel(name, mixer=None, loop=None, stop_on_mute=True, tight=False, file_prefix="", file_suffix="", buffer_queue=True):
"""
:doc: other
:doc: audio
This registers a new audio channel named `name`. Audio can then be
played on the channel by supplying the channel name to the play or
@@ -529,7 +710,7 @@ def register_channel(name, mixer=None, loop=None, stop_on_mute=True, tight=False
`stop_on_mute`
If true, music on the channel is stopped when the channel is muted.
`tight`
If true, sounds will loop even when fadeout is occuring. This should
be set to True for a sound effects or seamless music channel, and False
@@ -551,12 +732,17 @@ def register_channel(name, mixer=None, loop=None, stop_on_mute=True, tight=False
if not renpy.game.context().init_phase:
raise Exception("Can't register channel outside of init phase.")
c = Channel(name, loop, stop_on_mute, tight, file_prefix, file_suffix, buffer_queue)
if renpy.android and name == "movie":
c = AndroidVideoChannel(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)
c.mixer = mixer
all_channels.append(c)
channels[name] = c
def alias_channel(name, newname):
if not renpy.game.context().init_phase:
raise Exception("Can't alias channel outside of init phase.")
@@ -564,13 +750,13 @@ def alias_channel(name, newname):
c = get_channel(name)
channels[newname] = c
def get_channel(name):
rv = channels.get(name)
if rv is None:
raise Exception("Audio channel %r is unknown." % name)
return rv
def set_force_stop(name, value):
@@ -588,7 +774,7 @@ def init():
if pcm_ok is None and pss:
bufsize = 2048
if 'RENPY_SOUND_BUFSIZE' in os.environ:
bufsize = int(os.environ['RENPY_SOUND_BUFSIZE'])
@@ -608,7 +794,7 @@ def init():
mixers.append(c.mixer)
default_volume = 1.0
for m in mixers:
renpy.game.preferences.volumes.setdefault(m, default_volume)
renpy.game.preferences.mute.setdefault(m, False)
@@ -621,23 +807,23 @@ def quit(): #@ReservedAssignment
if not pcm_ok:
return
for c in all_channels:
c.dequeue()
c.fadeout(0)
c.queue = [ ]
c.loop = [ ]
c.playing = False
c.playing_midi = False
c.wait_stop = False
c.synchro_start = False
pss.quit()
pcm_ok = None
mix_ok = None
# The last-set pcm volume.
pcm_volume = None
@@ -661,12 +847,12 @@ def periodic():
c.periodic()
pss.periodic()
# Perform a synchro-start if necessary.
need_ss = False
for c in all_channels:
if c.synchro_start and c.wait_stop:
need_ss = False
break
@@ -680,23 +866,11 @@ def periodic():
for c in all_channels:
c.synchro_start = False
# Now, consider adjusting the volume of the channel.
volumes = renpy.game.preferences.volumes
for c in all_channels:
vol = c.chan_volume * volumes[c.mixer]
if vol != c.actual_volume:
pss.set_volume(c.number, vol)
c.actual_volume = vol
except:
if renpy.config.debug_sound:
raise
def interact():
"""
Called at least once per interaction.
@@ -714,7 +888,7 @@ def interact():
# c.set_volume(_music_volumes.get(i, 1.0))
ctx = c.context
# If we're in the same music change, then do nothing with the
# music.
if c.last_changed == ctx.last_changed:
+132 -67
View File
@@ -37,45 +37,57 @@ register_channel; alias_channel
def play(filenames, channel="music", loop=None, fadeout=None, synchro_start=False, fadein=0, tight=None, if_changed=False):
"""
:doc: audio
This stops the music currently playing on the numbered channel, dequeues
any queued music, and begins playing the specified file or files. If loop
is True, the tracks will loop while they are the last thing in
the queue. If fadeout is None, the fadeout time is taken from
config.fade_music, otherwise it is a time in seconds to fade for.
any queued music, and begins playing the specified file or files.
Filenames may be a single file, or a list of files.
`filenames`
This may be a single file, or a list of files to be played.
Fadein is the number of seconds to fade the music in for, on the
first loop only.
If synchro_start is given, all the channels that have had play
called on them with synchro_start set to True will be started at
the same time, in a sample accurate manner.
The filenames given becomes the last queued files if loop is
True. If loop is False, then there are no last queued files.
If tight is True, then fadeouts will span into the next-queued sound.
`channel`
The channel to play the sound on.
If if_changed is True, and the music file is currently playing,
then it will not be stopped/faded out and faded back in again, but
instead will be kept playing. (This will always queue up an additional
loop of the music.)
`loop`
If this is True, the tracks will loop while they are the last thing
in the queue.
`fadeout`
If not None, this is a time in seconds to fade for. Otherwise the
fadeout time is taken from config.fade_music.
`synchro_start`
If synchro_start is given, all the channels that have had play
called on them with synchro_start set to True will be started at
the same time, in a sample accurate manner.
`fadein`
This is the number of seconds to fade the music in for, on the
first loop only.
`tight`
If this is True, then fadeouts will span into the next-queued sound.
`if_changed`
If this is True, and the music file is currently playing,
then it will not be stopped/faded out and faded back in again, but
instead will be kept playing. (This will always queue up an additional
loop of the music.)
"""
if renpy.game.context().init_phase:
raise Exception("Can't play music during init phase.")
if filenames is None:
return
if isinstance(filenames, basestring):
filenames = [ filenames ]
try:
try:
c = get_channel(channel)
ctx = c.context
if loop is None:
loop = c.default_loop
@@ -90,44 +102,51 @@ def play(filenames, channel="music", loop=None, fadeout=None, synchro_start=Fals
c.fadeout(fadeout)
c.enqueue(filenames, loop=loop, synchro_start=synchro_start, fadein=fadein, tight=tight)
t = get_serial()
ctx.last_changed = t
c.last_changed = t
if loop:
ctx.last_filenames = filenames
ctx.last_tight = tight
ctx.last_filenames = filenames
ctx.last_tight = tight
else:
ctx.last_filenames = [ ]
ctx.last_tight = False
except:
if renpy.config.debug_sound:
raise
def queue(filenames, channel="music", loop=None, clear_queue=True, fadein=0, tight=None):
"""
This queues the given filenames on the specified channel. If
clear_queue is True, then the queue is cleared, making these files
the files that are played when the currently playing file
finishes. If it is False, then these files are placed at the back of
the queue. In either case, if no music is playing these files
begin playing immediately.
:doc: audio
Filenames may either be a single filename, or a list of filenames.
This queues the given filenames on the specified channel.
Fadein is the number of seconds to fade the music in for, on the
first loop only.
`filenames`
This may be a single file, or a list of files to be played.
If loop is True, then this music will repeat as long as it is the
last element of the queue.
`channel`
The channel to play the sound on.
`loop`
If this is True, the tracks will loop while they are the last thing
in the queue.
The filenames given becomes the last queued file if loop is
True. If loop is False, then the last queued file is set to None.
`clear_queue`
If True, then the queue is cleared, making these files the files that
are played when the currently playing file finishes. If it is False,
then these files are placed at the back of the queue. In either case,
if no music is playing these files begin playing immediately.
If tight is True, then fadeouts will span into the next-queued sound.
`fadein`
This is the number of seconds to fade the music in for, on the
first loop only.
`tight`
If this is True, then fadeouts will span into the next-queued sound.
"""
if renpy.game.context().init_phase:
@@ -140,19 +159,19 @@ def queue(filenames, channel="music", loop=None, clear_queue=True, fadein=0, tig
if isinstance(filenames, basestring):
filenames = [ filenames ]
try:
try:
c = get_channel(channel)
ctx = c.context
if loop is None:
loop = c.default_loop
if clear_queue:
c.dequeue(True)
c.enqueue(filenames, loop=loop, fadein=fadein, tight=tight)
t = get_serial()
ctx.last_changed = t
c.last_changed = t
@@ -163,7 +182,7 @@ def queue(filenames, channel="music", loop=None, clear_queue=True, fadein=0, tig
else:
ctx.last_filenames = [ ]
ctx.last_tight = False
except:
if renpy.config.debug_sound:
raise
@@ -175,43 +194,54 @@ def playable(filename, channel="music"):
"""
c = get_channel(channel)
return renpy.loader.loadable(c.file_prefix + filename + c.file_suffix)
def stop(channel="music", fadeout=None):
"""
:doc: audio
This stops the music that is currently playing, and dequeues all
queued music. If fadeout is None, the music is faded out for the
time given in config.fade_music, otherwise it is faded for fadeout
seconds.
This sets the last queued file to None.
`channel`
The channel to stop the sound on.
`fadeout`
If not None, this is a time in seconds to fade for. Otherwise the
fadeout time is taken from config.fade_music.
"""
if renpy.game.context().init_phase:
return
try:
try:
c = get_channel(channel)
ctx = c.context
if fadeout is None:
fadeout = renpy.config.fade_music
c.fadeout(fadeout)
c.fadeout(fadeout)
t = get_serial()
ctx.last_changed = t
c.last_changed = t
ctx.last_filenames = [ ]
ctx.last_tight = False
except:
if renpy.config.debug_sound:
raise
def set_music(channel, flag, default=False):
"""
Determines if channel will loop by default.
@@ -224,7 +254,7 @@ def set_music(channel, flag, default=False):
c.default_loop = flag
c.default_loop_set = True
def get_delay(time, channel="music"):
"""
Returns the number of seconds left until the given time in the
@@ -248,9 +278,11 @@ def get_delay(time, channel="music"):
raise
return None
def get_playing(channel="music"):
"""
:doc: audio
Returns true if the given channel is playing.
"""
@@ -265,20 +297,36 @@ def get_playing(channel="music"):
def is_playing(channel="music"):
"""
:doc: audio
Returns True if the channel is currently playing a sound, False if
it is not, or if the sound system isn't working.
"""
return (get_playing(channel=channel) is not None)
def set_volume(volume, delay=0, channel="music"):
"""
:doc: audio
Sets the volume of this channel, as a fraction of the volume of the
mixer controlling the channel.
`volume`
This is a number between 0.0 and 1.0, and is interpreted as a fraction
of the mixer volume for the channel.
`delay`
It takes delay seconds to change/fade the volume from the old to
the new value. This value is persisted into saves, and participates
in rollback.
`channel`
The channel to be set
"""
try:
try:
c = renpy.audio.audio.get_channel(channel)
c.set_secondary_volume(volume, delay)
except:
@@ -287,10 +335,25 @@ def set_volume(volume, delay=0, channel="music"):
def set_pan(pan, delay, channel="music"):
"""
:doc: audio
Sets the pan of this channel.
`pan`
A number between -1 and 1 that control the placement of the audio.
If this is -1, then all audio is sent to the left channel.
If it's 0, then the two channels are equally balanced. If it's 1,
then all audio is sent to the right ear.
`delay`
The amount of time it takes for the panning to occur.
`channel`
The channel the panning takes place on. This can be a sound or a music
channel. Often, this is channel 7, the default music channel.
"""
try:
try:
c = renpy.audio.audio.get_channel(channel)
c.set_pan(pan, delay)
except:
@@ -299,6 +362,8 @@ def set_pan(pan, delay, channel="music"):
def set_queue_empty_callback(callback, channel="music"):
"""
:doc: audio
This sets a callback that is called when the queue is empty. This
callback is called when the queue first becomes empty, and at
least once per interaction while the queue is empty.
@@ -308,7 +373,7 @@ def set_queue_empty_callback(callback, channel="music"):
note that the callback may be called while a sound is playing, as
long as a queue slot is empty.
"""
try:
try:
c = renpy.audio.audio.get_channel(channel)
c.callback = callback
except:
@@ -327,7 +392,7 @@ def set_mixer(channel, mixer, default=False):
This function should only be called in an init block.
"""
try:
try:
c = renpy.audio.audio.get_channel(channel)
if not default or c.mixer is None:
@@ -342,12 +407,12 @@ def channel_defined(channel):
Returns True if the channel exists, or False otherwise.
"""
try:
try:
renpy.audio.audio.get_channel(channel)
return True
except:
return False
# Music change logic:
+1 -1
View File
@@ -49,7 +49,7 @@ def queue(filename, channel="sound", clear_queue=True, fadein=0, tight=False, lo
def stop(channel="sound", fadeout=0):
renpy.audio.music.stop(channel=channel,
fadeout=fadeout)
set_mixer = renpy.audio.music.set_mixer
set_queue_empty_callback = renpy.audio.music.set_queue_empty_callback
+60 -62
View File
@@ -55,7 +55,7 @@ def extra_imports():
import difflib; difflib
import shutil; shutil
import tarfile; tarfile
import bz2; bz2
import bz2; bz2 # @UnresolvedImport
import webbrowser; webbrowser
import pygame.locals; pygame.locals
import pygame.color; pygame.color
@@ -74,31 +74,31 @@ def extra_imports():
import rsa; rsa
import decimal; decimal
import plistlib; plistlib
class NullFile(io.IOBase):
"""
This file raises an error on input, and IOError on read.
"""
def write(self, s):
return
def read(self, length=None):
raise IOError("Not implemented.")
def null_files():
try:
if sys.stderr.fileno() < 0:
sys.stderr = NullFile()
if sys.stdout.fileno() < 0:
sys.stdout = NullFile()
except:
pass
null_files()
trace_file = None
trace_local = None
@@ -106,7 +106,7 @@ def trace_function(frame, event, arg):
fn = os.path.basename(frame.f_code.co_filename)
print >>trace_file, fn, frame.f_lineno, frame.f_code.co_name, event
return trace_local
def enable_trace(level):
global trace_file
global trace_local
@@ -117,7 +117,7 @@ def enable_trace(level):
trace_local = trace_function
else:
trace_local = None
sys.settrace(trace_function)
def mac_start(fn):
@@ -136,7 +136,7 @@ def bootstrap(renpy_base):
os.environ["RENPY_BASE"] = os.path.abspath(renpy_base)
renpy_base = unicode(renpy_base, FSENCODING, "replace")
# If environment.txt exists, load it into the os.environ dictionary.
if os.path.exists(renpy_base + "/environment.txt"):
evars = { }
@@ -150,14 +150,14 @@ def bootstrap(renpy_base):
alt_path = os.path.abspath("renpy_base")
if ".app" in alt_path:
alt_path = alt_path[:alt_path.find(".app")+4]
if os.path.exists(alt_path + "/environment.txt"):
evars = { }
execfile(alt_path + "/environment.txt", evars)
for k, v in evars.iteritems():
if k not in os.environ:
os.environ[k] = str(v)
# Get a working name for the game.
name = os.path.basename(sys.argv[0])
@@ -173,7 +173,7 @@ def bootstrap(renpy_base):
if args.command == "presplash":
import renpy.display.presplash
renpy.display.presplash.show(sys.argv[3])
if args.trace:
enable_trace(args.trace)
@@ -181,7 +181,7 @@ def bootstrap(renpy_base):
basedir = os.path.abspath(args.basedir).decode(FSENCODING)
else:
basedir = renpy_base
gamedirs = [ name ]
game_name = name
@@ -207,7 +207,7 @@ def bootstrap(renpy_base):
gamedir = basedir
sys.path.insert(0, basedir)
# Force windib on windows, unless the user explicitly overrides.
if renpy.windows and not 'SDL_VIDEODRIVER' in os.environ:
os.environ['SDL_VIDEODRIVER'] = 'windib'
@@ -220,7 +220,7 @@ def bootstrap(renpy_base):
# If we're on a mac, install our own os.start.
if renpy.macintosh:
os.startfile = mac_start
# Check that we have installed pygame properly. This also deals with
# weird cases on Windows and Linux where we can't import modules. (On
# windows ";" is a directory separator in PATH, so if it's in a parent
@@ -230,12 +230,12 @@ def bootstrap(renpy_base):
import pygame; pygame
except:
print >>sys.stderr, """\
Could not import pygame. Please ensure that this program has been built
and unpacked properly. Also, make sure that the directories containing
Could not import pygame. Please ensure that this program has been built
and unpacked properly. Also, make sure that the directories containing
this program do not contain : or ; in their names.
"""
raise
# Load up all of Ren'Py, in the right order.
import renpy #@Reimport
@@ -253,7 +253,7 @@ this program do not contain : or ; in their names.
renpy.config.basedir = basedir
renpy.config.gamedir = gamedir
renpy.config.args = [ ]
if renpy.android:
renpy.config.logdir = os.environ['ANDROID_PUBLIC']
else:
@@ -274,7 +274,7 @@ this program do not contain : or ; in their names.
if renpy.display.draw:
renpy.display.draw.deinit()
renpy.display.draw.quit()
# On an UtterRestart, reload Ren'Py.
renpy.reload_all()
continue
@@ -292,7 +292,7 @@ this program do not contain : or ; in their names.
sys.exit(0)
finally:
if "RENPY_SHUTDOWN_TRACE" in os.environ:
enable_trace(int(os.environ["RENPY_SHUTDOWN_TRACE"]))
@@ -301,8 +301,6 @@ this program do not contain : or ; in their names.
if renpy.display.draw:
renpy.display.draw.quit()
renpy.translation.write_updated_strings()
# Prevent subprocess from throwing errors while trying to run it's
# __del__ method during shutdown.
import subprocess # W0403
@@ -312,12 +310,12 @@ def report_line(out, filename, line, what):
out.write(' File "%s", line %d, in %s\n' % (filename, line, what))
try:
fn = renpy.parser.unelide_filename(filename)
f = file(fn, "rb")
f = file(fn, "rb")
lines = f.read().decode("utf-8").replace("\r", "").split("\n")
out.write(" " + lines[line - 1].encode("utf-8") + "\n")
except:
pass
def write_utf8_traceback_list(out, l):
"""
@@ -327,28 +325,28 @@ def write_utf8_traceback_list(out, l):
ul = [ ]
for filename, line, what, text in l:
# Filename is either unicode or an fsecoded string.
if not isinstance(filename, unicode):
filename = unicode(filename, FSENCODING, "replace")
# Line is a number.
# Assume what is in a unicode encoding, since it is either python,
# or comes from inside Ren'Py.
# or comes from inside Ren'Py.
if isinstance(text, str):
text = text.decode("utf-8", "replace")
ul.append((filename, line, what, text))
for t in traceback.format_list(ul):
out.write(t.encode("utf-8", "replace"))
def script_level_traceback(out, tb):
"""
Writes a script-level traceback to out, based on the traceback
Writes a script-level traceback to out, based on the traceback
object tb.
"""
@@ -359,15 +357,15 @@ def script_level_traceback(out, tb):
line = tb.tb_lineno
co = f.f_code
filename = co.co_filename
if filename.endswith(".rpy") and not filename.replace("\\", "/").startswith("common/"):
tbl.append((filename, line, "python", None))
elif 'self' in f.f_locals:
obj = f.f_locals['self']
import renpy
if isinstance(obj, renpy.execution.Context):
tbl.extend(obj.report_tb(out))
@@ -380,34 +378,34 @@ def open_error_file(fn, mode):
Opens an error/log/file. Returns the open file, and the filename that
was opened.
"""
try:
f = file(os.path.join(renpy.config.logdir, fn), mode)
return f, fn
except:
pass
try:
f = file(fn, mode)
return f, fn
except:
pass
import tempfile
fn = os.path.join(tempfile.gettempdir(), "renpy-" + fn)
return file(fn, mode), fn
def report_exception(e, editor=True):
"""
Reports an exception by writing it to standard error and
traceback.txt. If `editor` is True, opens the traceback
Reports an exception by writing it to standard error and
traceback.txt. If `editor` is True, opens the traceback
up in a text editor.
Returns a two-unicode tuple, with the first item being
Returns a two-unicode tuple, with the first item being
a simple message, and the second being a full traceback.
"""
import codecs
type, _value, tb = sys.exc_info() #@ReservedAssignment
@@ -417,27 +415,27 @@ def report_exception(e, editor=True):
m = unicode(e)
except:
m = str(e)
if isinstance(m, unicode):
return m.encode("utf-8", "replace")
else:
return m
# Return values - which can be displayed to the user.
simple = cStringIO.StringIO()
full = cStringIO.StringIO()
print >>simple, renpy.game.exception_info
script_level_traceback(simple, tb)
print >>simple, type.__name__ + ":",
print >>simple, type.__name__ + ":",
print >>simple, safe_utf8(e)
print >>full, "Full traceback:"
tbl = traceback.extract_tb(tb)
write_utf8_traceback_list(full, tbl)
print >>full, type.__name__ + ":",
print >>full, type.__name__ + ":",
print >>full, safe_utf8(e)
# Write to stdout/stderr.
sys.stdout.write("\n")
sys.stdout.write(full.getvalue())
@@ -452,10 +450,10 @@ def report_exception(e, editor=True):
except:
pass
simple = simple.getvalue()
full = full.getvalue()
# Inside of the file, which may not be openable.
try:
@@ -467,14 +465,14 @@ def report_exception(e, editor=True):
print >>f
f.write(simple)
print >>f
print >>f, "-- Full Traceback ------------------------------------------------------------"
print >>f
print >>f
f.write(full)
f.close()
try:
if editor and renpy.game.args.command == "run": #@UndefinedVariable
renpy.exports.launch_editor([ traceback_fn ], 1, transient=1)
@@ -496,12 +494,12 @@ old_memory = { }
def memory_profile():
"""
Calling this function displays the change in the number of instances of
Calling this function displays the change in the number of instances of
each type of object.
"""
print "- Memory Profile ---------------------------------------------------"
import gc
gc.collect()
Regular → Executable
+119 -107
View File
@@ -47,14 +47,14 @@ class DialogueTextTags(object):
# The time to delay for each pause. None to delay forever.
self.pause_delay = [ ]
# True if we've encountered the no-wait tag.
self.no_wait = False
i = iter(TAG_RE.split(s))
while True:
try:
self.text += i.next()
@@ -65,7 +65,7 @@ class DialogueTextTags(object):
if value is not None:
value = float(value)
if quoted is not None:
self.text += quoted
continue
@@ -85,13 +85,13 @@ class DialogueTextTags(object):
self.no_wait = False
self.text += full_tag
except StopIteration:
break
self.pause_end.append(len(self.text))
if self.no_wait:
if self.no_wait:
self.pause_delay.append(0)
else:
self.pause_delay.append(None)
@@ -117,9 +117,9 @@ def predict_show_display_say(who, what, who_args, what_args, window_args, image=
two_window=two_window,
side_image=side_image,
**kwargs)
return
if image:
if image != "<Dynamic>":
renpy.easy.predict(who)
@@ -139,16 +139,16 @@ def compute_widget_properties(who_args, what_args, window_args, variant=None):
return d
in_rollback = renpy.exports.in_rollback()
if (not in_rollback) and (not variant):
return d
d = d.copy()
style = d["style"]
if isinstance(style, basestring):
style = getattr(renpy.store.style, style)
style = getattr(renpy.store.style, style)
if variant is not None:
style = style[variant]
@@ -159,7 +159,7 @@ def compute_widget_properties(who_args, what_args, window_args, variant=None):
d["style"] = style
return d
who_args = style_args(who_args)
what_args = style_args(what_args)
window_args = style_args(window_args)
@@ -212,9 +212,9 @@ def show_display_say(who, what, who_args={}, what_args={}, window_args={},
This function is required to return the ui.text() widget
displaying the what text.
"""
props = compute_widget_properties(who_args, what_args, window_args, variant=variant)
def handle_who():
if who:
if image:
@@ -229,10 +229,10 @@ def show_display_say(who, what, who_args={}, what_args={}, window_args={},
if variant is not None:
style = style[variant]
if renpy.exports.in_rollback():
style = style["rollback"]
rv = dict(style=style)
rv.update(properties)
return rv
@@ -241,11 +241,11 @@ def show_display_say(who, what, who_args={}, what_args={}, window_args={},
tag = screen
index = 0
while renpy.exports.showing(tag):
index += 1
tag = "%s%d" % (screen, index)
renpy.display.screen.show_screen(
screen,
_widget_properties=props,
@@ -261,12 +261,12 @@ def show_display_say(who, what, who_args={}, what_args={}, window_args={},
renpy.exports.shown_window()
return renpy.display.screen.get_widget(screen, "what")
# Apply the transform.
if transform:
renpy.ui.at(transform)
if two_window:
# Opens say_two_window_vbox.
@@ -295,7 +295,7 @@ def show_display_say(who, what, who_args={}, what_args={}, window_args={},
renpy.ui.image(side_image)
renpy.exports.shown_window()
return rv
@@ -311,7 +311,7 @@ class SlowDone(object):
self.type = type
self.cb_args = cb_args
self.delay = delay
def __call__(self):
if self.ctc and self.ctc_position == "fixed":
@@ -321,11 +321,11 @@ class SlowDone(object):
if self.delay is not None:
renpy.ui.pausebehavior(self.delay, True)
renpy.exports.restart_interaction()
for c in self.callback:
c("slow_done", interact=self.interact, type=self.type, **self.cb_args)
# This function takes care of repeatably showing the screen as part of
# an interaction.
def display_say(
@@ -347,10 +347,12 @@ def display_say(
ctc_timedpause=None,
ctc_force=False):
if (not renpy.game.preferences.skip_unseen) and (not renpy.game.context().seen_current(True)) and renpy.config.skipping == "fast":
renpy.config.skipping = None
# If we're in fast skipping mode, don't bother with say
# statements at all.
if interact and renpy.config.skipping == "fast":
# Clears out transients.
renpy.exports.with_statement(None)
return
@@ -361,30 +363,30 @@ def display_say(
callback = [ renpy.config.character_callback ]
else:
callback = [ ]
if not isinstance(callback, list):
callback = [ callback ]
callback = renpy.config.all_character_callbacks + callback
callback = renpy.config.all_character_callbacks + callback
# Call the begin callback.
for c in callback:
c("begin", interact=interact, type=type, **cb_args)
if renpy.exports.roll_forward_info():
roll_forward = False
else:
roll_forward = None
# If we're just after a rollback or roll_forward, disable slow.
after_rollback = renpy.game.after_rollback
if after_rollback:
slow = False
# If we're committed to skipping this statement, disable slow.
elif (renpy.config.skipping and
(renpy.game.preferences.skip_unseen or
renpy.game.context().seen_current(True))):
renpy.game.context().seen_current(True))):
slow = False
# Figure out which pause we're on. (Or set the pause to None in
@@ -403,20 +405,20 @@ def display_say(
pause_end = dtt.pause_end
pause_delay = dtt.pause_delay
for i, (start, end, delay) in enumerate(zip(pause_start, pause_end, pause_delay)):
last_pause = (i == len(pause_start) - 1)
# If we're going to do an interaction, then saybehavior needs
# to be here.
if interact:
behavior = renpy.ui.saybehavior(allow_dismiss=renpy.config.say_allow_dismiss)
if interact:
behavior = renpy.ui.saybehavior(allow_dismiss=renpy.config.say_allow_dismiss)
else:
behavior = None
# The string to show.
# The string to show.
what_string = dtt.text
# Figure out the CTC to use, if any.
@@ -427,10 +429,10 @@ def display_say(
what_ctc = ctc_timedpause or ctc_pause
else:
what_ctc = ctc_pause
if not (interact or ctc_force):
what_ctc = None
what_ctc = renpy.easy.displayable_or_none(what_ctc)
if what_ctc is not None:
@@ -438,10 +440,10 @@ def display_say(
if delay == 0:
what_ctc = None
# Create the callback that is called when the slow text is done.
slow_done = SlowDone(what_ctc, ctc_position, callback, interact, type, cb_args, delay)
# Run the show callback.
for c in callback:
c("show", interact=interact, type=type, **cb_args)
@@ -454,7 +456,7 @@ def display_say(
if what_ctc and ctc_position == "nestled":
what_text.set_ctc(what_ctc)
# Update the properties of the what_text widget.
what_text.start = start
what_text.end = end
@@ -470,18 +472,18 @@ def display_say(
if not slow:
slow_done()
if interact:
if interact:
rv = renpy.ui.interact(mouse='say', type=type, roll_forward=roll_forward)
# This is only the case if the user has rolled forward, {nw} happens, or
# maybe in some other obscure cases.
if rv is False:
break
break
if not last_pause:
for i in renpy.config.say_sustain_callbacks:
i()
# Do the checkpoint and with None.
if interact:
@@ -490,7 +492,7 @@ def display_say(
renpy.exports.checkpoint(True)
else:
renpy.game.after_rollback = after_rollback
if with_none is None:
with_none = renpy.config.implicit_with_none
@@ -524,7 +526,7 @@ class ADVCharacter(object):
]
voice_tag = None
# When adding a new argument here, remember to add it to copy below.
def __init__(
self,
@@ -547,13 +549,13 @@ class ADVCharacter(object):
return getattr(kind, n)
# Similar, but it grabs the value out of kind.display_args instead.
# Similar, but it grabs the value out of kind.display_args instead.
def d(n):
if n in properties:
return properties.pop(n)
else:
return kind.display_args[n]
self.name = v('name')
self.who_prefix = v('who_prefix')
self.who_suffix = v('who_suffix')
@@ -576,8 +578,8 @@ class ADVCharacter(object):
else:
self.image_tag = kind.image_tag
else:
self.image_tag = None
self.image_tag = None
self.display_args = dict(
interact = d('interact'),
slow = d('slow'),
@@ -606,13 +608,13 @@ class ADVCharacter(object):
self.show_args = { }
self.cb_args = { }
if not renpy.config.new_character_image_argument:
if not renpy.config.new_character_image_argument:
if "image" in properties:
self.show_args["image"] = properties.pop("image")
if "slow_abortable" in properties:
self.what_args["slow_abortable"] = properties.pop("slow_abortable")
for k in list(properties):
if "_" in k:
@@ -639,7 +641,7 @@ class ADVCharacter(object):
def copy(self, name=NotSet, **properties):
return type(self)(name, kind=self, **properties)
# This is called before the interaction.
# This is called before the interaction.
def do_add(self, who, what):
return
@@ -647,7 +649,7 @@ class ADVCharacter(object):
def do_show(self, who, what):
return self.show_function(
who,
what,
what,
who_args=self.who_args,
what_args=self.what_args,
window_args=self.window_args,
@@ -657,7 +659,7 @@ class ADVCharacter(object):
# This is called after the last interaction is done.
def do_done(self, who, what):
return
# This is called when an extend occurs, before the usual add/show
# cycel.
def do_extend(self):
@@ -669,8 +671,8 @@ class ADVCharacter(object):
what,
self.do_show,
**display_args)
# This is called to predict images that will be used by this
# statement.
def do_predict(self, who, what):
@@ -688,9 +690,9 @@ class ADVCharacter(object):
Deals with image attributes associated with the current say
statement.
"""
attrs = renpy.exports.get_say_attributes()
if not attrs:
return
@@ -700,14 +702,14 @@ class ADVCharacter(object):
else:
return
tagged_attrs = (self.image_tag,) + attrs
tagged_attrs = (self.image_tag,) + attrs
images = renpy.game.context().images
# If image is showing already, resolve it, then show or predict it.
if images.showing("master", (self.image_tag,)):
new_image = images.apply_attributes("master", self.image_tag, tagged_attrs)
if new_image is None:
new_image = tagged_attrs
@@ -718,17 +720,27 @@ class ADVCharacter(object):
if trans is not None:
renpy.exports.with_statement(None)
renpy.exports.show(new_image)
renpy.exports.show(new_image)
if trans is not None:
renpy.exports.with_statement(trans)
else:
# Otherwise, just record the attributes of the image.
# Otherwise, just record the attributes of the image.
images.predict_show("master", tagged_attrs, show=False)
def __str__(self):
who = self.name
# If dynamic is set, evaluate the name expression.
if self.dynamic:
who = renpy.python.py_eval(who)
return who
def __call__(self, what, interact=True, **kwargs):
# Check self.condition to see if we should show this line at all.
@@ -736,35 +748,35 @@ class ADVCharacter(object):
return True
self.resolve_say_attributes(False)
old_side_image_attributes = renpy.store._side_image_attributes
if self.image_tag:
attrs = (self.image_tag,) + renpy.game.context().images.get_attributes("master", self.image_tag)
else:
attrs = None
renpy.store._side_image_attributes = attrs
if renpy.config.voice_tag_callback is not None:
renpy.config.voice_tag_callback(self.voice_tag)
try:
if interact:
renpy.exports.mode(self.mode)
# Figure out the arguments to display.
display_args = self.display_args.copy()
display_args.update(kwargs)
display_args["interact"] = display_args["interact"] and interact
who = self.name
# If dynamic is set, evaluate the name expression.
if self.dynamic:
who = renpy.python.py_eval(who)
sub = renpy.substitutions.substitute
if who is not None:
@@ -773,9 +785,9 @@ class ADVCharacter(object):
who = who_pattern.replace("[who]", sub(who))
else:
who = self.who_prefix + who + self.who_suffix
ctx = renpy.game.context()
if (ctx.translate_language is not None) and (ctx.translate_identifier is not None):
translate = False
else:
@@ -786,40 +798,40 @@ class ADVCharacter(object):
what = what_pattern.replace("[what]", sub(what, translate=translate))
else:
what = self.what_prefix + what + self.what_suffix
# Run the add_function, to add this character to the
# things like NVL-mode.
self.do_add(who, what)
# Now, display the damned thing.
self.do_display(who, what, cb_args=self.cb_args, **display_args)
# Indicate that we're done.
self.do_done(who, what)
# Finally, log this line of dialogue.
# Finally, log this line of dialogue.
if who and isinstance(who, (str, unicode)):
renpy.exports.log(who)
renpy.exports.log(what)
renpy.exports.log("")
finally:
if interact:
renpy.store._side_image_attributes = old_side_image_attributes
def predict(self, what):
self.resolve_say_attributes(True)
old_side_image_attributes = renpy.store._side_image_attributes
if self.image_tag:
attrs = self.image_tag + renpy.game.context().images.get_attributes("master", self.image_tag)
else:
attrs = None
renpy.store._side_image_attributes = attrs
try:
@@ -828,11 +840,11 @@ class ADVCharacter(object):
who = "<Dynamic>"
else:
who = self.name
return self.do_predict(who, what)
finally:
renpy.store._side_image_attributes = old_side_image_attributes
finally:
renpy.store._side_image_attributes = old_side_image_attributes
def will_interact(self):
@@ -841,13 +853,13 @@ class ADVCharacter(object):
return self.display_args['interact']
def Character(name=NotSet, kind=None, **properties):
"""
:doc: character
:args: (name, kind=adv, **args)
:name: Character
Creates and returns a Character object, which controls the look
and feel of dialogue and narration.
@@ -870,16 +882,16 @@ def Character(name=NotSet, kind=None, **properties):
image to show when this character speaks.
`image`
A string giving the image tag that is linked with this
A string giving the image tag that is linked with this
character.
**Voice Tag**
If a voice tag is assign to a Character, the voice files that are
associated with it, can be muted or played in the preference
associated with it, can be muted or played in the preference
screen.
`voice_tag`
A String that enables the voice file assosiated with the
A String that enables the voice file assosiated with the
Character to be muted or played in the 'voice' channel.
**Prefixes and Suffixes.**
@@ -894,7 +906,7 @@ def Character(name=NotSet, kind=None, **properties):
`what_suffix`
A string that is appended to the dialogue being spoken before
it is shown.
`who_prefix`
A string that is prepended to the name of the character before
it is shown.
@@ -902,7 +914,7 @@ def Character(name=NotSet, kind=None, **properties):
`who_suffix`
A string that is appended to the name of the character before
it is shown.
**Changing Name Display.**
These options help to control the display of the name.
@@ -932,7 +944,7 @@ def Character(name=NotSet, kind=None, **properties):
`callback`
A function that is called when events occur while the
character is speaking. See the section on
:ref:`character callbacks` fore more information.
:ref:`character-callbacks` fore more information.
**Click-to-continue.**
A click-to-continue indicator is displayed once all the text has
@@ -951,7 +963,7 @@ def Character(name=NotSet, kind=None, **properties):
display of text is paused by the {p=} or {w=} text tags. When
None, this takes its default from ctc_pause, use ``Null()``
when you want a ctc_pause but no ctc_timedpause.
`ctc_position`
Controls the location of the click-to-continue indicator. If
``"nestled"``, the indicator is displayed as part of the text
@@ -980,12 +992,12 @@ def Character(name=NotSet, kind=None, **properties):
controlled by its position properties. This is often used
to show an image of the speaking character to the side
of the dialogue.
`show_two_window`
If true, restructures the layout so that the name of the
character is placed in one window, and the dialogue text in a
second window.
**Styling Text and Windows.**
Keyword arguments beginning with ``who_``, ``what_``, and
`window_`` have their prefix stripped, and are used to :ref:`style
+1 -1
View File
@@ -14,7 +14,7 @@ python early hide:
# return 1.0
# else:
# return 0.0
@renpy.atl_warper
def linear(t):
return t
+57 -122
View File
@@ -1,74 +1,12 @@
# Copyright 2004-2013 Tom Rothamel <pytom@bishoujo.us>
# See LICENSE.txt for license details.
# This file contains code that creates a few new statements. We'll
# also describe here the API for defining your own statements.
#
# Statements can be defined by calling renpy.statements.register. This
# function takes a string giving the keywords at the start of the
# statement, and then up to 4 functions defining the behavior of the
# statement: parse, execute, predict, and lint. If given the keyword
# argument block=True, the statement will take a block.
#
# The parse function takes a lexer object as an argument, and is
# expected to return some parser data. The lexer has the following
# methods.
#
# l.eol() - True if we are at the end of the line.
# l.match(re) - Matches an arbitrary regexp string.
# l.keyword(s) - Matches s.
# l.name() - Matches any non-keyword name. Note that this only
# counts built-in keywords.
# l.word() - Matches any word, period.
# l.string() - Matches a renpy string.
# l.integer() - Matches an integer, returns a string containing the
# integer.
# l.float() - Matches a floating point number.
# l.simple_expression() - Matches a simple python expression, returns
# it as a string.
# l.rest() - Skips whitespace, then returns the rest of the line.
# l.checkpoint() - Returns an opaque object representing the current
# point in the parse.
# l.revert(o) - Given the object returned by l.checkpoint(), returns
# there.
#
# l.subblock_lexer() - Return a lexer that gives a sub-block for the
# block associated with the current statement.
# l.advance() - In a subblock lexer, advance to the next line. This needs
# to be called before the above functions can be called on the first line.
#
#
#
# The parse function is expected to return an object, which is passed
# to the other functions. Parse should call renpy.error with the error
# message to report an error.
#
# The execute function is passed the object returned from parse, and
# is expected to execute the statement.
#
# The predict function is passed the object returned from parse, and
# is expected to return a list of images that are used by the
# statement.
#
# The lint function is called at lint time, after the init code is
# run. It should check the statement for errors, and report them by
# calling renpy.error with the appropriate error message.
#
# The scry function is called with a scry object. It may mutate that
# object, but it is then expected to return it. See 00nvlmode.rpy for
# details.
#
# The next function is expected to return a string giving the label of
# the next statement to execute, or None to indicate that next
# statement in the block should be executed. The next function is
# called during predict, scry, and execute, and should always return
# the same value. (Or scrying may be incorrect.)
# This file contains code that creates a few new statements.
python early hide:
# Music play - The example of a full statement.
def parse_play_music(l):
file = l.simple_expression()
@@ -80,7 +18,7 @@ python early hide:
channel = None
loop = None
if_changed = False
while True:
if l.eol():
@@ -88,18 +26,18 @@ python early hide:
if l.keyword('fadeout'):
fadeout = l.simple_expression()
if fadeout is None:
if fadeout is None:
renpy.error('expected simple expression')
continue
if l.keyword('fadein'):
fadein = l.simple_expression()
if fadein is None:
if fadein is None:
renpy.error('expected simple expression')
continue
if l.keyword('channel'):
channel = l.simple_expression()
if channel is None:
@@ -110,7 +48,7 @@ python early hide:
if l.keyword('loop'):
loop = True
continue
if l.keyword('noloop'):
loop = False
continue
@@ -118,7 +56,7 @@ python early hide:
if l.keyword('if_changed'):
if_changed = True
continue
renpy.error('could not parse statement.')
return dict(file=file,
@@ -134,7 +72,7 @@ python early hide:
channel = eval(p["channel"])
else:
channel = "music"
renpy.music.play(eval(p["file"]),
fadeout=eval(p["fadeout"]),
fadein=eval(p["fadein"]),
@@ -151,7 +89,7 @@ python early hide:
if p["channel"] is not None:
channel = _try_eval(p["channel"], 'channel')
if not isinstance(file, list):
file = [ file ]
@@ -162,8 +100,8 @@ python early hide:
renpy.error("%r is not loadable" % fn)
except:
pass
renpy.statements.register('play music',
renpy.register_statement('play music',
parse=parse_play_music,
execute=execute_play_music,
predict=predict_play_music,
@@ -180,9 +118,9 @@ python early hide:
channel = None
loop = None
while not l.eol():
if l.keyword('channel'):
channel = l.simple_expression()
if channel is None:
@@ -191,7 +129,7 @@ python early hide:
if l.keyword('loop'):
loop = True
continue
if l.keyword('noloop'):
loop = False
continue
@@ -205,14 +143,14 @@ python early hide:
channel = eval(p["channel"])
else:
channel = "music"
renpy.music.queue(
eval(p["file"]),
channel=channel,
loop=p.get("loop", None))
renpy.statements.register('queue music',
renpy.register_statement('queue music',
parse=parse_queue_music,
execute=execute_queue_music,
lint=lint_play_music)
@@ -224,7 +162,7 @@ python early hide:
fadeout = l.simple_expression()
channel = None
if l.keyword('channel'):
channel = l.simple_expression()
if channel is None:
@@ -233,7 +171,7 @@ python early hide:
if not l.eol():
renpy.error('expected end of line')
if fadeout is None:
if fadeout is None:
renpy.error('expected simple expression')
return dict(fadeout=fadeout, channel=channel)
@@ -243,10 +181,10 @@ python early hide:
channel = eval(p["channel"])
else:
channel = "music"
renpy.music.stop(fadeout=eval(p["fadeout"]), channel=channel)
renpy.statements.register('stop music',
renpy.register_statement('stop music',
parse=parse_stop_music,
execute=execute_stop_music)
@@ -255,14 +193,14 @@ python early hide:
# statements.
def execute_play_sound(p):
if p["channel"] is not None:
channel = eval(p["channel"])
else:
channel = "sound"
fadeout = eval(p["fadeout"]) or 0
renpy.sound.play(eval(p["file"]),
fadeout=fadeout,
fadein=eval(p["fadein"]),
@@ -270,8 +208,8 @@ python early hide:
def lint_play_sound(p, lint_play_music=lint_play_music):
return lint_play_music(p, channel="sound")
renpy.statements.register('play sound',
renpy.register_statement('play sound',
parse=parse_play_music,
execute=execute_play_sound,
lint=lint_play_sound)
@@ -285,7 +223,7 @@ python early hide:
renpy.sound.queue(eval(p["file"]), channel=channel)
renpy.statements.register('queue sound',
renpy.register_statement('queue sound',
parse=parse_queue_music,
execute=execute_queue_sound,
lint=lint_play_music)
@@ -300,7 +238,7 @@ python early hide:
renpy.sound.stop(fadeout=fadeout, channel=channel)
renpy.statements.register('stop sound',
renpy.register_statement('stop sound',
parse=parse_stop_music,
execute=execute_stop_sound)
@@ -346,7 +284,7 @@ python early hide:
def lint_play_generic(p, lint_play_music=lint_play_music):
channel = eval(p["channel"])
if not renpy.music.channel_defined(channel):
renpy.error("channel %r is not defined" % channel)
@@ -354,22 +292,22 @@ python early hide:
def lint_stop_generic(p):
channel = eval(p["channel"])
if not renpy.music.channel_defined(channel):
renpy.error("channel %r is not defined" % channel)
renpy.statements.register('play',
renpy.register_statement('play',
parse=parse_play_generic,
execute=execute_play_music,
predict=predict_play_music,
lint=lint_play_generic)
renpy.statements.register('queue',
renpy.register_statement('queue',
parse=parse_queue_generic,
execute=execute_queue_music,
lint=lint_play_generic)
renpy.statements.register('stop',
renpy.register_statement('stop',
parse=parse_stop_generic,
execute=execute_stop_music,
lint=lint_stop_generic)
@@ -384,7 +322,7 @@ python early hide:
renpy.error('expected end of line')
return p
def lint_window(p):
if p is not None:
_try_eval(p, 'window transition')
@@ -397,15 +335,15 @@ python early hide:
trans = eval(p)
else:
trans = config.window_show_transition
renpy.with_statement(None)
store._window = True
renpy.with_statement(trans)
def execute_window_hide(p):
if not _window:
return
if p is not None:
trans = eval(p)
else:
@@ -415,19 +353,19 @@ python early hide:
store._window = False
renpy.with_statement(trans)
renpy.statements.register('window show',
renpy.register_statement('window show',
parse=parse_window,
execute=execute_window_show,
lint=lint_window)
renpy.statements.register('window hide',
renpy.register_statement('window hide',
parse=parse_window,
execute=execute_window_hide,
lint=lint_window)
##########################################################################
# Pause statement.
def parse_pause(l):
delay = l.simple_expression()
@@ -441,7 +379,7 @@ python early hide:
if p["delay"]:
_try_eval(p["delay"], 'pause statement')
def execute_pause(p):
if p["delay"]:
@@ -451,23 +389,23 @@ python early hide:
renpy.pause()
renpy.statements.register('pause',
renpy.register_statement('pause',
parse=parse_pause,
lint=lint_pause,
execute=execute_pause)
init -1200 python:
config.window_show_transition = None
config.window_hide_transition = None
def _try_eval(e, what):
try:
return eval(e)
except:
renpy.error('unable to evaluate %s %r' % (what, e))
##############################################################################
# Screen-related statements.
@@ -481,7 +419,7 @@ python early hide:
# Parse the list of arguments.
arguments = renpy.parser.parse_arguments(l)
l.expect_eol()
return dict(name=name, arguments=arguments)
def parse_hide_screen(l):
@@ -494,7 +432,7 @@ python early hide:
def predict_screen(p):
if not p["arguments"]:
renpy.predict_screen(p["arguments"])
def execute_show_screen(p):
name = p["name"]
@@ -513,7 +451,7 @@ python early hide:
if a.extrapos is not None:
args.extend(eval(a.extrapos))
if a.extrakw is not None:
kwargs.update(eval(a.extrakw))
@@ -536,7 +474,7 @@ python early hide:
if a.extrapos is not None:
args.extend(eval(a.extrapos))
if a.extrakw is not None:
kwargs.update(eval(a.extrakw))
@@ -545,28 +483,25 @@ python early hide:
def execute_hide_screen(p):
name = p["name"]
renpy.hide_screen(name)
def lint_screen(p):
name = p["name"]
if not renpy.has_screen(name):
renpy.error("Screen %s does not exist." % name)
renpy.statements.register("show screen",
renpy.register_statement("show screen",
parse=parse_show_call_screen,
execute=execute_show_screen,
predict=predict_screen,
lint=lint_screen)
renpy.statements.register("call screen",
renpy.register_statement("call screen",
parse=parse_show_call_screen,
execute=execute_call_screen,
predict=predict_screen,
lint=lint_screen)
renpy.statements.register("hide screen",
renpy.register_statement("hide screen",
parse=parse_hide_screen,
execute=execute_hide_screen)
+18 -20
View File
@@ -9,9 +9,9 @@ init -1500 python:
class Play(Action):
"""
:doc: audio_action
Causes an audio file to be played on a given channel.
`channel`
The channel to play the sound on.
`file`
@@ -19,7 +19,7 @@ init -1500 python:
Any keyword arguments are passed to :func:`renpy.music.play`
"""
def __init__(self, channel, file, **kwargs):
self.channel = channel
self.file = file
@@ -29,9 +29,9 @@ init -1500 python:
def __call__(self):
renpy.music.play(self.file, channel=self.channel, **self.kwargs)
renpy.restart_interaction()
def get_selected(self):
return renpy.music.get_playing(self.channel) == self.file
return renpy.music.get_playing(self.channel) == self.file
def periodic(self, st):
if self.selected != self.get_selected():
@@ -39,7 +39,7 @@ init -1500 python:
return .1
class Queue(Action):
"""
:doc: audio_action
@@ -63,7 +63,7 @@ init -1500 python:
renpy.music.queue(self.file, channel=self.channel, **self.kwargs)
renpy.restart_interaction()
class Stop(Action):
"""
:doc: audio_action
@@ -71,11 +71,9 @@ init -1500 python:
Causes an audio channel to be stopped.
`channel`
The channel to play the sound on.
`file`
The file to play.
The channel to stop the sound on.
Any keyword arguments are passed to :func:`renpy.music.play`
Any keyword arguments are passed to :func:`renpy.music.stop`
"""
def __init__(self, channel, **kwargs):
@@ -89,25 +87,25 @@ init -1500 python:
class SetMixer(Action):
"""
:doc: audio_action
Sets the volume of `mixer` to `value`.
Sets the volume of `mixer` to `value`.
`mixer`
The mixer to set the volume of. A string, usually one of
The mixer to set the volume of. A string, usually one of
"music", "sfx", or "voice".
`value`
The value to set the volume to. A number between 0.0 and 1.0,
The value to set the volume to. A number between 0.0 and 1.0,
inclusive.
"""
def __init__(self, mixer, volume):
self.mixer = mixer
self.volume = volume
def __call__(self):
_preferences.set_volume(self.mixer, self.volume)
renpy.restart_interaction()
def get_selected(self):
return _preferences.get_volume(self.mixer) == self.volume
+30 -17
View File
@@ -3,10 +3,23 @@
init -1500 python:
class NullAction(Action):
"""
:doc: control_action
Does nothing.
This can be used to make a button responsive to hover/unhover events,
without actually doing anything.
"""
def __call__(self):
return
class Return(Action):
"""
:doc: control_action
Causes the current interaction to return the supplied value. This is
often used with menus and imagemaps, to select what the return value
of the interaction is.
@@ -28,35 +41,35 @@ init -1500 python:
else:
return self.value
class Jump(Action):
"""
:doc: control_action
Causes control to transfer to the given label. This can be used in
conjunction with renpy.run_screen to define an imagemap that jumps
to a label when run.
"""
def __init__(self, label):
self.label = label
def __call__(self):
renpy.jump(self.label)
class Show(Action):
"""
:doc: control_action
This causes another screen to be shown. `screen` is a string
giving the name of the screen. The keyword arguments are
passed to the screen being shown.
If not None, `transition` is use to show the new screen.
"""
args = None
def __init__(self, screen, transition=None, *args, **kwargs):
@@ -67,7 +80,7 @@ init -1500 python:
def predict(self):
renpy.predict_screen(self.screen, *self.args, **self.kwargs)
def __call__(self):
renpy.show_screen(self.screen, *self.args, **self.kwargs)
@@ -75,7 +88,7 @@ init -1500 python:
renpy.transition(self.transition)
renpy.restart_interaction()
def get_selected(self):
return renpy.showing(self.screen)
@@ -89,27 +102,27 @@ init -1500 python:
"""
return Show(screen, _transient=True, *args, **kwargs)
class Hide(Action):
"""
:doc: control_action
This causes the screen named `screen` to be hidden, if it is shown.
This causes the screen named `screen` to be hidden, if it is shown.
`transition`
If not None, a transition that occurs when hiding the screen.
"""
def __init__(self, screen, transition=None):
self.screen = screen
self.transition = transition
def __call__(self):
renpy.hide_screen(self.screen)
if self.transition is not None:
renpy.transition(self.transition)
renpy.restart_interaction()
+21 -18
View File
@@ -19,15 +19,15 @@ init -1600 python:
self.object = object
self.field = field
self.value = value
def __call__(self):
setattr(self.object, self.field, self.value)
renpy.restart_interaction()
def get_selected(self):
return getattr(self.object, self.field) == self.value
def SetVariable(variable, value):
"""
:doc: data_action
@@ -37,7 +37,7 @@ init -1600 python:
return SetField(store, variable, value)
class SetDict(Action):
"""
:doc: data_action
@@ -55,22 +55,25 @@ init -1600 python:
renpy.restart_interaction()
def get_selected(self):
if self.key not in self.dict:
return False
return self.dict[self.key] == self.value
def SetScreenVariable(name, value):
"""
:doc: data_action
Causes the variable `name` associated with the current screen to
Causes the variable `name` associated with the current screen to
be set to `value`.
"""
cs = renpy.current_screen()
if cs is not None:
return SetDict(cs.scope, name, value)
else:
return None
class ToggleField(Action):
"""
:doc: data_action
@@ -81,15 +84,15 @@ init -1600 python:
`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.
If not None, then this is the false value we use.
"""
def __init__(self, object, field, true_value=None, false_value=None):
self.object = object
self.field = field
self.true_value = true_value
self.false_value = false_value
def __call__(self):
value = getattr(self.object, self.field)
@@ -103,7 +106,7 @@ init -1600 python:
value = self.true_value
else:
value = self.false_value
setattr(self.object, self.field, value)
renpy.restart_interaction()
@@ -125,12 +128,12 @@ init -1600 python:
`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.
If not None, then this is the false value we use.
"""
return ToggleField(store, variable, true_value=true_value, false_value=false_value)
class ToggleDict(Action):
"""
:doc: data_action
@@ -141,15 +144,15 @@ init -1600 python:
`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.
If not None, then this is the false value we use.
"""
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 __call__(self):
value = self.dict[self.key]
@@ -184,9 +187,9 @@ init -1600 python:
`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.
If not None, then this is the false value we use.
"""
cs = renpy.current_screen()
if cs is not None:
+164 -123
View File
@@ -3,16 +3,16 @@
init -1500 python:
##########################################################################
# File functions
config.linear_saves_page_size = None
if persistent._file_page is None:
persistent._file_page = "1"
def __filename(name, page=None):
def __slotname(name, page=None):
if page is None:
page = persistent._file_page
@@ -27,54 +27,62 @@ init -1500 python:
return str(page) + "-" + str(name)
def __newest_slot():
"""
Returns the name of the newest slot on a page.
"""
return renpy.newest_slot(r'\d+')
def __unused_slot_name(page):
"""
Returns an unused slot name.
Returns an unused name of a slot on the current page. (This will
likely be a very big number, as it's based on the current unix time.)
"""
import time
rv = int(time.time())
while True:
if not renpy.can_load(__filename(str(rv), page)):
if not renpy.can_load(__slotname(str(rv), page)):
return str(rv)
rv += 1
def FileCurrentPage():
"""
:doc: file_action_function
Returns the current file page as a string.
"""
return str(persistent._file_page)
def FileUsedSlots(page=None, highest_first=True):
"""
:doc: file_action_function
Returns a list of used numeric file slots on the page.
`page`
The name of the page that will be scanned. If None, the current page
The name of the page that will be scanned. If None, the current page
is used.
`highest_first`
If true, the highest-numbered file slot is listed first.
Otherwise, the lowest-numbered slot is listed first.
"""
regexp = __filename(r'\d+', page)
regexp = __slotname(r'\d+', page)
rv = [ ]
for fn in renpy.list_saved_games(regexp=regexp, fast=True):
for fn in renpy.list_slots(regexp=regexp):
_page, _, slot = fn.partition('-')
rv.append(int(slot))
rv.sort()
if highest_first:
rv.reverse()
@@ -89,12 +97,12 @@ init -1500 python:
if the file is loadable, and false otherwise.
"""
return renpy.can_load(__filename(name, page))
return renpy.can_load(__slotname(name, page))
def FileScreenshot(name, empty=None, page=None):
"""
:doc: file_action_function
Returns the screenshot associated with the given file. If the
file is not loadable, then `empty` is returned, unless it's None,
in which case, a Null displayable is created.
@@ -102,60 +110,81 @@ init -1500 python:
The return value is a displayable.
"""
save_data = renpy.scan_saved_game(__filename(name, page))
screenshot = renpy.slot_screenshot(__slotname(name, page))
if save_data is None:
if empty is not None:
return empty
else:
return Null(config.thumbnail_width, config.thumbnail_height)
if screenshot is not None:
return screenshot
extra_info, displayable, save_time = save_data
if empty is not None:
return empty
else:
return Null(config.thumbnail_width, config.thumbnail_height)
return displayable
def FileTime(name, format=_("%b %d, %H:%M"), empty="", page=None):
"""
:doc: file_action_function
:doc: file_action_function
Returns the time the file was saved, formatted according
to the supplied `format`. If the file is not found, `empty` is
returned.
The return value is a string.
"""
save_data = renpy.scan_saved_game(__filename(name, page))
if save_data is None:
mtime = renpy.slot_mtime(__slotname(name, page))
if mtime is None:
return empty
import time
extra_info, displayable, save_time = save_data
format = renpy.translation.translate_string(format)
return time.strftime(format.encode("utf-8"), time.localtime(mtime)).decode("utf-8")
def FileJson(name, key=None, empty=None, missing=None, page=None):
"""
:doc: file_action_function
Accesses the Json information associated with `name`.
If `key` is None, returns the entire Json other object, or `empty` if the slot
is empty.
Otherwise, this returns json[key] if `key` is defined on the json object of the save,
`missing` if there is a save with the given name, but it does not contain `key`, or
`empty` if the save slot is empty.
"""
json = renpy.slot_json(__slotname(name, page))
if json is None:
return empty
if key is None:
return json
return json.get(key, missing)
return time.strftime(format.encode("utf-8"), time.localtime(save_time)).decode("utf-8")
def FileSaveName(name, empty="", page=None):
"""
:doc: file_action_function
Return the save_name that was in effect when the file was saved,
or `empty` if the file does not exist.
"""
save_data = renpy.scan_saved_game(__filename(name, page))
return FileJson(name, "_save_name", empty=empty, missing=empty, page=page)
if save_data is None:
return empty
def FileNewest(name, page=None):
"""
:doc: file_action_function
extra_info, displayable, save_time = save_data
return extra_info
Returns True if this is the newest file slot, or False otherwise.
"""
return __newest_slot() == __slotname(name, page)
class FileSave(Action):
"""
@@ -165,18 +194,16 @@ init -1500 python:
The button with this slot is selected if it's marked as the
newest save file.
`name`
The name of the slot to save to. If None, an unused slot
(a large number based on the current time) will be
will be used.
(a large number based on the current time) will be used.
`confirm`
If true, then we will prompt before overwriting a file.
`newest`
If true, then this file will be marked as the newest save
file when it's saved.
Ignored.
`page`
The name of the page that the slot is on. If None, the current
@@ -194,33 +221,25 @@ init -1500 python:
self.name = name
self.confirm = confirm
self.page = page
self.newest = newest
self.page = page
self.cycle = cycle
def __call__(self):
if not self.get_sensitive():
return
fn = __filename(self.name, self.page)
fn = __slotname(self.name, self.page)
if self.cycle:
renpy.renpy.loadsave.cycle_saves(self.page + "-", 10)
if renpy.scan_saved_game(fn):
if self.confirm:
layout.yesno_screen(layout.OVERWRITE_SAVE, FileSave(self.name, False, self.newest, self.page))
layout.yesno_screen(layout.OVERWRITE_SAVE, FileSave(self.name, False, False, self.page, cycle=self.cycle))
return
renpy.save(fn, extra_info=save_name)
if self.newest:
persistent._file_newest = fn
if self.page is not None:
persistent._file_page = self.page
renpy.restart_interaction()
def get_sensitive(self):
@@ -236,9 +255,9 @@ init -1500 python:
def get_selected(self):
if not self.confirm:
return False
return persistent._file_newest == __filename(self.name, self.page)
return __newest_slot() == __slotname(self.name, self.page)
class FileLoad(Action):
"""
:doc: file_action
@@ -248,35 +267,35 @@ init -1500 python:
`name`
The name of the slot to load from. If None, an unused slot
the file will not be loadable.
`confirm`
If true, prompt if loading the file will end the game.
`page`
The page that the file will be loaded from. If None, the
current page is used.
`newest`
If true, the button is selected if this is the newest file.
"""
def __init__(self, name, confirm=True, page=None, newest=True):
if name is None:
name = __unused_slot_name(page)
self.name = name
self.confirm = confirm
self.page = page
self.newest = newest
def __call__(self):
if not self.get_sensitive():
return
fn = __filename(self.name, self.page)
fn = __slotname(self.name, self.page)
if not renpy.context()._main_menu:
if self.confirm:
layout.yesno_screen(layout.LOADING, FileLoad(self.name, False, self.page))
@@ -287,14 +306,14 @@ init -1500 python:
def get_sensitive(self):
if _in_replay:
return False
return renpy.can_load(__filename(self.name, self.page))
return renpy.can_load(__slotname(self.name, self.page))
def get_selected(self):
if not self.confirm or not self.newest:
return False
return persistent._file_newest == __filename(self.name, self.page)
return __newest_slot() == __slotname(self.name, self.page)
class FileDelete(Action):
"""
@@ -305,19 +324,19 @@ init -1500 python:
`confirm`
If true, prompts before deleting a file.
"""
def __init__(self, name, confirm=True, page=None):
self.name = name
self.confirm = confirm
self.page = page
def __call__(self):
if not self.get_sensitive():
return
fn = __filename(self.name, self.page)
fn = __slotname(self.name, self.page)
if self.confirm:
layout.yesno_screen(layout.DELETE_SAVE, FileDelete(self.name, False, self.page))
return
@@ -325,12 +344,12 @@ init -1500 python:
renpy.unlink_save(fn)
def get_sensitive(self):
return renpy.can_load(__filename(self.name, self.page))
return renpy.can_load(__slotname(self.name, self.page))
def get_selected(self):
return persistent._file_newest == __filename(self.name, self.page)
return __newest_slot() == __slotname(self.name, self.page)
def FileAction(name, page=None):
"""
:doc: file_action
@@ -340,19 +359,18 @@ init -1500 python:
`name`
The name of the slot to save to or load from. If None, an unused slot
(a large number based on the current time) will be
will be used.
(a large number based on the current time) will be used.
`page`
The page that the file will be saved to or loaded from. If None, the
current page is used.
"""
if renpy.current_screen().screen_name[0] == "load":
return FileLoad(name, page=page)
else:
return FileSave(name, page=page)
class FilePage(Action):
"""
@@ -361,7 +379,7 @@ init -1500 python:
Sets the file page to `page`, which should be one of "auto", "quick",
or an integer.
"""
def __init__(self, page):
self.page = str(page)
@@ -371,10 +389,10 @@ init -1500 python:
persistent._file_page = self.page
renpy.restart_interaction()
def get_selected(self):
return self.page == persistent._file_page
def FilePageName(auto="a", quick="q"):
"""
:doc: file_action_function
@@ -402,7 +420,7 @@ init -1500 python:
that page numbers start with 1. When slot is 2, and slots_per_page
is 10, and the other variables are the defauts:
* When the first page is slowing, this returns "2".
* When the first page is showing, this returns "2".
* When the second page is showing, this returns "12".
* When the auto page is showing, this returns "a2".
* When the quicksave page is showing, this returns "q2".
@@ -437,8 +455,8 @@ init -1500 python:
page = int(page) - 1
return format % (prefix, page * slots_per_page + slot)
class FilePageNext(Action):
"""
:doc: file_action
@@ -448,9 +466,12 @@ init -1500 python:
`max`
If set, this should be an integer that gives the number of
the maximum file page we can go to.
`wrap`
If true, we can go to the first page when on the last file page if max is set.
"""
def __init__(self, max=None):
def __init__(self, max=None, wrap=False):
page = persistent._file_page
@@ -468,13 +489,21 @@ init -1500 python:
if max is not None:
if page > max:
page = None
if wrap:
if config.has_autosave:
page = "auto"
elif config.has_quicksave:
page = "quick"
else:
page = "1"
else:
page = None
if page is not None:
page = str(page)
self.page = page
def __call__(self):
if not self.get_sensitive():
return
@@ -484,27 +513,41 @@ init -1500 python:
def get_sensitive(self):
return self.page is not None
class FilePagePrevious(Action):
"""
:doc: file_action
Goes to the previous file page, if possible.
`max`
If set, this should be an integer that gives the number of
the maximum file page we can go to. This is required to enable
wrap.
`wrap`
If true, we can go to the last page when on the first file page if max is set.
"""
def __init__(self):
def __init__(self, max=None, wrap=False):
if wrap and max is not None:
max = str(max)
else:
max = None
page = persistent._file_page
if page == "auto":
page = None
page = max
elif page == "quick":
if config.has_autosave:
page = "auto"
else:
page = None
page = max
elif page == "1":
if config.has_quicksave:
@@ -512,13 +555,13 @@ init -1500 python:
elif config.has_autosave:
page = "auto"
else:
page = None
page = max
else:
page = str(int(page) - 1)
self.page = page
def __call__(self):
if not self.get_sensitive():
return
@@ -540,32 +583,30 @@ init -1500 python:
def __call__(self):
renpy.take_screenshot()
def QuickSave(message="Quick save complete.", newest=False):
def QuickSave(message=_("Quick save complete."), newest=False):
"""
:doc: file_action
Performs a quick save.
`message`
A message to display to the user when the quick save finishes.
`newest`
Set to true to mark the quicksave as the newest save.
"""
return [
return [
FileTakeScreenshot(),
FileSave(1, page="quick", confirm=False, cycle=True, newest=newest),
Notify("Quick save complete.") ]
FileSave(1, page="quick", confirm=False, cycle=True, newest=newest),
Notify(message) ]
def QuickLoad():
"""
:doc: file_action
Performs a quick load.
"""
return FileLoad(1, page="quick", confirm=True, newest=False)
return FileLoad(1, page="quick", confirm=True, newest=False)
Regular → Executable
+63 -28
View File
@@ -3,7 +3,7 @@
init -1500 python:
##########################################################################
# Menu-related actions.
@@ -32,7 +32,7 @@ init -1500 python:
ShowMenu without an argument will enter the game menu at the
default screen, taken from _game_menu_screen.
"""
def __init__(self, screen=None):
@@ -46,12 +46,12 @@ init -1500 python:
if not self.get_sensitive():
return
orig_screen = screen = self.screen or store._game_menu_screen
if not (renpy.has_screen(screen) or renpy.has_label(screen)):
screen = screen + "_screen"
# Ugly. We have different code depending on if we're in the
# game menu or not.
if renpy.context()._menu:
@@ -61,7 +61,7 @@ init -1500 python:
renpy.transition(config.intra_transition)
renpy.show_screen(screen, _transient=True)
renpy.restart_interaction()
elif renpy.has_label(screen):
renpy.transition(config.intra_transition)
@@ -85,12 +85,12 @@ init -1500 python:
return eval(config.show_menu_enable[self.screen])
else:
return True
class Start(Action):
"""
:doc: menu_action
Causes Ren'Py to jump out of the menu context to the named
label. The main use of this is to start a new game from the
main menu. Common uses are:
@@ -98,14 +98,14 @@ init -1500 python:
* Start() - Start at the start label.
* Start("foo") - Start at the "foo" label.
"""
def __init__(self, label="start"):
self.label = label
def __call__(self):
renpy.jump_out_of_context(self.label)
class MainMenu(Action):
"""
:doc: menu_action
@@ -120,13 +120,14 @@ init -1500 python:
def __init__(self, confirm=True):
self.confirm = confirm
def __call__(self):
if not self.get_sensitive():
return
if self.confirm:
renpy.loadsave.force_autosave()
layout.yesno_screen(layout.MAIN_MENU, MainMenu(False))
else:
renpy.full_restart()
@@ -145,7 +146,7 @@ init -1500 python:
If true, prompts the user if he wants to quit, rather
than quitting directly.
"""
def __init__(self, confirm=True):
self.confirm = confirm
@@ -154,10 +155,10 @@ init -1500 python:
if self.confirm:
renpy.loadsave.force_autosave()
layout.yesno_screen(layout.QUIT, Quit(False))
else:
renpy.quit()
else:
renpy.jump("_quit")
class Skip(Action):
"""
:doc: other_action
@@ -165,22 +166,41 @@ init -1500 python:
Causes the game to begin skipping. If the game is in a menu
context, then this returns to the game. Otherwise, it just
enables skipping.
`fast`
If True, skips directly to the next menu choice.
"""
fast = False
def __init__(self, fast=False):
`fast`
If true, skips directly to the next menu choice.
`confirm`
If true, asks for confirmation before beginning skipping.
"""
fast = False
confirm = False
def __init__(self, fast=False, confirm=False):
self.fast = fast
self.confirm = confirm
def __call__(self):
if not self.get_sensitive():
return
if self.confirm:
if self.fast:
if _preferences.skip_unseen:
layout.yesno_screen(layout.FAST_SKIP_UNSEEN, Skip(True))
else:
layout.yesno_screen(layout.FAST_SKIP_SEEN, Skip(True))
else:
layout.yesno_screen(layout.SLOW_SKIP, Skip(False))
return
if renpy.context()._menu:
renpy.jump("_return_skipping")
if self.fast:
renpy.jump("_return_fast_skipping")
else:
renpy.jump("_return_skipping")
else:
if not config.skipping:
@@ -194,10 +214,25 @@ init -1500 python:
renpy.restart_interaction()
def get_selected(self):
return config.skipping
if self.fast:
return config.skipping == "fast"
else:
return config.skipping and config.skipping != "fast"
def get_sensitive(self):
return config.allow_skipping and (not renpy.context()._main_menu)
if not config.allow_skipping:
return False
if store.main_menu:
return False
if renpy.game.context().seen_current(True):
return True
if _preferences.skip_unseen:
return True
return False
class Help(Action):
@@ -216,7 +251,7 @@ init -1500 python:
def __init__(self, help=None):
self.help = help
def __call__(self):
_help(self.help)
+78 -74
View File
@@ -6,7 +6,7 @@ init -1500 python:
class InvertSelected(Action):
"""
:doc: other_action
This inverts the selection state of the provided action, while
proxying over all of the other methods.
"""
@@ -19,7 +19,7 @@ init -1500 python:
def get_selected(self):
return not self.action.get_selected()
def get_sensitive(self):
return self.action.get_sensitive()
@@ -38,7 +38,7 @@ init -1500 python:
Note that the default, None, can be used as an action that causes
a button to be disabled.
"""
if expression:
return true
else:
@@ -48,64 +48,64 @@ init -1500 python:
class SelectedIf(Action):
"""
:doc: other_action
This allows an expression to control if a button should be marked
as selected. It should be used as part of a list with one or more
actions. For example::
# The button is selected if mars_flag is True
textbutton "Marsopolis":
textbutton "Marsopolis":
action [ Jump("mars"), SelectedIf(mars_flag) ]
"""
def __init__(self, expression):
self.expression = expression
def __call__(self):
return None
def get_selected(self):
return self.expression
class Screenshot(Action):
"""
:doc: other_action
Takes a screenshot.
"""
def __call__(self):
_screenshot()
def HideInterface():
"""
:doc other_action
Causes the interface to be hidden until the user clicks.
"""
return ui.callsinnewcontext("_hide_windows")
class OpenURL(Action):
"""
:doc: other_action
Causes `url` to be opened in a web browser.
"""
def __init__(self, url):
self.url = url
def __call__(self):
try:
import webbrowser
webbrowser.open_new(self.url)
except:
pass
class With(Action):
"""
:doc: other_action
@@ -114,8 +114,8 @@ init -1500 python:
"""
def __init__(self, transition):
self.transition = transition
self.transition = transition
def __call__(self):
renpy.transition(self.transition)
renpy.restart_interaction()
@@ -124,45 +124,49 @@ init -1500 python:
"""
:doc: other_action
Displays `message` using :func:`renpy.notify`.
Displays `message` using :func:`renpy.notify`.
"""
def __init__(self, message):
self.message = message
def predict(self):
renpy.predict_screen("notify")
def __call__(self):
renpy.notify(self.message)
def Rollback():
class Rollback(Action):
"""
:doc: other_action
This action causes a rollback to occur, when a rollback is possible.
Otherwise, nothing happens.
"""
return renpy.rollback
def __call__(self):
renpy.rollback()
def get_sensitive(self):
return renpy.can_rollback()
class RollForward(Action):
"""
:doc: other_action
This action causes a rollforward to occur, if a roll forward is
This action causes a rollforward to occur, if a roll forward is
possible. Otherwise, it is insensitive.
"""
def __call__(self):
return renpy.roll_forward_info()
def get_sensitive(self):
return renpy.roll_forward_info() is not None
#########################################################################
#########################################################################
class __TooltipAction(object):
def __init__(self, tooltip, value):
@@ -172,28 +176,28 @@ init -1500 python:
def __call__(self):
if self.tooltip.value != self.value:
self.tooltip.value = self.value
renpy.restart_interaction()
renpy.restart_interaction()
def unhovered(self):
if self.tooltip.value != self.tooltip.default:
self.tooltip.value = self.tooltip.default
renpy.restart_interaction()
renpy.restart_interaction()
_m1_00screen__TooltipAction = __TooltipAction
class Tooltip(object):
"""
:doc: tooltips class
A tooltip object can be used to define a portion of a screen that is
updated when the mouse hovers an area.
A tooltip object can be used to define a portion of a screen that is
updated when the mouse hovers an area.
A tooltip object has a ``value`` field, which is set to the `default`
value passed to the constructor when the tooltip is created. When
a button using an action creadted by the tooltip is hovered, the
value field changes to the value associated with the action.
value passed to the constructor when the tooltip is created. When
a button using an action creadted by the tooltip is hovered, the
value field changes to the value associated with the action.
"""
def __init__(self, default):
self.value = default
self.default = default
@@ -202,26 +206,26 @@ init -1500 python:
"""
:doc: tooltips method
:name: Action
Returns an action that is generally used as the hovered property
of a button. When the button is hovered, the value field of this
of a button. When the button is hovered, the value field of this
tooltip is set to `value`. When the buttton loses focus, the
value field of this tooltip reverts to the default.
"""
return __TooltipAction(self, value)
action = Action
#########################################################################
#########################################################################
class Language(Action):
"""
:doc: language_action
Changes the language of the game to `language`.
`language`
A string giving the language to translate to, or None to use
the default language of the game script.
@@ -229,21 +233,21 @@ init -1500 python:
def __init__(self, language):
self.language = language
def __call__(self):
renpy.change_language(self.language)
def get_selected(self):
return _preferences.language == self.language
def get_sensitive(self):
if self.language is None:
return True
return self.language in renpy.known_languages()
#########################################################################
#########################################################################
# Transitions used when entering and leaving replays.
config.enter_replay_transition = None
@@ -252,53 +256,53 @@ init -1500 python:
class Replay(Action):
"""
:doc: replay
An action that starts `label` as a replay.
`scope`
A dictionary mapping variable name to value. These variables are set
when entering the replay.
`locked`
If true, this replay is locked. If false, it is unlocked. If None, the
If true, this replay is locked. If false, it is unlocked. If None, the
replay is locked if the label has not been seen in any playthrough.
"""
def __init__(self, label, scope={}, locked=None):
self.label = label
self.scope = scope
if locked is None:
self.locked = not renpy.seen_label(label)
self.locked = locked
def __call__(self):
if self.locked:
return
if config.enter_replay_transition:
renpy.transition(config.enter_replay_transition)
renpy.call_replay(self.label, self.scope)
if config.exit_replay_transition:
renpy.transition(config.exit_replay_transition)
def get_sensitive(self):
return not self.locked
class EndReplay(Action):
"""
:doc: replay
An action that ends the current memory.
"""
def __call__(self):
renpy.end_replay()
def get_sensitive(self):
return _in_replay
+22 -22
View File
@@ -39,7 +39,7 @@ init -1500 python:
`delay`
The time it takes to animate the value, in seconds. Defaults
to 1.0.
`old_value`
The old value. If this is None, then the value is taken from the
AnimatedValue we replaced, if any. Otherwise, it is initialized
@@ -57,16 +57,16 @@ init -1500 python:
self.start_time = None
self.adjustment = None
def get_adjustment(self):
self.adjustment = ui.adjustment(value=self.value, range=self.range, adjustable=False)
return self.adjustment
def periodic(self, st):
if self.start_time is None:
self.start_time = st
if self.value == self.old_value:
return
@@ -95,7 +95,7 @@ init -1500 python:
class FieldValue(BarValue):
"""
:doc: value
A value that allows the user to adjust the value of a field
on an object.
@@ -120,9 +120,9 @@ init -1500 python:
The amount to change the bar by. If None, defaults to 1/10th of
the bar.
"""
offset = 0
def __init__(self, object, field, range, max_is_zero=False, style="bar", offset=0, step=None):
self.object = object
self.field = field
@@ -130,15 +130,15 @@ init -1500 python:
self.max_is_zero = max_is_zero
self.style = style
self.offset = offset
if step is None:
if isinstance(range, float):
step = range / 10.0
else:
step = max(range / 10, 1)
self.step = step
def changed(self, value):
if self.max_is_zero:
@@ -146,24 +146,24 @@ init -1500 python:
value = 0
else:
value = value + 1
value += self.offset
setattr(self.object, self.field, value)
renpy.restart_interaction()
def get_adjustment(self):
value = getattr(self.object, self.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,
@@ -176,7 +176,7 @@ init -1500 python:
def VariableValue(variable, range, max_is_zero=False, style="bar", offset=0, step=None):
"""
:doc: value
`variable`
A string giving the name of the variable to adjust.
`range`
@@ -196,9 +196,9 @@ init -1500 python:
The amount to change the bar by. If None, defaults to 1/10th of
the bar.
"""
return FieldValue(store, variable, range, max_is_zero=max_is_zero, style=style, offset=offset, step=step)
class MixerValue(BarValue):
"""
:doc: value
@@ -210,13 +210,13 @@ init -1500 python:
"music", "sfx", or "voice", but user code can create new
mixers.
"""
def __init__(self, mixer):
self.mixer = mixer
def set_mixer(self, value):
_preferences.set_volume(self.mixer, value)
def get_adjustment(self):
return ui.adjustment(
range=1.0,
@@ -268,4 +268,4 @@ init -1500 python:
return w.yadjustment
def get_style(self):
return "scrollbar", "vscrollbar"
return "scrollbar", "vscrollbar"
+126 -91
View File
@@ -1,38 +1,38 @@

# Contains functions and variables that control the building of
# Contains functions and variables that control the building of
# distributions.
init -1500 python in build:
def make_file_lists(s):
"""
Turns `s` into a (perhaps empty) list of file_lists.
If `s` is a list or None, then returns it. If it's a string, splits
it on whitespace. Otherwise, errors out.
"""
if s is None:
return s
elif isinstance(s, list):
return s
elif isinstance(s, basestring):
return s.split()
raise Exception("Expected a string, list, or None.")
def pattern_list(l):
"""
Apply file_lists to the second argument of each tuple in a list.
"""
rv = [ ]
for pattern, groups in l:
rv.append((pattern, make_file_lists(groups)))
return rv
# Patterns that are used to classify Ren'Py.
@@ -46,130 +46,137 @@ init -1500 python in build:
( "**/*.pyc", None),
( "renpy.py", "all"),
( "renpy/**", "all"),
( "renpy.py", "renpy"),
( "renpy/**", "renpy"),
# Ignore Ren'Py and renpy.exe.
( "lib/*/renpy", None),
( "lib/*/renpy.exe", None),
# Windows patterns.
( "lib/windows-i686/**", "windows"),
# Linux patterns.
# Linux patterns.
( "lib/linux-x86_64/**", "linux"),
( "lib/linux-i686/**", "linux"),
# Mac patterns
( "lib/darwin-x86_64/**", "mac"),
# Shared patterns.
( "/lib/**", "windows linux mac"),
( "renpy.sh", "linux mac"),
])
def classify_renpy(pattern, groups):
"""
Classifies files in the Ren'Py base directory according to pattern.
"""
renpy_patterns.append((pattern, make_file_lists(groups)))
# Patterns that are relative to the base directory.
early_base_patterns = pattern_list([
early_base_patterns = pattern_list([
("*.py", None),
("*.sh", None),
("*.app/", None),
("*.dll", None),
("*.manifest", None),
("lib/", None),
("renpy/", None),
("update/", None),
("common/", None),
("update/", None),
("icon.ico", None),
("icon.icns", None),
("project.json", None),
("log.txt", None),
("errors.txt", None),
("traceback.txt", None),
("image_cache.txt", None),
("text_overflow.txt", None),
("dialogue.txt", None),
("dialogue.tab", None),
("tmp/", None),
("game/saves/", None),
("archived/", None),
("launcherinfo.py", None),
("android.txt", None),
(".android.json", "android"),
("android-icon.png", "android"),
("ouya-icon.png", "android"),
])
base_patterns = [ ]
late_base_patterns = pattern_list([
(".*", None),
("**", "all")
])
def classify(pattern, file_list):
"""
:doc: build
Classifies files that match `pattern` into `file_list`.
"""
base_patterns.append((pattern, make_file_lists(file_list)))
def clear():
"""
:doc: build
Clears the list of patterns used to classify files.
"""
base_patterns[:] = [ ]
def remove(l, pattern):
"""
Removes the pattern from the list.
"""
l[:] = [ (p, fl) for i in l if p != pattern ]
# Archiving.
archives = [ ]
def archive(name, file_list="all"):
"""
:doc: build
Declares the existence of an archive. If one or more files are
classified with `name`, `name`.rpa is build as an archive. The
Declares the existence of an archive. If one or more files are
classified with `name`, `name`.rpa is build as an archive. The
archive is included in the named file lists.
"""
archives.append((name, make_file_lists(file_list)))
archive("archive", "all")
archive("archive", "all")
# Documentation patterns.
documentation_patterns = [ ]
def documentation(pattern):
"""
:doc: build
Declares a pattern that matches documentation. In a mac app build,
Declares a pattern that matches documentation. In a mac app build,
files matching the documentation pattern are stored twice - once
inside the app package, and again outside of it.
"""
documentation_patterns.append(pattern)
xbit_patterns = [
@@ -183,70 +190,74 @@ init -1500 python in build:
"lib/**/zsync",
"lib/**/zsyncmake",
]
def executable(pattern):
"""
:doc: build
Adds a pattern marking files as executable on platforms that support it.
Adds a pattern marking files as executable on platforms that support it.
(Linux and Macintosh)
"""
xbit_patterns.append(pattern)
# Packaging.
packages = [ ]
def package(name, format, file_lists, description=None, update=True, dlc=False):
def package(name, format, file_lists, description=None, update=True, dlc=False, hidden=False):
"""
:doc: build
Declares a package that can be built by the packaging
tool.
tool.
`name`
The name of the package.
`format`
The format of the package. A string containing a space separated
list of:
zip
A zip file.
A zip file.
app-zip
A zip file containing a macintosh application.
tar.bz2
A tar.bz2 file.
The empty string will not build any package formats (this
The empty string will not build any package formats (this
makes dlc possible).
`file_lists`
A list containing the file lists that will be contained
within the package.
`description`
An optional description of the package to be built.
`update`
If true and updates are being built, an update will be
If true and updates are being built, an update will be
built for this package.
`dlc`
If true, any zip or tar.bz2 file will be built in
If true, any zip or tar.bz2 file will be built in
standalone DLC mode, without an update directory.
`hidden`
If true, this will be hidden from the list of packages in
the launcher.
"""
formats = format.split()
for i in formats:
if i not in [ "zip", "app-zip", "tar.bz2" ]:
if i not in [ "zip", "app-zip", "tar.bz2", "directory" ]:
raise Exception("Format {} not known.".format(i))
if description is None:
description = name
d = {
"name" : name,
"formats" : formats,
@@ -254,47 +265,58 @@ init -1500 python in build:
"description" : description,
"update" : update,
"dlc" : dlc,
"hidden" : hidden,
}
packages.append(d)
package("all", "zip", "windows mac linux all", "All Desktop Platforms")
package("linux", "tar.bz2", "linux all", "Linux x86/x86_64")
package("mac", "app-zip", "mac all", "Macintosh x86")
package("win", "zip", "windows all", "Windows x86")
package("all", "zip", "windows mac linux renpy all", "All Desktop Platforms")
package("linux", "tar.bz2", "linux renpy all", "Linux x86/x86_64")
package("mac", "app-zip", "mac renpy all", "Macintosh x86")
package("win", "zip", "windows renpy all", "Windows x86")
package("android", "directory", "android all", hidden=True, update=False, dlc=True)
# Data that we expect the user to set.
# The name of directories in the archives.
directory_name = ""
# The name of executables.
executable_name = ""
# A verbose name to include in package info.
display_name = ""
# Should we include update information into the archives?
include_update = False
# A verbose version to include in the update.
version = None
# Are we building Ren'Py?
renpy = False
# Should we exclude empty directories from the zip and tar files?
exclude_empty_directories = True
# This function is called by the json_dump command to dump the build data
# The key used for google play.
google_play_key = None
# The salt used for google play.
google_play_salt = None
# The destination things are built in.
destination = "{directory_name}-dists"
# This function is called by the json_dump command to dump the build data
# into the json file.
def dump():
rv = { }
rv["directory_name"] = directory_name
rv["executable_name"] = executable_name
rv["include_update"] = include_update
rv["packages"] = packages
rv["archives"] = archives
rv["documentation_patterns"] = documentation_patterns
@@ -303,9 +325,22 @@ init -1500 python in build:
rv["xbit_patterns"] = xbit_patterns
rv["version"] = version or directory_name
rv["display_name"] = display_name or executable_name
rv["exclude_empty_directories"] = exclude_empty_directories
rv["renpy"] = renpy
rv["destination"] = destination.format(
directory_name=directory_name,
executable_name=executable_name,
display_name=display_name,
version=rv["version"],
)
if google_play_key:
rv["google_play_key"] = google_play_key
if google_play_salt:
rv["google_play_salt"] = google_play_salt
return rv
+20 -14
View File
@@ -4,7 +4,7 @@
init -1900 python:
# This is called when script_version is set, to immediately
# run code in response to a script_version change.
# run code in response to a script_version change.
def _set_script_version(version):
if version is None:
@@ -31,7 +31,7 @@ init -1900 python:
if version <= (6, 10, 2):
# Before 6.11, we used the image placement to handle
# the location of things on the screen.
# the location of things on the screen.
style.image_placement.xpos = 0.5
style.image_placement.ypos = 1.0
style.image_placement.xanchor = 0.5
@@ -40,7 +40,7 @@ init -1900 python:
config.transform_uses_child_position = False
config.default_transform = None
config.start_scene_black = True
if version <= (6, 11, 0):
config.movetransition_respects_offsets = False
@@ -54,28 +54,31 @@ init -1900 python:
config.image_attributes = False
config.new_character_image_argument = False
config.save_physical_size = False
if version <= (6, 12, 2):
style.default.language = "western"
style.default.layout = "greedy"
config.new_substitutions = False
config.broken_line_spacing = True
if (6, 12, 2) < version <= (6, 13, 8):
config.old_substitutions = False
if version <= (6, 13, 12):
global MoveTransition
MoveTransition = OldMoveTransition
define.move_transitions = define.old_move_transitions
define.move_transitions("move", 0.5)
define.move_transitions("ease", 0.5, _ease_time_warp, _ease_in_time_warp, _ease_out_time_warp)
define.move_transitions("ease", 0.5, _ease_time_warp, _ease_in_time_warp, _ease_out_time_warp)
if version <= (6, 14, 1):
config.key_repeat = None
if version <= (6, 15, 7):
MusicRoom.loop_compat = True
# The version of Ren'Py this script is intended for, or
# None if it's intended for the current version.
config.script_version = None
@@ -103,7 +106,7 @@ init 1900 python hide::
config.has_sound = False
config.has_music = False
config.has_voice = False
# Compat for SFont recoloring.
if compat(5, 1, 1):
config.recolor_sfonts = False
@@ -112,7 +115,7 @@ init 1900 python hide::
config.implicit_with_none = False
# Compat for changes to button look.
if compat(5, 5, 4):
if compat(5, 5, 4):
style.button.setdefault(xpos=0.5, xanchor=0.5)
style.menu_button.clear()
style.menu_button_text.clear()
@@ -125,17 +128,20 @@ init 1900 python hide::
if compat(6, 9, 0):
style.motion.clear()
if "Fullscreen" in config.translations:
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")
for i in layout.compat_funcs:
i()
if config.hyperlink_styler or config.hyperlink_callback or config.hyperlink_focus:
style.default.hyperlink_functions = (config.hyperlink_styler, config.hyperlink_callback, config.hyperlink_focus)
if compat(6, 15, 7):
config.has_quicksave = False
config.quit_action = ui.gamemenus("_confirm_quit")
config.default_afm_enable = None
+117 -117
View File
@@ -1,9 +1,9 @@
# console.rpy
# console.rpy
# Ren'Py console
# Copyright (C) 2012 Shiz, C, delta, PyTom
#
# This program is free software. It comes without any warranty, to the extent permitted by applicable law.
# You can redistribute it and/or modify it under the terms of the Do What The Fuck You Want To Public License,
# You can redistribute it and/or modify it under the terms of the Do What The Fuck You Want To Public License,
# Version 2, as published by Sam Hocevar. See http://sam.zoy.org/wtfpl/COPYING for more details.
#
# Usage:
@@ -12,7 +12,7 @@
#
# The following configuration variables are offered for customization:
# - config.console_history_size: the number of commands to store in history. default: 100
# - config.console_custom_commands: a simple name -> function dictionary for custom commands. Command functions should take a
# - config.console_custom_commands: a simple name -> function dictionary for custom commands. Command functions should take a
# lexer, and return result text.
# The following styles are offered for customization:
# - _console: the debug console frame.
@@ -43,7 +43,7 @@ init -1500 python:
config.console_history_size = 100
config.console_commands = { }
# If not None, this is called with the command that's about to be run
# If not None, this is called with the command that's about to be run
# by the console. (The command is represented as a list of strings.) It
# is expected to return a list of strings, which is the command that will
# be actually run.
@@ -52,56 +52,56 @@ init -1500 python:
# Create default styles. See above for documentation.
style.create('_console', '_default')
style._console.background = None
style.create('_console_text', '_default')
style._console_text.size = 14
style._console_text.color = "#ffffff"
style.create('_console_input', '_default')
style._console_input.background = "#0000006f"
style._console_input.xfill = True
style.create('_console_prompt', '_console_text')
style._console_prompt.minwidth = 20
style._console_prompt.text_align = 1.0
style.create('_console_input_text', '_console_text')
style._console_input_text.color = "#fafafa"
style.create('_console_history', '_default')
style._console_history.background = "#00000000"
style._console_history.xfill = True
style._console_history.yfill = True
style._console_history.yfill = True
style.create('_console_history_item', '_default')
style._console_history_item.background = "#00000040"
style._console_history_item.top_margin = 4
style._console_history_item.xfill = True
style.create('_console_command', '_default')
style._console_command.background = "#00000040"
style._console_command.left_padding = 24
style.create('_console_command_text', '_console_text')
style.create('_console_result', '_default')
style._console_result.background = "#00000000"
style._console_result.left_padding = 24
style.create('_console_result_text', '_console_text')
style.create('_console_error_text', '_console_text')
style._console_error_text.color = "#ff8080"
style.create('_console_trace', '_default')
style._console_trace.background = "#00000040"
style._console_trace.xalign = 1.0
style._console_trace.top_margin = 20
style._console_trace.right_margin = 20
style.create('_console_trace_var', '_console_text')
style._console_trace_var.bold = True
style.create('_console_trace_value', '_console_text')
init -1500 python in _console:
@@ -109,21 +109,21 @@ init -1500 python in _console:
import sys
import traceback
import store
class BoundedList(list):
"""
A list that's bounded at a certain size.
"""
def __init__(self, size):
self.size = size
def append(self, value):
super(BoundedList, self).append(value)
while len(self) >= self.size:
self.pop(0)
def clear(self):
self[:] = [ ]
@@ -131,53 +131,53 @@ init -1500 python in _console:
"""
Represents an entry in the history list.
"""
def __init__(self, command, result=None, is_error=False):
self.command = command
self.result = result
self.is_error = is_error
class ScriptErrorHandler(object):
"""
Handles error in Ren'Py script.
"""
def __init__(self):
self.target_depth = renpy.call_stack_depth()
def __call__(self, short, full, traceback_fn):
he = console.history[-1]
he = console.history[-1]
he.result = short.split("\n")[-2]
he.is_error = True
while renpy.call_stack_depth() > self.target_depth:
renpy.pop_call()
renpy.jump("_console")
class DebugConsole(object):
def __init__(self):
self.history = BoundedList(config.console_history_size)
self.line_history = BoundedList(config.console_history_size)
self.line_index = 0
self.first_time = True
self.reset()
def start(self):
he = HistoryEntry(None)
message = ""
if self.first_time:
if self.first_time:
message += renpy.version() + " console, originally by Shiz, C, and delta.\n"
message += "Press <esc> to exit console. Type help for help.\n"
self.first_time = False
if self.can_renpy():
message += "Ren'Py script enabled."
else:
@@ -187,32 +187,32 @@ init -1500 python in _console:
self.history.append(he)
def reset(self):
# The list of lines that have been entered by the user, but not yet
# processed.
self.lines = [ "" ]
self.line_index = len(self.line_history)
def recall_line(self, offset):
self.line_index += offset
if self.line_index < 0:
self.line_index = 0
if self.line_index > len(self.line_history):
self.line_index = len(self.line_history)
if self.line_index == len(self.line_history):
self.lines = [ "" ]
else:
self.lines = list(self.line_history[self.line_index])
renpy.jump("_console")
def older(self):
self.recall_line(-1)
def newer(self):
self.recall_line(1)
@@ -222,9 +222,9 @@ init -1500 python in _console:
"""
Computes the indentation for the line following line s.
"""
rv = ""
for i in s:
if i == " ":
rv += " "
@@ -236,11 +236,11 @@ init -1500 python in _console:
if not s.rstrip():
rv = rv[:-4]
return rv
renpy.game.context().exception_handler = None
renpy.show_screen("_console", lines=self.lines[:-1], default=self.lines[-1], history=self.history, _transient=True)
line = ui.interact()
@@ -252,31 +252,31 @@ init -1500 python in _console:
self.lines.append(indent)
return
lines = self.lines
self.line_history.append(lines)
self.reset()
if config.console_callback is not None:
if config.console_callback is not None:
lines = config.console_callback(lines)
if not lines:
return
self.run(lines)
def can_renpy(self):
"""
Returns true if we can run Ren'Py code.
"""
return renpy.game.context().rollback
def format_exception(self):
etype, evalue, etb = sys.exc_info()
return traceback.format_exception_only(etype, evalue)[-1]
def run(self, lines):
line_count = len(lines)
@@ -292,21 +292,21 @@ init -1500 python in _console:
block = [ ( "<console>", 1, code, [ ]) ]
l = renpy.parser.Lexer(block)
l.advance()
# Command can be None, but that's okay, since the lookup will fail.
command = l.name()
command_fn = config.console_commands.get(command, None)
if command_fn is not None:
he.result = command_fn(l)
return
error = None
# Try to run it as Ren'Py.
if self.can_renpy():
# TODO: Can we run Ren'Py code?
name = renpy.load_string(code + "\nreturn")
@@ -315,8 +315,8 @@ init -1500 python in _console:
renpy.call(name)
else:
error = "\n\n".join(renpy.get_parse_errors())
# Try to eval it.
# Try to eval it.
try:
renpy.python.py_compile(code, 'eval')
except:
@@ -325,7 +325,7 @@ init -1500 python in _console:
result = renpy.python.py_eval(code)
he.result = repr(result)
return
# Try to exec it.
try:
renpy.python.py_compile(code, "exec")
@@ -335,7 +335,7 @@ init -1500 python in _console:
else:
renpy.python.py_exec(code)
return
if error is not None:
he.result = error
he.is_error = True
@@ -369,14 +369,14 @@ init -1500 python in _console:
raise
except:
pass
renpy.call_in_new_context("_console")
init 1500 python in _console:
if config.developer or config.console:
console = DebugConsole()
init -1500 python in _console:
def command(help=None):
@@ -391,27 +391,27 @@ init -1500 python in _console:
def help(l):
keys = list(config.console_commands.iterkeys())
keys.sort()
rv = "commands:\n"
for k in keys:
f = config.console_commands[k]
if f.help is None:
continue
rv += " " + f.help + "\n"
rv += " " + f.help + "\n"
if console.can_renpy():
rv += " <renpy script statement>: run the statement\n"
rv += " <python expression or statement>: run the expression or statement"
return rv
@command()
def halp(l):
return help(l).replace("e", "a")
@command("clear: clear the console history")
def clear(l):
console.history[:] = [ ]
@@ -419,15 +419,15 @@ init -1500 python in _console:
@command("exit: exit the console")
def exit(l):
renpy.jump("_console_return")
@command()
def quit(l):
renpy.jump("_console_return")
@command("load <slot>: loads the game from slot")
def load(l):
name = l.rest().strip()
if not name:
raise Exception("Slot name must not be empty")
@@ -440,7 +440,7 @@ init -1500 python in _console:
@command("save <slot>: saves the game in slot")
def save(l):
name = l.rest().strip()
if not name:
raise Exception("Slot name must not be empty")
@@ -451,17 +451,17 @@ init -1500 python in _console:
@command("reload: reloads the game, refreshing the scripts")
def reload(l):
store._reload_game()
@command()
def R(l):
store._reload_game()
@command("watch <expression>: watch a python expression")
def watch(l):
expr = l.rest()
expr.strip()
renpy.python.py_compile(expr, 'eval')
traced_expressions.append(expr)
renpy.show_screen("_trace_screen")
@@ -469,7 +469,7 @@ init -1500 python in _console:
def unwatch(l):
expr = l.rest()
expr.strip()
if expr in traced_expressions:
traced_expressions.remove(expr)
@@ -477,21 +477,21 @@ init -1500 python in _console:
def unwatchall(l):
traced_expressions[:] = [ ]
renpy.hide_screen("_trace_screen")
@command("jump <label>: jumps to label")
def jump(l):
label = l.name()
if not console.can_renpy():
raise Exception("Ren'Py script not enabled. Not jumping.")
if not renpy.has_label(label):
raise Exception("Label %s not found." % label)
renpy.pop_call()
renpy.jump(label)
screen _console:
# This screen takes as arguments:
@@ -501,72 +501,72 @@ screen _console:
# indent
# Indentation to apply to the new line.
# history
# A list of command, result, is_error tuples.
# A list of command, result, is_error tuples.
zorder 1500
modal True
frame:
style "_console"
has vbox
# Draw the current input.
frame style "_console_input":
has vbox
for line in lines:
hbox:
spacing 4
if line[:1] != " ":
text "> " style "_console_prompt"
else:
text "... " style "_console_prompt"
text "[line!q]" style "_console_input_text"
hbox:
spacing 4
if default[:1] != " ":
text "> " style "_console_prompt"
else:
text "... " style "_console_prompt"
input default default style "_console_input_text" exclude ""
# Draw historical console input.
$ rev_history = list(history)
$ rev_history.reverse()
frame style "_console_history":
has viewport:
mousewheel True
has vbox:
xfill True
for he in rev_history:
frame style "_console_history_item":
has vbox
if he.command is not None:
frame style "_console_command":
xfill True
text "[he.command!q]" style "_console_command_text"
if he.result is not None:
frame style "_console_result":
if he.is_error:
text "[he.result!q]" style "_console_error_text"
else:
text "[he.result!q]" style "_console_result_text"
key "game_menu" action Jump("_console_return")
key "console_older" action _console.console.older
key "console_newer" action _console.console.newer
@@ -577,27 +577,27 @@ screen _trace_screen:
zorder 1501
if _console.traced_expressions:
frame style "_console_trace":
vbox:
for expr in _console.traced_expressions:
python:
try:
value = repr(eval(expr))
except:
value = "eval failed"
hbox:
text "[expr!q]: " style "_console_trace_var"
text "[value!q]" style "_console_trace_value"
# The label that is called by _console.enter to actually run the console.
# This can be called in the current context (for normal Ren'Py code) or
# This can be called in the current context (for normal Ren'Py code) or
# in a new context (in menus).
label _console:
while True:
python in _console:
console.interact()
+28 -10
View File
@@ -9,20 +9,29 @@ init -1500 python:
# basics: If not None, the default value of the text_cps
# preference when the game is first run.
config.default_text_cps = None
config.default_text_cps = None
# If not None, the default value of afm_time
config.default_afm_time = None
# If not None, the default value of afm_enable
config.default_afm_enable = None
config.default_afm_enable = False
# If not None, the default language to use.
config.default_language = None
# If not None, the default value of wait_voice
config.default_wait_for_voice = True
# If not None, the default value of voice_sustain
config.default_voice_sustain = False
init 1500 python:
if not persistent._set_preferences:
persistent._set_preferences = True
if config.default_fullscreen is not None:
_preferences.fullscreen = config.default_fullscreen
@@ -31,16 +40,25 @@ init 1500 python:
if config.default_afm_time is not None:
_preferences.afm_time = config.default_afm_time
if config.default_language is not None:
_preferences.language = config.default_language
if config.default_wait_for_voice is not None:
_preferences.wait_voice = config.default_wait_for_voice
if config.default_voice_sustain is not None:
_preferences.voice_sustain = config.default_voice_sustain
if config.default_afm_enable is not None:
_preferences.afm_enable = config.default_afm_enable
_preferences.using_afm_enable = True
else:
_preferences.afm_enable = True
_preferences.using_afm_enable = False
init -1500 python:
init -1500 python:
def _imagemap_auto_function(auto_param, variant):
rv = auto_param % variant
@@ -48,7 +66,7 @@ init -1500 python:
return rv
else:
return None
config.imagemap_auto_function = _imagemap_auto_function
init -1500 python:
+31 -31
View File
@@ -9,8 +9,8 @@
init -1400:
transform reset:
alpha 1 rotate None zoom 1 xzoom 1 yzoom 1 align (0, 0) alignaround (0, 0) subpixel False size None crop None
alpha 1 rotate None zoom 1 xzoom 1 yzoom 1 align (0, 0) alignaround (0, 0) subpixel False size None crop None
# These are positions that can be used inside at clauses. We set
# them up here so that they can be used throughout the program.
transform left:
@@ -27,16 +27,16 @@ init -1400:
transform topleft:
xpos 0.0 xanchor 0.0 ypos 0.0 yanchor 0.0
transform topright:
xpos 1.0 xanchor 1.0 ypos 0.0 yanchor 0.0
transform top:
xpos 0.5 xanchor 0.5 ypos 0.0 yanchor 0.0
# Offscreen positions for use with the move transition. Images at
# these positions are still shown (and consume
# resources)... remember to hide the image after the transition.
# resources)... remember to hide the image after the transition.
transform offscreenleft:
xpos 0.0 xanchor 1.0 ypos 1.0 yanchor 1.0
@@ -44,31 +44,31 @@ init -1400:
xpos 1.0 xanchor 0.0 ypos 1.0 yanchor 1.0
transform default:
alpha 1 rotate None zoom 1 xzoom 1 yzoom 1 align (0, 0) alignaround (0, 0) subpixel False size None crop None
alpha 1 rotate None zoom 1 xzoom 1 yzoom 1 align (0, 0) alignaround (0, 0) subpixel False size None crop None
xpos 0.5 xanchor 0.5 ypos 1.0 yanchor 1.0
# These are used by the transitions to move things offscreen. We don't
# expect them to be used by user code.
transform _moveleft:
xpos 0.0 xanchor 1.0
transform _moveright:
xpos 1.0 xanchor 0.0
transform _movetop:
ypos 0.0 yanchor 1.0
transform _movebottom:
ypos 1.0 yanchor 0.0
python:
config.default_transform = default
init -1400 python:
_define = define = object()
# Transitions ############################################################
# Simple transitions.
@@ -76,7 +76,7 @@ init -1400 python:
dissolve = Dissolve(0.5)
pixellate = Pixellate(1.0, 5)
# Various uses of CropMove.
# Various uses of CropMove.
wiperight = CropMove(1.0, "wiperight")
wipeleft = CropMove(1.0, "wipeleft")
wipeup = CropMove(1.0, "wipeup")
@@ -134,21 +134,21 @@ init -1400 python:
positions to their new locations, with newly hidden images leaving via
the appropriate side.
`time_warp`, `in_time_warp`, `out_time_warp`
`time_warp`, `in_time_warp`, `out_time_warp`
Time warp functions that are given a time from 0.0 to 1.0 representing
the fraction of the move complete, and return a value in the same
the fraction of the move complete, and return a value in the same
range giving the fraction of a linear move that is complete.
This can be used to define functions that ease the images around,
rather than moving them at a constant speed.
The three argument are used for images remaining on the screen,
newly shown images, and newly hidden images, respectively.
`old`
If true, the transitions to move the old displayables, rather than the new ones.
`layers`
`layers`
The layers the transition will apply to.
::
@@ -157,9 +157,9 @@ init -1400 python:
# with "move".
init python:
define.move_transitions("move", 0.5)
"""
moves = {
"" : MoveTransition(
delay,
@@ -184,7 +184,7 @@ init -1400 python:
time_warp=time_warp,
enter_time_warp=in_time_warp,
),
"intop" : MoveTransition(
delay,
old=old,
@@ -193,7 +193,7 @@ init -1400 python:
time_warp=time_warp,
enter_time_warp=in_time_warp,
),
"inbottom" : MoveTransition(
delay,
old=old,
@@ -255,21 +255,21 @@ init -1400 python:
delay,
old=old,
layers=layers,
factory=MoveFactory(time_warp=time_warp, **kwargs),
factory=MoveFactory(time_warp=time_warp, **kwargs),
enter_factory=MoveIn((1.0, None, 0.0, None), time_warp=in_time_warp, **kwargs)),
"inleft" : MoveTransition(
delay,
old=old,
layers=layers,
factory=MoveFactory(time_warp=time_warp, **kwargs),
factory=MoveFactory(time_warp=time_warp, **kwargs),
enter_factory=MoveIn((0.0, None, 1.0, None), time_warp=in_time_warp, **kwargs)),
"intop" : MoveTransition(
delay,
old=old,
layers=layers,
factory=MoveFactory(time_warp=time_warp, **kwargs),
factory=MoveFactory(time_warp=time_warp, **kwargs),
enter_factory=MoveIn((None, 0.0, None, 1.0), time_warp=in_time_warp, **kwargs)),
"inbottom" : MoveTransition(
@@ -283,28 +283,28 @@ init -1400 python:
delay,
old=old,
layers=layers,
factory=MoveFactory(time_warp=time_warp, **kwargs),
factory=MoveFactory(time_warp=time_warp, **kwargs),
leave_factory=MoveOut((1.0, None, 0.0, None), time_warp=out_time_warp, **kwargs)),
"outleft" : MoveTransition(
delay,
old=old,
layers=layers,
factory=MoveFactory(time_warp=time_warp, **kwargs),
factory=MoveFactory(time_warp=time_warp, **kwargs),
leave_factory=MoveOut((0.0, None, 1.0, None), time_warp=out_time_warp, **kwargs)),
"outtop" : MoveTransition(
delay,
old=old,
layers=layers,
factory=MoveFactory(time_warp=time_warp, **kwargs),
factory=MoveFactory(time_warp=time_warp, **kwargs),
leave_factory=MoveOut((None, 0.0, None, 1.0), time_warp=out_time_warp, **kwargs)),
"outbottom" : MoveTransition(
delay,
old=old,
layers=layers,
factory=MoveFactory(time_warp=time_warp, **kwargs),
factory=MoveFactory(time_warp=time_warp, **kwargs),
leave_factory=MoveOut((None, 1.0, None, 0.0), time_warp=time_warp, **kwargs)),
}
@@ -317,13 +317,13 @@ init -1400 python:
del old_move_transitions
define.move_transitions("move", 0.5)
define.move_transitions("ease", 0.5, _ease_time_warp, _ease_in_time_warp, _ease_out_time_warp)
define.move_transitions("ease", 0.5, _ease_time_warp, _ease_in_time_warp, _ease_out_time_warp)
# Zoom-based transitions. Legacy - nowadays, these are probably best done with ATL.
zoomin = OldMoveTransition(0.5, enter_factory=ZoomInOut(0.01, 1.0))
zoomout = OldMoveTransition(0.5, leave_factory=ZoomInOut(1.0, 0.01))
zoominout = OldMoveTransition(0.5, enter_factory=ZoomInOut(0.01, 1.0), leave_factory=ZoomInOut(1.0, 0.01))
# These shake the screen up and down for a quarter second.
# The delay needs to be an integer multiple of the period.
vpunch = Move((0, 10), (0, -10), .10, bounce=True, repeat=True, delay=.275)
+318 -97
View File
@@ -8,7 +8,7 @@ init -1500 python:
def check(self, all_prior):
return all_prior
class __GalleryArbitraryCondition(object):
def __init__(self, condition):
self.condition = condition
@@ -16,31 +16,31 @@ init -1500 python:
def check(self, all_prior):
return eval(self.condition)
class __GalleryUnlockCondition(object):
def __init__(self, images):
self.images = images
def check(self, all_prior):
for i in self.images:
if not renpy.seen_image(i):
return False
return True
class __GalleryImage(object):
def __init__(self, gallery, displayables):
# The gallery object we belong to.
self.gallery = gallery
# A list of conditions for this image to be displayed.
self.conditions = [ ]
# A list of displayables to show.
self.displayables = displayables
# A list of transforms to apply to those displayables, or None
# to not apply a transform.
self.transforms = [ None ] * len(displayables)
@@ -50,13 +50,13 @@ init -1500 python:
"""
Returns True if the image is unlocked.
"""
for i in self.conditions:
if not i.check(all_prior):
return False
return True
def show(self, locked, index, count):
"""
Shows this image when it's unlocked.
@@ -75,100 +75,138 @@ init -1500 python:
d = config.default_transform(d)
displayables.append(d)
renpy.show_screen("_gallery", locked=locked, index=index + 1, count=count, displayables=displayables, gallery=self.gallery)
ui.interact()
return ui.interact()
class __GalleryButton(object):
def __init__(self, gallery):
def __init__(self, gallery, index):
self.gallery = gallery
self.images = [ ]
self.conditions = [ ]
self.conditions = [ ]
self.index = index
def check_unlock(self):
for i in self.conditions:
if not i.check(True):
return False
for i in self.images:
if i.check_unlock(False):
return True
return False
def show(self):
all_prior = True
class __GalleryToggleSlideshow(Action):
def __init__(self, gallery):
self.gallery = gallery
def __call__(self):
self.gallery.slideshow = not self.gallery.slideshow
renpy.restart_interaction()
def get_selected(self):
return self.gallery.slideshow
for i, img in enumerate(self.images):
locked = not img.check_unlock(all_prior)
img.show(locked, i, len(self.images))
renpy.transition(self.gallery.transition)
class Gallery(object):
"""
:doc: gallery class
This class supports the creation of an image gallery by handling the
locking of images, providing an action that can show one or more images,
and a providing method that creates buttons that use that action.
This class supports the creation of an image gallery by handling the
locking of images, providing an action that can show one or more images,
and a providing method that creates buttons that use that action.
.. attribute:: transition
The transition that is used when changing images.
.. attribute:: locked_button
The default displayable used by make_button for a locked button.
.. attribute:: hover_border
The default hover border used by make_button.
.. attribute:: idle_border
The default idle border used by make_button.
.. attribute:: navigation
If true, the gallery will display navigation and slideshow
buttons on top of the images.
.. attribute:: span_buttons
If true, the gallery will advance between buttons.
.. attribute:: locked
If true, the gallery will advance through locked images.
.. attribute:: slideshow_delay
The time it will take for the gallery to advance between images
in slideshow mode.
"""
transition = None
hover_border = None
idle_border = None
locked_button = None
def __init__(self):
# A map from button name (or image) to __GalleryButton object.
self.buttons = { }
# A list of buttons.
self.button_list = [ ]
self.button_ = None
self.image_ = None
self.unlockable = None
self.navigation = False
self.span_buttons = False
self.locked = True
self.slideshow_delay = 5
def button(self, name):
"""
:doc: gallery method
Creates a new button, named `name`.
Creates a new button, named `name`.
`name`
The name of the button being created.
"""
self.button_ = __GalleryButton(self)
self.unlockable = self.button_
self.buttons[name] = self.button_
button = __GalleryButton(self, len(self.button_list))
self.unlockable = button
self.buttons[name] = button
self.button_list.append(button)
self.button_ = button
def image(self, *displayables):
"""
:doc: gallery method
Adds a new image to the current button, where an image consists
Adds a new image to the current button, where an image consists
of one or more displayables.
"""
self.image_ = __GalleryImage(self, displayables)
self.button_.images.append(self.image_)
self.unlockable = self.image_
@@ -178,12 +216,12 @@ init -1500 python:
def transform(self, *transforms):
"""
:doc: gallery method
Applies transforms to the last image registered. This should be
called with the same number of transforms as the image has
displayables. The transforms are applied to the corresponding
displayables.
called with the same number of transforms as the image has
displayables. The transforms are applied to the corresponding
displayables.
If a transform is None, the default transform is used.
"""
@@ -192,20 +230,20 @@ init -1500 python:
def unlock(self, *images):
"""
:doc: gallery method
A condition that takes one or more image names as argument, and
is satisfied when all the named images have been seen by the
player. The image names should be given as strings.
"""
A condition that takes one or more image names as argument, and
is satisfied when all the named images have been seen by the
player. The image names should be given as strings.
"""
self.unlockable.conditions.append(__GalleryUnlockCondition(images))
def condition(self, expression):
"""
:doc: gallery method
A condition that is satisfied when an expression evaluates to true.
`expression`
A string giving a python expression.
"""
@@ -218,22 +256,22 @@ init -1500 python:
def allprior(self):
"""
:doc: gallery method
A condition that is true if all prior images associated with the
A condition that is true if all prior images associated with the
current button have been unlocked.
"""
self.unlockable.conditions.append(__GalleryAllPriorCondition())
def unlock_image(self, *images):
"""
:doc: gallery method
A convenience method that is equivalent to calling image and unlock
with the same parameters. This will cause an image to be displayed
with the same parameters. This will cause an image to be displayed
if it has been seen before.
The images should be specified as strings giving image names.
The images should be specified as strings giving image names.
"""
self.image(*images)
@@ -242,71 +280,228 @@ init -1500 python:
def Action(self, name):
"""
:doc: gallery method
An action that displays the images associated with the given button
name.
"""
if name not in self.buttons:
raise Exception("{0!r} is not a button defined in this gallery.".format(name))
b = self.buttons[name]
if b.check_unlock():
return ui.invokesinnewcontext(b.show)
return ui.invokesinnewcontext(self.show, b.index)
else:
return None
def make_button(self, name, unlocked, locked=None, hover_border=None, idle_border=None, **properties):
"""
:doc: gallery method
This creates a button that displays the images associated with the given
button name.
This creates a button that displays the images associated with the given
button name.
`name`
The name of the button that will be created.
`unlocked`
A displayable that is displayed for this button when it is
A displayable that is displayed for this button when it is
unlocked.
`locked`
A displayable that is displayed for this button when it is
locked. If None, the locked_button field of the gallery
A displayable that is displayed for this button when it is
locked. If None, the locked_button field of the gallery
object is used instead.
`hover_border`
A displayable that is used to overlay this button when when
A displayable that is used to overlay this button when
it is unlocked and has focus. If None, the hover_border
field of the gallery object is used.
field of the gallery object is used.
`idle_border`
A displayable that is used to overlay this button when when
A displayable that is used to overlay this button when
it is unlocked but unfocused. If None, the idle_border
field of the gallery object is used.
Additional keyword arguments become style properties of the
field of the gallery object is used.
Additional keyword arguments become style properties of the
created button object.
"""
action = self.Action(name)
if locked is None:
locked = self.locked_button
if hover_border is None:
hover_border = self.hover_border
if idle_border is None:
idle_border = self.idle_border
return Button(action=action, child=unlocked, insensitive_child=locked, hover_foreground=hover_border, idle_foreground=idle_border, **properties)
def get_fraction(self, name, format="{seen}/{total}"):
"""
:doc: gallery method
Returns a text string giving the number of unlocked images and total number of images in the button
named `name`.
`format`
A python format string that's used to format the numbers. This has three values that
can be substituted in:
{seen}
The number of images that have been seen.
{total}
The total number of images in the button.
{locked}
The number of images that are still locked.
"""
seen = 0
total = 0
all_prior = True
for i in self.buttons[name].images:
total += 1
if i.check_unlock(all_prior):
seen += 1
else:
all_prior = False
return format.format(seen=seen, total=total, locked=total - seen)
def show(self, button=0, image=0):
"""
Starts showing gallery images.
`button`
The index of the button to start showing.
"""
# A list of (button, image) index pairs for all of the images we know
# about.
all_images = [ ]
# A list of (button, image) index pairs for all of the unlocked
# images.
unlocked_images = [ ]
for bi, b in enumerate(self.button_list):
all_unlocked = True
for ii, i in enumerate(b.images):
all_images.append((bi, ii))
unlocked = i.check_unlock(all_unlocked)
if unlocked:
unlocked_images.append((bi, ii))
else:
all_unlocked = False
self.slideshow = False
# Loop, displaying the images.
while True:
b = self.button_list[button]
i = b.images[image]
result = i.show((button, image) not in all_images, image, len(b.images))
# Default action for click.
if result is True:
result = "next"
if result == 'return':
break
# At this point, result is either 'next', "next_unlocked", "previous", or "previous_unlocked"
# Go through the common advance code.
if self.locked and result.endswith("_unlocked"):
images = all_images
else:
images = unlocked_images
index = images.index((button, image))
if result.startswith('previous'):
index -= 1
else:
index += 1
if index < 0 or index >= len(images):
break
new_button, new_image = images[index]
if not self.span_buttons:
if new_button != button:
break
button = new_button
image = new_image
renpy.transition(self.transition)
def Return(self):
"""
:doc: gallery method
Stops displaying gallery images.
"""
return ui.returns("return")
def Next(self, unlocked=False):
"""
:doc: gallery method
Advances to the next image in the gallery.
`unlocked`
If true, only considers unlocked images.
"""
if unlocked:
return ui.returns("next_unlocked")
else:
return ui.returns("next")
def Previous(self, unlocked=False):
"""
:doc: gallery method
Goes to the previous image in the gallery.
`unlocked`
If true, only considers unlocked images.
"""
if unlocked:
return ui.returns("previous_unlocked")
else:
return ui.returns("previous")
def ToggleSlideshow(self):
"""
:doc: gallery method
Toggles slideshow mode.
"""
return __GalleryToggleSlideshow(self)
init -1500:
# Displays a set of images in the gallery, or indicates that the images
# Displays a set of images in the gallery, or indicates that the images
# are locked. This is given the following arguments:
#
# locked
@@ -320,10 +515,36 @@ init -1500:
# gallery
# The image gallery object.
screen _gallery:
if locked:
add "#000"
text "Image [index] of [count] locked." align (0.5, 0.5)
add "#000"
text _("Image [index] of [count] locked.") align (0.5, 0.5)
else:
for d in displayables:
add d
if gallery.slideshow:
timer gallery.slideshow_delay action Return("next")
key "game_menu" action gallery.Return()
if gallery.navigation:
hbox:
spacing 20
style_group "gallery"
align (.98, .98)
textbutton _("prev") action gallery.Previous()
textbutton _("next") action gallery.Next()
textbutton _("slideshow") action gallery.ToggleSlideshow()
textbutton _("return") action gallery.Return()
python:
style.gallery = Style(style.default)
style.gallery_button.background = None
style.gallery_button_text.color = "#666"
style.gallery_button_text.hover_color = "#fff"
style.gallery_button_text.selected_color = "#fff"
style.gallery_button_text.size = 16
Regular → Executable
+27 -20
View File
@@ -45,7 +45,7 @@ init -1700 python:
config.has_autosave = True
# basics: True if quicksave has been enabled.
config.has_quicksave = False
config.has_quicksave = True
# A list of layers to clear when entering the main and game menus.
config.clear_layers = [ ]
@@ -57,40 +57,41 @@ init -1700 python:
config.menu_clear_layers = [ ]
# What we do on a quit, by default.
config.quit_action = ui.gamemenus("_confirm_quit")
config.quit_action = ui.gamemenus("_quit_prompt")
# What we do on a game menu invokcation.
config.game_menu_action = None
# The screen that we go to when entering the game menu.
_game_menu_screen = None
def _enter_menu():
config.skipping = None
renpy.movie_stop(only_fullscreen=True)
renpy.take_screenshot((config.thumbnail_width, config.thumbnail_height))
if not renpy.context()._menu:
renpy.take_screenshot((config.thumbnail_width, config.thumbnail_height))
for i in config.menu_clear_layers:
renpy.scene(layer=i)
renpy.context()._menu = True
renpy.context()._main_menu = False
renpy.context()._menu = True
renpy.context()._main_menu = main_menu
renpy.context_dynamic("main_menu")
renpy.context_dynamic("_window_subtitle")
renpy.context_dynamic("_window")
store.main_menu = False
store._window_subtitle = config.menu_window_subtitle
store._window = False
store.mouse_visible = True
store.suppress_overlay = True
ui.clear()
for i in config.clear_layers:
renpy.scene(layer=i)
def _invoke_game_menu():
if renpy.context()._menu:
if renpy.context()._main_menu:
@@ -100,10 +101,10 @@ init -1700 python:
else:
if config.game_menu_action:
renpy.display.behavior.run(config.game_menu_action)
else:
else:
renpy.call_in_new_context('_game_menu')
# Run at the end of init, to set up autosaving based on the user's
# choices.
init 1700 python:
@@ -112,7 +113,7 @@ init 1700 python:
config.autosave_slots = 10
else:
config.autosave_frequency = None
# Factored this all into one place, to make our lives a bit easier.
label _enter_game_menu:
$ _enter_menu()
@@ -133,9 +134,9 @@ label _game_menu(_game_menu_screen=_game_menu_screen):
return
$ renpy.play(config.enter_sound)
call _enter_game_menu from _call__enter_game_menu_0
if renpy.has_label("game_menu"):
jump expression "game_menu"
@@ -143,7 +144,7 @@ label _game_menu(_game_menu_screen=_game_menu_screen):
$ renpy.show_screen(_game_menu_screen)
$ ui.interact()
jump _noisy_return
jump expression _game_menu_screen
label _game_menu_save:
@@ -171,12 +172,18 @@ label _game_menu_preferences:
jump expression "preferences_screen"
label _quit:
if renpy.has_label("quit"):
call expression "quit"
$ renpy.quit()
label _return_fast_skipping:
$ config.skipping = "fast"
jump _return
label _return_skipping:
$ config.skipping = "slow"
jump _return
# Make some noise, then return.
label _noisy_return:
$ renpy.play(config.exit_sound)
+72 -78
View File
@@ -13,36 +13,36 @@ init -1500:
python:
class _SetRenderer(Action):
"""
Sets the preferred renderer to one of "auto", "angle", "gl", or
"sw".
Sets the preferred renderer to one of "auto", "angle", "gl", or
"sw".
"""
def __init__(self, renderer):
self.renderer = renderer
def __call__(self):
_preferences.renderer = self.renderer
renpy.restart_interaction()
def get_selected(self):
return _preferences.renderer == self.renderer
# This is displayed to ask the user to choose the renderer he or she
# wants to use. It takes no parameters, doesn't return anything, and
# wants to use. It takes no parameters, doesn't return anything, and
# is expected to call _SetRenderer actions and quit when done.
#
# This screen can be customized by the creator, provided the actions
# remain available.
# This screen can be customized by the creator, provided the actions
# remain available.
screen _choose_renderer:
frame:
style_group ""
xalign .5
yalign .33
xpadding 20
ypadding 20
xmaximum 400
has vbox
@@ -50,16 +50,16 @@ init -1500:
label _("Graphics Acceleration")
null height 10
textbutton _("Automatically Choose"):
action _SetRenderer("auto")
xfill True
if renpy.renpy.windows:
textbutton _("Force Angle/DirectX Renderer"):
action _SetRenderer("angle")
xfill True
textbutton _("Force OpenGL Renderer"):
action _SetRenderer("gl")
xfill True
@@ -71,7 +71,7 @@ init -1500:
null height 10
text _("Changes will take effect the next time this program is run.") substitute True
null height 10
textbutton _(u"Quit"):
@@ -79,13 +79,13 @@ init -1500:
xfill True
if not renpy.display.interface.safe_mode:
textbutton _("Return"):
textbutton _("Return"):
action Return(0)
xfill True
# This is displayed when a display performance problem occurs.
#
# This is displayed when a display performance problem occurs.
#
# `problem` is the kind of problem that is occuring. It can be:
# - "sw" if the software renderer was selected.
# - "slow" if the performance test failed.
@@ -95,10 +95,10 @@ init -1500:
# `url` is the url of a web page on renpy.org that will include
# info on troubleshooting display problems.
screen _performance_warning:
frame:
style_group ""
xalign .5
yalign .33
@@ -110,9 +110,9 @@ init -1500:
has vbox
label _("Performance Warning")
null height 10
if problem == "sw":
text _("This computer is using software rendering.")
elif problem == "fixed":
@@ -120,44 +120,44 @@ init -1500:
elif problem == "slow":
text _("This computer is displaying graphics slowly.")
else:
text _("This computer has a problem displaying graphics: [problem].") substitute True
text _("This computer has a problem displaying graphics: [problem].") substitute True
null height 10
if directx_update:
text _("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.")
else:
else:
text _("Its graphics drivers may be out of date or not operating correctly. This can lead to slow or incorrect graphics display.")
if directx_update:
null height 10
textbutton _("Update DirectX"):
action directx_update
xfill True
null height 10
textbutton _("Continue, Show this warning again"):
textbutton _("Continue, Show this warning again"):
action Return(True)
xfill True
textbutton _("Continue, Don't show warning again"):
action Return(False)
xfill True
null height 10
textbutton _("Quit"):
textbutton _("Quit"):
action Quit(confirm=False)
xfill True
# Used while a directx update is ongoing.
screen _directx_update:
frame:
style_group ""
xalign .5
yalign .33
@@ -167,19 +167,19 @@ init -1500:
xmaximum 400
has vbox
label _("Updating DirectX.")
null height 10
text _("DirectX web setup has been started. It may start minimized in the taskbar. Please follow the prompts to install DirectX.")
null height 10
text _("{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.")
null height 10
null height 10
text _("When setup finishes, please click below to restart this program.")
textbutton _("Restart") action Return(True)
@@ -196,7 +196,7 @@ init -1500 python:
the number of seconds it's been displayed, and uses them
to make the decisions as to if OpenGL is working or not.
"""
def __init__(self, frames, fps, timeout):
super(__GLTest, self).__init__()
@@ -206,9 +206,9 @@ init -1500 python:
self.times = [ ]
self.success = False
renpy.renpy.display.log.write("- Target is {0} frames in {1} seconds.".format(frames, self.target))
def render(self, width, height, st, at):
rv = renpy.Render(width, height)
@@ -220,7 +220,7 @@ init -1500 python:
renpy.redraw(self, 0)
renpy.renpy.display.log.write("- Frame drawn at %f seconds." % st)
if len(self.times) >= self.frames:
frames_timing = self.times[-1] - self.times[-self.frames]
@@ -230,8 +230,8 @@ init -1500 python:
self.success = True
renpy.timeout(0)
return rv
return rv
def event(self, ev, x, y, st):
if self.success:
@@ -241,9 +241,9 @@ init -1500 python:
return False
renpy.timeout(self.timeout - st)
config.performance_test = True
def __gl_test():
import os
@@ -256,7 +256,7 @@ init -1500 python:
if not config.gl_enable:
return
if renpy.display.interface.safe_mode:
renpy.call_in_new_context("_choose_renderer")
@@ -266,7 +266,7 @@ init -1500 python:
_gl_performance_test()
def _gl_performance_test():
import os
if not _preferences.performance_test and "RENPY_PERFORMANCE_TEST" not in os.environ:
@@ -277,7 +277,7 @@ init -1500 python:
return
renpy.renpy.display.log.write("Performance test:")
# This will cause the screen to start displaying.
ui.pausebehavior(0)
ui.interact(suppress_underlay=True, suppress_overlay=True)
@@ -290,29 +290,29 @@ init -1500 python:
# Software renderer check.
if config.renderer != "sw" and renderer_info["renderer"] == "sw":
problem = "sw"
# Speed check.
if problem is None:
# The parameters of the performance test. If we do not hit FPS fps
# over FRAMES frames before DELAY seconds are up, we fail.
# over FRAMES frames before DELAY seconds are up, we fail.
FRAMES = 5
FPS = 15
DELAY = 1.5
renpy.transition(Dissolve(DELAY), always=True, force=True)
ui.add(__GLTest(FRAMES, FPS, DELAY))
result = ui.interact(suppress_overlay=True, suppress_underlay=True)
if not result:
problem = "slow"
# Lack of shaders check.
if problem is None:
if not "RENPY_GL_ENVIRON" in os.environ:
if not "RENPY_GL_ENVIRON" in os.environ:
if renderer_info["renderer"] == "gl" and renderer_info["environ"] == "fixed":
problem = "fixed"
if problem is None:
return
@@ -320,31 +320,31 @@ init -1500 python:
directx_update = Jump("_directx_update")
else:
directx_update = None
# Give the warning message to the user.
# Give the warning message to the user.
renpy.show_screen("_performance_warning", problem=problem, directx_update=directx_update, _transient=True)
result = ui.interact(suppress_overlay=True, suppress_underlay=True)
# Store the user's choice, and continue.
# Store the user's choice, and continue.
_preferences.performance_test = result
return
label _gl_test:
# Show the test image.
scene
show expression config.gl_test_image
$ __gl_test()
# Hide the test image.
scene
return
# We can assume we're on windows here. We're also always restart once we
# We can assume we're on windows here. We're also always restart once we
# make it here.
label _directx_update:
@@ -354,24 +354,18 @@ label _directx_update:
label _directx_update_main:
python hide:
import subprocess
import sys
# Start dxsetup. We have to go through startfile to ensure that UAC
# doesn't cause problems.
os.startfile(__dxwebsetup)
renpy.show_screen("_directx_update")
ui.interact(suppress_overlay=True, suppress_underlay=True)
# Restart the current program.
subprocess.Popen(sys.argv)
renpy.quit()
label _choose_renderer:
renpy.quit(relaunch=True)
label _choose_renderer:
scene expression "#000"
$ renpy.call_screen("_choose_renderer")
$ renpy.call_screen("_choose_renderer")
return
+32 -30
View File
@@ -4,7 +4,7 @@
init -1600 python:
config.keymap = dict(
# Bindings present almost everywhere, unless explicitly
# disabled.
rollback = [ 'K_PAGEUP', 'mousedown_4', 'joy_rollback' ],
@@ -22,20 +22,20 @@ init -1600 python:
iconify = [ 'meta_m', 'alt_m' ],
help = [ 'K_F1', 'meta_shift_/' ],
choose_renderer = [ 'G' ],
# Say.
rollforward = [ 'mousedown_5', 'K_PAGEDOWN' ],
dismiss = [ 'mouseup_1', 'K_RETURN', 'K_SPACE', 'K_KP_ENTER', 'joy_dismiss' ],
# Pause.
dismiss_hard_pause = [ ],
# Focus.
focus_left = [ 'K_LEFT', 'joy_left' ],
focus_right = [ 'K_RIGHT', 'joy_right' ],
focus_up = [ 'K_UP', 'joy_up' ],
focus_down = [ 'K_DOWN', 'joy_down' ],
# Button.
button_ignore = [ 'mousedown_1' ],
button_select = [ 'mouseup_1', 'K_RETURN', 'K_KP_ENTER', 'joy_dismiss' ],
@@ -52,7 +52,7 @@ init -1600 python:
viewport_down = [ 'mousedown_5' ],
viewport_drag_start = [ 'mousedown_1' ],
viewport_drag_end = [ 'mouseup_1' ],
# These keys control skipping.
skip = [ 'K_LCTRL', 'K_RCTRL', 'joy_holdskip' ],
toggle_skip = [ 'K_TAB', 'joy_toggleskip' ],
@@ -72,7 +72,7 @@ init -1600 python:
# Draggable.
drag_activate = [ 'mousedown_1' ],
drag_deactivate = [ 'mouseup_1' ],
# Debug console.
console = [ 'shift_O' ],
console_older = [ 'K_UP' ],
@@ -93,7 +93,7 @@ init -1600 python:
if renpy.context()._menu:
renpy.jump("_noisy_return")
else:
else:
renpy.restart_interaction()
toggle_skipping = _toggle_skipping
@@ -103,9 +103,9 @@ init -1600 python:
return
_toggle_skipping()
config.help = None
def _help(help=None):
if help is None:
@@ -128,16 +128,17 @@ init -1600 python:
import os
config.screenshot_pattern = os.environ.get("RENPY_SCREENSHOT_PATTERN", "screenshot%04d.png")
del os
# Called to make a screenshot happen.
def _screenshot():
import os.path
import os
import __main__
# Pick the directory to save into.
dest = config.renpy_base.rstrip("/")
# Guess if we're an OSX App.
if dest.endswith("/Contents/Resources/autorun"):
# Go up 4 directories.
@@ -145,7 +146,7 @@ init -1600 python:
dest = os.path.dirname(dest)
dest = os.path.dirname(dest)
dest = os.path.dirname(dest)
# Try to pick a filename.
i = 1
while True:
@@ -164,21 +165,20 @@ init -1600 python:
config.screenshot_callback(fn)
def _screenshot_callback(fn):
renpy.notify(_("Saved screenshot as %s.") % fn)
renpy.notify(__("Saved screenshot as %s.") % fn)
config.screenshot_callback = _screenshot_callback
def _dump_styles():
if config.developer:
renpy.style.write_text("styles.txt")
def _fast_skip():
if config.fast_skipping or config.developer:
config.skipping = "fast"
if not config.fast_skipping and not config.developer:
return
if renpy.context()._menu:
renpy.jump("_noisy_return")
Skip(fast=True, confirm=not config.developer)
def _reload_game():
if not config.developer:
@@ -192,9 +192,9 @@ init -1600 python:
filename, line = renpy.get_filename_line()
renpy.launch_editor([ filename ], line)
init -1100 python:
# The default keymap. We might also want to put some of this into
# the launcher.
km = renpy.Keymap(
@@ -219,12 +219,14 @@ init -1100 python:
config.underlay = [ km ]
del km
label _hide_windows:
if renpy.context()._menu:
return
if _windows_hidden:
return
@@ -235,13 +237,13 @@ label _hide_windows:
_windows_hidden = False
return
label _save_reload_game:
python hide:
renpy.take_screenshot((config.thumbnail_width, config.thumbnail_height))
renpy.music.stop()
ui.add(Solid((0, 0, 0, 255)))
ui.text("Saving game...",
size=32, xalign=0.5, yalign=0.5, color=(255, 255, 255, 255))
@@ -249,19 +251,19 @@ label _save_reload_game:
renpy.pause(0)
renpy.save("_reload-1", "reload save game")
ui.add(Solid((0, 0, 0, 255)))
ui.text("Reloading script...",
size=32, xalign=0.5, yalign=0.5, color=(255, 255, 255, 255))
renpy.pause(0)
renpy.utter_restart()
label _load_reload_game:
if not renpy.can_load("_reload-1"):
return
return
python hide:
renpy.rename_save("_reload-1", "_reload-2")
@@ -272,7 +274,7 @@ label _load_reload_game:
ui.pausebehavior(0)
ui.interact(suppress_underlay=True, suppress_overlay=True)
renpy.load("_reload-2")
return
return
Regular → Executable
+49 -46
View File
@@ -73,7 +73,7 @@ init -1400 python hide:
if kind in layout.provided:
raise Exception("%s has already been provided by another layout function." % kind)
layout.provided.add(kind)
# Called by registered themes to have the default versions of
@@ -85,7 +85,7 @@ init -1400 python hide:
# Do nothing, in compatibility mode.
if 'compat' in layout.provided:
return
defaults = dict(
main_menu=layout.classic_main_menu,
load_save=layout.classic_load_save,
@@ -109,7 +109,7 @@ init -1400 python hide:
if renpy.has_screen("joystick_preferences"):
defaults["joystick_preferences"] = layout.screen_joystick_preferences
for k, v in defaults.iteritems():
if k not in layout.provided:
v()
@@ -119,8 +119,8 @@ init -1400 python hide:
store._game_menu_screen = "save_screen"
elif renpy.has_label("preferences_screen"):
store._game_menu_screen = "preferences_screen"
##########################################################################
# These live on the layout object, and ease the construction of buttons,
# labels, and prompts. A sufficently aggressive layout might change
@@ -136,7 +136,7 @@ init -1400 python hide:
unhovered=None,
index=None,
**properties):
"""
label - The label of this button. Will be translated if necessary.
type - The type of this button. Used to generate the appropriate styles.
@@ -163,10 +163,10 @@ init -1400 python hide:
else:
button_style = 'button'
text_style = 'button_text'
if index is None:
index = label
button_style = getattr(style, button_style)[index]
text_style = getattr(style, text_style)[index]
@@ -187,7 +187,7 @@ init -1400 python hide:
else:
label_style = style.label[index]
text_style = style.label_text[index]
ui.window(style=label_style, **properties)
ui.text(_(label), style=text_style)
@@ -202,7 +202,7 @@ init -1400 python hide:
ui.vbox(style=style.list_box)
size_group = str(id(entries))
for i, (spacers, content) in enumerate(entries):
if clicked is not None:
new_clicked = renpy.curry(clicked)(i)
@@ -213,7 +213,7 @@ init -1400 python hide:
new_hovered = renpy.curry(hovered)(i)
else:
new_hovered = None
if unhovered is not None:
new_unhovered = renpy.curry(unhovered)(i)
else:
@@ -226,14 +226,14 @@ init -1400 python hide:
ui.button(style=style.list_row[i%2],
clicked=new_clicked,
hovered=new_hovered,
hovered=new_hovered,
unhovered=new_unhovered,
role=role,
size_group=size_group,
)
ui.hbox(style=style.list_row_box)
for j in range(0, spacers):
ui.window(style=style.list_spacer)
ui.null()
@@ -241,12 +241,12 @@ init -1400 python hide:
ui.text(content, style=style.list_text)
ui.close() # row
ui.close() # vbox
##########################################################################
# Misc.
@layout
def button_menu():
@@ -261,12 +261,12 @@ init -1400 python hide:
style.menu_choice_button.set_parent(style.button)
style.menu_choice_button.xminimum = int(config.screen_width * 0.75)
style.menu_choice_button.xmaximum = int(config.screen_width * 0.75)
store._button_menu = button_menu
###########################################################################
# Compat layout.
@layout
def compat():
layout.provides('compat')
@@ -312,7 +312,7 @@ init -1400 python hide:
@layout
def two_column_preferences():
renpy.load_module("_layout/two_column_preferences")
@layout
def one_column_preferences():
renpy.load_module("_layout/one_column_preferences")
@@ -325,7 +325,7 @@ init -1400 python hide:
if equal_size:
style.mm_button.size_group = "main_menu"
@layout
def grouped_navigation(per_group=2, equal_size=True):
renpy.load_module("_layout/grouped_navigation")
@@ -347,13 +347,13 @@ init -1400 python hide:
config.main_menu_selected[variant] = selected
config.main_menu_hotspots[variant] = hotspots
config.main_menu_idle[variant] = idle
@layout
def imagemap_navigation(ground, idle, hover, selected_idle, selected_hover,
hotspots):
renpy.load_module("_layout/imagemap_navigation")
config.navigation_ground = ground
config.navigation_idle = idle
config.navigation_hover = hover
@@ -366,7 +366,7 @@ init -1400 python hide:
hotspots):
renpy.load_module("_layout/imagemap_preferences")
config.preferences_ground = ground
config.preferences_idle = idle
config.preferences_hover = hover
@@ -378,19 +378,19 @@ init -1400 python hide:
def imagemap_yesno_prompt(ground, idle, hover, hotspots, prompt_images={ }):
renpy.load_module("_layout/imagemap_yesno_prompt")
config.yesno_prompt_ground = ground
config.yesno_prompt_idle = idle
config.yesno_prompt_hover = hover
config.yesno_prompt_hotspots = hotspots
config.yesno_prompt_message_images = prompt_images
@layout
def imagemap_load_save(ground, idle, hover, selected_idle, selected_hover,
hotspots, variant=None):
renpy.load_module("_layout/imagemap_load_save")
config.load_save_ground[variant] = ground
config.load_save_idle[variant] = idle
config.load_save_hover[variant] = hover
@@ -401,46 +401,49 @@ init -1400 python hide:
@layout
def screen_main_menu():
renpy.load_module("_layout/screen_main_menu")
@layout
def screen_load_save():
renpy.load_module("_layout/screen_load_save")
@layout
def screen_preferences():
renpy.load_module("_layout/screen_preferences")
@layout
def screen_joystick_preferences():
renpy.load_module("_layout/screen_joystick_preferences")
@layout
def screen_yesno_prompt():
renpy.load_module("_layout/screen_yesno_prompt")
layout.ARE_YOU_SURE = _("Are you sure?")
layout.DELETE_SAVE = _("Are you sure you want to delete this save?")
layout.OVERWRITE_SAVE = _("Are you sure you want to overwrite your save?")
layout.LOADING = _("Loading will lose unsaved progress.\nAre you sure you want to do this?")
layout.QUIT = _("Are you sure you want to quit?")
layout.MAIN_MENU = _("Are you sure you want to return to the main menu?\nThis will lose unsaved progress.")
layout.SLOW_SKIP = _("Are you sure you want to begin skipping?")
layout.FAST_SKIP_UNSEEN = _("Are you sure you want to skip to the next choice?")
layout.FAST_SKIP_SEEN = _("Are you sure you want to skip to unseen dialogue or the next choice?")
config.enter_yesno_transition = None
config.exit_yesno_transition = None
@layout
def invoke_yesno_prompt(*args):
_enter_menu()
if config.enter_yesno_transition:
renpy.transition(config.enter_yesno_transition)
rv = layout.yesno_prompt(*args)
if config.enter_yesno_transition:
if config.exit_yesno_transition:
renpy.transition(config.exit_yesno_transition)
return rv
@layout
@@ -471,30 +474,30 @@ init -1400 python hide:
yes_action.append(yes)
if no is not None:
no_action.append(no)
if config.enter_yesno_transition:
renpy.transition(config.enter_yesno_transition)
renpy.show_screen(
"yesno_prompt",
"yesno_prompt",
message=message,
yes_action=yes_action,
no_action=no_action)
renpy.restart_interaction()
return
if renpy.invoke_in_new_context(layout.invoke_yesno_prompt, None, message):
if yes is not None:
yes()
else:
if no is not None:
no()
def __auto_save_extra_info():
return save_name
config.auto_save_extra_info = __auto_save_extra_info
+19 -21
View File
@@ -14,7 +14,7 @@ init -1700 python:
save_name = ''
def _default_empty_window():
who = _last_say_who
if who is not None:
@@ -27,9 +27,9 @@ init -1700 python:
nvl_show_core()
else:
store.narrator("", interact=False)
config.empty_window = _default_empty_window
style.skip_indicator = Style(style.default, heavy=True, help='The skip indicator.')
style.skip_indicator.xpos = 10
style.skip_indicator.ypos = 10
@@ -46,22 +46,22 @@ init -1700 python:
who = eval(who)
if who is None:
who = narrator
who = narrator
if isinstance(who, basestring):
who = unknown.copy(who)
# This ensures extend works even with NVL mode.
who.do_extend()
what = _last_say_what + config.extend_interjection + what
what = _last_say_what + config.extend_interjection + _last_raw_what
renpy.exports.say(who, what, interact=interact)
store._last_say_what = what
extend.record_say = False
init -1700 python:
_predict_screens = [ ]
@@ -69,7 +69,7 @@ init -1700 python:
def skip_indicator():
### skip_indicator default
# (text) The style and placement of the skip indicator.
# (text) The style and placement of the skip indicator.
if config.skip_indicator is True:
@@ -91,7 +91,7 @@ init -1700 python:
config.overlay_functions.append(skip_indicator)
# Prediction of screens.
# Prediction of screens.
def predict():
for s in _predict_screens:
@@ -113,25 +113,25 @@ init -1700 python:
renpy.predict_screen(s)
return
config.predict_callbacks.append(predict)
init -1700 python:
##########################################################################
# Side Images
config.side_image_tag = None
config.side_image_only_not_showing = False
def SideImage(prefix_tag="side"):
"""
:doc: side_image_function
Returns the side image associated with the currently speaking character,
Returns the side image associated with the currently speaking character,
or a Null displayable if no such side image exists.
"""
name = renpy.get_side_image(prefix_tag, image_tag=config.side_image_tag, not_showing=config.side_image_only_not_showing)
if name is None:
return Null()
@@ -151,7 +151,7 @@ init 1700 python hide:
if config.debug_sound is None:
config.debug_sound = True
renpy.load_module("_developer")
# Entry point for the developer screen. The rest of it is loaded from
@@ -170,5 +170,3 @@ label _developer:
# its own layer.
screen _ctc:
add ctc
+7 -7
View File
@@ -5,7 +5,7 @@
# the user sets config.has_music, .has_sound, and .has_voice.
init -1600 python hide:
# Set to true in the very unlikely event you want to manually init
# the sound system.
config.force_sound = False
@@ -15,11 +15,11 @@ init -1600 python hide:
# basics: True if the game will have sound effects.
config.has_sound = True
# Sample sounds for various channels.
config.sample_sound = None
config.sample_voice = None
# Register 8 channels by default, for compatiblity with older version
# of Ren'Py.
@@ -27,19 +27,19 @@ init -1600 python hide:
renpy.music.register_channel(i)
renpy.music.register_channel("movie", "music", False, stop_on_mute=False, buffer_queue=False)
# Set up default names for some of the channels.
renpy.music.alias_channel(0, "sound")
renpy.music.alias_channel(7, "music")
renpy.music.alias_channel(2, "voice")
init 1600:
python hide:
if not config.has_music and not config.has_sound:
mixers = None
elif not config.has_music and config.has_sound:
mixers = [ 'sfx' ] * 8
@@ -65,4 +65,4 @@ init 1600:
renpy.music.set_music(i, True, default=True)
else:
renpy.music.set_music(i, False, default=True)
+306 -80
View File
@@ -3,7 +3,7 @@
# This file contains the code to implement Ren'Py's music room function,
# which consists of the ability to set up a playlist (the aforementioned
# "music room"), and then a series of actions that let the player
# "music room"), and then a series of actions that let the player
# navigate through the music room.
init -1500 python:
@@ -12,39 +12,77 @@ init -1500 python:
"""
The action returned by MusicRoom.Play when called with a file.
"""
def __init__(self, mr, filename):
self.mr = mr
self.filename = filename
self.selected = self.get_selected()
def __call__(self):
self.mr.play(self.filename, 0)
def get_sensitive(self):
return self.mr.is_unlocked(self.filename)
def get_selected(self):
return renpy.music.get_playing(self.mr.channel) == self.filename
def periodic(self, st):
if self.selected != self.get_selected():
renpy.restart_interaction()
self.mr.periodic(st)
return .1
class __MusicRoomRandomPlay(Action):
"""
The action returned by MusicRoom.RandomPlay
"""
def __init__(self, mr):
self.mr = mr
def __call__(self):
playlist = self.mr.unlocked_playlist()
if not playlist:
return
self.mr.shuffled = None
self.mr.play(renpy.random.choice(playlist), 0)
class __MusicRoomTogglePlay(Action):
"""
The action returned by MusicRoom.TogglePlay
"""
def __init__(self, mr):
self.mr = mr
def __call__(self):
if renpy.music.get_playing(self.mr.channel):
return self.mr.stop()
self.mr.shuffled = None
return self.mr.play()
def get_selected(self):
return renpy.music.get_playing(self.mr.channel) is not None
class MusicRoom(object):
"""
:doc: music_room class
A music room that contains a series of songs that can be unlocked
by the user, and actions that can play entries from the list in
order.
"""
loop = False
def __init__(self, channel="music", fadeout=0.0, fadein=0.0, loop=False):
loop_compat = False
def __init__(self, channel="music", fadeout=0.0, fadein=0.0, loop=True, single_track=False, shuffle=False, stop_action=None):
"""
`channel`
The channel that this music room will operate on.
@@ -52,79 +90,163 @@ init -1500 python:
`fadeout`
The number of seconds it takes to fade out the old
music when changing tracks.
`fadein`
The number of seconds it takes to fade in the new
music when changing tracks.
`loop`
If true, a music track will loop once played. If False, it
will advance to the next track.
Determines if playback will loop or stop when it reaches
the end of the playlist.
`single_track`
If true, only a single track will play. If loop is true,
that track will loop. Otherwise, playback will stop when the
track finishes.
`shuffle`
If true, the tracks are shuffled, and played in the shuffled
order. If false, the tracks are played in the order they're
added to the MusicRoom.
`stop_action`
An action to run when the music has stopped.
"""
self.channel = channel
self.fadeout = fadeout
self.fadein = fadein
# A map from track name (or "" for stopped) to the appropriate
# action.
self.action = { "" : stop_action }
# The last track playing, or "" if we've been stopped.
self.last_playing = None
# The list of strings giving the titles of songs that make up the
# playlist.
self.playlist = [ ]
# A shuffled copy of the playlist. (Created on demand when we
# need it.)
self.shuffled = None
# A set of filenames, so we can quickly check if a valid filename
# has been provided.
self.filenames = set()
# The set of songs that are always unlocked.
self.always_unlocked = set()
# Should we loop rather than advancing to the next track?
# Should we loop a single track rather than advancing to the next
# track?
self.loop = loop
def add(self, filename, always_unlocked=False):
# Should we play a single track?
self.single_track = single_track
# Should we shuffle the playlist?
self.shuffle = shuffle
# In older versions, loop would loop a single trak.
if self.loop_compat and loop:
self.single_track = True
# The last shown time for the music room.
self.st = -1
def periodic(self, st):
if st == self.st:
return
elif st < self.st:
self.last_playing = None
self.st = st
current_playing = renpy.music.get_playing(self.channel)
if current_playing is None:
current_playing = ""
if self.last_playing != current_playing:
action = self.action.get(current_playing, None)
renpy.run_action(action)
self.last_playing = current_playing
def add(self, filename, always_unlocked=False, action=None):
"""
:doc: music_room method
Adds the music file `filename` to this music room. The music room
will play unlocked files in the order that they are added to the
room.
`always_unlocked`
If true, the music file will be always unlocked. This allows
the file to show up in the music room before it has been
the file to show up in the music room before it has been
played in the game.
`action`
This is a action or the list of actions. these are called when this
file is played.
For example, These actions is used to change a screen or background, description
by the playing file.
"""
self.playlist.append(filename)
self.filenames.add(filename)
if action:
self.action[filename] = action
if always_unlocked:
self.always_unlocked.add(filename)
def is_unlocked(self, filename):
"""
:doc: music_room method
Returns true if the filename has been unlocked (or is always
unlocked), and false if it is still locked.
"""
if filename in self.always_unlocked:
return True
return renpy.seen_audio(filename)
def unlocked_playlist(self):
def unlocked_playlist(self, filename=None):
"""
Returns a list of filenames in the playlist that have been
Returns a list of filenames in the playlist that have been
unlocked.
"""
return [ i for i in self.playlist if self.is_unlocked(i) ]
def play(self, filename=None, offset=0):
if self.shuffle:
if self.shuffled is None or (filename and self.shuffled[0] != filename):
import random
self.shuffled = list(self.playlist)
random.shuffle(self.shuffled)
if filename in self.shuffled:
self.shuffled.remove(filename)
self.shuffled.insert(0, filename)
playlist = self.shuffled
else:
self.shuffled = None
playlist = self.playlist
return [ i for i in playlist if self.is_unlocked(i) ]
def play(self, filename=None, offset=0, queue=False):
"""
Starts the music room playing. The file we start playing with is
selected in two steps.
Starts the music room playing. The file we start playing with is
selected in two steps.
If `filename` is an unlocked file, we start by playing it.
Otherwise, we start by playing the currently playing file, and if
@@ -132,101 +254,205 @@ init -1500 python:
We then apply `offset`. If `offset` is positive, we advance that many
files, otherwise we go back that many files.
The selected file is then played.
If `queue` is true, the music is queued. Otherwise, it is played
immediately.
"""
playlist = self.unlocked_playlist()
playlist = self.unlocked_playlist(filename)
if not playlist:
return
if filename is None:
filename = renpy.music.get_playing(channel=self.channel)
try:
idx = playlist.index(filename)
except ValueError:
idx = 0
idx = (idx + offset) % len(playlist)
if self.loop:
if self.single_track:
playlist = [ playlist[idx] ]
else:
elif self.loop:
playlist = playlist[idx:] + playlist[:idx]
renpy.music.play(playlist, channel=self.channel, fadeout=self.fadeout, fadein=self.fadein)
else:
playlist = playlist[idx:]
if queue:
renpy.music.queue(playlist, channel=self.channel, loop=self.loop)
else:
renpy.music.play(playlist, channel=self.channel, fadeout=self.fadeout, fadein=self.fadein, loop=self.loop)
def queue_if_playing(self):
"""
If the music is not playing, do nothing.
Otherwise, redo the queue such that we have the right tracks
queued up.
"""
filename = renpy.music.get_playing(channel=self.channel)
if filename is None:
return
if self.single_track:
self.play(None, offset=0, queue=True)
else:
self.play(None, offset=1, queue=True)
def stop(self):
"""
Stops the music from playing.
"""
renpy.music.stop(channel=self.channel, fadeout=self.fadeout)
def next(self):
"""
Plays the next file in the playlist.
"""
return self.play(None, 1)
filename = renpy.music.get_playing(channel=self.channel)
if filename is None:
return self.play(None, 0)
else:
return self.play(None, 1)
def previous(self):
"""
Plays the previous file in the playlist.
"""
return self.play(None, -1)
def Play(self, filename=None):
"""
:doc: music_room method
Causes the music room to start playing. If `filename` is given, that
file begins playing. Otherwise, the currently playing file starts
This action causes the music room to start playing. If `filename` is given, that
file begins playing. Otherwise, the currently playing file starts
over (if it's unlocked), or the first file starts playing.
If `filename` is given, buttons with this action will be insensitive
while `filename` is locked, and will be selected when `filename`
is playing.
"""
if filename is None:
return self.play
if filename not in self.filenames:
raise Exception("{0!r} is not a filename registered with this music room.".format(filename))
return __MusicRoomPlay(self, filename)
def RandomPlay(self):
"""
:doc: music_room method
This action causes the music room to start playing a randomly selected unlocked
music track.
"""
return __MusicRoomRandomPlay(self)
def TogglePlay(self):
"""
:doc: music_room method
If no music is currently playing, this action starts playing the first
unlocked track. Otherwise, stops the currently playing music.
This button is selected when any music is playing.
"""
return __MusicRoomTogglePlay(self)
def Stop(self):
"""
:doc: music_room method
Stops the music.
This action stops the music.
"""
return self.stop
def Next(self):
"""
:doc: music_room method
An action that causes the music room to play the next unlocked file
in the playlist.
"""
return self.next
def Previous(self):
"""
:doc: music_room method
An action that causes the music room to play the previous unlocked
An action that causes the music room to play the previous unlocked
file in the playlist.
"""
return self.previous
def SetLoop(self, value):
"""
:doc: music_room method
This action sets the value of the loop property.
"""
return [ SetField(self, "loop", value), self.queue_if_playing ]
def SetSingleTrack(self, value):
"""
:doc: music_room method
This action sets the value of the single_track property.
"""
return [ SetField(self, "single_track", value), self.queue_if_playing ]
def SetShuffle(self, value):
"""
:doc: music_room method
This action sets the value of the shuffle property.
"""
return [ SetField(self, "shuffle", value), self.queue_if_playing ]
def ToggleLoop(self):
"""
:doc: music_room method
This action toggles the value of the loop property.
"""
return [ ToggleField(self, "loop"), self.queue_if_playing ]
def ToggleSingleTrack(self):
"""
:doc: music_room method
This action toggles the value of the single_track property.
"""
return [ ToggleField(self, "single_track"), self.queue_if_playing ]
def ToggleShuffle(self):
"""
:doc: music_room method
This action toggles the value of the shuffle property.
"""
return [ ToggleField(self, "shuffle"), self.queue_if_playing ]
+41 -44
View File
@@ -17,7 +17,7 @@
#
# init:
# $ narrator = NVLCharacter(None)
##############################################################################
# The implementation of NVL mode lives below this line.
@@ -66,7 +66,7 @@ init -1500 python:
# A hook that delta wanted, that is called instead of renpy.show_display_say
config.nvl_show_display_say = renpy.show_display_say
# A list of arguments that have been passed to nvl_record_show.
nvl_list = None
@@ -88,7 +88,7 @@ init -1500 python:
widget_properties = { }
dialogue = [ ]
for i, entry in enumerate(nvl_list):
if not entry:
continue
@@ -104,40 +104,40 @@ init -1500 python:
who_id = "who%d" % i
what_id = "what%d" % i
window_id = "window%d" % i
widget_properties[who_id] = kwargs["who_args"]
widget_properties[what_id] = kwargs["what_args"]
widget_properties[window_id] = kwargs["window_args"]
dialogue.append((who, what, who_id, what_id, window_id))
return widget_properties, dialogue
def __nvl_show_screen(screen_name, **scope):
"""
Shows an nvl-mode screen. Returns the "what" widget.
"""
widget_properties, dialogue = __nvl_screen_dialogue()
widget_properties, dialogue = __nvl_screen_dialogue()
renpy.show_screen(screen_name, _transient=True, _widget_properties=widget_properties, dialogue=dialogue, **scope)
renpy.show_screen(screen_name, _transient=True, _widget_properties=widget_properties, dialogue=dialogue, **scope)
renpy.shown_window()
return renpy.get_widget(screen_name, "what")
def nvl_show_core(who=None, what=None):
# Screen version.
if renpy.has_screen("nvl"):
return __nvl_show_screen("nvl", items=[ ])
if renpy.in_rollback():
nvl_window = __s(style.nvl_window)['rollback']
nvl_vbox = __s(style.nvl_vbox)['rollback']
else:
nvl_window = __s(style.nvl_window)
nvl_vbox = __s(style.nvl_vbox)
ui.window(style=nvl_window)
ui.vbox(style=nvl_vbox)
@@ -147,18 +147,18 @@ init -1500 python:
if not i:
continue
who, what, kw = i
who, what, kw = i
rv = config.nvl_show_display_say(who, what, variant=nvl_variant, **kw)
ui.close()
renpy.shown_window()
return rv
def nvl_window():
nvl_show_core()
def nvl_show(with_):
nvl_show_core()
renpy.with_statement(with_)
@@ -193,39 +193,39 @@ init -1500 python:
return False
scry = scry.next()
class NVLCharacter(ADVCharacter):
def __init__(self,
def __init__(self,
who=renpy.character.NotSet,
kind=None,
**properties):
if kind is None:
kind = store.nvl
if "clear" in properties:
self.clear = properties.pop("clear")
else:
self.clear = kind.clear
ADVCharacter.__init__(
self,
self,
who,
kind=kind,
**properties)
def do_add(self, who, what):
if store.nvl_list is None:
store.nvl_list = [ ]
kwargs = self.show_args.copy()
kwargs["what_args"] = self.what_args
kwargs["who_args"] = self.who_args
kwargs["window_args"] = self.window_args
store.nvl_list.append((who, what, kwargs))
def do_display(self, who, what, **display_args):
@@ -245,14 +245,14 @@ init -1500 python:
checkpoint = False
else:
checkpoint = True
renpy.display_say(
who,
what,
nvl_show_core,
checkpoint=checkpoint,
**display_args)
def do_done(self, who, what):
if self.clear:
nvl_clear()
@@ -260,7 +260,7 @@ init -1500 python:
def do_extend(self):
renpy.mode(self.mode)
store.nvl_list = store.nvl_list[:-1]
# The default NVLCharacter.
nvl = NVLCharacter(
show_say_vbox_properties={ 'box_layout' : 'horizontal' },
@@ -271,31 +271,31 @@ init -1500 python:
mode='nvl',
clear=False,
kind=adv)
def nvl_clear():
store.nvl_list = [ ]
# Run clear at the start of the game.
config.start_callbacks.append(nvl_clear)
def nvl_menu(items):
renpy.mode('nvl_menu')
if nvl_list is None:
store.nvl_list = [ ]
screen = None
if renpy.has_screen("nvl_choice"):
screen = "nvl_choice"
elif renpy.has_screen("nvl"):
screen = "nvl"
if screen is not None:
widget_properties, dialogue = __nvl_screen_dialogue()
widget_properties, dialogue = __nvl_screen_dialogue()
return renpy.display_menu(
items,
@@ -307,7 +307,7 @@ init -1500 python:
choice_chosen_style=__s(style.nvl_menu_choice_chosen),
choice_button_style=__s(style.nvl_menu_choice_button),
choice_chosen_button_style=__s(style.nvl_menu_choice_chosen_button),
type="nvl",
type="nvl",
)
@@ -323,7 +323,7 @@ init -1500 python:
if not i:
continue
who, what, kw = i
who, what, kw = i
rv = renpy.show_display_say(who, what, **kw)
renpy.display_menu(items, interact=False,
@@ -344,7 +344,7 @@ init -1500 python:
return rv
NVLSpeaker = NVLCharacter
config.nvl_adv_transition = None
config.adv_nvl_transition = None
@@ -358,14 +358,14 @@ init -1500 python:
if mode == "nvl" or mode == "nvl_menu":
if old == "say" or old == "menu":
nvl_show(config.adv_nvl_transition)
if config.nvl_adv_transition:
if mode == "say" or mode == "menu":
if old == "nvl" or old == "nvl_menu":
nvl_hide(config.nvl_adv_transition)
config.mode_callbacks.append(_nvl_adv_callback)
python early hide:
def parse_nvl_show_hide(l):
@@ -377,7 +377,7 @@ python early hide:
renpy.error('expected end of line')
return rv
def lint_nvl_show_hide(trans):
_try_eval(trans, 'transition')
@@ -408,12 +408,9 @@ python early hide:
def scry_nvl_clear(parse, scry):
scry.nvl_clear = True
renpy.statements.register('nvl clear',
parse=parse_nvl_clear,
execute=execute_nvl_clear,
scry=scry_nvl_clear,
translatable=True)
+10 -10
View File
@@ -1,7 +1,7 @@
# Copyright 2004-2013 Tom Rothamel <pytom@bishoujo.us>
# See LICENSE.txt for license details.
# This file defines variables and functions that are obsolete in modern version
# This file defines variables and functions that are obsolete in modern version
# of Ren'Py. (But there are other functions that are also obsolete.)
init -1900 python:
@@ -14,7 +14,7 @@ init -1900 python:
config.module_warning = False
init -1900 python:
# basics: A map from a string that's displayed by the interface to
# a translated value of that string.
config.translations = { }
@@ -52,17 +52,17 @@ init -1900 python:
# The minimum number of components which the image name consists of is 2 by default.
config.automatic_images_minimum_components = 2
init 1900 python hide:
def create_automatic_images():
seps = config.automatic_images
if seps is True:
seps = [ ' ', '/', '_' ]
for dir, fn in renpy.loader.listdirfiles():
if fn.startswith("_"):
@@ -74,7 +74,7 @@ init 1900 python hide:
# Strip the extension, replace slashes.
shortfn = fn[:-4].replace("\\", "/")
# Determine the name.
name = ( shortfn, )
for sep in seps:
@@ -88,11 +88,11 @@ init 1900 python hide:
break
else:
break
# Only names of 2 components or more by default.
if len(name) < config.automatic_images_minimum_components:
continue
# Reject if it already exists.
if name in renpy.display.image.images:
continue
@@ -115,7 +115,7 @@ init -1900 python:
# narrator("", interact=False)
ui.window(style=style.say_window["empty"])
ui.null()
return trans
config.with_callback = _default_with_callback
+40 -22
View File
@@ -6,18 +6,18 @@ init -1500 python:
class __DisplayAction(Action):
def __init__(self, factor):
self.width = int(factor * config.screen_width)
self.height = int(factor * config.screen_height)
self.height = int(factor * config.screen_height)
def __call__(self):
renpy.set_physical_size((self.width, self.height))
renpy.restart_interaction()
def get_sensitive(self):
if self.width == config.screen_width and self.height == config.screen_height:
return True
return renpy.get_renderer_info()["resizable"]
def get_selected(self):
if _preferences.fullscreen:
return False
@@ -31,7 +31,7 @@ init -1500 python:
def Preference(name, value=None):
"""
:doc: preference_action
This constructs the approprate action or value from a preference.
The preference name should be the name given in the standard
menus, while the value should be either the name of a choice,
@@ -71,6 +71,15 @@ init -1500 python:
* Preference("auto-forward", "disable") - Disable auto-forward mode.
* Preference("auto-forward", "toggle") - Toggle auto-forward mode.
* Preference("wait for voice", "enable") - Wait for the currently playing voice to complete before auto-forwarding.
* Preference("wait for voice", "disable") - Do not wait for the currently playing voice to complete before auto-forwarding.
* Preference("wait for voice", "toggle") - Toggle wait voice.
* Preference("voice sustain", "enable") - Sustain voice past the current interaction.
* Preference("voice sustain", "disable") - Don't sustain voice past the current interaction.
* Preference("voice sustain", "toggle") - Toggle voice sustain.
* Preference("music mute", "enable") - Mute the music mixer.
* Preference("music mute", "disable") - Un-mute the music mixer.
* Preference("music mute", "toggle") - Toggle music mute.
@@ -86,14 +95,14 @@ init -1500 python:
* Preference("music volume", 0.5) - Set the music volume.
* Preference("sound volume", 0.5) - Set the sound volume.
* Preference("volice volume", 0.5) - Set the voice volume.
Values that can be used with bars are:
* Preference("text speed")
* Preference("auto-forward time")
* Preference("music volume")
* Preference("sound volume")
* Preference("voice volume")
* Preference("voice volume")
"""
name = name.lower()
@@ -123,7 +132,7 @@ init -1500 python:
return ToggleField(_preferences, "transitions", true_value=2, false_value=0)
elif name == "text speed":
if value is None:
return FieldValue(_preferences, "text_cps", range=200, max_is_zero=True, style="slider")
elif isinstance(value, int):
@@ -137,16 +146,7 @@ init -1500 python:
return None
elif name == "skip":
if value == "all messages" or value == "all":
return SetField(_preferences, "skip_unseen", True)
elif value == "seen messages" or value == "seen":
return SetField(_preferences, "skip_unseen", False)
elif value == "toggle":
return ToggleField(_preferences, "skip_unseen")
elif name == "skip":
if value == "all messages" or value == "all":
return SetField(_preferences, "skip_unseen", True)
elif value == "seen messages" or value == "seen":
@@ -159,23 +159,23 @@ init -1500 python:
return Skip()
elif name == "after choices":
if value == "keep skipping" or value == "keep" or value == "skip":
return SetField(_preferences, "skip_after_choices", True)
elif value == "stop skipping" or value == "stop":
return SetField(_preferences, "skip_after_choices", False)
elif value == "toggle":
return ToggleField(_preferences, "skip_after_choices")
elif name == "auto-forward time":
if value is None:
if config.default_afm_enable is None:
return FieldValue(_preferences, "afm_time", range=30.0, max_is_zero=True, style="slider")
else:
return FieldValue(_preferences, "afm_time", range=29.9, style="slider", offset=.1)
elif isinstance(value, int):
return SetField(_preferences, "afm_time", value)
@@ -187,7 +187,16 @@ init -1500 python:
return SetField(_preferences, "afm_enable", False)
elif value == "toggle":
return ToggleField(_preferences, "afm_enable")
elif name == "wait for voice":
if value == "enable":
return SetField(_preferences, "wait_voice", True)
elif value == "disable":
return SetField(_preferences, "wait_voice", False)
elif value == "toggle":
return ToggleField(_preferences, "wait_voice")
elif name == "music volume":
if value is None:
@@ -236,6 +245,15 @@ init -1500 python:
elif value == "toggle":
return ToggleDict(_preferences.mute, "voice")
elif name == "voice sustain":
if value == "enable":
return SetField(_preferences, "voice_sustain", True)
elif value == "disable":
return SetField(_preferences, "voice_sustain", False)
elif value == "toggle":
return ToggleField(_preferences, "voice_sustain")
else:
raise Exception("Preference(%r, %r) is unknown." % (name , value))

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