Here's what causes #401: After the fade to menu delay ticks down to 0,
the game calls game.quittomenu(), but the rest of mapinput() still
executes. This means that the block that detects your ACTION press gets
executed, because there's a check that fadetomenudelay is less than or
equal to 0, and, well, it is.
So if you've pressed ACTION on the exact frame that it counts down to 0,
then the game detects your ACTION press, then processes it accordingly,
and then sets the fadetomenudelay, which means it'll get reactivated the
next time you open the map screen. But at this point, you get sent to
TITLEMODE, because game.quittomenu() set game.gamestate accordingly.
(This is why resetting game.fadetomenu or game.fadetomenudelay in
game.quittomenu() or script.hardreset() won't fix this bug.)
The solution here is to add a game.fadetomenu check to the ACTION press
processing.
Same-frame state transition logic is hard... actually, any sort of thing
where two things happen on the same frame is really annoying.
This also applies to fadetolab and fadetolabdelay, too.
Fixes#401.
The game previously did this dumb thing where it lumped in all its
settings with its file that tracked your records and unlocks,
`unlock.vvv`. It wasn't really an issue, until 2.3 came along and added
a few settings, suddenly making a problem where 2.3 settings would be
reset by chance if you decided to touch 2.2.
The solution to this is to move all settings to a new file,
`settings.vvv`. However, for compatibility with 2.2, settings will still
be written to `unlock.vvv`.
The game will prioritize reading from `settings.vvv` instead of
`unlock.vvv`, so if there's a setting that's missing from `unlock.vvv`,
no worries there. But if `settings.vvv` is missing, then it'll read
settings from `unlock.vvv`. As well, if `unlock.vvv` is missing, then
`settings.vvv` will be read from instead (I explicitly tested for this,
and found that I had to write special code to handle this case,
otherwise the game would overwrite the existing `settings.vvv` before
reading from it; kids, make sure to always test your code!).
Closes#373 fully.
Now that tower, title, and horizontal/veritcal warp backgrounds all use
separate buffers, there's no longer any need to temporarily store
variables as a workaround for the buffers stepping on each other.
With the previous commit in place, we can now simply move some usages of
the previous towerbg to use a separate object instead. That way, we
don't have to mess with a monolithic state, or some better way to phrase
what I just said, and we instead have two separate objects that can
coexist side-by-side.
Previously, the tower background was controlled by a disparate set of
attributes on Graphics and mapclass, and wasn't really encapsulated. (If
that's what that word means, I don't particularly care about
object-oriented lingo.) But now, all relevant things that a tower
background has has been put into a TowerBG struct, so it will be easy to
make multiple copies without having to duplicate the code that handles
it.
It's better to do INBOUNDS_VEC(i, obj.entities) instead of 'i > -1'.
'i > -1' is used in cases like obj.getplayer(), which COULD return a
sentinel value of -1 and so correct code will have to check that value.
However, I am now of the opinion that INBOUNDS_VEC() should be used and
isn't unnecessary.
Consider the case of the face() script command: it's not enough to check
i > -1, you should read the routine carefully. Because if you look
closely, you'll see that it's not guaranteed that 'i' will be initialized
at all in that command. Indeed, if you call face() with invalid
arguments, it won't be. And so, 'i' could be something like 215, and
that would index out-of-bounds, and that wouldn't be good. Therefore,
it's better to have the full bounds check instead of checking only one
bounds. Many commands are like this, after some searching I can also
name position(), changemood(), changetile(), changegravity(), etc.
It also makes the code more explicit. Now you don't have to wonder what
-1 means or why it's being checked, you can just read the 'INBOUNDS' and
go "oh, that checks if it's actually inbounds or not".
There's not really any good reason to prevent this action during a fade
animation. That just makes the fade timer one more potential contributor
to a softlock.
I'm leaving the fademode conditional on the Time Trial quick restart,
though - removing it would mean being able to quick restart during a
fade-in, and thus being able to spam Enter over and over to keep
re-starting the fade animation, which looks goofy.
The hooks to bring up the map screen, pause screen, quit from Super
Gravitron, restart Time Trial, and commit suicide have now been hoisted
out of the for-loop that checked for a player entity. None of these
actions require a player entity, and there's no good reason to take away
your control from any of these actions, especially being able to quit to
the menu. The only actions inside the for-loop now are activating a
terminal and activating a teleporter, both of which require a player
entity to be standing in front of a terminal or teleporter, and both of
which have good reasons to be temporarily disabled.
There's not really any need for it to be there. It gets called when the
Time Trial restarts, as restarting the Time Trial calls
script.startgamemode(), which calls script.hardreset() anyway.
Furthermore, since script.hardreset() is removed, we can also remove two
lines that are meant to work around the fact that everything gets reset,
which is now no longer the case.
Fixes#367.
This fixes the bug where in glitchrunner mode, quitting to the menu
would always put you back at the play menu on the first option, instead
of the menu you entered the game from.
The problem is the script.hardreset() that gets called before the game
actually quits to the menu, so when Game::quittomenu() gets called to
quit to the menu, all the variables that keep track of whether you're in
a certain gamemode, such as game.insecretlab and map.custommode, all get
prematurely reset before that function can read them and put you back to
the correct menu.
The solution here is to simply reset only what's needed when quitting to
the menu. Specifically, in order for credits warp to work,
script.running needs to be set to false and all the text boxes need to
be removed. Text boxes need to be gone so the "- Press ACTION to advance
text -" prompt will stay up without a text box, enabling the player to
increment the gamestate at will by pressing ACTION, and the script needs
to stop running so further text boxes don't spawn in.
Fixes#389.
All that pressing R does in No Death Mode is end your run. As a result,
it'll only be pressed by accident, so it's better to just disable it
instead.
It's not even useful to quick-restart, because it's faster to quit and
go through the menu again than it is to wait through the Game Over
screen.
Additionally, I removed the `game.deathseq<=0` conditional because it's
unnecessary due to the if-statement already being inside a
`game.deathseq == -1` conditional.
The half-second delay comes from the fact that the game uses
graphics.resumegamemode to go back to GAMEMODE. This system waits for
graphics.menuoffset to reach a certain threshold before actually going
back (this is the animation of the map screen being brought down). To
speed it up, I'll just set graphics.menuoffset directly.
I could've directly set game.gamestate to GAMEMODE, but I wanted to be
safe and use the existing system instead.
Okay, so basically here's the include layout that this game now
consistently uses:
[The "main" header file, if any (e.g. Graphics.h for Graphics.cpp)]
[blank line]
[All system includes, such as tinyxml2/physfs/utfcpp/SDL]
[blank line]
[All project includes, such as Game.h/Entity.h/etc.]
And if applicable, another blank line, and then some special-case
include screwy stuff (take a look at editor.cpp or FileSystemUtils.cpp,
for example, they have ifdefs and defines with their includes).
Including a header file inside another header file means a bunch of
files are going to be unnecessarily recompiled whenever that inner
header file is changed. So I minimized the amount of header files
included in a header file, and only included the ones that were
necessary (system includes don't count, I'm only talking about includes
from within this project). Then the includes are only in the .cpp files
themselves.
This also minimizes problems such as a NO_CUSTOM_LEVELS build failing
because some file depended on an include that got included in editor.h,
which is another benefit of removing unnecessary includes from header
files.
The game would softlock if you brought up the map screen or quit screen
after exiting the Super Gravitron to the Secret Lab. This softlock would
only happen if you were in glitchrunner mode.
This is because glitchrunner mode set game.fadetolabdelay when it
shouldn't have, and also checked game.fadetolabdelay when it shouldn't
have.
So I made it so that the game will only set game.fadetolabdelay when not
in glitchrunner mode (I already had a check for game.fadetomenudelay,
too!) and the game will only check for game.fadetomenudelay and
game.fadetolabdelay when not in glitchrunner mode, as well.
I originally made the game check game.fadetomenudelay and
game.fadetolabdelay to prevent being able to re-press ACTION to re-start
the fadeout if the game was already fading out. And I made sure that
this wasn't broken, both in glitchrunner mode and normal mode.
There's no reason you shouldn't be allowed to press Enter on teleporters
during playtesting.
Well, except that you can press Esc in the teleporter menu in order to
go to the pause menu, which isn't intended in playtesting. But I can
just add a check there so that pressing Esc closes the teleporter menu
instead, it's fine.
So you get a trophy and achievement for completing the game in Flip
Mode. Which begs the question, how does the game know that you've played
through the game in Flip Mode the entire way, and haven't switched it
off at any point? It looks like if you play normally all the way up
until the checkpoint in V, and then turn on Flip Mode, the game won't
give you the trophy. What gives?
Well, actually, what happens is that every time you press Enter on a
teleporter, the game will set flag 73 to true if you're NOT in Flip
Mode. Then when Game Complete runs, the game will check if flag 73 is
off, and then give you the achievement and trophy accordingly.
However, what this means is that you could just save your game before
pressing Enter on a teleporter, then quit and go into options, turn on
Flip Mode, use the teleporter, then save your game (it's automatically
saved since you just used a teleporter), quit and go into options, and
turn it off. Then you'd get the Flip Mode trophy even though you haven't
actually played the entire game in Flip Mode.
Furthermore, in 2.3 you can bring up the pause menu to toggle Flip Mode,
so you don't even have to quit to circumvent this detection.
To fix both of these exploits, I moved the turning on of flag 73 to
starting a new game, loading a quicksave, and loading a telesave (cases
0, 1, and 2 respectively in scriptclass::startgamemode()). I also added
a Flip Mode check to the routine that runs whenever you exit an options
menu back to the pause menu, so you can't circumvent the detection that
way, either.
The music for the Tower is supposed to be ecroF evitisoP in Flip Mode,
and Positive Force when not in Flip Mode. However, if you go to the
options from the pause menu and toggle Flip Mode, the music isn't
changed.
Fixing this is pretty simple, just check the current area if not in a
custom level and play the correct track accordingly when toggling Flip
Mode from in-game.
So, originally, I wanted to keep them on Game, but it turns out that if
I initialize it in Game.cpp, the compiler will complain that other files
won't know what's actually inside the array. To do that, I'd have to
initialize it in Game.h. But I don't want to initialize it in Game.h
because that'd mean recompiling a lot of unnecessary files whenever
someone gets added to the credits.
So, I moved all the patrons, superpatrons, and GitHub contributors to a
new file, Credits.h, which only contains the list (and the credits max
position calculation). That way, whenever someone gets added, only the
minimal amount of files need to be recompiled.
This infinite loop would occur because once you pressed left or right,
the game keeps searching through all the list of teleporters until it
finds one that is unlocked. But if there's none that are unlocked, then
the game goes into an infinite loop, which brings up the Not Responding
dialog on Windows so you can kill it.
Normally, you're not supposed to have no teleporters unlocked while
being able to access a teleporter, but you can achieve this by going to
Class Dismissed from a custom level (while making sure you don't start
in 0,0, because there's a teleporter there that you would unlock).
The solution is to make sure at least one teleporter is unlocked before
doing any searching.
A do-while is just a while-loop, but the inner block will always run
once before the conditional is checked.
It looks like in order to achieve this desired behavior (always run the
block once before checking the conditional), instead of using a do-while
loop, Terry just used a normal while-loop and copy-pasted the inner
block on the outside.
So I'm de-duplicating the code.
The problem we're running into is entirely contained in the Screen - we need to
either decouple graphics context init from Screen::init or we need to take out
the screenbuffer interaction from loadstats (which I'm more in favor of since we
can just pull the config values and pass them to Screen::init later).
The options for fullscreen and scaling mode were at the top, then there
were various other graphical options, and then the option to resize to
the nearest window size that is of an integer multiple was all the way
below that. Now that last option is moved to be right below the other
options related to window sizing.
VVVVVV's menus are kind of packed to the brim, so I thought it was time
to recategorize the menus a little bit. There's now a new "advanced
options" menu which holds the following options which were moved out of
graphic options, game options and especially accessibility options:
- toggle mouse
- unfocus pause
- fake load screen
- room name background
- glitchrunner mode
I also made the positioning of the titles and descriptions more
consistent, and made some options which were moved to the new menu not
so abbreviated ("load screen" and "room name bg")
Flip Mode will now be in the game options menu if either:
(1) You're playing the M&P version.
(2) You have it unlocked and you came here from the in-game pause
screen.
This is because if you're playing M&P, you'd have to close the game,
edit unlock.vvv, and re-launch the game to toggle Flip Mode, since
there's no other way to do so. And if you're playing the full version,
you'd have to save and exit your session in order to toggle Flip Mode.
If you want your game window to simply be exactly 320x240, or 640x480,
or 960x720 etc. then it's really annoying that there's no easy way to do
this (to clarify, this is different from integer mode, which controls
the size of the game INSIDE the window). The easiest way would be having
to close the game, go into unlock.vvv, and edit the window size
manually. VCE has a 1x/2x/3x/4x graphics option to solve this, although
it does not account for actual monitor size (those 1x/2x/3x/4x modes are
all you get, whether or not you have a monitor too small for some of
them or too big for any of them to be what you want).
I discussed this with flibit, and he said that VCE's approach (if it
accounted for monitor size) wouldn't work on high-retina displays or
high DPIs, because getting the actual multiplier to account for those
monitors is kind of a pain. So the next best thing would be to add an
option that resizes to the nearest perfect multiple of 320x240. That way
you could simply resize the window and let the game correct any
imperfect dimensions automatically.
It's sometimes unwanted by people, and it's unwanted enough that there
exist instructions to hexedit the binary to remove it (
https://distractionware.com/forum/index.php?topic=3247.0 ).
Fun fact, the unfocus pause didn't exist in 2.0.
This was fixed in 2.3 because one of the side effects of this janky
system was being able to accidentally immediately quit to the title if
the screen was black during a cutscene, which is something very likely
to happen to casual players.
Anyway, credits warp uses this gamestate-based system because it
utilizes quitting to the title screen doing gamestate 80. From there,
you increment the gamestate to gamestate 94 to use the Space Station 2
expo script.
This is the first part of what is necessary for credits warp to work.
If the "- Press ACTION to advance text -" prompt is up, and you manage
to keep it up, then you can indefinitely increment the gamestate by
pressing ACTION.
This is first used in credits warp to teleport to the start of Space
Station 2 (by utilizing the Eurogame expo script, triggered by a
gamestate), and then again later by using a teleporter that has a high
gamestate number to increment to the [C[C[C[C[Captain!] cutscene.
Glitchrunner mode is intended to re-enable glitches that existed in
older versions of VVVVVV. These glitches were removed because they could
legitimately affect a casual player's experience. Glitches like various
R-pressing screwery, Space Station 1 skip, telejumping, Gravitron
out-of-bounds, etc. will not be patched.
This fixes a corner case where using gamestate 82 from the editor would
put you in a softlock because it would return to the editor settings
menu, which only functions in EDITORMODE and results in a softlock in
TITLEMODE.
This is already done for invincibility. It's kind of unnecessary, but
it's just to make sure if for some reason in the future variables like
insecretlab/intimetrial/nodeathmode don't get reset when exiting to the
menu.
Another thing that's annoyed me a lot is being unable to simply press
Esc to close the pause menu. You'd have to hover over the "return to
game" or "keep playing" option. This would be even more annoying with
more options on the menu, so allowing to press Esc is a nice
quality-of-life thing.
Otherwise you could keep re-pressing ACTION on the "yes" option and keep
stalling it until it finally faded out, or quickly go back past menu
options or something.
Otherwise the menu background would have this rendering glitch where the
bypos of the in-game tower wouldn't divide easily and have a bunch of
jitters in an otherwise smooth but overall still somewhat smooth
background.
Also set map.tdrawback to true when leaving the menu.
This is to fix the interpolated color of the tower background
persisting, as well as making sure the menu background doesn't persist
when exiting.