ZBot: Implemented immediate reloading of nav data after bot analysis, without requiring a full map restart

ZBot: Added cleanup for dangling navigation pointers in bots, enabling seamless map reanalysis
Minor refactoring
This commit is contained in:
s1lentq 2025-04-04 02:41:19 +07:00
parent f5cc6f3a8b
commit 6f70d6dd8e
16 changed files with 355 additions and 75 deletions

View File

@ -900,3 +900,129 @@ float CCSBot::GetRangeToFarthestEscortedHostage() const
return away.m_farRange;
}
// Remove all occurrences of a given area from the path list
void CCSBot::RemovePath(CNavArea *area)
{
int i = 0;
while (i < m_pathLength)
{
if (m_path[i].area == area)
{
// If this area is linked to a ladder, clear the reference
if (m_path[i].ladder == m_pathLadder)
m_pathLadder = nullptr;
m_pathLength--;
// adjust the current path index if the removed element is being used
if (i == m_pathIndex)
{
if (i > 0)
m_pathIndex = i - 1;
else
m_pathIndex = 0;
}
if (m_pathLength != i)
Q_memmove(&m_path[i], &m_path[i + 1], (m_pathLength - i) * sizeof(m_path[0]));
// clear the slot
Q_memset(&m_path[m_pathLength], 0, sizeof(m_path[m_pathLength]));
}
else
{
i++;
}
}
}
// Remove a hiding spot from the checked spots list
void CCSBot::RemoveHidingSpot(HidingSpot *spot)
{
int i = 0;
while (i < m_pathLength)
{
if (m_checkedHidingSpot[i].spot == spot)
{
m_checkedHidingSpotCount--;
if (m_checkedHidingSpotCount != i)
Q_memmove(&m_checkedHidingSpot[i], &m_checkedHidingSpot[i + 1], (m_checkedHidingSpotCount - i) * sizeof(m_checkedHidingSpot[0]));
// clear the slot
Q_memset(&m_checkedHidingSpot[m_checkedHidingSpotCount], 0, sizeof(m_checkedHidingSpot[m_checkedHidingSpotCount]));
}
else
{
i++;
}
}
}
// Handle navigation-related cleanup when a nav area, spot, or encounter is destroyed
void CCSBot::OnDestroyNavDataNotify(NavNotifyDestroyType navNotifyType, void *dead)
{
switch (navNotifyType)
{
case NAV_NOTIFY_DESTROY_AREA:
{
CNavArea *area = static_cast<CNavArea *>(dead);
// If the destroyed area was linked to a spot encounter, clear it
if (m_spotEncounter)
{
if (m_spotEncounter->from.area == area || m_spotEncounter->to.area == area)
m_spotEncounter = nullptr;
}
RemovePath(area);
// Invalidate any references to the destroyed area
if (m_noiseArea == area)
m_noiseArea = nullptr;
if (m_currentArea == area)
m_currentArea = nullptr;
if (m_lastKnownArea == area)
m_lastKnownArea = nullptr;
if (m_hideState.GetSearchArea() == area)
m_hideState.SetSearchArea(nullptr);
if (m_huntState.GetHuntArea() == area)
m_huntState.ClearHuntArea();
break;
}
case NAV_NOTIFY_DESTROY_SPOT_ENCOUNTER:
{
CNavArea *area = static_cast<CNavArea *>(dead);
// Remove the encounter if it references the destroyed area
if (m_spotEncounter && area->HasSpotEncounter(m_spotEncounter))
m_spotEncounter = nullptr;
break;
}
case NAV_NOTIFY_DESTROY_SPOT:
{
HidingSpot *spot = static_cast<HidingSpot *>(dead);
// Remove the destroyed hiding spot from the spot order list
if (m_spotEncounter)
{
SpotOrderList &spotOrderList = m_spotEncounter->spotList;
spotOrderList.erase(std::remove_if(spotOrderList.begin(), spotOrderList.end(), [&](SpotOrder &spotOrder) {
return spotOrder.spot == spot;
}
), spotOrderList.end());
}
RemoveHidingSpot(spot);
break;
}
}
}

View File

