1
0
mirror of https://github.com/TerryCavanagh/VVVVVV.git synced 2024-06-28 23:48:30 +02:00
VVVVVV/desktop_version/src/Entity.h
Misa 3ca7b09012 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.
2021-07-28 20:11:16 -04:00

205 lines
4.3 KiB
C++

#ifndef ENTITY_H
#define ENTITY_H
#include <SDL.h>
#include <string>
#include <vector>
#include "Maths.h"
#include "Ent.h"
#include "BlockV.h"
#include "Game.h"
enum
{
BLOCK = 0,
TRIGGER = 1,
DAMAGE = 2,
DIRECTIONAL = 3,
SAFE = 4,
ACTIVITY = 5
};
class entityclass
{
public:
void init(void);
void resetallflags(void);
void fatal_top(void)
{
createblock(DAMAGE, -8, -8, 384, 16);
}
void fatal_bottom(void)
{
createblock(DAMAGE, -8, 224, 384, 16);
}
void fatal_left(void)
{
createblock(DAMAGE, -8, -8, 16, 260);
}
void fatal_right(void)
{
createblock(DAMAGE, 312, -8, 16, 260);
}
int swncolour(int t );
void swnenemiescol(int t);
void gravcreate(int ypos, int dir, int xoff = 0, int yoff = 0);
void generateswnwave(int t);
void createblock(int t, int xp, int yp, int w, int h, int trig = 0, const std::string& script = "");
bool disableentity(int t);
void removeallblocks(void);
void disableblock(int t);
void disableblockat(int x, int y);
void moveblockto(int x1, int y1, int x2, int y2, int w, int h);
void removetrigger(int t);
void copylinecross(int t);
void revertlinecross(int t, int s);
bool gridmatch(int p1, int p2, int p3, int p4, int p11, int p21, int p31, int p41);
int crewcolour(int t);
void createentity(int xp, int yp, int t, int meta1, int meta2,
int p1, int p2, int p3, int p4);
void createentity(int xp, int yp, int t, int meta1, int meta2,
int p1, int p2);
void createentity(int xp, int yp, int t, int meta1, int meta2,
int p1);
void createentity(int xp, int yp, int t, int meta1, int meta2);
void createentity(int xp, int yp, int t, int meta1);
void createentity(int xp, int yp, int t);
bool updateentities(int i);
void animateentities(int i);
void animatehumanoidcollision(const int i);
int getcompanion(void);
int getplayer(void);
int getscm(void);
int getlineat(int t);
int getcrewman(int t);
int getcustomcrewman(int t);
int getteleporter(void);
bool entitycollide(int a, int b);
bool checkdamage(bool scm = false);
int checktrigger(int* block_idx);
int checkactivity(void);
int getgridpoint(int t);
bool checkplatform(const SDL_Rect& temprect, int* px, int* py);
bool checkblocks(const SDL_Rect& temprect, const float dx, const float dy, const float dr, const bool skipdirblocks);
bool checktowerspikes(int t);
bool checkwall(const SDL_Rect& temprect, const float dx, const float dy, const float dr, const bool skipblocks, const bool skipdirblocks);
bool checkwall(const SDL_Rect& temprect);
float hplatformat(const int px, const int py);
int yline(int a, int b);
bool entityhlinecollide(int t, int l);
bool entityvlinecollide(int t, int l);
bool entitywarphlinecollide(int t, int l);
bool entitywarpvlinecollide(int t, int l);
void customwarplinecheck(int i);
float entitycollideplatformroof(int t);
float entitycollideplatformfloor(int t);
bool entitycollidefloor(int t);
bool entitycollideroof(int t);
bool testwallsx(int t, int tx, int ty, const bool skipdirblocks);
bool testwallsy(int t, float tx, float ty);
void applyfriction(int t, float xrate, float yrate);
void updateentitylogic(int t);
void entitymapcollision(int t);
void movingplatformfix(int t, int j);
void entitycollisioncheck(void);
void collisioncheck(int i, int j, bool scm = false);
void stuckprevention(int t);
std::vector<entclass> entities;
std::vector<entclass> linecrosskludge;
int k;
std::vector<blockclass> blocks;
bool flags[100];
bool collect[100];
bool customcollect[100];
int platformtile;
bool vertplatforms, horplatforms;
// :(
bool nearelephant, upsetmode;
int upset;
//Trophy Text
int trophytext, trophytype;
int oldtrophytext;
//Secret lab scripts
int altstates;
//Custom stuff
int customenemy;
int customplatformtile;
bool customwarpmode, customwarpmodevon, customwarpmodehon;
std::string customscript;
bool customcrewmoods[Game::numcrew];
};
#ifndef OBJ_DEFINITION
extern entityclass obj;
#endif
#endif /* ENTITY_H */