diff --git a/src/game/server/CMakeLists.txt b/src/game/server/CMakeLists.txt index 16dfcf289..ad975184c 100644 --- a/src/game/server/CMakeLists.txt +++ b/src/game/server/CMakeLists.txt @@ -1400,6 +1400,8 @@ target_sources_grouped( neo/neo_player.h neo/neo_smokelineofsightblocker.cpp neo/neo_smokelineofsightblocker.h + neo/neo_spawn_manager.cpp + neo/neo_spawn_manager.h neo/neo_smokegrenade.cpp neo/neo_smokegrenade.h neo/neo_te_tocflash.cpp diff --git a/src/game/server/gameinterface.cpp b/src/game/server/gameinterface.cpp index 97f6094c7..0019a2912 100644 --- a/src/game/server/gameinterface.cpp +++ b/src/game/server/gameinterface.cpp @@ -112,6 +112,10 @@ extern ConVar tf_mm_servermode; #include "NextBotManager.h" #endif +#ifdef NEO +#include "neo_spawn_manager.h" +#endif + #ifdef USES_ECON_ITEMS #include "econ_item_system.h" #endif // USES_ECON_ITEMS @@ -1180,6 +1184,10 @@ void CServerGameDLL::ServerActivate( edict_t *pEdictList, int edictCount, int cl #ifdef NEXT_BOT TheNextBots().OnMapLoaded(); #endif + +#ifdef NEO + NeoSpawnManager::Init(); +#endif } //----------------------------------------------------------------------------- @@ -1448,6 +1456,10 @@ void CServerGameDLL::LevelShutdown( void ) } #endif #endif + +#ifdef NEO + NeoSpawnManager::Deinit(); +#endif } //----------------------------------------------------------------------------- diff --git a/src/game/server/neo/neo_player.cpp b/src/game/server/neo/neo_player.cpp index d75a80409..c2e9698b9 100644 --- a/src/game/server/neo/neo_player.cpp +++ b/src/game/server/neo/neo_player.cpp @@ -39,6 +39,7 @@ #include "neo_player_shared.h" #include "bot/neo_bot.h" #include "nav_mesh.h" +#include "neo_spawn_manager.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" @@ -2917,35 +2918,7 @@ CBaseEntity* CNEO_Player::EntSelectSpawnPoint( void ) } } - pSpot = pLastSpawnPoint; - // Randomize the start spot - for (int i = random->RandomInt(1, 5); i > 0; i--) - pSpot = gEntList.FindEntityByClassname(pSpot, pSpawnpointName); - if (!pSpot) // skip over the null point - pSpot = gEntList.FindEntityByClassname(pSpot, pSpawnpointName); - - CBaseEntity *pFirstSpot = pSpot; - - do - { - if (pSpot) - { - // check if pSpot is valid - if (g_pGameRules->IsSpawnPointValid(pSpot, this)) - { - if (pSpot->GetLocalOrigin() == vec3_origin) - { - pSpot = gEntList.FindEntityByClassname(pSpot, pSpawnpointName); - continue; - } - - // if so, go to pSpot - goto ReturnSpot; - } - } - // increment pSpot - pSpot = gEntList.FindEntityByClassname(pSpot, pSpawnpointName); - } while (pSpot != pFirstSpot); // loop if we're not back to the start + pSpot = NeoSpawnManager::RequestSpawn(GetTeamNumber(), this); // we haven't found a place to spawn yet, so kill any guy at the first spawn point and spawn there if (pSpot) diff --git a/src/game/server/neo/neo_spawn_manager.cpp b/src/game/server/neo/neo_spawn_manager.cpp new file mode 100644 index 000000000..39bd00c80 --- /dev/null +++ b/src/game/server/neo/neo_spawn_manager.cpp @@ -0,0 +1,130 @@ +#include "neo_spawn_manager.h" + +#include +#include +#include + +#include "neo_gamerules.h" +#include "neo_player_spawnpoint.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +struct SpawnInfo +{ + CHandle handle; + bool isUsed; // Whether this spawn point has been spawned into for the current round. +}; + +class CBasePlayer; + +class CNEO_SpawnManager : public CGameEventListener +{ +public: + CNEO_SpawnManager(); + CNEOSpawnPoint* RequestSpawn(int team, CBasePlayer* player); + virtual void FireGameEvent(IGameEvent* event) override final; + + CUtlVector m_spawns; +} manager; + +CNEO_SpawnManager::CNEO_SpawnManager() +{ + m_spawns.EnsureCapacity(MAX_PLAYERS); +} + +void CNEO_SpawnManager::FireGameEvent(IGameEvent* event) +{ + Assert(!V_strcmp("round_start", event->GetName())); + for (int i = 0; i < manager.m_spawns.Count(); ++i) + { + auto& spawn = manager.m_spawns[i]; + if (!spawn.handle || !spawn.handle.IsValid()) + manager.m_spawns.Remove(i--); + else + spawn.isUsed = false; + } + manager.m_spawns.Shuffle(); +} + +namespace NeoSpawnManager +{ + void Init() + { + manager.ListenForGameEvent("round_start"); + } + + void Deinit() + { + manager.StopListeningForAllEvents(); + } + + CNEOSpawnPoint* RequestSpawn(int team, CBasePlayer* player) + { + // Nothing we can do to salvage this... This will fall back + // to spawning at info_player_start or related logic in the caller. + if (manager.m_spawns.IsEmpty()) + return nullptr; + + auto rules = NEORules(); + if (!rules) + { + Assert(false); + return nullptr; + } + + CNEOSpawnPoint* backup = nullptr; + auto idx = manager.m_spawns.FindPredicate( + [rules, team, player, &backup](const auto& spawn)->bool + { + if (!spawn.handle || !spawn.handle.IsValid()) + { + Assert(false); + return false; + } + + if (team != spawn.handle.Get()->GetOwningTeam()) + return false; + + // We know this spawn is valid and belongs to our team. + // Save it as backup, just in case we can't find a good fresh spawn. + backup = spawn.handle; + + if (spawn.isUsed) + return false; + + if (!rules->IsSpawnPointValid(spawn.handle, player)) + return false; + + return true; + }); + + if (idx == manager.m_spawns.InvalidIndex()) + { + // If we didn't find any free capzones, at least return an overlapping one. + // This can happen for maps with too few capzones for the amount of (re)spawns occurring. + // Can be nullptr if we got >0 spawns but none of them were considered valid, + // in which case it's up to the caller to handle. + return backup; + } + + manager.m_spawns[idx].isUsed = true; + return manager.m_spawns[idx].handle; + } + + void Register(CNEOSpawnPoint* spawn) + { + manager.m_spawns.AddToTail({ + .handle{spawn}, + .isUsed{false}}); + } + void Unregister(CNEOSpawnPoint* spawn) + { + auto idx = manager.m_spawns.FindPredicate([spawn](const auto& s)->bool + { + return spawn == s.handle; + }); + if (idx != manager.m_spawns.InvalidIndex()) + manager.m_spawns.Remove(idx); + } +} diff --git a/src/game/server/neo/neo_spawn_manager.h b/src/game/server/neo/neo_spawn_manager.h new file mode 100644 index 000000000..ea0c1b7b4 --- /dev/null +++ b/src/game/server/neo/neo_spawn_manager.h @@ -0,0 +1,15 @@ +#pragma once + +#include "neo_player_spawnpoint.h" + +class CBasePlayer; +class CNEOSpawnPoint; +namespace NeoSpawnManager +{ + CNEOSpawnPoint* RequestSpawn(int team, CBasePlayer* player); + + void Init(); + void Deinit(); + void Register(CNEOSpawnPoint*); + void Unregister(CNEOSpawnPoint*); +}; diff --git a/src/game/shared/gamerules.cpp b/src/game/shared/gamerules.cpp index 55f837248..3c2c43277 100644 --- a/src/game/shared/gamerules.cpp +++ b/src/game/shared/gamerules.cpp @@ -236,7 +236,7 @@ bool CGameRules::IsSpawnPointValid( CBaseEntity *pSpot, CBasePlayer *pPlayer ) CBaseEntity *ent = NULL; #if defined NEO && defined GAME_DLL - if ( auto* pNEOSpot = dynamic_cast( pSpot ) ) + if ( auto* pNEOSpot = assert_cast( pSpot ) ) { if ( pNEOSpot->m_bDisabled ) { diff --git a/src/game/shared/neo/neo_player_spawnpoint.cpp b/src/game/shared/neo/neo_player_spawnpoint.cpp index ab78130bb..80518e766 100644 --- a/src/game/shared/neo/neo_player_spawnpoint.cpp +++ b/src/game/shared/neo/neo_player_spawnpoint.cpp @@ -3,6 +3,10 @@ #include "neo_gamerules.h" +#ifdef GAME_DLL +#include "neo_spawn_manager.h" +#endif + // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" @@ -56,7 +60,9 @@ CNEOSpawnPoint::CNEOSpawnPoint() CNEOSpawnPoint::~CNEOSpawnPoint() { - +#ifdef GAME_DLL + NeoSpawnManager::Unregister(this); +#endif } void CNEOSpawnPoint::Spawn() @@ -71,6 +77,10 @@ void CNEOSpawnPoint::Spawn() (m_iOwningTeam == TEAM_JINRAI ? "Jinrai" : "NSF"), GetAbsOrigin().x, GetAbsOrigin().y, GetAbsOrigin().z); #endif + +#ifdef GAME_DLL + NeoSpawnManager::Register(this); +#endif } #ifdef GAME_DLL diff --git a/src/game/shared/neo/neo_player_spawnpoint.h b/src/game/shared/neo/neo_player_spawnpoint.h index 83cb6d27a..d9650bab4 100644 --- a/src/game/shared/neo/neo_player_spawnpoint.h +++ b/src/game/shared/neo/neo_player_spawnpoint.h @@ -4,6 +4,7 @@ #pragma once #endif +#include "cbase.h" #include "baseentity_shared.h" #include "baseplayer_shared.h" @@ -26,7 +27,8 @@ class CNEOSpawnPoint : public CBaseEntity CNEOSpawnPoint(); ~CNEOSpawnPoint(); - virtual void Spawn(); + virtual void Spawn() override; + int GetOwningTeam() const { return m_iOwningTeam; } #ifdef GAME_DLL bool m_bDisabled;