mirror of
https://github.com/ddnet/ddnet.git
synced 2024-11-10 10:08:18 +00:00
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:
parent
4ee5c5a781
commit
e2d3e353cf
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue