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.
1781 lines
56 KiB
1781 lines
56 KiB
/* |
|
* |
|
* 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. |
|
* |
|
*/ |
|
|
|
#ifndef CS_BOT_H |
|
#define CS_BOT_H |
|
#ifdef _WIN32 |
|
#pragma once |
|
#endif |
|
|
|
#include "bot/cs_gamestate.h" |
|
#include "bot/cs_bot_manager.h" |
|
#include "bot/cs_bot_chatter.h" |
|
|
|
#define CSBOT_VERSION_MAJOR 1 |
|
#define CSBOT_VERSION_MINOR 50 |
|
|
|
#define PRIMARY_WEAPON_BUY_COUNT 13 |
|
#define SECONDARY_WEAPON_BUY_COUNT 3 |
|
|
|
#define FLAG_PROGRESS_DRAW 0x0 // draw status bar progress |
|
#define FLAG_PROGRESS_START 0x1 // init status bar progress |
|
#define FLAG_PROGRESS_HIDE 0x2 // hide status bar progress |
|
|
|
#define HI_X 0x01 |
|
#define LO_X 0x02 |
|
#define HI_Y 0x04 |
|
#define LO_Y 0x08 |
|
#define HI_Z 0x10 |
|
#define LO_Z 0x20 |
|
|
|
extern int _navAreaCount; |
|
extern int _currentIndex; |
|
/* |
|
extern struct BuyInfo primaryWeaponBuyInfoCT[ PRIMARY_WEAPON_BUY_COUNT ]; |
|
extern struct BuyInfo secondaryWeaponBuyInfoCT[ SECONDARY_WEAPON_BUY_COUNT ]; |
|
|
|
extern struct BuyInfo primaryWeaponBuyInfoT[ PRIMARY_WEAPON_BUY_COUNT ]; |
|
extern struct BuyInfo secondaryWeaponBuyInfoT[ SECONDARY_WEAPON_BUY_COUNT ]; |
|
*/ |
|
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 = NULL; } |
|
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; |
|
}; |
|
#if 0 |
|
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; |
|
}; |
|
#endif |
|
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; |
|
}; |
|
#if 0 |
|
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"; } |
|
}; |
|
#endif |
|
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; |
|
}; |
|
#if 0 |
|
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"; } |
|
}; |
|
#endif |
|
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(CBaseEntity *leader) { m_leader = leader; } |
|
|
|
private: |
|
void ComputeLeaderMotionState(float leaderSpeed); |
|
|
|
EHANDLE 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 *entity) { m_entity = entity; } |
|
|
|
private: |
|
EHANDLE m_entity; |
|
}; |
|
|
|
// The Counter-strike Bot |
|
|
|
class CCSBot: public CBot |
|
{ |
|
public: |
|
CCSBot(); // constructor initializes all values to zero |
|
|
|
public: |
|
virtual int ObjectCaps( void ) { return CBaseMonster :: ObjectCaps() | FCAP_DONT_SAVE; } |
|
virtual int 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 *entity = NULL, CBaseEntity *other = NULL); // invoked when event occurs in the game (some events have NULL entity) |
|
|
|
#define CHECK_FOV true |
|
virtual bool IsVisible(Vector *pos, bool testFOV = false) const; // return true if we can see the point |
|
virtual bool IsVisible(CBasePlayer *player, bool testFOV = false, unsigned char *visParts = NULL) 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 |
|
NOXREF bool IsUnhealthy() const; // returns true if bot is low on health |
|
|
|
// behaviors |
|
void Idle(); |
|
void Hide(CNavArea *searchFromArea = NULL, float duration = -1.0f, float hideRange = 750.0f, bool holdPosition = false); // DEPRECATED: Use TryToHide() instead |
|
#define USE_NEAREST true |
|
bool TryToHide(CNavArea *searchFromArea = NULL, 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 |
|
#if 0 |
|
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(); |
|
// use the entity |
|
|
|
bool IsBuying() const; |
|
#endif |
|
void UseEntity(CBaseEntity *entity); |
|
void Panic(CBasePlayer *enemy); // look around in panic |
|
void Follow(CBasePlayer *player); // 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 *entity = NULL); // 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 = NULL); |
|
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 |
|
NOXREF 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 *enemy); // 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 |
|
|
|
// 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 |
|
|
|
NOXREF 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(); |
|
NOXREF 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 = NULL); // 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 *entity); |
|
CBaseEntity *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 |
|
|
|
// approach points |
|
void ComputeApproachPoints(); // determine the set of "approach points" representing where the enemy can enter this region |
|
NOXREF 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 |
|
#if 0 |
|
int GetHostageEscortCount() const; |
|
void IncreaseHostageEscortCount(); |
|
float GetRangeToFarthestEscortedHostage() const; |
|
#endif |
|
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 *other); |
|
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 |
|
EHANDLE 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 |
|
EHANDLE 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 |
|
NOXREF 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 |
|
EHANDLE 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 = NULL); // 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; |
|
#if 0 |
|
// hostages mechanism |
|
byte m_hostageEscortCount; |
|
void UpdateHostageEscortCount(); |
|
float m_hostageEscortCountTimestamp; |
|
bool m_isWaitingForHostage; |
|
CountdownTimer m_inhibitWaitingForHostageTimer; |
|
CountdownTimer m_waitForHostageTimer; |
|
#endif |
|
// 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. |
|
NOXREF 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 EHANDLE 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 EHANDLE m_bomber; // points to bomber if we can see him |
|
|
|
int m_nearbyFriendCount; // number of nearby teammates |
|
mutable EHANDLE m_closestVisibleFriend; // the closest friend we can see |
|
mutable EHANDLE m_closestVisibleHumanFriend; // the closest human friend we can see |
|
|
|
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 *gun); // equip the given item |
|
|
|
void ReloadCheck(); // reload our weapon if we must |
|
void SilencerCheck(); // use silencer |
|
|
|
float m_fireWeaponTimestamp; |
|
|
|
// reaction time system |
|
enum { MAX_ENEMY_QUEUE = 20 }; |
|
struct ReactionState |
|
{ |
|
// NOTE: player position & orientation is not currently stored separately |
|
EHANDLE 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 |
|
EHANDLE 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; |
|
int m_analyzeIter; |
|
|
|
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(); |
|
public: |
|
int m_iTeam; |
|
}; |
|
|
|
// 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; |
|
} |
|
#if 0 |
|
inline bool CCSBot::IsCarryingBomb() const |
|
{ |
|
return false; |
|
} |
|
#endif |
|
|
|
inline bool CCSBot::IsFollowing() const |
|
{ |
|
return m_isFollowing; |
|
} |
|
|
|
inline CBasePlayer *CCSBot::GetFollowLeader() |
|
{ |
|
return (CBasePlayer*)(CBaseEntity*)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 |
|
{ |
|
return gpGlobals->time - m_surpriseTimestamp < 5.0f; |
|
} |
|
|
|
inline float CCSBot::GetSurpriseDelay() const |
|
{ |
|
return ( gpGlobals->time - IsSurprised() )? m_surpriseDelay : 0.0f; |
|
} |
|
|
|
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 false; |
|
} |
|
|
|
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 *entity) |
|
{ |
|
m_task = task; |
|
m_taskEntity = entity; |
|
} |
|
|
|
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 (CBasePlayer*)(CBaseEntity*)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 == NULL) ? false : true; |
|
} |
|
|
|
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; |
|
} |
|
|
|
inline bool CCSBot::HasPath() const |
|
{ |
|
return m_pathLength != 0; |
|
} |
|
|
|
inline void CCSBot::DestroyPath() |
|
{ |
|
m_pathLength = 0; |
|
m_pathLadder = NULL; |
|
} |
|
|
|
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 != NULL; |
|
} |
|
|
|
inline void CCSBot::SetGoalEntity(CBaseEntity *entity) |
|
{ |
|
m_goalEntity = entity; |
|
} |
|
|
|
inline CBaseEntity *CCSBot::GetGoalEntity() |
|
{ |
|
return m_goalEntity; |
|
} |
|
|
|
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 = NULL; |
|
} |
|
|
|
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 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; |
|
} |
|
#if 0 |
|
inline int CCSBot::GetHostageEscortCount() const |
|
{ |
|
return m_hostageEscortCount; |
|
} |
|
|
|
inline void CCSBot::IncreaseHostageEscortCount() |
|
{ |
|
++m_hostageEscortCount; |
|
} |
|
|
|
inline void CCSBot::ResetWaitForHostagePatience() |
|
{ |
|
m_isWaitingForHostage = false; |
|
m_inhibitWaitingForHostageTimer.Invalidate(); |
|
} |
|
#endif |
|
inline float CCSBot::GetFeetZ() const |
|
{ |
|
if (IsCrouching()) |
|
{ |
|
const Vector crouch = Vector(0, 0, -StepHeight); |
|
return (pev->origin + crouch).z; |
|
} |
|
else |
|
{ |
|
const Vector stand = Vector(0, 0, -HalfHumanHeight); |
|
return (pev->origin + stand).z; |
|
} |
|
} |
|
|
|
inline const Vector *CCSBot::GetNoisePosition() const |
|
{ |
|
if (m_noiseTimestamp > 0.0f) |
|
return &m_noisePosition; |
|
|
|
return NULL; |
|
} |
|
|
|
inline bool CCSBot::IsAwareOfEnemyDeath() const |
|
{ |
|
if (GetEnemyDeathTimestamp() == 0.0f) |
|
return false; |
|
|
|
if (m_enemy == NULL) |
|
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 |
|
const HidingSpotList *list = area->GetHidingSpotList (); |
|
|
|
FOR_EACH_LL ((*list), it) |
|
{ |
|
const HidingSpot *spot = (*list)[it]; |
|
|
|
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 != NULL && 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; |
|
|
|
/*if (hostage->pev->takedamage != DAMAGE_YES) |
|
return true; |
|
|
|
if (hostage->m_improv != NULL) |
|
{ |
|
if (!hostage->IsFollowingSomeone() || m_me != hostage->GetLeader()) |
|
return true; |
|
} |
|
else if (!hostage->IsFollowing(m_me)) |
|
return true; |
|
|
|
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 *bot, RouteType route = SAFEST_ROUTE) |
|
{ |
|
m_bot = bot; |
|
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 == NULL) |
|
{ |
|
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 |
|
float crouchPenalty = (m_route == FASTEST_ROUTE) ? 20.0f : 5.0f; |
|
#if 0 /// TODO: useful in coop modes |
|
// avoid crouch areas if we are rescuing hostages |
|
if (m_bot->GetHostageEscortCount()) |
|
{ |
|
crouchPenalty *= 3.0f; |
|
} |
|
#endif |
|
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 *player) |
|
{ |
|
m_player = player; |
|
m_forward.x = player->pev->velocity.x; |
|
m_forward.y = player->pev->velocity.y; |
|
|
|
float speed = m_forward.NormalizeInPlace(); |
|
|
|
const float walkSpeed = 100.0f; |
|
if (speed < walkSpeed) |
|
{ |
|
m_cutoff.x = player->pev->origin.x; |
|
m_cutoff.y = player->pev->origin.y; |
|
|
|
m_forward.x = 0.0f; |
|
m_forward.y = 0.0f; |
|
} |
|
else |
|
{ |
|
const float k = 1.5f; |
|
float trimSpeed = (speed < 200.0f) ? speed : 200.0f; |
|
|
|
m_cutoff.x = player->pev->origin.x + k * trimSpeed * m_forward.x; |
|
m_cutoff.y = player->pev->origin.y + k * trimSpeed * m_forward.y; |
|
} |
|
|
|
m_targetAreaCount = 0; |
|
} |
|
|
|
enum { MAX_TARGET_AREAS = 128 }; |
|
|
|
/* <568dc3> ../cstrike/dlls/bot/states/cs_bot_follow.cpp:124 */ |
|
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) |
|
if ((to.x * m_forward.x + to.y * m_forward.y) > 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 *leader); |
|
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); |
|
|
|
#endif // CS_BOT_H
|
|
|