1
0
mirror of https://github.com/TerryCavanagh/VVVVVV.git synced 2024-06-26 14:38:30 +02:00
Commit Graph

1768 Commits

Author SHA1 Message Date
Misa
0fc17ec277 Fix only removing duplicate script names from script name list
If there were two scripts with the same name, removing one of them would
only remove the other script from the script name list, and not also
remove the contents of said script - leading to a desync in state, which
is probably bad.

Fixing this isn't as simple as removing the break statement - I either
also have to decrement the loop variable when removing the script, or
iterate backwards. I chose to iterate backwards here because it
relocates less memory than iterating forwards.
2021-04-20 17:34:49 -04:00
Misa
779a48dbb4 Remove use of <algorithm> from editor.cpp
No need to use it when good ol' loops work just fine.

Iterating backwards is correct here, in case there happen to be more
than one of the item in the vectors, and also to minimize the amount of
memory that needs to be relocated.
2021-04-20 17:34:49 -04:00
Misa
b2c6c08621 Quote CMAKE_CXX_FLAGS
Otherwise this would result in multiple input arguments to the regex
instead of just one. Similar to shell scripting.
2021-04-20 14:22:53 -04:00
Misa
370e53f4d3 Draw minimap.png if it is mounted
This is a simple change - we draw minimap.png, instead of the generated
custom map, if it is a per-level mounted custom asset.

Custom levels have already been able to utilize minimap.png, but it was
limited - they could do gamemode(teleporter) in a script, and that would
show their customized minimap.png, but it's not like the player could
look at it during gameplay.

I would have done this earlier if I had figured out how to check if a
specific asset was mounted or not.
2021-04-19 10:08:38 -04:00
Misa
186f36beea Add error checking to base path setup
Previously, if the game couldn't set the write dir to the base
directory, or couldn't make the base directory, or couldn't calculate
the base directory, it would probably dereference NULL or read from
uninitialized memory or murder your family or something. But now, I've
eliminated the potential Undefined Behavior from the code dealing with
the base path.
2021-04-18 18:14:03 -04:00
Misa
2b4f3ab19e Pass size of output through instead of hardcoding it
Previously, this function had a bug due to failing to account for array
decay. My solution was to just repeat the MAX_PATH again. But in
hindsight I realize that's bad because it hardcodes it, and introduces
the opportunity for an error where we update the size of the original
path but not the size in the function.

So instead, just pass the size through to the function.
2021-04-18 18:14:03 -04:00
Misa
3102cac9d9 Add asserts for missing images and sound effects
I don't want to add too many asserts, because sometimes it's okay if a
file is missing (mmmmmm.vvv). But currently, the game basically expects
all images and sound effects to be present. That might change in the
future, but for now, these asserts are okay.
2021-04-18 15:01:43 -04:00
Misa
9f11438dcc Fix dereferencing NULL if image fails to load
If LoadImage() returned NULL, the game would dereference it and
segfault. So I've added NULL checks to dereferencing the pointers.
2021-04-18 15:01:43 -04:00
Misa
8956b04d67 Fix missing return statements in drawsprite()
Whoops. Otherwise the game would end up indexing out-of-bounds despite
checking for it anyways.
2021-04-18 15:01:43 -04:00
Misa
1191f425b3 Add NULL checks to FILESYSTEM_loadFileToMemory()
FILESYSTEM_loadFileToMemory() dereferenced pointers without checking if
they were valid... I don't know of any cases where they could have been
NULL, but better safe than sorry.
2021-04-18 15:01:43 -04:00
Misa
3ebdc1da89 Transfer param init responsibility to loadFileToMemory
So, the codebase was kind of undecided about who is responsible for
initializing the parameters passed to FILESYSTEM_loadFileToMemory() - is
it the caller? Is it FILESYSTEM_loadFileToMemory()? Sometimes callers
would initialize one variable but not the other, and it was always a
toss-up whether or not FILESYSTEM_loadFileToMemory() would end up
initializing everything in the end.

All of this is to say that the game dereferences an uninitialized
pointer if it can't load a sound effect. Which is bad. Now, I could
either fix that single case, or fix every case. Judging by the title of
this commit, you can infer that I decided to fix every case - fixing
every case means not just all cases that currently exist (which, as far
as I know, is only the sound effect one), but all cases that could exist
in the future.

So, FILESYSTEM_loadFileToMemory() is now guaranteed to initialize its
parameters even if the file fails to be loaded. This is better than
passing the responsibility to the caller anyway, because if the caller
initialized it, then that would be wasted work if the file succeeds
anyway because FILESYSTEM_loadFileToMemory() will overwrite it, and if
the file fails to load, well that's when the variables get initialized
anyway.
2021-04-18 15:01:43 -04:00
Misa
8c8118e43f Don't mix code and decls in loadFileToMemory()
My next commit will involve using goto to jump to the end of a function
to initialize the variables to NULL, but that results in a compiler
error if we have initializations in the middle of the function. We might
as well put all declarations at the top of each block anyway, to help
the move to C, so I'm doing this now.

Since the length variable in the STDIN block now overshadows the length
variable in the outer block, I've renamed the length variable in the
block to stdin_length.
2021-04-18 15:01:43 -04:00
Misa
120bb7288b Don't mix code and decls in SoundTrack constructor
Minor style fix.
2021-04-18 15:01:43 -04:00
Misa
b02cf00ce6 Remove unnecessary Sint16 casts
These casts are sprinkled all throughout the graphics code when creating
and initializing an SDL_Rect on the same line. Unfortunately, most of
these are unnecessary, and at worst are wasteful because they result in
narrowing a 4-byte integer into a 2-byte one when they don't need to
(SDL_Rects are made up of 4-byte integers).

