Compare commits

...

79 Commits

Author SHA1 Message Date
Tom Rothamel c5cdd0038e Merge pull request #5891 from DipeshAggarwal/patch-2
Fix the wrong keymap for accessibility
2024-11-08 21:18:51 -05:00
Tom Rothamel 8bac98c29a Merge pull request #5892 from DipeshAggarwal/patch-3
Replace `balloon` with 'bubble` to represent a valid type for callbacks
2024-11-08 21:18:37 -05:00
Dipesh Aggarwal 421d8cc37d Replace balloon with 'bubble` to represent a valid type for callbacks 2024-11-09 07:08:01 +05:30
Dipesh Aggarwal c097434036 Fix the wrong keymap for accessibility 2024-11-09 06:59:12 +05:30
Dipesh Aggarwal e3cc4be1b8 Fix UnboundLocalError: local variable 'keyboard_mode' referenced before assignment
(cherry picked from commit 15f498516a)
2024-11-09 03:31:10 +04:00
Tom Rothamel b5711f7e11 doc: Changelog whitespace changes. 2024-11-08 01:07:11 -05:00
Tom Rothamel 8f2649e3a7 doc: Changelog. 2024-11-07 22:29:47 -05:00
Tom Rothamel ab42c0f6e1 Only skip the BOM, not other characters. 2024-11-07 22:02:23 -05:00
Tom Rothamel 01eacd2932 Avoid forcing synchro_start when playing a movie.
Fixes #5887.

This may cause mask_channel to get a bit more out of sync, but it's
already so-so, and side_mask is the superior alternative.
2024-11-07 21:55:08 -05:00
Tom Rothamel c25f22fe4f doc: Credits. 2024-11-07 21:54:55 -05:00
Tom Rothamel fe3b800cab Change Model to create meshes as documented.
Fixes #5692.
2024-11-05 00:36:12 -05:00
Tom Rothamel ccde74bdb8 doc: Fix config.skip_sounds.
Fixes #5883.
2024-11-04 08:47:28 -05:00
Tom Rothamel ab6f469811 textshader: Consider all glyphs when assigning coordinates.
Fixes #5884.
2024-11-03 23:22:46 -05:00
Tom Rothamel f92b70d38c Protect against errors with disconnected audio sources.
Fixes #5882, with low confidence.
2024-11-03 21:14:34 -05:00
Andy_kl e89338342a Skip BOM in ren_py_to_rpy so RENPY can start at first line. 2024-11-04 02:29:14 +04:00
Tom Rothamel 62ca262aec Fix tyop. 2024-11-02 15:49:52 -04:00
Tom Rothamel eb5a84d420 Remove unnecessary locking in im.Cache.
This is the most likely cause of the performance issues in #5295.
Now, the lock should be held for brief periods of time.
2024-11-02 15:48:08 -04:00
Tom Rothamel 3280bcc5ff Clarify where gui.unscrollable works.
Fixes #5218.
2024-11-02 15:06:05 -04:00
Tom Rothamel 5c5f72659c Restore old behavior of screenshot.
When render_tree is None, screenshot takes the old contents of
the framebuffer. Which isn't great, but since it happens when
at the main menu, isn't a huge deal.

Fixes #5879.
2024-11-01 22:47:15 -04:00
Tom Rothamel b9789ae25c Fix icon font in the director.
Fixes #5876.
2024-10-31 01:29:21 -04:00
Tom Rothamel 58e56cff88 Quote attributes in the image attributes screen.
Per #5877.
2024-10-30 22:30:28 -04:00
Tom Rothamel 4b532ac11e Produce a sensible error when monologue mode is used in a menu.
Fixes #5872.
2024-10-29 21:40:41 -04:00
Tom Rothamel a7f16eee3f Remove the "always" steam deck keyboard mode. 2024-10-29 21:13:53 -04:00
Tom Rothamel fff2558083 Revert "Fix an issue that could stop audio rollback from working."
This reverts commit 14ced3377a.
2024-10-28 01:03:30 -04:00
Tom Rothamel 944b7d0e9c Copy the music dictionary when it changes.
Fixes #5868 while continuing to fix #5802.
2024-10-28 00:51:38 -04:00
Tom Rothamel 7022001dbe Keep high-rate drawing for 12 frames (.2s).
This ensures that a static image is pushed through any sort of
compositor that requires constant flipping.

Fixes #5867.
2024-10-27 01:38:58 -04:00
Tom Rothamel 0ab9444676 hbfont: Underlines and strikethroughs should not include glyph.x_offset.
Fixes #5861.
2024-10-26 19:19:17 -04:00
Tom Rothamel 0795565e07 Quit out of pygame.display when the presplash ends.
This resets the IME system, which is needed to fix #5801.
2024-10-25 00:51:30 -04:00
Tom Rothamel 3cf31edae2 doc: Fix mistake.
Fixes #5863.
2024-10-24 21:45:59 -04:00
Craig de Stigter 8b356090d9 Fix an obvious undefined variable error 2024-10-24 21:28:40 -04:00
Tom Rothamel 9f3be6e604 Only synchro start movie channels when explicitly requested.
Per #5914.
2024-10-23 21:28:48 -04:00
Tom Rothamel 0e6522f867 Use a different approach to #5854.
The focus system may not always use the same displayable, so .focus
is a good idea. Instead, change the prefix each time it's asked for.
2024-10-23 01:50:46 -04:00
Tom Rothamel 3cbe6fa1d8 Avoid re-focusing a displayable that has replaced another.
Fixes #5854.
2024-10-22 23:42:02 -04:00
Ayowel bb5588f182 Do not needlessly track files in late_base_patterns 2024-10-21 23:38:34 -04:00
Ayowel 45a6a08559 Only ignore .rpe files if they are not explicitly tracked 2024-10-21 23:38:23 -04:00
Tom Rothamel e1cb42f253 gl2: Round coordinates during pixel_perfect.
Rounding errors could cause problems when performing the pixel
perfect correction, and the rounding errors could be different
in normal rendering and render-to-texture.

By rounding first, we eliminate these problems.

Fixes #5844.
2024-10-21 00:53:08 -04:00
Tom Rothamel dde19b8716 gl2: Fix gl_anisotropic
Which used the incorrect field.

Fixes #5851.
2024-10-20 21:57:18 -04:00
Tom Rothamel 906788fe88 video: Avoid stopping movies if both current and old have _play.
To make Ren'Py cooperate better with games that run the movies
themselves.

