//========= Copyright Valve Corporation, All rights reserved. ============// // tf_bot.h // Team Fortress NextBot // Michael Booth, February 2009 #ifndef TF_BOT_H #define TF_BOT_H #include "Player/NextBotPlayer.h" #include "../nav_mesh/tf_nav_mesh.h" #include "tf_bot_vision.h" #include "tf_bot_body.h" #include "tf_bot_locomotion.h" #include "tf_player.h" #include "tf_bot_squad.h" #include "bot/map_entities/tf_bot_proxy.h" #include "tf_gamerules.h" #include "entity_capture_flag.h" #include "func_capture_zone.h" #include "nav_entities.h" #include "utlstack.h" #define TF_BOT_TYPE 1337 class CTriggerAreaCapture; class CTFBotActionPoint; class CObjectSentrygun; class CTFBotGenerator; extern void BotGenerateAndWearItem( CTFPlayer *pBot, const char *itemName ); //---------------------------------------------------------------------------- // These must remain in sync with the bot_generator's spawnflags in tf.fgd: #define TFBOT_IGNORE_ENEMY_SCOUTS 0x0001 #define TFBOT_IGNORE_ENEMY_SOLDIERS 0x0002 #define TFBOT_IGNORE_ENEMY_PYROS 0x0004 #define TFBOT_IGNORE_ENEMY_DEMOMEN 0x0008 #define TFBOT_IGNORE_ENEMY_HEAVIES 0x0010 #define TFBOT_IGNORE_ENEMY_MEDICS 0x0020 #define TFBOT_IGNORE_ENEMY_ENGINEERS 0x0040 #define TFBOT_IGNORE_ENEMY_SNIPERS 0x0080 #define TFBOT_IGNORE_ENEMY_SPIES 0x0100 #define TFBOT_IGNORE_ENEMY_SENTRY_GUNS 0x0200 #define TFBOT_IGNORE_SCENARIO_GOALS 0x0400 #define TFBOT_ALL_BEHAVIOR_FLAGS 0xFFFF #define TFBOT_MVM_MAX_PATH_LENGTH 0.0f // 7000.0f // in MvM, all pathfinds are limited to this (0 == no limit) //---------------------------------------------------------------------------- class CTFBot: public NextBotPlayer< CTFPlayer >, public CGameEventListener { public: DECLARE_CLASS( CTFBot, NextBotPlayer< CTFPlayer > ); CTFBot(); virtual ~CTFBot(); virtual void Spawn(); virtual void FireGameEvent( IGameEvent *event ); virtual void Event_Killed( const CTakeDamageInfo &info ); virtual void PhysicsSimulate( void ); virtual void Touch( CBaseEntity *pOther ); virtual void AvoidPlayers( CUserCmd *pCmd ); // some game types allow players to pass through each other, this method pushes them apart virtual void UpdateOnRemove( void ); virtual int ShouldTransmit( const CCheckTransmitInfo *pInfo ) OVERRIDE; virtual void ChangeTeam( int iTeamNum, bool bAutoTeam, bool bSilent, bool bAutoBalance = false ) OVERRIDE; virtual bool ShouldGib( const CTakeDamageInfo &info ) OVERRIDE; virtual int DrawDebugTextOverlays(void); virtual bool IsAllowedToPickUpFlag( void ) const; virtual void InitClass( void ); // set health/etc void ModifyMaxHealth( int nNewMaxHealth, bool bSetCurrentHealth = true, bool bAllowModelScaling = true ); virtual int GetBotType( void ) const; // return a unique int representing the type of bot instance this is virtual CTFNavArea *GetLastKnownArea( void ) const { return static_cast< CTFNavArea * >( BaseClass::GetLastKnownArea() ); } // return the last nav area the player occupied - NULL if unknown // NextBotPlayer static CBasePlayer *AllocatePlayerEntity( edict_t *pEdict, const char *playerName ); virtual void PressFireButton( float duration = -1.0f ) OVERRIDE; virtual void PressAltFireButton( float duration = -1.0f ) OVERRIDE; virtual void PressSpecialFireButton( float duration = -1.0f ) OVERRIDE; // INextBot virtual CTFBotLocomotion *GetLocomotionInterface( void ) const { return m_locomotor; } virtual CTFBotBody *GetBodyInterface( void ) const { return m_body; } virtual CTFBotVision *GetVisionInterface( void ) const { return m_vision; } DECLARE_INTENTION_INTERFACE( CTFBot ); virtual bool IsDormantWhenDead( void ) const; // should this player-bot continue to update itself when dead (respawn logic, etc) virtual void OnWeaponFired( CBaseCombatCharacter *whoFired, CBaseCombatWeapon *weapon ); // when someone fires their weapon virtual bool IsDebugFilterMatch( const char *name ) const; // return true if we match the given debug symbol virtual int GetAllowedTauntPartnerTeam() const OVERRIDE { return GetTeamNumber(); } // CTFBot specific CTeamControlPoint *GetMyControlPoint( void ) const; // return point we want to capture, or need to defend void ClearMyControlPoint( void ); bool WasPointJustLost( void ) const; // return true if we just lost territory recently bool AreAllPointsUncontestedSoFar( void ) const; // return true if no enemy has contested any point yet bool IsPointBeingCaptured( CTeamControlPoint *point ) const; // return true if the given point is being captured bool IsAnyPointBeingCaptured( void ) const; // return true if any point is being captured bool IsNearPoint( CTeamControlPoint *point ) const; // return true if we are within a short travel distance of the current point float GetTimeLeftToCapture( void ) const; // return time left to capture the point before we lose the game CCaptureFlag *GetFlagToFetch( void ) const; // return flag we want to fetch CCaptureZone *GetFlagCaptureZone( void ) const; // return capture zone for our flag(s) struct SniperSpotInfo { CTFNavArea *m_vantageArea; Vector m_vantageSpot; CTFNavArea *m_theaterArea; Vector m_theaterSpot; float m_range; float m_advantage; // the difference in how long it takes us to reach our vantage spot vs them to reach the theater spot }; void AccumulateSniperSpots( void ); // find good sniping spots and store them const CUtlVector< SniperSpotInfo > *GetSniperSpots( void ) const; // return vector of good sniping positions bool HasSniperSpots( void ) const; void ClearSniperSpots( void ); // search outwards from startSearchArea and collect all reachable objects from the given list that pass the given filter void SelectReachableObjects( const CUtlVector< CHandle< CBaseEntity > > &candidateObjectVector, CUtlVector< CHandle< CBaseEntity > > *selectedObjectVector, const INextBotFilter &filter, CNavArea *startSearchArea, float maxRange = 2000.0f ) const; CBaseEntity *FindClosestReachableObject( const char *objectName, CNavArea *from, float maxRange = 2000.0f ) const; CTFNavArea *GetSpawnArea( void ) const; // get area where we spawned in bool IsAmmoLow( void ) const; bool IsAmmoFull( void ) const; void UpdateLookingAroundForEnemies( void ); // update our view to keep an eye on enemies, and where enemies come from #define LOOK_FOR_FRIENDS false #define LOOK_FOR_ENEMIES true void UpdateLookingAroundForIncomingPlayers( bool lookForEnemies ); // update our view to watch where friends or enemies will be coming from void StartLookingAroundForEnemies( void ); // enable updating view for enemy searching void StopLookingAroundForEnemies( void ); // disable updating view for enemy searching void SetAttentionFocus( CBaseEntity *focusOn ); // restrict bot's attention to only this entity (or radius around this entity) to the exclusion of everything else void ClearAttentionFocus( void ); // remove attention focus restrictions bool IsAttentionFocused( void ) const; bool IsAttentionFocusedOn( CBaseEntity *who ) const; void DelayedThreatNotice( CHandle< CBaseEntity > who, float noticeDelay ); // notice the given threat after the given number of seconds have elapsed void UpdateDelayedThreatNotices( void ); CTFNavArea *FindVantagePoint( float maxTravelDistance = 2000.0f ) const; // return a nearby area where we can see a member of the enemy team const char *GetNextSpawnClassname( void ) const; float GetThreatDanger( CBaseCombatCharacter *who ) const; // return perceived danger of threat (0=none, 1=immediate deadly danger) float GetMaxAttackRange( void ) const; // return the max range at which we can effectively attack float GetDesiredAttackRange( void ) const; // return the ideal range at which we can effectively attack bool EquipRequiredWeapon( void ); // if we're required to equip a specific weapon, do it. void EquipBestWeaponForThreat( const CKnownEntity *threat ); // equip the best weapon we have to attack the given threat bool EquipLongRangeWeapon( void ); // equip a weapon that can damage far-away targets void PushRequiredWeapon( CTFWeaponBase *weapon ); // force us to equip and use this weapon until popped off the required stack void PopRequiredWeapon( void ); // pop top required weapon off of stack and discard #define MY_CURRENT_GUN NULL // can be passed as weapon to following queries bool IsCombatWeapon( CTFWeaponBase *weapon ) const; // return true if given weapon can be used to attack bool IsHitScanWeapon( CTFWeaponBase *weapon ) const; // return true if given weapon is a "hitscan" weapon (scattered tracelines with instant damage) bool IsContinuousFireWeapon( CTFWeaponBase *weapon ) const; // return true if given weapon "sprays" bullets/fire/etc continuously (ie: not individual rockets/etc) bool IsExplosiveProjectileWeapon( CTFWeaponBase *weapon ) const;// return true if given weapon launches explosive projectiles with splash damage bool IsBarrageAndReloadWeapon( CTFWeaponBase *weapon ) const; // return true if given weapon has small clip and long reload cost (ie: rocket launcher, etc) bool IsQuietWeapon( CTFWeaponBase *weapon ) const; // return true if given weapon doesn't make much sound when used (ie: spy knife, etc) bool IsEnvironmentNoisy( void ) const; // return true if there are/have been loud noises (ie: non-quiet weapons) nearby very recently enum WeaponRestrictionType { ANY_WEAPON = 0, MELEE_ONLY = 0x0001, PRIMARY_ONLY = 0x0002, SECONDARY_ONLY = 0x0004, }; void ClearWeaponRestrictions( void ); void SetWeaponRestriction( int restrictionFlags ); bool HasWeaponRestriction( int restrictionFlags ) const; bool IsWeaponRestricted( CTFWeaponBase *weapon ) const; bool ShouldFireCompressionBlast( void ); bool IsLineOfFireClear( const Vector &where ) const; // return true if a weapon has no obstructions along the line from our eye to the given position bool IsLineOfFireClear( CBaseEntity *who ) const; // return true if a weapon has no obstructions along the line from our eye to the given entity bool IsLineOfFireClear( const Vector &from, const Vector &to ) const; // return true if a weapon has no obstructions along the line between the given points bool IsLineOfFireClear( const Vector &from, CBaseEntity *who ) const; // return true if a weapon has no obstructions along the line between the given point and entity bool IsEntityBetweenTargetAndSelf( CBaseEntity *other, CBaseEntity *target ); // return true if "other" is positioned inbetween us and "target" class SuspectedSpyInfo_t { public: bool IsCurrentlySuspected(); void Suspect(); // The verb form of the word, not the noun. bool TestForRealizing(); CHandle< CTFPlayer > m_suspectedSpy; private: CUtlVector< int > m_touchTimes; }; bool IsKnownSpy( CTFPlayer *player ) const; // return true if we are sure this player actually is an enemy spy SuspectedSpyInfo_t* IsSuspectedSpy( CTFPlayer *player ); // return true if we suspect this player might be an enemy spy void SuspectSpy( CTFPlayer *player ); // note that this player might be a spy void RealizeSpy( CTFPlayer *player ); // note that this player *IS* a spy void ForgetSpy( CTFPlayer *player ); // remove player from spy suspect system void StopSuspectingSpy( CTFPlayer *pPlayer ); CTFPlayer *GetClosestHumanLookingAtMe( int team = TEAM_ANY ) const; // return the nearest human player on the given team who is looking directly at me enum AttributeType { REMOVE_ON_DEATH = 1<<0, // kick bot from server when killed AGGRESSIVE = 1<<1, // in MvM mode, push for the cap point IS_NPC = 1<<2, // a non-player support character SUPPRESS_FIRE = 1<<3, DISABLE_DODGE = 1<<4, BECOME_SPECTATOR_ON_DEATH = 1<<5, // move bot to spectator team when killed QUOTA_MANANGED = 1<<6, // managed by the bot quota in CTFBotManager RETAIN_BUILDINGS = 1<<7, // don't destroy this bot's buildings when it disconnects SPAWN_WITH_FULL_CHARGE = 1<<8, // all weapons start with full charge (ie: uber) ALWAYS_CRIT = 1<<9, // always fire critical hits IGNORE_ENEMIES = 1<<10, HOLD_FIRE_UNTIL_FULL_RELOAD = 1<<11, // don't fire our barrage weapon until it is full reloaded (rocket launcher, etc) PRIORITIZE_DEFENSE = 1<<12, // bot prioritizes defending when possible ALWAYS_FIRE_WEAPON = 1<<13, // constantly fire our weapon TELEPORT_TO_HINT = 1<<14, // bot will teleport to hint target instead of walking out from the spawn point MINIBOSS = 1<<15, // is miniboss? USE_BOSS_HEALTH_BAR = 1<<16, // should I use boss health bar? IGNORE_FLAG = 1<<17, // don't pick up flag/bomb AUTO_JUMP = 1<<18, // auto jump AIR_CHARGE_ONLY = 1<<19, // demo knight only charge in the air PREFER_VACCINATOR_BULLETS = 1<<20, // When using the vaccinator, prefer to use the bullets shield PREFER_VACCINATOR_BLAST = 1<<21, // When using the vaccinator, prefer to use the blast shield PREFER_VACCINATOR_FIRE = 1<<22, // When using the vaccinator, prefer to use the fire shield BULLET_IMMUNE = 1<<23, // Has a shield that makes the bot immune to bullets BLAST_IMMUNE = 1<<24, // "" blast FIRE_IMMUNE = 1<<25, // "" fire PARACHUTE = 1<<26, // demo/soldier parachute when falling PROJECTILE_SHIELD = 1<<27, // medic projectile shield }; void SetAttribute( int attributeFlag ); void ClearAttribute( int attributeFlag ); void ClearAllAttributes(); bool HasAttribute( int attributeFlag ) const; enum DifficultyType { UNDEFINED = -1, EASY = 0, NORMAL = 1, HARD = 2, EXPERT = 3, NUM_DIFFICULTY_LEVELS }; DifficultyType GetDifficulty( void ) const; void SetDifficulty( DifficultyType difficulty ); bool IsDifficulty( DifficultyType skill ) const; void SetHomeArea( CTFNavArea *area ); CTFNavArea *GetHomeArea( void ) const; CObjectSentrygun *GetEnemySentry( void ) const; // if we've been attacked/killed by an enemy sentry, this will return it, otherwise NULL void RememberEnemySentry( CObjectSentrygun *sentry, const Vector &injurySpot ); const Vector &GetSpotWhereEnemySentryLastInjuredMe( void ) const; void SetActionPoint( CTFBotActionPoint *point ); CTFBotActionPoint *GetActionPoint( void ) const; bool HasProxy( void ) const; void SetProxy( CTFBotProxy *proxy ); // attach this bot to a bot_proxy entity for map I/O communications CTFBotProxy *GetProxy( void ) const; bool HasSpawner( void ) const; void SetSpawner( CTFBotGenerator *spawner ); CTFBotGenerator *GetSpawner( void ) const; void JoinSquad( CTFBotSquad *squad ); // become a member of the given squad void LeaveSquad( void ); // leave our current squad void DeleteSquad( void ); bool IsInASquad( void ) const; bool IsSquadmate( CTFPlayer *who ) const; // return true if given bot is in my squad CTFBotSquad *GetSquad( void ) const; // return squad we are in, or NULL float GetSquadFormationError( void ) const; // return normalized error term where 0 = in formation position and 1 = completely out of position void SetSquadFormationError( float error ); bool HasBrokenFormation( void ) const; // return true if this bot is far out of formation, or has no path back void SetBrokenFormation( bool state ); float TransientlyConsistentRandomValue( float period = 10.0f, int seedValue = 0 ) const; // compute a pseudo random value (0-1) that stays consistent for the given period of time, but changes unpredictably each period void SetBehaviorFlag( unsigned int flags ); void ClearBehaviorFlag( unsigned int flags ); bool IsBehaviorFlagSet( unsigned int flags ) const; bool FindSplashTarget( CBaseEntity *target, float maxSplashRadius, Vector *splashTarget ) const; void GiveRandomItem( loadout_positions_t loadoutPosition ); enum MissionType { NO_MISSION = 0, MISSION_SEEK_AND_DESTROY, // focus on finding and killing enemy players MISSION_DESTROY_SENTRIES, // focus on finding and destroying enemy sentry guns (and buildings) MISSION_SNIPER, // maintain teams of snipers harassing the enemy MISSION_SPY, // maintain teams of spies harassing the enemy MISSION_ENGINEER, // maintain engineer nests for harassing the enemy MISSION_REPROGRAMMED, // MvM: robot has been hacked and will do bad things to their team }; #define MISSION_DOESNT_RESET_BEHAVIOR_SYSTEM false void SetMission( MissionType mission, bool resetBehaviorSystem = true ); void SetPrevMission( MissionType mission ); MissionType GetMission( void ) const; MissionType GetPrevMission( void ) const; bool HasMission( MissionType mission ) const; bool IsOnAnyMission( void ) const; void SetMissionTarget( CBaseEntity *target ); CBaseEntity *GetMissionTarget( void ) const; void SetMissionString( CUtlString string ); CUtlString *GetMissionString( void ); void SetTeleportWhere( const CUtlStringList& teleportWhereName ); const CUtlStringList& GetTeleportWhere(); void ClearTeleportWhere(); void SetScaleOverride( float fScale ); void SetMaxVisionRangeOverride( float range ); float GetMaxVisionRangeOverride( void ) const; void DisguiseAsMemberOfEnemyTeam( void ); // set Spy disguise to be a class that someone on the enemy team is actually using CBaseObject *GetNearestKnownSappableTarget( void ); void ClearTags( void ); void AddTag( const char *tag ); void RemoveTag( const char *tag ); bool HasTag( const char *tag ); Action< CTFBot > *OpportunisticallyUseWeaponAbilities( void ); CTFPlayer *SelectRandomReachableEnemy( void ); // mostly for MvM - pick a random enemy player that is not in their spawn room float GetDesiredPathLookAheadRange( void ) const; // different sized bots used different lookahead distances void StartIdleSound( void ); void StopIdleSound( void ); bool ShouldQuickBuild() const { return m_bForceQuickBuild; } void SetShouldQuickBuild( bool bShouldQuickBuild ) { m_bForceQuickBuild = bShouldQuickBuild; } void SetAutoJump( float flAutoJumpMin, float flAutoJumpMax ) { m_flAutoJumpMin = flAutoJumpMin; m_flAutoJumpMax = flAutoJumpMax; } bool ShouldAutoJump(); void SetFlagTarget( CCaptureFlag* pFlag ); CCaptureFlag* GetFlagTarget() const { return m_hFollowingFlagTarget; } bool HasFlagTaget() const { return m_hFollowingFlagTarget != NULL; } struct EventChangeAttributes_t { EventChangeAttributes_t() { Reset(); } EventChangeAttributes_t( const EventChangeAttributes_t& copy ) { Reset(); m_eventName = copy.m_eventName; m_skill = copy.m_skill; m_weaponRestriction = copy.m_weaponRestriction; m_mission = copy.m_mission; m_prevMission = copy.m_prevMission; m_attributeFlags = copy.m_attributeFlags; m_maxVisionRange = copy.m_maxVisionRange; for ( int i=0; i m_attributes; }; CUtlVector< item_attributes_t > m_itemsAttributes; CUtlVector< static_attrib_t > m_characterAttributes; CUtlStringList m_tags; }; void ClearEventChangeAttributes() { m_eventChangeAttributes.RemoveAll(); } void AddEventChangeAttributes( const EventChangeAttributes_t* newEvent ); const EventChangeAttributes_t* GetEventChangeAttributes( const char* pszEventName ) const; void OnEventChangeAttributes( const CTFBot::EventChangeAttributes_t* pEvent ); void AddItem( const char* pszItemName ); int GetUberHealthThreshold(); float GetUberDeployDelayDuration(); private: CTFBotLocomotion *m_locomotor; CTFBotBody *m_body; CTFBotVision *m_vision; CountdownTimer m_lookAtEnemyInvasionAreasTimer; CTFNavArea *m_spawnArea; // where we spawned CountdownTimer m_justLostPointTimer; int m_weaponRestrictionFlags; int m_attributeFlags; DifficultyType m_difficulty; CTFNavArea *m_homeArea; CHandle< CTFBotActionPoint > m_actionPoint; CHandle< CTFBotProxy > m_proxy; CHandle< CTFBotGenerator > m_spawner; CTFBotSquad *m_squad; bool m_didReselectClass; CHandle< CObjectSentrygun > m_enemySentry; Vector m_spotWhereEnemySentryLastInjuredMe; // the last position where I was injured by an enemy sentry CUtlVector< SuspectedSpyInfo_t* > m_suspectedSpyVector; CUtlVector< CHandle< CTFPlayer > > m_knownSpyVector; CUtlVector< SniperSpotInfo > m_sniperSpotVector; // collection of good sniping spots for the current objective CUtlVector< CTFNavArea * > m_sniperVantageAreaVector; CUtlVector< CTFNavArea * > m_sniperTheaterAreaVector; CBaseEntity *m_snipingGoalEntity; // the entity we are guarding (control point, payload cart) Vector m_lastSnipingGoalEntityPosition; void SetupSniperSpotAccumulation( void ); // do internal setup when control point changes CountdownTimer m_retrySniperSpotSetupTimer; bool m_isLookingAroundForEnemies; unsigned int m_behaviorFlags; // spawnflags from the bot_generator that spawned us CUtlVector< CFmtStr > m_tags; CHandle< CBaseEntity > m_attentionFocusEntity; CTeamControlPoint *SelectPointToCapture( CUtlVector< CTeamControlPoint * > *captureVector ) const; CTeamControlPoint *SelectPointToDefend( CUtlVector< CTeamControlPoint * > *defendVector ) const; mutable CHandle< CTeamControlPoint > m_myControlPoint; mutable CountdownTimer m_evaluateControlPointTimer; float m_fModelScaleOverride; MissionType m_mission; MissionType m_prevMission; CHandle< CBaseEntity > m_missionTarget; CUtlString m_missionString; CUtlStack< CHandle > m_requiredWeaponStack; // if non-empty, bot must equip the weapon on top of the stack CountdownTimer m_noisyTimer; struct DelayedNoticeInfo { CHandle< CBaseEntity > m_who; float m_when; }; CUtlVector< DelayedNoticeInfo > m_delayedNoticeVector; float m_maxVisionRangeOverride; CountdownTimer m_opportunisticTimer; CSoundPatch *m_pIdleSound; float m_squadFormationError; bool m_hasBrokenFormation; CUtlStringList m_teleportWhereName; // spawn name an engineer mission teleporter will override bool m_bForceQuickBuild; float m_flAutoJumpMin; float m_flAutoJumpMax; CountdownTimer m_autoJumpTimer; CHandle< CCaptureFlag > m_hFollowingFlagTarget; CUtlVector< const EventChangeAttributes_t* > m_eventChangeAttributes; }; inline void CTFBot::SetTeleportWhere( const CUtlStringList& teleportWhereName ) { // deep copy strings for ( int i=0; i *CTFBot::GetSniperSpots( void ) const { return &m_sniperSpotVector; } inline bool CTFBot::HasSniperSpots( void ) const { return m_sniperSpotVector.Count() > 0 ? true : false; } inline CTFBot::MissionType CTFBot::GetMission( void ) const { return m_mission; } inline void CTFBot::SetPrevMission( MissionType mission ) { m_prevMission = mission; } inline CTFBot::MissionType CTFBot::GetPrevMission( void ) const { return m_prevMission; } inline bool CTFBot::HasMission( MissionType mission ) const { return m_mission == mission ? true : false; } inline bool CTFBot::IsOnAnyMission( void ) const { return m_mission == NO_MISSION ? false : true; } inline void CTFBot::SetScaleOverride( float fScale ) { m_fModelScaleOverride = fScale; SetModelScale( m_fModelScaleOverride > 0.0f ? m_fModelScaleOverride : 1.0f ); } inline bool CTFBot::IsEnvironmentNoisy( void ) const { return !m_noisyTimer.IsElapsed(); } //--------------------------------------------------------------------------------------------- inline CTFBot *ToTFBot( CBaseEntity *pEntity ) { if ( !pEntity || !pEntity->IsPlayer() || !ToTFPlayer( pEntity )->IsBotOfType( TF_BOT_TYPE ) ) return NULL; Assert( "***IMPORTANT!!! DONT IGNORE ME!!!***" && dynamic_cast< CTFBot * >( pEntity ) != 0 ); return static_cast< CTFBot * >( pEntity ); } //--------------------------------------------------------------------------------------------- inline const CTFBot *ToTFBot( const CBaseEntity *pEntity ) { if ( !pEntity || !pEntity->IsPlayer() || !ToTFPlayer( const_cast< CBaseEntity * >( pEntity ) )->IsBotOfType( TF_BOT_TYPE ) ) return NULL; Assert( "***IMPORTANT!!! DONT IGNORE ME!!!***" && dynamic_cast< const CTFBot * >( pEntity ) != 0 ); return static_cast< const CTFBot * >( pEntity ); } //-------------------------------------------------------------------------------------------------------------- /** * Functor used with NavAreaBuildPath() */ class CTFBotPathCost : public IPathCost { public: CTFBotPathCost( CTFBot *me, RouteType routeType ) { m_me = me; m_routeType = routeType; m_stepHeight = me->GetLocomotionInterface()->GetStepHeight(); m_maxJumpHeight = me->GetLocomotionInterface()->GetMaxJumpHeight(); m_maxDropHeight = me->GetLocomotionInterface()->GetDeathDropHeight(); } virtual float operator()( CNavArea *baseArea, CNavArea *fromArea, const CNavLadder *ladder, const CFuncElevator *elevator, float length ) const { VPROF_BUDGET( "CTFBotPathCost::operator()", "NextBot" ); CTFNavArea *area = (CTFNavArea *)baseArea; if ( fromArea == NULL ) { // first area in path, no cost return 0.0f; } else { if ( !m_me->GetLocomotionInterface()->IsAreaTraversable( area ) ) { return -1.0f; } // in training, avoid capturing the point until the human trainee does so if ( TFGameRules()->IsInTraining() && area->HasAttributeTF( TF_NAV_CONTROL_POINT ) && !m_me->IsAnyPointBeingCaptured() && !m_me->IsPlayerClass( TF_CLASS_ENGINEER ) ) // allow engineers to path so they can test travel distance for sentry placement { return -1.0f; } // don't path through enemy spawn rooms if ( ( m_me->GetTeamNumber() == TF_TEAM_RED && area->HasAttributeTF( TF_NAV_SPAWN_ROOM_BLUE ) ) || ( m_me->GetTeamNumber() == TF_TEAM_BLUE && area->HasAttributeTF( TF_NAV_SPAWN_ROOM_RED ) ) ) { if ( !TFGameRules()->RoundHasBeenWon() ) { return -1.0f; } } // compute distance traveled along path so far float dist; if ( ladder ) { dist = ladder->m_length; } else if ( length > 0.0 ) { dist = length; } else { dist = ( area->GetCenter() - fromArea->GetCenter() ).Length(); } // check height change float deltaZ = fromArea->ComputeAdjacentConnectionHeightChange( area ); if ( deltaZ >= m_stepHeight ) { if ( deltaZ >= m_maxJumpHeight ) { // too high to reach return -1.0f; } // jumping is slower than flat ground const float jumpPenalty = 2.0f; dist *= jumpPenalty; } else if ( deltaZ < -m_maxDropHeight ) { // too far to drop return -1.0f; } // add a random penalty unique to this character so they choose different routes to the same place float preference = 1.0f; if ( m_routeType == DEFAULT_ROUTE && !m_me->IsMiniBoss() ) { // this term causes the same bot to choose different routes over time, // but keep the same route for a period in case of repaths int timeMod = (int)( gpGlobals->curtime / 10.0f ) + 1; preference = 1.0f + 50.0f * ( 1.0f + FastCos( (float)( m_me->GetEntity()->entindex() * area->GetID() * timeMod ) ) ); } if ( m_routeType == SAFEST_ROUTE ) { // avoid combat areas if ( area->IsInCombat() ) { const float combatDangerCost = 4.0f; dist *= combatDangerCost * area->GetCombatIntensity(); } // if this area exposes us to enemy sentry fire, avoid it const float sentryDangerCost = 5.0f; if ( ( m_me->GetTeamNumber() == TF_TEAM_RED && area->HasAttributeTF( TF_NAV_BLUE_SENTRY_DANGER ) ) || ( m_me->GetTeamNumber() == TF_TEAM_BLUE && area->HasAttributeTF( TF_NAV_RED_SENTRY_DANGER ) ) ) { dist *= sentryDangerCost; } } if ( m_me->IsPlayerClass( TF_CLASS_SPY ) ) { int enemyTeam = GetEnemyTeam( m_me->GetTeamNumber() ); // Since spies can get right up to enemy buildings, avoid them. for ( int oit = 0; oit < IBaseObjectAutoList::AutoList().Count(); ++oit ) { CBaseObject *enemyObj = static_cast< CBaseObject* >( IBaseObjectAutoList::AutoList()[ oit ] ); if ( ( enemyObj->ObjectType() == OBJ_SENTRYGUN ) && ( enemyObj->GetTeamNumber() == enemyTeam ) ) { enemyObj->UpdateLastKnownArea(); if ( enemyObj->GetLastKnownArea() == area ) { // There is an enemy building in this area - avoid it as a spy. const float enemyBuildingCost = 10.0f; dist *= enemyBuildingCost; } } } // Spies avoid teammates, since they draw attention and gunfire. const float teammateCost = 10.0f; dist += dist * teammateCost * area->GetPlayerCount( m_me->GetTeamNumber() ); // We shouldn't be getting NaNs here. It will be handled when we return, but ideally // it should be fixed here and not just worked around in NavAreaBuildPath. DebuggerBreakOnNaN_StagingOnly( dist ); } float cost = ( dist * preference ); if ( area->HasAttributes( NAV_MESH_FUNC_COST ) ) { cost *= area->ComputeFuncNavCost( m_me ); DebuggerBreakOnNaN_StagingOnly( cost ); } return cost + fromArea->GetCostSoFar(); } } CTFBot *m_me; RouteType m_routeType; float m_stepHeight; float m_maxJumpHeight; float m_maxDropHeight; }; //--------------------------------------------------------------------------------------------- class CClosestTFPlayer { public: CClosestTFPlayer( const Vector &where, int team = TEAM_ANY ) { m_where = where; m_closeRangeSq = FLT_MAX; m_closePlayer = NULL; m_team = team; } CClosestTFPlayer( CBaseEntity *entity, int team = TEAM_ANY ) { m_where = entity->WorldSpaceCenter(); m_closeRangeSq = FLT_MAX; m_closePlayer = NULL; m_team = team; } bool operator() ( CBasePlayer *player ) { if ( !player->IsAlive() ) return true; if ( player->GetTeamNumber() != TF_TEAM_RED && player->GetTeamNumber() != TF_TEAM_BLUE ) return true; if ( m_team != TEAM_ANY && player->GetTeamNumber() != m_team ) return true; CTFBot *bot = ToTFBot( player ); if ( bot && bot->HasAttribute( CTFBot::IS_NPC ) ) return true; float rangeSq = ( m_where - player->GetAbsOrigin() ).LengthSqr(); if ( rangeSq < m_closeRangeSq ) { m_closeRangeSq = rangeSq; m_closePlayer = player; } return true; } Vector m_where; float m_closeRangeSq; CBasePlayer *m_closePlayer; int m_team; }; #endif // TF_BOT_H