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

Fix regression: quick stopping changing drawframe

This fixes a regression that desyncs my Nova TAS after re-removing the
1-frame input delay.

Quick stopping is simply holding left/right but for less than 5 frames.
Viridian doesn't decelerate when you let go and they immediately stop in
place. (The code calls this tapping, but "quick stopping" is a better
name because you can immediately counter-strafe to stop yourself from
decelrating in the first place, and that works because of this same
code.)

So, the sequence of events in 2.2 and previous looks like this:

- gameinput()
  - If quick stopping, set vx to 0
- gamerender()
  - Change drawframe depending on vx
- gamelogic()
  - Use drawframe for collision (whyyyyyyyyyyyyyyyyyyyyyyyyyyy)

And now (ignoring the intermediate period where the whole loop order was
wrong), the sequence of events in 2.3 looks like this:

- gamerenderfixed()
  - Change drawframe depending on vx
- gamerender()
- gameinput()
  - If quick stopping, set vx to 0
- gamelogic()
  - Use drawframe for collision (my mind has become numb to pain)

So, this means that all the player movement stuff is completely the
same. Except their drawframe is going to be different.

Unfortunately, I had overlooked that gameinput() sets vx and that
animateentities() (in gamerenderfixed()) checks vx. Although, to be
fair, it's a pretty dumb decision to make collision detection be based
on the actual sprites' pixels themselves, instead of a hitbox, in the
first place, so you'd expect THAT to be the end of the dumb parade. Or
maybe you shouldn't, I don't know.

So, what's the solution?

What I've done here is added duplicates of framedelay, drawframe, and
walkingframe, for collision use only. They get updated in gamelogic(),
after gameinput(), which is after when vx could be set to 0.

I've kept the original framedelay, drawframe, and walkingframe around,
to keep the same visuals as closely as possible.

However, due to the removal of the input delay, whenever you quick stop,
your sprite will be wrong for just 1 frame - because when you let go of
the direction key, the game will set your vx to 0 and the logical
drawframe will update to reflect that, but the previous frame cannot
know in advance that you'll release the key on the next frame, and so
the visual drawframe will assume that you keep holding the key.

Whereas in 2.2 and below, when you release a direction key, the player's
position will only update to reflect that on the next frame, but the
current frame can immediately recognize that and update the drawframe
now, instead of retconning it later.

Basically the visual drawframe assumes that you keep holding the key,
and if you don't, then it takes on the value of the collision drawframe
anyway, so it's okay. And it's only visual, anyway - the collision
drawframe of the next frame (when you release the key) will be the same
as the drawframe of the frame you release the key in 2.2 and below.

But I really don't care to try and fix this for if you re-enable the
input delay because it's minor and it'd be more complicated.
This commit is contained in:
Misa 2021-06-15 19:18:58 -07:00 committed by Ethan Lee
parent f77723d12f
commit 3ca7b09012
5 changed files with 105 additions and 1 deletions

View file

@ -46,6 +46,9 @@ void entclass::clear(void)
gravity = false; gravity = false;
onground = 0; onground = 0;
onroof = 0; onroof = 0;
collisionframedelay = 0;
collisiondrawframe = 0;
collisionwalkingframe = 0;
visualonground = 0; visualonground = 0;
visualonroof = 0; visualonroof = 0;

View file

@ -49,6 +49,7 @@ public:
int onground, onroof; int onground, onroof;
//Animation //Animation
int framedelay, drawframe, walkingframe, dir, actionframe; int framedelay, drawframe, walkingframe, dir, actionframe;
int collisionframedelay, collisiondrawframe, collisionwalkingframe;
int visualonground, visualonroof; int visualonground, visualonroof;
int yp;int xp; int yp;int xp;

View file

@ -3748,6 +3748,102 @@ void entityclass::animateentities( int _i )
} }
} }
void entityclass::animatehumanoidcollision(const int i)
{
/* For some awful reason, drawframe is used for actual collision.
* And removing the input delay changes collision drawframe
* because vx is checked in animateentities().
* So we need to separate the collision drawframe from the visual drawframe
* and update it separately in gamelogic.
* Hence this function.
*/
entclass* entity;
if (!INBOUNDS_VEC(i, entities))
{
puts("animatehumanoidcollision() out-of-bounds!");
return;
}
entity = &entities[i];
if (!entity->ishumanoid())
{
return;
}
if (entity->statedelay > 0)
{
return;
}
--entity->collisionframedelay;
if (entity->dir == 1)
{
entity->collisiondrawframe = entity->tile;
}
else
{
entity->collisiondrawframe = entity->tile + 3;
}
if (entity->visualonground > 0 || entity->visualonroof > 0)
{
if (entity->vx > 0.0f || entity->vx < -0.0f)
{
/* Walking */
if (entity->collisionframedelay <= 1)
{
entity->collisionframedelay = 4;
++entity->collisionwalkingframe;
}
if (entity->collisionwalkingframe >= 2)
{
entity->collisionwalkingframe = 0;
}
entity->collisiondrawframe += entity->collisionwalkingframe + 1;
}
if (entity->visualonroof > 0)
{
entity->collisiondrawframe += 6;
}
}
else
{
++entity->collisiondrawframe;
if (entity->type == 0 && game.gravitycontrol == 1)
{
entity->collisiondrawframe += 6;
}
}
/* deathseq shouldn't matter, but handling it anyway just in case */
if (game.deathseq > -1)
{
entity->collisiondrawframe = 13;
if (entity->dir == 1)
{
entity->collisiondrawframe = 12;
}
if ((entity->type == 0 && game.gravitycontrol == 1)
|| (entity->type != 0 && entity->rule == 7))
{
entity->collisiondrawframe += 2;
}
}
entity->framedelay = entity->collisionframedelay;
entity->drawframe = entity->collisiondrawframe;
entity->walkingframe = entity->collisionwalkingframe;
}
int entityclass::getcompanion(void) int entityclass::getcompanion(void)
{ {
//Returns the index of the companion with rule t //Returns the index of the companion with rule t
@ -4653,7 +4749,7 @@ void entityclass::collisioncheck(int i, int j, bool scm /*= false*/)
point colpoint2; point colpoint2;
colpoint2.x = entities[j].xp; colpoint2.x = entities[j].xp;
colpoint2.y = entities[j].yp; colpoint2.y = entities[j].yp;
int drawframe1 = entities[i].drawframe; int drawframe1 = entities[i].collisiondrawframe;
int drawframe2 = entities[j].drawframe; int drawframe2 = entities[j].drawframe;
std::vector<SDL_Surface*>& spritesvec = graphics.flipmode ? graphics.flipsprites : graphics.sprites; std::vector<SDL_Surface*>& spritesvec = graphics.flipmode ? graphics.flipsprites : graphics.sprites;
if (INBOUNDS_VEC(drawframe1, spritesvec) && INBOUNDS_VEC(drawframe2, spritesvec) if (INBOUNDS_VEC(drawframe1, spritesvec) && INBOUNDS_VEC(drawframe2, spritesvec)

View file

@ -88,6 +88,8 @@ public:
void animateentities(int i); void animateentities(int i);
void animatehumanoidcollision(const int i);
int getcompanion(void); int getcompanion(void);
int getplayer(void); int getplayer(void);

View file

@ -152,6 +152,8 @@ void gamelogic(void)
{ {
--obj.entities[i].onroof; --obj.entities[i].onroof;
} }
obj.animatehumanoidcollision(i);
} }
} }