16 months agoSwitch assets mounting to dedicated directory master
Misa [Mon, 5 Apr 2021 18:32:10 +0000 (11:32 -0700)]
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

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

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...

16 months agoAdd filename to mountAssetsFrom() error message
Misa [Mon, 5 Apr 2021 08:07:43 +0000 (01:07 -0700)]
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.

16 months agoReplace asset load calls with loadAssetToMemory()
Misa [Mon, 5 Apr 2021 07:54:32 +0000 (00:54 -0700)]
Replace asset load calls with loadAssetToMemory()

All assets now use FILESYSTEM_loadAssetToMemory() instead of

16 months agoAdd FILESYSTEM_loadAssetToMemory()
Misa [Mon, 5 Apr 2021 07:53:04 +0000 (00:53 -0700)]
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.

16 months agoRemove default argument from loadFileToMemory()
Misa [Mon, 5 Apr 2021 07:48:40 +0000 (00:48 -0700)]
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.

16 months agoRemove redundant getDirSeparator() calls in init()
Misa [Mon, 5 Apr 2021 07:45:39 +0000 (00:45 -0700)]
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).

16 months agoRemove getDirSeparator() usage from mountAssetsFrom()
Misa [Sat, 3 Apr 2021 21:20:27 +0000 (14:20 -0700)]
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.

16 months agoRename FILESYSTEM_mount() to FILESYSTEM_mountAssetsFrom()
Misa [Sat, 3 Apr 2021 21:17:40 +0000 (14:17 -0700)]
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.

16 months agoUse FILESYSTEM_mount() when mounting zips
Misa [Sat, 3 Apr 2021 21:07:51 +0000 (14:07 -0700)]
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

Whoever cobbled this asset mounting thing together really didn't fully
understand what they were doing.

16 months agoProperly camel-case FILESYSTEM_[un]mountassets()
Misa [Sat, 3 Apr 2021 19:48:47 +0000 (12:48 -0700)]
Properly camel-case FILESYSTEM_[un]mountassets()

They are now camel-cased to be consistent with the rest of the
filesystem functions.

16 months agoReturn early if FILESYSTEM_mountassets() fails
Misa [Sat, 3 Apr 2021 09:14:15 +0000 (02:14 -0700)]
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.

16 months agoUn-export FILESYSTEM_mount()
Misa [Sat, 3 Apr 2021 09:11:02 +0000 (02:11 -0700)]
Un-export FILESYSTEM_mount()

This function is never used outside of FileSystemUtils.cpp; there is no
reason to export it.

16 months agoOnly do focus fullscreen toggling on X11.
Ethan Lee [Mon, 5 Apr 2021 15:07:32 +0000 (11:07 -0400)]
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.

16 months agoRe-fix resumemusic/musicfadein once again
Misa [Fri, 2 Apr 2021 19:56:25 +0000 (12:56 -0700)]
Re-fix resumemusic/musicfadein once again

