6150: Recreate all entities when restarting round, fix server crash on maps with more than 64 spawn points of same type r=def- a=Robyt3



## Checklist

- [X] Tested the change ingame
- [ ] Provided screenshots if it is a visual change
- [ ] Tested in combination with possibly related configuration options
- [ ] Written a unit test (especially base/) or added coverage to integration test
- [ ] Considered possible null pointers and out of bounds array indexing
- [X] Changed no physics that affect existing maps
- [ ] Tested the change with [ASan+UBSan or valgrind's memcheck](https://github.com/ddnet/ddnet/#using-addresssanitizer--undefinedbehavioursanitizer-or-valgrinds-memcheck) (optional)


Co-authored-by: Robert Müller <robytemueller@gmail.com>
This commit is contained in:
bors[bot] 2022-12-17 12:36:40 +00:00 committed by GitHub
commit f51dd365e4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 147 additions and 161 deletions

View file

@ -40,6 +40,11 @@ void CDoor::ResetCollision()
}
}
void CDoor::Reset()
{
m_MarkedForDestroy = true;
}
void CDoor::Snap(int SnappingClient)
{
if(NetworkClipped(SnappingClient, m_Pos) && NetworkClipped(SnappingClient, m_To))

View file

@ -17,6 +17,7 @@ public:
CDoor(CGameWorld *pGameWorld, vec2 Pos, float Rotation, int Length,
int Number);
void Reset() override;
void Snap(int SnappingClient) override;
};

View file

@ -21,13 +21,12 @@ CPickup::CPickup(CGameWorld *pGameWorld, int Type, int SubType, int Layer, int N
m_Layer = Layer;
m_Number = Number;
Reset();
GameWorld()->InsertEntity(this);
}
void CPickup::Reset()
{
m_MarkedForDestroy = true;
}
void CPickup::Tick()

View file