Now, removing them reveals why they were placed there in the first place
- a warning is raised (-Wnarrowing) that implicit narrowing conversions
are prohibited in initializer lists in C++11. (Notably, if the
conversion wasn't narrowing, or implicit, or done in an initializer
list, it would be fine. This is a really specific prohibition that
doesn't apply if any of its sub-cases are true.)

We don't use C++11, but this warning can be easily vanquished by a
simple explicit cast to int (similar to the error of implicitly
converting void* to any other pointer in C++, which works just fine in
C), and we only need to do it when the warning is raised (not every
single time we make an SDL_Rect), so there we go.
2021-04-18 14:55:33 -04:00
Misa
15b085b326 Fix duplicating ed_settings when opening editor menu second time
This fixes a bug where after loading in to the level editor, pressing
Esc and then switching your option to something other than the first
option, then pressing Esc again to close the menu, then pressing Esc
once more would not keep your menu option.

This is because the code that checks if Menu::ed_settings is already in
the stack doesn't account for if ed_settings is the current menu - the
current menu doesn't get put in to the stack.

In hindsight, maybe I could have designed the new menu system better so
the current menu IS on the stack, and/or should have used a
statically-allocated linked list for each menu name for the stack frames
(instead of an std::vector) and asserted if a menu that already existed
in the stack was created instead... that'll have to be done later,
though.
2021-04-18 13:13:04 -04:00
Misa
90b48887ed Fix missing colon in prompt when placing terminal
All other instances of "Enter script name:" have a colon except for this
one.
2021-04-18 06:53:24 -04:00
Misa
c37eb37b4d Consistently play Viridian squeak for pause menu inputs
Pressing Esc to cancel the confirm quit menu didn't play the squeak, in
contrast to pressing ACTION to cancel it, so now it does; pressing Esc
to close the pause menu or pressing ACTION will also now play the
Viridian squeak too.
2021-04-18 06:52:58 -04:00
Misa
c84d7ebf08 Rename vx/vy createentity args to meta1/meta2
vx/vy mean x-velocity and y-velocity... except here, where it seems like
they're used as extra parameters that do different things depending on
the entity. But it seems like at one point they were actually meant to
be the speed of the entity (this is the case for the unused decorative
particle entities), and then just never got renamed when they weren't.

The custom levels community named these two parameters meta1 and meta2
in the reference list of entities for the createentity() script command,
so that's what I'm naming them here. This will avoid confusion (I know
that some people reading this function have genuinely mistaken the vx/vy
for actually meaning x-velocity and y-velocity, simply because they were
named that way).
2021-04-17 18:29:17 -04:00
Misa
9ba30caeb3 Remove default function args from createentity()
I have spelled out each overloaded version instead, and only the
overloads that are actually used - which just happens to be everything
except the 8-argument one. I don't want to deal with callers right now
(there are too many of them), so I'm not going to change the names that
the callers use, nor do I want to change the amount of arguments any
existing callers use right now - but we will have to deal with them in
one way or another when we move to C.
2021-04-17 18:29:17 -04:00
Misa
7d223db211 Change all createentity args to be ints
The script command createentity() is always an int. But not only that,
every time createentity() is used, its arguments are always treated like
ints. Always. I knew that vx/vy were floats because of the int casts in
the function, but I didn't even realize that xp/yp were floats, too,
until I checked just now! That's how much they're treated like ints.

All int casts in createentity() have also been removed, due to being
unnecessary (either because of us suppressing MSVC implicit conversion
warnings, or because there are now no longer any conversions happening).
2021-04-17 18:29:17 -04:00
Misa
516e71c7da Remove customlevelstatsloaded from Game
This boolean is assigned, and it is checked... but it's never assigned
to true, thus making it useless. I also checked 2.2 source and the same
thing happens there; to prevent any confusion, I'm removing this.
2021-04-17 09:53:17 -04:00
Misa
28c3ec9572 Make map.[old]ypos ints instead of floats
So... I did see that map.ypos was a float when I added over-30-FPS mode,
because map.oldypos wasn't there before... I'm guessing that I kind of
just ignored it at the time. But, c'mon, map.ypos and map.oldypos are
always treated as ints, so there's literally no reason for them to be
actually floats in reality. I didn't even know they were anything other
than ints until I checked Map.h.
2021-04-17 09:52:30 -04:00
Misa
83ad5e66de Show and hide mouse cursor based on user input
This is quite simple - whenever the user uses their keyboard or
controller, we hide the mouse cursor. Whenever they move the mouse, we
show it again. This makes it so the cursor gets out of the way when they
play the game, but reappears when they need it.

There is also a timeout, to prevent strobing if the user decides to use
the keyboard/controller and mouse at the same time. There is no timeout
from hiding the mouse cursor, but there is a timeout from showing the
mouse cursor - this because it's okay if the mouse lingers for a few
frames when you start using the keyboard, but really annoying if the
mouse doesn't instantly appear when you move it.
2021-04-16 22:58:45 -07:00
Misa
c76c67b125 Axe mouse cursor config option
The config option has been removed. I'm going to implement something
that automatically shows and hides the mouse cursor whenever
appropriate, which is better than a config option.
2021-04-16 22:00:33 -07:00
Misa
390720dacc Disable exceptions and RTTI in CMakeLists
These are two C++ features that we don't need, don't use, and will never
use in the future. Apparently the best way of doing this in CMake is to
fiddle with the CXX_FLAGS using regex.

Now this is one less flag I need to supply myself when I invoke CMake...
2021-04-17 00:50:04 -04:00
Misa
83f438d694 Use booleans for SUPPORTS_IMPLICIT_FALLTHROUGH
CMake has booleans, so why not use them?
2021-04-17 00:50:04 -04:00
Misa
2229c2c6c0 Remove usage of undefined variable PFSP_SRC
This variable is not defined anywhere and never has been since the
source code release (which is when this CMake file was first created).
To make things clearer, I'm cleaning this variable up.
2021-04-17 00:50:04 -04:00
Misa
afae945930 Don't pollute global includes/defines namespace
A function like add_definitions() adds definitions to ALL targets, not
just VVVVVV. This kind of namespace pollution is messy, and could result
in bugs if you pollute with the right kind of pollutant.

So instead of using add_definitions(), use target_compile_definitions().
And instead of using include_directories(), use
target_include_directories().
2021-04-17 00:50:04 -04:00
Misa
78aa3ca715 Switch SDL2 includes to be private
There's no reason for these to be public, and putting it public is
probably going to cause more harm than not.
2021-04-17 00:50:04 -04:00
Misa
ce4dc7e7bc Set C standard to C90 in CMake file
All the C third-party dependencies are C90, and all the C files we have
are also C90 (well, almost, but that's easily sorted). So we have
basically no reason to not go with C90 here.

The only wrinkle is, turning C extensions off for physfs-static results
in linker errors because PhysFS implicitly uses alloca() without
including it properly (on Linux). I am not the only one who has ran into
this - see https://icculus.org/pipermail/physfs/2020-April/001293.html -
and it's a bug with PhysFS. The workaround I've gone with is to enable C
extensions. (There might also be some funkiness with PhysFS's use of the
`inline` keyword, so enabling extensions will paper over that as well.)
2021-04-17 00:50:04 -04:00
Misa
c17ea40866 Use multiline comments in C files
So there were actually only two instances of C99-style end-of-line
comments in C files - and technically one of them was just a C file
including MakeAndPlay.h.
2021-04-17 00:50:04 -04:00
Misa
4c199efbac Add CMake version guard around setting C++ standard
It seems like CMake 3.1.3 introduced the C/C++ standard properties,
while the minimum version of this CMake file is 2.8.12. So we do what
FAudio does, which is print a warning if the CMake version is too old
and otherwise use it if we have the feature.
2021-04-17 00:50:04 -04:00
Misa
575698e715 Change cache bool variables into options
They're the same thing, but using option() better conveys intent.
However this can't be done for anything that isn't a bool, which the
CUSTOM_LEVEL_SUPPORT option is not (it's a tri-state string).
2021-04-17 00:50:04 -04:00
Misa
c060419a47 Remove redundant C++ standard sets from CMakeFiles
These were introduced in 098fb77611 - did
Leo not know that they were already there at the top of the file? This
does the same thing, except it only sets it for VVVVVV instead of
everything (so this wouldn't set it for the third-party dependencies).
2021-04-17 00:50:04 -04:00
Misa
c41ec551f7 Lowercase function calls in CMakeLists
Lowercase names are easier to type and are more distinguishable from the
capital letters that usually go inside the function calls as arguments.
2021-04-17 00:50:04 -04:00
Misa
d2951fb7f4 Remove extra semicolon from end of Credits namespace
-Wpedantic warns about this.
2021-04-17 00:50:04 -04:00
Misa
0dceec8c20 Add -Wpedantic to GCC/Clang in CMakeLists
This adds -Wpedantic so we get warned if our code doesn't strictly
conform to ISO C++98. It doesn't seem like MSVC has it.
2021-04-17 00:50:04 -04:00
Misa
be9a6382ea Fix playing same track after it has faded out
If a track was restarted after it faded out, then it wouldn't play. This
is because currentsong wasn't set to -1 after fading out, and that is
because the fade out calls pause() instead of haltdasmusik() when it
finishes.

Unlike f196fcd896, this fixes the time
trial music while keeping it to the same behavior as 2.2, and fixes
every single possible case that this music bug could have happened.
2021-04-14 13:02:00 -04:00
Misa
75133c74e7 Revert part of "Fix music stopping when restarting time trial"
This reverts only a part of f196fcd896 -
as the original commit author did not do their changes atomically, they
also squashed in a de-duplication within the same commit. So I'm only
reverting the part of the commit that wasn't the de-duplication, which
is simply the changes to the music.fadeout() calls.

This is being (partially) reverted for several reasons:

1. It's not the correct behavior. What this does instead is persist the
   track through after you restart the time trial, instead of fading it
   out, then restarting it again. This is in contrast to behavior in
   2.2, and I see no reason to not keep the same behavior.

2. It's a single-case patch. The time trials are not the only time in
   the game a music track could fade out and then be restarted with the
   same track - custom levels could do the same thing too. Instead of
   fixing only one case, we should strive to fix EVERY case.

The original commit author (trelbutate) also didn't write anything in
the commit description of f196fcd896. What
you should write in the commit description is things like rationale,
analysis, and other good information that would be useful to anyone
looking at your commit to understand why you did what you did. Having no
commit description leaves readers in the dark as to why you did what you
did.

Thus, I don't know why trelbutate went with this solution, or if they
knew that it was only a single-case patching, or if they knew that it
wasn't 2.2 behavior.

By not writing the commit description, they miss a chance for
reflection; speaking from personal experience, I myself have gone back
and improved my commits countless times because I wrote commit
descriptions for every single one of them, and sometimes whenever I
write them, I think to myself "hang on a minute, that doesn't sound
quite right" and end up finding improvements.

If trelbutate wrote a commit description, they might have realized that
it wasn't 2.2 behavior, and gone back and fixed up their commit to be
correct. As it stands, though, they didn't have to think about it in the
first place because they never bothered to write a commit description.
2021-04-14 13:02:00 -04:00
Misa
815d96dd4c Fix bracing of time trial startgamemode
It should be next-line brace, not same-line brace.
2021-04-14 13:02:00 -04:00
Malte Desktop
e0749801ea Add to contributors 2021-04-14 11:09:01 -04:00
Malte Desktop
f196fcd896 Fix music stopping when restarting a time trial 2021-04-14 11:09:01 -04:00
Misa
a0d40b5d74 Add bounds check to use of edteleportent
edteleportent is a global variable that gets assigned whenever the
player collides with a warp token, and gets read from later down the
line in gamelogic(). While I don't know of any way to cause anything bad
with this (and I did try), storing a temporary indexing variable like
this is only bound to be a liability in the future - so we might as well
prevent badness now by adding a bounds check here.
2021-04-14 07:15:14 -04:00
Misa
e3145c09f2 Reset cliplaytest when exiting to menu and check it when loading
This fixes a bug where quitting to the menu from command-line
playtesting with -playassets specified would always use those assets
when loading back in to any custom level. This also fixes loading in to
a custom level quicksave always using the command-line playtesting
arguments instead of using the actual quicksave.
2021-04-14 07:14:34 -04:00
Misa
174e804c6b Fix wrong variable being bounds-checked when collecting trinket
I was working on another PR, and spotted this. This is a GIANT whoops
moment right here!
2021-04-14 07:14:03 -04:00
Misa
7f55b0e887 Increase threshold for drawing top entities at bottom of screen
In a vertically-warping room, the 'height' of the room becomes 232
pixels, regardless of if you have a room name or not. So the remaining 8
rows of pixels at the bottom of the screen corresponds with the first 8
rows of pixels at the top of the screen, and entities in the bottom 8
rows of pixels get teleported to the top of the screen.

The screen wrapping drawing code doesn't draw entities in the top 8 rows
of pixels at the bottom, leading to a discontinuous effect where it
looks like vertically-warping entities don't neatly change from the
bottom to the top or vice versa - this is especially noticeable with
enemies. To fix this, just increase the threshold for drawing top
entities at the bottom of the screen by 8 pixels.
2021-04-13 22:22:03 -04:00
Misa
8b042a5813 Fix vertically-warping entities being drawn with wrong offset
When an entity vertically warps, it teleports upwards or downwards by
232 pixels. However, the graphics code draws them with an offset of 230
pixels. This is off by 2 pixels, but it's enough to make a
downwards-moving enemy look like it suddenly collides with the bottom of
the screen (in a room without a room name) before it warps, especially
if you go frame-by-frame.
2021-04-13 22:22:03 -04:00
Misa
235046f7ad Don't ditch frames when loading saves
It seems like for whatever reason that the frames portion of save files
is never read from, and always zeroed. Well, technically they get parsed
but the result is immediately discarded afterwards.

I see no reason to do this, so I'm removing these zeroes.
2021-04-13 17:51:24 -04:00
Misa
ea2ecc81b1 Don't process music, fades, or timer when unfocused
This fixes being able to make music fully fade in (or out) by unfocusing
the game, or making the fade bars fully fade in (or out) by unfocusing
the game, or racking up the timer while the game is unfocused.
2021-04-13 17:51:24 -04:00
Misa
01ba834086 Don't tick timer if in time trial countdown
In 2.2 and previous, the game would call resetgameclock() every frame
for the last 30 frames of the time trial countdown in order to make sure
it gets reset. This was in a render function, and didn't get brought out
in 2.3, so 2.3 resets the game clock *while rendering*, which is kinda
bad and is an oversight on my part for not noticing.

Instead of doing that, just add a conditional to the timer so that it
won't tick during the time trial countdown. This fixes #699 even further
by making it so the time trial par can't even be lost during the
countdown, because the timer won't tick up - so you can never get a sad
squeak to play by pausing the game or unfocus-pausing it during the
countdown.
2021-04-13 17:51:24 -04:00
Misa
2517900010 De-duplicate zeroing the game clock
For some reason, resetgameclock() is only ever used in gamerender(), and
everywhere else just zeroes the clock manually. This is weird to me, so
I've made it so everywhere that zeroes the clock uses the
resetgameclock() function to do so.
2021-04-13 17:51:24 -04:00
Misa
54c0a8a109 Don't reset timetrialparlost every frame during countdown
Otherwise, if the timer ticked up past the par (via using the unfocus
pause or pause menu), it would result in the sad squeak being played
every frame because the game would constantly be setting
timetrialparlost, then moving to the code block below, assuming that
since timetrialparlost that we haven't lost the par already, and playing
the squeak.

timetrialparlost gets reset in hardreset() and startgamemode() anyways,
so there's no need to be constantly resetting this variable.

Fixes #699.
2021-04-13 17:51:24 -04:00
Misa
3728edc335 Remove time trial replay menu stack popping
It turns out this entire chunk of code is simply unneeded (and is
actively harmful) since when we're done with the time trial,
quittomenu() gets called, and that removes the previous stack frame
anyway.

I'm guessing that I added this code, then added quittomenu(), then
didn't consider how this code and quittomenu() would mix. But anyways,
this bug is fixed.

Fixes #714.
2021-04-13 13:41:48 -04:00
Misa
a4999c5308 Fix music files not using virtual mount path
This fixes music files being unable to be mounted in per-level custom
assets, because they didn't use the virtual mount path. But now they do.
2021-04-13 10:02:40 -04:00
Misa
3f46c5ac5c Abstract binary blob loading to FileSystemUtils
This seems to be a comment left by Ethan that he never got around to. So
I did it for him.

What I've done is made it so FileSystemUtils.cpp knows what a binary
blob is, and moved the binary blob loading code directly to
FileSystemUtils.cpp. To do this, I removed the private access modifier
from binaryBlob - I don't think we'll need it, and anyways when we move
to C we can't use it.

Along the way, I also cleaned up the style of the function a bit - the
null termination offset is no longer hardcoded, and the function no
longer mixes code and declarations together in the same block.

I also noticed that when printing all the filenames at the end, a single
invalid header would stop the whole loop instead of just being skipped
over... this seems to be a bug to me, so I've made it so invalid headers
just get skipped over instead of stopping the whole loop.

In FileSystemUtils.h, I used a forward declaration. In hindsight,
incomplete forward declarations should basically always be done in
header files if possible, otherwise this introduces the possibility of
transitive includes - if a file includes this header and it does a full
include, the file is silently able to use the full header, whereas if
it's a forward declaration, then the moment the file tries to use the
full header it fails, and then it's forced to include the full header
for itself. But uh, that's a code cleanup for later.
2021-04-13 10:02:40 -04:00
Misa
a64f75a880 Fix isAssetMounted not using virtual mount path
Otherwise, it would report that tiles.png wasn't mounted when it was,
and recolor one-way tiles in levels that don't want their one-ways
recolored.
2021-04-13 10:02:40 -04:00
Misa
9b4691676f Factor out mount path calculation to separate function
This means we don't have to copy-paste the virtual mount path
calculation code.
2021-04-13 10:02:40 -04:00
Misa
488916b51c Process queued music after processing fades
While fixing all the other music bugs, I discovered that starting
playtesting in the editor wouldn't play the level music.

The problem is that the editor playtesting start code calls
music.fadeout() before calling music.play(). This queues up the track
from the music.play() call. After that, what should happen is that
processmusic() processes the fade, the fade is then finished, and then
after that it sees that the music is halted so it can play the queued
track.

Instead what happens is that the function first attempts to play the
music before the fade is processed and finished, so play() will re-queue
the music again, but the queue gets cleared right after that (this is a
subtle bit of behavior - it means if the game fails to play a queued
track due to it fading, it's not going to re-queue it again and end up
in some sort of infinite loop).

This is a frame ordering issue - the function is tripping over itself
when it shouldn't be. To fix it, just put the queue processing code
after the fade processing code.
2021-04-12 16:17:31 -04:00
Misa
0bde6f1eca Reset fade booleans when halting music
This fixes the 2.2-and-below music blocking workaround not working in
2.3.

The issue was that when the music got halted by the script, the fade
volume would still be processing, silently being decremented in the
background. So the script playing the track afterwards would make the
game queue it (as it was called during the fade), but then the music is
halted so the game would attempt to play it, but the fade is STILL
happening so it wouldn't actually play it and would attempt to queue the
track again.

However, that queue gets discarded immediately afterwards because the
music.play() call happened inside the code responsible for playing the
queued music, and that code unconditionally clears the queue variables
immediately after calling play(). So that's good to know - if the game
queues a song, but fails to play it because of a fade, it's not going to
immediately re-queue it and potentially get stuck in a loop of
infinitely queueing the same song over and over again each frame.

Anyways, the source of the problem is not resetting the fade booleans
when halting music, so I've reset them.

Fixes #701.
2021-04-12 16:17:31 -04:00
Misa
af2e6a2331 Fix 1-frame glitch when fading in from zero
The problem here is that even though we start playing the music when the
volume is set to zero, mixer's state doesn't have volume zero, so
whatever it plays next will be the very first quanta of the track but at
the previous volume (in this case, the maximum volume). To fix this,
just update mixer when we update the volume here - it's okay to not
account for user volume because it ends up being zero anyway.

Fixes #710.
2021-04-12 16:17:31 -04:00
Misa
27d0b1a1d4 Ensure all fade-ins start from zero
This fixes a bug where fading music in but not going through the
music.play() path wouldn't start the fade volume from zero. If this
happened, then the previous volume would persist, and if the previous
volume was the max volume, then that essentially canceled out the
fade-in and prevented it from happening at all. But now all paths to
fadeMusicVolumeIn() set the volume to zero first, instead of only the
caller of music.play().
2021-04-12 16:17:31 -04:00
Misa
f6919981e5 Silence music instead of halting in foundtrinket
When you pick up a trinket in the wild, the music gets silenced, so it
silently plays in the background until you advance the trinket text.
However, foundtrinket (used when Victoria or Vitellary give you a
trinket) is inconsistent with this, and halts the music instead of
silencing it.

This was probably due to the musicfadein script command not being
implemented, so Terry or Simon had to simply make do and halt the music
instead. But musicfadein is implemented and is being used in the trinket
cutscenes, so this is another inconsistency that I will fix.
2021-04-12 16:17:31 -04:00
Misa
d965a11988 Don't restart music in trinket cutscenes
When you pick up a trinket in the wild, the music will fade back in
afterwards. However, the special trinket cutscenes (where Victoria or
Vitellary will directly give you a trinket) are inconsistent with this,
and restart the music instead of fading it back in.

Looking at the scripts themselves, it immediately becomes obvious the
reason for this inconsistency - 2.2 and previous didn't implement the
musicfadein command, so it couldn't be used, and Terry or Simon simply
had to make do with simply restarting the music. However, 2.3 implements
musicfadein, so we can simply swap it out and remove the
trinketscriptmusic command.
2021-04-12 16:17:31 -04:00
Misa
711b36c9c8 Set currentsong to -1 when halting music
This is 2.2 behavior, which I forgot to keep. Otherwise, if music has
halted and you try to play the same track, it simply won't work, because
the current song is the same as the song you're trying to play. This is
what happened with the trinket scripts - the game halted music, then
tried to play the same track.

Fixes #712.
2021-04-12 16:17:31 -04:00
Dav999-v
dbd9a114b6 Change mode in Windows-only mkdir to 0777
It's not really used because CreateDirectory doesn't support setting
chmod values, but it does clarify intent of the argument.

Co-authored-by: Ethan Lee <flibitijibibo@gmail.com>
2021-04-12 13:25:27 -04:00
Dav999-v
274ae98915 Re-fix handling filepaths with non-ASCII characters on Windows
In #52 I fixed VVVVVV not being able to handle filepaths with non-ASCII
characters on Windows. 2f0a0bce4c and
aa5c2d9dc2 reintroduce this problem,
however, by reverting the definition of mkdir to how it was before the
fix and using the non-Unicode version of CreateDirectory. And I can
confirm that VVVVVV indeed doesn't make its folder anymore with a
Windows username of "тест". This commit fixes that issue.
2021-04-12 13:25:27 -04:00
Misa
e8316c7e9a Implement music and sound volume sliders
This adds music and volume sliders to the audio options. To use the
sliders, you navigate to the given option, then press ACTION, and your
selection will be transferred to the slider. Pressing left or right will
move the slider accordingly. Then you can press ACTION to confirm the
volume is what you want and deselect it, or you can press Esc to cancel
the volume change, and it will revert to the previous volume; both
actions will write your settings to disk.

Most of this commit is just adding infrastructure to support having
sliders in menus (without copy-pasting code), which is a totally
completely new user interface that has never been used before in this
game. If we're going to be adding something new, I want to make sure
that it at least is done the RIGHT way.

Closes #706.
2021-04-11 20:56:16 -04:00
Misa
27874e1dc6 Add music and sound volume config options
This adds <musicvolume> and <soundvolume> tags to unlock.vvv and
settings.vvv, so users' volume preferences will be persistent across
game sessions. This does not add the user interface to change them from
in-game; the next commit will do that.
2021-04-11 20:56:16 -04:00
Misa
2a3f17f1f7 Add audio category to options menu
The audio category contains the MMMMMM soundtrack option, as well as
stubs for the soon-to-be-implemented volume slider options.
2021-04-11 20:56:16 -04:00
Misa
5133d58777 Add VVV_fillstring()
This function is simple - it takes a given buffer and its size, fills it
with a certain character, and null-terminates it. It's meant to be used
with freshly-created buffers, so we don't copy-paste code.
2021-04-11 20:56:16 -04:00
Misa
eb9d3582d8 Fix incorrect return code from game options and graphic options
Pressing return in gameplay options would send you back to the pause
menu instead of the general options menu, and pressing return in graphic
options would send you back to the pause menu instead of the general
options menu, too. Additionally, pressing Esc in graphic options would
also send you back to the pause menu instead of the general options
menu.

Like I said before, the menu system is still a bit hardcoded in some
places, and these happened because Terry forgot to update them when he
changed the menus around.

Fixes #711.
2021-04-11 16:58:10 -04:00
Misa
d89ce76577 Fix returning from confirm quit menu putting cursor on options
The in-game menu code is better than it was in 2.2 but still pretty
hardcoded, so to fix this just change each individual case around. This
bug happened because the "options" button was in the place where "quit
to menu" was previously, but Terry forgot to update it when changing all
the options around.
2021-04-11 16:58:10 -04:00
Ethan Lee
aa5c2d9dc2 Move first CreateDirectory to getOSDirectory, avoids redundancy on non-Win32 2021-04-11 11:55:35 -04:00
Ethan Lee
2e630ce06c Forgot a 0... 2021-04-11 11:44:56 -04:00
Ethan Lee
2f0a0bce4c Revert to my old userdata folder creation code.
PhysFS requires a write dir to create a directory, so the first PHYSFS_mkdir
never could have worked. Because of that we need to go back to the old mkdir,
and since we're bringing that back we can reuse it for saves/levels, because we
know it works and we don't have to worry about middlewares ruining anything.
2021-04-11 11:32:29 -04:00
Ethan Lee
aa97a5d6a1 Revert "Only migrate savedata if "saves" was created", need this for levels too
This reverts commit fcb09f85cb.
2021-04-11 11:26:11 -04:00
Ethan Lee
fcb09f85cb
Only migrate savedata if "saves" was created 2021-04-11 11:19:49 -04:00
Misa
bc0b9e1fa0 Fix softlocks from turning off advancetext at wrong time
When a text box in the script system (not the gamestate system) is
displayed onscreen and "- Press ACTION to advance text -" is up, the
game sets pausescript to true, so the script system won't blare past the
text box and keep executing. Then it also sets advancetext to true.
Crucially, these two variables are different, so if you have pausescript
true but advancetext false, then what happens?

Well, you get softlocked. There's no way to continue the script.

How is this possible? Well, you can teleport to the (0,0) teleporter
(the teleporter in the very top-left of the map) and regain control
during the teleporter animation. To do that, in 2.2 and below, you have
to press R at the same time you press Enter on the teleporter, or in 2.3
you can simply press R during the cutscene. Then once you teleport to
the room, it's really precise and a bit difficult (especially if
Viridian is invisible), but you can quickly walk over to the terminal in
that room and press Enter on it.

Then what will happen is the terminal script will run, but the
teleporter gamestate sequence will finish and turn advancetext off in
the middle of it. And then you're softlocked.

To fix this, just add a check so if we're in gamestate 0 and there's a
script running, but we have pausescript on and advancetext off, just
turn pausescript off so the game automatically advances the script.

This softlock was reported by Tzann on the VVVVVV speedrunning Discord.
2021-04-10 21:47:23 -04:00
Misa
fe8df3001e Reset completestop in hardreset
If you manage to get softlocked by being stuck in completestop, the next
thing you'll notice is that quitting to the menu or loading back in will
not reset this.

So you can actually softlock yourself in 2.3 by doing the trinket
cutscene, then quitting to the menu (because 2.3 lets you open the pause
menu during completestop). This is a bug, and should be fixed.
2021-04-10 21:47:23 -04:00
Misa
dd4100f752 Fix softlocks from mistimed trinket text skip
You can skip the "You have found a shiny trinket!" cutscene. The
conditions are that this can only be done in the main game, in the main
dimension (no Polar Dimension), the checkpoint that you last touched
must not be in the same room as the trinket, and you have to have
skipped the Comms Relay cutscene. To do the skip, you press R on the
exact frame (or previous frame, if input delay is enabled) that Viridian
touches the trinket. Then, the gamestate will be immediately set to 0
(because of the gotoroom) and the cutscene will be skipped.

Speedrunners of the main game, well, run the main game already, the
only trinket in the Polar Dimension is not one you want to do a death
warp at, and they have a habit of automatically skipping over the Comms
Relay cutscene because they press R at the beginning of the run when
Viridian teleports to Welcome Aboard, to warp back to the Ship and so
they can leave rescuing Violet for later.

So someone reported softlocking themselves by doing the trinket text
skip in 2.3. The softlock is because they're stuck in a state where
completestop is true but can't advance to a state that turns it off. How
does this happen? It's because they pressed R too late and interrupted
the gamestate sequence. In 2.2 and previous, if you're in the gamestate
sequence then you can't press R at all, but 2.3 removes this restriction
(on account of aiming to prevent softlocks). So only on the very first
frame can you death warp and interrupt the gamestate sequence before it
happens at all.

Anyways to fix this, just turn completestop off automatically if we're
in gamestate 0 and there's no script running.

This softlock was reported by Euni on the VVVVVV speedrunning Discord.
2021-04-10 21:47:23 -04:00
Vittorio Romeo
082a0c2205 Fix '-Wpedantic' warnings under gcc 10.x 2021-04-10 20:53:01 -04:00
viri
6e1054e362 CONTRIBUTORS.txt: Update contact info 2021-04-09 22:39:33 -04:00
Misa
f96f5703ff Fix level list segfault when upgrading old levelstats.vvv
So some people reported the levels list crashing when they loaded it.
But this wasn't reproducible every time. They didn't provide any
debugging information, so I had to use my backup plan: doing a full
audit of the code path taken for loading the levels list.

And then I found this. It turns out this was because I used a
LOAD_ARRAY_RENAME() macro on an std::vector. You can't do that because
you need to use push_back() to resize a vector, so the macro will end up
indexing into nothing, causing a segfault. However, this code path would
only be taken if you have an old levelstats.vvv, from 2.2 and previous -
which explains why it wasn't 100% reproducible. But now that I know you
need an old levelstats.vvv, this bug happens 100% of the time.

Anyways, to fix this, just ditch the macro and expand it manually, while
replacing the indexing with a proper usage of push_back().
2021-04-09 22:22:05 -04:00
Misa
0f0e218cf6 Use macros to calculate accessibility offset
This means the offset is calculated at compile-time, and we can use a
proper case-switch here instead of an else-if chain.
2021-04-09 17:42:58 -04:00
Misa
b5f1cbb2d1 Clean up unused vars/funcs from eee98b0e07
eee98b0e07 made some functions and
variables unused, which generate compiler warnings. So I've removed
them.
2021-04-09 17:42:58 -04:00
Misa
8e4b904f57 Fix whitespace from eee98b0e07
eee98b0e07 introduced mixed indentation
and one trailing whitespace, which I've cleaned up.
2021-04-09 17:42:58 -04:00
Misa
9e560e31cf Blacklist non-.vvvvvv files in level metadata
While the game does support playing levels with filenames that don't
have the .vvvvvv extension, it doesn't do it well.

Namely, those files can't be loaded or saved into the editor (because a
.vvvvvv always gets tacked on to your input when saving or loading). In
2.3, this gets worse because you can't load a level without a .vvvvvv
extension from command-line playtesting (because a .vvvvvv automatically
gets added) and you can't load per-level custom assets.

The only place where extensionless level files are supported is when
loading level metadata. But this makes it so they no longer work. This
is technically an API break, but it's easily fixed (just add the
.vvvvvv), plus there's nothing to be gained from not having an
extension, plus basically no one ever actually did this in the first
place (as far as I know, I am the only person to have ever done this,
and no one else ever has).
2021-04-09 11:26:59 -04:00
TerryCavanagh
8b1efb4335 Default to 60 FPS mode (fixes #702) 2021-04-09 20:40:22 +10:30
TerryCavanagh
eee98b0e07 Clean up of options menus for v2.3 (fixes #696) 2021-04-09 20:39:12 +10:30
TerryCavanagh
d9d5cbbab2 Changed the wording on "Invincibility" and "Game Speed" accessibility options
I've wanted to do this one for a very long time
2021-04-09 17:53:15 +10:30
Misa
300f1b7919 Switch assets mounting to dedicated directory
This fixes an issue where you would be able to mount things other than
custom assets in per-level custom asset directories and zips.

To be fair, the effects of this issue were fairly limited - about the
only thing I could do with it was to override a user-made quicksave of a
custom level with one of my own. However, since the quicksave check
happens before assets are mounted, if the user didn't have an existing
quicksave then they wouldn't be able load my quicksave. Furthermore,
mounting things like settings.vvv simply doesn't work because assets
only get mounted when the level gets loaded, but the game only reads
from settings.vvv on startup.

Still, this is an issue, and just because it only has one effect doesn't
mean we should single-case patch that one effect only. So what can we
do?

I was thinking that we should (1) mount custom assets in a dedicated
directory, and then from there (2) mount each specific asset directly -
namely, mount the graphics/ and sounds/ folders, and mount the
vvvvvvmusic.vvv and mmmmmm.vvv files. For (1), assets are now mounted at
a (non-existent) location named .vvv-mnt/assets/. However, (2) doesn't
fully work due to how PhysFS works.

What DOES work is being able to mount the graphics/ and sounds/ folders,
but only if the custom assets directory is a directory. And, you
actually have to use the real directory where those graphics/ and
sounds/ folders are located, and not the mounted directory, because
PHYSFS_mount() only accepts real directories. (In which case why bother
mounting the directory in the first place if we have to use real
directories anyway?) So already this seems like having different
directory and zip mounting paths, which I don't want...

I tried to unify the directory and zip paths and get around the real
directory limitation. So for mounting each individual asset (i.e.
graphics/, sounds/, but especially vvvvvvmusic.vvv and mmmmmm.vvv), I
tried doing PHYSFS_openRead() followed by PHYSFS_mountHandle() with that
PHYSFS_File, but this simply doesn't work, because PHYSFS_mountHandle()
will always create a PHYSFS_Io object, and pass it to a PhysFS internal
helper function named openDirectory() which will only attempt to treat
it as a directory if the PHYSFS_Io* passed is NULL. Since
PHYSFS_mountHandle() always passes a non-NULL PHYSFS_Io*,
openDirectory() will always treat it like a zip file and never as a
directory - in contrast, PHYSFS_mount() will always pass a NULL
PHYSFS_Io* to openDirectory(), so PHYSFS_mount() is the only function
that works for mounting directories.

(And even if this did work, having to keep the file open (because of the
PHYSFS_openRead()) results in the user being unable to touch the file on
Windows until it gets closed, which I also don't want.)

As for zip files, PHYSFS_mount() works just fine on them, but then we
run into the issue of accessing the individual assets inside it. As
covered above, PHYSFS_mount() only accepts real directories, so we can't
use it to access the assets inside, but then if we do the
PHYSFS_openRead() and PHYSFS_mountHandle() approach,
PHYSFS_mountHandle() will treat the assets inside as zip files instead
of just mounting them normally!

So in short, PhysFS only seems to be able to mount directories and zip
files, and not any loose individual files (like vvvvvvmusic.vvv and
mmmmmm.vvv). Furthermore, directories inside directories works, but
directories inside zip files doesn't (only zip files inside zip files
work).

It seems like our asset paths don't really work well with PhysFS's
design. Currently, graphics/, sounds/, vvvvvvmusic.vvv, and mmmmmm.vvv
all live at the root directory of the VVVVVV folder. But what would work
better is if all of those items were organized into a subfolder, for
example, a folder named assets/. So the previous assets mounting system
before this patch would just have mounted assets/ and be done with it,
and there would be no risk of mounting extraneous files that could do
bad things. However, due to our unorganized asset paths, the previous
system has to mount assets at the root of the VVVVVV folder, which
invites the possibility of those extraneous bad files being mounted.

Well, we can't change the asset paths now, that would be a pretty big
API break (maybe it should be a 2.4 thing). So what can we do?

What I've done is, after mounting the assets at .vvv-mnt/assets/, when
the game loads an asset, it checks if there's an override available
inside .vvv-mnt/assets/, and if so, the game will load that asset
instead of the regular one. This is basically reimplementing what PhysFS
SHOULD be able to do for us, but can't. This fixes the issue of being
able to mount a quicksave for a custom level inside its asset directory.

I should also note, the unorganized asset paths issue also means that
for .zip files (which contain the level file), the level file itself is
also technically mounted at .vvv-mnt/assets/. This is harmless (because
when we load a level file, we never load it as an asset) but it's still
a bit ugly. Changing the asset paths now seems more and more like a good
thing to do...
2021-04-05 16:39:37 -04:00
Misa
798bf9e490 Add filename to mountAssetsFrom() error message
This will clarify which directory, exactly, failed to mount. I know it
gets printed earlier in the mounting process, but it can't hurt to print
it twice, just to be sure. Also this is for consistency.
2021-04-05 16:39:37 -04:00
Misa
ff3cba9cee Replace asset load calls with loadAssetToMemory()
All assets now use FILESYSTEM_loadAssetToMemory() instead of
FILESYSTEM_loadFileToMemory().
2021-04-05 16:39:37 -04:00
Misa
72ae6921ea Add FILESYSTEM_loadAssetToMemory()
This is currently just a wrapper around FILESYSTEM_loadFileToMemory(),
but assets are supposed to use this function instead of the regular one.
2021-04-05 16:39:37 -04:00
Misa
aea5611e5b Remove default argument from loadFileToMemory()
Default function arguments are the devil, and it's better to be more
explicit about what you're passing into the function. Also because we
might become C-only in the future and to help faciliate that, we should
get rid of C++-isms like default function arguments now.
2021-04-05 16:39:37 -04:00
Misa
a11920e1a9 Remove redundant getDirSeparator() calls in init()
PHYSFS_getDirSeparator() already gets called and stored in pathSep at
the top of FILESYSTEM_init(). So clearly, two people worked on this
function and forgot that both pieces of code existed at the same time
(or it was one person independently forgetting both).
2021-04-05 16:39:37 -04:00
Misa
34ec943b5c Remove getDirSeparator() usage from mountAssetsFrom()
PhysFS uses platform-independent notation, so we really don't need to
care about getting the correct dir separator here. Especially since we
don't ever do so anywhere else (e.g. load/saveTiXml2Document()), either.
2021-04-05 16:39:37 -04:00
Misa
d95ba3a8b3 Rename FILESYSTEM_mount() to FILESYSTEM_mountAssetsFrom()
This is to make it clear that this is not a general-purpose mounting
function; it is a helper function for FILESYSTEM_mountAssets()
specifically for treating a directory or file as an assets directory,
and mounting assets from there.
2021-04-05 16:39:37 -04:00
Misa
43692388c0 Use FILESYSTEM_mount() when mounting zips
There's no reason to handle mounting .zip files differently than
mounting a directory... we already mount .data.zip files using
FILESYSTEM_mount(), so why go through the trouble of opening a .zip
manually (which means on Windows the .zip can't be touched for the
duration of playing the custom level), making up a place to mount it at,
and then mount that made-up name, instead of just using
FILESYSTEM_mount()?

Whoever cobbled this asset mounting thing together really didn't fully
understand what they were doing.
2021-04-05 16:39:37 -04:00
Misa
a8a09a207f Properly camel-case FILESYSTEM_[un]mountassets()
They are now camel-cased to be consistent with the rest of the
filesystem functions.
2021-04-05 16:39:37 -04:00
Misa
9c8ecdb0f4 Return early if FILESYSTEM_mountassets() fails
This way, we avoid the unnecessary graphics.reloadresources() call - if
we can't mount assets, why bother reloading resources?

The return type of FILESYSTEM_mount() has been changed from void to bool
to indicate success, accomodating its callers accordingly.
2021-04-05 16:39:37 -04:00
Misa
1e375f9ecf Un-export FILESYSTEM_mount()
This function is never used outside of FileSystemUtils.cpp; there is no
reason to export it.
2021-04-05 16:39:37 -04:00
Ethan Lee
5060b4dfe3 Only do focus fullscreen toggling on X11.
I haven't been able to reproduce this old thing on any setup I have. The patch
from 2013 was originally for X11, and Wayland's fullscreen doesn't allow for
this sort of thing, so let's start scoping this down for eventual removal when
X11 is finally out of our minds forever.
2021-04-05 11:07:32 -04:00
Misa
510ec07021 Re-fix resumemusic/musicfadein once again
So it looks like facb079b35 (PR #316) had
a few issues.

The SDL performance counter doesn't really work that well. Testing
reveals that unfocusing and focusing the game again results in
the resumemusic() script command resuming the track at the wrong time.
Even when not unfocusing the game at all, stopping a track and resuming
it resumes it at the wrong time. (Only disabling the unfocus pause fixes
this.)

Furthermore, there's also the fact that the SDL performance counter
keeps incrementing when the game is paused under GDB. So... yeah.

Instead of dealing with the SDL performance counter, I'm just going to
pause and resume the music directly (so the stopmusic() script command
just pauses the music instead). As a result, we no longer can keep
constantly calling Mix_PauseMusic() or Mix_ResumeMusic() when focused or
unfocused, so I've moved those calls to happen directly when the
relevant SDL events are received (the constant calls were originally in
VCE, and whoever added them (I'm pretty sure it was Leo) was not the
sharpest tool in the shed...).

And we are going to switch over to using our own fade system instead of
the SDL mixer fade system. In fact, we were already using our own fade
system for fadeins after collecting a trinket or a custom level
crewmate, but we were still using the mixer system for the rest. This is
an inconsistency that I am glad to correct, so we're also doing our own
fadeouts now.

There is, however, an issue with the fade system where the length it
goes for is inaccurate, because it's based on a volume-per-frame second
calculation that gets truncated. But that's an issue to fix later - at
least what I'm doing right now makes resumemusic() and musicfadein()
work better than before.
2021-04-02 16:13:54 -04:00
Misa
6d3a73c540 Add pause(), pauseef(), and resumeef() to musicclass
musicclass already had a resume() function for music.

These are just wrappers around the appropriate SDL_mixer functions, to
avoid direct function calls to the mixer API. So if we ever need to do
something with all callers of pausing and resuming in the future, or we
switch to a different audio backend, the work is already done for us.

Also it just looks cleaner to be calling our musicclass function instead
of doing a direct API call to the mixer.
2021-04-02 16:13:54 -04:00
Misa
92b3c0b413 Factor fade amount calculation to separate function
This makes it so to reuse this code, we don't have to copy-paste it.

Additionally, I added a check for the milliseconds being 0, to avoid a
division by zero. Logically and mathematically, if the fade amount is 0
milliseconds, then that means the fade should happen instantly -
however, dividing by zero is undefined (both in math and in C/C++), so
this check needs to be added.
2021-04-02 16:13:54 -04:00
Misa
f6ea05f521 Factor timestep calculation out to Game function
This is so we can grab the game's timestep anywhere else in the codebase
without copy-pasting it.
2021-04-02 16:13:54 -04:00
Misa
f8f6f3b96e Add option to re-enable 1-frame input delay
This is an option for speedrunners whose muscle memory is precisely
trained and used to the 1-frame input delay that existed in 2.2 and
below. It is located in Game Options -> Advanced Options, and is off by
default.

To re-add the 1-frame input delay, we simply move the key.Poll() to the
start of the frame, instead of before an input function gets ran -
undoing what #535 did.

There is a frame ordering-sensitive issue here, where toggling
game.inputdelay at the wrong time could cause double-polling. However,
we only toggle it in an input function, which regardless is always
guaranteed to be ran after key.Poll() (it either happened at the start
of the frame or just before the input function got ran), so this is not
an issue. But, in case we ever need to toggle this variable in the
future, we can just use the defer callbacks system to defer the toggle
to the end of the frame - also added by #535.

Added at the request of Habeechee on the VVVVVV speedrunning Discord
server.
2021-04-02 11:18:32 -04:00
Misa
4b3409e2e8 Remove advancetext check from teleporter prompt logic
This fixes being unable to use teleporters while the "- Press ACTION to
advance text -" prompt is up, which is used to perform credits warp.

In 2.2 and 2.0, this advancetext check was only in gamerender() for
rendering the "- Press ENTER to Teleport -" prompt and didn't affect any
logic. In 2.3, I moved the check (and the rest of the conditional it was
in) to gamelogic() - same as the activity zone prompt conditionals - so
if you gained control while being in a prompt zone, the prompt wouldn't
suddenly appear[1].

As a side effect, this ended up aligning rendering and logic together,
so if you couldn't see the teleporter prompt, you weren't able to
teleport - whereas in 2.2 and 2.0, you could still use the teleporter
even though the prompt wasn't up.

So by removing the advancetext check, you are now able to use the
teleporter again, AND the "- Press ENTER to Teleport -" prompt will also
show up as well.

Habeechee reported this regression on the VVVVVV speedrunning Discord
server.

[1]: f07a8d2143, PR #421
2021-04-02 11:16:42 -04:00
Misa
cd2f5ccde9 Add period to glitchrunner mode description text
This is to be consistent with the other options in the Advanced Options
menu.
2021-04-02 11:15:53 -04:00
Misa
91bc438d79 Fix funcs not being reassigned if delta func is last func in list
One of the solutions to the quit signal unfocus pause regression is to
add a no-op delta func to the unfocused func table. However, this
results in the game being stuck in unfocus pause forever, because when
it reaches the end of a list on a delta func, it won't reassign the
active functions - only when the end of a list is a fixed func will it
do so. A workaround is to then add a no-op fixed func afterwards, but
that's inelegant.

The solution in the end to the quit signal regression is to not bother
with adding a delta func, so the game as of right now actually never has
a delta func at the end of a list, and probably never will - but this is
one piece of technical debt I don't want to leave laying around. In case
we're ever going to put a delta function at the end of a list, I've made
it so that delta functions will now reassign the list of active funcs if
they happen to be at the end of the func list.
2021-04-02 11:15:18 -04:00
Misa
03a0b1feb2 Call VVV_exit() when SDL_QUIT is received
This fixes a regression introduced by #535 where a quit signal (e.g.
Ctrl-C) sent to the window while the game was in unfocus pause wouldn't
close the game.

One problem was that key.quitProgram would only be checked when control
flow switched back to the outer loop in main(), which would only happen
when the loop order state machine switched to a delta function. As the
unfocused func table didn't have any delta functions, this means
key.quitProgram would never be checked.

So a naïve solution to this would just be to add a no-op delta func
entry to the unfocused func table. However, we then run into a separate
issue where a delta function at the end of a func list never reassigns
the active funcs, causing the game to be stuck in the unfocus pause
forever. Active func reassignment only happens after fixed funcs. So
then a naïve solution after that would be to simply add a no-op fixed
func entry after that. And indeed, that would fix the whole issue.

However, I want to do things the right way. And this does not seem like
the right way. Even putting aside the separate last-func-being-delta
issue, it mandates that every func list needs a delta function. Which
seems quite unnecessary to me.

Another solution I considered was copy-pasting the key.quitProgram check
to the inner loops, or adding some sort of signal propagation to
the inner loops - implemented by copy-pasting checks after each loop -
so we didn't need to copy-paste key.quitProgram... but that seems really
messy, too.

So, I realized that we could throw away key.quitProgram, and simply call
VVV_exit() directly when we receive an SDL_QUIT event. This fixes the
issue, this removes an unnecessary middleman variable, and it's pretty
cleanly and simply the right thing to do.
2021-04-02 11:14:24 -04:00
Terry Cavanagh
4ebbfe476a
Merge pull request #588 from InfoTeddy/general-bug-fixes
Fix tile of disappearing platforms during final stretch
2021-04-02 17:51:30 +10:30
Terry Cavanagh
886a57ab83
Merge pull request #667 from InfoTeddy/general-improvements
Add text outlines to textboxless textboxes and gravitron text
2021-04-02 17:49:20 +10:30
Misa
ef091de23e Outline all gravitron text
This includes all text from the Gravitron and Super Gravitron.

This is to make the text more readable if they are placed in weird
situations - for example, in custom levels, where the background these
texts get placed on could be anything (custom level makers are crazy!).
2021-03-30 23:57:00 -07:00
Misa
6538d1e5dd Add Graphics::bigrprint()
Same as bigbprint(), we duplicate some of the calculations because it's
better than duplicating another text printing function.
2021-03-30 23:57:00 -07:00
Misa
f7173027ce Add Graphics::bigbprint()
It's just like bigprint() except it duplicates some of the calculations
because I didn't want to make a bigprintoff() function which would
duplicate even more code. I'm beginning to think these text printing
functions are completely horrible to work with...
2021-03-30 23:57:00 -07:00
Misa
827b3e430b Outline textboxless textboxes
In case they get drawn against a non-contrasting background, it's still
useful to keep them readable by outlining them. This could happen if
someone were to use the Game Complete gamestate sequence in a custom
level (or presses R during Game Complete).
2021-03-30 23:57:00 -07:00
Misa
0c72260c5d Fix oversight with unfocus pause screen in Flip Mode
Flip Mode flips all the unfocus pause screen text upside-down, to make
it read in reverse order. This looks kind of strange to me, and I don't
think it was intended. So I'm flipping the text again so it's the right
way up in Flip Mode.
2021-03-30 23:55:42 -07:00
Misa
f9e76d9dc0 Fix tile of disappearing platforms during final stretch
During the final stretch, after Viridian turns off the Dimensional
Stability Generator, the map goes all psychedelic and changes colors
every 40 frames. Entities change their colors too, including conveyors,
moving platforms, and disappearing platforms.

But play around with the disappearing platforms for a bit and you'll
notice they seem a bit glitchy. If you run on them at the right time,
the tile they use while disappearing seems to abruptly change whenever
the color of the room changes. If there's a color change while they're
reappearing (when you die and respawn in the same room as them), they'll
have the wrong tile and look like a conveyor. And even if you've never
interacted with them at all, dying and respawning in the same room as
them will change their tile to something wrong and also look like a
conveyor.

So, what's the problem? Well, first off, the tile of every untouched
disappearing platform changing into a conveyor after you die and respawn
in the same room is caused by a block of code in gamelogic() that gets
run on each entity whenever you die. This block of code is the exact
same block of code that gets ran on a disappearing platform if it's in
the middle of disappearing.

As a quick primer, every entity in the game has a state, which is just a
number. You can view each entity's state in
entityclass::updateentities().

State 0 of disappearing platforms is doing nothing, and they start with
an onentity of 1, which means they turn to state 1 when they get
touched. State 1 moves to state 2. State 2 does some decrementing, then
moves to state 3 and sets the onentity to 4. State 3 also does nothing.
After being touched, state 4 makes the platform reappear and move to
state 5, but state 5 does the actual reappearing; state 5 then sets the
state back to 0 and onentity back to 1.

So, back to the copy-pasted block of code. The block of code was
originally intended to fast-forward disappearing platforms if they were
in the middle of disappearing, so the player respawn code would properly
respawn the disappearing platform, instead of leaving it disappeared.
What it does is keep updating the entity, while the state of the entity
is 2, until it is no longer in state 2, then sets it to state 4.

Crucially, the original block of code only ran if the disappearing
platform was in state 2. But the other block of code, which was
copy-pasted with slight modifications, runs on ALL disappearing
platforms in final stretch, regardless of if they are in state 2 or not.

Thus, all untouched platforms will be set to state 4, and state 4 will
do the animation of the platform reappearing, which is invalid given
that the platform never disappeared in the first place. So that's why
dying and respawning in the same room as some disappearing platforms
during final stretch will change their tiles to be conveyors.

It seems to me that doing anything with death is wrong, here. The root
cause is that map.changefinalcol() "resets" the tile of every
disappearing platform, which is a function that gets called on every
color change. The color change has nothing to do with dying, so why
fiddle with the death code?

Thus, I've deleted that entire block of code.

What I've done to fix the issue is to make it so the tile of
disappearing platforms aren't manually controlled. You see, unlike other
entities in the game, the tile of disappearing platforms gets manually
modified whenever it disappears or reappears. Other entities use the
tile as a base and store their tile offset in the separate walkingframe
attribute, which will be added to the tile attribute to produce the
drawframe, which is the final thing that gets rendered - but for
disappearing platforms, their tile gets directly incremented or
decremented whenever they disappear or reappear, so when
map.changefinalcol() gets ran to update the tile of every platform and
conveyor, it basically discards the tile offset that was manually added
in.

Instead, what I've done is make it so disappearing platforms now use
walkingframe, and thus their final drawframe will be their tile plus
their walkingframe. Whenever map.changefinalcol() gets called, it is now
free to modify the tile of disappearing platforms accordingly - after
all, the tile offset is now stored in walkingframe, so no weird
glitchiness can happen there.
2021-03-30 23:55:34 -07:00
Misa
a405635cb2 Replace other usage of PHYSFS_getBaseDir() with SDL_GetBasePath()
Ethan, you forgot this other one.

I do have to rejiggle the control flow of the function a bit, so it
doesn't leak memory upon failure. (Although the SDL message box leaks
memory anyway because of X11 so... whatever.) Also, there's a NULL check
for if SDL_GetBasePath() fails now.
2021-03-31 02:51:52 -04:00
Ethan Lee
051fe9eba9 Skip the icon on Apple targets, it also has the icns 2021-03-31 02:50:38 -04:00
Ethan Lee
b99abaf1d5 Put Misa at the top of GitHub Friends 2021-03-31 02:43:15 -04:00
Ethan Lee
4c4f8de0d5 Fix leaking GetBasePath result 2021-03-31 02:43:15 -04:00
Misa
8cf79aaf72 Fix memory leak when pasting text
According to SDL documentation[1], the returned pointer needs to be
freed. A glance at the source code confirms that the function allocates,
and also Valgrind complains about it.

Also if it couldn't allocate, the game no longer segfaults (std::strings
do not check if the pointer is non-NULL for operator+=).

[1]: https://wiki.libsdl.org/SDL_GetClipboardText
2021-03-31 02:29:36 -04:00
Ethan Lee
9d2ebbc982
Re-sync CONTRIBUTORS and GitHub Friends lists 2021-03-30 23:08:25 -04:00
Ethan Lee
b87f6e487a Use SDL_GetBashPath instead of PHYSFS_getBaseDir, latter is made of fail on macOS 2021-03-30 12:18:09 -04:00
Ethan Lee
076a870ba2 VS2010 buildfix 2021-03-30 11:51:42 -04:00
Misa
367e77fb59 Fix targets (question marks) not showing up on minimap
Followup to #635: I had misread the original '==0' comparisons as being
truthy comparisons instead of being falsy comparisons. Whoops.
2021-03-26 00:02:26 -04:00
Misa
892e7c93fc Fix background not changing when pressing Esc on main menu
Followup to #664: Pressing "quit game" instead of using Esc changes the
background, but pressing Esc doesn't. Whoops.
2021-03-25 23:32:59 -04:00
Misa
100d986431 Remove mainmenu from Game
Since mainmenu is only ever used in Input.cpp, I might as well make it
clearer by moving it into a static global variable in Input.cpp. (The
same applies to fadetolab/fadetomenu, but I didn't think much about
those at the time... that'll be a refactor for later.)
2021-03-25 23:32:39 -04:00
Misa
be10487c5c Set fademode to temp 0 when going to in-game options
While I've decoupled fademode from gamemode starting, being faded out on
the title screen results in a black screen and you being unable to make
any input. So we'll need to store the current fademode in a temporary
variable when going to in-game options, then put it back when we return
to the pause menu. Yes, you can turn on glitchrunner mode during the
in-game options, and then immediately return to the pause menu to
instantly go back to the title screen; this is intended.

Due to frame ordering, putting the fademode back needs to be deferred to
the end of the frame to prevent a 1-frame flicker.

It's actually sufficient enough to do this temporary fademode storage to
fix the whole thing, but I also decided to decouple fademode and
gamemode starting just to be sure.
2021-03-25 23:32:39 -04:00
Misa
82dfa0b86c Decouple fademode from starting gamemode
Assuming glitchrunner mode is off, if you open the pause menu while
fully faded-out and then go to Graphic Options or Game Options, then the
'mode' that you selected previously will kick in again and you'll be
suddenly warped back.

So if you previously started a new game in the main game (mode 0, also
the selected mode if you do this from command-line playtesting), and
then open the pause menu and go to in-game options, then you'll suddenly
go back to starting a new game again. If you had started a custom level,
doing this will warp you back to the start of the level again.

The problem is simple - when the title screen is fully faded out, it
calls startgamemode(). So the solution is simple as well - just decouple
the fademode from calling startgamemode(), and use a different variable
to know when to actually call startgamemode().
2021-03-25 23:32:39 -04:00
Misa
cd0c9ccb31 De-duplicate setting mainmenu and fademode
This makes my work easier later.
2021-03-25 23:32:39 -04:00
Misa
6a6c09f69d Fix 1-frame flicker entering room with warp lines and entity on edge
Custom levels can have warp lines. If you have a warp line and a warping
background in the same room, the warp line takes precedence over the
warp background.

However, whenever you enter a room with a warp line and warp background,
any entities on the warping edges will be drawn with screenwrapping for
one frame, even though they never wrapped at all.

This is due to frame ordering: when the warp line gets created,
obj.customwarpmode gets set to true. Then when the screen edges and
warping logic gets ran, the very first thing that gets checked is this
exact variable, and map.warpx/map.warpy get set appropriately - so
there's no way the entity could legitimately screenwrap.

However, that happens in gamelogic(). gamelogic() is also the one
responsible for creating entities upon room load, but that happens after
the obj.customwarpmode check - so when the game gets around to rendering
in gamerender(), it sees that map.warpx or map.warpy is on, and draws
the screenwrapping, even though map.warpx/map.warpy aren't really on at
all. Only when gamelogic() is called in the frame later do map.warpx and
map.warpy finally get set to false.

To fix this, just set map.warpx and map.warpy to false when creating
warp lines.
2021-03-25 22:29:40 -04:00
Misa
8fd7210d37 De-duplicate warp line creation in createentity()
Copy-pasted code begone. This makes it much clearer what the difference
between all the warp line entities in this function are.
2021-03-25 22:29:40 -04:00
Misa
c8f4c37c88 Add bounds check for color of createentity number 55
I just spotted this one - if vy isn't bounds-checked, this causes bogus
input from the createentity() script command to commit Undefined
Behavior. Should've spotted this one when I was adding bounds checks to
the rest of createentity() earlier, but at least it's fixed now.
2021-03-25 21:43:51 -04:00
Misa
423c79b572 Add bounds checks to room explored getter and setter
This means you can no longer cause Undefined Behavior by exploring a
room that is outside the array of explored room statuses.
2021-03-24 15:55:34 -04:00
Misa
c5e999c1d5 Refactor explored rooms to use setters and getters
This makes it easier to add bounds checks to all accesses of
map.explored. Also, all manually-written existing bounds checks have
been removed, because they're going to go into the new getters and
setters.

The getter is mapclass::isexplored() and the setter is
mapclass::setexplored().
2021-03-24 15:55:34 -04:00
Misa
b340a6ccc4 Add bounds checks to room propety getters and setters
It is no longer possible to cause Undefined Behavior via accessing
out-of-bounds room properties.

What happens instead is - if you attempt to fetch an out-of-bounds room
property, you get a "blank" room property that just has all of the
defaults, plus its tileset is 1 because all tilesets that are nonzero
use tiles2.png, and it closely emulates the previous behavior where it
was some bogus value but definitely not zero. Its Direct Mode is also 1,
because the tiles contained within it are just mishmashed repeats of
existing tiles on the map, and we shouldn't autotile that.

The roomname also gets cleared in case the user attempts to set the room
name of an out-of-bounds room property.

If you attempt to set the property of an out-of-bounds room property,
then nothing happens.
2021-03-24 15:55:34 -04:00
Misa
945d5f244a Refactor room properties to use setter and getter funcs
This replaces all raw ed.level accesses with new setter and getter
funcs, which makes it easier to add bounds checks later. And I've also
removed all the manually-written bounds checks, since they will go into
the new getter and setter.

To get the room properties of a specific room, you use
editorclass::getroomprop(), which returns a pointer to the room
properties - then you just read off of that pointer. To set a room
property, you use editorclass::setroom<PROP>(), where <PROP> is the name
of the property. These are maintained using X macros to avoid
copy-pasting. editorclass::getroompropidx() is a helper function and
shouldn't be used directly.
2021-03-24 15:55:34 -04:00
Misa
cfd5be1bc5 Add bounds checks to tile setter and getters
This removes all traces of Undefined Behavior from getting and placing
tiles.

This mimics the previous behavior (2.2 and below) as reasonably as
possible. `vmult` was previously a vector, there was a bunch of unused
space directly after the end of the usable space of the vector, which
was all filled with zeroes. The same goes for `contents`, having
previously been a vector, and so having a bunch of zeroes immediately
following the end of the in-bounds space. That's why both are 0 if you
index them out of bounds.
2021-03-24 15:55:34 -04:00
Misa
344c93e754 Refactor tiles to use setter and getter functions
This makes it easier to add bounds checks to all accesses of
ed.contents.

To do this, I've added editorclass::gettile(), editorclass::settile(),
and editorclass::getabstile() (with a helper function of
editorclass::gettileidx() that really shouldn't be used directly), and
replaced all raw accesses of ed.contents with those functions
appropriately.

This also makes the code more readable, as a side effect.
2021-03-24 15:55:34 -04:00
Misa
ccdb0c9148 Fix bounds checks in drawentity()
The existing bounds checks were correct sometimes but other times were
not.

The bounds check for 2x2 and 2x1 sprites only covered the top-left
sprite drawn; the other sprites could still be out of bounds. But if the
top-left sprite was out of bounds, then none of the other sprites
wouldn't be drawn - although it ought to be that the other sprites still
get attempted to be drawn. So I've updated the bounds checks
accordingly, and now an out of bounds top-left sprite won't prevent the
drawing of the rest of the sprites.

Similarly, if the sprite of a Gravitron square was out of bounds, that
would prevent its indicators from being drawn. But the indicators
weren't being bounds-checked either (2.3 lets you have less than 1200
tiles in a given tilesheet). So the bounds check has been moved to only
cover the drawframe and the indicator indexes accordingly, and an out of
bounds sprite won't prevent attempting to draw the indicators.
2021-03-24 15:42:28 -04:00
Misa
4e52dccdae Initialize temporary edentity when loading levels
It is possible for any of the QueryIntAttribute()s to fail, most
commonly if the attributes don't exist. If that happens, then that part
of the temporary edentity won't be initialized, and we'll end up having
a partially-uninitialized edentity - then doing much of anything with it
will result in undefined behavior.

To fix this, just initialize the temporary edentity.
2021-03-24 15:26:09 -04:00
Misa
69b0f0b650 Add missing pText NULL checks
If an XML tag doesn't contain anything inside, pText will be NULL. If
that happens without being checked, then NULL will be passed to
SDL_strcmp(). SDL_strcmp() will either call libc strcmp() or use its own
implementation; both implementations will still dereference the NULL
without checking it.

This is undefined behavior, so I'm fixing it. The solution is to do what
is done with all other XML parsing functions, and to make sure pText
gets set to a safe empty string (which is just a pointer to a null
terminator) if it happens to be NULL.
2021-03-24 15:26:09 -04:00
Misa
0da9b5069a Capitalize "OFF" when invincibility is off
All other settings capitalize "ON" and "OFF", so this one should, too.
2021-03-21 20:53:40 -04:00
Misa
9ab61af1da Add period to text outline description
This is to be consistent with all other option descriptions, which all
end in a period as well.
2021-03-21 20:53:40 -04:00
Misa
d45e6f6254 Remove game.gametimer in favor of game.frames
PR #279 added game.gametimer solely for the editor ghosts feature. It
seems that whoever originally wrote it (Leo for the now-dead VVVVVV:
Community Edition, I believe) forgot that the game already had its own
timer, that they could use.

The game timer does increment on unfocus pause (whereas this doesn't),
but that's a separate issue, and it ought to not do that.
2021-03-21 20:53:11 -04:00
Misa
17169320b4 Fix text box deltaframe flashing on deltaframes after fully opaque
So #434 didn't end up solving the deltaframe flashing fully, only
reduced the chances that it could happen.

I've had the Level Complete image flash a few times when the Game Saved
text box pops up. This seems to be because the Level Complete image is
based off of the text box being at y-position 12, and the Game Saved
text box is also at y-position 12. Level Complete only gets drawn if the
text box additionally has a red channel value of 165, and the Game Saved
text box has a red channel value of 174. However, there is a check that
the text box be fully opaque first before drawing special images. So
what went wrong?

Well, after thinking about it for a while, I realized that even though
there is indeed an opaqueness check, the alpha of the text box updates
BEFORE it gets drawn. And during the deltaframes immediately after it
gets updated, the text box is considered fully opaque. It's completely
possible for the linear interpolation to end up with a red channel value
of 165 during these deltaframes, while the text box is opaque as well.

As always, it helps if you have a high refresh rate, and run the game
under 40% slowdown.

Anyways, so what's the final fix for this issue? Well, use the text box
'target' RGB values instead - its tr/tg/tb attributes instead of its
r/g/b attributes. They are not subject to interpolation and so are
completely reliable. The opaqueness check should still be kept, though,
because the target values don't account for opaqueness. And this way, we
get no more deltaframe flashes during text box fades.

An even better fix would be to not use magic RGB values to draw special
images... but that'd be something to do later.
2021-03-21 19:01:36 -04:00
Ethan Lee
415d4790e2
Fix a crash on first unfocus 2021-03-21 17:19:08 -04:00
Ethan Lee
7abd4bb8d8
Visual Studio buildfix 2021-03-21 17:15:36 -04:00
Misa
4a79f02842 Add braces around sub-object in unfocused_func_list
Clang warns on this. This doesn't fix anything but it does ensure that
whoever's reading it won't be focused as to whether or not omitting the
second set of braces is legal or not.
2021-03-21 17:03:17 -04:00
Misa
02560ca6e5 Remove now-unneeded kludge for finalmode entity colors
Previously, with the wrong loop order, this kludge needed to exist so
entities in finalmode didn't have wrong colors for 1 frame when entering
a room. But now the loop order has been fixed, and so this kludge is no
longer needed.
2021-03-21 02:55:42 -04:00
Misa
287061c768 Fix filter/screenshake/flash update order
In 2.2, at render time, the game rendered screenshakes and flashes if
their timers were above 0, and then decremented them afterwards. The
game would also update the analogue filter right before rendering it,
too.

In 2.3, this was changed so the flash and screenshake timers were
unified, and also done at the end of the frame - right before rendering
happened. This resulted in 1-frame flashes and screenshakes not
rendering at all. The other changes in this patchset don't fix this
either. The analogue filter was also in the wrong order, but that is
less of an issue than flashes and screenshakes.

So, what I've done is made the flash and screenshake timers update right
before the loop switches over to rendering, and only decrements them
when we switch back to fixed functions (after rendering). The analogue
filter is also updated right before rendering as well. This restores
1-frame flashes and screenshakes, as well as restores the correct order
of analogue filter updates.
2021-03-21 02:55:42 -04:00
Misa
094209bd12 Move logical onground/onroof updates to start of gamelogic
This reintroduces 2-frame edge-flipping after the 1-frame input delay
got removed. This is because along with processing input and moving
Viridian, logical onground/onroof assignments need to processed in the
same between-render sequence as well - otherwise Viridian only gets 1
frame of edge-flipping due to frame ordering.
2021-03-21 02:55:42 -04:00
Misa
63a60b11cc Split onground/onroof into visual and logical variables
I will need to separate these into two different variables because I
will need to move logical onground/onroof assignments to the start of
gamelogic() - if I kept them together, however, that would change the
visuals of onground/onroof, which I want to keep consistent with 2.2.
2021-03-21 02:55:42 -04:00
Misa
d5d9d9ba96 Re-remove 1-frame input delay
To do this, GAMEMODE input needs to be processed, and Viridian needs to
be moved, in the same sequence between render frames. So just move
gameinput to after gamerender. Yes, this is not 2.2 order, but gameinput
only handles player input and nothing else - plus a 1-frame input delay
feels really awful to play with in over-30-mode.
2021-03-21 02:55:42 -04:00
Misa
f1f434accc Move key.Poll() calls to just before input funcs
In order to re-remove the 1-frame input delay, we will have to poll
input right after rendering a frame - in other words, just before an
input function gets called.

To do this, I've added a new function enum type - Func_input - that is
the same as a fixed function, but before its function gets called,
key.Poll() gets called. And all input functions have been updated to use
this enum accordingly.
2021-03-21 02:55:42 -04:00
Misa
ab26985fde Re-fix crewmate directions (without copy-pasting)
This once again fixes the facing directions of crewmates upon room load,
except now it covers more cases.

So, here is the saga so far:

- 2.0 (presumably) to 2.2: crewmate direction fix is special-cased at
  the end of mapclass::loadlevel(). Only covers crewmates created during
  the room load, does not cover crewmates created from scripts, only
  covers state 18 of crewmates.

- 2.3 currently (after #220): crewmate direction fix is moved to
  entityclass::createentity(), which covers every avenue of crewmate
  creation (including from scripts), but still only covers state 18.

- This commit: crewmate direction fix now covers every possible state of
  the crewmate, also does not copy-paste any code.

What I've done instead is to make it so createentity() will immediately
call updateentities() on the pushed-back entity. This is kludge-y, but
is completely okay to do, because unlike other entities, crewmate
entities never change their state or have any side-effects from
double-evaluation, meaning calling updateentities() on them is
idempotent and it's okay to call their updateentities() more than once.

This does have the slight danger that if the states of crewmates were to
change in the future to no longer be idempotent, this would end up
resulting in a somewhat hard-to-track-down double-evaluation bug, but
it's worth taking that risk.

This fix is not applied to entity 14 (the supercrewmate) because it is
possible that calling updateentities() on it will immediately remove the
entity, which is not idempotent (it's changing the state of something
outside the object). Supercrewmates are a bit difficult to work with
outside of the main game anyways, and if you spawn them you could
probably just use the changedir() script command to fix their direction,
so I'm not inclined to fix this for them anyway.
2021-03-21 02:55:42 -04:00
Misa
cb5d181ce8 Remove entityclass::createentity() deltaframe kludge
This copy-pasted code only existed because the previous loop order was
incorrect and rendered entities before they would get properly updated
by the fixed render function. Now, the fixed render function is
guaranteed to be called before the render function, so we can rely on
that to update the drawframe and realcol of entities instead of
duplicating the code ourselves in createentity().

The drawframe assignment is still kept to fix the case where dying while
completestop is active (i.e. during a trinket or crewmate rescue
cutscene) and respawning in a different room won't turn everything into
Viridian sprites.
2021-03-21 02:55:42 -04:00
Misa
2d9d0cffa5 Fix 1-frame glitch when going to in-game options from edsettings
The menu creation of Graphic Options or Game Options, as well as the
map.nexttowercolour() call, all need to be deferred until the end of the
frame.
2021-03-21 02:55:42 -04:00
Misa
951679b1f8 Fix 1-frame background glitch when returning from in-game options
The background would change for 1 frame before sending you back to the
pause menu or editor settings. The map.nexttowercolour() call needs to
be deferred until the end of the frame.
2021-03-21 02:55:42 -04:00
Misa
5088ff40e9 Fix 1-frame text glitch when returning to editor settings from options
The returnmenu() needs to be deferred until the end of the frame.
2021-03-21 02:55:42 -04:00
Misa
8a3e292041 Fix 1-frame text glitch returning to pause menu from in-game options
The new loop order introduces a glitch where the menu would display
whichever menu was saved to kludge_ingametemp for 1 frame right as the
user returned to the pause menu. This happened because the
game.returntomenu() happens in titleinput(), which comes before
titlerender(). To fix this, we just need to defer it to the end of the
frame.
2021-03-21 02:55:42 -04:00
Misa
3da0e31215 Remove game.shouldreturntoeditor in favor of using defer callback
game.shouldreturntoeditor was added to fix a frame ordering issue that
was causing a bug where if you started playtesting in a room with a
horizontal/vertical warp background, and exited playtesting in a
different room that also had a horizontal/vertical warp background and
which was different, then the background of the room you exited in would
slowly scroll offscreen, when you re-entered the editor, instead of the
background consisting entirely of the actual background of the room.

Namely, the issue was that the game would render one more frame of
GAMEMODE after graphics.backgrounddrawn got set to false, and re-set it
to true, thus negating the background redraw, so the editor background
would be incorrect.

With defer callbacks, we can now just use a couple lines of code,
instead of having to add an extra kludge variable and putting handling
for it all over the code.
2021-03-21 02:55:42 -04:00
Misa
32be2fcd81 Update player lerpoldxp/yp in moveplayer()
Just like gotoposition(), the player would otherwise appear to "zip"
after the command got run. This did not happen in the previous loop
order.
2021-03-21 02:55:42 -04:00
Misa
c8537beac1 Add deferred callbacks to game loop
Sometimes, there needs to be code that gets ran at the end of the game
loop, otherwise rendering issues might occur. Currently, we do this by
special-casing each deferred routine (e.g. shouldreturntoeditor), but it
would be better if we could generalize this deference instead.

Deferred callbacks can be added using the DEFER_CALLBACK macro. It takes
in one argument, which is the name of a function, and that function must
be a void function that takes in no arguments. Also, due to annoying C++
quirks, void functions taking no arguments cannot be attributes of
objects (because they have an implicit `this` parameter), so it's
recommended to create each callback separately before using the
DEFER_CALLBACK macro.
2021-03-21 02:55:42 -04:00
Misa
c8958de537 Update player lerpoldxp/yp in gotoposition()
Otherwise, the player would appear to "zip" during the deltaframes
between their previous position and their new position. This did not
happen in the previous game loop order and only happens in the new one.
2021-03-21 02:55:42 -04:00
Misa
af70076088 Remove now-unneeded deltaframe fix when going to in-game settings
Previously, before the game loop order got fixed, going to the in-game
settings would switch over to the new render function too early, causing
a deltaframe glitch that had to be fixed. But now, the render function
only gets switched when the current gamestate's function list gets
finished executing, so the game won't suddenly switch to titlerender()
in the middle of the ACTION press to the in-game settings screen.

As a consequence, titleupdatetextcol() no longer needs to be exported to
Input.cpp.
2021-03-21 02:55:42 -04:00
Misa
c26b701f5b Remove gravity line kludge from Graphics::drawgravityline()
Now that the game loop order is now fixed, there is no longer any need
for this kludge.
2021-03-21 02:55:42 -04:00
Misa
5e2fc6f0fe Move updating lerpoldxp/yp to start of gamelogic()
The previous location of this loop was placed there because it happened
just after the end of the render function. Now that the loop order is
fixed, the first thing that happens after the render function is the
start of gamelogic(), so this loop should go there now, else entity
positions won't be interpolated.

Also it now preincrements instead of postincrements because I like
preincrements.
2021-03-21 02:55:42 -04:00
Misa
585ae47d78 Remove script.dontrunnextframe kludge
Now that the game loop order is fixed, this kludge (on top of kludge) is
no longer needed, and can be safely removed.
2021-03-21 02:55:42 -04:00
Misa
c82c2afbbd Unindent unfocused_run() and focused_begin() from previous commit
As always, indentation changes are applied in a separate commit to
minimize diff noise.
2021-03-21 02:55:42 -04:00
Misa
1e9fb6aac0 Generalize game loop order and fix it to what it was in 2.2
Okay, so the reason why all render functions were moved to the end of
the frame in #220 is because it's simpler to call two fixed functions
and then a delta function instead of one fixed function, then a delta
function, and then another fixed function.

This is because fixed functions need special handling inside
deltaloop(), and you can't simply duplicate this handling after calling
a delta function. Oh, and to make matters worse, it's not always
fixed-delta-fixed, sometimes (like in MAPMODE and TELEPORTERMODE) it's
delta-fixed-fixed, so we'd need to handle that somehow too.

The solution here is to generalize the game loop and factor out each
function, instead of hardcoding it. Instead of having hardcoded
case-switches directly in the loop, I made a function that returns an
array of functions for a given gamestate, along with the number of
functions, then the game loop processes it accordingly. In fixedloop(),
it iterates over the array and executes each function until it reaches a
delta function, at which point it stops. And when it reaches the end of
the array, it goes back to the start of the array.

But anyway, if it gets to a delta function, it'll stop the loop and
finish fixedloop(). Then deltaloop() will call the delta function. And
then on the next frame, the function index will be incremented again, so
fixedloop() will call the fixed functions again.

Actually, the previous game loop was actually made up of one big loop,
with a gamestate function loop nested inside it, flanked with code that
ran at the start and end of the "big loop". This would be easy to handle
with one loop (just include the beginning and end functions with the
gamestate functions in the array), except that the gamestate functions
could suddenly be swapped out with unfocused functions (the ones that
run when you unfocus the window) at any time (well, on frame boundaries,
since key.isActive only got checked once, guarding the entire "inner
loop" - and I made sure that changing key.isActive wouldn't immediately
apply, just like the previous game loop order) - so I had to add yet
another layer of indirection, where the gamestate functions could
immediately be swapped out with the unfocused functions (while still
running the beginning and end code, because that was how the previous
loop order worked, after all).

This also fixes a regression that the game loop that #220 introduced
had, where if the fixed functions switched the gamestate, the game would
prematurely start rendering the gamestate function of the new gamestate
in the deltaframes, which was a source of some deltaframe glitches. But
fixing this is likely to just as well cause deltaframe glitches, so it'd
be better to fix this along with fixing the loop order, and only have
one round of QA to do in the end, instead of doing one round after each
change separately.

Fixes #464... but this isn't the end of the patchset. There are bugs
that need to be fixed, and kludges that need to be reverted.
2021-03-21 02:55:42 -04:00
Misa
5e440ac48d Remove special text box checks for y-position 180
Y-position 180 would be the position of the Level Complete and Game
Complete special text boxes in Flip Mode. However, since the y-position
of flipme text boxes actually no longer change (because we have to
accomodate changing Flip Mode on-the-fly), these text boxes will never
actually be y-position 180 - so we should remove these checks for
clarity.
2021-03-21 02:53:25 -04:00
Misa
596696dcf3 Make foundtrinket() Flip Mode-aware
A-ha! I've spotted an inconsistency! The normal trinket collection text
boxes (gamestate 1000-1003) is aware of Flip Mode, and will position
themselves accordingly to read the correct way in Flip Mode. However,
foundtrinket() doesn't do this.

Well, now it does.
2021-03-21 02:53:25 -04:00
Misa
db9ee0d8e3 Switch flipme script command to use flipme textbox attribute
This is why the text box attribute was named flipme, after all.

You may have noticed that the flipme command inverts textflipme instead
of simply setting it to true. Well, that's because it should be the same
as the previous behavior, which was essentially to invert it instead of
setting it to true - i.e. calling flipme twice would keep the original
text box position in Flip Mode, which means it would be upside-down
(this is a lot of flipping to keep track of...) - because flipme added
to texty in-place instead of simply assigning to it. (It did the
calculation incorrectly in 2.2 and previous, but I digress.)

Similarly, textflipme is not reset in hardreset(), because none of the
other script text box variables are reset either.
2021-03-21 02:53:25 -04:00
Misa
6939563e6d Switch all flippable text boxes to use createtextboxflipme
This ensures that if the player decides to toggle Flip Mode while one of
these text boxes is up, they won't be oriented improperly. Additionally,
it also de-duplicates a bunch of Flip Mode check code, which is also a
win.
2021-03-21 02:53:25 -04:00
Misa
c7cc2f4adc Add createtextboxreal() and createtextboxflipme()
createtextboxreal() is the same as createtextbox(), but with a flipme
parameter added to create text boxes that have their flipme attribute
set to true. createtextbox() just calls createtextboxreal() with flipme
set to false, and createtextboxflipme() just calls createtextboxreal()
with flipme set to true; this is because I do not want to use C++
function overloading.
2021-03-21 02:53:25 -04:00
Misa
1a9f2d9342 Add flipme attribute to textboxclass
Instead of calculating the y-position of the text box when it's created,
we will store a flag that says whether or not the text box should be
flipped in Flip Mode (and thus stay right-side-up), and when it comes
time to draw the text box, we will check Flip Mode and calculate the
position then.
2021-03-21 02:53:25 -04:00
Misa
2ac13815e4 Remove textrect attribute from textboxclass
Instead of duplicating the same variables over and over again,
Graphics::drawgui() can just make its own SDL_Rect. It's not that hard.

As far as I can tell, textrect was always being properly kept up to date
by the time Graphics::drawgui() got around to rendering
(textboxclass::resize() keeps being called a LOT), so this shouldn't be
a noticeable change from the user perspective.
2021-03-21 02:53:25 -04:00
Misa
334302c800 Remove unused x/y textboxclass attributes
These unused variables distract from properly analyzing the code when
you read it, since the xp/yp attributes of textboxclass already exist,
too.
2021-03-21 02:53:25 -04:00
Misa
30719b87db De-duplicate "Game Saved" telesave textbox
The "Game Saved" text box, along with its associated telesave() call,
exists in both Game.cpp and Script.cpp, so one of them is the copy-paste
of the other. Unfortunately this copy-paste resulted in an inconsistency
where both of them don't check for the same things when deciding whether
or not the telesave should actually happen (this is why you don't
copy-paste, kids... it's scary!).

Either way, de-duplicating this now is less work for me later.
2021-03-21 02:53:25 -04:00
Misa
5de884f584 De-duplicate Level Complete sequence textboxes
Every Level Complete sequence is the same copy-pasted thing, but with
minor changes. To make my work easier, I'm de-duplicating them so I have
less text boxes to change later, and less grind to grind.
2021-03-21 02:53:25 -04:00
Misa
b7ca408076 Remove default arguments from createtextbox()
These default arguments are never used anywhere. And if they were used
anywhere, it'd be better to explicitly say 255,255,255 than make readers
have to look at the header file to see what these default to. Also, this
creates four different overloads of createtextbox(), instead of only
two - but we ought to not be using function overloading anyway.
2021-03-21 02:53:25 -04:00
Misa
d5ed49d8dc Remove commented-out code from Game.cpp and Script.cpp
These commented-out code blocks just get in the way of clarity when I'm
refactoring flipped textboxes created in the gamestate system. So I'm
getting rid of them. If we need them back, we always have Git history.
2021-03-21 02:53:25 -04:00
Misa
5f2b9409b2 De-duplicate Gravitron initial message
Since the only difference is the y-positions, I've decided to remove the
copy-pasted code. A better solution would be to have a function that
draws multiline text and handles it accordingly in Flip Mode, but that
could be done later.
2021-03-21 02:06:39 -04:00
Misa
c7e807541c De-duplicate Flip Mode textbox crewmate rendering
The only difference between Flip Mode and normal mode is the y-position
and sprite used to draw the crewmates. Everything else is the same, so
I've removed the copy-pasted portion.

The diff might look a bit ugly due to the unindentation.
2021-03-21 02:06:39 -04:00
Misa
52a7d42672 De-duplicate Flip Mode text printing
Since the only difference in Flip Mode is the positiveness/negativeness
of the iterator variable, plus the starting y-offset, I've removed the
copy-pasted code and did this instead.

The diff might look a bit ugly due to the unindentation.
2021-03-21 02:06:39 -04:00
Misa
f6ecf83190 Ensure oldfadeamount is updated when fadeamount is
Like cutscene bars, I've added Graphics::setfade(), to ensure that no
deltaframe rendering glitches happen due to oldfadeamount not being
updated properly.

And indeed, this fixes a deltaframe rendering glitch that happens if you
return to the editor from playtesting on a faded-out screen, then fade
out again (by either re-entering playtesting and then cause a fadeout to
happen again, or by quitting from the editor afterwards). The same
glitch also happens outside of in-editor playtesting if you exit to the
menu while the screen is faded out.
2021-03-21 01:06:29 -04:00
Misa
f22756dd99 Ensure oldcutscenebars is updated when cutscenebarspos is
To do this, I've added Graphics::setbars(), to make sure
oldcutscenebarspos always gets assigned when cutscenebarspos is. This
fixes potential deltaframe rendering issues if these two mismatch.
2021-03-21 01:06:29 -04:00
Misa
9e2716b253 Fix a few missing implicit void arg declarations
While working on #535, I noticed that editormenuactionpress() still
didn't do the explicit void declaration. Then I ran `rg 'void.*\(\)'`
and found three other functions that I somehow missed in #628. Whoops.
Well, now they no longer are missed.
2021-03-19 10:27:54 -04:00
Misa
2c8d338e47 Add graphic options and game options to editor settings
This is a small quality-of-life tweak that makes it so if you're in the
middle of editing a level, you don't have to save the level, exit to the
menu, change whatever setting you wanted, re-enter the editor, and type
in the level name, just to change one setting. This is the same as
adding Graphic Options and Game Options to the in-game pause menu,
except for the editor, too.

To do this, I'm reusing Game::returntopausemenu() (because all of its
callers are the same callers for returning to editor settings) and
renamed it to returntoingame(), then added a variable named
ingame_editormode to Game. When we're in the options menus but still in
the editor, BOTH ingame_titlemode and ingame_editormode will be true.
2021-03-18 23:01:00 -04:00
Misa
fc8c7d034d Add being able to press Esc to return to previous menu
This is a small quality-of-life thing that makes it so you don't have to
move your menu selection all the way over to the "return" button in
order to return to the previous menu. You can just press Escape instead
to return to the previous menu. The previous behavior of pressing Escape
was to bring up the 'confirm quit' menu, or if you were in an options
menu in-game, return to the pause menu.

If you're on the main menu (and thus don't have any previous menu) and
press Escape, the game will instead bring up the 'confirm quit' menu.
For consistency, the "quit game" option on the main menu will also bring
up the 'confirm quit' menu as well, instead of immediately closing the
game.

Pressing the controller button mapped to Escape will also work as well.

The only menus that don't have return buttons are the 'countdown' menus
- so the game will not let you press Escape if there's a menu countdown
happening.

Now that pressing Escape in the 'continue' menu will just bring you back
to the 'play' menu, there's no need to specifically put
map.nexttowercolour() first when canceling the 'confirm quit' menu.
2021-03-18 20:38:23 -04:00
Misa
61e5b819e4 Fix VVVVVV-Man not being interpolated
This is because it directly uses the xp and yp of the player instead of
the interpolated xp and yp. Whoops.
2021-03-18 18:00:45 -04:00
Misa
e70586b154 Inline cutscene bars timer for gamemodes that used it in 2.2
As part of my work in #535, I've noticed that 2.3 currently with 2.2
loop order doesn't have interpolated cutscene bars. This is because
cutscene bars in 2.3 get updated at the start of the frame, which
interpolates them correctly until the render functions are put in their
proper place.

There is, however, a somewhat bigger issue, outside the scope of #535,
where cutscene bars always get updated regardless of which gamemode you
are in. Previously in 2.2 and previous, cutscene bars only got updated
in GAMEMODE and TELEPORTERMODE; sometime during 2.3, the cutscene bars
timer got pulled out of all the individual game modes and moved to the
very start of the loop. (I was probably the one who did this change;
I've been caught in a trap of my own devising.)

Thus, going to MAPMODE during the cutscene bars animation doesn't keep
their position paused like it would in 2.2. This is also categorically a
more-than-visual change, since the untilbars() script command depends
on the cutscene bars timer. I see no reason for the cutscene bars to
behave differently in this way than 2.2; #535 would also end up doing
the same fix more-or-less anyway.

Since TELEPORTERMODE currently uses the same renderfixed function as
MAPMODE, I've had to add a teleporterrenderfixed() that just calls
maprenderfixed(), but also does the cutscene bars timer.
2021-03-17 11:03:14 -04:00
Misa
4e12c162d4 Add SDL2 version number to desktop_version/ README
As a partial fix for #618, adding the SDL2 version number to the README
will clarify that you need a specific version of SDL2 in order to
compile (and run) the current version of the game (2.3 at the time of
writing); in the future, the SDL2 dependency will be upgraded with each
SDL release.

This is to avoid error messages that complain about missing symbols like
SDL_zeroa() (added in SDL 2.0.14) not being present at the time of
compilation.

Closes #626.
2021-03-17 10:58:13 -04:00
Misa
2608db9151 Directly toggle fullscreen if keybind pressed in key.Poll()
This moves the responsibility of toggling fullscreen when any of the
three toggle fullscreen keybinds are pressed (F11, Alt+Enter, Alt+F)
directly into key.Poll() itself, and not its caller (which is main() -
more specifically, fixedloop()). Furthermore, the fullscreen toggle
itself has been moved to a separate function that key.Poll() just calls,
to prevent cluttering key.Poll() with more business logic (the function
is already quite big enough as it is).

As part of my work in re-removing the 1-frame input delay in #535, I'm
moving the callsite of key.Poll() around, and I don't want to have to
lug this block of code around with it. I'd rather refactor it upfront
than touch any more lines than necessary in that PR.
2021-03-17 03:01:19 -04:00
Misa
babd86916c Move resumesong assignment to songend()
This fixes a bug where the resumemusic() script command would always
play MMMMMM track 15 (or, if you're using PPPPPP, just not work). This
is because musicclass::haltdasmusik() assigns resumesong AFTER calling
Mix_HaltMusic(), but the songend() callback fires before the resumesong
assignment, meaning resumesong gets set to -1 instead of whatever
currentsong was previously.

To fix this, just move the assignment into the callback itself (I don't
know why this wasn't done before). I could have moved it to before the
Mix_HaltMusic() call, but moving it into the callback itself fixes it
for all cases of the music stopping (such as when the music fades out).
2021-03-10 09:45:20 -05:00