mirror of
https://github.com/celisej567/source-engine.git
synced 2026-01-05 22:09:59 +03:00
1
This commit is contained in:
118
game/server/NextBot/Behavior/BehaviorBackUp.h
Normal file
118
game/server/NextBot/Behavior/BehaviorBackUp.h
Normal 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_
|
||||
|
||||
126
game/server/NextBot/Behavior/BehaviorMoveTo.h
Normal file
126
game/server/NextBot/Behavior/BehaviorMoveTo.h
Normal 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_
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
56
game/server/NextBot/NavMeshEntities/func_nav_prerequisite.h
Normal file
56
game/server/NextBot/NavMeshEntities/func_nav_prerequisite.h
Normal 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
|
||||
523
game/server/NextBot/NextBot.cpp
Normal file
523
game/server/NextBot/NextBot.cpp
Normal file
@@ -0,0 +1,523 @@
|
||||
// NextBotCombatCharacter.cpp
|
||||
// Next generation bot system
|
||||
// Author: Michael Booth, April 2005
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
|
||||
#include "cbase.h"
|
||||
|
||||
#include "team.h"
|
||||
#include "CRagdollMagnet.h"
|
||||
|
||||
#include "NextBot.h"
|
||||
#include "NextBotLocomotionInterface.h"
|
||||
#include "NextBotBodyInterface.h"
|
||||
|
||||
#ifdef TERROR
|
||||
#include "TerrorGamerules.h"
|
||||
#endif
|
||||
|
||||
#include "vprof.h"
|
||||
#include "datacache/imdlcache.h"
|
||||
#include "EntityFlame.h"
|
||||
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
|
||||
ConVar NextBotStop( "nb_stop", "0", FCVAR_CHEAT | FCVAR_REPLICATED, "Stop all NextBots" );
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------
|
||||
class CSendBotCommand
|
||||
{
|
||||
public:
|
||||
CSendBotCommand( const char *command )
|
||||
{
|
||||
m_command = command;
|
||||
}
|
||||
|
||||
bool operator() ( INextBot *bot )
|
||||
{
|
||||
bot->OnCommandString( m_command );
|
||||
return true;
|
||||
}
|
||||
|
||||
const char *m_command;
|
||||
};
|
||||
|
||||
|
||||
CON_COMMAND_F( nb_command, "Sends a command string to all bots", FCVAR_CHEAT )
|
||||
{
|
||||
if ( args.ArgC() <= 1 )
|
||||
{
|
||||
Msg( "Missing command string" );
|
||||
return;
|
||||
}
|
||||
|
||||
CSendBotCommand sendCmd( args.ArgS() );
|
||||
TheNextBots().ForEachBot( sendCmd );
|
||||
}
|
||||
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------------------------------
|
||||
BEGIN_DATADESC( NextBotCombatCharacter )
|
||||
|
||||
DEFINE_THINKFUNC( DoThink ),
|
||||
|
||||
END_DATADESC()
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------------------------------
|
||||
IMPLEMENT_SERVERCLASS_ST( NextBotCombatCharacter, DT_NextBot )
|
||||
END_SEND_TABLE()
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------------------------------
|
||||
NextBotDestroyer::NextBotDestroyer( int team )
|
||||
{
|
||||
m_team = team;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------------------------------
|
||||
bool NextBotDestroyer::operator() ( INextBot *bot )
|
||||
{
|
||||
if ( m_team == TEAM_ANY || bot->GetEntity()->GetTeamNumber() == m_team )
|
||||
{
|
||||
// players need to be kicked, not deleted
|
||||
if ( bot->GetEntity()->IsPlayer() )
|
||||
{
|
||||
CBasePlayer *player = dynamic_cast< CBasePlayer * >( bot->GetEntity() );
|
||||
engine->ServerCommand( UTIL_VarArgs( "kickid %d\n", player->GetUserID() ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
UTIL_Remove( bot->GetEntity() );
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------------------------------
|
||||
CON_COMMAND_F( nb_delete_all, "Delete all non-player NextBot entities.", FCVAR_CHEAT )
|
||||
{
|
||||
// Listenserver host or rcon access only!
|
||||
if ( !UTIL_IsCommandIssuedByServerAdmin() )
|
||||
return;
|
||||
|
||||
CTeam *team = NULL;
|
||||
|
||||
if ( args.ArgC() == 2 )
|
||||
{
|
||||
const char *teamName = args[1];
|
||||
|
||||
for( int i=0; i < g_Teams.Count(); ++i )
|
||||
{
|
||||
if ( FStrEq( teamName, g_Teams[i]->GetName() ) )
|
||||
{
|
||||
// delete all bots on this team
|
||||
team = g_Teams[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( team == NULL )
|
||||
{
|
||||
Msg( "Invalid team '%s'\n", teamName );
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// delete all bots on all teams
|
||||
NextBotDestroyer destroyer( team ? team->GetTeamNumber() : TEAM_ANY );
|
||||
TheNextBots().ForEachBot( destroyer );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------------------------------
|
||||
class NextBotApproacher
|
||||
{
|
||||
public:
|
||||
NextBotApproacher( void )
|
||||
{
|
||||
CBasePlayer *player = UTIL_GetListenServerHost();
|
||||
if ( player )
|
||||
{
|
||||
Vector forward;
|
||||
player->EyeVectors( &forward );
|
||||
|
||||
trace_t result;
|
||||
unsigned int mask = MASK_BLOCKLOS_AND_NPCS|CONTENTS_IGNORE_NODRAW_OPAQUE | CONTENTS_GRATE | CONTENTS_WINDOW;
|
||||
UTIL_TraceLine( player->EyePosition(), player->EyePosition() + 999999.9f * forward, mask, player, COLLISION_GROUP_NONE, &result );
|
||||
if ( result.DidHit() )
|
||||
{
|
||||
NDebugOverlay::Cross3D( result.endpos, 5, 0, 255, 0, true, 10.0f );
|
||||
m_isGoalValid = true;
|
||||
m_goal = result.endpos;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_isGoalValid = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool operator() ( INextBot *bot )
|
||||
{
|
||||
if ( TheNextBots().IsDebugFilterMatch( bot ) )
|
||||
{
|
||||
bot->OnCommandApproach( m_goal );
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool m_isGoalValid;
|
||||
Vector m_goal;
|
||||
};
|
||||
|
||||
CON_COMMAND_F( nb_move_to_cursor, "Tell all NextBots to move to the cursor position", FCVAR_CHEAT )
|
||||
{
|
||||
// Listenserver host or rcon access only!
|
||||
if ( !UTIL_IsCommandIssuedByServerAdmin() )
|
||||
return;
|
||||
|
||||
NextBotApproacher approach;
|
||||
TheNextBots().ForEachBot( approach );
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------
|
||||
//----------------------------------------------------------------------------------------------------------
|
||||
bool IgnoreActorsTraceFilterFunction( IHandleEntity *pServerEntity, int contentsMask )
|
||||
{
|
||||
CBaseEntity *entity = EntityFromEntityHandle( pServerEntity );
|
||||
return ( entity->MyCombatCharacterPointer() == NULL ); // includes all bots, npcs, players, and TF2 buildings
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------
|
||||
//----------------------------------------------------------------------------------------------------------
|
||||
bool VisionTraceFilterFunction( IHandleEntity *pServerEntity, int contentsMask )
|
||||
{
|
||||
// Honor BlockLOS also to allow seeing through partially-broken doors
|
||||
CBaseEntity *entity = EntityFromEntityHandle( pServerEntity );
|
||||
return ( entity->MyCombatCharacterPointer() == NULL && entity->BlocksLOS() );
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------
|
||||
//----------------------------------------------------------------------------------------------------------
|
||||
|
||||
NextBotCombatCharacter::NextBotCombatCharacter( void )
|
||||
|
||||
{
|
||||
m_lastAttacker = NULL;
|
||||
m_didModelChange = false;
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------
|
||||
void NextBotCombatCharacter::Spawn( void )
|
||||
{
|
||||
BaseClass::Spawn();
|
||||
|
||||
// reset bot components
|
||||
Reset();
|
||||
|
||||
SetSolid( SOLID_BBOX );
|
||||
AddSolidFlags( FSOLID_NOT_STANDABLE );
|
||||
|
||||
SetMoveType( MOVETYPE_CUSTOM );
|
||||
|
||||
SetCollisionGroup( COLLISION_GROUP_PLAYER );
|
||||
|
||||
m_iMaxHealth = m_iHealth;
|
||||
m_takedamage = DAMAGE_YES;
|
||||
|
||||
MDLCACHE_CRITICAL_SECTION();
|
||||
InitBoneControllers( );
|
||||
|
||||
// set up think callback
|
||||
SetThink( &NextBotCombatCharacter::DoThink );
|
||||
SetNextThink( gpGlobals->curtime );
|
||||
|
||||
m_lastAttacker = NULL;
|
||||
}
|
||||
|
||||
|
||||
bool NextBotCombatCharacter::IsAreaTraversable( const CNavArea *area ) const
|
||||
{
|
||||
if ( !area )
|
||||
return false;
|
||||
ILocomotion *mover = GetLocomotionInterface();
|
||||
if ( mover && !mover->IsAreaTraversable( area ) )
|
||||
return false;
|
||||
return BaseClass::IsAreaTraversable( area );
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------
|
||||
void NextBotCombatCharacter::DoThink( void )
|
||||
{
|
||||
VPROF_BUDGET( "NextBotCombatCharacter::DoThink", "NextBot" );
|
||||
|
||||
SetNextThink( gpGlobals->curtime );
|
||||
|
||||
if ( BeginUpdate() )
|
||||
{
|
||||
// emit model change event
|
||||
if ( m_didModelChange )
|
||||
{
|
||||
m_didModelChange = false;
|
||||
|
||||
OnModelChanged();
|
||||
|
||||
// propagate model change into NextBot event responders
|
||||
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
|
||||
{
|
||||
sub->OnModelChanged();
|
||||
}
|
||||
}
|
||||
|
||||
UpdateLastKnownArea();
|
||||
|
||||
// update bot components
|
||||
if ( !NextBotStop.GetBool() && (GetFlags() & FL_FROZEN) == 0 )
|
||||
{
|
||||
Update();
|
||||
}
|
||||
|
||||
EndUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------
|
||||
void NextBotCombatCharacter::Touch( CBaseEntity *other )
|
||||
{
|
||||
if ( ShouldTouch( other ) )
|
||||
{
|
||||
// propagate touch into NextBot event responders
|
||||
trace_t result;
|
||||
result = GetTouchTrace();
|
||||
|
||||
// OnContact refers to *physical* contact, not triggers or other non-physical entities
|
||||
if ( result.DidHit() || other->MyCombatCharacterPointer() != NULL )
|
||||
{
|
||||
OnContact( other, &result );
|
||||
}
|
||||
}
|
||||
|
||||
BaseClass::Touch( other );
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------
|
||||
void NextBotCombatCharacter::SetModel( const char *szModelName )
|
||||
{
|
||||
// actually change the model
|
||||
BaseClass::SetModel( szModelName );
|
||||
|
||||
// need to do a lazy-check because precache system also invokes this
|
||||
m_didModelChange = true;
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------
|
||||
void NextBotCombatCharacter::Ignite( float flFlameLifetime, bool bNPCOnly, float flSize, bool bCalledByLevelDesigner )
|
||||
{
|
||||
BaseClass::Ignite( flFlameLifetime, bNPCOnly, flSize, bCalledByLevelDesigner );
|
||||
|
||||
// propagate event to components
|
||||
OnIgnite();
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------
|
||||
void NextBotCombatCharacter::Ignite( float flFlameLifetime, CBaseEntity *pAttacker )
|
||||
{
|
||||
if ( IsOnFire() )
|
||||
return;
|
||||
|
||||
// BaseClass::Ignite stuff, plus SetAttacker on the flame, so our attacker gets credit
|
||||
CEntityFlame *pFlame = CEntityFlame::Create( this );
|
||||
if ( pFlame )
|
||||
{
|
||||
pFlame->SetLifetime( flFlameLifetime );
|
||||
AddFlag( FL_ONFIRE );
|
||||
|
||||
SetEffectEntity( pFlame );
|
||||
}
|
||||
m_OnIgnite.FireOutput( this, this );
|
||||
|
||||
// propagate event to components
|
||||
OnIgnite();
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------
|
||||
int NextBotCombatCharacter::OnTakeDamage_Alive( const CTakeDamageInfo &info )
|
||||
{
|
||||
// track our last attacker
|
||||
if ( info.GetAttacker() && info.GetAttacker()->MyCombatCharacterPointer() )
|
||||
{
|
||||
m_lastAttacker = info.GetAttacker()->MyCombatCharacterPointer();
|
||||
}
|
||||
|
||||
// propagate event to components
|
||||
OnInjured( info );
|
||||
|
||||
return CBaseCombatCharacter::OnTakeDamage_Alive( info );
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------
|
||||
int NextBotCombatCharacter::OnTakeDamage_Dying( const CTakeDamageInfo &info )
|
||||
{
|
||||
// track our last attacker
|
||||
if ( info.GetAttacker()->MyCombatCharacterPointer() )
|
||||
{
|
||||
m_lastAttacker = info.GetAttacker()->MyCombatCharacterPointer();
|
||||
}
|
||||
|
||||
// propagate event to components
|
||||
OnInjured( info );
|
||||
|
||||
return CBaseCombatCharacter::OnTakeDamage_Dying( info );
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Can't use CBaseCombatCharacter's Event_Killed because it will immediately ragdoll us
|
||||
*/
|
||||
static int g_DeathStartEvent = 0;
|
||||
void NextBotCombatCharacter::Event_Killed( const CTakeDamageInfo &info )
|
||||
{
|
||||
// track our last attacker
|
||||
if ( info.GetAttacker() && info.GetAttacker()->MyCombatCharacterPointer() )
|
||||
{
|
||||
m_lastAttacker = info.GetAttacker()->MyCombatCharacterPointer();
|
||||
}
|
||||
|
||||
// propagate event to my components
|
||||
OnKilled( info );
|
||||
|
||||
// Advance life state to dying
|
||||
m_lifeState = LIFE_DYING;
|
||||
|
||||
#ifdef TERROR
|
||||
/*
|
||||
* TODO: Make this game-generic
|
||||
*/
|
||||
// Create the death event just like players do.
|
||||
TerrorGameRules()->DeathNoticeForEntity( this, info );
|
||||
|
||||
// Infected specific event
|
||||
TerrorGameRules()->DeathNoticeForInfected( this, info );
|
||||
#endif
|
||||
|
||||
if ( GetOwnerEntity() != NULL )
|
||||
{
|
||||
GetOwnerEntity()->DeathNotice( this );
|
||||
}
|
||||
|
||||
// inform the other bots
|
||||
TheNextBots().OnKilled( this, info );
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------
|
||||
void NextBotCombatCharacter::PerformCustomPhysics( Vector *pNewPosition, Vector *pNewVelocity, QAngle *pNewAngles, QAngle *pNewAngVelocity )
|
||||
{
|
||||
ILocomotion *mover = GetLocomotionInterface();
|
||||
if ( mover )
|
||||
{
|
||||
// hack to keep ground entity from being NULL'd when Z velocity is positive
|
||||
SetGroundEntity( mover->GetGround() );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------
|
||||
bool NextBotCombatCharacter::BecomeRagdoll( const CTakeDamageInfo &info, const Vector &forceVector )
|
||||
{
|
||||
// See if there's a ragdoll magnet that should influence our force.
|
||||
Vector adjustedForceVector = forceVector;
|
||||
CRagdollMagnet *magnet = CRagdollMagnet::FindBestMagnet( this );
|
||||
if ( magnet )
|
||||
{
|
||||
adjustedForceVector += magnet->GetForceVector( this );
|
||||
}
|
||||
|
||||
// clear the deceased's sound channels.(may have been firing or reloading when killed)
|
||||
EmitSound( "BaseCombatCharacter.StopWeaponSounds" );
|
||||
|
||||
return BaseClass::BecomeRagdoll( info, adjustedForceVector );
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------
|
||||
void NextBotCombatCharacter::HandleAnimEvent( animevent_t *event )
|
||||
{
|
||||
// propagate event to components
|
||||
OnAnimationEvent( event );
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Propagate event into NextBot event responders
|
||||
*/
|
||||
void NextBotCombatCharacter::OnNavAreaChanged( CNavArea *enteredArea, CNavArea *leftArea )
|
||||
{
|
||||
INextBotEventResponder::OnNavAreaChanged( enteredArea, leftArea );
|
||||
|
||||
BaseClass::OnNavAreaChanged( enteredArea, leftArea );
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------
|
||||
Vector NextBotCombatCharacter::EyePosition( void )
|
||||
{
|
||||
if ( GetBodyInterface() )
|
||||
{
|
||||
return GetBodyInterface()->GetEyePosition();
|
||||
}
|
||||
|
||||
return BaseClass::EyePosition();
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Return true if this object can be +used by the bot
|
||||
*/
|
||||
bool NextBotCombatCharacter::IsUseableEntity( CBaseEntity *entity, unsigned int requiredCaps )
|
||||
{
|
||||
if ( entity )
|
||||
{
|
||||
int caps = entity->ObjectCaps();
|
||||
if ( caps & (FCAP_IMPULSE_USE|FCAP_CONTINUOUS_USE|FCAP_ONOFF_USE|FCAP_DIRECTIONAL_USE) )
|
||||
{
|
||||
if ( (caps & requiredCaps) == requiredCaps )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------
|
||||
void NextBotCombatCharacter::UseEntity( CBaseEntity *entity, USE_TYPE useType )
|
||||
{
|
||||
if ( IsUseableEntity( entity ) )
|
||||
{
|
||||
variant_t emptyVariant;
|
||||
entity->AcceptInput( "Use", this, this, emptyVariant, useType );
|
||||
}
|
||||
}
|
||||
104
game/server/NextBot/NextBot.h
Normal file
104
game/server/NextBot/NextBot.h
Normal 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_
|
||||
162
game/server/NextBot/NextBotAttentionInterface.cpp
Normal file
162
game/server/NextBot/NextBotAttentionInterface.cpp
Normal 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;
|
||||
}
|
||||
81
game/server/NextBot/NextBotAttentionInterface.h
Normal file
81
game/server/NextBot/NextBotAttentionInterface.h
Normal 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 ¶ms ); // 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_
|
||||
|
||||
1936
game/server/NextBot/NextBotBehavior.h
Normal file
1936
game/server/NextBot/NextBotBehavior.h
Normal file
File diff suppressed because it is too large
Load Diff
55
game/server/NextBot/NextBotBodyInterface.cpp
Normal file
55
game/server/NextBot/NextBotBodyInterface.cpp
Normal 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;
|
||||
}
|
||||
325
game/server/NextBot/NextBotBodyInterface.h
Normal file
325
game/server/NextBot/NextBotBodyInterface.h
Normal 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_
|
||||
24
game/server/NextBot/NextBotComponentInterface.cpp
Normal file
24
game/server/NextBot/NextBotComponentInterface.cpp
Normal 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 );
|
||||
}
|
||||
|
||||
|
||||
98
game/server/NextBot/NextBotComponentInterface.h
Normal file
98
game/server/NextBot/NextBotComponentInterface.h
Normal 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_
|
||||
102
game/server/NextBot/NextBotContextualQueryInterface.h
Normal file
102
game/server/NextBot/NextBotContextualQueryInterface.h
Normal 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_
|
||||
23
game/server/NextBot/NextBotDebug.h
Normal file
23
game/server/NextBot/NextBotDebug.h
Normal 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
|
||||
550
game/server/NextBot/NextBotEventResponderInterface.h
Normal file
550
game/server/NextBot/NextBotEventResponderInterface.h
Normal 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_
|
||||
1442
game/server/NextBot/NextBotGroundLocomotion.cpp
Normal file
1442
game/server/NextBot/NextBotGroundLocomotion.cpp
Normal file
File diff suppressed because it is too large
Load Diff
274
game/server/NextBot/NextBotGroundLocomotion.h
Normal file
274
game/server/NextBot/NextBotGroundLocomotion.h
Normal 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
|
||||
|
||||
34
game/server/NextBot/NextBotHearingInterface.h
Normal file
34
game/server/NextBot/NextBotHearingInterface.h
Normal 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_
|
||||
91
game/server/NextBot/NextBotIntentionInterface.cpp
Normal file
91
game/server/NextBot/NextBotIntentionInterface.cpp
Normal 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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
211
game/server/NextBot/NextBotIntentionInterface.h
Normal file
211
game/server/NextBot/NextBotIntentionInterface.h
Normal 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_
|
||||
537
game/server/NextBot/NextBotInterface.cpp
Normal file
537
game/server/NextBot/NextBotInterface.cpp
Normal file
@@ -0,0 +1,537 @@
|
||||
// NextBotInterface.cpp
|
||||
// Implentation of system methods for NextBot interface
|
||||
// Author: Michael Booth, May 2006
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
|
||||
#include "cbase.h"
|
||||
|
||||
#include "props.h"
|
||||
#include "fmtstr.h"
|
||||
#include "team.h"
|
||||
|
||||
#include "NextBotInterface.h"
|
||||
#include "NextBotBodyInterface.h"
|
||||
#include "NextBotManager.h"
|
||||
|
||||
#include "tier0/vprof.h"
|
||||
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
// development only, off by default for 360
|
||||
ConVar NextBotDebugHistory( "nb_debug_history", IsX360() ? "0" : "1", FCVAR_CHEAT, "If true, each bot keeps a history of debug output in memory" );
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------------
|
||||
INextBot::INextBot( void ) : m_debugHistory( MAX_NEXTBOT_DEBUG_HISTORY, 0 ) // CUtlVector: grow to max length, alloc 0 initially
|
||||
{
|
||||
m_tickLastUpdate = -999;
|
||||
m_id = -1;
|
||||
m_componentList = NULL;
|
||||
m_debugDisplayLine = 0;
|
||||
|
||||
m_immobileTimer.Invalidate();
|
||||
m_immobileCheckTimer.Invalidate();
|
||||
m_immobileAnchor = vec3_origin;
|
||||
|
||||
m_currentPath = NULL;
|
||||
|
||||
// register with the manager
|
||||
m_id = TheNextBots().Register( this );
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------------
|
||||
INextBot::~INextBot()
|
||||
{
|
||||
ResetDebugHistory();
|
||||
|
||||
// tell the manager we're gone
|
||||
TheNextBots().UnRegister( this );
|
||||
|
||||
// delete Intention first, since destruction of Actions may access other components
|
||||
if ( m_baseIntention )
|
||||
delete m_baseIntention;
|
||||
|
||||
if ( m_baseLocomotion )
|
||||
delete m_baseLocomotion;
|
||||
|
||||
if ( m_baseBody )
|
||||
delete m_baseBody;
|
||||
|
||||
if ( m_baseVision )
|
||||
delete m_baseVision;
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------------
|
||||
void INextBot::Reset( void )
|
||||
{
|
||||
m_tickLastUpdate = -999;
|
||||
m_debugType = 0;
|
||||
m_debugDisplayLine = 0;
|
||||
|
||||
m_immobileTimer.Invalidate();
|
||||
m_immobileCheckTimer.Invalidate();
|
||||
m_immobileAnchor = vec3_origin;
|
||||
|
||||
for( INextBotComponent *comp = m_componentList; comp; comp = comp->m_nextComponent )
|
||||
{
|
||||
comp->Reset();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------------
|
||||
void INextBot::ResetDebugHistory( void )
|
||||
{
|
||||
for ( int i=0; i<m_debugHistory.Count(); ++i )
|
||||
{
|
||||
delete m_debugHistory[i];
|
||||
}
|
||||
|
||||
m_debugHistory.RemoveAll();
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------------
|
||||
bool INextBot::BeginUpdate()
|
||||
{
|
||||
if ( TheNextBots().ShouldUpdate( this ) )
|
||||
{
|
||||
TheNextBots().NotifyBeginUpdate( this );
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------------
|
||||
void INextBot::EndUpdate( void )
|
||||
{
|
||||
TheNextBots().NotifyEndUpdate( this );
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------------
|
||||
void INextBot::Update( void )
|
||||
{
|
||||
VPROF_BUDGET( "INextBot::Update", "NextBot" );
|
||||
|
||||
m_debugDisplayLine = 0;
|
||||
|
||||
if ( IsDebugging( NEXTBOT_DEBUG_ALL ) )
|
||||
{
|
||||
CFmtStr msg;
|
||||
DisplayDebugText( msg.sprintf( "#%d", GetEntity()->entindex() ) );
|
||||
}
|
||||
|
||||
UpdateImmobileStatus();
|
||||
|
||||
// update all components
|
||||
for( INextBotComponent *comp = m_componentList; comp; comp = comp->m_nextComponent )
|
||||
{
|
||||
if ( comp->ComputeUpdateInterval() )
|
||||
{
|
||||
comp->Update();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------------
|
||||
void INextBot::Upkeep( void )
|
||||
{
|
||||
VPROF_BUDGET( "INextBot::Upkeep", "NextBot" );
|
||||
|
||||
// do upkeep for all components
|
||||
for( INextBotComponent *comp = m_componentList; comp; comp = comp->m_nextComponent )
|
||||
{
|
||||
comp->Upkeep();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------------
|
||||
bool INextBot::SetPosition( const Vector &pos )
|
||||
{
|
||||
IBody *body = GetBodyInterface();
|
||||
if (body)
|
||||
{
|
||||
return body->SetPosition( pos );
|
||||
}
|
||||
|
||||
// fall back to setting raw entity position
|
||||
GetEntity()->SetAbsOrigin( pos );
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------------
|
||||
const Vector &INextBot::GetPosition( void ) const
|
||||
{
|
||||
return const_cast< INextBot * >( this )->GetEntity()->GetAbsOrigin();
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Return true if given actor is our enemy
|
||||
*/
|
||||
bool INextBot::IsEnemy( const CBaseEntity *them ) const
|
||||
{
|
||||
if ( them == NULL )
|
||||
return false;
|
||||
|
||||
// this is not strictly correct, as spectators are not enemies
|
||||
return const_cast< INextBot * >( this )->GetEntity()->GetTeamNumber() != them->GetTeamNumber();
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Return true if given actor is our friend
|
||||
*/
|
||||
bool INextBot::IsFriend( const CBaseEntity *them ) const
|
||||
{
|
||||
if ( them == NULL )
|
||||
return false;
|
||||
|
||||
return const_cast< INextBot * >( this )->GetEntity()->GetTeamNumber() == them->GetTeamNumber();
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Return true if 'them' is actually me
|
||||
*/
|
||||
bool INextBot::IsSelf( const CBaseEntity *them ) const
|
||||
{
|
||||
if ( them == NULL )
|
||||
return false;
|
||||
|
||||
return const_cast< INextBot * >( this )->GetEntity()->entindex() == them->entindex();
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Components call this to register themselves with the bot that contains them
|
||||
*/
|
||||
void INextBot::RegisterComponent( INextBotComponent *comp )
|
||||
{
|
||||
// add to head of singly linked list
|
||||
comp->m_nextComponent = m_componentList;
|
||||
m_componentList = comp;
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------------
|
||||
bool INextBot::IsRangeLessThan( CBaseEntity *subject, float range ) const
|
||||
{
|
||||
Vector botPos;
|
||||
CBaseEntity *bot = const_cast< INextBot * >( this )->GetEntity();
|
||||
if ( !bot || !subject )
|
||||
return 0.0f;
|
||||
|
||||
bot->CollisionProp()->CalcNearestPoint( subject->WorldSpaceCenter(), &botPos );
|
||||
float computedRange = subject->CollisionProp()->CalcDistanceFromPoint( botPos );
|
||||
return computedRange < range;
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------------
|
||||
bool INextBot::IsRangeLessThan( const Vector &pos, float range ) const
|
||||
{
|
||||
Vector to = pos - GetPosition();
|
||||
return to.IsLengthLessThan( range );
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------------
|
||||
bool INextBot::IsRangeGreaterThan( CBaseEntity *subject, float range ) const
|
||||
{
|
||||
Vector botPos;
|
||||
CBaseEntity *bot = const_cast< INextBot * >( this )->GetEntity();
|
||||
if ( !bot || !subject )
|
||||
return true;
|
||||
|
||||
bot->CollisionProp()->CalcNearestPoint( subject->WorldSpaceCenter(), &botPos );
|
||||
float computedRange = subject->CollisionProp()->CalcDistanceFromPoint( botPos );
|
||||
return computedRange > range;
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------------
|
||||
bool INextBot::IsRangeGreaterThan( const Vector &pos, float range ) const
|
||||
{
|
||||
Vector to = pos - GetPosition();
|
||||
return to.IsLengthGreaterThan( range );
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------------
|
||||
float INextBot::GetRangeTo( CBaseEntity *subject ) const
|
||||
{
|
||||
Vector botPos;
|
||||
CBaseEntity *bot = const_cast< INextBot * >( this )->GetEntity();
|
||||
if ( !bot || !subject )
|
||||
return 0.0f;
|
||||
|
||||
bot->CollisionProp()->CalcNearestPoint( subject->WorldSpaceCenter(), &botPos );
|
||||
float computedRange = subject->CollisionProp()->CalcDistanceFromPoint( botPos );
|
||||
return computedRange;
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------------
|
||||
float INextBot::GetRangeTo( const Vector &pos ) const
|
||||
{
|
||||
Vector to = pos - GetPosition();
|
||||
return to.Length();
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------------
|
||||
float INextBot::GetRangeSquaredTo( CBaseEntity *subject ) const
|
||||
{
|
||||
Vector botPos;
|
||||
CBaseEntity *bot = const_cast< INextBot * >( this )->GetEntity();
|
||||
if ( !bot || !subject )
|
||||
return 0.0f;
|
||||
|
||||
bot->CollisionProp()->CalcNearestPoint( subject->WorldSpaceCenter(), &botPos );
|
||||
float computedRange = subject->CollisionProp()->CalcDistanceFromPoint( botPos );
|
||||
return computedRange * computedRange;
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------------
|
||||
float INextBot::GetRangeSquaredTo( const Vector &pos ) const
|
||||
{
|
||||
Vector to = pos - GetPosition();
|
||||
return to.LengthSqr();
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------------
|
||||
bool INextBot::IsDebugging( unsigned int type ) const
|
||||
{
|
||||
if ( TheNextBots().IsDebugging( type ) )
|
||||
{
|
||||
return TheNextBots().IsDebugFilterMatch( this );
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Return the name of this bot for debugging purposes
|
||||
*/
|
||||
const char *INextBot::GetDebugIdentifier( void ) const
|
||||
{
|
||||
const int nameSize = 256;
|
||||
static char name[ nameSize ];
|
||||
|
||||
Q_snprintf( name, nameSize, "%s(#%d)", const_cast< INextBot * >( this )->GetEntity()->GetClassname(), const_cast< INextBot * >( this )->GetEntity()->entindex() );
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Return true if we match the given debug symbol
|
||||
*/
|
||||
bool INextBot::IsDebugFilterMatch( const char *name ) const
|
||||
{
|
||||
// compare debug identifier
|
||||
if ( !Q_strnicmp( name, GetDebugIdentifier(), Q_strlen( name ) ) )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// compare team name
|
||||
CTeam *team = GetEntity()->GetTeam();
|
||||
if ( team && !Q_strnicmp( name, team->GetName(), Q_strlen( name ) ) )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* There are some things we never want to climb on
|
||||
*/
|
||||
bool INextBot::IsAbleToClimbOnto( const CBaseEntity *object ) const
|
||||
{
|
||||
if ( object == NULL || !const_cast<CBaseEntity *>(object)->IsAIWalkable() )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// never climb onto doors
|
||||
if ( FClassnameIs( const_cast< CBaseEntity * >( object ), "prop_door*" ) || FClassnameIs( const_cast< CBaseEntity * >( object ), "func_door*" ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// ok to climb on this object
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Can we break this object
|
||||
*/
|
||||
bool INextBot::IsAbleToBreak( const CBaseEntity *object ) const
|
||||
{
|
||||
if ( object && object->m_takedamage == DAMAGE_YES )
|
||||
{
|
||||
if ( FClassnameIs( const_cast< CBaseEntity * >( object ), "func_breakable" ) &&
|
||||
object->GetHealth() )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( FClassnameIs( const_cast< CBaseEntity * >( object ), "func_breakable_surf" ) )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( dynamic_cast< const CBreakableProp * >( object ) != NULL )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------
|
||||
void INextBot::DisplayDebugText( const char *text ) const
|
||||
{
|
||||
const_cast< INextBot * >( this )->GetEntity()->EntityText( m_debugDisplayLine++, text, 0.1 );
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------
|
||||
void INextBot::DebugConColorMsg( NextBotDebugType debugType, const Color &color, const char *fmt, ... )
|
||||
{
|
||||
bool isDataFormatted = false;
|
||||
|
||||
va_list argptr;
|
||||
char data[ MAX_NEXTBOT_DEBUG_LINE_LENGTH ];
|
||||
|
||||
if ( developer.GetBool() && IsDebugging( debugType ) )
|
||||
{
|
||||
va_start(argptr, fmt);
|
||||
Q_vsnprintf(data, sizeof( data ), fmt, argptr);
|
||||
va_end(argptr);
|
||||
isDataFormatted = true;
|
||||
|
||||
ConColorMsg( color, "%s", data );
|
||||
}
|
||||
|
||||
if ( !NextBotDebugHistory.GetBool() )
|
||||
{
|
||||
if ( m_debugHistory.Count() )
|
||||
{
|
||||
ResetDebugHistory();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't bother with event data - it's spammy enough to overshadow everything else.
|
||||
if ( debugType == NEXTBOT_EVENTS )
|
||||
return;
|
||||
|
||||
if ( !isDataFormatted )
|
||||
{
|
||||
va_start(argptr, fmt);
|
||||
Q_vsnprintf(data, sizeof( data ), fmt, argptr);
|
||||
va_end(argptr);
|
||||
isDataFormatted = true;
|
||||
}
|
||||
|
||||
int lastLine = m_debugHistory.Count() - 1;
|
||||
if ( lastLine >= 0 )
|
||||
{
|
||||
NextBotDebugLineType *line = m_debugHistory[lastLine];
|
||||
if ( line->debugType == debugType && V_strstr( line->data, "\n" ) == NULL )
|
||||
{
|
||||
// append onto previous line
|
||||
V_strncat( line->data, data, MAX_NEXTBOT_DEBUG_LINE_LENGTH );
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Prune out an old line if needed, keeping a pointer to re-use the memory
|
||||
NextBotDebugLineType *line = NULL;
|
||||
if ( m_debugHistory.Count() == MAX_NEXTBOT_DEBUG_HISTORY )
|
||||
{
|
||||
line = m_debugHistory[0];
|
||||
m_debugHistory.Remove( 0 );
|
||||
}
|
||||
|
||||
// Add to debug history
|
||||
if ( !line )
|
||||
{
|
||||
line = new NextBotDebugLineType;
|
||||
}
|
||||
line->debugType = debugType;
|
||||
V_strncpy( line->data, data, MAX_NEXTBOT_DEBUG_LINE_LENGTH );
|
||||
m_debugHistory.AddToTail( line );
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------
|
||||
// build a vector of debug history of the given types
|
||||
void INextBot::GetDebugHistory( unsigned int type, CUtlVector< const NextBotDebugLineType * > *lines ) const
|
||||
{
|
||||
if ( !lines )
|
||||
return;
|
||||
|
||||
lines->RemoveAll();
|
||||
|
||||
for ( int i=0; i<m_debugHistory.Count(); ++i )
|
||||
{
|
||||
NextBotDebugLineType *line = m_debugHistory[i];
|
||||
if ( line->debugType & type )
|
||||
{
|
||||
lines->AddToTail( line );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------
|
||||
void INextBot::UpdateImmobileStatus( void )
|
||||
{
|
||||
if ( m_immobileCheckTimer.IsElapsed() )
|
||||
{
|
||||
m_immobileCheckTimer.Start( 1.0f );
|
||||
|
||||
// if we haven't moved farther than this in 1 second, we're immobile
|
||||
if ( ( GetEntity()->GetAbsOrigin() - m_immobileAnchor ).IsLengthGreaterThan( GetImmobileSpeedThreshold() ) )
|
||||
{
|
||||
// moved far enough, not immobile
|
||||
m_immobileAnchor = GetEntity()->GetAbsOrigin();
|
||||
m_immobileTimer.Invalidate();
|
||||
}
|
||||
else
|
||||
{
|
||||
// haven't escaped our anchor - we are immobile
|
||||
if ( !m_immobileTimer.HasStarted() )
|
||||
{
|
||||
m_immobileTimer.Start();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
302
game/server/NextBot/NextBotInterface.h
Normal file
302
game/server/NextBot/NextBotInterface.h
Normal file
@@ -0,0 +1,302 @@
|
||||
// NextBotInterface.h
|
||||
// Interface for NextBot
|
||||
// Author: Michael Booth, May 2006
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
|
||||
#ifndef _NEXT_BOT_INTERFACE_H_
|
||||
#define _NEXT_BOT_INTERFACE_H_
|
||||
|
||||
#include "NextBot/NextBotKnownEntity.h"
|
||||
#include "NextBotComponentInterface.h"
|
||||
#include "NextBotLocomotionInterface.h"
|
||||
#include "NextBotBodyInterface.h"
|
||||
#include "NextBotIntentionInterface.h"
|
||||
#include "NextBotVisionInterface.h"
|
||||
#include "NextBotDebug.h"
|
||||
|
||||
class CBaseCombatCharacter;
|
||||
class PathFollower;
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* A general purpose filter interface for various bot systems
|
||||
*/
|
||||
class INextBotFilter
|
||||
{
|
||||
public:
|
||||
virtual bool IsSelected( const CBaseEntity *candidate ) const = 0; // return true if this entity passes the filter
|
||||
};
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------------
|
||||
class INextBot : public INextBotEventResponder
|
||||
{
|
||||
public:
|
||||
INextBot( void );
|
||||
virtual ~INextBot();
|
||||
|
||||
int GetBotId() const;
|
||||
|
||||
bool BeginUpdate();
|
||||
void EndUpdate();
|
||||
|
||||
virtual void Reset( void ); // (EXTEND) reset to initial state
|
||||
virtual void Update( void ); // (EXTEND) update internal state
|
||||
virtual void Upkeep( void ); // (EXTEND) lightweight update guaranteed to occur every server tick
|
||||
|
||||
void FlagForUpdate( bool b = true );
|
||||
bool IsFlaggedForUpdate();
|
||||
int GetTickLastUpdate() const;
|
||||
void SetTickLastUpdate( int );
|
||||
|
||||
virtual bool IsRemovedOnReset( void ) const { return true; } // remove this bot when the NextBot manager calls Reset
|
||||
|
||||
virtual CBaseCombatCharacter *GetEntity( void ) const = 0;
|
||||
virtual class NextBotCombatCharacter *GetNextBotCombatCharacter( void ) const { return NULL; }
|
||||
|
||||
#ifdef TERROR
|
||||
virtual class SurvivorBot *MySurvivorBotPointer() const { return NULL; }
|
||||
#endif
|
||||
|
||||
// interfaces are never NULL - return base no-op interfaces at a minimum
|
||||
virtual ILocomotion * GetLocomotionInterface( void ) const;
|
||||
virtual IBody * GetBodyInterface( void ) const;
|
||||
virtual IIntention * GetIntentionInterface( void ) const;
|
||||
virtual IVision * GetVisionInterface( void ) const;
|
||||
|
||||
/**
|
||||
* Attempt to change the bot's position. Return true if successful.
|
||||
*/
|
||||
virtual bool SetPosition( const Vector &pos );
|
||||
virtual const Vector &GetPosition( void ) const; // get the global position of the bot
|
||||
|
||||
/**
|
||||
* Friend/enemy/neutral queries
|
||||
*/
|
||||
virtual bool IsEnemy( const CBaseEntity *them ) const; // return true if given entity is our enemy
|
||||
virtual bool IsFriend( const CBaseEntity *them ) const; // return true if given entity is our friend
|
||||
virtual bool IsSelf( const CBaseEntity *them ) const; // return true if 'them' is actually me
|
||||
|
||||
/**
|
||||
* Can we climb onto this entity?
|
||||
*/
|
||||
virtual bool IsAbleToClimbOnto( const CBaseEntity *object ) const;
|
||||
|
||||
/**
|
||||
* Can we break this entity?
|
||||
*/
|
||||
virtual bool IsAbleToBreak( const CBaseEntity *object ) const;
|
||||
|
||||
/**
|
||||
* Sometimes we want to pass through other NextBots. OnContact() will always
|
||||
* be invoked, but collision resolution can be skipped if this
|
||||
* method returns false.
|
||||
*/
|
||||
virtual bool IsAbleToBlockMovementOf( const INextBot *botInMotion ) const { return true; }
|
||||
|
||||
/**
|
||||
* Should we ever care about noticing physical contact with this entity?
|
||||
*/
|
||||
virtual bool ShouldTouch( const CBaseEntity *object ) const { return true; }
|
||||
|
||||
/**
|
||||
* This immobile system is used to track the global state of "am I actually moving or not".
|
||||
* The OnStuck() event is only emitted when following a path, and paths can be recomputed, etc.
|
||||
*/
|
||||
virtual bool IsImmobile( void ) const; // return true if we haven't moved in awhile
|
||||
virtual float GetImmobileDuration( void ) const; // how long have we been immobile
|
||||
virtual void ClearImmobileStatus( void );
|
||||
virtual float GetImmobileSpeedThreshold( void ) const; // return units/second below which this actor is considered "immobile"
|
||||
|
||||
/**
|
||||
* Get the last PathFollower we followed. This method gives other interfaces a
|
||||
* single accessor to the most recent Path being followed by the myriad of
|
||||
* different PathFollowers used in the various behaviors the bot may be doing.
|
||||
*/
|
||||
virtual const PathFollower *GetCurrentPath( void ) const;
|
||||
virtual void SetCurrentPath( const PathFollower *path );
|
||||
virtual void NotifyPathDestruction( const PathFollower *path ); // this PathFollower is going away, which may or may not be ours
|
||||
|
||||
// between distance utility methods
|
||||
virtual bool IsRangeLessThan( CBaseEntity *subject, float range ) const;
|
||||
virtual bool IsRangeLessThan( const Vector &pos, float range ) const;
|
||||
virtual bool IsRangeGreaterThan( CBaseEntity *subject, float range ) const;
|
||||
virtual bool IsRangeGreaterThan( const Vector &pos, float range ) const;
|
||||
virtual float GetRangeTo( CBaseEntity *subject ) const;
|
||||
virtual float GetRangeTo( const Vector &pos ) const;
|
||||
virtual float GetRangeSquaredTo( CBaseEntity *subject ) const;
|
||||
virtual float GetRangeSquaredTo( const Vector &pos ) const;
|
||||
|
||||
// event propagation
|
||||
virtual INextBotEventResponder *FirstContainedResponder( void ) const;
|
||||
virtual INextBotEventResponder *NextContainedResponder( INextBotEventResponder *current ) const;
|
||||
|
||||
virtual bool IsDebugging( unsigned int type ) const; // return true if this bot is debugging any of the given types
|
||||
virtual const char *GetDebugIdentifier( void ) const; // return the name of this bot for debugging purposes
|
||||
virtual bool IsDebugFilterMatch( const char *name ) const; // return true if we match the given debug symbol
|
||||
virtual void DisplayDebugText( const char *text ) const; // show a line of text on the bot in the world
|
||||
void DebugConColorMsg( NextBotDebugType debugType, const Color &color, PRINTF_FORMAT_STRING const char *fmt, ... );
|
||||
|
||||
enum {
|
||||
MAX_NEXTBOT_DEBUG_HISTORY = 100,
|
||||
MAX_NEXTBOT_DEBUG_LINE_LENGTH = 256,
|
||||
};
|
||||
struct NextBotDebugLineType
|
||||
{
|
||||
NextBotDebugType debugType;
|
||||
char data[ MAX_NEXTBOT_DEBUG_LINE_LENGTH ];
|
||||
};
|
||||
void GetDebugHistory( unsigned int type, CUtlVector< const NextBotDebugLineType * > *lines ) const; // build a vector of debug history of the given types
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
|
||||
private:
|
||||
friend class INextBotComponent;
|
||||
void RegisterComponent( INextBotComponent *comp ); // components call this to register themselves with the bot that contains them
|
||||
INextBotComponent *m_componentList; // the first component
|
||||
|
||||
const PathFollower *m_currentPath; // the path we most recently followed
|
||||
|
||||
int m_id;
|
||||
bool m_bFlaggedForUpdate;
|
||||
int m_tickLastUpdate;
|
||||
|
||||
unsigned int m_debugType;
|
||||
mutable int m_debugDisplayLine;
|
||||
|
||||
Vector m_immobileAnchor;
|
||||
CountdownTimer m_immobileCheckTimer;
|
||||
IntervalTimer m_immobileTimer;
|
||||
void UpdateImmobileStatus( void );
|
||||
|
||||
mutable ILocomotion *m_baseLocomotion;
|
||||
mutable IBody *m_baseBody;
|
||||
mutable IIntention *m_baseIntention;
|
||||
mutable IVision *m_baseVision;
|
||||
//mutable IAttention *m_baseAttention;
|
||||
|
||||
// Debugging info
|
||||
void ResetDebugHistory( void );
|
||||
CUtlVector< NextBotDebugLineType * > m_debugHistory;
|
||||
};
|
||||
|
||||
|
||||
inline const PathFollower *INextBot::GetCurrentPath( void ) const
|
||||
{
|
||||
return m_currentPath;
|
||||
}
|
||||
|
||||
inline void INextBot::SetCurrentPath( const PathFollower *path )
|
||||
{
|
||||
m_currentPath = path;
|
||||
}
|
||||
|
||||
inline void INextBot::NotifyPathDestruction( const PathFollower *path )
|
||||
{
|
||||
if ( m_currentPath == path )
|
||||
m_currentPath = NULL;
|
||||
}
|
||||
|
||||
|
||||
inline ILocomotion *INextBot::GetLocomotionInterface( void ) const
|
||||
{
|
||||
// these base interfaces are lazy-allocated (instead of being fully instanced classes) for two reasons:
|
||||
// 1) so the memory is only used if needed
|
||||
// 2) so the component is registered properly
|
||||
if ( m_baseLocomotion == NULL )
|
||||
{
|
||||
m_baseLocomotion = new ILocomotion( const_cast< INextBot * >( this ) );
|
||||
}
|
||||
|
||||
return m_baseLocomotion;
|
||||
}
|
||||
|
||||
inline IBody *INextBot::GetBodyInterface( void ) const
|
||||
{
|
||||
if ( m_baseBody == NULL )
|
||||
{
|
||||
m_baseBody = new IBody( const_cast< INextBot * >( this ) );
|
||||
}
|
||||
|
||||
return m_baseBody;
|
||||
}
|
||||
|
||||
inline IIntention *INextBot::GetIntentionInterface( void ) const
|
||||
{
|
||||
if ( m_baseIntention == NULL )
|
||||
{
|
||||
m_baseIntention = new IIntention( const_cast< INextBot * >( this ) );
|
||||
}
|
||||
|
||||
return m_baseIntention;
|
||||
}
|
||||
|
||||
inline IVision *INextBot::GetVisionInterface( void ) const
|
||||
{
|
||||
if ( m_baseVision == NULL )
|
||||
{
|
||||
m_baseVision = new IVision( const_cast< INextBot * >( this ) );
|
||||
}
|
||||
|
||||
return m_baseVision;
|
||||
}
|
||||
|
||||
inline int INextBot::GetBotId() const
|
||||
{
|
||||
return m_id;
|
||||
}
|
||||
|
||||
inline void INextBot::FlagForUpdate( bool b )
|
||||
{
|
||||
m_bFlaggedForUpdate = b;
|
||||
}
|
||||
|
||||
inline bool INextBot::IsFlaggedForUpdate()
|
||||
{
|
||||
return m_bFlaggedForUpdate;
|
||||
}
|
||||
|
||||
inline int INextBot::GetTickLastUpdate() const
|
||||
{
|
||||
return m_tickLastUpdate;
|
||||
}
|
||||
|
||||
inline void INextBot::SetTickLastUpdate( int tick )
|
||||
{
|
||||
m_tickLastUpdate = tick;
|
||||
}
|
||||
|
||||
inline bool INextBot::IsImmobile( void ) const
|
||||
{
|
||||
return m_immobileTimer.HasStarted();
|
||||
}
|
||||
|
||||
inline float INextBot::GetImmobileDuration( void ) const
|
||||
{
|
||||
return m_immobileTimer.GetElapsedTime();
|
||||
}
|
||||
|
||||
inline void INextBot::ClearImmobileStatus( void )
|
||||
{
|
||||
m_immobileTimer.Invalidate();
|
||||
m_immobileAnchor = GetEntity()->GetAbsOrigin();
|
||||
}
|
||||
|
||||
inline float INextBot::GetImmobileSpeedThreshold( void ) const
|
||||
{
|
||||
return 30.0f;
|
||||
}
|
||||
|
||||
inline INextBotEventResponder *INextBot::FirstContainedResponder( void ) const
|
||||
{
|
||||
return m_componentList;
|
||||
}
|
||||
|
||||
|
||||
inline INextBotEventResponder *INextBot::NextContainedResponder( INextBotEventResponder *current ) const
|
||||
{
|
||||
return static_cast< INextBotComponent * >( current )->m_nextComponent;
|
||||
}
|
||||
|
||||
|
||||
#endif // _NEXT_BOT_INTERFACE_H_
|
||||
175
game/server/NextBot/NextBotKnownEntity.h
Normal file
175
game/server/NextBot/NextBotKnownEntity.h
Normal 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
|
||||
520
game/server/NextBot/NextBotLocomotionInterface.cpp
Normal file
520
game/server/NextBot/NextBotLocomotionInterface.cpp
Normal 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();
|
||||
}
|
||||
|
||||
335
game/server/NextBot/NextBotLocomotionInterface.h
Normal file
335
game/server/NextBot/NextBotLocomotionInterface.h
Normal 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_
|
||||
|
||||
892
game/server/NextBot/NextBotManager.cpp
Normal file
892
game/server/NextBot/NextBotManager.cpp
Normal file
@@ -0,0 +1,892 @@
|
||||
// NextBotManager.cpp
|
||||
// Author: Michael Booth, May 2006
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
|
||||
#include "cbase.h"
|
||||
|
||||
#include "NextBotManager.h"
|
||||
#include "NextBotInterface.h"
|
||||
|
||||
#ifdef TERROR
|
||||
#include "ZombieBot/Infected/Infected.h"
|
||||
#include "ZombieBot/Witch/Witch.h"
|
||||
#include "ZombieManager.h"
|
||||
#endif
|
||||
|
||||
#include "SharedFunctorUtils.h"
|
||||
//#include "../../common/blackbox_helper.h"
|
||||
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
extern ConVar ZombieMobMaxSize;
|
||||
|
||||
ConVar nb_update_frequency( "nb_update_frequency", ".1", FCVAR_CHEAT );
|
||||
ConVar nb_update_framelimit( "nb_update_framelimit", ( IsDebug() ) ? "30" : "15", FCVAR_CHEAT );
|
||||
ConVar nb_update_maxslide( "nb_update_maxslide", "2", FCVAR_CHEAT );
|
||||
ConVar nb_update_debug( "nb_update_debug", "0", FCVAR_CHEAT );
|
||||
|
||||
//---------------------------------------------------------------------------------------------
|
||||
//---------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Singleton accessor.
|
||||
* By returning a reference, we guarantee construction of the
|
||||
* instance before its first use.
|
||||
*/
|
||||
NextBotManager &TheNextBots( void )
|
||||
{
|
||||
if ( NextBotManager::GetInstance() )
|
||||
{
|
||||
return *NextBotManager::GetInstance();
|
||||
}
|
||||
else
|
||||
{
|
||||
static NextBotManager manager;
|
||||
NextBotManager::SetInstance( &manager );
|
||||
return manager;
|
||||
}
|
||||
}
|
||||
|
||||
NextBotManager* NextBotManager::sInstance = NULL;
|
||||
|
||||
//---------------------------------------------------------------------------------------------
|
||||
//---------------------------------------------------------------------------------------------
|
||||
static const char *debugTypeName[] =
|
||||
{
|
||||
"BEHAVIOR",
|
||||
"LOOK_AT",
|
||||
"PATH",
|
||||
"ANIMATION",
|
||||
"LOCOMOTION",
|
||||
"VISION",
|
||||
"HEARING",
|
||||
"EVENTS",
|
||||
"ERRORS",
|
||||
NULL
|
||||
};
|
||||
|
||||
|
||||
static void CC_SetDebug( const CCommand &args )
|
||||
{
|
||||
if ( args.ArgC() < 2 )
|
||||
{
|
||||
Msg( "Debugging stopped\n" );
|
||||
TheNextBots().SetDebugTypes( NEXTBOT_DEBUG_NONE );
|
||||
return;
|
||||
}
|
||||
|
||||
int debugType = 0;
|
||||
|
||||
for( int i=1; i<args.ArgC(); ++i )
|
||||
{
|
||||
int type;
|
||||
for( type = 0; debugTypeName[ type ]; ++type )
|
||||
{
|
||||
const char *token = args[i];
|
||||
|
||||
// special token that means "all"
|
||||
if ( token[0] == '*' )
|
||||
{
|
||||
debugType = NEXTBOT_DEBUG_ALL;
|
||||
break;
|
||||
}
|
||||
|
||||
if ( !Q_strnicmp( args[i], debugTypeName[ type ], Q_strlen( args[1] ) ) )
|
||||
{
|
||||
debugType |= ( 1 << type );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( !debugTypeName[ type ] )
|
||||
{
|
||||
Msg( "Invalid debug type '%s'\n", args[i] );
|
||||
}
|
||||
}
|
||||
|
||||
// enable debugging
|
||||
TheNextBots().SetDebugTypes( ( NextBotDebugType ) debugType );
|
||||
}
|
||||
static ConCommand SetDebug( "nb_debug", CC_SetDebug, "Debug NextBots. Categories are: BEHAVIOR, LOOK_AT, PATH, ANIMATION, LOCOMOTION, VISION, HEARING, EVENTS, ERRORS.", FCVAR_CHEAT );
|
||||
|
||||
//---------------------------------------------------------------------------------------------
|
||||
static void CC_SetDebugFilter( const CCommand &args )
|
||||
{
|
||||
if ( args.ArgC() < 2 )
|
||||
{
|
||||
Msg( "Debug filter cleared.\n" );
|
||||
TheNextBots().DebugFilterClear();
|
||||
return;
|
||||
}
|
||||
|
||||
for( int i=1; i<args.ArgC(); ++i )
|
||||
{
|
||||
int index = Q_atoi( args[i] );
|
||||
if ( index > 0 )
|
||||
{
|
||||
TheNextBots().DebugFilterAdd( index );
|
||||
}
|
||||
else
|
||||
{
|
||||
TheNextBots().DebugFilterAdd( args[i] );
|
||||
}
|
||||
}
|
||||
}
|
||||
static ConCommand SetDebugFilter( "nb_debug_filter", CC_SetDebugFilter, "Add items to the NextBot debug filter. Items can be entindexes or part of the indentifier of one or more bots.", FCVAR_CHEAT );
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------------------------
|
||||
class Selector
|
||||
{
|
||||
public:
|
||||
Selector( CBasePlayer *player, bool useLOS )
|
||||
{
|
||||
m_player = player;
|
||||
player->EyeVectors( &m_forward );
|
||||
|
||||
m_pick = NULL;
|
||||
m_pickRange = 99999999999999.9f;
|
||||
m_useLOS = useLOS;
|
||||
}
|
||||
|
||||
bool operator() ( INextBot *bot )
|
||||
{
|
||||
CBaseCombatCharacter *botEntity = bot->GetEntity();
|
||||
if ( botEntity->IsAlive() )
|
||||
{
|
||||
Vector to = botEntity->WorldSpaceCenter() - m_player->EyePosition();
|
||||
float range = to.NormalizeInPlace();
|
||||
|
||||
if ( DotProduct( m_forward, to ) > 0.98f && range < m_pickRange )
|
||||
{
|
||||
if ( !m_useLOS || m_player->IsAbleToSee( botEntity, CBaseCombatCharacter::DISREGARD_FOV ) )
|
||||
{
|
||||
m_pick = bot;
|
||||
m_pickRange = range;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
CBasePlayer *m_player;
|
||||
Vector m_forward;
|
||||
INextBot *m_pick;
|
||||
float m_pickRange;
|
||||
bool m_useLOS;
|
||||
};
|
||||
|
||||
static void CC_SelectBot( const CCommand &args )
|
||||
{
|
||||
CBasePlayer *player = UTIL_GetListenServerHost();
|
||||
if ( player )
|
||||
{
|
||||
Selector select( player, false );
|
||||
TheNextBots().ForEachBot( select );
|
||||
|
||||
TheNextBots().Select( select.m_pick );
|
||||
|
||||
if ( select.m_pick )
|
||||
{
|
||||
NDebugOverlay::Circle( select.m_pick->GetLocomotionInterface()->GetFeet() + Vector( 0, 0, 5 ), Vector( 1, 0, 0 ), Vector( 0, -1, 0 ), 25.0f, 0, 255, 0, 255, false, 1.0f );
|
||||
}
|
||||
}
|
||||
}
|
||||
static ConCommand SelectBot( "nb_select", CC_SelectBot, "Select the bot you are aiming at for further debug operations.", FCVAR_CHEAT );
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------------------------
|
||||
static void CC_ForceLookAt( const CCommand &args )
|
||||
{
|
||||
CBasePlayer *player = UTIL_GetListenServerHost();
|
||||
INextBot *pick = TheNextBots().GetSelected();
|
||||
|
||||
if ( player && pick )
|
||||
{
|
||||
pick->GetBodyInterface()->AimHeadTowards( player, IBody::CRITICAL, 9999999.9f, NULL, "Aim forced" );
|
||||
}
|
||||
}
|
||||
static ConCommand ForceLookAt( "nb_force_look_at", CC_ForceLookAt, "Force selected bot to look at the local player's position", FCVAR_CHEAT );
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------
|
||||
void CC_WarpSelectedHere( const CCommand &args )
|
||||
{
|
||||
CBasePlayer *me = dynamic_cast< CBasePlayer * >( UTIL_GetCommandClient() );
|
||||
INextBot *pick = TheNextBots().GetSelected();
|
||||
|
||||
if ( me == NULL || pick == NULL )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Vector forward;
|
||||
me->EyeVectors( &forward );
|
||||
|
||||
trace_t result;
|
||||
UTIL_TraceLine( me->EyePosition(), me->EyePosition() + 999999.9f * forward, MASK_BLOCKLOS_AND_NPCS|CONTENTS_IGNORE_NODRAW_OPAQUE, me, COLLISION_GROUP_NONE, &result );
|
||||
if ( result.DidHit() )
|
||||
{
|
||||
Vector spot = result.endpos + Vector( 0, 0, 10.0f );
|
||||
pick->GetEntity()->Teleport( &spot, &vec3_angle, &vec3_origin );
|
||||
}
|
||||
}
|
||||
static ConCommand WarpSelectedHere( "nb_warp_selected_here", CC_WarpSelectedHere, "Teleport the selected bot to your cursor position", FCVAR_CHEAT );
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------------------------
|
||||
//---------------------------------------------------------------------------------------------
|
||||
NextBotManager::NextBotManager( void )
|
||||
{
|
||||
m_debugType = 0;
|
||||
m_selectedBot = NULL;
|
||||
|
||||
m_iUpdateTickrate = 0;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------
|
||||
NextBotManager::~NextBotManager()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Reset to initial state
|
||||
*/
|
||||
void NextBotManager::Reset( void )
|
||||
{
|
||||
// remove the NextBots that should go away during a reset (they will unregister themselves as they go)
|
||||
int i = m_botList.Head();
|
||||
while ( i != m_botList.InvalidIndex() )
|
||||
{
|
||||
int iNext = m_botList.Next( i );
|
||||
if ( m_botList[i]->IsRemovedOnReset() )
|
||||
{
|
||||
UTIL_Remove( m_botList[i]->GetEntity() );
|
||||
//Assert( !m_botList.IsInList( i ) ); // UTIL_Remove() calls UpdateOnRemove, adds EFL_KILLME, but doesn't delete until the end of the frame
|
||||
}
|
||||
i = iNext;
|
||||
}
|
||||
|
||||
m_selectedBot = NULL;
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------------------------
|
||||
|
||||
inline bool IsDead( INextBot *pBot )
|
||||
{
|
||||
CBaseCombatCharacter *pEntity = pBot->GetEntity();
|
||||
if ( pEntity )
|
||||
{
|
||||
if ( pEntity->IsPlayer() && pEntity->m_lifeState == LIFE_DEAD )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( pEntity->IsMarkedForDeletion() )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( pEntity->m_pfnThink == &CBaseEntity::SUB_Remove )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------
|
||||
|
||||
// Debug stats for update balancing
|
||||
static int g_nRun;
|
||||
static int g_nSlid;
|
||||
static int g_nBlockedSlides;
|
||||
|
||||
void NextBotManager::Update( void )
|
||||
{
|
||||
// do lightweight upkeep every tick
|
||||
for( int u=m_botList.Head(); u != m_botList.InvalidIndex(); u = m_botList.Next( u ) )
|
||||
{
|
||||
m_botList[ u ]->Upkeep();
|
||||
}
|
||||
|
||||
// schedule full updates
|
||||
if ( m_botList.Count() )
|
||||
{
|
||||
static int iCurFrame = -1;
|
||||
if ( iCurFrame != gpGlobals->framecount )
|
||||
{
|
||||
iCurFrame = gpGlobals->framecount;
|
||||
m_SumFrameTime = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Don't run multiple ticks in a frame
|
||||
return;
|
||||
}
|
||||
|
||||
int tickRate = TIME_TO_TICKS( nb_update_frequency.GetFloat() );
|
||||
if ( tickRate < 0 )
|
||||
{
|
||||
tickRate = 0;
|
||||
}
|
||||
|
||||
if ( m_iUpdateTickrate != tickRate )
|
||||
{
|
||||
Msg( "NextBot tickrate changed from %d (%.3fms) to %d (%.3fms)\n", m_iUpdateTickrate, TICKS_TO_TIME( m_iUpdateTickrate ), tickRate, TICKS_TO_TIME( tickRate ) );
|
||||
m_iUpdateTickrate = tickRate;
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
int nScheduled = 0;
|
||||
int nNonResponsive = 0;
|
||||
int nDead = 0;
|
||||
if ( m_iUpdateTickrate > 0 )
|
||||
{
|
||||
INextBot *pBot;
|
||||
|
||||
// Count dead bots, they won't update and balancing calculations should exclude them
|
||||
for( i = m_botList.Head(); i != m_botList.InvalidIndex(); i = m_botList.Next( i ) )
|
||||
{
|
||||
if ( IsDead( m_botList[i] ) )
|
||||
{
|
||||
nDead++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int nTargetToRun = ceilf( (float)( m_botList.Count() - nDead ) / (float)m_iUpdateTickrate );
|
||||
int curtickcount = gpGlobals->tickcount;
|
||||
|
||||
for( i = m_botList.Head(); nTargetToRun && i != m_botList.InvalidIndex(); i = m_botList.Next( i ) )
|
||||
{
|
||||
pBot = m_botList[i];
|
||||
if ( pBot->IsFlaggedForUpdate() )
|
||||
{
|
||||
// Was offered a run last tick but didn't take it, push it back
|
||||
// Leave the flag set so that bot will run right away later, but be ignored
|
||||
// until then
|
||||
nNonResponsive++;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( curtickcount - pBot->GetTickLastUpdate() < m_iUpdateTickrate )
|
||||
{
|
||||
break;
|
||||
}
|
||||
if ( !IsDead( pBot ) )
|
||||
{
|
||||
pBot->FlagForUpdate();
|
||||
nTargetToRun--;
|
||||
nScheduled++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
nScheduled = m_botList.Count();
|
||||
}
|
||||
|
||||
if ( nb_update_debug.GetBool() )
|
||||
{
|
||||
int nIntentionalSliders = 0;
|
||||
if ( m_iUpdateTickrate > 0 )
|
||||
{
|
||||
for( ; i != m_botList.InvalidIndex(); i = m_botList.Next( i ) )
|
||||
{
|
||||
if ( gpGlobals->tickcount - m_botList[i]->GetTickLastUpdate() >= m_iUpdateTickrate )
|
||||
{
|
||||
nIntentionalSliders++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Msg( "Frame %8d/tick %8d: %3d run of %3d, %3d sliders, %3d blocked slides, scheduled %3d for next tick, %3d intentional sliders, %d nonresponsive, %d dead\n", gpGlobals->framecount - 1, gpGlobals->tickcount - 1, g_nRun, m_botList.Count() - nDead, g_nSlid, g_nBlockedSlides, nScheduled, nIntentionalSliders, nNonResponsive, nDead );
|
||||
g_nRun = g_nSlid = g_nBlockedSlides = 0;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------
|
||||
bool NextBotManager::ShouldUpdate( INextBot *bot )
|
||||
{
|
||||
if ( m_iUpdateTickrate < 1 )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
float frameLimit = nb_update_framelimit.GetFloat();
|
||||
float sumFrameTime = 0;
|
||||
if ( bot->IsFlaggedForUpdate() )
|
||||
{
|
||||
bot->FlagForUpdate( false );
|
||||
sumFrameTime = m_SumFrameTime * 1000.0;
|
||||
if ( frameLimit > 0.0f )
|
||||
{
|
||||
if ( sumFrameTime < frameLimit )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if ( nb_update_debug.GetBool() )
|
||||
{
|
||||
Msg( "Frame %8d/tick %8d: frame out of budget (%.2fms > %.2fms)\n", gpGlobals->framecount, gpGlobals->tickcount, sumFrameTime, frameLimit );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int nTicksSlid = ( gpGlobals->tickcount - bot->GetTickLastUpdate() ) - m_iUpdateTickrate;
|
||||
|
||||
if ( nTicksSlid >= nb_update_maxslide.GetInt() )
|
||||
{
|
||||
if ( frameLimit == 0.0 || sumFrameTime < nb_update_framelimit.GetFloat() * 2.0 )
|
||||
{
|
||||
g_nBlockedSlides++;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if ( nb_update_debug.GetBool() )
|
||||
{
|
||||
if ( nTicksSlid > 0 )
|
||||
{
|
||||
g_nSlid++;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------
|
||||
void NextBotManager::NotifyBeginUpdate( INextBot *bot )
|
||||
{
|
||||
if ( nb_update_debug.GetBool() )
|
||||
{
|
||||
g_nRun++;
|
||||
}
|
||||
|
||||
m_botList.Unlink( bot->GetBotId() );
|
||||
m_botList.LinkToTail( bot->GetBotId() );
|
||||
bot->SetTickLastUpdate( gpGlobals->tickcount );
|
||||
|
||||
m_CurUpdateStartTime = Plat_FloatTime();
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------
|
||||
void NextBotManager::NotifyEndUpdate( INextBot *bot )
|
||||
{
|
||||
// This might be a good place to detect a particular bot had spiked [3/14/2008 tom]
|
||||
m_SumFrameTime += Plat_FloatTime() - m_CurUpdateStartTime;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* When the server has changed maps
|
||||
*/
|
||||
void NextBotManager::OnMapLoaded( void )
|
||||
{
|
||||
Reset();
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* When the scenario restarts
|
||||
*/
|
||||
void NextBotManager::OnRoundRestart( void )
|
||||
{
|
||||
Reset();
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------------------------
|
||||
int NextBotManager::Register( INextBot *bot )
|
||||
{
|
||||
return m_botList.AddToHead( bot );
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------------------------
|
||||
void NextBotManager::UnRegister( INextBot *bot )
|
||||
{
|
||||
m_botList.Remove( bot->GetBotId() );
|
||||
|
||||
if ( bot == m_selectedBot)
|
||||
{
|
||||
// we can't access virtual methods because this is called from a destructor, so just clear it
|
||||
m_selectedBot = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------
|
||||
void NextBotManager::OnBeginChangeLevel( void )
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------
|
||||
class NextBotKilledNotifyScan
|
||||
{
|
||||
public:
|
||||
NextBotKilledNotifyScan( CBaseCombatCharacter *victim, const CTakeDamageInfo &info )
|
||||
{
|
||||
m_victim = victim;
|
||||
m_info = info;
|
||||
}
|
||||
|
||||
bool operator() ( INextBot *bot )
|
||||
{
|
||||
if ( bot->GetEntity()->IsAlive() && !bot->IsSelf( m_victim ) )
|
||||
{
|
||||
bot->OnOtherKilled( m_victim, m_info );
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
CBaseCombatCharacter *m_victim;
|
||||
CTakeDamageInfo m_info;
|
||||
};
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* When an actor is killed. Propagate to all NextBots.
|
||||
*/
|
||||
void NextBotManager::OnKilled( CBaseCombatCharacter *victim, const CTakeDamageInfo &info )
|
||||
{
|
||||
NextBotKilledNotifyScan notify( victim, info );
|
||||
TheNextBots().ForEachBot( notify );
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------
|
||||
class NextBotSoundNotifyScan
|
||||
{
|
||||
public:
|
||||
NextBotSoundNotifyScan( CBaseEntity *source, const Vector &pos, KeyValues *keys ) : m_source( source ), m_pos( pos ), m_keys( keys )
|
||||
{
|
||||
}
|
||||
|
||||
bool operator() ( INextBot *bot )
|
||||
{
|
||||
if ( bot->GetEntity()->IsAlive() && !bot->IsSelf( m_source ) )
|
||||
{
|
||||
bot->OnSound( m_source, m_pos, m_keys );
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
CBaseEntity *m_source;
|
||||
const Vector &m_pos;
|
||||
KeyValues *m_keys;
|
||||
};
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* When an entity emits a sound
|
||||
*/
|
||||
void NextBotManager::OnSound( CBaseEntity *source, const Vector &pos, KeyValues *keys )
|
||||
{
|
||||
NextBotSoundNotifyScan notify( source, pos, keys );
|
||||
TheNextBots().ForEachBot( notify );
|
||||
|
||||
if ( source && IsDebugging( NEXTBOT_HEARING ) )
|
||||
{
|
||||
int r,g,b;
|
||||
switch( source->GetTeamNumber() )
|
||||
{
|
||||
case FIRST_GAME_TEAM: r = 0; g = 255; b = 0; break;
|
||||
case (FIRST_GAME_TEAM+1): r = 255; g = 0; b = 0; break;
|
||||
default: r = 255; g = 255; b = 0; break;
|
||||
}
|
||||
NDebugOverlay::Circle( pos, Vector( 1, 0, 0 ), Vector( 0, -1, 0 ), 5.0f, r, g, b, 255, true, 3.0f );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------
|
||||
class NextBotResponseNotifyScan
|
||||
{
|
||||
public:
|
||||
NextBotResponseNotifyScan( CBaseCombatCharacter *who, AIConcept_t concept, AI_Response *response ) : m_who( who ), m_concept( concept ), m_response( response )
|
||||
{
|
||||
}
|
||||
|
||||
bool operator() ( INextBot *bot )
|
||||
{
|
||||
if ( bot->GetEntity()->IsAlive() )
|
||||
{
|
||||
bot->OnSpokeConcept( m_who, m_concept, m_response );
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
CBaseCombatCharacter *m_who;
|
||||
AIConcept_t m_concept;
|
||||
AI_Response *m_response;
|
||||
};
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* When an Actor speaks a concept
|
||||
*/
|
||||
void NextBotManager::OnSpokeConcept( CBaseCombatCharacter *who, AIConcept_t concept, AI_Response *response )
|
||||
{
|
||||
NextBotResponseNotifyScan notify( who, concept, response );
|
||||
TheNextBots().ForEachBot( notify );
|
||||
|
||||
if ( IsDebugging( NEXTBOT_HEARING ) )
|
||||
{
|
||||
// const char *who = response->GetCriteria()->GetValue( response->GetCriteria()->FindCriterionIndex( "Who" ) );
|
||||
|
||||
// TODO: Need concept.GetStringConcept()
|
||||
DevMsg( "%3.2f: OnSpokeConcept( %s, %s )\n", gpGlobals->curtime, who->GetDebugName(), "concept.GetStringConcept()" );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------
|
||||
class NextBotWeaponFiredNotifyScan
|
||||
{
|
||||
public:
|
||||
NextBotWeaponFiredNotifyScan( CBaseCombatCharacter *who, CBaseCombatWeapon *weapon ) : m_who( who ), m_weapon( weapon )
|
||||
{
|
||||
}
|
||||
|
||||
bool operator() ( INextBot *bot )
|
||||
{
|
||||
if ( bot->GetEntity()->IsAlive() )
|
||||
{
|
||||
bot->OnWeaponFired( m_who, m_weapon );
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
CBaseCombatCharacter *m_who;
|
||||
CBaseCombatWeapon *m_weapon;
|
||||
};
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* When someone fires a weapon
|
||||
*/
|
||||
void NextBotManager::OnWeaponFired( CBaseCombatCharacter *whoFired, CBaseCombatWeapon *weapon )
|
||||
{
|
||||
NextBotWeaponFiredNotifyScan notify( whoFired, weapon );
|
||||
TheNextBots().ForEachBot( notify );
|
||||
|
||||
if ( IsDebugging( NEXTBOT_EVENTS ) )
|
||||
{
|
||||
DevMsg( "%3.2f: OnWeaponFired( %s, %s )\n", gpGlobals->curtime, whoFired->GetDebugName(), weapon->GetName() );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Add given entindex to the debug filter
|
||||
*/
|
||||
void NextBotManager::DebugFilterAdd( int index )
|
||||
{
|
||||
DebugFilter filter;
|
||||
|
||||
filter.index = index;
|
||||
filter.name[0] = '\000';
|
||||
|
||||
m_debugFilterList.AddToTail( filter );
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Add given name to the debug filter
|
||||
*/
|
||||
void NextBotManager::DebugFilterAdd( const char *name )
|
||||
{
|
||||
DebugFilter filter;
|
||||
|
||||
filter.index = -1;
|
||||
Q_strncpy( filter.name, name, DebugFilter::MAX_DEBUG_NAME_SIZE );
|
||||
|
||||
m_debugFilterList.AddToTail( filter );
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Remove given entindex from the debug filter
|
||||
*/
|
||||
void NextBotManager::DebugFilterRemove( int index )
|
||||
{
|
||||
for( int i=0; i<m_debugFilterList.Count(); ++i )
|
||||
{
|
||||
if ( m_debugFilterList[i].index == index )
|
||||
{
|
||||
m_debugFilterList.Remove( i );
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Remove given name from the debug filter
|
||||
*/
|
||||
void NextBotManager::DebugFilterRemove( const char *name )
|
||||
{
|
||||
for( int i=0; i<m_debugFilterList.Count(); ++i )
|
||||
{
|
||||
if ( m_debugFilterList[i].name[0] != '\000' &&
|
||||
!Q_strnicmp( name, m_debugFilterList[i].name, MIN( Q_strlen( name ), sizeof( m_debugFilterList[i].name ) ) ) )
|
||||
{
|
||||
m_debugFilterList.Remove( i );
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Clear the debug filter (remove all entries)
|
||||
*/
|
||||
void NextBotManager::DebugFilterClear( void )
|
||||
{
|
||||
m_debugFilterList.RemoveAll();
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Return true if the given bot matches the debug filter
|
||||
*/
|
||||
bool NextBotManager::IsDebugFilterMatch( const INextBot *bot ) const
|
||||
{
|
||||
// if the filter is empty, all bots match
|
||||
if ( m_debugFilterList.Count() == 0 )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
for( int i=0; i<m_debugFilterList.Count(); ++i )
|
||||
{
|
||||
// compare entity index
|
||||
if ( m_debugFilterList[i].index == const_cast< INextBot * >( bot )->GetEntity()->entindex() )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// compare debug filter
|
||||
if ( m_debugFilterList[i].name[0] != '\000' && bot->IsDebugFilterMatch( m_debugFilterList[i].name ) )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// compare special keyword meaning local player is looking at them
|
||||
if ( !Q_strnicmp( m_debugFilterList[i].name, "lookat", Q_strlen( m_debugFilterList[i].name ) ) )
|
||||
{
|
||||
CBasePlayer *watcher = UTIL_GetListenServerHost();
|
||||
if ( watcher )
|
||||
{
|
||||
CBaseEntity *subject = watcher->GetObserverTarget();
|
||||
|
||||
if ( subject && bot->IsSelf( subject ) )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// compare special keyword meaning NextBot is selected
|
||||
if ( !Q_strnicmp( m_debugFilterList[i].name, "selected", Q_strlen( m_debugFilterList[i].name ) ) )
|
||||
{
|
||||
INextBot *selected = GetSelected();
|
||||
if ( selected && bot->IsSelf( selected->GetEntity() ) )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Get the bot under the given player's crosshair
|
||||
*/
|
||||
INextBot *NextBotManager::GetBotUnderCrosshair( CBasePlayer *picker )
|
||||
{
|
||||
if ( !picker )
|
||||
return NULL;
|
||||
|
||||
const float MaxDot = 0.7f;
|
||||
const float MaxRange = 4000.0f;
|
||||
TargetScan< CBaseCombatCharacter > scan( picker, TEAM_ANY, 1.0f - MaxDot, MaxRange );
|
||||
ForEachCombatCharacter( scan );
|
||||
CBaseCombatCharacter *target = scan.GetTarget();
|
||||
if ( target && target->MyNextBotPointer() )
|
||||
return target->MyNextBotPointer();
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#ifdef NEED_BLACK_BOX
|
||||
//---------------------------------------------------------------------------------------------
|
||||
CON_COMMAND( nb_dump_debug_history, "Dumps debug history for the bot under the cursor to the blackbox" )
|
||||
{
|
||||
if ( !NextBotDebugHistory.GetBool() )
|
||||
{
|
||||
BlackBox_Record( "bot", "nb_debug_history 0" );
|
||||
return;
|
||||
}
|
||||
|
||||
CBasePlayer *player = UTIL_GetCommandClient();
|
||||
if ( !player )
|
||||
{
|
||||
player = UTIL_GetListenServerHost();
|
||||
}
|
||||
INextBot *bot = TheNextBots().GetBotUnderCrosshair( player );
|
||||
if ( !bot )
|
||||
{
|
||||
BlackBox_Record( "bot", "no bot under crosshairs" );
|
||||
return;
|
||||
}
|
||||
|
||||
CUtlVector< const INextBot::NextBotDebugLineType * > lines;
|
||||
bot->GetDebugHistory( (NEXTBOT_DEBUG_ALL & (~NEXTBOT_EVENTS)), &lines );
|
||||
|
||||
for ( int i=0; i<lines.Count(); ++i )
|
||||
{
|
||||
if ( IsPC() )
|
||||
{
|
||||
BlackBox_Record( "bot", "%s", lines[i]->data );
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // NEED_BLACK_BOX
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------------------------
|
||||
void NextBotManager::CollectAllBots( CUtlVector< INextBot * > *botVector )
|
||||
{
|
||||
if ( !botVector )
|
||||
return;
|
||||
|
||||
botVector->RemoveAll();
|
||||
|
||||
for( int i=m_botList.Head(); i != m_botList.InvalidIndex(); i = m_botList.Next( i ) )
|
||||
{
|
||||
botVector->AddToTail( m_botList[i] );
|
||||
}
|
||||
}
|
||||
|
||||
211
game/server/NextBot/NextBotManager.h
Normal file
211
game/server/NextBot/NextBotManager.h
Normal file
@@ -0,0 +1,211 @@
|
||||
// NextBotManager.h
|
||||
// Author: Michael Booth, May 2006
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
|
||||
#ifndef _NEXT_BOT_MANAGER_H_
|
||||
#define _NEXT_BOT_MANAGER_H_
|
||||
|
||||
#include "NextBotInterface.h"
|
||||
|
||||
class CTerrorPlayer;
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* The NextBotManager manager
|
||||
*/
|
||||
class NextBotManager
|
||||
{
|
||||
public:
|
||||
NextBotManager( void );
|
||||
virtual ~NextBotManager();
|
||||
|
||||
void Reset( void ); // reset to initial state
|
||||
virtual void Update( void );
|
||||
|
||||
bool ShouldUpdate( INextBot *bot );
|
||||
void NotifyBeginUpdate( INextBot *bot );
|
||||
void NotifyEndUpdate( INextBot *bot );
|
||||
|
||||
int GetNextBotCount( void ) const; // How many nextbots are alive right now?
|
||||
|
||||
|
||||
/**
|
||||
* Populate given vector with all bots in the system
|
||||
*/
|
||||
void CollectAllBots( CUtlVector< INextBot * > *botVector );
|
||||
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use CollectAllBots().
|
||||
* Execute functor for each NextBot in the system.
|
||||
* If a functor returns false, stop iteration early
|
||||
* and return false.
|
||||
*/
|
||||
template < typename Functor >
|
||||
bool ForEachBot( Functor &func )
|
||||
{
|
||||
for( int i=m_botList.Head(); i != m_botList.InvalidIndex(); i = m_botList.Next( i ) )
|
||||
{
|
||||
if ( !func( m_botList[i] ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use CollectAllBots().
|
||||
* Execute functor for each NextBot in the system as
|
||||
* a CBaseCombatCharacter.
|
||||
* If a functor returns false, stop iteration early
|
||||
* and return false.
|
||||
*/
|
||||
template < typename Functor >
|
||||
bool ForEachCombatCharacter( Functor &func )
|
||||
{
|
||||
for( int i=m_botList.Head(); i != m_botList.InvalidIndex(); i = m_botList.Next( i ) )
|
||||
{
|
||||
if ( !func( m_botList[i]->GetEntity() ) )
|
||||
{
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return closest bot to given point that passes the given filter
|
||||
*/
|
||||
template < typename Filter >
|
||||
INextBot *GetClosestBot( const Vector &pos, Filter &filter )
|
||||
{
|
||||
INextBot *close = NULL;
|
||||
float closeRangeSq = FLT_MAX;
|
||||
|
||||
for( int i=m_botList.Head(); i != m_botList.InvalidIndex(); i = m_botList.Next( i ) )
|
||||
{
|
||||
float rangeSq = ( m_botList[i]->GetEntity()->GetAbsOrigin() - pos ).LengthSqr();
|
||||
if ( rangeSq < closeRangeSq && filter( m_botList[i] ) )
|
||||
{
|
||||
closeRangeSq = rangeSq;
|
||||
close = m_botList[i];
|
||||
}
|
||||
}
|
||||
|
||||
return close;
|
||||
}
|
||||
|
||||
/**
|
||||
* Event propagators
|
||||
*/
|
||||
virtual void OnMapLoaded( void ); // when the server has changed maps
|
||||
virtual void OnRoundRestart( void ); // when the scenario restarts
|
||||
virtual void OnBeginChangeLevel( void ); // when the server is about to change maps
|
||||
virtual void OnKilled( CBaseCombatCharacter *victim, const CTakeDamageInfo &info ); // when an actor is killed
|
||||
virtual void OnSound( CBaseEntity *source, const Vector &pos, KeyValues *keys ); // when an entity emits a sound
|
||||
virtual void OnSpokeConcept( CBaseCombatCharacter *who, AIConcept_t concept, AI_Response *response ); // when an Actor speaks a concept
|
||||
virtual void OnWeaponFired( CBaseCombatCharacter *whoFired, CBaseCombatWeapon *weapon ); // when someone fires a weapon
|
||||
|
||||
/**
|
||||
* Debugging
|
||||
*/
|
||||
bool IsDebugging( unsigned int type ) const; // return true if debugging system is on for the given type(s)
|
||||
void SetDebugTypes( NextBotDebugType type ); // start displaying debug info of the given type(s)
|
||||
|
||||
void DebugFilterAdd( int index ); // add given entindex to the debug filter
|
||||
void DebugFilterAdd( const char *name ); // add given name to the debug filter
|
||||
void DebugFilterRemove( int index ); // remove given entindex from the debug filter
|
||||
void DebugFilterRemove( const char *name ); // remove given name from the debug filter
|
||||
void DebugFilterClear( void ); // clear the debug filter (remove all entries)
|
||||
bool IsDebugFilterMatch( const INextBot *bot ) const; // return true if the given bot matches the debug filter
|
||||
|
||||
void Select( INextBot *bot ); // mark bot as selected for further operations
|
||||
void DeselectAll( void );
|
||||
INextBot *GetSelected( void ) const;
|
||||
|
||||
INextBot *GetBotUnderCrosshair( CBasePlayer *picker ); // Get the bot under the given player's crosshair
|
||||
|
||||
//
|
||||
// Put these in a derived class
|
||||
//
|
||||
void OnSurvivorVomitedUpon( CTerrorPlayer *victim ); // when a Survivor has been hit by Boomer Vomit
|
||||
|
||||
static void SetInstance( NextBotManager *pInstance ) { sInstance = pInstance; };
|
||||
static NextBotManager* GetInstance() { return sInstance; }
|
||||
|
||||
protected:
|
||||
static NextBotManager* sInstance;
|
||||
|
||||
friend class INextBot;
|
||||
|
||||
int Register( INextBot *bot );
|
||||
void UnRegister( INextBot *bot );
|
||||
|
||||
CUtlLinkedList< INextBot * > m_botList; // list of all active NextBots
|
||||
|
||||
int m_iUpdateTickrate;
|
||||
double m_CurUpdateStartTime;
|
||||
double m_SumFrameTime;
|
||||
|
||||
unsigned int m_debugType; // debug flags
|
||||
|
||||
struct DebugFilter
|
||||
{
|
||||
int index; // entindex
|
||||
enum { MAX_DEBUG_NAME_SIZE = 128 };
|
||||
char name[ MAX_DEBUG_NAME_SIZE ];
|
||||
};
|
||||
CUtlVector< DebugFilter > m_debugFilterList;
|
||||
|
||||
INextBot *m_selectedBot; // selected bot for further debug operations
|
||||
};
|
||||
|
||||
inline int NextBotManager::GetNextBotCount( void ) const
|
||||
{
|
||||
return m_botList.Count();
|
||||
}
|
||||
|
||||
inline bool NextBotManager::IsDebugging( unsigned int type ) const
|
||||
{
|
||||
if ( type & m_debugType )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
inline void NextBotManager::SetDebugTypes( NextBotDebugType type )
|
||||
{
|
||||
m_debugType = (unsigned int)type;
|
||||
}
|
||||
|
||||
|
||||
inline void NextBotManager::Select( INextBot *bot )
|
||||
{
|
||||
m_selectedBot = bot;
|
||||
}
|
||||
|
||||
inline void NextBotManager::DeselectAll( void )
|
||||
{
|
||||
m_selectedBot = NULL;
|
||||
}
|
||||
|
||||
inline INextBot *NextBotManager::GetSelected( void ) const
|
||||
{
|
||||
return m_selectedBot;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// singleton accessor
|
||||
extern NextBotManager &TheNextBots( void );
|
||||
|
||||
|
||||
#endif // _NEXT_BOT_MANAGER_H_
|
||||
|
||||
278
game/server/NextBot/NextBotUtil.h
Normal file
278
game/server/NextBot/NextBotUtil.h
Normal 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_
|
||||
802
game/server/NextBot/NextBotVisionInterface.cpp
Normal file
802
game/server/NextBot/NextBotVisionInterface.cpp
Normal 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 );
|
||||
}
|
||||
|
||||
|
||||
226
game/server/NextBot/NextBotVisionInterface.h
Normal file
226
game/server/NextBot/NextBotVisionInterface.h
Normal 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_
|
||||
166
game/server/NextBot/Path/NextBotChasePath.cpp
Normal file
166
game/server/NextBot/Path/NextBotChasePath.cpp
Normal 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 );
|
||||
}
|
||||
376
game/server/NextBot/Path/NextBotChasePath.h
Normal file
376
game/server/NextBot/Path/NextBotChasePath.h
Normal 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, ¢er, &halfWidth );
|
||||
|
||||
*crossPos = center;
|
||||
}
|
||||
|
||||
void NotifyVictim( INextBot *me, CBaseEntity *victim );
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
#endif // _NEXT_BOT_CHASE_PATH_
|
||||
1094
game/server/NextBot/Path/NextBotPath.cpp
Normal file
1094
game/server/NextBot/Path/NextBotPath.cpp
Normal file
File diff suppressed because it is too large
Load Diff
862
game/server/NextBot/Path/NextBotPath.h
Normal file
862
game/server/NextBot/Path/NextBotPath.h
Normal 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_
|
||||
|
||||
1923
game/server/NextBot/Path/NextBotPathFollow.cpp
Normal file
1923
game/server/NextBot/Path/NextBotPathFollow.cpp
Normal file
File diff suppressed because it is too large
Load Diff
106
game/server/NextBot/Path/NextBotPathFollow.h
Normal file
106
game/server/NextBot/Path/NextBotPathFollow.h
Normal 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_
|
||||
|
||||
|
||||
573
game/server/NextBot/Path/NextBotRetreatPath.h
Normal file
573
game/server/NextBot/Path/NextBotRetreatPath.h
Normal 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_
|
||||
22
game/server/NextBot/Player/NextBotPlayer.cpp
Normal file
22
game/server/NextBot/Player/NextBotPlayer.cpp
Normal 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" );
|
||||
|
||||
910
game/server/NextBot/Player/NextBotPlayer.h
Normal file
910
game/server/NextBot/Player/NextBotPlayer.h
Normal 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_
|
||||
881
game/server/NextBot/Player/NextBotPlayerBody.cpp
Normal file
881
game/server/NextBot/Player/NextBotPlayerBody.cpp
Normal 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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
153
game/server/NextBot/Player/NextBotPlayerBody.h
Normal file
153
game/server/NextBot/Player/NextBotPlayerBody.h
Normal 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_
|
||||
826
game/server/NextBot/Player/NextBotPlayerLocomotion.cpp
Normal file
826
game/server/NextBot/Player/NextBotPlayerLocomotion.cpp
Normal 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();
|
||||
}
|
||||
|
||||
223
game/server/NextBot/Player/NextBotPlayerLocomotion.h
Normal file
223
game/server/NextBot/Player/NextBotPlayerLocomotion.h
Normal 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_
|
||||
199
game/server/NextBot/simple_bot.cpp
Normal file
199
game/server/NextBot/simple_bot.cpp
Normal file
@@ -0,0 +1,199 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
// simple_bot.cpp
|
||||
// A simple bot
|
||||
// Michael Booth, February 2009
|
||||
|
||||
#include "cbase.h"
|
||||
#include "simple_bot.h"
|
||||
#include "nav_mesh.h"
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------------------------------
|
||||
// Command to add a Simple Bot where your crosshairs are aiming
|
||||
//-----------------------------------------------------------------------------------------------------
|
||||
CON_COMMAND_F( simple_bot_add, "Add a simple bot.", FCVAR_CHEAT )
|
||||
{
|
||||
CBasePlayer *player = UTIL_GetCommandClient();
|
||||
if ( !player )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Vector forward;
|
||||
player->EyeVectors( &forward );
|
||||
|
||||
trace_t result;
|
||||
UTIL_TraceLine( player->EyePosition(), player->EyePosition() + 999999.9f * forward, MASK_BLOCKLOS_AND_NPCS|CONTENTS_IGNORE_NODRAW_OPAQUE, player, COLLISION_GROUP_NONE, &result );
|
||||
if ( !result.DidHit() )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CSimpleBot *bot = static_cast< CSimpleBot * >( CreateEntityByName( "simple_bot" ) );
|
||||
if ( bot )
|
||||
{
|
||||
Vector forward = player->GetAbsOrigin() - result.endpos;
|
||||
forward.z = 0.0f;
|
||||
forward.NormalizeInPlace();
|
||||
|
||||
QAngle angles;
|
||||
VectorAngles( forward, angles );
|
||||
|
||||
bot->SetAbsAngles( angles );
|
||||
bot->SetAbsOrigin( result.endpos + Vector( 0, 0, 10.0f ) );
|
||||
|
||||
DispatchSpawn( bot );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------------------------------
|
||||
// The Simple Bot
|
||||
//-----------------------------------------------------------------------------------------------------
|
||||
LINK_ENTITY_TO_CLASS( simple_bot, CSimpleBot );
|
||||
|
||||
#ifndef TF_DLL
|
||||
PRECACHE_REGISTER( simple_bot );
|
||||
#endif
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------------------------------
|
||||
CSimpleBot::CSimpleBot()
|
||||
{
|
||||
ALLOCATE_INTENTION_INTERFACE( CSimpleBot );
|
||||
|
||||
m_locomotor = new NextBotGroundLocomotion( this );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------------------------------
|
||||
CSimpleBot::~CSimpleBot()
|
||||
{
|
||||
DEALLOCATE_INTENTION_INTERFACE;
|
||||
|
||||
if ( m_locomotor )
|
||||
delete m_locomotor;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------------------------------
|
||||
void CSimpleBot::Precache()
|
||||
{
|
||||
BaseClass::Precache();
|
||||
|
||||
#ifndef DOTA_DLL
|
||||
PrecacheModel( "models/humans/group01/female_01.mdl" );
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------------------------------
|
||||
void CSimpleBot::Spawn( void )
|
||||
{
|
||||
BaseClass::Spawn();
|
||||
|
||||
#ifndef DOTA_DLL
|
||||
SetModel( "models/humans/group01/female_01.mdl" );
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------------------------
|
||||
// The Simple Bot behaviors
|
||||
//---------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* For use with TheNavMesh->ForAllAreas()
|
||||
* Find the Nth area in the sequence
|
||||
*/
|
||||
class SelectNthAreaFunctor
|
||||
{
|
||||
public:
|
||||
SelectNthAreaFunctor( int count )
|
||||
{
|
||||
m_count = count;
|
||||
m_area = NULL;
|
||||
}
|
||||
|
||||
bool operator() ( CNavArea *area )
|
||||
{
|
||||
m_area = area;
|
||||
return ( m_count-- > 0 );
|
||||
}
|
||||
|
||||
int m_count;
|
||||
CNavArea *m_area;
|
||||
};
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* This action causes the bot to pick a random nav area in the mesh and move to it, then
|
||||
* pick another, etc.
|
||||
* Actions usually each have their own .cpp/.h file and are organized into folders since there
|
||||
* are often many of them. For this example, we're keeping everything to a single .cpp/.h file.
|
||||
*/
|
||||
class CSimpleBotRoam : public Action< CSimpleBot >
|
||||
{
|
||||
public:
|
||||
//----------------------------------------------------------------------------------
|
||||
// OnStart is called once when the Action first becomes active
|
||||
virtual ActionResult< CSimpleBot > OnStart( CSimpleBot *me, Action< CSimpleBot > *priorAction )
|
||||
{
|
||||
// smooth out the bot's path following by moving toward a point farther down the path
|
||||
m_path.SetMinLookAheadDistance( 300.0f );
|
||||
|
||||
return Continue();
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
// Update is called repeatedly (usually once per server frame) while the Action is active
|
||||
virtual ActionResult< CSimpleBot > Update( CSimpleBot *me, float interval )
|
||||
{
|
||||
if ( m_path.IsValid() && !m_timer.IsElapsed() )
|
||||
{
|
||||
// PathFollower::Update() moves the bot along the path using the bot's ILocomotion and IBody interfaces
|
||||
m_path.Update( me );
|
||||
}
|
||||
else
|
||||
{
|
||||
SelectNthAreaFunctor pick( RandomInt( 0, TheNavMesh->GetNavAreaCount() - 1 ) );
|
||||
TheNavMesh->ForAllAreas( pick );
|
||||
|
||||
if ( pick.m_area )
|
||||
{
|
||||
CSimpleBotPathCost cost( me );
|
||||
m_path.Compute( me, pick.m_area->GetCenter(), cost );
|
||||
}
|
||||
|
||||
// follow this path for a random duration (or until we reach the end)
|
||||
m_timer.Start( RandomFloat( 5.0f, 10.0f ) );
|
||||
}
|
||||
|
||||
return Continue();
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
// this is an event handler - many more are available (see declaration of Action< Actor > in NextBotBehavior.h)
|
||||
virtual EventDesiredResult< CSimpleBot > OnStuck( CSimpleBot *me )
|
||||
{
|
||||
// we are stuck trying to follow the current path - invalidate it so a new one is chosen
|
||||
m_path.Invalidate();
|
||||
|
||||
return TryContinue();
|
||||
}
|
||||
|
||||
|
||||
virtual const char *GetName( void ) const { return "Roam"; } // return name of this action
|
||||
|
||||
private:
|
||||
PathFollower m_path;
|
||||
CountdownTimer m_timer;
|
||||
};
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Instantiate the bot's Intention interface and start the initial Action (CSimpleBotRoam in this case)
|
||||
*/
|
||||
IMPLEMENT_INTENTION_INTERFACE( CSimpleBot, CSimpleBotRoam )
|
||||
116
game/server/NextBot/simple_bot.h
Normal file
116
game/server/NextBot/simple_bot.h
Normal 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
|
||||
Reference in New Issue
Block a user