add csrike source code

This commit is contained in:
nillerusr
2022-03-02 11:45:17 +03:00
parent f7233c84e0
commit 5b926feae6
357 changed files with 135241 additions and 12 deletions
@@ -0,0 +1,118 @@
// BehaviorBackUp.h
// Back up for a short duration
// Author: Michael Booth, March 2007
//========= Copyright Valve Corporation, All rights reserved. ============//
#ifndef _BEHAVIOR_BACK_UP_H_
#define _BEHAVIOR_BACK_UP_H_
//----------------------------------------------------------------------------------------------
/**
* Move backwards for a short duration away from a given position.
* Useful to dislodge ourselves if we get stuck while following our path.
*/
template < typename Actor >
class BehaviorBackUp : public Action< Actor >
{
public:
BehaviorBackUp( const Vector &avoidPos );
virtual ActionResult< Actor > OnStart( Actor *me, Action< Actor > *priorAction );
virtual ActionResult< Actor > Update( Actor *me, float interval );
virtual EventDesiredResult< Actor > OnStuck( Actor *me );
virtual const char *GetName( void ) const { return "BehaviorBackUp"; }
private:
CountdownTimer m_giveUpTimer;
CountdownTimer m_backupTimer;
CountdownTimer m_jumpTimer;
Vector m_way;
Vector m_avoidPos;
};
//----------------------------------------------------------------------------------------------
template < typename Actor >
inline BehaviorBackUp< Actor >::BehaviorBackUp( const Vector &avoidPos )
{
m_avoidPos = avoidPos;
}
//----------------------------------------------------------------------------------------------
template < typename Actor >
inline ActionResult< Actor > BehaviorBackUp< Actor >::OnStart( Actor *me, Action< Actor > *priorAction )
{
ILocomotion *mover = me->GetLocomotionInterface();
// don't back off if we're on a ladder
if ( mover && mover->IsUsingLadder() )
{
return Done();
}
float backupTime = RandomFloat( 0.3f, 0.5f );
m_backupTimer.Start( backupTime );
m_jumpTimer.Start( 1.5f * backupTime );
m_giveUpTimer.Start( 2.5f * backupTime );
m_way = me->GetPosition() - m_avoidPos;
m_way.NormalizeInPlace();
return Continue();
}
//----------------------------------------------------------------------------------------------
template < typename Actor >
inline ActionResult< Actor > BehaviorBackUp< Actor >::Update( Actor *me, float interval )
{
if ( m_giveUpTimer.IsElapsed() )
{
return Done();
}
// if ( m_jumpTimer.HasStarted() && m_jumpTimer.IsElapsed() )
// {
// me->GetLocomotionInterface()->Jump();
// m_jumpTimer.Invalidate();
// }
ILocomotion *mover = me->GetLocomotionInterface();
if ( mover )
{
Vector goal;
if ( m_backupTimer.IsElapsed() )
{
// move towards bad spot
goal = m_avoidPos; // me->GetPosition() - 100.0f * m_way;
}
else
{
// move away from bad spot
goal = me->GetPosition() + 100.0f * m_way;
}
mover->Approach( goal );
}
return Continue();
}
//----------------------------------------------------------------------------------------------
template < typename Actor >
inline EventDesiredResult< Actor > BehaviorBackUp< Actor >::OnStuck( Actor *me )
{
return TryToSustain( RESULT_IMPORTANT, "Stuck while trying to back up" );
}
#endif // _BEHAVIOR_BACK_UP_H_
@@ -0,0 +1,126 @@
// BehaviorMoveTo.h
// Move to a potentially far away position
// Author: Michael Booth, June 2007
//========= Copyright Valve Corporation, All rights reserved. ============//
#ifndef _BEHAVIOR_MOVE_TO_H_
#define _BEHAVIOR_MOVE_TO_H_
//----------------------------------------------------------------------------------------------
/**
* Move to a potentially far away position, using path planning.
*/
template < typename Actor, typename PathCost >
class BehaviorMoveTo : public Action< Actor >
{
public:
BehaviorMoveTo( const Vector &goal, Action< Actor > *successAction = NULL, Action< Actor > *failAction = NULL );
virtual ActionResult< Actor > OnStart( Actor *me, Action< Actor > *priorAction );
virtual ActionResult< Actor > Update( Actor *me, float interval );
virtual EventDesiredResult< Actor > OnMoveToSuccess( Actor *me, const Path *path );
virtual EventDesiredResult< Actor > OnMoveToFailure( Actor *me, const Path *path, MoveToFailureType reason );
virtual bool ComputePath( Actor *me, const Vector &goal, PathFollower *path );
virtual const char *GetName( void ) const { return "BehaviorMoveTo"; }
private:
Vector m_goal;
PathFollower m_path;
Action< Actor > *m_successAction;
Action< Actor > *m_failAction;
};
//----------------------------------------------------------------------------------------------
template < typename Actor, typename PathCost >
inline BehaviorMoveTo< Actor, PathCost >::BehaviorMoveTo( const Vector &goal, Action< Actor > *successAction, Action< Actor > *failAction )
{
m_goal = goal;
m_path.Invalidate();
m_successAction = successAction;
m_failAction = failAction;
}
//----------------------------------------------------------------------------------------------
template < typename Actor, typename PathCost >
inline bool BehaviorMoveTo< Actor, PathCost >::ComputePath( Actor *me, const Vector &goal, PathFollower *path )
{
PathCost cost( me );
return path->Compute( me, goal, cost );
}
//----------------------------------------------------------------------------------------------
template < typename Actor, typename PathCost >
inline ActionResult< Actor > BehaviorMoveTo< Actor, PathCost >::OnStart( Actor *me, Action< Actor > *priorAction )
{
if ( !this->ComputePath( me, m_goal, &m_path ) )
{
if ( m_failAction )
{
return this->ChangeTo( m_failAction, "No path to goal" );
}
return this->Done( "No path to goal" );
}
return this->Continue();
}
//----------------------------------------------------------------------------------------------
template < typename Actor, typename PathCost >
inline ActionResult< Actor > BehaviorMoveTo< Actor, PathCost >::Update( Actor *me, float interval )
{
// if path became invalid during last tick for any reason, we're done
if ( !m_path.IsValid() )
{
if ( m_failAction )
{
return this->ChangeTo( m_failAction, "Path is invalid" );
}
return this->Done( "Path is invalid" );
}
// move along path - success/fail event handlers will exit behavior when goal is reached
m_path.Update( me );
return this->Continue();
}
//----------------------------------------------------------------------------------------------
template < typename Actor, typename PathCost >
inline EventDesiredResult< Actor > BehaviorMoveTo< Actor, PathCost >::OnMoveToSuccess( Actor *me, const Path *path )
{
if ( m_successAction )
{
return this->TryChangeTo( m_successAction, RESULT_CRITICAL, "OnMoveToSuccess" );
}
return this->TryDone( RESULT_CRITICAL, "OnMoveToSuccess" );
}
//----------------------------------------------------------------------------------------------
template < typename Actor, typename PathCost >
inline EventDesiredResult< Actor > BehaviorMoveTo< Actor, PathCost >::OnMoveToFailure( Actor *me, const Path *path, MoveToFailureType reason )
{
if ( m_failAction )
{
return this->TryChangeTo( m_failAction, RESULT_CRITICAL, "OnMoveToFailure" );
}
return this->TryDone( RESULT_CRITICAL, "OnMoveToFailure" );
}
#endif // _BEHAVIOR_MOVE_TO_H_
@@ -0,0 +1,75 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// NextBot paths that go through this entity must fulfill the given prerequisites to pass
// Michael Booth, August 2009
#include "cbase.h"
#include "func_nav_prerequisite.h"
#include "ndebugoverlay.h"
#include "modelentities.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
LINK_ENTITY_TO_CLASS( func_nav_prerequisite, CFuncNavPrerequisite );
BEGIN_DATADESC( CFuncNavPrerequisite )
DEFINE_KEYFIELD( m_task, FIELD_INTEGER, "Task" ),
DEFINE_KEYFIELD( m_taskEntityName, FIELD_STRING, "Entity" ),
DEFINE_KEYFIELD( m_taskValue, FIELD_FLOAT, "Value" ),
DEFINE_KEYFIELD( m_isDisabled, FIELD_BOOLEAN, "StartDisabled" ),
DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ),
DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ),
END_DATADESC()
IMPLEMENT_AUTO_LIST( IFuncNavPrerequisiteAutoList );
//-----------------------------------------------------------------------------
CFuncNavPrerequisite::CFuncNavPrerequisite()
{
m_task = TASK_NONE;
m_hTaskEntity = NULL;
}
//-----------------------------------------------------------------------------
void CFuncNavPrerequisite::Spawn( void )
{
AddSpawnFlags( SF_TRIGGER_ALLOW_CLIENTS );
BaseClass::Spawn();
InitTrigger();
}
//-----------------------------------------------------------------------------
bool CFuncNavPrerequisite::IsTask( TaskType task ) const
{
return task == m_task ? true : false;
}
//-----------------------------------------------------------------------------
CBaseEntity *CFuncNavPrerequisite::GetTaskEntity( void )
{
if ( m_hTaskEntity == NULL )
{
m_hTaskEntity = gEntList.FindEntityByName( NULL, m_taskEntityName );
}
return m_hTaskEntity;
}
//--------------------------------------------------------------------------------------------------------
void CFuncNavPrerequisite::InputEnable( inputdata_t &inputdata )
{
m_isDisabled = false;
}
//--------------------------------------------------------------------------------------------------------
void CFuncNavPrerequisite::InputDisable( inputdata_t &inputdata )
{
m_isDisabled = true;
}
@@ -0,0 +1,56 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// NextBot paths that go through this entity must fulfill the given prerequisites to pass
// Michael Booth, August 2009
#ifndef FUNC_NAV_PREREQUISITE_H
#define FUNC_NAV_PREREQUISITE_H
#include "triggers.h"
/**
* NextBot paths that pass through this entity must fulfill the given prerequisites to pass
*/
DECLARE_AUTO_LIST( IFuncNavPrerequisiteAutoList );
class CFuncNavPrerequisite : public CBaseTrigger, public IFuncNavPrerequisiteAutoList
{
DECLARE_CLASS( CFuncNavPrerequisite, CBaseTrigger );
public:
CFuncNavPrerequisite();
DECLARE_DATADESC();
virtual void Spawn( void );
enum TaskType
{
TASK_NONE = 0,
TASK_DESTROY_ENTITY = 1,
TASK_MOVE_TO_ENTITY = 2,
TASK_WAIT = 3,
};
bool IsTask( TaskType type ) const;
CBaseEntity *GetTaskEntity( void );
float GetTaskValue( void ) const;
void InputEnable( inputdata_t &inputdata );
void InputDisable( inputdata_t &inputdata );
bool IsEnabled( void ) const { return !m_isDisabled; }
protected:
int m_task;
string_t m_taskEntityName;
float m_taskValue;
bool m_isDisabled;
EHANDLE m_hTaskEntity;
};
inline float CFuncNavPrerequisite::GetTaskValue( void ) const
{
return m_taskValue;
}
#endif // FUNC_NAV_PREREQUISITE_H
+523
View File
@@ -0,0 +1,523 @@
// NextBotCombatCharacter.cpp
// Next generation bot system
// Author: Michael Booth, April 2005
//========= Copyright Valve Corporation, All rights reserved. ============//
#include "cbase.h"
#include "team.h"
#include "CRagdollMagnet.h"
#include "NextBot.h"
#include "NextBotLocomotionInterface.h"
#include "NextBotBodyInterface.h"
#ifdef TERROR
#include "TerrorGamerules.h"
#endif
#include "vprof.h"
#include "datacache/imdlcache.h"
#include "EntityFlame.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
ConVar NextBotStop( "nb_stop", "0", FCVAR_CHEAT | FCVAR_REPLICATED, "Stop all NextBots" );
//--------------------------------------------------------------------------------------------------------
class CSendBotCommand
{
public:
CSendBotCommand( const char *command )
{
m_command = command;
}
bool operator() ( INextBot *bot )
{
bot->OnCommandString( m_command );
return true;
}
const char *m_command;
};
CON_COMMAND_F( nb_command, "Sends a command string to all bots", FCVAR_CHEAT )
{
if ( args.ArgC() <= 1 )
{
Msg( "Missing command string" );
return;
}
CSendBotCommand sendCmd( args.ArgS() );
TheNextBots().ForEachBot( sendCmd );
}
//-----------------------------------------------------------------------------------------------------
BEGIN_DATADESC( NextBotCombatCharacter )
DEFINE_THINKFUNC( DoThink ),
END_DATADESC()
//-----------------------------------------------------------------------------------------------------
IMPLEMENT_SERVERCLASS_ST( NextBotCombatCharacter, DT_NextBot )
END_SEND_TABLE()
//-----------------------------------------------------------------------------------------------------
NextBotDestroyer::NextBotDestroyer( int team )
{
m_team = team;
}
//-----------------------------------------------------------------------------------------------------
bool NextBotDestroyer::operator() ( INextBot *bot )
{
if ( m_team == TEAM_ANY || bot->GetEntity()->GetTeamNumber() == m_team )
{
// players need to be kicked, not deleted
if ( bot->GetEntity()->IsPlayer() )
{
CBasePlayer *player = dynamic_cast< CBasePlayer * >( bot->GetEntity() );
engine->ServerCommand( UTIL_VarArgs( "kickid %d\n", player->GetUserID() ) );
}
else
{
UTIL_Remove( bot->GetEntity() );
}
}
return true;
}
//-----------------------------------------------------------------------------------------------------
CON_COMMAND_F( nb_delete_all, "Delete all non-player NextBot entities.", FCVAR_CHEAT )
{
// Listenserver host or rcon access only!
if ( !UTIL_IsCommandIssuedByServerAdmin() )
return;
CTeam *team = NULL;
if ( args.ArgC() == 2 )
{
const char *teamName = args[1];
for( int i=0; i < g_Teams.Count(); ++i )
{
if ( FStrEq( teamName, g_Teams[i]->GetName() ) )
{
// delete all bots on this team
team = g_Teams[i];
break;
}
}
if ( team == NULL )
{
Msg( "Invalid team '%s'\n", teamName );
return;
}
}
// delete all bots on all teams
NextBotDestroyer destroyer( team ? team->GetTeamNumber() : TEAM_ANY );
TheNextBots().ForEachBot( destroyer );
}
//-----------------------------------------------------------------------------------------------------
class NextBotApproacher
{
public:
NextBotApproacher( void )
{
CBasePlayer *player = UTIL_GetListenServerHost();
if ( player )
{
Vector forward;
player->EyeVectors( &forward );
trace_t result;
unsigned int mask = MASK_BLOCKLOS_AND_NPCS|CONTENTS_IGNORE_NODRAW_OPAQUE | CONTENTS_GRATE | CONTENTS_WINDOW;
UTIL_TraceLine( player->EyePosition(), player->EyePosition() + 999999.9f * forward, mask, player, COLLISION_GROUP_NONE, &result );
if ( result.DidHit() )
{
NDebugOverlay::Cross3D( result.endpos, 5, 0, 255, 0, true, 10.0f );
m_isGoalValid = true;
m_goal = result.endpos;
}
else
{
m_isGoalValid = false;
}
}
}
bool operator() ( INextBot *bot )
{
if ( TheNextBots().IsDebugFilterMatch( bot ) )
{
bot->OnCommandApproach( m_goal );
}
return true;
}
bool m_isGoalValid;
Vector m_goal;
};
CON_COMMAND_F( nb_move_to_cursor, "Tell all NextBots to move to the cursor position", FCVAR_CHEAT )
{
// Listenserver host or rcon access only!
if ( !UTIL_IsCommandIssuedByServerAdmin() )
return;
NextBotApproacher approach;
TheNextBots().ForEachBot( approach );
}
//----------------------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------------------
bool IgnoreActorsTraceFilterFunction( IHandleEntity *pServerEntity, int contentsMask )
{
CBaseEntity *entity = EntityFromEntityHandle( pServerEntity );
return ( entity->MyCombatCharacterPointer() == NULL ); // includes all bots, npcs, players, and TF2 buildings
}
//----------------------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------------------
bool VisionTraceFilterFunction( IHandleEntity *pServerEntity, int contentsMask )
{
// Honor BlockLOS also to allow seeing through partially-broken doors
CBaseEntity *entity = EntityFromEntityHandle( pServerEntity );
return ( entity->MyCombatCharacterPointer() == NULL && entity->BlocksLOS() );
}
//----------------------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------------------
NextBotCombatCharacter::NextBotCombatCharacter( void )
{
m_lastAttacker = NULL;
m_didModelChange = false;
}
//----------------------------------------------------------------------------------------------------------
void NextBotCombatCharacter::Spawn( void )
{
BaseClass::Spawn();
// reset bot components
Reset();
SetSolid( SOLID_BBOX );
AddSolidFlags( FSOLID_NOT_STANDABLE );
SetMoveType( MOVETYPE_CUSTOM );
SetCollisionGroup( COLLISION_GROUP_PLAYER );
m_iMaxHealth = m_iHealth;
m_takedamage = DAMAGE_YES;
MDLCACHE_CRITICAL_SECTION();
InitBoneControllers( );
// set up think callback
SetThink( &NextBotCombatCharacter::DoThink );
SetNextThink( gpGlobals->curtime );
m_lastAttacker = NULL;
}
bool NextBotCombatCharacter::IsAreaTraversable( const CNavArea *area ) const
{
if ( !area )
return false;
ILocomotion *mover = GetLocomotionInterface();
if ( mover && !mover->IsAreaTraversable( area ) )
return false;
return BaseClass::IsAreaTraversable( area );
}
//----------------------------------------------------------------------------------------------------------
void NextBotCombatCharacter::DoThink( void )
{
VPROF_BUDGET( "NextBotCombatCharacter::DoThink", "NextBot" );
SetNextThink( gpGlobals->curtime );
if ( BeginUpdate() )
{
// emit model change event
if ( m_didModelChange )
{
m_didModelChange = false;
OnModelChanged();
// propagate model change into NextBot event responders
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnModelChanged();
}
}
UpdateLastKnownArea();
// update bot components
if ( !NextBotStop.GetBool() && (GetFlags() & FL_FROZEN) == 0 )
{
Update();
}
EndUpdate();
}
}
//----------------------------------------------------------------------------------------------------------
void NextBotCombatCharacter::Touch( CBaseEntity *other )
{
if ( ShouldTouch( other ) )
{
// propagate touch into NextBot event responders
trace_t result;
result = GetTouchTrace();
// OnContact refers to *physical* contact, not triggers or other non-physical entities
if ( result.DidHit() || other->MyCombatCharacterPointer() != NULL )
{
OnContact( other, &result );
}
}
BaseClass::Touch( other );
}
//----------------------------------------------------------------------------------------------------------
void NextBotCombatCharacter::SetModel( const char *szModelName )
{
// actually change the model
BaseClass::SetModel( szModelName );
// need to do a lazy-check because precache system also invokes this
m_didModelChange = true;
}
//----------------------------------------------------------------------------------------------------------
void NextBotCombatCharacter::Ignite( float flFlameLifetime, bool bNPCOnly, float flSize, bool bCalledByLevelDesigner )
{
BaseClass::Ignite( flFlameLifetime, bNPCOnly, flSize, bCalledByLevelDesigner );
// propagate event to components
OnIgnite();
}
//----------------------------------------------------------------------------------------------------------
void NextBotCombatCharacter::Ignite( float flFlameLifetime, CBaseEntity *pAttacker )
{
if ( IsOnFire() )
return;
// BaseClass::Ignite stuff, plus SetAttacker on the flame, so our attacker gets credit
CEntityFlame *pFlame = CEntityFlame::Create( this );
if ( pFlame )
{
pFlame->SetLifetime( flFlameLifetime );
AddFlag( FL_ONFIRE );
SetEffectEntity( pFlame );
}
m_OnIgnite.FireOutput( this, this );
// propagate event to components
OnIgnite();
}
//----------------------------------------------------------------------------------------------------------
int NextBotCombatCharacter::OnTakeDamage_Alive( const CTakeDamageInfo &info )
{
// track our last attacker
if ( info.GetAttacker() && info.GetAttacker()->MyCombatCharacterPointer() )
{
m_lastAttacker = info.GetAttacker()->MyCombatCharacterPointer();
}
// propagate event to components
OnInjured( info );
return CBaseCombatCharacter::OnTakeDamage_Alive( info );
}
//----------------------------------------------------------------------------------------------------------
int NextBotCombatCharacter::OnTakeDamage_Dying( const CTakeDamageInfo &info )
{
// track our last attacker
if ( info.GetAttacker()->MyCombatCharacterPointer() )
{
m_lastAttacker = info.GetAttacker()->MyCombatCharacterPointer();
}
// propagate event to components
OnInjured( info );
return CBaseCombatCharacter::OnTakeDamage_Dying( info );
}
//----------------------------------------------------------------------------------------------------------
/**
* Can't use CBaseCombatCharacter's Event_Killed because it will immediately ragdoll us
*/
static int g_DeathStartEvent = 0;
void NextBotCombatCharacter::Event_Killed( const CTakeDamageInfo &info )
{
// track our last attacker
if ( info.GetAttacker() && info.GetAttacker()->MyCombatCharacterPointer() )
{
m_lastAttacker = info.GetAttacker()->MyCombatCharacterPointer();
}
// propagate event to my components
OnKilled( info );
// Advance life state to dying
m_lifeState = LIFE_DYING;
#ifdef TERROR
/*
* TODO: Make this game-generic
*/
// Create the death event just like players do.
TerrorGameRules()->DeathNoticeForEntity( this, info );
// Infected specific event
TerrorGameRules()->DeathNoticeForInfected( this, info );
#endif
if ( GetOwnerEntity() != NULL )
{
GetOwnerEntity()->DeathNotice( this );
}
// inform the other bots
TheNextBots().OnKilled( this, info );
}
//----------------------------------------------------------------------------------------------------------
void NextBotCombatCharacter::PerformCustomPhysics( Vector *pNewPosition, Vector *pNewVelocity, QAngle *pNewAngles, QAngle *pNewAngVelocity )
{
ILocomotion *mover = GetLocomotionInterface();
if ( mover )
{
// hack to keep ground entity from being NULL'd when Z velocity is positive
SetGroundEntity( mover->GetGround() );
}
}
//----------------------------------------------------------------------------------------------------------
bool NextBotCombatCharacter::BecomeRagdoll( const CTakeDamageInfo &info, const Vector &forceVector )
{
// See if there's a ragdoll magnet that should influence our force.
Vector adjustedForceVector = forceVector;
CRagdollMagnet *magnet = CRagdollMagnet::FindBestMagnet( this );
if ( magnet )
{
adjustedForceVector += magnet->GetForceVector( this );
}
// clear the deceased's sound channels.(may have been firing or reloading when killed)
EmitSound( "BaseCombatCharacter.StopWeaponSounds" );
return BaseClass::BecomeRagdoll( info, adjustedForceVector );
}
//----------------------------------------------------------------------------------------------------------
void NextBotCombatCharacter::HandleAnimEvent( animevent_t *event )
{
// propagate event to components
OnAnimationEvent( event );
}
//----------------------------------------------------------------------------------------------------------
/**
* Propagate event into NextBot event responders
*/
void NextBotCombatCharacter::OnNavAreaChanged( CNavArea *enteredArea, CNavArea *leftArea )
{
INextBotEventResponder::OnNavAreaChanged( enteredArea, leftArea );
BaseClass::OnNavAreaChanged( enteredArea, leftArea );
}
//----------------------------------------------------------------------------------------------------------
Vector NextBotCombatCharacter::EyePosition( void )
{
if ( GetBodyInterface() )
{
return GetBodyInterface()->GetEyePosition();
}
return BaseClass::EyePosition();
}
//----------------------------------------------------------------------------------------------------------
/**
* Return true if this object can be +used by the bot
*/
bool NextBotCombatCharacter::IsUseableEntity( CBaseEntity *entity, unsigned int requiredCaps )
{
if ( entity )
{
int caps = entity->ObjectCaps();
if ( caps & (FCAP_IMPULSE_USE|FCAP_CONTINUOUS_USE|FCAP_ONOFF_USE|FCAP_DIRECTIONAL_USE) )
{
if ( (caps & requiredCaps) == requiredCaps )
{
return true;
}
}
}
return false;
}
//----------------------------------------------------------------------------------------------------------
void NextBotCombatCharacter::UseEntity( CBaseEntity *entity, USE_TYPE useType )
{
if ( IsUseableEntity( entity ) )
{
variant_t emptyVariant;
entity->AcceptInput( "Use", this, this, emptyVariant, useType );
}
}
+104
View File
@@ -0,0 +1,104 @@
// NextBotCombatCharacter.h
// Next generation bot system
// Author: Michael Booth, April 2005
//========= Copyright Valve Corporation, All rights reserved. ============//
#ifndef _NEXT_BOT_H_
#define _NEXT_BOT_H_
#include "NextBotInterface.h"
#include "NextBotManager.h"
#ifdef TERROR
#include "player_lagcompensation.h"
#endif
class NextBotCombatCharacter;
struct animevent_t;
extern ConVar NextBotStop;
//----------------------------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------------------------
/**
* A Next Bot derived from CBaseCombatCharacter
*/
class NextBotCombatCharacter : public CBaseCombatCharacter, public INextBot
{
public:
DECLARE_CLASS( NextBotCombatCharacter, CBaseCombatCharacter );
DECLARE_SERVERCLASS();
DECLARE_DATADESC();
NextBotCombatCharacter( void );
virtual ~NextBotCombatCharacter() { }
virtual void Spawn( void );
virtual Vector EyePosition( void );
virtual INextBot *MyNextBotPointer( void ) { return this; }
// Event hooks into NextBot system ---------------------------------------
virtual int OnTakeDamage_Alive( const CTakeDamageInfo &info );
virtual int OnTakeDamage_Dying( const CTakeDamageInfo &info );
virtual void Event_Killed( const CTakeDamageInfo &info );
virtual void HandleAnimEvent( animevent_t *event );
virtual void OnNavAreaChanged( CNavArea *enteredArea, CNavArea *leftArea ); // invoked (by UpdateLastKnownArea) when we enter a new nav area (or it is reset to NULL)
virtual void Touch( CBaseEntity *other );
virtual void SetModel( const char *szModelName );
virtual void Ignite( float flFlameLifetime, bool bNPCOnly = true, float flSize = 0.0f, bool bCalledByLevelDesigner = false );
virtual void Ignite( float flFlameLifetime, CBaseEntity *pAttacker );
//------------------------------------------------------------------------
virtual bool IsUseableEntity( CBaseEntity *entity, unsigned int requiredCaps = 0 );
void UseEntity( CBaseEntity *entity, USE_TYPE useType = USE_TOGGLE );
// Implement this if you use MOVETYPE_CUSTOM
virtual void PerformCustomPhysics( Vector *pNewPosition, Vector *pNewVelocity, QAngle *pNewAngles, QAngle *pNewAngVelocity );
virtual bool BecomeRagdoll( const CTakeDamageInfo &info, const Vector &forceVector );
// hook to INextBot update
void DoThink( void );
// expose to public
int GetLastHitGroup( void ) const; // where on our body were we injured last
virtual bool IsAreaTraversable( const CNavArea *area ) const; // return true if we can use the given area
virtual CBaseCombatCharacter *GetLastAttacker( void ) const; // return the character who last attacked me
// begin INextBot public interface ----------------------------------------------------------------
virtual NextBotCombatCharacter *GetEntity( void ) const { return const_cast< NextBotCombatCharacter * >( this ); }
virtual NextBotCombatCharacter *GetNextBotCombatCharacter( void ) const { return const_cast< NextBotCombatCharacter * >( this ); }
private:
EHANDLE m_lastAttacker;
bool m_didModelChange;
};
inline CBaseCombatCharacter *NextBotCombatCharacter::GetLastAttacker( void ) const
{
return ( m_lastAttacker.Get() == NULL ) ? NULL : m_lastAttacker->MyCombatCharacterPointer();
}
inline int NextBotCombatCharacter::GetLastHitGroup( void ) const
{
return LastHitGroup();
}
//-----------------------------------------------------------------------------------------------------
class NextBotDestroyer
{
public:
NextBotDestroyer( int team );
bool operator() ( INextBot *bot );
int m_team; // the team to delete bots from, or TEAM_ANY for any team
};
#endif // _NEXT_BOT_H_
@@ -0,0 +1,162 @@
// NextBotAttentionInterface.cpp
// Manage what this bot pays attention to
// Author: Michael Booth, April 2007
//========= Copyright Valve Corporation, All rights reserved. ============//
#include "cbase.h"
#include "NextBot.h"
#include "NextBotAttentionInterface.h"
#include "NextBotBodyInterface.h"
#include "tier0/vprof.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//------------------------------------------------------------------------------------------
/**
* Reset to initial state
*/
void IAttention::Reset( void )
{
m_body = GetBot()->GetBodyInterface();
m_attentionSet.RemoveAll();
}
//------------------------------------------------------------------------------------------
/**
* Update internal state
*/
void IAttention::Update( void )
{
}
//------------------------------------------------------------------------------------------
void IAttention::AttendTo( const CBaseCombatCharacter *who, const char *reason )
{
if ( !IsAwareOf( who ) )
{
PointOfInterest p;
p.m_type = PointOfInterest::WHO;
p.m_who = who;
p.m_duration.Start();
m_attentionSet.AddToTail( p );
}
}
//------------------------------------------------------------------------------------------
void IAttention::AttendTo( const CBaseEntity *what, const char *reason )
{
if ( !IsAwareOf( what ) )
{
PointOfInterest p;
p.m_type = PointOfInterest::WHAT;
p.m_what = what;
p.m_duration.Start();
m_attentionSet.AddToTail( p );
}
}
//------------------------------------------------------------------------------------------
void IAttention::AttendTo( const Vector &where, IAttention::SignificanceLevel significance, const char *reason )
{
PointOfInterest p;
p.m_type = PointOfInterest::WHERE;
p.m_where = where;
p.m_duration.Start();
m_attentionSet.AddToTail( p );
}
//------------------------------------------------------------------------------------------
void IAttention::Disregard( const CBaseCombatCharacter *who, const char *reason )
{
FOR_EACH_VEC( m_attentionSet, it )
{
if ( m_attentionSet[ it ].m_type == PointOfInterest::WHO )
{
CBaseCombatCharacter *myWho = m_attentionSet[ it ].m_who;
if ( !myWho || myWho->entindex() == who->entindex() )
{
m_attentionSet.Remove( it );
return;
}
}
}
}
//------------------------------------------------------------------------------------------
void IAttention::Disregard( const CBaseEntity *what, const char *reason )
{
FOR_EACH_VEC( m_attentionSet, it )
{
if ( m_attentionSet[ it ].m_type == PointOfInterest::WHAT )
{
CBaseCombatCharacter *myWhat = m_attentionSet[ it ].m_what;
if ( !myWhat || myWhat->entindex() == what->entindex() )
{
m_attentionSet.Remove( it );
return;
}
}
}
}
//------------------------------------------------------------------------------------------
/**
* Return true if given actor is in our attending set
*/
bool IAttention::IsAwareOf( const CBaseCombatCharacter *who ) const
{
FOR_EACH_VEC( m_attentionSet, it )
{
if ( m_attentionSet[ it ].m_type == PointOfInterest::WHO )
{
CBaseCombatCharacter *myWho = m_attentionSet[ it ].m_who;
if ( myWho && myWho->entindex() == who->entindex() )
{
return true;
}
}
}
return false;
}
//------------------------------------------------------------------------------------------
/**
* Return true if given object is in our attending set
*/
bool IAttention::IsAwareOf( const CBaseEntity *what ) const
{
FOR_EACH_VEC( m_attentionSet, it )
{
if ( m_attentionSet[ it ].m_type == PointOfInterest::WHAT )
{
CBaseEntity *myWhat = m_attentionSet[ it ].m_what;
if ( myWhat && myWhat->entindex() == what->entindex() )
{
return true;
}
}
}
return false;
}
@@ -0,0 +1,81 @@
// NextBotAttentionInterface.h
// Manage what this bot pays attention to
// Author: Michael Booth, April 2007
//========= Copyright Valve Corporation, All rights reserved. ============//
#ifndef _NEXT_BOT_ATTENTION_INTERFACE_H_
#define _NEXT_BOT_ATTENTION_INTERFACE_H_
#include "NextBotComponentInterface.h"
class INextBot;
class IBody;
//----------------------------------------------------------------------------------------------------------------
/**
* The interface for managing what a bot pays attention to.
* Vision determines what see see and notice -> Attention determines which of those things we look at -> Low level head/aiming simulation actually moves our head/eyes
*/
class IAttention : public INextBotComponent
{
public:
IAttention( INextBot *bot ) : INextBotComponent( bot ) { }
virtual ~IAttention() { }
virtual void Reset( void ) { } // reset to initial state
virtual void Update( void ) { } // update internal state
enum SignificanceLevel
{
BORING, // background noise
INTERESTING, // notably interesting
COMPELLING, // very hard to pay attention to anything else
IRRESISTIBLE, // can't look away
};
// override these to control the significance of entities in a context-specific way
virtual int CompareSignificance( const CBaseEntity *a, const CBaseEntity *b ) const; // returns <0 if a < b, 0 if a==b, or >0 if a>b
// bring things to our attention
virtual void AttendTo( CBaseEntity *what, const char *reason = NULL );
virtual void AttendTo( const Vector &where, SignificanceLevel significance, const char *reason = NULL );
// remove things from our attention
virtual void Disregard( CBaseEntity *what, const char *reason = NULL );
virtual bool IsAwareOf( CBaseEntity *what ) const; // return true if given object is in our attending set
virtual float GetAwareDuration( CBaseEntity *what ) const; // return how long we've been aware of this entity
// INextBotEventResponder ------------------------------------------------------------------
virtual void OnInjured( const CTakeDamageInfo &info ); // when bot is damaged by something
virtual void OnContact( CBaseEntity *other, CGameTrace *result = NULL ); // invoked when bot touches 'other'
virtual void OnSight( CBaseEntity *subject ); // when subject initially enters bot's visual awareness
virtual void OnLostSight( CBaseEntity *subject ); // when subject leaves enters bot's visual awareness
virtual void OnSound( CBaseEntity *source, const CSoundParameters &params ); // when an entity emits a sound
private:
IBody *m_body; // to access head aiming
struct PointOfInterest
{
enum { ENTITY, POSITION } m_type;
CHandle< CBaseEntity > m_entity;
Vector m_position;
IntervalTimer m_duration; // how long has this PoI been in our attention set
};
CUtlVector< PointOfInterest > m_attentionSet; // the set of things we are attending to
};
inline int IAttention::CompareSignificance( const CBaseEntity *a, const CBaseEntity *b ) const
{
return 0;
}
#endif // _NEXT_BOT_ATTENTION_INTERFACE_H_
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,55 @@
// NextBotBodyInterface.cpp
// Control and information about the bot's body state (posture, animation state, etc)
// Author: Michael Booth, April 2006
//========= Copyright Valve Corporation, All rights reserved. ============//
#include "cbase.h"
#include "NextBot.h"
#include "NextBotBodyInterface.h"
void IBody::AimHeadTowards( const Vector &lookAtPos, LookAtPriorityType priority, float duration, INextBotReply *replyWhenAimed, const char *reason )
{
if ( replyWhenAimed )
{
replyWhenAimed->OnFail( GetBot(), INextBotReply::FAILED );
}
}
void IBody::AimHeadTowards( CBaseEntity *subject, LookAtPriorityType priority, float duration, INextBotReply *replyWhenAimed, const char *reason )
{
if ( replyWhenAimed )
{
replyWhenAimed->OnFail( GetBot(), INextBotReply::FAILED );
}
}
bool IBody::SetPosition( const Vector &pos )
{
GetBot()->GetEntity()->SetAbsOrigin( pos );
return true;
}
const Vector &IBody::GetEyePosition( void ) const
{
static Vector eye;
eye = GetBot()->GetEntity()->WorldSpaceCenter();
return eye;
}
const Vector &IBody::GetViewVector( void ) const
{
static Vector view;
AngleVectors( GetBot()->GetEntity()->EyeAngles(), &view );
return view;
}
bool IBody::IsHeadAimingOnTarget( void ) const
{
return false;
}
+325
View File
@@ -0,0 +1,325 @@
// NextBotBodyInterface.h
// Control and information about the bot's body state (posture, animation state, etc)
// Author: Michael Booth, April 2006
//========= Copyright Valve Corporation, All rights reserved. ============//
#ifndef _NEXT_BOT_BODY_INTERFACE_H_
#define _NEXT_BOT_BODY_INTERFACE_H_
#include "animation.h"
#include "NextBotComponentInterface.h"
class INextBot;
struct animevent_t;
//----------------------------------------------------------------------------------------------------------------
/**
* The interface for control and information about the bot's body state (posture, animation state, etc)
*/
class IBody : public INextBotComponent
{
public:
IBody( INextBot *bot ) : INextBotComponent( bot ) { }
virtual ~IBody() { }
virtual void Reset( void ) { INextBotComponent::Reset(); } // reset to initial state
virtual void Update( void ) { } // update internal state
/**
* Move the bot to a new position.
* If the body is not currently movable or if it
* is in a motion-controlled animation activity
* the position will not be changed and false will be returned.
*/
virtual bool SetPosition( const Vector &pos );
virtual const Vector &GetEyePosition( void ) const; // return the eye position of the bot in world coordinates
virtual const Vector &GetViewVector( void ) const; // return the view unit direction vector in world coordinates
enum LookAtPriorityType
{
BORING,
INTERESTING, // last known enemy location, dangerous sound location
IMPORTANT, // a danger
CRITICAL, // an active threat to our safety
MANDATORY // nothing can interrupt this look at - two simultaneous look ats with this priority is an error
};
virtual void AimHeadTowards( const Vector &lookAtPos,
LookAtPriorityType priority = BORING,
float duration = 0.0f,
INextBotReply *replyWhenAimed = NULL,
const char *reason = NULL ); // aim the bot's head towards the given goal
virtual void AimHeadTowards( CBaseEntity *subject,
LookAtPriorityType priority = BORING,
float duration = 0.0f,
INextBotReply *replyWhenAimed = NULL,
const char *reason = NULL ); // continually aim the bot's head towards the given subject
virtual bool IsHeadAimingOnTarget( void ) const; // return true if the bot's head has achieved its most recent lookat target
virtual bool IsHeadSteady( void ) const; // return true if head is not rapidly turning to look somewhere else
virtual float GetHeadSteadyDuration( void ) const; // return the duration that the bot's head has not been rotating
virtual float GetHeadAimSubjectLeadTime( void ) const; // return how far into the future we should predict our moving subject's position to aim at when tracking subject look-ats
virtual float GetHeadAimTrackingInterval( void ) const; // return how often we should sample our target's position and velocity to update our aim tracking, to allow realistic slop in tracking
virtual void ClearPendingAimReply( void ) { } // clear out currently pending replyWhenAimed callback
virtual float GetMaxHeadAngularVelocity( void ) const; // return max turn rate of head in degrees/second
enum ActivityType
{
MOTION_CONTROLLED_XY = 0x0001, // XY position and orientation of the bot is driven by the animation.
MOTION_CONTROLLED_Z = 0x0002, // Z position of the bot is driven by the animation.
ACTIVITY_UNINTERRUPTIBLE= 0x0004, // activity can't be changed until animation finishes
ACTIVITY_TRANSITORY = 0x0008, // a short animation that takes over from the underlying animation momentarily, resuming it upon completion
ENTINDEX_PLAYBACK_RATE = 0x0010, // played back at different rates based on entindex
};
/**
* Begin an animation activity, return false if we cant do that right now.
*/
virtual bool StartActivity( Activity act, unsigned int flags = 0 );
virtual int SelectAnimationSequence( Activity act ) const; // given an Activity, select and return a specific animation sequence within it
virtual Activity GetActivity( void ) const; // return currently animating activity
virtual bool IsActivity( Activity act ) const; // return true if currently animating activity matches the given one
virtual bool HasActivityType( unsigned int flags ) const; // return true if currently animating activity has any of the given flags
enum PostureType
{
STAND,
CROUCH,
SIT,
CRAWL,
LIE
};
virtual void SetDesiredPosture( PostureType posture ) { } // request a posture change
virtual PostureType GetDesiredPosture( void ) const; // get posture body is trying to assume
virtual bool IsDesiredPosture( PostureType posture ) const; // return true if body is trying to assume this posture
virtual bool IsInDesiredPosture( void ) const; // return true if body's actual posture matches its desired posture
virtual PostureType GetActualPosture( void ) const; // return body's current actual posture
virtual bool IsActualPosture( PostureType posture ) const; // return true if body is actually in the given posture
virtual bool IsPostureMobile( void ) const; // return true if body's current posture allows it to move around the world
virtual bool IsPostureChanging( void ) const; // return true if body's posture is in the process of changing to new posture
/**
* "Arousal" is the level of excitedness/arousal/anxiety of the body.
* Is changes instantaneously to avoid complex interactions with posture transitions.
*/
enum ArousalType
{
NEUTRAL,
ALERT,
INTENSE
};
virtual void SetArousal( ArousalType arousal ) { } // arousal level change
virtual ArousalType GetArousal( void ) const; // get arousal level
virtual bool IsArousal( ArousalType arousal ) const; // return true if body is at this arousal level
virtual float GetHullWidth( void ) const; // width of bot's collision hull in XY plane
virtual float GetHullHeight( void ) const; // height of bot's current collision hull based on posture
virtual float GetStandHullHeight( void ) const; // height of bot's collision hull when standing
virtual float GetCrouchHullHeight( void ) const; // height of bot's collision hull when crouched
virtual const Vector &GetHullMins( void ) const; // return current collision hull minimums based on actual body posture
virtual const Vector &GetHullMaxs( void ) const; // return current collision hull maximums based on actual body posture
virtual unsigned int GetSolidMask( void ) const; // return the bot's collision mask (hack until we get a general hull trace abstraction here or in the locomotion interface)
virtual unsigned int GetCollisionGroup( void ) const;
};
inline bool IBody::IsHeadSteady( void ) const
{
return true;
}
inline float IBody::GetHeadSteadyDuration( void ) const
{
return 0.0f;
}
inline float IBody::GetHeadAimSubjectLeadTime( void ) const
{
return 0.0f;
}
inline float IBody::GetHeadAimTrackingInterval( void ) const
{
return 0.0f;
}
inline float IBody::GetMaxHeadAngularVelocity( void ) const
{
return 1000.0f;
}
inline bool IBody::StartActivity( Activity act, unsigned int flags )
{
return false;
}
inline int IBody::SelectAnimationSequence( Activity act ) const
{
return 0;
}
inline Activity IBody::GetActivity( void ) const
{
return ACT_INVALID;
}
inline bool IBody::IsActivity( Activity act ) const
{
return false;
}
inline bool IBody::HasActivityType( unsigned int flags ) const
{
return false;
}
inline IBody::PostureType IBody::GetDesiredPosture( void ) const
{
return IBody::STAND;
}
inline bool IBody::IsDesiredPosture( PostureType posture ) const
{
return true;
}
inline bool IBody::IsInDesiredPosture( void ) const
{
return true;
}
inline IBody::PostureType IBody::GetActualPosture( void ) const
{
return IBody::STAND;
}
inline bool IBody::IsActualPosture( PostureType posture ) const
{
return true;
}
inline bool IBody::IsPostureMobile( void ) const
{
return true;
}
inline bool IBody::IsPostureChanging( void ) const
{
return false;
}
inline IBody::ArousalType IBody::GetArousal( void ) const
{
return IBody::NEUTRAL;
}
inline bool IBody::IsArousal( ArousalType arousal ) const
{
return true;
}
//---------------------------------------------------------------------------------------------------------------------------
/**
* Width of bot's collision hull in XY plane
*/
inline float IBody::GetHullWidth( void ) const
{
return 26.0f;
}
//---------------------------------------------------------------------------------------------------------------------------
/**
* Height of bot's current collision hull based on posture
*/
inline float IBody::GetHullHeight( void ) const
{
switch( GetActualPosture() )
{
case LIE:
return 16.0f;
case SIT:
case CROUCH:
return GetCrouchHullHeight();
case STAND:
default:
return GetStandHullHeight();
}
}
//---------------------------------------------------------------------------------------------------------------------------
/**
* Height of bot's collision hull when standing
*/
inline float IBody::GetStandHullHeight( void ) const
{
return 68.0f;
}
//---------------------------------------------------------------------------------------------------------------------------
/**
* Height of bot's collision hull when crouched
*/
inline float IBody::GetCrouchHullHeight( void ) const
{
return 32.0f;
}
//---------------------------------------------------------------------------------------------------------------------------
/**
* Return current collision hull minimums based on actual body posture
*/
inline const Vector &IBody::GetHullMins( void ) const
{
static Vector hullMins;
hullMins.x = -GetHullWidth()/2.0f;
hullMins.y = hullMins.x;
hullMins.z = 0.0f;
return hullMins;
}
//---------------------------------------------------------------------------------------------------------------------------
/**
* Return current collision hull maximums based on actual body posture
*/
inline const Vector &IBody::GetHullMaxs( void ) const
{
static Vector hullMaxs;
hullMaxs.x = GetHullWidth()/2.0f;
hullMaxs.y = hullMaxs.x;
hullMaxs.z = GetHullHeight();
return hullMaxs;
}
inline unsigned int IBody::GetSolidMask( void ) const
{
return MASK_NPCSOLID;
}
inline unsigned int IBody::GetCollisionGroup( void ) const
{
return COLLISION_GROUP_NONE;
}
#endif // _NEXT_BOT_BODY_INTERFACE_H_
@@ -0,0 +1,24 @@
// NextBotComponentInterface.cpp
// Implentation of system methods for NextBot component interface
// Author: Michael Booth, May 2006
//========= Copyright Valve Corporation, All rights reserved. ============//
#include "cbase.h"
#include "NextBotInterface.h"
#include "NextBotComponentInterface.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
INextBotComponent::INextBotComponent( INextBot *bot )
{
m_curInterval = TICK_INTERVAL;
m_lastUpdateTime = 0;
m_bot = bot;
// register this component with the bot
bot->RegisterComponent( this );
}
@@ -0,0 +1,98 @@
// NextBotComponentInterface.h
// Interface for all components
// Author: Michael Booth, May 2006
//========= Copyright Valve Corporation, All rights reserved. ============//
#ifndef _NEXT_BOT_COMPONENT_INTERFACE_H_
#define _NEXT_BOT_COMPONENT_INTERFACE_H_
#include "NextBotEventResponderInterface.h"
class INextBot;
class Path;
class CGameTrace;
class CTakeDamageInfo;
//--------------------------------------------------------------------------------------------------------------------------
/**
* Various processes can invoke a "reply" (ie: callback) via instances of this interface
*/
class INextBotReply
{
public:
virtual void OnSuccess( INextBot *bot ) { } // invoked when process completed successfully
enum FailureReason
{
DENIED,
INTERRUPTED,
FAILED
};
virtual void OnFail( INextBot *bot, FailureReason reason ) { } // invoked when process failed
};
//--------------------------------------------------------------------------------------------------------------------------
/**
* Next Bot component interface
*/
class INextBotComponent : public INextBotEventResponder
{
public:
INextBotComponent( INextBot *bot );
virtual ~INextBotComponent() { }
virtual void Reset( void ) { m_lastUpdateTime = 0; m_curInterval = TICK_INTERVAL; } // reset to initial state
virtual void Update( void ) = 0; // update internal state
virtual void Upkeep( void ) { } // lightweight update guaranteed to occur every server tick
inline bool ComputeUpdateInterval(); // return false is no time has elapsed (interval is zero)
inline float GetUpdateInterval();
virtual INextBot *GetBot( void ) const { return m_bot; }
private:
float m_lastUpdateTime;
float m_curInterval;
friend class INextBot;
INextBot *m_bot;
INextBotComponent *m_nextComponent; // simple linked list of components in the bot
};
inline bool INextBotComponent::ComputeUpdateInterval()
{
if ( m_lastUpdateTime )
{
float interval = gpGlobals->curtime - m_lastUpdateTime;
const float minInterval = 0.0001f;
if ( interval > minInterval )
{
m_curInterval = interval;
m_lastUpdateTime = gpGlobals->curtime;
return true;
}
return false;
}
// First update - assume a reasonable interval.
// We need the very first update to do work, for cases
// where the bot was just created and we need to propagate
// an event to it immediately.
m_curInterval = 0.033f;
m_lastUpdateTime = gpGlobals->curtime - m_curInterval;
return true;
}
inline float INextBotComponent::GetUpdateInterval()
{
return m_curInterval;
}
#endif // _NEXT_BOT_COMPONENT_INTERFACE_H_
@@ -0,0 +1,102 @@
// NextBotContextualQueryInterface.h
// Queries within the context of the bot's current behavior state
// Author: Michael Booth, June 2007
//========= Copyright Valve Corporation, All rights reserved. ============//
#ifndef _NEXT_BOT_CONTEXTUAL_QUERY_H_
#define _NEXT_BOT_CONTEXTUAL_QUERY_H_
class INextBot;
class CBaseEntity;
class CBaseCombatCharacter;
class Path;
class CKnownEntity;
/**
* Since behaviors can have several concurrent actions active, we ask
* the topmost child action first, and if it defers, its parent, and so
* on, until we get a definitive answer.
*/
enum QueryResultType
{
ANSWER_NO,
ANSWER_YES,
ANSWER_UNDEFINED
};
// Can pass this into IContextualQuery::IsHindrance to see if any hindrance is ever possible
#define IS_ANY_HINDRANCE_POSSIBLE ( (CBaseEntity*)0xFFFFFFFF )
//----------------------------------------------------------------------------------------------------------------
/**
* The interface for queries that are dependent on the bot's current behavior state
*/
class IContextualQuery
{
public:
virtual ~IContextualQuery() { }
virtual QueryResultType ShouldPickUp( const INextBot *me, CBaseEntity *item ) const; // if the desired item was available right now, should we pick it up?
virtual QueryResultType ShouldHurry( const INextBot *me ) const; // are we in a hurry?
virtual QueryResultType ShouldRetreat( const INextBot *me ) const; // is it time to retreat?
virtual QueryResultType ShouldAttack( const INextBot *me, const CKnownEntity *them ) const; // should we attack "them"?
virtual QueryResultType IsHindrance( const INextBot *me, CBaseEntity *blocker ) const; // return true if we should wait for 'blocker' that is across our path somewhere up ahead.
virtual Vector SelectTargetPoint( const INextBot *me, const CBaseCombatCharacter *subject ) const; // given a subject, return the world space position we should aim at
/**
* Allow bot to approve of positions game movement tries to put him into.
* This is most useful for bots derived from CBasePlayer that go through
* the player movement system.
*/
virtual QueryResultType IsPositionAllowed( const INextBot *me, const Vector &pos ) const;
virtual const CKnownEntity * SelectMoreDangerousThreat( const INextBot *me,
const CBaseCombatCharacter *subject,
const CKnownEntity *threat1,
const CKnownEntity *threat2 ) const; // return the more dangerous of the two threats to 'subject', or NULL if we have no opinion
};
inline QueryResultType IContextualQuery::ShouldPickUp( const INextBot *me, CBaseEntity *item ) const
{
return ANSWER_UNDEFINED;
}
inline QueryResultType IContextualQuery::ShouldHurry( const INextBot *me ) const
{
return ANSWER_UNDEFINED;
}
inline QueryResultType IContextualQuery::ShouldRetreat( const INextBot *me ) const
{
return ANSWER_UNDEFINED;
}
inline QueryResultType IContextualQuery::ShouldAttack( const INextBot *me, const CKnownEntity *them ) const
{
return ANSWER_UNDEFINED;
}
inline QueryResultType IContextualQuery::IsHindrance( const INextBot *me, CBaseEntity *blocker ) const
{
return ANSWER_UNDEFINED;
}
inline Vector IContextualQuery::SelectTargetPoint( const INextBot *me, const CBaseCombatCharacter *subject ) const
{
return vec3_origin;
}
inline QueryResultType IContextualQuery::IsPositionAllowed( const INextBot *me, const Vector &pos ) const
{
return ANSWER_UNDEFINED;
}
inline const CKnownEntity *IContextualQuery::SelectMoreDangerousThreat( const INextBot *me, const CBaseCombatCharacter *subject, const CKnownEntity *threat1, const CKnownEntity *threat2 ) const
{
return NULL;
}
#endif // _NEXT_BOT_CONTEXTUAL_QUERY_H_
+23
View File
@@ -0,0 +1,23 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
#ifndef NEXTBOT_DEBUG_H
#define NEXTBOT_DEBUG_H
//------------------------------------------------------------------------------
// Debug flags for nextbot
enum NextBotDebugType
{
NEXTBOT_DEBUG_NONE = 0,
NEXTBOT_BEHAVIOR = 0x0001,
NEXTBOT_LOOK_AT = 0x0002,
NEXTBOT_PATH = 0x0004,
NEXTBOT_ANIMATION = 0x0008,
NEXTBOT_LOCOMOTION = 0x0010,
NEXTBOT_VISION = 0x0020,
NEXTBOT_HEARING = 0x0040,
NEXTBOT_EVENTS = 0x0080,
NEXTBOT_ERRORS = 0x0100, // when things go wrong, like being stuck
NEXTBOT_DEBUG_ALL = 0xFFFF
};
#endif
@@ -0,0 +1,550 @@
// NextBotEventResponderInterface.h
// Interface for propagating and responding to events
// Author: Michael Booth, May 2006
//========= Copyright Valve Corporation, All rights reserved. ============//
#ifndef _NEXT_BOT_EVENT_RESPONDER_INTERFACE_H_
#define _NEXT_BOT_EVENT_RESPONDER_INTERFACE_H_
class Path;
class CTakeDamageInfo;
class CBaseEntity;
class CDOTABaseAbility;
struct CSoundParameters;
struct animevent_t;
#include "ai_speech.h"
//--------------------------------------------------------------------------------------------------------------------------
enum MoveToFailureType
{
FAIL_NO_PATH_EXISTS,
FAIL_STUCK,
FAIL_FELL_OFF,
};
//--------------------------------------------------------------------------------------------------------------------------
/**
* Events propagated to/between components.
* To add an event, add its signature here and implement its propagation
* to derived classes via FirstContainedResponder() and NextContainedResponder().
* NOTE: Also add a translator to the Action class in NextBotBehavior.h.
*/
class INextBotEventResponder
{
public:
DECLARE_CLASS_NOBASE( INextBotEventResponder );
virtual ~INextBotEventResponder() { }
// these methods are used by derived classes to define how events propagate
virtual INextBotEventResponder *FirstContainedResponder( void ) const { return NULL; }
virtual INextBotEventResponder *NextContainedResponder( INextBotEventResponder *current ) const { return NULL; }
//
// Events. All events must be 'extended' by calling the derived class explicitly to ensure propagation.
// Each event must implement its propagation in this interface class.
//
virtual void OnLeaveGround( CBaseEntity *ground ); // invoked when bot leaves ground for any reason
virtual void OnLandOnGround( CBaseEntity *ground ); // invoked when bot lands on the ground after being in the air
virtual void OnContact( CBaseEntity *other, CGameTrace *result = NULL ); // invoked when bot touches 'other'
virtual void OnMoveToSuccess( const Path *path ); // invoked when a bot reaches the end of the given Path
virtual void OnMoveToFailure( const Path *path, MoveToFailureType reason ); // invoked when a bot fails to reach the end of the given Path
virtual void OnStuck( void ); // invoked when bot becomes stuck while trying to move
virtual void OnUnStuck( void ); // invoked when a previously stuck bot becomes un-stuck and can again move
virtual void OnPostureChanged( void ); // when bot has assumed new posture (query IBody for posture)
virtual void OnAnimationActivityComplete( int activity ); // when animation activity has finished playing
virtual void OnAnimationActivityInterrupted( int activity );// when animation activity was replaced by another animation
virtual void OnAnimationEvent( animevent_t *event ); // when a QC-file animation event is triggered by the current animation sequence
virtual void OnIgnite( void ); // when bot starts to burn
virtual void OnInjured( const CTakeDamageInfo &info ); // when bot is damaged by something
virtual void OnKilled( const CTakeDamageInfo &info ); // when the bot's health reaches zero
virtual void OnOtherKilled( CBaseCombatCharacter *victim, const CTakeDamageInfo &info ); // when someone else dies
virtual void OnSight( CBaseEntity *subject ); // when subject initially enters bot's visual awareness
virtual void OnLostSight( CBaseEntity *subject ); // when subject leaves enters bot's visual awareness
virtual void OnSound( CBaseEntity *source, const Vector &pos, KeyValues *keys ); // when an entity emits a sound. "pos" is world coordinates of sound. "keys" are from sound's GameData
virtual void OnSpokeConcept( CBaseCombatCharacter *who, AIConcept_t concept, AI_Response *response ); // when an Actor speaks a concept
virtual void OnWeaponFired( CBaseCombatCharacter *whoFired, CBaseCombatWeapon *weapon ); // when someone fires a weapon
virtual void OnNavAreaChanged( CNavArea *newArea, CNavArea *oldArea ); // when bot enters a new navigation area
virtual void OnModelChanged( void ); // when the entity's model has been changed
virtual void OnPickUp( CBaseEntity *item, CBaseCombatCharacter *giver ); // when something is added to our inventory
virtual void OnDrop( CBaseEntity *item ); // when something is removed from our inventory
virtual void OnActorEmoted( CBaseCombatCharacter *emoter, int emote ); // when "emoter" does an "emote" (ie: manual voice command, etc)
virtual void OnCommandAttack( CBaseEntity *victim ); // attack the given entity
virtual void OnCommandApproach( const Vector &pos, float range = 0.0f ); // move to within range of the given position
virtual void OnCommandApproach( CBaseEntity *goal ); // follow the given leader
virtual void OnCommandRetreat( CBaseEntity *threat, float range = 0.0f ); // retreat from the threat at least range units away (0 == infinite)
virtual void OnCommandPause( float duration = 0.0f ); // pause for the given duration (0 == forever)
virtual void OnCommandResume( void ); // resume after a pause
virtual void OnCommandString( const char *command ); // for debugging: respond to an arbitrary string representing a generalized command
virtual void OnShoved( CBaseEntity *pusher ); // 'pusher' has shoved me
virtual void OnBlinded( CBaseEntity *blinder ); // 'blinder' has blinded me with a flash of light
virtual void OnTerritoryContested( int territoryID ); // territory has been invaded and is changing ownership
virtual void OnTerritoryCaptured( int territoryID ); // we have captured enemy territory
virtual void OnTerritoryLost( int territoryID ); // we have lost territory to the enemy
virtual void OnWin( void );
virtual void OnLose( void );
#ifdef DOTA_SERVER_DLL
virtual void OnCommandMoveTo( const Vector &pos );
virtual void OnCommandMoveToAggressive( const Vector &pos );
virtual void OnCommandAttack( CBaseEntity *victim, bool bDeny );
virtual void OnCastAbilityNoTarget( CDOTABaseAbility *ability );
virtual void OnCastAbilityOnPosition( CDOTABaseAbility *ability, const Vector &pos );
virtual void OnCastAbilityOnTarget( CDOTABaseAbility *ability, CBaseEntity *target );
virtual void OnDropItem( const Vector &pos, CBaseEntity *item );
virtual void OnPickupItem( CBaseEntity *item );
virtual void OnPickupRune( CBaseEntity *item );
virtual void OnStop();
virtual void OnFriendThreatened( CBaseEntity *friendly, CBaseEntity *threat );
virtual void OnCancelAttack( CBaseEntity *pTarget );
virtual void OnDominated();
virtual void OnWarped( Vector vStartPos );
#endif
};
inline void INextBotEventResponder::OnLeaveGround( CBaseEntity *ground )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnLeaveGround( ground );
}
}
inline void INextBotEventResponder::OnLandOnGround( CBaseEntity *ground )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnLandOnGround( ground );
}
}
inline void INextBotEventResponder::OnContact( CBaseEntity *other, CGameTrace *result )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnContact( other, result );
}
}
inline void INextBotEventResponder::OnMoveToSuccess( const Path *path )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnMoveToSuccess( path );
}
}
inline void INextBotEventResponder::OnMoveToFailure( const Path *path, MoveToFailureType reason )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnMoveToFailure( path, reason );
}
}
inline void INextBotEventResponder::OnStuck( void )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnStuck();
}
}
inline void INextBotEventResponder::OnUnStuck( void )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnUnStuck();
}
}
inline void INextBotEventResponder::OnPostureChanged( void )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnPostureChanged();
}
}
inline void INextBotEventResponder::OnAnimationActivityComplete( int activity )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnAnimationActivityComplete( activity );
}
}
inline void INextBotEventResponder::OnAnimationActivityInterrupted( int activity )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnAnimationActivityInterrupted( activity );
}
}
inline void INextBotEventResponder::OnAnimationEvent( animevent_t *event )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnAnimationEvent( event );
}
}
inline void INextBotEventResponder::OnIgnite( void )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnIgnite();
}
}
inline void INextBotEventResponder::OnInjured( const CTakeDamageInfo &info )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnInjured( info );
}
}
inline void INextBotEventResponder::OnKilled( const CTakeDamageInfo &info )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnKilled( info );
}
}
inline void INextBotEventResponder::OnOtherKilled( CBaseCombatCharacter *victim, const CTakeDamageInfo &info )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnOtherKilled( victim, info );
}
}
inline void INextBotEventResponder::OnSight( CBaseEntity *subject )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnSight( subject );
}
}
inline void INextBotEventResponder::OnLostSight( CBaseEntity *subject )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnLostSight( subject );
}
}
inline void INextBotEventResponder::OnSound( CBaseEntity *source, const Vector &pos, KeyValues *keys )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnSound( source, pos, keys );
}
}
inline void INextBotEventResponder::OnSpokeConcept( CBaseCombatCharacter *who, AIConcept_t concept, AI_Response *response )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnSpokeConcept( who, concept, response );
}
}
inline void INextBotEventResponder::OnWeaponFired( CBaseCombatCharacter *whoFired, CBaseCombatWeapon *weapon )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnWeaponFired( whoFired, weapon );
}
}
inline void INextBotEventResponder::OnNavAreaChanged( CNavArea *newArea, CNavArea *oldArea )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnNavAreaChanged( newArea, oldArea );
}
}
inline void INextBotEventResponder::OnModelChanged( void )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnModelChanged();
}
}
inline void INextBotEventResponder::OnPickUp( CBaseEntity *item, CBaseCombatCharacter *giver )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnPickUp( item, giver );
}
}
inline void INextBotEventResponder::OnDrop( CBaseEntity *item )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnDrop( item );
}
}
inline void INextBotEventResponder::OnActorEmoted( CBaseCombatCharacter *emoter, int emote )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnActorEmoted( emoter, emote );
}
}
inline void INextBotEventResponder::OnShoved( CBaseEntity *pusher )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnShoved( pusher );
}
}
inline void INextBotEventResponder::OnBlinded( CBaseEntity *blinder )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnBlinded( blinder );
}
}
inline void INextBotEventResponder::OnCommandAttack( CBaseEntity *victim )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnCommandAttack( victim );
}
}
inline void INextBotEventResponder::OnCommandApproach( const Vector &pos, float range )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnCommandApproach( pos, range );
}
}
inline void INextBotEventResponder::OnCommandApproach( CBaseEntity *goal )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnCommandApproach( goal );
}
}
inline void INextBotEventResponder::OnCommandRetreat( CBaseEntity *threat, float range )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnCommandRetreat( threat, range );
}
}
inline void INextBotEventResponder::OnCommandPause( float duration )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnCommandPause( duration );
}
}
inline void INextBotEventResponder::OnCommandResume( void )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnCommandResume();
}
}
inline void INextBotEventResponder::OnCommandString( const char *command )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnCommandString( command );
}
}
inline void INextBotEventResponder::OnTerritoryContested( int territoryID )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnTerritoryContested( territoryID );
}
}
inline void INextBotEventResponder::OnTerritoryCaptured( int territoryID )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnTerritoryCaptured( territoryID );
}
}
inline void INextBotEventResponder::OnTerritoryLost( int territoryID )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnTerritoryLost( territoryID );
}
}
inline void INextBotEventResponder::OnWin( void )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnWin();
}
}
inline void INextBotEventResponder::OnLose( void )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnLose();
}
}
#ifdef DOTA_SERVER_DLL
inline void INextBotEventResponder::OnCommandMoveTo( const Vector &pos )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnCommandMoveTo( pos );
}
}
inline void INextBotEventResponder::OnCommandMoveToAggressive( const Vector &pos )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnCommandMoveToAggressive( pos );
}
}
inline void INextBotEventResponder::OnCommandAttack( CBaseEntity *victim, bool bDeny )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnCommandAttack( victim, bDeny );
}
}
inline void INextBotEventResponder::OnCastAbilityNoTarget( CDOTABaseAbility *ability )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnCastAbilityNoTarget( ability );
}
}
inline void INextBotEventResponder::OnCastAbilityOnPosition( CDOTABaseAbility *ability, const Vector &pos )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnCastAbilityOnPosition( ability, pos );
}
}
inline void INextBotEventResponder::OnCastAbilityOnTarget( CDOTABaseAbility *ability, CBaseEntity *target )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnCastAbilityOnTarget( ability, target );
}
}
inline void INextBotEventResponder::OnDropItem( const Vector &pos, CBaseEntity *item )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnDropItem( pos, item );
}
}
inline void INextBotEventResponder::OnPickupItem( CBaseEntity *item )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnPickupItem( item );
}
}
inline void INextBotEventResponder::OnPickupRune( CBaseEntity *item )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnPickupRune( item );
}
}
inline void INextBotEventResponder::OnStop()
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnStop();
}
}
inline void INextBotEventResponder::OnFriendThreatened( CBaseEntity *friendly, CBaseEntity *threat )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnFriendThreatened( friendly, threat );
}
}
inline void INextBotEventResponder::OnCancelAttack( CBaseEntity *pTarget )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnCancelAttack( pTarget );
}
}
inline void INextBotEventResponder::OnDominated()
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnDominated();
}
}
inline void INextBotEventResponder::OnWarped( Vector vStartPos )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnWarped( vStartPos );
}
}
#endif
#endif // _NEXT_BOT_EVENT_RESPONDER_INTERFACE_H_
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,274 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// NextBotGroundLocomotion.h
// Basic ground-based movement for NextBotCombatCharacters
// Author: Michael Booth, February 2009
// Note: This is a refactoring of ZombieBotLocomotion from L4D
#ifndef NEXT_BOT_GROUND_LOCOMOTION_H
#define NEXT_BOT_GROUND_LOCOMOTION_H
#include "NextBotLocomotionInterface.h"
#include "nav_mesh.h"
class NextBotCombatCharacter;
//----------------------------------------------------------------------------------------------------------------
/**
* Basic ground-based movement for NextBotCombatCharacters.
* This locomotor resolves collisions and assumes a ground-based bot under the influence of gravity.
*/
class NextBotGroundLocomotion : public ILocomotion
{
public:
DECLARE_CLASS( NextBotGroundLocomotion, ILocomotion );
NextBotGroundLocomotion( INextBot *bot );
virtual ~NextBotGroundLocomotion();
virtual void Reset( void ); // reset locomotor to initial state
virtual void Update( void ); // update internal state
virtual void Approach( const Vector &pos, float goalWeight = 1.0f ); // move directly towards the given position
virtual void DriveTo( const Vector &pos ); // Move the bot to the precise given position immediately,
virtual bool ClimbUpToLedge( const Vector &landingGoal, const Vector &landingForward, const CBaseEntity *obstacle ); // initiate a jump to an adjacent high ledge, return false if climb can't start
virtual void JumpAcrossGap( const Vector &landingGoal, const Vector &landingForward ); // initiate a jump across an empty volume of space to far side
virtual void Jump( void ); // initiate a simple undirected jump in the air
virtual bool IsClimbingOrJumping( void ) const; // is jumping in any form
virtual bool IsClimbingUpToLedge( void ) const; // is climbing up to a high ledge
virtual bool IsJumpingAcrossGap( void ) const; // is jumping across a gap to the far side
virtual void Run( void ); // set desired movement speed to running
virtual void Walk( void ); // set desired movement speed to walking
virtual void Stop( void ); // set desired movement speed to stopped
virtual bool IsRunning( void ) const;
virtual void SetDesiredSpeed( float speed ); // set desired speed for locomotor movement
virtual float GetDesiredSpeed( void ) const; // returns the current desired speed
virtual float GetSpeedLimit( void ) const; // get maximum speed bot can reach, regardless of desired speed
virtual bool IsOnGround( void ) const; // return true if standing on something
virtual void OnLeaveGround( CBaseEntity *ground ); // invoked when bot leaves ground for any reason
virtual void OnLandOnGround( CBaseEntity *ground ); // invoked when bot lands on the ground after being in the air
virtual CBaseEntity *GetGround( void ) const; // return the current ground entity or NULL if not on the ground
virtual const Vector &GetGroundNormal( void ) const;// surface normal of the ground we are in contact with
virtual void ClimbLadder( const CNavLadder *ladder, const CNavArea *dismountGoal ); // climb the given ladder to the top and dismount
virtual void DescendLadder( const CNavLadder *ladder, const CNavArea *dismountGoal ); // descend the given ladder to the bottom and dismount
virtual bool IsUsingLadder( void ) const;
virtual bool IsAscendingOrDescendingLadder( void ) const; // we are actually on the ladder right now, either climbing up or down
virtual void FaceTowards( const Vector &target ); // rotate body to face towards "target"
virtual void SetDesiredLean( const QAngle &lean );
virtual const QAngle &GetDesiredLean( void ) const;
virtual const Vector &GetFeet( void ) const; // return position of "feet" - the driving point where the bot contacts the ground
virtual float GetStepHeight( void ) const; // if delta Z is greater than this, we have to jump to get up
virtual float GetMaxJumpHeight( void ) const; // return maximum height of a jump
virtual float GetDeathDropHeight( void ) const; // distance at which we will die if we fall
virtual float GetRunSpeed( void ) const; // get maximum running speed
virtual float GetWalkSpeed( void ) const; // get maximum walking speed
virtual float GetMaxAcceleration( void ) const; // return maximum acceleration of locomotor
virtual float GetMaxDeceleration( void ) const; // return maximum deceleration of locomotor
virtual const Vector &GetAcceleration( void ) const; // return current world space acceleration
virtual void SetAcceleration( const Vector &accel ); // set world space acceleration
virtual const Vector &GetVelocity( void ) const; // return current world space velocity
virtual void SetVelocity( const Vector &vel ); // set world space velocity
virtual void OnMoveToSuccess( const Path *path ); // invoked when an bot reaches its MoveTo goal
virtual void OnMoveToFailure( const Path *path, MoveToFailureType reason ); // invoked when an bot fails to reach a MoveTo goal
private:
void UpdatePosition( const Vector &newPos ); // move to newPos, resolving any collisions along the way
void UpdateGroundConstraint( void ); // keep ground solid
Vector ResolveCollisionV0( Vector from, Vector to, int recursionLimit );
Vector ResolveZombieCollisions( const Vector &pos ); // push away zombies that are interpenetrating
Vector ResolveCollision( const Vector &from, const Vector &to, int recursionLimit ); // check for collisions along move
bool DetectCollision( trace_t *pTrace, int &nDestructionAllowed, const Vector &from, const Vector &to, const Vector &vecMins, const Vector &vecMaxs );
void ApplyAccumulatedApproach( void );
bool DidJustJump( void ) const; // return true if we just started a jump
bool TraverseLadder( void ); // return true if we are climbing a ladder
virtual float GetGravity( void ) const; // return gravity force acting on bot
virtual float GetFrictionForward( void ) const; // return magnitude of forward friction
virtual float GetFrictionSideways( void ) const; // return magnitude of lateral friction
virtual float GetMaxYawRate( void ) const; // return max rate of yaw rotation
private:
NextBotCombatCharacter *m_nextBot;
Vector m_priorPos; // last update's position
Vector m_lastValidPos; // last valid position (not interpenetrating)
Vector m_acceleration;
Vector m_velocity;
float m_desiredSpeed; // speed bot wants to be moving
float m_actualSpeed; // actual speed bot is moving
float m_maxRunSpeed;
float m_forwardLean;
float m_sideLean;
QAngle m_desiredLean;
bool m_isJumping; // if true, we have jumped and have not yet hit the ground
bool m_isJumpingAcrossGap; // if true, we have jumped across a gap and have not yet hit the ground
EHANDLE m_ground; // have to manage this ourselves, since MOVETYPE_CUSTOM always NULLs out GetGroundEntity()
Vector m_groundNormal; // surface normal of the ground we are in contact with
bool m_isClimbingUpToLedge; // true if we are jumping up to an adjacent ledge
Vector m_ledgeJumpGoalPos;
bool m_isUsingFullFeetTrace; // true if we're in the air and tracing the lowest StepHeight in ResolveCollision
const CNavLadder *m_ladder; // ladder we are currently climbing/descending
const CNavArea *m_ladderDismountGoal; // the area we enter when finished with our ladder move
bool m_isGoingUpLadder; // if false, we're going down
CountdownTimer m_inhibitObstacleAvoidanceTimer; // when active, turn off path following feelers
CountdownTimer m_wiggleTimer; // for wiggling
NavRelativeDirType m_wiggleDirection;
mutable Vector m_eyePos; // for use with GetEyes(), etc.
Vector m_moveVector; // the direction of our motion in XY plane
float m_moveYaw; // global yaw of movement direction
Vector m_accumApproachVectors; // weighted sum of Approach() calls since last update
float m_accumApproachWeights;
bool m_bRecomputePostureOnCollision;
CountdownTimer m_ignorePhysicsPropTimer; // if active, don't collide with physics props (because we got stuck in one)
EHANDLE m_ignorePhysicsProp; // which prop to ignore
};
inline float NextBotGroundLocomotion::GetGravity( void ) const
{
return 1000.0f;
}
inline float NextBotGroundLocomotion::GetFrictionForward( void ) const
{
return 0.0f;
}
inline float NextBotGroundLocomotion::GetFrictionSideways( void ) const
{
return 3.0f;
}
inline float NextBotGroundLocomotion::GetMaxYawRate( void ) const
{
return 250.0f;
}
inline CBaseEntity *NextBotGroundLocomotion::GetGround( void ) const
{
return m_ground;
}
inline const Vector &NextBotGroundLocomotion::GetGroundNormal( void ) const
{
return m_groundNormal;
}
inline void NextBotGroundLocomotion::SetDesiredLean( const QAngle &lean )
{
m_desiredLean = lean;
}
inline const QAngle &NextBotGroundLocomotion::GetDesiredLean( void ) const
{
return m_desiredLean;
}
inline void NextBotGroundLocomotion::SetDesiredSpeed( float speed )
{
m_desiredSpeed = speed;
}
inline float NextBotGroundLocomotion::GetDesiredSpeed( void ) const
{
return m_desiredSpeed;
}
inline bool NextBotGroundLocomotion::IsClimbingOrJumping( void ) const
{
return m_isJumping;
}
inline bool NextBotGroundLocomotion::IsClimbingUpToLedge( void ) const
{
return m_isClimbingUpToLedge;
}
inline bool NextBotGroundLocomotion::IsJumpingAcrossGap( void ) const
{
return m_isJumpingAcrossGap;
}
inline bool NextBotGroundLocomotion::IsRunning( void ) const
{
/// @todo Rethink interface to distinguish actual state vs desired state (do we want to be running, or are we actually at running speed right now)
return m_actualSpeed > 0.9f * GetRunSpeed();
}
inline float NextBotGroundLocomotion::GetStepHeight( void ) const
{
return 18.0f;
}
inline float NextBotGroundLocomotion::GetMaxJumpHeight( void ) const
{
return 180.0f; // 120.0f; // 84.0f; // 58.0f;
}
inline float NextBotGroundLocomotion::GetDeathDropHeight( void ) const
{
return 200.0f;
}
inline float NextBotGroundLocomotion::GetRunSpeed( void ) const
{
return 150.0f;
}
inline float NextBotGroundLocomotion::GetWalkSpeed( void ) const
{
return 75.0f;
}
inline float NextBotGroundLocomotion::GetMaxAcceleration( void ) const
{
return 500.0f;
}
inline float NextBotGroundLocomotion::GetMaxDeceleration( void ) const
{
return 500.0f;
}
#endif // NEXT_BOT_GROUND_LOCOMOTION_H
@@ -0,0 +1,34 @@
// NextBotHearingInterface.h
// Interface for auditory queries of a bot
// Author: Michael Booth, April 2005
//========= Copyright Valve Corporation, All rights reserved. ============//
#ifndef _NEXT_BOT_HEARING_INTERFACE_H_
#define _NEXT_BOT_HEARING_INTERFACE_H_
#include "NextBotComponentInterface.h"
//----------------------------------------------------------------------------------------------------------------
/**
* The interface for hearing sounds
*/
class IHearing : public INextBotComponent
{
public:
IHearing( INextBot *bot ) : INextBotComponent( bot ) { }
virtual ~IHearing() { }
virtual void Reset( void ); // reset to initial state
virtual void Update( void ); // update internal state
virtual float GetTimeSinceHeard( int team ) const; // return time since we heard any member of the given team
virtual CBaseEntity *GetClosestRecognized( int team = TEAM_ANY ) const; // return the closest recognized entity
virtual int GetRecognizedCount( int team, float rangeLimit = -1.0f ) const; // return the number of actors on the given team visible to us closer than rangeLimit
virtual float GetMaxHearingRange( void ) const; // return maximum distance we can hear
virtual float GetMinRecognizeTime( void ) const; // return HEARING reaction time
};
#endif // _NEXT_BOT_HEARING_INTERFACE_H_
@@ -0,0 +1,91 @@
// NextBotIntentionInterface.cpp
// Interface for intentional thinking
// Author: Michael Booth, November 2007
//========= Copyright Valve Corporation, All rights reserved. ============//
#include "cbase.h"
#include "NextBotInterface.h"
#include "NextBotIntentionInterface.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//------------------------------------------------------------------------------------------------------------------------
/**
* Given a subject, return the world space position we should aim at
*/
Vector IIntention::SelectTargetPoint( const INextBot *me, const CBaseCombatCharacter *subject ) const
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
const IContextualQuery *query = dynamic_cast< const IContextualQuery * >( sub );
if ( query )
{
// return the response of the first responder that gives a definitive answer
Vector result = query->SelectTargetPoint( me, subject );
if ( result != vec3_origin )
{
return result;
}
}
}
// no answer, use a reasonable position
Vector threatMins, threatMaxs;
subject->CollisionProp()->WorldSpaceAABB( &threatMins, &threatMaxs );
Vector targetPoint = subject->GetAbsOrigin();
targetPoint.z += 0.7f * ( threatMaxs.z - threatMins.z );
return targetPoint;
}
//------------------------------------------------------------------------------------------------------------------------
/**
* Given two threats, decide which one is more dangerous
*/
const CKnownEntity *IIntention::SelectMoreDangerousThreat( const INextBot *me, const CBaseCombatCharacter *subject, const CKnownEntity *threat1, const CKnownEntity *threat2 ) const
{
if ( !threat1 || threat1->IsObsolete() )
{
if ( threat2 && !threat2->IsObsolete() )
return threat2;
return NULL;
}
else if ( !threat2 || threat2->IsObsolete() )
{
return threat1;
}
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
const IContextualQuery *query = dynamic_cast< const IContextualQuery * >( sub );
if ( query )
{
// return the response of the first responder that gives a definitive answer
const CKnownEntity *result = query->SelectMoreDangerousThreat( me, subject, threat1, threat2 );
if ( result )
{
return result;
}
}
}
// no specific decision was made - return closest threat as most dangerous
float range1 = ( subject->GetAbsOrigin() - threat1->GetLastKnownPosition() ).LengthSqr();
float range2 = ( subject->GetAbsOrigin() - threat2->GetLastKnownPosition() ).LengthSqr();
if ( range1 < range2 )
{
return threat1;
}
return threat2;
}
@@ -0,0 +1,211 @@
// NextBotIntentionInterface.h
// Interface for intentional thinking
// Author: Michael Booth, April 2005
//========= Copyright Valve Corporation, All rights reserved. ============//
#ifndef _NEXT_BOT_INTENTION_INTERFACE_H_
#define _NEXT_BOT_INTENTION_INTERFACE_H_
#include "NextBotComponentInterface.h"
#include "NextBotContextualQueryInterface.h"
class INextBot;
//
// Insert this macro in your INextBot-derived class declaration to
// create a IIntention-derived class that handles the bookkeeping
// of instantiating a Behavior with an initial Action and updating it.
//
#define DECLARE_INTENTION_INTERFACE( Actor ) \
\
class Actor##Intention : public IIntention \
{ \
public: \
Actor##Intention( Actor *me ); \
virtual ~Actor##Intention(); \
virtual void Reset( void ); \
virtual void Update( void ); \
virtual INextBotEventResponder *FirstContainedResponder( void ) const { return m_behavior; } \
virtual INextBotEventResponder *NextContainedResponder( INextBotEventResponder *current ) const { return NULL; } \
private: \
Behavior< Actor > *m_behavior; \
}; \
\
public: virtual IIntention *GetIntentionInterface( void ) const { return m_intention; } \
private: Actor##Intention *m_intention; \
public:
//
// Use this macro to create the implementation code for the IIntention-derived class
// declared above. Since this requires InitialAction, it must occur after
// that Action has been declared, so it can be new'd here.
//
#define IMPLEMENT_INTENTION_INTERFACE( Actor, InitialAction ) \
Actor::Actor##Intention::Actor##Intention( Actor *me ) : IIntention( me ) { m_behavior = new Behavior< Actor >( new InitialAction ); } \
Actor::Actor##Intention::~Actor##Intention() { delete m_behavior; } \
void Actor::Actor##Intention::Reset( void ) { delete m_behavior; m_behavior = new Behavior< Actor >( new InitialAction ); } \
void Actor::Actor##Intention::Update( void ) { m_behavior->Update( static_cast< Actor * >( GetBot() ), GetUpdateInterval() ); }
//
// Use this macro in the constructor of your bot to allocate the IIntention-derived class
//
#define ALLOCATE_INTENTION_INTERFACE( Actor ) { m_intention = new Actor##Intention( this ); }
//
// Use this macro in the destructor of your bot to deallocate the IIntention-derived class
//
#define DEALLOCATE_INTENTION_INTERFACE { if ( m_intention ) delete m_intention; }
//----------------------------------------------------------------------------------------------------------------
/**
* The interface for intentional thinking.
* The assumption is that this is a container for one or more concurrent Behaviors.
* The "primary" Behavior is the FirstContainedResponder, and so on.
* IContextualQuery requests are prioritized in contained responder order, such that the first responder
* that returns a definitive answer is accepted. WITHIN a given responder (ie: a Behavior), the deepest child
* Behavior in the active stack is asked first, then its parent, and so on, allowing the most specific active
* Behavior to override the query responses of its more general parent Behaviors.
*/
class IIntention : public INextBotComponent, public IContextualQuery
{
public:
IIntention( INextBot *bot ) : INextBotComponent( bot ) { }
virtual ~IIntention() { }
virtual void Reset( void ) { INextBotComponent::Reset(); } // reset to initial state
virtual void Update( void ) { } // update internal state
// IContextualQuery propagation --------------------------------
virtual QueryResultType ShouldPickUp( const INextBot *me, CBaseEntity *item ) const; // if the desired item was available right now, should we pick it up?
virtual QueryResultType ShouldHurry( const INextBot *me ) const; // are we in a hurry?
virtual QueryResultType ShouldRetreat( const INextBot *me ) const; // is it time to retreat?
virtual QueryResultType ShouldAttack( const INextBot *me, const CKnownEntity *them ) const; // should we attack "them"?
virtual QueryResultType IsHindrance( const INextBot *me, CBaseEntity *blocker ) const; // return true if we should wait for 'blocker' that is across our path somewhere up ahead.
virtual Vector SelectTargetPoint( const INextBot *me, const CBaseCombatCharacter *subject ) const; // given a subject, return the world space position we should aim at
virtual QueryResultType IsPositionAllowed( const INextBot *me, const Vector &pos ) const; // is the a place we can be?
virtual const CKnownEntity * SelectMoreDangerousThreat( const INextBot *me,
const CBaseCombatCharacter *subject, // the subject of the danger
const CKnownEntity *threat1,
const CKnownEntity *threat2 ) const; // return the more dangerous of the two threats, or NULL if we have no opinion
// NOTE: As further queries are added, update the Behavior class to propagate them
};
inline QueryResultType IIntention::ShouldPickUp( const INextBot *me, CBaseEntity *item ) const
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
const IContextualQuery *query = dynamic_cast< const IContextualQuery * >( sub );
if ( query )
{
// return the response of the first responder that gives a definitive answer
QueryResultType result = query->ShouldPickUp( me, item );
if ( result != ANSWER_UNDEFINED )
{
return result;
}
}
}
return ANSWER_UNDEFINED;
}
inline QueryResultType IIntention::ShouldHurry( const INextBot *me ) const
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
const IContextualQuery *query = dynamic_cast< const IContextualQuery * >( sub );
if ( query )
{
// return the response of the first responder that gives a definitive answer
QueryResultType result = query->ShouldHurry( me );
if ( result != ANSWER_UNDEFINED )
{
return result;
}
}
}
return ANSWER_UNDEFINED;
}
inline QueryResultType IIntention::ShouldRetreat( const INextBot *me ) const
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
const IContextualQuery *query = dynamic_cast< const IContextualQuery * >( sub );
if ( query )
{
// return the response of the first responder that gives a definitive answer
QueryResultType result = query->ShouldRetreat( me );
if ( result != ANSWER_UNDEFINED )
{
return result;
}
}
}
return ANSWER_UNDEFINED;
}
inline QueryResultType IIntention::ShouldAttack( const INextBot *me, const CKnownEntity *them ) const
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
const IContextualQuery *query = dynamic_cast< const IContextualQuery * >( sub );
if ( query )
{
// return the response of the first responder that gives a definitive answer
QueryResultType result = query->ShouldAttack( me, them );
if ( result != ANSWER_UNDEFINED )
{
return result;
}
}
}
return ANSWER_UNDEFINED;
}
inline QueryResultType IIntention::IsHindrance( const INextBot *me, CBaseEntity *blocker ) const
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
const IContextualQuery *query = dynamic_cast< const IContextualQuery * >( sub );
if ( query )
{
// return the response of the first responder that gives a definitive answer
QueryResultType result = query->IsHindrance( me, blocker );
if ( result != ANSWER_UNDEFINED )
{
return result;
}
}
}
return ANSWER_UNDEFINED;
}
inline QueryResultType IIntention::IsPositionAllowed( const INextBot *me, const Vector &pos ) const
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
const IContextualQuery *query = dynamic_cast< const IContextualQuery * >( sub );
if ( query )
{
// return the response of the first responder that gives a definitive answer
QueryResultType result = query->IsPositionAllowed( me, pos );
if ( result != ANSWER_UNDEFINED )
{
return result;
}
}
}
return ANSWER_UNDEFINED;
}
#endif // _NEXT_BOT_INTENTION_INTERFACE_H_
+537
View File
@@ -0,0 +1,537 @@
// NextBotInterface.cpp
// Implentation of system methods for NextBot interface
// Author: Michael Booth, May 2006
//========= Copyright Valve Corporation, All rights reserved. ============//
#include "cbase.h"
#include "props.h"
#include "fmtstr.h"
#include "team.h"
#include "NextBotInterface.h"
#include "NextBotBodyInterface.h"
#include "NextBotManager.h"
#include "tier0/vprof.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
// development only, off by default for 360
ConVar NextBotDebugHistory( "nb_debug_history", IsX360() ? "0" : "1", FCVAR_CHEAT, "If true, each bot keeps a history of debug output in memory" );
//----------------------------------------------------------------------------------------------------------------
INextBot::INextBot( void ) : m_debugHistory( MAX_NEXTBOT_DEBUG_HISTORY, 0 ) // CUtlVector: grow to max length, alloc 0 initially
{
m_tickLastUpdate = -999;
m_id = -1;
m_componentList = NULL;
m_debugDisplayLine = 0;
m_immobileTimer.Invalidate();
m_immobileCheckTimer.Invalidate();
m_immobileAnchor = vec3_origin;
m_currentPath = NULL;
// register with the manager
m_id = TheNextBots().Register( this );
}
//----------------------------------------------------------------------------------------------------------------
INextBot::~INextBot()
{
ResetDebugHistory();
// tell the manager we're gone
TheNextBots().UnRegister( this );
// delete Intention first, since destruction of Actions may access other components
if ( m_baseIntention )
delete m_baseIntention;
if ( m_baseLocomotion )
delete m_baseLocomotion;
if ( m_baseBody )
delete m_baseBody;
if ( m_baseVision )
delete m_baseVision;
}
//----------------------------------------------------------------------------------------------------------------
void INextBot::Reset( void )
{
m_tickLastUpdate = -999;
m_debugType = 0;
m_debugDisplayLine = 0;
m_immobileTimer.Invalidate();
m_immobileCheckTimer.Invalidate();
m_immobileAnchor = vec3_origin;
for( INextBotComponent *comp = m_componentList; comp; comp = comp->m_nextComponent )
{
comp->Reset();
}
}
//----------------------------------------------------------------------------------------------------------------
void INextBot::ResetDebugHistory( void )
{
for ( int i=0; i<m_debugHistory.Count(); ++i )
{
delete m_debugHistory[i];
}
m_debugHistory.RemoveAll();
}
//----------------------------------------------------------------------------------------------------------------
bool INextBot::BeginUpdate()
{
if ( TheNextBots().ShouldUpdate( this ) )
{
TheNextBots().NotifyBeginUpdate( this );
return true;
}
return false;
}
//----------------------------------------------------------------------------------------------------------------
void INextBot::EndUpdate( void )
{
TheNextBots().NotifyEndUpdate( this );
}
//----------------------------------------------------------------------------------------------------------------
void INextBot::Update( void )
{
VPROF_BUDGET( "INextBot::Update", "NextBot" );
m_debugDisplayLine = 0;
if ( IsDebugging( NEXTBOT_DEBUG_ALL ) )
{
CFmtStr msg;
DisplayDebugText( msg.sprintf( "#%d", GetEntity()->entindex() ) );
}
UpdateImmobileStatus();
// update all components
for( INextBotComponent *comp = m_componentList; comp; comp = comp->m_nextComponent )
{
if ( comp->ComputeUpdateInterval() )
{
comp->Update();
}
}
}
//----------------------------------------------------------------------------------------------------------------
void INextBot::Upkeep( void )
{
VPROF_BUDGET( "INextBot::Upkeep", "NextBot" );
// do upkeep for all components
for( INextBotComponent *comp = m_componentList; comp; comp = comp->m_nextComponent )
{
comp->Upkeep();
}
}
//----------------------------------------------------------------------------------------------------------------
bool INextBot::SetPosition( const Vector &pos )
{
IBody *body = GetBodyInterface();
if (body)
{
return body->SetPosition( pos );
}
// fall back to setting raw entity position
GetEntity()->SetAbsOrigin( pos );
return true;
}
//----------------------------------------------------------------------------------------------------------------
const Vector &INextBot::GetPosition( void ) const
{
return const_cast< INextBot * >( this )->GetEntity()->GetAbsOrigin();
}
//----------------------------------------------------------------------------------------------------------------
/**
* Return true if given actor is our enemy
*/
bool INextBot::IsEnemy( const CBaseEntity *them ) const
{
if ( them == NULL )
return false;
// this is not strictly correct, as spectators are not enemies
return const_cast< INextBot * >( this )->GetEntity()->GetTeamNumber() != them->GetTeamNumber();
}
//----------------------------------------------------------------------------------------------------------------
/**
* Return true if given actor is our friend
*/
bool INextBot::IsFriend( const CBaseEntity *them ) const
{
if ( them == NULL )
return false;
return const_cast< INextBot * >( this )->GetEntity()->GetTeamNumber() == them->GetTeamNumber();
}
//----------------------------------------------------------------------------------------------------------------
/**
* Return true if 'them' is actually me
*/
bool INextBot::IsSelf( const CBaseEntity *them ) const
{
if ( them == NULL )
return false;
return const_cast< INextBot * >( this )->GetEntity()->entindex() == them->entindex();
}
//----------------------------------------------------------------------------------------------------------------
/**
* Components call this to register themselves with the bot that contains them
*/
void INextBot::RegisterComponent( INextBotComponent *comp )
{
// add to head of singly linked list
comp->m_nextComponent = m_componentList;
m_componentList = comp;
}
//----------------------------------------------------------------------------------------------------------------
bool INextBot::IsRangeLessThan( CBaseEntity *subject, float range ) const
{
Vector botPos;
CBaseEntity *bot = const_cast< INextBot * >( this )->GetEntity();
if ( !bot || !subject )
return 0.0f;
bot->CollisionProp()->CalcNearestPoint( subject->WorldSpaceCenter(), &botPos );
float computedRange = subject->CollisionProp()->CalcDistanceFromPoint( botPos );
return computedRange < range;
}
//----------------------------------------------------------------------------------------------------------------
bool INextBot::IsRangeLessThan( const Vector &pos, float range ) const
{
Vector to = pos - GetPosition();
return to.IsLengthLessThan( range );
}
//----------------------------------------------------------------------------------------------------------------
bool INextBot::IsRangeGreaterThan( CBaseEntity *subject, float range ) const
{
Vector botPos;
CBaseEntity *bot = const_cast< INextBot * >( this )->GetEntity();
if ( !bot || !subject )
return true;
bot->CollisionProp()->CalcNearestPoint( subject->WorldSpaceCenter(), &botPos );
float computedRange = subject->CollisionProp()->CalcDistanceFromPoint( botPos );
return computedRange > range;
}
//----------------------------------------------------------------------------------------------------------------
bool INextBot::IsRangeGreaterThan( const Vector &pos, float range ) const
{
Vector to = pos - GetPosition();
return to.IsLengthGreaterThan( range );
}
//----------------------------------------------------------------------------------------------------------------
float INextBot::GetRangeTo( CBaseEntity *subject ) const
{
Vector botPos;
CBaseEntity *bot = const_cast< INextBot * >( this )->GetEntity();
if ( !bot || !subject )
return 0.0f;
bot->CollisionProp()->CalcNearestPoint( subject->WorldSpaceCenter(), &botPos );
float computedRange = subject->CollisionProp()->CalcDistanceFromPoint( botPos );
return computedRange;
}
//----------------------------------------------------------------------------------------------------------------
float INextBot::GetRangeTo( const Vector &pos ) const
{
Vector to = pos - GetPosition();
return to.Length();
}
//----------------------------------------------------------------------------------------------------------------
float INextBot::GetRangeSquaredTo( CBaseEntity *subject ) const
{
Vector botPos;
CBaseEntity *bot = const_cast< INextBot * >( this )->GetEntity();
if ( !bot || !subject )
return 0.0f;
bot->CollisionProp()->CalcNearestPoint( subject->WorldSpaceCenter(), &botPos );
float computedRange = subject->CollisionProp()->CalcDistanceFromPoint( botPos );
return computedRange * computedRange;
}
//----------------------------------------------------------------------------------------------------------------
float INextBot::GetRangeSquaredTo( const Vector &pos ) const
{
Vector to = pos - GetPosition();
return to.LengthSqr();
}
//----------------------------------------------------------------------------------------------------------------
bool INextBot::IsDebugging( unsigned int type ) const
{
if ( TheNextBots().IsDebugging( type ) )
{
return TheNextBots().IsDebugFilterMatch( this );
}
return false;
}
//----------------------------------------------------------------------------------------------------------------
/**
* Return the name of this bot for debugging purposes
*/
const char *INextBot::GetDebugIdentifier( void ) const
{
const int nameSize = 256;
static char name[ nameSize ];
Q_snprintf( name, nameSize, "%s(#%d)", const_cast< INextBot * >( this )->GetEntity()->GetClassname(), const_cast< INextBot * >( this )->GetEntity()->entindex() );
return name;
}
//----------------------------------------------------------------------------------------------------------------
/**
* Return true if we match the given debug symbol
*/
bool INextBot::IsDebugFilterMatch( const char *name ) const
{
// compare debug identifier
if ( !Q_strnicmp( name, GetDebugIdentifier(), Q_strlen( name ) ) )
{
return true;
}
// compare team name
CTeam *team = GetEntity()->GetTeam();
if ( team && !Q_strnicmp( name, team->GetName(), Q_strlen( name ) ) )
{
return true;
}
return false;
}
//----------------------------------------------------------------------------------------------------------------
/**
* There are some things we never want to climb on
*/
bool INextBot::IsAbleToClimbOnto( const CBaseEntity *object ) const
{
if ( object == NULL || !const_cast<CBaseEntity *>(object)->IsAIWalkable() )
{
return false;
}
// never climb onto doors
if ( FClassnameIs( const_cast< CBaseEntity * >( object ), "prop_door*" ) || FClassnameIs( const_cast< CBaseEntity * >( object ), "func_door*" ) )
{
return false;
}
// ok to climb on this object
return true;
}
//----------------------------------------------------------------------------------------------------------------
/**
* Can we break this object
*/
bool INextBot::IsAbleToBreak( const CBaseEntity *object ) const
{
if ( object && object->m_takedamage == DAMAGE_YES )
{
if ( FClassnameIs( const_cast< CBaseEntity * >( object ), "func_breakable" ) &&
object->GetHealth() )
{
return true;
}
if ( FClassnameIs( const_cast< CBaseEntity * >( object ), "func_breakable_surf" ) )
{
return true;
}
if ( dynamic_cast< const CBreakableProp * >( object ) != NULL )
{
return true;
}
}
return false;
}
//----------------------------------------------------------------------------------------------------------
void INextBot::DisplayDebugText( const char *text ) const
{
const_cast< INextBot * >( this )->GetEntity()->EntityText( m_debugDisplayLine++, text, 0.1 );
}
//--------------------------------------------------------------------------------------------------------
void INextBot::DebugConColorMsg( NextBotDebugType debugType, const Color &color, const char *fmt, ... )
{
bool isDataFormatted = false;
va_list argptr;
char data[ MAX_NEXTBOT_DEBUG_LINE_LENGTH ];
if ( developer.GetBool() && IsDebugging( debugType ) )
{
va_start(argptr, fmt);
Q_vsnprintf(data, sizeof( data ), fmt, argptr);
va_end(argptr);
isDataFormatted = true;
ConColorMsg( color, "%s", data );
}
if ( !NextBotDebugHistory.GetBool() )
{
if ( m_debugHistory.Count() )
{
ResetDebugHistory();
}
return;
}
// Don't bother with event data - it's spammy enough to overshadow everything else.
if ( debugType == NEXTBOT_EVENTS )
return;
if ( !isDataFormatted )
{
va_start(argptr, fmt);
Q_vsnprintf(data, sizeof( data ), fmt, argptr);
va_end(argptr);
isDataFormatted = true;
}
int lastLine = m_debugHistory.Count() - 1;
if ( lastLine >= 0 )
{
NextBotDebugLineType *line = m_debugHistory[lastLine];
if ( line->debugType == debugType && V_strstr( line->data, "\n" ) == NULL )
{
// append onto previous line
V_strncat( line->data, data, MAX_NEXTBOT_DEBUG_LINE_LENGTH );
return;
}
}
// Prune out an old line if needed, keeping a pointer to re-use the memory
NextBotDebugLineType *line = NULL;
if ( m_debugHistory.Count() == MAX_NEXTBOT_DEBUG_HISTORY )
{
line = m_debugHistory[0];
m_debugHistory.Remove( 0 );
}
// Add to debug history
if ( !line )
{
line = new NextBotDebugLineType;
}
line->debugType = debugType;
V_strncpy( line->data, data, MAX_NEXTBOT_DEBUG_LINE_LENGTH );
m_debugHistory.AddToTail( line );
}
//--------------------------------------------------------------------------------------------------------
// build a vector of debug history of the given types
void INextBot::GetDebugHistory( unsigned int type, CUtlVector< const NextBotDebugLineType * > *lines ) const
{
if ( !lines )
return;
lines->RemoveAll();
for ( int i=0; i<m_debugHistory.Count(); ++i )
{
NextBotDebugLineType *line = m_debugHistory[i];
if ( line->debugType & type )
{
lines->AddToTail( line );
}
}
}
//--------------------------------------------------------------------------------------------------------
void INextBot::UpdateImmobileStatus( void )
{
if ( m_immobileCheckTimer.IsElapsed() )
{
m_immobileCheckTimer.Start( 1.0f );
// if we haven't moved farther than this in 1 second, we're immobile
if ( ( GetEntity()->GetAbsOrigin() - m_immobileAnchor ).IsLengthGreaterThan( GetImmobileSpeedThreshold() ) )
{
// moved far enough, not immobile
m_immobileAnchor = GetEntity()->GetAbsOrigin();
m_immobileTimer.Invalidate();
}
else
{
// haven't escaped our anchor - we are immobile
if ( !m_immobileTimer.HasStarted() )
{
m_immobileTimer.Start();
}
}
}
}
+302
View File
@@ -0,0 +1,302 @@
// NextBotInterface.h
// Interface for NextBot
// Author: Michael Booth, May 2006
//========= Copyright Valve Corporation, All rights reserved. ============//
#ifndef _NEXT_BOT_INTERFACE_H_
#define _NEXT_BOT_INTERFACE_H_
#include "NextBot/NextBotKnownEntity.h"
#include "NextBotComponentInterface.h"
#include "NextBotLocomotionInterface.h"
#include "NextBotBodyInterface.h"
#include "NextBotIntentionInterface.h"
#include "NextBotVisionInterface.h"
#include "NextBotDebug.h"
class CBaseCombatCharacter;
class PathFollower;
//----------------------------------------------------------------------------------------------------------------
/**
* A general purpose filter interface for various bot systems
*/
class INextBotFilter
{
public:
virtual bool IsSelected( const CBaseEntity *candidate ) const = 0; // return true if this entity passes the filter
};
//----------------------------------------------------------------------------------------------------------------
class INextBot : public INextBotEventResponder
{
public:
INextBot( void );
virtual ~INextBot();
int GetBotId() const;
bool BeginUpdate();
void EndUpdate();
virtual void Reset( void ); // (EXTEND) reset to initial state
virtual void Update( void ); // (EXTEND) update internal state
virtual void Upkeep( void ); // (EXTEND) lightweight update guaranteed to occur every server tick
void FlagForUpdate( bool b = true );
bool IsFlaggedForUpdate();
int GetTickLastUpdate() const;
void SetTickLastUpdate( int );
virtual bool IsRemovedOnReset( void ) const { return true; } // remove this bot when the NextBot manager calls Reset
virtual CBaseCombatCharacter *GetEntity( void ) const = 0;
virtual class NextBotCombatCharacter *GetNextBotCombatCharacter( void ) const { return NULL; }
#ifdef TERROR
virtual class SurvivorBot *MySurvivorBotPointer() const { return NULL; }
#endif
// interfaces are never NULL - return base no-op interfaces at a minimum
virtual ILocomotion * GetLocomotionInterface( void ) const;
virtual IBody * GetBodyInterface( void ) const;
virtual IIntention * GetIntentionInterface( void ) const;
virtual IVision * GetVisionInterface( void ) const;
/**
* Attempt to change the bot's position. Return true if successful.
*/
virtual bool SetPosition( const Vector &pos );
virtual const Vector &GetPosition( void ) const; // get the global position of the bot
/**
* Friend/enemy/neutral queries
*/
virtual bool IsEnemy( const CBaseEntity *them ) const; // return true if given entity is our enemy
virtual bool IsFriend( const CBaseEntity *them ) const; // return true if given entity is our friend
virtual bool IsSelf( const CBaseEntity *them ) const; // return true if 'them' is actually me
/**
* Can we climb onto this entity?
*/
virtual bool IsAbleToClimbOnto( const CBaseEntity *object ) const;
/**
* Can we break this entity?
*/
virtual bool IsAbleToBreak( const CBaseEntity *object ) const;
/**
* Sometimes we want to pass through other NextBots. OnContact() will always
* be invoked, but collision resolution can be skipped if this
* method returns false.
*/
virtual bool IsAbleToBlockMovementOf( const INextBot *botInMotion ) const { return true; }
/**
* Should we ever care about noticing physical contact with this entity?
*/
virtual bool ShouldTouch( const CBaseEntity *object ) const { return true; }
/**
* This immobile system is used to track the global state of "am I actually moving or not".
* The OnStuck() event is only emitted when following a path, and paths can be recomputed, etc.
*/
virtual bool IsImmobile( void ) const; // return true if we haven't moved in awhile
virtual float GetImmobileDuration( void ) const; // how long have we been immobile
virtual void ClearImmobileStatus( void );
virtual float GetImmobileSpeedThreshold( void ) const; // return units/second below which this actor is considered "immobile"
/**
* Get the last PathFollower we followed. This method gives other interfaces a
* single accessor to the most recent Path being followed by the myriad of
* different PathFollowers used in the various behaviors the bot may be doing.
*/
virtual const PathFollower *GetCurrentPath( void ) const;
virtual void SetCurrentPath( const PathFollower *path );
virtual void NotifyPathDestruction( const PathFollower *path ); // this PathFollower is going away, which may or may not be ours
// between distance utility methods
virtual bool IsRangeLessThan( CBaseEntity *subject, float range ) const;
virtual bool IsRangeLessThan( const Vector &pos, float range ) const;
virtual bool IsRangeGreaterThan( CBaseEntity *subject, float range ) const;
virtual bool IsRangeGreaterThan( const Vector &pos, float range ) const;
virtual float GetRangeTo( CBaseEntity *subject ) const;
virtual float GetRangeTo( const Vector &pos ) const;
virtual float GetRangeSquaredTo( CBaseEntity *subject ) const;
virtual float GetRangeSquaredTo( const Vector &pos ) const;
// event propagation
virtual INextBotEventResponder *FirstContainedResponder( void ) const;
virtual INextBotEventResponder *NextContainedResponder( INextBotEventResponder *current ) const;
virtual bool IsDebugging( unsigned int type ) const; // return true if this bot is debugging any of the given types
virtual const char *GetDebugIdentifier( void ) const; // return the name of this bot for debugging purposes
virtual bool IsDebugFilterMatch( const char *name ) const; // return true if we match the given debug symbol
virtual void DisplayDebugText( const char *text ) const; // show a line of text on the bot in the world
void DebugConColorMsg( NextBotDebugType debugType, const Color &color, PRINTF_FORMAT_STRING const char *fmt, ... );
enum {
MAX_NEXTBOT_DEBUG_HISTORY = 100,
MAX_NEXTBOT_DEBUG_LINE_LENGTH = 256,
};
struct NextBotDebugLineType
{
NextBotDebugType debugType;
char data[ MAX_NEXTBOT_DEBUG_LINE_LENGTH ];
};
void GetDebugHistory( unsigned int type, CUtlVector< const NextBotDebugLineType * > *lines ) const; // build a vector of debug history of the given types
//------------------------------------------------------------------------------
private:
friend class INextBotComponent;
void RegisterComponent( INextBotComponent *comp ); // components call this to register themselves with the bot that contains them
INextBotComponent *m_componentList; // the first component
const PathFollower *m_currentPath; // the path we most recently followed
int m_id;
bool m_bFlaggedForUpdate;
int m_tickLastUpdate;
unsigned int m_debugType;
mutable int m_debugDisplayLine;
Vector m_immobileAnchor;
CountdownTimer m_immobileCheckTimer;
IntervalTimer m_immobileTimer;
void UpdateImmobileStatus( void );
mutable ILocomotion *m_baseLocomotion;
mutable IBody *m_baseBody;
mutable IIntention *m_baseIntention;
mutable IVision *m_baseVision;
//mutable IAttention *m_baseAttention;
// Debugging info
void ResetDebugHistory( void );
CUtlVector< NextBotDebugLineType * > m_debugHistory;
};
inline const PathFollower *INextBot::GetCurrentPath( void ) const
{
return m_currentPath;
}
inline void INextBot::SetCurrentPath( const PathFollower *path )
{
m_currentPath = path;
}
inline void INextBot::NotifyPathDestruction( const PathFollower *path )
{
if ( m_currentPath == path )
m_currentPath = NULL;
}
inline ILocomotion *INextBot::GetLocomotionInterface( void ) const
{
// these base interfaces are lazy-allocated (instead of being fully instanced classes) for two reasons:
// 1) so the memory is only used if needed
// 2) so the component is registered properly
if ( m_baseLocomotion == NULL )
{
m_baseLocomotion = new ILocomotion( const_cast< INextBot * >( this ) );
}
return m_baseLocomotion;
}
inline IBody *INextBot::GetBodyInterface( void ) const
{
if ( m_baseBody == NULL )
{
m_baseBody = new IBody( const_cast< INextBot * >( this ) );
}
return m_baseBody;
}
inline IIntention *INextBot::GetIntentionInterface( void ) const
{
if ( m_baseIntention == NULL )
{
m_baseIntention = new IIntention( const_cast< INextBot * >( this ) );
}
return m_baseIntention;
}
inline IVision *INextBot::GetVisionInterface( void ) const
{
if ( m_baseVision == NULL )
{
m_baseVision = new IVision( const_cast< INextBot * >( this ) );
}
return m_baseVision;
}
inline int INextBot::GetBotId() const
{
return m_id;
}
inline void INextBot::FlagForUpdate( bool b )
{
m_bFlaggedForUpdate = b;
}
inline bool INextBot::IsFlaggedForUpdate()
{
return m_bFlaggedForUpdate;
}
inline int INextBot::GetTickLastUpdate() const
{
return m_tickLastUpdate;
}
inline void INextBot::SetTickLastUpdate( int tick )
{
m_tickLastUpdate = tick;
}
inline bool INextBot::IsImmobile( void ) const
{
return m_immobileTimer.HasStarted();
}
inline float INextBot::GetImmobileDuration( void ) const
{
return m_immobileTimer.GetElapsedTime();
}
inline void INextBot::ClearImmobileStatus( void )
{
m_immobileTimer.Invalidate();
m_immobileAnchor = GetEntity()->GetAbsOrigin();
}
inline float INextBot::GetImmobileSpeedThreshold( void ) const
{
return 30.0f;
}
inline INextBotEventResponder *INextBot::FirstContainedResponder( void ) const
{
return m_componentList;
}
inline INextBotEventResponder *INextBot::NextContainedResponder( INextBotEventResponder *current ) const
{
return static_cast< INextBotComponent * >( current )->m_nextComponent;
}
#endif // _NEXT_BOT_INTERFACE_H_
+175
View File
@@ -0,0 +1,175 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// NextBotKnownEntity.h
// Encapsulation of being aware of an entity
// Author: Michael Booth, June 2009
#ifndef NEXT_BOT_KNOWN_ENTITY_H
#define NEXT_BOT_KNOWN_ENTITY_H
//----------------------------------------------------------------------------
/**
* A "known entity" is an entity that we have seen or heard at some point
* and which may or may not be immediately visible to us right now but which
* we remember the last place we encountered it, and when.
*
* TODO: Enhance interface to allow for sets of areas where an unseen entity
* could potentially be, knowing his last position and his rate of movement.
*/
class CKnownEntity
{
public:
// constructing assumes we currently know about this entity
CKnownEntity( CBaseEntity *who )
{
m_who = who;
m_whenLastSeen = -1.0f;
m_whenLastBecameVisible = -1.0f;
m_isVisible = false;
m_whenBecameKnown = gpGlobals->curtime;
m_hasLastKnownPositionBeenSeen = false;
UpdatePosition();
}
virtual ~CKnownEntity() { }
virtual void Destroy( void )
{
m_who = NULL;
m_isVisible = false;
}
virtual void UpdatePosition( void ) // could be seen or heard, but now the entity's position is known
{
if ( m_who.Get() )
{
m_lastKnownPostion = m_who->GetAbsOrigin();
m_lastKnownArea = m_who->MyCombatCharacterPointer() ? m_who->MyCombatCharacterPointer()->GetLastKnownArea() : NULL;
m_whenLastKnown = gpGlobals->curtime;
}
}
virtual CBaseEntity *GetEntity( void ) const
{
return m_who;
}
virtual const Vector &GetLastKnownPosition( void ) const
{
return m_lastKnownPostion;
}
// Have we had a clear view of the last known position of this entity?
// This encapsulates the idea of "I just saw a guy right over *there* a few seconds ago, but I don't know where he is now"
virtual bool HasLastKnownPositionBeenSeen( void ) const
{
return m_hasLastKnownPositionBeenSeen;
}
virtual void MarkLastKnownPositionAsSeen( void )
{
m_hasLastKnownPositionBeenSeen = true;
}
virtual const CNavArea *GetLastKnownArea( void ) const
{
return m_lastKnownArea;
}
virtual float GetTimeSinceLastKnown( void ) const
{
return gpGlobals->curtime - m_whenLastKnown;
}
virtual float GetTimeSinceBecameKnown( void ) const
{
return gpGlobals->curtime - m_whenBecameKnown;
}
virtual void UpdateVisibilityStatus( bool visible )
{
if ( visible )
{
if ( !m_isVisible )
{
// just became visible
m_whenLastBecameVisible = gpGlobals->curtime;
}
m_whenLastSeen = gpGlobals->curtime;
}
m_isVisible = visible;
}
virtual bool IsVisibleInFOVNow( void ) const // return true if this entity is currently visible and in my field of view
{
return m_isVisible;
}
virtual bool IsVisibleRecently( void ) const // return true if this entity is visible or was very recently visible
{
if ( m_isVisible )
return true;
if ( WasEverVisible() && GetTimeSinceLastSeen() < 3.0f )
return true;
return false;
}
virtual float GetTimeSinceBecameVisible( void ) const
{
return gpGlobals->curtime - m_whenLastBecameVisible;
}
virtual float GetTimeWhenBecameVisible( void ) const
{
return m_whenLastBecameVisible;
}
virtual float GetTimeSinceLastSeen( void ) const
{
return gpGlobals->curtime - m_whenLastSeen;
}
virtual bool WasEverVisible( void ) const
{
return m_whenLastSeen > 0.0f;
}
// has our knowledge of this entity become obsolete?
virtual bool IsObsolete( void ) const
{
return GetEntity() == NULL || !m_who->IsAlive() || GetTimeSinceLastKnown() > 10.0f;
}
virtual bool operator==( const CKnownEntity &other ) const
{
if ( GetEntity() == NULL || other.GetEntity() == NULL )
return false;
return ( GetEntity() == other.GetEntity() );
}
virtual bool Is( CBaseEntity *who ) const
{
if ( GetEntity() == NULL || who == NULL )
return false;
return ( GetEntity() == who );
}
private:
CHandle< CBaseEntity > m_who;
Vector m_lastKnownPostion;
bool m_hasLastKnownPositionBeenSeen;
CNavArea *m_lastKnownArea;
float m_whenLastSeen;
float m_whenLastBecameVisible;
float m_whenLastKnown; // last seen or heard, confirming its existance
float m_whenBecameKnown;
bool m_isVisible; // flagged by IVision update as visible or not
};
#endif // NEXT_BOT_KNOWN_ENTITY_H
@@ -0,0 +1,520 @@
// NextBotLocomotionInterface.cpp
// Common functionality for all NextBot locomotors
// Author: Michael Booth, April 2005
//========= Copyright Valve Corporation, All rights reserved. ============//
#include "cbase.h"
#include "BasePropDoor.h"
#include "nav_area.h"
#include "NextBot.h"
#include "NextBotUtil.h"
#include "NextBotLocomotionInterface.h"
#include "NextBotBodyInterface.h"
#include "tier0/vprof.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
// how far a bot must move to not be considered "stuck"
#define STUCK_RADIUS 100.0f
//----------------------------------------------------------------------------------------------------------
/**
* Reset to initial state
*/
ILocomotion::ILocomotion( INextBot *bot ) : INextBotComponent( bot )
{
Reset();
}
ILocomotion::~ILocomotion()
{
}
void ILocomotion::Reset( void )
{
INextBotComponent::Reset();
m_motionVector = Vector( 1.0f, 0.0f, 0.0f );
m_speed = 0.0f;
m_groundMotionVector = m_motionVector;
m_groundSpeed = m_speed;
m_moveRequestTimer.Invalidate();
m_isStuck = false;
m_stuckTimer.Invalidate();
m_stuckPos = vec3_origin;
}
//----------------------------------------------------------------------------------------------------------
/**
* Update internal state
*/
void ILocomotion::Update( void )
{
StuckMonitor();
// maintain motion vector and speed values
const Vector &vel = GetVelocity();
m_speed = vel.Length();
m_groundSpeed = vel.AsVector2D().Length();
const float velocityThreshold = 10.0f;
if ( m_speed > velocityThreshold )
{
m_motionVector = vel / m_speed;
}
if ( m_groundSpeed > velocityThreshold )
{
m_groundMotionVector.x = vel.x / m_groundSpeed;
m_groundMotionVector.y = vel.y / m_groundSpeed;
m_groundMotionVector.z = 0.0f;
}
if ( GetBot()->IsDebugging( NEXTBOT_LOCOMOTION ) )
{
// show motion vector
NDebugOverlay::HorzArrow( GetFeet(), GetFeet() + 25.0f * m_groundMotionVector, 3.0f, 100, 255, 0, 255, true, 0.1f );
NDebugOverlay::HorzArrow( GetFeet(), GetFeet() + 25.0f * m_motionVector, 5.0f, 255, 255, 0, 255, true, 0.1f );
}
}
//----------------------------------------------------------------------------
void ILocomotion::AdjustPosture( const Vector &moveGoal )
{
// This function has no effect if we're not standing or crouching
IBody *body = GetBot()->GetBodyInterface();
if ( !body->IsActualPosture( IBody::STAND ) && !body->IsActualPosture( IBody::CROUCH ) )
return;
//
// Stand or crouch as needed
//
// get bounding limits, ignoring step-upable height
const Vector &mins = body->GetHullMins() + Vector( 0, 0, GetStepHeight() );
const float halfSize = body->GetHullWidth()/2.0f;
Vector standMaxs( halfSize, halfSize, body->GetStandHullHeight() );
trace_t trace;
NextBotTraversableTraceFilter filter( GetBot(), ILocomotion::IMMEDIATELY );
// snap forward movement vector along floor
const Vector &groundNormal = GetGroundNormal();
const Vector &feet = GetFeet();
Vector moveDir = moveGoal - feet;
float moveLength = moveDir.NormalizeInPlace();
Vector left( -moveDir.y, moveDir.x, 0.0f );
Vector goal = feet + moveLength * CrossProduct( left, groundNormal ).Normalized();
TraceHull( feet, goal, mins, standMaxs, body->GetSolidMask(), &filter, &trace );
if ( trace.fraction >= 1.0f && !trace.startsolid )
{
// no collision while standing
if ( body->IsActualPosture( IBody::CROUCH ) )
{
body->SetDesiredPosture( IBody::STAND );
}
return;
}
if ( body->IsActualPosture( IBody::CROUCH ) )
return;
// crouch hull check
Vector crouchMaxs( halfSize, halfSize, body->GetCrouchHullHeight() );
TraceHull( feet, goal, mins, crouchMaxs, body->GetSolidMask(), &filter, &trace );
if ( trace.fraction >= 1.0f && !trace.startsolid )
{
// no collision while crouching
body->SetDesiredPosture( IBody::CROUCH );
}
}
//----------------------------------------------------------------------------------------------------------
/**
* Move directly towards the given position
*/
void ILocomotion::Approach( const Vector &goalPos, float goalWeight )
{
// there is a desire to move
m_moveRequestTimer.Start();
}
//----------------------------------------------------------------------------------------------------------
/**
* Move the bot to the precise given position immediately
*/
void ILocomotion::DriveTo( const Vector &pos )
{
// there is a desire to move
m_moveRequestTimer.Start();
}
//----------------------------------------------------------------------------------------------------------
/**
* Return true if this locomotor could potentially move along the line given.
* If false is returned, fraction of walkable ray is returned in 'fraction'
*/
bool ILocomotion::IsPotentiallyTraversable( const Vector &from, const Vector &to, TraverseWhenType when, float *fraction ) const
{
VPROF_BUDGET( "Locomotion::IsPotentiallyTraversable", "NextBotExpensive" );
// if 'to' is high above us, it's not directly traversable
// Adding a bit of fudge room to allow for floating point roundoff errors
if ( ( to.z - from.z ) > GetMaxJumpHeight() + 0.1f )
{
Vector along = to - from;
along.NormalizeInPlace();
if ( along.z > GetTraversableSlopeLimit() )
{
if ( fraction )
{
*fraction = 0.0f;
}
return false;
}
}
trace_t result;
NextBotTraversableTraceFilter filter( GetBot(), when );
// use a small hull since we cannot simulate collision resolution and avoidance along the way
const float probeSize = 0.25f * GetBot()->GetBodyInterface()->GetHullWidth(); // Cant be TOO small, or open stairwells/grates/etc will cause problems
const float probeZ = GetStepHeight();
Vector hullMin( -probeSize, -probeSize, probeZ );
Vector hullMax( probeSize, probeSize, GetBot()->GetBodyInterface()->GetCrouchHullHeight() );
TraceHull( from, to, hullMin, hullMax, GetBot()->GetBodyInterface()->GetSolidMask(), &filter, &result );
/*
if ( result.DidHit() )
{
NDebugOverlay::SweptBox( from, result.endpos, hullMin, hullMax, vec3_angle, 255, 0, 0, 255, 9999.9f );
NDebugOverlay::SweptBox( result.endpos, to, hullMin, hullMax, vec3_angle, 255, 255, 0, 255, 9999.9f );
}
else
{
NDebugOverlay::SweptBox( from, to, hullMin, hullMax, vec3_angle, 255, 255, 0, 255, 0.1f );
}
*/
if ( fraction )
{
*fraction = result.fraction;
}
return ( result.fraction >= 1.0f ) && ( !result.startsolid );
}
//----------------------------------------------------------------------------------------------------------
/**
* Return true if there is a possible "gap" that will need to be jumped over
* If true is returned, fraction of ray before gap is returned in 'fraction'
*/
bool ILocomotion::HasPotentialGap( const Vector &from, const Vector &desiredTo, float *fraction ) const
{
VPROF_BUDGET( "Locomotion::HasPotentialGap", "NextBot" );
// find section of this ray that is actually traversable
float traversableFraction;
IsPotentiallyTraversable( from, desiredTo, IMMEDIATELY, &traversableFraction );
// compute end of traversable ray
Vector to = from + ( desiredTo - from ) * traversableFraction;
Vector forward = to - from;
float length = forward.NormalizeInPlace();
IBody *body = GetBot()->GetBodyInterface();
float step = body->GetHullWidth()/2.0f;
// scan along the line checking for gaps
Vector pos = from;
Vector delta = step * forward;
for( float t = 0.0f; t < (length + step); t += step )
{
if ( IsGap( pos, forward ) )
{
if ( fraction )
{
*fraction = ( t - step ) / ( length + step );
}
return true;
}
pos += delta;
}
if ( fraction )
{
*fraction = 1.0f;
}
return false;
}
//----------------------------------------------------------------------------------------------------------
/**
* Return true if there is a "gap" here when moving in the given direction.
* A "gap" is a vertical dropoff that is too high to jump back up to.
*/
bool ILocomotion::IsGap( const Vector &pos, const Vector &forward ) const
{
VPROF_BUDGET( "Locomotion::IsGap", "NextBotSpiky" );
IBody *body = GetBot()->GetBodyInterface();
//float halfWidth = ( body ) ? body->GetHullWidth()/2.0f : 1.0f;
// can't really jump effectively when crouched anyhow
//float hullHeight = ( body ) ? body->GetStandHullHeight() : 1.0f;
// use a small hull since we cannot simulate collision resolution and avoidance along the way
const float halfWidth = 1.0f;
const float hullHeight = 1.0f;
unsigned int mask = ( body ) ? body->GetSolidMask() : MASK_PLAYERSOLID;
trace_t ground;
NextBotTraceFilterIgnoreActors filter( GetBot()->GetEntity(), COLLISION_GROUP_NONE );
TraceHull( pos + Vector( 0, 0, GetStepHeight() ), // start up a bit to handle rough terrain
pos + Vector( 0, 0, -GetMaxJumpHeight() ),
Vector( -halfWidth, -halfWidth, 0 ), Vector( halfWidth, halfWidth, hullHeight ),
mask, &filter, &ground );
// int r,g,b;
//
// if ( ground.fraction >= 1.0f && !ground.startsolid )
// {
// r = 255, g = 0, b = 0;
// }
// else
// {
// r = 0, g = 255, b = 0;
// }
//
// NDebugOverlay::SweptBox( pos,
// pos + Vector( 0, 0, -GetStepHeight() ),
// Vector( -halfWidth, -halfWidth, 0 ), Vector( halfWidth, halfWidth, hullHeight ),
// vec3_angle,
// r, g, b, 255, 3.0f );
// if trace hit nothing, there's a gap ahead of us
return ( ground.fraction >= 1.0f && !ground.startsolid );
}
//----------------------------------------------------------------------------------------------------------
bool ILocomotion::IsEntityTraversable( CBaseEntity *obstacle, TraverseWhenType when ) const
{
if ( obstacle->IsWorld() )
return false;
// assume bot will open a door in its path
if ( FClassnameIs( obstacle, "prop_door*" ) || FClassnameIs( obstacle, "func_door*" ) )
{
CBasePropDoor *door = dynamic_cast< CBasePropDoor * >( obstacle );
if ( door && door->IsDoorOpen() )
{
// open doors are obstacles
return false;
}
return true;
}
// if we hit a clip brush, ignore it if it is not BRUSHSOLID_ALWAYS
if ( FClassnameIs( obstacle, "func_brush" ) )
{
CFuncBrush *brush = (CFuncBrush *)obstacle;
switch ( brush->m_iSolidity )
{
case CFuncBrush::BRUSHSOLID_ALWAYS:
return false;
case CFuncBrush::BRUSHSOLID_NEVER:
return true;
case CFuncBrush::BRUSHSOLID_TOGGLE:
return true;
}
}
if ( when == IMMEDIATELY )
{
// special rules in specific games can immediately break some breakables, etc.
return false;
}
// assume bot will EVENTUALLY break breakables in its path
return GetBot()->IsAbleToBreak( obstacle );
}
//--------------------------------------------------------------------------------------------------------------
bool ILocomotion::IsAreaTraversable( const CNavArea *baseArea ) const
{
return !baseArea->IsBlocked( GetBot()->GetEntity()->GetTeamNumber() );
}
//--------------------------------------------------------------------------------------------------------------
/**
* Reset stuck status to un-stuck
*/
void ILocomotion::ClearStuckStatus( const char *reason )
{
if ( IsStuck() )
{
m_isStuck = false;
// tell other components we're no longer stuck
GetBot()->OnUnStuck();
}
// always reset stuck monitoring data in case we cleared preemptively are were not yet stuck
m_stuckPos = GetFeet();
m_stuckTimer.Start();
if ( GetBot()->IsDebugging( NEXTBOT_LOCOMOTION ) )
{
DevMsg( "%3.2f: ClearStuckStatus: %s %s\n", gpGlobals->curtime, GetBot()->GetDebugIdentifier(), reason );
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Stuck check
*/
void ILocomotion::StuckMonitor( void )
{
// a timer is needed to smooth over a few frames of inactivity due to state changes, etc.
// we only want to detect idle situations when the bot really doesn't "want" to move.
const float idleTime = 0.25f;
if ( m_moveRequestTimer.IsGreaterThen( idleTime ) )
{
// we have no desire to move, and therefore cannot emit stuck events
// prepare our internal state for when the bot starts to move next
m_stuckPos = GetFeet();
m_stuckTimer.Start();
return;
}
// if ( !IsOnGround() )
// {
// // can't be stuck when in-air
// ClearStuckStatus( "Off the ground" );
// return;
// }
// if ( IsUsingLadder() )
// {
// // can't be stuck when on a ladder (for now)
// ClearStuckStatus( "On a ladder" );
// return;
// }
if ( IsStuck() )
{
// we are/were stuck - have we moved enough to consider ourselves "dislodged"
if ( GetBot()->IsRangeGreaterThan( m_stuckPos, STUCK_RADIUS ) )
{
// we've just become un-stuck
ClearStuckStatus( "UN-STUCK" );
}
else
{
// still stuck - periodically resend the event
if ( m_stillStuckTimer.IsElapsed() )
{
m_stillStuckTimer.Start( 1.0f );
if ( GetBot()->IsDebugging( NEXTBOT_LOCOMOTION ) )
{
DevMsg( "%3.2f: %s STILL STUCK\n", gpGlobals->curtime, GetBot()->GetDebugIdentifier() );
NDebugOverlay::Circle( m_stuckPos + Vector( 0, 0, 5.0f ), QAngle( -90.0f, 0, 0 ), 5.0f, 255, 0, 0, 255, true, 1.0f );
}
GetBot()->OnStuck();
}
}
}
else
{
// we're not stuck - yet
if ( /*IsClimbingOrJumping() || */GetBot()->IsRangeGreaterThan( m_stuckPos, STUCK_RADIUS ) )
{
// we have moved - reset anchor
m_stuckPos = GetFeet();
if ( GetBot()->IsDebugging( NEXTBOT_LOCOMOTION ) )
{
NDebugOverlay::Cross3D( m_stuckPos, 3.0f, 255, 0, 255, true, 3.0f );
}
m_stuckTimer.Start();
}
else
{
// within stuck range of anchor. if we've been here too long, we're stuck
if ( GetBot()->IsDebugging( NEXTBOT_LOCOMOTION ) )
{
NDebugOverlay::Line( GetBot()->GetEntity()->WorldSpaceCenter(), m_stuckPos, 255, 0, 255, true, 0.1f );
}
float minMoveSpeed = 0.1f * GetDesiredSpeed() + 0.1f;
float escapeTime = STUCK_RADIUS / minMoveSpeed;
if ( m_stuckTimer.IsGreaterThen( escapeTime ) )
{
// we have taken too long - we're stuck
m_isStuck = true;
if ( GetBot()->IsDebugging( NEXTBOT_ERRORS ) )
{
DevMsg( "%3.2f: %s STUCK at position( %3.2f, %3.2f, %3.2f )\n", gpGlobals->curtime, GetBot()->GetDebugIdentifier(), m_stuckPos.x, m_stuckPos.y, m_stuckPos.z );
NDebugOverlay::Circle( m_stuckPos + Vector( 0, 0, 15.0f ), QAngle( -90.0f, 0, 0 ), 3.0f, 255, 255, 0, 255, true, 1.0f );
NDebugOverlay::Circle( m_stuckPos + Vector( 0, 0, 5.0f ), QAngle( -90.0f, 0, 0 ), 5.0f, 255, 0, 0, 255, true, 9999999.9f );
}
// tell other components we've become stuck
GetBot()->OnStuck();
}
}
}
}
//--------------------------------------------------------------------------------------------------------------
const Vector &ILocomotion::GetFeet( void ) const
{
return GetBot()->GetEntity()->GetAbsOrigin();
}
@@ -0,0 +1,335 @@
// NextBotLocomotionInterface.h
// NextBot interface for movement through the environment
// Author: Michael Booth, April 2005
//========= Copyright Valve Corporation, All rights reserved. ============//
#ifndef _NEXT_BOT_LOCOMOTION_INTERFACE_H_
#define _NEXT_BOT_LOCOMOTION_INTERFACE_H_
#include "NextBotComponentInterface.h"
class Path;
class INextBot;
class CNavLadder;
//----------------------------------------------------------------------------------------------------------------
/**
* The interface encapsulating *how* a bot moves through the world (walking? flying? etc)
*/
class ILocomotion : public INextBotComponent
{
public:
ILocomotion( INextBot *bot );
virtual ~ILocomotion();
virtual void Reset( void ); // (EXTEND) reset to initial state
virtual void Update( void ); // (EXTEND) update internal state
//
// The primary locomotive method
// Depending on the physics of the bot's motion, it may not actually
// reach the given position precisely.
// The 'weight' can be used to combine multiple Approach() calls within
// a single frame into a single goal (ie: weighted average)
//
virtual void Approach( const Vector &goalPos, float goalWeight = 1.0f ); // (EXTEND) move directly towards the given position
//
// Move the bot to the precise given position immediately,
// updating internal state as needed
// Collision resolution is done to prevent interpenetration, which may prevent
// the bot from reaching the given position. If no collisions occur, the
// bot will be at the given position when this method returns.
//
virtual void DriveTo( const Vector &pos ); // (EXTEND) Move the bot to the precise given position immediately,
//
// Locomotion modifiers
//
virtual bool ClimbUpToLedge( const Vector &landingGoal, const Vector &landingForward, const CBaseEntity *obstacle ) { return true; } // initiate a jump to an adjacent high ledge, return false if climb can't start
virtual void JumpAcrossGap( const Vector &landingGoal, const Vector &landingForward ) { } // initiate a jump across an empty volume of space to far side
virtual void Jump( void ) { } // initiate a simple undirected jump in the air
virtual bool IsClimbingOrJumping( void ) const; // is jumping in any form
virtual bool IsClimbingUpToLedge( void ) const; // is climbing up to a high ledge
virtual bool IsJumpingAcrossGap( void ) const; // is jumping across a gap to the far side
virtual bool IsScrambling( void ) const; // is in the middle of a complex action (climbing a ladder, climbing a ledge, jumping, etc) that shouldn't be interrupted
virtual void Run( void ) { } // set desired movement speed to running
virtual void Walk( void ) { } // set desired movement speed to walking
virtual void Stop( void ) { } // set desired movement speed to stopped
virtual bool IsRunning( void ) const;
virtual void SetDesiredSpeed( float speed ) { } // set desired speed for locomotor movement
virtual float GetDesiredSpeed( void ) const; // returns the current desired speed
virtual void SetSpeedLimit( float speed ) { } // set maximum speed bot can reach, regardless of desired speed
virtual float GetSpeedLimit( void ) const { return 1000.0f; } // get maximum speed bot can reach, regardless of desired speed
virtual bool IsOnGround( void ) const; // return true if standing on something
virtual void OnLeaveGround( CBaseEntity *ground ) { } // invoked when bot leaves ground for any reason
virtual void OnLandOnGround( CBaseEntity *ground ) { } // invoked when bot lands on the ground after being in the air
virtual CBaseEntity *GetGround( void ) const; // return the current ground entity or NULL if not on the ground
virtual const Vector &GetGroundNormal( void ) const; // surface normal of the ground we are in contact with
virtual float GetGroundSpeed( void ) const; // return current world space speed in XY plane
virtual const Vector &GetGroundMotionVector( void ) const; // return unit vector in XY plane describing our direction of motion - even if we are currently not moving
virtual void ClimbLadder( const CNavLadder *ladder, const CNavArea *dismountGoal ) { } // climb the given ladder to the top and dismount
virtual void DescendLadder( const CNavLadder *ladder, const CNavArea *dismountGoal ) { } // descend the given ladder to the bottom and dismount
virtual bool IsUsingLadder( void ) const; // we are moving to get on, ascending/descending, and/or dismounting a ladder
virtual bool IsAscendingOrDescendingLadder( void ) const; // we are actually on the ladder right now, either climbing up or down
virtual bool IsAbleToAutoCenterOnLadder( void ) const { return false; }
virtual void FaceTowards( const Vector &target ) { } // rotate body to face towards "target"
virtual void SetDesiredLean( const QAngle &lean ) { }
virtual const QAngle &GetDesiredLean( void ) const;
//
// Locomotion information
//
virtual bool IsAbleToJumpAcrossGaps( void ) const; // return true if this bot can jump across gaps in its path
virtual bool IsAbleToClimb( void ) const; // return true if this bot can climb arbitrary geometry it encounters
virtual const Vector &GetFeet( void ) const; // return position of "feet" - the driving point where the bot contacts the ground
virtual float GetStepHeight( void ) const; // if delta Z is greater than this, we have to jump to get up
virtual float GetMaxJumpHeight( void ) const; // return maximum height of a jump
virtual float GetDeathDropHeight( void ) const; // distance at which we will die if we fall
virtual float GetRunSpeed( void ) const; // get maximum running speed
virtual float GetWalkSpeed( void ) const; // get maximum walking speed
virtual float GetMaxAcceleration( void ) const; // return maximum acceleration of locomotor
virtual float GetMaxDeceleration( void ) const; // return maximum deceleration of locomotor
virtual const Vector &GetVelocity( void ) const; // return current world space velocity
virtual float GetSpeed( void ) const; // return current world space speed (magnitude of velocity)
virtual const Vector &GetMotionVector( void ) const; // return unit vector describing our direction of motion - even if we are currently not moving
virtual bool IsAreaTraversable( const CNavArea *baseArea ) const; // return true if given area can be used for navigation
virtual float GetTraversableSlopeLimit( void ) const; // return Z component of unit normal of steepest traversable slope
// return true if the given entity can be ignored during locomotion
enum TraverseWhenType
{
IMMEDIATELY, // the entity will not block our motion - we'll carry right through
EVENTUALLY // the entity will block us until we spend effort to open/destroy it
};
/**
* Return true if this locomotor could potentially move along the line given.
* If false is returned, fraction of walkable ray is returned in 'fraction'
*/
virtual bool IsPotentiallyTraversable( const Vector &from, const Vector &to, TraverseWhenType when = EVENTUALLY, float *fraction = NULL ) const;
/**
* Return true if there is a possible "gap" that will need to be jumped over
* If true is returned, fraction of ray before gap is returned in 'fraction'
*/
virtual bool HasPotentialGap( const Vector &from, const Vector &to, float *fraction = NULL ) const;
// return true if there is a "gap" here when moving in the given direction
virtual bool IsGap( const Vector &pos, const Vector &forward ) const;
virtual bool IsEntityTraversable( CBaseEntity *obstacle, TraverseWhenType when = EVENTUALLY ) const;
//
// Stuck state. If the locomotor cannot make progress, it becomes "stuck" and can only leave
// this stuck state by successfully moving and becoming un-stuck.
//
virtual bool IsStuck( void ) const; // return true if bot is stuck
virtual float GetStuckDuration( void ) const; // return how long we've been stuck
virtual void ClearStuckStatus( const char *reason = "" ); // reset stuck status to un-stuck
virtual bool IsAttemptingToMove( void ) const; // return true if we have tried to Approach() or DriveTo() very recently
void TraceHull( const Vector& start, const Vector& end, const Vector &mins, const Vector &maxs, unsigned int fMask, ITraceFilter *pFilter, trace_t *pTrace ) const;
/**
* Should we collide with this entity?
*/
virtual bool ShouldCollideWith( const CBaseEntity *object ) const { return true; }
protected:
virtual void AdjustPosture( const Vector &moveGoal );
virtual void StuckMonitor( void );
private:
Vector m_motionVector;
Vector m_groundMotionVector;
float m_speed;
float m_groundSpeed;
// stuck monitoring
bool m_isStuck; // if true, we are stuck
IntervalTimer m_stuckTimer; // how long we've been stuck
CountdownTimer m_stillStuckTimer; // for resending stuck events
Vector m_stuckPos; // where we got stuck
IntervalTimer m_moveRequestTimer;
};
inline bool ILocomotion::IsAbleToJumpAcrossGaps( void ) const
{
return true;
}
inline bool ILocomotion::IsAbleToClimb( void ) const
{
return true;
}
inline bool ILocomotion::IsAttemptingToMove( void ) const
{
return m_moveRequestTimer.HasStarted() && m_moveRequestTimer.GetElapsedTime() < 0.25f;
}
inline bool ILocomotion::IsScrambling( void ) const
{
return !IsOnGround() || IsClimbingOrJumping() || IsAscendingOrDescendingLadder();
}
inline bool ILocomotion::IsClimbingOrJumping( void ) const
{
return false;
}
inline bool ILocomotion::IsClimbingUpToLedge( void ) const
{
return false;
}
inline bool ILocomotion::IsJumpingAcrossGap( void ) const
{
return false;
}
inline bool ILocomotion::IsRunning( void ) const
{
return false;
}
inline float ILocomotion::GetDesiredSpeed( void ) const
{
return 0.0f;
}
inline bool ILocomotion::IsOnGround( void ) const
{
return false;
}
inline CBaseEntity *ILocomotion::GetGround( void ) const
{
return NULL;
}
inline const Vector &ILocomotion::GetGroundNormal( void ) const
{
return vec3_origin;
}
inline float ILocomotion::GetGroundSpeed( void ) const
{
return m_groundSpeed;
}
inline const Vector & ILocomotion::GetGroundMotionVector( void ) const
{
return m_groundMotionVector;
}
inline bool ILocomotion::IsUsingLadder( void ) const
{
return false;
}
inline bool ILocomotion::IsAscendingOrDescendingLadder( void ) const
{
return false;
}
inline const QAngle &ILocomotion::GetDesiredLean( void ) const
{
return vec3_angle;
}
inline float ILocomotion::GetStepHeight( void ) const
{
return 0.0f;
}
inline float ILocomotion::GetMaxJumpHeight( void ) const
{
return 0.0f;
}
inline float ILocomotion::GetDeathDropHeight( void ) const
{
return 0.0f;
}
inline float ILocomotion::GetRunSpeed( void ) const
{
return 0.0f;
}
inline float ILocomotion::GetWalkSpeed( void ) const
{
return 0.0f;
}
inline float ILocomotion::GetMaxAcceleration( void ) const
{
return 0.0f;
}
inline float ILocomotion::GetMaxDeceleration( void ) const
{
return 0.0f;
}
inline const Vector &ILocomotion::GetVelocity( void ) const
{
return vec3_origin;
}
inline float ILocomotion::GetSpeed( void ) const
{
return m_speed;
}
inline const Vector & ILocomotion::GetMotionVector( void ) const
{
return m_motionVector;
}
inline float ILocomotion::GetTraversableSlopeLimit( void ) const
{
return 0.6;
}
inline bool ILocomotion::IsStuck( void ) const
{
return m_isStuck;
}
inline float ILocomotion::GetStuckDuration( void ) const
{
return ( IsStuck() ) ? m_stuckTimer.GetElapsedTime() : 0.0f;
}
inline void ILocomotion::TraceHull( const Vector& start, const Vector& end, const Vector &mins, const Vector &maxs, unsigned int fMask, ITraceFilter *pFilter, trace_t *pTrace ) const
{
// VPROF_BUDGET( "ILocomotion::TraceHull", "TraceHull" );
Ray_t ray;
ray.Init( start, end, mins, maxs );
enginetrace->TraceRay( ray, fMask, pFilter, pTrace );
}
#endif // _NEXT_BOT_LOCOMOTION_INTERFACE_H_
+892
View File
@@ -0,0 +1,892 @@
// NextBotManager.cpp
// Author: Michael Booth, May 2006
//========= Copyright Valve Corporation, All rights reserved. ============//
#include "cbase.h"
#include "NextBotManager.h"
#include "NextBotInterface.h"
#ifdef TERROR
#include "ZombieBot/Infected/Infected.h"
#include "ZombieBot/Witch/Witch.h"
#include "ZombieManager.h"
#endif
#include "SharedFunctorUtils.h"
//#include "../../common/blackbox_helper.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
extern ConVar ZombieMobMaxSize;
ConVar nb_update_frequency( "nb_update_frequency", ".1", FCVAR_CHEAT );
ConVar nb_update_framelimit( "nb_update_framelimit", ( IsDebug() ) ? "30" : "15", FCVAR_CHEAT );
ConVar nb_update_maxslide( "nb_update_maxslide", "2", FCVAR_CHEAT );
ConVar nb_update_debug( "nb_update_debug", "0", FCVAR_CHEAT );
//---------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------
/**
* Singleton accessor.
* By returning a reference, we guarantee construction of the
* instance before its first use.
*/
NextBotManager &TheNextBots( void )
{
if ( NextBotManager::GetInstance() )
{
return *NextBotManager::GetInstance();
}
else
{
static NextBotManager manager;
NextBotManager::SetInstance( &manager );
return manager;
}
}
NextBotManager* NextBotManager::sInstance = NULL;
//---------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------
static const char *debugTypeName[] =
{
"BEHAVIOR",
"LOOK_AT",
"PATH",
"ANIMATION",
"LOCOMOTION",
"VISION",
"HEARING",
"EVENTS",
"ERRORS",
NULL
};
static void CC_SetDebug( const CCommand &args )
{
if ( args.ArgC() < 2 )
{
Msg( "Debugging stopped\n" );
TheNextBots().SetDebugTypes( NEXTBOT_DEBUG_NONE );
return;
}
int debugType = 0;
for( int i=1; i<args.ArgC(); ++i )
{
int type;
for( type = 0; debugTypeName[ type ]; ++type )
{
const char *token = args[i];
// special token that means "all"
if ( token[0] == '*' )
{
debugType = NEXTBOT_DEBUG_ALL;
break;
}
if ( !Q_strnicmp( args[i], debugTypeName[ type ], Q_strlen( args[1] ) ) )
{
debugType |= ( 1 << type );
break;
}
}
if ( !debugTypeName[ type ] )
{
Msg( "Invalid debug type '%s'\n", args[i] );
}
}
// enable debugging
TheNextBots().SetDebugTypes( ( NextBotDebugType ) debugType );
}
static ConCommand SetDebug( "nb_debug", CC_SetDebug, "Debug NextBots. Categories are: BEHAVIOR, LOOK_AT, PATH, ANIMATION, LOCOMOTION, VISION, HEARING, EVENTS, ERRORS.", FCVAR_CHEAT );
//---------------------------------------------------------------------------------------------
static void CC_SetDebugFilter( const CCommand &args )
{
if ( args.ArgC() < 2 )
{
Msg( "Debug filter cleared.\n" );
TheNextBots().DebugFilterClear();
return;
}
for( int i=1; i<args.ArgC(); ++i )
{
int index = Q_atoi( args[i] );
if ( index > 0 )
{
TheNextBots().DebugFilterAdd( index );
}
else
{
TheNextBots().DebugFilterAdd( args[i] );
}
}
}
static ConCommand SetDebugFilter( "nb_debug_filter", CC_SetDebugFilter, "Add items to the NextBot debug filter. Items can be entindexes or part of the indentifier of one or more bots.", FCVAR_CHEAT );
//---------------------------------------------------------------------------------------------
class Selector
{
public:
Selector( CBasePlayer *player, bool useLOS )
{
m_player = player;
player->EyeVectors( &m_forward );
m_pick = NULL;
m_pickRange = 99999999999999.9f;
m_useLOS = useLOS;
}
bool operator() ( INextBot *bot )
{
CBaseCombatCharacter *botEntity = bot->GetEntity();
if ( botEntity->IsAlive() )
{
Vector to = botEntity->WorldSpaceCenter() - m_player->EyePosition();
float range = to.NormalizeInPlace();
if ( DotProduct( m_forward, to ) > 0.98f && range < m_pickRange )
{
if ( !m_useLOS || m_player->IsAbleToSee( botEntity, CBaseCombatCharacter::DISREGARD_FOV ) )
{
m_pick = bot;
m_pickRange = range;
}
}
}
return true;
}
CBasePlayer *m_player;
Vector m_forward;
INextBot *m_pick;
float m_pickRange;
bool m_useLOS;
};
static void CC_SelectBot( const CCommand &args )
{
CBasePlayer *player = UTIL_GetListenServerHost();
if ( player )
{
Selector select( player, false );
TheNextBots().ForEachBot( select );
TheNextBots().Select( select.m_pick );
if ( select.m_pick )
{
NDebugOverlay::Circle( select.m_pick->GetLocomotionInterface()->GetFeet() + Vector( 0, 0, 5 ), Vector( 1, 0, 0 ), Vector( 0, -1, 0 ), 25.0f, 0, 255, 0, 255, false, 1.0f );
}
}
}
static ConCommand SelectBot( "nb_select", CC_SelectBot, "Select the bot you are aiming at for further debug operations.", FCVAR_CHEAT );
//---------------------------------------------------------------------------------------------
static void CC_ForceLookAt( const CCommand &args )
{
CBasePlayer *player = UTIL_GetListenServerHost();
INextBot *pick = TheNextBots().GetSelected();
if ( player && pick )
{
pick->GetBodyInterface()->AimHeadTowards( player, IBody::CRITICAL, 9999999.9f, NULL, "Aim forced" );
}
}
static ConCommand ForceLookAt( "nb_force_look_at", CC_ForceLookAt, "Force selected bot to look at the local player's position", FCVAR_CHEAT );
//--------------------------------------------------------------------------------------------------------
void CC_WarpSelectedHere( const CCommand &args )
{
CBasePlayer *me = dynamic_cast< CBasePlayer * >( UTIL_GetCommandClient() );
INextBot *pick = TheNextBots().GetSelected();
if ( me == NULL || pick == NULL )
{
return;
}
Vector forward;
me->EyeVectors( &forward );
trace_t result;
UTIL_TraceLine( me->EyePosition(), me->EyePosition() + 999999.9f * forward, MASK_BLOCKLOS_AND_NPCS|CONTENTS_IGNORE_NODRAW_OPAQUE, me, COLLISION_GROUP_NONE, &result );
if ( result.DidHit() )
{
Vector spot = result.endpos + Vector( 0, 0, 10.0f );
pick->GetEntity()->Teleport( &spot, &vec3_angle, &vec3_origin );
}
}
static ConCommand WarpSelectedHere( "nb_warp_selected_here", CC_WarpSelectedHere, "Teleport the selected bot to your cursor position", FCVAR_CHEAT );
//---------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------
NextBotManager::NextBotManager( void )
{
m_debugType = 0;
m_selectedBot = NULL;
m_iUpdateTickrate = 0;
}
//---------------------------------------------------------------------------------------------
NextBotManager::~NextBotManager()
{
}
//---------------------------------------------------------------------------------------------
/**
* Reset to initial state
*/
void NextBotManager::Reset( void )
{
// remove the NextBots that should go away during a reset (they will unregister themselves as they go)
int i = m_botList.Head();
while ( i != m_botList.InvalidIndex() )
{
int iNext = m_botList.Next( i );
if ( m_botList[i]->IsRemovedOnReset() )
{
UTIL_Remove( m_botList[i]->GetEntity() );
//Assert( !m_botList.IsInList( i ) ); // UTIL_Remove() calls UpdateOnRemove, adds EFL_KILLME, but doesn't delete until the end of the frame
}
i = iNext;
}
m_selectedBot = NULL;
}
//---------------------------------------------------------------------------------------------
inline bool IsDead( INextBot *pBot )
{
CBaseCombatCharacter *pEntity = pBot->GetEntity();
if ( pEntity )
{
if ( pEntity->IsPlayer() && pEntity->m_lifeState == LIFE_DEAD )
{
return true;
}
if ( pEntity->IsMarkedForDeletion() )
{
return true;
}
if ( pEntity->m_pfnThink == &CBaseEntity::SUB_Remove )
{
return true;
}
}
return false;
}
//---------------------------------------------------------------------------------------------
// Debug stats for update balancing
static int g_nRun;
static int g_nSlid;
static int g_nBlockedSlides;
void NextBotManager::Update( void )
{
// do lightweight upkeep every tick
for( int u=m_botList.Head(); u != m_botList.InvalidIndex(); u = m_botList.Next( u ) )
{
m_botList[ u ]->Upkeep();
}
// schedule full updates
if ( m_botList.Count() )
{
static int iCurFrame = -1;
if ( iCurFrame != gpGlobals->framecount )
{
iCurFrame = gpGlobals->framecount;
m_SumFrameTime = 0;
}
else
{
// Don't run multiple ticks in a frame
return;
}
int tickRate = TIME_TO_TICKS( nb_update_frequency.GetFloat() );
if ( tickRate < 0 )
{
tickRate = 0;
}
if ( m_iUpdateTickrate != tickRate )
{
Msg( "NextBot tickrate changed from %d (%.3fms) to %d (%.3fms)\n", m_iUpdateTickrate, TICKS_TO_TIME( m_iUpdateTickrate ), tickRate, TICKS_TO_TIME( tickRate ) );
m_iUpdateTickrate = tickRate;
}
int i = 0;
int nScheduled = 0;
int nNonResponsive = 0;
int nDead = 0;
if ( m_iUpdateTickrate > 0 )
{
INextBot *pBot;
// Count dead bots, they won't update and balancing calculations should exclude them
for( i = m_botList.Head(); i != m_botList.InvalidIndex(); i = m_botList.Next( i ) )
{
if ( IsDead( m_botList[i] ) )
{
nDead++;
}
}
int nTargetToRun = ceilf( (float)( m_botList.Count() - nDead ) / (float)m_iUpdateTickrate );
int curtickcount = gpGlobals->tickcount;
for( i = m_botList.Head(); nTargetToRun && i != m_botList.InvalidIndex(); i = m_botList.Next( i ) )
{
pBot = m_botList[i];
if ( pBot->IsFlaggedForUpdate() )
{
// Was offered a run last tick but didn't take it, push it back
// Leave the flag set so that bot will run right away later, but be ignored
// until then
nNonResponsive++;
}
else
{
if ( curtickcount - pBot->GetTickLastUpdate() < m_iUpdateTickrate )
{
break;
}
if ( !IsDead( pBot ) )
{
pBot->FlagForUpdate();
nTargetToRun--;
nScheduled++;
}
}
}
}
else
{
nScheduled = m_botList.Count();
}
if ( nb_update_debug.GetBool() )
{
int nIntentionalSliders = 0;
if ( m_iUpdateTickrate > 0 )
{
for( ; i != m_botList.InvalidIndex(); i = m_botList.Next( i ) )
{
if ( gpGlobals->tickcount - m_botList[i]->GetTickLastUpdate() >= m_iUpdateTickrate )
{
nIntentionalSliders++;
}
}
}
Msg( "Frame %8d/tick %8d: %3d run of %3d, %3d sliders, %3d blocked slides, scheduled %3d for next tick, %3d intentional sliders, %d nonresponsive, %d dead\n", gpGlobals->framecount - 1, gpGlobals->tickcount - 1, g_nRun, m_botList.Count() - nDead, g_nSlid, g_nBlockedSlides, nScheduled, nIntentionalSliders, nNonResponsive, nDead );
g_nRun = g_nSlid = g_nBlockedSlides = 0;
}
}
}
//---------------------------------------------------------------------------------------------
bool NextBotManager::ShouldUpdate( INextBot *bot )
{
if ( m_iUpdateTickrate < 1 )
{
return true;
}
float frameLimit = nb_update_framelimit.GetFloat();
float sumFrameTime = 0;
if ( bot->IsFlaggedForUpdate() )
{
bot->FlagForUpdate( false );
sumFrameTime = m_SumFrameTime * 1000.0;
if ( frameLimit > 0.0f )
{
if ( sumFrameTime < frameLimit )
{
return true;
}
else if ( nb_update_debug.GetBool() )
{
Msg( "Frame %8d/tick %8d: frame out of budget (%.2fms > %.2fms)\n", gpGlobals->framecount, gpGlobals->tickcount, sumFrameTime, frameLimit );
}
}
}
int nTicksSlid = ( gpGlobals->tickcount - bot->GetTickLastUpdate() ) - m_iUpdateTickrate;
if ( nTicksSlid >= nb_update_maxslide.GetInt() )
{
if ( frameLimit == 0.0 || sumFrameTime < nb_update_framelimit.GetFloat() * 2.0 )
{
g_nBlockedSlides++;
return true;
}
}
if ( nb_update_debug.GetBool() )
{
if ( nTicksSlid > 0 )
{
g_nSlid++;
}
}
return false;
}
//---------------------------------------------------------------------------------------------
void NextBotManager::NotifyBeginUpdate( INextBot *bot )
{
if ( nb_update_debug.GetBool() )
{
g_nRun++;
}
m_botList.Unlink( bot->GetBotId() );
m_botList.LinkToTail( bot->GetBotId() );
bot->SetTickLastUpdate( gpGlobals->tickcount );
m_CurUpdateStartTime = Plat_FloatTime();
}
//---------------------------------------------------------------------------------------------
void NextBotManager::NotifyEndUpdate( INextBot *bot )
{
// This might be a good place to detect a particular bot had spiked [3/14/2008 tom]
m_SumFrameTime += Plat_FloatTime() - m_CurUpdateStartTime;
}
//---------------------------------------------------------------------------------------------
/**
* When the server has changed maps
*/
void NextBotManager::OnMapLoaded( void )
{
Reset();
}
//---------------------------------------------------------------------------------------------
/**
* When the scenario restarts
*/
void NextBotManager::OnRoundRestart( void )
{
Reset();
}
//---------------------------------------------------------------------------------------------
int NextBotManager::Register( INextBot *bot )
{
return m_botList.AddToHead( bot );
}
//---------------------------------------------------------------------------------------------
void NextBotManager::UnRegister( INextBot *bot )
{
m_botList.Remove( bot->GetBotId() );
if ( bot == m_selectedBot)
{
// we can't access virtual methods because this is called from a destructor, so just clear it
m_selectedBot = NULL;
}
}
//--------------------------------------------------------------------------------------------------------
void NextBotManager::OnBeginChangeLevel( void )
{
}
//----------------------------------------------------------------------------------------------------------
class NextBotKilledNotifyScan
{
public:
NextBotKilledNotifyScan( CBaseCombatCharacter *victim, const CTakeDamageInfo &info )
{
m_victim = victim;
m_info = info;
}
bool operator() ( INextBot *bot )
{
if ( bot->GetEntity()->IsAlive() && !bot->IsSelf( m_victim ) )
{
bot->OnOtherKilled( m_victim, m_info );
}
return true;
}
CBaseCombatCharacter *m_victim;
CTakeDamageInfo m_info;
};
//---------------------------------------------------------------------------------------------
/**
* When an actor is killed. Propagate to all NextBots.
*/
void NextBotManager::OnKilled( CBaseCombatCharacter *victim, const CTakeDamageInfo &info )
{
NextBotKilledNotifyScan notify( victim, info );
TheNextBots().ForEachBot( notify );
}
//----------------------------------------------------------------------------------------------------------
class NextBotSoundNotifyScan
{
public:
NextBotSoundNotifyScan( CBaseEntity *source, const Vector &pos, KeyValues *keys ) : m_source( source ), m_pos( pos ), m_keys( keys )
{
}
bool operator() ( INextBot *bot )
{
if ( bot->GetEntity()->IsAlive() && !bot->IsSelf( m_source ) )
{
bot->OnSound( m_source, m_pos, m_keys );
}
return true;
}
CBaseEntity *m_source;
const Vector &m_pos;
KeyValues *m_keys;
};
//---------------------------------------------------------------------------------------------
/**
* When an entity emits a sound
*/
void NextBotManager::OnSound( CBaseEntity *source, const Vector &pos, KeyValues *keys )
{
NextBotSoundNotifyScan notify( source, pos, keys );
TheNextBots().ForEachBot( notify );
if ( source && IsDebugging( NEXTBOT_HEARING ) )
{
int r,g,b;
switch( source->GetTeamNumber() )
{
case FIRST_GAME_TEAM: r = 0; g = 255; b = 0; break;
case (FIRST_GAME_TEAM+1): r = 255; g = 0; b = 0; break;
default: r = 255; g = 255; b = 0; break;
}
NDebugOverlay::Circle( pos, Vector( 1, 0, 0 ), Vector( 0, -1, 0 ), 5.0f, r, g, b, 255, true, 3.0f );
}
}
//----------------------------------------------------------------------------------------------------------
class NextBotResponseNotifyScan
{
public:
NextBotResponseNotifyScan( CBaseCombatCharacter *who, AIConcept_t concept, AI_Response *response ) : m_who( who ), m_concept( concept ), m_response( response )
{
}
bool operator() ( INextBot *bot )
{
if ( bot->GetEntity()->IsAlive() )
{
bot->OnSpokeConcept( m_who, m_concept, m_response );
}
return true;
}
CBaseCombatCharacter *m_who;
AIConcept_t m_concept;
AI_Response *m_response;
};
//---------------------------------------------------------------------------------------------
/**
* When an Actor speaks a concept
*/
void NextBotManager::OnSpokeConcept( CBaseCombatCharacter *who, AIConcept_t concept, AI_Response *response )
{
NextBotResponseNotifyScan notify( who, concept, response );
TheNextBots().ForEachBot( notify );
if ( IsDebugging( NEXTBOT_HEARING ) )
{
// const char *who = response->GetCriteria()->GetValue( response->GetCriteria()->FindCriterionIndex( "Who" ) );
// TODO: Need concept.GetStringConcept()
DevMsg( "%3.2f: OnSpokeConcept( %s, %s )\n", gpGlobals->curtime, who->GetDebugName(), "concept.GetStringConcept()" );
}
}
//----------------------------------------------------------------------------------------------------------
class NextBotWeaponFiredNotifyScan
{
public:
NextBotWeaponFiredNotifyScan( CBaseCombatCharacter *who, CBaseCombatWeapon *weapon ) : m_who( who ), m_weapon( weapon )
{
}
bool operator() ( INextBot *bot )
{
if ( bot->GetEntity()->IsAlive() )
{
bot->OnWeaponFired( m_who, m_weapon );
}
return true;
}
CBaseCombatCharacter *m_who;
CBaseCombatWeapon *m_weapon;
};
//---------------------------------------------------------------------------------------------
/**
* When someone fires a weapon
*/
void NextBotManager::OnWeaponFired( CBaseCombatCharacter *whoFired, CBaseCombatWeapon *weapon )
{
NextBotWeaponFiredNotifyScan notify( whoFired, weapon );
TheNextBots().ForEachBot( notify );
if ( IsDebugging( NEXTBOT_EVENTS ) )
{
DevMsg( "%3.2f: OnWeaponFired( %s, %s )\n", gpGlobals->curtime, whoFired->GetDebugName(), weapon->GetName() );
}
}
//---------------------------------------------------------------------------------------------
/**
* Add given entindex to the debug filter
*/
void NextBotManager::DebugFilterAdd( int index )
{
DebugFilter filter;
filter.index = index;
filter.name[0] = '\000';
m_debugFilterList.AddToTail( filter );
}
//---------------------------------------------------------------------------------------------
/**
* Add given name to the debug filter
*/
void NextBotManager::DebugFilterAdd( const char *name )
{
DebugFilter filter;
filter.index = -1;
Q_strncpy( filter.name, name, DebugFilter::MAX_DEBUG_NAME_SIZE );
m_debugFilterList.AddToTail( filter );
}
//---------------------------------------------------------------------------------------------
/**
* Remove given entindex from the debug filter
*/
void NextBotManager::DebugFilterRemove( int index )
{
for( int i=0; i<m_debugFilterList.Count(); ++i )
{
if ( m_debugFilterList[i].index == index )
{
m_debugFilterList.Remove( i );
break;
}
}
}
//---------------------------------------------------------------------------------------------
/**
* Remove given name from the debug filter
*/
void NextBotManager::DebugFilterRemove( const char *name )
{
for( int i=0; i<m_debugFilterList.Count(); ++i )
{
if ( m_debugFilterList[i].name[0] != '\000' &&
!Q_strnicmp( name, m_debugFilterList[i].name, MIN( Q_strlen( name ), sizeof( m_debugFilterList[i].name ) ) ) )
{
m_debugFilterList.Remove( i );
break;
}
}
}
//---------------------------------------------------------------------------------------------
/**
* Clear the debug filter (remove all entries)
*/
void NextBotManager::DebugFilterClear( void )
{
m_debugFilterList.RemoveAll();
}
//---------------------------------------------------------------------------------------------
/**
* Return true if the given bot matches the debug filter
*/
bool NextBotManager::IsDebugFilterMatch( const INextBot *bot ) const
{
// if the filter is empty, all bots match
if ( m_debugFilterList.Count() == 0 )
{
return true;
}
for( int i=0; i<m_debugFilterList.Count(); ++i )
{
// compare entity index
if ( m_debugFilterList[i].index == const_cast< INextBot * >( bot )->GetEntity()->entindex() )
{
return true;
}
// compare debug filter
if ( m_debugFilterList[i].name[0] != '\000' && bot->IsDebugFilterMatch( m_debugFilterList[i].name ) )
{
return true;
}
// compare special keyword meaning local player is looking at them
if ( !Q_strnicmp( m_debugFilterList[i].name, "lookat", Q_strlen( m_debugFilterList[i].name ) ) )
{
CBasePlayer *watcher = UTIL_GetListenServerHost();
if ( watcher )
{
CBaseEntity *subject = watcher->GetObserverTarget();
if ( subject && bot->IsSelf( subject ) )
{
return true;
}
}
}
// compare special keyword meaning NextBot is selected
if ( !Q_strnicmp( m_debugFilterList[i].name, "selected", Q_strlen( m_debugFilterList[i].name ) ) )
{
INextBot *selected = GetSelected();
if ( selected && bot->IsSelf( selected->GetEntity() ) )
{
return true;
}
}
}
return false;
}
//---------------------------------------------------------------------------------------------
/**
* Get the bot under the given player's crosshair
*/
INextBot *NextBotManager::GetBotUnderCrosshair( CBasePlayer *picker )
{
if ( !picker )
return NULL;
const float MaxDot = 0.7f;
const float MaxRange = 4000.0f;
TargetScan< CBaseCombatCharacter > scan( picker, TEAM_ANY, 1.0f - MaxDot, MaxRange );
ForEachCombatCharacter( scan );
CBaseCombatCharacter *target = scan.GetTarget();
if ( target && target->MyNextBotPointer() )
return target->MyNextBotPointer();
return NULL;
}
#ifdef NEED_BLACK_BOX
//---------------------------------------------------------------------------------------------
CON_COMMAND( nb_dump_debug_history, "Dumps debug history for the bot under the cursor to the blackbox" )
{
if ( !NextBotDebugHistory.GetBool() )
{
BlackBox_Record( "bot", "nb_debug_history 0" );
return;
}
CBasePlayer *player = UTIL_GetCommandClient();
if ( !player )
{
player = UTIL_GetListenServerHost();
}
INextBot *bot = TheNextBots().GetBotUnderCrosshair( player );
if ( !bot )
{
BlackBox_Record( "bot", "no bot under crosshairs" );
return;
}
CUtlVector< const INextBot::NextBotDebugLineType * > lines;
bot->GetDebugHistory( (NEXTBOT_DEBUG_ALL & (~NEXTBOT_EVENTS)), &lines );
for ( int i=0; i<lines.Count(); ++i )
{
if ( IsPC() )
{
BlackBox_Record( "bot", "%s", lines[i]->data );
}
}
}
#endif // NEED_BLACK_BOX
//---------------------------------------------------------------------------------------------
void NextBotManager::CollectAllBots( CUtlVector< INextBot * > *botVector )
{
if ( !botVector )
return;
botVector->RemoveAll();
for( int i=m_botList.Head(); i != m_botList.InvalidIndex(); i = m_botList.Next( i ) )
{
botVector->AddToTail( m_botList[i] );
}
}
+211
View File
@@ -0,0 +1,211 @@
// NextBotManager.h
// Author: Michael Booth, May 2006
//========= Copyright Valve Corporation, All rights reserved. ============//
#ifndef _NEXT_BOT_MANAGER_H_
#define _NEXT_BOT_MANAGER_H_
#include "NextBotInterface.h"
class CTerrorPlayer;
//----------------------------------------------------------------------------------------------------------------
/**
* The NextBotManager manager
*/
class NextBotManager
{
public:
NextBotManager( void );
virtual ~NextBotManager();
void Reset( void ); // reset to initial state
virtual void Update( void );
bool ShouldUpdate( INextBot *bot );
void NotifyBeginUpdate( INextBot *bot );
void NotifyEndUpdate( INextBot *bot );
int GetNextBotCount( void ) const; // How many nextbots are alive right now?
/**
* Populate given vector with all bots in the system
*/
void CollectAllBots( CUtlVector< INextBot * > *botVector );
/**
* DEPRECATED: Use CollectAllBots().
* Execute functor for each NextBot in the system.
* If a functor returns false, stop iteration early
* and return false.
*/
template < typename Functor >
bool ForEachBot( Functor &func )
{
for( int i=m_botList.Head(); i != m_botList.InvalidIndex(); i = m_botList.Next( i ) )
{
if ( !func( m_botList[i] ) )
{
return false;
}
}
return true;
}
/**
* DEPRECATED: Use CollectAllBots().
* Execute functor for each NextBot in the system as
* a CBaseCombatCharacter.
* If a functor returns false, stop iteration early
* and return false.
*/
template < typename Functor >
bool ForEachCombatCharacter( Functor &func )
{
for( int i=m_botList.Head(); i != m_botList.InvalidIndex(); i = m_botList.Next( i ) )
{
if ( !func( m_botList[i]->GetEntity() ) )
{
return false;
}
}
return true;
}
/**
* Return closest bot to given point that passes the given filter
*/
template < typename Filter >
INextBot *GetClosestBot( const Vector &pos, Filter &filter )
{
INextBot *close = NULL;
float closeRangeSq = FLT_MAX;
for( int i=m_botList.Head(); i != m_botList.InvalidIndex(); i = m_botList.Next( i ) )
{
float rangeSq = ( m_botList[i]->GetEntity()->GetAbsOrigin() - pos ).LengthSqr();
if ( rangeSq < closeRangeSq && filter( m_botList[i] ) )
{
closeRangeSq = rangeSq;
close = m_botList[i];
}
}
return close;
}
/**
* Event propagators
*/
virtual void OnMapLoaded( void ); // when the server has changed maps
virtual void OnRoundRestart( void ); // when the scenario restarts
virtual void OnBeginChangeLevel( void ); // when the server is about to change maps
virtual void OnKilled( CBaseCombatCharacter *victim, const CTakeDamageInfo &info ); // when an actor is killed
virtual void OnSound( CBaseEntity *source, const Vector &pos, KeyValues *keys ); // when an entity emits a sound
virtual void OnSpokeConcept( CBaseCombatCharacter *who, AIConcept_t concept, AI_Response *response ); // when an Actor speaks a concept
virtual void OnWeaponFired( CBaseCombatCharacter *whoFired, CBaseCombatWeapon *weapon ); // when someone fires a weapon
/**
* Debugging
*/
bool IsDebugging( unsigned int type ) const; // return true if debugging system is on for the given type(s)
void SetDebugTypes( NextBotDebugType type ); // start displaying debug info of the given type(s)
void DebugFilterAdd( int index ); // add given entindex to the debug filter
void DebugFilterAdd( const char *name ); // add given name to the debug filter
void DebugFilterRemove( int index ); // remove given entindex from the debug filter
void DebugFilterRemove( const char *name ); // remove given name from the debug filter
void DebugFilterClear( void ); // clear the debug filter (remove all entries)
bool IsDebugFilterMatch( const INextBot *bot ) const; // return true if the given bot matches the debug filter
void Select( INextBot *bot ); // mark bot as selected for further operations
void DeselectAll( void );
INextBot *GetSelected( void ) const;
INextBot *GetBotUnderCrosshair( CBasePlayer *picker ); // Get the bot under the given player's crosshair
//
// Put these in a derived class
//
void OnSurvivorVomitedUpon( CTerrorPlayer *victim ); // when a Survivor has been hit by Boomer Vomit
static void SetInstance( NextBotManager *pInstance ) { sInstance = pInstance; };
static NextBotManager* GetInstance() { return sInstance; }
protected:
static NextBotManager* sInstance;
friend class INextBot;
int Register( INextBot *bot );
void UnRegister( INextBot *bot );
CUtlLinkedList< INextBot * > m_botList; // list of all active NextBots
int m_iUpdateTickrate;
double m_CurUpdateStartTime;
double m_SumFrameTime;
unsigned int m_debugType; // debug flags
struct DebugFilter
{
int index; // entindex
enum { MAX_DEBUG_NAME_SIZE = 128 };
char name[ MAX_DEBUG_NAME_SIZE ];
};
CUtlVector< DebugFilter > m_debugFilterList;
INextBot *m_selectedBot; // selected bot for further debug operations
};
inline int NextBotManager::GetNextBotCount( void ) const
{
return m_botList.Count();
}
inline bool NextBotManager::IsDebugging( unsigned int type ) const
{
if ( type & m_debugType )
{
return true;
}
return false;
}
inline void NextBotManager::SetDebugTypes( NextBotDebugType type )
{
m_debugType = (unsigned int)type;
}
inline void NextBotManager::Select( INextBot *bot )
{
m_selectedBot = bot;
}
inline void NextBotManager::DeselectAll( void )
{
m_selectedBot = NULL;
}
inline INextBot *NextBotManager::GetSelected( void ) const
{
return m_selectedBot;
}
// singleton accessor
extern NextBotManager &TheNextBots( void );
#endif // _NEXT_BOT_MANAGER_H_
+278
View File
@@ -0,0 +1,278 @@
// NextBotUtil.h
// Utilities for the NextBot system
// Author: Michael Booth, May 2006
//========= Copyright Valve Corporation, All rights reserved. ============//
#ifndef _NEXT_BOT_UTIL_H_
#define _NEXT_BOT_UTIL_H_
#include "NextBotLocomotionInterface.h"
#include "nav_area.h"
#include "nav_mesh.h"
#include "nav_pathfind.h"
//--------------------------------------------------------------------------------------------
/**
* A simple filter interface for various NextBot queries
*/
class INextBotEntityFilter
{
public:
// return true if the given entity passes this filter
virtual bool IsAllowed( CBaseEntity *entity ) const = 0;
};
// trace filter callback functions. needed for use with the querycache/optimization functionality
bool VisionTraceFilterFunction( IHandleEntity *pServerEntity, int contentsMask );
bool IgnoreActorsTraceFilterFunction( IHandleEntity *pServerEntity, int contentsMask );
//--------------------------------------------------------------------------------------------
/**
* Trace filter that skips all players and NextBots
*/
class NextBotTraceFilterIgnoreActors : public CTraceFilterSimple
{
public:
NextBotTraceFilterIgnoreActors( const IHandleEntity *passentity, int collisionGroup ) : CTraceFilterSimple( passentity, collisionGroup, IgnoreActorsTraceFilterFunction )
{
}
};
//--------------------------------------------------------------------------------------------
/**
* Trace filter that skips all players, NextBots, and non-LOS blockers
*/
class NextBotVisionTraceFilter : public CTraceFilterSimple
{
public:
NextBotVisionTraceFilter( const IHandleEntity *passentity, int collisionGroup ) : CTraceFilterSimple( passentity, collisionGroup, VisionTraceFilterFunction )
{
}
};
//--------------------------------------------------------------------------------------------
/**
* Trace filter that skips all NextBots, but includes Players
*/
class NextBotTraceFilterIgnoreNextBots : public CTraceFilterSimple
{
public:
NextBotTraceFilterIgnoreNextBots( const IHandleEntity *passentity, int collisionGroup )
: CTraceFilterSimple( passentity, collisionGroup )
{
}
virtual bool ShouldHitEntity( IHandleEntity *pServerEntity, int contentsMask )
{
if ( CTraceFilterSimple::ShouldHitEntity( pServerEntity, contentsMask ) )
{
CBaseEntity *entity = EntityFromEntityHandle( pServerEntity );
#ifdef TERROR
CBasePlayer *player = ToBasePlayer( entity );
if ( player && player->IsGhost() )
return false;
#endif // TERROR
return ( entity->MyNextBotPointer() == NULL );
}
return false;
}
};
//--------------------------------------------------------------------------------------------
/**
* Trace filter that obeys INextBot::IsAbleToBlockMovementOf()
*/
class NextBotTraceFilter : public CTraceFilterSimple
{
public:
NextBotTraceFilter( const IHandleEntity *passentity, int collisionGroup )
: CTraceFilterSimple( passentity, collisionGroup )
{
CBaseEntity *entity = const_cast<CBaseEntity *>(EntityFromEntityHandle( passentity ));
m_passBot = entity->MyNextBotPointer();
}
virtual bool ShouldHitEntity( IHandleEntity *pServerEntity, int contentsMask )
{
if ( CTraceFilterSimple::ShouldHitEntity( pServerEntity, contentsMask ) )
{
CBaseEntity *entity = EntityFromEntityHandle( pServerEntity );
#ifdef TERROR
CBasePlayer *player = ToBasePlayer( entity );
if ( player && player->IsGhost() )
return false;
#endif // TERROR
// Skip players on the same team - they're not solid to us, and we'll avoid them
if ( entity->IsPlayer() && m_passBot && m_passBot->GetEntity() &&
m_passBot->GetEntity()->GetTeamNumber() == entity->GetTeamNumber() )
return false;
INextBot *bot = entity->MyNextBotPointer();
return ( !bot || bot->IsAbleToBlockMovementOf( m_passBot ) );
}
return false;
}
const INextBot *m_passBot;
};
//--------------------------------------------------------------------------------------------
/**
* Trace filter that only hits players and NextBots
*/
class NextBotTraceFilterOnlyActors : public CTraceFilterSimple
{
public:
NextBotTraceFilterOnlyActors( const IHandleEntity *passentity, int collisionGroup )
: CTraceFilterSimple( passentity, collisionGroup )
{
}
virtual TraceType_t GetTraceType() const
{
return TRACE_ENTITIES_ONLY;
}
virtual bool ShouldHitEntity( IHandleEntity *pServerEntity, int contentsMask )
{
if ( CTraceFilterSimple::ShouldHitEntity( pServerEntity, contentsMask ) )
{
CBaseEntity *entity = EntityFromEntityHandle( pServerEntity );
#ifdef TERROR
CBasePlayer *player = ToBasePlayer( entity );
if ( player && player->IsGhost() )
return false;
#endif // TERROR
return ( entity->MyNextBotPointer() || entity->IsPlayer() );
}
return false;
}
};
//--------------------------------------------------------------------------------------------
/**
* Trace filter that skips "traversable" entities. The "when" argument creates
* a temporal context for asking if an entity is IMMEDIATELY traversable (like thin
* glass that just breaks as we walk through it) or EVENTUALLY traversable (like a
* breakable object that will take some time to break through)
*/
class NextBotTraversableTraceFilter : public CTraceFilterSimple
{
public:
NextBotTraversableTraceFilter( INextBot *bot, ILocomotion::TraverseWhenType when = ILocomotion::EVENTUALLY ) : CTraceFilterSimple( bot->GetEntity(), COLLISION_GROUP_NONE )
{
m_bot = bot;
m_when = when;
}
virtual bool ShouldHitEntity( IHandleEntity *pServerEntity, int contentsMask )
{
CBaseEntity *entity = EntityFromEntityHandle( pServerEntity );
if ( m_bot->IsSelf( entity ) )
{
return false;
}
if ( CTraceFilterSimple::ShouldHitEntity( pServerEntity, contentsMask ) )
{
return !m_bot->GetLocomotionInterface()->IsEntityTraversable( entity, m_when );
}
return false;
}
private:
INextBot *m_bot;
ILocomotion::TraverseWhenType m_when;
};
//---------------------------------------------------------------------------------------------
/**
* Given a vector of entities, a nav area, and a max travel distance, return
* the entity that has the shortest travel distance.
*/
inline CBaseEntity *SelectClosestEntityByTravelDistance( INextBot *me, const CUtlVector< CBaseEntity * > &candidateEntities, CNavArea *startArea, float travelRange )
{
// collect nearby walkable areas within travelRange
CUtlVector< CNavArea * > nearbyAreaVector;
CollectSurroundingAreas( &nearbyAreaVector, startArea, travelRange, me->GetLocomotionInterface()->GetStepHeight(), me->GetLocomotionInterface()->GetDeathDropHeight() );
// find closest entity in the collected area set
CBaseEntity *closeEntity = NULL;
float closeTravelRange = FLT_MAX;
for( int i=0; i<candidateEntities.Count(); ++i )
{
CBaseEntity *candidate = candidateEntities[i];
CNavArea *area = TheNavMesh->GetNearestNavArea( candidate, GETNAVAREA_CHECK_LOS, 500.0f );
if ( area && area->IsMarked() && area->GetCostSoFar() < closeTravelRange )
{
closeEntity = candidate;
closeTravelRange = area->GetCostSoFar();
}
}
return closeEntity;
}
#ifdef OBSOLETE
//--------------------------------------------------------------------------------------------
/**
* Trace filter that skips "traversable" entities, but hits other Actors.
* Used for obstacle avoidance.
*/
class NextBotMovementAvoidanceTraceFilter : public CTraceFilterSimple
{
public:
NextBotMovementAvoidanceTraceFilter( INextBot *bot ) : CTraceFilterSimple( bot->GetEntity(), COLLISION_GROUP_NONE )
{
m_bot = bot;
}
virtual bool ShouldHitEntity( IHandleEntity *pServerEntity, int contentsMask )
{
CBaseEntity *entity = EntityFromEntityHandle( pServerEntity );
#ifdef TERROR
CBasePlayer *player = ToBasePlayer( entity );
if ( player && player->IsGhost() )
return false;
#endif // TERROR
if ( m_bot->IsSelf( entity ) )
{
return false;
}
if ( CTraceFilterSimple::ShouldHitEntity( pServerEntity, contentsMask ) )
{
return !m_bot->GetLocomotionInterface()->IsEntityTraversable( entity, ILocomotion::IMMEDIATELY );
}
return false;
}
private:
INextBot *m_bot;
};
#endif
#endif // _NEXT_BOT_UTIL_H_
@@ -0,0 +1,802 @@
// NextBotVisionInterface.cpp
// Implementation of common vision system
// Author: Michael Booth, May 2006
//========= Copyright Valve Corporation, All rights reserved. ============//
#include "cbase.h"
#include "nav.h"
#include "functorutils.h"
#include "NextBot.h"
#include "NextBotVisionInterface.h"
#include "NextBotBodyInterface.h"
#include "NextBotUtil.h"
#ifdef TERROR
#include "querycache.h"
#endif
#include "tier0/vprof.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
ConVar nb_blind( "nb_blind", "0", FCVAR_CHEAT, "Disable vision" );
ConVar nb_debug_known_entities( "nb_debug_known_entities", "0", FCVAR_CHEAT, "Show the 'known entities' for the bot that is the current spectator target" );
//------------------------------------------------------------------------------------------
IVision::IVision( INextBot *bot ) : INextBotComponent( bot )
{
Reset();
}
//------------------------------------------------------------------------------------------
/**
* Reset to initial state
*/
void IVision::Reset( void )
{
INextBotComponent::Reset();
m_knownEntityVector.RemoveAll();
m_lastVisionUpdateTimestamp = 0.0f;
m_primaryThreat = NULL;
m_FOV = GetDefaultFieldOfView();
m_cosHalfFOV = cos( 0.5f * m_FOV * M_PI / 180.0f );
for( int i=0; i<MAX_TEAMS; ++i )
{
m_notVisibleTimer[i].Invalidate();
}
}
//------------------------------------------------------------------------------------------
/**
* Ask the current behavior to select the most dangerous threat from
* our set of currently known entities
* TODO: Find a semantically better place for this to live.
*/
const CKnownEntity *IVision::GetPrimaryKnownThreat( bool onlyVisibleThreats ) const
{
if ( m_knownEntityVector.Count() == 0 )
return NULL;
const CKnownEntity *threat = NULL;
int i;
// find the first valid entity
for( i=0; i<m_knownEntityVector.Count(); ++i )
{
const CKnownEntity &firstThreat = m_knownEntityVector[i];
// check in case status changes between updates
if ( IsAwareOf( firstThreat ) && !firstThreat.IsObsolete() && !IsIgnored( firstThreat.GetEntity() ) && GetBot()->IsEnemy( firstThreat.GetEntity() ) )
{
if ( !onlyVisibleThreats || firstThreat.IsVisibleRecently() )
{
threat = &firstThreat;
break;
}
}
}
if ( threat == NULL )
{
m_primaryThreat = NULL;
return NULL;
}
for( ++i; i<m_knownEntityVector.Count(); ++i )
{
const CKnownEntity &newThreat = m_knownEntityVector[i];
// check in case status changes between updates
if ( IsAwareOf( newThreat ) && !newThreat.IsObsolete() && !IsIgnored( newThreat.GetEntity() ) && GetBot()->IsEnemy( newThreat.GetEntity() ) )
{
if ( !onlyVisibleThreats || newThreat.IsVisibleRecently() )
{
threat = GetBot()->GetIntentionInterface()->SelectMoreDangerousThreat( GetBot(), GetBot()->GetEntity(), threat, &newThreat );
}
}
}
// cache off threat
m_primaryThreat = threat ? threat->GetEntity() : NULL;
return threat;
}
//------------------------------------------------------------------------------------------
/**
* Return the closest recognized entity
*/
const CKnownEntity *IVision::GetClosestKnown( int team ) const
{
const Vector &myPos = GetBot()->GetPosition();
const CKnownEntity *close = NULL;
float closeRange = 999999999.9f;
for( int i=0; i < m_knownEntityVector.Count(); ++i )
{
const CKnownEntity &known = m_knownEntityVector[i];
if ( !known.IsObsolete() && IsAwareOf( known ) )
{
if ( team == TEAM_ANY || known.GetEntity()->GetTeamNumber() == team )
{
Vector to = known.GetLastKnownPosition() - myPos;
float rangeSq = to.LengthSqr();
if ( rangeSq < closeRange )
{
close = &known;
closeRange = rangeSq;
}
}
}
}
return close;
}
//------------------------------------------------------------------------------------------
/**
* Return the closest recognized entity that passes the given filter
*/
const CKnownEntity *IVision::GetClosestKnown( const INextBotEntityFilter &filter ) const
{
const Vector &myPos = GetBot()->GetPosition();
const CKnownEntity *close = NULL;
float closeRange = 999999999.9f;
for( int i=0; i < m_knownEntityVector.Count(); ++i )
{
const CKnownEntity &known = m_knownEntityVector[i];
if ( !known.IsObsolete() && IsAwareOf( known ) )
{
if ( filter.IsAllowed( known.GetEntity() ) )
{
Vector to = known.GetLastKnownPosition() - myPos;
float rangeSq = to.LengthSqr();
if ( rangeSq < closeRange )
{
close = &known;
closeRange = rangeSq;
}
}
}
}
return close;
}
//------------------------------------------------------------------------------------------
/**
* Given an entity, return our known version of it (or NULL if we don't know of it)
*/
const CKnownEntity *IVision::GetKnown( const CBaseEntity *entity ) const
{
if ( entity == NULL )
return NULL;
for( int i=0; i < m_knownEntityVector.Count(); ++i )
{
const CKnownEntity &known = m_knownEntityVector[i];
if ( known.GetEntity() && known.GetEntity()->entindex() == entity->entindex() && !known.IsObsolete() )
{
return &known;
}
}
return NULL;
}
//------------------------------------------------------------------------------------------
/**
* Introduce a known entity into the system. Its position is assumed to be known
* and will be updated, and it is assumed to not yet have been seen by us, allowing for learning
* of known entities by being told about them, hearing them, etc.
*/
void IVision::AddKnownEntity( CBaseEntity *entity )
{
if ( entity == NULL || entity->IsWorld() )
{
// the world is not an entity we can deal with
return;
}
CKnownEntity known( entity );
// only add it if we don't already know of it
if ( m_knownEntityVector.Find( known ) == m_knownEntityVector.InvalidIndex() )
{
m_knownEntityVector.AddToTail( known );
}
}
//------------------------------------------------------------------------------------------
// Remove the given entity from our awareness (whether we know if it or not)
// Useful if we've moved to where we last saw the entity, but it's not there any longer.
void IVision::ForgetEntity( CBaseEntity *forgetMe )
{
if ( !forgetMe )
return;
FOR_EACH_VEC( m_knownEntityVector, it )
{
const CKnownEntity &known = m_knownEntityVector[ it ];
if ( known.GetEntity() && known.GetEntity()->entindex() == forgetMe->entindex() )
{
m_knownEntityVector.FastRemove( it );
return;
}
}
}
//------------------------------------------------------------------------------------------
void IVision::ForgetAllKnownEntities( void )
{
m_knownEntityVector.RemoveAll();
}
//------------------------------------------------------------------------------------------
/**
* Return the number of entity on the given team known to us closer than rangeLimit
*/
int IVision::GetKnownCount( int team, bool onlyVisible, float rangeLimit ) const
{
int count = 0;
FOR_EACH_VEC( m_knownEntityVector, it )
{
const CKnownEntity &known = m_knownEntityVector[ it ];
if ( !known.IsObsolete() && IsAwareOf( known ) )
{
if ( team == TEAM_ANY || known.GetEntity()->GetTeamNumber() == team )
{
if ( !onlyVisible || known.IsVisibleRecently() )
{
if ( rangeLimit < 0.0f || GetBot()->IsRangeLessThan( known.GetLastKnownPosition(), rangeLimit ) )
{
++count;
}
}
}
}
}
return count;
}
//------------------------------------------------------------------------------------------
class PopulateVisibleVector
{
public:
PopulateVisibleVector( CUtlVector< CBaseEntity * > *potentiallyVisible )
{
m_potentiallyVisible = potentiallyVisible;
}
bool operator() ( CBaseEntity *actor )
{
m_potentiallyVisible->AddToTail( actor );
return true;
}
CUtlVector< CBaseEntity * > *m_potentiallyVisible;
};
//------------------------------------------------------------------------------------------
/**
* Populate "potentiallyVisible" with the set of all entities we could potentially see.
* Entities in this set will be tested for visibility/recognition in IVision::Update()
*/
void IVision::CollectPotentiallyVisibleEntities( CUtlVector< CBaseEntity * > *potentiallyVisible )
{
potentiallyVisible->RemoveAll();
// by default, only consider players and other bots as potentially visible
PopulateVisibleVector populate( potentiallyVisible );
ForEachActor( populate );
}
//------------------------------------------------------------------------------------------
class CollectVisible
{
public:
CollectVisible( IVision *vision )
{
m_vision = vision;
}
bool operator() ( CBaseEntity *entity )
{
if ( entity &&
!m_vision->IsIgnored( entity ) &&
entity->IsAlive() &&
entity != m_vision->GetBot()->GetEntity() &&
m_vision->IsAbleToSee( entity, IVision::USE_FOV ) )
{
m_recognized.AddToTail( entity );
}
return true;
}
bool Contains( CBaseEntity *entity ) const
{
for( int i=0; i < m_recognized.Count(); ++i )
{
if ( entity->entindex() == m_recognized[ i ]->entindex() )
{
return true;
}
}
return false;
}
IVision *m_vision;
CUtlVector< CBaseEntity * > m_recognized;
};
//------------------------------------------------------------------------------------------
void IVision::UpdateKnownEntities( void )
{
VPROF_BUDGET( "IVision::UpdateKnownEntities", "NextBot" );
// construct set of potentially visible objects
CUtlVector< CBaseEntity * > potentiallyVisible;
CollectPotentiallyVisibleEntities( &potentiallyVisible );
// collect set of visible and recognized entities at this moment
CollectVisible visibleNow( this );
FOR_EACH_VEC( potentiallyVisible, pit )
{
VPROF_BUDGET( "IVision::UpdateKnownEntities( collect visible )", "NextBot" );
if ( visibleNow( potentiallyVisible[ pit ] ) == false )
break;
}
// update known set with new data
{ VPROF_BUDGET( "IVision::UpdateKnownEntities( update status )", "NextBot" );
int i;
for( i=0; i < m_knownEntityVector.Count(); ++i )
{
CKnownEntity &known = m_knownEntityVector[i];
// clear out obsolete knowledge
if ( known.GetEntity() == NULL || known.IsObsolete() )
{
m_knownEntityVector.Remove( i );
--i;
continue;
}
if ( visibleNow.Contains( known.GetEntity() ) )
{
// this visible entity was already known (but perhaps not visible until now)
known.UpdatePosition();
known.UpdateVisibilityStatus( true );
// has our reaction time just elapsed?
if ( gpGlobals->curtime - known.GetTimeWhenBecameVisible() >= GetMinRecognizeTime() &&
m_lastVisionUpdateTimestamp - known.GetTimeWhenBecameVisible() < GetMinRecognizeTime() )
{
if ( GetBot()->IsDebugging( NEXTBOT_VISION ) )
{
ConColorMsg( Color( 0, 255, 0, 255 ), "%3.2f: %s caught sight of %s(#%d)\n",
gpGlobals->curtime,
GetBot()->GetDebugIdentifier(),
known.GetEntity()->GetClassname(),
known.GetEntity()->entindex() );
NDebugOverlay::Line( GetBot()->GetBodyInterface()->GetEyePosition(), known.GetLastKnownPosition(), 255, 255, 0, false, 0.2f );
}
GetBot()->OnSight( known.GetEntity() );
}
// restart 'not seen' timer
m_notVisibleTimer[ known.GetEntity()->GetTeamNumber() ].Start();
}
else // known entity is not currently visible
{
if ( known.IsVisibleInFOVNow() )
{
// previously known and visible entity is now no longer visible
known.UpdateVisibilityStatus( false );
// lost sight of this entity
if ( GetBot()->IsDebugging( NEXTBOT_VISION ) )
{
ConColorMsg( Color( 255, 0, 0, 255 ), "%3.2f: %s Lost sight of %s(#%d)\n",
gpGlobals->curtime,
GetBot()->GetDebugIdentifier(),
known.GetEntity()->GetClassname(),
known.GetEntity()->entindex() );
}
GetBot()->OnLostSight( known.GetEntity() );
}
if ( !known.HasLastKnownPositionBeenSeen() )
{
// can we see the entity's last know position?
if ( IsAbleToSee( known.GetLastKnownPosition(), IVision::USE_FOV ) )
{
known.MarkLastKnownPositionAsSeen();
}
}
}
}
}
// check for new recognizes that were not in the known set
{ VPROF_BUDGET( "IVision::UpdateKnownEntities( new recognizes )", "NextBot" );
int i, j;
for( i=0; i < visibleNow.m_recognized.Count(); ++i )
{
for( j=0; j < m_knownEntityVector.Count(); ++j )
{
if ( visibleNow.m_recognized[i] == m_knownEntityVector[j].GetEntity() )
{
break;
}
}
if ( j == m_knownEntityVector.Count() )
{
// recognized a previously unknown entity (emit OnSight() event after reaction time has passed)
CKnownEntity known( visibleNow.m_recognized[i] );
known.UpdatePosition();
known.UpdateVisibilityStatus( true );
m_knownEntityVector.AddToTail( known );
}
}
}
// debugging
if ( nb_debug_known_entities.GetBool() )
{
CBasePlayer *watcher = UTIL_GetListenServerHost();
if ( watcher )
{
CBaseEntity *subject = watcher->GetObserverTarget();
if ( subject && GetBot()->IsSelf( subject ) )
{
CUtlVector< CKnownEntity > knownVector;
CollectKnownEntities( &knownVector );
for( int i=0; i < knownVector.Count(); ++i )
{
CKnownEntity &known = knownVector[i];
if ( GetBot()->IsFriend( known.GetEntity() ) )
{
if ( IsAwareOf( known ) )
{
if ( known.IsVisibleInFOVNow() )
NDebugOverlay::HorzArrow( GetBot()->GetEntity()->GetAbsOrigin(), known.GetLastKnownPosition(), 5.0f, 0, 255, 0, 255, true, NDEBUG_PERSIST_TILL_NEXT_SERVER );
else
NDebugOverlay::HorzArrow( GetBot()->GetEntity()->GetAbsOrigin(), known.GetLastKnownPosition(), 2.0f, 0, 100, 0, 255, true, NDEBUG_PERSIST_TILL_NEXT_SERVER );
}
else
{
NDebugOverlay::HorzArrow( GetBot()->GetEntity()->GetAbsOrigin(), known.GetLastKnownPosition(), 1.0f, 0, 100, 0, 128, true, NDEBUG_PERSIST_TILL_NEXT_SERVER );
}
}
else
{
if ( IsAwareOf( known ) )
{
if ( known.IsVisibleInFOVNow() )
NDebugOverlay::HorzArrow( GetBot()->GetEntity()->GetAbsOrigin(), known.GetLastKnownPosition(), 5.0f, 255, 0, 0, 255, true, NDEBUG_PERSIST_TILL_NEXT_SERVER );
else
NDebugOverlay::HorzArrow( GetBot()->GetEntity()->GetAbsOrigin(), known.GetLastKnownPosition(), 2.0f, 100, 0, 0, 255, true, NDEBUG_PERSIST_TILL_NEXT_SERVER );
}
else
{
NDebugOverlay::HorzArrow( GetBot()->GetEntity()->GetAbsOrigin(), known.GetLastKnownPosition(), 1.0f, 100, 0, 0, 128, true, NDEBUG_PERSIST_TILL_NEXT_SERVER );
}
}
}
}
}
}
}
//------------------------------------------------------------------------------------------
/**
* Update internal state
*/
void IVision::Update( void )
{
VPROF_BUDGET( "IVision::Update", "NextBotExpensive" );
/* This adds significantly to bot's reaction times
// throttle update rate
if ( !m_scanTimer.IsElapsed() )
{
return;
}
m_scanTimer.Start( 0.5f * GetMinRecognizeTime() );
*/
if ( nb_blind.GetBool() )
{
m_knownEntityVector.RemoveAll();
return;
}
UpdateKnownEntities();
m_lastVisionUpdateTimestamp = gpGlobals->curtime;
}
//------------------------------------------------------------------------------------------
bool IVision::IsAbleToSee( CBaseEntity *subject, FieldOfViewCheckType checkFOV, Vector *visibleSpot ) const
{
VPROF_BUDGET( "IVision::IsAbleToSee", "NextBotExpensive" );
if ( GetBot()->IsRangeGreaterThan( subject, GetMaxVisionRange() ) )
{
return false;
}
if ( GetBot()->GetEntity()->IsHiddenByFog( subject ) )
{
// lost in the fog
return false;
}
if ( checkFOV == USE_FOV && !IsInFieldOfView( subject ) )
{
return false;
}
CBaseCombatCharacter *combat = subject->MyCombatCharacterPointer();
if ( combat )
{
CNavArea *subjectArea = combat->GetLastKnownArea();
CNavArea *myArea = GetBot()->GetEntity()->GetLastKnownArea();
if ( myArea && subjectArea )
{
if ( !myArea->IsPotentiallyVisible( subjectArea ) )
{
// subject is not potentially visible, skip the expensive raycast
return false;
}
}
}
// do actual line-of-sight trace
if ( !IsLineOfSightClearToEntity( subject ) )
{
return false;
}
return IsVisibleEntityNoticed( subject );
}
//------------------------------------------------------------------------------------------
bool IVision::IsAbleToSee( const Vector &pos, FieldOfViewCheckType checkFOV ) const
{
VPROF_BUDGET( "IVision::IsAbleToSee", "NextBotExpensive" );
if ( GetBot()->IsRangeGreaterThan( pos, GetMaxVisionRange() ) )
{
return false;
}
if ( GetBot()->GetEntity()->IsHiddenByFog( pos ) )
{
// lost in the fog
return false;
}
if ( checkFOV == USE_FOV && !IsInFieldOfView( pos ) )
{
return false;
}
// do actual line-of-sight trace
return IsLineOfSightClear( pos );
}
//------------------------------------------------------------------------------------------
/**
* Angle given in degrees
*/
void IVision::SetFieldOfView( float horizAngle )
{
m_FOV = horizAngle;
m_cosHalfFOV = cos( 0.5f * m_FOV * M_PI / 180.0f );
}
//------------------------------------------------------------------------------------------
bool IVision::IsInFieldOfView( const Vector &pos ) const
{
#ifdef CHECK_OLD_CODE_AGAINST_NEW
bool bCheck = PointWithinViewAngle( GetBot()->GetBodyInterface()->GetEyePosition(), pos, GetBot()->GetBodyInterface()->GetViewVector(), m_cosHalfFOV );
Vector to = pos - GetBot()->GetBodyInterface()->GetEyePosition();
to.NormalizeInPlace();
float cosDiff = DotProduct( GetBot()->GetBodyInterface()->GetViewVector(), to );
if ( ( cosDiff > m_cosHalfFOV ) != bCheck )
{
Assert(0);
bool bCheck2 =
PointWithinViewAngle( GetBot()->GetBodyInterface()->GetEyePosition(), pos, GetBot()->GetBodyInterface()->GetViewVector(), m_cosHalfFOV );
}
return ( cosDiff > m_cosHalfFOV );
#else
return PointWithinViewAngle( GetBot()->GetBodyInterface()->GetEyePosition(), pos, GetBot()->GetBodyInterface()->GetViewVector(), m_cosHalfFOV );
#endif
return true;
}
//------------------------------------------------------------------------------------------
bool IVision::IsInFieldOfView( CBaseEntity *subject ) const
{
/// @todo check more points
if ( IsInFieldOfView( subject->WorldSpaceCenter() ) )
{
return true;
}
return IsInFieldOfView( subject->EyePosition() );
}
//------------------------------------------------------------------------------------------
/**
* Return true if the ray to the given point is unobstructed
*/
bool IVision::IsLineOfSightClear( const Vector &pos ) const
{
VPROF_BUDGET( "IVision::IsLineOfSightClear", "NextBot" );
VPROF_INCREMENT_COUNTER( "IVision::IsLineOfSightClear", 1 );
trace_t result;
NextBotVisionTraceFilter filter( GetBot()->GetEntity(), COLLISION_GROUP_NONE );
UTIL_TraceLine( GetBot()->GetBodyInterface()->GetEyePosition(), pos, MASK_BLOCKLOS_AND_NPCS|CONTENTS_IGNORE_NODRAW_OPAQUE, &filter, &result );
return ( result.fraction >= 1.0f && !result.startsolid );
}
//------------------------------------------------------------------------------------------
bool IVision::IsLineOfSightClearToEntity( const CBaseEntity *subject, Vector *visibleSpot ) const
{
#ifdef TERROR
// TODO: Integration querycache & its dependencies
VPROF_INCREMENT_COUNTER( "IVision::IsLineOfSightClearToEntity", 1 );
VPROF_BUDGET( "IVision::IsLineOfSightClearToEntity", "NextBotSpiky" );
bool bClear = IsLineOfSightBetweenTwoEntitiesClear( GetBot()->GetBodyInterface()->GetEntity(), EOFFSET_MODE_EYEPOSITION,
subject, EOFFSET_MODE_WORLDSPACE_CENTER,
subject, COLLISION_GROUP_NONE,
MASK_BLOCKLOS_AND_NPCS|CONTENTS_IGNORE_NODRAW_OPAQUE, VisionTraceFilterFunction, 1.0 );
#ifdef USE_NON_CACHE_QUERY
trace_t result;
NextBotTraceFilterIgnoreActors filter( subject, COLLISION_GROUP_NONE );
UTIL_TraceLine( GetBot()->GetBodyInterface()->GetEyePosition(), subject->WorldSpaceCenter(), MASK_BLOCKLOS_AND_NPCS|CONTENTS_IGNORE_NODRAW_OPAQUE, &filter, &result );
Assert( result.DidHit() != bClear );
if ( subject->IsPlayer() && ! bClear )
{
UTIL_TraceLine( GetBot()->GetBodyInterface()->GetEyePosition(), subject->EyePosition(), MASK_BLOCKLOS_AND_NPCS|CONTENTS_IGNORE_NODRAW_OPAQUE, &filter, &result );
bClear = IsLineOfSightBetweenTwoEntitiesClear( GetBot()->GetEntity(),
EOFFSET_MODE_EYEPOSITION,
subject, EOFFSET_MODE_EYEPOSITION,
subject, COLLISION_GROUP_NONE,
MASK_BLOCKLOS_AND_NPCS|CONTENTS_IGNORE_NODRAW_OPAQUE,
IgnoreActorsTraceFilterFunction, 1.0 );
// this WILL assert - the query interface happens at a different time, and has hysteresis.
Assert( result.DidHit() != bClear );
}
#endif
return bClear;
#else
// TODO: Use plain-old traces until querycache/etc gets integrated
VPROF_BUDGET( "IVision::IsLineOfSightClearToEntity", "NextBot" );
trace_t result;
NextBotTraceFilterIgnoreActors filter( subject, COLLISION_GROUP_NONE );
UTIL_TraceLine( GetBot()->GetBodyInterface()->GetEyePosition(), subject->WorldSpaceCenter(), MASK_BLOCKLOS_AND_NPCS|CONTENTS_IGNORE_NODRAW_OPAQUE, &filter, &result );
if ( result.DidHit() )
{
UTIL_TraceLine( GetBot()->GetBodyInterface()->GetEyePosition(), subject->EyePosition(), MASK_BLOCKLOS_AND_NPCS|CONTENTS_IGNORE_NODRAW_OPAQUE, &filter, &result );
if ( result.DidHit() )
{
UTIL_TraceLine( GetBot()->GetBodyInterface()->GetEyePosition(), subject->GetAbsOrigin(), MASK_BLOCKLOS_AND_NPCS|CONTENTS_IGNORE_NODRAW_OPAQUE, &filter, &result );
}
}
if ( visibleSpot )
{
*visibleSpot = result.endpos;
}
return ( result.fraction >= 1.0f && !result.startsolid );
#endif
}
//------------------------------------------------------------------------------------------
/**
* Are we looking directly at the given position
*/
bool IVision::IsLookingAt( const Vector &pos, float cosTolerance ) const
{
Vector to = pos - GetBot()->GetBodyInterface()->GetEyePosition();
to.NormalizeInPlace();
Vector forward;
AngleVectors( GetBot()->GetEntity()->EyeAngles(), &forward );
return DotProduct( to, forward ) > cosTolerance;
}
//------------------------------------------------------------------------------------------
/**
* Are we looking directly at the given actor
*/
bool IVision::IsLookingAt( const CBaseCombatCharacter *actor, float cosTolerance ) const
{
return IsLookingAt( actor->EyePosition(), cosTolerance );
}
@@ -0,0 +1,226 @@
// NextBotVisionInterface.h
// Visual information query interface for bots
// Author: Michael Booth, April 2005
//========= Copyright Valve Corporation, All rights reserved. ============//
#ifndef _NEXT_BOT_VISION_INTERFACE_H_
#define _NEXT_BOT_VISION_INTERFACE_H_
#include "NextBotComponentInterface.h"
#include "NextBotKnownEntity.h"
class IBody;
class INextBotEntityFilter;
//----------------------------------------------------------------------------------------------------------------
/**
* The interface for HOW the bot sees (near sighted? night vision? etc)
*/
class IVision : public INextBotComponent
{
public:
IVision( INextBot *bot );
virtual ~IVision() { }
virtual void Reset( void ); // reset to initial state
virtual void Update( void ); // update internal state
//-- attention/short term memory interface follows ------------------------------------------
//
// WARNING: Do not keep CKnownEntity pointers returned by these methods, as they can be invalidated/freed
//
/**
* Iterate each interesting entity we are aware of.
* If functor returns false, stop iterating and return false.
* NOTE: known.GetEntity() is guaranteed to be non-NULL
*/
class IForEachKnownEntity
{
public:
virtual bool Inspect( const CKnownEntity &known ) = 0;
};
virtual bool ForEachKnownEntity( IForEachKnownEntity &func );
virtual void CollectKnownEntities( CUtlVector< CKnownEntity > *knownVector ); // populate given vector with all currently known entities
virtual const CKnownEntity *GetPrimaryKnownThreat( bool onlyVisibleThreats = false ) const; // return the biggest threat to ourselves that we are aware of
virtual float GetTimeSinceVisible( int team ) const; // return time since we saw any member of the given team
virtual const CKnownEntity *GetClosestKnown( int team = TEAM_ANY ) const; // return the closest known entity
virtual int GetKnownCount( int team, bool onlyVisible = false, float rangeLimit = -1.0f ) const; // return the number of entities on the given team known to us closer than rangeLimit
virtual const CKnownEntity *GetClosestKnown( const INextBotEntityFilter &filter ) const; // return the closest known entity that passes the given filter
virtual const CKnownEntity *GetKnown( const CBaseEntity *entity ) const; // given an entity, return our known version of it (or NULL if we don't know of it)
// Introduce a known entity into the system. Its position is assumed to be known
// and will be updated, and it is assumed to not yet have been seen by us, allowing for learning
// of known entities by being told about them, hearing them, etc.
virtual void AddKnownEntity( CBaseEntity *entity );
virtual void ForgetEntity( CBaseEntity *forgetMe ); // remove the given entity from our awareness (whether we know if it or not)
virtual void ForgetAllKnownEntities( void );
//-- physical vision interface follows ------------------------------------------------------
/**
* Populate "potentiallyVisible" with the set of all entities we could potentially see.
* Entities in this set will be tested for visibility/recognition in IVision::Update()
*/
virtual void CollectPotentiallyVisibleEntities( CUtlVector< CBaseEntity * > *potentiallyVisible );
virtual float GetMaxVisionRange( void ) const; // return maximum distance vision can reach
virtual float GetMinRecognizeTime( void ) const; // return VISUAL reaction time
/**
* IsAbleToSee() returns true if the viewer can ACTUALLY SEE the subject or position,
* taking into account blindness, smoke effects, invisibility, etc.
* If 'visibleSpot' is non-NULL, the highest priority spot on the subject that is visible is returned.
*/
enum FieldOfViewCheckType { USE_FOV, DISREGARD_FOV };
virtual bool IsAbleToSee( CBaseEntity *subject, FieldOfViewCheckType checkFOV, Vector *visibleSpot = NULL ) const;
virtual bool IsAbleToSee( const Vector &pos, FieldOfViewCheckType checkFOV ) const;
virtual bool IsIgnored( CBaseEntity *subject ) const; // return true to completely ignore this entity (may not be in sight when this is called)
virtual bool IsVisibleEntityNoticed( CBaseEntity *subject ) const; // return true if we 'notice' the subject, even though we have LOS to it
/**
* Check if 'subject' is within the viewer's field of view
*/
virtual bool IsInFieldOfView( const Vector &pos ) const;
virtual bool IsInFieldOfView( CBaseEntity *subject ) const;
virtual float GetDefaultFieldOfView( void ) const; // return default FOV in degrees
virtual float GetFieldOfView( void ) const; // return FOV in degrees
virtual void SetFieldOfView( float horizAngle ); // angle given in degrees
virtual bool IsLineOfSightClear( const Vector &pos ) const; // return true if the ray to the given point is unobstructed
/**
* Returns true if the ray between the position and the subject is unobstructed.
* A visible spot on the subject is returned in 'visibleSpot'.
*/
virtual bool IsLineOfSightClearToEntity( const CBaseEntity *subject, Vector *visibleSpot = NULL ) const;
/// @todo: Implement LookAt system
virtual bool IsLookingAt( const Vector &pos, float cosTolerance = 0.95f ) const; // are we looking at the given position
virtual bool IsLookingAt( const CBaseCombatCharacter *actor, float cosTolerance = 0.95f ) const; // are we looking at the given actor
private:
CountdownTimer m_scanTimer; // for throttling update rate
float m_FOV; // current FOV in degrees
float m_cosHalfFOV; // the cosine of FOV/2
CUtlVector< CKnownEntity > m_knownEntityVector; // the set of enemies/friends we are aware of
void UpdateKnownEntities( void );
bool IsAwareOf( const CKnownEntity &known ) const; // return true if our reaction time has passed for this entity
mutable CHandle< CBaseEntity > m_primaryThreat;
float m_lastVisionUpdateTimestamp;
IntervalTimer m_notVisibleTimer[ MAX_TEAMS ]; // for tracking interval since last saw a member of the given team
};
inline void IVision::CollectKnownEntities( CUtlVector< CKnownEntity > *knownVector )
{
if ( knownVector )
{
knownVector->RemoveAll();
for( int i=0; i<m_knownEntityVector.Count(); ++i )
{
if ( !m_knownEntityVector[i].IsObsolete() )
{
knownVector->AddToTail( m_knownEntityVector[i] );
}
}
}
}
inline float IVision::GetDefaultFieldOfView( void ) const
{
return 90.0f;
}
inline float IVision::GetFieldOfView( void ) const
{
return m_FOV;
}
inline float IVision::GetTimeSinceVisible( int team ) const
{
if ( team == TEAM_ANY )
{
// return minimum time
float time = 9999999999.9f;
for( int i=0; i<MAX_TEAMS; ++i )
{
if ( m_notVisibleTimer[i].HasStarted() )
{
if ( time > m_notVisibleTimer[i].GetElapsedTime() )
{
team = m_notVisibleTimer[i].GetElapsedTime();
}
}
}
return time;
}
if ( team >= 0 && team < MAX_TEAMS )
{
return m_notVisibleTimer[ team ].GetElapsedTime();
}
return 0.0f;
}
inline bool IVision::IsAwareOf( const CKnownEntity &known ) const
{
return known.GetTimeSinceBecameKnown() >= GetMinRecognizeTime();
}
inline bool IVision::ForEachKnownEntity( IVision::IForEachKnownEntity &func )
{
for( int i=0; i<m_knownEntityVector.Count(); ++i )
{
const CKnownEntity &known = m_knownEntityVector[i];
if ( !known.IsObsolete() && IsAwareOf( known ) )
{
if ( func.Inspect( known ) == false )
{
return false;
}
}
}
return true;
}
inline bool IVision::IsVisibleEntityNoticed( CBaseEntity *subject ) const
{
return true;
}
inline bool IVision::IsIgnored( CBaseEntity *subject ) const
{
return false;
}
inline float IVision::GetMaxVisionRange( void ) const
{
return 2000.0f;
}
inline float IVision::GetMinRecognizeTime( void ) const
{
return 0.0f;
}
#endif // _NEXT_BOT_VISION_INTERFACE_H_
@@ -0,0 +1,166 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================
#include "cbase.h"
#include "NextBotChasePath.h"
#include "tier1/fmtstr.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//----------------------------------------------------------------------------------------------
/**
* Try to cutoff our chase subject
*/
Vector ChasePath::PredictSubjectPosition( INextBot *bot, CBaseEntity *subject ) const
{
ILocomotion *mover = bot->GetLocomotionInterface();
const Vector &subjectPos = subject->GetAbsOrigin();
Vector to = subjectPos - bot->GetPosition();
to.z = 0.0f;
float flRangeSq = to.LengthSqr();
// don't lead if subject is very far away
float flLeadRadiusSq = GetLeadRadius();
flLeadRadiusSq *= flLeadRadiusSq;
if ( flRangeSq > flLeadRadiusSq )
return subjectPos;
// Normalize in place
float range = sqrt( flRangeSq );
to /= ( range + 0.0001f ); // avoid divide by zero
// estimate time to reach subject, assuming maximum speed
float leadTime = 0.5f + ( range / ( mover->GetRunSpeed() + 0.0001f ) );
// estimate amount to lead the subject
Vector lead = leadTime * subject->GetAbsVelocity();
lead.z = 0.0f;
if ( DotProduct( to, lead ) < 0.0f )
{
// the subject is moving towards us - only pay attention
// to his perpendicular velocity for leading
Vector2D to2D = to.AsVector2D();
to2D.NormalizeInPlace();
Vector2D perp( -to2D.y, to2D.x );
float enemyGroundSpeed = lead.x * perp.x + lead.y * perp.y;
lead.x = enemyGroundSpeed * perp.x;
lead.y = enemyGroundSpeed * perp.y;
}
// compute our desired destination
Vector pathTarget = subjectPos + lead;
// validate this destination
// don't lead through walls
if ( lead.LengthSqr() > 36.0f )
{
float fraction;
if ( !mover->IsPotentiallyTraversable( subjectPos, pathTarget, ILocomotion::IMMEDIATELY, &fraction ) )
{
// tried to lead through an unwalkable area - clip to walkable space
pathTarget = subjectPos + fraction * ( pathTarget - subjectPos );
}
}
// don't lead over cliffs
CNavArea *leadArea = NULL;
#ifdef NEED_GPGLOBALS_SERVERCOUNT_TO_DO_THIS
CBaseCombatCharacter *pBCC = subject->MyCombatCharacterPointer();
if ( pBCC && CloseEnough( pathTarget, subjectPos, 3.0 ) )
{
pathTarget = subjectPos;
leadArea = pBCC->GetLastKnownArea(); // can return null?
}
else
{
struct CacheEntry_t
{
CacheEntry_t() : pArea(NULL) {}
Vector target;
CNavArea *pArea;
};
static int iServer;
static CacheEntry_t cache[4];
static int iNext;
int i;
bool bFound = false;
if ( iServer != gpGlobals->serverCount )
{
for ( i = 0; i < ARRAYSIZE(cache); i++ )
{
cache[i].pArea = NULL;
}
iServer = gpGlobals->serverCount;
}
else
{
for ( i = 0; i < ARRAYSIZE(cache); i++ )
{
if ( cache[i].pArea && CloseEnough( cache[i].target, pathTarget, 2.0 ) )
{
pathTarget = cache[i].target;
leadArea = cache[i].pArea;
bFound = true;
break;
}
}
}
if ( !bFound )
{
leadArea = TheNavMesh->GetNearestNavArea( pathTarget );
if ( leadArea )
{
cache[iNext].target = pathTarget;
cache[iNext].pArea = leadArea;
iNext = ( iNext + 1 ) % ARRAYSIZE( cache );
}
}
}
#else
leadArea = TheNavMesh->GetNearestNavArea( pathTarget );
#endif
if ( !leadArea || leadArea->GetZ( pathTarget.x, pathTarget.y ) < pathTarget.z - mover->GetMaxJumpHeight() )
{
// would fall off a cliff
return subjectPos;
}
/** This needs more thought - it is preventing bots from using dropdowns
if ( mover->HasPotentialGap( subjectPos, pathTarget, &fraction ) )
{
// tried to lead over a cliff - clip to safe region
pathTarget = subjectPos + fraction * ( pathTarget - subjectPos );
}
*/
return pathTarget;
}
// if the victim is a player, poke them so they know they're being chased
void DirectChasePath::NotifyVictim( INextBot *me, CBaseEntity *victim )
{
CBaseCombatCharacter *pBCCVictim = ToBaseCombatCharacter( victim );
if ( !pBCCVictim )
return;
pBCCVictim->OnPursuedBy( me );
}
+376
View File
@@ -0,0 +1,376 @@
// NextBotChasePath.h
// Maintain and follow a "chase path" to a selected Actor
// Author: Michael Booth, September 2006
//========= Copyright Valve Corporation, All rights reserved. ============//
#ifndef _NEXT_BOT_CHASE_PATH_
#define _NEXT_BOT_CHASE_PATH_
#include "nav.h"
#include "NextBotInterface.h"
#include "NextBotLocomotionInterface.h"
#include "NextBotChasePath.h"
#include "NextBotUtil.h"
#include "NextBotPathFollow.h"
#include "tier0/vprof.h"
//----------------------------------------------------------------------------------------------
/**
* A ChasePath extends a PathFollower to periodically recompute a path to a chase
* subject, and to move along the path towards that subject.
*/
class ChasePath : public PathFollower
{
public:
enum SubjectChaseType
{
LEAD_SUBJECT,
DONT_LEAD_SUBJECT
};
ChasePath( SubjectChaseType chaseHow = DONT_LEAD_SUBJECT );
virtual ~ChasePath() { }
virtual void Update( INextBot *bot, CBaseEntity *subject, const IPathCost &cost, Vector *pPredictedSubjectPos = NULL ); // update path to chase target and move bot along path
virtual float GetLeadRadius( void ) const; // range where movement leading begins - beyond this just head right for the subject
virtual float GetMaxPathLength( void ) const; // return maximum path length
virtual Vector PredictSubjectPosition( INextBot *bot, CBaseEntity *subject ) const; // try to cutoff our chase subject, knowing our relative positions and velocities
virtual bool IsRepathNeeded( INextBot *bot, CBaseEntity *subject ) const; // return true if situation has changed enough to warrant recomputing the current path
virtual float GetLifetime( void ) const; // Return duration this path is valid. Path will become invalid at its earliest opportunity once this duration elapses. Zero = infinite lifetime
virtual void Invalidate( void ); // (EXTEND) cause the path to become invalid
private:
void RefreshPath( INextBot *bot, CBaseEntity *subject, const IPathCost &cost, Vector *pPredictedSubjectPos );
CountdownTimer m_failTimer; // throttle re-pathing if last path attempt failed
CountdownTimer m_throttleTimer; // require a minimum time between re-paths
CountdownTimer m_lifetimeTimer;
EHANDLE m_lastPathSubject; // the subject used to compute the current/last path
SubjectChaseType m_chaseHow;
};
inline ChasePath::ChasePath( SubjectChaseType chaseHow )
{
m_failTimer.Invalidate();
m_throttleTimer.Invalidate();
m_lifetimeTimer.Invalidate();
m_lastPathSubject = NULL;
m_chaseHow = chaseHow;
}
inline float ChasePath::GetLeadRadius( void ) const
{
return 500.0f; // 1000.0f;
}
inline float ChasePath::GetMaxPathLength( void ) const
{
// no limit
return 0.0f;
}
inline float ChasePath::GetLifetime( void ) const
{
// infinite duration
return 0.0f;
}
inline void ChasePath::Invalidate( void )
{
// path is gone, repath at earliest opportunity
m_throttleTimer.Invalidate();
m_lifetimeTimer.Invalidate();
// extend
PathFollower::Invalidate();
}
//----------------------------------------------------------------------------------------------
/**
* Maintain a path to our chase subject and move along that path
*/
inline void ChasePath::Update( INextBot *bot, CBaseEntity *subject, const IPathCost &cost, Vector *pPredictedSubjectPos )
{
VPROF_BUDGET( "ChasePath::Update", "NextBot" );
// maintain the path to the subject
RefreshPath( bot, subject, cost, pPredictedSubjectPos );
// move along the path towards the subject
PathFollower::Update( bot );
}
//----------------------------------------------------------------------------------------------
/**
* Return true if situation has changed enough to warrant recomputing the current path
*/
inline bool ChasePath::IsRepathNeeded( INextBot *bot, CBaseEntity *subject ) const
{
// the closer we get, the more accurate our path needs to be
Vector to = subject->GetAbsOrigin() - bot->GetPosition();
const float minTolerance = 0.0f; // 25.0f;
const float toleranceRate = 0.33f; // 1.0f; // 0.15f;
float tolerance = minTolerance + toleranceRate * to.Length();
return ( subject->GetAbsOrigin() - GetEndPosition() ).IsLengthGreaterThan( tolerance );
}
//----------------------------------------------------------------------------------------------
/**
* Periodically rebuild the path to our victim
*/
inline void ChasePath::RefreshPath( INextBot *bot, CBaseEntity *subject, const IPathCost &cost, Vector *pPredictedSubjectPos )
{
VPROF_BUDGET( "ChasePath::RefreshPath", "NextBot" );
ILocomotion *mover = bot->GetLocomotionInterface();
// don't change our path if we're on a ladder
if ( IsValid() && mover->IsUsingLadder() )
{
if ( bot->IsDebugging( NEXTBOT_PATH ) )
{
DevMsg( "%3.2f: bot(#%d) ChasePath::RefreshPath failed. Bot is on a ladder.\n", gpGlobals->curtime, bot->GetEntity()->entindex() );
}
// don't allow repath until a moment AFTER we have left the ladder
m_throttleTimer.Start( 1.0f );
return;
}
if ( subject == NULL )
{
if ( bot->IsDebugging( NEXTBOT_PATH ) )
{
DevMsg( "%3.2f: bot(#%d) CasePath::RefreshPath failed. No subject.\n", gpGlobals->curtime, bot->GetEntity()->entindex() );
}
return;
}
if ( !m_failTimer.IsElapsed() )
{
// if ( bot->IsDebugging( NEXTBOT_PATH ) )
// {
// DevMsg( "%3.2f: bot(#%d) ChasePath::RefreshPath failed. Fail timer not elapsed.\n", gpGlobals->curtime, bot->GetEntity()->entindex() );
// }
return;
}
// if our path subject changed, repath immediately
if ( subject != m_lastPathSubject )
{
if ( bot->IsDebugging( NEXTBOT_PATH ) )
{
DevMsg( "%3.2f: bot(#%d) Chase path subject changed (from %p to %p).\n", gpGlobals->curtime, bot->GetEntity()->entindex(), m_lastPathSubject.Get(), subject );
}
Invalidate();
// new subject, fresh attempt
m_failTimer.Invalidate();
}
if ( IsValid() && !m_throttleTimer.IsElapsed() )
{
// require a minimum time between repaths, as long as we have a path to follow
// if ( bot->IsDebugging( NEXTBOT_PATH ) )
// {
// DevMsg( "%3.2f: bot(#%d) ChasePath::RefreshPath failed. Rate throttled.\n", gpGlobals->curtime, bot->GetEntity()->entindex() );
// }
return;
}
if ( IsValid() && m_lifetimeTimer.HasStarted() && m_lifetimeTimer.IsElapsed() )
{
// this path's lifetime has elapsed
Invalidate();
}
if ( !IsValid() || IsRepathNeeded( bot, subject ) )
{
// the situation has changed - try a new path
bool isPath;
Vector pathTarget = subject->GetAbsOrigin();
if ( m_chaseHow == LEAD_SUBJECT )
{
pathTarget = pPredictedSubjectPos ? *pPredictedSubjectPos : PredictSubjectPosition( bot, subject );
isPath = Compute( bot, pathTarget, cost, GetMaxPathLength() );
}
else if ( subject->MyCombatCharacterPointer() && subject->MyCombatCharacterPointer()->GetLastKnownArea() )
{
isPath = Compute( bot, subject->MyCombatCharacterPointer(), cost, GetMaxPathLength() );
}
else
{
isPath = Compute( bot, pathTarget, cost, GetMaxPathLength() );
}
if ( isPath )
{
if ( bot->IsDebugging( NEXTBOT_PATH ) )
{
//const float size = 20.0f;
//NDebugOverlay::VertArrow( bot->GetPosition() + Vector( 0, 0, size ), bot->GetPosition(), size, 255, RandomInt( 0, 200 ), 255, 255, true, 30.0f );
DevMsg( "%3.2f: bot(#%d) REPATH\n", gpGlobals->curtime, bot->GetEntity()->entindex() );
}
m_lastPathSubject = subject;
const float minRepathInterval = 0.5f;
m_throttleTimer.Start( minRepathInterval );
// track the lifetime of this new path
float lifetime = GetLifetime();
if ( lifetime > 0.0f )
{
m_lifetimeTimer.Start( lifetime );
}
else
{
m_lifetimeTimer.Invalidate();
}
}
else
{
// can't reach subject - throttle retry based on range to subject
m_failTimer.Start( 0.005f * ( bot->GetRangeTo( subject ) ) );
// allow bot to react to path failure
bot->OnMoveToFailure( this, FAIL_NO_PATH_EXISTS );
if ( bot->IsDebugging( NEXTBOT_PATH ) )
{
//const float size = 20.0f;
const float dT = 90.0f;
int c = RandomInt( 0, 100 );
//NDebugOverlay::VertArrow( bot->GetPosition() + Vector( 0, 0, size ), bot->GetPosition(), size, 255, c, c, 255, true, dT );
NDebugOverlay::HorzArrow( bot->GetPosition(), pathTarget, 5.0f, 255, c, c, 255, true, dT );
DevMsg( "%3.2f: bot(#%d) REPATH FAILED\n", gpGlobals->curtime, bot->GetEntity()->entindex() );
}
Invalidate();
}
}
}
//----------------------------------------------------------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------------------------------------------------------
/**
* Directly beeline toward victim if we have a clear shot, otherwise pathfind.
*/
class DirectChasePath : public ChasePath
{
public:
DirectChasePath( ChasePath::SubjectChaseType chaseHow = ChasePath::DONT_LEAD_SUBJECT ) : ChasePath( chaseHow )
{
}
//-------------------------------------------------------------------------------------------------------
virtual void Update( INextBot *me, CBaseEntity *victim, const IPathCost &pathCost, Vector *pPredictedSubjectPos = NULL ) // update path to chase target and move bot along path
{
Assert( !pPredictedSubjectPos );
bool bComputedPredictedPosition;
Vector vecPredictedPosition;
if ( !DirectChase( &bComputedPredictedPosition, &vecPredictedPosition, me, victim ) )
{
// path around obstacles to reach our victim
ChasePath::Update( me, victim, pathCost, bComputedPredictedPosition ? &vecPredictedPosition : NULL );
}
NotifyVictim( me, victim );
}
//-------------------------------------------------------------------------------------------------------
bool DirectChase( bool *pPredictedPositionComputed, Vector *pPredictedPos, INextBot *me, CBaseEntity *victim ) // if there is nothing between us and our victim, run directly at them
{
*pPredictedPositionComputed = false;
ILocomotion *mover = me->GetLocomotionInterface();
if ( me->IsImmobile() || mover->IsScrambling() )
{
return false;
}
if ( IsDiscontinuityAhead( me, CLIMB_UP ) )
{
return false;
}
if ( IsDiscontinuityAhead( me, JUMP_OVER_GAP ) )
{
return false;
}
Vector leadVictimPos = PredictSubjectPosition( me, victim );
// Don't want to have to compute the predicted position twice.
*pPredictedPositionComputed = true;
*pPredictedPos = leadVictimPos;
if ( !mover->IsPotentiallyTraversable( mover->GetFeet(), leadVictimPos ) )
{
return false;
}
// the way is clear - move directly towards our victim
mover->FaceTowards( leadVictimPos );
mover->Approach( leadVictimPos );
me->GetBodyInterface()->AimHeadTowards( victim );
// old path is no longer useful since we've moved off of it
Invalidate();
return true;
}
//-------------------------------------------------------------------------------------------------------
virtual bool IsRepathNeeded( INextBot *bot, CBaseEntity *subject ) const // return true if situation has changed enough to warrant recomputing the current path
{
if ( ChasePath::IsRepathNeeded( bot, subject ) )
{
return true;
}
return bot->GetLocomotionInterface()->IsStuck() && bot->GetLocomotionInterface()->GetStuckDuration() > 2.0f;
}
//-------------------------------------------------------------------------------------------------------
/**
* Determine exactly where the path goes between the given two areas
* on the path. Return this point in 'crossPos'.
*/
virtual void ComputeAreaCrossing( INextBot *bot, const CNavArea *from, const Vector &fromPos, const CNavArea *to, NavDirType dir, Vector *crossPos ) const
{
Vector center;
float halfWidth;
from->ComputePortal( to, dir, &center, &halfWidth );
*crossPos = center;
}
void NotifyVictim( INextBot *me, CBaseEntity *victim );
};
#endif // _NEXT_BOT_CHASE_PATH_
File diff suppressed because it is too large Load Diff
+862
View File
@@ -0,0 +1,862 @@
// NextBotPath.h
// Encapsulate and manipulate a path through the world
// Author: Michael Booth, February 2006
//========= Copyright Valve Corporation, All rights reserved. ============//
#ifndef _NEXT_BOT_PATH_H_
#define _NEXT_BOT_PATH_H_
#include "NextBotInterface.h"
#include "tier0/vprof.h"
#define PATH_NO_LENGTH_LIMIT 0.0f // non-default argument value for Path::Compute()
#define PATH_TRUNCATE_INCOMPLETE_PATH false // non-default argument value for Path::Compute()
class INextBot;
class CNavArea;
class CNavLadder;
//---------------------------------------------------------------------------------------------------------------
/**
* The interface for pathfinding costs.
* TODO: Replace all template cost functors with this interface, so we can virtualize and derive from them.
*/
class IPathCost
{
public:
virtual float operator()( CNavArea *area, CNavArea *fromArea, const CNavLadder *ladder, const CFuncElevator *elevator, float length ) const = 0;
};
//---------------------------------------------------------------------------------------------------------------
/**
* The interface for selecting a goal area during "open goal" pathfinding
*/
class IPathOpenGoalSelector
{
public:
// compare "newArea" to "currentGoal" and return the area that is the better goal area
virtual CNavArea *operator() ( CNavArea *currentGoal, CNavArea *newArea ) const = 0;
};
//---------------------------------------------------------------------------------------------------------------
/**
* A Path through the world.
* Not only does this encapsulate a path to get from point A to point B,
* but also the selecting the decision algorithm for how to build that path.
*/
class Path
{
public:
Path( void );
virtual ~Path() { }
enum SegmentType
{
ON_GROUND,
DROP_DOWN,
CLIMB_UP,
JUMP_OVER_GAP,
LADDER_UP,
LADDER_DOWN,
NUM_SEGMENT_TYPES
};
// @todo Allow custom Segment classes for different kinds of paths
struct Segment
{
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
SegmentType type; // how to traverse this segment of the path
Vector forward; // unit vector along segment
float length; // length of this segment
float distanceFromStart; // distance of this node from the start of the path
float curvature; // how much the path 'curves' at this point in the XY plane (0 = none, 1 = 180 degree doubleback)
Vector m_portalCenter; // position of center of 'portal' between previous area and this area
float m_portalHalfWidth; // half width of 'portal'
};
virtual float GetLength( void ) const; // return length of path from start to finish
virtual const Vector &GetPosition( float distanceFromStart, const Segment *start = NULL ) const; // return a position on the path at the given distance from the path start
virtual const Vector &GetClosestPosition( const Vector &pos, const Segment *start = NULL, float alongLimit = 0.0f ) const; // return the closest point on the path to the given position
virtual const Vector &GetStartPosition( void ) const; // return the position where this path starts
virtual const Vector &GetEndPosition( void ) const; // return the position where this path ends
virtual CBaseCombatCharacter *GetSubject( void ) const; // return the actor this path leads to, or NULL if there is no subject
virtual const Path::Segment *GetCurrentGoal( void ) const; // return current goal along the path we are trying to reach
virtual float GetAge( void ) const; // return "age" of this path (time since it was built)
enum SeekType
{
SEEK_ENTIRE_PATH, // search the entire path length
SEEK_AHEAD, // search from current cursor position forward toward end of path
SEEK_BEHIND // search from current cursor position backward toward path start
};
virtual void MoveCursorToClosestPosition( const Vector &pos, SeekType type = SEEK_ENTIRE_PATH, float alongLimit = 0.0f ) const; // Set cursor position to closest point on path to given position
enum MoveCursorType
{
PATH_ABSOLUTE_DISTANCE,
PATH_RELATIVE_DISTANCE
};
virtual void MoveCursorToStart( void ); // set seek cursor to start of path
virtual void MoveCursorToEnd( void ); // set seek cursor to end of path
virtual void MoveCursor( float value, MoveCursorType type = PATH_ABSOLUTE_DISTANCE ); // change seek cursor position
virtual float GetCursorPosition( void ) const; // return position of seek cursor (distance along path)
struct Data
{
Vector pos; // the position along the path
Vector forward; // unit vector along path direction
float curvature; // how much the path 'curves' at this point in the XY plane (0 = none, 1 = 180 degree doubleback)
const Segment *segmentPrior; // the segment just before this position
};
virtual const Data &GetCursorData( void ) const; // return path state at the current cursor position
virtual bool IsValid( void ) const;
virtual void Invalidate( void ); // make path invalid (clear it)
virtual void Draw( const Path::Segment *start = NULL ) const; // draw the path for debugging
virtual void DrawInterpolated( float from, float to ); // draw the path for debugging - MODIFIES cursor position
virtual const Segment *FirstSegment( void ) const; // return first segment of path
virtual const Segment *NextSegment( const Segment *currentSegment ) const; // return next segment of path, given current one
virtual const Segment *PriorSegment( const Segment *currentSegment ) const; // return previous segment of path, given current one
virtual const Segment *LastSegment( void ) const; // return last segment of path
enum ResultType
{
COMPLETE_PATH,
PARTIAL_PATH,
NO_PATH
};
virtual void OnPathChanged( INextBot *bot, ResultType result ) { } // invoked when the path is (re)computed (path is valid at the time of this call)
virtual void Copy( INextBot *bot, const Path &path ); // Replace this path with the given path's data
//-----------------------------------------------------------------------------------------------------------------
/**
* Compute shortest path from bot to given actor via A* algorithm.
* If returns true, path was found to the subject.
* If returns false, path may either be invalid (use IsValid() to check), or valid but
* doesn't reach all the way to the subject.
*/
template< typename CostFunctor >
bool Compute( INextBot *bot, CBaseCombatCharacter *subject, CostFunctor &costFunc, float maxPathLength = 0.0f, bool includeGoalIfPathFails = true )
{
VPROF_BUDGET( "Path::Compute(subject)", "NextBot" );
Invalidate();
m_subject = subject;
const Vector &start = bot->GetPosition();
CNavArea *startArea = bot->GetEntity()->GetLastKnownArea();
if ( !startArea )
{
OnPathChanged( bot, NO_PATH );
return false;
}
CNavArea *subjectArea = subject->GetLastKnownArea();
if ( !subjectArea )
{
OnPathChanged( bot, NO_PATH );
return false;
}
Vector subjectPos = subject->GetAbsOrigin();
// if we are already in the subject area, build trivial path
if ( startArea == subjectArea )
{
BuildTrivialPath( bot, subjectPos );
return true;
}
//
// Compute shortest path to subject
//
CNavArea *closestArea = NULL;
bool pathResult = NavAreaBuildPath( startArea, subjectArea, &subjectPos, costFunc, &closestArea, maxPathLength, bot->GetEntity()->GetTeamNumber() );
// Failed?
if ( closestArea == NULL )
return false;
//
// Build actual path by following parent links back from goal area
//
// get count
int count = 0;
CNavArea *area;
for( area = closestArea; area; area = area->GetParent() )
{
++count;
if ( area == startArea )
{
// startArea can be re-evaluated during the pathfind and given a parent...
break;
}
if ( count >= MAX_PATH_SEGMENTS-1 ) // save room for endpoint
break;
}
if ( count == 1 )
{
BuildTrivialPath( bot, subjectPos );
return pathResult;
}
// assemble path
m_segmentCount = count;
for( area = closestArea; count && area; area = area->GetParent() )
{
--count;
m_path[ count ].area = area;
m_path[ count ].how = area->GetParentHow();
m_path[ count ].type = ON_GROUND;
}
if ( pathResult || includeGoalIfPathFails )
{
// append actual subject position
m_path[ m_segmentCount ].area = closestArea;
m_path[ m_segmentCount ].pos = subjectPos;
m_path[ m_segmentCount ].ladder = NULL;
m_path[ m_segmentCount ].how = NUM_TRAVERSE_TYPES;
m_path[ m_segmentCount ].type = ON_GROUND;
++m_segmentCount;
}
// compute path positions
if ( ComputePathDetails( bot, start ) == false )
{
Invalidate();
OnPathChanged( bot, NO_PATH );
return false;
}
// remove redundant nodes and clean up path
Optimize( bot );
PostProcess();
OnPathChanged( bot, pathResult ? COMPLETE_PATH : PARTIAL_PATH );
return pathResult;
}
//-----------------------------------------------------------------------------------------------------------------
/**
* Compute shortest path from bot to 'goal' via A* algorithm.
* If returns true, path was found to the goal position.
* If returns false, path may either be invalid (use IsValid() to check), or valid but
* doesn't reach all the way to the goal.
*/
template< typename CostFunctor >
bool Compute( INextBot *bot, const Vector &goal, CostFunctor &costFunc, float maxPathLength = 0.0f, bool includeGoalIfPathFails = true )
{
VPROF_BUDGET( "Path::Compute(goal)", "NextBotSpiky" );
Invalidate();
const Vector &start = bot->GetPosition();
CNavArea *startArea = bot->GetEntity()->GetLastKnownArea();
if ( !startArea )
{
OnPathChanged( bot, NO_PATH );
return false;
}
// check line-of-sight to the goal position when finding it's nav area
const float maxDistanceToArea = 200.0f;
CNavArea *goalArea = TheNavMesh->GetNearestNavArea( goal, true, maxDistanceToArea, true );
// if we are already in the goal area, build trivial path
if ( startArea == goalArea )
{
BuildTrivialPath( bot, goal );
return true;
}
// make sure path end position is on the ground
Vector pathEndPosition = goal;
if ( goalArea )
{
pathEndPosition.z = goalArea->GetZ( pathEndPosition );
}
else
{
TheNavMesh->GetGroundHeight( pathEndPosition, &pathEndPosition.z );
}
//
// Compute shortest path to goal
//
CNavArea *closestArea = NULL;
bool pathResult = NavAreaBuildPath( startArea, goalArea, &goal, costFunc, &closestArea, maxPathLength, bot->GetEntity()->GetTeamNumber() );
// Failed?
if ( closestArea == NULL )
return false;
//
// Build actual path by following parent links back from goal area
//
// get count
int count = 0;
CNavArea *area;
for( area = closestArea; area; area = area->GetParent() )
{
++count;
if ( area == startArea )
{
// startArea can be re-evaluated during the pathfind and given a parent...
break;
}
if ( count >= MAX_PATH_SEGMENTS-1 ) // save room for endpoint
break;
}
if ( count == 1 )
{
BuildTrivialPath( bot, goal );
return pathResult;
}
// assemble path
m_segmentCount = count;
for( area = closestArea; count && area; area = area->GetParent() )
{
--count;
m_path[ count ].area = area;
m_path[ count ].how = area->GetParentHow();
m_path[ count ].type = ON_GROUND;
}
if ( pathResult || includeGoalIfPathFails )
{
// append actual goal position
m_path[ m_segmentCount ].area = closestArea;
m_path[ m_segmentCount ].pos = pathEndPosition;
m_path[ m_segmentCount ].ladder = NULL;
m_path[ m_segmentCount ].how = NUM_TRAVERSE_TYPES;
m_path[ m_segmentCount ].type = ON_GROUND;
++m_segmentCount;
}
// compute path positions
if ( ComputePathDetails( bot, start ) == false )
{
Invalidate();
OnPathChanged( bot, NO_PATH );
return false;
}
// remove redundant nodes and clean up path
Optimize( bot );
PostProcess();
OnPathChanged( bot, pathResult ? COMPLETE_PATH : PARTIAL_PATH );
return pathResult;
}
//-----------------------------------------------------------------------------------------------------------------
/**
* Build a path from bot's current location to an undetermined goal area
* that minimizes the given cost along the final path and meets the
* goal criteria.
*/
virtual bool ComputeWithOpenGoal( INextBot *bot, const IPathCost &costFunc, const IPathOpenGoalSelector &goalSelector, float maxSearchRadius = 0.0f )
{
VPROF_BUDGET( "ComputeWithOpenGoal", "NextBot" );
int teamID = bot->GetEntity()->GetTeamNumber();
CNavArea *startArea = bot->GetEntity()->GetLastKnownArea();
if ( startArea == NULL )
return NULL;
startArea->SetParent( NULL );
// start search
CNavArea::ClearSearchLists();
float initCost = costFunc( startArea, NULL, NULL, NULL, -1.0f );
if ( initCost < 0.0f )
return NULL;
startArea->SetTotalCost( initCost );
startArea->AddToOpenList();
// find our goal as we search
CNavArea *goalArea = NULL;
//
// Dijkstra's algorithm (since we don't know our goal).
//
while( !CNavArea::IsOpenListEmpty() )
{
// get next area to check
CNavArea *area = CNavArea::PopOpenList();
area->AddToClosedList();
// don't consider blocked areas
if ( area->IsBlocked( teamID ) )
continue;
// build adjacent area array
CollectAdjacentAreas( area );
// search adjacent areas
for( int i=0; i<m_adjAreaIndex; ++i )
{
CNavArea *newArea = m_adjAreaVector[ i ].area;
// only visit each area once
if ( newArea->IsClosed() )
continue;
// don't consider blocked areas
if ( newArea->IsBlocked( teamID ) )
continue;
// don't use this area if it is out of range
if ( maxSearchRadius > 0.0f && ( newArea->GetCenter() - bot->GetEntity()->GetAbsOrigin() ).IsLengthGreaterThan( maxSearchRadius ) )
continue;
// determine cost of traversing this area
float newCost = costFunc( newArea, area, m_adjAreaVector[ i ].ladder, NULL, -1.0f );
// don't use adjacent area if cost functor says it is a dead-end
if ( newCost < 0.0f )
continue;
if ( newArea->IsOpen() && newArea->GetTotalCost() <= newCost )
{
// we have already visited this area, and it has a better path
continue;
}
else
{
// whether this area has been visited or not, we now have a better path to it
newArea->SetParent( area, m_adjAreaVector[ i ].how );
newArea->SetTotalCost( newCost );
// use 'cost so far' to hold cumulative cost
newArea->SetCostSoFar( newCost );
// tricky bit here - relying on OpenList being sorted by cost
if ( newArea->IsOpen() )
{
// area already on open list, update the list order to keep costs sorted
newArea->UpdateOnOpenList();
}
else
{
newArea->AddToOpenList();
}
// keep track of best goal so far
goalArea = goalSelector( goalArea, newArea );
}
}
}
if ( goalArea )
{
// compile the path details into a usable path
AssemblePrecomputedPath( bot, goalArea->GetCenter(), goalArea );
return true;
}
// all adjacent areas are likely too far away
return false;
}
//-----------------------------------------------------------------------------------------------------------------
/**
* Given the last area in a path with valid parent pointers,
* construct the actual path.
*/
void AssemblePrecomputedPath( INextBot *bot, const Vector &goal, CNavArea *endArea )
{
VPROF_BUDGET( "AssemblePrecomputedPath", "NextBot" );
const Vector &start = bot->GetPosition();
// get count
int count = 0;
CNavArea *area;
for( area = endArea; area; area = area->GetParent() )
{
++count;
}
// save room for endpoint
if ( count > MAX_PATH_SEGMENTS-1 )
{
count = MAX_PATH_SEGMENTS-1;
}
else if ( count == 0 )
{
return;
}
if ( count == 1 )
{
BuildTrivialPath( bot, goal );
return;
}
// assemble path
m_segmentCount = count;
for( area = endArea; count && area; area = area->GetParent() )
{
--count;
m_path[ count ].area = area;
m_path[ count ].how = area->GetParentHow();
m_path[ count ].type = ON_GROUND;
}
// append actual goal position
m_path[ m_segmentCount ].area = endArea;
m_path[ m_segmentCount ].pos = goal;
m_path[ m_segmentCount ].ladder = NULL;
m_path[ m_segmentCount ].how = NUM_TRAVERSE_TYPES;
m_path[ m_segmentCount ].type = ON_GROUND;
++m_segmentCount;
// compute path positions
if ( ComputePathDetails( bot, start ) == false )
{
Invalidate();
OnPathChanged( bot, NO_PATH );
return;
}
// remove redundant nodes and clean up path
Optimize( bot );
PostProcess();
OnPathChanged( bot, COMPLETE_PATH );
}
/**
* Utility function for when start and goal are in the same area
*/
bool BuildTrivialPath( INextBot *bot, const Vector &goal );
/**
* Determine exactly where the path goes between the given two areas
* on the path. Return this point in 'crossPos'.
*/
virtual void ComputeAreaCrossing( INextBot *bot, const CNavArea *from, const Vector &fromPos, const CNavArea *to, NavDirType dir, Vector *crossPos ) const;
private:
enum { MAX_PATH_SEGMENTS = 256 };
Segment m_path[ MAX_PATH_SEGMENTS ];
int m_segmentCount;
bool ComputePathDetails( INextBot *bot, const Vector &start ); // determine actual path positions
void Optimize( INextBot *bot );
void PostProcess( void );
int FindNextOccludedNode( INextBot *bot, int anchor ); // used by Optimize()
void InsertSegment( Segment newSegment, int i ); // insert new segment at index i
mutable Vector m_pathPos; // used by GetPosition()
mutable Vector m_closePos; // used by GetClosestPosition()
mutable float m_cursorPos; // current cursor position (distance along path)
mutable Data m_cursorData; // used by GetCursorData()
mutable bool m_isCursorDataDirty;
IntervalTimer m_ageTimer; // how old is this path?
CHandle< CBaseCombatCharacter > m_subject; // the subject this path leads to
/**
* Build a vector of adjacent areas reachable from the given area
*/
void CollectAdjacentAreas( CNavArea *area )
{
m_adjAreaIndex = 0;
const NavConnectVector &adjNorth = *area->GetAdjacentAreas( NORTH );
FOR_EACH_VEC( adjNorth, it )
{
if ( m_adjAreaIndex >= MAX_ADJ_AREAS )
break;
m_adjAreaVector[ m_adjAreaIndex ].area = adjNorth[ it ].area;
m_adjAreaVector[ m_adjAreaIndex ].how = GO_NORTH;
m_adjAreaVector[ m_adjAreaIndex ].ladder = NULL;
++m_adjAreaIndex;
}
const NavConnectVector &adjSouth = *area->GetAdjacentAreas( SOUTH );
FOR_EACH_VEC( adjSouth, it )
{
if ( m_adjAreaIndex >= MAX_ADJ_AREAS )
break;
m_adjAreaVector[ m_adjAreaIndex ].area = adjSouth[ it ].area;
m_adjAreaVector[ m_adjAreaIndex ].how = GO_SOUTH;
m_adjAreaVector[ m_adjAreaIndex ].ladder = NULL;
++m_adjAreaIndex;
}
const NavConnectVector &adjWest = *area->GetAdjacentAreas( WEST );
FOR_EACH_VEC( adjWest, it )
{
if ( m_adjAreaIndex >= MAX_ADJ_AREAS )
break;
m_adjAreaVector[ m_adjAreaIndex ].area = adjWest[ it ].area;
m_adjAreaVector[ m_adjAreaIndex ].how = GO_WEST;
m_adjAreaVector[ m_adjAreaIndex ].ladder = NULL;
++m_adjAreaIndex;
}
const NavConnectVector &adjEast = *area->GetAdjacentAreas( EAST );
FOR_EACH_VEC( adjEast, it )
{
if ( m_adjAreaIndex >= MAX_ADJ_AREAS )
break;
m_adjAreaVector[ m_adjAreaIndex ].area = adjEast[ it ].area;
m_adjAreaVector[ m_adjAreaIndex ].how = GO_EAST;
m_adjAreaVector[ m_adjAreaIndex ].ladder = NULL;
++m_adjAreaIndex;
}
const NavLadderConnectVector &adjUpLadder = *area->GetLadders( CNavLadder::LADDER_UP );
FOR_EACH_VEC( adjUpLadder, it )
{
CNavLadder *ladder = adjUpLadder[ it ].ladder;
if ( ladder->m_topForwardArea && m_adjAreaIndex < MAX_ADJ_AREAS )
{
m_adjAreaVector[ m_adjAreaIndex ].area = ladder->m_topForwardArea;
m_adjAreaVector[ m_adjAreaIndex ].how = GO_LADDER_UP;
m_adjAreaVector[ m_adjAreaIndex ].ladder = ladder;
++m_adjAreaIndex;
}
if ( ladder->m_topLeftArea && m_adjAreaIndex < MAX_ADJ_AREAS )
{
m_adjAreaVector[ m_adjAreaIndex ].area = ladder->m_topLeftArea;
m_adjAreaVector[ m_adjAreaIndex ].how = GO_LADDER_UP;
m_adjAreaVector[ m_adjAreaIndex ].ladder = ladder;
++m_adjAreaIndex;
}
if ( ladder->m_topRightArea && m_adjAreaIndex < MAX_ADJ_AREAS )
{
m_adjAreaVector[ m_adjAreaIndex ].area = ladder->m_topRightArea;
m_adjAreaVector[ m_adjAreaIndex ].how = GO_LADDER_UP;
m_adjAreaVector[ m_adjAreaIndex ].ladder = ladder;
++m_adjAreaIndex;
}
}
const NavLadderConnectVector &adjDownLadder = *area->GetLadders( CNavLadder::LADDER_DOWN );
FOR_EACH_VEC( adjDownLadder, it )
{
CNavLadder *ladder = adjDownLadder[ it ].ladder;
if ( m_adjAreaIndex >= MAX_ADJ_AREAS )
break;
if ( ladder->m_bottomArea )
{
m_adjAreaVector[ m_adjAreaIndex ].area = ladder->m_bottomArea;
m_adjAreaVector[ m_adjAreaIndex ].how = GO_LADDER_DOWN;
m_adjAreaVector[ m_adjAreaIndex ].ladder = ladder;
++m_adjAreaIndex;
}
}
}
enum { MAX_ADJ_AREAS = 64 };
struct AdjInfo
{
CNavArea *area;
CNavLadder *ladder;
NavTraverseType how;
};
AdjInfo m_adjAreaVector[ MAX_ADJ_AREAS ];
int m_adjAreaIndex;
};
inline float Path::GetLength( void ) const
{
if (m_segmentCount <= 0)
{
return 0.0f;
}
return m_path[ m_segmentCount-1 ].distanceFromStart;
}
inline bool Path::IsValid( void ) const
{
return (m_segmentCount > 0);
}
inline void Path::Invalidate( void )
{
m_segmentCount = 0;
m_cursorPos = 0.0f;
m_cursorData.pos = vec3_origin;
m_cursorData.forward = Vector( 1.0f, 0, 0 );
m_cursorData.curvature = 0.0f;
m_cursorData.segmentPrior = NULL;
m_isCursorDataDirty = true;
m_subject = NULL;
}
inline const Path::Segment *Path::FirstSegment( void ) const
{
return (IsValid()) ? &m_path[0] : NULL;
}
inline const Path::Segment *Path::NextSegment( const Segment *currentSegment ) const
{
if (currentSegment == NULL || !IsValid())
return NULL;
int i = currentSegment - m_path;
if (i < 0 || i >= m_segmentCount-1)
{
return NULL;
}
return &m_path[ i+1 ];
}
inline const Path::Segment *Path::PriorSegment( const Segment *currentSegment ) const
{
if (currentSegment == NULL || !IsValid())
return NULL;
int i = currentSegment - m_path;
if (i < 1 || i >= m_segmentCount)
{
return NULL;
}
return &m_path[ i-1 ];
}
inline const Path::Segment *Path::LastSegment( void ) const
{
return ( IsValid() ) ? &m_path[ m_segmentCount-1 ] : NULL;
}
inline const Vector &Path::GetStartPosition( void ) const
{
return ( IsValid() ) ? m_path[ 0 ].pos : vec3_origin;
}
inline const Vector &Path::GetEndPosition( void ) const
{
return ( IsValid() ) ? m_path[ m_segmentCount-1 ].pos : vec3_origin;
}
inline CBaseCombatCharacter *Path::GetSubject( void ) const
{
return m_subject;
}
inline void Path::MoveCursorToStart( void )
{
m_cursorPos = 0.0f;
m_isCursorDataDirty = true;
}
inline void Path::MoveCursorToEnd( void )
{
m_cursorPos = GetLength();
m_isCursorDataDirty = true;
}
inline void Path::MoveCursor( float value, MoveCursorType type )
{
if ( type == PATH_ABSOLUTE_DISTANCE )
{
m_cursorPos = value;
}
else // relative distance
{
m_cursorPos += value;
}
if ( m_cursorPos < 0.0f )
{
m_cursorPos = 0.0f;
}
else if ( m_cursorPos > GetLength() )
{
m_cursorPos = GetLength();
}
m_isCursorDataDirty = true;
}
inline float Path::GetCursorPosition( void ) const
{
return m_cursorPos;
}
inline const Path::Segment *Path::GetCurrentGoal( void ) const
{
return NULL;
}
inline float Path::GetAge( void ) const
{
return m_ageTimer.GetElapsedTime();
}
#endif // _NEXT_BOT_PATH_H_
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,106 @@
// NextBotPathFollow.h
// Path following
// Author: Michael Booth, April 2005
//========= Copyright Valve Corporation, All rights reserved. ============//
#ifndef _NEXT_BOT_PATH_FOLLOWER_
#define _NEXT_BOT_PATH_FOLLOWER_
#include "nav_mesh.h"
#include "nav_pathfind.h"
#include "NextBotPath.h"
class INextBot;
class ILocomotion;
//--------------------------------------------------------------------------------------------------------
/**
* A PathFollower extends a Path to include mechanisms to move along (follow) it
*/
class PathFollower : public Path
{
public:
PathFollower( void );
virtual ~PathFollower();
virtual void Invalidate( void ); // (EXTEND) cause the path to become invalid
virtual void Draw( const Path::Segment *start = NULL ) const; // (EXTEND) draw the path for debugging
virtual void OnPathChanged( INextBot *bot, Path::ResultType result ); // invoked when the path is (re)computed (path is valid at the time of this call)
virtual void Update( INextBot *bot ); // move bot along path
virtual const Path::Segment *GetCurrentGoal( void ) const; // return current goal along the path we are trying to reach
virtual void SetMinLookAheadDistance( float value ); // minimum range movement goal must be along path
virtual CBaseEntity *GetHindrance( void ) const; // returns entity that is hindering our progress along the path
virtual bool IsDiscontinuityAhead( INextBot *bot, Path::SegmentType type, float range = -1.0f ) const; // return true if there is a the given discontinuity ahead in the path within the given range (-1 = entire remaining path)
void SetGoalTolerance( float range ); // set tolerance within at which we're considered to be at our goal
private:
const Path::Segment *m_goal; // our current goal along the path
float m_minLookAheadRange;
bool CheckProgress( INextBot *bot );
bool IsAtGoal( INextBot *bot ) const; // return true if reached current path goal
//bool IsOnStairs( INextBot *bot ) const; // return true if bot is standing on a stairway
bool m_isOnStairs;
CountdownTimer m_avoidTimer; // do avoid check more often if we recently avoided
CountdownTimer m_waitTimer; // for waiting for a blocker to move off our path
CHandle< CBaseEntity > m_hindrance;
// debug display data for avoid volumes
bool m_didAvoidCheck;
Vector m_leftFrom;
Vector m_leftTo;
bool m_isLeftClear;
Vector m_rightFrom;
Vector m_rightTo;
bool m_isRightClear;
Vector m_hullMin, m_hullMax;
void AdjustSpeed( INextBot *bot ); // adjust speed based on path curvature
Vector Avoid( INextBot *bot, const Vector &goalPos, const Vector &forward, const Vector &left ); // avoidance movements for very nearby obstacles. returns modified goal position
bool Climbing( INextBot *bot, const Path::Segment *goal, const Vector &forward, const Vector &left, float goalRange ); // climb up ledges
bool JumpOverGaps( INextBot *bot, const Path::Segment *goal, const Vector &forward, const Vector &left, float goalRange ); // jump over gaps
bool LadderUpdate( INextBot *bot ); // move bot along ladder
CBaseEntity *FindBlocker( INextBot *bot ); // if entity is returned, it is blocking us from continuing along our path
float m_goalTolerance;
};
inline void PathFollower::SetGoalTolerance( float range )
{
m_goalTolerance = range;
}
inline const Path::Segment *PathFollower::GetCurrentGoal( void ) const
{
return m_goal;
}
inline void PathFollower::SetMinLookAheadDistance( float value )
{
m_minLookAheadRange = value;
}
inline CBaseEntity *PathFollower::GetHindrance( void ) const
{
return m_hindrance;
}
#endif // _NEXT_BOT_PATH_FOLLOWER_
@@ -0,0 +1,573 @@
// NextBotRetreatPath.h
// Maintain and follow a path that leads safely away from the given Actor
// Author: Michael Booth, February 2007
//========= Copyright Valve Corporation, All rights reserved. ============//
#ifndef _NEXT_BOT_RETREAT_PATH_
#define _NEXT_BOT_RETREAT_PATH_
#include "nav.h"
#include "NextBotInterface.h"
#include "NextBotLocomotionInterface.h"
#include "NextBotRetreatPath.h"
#include "NextBotUtil.h"
#include "NextBotPathFollow.h"
#include "tier0/vprof.h"
//----------------------------------------------------------------------------------------------
/**
* A RetreatPath extends a PathFollower to periodically recompute a path
* away from a threat, and to move along the path away from that threat.
*/
class RetreatPath : public PathFollower
{
public:
RetreatPath( void );
virtual ~RetreatPath() { }
void Update( INextBot *bot, CBaseEntity *threat ); // update path away from threat and move bot along path
virtual float GetMaxPathLength( void ) const; // return maximum path length
virtual void Invalidate( void ); // (EXTEND) cause the path to become invalid
private:
void RefreshPath( INextBot *bot, CBaseEntity *threat );
CountdownTimer m_throttleTimer; // require a minimum time between re-paths
EHANDLE m_pathThreat; // the threat of our existing path
Vector m_pathThreatPos; // where the threat was when the path was built
};
inline RetreatPath::RetreatPath( void )
{
m_throttleTimer.Invalidate();
m_pathThreat = NULL;
}
inline float RetreatPath::GetMaxPathLength( void ) const
{
return 1000.0f;
}
inline void RetreatPath::Invalidate( void )
{
// path is gone, repath at earliest opportunity
m_throttleTimer.Invalidate();
m_pathThreat = NULL;
// extend
PathFollower::Invalidate();
}
//----------------------------------------------------------------------------------------------
/**
* Maintain a path to our chase threat and move along that path
*/
inline void RetreatPath::Update( INextBot *bot, CBaseEntity *threat )
{
VPROF_BUDGET( "RetreatPath::Update", "NextBot" );
if ( threat == NULL )
{
return;
}
// if our path threat changed, repath immediately
if ( threat != m_pathThreat )
{
if ( bot->IsDebugging( INextBot::PATH ) )
{
DevMsg( "%3.2f: bot(#%d) Chase path threat changed (from %X to %X).\n", gpGlobals->curtime, bot->GetEntity()->entindex(), m_pathThreat.Get(), threat );
}
Invalidate();
}
// maintain the path away from the threat
RefreshPath( bot, threat );
// move along the path towards the threat
PathFollower::Update( bot );
}
//--------------------------------------------------------------------------------------------------------------
/**
* Build a path away from retreatFromArea up to retreatRange in length.
*/
class RetreatPathBuilder
{
public:
RetreatPathBuilder( INextBot *me, CBaseEntity *threat, float retreatRange = 500.0f )
{
m_me = me;
m_mover = me->GetLocomotionInterface();
m_threat = threat;
m_retreatRange = retreatRange;
}
CNavArea *ComputePath( void )
{
VPROF_BUDGET( "NavAreaBuildRetreatPath", "NextBot" );
if ( m_mover == NULL )
return NULL;
CNavArea *startArea = m_me->GetEntity()->GetLastKnownArea();
if ( startArea == NULL )
return NULL;
CNavArea *retreatFromArea = TheNavMesh->GetNearestNavArea( m_threat->GetAbsOrigin() );
if ( retreatFromArea == NULL )
return NULL;
startArea->SetParent( NULL );
// start search
CNavArea::ClearSearchLists();
float initCost = Cost( startArea, NULL, NULL );
if ( initCost < 0.0f )
return NULL;
int teamID = m_me->GetEntity()->GetTeamNumber();
startArea->SetTotalCost( initCost );
startArea->AddToOpenList();
// keep track of the area farthest away from the threat
CNavArea *farthestArea = NULL;
float farthestRange = 0.0f;
//
// Dijkstra's algorithm (since we don't know our goal).
// Build a path as far away from the retreat area as possible.
// Minimize total path length and danger.
// Maximize distance to threat of end of path.
//
while( !CNavArea::IsOpenListEmpty() )
{
// get next area to check
CNavArea *area = CNavArea::PopOpenList();
area->AddToClosedList();
// don't consider blocked areas
if ( area->IsBlocked( teamID ) )
continue;
// build adjacent area array
CollectAdjacentAreas( area );
// search adjacent areas
for( int i=0; i<m_adjAreaIndex; ++i )
{
CNavArea *newArea = m_adjAreaVector[ i ].area;
// only visit each area once
if ( newArea->IsClosed() )
continue;
// don't consider blocked areas
if ( newArea->IsBlocked( teamID ) )
continue;
// don't use this area if it is out of range
if ( ( newArea->GetCenter() - m_me->GetEntity()->GetAbsOrigin() ).IsLengthGreaterThan( m_retreatRange ) )
continue;
// determine cost of traversing this area
float newCost = Cost( newArea, area, m_adjAreaVector[ i ].ladder );
// don't use adjacent area if cost functor says it is a dead-end
if ( newCost < 0.0f )
continue;
if ( newArea->IsOpen() && newArea->GetTotalCost() <= newCost )
{
// we have already visited this area, and it has a better path
continue;
}
else
{
// whether this area has been visited or not, we now have a better path
newArea->SetParent( area, m_adjAreaVector[ i ].how );
newArea->SetTotalCost( newCost );
// use 'cost so far' to hold cumulative cost
newArea->SetCostSoFar( newCost );
// tricky bit here - relying on OpenList being sorted by cost
if ( newArea->IsOpen() )
{
// area already on open list, update the list order to keep costs sorted
newArea->UpdateOnOpenList();
}
else
{
newArea->AddToOpenList();
}
// keep track of area farthest from threat
float threatRange = ( newArea->GetCenter() - m_threat->GetAbsOrigin() ).Length();
if ( threatRange > farthestRange )
{
farthestArea = newArea;
farthestRange = threatRange;
}
}
}
}
return farthestArea;
}
/**
* Build a vector of adjacent areas reachable from the given area
*/
void CollectAdjacentAreas( CNavArea *area )
{
m_adjAreaIndex = 0;
const NavConnectVector &adjNorth = *area->GetAdjacentAreas( NORTH );
FOR_EACH_VEC( adjNorth, it )
{
if ( m_adjAreaIndex >= MAX_ADJ_AREAS )
break;
m_adjAreaVector[ m_adjAreaIndex ].area = adjNorth[ it ].area;
m_adjAreaVector[ m_adjAreaIndex ].how = GO_NORTH;
m_adjAreaVector[ m_adjAreaIndex ].ladder = NULL;
++m_adjAreaIndex;
}
const NavConnectVector &adjSouth = *area->GetAdjacentAreas( SOUTH );
FOR_EACH_VEC( adjSouth, it )
{
if ( m_adjAreaIndex >= MAX_ADJ_AREAS )
break;
m_adjAreaVector[ m_adjAreaIndex ].area = adjSouth[ it ].area;
m_adjAreaVector[ m_adjAreaIndex ].how = GO_SOUTH;
m_adjAreaVector[ m_adjAreaIndex ].ladder = NULL;
++m_adjAreaIndex;
}
const NavConnectVector &adjWest = *area->GetAdjacentAreas( WEST );
FOR_EACH_VEC( adjWest, it )
{
if ( m_adjAreaIndex >= MAX_ADJ_AREAS )
break;
m_adjAreaVector[ m_adjAreaIndex ].area = adjWest[ it ].area;
m_adjAreaVector[ m_adjAreaIndex ].how = GO_WEST;
m_adjAreaVector[ m_adjAreaIndex ].ladder = NULL;
++m_adjAreaIndex;
}
const NavConnectVector &adjEast = *area->GetAdjacentAreas( EAST );
FOR_EACH_VEC( adjEast, it )
{
if ( m_adjAreaIndex >= MAX_ADJ_AREAS )
break;
m_adjAreaVector[ m_adjAreaIndex ].area = adjEast[ it ].area;
m_adjAreaVector[ m_adjAreaIndex ].how = GO_EAST;
m_adjAreaVector[ m_adjAreaIndex ].ladder = NULL;
++m_adjAreaIndex;
}
const NavLadderConnectVector &adjUpLadder = *area->GetLadders( CNavLadder::LADDER_UP );
FOR_EACH_VEC( adjUpLadder, it )
{
CNavLadder *ladder = adjUpLadder[ it ].ladder;
if ( ladder->m_topForwardArea && m_adjAreaIndex < MAX_ADJ_AREAS )
{
m_adjAreaVector[ m_adjAreaIndex ].area = ladder->m_topForwardArea;
m_adjAreaVector[ m_adjAreaIndex ].how = GO_LADDER_UP;
m_adjAreaVector[ m_adjAreaIndex ].ladder = ladder;
++m_adjAreaIndex;
}
if ( ladder->m_topLeftArea && m_adjAreaIndex < MAX_ADJ_AREAS )
{
m_adjAreaVector[ m_adjAreaIndex ].area = ladder->m_topLeftArea;
m_adjAreaVector[ m_adjAreaIndex ].how = GO_LADDER_UP;
m_adjAreaVector[ m_adjAreaIndex ].ladder = ladder;
++m_adjAreaIndex;
}
if ( ladder->m_topRightArea && m_adjAreaIndex < MAX_ADJ_AREAS )
{
m_adjAreaVector[ m_adjAreaIndex ].area = ladder->m_topRightArea;
m_adjAreaVector[ m_adjAreaIndex ].how = GO_LADDER_UP;
m_adjAreaVector[ m_adjAreaIndex ].ladder = ladder;
++m_adjAreaIndex;
}
}
const NavLadderConnectVector &adjDownLadder = *area->GetLadders( CNavLadder::LADDER_DOWN );
FOR_EACH_VEC( adjDownLadder, it )
{
CNavLadder *ladder = adjDownLadder[ it ].ladder;
if ( m_adjAreaIndex >= MAX_ADJ_AREAS )
break;
if ( ladder->m_bottomArea )
{
m_adjAreaVector[ m_adjAreaIndex ].area = ladder->m_bottomArea;
m_adjAreaVector[ m_adjAreaIndex ].how = GO_LADDER_DOWN;
m_adjAreaVector[ m_adjAreaIndex ].ladder = ladder;
++m_adjAreaIndex;
}
}
}
/**
* Cost minimizes path length traveled thus far and "danger" (proximity to threat(s))
*/
float Cost( CNavArea *area, CNavArea *fromArea, const CNavLadder *ladder )
{
// check if we can use this area
if ( !m_mover->IsAreaTraversable( area ) )
{
return -1.0f;
}
int teamID = m_me->GetEntity()->GetTeamNumber();
if ( area->IsBlocked( teamID ) )
{
return -1.0f;
}
const float debugDeltaT = 3.0f;
float cost;
const float maxThreatRange = 500.0f;
const float dangerDensity = 1000.0f;
if ( fromArea == NULL )
{
cost = 0.0f;
if ( area->Contains( m_threat->GetAbsOrigin() ) )
{
// maximum danger - threat is in the area with us
cost += 10.0f * dangerDensity;
if ( m_me->IsDebugging( INextBot::PATH ) )
{
area->DrawFilled( 255, 0, 0, 128 );
}
}
else
{
// danger proportional to range to us
float rangeToThreat = ( m_threat->GetAbsOrigin() - m_me->GetEntity()->GetAbsOrigin() ).Length();
if ( rangeToThreat < maxThreatRange )
{
cost += dangerDensity * ( 1.0f - ( rangeToThreat / maxThreatRange ) );
if ( m_me->IsDebugging( INextBot::PATH ) )
{
NDebugOverlay::Line( m_me->GetEntity()->GetAbsOrigin(), m_threat->GetAbsOrigin(), 255, 0, 0, true, debugDeltaT );
}
}
}
}
else
{
// compute distance traveled along path so far
float dist;
if ( ladder )
{
const float ladderCostFactor = 100.0f;
dist = ladderCostFactor * ladder->m_length;
}
else
{
Vector to = area->GetCenter() - fromArea->GetCenter();
dist = to.Length();
// check for vertical discontinuities
Vector closeFrom, closeTo;
area->GetClosestPointOnArea( fromArea->GetCenter(), &closeTo );
fromArea->GetClosestPointOnArea( area->GetCenter(), &closeFrom );
float deltaZ = closeTo.z - closeFrom.z;
if ( deltaZ > m_mover->GetMaxJumpHeight() )
{
// too high to jump
return -1.0f;
}
else if ( -deltaZ > m_mover->GetDeathDropHeight() )
{
// too far down to drop
return -1.0f;
}
// prefer to maintain our level
const float climbCost = 10.0f;
dist += climbCost * fabs( deltaZ );
}
cost = dist + fromArea->GetTotalCost();
// Add in danger cost due to threat
// Assume straight line between areas and find closest point
// to the threat along that line segment. The distance between
// the threat and closest point on the line is the danger cost.
// path danger is CUMULATIVE
float dangerCost = fromArea->GetCostSoFar();
Vector close;
float t;
CalcClosestPointOnLineSegment( m_threat->GetAbsOrigin(), area->GetCenter(), fromArea->GetCenter(), close, &t );
if ( t < 0.0f )
{
close = area->GetCenter();
}
else if ( t > 1.0f )
{
close = fromArea->GetCenter();
}
float rangeToThreat = ( m_threat->GetAbsOrigin() - close ).Length();
if ( rangeToThreat < maxThreatRange )
{
float dangerFactor = 1.0f - ( rangeToThreat / maxThreatRange );
dangerCost = dangerDensity * dangerFactor;
if ( m_me->IsDebugging( INextBot::PATH ) )
{
NDebugOverlay::HorzArrow( fromArea->GetCenter(), area->GetCenter(), 5, 255 * dangerFactor, 0, 0, 255, true, debugDeltaT );
Vector to = close - m_threat->GetAbsOrigin();
to.NormalizeInPlace();
NDebugOverlay::Line( close, close - 50.0f * to, 255, 0, 0, true, debugDeltaT );
}
}
cost += dangerCost;
}
return cost;
}
private:
INextBot *m_me;
ILocomotion *m_mover;
CBaseEntity *m_threat;
float m_retreatRange;
enum { MAX_ADJ_AREAS = 64 };
struct AdjInfo
{
CNavArea *area;
CNavLadder *ladder;
NavTraverseType how;
};
AdjInfo m_adjAreaVector[ MAX_ADJ_AREAS ];
int m_adjAreaIndex;
};
//----------------------------------------------------------------------------------------------
/**
* Periodically rebuild the path away from our threat
*/
inline void RetreatPath::RefreshPath( INextBot *bot, CBaseEntity *threat )
{
VPROF_BUDGET( "RetreatPath::RefreshPath", "NextBot" );
if ( threat == NULL )
{
if ( bot->IsDebugging( INextBot::PATH ) )
{
DevMsg( "%3.2f: bot(#%d) CasePath::RefreshPath failed. No threat.\n", gpGlobals->curtime, bot->GetEntity()->entindex() );
}
return;
}
// don't change our path if we're on a ladder
ILocomotion *mover = bot->GetLocomotionInterface();
if ( IsValid() && mover && mover->IsUsingLadder() )
{
if ( bot->IsDebugging( INextBot::PATH ) )
{
DevMsg( "%3.2f: bot(#%d) RetreatPath::RefreshPath failed. Bot is on a ladder.\n", gpGlobals->curtime, bot->GetEntity()->entindex() );
}
return;
}
// the closer we get, the more accurate our path needs to be
Vector to = threat->GetAbsOrigin() - bot->GetPosition();
const float minTolerance = 0.0f;
const float toleranceRate = 0.33f;
float tolerance = minTolerance + toleranceRate * to.Length();
if ( !IsValid() || ( threat->GetAbsOrigin() - m_pathThreatPos ).IsLengthGreaterThan( tolerance ) )
{
if ( !m_throttleTimer.IsElapsed() )
{
// require a minimum time between repaths, as long as we have a path to follow
if ( bot->IsDebugging( INextBot::PATH ) )
{
DevMsg( "%3.2f: bot(#%d) RetreatPath::RefreshPath failed. Rate throttled.\n", gpGlobals->curtime, bot->GetEntity()->entindex() );
}
return;
}
// remember our path threat
m_pathThreat = threat;
m_pathThreatPos = threat->GetAbsOrigin();
RetreatPathBuilder retreat( bot, threat, GetMaxPathLength() );
CNavArea *goalArea = retreat.ComputePath();
if ( goalArea )
{
AssemblePrecomputedPath( bot, goalArea->GetCenter(), goalArea );
}
else
{
// all adjacent areas are too far away - just move directly away from threat
Vector to = threat->GetAbsOrigin() - bot->GetPosition();
BuildTrivialPath( bot, bot->GetPosition() - to );
}
const float minRepathInterval = 0.5f;
m_throttleTimer.Start( minRepathInterval );
}
}
#endif // _NEXT_BOT_RETREAT_PATH_
@@ -0,0 +1,22 @@
// NextBotPlayer.cpp
// A CBasePlayer bot based on the NextBot technology
// Author: Michael Booth, November 2005
//========= Copyright Valve Corporation, All rights reserved. ============//
#include "cbase.h"
#include "nav_mesh.h"
#include "NextBot.h"
#include "NextBotPlayer.h"
#include "in_buttons.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
ConVar NextBotPlayerStop( "nb_player_stop", "0", FCVAR_CHEAT, "Stop all NextBotPlayers from updating" );
ConVar NextBotPlayerWalk( "nb_player_walk", "0", FCVAR_CHEAT, "Force bots to walk" );
ConVar NextBotPlayerCrouch( "nb_player_crouch", "0", FCVAR_CHEAT, "Force bots to crouch" );
ConVar NextBotPlayerMove( "nb_player_move", "1", FCVAR_CHEAT, "Prevents bots from moving" );
+910
View File
@@ -0,0 +1,910 @@
// NextBotPlayer.h
// A CBasePlayer bot based on the NextBot technology
// Author: Michael Booth, November 2005
//========= Copyright Valve Corporation, All rights reserved. ============//
#ifndef _NEXT_BOT_PLAYER_H_
#define _NEXT_BOT_PLAYER_H_
#include "cbase.h"
#include "gameinterface.h"
#include "NextBot.h"
#include "Path/NextBotPathFollow.h"
//#include "NextBotPlayerBody.h"
#include "NextBotBehavior.h"
#include "in_buttons.h"
extern ConVar NextBotPlayerStop;
extern ConVar NextBotPlayerWalk;
extern ConVar NextBotPlayerCrouch;
extern ConVar NextBotPlayerMove;
//--------------------------------------------------------------------------------------------------
/**
* Instantiate a NextBot derived from CBasePlayer and spawn it into the environment.
* Assumes class T is derived from CBasePlayer, and has the following method that
* creates a new entity of type T and returns it:
*
* static CBasePlayer *T::AllocatePlayerEntity( edict_t *pEdict, const char *playerName )
*
*/
template < typename T >
T * NextBotCreatePlayerBot( const char *name, bool bReportFakeClient = true )
{
/*
if ( UTIL_ClientsInGame() >= gpGlobals->maxClients )
{
Msg( "CreatePlayerBot: Failed - server is full (%d/%d clients).\n", UTIL_ClientsInGame(), gpGlobals->maxClients );
return NULL;
}
*/
// This is a "back door" for allocating a custom player bot entity when
// the engine calls ClientPutInServer (from CreateFakeClient)
ClientPutInServerOverride( T::AllocatePlayerEntity );
// create the bot and spawn it into the environment
edict_t *botEdict = engine->CreateFakeClientEx( name, bReportFakeClient );
// close the "back door"
ClientPutInServerOverride( NULL );
if ( botEdict == NULL )
{
Msg( "CreatePlayerBot: Unable to create bot %s - CreateFakeClient() returned NULL.\n", name );
return NULL;
}
// create an instance of the bot's class and bind it to the edict
T *bot = dynamic_cast< T * >( CBaseEntity::Instance( botEdict ) );
if ( bot == NULL )
{
Assert( false );
Error( "CreatePlayerBot: Could not Instance() from the bot edict.\n" );
return NULL;
}
bot->SetPlayerName( name );
// flag this as a fakeclient (bot)
bot->ClearFlags();
bot->AddFlag( FL_CLIENT | FL_FAKECLIENT );
return bot;
}
//--------------------------------------------------------------------------------------------------
/**
* Interface to access player input buttons.
* Unless a duration is given, each button is released at the start of the next frame.
* The release methods allow releasing a button before its duration has elapsed.
*/
class INextBotPlayerInput
{
public:
virtual void PressFireButton( float duration = -1.0f ) = 0;
virtual void ReleaseFireButton( void ) = 0;
virtual void PressAltFireButton( float duration = -1.0f ) = 0;
virtual void ReleaseAltFireButton( void ) = 0;
virtual void PressMeleeButton( float duration = -1.0f ) = 0;
virtual void ReleaseMeleeButton( void ) = 0;
virtual void PressSpecialFireButton( float duration = -1.0f ) = 0;
virtual void ReleaseSpecialFireButton( void ) = 0;
virtual void PressUseButton( float duration = -1.0f ) = 0;
virtual void ReleaseUseButton( void ) = 0;
virtual void PressReloadButton( float duration = -1.0f ) = 0;
virtual void ReleaseReloadButton( void ) = 0;
virtual void PressForwardButton( float duration = -1.0f ) = 0;
virtual void ReleaseForwardButton( void ) = 0;
virtual void PressBackwardButton( float duration = -1.0f ) = 0;
virtual void ReleaseBackwardButton( void ) = 0;
virtual void PressLeftButton( float duration = -1.0f ) = 0;
virtual void ReleaseLeftButton( void ) = 0;
virtual void PressRightButton( float duration = -1.0f ) = 0;
virtual void ReleaseRightButton( void ) = 0;
virtual void PressJumpButton( float duration = -1.0f ) = 0;
virtual void ReleaseJumpButton( void ) = 0;
virtual void PressCrouchButton( float duration = -1.0f ) = 0;
virtual void ReleaseCrouchButton( void ) = 0;
virtual void PressWalkButton( float duration = -1.0f ) = 0;
virtual void ReleaseWalkButton( void ) = 0;
virtual void SetButtonScale( float forward, float right ) = 0;
};
//--------------------------------------------------------------------------------------------------
/**
* Drive a CBasePlayer-derived entity via NextBot logic
*/
template < typename PlayerType >
class NextBotPlayer : public PlayerType, public INextBot, public INextBotPlayerInput
{
public:
DECLARE_CLASS( NextBotPlayer, PlayerType );
NextBotPlayer( void );
virtual ~NextBotPlayer();
virtual void Spawn( void );
virtual void SetSpawnPoint( CBaseEntity *spawnPoint ); // define place in environment where bot will (re)spawn
virtual CBaseEntity *EntSelectSpawnPoint( void );
virtual void PhysicsSimulate( void );
virtual bool IsNetClient( void ) const { return false; } // Bots should return FALSE for this, they can't receive NET messages
virtual bool IsFakeClient( void ) const { return true; }
virtual bool IsBot( void ) const { return true; }
virtual INextBot *MyNextBotPointer( void ) { return this; }
// this is valid because the templatized PlayerType must be derived from CBasePlayer, which is derived from CBaseCombatCharacter
virtual CBaseCombatCharacter *GetEntity( void ) const { return ( PlayerType * )this; }
virtual bool IsRemovedOnReset( void ) const { return false; } // remove this bot when the NextBot manager calls Reset
virtual bool IsDormantWhenDead( void ) const { return true; } // should this player-bot continue to update itself when dead (respawn logic, etc)
// allocate a bot and bind it to the edict
static CBasePlayer *AllocatePlayerEntity( edict_t *edict, const char *playerName );
//------------------------------------------------------------------------
// utility methods
float GetDistanceBetween( CBaseEntity *other ) const; // return distance between us and the given entity
bool IsDistanceBetweenLessThan( CBaseEntity *other, float range ) const; // return true if distance between is less than the given value
bool IsDistanceBetweenGreaterThan( CBaseEntity *other, float range ) const; // return true if distance between is greater than the given value
float GetDistanceBetween( const Vector &target ) const; // return distance between us and the given entity
bool IsDistanceBetweenLessThan( const Vector &target, float range ) const; // return true if distance between is less than the given value
bool IsDistanceBetweenGreaterThan( const Vector &target, float range ) const; // return true if distance between is greater than the given value
//------------------------------------------------------------------------
// INextBotPlayerInput
virtual void PressFireButton( float duration = -1.0f );
virtual void ReleaseFireButton( void );
virtual void PressAltFireButton( float duration = -1.0f );
virtual void ReleaseAltFireButton( void );
virtual void PressMeleeButton( float duration = -1.0f );
virtual void ReleaseMeleeButton( void );
virtual void PressSpecialFireButton( float duration = -1.0f );
virtual void ReleaseSpecialFireButton( void );
virtual void PressUseButton( float duration = -1.0f );
virtual void ReleaseUseButton( void );
virtual void PressReloadButton( float duration = -1.0f );
virtual void ReleaseReloadButton( void );
virtual void PressForwardButton( float duration = -1.0f );
virtual void ReleaseForwardButton( void );
virtual void PressBackwardButton( float duration = -1.0f );
virtual void ReleaseBackwardButton( void );
virtual void PressLeftButton( float duration = -1.0f );
virtual void ReleaseLeftButton( void );
virtual void PressRightButton( float duration = -1.0f );
virtual void ReleaseRightButton( void );
virtual void PressJumpButton( float duration = -1.0f );
virtual void ReleaseJumpButton( void );
virtual void PressCrouchButton( float duration = -1.0f );
virtual void ReleaseCrouchButton( void );
virtual void PressWalkButton( float duration = -1.0f );
virtual void ReleaseWalkButton( void );
virtual void SetButtonScale( float forward, float right );
//------------------------------------------------------------------------
// Event hooks into NextBot system
virtual int OnTakeDamage_Alive( const CTakeDamageInfo &info );
virtual int OnTakeDamage_Dying( const CTakeDamageInfo &info );
virtual void Event_Killed( const CTakeDamageInfo &info );
virtual void HandleAnimEvent( animevent_t *event );
virtual void OnNavAreaChanged( CNavArea *enteredArea, CNavArea *leftArea ); // invoked (by UpdateLastKnownArea) when we enter a new nav area (or it is reset to NULL)
virtual void Touch( CBaseEntity *other );
virtual void Weapon_Equip( CBaseCombatWeapon *weapon ); // for OnPickUp
virtual void Weapon_Drop( CBaseCombatWeapon *weapon, const Vector *target, const Vector *velocity ); // for OnDrop
virtual void OnMainActivityComplete( Activity newActivity, Activity oldActivity );
virtual void OnMainActivityInterrupted( Activity newActivity, Activity oldActivity );
//------------------------------------------------------------------------
bool IsAbleToAutoCenterOnLadders( void ) const;
virtual void AvoidPlayers( CUserCmd *pCmd ) { } // some game types allow players to pass through each other, this method pushes them apart
public:
// begin INextBot ------------------------------------------------------------------------------------------------------------------
virtual void Update( void ); // (EXTEND) update internal state
protected:
int m_inputButtons; // this is still needed to guarantee each button press is captured at least once
int m_prevInputButtons;
CountdownTimer m_fireButtonTimer;
CountdownTimer m_meleeButtonTimer;
CountdownTimer m_specialFireButtonTimer;
CountdownTimer m_useButtonTimer;
CountdownTimer m_reloadButtonTimer;
CountdownTimer m_forwardButtonTimer;
CountdownTimer m_backwardButtonTimer;
CountdownTimer m_leftButtonTimer;
CountdownTimer m_rightButtonTimer;
CountdownTimer m_jumpButtonTimer;
CountdownTimer m_crouchButtonTimer;
CountdownTimer m_walkButtonTimer;
CountdownTimer m_buttonScaleTimer;
IntervalTimer m_burningTimer; // how long since we were last burning
float m_forwardScale;
float m_rightScale;
CHandle< CBaseEntity > m_spawnPointEntity;
};
template < typename PlayerType >
inline void NextBotPlayer< PlayerType >::SetSpawnPoint( CBaseEntity *spawnPoint )
{
m_spawnPointEntity = spawnPoint;
}
template < typename PlayerType >
inline CBaseEntity *NextBotPlayer< PlayerType >::EntSelectSpawnPoint( void )
{
if ( m_spawnPointEntity != NULL )
return m_spawnPointEntity;
return BaseClass::EntSelectSpawnPoint();
}
template < typename PlayerType >
inline float NextBotPlayer< PlayerType >::GetDistanceBetween( CBaseEntity *other ) const
{
return (this->GetAbsOrigin() - other->GetAbsOrigin()).Length();
}
template < typename PlayerType >
inline bool NextBotPlayer< PlayerType >::IsDistanceBetweenLessThan( CBaseEntity *other, float range ) const
{
return (this->GetAbsOrigin() - other->GetAbsOrigin()).IsLengthLessThan( range );
}
template < typename PlayerType >
inline bool NextBotPlayer< PlayerType >::IsDistanceBetweenGreaterThan( CBaseEntity *other, float range ) const
{
return (this->GetAbsOrigin() - other->GetAbsOrigin()).IsLengthGreaterThan( range );
}
template < typename PlayerType >
inline float NextBotPlayer< PlayerType >::GetDistanceBetween( const Vector &target ) const
{
return (this->GetAbsOrigin() - target).Length();
}
template < typename PlayerType >
inline bool NextBotPlayer< PlayerType >::IsDistanceBetweenLessThan( const Vector &target, float range ) const
{
return (this->GetAbsOrigin() - target).IsLengthLessThan( range );
}
template < typename PlayerType >
inline bool NextBotPlayer< PlayerType >::IsDistanceBetweenGreaterThan( const Vector &target, float range ) const
{
return (this->GetAbsOrigin() - target).IsLengthGreaterThan( range );
}
template < typename PlayerType >
inline void NextBotPlayer< PlayerType >::PressFireButton( float duration )
{
m_inputButtons |= IN_ATTACK;
m_fireButtonTimer.Start( duration );
}
template < typename PlayerType >
inline void NextBotPlayer< PlayerType >::ReleaseFireButton( void )
{
m_inputButtons &= ~IN_ATTACK;
m_fireButtonTimer.Invalidate();
}
template < typename PlayerType >
inline void NextBotPlayer< PlayerType >::PressAltFireButton( float duration )
{
PressMeleeButton( duration );
}
template < typename PlayerType >
inline void NextBotPlayer< PlayerType >::ReleaseAltFireButton( void )
{
ReleaseMeleeButton();
}
template < typename PlayerType >
inline void NextBotPlayer< PlayerType >::PressMeleeButton( float duration )
{
m_inputButtons |= IN_ATTACK2;
m_meleeButtonTimer.Start( duration );
}
template < typename PlayerType >
inline void NextBotPlayer< PlayerType >::ReleaseMeleeButton( void )
{
m_inputButtons &= ~IN_ATTACK2;
m_meleeButtonTimer.Invalidate();
}
template < typename PlayerType >
inline void NextBotPlayer< PlayerType >::PressSpecialFireButton( float duration )
{
m_inputButtons |= IN_ATTACK3;
m_specialFireButtonTimer.Start( duration );
}
template < typename PlayerType >
inline void NextBotPlayer< PlayerType >::ReleaseSpecialFireButton( void )
{
m_inputButtons &= ~IN_ATTACK3;
m_specialFireButtonTimer.Invalidate();
}
template < typename PlayerType >
inline void NextBotPlayer< PlayerType >::PressUseButton( float duration )
{
m_inputButtons |= IN_USE;
m_useButtonTimer.Start( duration );
}
template < typename PlayerType >
inline void NextBotPlayer< PlayerType >::ReleaseUseButton( void )
{
m_inputButtons &= ~IN_USE;
m_useButtonTimer.Invalidate();
}
template < typename PlayerType >
inline void NextBotPlayer< PlayerType >::PressReloadButton( float duration )
{
m_inputButtons |= IN_RELOAD;
m_reloadButtonTimer.Start( duration );
}
template < typename PlayerType >
inline void NextBotPlayer< PlayerType >::ReleaseReloadButton( void )
{
m_inputButtons &= ~IN_RELOAD;
m_reloadButtonTimer.Invalidate();
}
template < typename PlayerType >
inline void NextBotPlayer< PlayerType >::PressJumpButton( float duration )
{
m_inputButtons |= IN_JUMP;
m_jumpButtonTimer.Start( duration );
}
template < typename PlayerType >
inline void NextBotPlayer< PlayerType >::ReleaseJumpButton( void )
{
m_inputButtons &= ~IN_JUMP;
m_jumpButtonTimer.Invalidate();
}
template < typename PlayerType >
inline void NextBotPlayer< PlayerType >::PressCrouchButton( float duration )
{
m_inputButtons |= IN_DUCK;
m_crouchButtonTimer.Start( duration );
}
template < typename PlayerType >
inline void NextBotPlayer< PlayerType >::ReleaseCrouchButton( void )
{
m_inputButtons &= ~IN_DUCK;
m_crouchButtonTimer.Invalidate();
}
template < typename PlayerType >
inline void NextBotPlayer< PlayerType >::PressWalkButton( float duration )
{
m_inputButtons |= IN_SPEED;
m_walkButtonTimer.Start( duration );
}
template < typename PlayerType >
inline void NextBotPlayer< PlayerType >::ReleaseWalkButton( void )
{
m_inputButtons &= ~IN_SPEED;
m_walkButtonTimer.Invalidate();
}
template < typename PlayerType >
inline void NextBotPlayer< PlayerType >::PressForwardButton( float duration )
{
m_inputButtons |= IN_FORWARD;
m_forwardButtonTimer.Start( duration );
}
template < typename PlayerType >
inline void NextBotPlayer< PlayerType >::ReleaseForwardButton( void )
{
m_inputButtons &= ~IN_FORWARD;
m_forwardButtonTimer.Invalidate();
}
template < typename PlayerType >
inline void NextBotPlayer< PlayerType >::PressBackwardButton( float duration )
{
m_inputButtons |= IN_BACK;
m_backwardButtonTimer.Start( duration );
}
template < typename PlayerType >
inline void NextBotPlayer< PlayerType >::ReleaseBackwardButton( void )
{
m_inputButtons &= ~IN_BACK;
m_backwardButtonTimer.Invalidate();
}
template < typename PlayerType >
inline void NextBotPlayer< PlayerType >::PressLeftButton( float duration )
{
m_inputButtons |= IN_MOVELEFT;
m_leftButtonTimer.Start( duration );
}
template < typename PlayerType >
inline void NextBotPlayer< PlayerType >::ReleaseLeftButton( void )
{
m_inputButtons &= ~IN_MOVELEFT;
m_leftButtonTimer.Invalidate();
}
template < typename PlayerType >
inline void NextBotPlayer< PlayerType >::PressRightButton( float duration )
{
m_inputButtons |= IN_MOVERIGHT;
m_rightButtonTimer.Start( duration );
}
template < typename PlayerType >
inline void NextBotPlayer< PlayerType >::ReleaseRightButton( void )
{
m_inputButtons &= ~IN_MOVERIGHT;
m_rightButtonTimer.Invalidate();
}
template < typename PlayerType >
inline void NextBotPlayer< PlayerType >::SetButtonScale( float forward, float right )
{
m_forwardScale = forward;
m_rightScale = right;
m_buttonScaleTimer.Start( 0.01 );
}
//-----------------------------------------------------------------------------------------------------
template < typename PlayerType >
inline NextBotPlayer< PlayerType >::NextBotPlayer( void )
{
m_prevInputButtons = 0;
m_inputButtons = 0;
m_burningTimer.Invalidate();
m_spawnPointEntity = NULL;
}
//-----------------------------------------------------------------------------------------------------
template < typename PlayerType >
inline NextBotPlayer< PlayerType >::~NextBotPlayer()
{
}
//-----------------------------------------------------------------------------------------------------
template < typename PlayerType >
inline void NextBotPlayer< PlayerType >::Spawn( void )
{
engine->SetFakeClientConVarValue( this->edict(), "cl_autohelp", "0" );
m_prevInputButtons = m_inputButtons = 0;
m_fireButtonTimer.Invalidate();
m_meleeButtonTimer.Invalidate();
m_specialFireButtonTimer.Invalidate();
m_useButtonTimer.Invalidate();
m_reloadButtonTimer.Invalidate();
m_forwardButtonTimer.Invalidate();
m_backwardButtonTimer.Invalidate();
m_leftButtonTimer.Invalidate();
m_rightButtonTimer.Invalidate();
m_jumpButtonTimer.Invalidate();
m_crouchButtonTimer.Invalidate();
m_walkButtonTimer.Invalidate();
m_buttonScaleTimer.Invalidate();
m_forwardScale = m_rightScale = 0.04;
m_burningTimer.Invalidate();
// reset first, because Spawn() may access various interfaces
INextBot::Reset();
BaseClass::Spawn();
}
//-----------------------------------------------------------------------------------------------------
inline void _NextBot_BuildUserCommand( CUserCmd *cmd, const QAngle &viewangles, float forwardmove, float sidemove, float upmove, int buttons, byte impulse )
{
Q_memset( cmd, 0, sizeof( CUserCmd ) );
cmd->command_number = gpGlobals->tickcount;
cmd->forwardmove = forwardmove;
cmd->sidemove = sidemove;
cmd->upmove = upmove;
cmd->buttons = buttons;
cmd->impulse = impulse;
VectorCopy( viewangles, cmd->viewangles );
cmd->random_seed = random->RandomInt( 0, 0x7fffffff );
}
//-----------------------------------------------------------------------------------------------------
template < typename PlayerType >
inline void NextBotPlayer< PlayerType >::PhysicsSimulate( void )
{
VPROF( "NextBotPlayer::PhysicsSimulate" );
// Make sure not to simulate this guy twice per frame
if ( PlayerType::m_nSimulationTick == gpGlobals->tickcount )
{
return;
}
if ( engine->IsPaused() )
{
// We're paused - don't add new commands
PlayerType::PhysicsSimulate();
return;
}
if ( ( IsDormantWhenDead() && PlayerType::m_lifeState == LIFE_DEAD ) || NextBotStop.GetBool() )
{
// death animation complete - nothing left to do except let PhysicsSimulate run PreThink etc
PlayerType::PhysicsSimulate();
return;
}
int inputButtons;
//
// Update bot behavior
//
if ( BeginUpdate() )
{
Update();
// build button bits
if ( !m_fireButtonTimer.IsElapsed() )
m_inputButtons |= IN_ATTACK;
if ( !m_meleeButtonTimer.IsElapsed() )
m_inputButtons |= IN_ATTACK2;
if ( !m_specialFireButtonTimer.IsElapsed() )
m_inputButtons |= IN_ATTACK3;
if ( !m_useButtonTimer.IsElapsed() )
m_inputButtons |= IN_USE;
if ( !m_reloadButtonTimer.IsElapsed() )
m_inputButtons |= IN_RELOAD;
if ( !m_forwardButtonTimer.IsElapsed() )
m_inputButtons |= IN_FORWARD;
if ( !m_backwardButtonTimer.IsElapsed() )
m_inputButtons |= IN_BACK;
if ( !m_leftButtonTimer.IsElapsed() )
m_inputButtons |= IN_MOVELEFT;
if ( !m_rightButtonTimer.IsElapsed() )
m_inputButtons |= IN_MOVERIGHT;
if ( !m_jumpButtonTimer.IsElapsed() )
m_inputButtons |= IN_JUMP;
if ( !m_crouchButtonTimer.IsElapsed() )
m_inputButtons |= IN_DUCK;
if ( !m_walkButtonTimer.IsElapsed() )
m_inputButtons |= IN_SPEED;
m_prevInputButtons = m_inputButtons;
inputButtons = m_inputButtons;
EndUpdate();
}
else
{
// HACK: Smooth out body animations
GetBodyInterface()->Update();
// keep buttons pressed between Update() calls (m_prevInputButtons),
// and include any button presses that occurred this tick (m_inputButtons).
inputButtons = m_prevInputButtons | m_inputButtons;
}
//
// Convert NextBot locomotion and posture into
// player commands
//
IBody *body = GetBodyInterface();
ILocomotion *mover = GetLocomotionInterface();
if ( body->IsActualPosture( IBody::CROUCH ) )
{
inputButtons |= IN_DUCK;
}
float forwardSpeed = 0.0f;
float strafeSpeed = 0.0f;
float verticalSpeed = ( m_inputButtons & IN_JUMP ) ? mover->GetRunSpeed() : 0.0f;
if ( inputButtons & IN_FORWARD )
{
forwardSpeed = mover->GetRunSpeed();
}
else if ( inputButtons & IN_BACK )
{
forwardSpeed = -mover->GetRunSpeed();
}
if ( inputButtons & IN_MOVELEFT )
{
strafeSpeed = -mover->GetRunSpeed();
}
else if ( inputButtons & IN_MOVERIGHT )
{
strafeSpeed = mover->GetRunSpeed();
}
if ( NextBotPlayerWalk.GetBool() )
{
inputButtons |= IN_SPEED;
}
if ( NextBotPlayerCrouch.GetBool() )
{
inputButtons |= IN_DUCK;
}
if ( !m_buttonScaleTimer.IsElapsed() )
{
forwardSpeed = mover->GetRunSpeed() * m_forwardScale;
strafeSpeed = mover->GetRunSpeed() * m_rightScale;
}
if ( !NextBotPlayerMove.GetBool() )
{
inputButtons &= ~(IN_FORWARD | IN_BACK | IN_MOVELEFT | IN_MOVERIGHT | IN_JUMP );
forwardSpeed = 0.0f;
strafeSpeed = 0.0f;
verticalSpeed = 0.0f;
}
QAngle angles = this->EyeAngles();
#ifdef TERROR
if ( IsStunned() )
{
inputButtons &= ~(IN_FORWARD | IN_BACK | IN_MOVELEFT | IN_MOVERIGHT | IN_JUMP | IN_DUCK );
}
// "Look" in the direction we're climbing/stumbling etc. We can't do anything anyway, and it
// keeps motion extraction working.
if ( IsRenderYawOverridden() && IsMotionControlledXY( GetMainActivity() ) )
{
angles[YAW] = GetOverriddenRenderYaw();
}
#endif
// construct a "command" to move the player
CUserCmd userCmd;
_NextBot_BuildUserCommand( &userCmd, angles, forwardSpeed, strafeSpeed, verticalSpeed, inputButtons, 0 );
AvoidPlayers( &userCmd );
// allocate a new command and add it to the player's list of command to process
this->ProcessUsercmds( &userCmd, 1, 1, 0, false );
m_inputButtons = 0;
// actually execute player commands and do player physics
PlayerType::PhysicsSimulate();
}
//----------------------------------------------------------------------------------------------------------
template < typename PlayerType >
inline void NextBotPlayer< PlayerType >::OnNavAreaChanged( CNavArea *enteredArea, CNavArea *leftArea )
{
// propagate into NextBot responders
INextBotEventResponder::OnNavAreaChanged( enteredArea, leftArea );
BaseClass::OnNavAreaChanged( enteredArea, leftArea );
}
//----------------------------------------------------------------------------------------------------------
template < typename PlayerType >
inline void NextBotPlayer< PlayerType >::Touch( CBaseEntity *other )
{
if ( ShouldTouch( other ) )
{
// propagate touch into NextBot event responders
trace_t result;
result = this->GetTouchTrace();
OnContact( other, &result );
}
BaseClass::Touch( other );
}
//----------------------------------------------------------------------------------------------------------
template < typename PlayerType >
inline void NextBotPlayer< PlayerType >::Weapon_Equip( CBaseCombatWeapon *weapon )
{
#ifdef TERROR
// TODO: Reimplement GetDroppingPlayer() into GetLastOwner()
OnPickUp( weapon, weapon->GetDroppingPlayer() );
#else
OnPickUp( weapon, NULL );
#endif
BaseClass::Weapon_Equip( weapon );
}
//----------------------------------------------------------------------------------------------------------
template < typename PlayerType >
inline void NextBotPlayer< PlayerType >::Weapon_Drop( CBaseCombatWeapon *weapon, const Vector *target, const Vector *velocity )
{
OnDrop( weapon );
BaseClass::Weapon_Drop( weapon, target, velocity );
}
//--------------------------------------------------------------------------------------------------------
template < typename PlayerType >
inline void NextBotPlayer< PlayerType >::OnMainActivityComplete( Activity newActivity, Activity oldActivity )
{
#ifdef TERROR
BaseClass::OnMainActivityComplete( newActivity, oldActivity );
#endif
OnAnimationActivityComplete( oldActivity );
}
//--------------------------------------------------------------------------------------------------------
template < typename PlayerType >
inline void NextBotPlayer< PlayerType >::OnMainActivityInterrupted( Activity newActivity, Activity oldActivity )
{
#ifdef TERROR
BaseClass::OnMainActivityInterrupted( newActivity, oldActivity );
#endif
OnAnimationActivityInterrupted( oldActivity );
}
//----------------------------------------------------------------------------------------------------------
template < typename PlayerType >
inline void NextBotPlayer< PlayerType >::Update( void )
{
// don't spend CPU updating if this Survivor is dead
if ( ( this->IsAlive() || !IsDormantWhenDead() ) && !NextBotPlayerStop.GetBool() )
{
INextBot::Update();
}
}
//----------------------------------------------------------------------------------------------------------
template < typename PlayerType >
inline bool NextBotPlayer< PlayerType >::IsAbleToAutoCenterOnLadders( void ) const
{
const ILocomotion *locomotion = GetLocomotionInterface();
return locomotion && locomotion->IsAbleToAutoCenterOnLadder();
}
//----------------------------------------------------------------------------------------------------------
template < typename PlayerType >
inline int NextBotPlayer< PlayerType >::OnTakeDamage_Alive( const CTakeDamageInfo &info )
{
if ( info.GetDamageType() & DMG_BURN )
{
if ( !m_burningTimer.HasStarted() || m_burningTimer.IsGreaterThen( 1.0f ) )
{
// emit ignite event periodically as long as we are burning
OnIgnite();
m_burningTimer.Start();
}
}
// propagate event to components
OnInjured( info );
return BaseClass::OnTakeDamage_Alive( info );
}
//----------------------------------------------------------------------------------------------------------
template < typename PlayerType >
inline int NextBotPlayer< PlayerType >::OnTakeDamage_Dying( const CTakeDamageInfo &info )
{
if ( info.GetDamageType() & DMG_BURN )
{
if ( !m_burningTimer.HasStarted() || m_burningTimer.IsGreaterThen( 1.0f ) )
{
// emit ignite event periodically as long as we are burning
OnIgnite();
m_burningTimer.Start();
}
}
// propagate event to components
OnInjured( info );
return BaseClass::OnTakeDamage_Dying( info );
}
//----------------------------------------------------------------------------------------------------------
template < typename PlayerType >
inline void NextBotPlayer< PlayerType >::Event_Killed( const CTakeDamageInfo &info )
{
// propagate event to my components
OnKilled( info );
BaseClass::Event_Killed( info );
}
//----------------------------------------------------------------------------------------------------------
template < typename PlayerType >
inline void NextBotPlayer< PlayerType >::HandleAnimEvent( animevent_t *event )
{
// propagate event to components
OnAnimationEvent( event );
BaseClass::HandleAnimEvent( event );
}
#endif // _NEXT_BOT_PLAYER_H_
@@ -0,0 +1,881 @@
// NextBotPlayerBody.cpp
// Implementation of Body interface for CBasePlayer-derived classes
// Author: Michael Booth, October 2006
//========= Copyright Valve Corporation, All rights reserved. ============//
#include "cbase.h"
#include "NextBot.h"
#include "NextBotPlayerBody.h"
#include "NextBotPlayer.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
ConVar nb_saccade_time( "nb_saccade_time", "0.1", FCVAR_CHEAT );
ConVar nb_saccade_speed( "nb_saccade_speed", "1000", FCVAR_CHEAT );
ConVar nb_head_aim_steady_max_rate( "nb_head_aim_steady_max_rate", "100", FCVAR_CHEAT );
ConVar nb_head_aim_settle_duration( "nb_head_aim_settle_duration", "0.3", FCVAR_CHEAT );
ConVar nb_head_aim_resettle_angle( "nb_head_aim_resettle_angle", "100", FCVAR_CHEAT, "After rotating through this angle, the bot pauses to 'recenter' its virtual mouse on its virtual mousepad" );
ConVar nb_head_aim_resettle_time( "nb_head_aim_resettle_time", "0.3", FCVAR_CHEAT, "How long the bot pauses to 'recenter' its virtual mouse on its virtual mousepad" );
//-----------------------------------------------------------------------------------------------
/**
* A useful reply for IBody::AimHeadTowards. When the
* head is aiming on target, press the fire button.
*/
void PressFireButtonReply::OnSuccess( INextBot *bot )
{
INextBotPlayerInput *playerInput = dynamic_cast< INextBotPlayerInput * >( bot->GetEntity() );
if ( playerInput )
{
playerInput->PressFireButton();
}
}
//-----------------------------------------------------------------------------------------------
/**
* A useful reply for IBody::AimHeadTowards. When the
* head is aiming on target, press the alternate fire button.
*/
void PressAltFireButtonReply::OnSuccess( INextBot *bot )
{
INextBotPlayerInput *playerInput = dynamic_cast< INextBotPlayerInput * >( bot->GetEntity() );
if ( playerInput )
{
playerInput->PressMeleeButton();
}
}
//-----------------------------------------------------------------------------------------------
/**
* A useful reply for IBody::AimHeadTowards. When the
* head is aiming on target, press the jump button.
*/
void PressJumpButtonReply::OnSuccess( INextBot *bot )
{
INextBotPlayerInput *playerInput = dynamic_cast< INextBotPlayerInput * >( bot->GetEntity() );
if ( playerInput )
{
playerInput->PressJumpButton();
}
}
//-----------------------------------------------------------------------------------------------
//-----------------------------------------------------------------------------------------------
PlayerBody::PlayerBody( INextBot *bot ) : IBody( bot )
{
m_player = static_cast< CBasePlayer * >( bot->GetEntity() );
}
//-----------------------------------------------------------------------------------------------
PlayerBody::~PlayerBody()
{
}
//-----------------------------------------------------------------------------------------------
/**
* reset to initial state
*/
void PlayerBody::Reset( void )
{
m_posture = STAND;
m_lookAtPos = vec3_origin;
m_lookAtSubject = NULL;
m_lookAtReplyWhenAimed = NULL;
m_lookAtVelocity = vec3_origin;
m_lookAtExpireTimer.Invalidate();
m_lookAtPriority = BORING;
m_lookAtExpireTimer.Invalidate();
m_lookAtDurationTimer.Invalidate();
m_isSightedIn = false;
m_hasBeenSightedIn = false;
m_headSteadyTimer.Invalidate();
m_priorAngles = vec3_angle;
m_anchorRepositionTimer.Invalidate();
m_anchorForward = vec3_origin;
}
ConVar bot_mimic( "bot_mimic", "0", 0, "Bot uses usercmd of player by index." );
//-----------------------------------------------------------------------------------------------
/**
* Update internal state.
* Do this every tick to keep head aims smooth and accurate
*/
void PlayerBody::Upkeep( void )
{
// If mimicking the player, don't modify the view angles.
static ConVarRef bot_mimic( "bot_mimic" );
if ( bot_mimic.IsValid() && bot_mimic.GetBool() )
return;
const float deltaT = gpGlobals->frametime;
if ( deltaT < 0.00001f )
{
return;
}
CBasePlayer *player = ( CBasePlayer * )GetBot()->GetEntity();
// get current view angles
QAngle currentAngles = player->EyeAngles() + player->GetPunchAngle();
// track when our head is "steady"
bool isSteady = true;
float actualPitchRate = AngleDiff( currentAngles.x, m_priorAngles.x );
if ( abs( actualPitchRate ) > nb_head_aim_steady_max_rate.GetFloat() * deltaT )
{
isSteady = false;
}
else
{
float actualYawRate = AngleDiff( currentAngles.y, m_priorAngles.y );
if ( abs( actualYawRate ) > nb_head_aim_steady_max_rate.GetFloat() * deltaT )
{
isSteady = false;
}
}
if ( isSteady )
{
if ( !m_headSteadyTimer.HasStarted() )
{
m_headSteadyTimer.Start();
}
}
else
{
m_headSteadyTimer.Invalidate();
}
if ( GetBot()->IsDebugging( NEXTBOT_LOOK_AT ) )
{
if ( IsHeadSteady() )
{
const float maxTime = 3.0f;
float t = GetHeadSteadyDuration() / maxTime;
t = clamp( t, 0.f, 1.0f );
NDebugOverlay::Circle( player->EyePosition(), t * 10.0f, 0, 255, 0, 255, true, 2.0f * deltaT );
}
}
m_priorAngles = currentAngles;
// if our current look-at has expired, don't change our aim further
if ( m_hasBeenSightedIn && m_lookAtExpireTimer.IsElapsed() )
{
return;
}
// simulate limited range of mouse movements
// compute the angle change from "center"
const Vector &forward = GetViewVector();
float deltaAngle = RAD2DEG( acos( DotProduct( forward, m_anchorForward ) ) );
if ( deltaAngle > nb_head_aim_resettle_angle.GetFloat() )
{
// time to recenter our 'virtual mouse'
m_anchorRepositionTimer.Start( RandomFloat( 0.9f, 1.1f ) * nb_head_aim_resettle_time.GetFloat() );
m_anchorForward = forward;
return;
}
// if we're currently recentering our "virtual mouse", wait
if ( m_anchorRepositionTimer.HasStarted() && !m_anchorRepositionTimer.IsElapsed() )
{
return;
}
m_anchorRepositionTimer.Invalidate();
// if we have a subject, update lookat point
CBaseEntity *subject = m_lookAtSubject;
if ( subject )
{
if ( m_lookAtTrackingTimer.IsElapsed() )
{
// update subject tracking by periodically estimating linear aim velocity, allowing for "slop" between updates
Vector desiredLookAtPos;
if ( subject->MyCombatCharacterPointer() )
{
desiredLookAtPos = GetBot()->GetIntentionInterface()->SelectTargetPoint( GetBot(), subject->MyCombatCharacterPointer() );
}
else
{
desiredLookAtPos = subject->WorldSpaceCenter();
}
desiredLookAtPos += GetHeadAimSubjectLeadTime() * subject->GetAbsVelocity();
Vector errorVector = desiredLookAtPos - m_lookAtPos;
float error = errorVector.NormalizeInPlace();
float trackingInterval = GetHeadAimTrackingInterval();
if ( trackingInterval < deltaT )
{
trackingInterval = deltaT;
}
float errorVel = error / trackingInterval;
m_lookAtVelocity = ( errorVel * errorVector ) + subject->GetAbsVelocity();
m_lookAtTrackingTimer.Start( RandomFloat( 0.8f, 1.2f ) * trackingInterval );
}
m_lookAtPos += deltaT * m_lookAtVelocity;
}
// aim view towards last look at point
Vector to = m_lookAtPos - GetEyePosition();
to.NormalizeInPlace();
QAngle desiredAngles;
VectorAngles( to, desiredAngles );
QAngle angles;
if ( GetBot()->IsDebugging( NEXTBOT_LOOK_AT ) )
{
NDebugOverlay::Line( GetEyePosition(), GetEyePosition() + 100.0f * forward, 255, 255, 0, false, 2.0f * deltaT );
float thickness = isSteady ? 2.0f : 3.0f;
int r = m_isSightedIn ? 255 : 0;
int g = subject ? 255 : 0;
NDebugOverlay::HorzArrow( GetEyePosition(), m_lookAtPos, thickness, r, g, 255, 255, false, 2.0f * deltaT );
}
const float onTargetTolerance = 0.98f;
float dot = DotProduct( forward, to );
if ( dot > onTargetTolerance )
{
// on target
m_isSightedIn = true;
if ( !m_hasBeenSightedIn )
{
m_hasBeenSightedIn = true;
if ( GetBot()->IsDebugging( NEXTBOT_LOOK_AT ) )
{
ConColorMsg( Color( 255, 100, 0, 255 ), "%3.2f: %s Look At SIGHTED IN\n",
gpGlobals->curtime,
m_player->GetPlayerName() );
}
}
if ( m_lookAtReplyWhenAimed )
{
m_lookAtReplyWhenAimed->OnSuccess( GetBot() );
m_lookAtReplyWhenAimed = NULL;
}
}
else
{
// off target
m_isSightedIn = false;
}
// rotate view at a rate proportional to how far we have to turn
// max rate if we need to turn around
// want first derivative continuity of rate as our aim hits to avoid pop
float approachRate = GetMaxHeadAngularVelocity();
const float easeOut = 0.7f;
if ( dot > easeOut )
{
float t = RemapVal( dot, easeOut, 1.0f, 1.0f, 0.02f );
const float halfPI = 1.57f;
approachRate *= sin( halfPI * t );
}
const float easeInTime = 0.25f;
if ( m_lookAtDurationTimer.GetElapsedTime() < easeInTime )
{
approachRate *= m_lookAtDurationTimer.GetElapsedTime() / easeInTime;
}
angles.y = ApproachAngle( desiredAngles.y, currentAngles.y, approachRate * deltaT );
angles.x = ApproachAngle( desiredAngles.x, currentAngles.x, 0.5f * approachRate * deltaT );
angles.z = 0.0f;
// back out "punch angle"
angles -= player->GetPunchAngle();
angles.x = AngleNormalize( angles.x );
angles.y = AngleNormalize( angles.y );
player->SnapEyeAngles( angles );
}
//-----------------------------------------------------------------------------------------------
bool PlayerBody::SetPosition( const Vector &pos )
{
m_player->SetAbsOrigin( pos );
return true;
}
//-----------------------------------------------------------------------------------------------
/**
* Return the eye position of the bot in world coordinates
*/
const Vector &PlayerBody::GetEyePosition( void ) const
{
m_eyePos = m_player->EyePosition();
return m_eyePos;
}
CBaseEntity *PlayerBody::GetEntity( void )
{
return m_player;
}
//-----------------------------------------------------------------------------------------------
/**
* Return the view unit direction vector in world coordinates
*/
const Vector &PlayerBody::GetViewVector( void ) const
{
m_player->EyeVectors( &m_viewVector );
return m_viewVector;
}
//-----------------------------------------------------------------------------------------------
/**
* Aim the bot's head towards the given goal
*/
void PlayerBody::AimHeadTowards( const Vector &lookAtPos, LookAtPriorityType priority, float duration, INextBotReply *replyWhenAimed, const char *reason )
{
if ( duration <= 0.0f )
{
duration = 0.1f;
}
// don't spaz our aim around
if ( m_lookAtPriority == priority )
{
if ( !IsHeadSteady() || GetHeadSteadyDuration() < nb_head_aim_settle_duration.GetFloat() )
{
// we're still finishing a look-at at the same priority
if ( replyWhenAimed )
{
replyWhenAimed->OnFail( GetBot(), INextBotReply::DENIED );
}
if ( GetBot()->IsDebugging( NEXTBOT_LOOK_AT ) )
{
ConColorMsg( Color( 255, 0, 0, 255 ), "%3.2f: %s Look At '%s' rejected - previous aim not %s\n",
gpGlobals->curtime,
m_player->GetPlayerName(),
reason,
IsHeadSteady() ? "settled long enough" : "head-steady" );
}
return;
}
}
// don't short-circuit if "sighted in" to avoid rapid view jitter
if ( m_lookAtPriority > priority && !m_lookAtExpireTimer.IsElapsed() )
{
// higher priority lookat still ongoing
if ( replyWhenAimed )
{
replyWhenAimed->OnFail( GetBot(), INextBotReply::DENIED );
}
if ( GetBot()->IsDebugging( NEXTBOT_LOOK_AT ) )
{
ConColorMsg( Color( 255, 0, 0, 255 ), "%3.2f: %s Look At '%s' rejected - higher priority aim in progress\n",
gpGlobals->curtime,
m_player->GetPlayerName(),
reason );
}
return;
}
if ( m_lookAtReplyWhenAimed )
{
// in-process aim was interrupted
m_lookAtReplyWhenAimed->OnFail( GetBot(), INextBotReply::INTERRUPTED );
}
m_lookAtReplyWhenAimed = replyWhenAimed;
m_lookAtExpireTimer.Start( duration );
// if given the same point, just update priority
const float epsilon = 1.0f;
if ( ( m_lookAtPos - lookAtPos ).IsLengthLessThan( epsilon ) )
{
m_lookAtPriority = priority;
return;
}
// new look-at point
m_lookAtPos = lookAtPos;
m_lookAtSubject = NULL;
m_lookAtPriority = priority;
m_lookAtDurationTimer.Start();
// do NOT clear this here, or continuous calls to AimHeadTowards will keep IsHeadAimingOnTarget returning false all of the time
// m_isSightedIn = false;
m_hasBeenSightedIn = false;
if ( GetBot()->IsDebugging( NEXTBOT_LOOK_AT ) )
{
NDebugOverlay::Cross3D( lookAtPos, 2.0f, 255, 255, 100, true, 2.0f * duration );
const char *priName = "";
switch( priority )
{
case BORING: priName = "BORING"; break;
case INTERESTING: priName = "INTERESTING"; break;
case IMPORTANT: priName = "IMPORTANT"; break;
case CRITICAL: priName = "CRITICAL"; break;
}
ConColorMsg( Color( 255, 100, 0, 255 ), "%3.2f: %s Look At ( %g, %g, %g ) for %3.2f s, Pri = %s, Reason = %s\n",
gpGlobals->curtime,
m_player->GetPlayerName(),
lookAtPos.x, lookAtPos.y, lookAtPos.z,
duration,
priName,
( reason ) ? reason : "" );
}
}
//-----------------------------------------------------------------------------------------------
/**
* Aim the bot's head towards the given goal
*/
void PlayerBody::AimHeadTowards( CBaseEntity *subject, LookAtPriorityType priority, float duration, INextBotReply *replyWhenAimed, const char *reason )
{
if ( duration <= 0.0f )
{
duration = 0.1f;
}
if ( subject == NULL )
{
return;
}
// don't spaz our aim around
if ( m_lookAtPriority == priority )
{
if ( !IsHeadSteady() || GetHeadSteadyDuration() < nb_head_aim_settle_duration.GetFloat() )
{
// we're still finishing a look-at at the same priority
if ( replyWhenAimed )
{
replyWhenAimed->OnFail( GetBot(), INextBotReply::DENIED );
}
if ( GetBot()->IsDebugging( NEXTBOT_LOOK_AT ) )
{
ConColorMsg( Color( 255, 0, 0, 255 ), "%3.2f: %s Look At '%s' rejected - previous aim not %s\n",
gpGlobals->curtime,
m_player->GetPlayerName(),
reason,
IsHeadSteady() ? "head-steady" : "settled long enough" );
}
return;
}
}
// don't short-circuit if "sighted in" to avoid rapid view jitter
if ( m_lookAtPriority > priority && !m_lookAtExpireTimer.IsElapsed() )
{
// higher priority lookat still ongoing
if ( replyWhenAimed )
{
replyWhenAimed->OnFail( GetBot(), INextBotReply::DENIED );
}
if ( GetBot()->IsDebugging( NEXTBOT_LOOK_AT ) )
{
ConColorMsg( Color( 255, 0, 0, 255 ), "%3.2f: %s Look At '%s' rejected - higher priority aim in progress\n",
gpGlobals->curtime,
m_player->GetPlayerName(),
reason );
}
return;
}
if ( m_lookAtReplyWhenAimed )
{
// in-process aim was interrupted
m_lookAtReplyWhenAimed->OnFail( GetBot(), INextBotReply::INTERRUPTED );
}
m_lookAtReplyWhenAimed = replyWhenAimed;
m_lookAtExpireTimer.Start( duration );
// if given the same subject, just update priority
if ( subject == m_lookAtSubject )
{
m_lookAtPriority = priority;
return;
}
// new subject
m_lookAtSubject = subject;
#ifdef REFACTOR_FOR_CLIENT_SIDE_EYE_TRACKING
CBasePlayer *pMyPlayer = static_cast< CBasePlayer * >( GetEntity() );
if ( subject->IsPlayer() )
{
// looking at a player, look at their eye position
TerrorPlayer *pMyTarget = ToTerrorPlayer( subject );
m_lookAtPos = subject->EyePosition();
if(pMyPlayer)
{
pMyPlayer->SetLookatPlayer( pMyTarget );
}
}
else
{
// not looking at a player
m_lookAtPos = subject->WorldSpaceCenter();
if(pMyPlayer)
{
pMyPlayer->SetLookatPlayer( NULL );
}
}
#endif
m_lookAtPriority = priority;
m_lookAtDurationTimer.Start();
// do NOT clear this here, or continuous calls to AimHeadTowards will keep IsHeadAimingOnTarget returning false all of the time
// m_isSightedIn = false;
m_hasBeenSightedIn = false;
if ( GetBot()->IsDebugging( NEXTBOT_LOOK_AT ) )
{
NDebugOverlay::Cross3D( m_lookAtPos, 2.0f, 100, 100, 100, true, duration );
const char *priName = "";
switch( priority )
{
case BORING: priName = "BORING"; break;
case INTERESTING: priName = "INTERESTING"; break;
case IMPORTANT: priName = "IMPORTANT"; break;
case CRITICAL: priName = "CRITICAL"; break;
}
ConColorMsg( Color( 255, 100, 0, 255 ), "%3.2f: %s Look At subject %s for %3.2f s, Pri = %s, Reason = %s\n",
gpGlobals->curtime,
m_player->GetPlayerName(),
subject->GetClassname(),
duration,
priName,
( reason ) ? reason : "" );
}
}
//-----------------------------------------------------------------------------------------------
/**
* Return true if head is not rapidly turning to look somewhere else
*/
bool PlayerBody::IsHeadSteady( void ) const
{
return m_headSteadyTimer.HasStarted();
}
//-----------------------------------------------------------------------------------------------
/**
* Return the duration that the bot's head has been on-target
*/
float PlayerBody::GetHeadSteadyDuration( void ) const
{
// return ( IsHeadAimingOnTarget() ) ? m_headSteadyTimer.GetElapsedTime() : 0.0f;
return m_headSteadyTimer.HasStarted() ? m_headSteadyTimer.GetElapsedTime() : 0.0f;
}
//-----------------------------------------------------------------------------------------------
// Clear out currently pending replyWhenAimed callback
void PlayerBody::ClearPendingAimReply( void )
{
m_lookAtReplyWhenAimed = NULL;
}
//-----------------------------------------------------------------------------------------------
float PlayerBody::GetMaxHeadAngularVelocity( void ) const
{
return nb_saccade_speed.GetFloat();
}
//-----------------------------------------------------------------------------------------------
bool PlayerBody::StartActivity( Activity act, unsigned int flags )
{
// player animation state is controlled on the client
return false;
}
//-----------------------------------------------------------------------------------------------
/**
* Return currently animating activity
*/
Activity PlayerBody::GetActivity( void ) const
{
return ACT_INVALID;
}
//-----------------------------------------------------------------------------------------------
/**
* Return true if currently animating activity matches the given one
*/
bool PlayerBody::IsActivity( Activity act ) const
{
return false;
}
//-----------------------------------------------------------------------------------------------
/**
* Return true if currently animating activity has any of the given flags
*/
bool PlayerBody::HasActivityType( unsigned int flags ) const
{
return false;
}
//-----------------------------------------------------------------------------------------------
/**
* Request a posture change
*/
void PlayerBody::SetDesiredPosture( PostureType posture )
{
m_posture = posture;
}
//-----------------------------------------------------------------------------------------------
/**
* Get posture body is trying to assume
*/
IBody::PostureType PlayerBody::GetDesiredPosture( void ) const
{
return m_posture;
}
//-----------------------------------------------------------------------------------------------
/**
* Return true if body is trying to assume this posture
*/
bool PlayerBody::IsDesiredPosture( PostureType posture ) const
{
return ( posture == m_posture );
}
//-----------------------------------------------------------------------------------------------
/**
* Return true if body's actual posture matches its desired posture
*/
bool PlayerBody::IsInDesiredPosture( void ) const
{
return true;
}
//-----------------------------------------------------------------------------------------------
/**
* Return body's current actual posture
*/
IBody::PostureType PlayerBody::GetActualPosture( void ) const
{
return m_posture;
}
//-----------------------------------------------------------------------------------------------
/**
* Return true if body is actually in the given posture
*/
bool PlayerBody::IsActualPosture( PostureType posture ) const
{
return ( posture == m_posture );
}
//-----------------------------------------------------------------------------------------------
/**
* Return true if body's current posture allows it to move around the world
*/
bool PlayerBody::IsPostureMobile( void ) const
{
return true;
}
//-----------------------------------------------------------------------------------------------
/**
* Return true if body's posture is in the process of changing to new posture
*/
bool PlayerBody::IsPostureChanging( void ) const
{
return false;
}
//-----------------------------------------------------------------------------------------------
/**
* Arousal level change
*/
void PlayerBody::SetArousal( ArousalType arousal )
{
m_arousal = arousal;
}
//-----------------------------------------------------------------------------------------------
/**
* Get arousal level
*/
IBody::ArousalType PlayerBody::GetArousal( void ) const
{
return m_arousal;
}
//-----------------------------------------------------------------------------------------------
/**
* Return true if body is at this arousal level
*/
bool PlayerBody::IsArousal( ArousalType arousal ) const
{
return ( arousal == m_arousal );
}
//-----------------------------------------------------------------------------------------------
/**
* Width of bot's collision hull in XY plane
*/
float PlayerBody::GetHullWidth( void ) const
{
return VEC_HULL_MAX_SCALED( m_player ).x - VEC_HULL_MIN_SCALED( m_player ).x;
}
//-----------------------------------------------------------------------------------------------
/**
* Height of bot's current collision hull based on posture
*/
float PlayerBody::GetHullHeight( void ) const
{
if ( m_posture == CROUCH )
{
return GetCrouchHullHeight();
}
return GetStandHullHeight();
}
//-----------------------------------------------------------------------------------------------
/**
* Height of bot's collision hull when standing
*/
float PlayerBody::GetStandHullHeight( void ) const
{
return VEC_HULL_MAX_SCALED( m_player ).z - VEC_HULL_MIN_SCALED( m_player ).z;
}
//-----------------------------------------------------------------------------------------------
/**
* Height of bot's collision hull when crouched
*/
float PlayerBody::GetCrouchHullHeight( void ) const
{
return VEC_DUCK_HULL_MAX_SCALED( m_player ).z - VEC_DUCK_HULL_MIN_SCALED( m_player ).z;
}
//-----------------------------------------------------------------------------------------------
/**
* Return current collision hull minimums based on actual body posture
*/
const Vector &PlayerBody::GetHullMins( void ) const
{
if ( m_posture == CROUCH )
{
m_hullMins = VEC_DUCK_HULL_MIN_SCALED( m_player );
}
else
{
m_hullMins = VEC_HULL_MIN_SCALED( m_player );
}
return m_hullMins;
}
//-----------------------------------------------------------------------------------------------
/**
* Return current collision hull maximums based on actual body posture
*/
const Vector &PlayerBody::GetHullMaxs( void ) const
{
if ( m_posture == CROUCH )
{
m_hullMaxs = VEC_DUCK_HULL_MAX_SCALED( m_player );
}
else
{
m_hullMaxs = VEC_HULL_MAX_SCALED( m_player );
}
return m_hullMaxs;
}
//-----------------------------------------------------------------------------------------------
/**
* Return the bot's collision mask (hack until we get a general hull trace abstraction here or in the locomotion interface)
*/
unsigned int PlayerBody::GetSolidMask( void ) const
{
return ( m_player ) ? m_player->PlayerSolidMask() : MASK_PLAYERSOLID;
}
@@ -0,0 +1,153 @@
// NextBotPlayerBody.h
// Control and information about the bot's body state (posture, animation state, etc)
// Author: Michael Booth, October 2006
//========= Copyright Valve Corporation, All rights reserved. ============//
#ifndef _NEXT_BOT_PLAYER_BODY_H_
#define _NEXT_BOT_PLAYER_BODY_H_
#include "NextBotBodyInterface.h"
//----------------------------------------------------------------------------------------------------------------
/**
* A useful reply for IBody::AimHeadTowards. When the
* head is aiming on target, press the fire button.
*/
class PressFireButtonReply : public INextBotReply
{
public:
virtual void OnSuccess( INextBot *bot ); // invoked when process completed successfully
};
//----------------------------------------------------------------------------------------------------------------
/**
* A useful reply for IBody::AimHeadTowards. When the
* head is aiming on target, press the alt-fire button.
*/
class PressAltFireButtonReply : public INextBotReply
{
public:
virtual void OnSuccess( INextBot *bot ); // invoked when process completed successfully
};
//----------------------------------------------------------------------------------------------------------------
/**
* A useful reply for IBody::AimHeadTowards. When the
* head is aiming on target, press the jump button.
*/
class PressJumpButtonReply : public INextBotReply
{
public:
virtual void OnSuccess( INextBot *bot ); // invoked when process completed successfully
};
//----------------------------------------------------------------------------------------------------------------
/**
* The interface for control and information about the bot's body state (posture, animation state, etc)
*/
class PlayerBody : public IBody
{
public:
PlayerBody( INextBot *bot );
virtual ~PlayerBody();
virtual void Reset( void ); // reset to initial state
virtual void Upkeep( void ); // lightweight update guaranteed to occur every server tick
virtual bool SetPosition( const Vector &pos );
virtual const Vector &GetEyePosition( void ) const; // return the eye position of the bot in world coordinates
virtual const Vector &GetViewVector( void ) const; // return the view unit direction vector in world coordinates
virtual void AimHeadTowards( const Vector &lookAtPos,
LookAtPriorityType priority = BORING,
float duration = 0.0f,
INextBotReply *replyWhenAimed = NULL,
const char *reason = NULL ); // aim the bot's head towards the given goal
virtual void AimHeadTowards( CBaseEntity *subject,
LookAtPriorityType priority = BORING,
float duration = 0.0f,
INextBotReply *replyWhenAimed = NULL,
const char *reason = NULL ); // continually aim the bot's head towards the given subject
virtual bool IsHeadAimingOnTarget( void ) const; // return true if the bot's head has achieved its most recent lookat target
virtual bool IsHeadSteady( void ) const; // return true if head is not rapidly turning to look somewhere else
virtual float GetHeadSteadyDuration( void ) const; // return the duration that the bot's head has been on-target
virtual void ClearPendingAimReply( void ); // clear out currently pending replyWhenAimed callback
virtual float GetMaxHeadAngularVelocity( void ) const; // return max turn rate of head in degrees/second
virtual bool StartActivity( Activity act, unsigned int flags );
virtual Activity GetActivity( void ) const; // return currently animating activity
virtual bool IsActivity( Activity act ) const; // return true if currently animating activity matches the given one
virtual bool HasActivityType( unsigned int flags ) const; // return true if currently animating activity has any of the given flags
virtual void SetDesiredPosture( PostureType posture ); // request a posture change
virtual PostureType GetDesiredPosture( void ) const; // get posture body is trying to assume
virtual bool IsDesiredPosture( PostureType posture ) const; // return true if body is trying to assume this posture
virtual bool IsInDesiredPosture( void ) const; // return true if body's actual posture matches its desired posture
virtual PostureType GetActualPosture( void ) const; // return body's current actual posture
virtual bool IsActualPosture( PostureType posture ) const; // return true if body is actually in the given posture
virtual bool IsPostureMobile( void ) const; // return true if body's current posture allows it to move around the world
virtual bool IsPostureChanging( void ) const; // return true if body's posture is in the process of changing to new posture
virtual void SetArousal( ArousalType arousal ); // arousal level change
virtual ArousalType GetArousal( void ) const; // get arousal level
virtual bool IsArousal( ArousalType arousal ) const; // return true if body is at this arousal level
virtual float GetHullWidth( void ) const; // width of bot's collision hull in XY plane
virtual float GetHullHeight( void ) const; // height of bot's current collision hull based on posture
virtual float GetStandHullHeight( void ) const; // height of bot's collision hull when standing
virtual float GetCrouchHullHeight( void ) const; // height of bot's collision hull when crouched
virtual const Vector &GetHullMins( void ) const; // return current collision hull minimums based on actual body posture
virtual const Vector &GetHullMaxs( void ) const; // return current collision hull maximums based on actual body posture
virtual unsigned int GetSolidMask( void ) const; // return the bot's collision mask (hack until we get a general hull trace abstraction here or in the locomotion interface)
virtual CBaseEntity *GetEntity( void ); // get the entity
private:
CBasePlayer *m_player;
PostureType m_posture;
ArousalType m_arousal;
mutable Vector m_eyePos; // for use with GetEyePosition() ONLY
mutable Vector m_viewVector; // for use with GetViewVector() ONLY
mutable Vector m_hullMins; // for use with GetHullMins() ONLY
mutable Vector m_hullMaxs; // for use with GetHullMaxs() ONLY
Vector m_lookAtPos; // if m_lookAtSubject is non-NULL, it continually overwrites this position with its own
EHANDLE m_lookAtSubject;
Vector m_lookAtVelocity; // world velocity of lookat point, for tracking moving subjects
CountdownTimer m_lookAtTrackingTimer;
LookAtPriorityType m_lookAtPriority;
CountdownTimer m_lookAtExpireTimer; // how long until this lookat expired
IntervalTimer m_lookAtDurationTimer; // how long have we been looking at this target
INextBotReply *m_lookAtReplyWhenAimed;
bool m_isSightedIn; // true if we are looking at our last lookat target
bool m_hasBeenSightedIn; // true if we have hit the current lookat target
IntervalTimer m_headSteadyTimer;
QAngle m_priorAngles; // last update's head angles
QAngle m_desiredAngles;
CountdownTimer m_anchorRepositionTimer; // the time is takes us to recenter our virtual mouse
Vector m_anchorForward;
};
inline bool PlayerBody::IsHeadAimingOnTarget( void ) const
{
// TODO: Calling this immediately after AimHeadTowards will always return false until next Upkeep() (MSB)
return m_isSightedIn;
}
#endif // _NEXT_BOT_PLAYER_BODY_H_
@@ -0,0 +1,826 @@
// NextBotPlayerLocomotion.cpp
// Implementation of Locomotion interface for CBasePlayer-derived classes
// Author: Michael Booth, November 2005
//========= Copyright Valve Corporation, All rights reserved. ============//
#include "cbase.h"
#include "nav_mesh.h"
#include "in_buttons.h"
#include "NextBot.h"
#include "NextBotUtil.h"
#include "NextBotPlayer.h"
#include "NextBotPlayerLocomotion.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
ConVar NextBotPlayerMoveDirect( "nb_player_move_direct", "0" );
//-----------------------------------------------------------------------------------------------------
PlayerLocomotion::PlayerLocomotion( INextBot *bot ) : ILocomotion( bot )
{
m_player = NULL;
Reset();
}
//-----------------------------------------------------------------------------------------------------
/**
* Reset locomotor to initial state
*/
void PlayerLocomotion::Reset( void )
{
m_player = static_cast< CBasePlayer * >( GetBot()->GetEntity() );
m_isJumping = false;
m_isClimbingUpToLedge = false;
m_isJumpingAcrossGap = false;
m_hasLeftTheGround = false;
m_desiredSpeed = 0.0f;
m_ladderState = NO_LADDER;
m_ladderInfo = NULL;
m_ladderDismountGoal = NULL;
m_ladderTimer.Invalidate();
m_minSpeedLimit = 0.0f;
m_maxSpeedLimit = 9999999.9f;
BaseClass::Reset();
}
//-----------------------------------------------------------------------------------------------------
bool PlayerLocomotion::TraverseLadder( void )
{
switch( m_ladderState )
{
case APPROACHING_ASCENDING_LADDER:
m_ladderState = ApproachAscendingLadder();
return true;
case APPROACHING_DESCENDING_LADDER:
m_ladderState = ApproachDescendingLadder();
return true;
case ASCENDING_LADDER:
m_ladderState = AscendLadder();
return true;
case DESCENDING_LADDER:
m_ladderState = DescendLadder();
return true;
case DISMOUNTING_LADDER_TOP:
m_ladderState = DismountLadderTop();
return true;
case DISMOUNTING_LADDER_BOTTOM:
m_ladderState = DismountLadderBottom();
return true;
case NO_LADDER:
default:
m_ladderInfo = NULL;
if ( GetBot()->GetEntity()->GetMoveType() == MOVETYPE_LADDER )
{
// on ladder and don't want to be
GetBot()->GetEntity()->SetMoveType( MOVETYPE_WALK );
}
return false;
}
return true;
}
//-----------------------------------------------------------------------------------------------------
/**
* We're close, but not yet on, this ladder - approach it
*/
PlayerLocomotion::LadderState PlayerLocomotion::ApproachAscendingLadder( void )
{
if ( m_ladderInfo == NULL )
{
return NO_LADDER;
}
// sanity check - are we already at the end of this ladder?
if ( GetFeet().z >= m_ladderInfo->m_top.z - GetStepHeight() )
{
m_ladderTimer.Start( 2.0f );
return DISMOUNTING_LADDER_TOP;
}
// sanity check - are we too far below this ladder to reach it?
if ( GetFeet().z <= m_ladderInfo->m_bottom.z - GetMaxJumpHeight() )
{
return NO_LADDER;
}
FaceTowards( m_ladderInfo->m_bottom );
// it is important to approach precisely, so use a very large weight to wash out all other Approaches
Approach( m_ladderInfo->m_bottom, 9999999.9f );
if ( GetBot()->GetEntity()->GetMoveType() == MOVETYPE_LADDER )
{
// we're on the ladder
return ASCENDING_LADDER;
}
if ( GetBot()->IsDebugging( NEXTBOT_LOCOMOTION ) )
{
NDebugOverlay::EntityText( GetBot()->GetEntity()->entindex(), 0, "Approach ascending ladder", 0.1f, 255, 255, 255, 255 );
}
return APPROACHING_ASCENDING_LADDER;
}
//-----------------------------------------------------------------------------------------------------
PlayerLocomotion::LadderState PlayerLocomotion::ApproachDescendingLadder( void )
{
if ( m_ladderInfo == NULL )
{
return NO_LADDER;
}
// sanity check - are we already at the end of this ladder?
if ( GetFeet().z <= m_ladderInfo->m_bottom.z + GetMaxJumpHeight() )
{
m_ladderTimer.Start( 2.0f );
return DISMOUNTING_LADDER_BOTTOM;
}
Vector mountPoint = m_ladderInfo->m_top + 0.25f * GetBot()->GetBodyInterface()->GetHullWidth() * m_ladderInfo->GetNormal();
Vector to = mountPoint - GetFeet();
to.z = 0.0f;
float mountRange = to.NormalizeInPlace();
Vector moveGoal;
const float veryClose = 10.0f;
if ( mountRange < veryClose )
{
// we're right at the ladder - just keep moving forward until we grab it
const Vector &forward = GetMotionVector();
moveGoal = GetFeet() + 100.0f * forward;
}
else
{
if ( DotProduct( to, m_ladderInfo->GetNormal() ) < 0.0f )
{
// approaching front of downward ladder
// ##
// ->+ ##
// | ##
// | ##
// | ##
// <-+ ##
// ######
//
moveGoal = m_ladderInfo->m_top - 100.0f * m_ladderInfo->GetNormal();
}
else
{
// approaching back of downward ladder
//
// ->+
// ##|
// ##|
// ##+-->
// ######
//
moveGoal = m_ladderInfo->m_top + 100.0f * m_ladderInfo->GetNormal();
}
}
FaceTowards( moveGoal );
// it is important to approach precisely, so use a very large weight to wash out all other Approaches
Approach( moveGoal, 9999999.9f );
if ( GetBot()->GetEntity()->GetMoveType() == MOVETYPE_LADDER )
{
// we're on the ladder
return DESCENDING_LADDER;
}
if ( GetBot()->IsDebugging( NEXTBOT_LOCOMOTION ) )
{
NDebugOverlay::EntityText( GetBot()->GetEntity()->entindex(), 0, "Approach descending ladder", 0.1f, 255, 255, 255, 255 );
}
return APPROACHING_DESCENDING_LADDER;
}
//-----------------------------------------------------------------------------------------------------
PlayerLocomotion::LadderState PlayerLocomotion::AscendLadder( void )
{
if ( m_ladderInfo == NULL )
{
return NO_LADDER;
}
if ( GetBot()->GetEntity()->GetMoveType() != MOVETYPE_LADDER )
{
// slipped off ladder
m_ladderInfo = NULL;
return NO_LADDER;
}
if ( GetFeet().z >= m_ladderInfo->m_top.z )
{
// reached top of ladder
m_ladderTimer.Start( 2.0f );
return DISMOUNTING_LADDER_TOP;
}
// climb up this ladder - look up
Vector goal = GetFeet() + 100.0f * ( -m_ladderInfo->GetNormal() + Vector( 0, 0, 2 ) );
GetBot()->GetBodyInterface()->AimHeadTowards( goal, IBody::MANDATORY, 0.1f, NULL, "Ladder" );
// it is important to approach precisely, so use a very large weight to wash out all other Approaches
Approach( goal, 9999999.9f );
if ( GetBot()->IsDebugging( NEXTBOT_LOCOMOTION ) )
{
NDebugOverlay::EntityText( GetBot()->GetEntity()->entindex(), 0, "Ascend", 0.1f, 255, 255, 255, 255 );
}
return ASCENDING_LADDER;
}
//-----------------------------------------------------------------------------------------------------
PlayerLocomotion::LadderState PlayerLocomotion::DescendLadder( void )
{
if ( m_ladderInfo == NULL )
{
return NO_LADDER;
}
if ( GetBot()->GetEntity()->GetMoveType() != MOVETYPE_LADDER )
{
// slipped off ladder
m_ladderInfo = NULL;
return NO_LADDER;
}
if ( GetFeet().z <= m_ladderInfo->m_bottom.z + GetBot()->GetLocomotionInterface()->GetStepHeight() )
{
// reached bottom of ladder
m_ladderTimer.Start( 2.0f );
return DISMOUNTING_LADDER_BOTTOM;
}
// climb down this ladder - look down
Vector goal = GetFeet() + 100.0f * ( m_ladderInfo->GetNormal() + Vector( 0, 0, -2 ) );
GetBot()->GetBodyInterface()->AimHeadTowards( goal, IBody::MANDATORY, 0.1f, NULL, "Ladder" );
// it is important to approach precisely, so use a very large weight to wash out all other Approaches
Approach( goal, 9999999.9f );
if ( GetBot()->IsDebugging( NEXTBOT_LOCOMOTION ) )
{
NDebugOverlay::EntityText( GetBot()->GetEntity()->entindex(), 0, "Descend", 0.1f, 255, 255, 255, 255 );
}
return DESCENDING_LADDER;
}
//-----------------------------------------------------------------------------------------------------
PlayerLocomotion::LadderState PlayerLocomotion::DismountLadderTop( void )
{
if ( m_ladderInfo == NULL || m_ladderTimer.IsElapsed() )
{
m_ladderInfo = NULL;
return NO_LADDER;
}
IBody *body = GetBot()->GetBodyInterface();
Vector toGoal = m_ladderDismountGoal->GetCenter() - GetFeet();
toGoal.z = 0.0f;
float range = toGoal.NormalizeInPlace();
toGoal.z = 1.0f;
body->AimHeadTowards( body->GetEyePosition() + 100.0f * toGoal, IBody::MANDATORY, 0.1f, NULL, "Ladder dismount" );
// it is important to approach precisely, so use a very large weight to wash out all other Approaches
Approach( GetFeet() + 100.0f * toGoal, 9999999.9f );
if ( GetBot()->IsDebugging( NEXTBOT_LOCOMOTION ) )
{
NDebugOverlay::EntityText( GetBot()->GetEntity()->entindex(), 0, "Dismount top", 0.1f, 255, 255, 255, 255 );
NDebugOverlay::HorzArrow( GetFeet(), m_ladderDismountGoal->GetCenter(), 5.0f, 255, 255, 0, 255, true, 0.1f );
}
// test 2D vector here in case nav area is under the geometry a bit
const float tolerance = 10.0f;
if ( GetBot()->GetEntity()->GetLastKnownArea() == m_ladderDismountGoal && range < tolerance )
{
// reached dismount goal
m_ladderInfo = NULL;
return NO_LADDER;
}
return DISMOUNTING_LADDER_TOP;
}
//-----------------------------------------------------------------------------------------------------
PlayerLocomotion::LadderState PlayerLocomotion::DismountLadderBottom( void )
{
if ( m_ladderInfo == NULL || m_ladderTimer.IsElapsed() )
{
m_ladderInfo = NULL;
return NO_LADDER;
}
if ( GetBot()->GetEntity()->GetMoveType() == MOVETYPE_LADDER )
{
// near the bottom - just let go
GetBot()->GetEntity()->SetMoveType( MOVETYPE_WALK );
m_ladderInfo = NULL;
}
return NO_LADDER;
}
//-----------------------------------------------------------------------------------------------------
/**
* Update internal state
*/
void PlayerLocomotion::Update( void )
{
if ( TraverseLadder() )
{
return BaseClass::Update();
}
if ( m_isJumpingAcrossGap || m_isClimbingUpToLedge )
{
// force a run
SetMinimumSpeedLimit( GetRunSpeed() );
Vector toLanding = m_landingGoal - GetFeet();
toLanding.z = 0.0f;
toLanding.NormalizeInPlace();
if ( m_hasLeftTheGround )
{
// face into the jump/climb
GetBot()->GetBodyInterface()->AimHeadTowards( GetBot()->GetEntity()->EyePosition() + 100.0 * toLanding, IBody::MANDATORY, 0.25f, NULL, "Facing impending jump/climb" );
if ( IsOnGround() )
{
// back on the ground - jump is complete
m_isClimbingUpToLedge = false;
m_isJumpingAcrossGap = false;
SetMinimumSpeedLimit( 0.0f );
}
}
else
{
// haven't left the ground yet - just starting the jump
if ( !IsClimbingOrJumping() )
{
Jump();
}
Vector vel = GetBot()->GetEntity()->GetAbsVelocity();
if ( m_isJumpingAcrossGap )
{
// cheat and max our velocity in case we were stopped at the edge of this gap
vel.x = GetRunSpeed() * toLanding.x;
vel.y = GetRunSpeed() * toLanding.y;
// leave vel.z unchanged
}
GetBot()->GetEntity()->SetAbsVelocity( vel );
if ( !IsOnGround() )
{
// jump has begun
m_hasLeftTheGround = true;
}
}
Approach( m_landingGoal );
}
BaseClass::Update();
}
//-----------------------------------------------------------------------------------------------------
void PlayerLocomotion::AdjustPosture( const Vector &moveGoal )
{
// This function has no effect if we're not standing or crouching
IBody *body = GetBot()->GetBodyInterface();
if ( !body->IsActualPosture( IBody::STAND ) && !body->IsActualPosture( IBody::CROUCH ) )
return;
// not all games have auto-crouch, so don't assume it here
BaseClass::AdjustPosture( moveGoal );
}
//-----------------------------------------------------------------------------------------------------
/**
* Build a user command to move this player towards the goal position
*/
void PlayerLocomotion::Approach( const Vector &pos, float goalWeight )
{
VPROF_BUDGET( "PlayerLocomotion::Approach", "NextBot" );
BaseClass::Approach( pos );
AdjustPosture( pos );
if ( GetBot()->IsDebugging( NEXTBOT_LOCOMOTION ) )
{
NDebugOverlay::Line( GetFeet(), pos, 255, 255, 0, true, 0.1f );
}
INextBotPlayerInput *playerButtons = dynamic_cast< INextBotPlayerInput * >( GetBot() );
if ( !playerButtons )
{
DevMsg( "PlayerLocomotion::Approach: No INextBotPlayerInput\n " );
return;
}
Vector forward3D;
m_player->EyeVectors( &forward3D );
Vector2D forward( forward3D.x, forward3D.y );
forward.NormalizeInPlace();
Vector2D right( forward.y, -forward.x );
// compute unit vector to goal position
Vector2D to = ( pos - GetFeet() ).AsVector2D();
float goalDistance = to.NormalizeInPlace();
float ahead = to.Dot( forward );
float side = to.Dot( right );
#ifdef NEED_TO_INTEGRATE_MOTION_CONTROLLED_CODE_FROM_L4D_PLAYERS
// If we're climbing ledges, we need to stay crouched to prevent player movement code from messing
// with our origin.
CTerrorPlayer *player = ToTerrorPlayer(m_player);
if ( player && player->IsMotionControlledZ( player->GetMainActivity() ) )
{
playerButtons->PressCrouchButton();
return;
}
#endif
if ( m_player->IsOnLadder() && IsUsingLadder() && ( m_ladderState == ASCENDING_LADDER || m_ladderState == DESCENDING_LADDER ) )
{
// we are on a ladder and WANT to be on a ladder.
playerButtons->PressForwardButton();
// Stay in center of ladder. The gamemovement will autocenter us in most cases, but this is needed in case it doesn't.
if ( m_ladderInfo )
{
Vector posOnLadder;
CalcClosestPointOnLine( GetFeet(), m_ladderInfo->m_bottom, m_ladderInfo->m_top, posOnLadder );
Vector alongLadder = m_ladderInfo->m_top - m_ladderInfo->m_bottom;
alongLadder.NormalizeInPlace();
Vector rightLadder = CrossProduct( alongLadder, m_ladderInfo->GetNormal() );
Vector away = GetFeet() - posOnLadder;
// we only want error in plane of ladder
float error = DotProduct( away, rightLadder );
away.NormalizeInPlace();
const float tolerance = 5.0f + 0.25f * GetBot()->GetBodyInterface()->GetHullWidth();
if ( error > tolerance )
{
if ( DotProduct( away, rightLadder ) > 0.0f )
{
playerButtons->PressLeftButton();
}
else
{
playerButtons->PressRightButton();
}
}
}
}
else
{
const float epsilon = 0.25f;
if ( NextBotPlayerMoveDirect.GetBool() )
{
if ( goalDistance > epsilon )
{
playerButtons->SetButtonScale( ahead, side );
}
}
if ( ahead > epsilon )
{
playerButtons->PressForwardButton();
if ( GetBot()->IsDebugging( NEXTBOT_LOCOMOTION ) )
{
NDebugOverlay::HorzArrow( m_player->GetAbsOrigin(), m_player->GetAbsOrigin() + 50.0f * Vector( forward.x, forward.y, 0.0f ), 15.0f, 0, 255, 0, 255, true, 0.1f );
}
}
else if ( ahead < -epsilon )
{
playerButtons->PressBackwardButton();
if ( GetBot()->IsDebugging( NEXTBOT_LOCOMOTION ) )
{
NDebugOverlay::HorzArrow( m_player->GetAbsOrigin(), m_player->GetAbsOrigin() - 50.0f * Vector( forward.x, forward.y, 0.0f ), 15.0f, 255, 0, 0, 255, true, 0.1f );
}
}
if ( side <= -epsilon )
{
playerButtons->PressLeftButton();
if ( GetBot()->IsDebugging( NEXTBOT_LOCOMOTION ) )
{
NDebugOverlay::HorzArrow( m_player->GetAbsOrigin(), m_player->GetAbsOrigin() - 50.0f * Vector( right.x, right.y, 0.0f ), 15.0f, 255, 0, 255, 255, true, 0.1f );
}
}
else if ( side >= epsilon )
{
playerButtons->PressRightButton();
if ( GetBot()->IsDebugging( NEXTBOT_LOCOMOTION ) )
{
NDebugOverlay::HorzArrow( m_player->GetAbsOrigin(), m_player->GetAbsOrigin() + 50.0f * Vector( right.x, right.y, 0.0f ), 15.0f, 0, 255, 255, 255, true, 0.1f );
}
}
}
if ( !IsRunning() )
{
playerButtons->PressWalkButton();
}
}
//----------------------------------------------------------------------------------------------------
/**
* Move the bot to the precise given position immediately,
*/
void PlayerLocomotion::DriveTo( const Vector &pos )
{
BaseClass::DriveTo( pos );
Approach( pos );
}
//----------------------------------------------------------------------------------------------------
bool PlayerLocomotion::IsClimbPossible( INextBot *me, const CBaseEntity *obstacle ) const
{
// don't jump unless we have to
const PathFollower *path = GetBot()->GetCurrentPath();
if ( path )
{
const float watchForClimbRange = 75.0f;
if ( !path->IsDiscontinuityAhead( GetBot(), Path::CLIMB_UP, watchForClimbRange ) )
{
// we are not planning on climbing
// always allow climbing over movable obstacles
if ( obstacle && !const_cast< CBaseEntity * >( obstacle )->IsWorld() )
{
IPhysicsObject *physics = obstacle->VPhysicsGetObject();
if ( physics && physics->IsMoveable() )
{
// movable physics object - climb over it
return true;
}
}
if ( !GetBot()->GetLocomotionInterface()->IsStuck() )
{
// we're not stuck - don't try to jump up yet
return false;
}
}
}
return true;
}
//----------------------------------------------------------------------------------------------------
bool PlayerLocomotion::ClimbUpToLedge( const Vector &landingGoal, const Vector &landingForward, const CBaseEntity *obstacle )
{
if ( !IsClimbPossible( GetBot(), obstacle ) )
{
return false;
}
Jump();
m_isClimbingUpToLedge = true;
m_landingGoal = landingGoal;
m_hasLeftTheGround = false;
return true;
}
//----------------------------------------------------------------------------------------------------
void PlayerLocomotion::JumpAcrossGap( const Vector &landingGoal, const Vector &landingForward )
{
Jump();
// face forward
GetBot()->GetBodyInterface()->AimHeadTowards( landingGoal, IBody::MANDATORY, 1.0f, NULL, "Looking forward while jumping a gap" );
m_isJumpingAcrossGap = true;
m_landingGoal = landingGoal;
m_hasLeftTheGround = false;
}
//----------------------------------------------------------------------------------------------------
void PlayerLocomotion::Jump( void )
{
m_isJumping = true;
m_jumpTimer.Start( 0.5f );
INextBotPlayerInput *playerButtons = dynamic_cast< INextBotPlayerInput * >( GetBot() );
if ( playerButtons )
{
playerButtons->PressJumpButton();
}
}
//----------------------------------------------------------------------------------------------------
bool PlayerLocomotion::IsClimbingOrJumping( void ) const
{
if ( !m_isJumping )
return false;
if ( m_jumpTimer.IsElapsed() && IsOnGround() )
{
m_isJumping = false;
return false;
}
return true;
}
//----------------------------------------------------------------------------------------------------
bool PlayerLocomotion::IsClimbingUpToLedge( void ) const
{
return m_isClimbingUpToLedge;
}
//----------------------------------------------------------------------------------------------------
bool PlayerLocomotion::IsJumpingAcrossGap( void ) const
{
return m_isJumpingAcrossGap;
}
//----------------------------------------------------------------------------------------------------
/**
* Return true if standing on something
*/
bool PlayerLocomotion::IsOnGround( void ) const
{
return (m_player->GetGroundEntity() != NULL);
}
//----------------------------------------------------------------------------------------------------
/**
* Return the current ground entity or NULL if not on the ground
*/
CBaseEntity *PlayerLocomotion::GetGround( void ) const
{
return m_player->GetGroundEntity();
}
//----------------------------------------------------------------------------------------------------
/**
* Surface normal of the ground we are in contact with
*/
const Vector &PlayerLocomotion::GetGroundNormal( void ) const
{
static Vector up( 0, 0, 1.0f );
return up;
// TODO: Integrate movehelper_server for this: return m_player->GetGroundNormal();
}
//----------------------------------------------------------------------------------------------------
/**
* Climb the given ladder to the top and dismount
*/
void PlayerLocomotion::ClimbLadder( const CNavLadder *ladder, const CNavArea *dismountGoal )
{
// look up and push forward
// Vector goal = GetBot()->GetPosition() + 100.0f * ( Vector( 0, 0, 1.0f ) - ladder->GetNormal() );
// Approach( goal );
// FaceTowards( goal );
m_ladderState = APPROACHING_ASCENDING_LADDER;
m_ladderInfo = ladder;
m_ladderDismountGoal = dismountGoal;
}
//----------------------------------------------------------------------------------------------------
/**
* Descend the given ladder to the bottom and dismount
*/
void PlayerLocomotion::DescendLadder( const CNavLadder *ladder, const CNavArea *dismountGoal )
{
// look down and push forward
// Vector goal = GetBot()->GetPosition() + 100.0f * ( Vector( 0, 0, -1.0f ) - ladder->GetNormal() );
// Approach( goal );
// FaceTowards( goal );
m_ladderState = APPROACHING_DESCENDING_LADDER;
m_ladderInfo = ladder;
m_ladderDismountGoal = dismountGoal;
}
//----------------------------------------------------------------------------------------------------
bool PlayerLocomotion::IsUsingLadder( void ) const
{
return ( m_ladderState != NO_LADDER );
}
//----------------------------------------------------------------------------------------------------
/**
* Rotate body to face towards "target"
*/
void PlayerLocomotion::FaceTowards( const Vector &target )
{
// player body follows view direction
Vector look( target.x, target.y, GetBot()->GetEntity()->EyePosition().z );
GetBot()->GetBodyInterface()->AimHeadTowards( look, IBody::BORING, 0.1f, NULL, "Body facing" );
}
//-----------------------------------------------------------------------------------------------------
/**
* Return position of "feet" - point below centroid of bot at feet level
*/
const Vector &PlayerLocomotion::GetFeet( void ) const
{
return m_player->GetAbsOrigin();
}
//-----------------------------------------------------------------------------------------------------
/**
* Return current world space velocity
*/
const Vector &PlayerLocomotion::GetVelocity( void ) const
{
return m_player->GetAbsVelocity();
}
//-----------------------------------------------------------------------------------------------------
float PlayerLocomotion::GetRunSpeed( void ) const
{
return m_player->MaxSpeed();
}
//-----------------------------------------------------------------------------------------------------
float PlayerLocomotion::GetWalkSpeed( void ) const
{
return 0.5f * m_player->MaxSpeed();
}
@@ -0,0 +1,223 @@
// NextBotPlayerLocomotion.h
// Locomotor for CBasePlayer derived bots
// Author: Michael Booth, November 2005
//========= Copyright Valve Corporation, All rights reserved. ============//
#ifndef _NEXT_BOT_PLAYER_LOCOMOTION_H_
#define _NEXT_BOT_PLAYER_LOCOMOTION_H_
#include "NextBot.h"
#include "NextBotLocomotionInterface.h"
#include "Path/NextBotPathFollow.h"
class CBasePlayer;
//--------------------------------------------------------------------------------------------------
/**
* Basic player locomotion implementation
*/
class PlayerLocomotion : public ILocomotion
{
public:
DECLARE_CLASS( PlayerLocomotion, ILocomotion );
PlayerLocomotion( INextBot *bot );
virtual ~PlayerLocomotion() { }
virtual void Reset( void ); // reset to initial state
virtual void Update( void ); // update internal state
virtual void Approach( const Vector &pos, float goalWeight = 1.0f ); // move directly towards the given position
virtual void DriveTo( const Vector &pos ); // Move the bot to the precise given position immediately,
//
// ILocomotion modifiers
//
virtual bool ClimbUpToLedge( const Vector &landingGoal, const Vector &landingForward, const CBaseEntity *obstacle ); // initiate a jump to an adjacent high ledge, return false if climb can't start
virtual void JumpAcrossGap( const Vector &landingGoal, const Vector &landingForward ); // initiate a jump across an empty volume of space to far side
virtual void Jump( void ); // initiate a simple undirected jump in the air
virtual bool IsClimbingOrJumping( void ) const; // is jumping in any form
virtual bool IsClimbingUpToLedge( void ) const; // is climbing up to a high ledge
virtual bool IsJumpingAcrossGap( void ) const; // is jumping across a gap to the far side
virtual void Run( void ); // set desired movement speed to running
virtual void Walk( void ); // set desired movement speed to walking
virtual void Stop( void ); // set desired movement speed to stopped
virtual bool IsRunning( void ) const;
virtual void SetDesiredSpeed( float speed ); // set desired speed for locomotor movement
virtual float GetDesiredSpeed( void ) const; // returns the current desired speed
virtual void SetMinimumSpeedLimit( float limit ); // speed cannot drop below this
virtual void SetMaximumSpeedLimit( float limit ); // speed cannot rise above this
virtual bool IsOnGround( void ) const; // return true if standing on something
virtual CBaseEntity *GetGround( void ) const; // return the current ground entity or NULL if not on the ground
virtual const Vector &GetGroundNormal( void ) const; // surface normal of the ground we are in contact with
virtual void ClimbLadder( const CNavLadder *ladder, const CNavArea *dismountGoal ); // climb the given ladder to the top and dismount
virtual void DescendLadder( const CNavLadder *ladder, const CNavArea *dismountGoal ); // descend the given ladder to the bottom and dismount
virtual bool IsUsingLadder( void ) const;
virtual bool IsAscendingOrDescendingLadder( void ) const; // we are actually on the ladder right now, either climbing up or down
virtual bool IsAbleToAutoCenterOnLadder( void ) const;
virtual void FaceTowards( const Vector &target ); // rotate body to face towards "target"
virtual void SetDesiredLean( const QAngle &lean ) { }
virtual const QAngle &GetDesiredLean( void ) const { static QAngle junk; return junk; }
//
// ILocomotion information
//
virtual const Vector &GetFeet( void ) const; // return position of "feet" - point below centroid of bot at feet level
virtual float GetStepHeight( void ) const; // if delta Z is greater than this, we have to jump to get up
virtual float GetMaxJumpHeight( void ) const; // return maximum height of a jump
virtual float GetDeathDropHeight( void ) const; // distance at which we will die if we fall
virtual float GetRunSpeed( void ) const; // get maximum running speed
virtual float GetWalkSpeed( void ) const; // get maximum walking speed
virtual float GetMaxAcceleration( void ) const; // return maximum acceleration of locomotor
virtual float GetMaxDeceleration( void ) const; // return maximum deceleration of locomotor
virtual const Vector &GetVelocity( void ) const; // return current world space velocity
protected:
virtual void AdjustPosture( const Vector &moveGoal );
private:
CBasePlayer *m_player; // the player we are locomoting
mutable bool m_isJumping;
CountdownTimer m_jumpTimer;
bool m_isClimbingUpToLedge;
bool m_isJumpingAcrossGap;
Vector m_landingGoal;
bool m_hasLeftTheGround;
float m_desiredSpeed;
float m_minSpeedLimit;
float m_maxSpeedLimit;
bool TraverseLadder( void ); // when climbing/descending a ladder
enum LadderState
{
NO_LADDER, // not using a ladder
APPROACHING_ASCENDING_LADDER,
APPROACHING_DESCENDING_LADDER,
ASCENDING_LADDER,
DESCENDING_LADDER,
DISMOUNTING_LADDER_TOP,
DISMOUNTING_LADDER_BOTTOM,
};
LadderState m_ladderState;
LadderState ApproachAscendingLadder( void );
LadderState ApproachDescendingLadder( void );
LadderState AscendLadder( void );
LadderState DescendLadder( void );
LadderState DismountLadderTop( void );
LadderState DismountLadderBottom( void );
const CNavLadder *m_ladderInfo;
const CNavArea *m_ladderDismountGoal;
CountdownTimer m_ladderTimer; // a "give up" timer if things go awry
bool IsClimbPossible( INextBot *me, const CBaseEntity *obstacle ) const;
};
inline float PlayerLocomotion::GetStepHeight( void ) const
{
return 18.0f;
}
inline float PlayerLocomotion::GetMaxJumpHeight( void ) const
{
return 57.0f;
}
inline float PlayerLocomotion::GetDeathDropHeight( void ) const
{
return 200.0f;
}
inline float PlayerLocomotion::GetMaxAcceleration( void ) const
{
return 100.0f;
}
inline float PlayerLocomotion::GetMaxDeceleration( void ) const
{
return 200.0f;
}
inline void PlayerLocomotion::Run( void )
{
m_desiredSpeed = GetRunSpeed();
}
inline void PlayerLocomotion::Walk( void )
{
m_desiredSpeed = GetWalkSpeed();
}
inline void PlayerLocomotion::Stop( void )
{
m_desiredSpeed = 0.0f;
}
inline bool PlayerLocomotion::IsRunning( void ) const
{
return true;
}
inline void PlayerLocomotion::SetDesiredSpeed( float speed )
{
m_desiredSpeed = speed;
}
inline float PlayerLocomotion::GetDesiredSpeed( void ) const
{
return clamp( m_desiredSpeed, m_minSpeedLimit, m_maxSpeedLimit );
}
inline void PlayerLocomotion::SetMinimumSpeedLimit( float limit )
{
m_minSpeedLimit = limit;
}
inline void PlayerLocomotion::SetMaximumSpeedLimit( float limit )
{
m_maxSpeedLimit = limit;
}
inline bool PlayerLocomotion::IsAbleToAutoCenterOnLadder( void ) const
{
return IsUsingLadder() && (m_ladderState == ASCENDING_LADDER || m_ladderState == DESCENDING_LADDER);
}
inline bool PlayerLocomotion::IsAscendingOrDescendingLadder( void ) const
{
switch( m_ladderState )
{
case ASCENDING_LADDER:
case DESCENDING_LADDER:
case DISMOUNTING_LADDER_TOP:
case DISMOUNTING_LADDER_BOTTOM:
return true;
default:
// Explicitly handle the default so that clang knows not to warn us.
// warning: enumeration values 'NO_LADDER', 'APPROACHING_ASCENDING_LADDER', and 'APPROACHING_DESCENDING_LADDER' not handled in switch [-Wswitch-enum]
break;
}
return false;
}
#endif // _NEXT_BOT_PLAYER_LOCOMOTION_H_
+199
View File
@@ -0,0 +1,199 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// simple_bot.cpp
// A simple bot
// Michael Booth, February 2009
#include "cbase.h"
#include "simple_bot.h"
#include "nav_mesh.h"
//-----------------------------------------------------------------------------------------------------
// Command to add a Simple Bot where your crosshairs are aiming
//-----------------------------------------------------------------------------------------------------
CON_COMMAND_F( simple_bot_add, "Add a simple bot.", FCVAR_CHEAT )
{
CBasePlayer *player = UTIL_GetCommandClient();
if ( !player )
{
return;
}
Vector forward;
player->EyeVectors( &forward );
trace_t result;
UTIL_TraceLine( player->EyePosition(), player->EyePosition() + 999999.9f * forward, MASK_BLOCKLOS_AND_NPCS|CONTENTS_IGNORE_NODRAW_OPAQUE, player, COLLISION_GROUP_NONE, &result );
if ( !result.DidHit() )
{
return;
}
CSimpleBot *bot = static_cast< CSimpleBot * >( CreateEntityByName( "simple_bot" ) );
if ( bot )
{
Vector forward = player->GetAbsOrigin() - result.endpos;
forward.z = 0.0f;
forward.NormalizeInPlace();
QAngle angles;
VectorAngles( forward, angles );
bot->SetAbsAngles( angles );
bot->SetAbsOrigin( result.endpos + Vector( 0, 0, 10.0f ) );
DispatchSpawn( bot );
}
}
//-----------------------------------------------------------------------------------------------------
// The Simple Bot
//-----------------------------------------------------------------------------------------------------
LINK_ENTITY_TO_CLASS( simple_bot, CSimpleBot );
#ifndef TF_DLL
PRECACHE_REGISTER( simple_bot );
#endif
//-----------------------------------------------------------------------------------------------------
CSimpleBot::CSimpleBot()
{
ALLOCATE_INTENTION_INTERFACE( CSimpleBot );
m_locomotor = new NextBotGroundLocomotion( this );
}
//-----------------------------------------------------------------------------------------------------
CSimpleBot::~CSimpleBot()
{
DEALLOCATE_INTENTION_INTERFACE;
if ( m_locomotor )
delete m_locomotor;
}
//-----------------------------------------------------------------------------------------------------
void CSimpleBot::Precache()
{
BaseClass::Precache();
#ifndef DOTA_DLL
PrecacheModel( "models/humans/group01/female_01.mdl" );
#endif
}
//-----------------------------------------------------------------------------------------------------
void CSimpleBot::Spawn( void )
{
BaseClass::Spawn();
#ifndef DOTA_DLL
SetModel( "models/humans/group01/female_01.mdl" );
#endif
}
//---------------------------------------------------------------------------------------------
// The Simple Bot behaviors
//---------------------------------------------------------------------------------------------
/**
* For use with TheNavMesh->ForAllAreas()
* Find the Nth area in the sequence
*/
class SelectNthAreaFunctor
{
public:
SelectNthAreaFunctor( int count )
{
m_count = count;
m_area = NULL;
}
bool operator() ( CNavArea *area )
{
m_area = area;
return ( m_count-- > 0 );
}
int m_count;
CNavArea *m_area;
};
//---------------------------------------------------------------------------------------------
/**
* This action causes the bot to pick a random nav area in the mesh and move to it, then
* pick another, etc.
* Actions usually each have their own .cpp/.h file and are organized into folders since there
* are often many of them. For this example, we're keeping everything to a single .cpp/.h file.
*/
class CSimpleBotRoam : public Action< CSimpleBot >
{
public:
//----------------------------------------------------------------------------------
// OnStart is called once when the Action first becomes active
virtual ActionResult< CSimpleBot > OnStart( CSimpleBot *me, Action< CSimpleBot > *priorAction )
{
// smooth out the bot's path following by moving toward a point farther down the path
m_path.SetMinLookAheadDistance( 300.0f );
return Continue();
}
//----------------------------------------------------------------------------------
// Update is called repeatedly (usually once per server frame) while the Action is active
virtual ActionResult< CSimpleBot > Update( CSimpleBot *me, float interval )
{
if ( m_path.IsValid() && !m_timer.IsElapsed() )
{
// PathFollower::Update() moves the bot along the path using the bot's ILocomotion and IBody interfaces
m_path.Update( me );
}
else
{
SelectNthAreaFunctor pick( RandomInt( 0, TheNavMesh->GetNavAreaCount() - 1 ) );
TheNavMesh->ForAllAreas( pick );
if ( pick.m_area )
{
CSimpleBotPathCost cost( me );
m_path.Compute( me, pick.m_area->GetCenter(), cost );
}
// follow this path for a random duration (or until we reach the end)
m_timer.Start( RandomFloat( 5.0f, 10.0f ) );
}
return Continue();
}
//----------------------------------------------------------------------------------
// this is an event handler - many more are available (see declaration of Action< Actor > in NextBotBehavior.h)
virtual EventDesiredResult< CSimpleBot > OnStuck( CSimpleBot *me )
{
// we are stuck trying to follow the current path - invalidate it so a new one is chosen
m_path.Invalidate();
return TryContinue();
}
virtual const char *GetName( void ) const { return "Roam"; } // return name of this action
private:
PathFollower m_path;
CountdownTimer m_timer;
};
//---------------------------------------------------------------------------------------------
/**
* Instantiate the bot's Intention interface and start the initial Action (CSimpleBotRoam in this case)
*/
IMPLEMENT_INTENTION_INTERFACE( CSimpleBot, CSimpleBotRoam )
+116
View File
@@ -0,0 +1,116 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// simple_bot.h
// A mininal example of a NextBotCombatCharacter (ie: non-player) bot
// Michael Booth, February 2009
#ifndef SIMPLE_BOT_H
#define SIMPLE_BOT_H
#include "NextBot.h"
#include "NextBotBehavior.h"
#include "NextBotGroundLocomotion.h"
#include "Path/NextBotPathFollow.h"
//----------------------------------------------------------------------------
/**
* A Simple Bot
*/
class CSimpleBot : public NextBotCombatCharacter
{
public:
DECLARE_CLASS( CSimpleBot, NextBotCombatCharacter );
CSimpleBot();
virtual ~CSimpleBot();
virtual void Precache();
virtual void Spawn( void );
// INextBot
DECLARE_INTENTION_INTERFACE( CSimpleBot )
virtual NextBotGroundLocomotion *GetLocomotionInterface( void ) const { return m_locomotor; }
private:
NextBotGroundLocomotion *m_locomotor;
};
//--------------------------------------------------------------------------------------------------------------
/**
* Functor used with the A* algorithm of NavAreaBuildPath() to determine the "cost" of moving from one area to another.
* "Cost" is generally the weighted distance between the centers of the areas. If you want the bot
* to avoid an area/ladder/elevator, increase the cost. If you want to disallow an area/ladder/elevator, return -1.
*/
class CSimpleBotPathCost : public IPathCost
{
public:
CSimpleBotPathCost( CSimpleBot *me )
{
m_me = me;
}
// return the cost (weighted distance between) of moving from "fromArea" to "area", or -1 if the move is not allowed
virtual float operator()( CNavArea *area, CNavArea *fromArea, const CNavLadder *ladder, const CFuncElevator *elevator, float length ) const
{
if ( fromArea == NULL )
{
// first area in path, no cost
return 0.0f;
}
else
{
if ( !m_me->GetLocomotionInterface()->IsAreaTraversable( area ) )
{
// our locomotor says we can't move here
return -1.0f;
}
// compute distance traveled along path so far
float dist;
if ( ladder )
{
dist = ladder->m_length;
}
else if ( length > 0.0 )
{
// optimization to avoid recomputing length
dist = length;
}
else
{
dist = ( area->GetCenter() - fromArea->GetCenter() ).Length();
}
float cost = dist + fromArea->GetCostSoFar();
// check height change
float deltaZ = fromArea->ComputeAdjacentConnectionHeightChange( area );
if ( deltaZ >= m_me->GetLocomotionInterface()->GetStepHeight() )
{
if ( deltaZ >= m_me->GetLocomotionInterface()->GetMaxJumpHeight() )
{
// too high to reach
return -1.0f;
}
// jumping is slower than flat ground
const float jumpPenalty = 5.0f;
cost += jumpPenalty * dist;
}
else if ( deltaZ < -m_me->GetLocomotionInterface()->GetDeathDropHeight() )
{
// too far to drop
return -1.0f;
}
return cost;
}
}
CSimpleBot *m_me;
};
#endif // SIMPLE_BOT_H
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+656
View File
@@ -0,0 +1,656 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Bot radio chatter system
//
// $NoKeywords: $
//=============================================================================//
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
#ifndef CS_BOT_CHATTER_H
#define CS_BOT_CHATTER_H
#pragma warning( disable : 4786 ) // long STL names get truncated in browse info.
#include "nav_mesh.h"
#include "cs_gamestate.h"
class CCSBot;
class BotChatterInterface;
#define MAX_PLACES_PER_MAP 64
typedef unsigned int PlaceCriteria;
typedef unsigned int CountCriteria;
#define UNDEFINED_COUNT 0xFFFF
#define COUNT_CURRENT_ENEMIES 0xFF // use the number of enemies we see right when we speak
#define COUNT_MANY 4 // equal to or greater than this is "many"
#define UNDEFINED_SUBJECT (-1)
/// @todo Make Place a class with member fuctions for this
const Vector *GetRandomSpotAtPlace( Place place );
//----------------------------------------------------------------------------------------------------
/**
* A meme is a unit information that bots use to
* transmit information to each other via the radio
*/
class BotMeme
{
public:
void Transmit( CCSBot *sender ) const; ///< transmit meme to other bots
// It is a best practice to always have a virtual destructor in an interface
// class. Otherwise if the derived classes have destructors they will not be
// called.
virtual ~BotMeme() {}
virtual void Interpret( CCSBot *sender, CCSBot *receiver ) const = 0; ///< cause the given bot to act on this meme
};
//----------------------------------------------------------------------------------------------------
class BotHelpMeme : public BotMeme
{
public:
BotHelpMeme( Place place = UNDEFINED_PLACE )
{
m_place = place;
}
virtual void Interpret( CCSBot *sender, CCSBot *receiver ) const; ///< cause the given bot to act on this meme
private:
Place m_place; ///< where the help is needed
};
//----------------------------------------------------------------------------------------------------
class BotBombsiteStatusMeme : public BotMeme
{
public:
enum StatusType { CLEAR, PLANTED };
BotBombsiteStatusMeme( int zoneIndex, StatusType status )
{
m_zoneIndex = zoneIndex;
m_status = status;
}
virtual void Interpret( CCSBot *sender, CCSBot *receiver ) const; ///< cause the given bot to act on this meme
private:
int m_zoneIndex; ///< the bombsite
StatusType m_status; ///< whether it is cleared or the bomb is there (planted)
};
//----------------------------------------------------------------------------------------------------
class BotBombStatusMeme : public BotMeme
{
public:
BotBombStatusMeme( CSGameState::BombState state, const Vector &pos )
{
m_state = state;
m_pos = pos;
}
virtual void Interpret( CCSBot *sender, CCSBot *receiver ) const; ///< cause the given bot to act on this meme
private:
CSGameState::BombState m_state;
Vector m_pos;
};
//----------------------------------------------------------------------------------------------------
class BotFollowMeme : public BotMeme
{
public:
virtual void Interpret( CCSBot *sender, CCSBot *receiver ) const; ///< cause the given bot to act on this meme
};
//----------------------------------------------------------------------------------------------------
class BotDefendHereMeme : public BotMeme
{
public:
BotDefendHereMeme( const Vector &pos )
{
m_pos = pos;
}
virtual void Interpret( CCSBot *sender, CCSBot *receiver ) const; ///< cause the given bot to act on this meme
private:
Vector m_pos;
};
//----------------------------------------------------------------------------------------------------
class BotWhereBombMeme : public BotMeme
{
public:
virtual void Interpret( CCSBot *sender, CCSBot *receiver ) const; ///< cause the given bot to act on this meme
};
//----------------------------------------------------------------------------------------------------
class BotRequestReportMeme : public BotMeme
{
public:
virtual void Interpret( CCSBot *sender, CCSBot *receiver ) const; ///< cause the given bot to act on this meme
};
//----------------------------------------------------------------------------------------------------
class BotAllHostagesGoneMeme : public BotMeme
{
public:
virtual void Interpret( CCSBot *sender, CCSBot *receiver ) const; ///< cause the given bot to act on this meme
};
//----------------------------------------------------------------------------------------------------
class BotHostageBeingTakenMeme : public BotMeme
{
public:
virtual void Interpret( CCSBot *sender, CCSBot *receiver ) const; ///< cause the given bot to act on this meme
};
//----------------------------------------------------------------------------------------------------
class BotHeardNoiseMeme : public BotMeme
{
public:
virtual void Interpret( CCSBot *sender, CCSBot *receiver ) const; ///< cause the given bot to act on this meme
};
//----------------------------------------------------------------------------------------------------
class BotWarnSniperMeme : public BotMeme
{
public:
virtual void Interpret( CCSBot *sender, CCSBot *receiver ) const; ///< cause the given bot to act on this meme
};
//----------------------------------------------------------------------------------------------------
enum BotStatementType
{
REPORT_VISIBLE_ENEMIES,
REPORT_ENEMY_ACTION,
REPORT_MY_CURRENT_TASK,
REPORT_MY_INTENTION,
REPORT_CRITICAL_EVENT,
REPORT_REQUEST_HELP,
REPORT_REQUEST_INFORMATION,
REPORT_ROUND_END,
REPORT_MY_PLAN,
REPORT_INFORMATION,
REPORT_EMOTE,
REPORT_ACKNOWLEDGE, ///< affirmative or negative
REPORT_ENEMIES_REMAINING,
REPORT_FRIENDLY_FIRE,
REPORT_KILLED_FRIEND,
REPORT_ENEMY_LOST,
NUM_BOT_STATEMENT_TYPES
};
//----------------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------------
/**
* BotSpeakables are the smallest unit of bot chatter.
* They represent a specific wav file of a phrase, and the criteria for which it is useful
*/
class BotSpeakable
{
public:
BotSpeakable();
~BotSpeakable();
char *m_phrase;
float m_duration;
PlaceCriteria m_place;
CountCriteria m_count;
};
typedef CUtlVector< BotSpeakable * > BotSpeakableVector;
typedef CUtlVector< BotSpeakableVector * > BotVoiceBankVector;
//----------------------------------------------------------------------------------------------------
/**
* The BotPhrase class is a collection of Speakables associated with a name, ID, and criteria
*/
class BotPhrase
{
public:
char *GetSpeakable( int bankIndex, float *duration = NULL ) const; ///< return a random speakable and its duration in seconds that meets the current criteria
// NOTE: Criteria must be set just before the GetSpeakable() call, since they are shared among all bots
void ClearCriteria( void ) const;
void SetPlaceCriteria( PlaceCriteria place ) const; ///< all returned phrases must have this place criteria
void SetCountCriteria( CountCriteria count ) const; ///< all returned phrases must have this count criteria
const char *GetName( void ) const { return m_name; }
const unsigned int GetPlace( void ) const { return m_place; }
RadioType GetRadioEquivalent( void ) const { return m_radioEvent; } ///< return equivalent "standard radio" event
bool IsImportant( void ) const { return m_isImportant; } ///< return true if this phrase is part of an important statement
bool IsPlace( void ) const { return m_isPlace; }
void Randomize( void ); ///< randomly shuffle the speakable order
private:
friend class BotPhraseManager;
BotPhrase( bool isPlace );
~BotPhrase();
char *m_name;
Place m_place;
bool m_isPlace; ///< true if this is a Place phrase
RadioType m_radioEvent; ///< equivalent radio event
bool m_isImportant; ///< mission-critical statement
mutable BotVoiceBankVector m_voiceBank; ///< array of voice banks (arrays of speakables)
CUtlVector< int > m_count; ///< number of speakables
mutable CUtlVector< int > m_index; ///< index of next speakable to return
int m_numVoiceBanks; ///< number of voice banks that have been initialized
void InitVoiceBank( int bankIndex ); ///< sets up the vector of voice banks for the first bankIndex voice banks
mutable PlaceCriteria m_placeCriteria;
mutable CountCriteria m_countCriteria;
};
typedef CUtlVector<BotPhrase *> BotPhraseList;
inline void BotPhrase::ClearCriteria( void ) const
{
m_placeCriteria = ANY_PLACE;
m_countCriteria = UNDEFINED_COUNT;
}
inline void BotPhrase::SetPlaceCriteria( PlaceCriteria place ) const
{
m_placeCriteria = place;
}
inline void BotPhrase::SetCountCriteria( CountCriteria count ) const
{
m_countCriteria = count;
}
enum BotChatterOutputType
{
BOT_CHATTER_RADIO,
BOT_CHATTER_VOICE
};
typedef CUtlVector<BotChatterOutputType> BotOutputList;
//----------------------------------------------------------------------------------------------------
/**
* The BotPhraseManager is a singleton that provides an interface to all BotPhrase collections
*/
class BotPhraseManager
{
public:
BotPhraseManager( void );
~BotPhraseManager();
bool Initialize( const char *filename, int bankIndex ); ///< initialize phrase system from database file for a specific voice bank (0 is the default voice bank)
void OnRoundRestart( void ); ///< invoked when round resets
void OnMapChange( void ); ///< invoked when map changes
void Reset( void );
const BotPhrase *GetPhrase( const char *name ) const; ///< given a name, return the associated phrase collection
const BotPhrase *GetPainPhrase( void ) const { return m_painPhrase; } ///< optimization, replaces a static pointer to the phrase
const BotPhrase *GetAgreeWithPlanPhrase( void ) const { return m_agreeWithPlanPhrase; } ///< optimization, replaces a static pointer to the phrase
const BotPhrase *GetPlace( const char *name ) const; ///< given a name, return the associated Place phrase collection
const BotPhrase *GetPlace( unsigned int id ) const; ///< given an id, return the associated Place phrase collection
const BotPhraseList *GetPlaceList( void ) const { return &m_placeList; }
float GetPlaceStatementInterval( Place where ) const; ///< return time last statement of given type was emitted by a teammate for the given place
void ResetPlaceStatementInterval( Place where ); ///< set time of last statement of given type was emitted by a teammate for the given place
BotChatterOutputType GetOutputType( int voiceBank ) const;
private:
BotPhraseList m_list; ///< master list of all phrase collections
BotPhraseList m_placeList; ///< master list of all Place phrases
BotOutputList m_output;
const BotPhrase *m_painPhrase;
const BotPhrase *m_agreeWithPlanPhrase;
struct PlaceTimeInfo
{
Place placeID;
IntervalTimer timer;
};
mutable PlaceTimeInfo m_placeStatementHistory[ MAX_PLACES_PER_MAP ];
mutable int m_placeCount;
int FindPlaceIndex( Place where ) const;
};
inline int BotPhraseManager::FindPlaceIndex( Place where ) const
{
for( int i=0; i<m_placeCount; ++i )
if (m_placeStatementHistory[i].placeID == where)
return i;
// no such place - allocate it
if (m_placeCount < MAX_PLACES_PER_MAP)
{
m_placeStatementHistory[ ++m_placeCount ].placeID = where;
m_placeStatementHistory[ ++m_placeCount ].timer.Invalidate();
return m_placeCount-1;
}
// place directory is full
return -1;
}
/**
* Return time last statement of given type was emitted by a teammate for the given place
*/
inline float BotPhraseManager::GetPlaceStatementInterval( Place place ) const
{
int index = FindPlaceIndex( place );
if (index < 0)
return 999999.9f;
if (index >= m_placeCount)
return 999999.9f;
return m_placeStatementHistory[ index ].timer.GetElapsedTime();
}
/**
* Set time of last statement of given type was emitted by a teammate for the given place
*/
inline void BotPhraseManager::ResetPlaceStatementInterval( Place place )
{
int index = FindPlaceIndex( place );
if (index < 0)
return;
if (index >= m_placeCount)
return;
// update entry
m_placeStatementHistory[ index ].timer.Reset();
}
extern BotPhraseManager *TheBotPhrases;
//----------------------------------------------------------------------------------------------------
/**
* Statements are meaningful collections of phrases
*/
class BotStatement
{
public:
BotStatement( BotChatterInterface *chatter, BotStatementType type, float expireDuration );
~BotStatement();
BotChatterInterface *GetChatter( void ) const { return m_chatter; }
CCSBot *GetOwner( void ) const;
BotStatementType GetType( void ) const { return m_type; } ///< return the type of statement this is
bool IsImportant( void ) const; ///< return true if this statement is "important" and not personality chatter
bool HasSubject( void ) const { return (m_subject == UNDEFINED_SUBJECT) ? false : true; }
void SetSubject( int playerID ) { m_subject = playerID; } ///< who this statement is about
int GetSubject( void ) const { return m_subject; } ///< who this statement is about
bool HasPlace( void ) const { return (GetPlace()) ? true : false; }
Place GetPlace( void ) const; ///< if this statement refers to a specific place, return that place
void SetPlace( Place where ) { m_place = where; } ///< explicitly set place
bool HasCount( void ) const; ///< return true if this statement has an associated count
bool IsRedundant( const BotStatement *say ) const; ///< return true if this statement is the same as the given one
bool IsObsolete( void ) const; ///< return true if this statement is no longer appropriate to say
void Convert( const BotStatement *say ); ///< possibly change what were going to say base on what teammate is saying
void AppendPhrase( const BotPhrase *phrase );
void SetStartTime( float timestamp ) { m_startTime = timestamp; } ///< define the earliest time this statement can be spoken
float GetStartTime( void ) const { return m_startTime; }
enum ConditionType
{
IS_IN_COMBAT,
RADIO_SILENCE,
ENEMIES_REMAINING,
NUM_CONDITIONS
};
void AddCondition( ConditionType condition ); ///< conditions must be true for the statement to be spoken
bool IsValid( void ) const; ///< verify all attached conditions
enum ContextType
{
CURRENT_ENEMY_COUNT,
REMAINING_ENEMY_COUNT,
SHORT_DELAY,
LONG_DELAY,
ACCUMULATE_ENEMIES_DELAY
};
void AppendPhrase( ContextType contextPhrase ); ///< special phrases that depend on the context
bool Update( void ); ///< emit statement over time, return false if statement is done
bool IsSpeaking( void ) const { return m_isSpeaking; } ///< return true if this statement is currently being spoken
float GetTimestamp( void ) const { return m_timestamp; } ///< get time statement was created (but not necessarily started talking)
void AttachMeme( BotMeme *meme ); ///< attach a meme to this statement, to be transmitted to other friendly bots when spoken
private:
friend class BotChatterInterface;
BotChatterInterface *m_chatter; ///< the chatter system this statement is part of
BotStatement *m_next, *m_prev; ///< linked list hooks
BotStatementType m_type; ///< what kind of statement this is
int m_subject; ///< who this subject is about
Place m_place; ///< explicit place - note some phrases have implicit places as well
BotMeme *m_meme; ///< a statement can only have a single meme for now
float m_timestamp; ///< time when message was created
float m_startTime; ///< the earliest time this statement can be spoken
float m_expireTime; ///< time when this statement is no longer valid
float m_speakTimestamp; ///< time when message began being spoken
bool m_isSpeaking; ///< true if this statement is current being spoken
float m_nextTime; ///< time for next phrase to begin
enum { MAX_BOT_PHRASES = 4 };
struct
{
bool isPhrase;
union
{
const BotPhrase *phrase;
ContextType context;
};
}
m_statement[ MAX_BOT_PHRASES ];
enum { MAX_BOT_CONDITIONS = 4 };
ConditionType m_condition[ MAX_BOT_CONDITIONS ]; ///< conditions that must be true for the statement to be said
int m_conditionCount;
int m_index; ///< m_index refers to the phrase currently being spoken, or -1 if we havent started yet
int m_count;
};
//----------------------------------------------------------------------------------------------------
/**
* This class defines the interface to the bot radio chatter system
*/
class BotChatterInterface
{
public:
BotChatterInterface( CCSBot *me );
~BotChatterInterface( );
void Reset( void ); ///< reset to initial state
void Update( void ); ///< process ongoing chatter
/// invoked when event occurs in the game (some events have NULL entities)
void OnDeath( void ); ///< invoked when we die
enum VerbosityType
{
NORMAL, ///< full chatter
MINIMAL, ///< only scenario-critical events
RADIO, ///< use the standard radio instead
OFF ///< no chatter at all
};
VerbosityType GetVerbosity( void ) const; ///< return our current level of verbosity
CCSBot *GetOwner( void ) const { return m_me; }
bool IsTalking( void ) const; ///< return true if we are currently talking
float GetRadioSilenceDuration( void ); ///< return time since any teammate said anything
void ResetRadioSilenceDuration( void );
enum { MUST_ADD = 1 };
void AddStatement( BotStatement *statement, bool mustAdd = false ); ///< register a statement for speaking
void RemoveStatement( BotStatement *statement ); ///< remove a statement
BotStatement *GetActiveStatement( void ); ///< returns the statement that is being spoken, or is next to be spoken if no-one is speaking now
BotStatement *GetStatement( void ) const; ///< returns our current statement, or NULL if we aren't speaking
int GetPitch( void ) const { return m_pitch; }
//-- things the bots can say ---------------------------------------------------------------------
void Say( const char *phraseName, float lifetime = 3.0f, float delay = 0.0f );
void AnnouncePlan( const char *phraseName, Place where );
void Affirmative( void );
void Negative( void );
void EnemySpotted( void ); ///< report enemy sightings
void KilledMyEnemy( int victimID );
void EnemiesRemaining( void );
void SpottedSniper( void );
void FriendSpottedSniper( void );
void Clear( Place where );
void ReportIn( void ); ///< ask for current situation
void ReportingIn( void ); ///< report current situation
bool NeedBackup( void );
void PinnedDown( void );
void Scared( void );
void HeardNoise( const Vector &pos );
void FriendHeardNoise( void );
void TheyPickedUpTheBomb( void );
void GoingToPlantTheBomb( Place where );
void BombsiteClear( int zoneIndex );
void FoundPlantedBomb( int zoneIndex );
void PlantingTheBomb( Place where );
void SpottedBomber( CBasePlayer *bomber );
void SpottedLooseBomb( CBaseEntity *bomb );
void GuardingLooseBomb( CBaseEntity *bomb );
void RequestBombLocation( void );
#define IS_PLAN true
void GuardingHostages( Place where, bool isPlan = false );
void GuardingHostageEscapeZone( bool isPlan = false );
void HostagesBeingTaken( void );
void HostagesTaken( void );
void TalkingToHostages( void );
void EscortingHostages( void );
void HostageDown( void );
void GuardingBombsite( Place where );
void CelebrateWin( void );
void Encourage( const char *phraseName, float repeatInterval = 10.0f, float lifetime = 3.0f ); ///< "encourage" the player to do the scenario
void KilledFriend( void );
void FriendlyFire( void );
bool SeesAtLeastOneEnemy( void ) const { return m_seeAtLeastOneEnemy; }
private:
BotStatement *m_statementList; ///< list of all active/pending messages for this bot
void ReportEnemies( void ); ///< track nearby enemy count and generate enemy activity statements
bool ShouldSpeak( void ) const; ///< return true if we speaking makes sense now
CCSBot *m_me; ///< the bot this chatter is for
bool m_seeAtLeastOneEnemy;
float m_timeWhenSawFirstEnemy;
bool m_reportedEnemies;
bool m_requestedBombLocation; ///< true if we already asked where the bomb has been planted
int m_pitch;
static IntervalTimer m_radioSilenceInterval[ 2 ]; ///< one timer for each team
IntervalTimer m_needBackupInterval;
IntervalTimer m_spottedBomberInterval;
IntervalTimer m_scaredInterval;
IntervalTimer m_planInterval;
CountdownTimer m_spottedLooseBombTimer;
CountdownTimer m_heardNoiseTimer;
CountdownTimer m_escortingHostageTimer;
CountdownTimer m_warnSniperTimer;
static CountdownTimer m_encourageTimer; ///< timer to know when we can "encourage" the human player again - shared by all bots
};
inline BotChatterInterface::VerbosityType BotChatterInterface::GetVerbosity( void ) const
{
const char *string = cv_bot_chatter.GetString();
if (string == NULL)
return NORMAL;
if (string[0] == 'm' || string[0] == 'M')
return MINIMAL;
if (string[0] == 'r' || string[0] == 'R')
return RADIO;
if (string[0] == 'o' || string[0] == 'O')
return OFF;
return NORMAL;
}
inline bool BotChatterInterface::IsTalking( void ) const
{
if (m_statementList)
return m_statementList->IsSpeaking();
return false;
}
inline BotStatement *BotChatterInterface::GetStatement( void ) const
{
return m_statementList;
}
inline void BotChatterInterface::Say( const char *phraseName, float lifetime, float delay )
{
BotStatement *say = new BotStatement( this, REPORT_MY_INTENTION, lifetime );
say->AppendPhrase( TheBotPhrases->GetPhrase( phraseName ) );
if (delay > 0.0f)
say->SetStartTime( gpGlobals->curtime + delay );
AddStatement( say );
}
#endif // CS_BOT_CHATTER_H
+427
View File
@@ -0,0 +1,427 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
#include "cbase.h"
#include "cs_gamerules.h"
#include "KeyValues.h"
#include "cs_bot.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//--------------------------------------------------------------------------------------------------------------
/**
* Checks if the bot can hear the event
*/
void CCSBot::OnAudibleEvent( IGameEvent *event, CBasePlayer *player, float range, PriorityType priority, bool isHostile, bool isFootstep, const Vector *actualOrigin )
{
/// @todo Listen to non-player sounds
if (player == NULL)
return;
// don't pay attention to noise that friends make
if (!IsEnemy( player ))
return;
Vector playerOrigin = GetCentroid( player );
Vector myOrigin = GetCentroid( this );
// If the event occurs far from the triggering player, it may override the origin
if ( actualOrigin )
{
playerOrigin = *actualOrigin;
}
// check if noise is close enough for us to hear
const Vector *newNoisePosition = &playerOrigin;
float newNoiseDist = (myOrigin - *newNoisePosition).Length();
if (newNoiseDist < range)
{
// we heard the sound
if ((IsLocalPlayerWatchingMe() && cv_bot_debug.GetInt() == 3) || cv_bot_debug.GetInt() == 4)
{
PrintIfWatched( "Heard noise (%s from %s, pri %s, time %3.1f)\n",
(FStrEq( "weapon_fire", event->GetName() )) ? "Weapon fire " : "",
(player) ? player->GetPlayerName() : "NULL",
(priority == PRIORITY_HIGH) ? "HIGH" : ((priority == PRIORITY_MEDIUM) ? "MEDIUM" : "LOW"),
gpGlobals->curtime );
}
// should we pay attention to it
// if noise timestamp is zero, there is no prior noise
if (m_noiseTimestamp > 0.0f)
{
// only overwrite recent sound if we are louder (closer), or more important - if old noise was long ago, its faded
const float shortTermMemoryTime = 3.0f;
if (gpGlobals->curtime - m_noiseTimestamp < shortTermMemoryTime)
{
// prior noise is more important - ignore new one
if (priority < m_noisePriority)
return;
float oldNoiseDist = (myOrigin - m_noisePosition).Length();
if (newNoiseDist >= oldNoiseDist)
return;
}
}
// find the area in which the noise occured
/// @todo Better handle when noise occurs off the nav mesh
/// @todo Make sure noise area is not through a wall or ceiling from source of noise
/// @todo Change GetNavTravelTime to better deal with NULL destination areas
CNavArea *noiseArea = TheNavMesh->GetNearestNavArea( *newNoisePosition );
if (noiseArea == NULL)
{
PrintIfWatched( " *** Noise occurred off the nav mesh - ignoring!\n" );
return;
}
m_noiseArea = noiseArea;
// remember noise priority
m_noisePriority = priority;
// randomize noise position in the area a bit - hearing isn't very accurate
// the closer the noise is, the more accurate our placement
/// @todo Make sure not to pick a position on the opposite side of ourselves.
const float maxErrorRadius = 400.0f;
const float maxHearingRange = 2000.0f;
float errorRadius = maxErrorRadius * newNoiseDist/maxHearingRange;
m_noisePosition.x = newNoisePosition->x + RandomFloat( -errorRadius, errorRadius );
m_noisePosition.y = newNoisePosition->y + RandomFloat( -errorRadius, errorRadius );
// note the *travel distance* to the noise
m_noiseTravelDistance = GetTravelDistanceToPlayer( (CCSPlayer *)player );
// make sure noise position remains in the same area
m_noiseArea->GetClosestPointOnArea( m_noisePosition, &m_noisePosition );
// note when we heard the noise
m_noiseTimestamp = gpGlobals->curtime;
// if we hear a nearby enemy, become alert
const float nearbyNoiseRange = 1000.0f;
if (m_noiseTravelDistance < nearbyNoiseRange && m_noiseTravelDistance > 0.0f)
{
BecomeAlert();
}
}
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnHEGrenadeDetonate( IGameEvent *event )
{
if ( !IsAlive() )
return;
// don't react to our own events
CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
if ( player == this )
return;
OnAudibleEvent( event, player, 99999.0f, PRIORITY_HIGH, true ); // hegrenade_detonate
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnFlashbangDetonate( IGameEvent *event )
{
if ( !IsAlive() )
return;
// don't react to our own events
CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
if ( player == this )
return;
OnAudibleEvent( event, player, 1000.0f, PRIORITY_LOW, true ); // flashbang_detonate
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnSmokeGrenadeDetonate( IGameEvent *event )
{
if ( !IsAlive() )
return;
// don't react to our own events
CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
if ( player == this )
return;
OnAudibleEvent( event, player, 1000.0f, PRIORITY_LOW, true ); // smokegrenade_detonate
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnGrenadeBounce( IGameEvent *event )
{
if ( !IsAlive() )
return;
// don't react to our own events
CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
if ( player == this )
return;
OnAudibleEvent( event, player, 500.0f, PRIORITY_LOW, true ); // grenade_bounce
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnBulletImpact( IGameEvent *event )
{
if ( !IsAlive() )
return;
// don't react to our own events
CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
if ( player == this )
return;
// Construct an origin for the sound, since it can be far from the originating player
Vector actualOrigin;
actualOrigin.x = event->GetFloat( "x", 0.0f );
actualOrigin.y = event->GetFloat( "y", 0.0f );
actualOrigin.z = event->GetFloat( "z", 0.0f );
/// @todo Ignoring bullet impact events for now - we dont want bots to look directly at them!
//OnAudibleEvent( event, player, 1100.0f, PRIORITY_MEDIUM, true, false, &actualOrigin ); // bullet_impact
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnBreakProp( IGameEvent *event )
{
if ( !IsAlive() )
return;
// don't react to our own events
CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
if ( player == this )
return;
OnAudibleEvent( event, player, 1100.0f, PRIORITY_MEDIUM, true ); // break_prop
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnBreakBreakable( IGameEvent *event )
{
if ( !IsAlive() )
return;
// don't react to our own events
CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
if ( player == this )
return;
OnAudibleEvent( event, player, 1100.0f, PRIORITY_MEDIUM, true ); // break_glass
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnDoorMoving( IGameEvent *event )
{
if ( !IsAlive() )
return;
// don't react to our own events
CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
if ( player == this )
return;
OnAudibleEvent( event, player, 1100.0f, PRIORITY_MEDIUM, false ); // door_moving
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnHostageFollows( IGameEvent *event )
{
if ( !IsAlive() )
return;
// don't react to our own events
CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
if ( player == this )
return;
// player_follows needs a player
if (player == NULL)
return;
// don't pay attention to noise that friends make
if (!IsEnemy( player ))
return;
Vector playerOrigin = GetCentroid( player );
Vector myOrigin = GetCentroid( this );
const float range = 1200.0f;
// this is here so T's not only act on the noise, but look at it, too
if (GetTeamNumber() == TEAM_TERRORIST)
{
// make sure we can hear the noise
if ((playerOrigin - myOrigin).IsLengthGreaterThan( range ))
return;
// tell our teammates that the hostages are being taken
GetChatter()->HostagesBeingTaken();
// only move if we hear them being rescued and can't see any hostages
if (GetGameState()->GetNearestVisibleFreeHostage() == NULL)
{
// since we are guarding the hostages, presumably we know where they are
// if we're close enough to "hear" this event, either go to where the event occured,
// or head for an escape zone to head them off
if (GetTask() != CCSBot::GUARD_HOSTAGE_RESCUE_ZONE)
{
//const float headOffChance = 33.3f;
if (true) // || RandomFloat( 0, 100 ) < headOffChance)
{
// head them off at a rescue zone
if (GuardRandomZone())
{
SetTask( CCSBot::GUARD_HOSTAGE_RESCUE_ZONE );
SetDisposition( CCSBot::OPPORTUNITY_FIRE );
PrintIfWatched( "Trying to beat them to an escape zone!\n" );
}
}
else
{
SetTask( SEEK_AND_DESTROY );
StandUp();
Run();
MoveTo( playerOrigin, FASTEST_ROUTE );
}
}
}
}
else
{
// CT's don't care about this noise
return;
}
OnAudibleEvent( event, player, range, PRIORITY_MEDIUM, false ); // hostage_follows
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnRoundEnd( IGameEvent *event )
{
// Morale adjustments happen even for dead players
int winner = event->GetInt( "winner" );
switch ( winner )
{
case WINNER_TER:
if (GetTeamNumber() == TEAM_CT)
{
DecreaseMorale();
}
else
{
IncreaseMorale();
}
break;
case WINNER_CT:
if (GetTeamNumber() == TEAM_CT)
{
IncreaseMorale();
}
else
{
DecreaseMorale();
}
break;
default:
break;
}
m_gameState.OnRoundEnd( event );
if ( !IsAlive() )
return;
if ( event->GetInt( "winner" ) == WINNER_TER )
{
if (GetTeamNumber() == TEAM_TERRORIST)
GetChatter()->CelebrateWin();
}
else if ( event->GetInt( "winner" ) == WINNER_CT )
{
if (GetTeamNumber() == TEAM_CT)
GetChatter()->CelebrateWin();
}
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnRoundStart( IGameEvent *event )
{
m_gameState.OnRoundStart( event );
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnHostageRescuedAll( IGameEvent *event )
{
m_gameState.OnHostageRescuedAll( event );
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnNavBlocked( IGameEvent *event )
{
if ( event->GetBool( "blocked" ) )
{
unsigned int areaID = event->GetInt( "area" );
if ( areaID )
{
// An area was blocked off. Reset our path if it has this area on it.
for( int i=0; i<m_pathLength; ++i )
{
const ConnectInfo *info = &m_path[ i ];
if ( info->area && info->area->GetID() == areaID )
{
DestroyPath();
return;
}
}
}
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Invoked when bot enters a nav area
*/
void CCSBot::OnEnteredNavArea( CNavArea *newArea )
{
// assume that we "clear" an area of enemies when we enter it
newArea->SetClearedTimestamp( GetTeamNumber()-1 );
// if we just entered a 'stop' area, set the flag
if ( newArea->GetAttributes() & NAV_MESH_STOP )
{
m_isStopping = true;
}
/// @todo Flag these areas as spawn areas during load
if (IsAtEnemySpawn())
{
m_hasVisitedEnemySpawn = true;
}
}
@@ -0,0 +1,158 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
#include "cbase.h"
#include "cs_gamerules.h"
#include "KeyValues.h"
#include "cs_bot.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnBombPickedUp( IGameEvent *event )
{
if ( !IsAlive() )
return;
// don't react to our own events
CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
if ( player == this )
return;
if (GetTeamNumber() == TEAM_CT && player)
{
// check if we're close enough to hear it
const float bombPickupHearRangeSq = 1000.0f * 1000.0f;
Vector myOrigin = GetCentroid( this );
if ((myOrigin - player->GetAbsOrigin()).LengthSqr() < bombPickupHearRangeSq)
{
GetChatter()->TheyPickedUpTheBomb();
GetGameState()->UpdateBomber( player->GetAbsOrigin() );
}
}
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnBombPlanted( IGameEvent *event )
{
m_gameState.OnBombPlanted( event );
if ( !IsAlive() )
return;
// don't react to our own events
CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
if ( player == this )
return;
// if we're a TEAM_CT, forget what we're doing and go after the bomb
if (GetTeamNumber() == TEAM_CT)
{
Idle();
}
// if we are following someone, stop following
if (IsFollowing())
{
StopFollowing();
Idle();
}
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnBombBeep( IGameEvent *event )
{
if ( !IsAlive() )
return;
// don't react to our own events
CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
if ( player == this )
return;
CBaseEntity *entity = UTIL_EntityByIndex( event->GetInt( "entindex" ) );
Vector myOrigin = GetCentroid( this );
// if we don't know where the bomb is, but heard it beep, we've discovered it
if (GetGameState()->IsPlantedBombLocationKnown() == false && entity)
{
// check if we're close enough to hear it
const float bombBeepHearRangeSq = 1500.0f * 1500.0f;
if ((myOrigin - entity->GetAbsOrigin()).LengthSqr() < bombBeepHearRangeSq)
{
// radio the news to our team
if (GetTeamNumber() == TEAM_CT && GetGameState()->GetPlantedBombsite() == CSGameState::UNKNOWN)
{
const CCSBotManager::Zone *zone = TheCSBots()->GetZone( entity->GetAbsOrigin() );
if (zone)
GetChatter()->FoundPlantedBomb( zone->m_index );
}
// remember where the bomb is
GetGameState()->UpdatePlantedBomb( entity->GetAbsOrigin() );
}
}
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnBombDefuseBegin( IGameEvent *event )
{
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnBombDefused( IGameEvent *event )
{
m_gameState.OnBombDefused( event );
if ( !IsAlive() )
return;
// don't react to our own events
CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
if ( player == this )
return;
if (GetTeamNumber() == TEAM_CT)
{
if (TheCSBots()->GetBombTimeLeft() < 2.0f)
GetChatter()->Say( "BarelyDefused" );
}
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnBombDefuseAbort( IGameEvent *event )
{
if ( !IsAlive() )
return;
// don't react to our own events
CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
if ( player == this )
return;
PrintIfWatched( "BOMB DEFUSE ABORTED\n" );
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnBombExploded( IGameEvent *event )
{
m_gameState.OnBombExploded( event );
}
@@ -0,0 +1,227 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
#include "cbase.h"
#include "cs_gamerules.h"
#include "KeyValues.h"
#include "cs_bot.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnPlayerDeath( IGameEvent *event )
{
if ( !IsAlive() )
return;
// don't react to our own events
CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
if ( player == this )
return;
Vector playerOrigin = (player) ? GetCentroid( player ) : Vector( 0, 0, 0 );
CBasePlayer *other = UTIL_PlayerByUserId( event->GetInt( "attacker" ) );
CBasePlayer *victim = player;
CBasePlayer *killer = (other && other->IsPlayer()) ? static_cast<CBasePlayer *>( other ) : NULL;
// if the human player died in the single player game, tell the team
if (CSGameRules()->IsCareer() && victim && !victim->IsBot() && victim->GetTeamNumber() == GetTeamNumber())
{
GetChatter()->Say( "CommanderDown", 20.0f );
}
// keep track of the last player we killed
if (killer == this)
{
m_lastVictimID = victim ? victim->entindex() : 0;
}
// react to teammate death
if (victim && victim->GetTeamNumber() == GetTeamNumber())
{
// note time of death
m_friendDeathTimestamp = gpGlobals->curtime;
// chastise friendly fire from humans
if (killer && !killer->IsBot() && killer->GetTeamNumber() == GetTeamNumber() && killer != this)
{
GetChatter()->KilledFriend();
}
if (IsAttacking())
{
if (GetTimeSinceLastSawEnemy() > 0.4f)
{
PrintIfWatched( "Rethinking my attack due to teammate death\n" );
// allow us to sneak past windows, doors, etc
IgnoreEnemies( 1.0f );
// move to last known position of enemy - this could cause us to flank if
// the danger has changed due to our teammate's recent death
SetTask( MOVE_TO_LAST_KNOWN_ENEMY_POSITION, GetBotEnemy() );
MoveTo( GetLastKnownEnemyPosition() );
return;
}
}
else // not attacking
{
//
// If we just saw a nearby friend die, and we haven't yet acquired an enemy
// automatically acquire our dead friend's killer
//
if (GetDisposition() == ENGAGE_AND_INVESTIGATE || GetDisposition() == OPPORTUNITY_FIRE)
{
CBasePlayer *other = UTIL_PlayerByUserId( event->GetInt( "attacker" ) );
// check that attacker is an enemy (for friendly fire, etc)
if (other && other->IsPlayer())
{
CCSPlayer *killer = static_cast<CCSPlayer *>( other );
if (killer->GetTeamNumber() != GetTeamNumber())
{
// check if we saw our friend die - dont check FOV - assume we're aware of our surroundings in combat
// snipers stay put
if (!IsSniper() && IsVisible( playerOrigin ))
{
// people are dying - we should hurry
Hurry( RandomFloat( 10.0f, 15.0f ) );
// if we're hiding with only our knife, be a little more cautious
const float knifeAmbushChance = 50.0f;
if (!IsHiding() || !IsUsingKnife() || RandomFloat( 0, 100 ) < knifeAmbushChance)
{
PrintIfWatched( "Attacking our friend's killer!\n" );
Attack( killer );
return;
}
}
// if friend was far away and we haven't seen an enemy in awhile, go to where our friend was killed
const float longHidingTime = 20.0f;
if (IsHunting() || IsInvestigatingNoise() || (IsHiding() && GetTask() != FOLLOW && GetHidingTime() > longHidingTime))
{
const float someTime = 10.0f;
const float farAway = 750.0f;
if (GetTimeSinceLastSawEnemy() > someTime && (playerOrigin - GetAbsOrigin()).IsLengthGreaterThan( farAway ))
{
PrintIfWatched( "Checking out where our friend was killed\n" );
MoveTo( playerOrigin, FASTEST_ROUTE );
return;
}
}
}
}
}
}
}
else // an enemy was killed
{
// forget our current noise - it may have come from the now dead enemy
ForgetNoise();
if (killer && killer->GetTeamNumber() == GetTeamNumber())
{
// only chatter about enemy kills if we see them occur, and they were the last one we see
if (GetNearbyEnemyCount() <= 1)
{
// report if number of enemies left is few and we killed the last one we saw locally
GetChatter()->EnemiesRemaining();
Vector victimOrigin = (victim) ? GetCentroid( victim ) : Vector( 0, 0, 0 );
if (IsVisible( victimOrigin, CHECK_FOV ))
{
// congratulate teammates on their kills
if (killer && killer != this)
{
float delay = RandomFloat( 2.0f, 3.0f );
if (killer->IsBot())
{
if (RandomFloat( 0.0f, 100.0f ) < 40.0f)
GetChatter()->Say( "NiceShot", 3.0f, delay );
}
else
{
// humans get the honorific
if (CSGameRules()->IsCareer())
GetChatter()->Say( "NiceShotCommander", 3.0f, delay );
else
GetChatter()->Say( "NiceShotSir", 3.0f, delay );
}
}
}
}
}
}
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnPlayerRadio( IGameEvent *event )
{
if ( !IsAlive() )
return;
// don't react to our own events
CCSPlayer *player = ToCSPlayer( UTIL_PlayerByUserId( event->GetInt( "userid" ) ) );
if ( player == this )
return;
//
// Process radio events from our team
//
if (player && player->GetTeamNumber() == GetTeamNumber() )
{
/// @todo Distinguish between radio commands and responses
RadioType radioEvent = (RadioType)event->GetInt( "slot" );
if (radioEvent != RADIO_INVALID && radioEvent != RADIO_AFFIRMATIVE && radioEvent != RADIO_NEGATIVE && radioEvent != RADIO_REPORTING_IN)
{
m_lastRadioCommand = radioEvent;
m_lastRadioRecievedTimestamp = gpGlobals->curtime;
m_radioSubject = player;
m_radioPosition = GetCentroid( player );
}
}
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnPlayerFallDamage( IGameEvent *event )
{
if ( !IsAlive() )
return;
// don't react to our own events
CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
if ( player == this )
return;
OnAudibleEvent( event, player, 1100.0f, PRIORITY_LOW, false ); // player_falldamage
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnPlayerFootstep( IGameEvent *event )
{
if ( !IsAlive() )
return;
// don't react to our own events
CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
if ( player == this )
return;
OnAudibleEvent( event, player, 1100.0f, PRIORITY_LOW, false, IS_FOOTSTEP ); // player_footstep
}
@@ -0,0 +1,167 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
#include "cbase.h"
#include "cs_gamerules.h"
#include "KeyValues.h"
#include "cs_bot.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnWeaponFire( IGameEvent *event )
{
if ( !IsAlive() )
return;
// don't react to our own events
CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
if ( player == this )
return;
// for knife fighting - if our victim is attacking or reloading, rush him
/// @todo Propagate events into active state
if (GetEnemy() == player && IsUsingKnife())
{
ForceRun( 5.0f );
}
const float ShortRange = 1000.0f;
const float NormalRange = 2000.0f;
float range;
/// @todo Check weapon type (knives are pretty quiet)
/// @todo Use actual volume, account for silencers, etc.
CWeaponCSBase *weapon = (CWeaponCSBase *)((player)?player->GetActiveWeapon():NULL);
if (weapon == NULL)
return;
switch( weapon->GetWeaponID() )
{
// silent "firing"
case WEAPON_HEGRENADE:
case WEAPON_SMOKEGRENADE:
case WEAPON_FLASHBANG:
case WEAPON_SHIELDGUN:
case WEAPON_C4:
return;
// quiet
case WEAPON_KNIFE:
case WEAPON_TMP:
range = ShortRange;
break;
// M4A1 - check for silencer
case WEAPON_M4A1:
{
if (weapon->IsSilenced())
{
range = ShortRange;
}
else
{
range = NormalRange;
}
break;
}
// USP - check for silencer
case WEAPON_USP:
{
if (weapon->IsSilenced())
{
range = ShortRange;
}
else
{
range = NormalRange;
}
break;
}
// loud
case WEAPON_AWP:
range = 99999.0f;
break;
// normal
default:
range = NormalRange;
break;
}
OnAudibleEvent( event, player, range, PRIORITY_HIGH, true ); // weapon_fire
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnWeaponFireOnEmpty( IGameEvent *event )
{
if ( !IsAlive() )
return;
// don't react to our own events
CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
if ( player == this )
return;
// for knife fighting - if our victim is attacking or reloading, rush him
/// @todo Propagate events into active state
if (GetEnemy() == player && IsUsingKnife())
{
ForceRun( 5.0f );
}
OnAudibleEvent( event, player, 1100.0f, PRIORITY_LOW, false ); // weapon_fire_on_empty
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnWeaponReload( IGameEvent *event )
{
if ( !IsAlive() )
return;
// don't react to our own events
CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
if ( player == this )
return;
// for knife fighting - if our victim is attacking or reloading, rush him
/// @todo Propagate events into active state
if (GetEnemy() == player && IsUsingKnife())
{
ForceRun( 5.0f );
}
OnAudibleEvent( event, player, 1100.0f, PRIORITY_LOW, false ); // weapon_reload
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnWeaponZoom( IGameEvent *event )
{
if ( !IsAlive() )
return;
// don't react to our own events
CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
if ( player == this )
return;
OnAudibleEvent( event, player, 1100.0f, PRIORITY_LOW, false ); // weapon_zoom
}
+359
View File
@@ -0,0 +1,359 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
#include "cbase.h"
#include "cs_bot.h"
#include "cs_shareddefs.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
#pragma warning( disable : 4355 ) // warning 'this' used in base member initializer list - we're using it safely
//--------------------------------------------------------------------------------------------------------------
static void PrefixChanged( IConVar *c, const char *oldPrefix, float flOldValue )
{
if ( TheCSBots() && TheCSBots()->IsServerActive() )
{
for( int i = 1; i <= gpGlobals->maxClients; ++i )
{
CBasePlayer *player = static_cast<CBasePlayer *>( UTIL_PlayerByIndex( i ) );
if ( !player )
continue;
if ( !player->IsBot() || !IsEntityValid( player ) )
continue;
CCSBot *bot = dynamic_cast< CCSBot * >( player );
if ( !bot )
continue;
// set the bot's name
char botName[MAX_PLAYER_NAME_LENGTH];
UTIL_ConstructBotNetName( botName, MAX_PLAYER_NAME_LENGTH, bot->GetProfile() );
engine->SetFakeClientConVarValue( bot->edict(), "name", botName );
}
}
}
ConVar cv_bot_traceview( "bot_traceview", "0", FCVAR_REPLICATED | FCVAR_CHEAT, "For internal testing purposes." );
ConVar cv_bot_stop( "bot_stop", "0", FCVAR_REPLICATED | FCVAR_CHEAT, "If nonzero, immediately stops all bot processing." );
ConVar cv_bot_show_nav( "bot_show_nav", "0", FCVAR_REPLICATED | FCVAR_CHEAT, "For internal testing purposes." );
ConVar cv_bot_walk( "bot_walk", "0", FCVAR_REPLICATED, "If nonzero, bots can only walk, not run." );
ConVar cv_bot_difficulty( "bot_difficulty", "1", FCVAR_REPLICATED, "Defines the skill of bots joining the game. Values are: 0=easy, 1=normal, 2=hard, 3=expert." );
ConVar cv_bot_debug( "bot_debug", "0", FCVAR_REPLICATED | FCVAR_CHEAT, "For internal testing purposes." );
ConVar cv_bot_debug_target( "bot_debug_target", "0", FCVAR_REPLICATED | FCVAR_CHEAT, "For internal testing purposes." );
ConVar cv_bot_quota( "bot_quota", "0", FCVAR_REPLICATED | FCVAR_NOTIFY, "Determines the total number of bots in the game." );
ConVar cv_bot_quota_mode( "bot_quota_mode", "normal", FCVAR_REPLICATED, "Determines the type of quota.\nAllowed values: 'normal', 'fill', and 'match'.\nIf 'fill', the server will adjust bots to keep N players in the game, where N is bot_quota.\nIf 'match', the server will maintain a 1:N ratio of humans to bots, where N is bot_quota." );
ConVar cv_bot_prefix( "bot_prefix", "", FCVAR_REPLICATED, "This string is prefixed to the name of all bots that join the game.\n<difficulty> will be replaced with the bot's difficulty.\n<weaponclass> will be replaced with the bot's desired weapon class.\n<skill> will be replaced with a 0-100 representation of the bot's skill.", PrefixChanged );
ConVar cv_bot_allow_rogues( "bot_allow_rogues", "1", FCVAR_REPLICATED, "If nonzero, bots may occasionally go 'rogue'. Rogue bots do not obey radio commands, nor pursue scenario goals." );
ConVar cv_bot_allow_pistols( "bot_allow_pistols", "1", FCVAR_REPLICATED, "If nonzero, bots may use pistols." );
ConVar cv_bot_allow_shotguns( "bot_allow_shotguns", "1", FCVAR_REPLICATED, "If nonzero, bots may use shotguns." );
ConVar cv_bot_allow_sub_machine_guns( "bot_allow_sub_machine_guns", "1", FCVAR_REPLICATED, "If nonzero, bots may use sub-machine guns." );
ConVar cv_bot_allow_rifles( "bot_allow_rifles", "1", FCVAR_REPLICATED, "If nonzero, bots may use rifles." );
ConVar cv_bot_allow_machine_guns( "bot_allow_machine_guns", "1", FCVAR_REPLICATED, "If nonzero, bots may use the machine gun." );
ConVar cv_bot_allow_grenades( "bot_allow_grenades", "1", FCVAR_REPLICATED, "If nonzero, bots may use grenades." );
ConVar cv_bot_allow_snipers( "bot_allow_snipers", "1", FCVAR_REPLICATED, "If nonzero, bots may use sniper rifles." );
#ifdef CS_SHIELD_ENABLED
ConVar cv_bot_allow_shield( "bot_allow_shield", "1", FCVAR_REPLICATED );
#endif // CS_SHIELD_ENABLED
ConVar cv_bot_join_team( "bot_join_team", "any", FCVAR_REPLICATED, "Determines the team bots will join into. Allowed values: 'any', 'T', or 'CT'." );
ConVar cv_bot_join_after_player( "bot_join_after_player", "1", FCVAR_REPLICATED, "If nonzero, bots wait until a player joins before entering the game." );
ConVar cv_bot_auto_vacate( "bot_auto_vacate", "1", FCVAR_REPLICATED, "If nonzero, bots will automatically leave to make room for human players." );
ConVar cv_bot_zombie( "bot_zombie", "0", FCVAR_REPLICATED | FCVAR_CHEAT, "If nonzero, bots will stay in idle mode and not attack." );
ConVar cv_bot_defer_to_human( "bot_defer_to_human", "0", FCVAR_REPLICATED, "If nonzero and there is a human on the team, the bots will not do the scenario tasks." );
ConVar cv_bot_chatter( "bot_chatter", "normal", FCVAR_REPLICATED, "Control how bots talk. Allowed values: 'off', 'radio', 'minimal', or 'normal'." );
ConVar cv_bot_profile_db( "bot_profile_db", "BotProfile.db", FCVAR_REPLICATED, "The filename from which bot profiles will be read." );
ConVar cv_bot_dont_shoot( "bot_dont_shoot", "0", FCVAR_REPLICATED | FCVAR_CHEAT, "If nonzero, bots will not fire weapons (for debugging)." );
ConVar cv_bot_eco_limit( "bot_eco_limit", "2000", FCVAR_REPLICATED, "If nonzero, bots will not buy if their money falls below this amount." );
ConVar cv_bot_auto_follow( "bot_auto_follow", "0", FCVAR_REPLICATED, "If nonzero, bots with high co-op may automatically follow a nearby human player." );
ConVar cv_bot_flipout( "bot_flipout", "0", FCVAR_REPLICATED | FCVAR_CHEAT, "If nonzero, bots use no CPU for AI. Instead, they run around randomly." );
extern void FinishClientPutInServer( CCSPlayer *pPlayer );
//--------------------------------------------------------------------------------------------------------------
// Engine callback for custom server commands
void Bot_ServerCommand( void )
{
}
//--------------------------------------------------------------------------------------------------------------
/**
* Constructor
*/
CCSBot::CCSBot( void ) : m_chatter( this ), m_gameState( this )
{
m_hasJoined = false;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Destructor
*/
CCSBot::~CCSBot()
{
}
//--------------------------------------------------------------------------------------------------------------
/**
* Prepare bot for action
*/
bool CCSBot::Initialize( const BotProfile *profile, int team )
{
// extend
BaseClass::Initialize( profile, team );
// CS bot initialization
m_diedLastRound = false;
m_morale = POSITIVE; // starting a new round makes everyone a little happy
m_combatRange = RandomFloat( 325.0f, 425.0f );
// set initial safe time guess for this map
m_safeTime = 15.0f + 5.0f * GetProfile()->GetAggression();
m_name[0] = '\000';
ResetValues();
m_desiredTeam = team;
if (GetTeamNumber() == 0)
{
HandleCommand_JoinTeam( m_desiredTeam );
int desiredClass = GetProfile()->GetSkin();
if ( m_desiredTeam == TEAM_CT && desiredClass )
{
desiredClass = FIRST_CT_CLASS + desiredClass - 1;
}
else if ( m_desiredTeam == TEAM_TERRORIST && desiredClass )
{
desiredClass = FIRST_T_CLASS + desiredClass - 1;
}
HandleCommand_JoinClass( desiredClass );
}
return true;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Reset internal data to initial state
*/
void CCSBot::ResetValues( void )
{
m_chatter.Reset();
m_gameState.Reset();
m_avoid = NULL;
m_avoidTimestamp = 0.0f;
m_hurryTimer.Invalidate();
m_alertTimer.Invalidate();
m_sneakTimer.Invalidate();
m_noiseBendTimer.Invalidate();
m_bendNoisePositionValid = false;
m_isStuck = false;
m_stuckTimestamp = 0.0f;
m_wiggleTimer.Invalidate();
m_stuckJumpTimer.Invalidate();
m_pathLength = 0;
m_pathIndex = 0;
m_areaEnteredTimestamp = 0.0f;
m_currentArea = NULL;
m_lastKnownArea = NULL;
m_isStopping = false;
m_avoidFriendTimer.Invalidate();
m_isFriendInTheWay = false;
m_isWaitingBehindFriend = false;
m_isAvoidingGrenade.Invalidate();
StopPanicking();
m_disposition = ENGAGE_AND_INVESTIGATE;
m_enemy = NULL;
m_grenadeTossState = NOT_THROWING;
m_initialEncounterArea = NULL;
m_wasSafe = true;
m_nearbyEnemyCount = 0;
m_enemyPlace = 0;
m_nearbyFriendCount = 0;
m_closestVisibleFriend = NULL;
m_closestVisibleHumanFriend = NULL;
for( int w=0; w<MAX_PLAYERS; ++w )
{
m_watchInfo[w].timestamp = 0.0f;
m_watchInfo[w].isEnemy = false;
m_playerTravelDistance[ w ] = -1.0f;
}
// randomly offset each bot's timer to spread computation out
m_updateTravelDistanceTimer.Start( RandomFloat( 0.0f, 0.9f ) );
m_travelDistancePhase = 0;
m_isEnemyVisible = false;
m_visibleEnemyParts = NONE;
m_lastSawEnemyTimestamp = -999.9f;
m_firstSawEnemyTimestamp = 0.0f;
m_currentEnemyAcquireTimestamp = 0.0f;
m_isLastEnemyDead = true;
m_attacker = NULL;
m_attackedTimestamp = 0.0f;
m_enemyDeathTimestamp = 0.0f;
m_friendDeathTimestamp = 0.0f;
m_lastVictimID = 0;
m_isAimingAtEnemy = false;
m_fireWeaponTimestamp = 0.0f;
m_equipTimer.Invalidate();
m_zoomTimer.Invalidate();
m_isFollowing = false;
m_leader = NULL;
m_followTimestamp = 0.0f;
m_allowAutoFollowTime = 0.0f;
m_enemyQueueIndex = 0;
m_enemyQueueCount = 0;
m_enemyQueueAttendIndex = 0;
m_bomber = NULL;
m_isEnemySniperVisible = false;
m_sawEnemySniperTimer.Invalidate();
m_lookAroundStateTimestamp = 0.0f;
m_inhibitLookAroundTimestamp = 0.0f;
m_lookPitch = 0.0f;
m_lookPitchVel = 0.0f;
m_lookYaw = 0.0f;
m_lookYawVel = 0.0f;
m_aimOffsetTimestamp = 0.0f;
m_aimSpreadTimestamp = 0.0f;
m_lookAtSpotState = NOT_LOOKING_AT_SPOT;
for( int p=0; p<MAX_PLAYERS; ++p )
{
m_partInfo[p].m_validFrame = 0;
}
m_spotEncounter = NULL;
m_spotCheckTimestamp = 0.0f;
m_peripheralTimestamp = 0.0f;
m_avgVelIndex = 0;
m_avgVelCount = 0;
m_lastOrigin = GetCentroid( this );
m_lastRadioCommand = RADIO_INVALID;
m_lastRadioRecievedTimestamp = 0.0f;
m_lastRadioSentTimestamp = 0.0f;
m_radioSubject = NULL;
m_voiceEndTimestamp = 0.0f;
m_hostageEscortCount = 0;
m_hostageEscortCountTimestamp = 0.0f;
m_noisePosition = Vector( 0, 0, 0 );
m_noiseTimestamp = 0.0f;
m_stateTimestamp = 0.0f;
m_task = SEEK_AND_DESTROY;
m_taskEntity = NULL;
m_approachPointCount = 0;
m_approachPointViewPosition.x = 99999999999.9f;
m_approachPointViewPosition.y = 0.0f;
m_approachPointViewPosition.z = 0.0f;
m_checkedHidingSpotCount = 0;
StandUp();
Run();
m_mustRunTimer.Invalidate();
m_waitTimer.Invalidate();
m_pathLadder = NULL;
m_repathTimer.Invalidate();
m_huntState.ClearHuntArea();
m_hasVisitedEnemySpawn = false;
m_stillTimer.Invalidate();
// adjust morale - if we died, our morale decreased,
// but if we live, no adjustement (round win/loss also adjusts morale)
if (m_diedLastRound)
DecreaseMorale();
m_diedLastRound = false;
// IsRogue() randomly changes this
m_isRogue = false;
m_surpriseTimer.Invalidate();
// even though these are EHANDLEs, they need to be NULL-ed
m_goalEntity = NULL;
m_avoid = NULL;
m_enemy = NULL;
for ( int i=0; i<MAX_ENEMY_QUEUE; ++i )
{
m_enemyQueue[i].player = NULL;
m_enemyQueue[i].isReloading = false;
m_enemyQueue[i].isProtectedByShield = false;
}
// start in idle state
m_isOpeningDoor = false;
StopAttacking();
Idle();
}
//--------------------------------------------------------------------------------------------------------------
/**
* Called when bot is placed in map, and when bots are reset after a round ends.
* NOTE: For some reason, this can be called twice when a bot is added.
*/
void CCSBot::Spawn( void )
{
// do the normal player spawn process
BaseClass::Spawn();
ResetValues();
V_strcpy_safe( m_name, GetPlayerName() );
Buy();
}
+230
View File
@@ -0,0 +1,230 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
#include "cbase.h"
#include "cs_bot.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//--------------------------------------------------------------------------------------------------------------
bool CCSBot::IsNoiseHeard( void ) const
{
if (m_noiseTimestamp <= 0.0f)
return false;
// primitive reaction time simulation - cannot "hear" noise until reaction time has elapsed
if (gpGlobals->curtime - m_noiseTimestamp >= GetProfile()->GetReactionTime())
return true;
return false;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Listen for enemy noises, and determine if we should react to them.
* Returns true if heard a noise and should move to investigate.
*/
bool CCSBot::HeardInterestingNoise( void )
{
if (IsBlind())
return false;
// don't investigate noises during safe time
if (!IsWellPastSafe())
return false;
// if our disposition is not to investigate, dont investigate
if (GetDisposition() != ENGAGE_AND_INVESTIGATE)
return false;
// listen for enemy noises
if (IsNoiseHeard())
{
// if we are hiding, only react to noises very nearby, depending on how aggressive we are
if (IsAtHidingSpot() && GetNoiseRange() > 100.0f + 400.0f * GetProfile()->GetAggression())
return false;
// chance of investigating is inversely proportional to distance
const float maxNoiseDist = 3000.0f;
float chance = 100.0f * (1.0f - (GetNoiseRange()/maxNoiseDist));
// modify chance by number of friends remaining
// if we have lots of friends, presumably one of them is closer and will check it out
if (GetFriendsRemaining() >= 3)
{
float friendFactor = 5.0f * GetFriendsRemaining();
if (friendFactor > 50.0f)
friendFactor = 50.0f;
chance -= friendFactor;
}
if (RandomFloat( 0.0f, 100.0f ) <= chance)
{
return true;
}
}
return false;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return true if we hear nearby threatening enemy gunfire within given range
* -1 == infinite range
*/
bool CCSBot::CanHearNearbyEnemyGunfire( float range ) const
{
Vector myOrigin = GetCentroid( this );
// only attend to noise if it just happened
if (gpGlobals->curtime - m_noiseTimestamp > 0.5f)
return false;
// gunfire is high priority
if (m_noisePriority < PRIORITY_HIGH)
return false;
// check noise range
if (range > 0.0f && (myOrigin - m_noisePosition).IsLengthGreaterThan( range ))
return false;
// if we dont have line of sight, it's not threatening (cant get shot)
if (!CanSeeNoisePosition())
return false;
if (IsAttacking() && m_enemy != NULL && GetTimeSinceLastSawEnemy() < 1.0f)
{
// gunfire is only threatening if it is closer than our current enemy
float gunfireDistSq = (m_noisePosition - myOrigin).LengthSqr();
float enemyDistSq = (GetCentroid( m_enemy ) - myOrigin).LengthSqr();
const float muchCloserSq = 100.0f * 100.0f;
if (gunfireDistSq > enemyDistSq - muchCloserSq)
return false;
}
return true;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return true if we directly see where we think the noise came from
* NOTE: Dont check FOV, since this is used to determine if we should turn our head to look at the noise
* NOTE: Dont use IsVisible(), because smoke shouldnt cause us to not look toward noises
*/
bool CCSBot::CanSeeNoisePosition( void ) const
{
trace_t result;
CTraceFilterNoNPCsOrPlayer traceFilter( this, COLLISION_GROUP_NONE );
UTIL_TraceLine( EyePositionConst(), m_noisePosition + Vector( 0, 0, HalfHumanHeight ), MASK_VISIBLE_AND_NPCS, &traceFilter, &result );
if (result.fraction == 1.0f)
{
// we can see the source of the noise
return true;
}
return false;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return true if we decided to look towards the most recent noise source
* Assumes m_noisePosition is valid.
*/
bool CCSBot::UpdateLookAtNoise( void )
{
// make sure a noise exists
if (!IsNoiseHeard())
{
return false;
}
Vector spot;
// if we have clear line of sight to noise position, look directly at it
if (CanSeeNoisePosition())
{
/// @todo adjust noise Z to keep consistent with current height while fighting
spot = m_noisePosition + Vector( 0, 0, HalfHumanHeight );
// since we can see the noise spot, forget about it
ForgetNoise();
}
else
{
// line of sight is blocked, bend it
// the bending algorithm is very expensive, throttle how often it is done
if (m_noiseBendTimer.IsElapsed())
{
const float noiseBendLOSInterval = RandomFloat( 0.2f, 0.3f );
m_noiseBendTimer.Start( noiseBendLOSInterval );
// line of sight is blocked, bend it
if (BendLineOfSight( EyePosition(), m_noisePosition, &spot ) == false)
{
m_bendNoisePositionValid = false;
return false;
}
m_bentNoisePosition = spot;
m_bendNoisePositionValid = true;
}
else if (m_bendNoisePositionValid)
{
// use result of prior bend computation
spot = m_bentNoisePosition;
}
else
{
// prior bend failed
return false;
}
}
// it's always important to look at enemy noises, because they come from ... enemies!
PriorityType pri = PRIORITY_HIGH;
// look longer if we're hiding
if (IsAtHidingSpot())
{
// if there is only one enemy left, look for a long time
if (GetEnemiesRemaining() == 1)
{
SetLookAt( "Noise", spot, pri, RandomFloat( 5.0f, 15.0f ), true );
}
else
{
SetLookAt( "Noise", spot, pri, RandomFloat( 3.0f, 5.0f ), true );
}
}
else
{
const float closeRange = 500.0f;
if (GetNoiseRange() < closeRange)
{
// look at nearby enemy noises for a longer time
SetLookAt( "Noise", spot, pri, RandomFloat( 3.0f, 5.0f ), true );
}
else
{
SetLookAt( "Noise", spot, pri, RandomFloat( 1.0f, 2.0f ), true );
}
}
return true;
}
File diff suppressed because it is too large Load Diff
+400
View File
@@ -0,0 +1,400 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
#ifndef CS_CONTROL_H
#define CS_CONTROL_H
#include "bot_manager.h"
#include "nav_area.h"
#include "bot_util.h"
#include "bot_profile.h"
#include "cs_shareddefs.h"
#include "cs_player.h"
extern ConVar friendlyfire;
class CBasePlayerWeapon;
/**
* Given one team, return the other
*/
inline int OtherTeam( int team )
{
return (team == TEAM_TERRORIST) ? TEAM_CT : TEAM_TERRORIST;
}
class CCSBotManager;
// accessor for CS-specific bots
inline CCSBotManager *TheCSBots( void )
{
return reinterpret_cast< CCSBotManager * >( TheBots );
}
//--------------------------------------------------------------------------------------------------------------
class BotEventInterface : public IGameEventListener2
{
public:
virtual const char *GetEventName( void ) const = 0;
};
//--------------------------------------------------------------------------------------------------------------
//--------------------------------------------------------------------------------------------------------------
/**
* Macro to set up an OnEventClass() in TheCSBots.
*/
#define DECLARE_BOTMANAGER_EVENT_LISTENER( BotManagerSingleton, EventClass, EventName ) \
public: \
virtual void On##EventClass( IGameEvent *data ); \
private: \
class EventClass##Event : public BotEventInterface \
{ \
bool m_enabled; \
public: \
EventClass##Event( void ) \
{ \
gameeventmanager->AddListener( this, #EventName, true ); \
m_enabled = true; \
} \
~EventClass##Event( void ) \
{ \
if ( m_enabled ) gameeventmanager->RemoveListener( this ); \
} \
virtual const char *GetEventName( void ) const \
{ \
return #EventName; \
} \
void Enable( bool enable ) \
{ \
m_enabled = enable; \
if ( enable ) \
gameeventmanager->AddListener( this, #EventName, true ); \
else \
gameeventmanager->RemoveListener( this ); \
} \
bool IsEnabled( void ) const { return m_enabled; } \
void FireGameEvent( IGameEvent *event ) \
{ \
BotManagerSingleton()->On##EventClass( event ); \
} \
}; \
EventClass##Event m_##EventClass##Event;
//--------------------------------------------------------------------------------------------------------------
#define DECLARE_CSBOTMANAGER_EVENT_LISTENER( EventClass, EventName ) DECLARE_BOTMANAGER_EVENT_LISTENER( TheCSBots, EventClass, EventName )
//--------------------------------------------------------------------------------------------------------------
/**
* Macro to propogate an event from the bot manager to all bots
*/
#define CCSBOTMANAGER_ITERATE_BOTS( Callback, arg1 ) \
{ \
for ( int idx = 1; idx <= gpGlobals->maxClients; ++idx ) \
{ \
CBasePlayer *player = UTIL_PlayerByIndex( idx ); \
if (player == NULL) continue; \
if (!player->IsBot()) continue; \
CCSBot *bot = dynamic_cast< CCSBot * >(player); \
if ( !bot ) continue; \
bot->Callback( arg1 ); \
} \
}
//--------------------------------------------------------------------------------------------------------------
//
// The manager for Counter-Strike specific bots
//
class CCSBotManager : public CBotManager
{
public:
CCSBotManager();
virtual CBasePlayer *AllocateBotEntity( void ); ///< factory method to allocate the appropriate entity for the bot
virtual void ClientDisconnect( CBaseEntity *entity );
virtual bool ClientCommand( CBasePlayer *player, const CCommand &args );
virtual void ServerActivate( void );
virtual void ServerDeactivate( void );
virtual bool ServerCommand( const char *cmd );
bool IsServerActive( void ) const { return m_serverActive; }
virtual void RestartRound( void ); ///< (EXTEND) invoked when a new round begins
virtual void StartFrame( void ); ///< (EXTEND) called each frame
virtual unsigned int GetPlayerPriority( CBasePlayer *player ) const; ///< return priority of player (0 = max pri)
virtual bool IsImportantPlayer( CCSPlayer *player ) const; ///< return true if player is important to scenario (VIP, bomb carrier, etc)
void ExtractScenarioData( void ); ///< search the map entities to determine the game scenario and define important zones
// difficulty levels -----------------------------------------------------------------------------------------
static BotDifficultyType GetDifficultyLevel( void )
{
if (cv_bot_difficulty.GetFloat() < 0.9f)
return BOT_EASY;
if (cv_bot_difficulty.GetFloat() < 1.9f)
return BOT_NORMAL;
if (cv_bot_difficulty.GetFloat() < 2.9f)
return BOT_HARD;
return BOT_EXPERT;
}
// the supported game scenarios ------------------------------------------------------------------------------
enum GameScenarioType
{
SCENARIO_DEATHMATCH,
SCENARIO_DEFUSE_BOMB,
SCENARIO_RESCUE_HOSTAGES,
SCENARIO_ESCORT_VIP
};
GameScenarioType GetScenario( void ) const { return m_gameScenario; }
// "zones" ---------------------------------------------------------------------------------------------------
// depending on the game mode, these are bomb zones, rescue zones, etc.
enum { MAX_ZONES = 4 }; ///< max # of zones in a map
enum { MAX_ZONE_NAV_AREAS = 16 }; ///< max # of nav areas in a zone
struct Zone
{
CBaseEntity *m_entity; ///< the map entity
CNavArea *m_area[ MAX_ZONE_NAV_AREAS ]; ///< nav areas that overlap this zone
int m_areaCount;
Vector m_center;
bool m_isLegacy; ///< if true, use pev->origin and 256 unit radius as zone
int m_index;
bool m_isBlocked;
Extent m_extent;
};
const Zone *GetZone( int i ) const { return &m_zone[i]; }
const Zone *GetZone( const Vector &pos ) const; ///< return the zone that contains the given position
const Zone *GetClosestZone( const Vector &pos ) const; ///< return the closest zone to the given position
const Zone *GetClosestZone( const CBaseEntity *entity ) const; ///< return the closest zone to the given entity
int GetZoneCount( void ) const { return m_zoneCount; }
void CheckForBlockedZones( void );
const Vector *GetRandomPositionInZone( const Zone *zone ) const; ///< return a random position inside the given zone
CNavArea *GetRandomAreaInZone( const Zone *zone ) const; ///< return a random area inside the given zone
/**
* Return the zone closest to the given position, using the given cost heuristic
*/
template< typename CostFunctor >
const Zone *GetClosestZone( CNavArea *startArea, CostFunctor costFunc, float *travelDistance = NULL ) const
{
const Zone *closeZone = NULL;
float closeDist = 99999999.9f;
if (startArea == NULL)
return NULL;
for( int i=0; i<m_zoneCount; ++i )
{
if (m_zone[i].m_areaCount == 0)
continue;
if ( m_zone[i].m_isBlocked )
continue;
// just use the first overlapping nav area as a reasonable approximation
float dist = NavAreaTravelDistance( startArea, m_zone[i].m_area[0], costFunc );
if (dist >= 0.0f && dist < closeDist)
{
closeZone = &m_zone[i];
closeDist = dist;
}
}
if (travelDistance)
*travelDistance = closeDist;
return closeZone;
}
/// pick a zone at random and return it
const Zone *GetRandomZone( void ) const
{
if (m_zoneCount == 0)
return NULL;
int i;
CUtlVector< const Zone * > unblockedZones;
for ( i=0; i<m_zoneCount; ++i )
{
if ( m_zone[i].m_isBlocked )
continue;
unblockedZones.AddToTail( &(m_zone[i]) );
}
if ( unblockedZones.Count() == 0 )
return NULL;
return unblockedZones[ RandomInt( 0, unblockedZones.Count()-1 ) ];
}
/// returns a random spawn point for the given team (no arg means use both team spawnpoints)
CBaseEntity *GetRandomSpawn( int team = TEAM_MAXCOUNT ) const;
bool IsBombPlanted( void ) const { return m_isBombPlanted; } ///< returns true if bomb has been planted
float GetBombPlantTimestamp( void ) const { return m_bombPlantTimestamp; } ///< return time bomb was planted
bool IsTimeToPlantBomb( void ) const; ///< return true if it's ok to try to plant bomb
CCSPlayer *GetBombDefuser( void ) const { return m_bombDefuser; } ///< return the player currently defusing the bomb, or NULL
float GetBombTimeLeft( void ) const; ///< get the time remaining before the planted bomb explodes
CBaseEntity *GetLooseBomb( void ) { return m_looseBomb; } ///< return the bomb if it is loose on the ground
CNavArea *GetLooseBombArea( void ) const { return m_looseBombArea; } ///< return area that bomb is in/near
void SetLooseBomb( CBaseEntity *bomb );
float GetRadioMessageTimestamp( RadioType event, int teamID ) const; ///< return the last time the given radio message was sent for given team
float GetRadioMessageInterval( RadioType event, int teamID ) const; ///< return the interval since the last time this message was sent
void SetRadioMessageTimestamp( RadioType event, int teamID );
void ResetRadioMessageTimestamps( void );
float GetLastSeenEnemyTimestamp( void ) const { return m_lastSeenEnemyTimestamp; } ///< return the last time anyone has seen an enemy
void SetLastSeenEnemyTimestamp( void ) { m_lastSeenEnemyTimestamp = gpGlobals->curtime; }
float GetRoundStartTime( void ) const { return m_roundStartTimestamp; }
float GetElapsedRoundTime( void ) const { return gpGlobals->curtime - m_roundStartTimestamp; } ///< return the elapsed time since the current round began
bool AllowRogues( void ) const { return cv_bot_allow_rogues.GetBool(); }
bool AllowPistols( void ) const { return cv_bot_allow_pistols.GetBool(); }
bool AllowShotguns( void ) const { return cv_bot_allow_shotguns.GetBool(); }
bool AllowSubMachineGuns( void ) const { return cv_bot_allow_sub_machine_guns.GetBool(); }
bool AllowRifles( void ) const { return cv_bot_allow_rifles.GetBool(); }
bool AllowMachineGuns( void ) const { return cv_bot_allow_machine_guns.GetBool(); }
bool AllowGrenades( void ) const { return cv_bot_allow_grenades.GetBool(); }
bool AllowSnipers( void ) const { return cv_bot_allow_snipers.GetBool(); }
#ifdef CS_SHIELD_ENABLED
bool AllowTacticalShield( void ) const { return cv_bot_allow_shield.GetBool(); }
#else
bool AllowTacticalShield( void ) const { return false; }
#endif // CS_SHIELD_ENABLED
bool AllowFriendlyFireDamage( void ) const { return friendlyfire.GetBool(); }
bool IsWeaponUseable( const CWeaponCSBase *weapon ) const; ///< return true if the bot can use this weapon
bool IsDefenseRushing( void ) const { return m_isDefenseRushing; } ///< returns true if defense team has "decided" to rush this round
bool IsOnDefense( const CCSPlayer *player ) const; ///< return true if this player is on "defense"
bool IsOnOffense( const CCSPlayer *player ) const; ///< return true if this player is on "offense"
bool IsRoundOver( void ) const { return m_isRoundOver; } ///< return true if the round has ended
#define FROM_CONSOLE true
bool BotAddCommand( int team, bool isFromConsole = false, const char *profileName = NULL, CSWeaponType weaponType = WEAPONTYPE_UNKNOWN, BotDifficultyType difficulty = NUM_DIFFICULTY_LEVELS ); ///< process the "bot_add" console command
private:
enum SkillType { LOW, AVERAGE, HIGH, RANDOM };
void MaintainBotQuota( void );
static bool m_isMapDataLoaded; ///< true if we've attempted to load map data
bool m_serverActive; ///< true between ServerActivate() and ServerDeactivate()
GameScenarioType m_gameScenario; ///< what kind of game are we playing
Zone m_zone[ MAX_ZONES ];
int m_zoneCount;
bool m_isBombPlanted; ///< true if bomb has been planted
float m_bombPlantTimestamp; ///< time bomb was planted
float m_earliestBombPlantTimestamp; ///< don't allow planting until after this time has elapsed
CCSPlayer *m_bombDefuser; ///< the player currently defusing a bomb
EHANDLE m_looseBomb; ///< will be non-NULL if bomb is loose on the ground
CNavArea *m_looseBombArea; ///< area that bomb is is/near
bool m_isRoundOver; ///< true if the round has ended
CountdownTimer m_checkTransientAreasTimer; ///< when elapsed, all transient nav areas should be checked for blockage
float m_radioMsgTimestamp[ RADIO_END - RADIO_START_1 ][ 2 ];
float m_lastSeenEnemyTimestamp;
float m_roundStartTimestamp; ///< the time when the current round began
bool m_isDefenseRushing; ///< whether defensive team is rushing this round or not
// Event Handlers --------------------------------------------------------------------------------------------
DECLARE_CSBOTMANAGER_EVENT_LISTENER( PlayerFootstep, player_footstep )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( PlayerRadio, player_radio )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( PlayerDeath, player_death )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( PlayerFallDamage, player_falldamage )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( BombPickedUp, bomb_pickup )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( BombPlanted, bomb_planted )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( BombBeep, bomb_beep )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( BombDefuseBegin, bomb_begindefuse )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( BombDefused, bomb_defused )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( BombDefuseAbort, bomb_abortdefuse )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( BombExploded, bomb_exploded )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( RoundEnd, round_end )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( RoundStart, round_start )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( RoundFreezeEnd, round_freeze_end )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( DoorMoving, door_moving )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( BreakProp, break_prop )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( BreakBreakable, break_breakable )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( HostageFollows, hostage_follows )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( HostageRescuedAll, hostage_rescued_all )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( WeaponFire, weapon_fire )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( WeaponFireOnEmpty, weapon_fire_on_empty )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( WeaponReload, weapon_reload )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( WeaponZoom, weapon_zoom )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( BulletImpact, bullet_impact )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( HEGrenadeDetonate, hegrenade_detonate )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( FlashbangDetonate, flashbang_detonate )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( SmokeGrenadeDetonate, smokegrenade_detonate )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( GrenadeBounce, grenade_bounce )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( NavBlocked, nav_blocked )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( ServerShutdown, server_shutdown )
CUtlVector< BotEventInterface * > m_commonEventListeners; // These event listeners fire often, and can be disabled for performance gains when no bots are present.
bool m_eventListenersEnabled;
void EnableEventListeners( bool enable );
};
inline CBasePlayer *CCSBotManager::AllocateBotEntity( void )
{
return static_cast<CBasePlayer *>( CreateEntityByName( "cs_bot" ) );
}
inline bool CCSBotManager::IsTimeToPlantBomb( void ) const
{
return (gpGlobals->curtime >= m_earliestBombPlantTimestamp);
}
inline const CCSBotManager::Zone *CCSBotManager::GetClosestZone( const CBaseEntity *entity ) const
{
if (entity == NULL)
return NULL;
Vector centroid = entity->GetAbsOrigin();
centroid.z += HalfHumanHeight;
return GetClosestZone( centroid );
}
#endif
+963
View File
@@ -0,0 +1,963 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
#include "cbase.h"
#include "cs_bot.h"
#include "obstacle_pushaway.h"
#include "fmtstr.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
const float NearBreakableCheckDist = 20.0f;
const float FarBreakableCheckDist = 300.0f;
#define DEBUG_BREAKABLES 0
#define DEBUG_DOORS 0
//--------------------------------------------------------------------------------------------------------------
#if DEBUG_BREAKABLES
static void DrawOutlinedQuad( const Vector &p1,
const Vector &p2,
const Vector &p3,
const Vector &p4,
int r, int g, int b,
float duration )
{
NDebugOverlay::Triangle( p1, p2, p3, r, g, b, 20, false, duration );
NDebugOverlay::Triangle( p3, p4, p1, r, g, b, 20, false, duration );
NDebugOverlay::Line( p1, p2, r, g, b, false, duration );
NDebugOverlay::Line( p2, p3, r, g, b, false, duration );
NDebugOverlay::Line( p3, p4, r, g, b, false, duration );
NDebugOverlay::Line( p4, p1, r, g, b, false, duration );
}
ConVar bot_debug_breakable_duration( "bot_debug_breakable_duration", "30" );
#endif // DEBUG_BREAKABLES
//--------------------------------------------------------------------------------------------------------------
CBaseEntity * CheckForEntitiesAlongSegment( const Vector &start, const Vector &end, const Vector &mins, const Vector &maxs, CPushAwayEnumerator *enumerator )
{
CBaseEntity *entity = NULL;
Ray_t ray;
ray.Init( start, end, mins, maxs );
partition->EnumerateElementsAlongRay( PARTITION_ENGINE_SOLID_EDICTS, ray, false, enumerator );
if ( enumerator->m_nAlreadyHit > 0 )
{
entity = enumerator->m_AlreadyHit[0];
}
#if DEBUG_BREAKABLES
if ( entity )
{
DrawOutlinedQuad( start + mins, start + maxs, end + maxs, end + mins, 255, 0, 0, bot_debug_breakable_duration.GetFloat() );
}
else
{
DrawOutlinedQuad( start + mins, start + maxs, end + maxs, end + mins, 0, 255, 0, 0.1 );
}
#endif // DEBUG_BREAKABLES
return entity;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Look up to 'distance' units ahead on the bot's path for entities. Returns the closest one.
*/
CBaseEntity * CCSBot::FindEntitiesOnPath( float distance, CPushAwayEnumerator *enumerator, bool checkStuck )
{
Vector goal;
int pathIndex = FindPathPoint( distance, &goal, NULL );
bool isDegeneratePath = ( pathIndex == m_pathLength );
if ( isDegeneratePath )
{
goal = m_goalPosition;
}
goal.z += HalfHumanHeight;
Vector mins, maxs;
mins = Vector( 0, 0, -HalfHumanWidth );
maxs = Vector( 0, 0, HalfHumanHeight );
if ( distance <= NearBreakableCheckDist && m_isStuck && checkStuck )
{
mins = Vector( -HalfHumanWidth, -HalfHumanWidth, -HalfHumanWidth );
maxs = Vector( HalfHumanWidth, HalfHumanWidth, HalfHumanHeight );
}
CBaseEntity *entity = NULL;
if ( isDegeneratePath )
{
entity = CheckForEntitiesAlongSegment( WorldSpaceCenter(), m_goalPosition + Vector( 0, 0, HalfHumanHeight ), mins, maxs, enumerator );
#if DEBUG_BREAKABLES
if ( entity )
{
NDebugOverlay::HorzArrow( WorldSpaceCenter(), m_goalPosition, 6, 0, 0, 255, 255, true, bot_debug_breakable_duration.GetFloat() );
}
#endif // DEBUG_BREAKABLES
}
else
{
int startIndex = MAX( 0, m_pathIndex );
float distanceLeft = distance;
// HACK: start with an index one lower than normal, so we can trace from the bot's location to the
// start of the path nodes.
for( int i=startIndex-1; i<m_pathLength-1; ++i )
{
Vector start, end;
if ( i == startIndex - 1 )
{
start = GetAbsOrigin();
end = m_path[i+1].pos;
}
else
{
start = m_path[i].pos;
end = m_path[i+1].pos;
if ( m_path[i+1].how == GO_LADDER_UP )
{
// Need two checks. First we'll check along the ladder
start = m_path[i].pos;
end = m_path[i+1].ladder->m_top;
}
else if ( m_path[i].how == GO_LADDER_UP )
{
start = m_path[i].ladder->m_top;
}
else if ( m_path[i+1].how == GO_LADDER_DOWN )
{
// Need two checks. First we'll check along the ladder
start = m_path[i].pos;
end = m_path[i+1].ladder->m_bottom;
}
else if ( m_path[i].how == GO_LADDER_DOWN )
{
start = m_path[i].ladder->m_bottom;
}
}
float segmentLength = (start - end).Length();
if ( distanceLeft - segmentLength < 0 )
{
// scale our segment back so we don't look too far
Vector direction = end - start;
direction.NormalizeInPlace();
end = start + direction * distanceLeft;
}
entity = CheckForEntitiesAlongSegment( start + Vector( 0, 0, HalfHumanHeight ), end + Vector( 0, 0, HalfHumanHeight ), mins, maxs, enumerator );
if ( entity )
{
#if DEBUG_BREAKABLES
NDebugOverlay::HorzArrow( start, end, 4, 0, 255, 0, 255, true, bot_debug_breakable_duration.GetFloat() );
#endif // DEBUG_BREAKABLES
break;
}
if ( m_path[i].ladder && !IsOnLadder() && distance > NearBreakableCheckDist ) // don't try to break breakables on the other end of a ladder
break;
distanceLeft -= segmentLength;
if ( distanceLeft < 0 )
break;
if ( i != startIndex - 1 && m_path[i+1].ladder )
{
// Now we'll check from the ladder out to the endpoint
start = ( m_path[i+1].how == GO_LADDER_DOWN ) ? m_path[i+1].ladder->m_bottom : m_path[i+1].ladder->m_top;
end = m_path[i+1].pos;
entity = CheckForEntitiesAlongSegment( start + Vector( 0, 0, HalfHumanHeight ), end + Vector( 0, 0, HalfHumanHeight ), mins, maxs, enumerator );
if ( entity )
{
#if DEBUG_BREAKABLES
NDebugOverlay::HorzArrow( start, end, 4, 0, 255, 0, 255, true, bot_debug_breakable_duration.GetFloat() );
#endif // DEBUG_BREAKABLES
break;
}
}
}
}
if ( entity && !IsVisible( entity->WorldSpaceCenter(), false, entity ) )
return NULL;
return entity;
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::PushawayTouch( CBaseEntity *pOther )
{
#if DEBUG_BREAKABLES
NDebugOverlay::EntityBounds( pOther, 255, 0, 0, 127, 0.1f );
#endif // DEBUG_BREAKABLES
// if we're not stuck or crouched, we don't care
if ( !m_isStuck && !IsCrouching() )
return;
// See if it's breakable
CBaseEntity *props[1];
CBotBreakableEnumerator enumerator( props, ARRAYSIZE( props ) );
enumerator.EnumElement( pOther );
if ( enumerator.m_nAlreadyHit == 1 )
{
// it's breakable - try to shoot it.
SetLookAt( "Breakable", pOther->WorldSpaceCenter(), PRIORITY_HIGH, 0.1f, false, 5.0f, true );
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Check for breakable physics props and other breakable entities. We do this here instead of catching them
* in OnTouch() because players don't collide with physics props, so OnTouch() doesn't get called. Also,
* looking ahead like this lets us anticipate when we'll need to break something, and do it before being
* stopped by it.
*/
void CCSBot::BreakablesCheck( void )
{
#if DEBUG_BREAKABLES
/*
// Debug code to visually mark all breakables near us
{
Ray_t ray;
Vector origin = WorldSpaceCenter();
Vector mins( -400, -400, -400 );
Vector maxs( 400, 400, 400 );
ray.Init( origin, origin, mins, maxs );
CBaseEntity *props[40];
CBotBreakableEnumerator enumerator( props, ARRAYSIZE( props ) );
partition->EnumerateElementsAlongRay( PARTITION_ENGINE_SOLID_EDICTS, ray, false, &enumerator );
for ( int i=0; i<enumerator.m_nAlreadyHit; ++i )
{
CBaseEntity *prop = props[i];
if ( prop && prop->m_takedamage == DAMAGE_YES )
{
CFmtStr msg;
const char *text = msg.sprintf( "%s, %d health", prop->GetClassname(), prop->m_iHealth );
if ( prop->m_iHealth > 200 )
{
NDebugOverlay::EntityBounds( prop, 255, 0, 0, 10, 0.2f );
prop->EntityText( 0, text, 0.2f, 255, 0, 0, 255 );
}
else
{
NDebugOverlay::EntityBounds( prop, 0, 255, 0, 10, 0.2f );
prop->EntityText( 0, text, 0.2f, 0, 255, 0, 255 );
}
}
}
}
*/
#endif // DEBUG_BREAKABLES
if ( IsAttacking() )
{
// make sure we aren't running into a breakable trying to knife an enemy
if ( IsUsingKnife() && m_enemy != NULL )
{
CBaseEntity *breakables[1];
CBotBreakableEnumerator enumerator( breakables, ARRAYSIZE( breakables ) );
CBaseEntity *breakable = NULL;
Vector mins = Vector( -HalfHumanWidth, -HalfHumanWidth, -HalfHumanWidth );
Vector maxs = Vector( HalfHumanWidth, HalfHumanWidth, HalfHumanHeight );
breakable = CheckForEntitiesAlongSegment( WorldSpaceCenter(), m_enemy->WorldSpaceCenter(), mins, maxs, &enumerator );
if ( breakable )
{
#if DEBUG_BREAKABLES
NDebugOverlay::HorzArrow( WorldSpaceCenter(), m_enemy->WorldSpaceCenter(), 6, 0, 0, 255, 255, true, bot_debug_breakable_duration.GetFloat() );
#endif // DEBUG_BREAKABLES
// look at it (chances are we'll already be looking at it, since it's between us and our enemy)
SetLookAt( "Breakable", breakable->WorldSpaceCenter(), PRIORITY_HIGH, 0.1f, false, 5.0f, true );
// break it (again, don't wait: we don't have ammo, since we're using the knife, and we're looking mostly at it anyway)
PrimaryAttack();
}
}
return;
}
if ( !HasPath() )
return;
bool isNear = true;
// Check just in front of us on the path
CBaseEntity *breakables[4];
CBotBreakableEnumerator enumerator( breakables, ARRAYSIZE( breakables ) );
CBaseEntity *breakable = FindEntitiesOnPath( NearBreakableCheckDist, &enumerator, true );
// If we don't have an object right in front of us, check a ways out
if ( !breakable )
{
breakable = FindEntitiesOnPath( FarBreakableCheckDist, &enumerator, false );
isNear = false;
}
// Try to shoot a breakable we know about
if ( breakable )
{
// look at it
SetLookAt( "Breakable", breakable->WorldSpaceCenter(), PRIORITY_HIGH, 0.1f, false, 5.0f, true );
}
// break it
if ( IsLookingAtSpot( PRIORITY_HIGH ) && m_lookAtSpotAttack )
{
if ( IsUsingGrenade() || ( !isNear && IsUsingKnife() ) )
{
EquipBestWeapon( MUST_EQUIP );
}
else if ( GetActiveWeapon() && GetActiveWeapon()->m_flNextPrimaryAttack <= gpGlobals->curtime )
{
bool shouldShoot = IsLookingAtPosition( m_lookAtSpot, 10.0f );
if ( !shouldShoot )
{
CBaseEntity *breakables[1];
CBotBreakableEnumerator LOSbreakable( breakables, ARRAYSIZE( breakables ) );
// compute the unit vector along our view
Vector aimDir = GetViewVector();
// trace the potential bullet's path
trace_t result;
UTIL_TraceLine( EyePosition(), EyePosition() + FarBreakableCheckDist * aimDir, MASK_PLAYERSOLID, this, COLLISION_GROUP_NONE, &result );
if ( result.DidHitNonWorldEntity() )
{
LOSbreakable.EnumElement( result.m_pEnt );
if ( LOSbreakable.m_nAlreadyHit == 1 && LOSbreakable.m_AlreadyHit[0] == breakable )
{
shouldShoot = true;
}
}
}
shouldShoot = shouldShoot && !IsFriendInLineOfFire();
if ( shouldShoot )
{
PrimaryAttack();
}
}
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Check for doors that need +use to open.
*/
void CCSBot::DoorCheck( void )
{
if ( IsAttacking() && !IsUsingKnife() )
{
// If we're attacking with a gun or nade, don't bother with doors. If we're trying to
// knife someone, we might need to open a door.
m_isOpeningDoor = false;
return;
}
if ( !HasPath() )
return;
// Find any doors that need a +use to open just in front of us along the path.
CBaseEntity *doors[4];
CBotDoorEnumerator enumerator( doors, ARRAYSIZE( doors ) );
CBaseEntity *door = FindEntitiesOnPath( NearBreakableCheckDist, &enumerator, false );
if ( door )
{
if ( !IsLookingAtSpot( PRIORITY_HIGH ) )
{
if ( !IsOpeningDoor() )
{
OpenDoor( door );
}
}
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Reset the stuck-checker.
*/
void CCSBot::ResetStuckMonitor( void )
{
if (m_isStuck)
{
if (IsLocalPlayerWatchingMe() && cv_bot_debug.GetBool() && UTIL_GetListenServerHost())
{
CBasePlayer *localPlayer = UTIL_GetListenServerHost();
CSingleUserRecipientFilter filter( localPlayer );
EmitSound( filter, localPlayer->entindex(), "Bot.StuckSound" );
}
}
m_isStuck = false;
m_stuckTimestamp = 0.0f;
m_stuckJumpTimer.Invalidate();
m_avgVelIndex = 0;
m_avgVelCount = 0;
m_areaEnteredTimestamp = gpGlobals->curtime;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Test if we have become stuck
*/
void CCSBot::StuckCheck( void )
{
if (m_isStuck)
{
// we are stuck - see if we have moved far enough to be considered unstuck
Vector delta = GetAbsOrigin() - m_stuckSpot;
const float unstuckRange = 75.0f;
if (delta.IsLengthGreaterThan( unstuckRange ))
{
// we are no longer stuck
ResetStuckMonitor();
PrintIfWatched( "UN-STUCK\n" );
}
}
else
{
// check if we are stuck
// compute average velocity over a short period (for stuck check)
Vector vel = GetAbsOrigin() - m_lastOrigin;
// if we are jumping, ignore Z
if (IsJumping())
vel.z = 0.0f;
// cannot be Length2D, or will break ladder movement (they are only Z)
float moveDist = vel.Length();
float deltaT = g_BotUpdateInterval;
m_avgVel[ m_avgVelIndex++ ] = moveDist/deltaT;
if (m_avgVelIndex == MAX_VEL_SAMPLES)
m_avgVelIndex = 0;
if (m_avgVelCount < MAX_VEL_SAMPLES)
{
m_avgVelCount++;
}
else
{
// we have enough samples to know if we're stuck
float avgVel = 0.0f;
for( int t=0; t<m_avgVelCount; ++t )
avgVel += m_avgVel[t];
avgVel /= m_avgVelCount;
// cannot make this velocity too high, or bots will get "stuck" when going down ladders
float stuckVel = (IsUsingLadder()) ? 10.0f : 20.0f;
if (avgVel < stuckVel)
{
// we are stuck - note when and where we initially become stuck
m_stuckTimestamp = gpGlobals->curtime;
m_stuckSpot = GetAbsOrigin();
m_stuckJumpTimer.Start( RandomFloat( 0.3f, 0.75f ) ); // 1.0
PrintIfWatched( "STUCK\n" );
if (IsLocalPlayerWatchingMe() && cv_bot_debug.GetInt() > 0.0f && UTIL_GetListenServerHost())
{
CBasePlayer *localPlayer = UTIL_GetListenServerHost();
CSingleUserRecipientFilter filter( localPlayer );
EmitSound( filter, localPlayer->entindex(), "Bot.StuckStart" );
}
m_isStuck = true;
}
}
}
// always need to track this
m_lastOrigin = GetAbsOrigin();
}
//--------------------------------------------------------------------------------------------------------------
/**
* Check if we need to jump due to height change
*/
bool CCSBot::DiscontinuityJump( float ground, bool onlyJumpDown, bool mustJump )
{
// Don't try to jump if in the air.
if( !(GetFlags() & FL_ONGROUND) )
{
return false;
}
float dz = ground - GetFeetZ();
if (dz > StepHeight && !onlyJumpDown)
{
// dont restrict jump time when going up
if (Jump( MUST_JUMP ))
{
return true;
}
}
else if (!IsUsingLadder() && dz < -JumpHeight)
{
if (Jump( mustJump ))
{
return true;
}
}
return false;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Find "simple" ground height, treating current nav area as part of the floor
*/
bool CCSBot::GetSimpleGroundHeightWithFloor( const Vector &pos, float *height, Vector *normal )
{
if (TheNavMesh->GetSimpleGroundHeight( pos, height, normal ))
{
// our current nav area also serves as a ground polygon
if (m_lastKnownArea && m_lastKnownArea->IsOverlapping( pos ))
*height = MAX( (*height), m_lastKnownArea->GetZ( pos ) );
return true;
}
return false;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Get our current radio chatter place
*/
Place CCSBot::GetPlace( void ) const
{
if (m_lastKnownArea)
return m_lastKnownArea->GetPlace();
return UNDEFINED_PLACE;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Move towards position, independant of view angle
*/
void CCSBot::MoveTowardsPosition( const Vector &pos )
{
Vector myOrigin = GetCentroid( this );
//
// Jump up on ledges
// Because we may not be able to get to our goal position and enter the next
// area because our extent collides with a nearby vertical ledge, make sure
// we look far enough ahead to avoid this situation.
// Can't look too far ahead, or bots will try to jump up slopes.
//
// NOTE: We need to do this frequently to catch edges at the right time
// @todo Look ahead *along path* instead of straight line
//
if ((m_lastKnownArea == NULL || !(m_lastKnownArea->GetAttributes() & NAV_MESH_NO_JUMP)) &&
!IsOnLadder())
{
float ground;
Vector aheadRay( pos.x - myOrigin.x, pos.y - myOrigin.y, 0 );
aheadRay.NormalizeInPlace();
// look far ahead to allow us to smoothly jump over gaps, ledges, etc
// only jump if ground is flat at lookahead spot to avoid jumping up slopes
bool jumped = false;
if (IsRunning())
{
const float farLookAheadRange = 80.0f; // 60
Vector normal;
Vector stepAhead = myOrigin + farLookAheadRange * aheadRay;
stepAhead.z += HalfHumanHeight;
if (GetSimpleGroundHeightWithFloor( stepAhead, &ground, &normal ))
{
if (normal.z > 0.9f)
jumped = DiscontinuityJump( ground, ONLY_JUMP_DOWN );
}
}
if (!jumped)
{
// close up jumping
const float lookAheadRange = 30.0f; // cant be less or will miss jumps over low walls
Vector stepAhead = myOrigin + lookAheadRange * aheadRay;
stepAhead.z += HalfHumanHeight;
if (GetSimpleGroundHeightWithFloor( stepAhead, &ground ))
{
jumped = DiscontinuityJump( ground );
}
}
if (!jumped)
{
// about to fall gap-jumping
const float lookAheadRange = 10.0f;
Vector stepAhead = myOrigin + lookAheadRange * aheadRay;
stepAhead.z += HalfHumanHeight;
if (GetSimpleGroundHeightWithFloor( stepAhead, &ground ))
{
jumped = DiscontinuityJump( ground, ONLY_JUMP_DOWN, MUST_JUMP );
}
}
}
// compute our current forward and lateral vectors
float angle = EyeAngles().y;
Vector2D dir( BotCOS(angle), BotSIN(angle) );
Vector2D lat( -dir.y, dir.x );
// compute unit vector to goal position
Vector2D to( pos.x - myOrigin.x, pos.y - myOrigin.y );
to.NormalizeInPlace();
// move towards the position independant of our view direction
float toProj = to.x * dir.x + to.y * dir.y;
float latProj = to.x * lat.x + to.y * lat.y;
const float c = 0.25f; // 0.5
if (toProj > c)
MoveForward();
else if (toProj < -c)
MoveBackward();
// if we are avoiding someone via strafing, don't override
if (m_avoid != NULL)
return;
if (latProj >= c)
StrafeLeft();
else if (latProj <= -c)
StrafeRight();
}
//--------------------------------------------------------------------------------------------------------------
/**
* Move away from position, independant of view angle
*/
void CCSBot::MoveAwayFromPosition( const Vector &pos )
{
// compute our current forward and lateral vectors
float angle = EyeAngles().y;
Vector2D dir( BotCOS(angle), BotSIN(angle) );
Vector2D lat( -dir.y, dir.x );
// compute unit vector to goal position
Vector2D to( pos.x - GetAbsOrigin().x, pos.y - GetAbsOrigin().y );
to.NormalizeInPlace();
// move away from the position independant of our view direction
float toProj = to.x * dir.x + to.y * dir.y;
float latProj = to.x * lat.x + to.y * lat.y;
const float c = 0.5f;
if (toProj > c)
MoveBackward();
else if (toProj < -c)
MoveForward();
if (latProj >= c)
StrafeRight();
else if (latProj <= -c)
StrafeLeft();
}
//--------------------------------------------------------------------------------------------------------------
/**
* Strafe (sidestep) away from position, independant of view angle
*/
void CCSBot::StrafeAwayFromPosition( const Vector &pos )
{
// compute our current forward and lateral vectors
float angle = EyeAngles().y;
Vector2D dir( BotCOS(angle), BotSIN(angle) );
Vector2D lat( -dir.y, dir.x );
// compute unit vector to goal position
Vector2D to( pos.x - GetAbsOrigin().x, pos.y - GetAbsOrigin().y );
to.NormalizeInPlace();
float latProj = to.x * lat.x + to.y * lat.y;
if (latProj >= 0.0f)
StrafeRight();
else
StrafeLeft();
}
//--------------------------------------------------------------------------------------------------------------
/**
* For getting un-stuck
*/
void CCSBot::Wiggle( void )
{
if (IsCrouching())
{
return;
}
// for wiggling
if (m_wiggleTimer.IsElapsed())
{
m_wiggleDirection = (NavRelativeDirType)RandomInt( 0, 3 );
m_wiggleTimer.Start( RandomFloat( 0.3f, 0.5f ) ); // 0.3, 0.5
}
Vector forward, right;
EyeVectors( &forward, &right );
const float lookAheadRange = (m_lastKnownArea && (m_lastKnownArea->GetAttributes() & NAV_MESH_WALK)) ? 5.0f : 30.0f;
float ground;
switch( m_wiggleDirection )
{
case LEFT:
{
// don't move left if we will fall
Vector pos = GetAbsOrigin() - (lookAheadRange * right);
if (GetSimpleGroundHeightWithFloor( pos, &ground ))
{
if (GetAbsOrigin().z - ground < StepHeight)
{
StrafeLeft();
}
}
break;
}
case RIGHT:
{
// don't move right if we will fall
Vector pos = GetAbsOrigin() + (lookAheadRange * right);
if (GetSimpleGroundHeightWithFloor( pos, &ground ))
{
if (GetAbsOrigin().z - ground < StepHeight)
{
StrafeRight();
}
}
break;
}
case FORWARD:
{
// don't move forward if we will fall
Vector pos = GetAbsOrigin() + (lookAheadRange * forward);
if (GetSimpleGroundHeightWithFloor( pos, &ground ))
{
if (GetAbsOrigin().z - ground < StepHeight)
{
MoveForward();
}
}
break;
}
case BACKWARD:
{
// don't move backward if we will fall
Vector pos = GetAbsOrigin() - (lookAheadRange * forward);
if (GetSimpleGroundHeightWithFloor( pos, &ground ))
{
if (GetAbsOrigin().z - ground < StepHeight)
{
MoveBackward();
}
}
break;
}
}
if (m_stuckJumpTimer.IsElapsed() && m_lastKnownArea && !(m_lastKnownArea->GetAttributes() & NAV_MESH_NO_JUMP))
{
if (Jump())
{
m_stuckJumpTimer.Start( RandomFloat( 1.0f, 2.0f ) );
}
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Determine approach points from eye position and approach areas of current area
*/
void CCSBot::ComputeApproachPoints( void )
{
m_approachPointCount = 0;
if (m_lastKnownArea == NULL)
{
return;
}
// assume we're crouching for now
Vector eye = GetCentroid( this ); // + pev->view_ofs; // eye position
Vector ap;
float halfWidth;
for( int i=0; i<m_lastKnownArea->GetApproachInfoCount() && m_approachPointCount < MAX_APPROACH_POINTS; ++i )
{
const CCSNavArea::ApproachInfo *info = m_lastKnownArea->GetApproachInfo( i );
if (info->here.area == NULL || info->prev.area == NULL)
{
continue;
}
// compute approach point (approach area is "info->here")
if (info->prevToHereHow <= GO_WEST)
{
info->prev.area->ComputePortal( info->here.area, (NavDirType)info->prevToHereHow, &ap, &halfWidth );
ap.z = info->here.area->GetZ( ap );
}
else
{
// use the area's center as an approach point
ap = info->here.area->GetCenter();
}
// "bend" our line of sight around corners until we can see the approach point
Vector bendPoint;
if (BendLineOfSight( eye, ap + Vector( 0, 0, HalfHumanHeight ), &bendPoint ))
{
// put point on the ground
if (TheNavMesh->GetGroundHeight( bendPoint, &bendPoint.z ) == false)
{
bendPoint.z = ap.z;
}
m_approachPoint[ m_approachPointCount ].m_pos = bendPoint;
m_approachPoint[ m_approachPointCount ].m_area = info->here.area;
++m_approachPointCount;
}
}
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::DrawApproachPoints( void ) const
{
for( int i=0; i<m_approachPointCount; ++i )
{
if (TheCSBots()->GetElapsedRoundTime() >= m_approachPoint[i].m_area->GetEarliestOccupyTime( OtherTeam( GetTeamNumber() ) ))
NDebugOverlay::Cross3D( m_approachPoint[i].m_pos, 10.0f, 255, 0, 255, true, 0.1f );
else
NDebugOverlay::Cross3D( m_approachPoint[i].m_pos, 10.0f, 100, 100, 100, true, 0.1f );
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Find the approach point that is nearest to our current path, ahead of us
*/
bool CCSBot::FindApproachPointNearestPath( Vector *pos )
{
if (!HasPath())
return false;
// make sure approach points are accurate
ComputeApproachPoints();
if (m_approachPointCount == 0)
return false;
Vector target = Vector( 0, 0, 0 ), close;
float targetRangeSq = 0.0f;
bool found = false;
int start = m_pathIndex;
int end = m_pathLength;
//
// We dont want the strictly closest point, but the farthest approach point
// from us that is near our path
//
const float nearPathSq = 10000.0f; // (100)
for( int i=0; i<m_approachPointCount; ++i )
{
if (FindClosestPointOnPath( m_approachPoint[i].m_pos, start, end, &close ) == false)
continue;
float rangeSq = (m_approachPoint[i].m_pos - close).LengthSqr();
if (rangeSq > nearPathSq)
continue;
if (rangeSq > targetRangeSq)
{
target = close;
targetRangeSq = rangeSq;
found = true;
}
}
if (found)
{
*pos = target + Vector( 0, 0, HalfHumanHeight );
return true;
}
return false;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return true if we are at the/an enemy spawn right now
*/
bool CCSBot::IsAtEnemySpawn( void ) const
{
CBaseEntity *spot;
const char *spawnName = (GetTeamNumber() == TEAM_TERRORIST) ? "info_player_counterterrorist" : "info_player_terrorist";
// check if we are at any of the enemy's spawn points
for( spot = gEntList.FindEntityByClassname( NULL, spawnName ); spot; spot = gEntList.FindEntityByClassname( spot, spawnName ) )
{
CNavArea *area = TheNavMesh->GetNearestNavArea( spot->WorldSpaceCenter() );
if (area && GetLastKnownArea() == area)
{
return true;
}
}
return false;
}
File diff suppressed because it is too large Load Diff
+346
View File
@@ -0,0 +1,346 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
#include "cbase.h"
#include "cs_bot.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
extern int gmsgBotVoice;
//--------------------------------------------------------------------------------------------------------------
/**
* Returns true if the radio message is an order to do something
* NOTE: "Report in" is not considered a "command" because it doesnt ask the bot to go somewhere, or change its mind
*/
bool CCSBot::IsRadioCommand( RadioType event ) const
{
if (event == RADIO_AFFIRMATIVE ||
event == RADIO_NEGATIVE ||
event == RADIO_ENEMY_SPOTTED ||
event == RADIO_SECTOR_CLEAR ||
event == RADIO_REPORTING_IN ||
event == RADIO_REPORT_IN_TEAM ||
event == RADIO_ENEMY_DOWN ||
event == RADIO_INVALID )
return false;
return true;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Respond to radio commands from HUMAN players
*/
void CCSBot::RespondToRadioCommands( void )
{
// bots use the chatter system to respond to each other
if (m_radioSubject != NULL && m_radioSubject->IsPlayer())
{
CCSPlayer *player = m_radioSubject;
if (player->IsBot())
{
m_lastRadioCommand = RADIO_INVALID;
return;
}
}
if (m_lastRadioCommand == RADIO_INVALID)
return;
// a human player has issued a radio command
GetChatter()->ResetRadioSilenceDuration();
// if we are doing something important, ignore the radio
// unless it is a "report in" request - we can do that while we continue to do other things
/// @todo Create "uninterruptable" flag
if (m_lastRadioCommand != RADIO_REPORT_IN_TEAM)
{
if (IsBusy())
{
// consume command
m_lastRadioCommand = RADIO_INVALID;
return;
}
}
// wait for reaction time before responding
// delay needs to be long enough for the radio message we're responding to to finish
float respondTime = 1.0f + 2.0f * GetProfile()->GetReactionTime();
if (IsRogue())
respondTime += 2.0f;
if (gpGlobals->curtime - m_lastRadioRecievedTimestamp < respondTime)
return;
// rogues won't follow commands, unless already following the player
if (!IsFollowing() && IsRogue())
{
if (IsRadioCommand( m_lastRadioCommand ))
{
GetChatter()->Negative();
}
// consume command
m_lastRadioCommand = RADIO_INVALID;
return;
}
CCSPlayer *player = m_radioSubject;
if (player == NULL)
return;
// respond to command
bool canDo = false;
const float inhibitAutoFollowDuration = 60.0f;
switch( m_lastRadioCommand )
{
case RADIO_REPORT_IN_TEAM:
{
GetChatter()->ReportingIn();
break;
}
case RADIO_FOLLOW_ME:
case RADIO_COVER_ME:
case RADIO_STICK_TOGETHER_TEAM:
case RADIO_REGROUP_TEAM:
{
if (!IsFollowing())
{
Follow( player );
player->AllowAutoFollow();
canDo = true;
}
break;
}
case RADIO_ENEMY_SPOTTED:
case RADIO_NEED_BACKUP:
case RADIO_TAKING_FIRE:
if (!IsFollowing())
{
Follow( player );
GetChatter()->Say( "OnMyWay" );
player->AllowAutoFollow();
canDo = false;
}
break;
case RADIO_TEAM_FALL_BACK:
{
if (TryToRetreat())
canDo = true;
break;
}
case RADIO_HOLD_THIS_POSITION:
{
// find the leader's area
SetTask( HOLD_POSITION );
StopFollowing();
player->InhibitAutoFollow( inhibitAutoFollowDuration );
Hide( TheNavMesh->GetNearestNavArea( m_radioPosition ) );
canDo = true;
break;
}
case RADIO_GO_GO_GO:
case RADIO_STORM_THE_FRONT:
StopFollowing();
Hunt();
canDo = true;
player->InhibitAutoFollow( inhibitAutoFollowDuration );
break;
case RADIO_GET_OUT_OF_THERE:
if (TheCSBots()->IsBombPlanted())
{
EscapeFromBomb();
player->InhibitAutoFollow( inhibitAutoFollowDuration );
canDo = true;
}
break;
case RADIO_SECTOR_CLEAR:
{
// if this is a defusal scenario, and the bomb is planted,
// and a human player cleared a bombsite, check it off our list too
if (TheCSBots()->GetScenario() == CCSBotManager::SCENARIO_DEFUSE_BOMB)
{
if (GetTeamNumber() == TEAM_CT && TheCSBots()->IsBombPlanted())
{
const CCSBotManager::Zone *zone = TheCSBots()->GetClosestZone( player );
if (zone)
{
GetGameState()->ClearBombsite( zone->m_index );
// if we are huting for the planted bomb, re-select bombsite
if (GetTask() == FIND_TICKING_BOMB)
Idle();
canDo = true;
}
}
}
break;
}
default:
// ignore all other radio commands for now
return;
}
if (canDo)
{
// affirmative
GetChatter()->Affirmative();
// if we agreed to follow a new command, put away our grenade
if (IsRadioCommand( m_lastRadioCommand ) && IsUsingGrenade())
{
EquipBestWeapon();
}
}
// consume command
m_lastRadioCommand = RADIO_INVALID;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Decide if we should move to help the player, return true if we will
*/
bool CCSBot::RespondToHelpRequest( CCSPlayer *them, Place place, float maxRange )
{
if (IsRogue())
return false;
// if we're busy, ignore
if (IsBusy())
return false;
Vector themOrigin = GetCentroid( them );
// if we are too far away, ignore
if (maxRange > 0.0f)
{
// compute actual travel distance
PathCost cost(this);
float travelDistance = NavAreaTravelDistance( m_lastKnownArea, TheNavMesh->GetNearestNavArea( themOrigin ), cost );
if (travelDistance < 0.0f)
return false;
if (travelDistance > maxRange)
return false;
}
if (place == UNDEFINED_PLACE)
{
// if we have no "place" identifier, go directly to them
// if we are already there, ignore
float rangeSq = (them->GetAbsOrigin() - GetAbsOrigin()).LengthSqr();
const float close = 750.0f * 750.0f;
if (rangeSq < close)
return true;
MoveTo( themOrigin, FASTEST_ROUTE );
}
else
{
// if we are already there, ignore
if (GetPlace() == place)
return true;
// go to where help is needed
const Vector *pos = GetRandomSpotAtPlace( place );
if (pos)
{
MoveTo( *pos, FASTEST_ROUTE );
}
else
{
MoveTo( themOrigin, FASTEST_ROUTE );
}
}
// acknowledge
GetChatter()->Say( "OnMyWay" );
return true;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Send a radio message
*/
void CCSBot::SendRadioMessage( RadioType event )
{
// make sure this is a radio event
if (event <= RADIO_START_1 || event >= RADIO_END)
return;
PrintIfWatched( "%3.1f: SendRadioMessage( %s )\n", gpGlobals->curtime, RadioEventName[ event ] );
// note the time the message was sent
TheCSBots()->SetRadioMessageTimestamp( event, GetTeamNumber() );
m_lastRadioSentTimestamp = gpGlobals->curtime;
char slot[2];
slot[1] = '\000';
if (event > RADIO_START_1 && event < RADIO_START_2)
{
HandleMenu_Radio1( event - RADIO_START_1 );
}
else if (event > RADIO_START_2 && event < RADIO_START_3)
{
HandleMenu_Radio2( event - RADIO_START_2 );
}
else
{
HandleMenu_Radio3( event - RADIO_START_3 );
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Send voice chatter. Also sends the entindex and duration for voice feedback.
*/
void CCSBot::SpeakAudio( const char *voiceFilename, float duration, int pitch )
{
if( !IsAlive() )
return;
if ( IsObserver() )
return;
CRecipientFilter filter;
ConstructRadioFilter( filter );
UserMessageBegin ( filter, "RawAudio" );
WRITE_BYTE( pitch );
WRITE_BYTE( entindex() );
WRITE_FLOAT( duration );
WRITE_STRING( voiceFilename );
MessageEnd();
GetChatter()->ResetRadioSilenceDuration();
m_voiceEndTimestamp = gpGlobals->curtime + duration;
}
@@ -0,0 +1,693 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
#include "cbase.h"
#include "cs_bot.h"
#include "cs_nav_path.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//--------------------------------------------------------------------------------------------------------------
/**
* This method is the ONLY legal way to change a bot's current state
*/
void CCSBot::SetState( BotState *state )
{
PrintIfWatched( "%s: SetState: %s -> %s\n", GetPlayerName(), (m_state) ? m_state->GetName() : "NULL", state->GetName() );
/*
if ( IsDefusingBomb() )
{
const Vector *bombPos = GetGameState()->GetBombPosition();
if ( bombPos != NULL )
{
if ( TheCSBots()->GetBombDefuser() == this )
{
if ( TheCSBots()->IsBombPlanted() )
{
Msg( "Bot %s is switching from defusing the bomb to %s\n",
GetPlayerName(), state->GetName() );
}
}
}
}
*/
// if we changed state from within the special Attack state, we are no longer attacking
if (m_isAttacking)
StopAttacking();
if (m_state)
m_state->OnExit( this );
state->OnEnter( this );
m_state = state;
m_stateTimestamp = gpGlobals->curtime;
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::Idle( void )
{
SetTask( SEEK_AND_DESTROY );
SetState( &m_idleState );
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::EscapeFromBomb( void )
{
SetTask( ESCAPE_FROM_BOMB );
SetState( &m_escapeFromBombState );
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::Follow( CCSPlayer *player )
{
if (player == NULL)
return;
// note when we began following
if (!m_isFollowing || m_leader != player)
m_followTimestamp = gpGlobals->curtime;
m_isFollowing = true;
m_leader = player;
SetTask( FOLLOW );
m_followState.SetLeader( player );
SetState( &m_followState );
}
//--------------------------------------------------------------------------------------------------------------
/**
* Continue following our leader after finishing what we were doing
*/
void CCSBot::ContinueFollowing( void )
{
SetTask( FOLLOW );
m_followState.SetLeader( m_leader );
SetState( &m_followState );
}
//--------------------------------------------------------------------------------------------------------------
/**
* Stop following
*/
void CCSBot::StopFollowing( void )
{
m_isFollowing = false;
m_leader = NULL;
m_allowAutoFollowTime = gpGlobals->curtime + 10.0f;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Begin process of rescuing hostages
*/
void CCSBot::RescueHostages( void )
{
SetTask( RESCUE_HOSTAGES );
}
//--------------------------------------------------------------------------------------------------------------
/**
* Use the entity
*/
void CCSBot::UseEntity( CBaseEntity *entity )
{
m_useEntityState.SetEntity( entity );
SetState( &m_useEntityState );
}
//--------------------------------------------------------------------------------------------------------------
/**
* Open the door.
* This assumes the bot is directly in front of the door with no obstructions.
* NOTE: This state is special, like Attack, in that it suspends the current behavior and returns to it when done.
*/
void CCSBot::OpenDoor( CBaseEntity *door )
{
m_openDoorState.SetDoor( door );
m_isOpeningDoor = true;
m_openDoorState.OnEnter( this );
}
//--------------------------------------------------------------------------------------------------------------
/**
* DEPRECATED: Use TryToHide() instead.
* Move to a hiding place.
* If 'searchFromArea' is non-NULL, hiding spots are looked for from that area first.
*/
void CCSBot::Hide( CNavArea *searchFromArea, float duration, float hideRange, bool holdPosition )
{
DestroyPath();
CNavArea *source;
Vector sourcePos;
if (searchFromArea)
{
source = searchFromArea;
sourcePos = searchFromArea->GetCenter();
}
else
{
source = m_lastKnownArea;
sourcePos = GetCentroid( this );
}
if (source == NULL)
{
PrintIfWatched( "Hide from area is NULL.\n" );
Idle();
return;
}
m_hideState.SetSearchArea( source );
m_hideState.SetSearchRange( hideRange );
m_hideState.SetDuration( duration );
m_hideState.SetHoldPosition( holdPosition );
// search around source area for a good hiding spot
Vector useSpot;
const Vector *pos = FindNearbyHidingSpot( this, sourcePos, hideRange, IsSniper() );
if (pos == NULL)
{
PrintIfWatched( "No available hiding spots.\n" );
// hide at our current position
useSpot = GetCentroid( this );
}
else
{
useSpot = *pos;
}
m_hideState.SetHidingSpot( useSpot );
// build a path to our new hiding spot
if (ComputePath( useSpot, FASTEST_ROUTE ) == false)
{
PrintIfWatched( "Can't pathfind to hiding spot\n" );
Idle();
return;
}
SetState( &m_hideState );
}
//--------------------------------------------------------------------------------------------------------------
/**
* Move to the given hiding place
*/
void CCSBot::Hide( const Vector &hidingSpot, float duration, bool holdPosition )
{
CNavArea *hideArea = TheNavMesh->GetNearestNavArea( hidingSpot );
if (hideArea == NULL)
{
PrintIfWatched( "Hiding spot off nav mesh\n" );
Idle();
return;
}
DestroyPath();
m_hideState.SetSearchArea( hideArea );
m_hideState.SetSearchRange( 750.0f );
m_hideState.SetDuration( duration );
m_hideState.SetHoldPosition( holdPosition );
m_hideState.SetHidingSpot( hidingSpot );
// build a path to our new hiding spot
if (ComputePath( hidingSpot, FASTEST_ROUTE ) == false)
{
PrintIfWatched( "Can't pathfind to hiding spot\n" );
Idle();
return;
}
SetState( &m_hideState );
}
//--------------------------------------------------------------------------------------------------------------
/**
* Try to hide nearby. Return true if hiding, false if can't hide here.
* If 'searchFromArea' is non-NULL, hiding spots are looked for from that area first.
*/
bool CCSBot::TryToHide( CNavArea *searchFromArea, float duration, float hideRange, bool holdPosition, bool useNearest )
{
CNavArea *source;
Vector sourcePos;
if (searchFromArea)
{
source = searchFromArea;
sourcePos = searchFromArea->GetCenter();
}
else
{
source = m_lastKnownArea;
sourcePos = GetCentroid( this );
}
if (source == NULL)
{
PrintIfWatched( "Hide from area is NULL.\n" );
return false;
}
m_hideState.SetSearchArea( source );
m_hideState.SetSearchRange( hideRange );
m_hideState.SetDuration( duration );
m_hideState.SetHoldPosition( holdPosition );
// search around source area for a good hiding spot
const Vector *pos = FindNearbyHidingSpot( this, sourcePos, hideRange, IsSniper(), useNearest );
if (pos == NULL)
{
PrintIfWatched( "No available hiding spots.\n" );
return false;
}
m_hideState.SetHidingSpot( *pos );
// build a path to our new hiding spot
if (ComputePath( *pos, FASTEST_ROUTE ) == false)
{
PrintIfWatched( "Can't pathfind to hiding spot\n" );
return false;
}
SetState( &m_hideState );
return true;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Retreat to a nearby hiding spot, away from enemies
*/
bool CCSBot::TryToRetreat( float maxRange, float duration )
{
const Vector *spot = FindNearbyRetreatSpot( this, maxRange );
if (spot)
{
// ignore enemies for a second to give us time to hide
// reaching our hiding spot clears our disposition
IgnoreEnemies( 10.0f );
if (duration < 0.0f)
{
duration = RandomFloat( 3.0f, 15.0f );
}
StandUp();
Run();
Hide( *spot, duration );
PrintIfWatched( "Retreating to a safe spot!\n" );
return true;
}
return false;
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::Hunt( void )
{
SetState( &m_huntState );
}
//--------------------------------------------------------------------------------------------------------------
/**
* Attack our the given victim
* NOTE: Attacking does not change our task.
*/
void CCSBot::Attack( CCSPlayer *victim )
{
if (victim == NULL)
return;
// zombies never attack
if (cv_bot_zombie.GetBool())
return;
// cannot attack if we are reloading
if (IsReloading())
return;
// change enemy
SetBotEnemy( victim );
//
// Do not "re-enter" the attack state if we are already attacking
//
if (IsAttacking())
return;
// if we're holding a grenade, throw it at the victim
if (IsUsingGrenade())
{
// throw towards their feet
ThrowGrenade( victim->GetAbsOrigin() );
return;
}
// if we are currently hiding, increase our chances of crouching and holding position
if (IsAtHidingSpot())
m_attackState.SetCrouchAndHold( (RandomFloat( 0.0f, 100.0f ) < 60.0f) ? true : false );
else
m_attackState.SetCrouchAndHold( false );
//SetState( &m_attackState );
//PrintIfWatched( "ATTACK BEGIN (reaction time = %g (+ update time), surprise time = %g, attack delay = %g)\n",
// GetProfile()->GetReactionTime(), m_surpriseDelay, GetProfile()->GetAttackDelay() );
m_isAttacking = true;
m_attackState.OnEnter( this );
Vector victimOrigin = GetCentroid( victim );
// cheat a bit and give the bot the initial location of its victim
m_lastEnemyPosition = victimOrigin;
m_lastSawEnemyTimestamp = gpGlobals->curtime;
m_aimSpreadTimestamp = gpGlobals->curtime;
// compute the angle difference between where are looking, and where we need to look
Vector toEnemy = victimOrigin - GetCentroid( this );
QAngle idealAngle;
VectorAngles( toEnemy, idealAngle );
float deltaYaw = (float)fabs(m_lookYaw - idealAngle.y);
while( deltaYaw > 180.0f )
deltaYaw -= 360.0f;
if (deltaYaw < 0.0f)
deltaYaw = -deltaYaw;
// immediately aim at enemy - accuracy penalty depending on how far we must turn to aim
// accuracy is halved if we have to turn 180 degrees
float turn = deltaYaw / 180.0f;
float accuracy = GetProfile()->GetSkill() / (1.0f + turn);
SetAimOffset( accuracy );
// define time when aim offset will automatically be updated
// longer time the more we had to turn (surprise)
m_aimOffsetTimestamp = gpGlobals->curtime + RandomFloat( 0.25f + turn, 1.5f );
// forget any look at targets we have
ClearLookAt();
}
//--------------------------------------------------------------------------------------------------------------
/**
* Exit the Attack state
*/
void CCSBot::StopAttacking( void )
{
PrintIfWatched( "ATTACK END\n" );
m_attackState.OnExit( this );
m_isAttacking = false;
// if we are following someone, go to the Idle state after the attack to decide whether we still want to follow
if (IsFollowing())
{
Idle();
}
}
//--------------------------------------------------------------------------------------------------------------
bool CCSBot::IsAttacking( void ) const
{
return m_isAttacking;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return true if we are escaping from the bomb
*/
bool CCSBot::IsEscapingFromBomb( void ) const
{
if (m_state == static_cast<const BotState *>( &m_escapeFromBombState ))
return true;
return false;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return true if we are defusing the bomb
*/
bool CCSBot::IsDefusingBomb( void ) const
{
if (m_state == static_cast<const BotState *>( &m_defuseBombState ))
return true;
return false;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return true if we are hiding
*/
bool CCSBot::IsHiding( void ) const
{
if (m_state == static_cast<const BotState *>( &m_hideState ))
return true;
return false;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return true if we are hiding and at our hiding spot
*/
bool CCSBot::IsAtHidingSpot( void ) const
{
if (!IsHiding())
return false;
return m_hideState.IsAtSpot();
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return number of seconds we have been at our current hiding spot
*/
float CCSBot::GetHidingTime( void ) const
{
if (IsHiding())
{
return m_hideState.GetHideTime();
}
return 0.0f;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return true if we are huting
*/
bool CCSBot::IsHunting( void ) const
{
if (m_state == static_cast<const BotState *>( &m_huntState ))
return true;
return false;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return true if we are in the MoveTo state
*/
bool CCSBot::IsMovingTo( void ) const
{
if (m_state == static_cast<const BotState *>( &m_moveToState ))
return true;
return false;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return true if we are buying
*/
bool CCSBot::IsBuying( void ) const
{
if (m_state == static_cast<const BotState *>( &m_buyState ))
return true;
return false;
}
//--------------------------------------------------------------------------------------------------------------
bool CCSBot::IsInvestigatingNoise( void ) const
{
if (m_state == static_cast<const BotState *>( &m_investigateNoiseState ))
return true;
return false;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Move to potentially distant position
*/
void CCSBot::MoveTo( const Vector &pos, RouteType route )
{
m_moveToState.SetGoalPosition( pos );
m_moveToState.SetRouteType( route );
SetState( &m_moveToState );
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::PlantBomb( void )
{
SetState( &m_plantBombState );
}
//--------------------------------------------------------------------------------------------------------------
/**
* Bomb has been dropped - go get it
*/
void CCSBot::FetchBomb( void )
{
SetState( &m_fetchBombState );
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::DefuseBomb( void )
{
SetState( &m_defuseBombState );
}
//--------------------------------------------------------------------------------------------------------------
/**
* Investigate recent enemy noise
*/
void CCSBot::InvestigateNoise( void )
{
SetState( &m_investigateNoiseState );
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::Buy( void )
{
SetState( &m_buyState );
}
//--------------------------------------------------------------------------------------------------------------
/**
* Move to a hiding spot and wait for initial encounter with enemy team.
* Return false if can't do this behavior (ie: no hiding spots available).
*/
bool CCSBot::MoveToInitialEncounter( void )
{
int myTeam = GetTeamNumber();
int enemyTeam = OtherTeam( myTeam );
// build a path to an enemy spawn point
CBaseEntity *enemySpawn = TheCSBots()->GetRandomSpawn( enemyTeam );
if (enemySpawn == NULL)
{
PrintIfWatched( "MoveToInitialEncounter: No enemy spawn points?\n" );
return false;
}
// build a path from us to the enemy spawn
CCSNavPath path;
PathCost cost( this, FASTEST_ROUTE );
path.Compute( WorldSpaceCenter(), enemySpawn->GetAbsOrigin(), cost );
if (!path.IsValid())
{
PrintIfWatched( "MoveToInitialEncounter: Pathfind failed.\n" );
return false;
}
// find battlefront area where teams will first meet along this path
int i;
for( i=0; i<path.GetSegmentCount(); ++i )
{
if (path[i]->area->GetEarliestOccupyTime( myTeam ) > path[i]->area->GetEarliestOccupyTime( enemyTeam ))
{
break;
}
}
if (i == path.GetSegmentCount())
{
PrintIfWatched( "MoveToInitialEncounter: Can't find battlefront!\n" );
return false;
}
/// @todo Remove this evil side-effect
SetInitialEncounterArea( path[i]->area );
// find a hiding spot on our side of the battlefront that has LOS to it
const float maxRange = 1500.0f;
const HidingSpot *spot = FindInitialEncounterSpot( this, path[i]->area->GetCenter(), path[i]->area->GetEarliestOccupyTime( enemyTeam ), maxRange, IsSniper() );
if (spot == NULL)
{
PrintIfWatched( "MoveToInitialEncounter: Can't find a hiding spot\n" );
return false;
}
float timeToWait = path[i]->area->GetEarliestOccupyTime( enemyTeam ) - spot->GetArea()->GetEarliestOccupyTime( myTeam );
float minWaitTime = 4.0f * GetProfile()->GetAggression() + 3.0f;
if (timeToWait < minWaitTime)
{
timeToWait = minWaitTime;
}
Hide( spot->GetPosition(), timeToWait );
return true;
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,22 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
// Author: Michael Booth (mike@turtlerockstudios.com), 2003
// Author: Matthew D. Campbell (matt@turtlerockstudios.com), 2003
#include "cbase.h"
#include "cs_bot.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//--------------------------------------------------------------------------------------------------------
//
// Temporary solution until we have time to build something more elegant
// Very nasty - need to keep in sync with the buy aliases
// NOTE: Array must be NULL terminated
//
+767
View File
@@ -0,0 +1,767 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Encapsulation of the current scenario/game state. Allows each bot imperfect knowledge.
//
// $NoKeywords: $
//=============================================================================//
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
#include "cbase.h"
#include "KeyValues.h"
#include "cs_bot.h"
#include "cs_gamestate.h"
#include "cs_simple_hostage.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//--------------------------------------------------------------------------------------------------------------
CSGameState::CSGameState( CCSBot *owner )
{
m_owner = owner;
m_isRoundOver = false;
m_bombState = MOVING;
m_lastSawBomber.Invalidate();
m_lastSawLooseBomb.Invalidate();
m_isPlantedBombPosKnown = false;
m_plantedBombsite = UNKNOWN;
m_bombsiteCount = 0;
m_bombsiteSearchIndex = 0;
for( int i=0; i<MAX_HOSTAGES; ++i )
{
m_hostage[i].hostage = NULL;
m_hostage[i].isValid = false;
m_hostage[i].isAlive = false;
m_hostage[i].isFree = true;
m_hostage[i].knownPos = Vector( 0, 0, 0 );
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Reset at round start
*/
void CSGameState::Reset( void )
{
m_isRoundOver = false;
// bomb -----------------------------------------------------------------------
m_bombState = MOVING;
m_lastSawBomber.Invalidate();
m_lastSawLooseBomb.Invalidate();
m_isPlantedBombPosKnown = false;
m_plantedBombsite = UNKNOWN;
m_bombsiteCount = TheCSBots()->GetZoneCount();
int i;
for( i=0; i<m_bombsiteCount; ++i )
{
m_isBombsiteClear[i] = false;
m_bombsiteSearchOrder[i] = i;
}
// shuffle the bombsite search order
// allows T's to plant at random site, and TEAM_CT's to search in a random order
// NOTE: VS6 std::random_shuffle() doesn't work well with an array of two elements (most maps)
for( i=0; i < m_bombsiteCount; ++i )
{
int swap = m_bombsiteSearchOrder[i];
int rnd = RandomInt( i, m_bombsiteCount-1 );
m_bombsiteSearchOrder[i] = m_bombsiteSearchOrder[ rnd ];
m_bombsiteSearchOrder[ rnd ] = swap;
}
m_bombsiteSearchIndex = 0;
// hostage ---------------------------------------------------------------------
InitializeHostageInfo();
}
//--------------------------------------------------------------------------------------------------------------
/**
* Update game state based on events we have received
*/
void CSGameState::OnHostageRescuedAll( IGameEvent *event )
{
m_allHostagesRescued = true;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Update game state based on events we have received
*/
void CSGameState::OnRoundEnd( IGameEvent *event )
{
m_isRoundOver = true;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Update game state based on events we have received
*/
void CSGameState::OnRoundStart( IGameEvent *event )
{
Reset();
}
//--------------------------------------------------------------------------------------------------------------
/**
* Update game state based on events we have received
*/
void CSGameState::OnBombPlanted( IGameEvent *event )
{
// change state - the event is announced to everyone
SetBombState( PLANTED );
CBasePlayer *plantingPlayer = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
// Terrorists always know where the bomb is
if (m_owner->GetTeamNumber() == TEAM_TERRORIST && plantingPlayer)
{
UpdatePlantedBomb( plantingPlayer->GetAbsOrigin() );
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Update game state based on events we have received
*/
void CSGameState::OnBombDefused( IGameEvent *event )
{
// change state - the event is announced to everyone
SetBombState( DEFUSED );
}
//--------------------------------------------------------------------------------------------------------------
/**
* Update game state based on events we have received
*/
void CSGameState::OnBombExploded( IGameEvent *event )
{
// change state - the event is announced to everyone
SetBombState( EXPLODED );
}
//--------------------------------------------------------------------------------------------------------------
/**
* True if round has been won or lost (but not yet reset)
*/
bool CSGameState::IsRoundOver( void ) const
{
return m_isRoundOver;
}
//--------------------------------------------------------------------------------------------------------------
void CSGameState::SetBombState( BombState state )
{
// if state changed, reset "last seen" timestamps
if (m_bombState != state)
{
m_bombState = state;
}
}
//--------------------------------------------------------------------------------------------------------------
void CSGameState::UpdateLooseBomb( const Vector &pos )
{
m_looseBombPos = pos;
m_lastSawLooseBomb.Reset();
// we saw the loose bomb, update our state
SetBombState( LOOSE );
}
//--------------------------------------------------------------------------------------------------------------
float CSGameState::TimeSinceLastSawLooseBomb( void ) const
{
return m_lastSawLooseBomb.GetElapsedTime();
}
//--------------------------------------------------------------------------------------------------------------
bool CSGameState::IsLooseBombLocationKnown( void ) const
{
if (m_bombState != LOOSE)
return false;
return (m_lastSawLooseBomb.HasStarted()) ? true : false;
}
//--------------------------------------------------------------------------------------------------------------
void CSGameState::UpdateBomber( const Vector &pos )
{
m_bomberPos = pos;
m_lastSawBomber.Reset();
// we saw the bomber, update our state
SetBombState( MOVING );
}
//--------------------------------------------------------------------------------------------------------------
float CSGameState::TimeSinceLastSawBomber( void ) const
{
return m_lastSawBomber.GetElapsedTime();
}
//--------------------------------------------------------------------------------------------------------------
bool CSGameState::IsPlantedBombLocationKnown( void ) const
{
if (m_bombState != PLANTED)
return false;
return m_isPlantedBombPosKnown;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return the zone index of the planted bombsite, or UNKNOWN
*/
int CSGameState::GetPlantedBombsite( void ) const
{
if (m_bombState != PLANTED)
return UNKNOWN;
return m_plantedBombsite;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return true if we are currently in the bombsite where the bomb is planted
*/
bool CSGameState::IsAtPlantedBombsite( void ) const
{
if (m_bombState != PLANTED)
return false;
Vector myOrigin = GetCentroid( m_owner );
const CCSBotManager::Zone *zone = TheCSBots()->GetClosestZone( myOrigin );
if (zone)
{
return (m_plantedBombsite == zone->m_index);
}
return false;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return the zone index of the next bombsite to search
*/
int CSGameState::GetNextBombsiteToSearch( void )
{
if (m_bombsiteCount <= 0)
return 0;
int i;
// return next non-cleared bombsite index
for( i=m_bombsiteSearchIndex; i<m_bombsiteCount; ++i )
{
int z = m_bombsiteSearchOrder[i];
if (!m_isBombsiteClear[z])
{
m_bombsiteSearchIndex = i;
return z;
}
}
// all the bombsites are clear, someone must have been mistaken - start search over
for( i=0; i<m_bombsiteCount; ++i )
m_isBombsiteClear[i] = false;
m_bombsiteSearchIndex = 0;
return GetNextBombsiteToSearch();
}
//--------------------------------------------------------------------------------------------------------------
/**
* Returns position of bomb in its various states (moving, loose, planted),
* or NULL if we don't know where the bomb is
*/
const Vector *CSGameState::GetBombPosition( void ) const
{
switch( m_bombState )
{
case MOVING:
{
if (!m_lastSawBomber.HasStarted())
return NULL;
return &m_bomberPos;
}
case LOOSE:
{
if (IsLooseBombLocationKnown())
return &m_looseBombPos;
return NULL;
}
case PLANTED:
{
if (IsPlantedBombLocationKnown())
return &m_plantedBombPos;
return NULL;
}
}
return NULL;
}
//--------------------------------------------------------------------------------------------------------------
/**
* We see the planted bomb at 'pos'
*/
void CSGameState::UpdatePlantedBomb( const Vector &pos )
{
const CCSBotManager::Zone *zone = TheCSBots()->GetClosestZone( pos );
if (zone == NULL)
{
CONSOLE_ECHO( "ERROR: Bomb planted outside of a zone!\n" );
m_plantedBombsite = UNKNOWN;
}
else
{
m_plantedBombsite = zone->m_index;
}
m_plantedBombPos = pos;
m_isPlantedBombPosKnown = true;
SetBombState( PLANTED );
}
//--------------------------------------------------------------------------------------------------------------
/**
* Someone told us where the bomb is planted
*/
void CSGameState::MarkBombsiteAsPlanted( int zoneIndex )
{
m_plantedBombsite = zoneIndex;
SetBombState( PLANTED );
}
//--------------------------------------------------------------------------------------------------------------
/**
* Someone told us a bombsite is clear
*/
void CSGameState::ClearBombsite( int zoneIndex )
{
if (zoneIndex >= 0 && zoneIndex < m_bombsiteCount)
m_isBombsiteClear[ zoneIndex ] = true;
}
//--------------------------------------------------------------------------------------------------------------
bool CSGameState::IsBombsiteClear( int zoneIndex ) const
{
if (zoneIndex >= 0 && zoneIndex < m_bombsiteCount)
return m_isBombsiteClear[ zoneIndex ];
return false;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Initialize our knowledge of the number and location of hostages
*/
void CSGameState::InitializeHostageInfo( void )
{
m_hostageCount = 0;
m_allHostagesRescued = false;
m_haveSomeHostagesBeenTaken = false;
for( int i=0; i<g_Hostages.Count(); ++i )
{
m_hostage[ m_hostageCount ].hostage = g_Hostages[i];
m_hostage[ m_hostageCount ].knownPos = g_Hostages[i]->GetAbsOrigin();
m_hostage[ m_hostageCount ].isValid = true;
m_hostage[ m_hostageCount ].isAlive = true;
m_hostage[ m_hostageCount ].isFree = true;
++m_hostageCount;
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return the closest free and live hostage
* If we are a CT this information is perfect.
* Otherwise, this is based on our individual memory of the game state.
* If NULL is returned, we don't think there are any hostages left, or we dont know where they are.
* NOTE: a T can remember a hostage who has died. knowPos will be filled in, but NULL will be
* returned, since CHostages get deleted when they die.
*/
CHostage *CSGameState::GetNearestFreeHostage( Vector *knowPos ) const
{
if (m_owner == NULL)
return NULL;
CNavArea *startArea = m_owner->GetLastKnownArea();
if (startArea == NULL)
return NULL;
CHostage *close = NULL;
Vector closePos( 0, 0, 0 );
float closeDistance = 9999999999.9f;
for( int i=0; i<m_hostageCount; ++i )
{
CHostage *hostage = m_hostage[i].hostage;
Vector hostagePos;
if (m_owner->GetTeamNumber() == TEAM_CT)
{
// we know exactly where the hostages are, and if they are alive
if (!m_hostage[i].hostage || !m_hostage[i].hostage->IsValid())
continue;
if (m_hostage[i].hostage->IsFollowingSomeone())
continue;
hostagePos = m_hostage[i].hostage->GetAbsOrigin();
}
else
{
// use our memory of where we think the hostages are
if (m_hostage[i].isValid == false)
continue;
hostagePos = m_hostage[i].knownPos;
}
CNavArea *hostageArea = TheNavMesh->GetNearestNavArea( hostagePos );
if (hostageArea)
{
ShortestPathCost cost;
float travelDistance = NavAreaTravelDistance( startArea, hostageArea, cost );
if (travelDistance >= 0.0f && travelDistance < closeDistance)
{
closeDistance = travelDistance;
closePos = hostagePos;
close = hostage;
}
}
}
// return where we think the hostage is
if (knowPos && close)
*knowPos = closePos;
return close;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return the location of a "free" hostage, or NULL if we dont know of any
*/
const Vector *CSGameState::GetRandomFreeHostagePosition( void ) const
{
if (m_owner == NULL)
return NULL;
static Vector freePos[ MAX_HOSTAGES ];
int freeCount = 0;
for( int i=0; i<m_hostageCount; ++i )
{
const HostageInfo *info = &m_hostage[i];
if (m_owner->GetTeamNumber() == TEAM_CT)
{
// we know exactly where the hostages are, and if they are alive
if (!info->hostage || !info->hostage->IsAlive())
continue;
// escorted hostages are not "free"
if (info->hostage->IsFollowingSomeone())
continue;
freePos[ freeCount++ ] = info->hostage->GetAbsOrigin();
}
else
{
// use our memory of where we think the hostages are
if (info->isValid == false)
continue;
freePos[ freeCount++ ] = info->knownPos;
}
}
if (freeCount)
{
return &freePos[ RandomInt( 0, freeCount-1 ) ];
}
return NULL;
}
//--------------------------------------------------------------------------------------------------------------
/**
* If we can see any of the positions where we think a hostage is, validate it
* Return status of any changes (a hostage died or was moved)
*/
unsigned char CSGameState::ValidateHostagePositions( void )
{
// limit how often we validate
if (!m_validateInterval.IsElapsed())
return NO_CHANGE;
const float validateInterval = 0.5f;
m_validateInterval.Start( validateInterval );
// check the status of hostages
unsigned char status = NO_CHANGE;
int i;
int startValidCount = 0;
for( i=0; i<m_hostageCount; ++i )
if (m_hostage[i].isValid)
++startValidCount;
for( i=0; i<m_hostageCount; ++i )
{
HostageInfo *info = &m_hostage[i];
if (!info->hostage )
continue;
// if we can see a hostage, update our knowledge of it
Vector pos = info->hostage->GetAbsOrigin() + Vector( 0, 0, HalfHumanHeight );
if (m_owner->IsVisible( pos, CHECK_FOV ))
{
if (info->hostage->IsAlive())
{
// live hostage
// if hostage is being escorted by a CT, we don't "see" it, we see the CT
if (info->hostage->IsFollowingSomeone())
{
info->isValid = false;
}
else
{
info->knownPos = info->hostage->GetAbsOrigin();
info->isValid = true;
}
}
else
{
// dead hostage
// if we thought it was alive, this is news to us
if (info->isAlive)
status |= HOSTAGE_DIED;
info->isAlive = false;
info->isValid = false;
}
continue;
}
// if we dont know where this hostage is, nothing to validate
if (!info->isValid)
continue;
// can't directly see this hostage
// check line of sight to where we think this hostage is, to see if we noticed that is has moved
pos = info->knownPos + Vector( 0, 0, HalfHumanHeight );
if (m_owner->IsVisible( pos, CHECK_FOV ))
{
// we can see where we thought the hostage was - verify it is still there and alive
if (!info->hostage->IsValid())
{
// since we have line of sight to an invalid hostage, it must be dead
// discovered that hostage has been killed
status |= HOSTAGE_DIED;
info->isAlive = false;
info->isValid = false;
continue;
}
if (info->hostage->IsFollowingSomeone())
{
// discovered the hostage has been taken
status |= HOSTAGE_GONE;
info->isValid = false;
continue;
}
const float tolerance = 50.0f;
if ((info->hostage->GetAbsOrigin() - info->knownPos).IsLengthGreaterThan( tolerance ))
{
// discovered that hostage has been moved
status |= HOSTAGE_GONE;
info->isValid = false;
continue;
}
}
}
int endValidCount = 0;
for( i=0; i<m_hostageCount; ++i )
if (m_hostage[i].isValid)
++endValidCount;
if (endValidCount == 0 && startValidCount > 0)
{
// we discovered all the hostages are gone
status &= ~HOSTAGE_GONE;
status |= HOSTAGES_ALL_GONE;
}
return status;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return the nearest visible free hostage
* Since we can actually see any hostage we return, we know its actual position
*/
CHostage *CSGameState::GetNearestVisibleFreeHostage( void ) const
{
CHostage *close = NULL;
float closeRangeSq = 999999999.9f;
float rangeSq;
Vector pos;
Vector myOrigin = GetCentroid( m_owner );
for( int i=0; i<m_hostageCount; ++i )
{
const HostageInfo *info = &m_hostage[i];
if ( !info->hostage )
continue;
// if the hostage is dead or rescued, its not free
if (!info->hostage->IsAlive())
continue;
// if this hostage is following someone, its not free
if (info->hostage->IsFollowingSomeone())
continue;
/// @todo Use travel distance here
pos = info->hostage->GetAbsOrigin();
rangeSq = (pos - myOrigin).LengthSqr();
if (rangeSq < closeRangeSq)
{
if (!m_owner->IsVisible( pos ))
continue;
close = info->hostage;
closeRangeSq = rangeSq;
}
}
return close;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return true if there are no free hostages
*/
bool CSGameState::AreAllHostagesBeingRescued( void ) const
{
// if the hostages have all been rescued, they are not being rescued any longer
if (m_allHostagesRescued)
return false;
bool isAllDead = true;
for( int i=0; i<m_hostageCount; ++i )
{
const HostageInfo *info = &m_hostage[i];
if (m_owner->GetTeamNumber() == TEAM_CT)
{
// CT's have perfect knowledge via their radar
if (info->hostage && info->hostage->IsValid())
{
if (!info->hostage->IsFollowingSomeone())
return false;
isAllDead = false;
}
}
else
{
if (info->isValid && info->isAlive)
return false;
if (info->isAlive)
isAllDead = false;
}
}
// if all of the remaining hostages are dead, they arent being rescued
if (isAllDead)
return false;
return true;
}
//--------------------------------------------------------------------------------------------------------------
/**
* All hostages have been rescued or are dead
*/
bool CSGameState::AreAllHostagesGone( void ) const
{
if (m_allHostagesRescued)
return true;
// do we know that all the hostages are dead
for( int i=0; i<m_hostageCount; ++i )
{
const HostageInfo *info = &m_hostage[i];
if (m_owner->GetTeamNumber() == TEAM_CT)
{
// CT's have perfect knowledge via their radar
if (info->hostage && info->hostage->IsAlive())
return false;
}
else
{
if (info->isValid && info->isAlive)
return false;
}
}
return true;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Someone told us all the hostages are gone
*/
void CSGameState::AllHostagesGone( void )
{
for( int i=0; i<m_hostageCount; ++i )
m_hostage[i].isValid = false;
}
+151
View File
@@ -0,0 +1,151 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
#ifndef _GAME_STATE_H_
#define _GAME_STATE_H_
#include "bot_util.h"
class CHostage;
class CCSBot;
/**
* This class represents the game state as known by a particular bot
*/
class CSGameState
{
public:
CSGameState( CCSBot *owner );
void Reset( void );
// Event handling
void OnHostageRescuedAll( IGameEvent *event );
void OnRoundEnd( IGameEvent *event );
void OnRoundStart( IGameEvent *event );
void OnBombPlanted( IGameEvent *event );
void OnBombDefused( IGameEvent *event );
void OnBombExploded( IGameEvent *event );
bool IsRoundOver( void ) const; ///< true if round has been won or lost (but not yet reset)
// bomb defuse scenario -----------------------------------------------------------------------------
enum BombState
{
MOVING, ///< being carried by a Terrorist
LOOSE, ///< loose on the ground somewhere
PLANTED, ///< planted and ticking
DEFUSED, ///< the bomb has been defused
EXPLODED ///< the bomb has exploded
};
bool IsBombMoving( void ) const { return (m_bombState == MOVING); }
bool IsBombLoose( void ) const { return (m_bombState == LOOSE); }
bool IsBombPlanted( void ) const { return (m_bombState == PLANTED); }
bool IsBombDefused( void ) const { return (m_bombState == DEFUSED); }
bool IsBombExploded( void ) const { return (m_bombState == EXPLODED); }
void UpdateLooseBomb( const Vector &pos ); ///< we see the loose bomb
float TimeSinceLastSawLooseBomb( void ) const; ///< how long has is been since we saw the loose bomb
bool IsLooseBombLocationKnown( void ) const; ///< do we know where the loose bomb is
void UpdateBomber( const Vector &pos ); ///< we see the bomber
float TimeSinceLastSawBomber( void ) const; ///< how long has is been since we saw the bomber
void UpdatePlantedBomb( const Vector &pos ); ///< we see the planted bomb
bool IsPlantedBombLocationKnown( void ) const; ///< do we know where the bomb was planted
void MarkBombsiteAsPlanted( int zoneIndex ); ///< mark bombsite as the location of the planted bomb
enum { UNKNOWN = -1 };
int GetPlantedBombsite( void ) const; ///< return the zone index of the planted bombsite, or UNKNOWN
bool IsAtPlantedBombsite( void ) const; ///< return true if we are currently in the bombsite where the bomb is planted
int GetNextBombsiteToSearch( void ); ///< return the zone index of the next bombsite to search
bool IsBombsiteClear( int zoneIndex ) const; ///< return true if given bombsite has been cleared
void ClearBombsite( int zoneIndex ); ///< mark bombsite as clear
const Vector *GetBombPosition( void ) const; ///< return where we think the bomb is, or NULL if we don't know
// hostage rescue scenario ------------------------------------------------------------------------
CHostage *GetNearestFreeHostage( Vector *knowPos = NULL ) const; ///< return the closest free hostage, and where we think it is (knowPos)
const Vector *GetRandomFreeHostagePosition( void ) const;
bool AreAllHostagesBeingRescued( void ) const; ///< return true if there are no free hostages
bool AreAllHostagesGone( void ) const; ///< all hostages have been rescued or are dead
void AllHostagesGone( void ); ///< someone told us all the hostages are gone
bool HaveSomeHostagesBeenTaken( void ) const ///< return true if one or more hostages have been moved by the CT's
{
return m_haveSomeHostagesBeenTaken;
}
void HostageWasTaken( void ) ///< someone told us a CT is talking to a hostage
{
m_haveSomeHostagesBeenTaken = true;
}
CHostage *GetNearestVisibleFreeHostage( void ) const;
enum ValidateStatusType
{
NO_CHANGE = 0x00,
HOSTAGE_DIED = 0x01,
HOSTAGE_GONE = 0x02,
HOSTAGES_ALL_GONE = 0x04
};
unsigned char ValidateHostagePositions( void ); ///< update our knowledge with what we currently see - returns bitflag events
private:
CCSBot *m_owner; ///< who owns this gamestate
bool m_isRoundOver; ///< true if round is over, but no yet reset
// bomb defuse scenario ---------------------------------------------------------------------------
void SetBombState( BombState state );
BombState GetBombState( void ) const { return m_bombState; }
BombState m_bombState; ///< what we think the bomb is doing
IntervalTimer m_lastSawBomber;
Vector m_bomberPos;
IntervalTimer m_lastSawLooseBomb;
Vector m_looseBombPos;
bool m_isBombsiteClear[ CCSBotManager::MAX_ZONES ]; ///< corresponds to zone indices in CCSBotManager
int m_bombsiteSearchOrder[ CCSBotManager::MAX_ZONES ]; ///< randomized order of bombsites to search
int m_bombsiteCount;
int m_bombsiteSearchIndex; ///< the next step in the search
int m_plantedBombsite; ///< zone index of the bombsite where the planted bomb is
bool m_isPlantedBombPosKnown; ///< if true, we know the exact location of the bomb
Vector m_plantedBombPos;
// hostage rescue scenario ------------------------------------------------------------------------
struct HostageInfo
{
CHandle<CHostage> hostage;
Vector knownPos;
bool isValid;
bool isAlive;
bool isFree; ///< not being escorted by a CT
}
m_hostage[ MAX_HOSTAGES ];
int m_hostageCount; ///< number of hostages left in map
CountdownTimer m_validateInterval;
CBaseEntity *GetNearestHostage( void ) const; ///< return the closest live hostage
void InitializeHostageInfo( void ); ///< initialize our knowledge of the number and location of hostages
bool m_allHostagesRescued;
bool m_haveSomeHostagesBeenTaken; ///< true if a hostage has been moved by a CT (and we've seen it)
};
#endif // _GAME_STATE_
@@ -0,0 +1,710 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
#include "cbase.h"
#include "cs_bot.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//--------------------------------------------------------------------------------------------------------------
/**
* Begin attacking
*/
void AttackState::OnEnter( CCSBot *me )
{
CBasePlayer *enemy = me->GetBotEnemy();
// store our posture when the attack began
me->PushPostureContext();
me->DestroyPath();
// if we are using a knife, try to sneak up on the enemy
if (enemy && me->IsUsingKnife() && !me->IsPlayerFacingMe( enemy ))
me->Walk();
else
me->Run();
me->GetOffLadder();
me->ResetStuckMonitor();
m_repathTimer.Invalidate();
m_haveSeenEnemy = me->IsEnemyVisible();
m_nextDodgeStateTimestamp = 0.0f;
m_firstDodge = true;
m_isEnemyHidden = false;
m_reacquireTimestamp = 0.0f;
m_pinnedDownTimestamp = gpGlobals->curtime + RandomFloat( 7.0f, 10.0f );
m_shieldToggleTimestamp = gpGlobals->curtime + RandomFloat( 2.0f, 10.0f );
m_shieldForceOpen = false;
// if we encountered someone while escaping, grab our weapon and fight!
if (me->IsEscapingFromBomb())
me->EquipBestWeapon();
if (me->IsUsingKnife())
{
// can't crouch and hold with a knife
m_crouchAndHold = false;
me->StandUp();
}
else if (me->CanSeeSniper() && !me->IsSniper())
{
// don't sit still if we see a sniper!
m_crouchAndHold = false;
me->StandUp();
}
else
{
// decide whether to crouch where we are, or run and gun (if we havent already - see CCSBot::Attack())
if (!m_crouchAndHold)
{
if (enemy)
{
const float crouchFarRange = 750.0f;
float crouchChance;
// more likely to crouch if using sniper rifle or if enemy is far away
if (me->IsUsingSniperRifle())
crouchChance = 50.0f;
else if ((GetCentroid( me ) - GetCentroid( enemy )).IsLengthGreaterThan( crouchFarRange ))
crouchChance = 50.0f;
else
crouchChance = 20.0f * (1.0f - me->GetProfile()->GetAggression());
if (RandomFloat( 0.0f, 100.0f ) < crouchChance)
{
// make sure we can still see if we crouch
trace_t result;
Vector origin = GetCentroid( me );
if (!me->IsCrouching())
{
// we are standing, adjust for lower crouch origin
origin.z -= 20.0f;
}
UTIL_TraceLine( origin, enemy->EyePosition(), MASK_PLAYERSOLID, me, COLLISION_GROUP_NONE, &result );
if (result.fraction == 1.0f)
{
m_crouchAndHold = true;
}
}
}
}
if (m_crouchAndHold)
{
me->Crouch();
me->PrintIfWatched( "Crouch and hold attack!\n" );
}
}
m_scopeTimestamp = 0;
m_didAmbushCheck = false;
float skill = me->GetProfile()->GetSkill();
// tendency to dodge is proportional to skill
float dodgeChance = 80.0f * skill;
// high skill bots always dodge if outnumbered, or they see a sniper
if (skill > 0.5f && (me->IsOutnumbered() || me->CanSeeSniper()))
{
dodgeChance = 100.0f;
}
m_shouldDodge = (RandomFloat( 0, 100 ) <= dodgeChance);
// decide whether we might bail out of this fight
m_isCoward = (RandomFloat( 0, 100 ) > 100.0f * me->GetProfile()->GetAggression());
}
//--------------------------------------------------------------------------------------------------------------
/**
* When we are done attacking, this is invoked
*/
void AttackState::StopAttacking( CCSBot *me )
{
if (me->GetTask() == CCSBot::SNIPING)
{
// stay in our hiding spot
me->Hide( me->GetLastKnownArea(), -1.0f, 50.0f );
}
else
{
me->StopAttacking();
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Do dodge behavior
*/
void AttackState::Dodge( CCSBot *me )
{
//
// Dodge.
// If sniping or crouching, stand still.
//
if (m_shouldDodge && !me->IsUsingSniperRifle() && !m_crouchAndHold)
{
CBasePlayer *enemy = me->GetBotEnemy();
if (enemy == NULL)
{
return;
}
Vector toEnemy = enemy->GetAbsOrigin() - me->GetAbsOrigin();
float range = toEnemy.Length();
const float hysterisRange = 125.0f; // (+/-) m_combatRange
float minRange = me->GetCombatRange() - hysterisRange;
float maxRange = me->GetCombatRange() + hysterisRange;
if (me->IsUsingKnife())
{
// dodge when far away if armed only with a knife
maxRange = 999999.9f;
}
// move towards (or away from) enemy if we are using a knife, behind a corner, or we aren't very skilled
if (me->GetProfile()->GetSkill() < 0.66f || !me->IsEnemyVisible())
{
if (range > maxRange)
me->MoveForward();
else if (range < minRange)
me->MoveBackward();
}
// don't dodge if enemy is facing away
const float dodgeRange = 2000.0f;
if (!me->CanSeeSniper() && (range > dodgeRange || !me->IsPlayerFacingMe( enemy )))
{
m_dodgeState = STEADY_ON;
m_nextDodgeStateTimestamp = 0.0f;
}
else if (gpGlobals->curtime >= m_nextDodgeStateTimestamp)
{
int next;
// high-skill bots keep moving and don't jump if they see a sniper
if (me->GetProfile()->GetSkill() > 0.5f && me->CanSeeSniper())
{
// juke back and forth
if (m_firstDodge)
{
next = (RandomInt( 0, 100 ) < 50) ? SLIDE_RIGHT : SLIDE_LEFT;
}
else
{
next = (m_dodgeState == SLIDE_LEFT) ? SLIDE_RIGHT : SLIDE_LEFT;
}
}
else
{
// select next dodge state that is different that our current one
do
{
// low-skill bots may jump when first engaging the enemy (if they are moving)
const float jumpChance = 33.3f;
if (m_firstDodge && me->GetProfile()->GetSkill() < 0.5f && RandomFloat( 0, 100 ) < jumpChance && !me->IsNotMoving())
next = RandomInt( 0, NUM_ATTACK_STATES-1 );
else
next = RandomInt( 0, NUM_ATTACK_STATES-2 );
}
while( !m_firstDodge && next == m_dodgeState );
}
m_dodgeState = (DodgeStateType)next;
m_nextDodgeStateTimestamp = gpGlobals->curtime + RandomFloat( 0.3f, 1.0f );
m_firstDodge = false;
}
Vector forward, right;
me->EyeVectors( &forward, &right );
const float lookAheadRange = 30.0f;
float ground;
switch( m_dodgeState )
{
case STEADY_ON:
{
break;
}
case SLIDE_LEFT:
{
// don't move left if we will fall
Vector pos = me->GetAbsOrigin() - (lookAheadRange * right);
if (me->GetSimpleGroundHeightWithFloor( pos, &ground ))
{
if (me->GetAbsOrigin().z - ground < StepHeight)
{
me->StrafeLeft();
}
}
break;
}
case SLIDE_RIGHT:
{
// don't move left if we will fall
Vector pos = me->GetAbsOrigin() + (lookAheadRange * right);
if (me->GetSimpleGroundHeightWithFloor( pos, &ground ))
{
if (me->GetAbsOrigin().z - ground < StepHeight)
{
me->StrafeRight();
}
}
break;
}
case JUMP:
{
if (me->m_isEnemyVisible)
{
me->Jump();
}
break;
}
}
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Perform attack behavior
*/
void AttackState::OnUpdate( CCSBot *me )
{
// can't be stuck while attacking
me->ResetStuckMonitor();
// if we somehow ended up with the C4 or a grenade in our hands, grab our weapon!
CWeaponCSBase *weapon = me->GetActiveCSWeapon();
if (weapon)
{
if (weapon->GetWeaponID() == WEAPON_C4 ||
weapon->GetWeaponID() == WEAPON_HEGRENADE ||
weapon->GetWeaponID() == WEAPON_FLASHBANG ||
weapon->GetWeaponID() == WEAPON_SMOKEGRENADE)
{
me->EquipBestWeapon();
}
}
CBasePlayer *enemy = me->GetBotEnemy();
if (enemy == NULL)
{
StopAttacking( me );
return;
}
Vector myOrigin = GetCentroid( me );
Vector enemyOrigin = GetCentroid( enemy );
// keep track of whether we have seen our enemy at least once yet
if (!m_haveSeenEnemy)
m_haveSeenEnemy = me->IsEnemyVisible();
//
// Retreat check
// Do not retreat if the enemy is too close
//
if (m_retreatTimer.IsElapsed())
{
// If we've been fighting this battle for awhile, we're "pinned down" and
// need to do something else.
// If we are outnumbered, retreat.
// If we see a sniper and we aren't a sniper, retreat.
bool isPinnedDown = (gpGlobals->curtime > m_pinnedDownTimestamp);
if (isPinnedDown ||
(me->CanSeeSniper() && !me->IsSniper()) ||
(me->IsOutnumbered() && m_isCoward) ||
(me->OutnumberedCount() >= 2 && me->GetProfile()->GetAggression() < 1.0f))
{
// only retreat if at least one of them is aiming at me
if (me->IsAnyVisibleEnemyLookingAtMe( CHECK_FOV ))
{
// tell our teammates our plight
if (isPinnedDown)
me->GetChatter()->PinnedDown();
else if (!me->CanSeeSniper())
me->GetChatter()->Scared();
m_retreatTimer.Start( RandomFloat( 3.0f, 15.0f ) );
// try to retreat
if (me->TryToRetreat())
{
// if we are a sniper, equip our pistol so we can fire while retreating
/*
if (me->IsUsingSniperRifle())
{
// wait a moment to allow one last shot
me->Wait( 0.5f );
//me->EquipPistol();
}
*/
// request backup if outnumbered
if (me->IsOutnumbered())
{
me->GetChatter()->NeedBackup();
}
}
else
{
me->PrintIfWatched( "I want to retreat, but no safe spots nearby!\n" );
}
}
}
}
//
// Knife fighting
// We need to pathfind right to the enemy to cut him
//
if (me->IsUsingKnife())
{
// can't crouch and hold with a knife
m_crouchAndHold = false;
me->StandUp();
// if we are using a knife and our prey is looking towards us, run at him
if (me->IsPlayerFacingMe( enemy ))
{
me->ForceRun( 5.0f );
me->Hurry( 10.0f );
}
// slash our victim
me->FireWeaponAtEnemy();
// if toe to toe with our enemy, don't dodge, just slash
const float slashRange = 70.0f;
if ((enemy->GetAbsOrigin() - me->GetAbsOrigin()).IsLengthGreaterThan( slashRange ))
{
const float repathInterval = 0.5f;
// if our victim has moved, repath
bool repath = false;
if (me->HasPath())
{
const float repathRange = 100.0f; // 50
if ((me->GetPathEndpoint() - enemy->GetAbsOrigin()).IsLengthGreaterThan( repathRange ))
{
repath = true;
}
}
else
{
repath = true;
}
if (repath && m_repathTimer.IsElapsed())
{
Vector enemyPos = enemy->GetAbsOrigin() + Vector( 0, 0, HalfHumanHeight );
me->ComputePath( enemyPos, FASTEST_ROUTE );
m_repathTimer.Start( repathInterval );
}
// move towards victim
if (me->UpdatePathMovement( NO_SPEED_CHANGE ) != CCSBot::PROGRESSING)
{
me->DestroyPath();
}
}
return;
}
//
// Simple shield usage
//
if (me->HasShield())
{
if (me->IsEnemyVisible() && !m_shieldForceOpen)
{
if (!me->IsRecognizedEnemyReloading() && !me->IsReloading() && me->IsPlayerLookingAtMe( enemy ))
{
// close up - enemy is pointing his gun at us
if (!me->IsProtectedByShield())
me->SecondaryAttack();
}
else
{
// enemy looking away or reloading his weapon - open up and shoot him
if (me->IsProtectedByShield())
me->SecondaryAttack();
}
}
else
{
// can't see enemy, open up
if (me->IsProtectedByShield())
me->SecondaryAttack();
}
if (gpGlobals->curtime > m_shieldToggleTimestamp)
{
m_shieldToggleTimestamp = gpGlobals->curtime + RandomFloat( 0.5, 2.0f );
// toggle shield force open
m_shieldForceOpen = !m_shieldForceOpen;
}
}
// check if our weapon range is bad and we should switch to pistol
if (me->IsUsingSniperRifle())
{
// if we have a sniper rifle and our enemy is too close, switch to pistol
const float sniperMinRange = 160.0f; // NOTE: Must be larger than NO_ZOOM range in AdjustZoom()
if ((enemyOrigin - myOrigin).IsLengthLessThan( sniperMinRange ))
me->EquipPistol();
}
else if (me->IsUsingShotgun())
{
// if we have a shotgun equipped and enemy is too far away, switch to pistol
const float shotgunMaxRange = 600.0f;
if ((enemyOrigin - myOrigin).IsLengthGreaterThan( shotgunMaxRange ))
me->EquipPistol();
}
// if we're sniping, look through the scope - need to do this here in case a reload resets our scope
if (me->IsUsingSniperRifle())
{
// for Scouts and AWPs, we need to wait for zoom to resume
if (me->m_bResumeZoom)
{
m_scopeTimestamp = gpGlobals->curtime;
return;
}
Vector toAimSpot3D = me->m_aimSpot - myOrigin;
float targetRange = toAimSpot3D.Length();
// dont adjust zoom level if we're already zoomed in - just fire
if (me->GetZoomLevel() == CCSBot::NO_ZOOM && me->AdjustZoom( targetRange ))
m_scopeTimestamp = gpGlobals->curtime;
const float waitScopeTime = 0.3f + me->GetProfile()->GetReactionTime();
if (gpGlobals->curtime - m_scopeTimestamp < waitScopeTime)
{
// force us to wait until zoomed in before firing
return;
}
}
// see if we "notice" that our prey is dead
if (me->IsAwareOfEnemyDeath())
{
// let team know if we killed the last enemy
if (me->GetLastVictimID() == enemy->entindex() && me->GetNearbyEnemyCount() <= 1)
{
me->GetChatter()->KilledMyEnemy( enemy->entindex() );
// if there are other enemies left, wait a moment - they usually come in groups
if (me->GetEnemiesRemaining())
{
me->Wait( RandomFloat( 1.0f, 3.0f ) );
}
}
StopAttacking( me );
return;
}
float notSeenEnemyTime = gpGlobals->curtime - me->GetLastSawEnemyTimestamp();
// if we haven't seen our enemy for a moment, continue on if we dont want to fight, or decide to ambush if we do
if (!me->IsEnemyVisible())
{
// attend to nearby enemy gunfire
if (notSeenEnemyTime > 0.5f && me->CanHearNearbyEnemyGunfire())
{
// give up the attack, since we didn't want it in the first place
StopAttacking( me );
const Vector *pos = me->GetNoisePosition();
if (pos)
{
me->SetLookAt( "Nearby enemy gunfire", *pos, PRIORITY_HIGH, 0.0f );
me->PrintIfWatched( "Checking nearby threatening enemy gunfire!\n" );
return;
}
}
// check if we have lost track of our enemy during combat
if (notSeenEnemyTime > 0.25f)
{
m_isEnemyHidden = true;
}
if (notSeenEnemyTime > 0.1f)
{
if (me->GetDisposition() == CCSBot::ENGAGE_AND_INVESTIGATE)
{
// decide whether we should hide and "ambush" our enemy
if (m_haveSeenEnemy && !m_didAmbushCheck)
{
float hideChance = 33.3f;
if (RandomFloat( 0.0, 100.0f ) < hideChance)
{
float ambushTime = RandomFloat( 3.0f, 15.0f );
// hide in ambush nearby
/// @todo look towards where we know enemy is
const Vector *spot = FindNearbyRetreatSpot( me, 200.0f );
if (spot)
{
me->IgnoreEnemies( 1.0f );
me->Run();
me->StandUp();
me->Hide( *spot, ambushTime, true );
return;
}
}
// don't check again
m_didAmbushCheck = true;
}
}
else
{
// give up the attack, since we didn't want it in the first place
StopAttacking( me );
return;
}
}
}
else
{
// we can see the enemy again - reset our ambush check
m_didAmbushCheck = false;
// if the enemy is coming out of hiding, we need time to react
if (m_isEnemyHidden)
{
m_reacquireTimestamp = gpGlobals->curtime + me->GetProfile()->GetReactionTime();
m_isEnemyHidden = false;
}
}
// if we haven't seen our enemy for a long time, chase after them
float chaseTime = 2.0f + 2.0f * (1.0f - me->GetProfile()->GetAggression());
// if we are sniping, be very patient
if (me->IsUsingSniperRifle())
chaseTime += 3.0f;
else if (me->IsCrouching()) // if we are crouching, be a little patient
chaseTime += 1.0f;
// if we can't see the enemy, and have either seen him but currently lost sight of him,
// or haven't yet seen him, chase after him (unless we are a sniper)
if (!me->IsEnemyVisible() && (notSeenEnemyTime > chaseTime || !m_haveSeenEnemy))
{
// snipers don't chase their prey - they wait for their prey to come to them
if (me->GetTask() == CCSBot::SNIPING)
{
StopAttacking( me );
return;
}
else
{
// move to last known position of enemy
me->SetTask( CCSBot::MOVE_TO_LAST_KNOWN_ENEMY_POSITION, enemy );
me->MoveTo( me->GetLastKnownEnemyPosition() );
return;
}
}
// if we can't see our enemy at the moment, and were shot by
// a different visible enemy, engage them instead
const float hurtRecentlyTime = 3.0f;
if (!me->IsEnemyVisible() &&
me->GetTimeSinceAttacked() < hurtRecentlyTime &&
me->GetAttacker() &&
me->GetAttacker() != me->GetBotEnemy())
{
// if we can see them, attack, otherwise panic
if (me->IsVisible( me->GetAttacker(), CHECK_FOV ))
{
me->Attack( me->GetAttacker() );
me->PrintIfWatched( "Switching targets to retaliate against new attacker!\n" );
}
/*
* Rethink this
else
{
me->Panic( me->GetAttacker() );
me->PrintIfWatched( "Panicking from crossfire while attacking!\n" );
}
*/
return;
}
if (true || gpGlobals->curtime > m_reacquireTimestamp)
me->FireWeaponAtEnemy();
// do dodge behavior
Dodge( me );
}
//--------------------------------------------------------------------------------------------------------------
/**
* Finish attack
*/
void AttackState::OnExit( CCSBot *me )
{
me->PrintIfWatched( "AttackState:OnExit()\n" );
m_crouchAndHold = false;
// clear any noises we heard during battle
me->ForgetNoise();
me->ResetStuckMonitor();
// resume our original posture
me->PopPostureContext();
// put shield away
if (me->IsProtectedByShield())
me->SecondaryAttack();
//me->StopAiming();
}
@@ -0,0 +1,690 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
#include "cbase.h"
#include "cs_gamerules.h"
#include "cs_bot.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//--------------------------------------------------------------------------------------------------------------
ConVar bot_loadout( "bot_loadout", "", FCVAR_CHEAT, "bots are given these items at round start" );
ConVar bot_randombuy( "bot_randombuy", "0", FCVAR_CHEAT, "should bots ignore their prefered weapons and just buy weapons at random?" );
//--------------------------------------------------------------------------------------------------------------
/**
* Debug command to give a named weapon
*/
void CCSBot::GiveWeapon( const char *weaponAlias )
{
const char *translatedAlias = GetTranslatedWeaponAlias( weaponAlias );
char wpnName[128];
Q_snprintf( wpnName, sizeof( wpnName ), "weapon_%s", translatedAlias );
WEAPON_FILE_INFO_HANDLE hWpnInfo = LookupWeaponInfoSlot( wpnName );
if ( hWpnInfo == GetInvalidWeaponInfoHandle() )
{
return;
}
CCSWeaponInfo *pWeaponInfo = dynamic_cast< CCSWeaponInfo* >( GetFileWeaponInfoFromHandle( hWpnInfo ) );
if ( !pWeaponInfo )
{
return;
}
if ( !Weapon_OwnsThisType( wpnName ) )
{
CBaseCombatWeapon *pWeapon = Weapon_GetSlot( pWeaponInfo->iSlot );
if ( pWeapon )
{
if ( pWeaponInfo->iSlot == WEAPON_SLOT_PISTOL )
{
DropPistol();
}
else if ( pWeaponInfo->iSlot == WEAPON_SLOT_RIFLE )
{
DropRifle();
}
}
}
GiveNamedItem( wpnName );
}
//--------------------------------------------------------------------------------------------------------------
static bool HasDefaultPistol( CCSBot *me )
{
CWeaponCSBase *pistol = (CWeaponCSBase *)me->Weapon_GetSlot( WEAPON_SLOT_PISTOL );
if (pistol == NULL)
return false;
if (me->GetTeamNumber() == TEAM_TERRORIST && pistol->IsA( WEAPON_GLOCK ))
return true;
if (me->GetTeamNumber() == TEAM_CT && pistol->IsA( WEAPON_USP ))
return true;
return false;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Buy weapons, armor, etc.
*/
void BuyState::OnEnter( CCSBot *me )
{
m_retries = 0;
m_prefRetries = 0;
m_prefIndex = 0;
const char *cheatWeaponString = bot_loadout.GetString();
if ( cheatWeaponString && *cheatWeaponString )
{
m_doneBuying = false; // we're going to be given weapons - ignore the eco limit
}
else
{
// check if we are saving money for the next round
if (me->m_iAccount < cv_bot_eco_limit.GetFloat())
{
me->PrintIfWatched( "Saving money for next round.\n" );
m_doneBuying = true;
}
else
{
m_doneBuying = false;
}
}
m_isInitialDelay = true;
// this will force us to stop holding live grenade
me->EquipBestWeapon( MUST_EQUIP );
m_buyDefuseKit = false;
m_buyShield = false;
if (me->GetTeamNumber() == TEAM_CT)
{
if (TheCSBots()->GetScenario() == CCSBotManager::SCENARIO_DEFUSE_BOMB)
{
// CT's sometimes buy defuse kits in the bomb scenario (except in career mode, where the player should defuse)
if (CSGameRules()->IsCareer() == false)
{
const float buyDefuseKitChance = 100.0f * (me->GetProfile()->GetSkill() + 0.2f);
if (RandomFloat( 0.0f, 100.0f ) < buyDefuseKitChance)
{
m_buyDefuseKit = true;
}
}
}
// determine if we want a tactical shield
if (!me->HasPrimaryWeapon() && TheCSBots()->AllowTacticalShield())
{
if (me->m_iAccount > 2500)
{
if (me->m_iAccount < 4000)
m_buyShield = (RandomFloat( 0, 100.0f ) < 33.3f) ? true : false;
else
m_buyShield = (RandomFloat( 0, 100.0f ) < 10.0f) ? true : false;
}
}
}
if (TheCSBots()->AllowGrenades())
{
m_buyGrenade = (RandomFloat( 0.0f, 100.0f ) < 33.3f) ? true : false;
}
else
{
m_buyGrenade = false;
}
m_buyPistol = false;
if (TheCSBots()->AllowPistols())
{
// check if we have a pistol
if (me->Weapon_GetSlot( WEAPON_SLOT_PISTOL ))
{
// if we have our default pistol, think about buying a different one
if (HasDefaultPistol( me ))
{
// if everything other than pistols is disallowed, buy a pistol
if (TheCSBots()->AllowShotguns() == false &&
TheCSBots()->AllowSubMachineGuns() == false &&
TheCSBots()->AllowRifles() == false &&
TheCSBots()->AllowMachineGuns() == false &&
TheCSBots()->AllowTacticalShield() == false &&
TheCSBots()->AllowSnipers() == false)
{
m_buyPistol = (RandomFloat( 0, 100 ) < 75.0f);
}
else if (me->m_iAccount < 1000)
{
// if we're low on cash, buy a pistol
m_buyPistol = (RandomFloat( 0, 100 ) < 75.0f);
}
else
{
m_buyPistol = (RandomFloat( 0, 100 ) < 33.3f);
}
}
}
else
{
// we dont have a pistol - buy one
m_buyPistol = true;
}
}
}
enum WeaponType
{
PISTOL,
SHOTGUN,
SUB_MACHINE_GUN,
RIFLE,
MACHINE_GUN,
SNIPER_RIFLE,
GRENADE,
NUM_WEAPON_TYPES
};
struct BuyInfo
{
WeaponType type;
bool preferred; ///< more challenging bots prefer these weapons
const char *buyAlias; ///< the buy alias for this equipment
};
#define PRIMARY_WEAPON_BUY_COUNT 13
#define SECONDARY_WEAPON_BUY_COUNT 3
/**
* These tables MUST be kept in sync with the CT and T buy aliases
*/
static BuyInfo primaryWeaponBuyInfoCT[ PRIMARY_WEAPON_BUY_COUNT ] =
{
{ SHOTGUN, false, "m3" }, // WEAPON_M3
{ SHOTGUN, false, "xm1014" }, // WEAPON_XM1014
{ SUB_MACHINE_GUN, false, "tmp" }, // WEAPON_TMP
{ SUB_MACHINE_GUN, false, "mp5navy" }, // WEAPON_MP5N
{ SUB_MACHINE_GUN, false, "ump45" }, // WEAPON_UMP45
{ SUB_MACHINE_GUN, false, "p90" }, // WEAPON_P90
{ RIFLE, true, "famas" }, // WEAPON_FAMAS
{ SNIPER_RIFLE, false, "scout" }, // WEAPON_SCOUT
{ RIFLE, true, "m4a1" }, // WEAPON_M4A1
{ RIFLE, false, "aug" }, // WEAPON_AUG
{ SNIPER_RIFLE, true, "sg550" }, // WEAPON_SG550
{ SNIPER_RIFLE, true, "awp" }, // WEAPON_AWP
{ MACHINE_GUN, false, "m249" } // WEAPON_M249
};
static BuyInfo secondaryWeaponBuyInfoCT[ SECONDARY_WEAPON_BUY_COUNT ] =
{
// { PISTOL, false, "glock" },
// { PISTOL, false, "usp" },
{ PISTOL, true, "p228" },
{ PISTOL, true, "deagle" },
{ PISTOL, true, "fn57" }
};
static BuyInfo primaryWeaponBuyInfoT[ PRIMARY_WEAPON_BUY_COUNT ] =
{
{ SHOTGUN, false, "m3" }, // WEAPON_M3
{ SHOTGUN, false, "xm1014" }, // WEAPON_XM1014
{ SUB_MACHINE_GUN, false, "mac10" }, // WEAPON_MAC10
{ SUB_MACHINE_GUN, false, "mp5navy" }, // WEAPON_MP5N
{ SUB_MACHINE_GUN, false, "ump45" }, // WEAPON_UMP45
{ SUB_MACHINE_GUN, false, "p90" }, // WEAPON_P90
{ RIFLE, true, "galil" }, // WEAPON_GALIL
{ RIFLE, true, "ak47" }, // WEAPON_AK47
{ SNIPER_RIFLE, false, "scout" }, // WEAPON_SCOUT
{ RIFLE, true, "sg552" }, // WEAPON_SG552
{ SNIPER_RIFLE, true, "awp" }, // WEAPON_AWP
{ SNIPER_RIFLE, true, "g3sg1" }, // WEAPON_G3SG1
{ MACHINE_GUN, false, "m249" } // WEAPON_M249
};
static BuyInfo secondaryWeaponBuyInfoT[ SECONDARY_WEAPON_BUY_COUNT ] =
{
// { PISTOL, false, "glock" },
// { PISTOL, false, "usp" },
{ PISTOL, true, "p228" },
{ PISTOL, true, "deagle" },
{ PISTOL, true, "elites" }
};
/**
* Given a weapon alias, return the kind of weapon it is
*/
inline WeaponType GetWeaponType( const char *alias )
{
int i;
for( i=0; i<PRIMARY_WEAPON_BUY_COUNT; ++i )
{
if (!stricmp( alias, primaryWeaponBuyInfoCT[i].buyAlias ))
return primaryWeaponBuyInfoCT[i].type;
if (!stricmp( alias, primaryWeaponBuyInfoT[i].buyAlias ))
return primaryWeaponBuyInfoT[i].type;
}
for( i=0; i<SECONDARY_WEAPON_BUY_COUNT; ++i )
{
if (!stricmp( alias, secondaryWeaponBuyInfoCT[i].buyAlias ))
return secondaryWeaponBuyInfoCT[i].type;
if (!stricmp( alias, secondaryWeaponBuyInfoT[i].buyAlias ))
return secondaryWeaponBuyInfoT[i].type;
}
return NUM_WEAPON_TYPES;
}
//--------------------------------------------------------------------------------------------------------------
void BuyState::OnUpdate( CCSBot *me )
{
char cmdBuffer[256];
// wait for a Navigation Mesh
if (!TheNavMesh->IsLoaded())
return;
// apparently we cant buy things in the first few seconds, so wait a bit
if (m_isInitialDelay)
{
const float waitToBuyTime = 0.25f;
if (gpGlobals->curtime - me->GetStateTimestamp() < waitToBuyTime)
return;
m_isInitialDelay = false;
}
// if we're done buying and still in the freeze period, wait
if (m_doneBuying)
{
if (CSGameRules()->IsMultiplayer() && CSGameRules()->IsFreezePeriod())
{
// make sure we're locked and loaded
me->EquipBestWeapon( MUST_EQUIP );
me->Reload();
me->ResetStuckMonitor();
return;
}
me->Idle();
return;
}
// If we're supposed to buy a specific weapon for debugging, do so and then bail
const char *cheatWeaponString = bot_loadout.GetString();
if ( cheatWeaponString && *cheatWeaponString )
{
CUtlVector<char*, CUtlMemory<char*> > loadout;
Q_SplitString( cheatWeaponString, " ", loadout );
for ( int i=0; i<loadout.Count(); ++i )
{
const char *item = loadout[i];
if ( FStrEq( item, "vest" ) )
{
me->GiveNamedItem( "item_kevlar" );
}
else if ( FStrEq( item, "vesthelm" ) )
{
me->GiveNamedItem( "item_assaultsuit" );
}
else if ( FStrEq( item, "defuser" ) )
{
if ( me->GetTeamNumber() == TEAM_CT )
{
me->GiveDefuser();
}
}
else if ( FStrEq( item, "nvgs" ) )
{
me->m_bHasNightVision = true;
}
else if ( FStrEq( item, "primammo" ) )
{
me->AttemptToBuyAmmo( 0 );
}
else if ( FStrEq( item, "secammo" ) )
{
me->AttemptToBuyAmmo( 1 );
}
else
{
me->GiveWeapon( item );
}
}
m_doneBuying = true;
return;
}
if (!me->IsInBuyZone())
{
m_doneBuying = true;
CONSOLE_ECHO( "%s bot spawned outside of a buy zone (%d, %d, %d)\n",
(me->GetTeamNumber() == TEAM_CT) ? "CT" : "Terrorist",
(int)me->GetAbsOrigin().x,
(int)me->GetAbsOrigin().y,
(int)me->GetAbsOrigin().z );
return;
}
// try to buy some weapons
const float buyInterval = 0.02f;
if (gpGlobals->curtime - me->GetStateTimestamp() > buyInterval)
{
me->m_stateTimestamp = gpGlobals->curtime;
bool isPreferredAllDisallowed = true;
// try to buy our preferred weapons first
if (m_prefIndex < me->GetProfile()->GetWeaponPreferenceCount() && bot_randombuy.GetBool() == false )
{
// need to retry because sometimes first buy fails??
const int maxPrefRetries = 2;
if (m_prefRetries >= maxPrefRetries)
{
// try to buy next preferred weapon
++m_prefIndex;
m_prefRetries = 0;
return;
}
int weaponPreference = me->GetProfile()->GetWeaponPreference( m_prefIndex );
// don't buy it again if we still have one from last round
char weaponPreferenceName[32];
Q_snprintf( weaponPreferenceName, sizeof(weaponPreferenceName), "weapon_%s", me->GetProfile()->GetWeaponPreferenceAsString( m_prefIndex ) );
if( me->Weapon_OwnsThisType(weaponPreferenceName) )//Prefs and buyalias use the short version, this uses the long
{
// done with buying preferred weapon
m_prefIndex = 9999;
return;
}
if (me->HasShield() && weaponPreference == WEAPON_SHIELDGUN)
{
// done with buying preferred weapon
m_prefIndex = 9999;
return;
}
const char *buyAlias = NULL;
if (weaponPreference == WEAPON_SHIELDGUN)
{
if (TheCSBots()->AllowTacticalShield())
buyAlias = "shield";
}
else
{
buyAlias = WeaponIDToAlias( weaponPreference );
WeaponType type = GetWeaponType( buyAlias );
switch( type )
{
case PISTOL:
if (!TheCSBots()->AllowPistols())
buyAlias = NULL;
break;
case SHOTGUN:
if (!TheCSBots()->AllowShotguns())
buyAlias = NULL;
break;
case SUB_MACHINE_GUN:
if (!TheCSBots()->AllowSubMachineGuns())
buyAlias = NULL;
break;
case RIFLE:
if (!TheCSBots()->AllowRifles())
buyAlias = NULL;
break;
case MACHINE_GUN:
if (!TheCSBots()->AllowMachineGuns())
buyAlias = NULL;
break;
case SNIPER_RIFLE:
if (!TheCSBots()->AllowSnipers())
buyAlias = NULL;
break;
}
}
if (buyAlias)
{
Q_snprintf( cmdBuffer, 256, "buy %s\n", buyAlias );
CCommand args;
args.Tokenize( cmdBuffer );
me->ClientCommand( args );
me->PrintIfWatched( "Tried to buy preferred weapon %s.\n", buyAlias );
isPreferredAllDisallowed = false;
}
++m_prefRetries;
// bail out so we dont waste money on other equipment
// unless everything we prefer has been disallowed, then buy at random
if (isPreferredAllDisallowed == false)
return;
}
// if we have no preferred primary weapon (or everything we want is disallowed), buy at random
if (!me->HasPrimaryWeapon() && (isPreferredAllDisallowed || !me->GetProfile()->HasPrimaryPreference()))
{
if (m_buyShield)
{
// buy a shield
CCommand args;
args.Tokenize( "buy shield" );
me->ClientCommand( args );
me->PrintIfWatched( "Tried to buy a shield.\n" );
}
else
{
// build list of allowable weapons to buy
BuyInfo *masterPrimary = (me->GetTeamNumber() == TEAM_TERRORIST) ? primaryWeaponBuyInfoT : primaryWeaponBuyInfoCT;
BuyInfo *stockPrimary[ PRIMARY_WEAPON_BUY_COUNT ];
int stockPrimaryCount = 0;
// dont choose sniper rifles as often
const float sniperRifleChance = 50.0f;
bool wantSniper = (RandomFloat( 0, 100 ) < sniperRifleChance) ? true : false;
if ( bot_randombuy.GetBool() )
{
wantSniper = true;
}
for( int i=0; i<PRIMARY_WEAPON_BUY_COUNT; ++i )
{
if ((masterPrimary[i].type == SHOTGUN && TheCSBots()->AllowShotguns()) ||
(masterPrimary[i].type == SUB_MACHINE_GUN && TheCSBots()->AllowSubMachineGuns()) ||
(masterPrimary[i].type == RIFLE && TheCSBots()->AllowRifles()) ||
(masterPrimary[i].type == SNIPER_RIFLE && TheCSBots()->AllowSnipers() && wantSniper) ||
(masterPrimary[i].type == MACHINE_GUN && TheCSBots()->AllowMachineGuns()))
{
stockPrimary[ stockPrimaryCount++ ] = &masterPrimary[i];
}
}
if (stockPrimaryCount)
{
// buy primary weapon if we don't have one
int which;
// on hard difficulty levels, bots try to buy preferred weapons on the first pass
if (m_retries == 0 && TheCSBots()->GetDifficultyLevel() >= BOT_HARD && bot_randombuy.GetBool() == false )
{
// count up available preferred weapons
int prefCount = 0;
for( which=0; which<stockPrimaryCount; ++which )
if (stockPrimary[which]->preferred)
++prefCount;
if (prefCount)
{
int whichPref = RandomInt( 0, prefCount-1 );
for( which=0; which<stockPrimaryCount; ++which )
if (stockPrimary[which]->preferred && whichPref-- == 0)
break;
}
else
{
// no preferred weapons available, just pick randomly
which = RandomInt( 0, stockPrimaryCount-1 );
}
}
else
{
which = RandomInt( 0, stockPrimaryCount-1 );
}
Q_snprintf( cmdBuffer, 256, "buy %s\n", stockPrimary[ which ]->buyAlias );
CCommand args;
args.Tokenize( cmdBuffer );
me->ClientCommand( args );
me->PrintIfWatched( "Tried to buy %s.\n", stockPrimary[ which ]->buyAlias );
}
}
}
//
// If we now have a weapon, or have tried for too long, we're done
//
if (me->HasPrimaryWeapon() || m_retries++ > 5)
{
// primary ammo
CCommand args;
if (me->HasPrimaryWeapon())
{
args.Tokenize( "buy primammo" );
me->ClientCommand( args );
}
// buy armor last, to make sure we bought a weapon first
args.Tokenize( "buy vesthelm" );
me->ClientCommand( args );
args.Tokenize( "buy vest" );
me->ClientCommand( args );
// pistols - if we have no preferred pistol, buy at random
if (TheCSBots()->AllowPistols() && !me->GetProfile()->HasPistolPreference())
{
if (m_buyPistol)
{
int which = RandomInt( 0, SECONDARY_WEAPON_BUY_COUNT-1 );
const char *what = NULL;
if (me->GetTeamNumber() == TEAM_TERRORIST)
what = secondaryWeaponBuyInfoT[ which ].buyAlias;
else
what = secondaryWeaponBuyInfoCT[ which ].buyAlias;
Q_snprintf( cmdBuffer, 256, "buy %s\n", what );
args.Tokenize( cmdBuffer );
me->ClientCommand( args );
// only buy one pistol
m_buyPistol = false;
}
// make sure we have enough pistol ammo
args.Tokenize( "buy secammo" );
me->ClientCommand( args );
}
// buy a grenade if we wish, and we don't already have one
if (m_buyGrenade && !me->HasGrenade())
{
if (UTIL_IsTeamAllBots( me->GetTeamNumber() ))
{
// only allow Flashbangs if everyone on the team is a bot (dont want to blind our friendly humans)
float rnd = RandomFloat( 0, 100 );
if (rnd < 10)
{
args.Tokenize( "buy smokegrenade" );
me->ClientCommand( args ); // smoke grenade
}
else if (rnd < 35)
{
args.Tokenize( "buy flashbang" );
me->ClientCommand( args ); // flashbang
}
else
{
args.Tokenize( "buy hegrenade" );
me->ClientCommand( args ); // he grenade
}
}
else
{
if (RandomFloat( 0, 100 ) < 10)
{
args.Tokenize( "buy smokegrenade" ); // smoke grenade
me->ClientCommand( args );
}
else
{
args.Tokenize( "buy hegrenade" ); // he grenade
me->ClientCommand( args );
}
}
}
if (m_buyDefuseKit)
{
args.Tokenize( "buy defuser" );
me->ClientCommand( args );
}
m_doneBuying = true;
}
}
}
//--------------------------------------------------------------------------------------------------------------
void BuyState::OnExit( CCSBot *me )
{
me->ResetStuckMonitor();
me->EquipBestWeapon();
}
@@ -0,0 +1,82 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
#include "cbase.h"
#include "cs_bot.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//--------------------------------------------------------------------------------------------------------------
/**
* Begin defusing the bomb
*/
void DefuseBombState::OnEnter( CCSBot *me )
{
me->Crouch();
me->SetDisposition( CCSBot::SELF_DEFENSE );
me->GetChatter()->Say( "DefusingBomb" );
}
//--------------------------------------------------------------------------------------------------------------
/**
* Defuse the bomb
*/
void DefuseBombState::OnUpdate( CCSBot *me )
{
const Vector *bombPos = me->GetGameState()->GetBombPosition();
if (bombPos == NULL)
{
me->PrintIfWatched( "In Defuse state, but don't know where the bomb is!\n" );
me->Idle();
return;
}
// look at the bomb
me->SetLookAt( "Defuse bomb", *bombPos, PRIORITY_HIGH );
// defuse...
me->UseEnvironment();
if (gpGlobals->curtime - me->GetStateTimestamp() > 1.0f)
{
// if we missed starting the defuse, give up
if (TheCSBots()->GetBombDefuser() == NULL)
{
me->PrintIfWatched( "Failed to start defuse, giving up\n" );
me->Idle();
return;
}
else if (TheCSBots()->GetBombDefuser() != me)
{
// if someone else got the defuse, give up
me->PrintIfWatched( "Someone else started defusing, giving up\n" );
me->Idle();
return;
}
}
// if bomb has been defused, give up
if (!TheCSBots()->IsBombPlanted())
{
me->Idle();
return;
}
}
//--------------------------------------------------------------------------------------------------------------
void DefuseBombState::OnExit( CCSBot *me )
{
me->StandUp();
me->ResetStuckMonitor();
me->SetTask( CCSBot::SEEK_AND_DESTROY );
me->SetDisposition( CCSBot::ENGAGE_AND_INVESTIGATE );
me->ClearLookAt();
}
@@ -0,0 +1,67 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
#include "cbase.h"
#include "cs_bot.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//--------------------------------------------------------------------------------------------------------------
/**
* Escape from the bomb.
*/
void EscapeFromBombState::OnEnter( CCSBot *me )
{
me->StandUp();
me->Run();
me->DestroyPath();
me->EquipKnife();
}
//--------------------------------------------------------------------------------------------------------------
/**
* Escape from the bomb.
*/
void EscapeFromBombState::OnUpdate( CCSBot *me )
{
const Vector *bombPos = me->GetGameState()->GetBombPosition();
// if we don't know where the bomb is, we shouldn't be in this state
if (bombPos == NULL)
{
me->Idle();
return;
}
// grab our knife to move quickly
me->EquipKnife();
// look around
me->UpdateLookAround();
if (me->UpdatePathMovement() != CCSBot::PROGRESSING)
{
// we have no path, or reached the end of one - create a new path far away from the bomb
FarAwayFromPositionFunctor func( *bombPos );
CNavArea *goalArea = FindMinimumCostArea( me->GetLastKnownArea(), func );
// if this fails, we'll try again next time
me->ComputePath( goalArea->GetCenter(), FASTEST_ROUTE );
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Escape from the bomb.
*/
void EscapeFromBombState::OnExit( CCSBot *me )
{
me->EquipBestWeapon();
}
@@ -0,0 +1,69 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
#include "cbase.h"
#include "cs_bot.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//--------------------------------------------------------------------------------------------------------------
/**
* Move to the bomb on the floor and pick it up
*/
void FetchBombState::OnEnter( CCSBot *me )
{
me->DestroyPath();
}
//--------------------------------------------------------------------------------------------------------------
/**
* Move to the bomb on the floor and pick it up
*/
void FetchBombState::OnUpdate( CCSBot *me )
{
if (me->HasC4())
{
me->PrintIfWatched( "I picked up the bomb\n" );
me->Idle();
return;
}
CBaseEntity *bomb = TheCSBots()->GetLooseBomb();
if (bomb)
{
if (!me->HasPath())
{
// build a path to the bomb
if (me->ComputePath( bomb->GetAbsOrigin() ) == false)
{
me->PrintIfWatched( "Fetch bomb pathfind failed\n" );
// go Hunt instead of Idle to prevent continuous re-pathing to inaccessible bomb
me->Hunt();
return;
}
}
}
else
{
// someone picked up the bomb
me->PrintIfWatched( "Someone else picked up the bomb.\n" );
me->Idle();
return;
}
// look around
me->UpdateLookAround();
if (me->UpdatePathMovement() != CCSBot::PROGRESSING)
me->Idle();
}
@@ -0,0 +1,366 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
#include "cbase.h"
#include "cs_bot.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//--------------------------------------------------------------------------------------------------------------
/**
* Follow our leader
*/
void FollowState::OnEnter( CCSBot *me )
{
me->StandUp();
me->Run();
me->DestroyPath();
m_isStopped = false;
m_stoppedTimestamp = 0.0f;
// to force immediate repath
m_lastLeaderPos.x = -99999999.9f;
m_lastLeaderPos.y = -99999999.9f;
m_lastLeaderPos.z = -99999999.9f;
m_lastSawLeaderTime = 0;
// set re-pathing frequency
m_repathInterval.Invalidate();
m_isSneaking = false;
m_walkTime.Invalidate();
m_isAtWalkSpeed = false;
m_leaderMotionState = INVALID;
m_idleTimer.Start( RandomFloat( 2.0f, 5.0f ) );
}
//--------------------------------------------------------------------------------------------------------------
/**
* Determine the leader's motion state by tracking his speed
*/
void FollowState::ComputeLeaderMotionState( float leaderSpeed )
{
// walk = 130, run = 250
const float runWalkThreshold = 140.0f;
const float walkStopThreshold = 10.0f; // 120.0f;
LeaderMotionStateType prevState = m_leaderMotionState;
if (leaderSpeed > runWalkThreshold)
{
m_leaderMotionState = RUNNING;
m_isAtWalkSpeed = false;
}
else if (leaderSpeed > walkStopThreshold)
{
// track when began to walk
if (!m_isAtWalkSpeed)
{
m_walkTime.Start();
m_isAtWalkSpeed = true;
}
const float minWalkTime = 0.25f;
if (m_walkTime.GetElapsedTime() > minWalkTime)
{
m_leaderMotionState = WALKING;
}
}
else
{
m_leaderMotionState = STOPPED;
m_isAtWalkSpeed = false;
}
// track time spent in this motion state
if (prevState != m_leaderMotionState)
{
m_leaderMotionStateTime.Start();
m_waitTime = RandomFloat( 1.0f, 3.0f );
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Functor to collect all areas in the forward direction of the given player within a radius
*/
class FollowTargetCollector
{
public:
FollowTargetCollector( CBasePlayer *player )
{
m_player = player;
Vector playerVel = player->GetAbsVelocity();
m_forward.x = playerVel.x;
m_forward.y = playerVel.y;
float speed = m_forward.NormalizeInPlace();
Vector playerOrigin = GetCentroid( player );
const float walkSpeed = 100.0f;
if (speed < walkSpeed)
{
m_cutoff.x = playerOrigin.x;
m_cutoff.y = playerOrigin.y;
m_forward.x = 0.0f;
m_forward.y = 0.0f;
}
else
{
const float k = 1.5f; // 2.0f;
float trimSpeed = MIN( speed, 200.0f );
m_cutoff.x = playerOrigin.x + k * trimSpeed * m_forward.x;
m_cutoff.y = playerOrigin.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)
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;
};
//--------------------------------------------------------------------------------------------------------------
/**
* Follow our leader
* @todo Clean up this nasty mess
*/
void FollowState::OnUpdate( CCSBot *me )
{
// if we lost our leader, give up
if (m_leader == NULL || !m_leader->IsAlive())
{
me->Idle();
return;
}
// if we are carrying the bomb and at a bombsite, plant
if (me->HasC4() && me->IsAtBombsite())
{
// plant it
me->SetTask( CCSBot::PLANT_BOMB );
me->PlantBomb();
// radio to the team
me->GetChatter()->PlantingTheBomb( me->GetPlace() );
return;
}
// look around
me->UpdateLookAround();
// if we are moving, we are not idle
if (me->IsNotMoving() == false)
m_idleTimer.Start( RandomFloat( 2.0f, 5.0f ) );
// compute the leader's speed
Vector leaderVel = m_leader->GetAbsVelocity();
float leaderSpeed = Vector2D( leaderVel.x, leaderVel.y ).Length();
// determine our leader's movement state
ComputeLeaderMotionState( leaderSpeed );
// track whether we can see the leader
bool isLeaderVisible;
Vector leaderOrigin = GetCentroid( m_leader );
if (me->IsVisible( leaderOrigin ))
{
m_lastSawLeaderTime = gpGlobals->curtime;
isLeaderVisible = true;
}
else
{
isLeaderVisible = false;
}
// determine whether we should sneak or not
const float farAwayRange = 750.0f;
Vector myOrigin = GetCentroid( me );
if ((leaderOrigin - myOrigin).IsLengthGreaterThan( farAwayRange ))
{
// far away from leader - run to catch up
m_isSneaking = false;
}
else if (isLeaderVisible)
{
// if we see leader walking and we are nearby, walk
if (m_leaderMotionState == WALKING)
m_isSneaking = true;
// if we are sneaking and our leader starts running, stop sneaking
if (m_isSneaking && m_leaderMotionState == RUNNING)
m_isSneaking = false;
}
// if we haven't seen the leader for a long time, run
const float longTime = 20.0f;
if (gpGlobals->curtime - m_lastSawLeaderTime > longTime)
m_isSneaking = false;
if (m_isSneaking)
me->Walk();
else
me->Run();
bool repath = false;
// if the leader has stopped, hide nearby
const float nearLeaderRange = 250.0f;
if (!me->HasPath() && m_leaderMotionState == STOPPED && m_leaderMotionStateTime.GetElapsedTime() > m_waitTime)
{
// throttle how often this check occurs
m_waitTime += RandomFloat( 1.0f, 3.0f );
// the leader has stopped - if we are close to him, take up a hiding spot
if ((leaderOrigin - myOrigin).IsLengthLessThan( nearLeaderRange ))
{
const float hideRange = 250.0f;
if (me->TryToHide( NULL, -1.0f, hideRange, false, USE_NEAREST ))
{
me->ResetStuckMonitor();
return;
}
}
}
// if we have been idle for awhile, move
if (m_idleTimer.IsElapsed())
{
repath = true;
// always walk when we move such a short distance
m_isSneaking = true;
}
// if our leader has moved, repath (don't repath if leading is stopping)
if (leaderSpeed > 100.0f && m_leaderMotionState != STOPPED)
{
repath = true;
}
// move along our path
if (me->UpdatePathMovement( NO_SPEED_CHANGE ) != CCSBot::PROGRESSING)
{
me->DestroyPath();
}
// recompute our path if necessary
if (repath && m_repathInterval.IsElapsed() && !me->IsOnLadder())
{
// recompute our path to keep us near our leader
m_lastLeaderPos = leaderOrigin;
me->ResetStuckMonitor();
const float runSpeed = 200.0f;
const float collectRange = (leaderSpeed > runSpeed) ? 600.0f : 400.0f; // 400, 200
FollowTargetCollector collector( m_leader );
SearchSurroundingAreas( TheNavMesh->GetNearestNavArea( m_lastLeaderPos ), m_lastLeaderPos, collector, collectRange );
if (cv_bot_debug.GetBool())
{
for( int i=0; i<collector.m_targetAreaCount; ++i )
collector.m_targetArea[i]->Draw( /*255, 0, 0, 2*/ );
}
// move to one of the collected areas
if (collector.m_targetAreaCount)
{
CNavArea *target = NULL;
Vector targetPos;
// if we are idle, pick a random area
if (m_idleTimer.IsElapsed())
{
target = collector.m_targetArea[ RandomInt( 0, collector.m_targetAreaCount-1 ) ];
targetPos = target->GetCenter();
me->PrintIfWatched( "%4.1f: Bored. Repathing to a new nearby area\n", gpGlobals->curtime );
}
else
{
me->PrintIfWatched( "%4.1f: Repathing to stay with leader.\n", gpGlobals->curtime );
// find closest area to where we are
CNavArea *area;
float closeRangeSq = 9999999999.9f;
Vector close;
for( int a=0; a<collector.m_targetAreaCount; ++a )
{
area = collector.m_targetArea[a];
area->GetClosestPointOnArea( myOrigin, &close );
float rangeSq = (myOrigin - close).LengthSqr();
if (rangeSq < closeRangeSq)
{
target = area;
targetPos = close;
closeRangeSq = rangeSq;
}
}
}
if (target == NULL || me->ComputePath( target->GetCenter(), FASTEST_ROUTE ) == false)
me->PrintIfWatched( "Pathfind to leader failed.\n" );
// throttle how often we repath
m_repathInterval.Start( 0.5f );
m_idleTimer.Reset();
}
}
}
//--------------------------------------------------------------------------------------------------------------
void FollowState::OnExit( CCSBot *me )
{
}
@@ -0,0 +1,549 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
#include "cbase.h"
#include "cs_simple_hostage.h"
#include "cs_bot.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//--------------------------------------------------------------------------------------------------------------
/**
* Begin moving to a nearby hidey-hole.
* NOTE: Do not forget this state may include a very long "move-to" time to get to our hidey spot!
*/
void HideState::OnEnter( CCSBot *me )
{
m_isAtSpot = false;
m_isLookingOutward = false;
// if duration is "infinite", set it to a reasonably long time to prevent infinite camping
if (m_duration < 0.0f)
{
m_duration = RandomFloat( 30.0f, 60.0f );
}
// decide whether to "ambush" or not - never set to false so as not to override external setting
if (RandomFloat( 0.0f, 100.0f ) < 50.0f)
{
m_isHoldingPosition = true;
}
// if we are holding position, decide for how long
if (m_isHoldingPosition)
{
m_holdPositionTime = RandomFloat( 3.0f, 10.0f );
}
else
{
m_holdPositionTime = 0.0f;
}
m_heardEnemy = false;
m_firstHeardEnemyTime = 0.0f;
m_retry = 0;
if (me->IsFollowing())
{
m_leaderAnchorPos = GetCentroid( me->GetFollowLeader() );
}
// if we are a sniper, we need to periodically pause while we retreat to squeeze off a shot or two
if (me->IsSniper())
{
// start off paused to allow a final shot before retreating
m_isPaused = false;
m_pauseTimer.Invalidate();
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Move to a nearby hidey-hole.
* NOTE: Do not forget this state may include a very long "move-to" time to get to our hidey spot!
*/
void HideState::OnUpdate( CCSBot *me )
{
Vector myOrigin = GetCentroid( me );
// wait until finished reloading to leave hide state
if (!me->IsReloading())
{
// if we are momentarily hiding while following someone, check to see if he has moved on
if (me->IsFollowing())
{
CCSPlayer *leader = static_cast<CCSPlayer *>( static_cast<CBaseEntity *>( me->GetFollowLeader() ) );
Vector leaderOrigin = GetCentroid( leader );
// BOTPORT: Determine walk/run velocity thresholds
float runThreshold = 200.0f;
if (leader->GetAbsVelocity().IsLengthGreaterThan( runThreshold ))
{
// leader is running, stay with him
me->Follow( leader );
return;
}
// if leader has moved, stay with him
const float followRange = 250.0f;
if ((m_leaderAnchorPos - leaderOrigin).IsLengthGreaterThan( followRange ))
{
me->Follow( leader );
return;
}
}
// if we see a nearby buddy in combat, join him
/// @todo - Perhaps tie in to TakeDamage(), so it works for human players, too
//
// Scenario logic
//
switch( TheCSBots()->GetScenario() )
{
case CCSBotManager::SCENARIO_DEFUSE_BOMB:
{
if (me->GetTeamNumber() == TEAM_CT)
{
// if we are just holding position (due to a radio order) and the bomb has just planted, go defuse it
if (me->GetTask() == CCSBot::HOLD_POSITION &&
TheCSBots()->IsBombPlanted() &&
TheCSBots()->GetBombPlantTimestamp() > me->GetStateTimestamp())
{
me->Idle();
return;
}
// if we are guarding the defuser and he dies/gives up, stop hiding (to choose another defuser)
if (me->GetTask() == CCSBot::GUARD_BOMB_DEFUSER && TheCSBots()->GetBombDefuser() == NULL)
{
me->Idle();
return;
}
// if we are guarding the loose bomb and it is picked up, stop hiding
if (me->GetTask() == CCSBot::GUARD_LOOSE_BOMB && TheCSBots()->GetLooseBomb() == NULL)
{
me->GetChatter()->TheyPickedUpTheBomb();
me->Idle();
return;
}
// if we are guarding a bombsite and the bomb is dropped and we hear about it, stop guarding
if (me->GetTask() == CCSBot::GUARD_BOMB_ZONE && me->GetGameState()->IsLooseBombLocationKnown())
{
me->Idle();
return;
}
// if we are guarding (bombsite, initial encounter, etc) and the bomb is planted, go defuse it
if (me->IsDoingScenario() && me->GetTask() != CCSBot::GUARD_BOMB_DEFUSER && TheCSBots()->IsBombPlanted())
{
me->Idle();
return;
}
}
else // TERRORIST
{
// if we are near the ticking bomb and someone starts defusing it, attack!
if (TheCSBots()->GetBombDefuser())
{
Vector defuserOrigin = GetCentroid( TheCSBots()->GetBombDefuser() );
Vector toDefuser = defuserOrigin - myOrigin;
const float hearDefuseRange = 2000.0f;
if (toDefuser.IsLengthLessThan( hearDefuseRange ))
{
// if we are nearby, attack, otherwise move to the bomb (which will cause us to attack when we see defuser)
if (me->CanSeePlantedBomb())
{
me->Attack( TheCSBots()->GetBombDefuser() );
}
else
{
me->MoveTo( defuserOrigin, FASTEST_ROUTE );
me->InhibitLookAround( 10.0f );
}
return;
}
}
}
break;
}
//--------------------------------------------------------------------------------------------------
case CCSBotManager::SCENARIO_RESCUE_HOSTAGES:
{
// if we're guarding the hostages and they all die or are taken, do something else
if (me->GetTask() == CCSBot::GUARD_HOSTAGES)
{
if (me->GetGameState()->AreAllHostagesBeingRescued() || me->GetGameState()->AreAllHostagesGone())
{
me->Idle();
return;
}
}
else if (me->GetTask() == CCSBot::GUARD_HOSTAGE_RESCUE_ZONE)
{
// if we stumble across a hostage, guard it
CHostage *hostage = me->GetGameState()->GetNearestVisibleFreeHostage();
if (hostage)
{
// we see a free hostage, guard it
Vector hostageOrigin = GetCentroid( hostage );
CNavArea *area = TheNavMesh->GetNearestNavArea( hostageOrigin );
if (area)
{
me->SetTask( CCSBot::GUARD_HOSTAGES );
me->Hide( area );
me->PrintIfWatched( "I'm guarding hostages I found\n" );
// don't chatter here - he'll tell us when he's in his hiding spot
return;
}
}
}
}
}
bool isSettledInSniper = (me->IsSniper() && m_isAtSpot) ? true : false;
// only investigate noises if we are initiating attacks, and we aren't a "settled in" sniper
// dont investigate noises if we are reloading
if (!me->IsReloading() &&
!isSettledInSniper &&
me->GetDisposition() == CCSBot::ENGAGE_AND_INVESTIGATE)
{
// if we are holding position, and have heard the enemy nearby, investigate after our hold time is up
if (m_isHoldingPosition && m_heardEnemy && (gpGlobals->curtime - m_firstHeardEnemyTime > m_holdPositionTime))
{
/// @todo We might need to remember specific location of last enemy noise here
me->InvestigateNoise();
return;
}
// investigate nearby enemy noises
if (me->HeardInterestingNoise())
{
// if we are holding position, check if enough time has elapsed since we first heard the enemy
if (m_isAtSpot && m_isHoldingPosition)
{
if (!m_heardEnemy)
{
// first time we heard the enemy
m_heardEnemy = true;
m_firstHeardEnemyTime = gpGlobals->curtime;
me->PrintIfWatched( "Heard enemy, holding position for %f2.1 seconds...\n", m_holdPositionTime );
}
}
else
{
// not holding position - investigate enemy noise
me->InvestigateNoise();
return;
}
}
}
} // end reloading check
// look around
me->UpdateLookAround();
// if we are at our hiding spot, crouch and wait
if (m_isAtSpot)
{
me->ResetStuckMonitor();
CNavArea *area = TheNavMesh->GetNavArea( m_hidingSpot );
if ( !area || !( area->GetAttributes() & NAV_MESH_STAND ) )
{
me->Crouch();
}
// check if duration has expired
if (m_hideTimer.IsElapsed())
{
if (me->GetTask() == CCSBot::GUARD_LOOSE_BOMB)
{
// if we're guarding the loose bomb, continue to guard it but pick a new spot
me->Hide( TheCSBots()->GetLooseBombArea() );
return;
}
else if (me->GetTask() == CCSBot::GUARD_BOMB_ZONE)
{
// if we're guarding a bombsite, continue to guard it but pick a new spot
const CCSBotManager::Zone *zone = TheCSBots()->GetClosestZone( myOrigin );
if (zone)
{
CNavArea *area = TheCSBots()->GetRandomAreaInZone( zone );
if (area)
{
me->Hide( area );
return;
}
}
}
else if (me->GetTask() == CCSBot::GUARD_HOSTAGE_RESCUE_ZONE)
{
// if we're guarding a rescue zone, continue to guard this or another rescue zone
if (me->GuardRandomZone())
{
me->SetTask( CCSBot::GUARD_HOSTAGE_RESCUE_ZONE );
me->PrintIfWatched( "Continuing to guard hostage rescue zones\n" );
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
me->GetChatter()->GuardingHostageEscapeZone( IS_PLAN );
return;
}
}
me->Idle();
return;
}
/*
// if we are watching for an approaching noisy enemy, anticipate and fire before they round the corner
/// @todo Need to check if we are looking at an ENEMY_NOISE here
const float veryCloseNoise = 250.0f;
if (me->IsLookingAtSpot() && me->GetNoiseRange() < veryCloseNoise)
{
// fire!
me->PrimaryAttack();
me->PrintIfWatched( "Firing at anticipated enemy coming around the corner!\n" );
}
*/
// if we have a shield, hide behind it
if (me->HasShield() && !me->IsProtectedByShield())
me->SecondaryAttack();
// while sitting at our hiding spot, if we are being attacked but can't see our attacker, move somewhere else
const float hurtRecentlyTime = 1.0f;
if (!me->IsEnemyVisible() && me->GetTimeSinceAttacked() < hurtRecentlyTime)
{
me->Idle();
return;
}
// encourage the human player
if (!me->IsDoingScenario())
{
if (me->GetTeamNumber() == TEAM_CT)
{
if (me->GetTask() == CCSBot::GUARD_BOMB_ZONE &&
me->IsAtHidingSpot() &&
TheCSBots()->IsBombPlanted())
{
if (me->GetNearbyEnemyCount() == 0)
{
const float someTime = 30.0f;
const float littleTime = 11.0;
if (TheCSBots()->GetBombTimeLeft() > someTime)
me->GetChatter()->Encourage( "BombsiteSecure", RandomFloat( 10.0f, 15.0f ) );
else if (TheCSBots()->GetBombTimeLeft() > littleTime)
me->GetChatter()->Encourage( "WaitingForHumanToDefuseBomb", RandomFloat( 5.0f, 8.0f ) );
else
me->GetChatter()->Encourage( "WaitingForHumanToDefuseBombPanic", RandomFloat( 3.0f, 4.0f ) );
}
}
if (me->GetTask() == CCSBot::GUARD_HOSTAGES && me->IsAtHidingSpot())
{
if (me->GetNearbyEnemyCount() == 0)
{
CHostage *hostage = me->GetGameState()->GetNearestVisibleFreeHostage();
if (hostage)
{
me->GetChatter()->Encourage( "WaitingForHumanToRescueHostages", RandomFloat( 10.0f, 15.0f ) );
}
}
}
}
}
}
else
{
// we are moving to our hiding spot
// snipers periodically pause and fire while retreating
if (me->IsSniper() && me->IsEnemyVisible())
{
if (m_isPaused)
{
if (m_pauseTimer.IsElapsed())
{
// get moving
m_isPaused = false;
m_pauseTimer.Start( RandomFloat( 1.0f, 3.0f ) );
}
else
{
me->Wait( 0.2f );
}
}
else
{
if (m_pauseTimer.IsElapsed())
{
// pause for a moment
m_isPaused = true;
m_pauseTimer.Start( RandomFloat( 0.5f, 1.5f ) );
}
}
}
// if a Player is using this hiding spot, give up
float range;
CCSPlayer *camper = static_cast<CCSPlayer *>( UTIL_GetClosestPlayer( m_hidingSpot, &range ) );
const float closeRange = 75.0f;
if (camper && camper != me && range < closeRange && me->IsVisible( camper, CHECK_FOV ))
{
// player is in our hiding spot
me->PrintIfWatched( "Someone's in my hiding spot - picking another...\n" );
const int maxRetries = 3;
if (m_retry++ >= maxRetries)
{
me->PrintIfWatched( "Can't find a free hiding spot, giving up.\n" );
me->Idle();
return;
}
// pick another hiding spot near where we were planning on hiding
me->Hide( TheNavMesh->GetNavArea( m_hidingSpot ) );
return;
}
Vector toSpot;
toSpot.x = m_hidingSpot.x - myOrigin.x;
toSpot.y = m_hidingSpot.y - myOrigin.y;
toSpot.z = m_hidingSpot.z - me->GetFeetZ(); // use feet location
range = toSpot.Length();
// look outwards as we get close to our hiding spot
if (!me->IsEnemyVisible() && !m_isLookingOutward)
{
const float lookOutwardRange = 200.0f;
const float nearSpotRange = 10.0f;
if (range < lookOutwardRange && range > nearSpotRange)
{
m_isLookingOutward = true;
toSpot.x /= range;
toSpot.y /= range;
toSpot.z /= range;
me->SetLookAt( "Face outward", me->EyePosition() - 1000.0f * toSpot, PRIORITY_HIGH, 3.0f );
}
}
const float atDist = 20.0f;
if (range < atDist)
{
//-------------------------------------
// Just reached our hiding spot
//
m_isAtSpot = true;
m_hideTimer.Start( m_duration );
// make sure our approach points are valid, since we'll be watching them
me->ComputeApproachPoints();
me->ClearLookAt();
// ready our weapon and prepare to attack
me->EquipBestWeapon( me->IsUsingGrenade() );
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
// if we are a sniper, update our task
if (me->GetTask() == CCSBot::MOVE_TO_SNIPER_SPOT)
{
me->SetTask( CCSBot::SNIPING );
}
else if (me->GetTask() == CCSBot::GUARD_INITIAL_ENCOUNTER)
{
const float campChatterChance = 20.0f;
if (RandomFloat( 0, 100 ) < campChatterChance)
{
me->GetChatter()->Say( "WaitingHere" );
}
}
// determine which way to look
trace_t result;
float outAngle = 0.0f;
float outAngleRange = 0.0f;
for( float angle = 0.0f; angle < 360.0f; angle += 45.0f )
{
UTIL_TraceLine( me->EyePosition(), me->EyePosition() + 1000.0f * Vector( BotCOS(angle), BotSIN(angle), 0.0f ), MASK_PLAYERSOLID, me, COLLISION_GROUP_NONE, &result );
if (result.fraction > outAngleRange)
{
outAngle = angle;
outAngleRange = result.fraction;
}
}
me->SetLookAheadAngle( outAngle );
}
// move to hiding spot
if (me->UpdatePathMovement() != CCSBot::PROGRESSING && !m_isAtSpot)
{
// we couldn't get to our hiding spot - pick another
me->PrintIfWatched( "Can't get to my hiding spot - finding another...\n" );
// search from hiding spot, since we know it was valid
const Vector *pos = FindNearbyHidingSpot( me, m_hidingSpot, m_range, me->IsSniper() );
if (pos == NULL)
{
// no available hiding spots
me->PrintIfWatched( "No available hiding spots - hiding where I'm at.\n" );
// hide where we are
m_hidingSpot.x = myOrigin.x;
m_hidingSpot.x = myOrigin.y;
m_hidingSpot.z = me->GetFeetZ();
}
else
{
m_hidingSpot = *pos;
}
// build a path to our new hiding spot
if (me->ComputePath( m_hidingSpot, FASTEST_ROUTE ) == false)
{
me->PrintIfWatched( "Can't pathfind to hiding spot\n" );
me->Idle();
return;
}
}
}
}
//--------------------------------------------------------------------------------------------------------------
void HideState::OnExit( CCSBot *me )
{
m_isHoldingPosition = false;
me->StandUp();
me->ResetStuckMonitor();
//me->ClearLookAt();
me->ClearApproachPoints();
// if we have a shield, put it away
if (me->HasShield() && me->IsProtectedByShield())
me->SecondaryAttack();
}
@@ -0,0 +1,234 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
#include "cbase.h"
#include "cs_simple_hostage.h"
#include "cs_bot.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//--------------------------------------------------------------------------------------------------------------
/**
* Begin the hunt
*/
void HuntState::OnEnter( CCSBot *me )
{
// lurking death
if (me->IsUsingKnife() && me->IsWellPastSafe() && !me->IsHurrying())
me->Walk();
else
me->Run();
me->StandUp();
me->SetDisposition( CCSBot::ENGAGE_AND_INVESTIGATE );
me->SetTask( CCSBot::SEEK_AND_DESTROY );
me->DestroyPath();
}
//--------------------------------------------------------------------------------------------------------------
/**
* Hunt down our enemies
*/
void HuntState::OnUpdate( CCSBot *me )
{
// if we've been hunting for a long time, drop into Idle for a moment to
// select something else to do
const float huntingTooLongTime = 30.0f;
if (gpGlobals->curtime - me->GetStateTimestamp() > huntingTooLongTime)
{
// stop being a rogue and do the scenario, since there must not be many enemies left to hunt
me->PrintIfWatched( "Giving up hunting.\n" );
me->SetRogue( false );
me->Idle();
return;
}
// scenario logic
if (TheCSBots()->GetScenario() == CCSBotManager::SCENARIO_DEFUSE_BOMB)
{
if (me->GetTeamNumber() == TEAM_TERRORIST)
{
// if we have the bomb and it's time to plant, or we happen to be in a bombsite and it seems safe, do it
if (me->HasC4())
{
const float safeTime = 3.0f;
if (TheCSBots()->IsTimeToPlantBomb() ||
(me->IsAtBombsite() && gpGlobals->curtime - me->GetLastSawEnemyTimestamp() > safeTime))
{
me->Idle();
return;
}
}
// if we notice the bomb lying on the ground, go get it
if (me->NoticeLooseBomb())
{
me->FetchBomb();
return;
}
// if bomb has been planted, and we hear it, move to a hiding spot near the bomb and watch it
const Vector *bombPos = me->GetGameState()->GetBombPosition();
if (!me->IsRogue() && me->GetGameState()->IsBombPlanted() && bombPos)
{
me->SetTask( CCSBot::GUARD_TICKING_BOMB );
me->Hide( TheNavMesh->GetNavArea( *bombPos ) );
return;
}
}
else // CT
{
if (!me->IsRogue() && me->CanSeeLooseBomb())
{
// if we are near the loose bomb and can see it, hide nearby and guard it
me->SetTask( CCSBot::GUARD_LOOSE_BOMB );
me->Hide( TheCSBots()->GetLooseBombArea() );
me->GetChatter()->GuardingLooseBomb( TheCSBots()->GetLooseBomb() );
return;
}
else if (TheCSBots()->IsBombPlanted())
{
// rogues will defuse a bomb, but not guard the defuser
if (!me->IsRogue() || !TheCSBots()->GetBombDefuser())
{
// search for the planted bomb to defuse
me->Idle();
return;
}
}
}
}
else if (TheCSBots()->GetScenario() == CCSBotManager::SCENARIO_RESCUE_HOSTAGES)
{
if (me->GetTeamNumber() == TEAM_TERRORIST)
{
if (me->GetGameState()->AreAllHostagesBeingRescued())
{
// all hostages are being rescued, head them off at the escape zones
if (me->GuardRandomZone())
{
me->SetTask( CCSBot::GUARD_HOSTAGE_RESCUE_ZONE );
me->PrintIfWatched( "Trying to beat them to an escape zone!\n" );
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
me->GetChatter()->GuardingHostageEscapeZone( IS_PLAN );
return;
}
}
// if safe time is up, and we stumble across a hostage, guard it
if (!me->IsRogue() && !me->IsSafe())
{
CHostage *hostage = me->GetGameState()->GetNearestVisibleFreeHostage();
if (hostage)
{
CNavArea *area = TheNavMesh->GetNearestNavArea( GetCentroid( hostage ) );
if (area)
{
// we see a free hostage, guard it
me->SetTask( CCSBot::GUARD_HOSTAGES );
me->Hide( area );
me->PrintIfWatched( "I'm guarding hostages\n" );
me->GetChatter()->GuardingHostages( area->GetPlace(), IS_PLAN );
return;
}
}
}
}
}
// listen for enemy noises
if (me->HeardInterestingNoise())
{
me->InvestigateNoise();
return;
}
// look around
me->UpdateLookAround();
// if we have reached our destination area, pick a new one
// if our path fails, pick a new one
if (me->GetLastKnownArea() == m_huntArea || me->UpdatePathMovement() != CCSBot::PROGRESSING)
{
// pick a new hunt area
const float earlyGameTime = 45.0f;
if (TheCSBots()->GetElapsedRoundTime() < earlyGameTime && !me->HasVisitedEnemySpawn())
{
// in the early game, rush the enemy spawn
CBaseEntity *enemySpawn = TheCSBots()->GetRandomSpawn( OtherTeam( me->GetTeamNumber() ) );
//ADRIAN: REVISIT
if ( enemySpawn )
{
m_huntArea = TheNavMesh->GetNavArea( enemySpawn->WorldSpaceCenter() );
}
}
else
{
m_huntArea = NULL;
float oldest = 0.0f;
int areaCount = 0;
const float minSize = 150.0f;
FOR_EACH_VEC( TheNavAreas, it )
{
CNavArea *area = TheNavAreas[ it ];
++areaCount;
// skip the small areas
Extent extent;
area->GetExtent(&extent);
if (extent.hi.x - extent.lo.x < minSize || extent.hi.y - extent.lo.y < minSize)
continue;
// keep track of the least recently cleared area
float age = gpGlobals->curtime - area->GetClearedTimestamp( me->GetTeamNumber()-1 );
if (age > oldest)
{
oldest = age;
m_huntArea = area;
}
}
// if all the areas were too small, pick one at random
int which = RandomInt( 0, areaCount-1 );
areaCount = 0;
FOR_EACH_VEC( TheNavAreas, hit )
{
m_huntArea = TheNavAreas[ hit ];
if (which == areaCount)
break;
--which;
}
}
if (m_huntArea)
{
// create a new path to a far away area of the map
me->ComputePath( m_huntArea->GetCenter() );
}
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Done hunting
*/
void HuntState::OnExit( CCSBot *me )
{
}
@@ -0,0 +1,887 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
#include "cbase.h"
#include "cs_simple_hostage.h"
#include "cs_bot.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
// range for snipers to select a hiding spot
const float sniperHideRange = 2000.0f;
//--------------------------------------------------------------------------------------------------------------
/**
* The Idle state.
* We never stay in the Idle state - it is a "home base" for the state machine that
* does various checks to determine what we should do next.
*/
void IdleState::OnEnter( CCSBot *me )
{
me->DestroyPath();
me->SetBotEnemy( NULL );
// lurking death
if (me->IsUsingKnife() && me->IsWellPastSafe() && !me->IsHurrying())
me->Walk();
//
// Since Idle assigns tasks, we assume that coming back to Idle means our task is complete
//
me->SetTask( CCSBot::SEEK_AND_DESTROY );
me->SetDisposition( CCSBot::ENGAGE_AND_INVESTIGATE );
}
//--------------------------------------------------------------------------------------------------------------
/**
* Determine what we should do next
*/
void IdleState::OnUpdate( CCSBot *me )
{
// all other states assume GetLastKnownArea() is valid, ensure that it is
if (me->GetLastKnownArea() == NULL && me->StayOnNavMesh() == false)
return;
// zombies never leave the Idle state
if (cv_bot_zombie.GetBool())
{
me->ResetStuckMonitor();
return;
}
// if we are in the early "safe" time, grab a knife or grenade
if (me->IsSafe())
{
// if we have a grenade, use it
if (!me->EquipGrenade())
{
// high-skill bots run with the knife, unless using the Scout (which moves faster)
if (me->GetProfile()->GetSkill() > 0.33f && !me->IsUsing( WEAPON_SCOUT ))
{
me->EquipKnife();
}
}
}
// if round is over, hunt
if (me->GetGameState()->IsRoundOver())
{
// if we are escorting hostages, try to get to the rescue zone
if (me->GetHostageEscortCount())
{
const CCSBotManager::Zone *zone = TheCSBots()->GetClosestZone( me->GetLastKnownArea(), PathCost( me, FASTEST_ROUTE ) );
const Vector *zonePos = TheCSBots()->GetRandomPositionInZone( zone );
if (zonePos)
{
me->SetTask( CCSBot::RESCUE_HOSTAGES );
me->Run();
me->SetDisposition( CCSBot::SELF_DEFENSE );
me->MoveTo( *zonePos, FASTEST_ROUTE );
me->PrintIfWatched( "Trying to rescue hostages at the end of the round\n" );
return;
}
}
me->Hunt();
return;
}
const float defenseSniperCampChance = 75.0f;
const float offenseSniperCampChance = 10.0f;
// if we were following someone, continue following them
if (me->IsFollowing())
{
me->ContinueFollowing();
return;
}
//
// Scenario logic
//
switch (TheCSBots()->GetScenario())
{
//======================================================================================================
case CCSBotManager::SCENARIO_DEFUSE_BOMB:
{
// if this is a bomb game and we have the bomb, go plant it
if (me->GetTeamNumber() == TEAM_TERRORIST)
{
if (me->GetGameState()->IsBombPlanted())
{
if (me->GetGameState()->GetPlantedBombsite() != CSGameState::UNKNOWN)
{
// T's always know where the bomb is - go defend it
const CCSBotManager::Zone *zone = TheCSBots()->GetZone( me->GetGameState()->GetPlantedBombsite() );
if (zone)
{
me->SetTask( CCSBot::GUARD_TICKING_BOMB );
Place place = TheNavMesh->GetPlace( zone->m_center );
if (place != UNDEFINED_PLACE)
{
// pick a random hiding spot in this place
const Vector *spot = FindRandomHidingSpot( me, place, me->IsSniper() );
if (spot)
{
me->Hide( *spot );
return;
}
}
// hide nearby
me->Hide( TheNavMesh->GetNearestNavArea( zone->m_center ) );
return;
}
}
else
{
// ask our teammates where the bomb is
me->GetChatter()->RequestBombLocation();
// we dont know where the bomb is - we must search the bombsites
int zoneIndex = me->GetGameState()->GetNextBombsiteToSearch();
// move to bombsite - if we reach it, we'll update its cleared status, causing us to select another
const Vector *pos = TheCSBots()->GetRandomPositionInZone( TheCSBots()->GetZone( zoneIndex ) );
if (pos)
{
me->SetTask( CCSBot::FIND_TICKING_BOMB );
me->MoveTo( *pos );
return;
}
}
}
else if (me->HasC4())
{
// if we're at a bomb site, plant the bomb
if (me->IsAtBombsite())
{
// plant it
me->SetTask( CCSBot::PLANT_BOMB );
me->PlantBomb();
// radio to the team
me->GetChatter()->PlantingTheBomb( me->GetPlace() );
return;
}
else if (TheCSBots()->IsTimeToPlantBomb())
{
// move to the closest bomb site
const CCSBotManager::Zone *zone = TheCSBots()->GetClosestZone( me->GetLastKnownArea(), PathCost( me ) );
if (zone)
{
// pick a random spot within the bomb zone
const Vector *pos = TheCSBots()->GetRandomPositionInZone( zone );
if (pos)
{
// move to bombsite
me->SetTask( CCSBot::PLANT_BOMB );
me->Run();
me->MoveTo( *pos );
return;
}
}
}
}
else
{
// at the start of the round, we may decide to defend "initial encounter" areas
// where we will first meet the enemy rush
if (me->IsSafe())
{
float defendRushChance = -17.0f * (me->GetMorale() - 2);
if (me->IsSniper() || RandomFloat( 0.0f, 100.0f ) < defendRushChance)
{
if (me->MoveToInitialEncounter())
{
me->PrintIfWatched( "I'm guarding an initial encounter area\n" );
me->SetTask( CCSBot::GUARD_INITIAL_ENCOUNTER );
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
return;
}
}
}
// small chance of sniper camping on offense, if we aren't carrying the bomb
if (me->GetFriendsRemaining() && me->IsSniper() && RandomFloat( 0, 100.0f ) < offenseSniperCampChance)
{
me->SetTask( CCSBot::MOVE_TO_SNIPER_SPOT );
me->Hide( me->GetLastKnownArea(), RandomFloat( 10.0f, 30.0f ), sniperHideRange );
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
me->PrintIfWatched( "Sniping!\n" );
return;
}
// if the bomb is loose (on the ground), go get it
if (me->NoticeLooseBomb())
{
me->FetchBomb();
return;
}
// if bomb has been planted, and we hear it, move to a hiding spot near the bomb and guard it
if (!me->IsRogue() && me->GetGameState()->IsBombPlanted() && me->GetGameState()->GetBombPosition())
{
const Vector *bombPos = me->GetGameState()->GetBombPosition();
if (bombPos)
{
me->SetTask( CCSBot::GUARD_TICKING_BOMB );
me->Hide( TheNavMesh->GetNavArea( *bombPos ) );
return;
}
}
}
}
else // CT ------------------------------------------------------------------------------------------
{
if (me->GetGameState()->IsBombPlanted())
{
// if the bomb has been planted, attempt to defuse it
const Vector *bombPos = me->GetGameState()->GetBombPosition();
if (bombPos)
{
// if someone is defusing the bomb, guard them
if (TheCSBots()->GetBombDefuser())
{
if (!me->IsRogue())
{
me->SetTask( CCSBot::GUARD_BOMB_DEFUSER );
me->Hide( TheNavMesh->GetNavArea( *bombPos ) );
return;
}
}
else if (me->IsDoingScenario())
{
// move to the bomb and defuse it
me->SetTask( CCSBot::DEFUSE_BOMB );
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
me->MoveTo( *bombPos );
return;
}
else
{
// we're not allowed to defuse, guard the bomb zone
me->SetTask( CCSBot::GUARD_BOMB_ZONE );
me->Hide( TheNavMesh->GetNavArea( *bombPos ) );
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
return;
}
}
else if (me->GetGameState()->GetPlantedBombsite() != CSGameState::UNKNOWN)
{
// we know which bombsite, but not exactly where the bomb is, go there
const CCSBotManager::Zone *zone = TheCSBots()->GetZone( me->GetGameState()->GetPlantedBombsite() );
if (zone)
{
if (me->IsDoingScenario())
{
me->SetTask( CCSBot::DEFUSE_BOMB );
me->MoveTo( zone->m_center );
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
return;
}
else
{
// we're not allowed to defuse, guard the bomb zone
me->SetTask( CCSBot::GUARD_BOMB_ZONE );
me->Hide( TheNavMesh->GetNavArea( zone->m_center ) );
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
return;
}
}
}
else
{
// we dont know where the bomb is - we must search the bombsites
// find closest un-cleared bombsite
const CCSBotManager::Zone *zone = NULL;
float travelDistance = 9999999.9f;
for( int z=0; z<TheCSBots()->GetZoneCount(); ++z )
{
if (TheCSBots()->GetZone(z)->m_areaCount == 0)
continue;
// don't check bombsites that have been cleared
if (me->GetGameState()->IsBombsiteClear( z ))
continue;
// just use the first overlapping nav area as a reasonable approximation
ShortestPathCost cost = ShortestPathCost();
float dist = NavAreaTravelDistance( me->GetLastKnownArea(),
TheNavMesh->GetNearestNavArea( TheCSBots()->GetZone(z)->m_center ),
cost );
if (dist >= 0.0f && dist < travelDistance)
{
zone = TheCSBots()->GetZone(z);
travelDistance = dist;
}
}
if (zone)
{
const float farAwayRange = 2000.0f;
if (travelDistance > farAwayRange)
{
zone = NULL;
}
}
// if closest bombsite is "far away", pick one at random
if (zone == NULL)
{
int zoneIndex = me->GetGameState()->GetNextBombsiteToSearch();
zone = TheCSBots()->GetZone( zoneIndex );
}
// move to bombsite - if we reach it, we'll update its cleared status, causing us to select another
if (zone)
{
const Vector *pos = TheCSBots()->GetRandomPositionInZone( zone );
if (pos)
{
me->SetTask( CCSBot::FIND_TICKING_BOMB );
me->MoveTo( *pos );
return;
}
}
}
AssertMsg( 0, "A CT bot doesn't know what to do while the bomb is planted!\n" );
}
// if we have a sniper rifle, we like to camp, whether rogue or not
if (me->IsSniper() && !me->IsSafe())
{
if (RandomFloat( 0, 100 ) <= defenseSniperCampChance)
{
CNavArea *snipingArea = NULL;
// if the bomb is loose, snipe near it
const Vector *bombPos = me->GetGameState()->GetBombPosition();
if (me->GetGameState()->IsLooseBombLocationKnown() && bombPos)
{
snipingArea = TheNavMesh->GetNearestNavArea( *bombPos );
me->PrintIfWatched( "Sniping near loose bomb\n" );
}
else
{
// snipe bomb zone(s)
const CCSBotManager::Zone *zone = TheCSBots()->GetRandomZone();
if (zone)
{
snipingArea = TheCSBots()->GetRandomAreaInZone( zone );
me->PrintIfWatched( "Sniping near bombsite\n" );
}
}
if (snipingArea)
{
me->SetTask( CCSBot::MOVE_TO_SNIPER_SPOT );
me->Hide( snipingArea, -1.0, sniperHideRange );
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
return;
}
}
}
// rogues just hunt, unless they want to snipe
// if the whole team has decided to rush, hunt
// if we know the bomb is dropped, hunt for enemies and the loose bomb
if (me->IsRogue() || TheCSBots()->IsDefenseRushing() || me->GetGameState()->IsLooseBombLocationKnown())
{
me->Hunt();
return;
}
// the lower our morale gets, the more we want to camp the bomb zone(s)
// only decide to camp at the start of the round, or if we haven't seen anything for a long time
if (me->IsSafe() || me->HasNotSeenEnemyForLongTime())
{
float guardBombsiteChance = -34.0f * me->GetMorale();
if (RandomFloat( 0.0f, 100.0f ) < guardBombsiteChance)
{
float guardRange = 500.0f + 100.0f * (me->GetMorale() + 3);
// guard bomb zone(s)
const CCSBotManager::Zone *zone = TheCSBots()->GetRandomZone();
if (zone)
{
CNavArea *area = TheCSBots()->GetRandomAreaInZone( zone );
if (area)
{
me->PrintIfWatched( "I'm guarding a bombsite\n" );
me->GetChatter()->GuardingBombsite( area->GetPlace() );
me->SetTask( CCSBot::GUARD_BOMB_ZONE );
me->Hide( area, -1.0, guardRange );
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
return;
}
}
}
// at the start of the round, we may decide to defend "initial encounter" areas
// where we will first meet the enemy rush
if (me->IsSafe())
{
float defendRushChance = -17.0f * (me->GetMorale() - 2);
if (me->IsSniper() || RandomFloat( 0.0f, 100.0f ) < defendRushChance)
{
if (me->MoveToInitialEncounter())
{
me->PrintIfWatched( "I'm guarding an initial encounter area\n" );
me->SetTask( CCSBot::GUARD_INITIAL_ENCOUNTER );
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
return;
}
}
}
}
}
break;
}
//======================================================================================================
case CCSBotManager::SCENARIO_ESCORT_VIP:
{
if (me->GetTeamNumber() == TEAM_TERRORIST)
{
// if we have a sniper rifle, we like to camp, whether rogue or not
if (me->IsSniper())
{
if (RandomFloat( 0, 100 ) <= defenseSniperCampChance)
{
// snipe escape zone(s)
const CCSBotManager::Zone *zone = TheCSBots()->GetRandomZone();
if (zone)
{
CNavArea *area = TheCSBots()->GetRandomAreaInZone( zone );
if (area)
{
me->SetTask( CCSBot::MOVE_TO_SNIPER_SPOT );
me->Hide( area, -1.0, sniperHideRange );
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
me->PrintIfWatched( "Sniping near escape zone\n" );
return;
}
}
}
}
// rogues just hunt, unless they want to snipe
// if the whole team has decided to rush, hunt
if (me->IsRogue() || TheCSBots()->IsDefenseRushing())
break;
// the lower our morale gets, the more we want to camp the escape zone(s)
float guardEscapeZoneChance = -34.0f * me->GetMorale();
if (RandomFloat( 0.0f, 100.0f ) < guardEscapeZoneChance)
{
// guard escape zone(s)
const CCSBotManager::Zone *zone = TheCSBots()->GetRandomZone();
if (zone)
{
CNavArea *area = TheCSBots()->GetRandomAreaInZone( zone );
if (area)
{
// guard the escape zone - stay closer if our morale is low
me->SetTask( CCSBot::GUARD_VIP_ESCAPE_ZONE );
me->PrintIfWatched( "I'm guarding an escape zone\n" );
float escapeGuardRange = 750.0f + 250.0f * (me->GetMorale() + 3);
me->Hide( area, -1.0, escapeGuardRange );
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
return;
}
}
}
}
else // CT
{
if (me->m_bIsVIP)
{
// if early in round, pick a random zone, otherwise pick closest zone
const float earlyTime = 20.0f;
const CCSBotManager::Zone *zone = NULL;
if (TheCSBots()->GetElapsedRoundTime() < earlyTime)
{
// pick random zone
zone = TheCSBots()->GetRandomZone();
}
else
{
// pick closest zone
zone = TheCSBots()->GetClosestZone( me->GetLastKnownArea(), PathCost( me ) );
}
if (zone)
{
// pick a random spot within the escape zone
const Vector *pos = TheCSBots()->GetRandomPositionInZone( zone );
if (pos)
{
// move to escape zone
me->SetTask( CCSBot::VIP_ESCAPE );
me->Run();
me->MoveTo( *pos );
// tell team to follow
const float repeatTime = 30.0f;
if (me->GetFriendsRemaining() &&
TheCSBots()->GetRadioMessageInterval( RADIO_FOLLOW_ME, me->GetTeamNumber() ) > repeatTime)
me->SendRadioMessage( RADIO_FOLLOW_ME );
return;
}
}
}
else
{
// small chance of sniper camping on offense, if we aren't VIP
if (me->GetFriendsRemaining() && me->IsSniper() && RandomFloat( 0, 100.0f ) < offenseSniperCampChance)
{
me->SetTask( CCSBot::MOVE_TO_SNIPER_SPOT );
me->Hide( me->GetLastKnownArea(), RandomFloat( 10.0f, 30.0f ), sniperHideRange );
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
me->PrintIfWatched( "Sniping!\n" );
return;
}
}
}
break;
}
//======================================================================================================
case CCSBotManager::SCENARIO_RESCUE_HOSTAGES:
{
if (me->GetTeamNumber() == TEAM_TERRORIST)
{
bool campHostages;
// if we are in early game, camp the hostages
if (me->IsSafe())
{
campHostages = true;
}
else if (me->GetGameState()->HaveSomeHostagesBeenTaken() || me->GetGameState()->AreAllHostagesBeingRescued())
{
campHostages = false;
}
else
{
// later in the game, camp either hostages or escape zone
const float campZoneChance = 100.0f * (TheCSBots()->GetElapsedRoundTime() - me->GetSafeTime())/120.0f;
campHostages = (RandomFloat( 0, 100 ) > campZoneChance) ? true : false;
}
// if we have a sniper rifle, we like to camp, whether rogue or not
if (me->IsSniper())
{
// the at start of the round, snipe the initial rush
if (me->IsSafe())
{
if (me->MoveToInitialEncounter())
{
me->PrintIfWatched( "I'm sniping an initial encounter area\n" );
me->SetTask( CCSBot::GUARD_INITIAL_ENCOUNTER );
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
return;
}
}
if (RandomFloat( 0, 100 ) <= defenseSniperCampChance)
{
const Vector *hostagePos = me->GetGameState()->GetRandomFreeHostagePosition();
if (hostagePos && campHostages)
{
me->SetTask( CCSBot::MOVE_TO_SNIPER_SPOT );
me->PrintIfWatched( "Sniping near hostages\n" );
me->Hide( TheNavMesh->GetNearestNavArea( *hostagePos ), -1.0, sniperHideRange );
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
return;
}
else
{
// camp the escape zone(s)
if (me->GuardRandomZone( sniperHideRange ))
{
me->SetTask( CCSBot::MOVE_TO_SNIPER_SPOT );
me->PrintIfWatched( "Sniping near a rescue zone\n" );
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
return;
}
}
}
}
// if safe time is up, and we stumble across a hostage, guard it
if (!me->IsSafe() && !me->IsRogue())
{
CBaseEntity *hostage = me->GetGameState()->GetNearestVisibleFreeHostage();
if (hostage)
{
// we see a free hostage, guard it
CNavArea *area = TheNavMesh->GetNearestNavArea( GetCentroid( hostage ) );
if (area)
{
me->SetTask( CCSBot::GUARD_HOSTAGES );
me->Hide( area );
me->PrintIfWatched( "I'm guarding hostages I found\n" );
// don't chatter here - he'll tell us when he's in his hiding spot
return;
}
}
}
// decide if we want to hunt, or guard
const float huntChance = 70.0f + 25.0f * me->GetMorale();
// rogues just hunt, unless they want to snipe
// if the whole team has decided to rush, hunt
if (me->GetFriendsRemaining())
{
if (me->IsRogue() || TheCSBots()->IsDefenseRushing() || RandomFloat( 0, 100 ) < huntChance)
{
me->Hunt();
return;
}
}
// at the start of the round, we may decide to defend "initial encounter" areas
// where we will first meet the enemy rush
if (me->IsSafe())
{
float defendRushChance = -17.0f * (me->GetMorale() - 2);
if (me->IsSniper() || RandomFloat( 0.0f, 100.0f ) < defendRushChance)
{
if (me->MoveToInitialEncounter())
{
me->PrintIfWatched( "I'm guarding an initial encounter area\n" );
me->SetTask( CCSBot::GUARD_INITIAL_ENCOUNTER );
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
return;
}
}
}
// decide whether to camp the hostages or the escape zones
const Vector *hostagePos = me->GetGameState()->GetRandomFreeHostagePosition();
if (hostagePos && campHostages)
{
CNavArea *area = TheNavMesh->GetNearestNavArea( *hostagePos );
if (area)
{
// guard the hostages - stay closer to hostages if our morale is low
me->SetTask( CCSBot::GUARD_HOSTAGES );
me->PrintIfWatched( "I'm guarding hostages\n" );
float hostageGuardRange = 750.0f + 250.0f * (me->GetMorale() + 3); // 2000
me->Hide( area, -1.0, hostageGuardRange );
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
if (RandomFloat( 0, 100 ) < 50)
me->GetChatter()->GuardingHostages( area->GetPlace(), IS_PLAN );
return;
}
}
// guard rescue zone(s)
if (me->GuardRandomZone())
{
me->SetTask( CCSBot::GUARD_HOSTAGE_RESCUE_ZONE );
me->PrintIfWatched( "I'm guarding a rescue zone\n" );
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
me->GetChatter()->GuardingHostageEscapeZone( IS_PLAN );
return;
}
}
else // CT ---------------------------------------------------------------------------------
{
// only decide to do something else if we aren't already rescuing hostages
if (!me->GetHostageEscortCount())
{
// small chance of sniper camping on offense
if (me->GetFriendsRemaining() && me->IsSniper() && RandomFloat( 0, 100.0f ) < offenseSniperCampChance)
{
me->SetTask( CCSBot::MOVE_TO_SNIPER_SPOT );
me->Hide( me->GetLastKnownArea(), RandomFloat( 10.0f, 30.0f ), sniperHideRange );
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
me->PrintIfWatched( "Sniping!\n" );
return;
}
if (me->GetFriendsRemaining() && !me->GetHostageEscortCount())
{
// rogues just hunt, unless all friends are dead
// if we have friends left, we might go hunting instead of hostage rescuing
const float huntChance = 33.3f;
if (me->IsRogue() || RandomFloat( 0.0f, 100.0f ) < huntChance)
{
me->Hunt();
return;
}
}
}
// at the start of the round, we may decide to defend "initial encounter" areas
// where we will first meet the enemy rush
if (me->IsSafe())
{
float defendRushChance = -17.0f * (me->GetMorale() - 2);
if (me->IsSniper() || RandomFloat( 0.0f, 100.0f ) < defendRushChance)
{
if (me->MoveToInitialEncounter())
{
me->PrintIfWatched( "I'm guarding an initial encounter area\n" );
me->SetTask( CCSBot::GUARD_INITIAL_ENCOUNTER );
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
return;
}
}
}
// look for free hostages - CT's have radar so they know where hostages are at all times
CHostage *hostage = me->GetGameState()->GetNearestFreeHostage();
// if we are not allowed to do the scenario, guard the hostages to clear the area for the human(s)
if (!me->IsDoingScenario())
{
if (hostage)
{
CNavArea *area = TheNavMesh->GetNearestNavArea( GetCentroid( hostage ) );
if (area)
{
me->SetTask( CCSBot::GUARD_HOSTAGES );
me->Hide( area );
me->PrintIfWatched( "I'm securing the hostages for a human to rescue\n" );
return;
}
}
me->Hunt();
return;
}
bool fetchHostages = false;
bool rescueHostages = false;
const CCSBotManager::Zone *zone = NULL;
me->SetGoalEntity( NULL );
// if we are escorting hostages, determine where to take them
if (me->GetHostageEscortCount())
zone = TheCSBots()->GetClosestZone( me->GetLastKnownArea(), PathCost( me, FASTEST_ROUTE ) );
// if we are escorting hostages and there are more hostages to rescue,
// determine whether it's faster to rescue the ones we have, or go get the remaining ones
if (hostage)
{
Vector hostageOrigin = GetCentroid( hostage );
if (zone)
{
PathCost cost( me, FASTEST_ROUTE );
float toZone = NavAreaTravelDistance( me->GetLastKnownArea(), zone->m_area[0], cost );
float toHostage = NavAreaTravelDistance( me->GetLastKnownArea(), TheNavMesh->GetNearestNavArea( GetCentroid( hostage ) ), cost );
if (toHostage < 0.0f)
{
rescueHostages = true;
}
else
{
if (toZone < toHostage)
rescueHostages = true;
else
fetchHostages = true;
}
}
else
{
fetchHostages = true;
}
}
else if (zone)
{
rescueHostages = true;
}
if (fetchHostages)
{
// go get hostages
me->SetTask( CCSBot::COLLECT_HOSTAGES );
me->Run();
me->SetGoalEntity( hostage );
me->ResetWaitForHostagePatience();
// if we already have some hostages, move to the others by the quickest route
RouteType route = (me->GetHostageEscortCount()) ? FASTEST_ROUTE : SAFEST_ROUTE;
me->MoveTo( GetCentroid( hostage ), route );
me->PrintIfWatched( "I'm collecting hostages\n" );
return;
}
const Vector *zonePos = TheCSBots()->GetRandomPositionInZone( zone );
if (rescueHostages && zonePos)
{
me->SetTask( CCSBot::RESCUE_HOSTAGES );
me->Run();
me->SetDisposition( CCSBot::SELF_DEFENSE );
me->MoveTo( *zonePos, FASTEST_ROUTE );
me->PrintIfWatched( "I'm rescuing hostages\n" );
me->GetChatter()->EscortingHostages();
return;
}
}
break;
}
default: // deathmatch
{
// sniping check
if (me->GetFriendsRemaining() && me->IsSniper() && RandomFloat( 0, 100.0f ) < offenseSniperCampChance)
{
me->SetTask( CCSBot::MOVE_TO_SNIPER_SPOT );
me->Hide( me->GetLastKnownArea(), RandomFloat( 10.0f, 30.0f ), sniperHideRange );
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
me->PrintIfWatched( "Sniping!\n" );
return;
}
break;
}
}
// if we have nothing special to do, go hunting for enemies
me->Hunt();
}
@@ -0,0 +1,139 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
#include "cbase.h"
#include "cs_bot.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//--------------------------------------------------------------------------------------------------------------
/**
* Move towards currently heard noise
*/
void InvestigateNoiseState::AttendCurrentNoise( CCSBot *me )
{
if (!me->IsNoiseHeard() && me->GetNoisePosition())
return;
// remember where the noise we heard was
m_checkNoisePosition = *me->GetNoisePosition();
// tell our teammates (unless the noise is obvious, like gunfire)
if (me->IsWellPastSafe() && me->HasNotSeenEnemyForLongTime() && me->GetNoisePriority() != PRIORITY_HIGH)
me->GetChatter()->HeardNoise( *me->GetNoisePosition() );
// figure out how to get to the noise
me->PrintIfWatched( "Attending to noise...\n" );
me->ComputePath( m_checkNoisePosition, FASTEST_ROUTE );
const float minAttendTime = 3.0f;
const float maxAttendTime = 10.0f;
m_minTimer.Start( RandomFloat( minAttendTime, maxAttendTime ) );
// consume the noise
me->ForgetNoise();
}
//--------------------------------------------------------------------------------------------------------------
void InvestigateNoiseState::OnEnter( CCSBot *me )
{
AttendCurrentNoise( me );
}
//--------------------------------------------------------------------------------------------------------------
/**
* @todo Use TravelDistance instead of distance...
*/
void InvestigateNoiseState::OnUpdate( CCSBot *me )
{
Vector myOrigin = GetCentroid( me );
// keep an ear out for closer noises...
if (m_minTimer.IsElapsed())
{
const float nearbyRange = 500.0f;
if (me->HeardInterestingNoise() && me->GetNoiseRange() < nearbyRange)
{
// new sound is closer
AttendCurrentNoise( me );
}
}
// if the pathfind fails, give up
if (!me->HasPath())
{
me->Idle();
return;
}
// look around
me->UpdateLookAround();
// get distance remaining on our path until we reach the source of the noise
float range = me->GetPathDistanceRemaining();
if (me->IsUsingKnife())
{
if (me->IsHurrying())
me->Run();
else
me->Walk();
}
else
{
const float closeToNoiseRange = 1500.0f;
if (range < closeToNoiseRange)
{
// if we dont have many friends left, or we are alone, and we are near noise source, sneak quietly
if ((me->GetNearbyFriendCount() == 0 || me->GetFriendsRemaining() <= 2) && !me->IsHurrying())
{
me->Walk();
}
else
{
me->Run();
}
}
else
{
me->Run();
}
}
// if we can see the noise position and we're close enough to it and looking at it,
// we don't need to actually move there (it's checked enough)
const float closeRange = 500.0f;
if (range < closeRange)
{
if (me->IsVisible( m_checkNoisePosition, CHECK_FOV ))
{
// can see noise position
me->PrintIfWatched( "Noise location is clear.\n" );
me->ForgetNoise();
me->Idle();
return;
}
}
// move towards noise
if (me->UpdatePathMovement() != CCSBot::PROGRESSING)
{
me->Idle();
}
}
//--------------------------------------------------------------------------------------------------------------
void InvestigateNoiseState::OnExit( CCSBot *me )
{
// reset to run mode in case we were sneaking about
me->Run();
}
@@ -0,0 +1,366 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
#include "cbase.h"
#include "cs_simple_hostage.h"
#include "cs_bot.h"
#include "cs_gamerules.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//--------------------------------------------------------------------------------------------------------------
/**
* Move to a potentially far away position.
*/
void MoveToState::OnEnter( CCSBot *me )
{
if (me->IsUsingKnife() && me->IsWellPastSafe() && !me->IsHurrying())
{
me->Walk();
}
else
{
me->Run();
}
// if we need to find the bomb, get there as quick as we can
RouteType route;
switch (me->GetTask())
{
case CCSBot::FIND_TICKING_BOMB:
case CCSBot::DEFUSE_BOMB:
case CCSBot::MOVE_TO_LAST_KNOWN_ENEMY_POSITION:
route = FASTEST_ROUTE;
break;
default:
route = SAFEST_ROUTE;
break;
}
// build path to, or nearly to, goal position
me->ComputePath( m_goalPosition, route );
m_radioedPlan = false;
m_askedForCover = false;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Move to a potentially far away position.
*/
void MoveToState::OnUpdate( CCSBot *me )
{
Vector myOrigin = GetCentroid( me );
// assume that we are paying attention and close enough to know our enemy died
if (me->GetTask() == CCSBot::MOVE_TO_LAST_KNOWN_ENEMY_POSITION)
{
/// @todo Account for reaction time so we take some time to realized the enemy is dead
CBasePlayer *victim = static_cast<CBasePlayer *>( me->GetTaskEntity() );
if (victim == NULL || !victim->IsAlive())
{
me->PrintIfWatched( "The enemy I was chasing was killed - giving up.\n" );
me->Idle();
return;
}
}
// look around
me->UpdateLookAround();
//
// Scenario logic
//
switch (TheCSBots()->GetScenario())
{
case CCSBotManager::SCENARIO_DEFUSE_BOMB:
{
// if the bomb has been planted, find it
// NOTE: This task is used by both CT and T's to find the bomb
if (me->GetTask() == CCSBot::FIND_TICKING_BOMB)
{
if (!me->GetGameState()->IsBombPlanted())
{
// the bomb is not planted - give up this task
me->Idle();
return;
}
if (me->GetGameState()->GetPlantedBombsite() != CSGameState::UNKNOWN)
{
// we know where the bomb is planted, stop searching
me->Idle();
return;
}
// check off bombsites that we explore or happen to stumble into
for( int z=0; z<TheCSBots()->GetZoneCount(); ++z )
{
// don't re-check zones
if (me->GetGameState()->IsBombsiteClear( z ))
continue;
if (TheCSBots()->GetZone(z)->m_extent.Contains( myOrigin ))
{
// note this bombsite is clear
me->GetGameState()->ClearBombsite( z );
if (me->GetTeamNumber() == TEAM_CT)
{
// tell teammates this bombsite is clear
me->GetChatter()->BombsiteClear( z );
}
// find another zone to check
me->Idle();
return;
}
}
// move to a bombsite
break;
}
if (me->GetTeamNumber() == TEAM_CT)
{
if (me->GetGameState()->IsBombPlanted())
{
switch( me->GetTask() )
{
case CCSBot::DEFUSE_BOMB:
{
// if we are near the bombsite and there is time left, sneak in (unless all enemies are dead)
if (me->GetEnemiesRemaining())
{
const float plentyOfTime = 15.0f;
if (TheCSBots()->GetBombTimeLeft() > plentyOfTime)
{
// get distance remaining on our path until we reach the bombsite
float range = me->GetPathDistanceRemaining();
const float closeRange = 1500.0f;
if (range < closeRange)
{
me->Walk();
}
else
{
me->Run();
}
}
}
else
{
// everyone is dead - run!
me->Run();
}
// if we are trying to defuse the bomb, and someone has started defusing, guard them instead
if (me->CanSeePlantedBomb() && TheCSBots()->GetBombDefuser())
{
me->GetChatter()->Say( "CoveringFriend" );
me->Idle();
return;
}
// if we are near the bomb, defuse it (if we are reloading, don't try to defuse until we finish)
const Vector *bombPos = me->GetGameState()->GetBombPosition();
if (bombPos && !me->IsReloading())
{
const float defuseRange = 100.0f; // 50
if ((*bombPos - me->EyePosition()).IsLengthLessThan( defuseRange ))
{
// make sure we can see the bomb
if (me->IsVisible( *bombPos ))
{
me->DefuseBomb();
return;
}
}
}
break;
}
default:
{
// we need to find the bomb
me->Idle();
return;
}
}
}
}
else // TERRORIST
{
if (me->GetTask() == CCSBot::PLANT_BOMB )
{
if ( me->GetFriendsRemaining() )
{
// if we are about to plant, radio for cover
if (!m_askedForCover)
{
const float nearPlantSite = 50.0f;
if (me->IsAtBombsite() && me->GetPathDistanceRemaining() < nearPlantSite)
{
// radio to the team
me->GetChatter()->PlantingTheBomb( me->GetPlace() );
m_askedForCover = true;
}
// after we have started to move to the bombsite, tell team we're going to plant, and where
// don't do this if we have already radioed that we are starting to plant
if (!m_radioedPlan)
{
const float radioTime = 2.0f;
if (gpGlobals->curtime - me->GetStateTimestamp() > radioTime)
{
// radio to the team if we're more than 10 seconds (2400 units) out
const float nearPlantSite = 2400.0f;
if ( me->GetPathDistanceRemaining() >= nearPlantSite )
{
me->GetChatter()->GoingToPlantTheBomb( TheNavMesh->GetPlace( m_goalPosition ) );
}
m_radioedPlan = true;
}
}
}
}
}
}
break;
}
//--------------------------------------------------------------------------------------------------
case CCSBotManager::SCENARIO_RESCUE_HOSTAGES:
{
if (me->GetTask() == CCSBot::COLLECT_HOSTAGES)
{
//
// Since CT's have a radar, they can directly look at the actual hostage state
//
// check if someone else collected our hostage, or the hostage died or was rescued
CHostage *hostage = static_cast<CHostage *>( me->GetGoalEntity() );
if (hostage == NULL || !hostage->IsValid() || hostage->IsFollowingSomeone())
{
me->Idle();
return;
}
Vector hostageOrigin = GetCentroid( hostage );
// if our hostage has moved, repath
const float repathToleranceSq = 75.0f * 75.0f;
float error = (hostageOrigin - m_goalPosition).LengthSqr();
if (error > repathToleranceSq)
{
m_goalPosition = hostageOrigin;
me->ComputePath( m_goalPosition, SAFEST_ROUTE );
}
/// @todo Generalize ladder priorities over other tasks
if (!me->IsUsingLadder())
{
Vector pos = hostage->EyePosition();
Vector to = pos - me->EyePosition(); // "Use" checks from eye position, so we should too
// look at the hostage as we approach
const float watchHostageRange = 100.0f;
if (to.IsLengthLessThan( watchHostageRange ))
{
me->SetLookAt( "Hostage", pos, PRIORITY_LOW, 0.5f );
// randomly move just a bit to avoid infinite use loops from bad hostage placement
NavRelativeDirType dir = (NavRelativeDirType)RandomInt( 0, 3 );
switch( dir )
{
case LEFT: me->StrafeLeft(); break;
case RIGHT: me->StrafeRight(); break;
case FORWARD: me->MoveForward(); break;
case BACKWARD: me->MoveBackward(); break;
}
// check if we are close enough to the hostage to talk to him
const float useRange = PLAYER_USE_RADIUS - 10.0f; // shave off a fudge factor to make sure we're within range
if (to.IsLengthLessThan( useRange ))
{
me->UseEntity( me->GetGoalEntity() );
return;
}
}
}
}
else if (me->GetTask() == CCSBot::RESCUE_HOSTAGES)
{
// periodically check if we lost all our hostages
if (me->GetHostageEscortCount() == 0)
{
// lost our hostages - go get 'em
me->Idle();
return;
}
}
break;
}
}
if (me->UpdatePathMovement() != CCSBot::PROGRESSING)
{
// reached destination
switch( me->GetTask() )
{
case CCSBot::PLANT_BOMB:
// if we are at bombsite with the bomb, plant it
if (me->IsAtBombsite() && me->HasC4())
{
me->PlantBomb();
return;
}
break;
case CCSBot::MOVE_TO_LAST_KNOWN_ENEMY_POSITION:
{
CBasePlayer *victim = static_cast<CBasePlayer *>( me->GetTaskEntity() );
if (victim && victim->IsAlive())
{
// if we got here and haven't re-acquired the enemy, we lost him
BotStatement *say = new BotStatement( me->GetChatter(), REPORT_ENEMY_LOST, 8.0f );
say->AppendPhrase( TheBotPhrases->GetPhrase( "LostEnemy" ) );
say->SetStartTime( gpGlobals->curtime + RandomFloat( 3.0f, 5.0f ) );
me->GetChatter()->AddStatement( say );
}
break;
}
}
// default behavior when destination is reached
me->Idle();
return;
}
}
//--------------------------------------------------------------------------------------------------------------
void MoveToState::OnExit( CCSBot *me )
{
// reset to run in case we were walking near our goal position
me->Run();
me->SetDisposition( CCSBot::ENGAGE_AND_INVESTIGATE );
//me->StopAiming();
}
@@ -0,0 +1,94 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
// Author: Michael S. Booth (mike@turtlerockstudios.com), April 2005
#include "cbase.h"
#include "cs_bot.h"
#include "BasePropDoor.h"
#include "doors.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//-------------------------------------------------------------------------------------------------
/**
* Face the door and open it.
* NOTE: This state assumes we are standing in range of the door to be opened, with no obstructions.
*/
void OpenDoorState::OnEnter( CCSBot *me )
{
m_isDone = false;
m_timeout.Start( 1.0f );
}
//-------------------------------------------------------------------------------------------------
void OpenDoorState::SetDoor( CBaseEntity *door )
{
CBaseDoor *funcDoor = dynamic_cast< CBaseDoor * >(door);
if ( funcDoor )
{
m_funcDoor = funcDoor;
return;
}
CBasePropDoor *propDoor = dynamic_cast< CBasePropDoor * >(door);
if ( propDoor )
{
m_propDoor = propDoor;
return;
}
}
//-------------------------------------------------------------------------------------------------
void OpenDoorState::OnUpdate( CCSBot *me )
{
me->ResetStuckMonitor();
// wait for door to swing open before leaving state
if (m_timeout.IsElapsed())
{
m_isDone = true;
return;
}
// look at the door
Vector pos;
bool isDoorMoving = false;
if ( m_funcDoor )
{
pos = m_funcDoor->WorldSpaceCenter();
isDoorMoving = m_funcDoor->m_toggle_state == TS_GOING_UP || m_funcDoor->m_toggle_state == TS_GOING_DOWN;
}
else
{
pos = m_propDoor->WorldSpaceCenter();
isDoorMoving = m_propDoor->IsDoorOpening() || m_propDoor->IsDoorClosing();
}
me->SetLookAt( "Open door", pos, PRIORITY_HIGH );
// if we are looking at the door, "use" it and exit
if (me->IsLookingAtPosition( pos ))
{
me->UseEnvironment();
}
}
//-------------------------------------------------------------------------------------------------
void OpenDoorState::OnExit( CCSBot *me )
{
me->ClearLookAt();
me->ResetStuckMonitor();
}
@@ -0,0 +1,79 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
#include "cbase.h"
#include "cs_bot.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//--------------------------------------------------------------------------------------------------------------
/**
* Plant the bomb.
*/
void PlantBombState::OnEnter( CCSBot *me )
{
me->Crouch();
me->SetDisposition( CCSBot::SELF_DEFENSE );
// look at the floor
// Vector down( myOrigin.x, myOrigin.y, -1000.0f );
float yaw = me->EyeAngles().y;
Vector2D dir( BotCOS(yaw), BotSIN(yaw) );
Vector myOrigin = GetCentroid( me );
Vector down( myOrigin.x + 10.0f * dir.x, myOrigin.y + 10.0f * dir.y, me->GetFeetZ() );
me->SetLookAt( "Plant bomb on floor", down, PRIORITY_HIGH );
}
//--------------------------------------------------------------------------------------------------------------
/**
* Plant the bomb.
*/
void PlantBombState::OnUpdate( CCSBot *me )
{
CBaseCombatWeapon *gun = me->GetActiveWeapon();
bool holdingC4 = false;
if (gun)
{
if (FStrEq( gun->GetClassname(), "weapon_c4" ))
holdingC4 = true;
}
// if we aren't holding the C4, grab it, otherwise plant it
if (holdingC4)
me->PrimaryAttack();
else
me->SelectItem( "weapon_c4" );
// if we no longer have the C4, we've successfully planted
if (!me->HasC4())
{
// move to a hiding spot and watch the bomb
me->SetTask( CCSBot::GUARD_TICKING_BOMB );
me->Hide();
}
// if we time out, it's because we slipped into a non-plantable area
const float timeout = 5.0f;
if (gpGlobals->curtime - me->GetStateTimestamp() > timeout)
me->Idle();
}
//--------------------------------------------------------------------------------------------------------------
void PlantBombState::OnExit( CCSBot *me )
{
// equip our rifle (in case we were interrupted while holding C4)
me->EquipBestWeapon();
me->StandUp();
me->ResetStuckMonitor();
me->SetDisposition( CCSBot::ENGAGE_AND_INVESTIGATE );
me->ClearLookAt();
}
@@ -0,0 +1,63 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
#include "cbase.h"
#include "cs_bot.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
/**
* Face the entity and "use" it
* NOTE: This state assumes we are standing in range of the entity to be used, with no obstructions.
*/
void UseEntityState::OnEnter( CCSBot *me )
{
}
void UseEntityState::OnUpdate( CCSBot *me )
{
// in the very rare situation where two or more bots "used" a hostage at the same time,
// one bot will fail and needs to time out of this state
const float useTimeout = 5.0f;
if (me->GetStateTimestamp() - gpGlobals->curtime > useTimeout)
{
me->Idle();
return;
}
// look at the entity
Vector pos = m_entity->EyePosition();
me->SetLookAt( "Use entity", pos, PRIORITY_HIGH );
// if we are looking at the entity, "use" it and exit
if (me->IsLookingAtPosition( pos ))
{
if (TheCSBots()->GetScenario() == CCSBotManager::SCENARIO_RESCUE_HOSTAGES &&
me->GetTeamNumber() == TEAM_CT &&
me->GetTask() == CCSBot::COLLECT_HOSTAGES)
{
// we are collecting a hostage, assume we were successful - the update check will correct us if we weren't
me->IncreaseHostageEscortCount();
}
me->UseEnvironment();
me->Idle();
}
}
void UseEntityState::OnExit( CCSBot *me )
{
me->ClearLookAt();
me->ResetStuckMonitor();
}
+49
View File
@@ -0,0 +1,49 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Data for Autobuy and Rebuy
//
//=============================================================================//
#include "cbase.h"
#include "cs_autobuy.h"
// Weapon class information for each weapon including the class name and the buy command alias.
AutoBuyInfoStruct g_autoBuyInfo[] =
{
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_RIFLE), "galil", "weapon_galil" }, // galil
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_RIFLE), "ak47", "weapon_ak47" }, // ak47
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_SNIPERRIFLE), "scout", "weapon_scout" }, // scout
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_RIFLE), "sg552", "weapon_sg552" }, // sg552
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_SNIPERRIFLE), "awp", "weapon_awp" }, // awp
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_SNIPERRIFLE), "g3sg1", "weapon_g3sg1" }, // g3sg1
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_RIFLE), "famas", "weapon_famas" }, // famas
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_RIFLE), "m4a1", "weapon_m4a1" }, // m4a1
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_RIFLE), "aug", "weapon_aug" }, // aug
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_SNIPERRIFLE), "sg550", "weapon_sg550" }, // sg550
{ (AutoBuyClassType)(AUTOBUYCLASS_SECONDARY | AUTOBUYCLASS_PISTOL), "glock", "weapon_glock" }, // glock
{ (AutoBuyClassType)(AUTOBUYCLASS_SECONDARY | AUTOBUYCLASS_PISTOL), "usp", "weapon_usp" }, // usp
{ (AutoBuyClassType)(AUTOBUYCLASS_SECONDARY | AUTOBUYCLASS_PISTOL), "p228", "weapon_p228" }, // p228
{ (AutoBuyClassType)(AUTOBUYCLASS_SECONDARY | AUTOBUYCLASS_PISTOL), "deagle", "weapon_deagle" }, // deagle
{ (AutoBuyClassType)(AUTOBUYCLASS_SECONDARY | AUTOBUYCLASS_PISTOL), "elite", "weapon_elite" }, // elites
{ (AutoBuyClassType)(AUTOBUYCLASS_SECONDARY | AUTOBUYCLASS_PISTOL), "fn57", "weapon_fiveseven" }, // fn57
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_SHOTGUN), "m3", "weapon_m3" }, // m3
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_SHOTGUN), "xm1014", "weapon_xm1014" }, // xm1014
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_SMG), "mac10", "weapon_mac10" }, // mac10
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_SMG), "tmp", "weapon_tmp" }, // tmp
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_SMG), "mp5navy", "weapon_mp5navy" }, // mp5navy
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_SMG), "ump45", "weapon_ump45" }, // ump45
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_SMG), "p90", "weapon_p90" }, // p90
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_MACHINEGUN), "m249", "weapon_m249" }, // m249
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_AMMO), "primammo", "primammo" }, // primammo
{ (AutoBuyClassType)(AUTOBUYCLASS_SECONDARY | AUTOBUYCLASS_AMMO), "secammo", "secammo" }, // secmmo
{ (AutoBuyClassType)(AUTOBUYCLASS_ARMOR), "vest", "item_kevlar" }, // vest
{ (AutoBuyClassType)(AUTOBUYCLASS_ARMOR), "vesthelm", "item_assaultsuit" }, // vesthelm
{ (AutoBuyClassType)(AUTOBUYCLASS_GRENADE), "flashbang", "weapon_flashbang" }, // flash
{ (AutoBuyClassType)(AUTOBUYCLASS_GRENADE), "hegrenade", "weapon_hegrenade" }, // hegren
{ (AutoBuyClassType)(AUTOBUYCLASS_GRENADE), "smokegrenade", "weapon_smokegrenade" }, // sgren
{ (AutoBuyClassType)(AUTOBUYCLASS_NIGHTVISION), "nvgs", "nvgs" }, // nvgs
{ (AutoBuyClassType)(AUTOBUYCLASS_DEFUSER), "defuser", "defuser" }, // defuser
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_SHIELD), "shield", "shield" }, // shield
{ (AutoBuyClassType)0, "", "" } // last one, must be at end.
};
+53
View File
@@ -0,0 +1,53 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Headers and defines for Autobuy and Rebuy
//
//=============================================================================//
/**
* Weapon classes as used by the AutoBuy
* Has to be different that the previous ones because these are bitmasked values as a weapon can be from
* more than one class. This also includes all the classes of equipment that a player can buy.
*/
enum AutoBuyClassType
{
AUTOBUYCLASS_PRIMARY = 1,
AUTOBUYCLASS_SECONDARY = 2,
AUTOBUYCLASS_AMMO = 4,
AUTOBUYCLASS_ARMOR = 8,
AUTOBUYCLASS_DEFUSER = 16,
AUTOBUYCLASS_PISTOL = 32,
AUTOBUYCLASS_SMG = 64,
AUTOBUYCLASS_RIFLE = 128,
AUTOBUYCLASS_SNIPERRIFLE = 256,
AUTOBUYCLASS_SHOTGUN = 512,
AUTOBUYCLASS_MACHINEGUN = 1024,
AUTOBUYCLASS_GRENADE = 2048,
AUTOBUYCLASS_NIGHTVISION = 4096,
AUTOBUYCLASS_SHIELD = 8192,
};
struct AutoBuyInfoStruct
{
AutoBuyClassType m_class;
const char *m_command;
const char *m_classname;
};
struct RebuyStruct
{
char m_szPrimaryWeapon[64]; //"weapon_" string of the primary weapon
char m_szSecondaryWeapon[64]; //"weapon_" string of the secondary weapon
int m_primaryAmmo; // number of rounds the player had (not including rounds in the gun)
int m_secondaryAmmo; // number of rounds the player had (not including rounds in the gun)
int m_heGrenade; // number of grenades to buy
int m_flashbang; // number of grenades to buy
int m_smokeGrenade; // number of grenades to buy
int m_armor; // 0, 1, or 2 (0 = none, 1 = vest, 2 = vest + helmet)
bool m_defuser; // do we want a defuser
bool m_nightVision; // do we want night vision
};
extern AutoBuyInfoStruct g_autoBuyInfo[];
+554
View File
@@ -0,0 +1,554 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Basic BOT handling.
//
// $Workfile: $
// $Date: $
//
//-----------------------------------------------------------------------------
// $Log: $
//
// $NoKeywords: $
//=============================================================================//
#include "cbase.h"
#include "cs_player.h"
#include "in_buttons.h"
#include "movehelper_server.h"
#include "team.h"
#include "cs_gamerules.h"
#include "client.h"
void Bot_Think( CCSPlayer *pBot );
ConVar bot_forcefireweapon( "bot_forcefireweapon", "", 0, "Force bots with the specified weapon to fire." );
ConVar bot_forceattack2( "bot_forceattack2", "0", 0, "When firing, use attack2." );
ConVar bot_forceattackon( "bot_forceattackon", "0", 0, "When firing, don't tap fire, hold it down." );
ConVar bot_flipout( "bot_flipout", "0", 0, "When on, all bots fire their guns." );
ConVar bot_mimic( "bot_mimic", "0", 0, "Bot uses usercmd of player by index." );
static ConVar bot_mimic_yaw_offset( "bot_mimic_yaw_offset", "0", 0, "Offsets the bot yaw." );
static int BotNumber = 1;
static int g_iNextBotTeam = -1;
typedef struct
{
bool backwards;
float nextturntime;
bool lastturntoright;
float nextstrafetime;
float sidemove;
QAngle forwardAngle;
QAngle lastAngles;
int m_WantedTeam;
float m_flJoinTeamTime;
bool m_bTempBot; // Is this slot a dump temp bot or a real bot?
} botdata_t;
static botdata_t g_BotData[ MAX_PLAYERS ];
//-----------------------------------------------------------------------------
// Purpose: Create a new Bot and put it in the game.
// Output : Pointer to the new Bot, or NULL if there's no free clients.
//-----------------------------------------------------------------------------
CBasePlayer *BotPutInServer( bool bFrozen, int iTeam )
{
g_iNextBotTeam = iTeam;
char botname[ 64 ];
Q_snprintf( botname, sizeof( botname ), "Bot%02i", BotNumber );
edict_t *pEdict = engine->CreateFakeClient( botname );
if (!pEdict)
{
Msg( "Failed to create Bot.\n");
return NULL;
}
// Allocate a CBasePlayer for the bot, and call spawn
//ClientPutInServer( pEdict, botname );
//ClientActive( pEdict, false );
CCSPlayer *pPlayer = ((CCSPlayer *)CBaseEntity::Instance( pEdict ));
pPlayer->ClearFlags();
pPlayer->AddFlag( FL_CLIENT | FL_FAKECLIENT );
if ( bFrozen )
pPlayer->AddEFlags( EFL_BOT_FROZEN );
if ( iTeam == -1 )
iTeam = ( pPlayer->entindex() & 1 ) ? TEAM_TERRORIST : TEAM_CT;
botdata_t *pData = &g_BotData[pPlayer->entindex()-1];
pData->m_WantedTeam = iTeam;
pData->m_flJoinTeamTime = gpGlobals->curtime + 0.3;
pData->m_bTempBot = true;
BotNumber++;
return pPlayer;
}
bool IsTempBot( CBaseEntity *pEnt )
{
if ( !pEnt )
return false;
if ( !(pEnt->GetFlags() & FL_FAKECLIENT) )
return false;
int i = pEnt->entindex();
if ( i >= 1 && i < MAX_PLAYERS )
return g_BotData[i-1].m_bTempBot;
else
return false;
}
//-----------------------------------------------------------------------------
// Purpose: Run through all the Bots in the game and let them think.
//-----------------------------------------------------------------------------
void Bot_RunAll( void )
{
for ( int i = 1; i <= gpGlobals->maxClients; i++ )
{
CCSPlayer *pPlayer = ToCSPlayer( UTIL_PlayerByIndex( i ) );
if ( IsTempBot( pPlayer ) )
{
Bot_Think( pPlayer );
}
}
}
bool RunMimicCommand( CUserCmd& cmd )
{
if ( bot_mimic.GetInt() <= 0 )
return false;
if ( bot_mimic.GetInt() > gpGlobals->maxClients )
return false;
CBasePlayer *pPlayer = UTIL_PlayerByIndex( bot_mimic.GetInt() );
if ( !pPlayer )
return false;
if ( !pPlayer->GetLastUserCommand() )
return false;
cmd = *pPlayer->GetLastUserCommand();
cmd.viewangles[YAW] += bot_mimic_yaw_offset.GetFloat();
return true;
}
//-----------------------------------------------------------------------------
// Purpose: Simulates a single frame of movement for a player
// Input : *fakeclient -
// *viewangles -
// forwardmove -
// sidemove -
// upmove -
// buttons -
// impulse -
// msec -
// Output : virtual void
//-----------------------------------------------------------------------------
static void RunPlayerMove( CCSPlayer *fakeclient, const QAngle& viewangles, float forwardmove, float sidemove, float upmove, unsigned short buttons, byte impulse, float frametime )
{
if ( !fakeclient )
return;
CUserCmd cmd;
// Store off the globals.. they're gonna get whacked
float flOldFrametime = gpGlobals->frametime;
float flOldCurtime = gpGlobals->curtime;
float flTimeBase = gpGlobals->curtime;
fakeclient->SetTimeBase( flTimeBase );
Q_memset( &cmd, 0, sizeof( cmd ) );
if ( !RunMimicCommand( cmd ) )
{
VectorCopy( viewangles, cmd.viewangles );
cmd.forwardmove = forwardmove;
cmd.sidemove = sidemove;
cmd.upmove = upmove;
cmd.buttons = buttons;
cmd.impulse = impulse;
cmd.random_seed = random->RandomInt( 0, 0x7fffffff );
}
MoveHelperServer()->SetHost( fakeclient );
fakeclient->PlayerRunCommand( &cmd, MoveHelperServer() );
// save off the last good usercmd
fakeclient->SetLastUserCommand( cmd );
// Clear out any fixangle that has been set
fakeclient->pl.fixangle = FIXANGLE_NONE;
// Restore the globals..
gpGlobals->frametime = flOldFrametime;
gpGlobals->curtime = flOldCurtime;
}
//-----------------------------------------------------------------------------
// Purpose: Run this Bot's AI for one frame.
//-----------------------------------------------------------------------------
void Bot_Think( CCSPlayer *pBot )
{
// Make sure we stay being a bot
pBot->AddFlag( FL_FAKECLIENT );
botdata_t *botdata = &g_BotData[ pBot->entindex() - 1 ];
float forwardmove = 0.0;
float sidemove = botdata->sidemove;
float upmove = 0.0;
unsigned short buttons = 0;
byte impulse = 0;
float frametime = gpGlobals->frametime;
if ( pBot->GetTeamNumber() == TEAM_UNASSIGNED && gpGlobals->curtime > botdata->m_flJoinTeamTime )
{
pBot->HandleCommand_JoinTeam( botdata->m_WantedTeam );
}
else if ( pBot->GetTeamNumber() != TEAM_UNASSIGNED && pBot->PlayerClass() == CS_CLASS_NONE )
{
// If they're on a team but haven't picked a class, choose a random class..
pBot->HandleCommand_JoinClass( 0 );
}
else
{
QAngle vecViewAngles;
vecViewAngles = pBot->GetLocalAngles();
// Create some random values
if ( pBot->IsAlive() && (pBot->GetSolid() == SOLID_BBOX) )
{
trace_t trace;
// Stop when shot
if ( !pBot->IsEFlagSet(EFL_BOT_FROZEN) )
{
if ( pBot->m_iHealth == 100 )
{
forwardmove = 600 * ( botdata->backwards ? -1 : 1 );
if ( botdata->sidemove != 0.0f )
{
forwardmove *= random->RandomFloat( 0.1, 1.0f );
}
}
else
{
forwardmove = 0;
}
}
// Only turn if I haven't been hurt
if ( !pBot->IsEFlagSet(EFL_BOT_FROZEN) && pBot->m_iHealth == 100 )
{
Vector vecEnd;
Vector forward;
QAngle angle;
float angledelta = 15.0;
int maxtries = (int)360.0/angledelta;
if ( botdata->lastturntoright )
{
angledelta = -angledelta;
}
angle = pBot->GetLocalAngles();
Vector vecSrc;
while ( --maxtries >= 0 )
{
AngleVectors( angle, &forward, NULL, NULL );
vecSrc = pBot->GetLocalOrigin() + Vector( 0, 0, 36 );
vecEnd = vecSrc + forward * 10;
UTIL_TraceHull( vecSrc, vecEnd, VEC_HULL_MIN_SCALED( pBot ), VEC_HULL_MAX_SCALED( pBot ),
MASK_PLAYERSOLID, pBot, COLLISION_GROUP_NONE, &trace );
if ( trace.fraction == 1.0 )
{
//if ( gpGlobals->curtime < botdata->nextturntime )
//{
break;
//}
}
angle.y += angledelta;
if ( angle.y > 180 )
angle.y -= 360;
else if ( angle.y < -180 )
angle.y += 360;
botdata->nextturntime = gpGlobals->curtime + 2.0;
botdata->lastturntoright = random->RandomInt( 0, 1 ) == 0 ? true : false;
botdata->forwardAngle = angle;
botdata->lastAngles = angle;
}
/*
if ( gpGlobals->curtime >= botdata->nextstrafetime )
{
botdata->nextstrafetime = gpGlobals->curtime + 1.0f;
if ( random->RandomInt( 0, 5 ) == 0 )
{
botdata->sidemove = -600.0f + 1200.0f * random->RandomFloat( 0, 2 );
}
else
{
botdata->sidemove = 0;
}
sidemove = botdata->sidemove;
if ( random->RandomInt( 0, 20 ) == 0 )
{
botdata->backwards = true;
}
else
{
botdata->backwards = false;
}
}
*/
pBot->SetLocalAngles( angle );
vecViewAngles = angle;
}
// If bots are being forced to fire a weapon, see if I have it
else if ( bot_forcefireweapon.GetString() )
{
CBaseCombatWeapon *pWeapon = pBot->Weapon_OwnsThisType( bot_forcefireweapon.GetString() );
if ( pWeapon )
{
// Switch to it if we don't have it out
CBaseCombatWeapon *pActiveWeapon = pBot->GetActiveWeapon();
// Switch?
if ( pActiveWeapon != pWeapon )
{
pBot->Weapon_Switch( pWeapon );
}
else
{
// Start firing
// Some weapons require releases, so randomise firing
if ( bot_forceattackon.GetBool() || (RandomFloat(0.0,1.0) > 0.5) )
{
buttons |= bot_forceattack2.GetBool() ? IN_ATTACK2 : IN_ATTACK;
}
}
}
}
if ( bot_flipout.GetInt() )
{
if ( bot_forceattackon.GetBool() || (RandomFloat(0.0,1.0) > 0.5) )
{
buttons |= bot_forceattack2.GetBool() ? IN_ATTACK2 : IN_ATTACK;
}
}
}
else
{
// Wait for Reinforcement wave
if ( !pBot->IsAlive() )
{
// Try hitting my buttons occasionally
if ( random->RandomInt( 0, 100 ) > 80 )
{
// Respawn the bot
if ( random->RandomInt( 0, 1 ) == 0 )
{
buttons |= IN_JUMP;
}
else
{
buttons = 0;
}
}
}
}
if ( bot_flipout.GetInt() >= 2 )
{
QAngle angOffset = RandomAngle( -1, 1 );
botdata->lastAngles += angOffset;
for ( int i = 0 ; i < 2; i++ )
{
if ( fabs( botdata->lastAngles[ i ] - botdata->forwardAngle[ i ] ) > 15.0f )
{
if ( botdata->lastAngles[ i ] > botdata->forwardAngle[ i ] )
{
botdata->lastAngles[ i ] = botdata->forwardAngle[ i ] + 15;
}
else
{
botdata->lastAngles[ i ] = botdata->forwardAngle[ i ] - 15;
}
}
}
botdata->lastAngles[ 2 ] = 0;
pBot->SetLocalAngles( botdata->lastAngles );
}
}
pBot->SetPunchAngle( QAngle( 0, 0, 0 ) );
RunPlayerMove( pBot, pBot->GetLocalAngles(), forwardmove, sidemove, upmove, buttons, impulse, frametime );
}
// Handler for the "bot" command.
CON_COMMAND_F( "bot_old", "Add a bot.", FCVAR_CHEAT )
{
// Disable the CS bots, otherwise they'll interfere with the bot code here.
//extern bool g_bEnableCSBots;
//g_bEnableCSBots = false;
CCSPlayer *pPlayer = CCSPlayer::Instance( UTIL_GetCommandClientIndex() );
// The bot command uses switches like command-line switches.
// -count <count> tells how many bots to spawn.
// -team <index> selects the bot's team. Default is -1 which chooses randomly.
// Note: if you do -team !, then it
// -class <index> selects the bot's class. Default is -1 which chooses randomly.
// -frozen prevents the bots from running around when they spawn in.
int count = args.FindArgInt( "-count", 1 );
count = clamp( count, 1, 16 );
int iTeam = -1;
const char *pVal = args.FindArg( "-team" );
if ( pVal )
{
if ( pVal[0] == '!' )
{
if ( pPlayer->GetTeamNumber() == TEAM_TERRORIST )
iTeam = TEAM_CT;
else
iTeam = TEAM_TERRORIST;
}
else if ( pVal[0] == 't' || pVal[0] == 'T' )
{
iTeam = TEAM_TERRORIST;
}
else if ( pVal[0] == 'c' || pVal[0] == 'C' )
{
iTeam = TEAM_CT;
}
else
{
iTeam = atoi( pVal );
if ( iTeam == 1 )
iTeam = TEAM_TERRORIST;
else
iTeam = TEAM_CT;
}
}
// Look at -frozen.
bool bFrozen = !!args.FindArg( "-frozen" );
// Ok, spawn all the bots.
while ( --count >= 0 )
{
extern CBasePlayer *BotPutInServer( bool bFrozen, int iTeam );
BotPutInServer( bFrozen, iTeam );
}
}
// Handle the "PossessBot" command.
void PossessBot_f( const CCommand &args )
{
CCSPlayer *pPlayer = CCSPlayer::Instance( UTIL_GetCommandClientIndex() );
if ( !pPlayer )
return;
// Put the local player in control of this bot.
if ( args.ArgC() != 2 )
{
Warning( "PossessBot <client index>\n" );
return;
}
int iBotClient = atoi( args[1] );
int iBotEnt = iBotClient + 1;
if ( iBotClient < 0 ||
iBotClient >= gpGlobals->maxClients ||
pPlayer->entindex() == iBotEnt )
{
Warning( "PossessBot <client index>\n" );
}
else
{
edict_t *pPlayerData = pPlayer->edict();
edict_t *pBotData = engine->PEntityOfEntIndex( iBotEnt );
if ( pBotData && pBotData )
{
// SWAP EDICTS
// Backup things we don't want to swap.
edict_t oldPlayerData = *pPlayerData;
edict_t oldBotData = *pBotData;
// Swap edicts.
edict_t tmp = *pPlayerData;
*pPlayerData = *pBotData;
*pBotData = tmp;
// Restore things we didn't want to swap.
//pPlayerData->m_EntitiesTouched = oldPlayerData.m_EntitiesTouched;
//pBotData->m_EntitiesTouched = oldBotData.m_EntitiesTouched;
CBaseEntity *pPlayerBaseEnt = CBaseEntity::Instance( pPlayerData );
CBaseEntity *pBotBaseEnt = CBaseEntity::Instance( pBotData );
// Make the other a bot and make the player not a bot.
pPlayerBaseEnt->RemoveFlag( FL_FAKECLIENT );
pBotBaseEnt->AddFlag( FL_FAKECLIENT );
// Point the CBaseEntities at the right players.
pPlayerBaseEnt->NetworkProp()->SetEdict( pPlayerData );
pBotBaseEnt->NetworkProp()->SetEdict( pBotData );
// Freeze the bot.
pBotBaseEnt->AddEFlags( EFL_BOT_FROZEN );
}
}
}
ConCommand cc_PossessBot( "PossessBot", PossessBot_f, "Toggle. Possess a bot.\n\tArguments: <bot client number>", FCVAR_CHEAT );
+17
View File
@@ -0,0 +1,17 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================//
#ifndef CS_BOT_TEMP_H
#define CS_BOT_TEMP_H
#ifdef _WIN32
#pragma once
#endif
extern ConVar bot_mimic;
#endif // CS_BOT_TEMP_H
+205
View File
@@ -0,0 +1,205 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//
//=============================================================================//
/*
===== tf_client.cpp ========================================================
HL2 client/server game specific stuff
*/
#include "cbase.h"
#include "player.h"
#include "gamerules.h"
#include "entitylist.h"
#include "physics.h"
#include "game.h"
#include "ai_network.h"
#include "ai_node.h"
#include "ai_hull.h"
#include "shake.h"
#include "player_resource.h"
#include "engine/IEngineSound.h"
#include "cs_player.h"
#include "cs_gamerules.h"
#include "cs_bot.h"
#include "tier0/vprof.h"
#include "teamplayroundbased_gamerules.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
extern CBaseEntity *FindPickerEntity( CBasePlayer *pPlayer );
extern bool g_fGameOver;
void FinishClientPutInServer( CCSPlayer *pPlayer )
{
pPlayer->InitialSpawn();
pPlayer->Spawn();
if (!pPlayer->IsBot())
{
// When the player first joins the server, they
pPlayer->m_iNumSpawns = 0;
pPlayer->m_takedamage = DAMAGE_NO;
pPlayer->pl.deadflag = true;
pPlayer->m_lifeState = LIFE_DEAD;
pPlayer->AddEffects( EF_NODRAW );
pPlayer->ChangeTeam( TEAM_UNASSIGNED );
pPlayer->SetThink( NULL );
pPlayer->AddAccount( CSGameRules()->GetStartMoney() );
// Move them to the first intro camera.
pPlayer->MoveToNextIntroCamera();
pPlayer->SetMoveType( MOVETYPE_NONE );
}
char sName[128];
Q_strncpy( sName, pPlayer->GetPlayerName(), sizeof( sName ) );
// First parse the name and remove any %'s
for ( char *pApersand = sName; pApersand != NULL && *pApersand != 0; pApersand++ )
{
// Replace it with a space
if ( *pApersand == '%' )
*pApersand = ' ';
}
// notify other clients of player joining the game
UTIL_ClientPrintAll( HUD_PRINTNOTIFY, "#Game_connected", sName[0] != 0 ? sName : "<unconnected>" );
}
/*
===========
ClientPutInServer
called each time a player is spawned into the game
============
*/
void ClientPutInServer( edict_t *pEdict, const char *playername )
{
// Allocate a CBaseTFPlayer for pev, and call spawn
CCSPlayer *pPlayer = CCSPlayer::CreatePlayer( "player", pEdict );
pPlayer->SetPlayerName( playername );
}
void ClientActive( edict_t *pEdict, bool bLoadGame )
{
// Can't load games in CS!
Assert( !bLoadGame );
CCSPlayer *pPlayer = ToCSPlayer( CBaseEntity::Instance( pEdict ) );
FinishClientPutInServer( pPlayer );
CSingleUserRecipientFilter user( pPlayer );
user.MakeReliable();
// send the 4 end of match conditions. long frag limit, long max rounds, long rounds needed won, and long time
UserMessageBegin( user, "MatchEndConditions" );
WRITE_LONG( fraglimit.GetInt() );
WRITE_LONG( mp_maxrounds.GetInt() );
WRITE_LONG( mp_winlimit.GetInt() );
WRITE_LONG( mp_timelimit.GetInt() );
MessageEnd();
}
/*
===============
const char *GetGameDescription()
Returns the descriptive name of this .dll. E.g., Half-Life, or Team Fortress 2
===============
*/
const char *GetGameDescription()
{
if ( g_pGameRules ) // this function may be called before the world has spawned, and the game rules initialized
return g_pGameRules->GetGameDescription();
else
return "Counter-Strike: Source";
}
//-----------------------------------------------------------------------------
// Purpose: Precache game-specific models & sounds
//-----------------------------------------------------------------------------
void ClientGamePrecache( void )
{
// Materials used by the client effects
CBaseEntity::PrecacheModel( "sprites/white.vmt" );
CBaseEntity::PrecacheModel( "sprites/physbeam.vmt" );
// Legacy temp ents sounds
CBaseEntity::PrecacheScriptSound( "Bounce.PistolShell" );
CBaseEntity::PrecacheScriptSound( "Bounce.RifleShell" );
CBaseEntity::PrecacheScriptSound( "Bounce.ShotgunShell" );
// Moved to pure_server_minimal.txt
// // Flashbang-related files
// engine->ForceExactFile( "sprites/white.vmt" );
// engine->ForceExactFile( "sprites/white.vtf" );
// engine->ForceExactFile( "vgui/white.vmt" );
// engine->ForceExactFile( "vgui/white.vtf" );
// engine->ForceExactFile( "effects/flashbang.vmt" );
// engine->ForceExactFile( "effects/flashbang_white.vmt" );
//
// // Smoke grenade-related files
// engine->ForceExactFile( "particle/particle_smokegrenade1.vmt" );
// engine->ForceExactFile( "particle/particle_smokegrenade.vtf" );
//
// // Sniper scope
// engine->ForceExactFile( "sprites/scope_arc.vmt" );
// engine->ForceExactFile( "sprites/scope_arc.vtf" );
//
// // DSP presets - don't want people avoiding the deafening + ear ring
// engine->ForceExactFile( "scripts/dsp_presets.txt" );
}
// called by ClientKill and DeadThink
void respawn( CBaseEntity *pEdict, bool fCopyCorpse )
{
if (gpGlobals->coop || gpGlobals->deathmatch)
{
if ( fCopyCorpse )
{
// make a copy of the dead body for appearances sake
dynamic_cast< CBasePlayer* >( pEdict )->CreateCorpse();
}
// respawn player
pEdict->Spawn();
}
else
{ // restart the entire server
engine->ServerCommand("reload\n");
}
}
void GameStartFrame( void )
{
VPROF( "GameStartFrame" );
if ( g_fGameOver )
return;
gpGlobals->teamplay = teamplay.GetInt() ? true : false;
}
//=========================================================
// instantiate the proper game rules object
//=========================================================
void InstallGameRules()
{
CreateGameRulesObject( "CCSGameRules" );
}
+19
View File
@@ -0,0 +1,19 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================//
#ifndef CS_CLIENT_H
#define CS_CLIENT_H
#ifdef _WIN32
#pragma once
#endif
void respawn( CBaseEntity *pEdict, bool fCopyCorpse );
void FinishClientPutInServer( CCSPlayer *pPlayer );
#endif // CS_CLIENT_H
+321
View File
@@ -0,0 +1,321 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//
//=============================================================================//
#include "cbase.h"
#include "../EventLog.h"
#include "team.h"
#include "cs_gamerules.h"
#include "KeyValues.h"
#define LOG_DETAIL_ENEMY_ATTACKS 0x01
#define LOG_DETAIL_TEAMMATE_ATTACKS 0x02
ConVar mp_logdetail( "mp_logdetail", "0", FCVAR_NONE, "Logs attacks. Values are: 0=off, 1=enemy, 2=teammate, 3=both)", true, 0.0f, true, 3.0f );
class CCSEventLog : public CEventLog
{
private:
typedef CEventLog BaseClass;
public:
bool PrintEvent( IGameEvent *event ) // override virtual function
{
if ( !PrintCStrikeEvent( event ) ) // allow CS to override logging
{
return BaseClass::PrintEvent( event );
}
else
{
return true;
}
}
bool Init()
{
BaseClass::Init();
// listen to CS events
ListenForGameEvent( "round_end" );
ListenForGameEvent( "round_start" );
ListenForGameEvent( "bomb_pickup" );
ListenForGameEvent( "bomb_begindefuse" );
ListenForGameEvent( "bomb_dropped" );
ListenForGameEvent( "bomb_defused" );
ListenForGameEvent( "bomb_planted" );
ListenForGameEvent( "hostage_rescued" );
ListenForGameEvent( "hostage_killed" );
ListenForGameEvent( "hostage_follows" );
ListenForGameEvent( "player_hurt" );
return true;
}
protected:
bool PrintCStrikeEvent( IGameEvent *event ) // print Mod specific logs
{
const char *eventName = event->GetName();
// messages that don't have a user associated to them
if ( !Q_strncmp( eventName, "round_end", Q_strlen("round_end") ) )
{
const int winner = event->GetInt( "winner" );
const int reason = event->GetInt( "reason" );
const char *msg = event->GetString( "message" );
msg++; // remove the '#' char
switch( reason )
{
case Game_Commencing:
UTIL_LogPrintf( "World triggered \"Game_Commencing\"\n" );
return true;
break;
}
CTeam *ct = GetGlobalTeam( TEAM_CT );
CTeam *ter = GetGlobalTeam( TEAM_TERRORIST );
Assert( ct && ter );
switch ( winner )
{
case WINNER_CT:
UTIL_LogPrintf( "Team \"%s\" triggered \"%s\" (CT \"%i\") (T \"%i\")\n", ct->GetName(), msg, ct->GetScore(), ter->GetScore() );
break;
case WINNER_TER:
UTIL_LogPrintf( "Team \"%s\" triggered \"%s\" (CT \"%i\") (T \"%i\")\n", ter->GetName(), msg, ct->GetScore(), ter->GetScore() );
break;
case WINNER_DRAW:
default:
UTIL_LogPrintf( "World triggered \"%s\" (CT \"%i\") (T \"%i\")\n", msg, ct->GetScore(), ter->GetScore() );
break;
}
UTIL_LogPrintf( "Team \"CT\" scored \"%i\" with \"%i\" players\n", ct->GetScore(), ct->GetNumPlayers() );
UTIL_LogPrintf( "Team \"TERRORIST\" scored \"%i\" with \"%i\" players\n", ter->GetScore(), ter->GetNumPlayers() );
UTIL_LogPrintf("World triggered \"Round_End\"\n");
return true;
}
else if ( !Q_strncmp( eventName, "server_", strlen("server_")) )
{
return false; // ignore server_ messages
}
const int userid = event->GetInt( "userid" );
CBasePlayer *pPlayer = UTIL_PlayerByUserId( userid );
if ( !pPlayer )
{
return false;
}
if ( FStrEq( eventName, "player_hurt" ) )
{
const int attackerid = event->GetInt("attacker" );
const char *weapon = event->GetString( "weapon" );
CBasePlayer *pAttacker = UTIL_PlayerByUserId( attackerid );
if ( !pAttacker )
{
return false;
}
bool isTeamAttack = ( (pPlayer->GetTeamNumber() == pAttacker->GetTeamNumber() ) && (pPlayer != pAttacker) );
int detail = mp_logdetail.GetInt();
if ( ( isTeamAttack && ( detail & LOG_DETAIL_TEAMMATE_ATTACKS ) ) ||
( !isTeamAttack && ( detail & LOG_DETAIL_ENEMY_ATTACKS ) ) )
{
int hitgroup = event->GetInt( "hitgroup" );
const char *hitgroupStr = "GENERIC";
switch ( hitgroup )
{
case HITGROUP_GENERIC:
hitgroupStr = "generic";
break;
case HITGROUP_HEAD:
hitgroupStr = "head";
break;
case HITGROUP_CHEST:
hitgroupStr = "chest";
break;
case HITGROUP_STOMACH:
hitgroupStr = "stomach";
break;
case HITGROUP_LEFTARM:
hitgroupStr = "left arm";
break;
case HITGROUP_RIGHTARM:
hitgroupStr = "right arm";
break;
case HITGROUP_LEFTLEG:
hitgroupStr = "left leg";
break;
case HITGROUP_RIGHTLEG:
hitgroupStr = "right leg";
break;
}
UTIL_LogPrintf( "\"%s<%i><%s><%s>\" attacked \"%s<%i><%s><%s>\" with \"%s\" (damage \"%d\") (damage_armor \"%d\") (health \"%d\") (armor \"%d\") (hitgroup \"%s\")\n",
pAttacker->GetPlayerName(),
attackerid,
pAttacker->GetNetworkIDString(),
pAttacker->GetTeam()->GetName(),
pPlayer->GetPlayerName(),
userid,
pPlayer->GetNetworkIDString(),
pPlayer->GetTeam()->GetName(),
weapon,
event->GetInt( "dmg_health" ),
event->GetInt( "dmg_armor" ),
event->GetInt( "health" ),
event->GetInt( "armor" ),
hitgroupStr );
}
return true;
}
else if ( !Q_strncmp( eventName, "player_death", Q_strlen("player_death") ) )
{
const int attackerid = event->GetInt("attacker" );
const char *weapon = event->GetString( "weapon" );
const bool headShot = (event->GetInt( "headshot" ) == 1);
CBasePlayer *pAttacker = UTIL_PlayerByUserId( attackerid );
if ( pPlayer == pAttacker )
{
UTIL_LogPrintf( "\"%s<%i><%s><%s>\" committed suicide with \"%s\"\n",
pPlayer->GetPlayerName(),
userid,
pPlayer->GetNetworkIDString(),
pPlayer->GetTeam()->GetName(),
weapon
);
}
else if ( pAttacker )
{
UTIL_LogPrintf( "\"%s<%i><%s><%s>\" killed \"%s<%i><%s><%s>\" with \"%s\"%s\n",
pAttacker->GetPlayerName(),
attackerid,
pAttacker->GetNetworkIDString(),
pAttacker->GetTeam()->GetName(),
pPlayer->GetPlayerName(),
userid,
pPlayer->GetNetworkIDString(),
pPlayer->GetTeam()->GetName(),
weapon,
headShot ? " (headshot)":""
);
}
else
{
// killed by the world
UTIL_LogPrintf( "\"%s<%i><%s><%s>\" committed suicide with \"world\"\n",
pPlayer->GetPlayerName(),
userid,
pPlayer->GetNetworkIDString(),
pPlayer->GetTeam()->GetName()
);
}
return true;
}
else if ( !Q_strncmp( eventName, "round_start", Q_strlen("round_start") ) )
{
UTIL_LogPrintf("World triggered \"Round_Start\"\n");
return true;
}
else if ( !Q_strncmp( eventName, "hostage_follows", Q_strlen("hostage_follows") ) )
{
UTIL_LogPrintf( "\"%s<%i><%s><CT>\" triggered \"Touched_A_Hostage\"\n",
pPlayer->GetPlayerName(),
userid,
pPlayer->GetNetworkIDString()
);
return true;
}
else if ( !Q_strncmp( eventName, "hostage_killed", Q_strlen("hostage_killed") ) )
{
UTIL_LogPrintf( "\"%s<%i><%s><%s>\" triggered \"Killed_A_Hostage\"\n",
pPlayer->GetPlayerName(),
userid,
pPlayer->GetNetworkIDString(),
pPlayer->GetTeam()->GetName()
);
return true;
}
else if ( !Q_strncmp( eventName, "hostage_rescued", Q_strlen("hostage_rescued") ) )
{
UTIL_LogPrintf("\"%s<%i><%s><CT>\" triggered \"Rescued_A_Hostage\"\n",
pPlayer->GetPlayerName(),
userid,
pPlayer->GetNetworkIDString()
);
return true;
}
else if ( !Q_strncmp( eventName, "bomb_planted", Q_strlen("bomb_planted") ) )
{
UTIL_LogPrintf("\"%s<%i><%s><TERRORIST>\" triggered \"Planted_The_Bomb\"\n",
pPlayer->GetPlayerName(),
userid,
pPlayer->GetNetworkIDString()
);
return true;
}
else if ( !Q_strncmp( eventName, "bomb_defused", Q_strlen("bomb_defused") ) )
{
UTIL_LogPrintf("\"%s<%i><%s><CT>\" triggered \"Defused_The_Bomb\"\n",
pPlayer->GetPlayerName(),
userid,
pPlayer->GetNetworkIDString()
);
return true;
}
else if ( !Q_strncmp( eventName, "bomb_dropped", Q_strlen("bomb_dropped") ) )
{
UTIL_LogPrintf("\"%s<%i><%s><TERRORIST>\" triggered \"Dropped_The_Bomb\"\n",
pPlayer->GetPlayerName(),
userid,
pPlayer->GetNetworkIDString()
);
return true;
}
else if ( !Q_strncmp( eventName, "bomb_begindefuse", Q_strlen("bomb_begindefuse") ) )
{
const bool haskit = (event->GetInt( "haskit" ) == 1);
UTIL_LogPrintf("\"%s<%i><%s><CT>\" triggered \"%s\"\n",
pPlayer->GetPlayerName(),
userid,
pPlayer->GetNetworkIDString(),
haskit ? "Begin_Bomb_Defuse_With_Kit" : "Begin_Bomb_Defuse_Without_Kit"
);
return true;
}
else if ( !Q_strncmp( eventName, "bomb_pickup", Q_strlen("bomb_pickup") ) )
{
UTIL_LogPrintf("\"%s<%i><%s><TERRORIST>\" triggered \"Got_The_Bomb\"\n",
pPlayer->GetPlayerName(),
userid,
pPlayer->GetNetworkIDString()
);
return true;
}
// unused events:
//hostage_hurt
//bomb_exploded
return false;
}
};
CCSEventLog g_CSEventLog;
//-----------------------------------------------------------------------------
// Singleton access
//-----------------------------------------------------------------------------
IGameSystem* GameLogSystem()
{
return &g_CSEventLog;
}
+40
View File
@@ -0,0 +1,40 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================//
#include "cbase.h"
#include "gameinterface.h"
#include "mapentities.h"
#include "cs_gameinterface.h"
#include "AI_ResponseSystem.h"
// -------------------------------------------------------------------------------------------- //
// Mod-specific CServerGameClients implementation.
// -------------------------------------------------------------------------------------------- //
void CServerGameClients::GetPlayerLimits( int& minplayers, int& maxplayers, int &defaultMaxPlayers ) const
{
minplayers = 1; // allow single player for the test maps (but we default to multi)
maxplayers = MAX_PLAYERS;
defaultMaxPlayers = 32; // Default to 32 players unless they change it.
}
// -------------------------------------------------------------------------------------------- //
// Mod-specific CServerGameDLL implementation.
// -------------------------------------------------------------------------------------------- //
void CServerGameDLL::LevelInit_ParseAllEntities( const char *pMapEntities )
{
if ( Q_strcmp( STRING(gpGlobals->mapname), "cs_" ) )
{
// don't precache AI responses (hostages) if it's not a hostage rescure map
extern IResponseSystem *g_pResponseSystem;
g_pResponseSystem->PrecacheResponses( false );
}
}
+14
View File
@@ -0,0 +1,14 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================//
#ifndef CS_GAMEINTERFACE_H
#define CS_GAMEINTERFACE_H
#ifdef _WIN32
#pragma once
#endif
#endif // CS_GAMEINTERFACE_H
File diff suppressed because it is too large Load Diff
+340
View File
@@ -0,0 +1,340 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: The CS game stats header
//
// $NoKeywords: $
//=============================================================================//
#ifndef CS_GAMESTATS_H
#define CS_GAMESTATS_H
#ifdef _WIN32
#pragma once
#endif
#include "cs_blackmarket.h"
#include "gamestats.h"
#include "cs_gamestats_shared.h"
#include "GameEventListener.h"
#include "steamworks_gamestats.h"
#include "weapon_csbase.h"
// forward declares
class CBreakableProp;
#define CS_STATS_BLOB_VERSION 3
const float cDisseminationTimeHigh = 0.25f; // Time interval for high priority stats sent to the player
const float cDisseminationTimeLow = 2.5f; // Time interval for medium priority stats sent to the player
int GetCSLevelIndex( const char *pLevelName );
typedef struct
{
char szGameName[8];
byte iVersion;
char szMapName[32];
char ipAddr[4];
short port;
int serverid;
} gamestats_header_t;
typedef struct
{
gamestats_header_t header;
short iMinutesPlayed;
short iTerroristVictories[CS_NUM_LEVELS];
short iCounterTVictories[CS_NUM_LEVELS];
short iBlackMarketPurchases[WEAPON_MAX];
short iAutoBuyPurchases;
short iReBuyPurchases;
short iAutoBuyM4A1Purchases;
short iAutoBuyAK47Purchases;
short iAutoBuyFamasPurchases;
short iAutoBuyGalilPurchases;
short iAutoBuyVestHelmPurchases;
short iAutoBuyVestPurchases;
} cs_gamestats_t;
extern short g_iWeaponPurchases[WEAPON_MAX];
extern float g_flGameStatsUpdateTime;
extern short g_iTerroristVictories[CS_NUM_LEVELS];
extern short g_iCounterTVictories[CS_NUM_LEVELS];
extern short g_iWeaponPurchases[WEAPON_MAX];
extern short g_iAutoBuyPurchases;
extern short g_iReBuyPurchases;
extern short g_iAutoBuyM4A1Purchases;
extern short g_iAutoBuyAK47Purchases;
extern short g_iAutoBuyFamasPurchases;
extern short g_iAutoBuyGalilPurchases;
extern short g_iAutoBuyVestHelmPurchases;
extern short g_iAutoBuyVestPurchases;
struct sHappyCamperSnipePosition
{
sHappyCamperSnipePosition( int userid, Vector pos ) : m_iUserID(userid), m_vPos(pos) {}
int m_iUserID;
Vector m_vPos;
};
struct SMarketPurchases : public BaseStatData
{
SMarketPurchases( uint64 ulPlayerID, int iPrice, const char *pName ) : m_nPlayerID(ulPlayerID), ItemCost(iPrice)
{
if ( pName )
{
Q_strncpy( ItemID, pName, ARRAYSIZE(ItemID) );
}
else
{
Q_strncpy( ItemID, "unknown", ARRAYSIZE(ItemID) );
}
}
uint64 m_nPlayerID;
int ItemCost;
char ItemID[64];
BEGIN_STAT_TABLE( "CSSMarketPurchase" )
REGISTER_STAT( m_nPlayerID )
REGISTER_STAT( ItemCost )
REGISTER_STAT_STRING( ItemID )
END_STAT_TABLE()
};
typedef CUtlVector< SMarketPurchases* > VectorMarketPurchaseData;
struct WeaponStats
{
int shots;
int hits;
int kills;
int damage;
};
struct SCSSWeaponData : public BaseStatData
{
SCSSWeaponData( const char *pWeaponName, const WeaponStats &wpnData )
{
if ( pWeaponName )
{
Q_strncpy( WeaponID, pWeaponName, ARRAYSIZE(WeaponID) );
}
else
{
Q_strncpy( WeaponID, "unknown", ARRAYSIZE(WeaponID) );
}
Shots = wpnData.shots;
Hits = wpnData.hits;
Kills = wpnData.kills;
Damage = wpnData.damage;
}
char WeaponID[64];
int Shots;
int Hits;
int Kills;
int Damage;
BEGIN_STAT_TABLE( "CSSWeaponData" )
REGISTER_STAT_STRING( WeaponID )
REGISTER_STAT( Shots )
REGISTER_STAT( Hits )
REGISTER_STAT( Kills )
REGISTER_STAT( Damage )
END_STAT_TABLE()
};
typedef CUtlVector< SCSSWeaponData* > CSSWeaponData;
struct SCSSDeathData : public BaseStatData
{
SCSSDeathData( CBasePlayer *pVictim, const CTakeDamageInfo &info )
{
m_bUseGlobalData = false;
m_DeathPos = info.GetDamagePosition();
m_iVictimTeam = pVictim->GetTeamNumber();
CCSPlayer *pCSPlayer = ToCSPlayer( info.GetAttacker() );
if ( pCSPlayer )
{
m_iKillerTeam = pCSPlayer->GetTeamNumber();
}
else
{
m_iKillerTeam = -1;
}
const char *pszWeaponName = info.GetInflictor() ? info.GetInflictor()->GetClassname() : "unknown";
if ( pszWeaponName )
{
if ( V_strcmp(pszWeaponName, "player") == 0 )
{
// get the player's weapon
if ( pCSPlayer && pCSPlayer->GetActiveCSWeapon() )
{
pszWeaponName = pCSPlayer->GetActiveCSWeapon()->GetClassname();
}
}
}
m_uiDeathParam = WEAPON_NONE;
if ( (m_uiDeathParam = AliasToWeaponID( pszWeaponName )) == WEAPON_NONE )
{
m_uiDeathParam = AliasToWeaponID( pszWeaponName );
}
const char *pszMapName = gpGlobals->mapname.ToCStr() ? gpGlobals->mapname.ToCStr() : "unknown";
Q_strncpy( m_szMapName, pszMapName, ARRAYSIZE(m_szMapName) );
}
Vector m_DeathPos;
int m_iVictimTeam;
int m_iKillerTeam;
int m_iDamageType;
uint64 m_uiDeathParam;
char m_szMapName[64];
BEGIN_STAT_TABLE( "Deaths" )
REGISTER_STAT_NAMED( m_DeathPos.x, "XCoord" )
REGISTER_STAT_NAMED( m_DeathPos.y, "YCoord" )
REGISTER_STAT_NAMED( m_DeathPos.z, "ZCoord" )
REGISTER_STAT_NAMED( m_iVictimTeam, "Team" )
REGISTER_STAT_NAMED( m_iKillerTeam, "DeathCause" )
REGISTER_STAT_NAMED( m_uiDeathParam, "DeathParam" )
REGISTER_STAT_NAMED( m_szMapName, "DeathMap" )
END_STAT_TABLE()
};
typedef CUtlVector< SCSSDeathData* > CSSDeathData;
//=============================================================================
//
// CS Game Stats Class
//
class CCSGameStats : public CBaseGameStats, public CGameEventListener, public CAutoGameSystemPerFrame, public IGameStatTracker
{
public:
// Constructor/Destructor.
CCSGameStats( void );
~CCSGameStats( void );
virtual void Clear( void );
virtual bool Init();
virtual void PreClientUpdate();
virtual void PostInit();
virtual void LevelShutdownPreClearSteamAPIContext();
void UploadRoundStats( void );
// Overridden events
virtual void Event_LevelInit( void );
virtual void Event_LevelShutdown( float flElapsed );
virtual void Event_ShotFired( CBasePlayer *pPlayer, CBaseCombatWeapon* pWeapon );
virtual void Event_ShotHit( CBasePlayer *pPlayer, const CTakeDamageInfo &info );
virtual void Event_PlayerKilled( CBasePlayer *pPlayer, const CTakeDamageInfo &info );
virtual void Event_PlayerKilled_PreWeaponDrop( CBasePlayer *pPlayer, const CTakeDamageInfo &info );
void UpdatePlayerRoundStats(int winner);
virtual void Event_PlayerConnected( CBasePlayer *pPlayer );
virtual void Event_PlayerDisconnected( CBasePlayer *pPlayer );
virtual void Event_WindowShattered( CBasePlayer *pPlayer );
virtual void Event_PlayerKilledOther( CBasePlayer *pAttacker, CBaseEntity *pVictim, const CTakeDamageInfo &info );
// CSS specific events
void Event_BombPlanted( CCSPlayer *pPlayer );
void Event_BombDefused( CCSPlayer *pPlayer );
void Event_PlayerDamage( CBasePlayer *pBasePlayer, const CTakeDamageInfo &info );
void Event_BombExploded( CCSPlayer *pPlayer );
void Event_MoneyEarned( CCSPlayer *pPlayer, int moneyEarned );
void Event_MoneySpent( CCSPlayer *pPlayer, int moneySpent, const char *pItemName );
void Event_HostageRescued( CCSPlayer *pPlayer );
void Event_PlayerSprayedDecal( CCSPlayer*pPlayer );
void Event_AllHostagesRescued();
void Event_BreakProp( CCSPlayer *pPlayer, CBreakableProp *pProp );
void Event_PlayerDonatedWeapon (CCSPlayer* pPlayer);
void Event_PlayerDominatedOther( CCSPlayer* pAttacker, CCSPlayer* pVictim);
void Event_PlayerRevenge( CCSPlayer* pAttacker );
void Event_PlayerAvengedTeammate( CCSPlayer* pAttacker, CCSPlayer* pAvengedPlayer );
void Event_MVPEarned( CCSPlayer* pPlayer );
void Event_KnifeUse( CCSPlayer* pPlayer, bool bStab, int iDamage );
virtual void FireGameEvent( IGameEvent *event );
void DumpMatchWeaponMetrics();
const PlayerStats_t& FindPlayerStats( CBasePlayer *pPlayer ) const;
void ResetPlayerStats( CBasePlayer *pPlayer );
void ResetKillHistory( CBasePlayer *pPlayer );
void ResetRoundStats();
void ResetPlayerClassMatchStats();
const StatsCollection_t& GetTeamStats( int iTeamIndex ) const;
void ResetAllTeamStats();
void ResetAllStats();
void ResetWeaponStats();
void IncrementTeamStat( int iTeamIndex, int iStatIndex, int iAmount );
void CalcDominationAndRevenge( CCSPlayer *pAttacker, CCSPlayer *pVictim, int *piDeathFlags );
void CalculateOverkill( CCSPlayer* pAttacker, CCSPlayer* pVictim );
void PlayerKilled( CBasePlayer *pVictim, const CTakeDamageInfo &info );
void IncrementStat( CCSPlayer* pPlayer, CSStatType_t statId, int iValue, bool bPlayerOnly = false );
// Steamworks Gamestats
virtual void SubmitGameStats( KeyValues *pKV );
virtual StatContainerList_t* GetStatContainerList( void )
{
return s_StatLists;
}
protected:
void SetStat( CCSPlayer *pPlayer, CSStatType_t statId, int iValue );
void TrackKillStats( CCSPlayer *pAttacker, CCSPlayer *pVictim );
void ComputeRollingStatAverages();
void ComputeDirectStatAverages();
void SendRollingStatsAveragesToAllPlayers();
void SendDirectStatsAveragesToAllPlayers();
void SendStatsToPlayer( CCSPlayer * pPlayer, int iMinStatPriority );
private:
PlayerStats_t m_aPlayerStats[MAX_PLAYERS+1]; // List of stats for each player for current life - reset after each death
StatsCollection_t m_aTeamStats[TEAM_MAXCOUNT - FIRST_GAME_TEAM];
RoundStatsRollingAverage_t m_rollingCTStatAverages;
RoundStatsRollingAverage_t m_rollingTStatAverages;
RoundStatsRollingAverage_t m_rollingPlayerStatAverages;
RoundStatsDirectAverage_t m_directCTStatAverages;
RoundStatsDirectAverage_t m_directTStatAverages;
RoundStatsDirectAverage_t m_directPlayerStatAverages;
float m_fDisseminationTimerLow; // how long since last medium priority stat update
float m_fDisseminationTimerHigh; // how long since last high priority stat update
int m_numberOfRoundsForDirectAverages;
int m_numberOfTerroristEntriesForDirectAverages;
int m_numberOfCounterTerroristEntriesForDirectAverages;
CUtlDict< CSStatType_t, short > m_PropStatTable;
CUtlLinkedList<sHappyCamperSnipePosition, int> m_PlayerSnipedPosition;
WeaponStats m_weaponStats[WEAPON_MAX][2];
// Steamworks Gamestats
VectorMarketPurchaseData m_MarketPurchases;
CSSWeaponData m_WeaponData;
CSSDeathData m_DeathData;
// A static list of all the stat containers, one for each data structure being tracked
static StatContainerList_t * s_StatLists;
bool m_bInRound;
};
extern CCSGameStats CCS_GameStats;
#endif // CS_GAMESTATS_H
+149
View File
@@ -0,0 +1,149 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//
//=============================================================================//
#include "cbase.h"
#include "hltvdirector.h"
#include "igameevents.h"
class CCSHLTVDirector : public CHLTVDirector
{
public:
DECLARE_CLASS( CCSHLTVDirector, CHLTVDirector );
const char** GetModEvents();
void SetHLTVServer( IHLTVServer *hltv );
void CreateShotFromEvent( CHLTVGameEvent *event );
};
void CCSHLTVDirector::SetHLTVServer( IHLTVServer *hltv )
{
BaseClass::SetHLTVServer( hltv );
if ( m_pHLTVServer )
{
// mod specific events the director uses to find interesting shots
ListenForGameEvent( "hostage_rescued" );
ListenForGameEvent( "hostage_killed" );
ListenForGameEvent( "hostage_hurt" );
ListenForGameEvent( "hostage_follows" );
ListenForGameEvent( "bomb_pickup" );
ListenForGameEvent( "bomb_dropped" );
ListenForGameEvent( "bomb_exploded" );
ListenForGameEvent( "bomb_defused" );
ListenForGameEvent( "bomb_planted" );
ListenForGameEvent( "vip_escaped" );
ListenForGameEvent( "vip_killed" );
}
}
void CCSHLTVDirector::CreateShotFromEvent( CHLTVGameEvent *event )
{
// show event at least for 2 more seconds after it occured
const char *name = event->m_Event->GetName();
IGameEvent *shot = NULL;
if ( !Q_strcmp( "hostage_rescued", name ) ||
!Q_strcmp( "hostage_hurt", name ) ||
!Q_strcmp( "hostage_follows", name ) ||
!Q_strcmp( "hostage_killed", name ) )
{
CBaseEntity *player = UTIL_PlayerByUserId( event->m_Event->GetInt("userid") );
if ( !player )
return;
// shot player as primary, hostage as secondary target
shot = gameeventmanager->CreateEvent( "hltv_chase", true );
shot->SetInt( "target1", player->entindex() );
shot->SetInt( "target2", event->m_Event->GetInt("hostage") );
shot->SetFloat( "distance", 96.0f );
shot->SetInt( "theta", 40 );
shot->SetInt( "phi", 20 );
// shot 2 seconds after event
m_nNextShotTick = MIN( m_nNextShotTick, (event->m_Tick+TIME_TO_TICKS(2.0)) );
m_iPVSEntity = player->entindex();
}
else if ( !Q_strcmp( "bomb_pickup", name ) ||
!Q_strcmp( "bomb_dropped", name ) ||
!Q_strcmp( "bomb_planted", name ) ||
!Q_strcmp( "bomb_defused", name ) )
{
CBaseEntity *player = UTIL_PlayerByUserId( event->m_Event->GetInt("userid") );
if ( !player )
return;
shot = gameeventmanager->CreateEvent( "hltv_chase", true );
shot->SetInt( "target1", player->entindex() );
shot->SetInt( "target2", 0 );
shot->SetFloat( "distance", 64.0f );
shot->SetInt( "theta", 200 );
shot->SetInt( "phi", 10 );
// shot 2 seconds after pickup
m_nNextShotTick = MIN( m_nNextShotTick, (event->m_Tick+TIME_TO_TICKS(2.0)) );
m_iPVSEntity = player->entindex();
}
else
{
// let baseclass create a shot
BaseClass::CreateShotFromEvent( event );
return;
}
if ( shot )
{
m_pHLTVServer->BroadcastEvent( shot );
gameeventmanager->FreeEvent( shot );
DevMsg("DrcCmd: %s\n", name );
}
}
const char** CCSHLTVDirector::GetModEvents()
{
// game events relayed to spectator clients
static const char *s_modevents[] =
{
"hltv_status",
"hltv_chat",
"player_connect",
"player_disconnect",
"player_team",
"player_info",
"server_cvar",
"player_death",
"player_chat",
"round_start",
"round_end",
// additional CS:S events:
"bomb_planted",
"bomb_defused",
"hostage_killed",
"hostage_hurt",
NULL
};
return s_modevents;
}
static CCSHLTVDirector s_HLTVDirector; // singleton
EXPOSE_SINGLE_INTERFACE_GLOBALVAR(CHLTVDirector, IHLTVDirector, INTERFACEVERSION_HLTVDIRECTOR, s_HLTVDirector );
CHLTVDirector* HLTVDirector()
{
return &s_HLTVDirector;
}
IGameSystem* HLTVDirectorSystem()
{
return &s_HLTVDirector;
}
+73
View File
@@ -0,0 +1,73 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//
//=============================================================================//
// nav.h
// Data structures and constants for the Navigation Mesh system
// Author: Michael S. Booth (mike@turtlerockstudios.com), January 2003
#ifndef _CS_NAV_H_
#define _CS_NAV_H_
#include "nav.h"
/**
* Below are several constants used by the navigation system.
* @todo Move these into TheNavMesh singleton.
*/
const float BotRadius = 10.0f; ///< circular extent that contains bot
class CNavArea;
class CSNavNode;
#if 0
//--------------------------------------------------------------------------------------------------------------
/**
* Return true if given entity can be ignored when moving
*/
#define WALK_THRU_DOORS 0x01
#define WALK_THRU_BREAKABLES 0x02
#define WALK_THRU_TOGGLE_BRUSHES 0x04
#define WALK_THRU_EVERYTHING (WALK_THRU_DOORS | WALK_THRU_BREAKABLES | WALK_THRU_TOGGLE_BRUSHES)
inline bool IsEntityWalkable( CBaseEntity *entity, unsigned int flags )
{
if (FClassnameIs( entity, "worldspawn" ))
return false;
if (FClassnameIs( entity, "player" ))
return false;
// if we hit a door, assume its walkable because it will open when we touch it
if (FClassnameIs( entity, "prop_door*" ) || FClassnameIs( entity, "func_door*" ))
return (flags & WALK_THRU_DOORS) ? true : false;
// if we hit a clip brush, ignore it if it is not BRUSHSOLID_ALWAYS
if (FClassnameIs( entity, "func_brush" ))
{
CFuncBrush *brush = (CFuncBrush *)entity;
switch ( brush->m_iSolidity )
{
case CFuncBrush::BRUSHSOLID_ALWAYS:
return false;
case CFuncBrush::BRUSHSOLID_NEVER:
return true;
case CFuncBrush::BRUSHSOLID_TOGGLE:
return (flags & WALK_THRU_TOGGLE_BRUSHES) ? true : false;
}
}
// if we hit a breakable object, assume its walkable because we will shoot it when we touch it
if (FClassnameIs( entity, "func_breakable" ) && entity->GetHealth() && entity->m_takedamage == DAMAGE_YES)
return (flags & WALK_THRU_BREAKABLES) ? true : false;
if (FClassnameIs( entity, "func_breakable_surf" ) && entity->m_takedamage == DAMAGE_YES)
return (flags & WALK_THRU_BREAKABLES) ? true : false;
return false;
}
#endif
#endif // _CS_NAV_H_
+473
View File
@@ -0,0 +1,473 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//
//=============================================================================//
// nav_area.cpp
// AI Navigation areas
// Author: Michael S. Booth (mike@turtlerockstudios.com), January 2003
#include "cbase.h"
#include "cs_nav_mesh.h"
#include "cs_nav_area.h"
#include "nav_pathfind.h"
#include "nav_colors.h"
#include "fmtstr.h"
#include "props_shared.h"
#include "func_breakablesurf.h"
#include "Color.h"
#include "collisionutils.h"
// memdbgon must be the last include file in a .cpp file!!!
#include <tier0/memdbgon.h>
#ifdef _WIN32
#pragma warning (disable:4701) // disable warning that variable *may* not be initialized
#endif
//--------------------------------------------------------------------------------------------------------------
/**
* Constructor used during normal runtime.
*/
CCSNavArea::CCSNavArea( void )
{
m_approachCount = 0;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Destructor
*/
CCSNavArea::~CCSNavArea()
{
}
void CCSNavArea::OnServerActivate( void )
{
CNavArea::OnServerActivate();
}
void CCSNavArea::OnRoundRestart( void )
{
CNavArea::OnRoundRestart();
}
void CCSNavArea::Save( CUtlBuffer &fileBuffer, unsigned int version ) const
{
CNavArea::Save( fileBuffer, version );
//
// Save the approach areas for this area
//
// save number of approach areas
fileBuffer.PutUnsignedChar(m_approachCount);
// save approach area info
for( int a=0; a<m_approachCount; ++a )
{
if (m_approach[a].here.area)
fileBuffer.PutUnsignedInt(m_approach[a].here.area->GetID());
else
fileBuffer.PutUnsignedInt(0);
if (m_approach[a].prev.area)
fileBuffer.PutUnsignedInt(m_approach[a].prev.area->GetID());
else
fileBuffer.PutUnsignedInt(0);
fileBuffer.PutUnsignedChar(m_approach[a].prevToHereHow);
if (m_approach[a].next.area)
fileBuffer.PutUnsignedInt(m_approach[a].next.area->GetID());
else
fileBuffer.PutUnsignedInt(0);
fileBuffer.PutUnsignedChar(m_approach[a].hereToNextHow);
}
}
NavErrorType CCSNavArea::Load( CUtlBuffer &fileBuffer, unsigned int version, unsigned int subVersion )
{
if ( version < 15 )
return LoadLegacy(fileBuffer, version, subVersion);
// load base class data
NavErrorType error = CNavArea::Load( fileBuffer, version, subVersion );
switch ( subVersion )
{
case 1:
//
// Load number of approach areas
//
m_approachCount = fileBuffer.GetUnsignedChar();
// load approach area info (IDs)
for( int a = 0; a < m_approachCount; ++a )
{
m_approach[a].here.id = fileBuffer.GetUnsignedInt();
m_approach[a].prev.id = fileBuffer.GetUnsignedInt();
m_approach[a].prevToHereHow = (NavTraverseType)fileBuffer.GetUnsignedChar();
m_approach[a].next.id = fileBuffer.GetUnsignedInt();
m_approach[a].hereToNextHow = (NavTraverseType)fileBuffer.GetUnsignedChar();
}
if ( !fileBuffer.IsValid() )
error = NAV_INVALID_FILE;
// fall through
case 0:
// legacy version
break;
default:
Warning( "Unknown NavArea sub-version number\n" );
error = NAV_INVALID_FILE;
}
return error;
}
NavErrorType CCSNavArea::PostLoad( void )
{
NavErrorType error = CNavArea::PostLoad();
// resolve approach area IDs
for ( int a = 0; a < m_approachCount; ++a )
{
m_approach[a].here.area = TheNavMesh->GetNavAreaByID( m_approach[a].here.id );
if (m_approach[a].here.id && m_approach[a].here.area == NULL)
{
Msg( "CNavArea::PostLoad: Corrupt navigation data. Missing Approach Area (here).\n" );
error = NAV_CORRUPT_DATA;
}
m_approach[a].prev.area = TheNavMesh->GetNavAreaByID( m_approach[a].prev.id );
if (m_approach[a].prev.id && m_approach[a].prev.area == NULL)
{
Msg( "CNavArea::PostLoad: Corrupt navigation data. Missing Approach Area (prev).\n" );
error = NAV_CORRUPT_DATA;
}
m_approach[a].next.area = TheNavMesh->GetNavAreaByID( m_approach[a].next.id );
if (m_approach[a].next.id && m_approach[a].next.area == NULL)
{
Msg( "CNavArea::PostLoad: Corrupt navigation data. Missing Approach Area (next).\n" );
error = NAV_CORRUPT_DATA;
}
}
return error;
}
void CCSNavArea::Draw( void ) const
{
CNavArea::Draw();
}
void CCSNavArea::CustomAnalysis( bool isIncremental /*= false */ )
{
ComputeApproachAreas();
}
//--------------------------------------------------------------------------------------------------------------
/**
* Load legacy navigation area from the file
*/
NavErrorType CCSNavArea::LoadLegacy( CUtlBuffer &fileBuffer, unsigned int version, unsigned int subVersion )
{
// load ID
m_id = fileBuffer.GetUnsignedInt();
// update nextID to avoid collisions
if (m_id >= m_nextID)
m_nextID = m_id+1;
// load attribute flags
if ( version <= 8 )
{
m_attributeFlags = fileBuffer.GetUnsignedChar();
}
else if ( version < 13 )
{
m_attributeFlags = fileBuffer.GetUnsignedShort();
}
else
{
m_attributeFlags = fileBuffer.GetInt();
}
// load extent of area
fileBuffer.Get( &m_nwCorner, 3*sizeof(float) );
fileBuffer.Get( &m_seCorner, 3*sizeof(float) );
m_center.x = (m_nwCorner.x + m_seCorner.x)/2.0f;
m_center.y = (m_nwCorner.y + m_seCorner.y)/2.0f;
m_center.z = (m_nwCorner.z + m_seCorner.z)/2.0f;
if ( ( m_seCorner.x - m_nwCorner.x ) > 0.0f && ( m_seCorner.y - m_nwCorner.y ) > 0.0f )
{
m_invDxCorners = 1.0f / ( m_seCorner.x - m_nwCorner.x );
m_invDyCorners = 1.0f / ( m_seCorner.y - m_nwCorner.y );
}
else
{
m_invDxCorners = m_invDyCorners = 0;
DevWarning( "Degenerate Navigation Area #%d at setpos %g %g %g\n",
m_id, m_center.x, m_center.y, m_center.z );
}
// load heights of implicit corners
m_neZ = fileBuffer.GetFloat();
m_swZ = fileBuffer.GetFloat();
CheckWaterLevel();
// load connections (IDs) to adjacent areas
// in the enum order NORTH, EAST, SOUTH, WEST
for( int d=0; d<NUM_DIRECTIONS; d++ )
{
// load number of connections for this direction
unsigned int count = fileBuffer.GetUnsignedInt();
Assert( fileBuffer.IsValid() );
m_connect[d].EnsureCapacity( count );
for( unsigned int i=0; i<count; ++i )
{
NavConnect connect;
connect.id = fileBuffer.GetUnsignedInt();
Assert( fileBuffer.IsValid() );
// don't allow self-referential connections
if ( connect.id != m_id )
{
m_connect[d].AddToTail( connect );
}
}
}
//
// Load hiding spots
//
// load number of hiding spots
unsigned char hidingSpotCount = fileBuffer.GetUnsignedChar();
if (version == 1)
{
// load simple vector array
Vector pos;
for( int h=0; h<hidingSpotCount; ++h )
{
fileBuffer.Get( &pos, 3 * sizeof(float) );
// create new hiding spot and put on master list
HidingSpot *spot = TheNavMesh->CreateHidingSpot();
spot->SetPosition( pos );
spot->SetFlags( HidingSpot::IN_COVER );
m_hidingSpots.AddToTail( spot );
}
}
else
{
// load HidingSpot objects for this area
for( int h=0; h<hidingSpotCount; ++h )
{
// create new hiding spot and put on master list
HidingSpot *spot = TheNavMesh->CreateHidingSpot();
spot->Load( fileBuffer, version );
m_hidingSpots.AddToTail( spot );
}
}
if ( version < 15 )
{
//
// Load number of approach areas
//
m_approachCount = fileBuffer.GetUnsignedChar();
// load approach area info (IDs)
for( int a = 0; a < m_approachCount; ++a )
{
m_approach[a].here.id = fileBuffer.GetUnsignedInt();
m_approach[a].prev.id = fileBuffer.GetUnsignedInt();
m_approach[a].prevToHereHow = (NavTraverseType)fileBuffer.GetUnsignedChar();
m_approach[a].next.id = fileBuffer.GetUnsignedInt();
m_approach[a].hereToNextHow = (NavTraverseType)fileBuffer.GetUnsignedChar();
}
}
//
// Load encounter paths for this area
//
unsigned int count = fileBuffer.GetUnsignedInt();
if (version < 3)
{
// old data, read and discard
for( unsigned int e=0; e<count; ++e )
{
SpotEncounter encounter;
encounter.from.id = fileBuffer.GetUnsignedInt();
encounter.to.id = fileBuffer.GetUnsignedInt();
fileBuffer.Get( &encounter.path.from.x, 3 * sizeof(float) );
fileBuffer.Get( &encounter.path.to.x, 3 * sizeof(float) );
// read list of spots along this path
unsigned char spotCount = fileBuffer.GetUnsignedChar();
for( int s=0; s<spotCount; ++s )
{
fileBuffer.GetFloat();
fileBuffer.GetFloat();
fileBuffer.GetFloat();
fileBuffer.GetFloat();
}
}
return NAV_OK;
}
for( unsigned int e=0; e<count; ++e )
{
SpotEncounter *encounter = new SpotEncounter;
encounter->from.id = fileBuffer.GetUnsignedInt();
unsigned char dir = fileBuffer.GetUnsignedChar();
encounter->fromDir = static_cast<NavDirType>( dir );
encounter->to.id = fileBuffer.GetUnsignedInt();
dir = fileBuffer.GetUnsignedChar();
encounter->toDir = static_cast<NavDirType>( dir );
// read list of spots along this path
unsigned char spotCount = fileBuffer.GetUnsignedChar();
SpotOrder order;
for( int s=0; s<spotCount; ++s )
{
order.id = fileBuffer.GetUnsignedInt();
unsigned char t = fileBuffer.GetUnsignedChar();
order.t = (float)t/255.0f;
encounter->spots.AddToTail( order );
}
m_spotEncounters.AddToTail( encounter );
}
if (version < 5)
return NAV_OK;
//
// Load Place data
//
PlaceDirectory::IndexType entry = fileBuffer.GetUnsignedShort();
// convert entry to actual Place
SetPlace( placeDirectory.IndexToPlace( entry ) );
if ( version < 7 )
return NAV_OK;
// load ladder data
for ( int dir=0; dir<CNavLadder::NUM_LADDER_DIRECTIONS; ++dir )
{
count = fileBuffer.GetUnsignedInt();
for( unsigned int i=0; i<count; ++i )
{
NavLadderConnect connect;
connect.id = fileBuffer.GetUnsignedInt();
bool alreadyConnected = false;
FOR_EACH_VEC( m_ladder[dir], j )
{
if ( m_ladder[dir][j].id == connect.id )
{
alreadyConnected = true;
break;
}
}
if ( !alreadyConnected )
{
m_ladder[dir].AddToTail( connect );
}
}
}
if ( version < 8 )
return NAV_OK;
// load earliest occupy times
for( int i=0; i<MAX_NAV_TEAMS; ++i )
{
// no spot in the map should take longer than this to reach
m_earliestOccupyTime[i] = fileBuffer.GetFloat();
}
if ( version < 11 )
return NAV_OK;
// load light intensity
for ( int i=0; i<NUM_CORNERS; ++i )
{
m_lightIntensity[i] = fileBuffer.GetFloat();
}
if ( version < 16 )
return NAV_OK;
// load visibility information
unsigned int visibleAreaCount = fileBuffer.GetUnsignedInt();
if ( !IsX360() )
{
m_potentiallyVisibleAreas.EnsureCapacity( visibleAreaCount );
}
else
{
/* TODO: Re-enable when latest 360 code gets integrated (MSB 5/5/09)
size_t nBytes = visibleAreaCount * sizeof( AreaBindInfo );
m_potentiallyVisibleAreas.~CAreaBindInfoArray();
new ( &m_potentiallyVisibleAreas ) CAreaBindInfoArray( (AreaBindInfo *)engine->AllocLevelStaticData( nBytes ), visibleAreaCount );
*/
}
for( unsigned int j=0; j<visibleAreaCount; ++j )
{
AreaBindInfo info;
info.id = fileBuffer.GetUnsignedInt();
info.attributes = fileBuffer.GetUnsignedChar();
m_potentiallyVisibleAreas.AddToTail( info );
}
// read area from which we inherit visibility
m_inheritVisibilityFrom.id = fileBuffer.GetUnsignedInt();
return NAV_OK;
}
+81
View File
@@ -0,0 +1,81 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//
//=============================================================================//
// nav_area.h
// Navigation areas
// Author: Michael S. Booth (mike@turtlerockstudios.com), January 2003
#ifndef _CS_NAV_AREA_H_
#define _CS_NAV_AREA_H_
#include "nav_area.h"
//-------------------------------------------------------------------------------------------------------------------
/**
* A CNavArea is a rectangular region defining a walkable area in the environment
*/
class CCSNavArea : public CNavArea
{
public:
DECLARE_CLASS( CCSNavArea, CNavArea );
CCSNavArea( void );
~CCSNavArea();
virtual void OnServerActivate( void ); // (EXTEND) invoked when map is initially loaded
virtual void OnRoundRestart( void ); // (EXTEND) invoked for each area when the round restarts
virtual void Draw( void ) const; // draw area for debugging & editing
virtual void Save( CUtlBuffer &fileBuffer, unsigned int version ) const; // (EXTEND)
virtual NavErrorType Load( CUtlBuffer &fileBuffer, unsigned int version, unsigned int subVersion ); // (EXTEND)
virtual NavErrorType PostLoad( void ); // (EXTEND) invoked after all areas have been loaded - for pointer binding, etc
virtual void CustomAnalysis( bool isIncremental = false ); // for game-specific analysis
//- approach areas ----------------------------------------------------------------------------------
struct ApproachInfo
{
NavConnect here; ///< the approach area
NavConnect prev; ///< the area just before the approach area on the path
NavTraverseType prevToHereHow;
NavConnect next; ///< the area just after the approach area on the path
NavTraverseType hereToNextHow;
};
const ApproachInfo *GetApproachInfo( int i ) const { return &m_approach[i]; }
int GetApproachInfoCount( void ) const { return m_approachCount; }
void ComputeApproachAreas( void ); ///< determine the set of "approach areas" - for map learning
//- player counting --------------------------------------------------------------------------------
void ClearPlayerCount( void ); ///< set the player count to zero
protected:
NavErrorType LoadLegacy( CUtlBuffer &fileBuffer, unsigned int version, unsigned int subVersion );
private:
//- approach areas ----------------------------------------------------------------------------------
enum { MAX_APPROACH_AREAS = 16 };
ApproachInfo m_approach[ MAX_APPROACH_AREAS ];
unsigned char m_approachCount;
};
//--------------------------------------------------------------------------------------------------------------
//--------------------------------------------------------------------------------------------------------------
//
// Inlines
//
inline void CCSNavArea::ClearPlayerCount( void )
{
for( int i=0; i<MAX_NAV_TEAMS; ++i )
{
m_playerCount[ i ] = 0;
}
}
#endif // _CS_NAV_AREA_H_
+69
View File
@@ -0,0 +1,69 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//
//=============================================================================//
// nav_edit.cpp
// Implementation of Navigation Mesh edit mode
// Author: Michael Booth, 2003-2004
#include "cbase.h"
#include "nav_mesh.h"
#include "cs_nav_pathfind.h"
#include "cs_nav_node.h"
#include "nav_colors.h"
#include "Color.h"
#include "tier0/vprof.h"
#include "collisionutils.h"
ConVar nav_show_area_info( "nav_show_area_info", "0.5", FCVAR_GAMEDLL, "Duration in seconds to show nav area ID and attributes while editing" );
ConVar nav_snap_to_grid( "nav_snap_to_grid", "0", FCVAR_GAMEDLL, "Snap to the nav generation grid when creating new nav areas" );
ConVar nav_create_place_on_ground( "nav_create_place_on_ground", "0", FCVAR_GAMEDLL, "If true, nav areas will be placed flush with the ground when created by hand." );
#if DEBUG_NAV_NODES
extern ConVar nav_show_nodes;
#endif // DEBUG_NAV_NODES
//--------------------------------------------------------------------------------------------------------------
void EditNav_Precache(void *pUser)
{
CBaseEntity::PrecacheScriptSound( "Bot.EditSwitchOn" );
CBaseEntity::PrecacheScriptSound( "EDIT_TOGGLE_PLACE_MODE" );
CBaseEntity::PrecacheScriptSound( "Bot.EditSwitchOff" );
CBaseEntity::PrecacheScriptSound( "EDIT_PLACE_PICK" );
CBaseEntity::PrecacheScriptSound( "EDIT_DELETE" );
CBaseEntity::PrecacheScriptSound( "EDIT.ToggleAttribute" );
CBaseEntity::PrecacheScriptSound( "EDIT_SPLIT.MarkedArea" );
CBaseEntity::PrecacheScriptSound( "EDIT_SPLIT.NoMarkedArea" );
CBaseEntity::PrecacheScriptSound( "EDIT_MERGE.Enable" );
CBaseEntity::PrecacheScriptSound( "EDIT_MERGE.Disable" );
CBaseEntity::PrecacheScriptSound( "EDIT_MARK.Enable" );
CBaseEntity::PrecacheScriptSound( "EDIT_MARK.Disable" );
CBaseEntity::PrecacheScriptSound( "EDIT_MARK_UNNAMED.Enable" );
CBaseEntity::PrecacheScriptSound( "EDIT_MARK_UNNAMED.NoMarkedArea" );
CBaseEntity::PrecacheScriptSound( "EDIT_MARK_UNNAMED.MarkedArea" );
CBaseEntity::PrecacheScriptSound( "EDIT_CONNECT.AllDirections" );
CBaseEntity::PrecacheScriptSound( "EDIT_CONNECT.Added" );
CBaseEntity::PrecacheScriptSound( "EDIT_DISCONNECT.MarkedArea" );
CBaseEntity::PrecacheScriptSound( "EDIT_DISCONNECT.NoMarkedArea" );
CBaseEntity::PrecacheScriptSound( "EDIT_SPLICE.MarkedArea" );
CBaseEntity::PrecacheScriptSound( "EDIT_SPLICE.NoMarkedArea" );
CBaseEntity::PrecacheScriptSound( "EDIT_SELECT_CORNER.MarkedArea" );
CBaseEntity::PrecacheScriptSound( "EDIT_SELECT_CORNER.NoMarkedArea" );
CBaseEntity::PrecacheScriptSound( "EDIT_MOVE_CORNER.MarkedArea" );
CBaseEntity::PrecacheScriptSound( "EDIT_MOVE_CORNER.NoMarkedArea" );
CBaseEntity::PrecacheScriptSound( "EDIT_BEGIN_AREA.Creating" );
CBaseEntity::PrecacheScriptSound( "EDIT_BEGIN_AREA.NotCreating" );
CBaseEntity::PrecacheScriptSound( "EDIT_END_AREA.Creating" );
CBaseEntity::PrecacheScriptSound( "EDIT_END_AREA.NotCreating" );
CBaseEntity::PrecacheScriptSound( "EDIT_WARP_TO_MARK" );
}
#ifdef CSTRIKE_DLL
PRECACHE_REGISTER_FN( EditNav_Precache );
#endif
File diff suppressed because it is too large Load Diff
+269
View File
@@ -0,0 +1,269 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//
//=============================================================================//
// nav_generate.cpp
// Auto-generate a Navigation Mesh by sampling the current map
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
#include "cbase.h"
#include "util_shared.h"
#include "nav_mesh.h"
#include "cs_nav_area.h"
#include "cs_nav_node.h"
#include "cs_nav_pathfind.h"
#include "viewport_panel_names.h"
enum { MAX_BLOCKED_AREAS = 256 };
static unsigned int blockedID[ MAX_BLOCKED_AREAS ];
static int blockedIDCount = 0;
static float lastMsgTime = 0.0f;
//ConVar nav_slope_limit( "nav_slope_limit", "0.7", FCVAR_GAMEDLL, "The ground unit normal's Z component must be greater than this for nav areas to be generated." );
ConVar nav_restart_after_analysis( "nav_restart_after_analysis", "1", FCVAR_GAMEDLL, "When nav nav_restart_after_analysis finishes, restart the server. Turning this off can cause crashes, but is useful for incremental generation." );
//--------------------------------------------------------------------------------------------------------------
/**
* Shortest path cost, paying attention to "blocked" areas
*/
class ApproachAreaCost
{
public:
// HPE_TODO[pmf]: check that these new parameters are okay to be ignored
float operator() ( CNavArea *area, CNavArea *fromArea, const CNavLadder *ladder, const CFuncElevator *elevator, float length )
{
// check if this area is "blocked"
for( int i=0; i<blockedIDCount; ++i )
{
if (area->GetID() == blockedID[i])
{
return -1.0f;
}
}
if (fromArea == NULL)
{
// first area in path, no cost
return 0.0f;
}
else
{
// compute distance traveled along path so far
float dist;
if (ladder)
{
dist = ladder->m_length;
}
else
{
dist = (area->GetCenter() - fromArea->GetCenter()).Length();
}
float cost = dist + fromArea->GetCostSoFar();
return cost;
}
}
};
/*
* Determine the set of "approach areas".
* An approach area is an area representing a place where players
* move into/out of our local neighborhood of areas.
* @todo Optimize by search from eye outward and modifying pathfinder to treat all links as bi-directional
*/
void CCSNavArea::ComputeApproachAreas( void )
{
m_approachCount = 0;
if (nav_quicksave.GetBool())
return;
// use the center of the nav area as the "view" point
Vector eye = m_center;
if (TheNavMesh->GetGroundHeight( eye, &eye.z ) == false)
return;
// approximate eye position
if (GetAttributes() & NAV_MESH_CROUCH)
eye.z += 0.9f * HalfHumanHeight;
else
eye.z += 0.9f * HumanHeight;
enum { MAX_PATH_LENGTH = 256 };
CNavArea *path[ MAX_PATH_LENGTH ];
ApproachAreaCost cost;
enum SearchType
{
FROM_EYE, ///< start search from our eyepoint outward to farArea
TO_EYE, ///< start search from farArea beack towards our eye
SEARCH_FINISHED
};
//
// In order to *completely* enumerate all of the approach areas, we
// need to search from our eyepoint outward, as well as from outwards
// towards our eyepoint
//
for( int searchType = FROM_EYE; searchType != SEARCH_FINISHED; ++searchType )
{
//
// In order to enumerate all of the approach areas, we need to
// run the algorithm many times, once for each "far away" area
// and keep the union of the approach area sets
//
int it;
for( it = 0; it < TheNavAreas.Count(); ++it )
{
CNavArea *farArea = TheNavAreas[ it ];
blockedIDCount = 0;
// skip the small areas
const float minSize = 200.0f; // 150
Extent extent;
farArea->GetExtent(&extent);
if (extent.SizeX() < minSize || extent.SizeY() < minSize)
{
continue;
}
// if we can see 'farArea', try again - the whole point is to go "around the bend", so to speak
if (farArea->IsVisible( eye ))
{
continue;
}
//
// Keep building paths to farArea and blocking them off until we
// cant path there any more.
// As areas are blocked off, all exits will be enumerated.
//
while( m_approachCount < MAX_APPROACH_AREAS )
{
CNavArea *from, *to;
if (searchType == FROM_EYE)
{
// find another path *to* 'farArea'
// we must pathfind from us in order to pick up one-way paths OUT OF our area
from = this;
to = farArea;
}
else // TO_EYE
{
// find another path *from* 'farArea'
// we must pathfind to us in order to pick up one-way paths INTO our area
from = farArea;
to = this;
}
// build the actual path
if (NavAreaBuildPath( from, to, NULL, cost ) == false)
{
break;
}
// find number of areas on path
int count = 0;
CNavArea *area;
for( area = to; area; area = area->GetParent() )
{
++count;
}
if (count > MAX_PATH_LENGTH)
{
count = MAX_PATH_LENGTH;
}
// if the path is only two areas long, there can be no approach points
if (count <= 2)
{
break;
}
// build path starting from eye
int i = 0;
if (searchType == FROM_EYE)
{
for( area = to; i < count && area; area = area->GetParent() )
{
path[ count-i-1 ] = area;
++i;
}
}
else // TO_EYE
{
for( area = to; i < count && area; area = area->GetParent() )
{
path[ i++ ] = area;
}
}
// traverse path to find first area we cannot see (skip the first area)
for( i=1; i<count; ++i )
{
// if we see this area, continue on
if (path[i]->IsVisible( eye ))
{
continue;
}
// we can't see this area - mark this area as "blocked" and unusable by subsequent approach paths
if (blockedIDCount == MAX_BLOCKED_AREAS)
{
Msg( "Overflow computing approach areas for area #%d.\n", GetID());
return;
}
// if the area to be blocked is actually farArea, block the one just prior
// (blocking farArea will cause all subsequent pathfinds to fail)
int block = (path[i] == farArea) ? i-1 : i;
// dont block the start area, or all subsequence pathfinds will fail
if (block == 0)
{
continue;
}
blockedID[ blockedIDCount++ ] = path[ block ]->GetID();
// store new approach area if not already in set
int a;
for( a=0; a<m_approachCount; ++a )
{
if (m_approach[a].here.area == path[block-1])
{
break;
}
}
if (a == m_approachCount)
{
m_approach[ m_approachCount ].prev.area = (block >= 2) ? path[block-2] : NULL;
m_approach[ m_approachCount ].here.area = path[block-1];
m_approach[ m_approachCount ].prevToHereHow = path[block-1]->GetParentHow();
m_approach[ m_approachCount ].next.area = path[block];
m_approach[ m_approachCount ].hereToNextHow = path[block]->GetParentHow();
++m_approachCount;
}
// we are done with this path
break;
}
}
}
}
}

Some files were not shown because too many files have changed in this diff Show More