Per #5814.
2024-10-19 16:35:26 -04:00
Tom Rothamel 69b019f03c Use last_channel_movie to stop movies, since it exists. 2024-10-19 16:30:11 -04:00
Tom Rothamel 9de1c93ef9 Document that transforms shouldn't be applied to a Drag directly.
Per #5847.
2024-10-19 01:19:06 -04:00
Tom Rothamel c4f478160b Add referrer support to installer.download. 2024-10-17 23:21:01 -04:00
Tom Rothamel 054535f0c8 Restart looping movies during rollback.
Fixes #5564.
2024-10-16 23:19:10 -04:00
Tom Rothamel 16b9c2be67 Use insets when evaluating keyboard focus conditions.
Fixes #5831.
2024-10-15 01:05:44 -04:00
Tom Rothamel 4a591d552b Fix a rounding issue that stopped bar_invert from working.
Fixes #5843.
2024-10-14 00:43:15 -04:00
Tom Rothamel 576a1138d3 Changelog last. 2024-10-13 19:35:41 -04:00
avonder 50ef339f16 android: Install-time asset pack support. (#5837)
Enable reading from APK-based asset packs
2024-10-13 19:32:49 -04:00
Tom Rothamel 0385bbcb9b Re-render displayables after box_wrap.
After wrap, a displayable may be offered a different size, and
so the render needs to happen again.
2024-10-12 23:52:07 -04:00
Tom Rothamel 20a5d6cd69 Factor out check_spline_types.
Recursive inner functions create reference loops that need to be
GC-ed, so eliminating such can reduce GC pauses.

Per #5832.
2024-10-07 22:33:42 -04:00
Tom Rothamel 34a1f57e50 Make all default_focus activations explicit.
So the hovered handler runs.

Fixes #5794.
2024-10-07 02:06:43 -04:00
Tom Rothamel 5245fbb38e Consider controller events inputs before being mapped.
Fixes #5780.
2024-10-06 23:16:54 -04:00
Tom Rothamel 937c91f314 Proactively remove ongoing transitions.
Fixes #5827.
2024-10-06 01:39:47 -04:00
Tom Rothamel d90f1cdc30 Another fix to grabs and viewports. 2024-10-04 21:48:41 -04:00
Tom Rothamel 8c82667ae1 Disable pixel-perfect for Frame.
It only would work in corners, and wasn't working on the other sections
except at 1:1. So disable it.

Fixes #5793.
2024-10-04 00:28:32 -04:00
Tom Rothamel 3085c15caf Add a method in render.pyx to find GL properties. 2024-10-03 23:37:14 -04:00
Tom Rothamel 2e37df7103 Prevent crash when Live2D.used_nonexclusive is None.
Fixes #5816.
2024-10-03 21:46:32 -04:00
Tom Rothamel 9a75b6628b Another fix to viewport dragging.
Per #5776.

Co-Authored-by: the66F95 <54831834+the66F95@users.noreply.github.com>
2024-10-03 21:37:34 -04:00
Tom Rothamel 405d8d4399 Allow drag to begin inside a button.
Fixes #5776, comment 2383488254.
2024-10-03 00:51:19 -04:00
Mark 56dda5590b doc: Fix code block in cds.rst 2024-10-01 22:48:48 -04:00
Tom Rothamel 86046383a8 Improve how draggable viewports and drags interact.
After these changes, the innermost draggable should take precedence,
rather than what's focused.

Fixes #5776.
2024-09-29 22:16:42 -04:00
Tom Rothamel f907fe119a Changes to audio filters take place immediately after reload.
Fixes #5802.
2024-09-29 02:01:52 -04:00
Tom Rothamel 14ced3377a Fix an issue that could stop audio rollback from working.
The failure would take place in the case where a channel is changed
between roll-forward and and an interaction, but the sound playing
would not change (ie. a queue operation).
2024-09-29 01:47:28 -04:00
Tom Rothamel ad690d8a16 audiofilter: Make the time the filter changes well defined. 2024-09-28 23:47:12 -04:00
Tom Rothamel 1c830a0be1 Audited #5797 2024-09-27 18:21:28 -04:00
Tom Rothamel ee89717b47 linvol: Remove compatibility.
This was compat code that has served it's purpose - players can still
adjust the volumes as required.

Fixes #5806.
2024-09-26 01:09:13 -04:00
Tom Rothamel 6cfef151da Write <package>.update.json in a temporary directory, only copy if needed.
Fixes #5798.
2024-09-25 20:18:42 -04:00
Tom Rothamel 8d2a6d2596 Allow textshaders to work with glyphs at negative offsets.
Fixes #5787.
2024-09-19 00:20:57 -04:00
Tom Rothamel f225453ff1 Only store transform events on Displayables that use or produce them.
This prevents an issue from occurring when a non-duplicatable
displayable is the child of multiple other displayables, which
could set transform_event multiple times.

As it doesn't respond to the event, and doesn't produce it, there's
no reason for the event to be stored. So we don't.

Fixes #5788
2024-09-18 22:27:12 -04:00
Tom Rothamel 209eb580cf web: Prevent access to files and directories starting with "." 2024-09-17 21:24:01 -04:00
Tom Rothamel 3103cff7f7 Restore RENPY_NO_STEAM support. 2024-09-17 20:34:10 -04:00
brimbel 138884b545 Fix example of shader text tag
jitter text shader does not have a speed uniform
2024-09-17 00:46:19 -04:00
Tom Rothamel 1489a4e748 text: Add pseudo-glyphs to the start and end of outlined lines.
These are shown to ensure that all of the outlines are shown, even
if there is no character in that space.

Fixes #5778.
2024-09-16 01:48:00 -04:00
Tom Rothamel 1a7fb9c008 text: Ensure that there is no space between textshader glyphs. 2024-09-15 21:50:26 -04:00
Tom Rothamel 6506a3a45e Fix an issue with with sdist, versions, and pygame_sdl2. 2024-09-14 13:15:06 -04:00
levicratic 3913ca9c0c Fix size tag preventing outer tags from closing 2024-09-11 21:36:45 -04:00
Tom Rothamel 4e03602bd6 Bump versions. 2024-09-10 00:43:15 -04:00
Tom Rothamel a2e08864c4 doc: Changelog. 2024-09-09 01:09:54 -04:00
Tom Rothamel 14563943ed Do not include keys in build. 2024-09-09 00:48:57 -04:00
Tom Rothamel 5e91783620 Bump fix version. 2024-09-08 00:04:52 -04:00
Tom Rothamel cde01cf27b Do not allow image keywords to occur in image simple expressions. 2024-09-06 22:12:11 -04:00
53 changed files with 583 additions and 237 deletions
+1 -1
View File
@@ -252,7 +252,7 @@ def main():
"-q",
"egg_info",
"--tag-build",
"+renpy" + args.version,
"+renpy" + args.version.replace("+", "-"),
"sdist",
"-d",
os.path.abspath(destination)
+1 -1
View File
@@ -328,7 +328,7 @@ define gui.vbar_borders = Borders(gui.scale(4), gui.scale(4), gui.scale(4), gui.
define gui.vscrollbar_borders = Borders(gui.scale(4), gui.scale(4), gui.scale(4), gui.scale(4))
define gui.vslider_borders = Borders(gui.scale(4), gui.scale(4), gui.scale(4), gui.scale(4))
## What to do with unscrollable scrollbars in the gui. "hide" hides them, while
## What to do with unscrollable scrollbars in the game menu. "hide" hides them, while
## None shows them.
define gui.unscrollable = "hide"
+6 -3
View File
@@ -1531,7 +1531,8 @@ fix_dlc("renios", "renios")
update = { variant : { "version" : self.update_versions[variant], "base_name" : self.base_name, "files" : update_files, "directories" : update_directories, "xbit" : update_xbit } }
update_fn = os.path.join(self.destination, filename + ".update.json")
update_fn = self.temp_filename(filename + ".update.json")
if self.include_update and not format.startswith("app-"):
@@ -1565,9 +1566,11 @@ fix_dlc("renios", "renios")
in this thread or a background thread.
"""
if self.include_update and not self.build_update and not dlc:
final_update_fn = os.path.join(self.destination, filename + ".update.json")
if self.build_update or dlc:
if os.path.exists(update_fn):
os.unlink(update_fn)
shutil.copy(update_fn, final_update_fn)
if not directory:
file_hash = hash_file(path)
+6 -2
View File
@@ -169,7 +169,7 @@ def _check_hash(filename, hashj):
download_file = ""
download_url = ""
def download(url, filename, hash=None):
def download(url, filename, hash=None, headers=None, requests_kwargs={}):
"""
Downloads `url` to `filename`, a tempfile.
"""
@@ -188,9 +188,13 @@ def download(url, filename, hash=None):
progress_time = time.time()
all_headers = {"User-Agent": 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36 renpy/8'}
if headers:
all_headers.update(headers)
try:
response = requests.get(url, stream=True, proxies=renpy.exports.proxies, timeout=15)
response = requests.get(url, stream=True, proxies=renpy.exports.proxies, timeout=15, headers=all_headers, **requests_kwargs)
response.raise_for_status()
total_size = int(response.headers.get('content-length', 1))
+2
View File
@@ -301,6 +301,8 @@ init python:
build.classify_renpy("**.new", None)
build.classify_renpy("**.bak", None)
build.classify_renpy("**.keystore", None)
build.classify_renpy("**/log.txt", None)
build.classify_renpy("**/traceback.txt", None)
build.classify_renpy("**/errors.txt", None)
+12 -1
View File
@@ -17,6 +17,8 @@ root = ""
import renpy
class BannedException(Exception):
pass
class WebHandler(http.server.BaseHTTPRequestHandler):
@@ -59,7 +61,13 @@ class WebHandler(http.server.BaseHTTPRequestHandler):
None, in which case the caller has nothing further to do.
"""
path = self.translate_path(self.path)
try:
path = self.translate_path(self.path)
except BannedException:
self.send_error(403, "File not found")
return None
f = None
if os.path.isdir(path):
parts = urllib.parse.urlsplit(self.path)
@@ -192,6 +200,9 @@ class WebHandler(http.server.BaseHTTPRequestHandler):
path = root
for word in words:
if word.startswith("."):
raise BannedException("Invalid path.")
if os.path.dirname(word) or word in (os.curdir, os.pardir):
# Ignore components that are not a simple file/directory name
continue
+8 -5
View File
@@ -1202,7 +1202,7 @@ void RPS_set_secondary_volume(int channel, float vol2, float delay) {
/**
* Replaces audio filters with the given PyObject.
*/
void RPS_replace_audio_filter(int channel, PyObject *new_filter) {
void RPS_replace_audio_filter(int channel, PyObject *new_filter, int primary) {
struct Channel *c;
@@ -1214,10 +1214,13 @@ void RPS_replace_audio_filter(int channel, PyObject *new_filter) {
LOCK_AUDIO();
if (c->playing_audio_filter) {
Py_DECREF(c->playing_audio_filter);
Py_INCREF(new_filter);
c->playing_audio_filter = new_filter;
if (primary) {
if (c->playing_audio_filter) {
Py_DECREF(c->playing_audio_filter);
Py_INCREF(new_filter);
c->playing_audio_filter = new_filter;
}
}
if (c->queued_audio_filter) {
+1 -1
View File
@@ -43,7 +43,7 @@ void RPS_set_volume(int channel, float volume);
float RPS_get_volume(int channel);
void RPS_set_pan(int channel, float pan, float delay);
void RPS_set_secondary_volume(int channel, float vol2, float delay);
void RPS_replace_audio_filter(int channel, PyObject *audio_filter);
void RPS_replace_audio_filter(int channel, PyObject *audio_filter, int primary);
int RPS_video_ready(int channel);
PyObject *RPS_read_video(int channel);
+10 -9
View File
@@ -1146,6 +1146,16 @@ compatible_pairs = [
# values of the variables here.
def check_spline_types(value):
if isinstance(value, (position, int, float)):
return True
if isinstance(value, tuple):
return all(check_spline_types(i) for i in value)
return False
class RawMultipurpose(RawStatement):
warp_function = None
@@ -1208,15 +1218,6 @@ class RawMultipurpose(RawStatement):
compiling(self.loc)
def check_spline_types(value):
if isinstance(value, (position, int, float)):
return True
if isinstance(value, tuple):
return all(check_spline_types(i) for i in value)
return False
# Figure out what kind of statement we have. If there's no
# interpolator, and no properties, than we have either a
# call, or a child statement.
+26 -11
View File
@@ -230,6 +230,7 @@ class Channel(object):
# The audio filter to use.
audio_filter = None
raw_audio_filter = None
def __init__(self, name, default_loop, stop_on_mute, tight, file_prefix, file_suffix, buffer_queue, movie, framedrop, synchro_start):
@@ -364,9 +365,12 @@ class Channel(object):
ExecutionContext to point to the copy, and returns the copy.
"""
mcd = renpy.game.context().music
context = renpy.game.context()
mcd = dict(context.music)
context.music = mcd
ctx = self.get_context().copy()
mcd[self.name] = ctx
return ctx
@@ -690,6 +694,7 @@ class Channel(object):
old_raw_audio_filter = self.context.raw_audio_filter
self.context.raw_audio_filter = audio_filter
self.raw_audio_filter = audio_filter
if old_raw_audio_filter is None and audio_filter is None:
new_audio_filter = None
@@ -701,11 +706,14 @@ class Channel(object):
self.context.audio_filter = new_audio_filter
if replace:
for q in self.queue:
q.audio_filter = new_audio_filter
for q in self.queue:
q.audio_filter = new_audio_filter
if replace:
renpysound.replace_audio_filter(self.number, new_audio_filter, 1)
else:
renpysound.replace_audio_filter(self.number, new_audio_filter, 0)
renpysound.replace_audio_filter(self.number, new_audio_filter)
def enqueue(self, filenames, loop=True, synchro_start=None, fadein=0, tight=None, loop_only=False, relative_volume=1.0):
@@ -905,17 +913,20 @@ def register_channel(name,
Ren'Py will display frames late rather than dropping them.
`synchro_start`
Does this channel particpate in synchro start? Synchro start determines if
Does this channel participate in synchro start? Synchro start determines if
the channel will start playing at the same time as other channels. If None,
this defaults to `loop`.
this defaults to `loop` if `movie` is False, and False otherwise.
"""
if synchro_start is None:
synchro_start = loop
if name == "movie":
movie = True
if synchro_start is None:
if movie:
synchro_start = False
else:
synchro_start = loop
if not force and not renpy.game.context().init_phase and (" " not in name):
raise Exception("Can't register channel outside of init phase.")
@@ -1260,6 +1271,7 @@ def interact():
ctx = c.context
# If we're in the same music change, then do nothing with the
# music.
if c.last_changed == ctx.last_changed:
@@ -1268,11 +1280,14 @@ def interact():
filenames = ctx.last_filenames
tight = ctx.last_tight
if ctx.raw_audio_filter != c.raw_audio_filter:
c.set_audio_filter(ctx.raw_audio_filter, True)
if c.loop != filenames:
c.fadeout(max(renpy.config.context_fadeout_music, renpy.config.fadeout_audio))
if filenames:
c.enqueue(filenames, loop=True, synchro_start=True, tight=tight, fadein=renpy.config.context_fadein_music, relative_volume=ctx.last_relative_volume)
c.enqueue(filenames, loop=True, synchro_start=c.default_synchro_start, tight=tight, fadein=renpy.config.context_fadein_music, relative_volume=ctx.last_relative_volume)
c.last_changed = ctx.last_changed
+8
View File
@@ -662,11 +662,19 @@ def set_audio_filter(channel, audio_filter, replace=False, duration=0.016):
The duration to change from the current to the new filter, in seconds.
This prevents a popping sound when changing filters.
"""
replace = replace or renpy.game.after_rollback
if audio_filter is not None:
audio_filter = renpy.audio.filter.to_audio_filter(audio_filter)
try:
c = renpy.audio.audio.get_channel(channel)
ctx = c.copy_context()
t = get_serial()
ctx.last_changed = t
c.set_audio_filter(audio_filter, replace=replace, duration=duration)
except Exception:
if renpy.config.debug_sound:
+7 -3
View File
@@ -74,7 +74,7 @@ cdef extern from "renpysound_core.h":
float RPS_get_volume(int channel)
void RPS_set_pan(int channel, float pan, float delay)
void RPS_set_secondary_volume(int channel, float vol2, float delay)
void RPS_replace_audio_filter(int channel, object audio_filter)
void RPS_replace_audio_filter(int channel, object audio_filter, int primary)
void RPS_advance_time()
int RPS_video_ready(int channel)
@@ -343,15 +343,19 @@ def set_secondary_volume(channel, volume, delay):
check_error()
def replace_audio_filter(channel, audio_filter):
def replace_audio_filter(channel, audio_filter, playing):
"""
Replaces the audio filter for `channel` with `audio_filter`.
`playing`
If true, the filter is applied to the currently playing file and queued file. If false,
the filter is only applied to the queued file.
"""
if audio_filter is not None:
audio_filter.prepare(get_sample_rate())
RPS_replace_audio_filter(channel, audio_filter)
RPS_replace_audio_filter(channel, audio_filter, playing)
def deallocate_audio_filter(audio_filter):
+2 -2
View File
@@ -352,14 +352,14 @@ def set_secondary_volume(channel, volume, delay):
@proxy_with_channel
def replace_audio_filter(channel, audio_filter):
def replace_audio_filter(channel, audio_filter, primary):
"""
Replaces the audio filter for `channel` with `audio_filter`.
"""
afid = load_audio_filter(audio_filter)
call("replace_audio_filter", channel, afid)
call("replace_audio_filter", channel, afid, primary)
def audio_filter_constructor(f):
-1
View File
@@ -163,7 +163,6 @@ init -1500 python in build:
("*.dll", None),
("*.manifest", None),
("*.keystore", None),
( "**.rpe", None),
( "**.rpe.py", None),
("update.pem", None),
-9
View File
@@ -135,15 +135,6 @@ init 1500 python hide:
config.emphasize_audio_volume = _vol(config.emphasize_audio_volume)
if not persistent._linearized_volumes:
for k, v in _preferences.volumes.items():
_preferences.volumes[k] = v ** 2
for k, v in persistent._character_volume.items():
persistent._character_volume[k] = v ** 2
persistent._linearized_volumes = True
_apply_default_preferences()
error = _preferences.check()
+1
View File
@@ -1493,6 +1493,7 @@ style director_icon_action_button is director_action_button:
style director_icon_action_button_text is director_action_button_text:
font "DejaVuSans.ttf"
emoji_font None
style director_statement_text is director_text:
size 20
+6 -4
View File
@@ -720,15 +720,17 @@ init -1499 python in _renpysteam:
def keyboard_periodic():
global keyboard_mode
global keyboard_showing
global keyboard_primed
global keyboard_shift
global keyboard_baseline
if keyboard_mode == "always":
keyboard_mode = "once"
if keyboard_mode == "never":
return
elif keyboard_mode == "always":
keyboard_primed = True
elif keyboard_mode != "once":
raise Exception("Bad steam keyboard_mode.")
@@ -982,8 +984,8 @@ init -1499 python in achievement:
if not config.enable_steam:
return
# if "RENPY_NO_STEAM" in os.environ:
# return
if "RENPY_NO_STEAM" in os.environ:
return
dll = ctypes.cdll[dll_path]
+6 -4
View File
@@ -211,12 +211,13 @@ let stop_playing = (c) => {
if (c.playing !== null && c.playing.source !== null) {
renpyAudio.disconnectFilter(c.playing.filter, c.playing.source, c.destination);
try {
c.playing.source.stop()
} catch (e) {
}
renpyAudio.disconnectFilter(c.playing.filter, c.playing.source, c.destination);
}
c.playing = c.queued;
@@ -308,6 +309,7 @@ let video_start = (c) => {
};
let video_pause = (c) => {
const p = c.playing;
if (p.started === null) {
return;
}
@@ -713,11 +715,11 @@ renpyAudio.set_pan = (channel, pan, delay) => {
linearRampToValue(control, control.value, pan, delay);
};
renpyAudio.replace_audio_filter = (channel, afid) => {
renpyAudio.replace_audio_filter = (channel, afid, primary) => {
let c = get_channel(channel);
let filter = renpyAudio.getFilter(afid);
if (c.playing) {
if (c.playing && primary) {
renpyAudio.disconnectFilter(c.playing.filter, c.playing.source, c.destination);
c.playing.filter = filter;
renpyAudio.connectFilter(filter, c.playing.source, c.destination);
+12 -3
View File
@@ -71,16 +71,25 @@ renpyAudio.connectFilter = function (filter, source, destination) {
*/
renpyAudio.disconnectFilter = function (filter, source, destination) {
if (filter === null) {
source.disconnect(destination);
try {
source.disconnect(destination);
} catch (e) {
}
return;
}
for (let input of filter.inputs) {
source.disconnect(input);
try {
source.disconnect(input);
} catch (e) {
}
}
for (let output of filter.outputs) {
output.disconnect(destination);
try {
output.disconnect(destination);
} catch (e) {
}
}
}
+2 -2
View File
@@ -130,11 +130,11 @@ screen _image_attributes():
for name in hidden:
$ attributes = " ".join(renpy.get_attributes(name, layer=l))
text _(" [name] [attributes] (hidden)")
text _(" [name!q] [attributes!q] (hidden)")
for name in showing:
$ attributes = " ".join(renpy.get_attributes(name, layer=l))
text _(" [name] [attributes]")
text _(" [name!q] [attributes!q]")
text " "
+2 -2
View File
@@ -886,7 +886,7 @@ loadable_callback = None
# How many frames should be drawn fast each time the screen needs to be
# updated?
fast_redraw_frames = 4
fast_redraw_frames = 12
# The color passed to glClearColor when clearing the screen.
gl_clear_color = "#000"
@@ -983,7 +983,7 @@ menu_include_disabled = False
# Should we report extraneous attributes?
report_extraneous_attributes = True
# Should we play non-loooped music when skipping?
# Should we avoid playing non-loooped music when skipping?
skip_sounds = False
# Should we lint screens without parameters?
+17 -3
View File
@@ -895,6 +895,8 @@ KEY_EVENTS = (
class Button(renpy.display.layout.Window):
_store_transform_event = True
keymap = { }
action = None
alternate = None
@@ -2188,6 +2190,8 @@ class Bar(renpy.display.displayable.Displayable):
to clicks on that value.
"""
_store_transform_event = True
@property
def _draggable(self):
return self.focusable
@@ -2456,8 +2460,10 @@ class Bar(renpy.display.displayable.Displayable):
vertical = self.style.bar_vertical
invert = self.style.bar_invert ^ vertical
if invert:
value = range - value
old_inverted_value = value
grabbed = (renpy.display.focus.get_grab() is self)
just_grabbed = False
@@ -2467,7 +2473,7 @@ class Bar(renpy.display.displayable.Displayable):
if not grabbed and map_event(ev, "bar_activate"):
renpy.display.tts.speak(renpy.minstore.__("activate"))
renpy.display.focus.set_grab(self)
self.set_style_prefix("selected_hover_", True)
self.set_style_prefix("hover_", True)
just_grabbed = True
grabbed = True
ignore_event = True
@@ -2527,12 +2533,16 @@ class Bar(renpy.display.displayable.Displayable):
value = range
if invert:
value = range - value
if value == old_inverted_value: # type: ignore
value = old_value
else:
value = range - value
if grabbed and not just_grabbed and map_event(ev, "bar_deactivate"):
renpy.display.tts.speak(renpy.minstore.__("deactivate"))
self.set_style_prefix("hover_", True)
renpy.display.focus.set_grab(None)
self.set_style_prefix("hover_", True)
# Invoke rounding adjustment on bar release
value = self.adjustment.round_value(value, release=True)
@@ -2560,6 +2570,10 @@ class Bar(renpy.display.displayable.Displayable):
def set_style_prefix(self, prefix, root):
if root:
if renpy.display.focus.get_grab() is self:
prefix = "selected_" + prefix
super(Bar, self).set_style_prefix(prefix, root)
def _tts(self):
+21 -13
View File
@@ -994,6 +994,14 @@ class Interface(object):
if renpy.android:
self.check_android_start()
gc.collect()
if gc.garbage:
del gc.garbage[:]
# Kill off the presplash.
renpy.display.presplash.end()
# Initialize audio.
pygame.display.hint("SDL_APP_NAME", (renpy.config.name or "Ren'Py Game").encode("utf-8"))
pygame.display.hint("SDL_AUDIO_DEVICE_APP_NAME", (renpy.config.name or "Ren'Py Game").encode("utf-8"))
@@ -1011,16 +1019,8 @@ class Interface(object):
renpy.display.emulator.init_emulator()
gc.collect()
if gc.garbage:
del gc.garbage[:]
renpy.display.render.render_ready()
# Kill off the presplash.
renpy.display.presplash.end()
# If we are on the web browser, start preloading the browser cache.
if renpy.emscripten and renpy.game.preferences.web_cache_preload:
emscripten.run_script("loadCache()")
@@ -2942,6 +2942,14 @@ class Interface(object):
renpy.loadsave.did_autosave = False
renpy.exports.run(renpy.config.autosave_callback)
# End an obsolete ongoing transition.
if (not trans_pause) and self.ongoing_transition.get(None, None) and not self.get_ongoing_transition(None):
self.transition.pop(None, None)
self.ongoing_transition.pop(None, None)
self.transition_time.pop(None, None)
self.transition_from.pop(None, None)
self.restart_interaction = True
# See if we want to restart the interaction entirely.
if self.restart_interaction and not self.display_reset:
return True, None
@@ -3249,6 +3257,11 @@ class Interface(object):
x = -1
y = -1
self.event_time = end_time = get_time()
if ev.type in input_events:
self.input_event_time = self.event_time
# This can set the event to None, to ignore it.
ev = renpy.display.controller.event(ev)
if not ev:
@@ -3257,11 +3270,6 @@ class Interface(object):
# Handle skipping.
renpy.display.behavior.skipping(ev)
self.event_time = end_time = get_time()
if ev.type in input_events:
self.input_event_time = self.event_time
try:
if self.touch:
+14 -5
View File
@@ -505,18 +505,27 @@ class Displayable(renpy.object.Object):
return pos
_store_transform_event = False
def set_transform_event(self, event):
"""
Sets the transform event of this displayable to event.
transform_event_responder needs to be set on displayables that respond to transform events.
_store_transform_event should be set on displayables that store a generated transform event,
like Button or Bar.
"""
if event == self.transform_event:
return
if self.transform_event_responder or self._store_transform_event:
self.transform_event = event
if event == self.transform_event:
return
if self.transform_event_responder:
renpy.display.render.redraw(self, 0)
self.transform_event = event
if self.transform_event_responder:
renpy.display.render.redraw(self, 0)
def _handles_event(self, event):
"""
+8 -1
View File
@@ -94,6 +94,9 @@ class Drag(renpy.display.displayable.Displayable, renpy.revertable.RevertableObj
has been computed, the layout properties are ignored in favor of the
position stored inside the Drag.
Transforms should not be applied to a Drag directly. Instead, apply
the transform to the child of the Drag.
`d`
If present, the child of this Drag. Drags use the child style
in preference to this, if it's not None.
@@ -736,8 +739,12 @@ class Drag(renpy.display.displayable.Displayable, renpy.revertable.RevertableObj
def event(self, ev, x, y, st):
rv = self.child.event(ev, x, y, st)
if rv is not None:
return rv
if not self.is_focused():
return self.child.event(ev, x, y, st)
return None
# Mouse, in parent-relative coordinates.
par_x = int(self.last_x + x)
+10 -8
View File
@@ -468,6 +468,7 @@ def before_interact(roots):
set_grab(None)
set_focused(max_default_focus, None, max_default_screen)
old_max_default = max_default
explicit = True
# Try to find the current focus.
if current is not None:
@@ -778,7 +779,7 @@ def focus_nearest(from_x0, from_y0, from_x1, from_y1,
focus_extreme(xmul, ymul, wmul, hmul)
return
from_focus_x, from_focus_y, from_focus_w, from_focus_h = from_focus.inset_rect()
from_focus_x, from_focus_y, from_focus_w, from_focus_h = from_rect = from_focus.inset_rect()
fx0 = from_focus_x + from_focus_w * from_x0
fy0 = from_focus_y + from_focus_h * from_y0
@@ -806,10 +807,10 @@ def focus_nearest(from_x0, from_y0, from_x1, from_y1,
if f.x is False:
continue
if not condition(from_focus, f):
continue
f_x, f_y, f_w, f_h = to_rect = f.inset_rect()
f_x, f_y, f_w, f_h = f.inset_rect()
if not condition(from_rect, to_rect):
continue
tx0 = f_x + f_w * to_x0
ty0 = f_y + f_h * to_y0
@@ -899,30 +900,31 @@ def key_handler(ev):
else:
if map_event(ev, 'focus_right'):
return focus_nearest(0.9, 0.1, 0.9, 0.9,
0.1, 0.1, 0.1, 0.9,
verti_line_dist,
lambda old, new : old.x + old.w <= new.x,
lambda old, new : old[0] + old[2] <= new[0],
-1, 0, 0, 0)
if map_event(ev, 'focus_left'):
return focus_nearest(0.1, 0.1, 0.1, 0.9,
0.9, 0.1, 0.9, 0.9,
verti_line_dist,
lambda old, new : new.x + new.w <= old.x,
lambda old, new : new[0] + new[2] <= old[0],
1, 0, 1, 0)
if map_event(ev, 'focus_up'):
return focus_nearest(0.1, 0.1, 0.9, 0.1,
0.1, 0.9, 0.9, 0.9,
horiz_line_dist,
lambda old, new : new.y + new.h <= old.y,
lambda old, new : new[1] + new[3] <= old[1],
0, 1, 0, 1)
if map_event(ev, 'focus_down'):
return focus_nearest(0.1, 0.9, 0.9, 0.9,
0.1, 0.1, 0.9, 0.1,
horiz_line_dist,
lambda old, new : old.y + old.h <= new.y,
lambda old, new : old[1] + old[3] <= new[1],
0, -1, 0, 0)
+45 -53
View File
@@ -146,8 +146,7 @@ class Cache(object):
cache, in pixels.
"""
with self.lock:
return self.cache_size
return self.cache_size
def get_current_size(self, generations):
"""
@@ -191,18 +190,16 @@ class Cache(object):
# Clears out the cache.
def clear(self):
self.lock.acquire()
with self.lock:
self.preloads = [ ]
self.preloads = [ ]
self.cache = { }
self.cache_size = 0
self.cache = { }
self.cache_size = 0
self.first_preload_in_tick = True
self.first_preload_in_tick = True
self.added.clear()
self.lock.release()
self.added.clear()
def get_renders(self):
"""
@@ -211,14 +208,8 @@ class Cache(object):
Render = renpy.display.render.Render
rv = [ ]
with self.lock:
for ce in self.cache.values():
if isinstance(ce.texture, Render):
rv.append(ce.texture)
return rv
return [ ce.texture for ce in self.cache.values() if isinstance(ce.texture, Render) ]
# Increments time, and clears the list of images to be
# preloaded.
@@ -328,21 +319,23 @@ class Cache(object):
else:
bounds = (0, 0, w, h)
ce = CacheEntry(image, surf, bounds)
with self.lock:
ce = CacheEntry(image, surf, bounds)
if image in self.cache:
self.kill(self.cache[image])
old_ce = self.cache.get(image, None)
self.cache[image] = ce
self.cache_size += ce.size()
if renpy.config.debug_image_cache:
if predict:
renpy.display.ic_log.write("Added %r (%.02f%%)", ce.what, 100.0 * self.get_total_size() / self.cache_limit)
else:
renpy.display.ic_log.write("Total Miss %r", ce.what)
if renpy.config.debug_image_cache:
if predict:
renpy.display.ic_log.write("Added %r (%.02f%%)", ce.what, 100.0 * self.get_total_size() / self.cache_limit)
else:
renpy.display.ic_log.write("Total Miss %r", ce.what)
if old_ce:
self.kill(old_ce)
renpy.display.render.mutated_surface(ce.surf)
@@ -388,8 +381,7 @@ class Cache(object):
return make_render(ce)
if (ce.surf is None) and (ce.texture is None):
with self.lock:
self.kill(ce)
self.kill(ce)
return rv
@@ -400,8 +392,9 @@ class Cache(object):
if ce.surf is not None:
renpy.display.draw.mutated_surface(ce.surf)
self.cache_size -= ce.size()
del self.cache[ce.what]
with self.lock:
self.cache_size -= ce.size()
del self.cache[ce.what]
if renpy.config.debug_image_cache:
renpy.display.ic_log.write("Removed %r", ce.what)
@@ -420,7 +413,10 @@ class Cache(object):
# If we're outside the cache limit, we need to go and start
# killing off some of the entries until we're back inside it.
for ce in sorted(self.cache.values(), key=lambda a : a.time):
with self.lock:
cache_values = list(self.cache.values())
for ce in sorted(cache_values, key=lambda a : a.time):
if ce.time == self.time:
# If we're bigger than the limit, and there's nothing
@@ -475,21 +471,19 @@ class Cache(object):
if not isinstance(im, ImageBase):
return
with self.lock:
if im in self.added:
return
if im in self.added:
return
self.added.add(im)
self.added.add(im)
ce = self.cache.get(im, None)
ce = self.cache.get(im, None)
if ce and ce.texture:
ce.time = self.time
in_cache = True
else:
self.preloads.append(im)
in_cache = False
if ce and ce.texture:
ce.time = self.time
in_cache = True
else:
self.preloads.append(im)
in_cache = False
if not in_cache:
@@ -524,18 +518,17 @@ class Cache(object):
# If the size of the current generation is bigger than the
# total cache size, stop preloading.
with self.lock:
# If the cache is overfull, clean it out.
if not self.cleanout():
# If the cache is overfull, clean it out.
if not self.cleanout():
if renpy.config.debug_image_cache:
for i in self.preloads:
renpy.display.ic_log.write("Overfull %r", i)
if renpy.config.debug_image_cache:
for i in self.preloads:
renpy.display.ic_log.write("Overfull %r", i)
self.preloads = [ ]
self.preloads = [ ]
break
return
try:
image = self.preloads.pop(0)
@@ -548,8 +541,7 @@ class Cache(object):
except Exception:
pass
with self.lock:
self.cleanout()
self.cleanout()
def add_load_log(self, filename):
+1
View File
@@ -425,6 +425,7 @@ class Frame(renpy.display.displayable.Displayable):
return
rv = Render(dw, dh)
rv.add_property("pixel_perfect", False)
self.draw_pattern(draw, left, top, right, bottom)
+6 -3
View File
@@ -132,9 +132,6 @@ class Container(renpy.display.displayable.Displayable):
super(Container, self).__init__(**properties)
def set_transform_event(self, event):
"""
Sets the transform event of this displayable to event.
"""
super(Container, self).set_transform_event(event)
@@ -1127,6 +1124,9 @@ class MultiBox(Container):
first_line = False
next_padding = 0
surf = render(d, rw, height - y, cst, cat)
sw, sh = surf.get_size()
line.append((d, (next_padding, 0), x, y, surf))
line_height = max(line_height, sh)
x += sw + next_padding
@@ -1182,6 +1182,9 @@ class MultiBox(Container):
first_line = False
next_padding = 0
surf = render(d, width - x, rh, cst, cat)
sw, sh = surf.get_size()
line.append((d, (0, next_padding), x, y, surf))
line_width = max(line_width, sw)
y += sh + next_padding
+1 -1
View File
@@ -81,7 +81,7 @@ class Model(renpy.display.displayable.Displayable):
self.size = size
self.textures = [ ]
self._mesh = True
self._mesh = None
self.shaders = [ ]
self.uniforms = { }
+2
View File
@@ -206,6 +206,8 @@ def end():
global progress_bar
progress_bar = None
pygame_sdl2.display.quit()
def sleep():
"""
+11
View File
@@ -1662,6 +1662,17 @@ cdef class Render:
else:
self.properties[name] = value
def get_property(self, name, default):
if self.properties is None:
return default
if name[:3] == "gl_":
name = name[3:]
return self.properties.get(name, default)
class Canvas(object):
def __init__(self, surf): #@DuplicatedSignature
+17 -4
View File
@@ -288,10 +288,10 @@ def render_movie(channel, width, height):
def default_play_callback(old, new): # @UnusedVariable
renpy.audio.music.play(new._play, channel=new.channel, loop=new.loop, synchro_start=True)
renpy.audio.music.play(new._play, channel=new.channel, loop=new.loop)
if new.mask:
renpy.audio.music.play(new.mask, channel=new.mask_channel, loop=new.loop, synchro_start=True)
renpy.audio.music.play(new.mask, channel=new.mask_channel, loop=new.loop)
# A serial number that's used to generated movie channels.
movie_channel_serial = 0
@@ -592,6 +592,9 @@ class Movie(renpy.display.displayable.Displayable):
else:
old_play = old._play
if (self._play is None) and (old_play is None):
return
if (self._play != old_play) or renpy.config.replay_movie_sprites:
if self._play:
@@ -641,29 +644,39 @@ def playing():
return
# A map from a channel to the movie playing on it in the last
# interaction. Used to restart looping movies.
last_channel_movie = { }
def update_playing():
"""
Calls play/stop on Movie displayables.
"""
global last_channel_movie
old_channel_movie = renpy.game.context().movie
for c, m in channel_movie.items():
old = old_channel_movie.get(c, None)
last = last_channel_movie.get(c, None)
if (c in reset_channels) and renpy.config.replay_movie_sprites:
m.play(old)
elif old is not m:
m.play(old)
elif m.loop and last is not m:
m.play(last)
for c, m in old_channel_movie.items():
for c, m in last_channel_movie.items():
if c not in channel_movie:
m.stop()
renpy.game.context().movie = dict(channel_movie)
renpy.game.context().movie = last_channel_movie = dict(channel_movie)
reset_channels.clear()
def frequent():
"""
Called to update the video playback. Returns true if a video refresh is
+34 -29
View File
@@ -336,12 +336,14 @@ class Viewport(renpy.display.layout.Container):
else:
inside = True
# True if the player can drag the viewoport.
# True if the player can drag the viewport.
draggable = self.draggable and (self.xadjustment.range or self.yadjustment.range)
grab = renpy.display.focus.get_grab()
if draggable:
if (grab is not None) and getattr(grab, '_draggable', False) and (grab is not self):
self.drag_position = None
elif draggable:
if grab is None and renpy.display.behavior.map_event(ev, 'viewport_drag_end'):
self.drag_position = None
else:
@@ -349,24 +351,22 @@ class Viewport(renpy.display.layout.Container):
if inside and draggable and (self.drag_position is not None) and (grab is not self):
focused = renpy.display.focus.get_focused()
if ev.type == pygame.MOUSEMOTION:
if (focused is None) or (focused is self) or not focused._draggable:
oldx, oldy = self.drag_position
if ev.type == pygame.MOUSEMOTION:
grabbed = getattr(grab, "_draggable", False) and grab.is_focused()
oldx, oldy = self.drag_position
if math.hypot(oldx - x, oldy - y) >= renpy.config.viewport_drag_radius and not grabbed:
rv = renpy.display.focus.force_focus(self)
renpy.display.focus.set_grab(self)
self.drag_position = (x, y)
self.drag_position_time = st
self.drag_speed = (0.0, 0.0)
grab = self
if math.hypot(oldx - x, oldy - y) >= renpy.config.viewport_drag_radius:
rv = renpy.display.focus.force_focus(self)
renpy.display.focus.set_grab(self)
self.drag_position = (x, y)
self.drag_position_time = st
self.drag_speed = (0.0, 0.0)
grab = self
if rv is not None:
return rv
if rv is not None:
return rv
if renpy.display.focus.get_grab() == self:
@@ -538,19 +538,6 @@ class Viewport(renpy.display.layout.Container):
else:
raise renpy.display.core.IgnoreEvent()
if inside and draggable:
focused = renpy.display.focus.get_focused()
if (focused is self) or (focused is None) or (not focused._draggable):
if renpy.display.behavior.map_event(ev, 'viewport_drag_start'):
self.drag_position = (x, y)
self.drag_position_time = st
self.drag_speed = (0.0, 0.0)
self.xadjustment.end_animation(instantly=True)
self.yadjustment.end_animation(instantly=True)
if inside and self.edge_size and ev.type in [ pygame.MOUSEMOTION, pygame.MOUSEBUTTONDOWN, pygame.MOUSEBUTTONUP ]:
def speed(n, zero, one):
@@ -584,11 +571,29 @@ class Viewport(renpy.display.layout.Container):
else:
self.edge_last_st = None
ignore_event = False
if inside and draggable:
if renpy.display.behavior.map_event(ev, 'viewport_drag_start'):
self.drag_position = (x, y)
self.drag_position_time = st
self.drag_speed = (0.0, 0.0)
self.xadjustment.end_animation(instantly=True)
self.yadjustment.end_animation(instantly=True)
ignore_event = True
rv = super(Viewport, self).event(ev, x, y, st)
if rv is not None:
return rv
if ignore_event:
raise renpy.display.core.IgnoreEvent()
return None
def set_xoffset(self, offset):
+8 -2
View File
@@ -1215,8 +1215,11 @@ cdef class GL2Draw:
cdef unsigned char *rpp
cdef int x, y, pitch
sw = render_tree.width * self.draw_per_virt
sh = render_tree.height * self.draw_per_virt
if render_tree is not None:
sw = render_tree.width * self.draw_per_virt
sh = render_tree.height * self.draw_per_virt
else:
sw, sh = self.drawable_size
full = renpy.display.pgrender.surface_unscaled((sw, sh), True)
surf = PySurface_AsSurface(full)
@@ -1356,6 +1359,9 @@ cdef class GL2DrawingContext:
sx, sy = transform.transform(0, 0)
sx = round(sx, 5)
sy = round(sy, 5)
sx = sx * halfwidth + halfwidth
sy = sy * halfheight + halfheight
+5
View File
@@ -253,6 +253,7 @@ cdef class Mesh2(Mesh):
double left, double top, double right, double bottom,
double left_time, double right_time,
double ascent, double descent,
double xoffset, double yoffset
):
"""
Adds a glyph to a mesh created by `text_mesh`.
@@ -282,6 +283,10 @@ cdef class Mesh2(Mesh):
cdef int stride = self.layout.stride
cdef int attribute = self.points * stride
left -= xoffset
right -= xoffset
cx -= xoffset
self.point[point + 0].x = left
self.point[point + 0].y = bottom
self.attribute[attribute + 0] = tex_left
+3 -3
View File
@@ -118,7 +118,7 @@ cdef class UniformSampler2D(Uniform):
self.cleanup = True
if "anisotropic" in properties:
if not properties.get("anisotropic", True) and renpy.display.draw.loader.max_anisotropy > 1.0:
if not properties.get("anisotropic", True) and renpy.display.draw.texture_loader.max_anisotropy > 1.0:
glTexParameterf(GL_TEXTURE_2D, TEXTURE_MAX_ANISOTROPY_EXT, 1.0)
self.cleanup = True
@@ -141,8 +141,8 @@ cdef class UniformSampler2D(Uniform):
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)
if "anisotropic" in properties:
if not properties.get("anisotropic", True) and renpy.display.draw.loader.max_anisotropy > 1.0:
glTexParameterf(GL_TEXTURE_2D, TEXTURE_MAX_ANISOTROPY_EXT, renpy.display.draw.loader.max_anisotropy)
if not properties.get("anisotropic", True) and renpy.display.draw.texture_loader.max_anisotropy > 1.0:
glTexParameterf(GL_TEXTURE_2D, TEXTURE_MAX_ANISOTROPY_EXT, renpy.display.draw.texture_loader.max_anisotropy)
self.last_data = None
+5 -1
View File
@@ -863,7 +863,11 @@ class Live2D(renpy.display.displayable.Displayable):
state.old_expressions = [ (name, shown, hidden) for (name, shown, hidden) in state.old_expressions if (now - hidden) < common.all_expressions[name].fadeout ]
# Determine the list of expressions that are being shown by this displayable.
expressions = list(self.used_nonexclusive) # type: ignore
if self.used_nonexclusive is None:
expressions = [ ]
else:
expressions = list(self.used_nonexclusive) # type: ignore
if self.expression:
expressions.append(self.expression)
+12 -3
View File
@@ -1439,7 +1439,7 @@ class Lexer(object):
return self.filename, self.number
def require(self, thing, name=None):
def require(self, thing, name=None, **kwargs):
"""
Tries to parse thing, and reports an error if it cannot be done.
@@ -1452,10 +1452,14 @@ class Lexer(object):
name = name or thing
rv = self.match(thing)
else:
name = name or thing.__func__.__name__
rv = thing()
rv = thing(**kwargs)
if rv is None:
if isinstance(thing, basestring):
name = name or thing
else:
name = name or thing.__func__.__name__
self.error("expected '%s' not found." % name)
return rv
@@ -1587,6 +1591,11 @@ def ren_py_to_rpy(text, filename):
"""
lines = text.splitlines()
# Skip the BOM, if any.
if lines and lines[0][:1] == u'\ufeff':
lines[0] = lines[0][1:]
result = [ ]
# The prefix prepended to Python lines.
+20 -5
View File
@@ -71,17 +71,27 @@ def get_path(fn):
apks = [ ]
game_apks = [ ]
split_apks = [ ]
if renpy.android:
import android.apk # type: ignore
packs = [os.environ[i] for i in ["ANDROID_PACK_FF" + str(j+1) for j in range(4)] if i in os.environ and os.environ[i].endswith(".apk")]
if renpy.config.renpy_base == renpy.config.basedir:
# Read the game data from the APK.
# Read the game data from the APKs.
apks.append(android.apk.APK(prefix='assets/x-game/'))
game_apks.append(apks[0])
game_apks.append(apks[-1])
for i in packs:
apks.append(android.apk.APK(apk=i, prefix='assets/game/'))
game_apks.append(apks[-1])
split_apks.append(apks[-1])
apks.append(android.apk.APK(prefix='assets/x-renpy/x-common/'))
for i in packs:
apks.append(android.apk.APK(apk=i, prefix='assets/renpy/common/'))
split_apks.append(apks[-1])
# Files on disk should be checked before archives. Otherwise, among
@@ -360,7 +370,8 @@ def scandirfiles_from_apk(add, seen):
# Strip off the "x-" in front of each filename, which is there
# to ensure that aapt actually includes every file.
f = "/".join(i[2:] for i in f.split("/"))
if apk not in split_apks:
f = "/".join(i[2:] for i in f.split("/"))
add(None, f, files, seen)
@@ -579,7 +590,9 @@ def load_from_apk(name):
"""
for apk in apks:
prefixed_name = "/".join("x-" + i for i in name.split("/"))
prefixed_name = name
if apk not in split_apks:
prefixed_name = "/".join("x-" + i for i in name.split("/"))
try:
return apk.open(prefixed_name)
@@ -691,7 +704,9 @@ def loadable_core(name):
pass
for apk in apks:
prefixed_name = "/".join("x-" + i for i in name.split("/"))
prefixed_name = name
if apk not in split_apks:
prefixed_name = "/".join("x-" + i for i in name.split("/"))
if prefixed_name in apk.info:
loadable_cache[name] = True
return True
+12 -6
View File
@@ -98,20 +98,20 @@ def parse_image_name(l, string=False, nodash=False):
return tuple(rv)
def parse_simple_expression_list(l):
def parse_simple_expression_list(l, image=False):
"""
This parses a comma-separated list of simple_expressions, and
returns a list of strings. It requires at least one
simple_expression be present.
"""
rv = [ l.require(l.simple_expression) ]
rv = [ l.require(l.simple_expression, image=image) ]
while True:
if not l.match(','):
break
e = l.simple_expression()
e = l.simple_expression(image=image)
if not e:
break
@@ -133,7 +133,7 @@ def parse_image_specifier(l):
behind = [ ]
if l.keyword("expression") or l.keyword("image"):
expression = l.require(l.simple_expression)
expression = l.require(l.simple_expression, image=True)
image_name = (expression.strip(),)
else:
image_name = parse_image_name(l, True)
@@ -154,7 +154,7 @@ def parse_image_specifier(l):
if at_list:
l.error("multiple at clauses are prohibited.")
else:
at_list = parse_simple_expression_list(l)
at_list = parse_simple_expression_list(l, image=True)
continue
@@ -172,7 +172,7 @@ def parse_image_specifier(l):
if zorder is not None:
l.error("multiple zorder clauses are prohibited.")
else:
zorder = l.require(l.simple_expression)
zorder = l.require(l.simple_expression, image=True)
continue
@@ -271,6 +271,12 @@ def parse_menu(stmtl, loc, arguments):
say_ast = finish_say(l, l.get_location(), who, what, attributes, temporary_attributes, interact=False)
if isinstance(say_ast, list):
if len(say_ast) == 1:
say_ast = say_ast[0]
else:
l.error("Monologue mode cannot be used in a menu.")
l.expect_eol()
l.expect_noblock("say menuitem")
continue
+2 -2
View File
@@ -1036,8 +1036,8 @@ cdef class HBFont:
x = <int> (glyph.x + xo + glyph.x_offset)
y = <int> (glyph.y + yo + glyph.y_offset)
underline_x = x - glyph.delta_x_adjustment
underline_end = x + <int> (glyph.advance + expand + .9999)
underline_x = <int> (glyph.x + xo - glyph.delta_x_adjustment)
underline_end = <int> (glyph.x + xo + glyph.advance + expand + .9999)
cache = self.get_glyph(glyph.glyph)
+87 -23
View File
@@ -1406,7 +1406,8 @@ class Layout(object):
if value[0] in "+-":
push().size += int(value)
elif value[0] == "*":
push().size = int(float(value[1:]) * push().size)
ts = push()
ts.size = int(float(value[1:]) * ts.size)
else:
push().size = int(value)
@@ -1735,6 +1736,13 @@ class Layout(object):
The text shader.
"""
first_shader = ts
for l in lines:
for g in l.glyphs:
first_shader = g.shader
break
tw, th = tex.get_size()
if lines:
@@ -1745,12 +1753,17 @@ class Layout(object):
# The number of glyphs in the mesh.
n_glyphs = sum(len(l.glyphs) for l in lines)
mesh = renpy.gl2.gl2mesh2.Mesh2.text_mesh(n_glyphs)
mesh = renpy.gl2.gl2mesh2.Mesh2.text_mesh(n_glyphs + 2 * len(lines))
# The y coordinate of the top line.
top = 0
# The index of the last glyph to be shown.
last_index = 0
# The time of the last glyph to be shown.
last_time = 0.0
for line in lines:
# Line bottom is the y coordinate of the bottom line.
@@ -1766,26 +1779,48 @@ class Layout(object):
first_glyph = None
last_glyph = None
left = 0
if first_glyph:
right = first_glyph.x + self.add_left
else:
right = tw
# Generate a psuedo-glyph for the text to the left of the line.
# These pseudo-glyphs are used to make sure that outlines of lines above and below
# are displayed.
if outline:
if right > 0 and (ts == first_shader):
cx = 0 + right / 2
cy = outline + line.baseline
mesh.add_glyph(
tw, th,
cx, cy,
last_index,
left, top, right, bottom,
last_time, last_time,
line.baseline, line.height - line.baseline,
self.add_left, self.add_top,
)
# Generate the actual glyphs.
for g in line.glyphs:
left = right
if g.time == -1:
continue
# Check that this is the right shader to use.
if (g.shader is not ts) and (g.shader != ts):
continue
# The x-coordinate of the left edge of the glyph.
if g is first_glyph:
left = g.x - self.add_left
else:
left = g.x + outline
# The x-coordinate of the right edge of the glyph.
if g is last_glyph:
right = g.x + g.advance + outline * 2 + self.add_right
right = g.x + g.advance + outline * 2 + self.add_left + self.add_right
else:
right = g.x + g.advance + outline
right = g.x + g.advance + outline + self.add_left
if left < 0:
left = 0
@@ -1794,7 +1829,7 @@ class Layout(object):
# The center coordinates of the glyph. These aren't the
# actual center, but the center of the baseline.
cx = g.x + g.advance / 2
cx = g.x + g.advance / 2 + self.add_left
cy = outline + line.baseline
duration = g.duration
@@ -1808,14 +1843,43 @@ class Layout(object):
left_time = g.time - duration
right_time = g.time
mesh.add_glyph(
tw, th,
cx, cy,
g.index,
left, top, right, bottom,
left_time, right_time,
g.ascent, g.descent,
)
# Check that this is the right shader to use.
if (g.shader is ts) or (g.shader == ts):
mesh.add_glyph(
tw, th,
cx, cy,
g.index,
left, top, right, bottom,
left_time, right_time,
g.ascent, g.descent,
self.add_left, self.add_top,
)
last_time = g.time
last_index = g.index
# Handle the empty space to the right of the last glyph.
if outline:
if right < tw and (ts == first_shader):
left = right
right = tw
cx = left + right / 2
cy = outline + line.baseline
mesh.add_glyph(
tw, th,
cx, cy,
last_index,
left, top, right, bottom,
last_time, last_time,
line.baseline, line.height - line.baseline,
self.add_left, self.add_top,
)
top = bottom
+2 -2
View File
@@ -51,8 +51,8 @@ class Version(object):
Version("main", 3, "8.4.0", "TBD")
Version("fix", 3, "8.3.1", "Second Star to the Right")
Version("fix", 2, "7.8.1", "Straight on Till Morning")
Version("fix", 3, "8.3.3", "Second Star to the Right")
Version("fix", 2, "7.8.3", "Straight on Till Morning")
def make_dict(branch, suffix="00000000", official=False, nightly=False):
+3 -1
View File
@@ -51,7 +51,9 @@ For example, the above behaviour, but written as a CDS::
def lint_random(parsed_object):
for i in parsed_object:
renpy.error(renpy.check_text_tags(i.what))
check = renpy.check_text_tags(i.block[0].what)
if check:
renpy.error(check)
renpy.register_statement(
+89
View File
@@ -4,6 +4,92 @@ Changelog (Ren'Py 7.x-)
*There is also a list of* :doc:`incompatible changes <incompatible>`
.. _renpy-8.3.3:
.. _renpy-7.8.3:
Fixes
-----
Changes to audio filters take place immediately after reload.
:var:`config.skip_sounds` now works as documented.
:class:`Model` now creates meshes as described in the documentation when no textures are supplied.
The image attributes screen now quotes image and displayable names.
An issue with rollback not restarting music has been fixed.
Underlines and strikethroughs will not be broken when drawn using harfbuzz-based text shaping.
Ren'Py now ensures that IMEs are activated on the primary window when the game starts, rather than on
a presplash window.
Bars no longer lose the ``selected_hover`` prefix when adjusted using keyboard or game controller.
Rounding errors during pixel perfect text positioning have been fixed. These errors could cause text to
jump 1 pixel during dissolves.
The rarely-used ``gl_anisotropic`` transform property now works.
The :propref:`keyboard_focus_insets` property now works as documented.
A rounding issue that could cause :propref:`bar_invert` from working has been fixed.
Ren'Py will render a displayable a second time if :propref:`box_wrap` is True, to ensure that the displayable
is offered the correct amount of space when wrapped to a second line. In rare cases, this could change layout.
Controller events can now cause Ren'Py to focus a displayable with `default_focus` set. Previously, these
events weren't considered inputs by the focus system.
There have been a number of fixes to the way Ren'Py handles dragging a viewport filled with buttons.
A drag may now contain a draggable viewport, allowing a window to be more directly emulated.
Other Changes
-------------
Android bundles now use install-time assets packs, rather than fast-follow packs, to ensure that all assets
are available when the game is run.
The "always" option to _renpysteam.keyboard_mode is no longer supported. If given, the "once" mode is
used, requiring the player to explicitly request the Steam Deck keyboard when required.
The number of frames that Ren'Py passes through the rendering pipeline before switching to powersave
mode has been increased to 12, to ensure that frames make it through compositors in a timely manner.
Ren'Py locks the image cache less, which should prevent some frame drops when loading images.
Synchronized start of audio/video no longer occurs on movie channels unless explicitly requested.
When rolling back to a point where a looping :class:`Movie` was showing, the looping movie will be played again
if it had been stopped.
A :class:`Movie` will only stop movies that it has played, rather than any movie on the associated channel.
When :func:`renpy.set_audio_filter` is called with `immediate` false, the filter will be applied when
the queued file is played, rather than at some indeterminate time in the future.
The :class:`Frame` displayable is no longer adjusted to be pixel perfect, preventing visual glitches.
When using text shaders to display text with outlines, Ren'Py will create pseudo-glyphs. These pseudo-glyphs
cover the start and end of each line, and are used to ensure the outlines will be shown.
.. _renpy-8.3.2:
.. _renpy-7.8.2:
8.3.2 / 7.8.2
=============
Fixes
-----
Fixed a build issue with 8.3.1 and 7.8.1 that prevented the Android version of Ren'Py from starting properly,
making games unplayable.
.. _renpy-8.3.1:
.. _renpy-7.8.1:
@@ -13,6 +99,9 @@ Changelog (Ren'Py 7.x-)
Fixes
-----
Image keywords (``zorder``, ``behind``, ``at``, ``onlayer``, and ``transform``) may not occur in an expression,
like a list after ``at``.
Using local shader variables by name in {shader} tags now works.
Textshaders now work with very large sizes and numbers of characters.
+1 -1
View File
@@ -36,7 +36,7 @@ The callback is called with at the keyword arguments:
This is true if the dialogue causes an interaction to occur.
`type`
The type of character (e.g. "nvl", "adv", "balloon").
The type of character (e.g. "nvl", "adv", "bubble").
`what`
The text that is going to be supplied to the what displayable.
+1 -1
View File
@@ -769,7 +769,7 @@ Media (Music, Sound, and Video)
.. var:: config.skip_sounds = False
If False, non-looping audio will not be played when Ren'Py is
If True, non-looping audio will not be played when Ren'Py is
skipping.
.. var:: config.sound = True
+18
View File
@@ -19,7 +19,9 @@ the omission in future versions.
* Ajkahn96
* Aleema
* Alessio
* Aleksandar Belic Aleksanchez
* Alex
* Alexandre Detiste
* Alexandre Tranchant
* Alisha Taylor
* Altskop
@@ -31,6 +33,7 @@ the omission in future versions.
* Arowana-vx
* Asfdfdfd
* Auro-Cyanide
* Avonder
* Awakening
* Ayowel
* Baekansi
@@ -39,12 +42,14 @@ the omission in future versions.
* Ben Wright
* BlackDragonHunt/Mangagamer
* Brainos
* Brimbel
* Bryan Tsang
* Carl
* Capntrips
* Caryoscelus
* CensoredUsername (C)
* Charles Surett
* Chengtian He
* Cherie Davidson
* Chrisclone
* Chris Mear
@@ -53,6 +58,7 @@ the omission in future versions.
* Clinton Nguyen
* CobaltCore
* CodePsy
* Craig de Stigter
* Craig P. Donson
* 被诅咒的章鱼
* DaFool
@@ -101,6 +107,7 @@ the omission in future versions.
* GNCanva
* Gouvernathor
* Gratusfr
* Gregor Riepl
* Gregory Pease
* Grey
* Gustavo Carvalho
@@ -152,6 +159,7 @@ the omission in future versions.
* Konstantin Mozheyko
* Konstantin Nikolayev
* KorDen
* Kuro
* Kuroonehalf
* Kyouryuukunn
* Lapalissiano
@@ -166,13 +174,17 @@ the omission in future versions.
* Liu Wenyuan
* Lore
* Lucas Ramage
* Luejerry
* M-From-Space
* Maciej Katafiasz
* Maissara Moustafa
* Mal Graty
* Marcel
* Mark
* Markus Koschany
* Mason Chou
* Matias B.
* Matěj Račinský
* Matt George
* Matthew Vimislik
* Max le Fou
@@ -197,9 +209,11 @@ the omission in future versions.
* NoJoker
* Nolanlemahn
* Noriverwater
* Npckc
* Numerlor
* Nxcrft
* Nyaatrap
* OctoSpacc
* OleSTEEP
* Oscar Six
* Oshi-Shinobu
@@ -208,6 +222,7 @@ the omission in future versions.
* Paul Morio
* Pavel Langwell
* Peter DeVita
* Petr Abdulin
* Philat
* Pionere
* Piroshki
@@ -243,6 +258,7 @@ the omission in future versions.
* Siege-Wizard
* SleepKirby
* Spiky Caterpillar
* Sunrise Sarsaparilla
* Susnux
* Sylvain Beucler
* Symegac
@@ -255,6 +271,8 @@ the omission in future versions.
* Totally a booplicate
* Vadim Karpenko
* Valery Iwanofu
* Veydzh3r
* Viktoras Agejevas
* Viliam Búr
* Vladya
* Vollschauer
+1 -1
View File
@@ -148,7 +148,7 @@ resolution graphics.
Oversampling is automatically enabled if the image ends with an '@' followed
by a number, before the extension. For example, :file:`eileen happy@2.png` is
2x oversampled, and :file:`eileen happy@3x.png` will be 3x oversampled. Oversampling
2x oversampled, and :file:`eileen happy@3.png` will be 3x oversampled. Oversampling
can also be enabled by giving the `oversample` keyword argument to :func:`Image`.
A directory can also specify how much to oversample images inside it. For example,
+1 -1
View File
@@ -110,7 +110,7 @@ as of version 8.1.0 is as follows::
help = [ 'K_F1', 'meta_shift_/' ],
choose_renderer = ['alt_K_g', 'shift_K_g' ],
progress_screen = [ 'alt_shift_K_p', 'meta_shift_K_p', 'K_F2' ],
accessibility = [ 'K_a' ],
accessibility = [ 'shift_K_a' ],
# Accessibility.
self_voicing = [ 'alt_K_v', 'K_v' ],
+1 -1
View File
@@ -373,7 +373,7 @@ Tags that apply to all text are:
The shader tag applies a text shader to a section of text.
The argument is the name of the shader to apply. ::
"This text is {shader=jitter:speed=3}jittery{/shader}."
"This text is {shader=jitter:u__jitter=1.0, 3.0}jittery{/shader}."
See :doc:`textshaders` for more information.
+6
View File
@@ -234,6 +234,12 @@ locations, but these are passed to the shader as different vertices.
The rectangle containing the glyph, in drawable pixels. This is a vec4 with the x, y, width, and height of the rectangle,
in drawable pixels. This can be converted to texture coordinates by dividing it by ``res0``.
Pseudo-Glyphs
-------------
When drawing outlined text, Ren'Py will creates pseudo-glyphs that cover the start and end of each line. If a line
is blank, one pseudo-glyph is created covering the whole line. These pseudo-glyphs are necessary to cover areas
where outlines may extend into a line above or below the current line.
Example
-------