So it looks like facb079b3597b380f876537523cd351a0e637b62 (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

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.

16 months agoAdd pause(), pauseef(), and resumeef() to musicclass
Misa [Fri, 2 Apr 2021 19:07:19 +0000 (12:07 -0700)]
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.

16 months agoFactor fade amount calculation to separate function
Misa [Fri, 2 Apr 2021 19:02:01 +0000 (12:02 -0700)]
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.

16 months agoFactor timestep calculation out to Game function
Misa [Fri, 2 Apr 2021 18:50:30 +0000 (11:50 -0700)]
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.

16 months agoAdd option to re-enable 1-frame input delay
Misa [Thu, 1 Apr 2021 22:39:56 +0000 (15:39 -0700)]
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

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

16 months agoRemove advancetext check from teleporter prompt logic
Misa [Thu, 1 Apr 2021 23:21:58 +0000 (16:21 -0700)]
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

[1]: f07a8d2143479a465c31aea2e08764197a37a2e3, PR #421

16 months agoAdd period to glitchrunner mode description text
Misa [Thu, 1 Apr 2021 22:40:24 +0000 (15:40 -0700)]
Add period to glitchrunner mode description text

This is to be consistent with the other options in the Advanced Options

16 months agoFix funcs not being reassigned if delta func is last func in list
Misa [Thu, 1 Apr 2021 22:25:07 +0000 (15:25 -0700)]
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.

16 months agoCall VVV_exit() when SDL_QUIT is received
Misa [Thu, 1 Apr 2021 22:24:14 +0000 (15:24 -0700)]
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.

16 months agoMerge pull request #588 from InfoTeddy/general-bug-fixes
Terry Cavanagh [Fri, 2 Apr 2021 07:21:30 +0000 (17:51 +1030)]
Merge pull request #588 from InfoTeddy/general-bug-fixes

Fix tile of disappearing platforms during final stretch

16 months agoMerge pull request #667 from InfoTeddy/general-improvements
Terry Cavanagh [Fri, 2 Apr 2021 07:19:20 +0000 (17:49 +1030)]
Merge pull request #667 from InfoTeddy/general-improvements

Add text outlines to textboxless textboxes and gravitron text

16 months agoMerge pull request #668 from InfoTeddy/general-bug-fixes-2
Terry Cavanagh [Fri, 2 Apr 2021 07:17:27 +0000 (17:47 +1030)]
Merge pull request #668 from InfoTeddy/general-bug-fixes-2

Fix oversight with unfocus pause screen in Flip Mode

16 months agoOutline all gravitron text
Misa [Fri, 19 Mar 2021 21:29:03 +0000 (14:29 -0700)]
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!).

16 months agoAdd Graphics::bigrprint()
Misa [Fri, 19 Mar 2021 21:28:04 +0000 (14:28 -0700)]
Add Graphics::bigrprint()

Same as bigbprint(), we duplicate some of the calculations because it's
better than duplicating another text printing function.

16 months agoAdd Graphics::bigbprint()
Misa [Fri, 19 Mar 2021 21:26:59 +0000 (14:26 -0700)]
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...

16 months agoOutline textboxless textboxes
Misa [Fri, 19 Mar 2021 20:38:25 +0000 (13:38 -0700)]
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).

16 months agoFix oversight with unfocus pause screen in Flip Mode
Misa [Fri, 19 Mar 2021 22:58:52 +0000 (15:58 -0700)]
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.

16 months agoFix tile of disappearing platforms during final stretch
Misa [Sat, 9 Jan 2021 22:50:06 +0000 (14:50 -0800)]
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

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

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

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.

16 months agoReplace other usage of PHYSFS_getBaseDir() with SDL_GetBasePath()
Misa [Wed, 31 Mar 2021 06:48:25 +0000 (23:48 -0700)]
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.

16 months agoSkip the icon on Apple targets, it also has the icns
Ethan Lee [Wed, 31 Mar 2021 06:50:38 +0000 (02:50 -0400)]
Skip the icon on Apple targets, it also has the icns

16 months agoPut Misa at the top of GitHub Friends
Ethan Lee [Wed, 31 Mar 2021 06:43:05 +0000 (02:43 -0400)]
Put Misa at the top of GitHub Friends

16 months agoFix leaking GetBasePath result
Ethan Lee [Wed, 31 Mar 2021 06:42:15 +0000 (02:42 -0400)]
Fix leaking GetBasePath result

16 months agoFix memory leak when pasting text
Misa [Wed, 31 Mar 2021 06:23:29 +0000 (23:23 -0700)]
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

16 months agoRe-sync CONTRIBUTORS and GitHub Friends lists
Ethan Lee [Wed, 31 Mar 2021 03:08:25 +0000 (23:08 -0400)]
Re-sync CONTRIBUTORS and GitHub Friends lists

