1
0
Fork 0
mirror of https://github.com/TerryCavanagh/VVVVVV.git synced 2024-12-23 10:09:43 +01:00
Commit graph

1179 commits

Author SHA1 Message Date
Misa
942217f871 Fix glitchy y-position when colliding with a conveyor
There is this issue with conveyors where if you collide with them, your
intended next y-position doesn't get updated to the position of the
conveyor, and then your y-position gets set to your intended next
y-position. This also applies to horizontally moving platforms.

This bug used to not produce any problems, if at all, until #502 got
merged. Since then, respawning from checkpoints that are on conveyors
would sometimes not update your y-position at all, making it possible to
get stuck inside a death loop that would require you to exit the game
and re-enter it.

But you can always reliably create this bug simply by going into the
editor and placing down a conveyor and checkpoint on top of each other.
Then enter and exit playtesting a bunch of times, and you'll notice the
glitchy y-position Viridian keeps taking on.

The root cause of this is how the game moves the player whenever they
stand on the top or bottom of a conveyor (or a horizontally moving
platform). The game sets their intended next x-position (newxp), then
calls obj.entitymapcollision() on them. This would be okay, except their
intended next y-position (newyp) doesn't get set along with their newxp,
so entitymapcollision() will use the wrong newyp, then find that there
is nothing that will collide with the player at that given newyp, then
update the yp of the player to the wrong newyp.

So, the platform logic simply doesn't set the player's newyp. Why does
the player have the wrong newyp? It's because moving platforms (and
conveyors) are updated first before all other entities are, and this
includes the code that checks the player for collisions with moving
platforms. That's right: the moving platform collision code gets ran
before the player properly gets updated. This means that the game will
use the newyp of the previous frame, which could be anything, really,
but it most likely just means the intended next y-position of the player
gets canceled, leaving the player with the same y-position they had
before.

