Vector v = *point - *eye;
	float startAngle = UTIL_VecToYaw(v);
	float length = v.Length2D();
	v.NormalizeInPlace();

	float angleInc = 10.0f;

#include "extdll.h"
#include "util.h"
#include "cbase.h"
#include "monsters.h"
#include "weapons.h"
#include "player.h"
#include "gamerules.h"
#include "training_gamerules.h"
#include "hltv.h"
#include "game.h"
#include "trains.h"
#include "vehicle.h"
#include "globals.h"

#include "bot_include.h"

namespace sv {

// Used to update view angles to stay on a ladder

float StayOnLadderLine(CCSBot *me, const CNavLadder *ladder)
{
	// determine our facing
	NavDirType faceDir = AngleToDirection(me->pev->v_angle.y);
	const float stiffness = 1.0f;

	// move toward ladder mount point
	switch (faceDir)
	{
	case NORTH:
		return (stiffness * (ladder->m_top.x - me->pev->origin.x));
	case EAST:
		return (stiffness * (ladder->m_top.y - me->pev->origin.y));
	case SOUTH:
		return (-stiffness * (ladder->m_top.x - me->pev->origin.x));
	case WEST:
		return (-stiffness * (ladder->m_top.y - me->pev->origin.y));
	default:
		break;
	}

	return 0.0f;
}

// Move actual view angles towards desired ones.
// This is the only place v_angle is altered.
// TODO: Make stiffness and turn rate constants timestep invariant.
void CCSBot::UpdateLookAngles()
{
	const auto deltaT = g_flBotCommandInterval;
	float maxAccel;
	float stiffness;
	float damping;

	// springs are stiffer when attacking, so we can track and move between targets better
	if (IsAttacking())
	{
		stiffness = 300.0f;
		damping = 30.0f;
		maxAccel = 3000.0f;
	}
	else
	{
		stiffness = 200.0f;
		damping = 25.0f;
		maxAccel = 3000.0f;
	}

	// these may be overridden by ladder logic
	float useYaw = m_lookYaw;
	float usePitch = m_lookPitch;

	// Ladders require precise movement, therefore we need to look at the
	// ladder as we approach and ascend/descend it.
	// If we are on a ladder, we need to look up or down to traverse it - override pitch in this case.
	// If we're trying to break something, though, we actually need to look at it before we can
	// look at the ladder
	if (IsUsingLadder())
	{
		// set yaw to aim at ladder
		Vector to = m_pathLadder->m_top - pev->origin;
		float idealYaw = UTIL_VecToYaw(to);

		NavDirType faceDir = m_pathLadder->m_dir;

		if (m_pathLadderFaceIn)
		{
			faceDir = OppositeDirection(faceDir);
		}

		const float lookAlongLadderRange = 100.0f;
		const float ladderPitch = 60.0f;

		// adjust pitch to look up/down ladder as we ascend/descend
		switch (m_pathLadderState)
		{
			case APPROACH_ASCENDING_LADDER:
			{
				Vector to = m_goalPosition - pev->origin;
				useYaw = idealYaw;

				if (to.IsLengthLessThan(lookAlongLadderRange))
					usePitch = -ladderPitch;
				break;
			}
			case APPROACH_DESCENDING_LADDER:
			{
				Vector to = m_goalPosition - pev->origin;
				useYaw = idealYaw;

				if (to.IsLengthLessThan(lookAlongLadderRange))
					usePitch = ladderPitch;
				break;
			}
			case FACE_ASCENDING_LADDER:
			{
				useYaw = idealYaw;
				usePitch = -ladderPitch;
				break;
			}
			case FACE_DESCENDING_LADDER:
			{
				useYaw = idealYaw;
				usePitch = ladderPitch;
				break;
			}
			case MOUNT_ASCENDING_LADDER:
			case ASCEND_LADDER:
			{
				useYaw = DirectionToAngle(faceDir) + StayOnLadderLine(this, m_pathLadder);
				usePitch = -ladderPitch;
				break;
			}
			case MOUNT_DESCENDING_LADDER:
			case DESCEND_LADDER:
			{
				useYaw = DirectionToAngle(faceDir) + StayOnLadderLine(this, m_pathLadder);
				usePitch = ladderPitch;
				break;
			}
			case DISMOUNT_ASCENDING_LADDER:
			case DISMOUNT_DESCENDING_LADDER:
			{
				useYaw = DirectionToAngle(faceDir);
				break;
			}
			default:
				break;
		}
	}

	// Yaw
	float angleDiff = NormalizeAngle(useYaw - pev->v_angle.y);

	// if almost at target angle, snap to it
	const float onTargetTolerance = 1.0f;
	if (angleDiff < onTargetTolerance && angleDiff > -onTargetTolerance)
	{
		m_lookYawVel = 0.0f;
		pev->v_angle.y = useYaw;
	}
	else
	{
		// simple angular spring/damper
		float accel = stiffness * angleDiff - damping * m_lookYawVel;

		// limit rate
		if (accel > maxAccel)
			accel = maxAccel;

		else if (accel < -maxAccel)
			accel = -maxAccel;

		m_lookYawVel += deltaT / 1s * accel;
		pev->v_angle.y += deltaT / 1s * m_lookYawVel;
	}

	// Pitch
	// Actually, this is negative pitch.
	angleDiff = usePitch - pev->v_angle.x;

	angleDiff = NormalizeAngle(angleDiff);

	if (false && angleDiff < onTargetTolerance && angleDiff > -onTargetTolerance)
	{
		m_lookPitchVel = 0.0f;
		pev->v_angle.x = usePitch;
	}
	else
	{
		// simple angular spring/damper
		// double the stiffness since pitch is only +/- 90 and yaw is +/- 180
		float accel = 2.0f * stiffness * angleDiff - damping * m_lookPitchVel;

		// limit rate
		if (accel > maxAccel)
			accel = maxAccel;

		else if (accel < -maxAccel)
			accel = -maxAccel;

		m_lookPitchVel += deltaT / 1s * accel;
		pev->v_angle.x += deltaT / 1s * m_lookPitchVel;
	}

	// limit range - avoid gimbal lock
	if (pev->v_angle.x < -89.0f)
		pev->v_angle.x = -89.0f;
	else if (pev->v_angle.x > 89.0f)
		pev->v_angle.x = 89.0f;

	pev->v_angle.z = 0.0f;
}

// Return true if we can see the point
bool CCSBot::IsVisible(const Vector *pos, bool testFOV) const
{
	// we can't see anything if we're blind
	if (IsBlind())
		return false;

	// is it in my general viewcone?
	if (testFOV && !(const_cast<CCSBot *>(this)->FInViewCone(pos)))
		return false;

	// check line of sight against smoke
	if (TheCSBots()->IsLineBlockedBySmoke(&GetEyePosition(), pos))
		return false;

	// check line of sight
	// Must include CONTENTS_MONSTER to pick up all non-brush objects like barrels
	TraceResult result;
	UTIL_TraceLine(GetEyePosition(), *pos, ignore_monsters, ignore_glass, ENT(pev), &result);

	if (result.flFraction != 1.0f)
		return false;

	return true;
}

// Return true if we can see any part of the player
// Check parts in order of importance. Return the first part seen in "visParts" if it is non-NULL.

bool CCSBot::IsVisible(CBasePlayer *player, bool testFOV, unsigned char *visParts) const
{
	if ((player->pev->flags & FL_NOTARGET) || (player->pev->effects & EF_NODRAW))
		return false;

	Vector spot = player->pev->origin;
	int testVisParts = NONE;

	// finish chest check
	if (IsVisible(&spot, testFOV))
		testVisParts |= CHEST;

	// check top of head
	spot = spot + Vector(0, 0, 25.0f);

	if (IsVisible(&spot, testFOV))
		testVisParts |= HEAD;

	// check feet
	const float standFeet = 34.0f;
	const float crouchFeet = 14.0f;

	if (player->pev->flags & FL_DUCKING)
		spot.z = player->pev->origin.z - crouchFeet;
	else
		spot.z = player->pev->origin.z - standFeet;

	// check feet
	if (IsVisible(&spot, testFOV))
		testVisParts |= FEET;

	const float edgeOffset = 13.0f;
	Vector2D dir = (player->pev->origin - pev->origin).Make2D();
	dir.NormalizeInPlace();

	Vector2D perp(-dir.y, dir.x);

	spot = player->pev->origin + Vector(perp.x * edgeOffset, perp.y * edgeOffset, 0);

	if (IsVisible(&spot, testFOV))
		testVisParts |= LEFT_SIDE;

	spot = player->pev->origin - Vector(perp.x * edgeOffset, perp.y * edgeOffset, 0);

	if (IsVisible(&spot, testFOV))
		testVisParts |= RIGHT_SIDE;

	if (visParts != NULL)
		*visParts = testVisParts;

	if (testVisParts != NONE)
		return true;

	return false;
}

bool CCSBot::IsEnemyPartVisible(VisiblePartType part) const
{
	if (!IsEnemyVisible())
		return false;

	return (m_visibleEnemyParts & part) != 0;
}

void CCSBot::UpdateLookAt()
{
	Vector to = m_lookAtSpot - EyePosition();
	Vector idealAngle = UTIL_VecToAngles(to);
	idealAngle.x = 360.0f - idealAngle.x;

	SetLookAngles(idealAngle.y, idealAngle.x);
}

// Look at the given point in space for the given duration (-1 means forever)

void CCSBot::SetLookAt(const char *desc, const Vector *pos, PriorityType pri, duration_t duration, bool clearIfClose, float angleTolerance)
{
	if (pos == NULL)
		return;

	// if currently looking at a point in space with higher priority, ignore this request
	if (m_lookAtSpotState != NOT_LOOKING_AT_SPOT && m_lookAtSpotPriority > pri)
		return;

	// if already looking at this spot, just extend the time
	const float tolerance = 10.0f;
	if (m_lookAtSpotState != NOT_LOOKING_AT_SPOT && VectorsAreEqual(pos, &m_lookAtSpot, tolerance))
	{
		m_lookAtSpotDuration = duration;

		if (m_lookAtSpotPriority < pri)
			m_lookAtSpotPriority = pri;
	}
	else
	{
		// look at new spot
		m_lookAtSpot = *pos;
		m_lookAtSpotState = LOOK_TOWARDS_SPOT;
		m_lookAtSpotDuration = duration;
		m_lookAtSpotPriority = pri;
	}

	m_lookAtSpotAngleTolerance = angleTolerance;
	m_lookAtSpotClearIfClose = clearIfClose;
	m_lookAtDesc = desc;
}

// Block all "look at" and "look around" behavior for given duration - just look ahead

void CCSBot::InhibitLookAround(duration_t duration)
{
	m_inhibitLookAroundTimestamp = gpGlobals->time + duration;
}

// Update enounter spot timestamps, etc

void CCSBot::UpdatePeripheralVision()
{
	// if we update at 10Hz, this ensures we test once every three
	constexpr auto peripheralUpdateInterval = 0.29s;
	if (gpGlobals->time - m_peripheralTimestamp < peripheralUpdateInterval)
		return;

	m_peripheralTimestamp = gpGlobals->time;

	if (m_spotEncounter)
	{
		// check LOS to all spots in case we see them with our "peripheral vision"
		Vector pos;
		for (auto &spotOrder : m_spotEncounter->spotList)
		{
			const Vector *spotPos = spotOrder.spot->GetPosition();

			pos.x = spotPos->x;
			pos.y = spotPos->y;
			pos.z = spotPos->z + HalfHumanHeight;

			if (!IsVisible(&pos, CHECK_FOV))
				continue;

			// can see hiding spot, remember when we saw it last
			SetHidingSpotCheckTimestamp(spotOrder.spot);
		}
	}
}

// Update the "looking around" behavior.

void CCSBot::UpdateLookAround(bool updateNow)
{
	// check if looking around has been inhibited
	// Moved inhibit to allow high priority enemy lookats to still occur
	if (gpGlobals->time < m_inhibitLookAroundTimestamp)
		return;

	constexpr auto recentThreatTime = 0.25s; // 1.0f;

	// Unless we can hear them moving, in which case look towards the noise
	if (!IsEnemyVisible())
	{
		const float noiseStartleRange = 1000.0f;
		if (CanHearNearbyEnemyGunfire(noiseStartleRange))
		{
			Vector spot = m_noisePosition;
			spot.z += HalfHumanHeight;

			SetLookAt("Check dangerous noise", &spot, PRIORITY_HIGH, recentThreatTime);
			InhibitLookAround(RandomDuration(2.0s, 4.0s));

			return;
		}
	}

	// If we recently saw an enemy, look towards where we last saw them
	if (!IsLookingAtSpot(PRIORITY_MEDIUM) && gpGlobals->time - m_lastSawEnemyTimestamp < recentThreatTime)
	{
		ClearLookAt();

		Vector spot = m_lastEnemyPosition;

		// find enemy position on the ground
		if (GetSimpleGroundHeight(&m_lastEnemyPosition, &spot.z))
		{
			spot.z += HalfHumanHeight;
			SetLookAt("Last Enemy Position", &spot, PRIORITY_MEDIUM, RandomDuration(2.0s, 3.0s), true);
			return;
		}
	}

	// Look at nearby enemy noises
	if (UpdateLookAtNoise())
		return;

	if (IsNotMoving())
	{
		// if we're sniping, zoom in to watch our approach points
		if (IsUsingSniperRifle())
		{
			// low skill bots don't pre-zoom
			if (GetProfile()->GetSkill() > 0.4f)
			{
				if (!IsViewMoving())
				{
					float range = ComputeWeaponSightRange();
					AdjustZoom(range);
				}
				else
				{
					// zoom out
					if (GetZoomLevel() != NO_ZOOM)
						SecondaryAttack();
				}
			}
		}

		if (m_lastKnownArea == NULL)
			return;

		if (gpGlobals->time < m_lookAroundStateTimestamp)
			return;

		// if we're sniping, switch look-at spots less often
		if (IsUsingSniperRifle())
			m_lookAroundStateTimestamp = gpGlobals->time + RandomDuration(5.0s, 10.0s);
		else
			m_lookAroundStateTimestamp = gpGlobals->time + RandomDuration(1.0s, 2.0s);

		if (m_approachPointCount == 0)
		{
			ClearLookAt();
			return;
		}

		int which = RANDOM_LONG(0, m_approachPointCount - 1);
		Vector spot = m_approachPoint[ which ];

		// don't look at the floor, look roughly at chest level
		// TODO: If this approach point is very near, this will cause us to aim up in the air if were crouching
		spot.z += HalfHumanHeight;

		SetLookAt("Approach Point (Hiding)", &spot, PRIORITY_LOW);

		return;
	}

	// Glance at "encouter spots" as we move past them
	if (m_spotEncounter)
	{
		// Check encounter spots
		if (!IsSafe() && !IsLookingAtSpot(PRIORITY_LOW))
		{
			// allow a short time to look where we're going
			if (gpGlobals->time < m_spotCheckTimestamp)
				return;

			// TODO: Use skill parameter instead of accuracy

			// lower skills have exponentially longer delays
			float asleep = (1.0f - GetProfile()->GetSkill());
			asleep *= asleep;
			asleep *= asleep;

			m_spotCheckTimestamp = gpGlobals->time + asleep * RandomDuration(10.0s, 30.0s);

			// figure out how far along the path segment we are
			Vector delta = m_spotEncounter->path.to - m_spotEncounter->path.from;
			float length = delta.Length();
			float adx = abs(delta.x);
			float ady = abs(delta.y);
			float t;

			if (adx > ady)
				t = (pev->origin.x - m_spotEncounter->path.from.x) / delta.x;
			else
				t = (pev->origin.y - m_spotEncounter->path.from.y) / delta.y;

			// advance parameter a bit so we "lead" our checks
			const float leadCheckRange = 50.0f;
			t += leadCheckRange / length;

			if (t < 0.0f)
				t = 0.0f;
			else if (t > 1.0f)
				t = 1.0f;

			// collect the unchecked spots so far
			#define MAX_DANGER_SPOTS 8
			HidingSpot *dangerSpot[MAX_DANGER_SPOTS];
			int dangerSpotCount = 0;
			int dangerIndex = 0;

			constexpr auto checkTime = 10.0s;
			const SpotOrder *spotOrder;

			for (auto &spotOrder : m_spotEncounter->spotList)
			{
				// if we have seen this spot recently, we don't need to look at it
				if (gpGlobals->time - GetHidingSpotCheckTimestamp(spotOrder.spot) <= checkTime)
					continue;

				if (spotOrder.t > t)
					break;

				dangerSpot[ dangerIndex++ ] = spotOrder.spot;
				if (dangerIndex >= MAX_DANGER_SPOTS)
					dangerIndex = 0;
				if (dangerSpotCount < MAX_DANGER_SPOTS)
					++dangerSpotCount;
			}

			if (dangerSpotCount)
			{
				// pick one of the spots at random
				int which = RANDOM_LONG(0, dangerSpotCount - 1);

				const Vector *checkSpot = dangerSpot[ which ]->GetPosition();
				// could be nullptr, fix it
				if (checkSpot)
				{
					Vector pos = *checkSpot;
					pos.z += HalfHumanHeight;

					// glance at the spot for minimum time
					SetLookAt("Encounter Spot", &pos, PRIORITY_LOW, 0s, true, 10.0f);
				}
				// immediately mark it as "checked", so we don't check it again
				// if we get distracted before we check it - that's the way it goes
				SetHidingSpotCheckTimestamp(dangerSpot[which]);
			}
		}
	}
}

// "Bend" our line of sight around corners until we can "see" the point.

bool CCSBot::BendLineOfSight(const Vector *eye, const Vector *point, Vector *bend) const
{
	// if we can directly see the point, use it
	TraceResult result;
	UTIL_TraceLine(*eye, *point + Vector(0, 0, HalfHumanHeight), ignore_monsters, ENT(pev), &result);

	if (result.flFraction == 1.0f && !result.fStartSolid)
	{
		// can directly see point, no bending needed
		*bend = *point;
		return true;
	}

	// "bend" our line of sight until we can see the approach point

	for (float angle = angleInc; angle <= 135.0f; angle += angleInc)
	{
		// check both sides at this angle offset
		for (int side = 0; side < 2; ++side)
		{
			float actualAngle = (side) ? (startAngle + angle) : (startAngle - angle);

			float dx = BotCOS(actualAngle);
			float dy = BotSIN(actualAngle);

			// compute rotated point ray endpoint
			Vector rotPoint(eye->x + length * dx, eye->y + length * dy, point->z);

			TraceResult result;
			UTIL_TraceLine(*eye, rotPoint + Vector(0, 0, HalfHumanHeight), ignore_monsters, ENT(pev), &result);

			// if this ray started in an obstacle, skip it
			if (result.fStartSolid)
			{
				continue;
			}

			Vector ray = rotPoint - *eye;
			float rayLength = ray.NormalizeInPlace();
			float visibleLength = rayLength * result.flFraction;

			// step along ray, checking if point is visible from ray point
			const float bendStepSize = 50.0f;
			for (float bendLength = bendStepSize; bendLength <= visibleLength; bendLength += bendStepSize)
			{
				// compute point along ray
				Vector rayPoint = *eye + bendLength * ray;

				// check if we can see approach point from this bend point
				UTIL_TraceLine(rayPoint, *point + Vector(0, 0, HalfHumanHeight), ignore_monsters, ENT(pev), &result);

				if (result.flFraction == 1.0f && !result.fStartSolid)
				{
					// target is visible from this bend point on the ray - use this point on the ray as our point

					// keep "bent" point at correct height along line of sight
					if (!GetGroundHeight(&rayPoint, &rayPoint.z))
					{
						rayPoint.z = point->z;
					}

					*bend = rayPoint;
					return true;
				}
			}
		}
	}

	*bend = *point;

	// bending rays didn't help - still can't see the point
	return false;
}

CBasePlayer *CCSBot::FindMostDangerousThreat()
{
	// maximum number of simulataneously attendable threats
	const int MAX_THREATS = MAX_CLIENTS;
	struct CloseInfo
	{
		CBasePlayer *enemy;
		float range;
	}
	threat[ MAX_THREATS + 1 ];
	int threatCount = 0;

	m_bomber = NULL;

	m_closestVisibleFriend = NULL;
	float closeFriendRange = 99999999999.9f;

	m_closestVisibleHumanFriend = NULL;
	float closeHumanFriendRange = 99999999999.9f;

	int i;

	{
		for (i = 1; i <= gpGlobals->maxClients; ++i)
		{
			CBaseEntity *entity = UTIL_PlayerByIndex(i);

			if (entity == NULL)
				continue;

			if (FNullEnt(entity->pev))
				continue;

			// is it a player?
			if (!entity->IsPlayer())
				continue;

			CBasePlayer *player = static_cast<CBasePlayer *>(entity);

			// ignore self
			if (player->entindex() == entindex())
				continue;

			// is it alive?
			if (!player->IsAlive())
				continue;

			if ((player->pev->flags & FL_NOTARGET) || (player->pev->effects & EF_NODRAW))
				continue;

			// is it an enemy?
			if (g_pGameRules->IsTeamplay() && player->m_iTeam == m_iTeam)
			{
				TraceResult result;
				UTIL_TraceLine(GetEyePosition(), player->pev->origin, ignore_monsters, ignore_glass, edict(), &result);
				if (result.flFraction == 1.0f)
				{
					// update watch timestamp
					int idx = player->entindex() - 1;
					m_watchInfo[idx].timestamp = gpGlobals->time;
					m_watchInfo[idx].isEnemy = false;

					// keep track of our closest friend
					Vector to = pev->origin - player->pev->origin;
					float rangeSq = to.LengthSquared();
					if (rangeSq < closeFriendRange)
					{
						m_closestVisibleFriend = player;
						closeFriendRange = rangeSq;
					}

					// keep track of our closest human friend
					if (!player->IsBot() && rangeSq < closeHumanFriendRange)
					{
						m_closestVisibleHumanFriend = player;
						closeHumanFriendRange = rangeSq;
					}
				}

				continue;
			}

			// check if this enemy is fully
			if (!IsVisible(player, CHECK_FOV))
				continue;

			// update watch timestamp
			int idx = player->entindex() - 1;
			m_watchInfo[idx].timestamp = gpGlobals->time;
			m_watchInfo[idx].isEnemy = true;

			// note if we see the bomber
			if (player->IsBombGuy())
			{
				m_bomber = player;
			}

			// keep track of all visible threats
			Vector d = pev->origin - player->pev->origin;
			float distSq = d.LengthSquared();

			// maintain set of visible threats, sorted by increasing distance
			if (threatCount == 0)
			{
				threat[0].enemy = player;
				threat[0].range = distSq;
				threatCount = 1;
			}
			else
			{
				// find insertion point
				int j;
				for (j = 0; j < threatCount; ++j)
				{
					if (distSq < threat[j].range)
						break;
				}

				// shift lower half down a notch
				for (int k = threatCount - 1; k >= j; --k)
					threat[k + 1] = threat[k];

				// insert threat into sorted list
				threat[j].enemy = player;
				threat[j].range = distSq;

				if (threatCount < MAX_THREATS)
					++threatCount;
			}
		}
	}
	{
		// track the maximum enemy and friend counts we've seen recently
		int prevEnemies = m_nearbyEnemyCount;
		int prevFriends = m_nearbyFriendCount;
		m_nearbyEnemyCount = 0;
		m_nearbyFriendCount = 0;

		for (i = 0; i < MAX_CLIENTS; ++i)
		{
			if (m_watchInfo[i].timestamp <= invalid_time_point)
				continue;

			constexpr auto recentTime = 3.0s;
			if (gpGlobals->time - m_watchInfo[i].timestamp < recentTime)
			{
				if (m_watchInfo[i].isEnemy)
					++m_nearbyEnemyCount;
				else
					++m_nearbyFriendCount;
			}
		}

		// note when we saw this batch of enemies
		if (prevEnemies == 0 && m_nearbyEnemyCount > 0)
		{
			m_firstSawEnemyTimestamp = gpGlobals->time;
		}

		if (prevEnemies != m_nearbyEnemyCount || prevFriends != m_nearbyFriendCount)
		{
			PrintIfWatched("Nearby friends = %d, enemies = %d\n", m_nearbyFriendCount, m_nearbyEnemyCount);
		}
	}
	{
		// Track the place where we saw most of our enemies
		struct PlaceRank
		{
			unsigned int place;
			int count;
		};
		static PlaceRank placeRank[ MAX_PLACES_PER_MAP ];
		int locCount = 0;

		PlaceRank common;
		common.place = 0;
		common.count = 0;

		for (i = 0; i < threatCount; ++i)
		{
			// find the area the player/bot is standing on
			CNavArea *area;
			CCSBot *bot = dynamic_cast<CCSBot *>(threat[i].enemy);
			if (bot != NULL && bot->IsBot())
			{
				area = bot->GetLastKnownArea();
			}
			else
			{
				area = TheNavAreaGrid.GetNearestNavArea(&threat[i].enemy->pev->origin);
			}

			if (area == NULL)
				continue;

			unsigned int threatLoc = area->GetPlace();
			if (!threatLoc)
				continue;

			// if place is already in set, increment count
			int j;
			for (j = 0; j < locCount; ++j)
			{
				if (placeRank[j].place == threatLoc)
					break;
			}

			if (j == locCount)
			{
				if (locCount < MAX_PLACES_PER_MAP)
				{
					placeRank[ locCount ].place = threatLoc;
					placeRank[ locCount ].count = 1;

					if (common.count == 0)
						common = placeRank[locCount];

					++locCount;
				}
			}
			else
			{
				// others are in that place, increment
				++placeRank[j].count;

				// keep track of the most common place
				if (placeRank[j].count > common.count)
					common = placeRank[j];
			}
		}

		// remember most common place
		m_enemyPlace = common.place;
	}

	{
		if (threatCount == 0)
			return NULL;

		// otherwise, find the closest threat that without using shield
		int t;
		for (t = 0; t < threatCount; ++t)
		{
			if (!threat[t].enemy->IsProtectedByShield())
			{
				return threat[t].enemy;
			}
		}
	}

	// return closest threat
	return threat[0].enemy;
}

// Update our reaction time queue

void CCSBot::UpdateReactionQueue()
{
	// zombies dont see any threats
	if (cv_bot_zombie.value > 0.0f)
		return;

	// find biggest threat at this instant
	CBasePlayer *threat = FindMostDangerousThreat();

	int now = m_enemyQueueIndex;

	// store a snapshot of its state at the end of the reaction time queue
	if (threat != NULL)
	{
		m_enemyQueue[ now ].player = threat;
		m_enemyQueue[ now ].isReloading = threat->IsReloading();
		m_enemyQueue[ now ].isProtectedByShield = threat->IsProtectedByShield();
	}
	else
	{
		m_enemyQueue[ now ].player = NULL;
		m_enemyQueue[ now ].isReloading = false;
		m_enemyQueue[ now ].isProtectedByShield = false;
		if(m_isAttacking)
			StopAttacking();
	}

	// queue is round-robin
	++m_enemyQueueIndex;
	if (m_enemyQueueIndex >= MAX_ENEMY_QUEUE)
		m_enemyQueueIndex = 0;

	if (m_enemyQueueCount < MAX_ENEMY_QUEUE)
		++m_enemyQueueCount;

	// clamp reaction time to enemy queue size
	auto reactionTime = GetProfile()->GetReactionTime();
	const auto maxReactionTime = (MAX_ENEMY_QUEUE * g_flBotFullThinkInterval) - 0.01s;
	if (reactionTime > maxReactionTime)
		reactionTime = maxReactionTime;

	// "rewind" time back to our reaction time
	int reactionTimeSteps = (int)((reactionTime.count() / (g_flBotFullThinkInterval / 1s)) + 0.5f);

	int i = now - reactionTimeSteps;
	if (i < 0)
		i += MAX_ENEMY_QUEUE;

	m_enemyQueueAttendIndex = (byte)i;
}

// Return the most dangerous threat we are "conscious" of

CBaseEntity *CCSBot::GetRecognizedEnemy()
{
	if (m_enemyQueueAttendIndex >= m_enemyQueueCount)
		return NULL;

	return m_enemyQueue[ m_enemyQueueAttendIndex ].player;
}

// Return true if the enemy we are "conscious" of is reloading

bool CCSBot::IsRecognizedEnemyReloading()
{
	if (m_enemyQueueAttendIndex >= m_enemyQueueCount)
		return false;

	return m_enemyQueue[ m_enemyQueueAttendIndex ].isReloading;
}

// Return true if the enemy we are "conscious" of is hiding behind a shield

bool CCSBot::IsRecognizedEnemyProtectedByShield()
{
	if (m_enemyQueueAttendIndex >= m_enemyQueueCount)
		return false;

	return m_enemyQueue[ m_enemyQueueAttendIndex ].isProtectedByShield;
}

// Return distance to closest enemy we are "conscious" of

float CCSBot::GetRangeToNearestRecognizedEnemy()
{
	const CBaseEntity *enemy = GetRecognizedEnemy();

	if (enemy != NULL)
	{
		return (pev->origin - enemy->pev->origin).Length();
	}

	return 99999999.9f;
}

// Blind the bot for the given duration

void CCSBot::Blind(duration_t duration, duration_t holdTime, duration_t fadeTime, int alpha)
{
	// extend
	CBasePlayer::Blind(duration, holdTime, fadeTime, alpha);

	PrintIfWatched("I'm blind!\n");

	if (RANDOM_FLOAT(0.0f, 100.0f) < 33.3f)
	{
		GetChatter()->Say("Blinded", 1.0s);
	}

	// decide which way to move while blind
	m_blindMoveDir = static_cast<NavRelativeDirType>(RANDOM_LONG(1, NUM_RELATIVE_DIRECTIONS - 1));

	// if blinded while in combat - then spray and pray!
	m_blindFire = (RANDOM_FLOAT(0.0f, 100.0f) < 10.0f) != 0;

	// no longer safe
	AdjustSafeTime();
}

}