@ -71,6 +71,7 @@ public:
virtual const char *GetName() const { return "Hunt"; }
void ClearHuntArea() { m_huntArea = nullptr; }
CNavArea *GetHuntArea() { return m_huntArea; }
private:
CNavArea *m_huntArea;
@ -204,6 +205,7 @@ public:
const Vector &GetHidingSpot() const { return m_hidingSpot; }
void SetSearchArea(CNavArea *area) { m_searchFromArea = area; }
CNavArea *GetSearchArea() { return m_searchFromArea; }
void SetSearchRange(float range) { m_range = range; }
void SetDuration(float time) { m_duration = time; }
@ -544,6 +546,10 @@ public:
float GetFeetZ() const; // return Z of bottom of feet
void OnDestroyNavDataNotify(NavNotifyDestroyType navNotifyType, void *dead);
void RemovePath(CNavArea *area);
void RemoveHidingSpot(HidingSpot *spot);
enum PathResult
{
PROGRESSING, // we are moving along the path

View File

@ -337,7 +337,7 @@ void CCSBot::ResetValues()
// NOTE: For some reason, this can be called twice when a bot is added.
void CCSBot::SpawnBot()
{
TheCSBots()->ValidateMapData();
TheCSBots()->LoadNavigationMap();
ResetValues();
Q_strlcpy(m_name, STRING(pev->netname));

View File

@ -477,31 +477,24 @@ void CCSBot::StartSaveProcess()
void CCSBot::UpdateSaveProcess()
{
char msg[256];
char cmd[128];
char gd[64]{};
GET_GAME_DIR(gd);
char filename[MAX_OSPATH];
Q_snprintf(filename, sizeof(filename), "%s\\%s", gd, TheBots->GetNavMapFilename());
HintMessageToAllPlayers("Saving...");
SaveNavigationMap(filename);
char msg[256]{};
Q_snprintf(msg, sizeof(msg), "Navigation file '%s' saved.", filename);
HintMessageToAllPlayers(msg);
CONSOLE_ECHO("%s\n", msg);
hideProgressMeter();
StartNormalProcess();
#ifndef REGAMEDLL_FIXES
Q_snprintf(cmd, sizeof(cmd), "map %s\n", STRING(gpGlobals->mapname));
#else
Q_snprintf(cmd, sizeof(cmd), "changelevel %s\n", STRING(gpGlobals->mapname));
#endif
SERVER_COMMAND(cmd);
// tell bot manager that the analysis is completed
if (TheCSBots())
TheCSBots()->AnalysisCompleted();
}
void CCSBot::StartNormalProcess()

View File

@ -231,13 +231,12 @@ bool CCSBotManager::IsOnOffense(CBasePlayer *pPlayer) const
// Invoked when a map has just been loaded
void CCSBotManager::ServerActivate()
{
DestroyNavigationMap();
m_isMapDataLoaded = false;
m_zoneCount = 0;
m_gameScenario = SCENARIO_DEATHMATCH;
ValidateMapData();
LoadNavigationMap();
RestartRound();
m_isLearningMap = false;
@ -591,7 +590,8 @@ void CCSBotManager::ServerCommand(const char *pcmd)
}
else if (FStrEq(pcmd, "bot_nav_load"))
{
ValidateMapData();
m_isMapDataLoaded = false; // force nav reload
LoadNavigationMap();
}
else if (FStrEq(pcmd, "bot_nav_use_place"))
{
@ -1144,7 +1144,6 @@ private:
CCSBotManager::Zone *m_zone;
};
#ifdef REGAMEDLL_ADD
LINK_ENTITY_TO_CLASS(info_spawn_point, CPointEntity, CCSPointEntity)
inline bool IsFreeSpace(Vector vecOrigin, int iHullNumber, edict_t *pSkipEnt = nullptr)
@ -1163,6 +1162,9 @@ inline bool pointInRadius(Vector vecOrigin, float radius)
CBaseEntity *pEntity = nullptr;
while ((pEntity = UTIL_FindEntityInSphere(pEntity, vecOrigin, radius)))
{
if (!UTIL_IsValidEntity(pEntity->edict()))
continue; // ignore the entity marked for deletion
if (FClassnameIs(pEntity->edict(), "info_spawn_point"))
return true;
}
@ -1171,12 +1173,10 @@ inline bool pointInRadius(Vector vecOrigin, float radius)
}
// a simple algorithm that searches for the farthest point (so that the player does not look at the wall)
inline Vector GetBestAngle(const Vector &vecStart)
inline bool GetIdealLookYawForSpawnPoint(const Vector &vecStart, float &flIdealLookYaw)
{
const float ANGLE_STEP = 30.0f;
float bestAngle = 0.0f;
float bestDistance = 0.0f;
Vector vecBestAngle = Vector(0, -1, 0);
for (float angleYaw = 0.0f; angleYaw <= 360.0f; angleYaw += ANGLE_STEP)
{
@ -1187,15 +1187,14 @@ inline Vector GetBestAngle(const Vector &vecStart)
UTIL_TraceLine(vecStart, vecEnd, ignore_monsters, nullptr, &tr);
float distance = (vecStart - tr.vecEndPos).Length();
if (distance > bestDistance)
{
bestDistance = distance;
vecBestAngle.y = angleYaw;
flIdealLookYaw = angleYaw;
}
}
return vecBestAngle;
return bestDistance > 0.0f;
}
// this function from leaked csgo sources 2020y
@ -1231,58 +1230,69 @@ inline bool IsValidArea(CNavArea *area)
return false;
}
void GetSpawnPositions()
{
const float MIN_AREA_SIZE = 32.0f;
const int MAX_SPAWNS_POINTS = 128;
const float MAX_SLOPE = 0.85f;
#ifdef REGAMEDLL_ADD
// Generates spawn points (info_spawn_point entities) for players and bots based on the navigation map data
// It uses the navigation areas to find valid spots for spawn points considering factors like area size, slope,
// available free space, and distance from other spawn points.
void GenerateSpawnPointsFromNavData()
{
// Remove any existing spawn points
UTIL_RemoveOther("info_spawn_point");
const int MAX_SPAWNS_POINTS = 128; // Max allowed spawn points
const float MAX_SLOPE = 0.85f; // Maximum slope allowed for a spawn point
const float MIN_AREA_SIZE = 32.0f; // Minimum area size for a valid spawn point
const float MIN_NEARBY_SPAWNPOINT = 128.0f; // Minimum distance between spawn point
// Total number of spawn points generated
int totalSpawns = 0;
for (NavAreaList::iterator iter = TheNavAreaList.begin(); iter != TheNavAreaList.end(); iter++)
for (CNavArea *area : TheNavAreaList)
{
if (totalSpawns >= MAX_SPAWNS_POINTS)
break;
CNavArea *area = *iter;
if (!area)
continue;
// ignore small areas
// Skip areas that are too small
if (area->GetSizeX() < MIN_AREA_SIZE || area->GetSizeY() < MIN_AREA_SIZE)
continue;
// ignore areas jump, crouch etc
if (area->GetAttributes())
// Skip areas with unwanted attributes (jump, crouch, etc.)
if (area->GetAttributes() != 0)
continue;
// Skip areas with steep slopes
if (area->GetAreaSlope() < MAX_SLOPE)
{
//CONSOLE_ECHO("Skip area slope: %0.3f\n", area->GetAreaSlope());
continue;
}
// Calculate the spawn point position above the area center
Vector vecOrigin = *area->GetCenter() + Vector(0, 0, HalfHumanHeight + 5);
// Ensure there is free space at the calculated position
if (!IsFreeSpace(vecOrigin, human_hull))
{
//CONSOLE_ECHO("No free space!\n");
continue;
}
if (pointInRadius(vecOrigin, 128.0f))
continue;
if (pointInRadius(vecOrigin, MIN_NEARBY_SPAWNPOINT))
continue; // spawn point is too close to others
if (!IsValidArea(area))
continue;
Vector bestAngle = GetBestAngle(vecOrigin);
if (bestAngle.y != -1)
// Calculate ideal spawn point yaw angle
float flIdealSpawnPointYaw = 0.0f;
if (GetIdealLookYawForSpawnPoint(vecOrigin, flIdealSpawnPointYaw))
{
CBaseEntity* pPoint = CBaseEntity::Create("info_spawn_point", vecOrigin, bestAngle, nullptr);
CBaseEntity *pPoint = CBaseEntity::Create("info_spawn_point", vecOrigin, Vector(0, flIdealSpawnPointYaw, 0), nullptr);
if (pPoint)
{
totalSpawns++;
@ -1302,24 +1312,64 @@ void GetSpawnPositions()
CONSOLE_ECHO("Total spawns points: %i\n", totalSpawns);
}
#endif
// Search the map entities to determine the game scenario and define important zones.
void CCSBotManager::ValidateMapData()
// Load the map's navigation data
bool CCSBotManager::LoadNavigationMap()
{
// check if the map data is already loaded or if bots are not allowed
if (m_isMapDataLoaded || !AreBotsAllowed())
return;
return false;
m_isMapDataLoaded = true;
if (LoadNavigationMap())
// Clear navigation map data from previous map
DestroyNavigationMap();
// Try to load the map's navigation file
NavErrorType navStatus = ::LoadNavigationMap();
if (navStatus != NAV_OK)
{
CONSOLE_ECHO("Failed to load navigation map.\n");
return;
CONSOLE_ECHO("ERROR: Failed to load 'maps/%s.nav' file navigation map!\n", STRING(gpGlobals->mapname));
switch (navStatus)
{
case NAV_CANT_ACCESS_FILE:
CONSOLE_ECHO("\tNavigation file not found or access denied.\n");
break;
case NAV_INVALID_FILE:
CONSOLE_ECHO("\tInvalid navigation file format.\n");
break;
case NAV_BAD_FILE_VERSION:
CONSOLE_ECHO("\tBad navigation file version.\n");
break;
case NAV_CORRUPT_DATA:
CONSOLE_ECHO("\tCorrupted navigation data detected.\n");
break;
default:
break;
}
if (navStatus != NAV_CANT_ACCESS_FILE)
CONSOLE_ECHO("\tTry regenerating it using the command: bot_nav_analyze\n");
return false;
}
CONSOLE_ECHO("Navigation map loaded.\n");
// Determine the scenario for the current map (e.g., bomb defuse, hostage rescue etc)
DetermineMapScenario();
#ifdef REGAMEDLL_ADD
GenerateSpawnPointsFromNavData();
#endif
return true;
}
// Search the map entities to determine the game scenario and define important zones.
void CCSBotManager::DetermineMapScenario()
{
m_zoneCount = 0;
m_gameScenario = SCENARIO_DEATHMATCH;
@ -1459,6 +1509,59 @@ void CCSBotManager::ValidateMapData()
}
}
// Tell all bots that the given nav data no longer exists
// This function is called when a part of the map or the nav data is destroyed
void CCSBotManager::OnDestroyNavDataNotify(NavNotifyDestroyType navNotifyType, void *dead)
{
for (int i = 1; i <= gpGlobals->maxClients; i++)
{
CBasePlayer *pPlayer = UTIL_PlayerByIndex(i);
if (!UTIL_IsValidPlayer(pPlayer))
continue;
if (!pPlayer->IsBot())
continue;
// Notify the bot about the destroyed nav data
CCSBot *pBot = static_cast<CCSBot *>(pPlayer);
pBot->OnDestroyNavDataNotify(navNotifyType, dead);
}
}
// Called when the map analysis process has completed
// This function makes sure all bots are removed from the map analysis process
// and are reset to normal bot behavior. It also reloads the navigation map
// and triggers a game restart after the analysis is completed
void CCSBotManager::AnalysisCompleted()
{
// Ensure that all bots are no longer involved in map analysis and start their normal process
for (int i = 1; i <= gpGlobals->maxClients; i++)
{
CBasePlayer *pPlayer = UTIL_PlayerByIndex(i);
if (!UTIL_IsValidPlayer(pPlayer))
continue;
if (!pPlayer->IsBot())
continue;
CCSBot *pBot = static_cast<CCSBot *>(pPlayer);
pBot->StartNormalProcess();
}
m_isLearningMap = false;
m_isMapDataLoaded = false;
m_isAnalysisRequested = false;
// Try to reload the navigation map from the file
if (LoadNavigationMap())
{
// Initiate a game restart in 3 seconds
CVAR_SET_FLOAT("sv_restart", 3);
}
}
bool CCSBotManager::AddBot(const BotProfile *profile, BotProfileTeamType team)
{
if (!AreBotsAllowed())

View File

@ -56,13 +56,16 @@ public:
virtual bool IsImportantPlayer(CBasePlayer *pPlayer) const; // return true if pPlayer is important to scenario (VIP, bomb carrier, etc)
public:
void ValidateMapData();
bool LoadNavigationMap();
void DetermineMapScenario();
void OnFreeEntPrivateData(CBaseEntity *pEntity);
void OnDestroyNavDataNotify(NavNotifyDestroyType navNotifyType, void *dead);
bool IsLearningMap() const { return m_isLearningMap; }
void SetLearningMapFlag() { m_isLearningMap = true; }
bool IsAnalysisRequested() const { return m_isAnalysisRequested; }
void RequestAnalysis() { m_isAnalysisRequested = true; }
void AckAnalysisRequest() { m_isAnalysisRequested = false; }
void AnalysisCompleted();
// difficulty levels
static BotDifficultyType GetDifficultyLevel()
@ -269,6 +272,4 @@ inline bool AreBotsAllowed()
}
void PrintAllEntities();
#ifdef REGAMEDLL_ADD
void GetSpawnPositions();
#endif
void GenerateSpawnPointsFromNavData();

View File

@ -1003,7 +1003,7 @@ CBasePlayer *CCSBot::FindMostDangerousThreat()
return currentThreat;
}
// if we are a sniper and we see a sniper threat, attack it unless
// if we are a sniper and we see a sniper threat, attack it unless
// there are other close enemies facing me
if (IsSniper() && sniperThreat)
{

View File

@ -844,7 +844,7 @@ void Host_Say(edict_t *pEntity, BOOL teamonly)
#ifdef REGAMEDLL_ADD
// there's no team on FFA mode
if (teamonly && CSGameRules()->IsFreeForAll() && (pPlayer->m_iTeam == CT || pPlayer->m_iTeam == TERRORIST))
teamonly = FALSE;
teamonly = FALSE;
#endif
// team only
@ -1001,7 +1001,7 @@ void Host_Say(edict_t *pEntity, BOOL teamonly)
if (gpGlobals->deathmatch != 0.0f && CSGameRules()->m_VoiceGameMgr.PlayerHasBlockedPlayer(pReceiver, pPlayer))
continue;
if (teamonly
if (teamonly
#ifdef REGAMEDLL_FIXES
&& CSGameRules()->PlayerRelationship(pPlayer, pReceiver) != GR_TEAMMATE
#else
@ -1020,7 +1020,7 @@ void Host_Say(edict_t *pEntity, BOOL teamonly)
continue;
}
if ((pReceiver->m_iIgnoreGlobalChat == IGNOREMSG_ENEMY
if ((pReceiver->m_iIgnoreGlobalChat == IGNOREMSG_ENEMY
#ifdef REGAMEDLL_FIXES
&& CSGameRules()->PlayerRelationship(pPlayer, pReceiver) == GR_TEAMMATE
#else
@ -3833,11 +3833,6 @@ void EXT_FUNC ServerActivate(edict_t *pEdictList, int edictCount, int clientMax)
#ifdef REGAMEDLL_ADD
CSGameRules()->ServerActivate();
if (LoadNavigationMap() == NAV_OK)
{
GetSpawnPositions();
}
#endif
}

View File

@ -174,9 +174,9 @@ cvar_t freezetime_duck = { "mp_freezetime_duck", "1", 0, 1.0f,
cvar_t freezetime_jump = { "mp_freezetime_jump", "1", 0, 1.0f, nullptr };
cvar_t jump_height = { "mp_jump_height", "45", FCVAR_SERVER, 45.0f, nullptr };
cvar_t hostages_rescued_ratio = { "mp_hostages_rescued_ratio", "1.0", 0, 1.0f, nullptr };
cvar_t hostages_rescued_ratio = { "mp_hostages_rescued_ratio", "1.0", 0, 1.0f, nullptr };
cvar_t legacy_vehicle_block = { "mp_legacy_vehicle_block", "1", 0, 0.0f, nullptr };
cvar_t legacy_vehicle_block = { "mp_legacy_vehicle_block", "1", 0, 0.0f, nullptr };
cvar_t dying_time = { "mp_dying_time", "3.0", 0, 3.0f, nullptr };
cvar_t defuser_allocation = { "mp_defuser_allocation", "0", 0, 0.0f, nullptr };

View File

@ -5594,7 +5594,9 @@ CTSpawn:
// The terrorist spawn points
else if (g_pGameRules->IsDeathmatch() && m_iTeam == TERRORIST)
{
#ifdef REGAMEDLL_ADD
TSpawn:
#endif
pSpot = g_pLastTerroristSpawn;
if (SelectSpawnSpot("info_player_deathmatch", pSpot))

View File

@ -1464,7 +1464,7 @@ void UTIL_Remove(CBaseEntity *pEntity)
pEntity->pev->targetname = 0;
}
NOXREF BOOL UTIL_IsValidEntity(edict_t *pent)
BOOL UTIL_IsValidEntity(edict_t *pent)
{
if (!pent || pent->free || (pent->v.flags & FL_KILLME))
return FALSE;

View File

@ -71,6 +71,13 @@ enum NavAttributeType
NAV_NO_JUMP = 0x08, // inhibit discontinuity jumping
};
enum NavNotifyDestroyType
{
NAV_NOTIFY_DESTROY_AREA,
NAV_NOTIFY_DESTROY_SPOT,
NAV_NOTIFY_DESTROY_SPOT_ENCOUNTER
};
enum NavDirType
{
NORTH = 0,

View File

@ -72,17 +72,29 @@ NOXREF void buildGoodSizedList()
void DestroyHidingSpots()
{
// remove all hiding spot references from the nav areas
for (auto area : TheNavAreaList)
area->m_hidingSpotList.clear();
HidingSpot::m_nextID = 0;
// free all the HidingSpots
for (auto spot : TheHidingSpotList)
for (HidingSpot *spot : TheHidingSpotList)
{
TheCSBots()->OnDestroyNavDataNotify(NAV_NOTIFY_DESTROY_SPOT, spot);
delete spot;
}
TheHidingSpotList.clear();
// remove all hiding spot references from the nav areas
for (CNavArea *area : TheNavAreaList)
{
area->m_hidingSpotList.clear();
// free all the HidingSpots in area
for (SpotEncounter &e : area->m_spotEncounterList)
e.spotList.clear();
TheCSBots()->OnDestroyNavDataNotify(NAV_NOTIFY_DESTROY_SPOT_ENCOUNTER, area);
area->m_spotEncounterList.clear();
}
HidingSpot::m_nextID = 0;
}
// For use when loading from a file
@ -249,6 +261,9 @@ CNavArea::CNavArea(CNavNode *nwNode, class CNavNode *neNode, class CNavNode *seN
// Destructor
CNavArea::~CNavArea()
{
// tell all bots that this area no longer exists
TheCSBots()->OnDestroyNavDataNotify(NAV_NOTIFY_DESTROY_AREA, this);
// if we are resetting the system, don't bother cleaning up - all areas are being destroyed
if (m_isReset)
return;
@ -344,6 +359,7 @@ void CNavArea::FinishMerge(CNavArea *adjArea)
// remove subsumed adjacent area
TheNavAreaList.remove(adjArea);
TheCSBots()->OnDestroyNavDataNotify(NAV_NOTIFY_DESTROY_AREA, this);
delete adjArea;
}
@ -536,6 +552,7 @@ bool CNavArea::SplitEdit(bool splitAlongX, float splitEdge, CNavArea **outAlpha,
// remove original area
TheNavAreaList.remove(this);
TheCSBots()->OnDestroyNavDataNotify(NAV_NOTIFY_DESTROY_AREA, this);
delete this;
return true;
@ -868,6 +885,7 @@ bool CNavArea::MergeEdit(CNavArea *adj)
// remove subsumed adjacent area
TheNavAreaList.remove(adj);
TheCSBots()->OnDestroyNavDataNotify(NAV_NOTIFY_DESTROY_AREA, adj);
delete adj;
return true;
@ -900,6 +918,9 @@ void DestroyNavigationMap()
{
CNavArea::m_isReset = true;
// destroy all hiding spots
DestroyHidingSpots();
// remove each element of the list and delete them
while (!TheNavAreaList.empty())
{
@ -913,8 +934,8 @@ void DestroyNavigationMap()
// destroy ladder representations
DestroyLadders();
// destroy all hiding spots
DestroyHidingSpots();
// cleanup from previous analysis
CleanupApproachAreaAnalysisPrep();
// destroy navigation nodes created during map learning
CNavNode *node, *next;
@ -2828,6 +2849,22 @@ SpotEncounter *CNavArea::GetSpotEncounter(const CNavArea *from, const CNavArea *
return nullptr;
}
// Checks if a SpotEncounter is present in the list of encounters for the given CNavArea
bool CNavArea::HasSpotEncounter(const SpotEncounter *encounter)
{
SpotEncounter *e;
for (SpotEncounterList::iterator iter = m_spotEncounterList.begin(); iter != m_spotEncounterList.end(); iter++)
{
e = &(*iter);
if (e == encounter)
return true;
}
return false;
}
// Add spot encounter data when moving from area to area
void CNavArea::AddSpotEncounters(const class CNavArea *from, NavDirType fromDir, const CNavArea *to, NavDirType toDir)
{
@ -3968,6 +4005,7 @@ void EditNavAreas(NavEditCmdType cmd)
case EDIT_DELETE:
EMIT_SOUND_DYN(ENT(pLocalPlayer->pev), CHAN_ITEM, "buttons/blip1.wav", 1, ATTN_NORM, 0, 100);
TheNavAreaList.remove(area);
TheCSBots()->OnDestroyNavDataNotify(NAV_NOTIFY_DESTROY_AREA, area);
delete area;
return;
case EDIT_ATTRIB_CROUCH:

View File

@ -273,6 +273,7 @@ public:
void ComputeHidingSpots(); // analyze local area neighborhood to find "hiding spots" in this area - for map learning
void ComputeSniperSpots(); // analyze local area neighborhood to find "sniper spots" in this area - for map learning
bool HasSpotEncounter(const SpotEncounter *encounter);
SpotEncounter *GetSpotEncounter(const CNavArea *from, const CNavArea *to); // given the areas we are moving between, return the spots we will encounter
void ComputeSpotEncounters(); // compute spot encounter data - for map learning

View File

@ -623,6 +623,13 @@ bool SaveNavigationMap(const char *filename)
area->Save(fd, version);
}
// Ensure that all data is flushed to disk
#ifdef WIN32
_commit(fd);
#else
fsync(fd);
#endif
_close(fd);
return true;
}

View File

@ -932,7 +932,7 @@
<Optimization>Disabled</Optimization>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>REGAMEDLL_ADD;REGAMEDLL_API;REGAMEDLL_FIXES;REGAMEDLL_SSE;REGAMEDLL_SELF;REGAMEDLL_CHECKS;UNICODE_FIXES;BUILD_LATEST;BUILD_LATEST_FIXES;CLIENT_WEAPONS;USE_QSTRING;_CRT_SECURE_NO_WARNINGS;_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<FloatingPointModel>Fast</FloatingPointModel>
<FloatingPointModel>Precise</FloatingPointModel>
<AdditionalOptions>/arch:IA32 %(AdditionalOptions)</AdditionalOptions>
<AdditionalOptions Condition="'$(PlatformToolset)' == 'v140_xp' OR '$(PlatformToolset)' == 'v141_xp'">/Zc:threadSafeInit- %(AdditionalOptions)</AdditionalOptions>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
@ -969,7 +969,7 @@
<Optimization>Full</Optimization>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>REGAMEDLL_ADD;REGAMEDLL_API;REGAMEDLL_FIXES;REGAMEDLL_SSE;REGAMEDLL_SELF;REGAMEDLL_CHECKS;UNICODE_FIXES;BUILD_LATEST;BUILD_LATEST_FIXES;CLIENT_WEAPONS;USE_QSTRING;_CRT_SECURE_NO_WARNINGS;NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<FloatingPointModel>Fast</FloatingPointModel>
<FloatingPointModel>Precise</FloatingPointModel>
<AdditionalOptions>/arch:IA32 %(AdditionalOptions)</AdditionalOptions>
<AdditionalOptions Condition="'$(PlatformToolset)' == 'v140_xp' OR '$(PlatformToolset)' == 'v141_xp'">/Zc:threadSafeInit- %(AdditionalOptions)</AdditionalOptions>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
@ -1102,6 +1102,7 @@
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>precompiled.h</PrecompiledHeaderFile>
<EnableEnhancedInstructionSet>NoExtensions</EnableEnhancedInstructionSet>
<FloatingPointModel>Precise</FloatingPointModel>
</ClCompile>
<Link>
<GenerateDebugInformation>true</GenerateDebugInformation>