@ -51,8 +51,7 @@ CProjectile::CProjectile(
void CProjectile::Reset()
{
if(m_LifeSpan > -2)
m_MarkedForDestroy = true;
m_MarkedForDestroy = true;
}
vec2 CProjectile::GetPos(float Time)

View file

@ -1547,7 +1547,6 @@ void CGameContext::OnClientDrop(int ClientID, const char *pReason)
delete m_apPlayers[ClientID];
m_apPlayers[ClientID] = 0;
//(void)m_pController->CheckTeamBalance();
m_VoteUpdate = true;
// update spectator modes
@ -2046,7 +2045,6 @@ void CGameContext::OnMessage(int MsgID, CUnpacker *pUnpacker, int ClientID)
m_apPlayers[ClientID]->m_Last_KickVote = time_get();
return;
}
//else if(!g_Config.m_SvVoteKick)
else if(!g_Config.m_SvVoteKick && !Authed) // allow admins to call kick votes even if they are forbidden
{
SendChatTarget(ClientID, "Server does not allow voting to kick players");
@ -3242,7 +3240,7 @@ void CGameContext::OnConsoleInit()
#include <game/ddracechat.h>
}
void CGameContext::OnInit(/*class IKernel *pKernel*/)
void CGameContext::OnInit()
{
m_pServer = Kernel()->RequestInterface<IServer>();
m_pConfig = Kernel()->RequestInterface<IConfigManager>()->Values();
@ -3264,9 +3262,6 @@ void CGameContext::OnInit(/*class IKernel *pKernel*/)
DeleteTempfile();
//if(!data) // only load once
//data = load_data_from_memory(internal_data);
for(int i = 0; i < NUM_NETOBJTYPES; i++)
Server()->SnapSetStaticsize(i, m_NetObjHandler.GetObjSize(i));
@ -3281,10 +3276,6 @@ void CGameContext::OnInit(/*class IKernel *pKernel*/)
Server()->GetMapInfo(aMapName, sizeof(aMapName), &MapSize, &MapSha256, &MapCrc);
m_MapBugs = GetMapBugs(aMapName, MapSize, MapSha256);
// reset everything here
//world = new GAMEWORLD;
//players = new CPlayer[MAX_CLIENTS];
// Reset Tunezones
CTuningParams TuningParams;
for(int i = 0; i < NUM_TUNEZONES; i++)
@ -3440,108 +3431,8 @@ void CGameContext::OnInit(/*class IKernel *pKernel*/)
m_pScore = new CScore(this, ((CServer *)Server())->DbPool());
}
// setup core world
//for(int i = 0; i < MAX_CLIENTS; i++)
// game.players[i].core.world = &game.world.core;
// create all entities from the game layer
CMapItemLayerTilemap *pTileMap = m_Layers.GameLayer();
CTile *pTiles = (CTile *)Kernel()->RequestInterface<IMap>()->GetData(pTileMap->m_Data);
CTile *pFront = 0;
CSwitchTile *pSwitch = 0;
if(m_Layers.FrontLayer())
pFront = (CTile *)Kernel()->RequestInterface<IMap>()->GetData(m_Layers.FrontLayer()->m_Front);
if(m_Layers.SwitchLayer())
pSwitch = (CSwitchTile *)Kernel()->RequestInterface<IMap>()->GetData(m_Layers.SwitchLayer()->m_Switch);
for(int y = 0; y < pTileMap->m_Height; y++)
{
for(int x = 0; x < pTileMap->m_Width; x++)
{
int Index = pTiles[y * pTileMap->m_Width + x].m_Index;
if(Index == TILE_OLDLASER)
{
g_Config.m_SvOldLaser = 1;
dbg_msg("game_layer", "found old laser tile");
}
else if(Index == TILE_NPC)
{
m_Tuning.Set("player_collision", 0);
dbg_msg("game_layer", "found no collision tile");
}
else if(Index == TILE_EHOOK)
{
g_Config.m_SvEndlessDrag = 1;
dbg_msg("game_layer", "found unlimited hook time tile");
}
else if(Index == TILE_NOHIT)
{
g_Config.m_SvHit = 0;
dbg_msg("game_layer", "found no weapons hitting others tile");
}
else if(Index == TILE_NPH)
{
m_Tuning.Set("player_hooking", 0);
dbg_msg("game_layer", "found no player hooking tile");
}
if(Index >= ENTITY_OFFSET)
{
vec2 Pos(x * 32.0f + 16.0f, y * 32.0f + 16.0f);
m_pController->OnEntity(Index - ENTITY_OFFSET, Pos, LAYER_GAME, pTiles[y * pTileMap->m_Width + x].m_Flags);
}
if(pFront)
{
Index = pFront[y * pTileMap->m_Width + x].m_Index;
if(Index == TILE_OLDLASER)
{
g_Config.m_SvOldLaser = 1;
dbg_msg("front_layer", "found old laser tile");
}
else if(Index == TILE_NPC)
{
m_Tuning.Set("player_collision", 0);
dbg_msg("front_layer", "found no collision tile");
}
else if(Index == TILE_EHOOK)
{
g_Config.m_SvEndlessDrag = 1;
dbg_msg("front_layer", "found unlimited hook time tile");
}
else if(Index == TILE_NOHIT)
{
g_Config.m_SvHit = 0;
dbg_msg("front_layer", "found no weapons hitting others tile");
}
else if(Index == TILE_NPH)
{
m_Tuning.Set("player_hooking", 0);
dbg_msg("front_layer", "found no player hooking tile");
}
if(Index >= ENTITY_OFFSET)
{
vec2 Pos(x * 32.0f + 16.0f, y * 32.0f + 16.0f);
m_pController->OnEntity(Index - ENTITY_OFFSET, Pos, LAYER_FRONT, pFront[y * pTileMap->m_Width + x].m_Flags);
}
}
if(pSwitch)
{
Index = pSwitch[y * pTileMap->m_Width + x].m_Type;
// TODO: Add off by default door here
// if (Index == TILE_DOOR_OFF)
if(Index >= ENTITY_OFFSET)
{
vec2 Pos(x * 32.0f + 16.0f, y * 32.0f + 16.0f);
m_pController->OnEntity(Index - ENTITY_OFFSET, Pos, LAYER_SWITCH, pSwitch[y * pTileMap->m_Width + x].m_Flags, pSwitch[y * pTileMap->m_Width + x].m_Number);
}
}
}
}
//game.world.insert_entity(game.Controller);
CreateAllEntities(true);
if(GIT_SHORTREV_HASH)
Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "git-revision", GIT_SHORTREV_HASH);
@ -3557,6 +3448,107 @@ void CGameContext::OnInit(/*class IKernel *pKernel*/)
#endif
}
void CGameContext::CreateAllEntities(bool Initial)
{
const CMapItemLayerTilemap *pTileMap = m_Layers.GameLayer();
const CTile *pTiles = static_cast<CTile *>(Kernel()->RequestInterface<IMap>()->GetData(pTileMap->m_Data));
const CTile *pFront = nullptr;
if(m_Layers.FrontLayer())
pFront = static_cast<CTile *>(Kernel()->RequestInterface<IMap>()->GetData(m_Layers.FrontLayer()->m_Front));
const CSwitchTile *pSwitch = nullptr;
if(m_Layers.SwitchLayer())
pSwitch = static_cast<CSwitchTile *>(Kernel()->RequestInterface<IMap>()->GetData(m_Layers.SwitchLayer()->m_Switch));
for(int y = 0; y < pTileMap->m_Height; y++)
{
for(int x = 0; x < pTileMap->m_Width; x++)
{
const int Index = y * pTileMap->m_Width + x;
// Game layer
{
const int GameIndex = pTiles[Index].m_Index;
if(GameIndex == TILE_OLDLASER)
{
g_Config.m_SvOldLaser = 1;
dbg_msg("game_layer", "found old laser tile");
}
else if(GameIndex == TILE_NPC)
{
m_Tuning.Set("player_collision", 0);
dbg_msg("game_layer", "found no collision tile");
}
else if(GameIndex == TILE_EHOOK)
{
g_Config.m_SvEndlessDrag = 1;
dbg_msg("game_layer", "found unlimited hook time tile");
}
else if(GameIndex == TILE_NOHIT)
{
g_Config.m_SvHit = 0;
dbg_msg("game_layer", "found no weapons hitting others tile");
}
else if(GameIndex == TILE_NPH)
{
m_Tuning.Set("player_hooking", 0);
dbg_msg("game_layer", "found no player hooking tile");
}
else if(GameIndex >= ENTITY_OFFSET)
{
m_pController->OnEntity(GameIndex - ENTITY_OFFSET, x, y, LAYER_GAME, pTiles[Index].m_Flags, Initial);
}
}
if(pFront)
{
const int FrontIndex = pFront[Index].m_Index;
if(FrontIndex == TILE_OLDLASER)
{
g_Config.m_SvOldLaser = 1;
dbg_msg("front_layer", "found old laser tile");
}
else if(FrontIndex == TILE_NPC)
{
m_Tuning.Set("player_collision", 0);
dbg_msg("front_layer", "found no collision tile");
}
else if(FrontIndex == TILE_EHOOK)
{
g_Config.m_SvEndlessDrag = 1;
dbg_msg("front_layer", "found unlimited hook time tile");
}
else if(FrontIndex == TILE_NOHIT)
{
g_Config.m_SvHit = 0;
dbg_msg("front_layer", "found no weapons hitting others tile");
}
else if(FrontIndex == TILE_NPH)
{
m_Tuning.Set("player_hooking", 0);
dbg_msg("front_layer", "found no player hooking tile");
}
else if(FrontIndex >= ENTITY_OFFSET)
{
m_pController->OnEntity(FrontIndex - ENTITY_OFFSET, x, y, LAYER_FRONT, pFront[Index].m_Flags, Initial);
}
}
if(pSwitch)
{
const int SwitchType = pSwitch[Index].m_Type;
// TODO: Add off by default door here
// if(SwitchType == TILE_DOOR_OFF)
if(SwitchType >= ENTITY_OFFSET)
{
m_pController->OnEntity(SwitchType - ENTITY_OFFSET, x, y, LAYER_SWITCH, pSwitch[Index].m_Flags, Initial, pSwitch[Index].m_Number);
}
}
}
}
}
void CGameContext::DeleteTempfile()
{
if(m_aDeleteTempfile[0] != 0)
@ -3864,16 +3856,16 @@ void CGameContext::SendRecord(int ClientID)
}
}
int CGameContext::ProcessSpamProtection(int ClientID, bool RespectChatInitialDelay)
bool CGameContext::ProcessSpamProtection(int ClientID, bool RespectChatInitialDelay)
{
if(!m_apPlayers[ClientID])
return 0;
return false;
if(g_Config.m_SvSpamprotection && m_apPlayers[ClientID]->m_LastChat && m_apPlayers[ClientID]->m_LastChat + Server()->TickSpeed() * g_Config.m_SvChatDelay > Server()->Tick())
return 1;
return true;
else if(g_Config.m_SvDnsblChat && Server()->DnsblBlack(ClientID))
{
SendChatTarget(ClientID, "Players are not allowed to chat from VPNs at this time");
return 1;
return true;
}
else
m_apPlayers[ClientID]->m_LastChat = Server()->Tick();
@ -3896,17 +3888,17 @@ int CGameContext::ProcessSpamProtection(int ClientID, bool RespectChatInitialDel
char aBuf[128];
str_format(aBuf, sizeof aBuf, "You are not permitted to talk for the next %d seconds.", Muted);
SendChatTarget(ClientID, aBuf);
return 1;
return true;
}
if(g_Config.m_SvSpamMuteDuration && (m_apPlayers[ClientID]->m_ChatScore += g_Config.m_SvChatPenalty) > g_Config.m_SvChatThreshold)
{
Mute(&Addr, g_Config.m_SvSpamMuteDuration, Server()->ClientName(ClientID));
m_apPlayers[ClientID]->m_ChatScore = 0;
return 1;
return true;
}
return 0;
return false;
}
int CGameContext::GetDDRaceTeam(int ClientID)
@ -3934,16 +3926,14 @@ bool CheckClientID2(int ClientID)
void CGameContext::Whisper(int ClientID, char *pStr)
{
char *pName;
char *pMessage;
int Error = 0;
if(ProcessSpamProtection(ClientID))
return;
pStr = str_skip_whitespaces(pStr);
char *pName;
int Victim;
bool Error = false;
// add token
if(*pStr == '"')
@ -3967,7 +3957,7 @@ void CGameContext::Whisper(int ClientID, char *pStr)
}
else if(pStr[0] == 0)
{
Error = 1;
Error = true;
break;
}
@ -3995,7 +3985,7 @@ void CGameContext::Whisper(int ClientID, char *pStr)
{
if(pStr[0] == 0)
{
Error = 1;
Error = true;
break;
}
if(pStr[0] == ' ')
@ -4016,14 +4006,13 @@ void CGameContext::Whisper(int ClientID, char *pStr)
if(pStr[0] != ' ')
{
Error = 1;
Error = true;
}
*pStr = 0;
pStr++;
pMessage = pStr;
char *pMessage = pStr;
char aBuf[256];
if(Error)

View file

@ -187,6 +187,8 @@ public:
char m_aaZoneEnterMsg[NUM_TUNEZONES][256]; // 0 is used for switching from or to area without tunings
char m_aaZoneLeaveMsg[NUM_TUNEZONES][256];
void CreateAllEntities(bool Initial);
char m_aDeleteTempfile[128];
void DeleteTempfile();
@ -287,7 +289,7 @@ public:
void OnPreTickTeehistorian() override;
bool OnClientDDNetVersionKnown(int ClientID);
void FillAntibot(CAntibotRoundData *pData) override;
int ProcessSpamProtection(int ClientID, bool RespectChatInitialDelay = true);
bool ProcessSpamProtection(int ClientID, bool RespectChatInitialDelay = true);
int GetDDRaceTeam(int ClientID);
// Describes the time when the first player joined the server.
int64_t m_NonEmptySince;

View file

@ -37,10 +37,6 @@ IGameController::IGameController(class CGameContext *pGameServer)
m_UnbalancedTick = -1;
m_ForceBalanced = false;
m_aNumSpawnPoints[0] = 0;
m_aNumSpawnPoints[1] = 0;
m_aNumSpawnPoints[2] = 0;
m_CurrentRecord = 0;
}
@ -119,15 +115,14 @@ void IGameController::EvaluateSpawnType(CSpawnEval *pEval, int Type, int DDTeam)
for(int j = 0; j < 2 && !pEval->m_Got; j++)
{
// get spawn point
for(int i = 0; i < m_aNumSpawnPoints[Type]; i++)
for(const vec2 &SpawnPoint : m_avSpawnPoints[Type])
{
vec2 P = m_aaSpawnPoints[Type][i];
vec2 P = SpawnPoint;
if(j == 0)
{
// check if the position is occupado
CEntity *apEnts[MAX_CLIENTS];
int Num = GameServer()->m_World.FindEntities(m_aaSpawnPoints[Type][i], 64, apEnts, MAX_CLIENTS, CGameWorld::ENTTYPE_CHARACTER);
int Num = GameServer()->m_World.FindEntities(SpawnPoint, 64, apEnts, MAX_CLIENTS, CGameWorld::ENTTYPE_CHARACTER);
vec2 aPositions[5] = {vec2(0.0f, 0.0f), vec2(-32.0f, 0.0f), vec2(0.0f, -32.0f), vec2(32.0f, 0.0f), vec2(0.0f, 32.0f)}; // start, left, up, right, down
int Result = -1;
for(int Index = 0; Index < 5 && Result == -1; ++Index)
@ -138,8 +133,8 @@ void IGameController::EvaluateSpawnType(CSpawnEval *pEval, int Type, int DDTeam)
for(int c = 0; c < Num; ++c)
{
CCharacter *pChr = static_cast<CCharacter *>(apEnts[c]);
if(GameServer()->Collision()->CheckPoint(m_aaSpawnPoints[Type][i] + aPositions[Index]) ||
distance(pChr->m_Pos, m_aaSpawnPoints[Type][i] + aPositions[Index]) <= pChr->GetProximityRadius())
if(GameServer()->Collision()->CheckPoint(SpawnPoint + aPositions[Index]) ||
distance(pChr->m_Pos, SpawnPoint + aPositions[Index]) <= pChr->GetProximityRadius())
{
Result = -1;
break;
@ -165,12 +160,11 @@ void IGameController::EvaluateSpawnType(CSpawnEval *pEval, int Type, int DDTeam)
bool IGameController::CanSpawn(int Team, vec2 *pOutPos, int DDTeam)
{
CSpawnEval Eval;
// spectators can't spawn
if(Team == TEAM_SPECTATORS)
return false;
CSpawnEval Eval;
EvaluateSpawnType(&Eval, 0, DDTeam);
EvaluateSpawnType(&Eval, 1, DDTeam);
EvaluateSpawnType(&Eval, 2, DDTeam);
@ -179,14 +173,12 @@ bool IGameController::CanSpawn(int Team, vec2 *pOutPos, int DDTeam)
return Eval.m_Got;
}
bool IGameController::OnEntity(int Index, vec2 Pos, int Layer, int Flags, int Number)
bool IGameController::OnEntity(int Index, int x, int y, int Layer, int Flags, bool Initial, int Number)
{
if(Index < 0)
return false;
dbg_assert(Index >= 0, "Invalid entity index");
const vec2 Pos(x * 32.0f + 16.0f, y * 32.0f + 16.0f);
int x, y;
x = (Pos.x - 16.0f) / 32.0f;
y = (Pos.y - 16.0f) / 32.0f;
int aSides[8];
aSides[0] = GameServer()->Collision()->Entity(x, y + 1, Layer);
aSides[1] = GameServer()->Collision()->Entity(x + 1, y + 1, Layer);
@ -197,13 +189,11 @@ bool IGameController::OnEntity(int Index, vec2 Pos, int Layer, int Flags, int Nu
aSides[6] = GameServer()->Collision()->Entity(x - 1, y, Layer);
aSides[7] = GameServer()->Collision()->Entity(x - 1, y + 1, Layer);
if(Index >= ENTITY_SPAWN && Index <= ENTITY_SPAWN_BLUE)
if(Index >= ENTITY_SPAWN && Index <= ENTITY_SPAWN_BLUE && Initial)
{
int Type = Index - ENTITY_SPAWN;
m_aaSpawnPoints[Type][m_aNumSpawnPoints[Type]] = Pos;
m_aNumSpawnPoints[Type] = minimum(m_aNumSpawnPoints[Type] + 1, (int)std::size(m_aaSpawnPoints[0]));
const int Type = Index - ENTITY_SPAWN;
m_avSpawnPoints[Type].push_back(Pos);
}
else if(Index == ENTITY_DOOR)
{
for(int i = 0; i < 8; i++)
@ -322,7 +312,6 @@ bool IGameController::OnEntity(int Index, vec2 Pos, int Layer, int Flags, int Nu
aSides2[6] = GameServer()->Collision()->Entity(x - 2, y, Layer);
aSides2[7] = GameServer()->Collision()->Entity(x - 2, y + 2, Layer);
float AngularSpeed = 0.0f;
int Ind = Index - ENTITY_LASER_STOP;
int M;
if(Ind < 0)
@ -335,6 +324,7 @@ bool IGameController::OnEntity(int Index, vec2 Pos, int Layer, int Flags, int Nu
else
M = -1;
float AngularSpeed = 0.0f;
if(Ind == 0)
AngularSpeed = 0.0f;
else if(Ind == 1)
@ -454,8 +444,6 @@ const char *IGameController::GetTeamName(int Team)
return "spectators";
}
//static bool IsSeparator(char c) { return c == ';' || c == ' ' || c == ',' || c == '\t'; }
void IGameController::StartRound()
{
ResetGame();

View file

@ -6,6 +6,8 @@
#include <base/vmath.h>
#include <engine/map.h>
#include <vector>
/*
Class: Game Controller
Controls the main game logic. Keeping track of team and player score,
@ -15,8 +17,7 @@ class IGameController
{
friend class CSaveTeam; // need access to GameServer() and Server()
vec2 m_aaSpawnPoints[3][64];
int m_aNumSpawnPoints[3];
std::vector<vec2> m_avSpawnPoints[3];
class CGameContext *m_pGameServer;
class CConfig *m_pConfig;
@ -103,7 +104,7 @@ public:
Returns:
bool?
*/
virtual bool OnEntity(int Index, vec2 Pos, int Layer, int Flags, int Number = 0);
virtual bool OnEntity(int Index, int x, int y, int Layer, int Flags, bool Initial, int Number = 0);
virtual void OnPlayerConnect(class CPlayer *pPlayer);
virtual void OnPlayerDisconnect(class CPlayer *pPlayer, const char *pReason);

View file

@ -146,6 +146,8 @@ void CGameWorld::Reset()
RemoveEntities();
m_ResetRequested = false;
GameServer()->CreateAllEntities(false);
}
void CGameWorld::RemoveEntities()