for (float angle = angleInc; angle <= 135.0f; angle += angleInc)
	{
		// check both sides at this angle offset
		for (int side = 0; side < 2; ++side)
		{
			float actualAngle = (side) ? (startAngle + angle) : (startAngle - angle);

			float dx = BotCOS(actualAngle);
			float dy = BotSIN(actualAngle);

			// compute rotated point ray endpoint
			Vector rotPoint(eye->x + length * dx, eye->y + length * dy, point->z);

			TraceResult result;
			UTIL_TraceLine(*eye, rotPoint + Vector(0, 0, HalfHumanHeight), ignore_monsters, ENT(pev), &result);

			// if this ray started in an obstacle, skip it
			if (result.fStartSolid)
			{
				continue;
			}

			Vector ray = rotPoint - *eye;
			float rayLength = ray.NormalizeInPlace();
			float visibleLength = rayLength * result.flFraction;

			// step along ray, checking if point is visible from ray point
			const float bendStepSize = 50.0f;
			for (float bendLength = bendStepSize; bendLength <= visibleLength; bendLength += bendStepSize)
			{
				// compute point along ray
				Vector rayPoint = *eye + bendLength * ray;

				// check if we can see approach point from this bend point
				UTIL_TraceLine(rayPoint, *point + Vector(0, 0, HalfHumanHeight), ignore_monsters, ENT(pev), &result);

				if (result.flFraction == 1.0f && !result.fStartSolid)
				{
					// target is visible from this bend point on the ray - use this point on the ray as our point

					// keep "bent" point at correct height along line of sight
					if (!GetGroundHeight(&rayPoint, &rayPoint.z))
					{
						rayPoint.z = point->z;
					}

					*bend = rayPoint;
					return true;
				}
			}
		}
	}

	*bend = *point;

	// bending rays didn't help - still can't see the point
	return false;
}

