`platv` is a room property that controls platform speed, and it has
always worked (other than some weird storage issues due to a bug).
However, the editor has no way to edit it currently, so people had to
resort to editing the level file by hand, or with a third-party tool.
This commit simply adds an easy way to modify platform speed.
VED has a fill bucket subtool for tiles and backgrounds, which is
really useful when creating rooms. This commit adds a fill bucket as
well, with an adaptive tool highlight, unlike VED.
This fixes a regression from 2.3 where the very beginning of A New
Dimension isn't silent.
A New Dimension's level music is set to Predestined Fate, but there is a
script box the player touches right upon spawning that stops playing
music. Then after the player ascends upwards, they touch a script box
that plays Predestined Fate. But in 2.2 and before, the very beginning
is silent due to the script box that stops music.
However in 2.3, due to the changes made to playing music during a fade,
the initial level music trying to play Predestined Fate during a music
fadeout from the main menu resulted in Predestined Fate being stored in
the nice fade variables, which kicked in after halting the music since
halting didn't reset those variables.
This resets those variables whenever music is halted, and now the
beginning of A New Dimension is back to how it was in 2.2 and before.
This fixes a regression where the red channel 0 glitch didn't work,
because the surface was always whitened, because LoadSprites would
whiten the image before converting it to surface.
This regression happened because of #923.
Fixes#962.
Misa asked me if this should only work for non-transparent textboxes,
and it shouldn't - that was kind of an oversight.
To make it work for transparent textboxes as well, I made a little
restructuring to avoid duplicating the code - fill_buttons() is now
called textbox_line(), and it replaces the direct accessing of the
textbox lines in the printing loops. The code that checks the width
of the textbox does not need to be copied, since the text box is
naturally not drawn for transparent text boxes.
This makes it so that `CustomEntity`s, at least internally, do not use
global tile position. Instead, they will use room-x and room-y
coordinates, which will be separate from their x- and y- positions.
This makes it much easier to deal with `CustomEntity`s, because you
don't have to divide and modulo everywhere to use them.
Since editorclass::add_entity and editorclass::get_entity_at expect
global tile position in their arguments, I've added room-x and room-y
arguments to these functions too.
Of course, due to compatibility reasons, the XML files will still have
to use global tile position. For the same reason, warp token
destinations are still using global tile position too.
As reported by Lilithtreasure on the VVVVVV Discord server, it is
possible to get gray moving platforms and enemies in the main game.
This happens if you play the main game after loading a custom level with
a room that is gray at the same coordinates. E.g. if you play a custom
level with a gray room at (12, 4), then exit and go to Gantry and Dolly
in the main game which is also at (12, 4), then the platforms there
would be gray too.
This is because there is a missing map.custommode check.
The missing piece from sound effects was handling what to do when the
buffer ran out. Which seems to happen often when decoding from OGG,
unlike WAV. This handling involves callbacks to functions named
refillReserve and swapBuffers.
Without this code,some sound effects would be cut off early, as
documented in #953. This might also explain the division by 20 - which
I've copied too, just in case.
Now OGG sound effect tracks should be identical to music tracks (except
I've stripped the looping code out).
Fixes#953.
We were using this function to check if the format of the existing voice
is different from the format of the voice we intended to play. However,
whereas the format we use is the FAudioWaveFormatEx struct, the only
details we get from FAudioVoice_GetVoiceDetails is the
FAudioVoiceDetails struct, which has much less details and is missing
important things like whether the format is 8-bit or 16-bit or something
else.
And of course, if we don't check that the number of bits matches, then
it still leads to playback issues as described in #953.
There are no other functions in FAudio that operate on
FAudioWaveFormatEx structs. So instead, we'll just have to do it
ourselves, and store the format of the existing voice alongside the
format of the intended voice. And then use SDL_memcmp to make sure the
formats are the same before playing, and if not, then destroy and
re-create the voice.
Fixes#953.
There's a few places where textboxes are constructed through code, but
they pass in the color's RGB values in manually. This commit
unhardcodes most of them them, replacing them with a color lookup.
The ones that weren't changed are special cases, like `175, 174, 174`.
I thought 2.2 already had separate map and interact gamepad bindings,
and they simply got neglected and broken with 2.3's split Enter/E key
option. But actually, the new split Enter/E option also applied to
gamepad buttons, and a separate interact binding was added, without
really indicating anything if Enter and E are not split. And I guess
using the same button for map and interact by default also makes sense
for simplicity...
This commit makes sure the button glyph displayed in-game is at least
the correct button. The gamepad bindings menu is also slightly modified
to darken the interact option - the button glyphs code now
automatically causes them to show equal buttons anyway, so it wasn't
too big of a change to also darken the line and disable the binding
option. To me this says: "the interact key is fixed to be the same as
enter right now, but there is a way to change it."
It's still not ideal of course, and I know a similar change to the
gamepad menu to hide the interact option was rejected a year ago
because action sets would already fix it, but it's a year later now,
and showing misleading button glyphs should be fixed in 2.4, whether it
will already have action sets or not (And at this point I think the
plan already is to keep the existing input system for 2.4)
And it's a 3 line diff to darken and disable the option, compared to
fully hiding the option.
The language screen has a "Press Space, Z, or V to select" hint, which
I forgot to update for supporting button glyphs in #943, so this commit
does.
<action_hint>Press Space, Z, or V to select</action_hint>
<gamepad_hint>Press {button} to select</gamepad_hint>
This was easier than I expected - just add an optional buttons="1"
attribute to cutscenes.xml. It's treated like the speaker attribute -
it's only there as context for the translator, and for the cutscene
test.
If a controller button is pressed, a controller is connected (even at
startup!) or an axis is moved, the game will switch to displaying
controller glyphs. If a keyboard key is pressed or the last controller
is removed, the game will switch to displaying keyboard keys.
This adds mappings from SDL's Xbox-based SDL_GameControllerButton
constants, to glyphs for the following layouts:
- LAYOUT_NINTENDO_SWITCH_PRO,
- LAYOUT_NINTENDO_SWITCH_JOYCON_L,
- LAYOUT_NINTENDO_SWITCH_JOYCON_R,
- LAYOUT_DECK,
- LAYOUT_PLAYSTATION,
- LAYOUT_XBOX,
- LAYOUT_GENERIC,
There may still be errors in these, but they should be mostly correct.
I'm leaving it up to Ethan to make it show the correct button glyphs
for the correct controllers being connected (and possibly to fix these
mappings where needed).
Violet's dialogue now looks like this:
squeak(purple)
text(purple,0,0,2)
Remember that you can press {b_map}
to check where you are on the map!
position(purple,above)
textbuttons()
speak_active
The new textbuttons() command sets the next textbox to replace {b_map}
with the map button, and {b_int} with the interact button. The
remaining keys would be added as soon as they need to be added to
ActionSets.h as well.
This adds a function that converts an action (such as interacting
in-game) to the corresponding button text ("ENTER", "E") or button
glyph (PlayStation triangle, Steam Deck Y, etc). This function
currently only gives the existing ENTERs or Es, because I don't know
how best to detect controller usage, or whether the game is running on
a Steam Deck, or what buttons need to be displayed there. Still, it
should now be really easy to adapt the rendering of keyboard keys to
consoles, controllers, or rebound keys.
To identify the actions that currently need to be displayed, this
commit also adds the initial enums for action sets as described by
Ethan in a comment in #834 (Jan 18, 2022).
These visualize the horizontal gravity line kludge for rooms beside
eachother. When you enter another room, gravity lines which look like
they're connected between the rooms try to have the same activated
state.
Basically, if you're in room (1,4) and you go into (2,4), if a
gravity line in (1,4) is activated (gray, on cooldown) and it's
touching the gravity line in (2,4), that gravity line will also be
activated.
This uses DDA (https://w.wiki/6RSQ) to draw a line between the previous
frame's mouse position, and the current frame's mouse position. This
means that there will no longer be gaps in lines of tiles if you move
your mouse fast enough (which is actually rather slow, so it gets
annoying quickly).
The editor's timestep is no longer hardcoded to 24, as I assume that
was only done so there would be less gaps in lines of tiles drawn.
With interpolation, that is no longer an issue, so I've removed the
editor's special case for the timestep.
Scripts used a weird "hook" system, where script names were extracted
into their own list. This was completely unneeded, so it has been
replaced with using the script.customscripts vector directly.
The script editor has been cleaned up, so the cursor's Y position is
relative to the entire script, rather than what's just displaying on
the screen currently. This simplifies a lot of code, and I don't know
why it was done the other way in the first place.
The script selector and script editor cursors have been sped up, since
both lists can be massive, and waiting 6 frames per line is extremely
slow and boring. This is still slow and boring, but we don't have
proper input repetition yet.
This commit moves everything left out of the previous commit to the
state system. This means a bunch of new functions were added as well,
to avoid the code in each function becoming too huge. A lot of cleanup
was done as well, simplifying logic, merging duplicated code, etc.
This commit does NOT touch "script hooks", script editor logic and
autotiling, as those seem to be their own separate beasts.
While warp lines were being drawn, they also got resized to
automatically fit between collision. In renderfixed, gravity lines are
resized the same way. Doing logic while drawing is very poor practice,
so resizing of these has been moved into logic, and merged together.
Aside from some more cleanup, this commit also removes the very poorly
done right click emulation, when you hold CTRL and click. It never
worked well in the past, and even requires a right click to use, so
there's not really any point to keeping it around.
The drawer could definitely be improved further, however I cleaned up a
little bit of the code duplication. I'll have to take a closer look
some other time, but I'm pretty sure that the duplicated code at the
bottom can be removed with a few tweaks, but I'll do that carefully
in a different commit.
Tools were a mess, spread all over the code with hundreds of `else-if`
statements. Instead of magic numbers denoting tools, an enum has been
created, and logic has been extracted into simple switch/cases, shared
logic being deduplicated.
The base of a state system for the editor has been created as well,
laying a good path for further organization improvements. Because of
this, the entire editor no longer gets drawn underneath the menus,
except for a few pieces which I haven't extracted yet. Either way,
this should be good for performance, if that was a concern.
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.