I have this annoying issue where the game will open on the wrong monitor
in fullscreen mode, because that monitor is considered to be display 0,
whereas the primary monitor I want is display 1.
To mitigate this somewhat, the game now stores the display index that it
was closed on, and will save it to settings. Then the next time the game
opens, it will open on that display index. This should work pretty well
as long as you aren't changing your monitor setup constantly.
Of course, none of this applies if your window manager is busted. For
example, on GNOME Wayland, which is what I use, in windowed mode the
game will always open on the monitor the cursor is on, and it won't even
be centered in the monitor. But it works fine if I use XWayland via
SDL_VIDEODRIVER=x11.
Previously, the game would not store the size of the window itself, and
would always call SDL_GetRendererOutputSize() (via
Screen::GetWindowSize()) to figure out the size of the window. The only
problem is, this would return the size of the whole monitor if the game
was in fullscreen mode. And the only place where the original windowed
mode size was stored would be in SDL itself, but that wouldn't persist
after the game was closed.
So, if you exited the game while in fullscreen mode, then your window
size would get set to the size of your monitor (1920 by 1080 in my
case). Then when you opened the game and toggled fullscreen off, it
would go back to the default window size, which is 640 by 480.
This is made worse, however, if you were in forced fullscreen mode when
you previously exited the game in windowed mode. In that case, the game
saves the size of 1920 by 1080, but doesn't save that you were in
fullscreen mode, so opening the game not in forced fullscreen mode would
result in you having a 1920 by 1080 window, but in windowed mode.
Meaning that not even fullscreening and unfullscreening would put the
game window back to normal size.
The solution, of course, is to just store the window size ourselves,
like any other screen setting, and only use GetWindowSize() if needed.
And just to make things clear, I've also renamed the GetWindowSize()
function to GetScreenSize(), because if it was named "window" it could
lead one to think that it would always return the size of the screen in
windowed mode, when in fact it returns the size of the screen whatever
mode it is in - fullscreen size if in fullscreen mode and window size if
in windowed mode.
And doing this also fixes the FIXME above Screen::isForcedFullscreen().
This fixes the following bug that only occurs on Wayland: If the game is
configured to be fullscreened and in stretch mode, on startup, it won't
be in stretch mode. It will appear to be in letterbox mode, but the game
still thinks it's in stretch mode.
This is because during the ResizeScreen() call on startup, for whatever
reason, the window size will be reported to be the default size (640 by
480) instead of the screen resolution of the monitor, as one would
expect from being in fullscreen. It seems like when the game queries the
window size, the window isn't actually in fullscreen at that time, even
though this is after fullscreen has been set to true.
To fix this, I decided to always update the logical size before
SDL_RenderPresent() is called. To make this neater, I put the scaling
code in its own function named UpdateScaling().
This bug has existed since 2.3 and does not occur on X11. I tested this
on GNOME Wayland, and for testing it on X11, I used Openbox in a Xephyr
session while running VVVVVV with SDL_VIDEODRIVER=x11.
It turns out this texture is only used as a temporary texture to draw
the screen with an offset before rendering it to the output target.
I thought it was used for drawing the map menu animation, but that was
only true of `tempBuffer`, and is no longer true of the new render
system.
When `blackout` is active, the screen (to simplify) stops getting drawn
to. The excecption is textboxes, which draw anyway. But since the
screen isn't being cleared, removed textboxes stay on screen, since
that texture isn't being cleared. In the SDL_Renderer PR, I
accidentally broke this behavior, so this commit fixes it.
Fixes#951.
Dav999 pointed out this potential issue on Discord.
While basically all memcmp implementations will terminate early here if
there's a null byte (because it's mismatched), it doesn't hurt to add
the check here.
This fixes a bug where the trinket collection text boxes, along with the
cutscene bars, would stay on-screen if the player warped to the ship
while they were up.
This only happens during the gamestate 0 anti-softlock checks, and only
if completestop is active in the first place, so text boxes aren't
cleared if the player is doing something that wouldn't lead to a
softlock otherise.
Fixes#921.
This fixes a regression where the behavior of duplicate player entities
is different, causing a gameplay section in Vespera Scientifica to be
impossible, as described in #903.
In the level, you are allowed to flip in mid-air. Vespera accomplishes
this by having two duplicate player entities stuck in a platform. One of
them is responsible for letting the player flip in one direction, and
one of them is responsible for letting them flip in the other.
In 2.3, this works because in order for a player entity to flip,
`game.jumppressed` is checked, and the entity will flip if
`game.jumppressed` is greater than 0, then `game.jumppressed` will be
set to 0. In this way, whenever a player entity flips, it will set
`game.jumppressed` to 0, so whenever the next player entity is
evaluated, `game.jumppressed` is 0 and thus _that_ entity will not flip.
This is because the for-loop surrounds both the `game.jumppressed` check
and flipping the entity. In 2.4 after #609 and subsequent patches,
however, this is not the case. Here, the for-loop only surrounds
flipping the entity. Therefore, the `game.jumppressed` check is
evaluated only once. So, it will end up flipping every player entity if
the entities are eligible. In this case, one of them is eligible twice,
resulting in the game flipping gravitycontrol four times, which is
essentially the same as not flipping at all (except for all the sound
effects).
Hence, the fix here is to make it so the for-loop surrounds the
`game.jumppressed` check.
Now, this doesn't mean that the intent of #609 - that duplicate player
entities have the same initial velocity applied to them when flipping -
has to be removed. We can just put the for-loops back in. But I
carefully implemented them in such a way that the overall function is
not quadratic, i.e. O(n²). Instead, there's a pass done over the
`obj.entities` vector beforehand to store all indexes of a player entity
in a temporary vector, and then that vector is used to update all the
player entities. In this manner, the function is still linear - O(n) -
over the number of entities in the room.
I tested this to make sure that no previous regressions popped up again,
including #839, #855, and #887. And #484 is still resolved.
Fixes#903.
This adds support for OGG files as sound effects (via renaming them to
the wrong .wav file extension), because in previous versions of the
game, SDL_mixer didn't care what the file extension was, and so some
people relied on this, as described in #900.
This is accomplished by copy-pasting the OGG loading code for music
tracks. For a bit of cleanliness, I put the WAV and OGG loading code in
separate functions.
This is mostly the same code, except that because sound effects don't
loop and can't be paused or resumed, there's no reserve buffer, and
there's no data for loop points.
Also, for some reason, the music loading code divided by 20 in the
`size` calculation. I found that this prematurely cut off sound effects,
and that it made more sense to just not do the division. I don't know
why it was there, but removing it works.
Also also, some OGG files don't work with this. Namely, ones produced by
FFmpeg. To test this, I just extracted 0levelcomplete.ogg from
vvvvvvmusic.vvv and replaced terminal.wav with it. And it works, so
hopefully I won't have to touch audio code again, although I might if
someone complains about this. But either way, I'm committing this
because it's better than it was before.
Fixes#900.
I noticed that this call wasn't using VVV_freefunc. I missed it earlier
when going through Music.cpp and checking for instances when
VVV_freefunc should have been used
(a926ce9851).
Sound effects already get recreated if the number of channels
mismatches, but the same could be true if the sample rate mismatches
too, which was the case with music tracks as described in #886.
So, just to be sure - and to be consistent with music tracks - sound
effects now check that the sample rate matches, too, and if not, will be
recreated.
This fixes an issue where sound effects of bit depths that weren't 16,
such as 8, were being played incorrectly, as described in #886.
The problem is that when loading the sound effect, we would always set
the bit depth to 16 no matter what! Instead, we should set the bit depth
to the actual bit depth of the file.
Fixes#886.
As described in #886, if a track was played when an existing track was
already playing, and the sample rates of the two tracks differ, then the
second track would play wrong and distorted.
This is because the second track would play with the sample rate of the
first. To fix this, halt the track if the sample rate is mismatched,
which destroys the voice. This results in the voice being recreated
later in the Play() function. The track is also halted if the number of
channels or bit depth is mismatched.
Fixes#886.
The style we have here is that functions with no arguments are to have
explicit `void` arguments, even though this doesn't apply in C++,
because it does apply in C.
Unfortunately, some have slipped through the cracks. This commit fixes
them.
This removes the `addnull` argument from `FILESYSTEM_loadFileToMemory`
and `FILESYSTEM_loadAssetToMemory`, and makes it so a null terminator is
always appended no matter what.
This simplifies things and removes the need for callers to make the
decision about null termination and what its implications are. Then you
get cases where null termination might not happen when it should be,
such as the one df577c59ef (#947) fixed.
When FIQ added the `addnull` argument in
5862af4445 (#117), I'm guessing he did it
because he wanted to be cautious about adding the null terminator to
every file, so he only did it for XML files, which was the only case
needed at the time. But really, there's no downsides to always appending
a null terminator. In fact, it's already always done whenever the STDIN
buffer is loaded.
Because of how `blackout` works, screen shaking must clear the gameplay
buffer. `blackout` simply pauses rendering, so if the gameplay buffer
gets cleared, then the screen will just be black, otherwise it'll look
like the game is "frozen". VVVVVV only uses `blackout` during screen
shaking, so it works as intended. However, when reimplementing this
behavior in the move to using the SDL_Renderer system, I failed to
notice that since my implementation always clears the gameplay buffer
when shaking, if you open the menu during a shake, instead of seeing
gameplay during the transition animation, you only see black. This has
been fixed with a simple `game.blackout` check before clearing the
gameplay buffer.
For some reason, originally, this function mutated the std::string
passed into it by reference. So calling the function could potentially
mutate whatever got passed in, and callers potentially could have relied
on that behavior.
Now that the surrounding callsites have all been cleaned up, though
(especially scriptclass::startgamemode), it's clear that it's only used
in two places: Loading the level in the editor, and loading the level in
live gameplay. In both cases, the passed-in string isn't used ever again
afterwards.
So, it's safe to delete the mutable reference without any undesirable
effects, making the code cleaner and easier to understand. The function
now mutates a copy instead of mutating whatever the caller has.
An example is Maximally Misleading Miserable Misadventure, which has a
font.txt which includes all ASCII characters starting with a 0x00 byte.
This would accidentally null-terminate the string too early.
Instead, we now use the total length of the file again, and keep
getting the next UTF-8 codepoint until the file ends. We still need to
null-terminate it - it protects against incomplete sequences getting
the UTF-8 decoder to read out of bounds.
This was an oversight when we migrated to the new UTF-8 system - it
expects a null-terminated string, but the utfcpp implementation worked
with a pointer to the end of the file instead.
I also added an assert in FILESYSTEM_loadFileToMemory() so this is less
likely to happen again - because there should be no valid reason to
have a NULL pointer for the total file size, as well as not wanting a
null terminator to be added at the end of the file.
`hardestroom` currently stores the current roomname, but it was missing
a call to get the translated string. This has been added, fixing the
hardest room appearing as untranslated.
See the previous two commits, a lot of the time we don't need
std::string objects to be passed to these functions because we already
have C strings.
Commit 1/3: font::print_wrap
Commit 2/3: font::print
-> Commit 3/3: font::len
Turns out I was overplaying my hand a little when changing font::print
from std::string to const char*, so instead, I'll overload the
function: it can take either a const char* (the main function) or a
std::string (a wrapper). This means any C string that's printed
everywhere else (which is common, especially because loc::gettext gives
them) no longer needs to be converted to a std::string object each call.
Commit 1/3: font::print_wrap
-> Commit 2/3: font::print
Commit 3/3: font::len
We no longer need to pass a std::string object to the print and len
functions - in fact, we often only have a C string that we want to
print or get the visual width of (that C string most often comes from
loc::gettext), and it's a bit wasteful to wrap it in a new std::string
object on every print/len call.
This does mean adding a few more .c_str()s, but there's not many places
where a std::string is being passed to these functions, and we already
use .c_str() sometimes.
-> Commit 1/3: font::print_wrap
Commit 2/3: font::print
Commit 3/3: font::len
This removes memory churn caused by using analogue mode.
The surfaces are only allocated if analogue mode is turned on, and kept
after they are initialized. Otherwise, if analogue mode is never turned
on (which will be the case for the vast majority of the time the game is
played), then no extra memory is used.
Drawing a texture onto itself seems to produce issues on Metal.
To fix this, use a temporary texture instead, that then gets drawn onto
the original texture.
Fixes#927.
This is because destroying the renderer causes use-after-frees since the
renderer destroys all textures when it gets destroyed.
This fixes a Valgrind error where an invalid read occurs because the
font textures get destroyed again after the renderer is destroyed.
The hashmap would get populated with the name of each font, as each
font was being added. Unfortunately, adding a font would also realloc
the storage for fonts, in which the names are also stored... Possibly
invalidating the pointers to the names. This is now fixed by populating
the hashmap after all the fonts are added.
For consistency, since they are created in create_buffers as well. I
checked with Valgrind (which is very noisy on Wayland, it turns out),
but I didn't see anything about them not being freed. It doesn't hurt to
use VVV_freefunc here anyway, though, since it does a NULL check and
nulls the pointer afterwards, which should prevent double-freeing and
use-after-frees.
I'm going to soon be creating an actually temporary texture, so having
two textures named "temp" would get confusing. This is also a good
chance to correct the name of this texture, because it's not really
temporary, but it's used for map menu animation rendering.
This commit replaces the old system with the new one, making it much
easier to edit the transforming and glitchy roomnames. Additionally,
this syncs flag 72 to finalstretch.
Co-authored-by: Misa Elizabeth Kai <infoteddy@infoteddy.info>
This commit adds a better system for animated roomnames.
The old system, like many other systems, were very hardcoded, and can be
described as mostly else-if chains, with some fun string comparisons.
The new system uses lists of text for transformations and glitchy names,
making it much easier to add new cases if needeed.
This commit implements the system but does not replace the old system,
where that is done in the next commit.
The settings for special roomnames can be read from level XML, and
`setroomname()` can be used from commands to set a new, static name.
I'm also planning to change the argument types of font::len,
font::print and font::print_wrap from const std::string&s to
const char*s, but I'll do that separately.
This is a small library I wrote to handle UTF-8.
Usage is meant to be as simple as possible - see for example decoding
a UTF-8 string:
const char* str = "asdf";
uint32_t codepoint;
while ((codepoint = UTF8_next(&str)))
{
// you have a codepoint congrats
}
Or encoding a single codepoint to add it to a string:
std::string result;
result.append(UTF8_encode(0x1234).bytes);
There are some other functions (UTF8_total_codepoints() to get the
total number of codepoints in a string, UTF8_backspace() to get the
length of a string after backspacing one character, and
UTF8_peek_next() as a slightly less fancy version of UTF8_next()), but
more functions could always be added if we need them.
This will allow us to replace utfcpp (utf8::unchecked) and also fix
some less-than-ideal code:
- Some places have to resort to ignoring UTF-8 (next_wrap) or using
UCS-4→UTF-8 functions (VFormat had to use PHYSFS ones, and one other
place has four lines of code including a std::back_inserter just for
one character)
- The iterator stuff is kinda confusing and verbose anyway
Originally the changedir command was used here, making
Vitellary look left and then immediately snap back to
looking right. Now the changeai command is used instead
to make him actually look left, and then look back to
the right on his last textbox.
The `-addresses` command-line option added in 64be99d4 helps
autosplitters on platforms where VVVVVV is not built as a
position-independent executable. macOS has made it increasingly
difficult, or impossible, to build binaries without PIE.
Adding an obvious string to search for will help tools that need to deal
with versions of VVVVVV built with PIE. The bytestring to search for is
`[vVvVvV]game`, followed by four null bytes (to avoid finding it in the
program code section). This identifies the beginning of the game object;
addresses to other objects can be figured out by relative offsets
printed by `-addresses`, since ASLR can only change where the globals
begin.
Partially fixes#928; it may still be advisable to figure out how to
explicitly disable PIE on Windows/Linux.
Activity zone prompts have always been limited to a single line,
because the text box had a hardcoded size. A translator requested for
the possibility to add a subtitle under music names for the jukebox,
and the easiest solution is to make it possible to translate a prompt
with multiple lines. This is possible now, and the textbox even
wordwraps automatically.
(I wouldn't really like to see translations using multiple lines for
stuff like "Press ENTER to talk to Vitellary", especially if it wraps
with one name and not with another, but if a string is too long,
wordwrapping will look better than text running out of the box.)
There were two print calls, one for the transparent case, and one for
a regular textbox. The print calls were nearly the same except for the
color, and for some reason the transparent case didn't have PR_CJK_LOW
(that one is on me).