ddnet/src/game/collision.cpp
Patiga be8f6217ce Make tileflag names consistent with automappers
Follow-up to commit a4867d29c6

- `TILEFLAG_FLIP_HORIZONTAL` -> `TILEFLAG_XFLIP`
- `TILEFLAG_FLIP_VERTICAL` -> `TILEFLAG_YFLIP`

In the previous commit, I pretty much just switched `V` and `H` and
changed the naming a little more to break further uses.
The reason was that the two flags were called counter-intuitively.
Since then, I realized that the auto mapper syntax also already faced
this issue and is already using `XFLIP` and `YFLIP`.
For more consistency and to reduce the amount of names for these flips,
these flags should also be called like that.

It also turned out that more things are connected to `V` and `H`.
Those letters are shown in the `Info` mode in the editor, and are used
extensively by the automapper community.
Switching to `X` and `Y` allows keeping backwards compatibility while
introducing more intuitive names.
2022-12-19 18:39:36 +01:00

1269 lines
31 KiB
C++

/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
/* If you are missing that file, acquire a complete release at teeworlds.com. */
#include <base/math.h>
#include <base/system.h>
#include <base/vmath.h>
#include <antibot/antibot_data.h>
#include <cmath>
#include <engine/map.h>
#include <game/collision.h>
#include <game/layers.h>
#include <game/mapitems.h>
#include <engine/shared/config.h>
vec2 ClampVel(int MoveRestriction, vec2 Vel)
{
if(Vel.x > 0 && (MoveRestriction & CANTMOVE_RIGHT))
{
Vel.x = 0;
}
if(Vel.x < 0 && (MoveRestriction & CANTMOVE_LEFT))
{
Vel.x = 0;
}
if(Vel.y > 0 && (MoveRestriction & CANTMOVE_DOWN))
{
Vel.y = 0;
}
if(Vel.y < 0 && (MoveRestriction & CANTMOVE_UP))
{
Vel.y = 0;
}
return Vel;
}
CCollision::CCollision()
{
m_pTiles = 0;
m_Width = 0;
m_Height = 0;
m_pLayers = 0;
m_pTele = 0;
m_pSpeedup = 0;
m_pFront = 0;
m_pSwitch = 0;
m_pDoor = 0;
m_pTune = 0;
}
CCollision::~CCollision()
{
Dest();
}
void CCollision::Init(class CLayers *pLayers)
{
Dest();
m_HighestSwitchNumber = 0;
m_pLayers = pLayers;
m_Width = m_pLayers->GameLayer()->m_Width;
m_Height = m_pLayers->GameLayer()->m_Height;
m_pTiles = static_cast<CTile *>(m_pLayers->Map()->GetData(m_pLayers->GameLayer()->m_Data));
if(m_pLayers->TeleLayer())
{
unsigned int Size = m_pLayers->Map()->GetDataSize(m_pLayers->TeleLayer()->m_Tele);
if(Size >= (size_t)m_Width * m_Height * sizeof(CTeleTile))
m_pTele = static_cast<CTeleTile *>(m_pLayers->Map()->GetData(m_pLayers->TeleLayer()->m_Tele));
}
if(m_pLayers->SpeedupLayer())
{
unsigned int Size = m_pLayers->Map()->GetDataSize(m_pLayers->SpeedupLayer()->m_Speedup);
if(Size >= (size_t)m_Width * m_Height * sizeof(CSpeedupTile))
m_pSpeedup = static_cast<CSpeedupTile *>(m_pLayers->Map()->GetData(m_pLayers->SpeedupLayer()->m_Speedup));
}
if(m_pLayers->SwitchLayer())
{
unsigned int Size = m_pLayers->Map()->GetDataSize(m_pLayers->SwitchLayer()->m_Switch);
if(Size >= (size_t)m_Width * m_Height * sizeof(CSwitchTile))
m_pSwitch = static_cast<CSwitchTile *>(m_pLayers->Map()->GetData(m_pLayers->SwitchLayer()->m_Switch));
m_pDoor = new CDoorTile[m_Width * m_Height];
mem_zero(m_pDoor, (size_t)m_Width * m_Height * sizeof(CDoorTile));
}
else
{
m_pDoor = 0;
}
if(m_pLayers->TuneLayer())
{
unsigned int Size = m_pLayers->Map()->GetDataSize(m_pLayers->TuneLayer()->m_Tune);
if(Size >= (size_t)m_Width * m_Height * sizeof(CTuneTile))
m_pTune = static_cast<CTuneTile *>(m_pLayers->Map()->GetData(m_pLayers->TuneLayer()->m_Tune));
}
if(m_pLayers->FrontLayer())
{
unsigned int Size = m_pLayers->Map()->GetDataSize(m_pLayers->FrontLayer()->m_Front);
if(Size >= (size_t)m_Width * m_Height * sizeof(CTile))
m_pFront = static_cast<CTile *>(m_pLayers->Map()->GetData(m_pLayers->FrontLayer()->m_Front));
}
for(int i = 0; i < m_Width * m_Height; i++)
{
int Index;
if(m_pSwitch)
{
if(m_pSwitch[i].m_Number > m_HighestSwitchNumber)
m_HighestSwitchNumber = m_pSwitch[i].m_Number;
if(m_pSwitch[i].m_Number)
m_pDoor[i].m_Number = m_pSwitch[i].m_Number;
else
m_pDoor[i].m_Number = 0;
Index = m_pSwitch[i].m_Type;
if(Index <= TILE_NPH_ENABLE)
{
if((Index >= TILE_JUMP && Index <= TILE_SUBTRACT_TIME) || Index == TILE_ALLOW_TELE_GUN || Index == TILE_ALLOW_BLUE_TELE_GUN)
m_pSwitch[i].m_Type = Index;
else
m_pSwitch[i].m_Type = 0;
}
}
}
}
void CCollision::FillAntibot(CAntibotMapData *pMapData)
{
pMapData->m_Width = m_Width;
pMapData->m_Height = m_Height;
pMapData->m_pTiles = (unsigned char *)malloc((size_t)m_Width * m_Height);
for(int i = 0; i < m_Width * m_Height; i++)
{
pMapData->m_pTiles[i] = 0;
if(m_pTiles[i].m_Index >= TILE_SOLID && m_pTiles[i].m_Index <= TILE_NOLASER)
{
pMapData->m_pTiles[i] = m_pTiles[i].m_Index;
}
}
}
enum
{
MR_DIR_HERE = 0,
MR_DIR_RIGHT,
MR_DIR_DOWN,
MR_DIR_LEFT,
MR_DIR_UP,
NUM_MR_DIRS
};
static int GetMoveRestrictionsRaw(int Direction, int Tile, int Flags)
{
Flags = Flags & (TILEFLAG_XFLIP | TILEFLAG_YFLIP | TILEFLAG_ROTATE);
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;
case TILEFLAG_YFLIP ^ ROTATION_0: return CANTMOVE_UP;
case TILEFLAG_YFLIP ^ ROTATION_90: return CANTMOVE_RIGHT;
case TILEFLAG_YFLIP ^ ROTATION_180: return CANTMOVE_DOWN;
case TILEFLAG_YFLIP ^ ROTATION_270: return CANTMOVE_LEFT;
}
break;
case TILE_STOPS:
switch(Flags)
{
case ROTATION_0:
case ROTATION_180:
case TILEFLAG_YFLIP ^ ROTATION_0:
case TILEFLAG_YFLIP ^ ROTATION_180:
return CANTMOVE_DOWN | CANTMOVE_UP;
case ROTATION_90:
case ROTATION_270:
case TILEFLAG_YFLIP ^ ROTATION_90:
case TILEFLAG_YFLIP ^ ROTATION_270:
return CANTMOVE_LEFT | CANTMOVE_RIGHT;
}
break;
case TILE_STOPA:
return CANTMOVE_LEFT | CANTMOVE_RIGHT | CANTMOVE_UP | CANTMOVE_DOWN;
}
return 0;
}
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;
default: dbg_assert(false, "invalid dir");
}
return 0;
}
static int GetMoveRestrictions(int Direction, int Tile, int Flags)
{
int Result = GetMoveRestrictionsRaw(Direction, Tile, Flags);
// Generally, stoppers only have an effect if they block us from moving
// *onto* them. The one exception is one-way blockers, they can also
// block us from moving if we're on top of them.
if(Direction == MR_DIR_HERE && Tile == TILE_STOP)
{
return Result;
}
return Result & GetMoveRestrictionsMask(Direction);
}
int CCollision::GetMoveRestrictions(CALLBACK_SWITCHACTIVE pfnSwitchActive, void *pUser, vec2 Pos, float Distance, int OverrideCenterTileIndex)
{
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;
int ModMapIndex = GetPureMapIndex(ModPos);
if(d == MR_DIR_HERE && OverrideCenterTileIndex >= 0)
{
ModMapIndex = OverrideCenterTileIndex;
}
for(int Front = 0; Front < 2; Front++)
{
int Tile;
int Flags;
if(!Front)
{
Tile = GetTileIndex(ModMapIndex);
Flags = GetTileFlags(ModMapIndex);
}
else
{
Tile = GetFTileIndex(ModMapIndex);
Flags = GetFTileFlags(ModMapIndex);
}
Restrictions |= ::GetMoveRestrictions(d, Tile, Flags);
}
if(pfnSwitchActive)
{
int TeleNumber = GetDTileNumber(ModMapIndex);
if(pfnSwitchActive(TeleNumber, pUser))
{
int Tile = GetDTileIndex(ModMapIndex);
int Flags = GetDTileFlags(ModMapIndex);
Restrictions |= ::GetMoveRestrictions(d, Tile, Flags);
}
}
}
return Restrictions;
}
int CCollision::GetTile(int x, int y) const
{
if(!m_pTiles)
return 0;
int Nx = clamp(x / 32, 0, m_Width - 1);
int Ny = clamp(y / 32, 0, m_Height - 1);
int pos = Ny * m_Width + Nx;
if(m_pTiles[pos].m_Index >= TILE_SOLID && m_pTiles[pos].m_Index <= TILE_NOLASER)
return m_pTiles[pos].m_Index;
return 0;
}
// TODO: rewrite this smarter!
int CCollision::IntersectLine(vec2 Pos0, vec2 Pos1, vec2 *pOutCollision, vec2 *pOutBeforeCollision) const
{
float Distance = distance(Pos0, Pos1);
int End(Distance + 1);
vec2 Last = Pos0;
for(int i = 0; i <= End; i++)
{
float a = i / (float)End;
vec2 Pos = mix(Pos0, Pos1, a);
// Temporary position for checking collision
int ix = round_to_int(Pos.x);
int iy = round_to_int(Pos.y);
if(CheckPoint(ix, iy))
{
if(pOutCollision)
*pOutCollision = Pos;
if(pOutBeforeCollision)
*pOutBeforeCollision = Last;
return GetCollisionAt(ix, iy);
}
Last = Pos;
}
if(pOutCollision)
*pOutCollision = Pos1;
if(pOutBeforeCollision)
*pOutBeforeCollision = Pos1;
return 0;
}
int CCollision::IntersectLineTeleHook(vec2 Pos0, vec2 Pos1, vec2 *pOutCollision, vec2 *pOutBeforeCollision, int *pTeleNr) const
{
float Distance = distance(Pos0, Pos1);
int End(Distance + 1);
vec2 Last = Pos0;
int dx = 0, dy = 0; // Offset for checking the "through" tile
ThroughOffset(Pos0, Pos1, &dx, &dy);
for(int i = 0; i <= End; i++)
{
float a = i / (float)End;
vec2 Pos = mix(Pos0, Pos1, a);
// Temporary position for checking collision
int ix = round_to_int(Pos.x);
int iy = round_to_int(Pos.y);
int Index = GetPureMapIndex(Pos);
if(g_Config.m_SvOldTeleportHook)
*pTeleNr = IsTeleport(Index);
else
*pTeleNr = IsTeleportHook(Index);
if(*pTeleNr)
{
if(pOutCollision)
*pOutCollision = Pos;
if(pOutBeforeCollision)
*pOutBeforeCollision = Last;
return TILE_TELEINHOOK;
}
int hit = 0;
if(CheckPoint(ix, iy))
{
if(!IsThrough(ix, iy, dx, dy, Pos0, Pos1))
hit = GetCollisionAt(ix, iy);
}
else if(IsHookBlocker(ix, iy, Pos0, Pos1))
{
hit = TILE_NOHOOK;
}
if(hit)
{
if(pOutCollision)
*pOutCollision = Pos;
if(pOutBeforeCollision)
*pOutBeforeCollision = Last;
return hit;
}
Last = Pos;
}
if(pOutCollision)
*pOutCollision = Pos1;
if(pOutBeforeCollision)
*pOutBeforeCollision = Pos1;
return 0;
}
int CCollision::IntersectLineTeleWeapon(vec2 Pos0, vec2 Pos1, vec2 *pOutCollision, vec2 *pOutBeforeCollision, int *pTeleNr) const
{
float Distance = distance(Pos0, Pos1);
int End(Distance + 1);
vec2 Last = Pos0;
for(int i = 0; i <= End; i++)
{
float a = i / (float)End;
vec2 Pos = mix(Pos0, Pos1, a);
// Temporary position for checking collision
int ix = round_to_int(Pos.x);
int iy = round_to_int(Pos.y);
int Index = GetPureMapIndex(Pos);
if(g_Config.m_SvOldTeleportWeapons)
*pTeleNr = IsTeleport(Index);
else
*pTeleNr = IsTeleportWeapon(Index);
if(*pTeleNr)
{
if(pOutCollision)
*pOutCollision = Pos;
if(pOutBeforeCollision)
*pOutBeforeCollision = Last;
return TILE_TELEINWEAPON;
}
if(CheckPoint(ix, iy))
{
if(pOutCollision)
*pOutCollision = Pos;
if(pOutBeforeCollision)
*pOutBeforeCollision = Last;
return GetCollisionAt(ix, iy);
}
Last = Pos;
}
if(pOutCollision)
*pOutCollision = Pos1;
if(pOutBeforeCollision)
*pOutBeforeCollision = Pos1;
return 0;
}
// TODO: OPT: rewrite this smarter!
void CCollision::MovePoint(vec2 *pInoutPos, vec2 *pInoutVel, float Elasticity, int *pBounces) const
{
if(pBounces)
*pBounces = 0;
vec2 Pos = *pInoutPos;
vec2 Vel = *pInoutVel;
if(CheckPoint(Pos + Vel))
{
int Affected = 0;
if(CheckPoint(Pos.x + Vel.x, Pos.y))
{
pInoutVel->x *= -Elasticity;
if(pBounces)
(*pBounces)++;
Affected++;
}
if(CheckPoint(Pos.x, Pos.y + Vel.y))
{
pInoutVel->y *= -Elasticity;
if(pBounces)
(*pBounces)++;
Affected++;
}
if(Affected == 0)
{
pInoutVel->x *= -Elasticity;
pInoutVel->y *= -Elasticity;
}
}
else
{
*pInoutPos = Pos + Vel;
}
}
bool CCollision::TestBox(vec2 Pos, vec2 Size) const
{
Size *= 0.5f;
if(CheckPoint(Pos.x - Size.x, Pos.y - Size.y))
return true;
if(CheckPoint(Pos.x + Size.x, Pos.y - Size.y))
return true;
if(CheckPoint(Pos.x - Size.x, Pos.y + Size.y))
return true;
if(CheckPoint(Pos.x + Size.x, Pos.y + Size.y))
return true;
return false;
}
void CCollision::MoveBox(vec2 *pInoutPos, vec2 *pInoutVel, vec2 Size, float Elasticity) const
{
// do the move
vec2 Pos = *pInoutPos;
vec2 Vel = *pInoutVel;
float Distance = length(Vel);
int Max = (int)Distance;
if(Distance > 0.00001f)
{
float Fraction = 1.0f / (float)(Max + 1);
for(int i = 0; i <= Max; i++)
{
// Early break as optimization to stop checking for collisions for
// large distances after the obstacles we have already hit reduced
// our speed to exactly 0.
if(Vel == vec2(0, 0))
{
break;
}
vec2 NewPos = Pos + Vel * Fraction; // TODO: this row is not nice
// Fraction can be very small and thus the calculation has no effect, no
// reason to continue calculating.
if(NewPos == Pos)
{
break;
}
if(TestBox(vec2(NewPos.x, NewPos.y), Size))
{
int Hits = 0;
if(TestBox(vec2(Pos.x, NewPos.y), Size))
{
NewPos.y = Pos.y;
Vel.y *= -Elasticity;
Hits++;
}
if(TestBox(vec2(NewPos.x, Pos.y), Size))
{
NewPos.x = Pos.x;
Vel.x *= -Elasticity;
Hits++;
}
// neither of the tests got a collision.
// this is a real _corner case_!
if(Hits == 0)
{
NewPos.y = Pos.y;
Vel.y *= -Elasticity;
NewPos.x = Pos.x;
Vel.x *= -Elasticity;
}
}
Pos = NewPos;
}
}
*pInoutPos = Pos;
*pInoutVel = Vel;
}
// DDRace
void CCollision::Dest()
{
delete[] m_pDoor;
m_pTiles = 0;
m_Width = 0;
m_Height = 0;
m_pLayers = 0;
m_pTele = 0;
m_pSpeedup = 0;
m_pFront = 0;
m_pSwitch = 0;
m_pTune = 0;
m_pDoor = 0;
}
int CCollision::IsSolid(int x, int y) const
{
int index = GetTile(x, y);
return index == TILE_SOLID || index == TILE_NOHOOK;
}
bool CCollision::IsThrough(int x, int y, int xoff, int yoff, vec2 pos0, vec2 pos1) const
{
int pos = GetPureMapIndex(x, y);
if(m_pFront && (m_pFront[pos].m_Index == TILE_THROUGH_ALL || m_pFront[pos].m_Index == TILE_THROUGH_CUT))
return true;
if(m_pFront && m_pFront[pos].m_Index == TILE_THROUGH_DIR && ((m_pFront[pos].m_Flags == ROTATION_0 && pos0.y > pos1.y) || (m_pFront[pos].m_Flags == ROTATION_90 && pos0.x < pos1.x) || (m_pFront[pos].m_Flags == ROTATION_180 && pos0.y < pos1.y) || (m_pFront[pos].m_Flags == ROTATION_270 && pos0.x > pos1.x)))
return true;
int offpos = GetPureMapIndex(x + xoff, y + yoff);
return m_pTiles[offpos].m_Index == TILE_THROUGH || (m_pFront && m_pFront[offpos].m_Index == TILE_THROUGH);
}
bool CCollision::IsHookBlocker(int x, int y, vec2 pos0, vec2 pos1) const
{
int pos = GetPureMapIndex(x, y);
if(m_pTiles[pos].m_Index == TILE_THROUGH_ALL || (m_pFront && m_pFront[pos].m_Index == TILE_THROUGH_ALL))
return true;
if(m_pTiles[pos].m_Index == TILE_THROUGH_DIR && ((m_pTiles[pos].m_Flags == ROTATION_0 && pos0.y < pos1.y) ||
(m_pTiles[pos].m_Flags == ROTATION_90 && pos0.x > pos1.x) ||
(m_pTiles[pos].m_Flags == ROTATION_180 && pos0.y > pos1.y) ||
(m_pTiles[pos].m_Flags == ROTATION_270 && pos0.x < pos1.x)))
return true;
if(m_pFront && m_pFront[pos].m_Index == TILE_THROUGH_DIR && ((m_pFront[pos].m_Flags == ROTATION_0 && pos0.y < pos1.y) || (m_pFront[pos].m_Flags == ROTATION_90 && pos0.x > pos1.x) || (m_pFront[pos].m_Flags == ROTATION_180 && pos0.y > pos1.y) || (m_pFront[pos].m_Flags == ROTATION_270 && pos0.x < pos1.x)))
return true;
return false;
}
int CCollision::IsWallJump(int Index) const
{
if(Index < 0)
return 0;
return m_pTiles[Index].m_Index == TILE_WALLJUMP;
}
int CCollision::IsNoLaser(int x, int y) const
{
return (CCollision::GetTile(x, y) == TILE_NOLASER);
}
int CCollision::IsFNoLaser(int x, int y) const
{
return (CCollision::GetFTile(x, y) == TILE_NOLASER);
}
int CCollision::IsTeleport(int Index) const
{
if(Index < 0 || !m_pTele)
return 0;
if(m_pTele[Index].m_Type == TILE_TELEIN)
return m_pTele[Index].m_Number;
return 0;
}
int CCollision::IsEvilTeleport(int Index) const
{
if(Index < 0)
return 0;
if(!m_pTele)
return 0;
if(m_pTele[Index].m_Type == TILE_TELEINEVIL)
return m_pTele[Index].m_Number;
return 0;
}
int CCollision::IsCheckTeleport(int Index) const
{
if(Index < 0)
return 0;
if(!m_pTele)
return 0;
if(m_pTele[Index].m_Type == TILE_TELECHECKIN)
return m_pTele[Index].m_Number;
return 0;
}
int CCollision::IsCheckEvilTeleport(int Index) const
{
if(Index < 0)
return 0;
if(!m_pTele)
return 0;
if(m_pTele[Index].m_Type == TILE_TELECHECKINEVIL)
return m_pTele[Index].m_Number;
return 0;
}
int CCollision::IsTeleCheckpoint(int Index) const
{
if(Index < 0)
return 0;
if(!m_pTele)
return 0;
if(m_pTele[Index].m_Type == TILE_TELECHECK)
return m_pTele[Index].m_Number;
return 0;
}
int CCollision::IsTeleportWeapon(int Index) const
{
if(Index < 0 || !m_pTele)
return 0;
if(m_pTele[Index].m_Type == TILE_TELEINWEAPON)
return m_pTele[Index].m_Number;
return 0;
}
int CCollision::IsTeleportHook(int Index) const
{
if(Index < 0 || !m_pTele)
return 0;
if(m_pTele[Index].m_Type == TILE_TELEINHOOK)
return m_pTele[Index].m_Number;
return 0;
}
int CCollision::IsSpeedup(int Index) const
{
if(Index < 0 || !m_pSpeedup)
return 0;
if(m_pSpeedup[Index].m_Force > 0)
return Index;
return 0;
}
int CCollision::IsTune(int Index) const
{
if(Index < 0 || !m_pTune)
return 0;
if(m_pTune[Index].m_Type)
return m_pTune[Index].m_Number;
return 0;
}
void CCollision::GetSpeedup(int Index, vec2 *pDir, int *pForce, int *pMaxSpeed) const
{
if(Index < 0 || !m_pSpeedup)
return;
float Angle = m_pSpeedup[Index].m_Angle * (pi / 180.0f);
*pForce = m_pSpeedup[Index].m_Force;
*pDir = vec2(cos(Angle), sin(Angle));
if(pMaxSpeed)
*pMaxSpeed = m_pSpeedup[Index].m_MaxSpeed;
}
int CCollision::GetSwitchType(int Index) const
{
if(Index < 0 || !m_pSwitch)
return 0;
if(m_pSwitch[Index].m_Type > 0)
return m_pSwitch[Index].m_Type;
return 0;
}
int CCollision::GetSwitchNumber(int Index) const
{
if(Index < 0 || !m_pSwitch)
return 0;
if(m_pSwitch[Index].m_Type > 0 && m_pSwitch[Index].m_Number > 0)
return m_pSwitch[Index].m_Number;
return 0;
}
int CCollision::GetSwitchDelay(int Index) const
{
if(Index < 0 || !m_pSwitch)
return 0;
if(m_pSwitch[Index].m_Type > 0)
return m_pSwitch[Index].m_Delay;
return 0;
}
int CCollision::IsMover(int x, int y, int *pFlags) const
{
int Nx = clamp(x / 32, 0, m_Width - 1);
int Ny = clamp(y / 32, 0, m_Height - 1);
int Index = m_pTiles[Ny * m_Width + Nx].m_Index;
*pFlags = m_pTiles[Ny * m_Width + Nx].m_Flags;
if(Index < 0)
return 0;
if(Index == TILE_CP || Index == TILE_CP_F)
return Index;
else
return 0;
}
vec2 CCollision::CpSpeed(int Index, int Flags) const
{
if(Index < 0)
return vec2(0, 0);
vec2 target;
if(Index == TILE_CP || Index == TILE_CP_F)
switch(Flags)
{
case ROTATION_0:
target.x = 0;
target.y = -4;
break;
case ROTATION_90:
target.x = 4;
target.y = 0;
break;
case ROTATION_180:
target.x = 0;
target.y = 4;
break;
case ROTATION_270:
target.x = -4;
target.y = 0;
break;
default:
target = vec2(0, 0);
break;
}
if(Index == TILE_CP_F)
target *= 4;
return target;
}
int CCollision::GetPureMapIndex(float x, float y) const
{
int Nx = clamp(round_to_int(x) / 32, 0, m_Width - 1);
int Ny = clamp(round_to_int(y) / 32, 0, m_Height - 1);
return Ny * m_Width + Nx;
}
bool CCollision::TileExists(int Index) const
{
if(Index < 0)
return false;
if((m_pTiles[Index].m_Index >= TILE_FREEZE && m_pTiles[Index].m_Index <= TILE_TELE_LASER_DISABLE) || (m_pTiles[Index].m_Index >= TILE_LFREEZE && m_pTiles[Index].m_Index <= TILE_LUNFREEZE))
return true;
if(m_pFront && ((m_pFront[Index].m_Index >= TILE_FREEZE && m_pFront[Index].m_Index <= TILE_TELE_LASER_DISABLE) || (m_pFront[Index].m_Index >= TILE_LFREEZE && m_pFront[Index].m_Index <= TILE_LUNFREEZE)))
return true;
if(m_pTele && (m_pTele[Index].m_Type == TILE_TELEIN || m_pTele[Index].m_Type == TILE_TELEINEVIL || m_pTele[Index].m_Type == TILE_TELECHECKINEVIL || m_pTele[Index].m_Type == TILE_TELECHECK || m_pTele[Index].m_Type == TILE_TELECHECKIN))
return true;
if(m_pSpeedup && m_pSpeedup[Index].m_Force > 0)
return true;
if(m_pDoor && m_pDoor[Index].m_Index)
return true;
if(m_pSwitch && m_pSwitch[Index].m_Type)
return true;
if(m_pTune && m_pTune[Index].m_Type)
return true;
return TileExistsNext(Index);
}
bool CCollision::TileExistsNext(int Index) const
{
if(Index < 0)
return false;
int TileOnTheLeft = (Index - 1 > 0) ? Index - 1 : Index;
int TileOnTheRight = (Index + 1 < m_Width * m_Height) ? Index + 1 : Index;
int TileBelow = (Index + m_Width < m_Width * m_Height) ? Index + m_Width : Index;
int TileAbove = (Index - m_Width > 0) ? Index - m_Width : Index;
if((m_pTiles[TileOnTheRight].m_Index == TILE_STOP && m_pTiles[TileOnTheRight].m_Flags == ROTATION_270) || (m_pTiles[TileOnTheLeft].m_Index == TILE_STOP && m_pTiles[TileOnTheLeft].m_Flags == ROTATION_90))
return true;
if((m_pTiles[TileBelow].m_Index == TILE_STOP && m_pTiles[TileBelow].m_Flags == ROTATION_0) || (m_pTiles[TileAbove].m_Index == TILE_STOP && m_pTiles[TileAbove].m_Flags == ROTATION_180))
return true;
if(m_pTiles[TileOnTheRight].m_Index == TILE_STOPA || m_pTiles[TileOnTheLeft].m_Index == TILE_STOPA || ((m_pTiles[TileOnTheRight].m_Index == TILE_STOPS || m_pTiles[TileOnTheLeft].m_Index == TILE_STOPS)))
return true;
if(m_pTiles[TileBelow].m_Index == TILE_STOPA || m_pTiles[TileAbove].m_Index == TILE_STOPA || ((m_pTiles[TileBelow].m_Index == TILE_STOPS || m_pTiles[TileAbove].m_Index == TILE_STOPS) && m_pTiles[TileBelow].m_Flags | ROTATION_180 | ROTATION_0))
return true;
if(m_pFront)
{
if(m_pFront[TileOnTheRight].m_Index == TILE_STOPA || m_pFront[TileOnTheLeft].m_Index == TILE_STOPA || ((m_pFront[TileOnTheRight].m_Index == TILE_STOPS || m_pFront[TileOnTheLeft].m_Index == TILE_STOPS)))
return true;
if(m_pFront[TileBelow].m_Index == TILE_STOPA || m_pFront[TileAbove].m_Index == TILE_STOPA || ((m_pFront[TileBelow].m_Index == TILE_STOPS || m_pFront[TileAbove].m_Index == TILE_STOPS) && m_pFront[TileBelow].m_Flags | ROTATION_180 | ROTATION_0))
return true;
if((m_pFront[TileOnTheRight].m_Index == TILE_STOP && m_pFront[TileOnTheRight].m_Flags == ROTATION_270) || (m_pFront[TileOnTheLeft].m_Index == TILE_STOP && m_pFront[TileOnTheLeft].m_Flags == ROTATION_90))
return true;
if((m_pFront[TileBelow].m_Index == TILE_STOP && m_pFront[TileBelow].m_Flags == ROTATION_0) || (m_pFront[TileAbove].m_Index == TILE_STOP && m_pFront[TileAbove].m_Flags == ROTATION_180))
return true;
}
if(m_pDoor)
{
if(m_pDoor[TileOnTheRight].m_Index == TILE_STOPA || m_pDoor[TileOnTheLeft].m_Index == TILE_STOPA || ((m_pDoor[TileOnTheRight].m_Index == TILE_STOPS || m_pDoor[TileOnTheLeft].m_Index == TILE_STOPS)))
return true;
if(m_pDoor[TileBelow].m_Index == TILE_STOPA || m_pDoor[TileAbove].m_Index == TILE_STOPA || ((m_pDoor[TileBelow].m_Index == TILE_STOPS || m_pDoor[TileAbove].m_Index == TILE_STOPS) && m_pDoor[TileBelow].m_Flags | ROTATION_180 | ROTATION_0))
return true;
if((m_pDoor[TileOnTheRight].m_Index == TILE_STOP && m_pDoor[TileOnTheRight].m_Flags == ROTATION_270) || (m_pDoor[TileOnTheLeft].m_Index == TILE_STOP && m_pDoor[TileOnTheLeft].m_Flags == ROTATION_90))
return true;
if((m_pDoor[TileBelow].m_Index == TILE_STOP && m_pDoor[TileBelow].m_Flags == ROTATION_0) || (m_pDoor[TileAbove].m_Index == TILE_STOP && m_pDoor[TileAbove].m_Flags == ROTATION_180))
return true;
}
return false;
}
int CCollision::GetMapIndex(vec2 Pos) const
{
int Nx = clamp((int)Pos.x / 32, 0, m_Width - 1);
int Ny = clamp((int)Pos.y / 32, 0, m_Height - 1);
int Index = Ny * m_Width + Nx;
if(TileExists(Index))
return Index;
else
return -1;
}
std::list<int> CCollision::GetMapIndices(vec2 PrevPos, vec2 Pos, unsigned MaxIndices) const
{
std::list<int> Indices;
float d = distance(PrevPos, Pos);
int End(d + 1);
if(!d)
{
int Nx = clamp((int)Pos.x / 32, 0, m_Width - 1);
int Ny = clamp((int)Pos.y / 32, 0, m_Height - 1);
int Index = Ny * m_Width + Nx;
if(TileExists(Index))
{
Indices.push_back(Index);
return Indices;
}
else
return Indices;
}
else
{
int LastIndex = 0;
for(int i = 0; i < End; i++)
{
float a = i / d;
vec2 Tmp = mix(PrevPos, Pos, a);
int Nx = clamp((int)Tmp.x / 32, 0, m_Width - 1);
int Ny = clamp((int)Tmp.y / 32, 0, m_Height - 1);
int Index = Ny * m_Width + Nx;
if(TileExists(Index) && LastIndex != Index)
{
if(MaxIndices && Indices.size() > MaxIndices)
return Indices;
Indices.push_back(Index);
LastIndex = Index;
}
}
return Indices;
}
}
vec2 CCollision::GetPos(int Index) const
{
if(Index < 0)
return vec2(0, 0);
int x = Index % m_Width;
int y = Index / m_Width;
return vec2(x * 32 + 16, y * 32 + 16);
}
int CCollision::GetTileIndex(int Index) const
{
if(Index < 0)
return 0;
return m_pTiles[Index].m_Index;
}
int CCollision::GetFTileIndex(int Index) const
{
if(Index < 0 || !m_pFront)
return 0;
return m_pFront[Index].m_Index;
}
int CCollision::GetTileFlags(int Index) const
{
if(Index < 0)
return 0;
return m_pTiles[Index].m_Flags;
}
int CCollision::GetFTileFlags(int Index) const
{
if(Index < 0 || !m_pFront)
return 0;
return m_pFront[Index].m_Flags;
}
int CCollision::GetIndex(int Nx, int Ny) const
{
return m_pTiles[Ny * m_Width + Nx].m_Index;
}
int CCollision::GetIndex(vec2 PrevPos, vec2 Pos) const
{
float Distance = distance(PrevPos, Pos);
if(!Distance)
{
int Nx = clamp((int)Pos.x / 32, 0, m_Width - 1);
int Ny = clamp((int)Pos.y / 32, 0, m_Height - 1);
if((m_pTele) ||
(m_pSpeedup && m_pSpeedup[Ny * m_Width + Nx].m_Force > 0))
{
return Ny * m_Width + Nx;
}
}
for(int i = 0, id = (int)ceilf(Distance); i < id; i++)
{
float a = (float)i / Distance;
vec2 Tmp = mix(PrevPos, Pos, a);
int Nx = clamp((int)Tmp.x / 32, 0, m_Width - 1);
int Ny = clamp((int)Tmp.y / 32, 0, m_Height - 1);
if((m_pTele) ||
(m_pSpeedup && m_pSpeedup[Ny * m_Width + Nx].m_Force > 0))
{
return Ny * m_Width + Nx;
}
}
return -1;
}
int CCollision::GetFIndex(int Nx, int Ny) const
{
if(!m_pFront)
return 0;
return m_pFront[Ny * m_Width + Nx].m_Index;
}
int CCollision::GetFTile(int x, int y) const
{
if(!m_pFront)
return 0;
int Nx = clamp(x / 32, 0, m_Width - 1);
int Ny = clamp(y / 32, 0, m_Height - 1);
if(m_pFront[Ny * m_Width + Nx].m_Index == TILE_DEATH || m_pFront[Ny * m_Width + Nx].m_Index == TILE_NOLASER)
return m_pFront[Ny * m_Width + Nx].m_Index;
else
return 0;
}
int CCollision::Entity(int x, int y, int Layer) const
{
if((0 > x || x >= m_Width) || (0 > y || y >= m_Height))
{
char aBuf[12];
switch(Layer)
{
case LAYER_GAME:
str_format(aBuf, sizeof(aBuf), "Game");
break;
case LAYER_FRONT:
str_format(aBuf, sizeof(aBuf), "Front");
break;
case LAYER_SWITCH:
str_format(aBuf, sizeof(aBuf), "Switch");
break;
case LAYER_TELE:
str_format(aBuf, sizeof(aBuf), "Tele");
break;
case LAYER_SPEEDUP:
str_format(aBuf, sizeof(aBuf), "Speedup");
break;
case LAYER_TUNE:
str_format(aBuf, sizeof(aBuf), "Tune");
break;
default:
str_format(aBuf, sizeof(aBuf), "Unknown");
}
dbg_msg("collision", "something is VERY wrong with the %s layer please report this at https://github.com/ddnet/ddnet, you will need to post the map as well and any steps that u think may have led to this", aBuf);
return 0;
}
switch(Layer)
{
case LAYER_GAME:
return m_pTiles[y * m_Width + x].m_Index - ENTITY_OFFSET;
case LAYER_FRONT:
return m_pFront[y * m_Width + x].m_Index - ENTITY_OFFSET;
case LAYER_SWITCH:
return m_pSwitch[y * m_Width + x].m_Type - ENTITY_OFFSET;
case LAYER_TELE:
return m_pTele[y * m_Width + x].m_Type - ENTITY_OFFSET;
case LAYER_SPEEDUP:
return m_pSpeedup[y * m_Width + x].m_Type - ENTITY_OFFSET;
case LAYER_TUNE:
return m_pTune[y * m_Width + x].m_Type - ENTITY_OFFSET;
default:
return 0;
break;
}
}
void CCollision::SetCollisionAt(float x, float y, int id)
{
int Nx = clamp(round_to_int(x) / 32, 0, m_Width - 1);
int Ny = clamp(round_to_int(y) / 32, 0, m_Height - 1);
m_pTiles[Ny * m_Width + Nx].m_Index = id;
}
void CCollision::SetDCollisionAt(float x, float y, int Type, int Flags, int Number)
{
if(!m_pDoor)
return;
int Nx = clamp(round_to_int(x) / 32, 0, m_Width - 1);
int Ny = clamp(round_to_int(y) / 32, 0, m_Height - 1);
m_pDoor[Ny * m_Width + Nx].m_Index = Type;
m_pDoor[Ny * m_Width + Nx].m_Flags = Flags;
m_pDoor[Ny * m_Width + Nx].m_Number = Number;
}
int CCollision::GetDTileIndex(int Index) const
{
if(!m_pDoor || Index < 0 || !m_pDoor[Index].m_Index)
return 0;
return m_pDoor[Index].m_Index;
}
int CCollision::GetDTileNumber(int Index) const
{
if(!m_pDoor || Index < 0 || !m_pDoor[Index].m_Index)
return 0;
if(m_pDoor[Index].m_Number)
return m_pDoor[Index].m_Number;
return 0;
}
int CCollision::GetDTileFlags(int Index) const
{
if(!m_pDoor || Index < 0 || !m_pDoor[Index].m_Index)
return 0;
return m_pDoor[Index].m_Flags;
}
void ThroughOffset(vec2 Pos0, vec2 Pos1, int *pOffsetX, int *pOffsetY)
{
float x = Pos0.x - Pos1.x;
float y = Pos0.y - Pos1.y;
if(fabs(x) > fabs(y))
{
if(x < 0)
{
*pOffsetX = -32;
*pOffsetY = 0;
}
else
{
*pOffsetX = 32;
*pOffsetY = 0;
}
}
else
{
if(y < 0)
{
*pOffsetX = 0;
*pOffsetY = -32;
}
else
{
*pOffsetX = 0;
*pOffsetY = 32;
}
}
}
int CCollision::IntersectNoLaser(vec2 Pos0, vec2 Pos1, vec2 *pOutCollision, vec2 *pOutBeforeCollision) const
{
float d = distance(Pos0, Pos1);
vec2 Last = Pos0;
for(int i = 0, id = (int)ceilf(d); i < id; i++)
{
float a = (int)i / d;
vec2 Pos = mix(Pos0, Pos1, a);
int Nx = clamp(round_to_int(Pos.x) / 32, 0, m_Width - 1);
int Ny = clamp(round_to_int(Pos.y) / 32, 0, m_Height - 1);
if(GetIndex(Nx, Ny) == TILE_SOLID || GetIndex(Nx, Ny) == TILE_NOHOOK || GetIndex(Nx, Ny) == TILE_NOLASER || GetFIndex(Nx, Ny) == TILE_NOLASER)
{
if(pOutCollision)
*pOutCollision = Pos;
if(pOutBeforeCollision)
*pOutBeforeCollision = Last;
if(GetFIndex(Nx, Ny) == TILE_NOLASER)
return GetFCollisionAt(Pos.x, Pos.y);
else
return GetCollisionAt(Pos.x, Pos.y);
}
Last = Pos;
}
if(pOutCollision)
*pOutCollision = Pos1;
if(pOutBeforeCollision)
*pOutBeforeCollision = Pos1;
return 0;
}
int CCollision::IntersectNoLaserNW(vec2 Pos0, vec2 Pos1, vec2 *pOutCollision, vec2 *pOutBeforeCollision) const
{
float d = distance(Pos0, Pos1);
vec2 Last = Pos0;
for(int i = 0, id = (int)ceilf(d); i < id; i++)
{
float a = (float)i / d;
vec2 Pos = mix(Pos0, Pos1, a);
if(IsNoLaser(round_to_int(Pos.x), round_to_int(Pos.y)) || IsFNoLaser(round_to_int(Pos.x), round_to_int(Pos.y)))
{
if(pOutCollision)
*pOutCollision = Pos;
if(pOutBeforeCollision)
*pOutBeforeCollision = Last;
if(IsNoLaser(round_to_int(Pos.x), round_to_int(Pos.y)))
return GetCollisionAt(Pos.x, Pos.y);
else
return GetFCollisionAt(Pos.x, Pos.y);
}
Last = Pos;
}
if(pOutCollision)
*pOutCollision = Pos1;
if(pOutBeforeCollision)
*pOutBeforeCollision = Pos1;
return 0;
}
int CCollision::IntersectAir(vec2 Pos0, vec2 Pos1, vec2 *pOutCollision, vec2 *pOutBeforeCollision) const
{
float d = distance(Pos0, Pos1);
vec2 Last = Pos0;
for(int i = 0, id = (int)ceilf(d); i < id; i++)
{
float a = (float)i / d;
vec2 Pos = mix(Pos0, Pos1, a);
if(IsSolid(round_to_int(Pos.x), round_to_int(Pos.y)) || (!GetTile(round_to_int(Pos.x), round_to_int(Pos.y)) && !GetFTile(round_to_int(Pos.x), round_to_int(Pos.y))))
{
if(pOutCollision)
*pOutCollision = Pos;
if(pOutBeforeCollision)
*pOutBeforeCollision = Last;
if(!GetTile(round_to_int(Pos.x), round_to_int(Pos.y)) && !GetFTile(round_to_int(Pos.x), round_to_int(Pos.y)))
return -1;
else if(!GetTile(round_to_int(Pos.x), round_to_int(Pos.y)))
return GetTile(round_to_int(Pos.x), round_to_int(Pos.y));
else
return GetFTile(round_to_int(Pos.x), round_to_int(Pos.y));
}
Last = Pos;
}
if(pOutCollision)
*pOutCollision = Pos1;
if(pOutBeforeCollision)
*pOutBeforeCollision = Pos1;
return 0;
}
int CCollision::IsTimeCheckpoint(int Index) const
{
if(Index < 0)
return -1;
int z = m_pTiles[Index].m_Index;
if(z >= TILE_TIME_CHECKPOINT_FIRST && z <= TILE_TIME_CHECKPOINT_LAST)
return z - TILE_TIME_CHECKPOINT_FIRST;
return -1;
}
int CCollision::IsFTimeCheckpoint(int Index) const
{
if(Index < 0 || !m_pFront)
return -1;
int z = m_pFront[Index].m_Index;
if(z >= TILE_TIME_CHECKPOINT_FIRST && z <= TILE_TIME_CHECKPOINT_LAST)
return z - TILE_TIME_CHECKPOINT_FIRST;
return -1;
}