From 6f70d6dd8e61943f1a093d292b8768dc7cc50ef8 Mon Sep 17 00:00:00 2001
From: s1lentq <s1lentsk@yandex.ru>
Date: Fri, 4 Apr 2025 02:41:19 +0700
Subject: [PATCH] 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

---
 regamedll/dlls/bot/cs_bot.cpp          | 126 ++++++++++++++++++
 regamedll/dlls/bot/cs_bot.h            |   6 +
 regamedll/dlls/bot/cs_bot_init.cpp     |   2 +-
 regamedll/dlls/bot/cs_bot_learn.cpp    |  17 +--
 regamedll/dlls/bot/cs_bot_manager.cpp  | 173 ++++++++++++++++++++-----
 regamedll/dlls/bot/cs_bot_manager.h    |   9 +-
 regamedll/dlls/bot/cs_bot_vision.cpp   |   2 +-
 regamedll/dlls/client.cpp              |  11 +-
 regamedll/dlls/game.cpp                |   4 +-
 regamedll/dlls/player.cpp              |   2 +
 regamedll/dlls/util.cpp                |   2 +-
 regamedll/game_shared/bot/nav.h        |   7 +
 regamedll/game_shared/bot/nav_area.cpp |  56 ++++++--
 regamedll/game_shared/bot/nav_area.h   |   1 +
 regamedll/game_shared/bot/nav_file.cpp |   7 +
 regamedll/msvc/ReGameDLL.vcxproj       |   5 +-
 16 files changed, 355 insertions(+), 75 deletions(-)

diff --git a/regamedll/dlls/bot/cs_bot.cpp b/regamedll/dlls/bot/cs_bot.cpp
index dd85448c..b89a6912 100644
--- a/regamedll/dlls/bot/cs_bot.cpp
+++ b/regamedll/dlls/bot/cs_bot.cpp
@@ -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;
+	}
+	}
+}
diff --git a/regamedll/dlls/bot/cs_bot.h b/regamedll/dlls/bot/cs_bot.h
index 8f63e1a6..0ba29985 100644
--- a/regamedll/dlls/bot/cs_bot.h
+++ b/regamedll/dlls/bot/cs_bot.h
@@ -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
diff --git a/regamedll/dlls/bot/cs_bot_init.cpp b/regamedll/dlls/bot/cs_bot_init.cpp
index 3199cf91..2db6b02d 100644
--- a/regamedll/dlls/bot/cs_bot_init.cpp
+++ b/regamedll/dlls/bot/cs_bot_init.cpp
@@ -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));
diff --git a/regamedll/dlls/bot/cs_bot_learn.cpp b/regamedll/dlls/bot/cs_bot_learn.cpp
index 3cf041e3..e68a77a5 100644
--- a/regamedll/dlls/bot/cs_bot_learn.cpp
+++ b/regamedll/dlls/bot/cs_bot_learn.cpp
@@ -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()
diff --git a/regamedll/dlls/bot/cs_bot_manager.cpp b/regamedll/dlls/bot/cs_bot_manager.cpp
index e68bea53..15fc3de2 100644
--- a/regamedll/dlls/bot/cs_bot_manager.cpp
+++ b/regamedll/dlls/bot/cs_bot_manager.cpp
@@ -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())
diff --git a/regamedll/dlls/bot/cs_bot_manager.h b/regamedll/dlls/bot/cs_bot_manager.h
index 0d5e6e37..6f3b0e68 100644
--- a/regamedll/dlls/bot/cs_bot_manager.h
+++ b/regamedll/dlls/bot/cs_bot_manager.h
@@ -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
\ No newline at end of file
+void GenerateSpawnPointsFromNavData();
diff --git a/regamedll/dlls/bot/cs_bot_vision.cpp b/regamedll/dlls/bot/cs_bot_vision.cpp
index 6e9d8ddc..56f75678 100644
--- a/regamedll/dlls/bot/cs_bot_vision.cpp
+++ b/regamedll/dlls/bot/cs_bot_vision.cpp
@@ -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)
 		{
diff --git a/regamedll/dlls/client.cpp b/regamedll/dlls/client.cpp
index 971a41cf..b802a2e3 100644
--- a/regamedll/dlls/client.cpp
+++ b/regamedll/dlls/client.cpp
@@ -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
 }
 
diff --git a/regamedll/dlls/game.cpp b/regamedll/dlls/game.cpp
index e5ee0c2b..a022348e 100644
--- a/regamedll/dlls/game.cpp
+++ b/regamedll/dlls/game.cpp
@@ -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 };
diff --git a/regamedll/dlls/player.cpp b/regamedll/dlls/player.cpp
index 81548d34..1f096d13 100644
--- a/regamedll/dlls/player.cpp
+++ b/regamedll/dlls/player.cpp
@@ -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))
diff --git a/regamedll/dlls/util.cpp b/regamedll/dlls/util.cpp
index b1102c47..e0ffed53 100644
--- a/regamedll/dlls/util.cpp
+++ b/regamedll/dlls/util.cpp
@@ -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;
diff --git a/regamedll/game_shared/bot/nav.h b/regamedll/game_shared/bot/nav.h
index a917aa99..d23a734e 100644
--- a/regamedll/game_shared/bot/nav.h
+++ b/regamedll/game_shared/bot/nav.h
@@ -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,
diff --git a/regamedll/game_shared/bot/nav_area.cpp b/regamedll/game_shared/bot/nav_area.cpp
index 6c596051..a5f8f748 100644
--- a/regamedll/game_shared/bot/nav_area.cpp
+++ b/regamedll/game_shared/bot/nav_area.cpp
@@ -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:
diff --git a/regamedll/game_shared/bot/nav_area.h b/regamedll/game_shared/bot/nav_area.h
index 10eca2f8..2792cd3b 100644
--- a/regamedll/game_shared/bot/nav_area.h
+++ b/regamedll/game_shared/bot/nav_area.h
@@ -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
 
diff --git a/regamedll/game_shared/bot/nav_file.cpp b/regamedll/game_shared/bot/nav_file.cpp
index cf97f6f6..cee5618e 100644
--- a/regamedll/game_shared/bot/nav_file.cpp
+++ b/regamedll/game_shared/bot/nav_file.cpp
@@ -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;
 }
diff --git a/regamedll/msvc/ReGameDLL.vcxproj b/regamedll/msvc/ReGameDLL.vcxproj
index f33cfa3d..80e93c92 100644
--- a/regamedll/msvc/ReGameDLL.vcxproj
+++ b/regamedll/msvc/ReGameDLL.vcxproj
@@ -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>