16 months agoUse SDL_GetBashPath instead of PHYSFS_getBaseDir, latter is made of fail on macOS
Ethan Lee [Tue, 30 Mar 2021 16:18:09 +0000 (12:18 -0400)]
Use SDL_GetBashPath instead of PHYSFS_getBaseDir, latter is made of fail on macOS

16 months agoVS2010 buildfix
Ethan Lee [Tue, 30 Mar 2021 15:51:42 +0000 (11:51 -0400)]
VS2010 buildfix

16 months agoFix targets (question marks) not showing up on minimap
Misa [Fri, 26 Mar 2021 03:37:42 +0000 (20:37 -0700)]
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.

16 months agoFix background not changing when pressing Esc on main menu
Misa [Fri, 26 Mar 2021 03:26:45 +0000 (20:26 -0700)]
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.

16 months agoRemove mainmenu from Game
Misa [Sat, 20 Mar 2021 06:34:41 +0000 (23:34 -0700)]
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.)

16 months agoSet fademode to temp 0 when going to in-game options
Misa [Sat, 20 Mar 2021 06:23:18 +0000 (23:23 -0700)]
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.

16 months agoDecouple fademode from starting gamemode
Misa [Fri, 26 Mar 2021 03:11:27 +0000 (20:11 -0700)]
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().

16 months agoDe-duplicate setting mainmenu and fademode
Misa [Fri, 26 Mar 2021 03:10:36 +0000 (20:10 -0700)]
De-duplicate setting mainmenu and fademode

This makes my work easier later.

16 months agoFix 1-frame flicker entering room with warp lines and entity on edge
Misa [Fri, 26 Mar 2021 02:00:31 +0000 (19:00 -0700)]
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.

16 months agoDe-duplicate warp line creation in createentity()
Misa [Fri, 26 Mar 2021 01:57:53 +0000 (18:57 -0700)]
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.

16 months agoAdd bounds check for color of createentity number 55
Misa [Fri, 26 Mar 2021 01:38:36 +0000 (18:38 -0700)]
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.

16 months agoMerge pull request #680 from InfoTeddy/pr-template-update
Terry Cavanagh [Thu, 25 Mar 2021 01:27:34 +0000 (11:57 +1030)]
Merge pull request #680 from InfoTeddy/pr-template-update

Remove 2.3 example from pull request template

16 months agoRemove 2.3 example from pull request template
Misa [Wed, 24 Mar 2021 21:36:33 +0000 (14:36 -0700)]
Remove 2.3 example from pull request template

Mentioning 2.3 here is going to be anachronistic when 2.4 exists. And
there's not really any point in bumping this version number every time
the game's version number is bumped as well. So it's best to just remove

16 months agoAdd bounds checks to room explored getter and setter
Misa [Wed, 24 Mar 2021 19:14:28 +0000 (12:14 -0700)]
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.

16 months agoRefactor explored rooms to use setters and getters
Misa [Wed, 24 Mar 2021 19:12:39 +0000 (12:12 -0700)]
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

The getter is mapclass::isexplored() and the setter is

16 months agoAdd bounds checks to room propety getters and setters
Misa [Wed, 24 Mar 2021 19:04:06 +0000 (12:04 -0700)]
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.

16 months agoRefactor room properties to use setter and getter funcs
Misa [Wed, 24 Mar 2021 18:59:36 +0000 (11:59 -0700)]
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.

16 months agoAdd bounds checks to tile setter and getters
Misa [Wed, 24 Mar 2021 18:51:39 +0000 (11:51 -0700)]
Add bounds checks to tile setter and getters

This removes all traces of Undefined Behavior from getting and placing

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.

16 months agoRefactor tiles to use setter and getter functions
Misa [Wed, 24 Mar 2021 18:26:19 +0000 (11:26 -0700)]
Refactor tiles to use setter and getter functions

This makes it easier to add bounds checks to all accesses of

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

This also makes the code more readable, as a side effect.

