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