|
|
|
//========= Copyright Valve Corporation, All rights reserved. ============//
|
|
|
|
//
|
|
|
|
// Purpose:
|
|
|
|
//
|
|
|
|
//=============================================================================//
|
|
|
|
|
|
|
|
#include "cbase.h"
|
|
|
|
|
|
|
|
#include "ai_behavior_police.h"
|
|
|
|
#include "ai_navigator.h"
|
|
|
|
#include "ai_memory.h"
|
|
|
|
#include "collisionutils.h"
|
|
|
|
#include "npc_metropolice.h"
|
|
|
|
|
|
|
|
// memdbgon must be the last include file in a .cpp file!!!
|
|
|
|
#include "tier0/memdbgon.h"
|
|
|
|
|
|
|
|
BEGIN_DATADESC( CAI_PolicingBehavior )
|
|
|
|
|
|
|
|
DEFINE_FIELD( m_bEnabled, FIELD_BOOLEAN ),
|
|
|
|
DEFINE_FIELD( m_bStartPolicing, FIELD_BOOLEAN ),
|
|
|
|
DEFINE_FIELD( m_hPoliceGoal, FIELD_EHANDLE ),
|
|
|
|
DEFINE_FIELD( m_flNextHarassTime, FIELD_TIME ),
|
|
|
|
DEFINE_FIELD( m_flAggressiveTime, FIELD_TIME ),
|
|
|
|
DEFINE_FIELD( m_nNumWarnings, FIELD_INTEGER ),
|
|
|
|
DEFINE_FIELD( m_bTargetIsHostile, FIELD_BOOLEAN ),
|
|
|
|
DEFINE_FIELD( m_flTargetHostileTime,FIELD_TIME ),
|
|
|
|
|
|
|
|
END_DATADESC();
|
|
|
|
|
|
|
|
CAI_PolicingBehavior::CAI_PolicingBehavior( void )
|
|
|
|
{
|
|
|
|
m_bEnabled = false;
|
|
|
|
m_nNumWarnings = 0;
|
|
|
|
m_bTargetIsHostile = false;
|
|
|
|
m_hPoliceGoal = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Purpose:
|
|
|
|
// Output : Returns true on success, false on failure.
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool CAI_PolicingBehavior::TargetIsHostile( void )
|
|
|
|
{
|
|
|
|
if ( ( m_flTargetHostileTime < gpGlobals->curtime ) && ( !m_bTargetIsHostile ) )
|
|
|
|
return false;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Purpose:
|
|
|
|
// Input : *pGoal -
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void CAI_PolicingBehavior::Enable( CAI_PoliceGoal *pGoal )
|
|
|
|
{
|
|
|
|
m_hPoliceGoal = pGoal;
|
|
|
|
m_bEnabled = true;
|
|
|
|
|
|
|
|
m_bStartPolicing = true;
|
|
|
|
|
|
|
|
// Update ourselves immediately
|
|
|
|
GetOuter()->ClearSchedule( "Enable police behavior" );
|
|
|
|
//NotifyChangeBehaviorStatus( GetOuter()->IsInAScript() );
|
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Purpose:
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void CAI_PolicingBehavior::Disable( void )
|
|
|
|
{
|
|
|
|
m_hPoliceGoal = NULL;
|
|
|
|
m_bEnabled = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Purpose:
|
|
|
|
// Output : Returns true on success, false on failure.
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool CAI_PolicingBehavior::CanSelectSchedule( void )
|
|
|
|
{
|
|
|
|
// Must be activated and valid
|
|
|
|
if ( IsEnabled() == false || !m_hPoliceGoal || !m_hPoliceGoal->GetTarget() )
|
|
|
|
return false;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Purpose:
|
|
|
|
// Input : false -
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void CAI_PolicingBehavior::HostSetBatonState( bool state )
|
|
|
|
{
|
|
|
|
// If we're a cop, turn the baton on
|
|
|
|
CNPC_MetroPolice *pCop = dynamic_cast<CNPC_MetroPolice *>(GetOuter());
|
|
|
|
|
|
|
|
if ( pCop != NULL )
|
|
|
|
{
|
|
|
|
pCop->SetBatonState( state );
|
|
|
|
pCop->SetTarget( m_hPoliceGoal->GetTarget() );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Purpose:
|
|
|
|
// Input : false -
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool CAI_PolicingBehavior::HostBatonIsOn( void )
|
|
|
|
{
|
|
|
|
// If we're a cop, turn the baton on
|
|
|
|
CNPC_MetroPolice *pCop = dynamic_cast<CNPC_MetroPolice *>(GetOuter());
|
|
|
|
if ( pCop )
|
|
|
|
return pCop->BatonActive();
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Purpose:
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void CAI_PolicingBehavior::HostSpeakSentence( const char *pSentence, SentencePriority_t nSoundPriority, SentenceCriteria_t nCriteria )
|
|
|
|
{
|
|
|
|
// If we're a cop, turn the baton on
|
|
|
|
CNPC_MetroPolice *pCop = dynamic_cast<CNPC_MetroPolice *>(GetOuter());
|
|
|
|
|
|
|
|
if ( pCop != NULL )
|
|
|
|
{
|
|
|
|
CAI_Sentence< CNPC_MetroPolice > *pSentences = pCop->GetSentences();
|
|
|
|
|
|
|
|
pSentences->Speak( pSentence, nSoundPriority, nCriteria );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Purpose:
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void CAI_PolicingBehavior::BuildScheduleTestBits( void )
|
|
|
|
{
|
|
|
|
if ( IsCurSchedule( SCHED_IDLE_STAND ) || IsCurSchedule( SCHED_ALERT_STAND ) )
|
|
|
|
{
|
|
|
|
if ( m_flNextHarassTime < gpGlobals->curtime )
|
|
|
|
{
|
|
|
|
GetOuter()->SetCustomInterruptCondition( GetClassScheduleIdSpace()->ConditionLocalToGlobal( COND_POLICE_TARGET_TOO_CLOSE_HARASS ) );
|
|
|
|
}
|
|
|
|
|
|
|
|
GetOuter()->SetCustomInterruptCondition( GetClassScheduleIdSpace()->ConditionLocalToGlobal( COND_POLICE_TARGET_TOO_CLOSE_SUPPRESS ) );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Purpose:
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void CAI_PolicingBehavior::GatherConditions( void )
|
|
|
|
{
|
|
|
|
BaseClass::GatherConditions();
|
|
|
|
|
|
|
|
// Mapmaker may have removed our goal while we're running our schedule
|
|
|
|
if ( !m_hPoliceGoal )
|
|
|
|
{
|
|
|
|
Disable();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
ClearCondition( COND_POLICE_TARGET_TOO_CLOSE_HARASS );
|
|
|
|
ClearCondition( COND_POLICE_TARGET_TOO_CLOSE_SUPPRESS );
|
|
|
|
|
|
|
|
CBaseEntity *pTarget = m_hPoliceGoal->GetTarget();
|
|
|
|
|
|
|
|
if ( pTarget == NULL )
|
|
|
|
{
|
|
|
|
DevMsg( "ai_goal_police with NULL target entity!\n" );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// See if we need to knock out our target immediately
|
|
|
|
if ( ShouldKnockOutTarget( pTarget ) )
|
|
|
|
{
|
|
|
|
SetCondition( COND_POLICE_TARGET_TOO_CLOSE_SUPPRESS );
|
|
|
|
}
|
|
|
|
|
|
|
|
float flDistSqr = ( m_hPoliceGoal->WorldSpaceCenter() - pTarget->WorldSpaceCenter() ).Length2DSqr();
|
|
|
|
float radius = ( m_hPoliceGoal->GetRadius() * PATROL_RADIUS_RATIO );
|
|
|
|
float zDiff = fabs( m_hPoliceGoal->WorldSpaceCenter().z - pTarget->WorldSpaceCenter().z );
|
|
|
|
|
|
|
|
// If we're too far away, don't bother
|
|
|
|
if ( flDistSqr < (radius*radius) && zDiff < 32.0f )
|
|
|
|
{
|
|
|
|
SetCondition( COND_POLICE_TARGET_TOO_CLOSE_HARASS );
|
|
|
|
|
|
|
|
if ( flDistSqr < (m_hPoliceGoal->GetRadius()*m_hPoliceGoal->GetRadius()) )
|
|
|
|
{
|
|
|
|
SetCondition( COND_POLICE_TARGET_TOO_CLOSE_SUPPRESS );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we're supposed to stop chasing (aggression over), return
|
|
|
|
if ( m_bTargetIsHostile && m_flAggressiveTime < gpGlobals->curtime && IsCurSchedule(SCHED_CHASE_ENEMY) )
|
|
|
|
{
|
|
|
|
// Force me to re-evaluate my schedule
|
|
|
|
GetOuter()->ClearSchedule( "Stopped chasing, aggression over" );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// We're taking cover from danger
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void CAI_PolicingBehavior::AnnouncePolicing( void )
|
|
|
|
{
|
|
|
|
// We're policing
|
|
|
|
static const char *pWarnings[3] =
|
|
|
|
{
|
|
|
|
"METROPOLICE_MOVE_ALONG_A",
|
|
|
|
"METROPOLICE_MOVE_ALONG_B",
|
|
|
|
"METROPOLICE_MOVE_ALONG_C",
|
|
|
|
};
|
|
|
|
|
|
|
|
if ( m_nNumWarnings <= 3 )
|
|
|
|
{
|
|
|
|
HostSpeakSentence( pWarnings[ m_nNumWarnings - 1 ], SENTENCE_PRIORITY_MEDIUM, SENTENCE_CRITERIA_NORMAL );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// We loop at m_nNumWarnings == 4 for players who aren't moving
|
|
|
|
// but still pissing us off, and we're not allowed to do anything about it. (i.e. can't leave post)
|
|
|
|
// First two sentences sound pretty good, so randomly pick one of them.
|
|
|
|
int iSentence = RandomInt( 0, 1 );
|
|
|
|
HostSpeakSentence( pWarnings[ iSentence ], SENTENCE_PRIORITY_MEDIUM, SENTENCE_CRITERIA_NORMAL );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Purpose:
|
|
|
|
// Input : scheduleType -
|
|
|
|
// Output : int
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
int CAI_PolicingBehavior::TranslateSchedule( int scheduleType )
|
|
|
|
{
|
|
|
|
if ( scheduleType == SCHED_CHASE_ENEMY )
|
|
|
|
{
|
|
|
|
if ( m_hPoliceGoal->ShouldRemainAtPost() && !MaintainGoalPosition() )
|
|
|
|
return BaseClass::TranslateSchedule( SCHED_COMBAT_FACE );
|
|
|
|
}
|
|
|
|
|
|
|
|
return BaseClass::TranslateSchedule( scheduleType );
|
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Purpose:
|
|
|
|
// Input : newActivity -
|
|
|
|
// Output : Activity
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
Activity CAI_PolicingBehavior::NPC_TranslateActivity( Activity newActivity )
|
|
|
|
{
|
|
|
|
// See which harassment to play
|
|
|
|
if ( newActivity == ACT_POLICE_HARASS1 )
|
|
|
|
{
|
|
|
|
switch( m_nNumWarnings )
|
|
|
|
{
|
|
|
|
case 1:
|
|
|
|
return (Activity) ACT_POLICE_HARASS1;
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
case 2:
|
|
|
|
return (Activity) ACT_POLICE_HARASS2;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return BaseClass::NPC_TranslateActivity( newActivity );
|
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Purpose:
|
|
|
|
// Output : CBaseEntity
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
CBaseEntity *CAI_PolicingBehavior::GetGoalTarget( void )
|
|
|
|
{
|
|
|
|
if ( m_hPoliceGoal == NULL )
|
|
|
|
{
|
|
|
|
//NOTENOTE: This has been called before the behavior is actually active, or the goal has gone invalid
|
|
|
|
Assert(0);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
return m_hPoliceGoal->GetTarget();
|
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Purpose:
|
|
|
|
// Input : time -
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void CAI_PolicingBehavior::SetTargetHostileDuration( float time )
|
|
|
|
{
|
|
|
|
m_flTargetHostileTime = gpGlobals->curtime + time;
|
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Purpose:
|
|
|
|
// Input : *pTask -
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void CAI_PolicingBehavior::StartTask( const Task_t *pTask )
|
|
|
|
{
|
|
|
|
switch (pTask->iTask)
|
|
|
|
{
|
|
|
|
case TASK_POLICE_GET_PATH_TO_HARASS_GOAL:
|
|
|
|
{
|
|
|
|
Vector harassDir = ( m_hPoliceGoal->GetTarget()->WorldSpaceCenter() - WorldSpaceCenter() );
|
|
|
|
float flDist = VectorNormalize( harassDir );
|
|
|
|
|
|
|
|
// See if we're already close enough
|
|
|
|
if ( flDist < pTask->flTaskData )
|
|
|
|
{
|
|
|
|
TaskComplete();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
float flInter1, flInter2;
|
|
|
|
Vector harassPos = GetAbsOrigin() + ( harassDir * ( flDist - pTask->flTaskData ) );
|
|
|
|
|
|
|
|
// Find a point on our policing radius to stand on
|
|
|
|
if ( IntersectInfiniteRayWithSphere( GetAbsOrigin(), harassDir, m_hPoliceGoal->GetAbsOrigin(), m_hPoliceGoal->GetRadius(), &flInter1, &flInter2 ) )
|
|
|
|
{
|
|
|
|
Vector vPos = m_hPoliceGoal->GetAbsOrigin() + harassDir * ( MAX( flInter1, flInter2 ) );
|
|
|
|
|
|
|
|
// See how far away the default one is
|
|
|
|
float testDist = UTIL_DistApprox2D( m_hPoliceGoal->GetAbsOrigin(), harassPos );
|
|
|
|
|
|
|
|
// If our other goal is closer, choose it
|
|
|
|
if ( testDist > UTIL_DistApprox2D( m_hPoliceGoal->GetAbsOrigin(), vPos ) )
|
|
|
|
{
|
|
|
|
harassPos = vPos;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( GetNavigator()->SetGoal( harassPos, pTask->flTaskData ) )
|
|
|
|
{
|
|
|
|
GetNavigator()->SetMovementActivity( (Activity) ACT_WALK_ANGRY );
|
|
|
|
GetNavigator()->SetArrivalDirection( m_hPoliceGoal->GetTarget() );
|
|
|
|
TaskComplete();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
TaskFail( FAIL_NO_ROUTE );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case TASK_POLICE_GET_PATH_TO_POLICE_GOAL:
|
|
|
|
{
|
|
|
|
if ( GetNavigator()->SetGoal( m_hPoliceGoal->GetAbsOrigin(), pTask->flTaskData ) )
|
|
|
|
{
|
|
|
|
GetNavigator()->SetArrivalDirection( m_hPoliceGoal->GetAbsAngles() );
|
|
|
|
TaskComplete();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
TaskFail( FAIL_NO_ROUTE );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case TASK_POLICE_ANNOUNCE_HARASS:
|
|
|
|
{
|
|
|
|
AnnouncePolicing();
|
|
|
|
|
|
|
|
// Randomly say this again in the future
|
|
|
|
m_flNextHarassTime = gpGlobals->curtime + random->RandomInt( 4, 6 );
|
|
|
|
|
|
|
|
// Scatter rubber-neckers
|
|
|
|
CSoundEnt::InsertSound( SOUND_MOVE_AWAY, GetAbsOrigin(), 256.0f, 2.0f, GetOuter() );
|
|
|
|
}
|
|
|
|
TaskComplete();
|
|
|
|
break;
|
|
|
|
|
|
|
|
case TASK_POLICE_FACE_ALONG_GOAL:
|
|
|
|
{
|
|
|
|
// We may have lost our police goal in the 2 seconds we wait before this task
|
|
|
|
if ( m_hPoliceGoal )
|
|
|
|
{
|
|
|
|
GetMotor()->SetIdealYaw( m_hPoliceGoal->GetAbsAngles().y );
|
|
|
|
GetOuter()->SetTurnActivity();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
BaseClass::StartTask( pTask );
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Purpose:
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void CAI_PolicingBehavior::RunTask( const Task_t *pTask )
|
|
|
|
{
|
|
|
|
switch ( pTask->iTask )
|
|
|
|
{
|
|
|
|
case TASK_POLICE_FACE_ALONG_GOAL:
|
|
|
|
{
|
|
|
|
GetMotor()->UpdateYaw();
|
|
|
|
|
|
|
|
if ( GetOuter()->FacingIdeal() )
|
|
|
|
{
|
|
|
|
TaskComplete();
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
default:
|
|
|
|
BaseClass::RunTask( pTask);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Purpose:
|
|
|
|
// Output : Returns true on success, false on failure.
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool CAI_PolicingBehavior::MaintainGoalPosition( void )
|
|
|
|
{
|
|
|
|
Vector vecOrg = GetAbsOrigin();
|
|
|
|
Vector vecTarget = m_hPoliceGoal->GetAbsOrigin();
|
|
|
|
|
|
|
|
// Allow some slop on Z
|
|
|
|
if ( fabs(vecOrg.z - vecTarget.z) > 64 )
|
|
|
|
return true;
|
|
|
|
|
|
|
|
// Need to be very close on X/Y
|
|
|
|
if ( (vecOrg - vecTarget).Length2D() > 16 )
|
|
|
|
return true;
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Purpose:
|
|
|
|
// Output : Returns true on success, false on failure.
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool CAI_PolicingBehavior::ShouldKnockOutTarget( CBaseEntity *pTarget )
|
|
|
|
{
|
|
|
|
if ( m_hPoliceGoal == NULL )
|
|
|
|
{
|
|
|
|
//NOTENOTE: This has been called before the behavior is actually active, or the goal has gone invalid
|
|
|
|
Assert(0);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool bVisible = GetOuter()->FVisible( pTarget );
|
|
|
|
return m_hPoliceGoal->ShouldKnockOutTarget( pTarget->WorldSpaceCenter(), bVisible );
|
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Purpose:
|
|
|
|
// Input : *pTarget -
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void CAI_PolicingBehavior::KnockOutTarget( CBaseEntity *pTarget )
|
|
|
|
{
|
|
|
|
if ( m_hPoliceGoal == NULL )
|
|
|
|
{
|
|
|
|
//NOTENOTE: This has been called before the behavior is actually active, or the goal has gone invalid
|
|
|
|
Assert(0);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_hPoliceGoal->KnockOutTarget( pTarget );
|
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Purpose:
|
|
|
|
// Output : int
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
int CAI_PolicingBehavior::SelectSuppressSchedule( void )
|
|
|
|
{
|
|
|
|
CBaseEntity *pTarget = m_hPoliceGoal->GetTarget();
|
|
|
|
|
|
|
|
m_flAggressiveTime = gpGlobals->curtime + 4.0f;
|
|
|
|
|
|
|
|
if ( m_bTargetIsHostile == false )
|
|
|
|
{
|
|
|
|
// Mark this as a valid target
|
|
|
|
m_bTargetIsHostile = true;
|
|
|
|
|
|
|
|
// Attack the target
|
|
|
|
GetOuter()->SetEnemy( pTarget );
|
|
|
|
GetOuter()->SetState( NPC_STATE_COMBAT );
|
|
|
|
GetOuter()->UpdateEnemyMemory( pTarget, pTarget->GetAbsOrigin() );
|
|
|
|
|
|
|
|
HostSetBatonState( true );
|
|
|
|
|
|
|
|
// Remember that we're angry with the target
|
|
|
|
m_nNumWarnings = POLICE_MAX_WARNINGS;
|
|
|
|
|
|
|
|
// We need to let the system pickup the new enemy and deal with it on the next frame
|
|
|
|
return SCHED_COMBAT_FACE;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we're supposed to stand still, then we need to show aggression
|
|
|
|
if ( m_hPoliceGoal->ShouldRemainAtPost() )
|
|
|
|
{
|
|
|
|
// If we're off our mark, fight to it
|
|
|
|
if ( MaintainGoalPosition() )
|
|
|
|
{
|
|
|
|
return SCHED_CHASE_ENEMY;
|
|
|
|
}
|
|
|
|
|
|
|
|
//FIXME: This needs to be a more aggressive warning to the player
|
|
|
|
if ( m_flNextHarassTime < gpGlobals->curtime )
|
|
|
|
{
|
|
|
|
return SCHED_POLICE_WARN_TARGET;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return SCHED_COMBAT_FACE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return SCHED_CHASE_ENEMY;
|
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Purpose:
|
|
|
|
// Output : int
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
int CAI_PolicingBehavior::SelectHarassSchedule( void )
|
|
|
|
{
|
|
|
|
CBaseEntity *pTarget = m_hPoliceGoal->GetTarget();
|
|
|
|
|
|
|
|
m_flAggressiveTime = gpGlobals->curtime + 4.0f;
|
|
|
|
|
|
|
|
// If we just started to police, make sure we're on our mark
|
|
|
|
if ( MaintainGoalPosition() )
|
|
|
|
return SCHED_POLICE_RETURN_FROM_HARASS;
|
|
|
|
|
|
|
|
// Look at the target if they're too close
|
|
|
|
GetOuter()->AddLookTarget( pTarget, 0.5f, 5.0f );
|
|
|
|
|
|
|
|
// Say something if it's been long enough
|
|
|
|
if ( m_flNextHarassTime < gpGlobals->curtime )
|
|
|
|
{
|
|
|
|
// Gesture the player away
|
|
|
|
GetOuter()->SetTarget( pTarget );
|
|
|
|
|
|
|
|
// Send outputs for each level of warning
|
|
|
|
if ( m_nNumWarnings == 0 )
|
|
|
|
{
|
|
|
|
m_hPoliceGoal->FireWarningLevelOutput( 1 );
|
|
|
|
}
|
|
|
|
else if ( m_nNumWarnings == 1 )
|
|
|
|
{
|
|
|
|
m_hPoliceGoal->FireWarningLevelOutput( 2 );
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( m_nNumWarnings < POLICE_MAX_WARNINGS )
|
|
|
|
{
|
|
|
|
m_nNumWarnings++;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we're over our limit, just suppress the offender
|
|
|
|
if ( m_nNumWarnings >= POLICE_MAX_WARNINGS )
|
|
|
|
{
|
|
|
|
if ( m_bTargetIsHostile == false )
|
|
|
|
{
|
|
|
|
// Mark the target as a valid target
|
|
|
|
m_bTargetIsHostile = true;
|
|
|
|
|
|
|
|
GetOuter()->SetEnemy( pTarget );
|
|
|
|
GetOuter()->SetState( NPC_STATE_COMBAT );
|
|
|
|
GetOuter()->UpdateEnemyMemory( pTarget, pTarget->GetAbsOrigin() );
|
|
|
|
HostSetBatonState( true );
|
|
|
|
|
|
|
|
m_hPoliceGoal->FireWarningLevelOutput( 4 );
|
|
|
|
|
|
|
|
return SCHED_COMBAT_FACE;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( m_hPoliceGoal->ShouldRemainAtPost() == false )
|
|
|
|
return SCHED_CHASE_ENEMY;
|
|
|
|
}
|
|
|
|
|
|
|
|
// On our last warning, approach the target
|
|
|
|
if ( m_nNumWarnings == (POLICE_MAX_WARNINGS-1) )
|
|
|
|
{
|
|
|
|
m_hPoliceGoal->FireWarningLevelOutput( 3 );
|
|
|
|
|
|
|
|
GetOuter()->SetTarget( pTarget );
|
|
|
|
|
|
|
|
HostSetBatonState( true );
|
|
|
|
|
|
|
|
if ( m_hPoliceGoal->ShouldRemainAtPost() == false )
|
|
|
|
return SCHED_POLICE_HARASS_TARGET;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Otherwise just verbally warn him
|
|
|
|
return SCHED_POLICE_WARN_TARGET;
|
|
|
|
}
|
|
|
|
|
|
|
|
return SCHED_NONE;
|
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Purpose:
|
|
|
|
// Output : int
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
int CAI_PolicingBehavior::SelectSchedule( void )
|
|
|
|
{
|
|
|
|
CBaseEntity *pTarget = m_hPoliceGoal->GetTarget();
|
|
|
|
|
|
|
|
// Validate our target
|
|
|
|
if ( pTarget == NULL )
|
|
|
|
{
|
|
|
|
DevMsg( "ai_goal_police with NULL target entity!\n" );
|
|
|
|
|
|
|
|
// Turn us off
|
|
|
|
Disable();
|
|
|
|
return SCHED_NONE;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Attack if we're supposed to
|
|
|
|
if ( ( m_flAggressiveTime >= gpGlobals->curtime ) && HasCondition( COND_CAN_MELEE_ATTACK1 ) )
|
|
|
|
{
|
|
|
|
return SCHED_MELEE_ATTACK1;
|
|
|
|
}
|
|
|
|
|
|
|
|
// See if we should immediately begin to attack our target
|
|
|
|
if ( HasCondition( COND_POLICE_TARGET_TOO_CLOSE_SUPPRESS ) )
|
|
|
|
{
|
|
|
|
return SelectSuppressSchedule();
|
|
|
|
}
|
|
|
|
|
|
|
|
int newSchedule = SCHED_NONE;
|
|
|
|
|
|
|
|
// See if we're harassing
|
|
|
|
if ( HasCondition( COND_POLICE_TARGET_TOO_CLOSE_HARASS ) )
|
|
|
|
{
|
|
|
|
newSchedule = SelectHarassSchedule();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Return that schedule if it was found
|
|
|
|
if ( newSchedule != SCHED_NONE )
|
|
|
|
return newSchedule;
|
|
|
|
|
|
|
|
// If our enemy is set, fogeda'bout it!
|
|
|
|
if ( m_flAggressiveTime < gpGlobals->curtime )
|
|
|
|
{
|
|
|
|
// Return to your initial spot
|
|
|
|
if ( GetEnemy() )
|
|
|
|
{
|
|
|
|
GetOuter()->SetEnemy( NULL );
|
|
|
|
GetOuter()->SetState( NPC_STATE_ALERT );
|
|
|
|
GetOuter()->GetEnemies()->RefreshMemories();
|
|
|
|
}
|
|
|
|
|
|
|
|
HostSetBatonState( false );
|
|
|
|
m_bTargetIsHostile = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we just started to police, make sure we're on our mark
|
|
|
|
if ( MaintainGoalPosition() )
|
|
|
|
return SCHED_POLICE_RETURN_FROM_HARASS;
|
|
|
|
|
|
|
|
// If I've got my baton on, keep looking at the target
|
|
|
|
if ( HostBatonIsOn() )
|
|
|
|
return SCHED_POLICE_TRACK_TARGET;
|
|
|
|
|
|
|
|
// Re-align myself to the goal angles if I've strayed
|
|
|
|
if ( fabs(UTIL_AngleDiff( GetAbsAngles().y, m_hPoliceGoal->GetAbsAngles().y )) > 15 )
|
|
|
|
return SCHED_POLICE_FACE_ALONG_GOAL;
|
|
|
|
|
|
|
|
return SCHED_IDLE_STAND;
|
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Purpose:
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
int CAI_PolicingBehavior::SelectFailSchedule( int failedSchedule, int failedTask, AI_TaskFailureCode_t taskFailCode )
|
|
|
|
{
|
|
|
|
if ( failedSchedule == SCHED_CHASE_ENEMY )
|
|
|
|
{
|
|
|
|
// We've failed to chase our enemy, return to where we were came from
|
|
|
|
if ( MaintainGoalPosition() )
|
|
|
|
return SCHED_POLICE_RETURN_FROM_HARASS;
|
|
|
|
|
|
|
|
return SCHED_POLICE_WARN_TARGET;
|
|
|
|
}
|
|
|
|
|
|
|
|
return BaseClass::SelectFailSchedule( failedSchedule, failedTask, taskFailCode );
|
|
|
|
}
|
|
|
|
|
|
|
|
//-------------------------------------
|
|
|
|
|
|
|
|
AI_BEGIN_CUSTOM_SCHEDULE_PROVIDER( CAI_PolicingBehavior )
|
|
|
|
|
|
|
|
DECLARE_CONDITION( COND_POLICE_TARGET_TOO_CLOSE_HARASS );
|
|
|
|
DECLARE_CONDITION( COND_POLICE_TARGET_TOO_CLOSE_SUPPRESS );
|
|
|
|
|
|
|
|
DECLARE_TASK( TASK_POLICE_GET_PATH_TO_HARASS_GOAL );
|
|
|
|
DECLARE_TASK( TASK_POLICE_GET_PATH_TO_POLICE_GOAL );
|
|
|
|
DECLARE_TASK( TASK_POLICE_ANNOUNCE_HARASS );
|
|
|
|
DECLARE_TASK( TASK_POLICE_FACE_ALONG_GOAL );
|
|
|
|
|
|
|
|
DEFINE_SCHEDULE
|
|
|
|
(
|
|
|
|
SCHED_POLICE_WARN_TARGET,
|
|
|
|
|
|
|
|
" Tasks"
|
|
|
|
" TASK_STOP_MOVING 0"
|
|
|
|
" TASK_FACE_TARGET 0"
|
|
|
|
" TASK_POLICE_ANNOUNCE_HARASS 0"
|
|
|
|
" TASK_PLAY_SEQUENCE ACTIVITY:ACT_POLICE_HARASS1"
|
|
|
|
""
|
|
|
|
" Interrupts"
|
|
|
|
" COND_POLICE_TARGET_TOO_CLOSE_SUPPRESS"
|
|
|
|
);
|
|
|
|
|
|
|
|
DEFINE_SCHEDULE
|
|
|
|
(
|
|
|
|
SCHED_POLICE_HARASS_TARGET,
|
|
|
|
|
|
|
|
" Tasks"
|
|
|
|
" TASK_STOP_MOVING 0"
|
|
|
|
" TASK_FACE_TARGET 0"
|
|
|
|
" TASK_POLICE_GET_PATH_TO_HARASS_GOAL 64"
|
|
|
|
" TASK_WAIT_FOR_MOVEMENT 0"
|
|
|
|
" TASK_POLICE_ANNOUNCE_HARASS 0"
|
|
|
|
" TASK_PLAY_SEQUENCE ACTIVITY:ACT_POLICE_HARASS1"
|
|
|
|
""
|
|
|
|
" Interrupts"
|
|
|
|
" COND_POLICE_TARGET_TOO_CLOSE_SUPPRESS"
|
|
|
|
);
|
|
|
|
|
|
|
|
DEFINE_SCHEDULE
|
|
|
|
(
|
|
|
|
SCHED_POLICE_SUPPRESS_TARGET,
|
|
|
|
|
|
|
|
" Tasks"
|
|
|
|
" TASK_STOP_MOVING 0"
|
|
|
|
" TASK_FACE_TARGET 0"
|
|
|
|
" TASK_POLICE_ANNOUNCE_HARASS 0"
|
|
|
|
" TASK_PLAY_SEQUENCE ACTIVITY:ACT_POLICE_HARASS1"
|
|
|
|
""
|
|
|
|
" Interrupts"
|
|
|
|
);
|
|
|
|
|
|
|
|
DEFINE_SCHEDULE
|
|
|
|
(
|
|
|
|
SCHED_POLICE_RETURN_FROM_HARASS,
|
|
|
|
|
|
|
|
" Tasks"
|
|
|
|
" TASK_STOP_MOVING 0"
|
|
|
|
" TASK_POLICE_GET_PATH_TO_POLICE_GOAL 16"
|
|
|
|
" TASK_WALK_PATH 0"
|
|
|
|
" TASK_WAIT_FOR_MOVEMENT 0"
|
|
|
|
" TASK_STOP_MOVING 0"
|
|
|
|
""
|
|
|
|
" Interrupts"
|
|
|
|
" COND_POLICE_TARGET_TOO_CLOSE_SUPPRESS"
|
|
|
|
);
|
|
|
|
|
|
|
|
DEFINE_SCHEDULE
|
|
|
|
(
|
|
|
|
SCHED_POLICE_TRACK_TARGET,
|
|
|
|
|
|
|
|
" Tasks"
|
|
|
|
" TASK_FACE_TARGET 0"
|
|
|
|
""
|
|
|
|
" Interrupts"
|
|
|
|
" COND_POLICE_TARGET_TOO_CLOSE_SUPPRESS"
|
|
|
|
);
|
|
|
|
|
|
|
|
DEFINE_SCHEDULE
|
|
|
|
(
|
|
|
|
SCHED_POLICE_FACE_ALONG_GOAL,
|
|
|
|
|
|
|
|
" Tasks"
|
|
|
|
" TASK_WAIT_RANDOM 2"
|
|
|
|
" TASK_POLICE_FACE_ALONG_GOAL 0"
|
|
|
|
""
|
|
|
|
" Interrupts"
|
|
|
|
" COND_POLICE_TARGET_TOO_CLOSE_SUPPRESS"
|
|
|
|
);
|
|
|
|
|
|
|
|
AI_END_CUSTOM_SCHEDULE_PROVIDER()
|