16 months agoFix bounds checks in drawentity()
Misa [Wed, 24 Mar 2021 19:32:43 +0000 (12:32 -0700)]
Fix bounds checks in drawentity()

The existing bounds checks were correct sometimes but other times were

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.

16 months agoInitialize temporary edentity when loading levels
Misa [Wed, 24 Mar 2021 19:21:28 +0000 (12:21 -0700)]
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.

16 months agoAdd missing pText NULL checks
Misa [Wed, 24 Mar 2021 19:19:44 +0000 (12:19 -0700)]
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.

16 months agoCapitalize "OFF" when invincibility is off
Misa [Mon, 22 Mar 2021 00:41:07 +0000 (17:41 -0700)]
Capitalize "OFF" when invincibility is off

All other settings capitalize "ON" and "OFF", so this one should, too.

16 months agoAdd period to text outline description
Misa [Mon, 22 Mar 2021 00:36:23 +0000 (17:36 -0700)]
Add period to text outline description

This is to be consistent with all other option descriptions, which all
end in a period as well.

16 months agoRemove game.gametimer in favor of game.frames
Misa [Sun, 21 Mar 2021 23:58:08 +0000 (16:58 -0700)]
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.

16 months agoFix text box deltaframe flashing on deltaframes after fully opaque
Misa [Sun, 21 Mar 2021 19:49:14 +0000 (12:49 -0700)]
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.

16 months agoFix a crash on first unfocus
Ethan Lee [Sun, 21 Mar 2021 21:19:08 +0000 (17:19 -0400)]
Fix a crash on first unfocus

16 months agoVisual Studio buildfix
Ethan Lee [Sun, 21 Mar 2021 21:15:36 +0000 (17:15 -0400)]
Visual Studio buildfix

16 months agoAdd braces around sub-object in unfocused_func_list
Misa [Sun, 21 Mar 2021 20:53:29 +0000 (13:53 -0700)]
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.

16 months agoRemove now-unneeded kludge for finalmode entity colors
Misa [Fri, 19 Mar 2021 04:38:30 +0000 (21:38 -0700)]
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.

16 months agoFix filter/screenshake/flash update order
Misa [Thu, 18 Mar 2021 00:53:17 +0000 (17:53 -0700)]
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,

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.

16 months agoMove logical onground/onroof updates to start of gamelogic
Misa [Thu, 18 Mar 2021 20:20:56 +0000 (13:20 -0700)]
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.

16 months agoSplit onground/onroof into visual and logical variables
Misa [Thu, 18 Mar 2021 20:18:33 +0000 (13:18 -0700)]
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.

16 months agoRe-remove 1-frame input delay
Misa [Mon, 15 Mar 2021 09:46:29 +0000 (02:46 -0700)]
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.

16 months agoMove key.Poll() calls to just before input funcs
Misa [Mon, 15 Mar 2021 09:56:46 +0000 (02:56 -0700)]
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.

16 months agoRe-fix crewmate directions (without copy-pasting)
Misa [Mon, 18 Jan 2021 20:44:22 +0000 (12:44 -0800)]
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.

16 months agoRemove entityclass::createentity() deltaframe kludge
Misa [Sun, 17 Jan 2021 06:29:44 +0000 (22:29 -0800)]
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.

16 months agoFix 1-frame glitch when going to in-game options from edsettings
Misa [Fri, 19 Mar 2021 04:14:40 +0000 (21:14 -0700)]
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

16 months agoFix 1-frame background glitch when returning from in-game options
Misa [Fri, 19 Mar 2021 04:12:55 +0000 (21:12 -0700)]
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.

16 months agoFix 1-frame text glitch when returning to editor settings from options
Misa [Fri, 19 Mar 2021 04:11:52 +0000 (21:11 -0700)]
Fix 1-frame text glitch when returning to editor settings from options

The returnmenu() needs to be deferred until the end of the frame.

16 months agoFix 1-frame text glitch returning to pause menu from in-game options
Misa [Wed, 13 Jan 2021 04:11:50 +0000 (20:11 -0800)]
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

