You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
478 lines
13 KiB
478 lines
13 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
//=============================================================================// |
|
|
|
#include "cbase.h" |
|
#include "entitylist.h" |
|
#include "ai_navigator.h" |
|
#include "ai_behavior_operator.h" |
|
|
|
|
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include "tier0/memdbgon.h" |
|
|
|
//============================================================================= |
|
//============================================================================= |
|
// >OPERATOR BEHAVIOR |
|
//============================================================================= |
|
//============================================================================= |
|
BEGIN_DATADESC( CAI_OperatorBehavior ) |
|
DEFINE_FIELD( m_hGoalEntity, FIELD_EHANDLE ), |
|
DEFINE_FIELD( m_hPositionEnt, FIELD_EHANDLE ), |
|
DEFINE_FIELD( m_hContextTarget, FIELD_EHANDLE ), |
|
DEFINE_EMBEDDED( m_WatchSeeEntity ), |
|
END_DATADESC(); |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
CAI_OperatorBehavior::CAI_OperatorBehavior() |
|
{ |
|
m_hPositionEnt.Set(NULL); |
|
m_hGoalEntity.Set(NULL); |
|
m_hContextTarget.Set(NULL); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
//----------------------------------------------------------------------------- |
|
#define POSITION_ENT_ALWAYS_SEE_DIST Square(120) |
|
bool CAI_OperatorBehavior::CanSeePositionEntity() |
|
{ |
|
CAI_BaseNPC *pOuter = GetOuter(); |
|
|
|
Assert( m_hPositionEnt.Get() != NULL ); |
|
|
|
// early out here. |
|
if( !pOuter->QuerySeeEntity(m_hPositionEnt) ) |
|
{ |
|
m_WatchSeeEntity.Stop(); |
|
return false; |
|
} |
|
|
|
bool bSpotted = (pOuter->EyePosition().DistToSqr(m_hPositionEnt->GetAbsOrigin()) <= POSITION_ENT_ALWAYS_SEE_DIST); |
|
if ( !bSpotted ) |
|
{ |
|
bSpotted = ( pOuter->FInViewCone(m_hPositionEnt) && pOuter->FVisible(m_hPositionEnt) ); |
|
} |
|
|
|
if (bSpotted ) |
|
{ |
|
// If we haven't seen it up until now, start a timer. If we have seen it, wait for the |
|
// timer to finish. This prevents edge cases where turning on the flashlight makes |
|
// NPC spot the position entity a frame before she spots an enemy. |
|
if ( !m_WatchSeeEntity.IsRunning() ) |
|
{ |
|
m_WatchSeeEntity.Start( 0.3,0.31 ); |
|
return false; |
|
} |
|
|
|
if ( !m_WatchSeeEntity.Expired() ) |
|
return false; |
|
|
|
return true; |
|
} |
|
|
|
m_WatchSeeEntity.Stop(); |
|
return false; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
//----------------------------------------------------------------------------- |
|
bool CAI_OperatorBehavior::IsAtPositionEntity() |
|
{ |
|
Vector myPos = GetAbsOrigin(); |
|
Vector objectPos = m_hPositionEnt->GetAbsOrigin(); |
|
|
|
Vector vecDir = objectPos - myPos; |
|
|
|
return (vecDir.Length2D() <= 12.0f); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
//----------------------------------------------------------------------------- |
|
void CAI_OperatorBehavior::GatherConditionsNotActive() |
|
{ |
|
if( m_hPositionEnt ) |
|
{ |
|
// If we're not currently the active behavior, we have a position ent, and the |
|
// NPC can see it, coax the AI out of IDLE/ALERT schedules with this condition. |
|
if( CanSeePositionEntity() ) |
|
{ |
|
SetCondition( COND_IDLE_INTERRUPT ); |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
//----------------------------------------------------------------------------- |
|
void CAI_OperatorBehavior::GatherConditions( void ) |
|
{ |
|
if( GetGoalEntity() ) |
|
{ |
|
if( GetGoalEntity()->GetState() == OPERATOR_STATE_FINISHED ) |
|
{ |
|
if( IsCurSchedule(SCHED_OPERATOR_OPERATE) ) |
|
{ |
|
// Break us out of the operator schedule if the operation completes. |
|
SetCondition(COND_PROVOKED); |
|
} |
|
|
|
m_hGoalEntity.Set(NULL); |
|
m_hPositionEnt.Set(NULL); |
|
} |
|
else |
|
{ |
|
if( CanSeePositionEntity() ) |
|
{ |
|
ClearCondition( COND_OPERATOR_LOST_SIGHT_OF_POSITION ); |
|
} |
|
else |
|
{ |
|
SetCondition( COND_OPERATOR_LOST_SIGHT_OF_POSITION ); |
|
} |
|
} |
|
} |
|
|
|
BaseClass::GatherConditions(); |
|
|
|
// Ignore player pushing. |
|
ClearCondition( COND_PLAYER_PUSHING ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : *pTask - |
|
//----------------------------------------------------------------------------- |
|
void CAI_OperatorBehavior::StartTask( const Task_t *pTask ) |
|
{ |
|
switch( pTask->iTask ) |
|
{ |
|
case TASK_OPERATOR_OPERATE: |
|
{ |
|
// Fire the appropriate output! |
|
switch( GetGoalEntity()->GetState() ) |
|
{ |
|
case OPERATOR_STATE_NOT_READY: |
|
GetGoalEntity()->m_OnMakeReady.FireOutput(NULL, NULL, 0); |
|
break; |
|
|
|
case OPERATOR_STATE_READY: |
|
GetGoalEntity()->m_OnBeginOperating.FireOutput(NULL, NULL, 0); |
|
break; |
|
|
|
default: |
|
//!!!HACKHACK |
|
Assert(0); |
|
break; |
|
} |
|
} |
|
TaskComplete(); |
|
break; |
|
|
|
case TASK_OPERATOR_START_PATH: |
|
{ |
|
ChainStartTask(TASK_WALK_PATH); |
|
} |
|
break; |
|
|
|
case TASK_OPERATOR_GET_PATH_TO_POSITION: |
|
{ |
|
CBaseEntity *pGoal = m_hPositionEnt; |
|
|
|
if( !pGoal ) |
|
{ |
|
TaskFail("ai_goal_operator has no location entity\n"); |
|
break; |
|
} |
|
|
|
AI_NavGoal_t goal( pGoal->GetAbsOrigin() ); |
|
goal.pTarget = pGoal; |
|
|
|
if ( GetNavigator()->SetGoal( goal ) == false ) |
|
{ |
|
TaskFail( "Can't build path\n" ); |
|
/* |
|
// Try and get as close as possible otherwise |
|
AI_NavGoal_t nearGoal( GOALTYPE_LOCATION_NEAREST_NODE, m_hTargetObject->GetAbsOrigin(), AIN_DEF_ACTIVITY, 256 ); |
|
if ( GetNavigator()->SetGoal( nearGoal, AIN_CLEAR_PREVIOUS_STATE ) ) |
|
{ |
|
//FIXME: HACK! The internal pathfinding is setting this without our consent, so override it! |
|
ClearCondition( COND_TASK_FAILED ); |
|
GetNavigator()->SetArrivalDirection( m_hTargetObject->GetAbsAngles() ); |
|
TaskComplete(); |
|
return; |
|
} |
|
*/ |
|
} |
|
|
|
GetNavigator()->SetArrivalDirection( pGoal->GetAbsAngles() ); |
|
} |
|
break; |
|
|
|
default: |
|
BaseClass::StartTask( pTask ); |
|
break; |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : *pTask - |
|
//----------------------------------------------------------------------------- |
|
void CAI_OperatorBehavior::RunTask( const Task_t *pTask ) |
|
{ |
|
/* |
|
switch( pTask->iTask ) |
|
{ |
|
default: |
|
BaseClass::RunTask( pTask ); |
|
break; |
|
} |
|
*/ |
|
BaseClass::RunTask( pTask ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
//----------------------------------------------------------------------------- |
|
CAI_OperatorGoal *CAI_OperatorBehavior::GetGoalEntity() |
|
{ |
|
CAI_OperatorGoal *pGoal = dynamic_cast<CAI_OperatorGoal*>(m_hGoalEntity.Get()); |
|
|
|
// NULL is OK. |
|
return pGoal; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
bool CAI_OperatorBehavior::IsGoalReady() |
|
{ |
|
if( GetGoalEntity()->GetState() == OPERATOR_STATE_READY ) |
|
{ |
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CAI_OperatorBehavior::SetParameters( CAI_OperatorGoal *pGoal, CBaseEntity *pPositionEnt, CBaseEntity *pContextTarget ) |
|
{ |
|
m_hGoalEntity.Set( pGoal ); |
|
m_hPositionEnt.Set( pPositionEnt ); |
|
m_hContextTarget.Set( pContextTarget ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
bool CAI_OperatorBehavior::CanSelectSchedule() |
|
{ |
|
if ( m_hGoalEntity.Get() == NULL ) |
|
return false; |
|
|
|
if ( m_hPositionEnt.Get() == NULL ) |
|
return false; |
|
|
|
if( GetGoalEntity()->GetState() == OPERATOR_STATE_FINISHED ) |
|
{ |
|
m_hGoalEntity.Set(NULL); |
|
m_hPositionEnt.Set(NULL); |
|
return false; |
|
} |
|
|
|
if ( !GetOuter()->IsInterruptable() ) |
|
return false; |
|
|
|
if ( GetOuter()->m_NPCState == NPC_STATE_COMBAT || GetOuter()->m_NPCState == NPC_STATE_SCRIPT ) |
|
return false; |
|
|
|
// Don't grab NPCs who have been in combat recently |
|
if ( GetOuter()->GetLastEnemyTime() && (gpGlobals->curtime - GetOuter()->GetLastEnemyTime()) < 3.0 ) |
|
return false; |
|
|
|
if( !CanSeePositionEntity() ) |
|
return false; |
|
|
|
return true; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Output : int |
|
//----------------------------------------------------------------------------- |
|
int CAI_OperatorBehavior::SelectSchedule() |
|
{ |
|
if( !IsAtPositionEntity() ) |
|
{ |
|
GetGoalEntity()->m_OnBeginApproach.FireOutput( GetOuter(), GetOuter(), 0 ); |
|
return SCHED_OPERATOR_APPROACH_POSITION; |
|
} |
|
|
|
if( GetGoalEntity() && GetGoalEntity()->GetState() != OPERATOR_STATE_FINISHED) |
|
{ |
|
if( GetOuter()->GetActiveWeapon() && !GetOuter()->IsWeaponHolstered() ) |
|
{ |
|
GetOuter()->SetDesiredWeaponState( DESIREDWEAPONSTATE_HOLSTERED ); |
|
return SCHED_OPERATOR_WAIT_FOR_HOLSTER; |
|
} |
|
|
|
return SCHED_OPERATOR_OPERATE; |
|
} |
|
|
|
return BaseClass::SelectSchedule(); |
|
} |
|
|
|
|
|
//============================================================================= |
|
//============================================================================= |
|
// >AI_GOAL_OPERATOR |
|
//============================================================================= |
|
//============================================================================= |
|
LINK_ENTITY_TO_CLASS( ai_goal_operator, CAI_OperatorGoal ); |
|
|
|
BEGIN_DATADESC( CAI_OperatorGoal ) |
|
DEFINE_KEYFIELD( m_iState, FIELD_INTEGER, "state" ), |
|
DEFINE_KEYFIELD( m_iMoveTo, FIELD_INTEGER, "moveto" ), |
|
DEFINE_KEYFIELD( m_iszContextTarget, FIELD_STRING, "contexttarget" ), |
|
|
|
// Inputs |
|
DEFINE_INPUTFUNC( FIELD_VOID, "SetStateReady", InputSetStateReady ), |
|
DEFINE_INPUTFUNC( FIELD_VOID, "SetStateFinished", InputSetStateFinished ), |
|
DEFINE_INPUTFUNC( FIELD_VOID, "Activate", InputActivate ), |
|
|
|
// Outputs |
|
DEFINE_OUTPUT( m_OnBeginApproach, "OnBeginApproach" ), |
|
DEFINE_OUTPUT( m_OnMakeReady, "OnMakeReady" ), |
|
DEFINE_OUTPUT( m_OnBeginOperating, "OnBeginOperating" ), |
|
DEFINE_OUTPUT( m_OnFinished, "OnFinished" ), |
|
END_DATADESC() |
|
|
|
//----------------------------------------------------------------------------- |
|
//----------------------------------------------------------------------------- |
|
void CAI_OperatorGoal::EnableGoal( CAI_BaseNPC *pAI ) |
|
{ |
|
CAI_OperatorBehavior *pBehavior; |
|
|
|
if ( !pAI->GetBehavior( &pBehavior ) ) |
|
{ |
|
return; |
|
} |
|
|
|
CBaseEntity *pPosition = gEntList.FindEntityByName(NULL, m_target); |
|
|
|
if( !pPosition ) |
|
{ |
|
DevMsg("ai_goal_operator called %s with invalid position ent!\n", GetDebugName() ); |
|
return; |
|
} |
|
|
|
|
|
CBaseEntity *pContextTarget = NULL; |
|
|
|
if( m_iszContextTarget != NULL_STRING ) |
|
{ |
|
pContextTarget = gEntList.FindEntityByName( NULL, m_iszContextTarget ); |
|
} |
|
|
|
pBehavior->SetParameters(this, pPosition, pContextTarget); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : &inputdata - |
|
//----------------------------------------------------------------------------- |
|
void CAI_OperatorGoal::InputActivate( inputdata_t &inputdata ) |
|
{ |
|
BaseClass::InputActivate( inputdata ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : &inputdata - |
|
//----------------------------------------------------------------------------- |
|
void CAI_OperatorGoal::InputDeactivate( inputdata_t &inputdata ) |
|
{ |
|
BaseClass::InputDeactivate( inputdata ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
//----------------------------------------------------------------------------- |
|
void CAI_OperatorGoal::InputSetStateReady( inputdata_t &inputdata ) |
|
{ |
|
m_iState = OPERATOR_STATE_READY; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
//----------------------------------------------------------------------------- |
|
void CAI_OperatorGoal::InputSetStateFinished( inputdata_t &inputdata ) |
|
{ |
|
m_iState = OPERATOR_STATE_FINISHED; |
|
m_OnFinished.FireOutput( NULL, NULL, 0 ); |
|
} |
|
|
|
//============================================================================= |
|
//============================================================================= |
|
// >SCHEDULES |
|
//============================================================================= |
|
//============================================================================= |
|
AI_BEGIN_CUSTOM_SCHEDULE_PROVIDER( CAI_OperatorBehavior ) |
|
|
|
DECLARE_TASK( TASK_OPERATOR_GET_PATH_TO_POSITION ) |
|
DECLARE_TASK( TASK_OPERATOR_START_PATH ) |
|
DECLARE_TASK( TASK_OPERATOR_OPERATE ) |
|
|
|
DECLARE_CONDITION( COND_OPERATOR_LOST_SIGHT_OF_POSITION ) |
|
|
|
//========================================================= |
|
//========================================================= |
|
DEFINE_SCHEDULE |
|
( |
|
SCHED_OPERATOR_APPROACH_POSITION, |
|
" Tasks" |
|
" TASK_OPERATOR_GET_PATH_TO_POSITION 0" |
|
" TASK_OPERATOR_START_PATH 0" |
|
" TASK_WAIT_FOR_MOVEMENT 0" |
|
" TASK_STOP_MOVING 0" |
|
|
|
" " |
|
" Interrupts" |
|
" COND_NEW_ENEMY" |
|
" COND_HEAR_DANGER" |
|
" COND_OPERATOR_LOST_SIGHT_OF_POSITION" |
|
) |
|
|
|
//========================================================= |
|
//========================================================= |
|
DEFINE_SCHEDULE |
|
( |
|
SCHED_OPERATOR_OPERATE, |
|
" Tasks" |
|
" TASK_WAIT 0.2" // Allow pending entity I/O to settle |
|
" TASK_OPERATOR_OPERATE 0" |
|
" TASK_WAIT_INDEFINITE 0" |
|
" " |
|
" Interrupts" |
|
" COND_PROVOKED" |
|
) |
|
|
|
//========================================================= |
|
//========================================================= |
|
DEFINE_SCHEDULE |
|
( |
|
SCHED_OPERATOR_WAIT_FOR_HOLSTER, |
|
" Tasks" |
|
" TASK_WAIT 1.0" |
|
" " |
|
" Interrupts" |
|
" " |
|
) |
|
|
|
AI_END_CUSTOM_SCHEDULE_PROVIDER()
|
|
|