Portable Half-Life SDK. GoldSource and Xash3D. Crossplatform.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1011 lines
24 KiB

#include "bot_common.h"
// 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));
}
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 float 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;
}
}
}
// 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 * accel;
pev->v_angle.y += deltaT * 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 * accel;
pev->v_angle.x += deltaT * 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(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
{
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;
// check "edges"
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, float 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(float 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
const float peripheralUpdateInterval = 0.29f;
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"
const SpotOrder *spotOrder = NULL;
Vector pos;
FOR_EACH_LL (m_spotEncounter->spotList, it)
{
spotOrder = &m_spotEncounter->spotList[it];
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;
const float recentThreatTime = 0.25f; // 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(RANDOM_FLOAT(2.0f, 4.0f));
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, RANDOM_FLOAT(2.0f, 3.0f), 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 + RANDOM_FLOAT(5.0f, 10.0f);
else
m_lookAroundStateTimestamp = gpGlobals->time + RANDOM_FLOAT(1.0f, 2.0f);
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 * RANDOM_FLOAT(10.0f, 30.0f);
// 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 = (float)abs(int64(delta.x));
float ady = (float)abs(int64(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;
const float checkTime = 10.0f;
const SpotOrder *spotOrder;
FOR_EACH_LL (m_spotEncounter->spotList, it)
{
spotOrder = &m_spotEncounter->spotList[it];
// 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();
Vector pos = *checkSpot;
pos.z += HalfHumanHeight;
// glance at the spot for minimum time
SetLookAt("Encounter Spot", &pos, PRIORITY_LOW, 0, 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
enum { MAX_THREATS = 16 };
struct CloseInfo
{
CBasePlayer *enemy;
float range;
}
threat[ MAX_THREATS ];
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;
// is it an enemy?
//if (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 <= 0.0f)
continue;
const float recentTime = 3.0f;
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)
{
// new place
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;
}
// 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
float reactionTime = GetProfile()->GetReactionTime();
float maxReactionTime = (MAX_ENEMY_QUEUE * g_flBotFullThinkInterval) - 0.01f;
if (reactionTime > maxReactionTime)
reactionTime = maxReactionTime;
// "rewind" time back to our reaction time
int reactionTimeSteps = (int)((reactionTime / g_flBotFullThinkInterval) + 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
CBasePlayer *CCSBot::GetRecognizedEnemy()
{
if (m_enemyQueueAttendIndex >= m_enemyQueueCount)
return NULL;
return (CBasePlayer *)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 CBasePlayer *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(float duration, float holdTime, float 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.0f);
}
// 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();
}