Fix stoppers while retaining backward-compatibility

Stopper behavior is only changed when the player would otherwise go
entirely through the stopper. Players can still enter a stopper as far
as the stopper can still affect them. One-way blockers have a bigger
range, and thus allow tees to enter furtherly.

A possible (manual) test map for this is test_stoppers.map, sha256sum
ed8be386e54a03d7bd7ed69fdd962c86f51f654427972d58d492c8905c8fbdb7, crc
48812a51.
This commit is contained in:
heinrich5991 2018-08-16 16:50:53 +02:00
parent 4ee5c5a781
commit e2d3e353cf
4 changed files with 235 additions and 35 deletions

View file

@ -14,21 +14,21 @@
#include <engine/shared/config.h>
vec2 ClampVel(int MoveRestriction, vec2 Vel)
vec2 ClampVel(int MoveRestrictions, vec2 Vel)
{
if(Vel.x > 0 && (MoveRestriction&CANTMOVE_RIGHT))
if(Vel.x > 0 && (MoveRestrictions&CANTMOVE_RIGHT))
{
Vel.x = 0;
}
if(Vel.x < 0 && (MoveRestriction&CANTMOVE_LEFT))
if(Vel.x < 0 && (MoveRestrictions&CANTMOVE_LEFT))
{
Vel.x = 0;
}
if(Vel.y > 0 && (MoveRestriction&CANTMOVE_DOWN))
if(Vel.y > 0 && (MoveRestrictions&CANTMOVE_DOWN))
{
Vel.y = 0;
}
if(Vel.y < 0 && (MoveRestriction&CANTMOVE_UP))
if(Vel.y < 0 && (MoveRestrictions&CANTMOVE_UP))
{
Vel.y = 0;
}
@ -160,32 +160,91 @@ enum
NUM_MR_DIRS
};
static vec2 DirVec(int Direction)
{
switch(Direction)
{
case MR_DIR_HERE: return vec2(0, 0);
case MR_DIR_RIGHT: return vec2(1, 0);
case MR_DIR_DOWN: return vec2(0, 1);
case MR_DIR_LEFT: return vec2(-1, 0);
case MR_DIR_UP: return vec2(0, -1);
default: dbg_assert(false, "invalid dir");
}
return vec2(0, 0);
}
static int Twoway(int MoveRestrictions)
{
if(MoveRestrictions&CANTMOVE_LEFT)
{
MoveRestrictions |= CANTMOVE_LEFT_TWOWAY;
}
if(MoveRestrictions&CANTMOVE_RIGHT)
{
MoveRestrictions |= CANTMOVE_RIGHT_TWOWAY;
}
if(MoveRestrictions&CANTMOVE_UP)
{
MoveRestrictions |= CANTMOVE_UP_TWOWAY;
}
if(MoveRestrictions&CANTMOVE_DOWN)
{
MoveRestrictions |= CANTMOVE_DOWN_TWOWAY;
}
return MoveRestrictions;
}
static int Here(int MoveRestrictions)
{
if(MoveRestrictions&CANTMOVE_LEFT)
{
MoveRestrictions |= CANTMOVE_LEFT_HERE;
}
if(MoveRestrictions&CANTMOVE_RIGHT)
{
MoveRestrictions |= CANTMOVE_RIGHT_HERE;
}
if(MoveRestrictions&CANTMOVE_UP)
{
MoveRestrictions |= CANTMOVE_UP_HERE;
}
if(MoveRestrictions&CANTMOVE_DOWN)
{
MoveRestrictions |= CANTMOVE_DOWN_HERE;
}
return MoveRestrictions;
}
static int GetMoveRestrictionsRaw(int Direction, int Tile, int Flags)
{
switch(Tile)
{
case TILE_STOP:
switch(Flags)
{
case ROTATION_0: return CANTMOVE_DOWN;
case ROTATION_90: return CANTMOVE_LEFT;
case ROTATION_180: return CANTMOVE_UP;
case ROTATION_270: return CANTMOVE_RIGHT;
int MoveRestrictions = 0;
switch(Flags)
{
case ROTATION_0: MoveRestrictions = CANTMOVE_DOWN; break;
case ROTATION_90: MoveRestrictions = CANTMOVE_LEFT; break;
case ROTATION_180: MoveRestrictions = CANTMOVE_UP; break;
case ROTATION_270: MoveRestrictions = CANTMOVE_RIGHT; break;
}
return Direction == MR_DIR_HERE ? Here(MoveRestrictions) : MoveRestrictions;
}
break;
case TILE_STOPS:
switch(Flags)
{
case ROTATION_0:
case ROTATION_180:
return CANTMOVE_DOWN|CANTMOVE_UP;
return Twoway(CANTMOVE_DOWN|CANTMOVE_UP);
case ROTATION_90:
case ROTATION_270:
return CANTMOVE_LEFT|CANTMOVE_RIGHT;
return Twoway(CANTMOVE_LEFT|CANTMOVE_RIGHT);
}
break;
case TILE_STOPA:
return CANTMOVE_LEFT|CANTMOVE_RIGHT|CANTMOVE_UP|CANTMOVE_DOWN;
return Twoway(CANTMOVE_LEFT|CANTMOVE_RIGHT|CANTMOVE_UP|CANTMOVE_DOWN);
}
return 0;
}
@ -195,10 +254,10 @@ static int GetMoveRestrictionsMask(int Direction)
switch(Direction)
{
case MR_DIR_HERE: return 0;
case MR_DIR_RIGHT: return CANTMOVE_RIGHT;
case MR_DIR_DOWN: return CANTMOVE_DOWN;
case MR_DIR_LEFT: return CANTMOVE_LEFT;
case MR_DIR_UP: return CANTMOVE_UP;
case MR_DIR_RIGHT: return Twoway(CANTMOVE_RIGHT);
case MR_DIR_DOWN: return Twoway(CANTMOVE_DOWN);
case MR_DIR_LEFT: return Twoway(CANTMOVE_LEFT);
case MR_DIR_UP: return Twoway(CANTMOVE_UP);
default: dbg_assert(false, "invalid dir");
}
return 0;
@ -219,19 +278,11 @@ static int GetMoveRestrictions(int Direction, int Tile, int Flags)
int CCollision::GetMoveRestrictions(CALLBACK_SWITCHACTIVE pfnSwitchActive, void *pUser, vec2 Pos, float Distance)
{
static const vec2 DIRECTIONS[NUM_MR_DIRS] =
{
vec2(0, 0),
vec2(1, 0),
vec2(0, 1),
vec2(-1, 0),
vec2(0, -1)
};
dbg_assert(0.0f <= Distance && Distance <= 32.0f, "invalid distance");
int Restrictions = 0;
for(int d = 0; d < NUM_MR_DIRS; d++)
{
vec2 ModPos = Pos + DIRECTIONS[d] * Distance;
vec2 ModPos = Pos + DirVec(d) * Distance;
int ModMapIndex = GetPureMapIndex(ModPos);
for(int Front = 0; Front < 2; Front++)
{
@ -464,7 +515,58 @@ bool CCollision::TestBox(vec2 Pos, vec2 Size)
return false;
}
void CCollision::MoveBox(vec2 *pInoutPos, vec2 *pInoutVel, vec2 Size, float Elasticity)
static float *DirCoord(int Direction, vec2 *pVec)
{
switch(Direction)
{
case MR_DIR_RIGHT: case MR_DIR_LEFT: return &pVec->x;
case MR_DIR_DOWN: case MR_DIR_UP: return &pVec->y;
default: dbg_assert(false, "invalid dir");
}
return 0;
}
static int DirTwoway(int Direction)
{
switch(Direction)
{
case MR_DIR_HERE: return 0;
case MR_DIR_RIGHT: return CANTMOVE_RIGHT_TWOWAY;
case MR_DIR_DOWN: return CANTMOVE_DOWN_TWOWAY;
case MR_DIR_LEFT: return CANTMOVE_LEFT_TWOWAY;
case MR_DIR_UP: return CANTMOVE_UP_TWOWAY;
default: dbg_assert(false, "invalid dir");
}
return 0;
}
static int DirHere(int Direction)
{
switch(Direction)
{
case MR_DIR_HERE: return 0;
case MR_DIR_RIGHT: return CANTMOVE_RIGHT_HERE;
case MR_DIR_DOWN: return CANTMOVE_DOWN_HERE;
case MR_DIR_LEFT: return CANTMOVE_LEFT_HERE;
case MR_DIR_UP: return CANTMOVE_UP_HERE;
default: dbg_assert(false, "invalid dir");
}
return 0;
}
static int DirSign(int Direction)
{
switch(Direction)
{
case MR_DIR_HERE: return 0;
case MR_DIR_RIGHT: case MR_DIR_DOWN: return 1;
case MR_DIR_LEFT: case MR_DIR_UP: return -1;
default: dbg_assert(false, "invalid dir");
}
return 0;
}
void CCollision::MoveBox(CALLBACK_SWITCHACTIVE pfnSwitchActive, void *pUser, vec2 *pInoutPos, vec2 *pInoutVel, vec2 Size, float Elasticity, bool CheckStoppers)
{
// do the move
vec2 Pos = *pInoutPos;
@ -514,6 +616,75 @@ void CCollision::MoveBox(vec2 *pInoutPos, vec2 *pInoutVel, vec2 Size, float Elas
}
}
if(CheckStoppers)
{
// Yay. Backward-compatibility. Isn't that fun?
//
// Since you previously managed to get through
// stoppers (or into them) at high speeds, some
// maps started using it. A lot of maps
// actually. So we have to maintain bug-for-bug
// compatibility.
//
// The strategy for still preventing players to
// go through stoppers is as follows: If you're
// going so fast that you'd skip a stopper, you
// will instead be stopped at the last possible
// position where the stopper still has
// influence on you.
//
// We have to differentiate between one-way
// stoppers and multiple-way stoppers.
//
// One-way stoppers affect you until your
// center leaves the tile, so we just have to
// stop you from exiting the stopper in the
// wrong direction.
//
// Multiple-way stoppers affect you in a more
// complicated way: If you're blocked from,
// e.g. the right, then you're blocked as long
// as the position 18 units to your right is in
// that stopper. So we have to stop you from
// getting the position 18 units away from you
// out of that stopper tile in the wrong
// direction.
//
// Backward-compatibility. \o/
static const float OFFSET = 18.0f;
int MoveRestrictions = GetMoveRestrictions(pfnSwitchActive, pUser, Pos, OFFSET);
for(int d = 1; d < NUM_MR_DIRS; d++)
{
static const int TILESIZE = 32;
float *pPos = DirCoord(d, &Pos);
float *pNewPos = DirCoord(d, &NewPos);
float *pVel = DirCoord(d, &Vel);
int Sign = DirSign(d);
// Are we actually going in the
// direction we're checking?
if(*pVel * Sign <= 0)
{
continue;
}
bool Stop = false;
if(MoveRestrictions&DirTwoway(d)
&& round_to_int(*pPos + OFFSET * Sign) / TILESIZE != round_to_int(*pNewPos + OFFSET * Sign) / TILESIZE)
{
Stop = true;
}
if(MoveRestrictions&DirHere(d)
&& round_to_int(*pPos) / TILESIZE != round_to_int(*pNewPos) / TILESIZE)
{
Stop = true;
}
if(Stop)
{
*pVel = 0;
*pNewPos = *pPos;
}
}
}
Pos = NewPos;
}
}

