Compare commits

...

161 Commits

Author SHA1 Message Date
Gouvernathor e8e73ae2c2 Parse (ignore) typing annotations for define and default 2023-04-18 19:16:12 +02:00
Tom Rothamel fb803ea05c Force a new checkpoint to begin when checkpointing is suspended.
Per #4534.
2023-04-17 23:29:15 -04:00
Tom Rothamel 04d9ea3016 itch: Make the channels configurable.
Fixes #4481.
2023-04-17 01:33:38 -04:00
Tom Rothamel 1f26d829d1 doc: Reload is disabled when autoreload is active. 2023-04-16 21:39:47 -04:00
Tom Rothamel d9b30cf8a1 Merge pull request #4537 from renpy/replay-action
Replay action's lockedness
2023-04-15 23:00:20 -04:00
Tom Rothamel ebcc0ce7dc Believe _choose_attributes when it chooses attributes.
Even if it does odd things, like removing a required attribute
from the list of attributes.

Fixes #4505.
2023-04-15 15:10:04 -04:00
Gouvernathor 8a2c6d4dc4 Better behavior for the locked parameter 2023-04-15 19:21:49 +02:00
Gouvernathor 5c400fdbc9 Accurate doc entry 2023-04-15 19:14:22 +02:00
Tom Rothamel 34a011e79f Python string prefixes must immediately precede delimiters.
Fixes #4535
2023-04-15 12:33:12 -04:00
Gouvernathor 7545b87364 Fix rst comment block
yes, sphinx is *that* annoying
2023-04-15 17:26:16 +02:00
Tom Rothamel 72f0535fb3 Prevent greedy rollbacks from passing through suspend_checkpointing.
Fixes #4534.
2023-04-15 01:28:30 -04:00
Tom Rothamel e776a4c3f5 doc: SVG. 2023-04-14 01:50:22 -04:00
Tom Rothamel e439957896 Document last change. 2023-04-14 01:04:10 -04:00
Tom Rothamel dffbe3521c Allow triple quoted strings to be used in simple expressions.
For example:

text """\
line 1
line 2
line 3"""

is now allowed.
2023-04-13 23:34:55 -04:00
Tom Rothamel 1fad559548 doc: Speech bubble dialogue. 2023-04-13 00:28:46 -04:00
Tom Rothamel e6fec6ea50 doc: Credit. 2023-04-12 01:10:24 -04:00
Tom Rothamel a427a6611b gl2: Restore the window position after a reload. 2023-04-12 00:36:39 -04:00
Tom Rothamel 9932e4e892 Allow Character's kind argument to be a namspace.
The main reason for this is to allow Character(kind=bubble), which
looks way nicer that Character(kind=bubble.character) to me.
2023-04-11 23:42:56 -04:00
Tom Rothamel e702a15785 bubble: Add shift+B as a way of toggling the bubble editor. 2023-04-11 23:40:30 -04:00
Tom Rothamel cf7910687f bubble: Add four speech bubbles by default. 2023-04-11 23:25:39 -04:00
Tom Rothamel 2a6c719678 Clean language dependent data when jumping back after a load.
Fixes #4525.
2023-04-10 22:28:52 -04:00
Tom Rothamel 9f08129084 Fix steam.activate_overlay_to_web_page.
Fixes #4526.
2023-04-10 01:12:38 -04:00
Tom Rothamel 40e81d44a4 bubble: Support expanding beyond what's given.
This will provide room for tails.
2023-04-09 00:39:19 -04:00
Tom Rothamel 40ac61bf23 Merge pull request #4523 from awaken1ng/fix-compat
Fix compat using unknown config option.
2023-04-08 23:24:11 -04:00
Tom Rothamel 16372810e5 Reset the video to the beginning when it is shown again.
As opposed to when it's hidden.

Per the discussion at #4508.
2023-04-08 21:33:43 -04:00
awakening f2a76c5872 Fix compat using unknown config option.
Fixes `Exception: config.timer_blocks_pause is not a known configuration variable.`

7d4610d13d introduces `modal_blocks_timer`
which is probably what was meant to be used here.
2023-04-08 19:44:49 +07:00
Tom Rothamel 3350ffa713 gl2: Add functions to allow a user to create a mesh from Python.
Per request via discord.
2023-04-07 23:27:18 -04:00
Tom Rothamel bf0634f738 Disable skip mode in the launcher. 2023-04-07 14:28:44 -04:00
Tom Rothamel 13f9b3d04c Remove pointless maintenance of ctx.updating. 2023-04-07 14:27:05 -04:00
Tom Rothamel 1d96e7a16f Fix Scroll action.
Fixes #4520.
2023-04-07 00:18:08 -04:00
Tom Rothamel df524315a8 sl2: Do not reuse the scope when showing a 'used' screen.
This fixes the default statement when in a used screen that's
passed different parameters than it was predicted with.

Fixes #4519.
2023-04-06 22:51:13 -04:00
Tom Rothamel 7c082f0fb5 Fixes to applying 3d state properties.
Recent changes made many of the properties incorrectly conditional
on each other, which prevented the properties from working
correctly.

Fixes #4518.
2023-04-06 21:33:29 -04:00
Tom Rothamel 6d637237ff doc: Extend the gl1 timeline slightly further. 2023-04-06 20:26:13 -04:00
Tom Rothamel 2f6556052a doc: Correct version. 2023-04-06 20:09:24 -04:00
Tom Rothamel 9a6448dd94 doc: Pending deprecations.
* Mention the planned removal of gl1 and Python 2/Ren'Py 7.
2023-04-06 19:05:24 -04:00
Tom Rothamel 54f62730a2 Fit the error that occurs if a style statement has two is clauses.
Per #4510

Co-authored-by: <Ayowel@users.noreply.github.com>
2023-04-06 00:31:33 -04:00
Tom Rothamel ca7c78dbcd Only reset a channel if a hide happens.
Fixes #4508.
2023-04-05 22:29:01 -04:00
Moshibit 16fb5ffb85 Update Spanish translation (#4511)
* Update gui.rpy

* Update launcher.rpy

* Update screens.rpy
2023-04-05 18:38:42 +02:00
Gouvernathor aea1129812 Update translations 2023-04-05 06:59:54 +02:00
Tom Rothamel 46320ddcd4 Fix the type of paths on mac.
Fixes #4509.
2023-04-05 00:48:56 -04:00
Tom Rothamel a65cdd6f0e Fix the Android JDK link.
Add a bit of abstraction so it'll only need to be restranslated
once, and route it through renpy.org so the links can't break.
2023-04-05 00:48:56 -04:00
Gouvernathor 9d275e9349 doc: forgotten markup 2023-04-04 20:01:15 +02:00
Gouvernathor 534fb0f9f6 Fix typo in docstring 2023-04-04 19:58:17 +02:00
Gouvernathor 005be375fc french translations 2023-04-04 17:50:28 +02:00
Tom Rothamel b2d5dd87fa Ignore ATL transition arguments when comparing contexts.
The new_widget argument will be different each time the
interaction restarts - but it generally represents the same
thing, and so it makes sense to consider it equal for the
purposes of this test.

Fixes #4167.
2023-04-03 00:08:28 -04:00
Tom Rothamel a6b99364aa Propagate ATL state between transitions when an interaction restarts.
This partially addresses #4167.
2023-04-02 23:38:59 -04:00
Tom Rothamel e5c2e7f88c doc: Last change. 2023-04-02 23:35:45 -04:00
Tom Rothamel 907ba19bea tts: Make it respect the voice volume mixer.
At least more or less, as it generally doesn't go through Ren'Py.
But on all platforms where voice is supported, the amplitude of
voice is controlled by the voice mixer

This also makes the voice volume mixer available on the
accessiiblity screen, so the volume can be adjusted even
if the game doesn't otherwise use voice.

Fixes #4161.
2023-04-02 20:56:15 -04:00
Tom Rothamel 74d8c5c398 gl2: Handle the interaction of maximized, fullscreen, and window size/location.
The maximized flag is persisted, so that if the game is shut down
when maximized, it will start up again maximized.

The size of the game is only stored when non maximized or fullscreen,
so only sensible game sizes are persisten.

Switching from fullscreen to maximized or vice versa goes through
the windowed state.

The first time the window is in the windowed state, it's moved to the
center of the screen - rather than being undefined when the game
starts fullscreen.

Fixes #4155.
2023-04-02 19:06:07 -04:00
Tom Rothamel f65c1a1af3 Merge pull request #4497 from mal/fix-style-optimisation
Let Transforms benefit from style optimisation
2023-04-02 00:14:01 -04:00
Mal Graty b740e723a3 Let Transforms benefit from style optimisation
In ef6173de4f we added the facility for
alt text to be passed through Transforms to allow its use in SL2. A side
effect of this was to always pass alt text, meaning that the underlying
Displayable's properties could never be empty (they'd contain alt=None).
This has been preventing Transforms benefiting from the optimised code
path in Displayable that avoids creating a new anonymous style.

In order to address this, we now filter out arguments passed to
Transform that ought to be propagated up to the parent class, sending
them only when defined, thus re-enabling the optimisation for the vast
majority of Transform instances.
2023-04-02 04:47:41 +01:00
Tom Rothamel 5bdb9b5459 Name Ren'Py 8.1 and Ren'Py 7.6. 2023-04-01 02:03:57 -04:00
Tom Rothamel d70d6b8f26 Merge pull request #4494 from renpy/comments-in-shader-variables
Allow comments in shader variables lists
2023-03-31 22:45:35 -04:00
Gouvernathor 44cdc09745 Allow comments in shader variables lists 2023-03-30 22:56:44 +02:00
Tom Rothamel 1291c3cb26 Slightly adjust controller deadbands (based on my experience). 2023-03-30 02:11:08 -04:00
Tom Rothamel 493edfbb82 Add renpy.load_rgba, loading a rgba string into a surface.
This is intended to support image data being created elsewhere,
loaded in via ctypes, and then passed into Ren'Py.
2023-03-29 23:27:25 -04:00
Tom Rothamel ec6277588d Allow the Ren'Py audio system to be extended. 2023-03-29 23:27:25 -04:00
Tom Rothamel 3f057f159a Make the audio buffer size configurable. 2023-03-29 23:27:25 -04:00
Tom Rothamel 420e167d41 Merge pull request #4491 from renpy/fix-__-munging
Fix name-munging
2023-03-29 12:27:16 -04:00
Gouvernathor 2828f3b517 Fix name-munging 2023-03-29 18:03:53 +02:00
Tom Rothamel c378247a39 Add config.display_start_callbacks.
This is intended to be used to let DLLS gain access to GL functios
before a frame is renderer.
2023-03-29 01:57:15 -04:00
Tom Rothamel 9ab745db85 Increase the contrast of the light GUI.
Fixes #4470.
2023-03-29 01:43:44 -04:00
Tom Rothamel f92022308d Disable ui-less IME.
Which shows the candidate list for languages that require one -
not sure why this isn't the default.

Fixes #4474.
2023-03-28 02:44:59 -04:00
Tom Rothamel 4f0e882d97 Set empty properties on a texture.
Fixes #4488.
2023-03-28 02:04:34 -04:00
Tom Rothamel 46f62133d1 Add movie groups.
When a movie in a group is starting, and doesn't have the first
frame, it checks to see if another movie in the group is playing.
If so, it takes the frame from the other movie - preventing flashes
of transparency when switching movies.

Fixes #4475.
2023-03-28 01:46:15 -04:00
Tom Rothamel 77889607f3 gl2: Use a 1px framebuffer for is_pixel_opaque.
So there's less to clear.
2023-03-27 01:39:24 -04:00
Tom Rothamel f92dcd8068 doc: Anonymous transforms on the first sl line. 2023-03-26 10:20:57 -04:00
Tom Rothamel 815a3999b4 Compat for c492f2f8e6. 2023-03-26 10:16:52 -04:00
Tom Rothamel 11c9b8f34b Revert "Consider all variables in the #4095 fix, not just locals."
This reverts commit d2b5f2fdc9.

This doesn't seem like the right fix, as it has the potential to
cause memory leaks. I'm going to compat the old behavior, and
require it to be explicit.
2023-03-26 09:59:41 -04:00
Tom Rothamel cfe892405e sl2: Allow at transform outside of a block.
This makes for a more compact syntax.
2023-03-26 09:58:52 -04:00
Tom Rothamel d2b5f2fdc9 Consider all variables in the #4095 fix, not just locals. 2023-03-26 03:42:41 -04:00
Tom Rothamel 8c4387bae4 Merge pull request #4486 from KiloOscarSix/master
Fixed incorrect behaviour for ToggleDict.get_sensitive()
2023-03-26 03:05:22 -04:00
Tom Rothamel bfb3955e9e Merge pull request #4483 from Kassy2048/touch_alternate_fix
Cancel long press wait when user stops touching the screen
2023-03-26 03:01:24 -04:00
Oscar Six 1498f625df Fixed incorrect behaviour for ToggleDict.get_sensitive() when using lists 2023-03-26 05:25:39 +01:00
Kassy 9523222b37 Only check for mouseup when long press is being detected 2023-03-26 01:39:27 +01:00
Kassy 9c197abca3 Cancel long press wait when user stops touching the screen 2023-03-26 00:26:00 +01:00
Tom Rothamel d3dbc40749 accelerator: Cythionify, add typing information, and document. 2023-03-25 12:25:12 -04:00
Tom Rothamel 6362ad8550 quaternion: Add cython types. 2023-03-25 10:56:07 -04:00
Tom Rothamel 04a73d2ad4 Remove old theme. 2023-03-25 10:30:54 -04:00
Tom Rothamel ddfcf67486 Make the total texture size a Python object.
(Preventing a crash if we get the calculation wrong.)
2023-03-25 10:29:49 -04:00
Tom Rothamel 8f5a32deeb Merge pull request #4477 from kyouryuukunn/fix_README
add README sphinx_rtd_theme sphinx_rtd_dark_mode
2023-03-25 10:24:55 -04:00
kyouryuukunn 5155c08f54 add README sphinx_rtd_theme sphinx_rtd_dark_mode 2023-03-25 19:09:57 +09:00
Tom Rothamel 7511c3ff42 Move quaternion code into its own pyx file.
This is a combination of it seeming like something meaty enough
that it might grow into more operations, and this taking 5s
for clang to compile.
2023-03-25 02:50:54 -04:00
Tom Rothamel e4ff027499 accelerator: Break up transform_render fully.
Break the ridiculously long transform_render function into a
number of smaller methods in a class, and use fields on that
class to store data used by multiple methods.

This is mostly to speed up compilation time by reducing how much
work clang has to do to compile things.

This is a work in progress - it's not well tested, and typing
information is missing or commented out. But it's enough to
render basic displayables.
2023-03-25 02:32:27 -04:00
Tom Rothamel 1cccdbdaaf Create a class to render transforms.
Basically, to store the variables that had been updated by
the crazy-big transform_render function.
2023-03-24 23:15:15 -04:00
Tom Rothamel 1a100a0006 accelerator: Begin to break up transform_render. 2023-03-24 03:04:19 -04:00
Tom Rothamel 2863bb2d88 live2d: Sort the properties to ensure a stable order. 2023-03-24 01:00:58 -04:00
Tom Rothamel 14f653f0a5 Attributes can't be required and optional.
Fixes #4466.
2023-03-24 01:00:58 -04:00
Gouvernathor 93fe44fa94 Fix wrong SetField use
It does take a fourth parameter, kind, which is private and only used for error messages when the target is not found/writable. It takes a string, so this code doesn't make sense.
My mistake, btw.
2023-03-23 19:40:15 +01:00
Tom Rothamel e73c750ef1 launcher: Make selected text more obvious.
Fixes #4471.
2023-03-22 23:00:05 -04:00
Tom Rothamel b929fa838c Apply pixel_perfect to renders rather than models.
This means that all the models that are underneath the render will be
adjusted together, so that relative blits all wind up in the same
place.

This is mostly the case for text, where things like centering, slow
text, and outlines could cause text to appear in different locations, and then be
drawn separately - only to jump into place when slow text finished and
the Text was drawn as a whole.

Fixes #4103.
2023-03-22 22:53:38 -04:00
Mal Graty d25bd3c01f Fix orientation interpolation
Ended up with an erroneous indent after the recent point_to changes.
2023-03-22 14:46:35 +00:00
Gouvernathor 1a9870ad5a doc: fix wrong role 2023-03-22 14:26:49 +01:00
Tom Rothamel fbbc8e0e34 Replace point_to True with point_to Camera()
The latter seems like a more readable syntax to me, as True doesn't
really feel associated with the camera. (And this provides a place
to store the layer.)
2023-03-22 02:45:01 -04:00
Tom Rothamel cc77af4d9f Move the point_to logic from ATL to Transform.
This is for two reasons:

* It allows point_to True to work in Transforms, at least if we figure
  out a way to refresh Transforms when the camera changes.

* It gets things right if the camera changes position while not
  interpolating point_to.
2023-03-22 02:11:01 -04:00
Tom Rothamel 65ff148898 Merge pull request #4449 from kyouryuukunn/allow_poi_tag_and_camera3
allow point_to to take camera
2023-03-21 22:01:01 -04:00
Tom Rothamel 24ad7c9a01 Merge pull request #4464 from renpy/fix-im-always-doc
Python equivalent of layeredimage's Always layer
2023-03-21 21:58:41 -04:00
Gouvernathor 6ce6a877a5 doc: Python equivalent of layeredimage's Always layer 2023-03-21 20:30:19 +01:00
Tom Rothamel 65bceeddba End ongoing transitions after rollback.
Fixes #4105.
2023-03-21 02:19:05 -04:00
Tom Rothamel 77cb945b9f On Android and iOS, log the Ren'Py version to stdout. 2023-03-20 22:26:50 -04:00
Tom Rothamel eab3d2dab4 Fix how None interacts with the pressed mouse state. 2023-03-20 20:17:59 -04:00
kyouryuukunn 4e3d66b975 remove tag from point_to 2023-03-20 17:36:24 +09:00
Tom Rothamel a192a352e1 sl2: Allow both 'at' and 'at transform' to be given to the same statement.
Fixes #4455.
2023-03-19 23:55:17 -04:00
kyouryuukunn c4e9a64b1e simplify codes around poi 2023-03-20 12:31:09 +09:00
Tom Rothamel 533cbe10e3 gamepad: Process more than one analog axis motion per frame.
Fixes #4178, more or less. This certainly makes the controller more
responsive, though perhaps not as good as desktop.

This also gains a bit of performance by reducing how far the
controller needs to deflect, and introdces hysteresis to
prevent spurious controller events.
2023-03-19 15:44:59 -04:00
Tom Rothamel c492f2f8e6 atl: Only compare contexts based on used variables.
This is useful in screens, where a transform might have a context
contains many variables that are not used by the transform. The
transform shouldn't restart when an irrelevant variable is
changed.

Fixes #4095.
2023-03-19 14:20:38 -04:00
Tom Rothamel 9249c6ffd4 sl2, atl: Compile ATL transforms in screens.
These transforms weren't being compiled before the game started.
2023-03-19 14:20:38 -04:00
kyouryuukunn 98609fe72f remove 2-tuple format from point_to 2023-03-19 13:14:50 +09:00
kyouryuukunn 03bf276e15 fix 2023-03-19 12:44:50 +09:00
Tom Rothamel d52fa74ffd Merge pull request #4454 from valery-iwanofu/input_multiline
Multiline support for input
2023-03-18 12:00:13 -04:00
valery.iwanofu 15d2e037fe Multiline support for input 2023-03-18 21:09:57 +07:00
Tom Rothamel 2da6f8cab0 Avoid cache.get keeping the surface in memory when returning a texture. 2023-03-18 01:08:18 -04:00
Tom Rothamel 8c331b336e gl2: More accurate accounting of texture memory.
This adds a 1.34x multiplier to textures that have mipmaps associated
with them, and only indicates that a texture is consuming memory when
it's been transferred to the GPU.
2023-03-18 00:54:37 -04:00
Tom Rothamel 8e90d2f6ab Add a 1.34x multiplier to account for mipmapping.
Increase the default image cache size to match. This won't actually
increase the amount of memory used by the image cache, just
makes the setting match the memory being used.
2023-03-17 22:27:32 -04:00
kyouryuukunn 01c9e137e0 consider camera anchor when calculating point_to camera 2023-03-17 23:01:30 +09:00
kyouryuukunn 47418aa92d allow point_to to take "tag" or camera 2023-03-17 14:35:16 +09:00
Tom Rothamel c998b456a1 Merge pull request #4447 from renpy/point-to
Rename poi to point_to
2023-03-16 18:46:07 -04:00
Gouvernathor bb34b38432 Update renpy/display/accelerator.pyx
Co-authored-by: Mal Graty <mal.graty@googlemail.com>
2023-03-16 12:50:55 +01:00
Tom Rothamel ca1871863b Only close fds if stdout/stderr/stdin are not being used.
Fixes #4445.
2023-03-16 01:17:42 -04:00
Gouvernathor 9475a0f4b8 Better phrased error message 2023-03-16 05:26:07 +01:00
Gouvernathor 606a196e52 Rename poi to point_to (and simplify code) 2023-03-16 05:18:27 +01:00
Gouvernathor bcfdb73715 Simplify and optimize some code 2023-03-16 05:07:10 +01:00
Tom Rothamel f8673861aa Go back to the default colorspace, but with the full JPEG range.
Per #4263.
2023-03-15 22:56:07 -04:00
Tom Rothamel 1947db8acd Merge pull request #4444 from mal/improve-savedump
Improve savedump
2023-03-15 22:10:55 -04:00
Tom Rothamel 260b8f5f8d Merge pull request #4443 from mal/fix-tint-matrix
Fix TintMatrix to respect alpha
2023-03-15 21:25:17 -04:00
Mal Graty e5f58fe935 savedump: Add function support 2023-03-15 18:38:32 +00:00
Mal Graty cfee63b880 savedump: Keep pickle protocol in-sync 2023-03-15 18:21:58 +00:00
Mal Graty ee817a32f4 savedump: Write warning into cache
Previously it would never have been used.
2023-03-15 17:45:49 +00:00
Mal Graty 8039704b0d savedump: Improve representation of singletons 2023-03-15 17:45:11 +00:00
Mal Graty c0ff662d60 matrixcolor: Fix TintMatrix to respect alpha
Per the documentation, the alpha shouldn't ever have been impacted.

