This commit is contained in:
FluorescentCIAAfricanAmerican
2020-04-22 12:56:21 -04:00
commit 3bf9df6b27
15370 changed files with 5489726 additions and 0 deletions

View File

@@ -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_

View File

@@ -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_

View File

@@ -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;
}

View File

@@ -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

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 );
}
}

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_

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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;
}

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_

View File

@@ -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 );
}

View File

@@ -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_

View File

@@ -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_

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

View File

@@ -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

View File

@@ -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

View File

@@ -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_

View File

@@ -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;
}

View File

@@ -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_

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();
}
}
}
}

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_

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

View File

@@ -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();
}

View File

@@ -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_

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] );
}
}

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_

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_

View File

@@ -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 );
}

View File

@@ -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_

View File

@@ -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 );
}

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

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

View File

@@ -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_

View File

@@ -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_

View File

@@ -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" );

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_

View File

@@ -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;
}

View File

@@ -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_

View File

@@ -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();
}

View File

@@ -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_

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 )

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