#include "extdll.h"
#include "util.h"
#include "cbase.h"
#include "monsters.h"
#include "weapons.h"
#include "player.h"
#include "gamerules.h"
#include "training_gamerules.h"
#include "hltv.h"
#include "game.h"
#include "trains.h"
#include "vehicle.h"
#include "globals.h"

#include "bot_include.h"

namespace sv {

// Used to update view angles to stay on a ladder

float StayOnLadderLine(CCSBot *me, const CNavLadder *ladder)
{
	// determine our facing
	NavDirType faceDir = AngleToDirection(me->pev->v_angle.y);
	const float stiffness = 1.0f;

	// move toward ladder mount point
	switch (faceDir)
	{
	case NORTH:
		return (stiffness * (ladder->m_top.x - me->pev->origin.x));
	case EAST:
		return (stiffness * (ladder->m_top.y - me->pev->origin.y));
	case SOUTH:
		return (-stiffness * (ladder->m_top.x - me->pev->origin.x));
	case WEST:
		return (-stiffness * (ladder->m_top.y - me->pev->origin.y));
	default:
		break;
	}

	return 0.0f;
}

// Move actual view angles towards desired ones.
// This is the only place v_angle is altered.
// TODO: Make stiffness and turn rate constants timestep invariant.
void CCSBot::UpdateLookAngles()
{
	const auto deltaT = g_flBotCommandInterval;
	float maxAccel;
	float stiffness;
	float damping;

	// springs are stiffer when attacking, so we can track and move between targets better
	if (IsAttacking())
	{
		stiffness = 300.0f;
		damping = 30.0f;
		maxAccel = 3000.0f;
	}
	else
	{
		stiffness = 200.0f;
		damping = 25.0f;
		maxAccel = 3000.0f;
	}

	// these may be overridden by ladder logic
	float useYaw = m_lookYaw;
	float usePitch = m_lookPitch;

	// Ladders require precise movement, therefore we need to look at the
	// ladder as we approach and ascend/descend it.
	// If we are on a ladder, we need to look up or down to traverse it - override pitch in this case.
	// If we're trying to break something, though, we actually need to look at it before we can
	// look at the ladder
	if (IsUsingLadder())
	{
		// set yaw to aim at ladder
		Vector to = m_pathLadder->m_top - pev->origin;
		float idealYaw = UTIL_VecToYaw(to);

		NavDirType faceDir = m_pathLadder->m_dir;

		if (m_pathLadderFaceIn)
		{
			faceDir = OppositeDirection(faceDir);
		}

		const float lookAlongLadderRange = 100.0f;
		const float ladderPitch = 60.0f;

		// adjust pitch to look up/down ladder as we ascend/descend
		switch (m_pathLadderState)
		{
			case APPROACH_ASCENDING_LADDER:
			{
				Vector to = m_goalPosition - pev->origin;
				useYaw = idealYaw;

				if (to.IsLengthLessThan(lookAlongLadderRange))
					usePitch = -ladderPitch;
				break;
			}
			case APPROACH_DESCENDING_LADDER:
			{
				Vector to = m_goalPosition - pev->origin;
				useYaw = idealYaw;

				if (to.IsLengthLessThan(lookAlongLadderRange))
					usePitch = ladderPitch;
				break;
			}
			case FACE_ASCENDING_LADDER:
			{
				useYaw = idealYaw;
				usePitch = -ladderPitch;
				break;
			}
			case FACE_DESCENDING_LADDER:
			{
				useYaw = idealYaw;
				usePitch = ladderPitch;
				break;
			}
			case MOUNT_ASCENDING_LADDER:
			case ASCEND_LADDER:
			{
				useYaw = DirectionToAngle(faceDir) + StayOnLadderLine(this, m_pathLadder);
				usePitch = -ladderPitch;
				break;
			}
			case MOUNT_DESCENDING_LADDER:
			case DESCEND_LADDER:
			{
				useYaw = DirectionToAngle(faceDir) + StayOnLadderLine(this, m_pathLadder);
				usePitch = ladderPitch;
				break;
			}
			case DISMOUNT_ASCENDING_LADDER:
			case DISMOUNT_DESCENDING_LADDER:
			{
				useYaw = DirectionToAngle(faceDir);
				break;
			}
			default:
				break;
		}
	}

	// Yaw
	float angleDiff = NormalizeAngle(useYaw - pev->v_angle.y);

	// if almost at target angle, snap to it
	const float onTargetTolerance = 1.0f;
	if (angleDiff < onTargetTolerance && angleDiff > -onTargetTolerance)
	{
		m_lookYawVel = 0.0f;
		pev->v_angle.y = useYaw;
	}
	else
	{
		// simple angular spring/damper
		float accel = stiffness * angleDiff - damping * m_lookYawVel;

		// limit rate
		if (accel > maxAccel)
			accel = maxAccel;

		else if (accel < -maxAccel)
			accel = -maxAccel;

		m_lookYawVel += deltaT / 1s * accel;
		pev->v_angle.y += deltaT / 1s * m_lookYawVel;
	}

	// Pitch
	// Actually, this is negative pitch.
	angleDiff = usePitch - pev->v_angle.x;

	angleDiff = NormalizeAngle(angleDiff);

	if (false && angleDiff < onTargetTolerance && angleDiff > -onTargetTolerance)
	{
		m_lookPitchVel = 0.0f;
		pev->v_angle.x = usePitch;
	}
	else
	{
		// simple angular spring/damper
		// double the stiffness since pitch is only +/- 90 and yaw is +/- 180
		float accel = 2.0f * stiffness * angleDiff - damping * m_lookPitchVel;

		// limit rate
		if (accel > maxAccel)
			accel = maxAccel;

		else if (accel < -maxAccel)
			accel = -maxAccel;

		m_lookPitchVel += deltaT / 1s * accel;
		pev->v_angle.x += deltaT / 1s * m_lookPitchVel;
	}

	// limit range - avoid gimbal lock
	if (pev->v_angle.x < -89.0f)
		pev->v_angle.x = -89.0f;
	else if (pev->v_angle.x > 89.0f)
		pev->v_angle.x = 89.0f;

	pev->v_angle.z = 0.0f;
}

// Return true if we can see the point
bool CCSBot::IsVisible(const Vector *pos, bool testFOV) const
{
	// we can't see anything if we're blind
	if (IsBlind())
		return false;

	// is it in my general viewcone?
	if (testFOV && !(const_cast<CCSBot *>(this)->FInViewCone(pos)))
		return false;

	// check line of sight against smoke
	if (TheCSBots()->IsLineBlockedBySmoke(&GetEyePosition(), pos))
		return false;

	// check line of sight
	// Must include CONTENTS_MONSTER to pick up all non-brush objects like barrels
	TraceResult result;
	UTIL_TraceLine(GetEyePosition(), *pos, ignore_monsters, ignore_glass, ENT(pev), &result);

	if (result.flFraction != 1.0f)
		return false;

	return true;
}

// Return true if we can see any part of the player
// Check parts in order of importance. Return the first part seen in "visParts" if it is non-NULL.

bool CCSBot::IsVisible(CBasePlayer *player, bool testFOV, unsigned char *visParts) const
{
	if ((player->pev->flags & FL_NOTARGET) || (player->pev->effects & EF_NODRAW))
		return false;

	Vector spot = player->pev->origin;
	int testVisParts = NONE;

	// finish chest check
	if (IsVisible(&spot, testFOV))
		testVisParts |= CHEST;

	// check top of head
	spot = spot + Vector(0, 0, 25.0f);

	if (IsVisible(&spot, testFOV))
		testVisParts |= HEAD;

	// check feet
	const float standFeet = 34.0f;
	const float crouchFeet = 14.0f;

	if (player->pev->flags & FL_DUCKING)
		spot.z = player->pev->origin.z - crouchFeet;
	else
		spot.z = player->pev->origin.z - standFeet;

	// check feet
	if (IsVisible(&spot, testFOV))
		testVisParts |= FEET;

	const float edgeOffset = 13.0f;
	Vector2D dir = (player->pev->origin - pev->origin).Make2D();
	dir.NormalizeInPlace();

	Vector2D perp(-dir.y, dir.x);

	spot = player->pev->origin + Vector(perp.x * edgeOffset, perp.y * edgeOffset, 0);

	if (IsVisible(&spot, testFOV))
		testVisParts |= LEFT_SIDE;

	spot = player->pev->origin - Vector(perp.x * edgeOffset, perp.y * edgeOffset, 0);

	if (IsVisible(&spot, testFOV))
		testVisParts |= RIGHT_SIDE;

	if (visParts != NULL)
		*visParts = testVisParts;

	if (testVisParts != NONE)
		return true;

	return false;
}

bool CCSBot::IsEnemyPartVisible(VisiblePartType part) const
{
	if (!IsEnemyVisible())
		return false;

	return (m_visibleEnemyParts & part) != 0;
}

void CCSBot::UpdateLookAt()
{
	Vector to = m_lookAtSpot - EyePosition();
	Vector idealAngle = UTIL_VecToAngles(to);
	idealAngle.x = 360.0f - idealAngle.x;

	SetLookAngles(idealAngle.y, idealAngle.x);
}

// Look at the given point in space for the given duration (-1 means forever)

void CCSBot::SetLookAt(const char *desc, const Vector *pos, PriorityType pri, duration_t duration, bool clearIfClose, float angleTolerance)
{
	if (pos == NULL)
		return;

	// if currently looking at a point in space with higher priority, ignore this request
	if (m_lookAtSpotState != NOT_LOOKING_AT_SPOT && m_lookAtSpotPriority > pri)
		return;

	// if already looking at this spot, just extend the time
	const float tolerance = 10.0f;
	if (m_lookAtSpotState != NOT_LOOKING_AT_SPOT && VectorsAreEqual(pos, &m_lookAtSpot, tolerance))
	{
		m_lookAtSpotDuration = duration;

		if (m_lookAtSpotPriority < pri)
			m_lookAtSpotPriority = pri;
	}
	else
	{
		// look at new spot
		m_lookAtSpot = *pos;
		m_lookAtSpotState = LOOK_TOWARDS_SPOT;
		m_lookAtSpotDuration = duration;
		m_lookAtSpotPriority = pri;
	}

	m_lookAtSpotAngleTolerance = angleTolerance;
	m_lookAtSpotClearIfClose = clearIfClose;
	m_lookAtDesc = desc;
}

// Block all "look at" and "look around" behavior for given duration - just look ahead

void CCSBot::InhibitLookAround(duration_t duration)
{
	m_inhibitLookAroundTimestamp = gpGlobals->time + duration;
}

// Update enounter spot timestamps, etc

void CCSBot::UpdatePeripheralVision()
{
	// if we update at 10Hz, this ensures we test once every three
	constexpr auto peripheralUpdateInterval = 0.29s;
	if (gpGlobals->time - m_peripheralTimestamp < peripheralUpdateInterval)
		return;

	m_peripheralTimestamp = gpGlobals->time;

	if (m_spotEncounter)
	{
		// check LOS to all spots in case we see them with our "peripheral vision"
		Vector pos;
		for (auto &spotOrder : m_spotEncounter->spotList)
		{
			const Vector *spotPos = spotOrder.spot->GetPosition();

			pos.x = spotPos->x;
			pos.y = spotPos->y;
			pos.z = spotPos->z + HalfHumanHeight;

			if (!IsVisible(&pos, CHECK_FOV))
				continue;

			// can see hiding spot, remember when we saw it last
			SetHidingSpotCheckTimestamp(spotOrder.spot);
		}
	}
}

// Update the "looking around" behavior.

void CCSBot::UpdateLookAround(bool updateNow)
{
	// check if looking around has been inhibited
	// Moved inhibit to allow high priority enemy lookats to still occur
	if (gpGlobals->time < m_inhibitLookAroundTimestamp)
		return;

	constexpr auto recentThreatTime = 0.25s; // 1.0f;

	// Unless we can hear them moving, in which case look towards the noise
	if (!IsEnemyVisible())
	{
		const float noiseStartleRange = 1000.0f;
		if (CanHearNearbyEnemyGunfire(noiseStartleRange))
		{
			Vector spot = m_noisePosition;
			spot.z += HalfHumanHeight;

			SetLookAt("Check dangerous noise", &spot, PRIORITY_HIGH, recentThreatTime);
			InhibitLookAround(RandomDuration(2.0s, 4.0s));

			return;
		}
	}

	// If we recently saw an enemy, look towards where we last saw them
	if (!IsLookingAtSpot(PRIORITY_MEDIUM) && gpGlobals->time - m_lastSawEnemyTimestamp < recentThreatTime)
	{
		ClearLookAt();

		Vector spot = m_lastEnemyPosition;

		// find enemy position on the ground
		if (GetSimpleGroundHeight(&m_lastEnemyPosition, &spot.z))
		{
			spot.z += HalfHumanHeight;
			SetLookAt("Last Enemy Position", &spot, PRIORITY_MEDIUM, RandomDuration(2.0s, 3.0s), true);
			return;
		}
	}

	// Look at nearby enemy noises
	if (UpdateLookAtNoise())
		return;

	if (IsNotMoving())
	{
		// if we're sniping, zoom in to watch our approach points
		if (IsUsingSniperRifle())
		{
			// low skill bots don't pre-zoom
			if (GetProfile()->GetSkill() > 0.4f)
			{
				if (!IsViewMoving())
				{
					float range = ComputeWeaponSightRange();
					AdjustZoom(range);
				}
				else
				{
					// zoom out
					if (GetZoomLevel() != NO_ZOOM)
						SecondaryAttack();
				}
			}
		}

		if (m_lastKnownArea == NULL)
			return;

		if (gpGlobals->time < m_lookAroundStateTimestamp)
			return;

		// if we're sniping, switch look-at spots less often
		if (IsUsingSniperRifle())
			m_lookAroundStateTimestamp = gpGlobals->time + RandomDuration(5.0s, 10.0s);
		else
			m_lookAroundStateTimestamp = gpGlobals->time + RandomDuration(1.0s, 2.0s);

		if (m_approachPointCount == 0)
		{
			ClearLookAt();
			return;
		}

		int which = RANDOM_LONG(0, m_approachPointCount - 1);
		Vector spot = m_approachPoint[ which ];

		// don't look at the floor, look roughly at chest level
		// TODO: If this approach point is very near, this will cause us to aim up in the air if were crouching
		spot.z += HalfHumanHeight;

		SetLookAt("Approach Point (Hiding)", &spot, PRIORITY_LOW);

		return;
	}

	// Glance at "encouter spots" as we move past them
	if (m_spotEncounter)
	{
		// Check encounter spots
		if (!IsSafe() && !IsLookingAtSpot(PRIORITY_LOW))
		{
			// allow a short time to look where we're going
			if (gpGlobals->time < m_spotCheckTimestamp)
				return;

			// TODO: Use skill parameter instead of accuracy

			// lower skills have exponentially longer delays
			float asleep = (1.0f - GetProfile()->GetSkill());
			asleep *= asleep;
			asleep *= asleep;

			m_spotCheckTimestamp = gpGlobals->time + asleep * RandomDuration(10.0s, 30.0s);

			// figure out how far along the path segment we are
			Vector delta = m_spotEncounter->path.to - m_spotEncounter->path.from;
			float length = delta.Length();
			float adx = abs(delta.x);
			float ady = abs(delta.y);
			float t;

			if (adx > ady)
				t = (pev->origin.x - m_spotEncounter->path.from.x) / delta.x;
			else
				t = (pev->origin.y - m_spotEncounter->path.from.y) / delta.y;

			// advance parameter a bit so we "lead" our checks
			const float leadCheckRange = 50.0f;
			t += leadCheckRange / length;

			if (t < 0.0f)
				t = 0.0f;
			else if (t > 1.0f)
				t = 1.0f;

			// collect the unchecked spots so far
			#define MAX_DANGER_SPOTS 8
			HidingSpot *dangerSpot[MAX_DANGER_SPOTS];
			int dangerSpotCount = 0;
			int dangerIndex = 0;

			constexpr auto checkTime = 10.0s;
			const SpotOrder *spotOrder;

			for (auto &spotOrder : m_spotEncounter->spotList)
			{
				// if we have seen this spot recently, we don't need to look at it
				if (gpGlobals->time - GetHidingSpotCheckTimestamp(spotOrder.spot) <= checkTime)
					continue;

				if (spotOrder.t > t)
					break;

				dangerSpot[ dangerIndex++ ] = spotOrder.spot;
				if (dangerIndex >= MAX_DANGER_SPOTS)
					dangerIndex = 0;
				if (dangerSpotCount < MAX_DANGER_SPOTS)
					++dangerSpotCount;
			}

			if (dangerSpotCount)
			{
				// pick one of the spots at random
				int which = RANDOM_LONG(0, dangerSpotCount - 1);

				const Vector *checkSpot = dangerSpot[ which ]->GetPosition();
				// could be nullptr, fix it
				if (checkSpot)
				{
					Vector pos = *checkSpot;
					pos.z += HalfHumanHeight;

					// glance at the spot for minimum time
					SetLookAt("Encounter Spot", &pos, PRIORITY_LOW, 0s, true, 10.0f);
				}
				// immediately mark it as "checked", so we don't check it again
				// if we get distracted before we check it - that's the way it goes
				SetHidingSpotCheckTimestamp(dangerSpot[which]);
			}
		}
	}
}

// "Bend" our line of sight around corners until we can "see" the point.

bool CCSBot::BendLineOfSight(const Vector *eye, const Vector *point, Vector *bend) const
{
	// if we can directly see the point, use it
	TraceResult result;
	UTIL_TraceLine(*eye, *point + Vector(0, 0, HalfHumanHeight), ignore_monsters, ENT(pev), &result);

	if (result.flFraction == 1.0f && !result.fStartSolid)
	{
		// can directly see point, no bending needed
		*bend = *point;
		return true;
	}

	// "bend" our line of sight until we can see the approach point
	Vector v = *point - *eye;
	float startAngle = UTIL_VecToYaw(v);
	float length = v.Length2D();
	v.NormalizeInPlace();

	float angleInc = 10.0f;

CBasePlayer *CCSBot::FindMostDangerousThreat()
{
	// maximum number of simulataneously attendable threats
	const int MAX_THREATS = MAX_CLIENTS;
	struct CloseInfo
	{
		CBasePlayer *enemy;
		float range;
	}
	threat[ MAX_THREATS + 1 ];
	int threatCount = 0;

	m_bomber = NULL;

	m_closestVisibleFriend = NULL;
	float closeFriendRange = 99999999999.9f;

	m_closestVisibleHumanFriend = NULL;
	float closeHumanFriendRange = 99999999999.9f;

	int i;

	{
		for (i = 1; i <= gpGlobals->maxClients; ++i)
		{
			CBaseEntity *entity = UTIL_PlayerByIndex(i);

			if (entity == NULL)
				continue;

			if (FNullEnt(entity->pev))
				continue;

			// is it a player?
			if (!entity->IsPlayer())
				continue;

			CBasePlayer *player = static_cast<CBasePlayer *>(entity);

			// ignore self
			if (player->entindex() == entindex())
				continue;

			// is it alive?
			if (!player->IsAlive())
				continue;

			if ((player->pev->flags & FL_NOTARGET) || (player->pev->effects & EF_NODRAW))
				continue;

			// is it an enemy?
			if (g_pGameRules->IsTeamplay() && player->m_iTeam == m_iTeam)
			{
				TraceResult result;
				UTIL_TraceLine(GetEyePosition(), player->pev->origin, ignore_monsters, ignore_glass, edict(), &result);
				if (result.flFraction == 1.0f)
				{
					// update watch timestamp
					int idx = player->entindex() - 1;
					m_watchInfo[idx].timestamp = gpGlobals->time;
					m_watchInfo[idx].isEnemy = false;

					// keep track of our closest friend
					Vector to = pev->origin - player->pev->origin;
					float rangeSq = to.LengthSquared();
					if (rangeSq < closeFriendRange)
					{
						m_closestVisibleFriend = player;
						closeFriendRange = rangeSq;
					}

					// keep track of our closest human friend
					if (!player->IsBot() && rangeSq < closeHumanFriendRange)
					{
						m_closestVisibleHumanFriend = player;
						closeHumanFriendRange = rangeSq;
					}
				}

				continue;
			}

			// check if this enemy is fully
			if (!IsVisible(player, CHECK_FOV))
				continue;

			// update watch timestamp
			int idx = player->entindex() - 1;
			m_watchInfo[idx].timestamp = gpGlobals->time;
			m_watchInfo[idx].isEnemy = true;

			// note if we see the bomber
			if (player->IsBombGuy())
			{
				m_bomber = player;
			}

			// keep track of all visible threats
			Vector d = pev->origin - player->pev->origin;
			float distSq = d.LengthSquared();

			// maintain set of visible threats, sorted by increasing distance
			if (threatCount == 0)
			{
				threat[0].enemy = player;
				threat[0].range = distSq;
				threatCount = 1;
			}
			else
			{
				// find insertion point
				int j;
				for (j = 0; j < threatCount; ++j)
				{
					if (distSq < threat[j].range)
						break;
				}

				// shift lower half down a notch
				for (int k = threatCount - 1; k >= j; --k)
					threat[k + 1] = threat[k];

				// insert threat into sorted list
				threat[j].enemy = player;
				threat[j].range = distSq;

				if (threatCount < MAX_THREATS)
					++threatCount;
			}
		}
	}
	{
		// track the maximum enemy and friend counts we've seen recently
		int prevEnemies = m_nearbyEnemyCount;
		int prevFriends = m_nearbyFriendCount;
		m_nearbyEnemyCount = 0;
		m_nearbyFriendCount = 0;

		for (i = 0; i < MAX_CLIENTS; ++i)
		{
			if (m_watchInfo[i].timestamp <= invalid_time_point)
				continue;

			constexpr auto recentTime = 3.0s;
			if (gpGlobals->time - m_watchInfo[i].timestamp < recentTime)
			{
				if (m_watchInfo[i].isEnemy)
					++m_nearbyEnemyCount;
				else
					++m_nearbyFriendCount;
			}
		}

		// note when we saw this batch of enemies
		if (prevEnemies == 0 && m_nearbyEnemyCount > 0)
		{
			m_firstSawEnemyTimestamp = gpGlobals->time;
		}

		if (prevEnemies != m_nearbyEnemyCount || prevFriends != m_nearbyFriendCount)
		{
			PrintIfWatched("Nearby friends = %d, enemies = %d\n", m_nearbyFriendCount, m_nearbyEnemyCount);
		}
	}
	{
		// Track the place where we saw most of our enemies
		struct PlaceRank
		{
			unsigned int place;
			int count;
		};
		static PlaceRank placeRank[ MAX_PLACES_PER_MAP ];
		int locCount = 0;

		PlaceRank common;
		common.place = 0;
		common.count = 0;

		for (i = 0; i < threatCount; ++i)
		{
			// find the area the player/bot is standing on
			CNavArea *area;
			CCSBot *bot = dynamic_cast<CCSBot *>(threat[i].enemy);
			if (bot != NULL && bot->IsBot())
			{
				area = bot->GetLastKnownArea();
			}
			else
			{
				area = TheNavAreaGrid.GetNearestNavArea(&threat[i].enemy->pev->origin);
			}

			if (area == NULL)
				continue;

			unsigned int threatLoc = area->GetPlace();
			if (!threatLoc)
				continue;

			// if place is already in set, increment count
			int j;
			for (j = 0; j < locCount; ++j)
			{
				if (placeRank[j].place == threatLoc)
					break;
			}

			if (j == locCount)
			{
				if (locCount < MAX_PLACES_PER_MAP)
				{
					placeRank[ locCount ].place = threatLoc;
					placeRank[ locCount ].count = 1;

					if (common.count == 0)
						common = placeRank[locCount];

					++locCount;
				}
			}
			else
			{
				// others are in that place, increment
				++placeRank[j].count;

				// keep track of the most common place
				if (placeRank[j].count > common.count)
					common = placeRank[j];
			}
		}

		// remember most common place
		m_enemyPlace = common.place;
	}

	{
		if (threatCount == 0)
			return NULL;

		// otherwise, find the closest threat that without using shield
		int t;
		for (t = 0; t < threatCount; ++t)
		{
			if (!threat[t].enemy->IsProtectedByShield())
			{
				return threat[t].enemy;
			}
		}
	}

	// return closest threat
	return threat[0].enemy;
}

// Update our reaction time queue

void CCSBot::UpdateReactionQueue()
{
	// zombies dont see any threats
	if (cv_bot_zombie.value > 0.0f)
		return;

	// find biggest threat at this instant
	CBasePlayer *threat = FindMostDangerousThreat();

	int now = m_enemyQueueIndex;

	// store a snapshot of its state at the end of the reaction time queue
	if (threat != NULL)
	{
		m_enemyQueue[ now ].player = threat;
		m_enemyQueue[ now ].isReloading = threat->IsReloading();
		m_enemyQueue[ now ].isProtectedByShield = threat->IsProtectedByShield();
	}
	else
	{
		m_enemyQueue[ now ].player = NULL;
		m_enemyQueue[ now ].isReloading = false;
		m_enemyQueue[ now ].isProtectedByShield = false;
		if(m_isAttacking)
			StopAttacking();
	}

	// queue is round-robin
	++m_enemyQueueIndex;
	if (m_enemyQueueIndex >= MAX_ENEMY_QUEUE)
		m_enemyQueueIndex = 0;

	if (m_enemyQueueCount < MAX_ENEMY_QUEUE)
		++m_enemyQueueCount;

	// clamp reaction time to enemy queue size
	auto reactionTime = GetProfile()->GetReactionTime();
	const auto maxReactionTime = (MAX_ENEMY_QUEUE * g_flBotFullThinkInterval) - 0.01s;
	if (reactionTime > maxReactionTime)
		reactionTime = maxReactionTime;

	// "rewind" time back to our reaction time
	int reactionTimeSteps = (int)((reactionTime.count() / (g_flBotFullThinkInterval / 1s)) + 0.5f);

	int i = now - reactionTimeSteps;
	if (i < 0)
		i += MAX_ENEMY_QUEUE;

	m_enemyQueueAttendIndex = (byte)i;
}

// Return the most dangerous threat we are "conscious" of

CBaseEntity *CCSBot::GetRecognizedEnemy()
{
	if (m_enemyQueueAttendIndex >= m_enemyQueueCount)
		return NULL;

	return m_enemyQueue[ m_enemyQueueAttendIndex ].player;
}

// Return true if the enemy we are "conscious" of is reloading

bool CCSBot::IsRecognizedEnemyReloading()
{
	if (m_enemyQueueAttendIndex >= m_enemyQueueCount)
		return false;

	return m_enemyQueue[ m_enemyQueueAttendIndex ].isReloading;
}

// Return true if the enemy we are "conscious" of is hiding behind a shield

bool CCSBot::IsRecognizedEnemyProtectedByShield()
{
	if (m_enemyQueueAttendIndex >= m_enemyQueueCount)
		return false;

	return m_enemyQueue[ m_enemyQueueAttendIndex ].isProtectedByShield;
}

// Return distance to closest enemy we are "conscious" of

float CCSBot::GetRangeToNearestRecognizedEnemy()
{
	const CBaseEntity *enemy = GetRecognizedEnemy();

	if (enemy != NULL)
	{
		return (pev->origin - enemy->pev->origin).Length();
	}

	return 99999999.9f;
}

// Blind the bot for the given duration

void CCSBot::Blind(duration_t duration, duration_t holdTime, duration_t fadeTime, int alpha)
{
	// extend
	CBasePlayer::Blind(duration, holdTime, fadeTime, alpha);

	PrintIfWatched("I'm blind!\n");

	if (RANDOM_FLOAT(0.0f, 100.0f) < 33.3f)
	{
		GetChatter()->Say("Blinded", 1.0s);
	}

	// decide which way to move while blind
	m_blindMoveDir = static_cast<NavRelativeDirType>(RANDOM_LONG(1, NUM_RELATIVE_DIRECTIONS - 1));

	// if blinded while in combat - then spray and pray!
	m_blindFire = (RANDOM_FLOAT(0.0f, 100.0f) < 10.0f) != 0;

	// no longer safe
	AdjustSafeTime();
}

}


#include "extdll.h"
#include "util.h"
#include "cbase.h"
#include "monsters.h"
#include "weapons.h"
#include "player.h"
#include "gamerules.h"
#include "training_gamerules.h"
#include "hltv.h"
#include "game.h"
#include "trains.h"
#include "vehicle.h"
#include "globals.h"

#include "bot_include.h"

namespace sv {

// Used to update view angles to stay on a ladder

float StayOnLadderLine(CCSBot *me, const CNavLadder *ladder)
{
	// determine our facing
	NavDirType faceDir = AngleToDirection(me->pev->v_angle.y);
	const float stiffness = 1.0f;

	// move toward ladder mount point
	switch (faceDir)
	{
	case NORTH:
		return (stiffness * (ladder->m_top.x - me->pev->origin.x));
	case EAST:
		return (stiffness * (ladder->m_top.y - me->pev->origin.y));
	case SOUTH:
		return (-stiffness * (ladder->m_top.x - me->pev->origin.x));
	case WEST:
		return (-stiffness * (ladder->m_top.y - me->pev->origin.y));
	default:
		break;
	}

	return 0.0f;
}

// Move actual view angles towards desired ones.
// This is the only place v_angle is altered.
// TODO: Make stiffness and turn rate constants timestep invariant.
void CCSBot::UpdateLookAngles()
{
	const auto deltaT = g_flBotCommandInterval;
	float maxAccel;
	float stiffness;
	float damping;

	// springs are stiffer when attacking, so we can track and move between targets better
	if (IsAttacking())
	{
		stiffness = 300.0f;
		damping = 30.0f;
		maxAccel = 3000.0f;
	}
	else
	{
		stiffness = 200.0f;
		damping = 25.0f;
		maxAccel = 3000.0f;
	}

	// these may be overridden by ladder logic
	float useYaw = m_lookYaw;
	float usePitch = m_lookPitch;

	// Ladders require precise movement, therefore we need to look at the
	// ladder as we approach and ascend/descend it.
	// If we are on a ladder, we need to look up or down to traverse it - override pitch in this case.
	// If we're trying to break something, though, we actually need to look at it before we can
	// look at the ladder
	if (IsUsingLadder())
	{
		// set yaw to aim at ladder
		Vector to = m_pathLadder->m_top - pev->origin;
		float idealYaw = UTIL_VecToYaw(to);

		NavDirType faceDir = m_pathLadder->m_dir;

		if (m_pathLadderFaceIn)
		{
			faceDir = OppositeDirection(faceDir);
		}

		const float lookAlongLadderRange = 100.0f;
		const float ladderPitch = 60.0f;

		// adjust pitch to look up/down ladder as we ascend/descend
		switch (m_pathLadderState)
		{
			case APPROACH_ASCENDING_LADDER:
			{
				Vector to = m_goalPosition - pev->origin;
				useYaw = idealYaw;

				if (to.IsLengthLessThan(lookAlongLadderRange))
					usePitch = -ladderPitch;
				break;
			}
			case APPROACH_DESCENDING_LADDER:
			{
				Vector to = m_goalPosition - pev->origin;
				useYaw = idealYaw;

				if (to.IsLengthLessThan(lookAlongLadderRange))
					usePitch = ladderPitch;
				break;
			}
			case FACE_ASCENDING_LADDER:
			{
				useYaw = idealYaw;
				usePitch = -ladderPitch;
				break;
			}
			case FACE_DESCENDING_LADDER:
			{
				useYaw = idealYaw;
				usePitch = ladderPitch;
				break;
			}
			case MOUNT_ASCENDING_LADDER:
			case ASCEND_LADDER:
			{
				useYaw = DirectionToAngle(faceDir) + StayOnLadderLine(this, m_pathLadder);
				usePitch = -ladderPitch;
				break;
			}
			case MOUNT_DESCENDING_LADDER:
			case DESCEND_LADDER:
			{
				useYaw = DirectionToAngle(faceDir) + StayOnLadderLine(this, m_pathLadder);
				usePitch = ladderPitch;
				break;
			}
			case DISMOUNT_ASCENDING_LADDER:
			case DISMOUNT_DESCENDING_LADDER:
			{
				useYaw = DirectionToAngle(faceDir);
				break;
			}
			default:
				break;
		}
	}

	// Yaw
	float angleDiff = NormalizeAngle(useYaw - pev->v_angle.y);

	// if almost at target angle, snap to it
	const float onTargetTolerance = 1.0f;
	if (angleDiff < onTargetTolerance && angleDiff > -onTargetTolerance)
	{
		m_lookYawVel = 0.0f;
		pev->v_angle.y = useYaw;
	}
	else
	{
		// simple angular spring/damper
		float accel = stiffness * angleDiff - damping * m_lookYawVel;

		// limit rate
		if (accel > maxAccel)
			accel = maxAccel;

		else if (accel < -maxAccel)
			accel = -maxAccel;

		m_lookYawVel += deltaT / 1s * accel;
		pev->v_angle.y += deltaT / 1s * m_lookYawVel;
	}

	// Pitch
	// Actually, this is negative pitch.
	angleDiff = usePitch - pev->v_angle.x;

	angleDiff = NormalizeAngle(angleDiff);

	if (false && angleDiff < onTargetTolerance && angleDiff > -onTargetTolerance)
	{
		m_lookPitchVel = 0.0f;
		pev->v_angle.x = usePitch;
	}
	else
	{
		// simple angular spring/damper
		// double the stiffness since pitch is only +/- 90 and yaw is +/- 180
		float accel = 2.0f * stiffness * angleDiff - damping * m_lookPitchVel;

		// limit rate
		if (accel > maxAccel)
			accel = maxAccel;

		else if (accel < -maxAccel)
			accel = -maxAccel;

		m_lookPitchVel += deltaT / 1s * accel;
		pev->v_angle.x += deltaT / 1s * m_lookPitchVel;
	}

	// limit range - avoid gimbal lock
	if (pev->v_angle.x < -89.0f)
		pev->v_angle.x = -89.0f;
	else if (pev->v_angle.x > 89.0f)
		pev->v_angle.x = 89.0f;

	pev->v_angle.z = 0.0f;
}

// Return true if we can see the point
bool CCSBot::IsVisible(const Vector *pos, bool testFOV) const
{
	// we can't see anything if we're blind
	if (IsBlind())
		return false;

	// is it in my general viewcone?
	if (testFOV && !(const_cast<CCSBot *>(this)->FInViewCone(pos)))
		return false;

	// check line of sight against smoke
	if (TheCSBots()->IsLineBlockedBySmoke(&GetEyePosition(), pos))
		return false;

	// check line of sight
	// Must include CONTENTS_MONSTER to pick up all non-brush objects like barrels
	TraceResult result;
	UTIL_TraceLine(GetEyePosition(), *pos, ignore_monsters, ignore_glass, ENT(pev), &result);

	if (result.flFraction != 1.0f)
		return false;

	return true;
}

// Return true if we can see any part of the player
// Check parts in order of importance. Return the first part seen in "visParts" if it is non-NULL.

bool CCSBot::IsVisible(CBasePlayer *player, bool testFOV, unsigned char *visParts) const
{
	if ((player->pev->flags & FL_NOTARGET) || (player->pev->effects & EF_NODRAW))
		return false;

	Vector spot = player->pev->origin;
	int testVisParts = NONE;

	// finish chest check
	if (IsVisible(&spot, testFOV))
		testVisParts |= CHEST;

	// check top of head
	spot = spot + Vector(0, 0, 25.0f);

	if (IsVisible(&spot, testFOV))
		testVisParts |= HEAD;

	// check feet
	const float standFeet = 34.0f;
	const float crouchFeet = 14.0f;

	if (player->pev->flags & FL_DUCKING)
		spot.z = player->pev->origin.z - crouchFeet;
	else
		spot.z = player->pev->origin.z - standFeet;

	// check feet
	if (IsVisible(&spot, testFOV))
		testVisParts |= FEET;

	const float edgeOffset = 13.0f;
	Vector2D dir = (player->pev->origin - pev->origin).Make2D();
	dir.NormalizeInPlace();

	Vector2D perp(-dir.y, dir.x);

	spot = player->pev->origin + Vector(perp.x * edgeOffset, perp.y * edgeOffset, 0);

	if (IsVisible(&spot, testFOV))
		testVisParts |= LEFT_SIDE;

	spot = player->pev->origin - Vector(perp.x * edgeOffset, perp.y * edgeOffset, 0);

	if (IsVisible(&spot, testFOV))
		testVisParts |= RIGHT_SIDE;

	if (visParts != NULL)
		*visParts = testVisParts;

	if (testVisParts != NONE)
		return true;

	return false;
}

bool CCSBot::IsEnemyPartVisible(VisiblePartType part) const
{
	if (!IsEnemyVisible())
		return false;

	return (m_visibleEnemyParts & part) != 0;
}

void CCSBot::UpdateLookAt()
{
	Vector to = m_lookAtSpot - EyePosition();
	Vector idealAngle =

	idealAngle.x = 360.0f - idealAngle.x;

	SetLookAngles(idealAngle.y, idealAngle.x);
}

// Look at the given point in space for the given duration (-1 means forever)

void CCSBot::SetLookAt(const char *desc, const Vector *pos, PriorityType pri, duration_t duration, bool clearIfClose, float angleTolerance)
{
	if (pos == NULL)
		return;

	// if currently looking at a point in space with higher priority, ignore this request
	if (m_lookAtSpotState != NOT_LOOKING_AT_SPOT && m_lookAtSpotPriority > pri)
		return;

	// if already looking at this spot, just extend the time
	const float tolerance = 10.0f;
	if (m_lookAtSpotState != NOT_LOOKING_AT_SPOT && VectorsAreEqual(pos, &m_lookAtSpot, tolerance))
	{
		m_lookAtSpotDuration = duration;

		if (m_lookAtSpotPriority < pri)
			m_lookAtSpotPriority = pri;
	}
	else
	{
		// look at new spot
		m_lookAtSpot = *pos;
		m_lookAtSpotState = LOOK_TOWARDS_SPOT;
		m_lookAtSpotDuration = duration;
		m_lookAtSpotPriority = pri;
	}

	m_lookAtSpotAngleTolerance = angleTolerance;
	m_lookAtSpotClearIfClose = clearIfClose;
	m_lookAtDesc = desc;
}

// Block all "look at" and "look around" behavior for given duration - just look ahead

void CCSBot::InhibitLookAround(duration_t duration)
{
	m_inhibitLookAroundTimestamp = gpGlobals->time + duration;
}

// Update enounter spot timestamps, etc

void CCSBot::UpdatePeripheralVision()
{
	// if we update at 10Hz, this ensures we test once every three
	constexpr auto peripheralUpdateInterval = 0.29s;
	if (gpGlobals->time - m_peripheralTimestamp < peripheralUpdateInterval)
		return;

	m_peripheralTimestamp = gpGlobals->time;

	if (m_spotEncounter)
	{
		// check LOS to all spots in case we see them with our "peripheral vision"
		Vector pos;
		for (auto &spotOrder : m_spotEncounter->spotList)
		{
			const Vector *spotPos = spotOrder.spot->GetPosition();

			pos.x = spotPos->x;
			pos.y = spotPos->y;
			pos.z = spotPos->z + HalfHumanHeight;

			if (!IsVisible(&pos, CHECK_FOV))
				continue;

			// can see hiding spot, remember when we saw it last
			SetHidingSpotCheckTimestamp(spotOrder.spot);
		}
	}
}

// Update the "looking around" behavior.

void CCSBot::UpdateLookAround(bool updateNow)
{
	// check if looking around has been inhibited
	// Moved inhibit to allow high priority enemy lookats to still occur
	if (gpGlobals->time < m_inhibitLookAroundTimestamp)
		return;

	constexpr auto recentThreatTime = 0.25s; // 1.0f;

	// Unless we can hear them moving, in which case look towards the noise
	if (!IsEnemyVisible())
	{
		const float noiseStartleRange = 1000.0f;
		if (CanHearNearbyEnemyGunfire(noiseStartleRange))
		{
			Vector spot = m_noisePosition;
			spot.z += HalfHumanHeight;

			SetLookAt("Check dangerous noise", &spot, PRIORITY_HIGH, recentThreatTime);
			InhibitLookAround(RandomDuration(2.0s, 4.0s));

			return;
		}
	}

	// If we recently saw an enemy, look towards where we last saw them
	if (!IsLookingAtSpot(PRIORITY_MEDIUM) && gpGlobals->time - m_lastSawEnemyTimestamp < recentThreatTime)
	{
		ClearLookAt();

		Vector spot = m_lastEnemyPosition;

		// find enemy position on the ground
		if (GetSimpleGroundHeight(&m_lastEnemyPosition, &spot.z))
		{
			spot.z += HalfHumanHeight;
			SetLookAt("Last Enemy Position", &spot, PRIORITY_MEDIUM, RandomDuration(2.0s, 3.0s), true);
			return;
		}
	}

	// Look at nearby enemy noises
	if (UpdateLookAtNoise())
		return;

	if (IsNotMoving())
	{
		// if we're sniping, zoom in to watch our approach points
		if (IsUsingSniperRifle())
		{
			// low skill bots don't pre-zoom
			if (GetProfile()->GetSkill() > 0.4f)
			{
				if (!IsViewMoving())
				{
					float range = ComputeWeaponSightRange();
					AdjustZoom(range);
				}
				else
				{
					// zoom out
					if (GetZoomLevel() != NO_ZOOM)
						SecondaryAttack();
				}
			}
		}

		if (m_lastKnownArea == NULL)
			return;

		if (gpGlobals->time < m_lookAroundStateTimestamp)
			return;

		// if we're sniping, switch look-at spots less often
		if (IsUsingSniperRifle())
			m_lookAroundStateTimestamp = gpGlobals->time + RandomDuration(5.0s, 10.0s);
		else
			m_lookAroundStateTimestamp = gpGlobals->time + RandomDuration(1.0s, 2.0s);

		if (m_approachPointCount == 0)
		{
			ClearLookAt();
			return;
		}

		int which = RANDOM_LONG(0, m_approachPointCount - 1);
		Vector spot = m_approachPoint[ which ];

		// don't look at the floor, look roughly at chest level
		// TODO: If this approach point is very near, this will cause us to aim up in the air if were crouching
		spot.z += HalfHumanHeight;

		SetLookAt("Approach Point (Hiding)", &spot, PRIORITY_LOW);

		return;
	}

	// Glance at "encouter spots" as we move past them
	if (m_spotEncounter)
	{
		// Check encounter spots
		if (!IsSafe() && !IsLookingAtSpot(PRIORITY_LOW))
		{
			// allow a short time to look where we're going
			if (gpGlobals->time < m_spotCheckTimestamp)
				return;

			// TODO: Use skill parameter instead of accuracy

			// lower skills have exponentially longer delays
			float asleep = (1.0f - GetProfile()->GetSkill());
			asleep *= asleep;
			asleep *= asleep;

			m_spotCheckTimestamp = gpGlobals->time + asleep * RandomDuration(10.0s, 30.0s);

			// figure out how far along the path segment we are
			Vector delta = m_spotEncounter->path.to - m_spotEncounter->path.from;
			float length = delta.Length();
			float adx = abs(delta.x);
			float ady = abs(delta.y);
			float t;

			if (adx > ady)
				t = (pev->origin.x - m_spotEncounter->path.from.x) / delta.x;
			else
				t = (pev->origin.y - m_spotEncounter->path.from.y) / delta.y;

			// advance parameter a bit so we "lead" our checks
			const float leadCheckRange = 50.0f;
			t += leadCheckRange / length;

			if (t < 0.0f)
				t = 0.0f;
			else if (t > 1.0f)
				t = 1.0f;

			// collect the unchecked spots so far
			#define MAX_DANGER_SPOTS 8
			HidingSpot *dangerSpot[MAX_DANGER_SPOTS];
			int dangerSpotCount = 0;
			int dangerIndex = 0;

			constexpr auto checkTime = 10.0s;
			const SpotOrder *spotOrder;

			for (auto &spotOrder : m_spotEncounter->spotList)
			{
				// if we have seen this spot recently, we don't need to look at it
				if (gpGlobals->time - GetHidingSpotCheckTimestamp(spotOrder.spot) <= checkTime)
					continue;

				if (spotOrder.t > t)
					break;

				dangerSpot[ dangerIndex++ ] = spotOrder.spot;
				if (dangerIndex >= MAX_DANGER_SPOTS)
					dangerIndex = 0;
				if (dangerSpotCount < MAX_DANGER_SPOTS)
					++dangerSpotCount;
			}

			if (dangerSpotCount)
			{
				// pick one of the spots at random
				int which = RANDOM_LONG(0, dangerSpotCount - 1);

				const Vector *checkSpot = dangerSpot[ which ]->GetPosition();
				// could be nullptr, fix it
				if (checkSpot)
				{
					Vector pos = *checkSpot;
					pos.z += HalfHumanHeight;

					// glance at the spot for minimum time
					SetLookAt("Encounter Spot", &pos, PRIORITY_LOW, 0s, true, 10.0f);
				}
				// immediately mark it as "checked", so we don't check it again
				// if we get distracted before we check it - that's the way it goes
				SetHidingSpotCheckTimestamp(dangerSpot[which]);
			}
		}
	}
}

// "Bend" our line of sight around corners until we can "see" the point.

bool CCSBot::BendLineOfSight(const Vector *eye, const Vector *point, Vector *bend) const
{
	// if we can directly see the point, use it
	TraceResult result;
	UTIL_TraceLine(*eye, *point + Vector(0, 0, HalfHumanHeight), ignore_monsters, ENT(pev), &result);

	if (result.flFraction == 1.0f && !result.fStartSolid)
	{
		// can directly see point, no bending needed
		*bend = *point;
		return true;
	}

	// "bend" our line of sight until we can see the approach point
	Vector v = *point - *eye;
	float startAngle = UTIL_VecToYaw(v);
	float length = v.Length2D();
	v.NormalizeInPlace();

	float angleInc = 10.0f;
	for (float angle = angleInc; angle <= 135.0f; angle += angleInc)
	{
		// check both sides at this angle offset
		for (int side = 0; side < 2; ++side)
		{
			float actualAngle = (side) ? (startAngle + angle) : (startAngle - angle);

			float dx = BotCOS(actualAngle);
			float dy = BotSIN(actualAngle);

			// compute rotated point ray endpoint
			Vector rotPoint(eye->x + length * dx, eye->y + length * dy, point->z);

			TraceResult result;
			UTIL_TraceLine(*eye, rotPoint + Vector(0, 0, HalfHumanHeight), ignore_monsters, ENT(pev), &result);

			// if this ray started in an obstacle, skip it
			if (result.fStartSolid)
			{
				continue;
			}

			Vector ray = rotPoint - *eye;
			float rayLength = ray.NormalizeInPlace();
			float visibleLength = rayLength * result.flFraction;

			// step along ray, checking if point is visible from ray point
			const float bendStepSize = 50.0f;
			for (float bendLength = bendStepSize; bendLength <= visibleLength; bendLength += bendStepSize)
			{
				// compute point along ray
				Vector rayPoint = *eye + bendLength * ray;

				// check if we can see approach point from this bend point
				UTIL_TraceLine(rayPoint, *point + Vector(0, 0, HalfHumanHeight), ignore_monsters, ENT(pev), &result);

				if (result.flFraction == 1.0f && !result.fStartSolid)
				{
					// target is visible from this bend point on the ray - use this point on the ray as our point

					// keep "bent" point at correct height along line of sight
					if (!GetGroundHeight(&rayPoint, &rayPoint.z))
					{
						rayPoint.z = point->z;
					}

					*bend = rayPoint;
					return true;
				}
			}
		}
	}

	*bend = *point;

	// bending rays didn't help - still can't see the point
	return false;
}

CBasePlayer *CCSBot::FindMostDangerousThreat()
{
	// maximum number of simulataneously attendable threats
	const int MAX_THREATS = MAX_CLIENTS;
	struct CloseInfo
	{
		CBasePlayer *enemy;
		float range;
	}
	threat[ MAX_THREATS + 1 ];
	int threatCount = 0;

	m_bomber = NULL;

	m_closestVisibleFriend = NULL;
	float closeFriendRange = 99999999999.9f;

	m_closestVisibleHumanFriend = NULL;
	float closeHumanFriendRange = 99999999999.9f;

	int i;

	{
		for (i = 1; i <= gpGlobals->maxClients; ++i)
		{
			CBaseEntity *entity = UTIL_PlayerByIndex(i);

			if (entity == NULL)
				continue;

			if (FNullEnt(entity->pev))
				continue;

			// is it a player?
			if (!entity->IsPlayer())
				continue;

			CBasePlayer *player = static_cast<CBasePlayer *>(entity);

			// ignore self
			if (player->entindex() == entindex())
				continue;

			// is it alive?
			if (!player->IsAlive())
				continue;

			if ((player->pev->flags & FL_NOTARGET) || (player->pev->effects & EF_NODRAW))
				continue;

			// is it an enemy?
			if (g_pGameRules->IsTeamplay() && player->m_iTeam == m_iTeam)
			{
				TraceResult result;
				UTIL_TraceLine(GetEyePosition(), player->pev->origin, ignore_monsters, ignore_glass, edict(), &result);
				if (result.flFraction == 1.0f)
				{
					// update watch timestamp
					int idx = player->entindex() - 1;
					m_watchInfo[idx].timestamp = gpGlobals->time;
					m_watchInfo[idx].isEnemy = false;

					// keep track of our closest friend
					Vector to = pev->origin - player->pev->origin;
					float rangeSq = to.LengthSquared();
					if (rangeSq < closeFriendRange)
					{
						m_closestVisibleFriend = player;
						closeFriendRange = rangeSq;
					}

					// keep track of our closest human friend
					if (!player->IsBot() && rangeSq < closeHumanFriendRange)
					{
						m_closestVisibleHumanFriend = player;
						closeHumanFriendRange = rangeSq;
					}
				}

				continue;
			}

			// check if this enemy is fully
			if (!IsVisible(player, CHECK_FOV))
				continue;

			// update watch timestamp
			int idx = player->entindex() - 1;
			m_watchInfo[idx].timestamp = gpGlobals->time;
			m_watchInfo[idx].isEnemy = true;

			// note if we see the bomber
			if (player->IsBombGuy())
			{
				m_bomber = player;
			}

			// keep track of all visible threats
			Vector d = pev->origin - player->pev->origin;
			float distSq = d.LengthSquared();

			// maintain set of visible threats, sorted by increasing distance
			if (threatCount == 0)
			{
				threat[0].enemy = player;
				threat[0].range = distSq;
				threatCount = 1;
			}
			else
			{
				// find insertion point
				int j;
				for (j = 0; j < threatCount; ++j)
				{
					if (distSq < threat[j].range)
						break;
				}

				// shift lower half down a notch
				for (int k = threatCount - 1; k >= j; --k)
					threat[k + 1] = threat[k];

				// insert threat into sorted list
				threat[j].enemy = player;
				threat[j].range = distSq;

				if (threatCount < MAX_THREATS)
					++threatCount;
			}
		}
	}
	{
		// track the maximum enemy and friend counts we've seen recently
		int prevEnemies = m_nearbyEnemyCount;
		int prevFriends = m_nearbyFriendCount;
		m_nearbyEnemyCount = 0;
		m_nearbyFriendCount = 0;

		for (i = 0; i < MAX_CLIENTS; ++i)
		{
			if (m_watchInfo[i].timestamp <= invalid_time_point)
				continue;

			constexpr auto recentTime = 3.0s;
			if (gpGlobals->time - m_watchInfo[i].timestamp < recentTime)
			{
				if (m_watchInfo[i].isEnemy)
					++m_nearbyEnemyCount;
				else
					++m_nearbyFriendCount;
			}
		}

		// note when we saw this batch of enemies
		if (prevEnemies == 0 && m_nearbyEnemyCount > 0)
		{
			m_firstSawEnemyTimestamp = gpGlobals->time;
		}

		if (prevEnemies != m_nearbyEnemyCount || prevFriends != m_nearbyFriendCount)
		{
			PrintIfWatched("Nearby friends = %d, enemies = %d\n", m_nearbyFriendCount, m_nearbyEnemyCount);
		}
	}
	{
		// Track the place where we saw most of our enemies
		struct PlaceRank
		{
			unsigned int place;
			int count;
		};
		static PlaceRank placeRank[ MAX_PLACES_PER_MAP ];
		int locCount = 0;

		PlaceRank common;
		common.place = 0;
		common.count = 0;

		for (i = 0; i < threatCount; ++i)
		{
			// find the area the player/bot is standing on
			CNavArea *area;
			CCSBot *bot = dynamic_cast<CCSBot *>(threat[i].enemy);
			if (bot != NULL && bot->IsBot())
			{
				area = bot->GetLastKnownArea();
			}
			else
			{
				area = TheNavAreaGrid.GetNearestNavArea(&threat[i].enemy->pev->origin);
			}

			if (area == NULL)
				continue;

			unsigned int threatLoc = area->GetPlace();
			if (!threatLoc)
				continue;

			// if place is already in set, increment count
			int j;
			for (j = 0; j < locCount; ++j)
			{
				if (placeRank[j].place == threatLoc)
					break;
			}

			if (j == locCount)
			{
				if (locCount < MAX_PLACES_PER_MAP)
				{
					placeRank[ locCount ].place = threatLoc;
					placeRank[ locCount ].count = 1;

					if (common.count == 0)
						common = placeRank[locCount];

					++locCount;
				}
			}
			else
			{
				// others are in that place, increment
				++placeRank[j].count;

				// keep track of the most common place
				if (placeRank[j].count > common.count)
					common = placeRank[j];
			}
		}

		// remember most common place
		m_enemyPlace = common.place;
	}

	{
		if (threatCount == 0)
			return NULL;

		// otherwise, find the closest threat that without using shield
		int t;
		for (t = 0; t < threatCount; ++t)
		{
			if (!threat[t].enemy->IsProtectedByShield())
			{
				return threat[t].enemy;
			}
		}
	}

	// return closest threat
	return threat[0].enemy;
}

// Update our reaction time queue

void CCSBot::UpdateReactionQueue()
{
	// zombies dont see any threats
	if (cv_bot_zombie.value > 0.0f)
		return;

	// find biggest threat at this instant
	CBasePlayer *threat = FindMostDangerousThreat();

	int now = m_enemyQueueIndex;

	// store a snapshot of its state at the end of the reaction time queue
	if (threat != NULL)
	{
		m_enemyQueue[ now ].player = threat;
		m_enemyQueue[ now ].isReloading = threat->IsReloading();
		m_enemyQueue[ now ].isProtectedByShield = threat->IsProtectedByShield();
	}
	else
	{
		m_enemyQueue[ now ].player = NULL;
		m_enemyQueue[ now ].isReloading = false;
		m_enemyQueue[ now ].isProtectedByShield = false;
		if(m_isAttacking)
			StopAttacking();
	}

	// queue is round-robin
	++m_enemyQueueIndex;
	if (m_enemyQueueIndex >= MAX_ENEMY_QUEUE)
		m_enemyQueueIndex = 0;

	if (m_enemyQueueCount < MAX_ENEMY_QUEUE)
		++m_enemyQueueCount;

	// clamp reaction time to enemy queue size
	auto reactionTime = GetProfile()->GetReactionTime();
	const auto maxReactionTime = (MAX_ENEMY_QUEUE * g_flBotFullThinkInterval) - 0.01s;
	if (reactionTime > maxReactionTime)
		reactionTime = maxReactionTime;

	// "rewind" time back to our reaction time
	int reactionTimeSteps = (int)((reactionTime.count() / (g_flBotFullThinkInterval / 1s)) + 0.5f);

	int i = now - reactionTimeSteps;
	if (i < 0)
		i += MAX_ENEMY_QUEUE;

	m_enemyQueueAttendIndex = (byte)i;
}

// Return the most dangerous threat we are "conscious" of

CBaseEntity *CCSBot::GetRecognizedEnemy()
{
	if (m_enemyQueueAttendIndex >= m_enemyQueueCount)
		return NULL;

	return m_enemyQueue[ m_enemyQueueAttendIndex ].player;
}

// Return true if the enemy we are "conscious" of is reloading

bool CCSBot::IsRecognizedEnemyReloading()
{
	if (m_enemyQueueAttendIndex >= m_enemyQueueCount)
		return false;

	return m_enemyQueue[ m_enemyQueueAttendIndex ].isReloading;
}

// Return true if the enemy we are "conscious" of is hiding behind a shield

bool CCSBot::IsRecognizedEnemyProtectedByShield()
{
	if (m_enemyQueueAttendIndex >= m_enemyQueueCount)
		return false;

	return m_enemyQueue[ m_enemyQueueAttendIndex ].isProtectedByShield;
}

// Return distance to closest enemy we are "conscious" of

float CCSBot::GetRangeToNearestRecognizedEnemy()
{
	const CBaseEntity *enemy = GetRecognizedEnemy();

	if (enemy != NULL)
	{
		return (pev->origin - enemy->pev->origin).Length();
	}

	return 99999999.9f;
}

// Blind the bot for the given duration

void CCSBot::Blind(duration_t duration, duration_t holdTime, duration_t fadeTime, int alpha)
{
	// extend
	CBasePlayer::Blind(duration, holdTime, fadeTime, alpha);

	PrintIfWatched("I'm blind!\n");

	if (RANDOM_FLOAT(0.0f, 100.0f) < 33.3f)
	{
		GetChatter()->Say("Blinded", 1.0s);
	}

	// decide which way to move while blind
	m_blindMoveDir = static_cast<NavRelativeDirType>(RANDOM_LONG(1, NUM_RELATIVE_DIRECTIONS - 1));

	// if blinded while in combat - then spray and pray!
	m_blindFire = (RANDOM_FLOAT(0.0f, 100.0f) < 10.0f) != 0;

	// no longer safe
	AdjustSafeTime();
}

}