mirror of
https://github.com/nillerusr/source-engine.git
synced 2026-08-07 17:29:36 +00:00
add csrike source code
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
// BehaviorBackUp.h
|
||||
// Back up for a short duration
|
||||
// Author: Michael Booth, March 2007
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
|
||||
#ifndef _BEHAVIOR_BACK_UP_H_
|
||||
#define _BEHAVIOR_BACK_UP_H_
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Move backwards for a short duration away from a given position.
|
||||
* Useful to dislodge ourselves if we get stuck while following our path.
|
||||
*/
|
||||
template < typename Actor >
|
||||
class BehaviorBackUp : public Action< Actor >
|
||||
{
|
||||
public:
|
||||
BehaviorBackUp( const Vector &avoidPos );
|
||||
|
||||
virtual ActionResult< Actor > OnStart( Actor *me, Action< Actor > *priorAction );
|
||||
virtual ActionResult< Actor > Update( Actor *me, float interval );
|
||||
|
||||
virtual EventDesiredResult< Actor > OnStuck( Actor *me );
|
||||
|
||||
virtual const char *GetName( void ) const { return "BehaviorBackUp"; }
|
||||
|
||||
private:
|
||||
CountdownTimer m_giveUpTimer;
|
||||
CountdownTimer m_backupTimer;
|
||||
CountdownTimer m_jumpTimer;
|
||||
Vector m_way;
|
||||
Vector m_avoidPos;
|
||||
};
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------
|
||||
template < typename Actor >
|
||||
inline BehaviorBackUp< Actor >::BehaviorBackUp( const Vector &avoidPos )
|
||||
{
|
||||
m_avoidPos = avoidPos;
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------
|
||||
template < typename Actor >
|
||||
inline ActionResult< Actor > BehaviorBackUp< Actor >::OnStart( Actor *me, Action< Actor > *priorAction )
|
||||
{
|
||||
ILocomotion *mover = me->GetLocomotionInterface();
|
||||
|
||||
// don't back off if we're on a ladder
|
||||
if ( mover && mover->IsUsingLadder() )
|
||||
{
|
||||
return Done();
|
||||
}
|
||||
|
||||
float backupTime = RandomFloat( 0.3f, 0.5f );
|
||||
|
||||
m_backupTimer.Start( backupTime );
|
||||
m_jumpTimer.Start( 1.5f * backupTime );
|
||||
m_giveUpTimer.Start( 2.5f * backupTime );
|
||||
|
||||
m_way = me->GetPosition() - m_avoidPos;
|
||||
m_way.NormalizeInPlace();
|
||||
|
||||
return Continue();
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------
|
||||
template < typename Actor >
|
||||
inline ActionResult< Actor > BehaviorBackUp< Actor >::Update( Actor *me, float interval )
|
||||
{
|
||||
if ( m_giveUpTimer.IsElapsed() )
|
||||
{
|
||||
return Done();
|
||||
}
|
||||
|
||||
// if ( m_jumpTimer.HasStarted() && m_jumpTimer.IsElapsed() )
|
||||
// {
|
||||
// me->GetLocomotionInterface()->Jump();
|
||||
// m_jumpTimer.Invalidate();
|
||||
// }
|
||||
|
||||
ILocomotion *mover = me->GetLocomotionInterface();
|
||||
if ( mover )
|
||||
{
|
||||
Vector goal;
|
||||
|
||||
if ( m_backupTimer.IsElapsed() )
|
||||
{
|
||||
// move towards bad spot
|
||||
goal = m_avoidPos; // me->GetPosition() - 100.0f * m_way;
|
||||
}
|
||||
else
|
||||
{
|
||||
// move away from bad spot
|
||||
goal = me->GetPosition() + 100.0f * m_way;
|
||||
}
|
||||
|
||||
mover->Approach( goal );
|
||||
}
|
||||
|
||||
return Continue();
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------
|
||||
template < typename Actor >
|
||||
inline EventDesiredResult< Actor > BehaviorBackUp< Actor >::OnStuck( Actor *me )
|
||||
{
|
||||
return TryToSustain( RESULT_IMPORTANT, "Stuck while trying to back up" );
|
||||
}
|
||||
|
||||
|
||||
|
||||
#endif // _BEHAVIOR_BACK_UP_H_
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
// BehaviorMoveTo.h
|
||||
// Move to a potentially far away position
|
||||
// Author: Michael Booth, June 2007
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
|
||||
#ifndef _BEHAVIOR_MOVE_TO_H_
|
||||
#define _BEHAVIOR_MOVE_TO_H_
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Move to a potentially far away position, using path planning.
|
||||
*/
|
||||
template < typename Actor, typename PathCost >
|
||||
class BehaviorMoveTo : public Action< Actor >
|
||||
{
|
||||
public:
|
||||
BehaviorMoveTo( const Vector &goal, Action< Actor > *successAction = NULL, Action< Actor > *failAction = NULL );
|
||||
|
||||
virtual ActionResult< Actor > OnStart( Actor *me, Action< Actor > *priorAction );
|
||||
virtual ActionResult< Actor > Update( Actor *me, float interval );
|
||||
|
||||
virtual EventDesiredResult< Actor > OnMoveToSuccess( Actor *me, const Path *path );
|
||||
virtual EventDesiredResult< Actor > OnMoveToFailure( Actor *me, const Path *path, MoveToFailureType reason );
|
||||
|
||||
virtual bool ComputePath( Actor *me, const Vector &goal, PathFollower *path );
|
||||
|
||||
virtual const char *GetName( void ) const { return "BehaviorMoveTo"; }
|
||||
|
||||
private:
|
||||
Vector m_goal;
|
||||
PathFollower m_path;
|
||||
Action< Actor > *m_successAction;
|
||||
Action< Actor > *m_failAction;
|
||||
};
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------
|
||||
template < typename Actor, typename PathCost >
|
||||
inline BehaviorMoveTo< Actor, PathCost >::BehaviorMoveTo( const Vector &goal, Action< Actor > *successAction, Action< Actor > *failAction )
|
||||
{
|
||||
m_goal = goal;
|
||||
m_path.Invalidate();
|
||||
m_successAction = successAction;
|
||||
m_failAction = failAction;
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------
|
||||
template < typename Actor, typename PathCost >
|
||||
inline bool BehaviorMoveTo< Actor, PathCost >::ComputePath( Actor *me, const Vector &goal, PathFollower *path )
|
||||
{
|
||||
PathCost cost( me );
|
||||
return path->Compute( me, goal, cost );
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------
|
||||
template < typename Actor, typename PathCost >
|
||||
inline ActionResult< Actor > BehaviorMoveTo< Actor, PathCost >::OnStart( Actor *me, Action< Actor > *priorAction )
|
||||
{
|
||||
if ( !this->ComputePath( me, m_goal, &m_path ) )
|
||||
{
|
||||
if ( m_failAction )
|
||||
{
|
||||
return this->ChangeTo( m_failAction, "No path to goal" );
|
||||
}
|
||||
|
||||
return this->Done( "No path to goal" );
|
||||
}
|
||||
|
||||
return this->Continue();
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------
|
||||
template < typename Actor, typename PathCost >
|
||||
inline ActionResult< Actor > BehaviorMoveTo< Actor, PathCost >::Update( Actor *me, float interval )
|
||||
{
|
||||
// if path became invalid during last tick for any reason, we're done
|
||||
if ( !m_path.IsValid() )
|
||||
{
|
||||
if ( m_failAction )
|
||||
{
|
||||
return this->ChangeTo( m_failAction, "Path is invalid" );
|
||||
}
|
||||
|
||||
return this->Done( "Path is invalid" );
|
||||
}
|
||||
|
||||
// move along path - success/fail event handlers will exit behavior when goal is reached
|
||||
m_path.Update( me );
|
||||
|
||||
return this->Continue();
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------
|
||||
template < typename Actor, typename PathCost >
|
||||
inline EventDesiredResult< Actor > BehaviorMoveTo< Actor, PathCost >::OnMoveToSuccess( Actor *me, const Path *path )
|
||||
{
|
||||
if ( m_successAction )
|
||||
{
|
||||
return this->TryChangeTo( m_successAction, RESULT_CRITICAL, "OnMoveToSuccess" );
|
||||
}
|
||||
|
||||
return this->TryDone( RESULT_CRITICAL, "OnMoveToSuccess" );
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------
|
||||
template < typename Actor, typename PathCost >
|
||||
inline EventDesiredResult< Actor > BehaviorMoveTo< Actor, PathCost >::OnMoveToFailure( Actor *me, const Path *path, MoveToFailureType reason )
|
||||
{
|
||||
if ( m_failAction )
|
||||
{
|
||||
return this->TryChangeTo( m_failAction, RESULT_CRITICAL, "OnMoveToFailure" );
|
||||
}
|
||||
|
||||
return this->TryDone( RESULT_CRITICAL, "OnMoveToFailure" );
|
||||
}
|
||||
|
||||
|
||||
|
||||
#endif // _BEHAVIOR_MOVE_TO_H_
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
// NextBot paths that go through this entity must fulfill the given prerequisites to pass
|
||||
// Michael Booth, August 2009
|
||||
|
||||
#include "cbase.h"
|
||||
#include "func_nav_prerequisite.h"
|
||||
#include "ndebugoverlay.h"
|
||||
#include "modelentities.h"
|
||||
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
|
||||
LINK_ENTITY_TO_CLASS( func_nav_prerequisite, CFuncNavPrerequisite );
|
||||
|
||||
BEGIN_DATADESC( CFuncNavPrerequisite )
|
||||
DEFINE_KEYFIELD( m_task, FIELD_INTEGER, "Task" ),
|
||||
DEFINE_KEYFIELD( m_taskEntityName, FIELD_STRING, "Entity" ),
|
||||
DEFINE_KEYFIELD( m_taskValue, FIELD_FLOAT, "Value" ),
|
||||
DEFINE_KEYFIELD( m_isDisabled, FIELD_BOOLEAN, "StartDisabled" ),
|
||||
DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ),
|
||||
DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ),
|
||||
END_DATADESC()
|
||||
|
||||
IMPLEMENT_AUTO_LIST( IFuncNavPrerequisiteAutoList );
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
CFuncNavPrerequisite::CFuncNavPrerequisite()
|
||||
{
|
||||
m_task = TASK_NONE;
|
||||
m_hTaskEntity = NULL;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void CFuncNavPrerequisite::Spawn( void )
|
||||
{
|
||||
AddSpawnFlags( SF_TRIGGER_ALLOW_CLIENTS );
|
||||
|
||||
BaseClass::Spawn();
|
||||
InitTrigger();
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CFuncNavPrerequisite::IsTask( TaskType task ) const
|
||||
{
|
||||
return task == m_task ? true : false;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
CBaseEntity *CFuncNavPrerequisite::GetTaskEntity( void )
|
||||
{
|
||||
if ( m_hTaskEntity == NULL )
|
||||
{
|
||||
m_hTaskEntity = gEntList.FindEntityByName( NULL, m_taskEntityName );
|
||||
}
|
||||
return m_hTaskEntity;
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------
|
||||
void CFuncNavPrerequisite::InputEnable( inputdata_t &inputdata )
|
||||
{
|
||||
m_isDisabled = false;
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------
|
||||
void CFuncNavPrerequisite::InputDisable( inputdata_t &inputdata )
|
||||
{
|
||||
m_isDisabled = true;
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
// NextBot paths that go through this entity must fulfill the given prerequisites to pass
|
||||
// Michael Booth, August 2009
|
||||
|
||||
#ifndef FUNC_NAV_PREREQUISITE_H
|
||||
#define FUNC_NAV_PREREQUISITE_H
|
||||
|
||||
#include "triggers.h"
|
||||
|
||||
/**
|
||||
* NextBot paths that pass through this entity must fulfill the given prerequisites to pass
|
||||
*/
|
||||
DECLARE_AUTO_LIST( IFuncNavPrerequisiteAutoList );
|
||||
|
||||
class CFuncNavPrerequisite : public CBaseTrigger, public IFuncNavPrerequisiteAutoList
|
||||
{
|
||||
DECLARE_CLASS( CFuncNavPrerequisite, CBaseTrigger );
|
||||
|
||||
public:
|
||||
CFuncNavPrerequisite();
|
||||
|
||||
DECLARE_DATADESC();
|
||||
|
||||
virtual void Spawn( void );
|
||||
|
||||
enum TaskType
|
||||
{
|
||||
TASK_NONE = 0,
|
||||
TASK_DESTROY_ENTITY = 1,
|
||||
TASK_MOVE_TO_ENTITY = 2,
|
||||
TASK_WAIT = 3,
|
||||
};
|
||||
|
||||
bool IsTask( TaskType type ) const;
|
||||
CBaseEntity *GetTaskEntity( void );
|
||||
float GetTaskValue( void ) const;
|
||||
|
||||
void InputEnable( inputdata_t &inputdata );
|
||||
void InputDisable( inputdata_t &inputdata );
|
||||
bool IsEnabled( void ) const { return !m_isDisabled; }
|
||||
|
||||
protected:
|
||||
int m_task;
|
||||
string_t m_taskEntityName;
|
||||
float m_taskValue;
|
||||
bool m_isDisabled;
|
||||
EHANDLE m_hTaskEntity;
|
||||
};
|
||||
|
||||
inline float CFuncNavPrerequisite::GetTaskValue( void ) const
|
||||
{
|
||||
return m_taskValue;
|
||||
}
|
||||
|
||||
|
||||
#endif // FUNC_NAV_PREREQUISITE_H
|
||||
@@ -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 );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
// NextBotCombatCharacter.h
|
||||
// Next generation bot system
|
||||
// Author: Michael Booth, April 2005
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
|
||||
#ifndef _NEXT_BOT_H_
|
||||
#define _NEXT_BOT_H_
|
||||
|
||||
#include "NextBotInterface.h"
|
||||
#include "NextBotManager.h"
|
||||
|
||||
#ifdef TERROR
|
||||
#include "player_lagcompensation.h"
|
||||
#endif
|
||||
|
||||
class NextBotCombatCharacter;
|
||||
struct animevent_t;
|
||||
|
||||
extern ConVar NextBotStop;
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------------
|
||||
//----------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* A Next Bot derived from CBaseCombatCharacter
|
||||
*/
|
||||
class NextBotCombatCharacter : public CBaseCombatCharacter, public INextBot
|
||||
{
|
||||
public:
|
||||
DECLARE_CLASS( NextBotCombatCharacter, CBaseCombatCharacter );
|
||||
DECLARE_SERVERCLASS();
|
||||
DECLARE_DATADESC();
|
||||
|
||||
NextBotCombatCharacter( void );
|
||||
virtual ~NextBotCombatCharacter() { }
|
||||
|
||||
virtual void Spawn( void );
|
||||
|
||||
virtual Vector EyePosition( void );
|
||||
|
||||
virtual INextBot *MyNextBotPointer( void ) { return this; }
|
||||
|
||||
// Event hooks into NextBot system ---------------------------------------
|
||||
virtual int OnTakeDamage_Alive( const CTakeDamageInfo &info );
|
||||
virtual int OnTakeDamage_Dying( const CTakeDamageInfo &info );
|
||||
virtual void Event_Killed( const CTakeDamageInfo &info );
|
||||
virtual void HandleAnimEvent( animevent_t *event );
|
||||
virtual void OnNavAreaChanged( CNavArea *enteredArea, CNavArea *leftArea ); // invoked (by UpdateLastKnownArea) when we enter a new nav area (or it is reset to NULL)
|
||||
virtual void Touch( CBaseEntity *other );
|
||||
virtual void SetModel( const char *szModelName );
|
||||
virtual void Ignite( float flFlameLifetime, bool bNPCOnly = true, float flSize = 0.0f, bool bCalledByLevelDesigner = false );
|
||||
virtual void Ignite( float flFlameLifetime, CBaseEntity *pAttacker );
|
||||
//------------------------------------------------------------------------
|
||||
|
||||
virtual bool IsUseableEntity( CBaseEntity *entity, unsigned int requiredCaps = 0 );
|
||||
void UseEntity( CBaseEntity *entity, USE_TYPE useType = USE_TOGGLE );
|
||||
|
||||
// Implement this if you use MOVETYPE_CUSTOM
|
||||
virtual void PerformCustomPhysics( Vector *pNewPosition, Vector *pNewVelocity, QAngle *pNewAngles, QAngle *pNewAngVelocity );
|
||||
|
||||
virtual bool BecomeRagdoll( const CTakeDamageInfo &info, const Vector &forceVector );
|
||||
|
||||
// hook to INextBot update
|
||||
void DoThink( void );
|
||||
|
||||
// expose to public
|
||||
int GetLastHitGroup( void ) const; // where on our body were we injured last
|
||||
|
||||
virtual bool IsAreaTraversable( const CNavArea *area ) const; // return true if we can use the given area
|
||||
|
||||
virtual CBaseCombatCharacter *GetLastAttacker( void ) const; // return the character who last attacked me
|
||||
|
||||
// begin INextBot public interface ----------------------------------------------------------------
|
||||
virtual NextBotCombatCharacter *GetEntity( void ) const { return const_cast< NextBotCombatCharacter * >( this ); }
|
||||
virtual NextBotCombatCharacter *GetNextBotCombatCharacter( void ) const { return const_cast< NextBotCombatCharacter * >( this ); }
|
||||
|
||||
|
||||
private:
|
||||
EHANDLE m_lastAttacker;
|
||||
|
||||
bool m_didModelChange;
|
||||
};
|
||||
|
||||
|
||||
inline CBaseCombatCharacter *NextBotCombatCharacter::GetLastAttacker( void ) const
|
||||
{
|
||||
return ( m_lastAttacker.Get() == NULL ) ? NULL : m_lastAttacker->MyCombatCharacterPointer();
|
||||
}
|
||||
|
||||
inline int NextBotCombatCharacter::GetLastHitGroup( void ) const
|
||||
{
|
||||
return LastHitGroup();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------------------------------
|
||||
class NextBotDestroyer
|
||||
{
|
||||
public:
|
||||
NextBotDestroyer( int team );
|
||||
bool operator() ( INextBot *bot );
|
||||
int m_team; // the team to delete bots from, or TEAM_ANY for any team
|
||||
};
|
||||
|
||||
#endif // _NEXT_BOT_H_
|
||||
@@ -0,0 +1,162 @@
|
||||
// NextBotAttentionInterface.cpp
|
||||
// Manage what this bot pays attention to
|
||||
// Author: Michael Booth, April 2007
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
|
||||
#include "cbase.h"
|
||||
|
||||
#include "NextBot.h"
|
||||
#include "NextBotAttentionInterface.h"
|
||||
#include "NextBotBodyInterface.h"
|
||||
|
||||
#include "tier0/vprof.h"
|
||||
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Reset to initial state
|
||||
*/
|
||||
void IAttention::Reset( void )
|
||||
{
|
||||
m_body = GetBot()->GetBodyInterface();
|
||||
|
||||
m_attentionSet.RemoveAll();
|
||||
}
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Update internal state
|
||||
*/
|
||||
void IAttention::Update( void )
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------------------
|
||||
void IAttention::AttendTo( const CBaseCombatCharacter *who, const char *reason )
|
||||
{
|
||||
if ( !IsAwareOf( who ) )
|
||||
{
|
||||
PointOfInterest p;
|
||||
p.m_type = PointOfInterest::WHO;
|
||||
p.m_who = who;
|
||||
p.m_duration.Start();
|
||||
|
||||
m_attentionSet.AddToTail( p );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------------------
|
||||
void IAttention::AttendTo( const CBaseEntity *what, const char *reason )
|
||||
{
|
||||
if ( !IsAwareOf( what ) )
|
||||
{
|
||||
PointOfInterest p;
|
||||
p.m_type = PointOfInterest::WHAT;
|
||||
p.m_what = what;
|
||||
p.m_duration.Start();
|
||||
|
||||
m_attentionSet.AddToTail( p );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------------------
|
||||
void IAttention::AttendTo( const Vector &where, IAttention::SignificanceLevel significance, const char *reason )
|
||||
{
|
||||
PointOfInterest p;
|
||||
p.m_type = PointOfInterest::WHERE;
|
||||
p.m_where = where;
|
||||
p.m_duration.Start();
|
||||
|
||||
m_attentionSet.AddToTail( p );
|
||||
}
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------------------
|
||||
void IAttention::Disregard( const CBaseCombatCharacter *who, const char *reason )
|
||||
{
|
||||
FOR_EACH_VEC( m_attentionSet, it )
|
||||
{
|
||||
if ( m_attentionSet[ it ].m_type == PointOfInterest::WHO )
|
||||
{
|
||||
CBaseCombatCharacter *myWho = m_attentionSet[ it ].m_who;
|
||||
|
||||
if ( !myWho || myWho->entindex() == who->entindex() )
|
||||
{
|
||||
m_attentionSet.Remove( it );
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------------------
|
||||
void IAttention::Disregard( const CBaseEntity *what, const char *reason )
|
||||
{
|
||||
FOR_EACH_VEC( m_attentionSet, it )
|
||||
{
|
||||
if ( m_attentionSet[ it ].m_type == PointOfInterest::WHAT )
|
||||
{
|
||||
CBaseCombatCharacter *myWhat = m_attentionSet[ it ].m_what;
|
||||
|
||||
if ( !myWhat || myWhat->entindex() == what->entindex() )
|
||||
{
|
||||
m_attentionSet.Remove( it );
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Return true if given actor is in our attending set
|
||||
*/
|
||||
bool IAttention::IsAwareOf( const CBaseCombatCharacter *who ) const
|
||||
{
|
||||
FOR_EACH_VEC( m_attentionSet, it )
|
||||
{
|
||||
if ( m_attentionSet[ it ].m_type == PointOfInterest::WHO )
|
||||
{
|
||||
CBaseCombatCharacter *myWho = m_attentionSet[ it ].m_who;
|
||||
|
||||
if ( myWho && myWho->entindex() == who->entindex() )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Return true if given object is in our attending set
|
||||
*/
|
||||
bool IAttention::IsAwareOf( const CBaseEntity *what ) const
|
||||
{
|
||||
FOR_EACH_VEC( m_attentionSet, it )
|
||||
{
|
||||
if ( m_attentionSet[ it ].m_type == PointOfInterest::WHAT )
|
||||
{
|
||||
CBaseEntity *myWhat = m_attentionSet[ it ].m_what;
|
||||
|
||||
if ( myWhat && myWhat->entindex() == what->entindex() )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
// NextBotAttentionInterface.h
|
||||
// Manage what this bot pays attention to
|
||||
// Author: Michael Booth, April 2007
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
|
||||
#ifndef _NEXT_BOT_ATTENTION_INTERFACE_H_
|
||||
#define _NEXT_BOT_ATTENTION_INTERFACE_H_
|
||||
|
||||
#include "NextBotComponentInterface.h"
|
||||
|
||||
class INextBot;
|
||||
class IBody;
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* The interface for managing what a bot pays attention to.
|
||||
* Vision determines what see see and notice -> Attention determines which of those things we look at -> Low level head/aiming simulation actually moves our head/eyes
|
||||
*/
|
||||
class IAttention : public INextBotComponent
|
||||
{
|
||||
public:
|
||||
IAttention( INextBot *bot ) : INextBotComponent( bot ) { }
|
||||
virtual ~IAttention() { }
|
||||
|
||||
virtual void Reset( void ) { } // reset to initial state
|
||||
virtual void Update( void ) { } // update internal state
|
||||
|
||||
enum SignificanceLevel
|
||||
{
|
||||
BORING, // background noise
|
||||
INTERESTING, // notably interesting
|
||||
COMPELLING, // very hard to pay attention to anything else
|
||||
IRRESISTIBLE, // can't look away
|
||||
};
|
||||
|
||||
// override these to control the significance of entities in a context-specific way
|
||||
virtual int CompareSignificance( const CBaseEntity *a, const CBaseEntity *b ) const; // returns <0 if a < b, 0 if a==b, or >0 if a>b
|
||||
|
||||
// bring things to our attention
|
||||
virtual void AttendTo( CBaseEntity *what, const char *reason = NULL );
|
||||
virtual void AttendTo( const Vector &where, SignificanceLevel significance, const char *reason = NULL );
|
||||
|
||||
// remove things from our attention
|
||||
virtual void Disregard( CBaseEntity *what, const char *reason = NULL );
|
||||
|
||||
virtual bool IsAwareOf( CBaseEntity *what ) const; // return true if given object is in our attending set
|
||||
virtual float GetAwareDuration( CBaseEntity *what ) const; // return how long we've been aware of this entity
|
||||
|
||||
// INextBotEventResponder ------------------------------------------------------------------
|
||||
virtual void OnInjured( const CTakeDamageInfo &info ); // when bot is damaged by something
|
||||
virtual void OnContact( CBaseEntity *other, CGameTrace *result = NULL ); // invoked when bot touches 'other'
|
||||
virtual void OnSight( CBaseEntity *subject ); // when subject initially enters bot's visual awareness
|
||||
virtual void OnLostSight( CBaseEntity *subject ); // when subject leaves enters bot's visual awareness
|
||||
virtual void OnSound( CBaseEntity *source, const CSoundParameters ¶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_
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,55 @@
|
||||
// NextBotBodyInterface.cpp
|
||||
// Control and information about the bot's body state (posture, animation state, etc)
|
||||
// Author: Michael Booth, April 2006
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
|
||||
#include "cbase.h"
|
||||
|
||||
#include "NextBot.h"
|
||||
#include "NextBotBodyInterface.h"
|
||||
|
||||
|
||||
void IBody::AimHeadTowards( const Vector &lookAtPos, LookAtPriorityType priority, float duration, INextBotReply *replyWhenAimed, const char *reason )
|
||||
{
|
||||
if ( replyWhenAimed )
|
||||
{
|
||||
replyWhenAimed->OnFail( GetBot(), INextBotReply::FAILED );
|
||||
}
|
||||
}
|
||||
|
||||
void IBody::AimHeadTowards( CBaseEntity *subject, LookAtPriorityType priority, float duration, INextBotReply *replyWhenAimed, const char *reason )
|
||||
{
|
||||
if ( replyWhenAimed )
|
||||
{
|
||||
replyWhenAimed->OnFail( GetBot(), INextBotReply::FAILED );
|
||||
}
|
||||
}
|
||||
|
||||
bool IBody::SetPosition( const Vector &pos )
|
||||
{
|
||||
GetBot()->GetEntity()->SetAbsOrigin( pos );
|
||||
return true;
|
||||
}
|
||||
|
||||
const Vector &IBody::GetEyePosition( void ) const
|
||||
{
|
||||
static Vector eye;
|
||||
|
||||
eye = GetBot()->GetEntity()->WorldSpaceCenter();
|
||||
|
||||
return eye;
|
||||
}
|
||||
|
||||
const Vector &IBody::GetViewVector( void ) const
|
||||
{
|
||||
static Vector view;
|
||||
|
||||
AngleVectors( GetBot()->GetEntity()->EyeAngles(), &view );
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
bool IBody::IsHeadAimingOnTarget( void ) const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,325 @@
|
||||
// NextBotBodyInterface.h
|
||||
// Control and information about the bot's body state (posture, animation state, etc)
|
||||
// Author: Michael Booth, April 2006
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
|
||||
#ifndef _NEXT_BOT_BODY_INTERFACE_H_
|
||||
#define _NEXT_BOT_BODY_INTERFACE_H_
|
||||
|
||||
#include "animation.h"
|
||||
#include "NextBotComponentInterface.h"
|
||||
|
||||
class INextBot;
|
||||
struct animevent_t;
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* The interface for control and information about the bot's body state (posture, animation state, etc)
|
||||
*/
|
||||
class IBody : public INextBotComponent
|
||||
{
|
||||
public:
|
||||
IBody( INextBot *bot ) : INextBotComponent( bot ) { }
|
||||
virtual ~IBody() { }
|
||||
|
||||
virtual void Reset( void ) { INextBotComponent::Reset(); } // reset to initial state
|
||||
virtual void Update( void ) { } // update internal state
|
||||
|
||||
/**
|
||||
* Move the bot to a new position.
|
||||
* If the body is not currently movable or if it
|
||||
* is in a motion-controlled animation activity
|
||||
* the position will not be changed and false will be returned.
|
||||
*/
|
||||
virtual bool SetPosition( const Vector &pos );
|
||||
|
||||
virtual const Vector &GetEyePosition( void ) const; // return the eye position of the bot in world coordinates
|
||||
virtual const Vector &GetViewVector( void ) const; // return the view unit direction vector in world coordinates
|
||||
|
||||
enum LookAtPriorityType
|
||||
{
|
||||
BORING,
|
||||
INTERESTING, // last known enemy location, dangerous sound location
|
||||
IMPORTANT, // a danger
|
||||
CRITICAL, // an active threat to our safety
|
||||
MANDATORY // nothing can interrupt this look at - two simultaneous look ats with this priority is an error
|
||||
};
|
||||
virtual void AimHeadTowards( const Vector &lookAtPos,
|
||||
LookAtPriorityType priority = BORING,
|
||||
float duration = 0.0f,
|
||||
INextBotReply *replyWhenAimed = NULL,
|
||||
const char *reason = NULL ); // aim the bot's head towards the given goal
|
||||
virtual void AimHeadTowards( CBaseEntity *subject,
|
||||
LookAtPriorityType priority = BORING,
|
||||
float duration = 0.0f,
|
||||
INextBotReply *replyWhenAimed = NULL,
|
||||
const char *reason = NULL ); // continually aim the bot's head towards the given subject
|
||||
|
||||
virtual bool IsHeadAimingOnTarget( void ) const; // return true if the bot's head has achieved its most recent lookat target
|
||||
virtual bool IsHeadSteady( void ) const; // return true if head is not rapidly turning to look somewhere else
|
||||
virtual float GetHeadSteadyDuration( void ) const; // return the duration that the bot's head has not been rotating
|
||||
virtual float GetHeadAimSubjectLeadTime( void ) const; // return how far into the future we should predict our moving subject's position to aim at when tracking subject look-ats
|
||||
virtual float GetHeadAimTrackingInterval( void ) const; // return how often we should sample our target's position and velocity to update our aim tracking, to allow realistic slop in tracking
|
||||
virtual void ClearPendingAimReply( void ) { } // clear out currently pending replyWhenAimed callback
|
||||
|
||||
virtual float GetMaxHeadAngularVelocity( void ) const; // return max turn rate of head in degrees/second
|
||||
|
||||
enum ActivityType
|
||||
{
|
||||
MOTION_CONTROLLED_XY = 0x0001, // XY position and orientation of the bot is driven by the animation.
|
||||
MOTION_CONTROLLED_Z = 0x0002, // Z position of the bot is driven by the animation.
|
||||
ACTIVITY_UNINTERRUPTIBLE= 0x0004, // activity can't be changed until animation finishes
|
||||
ACTIVITY_TRANSITORY = 0x0008, // a short animation that takes over from the underlying animation momentarily, resuming it upon completion
|
||||
ENTINDEX_PLAYBACK_RATE = 0x0010, // played back at different rates based on entindex
|
||||
};
|
||||
|
||||
/**
|
||||
* Begin an animation activity, return false if we cant do that right now.
|
||||
*/
|
||||
virtual bool StartActivity( Activity act, unsigned int flags = 0 );
|
||||
virtual int SelectAnimationSequence( Activity act ) const; // given an Activity, select and return a specific animation sequence within it
|
||||
|
||||
virtual Activity GetActivity( void ) const; // return currently animating activity
|
||||
virtual bool IsActivity( Activity act ) const; // return true if currently animating activity matches the given one
|
||||
virtual bool HasActivityType( unsigned int flags ) const; // return true if currently animating activity has any of the given flags
|
||||
|
||||
enum PostureType
|
||||
{
|
||||
STAND,
|
||||
CROUCH,
|
||||
SIT,
|
||||
CRAWL,
|
||||
LIE
|
||||
};
|
||||
virtual void SetDesiredPosture( PostureType posture ) { } // request a posture change
|
||||
virtual PostureType GetDesiredPosture( void ) const; // get posture body is trying to assume
|
||||
virtual bool IsDesiredPosture( PostureType posture ) const; // return true if body is trying to assume this posture
|
||||
virtual bool IsInDesiredPosture( void ) const; // return true if body's actual posture matches its desired posture
|
||||
|
||||
virtual PostureType GetActualPosture( void ) const; // return body's current actual posture
|
||||
virtual bool IsActualPosture( PostureType posture ) const; // return true if body is actually in the given posture
|
||||
|
||||
virtual bool IsPostureMobile( void ) const; // return true if body's current posture allows it to move around the world
|
||||
virtual bool IsPostureChanging( void ) const; // return true if body's posture is in the process of changing to new posture
|
||||
|
||||
|
||||
/**
|
||||
* "Arousal" is the level of excitedness/arousal/anxiety of the body.
|
||||
* Is changes instantaneously to avoid complex interactions with posture transitions.
|
||||
*/
|
||||
enum ArousalType
|
||||
{
|
||||
NEUTRAL,
|
||||
ALERT,
|
||||
INTENSE
|
||||
};
|
||||
virtual void SetArousal( ArousalType arousal ) { } // arousal level change
|
||||
virtual ArousalType GetArousal( void ) const; // get arousal level
|
||||
virtual bool IsArousal( ArousalType arousal ) const; // return true if body is at this arousal level
|
||||
|
||||
|
||||
virtual float GetHullWidth( void ) const; // width of bot's collision hull in XY plane
|
||||
virtual float GetHullHeight( void ) const; // height of bot's current collision hull based on posture
|
||||
virtual float GetStandHullHeight( void ) const; // height of bot's collision hull when standing
|
||||
virtual float GetCrouchHullHeight( void ) const; // height of bot's collision hull when crouched
|
||||
virtual const Vector &GetHullMins( void ) const; // return current collision hull minimums based on actual body posture
|
||||
virtual const Vector &GetHullMaxs( void ) const; // return current collision hull maximums based on actual body posture
|
||||
|
||||
virtual unsigned int GetSolidMask( void ) const; // return the bot's collision mask (hack until we get a general hull trace abstraction here or in the locomotion interface)
|
||||
virtual unsigned int GetCollisionGroup( void ) const;
|
||||
};
|
||||
|
||||
|
||||
inline bool IBody::IsHeadSteady( void ) const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
inline float IBody::GetHeadSteadyDuration( void ) const
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
inline float IBody::GetHeadAimSubjectLeadTime( void ) const
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
inline float IBody::GetHeadAimTrackingInterval( void ) const
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
inline float IBody::GetMaxHeadAngularVelocity( void ) const
|
||||
{
|
||||
return 1000.0f;
|
||||
}
|
||||
|
||||
inline bool IBody::StartActivity( Activity act, unsigned int flags )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
inline int IBody::SelectAnimationSequence( Activity act ) const
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
inline Activity IBody::GetActivity( void ) const
|
||||
{
|
||||
return ACT_INVALID;
|
||||
}
|
||||
|
||||
inline bool IBody::IsActivity( Activity act ) const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
inline bool IBody::HasActivityType( unsigned int flags ) const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
inline IBody::PostureType IBody::GetDesiredPosture( void ) const
|
||||
{
|
||||
return IBody::STAND;
|
||||
}
|
||||
|
||||
inline bool IBody::IsDesiredPosture( PostureType posture ) const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool IBody::IsInDesiredPosture( void ) const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
inline IBody::PostureType IBody::GetActualPosture( void ) const
|
||||
{
|
||||
return IBody::STAND;
|
||||
}
|
||||
|
||||
inline bool IBody::IsActualPosture( PostureType posture ) const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool IBody::IsPostureMobile( void ) const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool IBody::IsPostureChanging( void ) const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
inline IBody::ArousalType IBody::GetArousal( void ) const
|
||||
{
|
||||
return IBody::NEUTRAL;
|
||||
}
|
||||
|
||||
inline bool IBody::IsArousal( ArousalType arousal ) const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Width of bot's collision hull in XY plane
|
||||
*/
|
||||
inline float IBody::GetHullWidth( void ) const
|
||||
{
|
||||
return 26.0f;
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Height of bot's current collision hull based on posture
|
||||
*/
|
||||
inline float IBody::GetHullHeight( void ) const
|
||||
{
|
||||
switch( GetActualPosture() )
|
||||
{
|
||||
case LIE:
|
||||
return 16.0f;
|
||||
|
||||
case SIT:
|
||||
case CROUCH:
|
||||
return GetCrouchHullHeight();
|
||||
|
||||
case STAND:
|
||||
default:
|
||||
return GetStandHullHeight();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Height of bot's collision hull when standing
|
||||
*/
|
||||
inline float IBody::GetStandHullHeight( void ) const
|
||||
{
|
||||
return 68.0f;
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Height of bot's collision hull when crouched
|
||||
*/
|
||||
inline float IBody::GetCrouchHullHeight( void ) const
|
||||
{
|
||||
return 32.0f;
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Return current collision hull minimums based on actual body posture
|
||||
*/
|
||||
inline const Vector &IBody::GetHullMins( void ) const
|
||||
{
|
||||
static Vector hullMins;
|
||||
|
||||
hullMins.x = -GetHullWidth()/2.0f;
|
||||
hullMins.y = hullMins.x;
|
||||
hullMins.z = 0.0f;
|
||||
|
||||
return hullMins;
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Return current collision hull maximums based on actual body posture
|
||||
*/
|
||||
inline const Vector &IBody::GetHullMaxs( void ) const
|
||||
{
|
||||
static Vector hullMaxs;
|
||||
|
||||
hullMaxs.x = GetHullWidth()/2.0f;
|
||||
hullMaxs.y = hullMaxs.x;
|
||||
hullMaxs.z = GetHullHeight();
|
||||
|
||||
return hullMaxs;
|
||||
}
|
||||
|
||||
|
||||
inline unsigned int IBody::GetSolidMask( void ) const
|
||||
{
|
||||
return MASK_NPCSOLID;
|
||||
}
|
||||
|
||||
inline unsigned int IBody::GetCollisionGroup( void ) const
|
||||
{
|
||||
return COLLISION_GROUP_NONE;
|
||||
}
|
||||
|
||||
|
||||
|
||||
#endif // _NEXT_BOT_BODY_INTERFACE_H_
|
||||
@@ -0,0 +1,24 @@
|
||||
// NextBotComponentInterface.cpp
|
||||
// Implentation of system methods for NextBot component interface
|
||||
// Author: Michael Booth, May 2006
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
|
||||
#include "cbase.h"
|
||||
|
||||
#include "NextBotInterface.h"
|
||||
#include "NextBotComponentInterface.h"
|
||||
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
INextBotComponent::INextBotComponent( INextBot *bot )
|
||||
{
|
||||
m_curInterval = TICK_INTERVAL;
|
||||
m_lastUpdateTime = 0;
|
||||
m_bot = bot;
|
||||
|
||||
// register this component with the bot
|
||||
bot->RegisterComponent( this );
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
// NextBotComponentInterface.h
|
||||
// Interface for all components
|
||||
// Author: Michael Booth, May 2006
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
|
||||
#ifndef _NEXT_BOT_COMPONENT_INTERFACE_H_
|
||||
#define _NEXT_BOT_COMPONENT_INTERFACE_H_
|
||||
|
||||
#include "NextBotEventResponderInterface.h"
|
||||
|
||||
class INextBot;
|
||||
class Path;
|
||||
class CGameTrace;
|
||||
class CTakeDamageInfo;
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Various processes can invoke a "reply" (ie: callback) via instances of this interface
|
||||
*/
|
||||
class INextBotReply
|
||||
{
|
||||
public:
|
||||
virtual void OnSuccess( INextBot *bot ) { } // invoked when process completed successfully
|
||||
|
||||
enum FailureReason
|
||||
{
|
||||
DENIED,
|
||||
INTERRUPTED,
|
||||
FAILED
|
||||
};
|
||||
virtual void OnFail( INextBot *bot, FailureReason reason ) { } // invoked when process failed
|
||||
};
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Next Bot component interface
|
||||
*/
|
||||
class INextBotComponent : public INextBotEventResponder
|
||||
{
|
||||
public:
|
||||
INextBotComponent( INextBot *bot );
|
||||
virtual ~INextBotComponent() { }
|
||||
|
||||
virtual void Reset( void ) { m_lastUpdateTime = 0; m_curInterval = TICK_INTERVAL; } // reset to initial state
|
||||
virtual void Update( void ) = 0; // update internal state
|
||||
virtual void Upkeep( void ) { } // lightweight update guaranteed to occur every server tick
|
||||
|
||||
inline bool ComputeUpdateInterval(); // return false is no time has elapsed (interval is zero)
|
||||
inline float GetUpdateInterval();
|
||||
|
||||
virtual INextBot *GetBot( void ) const { return m_bot; }
|
||||
|
||||
private:
|
||||
float m_lastUpdateTime;
|
||||
float m_curInterval;
|
||||
|
||||
friend class INextBot;
|
||||
|
||||
INextBot *m_bot;
|
||||
INextBotComponent *m_nextComponent; // simple linked list of components in the bot
|
||||
};
|
||||
|
||||
|
||||
inline bool INextBotComponent::ComputeUpdateInterval()
|
||||
{
|
||||
if ( m_lastUpdateTime )
|
||||
{
|
||||
float interval = gpGlobals->curtime - m_lastUpdateTime;
|
||||
|
||||
const float minInterval = 0.0001f;
|
||||
if ( interval > minInterval )
|
||||
{
|
||||
m_curInterval = interval;
|
||||
m_lastUpdateTime = gpGlobals->curtime;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// First update - assume a reasonable interval.
|
||||
// We need the very first update to do work, for cases
|
||||
// where the bot was just created and we need to propagate
|
||||
// an event to it immediately.
|
||||
m_curInterval = 0.033f;
|
||||
m_lastUpdateTime = gpGlobals->curtime - m_curInterval;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
inline float INextBotComponent::GetUpdateInterval()
|
||||
{
|
||||
return m_curInterval;
|
||||
}
|
||||
|
||||
#endif // _NEXT_BOT_COMPONENT_INTERFACE_H_
|
||||
@@ -0,0 +1,102 @@
|
||||
// NextBotContextualQueryInterface.h
|
||||
// Queries within the context of the bot's current behavior state
|
||||
// Author: Michael Booth, June 2007
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
|
||||
#ifndef _NEXT_BOT_CONTEXTUAL_QUERY_H_
|
||||
#define _NEXT_BOT_CONTEXTUAL_QUERY_H_
|
||||
|
||||
class INextBot;
|
||||
class CBaseEntity;
|
||||
class CBaseCombatCharacter;
|
||||
class Path;
|
||||
class CKnownEntity;
|
||||
|
||||
/**
|
||||
* Since behaviors can have several concurrent actions active, we ask
|
||||
* the topmost child action first, and if it defers, its parent, and so
|
||||
* on, until we get a definitive answer.
|
||||
*/
|
||||
enum QueryResultType
|
||||
{
|
||||
ANSWER_NO,
|
||||
ANSWER_YES,
|
||||
ANSWER_UNDEFINED
|
||||
};
|
||||
|
||||
// Can pass this into IContextualQuery::IsHindrance to see if any hindrance is ever possible
|
||||
#define IS_ANY_HINDRANCE_POSSIBLE ( (CBaseEntity*)0xFFFFFFFF )
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* The interface for queries that are dependent on the bot's current behavior state
|
||||
*/
|
||||
class IContextualQuery
|
||||
{
|
||||
public:
|
||||
virtual ~IContextualQuery() { }
|
||||
|
||||
virtual QueryResultType ShouldPickUp( const INextBot *me, CBaseEntity *item ) const; // if the desired item was available right now, should we pick it up?
|
||||
virtual QueryResultType ShouldHurry( const INextBot *me ) const; // are we in a hurry?
|
||||
virtual QueryResultType ShouldRetreat( const INextBot *me ) const; // is it time to retreat?
|
||||
virtual QueryResultType ShouldAttack( const INextBot *me, const CKnownEntity *them ) const; // should we attack "them"?
|
||||
virtual QueryResultType IsHindrance( const INextBot *me, CBaseEntity *blocker ) const; // return true if we should wait for 'blocker' that is across our path somewhere up ahead.
|
||||
|
||||
virtual Vector SelectTargetPoint( const INextBot *me, const CBaseCombatCharacter *subject ) const; // given a subject, return the world space position we should aim at
|
||||
|
||||
/**
|
||||
* Allow bot to approve of positions game movement tries to put him into.
|
||||
* This is most useful for bots derived from CBasePlayer that go through
|
||||
* the player movement system.
|
||||
*/
|
||||
virtual QueryResultType IsPositionAllowed( const INextBot *me, const Vector &pos ) const;
|
||||
|
||||
virtual const CKnownEntity * SelectMoreDangerousThreat( const INextBot *me,
|
||||
const CBaseCombatCharacter *subject,
|
||||
const CKnownEntity *threat1,
|
||||
const CKnownEntity *threat2 ) const; // return the more dangerous of the two threats to 'subject', or NULL if we have no opinion
|
||||
};
|
||||
|
||||
inline QueryResultType IContextualQuery::ShouldPickUp( const INextBot *me, CBaseEntity *item ) const
|
||||
{
|
||||
return ANSWER_UNDEFINED;
|
||||
}
|
||||
|
||||
inline QueryResultType IContextualQuery::ShouldHurry( const INextBot *me ) const
|
||||
{
|
||||
return ANSWER_UNDEFINED;
|
||||
}
|
||||
|
||||
inline QueryResultType IContextualQuery::ShouldRetreat( const INextBot *me ) const
|
||||
{
|
||||
return ANSWER_UNDEFINED;
|
||||
}
|
||||
|
||||
inline QueryResultType IContextualQuery::ShouldAttack( const INextBot *me, const CKnownEntity *them ) const
|
||||
{
|
||||
return ANSWER_UNDEFINED;
|
||||
}
|
||||
|
||||
inline QueryResultType IContextualQuery::IsHindrance( const INextBot *me, CBaseEntity *blocker ) const
|
||||
{
|
||||
return ANSWER_UNDEFINED;
|
||||
}
|
||||
|
||||
inline Vector IContextualQuery::SelectTargetPoint( const INextBot *me, const CBaseCombatCharacter *subject ) const
|
||||
{
|
||||
return vec3_origin;
|
||||
}
|
||||
|
||||
inline QueryResultType IContextualQuery::IsPositionAllowed( const INextBot *me, const Vector &pos ) const
|
||||
{
|
||||
return ANSWER_UNDEFINED;
|
||||
}
|
||||
|
||||
inline const CKnownEntity *IContextualQuery::SelectMoreDangerousThreat( const INextBot *me, const CBaseCombatCharacter *subject, const CKnownEntity *threat1, const CKnownEntity *threat2 ) const
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
#endif // _NEXT_BOT_CONTEXTUAL_QUERY_H_
|
||||
@@ -0,0 +1,23 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
#ifndef NEXTBOT_DEBUG_H
|
||||
#define NEXTBOT_DEBUG_H
|
||||
//------------------------------------------------------------------------------
|
||||
// Debug flags for nextbot
|
||||
|
||||
enum NextBotDebugType
|
||||
{
|
||||
NEXTBOT_DEBUG_NONE = 0,
|
||||
NEXTBOT_BEHAVIOR = 0x0001,
|
||||
NEXTBOT_LOOK_AT = 0x0002,
|
||||
NEXTBOT_PATH = 0x0004,
|
||||
NEXTBOT_ANIMATION = 0x0008,
|
||||
NEXTBOT_LOCOMOTION = 0x0010,
|
||||
NEXTBOT_VISION = 0x0020,
|
||||
NEXTBOT_HEARING = 0x0040,
|
||||
NEXTBOT_EVENTS = 0x0080,
|
||||
NEXTBOT_ERRORS = 0x0100, // when things go wrong, like being stuck
|
||||
|
||||
NEXTBOT_DEBUG_ALL = 0xFFFF
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,550 @@
|
||||
// NextBotEventResponderInterface.h
|
||||
// Interface for propagating and responding to events
|
||||
// Author: Michael Booth, May 2006
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
|
||||
#ifndef _NEXT_BOT_EVENT_RESPONDER_INTERFACE_H_
|
||||
#define _NEXT_BOT_EVENT_RESPONDER_INTERFACE_H_
|
||||
|
||||
class Path;
|
||||
class CTakeDamageInfo;
|
||||
class CBaseEntity;
|
||||
class CDOTABaseAbility;
|
||||
|
||||
struct CSoundParameters;
|
||||
struct animevent_t;
|
||||
|
||||
#include "ai_speech.h"
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------------------
|
||||
enum MoveToFailureType
|
||||
{
|
||||
FAIL_NO_PATH_EXISTS,
|
||||
FAIL_STUCK,
|
||||
FAIL_FELL_OFF,
|
||||
};
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Events propagated to/between components.
|
||||
* To add an event, add its signature here and implement its propagation
|
||||
* to derived classes via FirstContainedResponder() and NextContainedResponder().
|
||||
* NOTE: Also add a translator to the Action class in NextBotBehavior.h.
|
||||
*/
|
||||
class INextBotEventResponder
|
||||
{
|
||||
public:
|
||||
DECLARE_CLASS_NOBASE( INextBotEventResponder );
|
||||
|
||||
virtual ~INextBotEventResponder() { }
|
||||
|
||||
// these methods are used by derived classes to define how events propagate
|
||||
virtual INextBotEventResponder *FirstContainedResponder( void ) const { return NULL; }
|
||||
virtual INextBotEventResponder *NextContainedResponder( INextBotEventResponder *current ) const { return NULL; }
|
||||
|
||||
//
|
||||
// Events. All events must be 'extended' by calling the derived class explicitly to ensure propagation.
|
||||
// Each event must implement its propagation in this interface class.
|
||||
//
|
||||
virtual void OnLeaveGround( CBaseEntity *ground ); // invoked when bot leaves ground for any reason
|
||||
virtual void OnLandOnGround( CBaseEntity *ground ); // invoked when bot lands on the ground after being in the air
|
||||
|
||||
virtual void OnContact( CBaseEntity *other, CGameTrace *result = NULL ); // invoked when bot touches 'other'
|
||||
|
||||
virtual void OnMoveToSuccess( const Path *path ); // invoked when a bot reaches the end of the given Path
|
||||
virtual void OnMoveToFailure( const Path *path, MoveToFailureType reason ); // invoked when a bot fails to reach the end of the given Path
|
||||
virtual void OnStuck( void ); // invoked when bot becomes stuck while trying to move
|
||||
virtual void OnUnStuck( void ); // invoked when a previously stuck bot becomes un-stuck and can again move
|
||||
|
||||
virtual void OnPostureChanged( void ); // when bot has assumed new posture (query IBody for posture)
|
||||
|
||||
virtual void OnAnimationActivityComplete( int activity ); // when animation activity has finished playing
|
||||
virtual void OnAnimationActivityInterrupted( int activity );// when animation activity was replaced by another animation
|
||||
virtual void OnAnimationEvent( animevent_t *event ); // when a QC-file animation event is triggered by the current animation sequence
|
||||
|
||||
virtual void OnIgnite( void ); // when bot starts to burn
|
||||
virtual void OnInjured( const CTakeDamageInfo &info ); // when bot is damaged by something
|
||||
virtual void OnKilled( const CTakeDamageInfo &info ); // when the bot's health reaches zero
|
||||
virtual void OnOtherKilled( CBaseCombatCharacter *victim, const CTakeDamageInfo &info ); // when someone else dies
|
||||
|
||||
virtual void OnSight( CBaseEntity *subject ); // when subject initially enters bot's visual awareness
|
||||
virtual void OnLostSight( CBaseEntity *subject ); // when subject leaves enters bot's visual awareness
|
||||
|
||||
virtual void OnSound( CBaseEntity *source, const Vector &pos, KeyValues *keys ); // when an entity emits a sound. "pos" is world coordinates of sound. "keys" are from sound's GameData
|
||||
virtual void OnSpokeConcept( CBaseCombatCharacter *who, AIConcept_t concept, AI_Response *response ); // when an Actor speaks a concept
|
||||
virtual void OnWeaponFired( CBaseCombatCharacter *whoFired, CBaseCombatWeapon *weapon ); // when someone fires a weapon
|
||||
|
||||
virtual void OnNavAreaChanged( CNavArea *newArea, CNavArea *oldArea ); // when bot enters a new navigation area
|
||||
|
||||
virtual void OnModelChanged( void ); // when the entity's model has been changed
|
||||
|
||||
virtual void OnPickUp( CBaseEntity *item, CBaseCombatCharacter *giver ); // when something is added to our inventory
|
||||
virtual void OnDrop( CBaseEntity *item ); // when something is removed from our inventory
|
||||
virtual void OnActorEmoted( CBaseCombatCharacter *emoter, int emote ); // when "emoter" does an "emote" (ie: manual voice command, etc)
|
||||
|
||||
virtual void OnCommandAttack( CBaseEntity *victim ); // attack the given entity
|
||||
virtual void OnCommandApproach( const Vector &pos, float range = 0.0f ); // move to within range of the given position
|
||||
virtual void OnCommandApproach( CBaseEntity *goal ); // follow the given leader
|
||||
virtual void OnCommandRetreat( CBaseEntity *threat, float range = 0.0f ); // retreat from the threat at least range units away (0 == infinite)
|
||||
virtual void OnCommandPause( float duration = 0.0f ); // pause for the given duration (0 == forever)
|
||||
virtual void OnCommandResume( void ); // resume after a pause
|
||||
|
||||
virtual void OnCommandString( const char *command ); // for debugging: respond to an arbitrary string representing a generalized command
|
||||
|
||||
virtual void OnShoved( CBaseEntity *pusher ); // 'pusher' has shoved me
|
||||
virtual void OnBlinded( CBaseEntity *blinder ); // 'blinder' has blinded me with a flash of light
|
||||
|
||||
virtual void OnTerritoryContested( int territoryID ); // territory has been invaded and is changing ownership
|
||||
virtual void OnTerritoryCaptured( int territoryID ); // we have captured enemy territory
|
||||
virtual void OnTerritoryLost( int territoryID ); // we have lost territory to the enemy
|
||||
|
||||
virtual void OnWin( void );
|
||||
virtual void OnLose( void );
|
||||
|
||||
#ifdef DOTA_SERVER_DLL
|
||||
virtual void OnCommandMoveTo( const Vector &pos );
|
||||
virtual void OnCommandMoveToAggressive( const Vector &pos );
|
||||
virtual void OnCommandAttack( CBaseEntity *victim, bool bDeny );
|
||||
virtual void OnCastAbilityNoTarget( CDOTABaseAbility *ability );
|
||||
virtual void OnCastAbilityOnPosition( CDOTABaseAbility *ability, const Vector &pos );
|
||||
virtual void OnCastAbilityOnTarget( CDOTABaseAbility *ability, CBaseEntity *target );
|
||||
virtual void OnDropItem( const Vector &pos, CBaseEntity *item );
|
||||
virtual void OnPickupItem( CBaseEntity *item );
|
||||
virtual void OnPickupRune( CBaseEntity *item );
|
||||
virtual void OnStop();
|
||||
virtual void OnFriendThreatened( CBaseEntity *friendly, CBaseEntity *threat );
|
||||
virtual void OnCancelAttack( CBaseEntity *pTarget );
|
||||
virtual void OnDominated();
|
||||
virtual void OnWarped( Vector vStartPos );
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
inline void INextBotEventResponder::OnLeaveGround( CBaseEntity *ground )
|
||||
{
|
||||
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
|
||||
{
|
||||
sub->OnLeaveGround( ground );
|
||||
}
|
||||
}
|
||||
|
||||
inline void INextBotEventResponder::OnLandOnGround( CBaseEntity *ground )
|
||||
{
|
||||
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
|
||||
{
|
||||
sub->OnLandOnGround( ground );
|
||||
}
|
||||
}
|
||||
|
||||
inline void INextBotEventResponder::OnContact( CBaseEntity *other, CGameTrace *result )
|
||||
{
|
||||
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
|
||||
{
|
||||
sub->OnContact( other, result );
|
||||
}
|
||||
}
|
||||
|
||||
inline void INextBotEventResponder::OnMoveToSuccess( const Path *path )
|
||||
{
|
||||
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
|
||||
{
|
||||
sub->OnMoveToSuccess( path );
|
||||
}
|
||||
}
|
||||
|
||||
inline void INextBotEventResponder::OnMoveToFailure( const Path *path, MoveToFailureType reason )
|
||||
{
|
||||
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
|
||||
{
|
||||
sub->OnMoveToFailure( path, reason );
|
||||
}
|
||||
}
|
||||
|
||||
inline void INextBotEventResponder::OnStuck( void )
|
||||
{
|
||||
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
|
||||
{
|
||||
sub->OnStuck();
|
||||
}
|
||||
}
|
||||
|
||||
inline void INextBotEventResponder::OnUnStuck( void )
|
||||
{
|
||||
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
|
||||
{
|
||||
sub->OnUnStuck();
|
||||
}
|
||||
}
|
||||
|
||||
inline void INextBotEventResponder::OnPostureChanged( void )
|
||||
{
|
||||
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
|
||||
{
|
||||
sub->OnPostureChanged();
|
||||
}
|
||||
}
|
||||
|
||||
inline void INextBotEventResponder::OnAnimationActivityComplete( int activity )
|
||||
{
|
||||
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
|
||||
{
|
||||
sub->OnAnimationActivityComplete( activity );
|
||||
}
|
||||
}
|
||||
|
||||
inline void INextBotEventResponder::OnAnimationActivityInterrupted( int activity )
|
||||
{
|
||||
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
|
||||
{
|
||||
sub->OnAnimationActivityInterrupted( activity );
|
||||
}
|
||||
}
|
||||
|
||||
inline void INextBotEventResponder::OnAnimationEvent( animevent_t *event )
|
||||
{
|
||||
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
|
||||
{
|
||||
sub->OnAnimationEvent( event );
|
||||
}
|
||||
}
|
||||
|
||||
inline void INextBotEventResponder::OnIgnite( void )
|
||||
{
|
||||
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
|
||||
{
|
||||
sub->OnIgnite();
|
||||
}
|
||||
}
|
||||
|
||||
inline void INextBotEventResponder::OnInjured( const CTakeDamageInfo &info )
|
||||
{
|
||||
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
|
||||
{
|
||||
sub->OnInjured( info );
|
||||
}
|
||||
}
|
||||
|
||||
inline void INextBotEventResponder::OnKilled( const CTakeDamageInfo &info )
|
||||
{
|
||||
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
|
||||
{
|
||||
sub->OnKilled( info );
|
||||
}
|
||||
}
|
||||
|
||||
inline void INextBotEventResponder::OnOtherKilled( CBaseCombatCharacter *victim, const CTakeDamageInfo &info )
|
||||
{
|
||||
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
|
||||
{
|
||||
sub->OnOtherKilled( victim, info );
|
||||
}
|
||||
}
|
||||
|
||||
inline void INextBotEventResponder::OnSight( CBaseEntity *subject )
|
||||
{
|
||||
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
|
||||
{
|
||||
sub->OnSight( subject );
|
||||
}
|
||||
}
|
||||
|
||||
inline void INextBotEventResponder::OnLostSight( CBaseEntity *subject )
|
||||
{
|
||||
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
|
||||
{
|
||||
sub->OnLostSight( subject );
|
||||
}
|
||||
}
|
||||
|
||||
inline void INextBotEventResponder::OnSound( CBaseEntity *source, const Vector &pos, KeyValues *keys )
|
||||
{
|
||||
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
|
||||
{
|
||||
sub->OnSound( source, pos, keys );
|
||||
}
|
||||
}
|
||||
|
||||
inline void INextBotEventResponder::OnSpokeConcept( CBaseCombatCharacter *who, AIConcept_t concept, AI_Response *response )
|
||||
{
|
||||
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
|
||||
{
|
||||
sub->OnSpokeConcept( who, concept, response );
|
||||
}
|
||||
}
|
||||
|
||||
inline void INextBotEventResponder::OnWeaponFired( CBaseCombatCharacter *whoFired, CBaseCombatWeapon *weapon )
|
||||
{
|
||||
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
|
||||
{
|
||||
sub->OnWeaponFired( whoFired, weapon );
|
||||
}
|
||||
}
|
||||
|
||||
inline void INextBotEventResponder::OnNavAreaChanged( CNavArea *newArea, CNavArea *oldArea )
|
||||
{
|
||||
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
|
||||
{
|
||||
sub->OnNavAreaChanged( newArea, oldArea );
|
||||
}
|
||||
}
|
||||
|
||||
inline void INextBotEventResponder::OnModelChanged( void )
|
||||
{
|
||||
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
|
||||
{
|
||||
sub->OnModelChanged();
|
||||
}
|
||||
}
|
||||
|
||||
inline void INextBotEventResponder::OnPickUp( CBaseEntity *item, CBaseCombatCharacter *giver )
|
||||
{
|
||||
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
|
||||
{
|
||||
sub->OnPickUp( item, giver );
|
||||
}
|
||||
}
|
||||
|
||||
inline void INextBotEventResponder::OnDrop( CBaseEntity *item )
|
||||
{
|
||||
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
|
||||
{
|
||||
sub->OnDrop( item );
|
||||
}
|
||||
}
|
||||
|
||||
inline void INextBotEventResponder::OnActorEmoted( CBaseCombatCharacter *emoter, int emote )
|
||||
{
|
||||
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
|
||||
{
|
||||
sub->OnActorEmoted( emoter, emote );
|
||||
}
|
||||
}
|
||||
|
||||
inline void INextBotEventResponder::OnShoved( CBaseEntity *pusher )
|
||||
{
|
||||
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
|
||||
{
|
||||
sub->OnShoved( pusher );
|
||||
}
|
||||
}
|
||||
|
||||
inline void INextBotEventResponder::OnBlinded( CBaseEntity *blinder )
|
||||
{
|
||||
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
|
||||
{
|
||||
sub->OnBlinded( blinder );
|
||||
}
|
||||
}
|
||||
|
||||
inline void INextBotEventResponder::OnCommandAttack( CBaseEntity *victim )
|
||||
{
|
||||
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
|
||||
{
|
||||
sub->OnCommandAttack( victim );
|
||||
}
|
||||
}
|
||||
|
||||
inline void INextBotEventResponder::OnCommandApproach( const Vector &pos, float range )
|
||||
{
|
||||
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
|
||||
{
|
||||
sub->OnCommandApproach( pos, range );
|
||||
}
|
||||
}
|
||||
|
||||
inline void INextBotEventResponder::OnCommandApproach( CBaseEntity *goal )
|
||||
{
|
||||
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
|
||||
{
|
||||
sub->OnCommandApproach( goal );
|
||||
}
|
||||
}
|
||||
|
||||
inline void INextBotEventResponder::OnCommandRetreat( CBaseEntity *threat, float range )
|
||||
{
|
||||
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
|
||||
{
|
||||
sub->OnCommandRetreat( threat, range );
|
||||
}
|
||||
}
|
||||
|
||||
inline void INextBotEventResponder::OnCommandPause( float duration )
|
||||
{
|
||||
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
|
||||
{
|
||||
sub->OnCommandPause( duration );
|
||||
}
|
||||
}
|
||||
|
||||
inline void INextBotEventResponder::OnCommandResume( void )
|
||||
{
|
||||
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
|
||||
{
|
||||
sub->OnCommandResume();
|
||||
}
|
||||
}
|
||||
|
||||
inline void INextBotEventResponder::OnCommandString( const char *command )
|
||||
{
|
||||
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
|
||||
{
|
||||
sub->OnCommandString( command );
|
||||
}
|
||||
}
|
||||
|
||||
inline void INextBotEventResponder::OnTerritoryContested( int territoryID )
|
||||
{
|
||||
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
|
||||
{
|
||||
sub->OnTerritoryContested( territoryID );
|
||||
}
|
||||
}
|
||||
|
||||
inline void INextBotEventResponder::OnTerritoryCaptured( int territoryID )
|
||||
{
|
||||
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
|
||||
{
|
||||
sub->OnTerritoryCaptured( territoryID );
|
||||
}
|
||||
}
|
||||
|
||||
inline void INextBotEventResponder::OnTerritoryLost( int territoryID )
|
||||
{
|
||||
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
|
||||
{
|
||||
sub->OnTerritoryLost( territoryID );
|
||||
}
|
||||
}
|
||||
|
||||
inline void INextBotEventResponder::OnWin( void )
|
||||
{
|
||||
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
|
||||
{
|
||||
sub->OnWin();
|
||||
}
|
||||
}
|
||||
|
||||
inline void INextBotEventResponder::OnLose( void )
|
||||
{
|
||||
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
|
||||
{
|
||||
sub->OnLose();
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef DOTA_SERVER_DLL
|
||||
inline void INextBotEventResponder::OnCommandMoveTo( const Vector &pos )
|
||||
{
|
||||
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
|
||||
{
|
||||
sub->OnCommandMoveTo( pos );
|
||||
}
|
||||
}
|
||||
|
||||
inline void INextBotEventResponder::OnCommandMoveToAggressive( const Vector &pos )
|
||||
{
|
||||
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
|
||||
{
|
||||
sub->OnCommandMoveToAggressive( pos );
|
||||
}
|
||||
}
|
||||
|
||||
inline void INextBotEventResponder::OnCommandAttack( CBaseEntity *victim, bool bDeny )
|
||||
{
|
||||
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
|
||||
{
|
||||
sub->OnCommandAttack( victim, bDeny );
|
||||
}
|
||||
}
|
||||
|
||||
inline void INextBotEventResponder::OnCastAbilityNoTarget( CDOTABaseAbility *ability )
|
||||
{
|
||||
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
|
||||
{
|
||||
sub->OnCastAbilityNoTarget( ability );
|
||||
}
|
||||
}
|
||||
|
||||
inline void INextBotEventResponder::OnCastAbilityOnPosition( CDOTABaseAbility *ability, const Vector &pos )
|
||||
{
|
||||
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
|
||||
{
|
||||
sub->OnCastAbilityOnPosition( ability, pos );
|
||||
}
|
||||
}
|
||||
|
||||
inline void INextBotEventResponder::OnCastAbilityOnTarget( CDOTABaseAbility *ability, CBaseEntity *target )
|
||||
{
|
||||
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
|
||||
{
|
||||
sub->OnCastAbilityOnTarget( ability, target );
|
||||
}
|
||||
}
|
||||
|
||||
inline void INextBotEventResponder::OnDropItem( const Vector &pos, CBaseEntity *item )
|
||||
{
|
||||
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
|
||||
{
|
||||
sub->OnDropItem( pos, item );
|
||||
}
|
||||
}
|
||||
|
||||
inline void INextBotEventResponder::OnPickupItem( CBaseEntity *item )
|
||||
{
|
||||
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
|
||||
{
|
||||
sub->OnPickupItem( item );
|
||||
}
|
||||
}
|
||||
|
||||
inline void INextBotEventResponder::OnPickupRune( CBaseEntity *item )
|
||||
{
|
||||
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
|
||||
{
|
||||
sub->OnPickupRune( item );
|
||||
}
|
||||
}
|
||||
|
||||
inline void INextBotEventResponder::OnStop()
|
||||
{
|
||||
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
|
||||
{
|
||||
sub->OnStop();
|
||||
}
|
||||
}
|
||||
|
||||
inline void INextBotEventResponder::OnFriendThreatened( CBaseEntity *friendly, CBaseEntity *threat )
|
||||
{
|
||||
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
|
||||
{
|
||||
sub->OnFriendThreatened( friendly, threat );
|
||||
}
|
||||
}
|
||||
|
||||
inline void INextBotEventResponder::OnCancelAttack( CBaseEntity *pTarget )
|
||||
{
|
||||
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
|
||||
{
|
||||
sub->OnCancelAttack( pTarget );
|
||||
}
|
||||
}
|
||||
|
||||
inline void INextBotEventResponder::OnDominated()
|
||||
{
|
||||
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
|
||||
{
|
||||
sub->OnDominated();
|
||||
}
|
||||
}
|
||||
|
||||
inline void INextBotEventResponder::OnWarped( Vector vStartPos )
|
||||
{
|
||||
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
|
||||
{
|
||||
sub->OnWarped( vStartPos );
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // _NEXT_BOT_EVENT_RESPONDER_INTERFACE_H_
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,274 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
// NextBotGroundLocomotion.h
|
||||
// Basic ground-based movement for NextBotCombatCharacters
|
||||
// Author: Michael Booth, February 2009
|
||||
// Note: This is a refactoring of ZombieBotLocomotion from L4D
|
||||
|
||||
#ifndef NEXT_BOT_GROUND_LOCOMOTION_H
|
||||
#define NEXT_BOT_GROUND_LOCOMOTION_H
|
||||
|
||||
#include "NextBotLocomotionInterface.h"
|
||||
#include "nav_mesh.h"
|
||||
|
||||
|
||||
class NextBotCombatCharacter;
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Basic ground-based movement for NextBotCombatCharacters.
|
||||
* This locomotor resolves collisions and assumes a ground-based bot under the influence of gravity.
|
||||
*/
|
||||
class NextBotGroundLocomotion : public ILocomotion
|
||||
{
|
||||
public:
|
||||
DECLARE_CLASS( NextBotGroundLocomotion, ILocomotion );
|
||||
|
||||
NextBotGroundLocomotion( INextBot *bot );
|
||||
virtual ~NextBotGroundLocomotion();
|
||||
|
||||
virtual void Reset( void ); // reset locomotor to initial state
|
||||
virtual void Update( void ); // update internal state
|
||||
|
||||
virtual void Approach( const Vector &pos, float goalWeight = 1.0f ); // move directly towards the given position
|
||||
virtual void DriveTo( const Vector &pos ); // Move the bot to the precise given position immediately,
|
||||
|
||||
virtual bool ClimbUpToLedge( const Vector &landingGoal, const Vector &landingForward, const CBaseEntity *obstacle ); // initiate a jump to an adjacent high ledge, return false if climb can't start
|
||||
virtual void JumpAcrossGap( const Vector &landingGoal, const Vector &landingForward ); // initiate a jump across an empty volume of space to far side
|
||||
virtual void Jump( void ); // initiate a simple undirected jump in the air
|
||||
virtual bool IsClimbingOrJumping( void ) const; // is jumping in any form
|
||||
virtual bool IsClimbingUpToLedge( void ) const; // is climbing up to a high ledge
|
||||
virtual bool IsJumpingAcrossGap( void ) const; // is jumping across a gap to the far side
|
||||
|
||||
virtual void Run( void ); // set desired movement speed to running
|
||||
virtual void Walk( void ); // set desired movement speed to walking
|
||||
virtual void Stop( void ); // set desired movement speed to stopped
|
||||
virtual bool IsRunning( void ) const;
|
||||
virtual void SetDesiredSpeed( float speed ); // set desired speed for locomotor movement
|
||||
virtual float GetDesiredSpeed( void ) const; // returns the current desired speed
|
||||
|
||||
virtual float GetSpeedLimit( void ) const; // get maximum speed bot can reach, regardless of desired speed
|
||||
|
||||
virtual bool IsOnGround( void ) const; // return true if standing on something
|
||||
virtual void OnLeaveGround( CBaseEntity *ground ); // invoked when bot leaves ground for any reason
|
||||
virtual void OnLandOnGround( CBaseEntity *ground ); // invoked when bot lands on the ground after being in the air
|
||||
virtual CBaseEntity *GetGround( void ) const; // return the current ground entity or NULL if not on the ground
|
||||
virtual const Vector &GetGroundNormal( void ) const;// surface normal of the ground we are in contact with
|
||||
|
||||
virtual void ClimbLadder( const CNavLadder *ladder, const CNavArea *dismountGoal ); // climb the given ladder to the top and dismount
|
||||
virtual void DescendLadder( const CNavLadder *ladder, const CNavArea *dismountGoal ); // descend the given ladder to the bottom and dismount
|
||||
virtual bool IsUsingLadder( void ) const;
|
||||
virtual bool IsAscendingOrDescendingLadder( void ) const; // we are actually on the ladder right now, either climbing up or down
|
||||
|
||||
virtual void FaceTowards( const Vector &target ); // rotate body to face towards "target"
|
||||
|
||||
virtual void SetDesiredLean( const QAngle &lean );
|
||||
virtual const QAngle &GetDesiredLean( void ) const;
|
||||
|
||||
virtual const Vector &GetFeet( void ) const; // return position of "feet" - the driving point where the bot contacts the ground
|
||||
|
||||
virtual float GetStepHeight( void ) const; // if delta Z is greater than this, we have to jump to get up
|
||||
virtual float GetMaxJumpHeight( void ) const; // return maximum height of a jump
|
||||
virtual float GetDeathDropHeight( void ) const; // distance at which we will die if we fall
|
||||
|
||||
virtual float GetRunSpeed( void ) const; // get maximum running speed
|
||||
virtual float GetWalkSpeed( void ) const; // get maximum walking speed
|
||||
|
||||
virtual float GetMaxAcceleration( void ) const; // return maximum acceleration of locomotor
|
||||
virtual float GetMaxDeceleration( void ) const; // return maximum deceleration of locomotor
|
||||
|
||||
virtual const Vector &GetAcceleration( void ) const; // return current world space acceleration
|
||||
virtual void SetAcceleration( const Vector &accel ); // set world space acceleration
|
||||
|
||||
virtual const Vector &GetVelocity( void ) const; // return current world space velocity
|
||||
virtual void SetVelocity( const Vector &vel ); // set world space velocity
|
||||
|
||||
virtual void OnMoveToSuccess( const Path *path ); // invoked when an bot reaches its MoveTo goal
|
||||
virtual void OnMoveToFailure( const Path *path, MoveToFailureType reason ); // invoked when an bot fails to reach a MoveTo goal
|
||||
|
||||
private:
|
||||
void UpdatePosition( const Vector &newPos ); // move to newPos, resolving any collisions along the way
|
||||
void UpdateGroundConstraint( void ); // keep ground solid
|
||||
Vector ResolveCollisionV0( Vector from, Vector to, int recursionLimit );
|
||||
|
||||
Vector ResolveZombieCollisions( const Vector &pos ); // push away zombies that are interpenetrating
|
||||
Vector ResolveCollision( const Vector &from, const Vector &to, int recursionLimit ); // check for collisions along move
|
||||
bool DetectCollision( trace_t *pTrace, int &nDestructionAllowed, const Vector &from, const Vector &to, const Vector &vecMins, const Vector &vecMaxs );
|
||||
void ApplyAccumulatedApproach( void );
|
||||
bool DidJustJump( void ) const; // return true if we just started a jump
|
||||
bool TraverseLadder( void ); // return true if we are climbing a ladder
|
||||
|
||||
virtual float GetGravity( void ) const; // return gravity force acting on bot
|
||||
virtual float GetFrictionForward( void ) const; // return magnitude of forward friction
|
||||
virtual float GetFrictionSideways( void ) const; // return magnitude of lateral friction
|
||||
virtual float GetMaxYawRate( void ) const; // return max rate of yaw rotation
|
||||
|
||||
|
||||
private:
|
||||
NextBotCombatCharacter *m_nextBot;
|
||||
|
||||
Vector m_priorPos; // last update's position
|
||||
Vector m_lastValidPos; // last valid position (not interpenetrating)
|
||||
|
||||
Vector m_acceleration;
|
||||
Vector m_velocity;
|
||||
|
||||
float m_desiredSpeed; // speed bot wants to be moving
|
||||
float m_actualSpeed; // actual speed bot is moving
|
||||
|
||||
float m_maxRunSpeed;
|
||||
|
||||
float m_forwardLean;
|
||||
float m_sideLean;
|
||||
QAngle m_desiredLean;
|
||||
|
||||
bool m_isJumping; // if true, we have jumped and have not yet hit the ground
|
||||
bool m_isJumpingAcrossGap; // if true, we have jumped across a gap and have not yet hit the ground
|
||||
EHANDLE m_ground; // have to manage this ourselves, since MOVETYPE_CUSTOM always NULLs out GetGroundEntity()
|
||||
Vector m_groundNormal; // surface normal of the ground we are in contact with
|
||||
bool m_isClimbingUpToLedge; // true if we are jumping up to an adjacent ledge
|
||||
Vector m_ledgeJumpGoalPos;
|
||||
bool m_isUsingFullFeetTrace; // true if we're in the air and tracing the lowest StepHeight in ResolveCollision
|
||||
|
||||
const CNavLadder *m_ladder; // ladder we are currently climbing/descending
|
||||
const CNavArea *m_ladderDismountGoal; // the area we enter when finished with our ladder move
|
||||
bool m_isGoingUpLadder; // if false, we're going down
|
||||
|
||||
CountdownTimer m_inhibitObstacleAvoidanceTimer; // when active, turn off path following feelers
|
||||
|
||||
CountdownTimer m_wiggleTimer; // for wiggling
|
||||
NavRelativeDirType m_wiggleDirection;
|
||||
|
||||
mutable Vector m_eyePos; // for use with GetEyes(), etc.
|
||||
|
||||
Vector m_moveVector; // the direction of our motion in XY plane
|
||||
float m_moveYaw; // global yaw of movement direction
|
||||
|
||||
Vector m_accumApproachVectors; // weighted sum of Approach() calls since last update
|
||||
float m_accumApproachWeights;
|
||||
bool m_bRecomputePostureOnCollision;
|
||||
|
||||
CountdownTimer m_ignorePhysicsPropTimer; // if active, don't collide with physics props (because we got stuck in one)
|
||||
EHANDLE m_ignorePhysicsProp; // which prop to ignore
|
||||
};
|
||||
|
||||
|
||||
inline float NextBotGroundLocomotion::GetGravity( void ) const
|
||||
{
|
||||
return 1000.0f;
|
||||
}
|
||||
|
||||
inline float NextBotGroundLocomotion::GetFrictionForward( void ) const
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
inline float NextBotGroundLocomotion::GetFrictionSideways( void ) const
|
||||
{
|
||||
return 3.0f;
|
||||
}
|
||||
|
||||
inline float NextBotGroundLocomotion::GetMaxYawRate( void ) const
|
||||
{
|
||||
return 250.0f;
|
||||
}
|
||||
|
||||
inline CBaseEntity *NextBotGroundLocomotion::GetGround( void ) const
|
||||
{
|
||||
return m_ground;
|
||||
}
|
||||
|
||||
|
||||
inline const Vector &NextBotGroundLocomotion::GetGroundNormal( void ) const
|
||||
{
|
||||
return m_groundNormal;
|
||||
}
|
||||
|
||||
|
||||
inline void NextBotGroundLocomotion::SetDesiredLean( const QAngle &lean )
|
||||
{
|
||||
m_desiredLean = lean;
|
||||
}
|
||||
|
||||
|
||||
inline const QAngle &NextBotGroundLocomotion::GetDesiredLean( void ) const
|
||||
{
|
||||
return m_desiredLean;
|
||||
}
|
||||
|
||||
|
||||
inline void NextBotGroundLocomotion::SetDesiredSpeed( float speed )
|
||||
{
|
||||
m_desiredSpeed = speed;
|
||||
}
|
||||
|
||||
|
||||
inline float NextBotGroundLocomotion::GetDesiredSpeed( void ) const
|
||||
{
|
||||
return m_desiredSpeed;
|
||||
}
|
||||
|
||||
|
||||
inline bool NextBotGroundLocomotion::IsClimbingOrJumping( void ) const
|
||||
{
|
||||
return m_isJumping;
|
||||
}
|
||||
|
||||
inline bool NextBotGroundLocomotion::IsClimbingUpToLedge( void ) const
|
||||
{
|
||||
return m_isClimbingUpToLedge;
|
||||
}
|
||||
|
||||
inline bool NextBotGroundLocomotion::IsJumpingAcrossGap( void ) const
|
||||
{
|
||||
return m_isJumpingAcrossGap;
|
||||
}
|
||||
|
||||
inline bool NextBotGroundLocomotion::IsRunning( void ) const
|
||||
{
|
||||
/// @todo Rethink interface to distinguish actual state vs desired state (do we want to be running, or are we actually at running speed right now)
|
||||
return m_actualSpeed > 0.9f * GetRunSpeed();
|
||||
}
|
||||
|
||||
|
||||
inline float NextBotGroundLocomotion::GetStepHeight( void ) const
|
||||
{
|
||||
return 18.0f;
|
||||
}
|
||||
|
||||
|
||||
inline float NextBotGroundLocomotion::GetMaxJumpHeight( void ) const
|
||||
{
|
||||
return 180.0f; // 120.0f; // 84.0f; // 58.0f;
|
||||
}
|
||||
|
||||
|
||||
inline float NextBotGroundLocomotion::GetDeathDropHeight( void ) const
|
||||
{
|
||||
return 200.0f;
|
||||
}
|
||||
|
||||
|
||||
inline float NextBotGroundLocomotion::GetRunSpeed( void ) const
|
||||
{
|
||||
return 150.0f;
|
||||
}
|
||||
|
||||
|
||||
inline float NextBotGroundLocomotion::GetWalkSpeed( void ) const
|
||||
{
|
||||
return 75.0f;
|
||||
}
|
||||
|
||||
inline float NextBotGroundLocomotion::GetMaxAcceleration( void ) const
|
||||
{
|
||||
return 500.0f;
|
||||
}
|
||||
|
||||
inline float NextBotGroundLocomotion::GetMaxDeceleration( void ) const
|
||||
{
|
||||
return 500.0f;
|
||||
}
|
||||
|
||||
|
||||
#endif // NEXT_BOT_GROUND_LOCOMOTION_H
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
// NextBotHearingInterface.h
|
||||
// Interface for auditory queries of a bot
|
||||
// Author: Michael Booth, April 2005
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
|
||||
#ifndef _NEXT_BOT_HEARING_INTERFACE_H_
|
||||
#define _NEXT_BOT_HEARING_INTERFACE_H_
|
||||
|
||||
#include "NextBotComponentInterface.h"
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* The interface for hearing sounds
|
||||
*/
|
||||
class IHearing : public INextBotComponent
|
||||
{
|
||||
public:
|
||||
IHearing( INextBot *bot ) : INextBotComponent( bot ) { }
|
||||
virtual ~IHearing() { }
|
||||
|
||||
virtual void Reset( void ); // reset to initial state
|
||||
virtual void Update( void ); // update internal state
|
||||
|
||||
virtual float GetTimeSinceHeard( int team ) const; // return time since we heard any member of the given team
|
||||
|
||||
virtual CBaseEntity *GetClosestRecognized( int team = TEAM_ANY ) const; // return the closest recognized entity
|
||||
virtual int GetRecognizedCount( int team, float rangeLimit = -1.0f ) const; // return the number of actors on the given team visible to us closer than rangeLimit
|
||||
|
||||
virtual float GetMaxHearingRange( void ) const; // return maximum distance we can hear
|
||||
virtual float GetMinRecognizeTime( void ) const; // return HEARING reaction time
|
||||
};
|
||||
|
||||
|
||||
#endif // _NEXT_BOT_HEARING_INTERFACE_H_
|
||||
@@ -0,0 +1,91 @@
|
||||
// NextBotIntentionInterface.cpp
|
||||
// Interface for intentional thinking
|
||||
// Author: Michael Booth, November 2007
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
|
||||
#include "cbase.h"
|
||||
|
||||
#include "NextBotInterface.h"
|
||||
#include "NextBotIntentionInterface.h"
|
||||
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Given a subject, return the world space position we should aim at
|
||||
*/
|
||||
Vector IIntention::SelectTargetPoint( const INextBot *me, const CBaseCombatCharacter *subject ) const
|
||||
{
|
||||
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
|
||||
{
|
||||
const IContextualQuery *query = dynamic_cast< const IContextualQuery * >( sub );
|
||||
if ( query )
|
||||
{
|
||||
// return the response of the first responder that gives a definitive answer
|
||||
Vector result = query->SelectTargetPoint( me, subject );
|
||||
if ( result != vec3_origin )
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// no answer, use a reasonable position
|
||||
Vector threatMins, threatMaxs;
|
||||
subject->CollisionProp()->WorldSpaceAABB( &threatMins, &threatMaxs );
|
||||
Vector targetPoint = subject->GetAbsOrigin();
|
||||
targetPoint.z += 0.7f * ( threatMaxs.z - threatMins.z );
|
||||
|
||||
return targetPoint;
|
||||
}
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Given two threats, decide which one is more dangerous
|
||||
*/
|
||||
const CKnownEntity *IIntention::SelectMoreDangerousThreat( const INextBot *me, const CBaseCombatCharacter *subject, const CKnownEntity *threat1, const CKnownEntity *threat2 ) const
|
||||
{
|
||||
if ( !threat1 || threat1->IsObsolete() )
|
||||
{
|
||||
if ( threat2 && !threat2->IsObsolete() )
|
||||
return threat2;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
else if ( !threat2 || threat2->IsObsolete() )
|
||||
{
|
||||
return threat1;
|
||||
}
|
||||
|
||||
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
|
||||
{
|
||||
const IContextualQuery *query = dynamic_cast< const IContextualQuery * >( sub );
|
||||
if ( query )
|
||||
{
|
||||
// return the response of the first responder that gives a definitive answer
|
||||
const CKnownEntity *result = query->SelectMoreDangerousThreat( me, subject, threat1, threat2 );
|
||||
if ( result )
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// no specific decision was made - return closest threat as most dangerous
|
||||
float range1 = ( subject->GetAbsOrigin() - threat1->GetLastKnownPosition() ).LengthSqr();
|
||||
float range2 = ( subject->GetAbsOrigin() - threat2->GetLastKnownPosition() ).LengthSqr();
|
||||
|
||||
if ( range1 < range2 )
|
||||
{
|
||||
return threat1;
|
||||
}
|
||||
|
||||
return threat2;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,211 @@
|
||||
// NextBotIntentionInterface.h
|
||||
// Interface for intentional thinking
|
||||
// Author: Michael Booth, April 2005
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
|
||||
#ifndef _NEXT_BOT_INTENTION_INTERFACE_H_
|
||||
#define _NEXT_BOT_INTENTION_INTERFACE_H_
|
||||
|
||||
#include "NextBotComponentInterface.h"
|
||||
#include "NextBotContextualQueryInterface.h"
|
||||
|
||||
class INextBot;
|
||||
|
||||
//
|
||||
// Insert this macro in your INextBot-derived class declaration to
|
||||
// create a IIntention-derived class that handles the bookkeeping
|
||||
// of instantiating a Behavior with an initial Action and updating it.
|
||||
//
|
||||
#define DECLARE_INTENTION_INTERFACE( Actor ) \
|
||||
\
|
||||
class Actor##Intention : public IIntention \
|
||||
{ \
|
||||
public: \
|
||||
Actor##Intention( Actor *me ); \
|
||||
virtual ~Actor##Intention(); \
|
||||
virtual void Reset( void ); \
|
||||
virtual void Update( void ); \
|
||||
virtual INextBotEventResponder *FirstContainedResponder( void ) const { return m_behavior; } \
|
||||
virtual INextBotEventResponder *NextContainedResponder( INextBotEventResponder *current ) const { return NULL; } \
|
||||
private: \
|
||||
Behavior< Actor > *m_behavior; \
|
||||
}; \
|
||||
\
|
||||
public: virtual IIntention *GetIntentionInterface( void ) const { return m_intention; } \
|
||||
private: Actor##Intention *m_intention; \
|
||||
public:
|
||||
|
||||
|
||||
//
|
||||
// Use this macro to create the implementation code for the IIntention-derived class
|
||||
// declared above. Since this requires InitialAction, it must occur after
|
||||
// that Action has been declared, so it can be new'd here.
|
||||
//
|
||||
#define IMPLEMENT_INTENTION_INTERFACE( Actor, InitialAction ) \
|
||||
Actor::Actor##Intention::Actor##Intention( Actor *me ) : IIntention( me ) { m_behavior = new Behavior< Actor >( new InitialAction ); } \
|
||||
Actor::Actor##Intention::~Actor##Intention() { delete m_behavior; } \
|
||||
void Actor::Actor##Intention::Reset( void ) { delete m_behavior; m_behavior = new Behavior< Actor >( new InitialAction ); } \
|
||||
void Actor::Actor##Intention::Update( void ) { m_behavior->Update( static_cast< Actor * >( GetBot() ), GetUpdateInterval() ); }
|
||||
|
||||
|
||||
//
|
||||
// Use this macro in the constructor of your bot to allocate the IIntention-derived class
|
||||
//
|
||||
#define ALLOCATE_INTENTION_INTERFACE( Actor ) { m_intention = new Actor##Intention( this ); }
|
||||
|
||||
//
|
||||
// Use this macro in the destructor of your bot to deallocate the IIntention-derived class
|
||||
//
|
||||
#define DEALLOCATE_INTENTION_INTERFACE { if ( m_intention ) delete m_intention; }
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* The interface for intentional thinking.
|
||||
* The assumption is that this is a container for one or more concurrent Behaviors.
|
||||
* The "primary" Behavior is the FirstContainedResponder, and so on.
|
||||
* IContextualQuery requests are prioritized in contained responder order, such that the first responder
|
||||
* that returns a definitive answer is accepted. WITHIN a given responder (ie: a Behavior), the deepest child
|
||||
* Behavior in the active stack is asked first, then its parent, and so on, allowing the most specific active
|
||||
* Behavior to override the query responses of its more general parent Behaviors.
|
||||
*/
|
||||
class IIntention : public INextBotComponent, public IContextualQuery
|
||||
{
|
||||
public:
|
||||
IIntention( INextBot *bot ) : INextBotComponent( bot ) { }
|
||||
virtual ~IIntention() { }
|
||||
|
||||
virtual void Reset( void ) { INextBotComponent::Reset(); } // reset to initial state
|
||||
virtual void Update( void ) { } // update internal state
|
||||
|
||||
// IContextualQuery propagation --------------------------------
|
||||
virtual QueryResultType ShouldPickUp( const INextBot *me, CBaseEntity *item ) const; // if the desired item was available right now, should we pick it up?
|
||||
virtual QueryResultType ShouldHurry( const INextBot *me ) const; // are we in a hurry?
|
||||
virtual QueryResultType ShouldRetreat( const INextBot *me ) const; // is it time to retreat?
|
||||
virtual QueryResultType ShouldAttack( const INextBot *me, const CKnownEntity *them ) const; // should we attack "them"?
|
||||
virtual QueryResultType IsHindrance( const INextBot *me, CBaseEntity *blocker ) const; // return true if we should wait for 'blocker' that is across our path somewhere up ahead.
|
||||
virtual Vector SelectTargetPoint( const INextBot *me, const CBaseCombatCharacter *subject ) const; // given a subject, return the world space position we should aim at
|
||||
virtual QueryResultType IsPositionAllowed( const INextBot *me, const Vector &pos ) const; // is the a place we can be?
|
||||
virtual const CKnownEntity * SelectMoreDangerousThreat( const INextBot *me,
|
||||
const CBaseCombatCharacter *subject, // the subject of the danger
|
||||
const CKnownEntity *threat1,
|
||||
const CKnownEntity *threat2 ) const; // return the more dangerous of the two threats, or NULL if we have no opinion
|
||||
// NOTE: As further queries are added, update the Behavior class to propagate them
|
||||
};
|
||||
|
||||
|
||||
inline QueryResultType IIntention::ShouldPickUp( const INextBot *me, CBaseEntity *item ) const
|
||||
{
|
||||
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
|
||||
{
|
||||
const IContextualQuery *query = dynamic_cast< const IContextualQuery * >( sub );
|
||||
if ( query )
|
||||
{
|
||||
// return the response of the first responder that gives a definitive answer
|
||||
QueryResultType result = query->ShouldPickUp( me, item );
|
||||
if ( result != ANSWER_UNDEFINED )
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ANSWER_UNDEFINED;
|
||||
}
|
||||
|
||||
|
||||
inline QueryResultType IIntention::ShouldHurry( const INextBot *me ) const
|
||||
{
|
||||
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
|
||||
{
|
||||
const IContextualQuery *query = dynamic_cast< const IContextualQuery * >( sub );
|
||||
if ( query )
|
||||
{
|
||||
// return the response of the first responder that gives a definitive answer
|
||||
QueryResultType result = query->ShouldHurry( me );
|
||||
if ( result != ANSWER_UNDEFINED )
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ANSWER_UNDEFINED;
|
||||
}
|
||||
|
||||
|
||||
inline QueryResultType IIntention::ShouldRetreat( const INextBot *me ) const
|
||||
{
|
||||
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
|
||||
{
|
||||
const IContextualQuery *query = dynamic_cast< const IContextualQuery * >( sub );
|
||||
if ( query )
|
||||
{
|
||||
// return the response of the first responder that gives a definitive answer
|
||||
QueryResultType result = query->ShouldRetreat( me );
|
||||
if ( result != ANSWER_UNDEFINED )
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ANSWER_UNDEFINED;
|
||||
}
|
||||
|
||||
|
||||
inline QueryResultType IIntention::ShouldAttack( const INextBot *me, const CKnownEntity *them ) const
|
||||
{
|
||||
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
|
||||
{
|
||||
const IContextualQuery *query = dynamic_cast< const IContextualQuery * >( sub );
|
||||
if ( query )
|
||||
{
|
||||
// return the response of the first responder that gives a definitive answer
|
||||
QueryResultType result = query->ShouldAttack( me, them );
|
||||
if ( result != ANSWER_UNDEFINED )
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ANSWER_UNDEFINED;
|
||||
}
|
||||
|
||||
|
||||
inline QueryResultType IIntention::IsHindrance( const INextBot *me, CBaseEntity *blocker ) const
|
||||
{
|
||||
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
|
||||
{
|
||||
const IContextualQuery *query = dynamic_cast< const IContextualQuery * >( sub );
|
||||
if ( query )
|
||||
{
|
||||
// return the response of the first responder that gives a definitive answer
|
||||
QueryResultType result = query->IsHindrance( me, blocker );
|
||||
if ( result != ANSWER_UNDEFINED )
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ANSWER_UNDEFINED;
|
||||
}
|
||||
|
||||
|
||||
inline QueryResultType IIntention::IsPositionAllowed( const INextBot *me, const Vector &pos ) const
|
||||
{
|
||||
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
|
||||
{
|
||||
const IContextualQuery *query = dynamic_cast< const IContextualQuery * >( sub );
|
||||
if ( query )
|
||||
{
|
||||
// return the response of the first responder that gives a definitive answer
|
||||
QueryResultType result = query->IsPositionAllowed( me, pos );
|
||||
if ( result != ANSWER_UNDEFINED )
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ANSWER_UNDEFINED;
|
||||
}
|
||||
|
||||
|
||||
#endif // _NEXT_BOT_INTENTION_INTERFACE_H_
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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_
|
||||
@@ -0,0 +1,175 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
// NextBotKnownEntity.h
|
||||
// Encapsulation of being aware of an entity
|
||||
// Author: Michael Booth, June 2009
|
||||
|
||||
#ifndef NEXT_BOT_KNOWN_ENTITY_H
|
||||
#define NEXT_BOT_KNOWN_ENTITY_H
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
/**
|
||||
* A "known entity" is an entity that we have seen or heard at some point
|
||||
* and which may or may not be immediately visible to us right now but which
|
||||
* we remember the last place we encountered it, and when.
|
||||
*
|
||||
* TODO: Enhance interface to allow for sets of areas where an unseen entity
|
||||
* could potentially be, knowing his last position and his rate of movement.
|
||||
*/
|
||||
class CKnownEntity
|
||||
{
|
||||
public:
|
||||
// constructing assumes we currently know about this entity
|
||||
CKnownEntity( CBaseEntity *who )
|
||||
{
|
||||
m_who = who;
|
||||
m_whenLastSeen = -1.0f;
|
||||
m_whenLastBecameVisible = -1.0f;
|
||||
m_isVisible = false;
|
||||
m_whenBecameKnown = gpGlobals->curtime;
|
||||
m_hasLastKnownPositionBeenSeen = false;
|
||||
UpdatePosition();
|
||||
}
|
||||
|
||||
virtual ~CKnownEntity() { }
|
||||
|
||||
virtual void Destroy( void )
|
||||
{
|
||||
m_who = NULL;
|
||||
m_isVisible = false;
|
||||
}
|
||||
|
||||
virtual void UpdatePosition( void ) // could be seen or heard, but now the entity's position is known
|
||||
{
|
||||
if ( m_who.Get() )
|
||||
{
|
||||
m_lastKnownPostion = m_who->GetAbsOrigin();
|
||||
m_lastKnownArea = m_who->MyCombatCharacterPointer() ? m_who->MyCombatCharacterPointer()->GetLastKnownArea() : NULL;
|
||||
m_whenLastKnown = gpGlobals->curtime;
|
||||
}
|
||||
}
|
||||
|
||||
virtual CBaseEntity *GetEntity( void ) const
|
||||
{
|
||||
return m_who;
|
||||
}
|
||||
|
||||
virtual const Vector &GetLastKnownPosition( void ) const
|
||||
{
|
||||
return m_lastKnownPostion;
|
||||
}
|
||||
|
||||
// Have we had a clear view of the last known position of this entity?
|
||||
// This encapsulates the idea of "I just saw a guy right over *there* a few seconds ago, but I don't know where he is now"
|
||||
virtual bool HasLastKnownPositionBeenSeen( void ) const
|
||||
{
|
||||
return m_hasLastKnownPositionBeenSeen;
|
||||
}
|
||||
|
||||
virtual void MarkLastKnownPositionAsSeen( void )
|
||||
{
|
||||
m_hasLastKnownPositionBeenSeen = true;
|
||||
}
|
||||
|
||||
virtual const CNavArea *GetLastKnownArea( void ) const
|
||||
{
|
||||
return m_lastKnownArea;
|
||||
}
|
||||
|
||||
virtual float GetTimeSinceLastKnown( void ) const
|
||||
{
|
||||
return gpGlobals->curtime - m_whenLastKnown;
|
||||
}
|
||||
|
||||
virtual float GetTimeSinceBecameKnown( void ) const
|
||||
{
|
||||
return gpGlobals->curtime - m_whenBecameKnown;
|
||||
}
|
||||
|
||||
virtual void UpdateVisibilityStatus( bool visible )
|
||||
{
|
||||
if ( visible )
|
||||
{
|
||||
if ( !m_isVisible )
|
||||
{
|
||||
// just became visible
|
||||
m_whenLastBecameVisible = gpGlobals->curtime;
|
||||
}
|
||||
|
||||
m_whenLastSeen = gpGlobals->curtime;
|
||||
}
|
||||
|
||||
m_isVisible = visible;
|
||||
}
|
||||
|
||||
virtual bool IsVisibleInFOVNow( void ) const // return true if this entity is currently visible and in my field of view
|
||||
{
|
||||
return m_isVisible;
|
||||
}
|
||||
|
||||
virtual bool IsVisibleRecently( void ) const // return true if this entity is visible or was very recently visible
|
||||
{
|
||||
if ( m_isVisible )
|
||||
return true;
|
||||
|
||||
if ( WasEverVisible() && GetTimeSinceLastSeen() < 3.0f )
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual float GetTimeSinceBecameVisible( void ) const
|
||||
{
|
||||
return gpGlobals->curtime - m_whenLastBecameVisible;
|
||||
}
|
||||
|
||||
virtual float GetTimeWhenBecameVisible( void ) const
|
||||
{
|
||||
return m_whenLastBecameVisible;
|
||||
}
|
||||
|
||||
virtual float GetTimeSinceLastSeen( void ) const
|
||||
{
|
||||
return gpGlobals->curtime - m_whenLastSeen;
|
||||
}
|
||||
|
||||
virtual bool WasEverVisible( void ) const
|
||||
{
|
||||
return m_whenLastSeen > 0.0f;
|
||||
}
|
||||
|
||||
// has our knowledge of this entity become obsolete?
|
||||
virtual bool IsObsolete( void ) const
|
||||
{
|
||||
return GetEntity() == NULL || !m_who->IsAlive() || GetTimeSinceLastKnown() > 10.0f;
|
||||
}
|
||||
|
||||
virtual bool operator==( const CKnownEntity &other ) const
|
||||
{
|
||||
if ( GetEntity() == NULL || other.GetEntity() == NULL )
|
||||
return false;
|
||||
|
||||
return ( GetEntity() == other.GetEntity() );
|
||||
}
|
||||
|
||||
virtual bool Is( CBaseEntity *who ) const
|
||||
{
|
||||
if ( GetEntity() == NULL || who == NULL )
|
||||
return false;
|
||||
|
||||
return ( GetEntity() == who );
|
||||
}
|
||||
|
||||
private:
|
||||
CHandle< CBaseEntity > m_who;
|
||||
Vector m_lastKnownPostion;
|
||||
bool m_hasLastKnownPositionBeenSeen;
|
||||
CNavArea *m_lastKnownArea;
|
||||
float m_whenLastSeen;
|
||||
float m_whenLastBecameVisible;
|
||||
float m_whenLastKnown; // last seen or heard, confirming its existance
|
||||
float m_whenBecameKnown;
|
||||
bool m_isVisible; // flagged by IVision update as visible or not
|
||||
};
|
||||
|
||||
|
||||
#endif // NEXT_BOT_KNOWN_ENTITY_H
|
||||
@@ -0,0 +1,520 @@
|
||||
// NextBotLocomotionInterface.cpp
|
||||
// Common functionality for all NextBot locomotors
|
||||
// Author: Michael Booth, April 2005
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
|
||||
#include "cbase.h"
|
||||
|
||||
#include "BasePropDoor.h"
|
||||
|
||||
#include "nav_area.h"
|
||||
#include "NextBot.h"
|
||||
#include "NextBotUtil.h"
|
||||
#include "NextBotLocomotionInterface.h"
|
||||
#include "NextBotBodyInterface.h"
|
||||
|
||||
#include "tier0/vprof.h"
|
||||
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
// how far a bot must move to not be considered "stuck"
|
||||
#define STUCK_RADIUS 100.0f
|
||||
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Reset to initial state
|
||||
*/
|
||||
ILocomotion::ILocomotion( INextBot *bot ) : INextBotComponent( bot )
|
||||
{
|
||||
Reset();
|
||||
}
|
||||
|
||||
ILocomotion::~ILocomotion()
|
||||
{
|
||||
}
|
||||
|
||||
void ILocomotion::Reset( void )
|
||||
{
|
||||
INextBotComponent::Reset();
|
||||
|
||||
m_motionVector = Vector( 1.0f, 0.0f, 0.0f );
|
||||
m_speed = 0.0f;
|
||||
m_groundMotionVector = m_motionVector;
|
||||
m_groundSpeed = m_speed;
|
||||
|
||||
m_moveRequestTimer.Invalidate();
|
||||
|
||||
m_isStuck = false;
|
||||
m_stuckTimer.Invalidate();
|
||||
m_stuckPos = vec3_origin;
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Update internal state
|
||||
*/
|
||||
void ILocomotion::Update( void )
|
||||
{
|
||||
StuckMonitor();
|
||||
|
||||
// maintain motion vector and speed values
|
||||
const Vector &vel = GetVelocity();
|
||||
m_speed = vel.Length();
|
||||
m_groundSpeed = vel.AsVector2D().Length();
|
||||
|
||||
const float velocityThreshold = 10.0f;
|
||||
if ( m_speed > velocityThreshold )
|
||||
{
|
||||
m_motionVector = vel / m_speed;
|
||||
}
|
||||
|
||||
if ( m_groundSpeed > velocityThreshold )
|
||||
{
|
||||
m_groundMotionVector.x = vel.x / m_groundSpeed;
|
||||
m_groundMotionVector.y = vel.y / m_groundSpeed;
|
||||
m_groundMotionVector.z = 0.0f;
|
||||
}
|
||||
|
||||
if ( GetBot()->IsDebugging( NEXTBOT_LOCOMOTION ) )
|
||||
{
|
||||
// show motion vector
|
||||
NDebugOverlay::HorzArrow( GetFeet(), GetFeet() + 25.0f * m_groundMotionVector, 3.0f, 100, 255, 0, 255, true, 0.1f );
|
||||
NDebugOverlay::HorzArrow( GetFeet(), GetFeet() + 25.0f * m_motionVector, 5.0f, 255, 255, 0, 255, true, 0.1f );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
void ILocomotion::AdjustPosture( const Vector &moveGoal )
|
||||
{
|
||||
// This function has no effect if we're not standing or crouching
|
||||
IBody *body = GetBot()->GetBodyInterface();
|
||||
if ( !body->IsActualPosture( IBody::STAND ) && !body->IsActualPosture( IBody::CROUCH ) )
|
||||
return;
|
||||
|
||||
//
|
||||
// Stand or crouch as needed
|
||||
//
|
||||
|
||||
// get bounding limits, ignoring step-upable height
|
||||
const Vector &mins = body->GetHullMins() + Vector( 0, 0, GetStepHeight() );
|
||||
|
||||
const float halfSize = body->GetHullWidth()/2.0f;
|
||||
Vector standMaxs( halfSize, halfSize, body->GetStandHullHeight() );
|
||||
|
||||
trace_t trace;
|
||||
NextBotTraversableTraceFilter filter( GetBot(), ILocomotion::IMMEDIATELY );
|
||||
|
||||
// snap forward movement vector along floor
|
||||
const Vector &groundNormal = GetGroundNormal();
|
||||
const Vector &feet = GetFeet();
|
||||
Vector moveDir = moveGoal - feet;
|
||||
float moveLength = moveDir.NormalizeInPlace();
|
||||
Vector left( -moveDir.y, moveDir.x, 0.0f );
|
||||
Vector goal = feet + moveLength * CrossProduct( left, groundNormal ).Normalized();
|
||||
|
||||
TraceHull( feet, goal, mins, standMaxs, body->GetSolidMask(), &filter, &trace );
|
||||
|
||||
if ( trace.fraction >= 1.0f && !trace.startsolid )
|
||||
{
|
||||
// no collision while standing
|
||||
if ( body->IsActualPosture( IBody::CROUCH ) )
|
||||
{
|
||||
body->SetDesiredPosture( IBody::STAND );
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if ( body->IsActualPosture( IBody::CROUCH ) )
|
||||
return;
|
||||
|
||||
// crouch hull check
|
||||
Vector crouchMaxs( halfSize, halfSize, body->GetCrouchHullHeight() );
|
||||
|
||||
TraceHull( feet, goal, mins, crouchMaxs, body->GetSolidMask(), &filter, &trace );
|
||||
|
||||
if ( trace.fraction >= 1.0f && !trace.startsolid )
|
||||
{
|
||||
// no collision while crouching
|
||||
body->SetDesiredPosture( IBody::CROUCH );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Move directly towards the given position
|
||||
*/
|
||||
void ILocomotion::Approach( const Vector &goalPos, float goalWeight )
|
||||
{
|
||||
// there is a desire to move
|
||||
m_moveRequestTimer.Start();
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Move the bot to the precise given position immediately
|
||||
*/
|
||||
void ILocomotion::DriveTo( const Vector &pos )
|
||||
{
|
||||
// there is a desire to move
|
||||
m_moveRequestTimer.Start();
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Return true if this locomotor could potentially move along the line given.
|
||||
* If false is returned, fraction of walkable ray is returned in 'fraction'
|
||||
*/
|
||||
bool ILocomotion::IsPotentiallyTraversable( const Vector &from, const Vector &to, TraverseWhenType when, float *fraction ) const
|
||||
{
|
||||
VPROF_BUDGET( "Locomotion::IsPotentiallyTraversable", "NextBotExpensive" );
|
||||
|
||||
// if 'to' is high above us, it's not directly traversable
|
||||
// Adding a bit of fudge room to allow for floating point roundoff errors
|
||||
if ( ( to.z - from.z ) > GetMaxJumpHeight() + 0.1f )
|
||||
{
|
||||
Vector along = to - from;
|
||||
along.NormalizeInPlace();
|
||||
if ( along.z > GetTraversableSlopeLimit() )
|
||||
{
|
||||
if ( fraction )
|
||||
{
|
||||
*fraction = 0.0f;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
trace_t result;
|
||||
NextBotTraversableTraceFilter filter( GetBot(), when );
|
||||
|
||||
// use a small hull since we cannot simulate collision resolution and avoidance along the way
|
||||
const float probeSize = 0.25f * GetBot()->GetBodyInterface()->GetHullWidth(); // Cant be TOO small, or open stairwells/grates/etc will cause problems
|
||||
const float probeZ = GetStepHeight();
|
||||
|
||||
Vector hullMin( -probeSize, -probeSize, probeZ );
|
||||
Vector hullMax( probeSize, probeSize, GetBot()->GetBodyInterface()->GetCrouchHullHeight() );
|
||||
TraceHull( from, to, hullMin, hullMax, GetBot()->GetBodyInterface()->GetSolidMask(), &filter, &result );
|
||||
|
||||
/*
|
||||
if ( result.DidHit() )
|
||||
{
|
||||
NDebugOverlay::SweptBox( from, result.endpos, hullMin, hullMax, vec3_angle, 255, 0, 0, 255, 9999.9f );
|
||||
NDebugOverlay::SweptBox( result.endpos, to, hullMin, hullMax, vec3_angle, 255, 255, 0, 255, 9999.9f );
|
||||
}
|
||||
else
|
||||
{
|
||||
NDebugOverlay::SweptBox( from, to, hullMin, hullMax, vec3_angle, 255, 255, 0, 255, 0.1f );
|
||||
}
|
||||
*/
|
||||
|
||||
if ( fraction )
|
||||
{
|
||||
*fraction = result.fraction;
|
||||
}
|
||||
|
||||
return ( result.fraction >= 1.0f ) && ( !result.startsolid );
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Return true if there is a possible "gap" that will need to be jumped over
|
||||
* If true is returned, fraction of ray before gap is returned in 'fraction'
|
||||
*/
|
||||
bool ILocomotion::HasPotentialGap( const Vector &from, const Vector &desiredTo, float *fraction ) const
|
||||
{
|
||||
VPROF_BUDGET( "Locomotion::HasPotentialGap", "NextBot" );
|
||||
|
||||
// find section of this ray that is actually traversable
|
||||
float traversableFraction;
|
||||
IsPotentiallyTraversable( from, desiredTo, IMMEDIATELY, &traversableFraction );
|
||||
|
||||
// compute end of traversable ray
|
||||
Vector to = from + ( desiredTo - from ) * traversableFraction;
|
||||
|
||||
Vector forward = to - from;
|
||||
float length = forward.NormalizeInPlace();
|
||||
|
||||
IBody *body = GetBot()->GetBodyInterface();
|
||||
|
||||
float step = body->GetHullWidth()/2.0f;
|
||||
|
||||
// scan along the line checking for gaps
|
||||
Vector pos = from;
|
||||
Vector delta = step * forward;
|
||||
for( float t = 0.0f; t < (length + step); t += step )
|
||||
{
|
||||
if ( IsGap( pos, forward ) )
|
||||
{
|
||||
if ( fraction )
|
||||
{
|
||||
*fraction = ( t - step ) / ( length + step );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
pos += delta;
|
||||
}
|
||||
|
||||
if ( fraction )
|
||||
{
|
||||
*fraction = 1.0f;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Return true if there is a "gap" here when moving in the given direction.
|
||||
* A "gap" is a vertical dropoff that is too high to jump back up to.
|
||||
*/
|
||||
bool ILocomotion::IsGap( const Vector &pos, const Vector &forward ) const
|
||||
{
|
||||
VPROF_BUDGET( "Locomotion::IsGap", "NextBotSpiky" );
|
||||
|
||||
IBody *body = GetBot()->GetBodyInterface();
|
||||
|
||||
//float halfWidth = ( body ) ? body->GetHullWidth()/2.0f : 1.0f;
|
||||
|
||||
// can't really jump effectively when crouched anyhow
|
||||
//float hullHeight = ( body ) ? body->GetStandHullHeight() : 1.0f;
|
||||
|
||||
// use a small hull since we cannot simulate collision resolution and avoidance along the way
|
||||
const float halfWidth = 1.0f;
|
||||
const float hullHeight = 1.0f;
|
||||
|
||||
unsigned int mask = ( body ) ? body->GetSolidMask() : MASK_PLAYERSOLID;
|
||||
|
||||
trace_t ground;
|
||||
|
||||
NextBotTraceFilterIgnoreActors filter( GetBot()->GetEntity(), COLLISION_GROUP_NONE );
|
||||
|
||||
TraceHull( pos + Vector( 0, 0, GetStepHeight() ), // start up a bit to handle rough terrain
|
||||
pos + Vector( 0, 0, -GetMaxJumpHeight() ),
|
||||
Vector( -halfWidth, -halfWidth, 0 ), Vector( halfWidth, halfWidth, hullHeight ),
|
||||
mask, &filter, &ground );
|
||||
|
||||
// int r,g,b;
|
||||
//
|
||||
// if ( ground.fraction >= 1.0f && !ground.startsolid )
|
||||
// {
|
||||
// r = 255, g = 0, b = 0;
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// r = 0, g = 255, b = 0;
|
||||
// }
|
||||
//
|
||||
// NDebugOverlay::SweptBox( pos,
|
||||
// pos + Vector( 0, 0, -GetStepHeight() ),
|
||||
// Vector( -halfWidth, -halfWidth, 0 ), Vector( halfWidth, halfWidth, hullHeight ),
|
||||
// vec3_angle,
|
||||
// r, g, b, 255, 3.0f );
|
||||
|
||||
// if trace hit nothing, there's a gap ahead of us
|
||||
return ( ground.fraction >= 1.0f && !ground.startsolid );
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------
|
||||
bool ILocomotion::IsEntityTraversable( CBaseEntity *obstacle, TraverseWhenType when ) const
|
||||
{
|
||||
if ( obstacle->IsWorld() )
|
||||
return false;
|
||||
|
||||
// assume bot will open a door in its path
|
||||
if ( FClassnameIs( obstacle, "prop_door*" ) || FClassnameIs( obstacle, "func_door*" ) )
|
||||
{
|
||||
CBasePropDoor *door = dynamic_cast< CBasePropDoor * >( obstacle );
|
||||
|
||||
if ( door && door->IsDoorOpen() )
|
||||
{
|
||||
// open doors are obstacles
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// if we hit a clip brush, ignore it if it is not BRUSHSOLID_ALWAYS
|
||||
if ( FClassnameIs( obstacle, "func_brush" ) )
|
||||
{
|
||||
CFuncBrush *brush = (CFuncBrush *)obstacle;
|
||||
|
||||
switch ( brush->m_iSolidity )
|
||||
{
|
||||
case CFuncBrush::BRUSHSOLID_ALWAYS:
|
||||
return false;
|
||||
case CFuncBrush::BRUSHSOLID_NEVER:
|
||||
return true;
|
||||
case CFuncBrush::BRUSHSOLID_TOGGLE:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if ( when == IMMEDIATELY )
|
||||
{
|
||||
// special rules in specific games can immediately break some breakables, etc.
|
||||
return false;
|
||||
}
|
||||
|
||||
// assume bot will EVENTUALLY break breakables in its path
|
||||
return GetBot()->IsAbleToBreak( obstacle );
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
bool ILocomotion::IsAreaTraversable( const CNavArea *baseArea ) const
|
||||
{
|
||||
return !baseArea->IsBlocked( GetBot()->GetEntity()->GetTeamNumber() );
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Reset stuck status to un-stuck
|
||||
*/
|
||||
void ILocomotion::ClearStuckStatus( const char *reason )
|
||||
{
|
||||
if ( IsStuck() )
|
||||
{
|
||||
m_isStuck = false;
|
||||
|
||||
// tell other components we're no longer stuck
|
||||
GetBot()->OnUnStuck();
|
||||
}
|
||||
|
||||
// always reset stuck monitoring data in case we cleared preemptively are were not yet stuck
|
||||
m_stuckPos = GetFeet();
|
||||
m_stuckTimer.Start();
|
||||
|
||||
if ( GetBot()->IsDebugging( NEXTBOT_LOCOMOTION ) )
|
||||
{
|
||||
DevMsg( "%3.2f: ClearStuckStatus: %s %s\n", gpGlobals->curtime, GetBot()->GetDebugIdentifier(), reason );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Stuck check
|
||||
*/
|
||||
void ILocomotion::StuckMonitor( void )
|
||||
{
|
||||
// a timer is needed to smooth over a few frames of inactivity due to state changes, etc.
|
||||
// we only want to detect idle situations when the bot really doesn't "want" to move.
|
||||
const float idleTime = 0.25f;
|
||||
if ( m_moveRequestTimer.IsGreaterThen( idleTime ) )
|
||||
{
|
||||
// we have no desire to move, and therefore cannot emit stuck events
|
||||
|
||||
// prepare our internal state for when the bot starts to move next
|
||||
m_stuckPos = GetFeet();
|
||||
m_stuckTimer.Start();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// if ( !IsOnGround() )
|
||||
// {
|
||||
// // can't be stuck when in-air
|
||||
// ClearStuckStatus( "Off the ground" );
|
||||
// return;
|
||||
// }
|
||||
|
||||
// if ( IsUsingLadder() )
|
||||
// {
|
||||
// // can't be stuck when on a ladder (for now)
|
||||
// ClearStuckStatus( "On a ladder" );
|
||||
// return;
|
||||
// }
|
||||
|
||||
if ( IsStuck() )
|
||||
{
|
||||
// we are/were stuck - have we moved enough to consider ourselves "dislodged"
|
||||
if ( GetBot()->IsRangeGreaterThan( m_stuckPos, STUCK_RADIUS ) )
|
||||
{
|
||||
// we've just become un-stuck
|
||||
ClearStuckStatus( "UN-STUCK" );
|
||||
}
|
||||
else
|
||||
{
|
||||
// still stuck - periodically resend the event
|
||||
if ( m_stillStuckTimer.IsElapsed() )
|
||||
{
|
||||
m_stillStuckTimer.Start( 1.0f );
|
||||
|
||||
if ( GetBot()->IsDebugging( NEXTBOT_LOCOMOTION ) )
|
||||
{
|
||||
DevMsg( "%3.2f: %s STILL STUCK\n", gpGlobals->curtime, GetBot()->GetDebugIdentifier() );
|
||||
NDebugOverlay::Circle( m_stuckPos + Vector( 0, 0, 5.0f ), QAngle( -90.0f, 0, 0 ), 5.0f, 255, 0, 0, 255, true, 1.0f );
|
||||
}
|
||||
|
||||
GetBot()->OnStuck();
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// we're not stuck - yet
|
||||
|
||||
if ( /*IsClimbingOrJumping() || */GetBot()->IsRangeGreaterThan( m_stuckPos, STUCK_RADIUS ) )
|
||||
{
|
||||
// we have moved - reset anchor
|
||||
m_stuckPos = GetFeet();
|
||||
|
||||
if ( GetBot()->IsDebugging( NEXTBOT_LOCOMOTION ) )
|
||||
{
|
||||
NDebugOverlay::Cross3D( m_stuckPos, 3.0f, 255, 0, 255, true, 3.0f );
|
||||
}
|
||||
|
||||
m_stuckTimer.Start();
|
||||
}
|
||||
else
|
||||
{
|
||||
// within stuck range of anchor. if we've been here too long, we're stuck
|
||||
if ( GetBot()->IsDebugging( NEXTBOT_LOCOMOTION ) )
|
||||
{
|
||||
NDebugOverlay::Line( GetBot()->GetEntity()->WorldSpaceCenter(), m_stuckPos, 255, 0, 255, true, 0.1f );
|
||||
}
|
||||
|
||||
float minMoveSpeed = 0.1f * GetDesiredSpeed() + 0.1f;
|
||||
float escapeTime = STUCK_RADIUS / minMoveSpeed;
|
||||
if ( m_stuckTimer.IsGreaterThen( escapeTime ) )
|
||||
{
|
||||
// we have taken too long - we're stuck
|
||||
m_isStuck = true;
|
||||
|
||||
if ( GetBot()->IsDebugging( NEXTBOT_ERRORS ) )
|
||||
{
|
||||
DevMsg( "%3.2f: %s STUCK at position( %3.2f, %3.2f, %3.2f )\n", gpGlobals->curtime, GetBot()->GetDebugIdentifier(), m_stuckPos.x, m_stuckPos.y, m_stuckPos.z );
|
||||
|
||||
NDebugOverlay::Circle( m_stuckPos + Vector( 0, 0, 15.0f ), QAngle( -90.0f, 0, 0 ), 3.0f, 255, 255, 0, 255, true, 1.0f );
|
||||
NDebugOverlay::Circle( m_stuckPos + Vector( 0, 0, 5.0f ), QAngle( -90.0f, 0, 0 ), 5.0f, 255, 0, 0, 255, true, 9999999.9f );
|
||||
}
|
||||
|
||||
// tell other components we've become stuck
|
||||
GetBot()->OnStuck();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
const Vector &ILocomotion::GetFeet( void ) const
|
||||
{
|
||||
return GetBot()->GetEntity()->GetAbsOrigin();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,335 @@
|
||||
// NextBotLocomotionInterface.h
|
||||
// NextBot interface for movement through the environment
|
||||
// Author: Michael Booth, April 2005
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
|
||||
#ifndef _NEXT_BOT_LOCOMOTION_INTERFACE_H_
|
||||
#define _NEXT_BOT_LOCOMOTION_INTERFACE_H_
|
||||
|
||||
#include "NextBotComponentInterface.h"
|
||||
|
||||
class Path;
|
||||
class INextBot;
|
||||
class CNavLadder;
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* The interface encapsulating *how* a bot moves through the world (walking? flying? etc)
|
||||
*/
|
||||
class ILocomotion : public INextBotComponent
|
||||
{
|
||||
public:
|
||||
ILocomotion( INextBot *bot );
|
||||
virtual ~ILocomotion();
|
||||
|
||||
virtual void Reset( void ); // (EXTEND) reset to initial state
|
||||
virtual void Update( void ); // (EXTEND) update internal state
|
||||
|
||||
//
|
||||
// The primary locomotive method
|
||||
// Depending on the physics of the bot's motion, it may not actually
|
||||
// reach the given position precisely.
|
||||
// The 'weight' can be used to combine multiple Approach() calls within
|
||||
// a single frame into a single goal (ie: weighted average)
|
||||
//
|
||||
virtual void Approach( const Vector &goalPos, float goalWeight = 1.0f ); // (EXTEND) move directly towards the given position
|
||||
|
||||
//
|
||||
// Move the bot to the precise given position immediately,
|
||||
// updating internal state as needed
|
||||
// Collision resolution is done to prevent interpenetration, which may prevent
|
||||
// the bot from reaching the given position. If no collisions occur, the
|
||||
// bot will be at the given position when this method returns.
|
||||
//
|
||||
virtual void DriveTo( const Vector &pos ); // (EXTEND) Move the bot to the precise given position immediately,
|
||||
|
||||
//
|
||||
// Locomotion modifiers
|
||||
//
|
||||
virtual bool ClimbUpToLedge( const Vector &landingGoal, const Vector &landingForward, const CBaseEntity *obstacle ) { return true; } // initiate a jump to an adjacent high ledge, return false if climb can't start
|
||||
virtual void JumpAcrossGap( const Vector &landingGoal, const Vector &landingForward ) { } // initiate a jump across an empty volume of space to far side
|
||||
virtual void Jump( void ) { } // initiate a simple undirected jump in the air
|
||||
virtual bool IsClimbingOrJumping( void ) const; // is jumping in any form
|
||||
virtual bool IsClimbingUpToLedge( void ) const; // is climbing up to a high ledge
|
||||
virtual bool IsJumpingAcrossGap( void ) const; // is jumping across a gap to the far side
|
||||
virtual bool IsScrambling( void ) const; // is in the middle of a complex action (climbing a ladder, climbing a ledge, jumping, etc) that shouldn't be interrupted
|
||||
|
||||
virtual void Run( void ) { } // set desired movement speed to running
|
||||
virtual void Walk( void ) { } // set desired movement speed to walking
|
||||
virtual void Stop( void ) { } // set desired movement speed to stopped
|
||||
virtual bool IsRunning( void ) const;
|
||||
virtual void SetDesiredSpeed( float speed ) { } // set desired speed for locomotor movement
|
||||
virtual float GetDesiredSpeed( void ) const; // returns the current desired speed
|
||||
|
||||
virtual void SetSpeedLimit( float speed ) { } // set maximum speed bot can reach, regardless of desired speed
|
||||
virtual float GetSpeedLimit( void ) const { return 1000.0f; } // get maximum speed bot can reach, regardless of desired speed
|
||||
|
||||
virtual bool IsOnGround( void ) const; // return true if standing on something
|
||||
virtual void OnLeaveGround( CBaseEntity *ground ) { } // invoked when bot leaves ground for any reason
|
||||
virtual void OnLandOnGround( CBaseEntity *ground ) { } // invoked when bot lands on the ground after being in the air
|
||||
virtual CBaseEntity *GetGround( void ) const; // return the current ground entity or NULL if not on the ground
|
||||
virtual const Vector &GetGroundNormal( void ) const; // surface normal of the ground we are in contact with
|
||||
virtual float GetGroundSpeed( void ) const; // return current world space speed in XY plane
|
||||
virtual const Vector &GetGroundMotionVector( void ) const; // return unit vector in XY plane describing our direction of motion - even if we are currently not moving
|
||||
|
||||
virtual void ClimbLadder( const CNavLadder *ladder, const CNavArea *dismountGoal ) { } // climb the given ladder to the top and dismount
|
||||
virtual void DescendLadder( const CNavLadder *ladder, const CNavArea *dismountGoal ) { } // descend the given ladder to the bottom and dismount
|
||||
virtual bool IsUsingLadder( void ) const; // we are moving to get on, ascending/descending, and/or dismounting a ladder
|
||||
virtual bool IsAscendingOrDescendingLadder( void ) const; // we are actually on the ladder right now, either climbing up or down
|
||||
virtual bool IsAbleToAutoCenterOnLadder( void ) const { return false; }
|
||||
|
||||
virtual void FaceTowards( const Vector &target ) { } // rotate body to face towards "target"
|
||||
|
||||
virtual void SetDesiredLean( const QAngle &lean ) { }
|
||||
virtual const QAngle &GetDesiredLean( void ) const;
|
||||
|
||||
|
||||
//
|
||||
// Locomotion information
|
||||
//
|
||||
virtual bool IsAbleToJumpAcrossGaps( void ) const; // return true if this bot can jump across gaps in its path
|
||||
virtual bool IsAbleToClimb( void ) const; // return true if this bot can climb arbitrary geometry it encounters
|
||||
|
||||
virtual const Vector &GetFeet( void ) const; // return position of "feet" - the driving point where the bot contacts the ground
|
||||
|
||||
virtual float GetStepHeight( void ) const; // if delta Z is greater than this, we have to jump to get up
|
||||
virtual float GetMaxJumpHeight( void ) const; // return maximum height of a jump
|
||||
virtual float GetDeathDropHeight( void ) const; // distance at which we will die if we fall
|
||||
|
||||
virtual float GetRunSpeed( void ) const; // get maximum running speed
|
||||
virtual float GetWalkSpeed( void ) const; // get maximum walking speed
|
||||
|
||||
virtual float GetMaxAcceleration( void ) const; // return maximum acceleration of locomotor
|
||||
virtual float GetMaxDeceleration( void ) const; // return maximum deceleration of locomotor
|
||||
|
||||
virtual const Vector &GetVelocity( void ) const; // return current world space velocity
|
||||
virtual float GetSpeed( void ) const; // return current world space speed (magnitude of velocity)
|
||||
virtual const Vector &GetMotionVector( void ) const; // return unit vector describing our direction of motion - even if we are currently not moving
|
||||
|
||||
virtual bool IsAreaTraversable( const CNavArea *baseArea ) const; // return true if given area can be used for navigation
|
||||
|
||||
virtual float GetTraversableSlopeLimit( void ) const; // return Z component of unit normal of steepest traversable slope
|
||||
|
||||
// return true if the given entity can be ignored during locomotion
|
||||
enum TraverseWhenType
|
||||
{
|
||||
IMMEDIATELY, // the entity will not block our motion - we'll carry right through
|
||||
EVENTUALLY // the entity will block us until we spend effort to open/destroy it
|
||||
};
|
||||
|
||||
/**
|
||||
* Return true if this locomotor could potentially move along the line given.
|
||||
* If false is returned, fraction of walkable ray is returned in 'fraction'
|
||||
*/
|
||||
virtual bool IsPotentiallyTraversable( const Vector &from, const Vector &to, TraverseWhenType when = EVENTUALLY, float *fraction = NULL ) const;
|
||||
|
||||
/**
|
||||
* Return true if there is a possible "gap" that will need to be jumped over
|
||||
* If true is returned, fraction of ray before gap is returned in 'fraction'
|
||||
*/
|
||||
virtual bool HasPotentialGap( const Vector &from, const Vector &to, float *fraction = NULL ) const;
|
||||
|
||||
// return true if there is a "gap" here when moving in the given direction
|
||||
virtual bool IsGap( const Vector &pos, const Vector &forward ) const;
|
||||
|
||||
virtual bool IsEntityTraversable( CBaseEntity *obstacle, TraverseWhenType when = EVENTUALLY ) const;
|
||||
|
||||
//
|
||||
// Stuck state. If the locomotor cannot make progress, it becomes "stuck" and can only leave
|
||||
// this stuck state by successfully moving and becoming un-stuck.
|
||||
//
|
||||
virtual bool IsStuck( void ) const; // return true if bot is stuck
|
||||
virtual float GetStuckDuration( void ) const; // return how long we've been stuck
|
||||
virtual void ClearStuckStatus( const char *reason = "" ); // reset stuck status to un-stuck
|
||||
|
||||
virtual bool IsAttemptingToMove( void ) const; // return true if we have tried to Approach() or DriveTo() very recently
|
||||
|
||||
void TraceHull( const Vector& start, const Vector& end, const Vector &mins, const Vector &maxs, unsigned int fMask, ITraceFilter *pFilter, trace_t *pTrace ) const;
|
||||
|
||||
/**
|
||||
* Should we collide with this entity?
|
||||
*/
|
||||
virtual bool ShouldCollideWith( const CBaseEntity *object ) const { return true; }
|
||||
|
||||
|
||||
protected:
|
||||
virtual void AdjustPosture( const Vector &moveGoal );
|
||||
virtual void StuckMonitor( void );
|
||||
|
||||
private:
|
||||
Vector m_motionVector;
|
||||
Vector m_groundMotionVector;
|
||||
float m_speed;
|
||||
float m_groundSpeed;
|
||||
|
||||
// stuck monitoring
|
||||
bool m_isStuck; // if true, we are stuck
|
||||
IntervalTimer m_stuckTimer; // how long we've been stuck
|
||||
CountdownTimer m_stillStuckTimer; // for resending stuck events
|
||||
Vector m_stuckPos; // where we got stuck
|
||||
IntervalTimer m_moveRequestTimer;
|
||||
};
|
||||
|
||||
|
||||
inline bool ILocomotion::IsAbleToJumpAcrossGaps( void ) const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool ILocomotion::IsAbleToClimb( void ) const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool ILocomotion::IsAttemptingToMove( void ) const
|
||||
{
|
||||
return m_moveRequestTimer.HasStarted() && m_moveRequestTimer.GetElapsedTime() < 0.25f;
|
||||
}
|
||||
|
||||
inline bool ILocomotion::IsScrambling( void ) const
|
||||
{
|
||||
return !IsOnGround() || IsClimbingOrJumping() || IsAscendingOrDescendingLadder();
|
||||
}
|
||||
|
||||
inline bool ILocomotion::IsClimbingOrJumping( void ) const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
inline bool ILocomotion::IsClimbingUpToLedge( void ) const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
inline bool ILocomotion::IsJumpingAcrossGap( void ) const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
inline bool ILocomotion::IsRunning( void ) const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
inline float ILocomotion::GetDesiredSpeed( void ) const
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
inline bool ILocomotion::IsOnGround( void ) const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
inline CBaseEntity *ILocomotion::GetGround( void ) const
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
inline const Vector &ILocomotion::GetGroundNormal( void ) const
|
||||
{
|
||||
return vec3_origin;
|
||||
}
|
||||
|
||||
inline float ILocomotion::GetGroundSpeed( void ) const
|
||||
{
|
||||
return m_groundSpeed;
|
||||
}
|
||||
|
||||
inline const Vector & ILocomotion::GetGroundMotionVector( void ) const
|
||||
{
|
||||
return m_groundMotionVector;
|
||||
}
|
||||
|
||||
inline bool ILocomotion::IsUsingLadder( void ) const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
inline bool ILocomotion::IsAscendingOrDescendingLadder( void ) const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
inline const QAngle &ILocomotion::GetDesiredLean( void ) const
|
||||
{
|
||||
return vec3_angle;
|
||||
}
|
||||
|
||||
inline float ILocomotion::GetStepHeight( void ) const
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
inline float ILocomotion::GetMaxJumpHeight( void ) const
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
inline float ILocomotion::GetDeathDropHeight( void ) const
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
inline float ILocomotion::GetRunSpeed( void ) const
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
inline float ILocomotion::GetWalkSpeed( void ) const
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
inline float ILocomotion::GetMaxAcceleration( void ) const
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
inline float ILocomotion::GetMaxDeceleration( void ) const
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
inline const Vector &ILocomotion::GetVelocity( void ) const
|
||||
{
|
||||
return vec3_origin;
|
||||
}
|
||||
|
||||
inline float ILocomotion::GetSpeed( void ) const
|
||||
{
|
||||
return m_speed;
|
||||
}
|
||||
|
||||
inline const Vector & ILocomotion::GetMotionVector( void ) const
|
||||
{
|
||||
return m_motionVector;
|
||||
}
|
||||
|
||||
inline float ILocomotion::GetTraversableSlopeLimit( void ) const
|
||||
{
|
||||
return 0.6;
|
||||
}
|
||||
|
||||
inline bool ILocomotion::IsStuck( void ) const
|
||||
{
|
||||
return m_isStuck;
|
||||
}
|
||||
|
||||
inline float ILocomotion::GetStuckDuration( void ) const
|
||||
{
|
||||
return ( IsStuck() ) ? m_stuckTimer.GetElapsedTime() : 0.0f;
|
||||
}
|
||||
|
||||
inline void ILocomotion::TraceHull( const Vector& start, const Vector& end, const Vector &mins, const Vector &maxs, unsigned int fMask, ITraceFilter *pFilter, trace_t *pTrace ) const
|
||||
{
|
||||
// VPROF_BUDGET( "ILocomotion::TraceHull", "TraceHull" );
|
||||
Ray_t ray;
|
||||
ray.Init( start, end, mins, maxs );
|
||||
enginetrace->TraceRay( ray, fMask, pFilter, pTrace );
|
||||
}
|
||||
|
||||
|
||||
|
||||
#endif // _NEXT_BOT_LOCOMOTION_INTERFACE_H_
|
||||
|
||||
@@ -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] );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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_
|
||||
|
||||
@@ -0,0 +1,278 @@
|
||||
// NextBotUtil.h
|
||||
// Utilities for the NextBot system
|
||||
// Author: Michael Booth, May 2006
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
|
||||
#ifndef _NEXT_BOT_UTIL_H_
|
||||
#define _NEXT_BOT_UTIL_H_
|
||||
|
||||
#include "NextBotLocomotionInterface.h"
|
||||
#include "nav_area.h"
|
||||
#include "nav_mesh.h"
|
||||
#include "nav_pathfind.h"
|
||||
|
||||
//--------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* A simple filter interface for various NextBot queries
|
||||
*/
|
||||
class INextBotEntityFilter
|
||||
{
|
||||
public:
|
||||
// return true if the given entity passes this filter
|
||||
virtual bool IsAllowed( CBaseEntity *entity ) const = 0;
|
||||
};
|
||||
|
||||
|
||||
// trace filter callback functions. needed for use with the querycache/optimization functionality
|
||||
bool VisionTraceFilterFunction( IHandleEntity *pServerEntity, int contentsMask );
|
||||
bool IgnoreActorsTraceFilterFunction( IHandleEntity *pServerEntity, int contentsMask );
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Trace filter that skips all players and NextBots
|
||||
*/
|
||||
class NextBotTraceFilterIgnoreActors : public CTraceFilterSimple
|
||||
{
|
||||
public:
|
||||
NextBotTraceFilterIgnoreActors( const IHandleEntity *passentity, int collisionGroup ) : CTraceFilterSimple( passentity, collisionGroup, IgnoreActorsTraceFilterFunction )
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Trace filter that skips all players, NextBots, and non-LOS blockers
|
||||
*/
|
||||
class NextBotVisionTraceFilter : public CTraceFilterSimple
|
||||
{
|
||||
public:
|
||||
NextBotVisionTraceFilter( const IHandleEntity *passentity, int collisionGroup ) : CTraceFilterSimple( passentity, collisionGroup, VisionTraceFilterFunction )
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Trace filter that skips all NextBots, but includes Players
|
||||
*/
|
||||
class NextBotTraceFilterIgnoreNextBots : public CTraceFilterSimple
|
||||
{
|
||||
public:
|
||||
NextBotTraceFilterIgnoreNextBots( const IHandleEntity *passentity, int collisionGroup )
|
||||
: CTraceFilterSimple( passentity, collisionGroup )
|
||||
{
|
||||
}
|
||||
|
||||
virtual bool ShouldHitEntity( IHandleEntity *pServerEntity, int contentsMask )
|
||||
{
|
||||
if ( CTraceFilterSimple::ShouldHitEntity( pServerEntity, contentsMask ) )
|
||||
{
|
||||
CBaseEntity *entity = EntityFromEntityHandle( pServerEntity );
|
||||
#ifdef TERROR
|
||||
CBasePlayer *player = ToBasePlayer( entity );
|
||||
if ( player && player->IsGhost() )
|
||||
return false;
|
||||
#endif // TERROR
|
||||
|
||||
return ( entity->MyNextBotPointer() == NULL );
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Trace filter that obeys INextBot::IsAbleToBlockMovementOf()
|
||||
*/
|
||||
class NextBotTraceFilter : public CTraceFilterSimple
|
||||
{
|
||||
public:
|
||||
NextBotTraceFilter( const IHandleEntity *passentity, int collisionGroup )
|
||||
: CTraceFilterSimple( passentity, collisionGroup )
|
||||
{
|
||||
CBaseEntity *entity = const_cast<CBaseEntity *>(EntityFromEntityHandle( passentity ));
|
||||
m_passBot = entity->MyNextBotPointer();
|
||||
}
|
||||
|
||||
virtual bool ShouldHitEntity( IHandleEntity *pServerEntity, int contentsMask )
|
||||
{
|
||||
if ( CTraceFilterSimple::ShouldHitEntity( pServerEntity, contentsMask ) )
|
||||
{
|
||||
CBaseEntity *entity = EntityFromEntityHandle( pServerEntity );
|
||||
#ifdef TERROR
|
||||
CBasePlayer *player = ToBasePlayer( entity );
|
||||
if ( player && player->IsGhost() )
|
||||
return false;
|
||||
#endif // TERROR
|
||||
|
||||
// Skip players on the same team - they're not solid to us, and we'll avoid them
|
||||
if ( entity->IsPlayer() && m_passBot && m_passBot->GetEntity() &&
|
||||
m_passBot->GetEntity()->GetTeamNumber() == entity->GetTeamNumber() )
|
||||
return false;
|
||||
|
||||
INextBot *bot = entity->MyNextBotPointer();
|
||||
|
||||
return ( !bot || bot->IsAbleToBlockMovementOf( m_passBot ) );
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const INextBot *m_passBot;
|
||||
};
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Trace filter that only hits players and NextBots
|
||||
*/
|
||||
class NextBotTraceFilterOnlyActors : public CTraceFilterSimple
|
||||
{
|
||||
public:
|
||||
NextBotTraceFilterOnlyActors( const IHandleEntity *passentity, int collisionGroup )
|
||||
: CTraceFilterSimple( passentity, collisionGroup )
|
||||
{
|
||||
}
|
||||
|
||||
virtual TraceType_t GetTraceType() const
|
||||
{
|
||||
return TRACE_ENTITIES_ONLY;
|
||||
}
|
||||
|
||||
virtual bool ShouldHitEntity( IHandleEntity *pServerEntity, int contentsMask )
|
||||
{
|
||||
if ( CTraceFilterSimple::ShouldHitEntity( pServerEntity, contentsMask ) )
|
||||
{
|
||||
CBaseEntity *entity = EntityFromEntityHandle( pServerEntity );
|
||||
|
||||
#ifdef TERROR
|
||||
CBasePlayer *player = ToBasePlayer( entity );
|
||||
if ( player && player->IsGhost() )
|
||||
return false;
|
||||
#endif // TERROR
|
||||
|
||||
return ( entity->MyNextBotPointer() || entity->IsPlayer() );
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Trace filter that skips "traversable" entities. The "when" argument creates
|
||||
* a temporal context for asking if an entity is IMMEDIATELY traversable (like thin
|
||||
* glass that just breaks as we walk through it) or EVENTUALLY traversable (like a
|
||||
* breakable object that will take some time to break through)
|
||||
*/
|
||||
class NextBotTraversableTraceFilter : public CTraceFilterSimple
|
||||
{
|
||||
public:
|
||||
NextBotTraversableTraceFilter( INextBot *bot, ILocomotion::TraverseWhenType when = ILocomotion::EVENTUALLY ) : CTraceFilterSimple( bot->GetEntity(), COLLISION_GROUP_NONE )
|
||||
{
|
||||
m_bot = bot;
|
||||
m_when = when;
|
||||
}
|
||||
|
||||
virtual bool ShouldHitEntity( IHandleEntity *pServerEntity, int contentsMask )
|
||||
{
|
||||
CBaseEntity *entity = EntityFromEntityHandle( pServerEntity );
|
||||
|
||||
if ( m_bot->IsSelf( entity ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( CTraceFilterSimple::ShouldHitEntity( pServerEntity, contentsMask ) )
|
||||
{
|
||||
return !m_bot->GetLocomotionInterface()->IsEntityTraversable( entity, m_when );
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
INextBot *m_bot;
|
||||
ILocomotion::TraverseWhenType m_when;
|
||||
};
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Given a vector of entities, a nav area, and a max travel distance, return
|
||||
* the entity that has the shortest travel distance.
|
||||
*/
|
||||
inline CBaseEntity *SelectClosestEntityByTravelDistance( INextBot *me, const CUtlVector< CBaseEntity * > &candidateEntities, CNavArea *startArea, float travelRange )
|
||||
{
|
||||
// collect nearby walkable areas within travelRange
|
||||
CUtlVector< CNavArea * > nearbyAreaVector;
|
||||
CollectSurroundingAreas( &nearbyAreaVector, startArea, travelRange, me->GetLocomotionInterface()->GetStepHeight(), me->GetLocomotionInterface()->GetDeathDropHeight() );
|
||||
|
||||
// find closest entity in the collected area set
|
||||
CBaseEntity *closeEntity = NULL;
|
||||
float closeTravelRange = FLT_MAX;
|
||||
|
||||
for( int i=0; i<candidateEntities.Count(); ++i )
|
||||
{
|
||||
CBaseEntity *candidate = candidateEntities[i];
|
||||
|
||||
CNavArea *area = TheNavMesh->GetNearestNavArea( candidate, GETNAVAREA_CHECK_LOS, 500.0f );
|
||||
|
||||
if ( area && area->IsMarked() && area->GetCostSoFar() < closeTravelRange )
|
||||
{
|
||||
closeEntity = candidate;
|
||||
closeTravelRange = area->GetCostSoFar();
|
||||
}
|
||||
}
|
||||
|
||||
return closeEntity;
|
||||
}
|
||||
|
||||
|
||||
#ifdef OBSOLETE
|
||||
//--------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Trace filter that skips "traversable" entities, but hits other Actors.
|
||||
* Used for obstacle avoidance.
|
||||
*/
|
||||
class NextBotMovementAvoidanceTraceFilter : public CTraceFilterSimple
|
||||
{
|
||||
public:
|
||||
NextBotMovementAvoidanceTraceFilter( INextBot *bot ) : CTraceFilterSimple( bot->GetEntity(), COLLISION_GROUP_NONE )
|
||||
{
|
||||
m_bot = bot;
|
||||
}
|
||||
|
||||
virtual bool ShouldHitEntity( IHandleEntity *pServerEntity, int contentsMask )
|
||||
{
|
||||
CBaseEntity *entity = EntityFromEntityHandle( pServerEntity );
|
||||
|
||||
#ifdef TERROR
|
||||
CBasePlayer *player = ToBasePlayer( entity );
|
||||
if ( player && player->IsGhost() )
|
||||
return false;
|
||||
#endif // TERROR
|
||||
|
||||
if ( m_bot->IsSelf( entity ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( CTraceFilterSimple::ShouldHitEntity( pServerEntity, contentsMask ) )
|
||||
{
|
||||
return !m_bot->GetLocomotionInterface()->IsEntityTraversable( entity, ILocomotion::IMMEDIATELY );
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
INextBot *m_bot;
|
||||
};
|
||||
#endif
|
||||
|
||||
|
||||
#endif // _NEXT_BOT_UTIL_H_
|
||||
@@ -0,0 +1,802 @@
|
||||
// NextBotVisionInterface.cpp
|
||||
// Implementation of common vision system
|
||||
// Author: Michael Booth, May 2006
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
|
||||
#include "cbase.h"
|
||||
|
||||
#include "nav.h"
|
||||
#include "functorutils.h"
|
||||
|
||||
#include "NextBot.h"
|
||||
#include "NextBotVisionInterface.h"
|
||||
#include "NextBotBodyInterface.h"
|
||||
#include "NextBotUtil.h"
|
||||
|
||||
#ifdef TERROR
|
||||
#include "querycache.h"
|
||||
#endif
|
||||
|
||||
#include "tier0/vprof.h"
|
||||
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
|
||||
ConVar nb_blind( "nb_blind", "0", FCVAR_CHEAT, "Disable vision" );
|
||||
ConVar nb_debug_known_entities( "nb_debug_known_entities", "0", FCVAR_CHEAT, "Show the 'known entities' for the bot that is the current spectator target" );
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------------------
|
||||
IVision::IVision( INextBot *bot ) : INextBotComponent( bot )
|
||||
{
|
||||
Reset();
|
||||
}
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Reset to initial state
|
||||
*/
|
||||
void IVision::Reset( void )
|
||||
{
|
||||
INextBotComponent::Reset();
|
||||
|
||||
m_knownEntityVector.RemoveAll();
|
||||
m_lastVisionUpdateTimestamp = 0.0f;
|
||||
m_primaryThreat = NULL;
|
||||
|
||||
m_FOV = GetDefaultFieldOfView();
|
||||
m_cosHalfFOV = cos( 0.5f * m_FOV * M_PI / 180.0f );
|
||||
|
||||
for( int i=0; i<MAX_TEAMS; ++i )
|
||||
{
|
||||
m_notVisibleTimer[i].Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Ask the current behavior to select the most dangerous threat from
|
||||
* our set of currently known entities
|
||||
* TODO: Find a semantically better place for this to live.
|
||||
*/
|
||||
const CKnownEntity *IVision::GetPrimaryKnownThreat( bool onlyVisibleThreats ) const
|
||||
{
|
||||
if ( m_knownEntityVector.Count() == 0 )
|
||||
return NULL;
|
||||
|
||||
const CKnownEntity *threat = NULL;
|
||||
int i;
|
||||
|
||||
// find the first valid entity
|
||||
for( i=0; i<m_knownEntityVector.Count(); ++i )
|
||||
{
|
||||
const CKnownEntity &firstThreat = m_knownEntityVector[i];
|
||||
|
||||
// check in case status changes between updates
|
||||
if ( IsAwareOf( firstThreat ) && !firstThreat.IsObsolete() && !IsIgnored( firstThreat.GetEntity() ) && GetBot()->IsEnemy( firstThreat.GetEntity() ) )
|
||||
{
|
||||
if ( !onlyVisibleThreats || firstThreat.IsVisibleRecently() )
|
||||
{
|
||||
threat = &firstThreat;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( threat == NULL )
|
||||
{
|
||||
m_primaryThreat = NULL;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for( ++i; i<m_knownEntityVector.Count(); ++i )
|
||||
{
|
||||
const CKnownEntity &newThreat = m_knownEntityVector[i];
|
||||
|
||||
// check in case status changes between updates
|
||||
if ( IsAwareOf( newThreat ) && !newThreat.IsObsolete() && !IsIgnored( newThreat.GetEntity() ) && GetBot()->IsEnemy( newThreat.GetEntity() ) )
|
||||
{
|
||||
if ( !onlyVisibleThreats || newThreat.IsVisibleRecently() )
|
||||
{
|
||||
threat = GetBot()->GetIntentionInterface()->SelectMoreDangerousThreat( GetBot(), GetBot()->GetEntity(), threat, &newThreat );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// cache off threat
|
||||
m_primaryThreat = threat ? threat->GetEntity() : NULL;
|
||||
|
||||
return threat;
|
||||
}
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Return the closest recognized entity
|
||||
*/
|
||||
const CKnownEntity *IVision::GetClosestKnown( int team ) const
|
||||
{
|
||||
const Vector &myPos = GetBot()->GetPosition();
|
||||
|
||||
const CKnownEntity *close = NULL;
|
||||
float closeRange = 999999999.9f;
|
||||
|
||||
for( int i=0; i < m_knownEntityVector.Count(); ++i )
|
||||
{
|
||||
const CKnownEntity &known = m_knownEntityVector[i];
|
||||
|
||||
if ( !known.IsObsolete() && IsAwareOf( known ) )
|
||||
{
|
||||
if ( team == TEAM_ANY || known.GetEntity()->GetTeamNumber() == team )
|
||||
{
|
||||
Vector to = known.GetLastKnownPosition() - myPos;
|
||||
float rangeSq = to.LengthSqr();
|
||||
|
||||
if ( rangeSq < closeRange )
|
||||
{
|
||||
close = &known;
|
||||
closeRange = rangeSq;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return close;
|
||||
}
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Return the closest recognized entity that passes the given filter
|
||||
*/
|
||||
const CKnownEntity *IVision::GetClosestKnown( const INextBotEntityFilter &filter ) const
|
||||
{
|
||||
const Vector &myPos = GetBot()->GetPosition();
|
||||
|
||||
const CKnownEntity *close = NULL;
|
||||
float closeRange = 999999999.9f;
|
||||
|
||||
for( int i=0; i < m_knownEntityVector.Count(); ++i )
|
||||
{
|
||||
const CKnownEntity &known = m_knownEntityVector[i];
|
||||
|
||||
if ( !known.IsObsolete() && IsAwareOf( known ) )
|
||||
{
|
||||
if ( filter.IsAllowed( known.GetEntity() ) )
|
||||
{
|
||||
Vector to = known.GetLastKnownPosition() - myPos;
|
||||
float rangeSq = to.LengthSqr();
|
||||
|
||||
if ( rangeSq < closeRange )
|
||||
{
|
||||
close = &known;
|
||||
closeRange = rangeSq;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return close;
|
||||
}
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Given an entity, return our known version of it (or NULL if we don't know of it)
|
||||
*/
|
||||
const CKnownEntity *IVision::GetKnown( const CBaseEntity *entity ) const
|
||||
{
|
||||
if ( entity == NULL )
|
||||
return NULL;
|
||||
|
||||
for( int i=0; i < m_knownEntityVector.Count(); ++i )
|
||||
{
|
||||
const CKnownEntity &known = m_knownEntityVector[i];
|
||||
|
||||
if ( known.GetEntity() && known.GetEntity()->entindex() == entity->entindex() && !known.IsObsolete() )
|
||||
{
|
||||
return &known;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Introduce a known entity into the system. Its position is assumed to be known
|
||||
* and will be updated, and it is assumed to not yet have been seen by us, allowing for learning
|
||||
* of known entities by being told about them, hearing them, etc.
|
||||
*/
|
||||
void IVision::AddKnownEntity( CBaseEntity *entity )
|
||||
{
|
||||
if ( entity == NULL || entity->IsWorld() )
|
||||
{
|
||||
// the world is not an entity we can deal with
|
||||
return;
|
||||
}
|
||||
|
||||
CKnownEntity known( entity );
|
||||
|
||||
// only add it if we don't already know of it
|
||||
if ( m_knownEntityVector.Find( known ) == m_knownEntityVector.InvalidIndex() )
|
||||
{
|
||||
m_knownEntityVector.AddToTail( known );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------------------
|
||||
// Remove the given entity from our awareness (whether we know if it or not)
|
||||
// Useful if we've moved to where we last saw the entity, but it's not there any longer.
|
||||
void IVision::ForgetEntity( CBaseEntity *forgetMe )
|
||||
{
|
||||
if ( !forgetMe )
|
||||
return;
|
||||
|
||||
FOR_EACH_VEC( m_knownEntityVector, it )
|
||||
{
|
||||
const CKnownEntity &known = m_knownEntityVector[ it ];
|
||||
|
||||
if ( known.GetEntity() && known.GetEntity()->entindex() == forgetMe->entindex() )
|
||||
{
|
||||
m_knownEntityVector.FastRemove( it );
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------------------
|
||||
void IVision::ForgetAllKnownEntities( void )
|
||||
{
|
||||
m_knownEntityVector.RemoveAll();
|
||||
}
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Return the number of entity on the given team known to us closer than rangeLimit
|
||||
*/
|
||||
int IVision::GetKnownCount( int team, bool onlyVisible, float rangeLimit ) const
|
||||
{
|
||||
int count = 0;
|
||||
|
||||
FOR_EACH_VEC( m_knownEntityVector, it )
|
||||
{
|
||||
const CKnownEntity &known = m_knownEntityVector[ it ];
|
||||
|
||||
if ( !known.IsObsolete() && IsAwareOf( known ) )
|
||||
{
|
||||
if ( team == TEAM_ANY || known.GetEntity()->GetTeamNumber() == team )
|
||||
{
|
||||
if ( !onlyVisible || known.IsVisibleRecently() )
|
||||
{
|
||||
if ( rangeLimit < 0.0f || GetBot()->IsRangeLessThan( known.GetLastKnownPosition(), rangeLimit ) )
|
||||
{
|
||||
++count;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------------------
|
||||
class PopulateVisibleVector
|
||||
{
|
||||
public:
|
||||
PopulateVisibleVector( CUtlVector< CBaseEntity * > *potentiallyVisible )
|
||||
{
|
||||
m_potentiallyVisible = potentiallyVisible;
|
||||
}
|
||||
|
||||
bool operator() ( CBaseEntity *actor )
|
||||
{
|
||||
m_potentiallyVisible->AddToTail( actor );
|
||||
return true;
|
||||
}
|
||||
|
||||
CUtlVector< CBaseEntity * > *m_potentiallyVisible;
|
||||
};
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Populate "potentiallyVisible" with the set of all entities we could potentially see.
|
||||
* Entities in this set will be tested for visibility/recognition in IVision::Update()
|
||||
*/
|
||||
void IVision::CollectPotentiallyVisibleEntities( CUtlVector< CBaseEntity * > *potentiallyVisible )
|
||||
{
|
||||
potentiallyVisible->RemoveAll();
|
||||
|
||||
// by default, only consider players and other bots as potentially visible
|
||||
PopulateVisibleVector populate( potentiallyVisible );
|
||||
ForEachActor( populate );
|
||||
}
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------------------
|
||||
class CollectVisible
|
||||
{
|
||||
public:
|
||||
CollectVisible( IVision *vision )
|
||||
{
|
||||
m_vision = vision;
|
||||
}
|
||||
|
||||
bool operator() ( CBaseEntity *entity )
|
||||
{
|
||||
if ( entity &&
|
||||
!m_vision->IsIgnored( entity ) &&
|
||||
entity->IsAlive() &&
|
||||
entity != m_vision->GetBot()->GetEntity() &&
|
||||
m_vision->IsAbleToSee( entity, IVision::USE_FOV ) )
|
||||
{
|
||||
m_recognized.AddToTail( entity );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Contains( CBaseEntity *entity ) const
|
||||
{
|
||||
for( int i=0; i < m_recognized.Count(); ++i )
|
||||
{
|
||||
if ( entity->entindex() == m_recognized[ i ]->entindex() )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
IVision *m_vision;
|
||||
CUtlVector< CBaseEntity * > m_recognized;
|
||||
};
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------------------
|
||||
void IVision::UpdateKnownEntities( void )
|
||||
{
|
||||
VPROF_BUDGET( "IVision::UpdateKnownEntities", "NextBot" );
|
||||
|
||||
// construct set of potentially visible objects
|
||||
CUtlVector< CBaseEntity * > potentiallyVisible;
|
||||
CollectPotentiallyVisibleEntities( &potentiallyVisible );
|
||||
|
||||
// collect set of visible and recognized entities at this moment
|
||||
CollectVisible visibleNow( this );
|
||||
FOR_EACH_VEC( potentiallyVisible, pit )
|
||||
{
|
||||
VPROF_BUDGET( "IVision::UpdateKnownEntities( collect visible )", "NextBot" );
|
||||
|
||||
if ( visibleNow( potentiallyVisible[ pit ] ) == false )
|
||||
break;
|
||||
}
|
||||
|
||||
// update known set with new data
|
||||
{ VPROF_BUDGET( "IVision::UpdateKnownEntities( update status )", "NextBot" );
|
||||
|
||||
int i;
|
||||
for( i=0; i < m_knownEntityVector.Count(); ++i )
|
||||
{
|
||||
CKnownEntity &known = m_knownEntityVector[i];
|
||||
|
||||
// clear out obsolete knowledge
|
||||
if ( known.GetEntity() == NULL || known.IsObsolete() )
|
||||
{
|
||||
m_knownEntityVector.Remove( i );
|
||||
--i;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( visibleNow.Contains( known.GetEntity() ) )
|
||||
{
|
||||
// this visible entity was already known (but perhaps not visible until now)
|
||||
known.UpdatePosition();
|
||||
known.UpdateVisibilityStatus( true );
|
||||
|
||||
// has our reaction time just elapsed?
|
||||
if ( gpGlobals->curtime - known.GetTimeWhenBecameVisible() >= GetMinRecognizeTime() &&
|
||||
m_lastVisionUpdateTimestamp - known.GetTimeWhenBecameVisible() < GetMinRecognizeTime() )
|
||||
{
|
||||
if ( GetBot()->IsDebugging( NEXTBOT_VISION ) )
|
||||
{
|
||||
ConColorMsg( Color( 0, 255, 0, 255 ), "%3.2f: %s caught sight of %s(#%d)\n",
|
||||
gpGlobals->curtime,
|
||||
GetBot()->GetDebugIdentifier(),
|
||||
known.GetEntity()->GetClassname(),
|
||||
known.GetEntity()->entindex() );
|
||||
|
||||
NDebugOverlay::Line( GetBot()->GetBodyInterface()->GetEyePosition(), known.GetLastKnownPosition(), 255, 255, 0, false, 0.2f );
|
||||
}
|
||||
|
||||
GetBot()->OnSight( known.GetEntity() );
|
||||
}
|
||||
|
||||
// restart 'not seen' timer
|
||||
m_notVisibleTimer[ known.GetEntity()->GetTeamNumber() ].Start();
|
||||
}
|
||||
else // known entity is not currently visible
|
||||
{
|
||||
if ( known.IsVisibleInFOVNow() )
|
||||
{
|
||||
// previously known and visible entity is now no longer visible
|
||||
known.UpdateVisibilityStatus( false );
|
||||
|
||||
// lost sight of this entity
|
||||
if ( GetBot()->IsDebugging( NEXTBOT_VISION ) )
|
||||
{
|
||||
ConColorMsg( Color( 255, 0, 0, 255 ), "%3.2f: %s Lost sight of %s(#%d)\n",
|
||||
gpGlobals->curtime,
|
||||
GetBot()->GetDebugIdentifier(),
|
||||
known.GetEntity()->GetClassname(),
|
||||
known.GetEntity()->entindex() );
|
||||
}
|
||||
|
||||
GetBot()->OnLostSight( known.GetEntity() );
|
||||
}
|
||||
|
||||
if ( !known.HasLastKnownPositionBeenSeen() )
|
||||
{
|
||||
// can we see the entity's last know position?
|
||||
if ( IsAbleToSee( known.GetLastKnownPosition(), IVision::USE_FOV ) )
|
||||
{
|
||||
known.MarkLastKnownPositionAsSeen();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check for new recognizes that were not in the known set
|
||||
{ VPROF_BUDGET( "IVision::UpdateKnownEntities( new recognizes )", "NextBot" );
|
||||
|
||||
int i, j;
|
||||
for( i=0; i < visibleNow.m_recognized.Count(); ++i )
|
||||
{
|
||||
for( j=0; j < m_knownEntityVector.Count(); ++j )
|
||||
{
|
||||
if ( visibleNow.m_recognized[i] == m_knownEntityVector[j].GetEntity() )
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( j == m_knownEntityVector.Count() )
|
||||
{
|
||||
// recognized a previously unknown entity (emit OnSight() event after reaction time has passed)
|
||||
CKnownEntity known( visibleNow.m_recognized[i] );
|
||||
known.UpdatePosition();
|
||||
known.UpdateVisibilityStatus( true );
|
||||
m_knownEntityVector.AddToTail( known );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// debugging
|
||||
if ( nb_debug_known_entities.GetBool() )
|
||||
{
|
||||
CBasePlayer *watcher = UTIL_GetListenServerHost();
|
||||
if ( watcher )
|
||||
{
|
||||
CBaseEntity *subject = watcher->GetObserverTarget();
|
||||
|
||||
if ( subject && GetBot()->IsSelf( subject ) )
|
||||
{
|
||||
CUtlVector< CKnownEntity > knownVector;
|
||||
CollectKnownEntities( &knownVector );
|
||||
|
||||
for( int i=0; i < knownVector.Count(); ++i )
|
||||
{
|
||||
CKnownEntity &known = knownVector[i];
|
||||
|
||||
if ( GetBot()->IsFriend( known.GetEntity() ) )
|
||||
{
|
||||
if ( IsAwareOf( known ) )
|
||||
{
|
||||
if ( known.IsVisibleInFOVNow() )
|
||||
NDebugOverlay::HorzArrow( GetBot()->GetEntity()->GetAbsOrigin(), known.GetLastKnownPosition(), 5.0f, 0, 255, 0, 255, true, NDEBUG_PERSIST_TILL_NEXT_SERVER );
|
||||
else
|
||||
NDebugOverlay::HorzArrow( GetBot()->GetEntity()->GetAbsOrigin(), known.GetLastKnownPosition(), 2.0f, 0, 100, 0, 255, true, NDEBUG_PERSIST_TILL_NEXT_SERVER );
|
||||
}
|
||||
else
|
||||
{
|
||||
NDebugOverlay::HorzArrow( GetBot()->GetEntity()->GetAbsOrigin(), known.GetLastKnownPosition(), 1.0f, 0, 100, 0, 128, true, NDEBUG_PERSIST_TILL_NEXT_SERVER );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( IsAwareOf( known ) )
|
||||
{
|
||||
if ( known.IsVisibleInFOVNow() )
|
||||
NDebugOverlay::HorzArrow( GetBot()->GetEntity()->GetAbsOrigin(), known.GetLastKnownPosition(), 5.0f, 255, 0, 0, 255, true, NDEBUG_PERSIST_TILL_NEXT_SERVER );
|
||||
else
|
||||
NDebugOverlay::HorzArrow( GetBot()->GetEntity()->GetAbsOrigin(), known.GetLastKnownPosition(), 2.0f, 100, 0, 0, 255, true, NDEBUG_PERSIST_TILL_NEXT_SERVER );
|
||||
}
|
||||
else
|
||||
{
|
||||
NDebugOverlay::HorzArrow( GetBot()->GetEntity()->GetAbsOrigin(), known.GetLastKnownPosition(), 1.0f, 100, 0, 0, 128, true, NDEBUG_PERSIST_TILL_NEXT_SERVER );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Update internal state
|
||||
*/
|
||||
void IVision::Update( void )
|
||||
{
|
||||
VPROF_BUDGET( "IVision::Update", "NextBotExpensive" );
|
||||
|
||||
/* This adds significantly to bot's reaction times
|
||||
// throttle update rate
|
||||
if ( !m_scanTimer.IsElapsed() )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_scanTimer.Start( 0.5f * GetMinRecognizeTime() );
|
||||
*/
|
||||
|
||||
if ( nb_blind.GetBool() )
|
||||
{
|
||||
m_knownEntityVector.RemoveAll();
|
||||
return;
|
||||
}
|
||||
|
||||
UpdateKnownEntities();
|
||||
|
||||
m_lastVisionUpdateTimestamp = gpGlobals->curtime;
|
||||
}
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------------------
|
||||
bool IVision::IsAbleToSee( CBaseEntity *subject, FieldOfViewCheckType checkFOV, Vector *visibleSpot ) const
|
||||
{
|
||||
VPROF_BUDGET( "IVision::IsAbleToSee", "NextBotExpensive" );
|
||||
|
||||
if ( GetBot()->IsRangeGreaterThan( subject, GetMaxVisionRange() ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
if ( GetBot()->GetEntity()->IsHiddenByFog( subject ) )
|
||||
{
|
||||
// lost in the fog
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( checkFOV == USE_FOV && !IsInFieldOfView( subject ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
CBaseCombatCharacter *combat = subject->MyCombatCharacterPointer();
|
||||
if ( combat )
|
||||
{
|
||||
CNavArea *subjectArea = combat->GetLastKnownArea();
|
||||
CNavArea *myArea = GetBot()->GetEntity()->GetLastKnownArea();
|
||||
if ( myArea && subjectArea )
|
||||
{
|
||||
if ( !myArea->IsPotentiallyVisible( subjectArea ) )
|
||||
{
|
||||
// subject is not potentially visible, skip the expensive raycast
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// do actual line-of-sight trace
|
||||
if ( !IsLineOfSightClearToEntity( subject ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return IsVisibleEntityNoticed( subject );
|
||||
}
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------------------
|
||||
bool IVision::IsAbleToSee( const Vector &pos, FieldOfViewCheckType checkFOV ) const
|
||||
{
|
||||
VPROF_BUDGET( "IVision::IsAbleToSee", "NextBotExpensive" );
|
||||
|
||||
|
||||
if ( GetBot()->IsRangeGreaterThan( pos, GetMaxVisionRange() ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( GetBot()->GetEntity()->IsHiddenByFog( pos ) )
|
||||
{
|
||||
// lost in the fog
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( checkFOV == USE_FOV && !IsInFieldOfView( pos ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// do actual line-of-sight trace
|
||||
return IsLineOfSightClear( pos );
|
||||
}
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Angle given in degrees
|
||||
*/
|
||||
void IVision::SetFieldOfView( float horizAngle )
|
||||
{
|
||||
m_FOV = horizAngle;
|
||||
m_cosHalfFOV = cos( 0.5f * m_FOV * M_PI / 180.0f );
|
||||
}
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------------------
|
||||
bool IVision::IsInFieldOfView( const Vector &pos ) const
|
||||
{
|
||||
#ifdef CHECK_OLD_CODE_AGAINST_NEW
|
||||
bool bCheck = PointWithinViewAngle( GetBot()->GetBodyInterface()->GetEyePosition(), pos, GetBot()->GetBodyInterface()->GetViewVector(), m_cosHalfFOV );
|
||||
Vector to = pos - GetBot()->GetBodyInterface()->GetEyePosition();
|
||||
to.NormalizeInPlace();
|
||||
|
||||
float cosDiff = DotProduct( GetBot()->GetBodyInterface()->GetViewVector(), to );
|
||||
|
||||
if ( ( cosDiff > m_cosHalfFOV ) != bCheck )
|
||||
{
|
||||
Assert(0);
|
||||
bool bCheck2 =
|
||||
PointWithinViewAngle( GetBot()->GetBodyInterface()->GetEyePosition(), pos, GetBot()->GetBodyInterface()->GetViewVector(), m_cosHalfFOV );
|
||||
|
||||
}
|
||||
|
||||
return ( cosDiff > m_cosHalfFOV );
|
||||
#else
|
||||
return PointWithinViewAngle( GetBot()->GetBodyInterface()->GetEyePosition(), pos, GetBot()->GetBodyInterface()->GetViewVector(), m_cosHalfFOV );
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------------------
|
||||
bool IVision::IsInFieldOfView( CBaseEntity *subject ) const
|
||||
{
|
||||
/// @todo check more points
|
||||
if ( IsInFieldOfView( subject->WorldSpaceCenter() ) )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return IsInFieldOfView( subject->EyePosition() );
|
||||
}
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Return true if the ray to the given point is unobstructed
|
||||
*/
|
||||
bool IVision::IsLineOfSightClear( const Vector &pos ) const
|
||||
{
|
||||
VPROF_BUDGET( "IVision::IsLineOfSightClear", "NextBot" );
|
||||
VPROF_INCREMENT_COUNTER( "IVision::IsLineOfSightClear", 1 );
|
||||
|
||||
trace_t result;
|
||||
NextBotVisionTraceFilter filter( GetBot()->GetEntity(), COLLISION_GROUP_NONE );
|
||||
|
||||
UTIL_TraceLine( GetBot()->GetBodyInterface()->GetEyePosition(), pos, MASK_BLOCKLOS_AND_NPCS|CONTENTS_IGNORE_NODRAW_OPAQUE, &filter, &result );
|
||||
|
||||
return ( result.fraction >= 1.0f && !result.startsolid );
|
||||
}
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------------------
|
||||
bool IVision::IsLineOfSightClearToEntity( const CBaseEntity *subject, Vector *visibleSpot ) const
|
||||
{
|
||||
#ifdef TERROR
|
||||
// TODO: Integration querycache & its dependencies
|
||||
|
||||
VPROF_INCREMENT_COUNTER( "IVision::IsLineOfSightClearToEntity", 1 );
|
||||
VPROF_BUDGET( "IVision::IsLineOfSightClearToEntity", "NextBotSpiky" );
|
||||
|
||||
bool bClear = IsLineOfSightBetweenTwoEntitiesClear( GetBot()->GetBodyInterface()->GetEntity(), EOFFSET_MODE_EYEPOSITION,
|
||||
subject, EOFFSET_MODE_WORLDSPACE_CENTER,
|
||||
subject, COLLISION_GROUP_NONE,
|
||||
MASK_BLOCKLOS_AND_NPCS|CONTENTS_IGNORE_NODRAW_OPAQUE, VisionTraceFilterFunction, 1.0 );
|
||||
|
||||
#ifdef USE_NON_CACHE_QUERY
|
||||
trace_t result;
|
||||
NextBotTraceFilterIgnoreActors filter( subject, COLLISION_GROUP_NONE );
|
||||
|
||||
UTIL_TraceLine( GetBot()->GetBodyInterface()->GetEyePosition(), subject->WorldSpaceCenter(), MASK_BLOCKLOS_AND_NPCS|CONTENTS_IGNORE_NODRAW_OPAQUE, &filter, &result );
|
||||
Assert( result.DidHit() != bClear );
|
||||
if ( subject->IsPlayer() && ! bClear )
|
||||
{
|
||||
UTIL_TraceLine( GetBot()->GetBodyInterface()->GetEyePosition(), subject->EyePosition(), MASK_BLOCKLOS_AND_NPCS|CONTENTS_IGNORE_NODRAW_OPAQUE, &filter, &result );
|
||||
bClear = IsLineOfSightBetweenTwoEntitiesClear( GetBot()->GetEntity(),
|
||||
EOFFSET_MODE_EYEPOSITION,
|
||||
subject, EOFFSET_MODE_EYEPOSITION,
|
||||
subject, COLLISION_GROUP_NONE,
|
||||
MASK_BLOCKLOS_AND_NPCS|CONTENTS_IGNORE_NODRAW_OPAQUE,
|
||||
IgnoreActorsTraceFilterFunction, 1.0 );
|
||||
|
||||
// this WILL assert - the query interface happens at a different time, and has hysteresis.
|
||||
Assert( result.DidHit() != bClear );
|
||||
}
|
||||
#endif
|
||||
|
||||
return bClear;
|
||||
|
||||
#else
|
||||
|
||||
// TODO: Use plain-old traces until querycache/etc gets integrated
|
||||
VPROF_BUDGET( "IVision::IsLineOfSightClearToEntity", "NextBot" );
|
||||
|
||||
trace_t result;
|
||||
NextBotTraceFilterIgnoreActors filter( subject, COLLISION_GROUP_NONE );
|
||||
|
||||
UTIL_TraceLine( GetBot()->GetBodyInterface()->GetEyePosition(), subject->WorldSpaceCenter(), MASK_BLOCKLOS_AND_NPCS|CONTENTS_IGNORE_NODRAW_OPAQUE, &filter, &result );
|
||||
if ( result.DidHit() )
|
||||
{
|
||||
UTIL_TraceLine( GetBot()->GetBodyInterface()->GetEyePosition(), subject->EyePosition(), MASK_BLOCKLOS_AND_NPCS|CONTENTS_IGNORE_NODRAW_OPAQUE, &filter, &result );
|
||||
|
||||
if ( result.DidHit() )
|
||||
{
|
||||
UTIL_TraceLine( GetBot()->GetBodyInterface()->GetEyePosition(), subject->GetAbsOrigin(), MASK_BLOCKLOS_AND_NPCS|CONTENTS_IGNORE_NODRAW_OPAQUE, &filter, &result );
|
||||
}
|
||||
}
|
||||
|
||||
if ( visibleSpot )
|
||||
{
|
||||
*visibleSpot = result.endpos;
|
||||
}
|
||||
|
||||
return ( result.fraction >= 1.0f && !result.startsolid );
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Are we looking directly at the given position
|
||||
*/
|
||||
bool IVision::IsLookingAt( const Vector &pos, float cosTolerance ) const
|
||||
{
|
||||
Vector to = pos - GetBot()->GetBodyInterface()->GetEyePosition();
|
||||
to.NormalizeInPlace();
|
||||
|
||||
Vector forward;
|
||||
AngleVectors( GetBot()->GetEntity()->EyeAngles(), &forward );
|
||||
|
||||
return DotProduct( to, forward ) > cosTolerance;
|
||||
}
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Are we looking directly at the given actor
|
||||
*/
|
||||
bool IVision::IsLookingAt( const CBaseCombatCharacter *actor, float cosTolerance ) const
|
||||
{
|
||||
return IsLookingAt( actor->EyePosition(), cosTolerance );
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,226 @@
|
||||
// NextBotVisionInterface.h
|
||||
// Visual information query interface for bots
|
||||
// Author: Michael Booth, April 2005
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
|
||||
#ifndef _NEXT_BOT_VISION_INTERFACE_H_
|
||||
#define _NEXT_BOT_VISION_INTERFACE_H_
|
||||
|
||||
#include "NextBotComponentInterface.h"
|
||||
#include "NextBotKnownEntity.h"
|
||||
|
||||
class IBody;
|
||||
class INextBotEntityFilter;
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* The interface for HOW the bot sees (near sighted? night vision? etc)
|
||||
*/
|
||||
class IVision : public INextBotComponent
|
||||
{
|
||||
public:
|
||||
IVision( INextBot *bot );
|
||||
virtual ~IVision() { }
|
||||
|
||||
virtual void Reset( void ); // reset to initial state
|
||||
virtual void Update( void ); // update internal state
|
||||
|
||||
//-- attention/short term memory interface follows ------------------------------------------
|
||||
|
||||
//
|
||||
// WARNING: Do not keep CKnownEntity pointers returned by these methods, as they can be invalidated/freed
|
||||
//
|
||||
|
||||
/**
|
||||
* Iterate each interesting entity we are aware of.
|
||||
* If functor returns false, stop iterating and return false.
|
||||
* NOTE: known.GetEntity() is guaranteed to be non-NULL
|
||||
*/
|
||||
class IForEachKnownEntity
|
||||
{
|
||||
public:
|
||||
virtual bool Inspect( const CKnownEntity &known ) = 0;
|
||||
};
|
||||
virtual bool ForEachKnownEntity( IForEachKnownEntity &func );
|
||||
|
||||
virtual void CollectKnownEntities( CUtlVector< CKnownEntity > *knownVector ); // populate given vector with all currently known entities
|
||||
|
||||
virtual const CKnownEntity *GetPrimaryKnownThreat( bool onlyVisibleThreats = false ) const; // return the biggest threat to ourselves that we are aware of
|
||||
virtual float GetTimeSinceVisible( int team ) const; // return time since we saw any member of the given team
|
||||
|
||||
virtual const CKnownEntity *GetClosestKnown( int team = TEAM_ANY ) const; // return the closest known entity
|
||||
virtual int GetKnownCount( int team, bool onlyVisible = false, float rangeLimit = -1.0f ) const; // return the number of entities on the given team known to us closer than rangeLimit
|
||||
|
||||
virtual const CKnownEntity *GetClosestKnown( const INextBotEntityFilter &filter ) const; // return the closest known entity that passes the given filter
|
||||
|
||||
virtual const CKnownEntity *GetKnown( const CBaseEntity *entity ) const; // given an entity, return our known version of it (or NULL if we don't know of it)
|
||||
|
||||
// Introduce a known entity into the system. Its position is assumed to be known
|
||||
// and will be updated, and it is assumed to not yet have been seen by us, allowing for learning
|
||||
// of known entities by being told about them, hearing them, etc.
|
||||
virtual void AddKnownEntity( CBaseEntity *entity );
|
||||
|
||||
virtual void ForgetEntity( CBaseEntity *forgetMe ); // remove the given entity from our awareness (whether we know if it or not)
|
||||
virtual void ForgetAllKnownEntities( void );
|
||||
|
||||
//-- physical vision interface follows ------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Populate "potentiallyVisible" with the set of all entities we could potentially see.
|
||||
* Entities in this set will be tested for visibility/recognition in IVision::Update()
|
||||
*/
|
||||
virtual void CollectPotentiallyVisibleEntities( CUtlVector< CBaseEntity * > *potentiallyVisible );
|
||||
|
||||
virtual float GetMaxVisionRange( void ) const; // return maximum distance vision can reach
|
||||
virtual float GetMinRecognizeTime( void ) const; // return VISUAL reaction time
|
||||
|
||||
/**
|
||||
* IsAbleToSee() returns true if the viewer can ACTUALLY SEE the subject or position,
|
||||
* taking into account blindness, smoke effects, invisibility, etc.
|
||||
* If 'visibleSpot' is non-NULL, the highest priority spot on the subject that is visible is returned.
|
||||
*/
|
||||
enum FieldOfViewCheckType { USE_FOV, DISREGARD_FOV };
|
||||
virtual bool IsAbleToSee( CBaseEntity *subject, FieldOfViewCheckType checkFOV, Vector *visibleSpot = NULL ) const;
|
||||
virtual bool IsAbleToSee( const Vector &pos, FieldOfViewCheckType checkFOV ) const;
|
||||
|
||||
virtual bool IsIgnored( CBaseEntity *subject ) const; // return true to completely ignore this entity (may not be in sight when this is called)
|
||||
virtual bool IsVisibleEntityNoticed( CBaseEntity *subject ) const; // return true if we 'notice' the subject, even though we have LOS to it
|
||||
|
||||
/**
|
||||
* Check if 'subject' is within the viewer's field of view
|
||||
*/
|
||||
virtual bool IsInFieldOfView( const Vector &pos ) const;
|
||||
virtual bool IsInFieldOfView( CBaseEntity *subject ) const;
|
||||
virtual float GetDefaultFieldOfView( void ) const; // return default FOV in degrees
|
||||
virtual float GetFieldOfView( void ) const; // return FOV in degrees
|
||||
virtual void SetFieldOfView( float horizAngle ); // angle given in degrees
|
||||
|
||||
virtual bool IsLineOfSightClear( const Vector &pos ) const; // return true if the ray to the given point is unobstructed
|
||||
|
||||
/**
|
||||
* Returns true if the ray between the position and the subject is unobstructed.
|
||||
* A visible spot on the subject is returned in 'visibleSpot'.
|
||||
*/
|
||||
virtual bool IsLineOfSightClearToEntity( const CBaseEntity *subject, Vector *visibleSpot = NULL ) const;
|
||||
|
||||
/// @todo: Implement LookAt system
|
||||
virtual bool IsLookingAt( const Vector &pos, float cosTolerance = 0.95f ) const; // are we looking at the given position
|
||||
virtual bool IsLookingAt( const CBaseCombatCharacter *actor, float cosTolerance = 0.95f ) const; // are we looking at the given actor
|
||||
|
||||
private:
|
||||
CountdownTimer m_scanTimer; // for throttling update rate
|
||||
|
||||
float m_FOV; // current FOV in degrees
|
||||
float m_cosHalfFOV; // the cosine of FOV/2
|
||||
|
||||
CUtlVector< CKnownEntity > m_knownEntityVector; // the set of enemies/friends we are aware of
|
||||
void UpdateKnownEntities( void );
|
||||
bool IsAwareOf( const CKnownEntity &known ) const; // return true if our reaction time has passed for this entity
|
||||
mutable CHandle< CBaseEntity > m_primaryThreat;
|
||||
|
||||
float m_lastVisionUpdateTimestamp;
|
||||
IntervalTimer m_notVisibleTimer[ MAX_TEAMS ]; // for tracking interval since last saw a member of the given team
|
||||
};
|
||||
|
||||
inline void IVision::CollectKnownEntities( CUtlVector< CKnownEntity > *knownVector )
|
||||
{
|
||||
if ( knownVector )
|
||||
{
|
||||
knownVector->RemoveAll();
|
||||
|
||||
for( int i=0; i<m_knownEntityVector.Count(); ++i )
|
||||
{
|
||||
if ( !m_knownEntityVector[i].IsObsolete() )
|
||||
{
|
||||
knownVector->AddToTail( m_knownEntityVector[i] );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline float IVision::GetDefaultFieldOfView( void ) const
|
||||
{
|
||||
return 90.0f;
|
||||
}
|
||||
|
||||
inline float IVision::GetFieldOfView( void ) const
|
||||
{
|
||||
return m_FOV;
|
||||
}
|
||||
|
||||
|
||||
inline float IVision::GetTimeSinceVisible( int team ) const
|
||||
{
|
||||
if ( team == TEAM_ANY )
|
||||
{
|
||||
// return minimum time
|
||||
float time = 9999999999.9f;
|
||||
for( int i=0; i<MAX_TEAMS; ++i )
|
||||
{
|
||||
if ( m_notVisibleTimer[i].HasStarted() )
|
||||
{
|
||||
if ( time > m_notVisibleTimer[i].GetElapsedTime() )
|
||||
{
|
||||
team = m_notVisibleTimer[i].GetElapsedTime();
|
||||
}
|
||||
}
|
||||
}
|
||||
return time;
|
||||
}
|
||||
|
||||
if ( team >= 0 && team < MAX_TEAMS )
|
||||
{
|
||||
return m_notVisibleTimer[ team ].GetElapsedTime();
|
||||
}
|
||||
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
|
||||
inline bool IVision::IsAwareOf( const CKnownEntity &known ) const
|
||||
{
|
||||
return known.GetTimeSinceBecameKnown() >= GetMinRecognizeTime();
|
||||
}
|
||||
|
||||
|
||||
inline bool IVision::ForEachKnownEntity( IVision::IForEachKnownEntity &func )
|
||||
{
|
||||
for( int i=0; i<m_knownEntityVector.Count(); ++i )
|
||||
{
|
||||
const CKnownEntity &known = m_knownEntityVector[i];
|
||||
|
||||
if ( !known.IsObsolete() && IsAwareOf( known ) )
|
||||
{
|
||||
if ( func.Inspect( known ) == false )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool IVision::IsVisibleEntityNoticed( CBaseEntity *subject ) const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool IVision::IsIgnored( CBaseEntity *subject ) const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
inline float IVision::GetMaxVisionRange( void ) const
|
||||
{
|
||||
return 2000.0f;
|
||||
}
|
||||
|
||||
inline float IVision::GetMinRecognizeTime( void ) const
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
|
||||
#endif // _NEXT_BOT_VISION_INTERFACE_H_
|
||||
@@ -0,0 +1,166 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose:
|
||||
//
|
||||
//=============================================================================
|
||||
|
||||
#include "cbase.h"
|
||||
|
||||
#include "NextBotChasePath.h"
|
||||
#include "tier1/fmtstr.h"
|
||||
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Try to cutoff our chase subject
|
||||
*/
|
||||
Vector ChasePath::PredictSubjectPosition( INextBot *bot, CBaseEntity *subject ) const
|
||||
{
|
||||
ILocomotion *mover = bot->GetLocomotionInterface();
|
||||
|
||||
const Vector &subjectPos = subject->GetAbsOrigin();
|
||||
|
||||
Vector to = subjectPos - bot->GetPosition();
|
||||
to.z = 0.0f;
|
||||
float flRangeSq = to.LengthSqr();
|
||||
|
||||
// don't lead if subject is very far away
|
||||
float flLeadRadiusSq = GetLeadRadius();
|
||||
flLeadRadiusSq *= flLeadRadiusSq;
|
||||
if ( flRangeSq > flLeadRadiusSq )
|
||||
return subjectPos;
|
||||
|
||||
// Normalize in place
|
||||
float range = sqrt( flRangeSq );
|
||||
to /= ( range + 0.0001f ); // avoid divide by zero
|
||||
|
||||
// estimate time to reach subject, assuming maximum speed
|
||||
float leadTime = 0.5f + ( range / ( mover->GetRunSpeed() + 0.0001f ) );
|
||||
|
||||
// estimate amount to lead the subject
|
||||
Vector lead = leadTime * subject->GetAbsVelocity();
|
||||
lead.z = 0.0f;
|
||||
|
||||
if ( DotProduct( to, lead ) < 0.0f )
|
||||
{
|
||||
// the subject is moving towards us - only pay attention
|
||||
// to his perpendicular velocity for leading
|
||||
Vector2D to2D = to.AsVector2D();
|
||||
to2D.NormalizeInPlace();
|
||||
|
||||
Vector2D perp( -to2D.y, to2D.x );
|
||||
|
||||
float enemyGroundSpeed = lead.x * perp.x + lead.y * perp.y;
|
||||
|
||||
lead.x = enemyGroundSpeed * perp.x;
|
||||
lead.y = enemyGroundSpeed * perp.y;
|
||||
}
|
||||
|
||||
// compute our desired destination
|
||||
Vector pathTarget = subjectPos + lead;
|
||||
|
||||
// validate this destination
|
||||
|
||||
// don't lead through walls
|
||||
if ( lead.LengthSqr() > 36.0f )
|
||||
{
|
||||
float fraction;
|
||||
if ( !mover->IsPotentiallyTraversable( subjectPos, pathTarget, ILocomotion::IMMEDIATELY, &fraction ) )
|
||||
{
|
||||
// tried to lead through an unwalkable area - clip to walkable space
|
||||
pathTarget = subjectPos + fraction * ( pathTarget - subjectPos );
|
||||
}
|
||||
}
|
||||
|
||||
// don't lead over cliffs
|
||||
CNavArea *leadArea = NULL;
|
||||
|
||||
#ifdef NEED_GPGLOBALS_SERVERCOUNT_TO_DO_THIS
|
||||
CBaseCombatCharacter *pBCC = subject->MyCombatCharacterPointer();
|
||||
if ( pBCC && CloseEnough( pathTarget, subjectPos, 3.0 ) )
|
||||
{
|
||||
pathTarget = subjectPos;
|
||||
leadArea = pBCC->GetLastKnownArea(); // can return null?
|
||||
}
|
||||
else
|
||||
{
|
||||
struct CacheEntry_t
|
||||
{
|
||||
CacheEntry_t() : pArea(NULL) {}
|
||||
Vector target;
|
||||
CNavArea *pArea;
|
||||
};
|
||||
|
||||
static int iServer;
|
||||
static CacheEntry_t cache[4];
|
||||
static int iNext;
|
||||
int i;
|
||||
|
||||
bool bFound = false;
|
||||
if ( iServer != gpGlobals->serverCount )
|
||||
{
|
||||
for ( i = 0; i < ARRAYSIZE(cache); i++ )
|
||||
{
|
||||
cache[i].pArea = NULL;
|
||||
}
|
||||
iServer = gpGlobals->serverCount;
|
||||
}
|
||||
else
|
||||
{
|
||||
for ( i = 0; i < ARRAYSIZE(cache); i++ )
|
||||
{
|
||||
if ( cache[i].pArea && CloseEnough( cache[i].target, pathTarget, 2.0 ) )
|
||||
{
|
||||
pathTarget = cache[i].target;
|
||||
leadArea = cache[i].pArea;
|
||||
bFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( !bFound )
|
||||
{
|
||||
leadArea = TheNavMesh->GetNearestNavArea( pathTarget );
|
||||
if ( leadArea )
|
||||
{
|
||||
cache[iNext].target = pathTarget;
|
||||
cache[iNext].pArea = leadArea;
|
||||
iNext = ( iNext + 1 ) % ARRAYSIZE( cache );
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
leadArea = TheNavMesh->GetNearestNavArea( pathTarget );
|
||||
#endif
|
||||
|
||||
|
||||
if ( !leadArea || leadArea->GetZ( pathTarget.x, pathTarget.y ) < pathTarget.z - mover->GetMaxJumpHeight() )
|
||||
{
|
||||
// would fall off a cliff
|
||||
return subjectPos;
|
||||
}
|
||||
|
||||
/** This needs more thought - it is preventing bots from using dropdowns
|
||||
if ( mover->HasPotentialGap( subjectPos, pathTarget, &fraction ) )
|
||||
{
|
||||
// tried to lead over a cliff - clip to safe region
|
||||
pathTarget = subjectPos + fraction * ( pathTarget - subjectPos );
|
||||
}
|
||||
*/
|
||||
|
||||
return pathTarget;
|
||||
}
|
||||
|
||||
// if the victim is a player, poke them so they know they're being chased
|
||||
void DirectChasePath::NotifyVictim( INextBot *me, CBaseEntity *victim )
|
||||
{
|
||||
CBaseCombatCharacter *pBCCVictim = ToBaseCombatCharacter( victim );
|
||||
if ( !pBCCVictim )
|
||||
return;
|
||||
|
||||
pBCCVictim->OnPursuedBy( me );
|
||||
}
|
||||
@@ -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_
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,862 @@
|
||||
// NextBotPath.h
|
||||
// Encapsulate and manipulate a path through the world
|
||||
// Author: Michael Booth, February 2006
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
|
||||
#ifndef _NEXT_BOT_PATH_H_
|
||||
#define _NEXT_BOT_PATH_H_
|
||||
|
||||
#include "NextBotInterface.h"
|
||||
|
||||
#include "tier0/vprof.h"
|
||||
|
||||
#define PATH_NO_LENGTH_LIMIT 0.0f // non-default argument value for Path::Compute()
|
||||
#define PATH_TRUNCATE_INCOMPLETE_PATH false // non-default argument value for Path::Compute()
|
||||
|
||||
class INextBot;
|
||||
class CNavArea;
|
||||
class CNavLadder;
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* The interface for pathfinding costs.
|
||||
* TODO: Replace all template cost functors with this interface, so we can virtualize and derive from them.
|
||||
*/
|
||||
class IPathCost
|
||||
{
|
||||
public:
|
||||
virtual float operator()( CNavArea *area, CNavArea *fromArea, const CNavLadder *ladder, const CFuncElevator *elevator, float length ) const = 0;
|
||||
};
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* The interface for selecting a goal area during "open goal" pathfinding
|
||||
*/
|
||||
class IPathOpenGoalSelector
|
||||
{
|
||||
public:
|
||||
// compare "newArea" to "currentGoal" and return the area that is the better goal area
|
||||
virtual CNavArea *operator() ( CNavArea *currentGoal, CNavArea *newArea ) const = 0;
|
||||
};
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* A Path through the world.
|
||||
* Not only does this encapsulate a path to get from point A to point B,
|
||||
* but also the selecting the decision algorithm for how to build that path.
|
||||
*/
|
||||
class Path
|
||||
{
|
||||
public:
|
||||
Path( void );
|
||||
virtual ~Path() { }
|
||||
|
||||
enum SegmentType
|
||||
{
|
||||
ON_GROUND,
|
||||
DROP_DOWN,
|
||||
CLIMB_UP,
|
||||
JUMP_OVER_GAP,
|
||||
LADDER_UP,
|
||||
LADDER_DOWN,
|
||||
|
||||
NUM_SEGMENT_TYPES
|
||||
};
|
||||
|
||||
// @todo Allow custom Segment classes for different kinds of paths
|
||||
struct Segment
|
||||
{
|
||||
CNavArea *area; // the area along the path
|
||||
NavTraverseType how; // how to enter this area from the previous one
|
||||
Vector pos; // our movement goal position at this point in the path
|
||||
const CNavLadder *ladder; // if "how" refers to a ladder, this is it
|
||||
|
||||
SegmentType type; // how to traverse this segment of the path
|
||||
Vector forward; // unit vector along segment
|
||||
float length; // length of this segment
|
||||
float distanceFromStart; // distance of this node from the start of the path
|
||||
float curvature; // how much the path 'curves' at this point in the XY plane (0 = none, 1 = 180 degree doubleback)
|
||||
|
||||
Vector m_portalCenter; // position of center of 'portal' between previous area and this area
|
||||
float m_portalHalfWidth; // half width of 'portal'
|
||||
};
|
||||
|
||||
virtual float GetLength( void ) const; // return length of path from start to finish
|
||||
virtual const Vector &GetPosition( float distanceFromStart, const Segment *start = NULL ) const; // return a position on the path at the given distance from the path start
|
||||
virtual const Vector &GetClosestPosition( const Vector &pos, const Segment *start = NULL, float alongLimit = 0.0f ) const; // return the closest point on the path to the given position
|
||||
|
||||
virtual const Vector &GetStartPosition( void ) const; // return the position where this path starts
|
||||
virtual const Vector &GetEndPosition( void ) const; // return the position where this path ends
|
||||
virtual CBaseCombatCharacter *GetSubject( void ) const; // return the actor this path leads to, or NULL if there is no subject
|
||||
|
||||
virtual const Path::Segment *GetCurrentGoal( void ) const; // return current goal along the path we are trying to reach
|
||||
|
||||
virtual float GetAge( void ) const; // return "age" of this path (time since it was built)
|
||||
|
||||
enum SeekType
|
||||
{
|
||||
SEEK_ENTIRE_PATH, // search the entire path length
|
||||
SEEK_AHEAD, // search from current cursor position forward toward end of path
|
||||
SEEK_BEHIND // search from current cursor position backward toward path start
|
||||
};
|
||||
virtual void MoveCursorToClosestPosition( const Vector &pos, SeekType type = SEEK_ENTIRE_PATH, float alongLimit = 0.0f ) const; // Set cursor position to closest point on path to given position
|
||||
|
||||
enum MoveCursorType
|
||||
{
|
||||
PATH_ABSOLUTE_DISTANCE,
|
||||
PATH_RELATIVE_DISTANCE
|
||||
};
|
||||
virtual void MoveCursorToStart( void ); // set seek cursor to start of path
|
||||
virtual void MoveCursorToEnd( void ); // set seek cursor to end of path
|
||||
virtual void MoveCursor( float value, MoveCursorType type = PATH_ABSOLUTE_DISTANCE ); // change seek cursor position
|
||||
virtual float GetCursorPosition( void ) const; // return position of seek cursor (distance along path)
|
||||
|
||||
struct Data
|
||||
{
|
||||
Vector pos; // the position along the path
|
||||
Vector forward; // unit vector along path direction
|
||||
float curvature; // how much the path 'curves' at this point in the XY plane (0 = none, 1 = 180 degree doubleback)
|
||||
const Segment *segmentPrior; // the segment just before this position
|
||||
};
|
||||
virtual const Data &GetCursorData( void ) const; // return path state at the current cursor position
|
||||
|
||||
virtual bool IsValid( void ) const;
|
||||
virtual void Invalidate( void ); // make path invalid (clear it)
|
||||
|
||||
virtual void Draw( const Path::Segment *start = NULL ) const; // draw the path for debugging
|
||||
virtual void DrawInterpolated( float from, float to ); // draw the path for debugging - MODIFIES cursor position
|
||||
|
||||
virtual const Segment *FirstSegment( void ) const; // return first segment of path
|
||||
virtual const Segment *NextSegment( const Segment *currentSegment ) const; // return next segment of path, given current one
|
||||
virtual const Segment *PriorSegment( const Segment *currentSegment ) const; // return previous segment of path, given current one
|
||||
virtual const Segment *LastSegment( void ) const; // return last segment of path
|
||||
|
||||
enum ResultType
|
||||
{
|
||||
COMPLETE_PATH,
|
||||
PARTIAL_PATH,
|
||||
NO_PATH
|
||||
};
|
||||
virtual void OnPathChanged( INextBot *bot, ResultType result ) { } // invoked when the path is (re)computed (path is valid at the time of this call)
|
||||
|
||||
virtual void Copy( INextBot *bot, const Path &path ); // Replace this path with the given path's data
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Compute shortest path from bot to given actor via A* algorithm.
|
||||
* If returns true, path was found to the subject.
|
||||
* If returns false, path may either be invalid (use IsValid() to check), or valid but
|
||||
* doesn't reach all the way to the subject.
|
||||
*/
|
||||
template< typename CostFunctor >
|
||||
bool Compute( INextBot *bot, CBaseCombatCharacter *subject, CostFunctor &costFunc, float maxPathLength = 0.0f, bool includeGoalIfPathFails = true )
|
||||
{
|
||||
VPROF_BUDGET( "Path::Compute(subject)", "NextBot" );
|
||||
|
||||
Invalidate();
|
||||
|
||||
m_subject = subject;
|
||||
|
||||
const Vector &start = bot->GetPosition();
|
||||
|
||||
CNavArea *startArea = bot->GetEntity()->GetLastKnownArea();
|
||||
if ( !startArea )
|
||||
{
|
||||
OnPathChanged( bot, NO_PATH );
|
||||
return false;
|
||||
}
|
||||
|
||||
CNavArea *subjectArea = subject->GetLastKnownArea();
|
||||
if ( !subjectArea )
|
||||
{
|
||||
OnPathChanged( bot, NO_PATH );
|
||||
return false;
|
||||
}
|
||||
|
||||
Vector subjectPos = subject->GetAbsOrigin();
|
||||
|
||||
// if we are already in the subject area, build trivial path
|
||||
if ( startArea == subjectArea )
|
||||
{
|
||||
BuildTrivialPath( bot, subjectPos );
|
||||
return true;
|
||||
}
|
||||
|
||||
//
|
||||
// Compute shortest path to subject
|
||||
//
|
||||
CNavArea *closestArea = NULL;
|
||||
bool pathResult = NavAreaBuildPath( startArea, subjectArea, &subjectPos, costFunc, &closestArea, maxPathLength, bot->GetEntity()->GetTeamNumber() );
|
||||
|
||||
// Failed?
|
||||
if ( closestArea == NULL )
|
||||
return false;
|
||||
|
||||
//
|
||||
// Build actual path by following parent links back from goal area
|
||||
//
|
||||
|
||||
// get count
|
||||
int count = 0;
|
||||
CNavArea *area;
|
||||
for( area = closestArea; area; area = area->GetParent() )
|
||||
{
|
||||
++count;
|
||||
|
||||
if ( area == startArea )
|
||||
{
|
||||
// startArea can be re-evaluated during the pathfind and given a parent...
|
||||
break;
|
||||
}
|
||||
if ( count >= MAX_PATH_SEGMENTS-1 ) // save room for endpoint
|
||||
break;
|
||||
}
|
||||
|
||||
if ( count == 1 )
|
||||
{
|
||||
BuildTrivialPath( bot, subjectPos );
|
||||
return pathResult;
|
||||
}
|
||||
|
||||
// assemble path
|
||||
m_segmentCount = count;
|
||||
for( area = closestArea; count && area; area = area->GetParent() )
|
||||
{
|
||||
--count;
|
||||
m_path[ count ].area = area;
|
||||
m_path[ count ].how = area->GetParentHow();
|
||||
m_path[ count ].type = ON_GROUND;
|
||||
}
|
||||
|
||||
if ( pathResult || includeGoalIfPathFails )
|
||||
{
|
||||
// append actual subject position
|
||||
m_path[ m_segmentCount ].area = closestArea;
|
||||
m_path[ m_segmentCount ].pos = subjectPos;
|
||||
m_path[ m_segmentCount ].ladder = NULL;
|
||||
m_path[ m_segmentCount ].how = NUM_TRAVERSE_TYPES;
|
||||
m_path[ m_segmentCount ].type = ON_GROUND;
|
||||
++m_segmentCount;
|
||||
}
|
||||
|
||||
// compute path positions
|
||||
if ( ComputePathDetails( bot, start ) == false )
|
||||
{
|
||||
Invalidate();
|
||||
OnPathChanged( bot, NO_PATH );
|
||||
return false;
|
||||
}
|
||||
|
||||
// remove redundant nodes and clean up path
|
||||
Optimize( bot );
|
||||
|
||||
PostProcess();
|
||||
|
||||
OnPathChanged( bot, pathResult ? COMPLETE_PATH : PARTIAL_PATH );
|
||||
|
||||
return pathResult;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Compute shortest path from bot to 'goal' via A* algorithm.
|
||||
* If returns true, path was found to the goal position.
|
||||
* If returns false, path may either be invalid (use IsValid() to check), or valid but
|
||||
* doesn't reach all the way to the goal.
|
||||
*/
|
||||
template< typename CostFunctor >
|
||||
bool Compute( INextBot *bot, const Vector &goal, CostFunctor &costFunc, float maxPathLength = 0.0f, bool includeGoalIfPathFails = true )
|
||||
{
|
||||
VPROF_BUDGET( "Path::Compute(goal)", "NextBotSpiky" );
|
||||
|
||||
Invalidate();
|
||||
|
||||
const Vector &start = bot->GetPosition();
|
||||
|
||||
CNavArea *startArea = bot->GetEntity()->GetLastKnownArea();
|
||||
if ( !startArea )
|
||||
{
|
||||
OnPathChanged( bot, NO_PATH );
|
||||
return false;
|
||||
}
|
||||
|
||||
// check line-of-sight to the goal position when finding it's nav area
|
||||
const float maxDistanceToArea = 200.0f;
|
||||
CNavArea *goalArea = TheNavMesh->GetNearestNavArea( goal, true, maxDistanceToArea, true );
|
||||
|
||||
// if we are already in the goal area, build trivial path
|
||||
if ( startArea == goalArea )
|
||||
{
|
||||
BuildTrivialPath( bot, goal );
|
||||
return true;
|
||||
}
|
||||
|
||||
// make sure path end position is on the ground
|
||||
Vector pathEndPosition = goal;
|
||||
if ( goalArea )
|
||||
{
|
||||
pathEndPosition.z = goalArea->GetZ( pathEndPosition );
|
||||
}
|
||||
else
|
||||
{
|
||||
TheNavMesh->GetGroundHeight( pathEndPosition, &pathEndPosition.z );
|
||||
}
|
||||
|
||||
//
|
||||
// Compute shortest path to goal
|
||||
//
|
||||
CNavArea *closestArea = NULL;
|
||||
bool pathResult = NavAreaBuildPath( startArea, goalArea, &goal, costFunc, &closestArea, maxPathLength, bot->GetEntity()->GetTeamNumber() );
|
||||
|
||||
// Failed?
|
||||
if ( closestArea == NULL )
|
||||
return false;
|
||||
|
||||
//
|
||||
// Build actual path by following parent links back from goal area
|
||||
//
|
||||
|
||||
// get count
|
||||
int count = 0;
|
||||
CNavArea *area;
|
||||
for( area = closestArea; area; area = area->GetParent() )
|
||||
{
|
||||
++count;
|
||||
|
||||
if ( area == startArea )
|
||||
{
|
||||
// startArea can be re-evaluated during the pathfind and given a parent...
|
||||
break;
|
||||
}
|
||||
if ( count >= MAX_PATH_SEGMENTS-1 ) // save room for endpoint
|
||||
break;
|
||||
}
|
||||
|
||||
if ( count == 1 )
|
||||
{
|
||||
BuildTrivialPath( bot, goal );
|
||||
return pathResult;
|
||||
}
|
||||
|
||||
// assemble path
|
||||
m_segmentCount = count;
|
||||
for( area = closestArea; count && area; area = area->GetParent() )
|
||||
{
|
||||
--count;
|
||||
m_path[ count ].area = area;
|
||||
m_path[ count ].how = area->GetParentHow();
|
||||
m_path[ count ].type = ON_GROUND;
|
||||
}
|
||||
|
||||
if ( pathResult || includeGoalIfPathFails )
|
||||
{
|
||||
// append actual goal position
|
||||
m_path[ m_segmentCount ].area = closestArea;
|
||||
m_path[ m_segmentCount ].pos = pathEndPosition;
|
||||
m_path[ m_segmentCount ].ladder = NULL;
|
||||
m_path[ m_segmentCount ].how = NUM_TRAVERSE_TYPES;
|
||||
m_path[ m_segmentCount ].type = ON_GROUND;
|
||||
++m_segmentCount;
|
||||
}
|
||||
|
||||
// compute path positions
|
||||
if ( ComputePathDetails( bot, start ) == false )
|
||||
{
|
||||
Invalidate();
|
||||
OnPathChanged( bot, NO_PATH );
|
||||
return false;
|
||||
}
|
||||
|
||||
// remove redundant nodes and clean up path
|
||||
Optimize( bot );
|
||||
|
||||
PostProcess();
|
||||
|
||||
OnPathChanged( bot, pathResult ? COMPLETE_PATH : PARTIAL_PATH );
|
||||
|
||||
return pathResult;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Build a path from bot's current location to an undetermined goal area
|
||||
* that minimizes the given cost along the final path and meets the
|
||||
* goal criteria.
|
||||
*/
|
||||
virtual bool ComputeWithOpenGoal( INextBot *bot, const IPathCost &costFunc, const IPathOpenGoalSelector &goalSelector, float maxSearchRadius = 0.0f )
|
||||
{
|
||||
VPROF_BUDGET( "ComputeWithOpenGoal", "NextBot" );
|
||||
|
||||
int teamID = bot->GetEntity()->GetTeamNumber();
|
||||
|
||||
CNavArea *startArea = bot->GetEntity()->GetLastKnownArea();
|
||||
|
||||
if ( startArea == NULL )
|
||||
return NULL;
|
||||
|
||||
startArea->SetParent( NULL );
|
||||
|
||||
// start search
|
||||
CNavArea::ClearSearchLists();
|
||||
|
||||
float initCost = costFunc( startArea, NULL, NULL, NULL, -1.0f );
|
||||
if ( initCost < 0.0f )
|
||||
return NULL;
|
||||
|
||||
startArea->SetTotalCost( initCost );
|
||||
startArea->AddToOpenList();
|
||||
|
||||
// find our goal as we search
|
||||
CNavArea *goalArea = NULL;
|
||||
|
||||
//
|
||||
// Dijkstra's algorithm (since we don't know our goal).
|
||||
//
|
||||
while( !CNavArea::IsOpenListEmpty() )
|
||||
{
|
||||
// get next area to check
|
||||
CNavArea *area = CNavArea::PopOpenList();
|
||||
|
||||
area->AddToClosedList();
|
||||
|
||||
// don't consider blocked areas
|
||||
if ( area->IsBlocked( teamID ) )
|
||||
continue;
|
||||
|
||||
// build adjacent area array
|
||||
CollectAdjacentAreas( area );
|
||||
|
||||
// search adjacent areas
|
||||
for( int i=0; i<m_adjAreaIndex; ++i )
|
||||
{
|
||||
CNavArea *newArea = m_adjAreaVector[ i ].area;
|
||||
|
||||
// only visit each area once
|
||||
if ( newArea->IsClosed() )
|
||||
continue;
|
||||
|
||||
// don't consider blocked areas
|
||||
if ( newArea->IsBlocked( teamID ) )
|
||||
continue;
|
||||
|
||||
// don't use this area if it is out of range
|
||||
if ( maxSearchRadius > 0.0f && ( newArea->GetCenter() - bot->GetEntity()->GetAbsOrigin() ).IsLengthGreaterThan( maxSearchRadius ) )
|
||||
continue;
|
||||
|
||||
// determine cost of traversing this area
|
||||
float newCost = costFunc( newArea, area, m_adjAreaVector[ i ].ladder, NULL, -1.0f );
|
||||
|
||||
// don't use adjacent area if cost functor says it is a dead-end
|
||||
if ( newCost < 0.0f )
|
||||
continue;
|
||||
|
||||
if ( newArea->IsOpen() && newArea->GetTotalCost() <= newCost )
|
||||
{
|
||||
// we have already visited this area, and it has a better path
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
// whether this area has been visited or not, we now have a better path to it
|
||||
newArea->SetParent( area, m_adjAreaVector[ i ].how );
|
||||
newArea->SetTotalCost( newCost );
|
||||
|
||||
// use 'cost so far' to hold cumulative cost
|
||||
newArea->SetCostSoFar( newCost );
|
||||
|
||||
// tricky bit here - relying on OpenList being sorted by cost
|
||||
if ( newArea->IsOpen() )
|
||||
{
|
||||
// area already on open list, update the list order to keep costs sorted
|
||||
newArea->UpdateOnOpenList();
|
||||
}
|
||||
else
|
||||
{
|
||||
newArea->AddToOpenList();
|
||||
}
|
||||
|
||||
// keep track of best goal so far
|
||||
goalArea = goalSelector( goalArea, newArea );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( goalArea )
|
||||
{
|
||||
// compile the path details into a usable path
|
||||
AssemblePrecomputedPath( bot, goalArea->GetCenter(), goalArea );
|
||||
return true;
|
||||
}
|
||||
|
||||
// all adjacent areas are likely too far away
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Given the last area in a path with valid parent pointers,
|
||||
* construct the actual path.
|
||||
*/
|
||||
void AssemblePrecomputedPath( INextBot *bot, const Vector &goal, CNavArea *endArea )
|
||||
{
|
||||
VPROF_BUDGET( "AssemblePrecomputedPath", "NextBot" );
|
||||
|
||||
const Vector &start = bot->GetPosition();
|
||||
|
||||
// get count
|
||||
int count = 0;
|
||||
CNavArea *area;
|
||||
for( area = endArea; area; area = area->GetParent() )
|
||||
{
|
||||
++count;
|
||||
}
|
||||
|
||||
// save room for endpoint
|
||||
if ( count > MAX_PATH_SEGMENTS-1 )
|
||||
{
|
||||
count = MAX_PATH_SEGMENTS-1;
|
||||
}
|
||||
else if ( count == 0 )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if ( count == 1 )
|
||||
{
|
||||
BuildTrivialPath( bot, goal );
|
||||
return;
|
||||
}
|
||||
|
||||
// assemble path
|
||||
m_segmentCount = count;
|
||||
for( area = endArea; count && area; area = area->GetParent() )
|
||||
{
|
||||
--count;
|
||||
m_path[ count ].area = area;
|
||||
m_path[ count ].how = area->GetParentHow();
|
||||
m_path[ count ].type = ON_GROUND;
|
||||
}
|
||||
|
||||
// append actual goal position
|
||||
m_path[ m_segmentCount ].area = endArea;
|
||||
m_path[ m_segmentCount ].pos = goal;
|
||||
m_path[ m_segmentCount ].ladder = NULL;
|
||||
m_path[ m_segmentCount ].how = NUM_TRAVERSE_TYPES;
|
||||
m_path[ m_segmentCount ].type = ON_GROUND;
|
||||
++m_segmentCount;
|
||||
|
||||
// compute path positions
|
||||
if ( ComputePathDetails( bot, start ) == false )
|
||||
{
|
||||
Invalidate();
|
||||
OnPathChanged( bot, NO_PATH );
|
||||
return;
|
||||
}
|
||||
|
||||
// remove redundant nodes and clean up path
|
||||
Optimize( bot );
|
||||
|
||||
PostProcess();
|
||||
|
||||
OnPathChanged( bot, COMPLETE_PATH );
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function for when start and goal are in the same area
|
||||
*/
|
||||
bool BuildTrivialPath( INextBot *bot, const Vector &goal );
|
||||
|
||||
/**
|
||||
* Determine exactly where the path goes between the given two areas
|
||||
* on the path. Return this point in 'crossPos'.
|
||||
*/
|
||||
virtual void ComputeAreaCrossing( INextBot *bot, const CNavArea *from, const Vector &fromPos, const CNavArea *to, NavDirType dir, Vector *crossPos ) const;
|
||||
|
||||
|
||||
private:
|
||||
enum { MAX_PATH_SEGMENTS = 256 };
|
||||
Segment m_path[ MAX_PATH_SEGMENTS ];
|
||||
int m_segmentCount;
|
||||
|
||||
bool ComputePathDetails( INextBot *bot, const Vector &start ); // determine actual path positions
|
||||
|
||||
void Optimize( INextBot *bot );
|
||||
void PostProcess( void );
|
||||
int FindNextOccludedNode( INextBot *bot, int anchor ); // used by Optimize()
|
||||
|
||||
void InsertSegment( Segment newSegment, int i ); // insert new segment at index i
|
||||
|
||||
mutable Vector m_pathPos; // used by GetPosition()
|
||||
mutable Vector m_closePos; // used by GetClosestPosition()
|
||||
|
||||
mutable float m_cursorPos; // current cursor position (distance along path)
|
||||
mutable Data m_cursorData; // used by GetCursorData()
|
||||
mutable bool m_isCursorDataDirty;
|
||||
|
||||
IntervalTimer m_ageTimer; // how old is this path?
|
||||
CHandle< CBaseCombatCharacter > m_subject; // the subject this path leads to
|
||||
|
||||
/**
|
||||
* Build a vector of adjacent areas reachable from the given area
|
||||
*/
|
||||
void CollectAdjacentAreas( CNavArea *area )
|
||||
{
|
||||
m_adjAreaIndex = 0;
|
||||
|
||||
const NavConnectVector &adjNorth = *area->GetAdjacentAreas( NORTH );
|
||||
FOR_EACH_VEC( adjNorth, it )
|
||||
{
|
||||
if ( m_adjAreaIndex >= MAX_ADJ_AREAS )
|
||||
break;
|
||||
|
||||
m_adjAreaVector[ m_adjAreaIndex ].area = adjNorth[ it ].area;
|
||||
m_adjAreaVector[ m_adjAreaIndex ].how = GO_NORTH;
|
||||
m_adjAreaVector[ m_adjAreaIndex ].ladder = NULL;
|
||||
++m_adjAreaIndex;
|
||||
}
|
||||
|
||||
const NavConnectVector &adjSouth = *area->GetAdjacentAreas( SOUTH );
|
||||
FOR_EACH_VEC( adjSouth, it )
|
||||
{
|
||||
if ( m_adjAreaIndex >= MAX_ADJ_AREAS )
|
||||
break;
|
||||
|
||||
m_adjAreaVector[ m_adjAreaIndex ].area = adjSouth[ it ].area;
|
||||
m_adjAreaVector[ m_adjAreaIndex ].how = GO_SOUTH;
|
||||
m_adjAreaVector[ m_adjAreaIndex ].ladder = NULL;
|
||||
++m_adjAreaIndex;
|
||||
}
|
||||
|
||||
const NavConnectVector &adjWest = *area->GetAdjacentAreas( WEST );
|
||||
FOR_EACH_VEC( adjWest, it )
|
||||
{
|
||||
if ( m_adjAreaIndex >= MAX_ADJ_AREAS )
|
||||
break;
|
||||
|
||||
m_adjAreaVector[ m_adjAreaIndex ].area = adjWest[ it ].area;
|
||||
m_adjAreaVector[ m_adjAreaIndex ].how = GO_WEST;
|
||||
m_adjAreaVector[ m_adjAreaIndex ].ladder = NULL;
|
||||
++m_adjAreaIndex;
|
||||
}
|
||||
|
||||
const NavConnectVector &adjEast = *area->GetAdjacentAreas( EAST );
|
||||
FOR_EACH_VEC( adjEast, it )
|
||||
{
|
||||
if ( m_adjAreaIndex >= MAX_ADJ_AREAS )
|
||||
break;
|
||||
|
||||
m_adjAreaVector[ m_adjAreaIndex ].area = adjEast[ it ].area;
|
||||
m_adjAreaVector[ m_adjAreaIndex ].how = GO_EAST;
|
||||
m_adjAreaVector[ m_adjAreaIndex ].ladder = NULL;
|
||||
++m_adjAreaIndex;
|
||||
}
|
||||
|
||||
const NavLadderConnectVector &adjUpLadder = *area->GetLadders( CNavLadder::LADDER_UP );
|
||||
FOR_EACH_VEC( adjUpLadder, it )
|
||||
{
|
||||
CNavLadder *ladder = adjUpLadder[ it ].ladder;
|
||||
|
||||
if ( ladder->m_topForwardArea && m_adjAreaIndex < MAX_ADJ_AREAS )
|
||||
{
|
||||
m_adjAreaVector[ m_adjAreaIndex ].area = ladder->m_topForwardArea;
|
||||
m_adjAreaVector[ m_adjAreaIndex ].how = GO_LADDER_UP;
|
||||
m_adjAreaVector[ m_adjAreaIndex ].ladder = ladder;
|
||||
++m_adjAreaIndex;
|
||||
}
|
||||
|
||||
if ( ladder->m_topLeftArea && m_adjAreaIndex < MAX_ADJ_AREAS )
|
||||
{
|
||||
m_adjAreaVector[ m_adjAreaIndex ].area = ladder->m_topLeftArea;
|
||||
m_adjAreaVector[ m_adjAreaIndex ].how = GO_LADDER_UP;
|
||||
m_adjAreaVector[ m_adjAreaIndex ].ladder = ladder;
|
||||
++m_adjAreaIndex;
|
||||
}
|
||||
|
||||
if ( ladder->m_topRightArea && m_adjAreaIndex < MAX_ADJ_AREAS )
|
||||
{
|
||||
m_adjAreaVector[ m_adjAreaIndex ].area = ladder->m_topRightArea;
|
||||
m_adjAreaVector[ m_adjAreaIndex ].how = GO_LADDER_UP;
|
||||
m_adjAreaVector[ m_adjAreaIndex ].ladder = ladder;
|
||||
++m_adjAreaIndex;
|
||||
}
|
||||
}
|
||||
|
||||
const NavLadderConnectVector &adjDownLadder = *area->GetLadders( CNavLadder::LADDER_DOWN );
|
||||
FOR_EACH_VEC( adjDownLadder, it )
|
||||
{
|
||||
CNavLadder *ladder = adjDownLadder[ it ].ladder;
|
||||
|
||||
if ( m_adjAreaIndex >= MAX_ADJ_AREAS )
|
||||
break;
|
||||
|
||||
if ( ladder->m_bottomArea )
|
||||
{
|
||||
m_adjAreaVector[ m_adjAreaIndex ].area = ladder->m_bottomArea;
|
||||
m_adjAreaVector[ m_adjAreaIndex ].how = GO_LADDER_DOWN;
|
||||
m_adjAreaVector[ m_adjAreaIndex ].ladder = ladder;
|
||||
++m_adjAreaIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum { MAX_ADJ_AREAS = 64 };
|
||||
|
||||
struct AdjInfo
|
||||
{
|
||||
CNavArea *area;
|
||||
CNavLadder *ladder;
|
||||
NavTraverseType how;
|
||||
};
|
||||
|
||||
AdjInfo m_adjAreaVector[ MAX_ADJ_AREAS ];
|
||||
int m_adjAreaIndex;
|
||||
|
||||
};
|
||||
|
||||
|
||||
inline float Path::GetLength( void ) const
|
||||
{
|
||||
if (m_segmentCount <= 0)
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
return m_path[ m_segmentCount-1 ].distanceFromStart;
|
||||
}
|
||||
|
||||
inline bool Path::IsValid( void ) const
|
||||
{
|
||||
return (m_segmentCount > 0);
|
||||
}
|
||||
|
||||
inline void Path::Invalidate( void )
|
||||
{
|
||||
m_segmentCount = 0;
|
||||
|
||||
m_cursorPos = 0.0f;
|
||||
|
||||
m_cursorData.pos = vec3_origin;
|
||||
m_cursorData.forward = Vector( 1.0f, 0, 0 );
|
||||
m_cursorData.curvature = 0.0f;
|
||||
m_cursorData.segmentPrior = NULL;
|
||||
|
||||
m_isCursorDataDirty = true;
|
||||
|
||||
m_subject = NULL;
|
||||
}
|
||||
|
||||
inline const Path::Segment *Path::FirstSegment( void ) const
|
||||
{
|
||||
return (IsValid()) ? &m_path[0] : NULL;
|
||||
}
|
||||
|
||||
inline const Path::Segment *Path::NextSegment( const Segment *currentSegment ) const
|
||||
{
|
||||
if (currentSegment == NULL || !IsValid())
|
||||
return NULL;
|
||||
|
||||
int i = currentSegment - m_path;
|
||||
|
||||
if (i < 0 || i >= m_segmentCount-1)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return &m_path[ i+1 ];
|
||||
}
|
||||
|
||||
inline const Path::Segment *Path::PriorSegment( const Segment *currentSegment ) const
|
||||
{
|
||||
if (currentSegment == NULL || !IsValid())
|
||||
return NULL;
|
||||
|
||||
int i = currentSegment - m_path;
|
||||
|
||||
if (i < 1 || i >= m_segmentCount)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return &m_path[ i-1 ];
|
||||
}
|
||||
|
||||
inline const Path::Segment *Path::LastSegment( void ) const
|
||||
{
|
||||
return ( IsValid() ) ? &m_path[ m_segmentCount-1 ] : NULL;
|
||||
}
|
||||
|
||||
inline const Vector &Path::GetStartPosition( void ) const
|
||||
{
|
||||
return ( IsValid() ) ? m_path[ 0 ].pos : vec3_origin;
|
||||
}
|
||||
|
||||
inline const Vector &Path::GetEndPosition( void ) const
|
||||
{
|
||||
return ( IsValid() ) ? m_path[ m_segmentCount-1 ].pos : vec3_origin;
|
||||
}
|
||||
|
||||
inline CBaseCombatCharacter *Path::GetSubject( void ) const
|
||||
{
|
||||
return m_subject;
|
||||
}
|
||||
|
||||
inline void Path::MoveCursorToStart( void )
|
||||
{
|
||||
m_cursorPos = 0.0f;
|
||||
m_isCursorDataDirty = true;
|
||||
}
|
||||
|
||||
inline void Path::MoveCursorToEnd( void )
|
||||
{
|
||||
m_cursorPos = GetLength();
|
||||
m_isCursorDataDirty = true;
|
||||
}
|
||||
|
||||
inline void Path::MoveCursor( float value, MoveCursorType type )
|
||||
{
|
||||
if ( type == PATH_ABSOLUTE_DISTANCE )
|
||||
{
|
||||
m_cursorPos = value;
|
||||
}
|
||||
else // relative distance
|
||||
{
|
||||
m_cursorPos += value;
|
||||
}
|
||||
|
||||
if ( m_cursorPos < 0.0f )
|
||||
{
|
||||
m_cursorPos = 0.0f;
|
||||
}
|
||||
else if ( m_cursorPos > GetLength() )
|
||||
{
|
||||
m_cursorPos = GetLength();
|
||||
}
|
||||
|
||||
m_isCursorDataDirty = true;
|
||||
}
|
||||
|
||||
inline float Path::GetCursorPosition( void ) const
|
||||
{
|
||||
return m_cursorPos;
|
||||
}
|
||||
|
||||
inline const Path::Segment *Path::GetCurrentGoal( void ) const
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
inline float Path::GetAge( void ) const
|
||||
{
|
||||
return m_ageTimer.GetElapsedTime();
|
||||
}
|
||||
|
||||
|
||||
#endif // _NEXT_BOT_PATH_H_
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,106 @@
|
||||
// NextBotPathFollow.h
|
||||
// Path following
|
||||
// Author: Michael Booth, April 2005
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
|
||||
#ifndef _NEXT_BOT_PATH_FOLLOWER_
|
||||
#define _NEXT_BOT_PATH_FOLLOWER_
|
||||
|
||||
#include "nav_mesh.h"
|
||||
#include "nav_pathfind.h"
|
||||
#include "NextBotPath.h"
|
||||
|
||||
class INextBot;
|
||||
class ILocomotion;
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* A PathFollower extends a Path to include mechanisms to move along (follow) it
|
||||
*/
|
||||
class PathFollower : public Path
|
||||
{
|
||||
public:
|
||||
PathFollower( void );
|
||||
virtual ~PathFollower();
|
||||
|
||||
virtual void Invalidate( void ); // (EXTEND) cause the path to become invalid
|
||||
virtual void Draw( const Path::Segment *start = NULL ) const; // (EXTEND) draw the path for debugging
|
||||
virtual void OnPathChanged( INextBot *bot, Path::ResultType result ); // invoked when the path is (re)computed (path is valid at the time of this call)
|
||||
|
||||
virtual void Update( INextBot *bot ); // move bot along path
|
||||
|
||||
virtual const Path::Segment *GetCurrentGoal( void ) const; // return current goal along the path we are trying to reach
|
||||
|
||||
virtual void SetMinLookAheadDistance( float value ); // minimum range movement goal must be along path
|
||||
|
||||
virtual CBaseEntity *GetHindrance( void ) const; // returns entity that is hindering our progress along the path
|
||||
|
||||
virtual bool IsDiscontinuityAhead( INextBot *bot, Path::SegmentType type, float range = -1.0f ) const; // return true if there is a the given discontinuity ahead in the path within the given range (-1 = entire remaining path)
|
||||
|
||||
void SetGoalTolerance( float range ); // set tolerance within at which we're considered to be at our goal
|
||||
|
||||
private:
|
||||
const Path::Segment *m_goal; // our current goal along the path
|
||||
float m_minLookAheadRange;
|
||||
|
||||
bool CheckProgress( INextBot *bot );
|
||||
bool IsAtGoal( INextBot *bot ) const; // return true if reached current path goal
|
||||
|
||||
//bool IsOnStairs( INextBot *bot ) const; // return true if bot is standing on a stairway
|
||||
bool m_isOnStairs;
|
||||
|
||||
CountdownTimer m_avoidTimer; // do avoid check more often if we recently avoided
|
||||
|
||||
CountdownTimer m_waitTimer; // for waiting for a blocker to move off our path
|
||||
CHandle< CBaseEntity > m_hindrance;
|
||||
|
||||
// debug display data for avoid volumes
|
||||
bool m_didAvoidCheck;
|
||||
Vector m_leftFrom;
|
||||
Vector m_leftTo;
|
||||
bool m_isLeftClear;
|
||||
Vector m_rightFrom;
|
||||
Vector m_rightTo;
|
||||
bool m_isRightClear;
|
||||
Vector m_hullMin, m_hullMax;
|
||||
|
||||
void AdjustSpeed( INextBot *bot ); // adjust speed based on path curvature
|
||||
|
||||
Vector Avoid( INextBot *bot, const Vector &goalPos, const Vector &forward, const Vector &left ); // avoidance movements for very nearby obstacles. returns modified goal position
|
||||
bool Climbing( INextBot *bot, const Path::Segment *goal, const Vector &forward, const Vector &left, float goalRange ); // climb up ledges
|
||||
bool JumpOverGaps( INextBot *bot, const Path::Segment *goal, const Vector &forward, const Vector &left, float goalRange ); // jump over gaps
|
||||
|
||||
bool LadderUpdate( INextBot *bot ); // move bot along ladder
|
||||
CBaseEntity *FindBlocker( INextBot *bot ); // if entity is returned, it is blocking us from continuing along our path
|
||||
|
||||
float m_goalTolerance;
|
||||
};
|
||||
|
||||
|
||||
inline void PathFollower::SetGoalTolerance( float range )
|
||||
{
|
||||
m_goalTolerance = range;
|
||||
}
|
||||
|
||||
|
||||
inline const Path::Segment *PathFollower::GetCurrentGoal( void ) const
|
||||
{
|
||||
return m_goal;
|
||||
}
|
||||
|
||||
|
||||
inline void PathFollower::SetMinLookAheadDistance( float value )
|
||||
{
|
||||
m_minLookAheadRange = value;
|
||||
}
|
||||
|
||||
inline CBaseEntity *PathFollower::GetHindrance( void ) const
|
||||
{
|
||||
return m_hindrance;
|
||||
}
|
||||
|
||||
|
||||
#endif // _NEXT_BOT_PATH_FOLLOWER_
|
||||
|
||||
|
||||
@@ -0,0 +1,573 @@
|
||||
// NextBotRetreatPath.h
|
||||
// Maintain and follow a path that leads safely away from the given Actor
|
||||
// Author: Michael Booth, February 2007
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
|
||||
#ifndef _NEXT_BOT_RETREAT_PATH_
|
||||
#define _NEXT_BOT_RETREAT_PATH_
|
||||
|
||||
#include "nav.h"
|
||||
#include "NextBotInterface.h"
|
||||
#include "NextBotLocomotionInterface.h"
|
||||
#include "NextBotRetreatPath.h"
|
||||
#include "NextBotUtil.h"
|
||||
#include "NextBotPathFollow.h"
|
||||
#include "tier0/vprof.h"
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* A RetreatPath extends a PathFollower to periodically recompute a path
|
||||
* away from a threat, and to move along the path away from that threat.
|
||||
*/
|
||||
class RetreatPath : public PathFollower
|
||||
{
|
||||
public:
|
||||
RetreatPath( void );
|
||||
virtual ~RetreatPath() { }
|
||||
|
||||
void Update( INextBot *bot, CBaseEntity *threat ); // update path away from threat and move bot along path
|
||||
|
||||
virtual float GetMaxPathLength( void ) const; // return maximum path length
|
||||
|
||||
virtual void Invalidate( void ); // (EXTEND) cause the path to become invalid
|
||||
|
||||
private:
|
||||
void RefreshPath( INextBot *bot, CBaseEntity *threat );
|
||||
|
||||
CountdownTimer m_throttleTimer; // require a minimum time between re-paths
|
||||
EHANDLE m_pathThreat; // the threat of our existing path
|
||||
Vector m_pathThreatPos; // where the threat was when the path was built
|
||||
};
|
||||
|
||||
inline RetreatPath::RetreatPath( void )
|
||||
{
|
||||
m_throttleTimer.Invalidate();
|
||||
m_pathThreat = NULL;
|
||||
}
|
||||
|
||||
inline float RetreatPath::GetMaxPathLength( void ) const
|
||||
{
|
||||
return 1000.0f;
|
||||
}
|
||||
|
||||
inline void RetreatPath::Invalidate( void )
|
||||
{
|
||||
// path is gone, repath at earliest opportunity
|
||||
m_throttleTimer.Invalidate();
|
||||
m_pathThreat = NULL;
|
||||
|
||||
// extend
|
||||
PathFollower::Invalidate();
|
||||
}
|
||||
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Maintain a path to our chase threat and move along that path
|
||||
*/
|
||||
inline void RetreatPath::Update( INextBot *bot, CBaseEntity *threat )
|
||||
{
|
||||
VPROF_BUDGET( "RetreatPath::Update", "NextBot" );
|
||||
|
||||
if ( threat == NULL )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// if our path threat changed, repath immediately
|
||||
if ( threat != m_pathThreat )
|
||||
{
|
||||
if ( bot->IsDebugging( INextBot::PATH ) )
|
||||
{
|
||||
DevMsg( "%3.2f: bot(#%d) Chase path threat changed (from %X to %X).\n", gpGlobals->curtime, bot->GetEntity()->entindex(), m_pathThreat.Get(), threat );
|
||||
}
|
||||
|
||||
Invalidate();
|
||||
}
|
||||
|
||||
// maintain the path away from the threat
|
||||
RefreshPath( bot, threat );
|
||||
|
||||
// move along the path towards the threat
|
||||
PathFollower::Update( bot );
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Build a path away from retreatFromArea up to retreatRange in length.
|
||||
*/
|
||||
class RetreatPathBuilder
|
||||
{
|
||||
public:
|
||||
RetreatPathBuilder( INextBot *me, CBaseEntity *threat, float retreatRange = 500.0f )
|
||||
{
|
||||
m_me = me;
|
||||
m_mover = me->GetLocomotionInterface();
|
||||
|
||||
m_threat = threat;
|
||||
m_retreatRange = retreatRange;
|
||||
}
|
||||
|
||||
CNavArea *ComputePath( void )
|
||||
{
|
||||
VPROF_BUDGET( "NavAreaBuildRetreatPath", "NextBot" );
|
||||
|
||||
if ( m_mover == NULL )
|
||||
return NULL;
|
||||
|
||||
CNavArea *startArea = m_me->GetEntity()->GetLastKnownArea();
|
||||
|
||||
if ( startArea == NULL )
|
||||
return NULL;
|
||||
|
||||
CNavArea *retreatFromArea = TheNavMesh->GetNearestNavArea( m_threat->GetAbsOrigin() );
|
||||
if ( retreatFromArea == NULL )
|
||||
return NULL;
|
||||
|
||||
startArea->SetParent( NULL );
|
||||
|
||||
// start search
|
||||
CNavArea::ClearSearchLists();
|
||||
|
||||
float initCost = Cost( startArea, NULL, NULL );
|
||||
if ( initCost < 0.0f )
|
||||
return NULL;
|
||||
|
||||
int teamID = m_me->GetEntity()->GetTeamNumber();
|
||||
|
||||
startArea->SetTotalCost( initCost );
|
||||
|
||||
startArea->AddToOpenList();
|
||||
|
||||
// keep track of the area farthest away from the threat
|
||||
CNavArea *farthestArea = NULL;
|
||||
float farthestRange = 0.0f;
|
||||
|
||||
//
|
||||
// Dijkstra's algorithm (since we don't know our goal).
|
||||
// Build a path as far away from the retreat area as possible.
|
||||
// Minimize total path length and danger.
|
||||
// Maximize distance to threat of end of path.
|
||||
//
|
||||
while( !CNavArea::IsOpenListEmpty() )
|
||||
{
|
||||
// get next area to check
|
||||
CNavArea *area = CNavArea::PopOpenList();
|
||||
|
||||
area->AddToClosedList();
|
||||
|
||||
// don't consider blocked areas
|
||||
if ( area->IsBlocked( teamID ) )
|
||||
continue;
|
||||
|
||||
// build adjacent area array
|
||||
CollectAdjacentAreas( area );
|
||||
|
||||
// search adjacent areas
|
||||
for( int i=0; i<m_adjAreaIndex; ++i )
|
||||
{
|
||||
CNavArea *newArea = m_adjAreaVector[ i ].area;
|
||||
|
||||
// only visit each area once
|
||||
if ( newArea->IsClosed() )
|
||||
continue;
|
||||
|
||||
// don't consider blocked areas
|
||||
if ( newArea->IsBlocked( teamID ) )
|
||||
continue;
|
||||
|
||||
// don't use this area if it is out of range
|
||||
if ( ( newArea->GetCenter() - m_me->GetEntity()->GetAbsOrigin() ).IsLengthGreaterThan( m_retreatRange ) )
|
||||
continue;
|
||||
|
||||
// determine cost of traversing this area
|
||||
float newCost = Cost( newArea, area, m_adjAreaVector[ i ].ladder );
|
||||
|
||||
// don't use adjacent area if cost functor says it is a dead-end
|
||||
if ( newCost < 0.0f )
|
||||
continue;
|
||||
|
||||
if ( newArea->IsOpen() && newArea->GetTotalCost() <= newCost )
|
||||
{
|
||||
// we have already visited this area, and it has a better path
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
// whether this area has been visited or not, we now have a better path
|
||||
newArea->SetParent( area, m_adjAreaVector[ i ].how );
|
||||
newArea->SetTotalCost( newCost );
|
||||
|
||||
// use 'cost so far' to hold cumulative cost
|
||||
newArea->SetCostSoFar( newCost );
|
||||
|
||||
// tricky bit here - relying on OpenList being sorted by cost
|
||||
if ( newArea->IsOpen() )
|
||||
{
|
||||
// area already on open list, update the list order to keep costs sorted
|
||||
newArea->UpdateOnOpenList();
|
||||
}
|
||||
else
|
||||
{
|
||||
newArea->AddToOpenList();
|
||||
}
|
||||
|
||||
// keep track of area farthest from threat
|
||||
float threatRange = ( newArea->GetCenter() - m_threat->GetAbsOrigin() ).Length();
|
||||
if ( threatRange > farthestRange )
|
||||
{
|
||||
farthestArea = newArea;
|
||||
farthestRange = threatRange;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return farthestArea;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Build a vector of adjacent areas reachable from the given area
|
||||
*/
|
||||
void CollectAdjacentAreas( CNavArea *area )
|
||||
{
|
||||
m_adjAreaIndex = 0;
|
||||
|
||||
const NavConnectVector &adjNorth = *area->GetAdjacentAreas( NORTH );
|
||||
FOR_EACH_VEC( adjNorth, it )
|
||||
{
|
||||
if ( m_adjAreaIndex >= MAX_ADJ_AREAS )
|
||||
break;
|
||||
|
||||
m_adjAreaVector[ m_adjAreaIndex ].area = adjNorth[ it ].area;
|
||||
m_adjAreaVector[ m_adjAreaIndex ].how = GO_NORTH;
|
||||
m_adjAreaVector[ m_adjAreaIndex ].ladder = NULL;
|
||||
++m_adjAreaIndex;
|
||||
}
|
||||
|
||||
const NavConnectVector &adjSouth = *area->GetAdjacentAreas( SOUTH );
|
||||
FOR_EACH_VEC( adjSouth, it )
|
||||
{
|
||||
if ( m_adjAreaIndex >= MAX_ADJ_AREAS )
|
||||
break;
|
||||
|
||||
m_adjAreaVector[ m_adjAreaIndex ].area = adjSouth[ it ].area;
|
||||
m_adjAreaVector[ m_adjAreaIndex ].how = GO_SOUTH;
|
||||
m_adjAreaVector[ m_adjAreaIndex ].ladder = NULL;
|
||||
++m_adjAreaIndex;
|
||||
}
|
||||
|
||||
const NavConnectVector &adjWest = *area->GetAdjacentAreas( WEST );
|
||||
FOR_EACH_VEC( adjWest, it )
|
||||
{
|
||||
if ( m_adjAreaIndex >= MAX_ADJ_AREAS )
|
||||
break;
|
||||
|
||||
m_adjAreaVector[ m_adjAreaIndex ].area = adjWest[ it ].area;
|
||||
m_adjAreaVector[ m_adjAreaIndex ].how = GO_WEST;
|
||||
m_adjAreaVector[ m_adjAreaIndex ].ladder = NULL;
|
||||
++m_adjAreaIndex;
|
||||
}
|
||||
|
||||
const NavConnectVector &adjEast = *area->GetAdjacentAreas( EAST );
|
||||
FOR_EACH_VEC( adjEast, it )
|
||||
{
|
||||
if ( m_adjAreaIndex >= MAX_ADJ_AREAS )
|
||||
break;
|
||||
|
||||
m_adjAreaVector[ m_adjAreaIndex ].area = adjEast[ it ].area;
|
||||
m_adjAreaVector[ m_adjAreaIndex ].how = GO_EAST;
|
||||
m_adjAreaVector[ m_adjAreaIndex ].ladder = NULL;
|
||||
++m_adjAreaIndex;
|
||||
}
|
||||
|
||||
const NavLadderConnectVector &adjUpLadder = *area->GetLadders( CNavLadder::LADDER_UP );
|
||||
FOR_EACH_VEC( adjUpLadder, it )
|
||||
{
|
||||
CNavLadder *ladder = adjUpLadder[ it ].ladder;
|
||||
|
||||
if ( ladder->m_topForwardArea && m_adjAreaIndex < MAX_ADJ_AREAS )
|
||||
{
|
||||
m_adjAreaVector[ m_adjAreaIndex ].area = ladder->m_topForwardArea;
|
||||
m_adjAreaVector[ m_adjAreaIndex ].how = GO_LADDER_UP;
|
||||
m_adjAreaVector[ m_adjAreaIndex ].ladder = ladder;
|
||||
++m_adjAreaIndex;
|
||||
}
|
||||
|
||||
if ( ladder->m_topLeftArea && m_adjAreaIndex < MAX_ADJ_AREAS )
|
||||
{
|
||||
m_adjAreaVector[ m_adjAreaIndex ].area = ladder->m_topLeftArea;
|
||||
m_adjAreaVector[ m_adjAreaIndex ].how = GO_LADDER_UP;
|
||||
m_adjAreaVector[ m_adjAreaIndex ].ladder = ladder;
|
||||
++m_adjAreaIndex;
|
||||
}
|
||||
|
||||
if ( ladder->m_topRightArea && m_adjAreaIndex < MAX_ADJ_AREAS )
|
||||
{
|
||||
m_adjAreaVector[ m_adjAreaIndex ].area = ladder->m_topRightArea;
|
||||
m_adjAreaVector[ m_adjAreaIndex ].how = GO_LADDER_UP;
|
||||
m_adjAreaVector[ m_adjAreaIndex ].ladder = ladder;
|
||||
++m_adjAreaIndex;
|
||||
}
|
||||
}
|
||||
|
||||
const NavLadderConnectVector &adjDownLadder = *area->GetLadders( CNavLadder::LADDER_DOWN );
|
||||
FOR_EACH_VEC( adjDownLadder, it )
|
||||
{
|
||||
CNavLadder *ladder = adjDownLadder[ it ].ladder;
|
||||
|
||||
if ( m_adjAreaIndex >= MAX_ADJ_AREAS )
|
||||
break;
|
||||
|
||||
if ( ladder->m_bottomArea )
|
||||
{
|
||||
m_adjAreaVector[ m_adjAreaIndex ].area = ladder->m_bottomArea;
|
||||
m_adjAreaVector[ m_adjAreaIndex ].how = GO_LADDER_DOWN;
|
||||
m_adjAreaVector[ m_adjAreaIndex ].ladder = ladder;
|
||||
++m_adjAreaIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cost minimizes path length traveled thus far and "danger" (proximity to threat(s))
|
||||
*/
|
||||
float Cost( CNavArea *area, CNavArea *fromArea, const CNavLadder *ladder )
|
||||
{
|
||||
// check if we can use this area
|
||||
if ( !m_mover->IsAreaTraversable( area ) )
|
||||
{
|
||||
return -1.0f;
|
||||
}
|
||||
|
||||
int teamID = m_me->GetEntity()->GetTeamNumber();
|
||||
if ( area->IsBlocked( teamID ) )
|
||||
{
|
||||
return -1.0f;
|
||||
}
|
||||
|
||||
const float debugDeltaT = 3.0f;
|
||||
|
||||
float cost;
|
||||
|
||||
const float maxThreatRange = 500.0f;
|
||||
const float dangerDensity = 1000.0f;
|
||||
|
||||
if ( fromArea == NULL )
|
||||
{
|
||||
cost = 0.0f;
|
||||
|
||||
if ( area->Contains( m_threat->GetAbsOrigin() ) )
|
||||
{
|
||||
// maximum danger - threat is in the area with us
|
||||
cost += 10.0f * dangerDensity;
|
||||
|
||||
if ( m_me->IsDebugging( INextBot::PATH ) )
|
||||
{
|
||||
area->DrawFilled( 255, 0, 0, 128 );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// danger proportional to range to us
|
||||
float rangeToThreat = ( m_threat->GetAbsOrigin() - m_me->GetEntity()->GetAbsOrigin() ).Length();
|
||||
|
||||
if ( rangeToThreat < maxThreatRange )
|
||||
{
|
||||
cost += dangerDensity * ( 1.0f - ( rangeToThreat / maxThreatRange ) );
|
||||
|
||||
if ( m_me->IsDebugging( INextBot::PATH ) )
|
||||
{
|
||||
NDebugOverlay::Line( m_me->GetEntity()->GetAbsOrigin(), m_threat->GetAbsOrigin(), 255, 0, 0, true, debugDeltaT );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// compute distance traveled along path so far
|
||||
float dist;
|
||||
|
||||
if ( ladder )
|
||||
{
|
||||
const float ladderCostFactor = 100.0f;
|
||||
dist = ladderCostFactor * ladder->m_length;
|
||||
}
|
||||
else
|
||||
{
|
||||
Vector to = area->GetCenter() - fromArea->GetCenter();
|
||||
|
||||
dist = to.Length();
|
||||
|
||||
// check for vertical discontinuities
|
||||
Vector closeFrom, closeTo;
|
||||
area->GetClosestPointOnArea( fromArea->GetCenter(), &closeTo );
|
||||
fromArea->GetClosestPointOnArea( area->GetCenter(), &closeFrom );
|
||||
|
||||
float deltaZ = closeTo.z - closeFrom.z;
|
||||
|
||||
if ( deltaZ > m_mover->GetMaxJumpHeight() )
|
||||
{
|
||||
// too high to jump
|
||||
return -1.0f;
|
||||
}
|
||||
else if ( -deltaZ > m_mover->GetDeathDropHeight() )
|
||||
{
|
||||
// too far down to drop
|
||||
return -1.0f;
|
||||
}
|
||||
|
||||
// prefer to maintain our level
|
||||
const float climbCost = 10.0f;
|
||||
dist += climbCost * fabs( deltaZ );
|
||||
}
|
||||
|
||||
cost = dist + fromArea->GetTotalCost();
|
||||
|
||||
|
||||
// Add in danger cost due to threat
|
||||
// Assume straight line between areas and find closest point
|
||||
// to the threat along that line segment. The distance between
|
||||
// the threat and closest point on the line is the danger cost.
|
||||
|
||||
// path danger is CUMULATIVE
|
||||
float dangerCost = fromArea->GetCostSoFar();
|
||||
|
||||
Vector close;
|
||||
float t;
|
||||
CalcClosestPointOnLineSegment( m_threat->GetAbsOrigin(), area->GetCenter(), fromArea->GetCenter(), close, &t );
|
||||
if ( t < 0.0f )
|
||||
{
|
||||
close = area->GetCenter();
|
||||
}
|
||||
else if ( t > 1.0f )
|
||||
{
|
||||
close = fromArea->GetCenter();
|
||||
}
|
||||
|
||||
float rangeToThreat = ( m_threat->GetAbsOrigin() - close ).Length();
|
||||
|
||||
if ( rangeToThreat < maxThreatRange )
|
||||
{
|
||||
float dangerFactor = 1.0f - ( rangeToThreat / maxThreatRange );
|
||||
dangerCost = dangerDensity * dangerFactor;
|
||||
|
||||
if ( m_me->IsDebugging( INextBot::PATH ) )
|
||||
{
|
||||
NDebugOverlay::HorzArrow( fromArea->GetCenter(), area->GetCenter(), 5, 255 * dangerFactor, 0, 0, 255, true, debugDeltaT );
|
||||
|
||||
Vector to = close - m_threat->GetAbsOrigin();
|
||||
to.NormalizeInPlace();
|
||||
|
||||
NDebugOverlay::Line( close, close - 50.0f * to, 255, 0, 0, true, debugDeltaT );
|
||||
}
|
||||
}
|
||||
|
||||
cost += dangerCost;
|
||||
}
|
||||
|
||||
return cost;
|
||||
}
|
||||
|
||||
private:
|
||||
INextBot *m_me;
|
||||
ILocomotion *m_mover;
|
||||
|
||||
CBaseEntity *m_threat;
|
||||
float m_retreatRange;
|
||||
|
||||
enum { MAX_ADJ_AREAS = 64 };
|
||||
|
||||
struct AdjInfo
|
||||
{
|
||||
CNavArea *area;
|
||||
CNavLadder *ladder;
|
||||
NavTraverseType how;
|
||||
};
|
||||
|
||||
AdjInfo m_adjAreaVector[ MAX_ADJ_AREAS ];
|
||||
int m_adjAreaIndex;
|
||||
|
||||
};
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Periodically rebuild the path away from our threat
|
||||
*/
|
||||
inline void RetreatPath::RefreshPath( INextBot *bot, CBaseEntity *threat )
|
||||
{
|
||||
VPROF_BUDGET( "RetreatPath::RefreshPath", "NextBot" );
|
||||
|
||||
if ( threat == NULL )
|
||||
{
|
||||
if ( bot->IsDebugging( INextBot::PATH ) )
|
||||
{
|
||||
DevMsg( "%3.2f: bot(#%d) CasePath::RefreshPath failed. No threat.\n", gpGlobals->curtime, bot->GetEntity()->entindex() );
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// don't change our path if we're on a ladder
|
||||
ILocomotion *mover = bot->GetLocomotionInterface();
|
||||
if ( IsValid() && mover && mover->IsUsingLadder() )
|
||||
{
|
||||
if ( bot->IsDebugging( INextBot::PATH ) )
|
||||
{
|
||||
DevMsg( "%3.2f: bot(#%d) RetreatPath::RefreshPath failed. Bot is on a ladder.\n", gpGlobals->curtime, bot->GetEntity()->entindex() );
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// the closer we get, the more accurate our path needs to be
|
||||
Vector to = threat->GetAbsOrigin() - bot->GetPosition();
|
||||
|
||||
const float minTolerance = 0.0f;
|
||||
const float toleranceRate = 0.33f;
|
||||
|
||||
float tolerance = minTolerance + toleranceRate * to.Length();
|
||||
|
||||
if ( !IsValid() || ( threat->GetAbsOrigin() - m_pathThreatPos ).IsLengthGreaterThan( tolerance ) )
|
||||
{
|
||||
if ( !m_throttleTimer.IsElapsed() )
|
||||
{
|
||||
// require a minimum time between repaths, as long as we have a path to follow
|
||||
if ( bot->IsDebugging( INextBot::PATH ) )
|
||||
{
|
||||
DevMsg( "%3.2f: bot(#%d) RetreatPath::RefreshPath failed. Rate throttled.\n", gpGlobals->curtime, bot->GetEntity()->entindex() );
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// remember our path threat
|
||||
m_pathThreat = threat;
|
||||
m_pathThreatPos = threat->GetAbsOrigin();
|
||||
|
||||
RetreatPathBuilder retreat( bot, threat, GetMaxPathLength() );
|
||||
|
||||
CNavArea *goalArea = retreat.ComputePath();
|
||||
|
||||
if ( goalArea )
|
||||
{
|
||||
AssemblePrecomputedPath( bot, goalArea->GetCenter(), goalArea );
|
||||
}
|
||||
else
|
||||
{
|
||||
// all adjacent areas are too far away - just move directly away from threat
|
||||
Vector to = threat->GetAbsOrigin() - bot->GetPosition();
|
||||
|
||||
BuildTrivialPath( bot, bot->GetPosition() - to );
|
||||
}
|
||||
|
||||
const float minRepathInterval = 0.5f;
|
||||
m_throttleTimer.Start( minRepathInterval );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
#endif // _NEXT_BOT_RETREAT_PATH_
|
||||
@@ -0,0 +1,22 @@
|
||||
// NextBotPlayer.cpp
|
||||
// A CBasePlayer bot based on the NextBot technology
|
||||
// Author: Michael Booth, November 2005
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
|
||||
#include "cbase.h"
|
||||
|
||||
#include "nav_mesh.h"
|
||||
|
||||
#include "NextBot.h"
|
||||
#include "NextBotPlayer.h"
|
||||
|
||||
#include "in_buttons.h"
|
||||
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
ConVar NextBotPlayerStop( "nb_player_stop", "0", FCVAR_CHEAT, "Stop all NextBotPlayers from updating" );
|
||||
ConVar NextBotPlayerWalk( "nb_player_walk", "0", FCVAR_CHEAT, "Force bots to walk" );
|
||||
ConVar NextBotPlayerCrouch( "nb_player_crouch", "0", FCVAR_CHEAT, "Force bots to crouch" );
|
||||
ConVar NextBotPlayerMove( "nb_player_move", "1", FCVAR_CHEAT, "Prevents bots from moving" );
|
||||
|
||||
@@ -0,0 +1,910 @@
|
||||
// NextBotPlayer.h
|
||||
// A CBasePlayer bot based on the NextBot technology
|
||||
// Author: Michael Booth, November 2005
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
|
||||
#ifndef _NEXT_BOT_PLAYER_H_
|
||||
#define _NEXT_BOT_PLAYER_H_
|
||||
|
||||
#include "cbase.h"
|
||||
#include "gameinterface.h"
|
||||
|
||||
#include "NextBot.h"
|
||||
#include "Path/NextBotPathFollow.h"
|
||||
//#include "NextBotPlayerBody.h"
|
||||
#include "NextBotBehavior.h"
|
||||
|
||||
#include "in_buttons.h"
|
||||
|
||||
extern ConVar NextBotPlayerStop;
|
||||
extern ConVar NextBotPlayerWalk;
|
||||
extern ConVar NextBotPlayerCrouch;
|
||||
extern ConVar NextBotPlayerMove;
|
||||
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Instantiate a NextBot derived from CBasePlayer and spawn it into the environment.
|
||||
* Assumes class T is derived from CBasePlayer, and has the following method that
|
||||
* creates a new entity of type T and returns it:
|
||||
*
|
||||
* static CBasePlayer *T::AllocatePlayerEntity( edict_t *pEdict, const char *playerName )
|
||||
*
|
||||
*/
|
||||
template < typename T >
|
||||
T * NextBotCreatePlayerBot( const char *name, bool bReportFakeClient = true )
|
||||
{
|
||||
/*
|
||||
if ( UTIL_ClientsInGame() >= gpGlobals->maxClients )
|
||||
{
|
||||
Msg( "CreatePlayerBot: Failed - server is full (%d/%d clients).\n", UTIL_ClientsInGame(), gpGlobals->maxClients );
|
||||
return NULL;
|
||||
}
|
||||
*/
|
||||
|
||||
// This is a "back door" for allocating a custom player bot entity when
|
||||
// the engine calls ClientPutInServer (from CreateFakeClient)
|
||||
ClientPutInServerOverride( T::AllocatePlayerEntity );
|
||||
|
||||
// create the bot and spawn it into the environment
|
||||
edict_t *botEdict = engine->CreateFakeClientEx( name, bReportFakeClient );
|
||||
|
||||
// close the "back door"
|
||||
ClientPutInServerOverride( NULL );
|
||||
|
||||
if ( botEdict == NULL )
|
||||
{
|
||||
Msg( "CreatePlayerBot: Unable to create bot %s - CreateFakeClient() returned NULL.\n", name );
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// create an instance of the bot's class and bind it to the edict
|
||||
T *bot = dynamic_cast< T * >( CBaseEntity::Instance( botEdict ) );
|
||||
|
||||
if ( bot == NULL )
|
||||
{
|
||||
Assert( false );
|
||||
Error( "CreatePlayerBot: Could not Instance() from the bot edict.\n" );
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bot->SetPlayerName( name );
|
||||
|
||||
// flag this as a fakeclient (bot)
|
||||
bot->ClearFlags();
|
||||
bot->AddFlag( FL_CLIENT | FL_FAKECLIENT );
|
||||
|
||||
return bot;
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Interface to access player input buttons.
|
||||
* Unless a duration is given, each button is released at the start of the next frame.
|
||||
* The release methods allow releasing a button before its duration has elapsed.
|
||||
*/
|
||||
class INextBotPlayerInput
|
||||
{
|
||||
public:
|
||||
virtual void PressFireButton( float duration = -1.0f ) = 0;
|
||||
virtual void ReleaseFireButton( void ) = 0;
|
||||
|
||||
virtual void PressAltFireButton( float duration = -1.0f ) = 0;
|
||||
virtual void ReleaseAltFireButton( void ) = 0;
|
||||
|
||||
virtual void PressMeleeButton( float duration = -1.0f ) = 0;
|
||||
virtual void ReleaseMeleeButton( void ) = 0;
|
||||
|
||||
virtual void PressSpecialFireButton( float duration = -1.0f ) = 0;
|
||||
virtual void ReleaseSpecialFireButton( void ) = 0;
|
||||
|
||||
virtual void PressUseButton( float duration = -1.0f ) = 0;
|
||||
virtual void ReleaseUseButton( void ) = 0;
|
||||
|
||||
virtual void PressReloadButton( float duration = -1.0f ) = 0;
|
||||
virtual void ReleaseReloadButton( void ) = 0;
|
||||
|
||||
virtual void PressForwardButton( float duration = -1.0f ) = 0;
|
||||
virtual void ReleaseForwardButton( void ) = 0;
|
||||
|
||||
virtual void PressBackwardButton( float duration = -1.0f ) = 0;
|
||||
virtual void ReleaseBackwardButton( void ) = 0;
|
||||
|
||||
virtual void PressLeftButton( float duration = -1.0f ) = 0;
|
||||
virtual void ReleaseLeftButton( void ) = 0;
|
||||
|
||||
virtual void PressRightButton( float duration = -1.0f ) = 0;
|
||||
virtual void ReleaseRightButton( void ) = 0;
|
||||
|
||||
virtual void PressJumpButton( float duration = -1.0f ) = 0;
|
||||
virtual void ReleaseJumpButton( void ) = 0;
|
||||
|
||||
virtual void PressCrouchButton( float duration = -1.0f ) = 0;
|
||||
virtual void ReleaseCrouchButton( void ) = 0;
|
||||
|
||||
virtual void PressWalkButton( float duration = -1.0f ) = 0;
|
||||
virtual void ReleaseWalkButton( void ) = 0;
|
||||
|
||||
virtual void SetButtonScale( float forward, float right ) = 0;
|
||||
};
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Drive a CBasePlayer-derived entity via NextBot logic
|
||||
*/
|
||||
template < typename PlayerType >
|
||||
class NextBotPlayer : public PlayerType, public INextBot, public INextBotPlayerInput
|
||||
{
|
||||
public:
|
||||
DECLARE_CLASS( NextBotPlayer, PlayerType );
|
||||
|
||||
NextBotPlayer( void );
|
||||
virtual ~NextBotPlayer();
|
||||
|
||||
virtual void Spawn( void );
|
||||
|
||||
virtual void SetSpawnPoint( CBaseEntity *spawnPoint ); // define place in environment where bot will (re)spawn
|
||||
virtual CBaseEntity *EntSelectSpawnPoint( void );
|
||||
|
||||
virtual void PhysicsSimulate( void );
|
||||
|
||||
virtual bool IsNetClient( void ) const { return false; } // Bots should return FALSE for this, they can't receive NET messages
|
||||
virtual bool IsFakeClient( void ) const { return true; }
|
||||
virtual bool IsBot( void ) const { return true; }
|
||||
virtual INextBot *MyNextBotPointer( void ) { return this; }
|
||||
|
||||
// this is valid because the templatized PlayerType must be derived from CBasePlayer, which is derived from CBaseCombatCharacter
|
||||
virtual CBaseCombatCharacter *GetEntity( void ) const { return ( PlayerType * )this; }
|
||||
|
||||
virtual bool IsRemovedOnReset( void ) const { return false; } // remove this bot when the NextBot manager calls Reset
|
||||
|
||||
virtual bool IsDormantWhenDead( void ) const { return true; } // should this player-bot continue to update itself when dead (respawn logic, etc)
|
||||
|
||||
// allocate a bot and bind it to the edict
|
||||
static CBasePlayer *AllocatePlayerEntity( edict_t *edict, const char *playerName );
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
// utility methods
|
||||
float GetDistanceBetween( CBaseEntity *other ) const; // return distance between us and the given entity
|
||||
bool IsDistanceBetweenLessThan( CBaseEntity *other, float range ) const; // return true if distance between is less than the given value
|
||||
bool IsDistanceBetweenGreaterThan( CBaseEntity *other, float range ) const; // return true if distance between is greater than the given value
|
||||
|
||||
float GetDistanceBetween( const Vector &target ) const; // return distance between us and the given entity
|
||||
bool IsDistanceBetweenLessThan( const Vector &target, float range ) const; // return true if distance between is less than the given value
|
||||
bool IsDistanceBetweenGreaterThan( const Vector &target, float range ) const; // return true if distance between is greater than the given value
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
// INextBotPlayerInput
|
||||
virtual void PressFireButton( float duration = -1.0f );
|
||||
virtual void ReleaseFireButton( void );
|
||||
|
||||
virtual void PressAltFireButton( float duration = -1.0f );
|
||||
virtual void ReleaseAltFireButton( void );
|
||||
|
||||
virtual void PressMeleeButton( float duration = -1.0f );
|
||||
virtual void ReleaseMeleeButton( void );
|
||||
|
||||
virtual void PressSpecialFireButton( float duration = -1.0f );
|
||||
virtual void ReleaseSpecialFireButton( void );
|
||||
|
||||
virtual void PressUseButton( float duration = -1.0f );
|
||||
virtual void ReleaseUseButton( void );
|
||||
|
||||
virtual void PressReloadButton( float duration = -1.0f );
|
||||
virtual void ReleaseReloadButton( void );
|
||||
|
||||
virtual void PressForwardButton( float duration = -1.0f );
|
||||
virtual void ReleaseForwardButton( void );
|
||||
|
||||
virtual void PressBackwardButton( float duration = -1.0f );
|
||||
virtual void ReleaseBackwardButton( void );
|
||||
|
||||
virtual void PressLeftButton( float duration = -1.0f );
|
||||
virtual void ReleaseLeftButton( void );
|
||||
|
||||
virtual void PressRightButton( float duration = -1.0f );
|
||||
virtual void ReleaseRightButton( void );
|
||||
|
||||
virtual void PressJumpButton( float duration = -1.0f );
|
||||
virtual void ReleaseJumpButton( void );
|
||||
|
||||
virtual void PressCrouchButton( float duration = -1.0f );
|
||||
virtual void ReleaseCrouchButton( void );
|
||||
|
||||
virtual void PressWalkButton( float duration = -1.0f );
|
||||
virtual void ReleaseWalkButton( void );
|
||||
|
||||
virtual void SetButtonScale( float forward, float right );
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
// Event hooks into NextBot system
|
||||
virtual int OnTakeDamage_Alive( const CTakeDamageInfo &info );
|
||||
virtual int OnTakeDamage_Dying( const CTakeDamageInfo &info );
|
||||
virtual void Event_Killed( const CTakeDamageInfo &info );
|
||||
virtual void HandleAnimEvent( animevent_t *event );
|
||||
virtual void OnNavAreaChanged( CNavArea *enteredArea, CNavArea *leftArea ); // invoked (by UpdateLastKnownArea) when we enter a new nav area (or it is reset to NULL)
|
||||
virtual void Touch( CBaseEntity *other );
|
||||
virtual void Weapon_Equip( CBaseCombatWeapon *weapon ); // for OnPickUp
|
||||
virtual void Weapon_Drop( CBaseCombatWeapon *weapon, const Vector *target, const Vector *velocity ); // for OnDrop
|
||||
virtual void OnMainActivityComplete( Activity newActivity, Activity oldActivity );
|
||||
virtual void OnMainActivityInterrupted( Activity newActivity, Activity oldActivity );
|
||||
//------------------------------------------------------------------------
|
||||
|
||||
bool IsAbleToAutoCenterOnLadders( void ) const;
|
||||
|
||||
virtual void AvoidPlayers( CUserCmd *pCmd ) { } // some game types allow players to pass through each other, this method pushes them apart
|
||||
|
||||
public:
|
||||
// begin INextBot ------------------------------------------------------------------------------------------------------------------
|
||||
virtual void Update( void ); // (EXTEND) update internal state
|
||||
|
||||
protected:
|
||||
int m_inputButtons; // this is still needed to guarantee each button press is captured at least once
|
||||
int m_prevInputButtons;
|
||||
CountdownTimer m_fireButtonTimer;
|
||||
CountdownTimer m_meleeButtonTimer;
|
||||
CountdownTimer m_specialFireButtonTimer;
|
||||
CountdownTimer m_useButtonTimer;
|
||||
CountdownTimer m_reloadButtonTimer;
|
||||
CountdownTimer m_forwardButtonTimer;
|
||||
CountdownTimer m_backwardButtonTimer;
|
||||
CountdownTimer m_leftButtonTimer;
|
||||
CountdownTimer m_rightButtonTimer;
|
||||
CountdownTimer m_jumpButtonTimer;
|
||||
CountdownTimer m_crouchButtonTimer;
|
||||
CountdownTimer m_walkButtonTimer;
|
||||
CountdownTimer m_buttonScaleTimer;
|
||||
IntervalTimer m_burningTimer; // how long since we were last burning
|
||||
float m_forwardScale;
|
||||
float m_rightScale;
|
||||
CHandle< CBaseEntity > m_spawnPointEntity;
|
||||
};
|
||||
|
||||
|
||||
template < typename PlayerType >
|
||||
inline void NextBotPlayer< PlayerType >::SetSpawnPoint( CBaseEntity *spawnPoint )
|
||||
{
|
||||
m_spawnPointEntity = spawnPoint;
|
||||
}
|
||||
|
||||
template < typename PlayerType >
|
||||
inline CBaseEntity *NextBotPlayer< PlayerType >::EntSelectSpawnPoint( void )
|
||||
{
|
||||
if ( m_spawnPointEntity != NULL )
|
||||
return m_spawnPointEntity;
|
||||
|
||||
return BaseClass::EntSelectSpawnPoint();
|
||||
}
|
||||
|
||||
template < typename PlayerType >
|
||||
inline float NextBotPlayer< PlayerType >::GetDistanceBetween( CBaseEntity *other ) const
|
||||
{
|
||||
return (this->GetAbsOrigin() - other->GetAbsOrigin()).Length();
|
||||
}
|
||||
|
||||
template < typename PlayerType >
|
||||
inline bool NextBotPlayer< PlayerType >::IsDistanceBetweenLessThan( CBaseEntity *other, float range ) const
|
||||
{
|
||||
return (this->GetAbsOrigin() - other->GetAbsOrigin()).IsLengthLessThan( range );
|
||||
}
|
||||
|
||||
template < typename PlayerType >
|
||||
inline bool NextBotPlayer< PlayerType >::IsDistanceBetweenGreaterThan( CBaseEntity *other, float range ) const
|
||||
{
|
||||
return (this->GetAbsOrigin() - other->GetAbsOrigin()).IsLengthGreaterThan( range );
|
||||
}
|
||||
|
||||
template < typename PlayerType >
|
||||
inline float NextBotPlayer< PlayerType >::GetDistanceBetween( const Vector &target ) const
|
||||
{
|
||||
return (this->GetAbsOrigin() - target).Length();
|
||||
}
|
||||
|
||||
template < typename PlayerType >
|
||||
inline bool NextBotPlayer< PlayerType >::IsDistanceBetweenLessThan( const Vector &target, float range ) const
|
||||
{
|
||||
return (this->GetAbsOrigin() - target).IsLengthLessThan( range );
|
||||
}
|
||||
|
||||
template < typename PlayerType >
|
||||
inline bool NextBotPlayer< PlayerType >::IsDistanceBetweenGreaterThan( const Vector &target, float range ) const
|
||||
{
|
||||
return (this->GetAbsOrigin() - target).IsLengthGreaterThan( range );
|
||||
}
|
||||
|
||||
template < typename PlayerType >
|
||||
inline void NextBotPlayer< PlayerType >::PressFireButton( float duration )
|
||||
{
|
||||
m_inputButtons |= IN_ATTACK;
|
||||
m_fireButtonTimer.Start( duration );
|
||||
}
|
||||
|
||||
template < typename PlayerType >
|
||||
inline void NextBotPlayer< PlayerType >::ReleaseFireButton( void )
|
||||
{
|
||||
m_inputButtons &= ~IN_ATTACK;
|
||||
m_fireButtonTimer.Invalidate();
|
||||
}
|
||||
|
||||
template < typename PlayerType >
|
||||
inline void NextBotPlayer< PlayerType >::PressAltFireButton( float duration )
|
||||
{
|
||||
PressMeleeButton( duration );
|
||||
}
|
||||
|
||||
template < typename PlayerType >
|
||||
inline void NextBotPlayer< PlayerType >::ReleaseAltFireButton( void )
|
||||
{
|
||||
ReleaseMeleeButton();
|
||||
}
|
||||
|
||||
template < typename PlayerType >
|
||||
inline void NextBotPlayer< PlayerType >::PressMeleeButton( float duration )
|
||||
{
|
||||
m_inputButtons |= IN_ATTACK2;
|
||||
m_meleeButtonTimer.Start( duration );
|
||||
}
|
||||
|
||||
template < typename PlayerType >
|
||||
inline void NextBotPlayer< PlayerType >::ReleaseMeleeButton( void )
|
||||
{
|
||||
m_inputButtons &= ~IN_ATTACK2;
|
||||
m_meleeButtonTimer.Invalidate();
|
||||
}
|
||||
|
||||
template < typename PlayerType >
|
||||
inline void NextBotPlayer< PlayerType >::PressSpecialFireButton( float duration )
|
||||
{
|
||||
m_inputButtons |= IN_ATTACK3;
|
||||
m_specialFireButtonTimer.Start( duration );
|
||||
}
|
||||
|
||||
template < typename PlayerType >
|
||||
inline void NextBotPlayer< PlayerType >::ReleaseSpecialFireButton( void )
|
||||
{
|
||||
m_inputButtons &= ~IN_ATTACK3;
|
||||
m_specialFireButtonTimer.Invalidate();
|
||||
}
|
||||
|
||||
template < typename PlayerType >
|
||||
inline void NextBotPlayer< PlayerType >::PressUseButton( float duration )
|
||||
{
|
||||
m_inputButtons |= IN_USE;
|
||||
m_useButtonTimer.Start( duration );
|
||||
}
|
||||
|
||||
template < typename PlayerType >
|
||||
inline void NextBotPlayer< PlayerType >::ReleaseUseButton( void )
|
||||
{
|
||||
m_inputButtons &= ~IN_USE;
|
||||
m_useButtonTimer.Invalidate();
|
||||
}
|
||||
|
||||
template < typename PlayerType >
|
||||
inline void NextBotPlayer< PlayerType >::PressReloadButton( float duration )
|
||||
{
|
||||
m_inputButtons |= IN_RELOAD;
|
||||
m_reloadButtonTimer.Start( duration );
|
||||
}
|
||||
|
||||
template < typename PlayerType >
|
||||
inline void NextBotPlayer< PlayerType >::ReleaseReloadButton( void )
|
||||
{
|
||||
m_inputButtons &= ~IN_RELOAD;
|
||||
m_reloadButtonTimer.Invalidate();
|
||||
}
|
||||
|
||||
template < typename PlayerType >
|
||||
inline void NextBotPlayer< PlayerType >::PressJumpButton( float duration )
|
||||
{
|
||||
m_inputButtons |= IN_JUMP;
|
||||
m_jumpButtonTimer.Start( duration );
|
||||
}
|
||||
|
||||
template < typename PlayerType >
|
||||
inline void NextBotPlayer< PlayerType >::ReleaseJumpButton( void )
|
||||
{
|
||||
m_inputButtons &= ~IN_JUMP;
|
||||
m_jumpButtonTimer.Invalidate();
|
||||
}
|
||||
|
||||
template < typename PlayerType >
|
||||
inline void NextBotPlayer< PlayerType >::PressCrouchButton( float duration )
|
||||
{
|
||||
m_inputButtons |= IN_DUCK;
|
||||
m_crouchButtonTimer.Start( duration );
|
||||
}
|
||||
|
||||
template < typename PlayerType >
|
||||
inline void NextBotPlayer< PlayerType >::ReleaseCrouchButton( void )
|
||||
{
|
||||
m_inputButtons &= ~IN_DUCK;
|
||||
m_crouchButtonTimer.Invalidate();
|
||||
}
|
||||
|
||||
template < typename PlayerType >
|
||||
inline void NextBotPlayer< PlayerType >::PressWalkButton( float duration )
|
||||
{
|
||||
m_inputButtons |= IN_SPEED;
|
||||
m_walkButtonTimer.Start( duration );
|
||||
}
|
||||
|
||||
template < typename PlayerType >
|
||||
inline void NextBotPlayer< PlayerType >::ReleaseWalkButton( void )
|
||||
{
|
||||
m_inputButtons &= ~IN_SPEED;
|
||||
m_walkButtonTimer.Invalidate();
|
||||
}
|
||||
|
||||
template < typename PlayerType >
|
||||
inline void NextBotPlayer< PlayerType >::PressForwardButton( float duration )
|
||||
{
|
||||
m_inputButtons |= IN_FORWARD;
|
||||
m_forwardButtonTimer.Start( duration );
|
||||
}
|
||||
|
||||
template < typename PlayerType >
|
||||
inline void NextBotPlayer< PlayerType >::ReleaseForwardButton( void )
|
||||
{
|
||||
m_inputButtons &= ~IN_FORWARD;
|
||||
m_forwardButtonTimer.Invalidate();
|
||||
}
|
||||
|
||||
template < typename PlayerType >
|
||||
inline void NextBotPlayer< PlayerType >::PressBackwardButton( float duration )
|
||||
{
|
||||
m_inputButtons |= IN_BACK;
|
||||
m_backwardButtonTimer.Start( duration );
|
||||
}
|
||||
|
||||
template < typename PlayerType >
|
||||
inline void NextBotPlayer< PlayerType >::ReleaseBackwardButton( void )
|
||||
{
|
||||
m_inputButtons &= ~IN_BACK;
|
||||
m_backwardButtonTimer.Invalidate();
|
||||
}
|
||||
|
||||
template < typename PlayerType >
|
||||
inline void NextBotPlayer< PlayerType >::PressLeftButton( float duration )
|
||||
{
|
||||
m_inputButtons |= IN_MOVELEFT;
|
||||
m_leftButtonTimer.Start( duration );
|
||||
}
|
||||
|
||||
template < typename PlayerType >
|
||||
inline void NextBotPlayer< PlayerType >::ReleaseLeftButton( void )
|
||||
{
|
||||
m_inputButtons &= ~IN_MOVELEFT;
|
||||
m_leftButtonTimer.Invalidate();
|
||||
}
|
||||
|
||||
template < typename PlayerType >
|
||||
inline void NextBotPlayer< PlayerType >::PressRightButton( float duration )
|
||||
{
|
||||
m_inputButtons |= IN_MOVERIGHT;
|
||||
m_rightButtonTimer.Start( duration );
|
||||
}
|
||||
|
||||
template < typename PlayerType >
|
||||
inline void NextBotPlayer< PlayerType >::ReleaseRightButton( void )
|
||||
{
|
||||
m_inputButtons &= ~IN_MOVERIGHT;
|
||||
m_rightButtonTimer.Invalidate();
|
||||
}
|
||||
|
||||
template < typename PlayerType >
|
||||
inline void NextBotPlayer< PlayerType >::SetButtonScale( float forward, float right )
|
||||
{
|
||||
m_forwardScale = forward;
|
||||
m_rightScale = right;
|
||||
m_buttonScaleTimer.Start( 0.01 );
|
||||
}
|
||||
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------------------------------
|
||||
template < typename PlayerType >
|
||||
inline NextBotPlayer< PlayerType >::NextBotPlayer( void )
|
||||
{
|
||||
m_prevInputButtons = 0;
|
||||
m_inputButtons = 0;
|
||||
m_burningTimer.Invalidate();
|
||||
m_spawnPointEntity = NULL;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------------------------------
|
||||
template < typename PlayerType >
|
||||
inline NextBotPlayer< PlayerType >::~NextBotPlayer()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------------------------------
|
||||
template < typename PlayerType >
|
||||
inline void NextBotPlayer< PlayerType >::Spawn( void )
|
||||
{
|
||||
engine->SetFakeClientConVarValue( this->edict(), "cl_autohelp", "0" );
|
||||
|
||||
m_prevInputButtons = m_inputButtons = 0;
|
||||
m_fireButtonTimer.Invalidate();
|
||||
m_meleeButtonTimer.Invalidate();
|
||||
m_specialFireButtonTimer.Invalidate();
|
||||
m_useButtonTimer.Invalidate();
|
||||
m_reloadButtonTimer.Invalidate();
|
||||
m_forwardButtonTimer.Invalidate();
|
||||
m_backwardButtonTimer.Invalidate();
|
||||
m_leftButtonTimer.Invalidate();
|
||||
m_rightButtonTimer.Invalidate();
|
||||
m_jumpButtonTimer.Invalidate();
|
||||
m_crouchButtonTimer.Invalidate();
|
||||
m_walkButtonTimer.Invalidate();
|
||||
m_buttonScaleTimer.Invalidate();
|
||||
m_forwardScale = m_rightScale = 0.04;
|
||||
m_burningTimer.Invalidate();
|
||||
|
||||
// reset first, because Spawn() may access various interfaces
|
||||
INextBot::Reset();
|
||||
|
||||
BaseClass::Spawn();
|
||||
}
|
||||
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------------------------------
|
||||
inline void _NextBot_BuildUserCommand( CUserCmd *cmd, const QAngle &viewangles, float forwardmove, float sidemove, float upmove, int buttons, byte impulse )
|
||||
{
|
||||
Q_memset( cmd, 0, sizeof( CUserCmd ) );
|
||||
|
||||
cmd->command_number = gpGlobals->tickcount;
|
||||
cmd->forwardmove = forwardmove;
|
||||
cmd->sidemove = sidemove;
|
||||
cmd->upmove = upmove;
|
||||
cmd->buttons = buttons;
|
||||
cmd->impulse = impulse;
|
||||
|
||||
VectorCopy( viewangles, cmd->viewangles );
|
||||
|
||||
cmd->random_seed = random->RandomInt( 0, 0x7fffffff );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------------------------------
|
||||
template < typename PlayerType >
|
||||
inline void NextBotPlayer< PlayerType >::PhysicsSimulate( void )
|
||||
{
|
||||
VPROF( "NextBotPlayer::PhysicsSimulate" );
|
||||
|
||||
// Make sure not to simulate this guy twice per frame
|
||||
if ( PlayerType::m_nSimulationTick == gpGlobals->tickcount )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if ( engine->IsPaused() )
|
||||
{
|
||||
// We're paused - don't add new commands
|
||||
PlayerType::PhysicsSimulate();
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ( IsDormantWhenDead() && PlayerType::m_lifeState == LIFE_DEAD ) || NextBotStop.GetBool() )
|
||||
{
|
||||
// death animation complete - nothing left to do except let PhysicsSimulate run PreThink etc
|
||||
PlayerType::PhysicsSimulate();
|
||||
return;
|
||||
}
|
||||
|
||||
int inputButtons;
|
||||
//
|
||||
// Update bot behavior
|
||||
//
|
||||
if ( BeginUpdate() )
|
||||
{
|
||||
Update();
|
||||
|
||||
// build button bits
|
||||
if ( !m_fireButtonTimer.IsElapsed() )
|
||||
m_inputButtons |= IN_ATTACK;
|
||||
|
||||
if ( !m_meleeButtonTimer.IsElapsed() )
|
||||
m_inputButtons |= IN_ATTACK2;
|
||||
|
||||
if ( !m_specialFireButtonTimer.IsElapsed() )
|
||||
m_inputButtons |= IN_ATTACK3;
|
||||
|
||||
if ( !m_useButtonTimer.IsElapsed() )
|
||||
m_inputButtons |= IN_USE;
|
||||
|
||||
if ( !m_reloadButtonTimer.IsElapsed() )
|
||||
m_inputButtons |= IN_RELOAD;
|
||||
|
||||
if ( !m_forwardButtonTimer.IsElapsed() )
|
||||
m_inputButtons |= IN_FORWARD;
|
||||
|
||||
if ( !m_backwardButtonTimer.IsElapsed() )
|
||||
m_inputButtons |= IN_BACK;
|
||||
|
||||
if ( !m_leftButtonTimer.IsElapsed() )
|
||||
m_inputButtons |= IN_MOVELEFT;
|
||||
|
||||
if ( !m_rightButtonTimer.IsElapsed() )
|
||||
m_inputButtons |= IN_MOVERIGHT;
|
||||
|
||||
if ( !m_jumpButtonTimer.IsElapsed() )
|
||||
m_inputButtons |= IN_JUMP;
|
||||
|
||||
if ( !m_crouchButtonTimer.IsElapsed() )
|
||||
m_inputButtons |= IN_DUCK;
|
||||
|
||||
if ( !m_walkButtonTimer.IsElapsed() )
|
||||
m_inputButtons |= IN_SPEED;
|
||||
|
||||
m_prevInputButtons = m_inputButtons;
|
||||
inputButtons = m_inputButtons;
|
||||
|
||||
EndUpdate();
|
||||
}
|
||||
else
|
||||
{
|
||||
// HACK: Smooth out body animations
|
||||
GetBodyInterface()->Update();
|
||||
|
||||
// keep buttons pressed between Update() calls (m_prevInputButtons),
|
||||
// and include any button presses that occurred this tick (m_inputButtons).
|
||||
inputButtons = m_prevInputButtons | m_inputButtons;
|
||||
}
|
||||
|
||||
//
|
||||
// Convert NextBot locomotion and posture into
|
||||
// player commands
|
||||
//
|
||||
IBody *body = GetBodyInterface();
|
||||
ILocomotion *mover = GetLocomotionInterface();
|
||||
|
||||
if ( body->IsActualPosture( IBody::CROUCH ) )
|
||||
{
|
||||
inputButtons |= IN_DUCK;
|
||||
}
|
||||
|
||||
float forwardSpeed = 0.0f;
|
||||
float strafeSpeed = 0.0f;
|
||||
float verticalSpeed = ( m_inputButtons & IN_JUMP ) ? mover->GetRunSpeed() : 0.0f;
|
||||
|
||||
if ( inputButtons & IN_FORWARD )
|
||||
{
|
||||
forwardSpeed = mover->GetRunSpeed();
|
||||
}
|
||||
else if ( inputButtons & IN_BACK )
|
||||
{
|
||||
forwardSpeed = -mover->GetRunSpeed();
|
||||
}
|
||||
|
||||
if ( inputButtons & IN_MOVELEFT )
|
||||
{
|
||||
strafeSpeed = -mover->GetRunSpeed();
|
||||
}
|
||||
else if ( inputButtons & IN_MOVERIGHT )
|
||||
{
|
||||
strafeSpeed = mover->GetRunSpeed();
|
||||
}
|
||||
|
||||
if ( NextBotPlayerWalk.GetBool() )
|
||||
{
|
||||
inputButtons |= IN_SPEED;
|
||||
}
|
||||
|
||||
if ( NextBotPlayerCrouch.GetBool() )
|
||||
{
|
||||
inputButtons |= IN_DUCK;
|
||||
}
|
||||
|
||||
if ( !m_buttonScaleTimer.IsElapsed() )
|
||||
{
|
||||
forwardSpeed = mover->GetRunSpeed() * m_forwardScale;
|
||||
strafeSpeed = mover->GetRunSpeed() * m_rightScale;
|
||||
}
|
||||
|
||||
if ( !NextBotPlayerMove.GetBool() )
|
||||
{
|
||||
inputButtons &= ~(IN_FORWARD | IN_BACK | IN_MOVELEFT | IN_MOVERIGHT | IN_JUMP );
|
||||
forwardSpeed = 0.0f;
|
||||
strafeSpeed = 0.0f;
|
||||
verticalSpeed = 0.0f;
|
||||
}
|
||||
|
||||
QAngle angles = this->EyeAngles();
|
||||
|
||||
#ifdef TERROR
|
||||
if ( IsStunned() )
|
||||
{
|
||||
inputButtons &= ~(IN_FORWARD | IN_BACK | IN_MOVELEFT | IN_MOVERIGHT | IN_JUMP | IN_DUCK );
|
||||
}
|
||||
|
||||
// "Look" in the direction we're climbing/stumbling etc. We can't do anything anyway, and it
|
||||
// keeps motion extraction working.
|
||||
if ( IsRenderYawOverridden() && IsMotionControlledXY( GetMainActivity() ) )
|
||||
{
|
||||
angles[YAW] = GetOverriddenRenderYaw();
|
||||
}
|
||||
#endif
|
||||
|
||||
// construct a "command" to move the player
|
||||
CUserCmd userCmd;
|
||||
_NextBot_BuildUserCommand( &userCmd, angles, forwardSpeed, strafeSpeed, verticalSpeed, inputButtons, 0 );
|
||||
|
||||
AvoidPlayers( &userCmd );
|
||||
|
||||
// allocate a new command and add it to the player's list of command to process
|
||||
this->ProcessUsercmds( &userCmd, 1, 1, 0, false );
|
||||
|
||||
m_inputButtons = 0;
|
||||
|
||||
// actually execute player commands and do player physics
|
||||
PlayerType::PhysicsSimulate();
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------
|
||||
template < typename PlayerType >
|
||||
inline void NextBotPlayer< PlayerType >::OnNavAreaChanged( CNavArea *enteredArea, CNavArea *leftArea )
|
||||
{
|
||||
// propagate into NextBot responders
|
||||
INextBotEventResponder::OnNavAreaChanged( enteredArea, leftArea );
|
||||
|
||||
BaseClass::OnNavAreaChanged( enteredArea, leftArea );
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------
|
||||
template < typename PlayerType >
|
||||
inline void NextBotPlayer< PlayerType >::Touch( CBaseEntity *other )
|
||||
{
|
||||
if ( ShouldTouch( other ) )
|
||||
{
|
||||
// propagate touch into NextBot event responders
|
||||
trace_t result;
|
||||
result = this->GetTouchTrace();
|
||||
OnContact( other, &result );
|
||||
}
|
||||
|
||||
BaseClass::Touch( other );
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------
|
||||
template < typename PlayerType >
|
||||
inline void NextBotPlayer< PlayerType >::Weapon_Equip( CBaseCombatWeapon *weapon )
|
||||
{
|
||||
#ifdef TERROR
|
||||
// TODO: Reimplement GetDroppingPlayer() into GetLastOwner()
|
||||
OnPickUp( weapon, weapon->GetDroppingPlayer() );
|
||||
#else
|
||||
OnPickUp( weapon, NULL );
|
||||
#endif
|
||||
|
||||
BaseClass::Weapon_Equip( weapon );
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------
|
||||
template < typename PlayerType >
|
||||
inline void NextBotPlayer< PlayerType >::Weapon_Drop( CBaseCombatWeapon *weapon, const Vector *target, const Vector *velocity )
|
||||
{
|
||||
OnDrop( weapon );
|
||||
|
||||
BaseClass::Weapon_Drop( weapon, target, velocity );
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------
|
||||
template < typename PlayerType >
|
||||
inline void NextBotPlayer< PlayerType >::OnMainActivityComplete( Activity newActivity, Activity oldActivity )
|
||||
{
|
||||
#ifdef TERROR
|
||||
BaseClass::OnMainActivityComplete( newActivity, oldActivity );
|
||||
#endif
|
||||
OnAnimationActivityComplete( oldActivity );
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------
|
||||
template < typename PlayerType >
|
||||
inline void NextBotPlayer< PlayerType >::OnMainActivityInterrupted( Activity newActivity, Activity oldActivity )
|
||||
{
|
||||
#ifdef TERROR
|
||||
BaseClass::OnMainActivityInterrupted( newActivity, oldActivity );
|
||||
#endif
|
||||
OnAnimationActivityInterrupted( oldActivity );
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------
|
||||
template < typename PlayerType >
|
||||
inline void NextBotPlayer< PlayerType >::Update( void )
|
||||
{
|
||||
// don't spend CPU updating if this Survivor is dead
|
||||
if ( ( this->IsAlive() || !IsDormantWhenDead() ) && !NextBotPlayerStop.GetBool() )
|
||||
{
|
||||
INextBot::Update();
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------
|
||||
template < typename PlayerType >
|
||||
inline bool NextBotPlayer< PlayerType >::IsAbleToAutoCenterOnLadders( void ) const
|
||||
{
|
||||
const ILocomotion *locomotion = GetLocomotionInterface();
|
||||
return locomotion && locomotion->IsAbleToAutoCenterOnLadder();
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------
|
||||
template < typename PlayerType >
|
||||
inline int NextBotPlayer< PlayerType >::OnTakeDamage_Alive( const CTakeDamageInfo &info )
|
||||
{
|
||||
if ( info.GetDamageType() & DMG_BURN )
|
||||
{
|
||||
if ( !m_burningTimer.HasStarted() || m_burningTimer.IsGreaterThen( 1.0f ) )
|
||||
{
|
||||
// emit ignite event periodically as long as we are burning
|
||||
OnIgnite();
|
||||
m_burningTimer.Start();
|
||||
}
|
||||
}
|
||||
|
||||
// propagate event to components
|
||||
OnInjured( info );
|
||||
|
||||
return BaseClass::OnTakeDamage_Alive( info );
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------
|
||||
template < typename PlayerType >
|
||||
inline int NextBotPlayer< PlayerType >::OnTakeDamage_Dying( const CTakeDamageInfo &info )
|
||||
{
|
||||
if ( info.GetDamageType() & DMG_BURN )
|
||||
{
|
||||
if ( !m_burningTimer.HasStarted() || m_burningTimer.IsGreaterThen( 1.0f ) )
|
||||
{
|
||||
// emit ignite event periodically as long as we are burning
|
||||
OnIgnite();
|
||||
m_burningTimer.Start();
|
||||
}
|
||||
}
|
||||
|
||||
// propagate event to components
|
||||
OnInjured( info );
|
||||
|
||||
return BaseClass::OnTakeDamage_Dying( info );
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------
|
||||
template < typename PlayerType >
|
||||
inline void NextBotPlayer< PlayerType >::Event_Killed( const CTakeDamageInfo &info )
|
||||
{
|
||||
// propagate event to my components
|
||||
OnKilled( info );
|
||||
|
||||
BaseClass::Event_Killed( info );
|
||||
}
|
||||
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------
|
||||
template < typename PlayerType >
|
||||
inline void NextBotPlayer< PlayerType >::HandleAnimEvent( animevent_t *event )
|
||||
{
|
||||
// propagate event to components
|
||||
OnAnimationEvent( event );
|
||||
|
||||
BaseClass::HandleAnimEvent( event );
|
||||
}
|
||||
|
||||
|
||||
#endif // _NEXT_BOT_PLAYER_H_
|
||||
@@ -0,0 +1,881 @@
|
||||
// NextBotPlayerBody.cpp
|
||||
// Implementation of Body interface for CBasePlayer-derived classes
|
||||
// Author: Michael Booth, October 2006
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
|
||||
#include "cbase.h"
|
||||
|
||||
#include "NextBot.h"
|
||||
#include "NextBotPlayerBody.h"
|
||||
#include "NextBotPlayer.h"
|
||||
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
|
||||
ConVar nb_saccade_time( "nb_saccade_time", "0.1", FCVAR_CHEAT );
|
||||
ConVar nb_saccade_speed( "nb_saccade_speed", "1000", FCVAR_CHEAT );
|
||||
ConVar nb_head_aim_steady_max_rate( "nb_head_aim_steady_max_rate", "100", FCVAR_CHEAT );
|
||||
ConVar nb_head_aim_settle_duration( "nb_head_aim_settle_duration", "0.3", FCVAR_CHEAT );
|
||||
ConVar nb_head_aim_resettle_angle( "nb_head_aim_resettle_angle", "100", FCVAR_CHEAT, "After rotating through this angle, the bot pauses to 'recenter' its virtual mouse on its virtual mousepad" );
|
||||
ConVar nb_head_aim_resettle_time( "nb_head_aim_resettle_time", "0.3", FCVAR_CHEAT, "How long the bot pauses to 'recenter' its virtual mouse on its virtual mousepad" );
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* A useful reply for IBody::AimHeadTowards. When the
|
||||
* head is aiming on target, press the fire button.
|
||||
*/
|
||||
void PressFireButtonReply::OnSuccess( INextBot *bot )
|
||||
{
|
||||
INextBotPlayerInput *playerInput = dynamic_cast< INextBotPlayerInput * >( bot->GetEntity() );
|
||||
if ( playerInput )
|
||||
{
|
||||
playerInput->PressFireButton();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* A useful reply for IBody::AimHeadTowards. When the
|
||||
* head is aiming on target, press the alternate fire button.
|
||||
*/
|
||||
void PressAltFireButtonReply::OnSuccess( INextBot *bot )
|
||||
{
|
||||
INextBotPlayerInput *playerInput = dynamic_cast< INextBotPlayerInput * >( bot->GetEntity() );
|
||||
if ( playerInput )
|
||||
{
|
||||
playerInput->PressMeleeButton();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* A useful reply for IBody::AimHeadTowards. When the
|
||||
* head is aiming on target, press the jump button.
|
||||
*/
|
||||
void PressJumpButtonReply::OnSuccess( INextBot *bot )
|
||||
{
|
||||
INextBotPlayerInput *playerInput = dynamic_cast< INextBotPlayerInput * >( bot->GetEntity() );
|
||||
if ( playerInput )
|
||||
{
|
||||
playerInput->PressJumpButton();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------------------------
|
||||
//-----------------------------------------------------------------------------------------------
|
||||
PlayerBody::PlayerBody( INextBot *bot ) : IBody( bot )
|
||||
{
|
||||
m_player = static_cast< CBasePlayer * >( bot->GetEntity() );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------------------------
|
||||
PlayerBody::~PlayerBody()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* reset to initial state
|
||||
*/
|
||||
void PlayerBody::Reset( void )
|
||||
{
|
||||
m_posture = STAND;
|
||||
|
||||
m_lookAtPos = vec3_origin;
|
||||
m_lookAtSubject = NULL;
|
||||
m_lookAtReplyWhenAimed = NULL;
|
||||
m_lookAtVelocity = vec3_origin;
|
||||
m_lookAtExpireTimer.Invalidate();
|
||||
|
||||
m_lookAtPriority = BORING;
|
||||
m_lookAtExpireTimer.Invalidate();
|
||||
m_lookAtDurationTimer.Invalidate();
|
||||
m_isSightedIn = false;
|
||||
m_hasBeenSightedIn = false;
|
||||
m_headSteadyTimer.Invalidate();
|
||||
m_priorAngles = vec3_angle;
|
||||
m_anchorRepositionTimer.Invalidate();
|
||||
m_anchorForward = vec3_origin;
|
||||
}
|
||||
|
||||
ConVar bot_mimic( "bot_mimic", "0", 0, "Bot uses usercmd of player by index." );
|
||||
|
||||
//-----------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Update internal state.
|
||||
* Do this every tick to keep head aims smooth and accurate
|
||||
*/
|
||||
void PlayerBody::Upkeep( void )
|
||||
{
|
||||
// If mimicking the player, don't modify the view angles.
|
||||
static ConVarRef bot_mimic( "bot_mimic" );
|
||||
if ( bot_mimic.IsValid() && bot_mimic.GetBool() )
|
||||
return;
|
||||
|
||||
const float deltaT = gpGlobals->frametime;
|
||||
if ( deltaT < 0.00001f )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CBasePlayer *player = ( CBasePlayer * )GetBot()->GetEntity();
|
||||
|
||||
// get current view angles
|
||||
QAngle currentAngles = player->EyeAngles() + player->GetPunchAngle();
|
||||
|
||||
// track when our head is "steady"
|
||||
bool isSteady = true;
|
||||
|
||||
float actualPitchRate = AngleDiff( currentAngles.x, m_priorAngles.x );
|
||||
if ( abs( actualPitchRate ) > nb_head_aim_steady_max_rate.GetFloat() * deltaT )
|
||||
{
|
||||
isSteady = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
float actualYawRate = AngleDiff( currentAngles.y, m_priorAngles.y );
|
||||
|
||||
if ( abs( actualYawRate ) > nb_head_aim_steady_max_rate.GetFloat() * deltaT )
|
||||
{
|
||||
isSteady = false;
|
||||
}
|
||||
}
|
||||
|
||||
if ( isSteady )
|
||||
{
|
||||
if ( !m_headSteadyTimer.HasStarted() )
|
||||
{
|
||||
m_headSteadyTimer.Start();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_headSteadyTimer.Invalidate();
|
||||
}
|
||||
|
||||
if ( GetBot()->IsDebugging( NEXTBOT_LOOK_AT ) )
|
||||
{
|
||||
if ( IsHeadSteady() )
|
||||
{
|
||||
const float maxTime = 3.0f;
|
||||
float t = GetHeadSteadyDuration() / maxTime;
|
||||
t = clamp( t, 0.f, 1.0f );
|
||||
NDebugOverlay::Circle( player->EyePosition(), t * 10.0f, 0, 255, 0, 255, true, 2.0f * deltaT );
|
||||
}
|
||||
}
|
||||
|
||||
m_priorAngles = currentAngles;
|
||||
|
||||
|
||||
// if our current look-at has expired, don't change our aim further
|
||||
if ( m_hasBeenSightedIn && m_lookAtExpireTimer.IsElapsed() )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// simulate limited range of mouse movements
|
||||
// compute the angle change from "center"
|
||||
const Vector &forward = GetViewVector();
|
||||
float deltaAngle = RAD2DEG( acos( DotProduct( forward, m_anchorForward ) ) );
|
||||
if ( deltaAngle > nb_head_aim_resettle_angle.GetFloat() )
|
||||
{
|
||||
// time to recenter our 'virtual mouse'
|
||||
m_anchorRepositionTimer.Start( RandomFloat( 0.9f, 1.1f ) * nb_head_aim_resettle_time.GetFloat() );
|
||||
m_anchorForward = forward;
|
||||
return;
|
||||
}
|
||||
|
||||
// if we're currently recentering our "virtual mouse", wait
|
||||
if ( m_anchorRepositionTimer.HasStarted() && !m_anchorRepositionTimer.IsElapsed() )
|
||||
{
|
||||
return;
|
||||
}
|
||||
m_anchorRepositionTimer.Invalidate();
|
||||
|
||||
|
||||
// if we have a subject, update lookat point
|
||||
CBaseEntity *subject = m_lookAtSubject;
|
||||
if ( subject )
|
||||
{
|
||||
if ( m_lookAtTrackingTimer.IsElapsed() )
|
||||
{
|
||||
// update subject tracking by periodically estimating linear aim velocity, allowing for "slop" between updates
|
||||
Vector desiredLookAtPos;
|
||||
|
||||
if ( subject->MyCombatCharacterPointer() )
|
||||
{
|
||||
desiredLookAtPos = GetBot()->GetIntentionInterface()->SelectTargetPoint( GetBot(), subject->MyCombatCharacterPointer() );
|
||||
}
|
||||
else
|
||||
{
|
||||
desiredLookAtPos = subject->WorldSpaceCenter();
|
||||
}
|
||||
|
||||
desiredLookAtPos += GetHeadAimSubjectLeadTime() * subject->GetAbsVelocity();
|
||||
|
||||
Vector errorVector = desiredLookAtPos - m_lookAtPos;
|
||||
float error = errorVector.NormalizeInPlace();
|
||||
|
||||
float trackingInterval = GetHeadAimTrackingInterval();
|
||||
if ( trackingInterval < deltaT )
|
||||
{
|
||||
trackingInterval = deltaT;
|
||||
}
|
||||
|
||||
float errorVel = error / trackingInterval;
|
||||
|
||||
m_lookAtVelocity = ( errorVel * errorVector ) + subject->GetAbsVelocity();
|
||||
|
||||
m_lookAtTrackingTimer.Start( RandomFloat( 0.8f, 1.2f ) * trackingInterval );
|
||||
}
|
||||
|
||||
m_lookAtPos += deltaT * m_lookAtVelocity;
|
||||
}
|
||||
|
||||
|
||||
// aim view towards last look at point
|
||||
Vector to = m_lookAtPos - GetEyePosition();
|
||||
to.NormalizeInPlace();
|
||||
|
||||
QAngle desiredAngles;
|
||||
VectorAngles( to, desiredAngles );
|
||||
|
||||
QAngle angles;
|
||||
|
||||
if ( GetBot()->IsDebugging( NEXTBOT_LOOK_AT ) )
|
||||
{
|
||||
NDebugOverlay::Line( GetEyePosition(), GetEyePosition() + 100.0f * forward, 255, 255, 0, false, 2.0f * deltaT );
|
||||
|
||||
float thickness = isSteady ? 2.0f : 3.0f;
|
||||
int r = m_isSightedIn ? 255 : 0;
|
||||
int g = subject ? 255 : 0;
|
||||
NDebugOverlay::HorzArrow( GetEyePosition(), m_lookAtPos, thickness, r, g, 255, 255, false, 2.0f * deltaT );
|
||||
}
|
||||
|
||||
|
||||
const float onTargetTolerance = 0.98f;
|
||||
float dot = DotProduct( forward, to );
|
||||
if ( dot > onTargetTolerance )
|
||||
{
|
||||
// on target
|
||||
m_isSightedIn = true;
|
||||
|
||||
if ( !m_hasBeenSightedIn )
|
||||
{
|
||||
m_hasBeenSightedIn = true;
|
||||
|
||||
if ( GetBot()->IsDebugging( NEXTBOT_LOOK_AT ) )
|
||||
{
|
||||
ConColorMsg( Color( 255, 100, 0, 255 ), "%3.2f: %s Look At SIGHTED IN\n",
|
||||
gpGlobals->curtime,
|
||||
m_player->GetPlayerName() );
|
||||
}
|
||||
}
|
||||
|
||||
if ( m_lookAtReplyWhenAimed )
|
||||
{
|
||||
m_lookAtReplyWhenAimed->OnSuccess( GetBot() );
|
||||
m_lookAtReplyWhenAimed = NULL;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// off target
|
||||
m_isSightedIn = false;
|
||||
}
|
||||
|
||||
|
||||
// rotate view at a rate proportional to how far we have to turn
|
||||
// max rate if we need to turn around
|
||||
// want first derivative continuity of rate as our aim hits to avoid pop
|
||||
float approachRate = GetMaxHeadAngularVelocity();
|
||||
|
||||
const float easeOut = 0.7f;
|
||||
if ( dot > easeOut )
|
||||
{
|
||||
float t = RemapVal( dot, easeOut, 1.0f, 1.0f, 0.02f );
|
||||
const float halfPI = 1.57f;
|
||||
approachRate *= sin( halfPI * t );
|
||||
}
|
||||
|
||||
const float easeInTime = 0.25f;
|
||||
if ( m_lookAtDurationTimer.GetElapsedTime() < easeInTime )
|
||||
{
|
||||
approachRate *= m_lookAtDurationTimer.GetElapsedTime() / easeInTime;
|
||||
}
|
||||
|
||||
angles.y = ApproachAngle( desiredAngles.y, currentAngles.y, approachRate * deltaT );
|
||||
angles.x = ApproachAngle( desiredAngles.x, currentAngles.x, 0.5f * approachRate * deltaT );
|
||||
angles.z = 0.0f;
|
||||
|
||||
// back out "punch angle"
|
||||
angles -= player->GetPunchAngle();
|
||||
|
||||
angles.x = AngleNormalize( angles.x );
|
||||
angles.y = AngleNormalize( angles.y );
|
||||
|
||||
player->SnapEyeAngles( angles );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------------------------
|
||||
bool PlayerBody::SetPosition( const Vector &pos )
|
||||
{
|
||||
m_player->SetAbsOrigin( pos );
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Return the eye position of the bot in world coordinates
|
||||
*/
|
||||
const Vector &PlayerBody::GetEyePosition( void ) const
|
||||
{
|
||||
m_eyePos = m_player->EyePosition();
|
||||
return m_eyePos;
|
||||
}
|
||||
|
||||
|
||||
CBaseEntity *PlayerBody::GetEntity( void )
|
||||
{
|
||||
return m_player;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Return the view unit direction vector in world coordinates
|
||||
*/
|
||||
const Vector &PlayerBody::GetViewVector( void ) const
|
||||
{
|
||||
m_player->EyeVectors( &m_viewVector );
|
||||
return m_viewVector;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Aim the bot's head towards the given goal
|
||||
*/
|
||||
void PlayerBody::AimHeadTowards( const Vector &lookAtPos, LookAtPriorityType priority, float duration, INextBotReply *replyWhenAimed, const char *reason )
|
||||
{
|
||||
if ( duration <= 0.0f )
|
||||
{
|
||||
duration = 0.1f;
|
||||
}
|
||||
|
||||
// don't spaz our aim around
|
||||
if ( m_lookAtPriority == priority )
|
||||
{
|
||||
if ( !IsHeadSteady() || GetHeadSteadyDuration() < nb_head_aim_settle_duration.GetFloat() )
|
||||
{
|
||||
// we're still finishing a look-at at the same priority
|
||||
if ( replyWhenAimed )
|
||||
{
|
||||
replyWhenAimed->OnFail( GetBot(), INextBotReply::DENIED );
|
||||
}
|
||||
|
||||
if ( GetBot()->IsDebugging( NEXTBOT_LOOK_AT ) )
|
||||
{
|
||||
ConColorMsg( Color( 255, 0, 0, 255 ), "%3.2f: %s Look At '%s' rejected - previous aim not %s\n",
|
||||
gpGlobals->curtime,
|
||||
m_player->GetPlayerName(),
|
||||
reason,
|
||||
IsHeadSteady() ? "settled long enough" : "head-steady" );
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// don't short-circuit if "sighted in" to avoid rapid view jitter
|
||||
if ( m_lookAtPriority > priority && !m_lookAtExpireTimer.IsElapsed() )
|
||||
{
|
||||
// higher priority lookat still ongoing
|
||||
if ( replyWhenAimed )
|
||||
{
|
||||
replyWhenAimed->OnFail( GetBot(), INextBotReply::DENIED );
|
||||
}
|
||||
|
||||
if ( GetBot()->IsDebugging( NEXTBOT_LOOK_AT ) )
|
||||
{
|
||||
ConColorMsg( Color( 255, 0, 0, 255 ), "%3.2f: %s Look At '%s' rejected - higher priority aim in progress\n",
|
||||
gpGlobals->curtime,
|
||||
m_player->GetPlayerName(),
|
||||
reason );
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if ( m_lookAtReplyWhenAimed )
|
||||
{
|
||||
// in-process aim was interrupted
|
||||
m_lookAtReplyWhenAimed->OnFail( GetBot(), INextBotReply::INTERRUPTED );
|
||||
}
|
||||
|
||||
m_lookAtReplyWhenAimed = replyWhenAimed;
|
||||
m_lookAtExpireTimer.Start( duration );
|
||||
|
||||
// if given the same point, just update priority
|
||||
const float epsilon = 1.0f;
|
||||
if ( ( m_lookAtPos - lookAtPos ).IsLengthLessThan( epsilon ) )
|
||||
{
|
||||
m_lookAtPriority = priority;
|
||||
return;
|
||||
}
|
||||
|
||||
// new look-at point
|
||||
|
||||
m_lookAtPos = lookAtPos;
|
||||
m_lookAtSubject = NULL;
|
||||
|
||||
m_lookAtPriority = priority;
|
||||
m_lookAtDurationTimer.Start();
|
||||
|
||||
// do NOT clear this here, or continuous calls to AimHeadTowards will keep IsHeadAimingOnTarget returning false all of the time
|
||||
// m_isSightedIn = false;
|
||||
|
||||
m_hasBeenSightedIn = false;
|
||||
|
||||
if ( GetBot()->IsDebugging( NEXTBOT_LOOK_AT ) )
|
||||
{
|
||||
NDebugOverlay::Cross3D( lookAtPos, 2.0f, 255, 255, 100, true, 2.0f * duration );
|
||||
|
||||
const char *priName = "";
|
||||
switch( priority )
|
||||
{
|
||||
case BORING: priName = "BORING"; break;
|
||||
case INTERESTING: priName = "INTERESTING"; break;
|
||||
case IMPORTANT: priName = "IMPORTANT"; break;
|
||||
case CRITICAL: priName = "CRITICAL"; break;
|
||||
}
|
||||
|
||||
ConColorMsg( Color( 255, 100, 0, 255 ), "%3.2f: %s Look At ( %g, %g, %g ) for %3.2f s, Pri = %s, Reason = %s\n",
|
||||
gpGlobals->curtime,
|
||||
m_player->GetPlayerName(),
|
||||
lookAtPos.x, lookAtPos.y, lookAtPos.z,
|
||||
duration,
|
||||
priName,
|
||||
( reason ) ? reason : "" );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Aim the bot's head towards the given goal
|
||||
*/
|
||||
void PlayerBody::AimHeadTowards( CBaseEntity *subject, LookAtPriorityType priority, float duration, INextBotReply *replyWhenAimed, const char *reason )
|
||||
{
|
||||
if ( duration <= 0.0f )
|
||||
{
|
||||
duration = 0.1f;
|
||||
}
|
||||
|
||||
if ( subject == NULL )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// don't spaz our aim around
|
||||
if ( m_lookAtPriority == priority )
|
||||
{
|
||||
if ( !IsHeadSteady() || GetHeadSteadyDuration() < nb_head_aim_settle_duration.GetFloat() )
|
||||
{
|
||||
// we're still finishing a look-at at the same priority
|
||||
if ( replyWhenAimed )
|
||||
{
|
||||
replyWhenAimed->OnFail( GetBot(), INextBotReply::DENIED );
|
||||
}
|
||||
|
||||
if ( GetBot()->IsDebugging( NEXTBOT_LOOK_AT ) )
|
||||
{
|
||||
ConColorMsg( Color( 255, 0, 0, 255 ), "%3.2f: %s Look At '%s' rejected - previous aim not %s\n",
|
||||
gpGlobals->curtime,
|
||||
m_player->GetPlayerName(),
|
||||
reason,
|
||||
IsHeadSteady() ? "head-steady" : "settled long enough" );
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// don't short-circuit if "sighted in" to avoid rapid view jitter
|
||||
if ( m_lookAtPriority > priority && !m_lookAtExpireTimer.IsElapsed() )
|
||||
{
|
||||
// higher priority lookat still ongoing
|
||||
if ( replyWhenAimed )
|
||||
{
|
||||
replyWhenAimed->OnFail( GetBot(), INextBotReply::DENIED );
|
||||
}
|
||||
|
||||
if ( GetBot()->IsDebugging( NEXTBOT_LOOK_AT ) )
|
||||
{
|
||||
ConColorMsg( Color( 255, 0, 0, 255 ), "%3.2f: %s Look At '%s' rejected - higher priority aim in progress\n",
|
||||
gpGlobals->curtime,
|
||||
m_player->GetPlayerName(),
|
||||
reason );
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if ( m_lookAtReplyWhenAimed )
|
||||
{
|
||||
// in-process aim was interrupted
|
||||
m_lookAtReplyWhenAimed->OnFail( GetBot(), INextBotReply::INTERRUPTED );
|
||||
}
|
||||
|
||||
m_lookAtReplyWhenAimed = replyWhenAimed;
|
||||
m_lookAtExpireTimer.Start( duration );
|
||||
|
||||
// if given the same subject, just update priority
|
||||
if ( subject == m_lookAtSubject )
|
||||
{
|
||||
m_lookAtPriority = priority;
|
||||
return;
|
||||
}
|
||||
|
||||
// new subject
|
||||
m_lookAtSubject = subject;
|
||||
|
||||
#ifdef REFACTOR_FOR_CLIENT_SIDE_EYE_TRACKING
|
||||
CBasePlayer *pMyPlayer = static_cast< CBasePlayer * >( GetEntity() );
|
||||
if ( subject->IsPlayer() )
|
||||
{
|
||||
// looking at a player, look at their eye position
|
||||
TerrorPlayer *pMyTarget = ToTerrorPlayer( subject );
|
||||
m_lookAtPos = subject->EyePosition();
|
||||
if(pMyPlayer)
|
||||
{
|
||||
pMyPlayer->SetLookatPlayer( pMyTarget );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// not looking at a player
|
||||
m_lookAtPos = subject->WorldSpaceCenter();
|
||||
if(pMyPlayer)
|
||||
{
|
||||
pMyPlayer->SetLookatPlayer( NULL );
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
m_lookAtPriority = priority;
|
||||
m_lookAtDurationTimer.Start();
|
||||
|
||||
// do NOT clear this here, or continuous calls to AimHeadTowards will keep IsHeadAimingOnTarget returning false all of the time
|
||||
// m_isSightedIn = false;
|
||||
|
||||
m_hasBeenSightedIn = false;
|
||||
|
||||
if ( GetBot()->IsDebugging( NEXTBOT_LOOK_AT ) )
|
||||
{
|
||||
NDebugOverlay::Cross3D( m_lookAtPos, 2.0f, 100, 100, 100, true, duration );
|
||||
|
||||
const char *priName = "";
|
||||
switch( priority )
|
||||
{
|
||||
case BORING: priName = "BORING"; break;
|
||||
case INTERESTING: priName = "INTERESTING"; break;
|
||||
case IMPORTANT: priName = "IMPORTANT"; break;
|
||||
case CRITICAL: priName = "CRITICAL"; break;
|
||||
}
|
||||
|
||||
ConColorMsg( Color( 255, 100, 0, 255 ), "%3.2f: %s Look At subject %s for %3.2f s, Pri = %s, Reason = %s\n",
|
||||
gpGlobals->curtime,
|
||||
m_player->GetPlayerName(),
|
||||
subject->GetClassname(),
|
||||
duration,
|
||||
priName,
|
||||
( reason ) ? reason : "" );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Return true if head is not rapidly turning to look somewhere else
|
||||
*/
|
||||
bool PlayerBody::IsHeadSteady( void ) const
|
||||
{
|
||||
return m_headSteadyTimer.HasStarted();
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Return the duration that the bot's head has been on-target
|
||||
*/
|
||||
float PlayerBody::GetHeadSteadyDuration( void ) const
|
||||
{
|
||||
// return ( IsHeadAimingOnTarget() ) ? m_headSteadyTimer.GetElapsedTime() : 0.0f;
|
||||
return m_headSteadyTimer.HasStarted() ? m_headSteadyTimer.GetElapsedTime() : 0.0f;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------------------------
|
||||
// Clear out currently pending replyWhenAimed callback
|
||||
void PlayerBody::ClearPendingAimReply( void )
|
||||
{
|
||||
m_lookAtReplyWhenAimed = NULL;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------------------------
|
||||
float PlayerBody::GetMaxHeadAngularVelocity( void ) const
|
||||
{
|
||||
return nb_saccade_speed.GetFloat();
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------------------------
|
||||
bool PlayerBody::StartActivity( Activity act, unsigned int flags )
|
||||
{
|
||||
// player animation state is controlled on the client
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Return currently animating activity
|
||||
*/
|
||||
Activity PlayerBody::GetActivity( void ) const
|
||||
{
|
||||
return ACT_INVALID;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Return true if currently animating activity matches the given one
|
||||
*/
|
||||
bool PlayerBody::IsActivity( Activity act ) const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Return true if currently animating activity has any of the given flags
|
||||
*/
|
||||
bool PlayerBody::HasActivityType( unsigned int flags ) const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Request a posture change
|
||||
*/
|
||||
void PlayerBody::SetDesiredPosture( PostureType posture )
|
||||
{
|
||||
m_posture = posture;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Get posture body is trying to assume
|
||||
*/
|
||||
IBody::PostureType PlayerBody::GetDesiredPosture( void ) const
|
||||
{
|
||||
return m_posture;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Return true if body is trying to assume this posture
|
||||
*/
|
||||
bool PlayerBody::IsDesiredPosture( PostureType posture ) const
|
||||
{
|
||||
return ( posture == m_posture );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Return true if body's actual posture matches its desired posture
|
||||
*/
|
||||
bool PlayerBody::IsInDesiredPosture( void ) const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Return body's current actual posture
|
||||
*/
|
||||
IBody::PostureType PlayerBody::GetActualPosture( void ) const
|
||||
{
|
||||
return m_posture;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Return true if body is actually in the given posture
|
||||
*/
|
||||
bool PlayerBody::IsActualPosture( PostureType posture ) const
|
||||
{
|
||||
return ( posture == m_posture );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Return true if body's current posture allows it to move around the world
|
||||
*/
|
||||
bool PlayerBody::IsPostureMobile( void ) const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Return true if body's posture is in the process of changing to new posture
|
||||
*/
|
||||
bool PlayerBody::IsPostureChanging( void ) const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Arousal level change
|
||||
*/
|
||||
void PlayerBody::SetArousal( ArousalType arousal )
|
||||
{
|
||||
m_arousal = arousal;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Get arousal level
|
||||
*/
|
||||
IBody::ArousalType PlayerBody::GetArousal( void ) const
|
||||
{
|
||||
return m_arousal;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Return true if body is at this arousal level
|
||||
*/
|
||||
bool PlayerBody::IsArousal( ArousalType arousal ) const
|
||||
{
|
||||
return ( arousal == m_arousal );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Width of bot's collision hull in XY plane
|
||||
*/
|
||||
float PlayerBody::GetHullWidth( void ) const
|
||||
{
|
||||
return VEC_HULL_MAX_SCALED( m_player ).x - VEC_HULL_MIN_SCALED( m_player ).x;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Height of bot's current collision hull based on posture
|
||||
*/
|
||||
float PlayerBody::GetHullHeight( void ) const
|
||||
{
|
||||
if ( m_posture == CROUCH )
|
||||
{
|
||||
return GetCrouchHullHeight();
|
||||
}
|
||||
|
||||
return GetStandHullHeight();
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Height of bot's collision hull when standing
|
||||
*/
|
||||
float PlayerBody::GetStandHullHeight( void ) const
|
||||
{
|
||||
return VEC_HULL_MAX_SCALED( m_player ).z - VEC_HULL_MIN_SCALED( m_player ).z;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Height of bot's collision hull when crouched
|
||||
*/
|
||||
float PlayerBody::GetCrouchHullHeight( void ) const
|
||||
{
|
||||
return VEC_DUCK_HULL_MAX_SCALED( m_player ).z - VEC_DUCK_HULL_MIN_SCALED( m_player ).z;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Return current collision hull minimums based on actual body posture
|
||||
*/
|
||||
const Vector &PlayerBody::GetHullMins( void ) const
|
||||
{
|
||||
if ( m_posture == CROUCH )
|
||||
{
|
||||
m_hullMins = VEC_DUCK_HULL_MIN_SCALED( m_player );
|
||||
}
|
||||
else
|
||||
{
|
||||
m_hullMins = VEC_HULL_MIN_SCALED( m_player );
|
||||
}
|
||||
|
||||
return m_hullMins;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Return current collision hull maximums based on actual body posture
|
||||
*/
|
||||
const Vector &PlayerBody::GetHullMaxs( void ) const
|
||||
{
|
||||
if ( m_posture == CROUCH )
|
||||
{
|
||||
m_hullMaxs = VEC_DUCK_HULL_MAX_SCALED( m_player );
|
||||
}
|
||||
else
|
||||
{
|
||||
m_hullMaxs = VEC_HULL_MAX_SCALED( m_player );
|
||||
}
|
||||
|
||||
return m_hullMaxs;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Return the bot's collision mask (hack until we get a general hull trace abstraction here or in the locomotion interface)
|
||||
*/
|
||||
unsigned int PlayerBody::GetSolidMask( void ) const
|
||||
{
|
||||
return ( m_player ) ? m_player->PlayerSolidMask() : MASK_PLAYERSOLID;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
// NextBotPlayerBody.h
|
||||
// Control and information about the bot's body state (posture, animation state, etc)
|
||||
// Author: Michael Booth, October 2006
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
|
||||
#ifndef _NEXT_BOT_PLAYER_BODY_H_
|
||||
#define _NEXT_BOT_PLAYER_BODY_H_
|
||||
|
||||
#include "NextBotBodyInterface.h"
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* A useful reply for IBody::AimHeadTowards. When the
|
||||
* head is aiming on target, press the fire button.
|
||||
*/
|
||||
class PressFireButtonReply : public INextBotReply
|
||||
{
|
||||
public:
|
||||
virtual void OnSuccess( INextBot *bot ); // invoked when process completed successfully
|
||||
};
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* A useful reply for IBody::AimHeadTowards. When the
|
||||
* head is aiming on target, press the alt-fire button.
|
||||
*/
|
||||
class PressAltFireButtonReply : public INextBotReply
|
||||
{
|
||||
public:
|
||||
virtual void OnSuccess( INextBot *bot ); // invoked when process completed successfully
|
||||
};
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* A useful reply for IBody::AimHeadTowards. When the
|
||||
* head is aiming on target, press the jump button.
|
||||
*/
|
||||
class PressJumpButtonReply : public INextBotReply
|
||||
{
|
||||
public:
|
||||
virtual void OnSuccess( INextBot *bot ); // invoked when process completed successfully
|
||||
};
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* The interface for control and information about the bot's body state (posture, animation state, etc)
|
||||
*/
|
||||
class PlayerBody : public IBody
|
||||
{
|
||||
public:
|
||||
PlayerBody( INextBot *bot );
|
||||
virtual ~PlayerBody();
|
||||
|
||||
virtual void Reset( void ); // reset to initial state
|
||||
virtual void Upkeep( void ); // lightweight update guaranteed to occur every server tick
|
||||
|
||||
virtual bool SetPosition( const Vector &pos );
|
||||
|
||||
virtual const Vector &GetEyePosition( void ) const; // return the eye position of the bot in world coordinates
|
||||
virtual const Vector &GetViewVector( void ) const; // return the view unit direction vector in world coordinates
|
||||
|
||||
virtual void AimHeadTowards( const Vector &lookAtPos,
|
||||
LookAtPriorityType priority = BORING,
|
||||
float duration = 0.0f,
|
||||
INextBotReply *replyWhenAimed = NULL,
|
||||
const char *reason = NULL ); // aim the bot's head towards the given goal
|
||||
|
||||
virtual void AimHeadTowards( CBaseEntity *subject,
|
||||
LookAtPriorityType priority = BORING,
|
||||
float duration = 0.0f,
|
||||
INextBotReply *replyWhenAimed = NULL,
|
||||
const char *reason = NULL ); // continually aim the bot's head towards the given subject
|
||||
|
||||
virtual bool IsHeadAimingOnTarget( void ) const; // return true if the bot's head has achieved its most recent lookat target
|
||||
virtual bool IsHeadSteady( void ) const; // return true if head is not rapidly turning to look somewhere else
|
||||
virtual float GetHeadSteadyDuration( void ) const; // return the duration that the bot's head has been on-target
|
||||
virtual void ClearPendingAimReply( void ); // clear out currently pending replyWhenAimed callback
|
||||
|
||||
virtual float GetMaxHeadAngularVelocity( void ) const; // return max turn rate of head in degrees/second
|
||||
|
||||
virtual bool StartActivity( Activity act, unsigned int flags );
|
||||
virtual Activity GetActivity( void ) const; // return currently animating activity
|
||||
virtual bool IsActivity( Activity act ) const; // return true if currently animating activity matches the given one
|
||||
virtual bool HasActivityType( unsigned int flags ) const; // return true if currently animating activity has any of the given flags
|
||||
|
||||
virtual void SetDesiredPosture( PostureType posture ); // request a posture change
|
||||
virtual PostureType GetDesiredPosture( void ) const; // get posture body is trying to assume
|
||||
virtual bool IsDesiredPosture( PostureType posture ) const; // return true if body is trying to assume this posture
|
||||
virtual bool IsInDesiredPosture( void ) const; // return true if body's actual posture matches its desired posture
|
||||
|
||||
virtual PostureType GetActualPosture( void ) const; // return body's current actual posture
|
||||
virtual bool IsActualPosture( PostureType posture ) const; // return true if body is actually in the given posture
|
||||
|
||||
virtual bool IsPostureMobile( void ) const; // return true if body's current posture allows it to move around the world
|
||||
virtual bool IsPostureChanging( void ) const; // return true if body's posture is in the process of changing to new posture
|
||||
|
||||
virtual void SetArousal( ArousalType arousal ); // arousal level change
|
||||
virtual ArousalType GetArousal( void ) const; // get arousal level
|
||||
virtual bool IsArousal( ArousalType arousal ) const; // return true if body is at this arousal level
|
||||
|
||||
virtual float GetHullWidth( void ) const; // width of bot's collision hull in XY plane
|
||||
virtual float GetHullHeight( void ) const; // height of bot's current collision hull based on posture
|
||||
virtual float GetStandHullHeight( void ) const; // height of bot's collision hull when standing
|
||||
virtual float GetCrouchHullHeight( void ) const; // height of bot's collision hull when crouched
|
||||
virtual const Vector &GetHullMins( void ) const; // return current collision hull minimums based on actual body posture
|
||||
virtual const Vector &GetHullMaxs( void ) const; // return current collision hull maximums based on actual body posture
|
||||
|
||||
virtual unsigned int GetSolidMask( void ) const; // return the bot's collision mask (hack until we get a general hull trace abstraction here or in the locomotion interface)
|
||||
|
||||
virtual CBaseEntity *GetEntity( void ); // get the entity
|
||||
private:
|
||||
CBasePlayer *m_player;
|
||||
|
||||
PostureType m_posture;
|
||||
ArousalType m_arousal;
|
||||
|
||||
mutable Vector m_eyePos; // for use with GetEyePosition() ONLY
|
||||
mutable Vector m_viewVector; // for use with GetViewVector() ONLY
|
||||
mutable Vector m_hullMins; // for use with GetHullMins() ONLY
|
||||
mutable Vector m_hullMaxs; // for use with GetHullMaxs() ONLY
|
||||
|
||||
Vector m_lookAtPos; // if m_lookAtSubject is non-NULL, it continually overwrites this position with its own
|
||||
EHANDLE m_lookAtSubject;
|
||||
Vector m_lookAtVelocity; // world velocity of lookat point, for tracking moving subjects
|
||||
CountdownTimer m_lookAtTrackingTimer;
|
||||
|
||||
LookAtPriorityType m_lookAtPriority;
|
||||
CountdownTimer m_lookAtExpireTimer; // how long until this lookat expired
|
||||
IntervalTimer m_lookAtDurationTimer; // how long have we been looking at this target
|
||||
INextBotReply *m_lookAtReplyWhenAimed;
|
||||
bool m_isSightedIn; // true if we are looking at our last lookat target
|
||||
bool m_hasBeenSightedIn; // true if we have hit the current lookat target
|
||||
|
||||
IntervalTimer m_headSteadyTimer;
|
||||
QAngle m_priorAngles; // last update's head angles
|
||||
QAngle m_desiredAngles;
|
||||
|
||||
CountdownTimer m_anchorRepositionTimer; // the time is takes us to recenter our virtual mouse
|
||||
Vector m_anchorForward;
|
||||
};
|
||||
|
||||
inline bool PlayerBody::IsHeadAimingOnTarget( void ) const
|
||||
{
|
||||
// TODO: Calling this immediately after AimHeadTowards will always return false until next Upkeep() (MSB)
|
||||
return m_isSightedIn;
|
||||
}
|
||||
|
||||
|
||||
#endif // _NEXT_BOT_PLAYER_BODY_H_
|
||||
@@ -0,0 +1,826 @@
|
||||
// NextBotPlayerLocomotion.cpp
|
||||
// Implementation of Locomotion interface for CBasePlayer-derived classes
|
||||
// Author: Michael Booth, November 2005
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
|
||||
#include "cbase.h"
|
||||
#include "nav_mesh.h"
|
||||
#include "in_buttons.h"
|
||||
#include "NextBot.h"
|
||||
#include "NextBotUtil.h"
|
||||
#include "NextBotPlayer.h"
|
||||
#include "NextBotPlayerLocomotion.h"
|
||||
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
ConVar NextBotPlayerMoveDirect( "nb_player_move_direct", "0" );
|
||||
|
||||
//-----------------------------------------------------------------------------------------------------
|
||||
PlayerLocomotion::PlayerLocomotion( INextBot *bot ) : ILocomotion( bot )
|
||||
{
|
||||
m_player = NULL;
|
||||
Reset();
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Reset locomotor to initial state
|
||||
*/
|
||||
void PlayerLocomotion::Reset( void )
|
||||
{
|
||||
m_player = static_cast< CBasePlayer * >( GetBot()->GetEntity() );
|
||||
|
||||
m_isJumping = false;
|
||||
m_isClimbingUpToLedge = false;
|
||||
m_isJumpingAcrossGap = false;
|
||||
m_hasLeftTheGround = false;
|
||||
m_desiredSpeed = 0.0f;
|
||||
|
||||
|
||||
m_ladderState = NO_LADDER;
|
||||
m_ladderInfo = NULL;
|
||||
m_ladderDismountGoal = NULL;
|
||||
m_ladderTimer.Invalidate();
|
||||
|
||||
m_minSpeedLimit = 0.0f;
|
||||
m_maxSpeedLimit = 9999999.9f;
|
||||
|
||||
BaseClass::Reset();
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------------------------------
|
||||
bool PlayerLocomotion::TraverseLadder( void )
|
||||
{
|
||||
switch( m_ladderState )
|
||||
{
|
||||
case APPROACHING_ASCENDING_LADDER:
|
||||
m_ladderState = ApproachAscendingLadder();
|
||||
return true;
|
||||
|
||||
case APPROACHING_DESCENDING_LADDER:
|
||||
m_ladderState = ApproachDescendingLadder();
|
||||
return true;
|
||||
|
||||
case ASCENDING_LADDER:
|
||||
m_ladderState = AscendLadder();
|
||||
return true;
|
||||
|
||||
case DESCENDING_LADDER:
|
||||
m_ladderState = DescendLadder();
|
||||
return true;
|
||||
|
||||
case DISMOUNTING_LADDER_TOP:
|
||||
m_ladderState = DismountLadderTop();
|
||||
return true;
|
||||
|
||||
case DISMOUNTING_LADDER_BOTTOM:
|
||||
m_ladderState = DismountLadderBottom();
|
||||
return true;
|
||||
|
||||
case NO_LADDER:
|
||||
default:
|
||||
m_ladderInfo = NULL;
|
||||
|
||||
if ( GetBot()->GetEntity()->GetMoveType() == MOVETYPE_LADDER )
|
||||
{
|
||||
// on ladder and don't want to be
|
||||
GetBot()->GetEntity()->SetMoveType( MOVETYPE_WALK );
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* We're close, but not yet on, this ladder - approach it
|
||||
*/
|
||||
PlayerLocomotion::LadderState PlayerLocomotion::ApproachAscendingLadder( void )
|
||||
{
|
||||
if ( m_ladderInfo == NULL )
|
||||
{
|
||||
return NO_LADDER;
|
||||
}
|
||||
|
||||
// sanity check - are we already at the end of this ladder?
|
||||
if ( GetFeet().z >= m_ladderInfo->m_top.z - GetStepHeight() )
|
||||
{
|
||||
m_ladderTimer.Start( 2.0f );
|
||||
return DISMOUNTING_LADDER_TOP;
|
||||
}
|
||||
|
||||
// sanity check - are we too far below this ladder to reach it?
|
||||
if ( GetFeet().z <= m_ladderInfo->m_bottom.z - GetMaxJumpHeight() )
|
||||
{
|
||||
return NO_LADDER;
|
||||
}
|
||||
|
||||
FaceTowards( m_ladderInfo->m_bottom );
|
||||
|
||||
// it is important to approach precisely, so use a very large weight to wash out all other Approaches
|
||||
Approach( m_ladderInfo->m_bottom, 9999999.9f );
|
||||
|
||||
if ( GetBot()->GetEntity()->GetMoveType() == MOVETYPE_LADDER )
|
||||
{
|
||||
// we're on the ladder
|
||||
return ASCENDING_LADDER;
|
||||
}
|
||||
|
||||
if ( GetBot()->IsDebugging( NEXTBOT_LOCOMOTION ) )
|
||||
{
|
||||
NDebugOverlay::EntityText( GetBot()->GetEntity()->entindex(), 0, "Approach ascending ladder", 0.1f, 255, 255, 255, 255 );
|
||||
}
|
||||
|
||||
return APPROACHING_ASCENDING_LADDER;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------------------------------
|
||||
PlayerLocomotion::LadderState PlayerLocomotion::ApproachDescendingLadder( void )
|
||||
{
|
||||
if ( m_ladderInfo == NULL )
|
||||
{
|
||||
return NO_LADDER;
|
||||
}
|
||||
|
||||
// sanity check - are we already at the end of this ladder?
|
||||
if ( GetFeet().z <= m_ladderInfo->m_bottom.z + GetMaxJumpHeight() )
|
||||
{
|
||||
m_ladderTimer.Start( 2.0f );
|
||||
return DISMOUNTING_LADDER_BOTTOM;
|
||||
}
|
||||
|
||||
Vector mountPoint = m_ladderInfo->m_top + 0.25f * GetBot()->GetBodyInterface()->GetHullWidth() * m_ladderInfo->GetNormal();
|
||||
Vector to = mountPoint - GetFeet();
|
||||
to.z = 0.0f;
|
||||
|
||||
float mountRange = to.NormalizeInPlace();
|
||||
Vector moveGoal;
|
||||
|
||||
const float veryClose = 10.0f;
|
||||
if ( mountRange < veryClose )
|
||||
{
|
||||
// we're right at the ladder - just keep moving forward until we grab it
|
||||
const Vector &forward = GetMotionVector();
|
||||
moveGoal = GetFeet() + 100.0f * forward;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( DotProduct( to, m_ladderInfo->GetNormal() ) < 0.0f )
|
||||
{
|
||||
// approaching front of downward ladder
|
||||
// ##
|
||||
// ->+ ##
|
||||
// | ##
|
||||
// | ##
|
||||
// | ##
|
||||
// <-+ ##
|
||||
// ######
|
||||
//
|
||||
moveGoal = m_ladderInfo->m_top - 100.0f * m_ladderInfo->GetNormal();
|
||||
}
|
||||
else
|
||||
{
|
||||
// approaching back of downward ladder
|
||||
//
|
||||
// ->+
|
||||
// ##|
|
||||
// ##|
|
||||
// ##+-->
|
||||
// ######
|
||||
//
|
||||
moveGoal = m_ladderInfo->m_top + 100.0f * m_ladderInfo->GetNormal();
|
||||
}
|
||||
}
|
||||
|
||||
FaceTowards( moveGoal );
|
||||
|
||||
// it is important to approach precisely, so use a very large weight to wash out all other Approaches
|
||||
Approach( moveGoal, 9999999.9f );
|
||||
|
||||
if ( GetBot()->GetEntity()->GetMoveType() == MOVETYPE_LADDER )
|
||||
{
|
||||
// we're on the ladder
|
||||
return DESCENDING_LADDER;
|
||||
}
|
||||
|
||||
if ( GetBot()->IsDebugging( NEXTBOT_LOCOMOTION ) )
|
||||
{
|
||||
NDebugOverlay::EntityText( GetBot()->GetEntity()->entindex(), 0, "Approach descending ladder", 0.1f, 255, 255, 255, 255 );
|
||||
}
|
||||
|
||||
return APPROACHING_DESCENDING_LADDER;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------------------------------
|
||||
PlayerLocomotion::LadderState PlayerLocomotion::AscendLadder( void )
|
||||
{
|
||||
if ( m_ladderInfo == NULL )
|
||||
{
|
||||
return NO_LADDER;
|
||||
}
|
||||
|
||||
if ( GetBot()->GetEntity()->GetMoveType() != MOVETYPE_LADDER )
|
||||
{
|
||||
// slipped off ladder
|
||||
m_ladderInfo = NULL;
|
||||
return NO_LADDER;
|
||||
}
|
||||
|
||||
if ( GetFeet().z >= m_ladderInfo->m_top.z )
|
||||
{
|
||||
// reached top of ladder
|
||||
m_ladderTimer.Start( 2.0f );
|
||||
return DISMOUNTING_LADDER_TOP;
|
||||
}
|
||||
|
||||
// climb up this ladder - look up
|
||||
Vector goal = GetFeet() + 100.0f * ( -m_ladderInfo->GetNormal() + Vector( 0, 0, 2 ) );
|
||||
|
||||
GetBot()->GetBodyInterface()->AimHeadTowards( goal, IBody::MANDATORY, 0.1f, NULL, "Ladder" );
|
||||
|
||||
// it is important to approach precisely, so use a very large weight to wash out all other Approaches
|
||||
Approach( goal, 9999999.9f );
|
||||
|
||||
if ( GetBot()->IsDebugging( NEXTBOT_LOCOMOTION ) )
|
||||
{
|
||||
NDebugOverlay::EntityText( GetBot()->GetEntity()->entindex(), 0, "Ascend", 0.1f, 255, 255, 255, 255 );
|
||||
}
|
||||
|
||||
return ASCENDING_LADDER;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------------------------------
|
||||
PlayerLocomotion::LadderState PlayerLocomotion::DescendLadder( void )
|
||||
{
|
||||
if ( m_ladderInfo == NULL )
|
||||
{
|
||||
return NO_LADDER;
|
||||
}
|
||||
|
||||
if ( GetBot()->GetEntity()->GetMoveType() != MOVETYPE_LADDER )
|
||||
{
|
||||
// slipped off ladder
|
||||
m_ladderInfo = NULL;
|
||||
return NO_LADDER;
|
||||
}
|
||||
|
||||
if ( GetFeet().z <= m_ladderInfo->m_bottom.z + GetBot()->GetLocomotionInterface()->GetStepHeight() )
|
||||
{
|
||||
// reached bottom of ladder
|
||||
m_ladderTimer.Start( 2.0f );
|
||||
return DISMOUNTING_LADDER_BOTTOM;
|
||||
}
|
||||
|
||||
// climb down this ladder - look down
|
||||
Vector goal = GetFeet() + 100.0f * ( m_ladderInfo->GetNormal() + Vector( 0, 0, -2 ) );
|
||||
|
||||
GetBot()->GetBodyInterface()->AimHeadTowards( goal, IBody::MANDATORY, 0.1f, NULL, "Ladder" );
|
||||
|
||||
// it is important to approach precisely, so use a very large weight to wash out all other Approaches
|
||||
Approach( goal, 9999999.9f );
|
||||
|
||||
if ( GetBot()->IsDebugging( NEXTBOT_LOCOMOTION ) )
|
||||
{
|
||||
NDebugOverlay::EntityText( GetBot()->GetEntity()->entindex(), 0, "Descend", 0.1f, 255, 255, 255, 255 );
|
||||
}
|
||||
|
||||
return DESCENDING_LADDER;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------------------------------
|
||||
PlayerLocomotion::LadderState PlayerLocomotion::DismountLadderTop( void )
|
||||
{
|
||||
if ( m_ladderInfo == NULL || m_ladderTimer.IsElapsed() )
|
||||
{
|
||||
m_ladderInfo = NULL;
|
||||
return NO_LADDER;
|
||||
}
|
||||
|
||||
IBody *body = GetBot()->GetBodyInterface();
|
||||
Vector toGoal = m_ladderDismountGoal->GetCenter() - GetFeet();
|
||||
toGoal.z = 0.0f;
|
||||
float range = toGoal.NormalizeInPlace();
|
||||
toGoal.z = 1.0f;
|
||||
|
||||
body->AimHeadTowards( body->GetEyePosition() + 100.0f * toGoal, IBody::MANDATORY, 0.1f, NULL, "Ladder dismount" );
|
||||
|
||||
// it is important to approach precisely, so use a very large weight to wash out all other Approaches
|
||||
Approach( GetFeet() + 100.0f * toGoal, 9999999.9f );
|
||||
|
||||
if ( GetBot()->IsDebugging( NEXTBOT_LOCOMOTION ) )
|
||||
{
|
||||
NDebugOverlay::EntityText( GetBot()->GetEntity()->entindex(), 0, "Dismount top", 0.1f, 255, 255, 255, 255 );
|
||||
NDebugOverlay::HorzArrow( GetFeet(), m_ladderDismountGoal->GetCenter(), 5.0f, 255, 255, 0, 255, true, 0.1f );
|
||||
}
|
||||
|
||||
// test 2D vector here in case nav area is under the geometry a bit
|
||||
const float tolerance = 10.0f;
|
||||
if ( GetBot()->GetEntity()->GetLastKnownArea() == m_ladderDismountGoal && range < tolerance )
|
||||
{
|
||||
// reached dismount goal
|
||||
m_ladderInfo = NULL;
|
||||
return NO_LADDER;
|
||||
}
|
||||
|
||||
return DISMOUNTING_LADDER_TOP;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------------------------------
|
||||
PlayerLocomotion::LadderState PlayerLocomotion::DismountLadderBottom( void )
|
||||
{
|
||||
if ( m_ladderInfo == NULL || m_ladderTimer.IsElapsed() )
|
||||
{
|
||||
m_ladderInfo = NULL;
|
||||
return NO_LADDER;
|
||||
}
|
||||
|
||||
if ( GetBot()->GetEntity()->GetMoveType() == MOVETYPE_LADDER )
|
||||
{
|
||||
// near the bottom - just let go
|
||||
GetBot()->GetEntity()->SetMoveType( MOVETYPE_WALK );
|
||||
m_ladderInfo = NULL;
|
||||
}
|
||||
|
||||
return NO_LADDER;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Update internal state
|
||||
*/
|
||||
void PlayerLocomotion::Update( void )
|
||||
{
|
||||
if ( TraverseLadder() )
|
||||
{
|
||||
return BaseClass::Update();
|
||||
}
|
||||
|
||||
if ( m_isJumpingAcrossGap || m_isClimbingUpToLedge )
|
||||
{
|
||||
// force a run
|
||||
SetMinimumSpeedLimit( GetRunSpeed() );
|
||||
|
||||
Vector toLanding = m_landingGoal - GetFeet();
|
||||
toLanding.z = 0.0f;
|
||||
toLanding.NormalizeInPlace();
|
||||
|
||||
if ( m_hasLeftTheGround )
|
||||
{
|
||||
// face into the jump/climb
|
||||
GetBot()->GetBodyInterface()->AimHeadTowards( GetBot()->GetEntity()->EyePosition() + 100.0 * toLanding, IBody::MANDATORY, 0.25f, NULL, "Facing impending jump/climb" );
|
||||
|
||||
if ( IsOnGround() )
|
||||
{
|
||||
// back on the ground - jump is complete
|
||||
m_isClimbingUpToLedge = false;
|
||||
m_isJumpingAcrossGap = false;
|
||||
SetMinimumSpeedLimit( 0.0f );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// haven't left the ground yet - just starting the jump
|
||||
|
||||
if ( !IsClimbingOrJumping() )
|
||||
{
|
||||
Jump();
|
||||
}
|
||||
|
||||
Vector vel = GetBot()->GetEntity()->GetAbsVelocity();
|
||||
|
||||
if ( m_isJumpingAcrossGap )
|
||||
{
|
||||
// cheat and max our velocity in case we were stopped at the edge of this gap
|
||||
vel.x = GetRunSpeed() * toLanding.x;
|
||||
vel.y = GetRunSpeed() * toLanding.y;
|
||||
// leave vel.z unchanged
|
||||
}
|
||||
|
||||
GetBot()->GetEntity()->SetAbsVelocity( vel );
|
||||
|
||||
if ( !IsOnGround() )
|
||||
{
|
||||
// jump has begun
|
||||
m_hasLeftTheGround = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Approach( m_landingGoal );
|
||||
}
|
||||
|
||||
BaseClass::Update();
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------------------------------
|
||||
void PlayerLocomotion::AdjustPosture( const Vector &moveGoal )
|
||||
{
|
||||
// This function has no effect if we're not standing or crouching
|
||||
IBody *body = GetBot()->GetBodyInterface();
|
||||
if ( !body->IsActualPosture( IBody::STAND ) && !body->IsActualPosture( IBody::CROUCH ) )
|
||||
return;
|
||||
|
||||
// not all games have auto-crouch, so don't assume it here
|
||||
BaseClass::AdjustPosture( moveGoal );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Build a user command to move this player towards the goal position
|
||||
*/
|
||||
void PlayerLocomotion::Approach( const Vector &pos, float goalWeight )
|
||||
{
|
||||
VPROF_BUDGET( "PlayerLocomotion::Approach", "NextBot" );
|
||||
|
||||
BaseClass::Approach( pos );
|
||||
|
||||
AdjustPosture( pos );
|
||||
|
||||
if ( GetBot()->IsDebugging( NEXTBOT_LOCOMOTION ) )
|
||||
{
|
||||
NDebugOverlay::Line( GetFeet(), pos, 255, 255, 0, true, 0.1f );
|
||||
}
|
||||
|
||||
INextBotPlayerInput *playerButtons = dynamic_cast< INextBotPlayerInput * >( GetBot() );
|
||||
|
||||
if ( !playerButtons )
|
||||
{
|
||||
DevMsg( "PlayerLocomotion::Approach: No INextBotPlayerInput\n " );
|
||||
return;
|
||||
}
|
||||
|
||||
Vector forward3D;
|
||||
m_player->EyeVectors( &forward3D );
|
||||
|
||||
Vector2D forward( forward3D.x, forward3D.y );
|
||||
forward.NormalizeInPlace();
|
||||
|
||||
Vector2D right( forward.y, -forward.x );
|
||||
|
||||
// compute unit vector to goal position
|
||||
Vector2D to = ( pos - GetFeet() ).AsVector2D();
|
||||
float goalDistance = to.NormalizeInPlace();
|
||||
|
||||
float ahead = to.Dot( forward );
|
||||
float side = to.Dot( right );
|
||||
|
||||
#ifdef NEED_TO_INTEGRATE_MOTION_CONTROLLED_CODE_FROM_L4D_PLAYERS
|
||||
// If we're climbing ledges, we need to stay crouched to prevent player movement code from messing
|
||||
// with our origin.
|
||||
CTerrorPlayer *player = ToTerrorPlayer(m_player);
|
||||
if ( player && player->IsMotionControlledZ( player->GetMainActivity() ) )
|
||||
{
|
||||
playerButtons->PressCrouchButton();
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
if ( m_player->IsOnLadder() && IsUsingLadder() && ( m_ladderState == ASCENDING_LADDER || m_ladderState == DESCENDING_LADDER ) )
|
||||
{
|
||||
// we are on a ladder and WANT to be on a ladder.
|
||||
playerButtons->PressForwardButton();
|
||||
|
||||
// Stay in center of ladder. The gamemovement will autocenter us in most cases, but this is needed in case it doesn't.
|
||||
if ( m_ladderInfo )
|
||||
{
|
||||
Vector posOnLadder;
|
||||
CalcClosestPointOnLine( GetFeet(), m_ladderInfo->m_bottom, m_ladderInfo->m_top, posOnLadder );
|
||||
|
||||
Vector alongLadder = m_ladderInfo->m_top - m_ladderInfo->m_bottom;
|
||||
alongLadder.NormalizeInPlace();
|
||||
|
||||
Vector rightLadder = CrossProduct( alongLadder, m_ladderInfo->GetNormal() );
|
||||
|
||||
Vector away = GetFeet() - posOnLadder;
|
||||
|
||||
// we only want error in plane of ladder
|
||||
float error = DotProduct( away, rightLadder );
|
||||
away.NormalizeInPlace();
|
||||
|
||||
const float tolerance = 5.0f + 0.25f * GetBot()->GetBodyInterface()->GetHullWidth();
|
||||
if ( error > tolerance )
|
||||
{
|
||||
if ( DotProduct( away, rightLadder ) > 0.0f )
|
||||
{
|
||||
playerButtons->PressLeftButton();
|
||||
}
|
||||
else
|
||||
{
|
||||
playerButtons->PressRightButton();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const float epsilon = 0.25f;
|
||||
if ( NextBotPlayerMoveDirect.GetBool() )
|
||||
{
|
||||
if ( goalDistance > epsilon )
|
||||
{
|
||||
playerButtons->SetButtonScale( ahead, side );
|
||||
}
|
||||
}
|
||||
|
||||
if ( ahead > epsilon )
|
||||
{
|
||||
playerButtons->PressForwardButton();
|
||||
|
||||
if ( GetBot()->IsDebugging( NEXTBOT_LOCOMOTION ) )
|
||||
{
|
||||
NDebugOverlay::HorzArrow( m_player->GetAbsOrigin(), m_player->GetAbsOrigin() + 50.0f * Vector( forward.x, forward.y, 0.0f ), 15.0f, 0, 255, 0, 255, true, 0.1f );
|
||||
}
|
||||
}
|
||||
else if ( ahead < -epsilon )
|
||||
{
|
||||
playerButtons->PressBackwardButton();
|
||||
|
||||
if ( GetBot()->IsDebugging( NEXTBOT_LOCOMOTION ) )
|
||||
{
|
||||
NDebugOverlay::HorzArrow( m_player->GetAbsOrigin(), m_player->GetAbsOrigin() - 50.0f * Vector( forward.x, forward.y, 0.0f ), 15.0f, 255, 0, 0, 255, true, 0.1f );
|
||||
}
|
||||
}
|
||||
|
||||
if ( side <= -epsilon )
|
||||
{
|
||||
playerButtons->PressLeftButton();
|
||||
|
||||
if ( GetBot()->IsDebugging( NEXTBOT_LOCOMOTION ) )
|
||||
{
|
||||
NDebugOverlay::HorzArrow( m_player->GetAbsOrigin(), m_player->GetAbsOrigin() - 50.0f * Vector( right.x, right.y, 0.0f ), 15.0f, 255, 0, 255, 255, true, 0.1f );
|
||||
}
|
||||
}
|
||||
else if ( side >= epsilon )
|
||||
{
|
||||
playerButtons->PressRightButton();
|
||||
|
||||
if ( GetBot()->IsDebugging( NEXTBOT_LOCOMOTION ) )
|
||||
{
|
||||
NDebugOverlay::HorzArrow( m_player->GetAbsOrigin(), m_player->GetAbsOrigin() + 50.0f * Vector( right.x, right.y, 0.0f ), 15.0f, 0, 255, 255, 255, true, 0.1f );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( !IsRunning() )
|
||||
{
|
||||
playerButtons->PressWalkButton();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Move the bot to the precise given position immediately,
|
||||
*/
|
||||
void PlayerLocomotion::DriveTo( const Vector &pos )
|
||||
{
|
||||
BaseClass::DriveTo( pos );
|
||||
|
||||
Approach( pos );
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
bool PlayerLocomotion::IsClimbPossible( INextBot *me, const CBaseEntity *obstacle ) const
|
||||
{
|
||||
// don't jump unless we have to
|
||||
const PathFollower *path = GetBot()->GetCurrentPath();
|
||||
if ( path )
|
||||
{
|
||||
const float watchForClimbRange = 75.0f;
|
||||
if ( !path->IsDiscontinuityAhead( GetBot(), Path::CLIMB_UP, watchForClimbRange ) )
|
||||
{
|
||||
// we are not planning on climbing
|
||||
|
||||
// always allow climbing over movable obstacles
|
||||
if ( obstacle && !const_cast< CBaseEntity * >( obstacle )->IsWorld() )
|
||||
{
|
||||
IPhysicsObject *physics = obstacle->VPhysicsGetObject();
|
||||
if ( physics && physics->IsMoveable() )
|
||||
{
|
||||
// movable physics object - climb over it
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if ( !GetBot()->GetLocomotionInterface()->IsStuck() )
|
||||
{
|
||||
// we're not stuck - don't try to jump up yet
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
bool PlayerLocomotion::ClimbUpToLedge( const Vector &landingGoal, const Vector &landingForward, const CBaseEntity *obstacle )
|
||||
{
|
||||
if ( !IsClimbPossible( GetBot(), obstacle ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Jump();
|
||||
|
||||
m_isClimbingUpToLedge = true;
|
||||
m_landingGoal = landingGoal;
|
||||
m_hasLeftTheGround = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
void PlayerLocomotion::JumpAcrossGap( const Vector &landingGoal, const Vector &landingForward )
|
||||
{
|
||||
Jump();
|
||||
|
||||
// face forward
|
||||
GetBot()->GetBodyInterface()->AimHeadTowards( landingGoal, IBody::MANDATORY, 1.0f, NULL, "Looking forward while jumping a gap" );
|
||||
|
||||
m_isJumpingAcrossGap = true;
|
||||
m_landingGoal = landingGoal;
|
||||
m_hasLeftTheGround = false;
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
void PlayerLocomotion::Jump( void )
|
||||
{
|
||||
m_isJumping = true;
|
||||
m_jumpTimer.Start( 0.5f );
|
||||
|
||||
INextBotPlayerInput *playerButtons = dynamic_cast< INextBotPlayerInput * >( GetBot() );
|
||||
if ( playerButtons )
|
||||
{
|
||||
playerButtons->PressJumpButton();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
bool PlayerLocomotion::IsClimbingOrJumping( void ) const
|
||||
{
|
||||
if ( !m_isJumping )
|
||||
return false;
|
||||
|
||||
if ( m_jumpTimer.IsElapsed() && IsOnGround() )
|
||||
{
|
||||
m_isJumping = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
bool PlayerLocomotion::IsClimbingUpToLedge( void ) const
|
||||
{
|
||||
return m_isClimbingUpToLedge;
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
bool PlayerLocomotion::IsJumpingAcrossGap( void ) const
|
||||
{
|
||||
return m_isJumpingAcrossGap;
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Return true if standing on something
|
||||
*/
|
||||
bool PlayerLocomotion::IsOnGround( void ) const
|
||||
{
|
||||
return (m_player->GetGroundEntity() != NULL);
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Return the current ground entity or NULL if not on the ground
|
||||
*/
|
||||
CBaseEntity *PlayerLocomotion::GetGround( void ) const
|
||||
{
|
||||
return m_player->GetGroundEntity();
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Surface normal of the ground we are in contact with
|
||||
*/
|
||||
const Vector &PlayerLocomotion::GetGroundNormal( void ) const
|
||||
{
|
||||
static Vector up( 0, 0, 1.0f );
|
||||
return up;
|
||||
|
||||
// TODO: Integrate movehelper_server for this: return m_player->GetGroundNormal();
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Climb the given ladder to the top and dismount
|
||||
*/
|
||||
void PlayerLocomotion::ClimbLadder( const CNavLadder *ladder, const CNavArea *dismountGoal )
|
||||
{
|
||||
// look up and push forward
|
||||
// Vector goal = GetBot()->GetPosition() + 100.0f * ( Vector( 0, 0, 1.0f ) - ladder->GetNormal() );
|
||||
// Approach( goal );
|
||||
// FaceTowards( goal );
|
||||
|
||||
m_ladderState = APPROACHING_ASCENDING_LADDER;
|
||||
m_ladderInfo = ladder;
|
||||
m_ladderDismountGoal = dismountGoal;
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Descend the given ladder to the bottom and dismount
|
||||
*/
|
||||
void PlayerLocomotion::DescendLadder( const CNavLadder *ladder, const CNavArea *dismountGoal )
|
||||
{
|
||||
// look down and push forward
|
||||
// Vector goal = GetBot()->GetPosition() + 100.0f * ( Vector( 0, 0, -1.0f ) - ladder->GetNormal() );
|
||||
// Approach( goal );
|
||||
// FaceTowards( goal );
|
||||
|
||||
m_ladderState = APPROACHING_DESCENDING_LADDER;
|
||||
m_ladderInfo = ladder;
|
||||
m_ladderDismountGoal = dismountGoal;
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
bool PlayerLocomotion::IsUsingLadder( void ) const
|
||||
{
|
||||
return ( m_ladderState != NO_LADDER );
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Rotate body to face towards "target"
|
||||
*/
|
||||
void PlayerLocomotion::FaceTowards( const Vector &target )
|
||||
{
|
||||
// player body follows view direction
|
||||
Vector look( target.x, target.y, GetBot()->GetEntity()->EyePosition().z );
|
||||
|
||||
GetBot()->GetBodyInterface()->AimHeadTowards( look, IBody::BORING, 0.1f, NULL, "Body facing" );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Return position of "feet" - point below centroid of bot at feet level
|
||||
*/
|
||||
const Vector &PlayerLocomotion::GetFeet( void ) const
|
||||
{
|
||||
return m_player->GetAbsOrigin();
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Return current world space velocity
|
||||
*/
|
||||
const Vector &PlayerLocomotion::GetVelocity( void ) const
|
||||
{
|
||||
return m_player->GetAbsVelocity();
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------------------------------
|
||||
float PlayerLocomotion::GetRunSpeed( void ) const
|
||||
{
|
||||
return m_player->MaxSpeed();
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------------------------------
|
||||
float PlayerLocomotion::GetWalkSpeed( void ) const
|
||||
{
|
||||
return 0.5f * m_player->MaxSpeed();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,223 @@
|
||||
// NextBotPlayerLocomotion.h
|
||||
// Locomotor for CBasePlayer derived bots
|
||||
// Author: Michael Booth, November 2005
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
|
||||
#ifndef _NEXT_BOT_PLAYER_LOCOMOTION_H_
|
||||
#define _NEXT_BOT_PLAYER_LOCOMOTION_H_
|
||||
|
||||
#include "NextBot.h"
|
||||
#include "NextBotLocomotionInterface.h"
|
||||
#include "Path/NextBotPathFollow.h"
|
||||
|
||||
class CBasePlayer;
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Basic player locomotion implementation
|
||||
*/
|
||||
class PlayerLocomotion : public ILocomotion
|
||||
{
|
||||
public:
|
||||
DECLARE_CLASS( PlayerLocomotion, ILocomotion );
|
||||
|
||||
PlayerLocomotion( INextBot *bot );
|
||||
virtual ~PlayerLocomotion() { }
|
||||
|
||||
virtual void Reset( void ); // reset to initial state
|
||||
virtual void Update( void ); // update internal state
|
||||
|
||||
virtual void Approach( const Vector &pos, float goalWeight = 1.0f ); // move directly towards the given position
|
||||
virtual void DriveTo( const Vector &pos ); // Move the bot to the precise given position immediately,
|
||||
|
||||
//
|
||||
// ILocomotion modifiers
|
||||
//
|
||||
virtual bool ClimbUpToLedge( const Vector &landingGoal, const Vector &landingForward, const CBaseEntity *obstacle ); // initiate a jump to an adjacent high ledge, return false if climb can't start
|
||||
virtual void JumpAcrossGap( const Vector &landingGoal, const Vector &landingForward ); // initiate a jump across an empty volume of space to far side
|
||||
virtual void Jump( void ); // initiate a simple undirected jump in the air
|
||||
virtual bool IsClimbingOrJumping( void ) const; // is jumping in any form
|
||||
virtual bool IsClimbingUpToLedge( void ) const; // is climbing up to a high ledge
|
||||
virtual bool IsJumpingAcrossGap( void ) const; // is jumping across a gap to the far side
|
||||
|
||||
virtual void Run( void ); // set desired movement speed to running
|
||||
virtual void Walk( void ); // set desired movement speed to walking
|
||||
virtual void Stop( void ); // set desired movement speed to stopped
|
||||
virtual bool IsRunning( void ) const;
|
||||
virtual void SetDesiredSpeed( float speed ); // set desired speed for locomotor movement
|
||||
virtual float GetDesiredSpeed( void ) const; // returns the current desired speed
|
||||
virtual void SetMinimumSpeedLimit( float limit ); // speed cannot drop below this
|
||||
virtual void SetMaximumSpeedLimit( float limit ); // speed cannot rise above this
|
||||
|
||||
virtual bool IsOnGround( void ) const; // return true if standing on something
|
||||
virtual CBaseEntity *GetGround( void ) const; // return the current ground entity or NULL if not on the ground
|
||||
virtual const Vector &GetGroundNormal( void ) const; // surface normal of the ground we are in contact with
|
||||
|
||||
virtual void ClimbLadder( const CNavLadder *ladder, const CNavArea *dismountGoal ); // climb the given ladder to the top and dismount
|
||||
virtual void DescendLadder( const CNavLadder *ladder, const CNavArea *dismountGoal ); // descend the given ladder to the bottom and dismount
|
||||
virtual bool IsUsingLadder( void ) const;
|
||||
virtual bool IsAscendingOrDescendingLadder( void ) const; // we are actually on the ladder right now, either climbing up or down
|
||||
virtual bool IsAbleToAutoCenterOnLadder( void ) const;
|
||||
|
||||
virtual void FaceTowards( const Vector &target ); // rotate body to face towards "target"
|
||||
|
||||
virtual void SetDesiredLean( const QAngle &lean ) { }
|
||||
virtual const QAngle &GetDesiredLean( void ) const { static QAngle junk; return junk; }
|
||||
|
||||
//
|
||||
// ILocomotion information
|
||||
//
|
||||
virtual const Vector &GetFeet( void ) const; // return position of "feet" - point below centroid of bot at feet level
|
||||
|
||||
virtual float GetStepHeight( void ) const; // if delta Z is greater than this, we have to jump to get up
|
||||
virtual float GetMaxJumpHeight( void ) const; // return maximum height of a jump
|
||||
virtual float GetDeathDropHeight( void ) const; // distance at which we will die if we fall
|
||||
|
||||
virtual float GetRunSpeed( void ) const; // get maximum running speed
|
||||
virtual float GetWalkSpeed( void ) const; // get maximum walking speed
|
||||
|
||||
virtual float GetMaxAcceleration( void ) const; // return maximum acceleration of locomotor
|
||||
virtual float GetMaxDeceleration( void ) const; // return maximum deceleration of locomotor
|
||||
|
||||
virtual const Vector &GetVelocity( void ) const; // return current world space velocity
|
||||
|
||||
protected:
|
||||
virtual void AdjustPosture( const Vector &moveGoal );
|
||||
|
||||
private:
|
||||
CBasePlayer *m_player; // the player we are locomoting
|
||||
|
||||
mutable bool m_isJumping;
|
||||
CountdownTimer m_jumpTimer;
|
||||
|
||||
bool m_isClimbingUpToLedge;
|
||||
bool m_isJumpingAcrossGap;
|
||||
Vector m_landingGoal;
|
||||
bool m_hasLeftTheGround;
|
||||
|
||||
float m_desiredSpeed;
|
||||
float m_minSpeedLimit;
|
||||
float m_maxSpeedLimit;
|
||||
|
||||
bool TraverseLadder( void ); // when climbing/descending a ladder
|
||||
|
||||
enum LadderState
|
||||
{
|
||||
NO_LADDER, // not using a ladder
|
||||
APPROACHING_ASCENDING_LADDER,
|
||||
APPROACHING_DESCENDING_LADDER,
|
||||
ASCENDING_LADDER,
|
||||
DESCENDING_LADDER,
|
||||
DISMOUNTING_LADDER_TOP,
|
||||
DISMOUNTING_LADDER_BOTTOM,
|
||||
};
|
||||
|
||||
LadderState m_ladderState;
|
||||
LadderState ApproachAscendingLadder( void );
|
||||
LadderState ApproachDescendingLadder( void );
|
||||
LadderState AscendLadder( void );
|
||||
LadderState DescendLadder( void );
|
||||
LadderState DismountLadderTop( void );
|
||||
LadderState DismountLadderBottom( void );
|
||||
|
||||
const CNavLadder *m_ladderInfo;
|
||||
const CNavArea *m_ladderDismountGoal;
|
||||
CountdownTimer m_ladderTimer; // a "give up" timer if things go awry
|
||||
|
||||
bool IsClimbPossible( INextBot *me, const CBaseEntity *obstacle ) const;
|
||||
};
|
||||
|
||||
|
||||
inline float PlayerLocomotion::GetStepHeight( void ) const
|
||||
{
|
||||
return 18.0f;
|
||||
}
|
||||
|
||||
|
||||
inline float PlayerLocomotion::GetMaxJumpHeight( void ) const
|
||||
{
|
||||
return 57.0f;
|
||||
}
|
||||
|
||||
|
||||
inline float PlayerLocomotion::GetDeathDropHeight( void ) const
|
||||
{
|
||||
return 200.0f;
|
||||
}
|
||||
|
||||
|
||||
inline float PlayerLocomotion::GetMaxAcceleration( void ) const
|
||||
{
|
||||
return 100.0f;
|
||||
}
|
||||
|
||||
inline float PlayerLocomotion::GetMaxDeceleration( void ) const
|
||||
{
|
||||
return 200.0f;
|
||||
}
|
||||
|
||||
inline void PlayerLocomotion::Run( void )
|
||||
{
|
||||
m_desiredSpeed = GetRunSpeed();
|
||||
}
|
||||
|
||||
inline void PlayerLocomotion::Walk( void )
|
||||
{
|
||||
m_desiredSpeed = GetWalkSpeed();
|
||||
}
|
||||
|
||||
inline void PlayerLocomotion::Stop( void )
|
||||
{
|
||||
m_desiredSpeed = 0.0f;
|
||||
}
|
||||
|
||||
inline bool PlayerLocomotion::IsRunning( void ) const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
inline void PlayerLocomotion::SetDesiredSpeed( float speed )
|
||||
{
|
||||
m_desiredSpeed = speed;
|
||||
}
|
||||
|
||||
inline float PlayerLocomotion::GetDesiredSpeed( void ) const
|
||||
{
|
||||
return clamp( m_desiredSpeed, m_minSpeedLimit, m_maxSpeedLimit );
|
||||
}
|
||||
|
||||
inline void PlayerLocomotion::SetMinimumSpeedLimit( float limit )
|
||||
{
|
||||
m_minSpeedLimit = limit;
|
||||
}
|
||||
|
||||
inline void PlayerLocomotion::SetMaximumSpeedLimit( float limit )
|
||||
{
|
||||
m_maxSpeedLimit = limit;
|
||||
}
|
||||
|
||||
inline bool PlayerLocomotion::IsAbleToAutoCenterOnLadder( void ) const
|
||||
{
|
||||
return IsUsingLadder() && (m_ladderState == ASCENDING_LADDER || m_ladderState == DESCENDING_LADDER);
|
||||
}
|
||||
|
||||
inline bool PlayerLocomotion::IsAscendingOrDescendingLadder( void ) const
|
||||
{
|
||||
switch( m_ladderState )
|
||||
{
|
||||
case ASCENDING_LADDER:
|
||||
case DESCENDING_LADDER:
|
||||
case DISMOUNTING_LADDER_TOP:
|
||||
case DISMOUNTING_LADDER_BOTTOM:
|
||||
return true;
|
||||
default:
|
||||
// Explicitly handle the default so that clang knows not to warn us.
|
||||
// warning: enumeration values 'NO_LADDER', 'APPROACHING_ASCENDING_LADDER', and 'APPROACHING_DESCENDING_LADDER' not handled in switch [-Wswitch-enum]
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
#endif // _NEXT_BOT_PLAYER_LOCOMOTION_H_
|
||||
@@ -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 )
|
||||
@@ -0,0 +1,116 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
// simple_bot.h
|
||||
// A mininal example of a NextBotCombatCharacter (ie: non-player) bot
|
||||
// Michael Booth, February 2009
|
||||
|
||||
#ifndef SIMPLE_BOT_H
|
||||
#define SIMPLE_BOT_H
|
||||
|
||||
#include "NextBot.h"
|
||||
#include "NextBotBehavior.h"
|
||||
#include "NextBotGroundLocomotion.h"
|
||||
#include "Path/NextBotPathFollow.h"
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
/**
|
||||
* A Simple Bot
|
||||
*/
|
||||
class CSimpleBot : public NextBotCombatCharacter
|
||||
{
|
||||
public:
|
||||
DECLARE_CLASS( CSimpleBot, NextBotCombatCharacter );
|
||||
|
||||
CSimpleBot();
|
||||
virtual ~CSimpleBot();
|
||||
|
||||
virtual void Precache();
|
||||
virtual void Spawn( void );
|
||||
|
||||
// INextBot
|
||||
DECLARE_INTENTION_INTERFACE( CSimpleBot )
|
||||
virtual NextBotGroundLocomotion *GetLocomotionInterface( void ) const { return m_locomotor; }
|
||||
|
||||
private:
|
||||
NextBotGroundLocomotion *m_locomotor;
|
||||
};
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Functor used with the A* algorithm of NavAreaBuildPath() to determine the "cost" of moving from one area to another.
|
||||
* "Cost" is generally the weighted distance between the centers of the areas. If you want the bot
|
||||
* to avoid an area/ladder/elevator, increase the cost. If you want to disallow an area/ladder/elevator, return -1.
|
||||
*/
|
||||
class CSimpleBotPathCost : public IPathCost
|
||||
{
|
||||
public:
|
||||
CSimpleBotPathCost( CSimpleBot *me )
|
||||
{
|
||||
m_me = me;
|
||||
}
|
||||
|
||||
// return the cost (weighted distance between) of moving from "fromArea" to "area", or -1 if the move is not allowed
|
||||
virtual float operator()( CNavArea *area, CNavArea *fromArea, const CNavLadder *ladder, const CFuncElevator *elevator, float length ) const
|
||||
{
|
||||
if ( fromArea == NULL )
|
||||
{
|
||||
// first area in path, no cost
|
||||
return 0.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( !m_me->GetLocomotionInterface()->IsAreaTraversable( area ) )
|
||||
{
|
||||
// our locomotor says we can't move here
|
||||
return -1.0f;
|
||||
}
|
||||
|
||||
// compute distance traveled along path so far
|
||||
float dist;
|
||||
|
||||
if ( ladder )
|
||||
{
|
||||
dist = ladder->m_length;
|
||||
}
|
||||
else if ( length > 0.0 )
|
||||
{
|
||||
// optimization to avoid recomputing length
|
||||
dist = length;
|
||||
}
|
||||
else
|
||||
{
|
||||
dist = ( area->GetCenter() - fromArea->GetCenter() ).Length();
|
||||
}
|
||||
|
||||
float cost = dist + fromArea->GetCostSoFar();
|
||||
|
||||
// check height change
|
||||
float deltaZ = fromArea->ComputeAdjacentConnectionHeightChange( area );
|
||||
if ( deltaZ >= m_me->GetLocomotionInterface()->GetStepHeight() )
|
||||
{
|
||||
if ( deltaZ >= m_me->GetLocomotionInterface()->GetMaxJumpHeight() )
|
||||
{
|
||||
// too high to reach
|
||||
return -1.0f;
|
||||
}
|
||||
|
||||
// jumping is slower than flat ground
|
||||
const float jumpPenalty = 5.0f;
|
||||
cost += jumpPenalty * dist;
|
||||
}
|
||||
else if ( deltaZ < -m_me->GetLocomotionInterface()->GetDeathDropHeight() )
|
||||
{
|
||||
// too far to drop
|
||||
return -1.0f;
|
||||
}
|
||||
|
||||
return cost;
|
||||
}
|
||||
}
|
||||
|
||||
CSimpleBot *m_me;
|
||||
};
|
||||
|
||||
|
||||
#endif // SIMPLE_BOT_H
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,656 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose: Bot radio chatter system
|
||||
//
|
||||
// $NoKeywords: $
|
||||
//=============================================================================//
|
||||
|
||||
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
|
||||
|
||||
#ifndef CS_BOT_CHATTER_H
|
||||
#define CS_BOT_CHATTER_H
|
||||
|
||||
#pragma warning( disable : 4786 ) // long STL names get truncated in browse info.
|
||||
|
||||
#include "nav_mesh.h"
|
||||
#include "cs_gamestate.h"
|
||||
|
||||
class CCSBot;
|
||||
class BotChatterInterface;
|
||||
|
||||
#define MAX_PLACES_PER_MAP 64
|
||||
|
||||
typedef unsigned int PlaceCriteria;
|
||||
|
||||
typedef unsigned int CountCriteria;
|
||||
#define UNDEFINED_COUNT 0xFFFF
|
||||
#define COUNT_CURRENT_ENEMIES 0xFF // use the number of enemies we see right when we speak
|
||||
#define COUNT_MANY 4 // equal to or greater than this is "many"
|
||||
|
||||
#define UNDEFINED_SUBJECT (-1)
|
||||
|
||||
/// @todo Make Place a class with member fuctions for this
|
||||
const Vector *GetRandomSpotAtPlace( Place place );
|
||||
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* A meme is a unit information that bots use to
|
||||
* transmit information to each other via the radio
|
||||
*/
|
||||
class BotMeme
|
||||
{
|
||||
public:
|
||||
void Transmit( CCSBot *sender ) const; ///< transmit meme to other bots
|
||||
// It is a best practice to always have a virtual destructor in an interface
|
||||
// class. Otherwise if the derived classes have destructors they will not be
|
||||
// called.
|
||||
virtual ~BotMeme() {}
|
||||
virtual void Interpret( CCSBot *sender, CCSBot *receiver ) const = 0; ///< cause the given bot to act on this meme
|
||||
};
|
||||
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
class BotHelpMeme : public BotMeme
|
||||
{
|
||||
public:
|
||||
BotHelpMeme( Place place = UNDEFINED_PLACE )
|
||||
{
|
||||
m_place = place;
|
||||
}
|
||||
|
||||
virtual void Interpret( CCSBot *sender, CCSBot *receiver ) const; ///< cause the given bot to act on this meme
|
||||
|
||||
private:
|
||||
Place m_place; ///< where the help is needed
|
||||
};
|
||||
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
class BotBombsiteStatusMeme : public BotMeme
|
||||
{
|
||||
public:
|
||||
enum StatusType { CLEAR, PLANTED };
|
||||
|
||||
BotBombsiteStatusMeme( int zoneIndex, StatusType status )
|
||||
{
|
||||
m_zoneIndex = zoneIndex;
|
||||
m_status = status;
|
||||
}
|
||||
|
||||
virtual void Interpret( CCSBot *sender, CCSBot *receiver ) const; ///< cause the given bot to act on this meme
|
||||
|
||||
private:
|
||||
int m_zoneIndex; ///< the bombsite
|
||||
StatusType m_status; ///< whether it is cleared or the bomb is there (planted)
|
||||
};
|
||||
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
class BotBombStatusMeme : public BotMeme
|
||||
{
|
||||
public:
|
||||
BotBombStatusMeme( CSGameState::BombState state, const Vector &pos )
|
||||
{
|
||||
m_state = state;
|
||||
m_pos = pos;
|
||||
}
|
||||
|
||||
virtual void Interpret( CCSBot *sender, CCSBot *receiver ) const; ///< cause the given bot to act on this meme
|
||||
|
||||
private:
|
||||
CSGameState::BombState m_state;
|
||||
Vector m_pos;
|
||||
};
|
||||
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
class BotFollowMeme : public BotMeme
|
||||
{
|
||||
public:
|
||||
virtual void Interpret( CCSBot *sender, CCSBot *receiver ) const; ///< cause the given bot to act on this meme
|
||||
};
|
||||
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
class BotDefendHereMeme : public BotMeme
|
||||
{
|
||||
public:
|
||||
BotDefendHereMeme( const Vector &pos )
|
||||
{
|
||||
m_pos = pos;
|
||||
}
|
||||
|
||||
virtual void Interpret( CCSBot *sender, CCSBot *receiver ) const; ///< cause the given bot to act on this meme
|
||||
|
||||
private:
|
||||
Vector m_pos;
|
||||
};
|
||||
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
class BotWhereBombMeme : public BotMeme
|
||||
{
|
||||
public:
|
||||
virtual void Interpret( CCSBot *sender, CCSBot *receiver ) const; ///< cause the given bot to act on this meme
|
||||
};
|
||||
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
class BotRequestReportMeme : public BotMeme
|
||||
{
|
||||
public:
|
||||
virtual void Interpret( CCSBot *sender, CCSBot *receiver ) const; ///< cause the given bot to act on this meme
|
||||
};
|
||||
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
class BotAllHostagesGoneMeme : public BotMeme
|
||||
{
|
||||
public:
|
||||
virtual void Interpret( CCSBot *sender, CCSBot *receiver ) const; ///< cause the given bot to act on this meme
|
||||
};
|
||||
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
class BotHostageBeingTakenMeme : public BotMeme
|
||||
{
|
||||
public:
|
||||
virtual void Interpret( CCSBot *sender, CCSBot *receiver ) const; ///< cause the given bot to act on this meme
|
||||
};
|
||||
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
class BotHeardNoiseMeme : public BotMeme
|
||||
{
|
||||
public:
|
||||
virtual void Interpret( CCSBot *sender, CCSBot *receiver ) const; ///< cause the given bot to act on this meme
|
||||
};
|
||||
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
class BotWarnSniperMeme : public BotMeme
|
||||
{
|
||||
public:
|
||||
virtual void Interpret( CCSBot *sender, CCSBot *receiver ) const; ///< cause the given bot to act on this meme
|
||||
};
|
||||
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
enum BotStatementType
|
||||
{
|
||||
REPORT_VISIBLE_ENEMIES,
|
||||
REPORT_ENEMY_ACTION,
|
||||
REPORT_MY_CURRENT_TASK,
|
||||
REPORT_MY_INTENTION,
|
||||
REPORT_CRITICAL_EVENT,
|
||||
REPORT_REQUEST_HELP,
|
||||
REPORT_REQUEST_INFORMATION,
|
||||
REPORT_ROUND_END,
|
||||
REPORT_MY_PLAN,
|
||||
REPORT_INFORMATION,
|
||||
REPORT_EMOTE,
|
||||
REPORT_ACKNOWLEDGE, ///< affirmative or negative
|
||||
REPORT_ENEMIES_REMAINING,
|
||||
REPORT_FRIENDLY_FIRE,
|
||||
REPORT_KILLED_FRIEND,
|
||||
REPORT_ENEMY_LOST,
|
||||
|
||||
NUM_BOT_STATEMENT_TYPES
|
||||
};
|
||||
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* BotSpeakables are the smallest unit of bot chatter.
|
||||
* They represent a specific wav file of a phrase, and the criteria for which it is useful
|
||||
*/
|
||||
class BotSpeakable
|
||||
{
|
||||
public:
|
||||
BotSpeakable();
|
||||
~BotSpeakable();
|
||||
char *m_phrase;
|
||||
float m_duration;
|
||||
PlaceCriteria m_place;
|
||||
CountCriteria m_count;
|
||||
};
|
||||
typedef CUtlVector< BotSpeakable * > BotSpeakableVector;
|
||||
typedef CUtlVector< BotSpeakableVector * > BotVoiceBankVector;
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* The BotPhrase class is a collection of Speakables associated with a name, ID, and criteria
|
||||
*/
|
||||
class BotPhrase
|
||||
{
|
||||
public:
|
||||
char *GetSpeakable( int bankIndex, float *duration = NULL ) const; ///< return a random speakable and its duration in seconds that meets the current criteria
|
||||
|
||||
// NOTE: Criteria must be set just before the GetSpeakable() call, since they are shared among all bots
|
||||
void ClearCriteria( void ) const;
|
||||
void SetPlaceCriteria( PlaceCriteria place ) const; ///< all returned phrases must have this place criteria
|
||||
void SetCountCriteria( CountCriteria count ) const; ///< all returned phrases must have this count criteria
|
||||
|
||||
const char *GetName( void ) const { return m_name; }
|
||||
const unsigned int GetPlace( void ) const { return m_place; }
|
||||
RadioType GetRadioEquivalent( void ) const { return m_radioEvent; } ///< return equivalent "standard radio" event
|
||||
bool IsImportant( void ) const { return m_isImportant; } ///< return true if this phrase is part of an important statement
|
||||
|
||||
bool IsPlace( void ) const { return m_isPlace; }
|
||||
|
||||
void Randomize( void ); ///< randomly shuffle the speakable order
|
||||
|
||||
private:
|
||||
friend class BotPhraseManager;
|
||||
BotPhrase( bool isPlace );
|
||||
~BotPhrase();
|
||||
|
||||
char *m_name;
|
||||
Place m_place;
|
||||
bool m_isPlace; ///< true if this is a Place phrase
|
||||
RadioType m_radioEvent; ///< equivalent radio event
|
||||
bool m_isImportant; ///< mission-critical statement
|
||||
|
||||
mutable BotVoiceBankVector m_voiceBank; ///< array of voice banks (arrays of speakables)
|
||||
CUtlVector< int > m_count; ///< number of speakables
|
||||
mutable CUtlVector< int > m_index; ///< index of next speakable to return
|
||||
int m_numVoiceBanks; ///< number of voice banks that have been initialized
|
||||
void InitVoiceBank( int bankIndex ); ///< sets up the vector of voice banks for the first bankIndex voice banks
|
||||
|
||||
mutable PlaceCriteria m_placeCriteria;
|
||||
mutable CountCriteria m_countCriteria;
|
||||
};
|
||||
typedef CUtlVector<BotPhrase *> BotPhraseList;
|
||||
|
||||
inline void BotPhrase::ClearCriteria( void ) const
|
||||
{
|
||||
m_placeCriteria = ANY_PLACE;
|
||||
m_countCriteria = UNDEFINED_COUNT;
|
||||
}
|
||||
|
||||
inline void BotPhrase::SetPlaceCriteria( PlaceCriteria place ) const
|
||||
{
|
||||
m_placeCriteria = place;
|
||||
}
|
||||
|
||||
inline void BotPhrase::SetCountCriteria( CountCriteria count ) const
|
||||
{
|
||||
m_countCriteria = count;
|
||||
}
|
||||
|
||||
enum BotChatterOutputType
|
||||
{
|
||||
BOT_CHATTER_RADIO,
|
||||
BOT_CHATTER_VOICE
|
||||
};
|
||||
typedef CUtlVector<BotChatterOutputType> BotOutputList;
|
||||
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* The BotPhraseManager is a singleton that provides an interface to all BotPhrase collections
|
||||
*/
|
||||
class BotPhraseManager
|
||||
{
|
||||
public:
|
||||
BotPhraseManager( void );
|
||||
~BotPhraseManager();
|
||||
|
||||
bool Initialize( const char *filename, int bankIndex ); ///< initialize phrase system from database file for a specific voice bank (0 is the default voice bank)
|
||||
|
||||
void OnRoundRestart( void ); ///< invoked when round resets
|
||||
void OnMapChange( void ); ///< invoked when map changes
|
||||
void Reset( void );
|
||||
|
||||
const BotPhrase *GetPhrase( const char *name ) const; ///< given a name, return the associated phrase collection
|
||||
const BotPhrase *GetPainPhrase( void ) const { return m_painPhrase; } ///< optimization, replaces a static pointer to the phrase
|
||||
const BotPhrase *GetAgreeWithPlanPhrase( void ) const { return m_agreeWithPlanPhrase; } ///< optimization, replaces a static pointer to the phrase
|
||||
|
||||
const BotPhrase *GetPlace( const char *name ) const; ///< given a name, return the associated Place phrase collection
|
||||
const BotPhrase *GetPlace( unsigned int id ) const; ///< given an id, return the associated Place phrase collection
|
||||
|
||||
const BotPhraseList *GetPlaceList( void ) const { return &m_placeList; }
|
||||
|
||||
float GetPlaceStatementInterval( Place where ) const; ///< return time last statement of given type was emitted by a teammate for the given place
|
||||
void ResetPlaceStatementInterval( Place where ); ///< set time of last statement of given type was emitted by a teammate for the given place
|
||||
|
||||
BotChatterOutputType GetOutputType( int voiceBank ) const;
|
||||
|
||||
private:
|
||||
BotPhraseList m_list; ///< master list of all phrase collections
|
||||
BotPhraseList m_placeList; ///< master list of all Place phrases
|
||||
|
||||
BotOutputList m_output;
|
||||
|
||||
const BotPhrase *m_painPhrase;
|
||||
const BotPhrase *m_agreeWithPlanPhrase;
|
||||
|
||||
struct PlaceTimeInfo
|
||||
{
|
||||
Place placeID;
|
||||
IntervalTimer timer;
|
||||
};
|
||||
mutable PlaceTimeInfo m_placeStatementHistory[ MAX_PLACES_PER_MAP ];
|
||||
mutable int m_placeCount;
|
||||
int FindPlaceIndex( Place where ) const;
|
||||
};
|
||||
|
||||
inline int BotPhraseManager::FindPlaceIndex( Place where ) const
|
||||
{
|
||||
for( int i=0; i<m_placeCount; ++i )
|
||||
if (m_placeStatementHistory[i].placeID == where)
|
||||
return i;
|
||||
|
||||
// no such place - allocate it
|
||||
if (m_placeCount < MAX_PLACES_PER_MAP)
|
||||
{
|
||||
m_placeStatementHistory[ ++m_placeCount ].placeID = where;
|
||||
m_placeStatementHistory[ ++m_placeCount ].timer.Invalidate();
|
||||
return m_placeCount-1;
|
||||
}
|
||||
|
||||
// place directory is full
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return time last statement of given type was emitted by a teammate for the given place
|
||||
*/
|
||||
inline float BotPhraseManager::GetPlaceStatementInterval( Place place ) const
|
||||
{
|
||||
int index = FindPlaceIndex( place );
|
||||
|
||||
if (index < 0)
|
||||
return 999999.9f;
|
||||
|
||||
if (index >= m_placeCount)
|
||||
return 999999.9f;
|
||||
|
||||
return m_placeStatementHistory[ index ].timer.GetElapsedTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set time of last statement of given type was emitted by a teammate for the given place
|
||||
*/
|
||||
inline void BotPhraseManager::ResetPlaceStatementInterval( Place place )
|
||||
{
|
||||
int index = FindPlaceIndex( place );
|
||||
|
||||
if (index < 0)
|
||||
return;
|
||||
|
||||
if (index >= m_placeCount)
|
||||
return;
|
||||
|
||||
// update entry
|
||||
m_placeStatementHistory[ index ].timer.Reset();
|
||||
}
|
||||
|
||||
extern BotPhraseManager *TheBotPhrases;
|
||||
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Statements are meaningful collections of phrases
|
||||
*/
|
||||
class BotStatement
|
||||
{
|
||||
public:
|
||||
BotStatement( BotChatterInterface *chatter, BotStatementType type, float expireDuration );
|
||||
~BotStatement();
|
||||
|
||||
BotChatterInterface *GetChatter( void ) const { return m_chatter; }
|
||||
CCSBot *GetOwner( void ) const;
|
||||
|
||||
BotStatementType GetType( void ) const { return m_type; } ///< return the type of statement this is
|
||||
bool IsImportant( void ) const; ///< return true if this statement is "important" and not personality chatter
|
||||
|
||||
bool HasSubject( void ) const { return (m_subject == UNDEFINED_SUBJECT) ? false : true; }
|
||||
void SetSubject( int playerID ) { m_subject = playerID; } ///< who this statement is about
|
||||
int GetSubject( void ) const { return m_subject; } ///< who this statement is about
|
||||
|
||||
bool HasPlace( void ) const { return (GetPlace()) ? true : false; }
|
||||
Place GetPlace( void ) const; ///< if this statement refers to a specific place, return that place
|
||||
void SetPlace( Place where ) { m_place = where; } ///< explicitly set place
|
||||
|
||||
bool HasCount( void ) const; ///< return true if this statement has an associated count
|
||||
|
||||
bool IsRedundant( const BotStatement *say ) const; ///< return true if this statement is the same as the given one
|
||||
bool IsObsolete( void ) const; ///< return true if this statement is no longer appropriate to say
|
||||
void Convert( const BotStatement *say ); ///< possibly change what were going to say base on what teammate is saying
|
||||
|
||||
void AppendPhrase( const BotPhrase *phrase );
|
||||
|
||||
void SetStartTime( float timestamp ) { m_startTime = timestamp; } ///< define the earliest time this statement can be spoken
|
||||
float GetStartTime( void ) const { return m_startTime; }
|
||||
|
||||
enum ConditionType
|
||||
{
|
||||
IS_IN_COMBAT,
|
||||
RADIO_SILENCE,
|
||||
ENEMIES_REMAINING,
|
||||
|
||||
NUM_CONDITIONS
|
||||
};
|
||||
|
||||
void AddCondition( ConditionType condition ); ///< conditions must be true for the statement to be spoken
|
||||
bool IsValid( void ) const; ///< verify all attached conditions
|
||||
|
||||
enum ContextType
|
||||
{
|
||||
CURRENT_ENEMY_COUNT,
|
||||
REMAINING_ENEMY_COUNT,
|
||||
SHORT_DELAY,
|
||||
LONG_DELAY,
|
||||
ACCUMULATE_ENEMIES_DELAY
|
||||
};
|
||||
void AppendPhrase( ContextType contextPhrase ); ///< special phrases that depend on the context
|
||||
|
||||
bool Update( void ); ///< emit statement over time, return false if statement is done
|
||||
bool IsSpeaking( void ) const { return m_isSpeaking; } ///< return true if this statement is currently being spoken
|
||||
float GetTimestamp( void ) const { return m_timestamp; } ///< get time statement was created (but not necessarily started talking)
|
||||
|
||||
void AttachMeme( BotMeme *meme ); ///< attach a meme to this statement, to be transmitted to other friendly bots when spoken
|
||||
|
||||
private:
|
||||
friend class BotChatterInterface;
|
||||
|
||||
BotChatterInterface *m_chatter; ///< the chatter system this statement is part of
|
||||
|
||||
BotStatement *m_next, *m_prev; ///< linked list hooks
|
||||
|
||||
BotStatementType m_type; ///< what kind of statement this is
|
||||
int m_subject; ///< who this subject is about
|
||||
Place m_place; ///< explicit place - note some phrases have implicit places as well
|
||||
BotMeme *m_meme; ///< a statement can only have a single meme for now
|
||||
|
||||
float m_timestamp; ///< time when message was created
|
||||
float m_startTime; ///< the earliest time this statement can be spoken
|
||||
float m_expireTime; ///< time when this statement is no longer valid
|
||||
float m_speakTimestamp; ///< time when message began being spoken
|
||||
bool m_isSpeaking; ///< true if this statement is current being spoken
|
||||
|
||||
float m_nextTime; ///< time for next phrase to begin
|
||||
|
||||
enum { MAX_BOT_PHRASES = 4 };
|
||||
struct
|
||||
{
|
||||
bool isPhrase;
|
||||
union
|
||||
{
|
||||
const BotPhrase *phrase;
|
||||
ContextType context;
|
||||
};
|
||||
}
|
||||
m_statement[ MAX_BOT_PHRASES ];
|
||||
|
||||
enum { MAX_BOT_CONDITIONS = 4 };
|
||||
ConditionType m_condition[ MAX_BOT_CONDITIONS ]; ///< conditions that must be true for the statement to be said
|
||||
int m_conditionCount;
|
||||
|
||||
int m_index; ///< m_index refers to the phrase currently being spoken, or -1 if we havent started yet
|
||||
int m_count;
|
||||
};
|
||||
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* This class defines the interface to the bot radio chatter system
|
||||
*/
|
||||
class BotChatterInterface
|
||||
{
|
||||
public:
|
||||
BotChatterInterface( CCSBot *me );
|
||||
~BotChatterInterface( );
|
||||
|
||||
void Reset( void ); ///< reset to initial state
|
||||
void Update( void ); ///< process ongoing chatter
|
||||
|
||||
/// invoked when event occurs in the game (some events have NULL entities)
|
||||
void OnDeath( void ); ///< invoked when we die
|
||||
|
||||
enum VerbosityType
|
||||
{
|
||||
NORMAL, ///< full chatter
|
||||
MINIMAL, ///< only scenario-critical events
|
||||
RADIO, ///< use the standard radio instead
|
||||
OFF ///< no chatter at all
|
||||
};
|
||||
VerbosityType GetVerbosity( void ) const; ///< return our current level of verbosity
|
||||
|
||||
CCSBot *GetOwner( void ) const { return m_me; }
|
||||
|
||||
bool IsTalking( void ) const; ///< return true if we are currently talking
|
||||
float GetRadioSilenceDuration( void ); ///< return time since any teammate said anything
|
||||
void ResetRadioSilenceDuration( void );
|
||||
|
||||
enum { MUST_ADD = 1 };
|
||||
void AddStatement( BotStatement *statement, bool mustAdd = false ); ///< register a statement for speaking
|
||||
void RemoveStatement( BotStatement *statement ); ///< remove a statement
|
||||
|
||||
BotStatement *GetActiveStatement( void ); ///< returns the statement that is being spoken, or is next to be spoken if no-one is speaking now
|
||||
BotStatement *GetStatement( void ) const; ///< returns our current statement, or NULL if we aren't speaking
|
||||
|
||||
int GetPitch( void ) const { return m_pitch; }
|
||||
|
||||
|
||||
//-- things the bots can say ---------------------------------------------------------------------
|
||||
void Say( const char *phraseName, float lifetime = 3.0f, float delay = 0.0f );
|
||||
|
||||
void AnnouncePlan( const char *phraseName, Place where );
|
||||
void Affirmative( void );
|
||||
void Negative( void );
|
||||
|
||||
void EnemySpotted( void ); ///< report enemy sightings
|
||||
void KilledMyEnemy( int victimID );
|
||||
void EnemiesRemaining( void );
|
||||
|
||||
void SpottedSniper( void );
|
||||
void FriendSpottedSniper( void );
|
||||
|
||||
void Clear( Place where );
|
||||
|
||||
void ReportIn( void ); ///< ask for current situation
|
||||
void ReportingIn( void ); ///< report current situation
|
||||
|
||||
bool NeedBackup( void );
|
||||
void PinnedDown( void );
|
||||
void Scared( void );
|
||||
void HeardNoise( const Vector &pos );
|
||||
void FriendHeardNoise( void );
|
||||
|
||||
void TheyPickedUpTheBomb( void );
|
||||
void GoingToPlantTheBomb( Place where );
|
||||
void BombsiteClear( int zoneIndex );
|
||||
void FoundPlantedBomb( int zoneIndex );
|
||||
void PlantingTheBomb( Place where );
|
||||
void SpottedBomber( CBasePlayer *bomber );
|
||||
void SpottedLooseBomb( CBaseEntity *bomb );
|
||||
void GuardingLooseBomb( CBaseEntity *bomb );
|
||||
void RequestBombLocation( void );
|
||||
|
||||
#define IS_PLAN true
|
||||
void GuardingHostages( Place where, bool isPlan = false );
|
||||
void GuardingHostageEscapeZone( bool isPlan = false );
|
||||
void HostagesBeingTaken( void );
|
||||
void HostagesTaken( void );
|
||||
void TalkingToHostages( void );
|
||||
void EscortingHostages( void );
|
||||
void HostageDown( void );
|
||||
void GuardingBombsite( Place where );
|
||||
|
||||
void CelebrateWin( void );
|
||||
|
||||
void Encourage( const char *phraseName, float repeatInterval = 10.0f, float lifetime = 3.0f ); ///< "encourage" the player to do the scenario
|
||||
|
||||
void KilledFriend( void );
|
||||
void FriendlyFire( void );
|
||||
|
||||
bool SeesAtLeastOneEnemy( void ) const { return m_seeAtLeastOneEnemy; }
|
||||
|
||||
private:
|
||||
BotStatement *m_statementList; ///< list of all active/pending messages for this bot
|
||||
|
||||
void ReportEnemies( void ); ///< track nearby enemy count and generate enemy activity statements
|
||||
bool ShouldSpeak( void ) const; ///< return true if we speaking makes sense now
|
||||
|
||||
CCSBot *m_me; ///< the bot this chatter is for
|
||||
|
||||
bool m_seeAtLeastOneEnemy;
|
||||
float m_timeWhenSawFirstEnemy;
|
||||
bool m_reportedEnemies;
|
||||
bool m_requestedBombLocation; ///< true if we already asked where the bomb has been planted
|
||||
|
||||
int m_pitch;
|
||||
|
||||
static IntervalTimer m_radioSilenceInterval[ 2 ]; ///< one timer for each team
|
||||
|
||||
IntervalTimer m_needBackupInterval;
|
||||
IntervalTimer m_spottedBomberInterval;
|
||||
IntervalTimer m_scaredInterval;
|
||||
IntervalTimer m_planInterval;
|
||||
CountdownTimer m_spottedLooseBombTimer;
|
||||
CountdownTimer m_heardNoiseTimer;
|
||||
CountdownTimer m_escortingHostageTimer;
|
||||
CountdownTimer m_warnSniperTimer;
|
||||
static CountdownTimer m_encourageTimer; ///< timer to know when we can "encourage" the human player again - shared by all bots
|
||||
};
|
||||
|
||||
inline BotChatterInterface::VerbosityType BotChatterInterface::GetVerbosity( void ) const
|
||||
{
|
||||
const char *string = cv_bot_chatter.GetString();
|
||||
|
||||
if (string == NULL)
|
||||
return NORMAL;
|
||||
|
||||
if (string[0] == 'm' || string[0] == 'M')
|
||||
return MINIMAL;
|
||||
|
||||
if (string[0] == 'r' || string[0] == 'R')
|
||||
return RADIO;
|
||||
|
||||
if (string[0] == 'o' || string[0] == 'O')
|
||||
return OFF;
|
||||
|
||||
return NORMAL;
|
||||
}
|
||||
|
||||
|
||||
inline bool BotChatterInterface::IsTalking( void ) const
|
||||
{
|
||||
if (m_statementList)
|
||||
return m_statementList->IsSpeaking();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
inline BotStatement *BotChatterInterface::GetStatement( void ) const
|
||||
{
|
||||
return m_statementList;
|
||||
}
|
||||
|
||||
|
||||
inline void BotChatterInterface::Say( const char *phraseName, float lifetime, float delay )
|
||||
{
|
||||
BotStatement *say = new BotStatement( this, REPORT_MY_INTENTION, lifetime );
|
||||
|
||||
say->AppendPhrase( TheBotPhrases->GetPhrase( phraseName ) );
|
||||
|
||||
if (delay > 0.0f)
|
||||
say->SetStartTime( gpGlobals->curtime + delay );
|
||||
|
||||
AddStatement( say );
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
#endif // CS_BOT_CHATTER_H
|
||||
@@ -0,0 +1,427 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose:
|
||||
//
|
||||
// $NoKeywords: $
|
||||
//=============================================================================//
|
||||
|
||||
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
|
||||
|
||||
#include "cbase.h"
|
||||
#include "cs_gamerules.h"
|
||||
#include "KeyValues.h"
|
||||
|
||||
#include "cs_bot.h"
|
||||
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Checks if the bot can hear the event
|
||||
*/
|
||||
void CCSBot::OnAudibleEvent( IGameEvent *event, CBasePlayer *player, float range, PriorityType priority, bool isHostile, bool isFootstep, const Vector *actualOrigin )
|
||||
{
|
||||
/// @todo Listen to non-player sounds
|
||||
if (player == NULL)
|
||||
return;
|
||||
|
||||
// don't pay attention to noise that friends make
|
||||
if (!IsEnemy( player ))
|
||||
return;
|
||||
|
||||
Vector playerOrigin = GetCentroid( player );
|
||||
Vector myOrigin = GetCentroid( this );
|
||||
|
||||
// If the event occurs far from the triggering player, it may override the origin
|
||||
if ( actualOrigin )
|
||||
{
|
||||
playerOrigin = *actualOrigin;
|
||||
}
|
||||
|
||||
// check if noise is close enough for us to hear
|
||||
const Vector *newNoisePosition = &playerOrigin;
|
||||
float newNoiseDist = (myOrigin - *newNoisePosition).Length();
|
||||
if (newNoiseDist < range)
|
||||
{
|
||||
// we heard the sound
|
||||
if ((IsLocalPlayerWatchingMe() && cv_bot_debug.GetInt() == 3) || cv_bot_debug.GetInt() == 4)
|
||||
{
|
||||
PrintIfWatched( "Heard noise (%s from %s, pri %s, time %3.1f)\n",
|
||||
(FStrEq( "weapon_fire", event->GetName() )) ? "Weapon fire " : "",
|
||||
(player) ? player->GetPlayerName() : "NULL",
|
||||
(priority == PRIORITY_HIGH) ? "HIGH" : ((priority == PRIORITY_MEDIUM) ? "MEDIUM" : "LOW"),
|
||||
gpGlobals->curtime );
|
||||
}
|
||||
|
||||
// should we pay attention to it
|
||||
// if noise timestamp is zero, there is no prior noise
|
||||
if (m_noiseTimestamp > 0.0f)
|
||||
{
|
||||
// only overwrite recent sound if we are louder (closer), or more important - if old noise was long ago, its faded
|
||||
const float shortTermMemoryTime = 3.0f;
|
||||
if (gpGlobals->curtime - m_noiseTimestamp < shortTermMemoryTime)
|
||||
{
|
||||
// prior noise is more important - ignore new one
|
||||
if (priority < m_noisePriority)
|
||||
return;
|
||||
|
||||
float oldNoiseDist = (myOrigin - m_noisePosition).Length();
|
||||
if (newNoiseDist >= oldNoiseDist)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// find the area in which the noise occured
|
||||
/// @todo Better handle when noise occurs off the nav mesh
|
||||
/// @todo Make sure noise area is not through a wall or ceiling from source of noise
|
||||
/// @todo Change GetNavTravelTime to better deal with NULL destination areas
|
||||
CNavArea *noiseArea = TheNavMesh->GetNearestNavArea( *newNoisePosition );
|
||||
if (noiseArea == NULL)
|
||||
{
|
||||
PrintIfWatched( " *** Noise occurred off the nav mesh - ignoring!\n" );
|
||||
return;
|
||||
}
|
||||
|
||||
m_noiseArea = noiseArea;
|
||||
|
||||
// remember noise priority
|
||||
m_noisePriority = priority;
|
||||
|
||||
// randomize noise position in the area a bit - hearing isn't very accurate
|
||||
// the closer the noise is, the more accurate our placement
|
||||
/// @todo Make sure not to pick a position on the opposite side of ourselves.
|
||||
const float maxErrorRadius = 400.0f;
|
||||
const float maxHearingRange = 2000.0f;
|
||||
float errorRadius = maxErrorRadius * newNoiseDist/maxHearingRange;
|
||||
|
||||
m_noisePosition.x = newNoisePosition->x + RandomFloat( -errorRadius, errorRadius );
|
||||
m_noisePosition.y = newNoisePosition->y + RandomFloat( -errorRadius, errorRadius );
|
||||
|
||||
// note the *travel distance* to the noise
|
||||
m_noiseTravelDistance = GetTravelDistanceToPlayer( (CCSPlayer *)player );
|
||||
|
||||
// make sure noise position remains in the same area
|
||||
m_noiseArea->GetClosestPointOnArea( m_noisePosition, &m_noisePosition );
|
||||
|
||||
// note when we heard the noise
|
||||
m_noiseTimestamp = gpGlobals->curtime;
|
||||
|
||||
// if we hear a nearby enemy, become alert
|
||||
const float nearbyNoiseRange = 1000.0f;
|
||||
if (m_noiseTravelDistance < nearbyNoiseRange && m_noiseTravelDistance > 0.0f)
|
||||
{
|
||||
BecomeAlert();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
void CCSBot::OnHEGrenadeDetonate( IGameEvent *event )
|
||||
{
|
||||
if ( !IsAlive() )
|
||||
return;
|
||||
|
||||
// don't react to our own events
|
||||
CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
|
||||
if ( player == this )
|
||||
return;
|
||||
|
||||
OnAudibleEvent( event, player, 99999.0f, PRIORITY_HIGH, true ); // hegrenade_detonate
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
void CCSBot::OnFlashbangDetonate( IGameEvent *event )
|
||||
{
|
||||
if ( !IsAlive() )
|
||||
return;
|
||||
|
||||
// don't react to our own events
|
||||
CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
|
||||
if ( player == this )
|
||||
return;
|
||||
|
||||
OnAudibleEvent( event, player, 1000.0f, PRIORITY_LOW, true ); // flashbang_detonate
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
void CCSBot::OnSmokeGrenadeDetonate( IGameEvent *event )
|
||||
{
|
||||
if ( !IsAlive() )
|
||||
return;
|
||||
|
||||
// don't react to our own events
|
||||
CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
|
||||
if ( player == this )
|
||||
return;
|
||||
|
||||
OnAudibleEvent( event, player, 1000.0f, PRIORITY_LOW, true ); // smokegrenade_detonate
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
void CCSBot::OnGrenadeBounce( IGameEvent *event )
|
||||
{
|
||||
if ( !IsAlive() )
|
||||
return;
|
||||
|
||||
// don't react to our own events
|
||||
CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
|
||||
if ( player == this )
|
||||
return;
|
||||
|
||||
OnAudibleEvent( event, player, 500.0f, PRIORITY_LOW, true ); // grenade_bounce
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
void CCSBot::OnBulletImpact( IGameEvent *event )
|
||||
{
|
||||
if ( !IsAlive() )
|
||||
return;
|
||||
|
||||
// don't react to our own events
|
||||
CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
|
||||
if ( player == this )
|
||||
return;
|
||||
|
||||
// Construct an origin for the sound, since it can be far from the originating player
|
||||
Vector actualOrigin;
|
||||
actualOrigin.x = event->GetFloat( "x", 0.0f );
|
||||
actualOrigin.y = event->GetFloat( "y", 0.0f );
|
||||
actualOrigin.z = event->GetFloat( "z", 0.0f );
|
||||
|
||||
/// @todo Ignoring bullet impact events for now - we dont want bots to look directly at them!
|
||||
//OnAudibleEvent( event, player, 1100.0f, PRIORITY_MEDIUM, true, false, &actualOrigin ); // bullet_impact
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
void CCSBot::OnBreakProp( IGameEvent *event )
|
||||
{
|
||||
if ( !IsAlive() )
|
||||
return;
|
||||
|
||||
// don't react to our own events
|
||||
CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
|
||||
if ( player == this )
|
||||
return;
|
||||
|
||||
OnAudibleEvent( event, player, 1100.0f, PRIORITY_MEDIUM, true ); // break_prop
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
void CCSBot::OnBreakBreakable( IGameEvent *event )
|
||||
{
|
||||
if ( !IsAlive() )
|
||||
return;
|
||||
|
||||
// don't react to our own events
|
||||
CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
|
||||
if ( player == this )
|
||||
return;
|
||||
|
||||
OnAudibleEvent( event, player, 1100.0f, PRIORITY_MEDIUM, true ); // break_glass
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
void CCSBot::OnDoorMoving( IGameEvent *event )
|
||||
{
|
||||
if ( !IsAlive() )
|
||||
return;
|
||||
|
||||
// don't react to our own events
|
||||
CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
|
||||
if ( player == this )
|
||||
return;
|
||||
|
||||
OnAudibleEvent( event, player, 1100.0f, PRIORITY_MEDIUM, false ); // door_moving
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
void CCSBot::OnHostageFollows( IGameEvent *event )
|
||||
{
|
||||
if ( !IsAlive() )
|
||||
return;
|
||||
|
||||
// don't react to our own events
|
||||
CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
|
||||
if ( player == this )
|
||||
return;
|
||||
|
||||
// player_follows needs a player
|
||||
if (player == NULL)
|
||||
return;
|
||||
|
||||
// don't pay attention to noise that friends make
|
||||
if (!IsEnemy( player ))
|
||||
return;
|
||||
|
||||
Vector playerOrigin = GetCentroid( player );
|
||||
Vector myOrigin = GetCentroid( this );
|
||||
const float range = 1200.0f;
|
||||
|
||||
// this is here so T's not only act on the noise, but look at it, too
|
||||
if (GetTeamNumber() == TEAM_TERRORIST)
|
||||
{
|
||||
// make sure we can hear the noise
|
||||
if ((playerOrigin - myOrigin).IsLengthGreaterThan( range ))
|
||||
return;
|
||||
|
||||
// tell our teammates that the hostages are being taken
|
||||
GetChatter()->HostagesBeingTaken();
|
||||
|
||||
// only move if we hear them being rescued and can't see any hostages
|
||||
if (GetGameState()->GetNearestVisibleFreeHostage() == NULL)
|
||||
{
|
||||
// since we are guarding the hostages, presumably we know where they are
|
||||
// if we're close enough to "hear" this event, either go to where the event occured,
|
||||
// or head for an escape zone to head them off
|
||||
if (GetTask() != CCSBot::GUARD_HOSTAGE_RESCUE_ZONE)
|
||||
{
|
||||
//const float headOffChance = 33.3f;
|
||||
if (true) // || RandomFloat( 0, 100 ) < headOffChance)
|
||||
{
|
||||
// head them off at a rescue zone
|
||||
if (GuardRandomZone())
|
||||
{
|
||||
SetTask( CCSBot::GUARD_HOSTAGE_RESCUE_ZONE );
|
||||
SetDisposition( CCSBot::OPPORTUNITY_FIRE );
|
||||
PrintIfWatched( "Trying to beat them to an escape zone!\n" );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
SetTask( SEEK_AND_DESTROY );
|
||||
StandUp();
|
||||
Run();
|
||||
MoveTo( playerOrigin, FASTEST_ROUTE );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// CT's don't care about this noise
|
||||
return;
|
||||
}
|
||||
|
||||
OnAudibleEvent( event, player, range, PRIORITY_MEDIUM, false ); // hostage_follows
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
void CCSBot::OnRoundEnd( IGameEvent *event )
|
||||
{
|
||||
// Morale adjustments happen even for dead players
|
||||
int winner = event->GetInt( "winner" );
|
||||
switch ( winner )
|
||||
{
|
||||
case WINNER_TER:
|
||||
if (GetTeamNumber() == TEAM_CT)
|
||||
{
|
||||
DecreaseMorale();
|
||||
}
|
||||
else
|
||||
{
|
||||
IncreaseMorale();
|
||||
}
|
||||
break;
|
||||
|
||||
case WINNER_CT:
|
||||
if (GetTeamNumber() == TEAM_CT)
|
||||
{
|
||||
IncreaseMorale();
|
||||
}
|
||||
else
|
||||
{
|
||||
DecreaseMorale();
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
m_gameState.OnRoundEnd( event );
|
||||
|
||||
if ( !IsAlive() )
|
||||
return;
|
||||
|
||||
if ( event->GetInt( "winner" ) == WINNER_TER )
|
||||
{
|
||||
if (GetTeamNumber() == TEAM_TERRORIST)
|
||||
GetChatter()->CelebrateWin();
|
||||
}
|
||||
else if ( event->GetInt( "winner" ) == WINNER_CT )
|
||||
{
|
||||
if (GetTeamNumber() == TEAM_CT)
|
||||
GetChatter()->CelebrateWin();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
void CCSBot::OnRoundStart( IGameEvent *event )
|
||||
{
|
||||
m_gameState.OnRoundStart( event );
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
void CCSBot::OnHostageRescuedAll( IGameEvent *event )
|
||||
{
|
||||
m_gameState.OnHostageRescuedAll( event );
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
void CCSBot::OnNavBlocked( IGameEvent *event )
|
||||
{
|
||||
if ( event->GetBool( "blocked" ) )
|
||||
{
|
||||
unsigned int areaID = event->GetInt( "area" );
|
||||
if ( areaID )
|
||||
{
|
||||
// An area was blocked off. Reset our path if it has this area on it.
|
||||
for( int i=0; i<m_pathLength; ++i )
|
||||
{
|
||||
const ConnectInfo *info = &m_path[ i ];
|
||||
if ( info->area && info->area->GetID() == areaID )
|
||||
{
|
||||
DestroyPath();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Invoked when bot enters a nav area
|
||||
*/
|
||||
void CCSBot::OnEnteredNavArea( CNavArea *newArea )
|
||||
{
|
||||
// assume that we "clear" an area of enemies when we enter it
|
||||
newArea->SetClearedTimestamp( GetTeamNumber()-1 );
|
||||
|
||||
// if we just entered a 'stop' area, set the flag
|
||||
if ( newArea->GetAttributes() & NAV_MESH_STOP )
|
||||
{
|
||||
m_isStopping = true;
|
||||
}
|
||||
|
||||
/// @todo Flag these areas as spawn areas during load
|
||||
if (IsAtEnemySpawn())
|
||||
{
|
||||
m_hasVisitedEnemySpawn = true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose:
|
||||
//
|
||||
// $NoKeywords: $
|
||||
//=============================================================================//
|
||||
|
||||
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
|
||||
|
||||
#include "cbase.h"
|
||||
#include "cs_gamerules.h"
|
||||
#include "KeyValues.h"
|
||||
|
||||
#include "cs_bot.h"
|
||||
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
void CCSBot::OnBombPickedUp( IGameEvent *event )
|
||||
{
|
||||
if ( !IsAlive() )
|
||||
return;
|
||||
|
||||
// don't react to our own events
|
||||
CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
|
||||
if ( player == this )
|
||||
return;
|
||||
|
||||
if (GetTeamNumber() == TEAM_CT && player)
|
||||
{
|
||||
// check if we're close enough to hear it
|
||||
const float bombPickupHearRangeSq = 1000.0f * 1000.0f;
|
||||
Vector myOrigin = GetCentroid( this );
|
||||
|
||||
if ((myOrigin - player->GetAbsOrigin()).LengthSqr() < bombPickupHearRangeSq)
|
||||
{
|
||||
GetChatter()->TheyPickedUpTheBomb();
|
||||
GetGameState()->UpdateBomber( player->GetAbsOrigin() );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
void CCSBot::OnBombPlanted( IGameEvent *event )
|
||||
{
|
||||
m_gameState.OnBombPlanted( event );
|
||||
|
||||
if ( !IsAlive() )
|
||||
return;
|
||||
|
||||
// don't react to our own events
|
||||
CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
|
||||
if ( player == this )
|
||||
return;
|
||||
|
||||
// if we're a TEAM_CT, forget what we're doing and go after the bomb
|
||||
if (GetTeamNumber() == TEAM_CT)
|
||||
{
|
||||
Idle();
|
||||
}
|
||||
|
||||
// if we are following someone, stop following
|
||||
if (IsFollowing())
|
||||
{
|
||||
StopFollowing();
|
||||
Idle();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
void CCSBot::OnBombBeep( IGameEvent *event )
|
||||
{
|
||||
if ( !IsAlive() )
|
||||
return;
|
||||
|
||||
// don't react to our own events
|
||||
CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
|
||||
if ( player == this )
|
||||
return;
|
||||
|
||||
CBaseEntity *entity = UTIL_EntityByIndex( event->GetInt( "entindex" ) );
|
||||
Vector myOrigin = GetCentroid( this );
|
||||
|
||||
// if we don't know where the bomb is, but heard it beep, we've discovered it
|
||||
if (GetGameState()->IsPlantedBombLocationKnown() == false && entity)
|
||||
{
|
||||
// check if we're close enough to hear it
|
||||
const float bombBeepHearRangeSq = 1500.0f * 1500.0f;
|
||||
if ((myOrigin - entity->GetAbsOrigin()).LengthSqr() < bombBeepHearRangeSq)
|
||||
{
|
||||
// radio the news to our team
|
||||
if (GetTeamNumber() == TEAM_CT && GetGameState()->GetPlantedBombsite() == CSGameState::UNKNOWN)
|
||||
{
|
||||
const CCSBotManager::Zone *zone = TheCSBots()->GetZone( entity->GetAbsOrigin() );
|
||||
if (zone)
|
||||
GetChatter()->FoundPlantedBomb( zone->m_index );
|
||||
}
|
||||
|
||||
// remember where the bomb is
|
||||
GetGameState()->UpdatePlantedBomb( entity->GetAbsOrigin() );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
void CCSBot::OnBombDefuseBegin( IGameEvent *event )
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
void CCSBot::OnBombDefused( IGameEvent *event )
|
||||
{
|
||||
m_gameState.OnBombDefused( event );
|
||||
|
||||
if ( !IsAlive() )
|
||||
return;
|
||||
|
||||
// don't react to our own events
|
||||
CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
|
||||
if ( player == this )
|
||||
return;
|
||||
|
||||
if (GetTeamNumber() == TEAM_CT)
|
||||
{
|
||||
if (TheCSBots()->GetBombTimeLeft() < 2.0f)
|
||||
GetChatter()->Say( "BarelyDefused" );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
void CCSBot::OnBombDefuseAbort( IGameEvent *event )
|
||||
{
|
||||
if ( !IsAlive() )
|
||||
return;
|
||||
|
||||
// don't react to our own events
|
||||
CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
|
||||
if ( player == this )
|
||||
return;
|
||||
|
||||
PrintIfWatched( "BOMB DEFUSE ABORTED\n" );
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
void CCSBot::OnBombExploded( IGameEvent *event )
|
||||
{
|
||||
m_gameState.OnBombExploded( event );
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,227 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose:
|
||||
//
|
||||
// $NoKeywords: $
|
||||
//=============================================================================//
|
||||
|
||||
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
|
||||
|
||||
#include "cbase.h"
|
||||
#include "cs_gamerules.h"
|
||||
#include "KeyValues.h"
|
||||
|
||||
#include "cs_bot.h"
|
||||
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
void CCSBot::OnPlayerDeath( IGameEvent *event )
|
||||
{
|
||||
if ( !IsAlive() )
|
||||
return;
|
||||
|
||||
// don't react to our own events
|
||||
CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
|
||||
if ( player == this )
|
||||
return;
|
||||
|
||||
Vector playerOrigin = (player) ? GetCentroid( player ) : Vector( 0, 0, 0 );
|
||||
|
||||
CBasePlayer *other = UTIL_PlayerByUserId( event->GetInt( "attacker" ) );
|
||||
CBasePlayer *victim = player;
|
||||
|
||||
CBasePlayer *killer = (other && other->IsPlayer()) ? static_cast<CBasePlayer *>( other ) : NULL;
|
||||
|
||||
// if the human player died in the single player game, tell the team
|
||||
if (CSGameRules()->IsCareer() && victim && !victim->IsBot() && victim->GetTeamNumber() == GetTeamNumber())
|
||||
{
|
||||
GetChatter()->Say( "CommanderDown", 20.0f );
|
||||
}
|
||||
|
||||
// keep track of the last player we killed
|
||||
if (killer == this)
|
||||
{
|
||||
m_lastVictimID = victim ? victim->entindex() : 0;
|
||||
}
|
||||
|
||||
// react to teammate death
|
||||
if (victim && victim->GetTeamNumber() == GetTeamNumber())
|
||||
{
|
||||
// note time of death
|
||||
m_friendDeathTimestamp = gpGlobals->curtime;
|
||||
|
||||
// chastise friendly fire from humans
|
||||
if (killer && !killer->IsBot() && killer->GetTeamNumber() == GetTeamNumber() && killer != this)
|
||||
{
|
||||
GetChatter()->KilledFriend();
|
||||
}
|
||||
|
||||
if (IsAttacking())
|
||||
{
|
||||
if (GetTimeSinceLastSawEnemy() > 0.4f)
|
||||
{
|
||||
PrintIfWatched( "Rethinking my attack due to teammate death\n" );
|
||||
|
||||
// allow us to sneak past windows, doors, etc
|
||||
IgnoreEnemies( 1.0f );
|
||||
|
||||
// move to last known position of enemy - this could cause us to flank if
|
||||
// the danger has changed due to our teammate's recent death
|
||||
SetTask( MOVE_TO_LAST_KNOWN_ENEMY_POSITION, GetBotEnemy() );
|
||||
MoveTo( GetLastKnownEnemyPosition() );
|
||||
return;
|
||||
}
|
||||
}
|
||||
else // not attacking
|
||||
{
|
||||
//
|
||||
// If we just saw a nearby friend die, and we haven't yet acquired an enemy
|
||||
// automatically acquire our dead friend's killer
|
||||
//
|
||||
if (GetDisposition() == ENGAGE_AND_INVESTIGATE || GetDisposition() == OPPORTUNITY_FIRE)
|
||||
{
|
||||
CBasePlayer *other = UTIL_PlayerByUserId( event->GetInt( "attacker" ) );
|
||||
|
||||
// check that attacker is an enemy (for friendly fire, etc)
|
||||
if (other && other->IsPlayer())
|
||||
{
|
||||
CCSPlayer *killer = static_cast<CCSPlayer *>( other );
|
||||
if (killer->GetTeamNumber() != GetTeamNumber())
|
||||
{
|
||||
// check if we saw our friend die - dont check FOV - assume we're aware of our surroundings in combat
|
||||
// snipers stay put
|
||||
if (!IsSniper() && IsVisible( playerOrigin ))
|
||||
{
|
||||
// people are dying - we should hurry
|
||||
Hurry( RandomFloat( 10.0f, 15.0f ) );
|
||||
|
||||
// if we're hiding with only our knife, be a little more cautious
|
||||
const float knifeAmbushChance = 50.0f;
|
||||
if (!IsHiding() || !IsUsingKnife() || RandomFloat( 0, 100 ) < knifeAmbushChance)
|
||||
{
|
||||
PrintIfWatched( "Attacking our friend's killer!\n" );
|
||||
Attack( killer );
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// if friend was far away and we haven't seen an enemy in awhile, go to where our friend was killed
|
||||
const float longHidingTime = 20.0f;
|
||||
if (IsHunting() || IsInvestigatingNoise() || (IsHiding() && GetTask() != FOLLOW && GetHidingTime() > longHidingTime))
|
||||
{
|
||||
const float someTime = 10.0f;
|
||||
const float farAway = 750.0f;
|
||||
if (GetTimeSinceLastSawEnemy() > someTime && (playerOrigin - GetAbsOrigin()).IsLengthGreaterThan( farAway ))
|
||||
{
|
||||
PrintIfWatched( "Checking out where our friend was killed\n" );
|
||||
MoveTo( playerOrigin, FASTEST_ROUTE );
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else // an enemy was killed
|
||||
{
|
||||
// forget our current noise - it may have come from the now dead enemy
|
||||
ForgetNoise();
|
||||
|
||||
if (killer && killer->GetTeamNumber() == GetTeamNumber())
|
||||
{
|
||||
// only chatter about enemy kills if we see them occur, and they were the last one we see
|
||||
if (GetNearbyEnemyCount() <= 1)
|
||||
{
|
||||
// report if number of enemies left is few and we killed the last one we saw locally
|
||||
GetChatter()->EnemiesRemaining();
|
||||
|
||||
Vector victimOrigin = (victim) ? GetCentroid( victim ) : Vector( 0, 0, 0 );
|
||||
if (IsVisible( victimOrigin, CHECK_FOV ))
|
||||
{
|
||||
// congratulate teammates on their kills
|
||||
if (killer && killer != this)
|
||||
{
|
||||
float delay = RandomFloat( 2.0f, 3.0f );
|
||||
if (killer->IsBot())
|
||||
{
|
||||
if (RandomFloat( 0.0f, 100.0f ) < 40.0f)
|
||||
GetChatter()->Say( "NiceShot", 3.0f, delay );
|
||||
}
|
||||
else
|
||||
{
|
||||
// humans get the honorific
|
||||
if (CSGameRules()->IsCareer())
|
||||
GetChatter()->Say( "NiceShotCommander", 3.0f, delay );
|
||||
else
|
||||
GetChatter()->Say( "NiceShotSir", 3.0f, delay );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
void CCSBot::OnPlayerRadio( IGameEvent *event )
|
||||
{
|
||||
if ( !IsAlive() )
|
||||
return;
|
||||
|
||||
// don't react to our own events
|
||||
CCSPlayer *player = ToCSPlayer( UTIL_PlayerByUserId( event->GetInt( "userid" ) ) );
|
||||
if ( player == this )
|
||||
return;
|
||||
|
||||
//
|
||||
// Process radio events from our team
|
||||
//
|
||||
if (player && player->GetTeamNumber() == GetTeamNumber() )
|
||||
{
|
||||
/// @todo Distinguish between radio commands and responses
|
||||
RadioType radioEvent = (RadioType)event->GetInt( "slot" );
|
||||
|
||||
if (radioEvent != RADIO_INVALID && radioEvent != RADIO_AFFIRMATIVE && radioEvent != RADIO_NEGATIVE && radioEvent != RADIO_REPORTING_IN)
|
||||
{
|
||||
m_lastRadioCommand = radioEvent;
|
||||
m_lastRadioRecievedTimestamp = gpGlobals->curtime;
|
||||
m_radioSubject = player;
|
||||
m_radioPosition = GetCentroid( player );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
void CCSBot::OnPlayerFallDamage( IGameEvent *event )
|
||||
{
|
||||
if ( !IsAlive() )
|
||||
return;
|
||||
|
||||
// don't react to our own events
|
||||
CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
|
||||
if ( player == this )
|
||||
return;
|
||||
|
||||
OnAudibleEvent( event, player, 1100.0f, PRIORITY_LOW, false ); // player_falldamage
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
void CCSBot::OnPlayerFootstep( IGameEvent *event )
|
||||
{
|
||||
if ( !IsAlive() )
|
||||
return;
|
||||
|
||||
// don't react to our own events
|
||||
CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
|
||||
if ( player == this )
|
||||
return;
|
||||
|
||||
OnAudibleEvent( event, player, 1100.0f, PRIORITY_LOW, false, IS_FOOTSTEP ); // player_footstep
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose:
|
||||
//
|
||||
// $NoKeywords: $
|
||||
//=============================================================================//
|
||||
|
||||
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
|
||||
|
||||
#include "cbase.h"
|
||||
#include "cs_gamerules.h"
|
||||
#include "KeyValues.h"
|
||||
|
||||
#include "cs_bot.h"
|
||||
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
void CCSBot::OnWeaponFire( IGameEvent *event )
|
||||
{
|
||||
if ( !IsAlive() )
|
||||
return;
|
||||
|
||||
// don't react to our own events
|
||||
CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
|
||||
if ( player == this )
|
||||
return;
|
||||
|
||||
// for knife fighting - if our victim is attacking or reloading, rush him
|
||||
/// @todo Propagate events into active state
|
||||
if (GetEnemy() == player && IsUsingKnife())
|
||||
{
|
||||
ForceRun( 5.0f );
|
||||
}
|
||||
|
||||
const float ShortRange = 1000.0f;
|
||||
const float NormalRange = 2000.0f;
|
||||
|
||||
float range;
|
||||
|
||||
/// @todo Check weapon type (knives are pretty quiet)
|
||||
/// @todo Use actual volume, account for silencers, etc.
|
||||
CWeaponCSBase *weapon = (CWeaponCSBase *)((player)?player->GetActiveWeapon():NULL);
|
||||
|
||||
if (weapon == NULL)
|
||||
return;
|
||||
|
||||
switch( weapon->GetWeaponID() )
|
||||
{
|
||||
// silent "firing"
|
||||
case WEAPON_HEGRENADE:
|
||||
case WEAPON_SMOKEGRENADE:
|
||||
case WEAPON_FLASHBANG:
|
||||
case WEAPON_SHIELDGUN:
|
||||
case WEAPON_C4:
|
||||
return;
|
||||
|
||||
// quiet
|
||||
case WEAPON_KNIFE:
|
||||
case WEAPON_TMP:
|
||||
range = ShortRange;
|
||||
break;
|
||||
|
||||
// M4A1 - check for silencer
|
||||
case WEAPON_M4A1:
|
||||
{
|
||||
if (weapon->IsSilenced())
|
||||
{
|
||||
range = ShortRange;
|
||||
}
|
||||
else
|
||||
{
|
||||
range = NormalRange;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// USP - check for silencer
|
||||
case WEAPON_USP:
|
||||
{
|
||||
if (weapon->IsSilenced())
|
||||
{
|
||||
range = ShortRange;
|
||||
}
|
||||
else
|
||||
{
|
||||
range = NormalRange;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// loud
|
||||
case WEAPON_AWP:
|
||||
range = 99999.0f;
|
||||
break;
|
||||
|
||||
// normal
|
||||
default:
|
||||
range = NormalRange;
|
||||
break;
|
||||
}
|
||||
|
||||
OnAudibleEvent( event, player, range, PRIORITY_HIGH, true ); // weapon_fire
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
void CCSBot::OnWeaponFireOnEmpty( IGameEvent *event )
|
||||
{
|
||||
if ( !IsAlive() )
|
||||
return;
|
||||
|
||||
// don't react to our own events
|
||||
CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
|
||||
if ( player == this )
|
||||
return;
|
||||
|
||||
// for knife fighting - if our victim is attacking or reloading, rush him
|
||||
/// @todo Propagate events into active state
|
||||
if (GetEnemy() == player && IsUsingKnife())
|
||||
{
|
||||
ForceRun( 5.0f );
|
||||
}
|
||||
|
||||
OnAudibleEvent( event, player, 1100.0f, PRIORITY_LOW, false ); // weapon_fire_on_empty
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
void CCSBot::OnWeaponReload( IGameEvent *event )
|
||||
{
|
||||
if ( !IsAlive() )
|
||||
return;
|
||||
|
||||
// don't react to our own events
|
||||
CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
|
||||
if ( player == this )
|
||||
return;
|
||||
|
||||
// for knife fighting - if our victim is attacking or reloading, rush him
|
||||
/// @todo Propagate events into active state
|
||||
if (GetEnemy() == player && IsUsingKnife())
|
||||
{
|
||||
ForceRun( 5.0f );
|
||||
}
|
||||
|
||||
OnAudibleEvent( event, player, 1100.0f, PRIORITY_LOW, false ); // weapon_reload
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
void CCSBot::OnWeaponZoom( IGameEvent *event )
|
||||
{
|
||||
if ( !IsAlive() )
|
||||
return;
|
||||
|
||||
// don't react to our own events
|
||||
CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
|
||||
if ( player == this )
|
||||
return;
|
||||
|
||||
OnAudibleEvent( event, player, 1100.0f, PRIORITY_LOW, false ); // weapon_zoom
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,359 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose:
|
||||
//
|
||||
// $NoKeywords: $
|
||||
//=============================================================================//
|
||||
|
||||
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
|
||||
|
||||
#include "cbase.h"
|
||||
#include "cs_bot.h"
|
||||
#include "cs_shareddefs.h"
|
||||
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
#pragma warning( disable : 4355 ) // warning 'this' used in base member initializer list - we're using it safely
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
static void PrefixChanged( IConVar *c, const char *oldPrefix, float flOldValue )
|
||||
{
|
||||
if ( TheCSBots() && TheCSBots()->IsServerActive() )
|
||||
{
|
||||
for( int i = 1; i <= gpGlobals->maxClients; ++i )
|
||||
{
|
||||
CBasePlayer *player = static_cast<CBasePlayer *>( UTIL_PlayerByIndex( i ) );
|
||||
|
||||
if ( !player )
|
||||
continue;
|
||||
|
||||
if ( !player->IsBot() || !IsEntityValid( player ) )
|
||||
continue;
|
||||
|
||||
CCSBot *bot = dynamic_cast< CCSBot * >( player );
|
||||
|
||||
if ( !bot )
|
||||
continue;
|
||||
|
||||
// set the bot's name
|
||||
char botName[MAX_PLAYER_NAME_LENGTH];
|
||||
UTIL_ConstructBotNetName( botName, MAX_PLAYER_NAME_LENGTH, bot->GetProfile() );
|
||||
|
||||
engine->SetFakeClientConVarValue( bot->edict(), "name", botName );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ConVar cv_bot_traceview( "bot_traceview", "0", FCVAR_REPLICATED | FCVAR_CHEAT, "For internal testing purposes." );
|
||||
ConVar cv_bot_stop( "bot_stop", "0", FCVAR_REPLICATED | FCVAR_CHEAT, "If nonzero, immediately stops all bot processing." );
|
||||
ConVar cv_bot_show_nav( "bot_show_nav", "0", FCVAR_REPLICATED | FCVAR_CHEAT, "For internal testing purposes." );
|
||||
ConVar cv_bot_walk( "bot_walk", "0", FCVAR_REPLICATED, "If nonzero, bots can only walk, not run." );
|
||||
ConVar cv_bot_difficulty( "bot_difficulty", "1", FCVAR_REPLICATED, "Defines the skill of bots joining the game. Values are: 0=easy, 1=normal, 2=hard, 3=expert." );
|
||||
ConVar cv_bot_debug( "bot_debug", "0", FCVAR_REPLICATED | FCVAR_CHEAT, "For internal testing purposes." );
|
||||
ConVar cv_bot_debug_target( "bot_debug_target", "0", FCVAR_REPLICATED | FCVAR_CHEAT, "For internal testing purposes." );
|
||||
ConVar cv_bot_quota( "bot_quota", "0", FCVAR_REPLICATED | FCVAR_NOTIFY, "Determines the total number of bots in the game." );
|
||||
ConVar cv_bot_quota_mode( "bot_quota_mode", "normal", FCVAR_REPLICATED, "Determines the type of quota.\nAllowed values: 'normal', 'fill', and 'match'.\nIf 'fill', the server will adjust bots to keep N players in the game, where N is bot_quota.\nIf 'match', the server will maintain a 1:N ratio of humans to bots, where N is bot_quota." );
|
||||
ConVar cv_bot_prefix( "bot_prefix", "", FCVAR_REPLICATED, "This string is prefixed to the name of all bots that join the game.\n<difficulty> will be replaced with the bot's difficulty.\n<weaponclass> will be replaced with the bot's desired weapon class.\n<skill> will be replaced with a 0-100 representation of the bot's skill.", PrefixChanged );
|
||||
ConVar cv_bot_allow_rogues( "bot_allow_rogues", "1", FCVAR_REPLICATED, "If nonzero, bots may occasionally go 'rogue'. Rogue bots do not obey radio commands, nor pursue scenario goals." );
|
||||
ConVar cv_bot_allow_pistols( "bot_allow_pistols", "1", FCVAR_REPLICATED, "If nonzero, bots may use pistols." );
|
||||
ConVar cv_bot_allow_shotguns( "bot_allow_shotguns", "1", FCVAR_REPLICATED, "If nonzero, bots may use shotguns." );
|
||||
ConVar cv_bot_allow_sub_machine_guns( "bot_allow_sub_machine_guns", "1", FCVAR_REPLICATED, "If nonzero, bots may use sub-machine guns." );
|
||||
ConVar cv_bot_allow_rifles( "bot_allow_rifles", "1", FCVAR_REPLICATED, "If nonzero, bots may use rifles." );
|
||||
ConVar cv_bot_allow_machine_guns( "bot_allow_machine_guns", "1", FCVAR_REPLICATED, "If nonzero, bots may use the machine gun." );
|
||||
ConVar cv_bot_allow_grenades( "bot_allow_grenades", "1", FCVAR_REPLICATED, "If nonzero, bots may use grenades." );
|
||||
ConVar cv_bot_allow_snipers( "bot_allow_snipers", "1", FCVAR_REPLICATED, "If nonzero, bots may use sniper rifles." );
|
||||
#ifdef CS_SHIELD_ENABLED
|
||||
ConVar cv_bot_allow_shield( "bot_allow_shield", "1", FCVAR_REPLICATED );
|
||||
#endif // CS_SHIELD_ENABLED
|
||||
ConVar cv_bot_join_team( "bot_join_team", "any", FCVAR_REPLICATED, "Determines the team bots will join into. Allowed values: 'any', 'T', or 'CT'." );
|
||||
ConVar cv_bot_join_after_player( "bot_join_after_player", "1", FCVAR_REPLICATED, "If nonzero, bots wait until a player joins before entering the game." );
|
||||
ConVar cv_bot_auto_vacate( "bot_auto_vacate", "1", FCVAR_REPLICATED, "If nonzero, bots will automatically leave to make room for human players." );
|
||||
ConVar cv_bot_zombie( "bot_zombie", "0", FCVAR_REPLICATED | FCVAR_CHEAT, "If nonzero, bots will stay in idle mode and not attack." );
|
||||
ConVar cv_bot_defer_to_human( "bot_defer_to_human", "0", FCVAR_REPLICATED, "If nonzero and there is a human on the team, the bots will not do the scenario tasks." );
|
||||
ConVar cv_bot_chatter( "bot_chatter", "normal", FCVAR_REPLICATED, "Control how bots talk. Allowed values: 'off', 'radio', 'minimal', or 'normal'." );
|
||||
ConVar cv_bot_profile_db( "bot_profile_db", "BotProfile.db", FCVAR_REPLICATED, "The filename from which bot profiles will be read." );
|
||||
ConVar cv_bot_dont_shoot( "bot_dont_shoot", "0", FCVAR_REPLICATED | FCVAR_CHEAT, "If nonzero, bots will not fire weapons (for debugging)." );
|
||||
ConVar cv_bot_eco_limit( "bot_eco_limit", "2000", FCVAR_REPLICATED, "If nonzero, bots will not buy if their money falls below this amount." );
|
||||
ConVar cv_bot_auto_follow( "bot_auto_follow", "0", FCVAR_REPLICATED, "If nonzero, bots with high co-op may automatically follow a nearby human player." );
|
||||
ConVar cv_bot_flipout( "bot_flipout", "0", FCVAR_REPLICATED | FCVAR_CHEAT, "If nonzero, bots use no CPU for AI. Instead, they run around randomly." );
|
||||
|
||||
|
||||
extern void FinishClientPutInServer( CCSPlayer *pPlayer );
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
// Engine callback for custom server commands
|
||||
void Bot_ServerCommand( void )
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
CCSBot::CCSBot( void ) : m_chatter( this ), m_gameState( this )
|
||||
{
|
||||
m_hasJoined = false;
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Destructor
|
||||
*/
|
||||
CCSBot::~CCSBot()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Prepare bot for action
|
||||
*/
|
||||
bool CCSBot::Initialize( const BotProfile *profile, int team )
|
||||
{
|
||||
// extend
|
||||
BaseClass::Initialize( profile, team );
|
||||
|
||||
// CS bot initialization
|
||||
m_diedLastRound = false;
|
||||
m_morale = POSITIVE; // starting a new round makes everyone a little happy
|
||||
|
||||
m_combatRange = RandomFloat( 325.0f, 425.0f );
|
||||
|
||||
// set initial safe time guess for this map
|
||||
m_safeTime = 15.0f + 5.0f * GetProfile()->GetAggression();
|
||||
|
||||
m_name[0] = '\000';
|
||||
|
||||
ResetValues();
|
||||
|
||||
m_desiredTeam = team;
|
||||
|
||||
if (GetTeamNumber() == 0)
|
||||
{
|
||||
HandleCommand_JoinTeam( m_desiredTeam );
|
||||
int desiredClass = GetProfile()->GetSkin();
|
||||
if ( m_desiredTeam == TEAM_CT && desiredClass )
|
||||
{
|
||||
desiredClass = FIRST_CT_CLASS + desiredClass - 1;
|
||||
}
|
||||
else if ( m_desiredTeam == TEAM_TERRORIST && desiredClass )
|
||||
{
|
||||
desiredClass = FIRST_T_CLASS + desiredClass - 1;
|
||||
}
|
||||
HandleCommand_JoinClass( desiredClass );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Reset internal data to initial state
|
||||
*/
|
||||
void CCSBot::ResetValues( void )
|
||||
{
|
||||
m_chatter.Reset();
|
||||
m_gameState.Reset();
|
||||
|
||||
m_avoid = NULL;
|
||||
m_avoidTimestamp = 0.0f;
|
||||
|
||||
m_hurryTimer.Invalidate();
|
||||
m_alertTimer.Invalidate();
|
||||
m_sneakTimer.Invalidate();
|
||||
m_noiseBendTimer.Invalidate();
|
||||
m_bendNoisePositionValid = false;
|
||||
|
||||
m_isStuck = false;
|
||||
m_stuckTimestamp = 0.0f;
|
||||
m_wiggleTimer.Invalidate();
|
||||
m_stuckJumpTimer.Invalidate();
|
||||
|
||||
m_pathLength = 0;
|
||||
m_pathIndex = 0;
|
||||
m_areaEnteredTimestamp = 0.0f;
|
||||
m_currentArea = NULL;
|
||||
m_lastKnownArea = NULL;
|
||||
m_isStopping = false;
|
||||
|
||||
m_avoidFriendTimer.Invalidate();
|
||||
m_isFriendInTheWay = false;
|
||||
m_isWaitingBehindFriend = false;
|
||||
m_isAvoidingGrenade.Invalidate();
|
||||
|
||||
StopPanicking();
|
||||
|
||||
m_disposition = ENGAGE_AND_INVESTIGATE;
|
||||
|
||||
m_enemy = NULL;
|
||||
|
||||
m_grenadeTossState = NOT_THROWING;
|
||||
m_initialEncounterArea = NULL;
|
||||
|
||||
m_wasSafe = true;
|
||||
|
||||
m_nearbyEnemyCount = 0;
|
||||
m_enemyPlace = 0;
|
||||
m_nearbyFriendCount = 0;
|
||||
m_closestVisibleFriend = NULL;
|
||||
m_closestVisibleHumanFriend = NULL;
|
||||
|
||||
for( int w=0; w<MAX_PLAYERS; ++w )
|
||||
{
|
||||
m_watchInfo[w].timestamp = 0.0f;
|
||||
m_watchInfo[w].isEnemy = false;
|
||||
|
||||
m_playerTravelDistance[ w ] = -1.0f;
|
||||
}
|
||||
|
||||
// randomly offset each bot's timer to spread computation out
|
||||
m_updateTravelDistanceTimer.Start( RandomFloat( 0.0f, 0.9f ) );
|
||||
m_travelDistancePhase = 0;
|
||||
|
||||
m_isEnemyVisible = false;
|
||||
m_visibleEnemyParts = NONE;
|
||||
m_lastSawEnemyTimestamp = -999.9f;
|
||||
m_firstSawEnemyTimestamp = 0.0f;
|
||||
m_currentEnemyAcquireTimestamp = 0.0f;
|
||||
m_isLastEnemyDead = true;
|
||||
m_attacker = NULL;
|
||||
m_attackedTimestamp = 0.0f;
|
||||
m_enemyDeathTimestamp = 0.0f;
|
||||
m_friendDeathTimestamp = 0.0f;
|
||||
m_lastVictimID = 0;
|
||||
m_isAimingAtEnemy = false;
|
||||
m_fireWeaponTimestamp = 0.0f;
|
||||
m_equipTimer.Invalidate();
|
||||
m_zoomTimer.Invalidate();
|
||||
|
||||
m_isFollowing = false;
|
||||
m_leader = NULL;
|
||||
m_followTimestamp = 0.0f;
|
||||
m_allowAutoFollowTime = 0.0f;
|
||||
|
||||
m_enemyQueueIndex = 0;
|
||||
m_enemyQueueCount = 0;
|
||||
m_enemyQueueAttendIndex = 0;
|
||||
m_bomber = NULL;
|
||||
|
||||
m_isEnemySniperVisible = false;
|
||||
m_sawEnemySniperTimer.Invalidate();
|
||||
|
||||
m_lookAroundStateTimestamp = 0.0f;
|
||||
m_inhibitLookAroundTimestamp = 0.0f;
|
||||
|
||||
m_lookPitch = 0.0f;
|
||||
m_lookPitchVel = 0.0f;
|
||||
m_lookYaw = 0.0f;
|
||||
m_lookYawVel = 0.0f;
|
||||
|
||||
m_aimOffsetTimestamp = 0.0f;
|
||||
m_aimSpreadTimestamp = 0.0f;
|
||||
m_lookAtSpotState = NOT_LOOKING_AT_SPOT;
|
||||
|
||||
for( int p=0; p<MAX_PLAYERS; ++p )
|
||||
{
|
||||
m_partInfo[p].m_validFrame = 0;
|
||||
}
|
||||
|
||||
m_spotEncounter = NULL;
|
||||
m_spotCheckTimestamp = 0.0f;
|
||||
m_peripheralTimestamp = 0.0f;
|
||||
|
||||
m_avgVelIndex = 0;
|
||||
m_avgVelCount = 0;
|
||||
|
||||
m_lastOrigin = GetCentroid( this );
|
||||
|
||||
m_lastRadioCommand = RADIO_INVALID;
|
||||
m_lastRadioRecievedTimestamp = 0.0f;
|
||||
m_lastRadioSentTimestamp = 0.0f;
|
||||
m_radioSubject = NULL;
|
||||
m_voiceEndTimestamp = 0.0f;
|
||||
|
||||
m_hostageEscortCount = 0;
|
||||
m_hostageEscortCountTimestamp = 0.0f;
|
||||
|
||||
m_noisePosition = Vector( 0, 0, 0 );
|
||||
m_noiseTimestamp = 0.0f;
|
||||
|
||||
m_stateTimestamp = 0.0f;
|
||||
m_task = SEEK_AND_DESTROY;
|
||||
m_taskEntity = NULL;
|
||||
|
||||
m_approachPointCount = 0;
|
||||
m_approachPointViewPosition.x = 99999999999.9f;
|
||||
m_approachPointViewPosition.y = 0.0f;
|
||||
m_approachPointViewPosition.z = 0.0f;
|
||||
|
||||
m_checkedHidingSpotCount = 0;
|
||||
|
||||
StandUp();
|
||||
Run();
|
||||
m_mustRunTimer.Invalidate();
|
||||
m_waitTimer.Invalidate();
|
||||
m_pathLadder = NULL;
|
||||
|
||||
m_repathTimer.Invalidate();
|
||||
|
||||
m_huntState.ClearHuntArea();
|
||||
m_hasVisitedEnemySpawn = false;
|
||||
m_stillTimer.Invalidate();
|
||||
|
||||
// adjust morale - if we died, our morale decreased,
|
||||
// but if we live, no adjustement (round win/loss also adjusts morale)
|
||||
if (m_diedLastRound)
|
||||
DecreaseMorale();
|
||||
|
||||
m_diedLastRound = false;
|
||||
|
||||
|
||||
// IsRogue() randomly changes this
|
||||
m_isRogue = false;
|
||||
|
||||
m_surpriseTimer.Invalidate();
|
||||
|
||||
// even though these are EHANDLEs, they need to be NULL-ed
|
||||
m_goalEntity = NULL;
|
||||
m_avoid = NULL;
|
||||
m_enemy = NULL;
|
||||
|
||||
for ( int i=0; i<MAX_ENEMY_QUEUE; ++i )
|
||||
{
|
||||
m_enemyQueue[i].player = NULL;
|
||||
m_enemyQueue[i].isReloading = false;
|
||||
m_enemyQueue[i].isProtectedByShield = false;
|
||||
}
|
||||
|
||||
// start in idle state
|
||||
m_isOpeningDoor = false;
|
||||
StopAttacking();
|
||||
Idle();
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Called when bot is placed in map, and when bots are reset after a round ends.
|
||||
* NOTE: For some reason, this can be called twice when a bot is added.
|
||||
*/
|
||||
void CCSBot::Spawn( void )
|
||||
{
|
||||
// do the normal player spawn process
|
||||
BaseClass::Spawn();
|
||||
|
||||
ResetValues();
|
||||
|
||||
V_strcpy_safe( m_name, GetPlayerName() );
|
||||
|
||||
Buy();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,230 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose:
|
||||
//
|
||||
// $NoKeywords: $
|
||||
//=============================================================================//
|
||||
|
||||
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
|
||||
|
||||
#include "cbase.h"
|
||||
#include "cs_bot.h"
|
||||
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
bool CCSBot::IsNoiseHeard( void ) const
|
||||
{
|
||||
if (m_noiseTimestamp <= 0.0f)
|
||||
return false;
|
||||
|
||||
// primitive reaction time simulation - cannot "hear" noise until reaction time has elapsed
|
||||
if (gpGlobals->curtime - m_noiseTimestamp >= GetProfile()->GetReactionTime())
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Listen for enemy noises, and determine if we should react to them.
|
||||
* Returns true if heard a noise and should move to investigate.
|
||||
*/
|
||||
bool CCSBot::HeardInterestingNoise( void )
|
||||
{
|
||||
if (IsBlind())
|
||||
return false;
|
||||
|
||||
// don't investigate noises during safe time
|
||||
if (!IsWellPastSafe())
|
||||
return false;
|
||||
|
||||
// if our disposition is not to investigate, dont investigate
|
||||
if (GetDisposition() != ENGAGE_AND_INVESTIGATE)
|
||||
return false;
|
||||
|
||||
// listen for enemy noises
|
||||
if (IsNoiseHeard())
|
||||
{
|
||||
// if we are hiding, only react to noises very nearby, depending on how aggressive we are
|
||||
if (IsAtHidingSpot() && GetNoiseRange() > 100.0f + 400.0f * GetProfile()->GetAggression())
|
||||
return false;
|
||||
|
||||
// chance of investigating is inversely proportional to distance
|
||||
const float maxNoiseDist = 3000.0f;
|
||||
float chance = 100.0f * (1.0f - (GetNoiseRange()/maxNoiseDist));
|
||||
|
||||
// modify chance by number of friends remaining
|
||||
// if we have lots of friends, presumably one of them is closer and will check it out
|
||||
if (GetFriendsRemaining() >= 3)
|
||||
{
|
||||
float friendFactor = 5.0f * GetFriendsRemaining();
|
||||
if (friendFactor > 50.0f)
|
||||
friendFactor = 50.0f;
|
||||
|
||||
chance -= friendFactor;
|
||||
}
|
||||
|
||||
if (RandomFloat( 0.0f, 100.0f ) <= chance)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Return true if we hear nearby threatening enemy gunfire within given range
|
||||
* -1 == infinite range
|
||||
*/
|
||||
bool CCSBot::CanHearNearbyEnemyGunfire( float range ) const
|
||||
{
|
||||
Vector myOrigin = GetCentroid( this );
|
||||
|
||||
// only attend to noise if it just happened
|
||||
if (gpGlobals->curtime - m_noiseTimestamp > 0.5f)
|
||||
return false;
|
||||
|
||||
// gunfire is high priority
|
||||
if (m_noisePriority < PRIORITY_HIGH)
|
||||
return false;
|
||||
|
||||
// check noise range
|
||||
if (range > 0.0f && (myOrigin - m_noisePosition).IsLengthGreaterThan( range ))
|
||||
return false;
|
||||
|
||||
// if we dont have line of sight, it's not threatening (cant get shot)
|
||||
if (!CanSeeNoisePosition())
|
||||
return false;
|
||||
|
||||
if (IsAttacking() && m_enemy != NULL && GetTimeSinceLastSawEnemy() < 1.0f)
|
||||
{
|
||||
// gunfire is only threatening if it is closer than our current enemy
|
||||
float gunfireDistSq = (m_noisePosition - myOrigin).LengthSqr();
|
||||
float enemyDistSq = (GetCentroid( m_enemy ) - myOrigin).LengthSqr();
|
||||
const float muchCloserSq = 100.0f * 100.0f;
|
||||
if (gunfireDistSq > enemyDistSq - muchCloserSq)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Return true if we directly see where we think the noise came from
|
||||
* NOTE: Dont check FOV, since this is used to determine if we should turn our head to look at the noise
|
||||
* NOTE: Dont use IsVisible(), because smoke shouldnt cause us to not look toward noises
|
||||
*/
|
||||
bool CCSBot::CanSeeNoisePosition( void ) const
|
||||
{
|
||||
trace_t result;
|
||||
CTraceFilterNoNPCsOrPlayer traceFilter( this, COLLISION_GROUP_NONE );
|
||||
UTIL_TraceLine( EyePositionConst(), m_noisePosition + Vector( 0, 0, HalfHumanHeight ), MASK_VISIBLE_AND_NPCS, &traceFilter, &result );
|
||||
if (result.fraction == 1.0f)
|
||||
{
|
||||
// we can see the source of the noise
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Return true if we decided to look towards the most recent noise source
|
||||
* Assumes m_noisePosition is valid.
|
||||
*/
|
||||
bool CCSBot::UpdateLookAtNoise( void )
|
||||
{
|
||||
// make sure a noise exists
|
||||
if (!IsNoiseHeard())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Vector spot;
|
||||
|
||||
// if we have clear line of sight to noise position, look directly at it
|
||||
if (CanSeeNoisePosition())
|
||||
{
|
||||
/// @todo adjust noise Z to keep consistent with current height while fighting
|
||||
spot = m_noisePosition + Vector( 0, 0, HalfHumanHeight );
|
||||
|
||||
// since we can see the noise spot, forget about it
|
||||
ForgetNoise();
|
||||
}
|
||||
else
|
||||
{
|
||||
// line of sight is blocked, bend it
|
||||
|
||||
// the bending algorithm is very expensive, throttle how often it is done
|
||||
if (m_noiseBendTimer.IsElapsed())
|
||||
{
|
||||
const float noiseBendLOSInterval = RandomFloat( 0.2f, 0.3f );
|
||||
m_noiseBendTimer.Start( noiseBendLOSInterval );
|
||||
|
||||
// line of sight is blocked, bend it
|
||||
if (BendLineOfSight( EyePosition(), m_noisePosition, &spot ) == false)
|
||||
{
|
||||
m_bendNoisePositionValid = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
m_bentNoisePosition = spot;
|
||||
m_bendNoisePositionValid = true;
|
||||
}
|
||||
else if (m_bendNoisePositionValid)
|
||||
{
|
||||
// use result of prior bend computation
|
||||
spot = m_bentNoisePosition;
|
||||
}
|
||||
else
|
||||
{
|
||||
// prior bend failed
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// it's always important to look at enemy noises, because they come from ... enemies!
|
||||
PriorityType pri = PRIORITY_HIGH;
|
||||
|
||||
// look longer if we're hiding
|
||||
if (IsAtHidingSpot())
|
||||
{
|
||||
// if there is only one enemy left, look for a long time
|
||||
if (GetEnemiesRemaining() == 1)
|
||||
{
|
||||
SetLookAt( "Noise", spot, pri, RandomFloat( 5.0f, 15.0f ), true );
|
||||
}
|
||||
else
|
||||
{
|
||||
SetLookAt( "Noise", spot, pri, RandomFloat( 3.0f, 5.0f ), true );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const float closeRange = 500.0f;
|
||||
if (GetNoiseRange() < closeRange)
|
||||
{
|
||||
// look at nearby enemy noises for a longer time
|
||||
SetLookAt( "Noise", spot, pri, RandomFloat( 3.0f, 5.0f ), true );
|
||||
}
|
||||
else
|
||||
{
|
||||
SetLookAt( "Noise", spot, pri, RandomFloat( 1.0f, 2.0f ), true );
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,400 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose:
|
||||
//
|
||||
// $NoKeywords: $
|
||||
//=============================================================================//
|
||||
|
||||
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
|
||||
|
||||
#ifndef CS_CONTROL_H
|
||||
#define CS_CONTROL_H
|
||||
|
||||
|
||||
#include "bot_manager.h"
|
||||
#include "nav_area.h"
|
||||
#include "bot_util.h"
|
||||
#include "bot_profile.h"
|
||||
#include "cs_shareddefs.h"
|
||||
#include "cs_player.h"
|
||||
|
||||
extern ConVar friendlyfire;
|
||||
|
||||
class CBasePlayerWeapon;
|
||||
|
||||
/**
|
||||
* Given one team, return the other
|
||||
*/
|
||||
inline int OtherTeam( int team )
|
||||
{
|
||||
return (team == TEAM_TERRORIST) ? TEAM_CT : TEAM_TERRORIST;
|
||||
}
|
||||
|
||||
class CCSBotManager;
|
||||
|
||||
// accessor for CS-specific bots
|
||||
inline CCSBotManager *TheCSBots( void )
|
||||
{
|
||||
return reinterpret_cast< CCSBotManager * >( TheBots );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
class BotEventInterface : public IGameEventListener2
|
||||
{
|
||||
public:
|
||||
virtual const char *GetEventName( void ) const = 0;
|
||||
};
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Macro to set up an OnEventClass() in TheCSBots.
|
||||
*/
|
||||
#define DECLARE_BOTMANAGER_EVENT_LISTENER( BotManagerSingleton, EventClass, EventName ) \
|
||||
public: \
|
||||
virtual void On##EventClass( IGameEvent *data ); \
|
||||
private: \
|
||||
class EventClass##Event : public BotEventInterface \
|
||||
{ \
|
||||
bool m_enabled; \
|
||||
public: \
|
||||
EventClass##Event( void ) \
|
||||
{ \
|
||||
gameeventmanager->AddListener( this, #EventName, true ); \
|
||||
m_enabled = true; \
|
||||
} \
|
||||
~EventClass##Event( void ) \
|
||||
{ \
|
||||
if ( m_enabled ) gameeventmanager->RemoveListener( this ); \
|
||||
} \
|
||||
virtual const char *GetEventName( void ) const \
|
||||
{ \
|
||||
return #EventName; \
|
||||
} \
|
||||
void Enable( bool enable ) \
|
||||
{ \
|
||||
m_enabled = enable; \
|
||||
if ( enable ) \
|
||||
gameeventmanager->AddListener( this, #EventName, true ); \
|
||||
else \
|
||||
gameeventmanager->RemoveListener( this ); \
|
||||
} \
|
||||
bool IsEnabled( void ) const { return m_enabled; } \
|
||||
void FireGameEvent( IGameEvent *event ) \
|
||||
{ \
|
||||
BotManagerSingleton()->On##EventClass( event ); \
|
||||
} \
|
||||
}; \
|
||||
EventClass##Event m_##EventClass##Event;
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
#define DECLARE_CSBOTMANAGER_EVENT_LISTENER( EventClass, EventName ) DECLARE_BOTMANAGER_EVENT_LISTENER( TheCSBots, EventClass, EventName )
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Macro to propogate an event from the bot manager to all bots
|
||||
*/
|
||||
#define CCSBOTMANAGER_ITERATE_BOTS( Callback, arg1 ) \
|
||||
{ \
|
||||
for ( int idx = 1; idx <= gpGlobals->maxClients; ++idx ) \
|
||||
{ \
|
||||
CBasePlayer *player = UTIL_PlayerByIndex( idx ); \
|
||||
if (player == NULL) continue; \
|
||||
if (!player->IsBot()) continue; \
|
||||
CCSBot *bot = dynamic_cast< CCSBot * >(player); \
|
||||
if ( !bot ) continue; \
|
||||
bot->Callback( arg1 ); \
|
||||
} \
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
//
|
||||
// The manager for Counter-Strike specific bots
|
||||
//
|
||||
class CCSBotManager : public CBotManager
|
||||
{
|
||||
public:
|
||||
CCSBotManager();
|
||||
|
||||
virtual CBasePlayer *AllocateBotEntity( void ); ///< factory method to allocate the appropriate entity for the bot
|
||||
|
||||
virtual void ClientDisconnect( CBaseEntity *entity );
|
||||
virtual bool ClientCommand( CBasePlayer *player, const CCommand &args );
|
||||
|
||||
virtual void ServerActivate( void );
|
||||
virtual void ServerDeactivate( void );
|
||||
virtual bool ServerCommand( const char *cmd );
|
||||
bool IsServerActive( void ) const { return m_serverActive; }
|
||||
|
||||
virtual void RestartRound( void ); ///< (EXTEND) invoked when a new round begins
|
||||
virtual void StartFrame( void ); ///< (EXTEND) called each frame
|
||||
|
||||
virtual unsigned int GetPlayerPriority( CBasePlayer *player ) const; ///< return priority of player (0 = max pri)
|
||||
virtual bool IsImportantPlayer( CCSPlayer *player ) const; ///< return true if player is important to scenario (VIP, bomb carrier, etc)
|
||||
|
||||
void ExtractScenarioData( void ); ///< search the map entities to determine the game scenario and define important zones
|
||||
|
||||
// difficulty levels -----------------------------------------------------------------------------------------
|
||||
static BotDifficultyType GetDifficultyLevel( void )
|
||||
{
|
||||
if (cv_bot_difficulty.GetFloat() < 0.9f)
|
||||
return BOT_EASY;
|
||||
if (cv_bot_difficulty.GetFloat() < 1.9f)
|
||||
return BOT_NORMAL;
|
||||
if (cv_bot_difficulty.GetFloat() < 2.9f)
|
||||
return BOT_HARD;
|
||||
|
||||
return BOT_EXPERT;
|
||||
}
|
||||
|
||||
// the supported game scenarios ------------------------------------------------------------------------------
|
||||
enum GameScenarioType
|
||||
{
|
||||
SCENARIO_DEATHMATCH,
|
||||
SCENARIO_DEFUSE_BOMB,
|
||||
SCENARIO_RESCUE_HOSTAGES,
|
||||
SCENARIO_ESCORT_VIP
|
||||
};
|
||||
GameScenarioType GetScenario( void ) const { return m_gameScenario; }
|
||||
|
||||
// "zones" ---------------------------------------------------------------------------------------------------
|
||||
// depending on the game mode, these are bomb zones, rescue zones, etc.
|
||||
|
||||
enum { MAX_ZONES = 4 }; ///< max # of zones in a map
|
||||
enum { MAX_ZONE_NAV_AREAS = 16 }; ///< max # of nav areas in a zone
|
||||
struct Zone
|
||||
{
|
||||
CBaseEntity *m_entity; ///< the map entity
|
||||
CNavArea *m_area[ MAX_ZONE_NAV_AREAS ]; ///< nav areas that overlap this zone
|
||||
int m_areaCount;
|
||||
Vector m_center;
|
||||
bool m_isLegacy; ///< if true, use pev->origin and 256 unit radius as zone
|
||||
int m_index;
|
||||
bool m_isBlocked;
|
||||
Extent m_extent;
|
||||
};
|
||||
|
||||
const Zone *GetZone( int i ) const { return &m_zone[i]; }
|
||||
const Zone *GetZone( const Vector &pos ) const; ///< return the zone that contains the given position
|
||||
const Zone *GetClosestZone( const Vector &pos ) const; ///< return the closest zone to the given position
|
||||
const Zone *GetClosestZone( const CBaseEntity *entity ) const; ///< return the closest zone to the given entity
|
||||
int GetZoneCount( void ) const { return m_zoneCount; }
|
||||
void CheckForBlockedZones( void );
|
||||
|
||||
|
||||
const Vector *GetRandomPositionInZone( const Zone *zone ) const; ///< return a random position inside the given zone
|
||||
CNavArea *GetRandomAreaInZone( const Zone *zone ) const; ///< return a random area inside the given zone
|
||||
|
||||
/**
|
||||
* Return the zone closest to the given position, using the given cost heuristic
|
||||
*/
|
||||
template< typename CostFunctor >
|
||||
const Zone *GetClosestZone( CNavArea *startArea, CostFunctor costFunc, float *travelDistance = NULL ) const
|
||||
{
|
||||
const Zone *closeZone = NULL;
|
||||
float closeDist = 99999999.9f;
|
||||
|
||||
if (startArea == NULL)
|
||||
return NULL;
|
||||
|
||||
for( int i=0; i<m_zoneCount; ++i )
|
||||
{
|
||||
if (m_zone[i].m_areaCount == 0)
|
||||
continue;
|
||||
|
||||
if ( m_zone[i].m_isBlocked )
|
||||
continue;
|
||||
|
||||
// just use the first overlapping nav area as a reasonable approximation
|
||||
float dist = NavAreaTravelDistance( startArea, m_zone[i].m_area[0], costFunc );
|
||||
|
||||
if (dist >= 0.0f && dist < closeDist)
|
||||
{
|
||||
closeZone = &m_zone[i];
|
||||
closeDist = dist;
|
||||
}
|
||||
}
|
||||
|
||||
if (travelDistance)
|
||||
*travelDistance = closeDist;
|
||||
|
||||
return closeZone;
|
||||
}
|
||||
|
||||
/// pick a zone at random and return it
|
||||
const Zone *GetRandomZone( void ) const
|
||||
{
|
||||
if (m_zoneCount == 0)
|
||||
return NULL;
|
||||
|
||||
int i;
|
||||
CUtlVector< const Zone * > unblockedZones;
|
||||
for ( i=0; i<m_zoneCount; ++i )
|
||||
{
|
||||
if ( m_zone[i].m_isBlocked )
|
||||
continue;
|
||||
|
||||
unblockedZones.AddToTail( &(m_zone[i]) );
|
||||
}
|
||||
|
||||
if ( unblockedZones.Count() == 0 )
|
||||
return NULL;
|
||||
|
||||
return unblockedZones[ RandomInt( 0, unblockedZones.Count()-1 ) ];
|
||||
}
|
||||
|
||||
|
||||
/// returns a random spawn point for the given team (no arg means use both team spawnpoints)
|
||||
CBaseEntity *GetRandomSpawn( int team = TEAM_MAXCOUNT ) const;
|
||||
|
||||
|
||||
bool IsBombPlanted( void ) const { return m_isBombPlanted; } ///< returns true if bomb has been planted
|
||||
float GetBombPlantTimestamp( void ) const { return m_bombPlantTimestamp; } ///< return time bomb was planted
|
||||
bool IsTimeToPlantBomb( void ) const; ///< return true if it's ok to try to plant bomb
|
||||
CCSPlayer *GetBombDefuser( void ) const { return m_bombDefuser; } ///< return the player currently defusing the bomb, or NULL
|
||||
float GetBombTimeLeft( void ) const; ///< get the time remaining before the planted bomb explodes
|
||||
CBaseEntity *GetLooseBomb( void ) { return m_looseBomb; } ///< return the bomb if it is loose on the ground
|
||||
CNavArea *GetLooseBombArea( void ) const { return m_looseBombArea; } ///< return area that bomb is in/near
|
||||
void SetLooseBomb( CBaseEntity *bomb );
|
||||
|
||||
|
||||
float GetRadioMessageTimestamp( RadioType event, int teamID ) const; ///< return the last time the given radio message was sent for given team
|
||||
float GetRadioMessageInterval( RadioType event, int teamID ) const; ///< return the interval since the last time this message was sent
|
||||
void SetRadioMessageTimestamp( RadioType event, int teamID );
|
||||
void ResetRadioMessageTimestamps( void );
|
||||
|
||||
float GetLastSeenEnemyTimestamp( void ) const { return m_lastSeenEnemyTimestamp; } ///< return the last time anyone has seen an enemy
|
||||
void SetLastSeenEnemyTimestamp( void ) { m_lastSeenEnemyTimestamp = gpGlobals->curtime; }
|
||||
|
||||
float GetRoundStartTime( void ) const { return m_roundStartTimestamp; }
|
||||
float GetElapsedRoundTime( void ) const { return gpGlobals->curtime - m_roundStartTimestamp; } ///< return the elapsed time since the current round began
|
||||
|
||||
bool AllowRogues( void ) const { return cv_bot_allow_rogues.GetBool(); }
|
||||
bool AllowPistols( void ) const { return cv_bot_allow_pistols.GetBool(); }
|
||||
bool AllowShotguns( void ) const { return cv_bot_allow_shotguns.GetBool(); }
|
||||
bool AllowSubMachineGuns( void ) const { return cv_bot_allow_sub_machine_guns.GetBool(); }
|
||||
bool AllowRifles( void ) const { return cv_bot_allow_rifles.GetBool(); }
|
||||
bool AllowMachineGuns( void ) const { return cv_bot_allow_machine_guns.GetBool(); }
|
||||
bool AllowGrenades( void ) const { return cv_bot_allow_grenades.GetBool(); }
|
||||
bool AllowSnipers( void ) const { return cv_bot_allow_snipers.GetBool(); }
|
||||
#ifdef CS_SHIELD_ENABLED
|
||||
bool AllowTacticalShield( void ) const { return cv_bot_allow_shield.GetBool(); }
|
||||
#else
|
||||
bool AllowTacticalShield( void ) const { return false; }
|
||||
#endif // CS_SHIELD_ENABLED
|
||||
|
||||
bool AllowFriendlyFireDamage( void ) const { return friendlyfire.GetBool(); }
|
||||
|
||||
bool IsWeaponUseable( const CWeaponCSBase *weapon ) const; ///< return true if the bot can use this weapon
|
||||
|
||||
bool IsDefenseRushing( void ) const { return m_isDefenseRushing; } ///< returns true if defense team has "decided" to rush this round
|
||||
bool IsOnDefense( const CCSPlayer *player ) const; ///< return true if this player is on "defense"
|
||||
bool IsOnOffense( const CCSPlayer *player ) const; ///< return true if this player is on "offense"
|
||||
|
||||
bool IsRoundOver( void ) const { return m_isRoundOver; } ///< return true if the round has ended
|
||||
|
||||
#define FROM_CONSOLE true
|
||||
bool BotAddCommand( int team, bool isFromConsole = false, const char *profileName = NULL, CSWeaponType weaponType = WEAPONTYPE_UNKNOWN, BotDifficultyType difficulty = NUM_DIFFICULTY_LEVELS ); ///< process the "bot_add" console command
|
||||
|
||||
private:
|
||||
enum SkillType { LOW, AVERAGE, HIGH, RANDOM };
|
||||
|
||||
void MaintainBotQuota( void );
|
||||
|
||||
static bool m_isMapDataLoaded; ///< true if we've attempted to load map data
|
||||
bool m_serverActive; ///< true between ServerActivate() and ServerDeactivate()
|
||||
|
||||
GameScenarioType m_gameScenario; ///< what kind of game are we playing
|
||||
|
||||
Zone m_zone[ MAX_ZONES ];
|
||||
int m_zoneCount;
|
||||
|
||||
bool m_isBombPlanted; ///< true if bomb has been planted
|
||||
float m_bombPlantTimestamp; ///< time bomb was planted
|
||||
float m_earliestBombPlantTimestamp; ///< don't allow planting until after this time has elapsed
|
||||
CCSPlayer *m_bombDefuser; ///< the player currently defusing a bomb
|
||||
EHANDLE m_looseBomb; ///< will be non-NULL if bomb is loose on the ground
|
||||
CNavArea *m_looseBombArea; ///< area that bomb is is/near
|
||||
|
||||
bool m_isRoundOver; ///< true if the round has ended
|
||||
|
||||
CountdownTimer m_checkTransientAreasTimer; ///< when elapsed, all transient nav areas should be checked for blockage
|
||||
|
||||
float m_radioMsgTimestamp[ RADIO_END - RADIO_START_1 ][ 2 ];
|
||||
|
||||
float m_lastSeenEnemyTimestamp;
|
||||
float m_roundStartTimestamp; ///< the time when the current round began
|
||||
|
||||
bool m_isDefenseRushing; ///< whether defensive team is rushing this round or not
|
||||
|
||||
// Event Handlers --------------------------------------------------------------------------------------------
|
||||
DECLARE_CSBOTMANAGER_EVENT_LISTENER( PlayerFootstep, player_footstep )
|
||||
DECLARE_CSBOTMANAGER_EVENT_LISTENER( PlayerRadio, player_radio )
|
||||
DECLARE_CSBOTMANAGER_EVENT_LISTENER( PlayerDeath, player_death )
|
||||
DECLARE_CSBOTMANAGER_EVENT_LISTENER( PlayerFallDamage, player_falldamage )
|
||||
|
||||
DECLARE_CSBOTMANAGER_EVENT_LISTENER( BombPickedUp, bomb_pickup )
|
||||
DECLARE_CSBOTMANAGER_EVENT_LISTENER( BombPlanted, bomb_planted )
|
||||
DECLARE_CSBOTMANAGER_EVENT_LISTENER( BombBeep, bomb_beep )
|
||||
DECLARE_CSBOTMANAGER_EVENT_LISTENER( BombDefuseBegin, bomb_begindefuse )
|
||||
DECLARE_CSBOTMANAGER_EVENT_LISTENER( BombDefused, bomb_defused )
|
||||
DECLARE_CSBOTMANAGER_EVENT_LISTENER( BombDefuseAbort, bomb_abortdefuse )
|
||||
DECLARE_CSBOTMANAGER_EVENT_LISTENER( BombExploded, bomb_exploded )
|
||||
|
||||
DECLARE_CSBOTMANAGER_EVENT_LISTENER( RoundEnd, round_end )
|
||||
DECLARE_CSBOTMANAGER_EVENT_LISTENER( RoundStart, round_start )
|
||||
DECLARE_CSBOTMANAGER_EVENT_LISTENER( RoundFreezeEnd, round_freeze_end )
|
||||
|
||||
DECLARE_CSBOTMANAGER_EVENT_LISTENER( DoorMoving, door_moving )
|
||||
|
||||
DECLARE_CSBOTMANAGER_EVENT_LISTENER( BreakProp, break_prop )
|
||||
DECLARE_CSBOTMANAGER_EVENT_LISTENER( BreakBreakable, break_breakable )
|
||||
|
||||
DECLARE_CSBOTMANAGER_EVENT_LISTENER( HostageFollows, hostage_follows )
|
||||
DECLARE_CSBOTMANAGER_EVENT_LISTENER( HostageRescuedAll, hostage_rescued_all )
|
||||
|
||||
DECLARE_CSBOTMANAGER_EVENT_LISTENER( WeaponFire, weapon_fire )
|
||||
DECLARE_CSBOTMANAGER_EVENT_LISTENER( WeaponFireOnEmpty, weapon_fire_on_empty )
|
||||
DECLARE_CSBOTMANAGER_EVENT_LISTENER( WeaponReload, weapon_reload )
|
||||
DECLARE_CSBOTMANAGER_EVENT_LISTENER( WeaponZoom, weapon_zoom )
|
||||
|
||||
DECLARE_CSBOTMANAGER_EVENT_LISTENER( BulletImpact, bullet_impact )
|
||||
|
||||
DECLARE_CSBOTMANAGER_EVENT_LISTENER( HEGrenadeDetonate, hegrenade_detonate )
|
||||
DECLARE_CSBOTMANAGER_EVENT_LISTENER( FlashbangDetonate, flashbang_detonate )
|
||||
DECLARE_CSBOTMANAGER_EVENT_LISTENER( SmokeGrenadeDetonate, smokegrenade_detonate )
|
||||
DECLARE_CSBOTMANAGER_EVENT_LISTENER( GrenadeBounce, grenade_bounce )
|
||||
|
||||
DECLARE_CSBOTMANAGER_EVENT_LISTENER( NavBlocked, nav_blocked )
|
||||
|
||||
DECLARE_CSBOTMANAGER_EVENT_LISTENER( ServerShutdown, server_shutdown )
|
||||
|
||||
CUtlVector< BotEventInterface * > m_commonEventListeners; // These event listeners fire often, and can be disabled for performance gains when no bots are present.
|
||||
bool m_eventListenersEnabled;
|
||||
void EnableEventListeners( bool enable );
|
||||
};
|
||||
|
||||
inline CBasePlayer *CCSBotManager::AllocateBotEntity( void )
|
||||
{
|
||||
return static_cast<CBasePlayer *>( CreateEntityByName( "cs_bot" ) );
|
||||
}
|
||||
|
||||
inline bool CCSBotManager::IsTimeToPlantBomb( void ) const
|
||||
{
|
||||
return (gpGlobals->curtime >= m_earliestBombPlantTimestamp);
|
||||
}
|
||||
|
||||
inline const CCSBotManager::Zone *CCSBotManager::GetClosestZone( const CBaseEntity *entity ) const
|
||||
{
|
||||
if (entity == NULL)
|
||||
return NULL;
|
||||
|
||||
Vector centroid = entity->GetAbsOrigin();
|
||||
centroid.z += HalfHumanHeight;
|
||||
return GetClosestZone( centroid );
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,963 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose:
|
||||
//
|
||||
// $NoKeywords: $
|
||||
//=============================================================================//
|
||||
|
||||
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
|
||||
|
||||
#include "cbase.h"
|
||||
#include "cs_bot.h"
|
||||
#include "obstacle_pushaway.h"
|
||||
#include "fmtstr.h"
|
||||
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
const float NearBreakableCheckDist = 20.0f;
|
||||
const float FarBreakableCheckDist = 300.0f;
|
||||
|
||||
#define DEBUG_BREAKABLES 0
|
||||
#define DEBUG_DOORS 0
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
#if DEBUG_BREAKABLES
|
||||
static void DrawOutlinedQuad( const Vector &p1,
|
||||
const Vector &p2,
|
||||
const Vector &p3,
|
||||
const Vector &p4,
|
||||
int r, int g, int b,
|
||||
float duration )
|
||||
{
|
||||
NDebugOverlay::Triangle( p1, p2, p3, r, g, b, 20, false, duration );
|
||||
NDebugOverlay::Triangle( p3, p4, p1, r, g, b, 20, false, duration );
|
||||
NDebugOverlay::Line( p1, p2, r, g, b, false, duration );
|
||||
NDebugOverlay::Line( p2, p3, r, g, b, false, duration );
|
||||
NDebugOverlay::Line( p3, p4, r, g, b, false, duration );
|
||||
NDebugOverlay::Line( p4, p1, r, g, b, false, duration );
|
||||
}
|
||||
ConVar bot_debug_breakable_duration( "bot_debug_breakable_duration", "30" );
|
||||
#endif // DEBUG_BREAKABLES
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
CBaseEntity * CheckForEntitiesAlongSegment( const Vector &start, const Vector &end, const Vector &mins, const Vector &maxs, CPushAwayEnumerator *enumerator )
|
||||
{
|
||||
CBaseEntity *entity = NULL;
|
||||
|
||||
Ray_t ray;
|
||||
ray.Init( start, end, mins, maxs );
|
||||
|
||||
partition->EnumerateElementsAlongRay( PARTITION_ENGINE_SOLID_EDICTS, ray, false, enumerator );
|
||||
if ( enumerator->m_nAlreadyHit > 0 )
|
||||
{
|
||||
entity = enumerator->m_AlreadyHit[0];
|
||||
}
|
||||
|
||||
#if DEBUG_BREAKABLES
|
||||
if ( entity )
|
||||
{
|
||||
DrawOutlinedQuad( start + mins, start + maxs, end + maxs, end + mins, 255, 0, 0, bot_debug_breakable_duration.GetFloat() );
|
||||
}
|
||||
else
|
||||
{
|
||||
DrawOutlinedQuad( start + mins, start + maxs, end + maxs, end + mins, 0, 255, 0, 0.1 );
|
||||
}
|
||||
#endif // DEBUG_BREAKABLES
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Look up to 'distance' units ahead on the bot's path for entities. Returns the closest one.
|
||||
*/
|
||||
CBaseEntity * CCSBot::FindEntitiesOnPath( float distance, CPushAwayEnumerator *enumerator, bool checkStuck )
|
||||
{
|
||||
Vector goal;
|
||||
|
||||
int pathIndex = FindPathPoint( distance, &goal, NULL );
|
||||
bool isDegeneratePath = ( pathIndex == m_pathLength );
|
||||
if ( isDegeneratePath )
|
||||
{
|
||||
goal = m_goalPosition;
|
||||
}
|
||||
goal.z += HalfHumanHeight;
|
||||
|
||||
Vector mins, maxs;
|
||||
mins = Vector( 0, 0, -HalfHumanWidth );
|
||||
maxs = Vector( 0, 0, HalfHumanHeight );
|
||||
|
||||
if ( distance <= NearBreakableCheckDist && m_isStuck && checkStuck )
|
||||
{
|
||||
mins = Vector( -HalfHumanWidth, -HalfHumanWidth, -HalfHumanWidth );
|
||||
maxs = Vector( HalfHumanWidth, HalfHumanWidth, HalfHumanHeight );
|
||||
}
|
||||
|
||||
CBaseEntity *entity = NULL;
|
||||
if ( isDegeneratePath )
|
||||
{
|
||||
entity = CheckForEntitiesAlongSegment( WorldSpaceCenter(), m_goalPosition + Vector( 0, 0, HalfHumanHeight ), mins, maxs, enumerator );
|
||||
#if DEBUG_BREAKABLES
|
||||
if ( entity )
|
||||
{
|
||||
NDebugOverlay::HorzArrow( WorldSpaceCenter(), m_goalPosition, 6, 0, 0, 255, 255, true, bot_debug_breakable_duration.GetFloat() );
|
||||
}
|
||||
#endif // DEBUG_BREAKABLES
|
||||
}
|
||||
else
|
||||
{
|
||||
int startIndex = MAX( 0, m_pathIndex );
|
||||
float distanceLeft = distance;
|
||||
// HACK: start with an index one lower than normal, so we can trace from the bot's location to the
|
||||
// start of the path nodes.
|
||||
for( int i=startIndex-1; i<m_pathLength-1; ++i )
|
||||
{
|
||||
Vector start, end;
|
||||
if ( i == startIndex - 1 )
|
||||
{
|
||||
start = GetAbsOrigin();
|
||||
end = m_path[i+1].pos;
|
||||
}
|
||||
else
|
||||
{
|
||||
start = m_path[i].pos;
|
||||
end = m_path[i+1].pos;
|
||||
|
||||
if ( m_path[i+1].how == GO_LADDER_UP )
|
||||
{
|
||||
// Need two checks. First we'll check along the ladder
|
||||
start = m_path[i].pos;
|
||||
end = m_path[i+1].ladder->m_top;
|
||||
}
|
||||
else if ( m_path[i].how == GO_LADDER_UP )
|
||||
{
|
||||
start = m_path[i].ladder->m_top;
|
||||
}
|
||||
else if ( m_path[i+1].how == GO_LADDER_DOWN )
|
||||
{
|
||||
// Need two checks. First we'll check along the ladder
|
||||
start = m_path[i].pos;
|
||||
end = m_path[i+1].ladder->m_bottom;
|
||||
}
|
||||
else if ( m_path[i].how == GO_LADDER_DOWN )
|
||||
{
|
||||
start = m_path[i].ladder->m_bottom;
|
||||
}
|
||||
}
|
||||
|
||||
float segmentLength = (start - end).Length();
|
||||
if ( distanceLeft - segmentLength < 0 )
|
||||
{
|
||||
// scale our segment back so we don't look too far
|
||||
Vector direction = end - start;
|
||||
direction.NormalizeInPlace();
|
||||
|
||||
end = start + direction * distanceLeft;
|
||||
}
|
||||
entity = CheckForEntitiesAlongSegment( start + Vector( 0, 0, HalfHumanHeight ), end + Vector( 0, 0, HalfHumanHeight ), mins, maxs, enumerator );
|
||||
if ( entity )
|
||||
{
|
||||
#if DEBUG_BREAKABLES
|
||||
NDebugOverlay::HorzArrow( start, end, 4, 0, 255, 0, 255, true, bot_debug_breakable_duration.GetFloat() );
|
||||
#endif // DEBUG_BREAKABLES
|
||||
break;
|
||||
}
|
||||
|
||||
if ( m_path[i].ladder && !IsOnLadder() && distance > NearBreakableCheckDist ) // don't try to break breakables on the other end of a ladder
|
||||
break;
|
||||
|
||||
distanceLeft -= segmentLength;
|
||||
if ( distanceLeft < 0 )
|
||||
break;
|
||||
|
||||
if ( i != startIndex - 1 && m_path[i+1].ladder )
|
||||
{
|
||||
// Now we'll check from the ladder out to the endpoint
|
||||
start = ( m_path[i+1].how == GO_LADDER_DOWN ) ? m_path[i+1].ladder->m_bottom : m_path[i+1].ladder->m_top;
|
||||
end = m_path[i+1].pos;
|
||||
|
||||
entity = CheckForEntitiesAlongSegment( start + Vector( 0, 0, HalfHumanHeight ), end + Vector( 0, 0, HalfHumanHeight ), mins, maxs, enumerator );
|
||||
if ( entity )
|
||||
{
|
||||
#if DEBUG_BREAKABLES
|
||||
NDebugOverlay::HorzArrow( start, end, 4, 0, 255, 0, 255, true, bot_debug_breakable_duration.GetFloat() );
|
||||
#endif // DEBUG_BREAKABLES
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( entity && !IsVisible( entity->WorldSpaceCenter(), false, entity ) )
|
||||
return NULL;
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
void CCSBot::PushawayTouch( CBaseEntity *pOther )
|
||||
{
|
||||
#if DEBUG_BREAKABLES
|
||||
NDebugOverlay::EntityBounds( pOther, 255, 0, 0, 127, 0.1f );
|
||||
#endif // DEBUG_BREAKABLES
|
||||
|
||||
// if we're not stuck or crouched, we don't care
|
||||
if ( !m_isStuck && !IsCrouching() )
|
||||
return;
|
||||
|
||||
// See if it's breakable
|
||||
CBaseEntity *props[1];
|
||||
CBotBreakableEnumerator enumerator( props, ARRAYSIZE( props ) );
|
||||
enumerator.EnumElement( pOther );
|
||||
|
||||
if ( enumerator.m_nAlreadyHit == 1 )
|
||||
{
|
||||
// it's breakable - try to shoot it.
|
||||
SetLookAt( "Breakable", pOther->WorldSpaceCenter(), PRIORITY_HIGH, 0.1f, false, 5.0f, true );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Check for breakable physics props and other breakable entities. We do this here instead of catching them
|
||||
* in OnTouch() because players don't collide with physics props, so OnTouch() doesn't get called. Also,
|
||||
* looking ahead like this lets us anticipate when we'll need to break something, and do it before being
|
||||
* stopped by it.
|
||||
*/
|
||||
void CCSBot::BreakablesCheck( void )
|
||||
{
|
||||
#if DEBUG_BREAKABLES
|
||||
/*
|
||||
// Debug code to visually mark all breakables near us
|
||||
{
|
||||
Ray_t ray;
|
||||
Vector origin = WorldSpaceCenter();
|
||||
Vector mins( -400, -400, -400 );
|
||||
Vector maxs( 400, 400, 400 );
|
||||
ray.Init( origin, origin, mins, maxs );
|
||||
|
||||
CBaseEntity *props[40];
|
||||
CBotBreakableEnumerator enumerator( props, ARRAYSIZE( props ) );
|
||||
partition->EnumerateElementsAlongRay( PARTITION_ENGINE_SOLID_EDICTS, ray, false, &enumerator );
|
||||
for ( int i=0; i<enumerator.m_nAlreadyHit; ++i )
|
||||
{
|
||||
CBaseEntity *prop = props[i];
|
||||
if ( prop && prop->m_takedamage == DAMAGE_YES )
|
||||
{
|
||||
CFmtStr msg;
|
||||
const char *text = msg.sprintf( "%s, %d health", prop->GetClassname(), prop->m_iHealth );
|
||||
if ( prop->m_iHealth > 200 )
|
||||
{
|
||||
NDebugOverlay::EntityBounds( prop, 255, 0, 0, 10, 0.2f );
|
||||
prop->EntityText( 0, text, 0.2f, 255, 0, 0, 255 );
|
||||
}
|
||||
else
|
||||
{
|
||||
NDebugOverlay::EntityBounds( prop, 0, 255, 0, 10, 0.2f );
|
||||
prop->EntityText( 0, text, 0.2f, 0, 255, 0, 255 );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
#endif // DEBUG_BREAKABLES
|
||||
|
||||
if ( IsAttacking() )
|
||||
{
|
||||
// make sure we aren't running into a breakable trying to knife an enemy
|
||||
if ( IsUsingKnife() && m_enemy != NULL )
|
||||
{
|
||||
CBaseEntity *breakables[1];
|
||||
CBotBreakableEnumerator enumerator( breakables, ARRAYSIZE( breakables ) );
|
||||
|
||||
CBaseEntity *breakable = NULL;
|
||||
Vector mins = Vector( -HalfHumanWidth, -HalfHumanWidth, -HalfHumanWidth );
|
||||
Vector maxs = Vector( HalfHumanWidth, HalfHumanWidth, HalfHumanHeight );
|
||||
breakable = CheckForEntitiesAlongSegment( WorldSpaceCenter(), m_enemy->WorldSpaceCenter(), mins, maxs, &enumerator );
|
||||
if ( breakable )
|
||||
{
|
||||
#if DEBUG_BREAKABLES
|
||||
NDebugOverlay::HorzArrow( WorldSpaceCenter(), m_enemy->WorldSpaceCenter(), 6, 0, 0, 255, 255, true, bot_debug_breakable_duration.GetFloat() );
|
||||
#endif // DEBUG_BREAKABLES
|
||||
|
||||
// look at it (chances are we'll already be looking at it, since it's between us and our enemy)
|
||||
SetLookAt( "Breakable", breakable->WorldSpaceCenter(), PRIORITY_HIGH, 0.1f, false, 5.0f, true );
|
||||
|
||||
// break it (again, don't wait: we don't have ammo, since we're using the knife, and we're looking mostly at it anyway)
|
||||
PrimaryAttack();
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if ( !HasPath() )
|
||||
return;
|
||||
|
||||
bool isNear = true;
|
||||
|
||||
// Check just in front of us on the path
|
||||
CBaseEntity *breakables[4];
|
||||
CBotBreakableEnumerator enumerator( breakables, ARRAYSIZE( breakables ) );
|
||||
CBaseEntity *breakable = FindEntitiesOnPath( NearBreakableCheckDist, &enumerator, true );
|
||||
|
||||
// If we don't have an object right in front of us, check a ways out
|
||||
if ( !breakable )
|
||||
{
|
||||
breakable = FindEntitiesOnPath( FarBreakableCheckDist, &enumerator, false );
|
||||
isNear = false;
|
||||
}
|
||||
|
||||
// Try to shoot a breakable we know about
|
||||
if ( breakable )
|
||||
{
|
||||
// look at it
|
||||
SetLookAt( "Breakable", breakable->WorldSpaceCenter(), PRIORITY_HIGH, 0.1f, false, 5.0f, true );
|
||||
}
|
||||
|
||||
// break it
|
||||
if ( IsLookingAtSpot( PRIORITY_HIGH ) && m_lookAtSpotAttack )
|
||||
{
|
||||
if ( IsUsingGrenade() || ( !isNear && IsUsingKnife() ) )
|
||||
{
|
||||
EquipBestWeapon( MUST_EQUIP );
|
||||
}
|
||||
else if ( GetActiveWeapon() && GetActiveWeapon()->m_flNextPrimaryAttack <= gpGlobals->curtime )
|
||||
{
|
||||
bool shouldShoot = IsLookingAtPosition( m_lookAtSpot, 10.0f );
|
||||
|
||||
if ( !shouldShoot )
|
||||
{
|
||||
CBaseEntity *breakables[1];
|
||||
CBotBreakableEnumerator LOSbreakable( breakables, ARRAYSIZE( breakables ) );
|
||||
|
||||
// compute the unit vector along our view
|
||||
Vector aimDir = GetViewVector();
|
||||
|
||||
// trace the potential bullet's path
|
||||
trace_t result;
|
||||
UTIL_TraceLine( EyePosition(), EyePosition() + FarBreakableCheckDist * aimDir, MASK_PLAYERSOLID, this, COLLISION_GROUP_NONE, &result );
|
||||
if ( result.DidHitNonWorldEntity() )
|
||||
{
|
||||
LOSbreakable.EnumElement( result.m_pEnt );
|
||||
if ( LOSbreakable.m_nAlreadyHit == 1 && LOSbreakable.m_AlreadyHit[0] == breakable )
|
||||
{
|
||||
shouldShoot = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
shouldShoot = shouldShoot && !IsFriendInLineOfFire();
|
||||
|
||||
if ( shouldShoot )
|
||||
{
|
||||
PrimaryAttack();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Check for doors that need +use to open.
|
||||
*/
|
||||
void CCSBot::DoorCheck( void )
|
||||
{
|
||||
if ( IsAttacking() && !IsUsingKnife() )
|
||||
{
|
||||
// If we're attacking with a gun or nade, don't bother with doors. If we're trying to
|
||||
// knife someone, we might need to open a door.
|
||||
m_isOpeningDoor = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if ( !HasPath() )
|
||||
return;
|
||||
|
||||
// Find any doors that need a +use to open just in front of us along the path.
|
||||
CBaseEntity *doors[4];
|
||||
CBotDoorEnumerator enumerator( doors, ARRAYSIZE( doors ) );
|
||||
CBaseEntity *door = FindEntitiesOnPath( NearBreakableCheckDist, &enumerator, false );
|
||||
|
||||
if ( door )
|
||||
{
|
||||
if ( !IsLookingAtSpot( PRIORITY_HIGH ) )
|
||||
{
|
||||
if ( !IsOpeningDoor() )
|
||||
{
|
||||
OpenDoor( door );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Reset the stuck-checker.
|
||||
*/
|
||||
void CCSBot::ResetStuckMonitor( void )
|
||||
{
|
||||
if (m_isStuck)
|
||||
{
|
||||
if (IsLocalPlayerWatchingMe() && cv_bot_debug.GetBool() && UTIL_GetListenServerHost())
|
||||
{
|
||||
CBasePlayer *localPlayer = UTIL_GetListenServerHost();
|
||||
CSingleUserRecipientFilter filter( localPlayer );
|
||||
EmitSound( filter, localPlayer->entindex(), "Bot.StuckSound" );
|
||||
}
|
||||
}
|
||||
|
||||
m_isStuck = false;
|
||||
m_stuckTimestamp = 0.0f;
|
||||
m_stuckJumpTimer.Invalidate();
|
||||
m_avgVelIndex = 0;
|
||||
m_avgVelCount = 0;
|
||||
|
||||
m_areaEnteredTimestamp = gpGlobals->curtime;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Test if we have become stuck
|
||||
*/
|
||||
void CCSBot::StuckCheck( void )
|
||||
{
|
||||
if (m_isStuck)
|
||||
{
|
||||
// we are stuck - see if we have moved far enough to be considered unstuck
|
||||
Vector delta = GetAbsOrigin() - m_stuckSpot;
|
||||
|
||||
const float unstuckRange = 75.0f;
|
||||
if (delta.IsLengthGreaterThan( unstuckRange ))
|
||||
{
|
||||
// we are no longer stuck
|
||||
ResetStuckMonitor();
|
||||
PrintIfWatched( "UN-STUCK\n" );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// check if we are stuck
|
||||
|
||||
// compute average velocity over a short period (for stuck check)
|
||||
Vector vel = GetAbsOrigin() - m_lastOrigin;
|
||||
|
||||
// if we are jumping, ignore Z
|
||||
if (IsJumping())
|
||||
vel.z = 0.0f;
|
||||
|
||||
// cannot be Length2D, or will break ladder movement (they are only Z)
|
||||
float moveDist = vel.Length();
|
||||
|
||||
float deltaT = g_BotUpdateInterval;
|
||||
|
||||
m_avgVel[ m_avgVelIndex++ ] = moveDist/deltaT;
|
||||
|
||||
if (m_avgVelIndex == MAX_VEL_SAMPLES)
|
||||
m_avgVelIndex = 0;
|
||||
|
||||
if (m_avgVelCount < MAX_VEL_SAMPLES)
|
||||
{
|
||||
m_avgVelCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
// we have enough samples to know if we're stuck
|
||||
|
||||
float avgVel = 0.0f;
|
||||
for( int t=0; t<m_avgVelCount; ++t )
|
||||
avgVel += m_avgVel[t];
|
||||
|
||||
avgVel /= m_avgVelCount;
|
||||
|
||||
// cannot make this velocity too high, or bots will get "stuck" when going down ladders
|
||||
float stuckVel = (IsUsingLadder()) ? 10.0f : 20.0f;
|
||||
|
||||
if (avgVel < stuckVel)
|
||||
{
|
||||
// we are stuck - note when and where we initially become stuck
|
||||
m_stuckTimestamp = gpGlobals->curtime;
|
||||
m_stuckSpot = GetAbsOrigin();
|
||||
m_stuckJumpTimer.Start( RandomFloat( 0.3f, 0.75f ) ); // 1.0
|
||||
|
||||
PrintIfWatched( "STUCK\n" );
|
||||
if (IsLocalPlayerWatchingMe() && cv_bot_debug.GetInt() > 0.0f && UTIL_GetListenServerHost())
|
||||
{
|
||||
CBasePlayer *localPlayer = UTIL_GetListenServerHost();
|
||||
CSingleUserRecipientFilter filter( localPlayer );
|
||||
EmitSound( filter, localPlayer->entindex(), "Bot.StuckStart" );
|
||||
}
|
||||
|
||||
m_isStuck = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// always need to track this
|
||||
m_lastOrigin = GetAbsOrigin();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Check if we need to jump due to height change
|
||||
*/
|
||||
bool CCSBot::DiscontinuityJump( float ground, bool onlyJumpDown, bool mustJump )
|
||||
{
|
||||
// Don't try to jump if in the air.
|
||||
if( !(GetFlags() & FL_ONGROUND) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
float dz = ground - GetFeetZ();
|
||||
|
||||
if (dz > StepHeight && !onlyJumpDown)
|
||||
{
|
||||
// dont restrict jump time when going up
|
||||
if (Jump( MUST_JUMP ))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (!IsUsingLadder() && dz < -JumpHeight)
|
||||
{
|
||||
if (Jump( mustJump ))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Find "simple" ground height, treating current nav area as part of the floor
|
||||
*/
|
||||
bool CCSBot::GetSimpleGroundHeightWithFloor( const Vector &pos, float *height, Vector *normal )
|
||||
{
|
||||
if (TheNavMesh->GetSimpleGroundHeight( pos, height, normal ))
|
||||
{
|
||||
// our current nav area also serves as a ground polygon
|
||||
if (m_lastKnownArea && m_lastKnownArea->IsOverlapping( pos ))
|
||||
*height = MAX( (*height), m_lastKnownArea->GetZ( pos ) );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Get our current radio chatter place
|
||||
*/
|
||||
Place CCSBot::GetPlace( void ) const
|
||||
{
|
||||
if (m_lastKnownArea)
|
||||
return m_lastKnownArea->GetPlace();
|
||||
|
||||
return UNDEFINED_PLACE;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Move towards position, independant of view angle
|
||||
*/
|
||||
void CCSBot::MoveTowardsPosition( const Vector &pos )
|
||||
{
|
||||
Vector myOrigin = GetCentroid( this );
|
||||
|
||||
//
|
||||
// Jump up on ledges
|
||||
// Because we may not be able to get to our goal position and enter the next
|
||||
// area because our extent collides with a nearby vertical ledge, make sure
|
||||
// we look far enough ahead to avoid this situation.
|
||||
// Can't look too far ahead, or bots will try to jump up slopes.
|
||||
//
|
||||
// NOTE: We need to do this frequently to catch edges at the right time
|
||||
// @todo Look ahead *along path* instead of straight line
|
||||
//
|
||||
if ((m_lastKnownArea == NULL || !(m_lastKnownArea->GetAttributes() & NAV_MESH_NO_JUMP)) &&
|
||||
!IsOnLadder())
|
||||
{
|
||||
float ground;
|
||||
Vector aheadRay( pos.x - myOrigin.x, pos.y - myOrigin.y, 0 );
|
||||
aheadRay.NormalizeInPlace();
|
||||
|
||||
// look far ahead to allow us to smoothly jump over gaps, ledges, etc
|
||||
// only jump if ground is flat at lookahead spot to avoid jumping up slopes
|
||||
bool jumped = false;
|
||||
if (IsRunning())
|
||||
{
|
||||
const float farLookAheadRange = 80.0f; // 60
|
||||
Vector normal;
|
||||
Vector stepAhead = myOrigin + farLookAheadRange * aheadRay;
|
||||
stepAhead.z += HalfHumanHeight;
|
||||
|
||||
if (GetSimpleGroundHeightWithFloor( stepAhead, &ground, &normal ))
|
||||
{
|
||||
if (normal.z > 0.9f)
|
||||
jumped = DiscontinuityJump( ground, ONLY_JUMP_DOWN );
|
||||
}
|
||||
}
|
||||
|
||||
if (!jumped)
|
||||
{
|
||||
// close up jumping
|
||||
const float lookAheadRange = 30.0f; // cant be less or will miss jumps over low walls
|
||||
Vector stepAhead = myOrigin + lookAheadRange * aheadRay;
|
||||
stepAhead.z += HalfHumanHeight;
|
||||
if (GetSimpleGroundHeightWithFloor( stepAhead, &ground ))
|
||||
{
|
||||
jumped = DiscontinuityJump( ground );
|
||||
}
|
||||
}
|
||||
|
||||
if (!jumped)
|
||||
{
|
||||
// about to fall gap-jumping
|
||||
const float lookAheadRange = 10.0f;
|
||||
Vector stepAhead = myOrigin + lookAheadRange * aheadRay;
|
||||
stepAhead.z += HalfHumanHeight;
|
||||
if (GetSimpleGroundHeightWithFloor( stepAhead, &ground ))
|
||||
{
|
||||
jumped = DiscontinuityJump( ground, ONLY_JUMP_DOWN, MUST_JUMP );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// compute our current forward and lateral vectors
|
||||
float angle = EyeAngles().y;
|
||||
|
||||
Vector2D dir( BotCOS(angle), BotSIN(angle) );
|
||||
Vector2D lat( -dir.y, dir.x );
|
||||
|
||||
// compute unit vector to goal position
|
||||
Vector2D to( pos.x - myOrigin.x, pos.y - myOrigin.y );
|
||||
to.NormalizeInPlace();
|
||||
|
||||
// move towards the position independant of our view direction
|
||||
float toProj = to.x * dir.x + to.y * dir.y;
|
||||
float latProj = to.x * lat.x + to.y * lat.y;
|
||||
|
||||
const float c = 0.25f; // 0.5
|
||||
if (toProj > c)
|
||||
MoveForward();
|
||||
else if (toProj < -c)
|
||||
MoveBackward();
|
||||
|
||||
// if we are avoiding someone via strafing, don't override
|
||||
if (m_avoid != NULL)
|
||||
return;
|
||||
|
||||
if (latProj >= c)
|
||||
StrafeLeft();
|
||||
else if (latProj <= -c)
|
||||
StrafeRight();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Move away from position, independant of view angle
|
||||
*/
|
||||
void CCSBot::MoveAwayFromPosition( const Vector &pos )
|
||||
{
|
||||
// compute our current forward and lateral vectors
|
||||
float angle = EyeAngles().y;
|
||||
|
||||
Vector2D dir( BotCOS(angle), BotSIN(angle) );
|
||||
Vector2D lat( -dir.y, dir.x );
|
||||
|
||||
// compute unit vector to goal position
|
||||
Vector2D to( pos.x - GetAbsOrigin().x, pos.y - GetAbsOrigin().y );
|
||||
to.NormalizeInPlace();
|
||||
|
||||
// move away from the position independant of our view direction
|
||||
float toProj = to.x * dir.x + to.y * dir.y;
|
||||
float latProj = to.x * lat.x + to.y * lat.y;
|
||||
|
||||
const float c = 0.5f;
|
||||
if (toProj > c)
|
||||
MoveBackward();
|
||||
else if (toProj < -c)
|
||||
MoveForward();
|
||||
|
||||
if (latProj >= c)
|
||||
StrafeRight();
|
||||
else if (latProj <= -c)
|
||||
StrafeLeft();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Strafe (sidestep) away from position, independant of view angle
|
||||
*/
|
||||
void CCSBot::StrafeAwayFromPosition( const Vector &pos )
|
||||
{
|
||||
// compute our current forward and lateral vectors
|
||||
float angle = EyeAngles().y;
|
||||
|
||||
Vector2D dir( BotCOS(angle), BotSIN(angle) );
|
||||
Vector2D lat( -dir.y, dir.x );
|
||||
|
||||
// compute unit vector to goal position
|
||||
Vector2D to( pos.x - GetAbsOrigin().x, pos.y - GetAbsOrigin().y );
|
||||
to.NormalizeInPlace();
|
||||
|
||||
float latProj = to.x * lat.x + to.y * lat.y;
|
||||
|
||||
if (latProj >= 0.0f)
|
||||
StrafeRight();
|
||||
else
|
||||
StrafeLeft();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* For getting un-stuck
|
||||
*/
|
||||
void CCSBot::Wiggle( void )
|
||||
{
|
||||
if (IsCrouching())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// for wiggling
|
||||
if (m_wiggleTimer.IsElapsed())
|
||||
{
|
||||
m_wiggleDirection = (NavRelativeDirType)RandomInt( 0, 3 );
|
||||
m_wiggleTimer.Start( RandomFloat( 0.3f, 0.5f ) ); // 0.3, 0.5
|
||||
}
|
||||
|
||||
Vector forward, right;
|
||||
EyeVectors( &forward, &right );
|
||||
|
||||
const float lookAheadRange = (m_lastKnownArea && (m_lastKnownArea->GetAttributes() & NAV_MESH_WALK)) ? 5.0f : 30.0f;
|
||||
float ground;
|
||||
|
||||
switch( m_wiggleDirection )
|
||||
{
|
||||
case LEFT:
|
||||
{
|
||||
// don't move left if we will fall
|
||||
Vector pos = GetAbsOrigin() - (lookAheadRange * right);
|
||||
|
||||
if (GetSimpleGroundHeightWithFloor( pos, &ground ))
|
||||
{
|
||||
if (GetAbsOrigin().z - ground < StepHeight)
|
||||
{
|
||||
StrafeLeft();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case RIGHT:
|
||||
{
|
||||
// don't move right if we will fall
|
||||
Vector pos = GetAbsOrigin() + (lookAheadRange * right);
|
||||
|
||||
if (GetSimpleGroundHeightWithFloor( pos, &ground ))
|
||||
{
|
||||
if (GetAbsOrigin().z - ground < StepHeight)
|
||||
{
|
||||
StrafeRight();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case FORWARD:
|
||||
{
|
||||
// don't move forward if we will fall
|
||||
Vector pos = GetAbsOrigin() + (lookAheadRange * forward);
|
||||
|
||||
if (GetSimpleGroundHeightWithFloor( pos, &ground ))
|
||||
{
|
||||
if (GetAbsOrigin().z - ground < StepHeight)
|
||||
{
|
||||
MoveForward();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case BACKWARD:
|
||||
{
|
||||
// don't move backward if we will fall
|
||||
Vector pos = GetAbsOrigin() - (lookAheadRange * forward);
|
||||
|
||||
if (GetSimpleGroundHeightWithFloor( pos, &ground ))
|
||||
{
|
||||
if (GetAbsOrigin().z - ground < StepHeight)
|
||||
{
|
||||
MoveBackward();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_stuckJumpTimer.IsElapsed() && m_lastKnownArea && !(m_lastKnownArea->GetAttributes() & NAV_MESH_NO_JUMP))
|
||||
{
|
||||
if (Jump())
|
||||
{
|
||||
m_stuckJumpTimer.Start( RandomFloat( 1.0f, 2.0f ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Determine approach points from eye position and approach areas of current area
|
||||
*/
|
||||
void CCSBot::ComputeApproachPoints( void )
|
||||
{
|
||||
m_approachPointCount = 0;
|
||||
|
||||
if (m_lastKnownArea == NULL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// assume we're crouching for now
|
||||
Vector eye = GetCentroid( this ); // + pev->view_ofs; // eye position
|
||||
|
||||
Vector ap;
|
||||
float halfWidth;
|
||||
for( int i=0; i<m_lastKnownArea->GetApproachInfoCount() && m_approachPointCount < MAX_APPROACH_POINTS; ++i )
|
||||
{
|
||||
const CCSNavArea::ApproachInfo *info = m_lastKnownArea->GetApproachInfo( i );
|
||||
|
||||
if (info->here.area == NULL || info->prev.area == NULL)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// compute approach point (approach area is "info->here")
|
||||
if (info->prevToHereHow <= GO_WEST)
|
||||
{
|
||||
info->prev.area->ComputePortal( info->here.area, (NavDirType)info->prevToHereHow, &ap, &halfWidth );
|
||||
ap.z = info->here.area->GetZ( ap );
|
||||
}
|
||||
else
|
||||
{
|
||||
// use the area's center as an approach point
|
||||
ap = info->here.area->GetCenter();
|
||||
}
|
||||
|
||||
// "bend" our line of sight around corners until we can see the approach point
|
||||
Vector bendPoint;
|
||||
if (BendLineOfSight( eye, ap + Vector( 0, 0, HalfHumanHeight ), &bendPoint ))
|
||||
{
|
||||
// put point on the ground
|
||||
if (TheNavMesh->GetGroundHeight( bendPoint, &bendPoint.z ) == false)
|
||||
{
|
||||
bendPoint.z = ap.z;
|
||||
}
|
||||
|
||||
m_approachPoint[ m_approachPointCount ].m_pos = bendPoint;
|
||||
m_approachPoint[ m_approachPointCount ].m_area = info->here.area;
|
||||
++m_approachPointCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
void CCSBot::DrawApproachPoints( void ) const
|
||||
{
|
||||
for( int i=0; i<m_approachPointCount; ++i )
|
||||
{
|
||||
if (TheCSBots()->GetElapsedRoundTime() >= m_approachPoint[i].m_area->GetEarliestOccupyTime( OtherTeam( GetTeamNumber() ) ))
|
||||
NDebugOverlay::Cross3D( m_approachPoint[i].m_pos, 10.0f, 255, 0, 255, true, 0.1f );
|
||||
else
|
||||
NDebugOverlay::Cross3D( m_approachPoint[i].m_pos, 10.0f, 100, 100, 100, true, 0.1f );
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Find the approach point that is nearest to our current path, ahead of us
|
||||
*/
|
||||
bool CCSBot::FindApproachPointNearestPath( Vector *pos )
|
||||
{
|
||||
if (!HasPath())
|
||||
return false;
|
||||
|
||||
// make sure approach points are accurate
|
||||
ComputeApproachPoints();
|
||||
|
||||
if (m_approachPointCount == 0)
|
||||
return false;
|
||||
|
||||
Vector target = Vector( 0, 0, 0 ), close;
|
||||
float targetRangeSq = 0.0f;
|
||||
bool found = false;
|
||||
|
||||
int start = m_pathIndex;
|
||||
int end = m_pathLength;
|
||||
|
||||
//
|
||||
// We dont want the strictly closest point, but the farthest approach point
|
||||
// from us that is near our path
|
||||
//
|
||||
const float nearPathSq = 10000.0f; // (100)
|
||||
|
||||
for( int i=0; i<m_approachPointCount; ++i )
|
||||
{
|
||||
if (FindClosestPointOnPath( m_approachPoint[i].m_pos, start, end, &close ) == false)
|
||||
continue;
|
||||
|
||||
float rangeSq = (m_approachPoint[i].m_pos - close).LengthSqr();
|
||||
if (rangeSq > nearPathSq)
|
||||
continue;
|
||||
|
||||
if (rangeSq > targetRangeSq)
|
||||
{
|
||||
target = close;
|
||||
targetRangeSq = rangeSq;
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (found)
|
||||
{
|
||||
*pos = target + Vector( 0, 0, HalfHumanHeight );
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Return true if we are at the/an enemy spawn right now
|
||||
*/
|
||||
bool CCSBot::IsAtEnemySpawn( void ) const
|
||||
{
|
||||
CBaseEntity *spot;
|
||||
const char *spawnName = (GetTeamNumber() == TEAM_TERRORIST) ? "info_player_counterterrorist" : "info_player_terrorist";
|
||||
|
||||
// check if we are at any of the enemy's spawn points
|
||||
for( spot = gEntList.FindEntityByClassname( NULL, spawnName ); spot; spot = gEntList.FindEntityByClassname( spot, spawnName ) )
|
||||
{
|
||||
CNavArea *area = TheNavMesh->GetNearestNavArea( spot->WorldSpaceCenter() );
|
||||
if (area && GetLastKnownArea() == area)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,346 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose:
|
||||
//
|
||||
// $NoKeywords: $
|
||||
//=============================================================================//
|
||||
|
||||
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
|
||||
|
||||
#include "cbase.h"
|
||||
#include "cs_bot.h"
|
||||
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
extern int gmsgBotVoice;
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Returns true if the radio message is an order to do something
|
||||
* NOTE: "Report in" is not considered a "command" because it doesnt ask the bot to go somewhere, or change its mind
|
||||
*/
|
||||
bool CCSBot::IsRadioCommand( RadioType event ) const
|
||||
{
|
||||
if (event == RADIO_AFFIRMATIVE ||
|
||||
event == RADIO_NEGATIVE ||
|
||||
event == RADIO_ENEMY_SPOTTED ||
|
||||
event == RADIO_SECTOR_CLEAR ||
|
||||
event == RADIO_REPORTING_IN ||
|
||||
event == RADIO_REPORT_IN_TEAM ||
|
||||
event == RADIO_ENEMY_DOWN ||
|
||||
event == RADIO_INVALID )
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Respond to radio commands from HUMAN players
|
||||
*/
|
||||
void CCSBot::RespondToRadioCommands( void )
|
||||
{
|
||||
// bots use the chatter system to respond to each other
|
||||
if (m_radioSubject != NULL && m_radioSubject->IsPlayer())
|
||||
{
|
||||
CCSPlayer *player = m_radioSubject;
|
||||
if (player->IsBot())
|
||||
{
|
||||
m_lastRadioCommand = RADIO_INVALID;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_lastRadioCommand == RADIO_INVALID)
|
||||
return;
|
||||
|
||||
// a human player has issued a radio command
|
||||
GetChatter()->ResetRadioSilenceDuration();
|
||||
|
||||
|
||||
// if we are doing something important, ignore the radio
|
||||
// unless it is a "report in" request - we can do that while we continue to do other things
|
||||
/// @todo Create "uninterruptable" flag
|
||||
if (m_lastRadioCommand != RADIO_REPORT_IN_TEAM)
|
||||
{
|
||||
if (IsBusy())
|
||||
{
|
||||
// consume command
|
||||
m_lastRadioCommand = RADIO_INVALID;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// wait for reaction time before responding
|
||||
// delay needs to be long enough for the radio message we're responding to to finish
|
||||
float respondTime = 1.0f + 2.0f * GetProfile()->GetReactionTime();
|
||||
if (IsRogue())
|
||||
respondTime += 2.0f;
|
||||
|
||||
if (gpGlobals->curtime - m_lastRadioRecievedTimestamp < respondTime)
|
||||
return;
|
||||
|
||||
// rogues won't follow commands, unless already following the player
|
||||
if (!IsFollowing() && IsRogue())
|
||||
{
|
||||
if (IsRadioCommand( m_lastRadioCommand ))
|
||||
{
|
||||
GetChatter()->Negative();
|
||||
}
|
||||
|
||||
// consume command
|
||||
m_lastRadioCommand = RADIO_INVALID;
|
||||
return;
|
||||
}
|
||||
|
||||
CCSPlayer *player = m_radioSubject;
|
||||
if (player == NULL)
|
||||
return;
|
||||
|
||||
// respond to command
|
||||
bool canDo = false;
|
||||
const float inhibitAutoFollowDuration = 60.0f;
|
||||
switch( m_lastRadioCommand )
|
||||
{
|
||||
case RADIO_REPORT_IN_TEAM:
|
||||
{
|
||||
GetChatter()->ReportingIn();
|
||||
break;
|
||||
}
|
||||
|
||||
case RADIO_FOLLOW_ME:
|
||||
case RADIO_COVER_ME:
|
||||
case RADIO_STICK_TOGETHER_TEAM:
|
||||
case RADIO_REGROUP_TEAM:
|
||||
{
|
||||
if (!IsFollowing())
|
||||
{
|
||||
Follow( player );
|
||||
player->AllowAutoFollow();
|
||||
canDo = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case RADIO_ENEMY_SPOTTED:
|
||||
case RADIO_NEED_BACKUP:
|
||||
case RADIO_TAKING_FIRE:
|
||||
if (!IsFollowing())
|
||||
{
|
||||
Follow( player );
|
||||
GetChatter()->Say( "OnMyWay" );
|
||||
player->AllowAutoFollow();
|
||||
canDo = false;
|
||||
}
|
||||
break;
|
||||
|
||||
case RADIO_TEAM_FALL_BACK:
|
||||
{
|
||||
if (TryToRetreat())
|
||||
canDo = true;
|
||||
break;
|
||||
}
|
||||
|
||||
case RADIO_HOLD_THIS_POSITION:
|
||||
{
|
||||
// find the leader's area
|
||||
SetTask( HOLD_POSITION );
|
||||
StopFollowing();
|
||||
player->InhibitAutoFollow( inhibitAutoFollowDuration );
|
||||
Hide( TheNavMesh->GetNearestNavArea( m_radioPosition ) );
|
||||
canDo = true;
|
||||
break;
|
||||
}
|
||||
|
||||
case RADIO_GO_GO_GO:
|
||||
case RADIO_STORM_THE_FRONT:
|
||||
StopFollowing();
|
||||
Hunt();
|
||||
canDo = true;
|
||||
player->InhibitAutoFollow( inhibitAutoFollowDuration );
|
||||
break;
|
||||
|
||||
case RADIO_GET_OUT_OF_THERE:
|
||||
if (TheCSBots()->IsBombPlanted())
|
||||
{
|
||||
EscapeFromBomb();
|
||||
player->InhibitAutoFollow( inhibitAutoFollowDuration );
|
||||
canDo = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case RADIO_SECTOR_CLEAR:
|
||||
{
|
||||
// if this is a defusal scenario, and the bomb is planted,
|
||||
// and a human player cleared a bombsite, check it off our list too
|
||||
if (TheCSBots()->GetScenario() == CCSBotManager::SCENARIO_DEFUSE_BOMB)
|
||||
{
|
||||
if (GetTeamNumber() == TEAM_CT && TheCSBots()->IsBombPlanted())
|
||||
{
|
||||
const CCSBotManager::Zone *zone = TheCSBots()->GetClosestZone( player );
|
||||
|
||||
if (zone)
|
||||
{
|
||||
GetGameState()->ClearBombsite( zone->m_index );
|
||||
|
||||
// if we are huting for the planted bomb, re-select bombsite
|
||||
if (GetTask() == FIND_TICKING_BOMB)
|
||||
Idle();
|
||||
|
||||
canDo = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
// ignore all other radio commands for now
|
||||
return;
|
||||
}
|
||||
|
||||
if (canDo)
|
||||
{
|
||||
// affirmative
|
||||
GetChatter()->Affirmative();
|
||||
|
||||
// if we agreed to follow a new command, put away our grenade
|
||||
if (IsRadioCommand( m_lastRadioCommand ) && IsUsingGrenade())
|
||||
{
|
||||
EquipBestWeapon();
|
||||
}
|
||||
}
|
||||
|
||||
// consume command
|
||||
m_lastRadioCommand = RADIO_INVALID;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Decide if we should move to help the player, return true if we will
|
||||
*/
|
||||
bool CCSBot::RespondToHelpRequest( CCSPlayer *them, Place place, float maxRange )
|
||||
{
|
||||
if (IsRogue())
|
||||
return false;
|
||||
|
||||
// if we're busy, ignore
|
||||
if (IsBusy())
|
||||
return false;
|
||||
|
||||
Vector themOrigin = GetCentroid( them );
|
||||
|
||||
// if we are too far away, ignore
|
||||
if (maxRange > 0.0f)
|
||||
{
|
||||
// compute actual travel distance
|
||||
PathCost cost(this);
|
||||
float travelDistance = NavAreaTravelDistance( m_lastKnownArea, TheNavMesh->GetNearestNavArea( themOrigin ), cost );
|
||||
if (travelDistance < 0.0f)
|
||||
return false;
|
||||
|
||||
if (travelDistance > maxRange)
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
if (place == UNDEFINED_PLACE)
|
||||
{
|
||||
// if we have no "place" identifier, go directly to them
|
||||
|
||||
// if we are already there, ignore
|
||||
float rangeSq = (them->GetAbsOrigin() - GetAbsOrigin()).LengthSqr();
|
||||
const float close = 750.0f * 750.0f;
|
||||
if (rangeSq < close)
|
||||
return true;
|
||||
|
||||
MoveTo( themOrigin, FASTEST_ROUTE );
|
||||
}
|
||||
else
|
||||
{
|
||||
// if we are already there, ignore
|
||||
if (GetPlace() == place)
|
||||
return true;
|
||||
|
||||
// go to where help is needed
|
||||
const Vector *pos = GetRandomSpotAtPlace( place );
|
||||
if (pos)
|
||||
{
|
||||
MoveTo( *pos, FASTEST_ROUTE );
|
||||
}
|
||||
else
|
||||
{
|
||||
MoveTo( themOrigin, FASTEST_ROUTE );
|
||||
}
|
||||
}
|
||||
|
||||
// acknowledge
|
||||
GetChatter()->Say( "OnMyWay" );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Send a radio message
|
||||
*/
|
||||
void CCSBot::SendRadioMessage( RadioType event )
|
||||
{
|
||||
// make sure this is a radio event
|
||||
if (event <= RADIO_START_1 || event >= RADIO_END)
|
||||
return;
|
||||
|
||||
PrintIfWatched( "%3.1f: SendRadioMessage( %s )\n", gpGlobals->curtime, RadioEventName[ event ] );
|
||||
|
||||
// note the time the message was sent
|
||||
TheCSBots()->SetRadioMessageTimestamp( event, GetTeamNumber() );
|
||||
|
||||
m_lastRadioSentTimestamp = gpGlobals->curtime;
|
||||
|
||||
char slot[2];
|
||||
slot[1] = '\000';
|
||||
|
||||
if (event > RADIO_START_1 && event < RADIO_START_2)
|
||||
{
|
||||
HandleMenu_Radio1( event - RADIO_START_1 );
|
||||
}
|
||||
else if (event > RADIO_START_2 && event < RADIO_START_3)
|
||||
{
|
||||
HandleMenu_Radio2( event - RADIO_START_2 );
|
||||
}
|
||||
else
|
||||
{
|
||||
HandleMenu_Radio3( event - RADIO_START_3 );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Send voice chatter. Also sends the entindex and duration for voice feedback.
|
||||
*/
|
||||
void CCSBot::SpeakAudio( const char *voiceFilename, float duration, int pitch )
|
||||
{
|
||||
if( !IsAlive() )
|
||||
return;
|
||||
|
||||
if ( IsObserver() )
|
||||
return;
|
||||
|
||||
CRecipientFilter filter;
|
||||
ConstructRadioFilter( filter );
|
||||
|
||||
UserMessageBegin ( filter, "RawAudio" );
|
||||
WRITE_BYTE( pitch );
|
||||
WRITE_BYTE( entindex() );
|
||||
WRITE_FLOAT( duration );
|
||||
WRITE_STRING( voiceFilename );
|
||||
MessageEnd();
|
||||
|
||||
GetChatter()->ResetRadioSilenceDuration();
|
||||
|
||||
m_voiceEndTimestamp = gpGlobals->curtime + duration;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,693 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose:
|
||||
//
|
||||
// $NoKeywords: $
|
||||
//=============================================================================//
|
||||
|
||||
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
|
||||
|
||||
#include "cbase.h"
|
||||
#include "cs_bot.h"
|
||||
#include "cs_nav_path.h"
|
||||
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* This method is the ONLY legal way to change a bot's current state
|
||||
*/
|
||||
void CCSBot::SetState( BotState *state )
|
||||
{
|
||||
PrintIfWatched( "%s: SetState: %s -> %s\n", GetPlayerName(), (m_state) ? m_state->GetName() : "NULL", state->GetName() );
|
||||
|
||||
/*
|
||||
if ( IsDefusingBomb() )
|
||||
{
|
||||
const Vector *bombPos = GetGameState()->GetBombPosition();
|
||||
if ( bombPos != NULL )
|
||||
{
|
||||
if ( TheCSBots()->GetBombDefuser() == this )
|
||||
{
|
||||
if ( TheCSBots()->IsBombPlanted() )
|
||||
{
|
||||
Msg( "Bot %s is switching from defusing the bomb to %s\n",
|
||||
GetPlayerName(), state->GetName() );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
// if we changed state from within the special Attack state, we are no longer attacking
|
||||
if (m_isAttacking)
|
||||
StopAttacking();
|
||||
|
||||
if (m_state)
|
||||
m_state->OnExit( this );
|
||||
|
||||
state->OnEnter( this );
|
||||
|
||||
m_state = state;
|
||||
m_stateTimestamp = gpGlobals->curtime;
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
void CCSBot::Idle( void )
|
||||
{
|
||||
SetTask( SEEK_AND_DESTROY );
|
||||
SetState( &m_idleState );
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
void CCSBot::EscapeFromBomb( void )
|
||||
{
|
||||
SetTask( ESCAPE_FROM_BOMB );
|
||||
SetState( &m_escapeFromBombState );
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
void CCSBot::Follow( CCSPlayer *player )
|
||||
{
|
||||
if (player == NULL)
|
||||
return;
|
||||
|
||||
// note when we began following
|
||||
if (!m_isFollowing || m_leader != player)
|
||||
m_followTimestamp = gpGlobals->curtime;
|
||||
|
||||
m_isFollowing = true;
|
||||
m_leader = player;
|
||||
|
||||
SetTask( FOLLOW );
|
||||
m_followState.SetLeader( player );
|
||||
SetState( &m_followState );
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Continue following our leader after finishing what we were doing
|
||||
*/
|
||||
void CCSBot::ContinueFollowing( void )
|
||||
{
|
||||
SetTask( FOLLOW );
|
||||
|
||||
m_followState.SetLeader( m_leader );
|
||||
|
||||
SetState( &m_followState );
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Stop following
|
||||
*/
|
||||
void CCSBot::StopFollowing( void )
|
||||
{
|
||||
m_isFollowing = false;
|
||||
m_leader = NULL;
|
||||
m_allowAutoFollowTime = gpGlobals->curtime + 10.0f;
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Begin process of rescuing hostages
|
||||
*/
|
||||
void CCSBot::RescueHostages( void )
|
||||
{
|
||||
SetTask( RESCUE_HOSTAGES );
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Use the entity
|
||||
*/
|
||||
void CCSBot::UseEntity( CBaseEntity *entity )
|
||||
{
|
||||
m_useEntityState.SetEntity( entity );
|
||||
SetState( &m_useEntityState );
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Open the door.
|
||||
* This assumes the bot is directly in front of the door with no obstructions.
|
||||
* NOTE: This state is special, like Attack, in that it suspends the current behavior and returns to it when done.
|
||||
*/
|
||||
void CCSBot::OpenDoor( CBaseEntity *door )
|
||||
{
|
||||
m_openDoorState.SetDoor( door );
|
||||
m_isOpeningDoor = true;
|
||||
m_openDoorState.OnEnter( this );
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* DEPRECATED: Use TryToHide() instead.
|
||||
* Move to a hiding place.
|
||||
* If 'searchFromArea' is non-NULL, hiding spots are looked for from that area first.
|
||||
*/
|
||||
void CCSBot::Hide( CNavArea *searchFromArea, float duration, float hideRange, bool holdPosition )
|
||||
{
|
||||
DestroyPath();
|
||||
|
||||
CNavArea *source;
|
||||
Vector sourcePos;
|
||||
if (searchFromArea)
|
||||
{
|
||||
source = searchFromArea;
|
||||
sourcePos = searchFromArea->GetCenter();
|
||||
}
|
||||
else
|
||||
{
|
||||
source = m_lastKnownArea;
|
||||
sourcePos = GetCentroid( this );
|
||||
}
|
||||
|
||||
if (source == NULL)
|
||||
{
|
||||
PrintIfWatched( "Hide from area is NULL.\n" );
|
||||
Idle();
|
||||
return;
|
||||
}
|
||||
|
||||
m_hideState.SetSearchArea( source );
|
||||
m_hideState.SetSearchRange( hideRange );
|
||||
m_hideState.SetDuration( duration );
|
||||
m_hideState.SetHoldPosition( holdPosition );
|
||||
|
||||
// search around source area for a good hiding spot
|
||||
Vector useSpot;
|
||||
|
||||
const Vector *pos = FindNearbyHidingSpot( this, sourcePos, hideRange, IsSniper() );
|
||||
if (pos == NULL)
|
||||
{
|
||||
PrintIfWatched( "No available hiding spots.\n" );
|
||||
// hide at our current position
|
||||
useSpot = GetCentroid( this );
|
||||
}
|
||||
else
|
||||
{
|
||||
useSpot = *pos;
|
||||
}
|
||||
|
||||
m_hideState.SetHidingSpot( useSpot );
|
||||
|
||||
// build a path to our new hiding spot
|
||||
if (ComputePath( useSpot, FASTEST_ROUTE ) == false)
|
||||
{
|
||||
PrintIfWatched( "Can't pathfind to hiding spot\n" );
|
||||
Idle();
|
||||
return;
|
||||
}
|
||||
|
||||
SetState( &m_hideState );
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Move to the given hiding place
|
||||
*/
|
||||
void CCSBot::Hide( const Vector &hidingSpot, float duration, bool holdPosition )
|
||||
{
|
||||
CNavArea *hideArea = TheNavMesh->GetNearestNavArea( hidingSpot );
|
||||
if (hideArea == NULL)
|
||||
{
|
||||
PrintIfWatched( "Hiding spot off nav mesh\n" );
|
||||
Idle();
|
||||
return;
|
||||
}
|
||||
|
||||
DestroyPath();
|
||||
|
||||
m_hideState.SetSearchArea( hideArea );
|
||||
m_hideState.SetSearchRange( 750.0f );
|
||||
m_hideState.SetDuration( duration );
|
||||
m_hideState.SetHoldPosition( holdPosition );
|
||||
m_hideState.SetHidingSpot( hidingSpot );
|
||||
|
||||
// build a path to our new hiding spot
|
||||
if (ComputePath( hidingSpot, FASTEST_ROUTE ) == false)
|
||||
{
|
||||
PrintIfWatched( "Can't pathfind to hiding spot\n" );
|
||||
Idle();
|
||||
return;
|
||||
}
|
||||
|
||||
SetState( &m_hideState );
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Try to hide nearby. Return true if hiding, false if can't hide here.
|
||||
* If 'searchFromArea' is non-NULL, hiding spots are looked for from that area first.
|
||||
*/
|
||||
bool CCSBot::TryToHide( CNavArea *searchFromArea, float duration, float hideRange, bool holdPosition, bool useNearest )
|
||||
{
|
||||
CNavArea *source;
|
||||
Vector sourcePos;
|
||||
if (searchFromArea)
|
||||
{
|
||||
source = searchFromArea;
|
||||
sourcePos = searchFromArea->GetCenter();
|
||||
}
|
||||
else
|
||||
{
|
||||
source = m_lastKnownArea;
|
||||
sourcePos = GetCentroid( this );
|
||||
}
|
||||
|
||||
if (source == NULL)
|
||||
{
|
||||
PrintIfWatched( "Hide from area is NULL.\n" );
|
||||
return false;
|
||||
}
|
||||
|
||||
m_hideState.SetSearchArea( source );
|
||||
m_hideState.SetSearchRange( hideRange );
|
||||
m_hideState.SetDuration( duration );
|
||||
m_hideState.SetHoldPosition( holdPosition );
|
||||
|
||||
// search around source area for a good hiding spot
|
||||
const Vector *pos = FindNearbyHidingSpot( this, sourcePos, hideRange, IsSniper(), useNearest );
|
||||
if (pos == NULL)
|
||||
{
|
||||
PrintIfWatched( "No available hiding spots.\n" );
|
||||
return false;
|
||||
}
|
||||
|
||||
m_hideState.SetHidingSpot( *pos );
|
||||
|
||||
// build a path to our new hiding spot
|
||||
if (ComputePath( *pos, FASTEST_ROUTE ) == false)
|
||||
{
|
||||
PrintIfWatched( "Can't pathfind to hiding spot\n" );
|
||||
return false;
|
||||
}
|
||||
|
||||
SetState( &m_hideState );
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Retreat to a nearby hiding spot, away from enemies
|
||||
*/
|
||||
bool CCSBot::TryToRetreat( float maxRange, float duration )
|
||||
{
|
||||
const Vector *spot = FindNearbyRetreatSpot( this, maxRange );
|
||||
if (spot)
|
||||
{
|
||||
// ignore enemies for a second to give us time to hide
|
||||
// reaching our hiding spot clears our disposition
|
||||
IgnoreEnemies( 10.0f );
|
||||
|
||||
if (duration < 0.0f)
|
||||
{
|
||||
duration = RandomFloat( 3.0f, 15.0f );
|
||||
}
|
||||
|
||||
StandUp();
|
||||
Run();
|
||||
Hide( *spot, duration );
|
||||
|
||||
PrintIfWatched( "Retreating to a safe spot!\n" );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
void CCSBot::Hunt( void )
|
||||
{
|
||||
SetState( &m_huntState );
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Attack our the given victim
|
||||
* NOTE: Attacking does not change our task.
|
||||
*/
|
||||
void CCSBot::Attack( CCSPlayer *victim )
|
||||
{
|
||||
if (victim == NULL)
|
||||
return;
|
||||
|
||||
// zombies never attack
|
||||
if (cv_bot_zombie.GetBool())
|
||||
return;
|
||||
|
||||
// cannot attack if we are reloading
|
||||
if (IsReloading())
|
||||
return;
|
||||
|
||||
// change enemy
|
||||
SetBotEnemy( victim );
|
||||
|
||||
//
|
||||
// Do not "re-enter" the attack state if we are already attacking
|
||||
//
|
||||
if (IsAttacking())
|
||||
return;
|
||||
|
||||
// if we're holding a grenade, throw it at the victim
|
||||
if (IsUsingGrenade())
|
||||
{
|
||||
// throw towards their feet
|
||||
ThrowGrenade( victim->GetAbsOrigin() );
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// if we are currently hiding, increase our chances of crouching and holding position
|
||||
if (IsAtHidingSpot())
|
||||
m_attackState.SetCrouchAndHold( (RandomFloat( 0.0f, 100.0f ) < 60.0f) ? true : false );
|
||||
else
|
||||
m_attackState.SetCrouchAndHold( false );
|
||||
|
||||
//SetState( &m_attackState );
|
||||
//PrintIfWatched( "ATTACK BEGIN (reaction time = %g (+ update time), surprise time = %g, attack delay = %g)\n",
|
||||
// GetProfile()->GetReactionTime(), m_surpriseDelay, GetProfile()->GetAttackDelay() );
|
||||
m_isAttacking = true;
|
||||
m_attackState.OnEnter( this );
|
||||
|
||||
|
||||
Vector victimOrigin = GetCentroid( victim );
|
||||
|
||||
// cheat a bit and give the bot the initial location of its victim
|
||||
m_lastEnemyPosition = victimOrigin;
|
||||
m_lastSawEnemyTimestamp = gpGlobals->curtime;
|
||||
m_aimSpreadTimestamp = gpGlobals->curtime;
|
||||
|
||||
// compute the angle difference between where are looking, and where we need to look
|
||||
Vector toEnemy = victimOrigin - GetCentroid( this );
|
||||
|
||||
QAngle idealAngle;
|
||||
VectorAngles( toEnemy, idealAngle );
|
||||
|
||||
float deltaYaw = (float)fabs(m_lookYaw - idealAngle.y);
|
||||
|
||||
while( deltaYaw > 180.0f )
|
||||
deltaYaw -= 360.0f;
|
||||
|
||||
if (deltaYaw < 0.0f)
|
||||
deltaYaw = -deltaYaw;
|
||||
|
||||
// immediately aim at enemy - accuracy penalty depending on how far we must turn to aim
|
||||
// accuracy is halved if we have to turn 180 degrees
|
||||
float turn = deltaYaw / 180.0f;
|
||||
float accuracy = GetProfile()->GetSkill() / (1.0f + turn);
|
||||
|
||||
SetAimOffset( accuracy );
|
||||
|
||||
// define time when aim offset will automatically be updated
|
||||
// longer time the more we had to turn (surprise)
|
||||
m_aimOffsetTimestamp = gpGlobals->curtime + RandomFloat( 0.25f + turn, 1.5f );
|
||||
|
||||
// forget any look at targets we have
|
||||
ClearLookAt();
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Exit the Attack state
|
||||
*/
|
||||
void CCSBot::StopAttacking( void )
|
||||
{
|
||||
PrintIfWatched( "ATTACK END\n" );
|
||||
m_attackState.OnExit( this );
|
||||
m_isAttacking = false;
|
||||
|
||||
// if we are following someone, go to the Idle state after the attack to decide whether we still want to follow
|
||||
if (IsFollowing())
|
||||
{
|
||||
Idle();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
bool CCSBot::IsAttacking( void ) const
|
||||
{
|
||||
return m_isAttacking;
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Return true if we are escaping from the bomb
|
||||
*/
|
||||
bool CCSBot::IsEscapingFromBomb( void ) const
|
||||
{
|
||||
if (m_state == static_cast<const BotState *>( &m_escapeFromBombState ))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Return true if we are defusing the bomb
|
||||
*/
|
||||
bool CCSBot::IsDefusingBomb( void ) const
|
||||
{
|
||||
if (m_state == static_cast<const BotState *>( &m_defuseBombState ))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Return true if we are hiding
|
||||
*/
|
||||
bool CCSBot::IsHiding( void ) const
|
||||
{
|
||||
if (m_state == static_cast<const BotState *>( &m_hideState ))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Return true if we are hiding and at our hiding spot
|
||||
*/
|
||||
bool CCSBot::IsAtHidingSpot( void ) const
|
||||
{
|
||||
if (!IsHiding())
|
||||
return false;
|
||||
|
||||
return m_hideState.IsAtSpot();
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Return number of seconds we have been at our current hiding spot
|
||||
*/
|
||||
float CCSBot::GetHidingTime( void ) const
|
||||
{
|
||||
if (IsHiding())
|
||||
{
|
||||
return m_hideState.GetHideTime();
|
||||
}
|
||||
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Return true if we are huting
|
||||
*/
|
||||
bool CCSBot::IsHunting( void ) const
|
||||
{
|
||||
if (m_state == static_cast<const BotState *>( &m_huntState ))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Return true if we are in the MoveTo state
|
||||
*/
|
||||
bool CCSBot::IsMovingTo( void ) const
|
||||
{
|
||||
if (m_state == static_cast<const BotState *>( &m_moveToState ))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Return true if we are buying
|
||||
*/
|
||||
bool CCSBot::IsBuying( void ) const
|
||||
{
|
||||
if (m_state == static_cast<const BotState *>( &m_buyState ))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
bool CCSBot::IsInvestigatingNoise( void ) const
|
||||
{
|
||||
if (m_state == static_cast<const BotState *>( &m_investigateNoiseState ))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Move to potentially distant position
|
||||
*/
|
||||
void CCSBot::MoveTo( const Vector &pos, RouteType route )
|
||||
{
|
||||
m_moveToState.SetGoalPosition( pos );
|
||||
m_moveToState.SetRouteType( route );
|
||||
SetState( &m_moveToState );
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
void CCSBot::PlantBomb( void )
|
||||
{
|
||||
SetState( &m_plantBombState );
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Bomb has been dropped - go get it
|
||||
*/
|
||||
void CCSBot::FetchBomb( void )
|
||||
{
|
||||
SetState( &m_fetchBombState );
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
void CCSBot::DefuseBomb( void )
|
||||
{
|
||||
SetState( &m_defuseBombState );
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Investigate recent enemy noise
|
||||
*/
|
||||
void CCSBot::InvestigateNoise( void )
|
||||
{
|
||||
SetState( &m_investigateNoiseState );
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
void CCSBot::Buy( void )
|
||||
{
|
||||
SetState( &m_buyState );
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Move to a hiding spot and wait for initial encounter with enemy team.
|
||||
* Return false if can't do this behavior (ie: no hiding spots available).
|
||||
*/
|
||||
bool CCSBot::MoveToInitialEncounter( void )
|
||||
{
|
||||
int myTeam = GetTeamNumber();
|
||||
int enemyTeam = OtherTeam( myTeam );
|
||||
|
||||
// build a path to an enemy spawn point
|
||||
CBaseEntity *enemySpawn = TheCSBots()->GetRandomSpawn( enemyTeam );
|
||||
|
||||
if (enemySpawn == NULL)
|
||||
{
|
||||
PrintIfWatched( "MoveToInitialEncounter: No enemy spawn points?\n" );
|
||||
return false;
|
||||
}
|
||||
|
||||
// build a path from us to the enemy spawn
|
||||
CCSNavPath path;
|
||||
PathCost cost( this, FASTEST_ROUTE );
|
||||
path.Compute( WorldSpaceCenter(), enemySpawn->GetAbsOrigin(), cost );
|
||||
|
||||
if (!path.IsValid())
|
||||
{
|
||||
PrintIfWatched( "MoveToInitialEncounter: Pathfind failed.\n" );
|
||||
return false;
|
||||
}
|
||||
|
||||
// find battlefront area where teams will first meet along this path
|
||||
int i;
|
||||
for( i=0; i<path.GetSegmentCount(); ++i )
|
||||
{
|
||||
if (path[i]->area->GetEarliestOccupyTime( myTeam ) > path[i]->area->GetEarliestOccupyTime( enemyTeam ))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (i == path.GetSegmentCount())
|
||||
{
|
||||
PrintIfWatched( "MoveToInitialEncounter: Can't find battlefront!\n" );
|
||||
return false;
|
||||
}
|
||||
|
||||
/// @todo Remove this evil side-effect
|
||||
SetInitialEncounterArea( path[i]->area );
|
||||
|
||||
// find a hiding spot on our side of the battlefront that has LOS to it
|
||||
const float maxRange = 1500.0f;
|
||||
const HidingSpot *spot = FindInitialEncounterSpot( this, path[i]->area->GetCenter(), path[i]->area->GetEarliestOccupyTime( enemyTeam ), maxRange, IsSniper() );
|
||||
|
||||
if (spot == NULL)
|
||||
{
|
||||
PrintIfWatched( "MoveToInitialEncounter: Can't find a hiding spot\n" );
|
||||
return false;
|
||||
}
|
||||
|
||||
float timeToWait = path[i]->area->GetEarliestOccupyTime( enemyTeam ) - spot->GetArea()->GetEarliestOccupyTime( myTeam );
|
||||
float minWaitTime = 4.0f * GetProfile()->GetAggression() + 3.0f;
|
||||
if (timeToWait < minWaitTime)
|
||||
{
|
||||
timeToWait = minWaitTime;
|
||||
}
|
||||
|
||||
Hide( spot->GetPosition(), timeToWait );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,22 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose:
|
||||
//
|
||||
// $NoKeywords: $
|
||||
//=============================================================================//
|
||||
|
||||
// Author: Michael Booth (mike@turtlerockstudios.com), 2003
|
||||
// Author: Matthew D. Campbell (matt@turtlerockstudios.com), 2003
|
||||
|
||||
#include "cbase.h"
|
||||
#include "cs_bot.h"
|
||||
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------
|
||||
//
|
||||
// Temporary solution until we have time to build something more elegant
|
||||
// Very nasty - need to keep in sync with the buy aliases
|
||||
// NOTE: Array must be NULL terminated
|
||||
//
|
||||
@@ -0,0 +1,767 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose: Encapsulation of the current scenario/game state. Allows each bot imperfect knowledge.
|
||||
//
|
||||
// $NoKeywords: $
|
||||
//=============================================================================//
|
||||
|
||||
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
|
||||
|
||||
#include "cbase.h"
|
||||
#include "KeyValues.h"
|
||||
|
||||
#include "cs_bot.h"
|
||||
#include "cs_gamestate.h"
|
||||
#include "cs_simple_hostage.h"
|
||||
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
CSGameState::CSGameState( CCSBot *owner )
|
||||
{
|
||||
m_owner = owner;
|
||||
m_isRoundOver = false;
|
||||
|
||||
m_bombState = MOVING;
|
||||
m_lastSawBomber.Invalidate();
|
||||
m_lastSawLooseBomb.Invalidate();
|
||||
m_isPlantedBombPosKnown = false;
|
||||
m_plantedBombsite = UNKNOWN;
|
||||
|
||||
m_bombsiteCount = 0;
|
||||
m_bombsiteSearchIndex = 0;
|
||||
|
||||
for( int i=0; i<MAX_HOSTAGES; ++i )
|
||||
{
|
||||
m_hostage[i].hostage = NULL;
|
||||
m_hostage[i].isValid = false;
|
||||
m_hostage[i].isAlive = false;
|
||||
m_hostage[i].isFree = true;
|
||||
m_hostage[i].knownPos = Vector( 0, 0, 0 );
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Reset at round start
|
||||
*/
|
||||
void CSGameState::Reset( void )
|
||||
{
|
||||
m_isRoundOver = false;
|
||||
|
||||
// bomb -----------------------------------------------------------------------
|
||||
m_bombState = MOVING;
|
||||
m_lastSawBomber.Invalidate();
|
||||
m_lastSawLooseBomb.Invalidate();
|
||||
m_isPlantedBombPosKnown = false;
|
||||
m_plantedBombsite = UNKNOWN;
|
||||
|
||||
m_bombsiteCount = TheCSBots()->GetZoneCount();
|
||||
|
||||
int i;
|
||||
for( i=0; i<m_bombsiteCount; ++i )
|
||||
{
|
||||
m_isBombsiteClear[i] = false;
|
||||
m_bombsiteSearchOrder[i] = i;
|
||||
}
|
||||
|
||||
// shuffle the bombsite search order
|
||||
// allows T's to plant at random site, and TEAM_CT's to search in a random order
|
||||
// NOTE: VS6 std::random_shuffle() doesn't work well with an array of two elements (most maps)
|
||||
for( i=0; i < m_bombsiteCount; ++i )
|
||||
{
|
||||
int swap = m_bombsiteSearchOrder[i];
|
||||
int rnd = RandomInt( i, m_bombsiteCount-1 );
|
||||
m_bombsiteSearchOrder[i] = m_bombsiteSearchOrder[ rnd ];
|
||||
m_bombsiteSearchOrder[ rnd ] = swap;
|
||||
}
|
||||
|
||||
m_bombsiteSearchIndex = 0;
|
||||
|
||||
// hostage ---------------------------------------------------------------------
|
||||
InitializeHostageInfo();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Update game state based on events we have received
|
||||
*/
|
||||
void CSGameState::OnHostageRescuedAll( IGameEvent *event )
|
||||
{
|
||||
m_allHostagesRescued = true;
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Update game state based on events we have received
|
||||
*/
|
||||
void CSGameState::OnRoundEnd( IGameEvent *event )
|
||||
{
|
||||
m_isRoundOver = true;
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Update game state based on events we have received
|
||||
*/
|
||||
void CSGameState::OnRoundStart( IGameEvent *event )
|
||||
{
|
||||
Reset();
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Update game state based on events we have received
|
||||
*/
|
||||
void CSGameState::OnBombPlanted( IGameEvent *event )
|
||||
{
|
||||
// change state - the event is announced to everyone
|
||||
SetBombState( PLANTED );
|
||||
|
||||
CBasePlayer *plantingPlayer = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
|
||||
|
||||
// Terrorists always know where the bomb is
|
||||
if (m_owner->GetTeamNumber() == TEAM_TERRORIST && plantingPlayer)
|
||||
{
|
||||
UpdatePlantedBomb( plantingPlayer->GetAbsOrigin() );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Update game state based on events we have received
|
||||
*/
|
||||
void CSGameState::OnBombDefused( IGameEvent *event )
|
||||
{
|
||||
// change state - the event is announced to everyone
|
||||
SetBombState( DEFUSED );
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Update game state based on events we have received
|
||||
*/
|
||||
void CSGameState::OnBombExploded( IGameEvent *event )
|
||||
{
|
||||
// change state - the event is announced to everyone
|
||||
SetBombState( EXPLODED );
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* True if round has been won or lost (but not yet reset)
|
||||
*/
|
||||
bool CSGameState::IsRoundOver( void ) const
|
||||
{
|
||||
return m_isRoundOver;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
void CSGameState::SetBombState( BombState state )
|
||||
{
|
||||
// if state changed, reset "last seen" timestamps
|
||||
if (m_bombState != state)
|
||||
{
|
||||
m_bombState = state;
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
void CSGameState::UpdateLooseBomb( const Vector &pos )
|
||||
{
|
||||
m_looseBombPos = pos;
|
||||
m_lastSawLooseBomb.Reset();
|
||||
|
||||
// we saw the loose bomb, update our state
|
||||
SetBombState( LOOSE );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
float CSGameState::TimeSinceLastSawLooseBomb( void ) const
|
||||
{
|
||||
return m_lastSawLooseBomb.GetElapsedTime();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
bool CSGameState::IsLooseBombLocationKnown( void ) const
|
||||
{
|
||||
if (m_bombState != LOOSE)
|
||||
return false;
|
||||
|
||||
return (m_lastSawLooseBomb.HasStarted()) ? true : false;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
void CSGameState::UpdateBomber( const Vector &pos )
|
||||
{
|
||||
m_bomberPos = pos;
|
||||
m_lastSawBomber.Reset();
|
||||
|
||||
// we saw the bomber, update our state
|
||||
SetBombState( MOVING );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
float CSGameState::TimeSinceLastSawBomber( void ) const
|
||||
{
|
||||
return m_lastSawBomber.GetElapsedTime();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
bool CSGameState::IsPlantedBombLocationKnown( void ) const
|
||||
{
|
||||
if (m_bombState != PLANTED)
|
||||
return false;
|
||||
|
||||
return m_isPlantedBombPosKnown;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Return the zone index of the planted bombsite, or UNKNOWN
|
||||
*/
|
||||
int CSGameState::GetPlantedBombsite( void ) const
|
||||
{
|
||||
if (m_bombState != PLANTED)
|
||||
return UNKNOWN;
|
||||
|
||||
return m_plantedBombsite;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Return true if we are currently in the bombsite where the bomb is planted
|
||||
*/
|
||||
bool CSGameState::IsAtPlantedBombsite( void ) const
|
||||
{
|
||||
if (m_bombState != PLANTED)
|
||||
return false;
|
||||
|
||||
Vector myOrigin = GetCentroid( m_owner );
|
||||
const CCSBotManager::Zone *zone = TheCSBots()->GetClosestZone( myOrigin );
|
||||
|
||||
if (zone)
|
||||
{
|
||||
return (m_plantedBombsite == zone->m_index);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Return the zone index of the next bombsite to search
|
||||
*/
|
||||
int CSGameState::GetNextBombsiteToSearch( void )
|
||||
{
|
||||
if (m_bombsiteCount <= 0)
|
||||
return 0;
|
||||
|
||||
int i;
|
||||
|
||||
// return next non-cleared bombsite index
|
||||
for( i=m_bombsiteSearchIndex; i<m_bombsiteCount; ++i )
|
||||
{
|
||||
int z = m_bombsiteSearchOrder[i];
|
||||
if (!m_isBombsiteClear[z])
|
||||
{
|
||||
m_bombsiteSearchIndex = i;
|
||||
return z;
|
||||
}
|
||||
}
|
||||
|
||||
// all the bombsites are clear, someone must have been mistaken - start search over
|
||||
for( i=0; i<m_bombsiteCount; ++i )
|
||||
m_isBombsiteClear[i] = false;
|
||||
m_bombsiteSearchIndex = 0;
|
||||
|
||||
return GetNextBombsiteToSearch();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Returns position of bomb in its various states (moving, loose, planted),
|
||||
* or NULL if we don't know where the bomb is
|
||||
*/
|
||||
const Vector *CSGameState::GetBombPosition( void ) const
|
||||
{
|
||||
switch( m_bombState )
|
||||
{
|
||||
case MOVING:
|
||||
{
|
||||
if (!m_lastSawBomber.HasStarted())
|
||||
return NULL;
|
||||
|
||||
return &m_bomberPos;
|
||||
}
|
||||
|
||||
case LOOSE:
|
||||
{
|
||||
if (IsLooseBombLocationKnown())
|
||||
return &m_looseBombPos;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
case PLANTED:
|
||||
{
|
||||
if (IsPlantedBombLocationKnown())
|
||||
return &m_plantedBombPos;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* We see the planted bomb at 'pos'
|
||||
*/
|
||||
void CSGameState::UpdatePlantedBomb( const Vector &pos )
|
||||
{
|
||||
const CCSBotManager::Zone *zone = TheCSBots()->GetClosestZone( pos );
|
||||
|
||||
if (zone == NULL)
|
||||
{
|
||||
CONSOLE_ECHO( "ERROR: Bomb planted outside of a zone!\n" );
|
||||
m_plantedBombsite = UNKNOWN;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_plantedBombsite = zone->m_index;
|
||||
}
|
||||
|
||||
m_plantedBombPos = pos;
|
||||
m_isPlantedBombPosKnown = true;
|
||||
SetBombState( PLANTED );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Someone told us where the bomb is planted
|
||||
*/
|
||||
void CSGameState::MarkBombsiteAsPlanted( int zoneIndex )
|
||||
{
|
||||
m_plantedBombsite = zoneIndex;
|
||||
SetBombState( PLANTED );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Someone told us a bombsite is clear
|
||||
*/
|
||||
void CSGameState::ClearBombsite( int zoneIndex )
|
||||
{
|
||||
if (zoneIndex >= 0 && zoneIndex < m_bombsiteCount)
|
||||
m_isBombsiteClear[ zoneIndex ] = true;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
bool CSGameState::IsBombsiteClear( int zoneIndex ) const
|
||||
{
|
||||
if (zoneIndex >= 0 && zoneIndex < m_bombsiteCount)
|
||||
return m_isBombsiteClear[ zoneIndex ];
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Initialize our knowledge of the number and location of hostages
|
||||
*/
|
||||
void CSGameState::InitializeHostageInfo( void )
|
||||
{
|
||||
m_hostageCount = 0;
|
||||
m_allHostagesRescued = false;
|
||||
m_haveSomeHostagesBeenTaken = false;
|
||||
|
||||
for( int i=0; i<g_Hostages.Count(); ++i )
|
||||
{
|
||||
m_hostage[ m_hostageCount ].hostage = g_Hostages[i];
|
||||
m_hostage[ m_hostageCount ].knownPos = g_Hostages[i]->GetAbsOrigin();
|
||||
m_hostage[ m_hostageCount ].isValid = true;
|
||||
m_hostage[ m_hostageCount ].isAlive = true;
|
||||
m_hostage[ m_hostageCount ].isFree = true;
|
||||
++m_hostageCount;
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Return the closest free and live hostage
|
||||
* If we are a CT this information is perfect.
|
||||
* Otherwise, this is based on our individual memory of the game state.
|
||||
* If NULL is returned, we don't think there are any hostages left, or we dont know where they are.
|
||||
* NOTE: a T can remember a hostage who has died. knowPos will be filled in, but NULL will be
|
||||
* returned, since CHostages get deleted when they die.
|
||||
*/
|
||||
CHostage *CSGameState::GetNearestFreeHostage( Vector *knowPos ) const
|
||||
{
|
||||
if (m_owner == NULL)
|
||||
return NULL;
|
||||
|
||||
CNavArea *startArea = m_owner->GetLastKnownArea();
|
||||
if (startArea == NULL)
|
||||
return NULL;
|
||||
|
||||
CHostage *close = NULL;
|
||||
Vector closePos( 0, 0, 0 );
|
||||
float closeDistance = 9999999999.9f;
|
||||
|
||||
for( int i=0; i<m_hostageCount; ++i )
|
||||
{
|
||||
CHostage *hostage = m_hostage[i].hostage;
|
||||
Vector hostagePos;
|
||||
|
||||
if (m_owner->GetTeamNumber() == TEAM_CT)
|
||||
{
|
||||
// we know exactly where the hostages are, and if they are alive
|
||||
if (!m_hostage[i].hostage || !m_hostage[i].hostage->IsValid())
|
||||
continue;
|
||||
|
||||
if (m_hostage[i].hostage->IsFollowingSomeone())
|
||||
continue;
|
||||
|
||||
hostagePos = m_hostage[i].hostage->GetAbsOrigin();
|
||||
}
|
||||
else
|
||||
{
|
||||
// use our memory of where we think the hostages are
|
||||
if (m_hostage[i].isValid == false)
|
||||
continue;
|
||||
|
||||
hostagePos = m_hostage[i].knownPos;
|
||||
}
|
||||
|
||||
CNavArea *hostageArea = TheNavMesh->GetNearestNavArea( hostagePos );
|
||||
if (hostageArea)
|
||||
{
|
||||
ShortestPathCost cost;
|
||||
float travelDistance = NavAreaTravelDistance( startArea, hostageArea, cost );
|
||||
|
||||
if (travelDistance >= 0.0f && travelDistance < closeDistance)
|
||||
{
|
||||
closeDistance = travelDistance;
|
||||
closePos = hostagePos;
|
||||
close = hostage;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// return where we think the hostage is
|
||||
if (knowPos && close)
|
||||
*knowPos = closePos;
|
||||
|
||||
return close;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Return the location of a "free" hostage, or NULL if we dont know of any
|
||||
*/
|
||||
const Vector *CSGameState::GetRandomFreeHostagePosition( void ) const
|
||||
{
|
||||
if (m_owner == NULL)
|
||||
return NULL;
|
||||
|
||||
static Vector freePos[ MAX_HOSTAGES ];
|
||||
int freeCount = 0;
|
||||
|
||||
for( int i=0; i<m_hostageCount; ++i )
|
||||
{
|
||||
const HostageInfo *info = &m_hostage[i];
|
||||
|
||||
if (m_owner->GetTeamNumber() == TEAM_CT)
|
||||
{
|
||||
// we know exactly where the hostages are, and if they are alive
|
||||
if (!info->hostage || !info->hostage->IsAlive())
|
||||
continue;
|
||||
|
||||
// escorted hostages are not "free"
|
||||
if (info->hostage->IsFollowingSomeone())
|
||||
continue;
|
||||
|
||||
freePos[ freeCount++ ] = info->hostage->GetAbsOrigin();
|
||||
}
|
||||
else
|
||||
{
|
||||
// use our memory of where we think the hostages are
|
||||
if (info->isValid == false)
|
||||
continue;
|
||||
|
||||
freePos[ freeCount++ ] = info->knownPos;
|
||||
}
|
||||
}
|
||||
|
||||
if (freeCount)
|
||||
{
|
||||
return &freePos[ RandomInt( 0, freeCount-1 ) ];
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* If we can see any of the positions where we think a hostage is, validate it
|
||||
* Return status of any changes (a hostage died or was moved)
|
||||
*/
|
||||
unsigned char CSGameState::ValidateHostagePositions( void )
|
||||
{
|
||||
// limit how often we validate
|
||||
if (!m_validateInterval.IsElapsed())
|
||||
return NO_CHANGE;
|
||||
|
||||
const float validateInterval = 0.5f;
|
||||
m_validateInterval.Start( validateInterval );
|
||||
|
||||
|
||||
// check the status of hostages
|
||||
unsigned char status = NO_CHANGE;
|
||||
|
||||
int i;
|
||||
int startValidCount = 0;
|
||||
for( i=0; i<m_hostageCount; ++i )
|
||||
if (m_hostage[i].isValid)
|
||||
++startValidCount;
|
||||
|
||||
for( i=0; i<m_hostageCount; ++i )
|
||||
{
|
||||
HostageInfo *info = &m_hostage[i];
|
||||
|
||||
if (!info->hostage )
|
||||
continue;
|
||||
|
||||
// if we can see a hostage, update our knowledge of it
|
||||
Vector pos = info->hostage->GetAbsOrigin() + Vector( 0, 0, HalfHumanHeight );
|
||||
if (m_owner->IsVisible( pos, CHECK_FOV ))
|
||||
{
|
||||
if (info->hostage->IsAlive())
|
||||
{
|
||||
// live hostage
|
||||
|
||||
// if hostage is being escorted by a CT, we don't "see" it, we see the CT
|
||||
if (info->hostage->IsFollowingSomeone())
|
||||
{
|
||||
info->isValid = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
info->knownPos = info->hostage->GetAbsOrigin();
|
||||
info->isValid = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// dead hostage
|
||||
|
||||
// if we thought it was alive, this is news to us
|
||||
if (info->isAlive)
|
||||
status |= HOSTAGE_DIED;
|
||||
|
||||
info->isAlive = false;
|
||||
info->isValid = false;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// if we dont know where this hostage is, nothing to validate
|
||||
if (!info->isValid)
|
||||
continue;
|
||||
|
||||
// can't directly see this hostage
|
||||
// check line of sight to where we think this hostage is, to see if we noticed that is has moved
|
||||
pos = info->knownPos + Vector( 0, 0, HalfHumanHeight );
|
||||
if (m_owner->IsVisible( pos, CHECK_FOV ))
|
||||
{
|
||||
// we can see where we thought the hostage was - verify it is still there and alive
|
||||
|
||||
if (!info->hostage->IsValid())
|
||||
{
|
||||
// since we have line of sight to an invalid hostage, it must be dead
|
||||
// discovered that hostage has been killed
|
||||
status |= HOSTAGE_DIED;
|
||||
info->isAlive = false;
|
||||
info->isValid = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (info->hostage->IsFollowingSomeone())
|
||||
{
|
||||
// discovered the hostage has been taken
|
||||
status |= HOSTAGE_GONE;
|
||||
info->isValid = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
const float tolerance = 50.0f;
|
||||
if ((info->hostage->GetAbsOrigin() - info->knownPos).IsLengthGreaterThan( tolerance ))
|
||||
{
|
||||
// discovered that hostage has been moved
|
||||
status |= HOSTAGE_GONE;
|
||||
info->isValid = false;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int endValidCount = 0;
|
||||
for( i=0; i<m_hostageCount; ++i )
|
||||
if (m_hostage[i].isValid)
|
||||
++endValidCount;
|
||||
|
||||
if (endValidCount == 0 && startValidCount > 0)
|
||||
{
|
||||
// we discovered all the hostages are gone
|
||||
status &= ~HOSTAGE_GONE;
|
||||
status |= HOSTAGES_ALL_GONE;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Return the nearest visible free hostage
|
||||
* Since we can actually see any hostage we return, we know its actual position
|
||||
*/
|
||||
CHostage *CSGameState::GetNearestVisibleFreeHostage( void ) const
|
||||
{
|
||||
CHostage *close = NULL;
|
||||
float closeRangeSq = 999999999.9f;
|
||||
float rangeSq;
|
||||
|
||||
Vector pos;
|
||||
Vector myOrigin = GetCentroid( m_owner );
|
||||
|
||||
for( int i=0; i<m_hostageCount; ++i )
|
||||
{
|
||||
const HostageInfo *info = &m_hostage[i];
|
||||
|
||||
if ( !info->hostage )
|
||||
continue;
|
||||
|
||||
// if the hostage is dead or rescued, its not free
|
||||
if (!info->hostage->IsAlive())
|
||||
continue;
|
||||
|
||||
// if this hostage is following someone, its not free
|
||||
if (info->hostage->IsFollowingSomeone())
|
||||
continue;
|
||||
|
||||
/// @todo Use travel distance here
|
||||
pos = info->hostage->GetAbsOrigin();
|
||||
rangeSq = (pos - myOrigin).LengthSqr();
|
||||
|
||||
if (rangeSq < closeRangeSq)
|
||||
{
|
||||
if (!m_owner->IsVisible( pos ))
|
||||
continue;
|
||||
|
||||
close = info->hostage;
|
||||
closeRangeSq = rangeSq;
|
||||
}
|
||||
}
|
||||
|
||||
return close;
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Return true if there are no free hostages
|
||||
*/
|
||||
bool CSGameState::AreAllHostagesBeingRescued( void ) const
|
||||
{
|
||||
// if the hostages have all been rescued, they are not being rescued any longer
|
||||
if (m_allHostagesRescued)
|
||||
return false;
|
||||
|
||||
bool isAllDead = true;
|
||||
|
||||
for( int i=0; i<m_hostageCount; ++i )
|
||||
{
|
||||
const HostageInfo *info = &m_hostage[i];
|
||||
|
||||
if (m_owner->GetTeamNumber() == TEAM_CT)
|
||||
{
|
||||
// CT's have perfect knowledge via their radar
|
||||
if (info->hostage && info->hostage->IsValid())
|
||||
{
|
||||
if (!info->hostage->IsFollowingSomeone())
|
||||
return false;
|
||||
|
||||
isAllDead = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (info->isValid && info->isAlive)
|
||||
return false;
|
||||
|
||||
if (info->isAlive)
|
||||
isAllDead = false;
|
||||
}
|
||||
}
|
||||
|
||||
// if all of the remaining hostages are dead, they arent being rescued
|
||||
if (isAllDead)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* All hostages have been rescued or are dead
|
||||
*/
|
||||
bool CSGameState::AreAllHostagesGone( void ) const
|
||||
{
|
||||
if (m_allHostagesRescued)
|
||||
return true;
|
||||
|
||||
// do we know that all the hostages are dead
|
||||
for( int i=0; i<m_hostageCount; ++i )
|
||||
{
|
||||
const HostageInfo *info = &m_hostage[i];
|
||||
|
||||
if (m_owner->GetTeamNumber() == TEAM_CT)
|
||||
{
|
||||
// CT's have perfect knowledge via their radar
|
||||
if (info->hostage && info->hostage->IsAlive())
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (info->isValid && info->isAlive)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Someone told us all the hostages are gone
|
||||
*/
|
||||
void CSGameState::AllHostagesGone( void )
|
||||
{
|
||||
for( int i=0; i<m_hostageCount; ++i )
|
||||
m_hostage[i].isValid = false;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose:
|
||||
//
|
||||
// $NoKeywords: $
|
||||
//=============================================================================//
|
||||
|
||||
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
|
||||
|
||||
#ifndef _GAME_STATE_H_
|
||||
#define _GAME_STATE_H_
|
||||
|
||||
|
||||
#include "bot_util.h"
|
||||
|
||||
|
||||
class CHostage;
|
||||
class CCSBot;
|
||||
|
||||
/**
|
||||
* This class represents the game state as known by a particular bot
|
||||
*/
|
||||
class CSGameState
|
||||
{
|
||||
public:
|
||||
CSGameState( CCSBot *owner );
|
||||
|
||||
void Reset( void );
|
||||
|
||||
// Event handling
|
||||
void OnHostageRescuedAll( IGameEvent *event );
|
||||
void OnRoundEnd( IGameEvent *event );
|
||||
void OnRoundStart( IGameEvent *event );
|
||||
void OnBombPlanted( IGameEvent *event );
|
||||
void OnBombDefused( IGameEvent *event );
|
||||
void OnBombExploded( IGameEvent *event );
|
||||
|
||||
bool IsRoundOver( void ) const; ///< true if round has been won or lost (but not yet reset)
|
||||
|
||||
// bomb defuse scenario -----------------------------------------------------------------------------
|
||||
|
||||
enum BombState
|
||||
{
|
||||
MOVING, ///< being carried by a Terrorist
|
||||
LOOSE, ///< loose on the ground somewhere
|
||||
PLANTED, ///< planted and ticking
|
||||
DEFUSED, ///< the bomb has been defused
|
||||
EXPLODED ///< the bomb has exploded
|
||||
};
|
||||
|
||||
bool IsBombMoving( void ) const { return (m_bombState == MOVING); }
|
||||
bool IsBombLoose( void ) const { return (m_bombState == LOOSE); }
|
||||
bool IsBombPlanted( void ) const { return (m_bombState == PLANTED); }
|
||||
bool IsBombDefused( void ) const { return (m_bombState == DEFUSED); }
|
||||
bool IsBombExploded( void ) const { return (m_bombState == EXPLODED); }
|
||||
|
||||
void UpdateLooseBomb( const Vector &pos ); ///< we see the loose bomb
|
||||
float TimeSinceLastSawLooseBomb( void ) const; ///< how long has is been since we saw the loose bomb
|
||||
bool IsLooseBombLocationKnown( void ) const; ///< do we know where the loose bomb is
|
||||
|
||||
void UpdateBomber( const Vector &pos ); ///< we see the bomber
|
||||
float TimeSinceLastSawBomber( void ) const; ///< how long has is been since we saw the bomber
|
||||
|
||||
void UpdatePlantedBomb( const Vector &pos ); ///< we see the planted bomb
|
||||
bool IsPlantedBombLocationKnown( void ) const; ///< do we know where the bomb was planted
|
||||
void MarkBombsiteAsPlanted( int zoneIndex ); ///< mark bombsite as the location of the planted bomb
|
||||
|
||||
enum { UNKNOWN = -1 };
|
||||
int GetPlantedBombsite( void ) const; ///< return the zone index of the planted bombsite, or UNKNOWN
|
||||
bool IsAtPlantedBombsite( void ) const; ///< return true if we are currently in the bombsite where the bomb is planted
|
||||
|
||||
int GetNextBombsiteToSearch( void ); ///< return the zone index of the next bombsite to search
|
||||
bool IsBombsiteClear( int zoneIndex ) const; ///< return true if given bombsite has been cleared
|
||||
void ClearBombsite( int zoneIndex ); ///< mark bombsite as clear
|
||||
|
||||
const Vector *GetBombPosition( void ) const; ///< return where we think the bomb is, or NULL if we don't know
|
||||
|
||||
// hostage rescue scenario ------------------------------------------------------------------------
|
||||
CHostage *GetNearestFreeHostage( Vector *knowPos = NULL ) const; ///< return the closest free hostage, and where we think it is (knowPos)
|
||||
const Vector *GetRandomFreeHostagePosition( void ) const;
|
||||
bool AreAllHostagesBeingRescued( void ) const; ///< return true if there are no free hostages
|
||||
bool AreAllHostagesGone( void ) const; ///< all hostages have been rescued or are dead
|
||||
void AllHostagesGone( void ); ///< someone told us all the hostages are gone
|
||||
bool HaveSomeHostagesBeenTaken( void ) const ///< return true if one or more hostages have been moved by the CT's
|
||||
{
|
||||
return m_haveSomeHostagesBeenTaken;
|
||||
}
|
||||
void HostageWasTaken( void ) ///< someone told us a CT is talking to a hostage
|
||||
{
|
||||
m_haveSomeHostagesBeenTaken = true;
|
||||
}
|
||||
|
||||
CHostage *GetNearestVisibleFreeHostage( void ) const;
|
||||
|
||||
enum ValidateStatusType
|
||||
{
|
||||
NO_CHANGE = 0x00,
|
||||
HOSTAGE_DIED = 0x01,
|
||||
HOSTAGE_GONE = 0x02,
|
||||
HOSTAGES_ALL_GONE = 0x04
|
||||
};
|
||||
unsigned char ValidateHostagePositions( void ); ///< update our knowledge with what we currently see - returns bitflag events
|
||||
|
||||
private:
|
||||
CCSBot *m_owner; ///< who owns this gamestate
|
||||
|
||||
bool m_isRoundOver; ///< true if round is over, but no yet reset
|
||||
|
||||
// bomb defuse scenario ---------------------------------------------------------------------------
|
||||
void SetBombState( BombState state );
|
||||
BombState GetBombState( void ) const { return m_bombState; }
|
||||
|
||||
BombState m_bombState; ///< what we think the bomb is doing
|
||||
|
||||
IntervalTimer m_lastSawBomber;
|
||||
Vector m_bomberPos;
|
||||
|
||||
IntervalTimer m_lastSawLooseBomb;
|
||||
Vector m_looseBombPos;
|
||||
|
||||
bool m_isBombsiteClear[ CCSBotManager::MAX_ZONES ]; ///< corresponds to zone indices in CCSBotManager
|
||||
int m_bombsiteSearchOrder[ CCSBotManager::MAX_ZONES ]; ///< randomized order of bombsites to search
|
||||
int m_bombsiteCount;
|
||||
int m_bombsiteSearchIndex; ///< the next step in the search
|
||||
|
||||
int m_plantedBombsite; ///< zone index of the bombsite where the planted bomb is
|
||||
|
||||
bool m_isPlantedBombPosKnown; ///< if true, we know the exact location of the bomb
|
||||
Vector m_plantedBombPos;
|
||||
|
||||
// hostage rescue scenario ------------------------------------------------------------------------
|
||||
struct HostageInfo
|
||||
{
|
||||
CHandle<CHostage> hostage;
|
||||
Vector knownPos;
|
||||
bool isValid;
|
||||
bool isAlive;
|
||||
bool isFree; ///< not being escorted by a CT
|
||||
}
|
||||
m_hostage[ MAX_HOSTAGES ];
|
||||
int m_hostageCount; ///< number of hostages left in map
|
||||
CountdownTimer m_validateInterval;
|
||||
|
||||
CBaseEntity *GetNearestHostage( void ) const; ///< return the closest live hostage
|
||||
void InitializeHostageInfo( void ); ///< initialize our knowledge of the number and location of hostages
|
||||
|
||||
bool m_allHostagesRescued;
|
||||
bool m_haveSomeHostagesBeenTaken; ///< true if a hostage has been moved by a CT (and we've seen it)
|
||||
};
|
||||
|
||||
#endif // _GAME_STATE_
|
||||
@@ -0,0 +1,710 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose:
|
||||
//
|
||||
// $NoKeywords: $
|
||||
//=============================================================================//
|
||||
|
||||
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
|
||||
|
||||
#include "cbase.h"
|
||||
#include "cs_bot.h"
|
||||
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Begin attacking
|
||||
*/
|
||||
void AttackState::OnEnter( CCSBot *me )
|
||||
{
|
||||
CBasePlayer *enemy = me->GetBotEnemy();
|
||||
|
||||
// store our posture when the attack began
|
||||
me->PushPostureContext();
|
||||
|
||||
me->DestroyPath();
|
||||
|
||||
// if we are using a knife, try to sneak up on the enemy
|
||||
if (enemy && me->IsUsingKnife() && !me->IsPlayerFacingMe( enemy ))
|
||||
me->Walk();
|
||||
else
|
||||
me->Run();
|
||||
|
||||
me->GetOffLadder();
|
||||
me->ResetStuckMonitor();
|
||||
|
||||
m_repathTimer.Invalidate();
|
||||
m_haveSeenEnemy = me->IsEnemyVisible();
|
||||
m_nextDodgeStateTimestamp = 0.0f;
|
||||
m_firstDodge = true;
|
||||
m_isEnemyHidden = false;
|
||||
m_reacquireTimestamp = 0.0f;
|
||||
|
||||
m_pinnedDownTimestamp = gpGlobals->curtime + RandomFloat( 7.0f, 10.0f );
|
||||
|
||||
m_shieldToggleTimestamp = gpGlobals->curtime + RandomFloat( 2.0f, 10.0f );
|
||||
m_shieldForceOpen = false;
|
||||
|
||||
// if we encountered someone while escaping, grab our weapon and fight!
|
||||
if (me->IsEscapingFromBomb())
|
||||
me->EquipBestWeapon();
|
||||
|
||||
if (me->IsUsingKnife())
|
||||
{
|
||||
// can't crouch and hold with a knife
|
||||
m_crouchAndHold = false;
|
||||
me->StandUp();
|
||||
}
|
||||
else if (me->CanSeeSniper() && !me->IsSniper())
|
||||
{
|
||||
// don't sit still if we see a sniper!
|
||||
m_crouchAndHold = false;
|
||||
me->StandUp();
|
||||
}
|
||||
else
|
||||
{
|
||||
// decide whether to crouch where we are, or run and gun (if we havent already - see CCSBot::Attack())
|
||||
if (!m_crouchAndHold)
|
||||
{
|
||||
if (enemy)
|
||||
{
|
||||
const float crouchFarRange = 750.0f;
|
||||
float crouchChance;
|
||||
|
||||
// more likely to crouch if using sniper rifle or if enemy is far away
|
||||
if (me->IsUsingSniperRifle())
|
||||
crouchChance = 50.0f;
|
||||
else if ((GetCentroid( me ) - GetCentroid( enemy )).IsLengthGreaterThan( crouchFarRange ))
|
||||
crouchChance = 50.0f;
|
||||
else
|
||||
crouchChance = 20.0f * (1.0f - me->GetProfile()->GetAggression());
|
||||
|
||||
if (RandomFloat( 0.0f, 100.0f ) < crouchChance)
|
||||
{
|
||||
// make sure we can still see if we crouch
|
||||
trace_t result;
|
||||
|
||||
Vector origin = GetCentroid( me );
|
||||
if (!me->IsCrouching())
|
||||
{
|
||||
// we are standing, adjust for lower crouch origin
|
||||
origin.z -= 20.0f;
|
||||
}
|
||||
|
||||
UTIL_TraceLine( origin, enemy->EyePosition(), MASK_PLAYERSOLID, me, COLLISION_GROUP_NONE, &result );
|
||||
|
||||
if (result.fraction == 1.0f)
|
||||
{
|
||||
m_crouchAndHold = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (m_crouchAndHold)
|
||||
{
|
||||
me->Crouch();
|
||||
me->PrintIfWatched( "Crouch and hold attack!\n" );
|
||||
}
|
||||
}
|
||||
|
||||
m_scopeTimestamp = 0;
|
||||
m_didAmbushCheck = false;
|
||||
|
||||
float skill = me->GetProfile()->GetSkill();
|
||||
|
||||
// tendency to dodge is proportional to skill
|
||||
float dodgeChance = 80.0f * skill;
|
||||
|
||||
// high skill bots always dodge if outnumbered, or they see a sniper
|
||||
if (skill > 0.5f && (me->IsOutnumbered() || me->CanSeeSniper()))
|
||||
{
|
||||
dodgeChance = 100.0f;
|
||||
}
|
||||
|
||||
m_shouldDodge = (RandomFloat( 0, 100 ) <= dodgeChance);
|
||||
|
||||
|
||||
// decide whether we might bail out of this fight
|
||||
m_isCoward = (RandomFloat( 0, 100 ) > 100.0f * me->GetProfile()->GetAggression());
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* When we are done attacking, this is invoked
|
||||
*/
|
||||
void AttackState::StopAttacking( CCSBot *me )
|
||||
{
|
||||
if (me->GetTask() == CCSBot::SNIPING)
|
||||
{
|
||||
// stay in our hiding spot
|
||||
me->Hide( me->GetLastKnownArea(), -1.0f, 50.0f );
|
||||
}
|
||||
else
|
||||
{
|
||||
me->StopAttacking();
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Do dodge behavior
|
||||
*/
|
||||
void AttackState::Dodge( CCSBot *me )
|
||||
{
|
||||
//
|
||||
// Dodge.
|
||||
// If sniping or crouching, stand still.
|
||||
//
|
||||
if (m_shouldDodge && !me->IsUsingSniperRifle() && !m_crouchAndHold)
|
||||
{
|
||||
CBasePlayer *enemy = me->GetBotEnemy();
|
||||
if (enemy == NULL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Vector toEnemy = enemy->GetAbsOrigin() - me->GetAbsOrigin();
|
||||
float range = toEnemy.Length();
|
||||
|
||||
const float hysterisRange = 125.0f; // (+/-) m_combatRange
|
||||
|
||||
float minRange = me->GetCombatRange() - hysterisRange;
|
||||
float maxRange = me->GetCombatRange() + hysterisRange;
|
||||
|
||||
if (me->IsUsingKnife())
|
||||
{
|
||||
// dodge when far away if armed only with a knife
|
||||
maxRange = 999999.9f;
|
||||
}
|
||||
|
||||
// move towards (or away from) enemy if we are using a knife, behind a corner, or we aren't very skilled
|
||||
if (me->GetProfile()->GetSkill() < 0.66f || !me->IsEnemyVisible())
|
||||
{
|
||||
if (range > maxRange)
|
||||
me->MoveForward();
|
||||
else if (range < minRange)
|
||||
me->MoveBackward();
|
||||
}
|
||||
|
||||
// don't dodge if enemy is facing away
|
||||
const float dodgeRange = 2000.0f;
|
||||
if (!me->CanSeeSniper() && (range > dodgeRange || !me->IsPlayerFacingMe( enemy )))
|
||||
{
|
||||
m_dodgeState = STEADY_ON;
|
||||
m_nextDodgeStateTimestamp = 0.0f;
|
||||
}
|
||||
else if (gpGlobals->curtime >= m_nextDodgeStateTimestamp)
|
||||
{
|
||||
int next;
|
||||
|
||||
// high-skill bots keep moving and don't jump if they see a sniper
|
||||
if (me->GetProfile()->GetSkill() > 0.5f && me->CanSeeSniper())
|
||||
{
|
||||
// juke back and forth
|
||||
if (m_firstDodge)
|
||||
{
|
||||
next = (RandomInt( 0, 100 ) < 50) ? SLIDE_RIGHT : SLIDE_LEFT;
|
||||
}
|
||||
else
|
||||
{
|
||||
next = (m_dodgeState == SLIDE_LEFT) ? SLIDE_RIGHT : SLIDE_LEFT;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// select next dodge state that is different that our current one
|
||||
do
|
||||
{
|
||||
// low-skill bots may jump when first engaging the enemy (if they are moving)
|
||||
const float jumpChance = 33.3f;
|
||||
if (m_firstDodge && me->GetProfile()->GetSkill() < 0.5f && RandomFloat( 0, 100 ) < jumpChance && !me->IsNotMoving())
|
||||
next = RandomInt( 0, NUM_ATTACK_STATES-1 );
|
||||
else
|
||||
next = RandomInt( 0, NUM_ATTACK_STATES-2 );
|
||||
}
|
||||
while( !m_firstDodge && next == m_dodgeState );
|
||||
}
|
||||
|
||||
m_dodgeState = (DodgeStateType)next;
|
||||
m_nextDodgeStateTimestamp = gpGlobals->curtime + RandomFloat( 0.3f, 1.0f );
|
||||
m_firstDodge = false;
|
||||
}
|
||||
|
||||
|
||||
Vector forward, right;
|
||||
me->EyeVectors( &forward, &right );
|
||||
|
||||
const float lookAheadRange = 30.0f;
|
||||
float ground;
|
||||
|
||||
switch( m_dodgeState )
|
||||
{
|
||||
case STEADY_ON:
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
case SLIDE_LEFT:
|
||||
{
|
||||
// don't move left if we will fall
|
||||
Vector pos = me->GetAbsOrigin() - (lookAheadRange * right);
|
||||
|
||||
if (me->GetSimpleGroundHeightWithFloor( pos, &ground ))
|
||||
{
|
||||
if (me->GetAbsOrigin().z - ground < StepHeight)
|
||||
{
|
||||
me->StrafeLeft();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case SLIDE_RIGHT:
|
||||
{
|
||||
// don't move left if we will fall
|
||||
Vector pos = me->GetAbsOrigin() + (lookAheadRange * right);
|
||||
|
||||
if (me->GetSimpleGroundHeightWithFloor( pos, &ground ))
|
||||
{
|
||||
if (me->GetAbsOrigin().z - ground < StepHeight)
|
||||
{
|
||||
me->StrafeRight();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case JUMP:
|
||||
{
|
||||
if (me->m_isEnemyVisible)
|
||||
{
|
||||
me->Jump();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Perform attack behavior
|
||||
*/
|
||||
void AttackState::OnUpdate( CCSBot *me )
|
||||
{
|
||||
// can't be stuck while attacking
|
||||
me->ResetStuckMonitor();
|
||||
|
||||
// if we somehow ended up with the C4 or a grenade in our hands, grab our weapon!
|
||||
CWeaponCSBase *weapon = me->GetActiveCSWeapon();
|
||||
if (weapon)
|
||||
{
|
||||
if (weapon->GetWeaponID() == WEAPON_C4 ||
|
||||
weapon->GetWeaponID() == WEAPON_HEGRENADE ||
|
||||
weapon->GetWeaponID() == WEAPON_FLASHBANG ||
|
||||
weapon->GetWeaponID() == WEAPON_SMOKEGRENADE)
|
||||
{
|
||||
me->EquipBestWeapon();
|
||||
}
|
||||
}
|
||||
|
||||
CBasePlayer *enemy = me->GetBotEnemy();
|
||||
if (enemy == NULL)
|
||||
{
|
||||
StopAttacking( me );
|
||||
return;
|
||||
}
|
||||
|
||||
Vector myOrigin = GetCentroid( me );
|
||||
Vector enemyOrigin = GetCentroid( enemy );
|
||||
|
||||
// keep track of whether we have seen our enemy at least once yet
|
||||
if (!m_haveSeenEnemy)
|
||||
m_haveSeenEnemy = me->IsEnemyVisible();
|
||||
|
||||
|
||||
//
|
||||
// Retreat check
|
||||
// Do not retreat if the enemy is too close
|
||||
//
|
||||
if (m_retreatTimer.IsElapsed())
|
||||
{
|
||||
// If we've been fighting this battle for awhile, we're "pinned down" and
|
||||
// need to do something else.
|
||||
// If we are outnumbered, retreat.
|
||||
// If we see a sniper and we aren't a sniper, retreat.
|
||||
|
||||
bool isPinnedDown = (gpGlobals->curtime > m_pinnedDownTimestamp);
|
||||
|
||||
if (isPinnedDown ||
|
||||
(me->CanSeeSniper() && !me->IsSniper()) ||
|
||||
(me->IsOutnumbered() && m_isCoward) ||
|
||||
(me->OutnumberedCount() >= 2 && me->GetProfile()->GetAggression() < 1.0f))
|
||||
{
|
||||
// only retreat if at least one of them is aiming at me
|
||||
if (me->IsAnyVisibleEnemyLookingAtMe( CHECK_FOV ))
|
||||
{
|
||||
// tell our teammates our plight
|
||||
if (isPinnedDown)
|
||||
me->GetChatter()->PinnedDown();
|
||||
else if (!me->CanSeeSniper())
|
||||
me->GetChatter()->Scared();
|
||||
|
||||
m_retreatTimer.Start( RandomFloat( 3.0f, 15.0f ) );
|
||||
|
||||
// try to retreat
|
||||
if (me->TryToRetreat())
|
||||
{
|
||||
// if we are a sniper, equip our pistol so we can fire while retreating
|
||||
/*
|
||||
if (me->IsUsingSniperRifle())
|
||||
{
|
||||
// wait a moment to allow one last shot
|
||||
me->Wait( 0.5f );
|
||||
//me->EquipPistol();
|
||||
}
|
||||
*/
|
||||
|
||||
// request backup if outnumbered
|
||||
if (me->IsOutnumbered())
|
||||
{
|
||||
me->GetChatter()->NeedBackup();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
me->PrintIfWatched( "I want to retreat, but no safe spots nearby!\n" );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Knife fighting
|
||||
// We need to pathfind right to the enemy to cut him
|
||||
//
|
||||
if (me->IsUsingKnife())
|
||||
{
|
||||
// can't crouch and hold with a knife
|
||||
m_crouchAndHold = false;
|
||||
me->StandUp();
|
||||
|
||||
// if we are using a knife and our prey is looking towards us, run at him
|
||||
if (me->IsPlayerFacingMe( enemy ))
|
||||
{
|
||||
me->ForceRun( 5.0f );
|
||||
me->Hurry( 10.0f );
|
||||
}
|
||||
|
||||
// slash our victim
|
||||
me->FireWeaponAtEnemy();
|
||||
|
||||
// if toe to toe with our enemy, don't dodge, just slash
|
||||
const float slashRange = 70.0f;
|
||||
if ((enemy->GetAbsOrigin() - me->GetAbsOrigin()).IsLengthGreaterThan( slashRange ))
|
||||
{
|
||||
const float repathInterval = 0.5f;
|
||||
|
||||
// if our victim has moved, repath
|
||||
bool repath = false;
|
||||
if (me->HasPath())
|
||||
{
|
||||
const float repathRange = 100.0f; // 50
|
||||
if ((me->GetPathEndpoint() - enemy->GetAbsOrigin()).IsLengthGreaterThan( repathRange ))
|
||||
{
|
||||
repath = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
repath = true;
|
||||
}
|
||||
|
||||
if (repath && m_repathTimer.IsElapsed())
|
||||
{
|
||||
Vector enemyPos = enemy->GetAbsOrigin() + Vector( 0, 0, HalfHumanHeight );
|
||||
me->ComputePath( enemyPos, FASTEST_ROUTE );
|
||||
m_repathTimer.Start( repathInterval );
|
||||
}
|
||||
|
||||
// move towards victim
|
||||
if (me->UpdatePathMovement( NO_SPEED_CHANGE ) != CCSBot::PROGRESSING)
|
||||
{
|
||||
me->DestroyPath();
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//
|
||||
// Simple shield usage
|
||||
//
|
||||
if (me->HasShield())
|
||||
{
|
||||
if (me->IsEnemyVisible() && !m_shieldForceOpen)
|
||||
{
|
||||
if (!me->IsRecognizedEnemyReloading() && !me->IsReloading() && me->IsPlayerLookingAtMe( enemy ))
|
||||
{
|
||||
// close up - enemy is pointing his gun at us
|
||||
if (!me->IsProtectedByShield())
|
||||
me->SecondaryAttack();
|
||||
}
|
||||
else
|
||||
{
|
||||
// enemy looking away or reloading his weapon - open up and shoot him
|
||||
if (me->IsProtectedByShield())
|
||||
me->SecondaryAttack();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// can't see enemy, open up
|
||||
if (me->IsProtectedByShield())
|
||||
me->SecondaryAttack();
|
||||
}
|
||||
|
||||
if (gpGlobals->curtime > m_shieldToggleTimestamp)
|
||||
{
|
||||
m_shieldToggleTimestamp = gpGlobals->curtime + RandomFloat( 0.5, 2.0f );
|
||||
|
||||
// toggle shield force open
|
||||
m_shieldForceOpen = !m_shieldForceOpen;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// check if our weapon range is bad and we should switch to pistol
|
||||
if (me->IsUsingSniperRifle())
|
||||
{
|
||||
// if we have a sniper rifle and our enemy is too close, switch to pistol
|
||||
const float sniperMinRange = 160.0f; // NOTE: Must be larger than NO_ZOOM range in AdjustZoom()
|
||||
if ((enemyOrigin - myOrigin).IsLengthLessThan( sniperMinRange ))
|
||||
me->EquipPistol();
|
||||
}
|
||||
else if (me->IsUsingShotgun())
|
||||
{
|
||||
// if we have a shotgun equipped and enemy is too far away, switch to pistol
|
||||
const float shotgunMaxRange = 600.0f;
|
||||
if ((enemyOrigin - myOrigin).IsLengthGreaterThan( shotgunMaxRange ))
|
||||
me->EquipPistol();
|
||||
}
|
||||
|
||||
// if we're sniping, look through the scope - need to do this here in case a reload resets our scope
|
||||
if (me->IsUsingSniperRifle())
|
||||
{
|
||||
// for Scouts and AWPs, we need to wait for zoom to resume
|
||||
if (me->m_bResumeZoom)
|
||||
{
|
||||
m_scopeTimestamp = gpGlobals->curtime;
|
||||
return;
|
||||
}
|
||||
|
||||
Vector toAimSpot3D = me->m_aimSpot - myOrigin;
|
||||
float targetRange = toAimSpot3D.Length();
|
||||
|
||||
// dont adjust zoom level if we're already zoomed in - just fire
|
||||
if (me->GetZoomLevel() == CCSBot::NO_ZOOM && me->AdjustZoom( targetRange ))
|
||||
m_scopeTimestamp = gpGlobals->curtime;
|
||||
|
||||
const float waitScopeTime = 0.3f + me->GetProfile()->GetReactionTime();
|
||||
if (gpGlobals->curtime - m_scopeTimestamp < waitScopeTime)
|
||||
{
|
||||
// force us to wait until zoomed in before firing
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// see if we "notice" that our prey is dead
|
||||
if (me->IsAwareOfEnemyDeath())
|
||||
{
|
||||
// let team know if we killed the last enemy
|
||||
if (me->GetLastVictimID() == enemy->entindex() && me->GetNearbyEnemyCount() <= 1)
|
||||
{
|
||||
me->GetChatter()->KilledMyEnemy( enemy->entindex() );
|
||||
|
||||
// if there are other enemies left, wait a moment - they usually come in groups
|
||||
if (me->GetEnemiesRemaining())
|
||||
{
|
||||
me->Wait( RandomFloat( 1.0f, 3.0f ) );
|
||||
}
|
||||
}
|
||||
|
||||
StopAttacking( me );
|
||||
return;
|
||||
}
|
||||
|
||||
float notSeenEnemyTime = gpGlobals->curtime - me->GetLastSawEnemyTimestamp();
|
||||
|
||||
// if we haven't seen our enemy for a moment, continue on if we dont want to fight, or decide to ambush if we do
|
||||
if (!me->IsEnemyVisible())
|
||||
{
|
||||
// attend to nearby enemy gunfire
|
||||
if (notSeenEnemyTime > 0.5f && me->CanHearNearbyEnemyGunfire())
|
||||
{
|
||||
// give up the attack, since we didn't want it in the first place
|
||||
StopAttacking( me );
|
||||
|
||||
const Vector *pos = me->GetNoisePosition();
|
||||
if (pos)
|
||||
{
|
||||
me->SetLookAt( "Nearby enemy gunfire", *pos, PRIORITY_HIGH, 0.0f );
|
||||
me->PrintIfWatched( "Checking nearby threatening enemy gunfire!\n" );
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// check if we have lost track of our enemy during combat
|
||||
if (notSeenEnemyTime > 0.25f)
|
||||
{
|
||||
m_isEnemyHidden = true;
|
||||
}
|
||||
|
||||
|
||||
if (notSeenEnemyTime > 0.1f)
|
||||
{
|
||||
if (me->GetDisposition() == CCSBot::ENGAGE_AND_INVESTIGATE)
|
||||
{
|
||||
// decide whether we should hide and "ambush" our enemy
|
||||
if (m_haveSeenEnemy && !m_didAmbushCheck)
|
||||
{
|
||||
float hideChance = 33.3f;
|
||||
|
||||
if (RandomFloat( 0.0, 100.0f ) < hideChance)
|
||||
{
|
||||
float ambushTime = RandomFloat( 3.0f, 15.0f );
|
||||
|
||||
// hide in ambush nearby
|
||||
/// @todo look towards where we know enemy is
|
||||
const Vector *spot = FindNearbyRetreatSpot( me, 200.0f );
|
||||
if (spot)
|
||||
{
|
||||
me->IgnoreEnemies( 1.0f );
|
||||
|
||||
me->Run();
|
||||
me->StandUp();
|
||||
me->Hide( *spot, ambushTime, true );
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// don't check again
|
||||
m_didAmbushCheck = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// give up the attack, since we didn't want it in the first place
|
||||
StopAttacking( me );
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// we can see the enemy again - reset our ambush check
|
||||
m_didAmbushCheck = false;
|
||||
|
||||
// if the enemy is coming out of hiding, we need time to react
|
||||
if (m_isEnemyHidden)
|
||||
{
|
||||
m_reacquireTimestamp = gpGlobals->curtime + me->GetProfile()->GetReactionTime();
|
||||
m_isEnemyHidden = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// if we haven't seen our enemy for a long time, chase after them
|
||||
float chaseTime = 2.0f + 2.0f * (1.0f - me->GetProfile()->GetAggression());
|
||||
|
||||
// if we are sniping, be very patient
|
||||
if (me->IsUsingSniperRifle())
|
||||
chaseTime += 3.0f;
|
||||
else if (me->IsCrouching()) // if we are crouching, be a little patient
|
||||
chaseTime += 1.0f;
|
||||
|
||||
// if we can't see the enemy, and have either seen him but currently lost sight of him,
|
||||
// or haven't yet seen him, chase after him (unless we are a sniper)
|
||||
if (!me->IsEnemyVisible() && (notSeenEnemyTime > chaseTime || !m_haveSeenEnemy))
|
||||
{
|
||||
// snipers don't chase their prey - they wait for their prey to come to them
|
||||
if (me->GetTask() == CCSBot::SNIPING)
|
||||
{
|
||||
StopAttacking( me );
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
// move to last known position of enemy
|
||||
me->SetTask( CCSBot::MOVE_TO_LAST_KNOWN_ENEMY_POSITION, enemy );
|
||||
me->MoveTo( me->GetLastKnownEnemyPosition() );
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// if we can't see our enemy at the moment, and were shot by
|
||||
// a different visible enemy, engage them instead
|
||||
const float hurtRecentlyTime = 3.0f;
|
||||
if (!me->IsEnemyVisible() &&
|
||||
me->GetTimeSinceAttacked() < hurtRecentlyTime &&
|
||||
me->GetAttacker() &&
|
||||
me->GetAttacker() != me->GetBotEnemy())
|
||||
{
|
||||
// if we can see them, attack, otherwise panic
|
||||
if (me->IsVisible( me->GetAttacker(), CHECK_FOV ))
|
||||
{
|
||||
me->Attack( me->GetAttacker() );
|
||||
me->PrintIfWatched( "Switching targets to retaliate against new attacker!\n" );
|
||||
}
|
||||
/*
|
||||
* Rethink this
|
||||
else
|
||||
{
|
||||
me->Panic( me->GetAttacker() );
|
||||
me->PrintIfWatched( "Panicking from crossfire while attacking!\n" );
|
||||
}
|
||||
*/
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (true || gpGlobals->curtime > m_reacquireTimestamp)
|
||||
me->FireWeaponAtEnemy();
|
||||
|
||||
|
||||
// do dodge behavior
|
||||
Dodge( me );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Finish attack
|
||||
*/
|
||||
void AttackState::OnExit( CCSBot *me )
|
||||
{
|
||||
me->PrintIfWatched( "AttackState:OnExit()\n" );
|
||||
|
||||
m_crouchAndHold = false;
|
||||
|
||||
// clear any noises we heard during battle
|
||||
me->ForgetNoise();
|
||||
me->ResetStuckMonitor();
|
||||
|
||||
// resume our original posture
|
||||
me->PopPostureContext();
|
||||
|
||||
// put shield away
|
||||
if (me->IsProtectedByShield())
|
||||
me->SecondaryAttack();
|
||||
|
||||
|
||||
//me->StopAiming();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,690 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose:
|
||||
//
|
||||
// $NoKeywords: $
|
||||
//=============================================================================//
|
||||
|
||||
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
|
||||
|
||||
#include "cbase.h"
|
||||
#include "cs_gamerules.h"
|
||||
#include "cs_bot.h"
|
||||
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
ConVar bot_loadout( "bot_loadout", "", FCVAR_CHEAT, "bots are given these items at round start" );
|
||||
ConVar bot_randombuy( "bot_randombuy", "0", FCVAR_CHEAT, "should bots ignore their prefered weapons and just buy weapons at random?" );
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Debug command to give a named weapon
|
||||
*/
|
||||
void CCSBot::GiveWeapon( const char *weaponAlias )
|
||||
{
|
||||
const char *translatedAlias = GetTranslatedWeaponAlias( weaponAlias );
|
||||
|
||||
char wpnName[128];
|
||||
Q_snprintf( wpnName, sizeof( wpnName ), "weapon_%s", translatedAlias );
|
||||
WEAPON_FILE_INFO_HANDLE hWpnInfo = LookupWeaponInfoSlot( wpnName );
|
||||
if ( hWpnInfo == GetInvalidWeaponInfoHandle() )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CCSWeaponInfo *pWeaponInfo = dynamic_cast< CCSWeaponInfo* >( GetFileWeaponInfoFromHandle( hWpnInfo ) );
|
||||
if ( !pWeaponInfo )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if ( !Weapon_OwnsThisType( wpnName ) )
|
||||
{
|
||||
CBaseCombatWeapon *pWeapon = Weapon_GetSlot( pWeaponInfo->iSlot );
|
||||
if ( pWeapon )
|
||||
{
|
||||
if ( pWeaponInfo->iSlot == WEAPON_SLOT_PISTOL )
|
||||
{
|
||||
DropPistol();
|
||||
}
|
||||
else if ( pWeaponInfo->iSlot == WEAPON_SLOT_RIFLE )
|
||||
{
|
||||
DropRifle();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GiveNamedItem( wpnName );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
static bool HasDefaultPistol( CCSBot *me )
|
||||
{
|
||||
CWeaponCSBase *pistol = (CWeaponCSBase *)me->Weapon_GetSlot( WEAPON_SLOT_PISTOL );
|
||||
|
||||
if (pistol == NULL)
|
||||
return false;
|
||||
|
||||
if (me->GetTeamNumber() == TEAM_TERRORIST && pistol->IsA( WEAPON_GLOCK ))
|
||||
return true;
|
||||
|
||||
if (me->GetTeamNumber() == TEAM_CT && pistol->IsA( WEAPON_USP ))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Buy weapons, armor, etc.
|
||||
*/
|
||||
void BuyState::OnEnter( CCSBot *me )
|
||||
{
|
||||
m_retries = 0;
|
||||
m_prefRetries = 0;
|
||||
m_prefIndex = 0;
|
||||
|
||||
const char *cheatWeaponString = bot_loadout.GetString();
|
||||
if ( cheatWeaponString && *cheatWeaponString )
|
||||
{
|
||||
m_doneBuying = false; // we're going to be given weapons - ignore the eco limit
|
||||
}
|
||||
else
|
||||
{
|
||||
// check if we are saving money for the next round
|
||||
if (me->m_iAccount < cv_bot_eco_limit.GetFloat())
|
||||
{
|
||||
me->PrintIfWatched( "Saving money for next round.\n" );
|
||||
m_doneBuying = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_doneBuying = false;
|
||||
}
|
||||
}
|
||||
|
||||
m_isInitialDelay = true;
|
||||
|
||||
// this will force us to stop holding live grenade
|
||||
me->EquipBestWeapon( MUST_EQUIP );
|
||||
|
||||
m_buyDefuseKit = false;
|
||||
m_buyShield = false;
|
||||
|
||||
if (me->GetTeamNumber() == TEAM_CT)
|
||||
{
|
||||
if (TheCSBots()->GetScenario() == CCSBotManager::SCENARIO_DEFUSE_BOMB)
|
||||
{
|
||||
// CT's sometimes buy defuse kits in the bomb scenario (except in career mode, where the player should defuse)
|
||||
if (CSGameRules()->IsCareer() == false)
|
||||
{
|
||||
const float buyDefuseKitChance = 100.0f * (me->GetProfile()->GetSkill() + 0.2f);
|
||||
if (RandomFloat( 0.0f, 100.0f ) < buyDefuseKitChance)
|
||||
{
|
||||
m_buyDefuseKit = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// determine if we want a tactical shield
|
||||
if (!me->HasPrimaryWeapon() && TheCSBots()->AllowTacticalShield())
|
||||
{
|
||||
if (me->m_iAccount > 2500)
|
||||
{
|
||||
if (me->m_iAccount < 4000)
|
||||
m_buyShield = (RandomFloat( 0, 100.0f ) < 33.3f) ? true : false;
|
||||
else
|
||||
m_buyShield = (RandomFloat( 0, 100.0f ) < 10.0f) ? true : false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (TheCSBots()->AllowGrenades())
|
||||
{
|
||||
m_buyGrenade = (RandomFloat( 0.0f, 100.0f ) < 33.3f) ? true : false;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_buyGrenade = false;
|
||||
}
|
||||
|
||||
|
||||
m_buyPistol = false;
|
||||
if (TheCSBots()->AllowPistols())
|
||||
{
|
||||
// check if we have a pistol
|
||||
if (me->Weapon_GetSlot( WEAPON_SLOT_PISTOL ))
|
||||
{
|
||||
// if we have our default pistol, think about buying a different one
|
||||
if (HasDefaultPistol( me ))
|
||||
{
|
||||
// if everything other than pistols is disallowed, buy a pistol
|
||||
if (TheCSBots()->AllowShotguns() == false &&
|
||||
TheCSBots()->AllowSubMachineGuns() == false &&
|
||||
TheCSBots()->AllowRifles() == false &&
|
||||
TheCSBots()->AllowMachineGuns() == false &&
|
||||
TheCSBots()->AllowTacticalShield() == false &&
|
||||
TheCSBots()->AllowSnipers() == false)
|
||||
{
|
||||
m_buyPistol = (RandomFloat( 0, 100 ) < 75.0f);
|
||||
}
|
||||
else if (me->m_iAccount < 1000)
|
||||
{
|
||||
// if we're low on cash, buy a pistol
|
||||
m_buyPistol = (RandomFloat( 0, 100 ) < 75.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_buyPistol = (RandomFloat( 0, 100 ) < 33.3f);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// we dont have a pistol - buy one
|
||||
m_buyPistol = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
enum WeaponType
|
||||
{
|
||||
PISTOL,
|
||||
SHOTGUN,
|
||||
SUB_MACHINE_GUN,
|
||||
RIFLE,
|
||||
MACHINE_GUN,
|
||||
SNIPER_RIFLE,
|
||||
GRENADE,
|
||||
|
||||
NUM_WEAPON_TYPES
|
||||
};
|
||||
|
||||
struct BuyInfo
|
||||
{
|
||||
WeaponType type;
|
||||
bool preferred; ///< more challenging bots prefer these weapons
|
||||
const char *buyAlias; ///< the buy alias for this equipment
|
||||
};
|
||||
|
||||
#define PRIMARY_WEAPON_BUY_COUNT 13
|
||||
#define SECONDARY_WEAPON_BUY_COUNT 3
|
||||
|
||||
/**
|
||||
* These tables MUST be kept in sync with the CT and T buy aliases
|
||||
*/
|
||||
|
||||
static BuyInfo primaryWeaponBuyInfoCT[ PRIMARY_WEAPON_BUY_COUNT ] =
|
||||
{
|
||||
{ SHOTGUN, false, "m3" }, // WEAPON_M3
|
||||
{ SHOTGUN, false, "xm1014" }, // WEAPON_XM1014
|
||||
{ SUB_MACHINE_GUN, false, "tmp" }, // WEAPON_TMP
|
||||
{ SUB_MACHINE_GUN, false, "mp5navy" }, // WEAPON_MP5N
|
||||
{ SUB_MACHINE_GUN, false, "ump45" }, // WEAPON_UMP45
|
||||
{ SUB_MACHINE_GUN, false, "p90" }, // WEAPON_P90
|
||||
{ RIFLE, true, "famas" }, // WEAPON_FAMAS
|
||||
{ SNIPER_RIFLE, false, "scout" }, // WEAPON_SCOUT
|
||||
{ RIFLE, true, "m4a1" }, // WEAPON_M4A1
|
||||
{ RIFLE, false, "aug" }, // WEAPON_AUG
|
||||
{ SNIPER_RIFLE, true, "sg550" }, // WEAPON_SG550
|
||||
{ SNIPER_RIFLE, true, "awp" }, // WEAPON_AWP
|
||||
{ MACHINE_GUN, false, "m249" } // WEAPON_M249
|
||||
};
|
||||
|
||||
static BuyInfo secondaryWeaponBuyInfoCT[ SECONDARY_WEAPON_BUY_COUNT ] =
|
||||
{
|
||||
// { PISTOL, false, "glock" },
|
||||
// { PISTOL, false, "usp" },
|
||||
{ PISTOL, true, "p228" },
|
||||
{ PISTOL, true, "deagle" },
|
||||
{ PISTOL, true, "fn57" }
|
||||
};
|
||||
|
||||
|
||||
static BuyInfo primaryWeaponBuyInfoT[ PRIMARY_WEAPON_BUY_COUNT ] =
|
||||
{
|
||||
{ SHOTGUN, false, "m3" }, // WEAPON_M3
|
||||
{ SHOTGUN, false, "xm1014" }, // WEAPON_XM1014
|
||||
{ SUB_MACHINE_GUN, false, "mac10" }, // WEAPON_MAC10
|
||||
{ SUB_MACHINE_GUN, false, "mp5navy" }, // WEAPON_MP5N
|
||||
{ SUB_MACHINE_GUN, false, "ump45" }, // WEAPON_UMP45
|
||||
{ SUB_MACHINE_GUN, false, "p90" }, // WEAPON_P90
|
||||
{ RIFLE, true, "galil" }, // WEAPON_GALIL
|
||||
{ RIFLE, true, "ak47" }, // WEAPON_AK47
|
||||
{ SNIPER_RIFLE, false, "scout" }, // WEAPON_SCOUT
|
||||
{ RIFLE, true, "sg552" }, // WEAPON_SG552
|
||||
{ SNIPER_RIFLE, true, "awp" }, // WEAPON_AWP
|
||||
{ SNIPER_RIFLE, true, "g3sg1" }, // WEAPON_G3SG1
|
||||
{ MACHINE_GUN, false, "m249" } // WEAPON_M249
|
||||
};
|
||||
|
||||
static BuyInfo secondaryWeaponBuyInfoT[ SECONDARY_WEAPON_BUY_COUNT ] =
|
||||
{
|
||||
// { PISTOL, false, "glock" },
|
||||
// { PISTOL, false, "usp" },
|
||||
{ PISTOL, true, "p228" },
|
||||
{ PISTOL, true, "deagle" },
|
||||
{ PISTOL, true, "elites" }
|
||||
};
|
||||
|
||||
/**
|
||||
* Given a weapon alias, return the kind of weapon it is
|
||||
*/
|
||||
inline WeaponType GetWeaponType( const char *alias )
|
||||
{
|
||||
int i;
|
||||
|
||||
for( i=0; i<PRIMARY_WEAPON_BUY_COUNT; ++i )
|
||||
{
|
||||
if (!stricmp( alias, primaryWeaponBuyInfoCT[i].buyAlias ))
|
||||
return primaryWeaponBuyInfoCT[i].type;
|
||||
|
||||
if (!stricmp( alias, primaryWeaponBuyInfoT[i].buyAlias ))
|
||||
return primaryWeaponBuyInfoT[i].type;
|
||||
}
|
||||
|
||||
for( i=0; i<SECONDARY_WEAPON_BUY_COUNT; ++i )
|
||||
{
|
||||
if (!stricmp( alias, secondaryWeaponBuyInfoCT[i].buyAlias ))
|
||||
return secondaryWeaponBuyInfoCT[i].type;
|
||||
|
||||
if (!stricmp( alias, secondaryWeaponBuyInfoT[i].buyAlias ))
|
||||
return secondaryWeaponBuyInfoT[i].type;
|
||||
}
|
||||
|
||||
return NUM_WEAPON_TYPES;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
void BuyState::OnUpdate( CCSBot *me )
|
||||
{
|
||||
char cmdBuffer[256];
|
||||
|
||||
// wait for a Navigation Mesh
|
||||
if (!TheNavMesh->IsLoaded())
|
||||
return;
|
||||
|
||||
// apparently we cant buy things in the first few seconds, so wait a bit
|
||||
if (m_isInitialDelay)
|
||||
{
|
||||
const float waitToBuyTime = 0.25f;
|
||||
if (gpGlobals->curtime - me->GetStateTimestamp() < waitToBuyTime)
|
||||
return;
|
||||
|
||||
m_isInitialDelay = false;
|
||||
}
|
||||
|
||||
// if we're done buying and still in the freeze period, wait
|
||||
if (m_doneBuying)
|
||||
{
|
||||
if (CSGameRules()->IsMultiplayer() && CSGameRules()->IsFreezePeriod())
|
||||
{
|
||||
// make sure we're locked and loaded
|
||||
me->EquipBestWeapon( MUST_EQUIP );
|
||||
me->Reload();
|
||||
me->ResetStuckMonitor();
|
||||
return;
|
||||
}
|
||||
|
||||
me->Idle();
|
||||
return;
|
||||
}
|
||||
|
||||
// If we're supposed to buy a specific weapon for debugging, do so and then bail
|
||||
const char *cheatWeaponString = bot_loadout.GetString();
|
||||
if ( cheatWeaponString && *cheatWeaponString )
|
||||
{
|
||||
CUtlVector<char*, CUtlMemory<char*> > loadout;
|
||||
Q_SplitString( cheatWeaponString, " ", loadout );
|
||||
for ( int i=0; i<loadout.Count(); ++i )
|
||||
{
|
||||
const char *item = loadout[i];
|
||||
if ( FStrEq( item, "vest" ) )
|
||||
{
|
||||
me->GiveNamedItem( "item_kevlar" );
|
||||
}
|
||||
else if ( FStrEq( item, "vesthelm" ) )
|
||||
{
|
||||
me->GiveNamedItem( "item_assaultsuit" );
|
||||
}
|
||||
else if ( FStrEq( item, "defuser" ) )
|
||||
{
|
||||
if ( me->GetTeamNumber() == TEAM_CT )
|
||||
{
|
||||
me->GiveDefuser();
|
||||
}
|
||||
}
|
||||
else if ( FStrEq( item, "nvgs" ) )
|
||||
{
|
||||
me->m_bHasNightVision = true;
|
||||
}
|
||||
else if ( FStrEq( item, "primammo" ) )
|
||||
{
|
||||
me->AttemptToBuyAmmo( 0 );
|
||||
}
|
||||
else if ( FStrEq( item, "secammo" ) )
|
||||
{
|
||||
me->AttemptToBuyAmmo( 1 );
|
||||
}
|
||||
else
|
||||
{
|
||||
me->GiveWeapon( item );
|
||||
}
|
||||
}
|
||||
m_doneBuying = true;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (!me->IsInBuyZone())
|
||||
{
|
||||
m_doneBuying = true;
|
||||
CONSOLE_ECHO( "%s bot spawned outside of a buy zone (%d, %d, %d)\n",
|
||||
(me->GetTeamNumber() == TEAM_CT) ? "CT" : "Terrorist",
|
||||
(int)me->GetAbsOrigin().x,
|
||||
(int)me->GetAbsOrigin().y,
|
||||
(int)me->GetAbsOrigin().z );
|
||||
return;
|
||||
}
|
||||
|
||||
// try to buy some weapons
|
||||
const float buyInterval = 0.02f;
|
||||
if (gpGlobals->curtime - me->GetStateTimestamp() > buyInterval)
|
||||
{
|
||||
me->m_stateTimestamp = gpGlobals->curtime;
|
||||
|
||||
bool isPreferredAllDisallowed = true;
|
||||
|
||||
// try to buy our preferred weapons first
|
||||
if (m_prefIndex < me->GetProfile()->GetWeaponPreferenceCount() && bot_randombuy.GetBool() == false )
|
||||
{
|
||||
// need to retry because sometimes first buy fails??
|
||||
const int maxPrefRetries = 2;
|
||||
if (m_prefRetries >= maxPrefRetries)
|
||||
{
|
||||
// try to buy next preferred weapon
|
||||
++m_prefIndex;
|
||||
m_prefRetries = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
int weaponPreference = me->GetProfile()->GetWeaponPreference( m_prefIndex );
|
||||
|
||||
// don't buy it again if we still have one from last round
|
||||
char weaponPreferenceName[32];
|
||||
Q_snprintf( weaponPreferenceName, sizeof(weaponPreferenceName), "weapon_%s", me->GetProfile()->GetWeaponPreferenceAsString( m_prefIndex ) );
|
||||
if( me->Weapon_OwnsThisType(weaponPreferenceName) )//Prefs and buyalias use the short version, this uses the long
|
||||
{
|
||||
// done with buying preferred weapon
|
||||
m_prefIndex = 9999;
|
||||
return;
|
||||
}
|
||||
|
||||
if (me->HasShield() && weaponPreference == WEAPON_SHIELDGUN)
|
||||
{
|
||||
// done with buying preferred weapon
|
||||
m_prefIndex = 9999;
|
||||
return;
|
||||
}
|
||||
|
||||
const char *buyAlias = NULL;
|
||||
|
||||
if (weaponPreference == WEAPON_SHIELDGUN)
|
||||
{
|
||||
if (TheCSBots()->AllowTacticalShield())
|
||||
buyAlias = "shield";
|
||||
}
|
||||
else
|
||||
{
|
||||
buyAlias = WeaponIDToAlias( weaponPreference );
|
||||
WeaponType type = GetWeaponType( buyAlias );
|
||||
switch( type )
|
||||
{
|
||||
case PISTOL:
|
||||
if (!TheCSBots()->AllowPistols())
|
||||
buyAlias = NULL;
|
||||
break;
|
||||
|
||||
case SHOTGUN:
|
||||
if (!TheCSBots()->AllowShotguns())
|
||||
buyAlias = NULL;
|
||||
break;
|
||||
|
||||
case SUB_MACHINE_GUN:
|
||||
if (!TheCSBots()->AllowSubMachineGuns())
|
||||
buyAlias = NULL;
|
||||
break;
|
||||
|
||||
case RIFLE:
|
||||
if (!TheCSBots()->AllowRifles())
|
||||
buyAlias = NULL;
|
||||
break;
|
||||
|
||||
case MACHINE_GUN:
|
||||
if (!TheCSBots()->AllowMachineGuns())
|
||||
buyAlias = NULL;
|
||||
break;
|
||||
|
||||
case SNIPER_RIFLE:
|
||||
if (!TheCSBots()->AllowSnipers())
|
||||
buyAlias = NULL;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (buyAlias)
|
||||
{
|
||||
Q_snprintf( cmdBuffer, 256, "buy %s\n", buyAlias );
|
||||
|
||||
CCommand args;
|
||||
args.Tokenize( cmdBuffer );
|
||||
me->ClientCommand( args );
|
||||
|
||||
me->PrintIfWatched( "Tried to buy preferred weapon %s.\n", buyAlias );
|
||||
isPreferredAllDisallowed = false;
|
||||
}
|
||||
|
||||
++m_prefRetries;
|
||||
|
||||
// bail out so we dont waste money on other equipment
|
||||
// unless everything we prefer has been disallowed, then buy at random
|
||||
if (isPreferredAllDisallowed == false)
|
||||
return;
|
||||
}
|
||||
|
||||
// if we have no preferred primary weapon (or everything we want is disallowed), buy at random
|
||||
if (!me->HasPrimaryWeapon() && (isPreferredAllDisallowed || !me->GetProfile()->HasPrimaryPreference()))
|
||||
{
|
||||
if (m_buyShield)
|
||||
{
|
||||
// buy a shield
|
||||
CCommand args;
|
||||
args.Tokenize( "buy shield" );
|
||||
me->ClientCommand( args );
|
||||
|
||||
me->PrintIfWatched( "Tried to buy a shield.\n" );
|
||||
}
|
||||
else
|
||||
{
|
||||
// build list of allowable weapons to buy
|
||||
BuyInfo *masterPrimary = (me->GetTeamNumber() == TEAM_TERRORIST) ? primaryWeaponBuyInfoT : primaryWeaponBuyInfoCT;
|
||||
BuyInfo *stockPrimary[ PRIMARY_WEAPON_BUY_COUNT ];
|
||||
int stockPrimaryCount = 0;
|
||||
|
||||
// dont choose sniper rifles as often
|
||||
const float sniperRifleChance = 50.0f;
|
||||
bool wantSniper = (RandomFloat( 0, 100 ) < sniperRifleChance) ? true : false;
|
||||
|
||||
if ( bot_randombuy.GetBool() )
|
||||
{
|
||||
wantSniper = true;
|
||||
}
|
||||
|
||||
for( int i=0; i<PRIMARY_WEAPON_BUY_COUNT; ++i )
|
||||
{
|
||||
if ((masterPrimary[i].type == SHOTGUN && TheCSBots()->AllowShotguns()) ||
|
||||
(masterPrimary[i].type == SUB_MACHINE_GUN && TheCSBots()->AllowSubMachineGuns()) ||
|
||||
(masterPrimary[i].type == RIFLE && TheCSBots()->AllowRifles()) ||
|
||||
(masterPrimary[i].type == SNIPER_RIFLE && TheCSBots()->AllowSnipers() && wantSniper) ||
|
||||
(masterPrimary[i].type == MACHINE_GUN && TheCSBots()->AllowMachineGuns()))
|
||||
{
|
||||
stockPrimary[ stockPrimaryCount++ ] = &masterPrimary[i];
|
||||
}
|
||||
}
|
||||
|
||||
if (stockPrimaryCount)
|
||||
{
|
||||
// buy primary weapon if we don't have one
|
||||
int which;
|
||||
|
||||
// on hard difficulty levels, bots try to buy preferred weapons on the first pass
|
||||
if (m_retries == 0 && TheCSBots()->GetDifficultyLevel() >= BOT_HARD && bot_randombuy.GetBool() == false )
|
||||
{
|
||||
// count up available preferred weapons
|
||||
int prefCount = 0;
|
||||
for( which=0; which<stockPrimaryCount; ++which )
|
||||
if (stockPrimary[which]->preferred)
|
||||
++prefCount;
|
||||
|
||||
if (prefCount)
|
||||
{
|
||||
int whichPref = RandomInt( 0, prefCount-1 );
|
||||
for( which=0; which<stockPrimaryCount; ++which )
|
||||
if (stockPrimary[which]->preferred && whichPref-- == 0)
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
// no preferred weapons available, just pick randomly
|
||||
which = RandomInt( 0, stockPrimaryCount-1 );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
which = RandomInt( 0, stockPrimaryCount-1 );
|
||||
}
|
||||
|
||||
Q_snprintf( cmdBuffer, 256, "buy %s\n", stockPrimary[ which ]->buyAlias );
|
||||
|
||||
CCommand args;
|
||||
args.Tokenize( cmdBuffer );
|
||||
me->ClientCommand( args );
|
||||
|
||||
me->PrintIfWatched( "Tried to buy %s.\n", stockPrimary[ which ]->buyAlias );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// If we now have a weapon, or have tried for too long, we're done
|
||||
//
|
||||
if (me->HasPrimaryWeapon() || m_retries++ > 5)
|
||||
{
|
||||
// primary ammo
|
||||
CCommand args;
|
||||
if (me->HasPrimaryWeapon())
|
||||
{
|
||||
args.Tokenize( "buy primammo" );
|
||||
me->ClientCommand( args );
|
||||
}
|
||||
|
||||
// buy armor last, to make sure we bought a weapon first
|
||||
args.Tokenize( "buy vesthelm" );
|
||||
me->ClientCommand( args );
|
||||
args.Tokenize( "buy vest" );
|
||||
me->ClientCommand( args );
|
||||
|
||||
// pistols - if we have no preferred pistol, buy at random
|
||||
if (TheCSBots()->AllowPistols() && !me->GetProfile()->HasPistolPreference())
|
||||
{
|
||||
if (m_buyPistol)
|
||||
{
|
||||
int which = RandomInt( 0, SECONDARY_WEAPON_BUY_COUNT-1 );
|
||||
|
||||
const char *what = NULL;
|
||||
|
||||
if (me->GetTeamNumber() == TEAM_TERRORIST)
|
||||
what = secondaryWeaponBuyInfoT[ which ].buyAlias;
|
||||
else
|
||||
what = secondaryWeaponBuyInfoCT[ which ].buyAlias;
|
||||
|
||||
Q_snprintf( cmdBuffer, 256, "buy %s\n", what );
|
||||
args.Tokenize( cmdBuffer );
|
||||
me->ClientCommand( args );
|
||||
|
||||
|
||||
// only buy one pistol
|
||||
m_buyPistol = false;
|
||||
}
|
||||
|
||||
// make sure we have enough pistol ammo
|
||||
args.Tokenize( "buy secammo" );
|
||||
me->ClientCommand( args );
|
||||
}
|
||||
|
||||
// buy a grenade if we wish, and we don't already have one
|
||||
if (m_buyGrenade && !me->HasGrenade())
|
||||
{
|
||||
if (UTIL_IsTeamAllBots( me->GetTeamNumber() ))
|
||||
{
|
||||
// only allow Flashbangs if everyone on the team is a bot (dont want to blind our friendly humans)
|
||||
float rnd = RandomFloat( 0, 100 );
|
||||
|
||||
if (rnd < 10)
|
||||
{
|
||||
args.Tokenize( "buy smokegrenade" );
|
||||
me->ClientCommand( args ); // smoke grenade
|
||||
}
|
||||
else if (rnd < 35)
|
||||
{
|
||||
args.Tokenize( "buy flashbang" );
|
||||
me->ClientCommand( args ); // flashbang
|
||||
}
|
||||
else
|
||||
{
|
||||
args.Tokenize( "buy hegrenade" );
|
||||
me->ClientCommand( args ); // he grenade
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (RandomFloat( 0, 100 ) < 10)
|
||||
{
|
||||
args.Tokenize( "buy smokegrenade" ); // smoke grenade
|
||||
me->ClientCommand( args );
|
||||
}
|
||||
else
|
||||
{
|
||||
args.Tokenize( "buy hegrenade" ); // he grenade
|
||||
me->ClientCommand( args );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (m_buyDefuseKit)
|
||||
{
|
||||
args.Tokenize( "buy defuser" );
|
||||
me->ClientCommand( args );
|
||||
}
|
||||
|
||||
m_doneBuying = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
void BuyState::OnExit( CCSBot *me )
|
||||
{
|
||||
me->ResetStuckMonitor();
|
||||
me->EquipBestWeapon();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose:
|
||||
//
|
||||
// $NoKeywords: $
|
||||
//=============================================================================//
|
||||
|
||||
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
|
||||
|
||||
#include "cbase.h"
|
||||
#include "cs_bot.h"
|
||||
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Begin defusing the bomb
|
||||
*/
|
||||
void DefuseBombState::OnEnter( CCSBot *me )
|
||||
{
|
||||
me->Crouch();
|
||||
me->SetDisposition( CCSBot::SELF_DEFENSE );
|
||||
me->GetChatter()->Say( "DefusingBomb" );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Defuse the bomb
|
||||
*/
|
||||
void DefuseBombState::OnUpdate( CCSBot *me )
|
||||
{
|
||||
const Vector *bombPos = me->GetGameState()->GetBombPosition();
|
||||
|
||||
if (bombPos == NULL)
|
||||
{
|
||||
me->PrintIfWatched( "In Defuse state, but don't know where the bomb is!\n" );
|
||||
me->Idle();
|
||||
return;
|
||||
}
|
||||
|
||||
// look at the bomb
|
||||
me->SetLookAt( "Defuse bomb", *bombPos, PRIORITY_HIGH );
|
||||
|
||||
// defuse...
|
||||
me->UseEnvironment();
|
||||
|
||||
if (gpGlobals->curtime - me->GetStateTimestamp() > 1.0f)
|
||||
{
|
||||
// if we missed starting the defuse, give up
|
||||
if (TheCSBots()->GetBombDefuser() == NULL)
|
||||
{
|
||||
me->PrintIfWatched( "Failed to start defuse, giving up\n" );
|
||||
me->Idle();
|
||||
return;
|
||||
}
|
||||
else if (TheCSBots()->GetBombDefuser() != me)
|
||||
{
|
||||
// if someone else got the defuse, give up
|
||||
me->PrintIfWatched( "Someone else started defusing, giving up\n" );
|
||||
me->Idle();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// if bomb has been defused, give up
|
||||
if (!TheCSBots()->IsBombPlanted())
|
||||
{
|
||||
me->Idle();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
void DefuseBombState::OnExit( CCSBot *me )
|
||||
{
|
||||
me->StandUp();
|
||||
me->ResetStuckMonitor();
|
||||
me->SetTask( CCSBot::SEEK_AND_DESTROY );
|
||||
me->SetDisposition( CCSBot::ENGAGE_AND_INVESTIGATE );
|
||||
me->ClearLookAt();
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose:
|
||||
//
|
||||
// $NoKeywords: $
|
||||
//=============================================================================//
|
||||
|
||||
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
|
||||
|
||||
#include "cbase.h"
|
||||
#include "cs_bot.h"
|
||||
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Escape from the bomb.
|
||||
*/
|
||||
void EscapeFromBombState::OnEnter( CCSBot *me )
|
||||
{
|
||||
me->StandUp();
|
||||
me->Run();
|
||||
me->DestroyPath();
|
||||
me->EquipKnife();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Escape from the bomb.
|
||||
*/
|
||||
void EscapeFromBombState::OnUpdate( CCSBot *me )
|
||||
{
|
||||
const Vector *bombPos = me->GetGameState()->GetBombPosition();
|
||||
|
||||
// if we don't know where the bomb is, we shouldn't be in this state
|
||||
if (bombPos == NULL)
|
||||
{
|
||||
me->Idle();
|
||||
return;
|
||||
}
|
||||
|
||||
// grab our knife to move quickly
|
||||
me->EquipKnife();
|
||||
|
||||
// look around
|
||||
me->UpdateLookAround();
|
||||
|
||||
if (me->UpdatePathMovement() != CCSBot::PROGRESSING)
|
||||
{
|
||||
// we have no path, or reached the end of one - create a new path far away from the bomb
|
||||
FarAwayFromPositionFunctor func( *bombPos );
|
||||
CNavArea *goalArea = FindMinimumCostArea( me->GetLastKnownArea(), func );
|
||||
|
||||
// if this fails, we'll try again next time
|
||||
me->ComputePath( goalArea->GetCenter(), FASTEST_ROUTE );
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Escape from the bomb.
|
||||
*/
|
||||
void EscapeFromBombState::OnExit( CCSBot *me )
|
||||
{
|
||||
me->EquipBestWeapon();
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose:
|
||||
//
|
||||
// $NoKeywords: $
|
||||
//=============================================================================//
|
||||
|
||||
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
|
||||
|
||||
#include "cbase.h"
|
||||
#include "cs_bot.h"
|
||||
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Move to the bomb on the floor and pick it up
|
||||
*/
|
||||
void FetchBombState::OnEnter( CCSBot *me )
|
||||
{
|
||||
me->DestroyPath();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Move to the bomb on the floor and pick it up
|
||||
*/
|
||||
void FetchBombState::OnUpdate( CCSBot *me )
|
||||
{
|
||||
if (me->HasC4())
|
||||
{
|
||||
me->PrintIfWatched( "I picked up the bomb\n" );
|
||||
me->Idle();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
CBaseEntity *bomb = TheCSBots()->GetLooseBomb();
|
||||
if (bomb)
|
||||
{
|
||||
if (!me->HasPath())
|
||||
{
|
||||
// build a path to the bomb
|
||||
if (me->ComputePath( bomb->GetAbsOrigin() ) == false)
|
||||
{
|
||||
me->PrintIfWatched( "Fetch bomb pathfind failed\n" );
|
||||
|
||||
// go Hunt instead of Idle to prevent continuous re-pathing to inaccessible bomb
|
||||
me->Hunt();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// someone picked up the bomb
|
||||
me->PrintIfWatched( "Someone else picked up the bomb.\n" );
|
||||
me->Idle();
|
||||
return;
|
||||
}
|
||||
|
||||
// look around
|
||||
me->UpdateLookAround();
|
||||
|
||||
if (me->UpdatePathMovement() != CCSBot::PROGRESSING)
|
||||
me->Idle();
|
||||
}
|
||||
@@ -0,0 +1,366 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose:
|
||||
//
|
||||
// $NoKeywords: $
|
||||
//=============================================================================//
|
||||
|
||||
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
|
||||
|
||||
#include "cbase.h"
|
||||
#include "cs_bot.h"
|
||||
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Follow our leader
|
||||
*/
|
||||
void FollowState::OnEnter( CCSBot *me )
|
||||
{
|
||||
me->StandUp();
|
||||
me->Run();
|
||||
me->DestroyPath();
|
||||
|
||||
m_isStopped = false;
|
||||
m_stoppedTimestamp = 0.0f;
|
||||
|
||||
// to force immediate repath
|
||||
m_lastLeaderPos.x = -99999999.9f;
|
||||
m_lastLeaderPos.y = -99999999.9f;
|
||||
m_lastLeaderPos.z = -99999999.9f;
|
||||
|
||||
m_lastSawLeaderTime = 0;
|
||||
|
||||
// set re-pathing frequency
|
||||
m_repathInterval.Invalidate();
|
||||
|
||||
m_isSneaking = false;
|
||||
|
||||
m_walkTime.Invalidate();
|
||||
m_isAtWalkSpeed = false;
|
||||
|
||||
m_leaderMotionState = INVALID;
|
||||
m_idleTimer.Start( RandomFloat( 2.0f, 5.0f ) );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Determine the leader's motion state by tracking his speed
|
||||
*/
|
||||
void FollowState::ComputeLeaderMotionState( float leaderSpeed )
|
||||
{
|
||||
// walk = 130, run = 250
|
||||
const float runWalkThreshold = 140.0f;
|
||||
const float walkStopThreshold = 10.0f; // 120.0f;
|
||||
LeaderMotionStateType prevState = m_leaderMotionState;
|
||||
if (leaderSpeed > runWalkThreshold)
|
||||
{
|
||||
m_leaderMotionState = RUNNING;
|
||||
m_isAtWalkSpeed = false;
|
||||
}
|
||||
else if (leaderSpeed > walkStopThreshold)
|
||||
{
|
||||
// track when began to walk
|
||||
if (!m_isAtWalkSpeed)
|
||||
{
|
||||
m_walkTime.Start();
|
||||
m_isAtWalkSpeed = true;
|
||||
}
|
||||
|
||||
const float minWalkTime = 0.25f;
|
||||
if (m_walkTime.GetElapsedTime() > minWalkTime)
|
||||
{
|
||||
m_leaderMotionState = WALKING;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_leaderMotionState = STOPPED;
|
||||
m_isAtWalkSpeed = false;
|
||||
}
|
||||
|
||||
// track time spent in this motion state
|
||||
if (prevState != m_leaderMotionState)
|
||||
{
|
||||
m_leaderMotionStateTime.Start();
|
||||
m_waitTime = RandomFloat( 1.0f, 3.0f );
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Functor to collect all areas in the forward direction of the given player within a radius
|
||||
*/
|
||||
class FollowTargetCollector
|
||||
{
|
||||
public:
|
||||
FollowTargetCollector( CBasePlayer *player )
|
||||
{
|
||||
m_player = player;
|
||||
|
||||
Vector playerVel = player->GetAbsVelocity();
|
||||
m_forward.x = playerVel.x;
|
||||
m_forward.y = playerVel.y;
|
||||
float speed = m_forward.NormalizeInPlace();
|
||||
|
||||
Vector playerOrigin = GetCentroid( player );
|
||||
|
||||
const float walkSpeed = 100.0f;
|
||||
if (speed < walkSpeed)
|
||||
{
|
||||
m_cutoff.x = playerOrigin.x;
|
||||
m_cutoff.y = playerOrigin.y;
|
||||
m_forward.x = 0.0f;
|
||||
m_forward.y = 0.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
const float k = 1.5f; // 2.0f;
|
||||
float trimSpeed = MIN( speed, 200.0f );
|
||||
m_cutoff.x = playerOrigin.x + k * trimSpeed * m_forward.x;
|
||||
m_cutoff.y = playerOrigin.y + k * trimSpeed * m_forward.y;
|
||||
}
|
||||
|
||||
m_targetAreaCount = 0;
|
||||
}
|
||||
|
||||
enum { MAX_TARGET_AREAS = 128 };
|
||||
|
||||
bool operator() ( CNavArea *area )
|
||||
{
|
||||
if (m_targetAreaCount >= MAX_TARGET_AREAS)
|
||||
return false;
|
||||
|
||||
// only use two-way connections
|
||||
if (!area->GetParent() || area->IsConnected( area->GetParent(), NUM_DIRECTIONS ))
|
||||
{
|
||||
if (m_forward.IsZero())
|
||||
{
|
||||
m_targetArea[ m_targetAreaCount++ ] = area;
|
||||
}
|
||||
else
|
||||
{
|
||||
// collect areas in the direction of the player's forward motion
|
||||
Vector2D to( area->GetCenter().x - m_cutoff.x, area->GetCenter().y - m_cutoff.y );
|
||||
to.NormalizeInPlace();
|
||||
|
||||
//if (DotProduct( to, m_forward ) > 0.7071f)
|
||||
if ((to.x * m_forward.x + to.y * m_forward.y) > 0.7071f)
|
||||
m_targetArea[ m_targetAreaCount++ ] = area;
|
||||
}
|
||||
}
|
||||
|
||||
return (m_targetAreaCount < MAX_TARGET_AREAS);
|
||||
}
|
||||
|
||||
|
||||
CBasePlayer *m_player;
|
||||
Vector2D m_forward;
|
||||
Vector2D m_cutoff;
|
||||
|
||||
CNavArea *m_targetArea[ MAX_TARGET_AREAS ];
|
||||
int m_targetAreaCount;
|
||||
};
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Follow our leader
|
||||
* @todo Clean up this nasty mess
|
||||
*/
|
||||
void FollowState::OnUpdate( CCSBot *me )
|
||||
{
|
||||
// if we lost our leader, give up
|
||||
if (m_leader == NULL || !m_leader->IsAlive())
|
||||
{
|
||||
me->Idle();
|
||||
return;
|
||||
}
|
||||
|
||||
// if we are carrying the bomb and at a bombsite, plant
|
||||
if (me->HasC4() && me->IsAtBombsite())
|
||||
{
|
||||
// plant it
|
||||
me->SetTask( CCSBot::PLANT_BOMB );
|
||||
me->PlantBomb();
|
||||
|
||||
// radio to the team
|
||||
me->GetChatter()->PlantingTheBomb( me->GetPlace() );
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// look around
|
||||
me->UpdateLookAround();
|
||||
|
||||
// if we are moving, we are not idle
|
||||
if (me->IsNotMoving() == false)
|
||||
m_idleTimer.Start( RandomFloat( 2.0f, 5.0f ) );
|
||||
|
||||
// compute the leader's speed
|
||||
Vector leaderVel = m_leader->GetAbsVelocity();
|
||||
float leaderSpeed = Vector2D( leaderVel.x, leaderVel.y ).Length();
|
||||
|
||||
// determine our leader's movement state
|
||||
ComputeLeaderMotionState( leaderSpeed );
|
||||
|
||||
// track whether we can see the leader
|
||||
bool isLeaderVisible;
|
||||
Vector leaderOrigin = GetCentroid( m_leader );
|
||||
if (me->IsVisible( leaderOrigin ))
|
||||
{
|
||||
m_lastSawLeaderTime = gpGlobals->curtime;
|
||||
isLeaderVisible = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
isLeaderVisible = false;
|
||||
}
|
||||
|
||||
|
||||
// determine whether we should sneak or not
|
||||
const float farAwayRange = 750.0f;
|
||||
Vector myOrigin = GetCentroid( me );
|
||||
if ((leaderOrigin - myOrigin).IsLengthGreaterThan( farAwayRange ))
|
||||
{
|
||||
// far away from leader - run to catch up
|
||||
m_isSneaking = false;
|
||||
}
|
||||
else if (isLeaderVisible)
|
||||
{
|
||||
// if we see leader walking and we are nearby, walk
|
||||
if (m_leaderMotionState == WALKING)
|
||||
m_isSneaking = true;
|
||||
|
||||
// if we are sneaking and our leader starts running, stop sneaking
|
||||
if (m_isSneaking && m_leaderMotionState == RUNNING)
|
||||
m_isSneaking = false;
|
||||
}
|
||||
|
||||
// if we haven't seen the leader for a long time, run
|
||||
const float longTime = 20.0f;
|
||||
if (gpGlobals->curtime - m_lastSawLeaderTime > longTime)
|
||||
m_isSneaking = false;
|
||||
|
||||
if (m_isSneaking)
|
||||
me->Walk();
|
||||
else
|
||||
me->Run();
|
||||
|
||||
|
||||
bool repath = false;
|
||||
|
||||
// if the leader has stopped, hide nearby
|
||||
const float nearLeaderRange = 250.0f;
|
||||
if (!me->HasPath() && m_leaderMotionState == STOPPED && m_leaderMotionStateTime.GetElapsedTime() > m_waitTime)
|
||||
{
|
||||
// throttle how often this check occurs
|
||||
m_waitTime += RandomFloat( 1.0f, 3.0f );
|
||||
|
||||
// the leader has stopped - if we are close to him, take up a hiding spot
|
||||
if ((leaderOrigin - myOrigin).IsLengthLessThan( nearLeaderRange ))
|
||||
{
|
||||
const float hideRange = 250.0f;
|
||||
if (me->TryToHide( NULL, -1.0f, hideRange, false, USE_NEAREST ))
|
||||
{
|
||||
me->ResetStuckMonitor();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if we have been idle for awhile, move
|
||||
if (m_idleTimer.IsElapsed())
|
||||
{
|
||||
repath = true;
|
||||
|
||||
// always walk when we move such a short distance
|
||||
m_isSneaking = true;
|
||||
}
|
||||
|
||||
// if our leader has moved, repath (don't repath if leading is stopping)
|
||||
if (leaderSpeed > 100.0f && m_leaderMotionState != STOPPED)
|
||||
{
|
||||
repath = true;
|
||||
}
|
||||
|
||||
// move along our path
|
||||
if (me->UpdatePathMovement( NO_SPEED_CHANGE ) != CCSBot::PROGRESSING)
|
||||
{
|
||||
me->DestroyPath();
|
||||
}
|
||||
|
||||
// recompute our path if necessary
|
||||
if (repath && m_repathInterval.IsElapsed() && !me->IsOnLadder())
|
||||
{
|
||||
// recompute our path to keep us near our leader
|
||||
m_lastLeaderPos = leaderOrigin;
|
||||
|
||||
me->ResetStuckMonitor();
|
||||
|
||||
const float runSpeed = 200.0f;
|
||||
|
||||
const float collectRange = (leaderSpeed > runSpeed) ? 600.0f : 400.0f; // 400, 200
|
||||
FollowTargetCollector collector( m_leader );
|
||||
SearchSurroundingAreas( TheNavMesh->GetNearestNavArea( m_lastLeaderPos ), m_lastLeaderPos, collector, collectRange );
|
||||
|
||||
if (cv_bot_debug.GetBool())
|
||||
{
|
||||
for( int i=0; i<collector.m_targetAreaCount; ++i )
|
||||
collector.m_targetArea[i]->Draw( /*255, 0, 0, 2*/ );
|
||||
}
|
||||
|
||||
// move to one of the collected areas
|
||||
if (collector.m_targetAreaCount)
|
||||
{
|
||||
CNavArea *target = NULL;
|
||||
Vector targetPos;
|
||||
|
||||
// if we are idle, pick a random area
|
||||
if (m_idleTimer.IsElapsed())
|
||||
{
|
||||
target = collector.m_targetArea[ RandomInt( 0, collector.m_targetAreaCount-1 ) ];
|
||||
targetPos = target->GetCenter();
|
||||
me->PrintIfWatched( "%4.1f: Bored. Repathing to a new nearby area\n", gpGlobals->curtime );
|
||||
}
|
||||
else
|
||||
{
|
||||
me->PrintIfWatched( "%4.1f: Repathing to stay with leader.\n", gpGlobals->curtime );
|
||||
|
||||
// find closest area to where we are
|
||||
CNavArea *area;
|
||||
float closeRangeSq = 9999999999.9f;
|
||||
Vector close;
|
||||
|
||||
for( int a=0; a<collector.m_targetAreaCount; ++a )
|
||||
{
|
||||
area = collector.m_targetArea[a];
|
||||
|
||||
area->GetClosestPointOnArea( myOrigin, &close );
|
||||
|
||||
float rangeSq = (myOrigin - close).LengthSqr();
|
||||
if (rangeSq < closeRangeSq)
|
||||
{
|
||||
target = area;
|
||||
targetPos = close;
|
||||
closeRangeSq = rangeSq;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (target == NULL || me->ComputePath( target->GetCenter(), FASTEST_ROUTE ) == false)
|
||||
me->PrintIfWatched( "Pathfind to leader failed.\n" );
|
||||
|
||||
// throttle how often we repath
|
||||
m_repathInterval.Start( 0.5f );
|
||||
|
||||
m_idleTimer.Reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
void FollowState::OnExit( CCSBot *me )
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,549 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose:
|
||||
//
|
||||
// $NoKeywords: $
|
||||
//=============================================================================//
|
||||
|
||||
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
|
||||
|
||||
#include "cbase.h"
|
||||
#include "cs_simple_hostage.h"
|
||||
#include "cs_bot.h"
|
||||
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Begin moving to a nearby hidey-hole.
|
||||
* NOTE: Do not forget this state may include a very long "move-to" time to get to our hidey spot!
|
||||
*/
|
||||
void HideState::OnEnter( CCSBot *me )
|
||||
{
|
||||
m_isAtSpot = false;
|
||||
m_isLookingOutward = false;
|
||||
|
||||
// if duration is "infinite", set it to a reasonably long time to prevent infinite camping
|
||||
if (m_duration < 0.0f)
|
||||
{
|
||||
m_duration = RandomFloat( 30.0f, 60.0f );
|
||||
}
|
||||
|
||||
// decide whether to "ambush" or not - never set to false so as not to override external setting
|
||||
if (RandomFloat( 0.0f, 100.0f ) < 50.0f)
|
||||
{
|
||||
m_isHoldingPosition = true;
|
||||
}
|
||||
|
||||
// if we are holding position, decide for how long
|
||||
if (m_isHoldingPosition)
|
||||
{
|
||||
m_holdPositionTime = RandomFloat( 3.0f, 10.0f );
|
||||
}
|
||||
else
|
||||
{
|
||||
m_holdPositionTime = 0.0f;
|
||||
}
|
||||
|
||||
m_heardEnemy = false;
|
||||
m_firstHeardEnemyTime = 0.0f;
|
||||
m_retry = 0;
|
||||
|
||||
if (me->IsFollowing())
|
||||
{
|
||||
m_leaderAnchorPos = GetCentroid( me->GetFollowLeader() );
|
||||
}
|
||||
|
||||
// if we are a sniper, we need to periodically pause while we retreat to squeeze off a shot or two
|
||||
if (me->IsSniper())
|
||||
{
|
||||
// start off paused to allow a final shot before retreating
|
||||
m_isPaused = false;
|
||||
m_pauseTimer.Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Move to a nearby hidey-hole.
|
||||
* NOTE: Do not forget this state may include a very long "move-to" time to get to our hidey spot!
|
||||
*/
|
||||
void HideState::OnUpdate( CCSBot *me )
|
||||
{
|
||||
Vector myOrigin = GetCentroid( me );
|
||||
|
||||
// wait until finished reloading to leave hide state
|
||||
if (!me->IsReloading())
|
||||
{
|
||||
// if we are momentarily hiding while following someone, check to see if he has moved on
|
||||
if (me->IsFollowing())
|
||||
{
|
||||
CCSPlayer *leader = static_cast<CCSPlayer *>( static_cast<CBaseEntity *>( me->GetFollowLeader() ) );
|
||||
Vector leaderOrigin = GetCentroid( leader );
|
||||
|
||||
// BOTPORT: Determine walk/run velocity thresholds
|
||||
float runThreshold = 200.0f;
|
||||
if (leader->GetAbsVelocity().IsLengthGreaterThan( runThreshold ))
|
||||
{
|
||||
// leader is running, stay with him
|
||||
me->Follow( leader );
|
||||
return;
|
||||
}
|
||||
|
||||
// if leader has moved, stay with him
|
||||
const float followRange = 250.0f;
|
||||
if ((m_leaderAnchorPos - leaderOrigin).IsLengthGreaterThan( followRange ))
|
||||
{
|
||||
me->Follow( leader );
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// if we see a nearby buddy in combat, join him
|
||||
/// @todo - Perhaps tie in to TakeDamage(), so it works for human players, too
|
||||
|
||||
//
|
||||
// Scenario logic
|
||||
//
|
||||
switch( TheCSBots()->GetScenario() )
|
||||
{
|
||||
case CCSBotManager::SCENARIO_DEFUSE_BOMB:
|
||||
{
|
||||
if (me->GetTeamNumber() == TEAM_CT)
|
||||
{
|
||||
// if we are just holding position (due to a radio order) and the bomb has just planted, go defuse it
|
||||
if (me->GetTask() == CCSBot::HOLD_POSITION &&
|
||||
TheCSBots()->IsBombPlanted() &&
|
||||
TheCSBots()->GetBombPlantTimestamp() > me->GetStateTimestamp())
|
||||
{
|
||||
me->Idle();
|
||||
return;
|
||||
}
|
||||
|
||||
// if we are guarding the defuser and he dies/gives up, stop hiding (to choose another defuser)
|
||||
if (me->GetTask() == CCSBot::GUARD_BOMB_DEFUSER && TheCSBots()->GetBombDefuser() == NULL)
|
||||
{
|
||||
me->Idle();
|
||||
return;
|
||||
}
|
||||
|
||||
// if we are guarding the loose bomb and it is picked up, stop hiding
|
||||
if (me->GetTask() == CCSBot::GUARD_LOOSE_BOMB && TheCSBots()->GetLooseBomb() == NULL)
|
||||
{
|
||||
me->GetChatter()->TheyPickedUpTheBomb();
|
||||
me->Idle();
|
||||
return;
|
||||
}
|
||||
|
||||
// if we are guarding a bombsite and the bomb is dropped and we hear about it, stop guarding
|
||||
if (me->GetTask() == CCSBot::GUARD_BOMB_ZONE && me->GetGameState()->IsLooseBombLocationKnown())
|
||||
{
|
||||
me->Idle();
|
||||
return;
|
||||
}
|
||||
|
||||
// if we are guarding (bombsite, initial encounter, etc) and the bomb is planted, go defuse it
|
||||
if (me->IsDoingScenario() && me->GetTask() != CCSBot::GUARD_BOMB_DEFUSER && TheCSBots()->IsBombPlanted())
|
||||
{
|
||||
me->Idle();
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
else // TERRORIST
|
||||
{
|
||||
// if we are near the ticking bomb and someone starts defusing it, attack!
|
||||
if (TheCSBots()->GetBombDefuser())
|
||||
{
|
||||
Vector defuserOrigin = GetCentroid( TheCSBots()->GetBombDefuser() );
|
||||
Vector toDefuser = defuserOrigin - myOrigin;
|
||||
|
||||
const float hearDefuseRange = 2000.0f;
|
||||
if (toDefuser.IsLengthLessThan( hearDefuseRange ))
|
||||
{
|
||||
// if we are nearby, attack, otherwise move to the bomb (which will cause us to attack when we see defuser)
|
||||
if (me->CanSeePlantedBomb())
|
||||
{
|
||||
me->Attack( TheCSBots()->GetBombDefuser() );
|
||||
}
|
||||
else
|
||||
{
|
||||
me->MoveTo( defuserOrigin, FASTEST_ROUTE );
|
||||
me->InhibitLookAround( 10.0f );
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
case CCSBotManager::SCENARIO_RESCUE_HOSTAGES:
|
||||
{
|
||||
// if we're guarding the hostages and they all die or are taken, do something else
|
||||
if (me->GetTask() == CCSBot::GUARD_HOSTAGES)
|
||||
{
|
||||
if (me->GetGameState()->AreAllHostagesBeingRescued() || me->GetGameState()->AreAllHostagesGone())
|
||||
{
|
||||
me->Idle();
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (me->GetTask() == CCSBot::GUARD_HOSTAGE_RESCUE_ZONE)
|
||||
{
|
||||
// if we stumble across a hostage, guard it
|
||||
CHostage *hostage = me->GetGameState()->GetNearestVisibleFreeHostage();
|
||||
if (hostage)
|
||||
{
|
||||
// we see a free hostage, guard it
|
||||
Vector hostageOrigin = GetCentroid( hostage );
|
||||
CNavArea *area = TheNavMesh->GetNearestNavArea( hostageOrigin );
|
||||
if (area)
|
||||
{
|
||||
me->SetTask( CCSBot::GUARD_HOSTAGES );
|
||||
me->Hide( area );
|
||||
me->PrintIfWatched( "I'm guarding hostages I found\n" );
|
||||
// don't chatter here - he'll tell us when he's in his hiding spot
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool isSettledInSniper = (me->IsSniper() && m_isAtSpot) ? true : false;
|
||||
|
||||
// only investigate noises if we are initiating attacks, and we aren't a "settled in" sniper
|
||||
// dont investigate noises if we are reloading
|
||||
if (!me->IsReloading() &&
|
||||
!isSettledInSniper &&
|
||||
me->GetDisposition() == CCSBot::ENGAGE_AND_INVESTIGATE)
|
||||
{
|
||||
// if we are holding position, and have heard the enemy nearby, investigate after our hold time is up
|
||||
if (m_isHoldingPosition && m_heardEnemy && (gpGlobals->curtime - m_firstHeardEnemyTime > m_holdPositionTime))
|
||||
{
|
||||
/// @todo We might need to remember specific location of last enemy noise here
|
||||
me->InvestigateNoise();
|
||||
return;
|
||||
}
|
||||
|
||||
// investigate nearby enemy noises
|
||||
if (me->HeardInterestingNoise())
|
||||
{
|
||||
// if we are holding position, check if enough time has elapsed since we first heard the enemy
|
||||
if (m_isAtSpot && m_isHoldingPosition)
|
||||
{
|
||||
if (!m_heardEnemy)
|
||||
{
|
||||
// first time we heard the enemy
|
||||
m_heardEnemy = true;
|
||||
m_firstHeardEnemyTime = gpGlobals->curtime;
|
||||
me->PrintIfWatched( "Heard enemy, holding position for %f2.1 seconds...\n", m_holdPositionTime );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// not holding position - investigate enemy noise
|
||||
me->InvestigateNoise();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
} // end reloading check
|
||||
|
||||
// look around
|
||||
me->UpdateLookAround();
|
||||
|
||||
// if we are at our hiding spot, crouch and wait
|
||||
if (m_isAtSpot)
|
||||
{
|
||||
me->ResetStuckMonitor();
|
||||
|
||||
CNavArea *area = TheNavMesh->GetNavArea( m_hidingSpot );
|
||||
if ( !area || !( area->GetAttributes() & NAV_MESH_STAND ) )
|
||||
{
|
||||
me->Crouch();
|
||||
}
|
||||
|
||||
// check if duration has expired
|
||||
if (m_hideTimer.IsElapsed())
|
||||
{
|
||||
if (me->GetTask() == CCSBot::GUARD_LOOSE_BOMB)
|
||||
{
|
||||
// if we're guarding the loose bomb, continue to guard it but pick a new spot
|
||||
me->Hide( TheCSBots()->GetLooseBombArea() );
|
||||
return;
|
||||
}
|
||||
else if (me->GetTask() == CCSBot::GUARD_BOMB_ZONE)
|
||||
{
|
||||
// if we're guarding a bombsite, continue to guard it but pick a new spot
|
||||
const CCSBotManager::Zone *zone = TheCSBots()->GetClosestZone( myOrigin );
|
||||
if (zone)
|
||||
{
|
||||
CNavArea *area = TheCSBots()->GetRandomAreaInZone( zone );
|
||||
if (area)
|
||||
{
|
||||
me->Hide( area );
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (me->GetTask() == CCSBot::GUARD_HOSTAGE_RESCUE_ZONE)
|
||||
{
|
||||
// if we're guarding a rescue zone, continue to guard this or another rescue zone
|
||||
if (me->GuardRandomZone())
|
||||
{
|
||||
me->SetTask( CCSBot::GUARD_HOSTAGE_RESCUE_ZONE );
|
||||
me->PrintIfWatched( "Continuing to guard hostage rescue zones\n" );
|
||||
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
|
||||
me->GetChatter()->GuardingHostageEscapeZone( IS_PLAN );
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
me->Idle();
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
// if we are watching for an approaching noisy enemy, anticipate and fire before they round the corner
|
||||
/// @todo Need to check if we are looking at an ENEMY_NOISE here
|
||||
const float veryCloseNoise = 250.0f;
|
||||
if (me->IsLookingAtSpot() && me->GetNoiseRange() < veryCloseNoise)
|
||||
{
|
||||
// fire!
|
||||
me->PrimaryAttack();
|
||||
me->PrintIfWatched( "Firing at anticipated enemy coming around the corner!\n" );
|
||||
}
|
||||
*/
|
||||
|
||||
// if we have a shield, hide behind it
|
||||
if (me->HasShield() && !me->IsProtectedByShield())
|
||||
me->SecondaryAttack();
|
||||
|
||||
// while sitting at our hiding spot, if we are being attacked but can't see our attacker, move somewhere else
|
||||
const float hurtRecentlyTime = 1.0f;
|
||||
if (!me->IsEnemyVisible() && me->GetTimeSinceAttacked() < hurtRecentlyTime)
|
||||
{
|
||||
me->Idle();
|
||||
return;
|
||||
}
|
||||
|
||||
// encourage the human player
|
||||
if (!me->IsDoingScenario())
|
||||
{
|
||||
if (me->GetTeamNumber() == TEAM_CT)
|
||||
{
|
||||
if (me->GetTask() == CCSBot::GUARD_BOMB_ZONE &&
|
||||
me->IsAtHidingSpot() &&
|
||||
TheCSBots()->IsBombPlanted())
|
||||
{
|
||||
if (me->GetNearbyEnemyCount() == 0)
|
||||
{
|
||||
const float someTime = 30.0f;
|
||||
const float littleTime = 11.0;
|
||||
|
||||
if (TheCSBots()->GetBombTimeLeft() > someTime)
|
||||
me->GetChatter()->Encourage( "BombsiteSecure", RandomFloat( 10.0f, 15.0f ) );
|
||||
else if (TheCSBots()->GetBombTimeLeft() > littleTime)
|
||||
me->GetChatter()->Encourage( "WaitingForHumanToDefuseBomb", RandomFloat( 5.0f, 8.0f ) );
|
||||
else
|
||||
me->GetChatter()->Encourage( "WaitingForHumanToDefuseBombPanic", RandomFloat( 3.0f, 4.0f ) );
|
||||
}
|
||||
}
|
||||
|
||||
if (me->GetTask() == CCSBot::GUARD_HOSTAGES && me->IsAtHidingSpot())
|
||||
{
|
||||
if (me->GetNearbyEnemyCount() == 0)
|
||||
{
|
||||
CHostage *hostage = me->GetGameState()->GetNearestVisibleFreeHostage();
|
||||
if (hostage)
|
||||
{
|
||||
me->GetChatter()->Encourage( "WaitingForHumanToRescueHostages", RandomFloat( 10.0f, 15.0f ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// we are moving to our hiding spot
|
||||
|
||||
// snipers periodically pause and fire while retreating
|
||||
if (me->IsSniper() && me->IsEnemyVisible())
|
||||
{
|
||||
if (m_isPaused)
|
||||
{
|
||||
if (m_pauseTimer.IsElapsed())
|
||||
{
|
||||
// get moving
|
||||
m_isPaused = false;
|
||||
m_pauseTimer.Start( RandomFloat( 1.0f, 3.0f ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
me->Wait( 0.2f );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_pauseTimer.IsElapsed())
|
||||
{
|
||||
// pause for a moment
|
||||
m_isPaused = true;
|
||||
m_pauseTimer.Start( RandomFloat( 0.5f, 1.5f ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if a Player is using this hiding spot, give up
|
||||
float range;
|
||||
CCSPlayer *camper = static_cast<CCSPlayer *>( UTIL_GetClosestPlayer( m_hidingSpot, &range ) );
|
||||
|
||||
const float closeRange = 75.0f;
|
||||
if (camper && camper != me && range < closeRange && me->IsVisible( camper, CHECK_FOV ))
|
||||
{
|
||||
// player is in our hiding spot
|
||||
me->PrintIfWatched( "Someone's in my hiding spot - picking another...\n" );
|
||||
|
||||
const int maxRetries = 3;
|
||||
if (m_retry++ >= maxRetries)
|
||||
{
|
||||
me->PrintIfWatched( "Can't find a free hiding spot, giving up.\n" );
|
||||
me->Idle();
|
||||
return;
|
||||
}
|
||||
|
||||
// pick another hiding spot near where we were planning on hiding
|
||||
me->Hide( TheNavMesh->GetNavArea( m_hidingSpot ) );
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Vector toSpot;
|
||||
toSpot.x = m_hidingSpot.x - myOrigin.x;
|
||||
toSpot.y = m_hidingSpot.y - myOrigin.y;
|
||||
toSpot.z = m_hidingSpot.z - me->GetFeetZ(); // use feet location
|
||||
range = toSpot.Length();
|
||||
|
||||
// look outwards as we get close to our hiding spot
|
||||
if (!me->IsEnemyVisible() && !m_isLookingOutward)
|
||||
{
|
||||
const float lookOutwardRange = 200.0f;
|
||||
const float nearSpotRange = 10.0f;
|
||||
if (range < lookOutwardRange && range > nearSpotRange)
|
||||
{
|
||||
m_isLookingOutward = true;
|
||||
|
||||
toSpot.x /= range;
|
||||
toSpot.y /= range;
|
||||
toSpot.z /= range;
|
||||
|
||||
me->SetLookAt( "Face outward", me->EyePosition() - 1000.0f * toSpot, PRIORITY_HIGH, 3.0f );
|
||||
}
|
||||
}
|
||||
|
||||
const float atDist = 20.0f;
|
||||
if (range < atDist)
|
||||
{
|
||||
//-------------------------------------
|
||||
// Just reached our hiding spot
|
||||
//
|
||||
m_isAtSpot = true;
|
||||
m_hideTimer.Start( m_duration );
|
||||
|
||||
// make sure our approach points are valid, since we'll be watching them
|
||||
me->ComputeApproachPoints();
|
||||
me->ClearLookAt();
|
||||
|
||||
// ready our weapon and prepare to attack
|
||||
me->EquipBestWeapon( me->IsUsingGrenade() );
|
||||
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
|
||||
|
||||
// if we are a sniper, update our task
|
||||
if (me->GetTask() == CCSBot::MOVE_TO_SNIPER_SPOT)
|
||||
{
|
||||
me->SetTask( CCSBot::SNIPING );
|
||||
}
|
||||
else if (me->GetTask() == CCSBot::GUARD_INITIAL_ENCOUNTER)
|
||||
{
|
||||
const float campChatterChance = 20.0f;
|
||||
if (RandomFloat( 0, 100 ) < campChatterChance)
|
||||
{
|
||||
me->GetChatter()->Say( "WaitingHere" );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// determine which way to look
|
||||
trace_t result;
|
||||
float outAngle = 0.0f;
|
||||
float outAngleRange = 0.0f;
|
||||
for( float angle = 0.0f; angle < 360.0f; angle += 45.0f )
|
||||
{
|
||||
UTIL_TraceLine( me->EyePosition(), me->EyePosition() + 1000.0f * Vector( BotCOS(angle), BotSIN(angle), 0.0f ), MASK_PLAYERSOLID, me, COLLISION_GROUP_NONE, &result );
|
||||
|
||||
if (result.fraction > outAngleRange)
|
||||
{
|
||||
outAngle = angle;
|
||||
outAngleRange = result.fraction;
|
||||
}
|
||||
}
|
||||
|
||||
me->SetLookAheadAngle( outAngle );
|
||||
|
||||
}
|
||||
|
||||
// move to hiding spot
|
||||
if (me->UpdatePathMovement() != CCSBot::PROGRESSING && !m_isAtSpot)
|
||||
{
|
||||
// we couldn't get to our hiding spot - pick another
|
||||
me->PrintIfWatched( "Can't get to my hiding spot - finding another...\n" );
|
||||
|
||||
// search from hiding spot, since we know it was valid
|
||||
const Vector *pos = FindNearbyHidingSpot( me, m_hidingSpot, m_range, me->IsSniper() );
|
||||
if (pos == NULL)
|
||||
{
|
||||
// no available hiding spots
|
||||
me->PrintIfWatched( "No available hiding spots - hiding where I'm at.\n" );
|
||||
|
||||
// hide where we are
|
||||
m_hidingSpot.x = myOrigin.x;
|
||||
m_hidingSpot.x = myOrigin.y;
|
||||
m_hidingSpot.z = me->GetFeetZ();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_hidingSpot = *pos;
|
||||
}
|
||||
|
||||
// build a path to our new hiding spot
|
||||
if (me->ComputePath( m_hidingSpot, FASTEST_ROUTE ) == false)
|
||||
{
|
||||
me->PrintIfWatched( "Can't pathfind to hiding spot\n" );
|
||||
me->Idle();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
void HideState::OnExit( CCSBot *me )
|
||||
{
|
||||
m_isHoldingPosition = false;
|
||||
|
||||
me->StandUp();
|
||||
me->ResetStuckMonitor();
|
||||
//me->ClearLookAt();
|
||||
me->ClearApproachPoints();
|
||||
|
||||
// if we have a shield, put it away
|
||||
if (me->HasShield() && me->IsProtectedByShield())
|
||||
me->SecondaryAttack();
|
||||
}
|
||||
@@ -0,0 +1,234 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose:
|
||||
//
|
||||
// $NoKeywords: $
|
||||
//=============================================================================//
|
||||
|
||||
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
|
||||
|
||||
#include "cbase.h"
|
||||
#include "cs_simple_hostage.h"
|
||||
#include "cs_bot.h"
|
||||
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Begin the hunt
|
||||
*/
|
||||
void HuntState::OnEnter( CCSBot *me )
|
||||
{
|
||||
// lurking death
|
||||
if (me->IsUsingKnife() && me->IsWellPastSafe() && !me->IsHurrying())
|
||||
me->Walk();
|
||||
else
|
||||
me->Run();
|
||||
|
||||
|
||||
me->StandUp();
|
||||
me->SetDisposition( CCSBot::ENGAGE_AND_INVESTIGATE );
|
||||
me->SetTask( CCSBot::SEEK_AND_DESTROY );
|
||||
|
||||
me->DestroyPath();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Hunt down our enemies
|
||||
*/
|
||||
void HuntState::OnUpdate( CCSBot *me )
|
||||
{
|
||||
// if we've been hunting for a long time, drop into Idle for a moment to
|
||||
// select something else to do
|
||||
const float huntingTooLongTime = 30.0f;
|
||||
if (gpGlobals->curtime - me->GetStateTimestamp() > huntingTooLongTime)
|
||||
{
|
||||
// stop being a rogue and do the scenario, since there must not be many enemies left to hunt
|
||||
me->PrintIfWatched( "Giving up hunting.\n" );
|
||||
me->SetRogue( false );
|
||||
me->Idle();
|
||||
return;
|
||||
}
|
||||
|
||||
// scenario logic
|
||||
if (TheCSBots()->GetScenario() == CCSBotManager::SCENARIO_DEFUSE_BOMB)
|
||||
{
|
||||
if (me->GetTeamNumber() == TEAM_TERRORIST)
|
||||
{
|
||||
// if we have the bomb and it's time to plant, or we happen to be in a bombsite and it seems safe, do it
|
||||
if (me->HasC4())
|
||||
{
|
||||
const float safeTime = 3.0f;
|
||||
|
||||
if (TheCSBots()->IsTimeToPlantBomb() ||
|
||||
(me->IsAtBombsite() && gpGlobals->curtime - me->GetLastSawEnemyTimestamp() > safeTime))
|
||||
{
|
||||
me->Idle();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// if we notice the bomb lying on the ground, go get it
|
||||
if (me->NoticeLooseBomb())
|
||||
{
|
||||
me->FetchBomb();
|
||||
return;
|
||||
}
|
||||
|
||||
// if bomb has been planted, and we hear it, move to a hiding spot near the bomb and watch it
|
||||
const Vector *bombPos = me->GetGameState()->GetBombPosition();
|
||||
if (!me->IsRogue() && me->GetGameState()->IsBombPlanted() && bombPos)
|
||||
{
|
||||
me->SetTask( CCSBot::GUARD_TICKING_BOMB );
|
||||
me->Hide( TheNavMesh->GetNavArea( *bombPos ) );
|
||||
return;
|
||||
}
|
||||
}
|
||||
else // CT
|
||||
{
|
||||
if (!me->IsRogue() && me->CanSeeLooseBomb())
|
||||
{
|
||||
// if we are near the loose bomb and can see it, hide nearby and guard it
|
||||
me->SetTask( CCSBot::GUARD_LOOSE_BOMB );
|
||||
me->Hide( TheCSBots()->GetLooseBombArea() );
|
||||
me->GetChatter()->GuardingLooseBomb( TheCSBots()->GetLooseBomb() );
|
||||
return;
|
||||
}
|
||||
else if (TheCSBots()->IsBombPlanted())
|
||||
{
|
||||
// rogues will defuse a bomb, but not guard the defuser
|
||||
if (!me->IsRogue() || !TheCSBots()->GetBombDefuser())
|
||||
{
|
||||
// search for the planted bomb to defuse
|
||||
me->Idle();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (TheCSBots()->GetScenario() == CCSBotManager::SCENARIO_RESCUE_HOSTAGES)
|
||||
{
|
||||
if (me->GetTeamNumber() == TEAM_TERRORIST)
|
||||
{
|
||||
if (me->GetGameState()->AreAllHostagesBeingRescued())
|
||||
{
|
||||
// all hostages are being rescued, head them off at the escape zones
|
||||
if (me->GuardRandomZone())
|
||||
{
|
||||
me->SetTask( CCSBot::GUARD_HOSTAGE_RESCUE_ZONE );
|
||||
me->PrintIfWatched( "Trying to beat them to an escape zone!\n" );
|
||||
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
|
||||
me->GetChatter()->GuardingHostageEscapeZone( IS_PLAN );
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// if safe time is up, and we stumble across a hostage, guard it
|
||||
if (!me->IsRogue() && !me->IsSafe())
|
||||
{
|
||||
CHostage *hostage = me->GetGameState()->GetNearestVisibleFreeHostage();
|
||||
if (hostage)
|
||||
{
|
||||
CNavArea *area = TheNavMesh->GetNearestNavArea( GetCentroid( hostage ) );
|
||||
if (area)
|
||||
{
|
||||
// we see a free hostage, guard it
|
||||
me->SetTask( CCSBot::GUARD_HOSTAGES );
|
||||
me->Hide( area );
|
||||
me->PrintIfWatched( "I'm guarding hostages\n" );
|
||||
me->GetChatter()->GuardingHostages( area->GetPlace(), IS_PLAN );
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// listen for enemy noises
|
||||
if (me->HeardInterestingNoise())
|
||||
{
|
||||
me->InvestigateNoise();
|
||||
return;
|
||||
}
|
||||
|
||||
// look around
|
||||
me->UpdateLookAround();
|
||||
|
||||
// if we have reached our destination area, pick a new one
|
||||
// if our path fails, pick a new one
|
||||
if (me->GetLastKnownArea() == m_huntArea || me->UpdatePathMovement() != CCSBot::PROGRESSING)
|
||||
{
|
||||
// pick a new hunt area
|
||||
const float earlyGameTime = 45.0f;
|
||||
if (TheCSBots()->GetElapsedRoundTime() < earlyGameTime && !me->HasVisitedEnemySpawn())
|
||||
{
|
||||
// in the early game, rush the enemy spawn
|
||||
CBaseEntity *enemySpawn = TheCSBots()->GetRandomSpawn( OtherTeam( me->GetTeamNumber() ) );
|
||||
|
||||
//ADRIAN: REVISIT
|
||||
if ( enemySpawn )
|
||||
{
|
||||
m_huntArea = TheNavMesh->GetNavArea( enemySpawn->WorldSpaceCenter() );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_huntArea = NULL;
|
||||
float oldest = 0.0f;
|
||||
|
||||
int areaCount = 0;
|
||||
const float minSize = 150.0f;
|
||||
|
||||
FOR_EACH_VEC( TheNavAreas, it )
|
||||
{
|
||||
CNavArea *area = TheNavAreas[ it ];
|
||||
|
||||
++areaCount;
|
||||
|
||||
// skip the small areas
|
||||
Extent extent;
|
||||
area->GetExtent(&extent);
|
||||
if (extent.hi.x - extent.lo.x < minSize || extent.hi.y - extent.lo.y < minSize)
|
||||
continue;
|
||||
|
||||
// keep track of the least recently cleared area
|
||||
float age = gpGlobals->curtime - area->GetClearedTimestamp( me->GetTeamNumber()-1 );
|
||||
if (age > oldest)
|
||||
{
|
||||
oldest = age;
|
||||
m_huntArea = area;
|
||||
}
|
||||
}
|
||||
|
||||
// if all the areas were too small, pick one at random
|
||||
int which = RandomInt( 0, areaCount-1 );
|
||||
|
||||
areaCount = 0;
|
||||
FOR_EACH_VEC( TheNavAreas, hit )
|
||||
{
|
||||
m_huntArea = TheNavAreas[ hit ];
|
||||
|
||||
if (which == areaCount)
|
||||
break;
|
||||
|
||||
--which;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_huntArea)
|
||||
{
|
||||
// create a new path to a far away area of the map
|
||||
me->ComputePath( m_huntArea->GetCenter() );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Done hunting
|
||||
*/
|
||||
void HuntState::OnExit( CCSBot *me )
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,887 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose:
|
||||
//
|
||||
// $NoKeywords: $
|
||||
//=============================================================================//
|
||||
|
||||
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
|
||||
|
||||
#include "cbase.h"
|
||||
#include "cs_simple_hostage.h"
|
||||
#include "cs_bot.h"
|
||||
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
// range for snipers to select a hiding spot
|
||||
const float sniperHideRange = 2000.0f;
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* The Idle state.
|
||||
* We never stay in the Idle state - it is a "home base" for the state machine that
|
||||
* does various checks to determine what we should do next.
|
||||
*/
|
||||
void IdleState::OnEnter( CCSBot *me )
|
||||
{
|
||||
me->DestroyPath();
|
||||
me->SetBotEnemy( NULL );
|
||||
|
||||
// lurking death
|
||||
if (me->IsUsingKnife() && me->IsWellPastSafe() && !me->IsHurrying())
|
||||
me->Walk();
|
||||
|
||||
//
|
||||
// Since Idle assigns tasks, we assume that coming back to Idle means our task is complete
|
||||
//
|
||||
me->SetTask( CCSBot::SEEK_AND_DESTROY );
|
||||
me->SetDisposition( CCSBot::ENGAGE_AND_INVESTIGATE );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Determine what we should do next
|
||||
*/
|
||||
void IdleState::OnUpdate( CCSBot *me )
|
||||
{
|
||||
// all other states assume GetLastKnownArea() is valid, ensure that it is
|
||||
if (me->GetLastKnownArea() == NULL && me->StayOnNavMesh() == false)
|
||||
return;
|
||||
|
||||
// zombies never leave the Idle state
|
||||
if (cv_bot_zombie.GetBool())
|
||||
{
|
||||
me->ResetStuckMonitor();
|
||||
return;
|
||||
}
|
||||
|
||||
// if we are in the early "safe" time, grab a knife or grenade
|
||||
if (me->IsSafe())
|
||||
{
|
||||
// if we have a grenade, use it
|
||||
if (!me->EquipGrenade())
|
||||
{
|
||||
// high-skill bots run with the knife, unless using the Scout (which moves faster)
|
||||
if (me->GetProfile()->GetSkill() > 0.33f && !me->IsUsing( WEAPON_SCOUT ))
|
||||
{
|
||||
me->EquipKnife();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if round is over, hunt
|
||||
if (me->GetGameState()->IsRoundOver())
|
||||
{
|
||||
// if we are escorting hostages, try to get to the rescue zone
|
||||
if (me->GetHostageEscortCount())
|
||||
{
|
||||
const CCSBotManager::Zone *zone = TheCSBots()->GetClosestZone( me->GetLastKnownArea(), PathCost( me, FASTEST_ROUTE ) );
|
||||
const Vector *zonePos = TheCSBots()->GetRandomPositionInZone( zone );
|
||||
|
||||
if (zonePos)
|
||||
{
|
||||
me->SetTask( CCSBot::RESCUE_HOSTAGES );
|
||||
me->Run();
|
||||
me->SetDisposition( CCSBot::SELF_DEFENSE );
|
||||
me->MoveTo( *zonePos, FASTEST_ROUTE );
|
||||
me->PrintIfWatched( "Trying to rescue hostages at the end of the round\n" );
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
me->Hunt();
|
||||
return;
|
||||
}
|
||||
|
||||
const float defenseSniperCampChance = 75.0f;
|
||||
const float offenseSniperCampChance = 10.0f;
|
||||
|
||||
// if we were following someone, continue following them
|
||||
if (me->IsFollowing())
|
||||
{
|
||||
me->ContinueFollowing();
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
// Scenario logic
|
||||
//
|
||||
switch (TheCSBots()->GetScenario())
|
||||
{
|
||||
//======================================================================================================
|
||||
case CCSBotManager::SCENARIO_DEFUSE_BOMB:
|
||||
{
|
||||
// if this is a bomb game and we have the bomb, go plant it
|
||||
if (me->GetTeamNumber() == TEAM_TERRORIST)
|
||||
{
|
||||
if (me->GetGameState()->IsBombPlanted())
|
||||
{
|
||||
if (me->GetGameState()->GetPlantedBombsite() != CSGameState::UNKNOWN)
|
||||
{
|
||||
// T's always know where the bomb is - go defend it
|
||||
const CCSBotManager::Zone *zone = TheCSBots()->GetZone( me->GetGameState()->GetPlantedBombsite() );
|
||||
if (zone)
|
||||
{
|
||||
me->SetTask( CCSBot::GUARD_TICKING_BOMB );
|
||||
|
||||
Place place = TheNavMesh->GetPlace( zone->m_center );
|
||||
if (place != UNDEFINED_PLACE)
|
||||
{
|
||||
// pick a random hiding spot in this place
|
||||
const Vector *spot = FindRandomHidingSpot( me, place, me->IsSniper() );
|
||||
if (spot)
|
||||
{
|
||||
me->Hide( *spot );
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// hide nearby
|
||||
me->Hide( TheNavMesh->GetNearestNavArea( zone->m_center ) );
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// ask our teammates where the bomb is
|
||||
me->GetChatter()->RequestBombLocation();
|
||||
|
||||
// we dont know where the bomb is - we must search the bombsites
|
||||
int zoneIndex = me->GetGameState()->GetNextBombsiteToSearch();
|
||||
|
||||
// move to bombsite - if we reach it, we'll update its cleared status, causing us to select another
|
||||
const Vector *pos = TheCSBots()->GetRandomPositionInZone( TheCSBots()->GetZone( zoneIndex ) );
|
||||
if (pos)
|
||||
{
|
||||
me->SetTask( CCSBot::FIND_TICKING_BOMB );
|
||||
me->MoveTo( *pos );
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (me->HasC4())
|
||||
{
|
||||
// if we're at a bomb site, plant the bomb
|
||||
if (me->IsAtBombsite())
|
||||
{
|
||||
// plant it
|
||||
me->SetTask( CCSBot::PLANT_BOMB );
|
||||
me->PlantBomb();
|
||||
|
||||
// radio to the team
|
||||
me->GetChatter()->PlantingTheBomb( me->GetPlace() );
|
||||
|
||||
return;
|
||||
}
|
||||
else if (TheCSBots()->IsTimeToPlantBomb())
|
||||
{
|
||||
// move to the closest bomb site
|
||||
const CCSBotManager::Zone *zone = TheCSBots()->GetClosestZone( me->GetLastKnownArea(), PathCost( me ) );
|
||||
if (zone)
|
||||
{
|
||||
// pick a random spot within the bomb zone
|
||||
const Vector *pos = TheCSBots()->GetRandomPositionInZone( zone );
|
||||
if (pos)
|
||||
{
|
||||
// move to bombsite
|
||||
me->SetTask( CCSBot::PLANT_BOMB );
|
||||
me->Run();
|
||||
me->MoveTo( *pos );
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// at the start of the round, we may decide to defend "initial encounter" areas
|
||||
// where we will first meet the enemy rush
|
||||
if (me->IsSafe())
|
||||
{
|
||||
float defendRushChance = -17.0f * (me->GetMorale() - 2);
|
||||
|
||||
if (me->IsSniper() || RandomFloat( 0.0f, 100.0f ) < defendRushChance)
|
||||
{
|
||||
if (me->MoveToInitialEncounter())
|
||||
{
|
||||
me->PrintIfWatched( "I'm guarding an initial encounter area\n" );
|
||||
me->SetTask( CCSBot::GUARD_INITIAL_ENCOUNTER );
|
||||
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// small chance of sniper camping on offense, if we aren't carrying the bomb
|
||||
if (me->GetFriendsRemaining() && me->IsSniper() && RandomFloat( 0, 100.0f ) < offenseSniperCampChance)
|
||||
{
|
||||
me->SetTask( CCSBot::MOVE_TO_SNIPER_SPOT );
|
||||
me->Hide( me->GetLastKnownArea(), RandomFloat( 10.0f, 30.0f ), sniperHideRange );
|
||||
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
|
||||
me->PrintIfWatched( "Sniping!\n" );
|
||||
return;
|
||||
}
|
||||
|
||||
// if the bomb is loose (on the ground), go get it
|
||||
if (me->NoticeLooseBomb())
|
||||
{
|
||||
me->FetchBomb();
|
||||
return;
|
||||
}
|
||||
|
||||
// if bomb has been planted, and we hear it, move to a hiding spot near the bomb and guard it
|
||||
if (!me->IsRogue() && me->GetGameState()->IsBombPlanted() && me->GetGameState()->GetBombPosition())
|
||||
{
|
||||
const Vector *bombPos = me->GetGameState()->GetBombPosition();
|
||||
|
||||
if (bombPos)
|
||||
{
|
||||
me->SetTask( CCSBot::GUARD_TICKING_BOMB );
|
||||
me->Hide( TheNavMesh->GetNavArea( *bombPos ) );
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else // CT ------------------------------------------------------------------------------------------
|
||||
{
|
||||
if (me->GetGameState()->IsBombPlanted())
|
||||
{
|
||||
// if the bomb has been planted, attempt to defuse it
|
||||
const Vector *bombPos = me->GetGameState()->GetBombPosition();
|
||||
if (bombPos)
|
||||
{
|
||||
// if someone is defusing the bomb, guard them
|
||||
if (TheCSBots()->GetBombDefuser())
|
||||
{
|
||||
if (!me->IsRogue())
|
||||
{
|
||||
me->SetTask( CCSBot::GUARD_BOMB_DEFUSER );
|
||||
me->Hide( TheNavMesh->GetNavArea( *bombPos ) );
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (me->IsDoingScenario())
|
||||
{
|
||||
// move to the bomb and defuse it
|
||||
me->SetTask( CCSBot::DEFUSE_BOMB );
|
||||
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
|
||||
me->MoveTo( *bombPos );
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
// we're not allowed to defuse, guard the bomb zone
|
||||
me->SetTask( CCSBot::GUARD_BOMB_ZONE );
|
||||
me->Hide( TheNavMesh->GetNavArea( *bombPos ) );
|
||||
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (me->GetGameState()->GetPlantedBombsite() != CSGameState::UNKNOWN)
|
||||
{
|
||||
// we know which bombsite, but not exactly where the bomb is, go there
|
||||
const CCSBotManager::Zone *zone = TheCSBots()->GetZone( me->GetGameState()->GetPlantedBombsite() );
|
||||
if (zone)
|
||||
{
|
||||
if (me->IsDoingScenario())
|
||||
{
|
||||
me->SetTask( CCSBot::DEFUSE_BOMB );
|
||||
me->MoveTo( zone->m_center );
|
||||
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
// we're not allowed to defuse, guard the bomb zone
|
||||
me->SetTask( CCSBot::GUARD_BOMB_ZONE );
|
||||
me->Hide( TheNavMesh->GetNavArea( zone->m_center ) );
|
||||
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// we dont know where the bomb is - we must search the bombsites
|
||||
|
||||
// find closest un-cleared bombsite
|
||||
const CCSBotManager::Zone *zone = NULL;
|
||||
float travelDistance = 9999999.9f;
|
||||
|
||||
for( int z=0; z<TheCSBots()->GetZoneCount(); ++z )
|
||||
{
|
||||
if (TheCSBots()->GetZone(z)->m_areaCount == 0)
|
||||
continue;
|
||||
|
||||
// don't check bombsites that have been cleared
|
||||
if (me->GetGameState()->IsBombsiteClear( z ))
|
||||
continue;
|
||||
|
||||
// just use the first overlapping nav area as a reasonable approximation
|
||||
ShortestPathCost cost = ShortestPathCost();
|
||||
float dist = NavAreaTravelDistance( me->GetLastKnownArea(),
|
||||
TheNavMesh->GetNearestNavArea( TheCSBots()->GetZone(z)->m_center ),
|
||||
cost );
|
||||
|
||||
if (dist >= 0.0f && dist < travelDistance)
|
||||
{
|
||||
zone = TheCSBots()->GetZone(z);
|
||||
travelDistance = dist;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (zone)
|
||||
{
|
||||
const float farAwayRange = 2000.0f;
|
||||
if (travelDistance > farAwayRange)
|
||||
{
|
||||
zone = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
// if closest bombsite is "far away", pick one at random
|
||||
if (zone == NULL)
|
||||
{
|
||||
int zoneIndex = me->GetGameState()->GetNextBombsiteToSearch();
|
||||
zone = TheCSBots()->GetZone( zoneIndex );
|
||||
}
|
||||
|
||||
// move to bombsite - if we reach it, we'll update its cleared status, causing us to select another
|
||||
if (zone)
|
||||
{
|
||||
const Vector *pos = TheCSBots()->GetRandomPositionInZone( zone );
|
||||
if (pos)
|
||||
{
|
||||
me->SetTask( CCSBot::FIND_TICKING_BOMB );
|
||||
me->MoveTo( *pos );
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
AssertMsg( 0, "A CT bot doesn't know what to do while the bomb is planted!\n" );
|
||||
}
|
||||
|
||||
|
||||
// if we have a sniper rifle, we like to camp, whether rogue or not
|
||||
if (me->IsSniper() && !me->IsSafe())
|
||||
{
|
||||
if (RandomFloat( 0, 100 ) <= defenseSniperCampChance)
|
||||
{
|
||||
CNavArea *snipingArea = NULL;
|
||||
|
||||
// if the bomb is loose, snipe near it
|
||||
const Vector *bombPos = me->GetGameState()->GetBombPosition();
|
||||
if (me->GetGameState()->IsLooseBombLocationKnown() && bombPos)
|
||||
{
|
||||
snipingArea = TheNavMesh->GetNearestNavArea( *bombPos );
|
||||
me->PrintIfWatched( "Sniping near loose bomb\n" );
|
||||
}
|
||||
else
|
||||
{
|
||||
// snipe bomb zone(s)
|
||||
const CCSBotManager::Zone *zone = TheCSBots()->GetRandomZone();
|
||||
if (zone)
|
||||
{
|
||||
snipingArea = TheCSBots()->GetRandomAreaInZone( zone );
|
||||
me->PrintIfWatched( "Sniping near bombsite\n" );
|
||||
}
|
||||
}
|
||||
|
||||
if (snipingArea)
|
||||
{
|
||||
me->SetTask( CCSBot::MOVE_TO_SNIPER_SPOT );
|
||||
me->Hide( snipingArea, -1.0, sniperHideRange );
|
||||
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// rogues just hunt, unless they want to snipe
|
||||
// if the whole team has decided to rush, hunt
|
||||
// if we know the bomb is dropped, hunt for enemies and the loose bomb
|
||||
if (me->IsRogue() || TheCSBots()->IsDefenseRushing() || me->GetGameState()->IsLooseBombLocationKnown())
|
||||
{
|
||||
me->Hunt();
|
||||
return;
|
||||
}
|
||||
|
||||
// the lower our morale gets, the more we want to camp the bomb zone(s)
|
||||
// only decide to camp at the start of the round, or if we haven't seen anything for a long time
|
||||
if (me->IsSafe() || me->HasNotSeenEnemyForLongTime())
|
||||
{
|
||||
float guardBombsiteChance = -34.0f * me->GetMorale();
|
||||
|
||||
if (RandomFloat( 0.0f, 100.0f ) < guardBombsiteChance)
|
||||
{
|
||||
float guardRange = 500.0f + 100.0f * (me->GetMorale() + 3);
|
||||
|
||||
// guard bomb zone(s)
|
||||
const CCSBotManager::Zone *zone = TheCSBots()->GetRandomZone();
|
||||
if (zone)
|
||||
{
|
||||
CNavArea *area = TheCSBots()->GetRandomAreaInZone( zone );
|
||||
if (area)
|
||||
{
|
||||
me->PrintIfWatched( "I'm guarding a bombsite\n" );
|
||||
me->GetChatter()->GuardingBombsite( area->GetPlace() );
|
||||
me->SetTask( CCSBot::GUARD_BOMB_ZONE );
|
||||
me->Hide( area, -1.0, guardRange );
|
||||
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// at the start of the round, we may decide to defend "initial encounter" areas
|
||||
// where we will first meet the enemy rush
|
||||
if (me->IsSafe())
|
||||
{
|
||||
float defendRushChance = -17.0f * (me->GetMorale() - 2);
|
||||
|
||||
if (me->IsSniper() || RandomFloat( 0.0f, 100.0f ) < defendRushChance)
|
||||
{
|
||||
if (me->MoveToInitialEncounter())
|
||||
{
|
||||
me->PrintIfWatched( "I'm guarding an initial encounter area\n" );
|
||||
me->SetTask( CCSBot::GUARD_INITIAL_ENCOUNTER );
|
||||
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
//======================================================================================================
|
||||
case CCSBotManager::SCENARIO_ESCORT_VIP:
|
||||
{
|
||||
if (me->GetTeamNumber() == TEAM_TERRORIST)
|
||||
{
|
||||
// if we have a sniper rifle, we like to camp, whether rogue or not
|
||||
if (me->IsSniper())
|
||||
{
|
||||
if (RandomFloat( 0, 100 ) <= defenseSniperCampChance)
|
||||
{
|
||||
// snipe escape zone(s)
|
||||
const CCSBotManager::Zone *zone = TheCSBots()->GetRandomZone();
|
||||
if (zone)
|
||||
{
|
||||
CNavArea *area = TheCSBots()->GetRandomAreaInZone( zone );
|
||||
if (area)
|
||||
{
|
||||
me->SetTask( CCSBot::MOVE_TO_SNIPER_SPOT );
|
||||
me->Hide( area, -1.0, sniperHideRange );
|
||||
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
|
||||
me->PrintIfWatched( "Sniping near escape zone\n" );
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// rogues just hunt, unless they want to snipe
|
||||
// if the whole team has decided to rush, hunt
|
||||
if (me->IsRogue() || TheCSBots()->IsDefenseRushing())
|
||||
break;
|
||||
|
||||
// the lower our morale gets, the more we want to camp the escape zone(s)
|
||||
float guardEscapeZoneChance = -34.0f * me->GetMorale();
|
||||
|
||||
if (RandomFloat( 0.0f, 100.0f ) < guardEscapeZoneChance)
|
||||
{
|
||||
// guard escape zone(s)
|
||||
const CCSBotManager::Zone *zone = TheCSBots()->GetRandomZone();
|
||||
if (zone)
|
||||
{
|
||||
CNavArea *area = TheCSBots()->GetRandomAreaInZone( zone );
|
||||
if (area)
|
||||
{
|
||||
// guard the escape zone - stay closer if our morale is low
|
||||
me->SetTask( CCSBot::GUARD_VIP_ESCAPE_ZONE );
|
||||
me->PrintIfWatched( "I'm guarding an escape zone\n" );
|
||||
|
||||
float escapeGuardRange = 750.0f + 250.0f * (me->GetMorale() + 3);
|
||||
me->Hide( area, -1.0, escapeGuardRange );
|
||||
|
||||
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else // CT
|
||||
{
|
||||
if (me->m_bIsVIP)
|
||||
{
|
||||
// if early in round, pick a random zone, otherwise pick closest zone
|
||||
const float earlyTime = 20.0f;
|
||||
const CCSBotManager::Zone *zone = NULL;
|
||||
|
||||
if (TheCSBots()->GetElapsedRoundTime() < earlyTime)
|
||||
{
|
||||
// pick random zone
|
||||
zone = TheCSBots()->GetRandomZone();
|
||||
}
|
||||
else
|
||||
{
|
||||
// pick closest zone
|
||||
zone = TheCSBots()->GetClosestZone( me->GetLastKnownArea(), PathCost( me ) );
|
||||
}
|
||||
|
||||
if (zone)
|
||||
{
|
||||
// pick a random spot within the escape zone
|
||||
const Vector *pos = TheCSBots()->GetRandomPositionInZone( zone );
|
||||
if (pos)
|
||||
{
|
||||
// move to escape zone
|
||||
me->SetTask( CCSBot::VIP_ESCAPE );
|
||||
me->Run();
|
||||
me->MoveTo( *pos );
|
||||
|
||||
// tell team to follow
|
||||
const float repeatTime = 30.0f;
|
||||
if (me->GetFriendsRemaining() &&
|
||||
TheCSBots()->GetRadioMessageInterval( RADIO_FOLLOW_ME, me->GetTeamNumber() ) > repeatTime)
|
||||
me->SendRadioMessage( RADIO_FOLLOW_ME );
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// small chance of sniper camping on offense, if we aren't VIP
|
||||
if (me->GetFriendsRemaining() && me->IsSniper() && RandomFloat( 0, 100.0f ) < offenseSniperCampChance)
|
||||
{
|
||||
me->SetTask( CCSBot::MOVE_TO_SNIPER_SPOT );
|
||||
me->Hide( me->GetLastKnownArea(), RandomFloat( 10.0f, 30.0f ), sniperHideRange );
|
||||
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
|
||||
me->PrintIfWatched( "Sniping!\n" );
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
//======================================================================================================
|
||||
case CCSBotManager::SCENARIO_RESCUE_HOSTAGES:
|
||||
{
|
||||
if (me->GetTeamNumber() == TEAM_TERRORIST)
|
||||
{
|
||||
bool campHostages;
|
||||
|
||||
// if we are in early game, camp the hostages
|
||||
if (me->IsSafe())
|
||||
{
|
||||
campHostages = true;
|
||||
}
|
||||
else if (me->GetGameState()->HaveSomeHostagesBeenTaken() || me->GetGameState()->AreAllHostagesBeingRescued())
|
||||
{
|
||||
campHostages = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// later in the game, camp either hostages or escape zone
|
||||
const float campZoneChance = 100.0f * (TheCSBots()->GetElapsedRoundTime() - me->GetSafeTime())/120.0f;
|
||||
|
||||
campHostages = (RandomFloat( 0, 100 ) > campZoneChance) ? true : false;
|
||||
}
|
||||
|
||||
|
||||
// if we have a sniper rifle, we like to camp, whether rogue or not
|
||||
if (me->IsSniper())
|
||||
{
|
||||
// the at start of the round, snipe the initial rush
|
||||
if (me->IsSafe())
|
||||
{
|
||||
if (me->MoveToInitialEncounter())
|
||||
{
|
||||
me->PrintIfWatched( "I'm sniping an initial encounter area\n" );
|
||||
me->SetTask( CCSBot::GUARD_INITIAL_ENCOUNTER );
|
||||
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (RandomFloat( 0, 100 ) <= defenseSniperCampChance)
|
||||
{
|
||||
const Vector *hostagePos = me->GetGameState()->GetRandomFreeHostagePosition();
|
||||
if (hostagePos && campHostages)
|
||||
{
|
||||
me->SetTask( CCSBot::MOVE_TO_SNIPER_SPOT );
|
||||
me->PrintIfWatched( "Sniping near hostages\n" );
|
||||
me->Hide( TheNavMesh->GetNearestNavArea( *hostagePos ), -1.0, sniperHideRange );
|
||||
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
// camp the escape zone(s)
|
||||
if (me->GuardRandomZone( sniperHideRange ))
|
||||
{
|
||||
me->SetTask( CCSBot::MOVE_TO_SNIPER_SPOT );
|
||||
me->PrintIfWatched( "Sniping near a rescue zone\n" );
|
||||
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if safe time is up, and we stumble across a hostage, guard it
|
||||
if (!me->IsSafe() && !me->IsRogue())
|
||||
{
|
||||
CBaseEntity *hostage = me->GetGameState()->GetNearestVisibleFreeHostage();
|
||||
if (hostage)
|
||||
{
|
||||
// we see a free hostage, guard it
|
||||
CNavArea *area = TheNavMesh->GetNearestNavArea( GetCentroid( hostage ) );
|
||||
if (area)
|
||||
{
|
||||
me->SetTask( CCSBot::GUARD_HOSTAGES );
|
||||
me->Hide( area );
|
||||
me->PrintIfWatched( "I'm guarding hostages I found\n" );
|
||||
// don't chatter here - he'll tell us when he's in his hiding spot
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// decide if we want to hunt, or guard
|
||||
const float huntChance = 70.0f + 25.0f * me->GetMorale();
|
||||
|
||||
// rogues just hunt, unless they want to snipe
|
||||
// if the whole team has decided to rush, hunt
|
||||
if (me->GetFriendsRemaining())
|
||||
{
|
||||
if (me->IsRogue() || TheCSBots()->IsDefenseRushing() || RandomFloat( 0, 100 ) < huntChance)
|
||||
{
|
||||
me->Hunt();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// at the start of the round, we may decide to defend "initial encounter" areas
|
||||
// where we will first meet the enemy rush
|
||||
if (me->IsSafe())
|
||||
{
|
||||
float defendRushChance = -17.0f * (me->GetMorale() - 2);
|
||||
|
||||
if (me->IsSniper() || RandomFloat( 0.0f, 100.0f ) < defendRushChance)
|
||||
{
|
||||
if (me->MoveToInitialEncounter())
|
||||
{
|
||||
me->PrintIfWatched( "I'm guarding an initial encounter area\n" );
|
||||
me->SetTask( CCSBot::GUARD_INITIAL_ENCOUNTER );
|
||||
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// decide whether to camp the hostages or the escape zones
|
||||
const Vector *hostagePos = me->GetGameState()->GetRandomFreeHostagePosition();
|
||||
if (hostagePos && campHostages)
|
||||
{
|
||||
CNavArea *area = TheNavMesh->GetNearestNavArea( *hostagePos );
|
||||
if (area)
|
||||
{
|
||||
// guard the hostages - stay closer to hostages if our morale is low
|
||||
me->SetTask( CCSBot::GUARD_HOSTAGES );
|
||||
me->PrintIfWatched( "I'm guarding hostages\n" );
|
||||
|
||||
float hostageGuardRange = 750.0f + 250.0f * (me->GetMorale() + 3); // 2000
|
||||
me->Hide( area, -1.0, hostageGuardRange );
|
||||
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
|
||||
|
||||
if (RandomFloat( 0, 100 ) < 50)
|
||||
me->GetChatter()->GuardingHostages( area->GetPlace(), IS_PLAN );
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// guard rescue zone(s)
|
||||
if (me->GuardRandomZone())
|
||||
{
|
||||
me->SetTask( CCSBot::GUARD_HOSTAGE_RESCUE_ZONE );
|
||||
me->PrintIfWatched( "I'm guarding a rescue zone\n" );
|
||||
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
|
||||
me->GetChatter()->GuardingHostageEscapeZone( IS_PLAN );
|
||||
return;
|
||||
}
|
||||
}
|
||||
else // CT ---------------------------------------------------------------------------------
|
||||
{
|
||||
// only decide to do something else if we aren't already rescuing hostages
|
||||
if (!me->GetHostageEscortCount())
|
||||
{
|
||||
// small chance of sniper camping on offense
|
||||
if (me->GetFriendsRemaining() && me->IsSniper() && RandomFloat( 0, 100.0f ) < offenseSniperCampChance)
|
||||
{
|
||||
me->SetTask( CCSBot::MOVE_TO_SNIPER_SPOT );
|
||||
me->Hide( me->GetLastKnownArea(), RandomFloat( 10.0f, 30.0f ), sniperHideRange );
|
||||
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
|
||||
me->PrintIfWatched( "Sniping!\n" );
|
||||
return;
|
||||
}
|
||||
|
||||
if (me->GetFriendsRemaining() && !me->GetHostageEscortCount())
|
||||
{
|
||||
// rogues just hunt, unless all friends are dead
|
||||
// if we have friends left, we might go hunting instead of hostage rescuing
|
||||
const float huntChance = 33.3f;
|
||||
if (me->IsRogue() || RandomFloat( 0.0f, 100.0f ) < huntChance)
|
||||
{
|
||||
me->Hunt();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// at the start of the round, we may decide to defend "initial encounter" areas
|
||||
// where we will first meet the enemy rush
|
||||
if (me->IsSafe())
|
||||
{
|
||||
float defendRushChance = -17.0f * (me->GetMorale() - 2);
|
||||
|
||||
if (me->IsSniper() || RandomFloat( 0.0f, 100.0f ) < defendRushChance)
|
||||
{
|
||||
if (me->MoveToInitialEncounter())
|
||||
{
|
||||
me->PrintIfWatched( "I'm guarding an initial encounter area\n" );
|
||||
me->SetTask( CCSBot::GUARD_INITIAL_ENCOUNTER );
|
||||
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// look for free hostages - CT's have radar so they know where hostages are at all times
|
||||
CHostage *hostage = me->GetGameState()->GetNearestFreeHostage();
|
||||
|
||||
// if we are not allowed to do the scenario, guard the hostages to clear the area for the human(s)
|
||||
if (!me->IsDoingScenario())
|
||||
{
|
||||
if (hostage)
|
||||
{
|
||||
CNavArea *area = TheNavMesh->GetNearestNavArea( GetCentroid( hostage ) );
|
||||
if (area)
|
||||
{
|
||||
me->SetTask( CCSBot::GUARD_HOSTAGES );
|
||||
me->Hide( area );
|
||||
me->PrintIfWatched( "I'm securing the hostages for a human to rescue\n" );
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
me->Hunt();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
bool fetchHostages = false;
|
||||
bool rescueHostages = false;
|
||||
const CCSBotManager::Zone *zone = NULL;
|
||||
me->SetGoalEntity( NULL );
|
||||
|
||||
// if we are escorting hostages, determine where to take them
|
||||
if (me->GetHostageEscortCount())
|
||||
zone = TheCSBots()->GetClosestZone( me->GetLastKnownArea(), PathCost( me, FASTEST_ROUTE ) );
|
||||
|
||||
// if we are escorting hostages and there are more hostages to rescue,
|
||||
// determine whether it's faster to rescue the ones we have, or go get the remaining ones
|
||||
if (hostage)
|
||||
{
|
||||
Vector hostageOrigin = GetCentroid( hostage );
|
||||
|
||||
if (zone)
|
||||
{
|
||||
PathCost cost( me, FASTEST_ROUTE );
|
||||
float toZone = NavAreaTravelDistance( me->GetLastKnownArea(), zone->m_area[0], cost );
|
||||
float toHostage = NavAreaTravelDistance( me->GetLastKnownArea(), TheNavMesh->GetNearestNavArea( GetCentroid( hostage ) ), cost );
|
||||
|
||||
if (toHostage < 0.0f)
|
||||
{
|
||||
rescueHostages = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (toZone < toHostage)
|
||||
rescueHostages = true;
|
||||
else
|
||||
fetchHostages = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
fetchHostages = true;
|
||||
}
|
||||
}
|
||||
else if (zone)
|
||||
{
|
||||
rescueHostages = true;
|
||||
}
|
||||
|
||||
|
||||
if (fetchHostages)
|
||||
{
|
||||
// go get hostages
|
||||
me->SetTask( CCSBot::COLLECT_HOSTAGES );
|
||||
me->Run();
|
||||
me->SetGoalEntity( hostage );
|
||||
me->ResetWaitForHostagePatience();
|
||||
|
||||
// if we already have some hostages, move to the others by the quickest route
|
||||
RouteType route = (me->GetHostageEscortCount()) ? FASTEST_ROUTE : SAFEST_ROUTE;
|
||||
me->MoveTo( GetCentroid( hostage ), route );
|
||||
|
||||
me->PrintIfWatched( "I'm collecting hostages\n" );
|
||||
return;
|
||||
}
|
||||
|
||||
const Vector *zonePos = TheCSBots()->GetRandomPositionInZone( zone );
|
||||
if (rescueHostages && zonePos)
|
||||
{
|
||||
me->SetTask( CCSBot::RESCUE_HOSTAGES );
|
||||
me->Run();
|
||||
me->SetDisposition( CCSBot::SELF_DEFENSE );
|
||||
me->MoveTo( *zonePos, FASTEST_ROUTE );
|
||||
me->PrintIfWatched( "I'm rescuing hostages\n" );
|
||||
me->GetChatter()->EscortingHostages();
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default: // deathmatch
|
||||
{
|
||||
// sniping check
|
||||
if (me->GetFriendsRemaining() && me->IsSniper() && RandomFloat( 0, 100.0f ) < offenseSniperCampChance)
|
||||
{
|
||||
me->SetTask( CCSBot::MOVE_TO_SNIPER_SPOT );
|
||||
me->Hide( me->GetLastKnownArea(), RandomFloat( 10.0f, 30.0f ), sniperHideRange );
|
||||
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
|
||||
me->PrintIfWatched( "Sniping!\n" );
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// if we have nothing special to do, go hunting for enemies
|
||||
me->Hunt();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose:
|
||||
//
|
||||
// $NoKeywords: $
|
||||
//=============================================================================//
|
||||
|
||||
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
|
||||
|
||||
#include "cbase.h"
|
||||
#include "cs_bot.h"
|
||||
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Move towards currently heard noise
|
||||
*/
|
||||
void InvestigateNoiseState::AttendCurrentNoise( CCSBot *me )
|
||||
{
|
||||
if (!me->IsNoiseHeard() && me->GetNoisePosition())
|
||||
return;
|
||||
|
||||
// remember where the noise we heard was
|
||||
m_checkNoisePosition = *me->GetNoisePosition();
|
||||
|
||||
// tell our teammates (unless the noise is obvious, like gunfire)
|
||||
if (me->IsWellPastSafe() && me->HasNotSeenEnemyForLongTime() && me->GetNoisePriority() != PRIORITY_HIGH)
|
||||
me->GetChatter()->HeardNoise( *me->GetNoisePosition() );
|
||||
|
||||
// figure out how to get to the noise
|
||||
me->PrintIfWatched( "Attending to noise...\n" );
|
||||
me->ComputePath( m_checkNoisePosition, FASTEST_ROUTE );
|
||||
|
||||
const float minAttendTime = 3.0f;
|
||||
const float maxAttendTime = 10.0f;
|
||||
m_minTimer.Start( RandomFloat( minAttendTime, maxAttendTime ) );
|
||||
|
||||
// consume the noise
|
||||
me->ForgetNoise();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
void InvestigateNoiseState::OnEnter( CCSBot *me )
|
||||
{
|
||||
AttendCurrentNoise( me );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* @todo Use TravelDistance instead of distance...
|
||||
*/
|
||||
void InvestigateNoiseState::OnUpdate( CCSBot *me )
|
||||
{
|
||||
Vector myOrigin = GetCentroid( me );
|
||||
|
||||
// keep an ear out for closer noises...
|
||||
if (m_minTimer.IsElapsed())
|
||||
{
|
||||
const float nearbyRange = 500.0f;
|
||||
if (me->HeardInterestingNoise() && me->GetNoiseRange() < nearbyRange)
|
||||
{
|
||||
// new sound is closer
|
||||
AttendCurrentNoise( me );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// if the pathfind fails, give up
|
||||
if (!me->HasPath())
|
||||
{
|
||||
me->Idle();
|
||||
return;
|
||||
}
|
||||
|
||||
// look around
|
||||
me->UpdateLookAround();
|
||||
|
||||
// get distance remaining on our path until we reach the source of the noise
|
||||
float range = me->GetPathDistanceRemaining();
|
||||
|
||||
if (me->IsUsingKnife())
|
||||
{
|
||||
if (me->IsHurrying())
|
||||
me->Run();
|
||||
else
|
||||
me->Walk();
|
||||
}
|
||||
else
|
||||
{
|
||||
const float closeToNoiseRange = 1500.0f;
|
||||
if (range < closeToNoiseRange)
|
||||
{
|
||||
// if we dont have many friends left, or we are alone, and we are near noise source, sneak quietly
|
||||
if ((me->GetNearbyFriendCount() == 0 || me->GetFriendsRemaining() <= 2) && !me->IsHurrying())
|
||||
{
|
||||
me->Walk();
|
||||
}
|
||||
else
|
||||
{
|
||||
me->Run();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
me->Run();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// if we can see the noise position and we're close enough to it and looking at it,
|
||||
// we don't need to actually move there (it's checked enough)
|
||||
const float closeRange = 500.0f;
|
||||
if (range < closeRange)
|
||||
{
|
||||
if (me->IsVisible( m_checkNoisePosition, CHECK_FOV ))
|
||||
{
|
||||
// can see noise position
|
||||
me->PrintIfWatched( "Noise location is clear.\n" );
|
||||
me->ForgetNoise();
|
||||
me->Idle();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// move towards noise
|
||||
if (me->UpdatePathMovement() != CCSBot::PROGRESSING)
|
||||
{
|
||||
me->Idle();
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
void InvestigateNoiseState::OnExit( CCSBot *me )
|
||||
{
|
||||
// reset to run mode in case we were sneaking about
|
||||
me->Run();
|
||||
}
|
||||
@@ -0,0 +1,366 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose:
|
||||
//
|
||||
// $NoKeywords: $
|
||||
//=============================================================================//
|
||||
|
||||
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
|
||||
|
||||
#include "cbase.h"
|
||||
#include "cs_simple_hostage.h"
|
||||
#include "cs_bot.h"
|
||||
#include "cs_gamerules.h"
|
||||
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Move to a potentially far away position.
|
||||
*/
|
||||
void MoveToState::OnEnter( CCSBot *me )
|
||||
{
|
||||
if (me->IsUsingKnife() && me->IsWellPastSafe() && !me->IsHurrying())
|
||||
{
|
||||
me->Walk();
|
||||
}
|
||||
else
|
||||
{
|
||||
me->Run();
|
||||
}
|
||||
|
||||
|
||||
// if we need to find the bomb, get there as quick as we can
|
||||
RouteType route;
|
||||
switch (me->GetTask())
|
||||
{
|
||||
case CCSBot::FIND_TICKING_BOMB:
|
||||
case CCSBot::DEFUSE_BOMB:
|
||||
case CCSBot::MOVE_TO_LAST_KNOWN_ENEMY_POSITION:
|
||||
route = FASTEST_ROUTE;
|
||||
break;
|
||||
|
||||
default:
|
||||
route = SAFEST_ROUTE;
|
||||
break;
|
||||
}
|
||||
|
||||
// build path to, or nearly to, goal position
|
||||
me->ComputePath( m_goalPosition, route );
|
||||
|
||||
m_radioedPlan = false;
|
||||
m_askedForCover = false;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Move to a potentially far away position.
|
||||
*/
|
||||
void MoveToState::OnUpdate( CCSBot *me )
|
||||
{
|
||||
Vector myOrigin = GetCentroid( me );
|
||||
|
||||
// assume that we are paying attention and close enough to know our enemy died
|
||||
if (me->GetTask() == CCSBot::MOVE_TO_LAST_KNOWN_ENEMY_POSITION)
|
||||
{
|
||||
/// @todo Account for reaction time so we take some time to realized the enemy is dead
|
||||
CBasePlayer *victim = static_cast<CBasePlayer *>( me->GetTaskEntity() );
|
||||
if (victim == NULL || !victim->IsAlive())
|
||||
{
|
||||
me->PrintIfWatched( "The enemy I was chasing was killed - giving up.\n" );
|
||||
me->Idle();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// look around
|
||||
me->UpdateLookAround();
|
||||
|
||||
//
|
||||
// Scenario logic
|
||||
//
|
||||
switch (TheCSBots()->GetScenario())
|
||||
{
|
||||
case CCSBotManager::SCENARIO_DEFUSE_BOMB:
|
||||
{
|
||||
// if the bomb has been planted, find it
|
||||
// NOTE: This task is used by both CT and T's to find the bomb
|
||||
if (me->GetTask() == CCSBot::FIND_TICKING_BOMB)
|
||||
{
|
||||
if (!me->GetGameState()->IsBombPlanted())
|
||||
{
|
||||
// the bomb is not planted - give up this task
|
||||
me->Idle();
|
||||
return;
|
||||
}
|
||||
|
||||
if (me->GetGameState()->GetPlantedBombsite() != CSGameState::UNKNOWN)
|
||||
{
|
||||
// we know where the bomb is planted, stop searching
|
||||
me->Idle();
|
||||
return;
|
||||
}
|
||||
|
||||
// check off bombsites that we explore or happen to stumble into
|
||||
for( int z=0; z<TheCSBots()->GetZoneCount(); ++z )
|
||||
{
|
||||
// don't re-check zones
|
||||
if (me->GetGameState()->IsBombsiteClear( z ))
|
||||
continue;
|
||||
|
||||
if (TheCSBots()->GetZone(z)->m_extent.Contains( myOrigin ))
|
||||
{
|
||||
// note this bombsite is clear
|
||||
me->GetGameState()->ClearBombsite( z );
|
||||
|
||||
if (me->GetTeamNumber() == TEAM_CT)
|
||||
{
|
||||
// tell teammates this bombsite is clear
|
||||
me->GetChatter()->BombsiteClear( z );
|
||||
}
|
||||
|
||||
// find another zone to check
|
||||
me->Idle();
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// move to a bombsite
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
if (me->GetTeamNumber() == TEAM_CT)
|
||||
{
|
||||
if (me->GetGameState()->IsBombPlanted())
|
||||
{
|
||||
switch( me->GetTask() )
|
||||
{
|
||||
case CCSBot::DEFUSE_BOMB:
|
||||
{
|
||||
// if we are near the bombsite and there is time left, sneak in (unless all enemies are dead)
|
||||
if (me->GetEnemiesRemaining())
|
||||
{
|
||||
const float plentyOfTime = 15.0f;
|
||||
if (TheCSBots()->GetBombTimeLeft() > plentyOfTime)
|
||||
{
|
||||
// get distance remaining on our path until we reach the bombsite
|
||||
float range = me->GetPathDistanceRemaining();
|
||||
|
||||
const float closeRange = 1500.0f;
|
||||
if (range < closeRange)
|
||||
{
|
||||
me->Walk();
|
||||
}
|
||||
else
|
||||
{
|
||||
me->Run();
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// everyone is dead - run!
|
||||
me->Run();
|
||||
}
|
||||
|
||||
// if we are trying to defuse the bomb, and someone has started defusing, guard them instead
|
||||
if (me->CanSeePlantedBomb() && TheCSBots()->GetBombDefuser())
|
||||
{
|
||||
me->GetChatter()->Say( "CoveringFriend" );
|
||||
me->Idle();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// if we are near the bomb, defuse it (if we are reloading, don't try to defuse until we finish)
|
||||
const Vector *bombPos = me->GetGameState()->GetBombPosition();
|
||||
if (bombPos && !me->IsReloading())
|
||||
{
|
||||
const float defuseRange = 100.0f; // 50
|
||||
if ((*bombPos - me->EyePosition()).IsLengthLessThan( defuseRange ))
|
||||
{
|
||||
// make sure we can see the bomb
|
||||
if (me->IsVisible( *bombPos ))
|
||||
{
|
||||
me->DefuseBomb();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
// we need to find the bomb
|
||||
me->Idle();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else // TERRORIST
|
||||
{
|
||||
if (me->GetTask() == CCSBot::PLANT_BOMB )
|
||||
{
|
||||
if ( me->GetFriendsRemaining() )
|
||||
{
|
||||
// if we are about to plant, radio for cover
|
||||
if (!m_askedForCover)
|
||||
{
|
||||
const float nearPlantSite = 50.0f;
|
||||
if (me->IsAtBombsite() && me->GetPathDistanceRemaining() < nearPlantSite)
|
||||
{
|
||||
// radio to the team
|
||||
me->GetChatter()->PlantingTheBomb( me->GetPlace() );
|
||||
m_askedForCover = true;
|
||||
}
|
||||
|
||||
// after we have started to move to the bombsite, tell team we're going to plant, and where
|
||||
// don't do this if we have already radioed that we are starting to plant
|
||||
if (!m_radioedPlan)
|
||||
{
|
||||
const float radioTime = 2.0f;
|
||||
if (gpGlobals->curtime - me->GetStateTimestamp() > radioTime)
|
||||
{
|
||||
// radio to the team if we're more than 10 seconds (2400 units) out
|
||||
const float nearPlantSite = 2400.0f;
|
||||
if ( me->GetPathDistanceRemaining() >= nearPlantSite )
|
||||
{
|
||||
me->GetChatter()->GoingToPlantTheBomb( TheNavMesh->GetPlace( m_goalPosition ) );
|
||||
}
|
||||
m_radioedPlan = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
case CCSBotManager::SCENARIO_RESCUE_HOSTAGES:
|
||||
{
|
||||
if (me->GetTask() == CCSBot::COLLECT_HOSTAGES)
|
||||
{
|
||||
//
|
||||
// Since CT's have a radar, they can directly look at the actual hostage state
|
||||
//
|
||||
|
||||
// check if someone else collected our hostage, or the hostage died or was rescued
|
||||
CHostage *hostage = static_cast<CHostage *>( me->GetGoalEntity() );
|
||||
if (hostage == NULL || !hostage->IsValid() || hostage->IsFollowingSomeone())
|
||||
{
|
||||
me->Idle();
|
||||
return;
|
||||
}
|
||||
|
||||
Vector hostageOrigin = GetCentroid( hostage );
|
||||
|
||||
// if our hostage has moved, repath
|
||||
const float repathToleranceSq = 75.0f * 75.0f;
|
||||
float error = (hostageOrigin - m_goalPosition).LengthSqr();
|
||||
if (error > repathToleranceSq)
|
||||
{
|
||||
m_goalPosition = hostageOrigin;
|
||||
me->ComputePath( m_goalPosition, SAFEST_ROUTE );
|
||||
}
|
||||
|
||||
/// @todo Generalize ladder priorities over other tasks
|
||||
if (!me->IsUsingLadder())
|
||||
{
|
||||
Vector pos = hostage->EyePosition();
|
||||
Vector to = pos - me->EyePosition(); // "Use" checks from eye position, so we should too
|
||||
|
||||
// look at the hostage as we approach
|
||||
const float watchHostageRange = 100.0f;
|
||||
if (to.IsLengthLessThan( watchHostageRange ))
|
||||
{
|
||||
me->SetLookAt( "Hostage", pos, PRIORITY_LOW, 0.5f );
|
||||
|
||||
// randomly move just a bit to avoid infinite use loops from bad hostage placement
|
||||
NavRelativeDirType dir = (NavRelativeDirType)RandomInt( 0, 3 );
|
||||
switch( dir )
|
||||
{
|
||||
case LEFT: me->StrafeLeft(); break;
|
||||
case RIGHT: me->StrafeRight(); break;
|
||||
case FORWARD: me->MoveForward(); break;
|
||||
case BACKWARD: me->MoveBackward(); break;
|
||||
}
|
||||
|
||||
// check if we are close enough to the hostage to talk to him
|
||||
const float useRange = PLAYER_USE_RADIUS - 10.0f; // shave off a fudge factor to make sure we're within range
|
||||
if (to.IsLengthLessThan( useRange ))
|
||||
{
|
||||
me->UseEntity( me->GetGoalEntity() );
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (me->GetTask() == CCSBot::RESCUE_HOSTAGES)
|
||||
{
|
||||
// periodically check if we lost all our hostages
|
||||
if (me->GetHostageEscortCount() == 0)
|
||||
{
|
||||
// lost our hostages - go get 'em
|
||||
me->Idle();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (me->UpdatePathMovement() != CCSBot::PROGRESSING)
|
||||
{
|
||||
// reached destination
|
||||
switch( me->GetTask() )
|
||||
{
|
||||
case CCSBot::PLANT_BOMB:
|
||||
// if we are at bombsite with the bomb, plant it
|
||||
if (me->IsAtBombsite() && me->HasC4())
|
||||
{
|
||||
me->PlantBomb();
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case CCSBot::MOVE_TO_LAST_KNOWN_ENEMY_POSITION:
|
||||
{
|
||||
CBasePlayer *victim = static_cast<CBasePlayer *>( me->GetTaskEntity() );
|
||||
if (victim && victim->IsAlive())
|
||||
{
|
||||
// if we got here and haven't re-acquired the enemy, we lost him
|
||||
BotStatement *say = new BotStatement( me->GetChatter(), REPORT_ENEMY_LOST, 8.0f );
|
||||
|
||||
say->AppendPhrase( TheBotPhrases->GetPhrase( "LostEnemy" ) );
|
||||
say->SetStartTime( gpGlobals->curtime + RandomFloat( 3.0f, 5.0f ) );
|
||||
|
||||
me->GetChatter()->AddStatement( say );
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// default behavior when destination is reached
|
||||
me->Idle();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
void MoveToState::OnExit( CCSBot *me )
|
||||
{
|
||||
// reset to run in case we were walking near our goal position
|
||||
me->Run();
|
||||
me->SetDisposition( CCSBot::ENGAGE_AND_INVESTIGATE );
|
||||
//me->StopAiming();
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose:
|
||||
//
|
||||
// $NoKeywords: $
|
||||
//=============================================================================//
|
||||
|
||||
// Author: Michael S. Booth (mike@turtlerockstudios.com), April 2005
|
||||
|
||||
#include "cbase.h"
|
||||
#include "cs_bot.h"
|
||||
#include "BasePropDoor.h"
|
||||
#include "doors.h"
|
||||
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
|
||||
//-------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Face the door and open it.
|
||||
* NOTE: This state assumes we are standing in range of the door to be opened, with no obstructions.
|
||||
*/
|
||||
void OpenDoorState::OnEnter( CCSBot *me )
|
||||
{
|
||||
m_isDone = false;
|
||||
m_timeout.Start( 1.0f );
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------------------------------------------------------
|
||||
void OpenDoorState::SetDoor( CBaseEntity *door )
|
||||
{
|
||||
CBaseDoor *funcDoor = dynamic_cast< CBaseDoor * >(door);
|
||||
if ( funcDoor )
|
||||
{
|
||||
m_funcDoor = funcDoor;
|
||||
return;
|
||||
}
|
||||
|
||||
CBasePropDoor *propDoor = dynamic_cast< CBasePropDoor * >(door);
|
||||
if ( propDoor )
|
||||
{
|
||||
m_propDoor = propDoor;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------------------------------------------------------
|
||||
void OpenDoorState::OnUpdate( CCSBot *me )
|
||||
{
|
||||
me->ResetStuckMonitor();
|
||||
|
||||
// wait for door to swing open before leaving state
|
||||
if (m_timeout.IsElapsed())
|
||||
{
|
||||
m_isDone = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// look at the door
|
||||
Vector pos;
|
||||
bool isDoorMoving = false;
|
||||
if ( m_funcDoor )
|
||||
{
|
||||
pos = m_funcDoor->WorldSpaceCenter();
|
||||
isDoorMoving = m_funcDoor->m_toggle_state == TS_GOING_UP || m_funcDoor->m_toggle_state == TS_GOING_DOWN;
|
||||
}
|
||||
else
|
||||
{
|
||||
pos = m_propDoor->WorldSpaceCenter();
|
||||
isDoorMoving = m_propDoor->IsDoorOpening() || m_propDoor->IsDoorClosing();
|
||||
}
|
||||
|
||||
me->SetLookAt( "Open door", pos, PRIORITY_HIGH );
|
||||
|
||||
// if we are looking at the door, "use" it and exit
|
||||
if (me->IsLookingAtPosition( pos ))
|
||||
{
|
||||
me->UseEnvironment();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------------------------------------------------------
|
||||
void OpenDoorState::OnExit( CCSBot *me )
|
||||
{
|
||||
me->ClearLookAt();
|
||||
me->ResetStuckMonitor();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose:
|
||||
//
|
||||
// $NoKeywords: $
|
||||
//=============================================================================//
|
||||
|
||||
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
|
||||
|
||||
#include "cbase.h"
|
||||
#include "cs_bot.h"
|
||||
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Plant the bomb.
|
||||
*/
|
||||
void PlantBombState::OnEnter( CCSBot *me )
|
||||
{
|
||||
me->Crouch();
|
||||
me->SetDisposition( CCSBot::SELF_DEFENSE );
|
||||
|
||||
// look at the floor
|
||||
// Vector down( myOrigin.x, myOrigin.y, -1000.0f );
|
||||
|
||||
float yaw = me->EyeAngles().y;
|
||||
Vector2D dir( BotCOS(yaw), BotSIN(yaw) );
|
||||
Vector myOrigin = GetCentroid( me );
|
||||
|
||||
Vector down( myOrigin.x + 10.0f * dir.x, myOrigin.y + 10.0f * dir.y, me->GetFeetZ() );
|
||||
me->SetLookAt( "Plant bomb on floor", down, PRIORITY_HIGH );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Plant the bomb.
|
||||
*/
|
||||
void PlantBombState::OnUpdate( CCSBot *me )
|
||||
{
|
||||
CBaseCombatWeapon *gun = me->GetActiveWeapon();
|
||||
bool holdingC4 = false;
|
||||
if (gun)
|
||||
{
|
||||
if (FStrEq( gun->GetClassname(), "weapon_c4" ))
|
||||
holdingC4 = true;
|
||||
}
|
||||
|
||||
// if we aren't holding the C4, grab it, otherwise plant it
|
||||
if (holdingC4)
|
||||
me->PrimaryAttack();
|
||||
else
|
||||
me->SelectItem( "weapon_c4" );
|
||||
|
||||
// if we no longer have the C4, we've successfully planted
|
||||
if (!me->HasC4())
|
||||
{
|
||||
// move to a hiding spot and watch the bomb
|
||||
me->SetTask( CCSBot::GUARD_TICKING_BOMB );
|
||||
me->Hide();
|
||||
}
|
||||
|
||||
// if we time out, it's because we slipped into a non-plantable area
|
||||
const float timeout = 5.0f;
|
||||
if (gpGlobals->curtime - me->GetStateTimestamp() > timeout)
|
||||
me->Idle();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
void PlantBombState::OnExit( CCSBot *me )
|
||||
{
|
||||
// equip our rifle (in case we were interrupted while holding C4)
|
||||
me->EquipBestWeapon();
|
||||
me->StandUp();
|
||||
me->ResetStuckMonitor();
|
||||
me->SetDisposition( CCSBot::ENGAGE_AND_INVESTIGATE );
|
||||
me->ClearLookAt();
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose:
|
||||
//
|
||||
// $NoKeywords: $
|
||||
//=============================================================================//
|
||||
|
||||
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
|
||||
|
||||
#include "cbase.h"
|
||||
#include "cs_bot.h"
|
||||
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
|
||||
/**
|
||||
* Face the entity and "use" it
|
||||
* NOTE: This state assumes we are standing in range of the entity to be used, with no obstructions.
|
||||
*/
|
||||
void UseEntityState::OnEnter( CCSBot *me )
|
||||
{
|
||||
}
|
||||
|
||||
void UseEntityState::OnUpdate( CCSBot *me )
|
||||
{
|
||||
// in the very rare situation where two or more bots "used" a hostage at the same time,
|
||||
// one bot will fail and needs to time out of this state
|
||||
const float useTimeout = 5.0f;
|
||||
if (me->GetStateTimestamp() - gpGlobals->curtime > useTimeout)
|
||||
{
|
||||
me->Idle();
|
||||
return;
|
||||
}
|
||||
|
||||
// look at the entity
|
||||
Vector pos = m_entity->EyePosition();
|
||||
me->SetLookAt( "Use entity", pos, PRIORITY_HIGH );
|
||||
|
||||
// if we are looking at the entity, "use" it and exit
|
||||
if (me->IsLookingAtPosition( pos ))
|
||||
{
|
||||
if (TheCSBots()->GetScenario() == CCSBotManager::SCENARIO_RESCUE_HOSTAGES &&
|
||||
me->GetTeamNumber() == TEAM_CT &&
|
||||
me->GetTask() == CCSBot::COLLECT_HOSTAGES)
|
||||
{
|
||||
// we are collecting a hostage, assume we were successful - the update check will correct us if we weren't
|
||||
me->IncreaseHostageEscortCount();
|
||||
}
|
||||
|
||||
me->UseEnvironment();
|
||||
me->Idle();
|
||||
}
|
||||
}
|
||||
|
||||
void UseEntityState::OnExit( CCSBot *me )
|
||||
{
|
||||
me->ClearLookAt();
|
||||
me->ResetStuckMonitor();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose: Data for Autobuy and Rebuy
|
||||
//
|
||||
//=============================================================================//
|
||||
|
||||
#include "cbase.h"
|
||||
#include "cs_autobuy.h"
|
||||
|
||||
// Weapon class information for each weapon including the class name and the buy command alias.
|
||||
AutoBuyInfoStruct g_autoBuyInfo[] =
|
||||
{
|
||||
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_RIFLE), "galil", "weapon_galil" }, // galil
|
||||
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_RIFLE), "ak47", "weapon_ak47" }, // ak47
|
||||
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_SNIPERRIFLE), "scout", "weapon_scout" }, // scout
|
||||
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_RIFLE), "sg552", "weapon_sg552" }, // sg552
|
||||
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_SNIPERRIFLE), "awp", "weapon_awp" }, // awp
|
||||
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_SNIPERRIFLE), "g3sg1", "weapon_g3sg1" }, // g3sg1
|
||||
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_RIFLE), "famas", "weapon_famas" }, // famas
|
||||
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_RIFLE), "m4a1", "weapon_m4a1" }, // m4a1
|
||||
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_RIFLE), "aug", "weapon_aug" }, // aug
|
||||
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_SNIPERRIFLE), "sg550", "weapon_sg550" }, // sg550
|
||||
{ (AutoBuyClassType)(AUTOBUYCLASS_SECONDARY | AUTOBUYCLASS_PISTOL), "glock", "weapon_glock" }, // glock
|
||||
{ (AutoBuyClassType)(AUTOBUYCLASS_SECONDARY | AUTOBUYCLASS_PISTOL), "usp", "weapon_usp" }, // usp
|
||||
{ (AutoBuyClassType)(AUTOBUYCLASS_SECONDARY | AUTOBUYCLASS_PISTOL), "p228", "weapon_p228" }, // p228
|
||||
{ (AutoBuyClassType)(AUTOBUYCLASS_SECONDARY | AUTOBUYCLASS_PISTOL), "deagle", "weapon_deagle" }, // deagle
|
||||
{ (AutoBuyClassType)(AUTOBUYCLASS_SECONDARY | AUTOBUYCLASS_PISTOL), "elite", "weapon_elite" }, // elites
|
||||
{ (AutoBuyClassType)(AUTOBUYCLASS_SECONDARY | AUTOBUYCLASS_PISTOL), "fn57", "weapon_fiveseven" }, // fn57
|
||||
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_SHOTGUN), "m3", "weapon_m3" }, // m3
|
||||
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_SHOTGUN), "xm1014", "weapon_xm1014" }, // xm1014
|
||||
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_SMG), "mac10", "weapon_mac10" }, // mac10
|
||||
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_SMG), "tmp", "weapon_tmp" }, // tmp
|
||||
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_SMG), "mp5navy", "weapon_mp5navy" }, // mp5navy
|
||||
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_SMG), "ump45", "weapon_ump45" }, // ump45
|
||||
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_SMG), "p90", "weapon_p90" }, // p90
|
||||
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_MACHINEGUN), "m249", "weapon_m249" }, // m249
|
||||
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_AMMO), "primammo", "primammo" }, // primammo
|
||||
{ (AutoBuyClassType)(AUTOBUYCLASS_SECONDARY | AUTOBUYCLASS_AMMO), "secammo", "secammo" }, // secmmo
|
||||
{ (AutoBuyClassType)(AUTOBUYCLASS_ARMOR), "vest", "item_kevlar" }, // vest
|
||||
{ (AutoBuyClassType)(AUTOBUYCLASS_ARMOR), "vesthelm", "item_assaultsuit" }, // vesthelm
|
||||
{ (AutoBuyClassType)(AUTOBUYCLASS_GRENADE), "flashbang", "weapon_flashbang" }, // flash
|
||||
{ (AutoBuyClassType)(AUTOBUYCLASS_GRENADE), "hegrenade", "weapon_hegrenade" }, // hegren
|
||||
{ (AutoBuyClassType)(AUTOBUYCLASS_GRENADE), "smokegrenade", "weapon_smokegrenade" }, // sgren
|
||||
{ (AutoBuyClassType)(AUTOBUYCLASS_NIGHTVISION), "nvgs", "nvgs" }, // nvgs
|
||||
{ (AutoBuyClassType)(AUTOBUYCLASS_DEFUSER), "defuser", "defuser" }, // defuser
|
||||
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_SHIELD), "shield", "shield" }, // shield
|
||||
|
||||
{ (AutoBuyClassType)0, "", "" } // last one, must be at end.
|
||||
};
|
||||
@@ -0,0 +1,53 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose: Headers and defines for Autobuy and Rebuy
|
||||
//
|
||||
//=============================================================================//
|
||||
|
||||
/**
|
||||
* Weapon classes as used by the AutoBuy
|
||||
* Has to be different that the previous ones because these are bitmasked values as a weapon can be from
|
||||
* more than one class. This also includes all the classes of equipment that a player can buy.
|
||||
*/
|
||||
enum AutoBuyClassType
|
||||
{
|
||||
AUTOBUYCLASS_PRIMARY = 1,
|
||||
AUTOBUYCLASS_SECONDARY = 2,
|
||||
AUTOBUYCLASS_AMMO = 4,
|
||||
AUTOBUYCLASS_ARMOR = 8,
|
||||
AUTOBUYCLASS_DEFUSER = 16,
|
||||
AUTOBUYCLASS_PISTOL = 32,
|
||||
AUTOBUYCLASS_SMG = 64,
|
||||
AUTOBUYCLASS_RIFLE = 128,
|
||||
AUTOBUYCLASS_SNIPERRIFLE = 256,
|
||||
AUTOBUYCLASS_SHOTGUN = 512,
|
||||
AUTOBUYCLASS_MACHINEGUN = 1024,
|
||||
AUTOBUYCLASS_GRENADE = 2048,
|
||||
AUTOBUYCLASS_NIGHTVISION = 4096,
|
||||
AUTOBUYCLASS_SHIELD = 8192,
|
||||
};
|
||||
|
||||
struct AutoBuyInfoStruct
|
||||
{
|
||||
AutoBuyClassType m_class;
|
||||
const char *m_command;
|
||||
const char *m_classname;
|
||||
};
|
||||
|
||||
struct RebuyStruct
|
||||
{
|
||||
char m_szPrimaryWeapon[64]; //"weapon_" string of the primary weapon
|
||||
char m_szSecondaryWeapon[64]; //"weapon_" string of the secondary weapon
|
||||
|
||||
int m_primaryAmmo; // number of rounds the player had (not including rounds in the gun)
|
||||
int m_secondaryAmmo; // number of rounds the player had (not including rounds in the gun)
|
||||
int m_heGrenade; // number of grenades to buy
|
||||
int m_flashbang; // number of grenades to buy
|
||||
int m_smokeGrenade; // number of grenades to buy
|
||||
int m_armor; // 0, 1, or 2 (0 = none, 1 = vest, 2 = vest + helmet)
|
||||
|
||||
bool m_defuser; // do we want a defuser
|
||||
bool m_nightVision; // do we want night vision
|
||||
};
|
||||
|
||||
extern AutoBuyInfoStruct g_autoBuyInfo[];
|
||||
@@ -0,0 +1,554 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose: Basic BOT handling.
|
||||
//
|
||||
// $Workfile: $
|
||||
// $Date: $
|
||||
//
|
||||
//-----------------------------------------------------------------------------
|
||||
// $Log: $
|
||||
//
|
||||
// $NoKeywords: $
|
||||
//=============================================================================//
|
||||
|
||||
#include "cbase.h"
|
||||
#include "cs_player.h"
|
||||
#include "in_buttons.h"
|
||||
#include "movehelper_server.h"
|
||||
#include "team.h"
|
||||
#include "cs_gamerules.h"
|
||||
#include "client.h"
|
||||
|
||||
|
||||
void Bot_Think( CCSPlayer *pBot );
|
||||
|
||||
ConVar bot_forcefireweapon( "bot_forcefireweapon", "", 0, "Force bots with the specified weapon to fire." );
|
||||
ConVar bot_forceattack2( "bot_forceattack2", "0", 0, "When firing, use attack2." );
|
||||
ConVar bot_forceattackon( "bot_forceattackon", "0", 0, "When firing, don't tap fire, hold it down." );
|
||||
ConVar bot_flipout( "bot_flipout", "0", 0, "When on, all bots fire their guns." );
|
||||
ConVar bot_mimic( "bot_mimic", "0", 0, "Bot uses usercmd of player by index." );
|
||||
static ConVar bot_mimic_yaw_offset( "bot_mimic_yaw_offset", "0", 0, "Offsets the bot yaw." );
|
||||
|
||||
static int BotNumber = 1;
|
||||
static int g_iNextBotTeam = -1;
|
||||
|
||||
|
||||
typedef struct
|
||||
{
|
||||
bool backwards;
|
||||
|
||||
float nextturntime;
|
||||
bool lastturntoright;
|
||||
|
||||
float nextstrafetime;
|
||||
float sidemove;
|
||||
|
||||
QAngle forwardAngle;
|
||||
QAngle lastAngles;
|
||||
|
||||
int m_WantedTeam;
|
||||
float m_flJoinTeamTime;
|
||||
|
||||
bool m_bTempBot; // Is this slot a dump temp bot or a real bot?
|
||||
} botdata_t;
|
||||
|
||||
static botdata_t g_BotData[ MAX_PLAYERS ];
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Create a new Bot and put it in the game.
|
||||
// Output : Pointer to the new Bot, or NULL if there's no free clients.
|
||||
//-----------------------------------------------------------------------------
|
||||
CBasePlayer *BotPutInServer( bool bFrozen, int iTeam )
|
||||
{
|
||||
g_iNextBotTeam = iTeam;
|
||||
|
||||
char botname[ 64 ];
|
||||
Q_snprintf( botname, sizeof( botname ), "Bot%02i", BotNumber );
|
||||
|
||||
edict_t *pEdict = engine->CreateFakeClient( botname );
|
||||
|
||||
if (!pEdict)
|
||||
{
|
||||
Msg( "Failed to create Bot.\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Allocate a CBasePlayer for the bot, and call spawn
|
||||
//ClientPutInServer( pEdict, botname );
|
||||
//ClientActive( pEdict, false );
|
||||
|
||||
CCSPlayer *pPlayer = ((CCSPlayer *)CBaseEntity::Instance( pEdict ));
|
||||
pPlayer->ClearFlags();
|
||||
pPlayer->AddFlag( FL_CLIENT | FL_FAKECLIENT );
|
||||
|
||||
if ( bFrozen )
|
||||
pPlayer->AddEFlags( EFL_BOT_FROZEN );
|
||||
|
||||
if ( iTeam == -1 )
|
||||
iTeam = ( pPlayer->entindex() & 1 ) ? TEAM_TERRORIST : TEAM_CT;
|
||||
|
||||
botdata_t *pData = &g_BotData[pPlayer->entindex()-1];
|
||||
pData->m_WantedTeam = iTeam;
|
||||
pData->m_flJoinTeamTime = gpGlobals->curtime + 0.3;
|
||||
pData->m_bTempBot = true;
|
||||
|
||||
BotNumber++;
|
||||
return pPlayer;
|
||||
}
|
||||
|
||||
bool IsTempBot( CBaseEntity *pEnt )
|
||||
{
|
||||
if ( !pEnt )
|
||||
return false;
|
||||
|
||||
if ( !(pEnt->GetFlags() & FL_FAKECLIENT) )
|
||||
return false;
|
||||
|
||||
int i = pEnt->entindex();
|
||||
if ( i >= 1 && i < MAX_PLAYERS )
|
||||
return g_BotData[i-1].m_bTempBot;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Run through all the Bots in the game and let them think.
|
||||
//-----------------------------------------------------------------------------
|
||||
void Bot_RunAll( void )
|
||||
{
|
||||
for ( int i = 1; i <= gpGlobals->maxClients; i++ )
|
||||
{
|
||||
CCSPlayer *pPlayer = ToCSPlayer( UTIL_PlayerByIndex( i ) );
|
||||
|
||||
if ( IsTempBot( pPlayer ) )
|
||||
{
|
||||
Bot_Think( pPlayer );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool RunMimicCommand( CUserCmd& cmd )
|
||||
{
|
||||
if ( bot_mimic.GetInt() <= 0 )
|
||||
return false;
|
||||
|
||||
if ( bot_mimic.GetInt() > gpGlobals->maxClients )
|
||||
return false;
|
||||
|
||||
|
||||
CBasePlayer *pPlayer = UTIL_PlayerByIndex( bot_mimic.GetInt() );
|
||||
if ( !pPlayer )
|
||||
return false;
|
||||
|
||||
if ( !pPlayer->GetLastUserCommand() )
|
||||
return false;
|
||||
|
||||
cmd = *pPlayer->GetLastUserCommand();
|
||||
cmd.viewangles[YAW] += bot_mimic_yaw_offset.GetFloat();
|
||||
return true;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Simulates a single frame of movement for a player
|
||||
// Input : *fakeclient -
|
||||
// *viewangles -
|
||||
// forwardmove -
|
||||
// sidemove -
|
||||
// upmove -
|
||||
// buttons -
|
||||
// impulse -
|
||||
// msec -
|
||||
// Output : virtual void
|
||||
//-----------------------------------------------------------------------------
|
||||
static void RunPlayerMove( CCSPlayer *fakeclient, const QAngle& viewangles, float forwardmove, float sidemove, float upmove, unsigned short buttons, byte impulse, float frametime )
|
||||
{
|
||||
if ( !fakeclient )
|
||||
return;
|
||||
|
||||
CUserCmd cmd;
|
||||
|
||||
// Store off the globals.. they're gonna get whacked
|
||||
float flOldFrametime = gpGlobals->frametime;
|
||||
float flOldCurtime = gpGlobals->curtime;
|
||||
|
||||
float flTimeBase = gpGlobals->curtime;
|
||||
fakeclient->SetTimeBase( flTimeBase );
|
||||
|
||||
Q_memset( &cmd, 0, sizeof( cmd ) );
|
||||
|
||||
if ( !RunMimicCommand( cmd ) )
|
||||
{
|
||||
VectorCopy( viewangles, cmd.viewangles );
|
||||
cmd.forwardmove = forwardmove;
|
||||
cmd.sidemove = sidemove;
|
||||
cmd.upmove = upmove;
|
||||
cmd.buttons = buttons;
|
||||
cmd.impulse = impulse;
|
||||
cmd.random_seed = random->RandomInt( 0, 0x7fffffff );
|
||||
}
|
||||
|
||||
MoveHelperServer()->SetHost( fakeclient );
|
||||
fakeclient->PlayerRunCommand( &cmd, MoveHelperServer() );
|
||||
|
||||
// save off the last good usercmd
|
||||
fakeclient->SetLastUserCommand( cmd );
|
||||
|
||||
// Clear out any fixangle that has been set
|
||||
fakeclient->pl.fixangle = FIXANGLE_NONE;
|
||||
|
||||
// Restore the globals..
|
||||
gpGlobals->frametime = flOldFrametime;
|
||||
gpGlobals->curtime = flOldCurtime;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Run this Bot's AI for one frame.
|
||||
//-----------------------------------------------------------------------------
|
||||
void Bot_Think( CCSPlayer *pBot )
|
||||
{
|
||||
// Make sure we stay being a bot
|
||||
pBot->AddFlag( FL_FAKECLIENT );
|
||||
|
||||
botdata_t *botdata = &g_BotData[ pBot->entindex() - 1 ];
|
||||
|
||||
float forwardmove = 0.0;
|
||||
float sidemove = botdata->sidemove;
|
||||
float upmove = 0.0;
|
||||
unsigned short buttons = 0;
|
||||
byte impulse = 0;
|
||||
float frametime = gpGlobals->frametime;
|
||||
|
||||
if ( pBot->GetTeamNumber() == TEAM_UNASSIGNED && gpGlobals->curtime > botdata->m_flJoinTeamTime )
|
||||
{
|
||||
pBot->HandleCommand_JoinTeam( botdata->m_WantedTeam );
|
||||
}
|
||||
else if ( pBot->GetTeamNumber() != TEAM_UNASSIGNED && pBot->PlayerClass() == CS_CLASS_NONE )
|
||||
{
|
||||
// If they're on a team but haven't picked a class, choose a random class..
|
||||
pBot->HandleCommand_JoinClass( 0 );
|
||||
}
|
||||
else
|
||||
{
|
||||
QAngle vecViewAngles;
|
||||
vecViewAngles = pBot->GetLocalAngles();
|
||||
|
||||
// Create some random values
|
||||
if ( pBot->IsAlive() && (pBot->GetSolid() == SOLID_BBOX) )
|
||||
{
|
||||
trace_t trace;
|
||||
|
||||
// Stop when shot
|
||||
if ( !pBot->IsEFlagSet(EFL_BOT_FROZEN) )
|
||||
{
|
||||
if ( pBot->m_iHealth == 100 )
|
||||
{
|
||||
forwardmove = 600 * ( botdata->backwards ? -1 : 1 );
|
||||
if ( botdata->sidemove != 0.0f )
|
||||
{
|
||||
forwardmove *= random->RandomFloat( 0.1, 1.0f );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
forwardmove = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Only turn if I haven't been hurt
|
||||
if ( !pBot->IsEFlagSet(EFL_BOT_FROZEN) && pBot->m_iHealth == 100 )
|
||||
{
|
||||
Vector vecEnd;
|
||||
Vector forward;
|
||||
|
||||
QAngle angle;
|
||||
float angledelta = 15.0;
|
||||
|
||||
int maxtries = (int)360.0/angledelta;
|
||||
|
||||
if ( botdata->lastturntoright )
|
||||
{
|
||||
angledelta = -angledelta;
|
||||
}
|
||||
|
||||
angle = pBot->GetLocalAngles();
|
||||
|
||||
Vector vecSrc;
|
||||
while ( --maxtries >= 0 )
|
||||
{
|
||||
AngleVectors( angle, &forward, NULL, NULL );
|
||||
|
||||
vecSrc = pBot->GetLocalOrigin() + Vector( 0, 0, 36 );
|
||||
|
||||
vecEnd = vecSrc + forward * 10;
|
||||
|
||||
UTIL_TraceHull( vecSrc, vecEnd, VEC_HULL_MIN_SCALED( pBot ), VEC_HULL_MAX_SCALED( pBot ),
|
||||
MASK_PLAYERSOLID, pBot, COLLISION_GROUP_NONE, &trace );
|
||||
|
||||
if ( trace.fraction == 1.0 )
|
||||
{
|
||||
//if ( gpGlobals->curtime < botdata->nextturntime )
|
||||
//{
|
||||
break;
|
||||
//}
|
||||
}
|
||||
|
||||
angle.y += angledelta;
|
||||
|
||||
if ( angle.y > 180 )
|
||||
angle.y -= 360;
|
||||
else if ( angle.y < -180 )
|
||||
angle.y += 360;
|
||||
|
||||
botdata->nextturntime = gpGlobals->curtime + 2.0;
|
||||
botdata->lastturntoright = random->RandomInt( 0, 1 ) == 0 ? true : false;
|
||||
|
||||
botdata->forwardAngle = angle;
|
||||
botdata->lastAngles = angle;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
if ( gpGlobals->curtime >= botdata->nextstrafetime )
|
||||
{
|
||||
botdata->nextstrafetime = gpGlobals->curtime + 1.0f;
|
||||
|
||||
if ( random->RandomInt( 0, 5 ) == 0 )
|
||||
{
|
||||
botdata->sidemove = -600.0f + 1200.0f * random->RandomFloat( 0, 2 );
|
||||
}
|
||||
else
|
||||
{
|
||||
botdata->sidemove = 0;
|
||||
}
|
||||
sidemove = botdata->sidemove;
|
||||
|
||||
if ( random->RandomInt( 0, 20 ) == 0 )
|
||||
{
|
||||
botdata->backwards = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
botdata->backwards = false;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
pBot->SetLocalAngles( angle );
|
||||
vecViewAngles = angle;
|
||||
}
|
||||
|
||||
// If bots are being forced to fire a weapon, see if I have it
|
||||
else if ( bot_forcefireweapon.GetString() )
|
||||
{
|
||||
CBaseCombatWeapon *pWeapon = pBot->Weapon_OwnsThisType( bot_forcefireweapon.GetString() );
|
||||
if ( pWeapon )
|
||||
{
|
||||
// Switch to it if we don't have it out
|
||||
CBaseCombatWeapon *pActiveWeapon = pBot->GetActiveWeapon();
|
||||
|
||||
// Switch?
|
||||
if ( pActiveWeapon != pWeapon )
|
||||
{
|
||||
pBot->Weapon_Switch( pWeapon );
|
||||
}
|
||||
else
|
||||
{
|
||||
// Start firing
|
||||
// Some weapons require releases, so randomise firing
|
||||
if ( bot_forceattackon.GetBool() || (RandomFloat(0.0,1.0) > 0.5) )
|
||||
{
|
||||
buttons |= bot_forceattack2.GetBool() ? IN_ATTACK2 : IN_ATTACK;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( bot_flipout.GetInt() )
|
||||
{
|
||||
if ( bot_forceattackon.GetBool() || (RandomFloat(0.0,1.0) > 0.5) )
|
||||
{
|
||||
buttons |= bot_forceattack2.GetBool() ? IN_ATTACK2 : IN_ATTACK;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Wait for Reinforcement wave
|
||||
if ( !pBot->IsAlive() )
|
||||
{
|
||||
// Try hitting my buttons occasionally
|
||||
if ( random->RandomInt( 0, 100 ) > 80 )
|
||||
{
|
||||
// Respawn the bot
|
||||
if ( random->RandomInt( 0, 1 ) == 0 )
|
||||
{
|
||||
buttons |= IN_JUMP;
|
||||
}
|
||||
else
|
||||
{
|
||||
buttons = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( bot_flipout.GetInt() >= 2 )
|
||||
{
|
||||
|
||||
QAngle angOffset = RandomAngle( -1, 1 );
|
||||
|
||||
botdata->lastAngles += angOffset;
|
||||
|
||||
for ( int i = 0 ; i < 2; i++ )
|
||||
{
|
||||
if ( fabs( botdata->lastAngles[ i ] - botdata->forwardAngle[ i ] ) > 15.0f )
|
||||
{
|
||||
if ( botdata->lastAngles[ i ] > botdata->forwardAngle[ i ] )
|
||||
{
|
||||
botdata->lastAngles[ i ] = botdata->forwardAngle[ i ] + 15;
|
||||
}
|
||||
else
|
||||
{
|
||||
botdata->lastAngles[ i ] = botdata->forwardAngle[ i ] - 15;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
botdata->lastAngles[ 2 ] = 0;
|
||||
|
||||
pBot->SetLocalAngles( botdata->lastAngles );
|
||||
}
|
||||
}
|
||||
|
||||
pBot->SetPunchAngle( QAngle( 0, 0, 0 ) );
|
||||
RunPlayerMove( pBot, pBot->GetLocalAngles(), forwardmove, sidemove, upmove, buttons, impulse, frametime );
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Handler for the "bot" command.
|
||||
CON_COMMAND_F( "bot_old", "Add a bot.", FCVAR_CHEAT )
|
||||
{
|
||||
// Disable the CS bots, otherwise they'll interfere with the bot code here.
|
||||
//extern bool g_bEnableCSBots;
|
||||
//g_bEnableCSBots = false;
|
||||
|
||||
CCSPlayer *pPlayer = CCSPlayer::Instance( UTIL_GetCommandClientIndex() );
|
||||
|
||||
// The bot command uses switches like command-line switches.
|
||||
// -count <count> tells how many bots to spawn.
|
||||
// -team <index> selects the bot's team. Default is -1 which chooses randomly.
|
||||
// Note: if you do -team !, then it
|
||||
// -class <index> selects the bot's class. Default is -1 which chooses randomly.
|
||||
// -frozen prevents the bots from running around when they spawn in.
|
||||
|
||||
int count = args.FindArgInt( "-count", 1 );
|
||||
count = clamp( count, 1, 16 );
|
||||
|
||||
int iTeam = -1;
|
||||
const char *pVal = args.FindArg( "-team" );
|
||||
if ( pVal )
|
||||
{
|
||||
if ( pVal[0] == '!' )
|
||||
{
|
||||
if ( pPlayer->GetTeamNumber() == TEAM_TERRORIST )
|
||||
iTeam = TEAM_CT;
|
||||
else
|
||||
iTeam = TEAM_TERRORIST;
|
||||
}
|
||||
else if ( pVal[0] == 't' || pVal[0] == 'T' )
|
||||
{
|
||||
iTeam = TEAM_TERRORIST;
|
||||
}
|
||||
else if ( pVal[0] == 'c' || pVal[0] == 'C' )
|
||||
{
|
||||
iTeam = TEAM_CT;
|
||||
}
|
||||
else
|
||||
{
|
||||
iTeam = atoi( pVal );
|
||||
if ( iTeam == 1 )
|
||||
iTeam = TEAM_TERRORIST;
|
||||
else
|
||||
iTeam = TEAM_CT;
|
||||
}
|
||||
}
|
||||
|
||||
// Look at -frozen.
|
||||
bool bFrozen = !!args.FindArg( "-frozen" );
|
||||
|
||||
// Ok, spawn all the bots.
|
||||
while ( --count >= 0 )
|
||||
{
|
||||
extern CBasePlayer *BotPutInServer( bool bFrozen, int iTeam );
|
||||
BotPutInServer( bFrozen, iTeam );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Handle the "PossessBot" command.
|
||||
void PossessBot_f( const CCommand &args )
|
||||
{
|
||||
CCSPlayer *pPlayer = CCSPlayer::Instance( UTIL_GetCommandClientIndex() );
|
||||
if ( !pPlayer )
|
||||
return;
|
||||
|
||||
// Put the local player in control of this bot.
|
||||
if ( args.ArgC() != 2 )
|
||||
{
|
||||
Warning( "PossessBot <client index>\n" );
|
||||
return;
|
||||
}
|
||||
|
||||
int iBotClient = atoi( args[1] );
|
||||
int iBotEnt = iBotClient + 1;
|
||||
|
||||
if ( iBotClient < 0 ||
|
||||
iBotClient >= gpGlobals->maxClients ||
|
||||
pPlayer->entindex() == iBotEnt )
|
||||
{
|
||||
Warning( "PossessBot <client index>\n" );
|
||||
}
|
||||
else
|
||||
{
|
||||
edict_t *pPlayerData = pPlayer->edict();
|
||||
edict_t *pBotData = engine->PEntityOfEntIndex( iBotEnt );
|
||||
if ( pBotData && pBotData )
|
||||
{
|
||||
// SWAP EDICTS
|
||||
|
||||
// Backup things we don't want to swap.
|
||||
edict_t oldPlayerData = *pPlayerData;
|
||||
edict_t oldBotData = *pBotData;
|
||||
|
||||
// Swap edicts.
|
||||
edict_t tmp = *pPlayerData;
|
||||
*pPlayerData = *pBotData;
|
||||
*pBotData = tmp;
|
||||
|
||||
// Restore things we didn't want to swap.
|
||||
//pPlayerData->m_EntitiesTouched = oldPlayerData.m_EntitiesTouched;
|
||||
//pBotData->m_EntitiesTouched = oldBotData.m_EntitiesTouched;
|
||||
|
||||
CBaseEntity *pPlayerBaseEnt = CBaseEntity::Instance( pPlayerData );
|
||||
CBaseEntity *pBotBaseEnt = CBaseEntity::Instance( pBotData );
|
||||
|
||||
// Make the other a bot and make the player not a bot.
|
||||
pPlayerBaseEnt->RemoveFlag( FL_FAKECLIENT );
|
||||
pBotBaseEnt->AddFlag( FL_FAKECLIENT );
|
||||
|
||||
|
||||
// Point the CBaseEntities at the right players.
|
||||
pPlayerBaseEnt->NetworkProp()->SetEdict( pPlayerData );
|
||||
pBotBaseEnt->NetworkProp()->SetEdict( pBotData );
|
||||
|
||||
// Freeze the bot.
|
||||
pBotBaseEnt->AddEFlags( EFL_BOT_FROZEN );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ConCommand cc_PossessBot( "PossessBot", PossessBot_f, "Toggle. Possess a bot.\n\tArguments: <bot client number>", FCVAR_CHEAT );
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose:
|
||||
//
|
||||
//=============================================================================//
|
||||
|
||||
#ifndef CS_BOT_TEMP_H
|
||||
#define CS_BOT_TEMP_H
|
||||
#ifdef _WIN32
|
||||
#pragma once
|
||||
#endif
|
||||
|
||||
|
||||
extern ConVar bot_mimic;
|
||||
|
||||
|
||||
#endif // CS_BOT_TEMP_H
|
||||
@@ -0,0 +1,205 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose:
|
||||
//
|
||||
// $NoKeywords: $
|
||||
//
|
||||
//=============================================================================//
|
||||
/*
|
||||
|
||||
===== tf_client.cpp ========================================================
|
||||
|
||||
HL2 client/server game specific stuff
|
||||
|
||||
*/
|
||||
|
||||
#include "cbase.h"
|
||||
#include "player.h"
|
||||
#include "gamerules.h"
|
||||
#include "entitylist.h"
|
||||
#include "physics.h"
|
||||
#include "game.h"
|
||||
#include "ai_network.h"
|
||||
#include "ai_node.h"
|
||||
#include "ai_hull.h"
|
||||
#include "shake.h"
|
||||
#include "player_resource.h"
|
||||
#include "engine/IEngineSound.h"
|
||||
#include "cs_player.h"
|
||||
#include "cs_gamerules.h"
|
||||
#include "cs_bot.h"
|
||||
#include "tier0/vprof.h"
|
||||
#include "teamplayroundbased_gamerules.h"
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
|
||||
extern CBaseEntity *FindPickerEntity( CBasePlayer *pPlayer );
|
||||
|
||||
extern bool g_fGameOver;
|
||||
|
||||
|
||||
void FinishClientPutInServer( CCSPlayer *pPlayer )
|
||||
{
|
||||
pPlayer->InitialSpawn();
|
||||
pPlayer->Spawn();
|
||||
|
||||
if (!pPlayer->IsBot())
|
||||
{
|
||||
// When the player first joins the server, they
|
||||
pPlayer->m_iNumSpawns = 0;
|
||||
pPlayer->m_takedamage = DAMAGE_NO;
|
||||
pPlayer->pl.deadflag = true;
|
||||
pPlayer->m_lifeState = LIFE_DEAD;
|
||||
pPlayer->AddEffects( EF_NODRAW );
|
||||
pPlayer->ChangeTeam( TEAM_UNASSIGNED );
|
||||
pPlayer->SetThink( NULL );
|
||||
pPlayer->AddAccount( CSGameRules()->GetStartMoney() );
|
||||
|
||||
// Move them to the first intro camera.
|
||||
pPlayer->MoveToNextIntroCamera();
|
||||
pPlayer->SetMoveType( MOVETYPE_NONE );
|
||||
}
|
||||
|
||||
|
||||
char sName[128];
|
||||
Q_strncpy( sName, pPlayer->GetPlayerName(), sizeof( sName ) );
|
||||
|
||||
// First parse the name and remove any %'s
|
||||
for ( char *pApersand = sName; pApersand != NULL && *pApersand != 0; pApersand++ )
|
||||
{
|
||||
// Replace it with a space
|
||||
if ( *pApersand == '%' )
|
||||
*pApersand = ' ';
|
||||
}
|
||||
|
||||
// notify other clients of player joining the game
|
||||
UTIL_ClientPrintAll( HUD_PRINTNOTIFY, "#Game_connected", sName[0] != 0 ? sName : "<unconnected>" );
|
||||
}
|
||||
|
||||
/*
|
||||
===========
|
||||
ClientPutInServer
|
||||
|
||||
called each time a player is spawned into the game
|
||||
============
|
||||
*/
|
||||
void ClientPutInServer( edict_t *pEdict, const char *playername )
|
||||
{
|
||||
// Allocate a CBaseTFPlayer for pev, and call spawn
|
||||
CCSPlayer *pPlayer = CCSPlayer::CreatePlayer( "player", pEdict );
|
||||
|
||||
pPlayer->SetPlayerName( playername );
|
||||
}
|
||||
|
||||
|
||||
void ClientActive( edict_t *pEdict, bool bLoadGame )
|
||||
{
|
||||
// Can't load games in CS!
|
||||
Assert( !bLoadGame );
|
||||
|
||||
CCSPlayer *pPlayer = ToCSPlayer( CBaseEntity::Instance( pEdict ) );
|
||||
FinishClientPutInServer( pPlayer );
|
||||
|
||||
CSingleUserRecipientFilter user( pPlayer );
|
||||
user.MakeReliable();
|
||||
|
||||
// send the 4 end of match conditions. long frag limit, long max rounds, long rounds needed won, and long time
|
||||
UserMessageBegin( user, "MatchEndConditions" );
|
||||
WRITE_LONG( fraglimit.GetInt() );
|
||||
WRITE_LONG( mp_maxrounds.GetInt() );
|
||||
WRITE_LONG( mp_winlimit.GetInt() );
|
||||
WRITE_LONG( mp_timelimit.GetInt() );
|
||||
MessageEnd();
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
===============
|
||||
const char *GetGameDescription()
|
||||
|
||||
Returns the descriptive name of this .dll. E.g., Half-Life, or Team Fortress 2
|
||||
===============
|
||||
*/
|
||||
const char *GetGameDescription()
|
||||
{
|
||||
if ( g_pGameRules ) // this function may be called before the world has spawned, and the game rules initialized
|
||||
return g_pGameRules->GetGameDescription();
|
||||
else
|
||||
return "Counter-Strike: Source";
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Precache game-specific models & sounds
|
||||
//-----------------------------------------------------------------------------
|
||||
void ClientGamePrecache( void )
|
||||
{
|
||||
// Materials used by the client effects
|
||||
CBaseEntity::PrecacheModel( "sprites/white.vmt" );
|
||||
CBaseEntity::PrecacheModel( "sprites/physbeam.vmt" );
|
||||
|
||||
// Legacy temp ents sounds
|
||||
CBaseEntity::PrecacheScriptSound( "Bounce.PistolShell" );
|
||||
CBaseEntity::PrecacheScriptSound( "Bounce.RifleShell" );
|
||||
CBaseEntity::PrecacheScriptSound( "Bounce.ShotgunShell" );
|
||||
|
||||
// Moved to pure_server_minimal.txt
|
||||
// // Flashbang-related files
|
||||
// engine->ForceExactFile( "sprites/white.vmt" );
|
||||
// engine->ForceExactFile( "sprites/white.vtf" );
|
||||
// engine->ForceExactFile( "vgui/white.vmt" );
|
||||
// engine->ForceExactFile( "vgui/white.vtf" );
|
||||
// engine->ForceExactFile( "effects/flashbang.vmt" );
|
||||
// engine->ForceExactFile( "effects/flashbang_white.vmt" );
|
||||
//
|
||||
// // Smoke grenade-related files
|
||||
// engine->ForceExactFile( "particle/particle_smokegrenade1.vmt" );
|
||||
// engine->ForceExactFile( "particle/particle_smokegrenade.vtf" );
|
||||
//
|
||||
// // Sniper scope
|
||||
// engine->ForceExactFile( "sprites/scope_arc.vmt" );
|
||||
// engine->ForceExactFile( "sprites/scope_arc.vtf" );
|
||||
//
|
||||
// // DSP presets - don't want people avoiding the deafening + ear ring
|
||||
// engine->ForceExactFile( "scripts/dsp_presets.txt" );
|
||||
}
|
||||
|
||||
|
||||
// called by ClientKill and DeadThink
|
||||
void respawn( CBaseEntity *pEdict, bool fCopyCorpse )
|
||||
{
|
||||
if (gpGlobals->coop || gpGlobals->deathmatch)
|
||||
{
|
||||
if ( fCopyCorpse )
|
||||
{
|
||||
// make a copy of the dead body for appearances sake
|
||||
dynamic_cast< CBasePlayer* >( pEdict )->CreateCorpse();
|
||||
}
|
||||
|
||||
// respawn player
|
||||
pEdict->Spawn();
|
||||
}
|
||||
else
|
||||
{ // restart the entire server
|
||||
engine->ServerCommand("reload\n");
|
||||
}
|
||||
}
|
||||
|
||||
void GameStartFrame( void )
|
||||
{
|
||||
VPROF( "GameStartFrame" );
|
||||
|
||||
if ( g_fGameOver )
|
||||
return;
|
||||
|
||||
gpGlobals->teamplay = teamplay.GetInt() ? true : false;
|
||||
}
|
||||
|
||||
//=========================================================
|
||||
// instantiate the proper game rules object
|
||||
//=========================================================
|
||||
void InstallGameRules()
|
||||
{
|
||||
CreateGameRulesObject( "CCSGameRules" );
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose:
|
||||
//
|
||||
//=============================================================================//
|
||||
|
||||
#ifndef CS_CLIENT_H
|
||||
#define CS_CLIENT_H
|
||||
#ifdef _WIN32
|
||||
#pragma once
|
||||
#endif
|
||||
|
||||
|
||||
void respawn( CBaseEntity *pEdict, bool fCopyCorpse );
|
||||
|
||||
void FinishClientPutInServer( CCSPlayer *pPlayer );
|
||||
|
||||
|
||||
#endif // CS_CLIENT_H
|
||||
@@ -0,0 +1,321 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose:
|
||||
//
|
||||
// $NoKeywords: $
|
||||
//
|
||||
//=============================================================================//
|
||||
#include "cbase.h"
|
||||
#include "../EventLog.h"
|
||||
#include "team.h"
|
||||
#include "cs_gamerules.h"
|
||||
#include "KeyValues.h"
|
||||
|
||||
#define LOG_DETAIL_ENEMY_ATTACKS 0x01
|
||||
#define LOG_DETAIL_TEAMMATE_ATTACKS 0x02
|
||||
|
||||
ConVar mp_logdetail( "mp_logdetail", "0", FCVAR_NONE, "Logs attacks. Values are: 0=off, 1=enemy, 2=teammate, 3=both)", true, 0.0f, true, 3.0f );
|
||||
|
||||
class CCSEventLog : public CEventLog
|
||||
{
|
||||
private:
|
||||
typedef CEventLog BaseClass;
|
||||
|
||||
public:
|
||||
bool PrintEvent( IGameEvent *event ) // override virtual function
|
||||
{
|
||||
if ( !PrintCStrikeEvent( event ) ) // allow CS to override logging
|
||||
{
|
||||
return BaseClass::PrintEvent( event );
|
||||
}
|
||||
else
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool Init()
|
||||
{
|
||||
BaseClass::Init();
|
||||
|
||||
// listen to CS events
|
||||
ListenForGameEvent( "round_end" );
|
||||
ListenForGameEvent( "round_start" );
|
||||
ListenForGameEvent( "bomb_pickup" );
|
||||
ListenForGameEvent( "bomb_begindefuse" );
|
||||
ListenForGameEvent( "bomb_dropped" );
|
||||
ListenForGameEvent( "bomb_defused" );
|
||||
ListenForGameEvent( "bomb_planted" );
|
||||
ListenForGameEvent( "hostage_rescued" );
|
||||
ListenForGameEvent( "hostage_killed" );
|
||||
ListenForGameEvent( "hostage_follows" );
|
||||
ListenForGameEvent( "player_hurt" );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
bool PrintCStrikeEvent( IGameEvent *event ) // print Mod specific logs
|
||||
{
|
||||
const char *eventName = event->GetName();
|
||||
|
||||
// messages that don't have a user associated to them
|
||||
if ( !Q_strncmp( eventName, "round_end", Q_strlen("round_end") ) )
|
||||
{
|
||||
const int winner = event->GetInt( "winner" );
|
||||
const int reason = event->GetInt( "reason" );
|
||||
const char *msg = event->GetString( "message" );
|
||||
msg++; // remove the '#' char
|
||||
|
||||
switch( reason )
|
||||
{
|
||||
case Game_Commencing:
|
||||
UTIL_LogPrintf( "World triggered \"Game_Commencing\"\n" );
|
||||
return true;
|
||||
break;
|
||||
}
|
||||
|
||||
CTeam *ct = GetGlobalTeam( TEAM_CT );
|
||||
CTeam *ter = GetGlobalTeam( TEAM_TERRORIST );
|
||||
Assert( ct && ter );
|
||||
|
||||
switch ( winner )
|
||||
{
|
||||
case WINNER_CT:
|
||||
UTIL_LogPrintf( "Team \"%s\" triggered \"%s\" (CT \"%i\") (T \"%i\")\n", ct->GetName(), msg, ct->GetScore(), ter->GetScore() );
|
||||
break;
|
||||
case WINNER_TER:
|
||||
UTIL_LogPrintf( "Team \"%s\" triggered \"%s\" (CT \"%i\") (T \"%i\")\n", ter->GetName(), msg, ct->GetScore(), ter->GetScore() );
|
||||
break;
|
||||
case WINNER_DRAW:
|
||||
default:
|
||||
UTIL_LogPrintf( "World triggered \"%s\" (CT \"%i\") (T \"%i\")\n", msg, ct->GetScore(), ter->GetScore() );
|
||||
break;
|
||||
}
|
||||
|
||||
UTIL_LogPrintf( "Team \"CT\" scored \"%i\" with \"%i\" players\n", ct->GetScore(), ct->GetNumPlayers() );
|
||||
UTIL_LogPrintf( "Team \"TERRORIST\" scored \"%i\" with \"%i\" players\n", ter->GetScore(), ter->GetNumPlayers() );
|
||||
|
||||
UTIL_LogPrintf("World triggered \"Round_End\"\n");
|
||||
return true;
|
||||
}
|
||||
else if ( !Q_strncmp( eventName, "server_", strlen("server_")) )
|
||||
{
|
||||
return false; // ignore server_ messages
|
||||
}
|
||||
|
||||
const int userid = event->GetInt( "userid" );
|
||||
CBasePlayer *pPlayer = UTIL_PlayerByUserId( userid );
|
||||
if ( !pPlayer )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( FStrEq( eventName, "player_hurt" ) )
|
||||
{
|
||||
const int attackerid = event->GetInt("attacker" );
|
||||
const char *weapon = event->GetString( "weapon" );
|
||||
CBasePlayer *pAttacker = UTIL_PlayerByUserId( attackerid );
|
||||
if ( !pAttacker )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool isTeamAttack = ( (pPlayer->GetTeamNumber() == pAttacker->GetTeamNumber() ) && (pPlayer != pAttacker) );
|
||||
int detail = mp_logdetail.GetInt();
|
||||
if ( ( isTeamAttack && ( detail & LOG_DETAIL_TEAMMATE_ATTACKS ) ) ||
|
||||
( !isTeamAttack && ( detail & LOG_DETAIL_ENEMY_ATTACKS ) ) )
|
||||
{
|
||||
int hitgroup = event->GetInt( "hitgroup" );
|
||||
const char *hitgroupStr = "GENERIC";
|
||||
switch ( hitgroup )
|
||||
{
|
||||
case HITGROUP_GENERIC:
|
||||
hitgroupStr = "generic";
|
||||
break;
|
||||
case HITGROUP_HEAD:
|
||||
hitgroupStr = "head";
|
||||
break;
|
||||
case HITGROUP_CHEST:
|
||||
hitgroupStr = "chest";
|
||||
break;
|
||||
case HITGROUP_STOMACH:
|
||||
hitgroupStr = "stomach";
|
||||
break;
|
||||
case HITGROUP_LEFTARM:
|
||||
hitgroupStr = "left arm";
|
||||
break;
|
||||
case HITGROUP_RIGHTARM:
|
||||
hitgroupStr = "right arm";
|
||||
break;
|
||||
case HITGROUP_LEFTLEG:
|
||||
hitgroupStr = "left leg";
|
||||
break;
|
||||
case HITGROUP_RIGHTLEG:
|
||||
hitgroupStr = "right leg";
|
||||
break;
|
||||
}
|
||||
|
||||
UTIL_LogPrintf( "\"%s<%i><%s><%s>\" attacked \"%s<%i><%s><%s>\" with \"%s\" (damage \"%d\") (damage_armor \"%d\") (health \"%d\") (armor \"%d\") (hitgroup \"%s\")\n",
|
||||
pAttacker->GetPlayerName(),
|
||||
attackerid,
|
||||
pAttacker->GetNetworkIDString(),
|
||||
pAttacker->GetTeam()->GetName(),
|
||||
pPlayer->GetPlayerName(),
|
||||
userid,
|
||||
pPlayer->GetNetworkIDString(),
|
||||
pPlayer->GetTeam()->GetName(),
|
||||
weapon,
|
||||
event->GetInt( "dmg_health" ),
|
||||
event->GetInt( "dmg_armor" ),
|
||||
event->GetInt( "health" ),
|
||||
event->GetInt( "armor" ),
|
||||
hitgroupStr );
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else if ( !Q_strncmp( eventName, "player_death", Q_strlen("player_death") ) )
|
||||
{
|
||||
const int attackerid = event->GetInt("attacker" );
|
||||
const char *weapon = event->GetString( "weapon" );
|
||||
const bool headShot = (event->GetInt( "headshot" ) == 1);
|
||||
CBasePlayer *pAttacker = UTIL_PlayerByUserId( attackerid );
|
||||
|
||||
if ( pPlayer == pAttacker )
|
||||
{
|
||||
UTIL_LogPrintf( "\"%s<%i><%s><%s>\" committed suicide with \"%s\"\n",
|
||||
pPlayer->GetPlayerName(),
|
||||
userid,
|
||||
pPlayer->GetNetworkIDString(),
|
||||
pPlayer->GetTeam()->GetName(),
|
||||
weapon
|
||||
);
|
||||
}
|
||||
else if ( pAttacker )
|
||||
{
|
||||
UTIL_LogPrintf( "\"%s<%i><%s><%s>\" killed \"%s<%i><%s><%s>\" with \"%s\"%s\n",
|
||||
pAttacker->GetPlayerName(),
|
||||
attackerid,
|
||||
pAttacker->GetNetworkIDString(),
|
||||
pAttacker->GetTeam()->GetName(),
|
||||
pPlayer->GetPlayerName(),
|
||||
userid,
|
||||
pPlayer->GetNetworkIDString(),
|
||||
pPlayer->GetTeam()->GetName(),
|
||||
weapon,
|
||||
headShot ? " (headshot)":""
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
// killed by the world
|
||||
UTIL_LogPrintf( "\"%s<%i><%s><%s>\" committed suicide with \"world\"\n",
|
||||
pPlayer->GetPlayerName(),
|
||||
userid,
|
||||
pPlayer->GetNetworkIDString(),
|
||||
pPlayer->GetTeam()->GetName()
|
||||
);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else if ( !Q_strncmp( eventName, "round_start", Q_strlen("round_start") ) )
|
||||
{
|
||||
UTIL_LogPrintf("World triggered \"Round_Start\"\n");
|
||||
return true;
|
||||
}
|
||||
else if ( !Q_strncmp( eventName, "hostage_follows", Q_strlen("hostage_follows") ) )
|
||||
{
|
||||
UTIL_LogPrintf( "\"%s<%i><%s><CT>\" triggered \"Touched_A_Hostage\"\n",
|
||||
pPlayer->GetPlayerName(),
|
||||
userid,
|
||||
pPlayer->GetNetworkIDString()
|
||||
);
|
||||
return true;
|
||||
}
|
||||
else if ( !Q_strncmp( eventName, "hostage_killed", Q_strlen("hostage_killed") ) )
|
||||
{
|
||||
UTIL_LogPrintf( "\"%s<%i><%s><%s>\" triggered \"Killed_A_Hostage\"\n",
|
||||
pPlayer->GetPlayerName(),
|
||||
userid,
|
||||
pPlayer->GetNetworkIDString(),
|
||||
pPlayer->GetTeam()->GetName()
|
||||
);
|
||||
return true;
|
||||
}
|
||||
else if ( !Q_strncmp( eventName, "hostage_rescued", Q_strlen("hostage_rescued") ) )
|
||||
{
|
||||
UTIL_LogPrintf("\"%s<%i><%s><CT>\" triggered \"Rescued_A_Hostage\"\n",
|
||||
pPlayer->GetPlayerName(),
|
||||
userid,
|
||||
pPlayer->GetNetworkIDString()
|
||||
);
|
||||
return true;
|
||||
}
|
||||
else if ( !Q_strncmp( eventName, "bomb_planted", Q_strlen("bomb_planted") ) )
|
||||
{
|
||||
UTIL_LogPrintf("\"%s<%i><%s><TERRORIST>\" triggered \"Planted_The_Bomb\"\n",
|
||||
pPlayer->GetPlayerName(),
|
||||
userid,
|
||||
pPlayer->GetNetworkIDString()
|
||||
);
|
||||
return true;
|
||||
}
|
||||
else if ( !Q_strncmp( eventName, "bomb_defused", Q_strlen("bomb_defused") ) )
|
||||
{
|
||||
UTIL_LogPrintf("\"%s<%i><%s><CT>\" triggered \"Defused_The_Bomb\"\n",
|
||||
pPlayer->GetPlayerName(),
|
||||
userid,
|
||||
pPlayer->GetNetworkIDString()
|
||||
);
|
||||
return true;
|
||||
}
|
||||
else if ( !Q_strncmp( eventName, "bomb_dropped", Q_strlen("bomb_dropped") ) )
|
||||
{
|
||||
UTIL_LogPrintf("\"%s<%i><%s><TERRORIST>\" triggered \"Dropped_The_Bomb\"\n",
|
||||
pPlayer->GetPlayerName(),
|
||||
userid,
|
||||
pPlayer->GetNetworkIDString()
|
||||
);
|
||||
return true;
|
||||
}
|
||||
else if ( !Q_strncmp( eventName, "bomb_begindefuse", Q_strlen("bomb_begindefuse") ) )
|
||||
{
|
||||
const bool haskit = (event->GetInt( "haskit" ) == 1);
|
||||
UTIL_LogPrintf("\"%s<%i><%s><CT>\" triggered \"%s\"\n",
|
||||
pPlayer->GetPlayerName(),
|
||||
userid,
|
||||
pPlayer->GetNetworkIDString(),
|
||||
haskit ? "Begin_Bomb_Defuse_With_Kit" : "Begin_Bomb_Defuse_Without_Kit"
|
||||
);
|
||||
return true;
|
||||
}
|
||||
else if ( !Q_strncmp( eventName, "bomb_pickup", Q_strlen("bomb_pickup") ) )
|
||||
{
|
||||
UTIL_LogPrintf("\"%s<%i><%s><TERRORIST>\" triggered \"Got_The_Bomb\"\n",
|
||||
pPlayer->GetPlayerName(),
|
||||
userid,
|
||||
pPlayer->GetNetworkIDString()
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
// unused events:
|
||||
//hostage_hurt
|
||||
//bomb_exploded
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
CCSEventLog g_CSEventLog;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Singleton access
|
||||
//-----------------------------------------------------------------------------
|
||||
IGameSystem* GameLogSystem()
|
||||
{
|
||||
return &g_CSEventLog;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose:
|
||||
//
|
||||
//=============================================================================//
|
||||
|
||||
#include "cbase.h"
|
||||
#include "gameinterface.h"
|
||||
#include "mapentities.h"
|
||||
#include "cs_gameinterface.h"
|
||||
#include "AI_ResponseSystem.h"
|
||||
|
||||
// -------------------------------------------------------------------------------------------- //
|
||||
// Mod-specific CServerGameClients implementation.
|
||||
// -------------------------------------------------------------------------------------------- //
|
||||
|
||||
void CServerGameClients::GetPlayerLimits( int& minplayers, int& maxplayers, int &defaultMaxPlayers ) const
|
||||
{
|
||||
minplayers = 1; // allow single player for the test maps (but we default to multi)
|
||||
maxplayers = MAX_PLAYERS;
|
||||
|
||||
defaultMaxPlayers = 32; // Default to 32 players unless they change it.
|
||||
}
|
||||
|
||||
|
||||
// -------------------------------------------------------------------------------------------- //
|
||||
// Mod-specific CServerGameDLL implementation.
|
||||
// -------------------------------------------------------------------------------------------- //
|
||||
|
||||
void CServerGameDLL::LevelInit_ParseAllEntities( const char *pMapEntities )
|
||||
{
|
||||
if ( Q_strcmp( STRING(gpGlobals->mapname), "cs_" ) )
|
||||
{
|
||||
// don't precache AI responses (hostages) if it's not a hostage rescure map
|
||||
extern IResponseSystem *g_pResponseSystem;
|
||||
g_pResponseSystem->PrecacheResponses( false );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose:
|
||||
//
|
||||
//=============================================================================//
|
||||
|
||||
#ifndef CS_GAMEINTERFACE_H
|
||||
#define CS_GAMEINTERFACE_H
|
||||
#ifdef _WIN32
|
||||
#pragma once
|
||||
#endif
|
||||
|
||||
|
||||
#endif // CS_GAMEINTERFACE_H
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,340 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose: The CS game stats header
|
||||
//
|
||||
// $NoKeywords: $
|
||||
//=============================================================================//
|
||||
|
||||
#ifndef CS_GAMESTATS_H
|
||||
#define CS_GAMESTATS_H
|
||||
#ifdef _WIN32
|
||||
#pragma once
|
||||
#endif
|
||||
|
||||
#include "cs_blackmarket.h"
|
||||
#include "gamestats.h"
|
||||
#include "cs_gamestats_shared.h"
|
||||
#include "GameEventListener.h"
|
||||
#include "steamworks_gamestats.h"
|
||||
#include "weapon_csbase.h"
|
||||
|
||||
// forward declares
|
||||
class CBreakableProp;
|
||||
|
||||
#define CS_STATS_BLOB_VERSION 3
|
||||
|
||||
const float cDisseminationTimeHigh = 0.25f; // Time interval for high priority stats sent to the player
|
||||
const float cDisseminationTimeLow = 2.5f; // Time interval for medium priority stats sent to the player
|
||||
|
||||
int GetCSLevelIndex( const char *pLevelName );
|
||||
|
||||
typedef struct
|
||||
{
|
||||
char szGameName[8];
|
||||
byte iVersion;
|
||||
char szMapName[32];
|
||||
char ipAddr[4];
|
||||
short port;
|
||||
int serverid;
|
||||
} gamestats_header_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
gamestats_header_t header;
|
||||
short iMinutesPlayed;
|
||||
|
||||
short iTerroristVictories[CS_NUM_LEVELS];
|
||||
short iCounterTVictories[CS_NUM_LEVELS];
|
||||
short iBlackMarketPurchases[WEAPON_MAX];
|
||||
|
||||
short iAutoBuyPurchases;
|
||||
short iReBuyPurchases;
|
||||
short iAutoBuyM4A1Purchases;
|
||||
short iAutoBuyAK47Purchases;
|
||||
short iAutoBuyFamasPurchases;
|
||||
short iAutoBuyGalilPurchases;
|
||||
short iAutoBuyVestHelmPurchases;
|
||||
short iAutoBuyVestPurchases;
|
||||
|
||||
} cs_gamestats_t;
|
||||
|
||||
extern short g_iWeaponPurchases[WEAPON_MAX];
|
||||
extern float g_flGameStatsUpdateTime;
|
||||
extern short g_iTerroristVictories[CS_NUM_LEVELS];
|
||||
extern short g_iCounterTVictories[CS_NUM_LEVELS];
|
||||
extern short g_iWeaponPurchases[WEAPON_MAX];
|
||||
|
||||
extern short g_iAutoBuyPurchases;
|
||||
extern short g_iReBuyPurchases;
|
||||
extern short g_iAutoBuyM4A1Purchases;
|
||||
extern short g_iAutoBuyAK47Purchases;
|
||||
extern short g_iAutoBuyFamasPurchases;
|
||||
extern short g_iAutoBuyGalilPurchases;
|
||||
extern short g_iAutoBuyVestHelmPurchases;
|
||||
extern short g_iAutoBuyVestPurchases;
|
||||
|
||||
|
||||
struct sHappyCamperSnipePosition
|
||||
{
|
||||
sHappyCamperSnipePosition( int userid, Vector pos ) : m_iUserID(userid), m_vPos(pos) {}
|
||||
|
||||
int m_iUserID;
|
||||
Vector m_vPos;
|
||||
};
|
||||
|
||||
struct SMarketPurchases : public BaseStatData
|
||||
{
|
||||
SMarketPurchases( uint64 ulPlayerID, int iPrice, const char *pName ) : m_nPlayerID(ulPlayerID), ItemCost(iPrice)
|
||||
{
|
||||
if ( pName )
|
||||
{
|
||||
Q_strncpy( ItemID, pName, ARRAYSIZE(ItemID) );
|
||||
}
|
||||
else
|
||||
{
|
||||
Q_strncpy( ItemID, "unknown", ARRAYSIZE(ItemID) );
|
||||
}
|
||||
}
|
||||
uint64 m_nPlayerID;
|
||||
int ItemCost;
|
||||
char ItemID[64];
|
||||
|
||||
BEGIN_STAT_TABLE( "CSSMarketPurchase" )
|
||||
REGISTER_STAT( m_nPlayerID )
|
||||
REGISTER_STAT( ItemCost )
|
||||
REGISTER_STAT_STRING( ItemID )
|
||||
END_STAT_TABLE()
|
||||
};
|
||||
typedef CUtlVector< SMarketPurchases* > VectorMarketPurchaseData;
|
||||
|
||||
struct WeaponStats
|
||||
{
|
||||
int shots;
|
||||
int hits;
|
||||
int kills;
|
||||
int damage;
|
||||
};
|
||||
|
||||
struct SCSSWeaponData : public BaseStatData
|
||||
{
|
||||
SCSSWeaponData( const char *pWeaponName, const WeaponStats &wpnData )
|
||||
{
|
||||
if ( pWeaponName )
|
||||
{
|
||||
Q_strncpy( WeaponID, pWeaponName, ARRAYSIZE(WeaponID) );
|
||||
}
|
||||
else
|
||||
{
|
||||
Q_strncpy( WeaponID, "unknown", ARRAYSIZE(WeaponID) );
|
||||
}
|
||||
|
||||
Shots = wpnData.shots;
|
||||
Hits = wpnData.hits;
|
||||
Kills = wpnData.kills;
|
||||
Damage = wpnData.damage;
|
||||
}
|
||||
|
||||
char WeaponID[64];
|
||||
int Shots;
|
||||
int Hits;
|
||||
int Kills;
|
||||
int Damage;
|
||||
|
||||
BEGIN_STAT_TABLE( "CSSWeaponData" )
|
||||
REGISTER_STAT_STRING( WeaponID )
|
||||
REGISTER_STAT( Shots )
|
||||
REGISTER_STAT( Hits )
|
||||
REGISTER_STAT( Kills )
|
||||
REGISTER_STAT( Damage )
|
||||
END_STAT_TABLE()
|
||||
};
|
||||
typedef CUtlVector< SCSSWeaponData* > CSSWeaponData;
|
||||
|
||||
struct SCSSDeathData : public BaseStatData
|
||||
{
|
||||
SCSSDeathData( CBasePlayer *pVictim, const CTakeDamageInfo &info )
|
||||
{
|
||||
m_bUseGlobalData = false;
|
||||
|
||||
m_DeathPos = info.GetDamagePosition();
|
||||
m_iVictimTeam = pVictim->GetTeamNumber();
|
||||
|
||||
CCSPlayer *pCSPlayer = ToCSPlayer( info.GetAttacker() );
|
||||
if ( pCSPlayer )
|
||||
{
|
||||
m_iKillerTeam = pCSPlayer->GetTeamNumber();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_iKillerTeam = -1;
|
||||
}
|
||||
|
||||
const char *pszWeaponName = info.GetInflictor() ? info.GetInflictor()->GetClassname() : "unknown";
|
||||
|
||||
if ( pszWeaponName )
|
||||
{
|
||||
if ( V_strcmp(pszWeaponName, "player") == 0 )
|
||||
{
|
||||
// get the player's weapon
|
||||
if ( pCSPlayer && pCSPlayer->GetActiveCSWeapon() )
|
||||
{
|
||||
pszWeaponName = pCSPlayer->GetActiveCSWeapon()->GetClassname();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
m_uiDeathParam = WEAPON_NONE;
|
||||
if ( (m_uiDeathParam = AliasToWeaponID( pszWeaponName )) == WEAPON_NONE )
|
||||
{
|
||||
m_uiDeathParam = AliasToWeaponID( pszWeaponName );
|
||||
}
|
||||
|
||||
const char *pszMapName = gpGlobals->mapname.ToCStr() ? gpGlobals->mapname.ToCStr() : "unknown";
|
||||
Q_strncpy( m_szMapName, pszMapName, ARRAYSIZE(m_szMapName) );
|
||||
}
|
||||
Vector m_DeathPos;
|
||||
int m_iVictimTeam;
|
||||
int m_iKillerTeam;
|
||||
int m_iDamageType;
|
||||
uint64 m_uiDeathParam;
|
||||
char m_szMapName[64];
|
||||
|
||||
BEGIN_STAT_TABLE( "Deaths" )
|
||||
REGISTER_STAT_NAMED( m_DeathPos.x, "XCoord" )
|
||||
REGISTER_STAT_NAMED( m_DeathPos.y, "YCoord" )
|
||||
REGISTER_STAT_NAMED( m_DeathPos.z, "ZCoord" )
|
||||
REGISTER_STAT_NAMED( m_iVictimTeam, "Team" )
|
||||
REGISTER_STAT_NAMED( m_iKillerTeam, "DeathCause" )
|
||||
REGISTER_STAT_NAMED( m_uiDeathParam, "DeathParam" )
|
||||
REGISTER_STAT_NAMED( m_szMapName, "DeathMap" )
|
||||
END_STAT_TABLE()
|
||||
};
|
||||
typedef CUtlVector< SCSSDeathData* > CSSDeathData;
|
||||
|
||||
//=============================================================================
|
||||
//
|
||||
// CS Game Stats Class
|
||||
//
|
||||
class CCSGameStats : public CBaseGameStats, public CGameEventListener, public CAutoGameSystemPerFrame, public IGameStatTracker
|
||||
{
|
||||
public:
|
||||
|
||||
// Constructor/Destructor.
|
||||
CCSGameStats( void );
|
||||
~CCSGameStats( void );
|
||||
|
||||
virtual void Clear( void );
|
||||
virtual bool Init();
|
||||
virtual void PreClientUpdate();
|
||||
virtual void PostInit();
|
||||
virtual void LevelShutdownPreClearSteamAPIContext();
|
||||
|
||||
void UploadRoundStats( void );
|
||||
// Overridden events
|
||||
virtual void Event_LevelInit( void );
|
||||
virtual void Event_LevelShutdown( float flElapsed );
|
||||
virtual void Event_ShotFired( CBasePlayer *pPlayer, CBaseCombatWeapon* pWeapon );
|
||||
virtual void Event_ShotHit( CBasePlayer *pPlayer, const CTakeDamageInfo &info );
|
||||
virtual void Event_PlayerKilled( CBasePlayer *pPlayer, const CTakeDamageInfo &info );
|
||||
virtual void Event_PlayerKilled_PreWeaponDrop( CBasePlayer *pPlayer, const CTakeDamageInfo &info );
|
||||
void UpdatePlayerRoundStats(int winner);
|
||||
virtual void Event_PlayerConnected( CBasePlayer *pPlayer );
|
||||
virtual void Event_PlayerDisconnected( CBasePlayer *pPlayer );
|
||||
virtual void Event_WindowShattered( CBasePlayer *pPlayer );
|
||||
virtual void Event_PlayerKilledOther( CBasePlayer *pAttacker, CBaseEntity *pVictim, const CTakeDamageInfo &info );
|
||||
|
||||
|
||||
// CSS specific events
|
||||
void Event_BombPlanted( CCSPlayer *pPlayer );
|
||||
void Event_BombDefused( CCSPlayer *pPlayer );
|
||||
void Event_PlayerDamage( CBasePlayer *pBasePlayer, const CTakeDamageInfo &info );
|
||||
void Event_BombExploded( CCSPlayer *pPlayer );
|
||||
void Event_MoneyEarned( CCSPlayer *pPlayer, int moneyEarned );
|
||||
void Event_MoneySpent( CCSPlayer *pPlayer, int moneySpent, const char *pItemName );
|
||||
void Event_HostageRescued( CCSPlayer *pPlayer );
|
||||
void Event_PlayerSprayedDecal( CCSPlayer*pPlayer );
|
||||
void Event_AllHostagesRescued();
|
||||
void Event_BreakProp( CCSPlayer *pPlayer, CBreakableProp *pProp );
|
||||
void Event_PlayerDonatedWeapon (CCSPlayer* pPlayer);
|
||||
void Event_PlayerDominatedOther( CCSPlayer* pAttacker, CCSPlayer* pVictim);
|
||||
void Event_PlayerRevenge( CCSPlayer* pAttacker );
|
||||
void Event_PlayerAvengedTeammate( CCSPlayer* pAttacker, CCSPlayer* pAvengedPlayer );
|
||||
void Event_MVPEarned( CCSPlayer* pPlayer );
|
||||
void Event_KnifeUse( CCSPlayer* pPlayer, bool bStab, int iDamage );
|
||||
|
||||
virtual void FireGameEvent( IGameEvent *event );
|
||||
|
||||
void DumpMatchWeaponMetrics();
|
||||
|
||||
const PlayerStats_t& FindPlayerStats( CBasePlayer *pPlayer ) const;
|
||||
void ResetPlayerStats( CBasePlayer *pPlayer );
|
||||
void ResetKillHistory( CBasePlayer *pPlayer );
|
||||
void ResetRoundStats();
|
||||
void ResetPlayerClassMatchStats();
|
||||
|
||||
const StatsCollection_t& GetTeamStats( int iTeamIndex ) const;
|
||||
void ResetAllTeamStats();
|
||||
void ResetAllStats();
|
||||
void ResetWeaponStats();
|
||||
void IncrementTeamStat( int iTeamIndex, int iStatIndex, int iAmount );
|
||||
void CalcDominationAndRevenge( CCSPlayer *pAttacker, CCSPlayer *pVictim, int *piDeathFlags );
|
||||
void CalculateOverkill( CCSPlayer* pAttacker, CCSPlayer* pVictim );
|
||||
void PlayerKilled( CBasePlayer *pVictim, const CTakeDamageInfo &info );
|
||||
|
||||
void IncrementStat( CCSPlayer* pPlayer, CSStatType_t statId, int iValue, bool bPlayerOnly = false );
|
||||
// Steamworks Gamestats
|
||||
virtual void SubmitGameStats( KeyValues *pKV );
|
||||
|
||||
virtual StatContainerList_t* GetStatContainerList( void )
|
||||
{
|
||||
return s_StatLists;
|
||||
}
|
||||
|
||||
protected:
|
||||
void SetStat( CCSPlayer *pPlayer, CSStatType_t statId, int iValue );
|
||||
void TrackKillStats( CCSPlayer *pAttacker, CCSPlayer *pVictim );
|
||||
void ComputeRollingStatAverages();
|
||||
void ComputeDirectStatAverages();
|
||||
void SendRollingStatsAveragesToAllPlayers();
|
||||
void SendDirectStatsAveragesToAllPlayers();
|
||||
void SendStatsToPlayer( CCSPlayer * pPlayer, int iMinStatPriority );
|
||||
|
||||
private:
|
||||
PlayerStats_t m_aPlayerStats[MAX_PLAYERS+1]; // List of stats for each player for current life - reset after each death
|
||||
StatsCollection_t m_aTeamStats[TEAM_MAXCOUNT - FIRST_GAME_TEAM];
|
||||
|
||||
RoundStatsRollingAverage_t m_rollingCTStatAverages;
|
||||
RoundStatsRollingAverage_t m_rollingTStatAverages;
|
||||
RoundStatsRollingAverage_t m_rollingPlayerStatAverages;
|
||||
|
||||
RoundStatsDirectAverage_t m_directCTStatAverages;
|
||||
RoundStatsDirectAverage_t m_directTStatAverages;
|
||||
RoundStatsDirectAverage_t m_directPlayerStatAverages;
|
||||
|
||||
float m_fDisseminationTimerLow; // how long since last medium priority stat update
|
||||
float m_fDisseminationTimerHigh; // how long since last high priority stat update
|
||||
|
||||
int m_numberOfRoundsForDirectAverages;
|
||||
int m_numberOfTerroristEntriesForDirectAverages;
|
||||
int m_numberOfCounterTerroristEntriesForDirectAverages;
|
||||
|
||||
CUtlDict< CSStatType_t, short > m_PropStatTable;
|
||||
|
||||
CUtlLinkedList<sHappyCamperSnipePosition, int> m_PlayerSnipedPosition;
|
||||
WeaponStats m_weaponStats[WEAPON_MAX][2];
|
||||
|
||||
// Steamworks Gamestats
|
||||
VectorMarketPurchaseData m_MarketPurchases;
|
||||
CSSWeaponData m_WeaponData;
|
||||
CSSDeathData m_DeathData;
|
||||
|
||||
// A static list of all the stat containers, one for each data structure being tracked
|
||||
static StatContainerList_t * s_StatLists;
|
||||
|
||||
bool m_bInRound;
|
||||
};
|
||||
|
||||
extern CCSGameStats CCS_GameStats;
|
||||
|
||||
#endif // CS_GAMESTATS_H
|
||||
@@ -0,0 +1,149 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose:
|
||||
//
|
||||
// $NoKeywords: $
|
||||
//
|
||||
//=============================================================================//
|
||||
#include "cbase.h"
|
||||
#include "hltvdirector.h"
|
||||
#include "igameevents.h"
|
||||
|
||||
class CCSHLTVDirector : public CHLTVDirector
|
||||
{
|
||||
public:
|
||||
DECLARE_CLASS( CCSHLTVDirector, CHLTVDirector );
|
||||
|
||||
const char** GetModEvents();
|
||||
void SetHLTVServer( IHLTVServer *hltv );
|
||||
void CreateShotFromEvent( CHLTVGameEvent *event );
|
||||
|
||||
};
|
||||
|
||||
void CCSHLTVDirector::SetHLTVServer( IHLTVServer *hltv )
|
||||
{
|
||||
BaseClass::SetHLTVServer( hltv );
|
||||
|
||||
if ( m_pHLTVServer )
|
||||
{
|
||||
// mod specific events the director uses to find interesting shots
|
||||
ListenForGameEvent( "hostage_rescued" );
|
||||
ListenForGameEvent( "hostage_killed" );
|
||||
ListenForGameEvent( "hostage_hurt" );
|
||||
ListenForGameEvent( "hostage_follows" );
|
||||
ListenForGameEvent( "bomb_pickup" );
|
||||
ListenForGameEvent( "bomb_dropped" );
|
||||
ListenForGameEvent( "bomb_exploded" );
|
||||
ListenForGameEvent( "bomb_defused" );
|
||||
ListenForGameEvent( "bomb_planted" );
|
||||
ListenForGameEvent( "vip_escaped" );
|
||||
ListenForGameEvent( "vip_killed" );
|
||||
}
|
||||
}
|
||||
|
||||
void CCSHLTVDirector::CreateShotFromEvent( CHLTVGameEvent *event )
|
||||
{
|
||||
// show event at least for 2 more seconds after it occured
|
||||
const char *name = event->m_Event->GetName();
|
||||
IGameEvent *shot = NULL;
|
||||
|
||||
if ( !Q_strcmp( "hostage_rescued", name ) ||
|
||||
!Q_strcmp( "hostage_hurt", name ) ||
|
||||
!Q_strcmp( "hostage_follows", name ) ||
|
||||
!Q_strcmp( "hostage_killed", name ) )
|
||||
{
|
||||
CBaseEntity *player = UTIL_PlayerByUserId( event->m_Event->GetInt("userid") );
|
||||
|
||||
if ( !player )
|
||||
return;
|
||||
|
||||
// shot player as primary, hostage as secondary target
|
||||
shot = gameeventmanager->CreateEvent( "hltv_chase", true );
|
||||
shot->SetInt( "target1", player->entindex() );
|
||||
shot->SetInt( "target2", event->m_Event->GetInt("hostage") );
|
||||
shot->SetFloat( "distance", 96.0f );
|
||||
shot->SetInt( "theta", 40 );
|
||||
shot->SetInt( "phi", 20 );
|
||||
|
||||
// shot 2 seconds after event
|
||||
m_nNextShotTick = MIN( m_nNextShotTick, (event->m_Tick+TIME_TO_TICKS(2.0)) );
|
||||
m_iPVSEntity = player->entindex();
|
||||
}
|
||||
|
||||
else if ( !Q_strcmp( "bomb_pickup", name ) ||
|
||||
!Q_strcmp( "bomb_dropped", name ) ||
|
||||
!Q_strcmp( "bomb_planted", name ) ||
|
||||
!Q_strcmp( "bomb_defused", name ) )
|
||||
{
|
||||
CBaseEntity *player = UTIL_PlayerByUserId( event->m_Event->GetInt("userid") );
|
||||
|
||||
if ( !player )
|
||||
return;
|
||||
|
||||
shot = gameeventmanager->CreateEvent( "hltv_chase", true );
|
||||
shot->SetInt( "target1", player->entindex() );
|
||||
shot->SetInt( "target2", 0 );
|
||||
shot->SetFloat( "distance", 64.0f );
|
||||
shot->SetInt( "theta", 200 );
|
||||
shot->SetInt( "phi", 10 );
|
||||
|
||||
// shot 2 seconds after pickup
|
||||
m_nNextShotTick = MIN( m_nNextShotTick, (event->m_Tick+TIME_TO_TICKS(2.0)) );
|
||||
m_iPVSEntity = player->entindex();
|
||||
}
|
||||
else
|
||||
{
|
||||
// let baseclass create a shot
|
||||
BaseClass::CreateShotFromEvent( event );
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ( shot )
|
||||
{
|
||||
m_pHLTVServer->BroadcastEvent( shot );
|
||||
gameeventmanager->FreeEvent( shot );
|
||||
DevMsg("DrcCmd: %s\n", name );
|
||||
}
|
||||
}
|
||||
|
||||
const char** CCSHLTVDirector::GetModEvents()
|
||||
{
|
||||
// game events relayed to spectator clients
|
||||
static const char *s_modevents[] =
|
||||
{
|
||||
"hltv_status",
|
||||
"hltv_chat",
|
||||
"player_connect",
|
||||
"player_disconnect",
|
||||
"player_team",
|
||||
"player_info",
|
||||
"server_cvar",
|
||||
"player_death",
|
||||
"player_chat",
|
||||
"round_start",
|
||||
"round_end",
|
||||
// additional CS:S events:
|
||||
"bomb_planted",
|
||||
"bomb_defused",
|
||||
"hostage_killed",
|
||||
"hostage_hurt",
|
||||
NULL
|
||||
};
|
||||
|
||||
return s_modevents;
|
||||
}
|
||||
|
||||
static CCSHLTVDirector s_HLTVDirector; // singleton
|
||||
|
||||
EXPOSE_SINGLE_INTERFACE_GLOBALVAR(CHLTVDirector, IHLTVDirector, INTERFACEVERSION_HLTVDIRECTOR, s_HLTVDirector );
|
||||
|
||||
CHLTVDirector* HLTVDirector()
|
||||
{
|
||||
return &s_HLTVDirector;
|
||||
}
|
||||
|
||||
IGameSystem* HLTVDirectorSystem()
|
||||
{
|
||||
return &s_HLTVDirector;
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose:
|
||||
//
|
||||
// $NoKeywords: $
|
||||
//
|
||||
//=============================================================================//
|
||||
// nav.h
|
||||
// Data structures and constants for the Navigation Mesh system
|
||||
// Author: Michael S. Booth (mike@turtlerockstudios.com), January 2003
|
||||
|
||||
#ifndef _CS_NAV_H_
|
||||
#define _CS_NAV_H_
|
||||
|
||||
#include "nav.h"
|
||||
|
||||
/**
|
||||
* Below are several constants used by the navigation system.
|
||||
* @todo Move these into TheNavMesh singleton.
|
||||
*/
|
||||
const float BotRadius = 10.0f; ///< circular extent that contains bot
|
||||
|
||||
class CNavArea;
|
||||
class CSNavNode;
|
||||
|
||||
#if 0
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Return true if given entity can be ignored when moving
|
||||
*/
|
||||
#define WALK_THRU_DOORS 0x01
|
||||
#define WALK_THRU_BREAKABLES 0x02
|
||||
#define WALK_THRU_TOGGLE_BRUSHES 0x04
|
||||
#define WALK_THRU_EVERYTHING (WALK_THRU_DOORS | WALK_THRU_BREAKABLES | WALK_THRU_TOGGLE_BRUSHES)
|
||||
inline bool IsEntityWalkable( CBaseEntity *entity, unsigned int flags )
|
||||
{
|
||||
if (FClassnameIs( entity, "worldspawn" ))
|
||||
return false;
|
||||
|
||||
if (FClassnameIs( entity, "player" ))
|
||||
return false;
|
||||
|
||||
// if we hit a door, assume its walkable because it will open when we touch it
|
||||
if (FClassnameIs( entity, "prop_door*" ) || FClassnameIs( entity, "func_door*" ))
|
||||
return (flags & WALK_THRU_DOORS) ? true : false;
|
||||
|
||||
// if we hit a clip brush, ignore it if it is not BRUSHSOLID_ALWAYS
|
||||
if (FClassnameIs( entity, "func_brush" ))
|
||||
{
|
||||
CFuncBrush *brush = (CFuncBrush *)entity;
|
||||
switch ( brush->m_iSolidity )
|
||||
{
|
||||
case CFuncBrush::BRUSHSOLID_ALWAYS:
|
||||
return false;
|
||||
case CFuncBrush::BRUSHSOLID_NEVER:
|
||||
return true;
|
||||
case CFuncBrush::BRUSHSOLID_TOGGLE:
|
||||
return (flags & WALK_THRU_TOGGLE_BRUSHES) ? true : false;
|
||||
}
|
||||
}
|
||||
|
||||
// if we hit a breakable object, assume its walkable because we will shoot it when we touch it
|
||||
if (FClassnameIs( entity, "func_breakable" ) && entity->GetHealth() && entity->m_takedamage == DAMAGE_YES)
|
||||
return (flags & WALK_THRU_BREAKABLES) ? true : false;
|
||||
|
||||
if (FClassnameIs( entity, "func_breakable_surf" ) && entity->m_takedamage == DAMAGE_YES)
|
||||
return (flags & WALK_THRU_BREAKABLES) ? true : false;
|
||||
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // _CS_NAV_H_
|
||||
@@ -0,0 +1,473 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose:
|
||||
//
|
||||
// $NoKeywords: $
|
||||
//
|
||||
//=============================================================================//
|
||||
// nav_area.cpp
|
||||
// AI Navigation areas
|
||||
// Author: Michael S. Booth (mike@turtlerockstudios.com), January 2003
|
||||
|
||||
#include "cbase.h"
|
||||
#include "cs_nav_mesh.h"
|
||||
#include "cs_nav_area.h"
|
||||
#include "nav_pathfind.h"
|
||||
#include "nav_colors.h"
|
||||
#include "fmtstr.h"
|
||||
#include "props_shared.h"
|
||||
#include "func_breakablesurf.h"
|
||||
#include "Color.h"
|
||||
#include "collisionutils.h"
|
||||
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
#include <tier0/memdbgon.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#pragma warning (disable:4701) // disable warning that variable *may* not be initialized
|
||||
#endif
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Constructor used during normal runtime.
|
||||
*/
|
||||
CCSNavArea::CCSNavArea( void )
|
||||
{
|
||||
m_approachCount = 0;
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Destructor
|
||||
*/
|
||||
CCSNavArea::~CCSNavArea()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
void CCSNavArea::OnServerActivate( void )
|
||||
{
|
||||
CNavArea::OnServerActivate();
|
||||
|
||||
}
|
||||
|
||||
void CCSNavArea::OnRoundRestart( void )
|
||||
{
|
||||
CNavArea::OnRoundRestart();
|
||||
}
|
||||
|
||||
|
||||
void CCSNavArea::Save( CUtlBuffer &fileBuffer, unsigned int version ) const
|
||||
{
|
||||
CNavArea::Save( fileBuffer, version );
|
||||
|
||||
//
|
||||
// Save the approach areas for this area
|
||||
//
|
||||
|
||||
// save number of approach areas
|
||||
fileBuffer.PutUnsignedChar(m_approachCount);
|
||||
|
||||
// save approach area info
|
||||
for( int a=0; a<m_approachCount; ++a )
|
||||
{
|
||||
if (m_approach[a].here.area)
|
||||
fileBuffer.PutUnsignedInt(m_approach[a].here.area->GetID());
|
||||
else
|
||||
fileBuffer.PutUnsignedInt(0);
|
||||
|
||||
if (m_approach[a].prev.area)
|
||||
fileBuffer.PutUnsignedInt(m_approach[a].prev.area->GetID());
|
||||
else
|
||||
fileBuffer.PutUnsignedInt(0);
|
||||
fileBuffer.PutUnsignedChar(m_approach[a].prevToHereHow);
|
||||
|
||||
if (m_approach[a].next.area)
|
||||
fileBuffer.PutUnsignedInt(m_approach[a].next.area->GetID());
|
||||
else
|
||||
fileBuffer.PutUnsignedInt(0);
|
||||
fileBuffer.PutUnsignedChar(m_approach[a].hereToNextHow);
|
||||
}
|
||||
}
|
||||
|
||||
NavErrorType CCSNavArea::Load( CUtlBuffer &fileBuffer, unsigned int version, unsigned int subVersion )
|
||||
{
|
||||
if ( version < 15 )
|
||||
return LoadLegacy(fileBuffer, version, subVersion);
|
||||
|
||||
// load base class data
|
||||
NavErrorType error = CNavArea::Load( fileBuffer, version, subVersion );
|
||||
|
||||
switch ( subVersion )
|
||||
{
|
||||
case 1:
|
||||
//
|
||||
// Load number of approach areas
|
||||
//
|
||||
m_approachCount = fileBuffer.GetUnsignedChar();
|
||||
|
||||
// load approach area info (IDs)
|
||||
for( int a = 0; a < m_approachCount; ++a )
|
||||
{
|
||||
m_approach[a].here.id = fileBuffer.GetUnsignedInt();
|
||||
|
||||
m_approach[a].prev.id = fileBuffer.GetUnsignedInt();
|
||||
m_approach[a].prevToHereHow = (NavTraverseType)fileBuffer.GetUnsignedChar();
|
||||
|
||||
m_approach[a].next.id = fileBuffer.GetUnsignedInt();
|
||||
m_approach[a].hereToNextHow = (NavTraverseType)fileBuffer.GetUnsignedChar();
|
||||
}
|
||||
|
||||
if ( !fileBuffer.IsValid() )
|
||||
error = NAV_INVALID_FILE;
|
||||
|
||||
// fall through
|
||||
|
||||
case 0:
|
||||
// legacy version
|
||||
break;
|
||||
|
||||
default:
|
||||
Warning( "Unknown NavArea sub-version number\n" );
|
||||
error = NAV_INVALID_FILE;
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
|
||||
NavErrorType CCSNavArea::PostLoad( void )
|
||||
{
|
||||
NavErrorType error = CNavArea::PostLoad();
|
||||
|
||||
// resolve approach area IDs
|
||||
for ( int a = 0; a < m_approachCount; ++a )
|
||||
{
|
||||
m_approach[a].here.area = TheNavMesh->GetNavAreaByID( m_approach[a].here.id );
|
||||
if (m_approach[a].here.id && m_approach[a].here.area == NULL)
|
||||
{
|
||||
Msg( "CNavArea::PostLoad: Corrupt navigation data. Missing Approach Area (here).\n" );
|
||||
error = NAV_CORRUPT_DATA;
|
||||
}
|
||||
|
||||
m_approach[a].prev.area = TheNavMesh->GetNavAreaByID( m_approach[a].prev.id );
|
||||
if (m_approach[a].prev.id && m_approach[a].prev.area == NULL)
|
||||
{
|
||||
Msg( "CNavArea::PostLoad: Corrupt navigation data. Missing Approach Area (prev).\n" );
|
||||
error = NAV_CORRUPT_DATA;
|
||||
}
|
||||
|
||||
m_approach[a].next.area = TheNavMesh->GetNavAreaByID( m_approach[a].next.id );
|
||||
if (m_approach[a].next.id && m_approach[a].next.area == NULL)
|
||||
{
|
||||
Msg( "CNavArea::PostLoad: Corrupt navigation data. Missing Approach Area (next).\n" );
|
||||
error = NAV_CORRUPT_DATA;
|
||||
}
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
|
||||
void CCSNavArea::Draw( void ) const
|
||||
{
|
||||
CNavArea::Draw();
|
||||
}
|
||||
|
||||
void CCSNavArea::CustomAnalysis( bool isIncremental /*= false */ )
|
||||
{
|
||||
ComputeApproachAreas();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Load legacy navigation area from the file
|
||||
*/
|
||||
NavErrorType CCSNavArea::LoadLegacy( CUtlBuffer &fileBuffer, unsigned int version, unsigned int subVersion )
|
||||
{
|
||||
// load ID
|
||||
m_id = fileBuffer.GetUnsignedInt();
|
||||
|
||||
// update nextID to avoid collisions
|
||||
if (m_id >= m_nextID)
|
||||
m_nextID = m_id+1;
|
||||
|
||||
// load attribute flags
|
||||
if ( version <= 8 )
|
||||
{
|
||||
m_attributeFlags = fileBuffer.GetUnsignedChar();
|
||||
}
|
||||
else if ( version < 13 )
|
||||
{
|
||||
m_attributeFlags = fileBuffer.GetUnsignedShort();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_attributeFlags = fileBuffer.GetInt();
|
||||
}
|
||||
|
||||
// load extent of area
|
||||
fileBuffer.Get( &m_nwCorner, 3*sizeof(float) );
|
||||
fileBuffer.Get( &m_seCorner, 3*sizeof(float) );
|
||||
|
||||
m_center.x = (m_nwCorner.x + m_seCorner.x)/2.0f;
|
||||
m_center.y = (m_nwCorner.y + m_seCorner.y)/2.0f;
|
||||
m_center.z = (m_nwCorner.z + m_seCorner.z)/2.0f;
|
||||
|
||||
if ( ( m_seCorner.x - m_nwCorner.x ) > 0.0f && ( m_seCorner.y - m_nwCorner.y ) > 0.0f )
|
||||
{
|
||||
m_invDxCorners = 1.0f / ( m_seCorner.x - m_nwCorner.x );
|
||||
m_invDyCorners = 1.0f / ( m_seCorner.y - m_nwCorner.y );
|
||||
}
|
||||
else
|
||||
{
|
||||
m_invDxCorners = m_invDyCorners = 0;
|
||||
|
||||
DevWarning( "Degenerate Navigation Area #%d at setpos %g %g %g\n",
|
||||
m_id, m_center.x, m_center.y, m_center.z );
|
||||
}
|
||||
|
||||
// load heights of implicit corners
|
||||
m_neZ = fileBuffer.GetFloat();
|
||||
m_swZ = fileBuffer.GetFloat();
|
||||
|
||||
CheckWaterLevel();
|
||||
|
||||
// load connections (IDs) to adjacent areas
|
||||
// in the enum order NORTH, EAST, SOUTH, WEST
|
||||
for( int d=0; d<NUM_DIRECTIONS; d++ )
|
||||
{
|
||||
// load number of connections for this direction
|
||||
unsigned int count = fileBuffer.GetUnsignedInt();
|
||||
Assert( fileBuffer.IsValid() );
|
||||
|
||||
m_connect[d].EnsureCapacity( count );
|
||||
for( unsigned int i=0; i<count; ++i )
|
||||
{
|
||||
NavConnect connect;
|
||||
connect.id = fileBuffer.GetUnsignedInt();
|
||||
Assert( fileBuffer.IsValid() );
|
||||
|
||||
// don't allow self-referential connections
|
||||
if ( connect.id != m_id )
|
||||
{
|
||||
m_connect[d].AddToTail( connect );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Load hiding spots
|
||||
//
|
||||
|
||||
// load number of hiding spots
|
||||
unsigned char hidingSpotCount = fileBuffer.GetUnsignedChar();
|
||||
|
||||
if (version == 1)
|
||||
{
|
||||
// load simple vector array
|
||||
Vector pos;
|
||||
for( int h=0; h<hidingSpotCount; ++h )
|
||||
{
|
||||
fileBuffer.Get( &pos, 3 * sizeof(float) );
|
||||
|
||||
// create new hiding spot and put on master list
|
||||
HidingSpot *spot = TheNavMesh->CreateHidingSpot();
|
||||
spot->SetPosition( pos );
|
||||
spot->SetFlags( HidingSpot::IN_COVER );
|
||||
m_hidingSpots.AddToTail( spot );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// load HidingSpot objects for this area
|
||||
for( int h=0; h<hidingSpotCount; ++h )
|
||||
{
|
||||
// create new hiding spot and put on master list
|
||||
HidingSpot *spot = TheNavMesh->CreateHidingSpot();
|
||||
|
||||
spot->Load( fileBuffer, version );
|
||||
|
||||
m_hidingSpots.AddToTail( spot );
|
||||
}
|
||||
}
|
||||
|
||||
if ( version < 15 )
|
||||
{
|
||||
//
|
||||
// Load number of approach areas
|
||||
//
|
||||
m_approachCount = fileBuffer.GetUnsignedChar();
|
||||
|
||||
// load approach area info (IDs)
|
||||
for( int a = 0; a < m_approachCount; ++a )
|
||||
{
|
||||
m_approach[a].here.id = fileBuffer.GetUnsignedInt();
|
||||
|
||||
m_approach[a].prev.id = fileBuffer.GetUnsignedInt();
|
||||
m_approach[a].prevToHereHow = (NavTraverseType)fileBuffer.GetUnsignedChar();
|
||||
|
||||
m_approach[a].next.id = fileBuffer.GetUnsignedInt();
|
||||
m_approach[a].hereToNextHow = (NavTraverseType)fileBuffer.GetUnsignedChar();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Load encounter paths for this area
|
||||
//
|
||||
unsigned int count = fileBuffer.GetUnsignedInt();
|
||||
|
||||
if (version < 3)
|
||||
{
|
||||
// old data, read and discard
|
||||
for( unsigned int e=0; e<count; ++e )
|
||||
{
|
||||
SpotEncounter encounter;
|
||||
|
||||
encounter.from.id = fileBuffer.GetUnsignedInt();
|
||||
encounter.to.id = fileBuffer.GetUnsignedInt();
|
||||
|
||||
fileBuffer.Get( &encounter.path.from.x, 3 * sizeof(float) );
|
||||
fileBuffer.Get( &encounter.path.to.x, 3 * sizeof(float) );
|
||||
|
||||
// read list of spots along this path
|
||||
unsigned char spotCount = fileBuffer.GetUnsignedChar();
|
||||
|
||||
for( int s=0; s<spotCount; ++s )
|
||||
{
|
||||
fileBuffer.GetFloat();
|
||||
fileBuffer.GetFloat();
|
||||
fileBuffer.GetFloat();
|
||||
fileBuffer.GetFloat();
|
||||
}
|
||||
}
|
||||
return NAV_OK;
|
||||
}
|
||||
|
||||
for( unsigned int e=0; e<count; ++e )
|
||||
{
|
||||
SpotEncounter *encounter = new SpotEncounter;
|
||||
|
||||
encounter->from.id = fileBuffer.GetUnsignedInt();
|
||||
|
||||
unsigned char dir = fileBuffer.GetUnsignedChar();
|
||||
encounter->fromDir = static_cast<NavDirType>( dir );
|
||||
|
||||
encounter->to.id = fileBuffer.GetUnsignedInt();
|
||||
|
||||
dir = fileBuffer.GetUnsignedChar();
|
||||
encounter->toDir = static_cast<NavDirType>( dir );
|
||||
|
||||
// read list of spots along this path
|
||||
unsigned char spotCount = fileBuffer.GetUnsignedChar();
|
||||
|
||||
SpotOrder order;
|
||||
for( int s=0; s<spotCount; ++s )
|
||||
{
|
||||
order.id = fileBuffer.GetUnsignedInt();
|
||||
|
||||
unsigned char t = fileBuffer.GetUnsignedChar();
|
||||
|
||||
order.t = (float)t/255.0f;
|
||||
|
||||
encounter->spots.AddToTail( order );
|
||||
}
|
||||
|
||||
m_spotEncounters.AddToTail( encounter );
|
||||
}
|
||||
|
||||
if (version < 5)
|
||||
return NAV_OK;
|
||||
|
||||
//
|
||||
// Load Place data
|
||||
//
|
||||
PlaceDirectory::IndexType entry = fileBuffer.GetUnsignedShort();
|
||||
|
||||
// convert entry to actual Place
|
||||
SetPlace( placeDirectory.IndexToPlace( entry ) );
|
||||
|
||||
if ( version < 7 )
|
||||
return NAV_OK;
|
||||
|
||||
// load ladder data
|
||||
for ( int dir=0; dir<CNavLadder::NUM_LADDER_DIRECTIONS; ++dir )
|
||||
{
|
||||
count = fileBuffer.GetUnsignedInt();
|
||||
for( unsigned int i=0; i<count; ++i )
|
||||
{
|
||||
NavLadderConnect connect;
|
||||
connect.id = fileBuffer.GetUnsignedInt();
|
||||
|
||||
bool alreadyConnected = false;
|
||||
FOR_EACH_VEC( m_ladder[dir], j )
|
||||
{
|
||||
if ( m_ladder[dir][j].id == connect.id )
|
||||
{
|
||||
alreadyConnected = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( !alreadyConnected )
|
||||
{
|
||||
m_ladder[dir].AddToTail( connect );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( version < 8 )
|
||||
return NAV_OK;
|
||||
|
||||
// load earliest occupy times
|
||||
for( int i=0; i<MAX_NAV_TEAMS; ++i )
|
||||
{
|
||||
// no spot in the map should take longer than this to reach
|
||||
m_earliestOccupyTime[i] = fileBuffer.GetFloat();
|
||||
}
|
||||
|
||||
if ( version < 11 )
|
||||
return NAV_OK;
|
||||
|
||||
// load light intensity
|
||||
for ( int i=0; i<NUM_CORNERS; ++i )
|
||||
{
|
||||
m_lightIntensity[i] = fileBuffer.GetFloat();
|
||||
}
|
||||
|
||||
if ( version < 16 )
|
||||
return NAV_OK;
|
||||
|
||||
// load visibility information
|
||||
unsigned int visibleAreaCount = fileBuffer.GetUnsignedInt();
|
||||
if ( !IsX360() )
|
||||
{
|
||||
m_potentiallyVisibleAreas.EnsureCapacity( visibleAreaCount );
|
||||
}
|
||||
else
|
||||
{
|
||||
/* TODO: Re-enable when latest 360 code gets integrated (MSB 5/5/09)
|
||||
size_t nBytes = visibleAreaCount * sizeof( AreaBindInfo );
|
||||
m_potentiallyVisibleAreas.~CAreaBindInfoArray();
|
||||
new ( &m_potentiallyVisibleAreas ) CAreaBindInfoArray( (AreaBindInfo *)engine->AllocLevelStaticData( nBytes ), visibleAreaCount );
|
||||
*/
|
||||
}
|
||||
|
||||
for( unsigned int j=0; j<visibleAreaCount; ++j )
|
||||
{
|
||||
AreaBindInfo info;
|
||||
info.id = fileBuffer.GetUnsignedInt();
|
||||
info.attributes = fileBuffer.GetUnsignedChar();
|
||||
|
||||
m_potentiallyVisibleAreas.AddToTail( info );
|
||||
}
|
||||
|
||||
// read area from which we inherit visibility
|
||||
m_inheritVisibilityFrom.id = fileBuffer.GetUnsignedInt();
|
||||
|
||||
return NAV_OK;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose:
|
||||
//
|
||||
// $NoKeywords: $
|
||||
//
|
||||
//=============================================================================//
|
||||
// nav_area.h
|
||||
// Navigation areas
|
||||
// Author: Michael S. Booth (mike@turtlerockstudios.com), January 2003
|
||||
|
||||
#ifndef _CS_NAV_AREA_H_
|
||||
#define _CS_NAV_AREA_H_
|
||||
|
||||
#include "nav_area.h"
|
||||
|
||||
//-------------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* A CNavArea is a rectangular region defining a walkable area in the environment
|
||||
*/
|
||||
class CCSNavArea : public CNavArea
|
||||
{
|
||||
public:
|
||||
DECLARE_CLASS( CCSNavArea, CNavArea );
|
||||
|
||||
CCSNavArea( void );
|
||||
~CCSNavArea();
|
||||
|
||||
virtual void OnServerActivate( void ); // (EXTEND) invoked when map is initially loaded
|
||||
virtual void OnRoundRestart( void ); // (EXTEND) invoked for each area when the round restarts
|
||||
|
||||
virtual void Draw( void ) const; // draw area for debugging & editing
|
||||
|
||||
virtual void Save( CUtlBuffer &fileBuffer, unsigned int version ) const; // (EXTEND)
|
||||
virtual NavErrorType Load( CUtlBuffer &fileBuffer, unsigned int version, unsigned int subVersion ); // (EXTEND)
|
||||
virtual NavErrorType PostLoad( void ); // (EXTEND) invoked after all areas have been loaded - for pointer binding, etc
|
||||
|
||||
virtual void CustomAnalysis( bool isIncremental = false ); // for game-specific analysis
|
||||
|
||||
//- approach areas ----------------------------------------------------------------------------------
|
||||
struct ApproachInfo
|
||||
{
|
||||
NavConnect here; ///< the approach area
|
||||
NavConnect prev; ///< the area just before the approach area on the path
|
||||
NavTraverseType prevToHereHow;
|
||||
NavConnect next; ///< the area just after the approach area on the path
|
||||
NavTraverseType hereToNextHow;
|
||||
};
|
||||
const ApproachInfo *GetApproachInfo( int i ) const { return &m_approach[i]; }
|
||||
int GetApproachInfoCount( void ) const { return m_approachCount; }
|
||||
void ComputeApproachAreas( void ); ///< determine the set of "approach areas" - for map learning
|
||||
|
||||
//- player counting --------------------------------------------------------------------------------
|
||||
void ClearPlayerCount( void ); ///< set the player count to zero
|
||||
|
||||
protected:
|
||||
NavErrorType LoadLegacy( CUtlBuffer &fileBuffer, unsigned int version, unsigned int subVersion );
|
||||
|
||||
|
||||
private:
|
||||
//- approach areas ----------------------------------------------------------------------------------
|
||||
enum { MAX_APPROACH_AREAS = 16 };
|
||||
ApproachInfo m_approach[ MAX_APPROACH_AREAS ];
|
||||
unsigned char m_approachCount;
|
||||
};
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
//
|
||||
// Inlines
|
||||
//
|
||||
|
||||
inline void CCSNavArea::ClearPlayerCount( void )
|
||||
{
|
||||
for( int i=0; i<MAX_NAV_TEAMS; ++i )
|
||||
{
|
||||
m_playerCount[ i ] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
#endif // _CS_NAV_AREA_H_
|
||||
@@ -0,0 +1,69 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose:
|
||||
//
|
||||
// $NoKeywords: $
|
||||
//
|
||||
//=============================================================================//
|
||||
// nav_edit.cpp
|
||||
// Implementation of Navigation Mesh edit mode
|
||||
// Author: Michael Booth, 2003-2004
|
||||
|
||||
#include "cbase.h"
|
||||
#include "nav_mesh.h"
|
||||
#include "cs_nav_pathfind.h"
|
||||
#include "cs_nav_node.h"
|
||||
#include "nav_colors.h"
|
||||
#include "Color.h"
|
||||
#include "tier0/vprof.h"
|
||||
#include "collisionutils.h"
|
||||
|
||||
ConVar nav_show_area_info( "nav_show_area_info", "0.5", FCVAR_GAMEDLL, "Duration in seconds to show nav area ID and attributes while editing" );
|
||||
ConVar nav_snap_to_grid( "nav_snap_to_grid", "0", FCVAR_GAMEDLL, "Snap to the nav generation grid when creating new nav areas" );
|
||||
ConVar nav_create_place_on_ground( "nav_create_place_on_ground", "0", FCVAR_GAMEDLL, "If true, nav areas will be placed flush with the ground when created by hand." );
|
||||
|
||||
#if DEBUG_NAV_NODES
|
||||
extern ConVar nav_show_nodes;
|
||||
#endif // DEBUG_NAV_NODES
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
void EditNav_Precache(void *pUser)
|
||||
{
|
||||
CBaseEntity::PrecacheScriptSound( "Bot.EditSwitchOn" );
|
||||
CBaseEntity::PrecacheScriptSound( "EDIT_TOGGLE_PLACE_MODE" );
|
||||
CBaseEntity::PrecacheScriptSound( "Bot.EditSwitchOff" );
|
||||
CBaseEntity::PrecacheScriptSound( "EDIT_PLACE_PICK" );
|
||||
CBaseEntity::PrecacheScriptSound( "EDIT_DELETE" );
|
||||
CBaseEntity::PrecacheScriptSound( "EDIT.ToggleAttribute" );
|
||||
CBaseEntity::PrecacheScriptSound( "EDIT_SPLIT.MarkedArea" );
|
||||
CBaseEntity::PrecacheScriptSound( "EDIT_SPLIT.NoMarkedArea" );
|
||||
CBaseEntity::PrecacheScriptSound( "EDIT_MERGE.Enable" );
|
||||
CBaseEntity::PrecacheScriptSound( "EDIT_MERGE.Disable" );
|
||||
CBaseEntity::PrecacheScriptSound( "EDIT_MARK.Enable" );
|
||||
CBaseEntity::PrecacheScriptSound( "EDIT_MARK.Disable" );
|
||||
CBaseEntity::PrecacheScriptSound( "EDIT_MARK_UNNAMED.Enable" );
|
||||
CBaseEntity::PrecacheScriptSound( "EDIT_MARK_UNNAMED.NoMarkedArea" );
|
||||
CBaseEntity::PrecacheScriptSound( "EDIT_MARK_UNNAMED.MarkedArea" );
|
||||
CBaseEntity::PrecacheScriptSound( "EDIT_CONNECT.AllDirections" );
|
||||
CBaseEntity::PrecacheScriptSound( "EDIT_CONNECT.Added" );
|
||||
CBaseEntity::PrecacheScriptSound( "EDIT_DISCONNECT.MarkedArea" );
|
||||
CBaseEntity::PrecacheScriptSound( "EDIT_DISCONNECT.NoMarkedArea" );
|
||||
CBaseEntity::PrecacheScriptSound( "EDIT_SPLICE.MarkedArea" );
|
||||
CBaseEntity::PrecacheScriptSound( "EDIT_SPLICE.NoMarkedArea" );
|
||||
CBaseEntity::PrecacheScriptSound( "EDIT_SELECT_CORNER.MarkedArea" );
|
||||
CBaseEntity::PrecacheScriptSound( "EDIT_SELECT_CORNER.NoMarkedArea" );
|
||||
CBaseEntity::PrecacheScriptSound( "EDIT_MOVE_CORNER.MarkedArea" );
|
||||
CBaseEntity::PrecacheScriptSound( "EDIT_MOVE_CORNER.NoMarkedArea" );
|
||||
CBaseEntity::PrecacheScriptSound( "EDIT_BEGIN_AREA.Creating" );
|
||||
CBaseEntity::PrecacheScriptSound( "EDIT_BEGIN_AREA.NotCreating" );
|
||||
CBaseEntity::PrecacheScriptSound( "EDIT_END_AREA.Creating" );
|
||||
CBaseEntity::PrecacheScriptSound( "EDIT_END_AREA.NotCreating" );
|
||||
CBaseEntity::PrecacheScriptSound( "EDIT_WARP_TO_MARK" );
|
||||
}
|
||||
|
||||
#ifdef CSTRIKE_DLL
|
||||
PRECACHE_REGISTER_FN( EditNav_Precache );
|
||||
#endif
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,269 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose:
|
||||
//
|
||||
// $NoKeywords: $
|
||||
//
|
||||
//=============================================================================//
|
||||
// nav_generate.cpp
|
||||
// Auto-generate a Navigation Mesh by sampling the current map
|
||||
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
|
||||
|
||||
#include "cbase.h"
|
||||
#include "util_shared.h"
|
||||
#include "nav_mesh.h"
|
||||
#include "cs_nav_area.h"
|
||||
#include "cs_nav_node.h"
|
||||
#include "cs_nav_pathfind.h"
|
||||
#include "viewport_panel_names.h"
|
||||
|
||||
enum { MAX_BLOCKED_AREAS = 256 };
|
||||
static unsigned int blockedID[ MAX_BLOCKED_AREAS ];
|
||||
static int blockedIDCount = 0;
|
||||
static float lastMsgTime = 0.0f;
|
||||
|
||||
|
||||
//ConVar nav_slope_limit( "nav_slope_limit", "0.7", FCVAR_GAMEDLL, "The ground unit normal's Z component must be greater than this for nav areas to be generated." );
|
||||
ConVar nav_restart_after_analysis( "nav_restart_after_analysis", "1", FCVAR_GAMEDLL, "When nav nav_restart_after_analysis finishes, restart the server. Turning this off can cause crashes, but is useful for incremental generation." );
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Shortest path cost, paying attention to "blocked" areas
|
||||
*/
|
||||
class ApproachAreaCost
|
||||
{
|
||||
public:
|
||||
// HPE_TODO[pmf]: check that these new parameters are okay to be ignored
|
||||
float operator() ( CNavArea *area, CNavArea *fromArea, const CNavLadder *ladder, const CFuncElevator *elevator, float length )
|
||||
{
|
||||
// check if this area is "blocked"
|
||||
for( int i=0; i<blockedIDCount; ++i )
|
||||
{
|
||||
if (area->GetID() == blockedID[i])
|
||||
{
|
||||
return -1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
if (fromArea == NULL)
|
||||
{
|
||||
// first area in path, no cost
|
||||
return 0.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
// compute distance traveled along path so far
|
||||
float dist;
|
||||
|
||||
if (ladder)
|
||||
{
|
||||
dist = ladder->m_length;
|
||||
}
|
||||
else
|
||||
{
|
||||
dist = (area->GetCenter() - fromArea->GetCenter()).Length();
|
||||
}
|
||||
|
||||
float cost = dist + fromArea->GetCostSoFar();
|
||||
|
||||
return cost;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Determine the set of "approach areas".
|
||||
* An approach area is an area representing a place where players
|
||||
* move into/out of our local neighborhood of areas.
|
||||
* @todo Optimize by search from eye outward and modifying pathfinder to treat all links as bi-directional
|
||||
*/
|
||||
void CCSNavArea::ComputeApproachAreas( void )
|
||||
{
|
||||
m_approachCount = 0;
|
||||
|
||||
if (nav_quicksave.GetBool())
|
||||
return;
|
||||
|
||||
// use the center of the nav area as the "view" point
|
||||
Vector eye = m_center;
|
||||
if (TheNavMesh->GetGroundHeight( eye, &eye.z ) == false)
|
||||
return;
|
||||
|
||||
// approximate eye position
|
||||
if (GetAttributes() & NAV_MESH_CROUCH)
|
||||
eye.z += 0.9f * HalfHumanHeight;
|
||||
else
|
||||
eye.z += 0.9f * HumanHeight;
|
||||
|
||||
enum { MAX_PATH_LENGTH = 256 };
|
||||
CNavArea *path[ MAX_PATH_LENGTH ];
|
||||
ApproachAreaCost cost;
|
||||
|
||||
enum SearchType
|
||||
{
|
||||
FROM_EYE, ///< start search from our eyepoint outward to farArea
|
||||
TO_EYE, ///< start search from farArea beack towards our eye
|
||||
SEARCH_FINISHED
|
||||
};
|
||||
|
||||
//
|
||||
// In order to *completely* enumerate all of the approach areas, we
|
||||
// need to search from our eyepoint outward, as well as from outwards
|
||||
// towards our eyepoint
|
||||
//
|
||||
for( int searchType = FROM_EYE; searchType != SEARCH_FINISHED; ++searchType )
|
||||
{
|
||||
//
|
||||
// In order to enumerate all of the approach areas, we need to
|
||||
// run the algorithm many times, once for each "far away" area
|
||||
// and keep the union of the approach area sets
|
||||
//
|
||||
int it;
|
||||
for( it = 0; it < TheNavAreas.Count(); ++it )
|
||||
{
|
||||
CNavArea *farArea = TheNavAreas[ it ];
|
||||
|
||||
blockedIDCount = 0;
|
||||
|
||||
// skip the small areas
|
||||
const float minSize = 200.0f; // 150
|
||||
Extent extent;
|
||||
farArea->GetExtent(&extent);
|
||||
if (extent.SizeX() < minSize || extent.SizeY() < minSize)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// if we can see 'farArea', try again - the whole point is to go "around the bend", so to speak
|
||||
if (farArea->IsVisible( eye ))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
//
|
||||
// Keep building paths to farArea and blocking them off until we
|
||||
// cant path there any more.
|
||||
// As areas are blocked off, all exits will be enumerated.
|
||||
//
|
||||
while( m_approachCount < MAX_APPROACH_AREAS )
|
||||
{
|
||||
CNavArea *from, *to;
|
||||
|
||||
if (searchType == FROM_EYE)
|
||||
{
|
||||
// find another path *to* 'farArea'
|
||||
// we must pathfind from us in order to pick up one-way paths OUT OF our area
|
||||
from = this;
|
||||
to = farArea;
|
||||
}
|
||||
else // TO_EYE
|
||||
{
|
||||
// find another path *from* 'farArea'
|
||||
// we must pathfind to us in order to pick up one-way paths INTO our area
|
||||
from = farArea;
|
||||
to = this;
|
||||
}
|
||||
|
||||
// build the actual path
|
||||
if (NavAreaBuildPath( from, to, NULL, cost ) == false)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// find number of areas on path
|
||||
int count = 0;
|
||||
CNavArea *area;
|
||||
for( area = to; area; area = area->GetParent() )
|
||||
{
|
||||
++count;
|
||||
}
|
||||
|
||||
if (count > MAX_PATH_LENGTH)
|
||||
{
|
||||
count = MAX_PATH_LENGTH;
|
||||
}
|
||||
|
||||
// if the path is only two areas long, there can be no approach points
|
||||
if (count <= 2)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// build path starting from eye
|
||||
int i = 0;
|
||||
|
||||
if (searchType == FROM_EYE)
|
||||
{
|
||||
for( area = to; i < count && area; area = area->GetParent() )
|
||||
{
|
||||
path[ count-i-1 ] = area;
|
||||
++i;
|
||||
}
|
||||
}
|
||||
else // TO_EYE
|
||||
{
|
||||
for( area = to; i < count && area; area = area->GetParent() )
|
||||
{
|
||||
path[ i++ ] = area;
|
||||
}
|
||||
}
|
||||
|
||||
// traverse path to find first area we cannot see (skip the first area)
|
||||
for( i=1; i<count; ++i )
|
||||
{
|
||||
// if we see this area, continue on
|
||||
if (path[i]->IsVisible( eye ))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// we can't see this area - mark this area as "blocked" and unusable by subsequent approach paths
|
||||
if (blockedIDCount == MAX_BLOCKED_AREAS)
|
||||
{
|
||||
Msg( "Overflow computing approach areas for area #%d.\n", GetID());
|
||||
return;
|
||||
}
|
||||
|
||||
// if the area to be blocked is actually farArea, block the one just prior
|
||||
// (blocking farArea will cause all subsequent pathfinds to fail)
|
||||
int block = (path[i] == farArea) ? i-1 : i;
|
||||
|
||||
// dont block the start area, or all subsequence pathfinds will fail
|
||||
if (block == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
blockedID[ blockedIDCount++ ] = path[ block ]->GetID();
|
||||
|
||||
// store new approach area if not already in set
|
||||
int a;
|
||||
for( a=0; a<m_approachCount; ++a )
|
||||
{
|
||||
if (m_approach[a].here.area == path[block-1])
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (a == m_approachCount)
|
||||
{
|
||||
m_approach[ m_approachCount ].prev.area = (block >= 2) ? path[block-2] : NULL;
|
||||
|
||||
m_approach[ m_approachCount ].here.area = path[block-1];
|
||||
m_approach[ m_approachCount ].prevToHereHow = path[block-1]->GetParentHow();
|
||||
|
||||
m_approach[ m_approachCount ].next.area = path[block];
|
||||
m_approach[ m_approachCount ].hereToNextHow = path[block]->GetParentHow();
|
||||
|
||||
++m_approachCount;
|
||||
}
|
||||
|
||||
// we are done with this path
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user