Okay, but this bug only seems to happen when you put a checkpoint inside
a conveyor (or a moving platform that hasn't started moving yet) and
start playtesting from it, so why doesn't this bug happen more often,
then? Well, it's probably because of luck and coincidence. Most of the
time, if you're colliding with a conveyor or horizontally moving
platform, you probably have a correct newyp from the previous frame of
the game, so there'd be no problems. And before #502 got merged, this
previous frame would be provided by the player having to fall to the
surface due to the y-offset of their savepoint. However, if you make it
so that you immediately teleport on to a conveyor (because you died),
then this bug will rear its ugly head.
2020-10-31 19:07:58 -04:00
Dav999-v
70e82dfe12 Fix "name lookup of ‘i’ changed" warning in editor.cpp
I got this warning during compilation because there were two nested for
loops both defining i. Better to have different names to make sure some
compilers won't overwrite the outer variable with the inner one.
2020-10-13 22:39:12 -04:00
Misa
b8a4b4dfe7 Restore previous oldxp/oldyp variables in favor of lerpoldxp/lerpoldyp
I was investigating a desync in my Nova TAS, and it turns out that
the gravity line collision functions check for the `oldxp` and `oldyp`
of the player, i.e. their position on the previous frame, along with
their position on the current frame. So, if the player either collided
with the gravity line last frame or this frame, then the player collided
with the gravity line this frame.

Except, that's not actually true. It turns out that `oldxp` and `oldyp`
don't necessarily always correspond to the `xp` and `yp` of the player
on the previous frame. It turns out that your `oldyp` will be updated if
you stand on a vertically moving platform, before the gravity line
collision function gets ran. So, if you were colliding with a gravity
line on the previous frame, but you got moved out of there by a
vertically moving platform, then you just don't collide with the gravity
line at all.

However, this behavior changed in 2.3 after my over-30-FPS patch got
merged (#220). That patch took advantage of the existing `oldxp` and
`oldyp` entity attributes, and uses them to interpolate their positions
during rendering to make everything look real smooth.

Previously, `oldxp` and `oldyp` would both be updated in
`entityclass::updateentitylogic()`. However, I moved it in that patch to
update right before `gameinput()` in `main.cpp`.

As a result, `oldyp` no longer gets updated whenever the player stands
on a vertically moving platform. This ends up desyncing my TAS.

As expected, updating `oldyp` in `entityclass::movingplatformfix()` (the
function responsible for moving the player whenever they stand on a
vertically moving platform) makes it so that my TAS syncs, but the
visuals are glitchy when standing on a vertically moving platform. And
as much as I'd like to get rid of gravity lines checking for whether
you've collided with them on the previous frame, doing that desyncs my
TAS, too.

In the end, it seems like I should just leave `oldxp` and `oldyp` alone,
and switch to using dedicated variables that are never used in the
physics of the game. So I'm introducing `lerpoldxp` and `lerpoldyp`, and
replacing all instances of using `oldxp` and `oldyp` that my over-30-FPS
patch added, with `lerpoldxp` and `lerpoldyp` instead.

After doing this, and applying #503 as well, my Nova TAS syncs after
some minor but acceptable fixes with Viridian's walkingframe.
2020-10-11 16:19:26 -04:00
Misa
e8084fe699 Remove now-unused entityclass::hormovingplatformfix()
This function is no longer used after I removed it in favor of using
`entityclass::moveblockto()`.
2020-10-11 16:18:30 -04:00
Misa
99297659ee Restore platform evaluation order to 2.2
This commit restores the evaluation order of moving platforms and conveyors to
be what it was in 2.2. The evaluation order changed in 2.3 after the patchset
to improve the handling of the `obj.entities` and `obj.blocks` vectors (#191).

By evaluation order, I'm talking about the order in which platforms and
conveyors will be evaluated (and thus will take priority) if Viridian stands
on both a conveyor or platform at once, and they either have different speeds
or are pointing in different directions. Nowhere in the main game is there a
place where you can stand on two different conveyors/platforms at once, so
this is solely within the territory of custom levels, which is my specialty.

So what caused this evaluation order to change? Well, every moving platform
and conveyor in the game is actually made up of two objects: an entity, and a
block. The entity is the part that moves around, and the block is the part
that actually has the collision.

But if the entity is the part that moves around, and entities and blocks are
in entirely separate vectors, how is the block part going to move along with
it? Well, maybe you'd guess some sort of unique ID system, but spend some time
digging around the code and you won't find any trace of any (there's no
attribute on an entity to store such an ID, for starters).

Instead, what the game does is actually remove all blocks that coincide with
the exact top-left corner of the entity, and then create a new one. Destroying
and creating blocks like this all the time is hugely wasteful, but hey, it
worked.

So why did the evaluation order change in 2.3? Well, to understand that,
you'll need to understand 2.2's `active` system. Instead of having an object
be real simply by virtue of it existing, 2.2 had this system where the object
was only real if it had its `active` attribute set to true. In other words,
you would be looking at a fake object that didn't actually exist if its
`active` attribute was false.

On the surface, this doesn't seem that bad. But this can lead to "holes" in a
given vector of objects. A hole is simply an inactive object neighbored by
active objects (or the inactive object could be the first one in the vector,
but then have an active object immediately following it).

If you have a vector of 3 objects, all of them active, then removing the
second one will result in the vector containing an active object, followed by
an inactive object, followed by an active one. However, since the switch to
more properly use vectors instead of relying on this `active` system, there's
no longer any way for holes to exist in a vector. Properly removing an object
from a vector will just shift the rest of the objects down, so if we remove
the second object after the vector fix, then this will simply move the third
object into the slot of where the second object used to be.

So, what happens if you destroy a block and then create a new one in the
`active` system? Let's say that your `obj.blocks` looks like this, and here
I'm denoting each block by writing out its coordinates:

    [30,60] [70,90] [80,100]

and that you want to update the position of the second one, because the entity
that that blocks belongs to has been updated. Okay, so, you delete that block,
which then makes things look like this:

    [30,60] [-] [80,100]

and then afterwards, you create a new block with the updated position,
resulting in this:

    [30,60] [74,90] [80,100]

Since `entityclass::createblock()` will find the first block slot that has a
false `active` attribute, it puts the new object in the same slot as the old
one. What has been essentially done here is that the slot of the block has
basically been reserved for the new block with the new position. Here, the
evaluation order of each block will stay the same.

But then 2.3 comes along and changes things up. So we start with an
`obj.blocks` like this again:

    [30,60] [70,90] [80,100]

and we want to update the second block, like before. So we remove the second
block, resulting in this:

    [30,60] [80,100]

It should be obvious that unlike before, where the third block stayed in the
third slot, the third block has now been moved to the second slot. But
continuing on; we are now going to create the new block with its updated
position, resulting in this:

    [30,60] [80,100] [70,90]

At this point, we can see that the evaluation order of these blocks has been
changed due to the fact that the third block has now been moved to the slot
that was previously the slot of the second block.

So what can we do about this? Well, we can basically emulate what VVVVVV did
in 2.2, which is disable a block without actually removing it - except I'm not
going to reintroduce an `active` attribute or anything. I'll disable the
collision of all blocks at a certain position by setting their widths and
heights to 0, and then re-enable them later by finding the first block at that
same position, updating its position, and re-assigning its width and height
again.

The former is what `entityclass::nocollisionat()` does; the latter is what
`entityclass::moveblockto()` does. The former mimicks turning off the `active`
attribute of all blocks sharing a certain top-left corner; the latter mimicks
creating a new block - and it will only do this for one block, because
`entityclass::createblock()` in 2.2 only looked for the first block with a
false `active` attribute.

Now, some quirks relied on the previous behavior of destroying and creating
blocks, but all of these quirks have been preserved with the way I implemented
this fix.

The first quirk is that platforms passing through 0,0 will destroy all spike
hitboxes, script boxes, activity zones, and one-way hitboxes in the room. The
hitboxes of moving platforms, disappearing platforms, 1x1 quicksand, and
conveyors will not be affected.

This is a consequence of the fact that the former group uses the `x` and `y`
of their `rect`, while the latter group uses the `xp` and `yp` attributes. So
the `xp` and `yp` of the former are both 0. Meaning, a platform passing
through 0,0 destroys them all.

Having these separate coordinates seems like an artifact from the Flash days.
(And furthermore, there's an unused `x` and `y` attribute on all blocks,
making for technically three separate sets of coordinates! This should
probably be cleaned up, except for what I'm about to say...) But actually, if
you merge both sets of coordinates into one, this lets moving platforms
destroy script boxes and activity zones if it passes through the top-left
corner of them, which is probably far worse than the destruction being
localized to a specific coordinate that would never likely be reached
normally.

This quirk is preserved just fine without any special-casing, because instead
of destroying all blocks at 0,0, they just get disabled, which does the same
job. This quirk seems trivial to fix if I made it so that the position of a
platform's block was updated instantaneously instead of having one step to
disable it and another step to re-enable it, but I aim to preserve as much
quirks as possible.

The second quirk is that a moving platform passing through the top-left corner
of a disappearing platform or 1x1 quicksand will destroy the block of that
disappearing platform. This is because, again, when a moving platform updates,
it destroys all blocks at its previous position, not just only one block. This
is automatically preserved because this commit just disables the block of the
disappearing platform instead of removing it. Just like the last one, this
quirk seems extremely trivial to fix, and this time by simply making it so
`entityclass::nocollisionat()` would have a `break` statement, i.e. only
disabling the first block it finds instead of all blocks it finds, but I want
to keep all quirks that are possible to keep.

The last quirk is that, apparently, in order to prevent pushing the player
vertically out of a moving platform if they get inside of one, the game
destroys the block of the moving platform. If I had missed this edge case,
then the block would've been destroyed, leaving the moving platform with no
collision. But I caught it in my testing, so the block gets disabled instead
of destroyed. Also, it seems obtuse for those who don't understand why a
platform's block gets destroyed or disabled whenever the player collides with
it in `entityclass::collisioncheck()`, so I've put up a comment documenting it
as well.

The different platform evaluation order desyncs my Nova TAS, but after
applying this patchset and #504, my TAS syncs fine (save for the different
walkingframe from starting immediately on the ground instead of in the air
(#502), but that's minor and can be easily fixed).

I've attached a test level to the pull request for this commit (#503) to
demonstrate that this patchset not only fixes platform evaluation order, but
preserves some bugs and quirks with the existing block system.

The first room demonstrates the fixed platform evaluation order, by stepping
on the conveyors that both point into each other. In 2.2, Viridian will move
to the right of the background pillar, but in 2.3, Viridian will move to the
left of the pillar. However, after applying this patch, Viridian will now move
to the right of the pillar once again.

The second room demonstrates that the platform-passing-through-0,0 trick still
works (as explained above).

The last room demonstrates that platforms passing through the top-left corners
of disappearing platforms or 1x1 quicksand will remove the blocks of those
entities, causing Viridian to immediately pass through them. This trick is
still preserved after my patchset is applied.
2020-10-11 16:18:30 -04:00
Misa
c83132f4fa Add entityclass::moveblockto()
This function will restore a block's hitbox after it has been disabled
by `entityclass::nocollisionat()`.
2020-10-11 16:18:30 -04:00
Misa
d8cee4866e Add entityclass::nocollisionat()
This function will disable a block instead of removing it entirely,
mimicking the previous `active` system of 2.2.
2020-10-11 16:18:30 -04:00
Misa
b3b1a0696b Unindent entity update loops from previous commit
This unindent is done in a separate commit to minimize diff noise.
2020-10-11 16:18:30 -04:00
Misa
7e04908cd4 Collapse and invert if-statements in entity logic loops
This makes the code easier to read by reducing the amount of indentation
it has.
2020-10-11 16:18:30 -04:00
Misa
c56cf009e7 Use SDL_abs() instead of libc abs() in Logic.cpp
There are other instances where SDL_abs() should be used, but I'm fine
with at least fixing these for now.
2020-10-11 16:18:30 -04:00
Misa
71c8c54313 Clean up music handling to one place
Previously, setting the actual volume of the music was all over the
place. Which isn't bad, but when I added being able to press N to mute
the music specifically, I should've made it so that there would be a
volume variable somewhere that the game looks at if the music is
unmuted, and otherwise sets the actual volume to 0 if the game is muted.

This resulted in things like #400 and #505 and having to add a bunch of
special-cased checks like game.musicmuted and game.completestop. But
instead of adding a bunch of special-case code, just make it so there's
a central place where Mix_VolumeMusic() actually gets called, and if
some piece of code wants to set the music volume, they can set
music.musicVolume. But the music handling logic in main.cpp gets the
final say on whether to listen to music.musicVolume, or to mute the game
entirely.

This is how the music handling code should have been from the start
(when pressing N to mute the music was added).

Fixes #505.
2020-10-11 16:16:57 -04:00
Misa
d0b3cfa08c Don't hardcode MIX_MAX_VOLUME value of 128
The value of the macro might not change in the future, but it's there
for a reason. That reason being to improve code readability, because
otherwise 128 would just be a magic number that plopped in out of
nowhere. Sometimes the game uses MIX_MAX_VOLUME, other times it uses
128, so to be consistent I'm just going to enforce MIX_MAX_VOLUME
entirely.
2020-10-11 16:16:57 -04:00
Misa
ba04c361c6 Revert "Fix #400"
This reverts commit cf5ad166e3.

My implementation will make it so single-case patches like this commit
won't be necessary anymore (there's no need to add a special-case check
for game.musicmuted, the way that I'm gonna do it). In fact, it's better
if I just revert the commit entirely.
2020-10-11 16:16:57 -04:00
Misa
7b7c7b2dc7 Fix inconsistencies with y-positions of spawns
It seems like the start point of a custom level and all checkpoints in
the game end up putting your spawn point one pixel away from the surface
it touches, which seems like an oversight. I'm going to enforce some
consistency here and make it so that all spawn points, whenever you
start from a start point or a checkpoint, will always be correctly
positioned flush with the surface they're standing on, and not one pixel
more or less than that.
2020-10-06 02:24:51 -04:00
Misa
dfdad165f5 Fix spawning from an exotic checkpoint setting invalid gravitycontrol
An exotic checkpoint is a checkpoint with a sprite that is neither the
flipped checkpoint nor unflipped checkpoint. More specifically, it's a
checkpoint whose edentity p1 attribute is something other than 0 or 1.

Normally, whenever you touch an exotic checkpoint in-game, your
savepoint's y-position and gravitycontrol don't get touched. However, in
the editor, spawning from an exotic checkpoint means that your
gravitycontrol gets set to a value that is neither 0 nor 1. In this
invalid gravitycontrol state, Viridian is treated like they're flipped,
but they cannot unflip.

This is an inconsistency between the editor and in-game, so I'm fixing
it. Now, spawning from an exotic checkpoint in the editor will just set
your gravitycontrol to 0, i.e. unflipped.
2020-10-06 02:24:51 -04:00
Misa
b4a0acc01d Fix onground/onroof taking 1 frame after landing on vertical plat
So, 77a636509b fixed the fact that you
only got 1 frame of onground/onroof when standing on a vertical moving
platform, but removing those lines entirely means that it takes 1 frame
before the onground/onroof of 2 actually takes effect. This desyncs my
Nova TAS, so it seems important to fix.
2020-10-06 02:16:49 -04:00
Misa
77a636509b Remove duplicate onroof/onground assignment for vertical moving plats
The onroof/onground attributes are used to determine if the player is
standing on a surface and is eligible to flip. Most notably, it is an
integer and not a boolean, and it starts at 2, giving the player 2
frames to edge-flip, i.e. they can still flip 2 frames after walking off
an edge.

However, these attributes are unnecessarily reassigned in
movingplatformfix() (which is the function that deals exclusively with
vertically-moving platforms; horizontal moving platforms get their own
hormovingplatformfix()). Whoever wrote this misunderstood what
onroof/onground meant; they thought that they were booleans, and so set
them to true, instead of the proper value of 2. This ends up setting
onroof/onground to 1 instead of 2, causing a discrepancy with vertical
moving platforms and the rest of the surfaces in the game.

The bigger mistake here is duplicating code that never needed to be
duplicated. The onroof/onground assignment in gamelogic() works
perfectly fine for vertical moving platforms. Indeed, after testing it
with libTAS, I can confirm that removing the duplicate assignments
restores being able to edge-flip off of moving platforms with 2 frames
of leeway, instead of only 1 frame. It also doesn't change how long it
takes for the onroof/onground to get set when the player is recognized
as standing on a vertically-moving platform, either.

And so, it's better to not duplicate this code, because when you
duplicate it you run the risk of making a mistake, as I just
demonstrated.
2020-09-28 15:46:41 -04:00
Misa
733cad723b Remove unused attributes from entclass
These attributes were `jumping` and `jumpframe`. Removal of unused
attributes makes reading the code easier.
2020-09-28 15:45:53 -04:00
Misa
cbceeccf78 Clean up and prevent unnecessary qualifiers to self
By "unnecessary qualifiers to self", I mean something like using the
'game.' qualifier for a variable on the Game class when you're inside a
function on the Game class itself. This patch is to enforce consistency
as most of the code doesn't have these unnecessary qualifiers.

To prevent further unnecessary qualifiers to self, I made it so the
extern in each header file can be omitted by using a define. That way,
if someone writes something referring to 'game.' on a Game function,
there will be a compile error.

However, if you really need to have a reference to the global name, and
you're within the same .cpp file as the implementation of that object,
you can just do the extern at the function-level. A good example of this
is editorinput()/editorrender()/editorlogic() in editor.cpp. In my
opinion, they should probably be split off into their own separate file
because editor.cpp is getting way too big, but this will do for now.
2020-09-28 01:34:40 -04:00
Misa
571ad1f7d8 Move all temporary variables off of entityclass
This is a refactor that simply moves all temporary variables off of
entityclass, and makes it so they are no longer global variables. This
makes the resulting code easier to understand as it is less entangled
with global state.

These attributes were:
 - colpoint1
 - colpoint2
 - tempx
 - tempy
 - tempw
 - temph
 - temp
 - temp2
 - tpx1
 - tpy1
 - tpx2
 - tpy2
 - temprect
 - temprect2
 - x (actually unused)
 - dx
 - dy
 - dr
 - px
 - py
 - linetemp
 - activetrigger
 - skipblocks
 - skipdirblocks

Most of these attributes were assigned before any of the times they were
used, so it's easy to prove that ungloballing them won't change any
behaviors. However, dx, dy, dr, and skipblocks are a bit more tricky to
analyze. They relate to blocks, with dx, dy, and dr more specifically
relating to one-way tiles. So after some testing with the quirks of
one-way tiles, it seems that the jankiness of one-way tiles haven't
changed at all, either.

Unfortunately, the attribute k is clearly used without being assigned
beforehand, so I can't move it off of entityclass. It's the same story
with the attribute k that Graphics has, too.
2020-09-27 19:08:37 -04:00
Misa
695e720c1c Prevent typing pipes in the script editor
This prevents users from being confused whenever they type a pipe in the
script editor, then save the level and load it again and see their
script lines unexpectedly splitting in two. Now if you attempt to type a
pipe, it simply won't happen at all.

Fixes #379.
2020-09-27 18:45:02 -04:00
Misa
25f27d502a Prevent undefined behavior with integer under/overflow with help.Int()
It's possible that SDL_atoi() could call the libc atoi(), and if a
string is provided that's too large to fit into an integer, then that
would result in undefined behavior. To avoid this, use SDL_strtol()
instead.
2020-09-27 17:05:00 -04:00
Misa
605b8a427c Make 'custom_' check more readable
Instead of copying to a temporary string, just use SDL_strncmp(). Also,
I checked the blame, and apparently I committed the line that used
strcmp() instead of SDL_strcmp(), for whatever reason. But that's fixed
now.
2020-09-27 16:31:40 -04:00
Misa
fe56764fbc Don't use bounds check for checktrigger() in twoframedelayfix()
This is like the previous patch, but for twoframedelayfix(), because I
forgot to read the comment that talked about twoframedelayfix().
2020-09-25 18:07:19 -04:00
Misa
c8f000af02 Don't use bounds check for result of checktrigger(), it's a gamestate
checktrigger() returns a gamestate number, not the index of an entity.

Whoops.
2020-09-25 18:00:18 -04:00
Misa
a38faad156 Add bounds checks to indexing of global "temporary" variable k
For some reason, the variable `k` is on entityclass and gets mutated in
createentity() and createblock(). Then updateentities() uses it without
checking if it's valid, because either `k` or the size of `entities`
could have changed in the meantime. To fix any potential undefined
behavior, these bounds checks should be added.
2020-09-25 17:16:15 -04:00
Misa
990ee63a6e Fix brace style in Graphics::Makebfont() and Graphics::bfontlen()
Seriously, Leo, why did you have to use braces like this...
2020-09-25 16:33:54 -04:00
Misa
2e78eab92f Clear font_positions if there is no font.txt
This fixes a bug where font_positions wouldn't get cleared after exiting
a custom level that had a font.txt if it didn't exist in the default
graphics, leading to messed-up-looking font rendering.

This bug was originally discovered by Ally.
2020-09-25 16:33:54 -04:00
Misa
96be0fc7a9 Fix unwinnable save from rescuing Violet out of order
You're intended to rescue Violet first, and not second, third, or
fourth, and especially not last.

If you rescue her second, third, or fourth, your crewmate progress will
be reset, but you won't be able to re-rescue them again. This is because
Vitellary, Verdigris, Victoria, and Vermilion will be temporarily marked
as rescued during the `bigopenworld` cutscene, so duplicate versions of
them don't spawn during the cutscene, and then will be marked as missing
later to undo it.

This first issue can be trivially fixed by simply toggling flags to
prevent duplicates of them from spawning during the cutscene instead of
fiddling with their rescue statuses.

However, there is still another issue. If you rescue Violet last, then
you won't be warped to the Final Level, meaning you can't properly
complete the game. This can be fixed by adding a `crewrescued() == 6`
check to the Space Station 1 Level Complete cutscene. There is
additionally a temporary unrescuing of Violet so she doesn't get
duplicated during the `bigopenworld` cutscene, and I've had to move that
to the start of the `bigopenworld` and `bigopenworldskip` scripts,
otherwise the `crewrescued() == 6` check won't work properly.

I haven't added hooks for Intermission 1 or 2 because you're not really
meant to play the intermissions with Violet (but you probably could
anyway, there'd just be no dialogue).

Oh, and the pre-Final Level cutscene expects the player to already be
hidden before it starts playing, but if you rescue Violet last the
player is still visible, so I've fixed that. But there still ends up
being two Violets, so I'll probably replace it with a special cutscene
later that's not so nonsensical.
2020-09-25 14:02:09 -04:00
Misa
3ffe1b2e3b Add bounds-check to entcolours in editor rendering
I have no idea why neither conveyors and moving and disappearing
platforms rendered in the editor or in-game use
Graphics::drawentcolours(), but this needs to be bounds-checked just as
I did for the in-game rendering function.
2020-09-25 13:51:47 -04:00
Misa
c325085ddb Fix up trinket and coin ID bounds checks
Trinket IDs weren't being bounds-checked upon creation, and coin IDs
weren't being bounds-checked upon pickup. But now they both are.
2020-09-25 13:51:47 -04:00
Misa
140861a79d Add bounds checks to both versions of Graphics::drawsprite()
For some reason, I forgot to add bounds checks to these functions. Well,
I'm glad I caught it, anyways.
2020-09-25 13:51:47 -04:00
Misa
76d6a3536b Bounds check all entity getters that can return 0
The entity getters I'm referring to are entityclass::getscm(),
entityclass::getlineat(), entityclass::getcrewman(), and
entityclass::getcustomcrewman().

Even though the player should always exist, and the player should always
be indice 0, I wouldn't want to make that assumption. I've been wrong
before.

Also, these functions returning 0 lull you into a false sense of
security. If you assume that commands using these functions are fine,
you'll forget about the fact that `i` in those commands could be
potentially anything, given an invalid argument. In fact, it's possible
to index createactivityzone(), flipgravity(), and customposition()
out-of-bounds by setting `i` to anything! Well, WAS possible. I fixed it
so now they can't.

Furthermore, in the game.scmmoveme block in gamelogic(), obj.getplayer()
wasn't even checked, even though it's been checked in all other places.
I only caught it just now because I wanted to bounds-check all usages of
obj.getscm(), too, and that game.scmmove block also used obj.getscm()
without bounds-checking it as well.
2020-09-25 13:51:47 -04:00
Misa
19a8352775 Don't hardcode 400 in editorclass::save()
That way, if the size of `level` changes, this code won't end up being
wrong.
2020-09-25 13:51:47 -04:00
Misa
f02dcbfdad Don't manually write out INBOUNDS_ARR() checks
When this is done, there is potential for a mistake to occur when
writing out the bounds check, which is eliminated when using the macro
instead. Luckily, this doesn't seem to have happened, but what's even
worse is I hardcoded 400 instead of using SDL_arraysize(ed.level), so if
the size of ed.level the bounds checks would all be wrong, which
wouldn't be good. But that's fixed now, too.
2020-09-25 13:51:47 -04:00
Misa
7b20d90446 Don't manually write out INBOUNDS_VEC() checks
This is because if they are manually written out, they are more likely
to contain mistakes.

In fact, after further review, there are several functions with
incorrect manually-written bounds checks:
 * entityclass::entitycollide()
 * entityclass::removeentity()
 * entityclass::removeblock()
 * entityclass::copylinecross()
 * entityclass::revertlinecross()

All of those functions forgot to do 'greater than or equal to' instead
of 'greater than' when comparing against the size of the vector. So they
were erroneous. But they are now fixed.
2020-09-25 13:51:47 -04:00
Misa
32b6de729d Use SDL_arraysize() instead of writing out array length
Using the macro makes it less likely that there will be a mistake when
writing out the array length.
2020-09-25 13:51:47 -04:00
Misa
b34be3f1ac Use explicit INBOUNDS_VEC() instead of checking sentinel -1
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".
2020-09-25 13:51:47 -04:00
Misa
b925352864 Reduce scope of 'j' in screen transition companion logic
Reducing the scope of the variable makes analysis easier - for example,
we can now see it is always initialized before being used.
2020-09-25 13:51:47 -04:00
Misa
a098448e89 Remove unused variable from gamestate 115
For some reason it called obj.getplayer() and did nothing with the
result. Weird. But it does say "Test script for space station" above.

Removing this fixes an 'unused variable' warning.
2020-09-25 13:51:47 -04:00
Misa
fad0071966 Reduce scope of temporary variables in Game::updatestate()
With the scope of these variables reduced, it makes analyzing this
function easier, as you can now clearly see all temporary variables are
actually initialized before they're used.
2020-09-25 13:51:47 -04:00
Misa
7ed495c373 Rename INBOUNDS() macro to INBOUNDS_VEC()
Since there's an INBOUNDS_ARR() macro, it's much better to specify the
macro for the vector is a macro for the vector, to avoid confusion.

All usages of this macro have been renamed accordingly.
2020-09-25 13:51:47 -04:00
Misa
fae14f4e98 De-duplicate stuck prevention for the player/SCM
Stuck prevention (pushing the player/supercrewmate out if they are
inside a wall) has been factored out into its own function, so it's no
longer copy-pasted but slightly tweaked just for the supercrewmate.
2020-09-25 13:37:38 -04:00
Misa
5e29d676e9 Use case-switch for entityclass::collisioncheck()
This makes each case in the function less verbose.
2020-09-25 13:37:38 -04:00
Misa
b5806c8bb0 De-duplicate entity collision checks for player/SCM
The entity collision check routine has been factored out into its own
function, so it no longer needs to be copy-pasted for the supercrewmate.
2020-09-25 13:37:38 -04:00
Misa
ceaee392e5 De-duplicate vertical moving platform fix for player/SCM
Instead of having two separate functions to move entities along vertical
moving platforms, one for the player and one for the supercrewmate, they
have been consolidated into one function.
2020-09-25 13:37:38 -04:00
Misa
1c5b72410a De-duplicate spike hitbox checks for player/SCM
The spike hitbox check is now one function for both the player and the
supercrewmate, instead of being two separate functions.
2020-09-25 13:37:38 -04:00
Ally
46049705f4
Fix entities in the Warp Zone's gray tileset not being gray in the editor (#480)
In-level, they were made to be gray in #323. The editor does not reflect this however; they're still shown as
green. For the same reasons in #323, this adds special cases to draw the entities as gray.
Closes #372.
Also, I changed my name in contributors.txt to be my username as I didn't feel comfortable with it being my name.

Co-authored-by: Misa <ness.of.onett.earthbound@gmail.com>
2020-09-25 13:35:03 -04:00
Misa
7fcc1c8cdc De-duplicate reading/writing main game telesave/quicksave
The game has four different functions for the same XML schema:
Game::loadtele(), Game::savetele(), Game::loadquick(), and
Game::savequick(). This essentially means one XML schema has been
copy-pasted three different times.

I can at least trim that number down to being copy-pasted only one time
by de-duplicating the reading and writing part. So both Game::loadtele()
and Game::loadquick() now use Game::readmaingamesave(), and
Game::savetele() and Game::savequick() now use
Game::writemaingamesave().

This will make it take less work to add XML forwards compatibility
(#373).
2020-09-25 13:32:41 -04:00
Misa
e01efbec89 Remove tzann's trailing whitespace from Graphics.cpp
This trailing whitespace was introduced by tzann in commit
8aac6a758d. It might be because he used
Visual Studio.
2020-09-25 13:30:59 -04:00
Misa
1a3577e196 Add kludge to fix rendering of gravity lines in a gotoroom
Due to #464, standing inside a gravity line during a gotoroom that
occurs every frame will end up with the gravity line being gray instead
of being white. To temporarily fix this (until #464 is properly fixed),
I decided to add some kludge that colors it white if its onentity is 1.
I tested this patch with gravity lines in both constant-gotoroom and
normal environments, and it seems to be fine for both.
2020-09-25 13:30:59 -04:00
Misa
d1fd910bdb Fix flipgravity() rule conversion being inverted
In 2.0, 2.1, and 2.2, calling flipgravity() on an entity that wasn't
rule 6 would change it to rule 7. In 2.3 currently, doing this will only
change it to rule 7 if it's already rule 6, starting with the
introduction of the change where if an entity was rule 7 it would be
changed to rule 6.

The crewmate conversion trick has been restored, but converting an
entity to a crewmate will change its rule to 6, not 7 like in pre-2.3.
If you want it to be changed to rule 7 instead of 6, you'd have to call
flipgravity() twice in 2.3 and only once in pre-2.3, which would make
maintaining compatibility between versions a bit harder.

So to fix this, I'm inverting it so that if you call flipgravity() on an
entity that isn't rule 7, it will be converted to rule 7, and only if
it's rule 7 will it be converted to rule 6.
2020-09-24 02:47:53 -04:00
Misa
5b9a8e5f08 Allow calling flipgravity() on duplicate player entities
This is a followup to b7cf6855b0 and
10ed0058fd.

In 2.2, if you had a duplicate player entity, there'd be no way to get
rid of it. Except for the recently-discovered Arbitrary Entity
Manipulation glitch, where you set `i` to the indice of the entity and
call flipgravity() on it, turning its rule to 7 and making it no longer
a player entity.

However, I patched this useful mechanic out when I made it so that you'd
no longer be able to convert entities with rule 0 to rule 6
(10ed0058fd, upheld in
b7cf6855b0), because doing so would mean
being able to softlock the game by not having any player entity.

So, in this patch, I'm making it so that you CAN convert duplicate
player entities to crewmates (and thus basically destroy them), but you
can't do that to the TRUE player entity (i.e. the first entity indice
that has rule 0, which is basically always indice 0).
2020-09-17 23:49:00 -04:00
Misa
42010f8f42 Fix default Graphics::setcol() value having no alpha
This patch fixes a regression caused by commit
6b1a7ebce6.

When you spawn a crewmate with an invalid color, by doing something like
`createentity(100,100,18,-1,0)` (here the color is -1, which is
invalid), a white crewmate with the color of solid white (255, 255, 255)
would appear.

That is, until AllyTally came along and committed commit
6b1a7ebce6 (Make "[Press ENTER to
return to editor]" fade out after a bit) (PR #158). Then after that
commit, it would seem like the crewmate didn't appear, but in reality
they were just invisible, because they had an invisible color.

As part of Ally's changes, to properly support drawing text with a
certain amount of alpha, she made BlitSurfaceColoured() account for the
alpha of the color given instead of only caring about the RGB of the
color, discarding the alpha, and using the alpha of the surface it was
drawing instead. This effectively made it so the alpha of whatever it
was drawing would be 255 all the time, except for if you had custom
textures and your custom textures had translucent pixels.

However, the default color set by Graphics::setcol() if you didn't
provide a valid color index was 0xFFFFFF. Which is only (255, 255, 255)
but ends up having an alpha value of 0 (because it's actually
0x00FFFFFF). And all colors drawn with alpha 0 end up being drawn with
alpha 0 after 6b1a7ebce6. So
invalid-colored entities will end up being invisible.

To fix this, I just decided to add alpha to the default value instead.
In addition, I used getRGB() to be consistent with all the other colors
in the function.
2020-09-17 10:13:46 -04:00
MAO3J1m0Op
03abe10f67
Add space after lines terminated by a colon in scripts (#479) 2020-09-14 20:34:12 -04:00
Misa
dcf1a81e80 Fix duplicate levels in levelstats if you don't have a higher score
This is a regression from 25779606b4
(PR #74).

On the one hand, I should've thought this through carefully when
implementing the fix for the lower-score overwrite bug. On the other
hand, flibit didn't seem to notice either. And on the third hand, no one
else seems to have noticed, when they have had over 8 months to do so.
Not even the person who originally reported the lower-score overwrite
bug and also reported other bugs I hadn't noticed (e.g. the "You have
rescued a crewmate!" in Flip Mode) noticed this bug, which I believe was
weee50. But to be fair, he does seem to be less active nowadays.

On the fourth hand, I only realized the cause of the duplicate bug after
stepping through it in GDB, instead of just looking at it and going "hey
wait a minute" earlier. I'm surprised it didn't take me longer to
realize the problem.

I'm not sure what all these hands mean anymore, or where I'm getting
extra hands from. Whatever. This regression is fixed now.
2020-09-13 01:33:13 -04:00
Misa
1d40bbdbc7 Restore pre-2.1 warp bypass glitch in glitchrunner mode
So, I was staring at VVVVVV code one day, as I usually do, and I noticed
that warp lines had this curious code in entityclass::updateentities()
that set their statedelay to 2, and I thought, hm, maybe the pre-2.1
warp line bypass is caused by this statedelay. And, it doesn't seem like
this is the primary code used to detect if the player collides with warp
lines, the actual code is commented with "Rewritten system for mobile
update" and bolted-on in gamelogic() instead of properly being in
entityclass::entitycollisioncheck().

So, after getting tripped up on the misleading indentation of that
"Rewritten system" block, I removed the rewritten system, re-added
collision detection for rule 7 (horizontal warp lines), and after
checking the resulting behavior, it appears to be nearly identical to
that of warp lines in 2.0.

You see, if you use warp lines to flip up from the top of the screen
onto the bottom of the screen, close to the edge of the bottom of the
screen, Viridian's head will display on the top of the screen in 2.0. In
2.1 and later, this doesn't happen, confirming that my theory is
correct. I also performed warp line bypass multiple times in 2.0 and
with my restored code, and it is pretty much the exact same behavior.

So now, the pre-2.1 warp line bypass glitch has been re-enabled in
glitchrunner mode.
2020-09-09 22:05:43 -04:00
Misa
23434f0842 Fix misleading indentation in obj.customwarpmode block
This misleading indentation makes it really easy to misanalyze this
block as only containing the statements up to obj.customwarplinecheck(),
when in reality it contains everything up to the modifying of
map.warpx/map.warpy. I have made this misanalysis just now in my attempt
to figure out the pre-2.1 warp line bypass glitch, and I don't like it.
So I'm fixing this indentation now.
2020-09-09 22:05:43 -04:00
Misa
b7cf6855b0 Restore entity-to-crewmate conversion trick
So there's this trick that I recently discovered, since many script
commands don't initialize `i` it's possible to use them to manipulate
arbitrary entities by specifying their indice.

This means in 2.2 you can convert entities to pseudo-crewmates by
changing their rule to 6. Except in 2.3, this was fixed when I fixed the
command to work on flipped crewmates as well. So I'm restoring this
functionality, but I recognize the protection that my previous change to
the command did in preventing levels from destroying the player entity
by changing the player's rule to something nonzero, so instead of
removing the if-conditional entirely, I'm making it so that it will only
set the rule if the entity's rule isn't 0.
2020-09-09 10:02:09 -04:00
Misa
760f1e8e5a Don't hardcode SDL_CreateRGBSurface BitsPerPixel/mask args
For some reason, GetSubSurface() and ApplyFilter() were hardcoding the
bits-per-pixel and/or mask arguments to SDL_CreateRGBSurface(). I've
made them simply re-use the bits-per-pixel and masks of the input
surfaces they operate on, but the bits-per-pixel should always be 32 and
masks should always go first-byte alpha, second-byte red, third-byte
green, fourth-byte blue.
2020-09-08 16:16:40 -04:00
Misa
8af9007b5f Axe SDL_BYTEORDER checks
The game usually runs on little-endian anyways, but even if it did run
on big-endian, it doesn't check endianness everywhere so these checks
are useless. Furthermore, the code should be written so that endianness
doesn't matter in the first place.
2020-09-08 16:16:40 -04:00
Misa
68a54d2594 Axe endian_swap() function and template
Neither of these were used anywhere, so to simplify the code and prevent
having potentially broken code that's never shown to be broken because
it's never tested, I'm removing these.
2020-09-08 16:16:40 -04:00
Misa
90150a9fad Use clamp() macro to clamp colors in Graphics::bigrprint()
All other graphics functions use this macro instead of writing out the
clamping manually, so this one should, too.
2020-09-08 16:15:50 -04:00
Misa
efc9d4ea80 Remove duplicate color-clamping code from Graphics::bigrprint()
For some reason, there's some color-clamping code in this function
directly after already-existing color-clamping code. This code dates
back to 2.2. And also, there's a smaller lower-bound of -1 for the red
channel, despite the fact that this smaller value doesn't matter because
the red would get clamped to 0 by the first code anyway.

So even if this was put here for some strange reason, it doesn't matter
because it doesn't do anything anyway.
2020-09-08 16:15:50 -04:00
Misa
a6a8173e20 Prevent same-frame infinite loops in scripts
It's trivially easy to send the scripting system into an infinite loop
on the same frame (i.e. without any script delay in between, i.e. within
the same execution of script.run()). Just take a look at these two
scripts:

    a:
    iftrinkets(0,b)
    #

    b:
    iftrinkets(0,a)
    #

The hashes are to prevent the scripting system from parsing iftrinkets()
using the internal version instead of the simplified version, because
after doing a simplified iftrinkets(), the parser will (to oversimplify)
execute the last line of the script as internal.

Anyway, sending the game into an infinite loop like this will cause the
Not Responding dialog on Windows.

So to prevent this from happening, I've added an execution counter to
scriptclass::run(). If it gets too high, we're in an infinite loop and
so we stop running the script.
2020-09-08 15:57:21 -04:00
Misa
662a658cf6 Optimize entity collision checking to O(n)
I noticed that if I have a large amount of entities in the room, the
game starts to freeze and one frame would take a very long time. I
identified an obvious cause of this, which is that the entity collision
checking in entityclass::entitycollisioncheck() is O(n²), n being the
number of entities in the room.

But it doesn't need to be O(n²). The only entities you need to check
against all other entities are the player and the supercrewmate. You
don't need to "test entity to entity" if 99% of the pairs of entities
you're checking don't involve the player or supercrewmate.

How do we make it O(n)? Well, just hoist the rule 0 and type 14 checks
out of the inner for-loop. That way, the inner for-loop won't be
unconditionally ran, meaning that in most cases it will always be O(n).
However, if you start having large amounts of duplicate player or
supercrewmate entities (I don't know why you would), it would start
approaching O(n²), but I feel like that's fair in that case. But most of
the time, it will be O(n).

So that's how collision checking is now O(n) instead.
2020-09-07 23:03:34 -04:00
Misa
d4336b9a33 Hoist Enter check out of script.running clause
This fixes the fact that an Enter release during a "- Press ACTION to
advacne text -" prompt wouldn't be properly registered.
2020-09-07 20:47:33 -04:00
Misa
69337009b8 Remove fademode conditional from map screen
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.
2020-09-07 20:47:33 -04:00
Misa
fd19b4e5d8 Hoist Enter/Esc presses out of player entity for-loop
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.
2020-09-07 20:47:33 -04:00
Misa
610f4e7782 Move crewmate drawframe fix to entity creation instead of loadlevel
This makes it so the fix for crewmates' drawframes being wrong for
1-frame is fixed for all crewmates regardless of when they get created.
Sure, crewmates created in mapclass::loadlevel() have their drawframes
fixed there, but for crewmates that get created from scripting (such as
Violet when gotorooming to the Ship teleporter room after Space Station
1), this fix doesn't apply to them. But now it does, and Violet will no
longer be facing the wrong way for 1 frame when teleporting to the Ship
teleporter room in the Space Station 1 Level Complete cutscene.
2020-09-07 20:46:01 -04:00
Terry Cavanagh
f014ed6fe8
Merge pull request #467 from InfoTeddy/general-improvements
Remove checks to prevent having both warp lines/BGs in editor
2020-09-06 14:46:56 +10:30
Misa
9de3aba94b Fix sprite of player if they are stuck in a wall
If the player is stuck in a wall (which shouldn't happen in the first
place), their sprite would always default to being flipped, even if they
were unflipped.

Being stuck in a wall is characterized by having both positive onfloor
and onground.
2020-09-06 00:13:16 -04:00
Misa
d677ba3837 Remove checks to prevent having both warp lines/BGs in editor
It's perfectly acceptable to have both warp lines and a warping
background in the same room. Many levels do this exact thing, I would
say at least 30 or so levels, many of them popular and played by many,
and this has never caused any issues at all.

All that having both warp lines and warp BG does is make it so the
warping of the warping background gets overriden by the warp lines, but
make it so the background is still a warp background. So in effect, you
can have a warp background without any warping. This is perfectly
defined behavior. Except, for whatever reason, it's unintentional, and
the editor tries to prevent you from doing it.

Key word being "tries". The code to prevent having both warp types is
bugged (at least when you change the warp BG. The check when you place
warp lines seems to be solid). It compares the p1 and p2 attributes of
warp lines to the x-coordinate and y-coordinate of the room, despite p1
and p2 having nothing to do with room coordinates. p1 is the type of the
warp line and should be treated as an enum, and p2 is the offset of the
warp line from the top/left of the screen. This results in this check
sometimes working if you're unlucky, but never actually working properly
most of the time. This means people can first place warp lines, and then
change the warp background later, to have both warp lines and a warp
background.

Having these checks just further complicates the code, makes it more
error-prone, and just inconveniences people when they want to do
something that's perfectly fine to do. So it's best if we just remove
these checks.
2020-09-05 20:02:27 -07:00
Misa
6658a46b2e Fix 1-frame render glitch with running teleport scripts
Fixes #402 (Violet appearing 1 frame after the Ship teleporter room
appears).

The root cause of this bug is due to the game loop order changes made
with the over-30-FPS patch. 2.2's game loop order was gameinput(),
gamerender(), then gamelogic(). In 2.3, gamerender() was moved to the end
as it required special code to render more than 30 frames a second. So
2.3's game loop order is gameinput(), gamelogic(), then gamerender().

In hindsight, I could have preserved the game loop order, but this would
require some more complex code in the game loop than what is there
currently. Fixing it now would fix rendering glitches such as this one
(along with being able to remove script.dontrunnextframe with the
two-frame-delay fix), but it could also introduce new rendering glitches
we don't yet know about. After discussing this in Discord DMs with
flibit, we agreed that the game loop order should be fixed in 2.4
instead.

When the game teleports you, gamelogic() runs script.teleport(). This
function will gotoroom to the teleporter destination, then it loads the
teleport script. Some teleport scripts (such as levelonecomplete, which
creates Violet) expect that their entities will be created, and more
generally that their script will be ran, on the same frame that the
gotoroom happens, i.e. by the time that the next gamerender() happens,
i.e. script.run() should be ran before the next gamerender() happens.
This would be true on the old game loop order, but with the new game
loop order, gamerender() gets ran directly after gamelogic() with no
script.run() in between.

To fix this, I did the same thing I did with the two-frame-delay fix
(#317), where I ran the script for that frame, but in order to prevent
running it twice I set script.dontrunnextframe to true.
2020-09-05 18:17:59 -04:00
Misa
c29d7c7d14 De-duplicate activity zone rendering, don't ignore act_fade
This is a follow-up to #421.

The game would draw the activity zone if there was one active at all,
and would ignore game.act_fade. Normally this wouldn't be a problem, but
since #421, it's possible that there could be an active activity zone
but game.act_fade would still be 5. This would happen if you entered a
new activity zone while a cutscene was running.

To fix this, just make it so that the prompt gets drawn on its own and
only depends on the state of game.act_fade (or game.prev_act_fade), and
won't be unconditionally drawn if there's an active activity zone. As a
bonus, this de-duplicates the drawing of the activity prompt, so it's no
longer copy-pasted.
2020-08-24 22:02:58 -04:00
Misa
cf53de9ed5 Move text entry start to script open instead of script list open
This fixes a bug where opening a script, then closing the script but not
exiting the script list, then opening a script again (it can be the same
script as before) would result in you being unable to type in the
script. You would have to close the script, then close the script list,
then re-open the script list in order to be able to type properly.

This was caused by the fact that key.enabletextentry() was being called
when the script list was opened, but key.disabletextentry() was being
called when closing a script. So the fix for this is to simply move the
key.enabletextentry() to its proper place, which is when the script is
being opened, and not when the script list is.
2020-08-17 19:30:09 -04:00
Misa
743eb3f3d6 Add custommode check to mapclass::twoframedelayfix()
This is to ensure that this function only has an effect inside custom
levels. It might do something weird or bad in the main game.
2020-08-17 15:14:22 -04:00
Misa
77b47a3c7e Fix compatibility with levels that rely on gamestate-based script boxes
Some levels rely specifically on the fact that certain script boxes are
loaded using gamestates, instead of directly loading the script and
bypassing the gamestate system. Then weird things could happen. This
restores compatibility with those levels.

mapclass::twoframedelayfix() doesn't need to be updated because the
point of that function is to bypass the gamestate system entirely
anyways.
2020-08-17 15:14:22 -04:00
Misa
fb6635abec Remove script.hardreset() when restarting Time Trial
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.
2020-08-14 23:07:02 -04:00
Misa
c95b95f5b7 Check rw in musicclass::init()
It's possible that SDL_RWFromMem could return NULL, and if this is the
case, then we really shouldn't be passing it to MusicTrack().
2020-08-14 09:51:19 -04:00
Misa
6bed5fc88c Check divisor in musicclass::play()
Previously, it was assuming that the number of PPPPPP/MMMMMM tracks
would always be 16, since if that wasn't the case... then the game would
rudely and abruptly segfault when attempting to load the file. Huh.

But now that the game properly deals with invalid headers, it's possible
for the number of tracks to be 0. So I'll need to remove this
assumption.
2020-08-14 09:51:19 -04:00
Misa
4bfd9de371 Check index of tracks in musicclass::init()
It's possible that musicReadBlob.getIndex() could return the sentinel
value of -1 in case the header with that name is invalid, in which case
we should simply not do anything. Otherwise it'll lead to segfaults. I
opted to do the full bounds check just to be safe, too.

For further safety, I hardcoded the max number of headers, 128, less, so
128 is copy-pasted less and in the future if it needs to be changed
it'll only have to be changed in one place.
2020-08-14 09:51:19 -04:00
Misa
e481c75404 Use SDL stdlib funcs for free/malloc/strcmp
It's always good to have less of a dependency on a libc.
2020-08-14 09:51:19 -04:00
Misa
cf8442b12c Add bounds checks to getSize() and getAddress()
It's all too easy for someone to pass in a bad index and index out of
bounds, so we need to prevent that from happening.
2020-08-14 09:51:19 -04:00
Misa
6991b2045d Set valid to false if size is bogus
This is to prevent callers from parsing bogus sizes. If they listen to
the -1 sentinel value, at least...
2020-08-14 09:51:19 -04:00
Misa
7903b8967e Check valid in binaryBlob::getIndex()
The binary blob shouldn't return an index if it ends up being invalid.
That could cause a whole lot of issues if musicclass ends up parsing the
resulting struct.

With all that said, though, musicclass doesn't check the -1 sentinel
value anyway, even though it should, but that'll be fixed later.
2020-08-14 09:51:19 -04:00
Misa
751e621c14 Fix aggressive glitchrunner hardreset causing issues quitting to menu
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.
2020-08-13 23:51:43 -04:00
Misa
d7040b23d0 Axe manual state trackers and use SDL_IsTextInputActive()
After looking at pull request #446, I got a bit annoyed that we have TWO
variables, key.textentrymode and ed.textentry, that we rolled ourselves
to track the state of something SDL already provides us a function to
easily query: SDL_IsTextInputActive(). We don't need to have either of
these two variables, and we shouldn't.

So that's what I do in this patch. Both variables have been axed in
favor of using this function, and I just made a wrapper out of it, named
key.textentry().

For bonus points, this gets rid of the ugly NO_CUSTOM_LEVELS and
NO_EDITOR ifdef in main.cpp, since text entry is enabled when entering
the script list and disabled when exiting it. This makes the code there
easier to read, too.

Furthermore, apparently key.textentrymode was initialized to *true*
instead of false... for whatever reason. But that's gone now, too.

Now, you'd think there wouldn't be any downside to using
SDL_IsTextInputActive(). After all, it's a function that SDL itself
provides, right?

Wrong. For whatever reason, it seems like text input is active *from the
start of the program*, meaning that what would happen is I would go into
the editor, and find that I can't move around nor place tiles nor
anything else. Then I would press Esc, and then suddenly become able to
do those things I wanted to do before.

I have no idea why the above happens, but all I can do is to just insert
an SDL_StopTextInput() immediately after the SDL_Init() in main.cpp. Of
course, I have to surround it with an SDL_IsTextInputActive() check to
make sure I don't do anything extraneous by stopping input when it's
already stopped.
2020-08-13 17:51:38 -04:00
AllyTally
48313169b6 Don't fade music out when returning to the menu if it's Presenting VVVVVV
Otherwise, the Presenting VVVVVV for the menu wouldn't play at all.
2020-08-13 10:08:20 -04:00
Nichole Mattera
d865c37951 Fixed the title screen showing up when going to graphics options while playtesting. 2020-08-13 10:03:48 -04:00
Nichole Mattera
ab323a7550 Simplified the fixes. 2020-08-12 23:07:13 -04:00
Nichole Mattera
51119d4394 Adding myself as a contributor. 2020-08-12 23:07:13 -04:00
Nichole Mattera
2ddd7a96fb Fix issues with hotkey during text inputs. 2020-08-12 23:07:13 -04:00
Misa
4d5a0e2cb0 Fix other array decay too
Whoops.
2020-08-09 13:39:12 -04:00
Nichole Mattera
b4da818e8c Missed adding the option to the createmenu method. 2020-08-08 19:10:31 -04:00
Nichole Mattera
1376e65b5d Added ability to bind restart to a controller. 2020-08-08 19:02:14 -04:00
Misa
af88aee7c0 Disable pressing R in No Death Mode
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.
2020-08-08 16:42:19 -04:00
Nichole Mattera
f4d247f7b6 Fixed controllers not being able to navigate the map and menu with up and down buttons. 2020-08-08 16:41:53 -04:00
Misa
688f759967 Fix array decay in PLATFORM_getOSDirectory()
Whoops.
2020-08-07 17:55:18 -04:00
Misa
609ceb782c Clean up strcat()s/strcpy()s, EXCEPT for migrateSaveData()
strcat()s and strcpy()s have been replaced with SDL_snprintf() where
possible, to clearly convey the intent of just building a string that
looks a certain way, instead of spanning it out over multiple lines.

Where there's not really a good way to avoid strcat()/strcpy() (e.g. in
PLATFORM_getOSDirectory()), they will at least be replaced with
SDL_strlcat() and SDL_strlcpy(), which are safer functions and are less
likely to have issues with null termination.

I decided not to bother with PLATFORM_migrateSaveData(), because it's
going to be axed in 2.4 anyways.
2020-08-07 10:36:39 -04:00