This change instead treats the alpha in the supplied RGBA value as the
tint opacity rather than applying the tint formula to the alpha channel.
The result is the ability to use a colour value of #FF000080 to apply
a 50%-red tint to the target.
2023-03-15 17:23:05 +00:00
Tom Rothamel bfd6f2c9a8 Clear a deferred side image if a say statement runs.
Fixes #4438.
2023-03-15 02:27:27 -04:00
Tom Rothamel faa9155c88 More work on #4263.
Fundamentally, the problem is that ffmpeg's yuv->rgb conversion is
inaccurate, unless it's given flags that cause a ~12x slowdown -
SWS_ACCURATE_RND or SWS_ERROR_DIFFUSION.

That's not tolerable, so we do enable SWS_FULL_CHR_H_INT and
SWS_FULL_CHR_H_INP, which seem to improve things somewhat, without
slowing down conversion by much.

We now also default to the ITU 709 colorspace, which is the default
for HD video.
2023-03-14 23:18:15 -04:00
Tom Rothamel 444d002304 Revert "ffmedia: Convert to YUV444, then RGB."
This reverts commit a516bda700.

This conversion was too slow for practical use.
2023-03-14 23:13:32 -04:00
Tom Rothamel 2f56256a82 doc: Renpy.redraw is documented in cdd.rst. 2023-03-14 18:48:04 -04:00
Tom Rothamel 16cba5ef11 Remove debug print. 2023-03-14 01:29:46 -04:00
Tom Rothamel 53b9879a7a Limit the size of the icon to 1024x1024.
A 2048x2048 icon causes an X11 crash.
2023-03-14 00:37:11 -04:00
Tom Rothamel a628b2dba3 Document that being redrawn cancels redraw. 2023-03-13 23:51:21 -04:00
Tom Rothamel e45a3b18ae Separate angle and radius in ATL.
This allows angle and radius to be computed in parallel.

Fixes #4118.
2023-03-13 22:05:02 -04:00
Tom Rothamel a3b8f75a8d Avoid reverting the slast serial number.
This prevents slast nodes from getting the same serial number
after a shift+R reload.
2023-03-13 00:10:57 -04:00
Tom Rothamel a516bda700 ffmedia: Convert to YUV444, then RGB.
This works around some issues, reported in #4263, where going
from YUV420 to RGB directly produces artifacts. The two-step
conversion process eliminates these artifacts.
2023-03-12 18:29:34 -04:00
Tom Rothamel 44e23a9665 Use bilinear interpolation when resampling video.
This should only kick in when resampling movies that are
chroma subsampled, as the luma should always be full size.

Fixes #4263
2023-03-12 00:02:19 -05:00
Tom Rothamel 45e5535e89 Allow greedy rollback to the statement after renpy.block_rollback.
Previously, it woudn't proceed past the next checkpoint after
renpy.block_rollback, which could be several statements away.

Fixes #4138.
2023-03-11 23:08:11 -05:00
Gouvernathor 652a06c1a8 doc: remove obsolete name-munging in f-strings warning 2023-03-12 02:31:12 +01:00
Tom Rothamel e48cd58b2c Add config.file_slotname_callback and config.autosave_prefix_callback.
The former allows a game to customize the mapping from page and name
(used by the File actions) to slot (used by the save functions).

The latter lets the names of the autosaves Ren'Py generates be
customized to match.

Fixes #4067.
2023-03-11 20:27:03 -05:00
Tom Rothamel 8f2c27979a doc: Changelog f-string variable munging. 2023-03-11 19:21:21 -05:00
Tom Rothamel 0d7e5ec9e5 Munge variables in f-strings beginning with __.
As per the rest of Ren'Py. Rather than parsing f-strings ourselves,
this is done as part of the Python compiler.

Fixes #4084.
2023-03-11 18:14:11 -05:00
Tom Rothamel d89c89f368 Munge a _ren.py file the same as the .rpy file it replaces. 2023-03-11 17:44:01 -05:00
Tom Rothamel ec48202e27 Use equality when comparing InputValues.
This is to match the way equality is used in sl2ast to avoid
updating the displayables.

This could be wrong if two displayables use the same (or equal)
InputValues, but that's likely a rare situation.

Fixes #4042.
2023-03-11 16:45:50 -05:00
Tom Rothamel 245576331b Duplicate the control argument of AlphaDissolve.
So that the transform that's being used doesn't run.

Fixes #4435.
2023-03-10 22:31:18 -05:00
Tom Rothamel d426c80761 doc: Fix typo. 2023-03-10 09:14:10 -05:00
Tom Rothamel 303dadf377 Drag improvements.
* Apply displayable before calling per_interact, fixing set_child.
* In the examples, show the use of add rather than child, as the
  style property is for specialized use.