16 months agoRemove game.shouldreturntoeditor in favor of using defer callback
Misa [Sun, 10 Jan 2021 22:48:25 +0000 (14:48 -0800)]
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.

16 months agoUpdate player lerpoldxp/yp in moveplayer()
Misa [Sun, 10 Jan 2021 07:34:26 +0000 (23:34 -0800)]
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

16 months agoAdd deferred callbacks to game loop
Misa [Thu, 24 Dec 2020 07:24:31 +0000 (23:24 -0800)]
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

16 months agoUpdate player lerpoldxp/yp in gotoposition()
Misa [Mon, 23 Nov 2020 10:02:41 +0000 (02:02 -0800)]
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.

16 months agoRemove now-unneeded deltaframe fix when going to in-game settings
Misa [Tue, 10 Nov 2020 08:27:42 +0000 (00:27 -0800)]
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

16 months agoRemove gravity line kludge from Graphics::drawgravityline()
Misa [Sun, 8 Nov 2020 01:29:06 +0000 (17:29 -0800)]
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.

16 months agoMove updating lerpoldxp/yp to start of gamelogic()
Misa [Sun, 8 Nov 2020 01:00:42 +0000 (17:00 -0800)]
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

16 months agoRemove script.dontrunnextframe kludge
Misa [Sun, 8 Nov 2020 00:49:32 +0000 (16:49 -0800)]
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.

16 months agoUnindent unfocused_run() and focused_begin() from previous commit
Misa [Sat, 16 Jan 2021 19:40:26 +0000 (11:40 -0800)]
Unindent unfocused_run() and focused_begin() from previous commit

As always, indentation changes are applied in a separate commit to
minimize diff noise.

16 months agoGeneralize game loop order and fix it to what it was in 2.2
Misa [Sat, 7 Nov 2020 00:08:22 +0000 (16:08 -0800)]
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.

16 months agoRemove special text box checks for y-position 180
Misa [Sun, 21 Mar 2021 06:17:12 +0000 (23:17 -0700)]
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

16 months agoMake foundtrinket() Flip Mode-aware
Misa [Sat, 20 Mar 2021 03:12:42 +0000 (20:12 -0700)]
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.

16 months agoSwitch flipme script command to use flipme textbox attribute
Misa [Sat, 20 Mar 2021 03:08:41 +0000 (20:08 -0700)]
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.

16 months agoSwitch all flippable text boxes to use createtextboxflipme
Misa [Sat, 20 Mar 2021 03:07:14 +0000 (20:07 -0700)]
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

16 months agoAdd createtextboxreal() and createtextboxflipme()
Misa [Sat, 20 Mar 2021 03:03:08 +0000 (20:03 -0700)]
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.

16 months agoAdd flipme attribute to textboxclass
Misa [Sat, 20 Mar 2021 02:51:36 +0000 (19:51 -0700)]
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.

16 months agoRemove textrect attribute from textboxclass
Misa [Sat, 20 Mar 2021 02:47:37 +0000 (19:47 -0700)]
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.

16 months agoRemove unused x/y textboxclass attributes
Misa [Sat, 20 Mar 2021 02:45:28 +0000 (19:45 -0700)]
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,

16 months agoDe-duplicate "Game Saved" telesave textbox
Misa [Sat, 20 Mar 2021 02:40:16 +0000 (19:40 -0700)]
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.

16 months agoDe-duplicate Level Complete sequence textboxes
Misa [Sat, 20 Mar 2021 02:37:54 +0000 (19:37 -0700)]
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.

16 months agoRemove default arguments from createtextbox()
Misa [Fri, 19 Mar 2021 23:41:41 +0000 (16:41 -0700)]
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.

16 months agoRemove commented-out code from Game.cpp and Script.cpp
Misa [Fri, 19 Mar 2021 23:24:16 +0000 (16:24 -0700)]
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.