diff --git a/CMakeLists.txt b/CMakeLists.txt index 42d392b8..6e4c7143 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -903,6 +903,8 @@ set_glob(GAME_SERVER GLOB_RECURSE src/game/server infclass/entities/hero-flag.h infclass/entities/ic-pickup.cpp infclass/entities/ic-pickup.h + infclass/entities/ic_door.cpp + infclass/entities/ic_door.h infclass/entities/infc-laser.cpp infclass/entities/infc-laser.h infclass/entities/infc-placed-object.cpp diff --git a/src/game/collision.cpp b/src/game/collision.cpp index aad66ecf..fc5ab39d 100644 --- a/src/game/collision.cpp +++ b/src/game/collision.cpp @@ -63,6 +63,8 @@ void CCollision::Init(class CLayers *pLayers) m_Height = m_pLayers->GameLayer()->m_Height; m_pTiles = static_cast(m_pLayers->Map()->GetData(m_pLayers->GameLayer()->m_Data)); + InitPhysicalLayer(); + InitDoorsLayer(); InitTeleports(); if(m_pLayers->SpeedupLayer()) @@ -73,6 +75,41 @@ void CCollision::Init(class CLayers *pLayers) } } +constexpr EZonePhysics GetPhysicalTileByTile(int Tile) +{ + switch(Tile) + { + case TILE_SOLID: + return EZonePhysics::Solid; + case TILE_NOHOOK: + return EZonePhysics::NoHook; + default: + break; + } + + return EZonePhysics::Null; +} + +void CCollision::InitPhysicalLayer() +{ + mv_Physics.resize(m_Width * m_Height); + for(int Y = 0; Y < m_Height; ++Y) + { + for(int X = 0; X < m_Width; ++X) + { + int Index = Y * m_Width + X; + int Tile = m_pTiles[Index].m_Index; + mv_Physics[Index] = GetPhysicalTileByTile(Tile); + } + } +} + +void CCollision::InitDoorsLayer() +{ + mv_Doors.clear(); + mv_Doors.resize(m_Width * m_Height); +} + void CCollision::InitTeleports() { if(!m_pLayers->TeleLayer()) @@ -218,6 +255,76 @@ int CCollision::GetMoveRestrictions(CALLBACK_SWITCHACTIVE pfnSwitchActive, void return Restrictions; } +void CCollision::SetDoorCollisionAt(int Index, bool HasDoor) +{ + if (HasDoor) + { + mv_Doors[Index]++; + } + else + { + mv_Doors[Index]--; + } +} + +void CCollision::SetDoorCollisionAt(vec2 Pos, bool HasDoor) +{ + SetDoorCollisionAt(GetPureMapIndex(Pos), HasDoor); +} + +int CCollision::GetDoorCollisionAt(vec2 Pos) const +{ + int Nx = clamp(static_cast(Pos.x) / 32, 0, m_Width - 1); + int Ny = clamp(static_cast(Pos.y) / 32, 0, m_Height - 1); + int Index = Ny * m_Width + Nx; + + return mv_Doors.at(Index); +} + +int CCollision::IntersectLineWithDoors(vec2 From, vec2 To, vec2 *pOutCollision, vec2 *pOutBeforeCollision) const +{ + vec2 Pos1Pos0 = To - From; + float Distance = length(Pos1Pos0); + int End(Distance + 1); + vec2 Last = From; + + for(int i = 0; i < End; i++) + { + float a = i / Distance; + vec2 Pos = From + Pos1Pos0 * a; + if(GetDoorCollisionAt(Pos)) + { + if(pOutCollision) + *pOutCollision = Pos; + if(pOutBeforeCollision) + *pOutBeforeCollision = Last; + return GetDoorCollisionAt(Pos); + } + Last = Pos; + } + if(pOutCollision) + *pOutCollision = To; + if(pOutBeforeCollision) + *pOutBeforeCollision = To; + return 0; +} + +EZonePhysics CCollision::GetPhysicsTile(int x, int y) const +{ + int Nx = clamp(x / 32, 0, m_Width - 1); + int Ny = clamp(y / 32, 0, m_Height - 1); + int Index = Ny * m_Width + Nx; + + EZonePhysics Value = mv_Physics.at(Index); + if (Value == EZonePhysics::Null) + { + if(mv_Doors.at(Index)) + return EZonePhysics::NoHook; + } + + return Value; +} + int CCollision::GetTile(int x, int y) const { if(!m_pTiles) @@ -230,6 +337,10 @@ int CCollision::GetTile(int x, int y) const int Index = m_pTiles[pos].m_Index; if(Index >= TILE_SOLID && Index <= TILE_NOLASER) return Index; + + if(mv_Doors.at(pos)) + return TILE_NOHOOK; + return 0; } @@ -434,8 +545,7 @@ void CCollision::Dest() bool CCollision::IsSolid(int x, int y) const { - int index = GetTile(x, y); - return index == TILE_SOLID || index == TILE_NOHOOK; + return GetPhysicsTile(x, y) != EZonePhysics::Null; } int CCollision::IsSpeedup(int Index) const diff --git a/src/game/collision.h b/src/game/collision.h index 1eeb5a54..7bd1cc38 100644 --- a/src/game/collision.h +++ b/src/game/collision.h @@ -17,6 +17,8 @@ enum CANTMOVE_DOWN = 1 << 3, }; +enum class EZonePhysics : int8_t; + vec2 ClampVel(int MoveRestriction, vec2 Vel); typedef bool (*CALLBACK_SWITCHACTIVE)(int Number, void *pUser); @@ -29,6 +31,8 @@ struct ZoneData class CCollision { + std::vector mv_Physics; + std::vector mv_Doors; class CTile *m_pTiles; int m_Width; int m_Height; @@ -39,7 +43,6 @@ class CCollision array< array > m_Zones; bool IsSolid(int x, int y) const; - int GetTile(int x, int y) const; public: enum @@ -52,6 +55,8 @@ class CCollision CCollision(); ~CCollision(); void Init(class CLayers *pLayers); + void InitPhysicalLayer(); + void InitDoorsLayer(); void InitTeleports(); bool CheckPoint(float x, float y) const { return IsSolid(round_to_int(x), round(y)); } @@ -74,6 +79,15 @@ class CCollision return GetMoveRestrictions(0, 0, Pos, Distance); } + EZonePhysics GetPhysicsTile(int x, int y) const; + int GetTile(int x, int y) const; + int GetFTile(int x, int y) const; + + void SetDoorCollisionAt(int Index, bool HasDoor); + void SetDoorCollisionAt(vec2 Pos, bool HasDoor); + int GetDoorCollisionAt(vec2 Pos) const; + int IntersectLineWithDoors(vec2 From, vec2 To, vec2 *pOutCollision = nullptr, vec2 *pOutBeforeCollision = nullptr) const; + void SetTime(double Time) { m_Time = Time; } //This function return an Handle to access all zone layers with the name "pName" diff --git a/src/game/mapitems.h b/src/game/mapitems.h index 016a1c41..003f63f2 100644 --- a/src/game/mapitems.h +++ b/src/game/mapitems.h @@ -3,6 +3,8 @@ #ifndef GAME_MAPITEMS_H #define GAME_MAPITEMS_H +#include + // layer types enum { @@ -221,6 +223,13 @@ enum ZONE_BONUS_BONUS=1, }; +enum class EZonePhysics : int8_t +{ + Null, + Solid = TILE_SOLID, + NoHook = TILE_NOHOOK, +}; + enum class EZoneTele { Null, diff --git a/src/game/server/gameworld.h b/src/game/server/gameworld.h index 54638fd1..17876867 100644 --- a/src/game/server/gameworld.h +++ b/src/game/server/gameworld.h @@ -42,6 +42,8 @@ class CGameWorld ENTTYPE_LASER_TELEPORT, ENTTYPE_TURRET, ENTTYPE_PLASMA, + + ENTTYPE_DOOR, NUM_ENTTYPES }; diff --git a/src/game/server/infclass/entities/ic_door.cpp b/src/game/server/infclass/entities/ic_door.cpp new file mode 100644 index 00000000..2ae7c59c --- /dev/null +++ b/src/game/server/infclass/entities/ic_door.cpp @@ -0,0 +1,128 @@ +#include "ic_door.h" + +#include +#include + +CDoor::CDoor(CGameContext *pGameContext, vec2 Pos, vec2 PosTo) : + CPlacedObject(pGameContext, CGameWorld::ENTTYPE_DOOR), + m_Open{true} +{ + m_Pos = Pos; + m_Pos2 = PosTo; + + m_InfClassObjectFlags = INFCLASS_OBJECT_FLAG_HAS_SECOND_POSITION; + + SetOpen(false); + GameWorld()->InsertEntity(this); +} + +void CDoor::Destroy() +{ + SetOpen(true); + + CPlacedObject::Destroy(); +} + +void CDoor::SetCollisions(bool Set) +{ + const vec2 DoorVector = m_Pos2 - m_Pos; + const float Distance = length(DoorVector); + + int PrevIndex = -1; + auto SetOncePerTile = [&](vec2 Pos) + { + int Index = GameServer()->Collision()->GetPureMapIndex(Pos); + if (Index == PrevIndex) + return; + + PrevIndex = Index; + GameServer()->Collision()->SetDoorCollisionAt(Index, Set); + }; + + SetOncePerTile(m_Pos); + if(Distance > TileSizeF) + { + float Step = TileSize / 2; + vec2 NormalizedDoor = DoorVector / Distance; + float ProcessedDistance = Step; + while(ProcessedDistance < Distance) + { + SetOncePerTile(m_Pos + NormalizedDoor * ProcessedDistance); + ProcessedDistance += Step; + } + } + SetOncePerTile(m_Pos2); +} + +void CDoor::Reset() +{ + MarkForDestroy(); +} + +void CDoor::Snap(int SnappingClientId) +{ + const std::optional ViewParams = GetViewParams(GameServer(), SnappingClientId); + + int SnappingClientVersion = GameServer()->GetClientVersion(SnappingClientId); + + vec2 To = m_Pos; + vec2 From = IsOpen() ? m_Pos : m_Pos2; + int StartTick = 0; + + if(SnappingClientVersion < VERSION_DDNET_ENTITY_NETOBJS) + { + StartTick = Server()->Tick(); + } + + const bool ForcedShowOpen = Config()->m_SvShowOpenDoors && IsOpen(); + if(ForcedShowOpen) + { + From = m_Pos2; + } + int MaxY = Collision()->GetHeight() * TileSize; + if((From != To) && ViewParams.has_value()) + { + if(m_Pos.x == m_Pos2.x) + { + const bool AutoextendTop = ((m_Pos.y < 32) || (m_Pos2.y < 32)); + const bool AutoextendBottom = ((m_Pos.y >= MaxY) || (m_Pos2.y >= MaxY)); + + if(AutoextendTop) + { + const auto TopY = ViewParams->ViewPos.y - ViewParams->ShowDistance.y - 1000; + if(To.y < 32) + { + To.y = TopY; + } + else + { + From.y = TopY; + } + } + if(AutoextendBottom) + { + const auto BottomY = ViewParams->ViewPos.y + ViewParams->ShowDistance.y + 1000; + if(To.y >= MaxY) + { + To.y = BottomY; + } + else + { + From.y = BottomY; + } + } + } + } + + GameServer()->SnapLaserObject(CSnapContext(SnappingClientVersion), GetId(), To, From, StartTick, -1, ForcedShowOpen ? LASERTYPE_FREEZE : LASERTYPE_DOOR); + // TODO: CGameContext::SnapSwitchers() +} + +void CDoor::SetOpen(bool Open) +{ + if (m_Open == Open) + return; + + m_Open = Open; + SetCollisions(!Open); +} diff --git a/src/game/server/infclass/entities/ic_door.h b/src/game/server/infclass/entities/ic_door.h new file mode 100644 index 00000000..2513a6f1 --- /dev/null +++ b/src/game/server/infclass/entities/ic_door.h @@ -0,0 +1,27 @@ +/* (c) Shereef Marzouk. See "licence DDRace.txt" and the readme.txt in the root of the distribution for more information. */ +#ifndef GAME_SERVER_ENTITIES_DOOR_H +#define GAME_SERVER_ENTITIES_DOOR_H + +#include + +class CGameWorld; + +class CDoor : public CPlacedObject +{ +public: + CDoor(CGameContext *pGameContext, vec2 Pos, vec2 PosTo); + + void Destroy() override; + + void Reset() override; + void Snap(int SnappingClientId) override; + + bool IsOpen() const { return m_Open; } + void SetOpen(bool Open); + +protected: + void SetCollisions(bool Set); + bool m_Open = false; +}; + +#endif // GAME_SERVER_ENTITIES_DOOR_H diff --git a/src/game/server/infclass/entities/laser-teleport.cpp b/src/game/server/infclass/entities/laser-teleport.cpp index 4bff9e50..efc54485 100644 --- a/src/game/server/infclass/entities/laser-teleport.cpp +++ b/src/game/server/infclass/entities/laser-teleport.cpp @@ -46,6 +46,12 @@ std::optional CLaserTeleport::FindPortalPosition(CInfClassCharacter *pChar if(length(PortalShift) > 500.0f) PortalShift = PortalDir * 500.0f; + vec2 EndPoint{}; + if(pCharacter->GameServer()->Collision()->IntersectLineWithDoors(pCharacter->GetPos(), pCharacter->GetPos() + PortalShift, nullptr, &EndPoint)) + { + PortalShift = EndPoint - pCharacter->GetPos(); + } + float Iterator = length(PortalShift); while(Iterator > 0.0f) { diff --git a/src/game/server/infclass/infc_config_variables.h b/src/game/server/infclass/infc_config_variables.h index ad3dcdeb..36080af3 100644 --- a/src/game/server/infclass/infc_config_variables.h +++ b/src/game/server/infclass/infc_config_variables.h @@ -21,6 +21,7 @@ MACRO_CONFIG_STR(AboutContactsMatrix, about_contacts_matrix, 128, "https://infcl MACRO_CONFIG_STR(InfConverterId, inf_converter_id, 16, "v2", CFGFLAG_SERVER, "Map converter version id") MACRO_CONFIG_INT(InfConverterForceRegeneration, inf_converter_force_regeneration, 0, 0, 1, CFGFLAG_SERVER, "Always (re)generate client map (regardless of cache)") +MACRO_CONFIG_INT(SvShowOpenDoors, sv_show_open_doors, 0, 0, 1, CFGFLAG_SERVER, "Show open doors (0 = no, 1 = yes)") MACRO_CONFIG_INT(SvTimelimitInSeconds, sv_timelimit_in_seconds, 0, 0, 10000, CFGFLAG_SERVER, "Time limit in seconds (0 means 'fallback to sv_timelimit')") MACRO_CONFIG_INT(SvMaxDDNetVersion, sv_max_ddnet_version, 0, 0, 9999999, CFGFLAG_SERVER, "Automatically kick clients with DDNet version higher than specified") diff --git a/src/game/server/infclass/infcgamecontroller.cpp b/src/game/server/infclass/infcgamecontroller.cpp index aeb9ae8b..23ace99b 100644 --- a/src/game/server/infclass/infcgamecontroller.cpp +++ b/src/game/server/infclass/infcgamecontroller.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include @@ -2437,6 +2438,11 @@ void CInfClassGameController::ChatWitch(IConsole::IResult *pResult) } } +CDoor *CInfClassGameController::AddDoor(const vec2 &From, const vec2 &To) +{ + return new CDoor(GameServer(), From, To); +} + IConsole *CInfClassGameController::Console() const { return GameServer()->Console(); diff --git a/src/game/server/infclass/infcgamecontroller.h b/src/game/server/infclass/infcgamecontroller.h index a972e49e..97e63620 100644 --- a/src/game/server/infclass/infcgamecontroller.h +++ b/src/game/server/infclass/infcgamecontroller.h @@ -11,6 +11,7 @@ #include #include +class CDoor; class CGameWorld; class CHintMessage; class CInfClassCharacter; @@ -286,6 +287,8 @@ class CInfClassGameController : public IGameController static void ChatWitch(IConsole::IResult *pResult, void *pUserData); void ChatWitch(IConsole::IResult *pResult); + CDoor *AddDoor(const vec2 &From, const vec2 &To); + using IGameController::GameServer; CGameWorld *GameWorld(); IConsole *Console() const;