View file

@ -14,6 +14,26 @@ enum
CANTMOVE_RIGHT=1<<1,
CANTMOVE_UP=1<<2,
CANTMOVE_DOWN=1<<3,
CANTMOVE_DIRECTIONS=0xf,
// These get set when you can't move in the direction, caused by a
// two-way or all-way stopper.
CANTMOVE_LEFT_TWOWAY=1<<4,
CANTMOVE_RIGHT_TWOWAY=1<<5,
CANTMOVE_UP_TWOWAY=1<<6,
CANTMOVE_DOWN_TWOWAY=1<<7,
CANTMOVE_TWOWAY_DIRECTIONS=0xf0,
// These get set when you can't move in the direction, caused by a
// one-way stopper on the tile of the character.
CANTMOVE_LEFT_HERE=1<<8,
CANTMOVE_RIGHT_HERE=1<<9,
CANTMOVE_UP_HERE=1<<10,
CANTMOVE_DOWN_HERE=1<<11,
CANTMOVE_HERE_DIRECTIONS=0xf00,
};
vec2 ClampVel(int MoveRestriction, vec2 Vel);
@ -40,7 +60,11 @@ public:
int IntersectLineTeleWeapon(vec2 Pos0, vec2 Pos1, vec2 *pOutCollision, vec2 *pOutBeforeCollision, int *pTeleNr);
int IntersectLineTeleHook(vec2 Pos0, vec2 Pos1, vec2 *pOutCollision, vec2 *pOutBeforeCollision, int *pTeleNr);
void MovePoint(vec2 *pInoutPos, vec2 *pInoutVel, float Elasticity, int *pBounces);
void MoveBox(vec2 *pInoutPos, vec2 *pInoutVel, vec2 Size, float Elasticity);
void MoveBox(CALLBACK_SWITCHACTIVE pfnSwitchActive, void *pUser, vec2 *pInoutPos, vec2 *pInoutVel, vec2 Size, float Elasticity, bool CheckStopper);
void MoveBox(vec2 *pInoutPos, vec2 *pInoutVel, vec2 Size, float Elasticity, bool CheckStopper)
{
MoveBox(0, 0, pInoutPos, pInoutVel, Size, Elasticity, CheckStopper);
}
bool TestBox(vec2 Pos, vec2 Size);
// DDRace

