This is to be extra safe and ensure that your hard-earned record isn't
lost at all.
In 2.2, the game didn't save your Super Gravitron record at all. It only
saved it if you closed the game by quitting to the title screen and
pressing ACTION on "quit game". You couldn't press Alt+F4, and you
couldn't press X, you had to do it that way, otherwise your record would
be lost.
In 2.3 right now, the game WILL save your unlock.vvv when you close the
game gracefully by any means, but this still means that if it doesn't
otherwise close gracefully (like, say, a crash), it won't save your
record. It feels like we shouldn't rely on this catch-all saving to save
Super Gravitron records.
Flag 67 seems to be a general-purpose Game Complete flag. It's used to
replace the CREW option with the SHIP option on the pause screen.
Unfortunately, it gets turned on too early during Game Complete. Right
when Viridian starts to teleport, you can bring up the pause screen and
select the SHIP option.
It will teleport you to the ship coordinates, but still keep you in
finalmode, and since the ship coordinates are at 102,111 (finalmode is
only around 46,54), you'll still be stuck in Outside Dimension VVVVVV,
and you've interrupted the Game Complete gamestate by switching to the
teleporting gamestate. Oh, and your checkpoint is set, too, so you can't
even press R. Oh and since there's no teleporter, and checkpoint setting
doesn't check the sentinel value, this results in Undefined Behavior,
too.
So this results in an in-game softlock. The only option you can do is
quit the game at this point.
To fix this issue, just move turning on flag 67 before the savetele() in
gamecompletelogic2().
This makes it so if you bring up the quit screen during a faded-out
screen, you will at least see the screen and won't see black. And also
the fade-in and fade-out animations will still work on the quit screen.
And more importantly, I tested and there's no 1-frame flicker or
anything.
This was used by the old system, which also had an over-reliance on
Terry's State Machine. And due to the fact that it relied on fademode,
it also meant that bringing up the pause screen while faded-out would
result in the player getting sent back to the menu, so one accidental
Esc press during a cutscene could mean countless hours of progress lost
(especially in custom levels).
This would cause the editor to think that it itself is in the middle of
fading to the menu, and so then will fade to the menu, thus losing any
unsaved work without warning.
Having to rely on a script means the fade out wouldn't be self-contained
in MAPMODE, which could cause a small issue where you could die during
the return to the lab. But that issue is now fixed. There's no need to
use the script, and anyway the endcutscene() and untilbars() in said
script don't do anything because there are no cutscene bars in the first
place, so no need to worry about those.
Again, what I've done here is removed the over-reliance on Terry's State
Machine to return to the lab, and just moved it into separate variables
instead. This means that returning to the lab is ALMOST entirely
self-contained in MAPMODE, except there's a quick jaunt over to GAMEMODE
to run a script because you can only run scripts in GAMEMODE.
Alright, so what I've done here is made exiting to the menu entirely
separate from Terry's State Machine, and thus it can now take place
entirely within MAPMODE instead of having to go back to GAMEMODE. Also,
it's faster by 15 frames since we don't need to wait for the map screen
to go back down.
This cleans up a whole lot of kludge variables, because this aggressive
hardreset() right as ACTION is pressed doesn't do anyone any favors.
This aggressive hardreset() was probably here because of the whole fact
that exiting to the menu uses Terry's State Machine, to minimize the
chances of interruption, but it actually causes more issues and allows
towers to interrupt the fadeout. And we should fix the root cause (the
usage of the state machine) instead of patching together some kludge.
I don't want the quit code to only be in the state machine, but I'll
keep the gamestate around for compatibility reasons (there are custom
levels that directly use gamestate 80 to quit to the menu).
Due to the previous commit, these will no longer be regularly taking in
out-of-bounds entity indices. Or at least they shouldn't, so I'm putting
in these print statements here on the off-chance that they do.
Otherwise, this would result in the game updating an entity twice, which
isn't good. This is most noticeable in the Gravitron, where many
Gravitron squares are created and destroyed at a time, and it's
especially noticeable during the part near the end of the Gravitron
where the pattern is two Gravitron squares, one at the top and bottom,
and then two Gravitron squares in the middle afterwards. The timing is
just right such that the top one of the two middle ones would be
misaligned with the bottom one of the two when a Gravitron square gets
outside the screen.
To do this, I changed entityclass::updateentities() into a bool, and
made every single caller check its return value. I only needed to do
this for the ones preceding updateentitylogic() and
entitymapcollision(), but I wanted to play it safe and be defensive, so
I did it for the disappearing platform kludge, as well as the
updateentities() within the updateentities() function.
Apparently, the game just leaves minitowermode on, which can cause
issues if you're in a horizontal warping room (and not any other room
type, due to how frame ordering works). This would manifest itself as a
wrong warp to (20,20) because the game is trying to apply the hardcoded
minitower screen transitions when it shouldn't be.
The main ones to beware of here are entityclass::updateentities(),
entityclass::updateentitylogic(), and entityclass::entitymapcollision().
They would index out-of-bounds and thus commit Undefined Behavior if the
entity was removed in entityclass::updateentities(). And it would've
been fine enough if I only added bounds checks to those functions.
However, I decided to be a bit more defensive and play it safe, and
added bounds checks to ALL functions taking in not only an entity
indice, but also blocks and linecrosskludge indices.
Otherwise, if you died after entering a room with a horizontal or
vertical warp background (but not the all-sides warp background), the
warp background would be the first thing you see when going to the Game
Over screen, and would then start scrolling downwards with the proper
tower background coming in from the top.
This oversight seems to have always been in the game.
Was No Death Mode actually tested? Like, did anyone ever play through
the entire game without dying in the Warp Zone, or even AFTER completing
the Warp Zone, like, ever?
When you complete the game, you're now redirected to the play menu. This
is because your quicksave will have been deleted so you can't go back to
the summary menu.
When you complete a custom level, you'll go back to the levels list, in
case you started the level from a quicksave.
Looks like there wasn't a custommode check for the spawning of crewmates
based on which crewmates were rescued, but now there is.
This has gone undiscovered for a long time, mostly because people don't
use the rescued() internal command.
While I was working on my over-30-FPS patch, I found out that the tower
background in the credits scroll was being completely re-drawn every
single frame, which was a bit wasteful and expensive. It's also harder
to interpolate for my over-30-FPS patch. I'm guessing this constant
re-draw was done because the math to get the surface scroll properly
working is a bit subtle, but I've figured the precise math out!
The first changes of this patch is just removing the unconditional
`map.tdrawback = true;`, and having to set `map.scrolldir` everywhere to
get the credits scrolling in the right direction but make sure the title
screen doesn't start scrolling like a descending tower, too.
After that, the first problem is that it looks like the ACTION press to
speed up the credits scrolling doesn't speed up the background, too. No
problem, just shove a `!game.press_action` check in
`gamecompletelogic()`.
However, this introduces a mini-problem, which is that NOW when you hold
down ACTION, the background appears to be slowly getting out of sync
with the credits text by a one-pixel-per-second difference. This is
actually due to the fact that, as a result of me adding the conditional,
`map.bscroll` is no longer always unconditionally getting set to 1,
while `game.creditposition` IS always unconditionally getting
decremented by 1. And when you hold down ACTION, `game.creditposition`
gets decremented by 6.
Thus, I need to set `map.bscroll` when holding down ACTION to be 7,
which is 6 plus 1.
Then we have another problem, which is that the incoming textures desync
when you press ACTION, and when you release ACTION. They desync by
precisely 6 pixels, which should be a familiar number. I (eventually)
tracked this down to `map.bypos` being updated at the same time
`map.bscroll` is, even though `map.bypos` should be updated a frame
later AFTER updating `map.bscroll`.
So I had to change the `map.bypos` update in `gamecompleteinput()` and
`gamecompletelogic()` to be `map.bypos += map.bscroll;` and then place
it before any `map.bscroll` update, thus ensuring that `map.bscroll`
updates exactly one frame before `map.ypos` does. I had to move the
`map.bypos += map.bscroll;` to be in `gamecompleteinput()`, because
`gamecompleteinput()` comes first before `gamecompletelogic()` in the
`main.cpp` game loop, otherwise the `map.bypos` update won't be delayed
by one frame for when you press ACTION to make it go faster, and thus
cause a desync when you press ACTION.
Oh and then after that, I had to make the descending tower background
draw a THIRD row of incoming tiles, otherwise you could see some black
flickering at the bottom of the screen when you held down ACTION.
All of this took me way too long to figure out, but now the credits
scroll works perfectly while being more optimized.
I had forgotten that the game flashed and did screen-shaking when you
pressed ACTION to quicksave.
While testing for my over-30-FPS patch I stumbled across this.
It's less code being copied and pasted, especially since for my
over-30-FPS patch I would have to make a separate function for each if
both of them were still there, but if they're unified into one then I
will only have to make one more function.
And since map.scrolldir is now used outside of GAMEMODE, we'll need to
reset it in hardreset() and when exiting playtesting.
Due to the previous commit, the descending tower background now has to
account for map.bscroll, or else it will be off by one pixel from the
incoming textures. But ascending tower backgrounds work fine, so no need
to do anything with those.
Looks like this was done as a quick fix instead of taking the time to
figure out the math needed to actually draw the incoming textures, which
is fair enough - it only makes one room, Panic Room, slightly laggier.
While I was working on my over-30-FPS patch, though, I came across the
fact that this background kept getting entirely redrawn every frame, and
it seems like it would be easier to interpolate descending tower
backgrounds if we scrolled what was already there instead.
Here, we have to draw two rows of incoming textures, otherwise the
scrolling surface will produce black lines.
Previously, if you had backgrounds disabled in accessibility options,
and went to the editor and opened up the editor menu, it would be drawn
straight on top of what was already there in the editor instead of being
drawn on top of black. So now it's drawn on top of black.
I want exiting No Death Mode to go back to the "play modes" menu, not to
the "start game" menu, because it's too far back. Also do the same if
you either die or complete No Death Mode.
Also I initialized Game::wasinintermission, probably a good thing to
initialize variables.
During testing, I made a cursed level that set the flash timer to
precisely 1,000,000 frames. It turns out that if I activated the timer
in playtesting, exited playtesting, and exited the editor without ever
re-entering playtesting, the timer still kept going. So to prevent being
able to do that, we should hardreset() when exiting the editor.
In-game because that's where screen effects are used the most. But on
the title screen, screen effects are used when you press ACTION to start
the game, and when you enable screen effects, too.
Otherwise, we don't need screen effects for any other game-gamestate.
This de-duplicates the screen effects rendering code by putting it
inside a function, Graphics::renderwithscreeneffects(), and using that
instead of copy-pasted code.
The code to decrement the timers for flashing and shaking is now handled
outside the game-gamestate case-switch, instead of having to be
duplicated inside each render function.
As a bonus, I made it so the timer decrements even if screen effects are
disabled. This is to prevent any theoretical situation where the timer
can "pile up" due to disabled screen effects not letting it tick down.
This removes a lot of duplicate code, which towerrender() mostly
consisted of, even though the only difference is that it draws a
separate map and screen edge spikes are drawn.
This removes lots of duplicated code that drawtowerentities() did,
because all that really changed was accounting for map.ypos (which can
be done conditionally) and where and when the room wrapped (which can
also be done conditionally).
This fixes an oddity that's only visual, which could only happen in
custom levels by using the createentity() internal command.
For the same reason that the second through fourth tiles of moving
platforms on the top and left was buggily rendered, SDL_BlitSurface()
strikes again to mutate the SDL_Rect we pass it and render the next
SDL_BlitSurface() call inbounds, even though we don't need it to.
Previously, the game could end up rendering a warping sprite twice due
to the fact that it could run "if entity is on the right side of the
screen" right after "if entity is on the left side of the screen" (but
not the other way around). This is most noticeable if you have a custom
player sprite with translucent pixels and stand on the left side of a
warping screen, but the code suggests it happens when warping through
the top of the screen, too.
Instead of doing
if (!obj.entities[i].invis)
{
...
}
It's better to do
if (obj.entities[i].invis)
{
continue;
}
...
It reduces the indentation by one level, which is always a good thing.
In the last commit, I removed having the flip mode conditional directly
inside the sprite-drawing code for each size type, which would reduce
the indentation one level. However, I opted to hold off un-indenting
until this commit, otherwise it would've produced too much noise.
The game uses flipsprites.png instead of sprites.png when in flip mode,
mostly to add exceptions for sprites that SHOULDN'T be flipped in flip
mode.
Looks like to achieve this, the routines for sprite drawing got
copy-and-pasted every single time flipsprites.png needed to be
conditionally used, resulting in large amounts of copy-pasted code. And
this copy-pasted code resulted in copy-paste errors, with relating to
VVVVVV-Man, because apparently due to two copy-pasting errors, the
combined giant crewmate in the epilogue uses flipsprites.png even if you
aren't in flip mode, and it also uses the width instead of the height of
sprites_rect when in flip mode (although, this doesn't end up mattering,
but still).
The solution here is to simply change the referenced sprites vector to a
pointer that can conditionally change based on the game being in flip
mode or not.
Looks like I forgot to test that my music silencing patch didn't break
the music being silent during the "You have found a shiny trinket" and
"You have found a shiny crewmate" text boxes. So I've added a check for
game.completestop in the music handling in main.cpp.
Found this bug while I was testing my towerlogic/gamelogic merge patch.