Huga a7395b054d
Bot Chatter: Bots will react to enemy snipers presence (#1055)
Friendly fire fixes
Bot Chatter: Bots will react to enemy snipers presence
previously unused chatter on both 1.6 and czero but is used on cs source
Probably fixed where teammate that use sniperrifles looking at bots that don't use sniperrifles got warned
2025-03-28 05:06:17 +07:00

1785 lines
56 KiB
C++

/*
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* In addition, as a special exception, the author gives permission to
* link the code of this program with the Half-Life Game Engine ("HL
* Engine") and Modified Game Libraries ("MODs") developed by Valve,
* L.L.C ("Valve"). You must obey the GNU General Public License in all
* respects for all of the code used other than the HL Engine and MODs
* from Valve. If you modify this file, you may extend this exception
* to your version of the file, but you are not obligated to do so. If
* you do not wish to do so, delete this exception statement from your
* version.
*
*/
#pragma once
#include "bot/cs_gamestate.h"
#include "bot/cs_bot_manager.h"
#include "bot/cs_bot_chatter.h"
const int MAX_BUY_WEAPON_PRIMARY = 13;
const int MAX_BUY_WEAPON_SECONDARY = 3;
enum
{
BOT_PROGGRESS_DRAW = 0, // draw status bar progress
BOT_PROGGRESS_START, // init status bar progress
BOT_PROGGRESS_HIDE, // hide status bar progress
};
class CCSBot;
class BotChatterInterface;
class BotState
{
public:
virtual void OnEnter(CCSBot *me) {}
virtual void OnUpdate(CCSBot *me) {}
virtual void OnExit(CCSBot *me) {}
virtual const char *GetName() const = 0;
};
class IdleState: public BotState
{
public:
virtual void OnEnter(CCSBot *me);
virtual void OnUpdate(CCSBot *me);
virtual const char *GetName() const { return "Idle"; }
};
class HuntState: public BotState
{
public:
virtual void OnEnter(CCSBot *me);
virtual void OnUpdate(CCSBot *me);
virtual void OnExit(CCSBot *me);
virtual const char *GetName() const { return "Hunt"; }
void ClearHuntArea() { m_huntArea = nullptr; }
private:
CNavArea *m_huntArea;
};
class AttackState: public BotState
{
public:
virtual void OnEnter(CCSBot *me);
virtual void OnUpdate(CCSBot *me);
virtual void OnExit(CCSBot *me);
virtual const char *GetName() const { return "Attack"; }
void SetCrouchAndHold(bool crouch) { m_crouchAndHold = crouch; }
void StopAttacking(CCSBot *me);
protected:
enum DodgeStateType
{
STEADY_ON,
SLIDE_LEFT,
SLIDE_RIGHT,
JUMP,
NUM_ATTACK_STATES
} m_dodgeState;
float m_nextDodgeStateTimestamp;
CountdownTimer m_repathTimer;
float m_scopeTimestamp;
bool m_haveSeenEnemy;
bool m_isEnemyHidden;
float m_reacquireTimestamp;
float m_shieldToggleTimestamp;
bool m_shieldForceOpen;
float m_pinnedDownTimestamp;
bool m_crouchAndHold;
bool m_didAmbushCheck;
bool m_dodge;
bool m_firstDodge;
bool m_isCoward;
CountdownTimer m_retreatTimer;
};
class InvestigateNoiseState: public BotState
{
public:
virtual void OnEnter(CCSBot *me);
virtual void OnUpdate(CCSBot *me);
virtual void OnExit(CCSBot *me);
virtual const char *GetName() const { return "InvestigateNoise"; }
private:
void AttendCurrentNoise(CCSBot *me);
Vector m_checkNoisePosition;
};
class BuyState: public BotState
{
public:
virtual void OnEnter(CCSBot *me);
virtual void OnUpdate(CCSBot *me);
virtual void OnExit(CCSBot *me);
virtual const char *GetName() const { return "Buy"; }
private:
bool m_isInitialDelay;
int m_prefRetries;
int m_prefIndex;
int m_retries;
bool m_doneBuying;
bool m_buyDefuseKit;
bool m_buyGrenade;
bool m_buyShield;
bool m_buyPistol;
};
class MoveToState: public BotState
{
public:
virtual void OnEnter(CCSBot *me);
virtual void OnUpdate(CCSBot *me);
virtual void OnExit(CCSBot *me);
virtual const char *GetName() const { return "MoveTo"; }
void SetGoalPosition(const Vector &pos) { m_goalPosition = pos; }
void SetRouteType(RouteType route) { m_routeType = route; }
private:
Vector m_goalPosition;
RouteType m_routeType;
bool m_radioedPlan;
bool m_askedForCover;
};
class FetchBombState: public BotState
{
public:
virtual void OnEnter(CCSBot *me);
virtual void OnUpdate(CCSBot *me);
virtual const char *GetName() const { return "FetchBomb"; }
};
class PlantBombState: public BotState
{
public:
virtual void OnEnter(CCSBot *me);
virtual void OnUpdate(CCSBot *me);
virtual void OnExit(CCSBot *me);
virtual const char *GetName() const { return "PlantBomb"; }
};
class DefuseBombState: public BotState
{
public:
virtual void OnEnter(CCSBot *me);
virtual void OnUpdate(CCSBot *me);
virtual void OnExit(CCSBot *me);
virtual const char *GetName() const { return "DefuseBomb"; }
};
class HideState: public BotState
{
public:
virtual void OnEnter(CCSBot *me);
virtual void OnUpdate(CCSBot *me);
virtual void OnExit(CCSBot *me);
virtual const char *GetName() const { return "Hide"; }
public:
void SetHidingSpot(const Vector &pos) { m_hidingSpot = pos; }
const Vector &GetHidingSpot() const { return m_hidingSpot; }
void SetSearchArea(CNavArea *area) { m_searchFromArea = area; }
void SetSearchRange(float range) { m_range = range; }
void SetDuration(float time) { m_duration = time; }
void SetHoldPosition(bool hold) { m_isHoldingPosition = hold; }
bool IsAtSpot() const { return m_isAtSpot; }
private:
CNavArea *m_searchFromArea;
float m_range;
Vector m_hidingSpot;
bool m_isAtSpot;
float m_duration;
bool m_isHoldingPosition;
float m_holdPositionTime;
bool m_heardEnemy;
float m_firstHeardEnemyTime;
int m_retry;
Vector m_leaderAnchorPos;
};
class EscapeFromBombState: public BotState
{
public:
virtual void OnEnter(CCSBot *me);
virtual void OnUpdate(CCSBot *me);
virtual void OnExit(CCSBot *me);
virtual const char *GetName() const { return "EscapeFromBomb"; }
};
class FollowState: public BotState
{
public:
virtual void OnEnter(CCSBot *me);
virtual void OnUpdate(CCSBot *me);
virtual void OnExit(CCSBot *me);
virtual const char *GetName() const { return "Follow"; }
void SetLeader(CBasePlayer *pLeader) { m_leader = pLeader; }
private:
void ComputeLeaderMotionState(float leaderSpeed);
EntityHandle<CBasePlayer> m_leader;
Vector m_lastLeaderPos;
bool m_isStopped;
float m_stoppedTimestamp;
enum LeaderMotionStateType
{
INVALID,
STOPPED,
WALKING,
RUNNING
} m_leaderMotionState;
IntervalTimer m_leaderMotionStateTime;
bool m_isSneaking;
float m_lastSawLeaderTime;
CountdownTimer m_repathInterval;
IntervalTimer m_walkTime;
bool m_isAtWalkSpeed;
float m_waitTime;
CountdownTimer m_idleTimer;
};
class UseEntityState: public BotState
{
public:
virtual void OnEnter(CCSBot *me);
virtual void OnUpdate(CCSBot *me);
virtual void OnExit(CCSBot *me);
virtual const char *GetName() const { return "UseEntity"; }
void SetEntity(CBaseEntity *pEntity) { m_entity = pEntity; }
private:
EntityHandle<CBaseEntity> m_entity;
};
// The Counter-strike Bot
class CCSBot: public CBot
{
public:
CCSBot(); // constructor initializes all values to zero
virtual BOOL TakeDamage(entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType); // invoked when injured by something (EXTEND) - returns the amount of damage inflicted
virtual void Killed(entvars_t *pevAttacker, int iGib); // invoked when killed (EXTEND)
virtual void RoundRespawn();
virtual void Blind(float duration, float holdTime, float fadeTime, int alpha = 255); // player blinded by a flashbang
virtual void OnTouchingWeapon(CWeaponBox *box); // invoked when in contact with a CWeaponBox
virtual bool Initialize(const BotProfile *profile); // (EXTEND) prepare bot for action
virtual void SpawnBot(); // (EXTEND) spawn the bot into the game
virtual void Upkeep(); // lightweight maintenance, invoked frequently
virtual void Update(); // heavyweight algorithms, invoked less often
virtual void Walk();
virtual bool Jump(bool mustJump = false); // returns true if jump was started
virtual void OnEvent(GameEventType event, CBaseEntity *pEntity = nullptr, CBaseEntity *pOther = nullptr); // invoked when event occurs in the game (some events have NULL entity)
#define CHECK_FOV true
virtual bool IsVisible(const Vector *pos, bool testFOV = false) const; // return true if we can see the point
virtual bool IsVisible(CBasePlayer *pPlayer, bool testFOV = false, unsigned char *visParts = nullptr) const; // return true if we can see any part of the player
virtual bool IsEnemyPartVisible(VisiblePartType part) const; // if enemy is visible, return the part we see for our current enemy
public:
void Disconnect();
// behavior properties
float GetCombatRange() const;
bool IsRogue() const; // return true if we dont listen to teammates or pursue scenario goals
void SetRogue(bool rogue);
bool IsHurrying() const; // return true if we are in a hurry
void Hurry(float duration); // force bot to hurry
bool IsSafe() const; // return true if we are in a safe region
bool IsWellPastSafe() const; // return true if it is well past the early, "safe", part of the round
bool IsEndOfSafeTime() const; // return true if we were in the safe time last update, but not now
float GetSafeTimeRemaining() const; // return the amount of "safe time" we have left
float GetSafeTime() const; // return what we think the total "safe time" for this map is
//bool IsUnhealthy() const; // returns true if bot is low on health
// behaviors
void Idle();
void Hide(CNavArea *searchFromArea = nullptr, float duration = -1.0f, float hideRange = 750.0f, bool holdPosition = false); // DEPRECATED: Use TryToHide() instead
#define USE_NEAREST true
bool TryToHide(CNavArea *searchFromArea = nullptr, float duration = -1.0f, float hideRange = 750.0f, bool holdPosition = false, bool useNearest = false); // try to hide nearby, return false if cannot
void Hide(const Vector *hidingSpot, float duration = -1.0f, bool holdPosition = false); // move to the given hiding place
bool IsHiding() const; // returns true if bot is currently hiding
bool IsAtHidingSpot() const; // return true if we are hiding and at our hiding spot
bool TryToRetreat(); // retreat to a nearby hiding spot, away from enemies
void Hunt();
bool IsHunting() const; // returns true if bot is currently hunting
void Attack(CBasePlayer *victim);
void FireWeaponAtEnemy(); // fire our active weapon towards our current enemy
void StopAttacking();
bool IsAttacking() const; // returns true if bot is currently engaging a target
void MoveTo(const Vector *pos, RouteType route = SAFEST_ROUTE); // move to potentially distant position
bool IsMovingTo() const; // return true if we are in the MoveTo state
void PlantBomb();
void FetchBomb(); // bomb has been dropped - go get it
bool NoticeLooseBomb() const; // return true if we noticed the bomb on the ground or on radar
bool CanSeeLooseBomb() const; // return true if we directly see the loose bomb
bool IsCarryingBomb() const;
void DefuseBomb();
bool IsDefusingBomb() const; // returns true if bot is currently defusing the bomb
bool CanSeePlantedBomb() const; // return true if we directly see the planted bomb
void EscapeFromBomb();
bool IsEscapingFromBomb() const; // return true if we are escaping from the bomb
void RescueHostages();
void UseEntity(CBaseEntity *pEntity); // use the entity
bool IsBuying() const;
#ifdef REGAMEDLL_FIXES
void Kill();
#endif
void Panic(CBasePlayer *pEnemy); // look around in panic
void Follow(CBasePlayer *pPlayer); // begin following given Player
void ContinueFollowing(); // continue following our leader after finishing what we were doing
void StopFollowing(); // stop following
bool IsFollowing() const; // return true if we are following someone (not necessarily in the follow state)
CBasePlayer *GetFollowLeader(); // return the leader we are following
float GetFollowDuration() const; // return how long we've been following our leader
bool CanAutoFollow() const; // return true if we can auto-follow
bool IsNotMoving() const; // return true if we are currently standing still
void AimAtEnemy(); // point our weapon towards our enemy
void StopAiming(); // stop aiming at enemy
bool IsAimingAtEnemy() const; // returns true if we are trying to aim at an enemy
bool IsSurprised() const; // return true if we are "surprised"
float GetSurpriseDelay() const;
void ClearSurpriseDelay();
float GetStateTimestamp() const; // get time current state was entered
bool IsDoingScenario() const; // return true if we will do scenario-related tasks
// scenario / gamestate
CSGameState *GetGameState(); // return an interface to this bot's gamestate
const CSGameState *GetGameState() const; // return an interface to this bot's gamestate
bool IsAtBombsite(); // return true if we are in a bomb planting zone
bool GuardRandomZone(float range = 500.0f); // pick a random zone and hide near it
bool IsBusy() const; // return true if we are busy doing something important
// high-level tasks
enum TaskType
{
SEEK_AND_DESTROY,
PLANT_BOMB,
FIND_TICKING_BOMB,
DEFUSE_BOMB,
GUARD_TICKING_BOMB,
GUARD_BOMB_DEFUSER,
GUARD_LOOSE_BOMB,
GUARD_BOMB_ZONE,
ESCAPE_FROM_BOMB,
HOLD_POSITION,
FOLLOW,
VIP_ESCAPE,
GUARD_VIP_ESCAPE_ZONE,
COLLECT_HOSTAGES,
RESCUE_HOSTAGES,
GUARD_HOSTAGES,
GUARD_HOSTAGE_RESCUE_ZONE,
MOVE_TO_LAST_KNOWN_ENEMY_POSITION,
MOVE_TO_SNIPER_SPOT,
SNIPING,
NUM_TASKS
};
void SetTask(TaskType task, CBaseEntity *pEntity = nullptr); // set our current "task"
TaskType GetTask() const;
CBaseEntity *GetTaskEntity();
// behavior modifiers
enum DispositionType
{
ENGAGE_AND_INVESTIGATE, // engage enemies on sight and investigate enemy noises
OPPORTUNITY_FIRE, // engage enemies on sight, but only look towards enemy noises, dont investigate
SELF_DEFENSE, // only engage if fired on, or very close to enemy
IGNORE_ENEMIES, // ignore all enemies - useful for ducking around corners, running away, etc
NUM_DISPOSITIONS
};
void SetDisposition(DispositionType disposition); // define how we react to enemies
DispositionType GetDisposition() const; // return enum describing current disposition
void IgnoreEnemies(float duration); // ignore enemies for a short duration
enum MoraleType
{
TERRIBLE = -3,
BAD = -2,
NEGATIVE = -1,
NEUTRAL = 0,
POSITIVE = 1,
GOOD = 2,
EXCELLENT = 3,
};
MoraleType GetMorale() const; // return enum describing current morale
void IncreaseMorale();
void DecreaseMorale();
// listening for noises
bool IsNoiseHeard() const; // return true if we have heard a noise
bool ShouldInvestigateNoise(float *retNoiseDist = nullptr);
void InvestigateNoise(); // investigate recent enemy noise
const Vector *GetNoisePosition() const; // return position of last heard noise, or NULL if none heard
CNavArea *GetNoiseArea() const; // return area where noise was heard
void ForgetNoise(); // clear the last heard noise
bool CanSeeNoisePosition() const; // return true if we directly see where we think the noise came from
float GetNoiseRange() const; // return approximate distance to last noise heard
bool CanHearNearbyEnemyGunfire(float range = -1.0f) const; // return true if we hear nearby threatening enemy gunfire within given range (-1 == infinite)
PriorityType GetNoisePriority() const; // return priority of last heard noise
// radio and chatter
void SendRadioMessage(GameEventType event); // send voice chatter
BotChatterInterface *GetChatter(); // return an interface to this bot's chatter system
bool RespondToHelpRequest(CBasePlayer *them, Place place, float maxRange = -1.0f); // decide if we should move to help the player, return true if we will
void StartVoiceFeedback(float duration);
bool IsUsingVoice() const; // new-style "voice" chatter gets voice feedback
// enemies
// BOTPORT: GetEnemy() collides with GetEnemy() in CBaseEntity - need to use different nomenclature
void SetEnemy(CBasePlayer *pEnemy); // set given player as our current enemy
CBasePlayer *GetEnemy();
int GetNearbyEnemyCount() const; // return max number of nearby enemies we've seen recently
unsigned int GetEnemyPlace() const; // return location where we see the majority of our enemies
bool CanSeeBomber() const; // return true if we can see the bomb carrier
CBasePlayer *GetBomber() const;
int GetNearbyFriendCount() const; // return number of nearby teammates
CBasePlayer *GetClosestVisibleFriend() const; // return the closest friend that we can see
CBasePlayer *GetClosestVisibleHumanFriend() const; // return the closest human friend that we can see
bool IsOutnumbered() const; // return true if we are outnumbered by enemies
int OutnumberedCount() const; // return number of enemies we are outnumbered by
#define ONLY_VISIBLE_ENEMIES true
CBasePlayer *GetImportantEnemy(bool checkVisibility = false) const; // return the closest "important" enemy for the given scenario (bomb carrier, VIP, hostage escorter)
void UpdateReactionQueue(); // update our reaction time queue
CBasePlayer *GetRecognizedEnemy(); // return the most dangerous threat we are "conscious" of
bool IsRecognizedEnemyReloading(); // return true if the enemy we are "conscious" of is reloading
bool IsRecognizedEnemyProtectedByShield(); // return true if the enemy we are "conscious" of is hiding behind a shield
float GetRangeToNearestRecognizedEnemy(); // return distance to closest enemy we are "conscious" of
CBasePlayer *GetAttacker() const; // return last enemy that hurt us
float GetTimeSinceAttacked() const; // return duration since we were last injured by an attacker
float GetFirstSawEnemyTimestamp() const; // time since we saw any enemies
float GetLastSawEnemyTimestamp() const;
float GetTimeSinceLastSawEnemy() const;
float GetTimeSinceAcquiredCurrentEnemy() const;
bool HasNotSeenEnemyForLongTime() const; // return true if we haven't seen an enemy for "a long time"
const Vector &GetLastKnownEnemyPosition() const;
bool IsEnemyVisible() const; // is our current enemy visible
float GetEnemyDeathTimestamp() const;
bool IsFriendInLineOfFire(); // return true if a friend is in our weapon's way
bool IsAwareOfEnemyDeath() const; // return true if we *noticed* that our enemy died
int GetLastVictimID() const; // return the ID (entindex) of the last victim we killed, or zero
#ifdef REGAMEDLL_ADD
bool CanSeeSniper(void) const; ///< return true if we can see an enemy sniper
bool HasSeenSniperRecently(void) const; ///< return true if we have seen a sniper recently
#endif
// navigation
bool HasPath() const;
void DestroyPath();
float GetFeetZ() const; // return Z of bottom of feet
enum PathResult
{
PROGRESSING, // we are moving along the path
END_OF_PATH, // we reached the end of the path
PATH_FAILURE, // we failed to reach the end of the path
};
#define NO_SPEED_CHANGE false
PathResult UpdatePathMovement(bool allowSpeedChange = true); // move along our computed path - if allowSpeedChange is true, bot will walk when near goal to ensure accuracy
bool AStarSearch(CNavArea *startArea, CNavArea *goalArea); // find shortest path from startArea to goalArea - don't actually buid the path
bool ComputePath(CNavArea *goalArea, const Vector *goal, RouteType route); // compute path to goal position
bool StayOnNavMesh();
CNavArea *GetLastKnownArea() const; // return the last area we know we were inside of
const Vector &GetPathEndpoint() const; // return final position of our current path
float GetPathDistanceRemaining() const; // eturn estimated distance left to travel along path
void ResetStuckMonitor();
bool IsAreaVisible(CNavArea *area) const; // is any portion of the area visible to this bot
const Vector &GetPathPosition(int numpath) const;
bool GetSimpleGroundHeightWithFloor(const Vector *pos, float *height, Vector *normal = nullptr); // find "simple" ground height, treating current nav area as part of the floor
Place GetPlace() const; // get our current radio chatter place
bool IsUsingLadder() const; // returns true if we are in the process of negotiating a ladder
void GetOffLadder();
void SetGoalEntity(CBaseEntity *pEntity);
template <typename T = CBaseEntity>
T *GetGoalEntity();
bool IsNearJump() const; // return true if nearing a jump in the path
float GetApproximateFallDamage(float height) const; // return how much damage will will take from the given fall height
void ForceRun(float duration); // force the bot to run if it moves for the given duration
void Wiggle(); // random movement, for getting un-stuck
bool IsFriendInTheWay(const Vector *goalPos) const; // return true if a friend is between us and the given position
void FeelerReflexAdjustment(Vector *goalPosition); // do reflex avoidance movements if our "feelers" are touched
// looking around
void SetLookAngles(float yaw, float pitch); // set our desired look angles
void UpdateLookAngles(); // move actual view angles towards desired ones
void UpdateLookAround(bool updateNow = false); // update "looking around" mechanism
void InhibitLookAround(float duration); // block all "look at" and "looking around" behavior for given duration - just look ahead
// TODO: Clean up notion of "forward angle" and "look ahead angle"
void SetForwardAngle(float angle); // define our forward facing
void SetLookAheadAngle(float angle); // define default look ahead angle
// look at the given point in space for the given duration (-1 means forever)
void SetLookAt(const char *desc, const Vector *pos, PriorityType pri, float duration = -1.0f, bool clearIfClose = false, float angleTolerance = 5.0f);
void ClearLookAt(); // stop looking at a point in space and just look ahead
bool IsLookingAtSpot(PriorityType pri = PRIORITY_LOW) const; // return true if we are looking at spot with equal or higher priority
bool IsViewMoving(float angleVelThreshold = 1.0f) const; // returns true if bot's view angles are rotating (not still)
const Vector &GetEyePosition() const
{
m_eyePos = pev->origin + pev->view_ofs;
return m_eyePos;
}
float ComputeWeaponSightRange(); // return line-of-sight distance to obstacle along weapon fire ray
bool IsSignificantlyCloser(const CBasePlayer *testPlayer, const CBasePlayer *referencePlayer) const; // return true if testPlayer is significantly closer than referencePlayer
// approach points
void ComputeApproachPoints(); // determine the set of "approach points" representing where the enemy can enter this region
void UpdateApproachPoints(); // recompute the approach point set if we have moved far enough to invalidate the current ones
void ClearApproachPoints();
void DrawApproachPoints(); // for debugging
float GetHidingSpotCheckTimestamp(HidingSpot *spot) const; // return time when given spot was last checked
void SetHidingSpotCheckTimestamp(HidingSpot *spot); // set the timestamp of the given spot to now
// weapon query and equip
#define MUST_EQUIP true
void EquipBestWeapon(bool mustEquip = false); // equip the best weapon we are carrying that has ammo
void EquipPistol(); // equip our pistol
void EquipKnife(); // equip our knife
#define DONT_USE_SMOKE_GRENADE true
bool EquipGrenade(bool noSmoke = false); // equip a grenade, return false if we cant
bool IsUsingKnife() const; // returns true if we have knife equipped
bool IsUsingPistol() const; // returns true if we have pistol equipped
bool IsUsingGrenade() const; // returns true if we have grenade equipped
bool IsUsingSniperRifle() const; // returns true if using a "sniper" rifle
bool IsUsingAWP() const; // returns true if we have AWP equipped
bool IsSniper() const; // return true if we have a sniper rifle in our inventory
bool IsSniping() const; // return true if we are actively sniping (moving to sniper spot or settled in)
bool IsUsingShotgun() const; // returns true if using a shotgun
bool IsUsingMachinegun() const; // returns true if using the big 'ol machinegun
void ThrowGrenade(const Vector *target); // begin the process of throwing the grenade
bool IsThrowingGrenade() const; // return true if we are in the process of throwing a grenade
bool HasGrenade() const; // return true if we have a grenade in our inventory
bool DoesActiveWeaponHaveSilencer() const;
bool IsUsingHEGrenade() const;
void StartRapidFire();
void StopRapidFire();
bool IsRapidFiring() const;
enum ZoomType { NO_ZOOM, LOW_ZOOM, HIGH_ZOOM };
ZoomType GetZoomLevel() const; // return the current zoom level of our weapon
bool AdjustZoom(float range); // change our zoom level to be appropriate for the given range
bool IsPrimaryWeaponEmpty() const; // return true if primary weapon doesn't exist or is totally out of ammo
bool IsPistolEmpty() const; // return true if secondary weapon doesn't exist or is totally out of ammo
int GetHostageEscortCount() const;
void IncreaseHostageEscortCount();
float GetRangeToFarthestEscortedHostage() const;
void ResetWaitForHostagePatience();
void ResetValues(); // reset internal data to initial state
void BotDeathThink();
CBasePlayer *FindNearbyPlayer();
void AdjustSafeTime(); // called when enemy seen to adjust safe time for this round
void EXPORT BotTouch(CBaseEntity *pOther);
bool HasAnyAmmo(CBasePlayerWeapon *weapon) const;
private:
friend class CCSBotManager;
// TODO: Get rid of these
friend class AttackState;
friend class BuyState;
char m_name[64]; // copied from STRING(pev->netname) for debugging
// behavior properties
float m_combatRange; // desired distance between us and them during gunplay
mutable bool m_isRogue; // if true, the bot is a "rogue" and listens to no-one
mutable CountdownTimer m_rogueTimer;
MoraleType m_morale; // our current morale, based on our win/loss history
bool m_diedLastRound; // true if we died last round
float m_safeTime; // duration at the beginning of the round where we feel "safe"
bool m_wasSafe; // true if we were in the safe time last update
NavRelativeDirType m_blindMoveDir; // which way to move when we're blind
bool m_blindFire; // if true, fire weapon while blinded
// TODO: implement through CountdownTimer
float m_surpriseDelay; // when we were surprised
float m_surpriseTimestamp;
bool m_isFollowing; // true if we are following someone
EntityHandle<CBasePlayer> m_leader; // the ID of who we are following
float m_followTimestamp; // when we started following
float m_allowAutoFollowTime; // time when we can auto follow
CountdownTimer m_hurryTimer; // if valid, bot is in a hurry
// instances of each possible behavior state, to avoid dynamic memory allocation during runtime
IdleState m_idleState;
HuntState m_huntState;
AttackState m_attackState;
InvestigateNoiseState m_investigateNoiseState;
BuyState m_buyState;
MoveToState m_moveToState;
FetchBombState m_fetchBombState;
PlantBombState m_plantBombState;
DefuseBombState m_defuseBombState;
HideState m_hideState;
EscapeFromBombState m_escapeFromBombState;
FollowState m_followState;
UseEntityState m_useEntityState;
// TODO: Allow multiple simultaneous state machines (look around, etc)
void SetState(BotState *state); // set the current behavior state
BotState *m_state; // current behavior state
float m_stateTimestamp; // time state was entered
bool m_isAttacking; // if true, special Attack state is overriding the state machine
TaskType m_task; // our current task
EntityHandle<CBaseEntity> m_taskEntity; // an entity used for our task
// navigation
Vector m_goalPosition;
EHandle m_goalEntity;
void MoveTowardsPosition(const Vector *pos); // move towards position, independant of view angle
void MoveAwayFromPosition(const Vector *pos); // move away from position, independant of view angle
void StrafeAwayFromPosition(const Vector *pos); // strafe (sidestep) away from position, independant of view angle
void StuckCheck(); // check if we have become stuck
CNavArea *m_currentArea; // the nav area we are standing on
CNavArea *m_lastKnownArea; // the last area we were in
EntityHandle<CBasePlayer> m_avoid; // higher priority player we need to make way for
float m_avoidTimestamp;
bool m_isJumpCrouching;
bool m_isJumpCrouched;
float m_jumpCrouchTimestamp;
// path navigation data
enum { MAX_PATH_LENGTH = 256 };
struct ConnectInfo
{
CNavArea *area; // the area along the path
NavTraverseType how; // how to enter this area from the previous one
Vector pos; // our movement goal position at this point in the path
const CNavLadder *ladder; // if "how" refers to a ladder, this is it
}
m_path[MAX_PATH_LENGTH];
int m_pathLength;
int m_pathIndex;
float m_areaEnteredTimestamp;
void BuildTrivialPath(const Vector *goal); // build trivial path to goal, assuming we are already in the same area
bool FindGrenadeTossPathTarget(Vector *pos);
CountdownTimer m_repathTimer; // must have elapsed before bot can pathfind again
bool ComputePathPositions(); // determine actual path positions bot will move between along the path
void SetupLadderMovement();
void SetPathIndex(int newIndex); // set the current index along the path
void DrawPath();
int FindOurPositionOnPath(Vector *close, bool local = false) const; // compute the closest point to our current position on our path
int FindPathPoint(float aheadRange, Vector *point, int *prevIndex = nullptr); // compute a point a fixed distance ahead along our path.
bool FindClosestPointOnPath(const Vector *worldPos, int startIndex, int endIndex, Vector *close) const; // compute closest point on path to given point
bool IsStraightLinePathWalkable(const Vector *goal) const; // test for un-jumpable height change, or unrecoverable fall
mutable CountdownTimer m_avoidFriendTimer; // used to throttle how often we check for friends in our path
mutable bool m_isFriendInTheWay; // true if a friend is blocking our path
CountdownTimer m_politeTimer; // we'll wait for friend to move until this runs out
bool m_isWaitingBehindFriend; // true if we are waiting for a friend to move
#define ONLY_JUMP_DOWN true
bool DiscontinuityJump(float ground, bool onlyJumpDown = false, bool mustJump = false); // check if we need to jump due to height change
enum LadderNavState
{
APPROACH_ASCENDING_LADDER, // prepare to scale a ladder
APPROACH_DESCENDING_LADDER, // prepare to go down ladder
FACE_ASCENDING_LADDER,
FACE_DESCENDING_LADDER,
MOUNT_ASCENDING_LADDER, // move toward ladder until "on" it
MOUNT_DESCENDING_LADDER, // move toward ladder until "on" it
ASCEND_LADDER, // go up the ladder
DESCEND_LADDER, // go down the ladder
DISMOUNT_ASCENDING_LADDER, // get off of the ladder
DISMOUNT_DESCENDING_LADDER, // get off of the ladder
MOVE_TO_DESTINATION, // dismount ladder and move to destination area
}
m_pathLadderState;
bool m_pathLadderFaceIn; // if true, face towards ladder, otherwise face away
const CNavLadder *m_pathLadder; // the ladder we need to use to reach the next area
bool UpdateLadderMovement(); // called by UpdatePathMovement()
NavRelativeDirType m_pathLadderDismountDir; // which way to dismount
float m_pathLadderDismountTimestamp; // time when dismount started
float m_pathLadderEnd; // if ascending, z of top, if descending z of bottom
void ComputeLadderEndpoint(bool isAscending);
float m_pathLadderTimestamp; // time when we started using ladder - for timeout check
CountdownTimer m_mustRunTimer; // if nonzero, bot cannot walk
// game scenario mechanisms
CSGameState m_gameState;
// hostages mechanism
byte m_hostageEscortCount;
void UpdateHostageEscortCount();
float m_hostageEscortCountTimestamp;
bool m_isWaitingForHostage;
CountdownTimer m_inhibitWaitingForHostageTimer;
CountdownTimer m_waitForHostageTimer;
// listening mechanism
Vector m_noisePosition; // position we last heard non-friendly noise
float m_noiseTimestamp; // when we heard it (can get zeroed)
CNavArea *m_noiseArea; // the nav area containing the noise
float m_noiseCheckTimestamp;
PriorityType m_noisePriority; // priority of currently heard noise
bool UpdateLookAtNoise(); // return true if we decided to look towards the most recent noise source
bool m_isNoiseTravelRangeChecked;
// "looking around" mechanism
float m_lookAroundStateTimestamp; // time of next state change
float m_lookAheadAngle; // our desired forward look angle
float m_forwardAngle; // our current forward facing direction
float m_inhibitLookAroundTimestamp; // time when we can look around again
enum LookAtSpotState
{
NOT_LOOKING_AT_SPOT, // not currently looking at a point in space
LOOK_TOWARDS_SPOT, // in the process of aiming at m_lookAtSpot
LOOK_AT_SPOT, // looking at m_lookAtSpot
NUM_LOOK_AT_SPOT_STATES
}
m_lookAtSpotState;
Vector m_lookAtSpot; // the spot we're currently looking at
PriorityType m_lookAtSpotPriority;
float m_lookAtSpotDuration; // how long we need to look at the spot
float m_lookAtSpotTimestamp; // when we actually began looking at the spot
float m_lookAtSpotAngleTolerance; // how exactly we must look at the spot
bool m_lookAtSpotClearIfClose; // if true, the look at spot is cleared if it gets close to us
const char *m_lookAtDesc; // for debugging
void UpdateLookAt();
void UpdatePeripheralVision(); // update enounter spot timestamps, etc
float m_peripheralTimestamp;
enum { MAX_APPROACH_POINTS = 16 };
Vector m_approachPoint[MAX_APPROACH_POINTS];
unsigned char m_approachPointCount;
Vector m_approachPointViewPosition; // the position used when computing current approachPoint set
bool BendLineOfSight(const Vector *eye, const Vector *point, Vector *bend) const; // "bend" our line of sight until we can see the target point. Return bend point, false if cant bend.
bool FindApproachPointNearestPath(Vector *pos); // find the approach point that is nearest to our current path, ahead of us
bool m_isWaitingToTossGrenade; // lining up throw
CountdownTimer m_tossGrenadeTimer; // timeout timer for grenade tossing
SpotEncounter *m_spotEncounter; // the spots we will encounter as we move thru our current area
float m_spotCheckTimestamp; // when to check next encounter spot
// TODO: Add timestamp for each possible client to hiding spots
enum { MAX_CHECKED_SPOTS = 64 };
struct HidingSpotCheckInfo
{
HidingSpot *spot;
float timestamp;
}
m_checkedHidingSpot[MAX_CHECKED_SPOTS];
int m_checkedHidingSpotCount;
// view angle mechanism
float m_lookPitch; // our desired look pitch angle
float m_lookPitchVel;
float m_lookYaw; // our desired look yaw angle
float m_lookYawVel;
// aim angle mechanism
mutable Vector m_eyePos;
Vector m_aimOffset; // current error added to victim's position to get actual aim spot
Vector m_aimOffsetGoal; // desired aim offset
float m_aimOffsetTimestamp; // time of next offset adjustment
float m_aimSpreadTimestamp; // time used to determine max spread as it begins to tighten up
void SetAimOffset(float accuracy); // set the current aim offset
void UpdateAimOffset(); // wiggle aim error based on m_accuracy
Vector m_aimSpot; // the spot we are currently aiming to fire at
// attack state data
DispositionType m_disposition; // how we will react to enemies
CountdownTimer m_ignoreEnemiesTimer; // how long will we ignore enemies
mutable EntityHandle<CBasePlayer> m_enemy; // our current enemy
bool m_isEnemyVisible; // result of last visibility test on enemy
unsigned char m_visibleEnemyParts; // which parts of the visible enemy do we see
Vector m_lastEnemyPosition; // last place we saw the enemy
float m_lastSawEnemyTimestamp;
float m_firstSawEnemyTimestamp;
float m_currentEnemyAcquireTimestamp;
float m_enemyDeathTimestamp; // if m_enemy is dead, this is when he died
bool m_isLastEnemyDead; // true if we killed or saw our last enemy die
int m_nearbyEnemyCount; // max number of enemies we've seen recently
unsigned int m_enemyPlace; // the location where we saw most of our enemies
struct WatchInfo
{
float timestamp;
bool isEnemy;
}
m_watchInfo[MAX_CLIENTS];
mutable EntityHandle<CBasePlayer> m_bomber; // points to bomber if we can see him
int m_nearbyFriendCount; // number of nearby teammates
mutable EntityHandle<CBasePlayer> m_closestVisibleFriend; // the closest friend we can see
mutable EntityHandle<CBasePlayer> m_closestVisibleHumanFriend; // the closest human friend we can see
#ifdef REGAMEDLL_ADD
IntervalTimer m_attentionInterval; // time between attention checks
#endif
CBasePlayer *m_attacker; // last enemy that hurt us (may not be same as m_enemy)
float m_attackedTimestamp; // when we were hurt by the m_attacker
int m_lastVictimID; // the entindex of the last victim we killed, or zero
bool m_isAimingAtEnemy; // if true, we are trying to aim at our enemy
bool m_isRapidFiring; // if true, RunUpkeep() will toggle our primary attack as fast as it can
IntervalTimer m_equipTimer; // how long have we had our current weapon equipped
bool DoEquip(CBasePlayerWeapon *pWeapon); // equip the given item
void ReloadCheck(); // reload our weapon if we must
void SilencerCheck(); // use silencer
float m_fireWeaponTimestamp;
#ifdef REGAMEDLL_ADD
bool m_isEnemySniperVisible; ///< do we see an enemy sniper right now
CountdownTimer m_sawEnemySniperTimer; ///< tracking time since saw enemy sniper
#endif
// reaction time system
enum { MAX_ENEMY_QUEUE = 20 };
struct ReactionState
{
// NOTE: player position & orientation is not currently stored separately
EntityHandle<CBasePlayer> player;
bool isReloading;
bool isProtectedByShield;
}
m_enemyQueue[MAX_ENEMY_QUEUE]; // round-robin queue for simulating reaction times
byte m_enemyQueueIndex;
byte m_enemyQueueCount;
byte m_enemyQueueAttendIndex; // index of the timeframe we are "conscious" of
CBasePlayer *FindMostDangerousThreat(); // return most dangerous threat in my field of view (feeds into reaction time queue)
// stuck detection
bool m_isStuck;
float m_stuckTimestamp; // time when we got stuck
Vector m_stuckSpot; // the location where we became stuck
NavRelativeDirType m_wiggleDirection;
float m_wiggleTimestamp;
float m_stuckJumpTimestamp; // time for next jump when stuck
enum { MAX_VEL_SAMPLES = 5 };
float m_avgVel[MAX_VEL_SAMPLES];
int m_avgVelIndex;
int m_avgVelCount;
Vector m_lastOrigin;
// chatter mechanism
GameEventType m_lastRadioCommand; // last radio command we recieved
void RespondToRadioCommands();
bool IsRadioCommand(GameEventType event) const; // returns true if the radio message is an order to do something
#define NO_FORCE false
void EndVoiceFeedback(bool force = true);
float m_lastRadioRecievedTimestamp; // time we recieved a radio message
float m_lastRadioSentTimestamp; // time when we send a radio message
EntityHandle<CBasePlayer> m_radioSubject; // who issued the radio message
Vector m_radioPosition; // position referred to in radio message
float m_voiceFeedbackStartTimestamp;
float m_voiceFeedbackEndTimestamp; // new-style "voice" chatter gets voice feedback
BotChatterInterface m_chatter;
// learn map mechanism
const CNavNode *m_navNodeList;
CNavNode *m_currentNode;
NavDirType m_generationDir;
enum ProcessType
{
PROCESS_NORMAL,
PROCESS_LEARN,
PROCESS_ANALYZE_ALPHA,
PROCESS_ANALYZE_BETA,
PROCESS_SAVE,
}
m_processMode;
CountdownTimer m_mumbleTimer;
CountdownTimer m_booTimer;
CountdownTimer m_relocateTimer;
CNavNode *AddNode(const Vector *destPos, const Vector *normal, NavDirType dir, CNavNode *source);
void StartLearnProcess();
void UpdateLearnProcess();
bool LearnStep();
void StartAnalyzeAlphaProcess();
void UpdateAnalyzeAlphaProcess();
bool AnalyzeAlphaStep();
void StartAnalyzeBetaProcess();
void UpdateAnalyzeBetaProcess();
bool AnalyzeBetaStep();
void StartSaveProcess();
void UpdateSaveProcess();
void StartNormalProcess();
#ifdef REGAMEDLL_ADD
bool IsNoticable(const CBasePlayer *pPlayer, unsigned char visibleParts) const; // return true if we "notice" given player
#endif
};
// Inlines
inline float CCSBot::GetCombatRange() const
{
return m_combatRange;
}
inline void CCSBot::SetRogue(bool rogue)
{
m_isRogue = rogue;
}
inline void CCSBot::Hurry(float duration)
{
m_hurryTimer.Start(duration);
}
inline float CCSBot::GetSafeTime() const
{
return m_safeTime;
}
inline bool CCSBot::IsCarryingBomb() const
{
return m_bHasC4;
}
inline bool CCSBot::IsFollowing() const
{
return m_isFollowing;
}
inline CBasePlayer *CCSBot::GetFollowLeader()
{
return m_leader;
}
inline float CCSBot::GetFollowDuration() const
{
return gpGlobals->time - m_followTimestamp;
}
inline bool CCSBot::CanAutoFollow() const
{
return (gpGlobals->time > m_allowAutoFollowTime);
}
inline void CCSBot::AimAtEnemy()
{
m_isAimingAtEnemy = true;
}
inline void CCSBot::StopAiming()
{
m_isAimingAtEnemy = false;
}
inline bool CCSBot::IsAimingAtEnemy() const
{
return m_isAimingAtEnemy;
}
inline bool CCSBot::IsSurprised() const
{
if (gpGlobals->time - m_surpriseTimestamp < 5.0f)
return true;
return false;
}
inline float CCSBot::GetSurpriseDelay() const
{
if (!IsSurprised())
return 0.0f;
return m_surpriseDelay;
}
inline void CCSBot::ClearSurpriseDelay()
{
m_surpriseDelay = 0.0f;
m_surpriseTimestamp = 0.0f;
}
inline float CCSBot::GetStateTimestamp() const
{
return m_stateTimestamp;
}
inline CSGameState *CCSBot::GetGameState()
{
return &m_gameState;
}
inline const CSGameState *CCSBot::GetGameState() const
{
return &m_gameState;
}
inline bool CCSBot::IsAtBombsite()
{
return (m_signals.GetState() & SIGNAL_BOMB) == SIGNAL_BOMB;
}
inline CCSBot::MoraleType CCSBot::GetMorale() const
{
return m_morale;
}
inline bool CCSBot::IsNoiseHeard() const
{
if (m_noiseTimestamp <= 0.0f)
return false;
// primitive reaction time simulation - cannot "hear" noise until reaction time has elapsed
if (gpGlobals->time - m_noiseTimestamp >= GetProfile()->GetReactionTime())
return true;
return false;
}
inline void CCSBot::SetTask(TaskType task, CBaseEntity *pEntity)
{
m_task = task;
m_taskEntity = pEntity;
}
inline CCSBot::TaskType CCSBot::GetTask() const
{
return m_task;
}
inline CBaseEntity *CCSBot::GetTaskEntity()
{
return m_taskEntity;
}
inline CNavArea *CCSBot::GetNoiseArea() const
{
return m_noiseArea;
}
inline void CCSBot::ForgetNoise()
{
m_noiseTimestamp = 0.0f;
}
inline PriorityType CCSBot::GetNoisePriority() const
{
return m_noisePriority;
}
inline BotChatterInterface *CCSBot::GetChatter()
{
return &m_chatter;
}
inline bool CCSBot::IsUsingVoice() const
{
return (m_voiceFeedbackEndTimestamp != 0.0f);
}
inline CBasePlayer *CCSBot::GetEnemy()
{
return m_enemy;
}
inline int CCSBot::GetNearbyEnemyCount() const
{
return Q_min(GetEnemiesRemaining(), m_nearbyEnemyCount);
}
inline unsigned int CCSBot::GetEnemyPlace() const
{
return m_enemyPlace;
}
inline bool CCSBot::CanSeeBomber() const
{
return m_bomber.IsValid();
}
inline CBasePlayer *CCSBot::GetBomber() const
{
return m_bomber;
}
inline int CCSBot::GetNearbyFriendCount() const
{
return Q_min(GetFriendsRemaining(), m_nearbyFriendCount);
}
inline CBasePlayer *CCSBot::GetClosestVisibleFriend() const
{
return m_closestVisibleFriend;
}
inline CBasePlayer *CCSBot::GetClosestVisibleHumanFriend() const
{
return m_closestVisibleHumanFriend;
}
inline float CCSBot::GetTimeSinceAttacked() const
{
return gpGlobals->time - m_attackedTimestamp;
}
inline float CCSBot::GetFirstSawEnemyTimestamp() const
{
return m_firstSawEnemyTimestamp;
}
inline float CCSBot::GetLastSawEnemyTimestamp() const
{
return m_lastSawEnemyTimestamp;
}
inline float CCSBot::GetTimeSinceLastSawEnemy() const
{
return gpGlobals->time - m_lastSawEnemyTimestamp;
}
inline float CCSBot::GetTimeSinceAcquiredCurrentEnemy() const
{
return gpGlobals->time - m_currentEnemyAcquireTimestamp;
}
inline const Vector &CCSBot::GetLastKnownEnemyPosition() const
{
return m_lastEnemyPosition;
}
inline bool CCSBot::IsEnemyVisible() const
{
return m_isEnemyVisible;
}
inline float CCSBot::GetEnemyDeathTimestamp() const
{
return m_enemyDeathTimestamp;
}
inline int CCSBot::GetLastVictimID() const
{
return m_lastVictimID;
}
#ifdef REGAMEDLL_ADD
inline bool CCSBot::CanSeeSniper(void) const
{
return m_isEnemySniperVisible;
}
inline bool CCSBot::HasSeenSniperRecently(void) const
{
return !m_sawEnemySniperTimer.IsElapsed();
}
#endif
inline bool CCSBot::HasPath() const
{
return m_pathLength != 0;
}
inline void CCSBot::DestroyPath()
{
m_pathLength = 0;
m_pathLadder = nullptr;
}
inline CNavArea *CCSBot::GetLastKnownArea() const
{
return m_lastKnownArea;
}
inline const Vector &CCSBot::GetPathEndpoint() const
{
return m_path[m_pathLength - 1].pos;
}
inline const Vector &CCSBot::GetPathPosition(int numpath) const
{
return m_path[numpath].pos;
}
inline bool CCSBot::IsUsingLadder() const
{
return m_pathLadder != nullptr;
}
inline void CCSBot::SetGoalEntity(CBaseEntity *pEntity)
{
m_goalEntity = pEntity;
}
template <typename T>
inline T *CCSBot::GetGoalEntity()
{
return m_goalEntity.Get<T>();
}
inline void CCSBot::ForceRun(float duration)
{
Run();
m_mustRunTimer.Start(duration);
}
inline void CCSBot::SetLookAngles(float yaw, float pitch)
{
m_lookYaw = yaw;
m_lookPitch = pitch;
}
inline void CCSBot::SetForwardAngle(float angle)
{
m_forwardAngle = angle;
}
inline void CCSBot::SetLookAheadAngle(float angle)
{
m_lookAheadAngle = angle;
}
inline void CCSBot::ClearLookAt()
{
//PrintIfWatched("ClearLookAt()\n");
m_lookAtSpotState = NOT_LOOKING_AT_SPOT;
m_lookAtDesc = nullptr;
}
inline bool CCSBot::IsLookingAtSpot(PriorityType pri) const
{
if (m_lookAtSpotState != NOT_LOOKING_AT_SPOT && m_lookAtSpotPriority >= pri)
return true;
return false;
}
inline bool CCSBot::IsViewMoving(float angleVelThreshold) const
{
if (m_lookYawVel < angleVelThreshold && m_lookYawVel > -angleVelThreshold &&
m_lookPitchVel < angleVelThreshold && m_lookPitchVel > -angleVelThreshold)
{
return false;
}
return true;
}
inline bool CCSBot::IsSignificantlyCloser(const CBasePlayer *testPlayer, const CBasePlayer *referencePlayer) const
{
if (!referencePlayer || !testPlayer)
return true;
float testDist = (pev->origin - testPlayer->pev->origin).Length();
float referenceDist = (pev->origin - referencePlayer->pev->origin).Length();
const float significantRangeFraction = 0.7f;
if (testDist < referenceDist * significantRangeFraction)
return true;
return false;
}
inline void CCSBot::ClearApproachPoints()
{
m_approachPointCount = 0;
}
inline bool CCSBot::IsThrowingGrenade() const
{
return m_isWaitingToTossGrenade;
}
inline void CCSBot::StartRapidFire()
{
m_isRapidFiring = true;
}
inline void CCSBot::StopRapidFire()
{
m_isRapidFiring = false;
}
inline CCSBot::ZoomType CCSBot::GetZoomLevel() const
{
if (m_iFOV > 60.0f)
return NO_ZOOM;
if (m_iFOV > 25.0f)
return LOW_ZOOM;
return HIGH_ZOOM;
}
inline int CCSBot::GetHostageEscortCount() const
{
return m_hostageEscortCount;
}
inline void CCSBot::IncreaseHostageEscortCount()
{
m_hostageEscortCount++;
}
inline void CCSBot::ResetWaitForHostagePatience()
{
m_isWaitingForHostage = false;
m_inhibitWaitingForHostageTimer.Invalidate();
}
inline float CCSBot::GetFeetZ() const
{
if (IsCrouching())
{
const Vector crouch(0, 0, -StepHeight);
return (pev->origin + crouch).z;
}
else
{
const Vector stand(0, 0, -HalfHumanHeight);
return (pev->origin + stand).z;
}
}
inline const Vector *CCSBot::GetNoisePosition() const
{
if (m_noiseTimestamp > 0.0f)
return &m_noisePosition;
return nullptr;
}
inline bool CCSBot::IsAwareOfEnemyDeath() const
{
if (GetEnemyDeathTimestamp() == 0.0f)
return false;
if (!m_enemy.IsValid())
return true;
if (!m_enemy->IsAlive() && gpGlobals->time - GetEnemyDeathTimestamp() > (1.0f - GetProfile()->GetSkill()))
return true;
return false;
}
inline bool CCSBot::IsNotMoving() const
{
const float stillSpeed = 10.0f;
return pev->velocity.IsLengthLessThan(stillSpeed);
}
inline bool CCSBot::HasAnyAmmo(CBasePlayerWeapon *weapon) const
{
return (weapon->m_iClip != 0 || m_rgAmmo[weapon->m_iPrimaryAmmoType] > 0);
}
class CollectRetreatSpotsFunctor
{
public:
CollectRetreatSpotsFunctor(CCSBot *me, float range)
{
m_me = me;
m_count = 0;
m_range = range;
}
enum { MAX_SPOTS = 256 };
bool operator()(CNavArea *area)
{
// collect all the hiding spots in this area
for (auto const spot : *area->GetHidingSpotList())
{
if (m_count >= MAX_SPOTS)
break;
// make sure hiding spot is in range
if (m_range > 0.0f)
{
if ((*spot->GetPosition() - m_me->pev->origin).IsLengthGreaterThan(m_range))
continue;
}
// if a Player is using this hiding spot, don't consider it
if (IsSpotOccupied(m_me, spot->GetPosition()))
{
// player is in hiding spot
// TODO: Check if player is moving or sitting still
continue;
}
// don't select spot if an enemy can see it
if (UTIL_IsVisibleToTeam(*spot->GetPosition() + Vector(0, 0, HalfHumanHeight), OtherTeam(m_me->m_iTeam)))
continue;
// don't select spot if it is closest to an enemy
CBasePlayer *owner = UTIL_GetClosestPlayer(spot->GetPosition());
if (owner && m_me->m_iTeam != owner->m_iTeam)
continue;
m_spot[m_count++] = spot->GetPosition();
}
// if we've filled up, stop searching
if (m_count == MAX_SPOTS)
return false;
return true;
}
CCSBot *m_me;
float m_range;
const Vector *m_spot[MAX_SPOTS];
int m_count;
};
class FarthestHostage
{
public:
FarthestHostage(const CCSBot *me)
{
m_me = me;
m_farRange = -1.0f;
}
bool operator()(CHostage *hostage)
{
if (hostage->IsFollowing(m_me))
{
float range = (hostage->Center() - m_me->pev->origin).Length();
if (range > m_farRange)
{
m_farRange = range;
}
}
return true;
}
const CCSBot *m_me;
float m_farRange;
};
// Functor used with NavAreaBuildPath()
class PathCost
{
public:
PathCost(CCSBot *pBot, RouteType route = SAFEST_ROUTE)
{
m_bot = pBot;
m_route = route;
}
float operator()(CNavArea *area, CNavArea *fromArea, const CNavLadder *ladder)
{
const float baseDangerFactor = 100.0f;
// respond to the danger modulated by our aggression (even super-aggressives pay SOME attention to danger)
float dangerFactor = (1.0f - (0.95f * m_bot->GetProfile()->GetAggression())) * baseDangerFactor;
if (fromArea == nullptr)
{
if (m_route == FASTEST_ROUTE)
return 0.0f;
// first area in path, cost is just danger
return dangerFactor * area->GetDanger(m_bot->m_iTeam - 1);
}
else if ((fromArea->GetAttributes() & NAV_JUMP) && (area->GetAttributes() & NAV_JUMP))
{
// cannot actually walk in jump areas - disallow moving from jump area to jump area
return -1.0f;
}
else
{
// compute distance from previous area to this area
float dist;
if (ladder)
{
// ladders are slow to use
const float ladderPenalty = 1.0f;
dist = ladderPenalty * ladder->m_length;
// if we are currently escorting hostages, avoid ladders (hostages are confused by them)
//if (m_bot->GetHostageEscortCount())
// dist *= 100.0f;
}
else
{
dist = (*area->GetCenter() - *fromArea->GetCenter()).Length();
}
// compute distance travelled along path so far
float cost = dist + fromArea->GetCostSoFar();
// zombies ignore all path penalties
if (cv_bot_zombie.value > 0.0f)
return cost;
// add cost of "jump down" pain unless we're jumping into water
if (!area->IsConnected(fromArea, NUM_DIRECTIONS))
{
// this is a "jump down" (one way drop) transition - estimate damage we will take to traverse it
float fallDistance = -fromArea->ComputeHeightChange(area);
// if it's a drop-down ladder, estimate height from the bottom of the ladder to the lower area
//if (ladder && ladder->m_bottom.z < fromArea->GetCenter()->z && ladder->m_bottom.z > area->GetCenter()->z)
//{
// fallDistance = ladder->m_bottom.z - area->GetCenter()->z;
//}
float fallDamage = m_bot->GetApproximateFallDamage(fallDistance);
if (fallDamage > 0.0f)
{
// if the fall would kill us, don't use it
const float deathFallMargin = 10.0f;
if (fallDamage + deathFallMargin >= m_bot->pev->health)
return -1.0f;
// if we need to get there in a hurry, ignore minor pain
const float painTolerance = 15.0f * m_bot->GetProfile()->GetAggression() + 10.0f;
if (m_route != FASTEST_ROUTE || fallDamage > painTolerance)
{
// cost is proportional to how much it hurts when we fall
// 10 points - not a big deal, 50 points - ouch!
cost += 100.0f * fallDamage * fallDamage;
}
}
}
// if this is a "crouch" area, add penalty
if (area->GetAttributes() & NAV_CROUCH)
{
// these areas are very slow to move through
real_t crouchPenalty = (m_route == FASTEST_ROUTE) ? 20.0f : 5.0f;
// avoid crouch areas if we are rescuing hostages
if (m_bot->GetHostageEscortCount())
{
crouchPenalty *= 3.0f;
}
cost += crouchPenalty * dist;
}
// if this is a "jump" area, add penalty
if (area->GetAttributes() & NAV_JUMP)
{
// jumping can slow you down
//const float jumpPenalty = (m_route == FASTEST_ROUTE) ? 100.0f : 0.5f;
const float jumpPenalty = 1.0f;
cost += jumpPenalty * dist;
}
if (m_route == SAFEST_ROUTE)
{
// add in the danger of this path - danger is per unit length travelled
cost += dist * dangerFactor * area->GetDanger(m_bot->m_iTeam - 1);
}
if (!m_bot->IsAttacking())
{
// add in cost of teammates in the way
// approximate density of teammates based on area
float size = (area->GetSizeX() + area->GetSizeY()) / 2.0f;
// degenerate check
if (size >= 1.0f)
{
// cost is proportional to the density of teammates in this area
const float costPerFriendPerUnit = 50000.0f;
cost += costPerFriendPerUnit * float(area->GetPlayerCount(m_bot->m_iTeam, m_bot)) / size;
}
}
return cost;
}
return 0.0f;
}
private:
CCSBot *m_bot;
RouteType m_route;
};
class FollowTargetCollector
{
public:
FollowTargetCollector(CBasePlayer *pPlayer)
{
m_player = pPlayer;
m_forward.x = pPlayer->pev->velocity.x;
m_forward.y = pPlayer->pev->velocity.y;
float speed = m_forward.NormalizeInPlace();
const float walkSpeed = 100.0f;
if (speed < walkSpeed)
{
m_cutoff.x = pPlayer->pev->origin.x;
m_cutoff.y = pPlayer->pev->origin.y;
m_forward.x = 0.0f;
m_forward.y = 0.0f;
}
else
{
const float k = 1.5f;
real_t trimSpeed = (speed < 200.0f) ? speed : 200.0f;
m_cutoff.x = pPlayer->pev->origin.x + k * trimSpeed * m_forward.x;
m_cutoff.y = pPlayer->pev->origin.y + k * trimSpeed * m_forward.y;
}
m_targetAreaCount = 0;
}
enum { MAX_TARGET_AREAS = 128 };
bool operator()(CNavArea *area)
{
if (m_targetAreaCount >= MAX_TARGET_AREAS)
return false;
// only use two-way connections
if (!area->GetParent() || area->IsConnected(area->GetParent(), NUM_DIRECTIONS))
{
if (m_forward.IsZero())
{
m_targetArea[m_targetAreaCount++] = area;
}
else
{
// collect areas in the direction of the player's forward motion
Vector2D to(((*area->GetCenter()).x - m_cutoff.x), (*area->GetCenter()).y - m_cutoff.y);
to.NormalizeInPlace();
if (DotProduct(to, m_forward) > 0.7071f)
{
m_targetArea[m_targetAreaCount++] = area;
}
}
}
return (m_targetAreaCount < MAX_TARGET_AREAS);
}
CBasePlayer *m_player;
Vector2D m_forward;
Vector2D m_cutoff;
CNavArea *m_targetArea[MAX_TARGET_AREAS];
int m_targetAreaCount;
};
void InstallBotControl();
void Bot_ServerCommand();
void Bot_RegisterCVars();
int GetBotFollowCount(CBasePlayer *pLeader);
const Vector *FindNearbyRetreatSpot(CCSBot *me, float maxRange);
void drawProgressMeter(float progress, char *title);
void startProgressMeter(const char *title);
void hideProgressMeter();
bool isSniperRifle(CBasePlayerItem *item);
float StayOnLadderLine(CCSBot *me, const CNavLadder *ladder);