View file

@ -506,7 +506,7 @@ void CCharacterCore::Move()
vec2 NewPos = m_Pos;
vec2 OldVel = m_Vel;
m_pCollision->MoveBox(&NewPos, &m_Vel, vec2(28.0f, 28.0f), 0);
m_pCollision->MoveBox(IsSwitchActiveCb, this, &NewPos, &m_Vel, vec2(28.0f, 28.0f), 0.0f, true);
m_Colliding = 0;
if(m_Vel.x < 0.001 && m_Vel.x > -0.001)
@ -610,10 +610,15 @@ void CCharacterCore::Quantize()
bool CCharacterCore::IsSwitchActiveCb(int Number, void *pUser)
{
CCharacterCore *pThis = (CCharacterCore *)pUser;
if(pThis->Collision()->m_pSwitchers)
if(pThis->m_pTeams->Team(pThis->m_Id) != (pThis->m_pTeams->m_IsDDRace16 ? VANILLA_TEAM_SUPER : TEAM_SUPER))
return pThis->Collision()->m_pSwitchers[Number].m_Status[pThis->m_pTeams->Team(pThis->m_Id)];
return false;
if(pThis->m_Id < 0 || !pThis->Collision()->m_pSwitchers)
{
return false;
}
if(pThis->m_pTeams->Team(pThis->m_Id) == (pThis->m_pTeams->m_IsDDRace16 ? VANILLA_TEAM_SUPER : TEAM_SUPER))
{
return false;
}
return pThis->Collision()->m_pSwitchers[Number].m_Status[pThis->m_pTeams->Team(pThis->m_Id)];
}
void CCharacterCore::LimitVel(vec2 *pVel)

View file

@ -209,7 +209,7 @@ void CCharacter::HandleNinja()
// Set velocity
m_Core.m_Vel = m_Ninja.m_ActivationDir * g_pData->m_Weapons.m_Ninja.m_Velocity;
vec2 OldPos = m_Pos;
GameServer()->Collision()->MoveBox(&m_Core.m_Pos, &m_Core.m_Vel, vec2(m_ProximityRadius, m_ProximityRadius), 0.f);
GameServer()->Collision()->MoveBox(&m_Core.m_Pos, &m_Core.m_Vel, vec2(m_ProximityRadius, m_ProximityRadius), 0.0f, false);
// reset velocity so the client doesn't predict stuff
m_Core.m_Vel = vec2(0.f, 0.f);