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

Evaluate flipping eligibility per-entity

This fixes a regression where the behavior of duplicate player entities
is different, causing a gameplay section in Vespera Scientifica to be
impossible, as described in #903.

In the level, you are allowed to flip in mid-air. Vespera accomplishes
this by having two duplicate player entities stuck in a platform. One of
them is responsible for letting the player flip in one direction, and
one of them is responsible for letting them flip in the other.

In 2.3, this works because in order for a player entity to flip,
`game.jumppressed` is checked, and the entity will flip if
`game.jumppressed` is greater than 0, then `game.jumppressed` will be
set to 0. In this way, whenever a player entity flips, it will set
`game.jumppressed` to 0, so whenever the next player entity is
evaluated, `game.jumppressed` is 0 and thus _that_ entity will not flip.

This is because the for-loop surrounds both the `game.jumppressed` check
and flipping the entity. In 2.4 after #609 and subsequent patches,
however, this is not the case. Here, the for-loop only surrounds
flipping the entity. Therefore, the `game.jumppressed` check is
evaluated only once. So, it will end up flipping every player entity if
the entities are eligible. In this case, one of them is eligible twice,
resulting in the game flipping gravitycontrol four times, which is
essentially the same as not flipping at all (except for all the sound
effects).

Hence, the fix here is to make it so the for-loop surrounds the
`game.jumppressed` check.

Now, this doesn't mean that the intent of #609 - that duplicate player
entities have the same initial velocity applied to them when flipping -
has to be removed. We can just put the for-loops back in. But I
carefully implemented them in such a way that the overall function is
not quadratic, i.e. O(n²). Instead, there's a pass done over the
`obj.entities` vector beforehand to store all indexes of a player entity
in a temporary vector, and then that vector is used to update all the
player entities. In this manner, the function is still linear - O(n) -
over the number of entities in the room.

I tested this to make sure that no previous regressions popped up again,
including #839, #855, and #887. And #484 is still resolved.

Fixes #903.
This commit is contained in:
Misa 2023-03-18 20:24:08 -07:00
parent ba7519106f
commit 88d49547d4

View file

@ -1,4 +1,5 @@
#include <tinyxml2.h> #include <tinyxml2.h>
#include <vector>
#include "Credits.h" #include "Credits.h"
#include "CustomLevels.h" #include "CustomLevels.h"
@ -2508,8 +2509,6 @@ void gameinput(void)
bool has_control = false; bool has_control = false;
bool enter_pressed = game.press_map && !game.mapheld; bool enter_pressed = game.press_map && !game.mapheld;
bool enter_already_processed = false; bool enter_already_processed = false;
bool any_onground = false;
bool any_onroof = false;
bool interact_pressed; bool interact_pressed;
if (game.separate_interact) if (game.separate_interact)
{ {
@ -2626,15 +2625,6 @@ void gameinput(void)
obj.entities[ie].dir = 1; obj.entities[ie].dir = 1;
} }
} }
if (obj.entities[ie].onground > 0)
{
any_onground = true;
}
if (obj.entities[ie].onroof > 0)
{
any_onroof = true;
}
} }
} }
@ -2695,33 +2685,51 @@ void gameinput(void)
game.jumpheld = true; game.jumpheld = true;
} }
if (game.jumppressed > 0) std::vector<size_t> player_entities;
for (size_t ie = 0; ie < obj.entities.size(); ie++)
{ {
if (obj.entities[ie].rule == 0)
{
player_entities.push_back(ie);
}
}
for (size_t ie = 0; ie < obj.entities.size(); ie++)
{
const bool process_flip = obj.entities[ie].rule == 0 &&
game.jumppressed > 0;
if (!process_flip)
{
continue;
}
game.jumppressed--; game.jumppressed--;
if (any_onground && game.gravitycontrol == 0) if (obj.entities[ie].onground > 0 && game.gravitycontrol == 0)
{ {
game.gravitycontrol = 1; game.gravitycontrol = 1;
for (size_t ie = 0; ie < obj.entities.size(); ++ie) for (size_t j = 0; j < player_entities.size(); j++)
{ {
if (obj.entities[ie].rule == 0 && (obj.entities[ie].onground > 0 || obj.entities[ie].onroof > 0)) const size_t e = player_entities[j];
if (obj.entities[e].onground > 0 || obj.entities[e].onroof > 0)
{ {
obj.entities[ie].vy = -4; obj.entities[e].vy = -4;
obj.entities[ie].ay = -3; obj.entities[e].ay = -3;
} }
} }
music.playef(0); music.playef(0);
game.jumppressed = 0; game.jumppressed = 0;
game.totalflips++; game.totalflips++;
} }
if (any_onroof && game.gravitycontrol == 1) if (obj.entities[ie].onroof > 0 && game.gravitycontrol == 1)
{ {
game.gravitycontrol = 0; game.gravitycontrol = 0;
for (size_t ie = 0; ie < obj.entities.size(); ++ie) for (size_t j = 0; j < player_entities.size(); j++)
{ {
if (obj.entities[ie].rule == 0 && (obj.entities[ie].onground > 0 || obj.entities[ie].onroof > 0)) const size_t e = player_entities[j];
if (obj.entities[e].onground > 0 || obj.entities[e].onroof > 0)
{ {
obj.entities[ie].vy = 4; obj.entities[e].vy = 4;
obj.entities[ie].ay = 3; obj.entities[e].ay = 3;
} }
} }
music.playef(1); music.playef(1);