Fixes #4432
2023-03-09 23:55:58 -05:00
Tom Rothamel f50be5712d Merge pull request #4434 from via-my/master
Logical mistake
2023-03-09 20:56:59 -05:00
LaUwUrence ea76473e81 Logical mistake 2023-03-10 00:32:46 +04:00
Tom Rothamel 6662238338 Merge pull request #4428 from Kassy2048/webloader_voice
Remove useless double iteration
2023-03-09 00:14:32 -05:00
Tom Rothamel c33f5c4d3d Force the browser out of fullscreen on input.
Fixes #4429.
2023-03-08 19:14:29 -05:00
Kassy 54fb3d9be8 Remove useless double iteration 2023-03-08 13:50:26 +01:00
Tom Rothamel 5c0f86c19e Update Android strings. 2023-03-08 02:28:58 -05:00
Gouvernathor 1e5c227909 Say statement equivalents tweaks and refactor (#4294)
* doc: tweak and warn about say statement equivalents

* doc: move things around about say statement equivalents

Drain the part about it from the dialogue page
Add a link from there to the statement equivalents page
Add the warning about skipping seen dialogue
Incorporate other explanations, examples and the dialogue window management equivalents

* doc: add more advice about use of the character store

* Remove out-of-place explanation

* Clarify, reorganize, readd interact info with more

* Move the interact override hierarchy around

but leave the interact being passed automatically in the call in the statement equivalents page

* More information about say_arguments_callback

* Add example for say_arguments_callback

fixes #4319
2023-03-08 02:16:48 -05:00
Tom Rothamel 6c8996207b Remove an isinstance check in per_interact().
By moving it into the Layer displayable, we can save the call.
2023-03-08 00:50:02 -05:00
Tom Rothamel cd8b496876 Visit the image child of a Frame.
Fixes #4005.
2023-03-07 23:05:17 -05:00
Tom Rothamel 2379d75173 py2: Default subprocess.Popen's close_fds to True.
This matches the behavior change in Python 3.

Fixes #4425.
2023-03-07 20:31:13 -05:00
119 changed files with 2857 additions and 1158 deletions
+1 -1
View File
@@ -112,7 +112,7 @@ link in a nightly build, or compile the modules as described above. You'll
also need the `Sphinx <https://www.sphinx-doc.org>`_ documentation generator.
If you have pip working, install Sphinx using::
pip install -U sphinx sphinx-bootstrap-theme
pip install -U sphinx sphinx_rtd_theme sphinx_rtd_dark_mode
Once Sphinx is installed, change into the ``sphinx`` directory inside the
Ren'Py checkout and run::
-18
View File
@@ -1,18 +0,0 @@
{
"start_a170b500": {
"area": [
53,
120,
426,
120
]
},
"start_f41f55d7": {
"area": [
106,
60,
426,
120
]
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 6.7 KiB

+39 -12
View File
@@ -1409,12 +1409,12 @@ style nvl_button_text:
## Bubble screen ###############################################################
##
## The Bubble screen is used to display dialogue to the player when using
## speech bubbles. The Bubble screen takes the same parameters as the say
## The bubble screen is used to display dialogue to the player when using
## speech bubbles. The bubble screen takes the same parameters as the say
## screen, must create a displayable with the id of "what", and can create
## displayables with the "namebox", "who", and "window" ids.
##
## https://www.renpy.org/doc/html/screen_special.html#bubble
## https://www.renpy.org/doc/html/bubble.html#bubble-screen
screen bubble(who, what):
style_prefix "bubble"
@@ -1434,22 +1434,18 @@ screen bubble(who, what):
text what:
id "what"
style bubble_window is empty
style bubble_namebox is empty
style bubble_who is default
style bubble_what is default
style bubble_window:
background Frame("gui/bubble.png", 15, 15, 15, 15)
padding (15, 10)
xpadding 30
top_padding 5
bottom_padding 5
style bubble_namebox:
background Frame("gui/bubble.png", 15, 15, 15, 15)
pos (-15, -5)
anchor (0.0, 1.0)
padding (15, 10)
xalign 0.5
style bubble_who:
xalign 0.5
@@ -1458,10 +1454,41 @@ style bubble_who:
style bubble_what:
align (0.5, 0.5)
textalign 0.5
text_align 0.5
layout "subtitle"
color "#000"
define bubble.frame = Frame("gui/bubble.png", 55, 55, 55, 95)
define bubble.properties = {
"bottom_left" : {
"window_background" : Transform(bubble.frame, xzoom=1, yzoom=1),
"window_bottom_padding" : 27,
},
"bottom_right" : {
"window_background" : Transform(bubble.frame, xzoom=-1, yzoom=1),
"window_bottom_padding" : 27,
},
"top_left" : {
"window_background" : Transform(bubble.frame, xzoom=1, yzoom=-1),
"window_top_padding" : 27,
},
"top_right" : {
"window_background" : Transform(bubble.frame, xzoom=-1, yzoom=-1),
"window_top_padding" : 27,
},
}
define bubble.expand_area = {
"bottom_left" : (0, 0, 0, 22),
"bottom_right" : (0, 0, 0, 22),
"top_left" : (0, 22, 0, 0),
"top_right" : (0, 22, 0, 0),
}
################################################################################
+3 -1
View File
@@ -31,8 +31,10 @@ init python:
ANDROID_NO_BUNDLE = 6
ANDROID_OK = 7
JDK_REQUIREMENT=8
NO_RAPT_TEXT = _("To build Android packages, please download RAPT, unzip it, and place it into the Ren'Py directory. Then restart the Ren'Py launcher.")
NO_JDK_TEXT = _("A 64-bit/x64 Java 8 Development Kit is required to build Android packages on Windows. The JDK is different from the JRE, so it's possible you have Java without having the JDK.\n\nPlease {a=https://adoptopenjdk.net/?variant=openjdk8&jvmVariant=hotspot}download and install the JDK{/a}, then restart the Ren'Py launcher.")
NO_JDK_TEXT = _("A 64-bit/x64 Java [JDK_REQUIREMENT] Development Kit is required to build Android packages on Windows. The JDK is different from the JRE, so it's possible you have Java without having the JDK.\n\nPlease {a=https://www.renpy.org/jdk/[JDK_REQUIREMENT]}download and install the JDK{/a}, then restart the Ren'Py launcher.")
NO_SDK_TEXT = _("RAPT has been installed, but you'll need to install the Android SDK before you can build Android packages. Choose Install SDK to do this.")
NO_KEY_TEXT = _("RAPT has been installed, but a key hasn't been configured. Please generate new keys, or copy android.keystore and bundle.keystore to the base directory.")
NO_CONFIG_TEXT = _("The current project has not been configured. Use \"Configure\" to configure it before building.")
+3 -2
View File
@@ -1,4 +1,4 @@
# This file contains strings used by RAPT, so the Ren'Py translation framework
# can find them. It's automatically generated by rapt/update_translations.py, and
# hence should not be changed by hand.
@@ -63,11 +63,12 @@ init python hide:
__("I found an android.keystore file in the rapt directory. Do you want to use this file?")
__("I can create an application signing key for you. This key is required to create Universal APK for sideloading and stores other than Google Play.\n\nDo you want to create a key?")
__("I will create the key in the android.keystore file.\n\nYou need to back this file up. If you lose it, you will not be able to upgrade your application.\n\nYou also need to keep the key safe. If evil people get this file, they could make fake versions of your application, and potentially steal your users' data.\n\nWill you make a backup of android.keystore, and keep it in a safe place?")
__("\n\nSaying 'No' will prevent key creation.")
__("Could not create android.keystore. Is keytool in your path?")
__("I've finished creating android.keystore. Please back it up, and keep it in a safe place.")
__("I found a bundle.keystore file in the rapt directory. Do you want to use this file?")
__("I can create a bundle signing key for you. This key is required to build an Android App Bundle (AAB) for upload to Google Play.\n\nDo you want to create a key?")
__("I will create the key in the bundle.keystore file.\n\nYou need to back this file up. If you lose it, you will not be able to upgrade your application.\n\nYou also need to keep the key safe. If evil people get this file, they could make fake versions of your application, and potentially steal your users' data.\n\nWill you make a backup of bundle.keystore, and keep it in a safe place?")
__("\n\nSaying 'No' will prevent key creation.")
__("Could not create bundle.keystore. Is keytool in your path?")
__("I've opened the directory containing android.keystore and bundle.keystore. Please back them up, and keep them in a safe place.")
__("It looks like you're ready to start packaging games.")
+2 -1
View File
@@ -214,7 +214,8 @@ class CodeGenerator(object):
'gui.insensitive_color' : repr(self.p.insensitive_color.hexcode),
'gui.text_color' : repr(self.p.text_color.hexcode),
'gui.interface_text_color' : repr(self.p.text_color.hexcode),
'gui.choice_text_color' : repr(self.p.choice_color.hexcode),
'gui.choice_button_text_idle_color' : repr(self.p.idle_color.hexcode),
'gui.choice_button_text_insensitive_color' : repr(self.p.insensitive_color.hexcode),
}
self.update_defines(replacements, language_defines[self.p.language])
+2 -2
View File
@@ -67,8 +67,8 @@ class GuiParameters(object):
if light:
self.selected_color = Color("#555555")
self.idle_color = Color("#aaaaaa")
self.idle_small_color = Color("#888888")
self.idle_color = Color("#707070")
self.idle_small_color = Color("#606060")
self.text_color = Color("#404040")
self.choice_color = Color("#cccccc")
+9 -1
View File
@@ -200,7 +200,15 @@ screen common:
add SPACER
for v, l in choices:
textbutton l action SetScreenVariable("selected", v)
textbutton l:
action SetScreenVariable("selected", v)
selected_background REVERSE_IDLE
selected_hover_background REVERSE_HOVER
xpadding 20
size_group "choice"
text_selected_idle_color REVERSE_TEXT
text_selected_hover_color REVERSE_TEXT
text_xalign 0.5
if selected is not None:
$ continue_ = Return(selected)
+4 -22
View File
@@ -97,32 +97,14 @@ label itch:
files = [ ]
for fn in os.listdir(destination):
fn = os.path.join(destination, fn)
if fn.endswith("-all.zip"):
channel = "win-osx-linux"
elif fn.endswith("-market.zip"):
channel = "win-osx-linux"
elif fn.endswith("-pc.zip"):
channel = "win-linux"
elif fn.endswith("-win.zip"):
channel = "win"
elif fn.endswith("-mac.zip"):
channel = "osx"
elif fn.endswith("-linux.tar.bz2"):
channel = "linux"
elif fn.endswith("-release.apk"):
channel = "android"
for pattern, channel in reversed(build['itch_channels']):
if fnmatch.fnmatch(fn, pattern):
break
else:
continue
fn = os.path.join(destination, fn)
files.append((fn, channel))
if not os.path.exists(destination):
+3
View File
@@ -449,3 +449,6 @@ python early:
# Since the launcher can be run directly or can be run from the SDK directory,
# uneliding files needs to be handled slightly differently.
define config.alternate_unelide_path = os.path.join(config.basedir, "launcher")
# Disable skipping.
define config.allow_skipping = False
+2 -2
View File
@@ -248,8 +248,8 @@ screen preferences():
add HALF_SPACER
textbutton _("Default theme") style "l_checkbox" action [SetField(persistent, "theme", None), RestartAtPreferences() ]
textbutton _("Dark theme") style "l_checkbox" action [SetField(persistent, "theme", "dark", None), RestartAtPreferences()]
textbutton _("Custom theme") style "l_checkbox" action [SetField(persistent, "theme", "custom", None), RestartAtPreferences()]
textbutton _("Dark theme") style "l_checkbox" action [SetField(persistent, "theme", "dark"), RestartAtPreferences()]
textbutton _("Custom theme") style "l_checkbox" action [SetField(persistent, "theme", "custom"), RestartAtPreferences()]
add SPACER
+8
View File
@@ -897,3 +897,11 @@ translate french strings:
# renpy/common/00webvideo.rpy:5
old "Touch to play the video."
new "Touchez pour jouer la vidéo."
# renpy/common/00gui.rpy:456
old "This save was created on a different device. Maliciously constructed save files can harm your computer. Do you trust this save's creator and everyone who could have changed the file?"
new "Cette sauvegarde a été créée sur un autre appareil. Des faux fichiers de sauvegarde peuvent être utilisés pour endommager votre ordinateur. Faites-vous confiance au créateur de cette sauvegarde et à tous ceux qui auraient pu modifier le fichier ?"
# renpy/common/00gui.rpy:457
old "Do you trust the device the save was created on? You should only choose yes if you are the device's sole user."
new "Faites-vous confiance à l'appareil sur lequel cette sauvegarde a été créée ? Vous ne devriez dire oui que si vous en êtes l'unique utilisateur ."
+1 -1
View File
@@ -78,7 +78,7 @@
# _developer/developer.rpym:532
old "\n{color=#cfc}✔ predicted image (good){/color}\n{color=#fcc}✘ unpredicted image (bad){/color}\n{color=#fff}Drag to move.{/color}"
new "\n{color=#cfc}✔ image prédite (bon){/color}\n{color=#fcc}✘ image prédite (mauvais){/color}\n{color=#fff}Glissez pour bouger.{/color}"
new "\n{color=#cfc}✔ image prédite (bien){/color}\n{color=#fcc}✘ image non prédite (pas bien){/color}\n{color=#fff}Glissez pour déplacer.{/color}"
# _developer/inspector.rpym:38
old "Displayable Inspector"
+4
View File
@@ -455,3 +455,7 @@
# gui/game/gui.rpy:441
old "## Change the size and spacing of various things."
new "## Changer la taille et l'espacement de diverses choses."
# gui/game/gui.rpy:14
old "## Enable checks for invalid or unstable properties in screens or transforms"
new "## Active la vérification de propriétés invalides ou instables dans les screens et transforms"
+49 -5
View File
@@ -41,7 +41,7 @@
# android.rpy:32
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 a été installé, mais vous devez installer le kit de développement Android pour pouvoir compiler les paquets Android. Choisissez « installer le kit de développement et créer les clés » pour cela."
new "RAPT a été installé, mais vous devez installer le kit de développement Android pour pouvoir compiler les paquets Android, avec « Installer le SDK »."
# android.rpy:33
old "RAPT has been installed, but a key hasn't been configured. Please create a new key, or restore android.keystore."
@@ -65,11 +65,11 @@
# android.rpy:39
old "Attempts to emulate a televison-based Android console, like the OUYA or Fire TV.\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 "Tentative d’émulation d'une console télé basée sur Android, comme OUYA ou Fire TV.\n\n Le contrôleur est émulé par les touches fléchées, le bouton select par la touche Entrée, le bouton menu par la touche Echap, et le bouton retour par la touche PageUp."
new "Tentative d’émulation d'une console télé basée sur Android, comme OUYA ou Fire TV.\n\nLa manette est émulée par les touches fléchées, le bouton select par la touche Entrée, le bouton menu par la touche Echap, et le bouton retour par la touche PageUp."
# android.rpy:41
old "Downloads and installs the Android SDK and supporting packages. Optionally, generates the keys required to sign the package."
new "Télécharge et installe le kit de développement Android et les paquets supportés. Optionnellement, génère les clés requises pour signer le paquet."
new "Télécharge et installe le kit de développement Android et les paquets requis. Optionnellement, génère les clés requises pour signer le paquet."
# android.rpy:42
old "Configures the package name, version, and other information about this project."
@@ -1192,8 +1192,8 @@
new "Le nom de fichier ne peut pas être vide."
# game/android.rpy:31
old "A 64-bit/x64 Java 8 Development Kit is required to build Android packages on Windows. The JDK is different from the JRE, so it's possible you have Java without having the JDK.\n\nPlease {a=https://adoptopenjdk.net/?variant=openjdk8&jvmVariant=hotspot}download and install the JDK{/a}, then restart the Ren'Py launcher."
new "Un kit de développement Java 8 64-bit/x64 est requis pour construire des paquets Android depuis Windows. Le JDK est différent du JRE, donc il est possible d'avoir Java sans avoir le JDK.\n\n{a=https://adoptopenjdk.net/?variant=openjdk8&jvmVariant=hotspot}Téléchargez et installez le JDK{/a}, puis redémarrez Ren'Py."
old "A 64-bit/x64 Java [JDK_REQUIREMENT] Development Kit is required to build Android packages on Windows. The JDK is different from the JRE, so it's possible you have Java without having the JDK.\n\nPlease {a=https://www.renpy.org/jdk/[JDK_REQUIREMENT]}download and install the JDK{/a}, then restart the Ren'Py launcher."
new "Un kit de développement Java [JDK_REQUIREMENT] 64-bit/x64 est requis pour construire des paquets Android depuis Windows. Le JDK est différent du JRE, donc il est possible d'avoir Java sans avoir le JDK.\n\n{a=https://www.renpy.org/jdk/[JDK_REQUIREMENT]}Téléchargez et installez le JDK{/a}, puis redémarrez Ren'Py."
# game/android.rpy:50
old "Selects the Debug build, which can be accessed through Android Studio. Changing between debug and release builds requires an uninstall from your device."
@@ -2086,3 +2086,47 @@
# game/web.rpy:484
old "Creating package..."
new "Création du package..."
# game/android.rpy:37
old "RAPT has been installed, but a key hasn't been configured. Please generate new keys, or copy android.keystore and bundle.keystore to the base directory."
new "RAPT a été installé, mais aucune clé na été configurée. Veuillez générer de nouvelles clés, ou copier android.keystore and bundle.keystore dans le dossier racine du projet."
# game/android.rpy:44
old "Attempts to emulate a televison-based Android console.\n\nController input is mapped to the arrow keys, Enter is mapped to the select button, Escape is mapped to the menu button, and PageUp is mapped to the back button."
new "Tentative d’émulation d'une console télé basée sur Android.\n\nLa manette est émulée par les touches fléchées, le bouton select par la touche Entrée, le bouton menu par la touche Echap, et le bouton retour par la touche PageUp."
# game/android.rpy:46
old "Downloads and installs the Android SDK and supporting packages."
new "Télécharge et installe le kit de développement Android et les paquets requis."
# game/android.rpy:47
old "Generates the keys required to sign the package."
new "Génère les clés requises pour signer le package."
# game/android.rpy:381
old "Install SDK"
new "Installer le SDK"
# game/android.rpy:385
old "Generate Keys"
new "Générer les clés"
# game/androidstrings.rpy:32
old "How much RAM (in GB) do you want to allocate to Gradle?\nThis must be a positive integer number."
new "Combien de RAM (in GB) voulez-vous allouer à Gradle ?\nEntrez un nombre entier positif."
# game/androidstrings.rpy:33
old "The RAM size must contain only numbers and be positive."
new "La taille de RAM ne doit contenir que des chiffres et être positive."
# game/androidstrings.rpy:63
old "I found an android.keystore file in the rapt directory. Do you want to use this file?"
new "J'ai trouvé un fichier android.keystore dans le dossier \"rapt\". Voulez-vous l'utiliser ?"
# game/androidstrings.rpy:66
old "\n\nSaying 'No' will prevent key creation."
new "\n\nRépondre \"Non\" empêchera la création de clés."
# game/androidstrings.rpy:69
old "I found a bundle.keystore file in the rapt directory. Do you want to use this file?"
new "J'ai trouvé un fichier bundle.keystore dans le dossier \"rapt\". Voulez-vous l'utiliser ?"
+8
View File
@@ -731,3 +731,11 @@
# gui/game/screens.rpy:1404
old "## https://www.renpy.org/doc/html/screen_special.html#bubble"
new "## https://www.renpy.org/doc/html/screen_special.html#bubble"
# gui/game/screens.rpy:676
old "Upload Sync"
new "Uploader Sync"
# gui/game/screens.rpy:680
old "Download Sync"
new "Télécharger Sync"
+2 -5
View File
@@ -1199,15 +1199,13 @@
old "Proceed"
new "Fortsetzen"
translate german strings:
# game/add_file.rpy:37
old "The file name may not be empty."
new "Der Dateiname darf nicht leer sein."
# game/android.rpy:35
old "A 64-bit/x64 Java 8 Development Kit is required to build Android packages on Windows. The JDK is different from the JRE, so it's possible you have Java without having the JDK.\n\nPlease {a=https://adoptopenjdk.net/?variant=openjdk8&jvmVariant=hotspot}download and install the JDK{/a}, then restart the Ren'Py launcher."
new "A 64-bit/x64 Java 8 Development Kit is required to build Android packages on Windows. The JDK is different from the JRE, so it's possible you have Java without having the JDK.\n\nPlease {a=https://adoptopenjdk.net/?variant=openjdk8&jvmVariant=hotspot}download and install the JDK{/a}, then restart the Ren'Py launcher."
old "A 64-bit/x64 Java [JDK_REQUIREMENT] Development Kit is required to build Android packages on Windows. The JDK is different from the JRE, so it's possible you have Java without having the JDK.\n\nPlease {a=https://www.renpy.org/jdk/[JDK_REQUIREMENT]}download and install the JDK{/a}, then restart the Ren'Py launcher."
new "A 64-bit/x64 Java [JDK_REQUIREMENT] Development Kit is required to build Android packages on Windows. The JDK is different from the JRE, so it's possible you have Java without having the JDK.\n\nPlease {a=https://www.renpy.org/jdk/[JDK_REQUIREMENT]}download and install the JDK{/a}, then restart the Ren'Py launcher."
# game/android.rpy:38
old "RAPT has been installed, but a bundle key hasn't been configured. Please create a new key, or restore bundle.keystore."
@@ -1932,4 +1930,3 @@ translate german strings:
# game/web.rpy:348
old "Before packaging web apps, you'll need to download RenPyWeb, Ren'Py's web support. Would you like to download RenPyWeb now?"
new "Before packaging web apps, you'll need to download RenPyWeb, Ren'Py's web support. Would you like to download RenPyWeb now?"
+2 -3
View File
@@ -1320,8 +1320,8 @@
new "ファイル名が与えられていません。"
# game/android.rpy:31
old "A 64-bit/x64 Java 8 Development Kit is required to build Android packages on Windows. The JDK is different from the JRE, so it's possible you have Java without having the JDK.\n\nPlease {a=http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html}download and install the JDK{/a}, then restart the Ren'Py launcher."
new "ウィンドウズでアンドロイドパッケージをビルドするには、 64-bit/x64 Java 8 Development Kit が必要になります。 JDK は JRE とは違うもので、Java には含まれていません。\n\n{a=http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html}JDK{/a}をダンロード、インストールしてから Ren'Py ランチャーを再起動してください。"
old "A 64-bit/x64 Java [JDK_REQUIREMENT] Development Kit is required to build Android packages on Windows. The JDK is different from the JRE, so it's possible you have Java without having the JDK.\n\nPlease {a=https://www.renpy.org/jdk/[JDK_REQUIREMENT]}download and install the JDK{/a}, then restart the Ren'Py launcher."
new "ウィンドウズでアンドロイドパッケージをビルドするには、 64-bit/x64 Java 8 Development Kit が必要になります。 JDK は JRE とは違うもので、Java には含まれていません。\n\n{a=https://www.renpy.org/jdk/[JDK_REQUIREMENT]}JDK{/a}をダンロード、インストールしてから Ren'Py ランチャーを再起動してください。"
# game/android.rpy:50
old "Selects the Debug build, which can be accessed through Android Studio. Changing between debug and release builds requires an uninstall from your device."
@@ -2094,4 +2094,3 @@
# game/web.rpy:344
old "We will restore support in a future release of Ren'Py 8. Until then, please use Ren'Py 7 for web support."
new "Ren'Py 8 の将来のリリースでサポートを復活させます。それまではRen'Py 7 をウェブプラットフォームに使用ください。"
+2 -2
View File
@@ -36,8 +36,8 @@
new "안드로이드 패키지를 만드려면, RAPT 파일을 내려받고 렌파이 디렉터리 안에 압축 해제하세요. 그다음 렌파이를 재시작하세요."
# game/android.rpy:31
old "A 64-bit/x64 Java 8 Development Kit is required to build Android packages on Windows. The JDK is different from the JRE, so it's possible you have Java without having the JDK.\n\nPlease {a=http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html}download and install the JDK{/a}, then restart the Ren'Py launcher."
new "윈도우에서 안드로이드 패키지를 만드려면 64비트 JDK가 필요합니다. JDK는 JRE와 다르므로 PC에 JDK가 없는 자바가 설치되어 있을 수 있습니다.\n\n{a=http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html}JDK를 내려받아 설치한 뒤{/a}, 렌파이를 재시작해주세요."
old "A 64-bit/x64 Java [JDK_REQUIREMENT] Development Kit is required to build Android packages on Windows. The JDK is different from the JRE, so it's possible you have Java without having the JDK.\n\nPlease {a=https://www.renpy.org/jdk/[JDK_REQUIREMENT]}download and install the JDK{/a}, then restart the Ren'Py launcher."
new "윈도우에서 안드로이드 패키지를 만드려면 64비트 JDK가 필요합니다. JDK는 JRE와 다르므로 PC에 JDK가 없는 자바가 설치되어 있을 수 있습니다.\n\n{a=https://www.renpy.org/jdk/[JDK_REQUIREMENT]}JDK를 내려받아 설치한 뒤{/a}, 렌파이를 재시작해주세요."
# game/android.rpy:32
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."
+2 -3
View File
@@ -38,8 +38,8 @@ translate piglatin strings:
new "Otay uildbay Ndroidaay ackagespay, easeplay ownloadday Aptray, unzipay itay, anday aceplay itay intoay hetay Enray'Ypay irectoryday. Hentay estartray hetay Enray'Ypay auncherlay."
# game/android.rpy:35
old "A 64-bit/x64 Java 8 Development Kit is required to build Android packages on Windows. The JDK is different from the JRE, so it's possible you have Java without having the JDK.\n\nPlease {a=https://adoptopenjdk.net/?variant=openjdk8&jvmVariant=hotspot}download and install the JDK{/a}, then restart the Ren'Py launcher."
new "Aay 46ay-itbay/64xay Avajay 8ay Evelopmentday Itkay isay equiredray otay uildbay Ndroidaay ackagespay onay Indowsway. Hetay Dkjay isay ifferentday omfray hetay Rejay, osay itay'say ossiblepay ouyay avehay Avajay ithoutway avinghay hetay Dkjay.\n\nLeasepay {a=https://adoptopenjdk.net/?variant=openjdk8&jvmVariant=hotspot}ownloadday anday installay hetay Dkjay{/a}, hentay estartray hetay Enray'Ypay auncherlay."
old "A 64-bit/x64 Java [JDK_REQUIREMENT] Development Kit is required to build Android packages on Windows. The JDK is different from the JRE, so it's possible you have Java without having the JDK.\n\nPlease {a=https://www.renpy.org/jdk/[JDK_REQUIREMENT]}download and install the JDK{/a}, then restart the Ren'Py launcher."
new "Aay 46ay-itbay/64xay Avajay 8ay Evelopmentday Itkay isay equiredray otay uildbay Ndroidaay ackagespay onay Indowsway. Hetay Dkjay isay ifferentday omfray hetay Rejay, osay itay'say ossiblepay ouyay avehay Avajay ithoutway avinghay hetay Dkjay.\n\nLeasepay {a=https://www.renpy.org/jdk/[JDK_REQUIREMENT]}ownloadday anday installay hetay Dkjay{/a}, hentay estartray hetay Enray'Ypay auncherlay."
# game/android.rpy:36
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."
@@ -1812,4 +1812,3 @@ translate piglatin strings:
# game/web.rpy:348
old "Before packaging web apps, you'll need to download RenPyWeb, Ren'Py's web support. Would you like to download RenPyWeb now?"
new "Eforebay ackagingpay ebway appsay, ouyay'llay eednay otay ownloadday Enpywebray, Enray'Ypay'say ebway upportsay. Ouldway ouyay ikelay otay ownloadday Enpywebray ownay?"
+2 -2
View File
@@ -40,8 +40,8 @@
new "Aby zbudować pakiet dla Androida, pobierz RAPT, rozpakuj i umieść w katalogu Ren'Py. Następnie uruchom ponownie program Ren'py."
# game/android.rpy:31
old "A 64-bit/x64 Java 8 Development Kit is required to build Android packages on Windows. The JDK is different from the JRE, so it's possible you have Java without having the JDK.\n\nPlease {a=https://adoptopenjdk.net/?variant=openjdk8&jvmVariant=hotspot}download and install the JDK{/a}, then restart the Ren'Py launcher."
new "Tworzenie pakietów dla systemu Android w systemie Windows wymaga pakietu 64-bit/x64 Java Development Kit. JDK różni się od JRE, istnieje możliwość, że posiadasz Javę bez JDK.\n\n{a=https://adoptopenjdk.net/?variant=openjdk8&jvmVariant=hotspot}Pobierz i zainstaluj JDK{/a}, a następnie uruchom ponownie program Ren'py."
old "A 64-bit/x64 Java [JDK_REQUIREMENT] Development Kit is required to build Android packages on Windows. The JDK is different from the JRE, so it's possible you have Java without having the JDK.\n\nPlease {a=https://www.renpy.org/jdk/[JDK_REQUIREMENT]}download and install the JDK{/a}, then restart the Ren'Py launcher."
new "Tworzenie pakietów dla systemu Android w systemie Windows wymaga pakietu 64-bit/x64 Java Development Kit. JDK różni się od JRE, istnieje możliwość, że posiadasz Javę bez JDK.\n\n{a=https://www.renpy.org/jdk/[JDK_REQUIREMENT]}Pobierz i zainstaluj JDK{/a}, a następnie uruchom ponownie program Ren'py."
# game/android.rpy:32
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."
+2 -2
View File
@@ -40,8 +40,8 @@
new "Чтобы построить Android-пакет, пожалуйста, загрузите RAPT, разархивируйте его, и поместите в директорию Ren'Py. Затем перезагрузите лаунчер Ren'Py."
# android.rpy:31
old "A 64-bit/x64 Java 8 Development Kit is required to build Android packages on Windows. The JDK is different from the JRE, so it's possible you have Java without having the JDK.\n\nPlease {a=http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html}download and install the JDK{/a}, then restart the Ren'Py launcher."
new "Чтобы построить Android-пакеты на Windows требуется 64-битный инструментарий разработки Java 8. JDK отличен от JRE, и возможно, у вас есть Java без JDK.\n\nПожалуйста, {a=http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html}загрузите и установите JDK{/a}, и перезапустите лаунчер Ren'Py."
old "A 64-bit/x64 Java [JDK_REQUIREMENT] Development Kit is required to build Android packages on Windows. The JDK is different from the JRE, so it's possible you have Java without having the JDK.\n\nPlease {a=https://www.renpy.org/jdk/[JDK_REQUIREMENT]}download and install the JDK{/a}, then restart the Ren'Py launcher."
new "Чтобы построить Android-пакеты на Windows требуется 64-битный инструментарий разработки Java 8. JDK отличен от JRE, и возможно, у вас есть Java без JDK.\n\nПожалуйста, {a=https://www.renpy.org/jdk/[JDK_REQUIREMENT]}загрузите и установите JDK{/a}, и перезапустите лаунчер Ren'Py."
# android.rpy:32
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."
+2 -2
View File
@@ -37,8 +37,8 @@
new "要构建安卓应用包,请下载 RAPT,并解压到 Ren'Py 目录中。之后重启 Ren'Py。"
# game/android.rpy:35
old "A 64-bit/x64 Java 8 Development Kit is required to build Android packages on Windows. The JDK is different from the JRE, so it's possible you have Java without having the JDK.\n\nPlease {a=https://adoptopenjdk.net/?variant=openjdk8&jvmVariant=hotspot}download and install the JDK{/a}, then restart the Ren'Py launcher."
new "在 Windows 中构建安卓应用包需要 64 位/x64 Java 8 开发套件(JDK)。JDK 不同于 JRE,所以您可能已安装过 Java 但尚未安装 JDK。\n\n请{a=https://adoptopenjdk.net/?variant=openjdk8&jvmVariant=hotspot}下载并安装 JDK{/a},然后重启 Ren'Py。"
old "A 64-bit/x64 Java [JDK_REQUIREMENT] Development Kit is required to build Android packages on Windows. The JDK is different from the JRE, so it's possible you have Java without having the JDK.\n\nPlease {a=https://www.renpy.org/jdk/[JDK_REQUIREMENT]}download and install the JDK{/a}, then restart the Ren'Py launcher."
new "在 Windows 中构建安卓应用包需要 64 位/x64 Java 8 开发套件(JDK)。JDK 不同于 JRE,所以您可能已安装过 Java 但尚未安装 JDK。\n\n请{a=https://www.renpy.org/jdk/[JDK_REQUIREMENT]}下载并安装 JDK{/a},然后重启 Ren'Py。"
# game/android.rpy:36
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."
+4
View File
@@ -431,3 +431,7 @@
# gui.rpy:457
old "## NVL-mode."
new "## Modo-NVL"
# gui/game/gui.rpy:14
old "## Enable checks for invalid or unstable properties in screens or transforms"
new "## Habilitar comprobaciones de propiedades no válidas o inestables en pantallas o transformaciones."
+47 -3
View File
@@ -36,8 +36,8 @@
new "Para construir un paquete para Android, por favor descarga RAPT, descomprímelo y colócalo dentro de la carpeta de Ren'Py. Después reinicia el lanzador de Ren'Py."
# game/android.rpy:31
old "A 64-bit/x64 Java 8 Development Kit is required to build Android packages on Windows. The JDK is different from the JRE, so it's possible you have Java without having the JDK.\n\nPlease {a=https://adoptopenjdk.net/?variant=openjdk8&jvmVariant=hotspot}download and install the JDK{/a}, then restart the Ren'Py launcher."
new "Se requiere un kit de desarrollo Java 8 de 64 bits/x64 para construir paquetes de Android en Windows. El JDK es diferente del JRE, por lo que es posible que tengas Java sin el JDK.\n\nPor favor, {a=https://adoptopenjdk.net/?variant=openjdk8&jvmVariant=hotspot}descarga e instala el JDK{/a}, luego reinicia el lanzador de Ren'Py."
old "A 64-bit/x64 Java [JDK_REQUIREMENT] Development Kit is required to build Android packages on Windows. The JDK is different from the JRE, so it's possible you have Java without having the JDK.\n\nPlease {a=https://www.renpy.org/jdk/[JDK_REQUIREMENT]}download and install the JDK{/a}, then restart the Ren'Py launcher."
new "Se requiere un kit de desarrollo Java 8 de 64 bits/x64 para construir paquetes de Android en Windows. El JDK es diferente del JRE, por lo que es posible que tengas Java sin el JDK.\n\nPor favor, {a=https://www.renpy.org/jdk/[JDK_REQUIREMENT]}descarga e instala el JDK{/a}, luego reinicia el lanzador de Ren'Py."
# android.rpy:32
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."
@@ -2060,7 +2060,7 @@ translate spanish strings:
# game/preferences.rpy:327
old "{#in language font}Start using Ren'Py in [lang_name]"
new "{font=fonts/Roboto-Light.ttf}Empieza a usar Ren'Py en [lang_name]{/font}"
# game/distribute_gui.rpy:231
old "(DLC)"
new "(DLC)"
@@ -2072,3 +2072,47 @@ translate spanish strings:
# game/web.rpy:484
old "Creating package..."
new "Creando paquete..."
# game/android.rpy:39
old "RAPT has been installed, but a key hasn't been configured. Please generate new keys, or copy android.keystore and bundle.keystore to the base directory."
new "RAPT ha sido instalado, pero no se ha configurado una clave. Por favor, genera nuevas claves, o copia android.keystore y bundle.keystore al directorio base."
# game/android.rpy:46
old "Attempts to emulate a televison-based Android console.\n\nController input is mapped to the arrow keys, Enter is mapped to the select button, Escape is mapped to the menu button, and PageUp is mapped to the back button."
new "Intenta emular una consola Android basada en televisión.\n\nLa entrada del controlador se asigna a las teclas de flecha, Enter se asigna al botón de selección, Escape se asigna al botón de menú y PageUp se asigna al botón de retroceso."
# game/android.rpy:48
old "Downloads and installs the Android SDK and supporting packages."
new "Descarga e instala el SDK de Android y los paquetes de soporte."
# game/android.rpy:49
old "Generates the keys required to sign the package."
new "Genera las claves necesarias para firmar el paquete."
# game/android.rpy:383
old "Install SDK"
new "Instalar SDK"
# game/android.rpy:387
old "Generate Keys"
new "Generar claves"
# game/androidstrings.rpy:32
old "How much RAM (in GB) do you want to allocate to Gradle?\nThis must be a positive integer number."
new "¿Cuánta RAM (en GB) quiere asignar a Gradle?\nDebe ser un número entero positivo."
# game/androidstrings.rpy:33
old "The RAM size must contain only numbers and be positive."
new "El tamaño de la RAM debe contener sólo números y ser positivo."
# game/androidstrings.rpy:63
old "I found an android.keystore file in the rapt directory. Do you want to use this file?"
new "He encontrado un archivo android.keystore en el directorio rapt. ¿Quieres usar este archivo?"
# game/androidstrings.rpy:66
old "\n\nSaying 'No' will prevent key creation."
new "\n\nDecir 'No' impedirá la creación de claves."
# game/androidstrings.rpy:69
old "I found a bundle.keystore file in the rapt directory. Do you want to use this file?"
new "He encontrado un archivo bundle.keystore en el directorio rapt. ¿Quieres usar este archivo?"
+8
View File
@@ -692,3 +692,11 @@ translate spanish strings:
# gui/game/screens.rpy:1404
old "## https://www.renpy.org/doc/html/screen_special.html#bubble"
new "## https://www.renpy.org/doc/html/screen_special.html#bubble"
# gui/game/screens.rpy:676
old "Upload Sync"
new "Subir Sync"
# gui/game/screens.rpy:680
old "Download Sync"
new "Descargar Sync"
+2 -2
View File
@@ -40,8 +40,8 @@
new "Android paketleri derlemek için lütfen RAPT'ı indirin, açın ve Ren'Py klasörüne atın. Ardından Ren'Py'ı yeniden başlatın."
# android.rpy:31
old "A 64-bit/x64 Java 8 Development Kit is required to build Android packages on Windows. The JDK is different from the JRE, so it's possible you have Java without having the JDK.\n\nPlease {a=http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html}download and install the JDK{/a}, then restart the Ren'Py launcher."
new "Windows'ta Android paketleri derlemek için bir 64-bit/x64 Java 8 Development Kit gerekir. JDK, JRE'den farklıdır, bu yüzden JDK olmayan bir Java kurulumunuz olabilir.\n\nLütfen JDK'yı {a=http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html}indirin ve kurun{/a}, ardından Ren'Py'ı yeniden başlatın."
old "A 64-bit/x64 Java [JDK_REQUIREMENT] Development Kit is required to build Android packages on Windows. The JDK is different from the JRE, so it's possible you have Java without having the JDK.\n\nPlease {a=https://www.renpy.org/jdk/[JDK_REQUIREMENT]}download and install the JDK{/a}, then restart the Ren'Py launcher."
new "Windows'ta Android paketleri derlemek için bir 64-bit/x64 Java 8 Development Kit gerekir. JDK, JRE'den farklıdır, bu yüzden JDK olmayan bir Java kurulumunuz olabilir.\n\nLütfen JDK'yı {a=https://www.renpy.org/jdk/[JDK_REQUIREMENT]}indirin ve kurun{/a}, ardından Ren'Py'ı yeniden başlatın."
# android.rpy:32
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."
+2 -2
View File
@@ -38,8 +38,8 @@ translate ukrainian strings:
new "Щоб створити пакети Android, завантажте RAPT, розархівуйте його та помістіть у каталог Ren'Py. Потім перезапустіть лаунчер Ren'Py."
# game/android.rpy:35
old "A 64-bit/x64 Java 8 Development Kit is required to build Android packages on Windows. The JDK is different from the JRE, so it's possible you have Java without having the JDK.\n\nPlease {a=https://adoptopenjdk.net/?variant=openjdk8&jvmVariant=hotspot}download and install the JDK{/a}, then restart the Ren'Py launcher."
new "Для створення пакетів Android у Windows потрібен 64-розрядний/x64 Java 8 Development Kit. JDK відрізняється від JRE, тому можливо, що у вас є Java без JDK.\n\nБудь ласка, {a=https://adoptopenjdk.net/?variant=openjdk8&jvmVariant=hotspot}завантажте та встановіть JDK{/a}, а потім перезапустіть лаунчер Ren'Py."
old "A 64-bit/x64 Java [JDK_REQUIREMENT] Development Kit is required to build Android packages on Windows. The JDK is different from the JRE, so it's possible you have Java without having the JDK.\n\nPlease {a=https://www.renpy.org/jdk/[JDK_REQUIREMENT]}download and install the JDK{/a}, then restart the Ren'Py launcher."
new "Для створення пакетів Android у Windows потрібен 64-розрядний/x64 Java 8 Development Kit. JDK відрізняється від JRE, тому можливо, що у вас є Java без JDK.\n\nБудь ласка, {a=https://www.renpy.org/jdk/[JDK_REQUIREMENT]}завантажте та встановіть JDK{/a}, а потім перезапустіть лаунчер Ren'Py."
# game/android.rpy:36
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."
+1
View File
@@ -21,6 +21,7 @@ renpy.styledata.style_selected_activate_functions gen/renpy.styledata.style_sele
renpy.display.matrix gen/renpy.display.matrix.c
renpy.display.render gen/renpy.display.render.c
renpy.display.accelerator gen/renpy.display.accelerator.c
renpy.display.quaternion gen/renpy.display.quaternion.c
renpy.uguu.gl gen/renpy.uguu.gl.c
renpy.uguu.uguu gen/renpy.uguu.uguu.c
renpy.gl.gldraw gen/renpy.gl.gldraw.c
+22 -16
View File
@@ -824,27 +824,33 @@ static SurfaceQueueEntry *decode_video_frame(MediaState *ms) {
SDL_Surface *sample = rgba_surface;
ms->sws = sws_getCachedContext(
ms->sws,
if (ms->sws == NULL) {
ms->sws = sws_getContext(
ms->video_decode_frame->width,
ms->video_decode_frame->height,
ms->video_decode_frame->format,
ms->video_decode_frame->width,
ms->video_decode_frame->height,
ms->video_decode_frame->format,
ms->video_decode_frame->width,
ms->video_decode_frame->height,
get_pixel_format(sample),
ms->video_decode_frame->width,
ms->video_decode_frame->height,
get_pixel_format(rgba_surface),
SWS_POINT | SWS_FULL_CHR_H_INP | SWS_FULL_CHR_H_INT,
SWS_POINT,
NULL,
NULL,
NULL
);
NULL,
NULL,
NULL
);
if (!ms->sws) {
ms->video_finished = 1;
return NULL;
if (!ms->sws) {
ms->video_finished = 1;
return NULL;
}
sws_setColorspaceDetails(ms->sws,
sws_getCoefficients(SWS_CS_DEFAULT), 0,
sws_getCoefficients(SWS_CS_DEFAULT), 0,
0, 1 << 16, 1 << 16);
}
SurfaceQueueEntry *rv = av_malloc(sizeof(SurfaceQueueEntry));
+11
View File
@@ -350,6 +350,13 @@ static inline void mix_sample(struct Channel *c, short left_in, short right_in,
*right_out += right * volume;
}
/** If not NULL, this can be replaced with a function that will be called
to generate audio. The functtio is called with a consistion of 2*length
shorts, and should fill the buffer with audio data. */
void (*RPS_generate_audio_c_function)(float *stream, int length) = NULL;
static void callback(void *userdata, Uint8 *stream, int length) {
// Convert the length to samples.
@@ -360,6 +367,10 @@ static void callback(void *userdata, Uint8 *stream, int length) {
memset(mix_buffer, 0, length * 2 * sizeof(float));
if (RPS_generate_audio_c_function) {
RPS_generate_audio_c_function(mix_buffer, length);
}
for (int channel = 0; channel < num_channels; channel++) {
// The number of samples that have been mixed.
+3
View File
@@ -58,4 +58,7 @@ void RPS_periodic(void);
char *RPS_get_error(void);
extern void (*RPS_generate_audio_c_function)(float *stream, int length);
#endif
+1
View File
@@ -165,6 +165,7 @@ for p in generate_styles.prefixes:
cython("renpy.display.matrix")
cython("renpy.display.render", libs=[ 'z', 'm' ])
cython("renpy.display.accelerator", libs=sdl + [ 'z', 'm' ])
cython("renpy.display.quaternion", libs=[ 'm' ])
cython("renpy.uguu.gl", libs=sdl)
cython("renpy.uguu.uguu", libs=sdl)
+5 -2
View File
@@ -91,7 +91,7 @@ if PY2:
version_tuple = VersionTuple(7, 6, 0, vc_version)
# The name of this version.
version_name = "TBD"
version_name = "To Boldly Go"
else:
@@ -99,7 +99,7 @@ else:
version_tuple = VersionTuple(8, 1, 0, vc_version)
# The name of this version.
version_name = "TBD"
version_name = "Where No One Has Gone Before"
# A string giving the version number only (8.0.1.123), with a suffix if needed.
version_only = ".".join(str(i) for i in version_tuple)
@@ -263,6 +263,8 @@ name_blacklist = {
"renpy.webloader.queue_lock",
"renpy.persistent.MP_instances",
"renpy.exports.sdl_dll",
"renpy.sl2.slast.serial",
"renpy.gl2.gl2draw.default_position",
}
class Backup(_object):
@@ -514,6 +516,7 @@ def import_all():
import renpy.display.tts
import renpy.display.gesture
import renpy.display.model
import renpy.display.quaternion
import renpy.display.error
+169 -16
View File
@@ -126,7 +126,7 @@ def interpolate(t, a, b, type): # @ReservedAssignment
"""
# Deal with booleans, nones, etc.
if b is None or isinstance(b, (bool, basestring, renpy.display.matrix.Matrix)):
if b is None or isinstance(b, (bool, basestring, renpy.display.matrix.Matrix, renpy.display.transform.Camera)):
if t >= 1.0:
return b
else:
@@ -228,7 +228,7 @@ def get_catmull_rom_value(t, p_1, p0, p1, p2):
+(t - 1) * t * t * p2) / 2)
# A list of atl transforms that may need to be compile.
# A list of atl transforms that may need to be compiled.
compile_queue = [ ]
@@ -241,11 +241,29 @@ def compile_all():
global compile_queue
for i in compile_queue:
i.atl.find_loaded_variables()
if i.atl.constant == GLOBAL_CONST:
i.compile()
compile_queue = [ ]
def find_loaded_variables(expr):
"""
Returns the set of variables that are loaded by the given expression.
"""
if expr is None:
return set()
ast = renpy.pyanalysis.ccache.ast_eval(expr)
return renpy.python.find_loaded_variables(ast)
# Used to indicate that a variable is not in the context.
NotInContext = renpy.object.Sentinel("NotInContext")
# 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,
@@ -267,6 +285,37 @@ class Context(object):
def __ne__(self, other):
return not (self == other)
def variables_equal(self, other, variables):
"""
Returns true if the variables in `variables` are equal in
this context and `other`. False if they are not equal.
Returns True if any variable cannot be compared.
"""
try:
if renpy.config.at_transform_compare_full_context:
if self.context != other.context:
return False
for i in variables:
if self.context.get(i, NotInContext) != other.context.get(i, NotInContext):
# Ignore the arguments given to ATL Transitions, which
# will change each time an interaction restarts. (See #4167.)
if i in ("new_widget", "old_widget"):
continue
return False
return True
except Exception:
return True
# 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.
@@ -386,13 +435,12 @@ class ATLTransformBase(renpy.object.Object):
elif t.atl is not self.atl:
return
# Important to do it this way, so we use __eq__. The exception handling
# optimistically assumes that uncomparable objects are the same.
try:
if not (t.context == self.context):
# Only take the execution state if the contexts haven't changed in
# a way that would affect the execution of the ATL.
if t.atl.constant != GLOBAL_CONST:
if not self.context.variables_equal(t.context, t.atl.find_loaded_variables()):
return
except Exception:
pass
self.done = t.done
self.block = t.block
@@ -649,6 +697,13 @@ class RawStatement(object):
self.constant = NOT_CONST
def find_loaded_variables(self):
"""
Returns the set of variables that are loaded by this statement.
"""
raise Exception("find_loaded_variables not implemented.")
# The base class for compiled ATL Statements.
@@ -706,6 +761,11 @@ class RawBlock(RawStatement):
# and use this value for all ATLTransform that use us as an atl.
compiled_block = None
# If this is the outermost ATL of a parse, a set giving the variables
# that are loaded by this block.
loaded_variable_cache = None
def __init__(self, loc, statements, animation):
super(RawBlock, self).__init__(loc)
@@ -767,6 +827,20 @@ class RawBlock(RawStatement):
self.constant = constant
def find_loaded_variables(self):
if self.loaded_variable_cache is not None:
return self.loaded_variable_cache
variables = set()
for i in self.statements:
variables.update(i.find_loaded_variables())
self.loaded_variable_cache = variables
return variables
# A compiled ATL block.
class Block(Statement):
@@ -1087,6 +1161,26 @@ class RawMultipurpose(RawStatement):
self.constant = constant
def find_loaded_variables(self):
rv = set()
rv.update(find_loaded_variables(self.warp_function))
rv.update(find_loaded_variables(self.duration))
rv.update(find_loaded_variables(self.circles))
for _name, expr in self.properties:
rv.update(find_loaded_variables(expr))
for _name, exprs in self.splines:
for expr in exprs:
rv.update(find_loaded_variables(expr))
for expr, withexpr in self.expressions:
rv.update(find_loaded_variables(expr))
rv.update(find_loaded_variables(withexpr))
return rv
def predict(self, ctx):
for i, _j in self.expressions:
@@ -1122,6 +1216,9 @@ class RawContainsExpr(RawStatement):
def mark_constant(self, analysis):
self.constant = analysis.is_constant_expr(self.expression)
def find_loaded_variables(self):
return find_loaded_variables(self.expression)
# This allows us to have multiple ATL transforms as children.
class RawChild(RawStatement):
@@ -1156,6 +1253,14 @@ class RawChild(RawStatement):
self.constant = constant
def find_loaded_variables(self):
rv = set()
for i in self.children:
rv.update(i.find_loaded_variables())
return rv
# This changes the child of this statement, optionally with a transition.
class Child(Statement):
@@ -1244,6 +1349,7 @@ class Interpolation(Statement):
newts.take_state(trans.state)
has_angle = False
has_radius = False
for k, v in self.properties:
setattr(newts, k, v)
@@ -1252,6 +1358,9 @@ class Interpolation(Statement):
newts.last_angle = v
has_angle = True
elif k == "radius":
has_radius = True
# Now, the things we change linearly are in the difference
# between the new and old states.
linear = trans.state.diff(newts)
@@ -1262,7 +1371,7 @@ class Interpolation(Statement):
revdir = self.revolution
circles = self.circles
if (revdir or (has_angle and renpy.config.automatic_polar_motion)) and (newts.xaround is not None):
if (revdir or ((has_angle or has_radius) and renpy.config.automatic_polar_motion)) and (newts.xaround is not None):
# Remove various irrelevant motions.
for i in [ 'xpos', 'ypos',
@@ -1302,13 +1411,22 @@ class Interpolation(Statement):
startangle += circles * 360
if has_radius:
linear["radius"] = (startradius, endradius)
# Store the revolution.
revolution = (startangle, endangle, startradius, endradius)
if has_angle:
revolution = (startangle, endangle)
else:
last_angle = trans.state.last_angle or trans.state.angle
revolution = (last_angle, newts.last_angle, trans.state.radius, newts.radius)
if has_angle:
last_angle = trans.state.last_angle or trans.state.angle
revolution = (last_angle, newts.last_angle)
if has_radius:
linear["radius"] = (trans.state.radius, newts.radius)
# Figure out the splines.
for name, values in self.splines:
@@ -1331,11 +1449,12 @@ class Interpolation(Statement):
if old is None:
old = (0.0, 0.0, 0.0)
if new is not None:
value = renpy.display.accelerator.quaternion_slerp(complete, old, new)
value = renpy.display.quaternion.euler_slerp(complete, old, new)
elif complete >= 1:
value = None
else:
value = old
else:
value = interpolate(complete, old, new, PROPERTIES[k])
@@ -1343,14 +1462,12 @@ class Interpolation(Statement):
# Handle the revolution.
if revolution is not None:
startangle, endangle, startradius, endradius = revolution
startangle, endangle = revolution[:2]
angle = interpolate(complete, startangle, endangle, float)
trans.state.last_angle = angle
trans.state.angle = angle
trans.state.radius = interpolate(complete, startradius, endradius, float)
# Handle any splines we might have.
for name, values in splines:
value = interpolate_spline(complete, values)
@@ -1388,6 +1505,8 @@ class RawRepeat(RawStatement):
def mark_constant(self, analysis):
self.constant = analysis.is_constant_expr(self.repeats)
def find_loaded_variables(self):
return find_loaded_variables(self.repeats)
class Repeat(Statement):
@@ -1426,6 +1545,14 @@ class RawParallel(RawStatement):
self.constant = constant
def find_loaded_variables(self):
rv = set()
for i in self.blocks:
rv.update(i.find_loaded_variables())
return rv
class Parallel(Statement):
@@ -1506,6 +1633,15 @@ class RawChoice(RawStatement):
self.constant = constant
def find_loaded_variables(self):
rv = set()
for chance, block in self.choices:
rv.update(find_loaded_variables(chance))
rv.update(block.find_loaded_variables())
return rv
class Choice(Statement):
@@ -1575,6 +1711,9 @@ class RawTime(RawStatement):
def mark_constant(self, analysis):
self.constant = analysis.is_constant_expr(self.time)
def find_loaded_variables(self):
return find_loaded_variables(self.time)
class Time(Statement):
@@ -1622,6 +1761,14 @@ class RawOn(RawStatement):
self.constant = constant
def find_loaded_variables(self):
rv = set()
for block in self.handlers.values():
rv.update(block.find_loaded_variables())
return rv
class On(Statement):
@@ -1731,6 +1878,9 @@ class RawEvent(RawStatement):
def mark_constant(self, analysis):
self.constant = GLOBAL_CONST
def find_loaded_variables(self):
return set()
class Event(Statement):
@@ -1757,6 +1907,9 @@ class RawFunction(RawStatement):
def mark_constant(self, analysis):
self.constant = analysis.is_constant_expr(self.expr)
def find_loaded_variables(self):
return find_loaded_variables(self.expr)
class Function(Statement):
+4
View File
@@ -785,6 +785,7 @@ class Channel(object):
return renpysound.video_ready(self.number)
# Use unconditional imports so these files get compiled during the build
# process.
@@ -988,6 +989,9 @@ def init():
bufsize = 8192 # works for me
# bufsize = 16384 # jitter/silence right after starting a sound
if renpy.config.sound_buffer_size is not None:
bufsize = renpy.config.sound_buffer_size
if 'RENPY_SOUND_BUFSIZE' in os.environ:
bufsize = int(os.environ['RENPY_SOUND_BUFSIZE'])
+21
View File
@@ -50,6 +50,8 @@ or is stopped.
from __future__ import print_function
from libc.stdint cimport uintptr_t
from pygame_sdl2 cimport *
import_pygame_sdl2()
@@ -84,6 +86,7 @@ cdef extern from "renpysound_core.h":
void RPS_periodic()
char *RPS_get_error()
void (*RPS_generate_audio_c_function)(float *stream, int length)
def check_error():
"""
@@ -419,6 +422,24 @@ def advance_time():
RPS_advance_time()
def set_generate_audio_c_function(fn):
"""
This can be use to set a C function that totally replaces the Ren'Py
audio system.
The function is expected to have the signature void (*)(short *buf, int samples,
and fill buf with samples samples of audio, where each sample consists of two
shorts.
"""
global RPS_generate_audio_c_function
if not isinstance(fn, int):
import ctypes
fn = ctypes.cast(fn, ctypes.c_void_p).value
RPS_generate_audio_c_function = <void (*)(float *, int)> <uintptr_t> fn
# Store the sample surfaces so they stay alive.
rgb_surface = None
rgba_surface = None
+10
View File
@@ -1258,6 +1258,11 @@ class ADVCharacter(object):
if not isinstance(what, basestring):
raise Exception("Character expects its what argument to be a string, got %r." % (what,))
if renpy.store._side_image_attributes_reset:
renpy.store._side_image_attributes = None
renpy.store._side_image_attributes_reset = False
# Figure out multiple and final. Multiple is None if this is not a multiple
# dialogue, or a step and the total number of steps in a multiple interaction.
@@ -1500,6 +1505,9 @@ def Character(name=NotSet, kind=None, **properties):
be used to define a template character, and then copy that
character with changes.
This can also be a namespace, in which case the 'character'
variable in the namespace is used as the kind.
**Linked Image.**
An image tag may be associated with a Character. This allows a
say statement involving this character to display an image with
@@ -1653,6 +1661,8 @@ def Character(name=NotSet, kind=None, **properties):
if kind is None:
kind = renpy.store.adv
kind = getattr(kind, "character", kind)
return type(kind)(name, kind=kind, **properties)
+10
View File
@@ -212,6 +212,14 @@ screen _accessibility():
null height 10
label _("Voice Volume")
null height 10
bar value Preference("voice volume")
null height 10
label _("Self-Voicing Volume Drop")
null height 10
@@ -219,6 +227,8 @@ screen _accessibility():
bar value Preference("self voicing volume drop")
vbox:
text _("The options on this menu are intended to improve accessibility. They may not work with all games, and some combinations of options may render the game unplayable. This is not an issue with the game or engine. For the best results when changing fonts, try to keep the text size the same as it originally was.")
+3 -3
View File
@@ -302,11 +302,11 @@ init -1600 python:
renpy.restart_interaction()
def get_selected(self):
if self.key not in self.dict:
try:
rv = self.dict[self.key]
except (KeyError, IndexError):
return False
rv = self.dict[self.key]
if self.true_value is not None:
rv = (rv == self.true_value)
+10 -2
View File
@@ -129,6 +129,8 @@ init -1500 python:
config.predict_file_pages = True
config.file_slotname_callback = None
def __slotname(name, page=None, slot=False):
if slot:
@@ -151,7 +153,13 @@ init -1500 python:
except ValueError:
pass
return str(page) + "-" + str(name)
page = str(page)
name = str(name)
if config.file_slotname_callback is not None:
return config.file_slotname_callback(page, name)
else:
return page + "-" + name
def __newest_slot():
"""
@@ -566,7 +574,7 @@ init -1500 python:
page = unicode(page)
for i in renpy.list_slots(page + "-"):
for i in renpy.list_slots(__slotname(page, r'\d+')):
renpy.predict(renpy.slot_screenshot(i))
@renpy.pure
+5 -4
View File
@@ -414,8 +414,9 @@ init -1500 python:
when entering the replay.
`locked`
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.
If true, this action is insensitive and will not do anything when triggered.
If false, it will behave normally. If None, it will be locked if the label
has not been seen in any playthrough.
"""
def __init__(self, label, scope={}, locked=None):
@@ -425,7 +426,7 @@ init -1500 python:
def __call__(self):
if self.locked:
if not self.get_sensitive():
return
if config.enter_replay_transition:
@@ -694,7 +695,7 @@ init -1500 python:
amount = delta * adjustment.step
elif self.amount == "page":
amount = delta * adjustment.page
elif isinstance(self.amount) and not isinstance(self.amount, absolute):
elif isinstance(self.amount, float) and not isinstance(self.amount, absolute):
amount = delta * self.amount * adjustment.range
else:
amount = delta * self.amount
+13
View File
@@ -456,6 +456,17 @@ init -1500 python in build:
# The itch.io project name.
itch_project = None
# Maps from files to itch.io channels.
itch_channels = [
( "*-all.zip", "win-osx-linux" ),
( "*-market.zip", "win-osx-linux" ),
( "*-pc.zip", "win-linux" ),
( "*-win.zip", "win" ),
( "*-mac.zip", "osx" ),
( "*-linux.tar.bz2", "linux" ),
( "*-release.apk", "android" ),
]
# Should we include the old Ren'Py themes?
include_old_themes = True
@@ -552,6 +563,8 @@ init -1500 python in build:
if itch_project:
rv["itch_project"] = itch_project
rv["itch_channels"] = itch_channels
if mac_identity:
rv["mac_identity"] = mac_identity
rv["mac_codesign_command"] = mac_codesign_command
+4 -3
View File
@@ -250,14 +250,14 @@ init -1100 python:
config.atl_function_always_blocks = True
if version <= (7, 4, 11):
config.timer_blocks_pause = False
config.modal_blocks_timer = False
config.modal_blocks_pause = False
elif _compat_versions(version, (7, 5, 1), (8, 0, 1)):
config.timer_blocks_pause = True
config.modal_blocks_timer = True
config.modal_blocks_pause = False
elif _compat_versions(version, (7, 5, 2), (8, 0, 2)):
config.modal_blocks_pause = True
config.timer_blocks_pause = True
config.modal_blocks_timer = True
if _compat_versions(version, (7, 5, 3), (8, 0, 3)):
config.quadratic_volumes = True
@@ -268,6 +268,7 @@ init -1100 python:
config.history_current_dialogue = False
config.scry_extend = False
config.fadeout_audio = 0.0
config.at_transform_compare_full_context = True
if version > (6, 99, 5):
config.search_prefixes.append("images/")
+1 -1
View File
@@ -24,7 +24,7 @@ import renpy.exports as renpy
from renpy.minstore import _dict, _object
"""renpy
init -1100 python:
init -1200 python:
"""
class _JSONDBDict(_dict):
+5 -3
View File
@@ -54,6 +54,7 @@ init -1600 python:
choose_renderer = ['alt_K_g', 'shift_K_g' ],
progress_screen = [ 'alt_shift_K_p', 'meta_shift_K_p', 'K_F2' ],
accessibility = [ 'K_a' ],
bubble_editor = [ 'alt_K_b', 'shift_K_b' ],
# Accessibility.
self_voicing = [ 'alt_K_v', 'K_v' ],
@@ -83,6 +84,7 @@ init -1600 python:
# Input.
input_backspace = [ 'any_K_BACKSPACE' ],
input_enter = [ 'K_RETURN', 'K_KP_ENTER' ],
input_next_line = [ 'shift_K_RETURN', 'shift_K_KP_ENTER' ],
input_left = [ 'any_K_LEFT', 'any_KP_LEFT' ],
input_right = [ 'any_K_RIGHT', 'any_KP_RIGHT' ],
input_up = [ 'any_K_UP', 'any_KP_UP' ],
@@ -296,7 +298,7 @@ init -1600 python:
dest = config.renpy_base
if renpy.macapp:
dest = os.path.expanduser(b"~/Desktop")
dest = os.path.expanduser("~/Desktop")
pattern = renpy.store._screenshot_pattern or config.screenshot_pattern
@@ -415,8 +417,7 @@ screen _progress:
init -1100 python:
# The default keymap. We might also want to put some of this into
# the launcher.
# The default keymap.
_default_keymap = renpy.Keymap(
rollback = renpy.rollback,
screenshot = _screenshot,
@@ -443,6 +444,7 @@ init -1100 python:
director = director.Start(),
performance = ToggleScreen("_performance"),
accessibility = ToggleScreen("_accessibility"),
bubble_editor = bubble.ToggleShown(),
)
config.underlay = [ _default_keymap ]
+5 -2
View File
@@ -438,7 +438,7 @@ python early in layeredimage:
class Always(Layer):
"""
:doc: li
:undocumented:
:name: Always
This is used for a displayable that is always shown.
@@ -515,11 +515,14 @@ python early in layeredimage:
displayables associated with those attribute.
`attributes`
This must be a list of Attribute objects. Each Attribute object
This must be a list of Attribute, Condition, ConditionGroup or
:func:`renpy.Displayable` objects. Each one
reflects a displayable that may or may not be displayed as part
of the image. The items in this list are in back-to-front order,
with the first item further from the viewer and the last
closest.
Passing a displayable directly is the equivalent of the `always`
layeredimage statement.
`at`
A transform or list of transforms that are applied to the displayable
+5 -6
View File
@@ -167,17 +167,16 @@ init -1500 python:
b = oldb + (b - oldb) * done
a = olda + (a - olda) * done
# To properly handle premultiplied alpha, the color channels
# have to be multiplied by the alpha channel.
r *= a
g *= a
b *= a
# Update the tint with opacity from the alpha channel.
r = 1 - (1 - r) * a
g = 1 - (1 - g) * a
b = 1 - (1 - b) * a
# Return a Matrix.
return Matrix([ r, 0, 0, 0,
0, g, 0, 0,
0, 0, b, 0,
0, 0, 0, a ])
0, 0, 0, 1 ])
class BrightnessMatrix(ColorMatrix, DictEquality):
"""
+53 -9
View File
@@ -27,7 +27,7 @@ default bubble.tag_properties = { }
# on the screen now.
default bubble.current_dialogue = [ ]
init -1050 python in bubble:
init -1150 python in bubble:
from store import config, ADVCharacter, Character, JSONDB, Action, Frame, NoRollback
@@ -41,13 +41,14 @@ init -1050 python in bubble:
# The path to the json file the bubble database is stored in.
db_filename = "bubble.json"
# The number of rows and columns in the bubble database.
# The number of rows and columns in the grid that is used to position
# speech bubbles.
cols = 24
rows = 24
# The default window area rectangle. This is expressed as squares, where units
# are defined by the rows and columns.
default_area = (0, 18, 24, 6)
default_area = (15, 1, 8, 5)
# The property that the area is supplied as.
area_property = "window_area"
@@ -55,17 +56,34 @@ init -1050 python in bubble:
# Additional properties that the player can use to customize the bubble.
# This is a map from a property name to a list of choices that are cycled
# through.
properties = {
"default" : { }
}
properties = { }
# The property group names, in order.
properties_order = [ "default" ]
properties_order = [ ]
# A map from property name to the (left, top, right, bottom) number of pixels
# areas with that property are expanded by. If a property is not in this
# map, None is tried.
expand_area = { }
# This is set to the JSONDB object that stores the bubble database,
# or None if the databse doesn't exist yet.
# or None if the database doesn't exist yet.
db = None
# This is not used directly, but is used by the default screens.rpy, and
# so should not be set.
frame = None
class ToggleShown(Action):
def __call__(self):
if not active and not shown.value:
return
shown.value = not shown.value
renpy.restart_interaction()
def get_selected(self):
return shown.value
def scene_callback(layer):
global tag_properties
@@ -117,6 +135,9 @@ init -1050 python in bubble:
for the given image tag.
"""
if not properties_order:
raise Exception("A speech bubble is being used, but bubble.properties has not been set.")
rv = { }
xgrid = config.screen_width / cols
@@ -134,6 +155,29 @@ init -1050 python in bubble:
"properties" : properties_order[0]
}
def expand_area(self, area, properties_key):
"""
This is called to expand the area of a bubble. It is given the
area, and the properties key, and returns a new area.
"""
x, y, w, h = area
expand = expand_area.get(properties_key, None) or expand_area.get(None, None)
if expand is None:
return area
left, top, right, bottom = expand
x = x - left
y = y - top
w = w + left + right
h = h + top + bottom
return (x, y, w, h)
def do_show(self, who, what, multiple=None, extra_properties=None):
if extra_properties is None:
@@ -157,7 +201,7 @@ init -1050 python in bubble:
properties_key = tag_properties[image_tag]["properties"]
extra_properties.update(properties.get(properties_key, { }))
extra_properties[area_property] = tag_properties[image_tag]["area"]
extra_properties[area_property] = self.expand_area(tag_properties[image_tag]["area"], properties_key)
return super(BubbleCharacter, self).do_show(who, what, multiple=multiple, extra_properties=extra_properties)
+1 -1
View File
@@ -362,7 +362,7 @@ init -1499 python in _renpysteam:
Activates the Steam overlay, and opens the web page at `url`.
"""
steamapi.SteamFriends().ActivateGameOverlayToWebPage(url.encode("utf-8"))
steamapi.SteamFriends().ActivateGameOverlayToWebPage(url.encode("utf-8"), steamapi.k_EActivateGameOverlayToWebPageMode_Default)
def activate_overlay_to_store(appid, flag=None):
"""
+6 -3
View File
@@ -287,7 +287,7 @@ let video_start = (c) => {
//TODO? c.playing.source.stop(context.currentTime + p.fadeout);
//TODO? } catch (e) {
//TODO? }
//TODO?
//TODO?
//TODO? }
setValue(c.relative_volume.gain, p.relative_volume);
@@ -702,10 +702,13 @@ renpyAudio.set_pan = (channel, pan, delay) => {
linearRampToValue(control, control.value, pan, delay);
};
renpyAudio.tts = (s) => {
console.log("tts: " + s);
renpyAudio.tts = (s, v) => {
console.log("tts:", s, "volume:", v);
v = v || 1.0;
let u = new SpeechSynthesisUtterance(s);
u.volume = v;
speechSynthesis.cancel();
speechSynthesis.speak(u);
};
+1 -1
View File
@@ -67,7 +67,7 @@ screen _developer:
if bubble.active:
textbutton _("Speech Bubble Editor"):
textbutton _("Speech Bubble Editor (Shift+B)"):
action [ SetField(bubble.shown, "value", True), Hide("_developer") ]
hbox:
+13
View File
@@ -179,6 +179,19 @@ if PY2:
add_attribute(io.TextIOWrapper, "_write", io.TextIOWrapper.write)
add_attribute(io.TextIOWrapper, "write", types.MethodType(text_write, None, io.TextIOWrapper)) # type: ignore
################################################################################
# Chance the default for subprocess.Popen.
if PY2:
import subprocess
class Popen(subprocess.Popen):
def __init__(self, *args, **kwargs):
if ("stdout" not in kwargs) and ("stderr" not in kwargs) and ("stdin" not in kwargs):
kwargs.setdefault("close_fds", True)
super(Popen, self).__init__(*args, **kwargs)
subprocess.Popen = Popen
################################################################################
# Export functions.
+13 -1
View File
@@ -97,7 +97,7 @@ savedir = None
image_cache_size = None
# The size of the image cache, in megabytes.
image_cache_size_mb = 300
image_cache_size_mb = 400
# The number of statements we will analyze when doing predictive
# loading. Please note that this is a total number of statements in a
@@ -1373,6 +1373,18 @@ check_translate_none = True
# Like developer, but available at the end of python early blocks.
early_developer = False
# A function that returns the autosave prefix.
autosave_prefix_callback = None
# Compatibility for c492f2f8e6de57b2740fa5d00566f445124caaba.
at_transform_compare_full_context = False
# A list of callbacks when the display starts.
display_start_callbacks = [ ]
# If not None, the size of the sound buffer, in bytes.
sound_buffer_size = None
del os
del collections
+2 -1
View File
@@ -163,7 +163,8 @@ ShowingSwitch = renpy.display.layout.ShowingSwitch
AlphaMask = renpy.display.layout.AlphaMask
Layer = renpy.display.layout.Layer
Transform = renpy.display.motion.Transform
Transform = renpy.display.transform.Transform
Camera = renpy.display.transform.Camera
Animation = anim.Animation
Movie = renpy.display.video.Movie
File diff suppressed because it is too large Load Diff
+17 -3
View File
@@ -1116,7 +1116,9 @@ class Button(renpy.display.layout.Window):
renpy.game.interface.timeout(renpy.config.longpress_duration)
if self.longpress_start is not None:
if math.hypot(x - self.longpress_x, y - self.longpress_y) > renpy.config.longpress_radius:
if ev.type == pygame.MOUSEBUTTONUP and ev.button == 1:
self.longpress_start = None
elif math.hypot(x - self.longpress_x, y - self.longpress_y) > renpy.config.longpress_radius:
self.longpress_start = None
elif st >= (self.longpress_start + renpy.config.longpress_duration):
renpy.exports.vibrate(renpy.config.longpress_vibrate)
@@ -1287,7 +1289,7 @@ def input_post_per_interact():
global input_value_active
for i in input_values:
if i is current_input_value:
if i == current_input_value:
break
else:
@@ -1298,7 +1300,7 @@ def input_post_per_interact():
for i in inputs:
editable = (i.value is current_input_value) and input_value_active and i.value.editable
editable = (i.value == current_input_value) and input_value_active and i.value.editable
content = i.value.get_text()
@@ -1365,6 +1367,7 @@ class Input(renpy.text.text.Text): # @UndefinedVariable
edit_text = u""
value = None
shown = False
multiline = False
st = 0
@@ -1384,6 +1387,7 @@ class Input(renpy.text.text.Text): # @UndefinedVariable
value=None,
copypaste=False,
caret_blink=None,
multiline=False,
**properties):
super(Input, self).__init__("", style=style, replaces=replaces, substitute=False, **properties)
@@ -1412,6 +1416,8 @@ class Input(renpy.text.text.Text): # @UndefinedVariable
self.editable = editable
self.pixel_width = pixel_width
self.multiline = multiline
caretprops = { 'color' : None }
for i, v in properties.items():
@@ -1582,6 +1588,14 @@ class Input(renpy.text.text.Text): # @UndefinedVariable
renpy.display.render.redraw(self, 0)
raise renpy.display.core.IgnoreEvent()
elif self.multiline and map_event(ev, 'input_next_line'):
content = self.content[:self.caret_pos] + '\n' + self.content[self.caret_pos:]
self.caret_pos += 1
self.update_text(content, self.editable)
renpy.display.render.redraw(self, 0)
raise renpy.display.core.IgnoreEvent()
elif map_event(ev, "input_enter"):
content = self.content
+20 -12
View File
@@ -84,7 +84,8 @@ controllers = { }
axis_positions = {}
# The axis threshold.
THRESHOLD = (32768 // 2)
THRESHOLD = 8192 + 4096
ZERO_THRESHOLD = 8192
# Should we ignore events?
ignore = False
@@ -254,21 +255,28 @@ def event(ev):
elif ev.type == CONTROLLERAXISMOTION:
if ev.value > THRESHOLD:
pos = "pos"
elif ev.value < -THRESHOLD:
pos = "neg"
else:
pos = "zero"
pygame_sdl2.event.pump()
events = [ ev ] + pygame.event.get(CONTROLLERAXISMOTION)
old_pos = axis_positions.get((ev.which, ev.axis), None)
for ev in events:
if pos == old_pos:
return None
old_pos = axis_positions.get((ev.which, ev.axis), None)
axis_positions[(ev.which, ev.axis)] = pos
if ev.value > THRESHOLD:
pos = "pos"
elif ev.value < -THRESHOLD:
pos = "neg"
elif abs(ev.value) < ZERO_THRESHOLD:
pos = "zero"
else:
pos = old_pos
controller_event(get_string_for_axis(ev.axis), pos)
if pos == old_pos:
continue
axis_positions[(ev.which, ev.axis)] = pos
controller_event(get_string_for_axis(ev.axis), pos)
return None
+85 -26
View File
@@ -2017,9 +2017,6 @@ class Interface(object):
self.old_scene = { }
self.transition = { }
self.ongoing_transition = { }
self.transition_time = { }
self.transition_from = { }
self.suppress_transition = False
self.quick_quit = False
self.force_redraw = False
@@ -2076,6 +2073,22 @@ class Interface(object):
# Is our audio paused?
self.audio_paused = False
# Transition-related:
# A map from layer (or None for the root) to the uninstantiate
# transition object that's on that layer.
self.ongoing_transition = { }
# The same, but for the transition after it's been called with the
# old and new displayables.
self.instantiated_transition = { }
# The time an ongoing transition started.
self.transition_time = { }
# The displayable that an ongoing transition is transitioning from.
self.transition_from = { }
# Init layers.
init_layers()
@@ -2360,6 +2373,7 @@ class Interface(object):
cursors = { }
for key, cursor_list in renpy.config.mouse.items():
l = [ ]
for i in cursor_list:
@@ -2373,11 +2387,18 @@ class Interface(object):
self.cursor_cache[key] = l
if ("default" not in self.cursor_cache) and (None in self.cursor_cache):
self.cursor_cache["default"] = self.cursor_cache[None]
s = "Total time until interface ready: {}s.".format(time.time() - import_time)
if renpy.android and not renpy.config.log_to_stdout:
print(s)
for i in renpy.config.display_start_callbacks:
i()
def post_init(self):
"""
This is called after display init, but before the window is created.
@@ -2388,6 +2409,7 @@ class Interface(object):
pygame.display.hint("SDL_TOUCH_MOUSE_EVENTS", "1")
pygame.display.hint("SDL_MOUSE_TOUCH_EVENTS", "0")
pygame.display.hint("SDL_EMSCRIPTEN_ASYNCIFY", "0")
pygame.display.hint("SDL_IME_SHOW_UI", "1")
if renpy.config.mouse_focus_clickthrough:
pygame.display.hint("SDL_MOUSE_FOCUS_CLICKTHROUGH", "1")
@@ -2451,6 +2473,9 @@ class Interface(object):
square_im.blit(im, ((imax - iw) // 2, (imax - ih) // 2))
im = square_im
if im.get_size()[0] > 1024:
im = renpy.display.scale.smoothscale(im, (1024, 1024))
pygame.display.set_icon(im)
except renpy.webloader.DownloadNeeded:
pass
@@ -2919,6 +2944,7 @@ class Interface(object):
for l in layers:
if l is None:
self.ongoing_transition.pop(None, None)
self.instantiated_transition.pop(None, None)
self.transition_time.pop(None, None)
self.transition_from.pop(None, None)
continue
@@ -2928,6 +2954,7 @@ class Interface(object):
if (self.frame_time - start) >= delay:
self.ongoing_transition.pop(l, None)
self.instantiated_transition.pop(l, None)
self.transition_time.pop(l, None)
self.transition_from.pop(l, None)
@@ -3138,6 +3165,9 @@ class Interface(object):
if interaction and (mouse_kind is None):
mouse_kind = self.mouse
if mouse_kind is None:
mouse_kind = "default"
if pygame.mouse.get_pressed()[0]:
mouse_kind = "pressed_" + mouse_kind # type: ignore
@@ -3328,6 +3358,7 @@ class Interface(object):
# Stop ongoing transitions.
self.ongoing_transition.clear()
self.instantiated_transition.clear()
self.transition_from.clear()
self.transition_time.clear()
@@ -3613,6 +3644,9 @@ class Interface(object):
else:
renpy.plog(1, "end idle_frame (inexpensive)")
# This gets assigned below.
take_layer_displayable = None
def interact_core(self,
show_mouse=True,
trans_pause=False,
@@ -3674,11 +3708,14 @@ class Interface(object):
self.suppress_transition = False
# Figure out transitions.
if suppress_transition:
if suppress_transition or renpy.game.after_rollback:
self.ongoing_transition.clear()
self.instantiated_transition.clear()
self.transition_from.clear()
self.transition_time.clear()
else:
if not suppress_transition:
for k, t in self.transition.items():
if k not in self.old_scene:
continue
@@ -3791,6 +3828,29 @@ class Interface(object):
layers_root = renpy.display.layout.MultiBox(layout='fixed')
layers_root.layers = { }
def instantiate_transition(layer, old_d, new_d):
"""
Create a transition that will be used to transition `layer` (which
can be None) to `d`.
"""
old_trans = self.instantiated_transition.get(layer, None)
trans = self.ongoing_transition[layer](
old_widget=old_d,
new_widget=new_d)
if not isinstance(trans, Displayable):
raise Exception("Expected transition to be a displayable, not a %r" % trans)
if isinstance(trans, renpy.display.transform.Transform) and isinstance(old_trans, renpy.display.transform.Transform):
trans.take_state(old_trans)
trans.take_execution_state(old_trans)
self.instantiated_transition[layer] = trans
return trans
def add_layer(where, layer):
scene_layer = scene[layer]
@@ -3799,12 +3859,7 @@ class Interface(object):
if (self.ongoing_transition.get(layer, None) and
not suppress_transition):
trans = self.ongoing_transition[layer](
old_widget=self.transition_from[layer],
new_widget=scene_layer)
if not isinstance(trans, Displayable):
raise Exception("Expected transition to be a displayable, not a %r" % trans)
trans = instantiate_transition(layer, self.transition_from[layer], scene_layer)
transition_time = self.transition_time.get(layer, None)
@@ -3835,9 +3890,7 @@ class Interface(object):
old_root.layers[layer] = d
old_root.add(d)
trans = self.ongoing_transition[None](
old_widget=old_root,
new_widget=layers_root)
trans = instantiate_transition(None, old_root, layers_root)
if not isinstance(trans, Displayable):
raise Exception("Expected transition to be a displayable, not a %r" % trans)
@@ -3896,23 +3949,29 @@ class Interface(object):
# caused by an exception propagating through this function.
try:
# Insert layers into Layer displayables.
def per_interact(i):
if isinstance(i, renpy.display.layout.Layer):
if i.layer in scene:
i.layers = layer_transitions
add_layer(i, i.layer)
del i.layers
else:
i.add(null)
# Call per-interaction code for all displayables.
def take_layer_displayable(ld):
"""
This is called by a layer displayable to add the layer's
contents to the layer displayable.
"""
i.per_interact()
if ld.layer in scene:
ld.layers = layer_transitions
add_layer(ld, ld.layer)
del ld.layers
else:
ld.add(null)
self.take_layer_displayable = take_layer_displayable
# Call per-interaction code for all widgets.
renpy.display.behavior.input_pre_per_interact()
root_widget.visit_all(per_interact)
root_widget.visit_all(lambda d : d.per_interact())
renpy.display.behavior.input_post_per_interact()
self.take_layer_displayable = None
# Consolidate static and layer transitions for later processing.
if layer_transitions:
layer_transitions.update(layers_root.layers)
+1 -1
View File
@@ -471,8 +471,8 @@ class Drag(renpy.display.core.Displayable, renpy.revertable.RevertableObject):
Changes the child of this drag to `d`.
"""
d.per_interact()
self.child = renpy.easy.displayable(d)
self.child.per_interact()
renpy.display.render.invalidate(self)
def top(self):
+29 -8
View File
@@ -71,7 +71,15 @@ class CacheEntry(object):
rv += self.width * self.height
if self.texture is not None:
rv += self.bounds[2] * self.bounds[3]
has_mipmaps = getattr(self.texture, "has_mipmaps", None)
if has_mipmaps and has_mipmaps():
mipmap_multiplier = 1.34
else:
mipmap_multiplier = 1.0
rv += int(self.bounds[2] * self.bounds[3] * mipmap_multiplier)
return rv
@@ -376,14 +384,9 @@ class Cache(object):
pass
if not predict:
if render:
return make_render(ce)
else:
rv = ce.texture
rv = ce.texture
else:
rv = None
else:
rv = ce.surf
@@ -394,11 +397,14 @@ class Cache(object):
ce.surf = None
if texture and render and not predict:
return make_render(ce)
if (ce.surf is None) and (ce.texture is None):
with self.lock:
self.kill(ce)
# Done... return the surface.
# Done. Return the surface or texture.
return rv
# This kills off a given cache entry.
@@ -2063,6 +2069,21 @@ def load_surface(im):
return cache.get(image(im))
def load_rgba(data, size):
"""
:name: renpy.load_rgba
:doc: udd_utility
Loads the image data `bytes` into a texture of size `size`, and return it.
`data`
Should be a bytes object containing the image data in RGBA8888 order.
"""
surf = renpy.display.pgrender.surface(size, True)
surf.from_data(data)
return renpy.display.draw.load_texture(surf)
def reset_module():
print("Resetting cache.")
+16 -13
View File
@@ -484,7 +484,7 @@ class ImageReference(renpy.display.core.Displayable):
def _handles_event(self, event):
if self.target is None:
return False
self.find_target()
return self.target._handles_event(event)
@@ -745,7 +745,7 @@ class DynamicImage(renpy.display.core.Displayable):
def _handles_event(self, event):
if self.target is None:
return False
self.find_target()
return self.target._handles_event(event)
@@ -1006,7 +1006,8 @@ class ShownImageInfo(renpy.object.Object):
if ca:
ca_required = [ i for i in required if i not in attrs ]
ca_optional = [ i for i in optional if i not in attrs ]
ca_optional = [ i for i in optional if i not in attrs if i not in required ]
newattrs = ca(tag, ca_required, ca_optional)
if newattrs is None:
@@ -1014,19 +1015,21 @@ class ShownImageInfo(renpy.object.Object):
attrs = attrs + newattrs
num_required = 0
else:
for i in attrs:
if i in required:
num_required += 1
num_required = 0
for i in attrs:
if i in required:
num_required += 1
continue
# We don't have any not-found attributes. But we might not
# have all of the attributes.
if num_required != len(required):
continue
# We don't have any not-found attributes. But we might not
# have all of the attributes.
if num_required != len(required):
continue
len_attrs = len(set(attrs))
if len_attrs < max_len:
+2 -2
View File
@@ -580,8 +580,8 @@ class Frame(renpy.display.core.Displayable):
return rv
def visit(self):
rv = [ ]
self.style._visit_frame(rv)
rv = [ self.image ]
self.style._visit_frame(rv.append)
return rv
+6
View File
@@ -2469,3 +2469,9 @@ class Layer(AdjustTimes):
seen.add(id_d)
d.visit_all(callback, seen)
def per_interact(self):
if renpy.game.interface and renpy.game.interface.take_layer_displayable:
renpy.game.interface.take_layer_displayable(self)
super(Layer, self).per_interact()
+1 -1
View File
@@ -247,7 +247,7 @@ def bilinear_scale(src, dst, sx=0, sy=0, sw=None, sh=None, dx=0, dy=0, dw=None,
_renpy.bilinear(src, dst, sx, sy, sw, sh, dx, dy, dw, dh, precise=precise)
transform = _renpy.transform
self = _renpy.transform
# Note: Blend requires all surfaces to be the same size.
blend = _renpy.blend
+197
View File
@@ -0,0 +1,197 @@
#cython: profile=False
# Copyright 2004-2023 Tom Rothamel <pytom@bishoujo.us>
#
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation files
# (the "Software"), to deal in the Software without restriction,
# including without limitation the rights to use, copy, modify, merge,
# publish, distribute, sublicense, and/or sell copies of the Software,
# and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
from __future__ import print_function
from libc.math cimport sin, cos, atan2, asin, acos
DEF pi = 3.14159265358979323846
cdef double radians(double degrees):
return degrees * pi / 180.0
cdef double degrees(double radians):
return radians * 180.0 / pi
def euler_slerp(double complete, old, new):
"""
Use quaternions to interpolate between two euler angles.
"""
cdef double coeff1
cdef double coeff2
cdef double cosx_cosp
cdef double cosz_cosp1
cdef double cosz_cosp2
cdef double cx
cdef double cy
cdef double cz
cdef double dot
cdef double new_q_w
cdef double new_q_x
cdef double new_q_y
cdef double new_q_z
cdef double new_x
cdef double new_x_div_2
cdef double new_y
cdef double new_y_div_2
cdef double new_z
cdef double new_z_div_2
cdef double old_q_mul_new_q
cdef double old_q_w
cdef double old_q_x
cdef double old_q_y
cdef double old_q_z
cdef double old_x
cdef double old_x_div_2
cdef double old_y
cdef double old_y_div_2
cdef double old_z
cdef double old_z_div_2
cdef double q_w
cdef double q_x
cdef double q_y
cdef double q_z
cdef double sinx_cosp
cdef double siny
cdef double sinz_cosp1
cdef double sinz_cosp2
cdef double sout
cdef double st
cdef double sut
cdef double sx
cdef double sy
cdef double sz
cdef double theta
cdef double x
cdef double y
cdef double z
if old == new:
return new
#select the shorten root
old_x, old_y, old_z = old
old_x = old_x % 360
old_y = old_y % 360
old_z = old_z % 360
new_x, new_y, new_z = new
new_x = new_x % 360
new_y = new_y % 360
new_z = new_z % 360
if new_x - old_x > 180:
new_x = new_x - 360
if new_y - old_y > 180:
new_y = new_y - 360
if new_z - old_z > 180:
new_z = new_z - 360
#z-y-x Euler angles to quaternion conversion
#https://en.wikipedia.org/wiki/Conversion_between_quaternions_and_Euler_angles
old_x_div_2 = radians(old_x) * 0.5
old_y_div_2 = radians(old_y) * 0.5
old_z_div_2 = radians(old_z) * 0.5
cx = cos(old_x_div_2)
sx = sin(old_x_div_2)
cy = cos(old_y_div_2)
sy = sin(old_y_div_2)
cz = cos(old_z_div_2)
sz = sin(old_z_div_2)
old_q_x = sx * cy * cz - cx * sy * sz
old_q_y = cx * sy * cz + sx * cy * sz
old_q_z = cx * cy * sz - sx * sy * cz
old_q_w = cx * cy * cz + sx * sy * sz
new_x_div_2 = radians(new_x) * 0.5
new_y_div_2 = radians(new_y) * 0.5
new_z_div_2 = radians(new_z) * 0.5
cx = cos(new_x_div_2)
sx = sin(new_x_div_2)
cy = cos(new_y_div_2)
sy = sin(new_y_div_2)
cz = cos(new_z_div_2)
sz = sin(new_z_div_2)
new_q_x = sx * cy * cz - cx * sy * sz
new_q_y = cx * sy * cz + sx * cy * sz
new_q_z = cx * cy * sz - sx * sy * cz
new_q_w = cx * cy * cz + sx * sy * sz
#calculate new quaternion between old and new.
old_q_mul_new_q = (old_q_x * new_q_x + old_q_y * new_q_y + old_q_z * new_q_z + old_q_w * new_q_w)
dot = old_q_mul_new_q
if dot > 1.0:
dot = 1.0
elif dot < -1.0:
dot = -1.0
theta = abs(acos(dot))
st = sin(theta)
sut = sin(theta * complete)
sout = sin(theta * (1 - complete))
coeff1 = sout / st
coeff2 = sut / st
q_x = coeff1 * old_q_x + coeff2 * new_q_x
q_y = coeff1 * old_q_y + coeff2 * new_q_y
q_z = coeff1 * old_q_z + coeff2 * new_q_z
q_w = coeff1 * old_q_w + coeff2 * new_q_w
#Quaternion to z-y-x Euler angles conversion
#https://en.wikipedia.org/wiki/Conversion_between_quaternions_and_Euler_angles
sinx_cosp = 2 * (q_w * q_x + q_y * q_z)
cosx_cosp = 1 - 2 * (q_x * q_x + q_y * q_y)
siny = 2 * (q_w * q_y - q_z * q_x)
sinz_cosp1 = 2 * (q_x * q_y - q_w * q_z)
cosz_cosp1 = 1 - 2 * (q_x * q_x + q_z * q_z)
sinz_cosp2 = 2 * (q_w * q_z + q_x * q_y)
cosz_cosp2 = 1 - 2 * (q_y * q_y + q_z * q_z)
if siny >= 1:
x = 0
y = pi/2
z = atan2(sinz_cosp1, cosz_cosp1)
elif siny <= -1:
x = 0
y = -pi/2
z = atan2(sinz_cosp1, cosz_cosp1)
else:
x = atan2(sinx_cosp, cosx_cosp)
if siny > 1.0:
siny = 1.0
elif siny < -1.0:
siny = -1.0
y = asin(siny)
z = atan2(sinz_cosp2, cosz_cosp2)
x = degrees(x) % 360
y = degrees(y) % 360
z = degrees(z) % 360
return (x, y, z)
+1 -4
View File
@@ -414,10 +414,7 @@ def redraw_time():
def redraw(d, when):
"""
:doc: udd_utility
Causes the displayable `d` to be redrawn after `when` seconds have
elapsed.
Documented in cdd.rst.
"""
if not renpy.game.interface:
+3
View File
@@ -1002,6 +1002,9 @@ def prepare_screens():
s.ast.unprepare_screen()
s.ast.prepare_screen()
# Compile ATL in screens.
renpy.atl.compile_all()
prepared = True
finally:
+1 -1
View File
@@ -558,7 +558,7 @@ def draw_transformed(dest, clip, what, xo, yo, alpha, forward, reverse):
dest = dest.subsurface((minx, miny, maxx - minx, maxy - miny))
renpy.display.module.transform(
renpy.display.module.self(
what, dest,
cx, cy,
forward.xdx, forward.ydx,
+39 -5
View File
@@ -31,9 +31,25 @@ import types # @UnresolvedImport
import renpy
from renpy.display.layout import Container
from renpy.display.accelerator import transform_render
from renpy.display.accelerator import RenderTransform
from renpy.atl import position, any_object, bool_or_none, float_or_none, matrix, mesh
class Camera(renpy.object.Object):
"""
:doc: point_to_camera
Instances of this class can be used with point_to to point
at the location of the camera for a particular layer.
`layer`
The name of the layer.
"""
def __init__(self, layer="master"):
self.layer = layer
# The null object that's used if we don't have a defined child.
null = None
@@ -433,14 +449,15 @@ class Transform(Container):
focus=None,
default=False,
_args=None,
alt=None,
**kwargs):
properties = {k: kwargs.pop(k) for k in style_properties if k in kwargs}
self.kwargs = kwargs
self.style_arg = style
super(Transform, self).__init__(style=style, focus=focus, default=default, _args=_args, alt=alt)
super(Transform, self).__init__(style=style, focus=focus, default=default, _args=_args, **properties)
self.function = function
@@ -750,7 +767,21 @@ class Transform(Container):
# The render method is now defined in accelerator.pyx.
def render(self, width, height, st, at):
return transform_render(self, width, height, st, at)
# Prevent time from ticking backwards, as can happen if we replace a
# transform but keep its state.
if st + self.st_offset <= self.st:
self.st_offset = self.st - st
if at + self.at_offset <= self.at:
self.at_offset = self.at - at
self.st = st = st + self.st_offset
self.at = at = at + self.at_offset
# Update the state.
self.update_state()
return RenderTransform(self).render(width, height, st, at)
def event(self, ev, x, y, st):
@@ -947,6 +978,9 @@ class ATLTransform(renpy.atl.ATLTransformBase, Transform):
return repr((self.child, self.atl.loc))
# Names of style properties that should be sent to the parent.
style_properties = {'alt'}
# Names of transform properties, and if the property should be handled with
# diff2 or diff4.
all_properties = set()
@@ -1025,7 +1059,7 @@ add_property("nearest", bool_or_none, None)
add_property("perspective", any_object, None)
add_property("rotate", float, None)
add_property("rotate_pad", bool, True)
add_property("poi", (float, float, float), None)
add_property("point_to", any_object, None)
add_property("orientation", (float, float, float), None)
add_property("xrotate", float, None)
add_property("yrotate", float, None)
+5
View File
@@ -639,6 +639,11 @@ class AlphaDissolve(Transition):
super(AlphaDissolve, self).__init__(delay, **properties)
self.control = renpy.display.layout.Fixed()
control = renpy.easy.displayable(control)
if control._duplicatable:
control = control._duplicate(self._args)
self.control.add(control)
self.old_widget = renpy.easy.displayable(old_widget)
+18 -6
View File
@@ -110,19 +110,29 @@ def default_tts_function(s):
fsencode = renpy.exports.fsencode
amplitude = renpy.game.preferences.get_mixer("voice")
amplitude_100 = int(amplitude * 100)
if "RENPY_TTS_COMMAND" in os.environ:
process = subprocess.Popen([ os.environ["RENPY_TTS_COMMAND"], fsencode(s) ])
elif renpy.linux:
if renpy.config.tts_voice is None:
process = subprocess.Popen([ "espeak", fsencode(s) ])
else:
process = subprocess.Popen([ "espeak", "-v", fsencode(renpy.config.tts_voice), fsencode(s) ])
cmd = [ "espeak", "-a", fsencode(str(amplitude_100)) ]
if renpy.config.tts_voice is not None:
cmd.extend([ "-v", fsencode(renpy.config.tts_voice) ])
cmd.append(fsencode(s))
process = subprocess.Popen(cmd)
elif renpy.macintosh:
s = "[[volm {}]]".format(amplitude) + s
if renpy.config.tts_voice is None:
process = subprocess.Popen([ "say", fsencode(s) ])
else:
@@ -137,13 +147,13 @@ def default_tts_function(s):
say_vbs = os.path.join(os.path.dirname(sys.executable), "say.vbs")
s = s.replace('"', "")
process = subprocess.Popen([ "wscript", fsencode(say_vbs), fsencode(s), fsencode(voice) ])
process = subprocess.Popen([ "wscript", fsencode(say_vbs), fsencode(s), fsencode(voice), fsencode(str(amplitude_100)) ])
elif renpy.emscripten and renpy.config.webaudio:
try:
from renpy.audio.webaudio import call
call("tts", s)
call("tts", s, amplitude)
except Exception:
pass
@@ -254,6 +264,8 @@ def displayable(d):
else:
prefix = renpy.translation.translate_string("Self-voicing enabled. ")
last_raw = None
for i in renpy.config.tts_voice_channels:
if not prefix and renpy.audio.music.get_playing(i):
return
+52 -9
View File
@@ -100,6 +100,9 @@ fullscreen = False
# place.
reset_channels = set()
# These store the textures for movies in the same group.
group_texture = { }
def early_interact():
"""
@@ -246,9 +249,15 @@ def get_movie_texture_web(channel, mask_channel, side_mask, mipmap):
return tex, new
def render_movie(channel, width, height):
def render_movie(channel, width, height, group=None):
tex, _new = get_movie_texture(channel)
if group is not None:
if tex is None:
tex = group_texture.get(group, None)
else:
group_texture[group] = tex
if tex is None:
return None
@@ -370,6 +379,13 @@ class Movie(renpy.display.core.Displayable):
If False, the movie will not loop. If `image` is defined, the image
will be displayed when the movie ends. Otherwise, the displayable will
become transparent.
`group`
If not None, this should be a string. If given, and if the movie has not
yet started playing, and another movie in the same group has played in
the previous frame, the last frame from that movie will be used for
this movie. This can prevent flashes of transparency when switching
between two movies.
"""
fullscreen = False
@@ -387,6 +403,7 @@ class Movie(renpy.display.core.Displayable):
play_callback = None
loop = True
group = None
def any_loadable(self, name):
@@ -417,7 +434,6 @@ class Movie(renpy.display.core.Displayable):
if self.mask_channel is not None:
self.mask_channel = self.channel + "_mask"
def ensure_channel(self, name):
if name is None:
@@ -433,7 +449,7 @@ class Movie(renpy.display.core.Displayable):
renpy.audio.music.register_channel(name, renpy.config.movie_mixer, loop=True, stop_on_mute=False, movie=True, framedrop=framedrop, force=True)
def __init__(self, fps=24, size=None, channel="movie", play=None, mask=None, mask_channel=None, image=None, play_callback=None, side_mask=False, loop=True, start_image=None, **properties):
def __init__(self, fps=24, size=None, channel="movie", play=None, mask=None, mask_channel=None, image=None, play_callback=None, side_mask=False, loop=True, start_image=None, group=None, **properties):
global movie_channel_serial
@@ -475,16 +491,23 @@ class Movie(renpy.display.core.Displayable):
self.play_callback = play_callback
self.group = group
if (self.channel == "movie") and (renpy.config.hw_video) and renpy.mobile:
raise Exception("Movie(channel='movie') doesn't work on mobile when config.hw_video is true. (Use a different channel argument.)")
def _handles_event(self, event):
return event == "show"
def set_transform_event(self, event):
if event == "show":
reset_channels.add(self.channel)
def render(self, width, height, st, at):
if self._play and not (renpy.game.preferences.video_image_fallback is True):
channel_movie[self.channel] = self
if st == 0:
reset_channels.add(self.channel)
if channel_movie.get(self.channel, None) is not self:
channel_movie[self.channel] = self
playing = renpy.audio.music.get_playing(self.channel)
@@ -493,6 +516,9 @@ class Movie(renpy.display.core.Displayable):
if self.channel in reset_channels:
not_playing = False
if self.group is not None and self.group in group_texture:
not_playing = False
if (self.image is not None) and not_playing:
surf = renpy.display.render.render(self.image, width, height, st, at)
w, h = surf.get_size()
@@ -505,6 +531,12 @@ class Movie(renpy.display.core.Displayable):
tex, _ = get_movie_texture(self.channel, self.mask_channel, self.side_mask, self.style.mipmap)
if self.group is not None:
if tex is None:
tex = group_texture.get(self.group, None)
else:
group_texture[self.group] = tex
if (not not_playing) and (tex is not None):
width, height = tex.get_size()
@@ -527,7 +559,7 @@ class Movie(renpy.display.core.Displayable):
if not playing:
rv = None
else:
rv = render_movie(self.channel, w, h)
rv = render_movie(self.channel, w, h, group=self.group)
if rv is None:
rv = renpy.display.render.Render(w, h)
@@ -613,7 +645,6 @@ def update_playing():
renpy.game.context().movie = dict(channel_movie)
reset_channels.clear()
def frequent():
"""
Called to update the video playback. Returns true if a video refresh is
@@ -624,6 +655,18 @@ def frequent():
renpy.audio.audio.advance_time()
# Cycle the group textures.
global group_texture
old_group_texture = group_texture
group_texture = { }
for movies in displayable_channels.values():
for m in movies:
if m.group is not None:
group_texture[m.group] = old_group_texture.get(m.group, None)
# Determine if we need to redraw.
if displayable_channels:
update = True
+10 -4
View File
@@ -75,7 +75,7 @@ from renpy.display.image import image_exists, image_exists as has_image, list_im
from renpy.display.image import get_available_image_tags, get_available_image_attributes, check_image_attributes, get_ordered_image_attributes
from renpy.display.image import get_registered_image
from renpy.display.im import load_surface, load_image
from renpy.display.im import load_surface, load_image, load_rgba
from renpy.curry import curry, partial
from renpy.display.video import movie_start_fullscreen, movie_start_displayable, movie_stop
@@ -835,6 +835,9 @@ def web_input(prompt, default='', allow=None, exclude='{}', length=None, mask=Fa
renpy.exports.mode('input')
# Take the user out of fullscreen during input.
renpy.game.preferences.fullscreen = False
prompt = renpy.text.extras.filter_text_tags(prompt, allow=set())
roll_forward = renpy.exports.roll_forward_info()
@@ -864,7 +867,7 @@ def web_input(prompt, default='', allow=None, exclude='{}', length=None, mask=Fa
return rv
def input(prompt, default='', allow=None, exclude='{}', length=None, with_none=None, pixel_width=None, screen="input", mask=None, copypaste=True, **kwargs): # @ReservedAssignment
def input(prompt, default='', allow=None, exclude='{}', length=None, with_none=None, pixel_width=None, screen="input", mask=None, copypaste=True, multiline=False, **kwargs): # @ReservedAssignment
"""
:doc: input
@@ -904,6 +907,9 @@ def input(prompt, default='', allow=None, exclude='{}', length=None, with_none=N
`copypaste`
When true, copying from and pasting to this input is allowed.
`multiline`
When true, move caret to next line is allowed.
If :var:`config.disable_input` is True, this function only returns
`default`.
@@ -940,7 +946,7 @@ def input(prompt, default='', allow=None, exclude='{}', length=None, with_none=N
if has_screen(screen):
widget_properties = { }
widget_properties["input"] = dict(default=default, length=length, allow=allow, exclude=exclude, editable=not fixed, pixel_width=pixel_width, mask=mask, copypaste=copypaste)
widget_properties["input"] = dict(default=default, length=length, allow=allow, exclude=exclude, editable=not fixed, pixel_width=pixel_width, mask=mask, copypaste=copypaste, multiline=multiline)
show_screen(screen, _transient=True, _widget_properties=widget_properties, prompt=prompt, **show_properties)
@@ -1447,7 +1453,7 @@ def say(who, what, *args, **kwargs):
e "Hello, world."
$ renpy.say(e, "Hello, world.")
$ e("Hello, world.")
$ e("Hello, world.") # when e is not a string
"""
if renpy.config.old_substitutions:
+10
View File
@@ -47,6 +47,7 @@ cdef class GL2Draw:
cdef tuple default_clip
cdef float dpi_scale
cdef object shader_cache
cdef bint ever_set_position
cdef public tuple clip_rtt_box
@@ -73,6 +74,15 @@ cdef class GL2Draw:
# The framebuffer object used for offscreen rendering.
cdef GLuint fbo
# The color texture object used for pixel tests (1x1).
cdef GLuint color_renderbuffer_1px
# The depth renderbuffer object used for pixel tests. (1x1)
cdef GLuint depth_renderbuffer_1px
# The framebuffer object used for pixel tests (1x1)
cdef GLuint fbo_1px
# The texture_loader singleton.
cdef public TextureLoader texture_loader
+97 -15
View File
@@ -76,6 +76,9 @@ vsync = True
# A list of frame end times, used for the same purpose.
frame_times = [ ]
# The default position of the window.
default_position = (pygame.WINDOWPOS_CENTERED, pygame.WINDOWPOS_CENTERED)
cdef class GL2Draw:
def __init__(self, name):
@@ -121,6 +124,9 @@ cdef class GL2Draw:
# The shader cache,
self.shader_cache = None
# Has the position of this window ever been set?
self.ever_set_position = False
def get_texture_size(self):
"""
Returns the amount of memory locked up in textures.
@@ -142,7 +148,7 @@ cdef class GL2Draw:
if old_surface is not None:
maximized = old_surface.get_flags() & pygame.WINDOW_MAXIMIZED
else:
maximized = False
maximized = renpy.game.preferences.maximized
# Information about the virtual size.
vwidth, vheight = self.virtual_size
@@ -370,6 +376,10 @@ cdef class GL2Draw:
self.window = None
if self.window is None:
if renpy.game.preferences.maximized:
window_flags |= pygame.WINDOW_MAXIMIZED
try:
renpy.display.log.write("Windowed mode.")
self.window = pygame.display.set_mode((pwidth, pheight), window_flags)
@@ -440,22 +450,35 @@ cdef class GL2Draw:
# Are we in fullscreen mode?
fullscreen = bool(pygame.display.get_window().get_window_flags() & (pygame.WINDOW_FULLSCREEN_DESKTOP | pygame.WINDOW_FULLSCREEN))
# Are we maximized?
maximized = bool(pygame.display.get_window().get_window_flags() & pygame.WINDOW_MAXIMIZED)
# See if we've ever set the screen position, and if not, center the window.
if not fullscreen and not maximized:
if not self.ever_set_position:
self.ever_set_position = True
pygame.display.get_window().set_position(default_position)
# Get the size of the created screen.
pwidth, pheight = renpy.display.core.get_size()
renpy.game.preferences.fullscreen = fullscreen
renpy.game.interface.fullscreen = fullscreen
vwidth, vheight = self.virtual_size
self.physical_size = (pwidth, pheight)
self.drawable_size = pygame.display.get_drawable_size()
if not fullscreen:
renpy.game.preferences.physical_size = self.get_physical_size()
renpy.display.log.write("Screen sizes: virtual=%r physical=%r drawable=%r" % (self.virtual_size, self.physical_size, self.drawable_size))
# Update the preferences.
renpy.game.preferences.fullscreen = fullscreen
renpy.game.interface.fullscreen = fullscreen
if not fullscreen:
renpy.game.preferences.maximized = maximized
if not fullscreen and not maximized:
renpy.game.preferences.physical_size = self.get_physical_size()
if renpy.config.adjust_view_size is not None:
view_width, view_height = renpy.config.adjust_view_size(pwidth, pheight)
else:
@@ -542,8 +565,12 @@ cdef class GL2Draw:
width = max(width, 256)
height = max(height, 256)
pygame.display.get_window().restore()
pygame.display.get_window().resize((width, height), opengl=True, fullscreen=fullscreen)
if pygame.display.get_window().get_window_flags() & pygame.WINDOW_FULLSCREEN:
maximized = renpy.game.preferences.maximized
else:
maximized = False
pygame.display.get_window().resize((width, height), opengl=True, fullscreen=fullscreen, maximized=maximized)
def update(self, force=False):
"""
@@ -568,6 +595,18 @@ cdef class GL2Draw:
Called when terminating the use of the OpenGL context.
"""
global default_position
# Are we in fullscreen mode?
fullscreen = bool(pygame.display.get_window().get_window_flags() & (pygame.WINDOW_FULLSCREEN_DESKTOP | pygame.WINDOW_FULLSCREEN))
# Are we maximized?
maximized = bool(pygame.display.get_window().get_window_flags() & pygame.WINDOW_MAXIMIZED)
# See if we've ever set the screen position, and if not, center the window.
if not fullscreen and not maximized:
default_position = pygame.display.get_position()
self.kill_textures()
if self.texture_loader is not None:
@@ -611,11 +650,14 @@ cdef class GL2Draw:
# Generate the framebuffer.
glGenFramebuffers(1, &self.fbo)
glGenFramebuffers(1, &self.fbo_1px)
glGenRenderbuffers(1, &self.color_renderbuffer)
glGenRenderbuffers(1, &self.color_renderbuffer_1px)
if renpy.config.depth_size:
glGenRenderbuffers(1, &self.depth_renderbuffer)
glGenRenderbuffers(1, &self.depth_renderbuffer_1px)
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max_texture_size)
glGetIntegerv(GL_MAX_RENDERBUFFER_SIZE, &max_renderbuffer_size)
@@ -642,6 +684,7 @@ cdef class GL2Draw:
self.texture_loader.max_texture_width = width
self.texture_loader.max_texture_height = height
# Full-size fbo.
self.change_fbo(self.fbo)
glBindRenderbuffer(GL_RENDERBUFFER, self.color_renderbuffer)
@@ -672,6 +715,37 @@ cdef class GL2Draw:
GL_RENDERBUFFER,
self.depth_renderbuffer)
# 1px fbo.
self.change_fbo(self.fbo_1px)
glBindRenderbuffer(GL_RENDERBUFFER, self.color_renderbuffer_1px)
glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, 1, 1)
glFramebufferRenderbuffer(
GL_FRAMEBUFFER,
GL_COLOR_ATTACHMENT0,
GL_RENDERBUFFER,
self.color_renderbuffer_1px)
if renpy.config.depth_size:
glBindRenderbuffer(GL_RENDERBUFFER, self.depth_renderbuffer_1px)
if self.gles:
if renpy.config.depth_size >= 24:
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, 1, 1)
else:
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, 1, 1)
else:
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, 1, 1)
glFramebufferRenderbuffer(
GL_FRAMEBUFFER,
GL_DEPTH_ATTACHMENT,
GL_RENDERBUFFER,
self.depth_renderbuffer_1px)
glBindRenderbuffer(GL_RENDERBUFFER, default_renderbuffer)
self.change_fbo(self.default_fbo)
@@ -680,12 +754,20 @@ cdef class GL2Draw:
self.change_fbo(self.default_fbo)
# Full-size fbo.
glDeleteFramebuffers(1, &self.fbo)
glDeleteRenderbuffers(1, &self.color_renderbuffer)
if renpy.config.depth_size:
glDeleteRenderbuffers(1, &self.depth_renderbuffer)
# 1px fbo.
glDeleteFramebuffers(1, &self.fbo_1px)
glDeleteRenderbuffers(1, &self.color_renderbuffer_1px)
if renpy.config.depth_size:
glDeleteRenderbuffers(1, &self.depth_renderbuffer_1px)
def can_block(self):
"""
@@ -940,7 +1022,7 @@ cdef class GL2Draw:
self.load_all_textures(what)
# Switch to the right FBO, and the right viewport.
self.change_fbo(self.fbo)
self.change_fbo(self.fbo_1px)
# Set up the viewport.
glViewport(0, 0, 1, 1)
@@ -1149,7 +1231,7 @@ cdef class GL2DrawingContext:
"""
cdef float halfwidth
cdef float halfheigh
cdef float halfheight
# This is the equivalent of projecting (0, 0, 0, 1), and getting x and y.
cdef float sx = transform.xdw
@@ -1175,9 +1257,6 @@ cdef class GL2DrawingContext:
if model.reverse is not IDENTITY:
transform = transform * model.reverse
if properties["pixel_perfect"]:
transform = self.correct_pixel_perfect(transform)
# If a clip polygon is in place, clip the mesh with it.
if clip_polygon is not None:
@@ -1271,10 +1350,13 @@ cdef class GL2DrawingContext:
has_reverse = (r.reverse is not None) and (r.reverse is not IDENTITY)
if r.properties and r.properties.get("pixel_perfect", False) and properties["pixel_perfect"] is None:
transform = self.correct_pixel_perfect(transform)
if has_reverse or r.properties:
properties = self.merge_properties(properties, r.properties)
if has_reverse and (properties["pixel_perfect"] is None):
if has_reverse:
properties["pixel_perfect"] = False
if r.shaders is not None:
+8 -8
View File
@@ -8,11 +8,11 @@ cdef class AttributeLayout:
# A map from a string giving the name of the attribute to the
# offset of the attribute.
cdef dict offset
cdef public dict offset
# The number of floats that make up the attributes for a single
# point.
cdef int stride
cdef public int stride
cdef class Mesh:
"""
@@ -21,28 +21,28 @@ cdef class Mesh:
"""
# The number of points that space has been allocated for.
cdef int allocated_points
cdef public int allocated_points
# The number of points that are in use.
cdef int points
cdef public int points
# The data corresponding to each point.
cdef float *point_data
# The number of floats corresponding to each point.
cdef int point_size
cdef public int point_size
# An AttributeLayout object controlling how attributes are stored.
cdef AttributeLayout layout
cdef public AttributeLayout layout
# The non-geometry attribute data. This is allocated_points * attribute_per_point in size.
cdef float *attribute
# The number of triangles that spaces has been allocated for.,
cdef int allocated_triangles
cdef public int allocated_triangles
# The number of triangles that are in use.
cdef int triangles
cdef public int triangles
# The triangle data, where each triangle consists of the index of three
# points. This is 3 * allocated_triangles in size.
+74
View File
@@ -32,6 +32,80 @@ TEXTURE_LAYOUT.add_attribute("a_tex_coord", 2)
cdef class Mesh:
def set_geometry_data(self, geometry):
"""
Sets the geometry data corresponding to this mesh.
`geometry`
This should be a sequence of floats, which are interpreted
as x, y for a Mesh2 or x, y, z for a Mesh3. The length of
the sequence must be a multiple of the point size of the
mesh, and must be less than or equal to the number of
allocated points.
This sets the `points` attribute of the mesh to the number of
points in the geometry.
"""
points = len(geometry) // self.point_size
if points > self.allocated_points:
raise Exception("Geometry contains too much data.")
self.points = points
cdef int i
cdef int len_geometry = len(geometry)
for 0 <= i < len_geometry:
self.point_data[i] = geometry[i]
def set_attribute_data(self, attributes):
"""
Sets the attribute data corresponding to this mesh.
`attributes`
This should be a list of floats, with the first
layout.stride floats corresponding to the first point, the
next layout.stride floats corresponding to the second point,
and so on. The length of the sequence must be a multiple of
the stride of the layout, and must be less than or equal to
the number of allocated points.
"""
cdef int i
cdef int len_attributes = len(attributes)
if len_attributes > self.allocated_points * self.layout.stride:
raise Exception("Attributes contains too much data.")
for 0 <= i < len_attributes:
self.attribute[i] = attributes[i]
def set_triangle_data(self, triangles):
"""
Sets the triangle data corresponding to this mesh.
`triangles`
This should be a list of integers, with each triple
corresponding to a triangle. The length of the sequence
must be a multiple of 3, and must be less than or equal to
the number of allocated triangles.
This sets the `triangles` attribute of the mesh to the number
of triangles given here.
"""
cdef int i
cdef int len_triangles = len(triangles)
if len_triangles > self.allocated_triangles * 3:
raise Exception("Triangles contains too much data.")
self.triangles = len_triangles // 3
for 0 <= i < len_triangles:
self.triangle[i] = triangles[i]
def get_triangles(self):
"""
Returns the triangles that make up this mesh as triples.
+1 -1
View File
@@ -105,7 +105,7 @@ class ShaderPart(object):
used.add(m.group(0))
for l in variables.split("\n"):
l = l.strip(' ;')
l = l.partition("//")[0].strip(' ;')
a = l.split()
if not a:
+1 -1
View File
@@ -37,7 +37,7 @@ cdef class TextureLoader:
# The total size (in bytes) of all the textures that have been allocated
# but not deallocated.
cdef int total_texture_size
cdef object total_texture_size
# The program used for fast texture loading
cdef Program ftl_program
+23 -7
View File
@@ -275,7 +275,6 @@ cdef class GLTexture(GL2Model):
# Update the loader.
self.loader = loader
self.loader.total_texture_size += self.width * self.height * 4
if renpy.emscripten and generate:
# Generate a texture name to access video frames for web
@@ -287,6 +286,15 @@ cdef class GLTexture(GL2Model):
0.0, 0.0, width, height,
0.0, 0.0, 1.0, 1.0,
)
self.properties = { }
def has_mipmaps(GLTexture self):
"""
Returns true if this texture has mipmaps (or will have mipmaps
when it's loaded).
"""
return self.properties.get("mipmap", True)
def get_number(GLTexture self):
return self.number if renpy.emscripten else None
@@ -311,6 +319,11 @@ cdef class GLTexture(GL2Model):
This renders `what` to this texture.
"""
self.properties = {
"mipmap" : properties.get("mipmap", True),
"pixel_perfect" : properties.get("pixel_perfect", False),
}
cw, ch = size = what.get_size()
loader = self.loader
@@ -381,9 +394,6 @@ cdef class GLTexture(GL2Model):
self.number = premultiplied
self.loader.allocated.add(self.number)
if "pixel_perfect" in properties:
self.properties = { "pixel_perfect" : properties["pixel_perfect"] }
self.loaded = True
@@ -494,8 +504,12 @@ cdef class GLTexture(GL2Model):
# Going from a single to multiple mipmap levels takes ~9ms when loading
# each mipmap, while allocating the space first reduces that to ~1ms.
glBindTexture(GL_TEXTURE_2D, tex)
if self.has_mipmaps():
self.loader.total_texture_size += int(self.width * self.height * 4 * 1.34)
else:
self.loader.total_texture_size += int(self.width * self.height * 4)
glBindTexture(GL_TEXTURE_2D, tex)
max_level = renpy.config.max_mipmap_level
@@ -525,7 +539,6 @@ cdef class GLTexture(GL2Model):
cdef GLuint level = 0
while True:
glTexImage2D(GL_TEXTURE_2D, level, GL_RGBA, tw, th, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
@@ -567,7 +580,10 @@ cdef class GLTexture(GL2Model):
if self.loaded:
self.loader.free_list.append(self.number)
self.loader.total_texture_size -= self.width * self.height * 4
if self.has_mipmaps():
self.loader.total_texture_size -= int(self.width * self.height * 4 * 1.34)
else:
self.loader.total_texture_size -= int(self.width * self.height * 4)
except TypeError:
pass # Let's not error on shutdown.
+1 -1
View File
@@ -671,7 +671,7 @@ class Live2D(renpy.display.core.Displayable):
break
# Choose all possible nonexclusive attributes.
for i in list(attributes) + list(optional):
for i in sorted(list(attributes) + list(optional)):
if i in common.nonexclusive:
rv.append(i)
+14 -21
View File
@@ -178,6 +178,10 @@ lllword = re.compile(r'__(\w+)|\w+| +|.', re.S)
def munge_filename(fn):
# The prefix that's used when __ is found in the file.
rv = os.path.basename(fn)
if rv.endswith("_ren.py"):
rv = rv[:-7]
rv = os.path.splitext(rv)[0]
rv = rv.replace(" ", "_")
@@ -1141,42 +1145,31 @@ class Lexer(object):
return False
old_pos = self.pos
c = self.text[self.pos]
# Allow unicode, raw, and formatted strings.
for mod in ('u', 'r', 'f'):
if c != mod:
continue
self.pos += 1
# Delimiter.
start = self.match(r'[urfURF]*("""|\'\'\'|"|\')')
if self.pos == len(self.text):
self.pos = old_pos
return False
c = self.text[self.pos]
if c not in ('"', "'"):
if not start:
self.pos = old_pos
return False
delim = c
delim = start.lstrip('urfURF')
# String contents.
while True:
self.pos += 1
if self.eol():
self.error("end of line reached while parsing string.")
c = self.text[self.pos]
if c == delim:
if self.match(delim):
break
if c == '\\':
if self.match(r'\\'):
self.pos += 1
continue
self.match(r'.[^\'"\\]*')
self.pos += 1
return True
def dotted_name(self):
+60 -34
View File
@@ -40,7 +40,7 @@ import sys
import renpy
from json import dumps as json_dumps
from renpy.compat.pickle import dump, loads
from renpy.compat.pickle import PROTOCOL, dump, loads
# This is used as a quick and dirty way of versioning savegame
@@ -86,6 +86,14 @@ def save_dump(roots, log):
else:
o_repr = "<method {0}.{1}>".format(o.__self__.__class__.__name__, o.__name__)
elif isinstance(o, types.FunctionType):
if PY2:
name = o.__name__
else:
name = o.__qualname__ or o.__name__
o_repr = o.__module__ + '.' + name
elif isinstance(o, object):
o_repr = "<{0}>".format(type(o).__name__)
@@ -115,47 +123,55 @@ def save_dump(roots, log):
elif isinstance(o, types.MethodType):
size = 1 + visit(o.__self__, path + ".im_self")
elif isinstance(o, types.FunctionType):
size = 1
else:
try:
reduction = o.__reduce_ex__(2)
reduction = o.__reduce_ex__(PROTOCOL)
except Exception:
reduction = [ ]
o_repr = "BAD REDUCTION " + o_repr
o_repr_cache[ido] = "BAD REDUCTION " + o_repr
# Gets an element from the reduction, or o if we don't have
# such an element.
def get(idx, default):
if idx < len(reduction) and reduction[idx] is not None:
return reduction[idx]
else:
return default
if isinstance(reduction, basestring):
o_repr_cache[ido] = o.__module__ + '.' + reduction
size = 1
# An estimate of the size of the object, in arbitrary units. (These units are about 20-25 bytes on
# my computer.)
size = 1
state = get(2, { })
if isinstance(state, dict):
for k, v in state.items():
size += 2
size += visit(v, path + "." + k)
else:
size += visit(state, path + ".__getstate__()")
# Gets an element from the reduction, or o if we don't have
# such an element.
def get(idx, default):
if idx < len(reduction) and reduction[idx] is not None:
return reduction[idx]
else:
return default
for i, oo in enumerate(get(3, [])): # type: ignore
size += 1
size += visit(oo, "{0}[{1}]".format(path, i))
# An estimate of the size of the object, in arbitrary units.
# (These units are about 20-25 bytes on my computer.)
size = 1
for i in get(4, []): # type: ignore
state = get(2, { })
if isinstance(state, dict):
for k, v in state.items():
size += 2
size += visit(v, path + "." + k)
else:
size += visit(state, path + ".__getstate__()")
if len(i) != 2:
continue
for i, oo in enumerate(get(3, [])): # type: ignore
size += 1
size += visit(oo, "{0}[{1}]".format(path, i))
k, v = i
for i in get(4, []): # type: ignore
size += 2
size += visit(v, "{0}[{1!r}]".format(path, k))
if len(i) != 2:
continue
k, v = i
size += 2
size += visit(v, "{0}[{1!r}]".format(path, k))
f.write("{0: 7d} {1} = {2}\n".format(size, path, o_repr_cache[ido]))
@@ -452,11 +468,16 @@ def autosave_thread_function(take_screenshot):
global autosave_counter
global did_autosave
if renpy.config.autosave_prefix_callback:
prefix = renpy.config.autosave_prefix_callback()
else:
prefix = "auto-"
try:
try:
cycle_saves("auto-", renpy.config.autosave_slots)
cycle_saves(prefix, renpy.config.autosave_slots)
if renpy.config.auto_save_extra_info:
extra_info = renpy.config.auto_save_extra_info()
@@ -466,7 +487,7 @@ def autosave_thread_function(take_screenshot):
if take_screenshot:
renpy.exports.take_screenshot(background=True)
save("auto-1", mutate_flag=True, extra_info=extra_info)
save(prefix + "1", mutate_flag=True, extra_info=extra_info)
autosave_counter = 0
did_autosave = True
@@ -561,12 +582,17 @@ def force_autosave(take_screenshot=False, block=False):
else:
extra_info = ""
cycle_saves("auto-", renpy.config.autosave_slots)
if renpy.config.autosave_prefix_callback:
prefix = renpy.config.autosave_prefix_callback()
else:
prefix = "auto-"
cycle_saves(prefix, renpy.config.autosave_slots)
if take_screenshot:
renpy.exports.take_screenshot()
save("auto-1", extra_info=extra_info)
save(prefix + "1", extra_info=extra_info)
return
@@ -736,7 +762,7 @@ def slot_json(slotname):
Returns the json information for `slotname`, or None if the slot is
empty.
Much like the ``d`` argument to the :var:`config.save_json_callback`
Much like the ``d`` argument to the :var:`config.save_json_callbacks`
function, it will be returned as a dictionary. More precisely, the
dictionary will contain the same data as it did when the game was saved.
"""
+3
View File
@@ -366,6 +366,9 @@ def main():
choose_variants()
renpy.display.touch = "touch" in renpy.config.variants
if (renpy.android or renpy.ios) and not renpy.config.log_to_stdout:
print("Version:", renpy.version)
log_clock("Early init.")
# Note the game directory.
+7 -2
View File
@@ -963,6 +963,9 @@ def define_statement(l, loc):
elif l.match(r'\|='):
operator = "|="
else:
if l.match(":"):
l.match(r"[^=\n]*")
l.require('=')
operator = "="
@@ -999,6 +1002,9 @@ def default_statement(l, loc):
store = store + "." + name
name = l.require(l.word)
if l.match(":"):
l.match(r"[^=\n]*")
l.require('=')
expr = l.rest()
@@ -1357,7 +1363,6 @@ def style_statement(l, loc):
# Parse priority and name.
name = l.require(l.word)
parent = None
rv = ast.Style(loc, name)
@@ -1366,7 +1371,7 @@ def style_statement(l, loc):
def parse_clause(l):
if l.keyword("is"):
if parent is not None:
if rv.parent is not None:
l.error("parent clause appears twice.")
rv.parent = l.require(l.word) # type: ignore
+5 -1
View File
@@ -182,6 +182,9 @@ Preference("web_cache_preload", False)
# Should the voice continue to play after the user enters the game menu.
Preference("voice_after_game_menu", False)
# Should the game be maximized?
Preference("maximized", False)
class Preferences(renpy.object.Object):
"""
Stores preferences that will one day be persisted.
@@ -232,6 +235,7 @@ class Preferences(renpy.object.Object):
audio_when_unfocused = True
web_cache_preload = False
voice_after_game_menu = False
maximized = False
def init(self):
"""
@@ -319,7 +323,7 @@ class Preferences(renpy.object.Object):
return self.mute[mixer]
def init_mixers(self):
for i in renpy.audio.music.get_all_mixers() + ["main"]:
for i in renpy.audio.music.get_all_mixers() + ["main", "voice"]:
self.volumes.setdefault(i, 1.0)
self.mute.setdefault(i, False)
+37
View File
@@ -404,6 +404,33 @@ class StarredVariables(ast.NodeVisitor):
# starred assignment.
find_starred_variables = StarredVariables().find
class WrapFormattedValue(ast.NodeTransformer):
"""
This walks through the children of a FormattedValue, to look for
nodes with the __name syntax, and format those nodes.
"""
def visit_Name(self, node):
name = node.id
if not name.startswith("__"):
return node
name = name[2:]
if (not name) or ("__" in name):
return node
prefix = renpy.lexer.munge_filename(compile_filename)
name = prefix + name
return ast.Name(id=name, ctx=node.ctx, lineno=node.lineno, col_offset=node.col_offset, end_lineno=node.end_lineno, end_col_offset=node.end_col_offset)
wrap_formatted_value = WrapFormattedValue().visit
class WrapNode(ast.NodeTransformer):
@@ -696,6 +723,10 @@ class WrapNode(ast.NodeTransformer):
kwargs=None)
def visit_FormattedValue(self, n):
n = wrap_formatted_value(n)
return self.generic_visit(n)
wrap_node = WrapNode()
@@ -899,6 +930,9 @@ def quote_eval(s):
return "".join(rv[:-2])
# The filename being compiled.
compile_filename = ""
def py_compile(source, mode, filename='<none>', lineno=1, ast_node=False, cache=True, py=None):
"""
Compiles the given source code using the supplied codegenerator.
@@ -925,6 +959,8 @@ def py_compile(source, mode, filename='<none>', lineno=1, ast_node=False, cache=
that would be used.
"""
global compile_filename
if ast_node:
cache = False
@@ -974,6 +1010,7 @@ def py_compile(source, mode, filename='<none>', lineno=1, ast_node=False, cache=
source = quote_eval(source)
line_offset = lineno - 1
compile_filename = filename
try:
+5 -1
View File
@@ -751,6 +751,8 @@ class RollbackLog(renpy.object.Object):
"""
self.checkpointing_suspended = flag
self.current.not_greedy = True
renpy.game.contexts[0].force_checkpoint = True
def block(self, purge=False):
"""
@@ -759,6 +761,8 @@ class RollbackLog(renpy.object.Object):
"""
self.rollback_limit = 0
if self.current is not None:
self.current.not_greedy = True
renpy.game.context().force_checkpoint = True
if purge:
@@ -897,7 +901,7 @@ class RollbackLog(renpy.object.Object):
force_checkpoint = False
# Try to rollback to just after the previous checkpoint.
while greedy and self.log and (self.rollback_limit > 0):
while greedy and self.log:
rb = self.log[-1]
+35 -13
View File
@@ -355,6 +355,9 @@ class SLBlock(SLNode):
# RawBlock from parse or None if not present.
atl_transform = None
# The actual transform created from the atl transform.
transform = None
def __init__(self, loc):
SLNode.__init__(self, loc)
@@ -439,6 +442,9 @@ class SLBlock(SLNode):
const = self.atl_transform.constant
self.constant = min(self.constant, const)
self.transform = renpy.display.transform.ATLTransform(self.atl_transform)
renpy.atl.compile_queue.append(self.transform)
was_last_keyword = False
for i in self.children:
if i.has_keyword:
@@ -485,7 +491,18 @@ class SLBlock(SLNode):
if self.atl_transform is not None:
transform = ATLTransform(self.atl_transform, context=context.scope)
context.keywords["at"] = transform
transform.parent_transform = self.transform # type: ignore
if "at" in context.keywords:
try:
at_list = list(context.keywords["at"])
except TypeError:
at_list = [ context.keywords["at"] ]
at_list.append(transform)
context.keywords["at"] = at_list
else:
context.keywords["at"] = transform
style_prefix = context.keywords.pop("style_prefix", NotGiven)
@@ -2048,15 +2065,13 @@ class SLUse(SLNode):
ctx.old_cache = context.old_use_cache.get(use_id, None) or context.old_cache.get(self.serial, None) or { }
if use_id in ctx.old_use_cache:
ctx.updating = True
ctx.new_use_cache[use_id] = ctx.new_cache
else:
ctx.old_cache = context.old_cache.get(self.serial, None) or { }
if not isinstance(ctx.old_cache, dict):
ctx.old_cache = { }
if not isinstance(ctx.miss_cache, dict):
@@ -2076,11 +2091,15 @@ class SLUse(SLNode):
args = [ ]
kwargs = { }
if ctx.updating:
scope = ctx.old_cache.get("scope", None) or ctx.miss_cache.get("scope", None) or { }
else:
scope = { }
# Apply the arguments to the parameters (if present) or to the scope of the used screen.
if ast.parameters is not None:
new_scope = ast.parameters.apply(args, kwargs, ignore_errors=context.predicting)
scope = ctx.old_cache.get("scope", None) or ctx.miss_cache.get("scope", None) or { }
scope.update(new_scope)
else:
@@ -2088,13 +2107,14 @@ class SLUse(SLNode):
if args:
raise Exception("Screen {} does not take positional arguments. ({} given)".format(self.target, len(args)))
scope = ctx.old_cache.get("scope", None) or ctx.miss_cache.get("scope", None) or { }
scope.clear()
scope.update(context.scope)
scope.update(kwargs)
scope["_scope"] = scope
ctx.new_cache["scope"] = scope
if not ctx.predicting:
ctx.new_cache["scope"] = scope
# Run the child screen.
ctx.scope = scope
@@ -2332,9 +2352,6 @@ class SLCustomUse(SLNode):
ctx.old_cache = context.old_use_cache.get(use_id, None) or context.old_cache.get(self.serial, None) or { }
if use_id in ctx.old_use_cache:
ctx.updating = True
ctx.new_use_cache[use_id] = ctx.new_cache
else:
@@ -2348,11 +2365,15 @@ class SLCustomUse(SLNode):
ast = self.ast
if ctx.updating:
scope = ctx.old_cache.get("scope", None) or ctx.miss_cache.get("scope", None) or { }
else:
scope = { }
# Apply the arguments to the parameters (if present) or to the scope of the used screen.
if ast.parameters is not None:
new_scope = ast.parameters.apply(args, kwargs, ignore_errors=context.predicting)
scope = ctx.old_cache.get("scope", None) or ctx.miss_cache.get("scope", None) or { }
scope.update(new_scope)
else:
@@ -2360,13 +2381,14 @@ class SLCustomUse(SLNode):
if args:
raise Exception("Screen {} does not take positional arguments. ({} given)".format(self.target, len(args)))
scope = ctx.old_cache.get("scope", None) or ctx.miss_cache.get("scope", None) or { }
scope.clear()
scope.update(context.scope)
scope.update(kwargs)
scope["_scope"] = scope
ctx.new_cache["scope"] = scope
if not ctx.predicting:
ctx.new_cache["scope"] = scope
# Run the child screen.
ctx.scope = scope
+1
View File
@@ -188,6 +188,7 @@ Keyword("pixel_width")
Keyword("value")
Keyword("mask")
Keyword("caret_blink")
Keyword("multiline")
Style("caret")
add(text_properties)
+16 -8
View File
@@ -282,15 +282,11 @@ class Parser(object):
else:
l.error('%r is not a keyword argument or valid child of the %s statement.' % (name, self.name))
if name in seen_keywords:
l.error('keyword argument %r appears more than once in a %s statement.' % (name, self.name))
incomprop = check_incompatible_props(name, seen_keywords)
if incomprop:
l.deferred_error("check_conflicting_properties", 'keyword argument {!r} is incompatible with {!r}.'.format(name, incomprop))
if name == "at" and l.keyword("transform"):
seen_keywords.add(name)
if target.atl_transform is not None:
l.error("More than one 'at transform' block is given.")
if name == "at" and block and l.keyword("transform"):
l.require(":")
l.expect_eol()
l.expect_block("ATL block")
@@ -298,6 +294,17 @@ class Parser(object):
target.atl_transform = expr
return
if name in seen_keywords:
l.error('keyword argument %r appears more than once in a %s statement.' % (name, self.name))
incomprop = check_incompatible_props(name, seen_keywords)
if incomprop:
l.deferred_error("check_conflicting_properties", 'keyword argument {!r} is incompatible with {!r}.'.format(name, incomprop))
if name == "at" and target.atl_transform:
l.error("The 'at' property must occur before the 'at transform' block.")
seen_keywords.add(name)
expr = l.comma_expression()
if expr is None:
l.error("the {} keyword argument was not given a value.".format(name))
@@ -330,7 +337,8 @@ class Parser(object):
break
if l.eol():
l.expect_noblock(self.name)
if not target.atl_transform:
l.expect_noblock(self.name)
block = False
break
+11 -3
View File
@@ -646,6 +646,15 @@ def new_change_language(tl, language):
renpy.config.init_system_styles()
def clean_data():
"""
This cleans out data that's dependent on the language.
"""
renpy.store._history_list = renpy.store.list() # type: ignore
renpy.store.nvl_list = renpy.store.list() # type: ignore
renpy.game.log.forward = [ ]
def change_language(language, force=False):
"""
@@ -658,8 +667,7 @@ def change_language(language, force=False):
global old_language
if old_language != language:
renpy.store._history_list = renpy.store.list() # type: ignore
renpy.store.nvl_list = renpy.store.list() # type: ignore
clean_data()
renpy.game.preferences.language = language
if old_language == language and not force:
@@ -728,7 +736,7 @@ def check_language():
# has already set translate_identifier to None.
ctx.translate_identifier = tid
renpy.game.log.forward = [ ]
clean_data()
raise renpy.game.JumpException(node.name)
+1 -1
View File
@@ -470,7 +470,7 @@ def translate_command():
ap.add_argument("--empty", help="Produce empty strings while generating translations.", dest="empty", action="store_true")
ap.add_argument("--count", help="Instead of generating files, print a count of missing translations.", dest="count", action="store_true")
ap.add_argument("--min-priority", help="Translate strings with more than this priority.", dest="min_priority", default=0, type=int)
ap.add_argument("--max-priority", help="Translate strings with more than this priority.", dest="max_priority", default=0, type=int)
ap.add_argument("--max-priority", help="Translate strings with less than this priority.", dest="max_priority", default=0, type=int)
ap.add_argument("--strings-only", help="Only translate strings (not dialogue).", dest="strings_only", default=False, action="store_true")
ap.add_argument("--common-only", help="Only translate string from the common code.", dest="common_only", default=False, action="store_true")
ap.add_argument("--no-todo", help="Do not include the TODO flag.", dest="todo", default=True, action="store_false")
+7 -3
View File
@@ -177,6 +177,7 @@ def enqueue(relpath, rtype, data):
global queue
with queue_lock:
voice_count = 0
for rr in queue:
# de-dup same .data/image_filename
# don't de-dup same .relpath (different .data == different cache entry)
@@ -189,9 +190,12 @@ def enqueue(relpath, rtype, data):
elif rr.rtype == rtype == 'voice':
if rr.relpath == relpath:
return
if len([True for rr in queue if rr.type == 'voice']) > renpy.config.predict_statements:
# don't stack skipped dialogs
return
voice_count += 1
if voice_count > renpy.config.predict_statements:
# don't stack skipped dialogs
return
queue.append(ReloadRequest(relpath, rtype, data))
+21 -6
View File
@@ -311,20 +311,35 @@ The following transform properties are used by the 3D Stage.
variables are floats, this is relative to the size of the child, otherwise
it's absolute pixels.
This sets the location of the (0, 0, 0) point that poi, orientation,
xrotate, yrotate, zrotate and matrixtransform applie its transform to.
This sets the location of the (0, 0, 0) point that point_to, orientation,
xrotate, yrotate, zrotate and matrixtransform apply their transforms to.
.. transform-property:: poi
.. transform-property:: point_to
:type: (float, float, float) or None
:type: (float, float, float), Camera, or None
:default: None
This gives the position of the point of interest. The camera or the
displayable being transformed are rotated to face the point of interest,
This gives a position to which to point. The camera or the
displayable being transformed are rotated to face that point,
even if the position of the camera or the displayable is changed.
If this is None, no point-of-interest rotation is applied.
If this is not None, this is a 3-tuple or an instance of :func:`Camera`.
An (x, y, z) format tuple represents the position of the point of interest.
An instance of Camera means to point at the camera.
Note point_to isn't updated automatically. so, you should write like below if
you want it is updated::
# eileen always faces to the camera.
show eileen happy at center:
point_to Camera()
0
repeat
.. include:: inc/point_to_camera
.. transform-property:: orientation
:type: (float, float, float) or None
+2 -2
View File
@@ -1148,7 +1148,7 @@ both horizontal and vertical positions.
There are also several sets of transform properties that are documented elsewhere:
3D Stage properties:
:tpref:`perspective`, :tpref:`poi`, :tpref:`orientation`, :tpref:`xrotate`, :tpref:`yrotate`, :tpref:`zrotate`, :tpref:`matrixanchor`, :tpref:`matrixtransform`, :tpref:`zpos`, :tpref:`zzoom`
:tpref:`perspective`, :tpref:`point_to`, :tpref:`orientation`, :tpref:`xrotate`, :tpref:`yrotate`, :tpref:`zrotate`, :tpref:`matrixanchor`, :tpref:`matrixtransform`, :tpref:`zpos`, :tpref:`zzoom`
Model-based rendering properties:
:tpref:`blend`, :tpref:`mesh`, :tpref:`mesh_pad`, :tpref:`shader`
@@ -1167,7 +1167,7 @@ These properties are applied in the following order:
#. crop, corner1, corner2
#. xysize, size, maxsize
#. zoom, xzoom, yzoom
#. poi
#. point_to
#. orientation
#. xrotate, yrotate, zrotate
#. rotate
+188
View File
@@ -0,0 +1,188 @@
Speech Bubbles
==============
Ren'Py supports dialogue that's displayed in speech bubbles, which can be
interactively positioned on the screen. This provides an alternative to
the textboxes used by ADV-style games, and the full screen dialogue used
by NVL-mode.
To use speech bubbles your game, you'll have to define Characters with
an image tag, a kind of ``bubble``. For example, ::
define e = Character(None, image="eileen", kind=bubble) # Eileen
define l = Character(None, image="lucy", kind=bubble) # Lucy
While a name is supported, in general the speaking character will be
implied by the tails of the bubble, so the name can be omitted.
You may then use these characters to write dialogue normally.
To position the balloons, hit shift+B to display the speech bubble editor.
For each character that has a speech balloon, this will have two buttons
in it.
Pressing the area button will launch the speech bubble editor. This editor
lets you drag to select the area where the speech bubble will be placed,
on a grid. When you complete the drag, the speech bubble will will change
locations.
Pressing the properties buttons will select between sets of properties
associated with the speech bubble. For the default speech bubble, the
different properties control the positionm of the speech bubble tail.
Once you've changed the area or properties for a character (or group of
characters with the same image tage), those properties remain set until
changed again, or until the next scene statement.
When the area or properties are being set on the current line of dialogue,
the corresponding line is brighter. If the values are being inherited from
a prior line of dialogue or the default, the button is dimmed out. Right
clicking on a button will prevent the current line from setting the value.
Tips
----
The speech bubbles use the same identifiers used by the translation system.
These identifiers can change if:
* The text of a line changes.
* A second line with the same text inside the same label is added or removed.
* The label before the line is added or removes (however, adding or removing
a label with the ``hide`` clause will not change the translation identifier).
If you edit a scene, it's suggested that you replay through it to make sure
the changes did not affect speech bubble placement.
Configuration Variables
-----------------------
The speech bubble system is controlled by variables in th ``bubble`` namespace,
and by the ``bubble`` screen and its associated styles.
The ``bubble`` namespace contains the following variables:
.. var:: bubble.db_filename = "bubble.json"
The database file, stored in the game directory, that contains the
speech bubble information.
.. var:: bubble.cols = 24
The granularity of the grid that's used to position and size speech bubbles,
in the horizontal direction.
.. var:: bubble.rows = 24
The granularity of the grid that's used to position and size speech bubbles,
in the vertical direction.
.. var:: bubble.default_area = (15, 1, 8, 5)
This is the default area that speech bubbles are placed in, if no other
area is specified. This is a tuple of the form (x, y, w, h),
where each value is a number of grid cells.
.. var:: bubble.properties = { ... }
These are properties, apart from the area, that can be used to customize
the speech bubble. This is a map from the name of a set of proprerties
to a dictionary of properties and values. These properties supersede the properties given
the character, and are then supplied to the ``bubble`` screen.
This uses the same prefixing system as :func:`Character` does. Properties
beginning with ``window_`` have the prefix removed, and are passed to the
displayable with id "window" in the bubble screen, which is the bubble
itself. Properties with ``what_`` have the prefix removed, and are passed
to the displayable with id "what" in the bubble screen, which is the text
of the bubble. Properties with ``who_`` are handled similarly, and given
to the characters name. Properties with ``show_`` are given as arguments
to the bubble screen itself.
In a new game, screens.rpy includes::
define bubble.frame = Frame("gui/bubble.png", 55, 55, 55, 95)
define bubble.properties = {
"bottom_left" : {
"window_background" : Transform(bubble.frame, xzoom=1, yzoom=1),
"window_bottom_padding" : 27,
},
"bottom_right" : {
"window_background" : Transform(bubble.frame, xzoom=-1, yzoom=1),
"window_bottom_padding" : 27,
},
"top_left" : {
"window_background" : Transform(bubble.frame, xzoom=1, yzoom=-1),
"window_top_padding" : 27,
},
"top_right" : {
"window_background" : Transform(bubble.frame, xzoom=-1, yzoom=-1),
"window_top_padding" : 27,
},
}
The bubble.frame variable is just used to make defining bubble.properties
easier. Then for each of the four styles of bubble, the bubble is flipped
so the tail is in the right place, and the padding is adjusted to leave
room for the tail.
.. var:: bubble.properties_order = [ ]
This is a list of the names of the sets of properties, in the order they should
be cycled through in the speech bubble editor. If the names of the sets of properties
are not given, the properties are cycled through in alphabetical order.
.. var:: bubble.expand_area = { ... }
This is a map from the name of a set of properties to a (left, top, right, bottom)
tuple. If found in this set, the area of the speech bubble is expanded by the
given number of pixels.
This makes the speech bubble bigger than the area the creator dragged out.
The intent is that this can be used to drag out the body of the speech
bubble without concern for the tail, and also for the text itself to stay
put when the set of properties is changed and the tail moves.
By default, this is::
define bubble.expand_area = {
"bottom_left" : (0, 0, 0, 22),
"bottom_right" : (0, 0, 0, 22),
"top_left" : (0, 22, 0, 0),
"top_right" : (0, 22, 0, 0),
}
Bubble Screen
-------------
The default ``bubble`` screen can be found in ``screens.rpy``, and is similar
to the default ``say`` screen::
screen bubble(who, what):
style_prefix "bubble"
window:
id "window"
if who is not None:
window:
id "namebox"
style "bubble_namebox"
text who:
id "who"
text what:
id "what"
It's separate from the say screen as it uses its own set of styles, including
``bubble_window``, ``bubble_what``, ``bubble_namebox``, and ``bubble_who``.
These styles can be customized directly to avoid having to set a property
in all of the sets of properties in :var:`bubble.properties`.
+15
View File
@@ -400,6 +400,21 @@ The following variables provide further control of the build process:
click "Build distributions", "Upload to itch.io" to cause an upload
to occur.
.. var:: build.itch_channels = { ... }
This maps a filename pattern (such as "\*-win.zip") to a string giving
the itch channel the file should be uploaded to. This defaults to::
{
"*-all.zip" : "win-osx-linux",
"*-market.zip" : "win-osx-linux",
"*-pc.zip" : "win-linux",
"*-win.zip" : "win",
"*-mac.zip" : "osx",
"*-linux.tar.bz2" : "linux",
"*-release.apk" : "android",
}
.. var:: build.mac_info_plist = { }
This is a dictionary mapping strings to strings, that can be used to

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