· 7 years ago · Feb 26, 2019, 09:06 PM
1//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
2//
3// Purpose:
4//
5//=============================================================================//
6
7#include "cbase.h"
8
9#include "ai_basenpc.h"
10#include "fmtstr.h"
11#include "activitylist.h"
12#include "animation.h"
13#include "basecombatweapon.h"
14#include "soundent.h"
15#include "decals.h"
16#include "entitylist.h"
17#include "eventqueue.h"
18#include "entityapi.h"
19#include "bitstring.h"
20#include "gamerules.h" // For g_pGameRules
21#include "scripted.h"
22#include "worldsize.h"
23#include "game.h"
24#include "shot_manipulator.h"
25
26#ifdef HL2_DLL
27#include "ai_interactions.h"
28#include "hl2_gamerules.h"
29#endif // HL2_DLL
30
31#include "ai_network.h"
32#include "ai_networkmanager.h"
33#include "ai_pathfinder.h"
34#include "ai_node.h"
35#include "ai_default.h"
36#include "ai_schedule.h"
37#include "ai_task.h"
38#include "ai_hull.h"
39#include "ai_moveprobe.h"
40#include "ai_hint.h"
41#include "ai_navigator.h"
42#include "ai_senses.h"
43#include "ai_squadslot.h"
44#include "ai_memory.h"
45#include "ai_squad.h"
46#include "ai_localnavigator.h"
47#include "ai_tacticalservices.h"
48#include "ai_behavior.h"
49#include "ai_dynamiclink.h"
50#include "AI_Criteria.h"
51#include "basegrenade_shared.h"
52#include "ammodef.h"
53#include "player.h"
54#include "sceneentity.h"
55#include "ndebugoverlay.h"
56#include "mathlib.h"
57#include "bone_setup.h"
58#include "IEffects.h"
59#include "vstdlib/random.h"
60#include "engine/IEngineSound.h"
61#include "vstdlib/strtools.h"
62#include "doors.h"
63#include "BasePropDoor.h"
64#include "saverestore_utlvector.h"
65#include "npcevent.h"
66#include "movevars_shared.h"
67#include "te_effect_dispatch.h"
68#include "globals.h"
69#include "saverestore_bitstring.h"
70#include "checksum_crc.h"
71#include "iservervehicle.h"
72#include "filters.h"
73#ifdef HL2_DLL
74#include "npc_bullseye.h"
75#include "hl2_player.h"
76#include "weapon_physcannon.h"
77#endif
78#include "waterbullet.h"
79#include "in_buttons.h"
80#include "eventlist.h"
81#include "globalstate.h"
82#include "physics_prop_ragdoll.h"
83#include "vphysics/friction.h"
84#include "physics_npc_solver.h"
85#include "tier0/vcrmode.h"
86#include "death_pose.h"
87#include "datacache/imdlcache.h"
88#ifdef HL2_EPISODIC
89#include "npc_alyx_episodic.h"
90#endif
91
92#include "env_debughistory.h"
93#include "collisionutils.h"
94
95extern ConVar sk_healthkit;
96
97// dvs: for opening doors -- these should probably not be here
98#include "ai_route.h"
99#include "ai_waypoint.h"
100
101// memdbgon must be the last include file in a .cpp file!!!
102#include "tier0/memdbgon.h"
103
104//#define DEBUG_LOOK
105
106bool RagdollManager_SaveImportant( CAI_BaseNPC *pNPC );
107
108#define MIN_PHYSICS_FLINCH_DAMAGE 5.0f
109
110#define NPC_GRENADE_FEAR_DIST 200
111#define MAX_GLASS_PENETRATION_DEPTH 16.0f
112
113#define FINDNAMEDENTITY_MAX_ENTITIES 32 // max number of entities to be considered for random entity selection in FindNamedEntity
114
115extern bool g_fDrawLines;
116extern short g_sModelIndexLaser; // holds the index for the laser beam
117extern short g_sModelIndexLaserDot; // holds the index for the laser beam dot
118
119// Debugging tools
120ConVar ai_no_select_box( "ai_no_select_box", "0" );
121
122ConVar ai_show_think_tolerance( "ai_show_think_tolerance", "0" );
123ConVar ai_debug_think_ticks( "ai_debug_think_ticks", "0" );
124ConVar ai_debug_doors( "ai_debug_doors", "0" );
125ConVar ai_debug_enemies( "ai_debug_enemies", "0" );
126
127ConVar ai_rebalance_thinks( "ai_rebalance_thinks", "1" );
128ConVar ai_use_efficiency( "ai_use_efficiency", "1" );
129ConVar ai_use_frame_think_limits( "ai_use_frame_think_limits", "1" );
130ConVar ai_default_efficient( "ai_default_efficient", ( IsXbox() ) ? "1" : "0" );
131ConVar ai_efficiency_override( "ai_efficiency_override", "0" );
132ConVar ai_debug_efficiency( "ai_debug_efficiency", "0" );
133ConVar ai_debug_dyninteractions( "ai_debug_dyninteractions", "0", FCVAR_NONE, "Debug the NPC dynamic interaction system." );
134ConVar ai_frametime_limit( "ai_frametime_limit", "50", FCVAR_NONE, "frametime limit for min efficiency AIE_NORMAL (in sec's)." );
135
136ConVar ai_use_think_optimizations( "ai_use_think_optimizations", "1" );
137
138ConVar ai_test_moveprobe_ignoresmall( "ai_test_moveprobe_ignoresmall", "0" );
139
140#ifndef _RETAIL
141#define ShouldUseEfficiency() ( ai_use_think_optimizations.GetBool() && ai_use_efficiency.GetBool() )
142#define ShouldUseFrameThinkLimits() ( ai_use_think_optimizations.GetBool() && ai_use_frame_think_limits.GetBool() )
143#define ShouldRebalanceThinks() ( ai_use_think_optimizations.GetBool() && ai_rebalance_thinks.GetBool() )
144#define ShouldDefaultEfficient() ( ai_use_think_optimizations.GetBool() && ai_default_efficient.GetBool() )
145#else
146#define ShouldUseEfficiency() ( true )
147#define ShouldUseFrameThinkLimits() ( true )
148#define ShouldRebalanceThinks() ( true )
149#define ShouldDefaultEfficient() ( true )
150#endif
151
152#ifndef _RETAIL
153#define DbgEnemyMsg if ( !ai_debug_enemies.GetBool() ) ; else DevMsg
154#else
155#define DbgEnemyMsg if ( 0 ) ; else DevMsg
156#endif
157
158#ifdef DEBUG_AI_FRAME_THINK_LIMITS
159#define DbgFrameLimitMsg DevMsg
160#else
161#define DbgFrameLimitMsg (void)
162#endif
163
164// NPC damage adjusters
165ConVar sk_npc_head( "sk_npc_head","2" );
166ConVar sk_npc_chest( "sk_npc_chest","1" );
167ConVar sk_npc_stomach( "sk_npc_stomach","1" );
168ConVar sk_npc_arm( "sk_npc_arm","1" );
169ConVar sk_npc_leg( "sk_npc_leg","1" );
170ConVar showhitlocation( "showhitlocation", "0" );
171
172// Squad debugging
173ConVar ai_debug_squads( "ai_debug_squads", "0" );
174ConVar ai_debug_loners( "ai_debug_loners", "0" );
175
176// Shoot trajectory
177ConVar ai_lead_time( "ai_lead_time","0.0" );
178ConVar ai_shot_stats( "ai_shot_stats", "0" );
179ConVar ai_shot_stats_term( "ai_shot_stats_term", "1000" );
180ConVar ai_shot_bias( "ai_shot_bias", "1.0" );
181
182ConVar ai_spread_defocused_cone_multiplier( "ai_spread_defocused_cone_multiplier","3.0" );
183ConVar ai_spread_cone_focus_time( "ai_spread_cone_focus_time","0.6" );
184ConVar ai_spread_pattern_focus_time( "ai_spread_pattern_focus_time","0.8" );
185
186ConVar ai_reaction_delay_idle( "ai_reaction_delay_idle","0.3" );
187ConVar ai_reaction_delay_alert( "ai_reaction_delay_alert", "0.1" );
188
189//-----------------------------------------------------------------------------
190//
191// Crude frame timings
192//
193
194CFastTimer g_AIRunTimer;
195CFastTimer g_AIPostRunTimer;
196CFastTimer g_AIMoveTimer;
197
198CFastTimer g_AIConditionsTimer;
199CFastTimer g_AIPrescheduleThinkTimer;
200CFastTimer g_AIMaintainScheduleTimer;
201
202//-----------------------------------------------------------------------------
203//-----------------------------------------------------------------------------
204
205CAI_Manager g_AI_Manager;
206
207//-------------------------------------
208
209CAI_Manager::CAI_Manager()
210{
211 m_AIs.EnsureCapacity( MAX_AIS );
212}
213
214//-------------------------------------
215
216CAI_BaseNPC **CAI_Manager::AccessAIs()
217{
218 if (m_AIs.Count())
219 return &m_AIs[0];
220 return NULL;
221}
222
223//-------------------------------------
224
225int CAI_Manager::NumAIs()
226{
227 return m_AIs.Count();
228}
229
230//-------------------------------------
231
232void CAI_Manager::AddAI( CAI_BaseNPC *pAI )
233{
234 m_AIs.AddToTail( pAI );
235}
236
237//-------------------------------------
238
239void CAI_Manager::RemoveAI( CAI_BaseNPC *pAI )
240{
241 int i = m_AIs.Find( pAI );
242
243 if ( i != -1 )
244 m_AIs.FastRemove( i );
245}
246
247
248//-----------------------------------------------------------------------------
249
250// ================================================================
251// Init static data
252// ================================================================
253int CAI_BaseNPC::m_nDebugBits = 0;
254CAI_BaseNPC* CAI_BaseNPC::m_pDebugNPC = NULL;
255int CAI_BaseNPC::m_nDebugPauseIndex = -1;
256
257CAI_ClassScheduleIdSpace CAI_BaseNPC::gm_ClassScheduleIdSpace( true );
258CAI_GlobalScheduleNamespace CAI_BaseNPC::gm_SchedulingSymbols;
259CAI_LocalIdSpace CAI_BaseNPC::gm_SquadSlotIdSpace( true );
260
261string_t CAI_BaseNPC::gm_iszPlayerSquad;
262
263int CAI_BaseNPC::gm_iNextThinkRebalanceTick;
264float CAI_BaseNPC::gm_flTimeLastSpawn;
265int CAI_BaseNPC::gm_nSpawnedThisFrame;
266
267CSimpleSimTimer CAI_BaseNPC::m_AnyUpdateEnemyPosTimer;
268
269// ================================================================
270// Class Methods
271// ================================================================
272
273//-----------------------------------------------------------------------------
274// Purpose: Static debug function to clear schedules for all NPCS
275// Input :
276// Output :
277//-----------------------------------------------------------------------------
278void CAI_BaseNPC::ClearAllSchedules(void)
279{
280 CAI_BaseNPC *npc = gEntList.NextEntByClass( (CAI_BaseNPC *)NULL );
281
282 while (npc)
283 {
284 npc->ClearSchedule();
285 npc->GetNavigator()->ClearGoal();
286 npc = gEntList.NextEntByClass(npc);
287 }
288}
289
290// ==============================================================================
291
292//-----------------------------------------------------------------------------
293// Purpose:
294// Input :
295// Output :
296//-----------------------------------------------------------------------------
297bool CAI_BaseNPC::Event_Gibbed( const CTakeDamageInfo &info )
298{
299 bool gibbed = CorpseGib( info );
300
301 if ( gibbed )
302 {
303 // don't remove players!
304 UTIL_Remove( this );
305 SetThink( NULL ); //We're going away, so don't think anymore.
306 }
307 else
308 {
309 CorpseFade();
310 }
311
312 return gibbed;
313}
314
315//=========================================================
316// GetFlinchActivity - determines the best type of flinch
317// anim to play.
318//=========================================================
319Activity CAI_BaseNPC::GetFlinchActivity( bool bHeavyDamage, bool bGesture )
320{
321 Activity flinchActivity;
322
323 switch ( LastHitGroup() )
324 {
325 // pick a region-specific flinch
326 case HITGROUP_HEAD:
327 flinchActivity = bGesture ? ACT_GESTURE_FLINCH_HEAD : ACT_FLINCH_HEAD;
328 break;
329 case HITGROUP_STOMACH:
330 flinchActivity = bGesture ? ACT_GESTURE_FLINCH_STOMACH : ACT_FLINCH_STOMACH;
331 break;
332 case HITGROUP_LEFTARM:
333 flinchActivity = bGesture ? ACT_GESTURE_FLINCH_LEFTARM : ACT_FLINCH_LEFTARM;
334 break;
335 case HITGROUP_RIGHTARM:
336 flinchActivity = bGesture ? ACT_GESTURE_FLINCH_RIGHTARM : ACT_FLINCH_RIGHTARM;
337 break;
338 case HITGROUP_LEFTLEG:
339 flinchActivity = bGesture ? ACT_GESTURE_FLINCH_LEFTLEG : ACT_FLINCH_LEFTLEG;
340 break;
341 case HITGROUP_RIGHTLEG:
342 flinchActivity = bGesture ? ACT_GESTURE_FLINCH_RIGHTLEG : ACT_FLINCH_RIGHTLEG;
343 break;
344 case HITGROUP_CHEST:
345 flinchActivity = bGesture ? ACT_GESTURE_FLINCH_CHEST : ACT_FLINCH_CHEST;
346 break;
347 case HITGROUP_GEAR:
348 case HITGROUP_GENERIC:
349 default:
350 // just get a generic flinch.
351 if ( bHeavyDamage )
352 {
353 flinchActivity = bGesture ? ACT_GESTURE_BIG_FLINCH : ACT_BIG_FLINCH;
354 }
355 else
356 {
357 flinchActivity = bGesture ? ACT_GESTURE_SMALL_FLINCH : ACT_SMALL_FLINCH;
358 }
359 break;
360 }
361
362 // do we have a sequence for the ideal activity?
363 if ( SelectWeightedSequence ( flinchActivity ) == ACTIVITY_NOT_AVAILABLE )
364 {
365 if ( bHeavyDamage )
366 {
367 flinchActivity = bGesture ? ACT_GESTURE_BIG_FLINCH : ACT_BIG_FLINCH;
368
369 // If we fail at finding a big flinch, resort to a small one
370 if ( SelectWeightedSequence ( flinchActivity ) == ACTIVITY_NOT_AVAILABLE )
371 {
372 flinchActivity = bGesture ? ACT_GESTURE_SMALL_FLINCH : ACT_SMALL_FLINCH;
373 }
374 }
375 else
376 {
377 flinchActivity = bGesture ? ACT_GESTURE_SMALL_FLINCH : ACT_SMALL_FLINCH;
378 }
379 }
380
381 return flinchActivity;
382}
383
384//-----------------------------------------------------------------------------
385// Purpose:
386// Input :
387//-----------------------------------------------------------------------------
388void CAI_BaseNPC::CleanupOnDeath( CBaseEntity *pCulprit, bool bFireDeathOutput )
389{
390 if ( !m_bDidDeathCleanup )
391 {
392 m_bDidDeathCleanup = true;
393
394 if ( m_NPCState == NPC_STATE_SCRIPT && m_hCine )
395 {
396 // bail out of this script here
397 m_hCine->CancelScript();
398 // now keep going with the death code
399 }
400
401 if ( GetHintNode() )
402 {
403 GetHintNode()->Unlock();
404 SetHintNode( NULL );
405 }
406
407 if( bFireDeathOutput )
408 {
409 m_OnDeath.FireOutput( pCulprit, this );
410 }
411
412 // Vacate any strategy slot I might have
413 VacateStrategySlot();
414
415 // Remove from squad if in one
416 if (m_pSquad)
417 {
418 // If I'm in idle it means that I didn't see who killed me
419 // and my squad is still in idle state. Tell squad we have
420 // an enemy to wake them up and put the enemy position at
421 // my death position
422 if ( m_NPCState == NPC_STATE_IDLE && pCulprit)
423 {
424 // If we already have some danger memory, don't do this cheat
425 if ( GetEnemies()->GetDangerMemory() == NULL )
426 {
427 UpdateEnemyMemory( pCulprit, GetAbsOrigin() );
428 }
429 }
430
431 // Remove from squad
432 m_pSquad->RemoveFromSquad(this, true);
433 m_pSquad = NULL;
434 }
435
436 RemoveActorFromScriptedScenes( this, false /*all scenes*/ );
437 }
438 else
439 DevMsg( "Unexpected double-death-cleanup\n" );
440}
441
442void CAI_BaseNPC::SelectDeathPose( const CTakeDamageInfo &info )
443{
444 if ( !GetModelPtr() || (info.GetDamageType() & DMG_PREVENT_PHYSICS_FORCE) )
445 return;
446
447 if ( ShouldPickADeathPose() == false )
448 return;
449
450 Activity aActivity = ACT_INVALID;
451 int iDeathFrame = 0;
452
453 SelectDeathPoseActivityAndFrame( this, info, LastHitGroup(), aActivity, iDeathFrame );
454 if ( aActivity == ACT_INVALID )
455 {
456 SetDeathPose( ACT_INVALID );
457 SetDeathPoseFrame( 0 );
458 return;
459 }
460
461 SetDeathPose( SelectWeightedSequence( aActivity ) );
462 SetDeathPoseFrame( iDeathFrame );
463}
464
465//-----------------------------------------------------------------------------
466// Purpose:
467// Input :
468//-----------------------------------------------------------------------------
469void CAI_BaseNPC::Event_Killed( const CTakeDamageInfo &info )
470{
471 if (IsCurSchedule(SCHED_NPC_FREEZE))
472 {
473 // We're frozen; don't die.
474 return;
475 }
476
477 Wake( false );
478
479 //Adrian: Select a death pose to extrapolate the ragdoll's velocity.
480 SelectDeathPose( info );
481
482 m_lifeState = LIFE_DYING;
483
484 CleanupOnDeath( info.GetAttacker() );
485
486 StopLoopingSounds();
487 DeathSound( info );
488
489 if ( ( GetFlags() & FL_NPC ) && ( ShouldGib( info ) == false ) )
490 {
491 SetTouch( NULL );
492 }
493
494 BaseClass::Event_Killed( info );
495
496
497 if ( m_bFadeCorpse )
498 {
499 m_bImportanRagdoll = RagdollManager_SaveImportant( this );
500 }
501
502 // Make sure this condition is fired too (OnTakeDamage breaks out before this happens on death)
503 SetCondition( COND_LIGHT_DAMAGE );
504 SetIdealState( NPC_STATE_DEAD );
505
506 // Some characters rely on getting a state transition, even to death.
507 // zombies, for instance. When a character becomes a ragdoll, their
508 // server entity ceases to think, so we have to set the dead state here
509 // because the AI code isn't going to pick up the change on the next think
510 // for us.
511
512 // Adrian - Only set this if we are going to become a ragdoll. We still want to
513 // select SCHED_DIE or do something special when this NPC dies and we wont
514 // catch the change of state if we set this to whatever the ideal state is.
515 if ( CanBecomeRagdoll() || IsRagdoll() )
516 SetState( NPC_STATE_DEAD );
517
518 // If the remove-no-ragdoll flag is set in the damage type, we're being
519 // told to remove ourselves immediately on death. This is used when something
520 // else has some special reason for us to vanish instead of creating a ragdoll.
521 // i.e. The barnacle does this because it's already got a ragdoll for us.
522 if ( info.GetDamageType() & DMG_REMOVENORAGDOLL )
523 {
524 if ( !IsEFlagSet( EFL_IS_BEING_LIFTED_BY_BARNACLE ) )
525 {
526 // Go away
527 RemoveDeferred();
528 }
529 }
530}
531
532//-----------------------------------------------------------------------------
533
534void CAI_BaseNPC::Ignite( float flFlameLifetime, bool bNPCOnly, float flSize, bool bCalledByLevelDesigner )
535{
536 BaseClass::Ignite( flFlameLifetime, bNPCOnly, flSize, bCalledByLevelDesigner );
537
538#ifdef HL2_EPISODIC
539 CBasePlayer *pPlayer = AI_GetSinglePlayer();
540 if ( pPlayer->IRelationType( this ) != D_LI )
541 {
542 CNPC_Alyx *alyx = CNPC_Alyx::GetAlyx();
543
544 if ( alyx )
545 {
546 alyx->EnemyIgnited( this );
547 }
548 }
549#endif
550}
551
552//-----------------------------------------------------------------------------
553
554ConVar ai_block_damage( "ai_block_damage","0" );
555
556bool CAI_BaseNPC::PassesDamageFilter( const CTakeDamageInfo &info )
557{
558 if ( ai_block_damage.GetBool() )
559 return false;
560 // FIXME: hook a friendly damage filter to the npc instead?
561 if ( (CapabilitiesGet() & bits_CAP_FRIENDLY_DMG_IMMUNE) && info.GetAttacker() && info.GetAttacker() != this )
562 {
563 // check attackers relationship with me
564 CBaseCombatCharacter *npcEnemy = info.GetAttacker()->MyCombatCharacterPointer();
565 bool bHitByVehicle = false;
566 if ( !npcEnemy )
567 {
568 if ( info.GetAttacker()->GetServerVehicle() )
569 {
570 bHitByVehicle = true;
571 }
572 }
573
574 if ( bHitByVehicle || (npcEnemy && npcEnemy->IRelationType( this ) == D_LI) )
575 {
576 m_fNoDamageDecal = true;
577
578 if ( npcEnemy && npcEnemy->IsPlayer() )
579 {
580 m_OnDamagedByPlayer.FireOutput( info.GetAttacker(), this );
581 // This also counts as being harmed by player's squad.
582 m_OnDamagedByPlayerSquad.FireOutput( info.GetAttacker(), this );
583 }
584
585 return false;
586 }
587 }
588
589 if ( !BaseClass::PassesDamageFilter( info ) )
590 {
591 m_fNoDamageDecal = true;
592 return false;
593 }
594 return true;
595}
596
597//-----------------------------------------------------------------------------
598// Purpose:
599// Input :
600// Output :
601//-----------------------------------------------------------------------------
602int CAI_BaseNPC::OnTakeDamage_Alive( const CTakeDamageInfo &info )
603{
604 Forget( bits_MEMORY_INCOVER );
605
606 if ( !BaseClass::OnTakeDamage_Alive( info ) )
607 return 0;
608
609 if ( GetSleepState() == AISS_WAITING_FOR_THREAT )
610 Wake();
611
612 // NOTE: This must happen after the base class is called; we need to reduce
613 // health before the pain sound, since some NPCs use the final health
614 // level as a modifier to determine which pain sound to use.
615
616 // REVISIT: Combine soldiers shoot each other a lot and then talk about it
617 // this improves that case a bunch, but it seems kind of harsh.
618 if ( !m_pSquad || !m_pSquad->SquadIsMember( info.GetAttacker() ) )
619 {
620 PainSound( info );// "Ouch!"
621 }
622
623 // See if we're running a dynamic interaction that should break when I am damaged.
624 if ( IsActiveDynamicInteraction() )
625 {
626 ScriptedNPCInteraction_t *pInteraction = GetRunningDynamicInteraction();
627 if ( pInteraction->iLoopBreakTriggerMethod & SNPCINT_LOOPBREAK_ON_DAMAGE )
628 {
629 // Can only break when we're in the action anim
630 if ( m_hCine->IsPlayingAction() )
631 {
632 m_hCine->StopActionLoop( true );
633 }
634 }
635 }
636
637 // If we're not allowed to die, refuse to die
638 // Allow my interaction partner to kill me though
639 if ( m_iHealth <= 0 && HasInteractionCantDie() && info.GetAttacker() != m_hInteractionPartner )
640 {
641 m_iHealth = 1;
642 }
643
644#if 0
645 // HACKHACK Don't kill npcs in a script. Let them break their scripts first
646 // THIS is a Half-Life 1 hack that's not cutting the mustard in the scripts
647 // that have been authored for Half-Life 2 thus far. (sjb)
648 if ( m_NPCState == NPC_STATE_SCRIPT )
649 {
650 SetCondition( COND_LIGHT_DAMAGE );
651 }
652#endif
653
654 // -----------------------------------
655 // Fire outputs
656 // -----------------------------------
657 if ( m_flLastDamageTime != gpGlobals->curtime )
658 {
659 // only fire once per frame
660 m_OnDamaged.FireOutput( info.GetAttacker(), this);
661
662 if( info.GetAttacker()->IsPlayer() )
663 {
664 m_OnDamagedByPlayer.FireOutput( info.GetAttacker(), this );
665
666 // This also counts as being harmed by player's squad.
667 m_OnDamagedByPlayerSquad.FireOutput( info.GetAttacker(), this );
668 }
669 else
670 {
671 // See if the person that injured me is an NPC.
672 CAI_BaseNPC *pAttacker = dynamic_cast<CAI_BaseNPC *>( info.GetAttacker() );
673 CBasePlayer *pPlayer = AI_GetSinglePlayer();
674
675 if( pAttacker && pAttacker->IsAlive() && pPlayer )
676 {
677 if( pAttacker->GetSquad() != NULL && pAttacker->IsInPlayerSquad() )
678 {
679 m_OnDamagedByPlayerSquad.FireOutput( info.GetAttacker(), this );
680 }
681 }
682 }
683 }
684
685 if( (info.GetDamageType() & DMG_CRUSH) && !(info.GetDamageType() & DMG_PHYSGUN) && info.GetDamage() >= MIN_PHYSICS_FLINCH_DAMAGE )
686 {
687 SetCondition( COND_PHYSICS_DAMAGE );
688 }
689
690 if ( m_iHealth <= ( m_iMaxHealth / 2 ) )
691 {
692 m_OnHalfHealth.FireOutput( info.GetAttacker(), this );
693 }
694
695 // react to the damage (get mad)
696 if ( ( (GetFlags() & FL_NPC) == 0 ) || !info.GetAttacker() )
697 return 1;
698
699 // If the attacker was an NPC or client update my position memory
700 if ( info.GetAttacker()->GetFlags() & (FL_NPC | FL_CLIENT) )
701 {
702 // ------------------------------------------------------------------
703 // DO NOT CHANGE THIS CODE W/O CONSULTING
704 // Only update information about my attacker I don't see my attacker
705 // ------------------------------------------------------------------
706 if ( !FInViewCone( info.GetAttacker() ) || !FVisible( info.GetAttacker() ) )
707 {
708 // -------------------------------------------------------------
709 // If I have an inflictor (enemy / grenade) update memory with
710 // position of inflictor, otherwise update with an position
711 // estimate for where the attack came from
712 // ------------------------------------------------------
713 Vector vAttackPos;
714 if (info.GetInflictor())
715 {
716 vAttackPos = info.GetInflictor()->GetAbsOrigin();
717 }
718 else
719 {
720 vAttackPos = (GetAbsOrigin() + ( g_vecAttackDir * 64 ));
721 }
722
723
724 // ----------------------------------------------------------------
725 // If I already have an enemy, assume that the attack
726 // came from the enemy and update my enemy's position
727 // unless I already know about the attacker or I can see my enemy
728 // ----------------------------------------------------------------
729 if ( GetEnemy() != NULL &&
730 !GetEnemies()->HasMemory( info.GetAttacker() ) &&
731 !HasCondition(COND_SEE_ENEMY) )
732 {
733 UpdateEnemyMemory(GetEnemy(), vAttackPos, GetEnemy());
734 }
735 // ----------------------------------------------------------------
736 // If I already know about this enemy, update his position
737 // ----------------------------------------------------------------
738 else if (GetEnemies()->HasMemory( info.GetAttacker() ))
739 {
740 UpdateEnemyMemory(info.GetAttacker(), vAttackPos);
741 }
742 // -----------------------------------------------------------------
743 // Otherwise just note the position, but don't add enemy to my list
744 // -----------------------------------------------------------------
745 else
746 {
747 UpdateEnemyMemory(NULL, vAttackPos);
748 }
749 }
750
751 // add pain to the conditions
752 if ( IsLightDamage( info ) )
753 {
754 SetCondition( COND_LIGHT_DAMAGE );
755 }
756 if ( IsHeavyDamage( info ) )
757 {
758 SetCondition( COND_HEAVY_DAMAGE );
759 }
760
761 ForceGatherConditions();
762
763 // Keep track of how much consecutive damage I have recieved
764 if ((gpGlobals->curtime - m_flLastDamageTime) < 1.0)
765 {
766 m_flSumDamage += info.GetDamage();
767 }
768 else
769 {
770 m_flSumDamage = info.GetDamage();
771 }
772 m_flLastDamageTime = gpGlobals->curtime;
773 if ( info.GetAttacker() && info.GetAttacker()->IsPlayer() )
774 m_flLastPlayerDamageTime = gpGlobals->curtime;
775 GetEnemies()->OnTookDamageFrom( info.GetAttacker() );
776
777 if (m_flSumDamage > m_iMaxHealth*0.3)
778 {
779 SetCondition(COND_REPEATED_DAMAGE);
780 }
781
782 NotifyFriendsOfDamage( info.GetAttacker() );
783 }
784
785 // ---------------------------------------------------------------
786 // Insert a combat sound so that nearby NPCs know I've been hit
787 // ---------------------------------------------------------------
788 CSoundEnt::InsertSound( SOUND_COMBAT, GetAbsOrigin(), 1024, 0.5, this, SOUNDENT_CHANNEL_INJURY );
789
790 return 1;
791}
792
793
794//=========================================================
795// OnTakeDamage_Dying - takedamage function called when a npc's
796// corpse is damaged.
797//=========================================================
798int CAI_BaseNPC::OnTakeDamage_Dying( const CTakeDamageInfo &info )
799{
800 if ( info.GetDamageType() & DMG_PLASMA )
801 {
802 if ( m_takedamage != DAMAGE_EVENTS_ONLY )
803 {
804 m_iHealth -= info.GetDamage();
805
806 if (m_iHealth < -500)
807 {
808 UTIL_Remove(this);
809 }
810 }
811 }
812 return BaseClass::OnTakeDamage_Dying( info );
813}
814
815//=========================================================
816// OnTakeDamage_Dead - takedamage function called when a npc's
817// corpse is damaged.
818//=========================================================
819int CAI_BaseNPC::OnTakeDamage_Dead( const CTakeDamageInfo &info )
820{
821 Vector vecDir;
822
823 // grab the vector of the incoming attack. ( pretend that the inflictor is a little lower than it really is, so the body will tend to fly upward a bit).
824 vecDir = vec3_origin;
825 if ( info.GetInflictor() )
826 {
827 vecDir = info.GetInflictor()->WorldSpaceCenter() - Vector ( 0, 0, 10 ) - WorldSpaceCenter();
828 VectorNormalize( vecDir );
829 g_vecAttackDir = vecDir;
830 }
831
832#if 0// turn this back on when the bounding box issues are resolved.
833
834 SetGroundEntity( NULL );
835 GetLocalOrigin().z += 1;
836
837 // let the damage scoot the corpse around a bit.
838 if ( info.GetInflictor() && (info.GetAttacker()->GetSolid() != SOLID_TRIGGER) )
839 {
840 ApplyAbsVelocityImpulse( vecDir * -DamageForce( flDamage ) );
841 }
842
843#endif
844
845 // kill the corpse if enough damage was done to destroy the corpse and the damage is of a type that is allowed to destroy the corpse.
846 if ( info.GetDamageType() & DMG_GIB_CORPSE )
847 {
848 // Accumulate corpse gibbing damage, so you can gib with multiple hits
849 if ( m_takedamage != DAMAGE_EVENTS_ONLY )
850 {
851 m_iHealth -= info.GetDamage() * 0.1;
852 }
853 }
854
855 if ( info.GetDamageType() & DMG_PLASMA )
856 {
857 if ( m_takedamage != DAMAGE_EVENTS_ONLY )
858 {
859 m_iHealth -= info.GetDamage();
860
861 if (m_iHealth < -500)
862 {
863 UTIL_Remove(this);
864 }
865 }
866 }
867
868 return 1;
869}
870
871//-----------------------------------------------------------------------------
872//-----------------------------------------------------------------------------
873void CAI_BaseNPC::NotifyFriendsOfDamage( CBaseEntity *pAttackerEntity )
874{
875 CAI_BaseNPC *pAttacker = pAttackerEntity->MyNPCPointer();
876 if ( pAttacker )
877 {
878 const Vector &origin = GetAbsOrigin();
879 for ( int i = 0; i < g_AI_Manager.NumAIs(); i++ )
880 {
881 const float NEAR_Z = 10*12;
882 const float NEAR_XY_SQ = Square( 50*12 );
883 CAI_BaseNPC *pNpc = g_AI_Manager.AccessAIs()[i];
884 if ( pNpc && pNpc != this )
885 {
886 const Vector &originNpc = pNpc->GetAbsOrigin();
887 if ( fabsf( originNpc.z - origin.z ) < NEAR_Z )
888 {
889 if ( (originNpc.AsVector2D() - origin.AsVector2D()).LengthSqr() < NEAR_XY_SQ )
890 {
891 if ( pNpc->GetSquad() == GetSquad() || IRelationType( pNpc ) == D_LI )
892 pNpc->OnFriendDamaged( this, pAttacker );
893 }
894 }
895 }
896 }
897 }
898}
899
900//-----------------------------------------------------------------------------
901//-----------------------------------------------------------------------------
902void CAI_BaseNPC::OnFriendDamaged( CBaseCombatCharacter *pSquadmate, CBaseEntity *pAttacker )
903{
904 if ( GetSleepState() != AISS_WAITING_FOR_INPUT )
905 {
906 float distSqToThreat = ( GetAbsOrigin() - pAttacker->GetAbsOrigin() ).LengthSqr();
907
908 if ( GetSleepState() != AISS_AWAKE && distSqToThreat < Square( 20 * 12 ) )
909 Wake();
910
911 if ( distSqToThreat < Square( 50 * 12 ) )
912 ForceGatherConditions();
913 }
914}
915
916//-----------------------------------------------------------------------------
917//-----------------------------------------------------------------------------
918bool CAI_BaseNPC::IsLightDamage( const CTakeDamageInfo &info )
919{
920 // ALL nonzero damage is light damage! Mask off COND_LIGHT_DAMAGE if you want to ignore light damage.
921 return ( info.GetDamage() > 0 );
922}
923
924bool CAI_BaseNPC::IsHeavyDamage( const CTakeDamageInfo &info )
925{
926 return ( info.GetDamage() > 20 );
927}
928
929void CAI_BaseNPC::DoRadiusDamage( const CTakeDamageInfo &info, int iClassIgnore, CBaseEntity *pEntityIgnore )
930{
931 RadiusDamage( info, GetAbsOrigin(), info.GetDamage() * 2.5, iClassIgnore, pEntityIgnore );
932}
933
934
935void CAI_BaseNPC::DoRadiusDamage( const CTakeDamageInfo &info, const Vector &vecSrc, int iClassIgnore, CBaseEntity *pEntityIgnore )
936{
937 RadiusDamage( info, vecSrc, info.GetDamage() * 2.5, iClassIgnore, pEntityIgnore );
938}
939
940
941//-----------------------------------------------------------------------------
942// Set the contents types that are solid by default to all NPCs
943//-----------------------------------------------------------------------------
944unsigned int CAI_BaseNPC::PhysicsSolidMaskForEntity( void ) const
945{
946 return MASK_NPCSOLID;
947}
948
949
950//=========================================================
951
952//-----------------------------------------------------------------------------
953// Purpose:
954//-----------------------------------------------------------------------------
955void CAI_BaseNPC::DecalTrace( trace_t *pTrace, char const *decalName )
956{
957 if ( m_fNoDamageDecal )
958 {
959 m_fNoDamageDecal = false;
960 // @Note (toml 04-23-03): e3, don't decal face on damage if still alive
961 return;
962 }
963 BaseClass::DecalTrace( pTrace, decalName );
964}
965
966//-----------------------------------------------------------------------------
967// Purpose:
968//-----------------------------------------------------------------------------
969void CAI_BaseNPC::ImpactTrace( trace_t *pTrace, int iDamageType, char *pCustomImpactName )
970{
971 if ( m_fNoDamageDecal )
972 {
973 m_fNoDamageDecal = false;
974 // @Note (toml 04-23-03): e3, don't decal face on damage if still alive
975 return;
976 }
977 BaseClass::ImpactTrace( pTrace, iDamageType, pCustomImpactName );
978}
979
980//---------------------------------------------------------
981// Return the number by which to multiply incoming damage
982// based on the hitgroup it hits. This exists mainly so
983// that individual NPC's can have more or less resistance
984// to damage done to certain hitgroups.
985//---------------------------------------------------------
986float CAI_BaseNPC::GetHitgroupDamageMultiplier( int iHitGroup, const CTakeDamageInfo &info )
987{
988 switch( iHitGroup )
989 {
990 case HITGROUP_GENERIC:
991 return 1.0f;
992
993 case HITGROUP_HEAD:
994 return sk_npc_head.GetFloat();
995
996 case HITGROUP_CHEST:
997 return sk_npc_chest.GetFloat();
998
999 case HITGROUP_STOMACH:
1000 return sk_npc_stomach.GetFloat();
1001
1002 case HITGROUP_LEFTARM:
1003 case HITGROUP_RIGHTARM:
1004 return sk_npc_arm.GetFloat();
1005
1006 case HITGROUP_LEFTLEG:
1007 case HITGROUP_RIGHTLEG:
1008 return sk_npc_leg.GetFloat();
1009
1010 default:
1011 return 1.0f;
1012 }
1013}
1014
1015//=========================================================
1016// TraceAttack
1017//=========================================================
1018void CAI_BaseNPC::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr )
1019{
1020 m_fNoDamageDecal = false;
1021 if ( m_takedamage == DAMAGE_NO )
1022 return;
1023
1024 CTakeDamageInfo subInfo = info;
1025
1026 SetLastHitGroup( ptr->hitgroup );
1027 m_nForceBone = ptr->physicsbone; // save this bone for physics forces
1028
1029 Assert( m_nForceBone > -255 && m_nForceBone < 256 );
1030
1031 bool bDebug = showhitlocation.GetBool();
1032
1033 switch ( ptr->hitgroup )
1034 {
1035 case HITGROUP_GENERIC:
1036 if( bDebug ) DevMsg("Hit Location: Generic\n");
1037 break;
1038
1039 // hit gear, react but don't bleed
1040 case HITGROUP_GEAR:
1041 subInfo.SetDamage( 0.01 );
1042 ptr->hitgroup = HITGROUP_GENERIC;
1043 if( bDebug ) DevMsg("Hit Location: Gear\n");
1044 break;
1045
1046 case HITGROUP_HEAD:
1047 subInfo.ScaleDamage( GetHitgroupDamageMultiplier(ptr->hitgroup, info) );
1048 if( bDebug ) DevMsg("Hit Location: Head\n");
1049 break;
1050
1051 case HITGROUP_CHEST:
1052 subInfo.ScaleDamage( GetHitgroupDamageMultiplier(ptr->hitgroup, info) );
1053 if( bDebug ) DevMsg("Hit Location: Chest\n");
1054 break;
1055
1056 case HITGROUP_STOMACH:
1057 subInfo.ScaleDamage( GetHitgroupDamageMultiplier(ptr->hitgroup, info) );
1058 if( bDebug ) DevMsg("Hit Location: Stomach\n");
1059 break;
1060
1061 case HITGROUP_LEFTARM:
1062 case HITGROUP_RIGHTARM:
1063 subInfo.ScaleDamage( GetHitgroupDamageMultiplier(ptr->hitgroup, info) );
1064 if( bDebug ) DevMsg("Hit Location: Left/Right Arm\n");
1065 break
1066 ;
1067 case HITGROUP_LEFTLEG:
1068 case HITGROUP_RIGHTLEG:
1069 subInfo.ScaleDamage( GetHitgroupDamageMultiplier(ptr->hitgroup, info) );
1070 if( bDebug ) DevMsg("Hit Location: Left/Right Leg\n");
1071 break;
1072
1073 default:
1074 if( bDebug ) DevMsg("Hit Location: UNKNOWN\n");
1075 break;
1076 }
1077
1078 if ( subInfo.GetDamage() >= 1.0 && !(subInfo.GetDamageType() & DMG_SHOCK ) )
1079 {
1080 if( !IsPlayer() || ( IsPlayer() && g_pGameRules->IsMultiplayer() ) )
1081 {
1082 // NPC's always bleed. Players only bleed in multiplayer.
1083 SpawnBlood( ptr->endpos, vecDir, BloodColor(), subInfo.GetDamage() );// a little surface blood.
1084 }
1085
1086 TraceBleed( subInfo.GetDamage(), vecDir, ptr, subInfo.GetDamageType() );
1087
1088 if ( ptr->hitgroup == HITGROUP_HEAD && m_iHealth - subInfo.GetDamage() > 0 )
1089 {
1090 m_fNoDamageDecal = true;
1091 }
1092 }
1093
1094 // Airboat gun will impart major force if it's about to kill him....
1095 if ( info.GetDamageType() & DMG_AIRBOAT )
1096 {
1097 if ( subInfo.GetDamage() >= GetHealth() )
1098 {
1099 float flMagnitude = subInfo.GetDamageForce().Length();
1100 if ( (flMagnitude != 0.0f) && (flMagnitude < 400.0f * 65.0f) )
1101 {
1102 subInfo.ScaleDamageForce( 400.0f * 65.0f / flMagnitude );
1103 }
1104 }
1105 }
1106
1107 if( info.GetInflictor() )
1108 {
1109 subInfo.SetInflictor( info.GetInflictor() );
1110 }
1111 else
1112 {
1113 subInfo.SetInflictor( info.GetAttacker() );
1114 }
1115
1116 AddMultiDamage( subInfo, this );
1117}
1118
1119//-----------------------------------------------------------------------------
1120// Purpose: Checks if point is in spread angle between source and target Pos
1121// Used to prevent friendly fire
1122// Input : Source of attack, target position, spread angle
1123// Output :
1124//-----------------------------------------------------------------------------
1125bool CAI_BaseNPC::PointInSpread( CBaseCombatCharacter *pCheckEntity, const Vector &sourcePos, const Vector &targetPos, const Vector &testPoint, float flSpread, float maxDistOffCenter )
1126{
1127 float distOffLine = CalcDistanceToLine2D( testPoint.AsVector2D(), sourcePos.AsVector2D(), targetPos.AsVector2D() );
1128 if ( distOffLine < maxDistOffCenter )
1129 {
1130 Vector toTarget = targetPos - sourcePos;
1131 float distTarget = VectorNormalize(toTarget);
1132
1133 Vector toTest = testPoint - sourcePos;
1134 float distTest = VectorNormalize(toTest);
1135 // Only reject if target is on other side
1136 if (distTarget > distTest)
1137 {
1138 toTarget.z = 0.0;
1139 toTest.z = 0.0;
1140
1141 float dotProduct = DotProduct(toTarget,toTest);
1142 if (dotProduct > flSpread)
1143 {
1144 return true;
1145 }
1146 else if( dotProduct > 0.0f )
1147 {
1148 // If this guy is in front, do the hull/line test:
1149 if( pCheckEntity )
1150 {
1151 float flBBoxDist = NAI_Hull::Width( pCheckEntity->GetHullType() );
1152 flBBoxDist *= 1.414f; // sqrt(2)
1153
1154 // !!!BUGBUG - this 2d check will stop a citizen shooting at a gunship or strider
1155 // if another citizen is between them, even though the strider or gunship may be
1156 // high up in the air (sjb)
1157 distOffLine = CalcDistanceToLine( testPoint, sourcePos, targetPos );
1158 if( distOffLine < flBBoxDist )
1159 {
1160 return true;
1161 }
1162 }
1163 }
1164 }
1165 }
1166 return false;
1167}
1168
1169//-----------------------------------------------------------------------------
1170// Purpose: Checks if player is in spread angle between source and target Pos
1171// Used to prevent friendly fire
1172// Input : Source of attack, target position, spread angle
1173// Output :
1174//-----------------------------------------------------------------------------
1175bool CAI_BaseNPC::PlayerInSpread( const Vector &sourcePos, const Vector &targetPos, float flSpread, float maxDistOffCenter, bool ignoreHatedPlayers )
1176{
1177 // loop through all players
1178 for (int i = 1; i <= gpGlobals->maxClients; i++ )
1179 {
1180 CBasePlayer *pPlayer = UTIL_PlayerByIndex( i );
1181
1182 if ( pPlayer && ( !ignoreHatedPlayers || IRelationType( pPlayer ) != D_HT ) )
1183 {
1184 if ( PointInSpread( pPlayer, sourcePos, targetPos, pPlayer->WorldSpaceCenter(), flSpread, maxDistOffCenter ) )
1185 return true;
1186 }
1187 }
1188 return false;
1189}
1190
1191//-----------------------------------------------------------------------------
1192// Purpose: Checks if player is in range of given location. Used by NPCs
1193// to prevent friendly fire
1194// Input :
1195// Output :
1196//-----------------------------------------------------------------------------
1197CBaseEntity *CAI_BaseNPC::PlayerInRange( const Vector &vecLocation, float flDist )
1198{
1199 // loop through all players
1200 for (int i = 1; i <= gpGlobals->maxClients; i++ )
1201 {
1202 CBasePlayer *pPlayer = UTIL_PlayerByIndex( i );
1203
1204 if (pPlayer && (vecLocation - pPlayer->WorldSpaceCenter() ).Length2D() <= flDist)
1205 {
1206 return pPlayer;
1207 }
1208 }
1209 return NULL;
1210}
1211
1212
1213#define BULLET_WIZZDIST 80.0
1214#define SLOPE ( -1.0 / BULLET_WIZZDIST )
1215
1216void BulletWizz( Vector vecSrc, Vector vecEndPos, edict_t *pShooter, bool isTracer )
1217{
1218 CBasePlayer *pPlayer;
1219 Vector vecBulletPath;
1220 Vector vecPlayerPath;
1221 Vector vecBulletDir;
1222 Vector vecNearestPoint;
1223 float flDist;
1224 float flBulletDist;
1225
1226 vecBulletPath = vecEndPos - vecSrc;
1227 vecBulletDir = vecBulletPath;
1228 VectorNormalize(vecBulletDir);
1229
1230 // see how near this bullet passed by player in a single player game
1231 // for multiplayer, we need to go through the list of clients.
1232 for (int i = 1; i <= gpGlobals->maxClients; i++ )
1233 {
1234 pPlayer = UTIL_PlayerByIndex( i );
1235
1236 if ( !pPlayer )
1237 continue;
1238
1239 // Don't hear one's own bullets
1240 if( pPlayer->edict() == pShooter )
1241 continue;
1242
1243 vecPlayerPath = pPlayer->EarPosition() - vecSrc;
1244 flDist = DotProduct( vecPlayerPath, vecBulletDir );
1245 vecNearestPoint = vecSrc + vecBulletDir * flDist;
1246 // FIXME: minus m_vecViewOffset?
1247 flBulletDist = ( vecNearestPoint - pPlayer->EarPosition() ).Length();
1248 }
1249}
1250
1251//-----------------------------------------------------------------------------
1252// Hits triggers with raycasts
1253//-----------------------------------------------------------------------------
1254class CTriggerTraceEnum : public IEntityEnumerator
1255{
1256public:
1257 CTriggerTraceEnum( Ray_t *pRay, const CTakeDamageInfo &info, const Vector& dir, int contentsMask ) :
1258 m_info( info ), m_VecDir(dir), m_ContentsMask(contentsMask), m_pRay(pRay)
1259 {
1260 }
1261
1262 virtual bool EnumEntity( IHandleEntity *pHandleEntity )
1263 {
1264 trace_t tr;
1265
1266 CBaseEntity *pEnt = gEntList.GetBaseEntity( pHandleEntity->GetRefEHandle() );
1267
1268 // Done to avoid hitting an entity that's both solid & a trigger.
1269 if ( pEnt->IsSolid() )
1270 return true;
1271
1272 enginetrace->ClipRayToEntity( *m_pRay, m_ContentsMask, pHandleEntity, &tr );
1273 if (tr.fraction < 1.0f)
1274 {
1275 pEnt->DispatchTraceAttack( m_info, m_VecDir, &tr );
1276 ApplyMultiDamage();
1277 }
1278
1279 return true;
1280 }
1281
1282private:
1283 Vector m_VecDir;
1284 int m_ContentsMask;
1285 Ray_t *m_pRay;
1286 CTakeDamageInfo m_info;
1287};
1288
1289void CBaseEntity::TraceAttackToTriggers( const CTakeDamageInfo &info, const Vector& start, const Vector& end, const Vector& dir )
1290{
1291 Ray_t ray;
1292 ray.Init( start, end );
1293
1294 CTriggerTraceEnum triggerTraceEnum( &ray, info, dir, MASK_SHOT );
1295 enginetrace->EnumerateEntities( ray, true, &triggerTraceEnum );
1296}
1297
1298//-----------------------------------------------------------------------------
1299// Purpose:
1300// Output : const char
1301//-----------------------------------------------------------------------------
1302const char *CAI_BaseNPC::GetTracerType( void )
1303{
1304 if ( GetActiveWeapon() )
1305 {
1306 return GetActiveWeapon()->GetTracerType();
1307 }
1308
1309 return BaseClass::GetTracerType();
1310}
1311
1312//-----------------------------------------------------------------------------
1313// Purpose:
1314// Input : &vecTracerSrc -
1315// &tr -
1316// iTracerType -
1317//-----------------------------------------------------------------------------
1318void CAI_BaseNPC::MakeTracer( const Vector &vecTracerSrc, const trace_t &tr, int iTracerType )
1319{
1320 if ( GetActiveWeapon() )
1321 {
1322 GetActiveWeapon()->MakeTracer( vecTracerSrc, tr, iTracerType );
1323 return;
1324 }
1325
1326 BaseClass::MakeTracer( vecTracerSrc, tr, iTracerType );
1327}
1328
1329//-----------------------------------------------------------------------------
1330// Purpose:
1331//-----------------------------------------------------------------------------
1332void CAI_BaseNPC::FireBullets( const FireBulletsInfo_t &info )
1333{
1334#ifdef HL2_DLL
1335 // If we're shooting at a bullseye, become perfectly accurate if the bullseye demands it
1336 if ( GetEnemy() && GetEnemy()->Classify() == CLASS_BULLSEYE )
1337 {
1338 CNPC_Bullseye *pBullseye = dynamic_cast<CNPC_Bullseye*>(GetEnemy());
1339 if ( pBullseye && pBullseye->UsePerfectAccuracy() )
1340 {
1341 FireBulletsInfo_t accurateInfo = info;
1342 accurateInfo.m_vecSpread = vec3_origin;
1343 BaseClass::FireBullets( accurateInfo );
1344 return;
1345 }
1346 }
1347#endif
1348
1349 BaseClass::FireBullets( info );
1350}
1351
1352
1353//-----------------------------------------------------------------------------
1354// Shot statistics
1355//-----------------------------------------------------------------------------
1356void CBaseEntity::UpdateShotStatistics( const trace_t &tr )
1357{
1358 if ( ai_shot_stats.GetBool() )
1359 {
1360 CAI_BaseNPC *pNpc = MyNPCPointer();
1361 if ( pNpc )
1362 {
1363 pNpc->m_TotalShots++;
1364 if ( tr.m_pEnt == pNpc->GetEnemy() )
1365 {
1366 pNpc->m_TotalHits++;
1367 }
1368 }
1369 }
1370}
1371
1372
1373//-----------------------------------------------------------------------------
1374// Handle shot entering water
1375//-----------------------------------------------------------------------------
1376void CBaseEntity::HandleShotImpactingGlass( const FireBulletsInfo_t &info,
1377 const trace_t &tr, const Vector &vecDir, ITraceFilter *pTraceFilter )
1378{
1379 // Move through the glass until we're at the other side
1380 Vector testPos = tr.endpos + ( vecDir * MAX_GLASS_PENETRATION_DEPTH );
1381
1382 CEffectData data;
1383
1384 data.m_vNormal = tr.plane.normal;
1385 data.m_vOrigin = tr.endpos;
1386
1387 DispatchEffect( "GlassImpact", data );
1388
1389 trace_t penetrationTrace;
1390
1391 // Re-trace as if the bullet had passed right through
1392 UTIL_TraceLine( testPos, tr.endpos, MASK_SHOT, pTraceFilter, &penetrationTrace );
1393
1394 // See if we found the surface again
1395 if ( penetrationTrace.startsolid || tr.fraction == 0.0f || penetrationTrace.fraction == 1.0f )
1396 return;
1397
1398 //FIXME: This is technically frustrating MultiDamage, but multiple shots hitting multiple targets in one call
1399 // would do exactly the same anyway...
1400
1401 // Impact the other side (will look like an exit effect)
1402 DoImpactEffect( penetrationTrace, GetAmmoDef()->DamageType(info.m_iAmmoType) );
1403
1404 data.m_vNormal = penetrationTrace.plane.normal;
1405 data.m_vOrigin = penetrationTrace.endpos;
1406
1407 DispatchEffect( "GlassImpact", data );
1408
1409 // Refire the round, as if starting from behind the glass
1410 FireBulletsInfo_t behindGlassInfo;
1411 behindGlassInfo.m_iShots = 1;
1412 behindGlassInfo.m_vecSrc = penetrationTrace.endpos;
1413 behindGlassInfo.m_vecDirShooting = vecDir;
1414 behindGlassInfo.m_vecSpread = vec3_origin;
1415 behindGlassInfo.m_flDistance = info.m_flDistance*( 1.0f - tr.fraction );
1416 behindGlassInfo.m_iAmmoType = info.m_iAmmoType;
1417 behindGlassInfo.m_iTracerFreq = info.m_iTracerFreq;
1418 behindGlassInfo.m_iDamage = info.m_iDamage;
1419 behindGlassInfo.m_pAttacker = info.m_pAttacker ? info.m_pAttacker : this;
1420 behindGlassInfo.m_nFlags = info.m_nFlags;
1421
1422 FireBullets( behindGlassInfo );
1423}
1424
1425
1426//-----------------------------------------------------------------------------
1427// Computes the tracer start position
1428//-----------------------------------------------------------------------------
1429#define SHOT_UNDERWATER_BUBBLE_DIST 400
1430
1431void CBaseEntity::CreateBubbleTrailTracer( const Vector &vecShotSrc, const Vector &vecShotEnd, const Vector &vecShotDir )
1432{
1433 int nBubbles;
1434 Vector vecBubbleEnd;
1435 float flLengthSqr = vecShotSrc.DistToSqr( vecShotEnd );
1436 if ( flLengthSqr > SHOT_UNDERWATER_BUBBLE_DIST * SHOT_UNDERWATER_BUBBLE_DIST )
1437 {
1438 VectorMA( vecShotSrc, SHOT_UNDERWATER_BUBBLE_DIST, vecShotDir, vecBubbleEnd );
1439 nBubbles = WATER_BULLET_BUBBLES_PER_INCH * SHOT_UNDERWATER_BUBBLE_DIST;
1440 }
1441 else
1442 {
1443 float flLength = sqrt(flLengthSqr) - 0.1f;
1444 nBubbles = WATER_BULLET_BUBBLES_PER_INCH * flLength;
1445 VectorMA( vecShotSrc, flLength, vecShotDir, vecBubbleEnd );
1446 }
1447
1448 Vector vecTracerSrc;
1449 ComputeTracerStartPosition( vecShotSrc, &vecTracerSrc );
1450 UTIL_BubbleTrail( vecTracerSrc, vecBubbleEnd, nBubbles );
1451}
1452
1453
1454//=========================================================
1455//=========================================================
1456void CAI_BaseNPC::MakeDamageBloodDecal ( int cCount, float flNoise, trace_t *ptr, Vector vecDir )
1457{
1458 // make blood decal on the wall!
1459 trace_t Bloodtr;
1460 Vector vecTraceDir;
1461 int i;
1462
1463 if ( !IsAlive() )
1464 {
1465 // dealing with a dead npc.
1466 if ( m_iMaxHealth <= 0 )
1467 {
1468 // no blood decal for a npc that has already decalled its limit.
1469 return;
1470 }
1471 else
1472 {
1473 m_iMaxHealth -= 1;
1474 }
1475 }
1476
1477 for ( i = 0 ; i < cCount ; i++ )
1478 {
1479 vecTraceDir = vecDir;
1480
1481 vecTraceDir.x += random->RandomFloat( -flNoise, flNoise );
1482 vecTraceDir.y += random->RandomFloat( -flNoise, flNoise );
1483 vecTraceDir.z += random->RandomFloat( -flNoise, flNoise );
1484
1485 AI_TraceLine( ptr->endpos, ptr->endpos + vecTraceDir * 172, MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &Bloodtr);
1486
1487 if ( Bloodtr.fraction != 1.0 )
1488 {
1489 UTIL_BloodDecalTrace( &Bloodtr, BloodColor() );
1490 }
1491 }
1492}
1493
1494
1495//-----------------------------------------------------------------------------
1496// Purpose:
1497// Input : &tr -
1498// nDamageType -
1499//-----------------------------------------------------------------------------
1500void CAI_BaseNPC::DoImpactEffect( trace_t &tr, int nDamageType )
1501{
1502 if ( GetActiveWeapon() != NULL )
1503 {
1504 GetActiveWeapon()->DoImpactEffect( tr, nDamageType );
1505 return;
1506 }
1507
1508 BaseClass::DoImpactEffect( tr, nDamageType );
1509}
1510
1511//---------------------------------------------------------
1512//---------------------------------------------------------
1513#define InterruptFromCondition( iCondition ) \
1514 AI_RemapFromGlobal( ( AI_IdIsLocal( iCondition ) ? GetClassScheduleIdSpace()->ConditionLocalToGlobal( iCondition ) : iCondition ) )
1515
1516void CAI_BaseNPC::SetCondition( int iCondition )
1517{
1518 int interrupt = InterruptFromCondition( iCondition );
1519
1520 if ( interrupt == -1 )
1521 {
1522 Assert(0);
1523 return;
1524 }
1525
1526 m_Conditions.SetBit( interrupt );
1527}
1528
1529//---------------------------------------------------------
1530//---------------------------------------------------------
1531bool CAI_BaseNPC::HasCondition( int iCondition )
1532{
1533 int interrupt = InterruptFromCondition( iCondition );
1534
1535 if ( interrupt == -1 )
1536 {
1537 Assert(0);
1538 return false;
1539 }
1540
1541 bool bReturn = m_Conditions.GetBit(interrupt);
1542 return (bReturn);
1543}
1544
1545//---------------------------------------------------------
1546//---------------------------------------------------------
1547bool CAI_BaseNPC::HasCondition( int iCondition, bool bUseIgnoreConditions )
1548{
1549 if ( bUseIgnoreConditions )
1550 return HasCondition( iCondition );
1551
1552 int interrupt = InterruptFromCondition( iCondition );
1553
1554 if ( interrupt == -1 )
1555 {
1556 Assert(0);
1557 return false;
1558 }
1559
1560 bool bReturn = m_ConditionsPreIgnore.GetBit(interrupt);
1561 return (bReturn);
1562}
1563
1564//---------------------------------------------------------
1565//---------------------------------------------------------
1566void CAI_BaseNPC::ClearCondition( int iCondition )
1567{
1568 int interrupt = InterruptFromCondition( iCondition );
1569
1570 if ( interrupt == -1 )
1571 {
1572 Assert(0);
1573 return;
1574 }
1575
1576 m_Conditions.ClearBit(interrupt);
1577}
1578
1579//---------------------------------------------------------
1580//---------------------------------------------------------
1581void CAI_BaseNPC::ClearConditions( int *pConditions, int nConditions )
1582{
1583 for ( int i = 0; i < nConditions; ++i )
1584 {
1585 int iCondition = pConditions[i];
1586 int interrupt = InterruptFromCondition( iCondition );
1587
1588 if ( interrupt == -1 )
1589 {
1590 Assert(0);
1591 continue;
1592 }
1593
1594 m_Conditions.ClearBit( interrupt );
1595 }
1596}
1597
1598//---------------------------------------------------------
1599//---------------------------------------------------------
1600void CAI_BaseNPC::SetIgnoreConditions( int *pConditions, int nConditions )
1601{
1602 for ( int i = 0; i < nConditions; ++i )
1603 {
1604 int iCondition = pConditions[i];
1605 int interrupt = InterruptFromCondition( iCondition );
1606
1607 if ( interrupt == -1 )
1608 {
1609 Assert(0);
1610 continue;
1611 }
1612
1613 m_InverseIgnoreConditions.ClearBit( interrupt ); // clear means ignore
1614 }
1615}
1616
1617void CAI_BaseNPC::ClearIgnoreConditions( int *pConditions, int nConditions )
1618{
1619 for ( int i = 0; i < nConditions; ++i )
1620 {
1621 int iCondition = pConditions[i];
1622 int interrupt = InterruptFromCondition( iCondition );
1623
1624 if ( interrupt == -1 )
1625 {
1626 Assert(0);
1627 continue;
1628 }
1629
1630 m_InverseIgnoreConditions.SetBit( interrupt ); // set means don't ignore
1631 }
1632}
1633
1634//---------------------------------------------------------
1635//---------------------------------------------------------
1636bool CAI_BaseNPC::HasInterruptCondition( int iCondition )
1637{
1638 if( !GetCurSchedule() )
1639 {
1640 return false;
1641 }
1642
1643 int interrupt = InterruptFromCondition( iCondition );
1644
1645 if ( interrupt == -1 )
1646 {
1647 Assert(0);
1648 return false;
1649 }
1650 return ( m_Conditions.GetBit( interrupt ) && GetCurSchedule()->HasInterrupt( interrupt ) );
1651}
1652
1653//---------------------------------------------------------
1654//---------------------------------------------------------
1655bool CAI_BaseNPC::ConditionInterruptsCurSchedule( int iCondition )
1656{
1657 if( !GetCurSchedule() )
1658 {
1659 return false;
1660 }
1661
1662 int interrupt = InterruptFromCondition( iCondition );
1663
1664 if ( interrupt == -1 )
1665 {
1666 Assert(0);
1667 return false;
1668 }
1669 return ( GetCurSchedule()->HasInterrupt( interrupt ) );
1670}
1671
1672//---------------------------------------------------------
1673//---------------------------------------------------------
1674bool CAI_BaseNPC::ConditionInterruptsSchedule( int localScheduleID, int iCondition )
1675{
1676 CAI_Schedule *pSchedule = GetSchedule( localScheduleID );
1677 if ( !pSchedule )
1678 return false;
1679
1680 int interrupt = InterruptFromCondition( iCondition );
1681
1682 if ( interrupt == -1 )
1683 {
1684 Assert(0);
1685 return false;
1686 }
1687 return ( pSchedule->HasInterrupt( interrupt ) );
1688}
1689
1690
1691//-----------------------------------------------------------------------------
1692// Returns whether we currently have any interrupt conditions that would
1693// interrupt the given schedule.
1694//-----------------------------------------------------------------------------
1695bool CAI_BaseNPC::HasConditionsToInterruptSchedule( int nLocalScheduleID )
1696{
1697 CAI_Schedule *pSchedule = GetSchedule( nLocalScheduleID );
1698 if ( !pSchedule )
1699 return false;
1700
1701 CAI_ScheduleBits bitsMask;
1702 pSchedule->GetInterruptMask( &bitsMask );
1703
1704 CAI_ScheduleBits bitsOut;
1705 AccessConditionBits().And( bitsMask, &bitsOut );
1706
1707 return !bitsOut.IsAllClear();
1708}
1709
1710
1711//-----------------------------------------------------------------------------
1712//-----------------------------------------------------------------------------
1713bool CAI_BaseNPC::IsCustomInterruptConditionSet( int nCondition )
1714{
1715 int interrupt = InterruptFromCondition( nCondition );
1716
1717 if ( interrupt == -1 )
1718 {
1719 Assert(0);
1720 return false;
1721 }
1722
1723 return m_CustomInterruptConditions.GetBit( interrupt );
1724}
1725
1726//-----------------------------------------------------------------------------
1727// Purpose: Sets a flag in the custom interrupt flags, translating the condition
1728// to the proper global space, if necessary
1729//-----------------------------------------------------------------------------
1730void CAI_BaseNPC::SetCustomInterruptCondition( int nCondition )
1731{
1732 int interrupt = InterruptFromCondition( nCondition );
1733
1734 if ( interrupt == -1 )
1735 {
1736 Assert(0);
1737 return;
1738 }
1739
1740 m_CustomInterruptConditions.SetBit( interrupt );
1741}
1742
1743//-----------------------------------------------------------------------------
1744// Purpose: Clears a flag in the custom interrupt flags, translating the condition
1745// to the proper global space, if necessary
1746//-----------------------------------------------------------------------------
1747void CAI_BaseNPC::ClearCustomInterruptCondition( int nCondition )
1748{
1749 int interrupt = InterruptFromCondition( nCondition );
1750
1751 if ( interrupt == -1 )
1752 {
1753 Assert(0);
1754 return;
1755 }
1756
1757 m_CustomInterruptConditions.ClearBit( interrupt );
1758}
1759
1760
1761//-----------------------------------------------------------------------------
1762// Purpose: Clears all the custom interrupt flags.
1763//-----------------------------------------------------------------------------
1764void CAI_BaseNPC::ClearCustomInterruptConditions()
1765{
1766 m_CustomInterruptConditions.ClearAllBits();
1767}
1768
1769
1770//-----------------------------------------------------------------------------
1771
1772void CAI_BaseNPC::SetDistLook( float flDistLook )
1773{
1774 m_pSenses->SetDistLook( flDistLook );
1775}
1776
1777//-----------------------------------------------------------------------------
1778
1779bool CAI_BaseNPC::QueryHearSound( CSound *pSound )
1780{
1781 if ( pSound->SoundContext() & SOUND_CONTEXT_COMBINE_ONLY )
1782 return false;
1783
1784 if ( pSound->SoundContext() & SOUND_CONTEXT_ALLIES_ONLY )
1785 {
1786 if ( !IsPlayerAlly() )
1787 return false;
1788 }
1789
1790 if ( pSound->IsSoundType( SOUND_PLAYER ) && GetState() == NPC_STATE_IDLE && !FVisible( pSound->GetSoundReactOrigin() ) )
1791 {
1792 // NPC's that are IDLE should disregard player movement sounds if they can't see them.
1793 // This does not affect them hearing the player's weapon.
1794 // !!!BUGBUG - this probably makes NPC's not hear doors opening, because doors opening put SOUND_PLAYER
1795 // in the world, but the door's model will block the FVisible() trace and this code will then
1796 // deduce that the sound can not be heard
1797 return false;
1798 }
1799
1800 // Disregard footsteps from our own class type
1801 if ( pSound->IsSoundType( SOUND_COMBAT ) && pSound->SoundChannel() == SOUNDENT_CHANNEL_NPC_FOOTSTEP )
1802 {
1803 if ( pSound->m_hOwner && pSound->m_hOwner->ClassMatches( m_iClassname ) )
1804 return false;
1805 }
1806
1807 if( ShouldIgnoreSound( pSound ) )
1808 return false;
1809
1810 return true;
1811}
1812
1813//-----------------------------------------------------------------------------
1814
1815bool CAI_BaseNPC::QuerySeeEntity( CBaseEntity *pEntity, bool bOnlyHateOrFearIfNPC )
1816{
1817 if ( bOnlyHateOrFearIfNPC && pEntity->IsNPC() )
1818 {
1819 Disposition_t disposition = IRelationType( pEntity );
1820 return ( disposition == D_HT || disposition == D_FR );
1821 }
1822 return true;
1823}
1824
1825//-----------------------------------------------------------------------------
1826
1827void CAI_BaseNPC::OnLooked( int iDistance )
1828{
1829 // DON'T let visibility information from last frame sit around!
1830 static int conditionsToClear[] =
1831 {
1832 COND_SEE_HATE,
1833 COND_SEE_DISLIKE,
1834 COND_SEE_ENEMY,
1835 COND_SEE_FEAR,
1836 COND_SEE_NEMESIS,
1837 COND_SEE_PLAYER,
1838 COND_LOST_PLAYER,
1839 COND_ENEMY_WENT_NULL,
1840 };
1841
1842 bool bHadSeePlayer = HasCondition(COND_SEE_PLAYER);
1843
1844 ClearConditions( conditionsToClear, ARRAYSIZE( conditionsToClear ) );
1845
1846 AISightIter_t iter;
1847 CBaseEntity *pSightEnt;
1848
1849 pSightEnt = GetSenses()->GetFirstSeenEntity( &iter );
1850
1851 while( pSightEnt )
1852 {
1853 if ( pSightEnt->IsPlayer() )
1854 {
1855 // if we see a client, remember that (mostly for scripted AI)
1856 SetCondition(COND_SEE_PLAYER);
1857 m_flLastSawPlayerTime = gpGlobals->curtime;
1858 }
1859
1860 Disposition_t relation = IRelationType( pSightEnt );
1861
1862 // the looker will want to consider this entity
1863 // don't check anything else about an entity that can't be seen, or an entity that you don't care about.
1864 if ( relation != D_NU )
1865 {
1866 if ( pSightEnt == GetEnemy() )
1867 {
1868 // we know this ent is visible, so if it also happens to be our enemy, store that now.
1869 SetCondition(COND_SEE_ENEMY);
1870 }
1871
1872 // don't add the Enemy's relationship to the conditions. We only want to worry about conditions when
1873 // we see npcs other than the Enemy.
1874 switch ( relation )
1875 {
1876 case D_HT:
1877 {
1878 int priority = IRelationPriority( pSightEnt );
1879 if (priority < 0)
1880 {
1881 SetCondition(COND_SEE_DISLIKE);
1882 }
1883 else if (priority > 10)
1884 {
1885 SetCondition(COND_SEE_NEMESIS);
1886 }
1887 else
1888 {
1889 SetCondition(COND_SEE_HATE);
1890 }
1891 UpdateEnemyMemory(pSightEnt,pSightEnt->GetAbsOrigin());
1892 break;
1893
1894 }
1895 case D_FR:
1896 UpdateEnemyMemory(pSightEnt,pSightEnt->GetAbsOrigin());
1897 SetCondition(COND_SEE_FEAR);
1898 break;
1899 case D_LI:
1900 case D_NU:
1901 break;
1902 default:
1903 DevWarning( 2, "%s can't assess %s\n", GetClassname(), pSightEnt->GetClassname() );
1904 break;
1905 }
1906 }
1907
1908 pSightEnt = GetSenses()->GetNextSeenEntity( &iter );
1909 }
1910
1911 // Did we lose the player?
1912 if ( bHadSeePlayer && !HasCondition(COND_SEE_PLAYER) )
1913 {
1914 SetCondition(COND_LOST_PLAYER);
1915 }
1916}
1917
1918//-----------------------------------------------------------------------------
1919
1920void CAI_BaseNPC::OnListened()
1921{
1922 AISoundIter_t iter;
1923
1924 CSound *pCurrentSound;
1925
1926 static int conditionsToClear[] =
1927 {
1928 COND_HEAR_DANGER,
1929 COND_HEAR_COMBAT,
1930 COND_HEAR_WORLD,
1931 COND_HEAR_PLAYER,
1932 COND_HEAR_THUMPER,
1933 COND_HEAR_BUGBAIT,
1934 COND_HEAR_PHYSICS_DANGER,
1935 COND_HEAR_BULLET_IMPACT,
1936 COND_HEAR_MOVE_AWAY,
1937
1938 COND_NO_HEAR_DANGER,
1939
1940 COND_SMELL,
1941 };
1942
1943 ClearConditions( conditionsToClear, ARRAYSIZE( conditionsToClear ) );
1944
1945 pCurrentSound = GetSenses()->GetFirstHeardSound( &iter );
1946
1947 while ( pCurrentSound )
1948 {
1949 // the npc cares about this sound, and it's close enough to hear.
1950 int condition = COND_NONE;
1951
1952 if ( pCurrentSound->FIsSound() )
1953 {
1954 // this is an audible sound.
1955 switch( pCurrentSound->SoundTypeNoContext() )
1956 {
1957 case SOUND_DANGER:
1958 if( gpGlobals->curtime > m_flIgnoreDangerSoundsUntil)
1959 condition = COND_HEAR_DANGER;
1960 break;
1961
1962 case SOUND_THUMPER: condition = COND_HEAR_THUMPER; break;
1963 case SOUND_BUGBAIT: condition = COND_HEAR_BUGBAIT; break;
1964 case SOUND_COMBAT:
1965 if ( pCurrentSound->SoundChannel() == SOUNDENT_CHANNEL_SPOOKY_NOISE )
1966 {
1967 condition = COND_HEAR_SPOOKY;
1968 }
1969 else
1970 {
1971 condition = COND_HEAR_COMBAT;
1972 }
1973 break;
1974
1975 case SOUND_WORLD: condition = COND_HEAR_WORLD; break;
1976 case SOUND_PLAYER: condition = COND_HEAR_PLAYER; break;
1977 case SOUND_BULLET_IMPACT: condition = COND_HEAR_BULLET_IMPACT; break;
1978 case SOUND_PHYSICS_DANGER: condition = COND_HEAR_PHYSICS_DANGER; break;
1979 case SOUND_DANGER_SNIPERONLY:/* silence warning */ break;
1980 case SOUND_MOVE_AWAY: condition = COND_HEAR_MOVE_AWAY; break;
1981 case SOUND_PLAYER_VEHICLE: condition = COND_HEAR_PLAYER; break;
1982
1983 default:
1984 DevMsg( "**ERROR: NPC %s hearing sound of unknown type %d!\n", GetClassname(), pCurrentSound->SoundType() );
1985 break;
1986 }
1987 }
1988 else
1989 {
1990 // if not a sound, must be a smell - determine if it's just a scent, or if it's a food scent
1991 condition = COND_SMELL;
1992 }
1993
1994 if ( condition != COND_NONE )
1995 {
1996 SetCondition( condition );
1997 }
1998
1999 pCurrentSound = GetSenses()->GetNextHeardSound( &iter );
2000 }
2001
2002 if( !HasCondition( COND_HEAR_DANGER ) )
2003 {
2004 SetCondition( COND_NO_HEAR_DANGER );
2005 }
2006
2007 // Sound outputs
2008 if ( HasCondition( COND_HEAR_WORLD ) )
2009 {
2010 m_OnHearWorld.FireOutput(this, this);
2011 }
2012
2013 if ( HasCondition( COND_HEAR_PLAYER ) )
2014 {
2015 m_OnHearPlayer.FireOutput(this, this);
2016 }
2017
2018 if ( HasCondition( COND_HEAR_COMBAT ) ||
2019 HasCondition( COND_HEAR_BULLET_IMPACT ) ||
2020 HasCondition( COND_HEAR_DANGER ) )
2021 {
2022 m_OnHearCombat.FireOutput(this, this);
2023 }
2024}
2025
2026//=========================================================
2027// FValidateHintType - tells use whether or not the npc cares
2028// about the type of Hint Node given
2029//=========================================================
2030bool CAI_BaseNPC::FValidateHintType ( CAI_Hint *pHint )
2031{
2032 return false;
2033}
2034
2035//-----------------------------------------------------------------------------
2036// Purpose: Override in subclasses to associate specific hint types
2037// with activities
2038//-----------------------------------------------------------------------------
2039Activity CAI_BaseNPC::GetHintActivity( short sHintType, Activity HintsActivity )
2040{
2041 if ( HintsActivity != ACT_INVALID )
2042 return HintsActivity;
2043
2044 return ACT_IDLE;
2045}
2046
2047//-----------------------------------------------------------------------------
2048// Purpose: Override in subclasses to give specific hint types delays
2049// before they can be used again
2050// Input :
2051// Output :
2052//-----------------------------------------------------------------------------
2053float CAI_BaseNPC::GetHintDelay( short sHintType )
2054{
2055 return 0;
2056}
2057
2058//-----------------------------------------------------------------------------
2059// Purpose: Return incoming grenade if spotted
2060// Input :
2061// Output :
2062//-----------------------------------------------------------------------------
2063CBaseGrenade* CAI_BaseNPC::IncomingGrenade(void)
2064{
2065 int iDist;
2066
2067 AIEnemiesIter_t iter;
2068 for( AI_EnemyInfo_t *pEMemory = GetEnemies()->GetFirst(&iter); pEMemory != NULL; pEMemory = GetEnemies()->GetNext(&iter) )
2069 {
2070 CBaseGrenade* pBG = dynamic_cast<CBaseGrenade*>((CBaseEntity*)pEMemory->hEnemy);
2071
2072 // Make sure this memory is for a grenade and grenade is not dead
2073 if (!pBG || pBG->m_lifeState == LIFE_DEAD)
2074 continue;
2075
2076 // Make sure it's visible
2077 if (!FVisible(pBG))
2078 continue;
2079
2080 // Check if it's near me
2081 iDist = ( pBG->GetAbsOrigin() - GetAbsOrigin() ).Length();
2082 if ( iDist <= NPC_GRENADE_FEAR_DIST )
2083 return pBG;
2084
2085 // Check if it's headed towards me
2086 Vector vGrenadeDir = GetAbsOrigin() - pBG->GetAbsOrigin();
2087 Vector vGrenadeVel;
2088 pBG->GetVelocity( &vGrenadeVel, NULL );
2089 VectorNormalize(vGrenadeDir);
2090 VectorNormalize(vGrenadeVel);
2091 float flDotPr = DotProduct(vGrenadeDir, vGrenadeVel);
2092 if (flDotPr > 0.85)
2093 return pBG;
2094 }
2095 return NULL;
2096}
2097
2098
2099//-----------------------------------------------------------------------------
2100// Purpose: Check my physical state with the environment
2101// Input :
2102// Output :
2103//-----------------------------------------------------------------------------
2104void CAI_BaseNPC::TryRestoreHull(void)
2105{
2106 if ( IsUsingSmallHull() && GetCurSchedule() )
2107 {
2108 trace_t tr;
2109 Vector vUpBit = GetAbsOrigin();
2110 vUpBit.z += 1;
2111
2112 AI_TraceHull( GetAbsOrigin(), vUpBit, GetHullMins(),
2113 GetHullMaxs(), MASK_SOLID, this, COLLISION_GROUP_NONE, &tr );
2114 if ( !tr.startsolid && (tr.fraction == 1.0) )
2115 {
2116 SetHullSizeNormal();
2117 }
2118 }
2119}
2120
2121//=========================================================
2122//=========================================================
2123int CAI_BaseNPC::GetSoundInterests( void )
2124{
2125 return SOUND_WORLD | SOUND_COMBAT | SOUND_PLAYER | SOUND_PLAYER_VEHICLE |
2126 SOUND_BULLET_IMPACT;
2127}
2128
2129//---------------------------------------------------------
2130//---------------------------------------------------------
2131int CAI_BaseNPC::GetSoundPriority( CSound *pSound )
2132{
2133 int iSoundTypeNoContext = pSound->SoundTypeNoContext();
2134 int iSoundContext = pSound->SoundContext();
2135
2136 if( iSoundTypeNoContext & SOUND_DANGER )
2137 {
2138 return SOUND_PRIORITY_HIGHEST;
2139 }
2140
2141 if( iSoundTypeNoContext & SOUND_COMBAT )
2142 {
2143 if( iSoundContext & SOUND_CONTEXT_EXPLOSION )
2144 {
2145 return SOUND_PRIORITY_VERY_HIGH;
2146 }
2147
2148 return SOUND_PRIORITY_HIGH;
2149 }
2150
2151 return SOUND_PRIORITY_NORMAL;
2152}
2153
2154//---------------------------------------------------------
2155// Return the loudest sound of this type in the sound list
2156//---------------------------------------------------------
2157CSound *CAI_BaseNPC::GetLoudestSoundOfType( int iType )
2158{
2159 return CSoundEnt::GetLoudestSoundOfType( iType, EarPosition() );
2160}
2161
2162//=========================================================
2163// GetBestSound - returns a pointer to the sound the npc
2164// should react to. Right now responds only to nearest sound.
2165//=========================================================
2166CSound* CAI_BaseNPC::GetBestSound( int validTypes )
2167{
2168 if ( m_pLockedBestSound->m_iType != SOUND_NONE )
2169 return m_pLockedBestSound;
2170 CSound *pResult = GetSenses()->GetClosestSound( false, validTypes );
2171 if ( pResult == NULL)
2172 DevMsg( "Warning: NULL Return from GetBestSound\n" ); // condition previously set now no longer true. Have seen this when play too many sounds...
2173 return pResult;
2174}
2175
2176//=========================================================
2177// PBestScent - returns a pointer to the scent the npc
2178// should react to. Right now responds only to nearest scent
2179//=========================================================
2180CSound* CAI_BaseNPC::GetBestScent( void )
2181{
2182 CSound *pResult = GetSenses()->GetClosestSound( true );
2183 if ( pResult == NULL)
2184 DevMsg("Warning: NULL Return from GetBestScent\n" );
2185 return pResult;
2186}
2187
2188//-----------------------------------------------------------------------------
2189void CAI_BaseNPC::LockBestSound()
2190{
2191 UnlockBestSound();
2192 CSound *pBestSound = GetBestSound();
2193 if ( pBestSound )
2194 *m_pLockedBestSound = *pBestSound;
2195}
2196
2197//-----------------------------------------------------------------------------
2198void CAI_BaseNPC::UnlockBestSound()
2199{
2200 if ( m_pLockedBestSound->m_iType != SOUND_NONE )
2201 {
2202 m_pLockedBestSound->m_iType = SOUND_NONE;
2203 OnListened(); // reset hearing conditions
2204 }
2205}
2206
2207//-----------------------------------------------------------------------------
2208// Purpose: Return true if the specified sound is visible. Handles sounds generated by entities correctly.
2209// Input : *pSound -
2210//-----------------------------------------------------------------------------
2211bool CAI_BaseNPC::SoundIsVisible( CSound *pSound )
2212{
2213 CBaseEntity *pBlocker = NULL;
2214 if ( !FVisible( pSound->GetSoundReactOrigin(), MASK_OPAQUE, &pBlocker ) )
2215 {
2216 // Is the blocker the sound owner?
2217 if ( pBlocker && pBlocker == pSound->m_hOwner )
2218 return true;
2219
2220 return false;
2221 }
2222 return true;
2223}
2224
2225//-----------------------------------------------------------------------------
2226// Purpose: Returns true if target is in legal range of eye movements
2227// Input :
2228// Output :
2229//-----------------------------------------------------------------------------
2230bool CAI_BaseNPC::ValidEyeTarget(const Vector &lookTargetPos)
2231{
2232 Vector vHeadDir = HeadDirection3D( );
2233 Vector lookTargetDir = lookTargetPos - EyePosition();
2234 VectorNormalize(lookTargetDir);
2235
2236 // Only look if it doesn't crank my eyeballs too far
2237 float dotPr = DotProduct(lookTargetDir, vHeadDir);
2238 if (dotPr > 0.7)
2239 {
2240 return true;
2241 }
2242 return false;
2243}
2244
2245
2246//-----------------------------------------------------------------------------
2247// Purpose: Integrate head turn over time
2248// Input :
2249// Output :
2250//-----------------------------------------------------------------------------
2251void CAI_BaseNPC::SetHeadDirection( const Vector &vTargetPos, float flInterval)
2252{
2253 if (!(CapabilitiesGet() & bits_CAP_TURN_HEAD))
2254 return;
2255
2256#ifdef DEBUG_LOOK
2257 // Draw line in body, head, and eye directions
2258 Vector vEyePos = EyePosition();
2259 Vector vHeadDir;
2260 HeadDirection3D(&vHeadDir);
2261 Vector vBodyDir;
2262 BodyDirection2D(&vBodyDir);
2263
2264 //UNDONE <<TODO>>
2265 // currently eye dir just returns head dir, so use vTargetPos for now
2266 //Vector vEyeDir; w
2267 //EyeDirection3D(&vEyeDir);
2268 NDebugOverlay::Line( vEyePos, vEyePos+(50*vHeadDir), 255, 0, 0, false, 0.1 );
2269 NDebugOverlay::Line( vEyePos, vEyePos+(50*vBodyDir), 0, 255, 0, false, 0.1 );
2270 NDebugOverlay::Line( vEyePos, vTargetPos, 0, 0, 255, false, 0.1 );
2271#endif
2272
2273 //--------------------------------------
2274 // Set head yaw
2275 //--------------------------------------
2276 float flDesiredYaw = VecToYaw(vTargetPos - GetLocalOrigin()) - GetLocalAngles().y;
2277 if (flDesiredYaw > 180)
2278 flDesiredYaw -= 360;
2279 if (flDesiredYaw < -180)
2280 flDesiredYaw += 360;
2281
2282 float iRate = 0.8;
2283
2284 // Make frame rate independent
2285 float timeToUse = flInterval;
2286 while (timeToUse > 0)
2287 {
2288 m_flHeadYaw = (iRate * m_flHeadYaw) + (1-iRate)*flDesiredYaw;
2289 timeToUse -= 0.1;
2290 }
2291 if (m_flHeadYaw > 360) m_flHeadYaw = 0;
2292
2293 m_flHeadYaw = SetBoneController( 0, m_flHeadYaw );
2294
2295
2296 //--------------------------------------
2297 // Set head pitch
2298 //--------------------------------------
2299 Vector vEyePosition = EyePosition();
2300 float fTargetDist = (vTargetPos - vEyePosition).Length();
2301 float fVertDist = vTargetPos.z - vEyePosition.z;
2302 float flDesiredPitch = -RAD2DEG(atan(fVertDist/fTargetDist));
2303
2304 // Make frame rate independent
2305 timeToUse = flInterval;
2306 while (timeToUse > 0)
2307 {
2308 m_flHeadPitch = (iRate * m_flHeadPitch) + (1-iRate)*flDesiredPitch;
2309 timeToUse -= 0.1;
2310 }
2311 if (m_flHeadPitch > 360) m_flHeadPitch = 0;
2312
2313 SetBoneController( 1, m_flHeadPitch );
2314
2315}
2316
2317//------------------------------------------------------------------------------
2318// Purpose : Calculate the NPC's eye direction in 2D world space
2319// Input :
2320// Output :
2321//------------------------------------------------------------------------------
2322Vector CAI_BaseNPC::EyeDirection2D( void )
2323{
2324 // UNDONE
2325 // For now just return head direction....
2326 return HeadDirection2D( );
2327}
2328
2329//------------------------------------------------------------------------------
2330// Purpose : Calculate the NPC's eye direction in 2D world space
2331// Input :
2332// Output :
2333//------------------------------------------------------------------------------
2334Vector CAI_BaseNPC::EyeDirection3D( void )
2335{
2336 // UNDONE //<<TODO>>
2337 // For now just return head direction....
2338 return HeadDirection3D( );
2339}
2340
2341//------------------------------------------------------------------------------
2342// Purpose : Calculate the NPC's head direction in 2D world space
2343// Input :
2344// Output :
2345//------------------------------------------------------------------------------
2346Vector CAI_BaseNPC::HeadDirection2D( void )
2347{
2348 // UNDONE <<TODO>>
2349 // This does not account for head rotations in the animation,
2350 // only those done via bone controllers
2351
2352 // Head yaw is in local cooridnate so it must be added to the body's yaw
2353 QAngle bodyAngles = BodyAngles();
2354 float flWorldHeadYaw = m_flHeadYaw + bodyAngles.y;
2355
2356 // Convert head yaw into facing direction
2357 return UTIL_YawToVector( flWorldHeadYaw );
2358}
2359
2360//------------------------------------------------------------------------------
2361// Purpose : Calculate the NPC's head direction in 3D world space
2362// Input :
2363// Output :
2364//------------------------------------------------------------------------------
2365Vector CAI_BaseNPC::HeadDirection3D( void )
2366{
2367 Vector vHeadDirection;
2368
2369 // UNDONE <<TODO>>
2370 // This does not account for head rotations in the animation,
2371 // only those done via bone controllers
2372
2373 // Head yaw is in local cooridnate so it must be added to the body's yaw
2374 QAngle bodyAngles = BodyAngles();
2375 float flWorldHeadYaw = m_flHeadYaw + bodyAngles.y;
2376
2377 // Convert head yaw into facing direction
2378 AngleVectors( QAngle( m_flHeadPitch, flWorldHeadYaw, 0), &vHeadDirection );
2379 return vHeadDirection;
2380}
2381
2382
2383//-----------------------------------------------------------------------------
2384// Purpose: Look at other NPCs and clients from time to time
2385// Input :
2386// Output :
2387//-----------------------------------------------------------------------------
2388CBaseEntity *CAI_BaseNPC::EyeLookTarget( void )
2389{
2390 if (m_flNextEyeLookTime < gpGlobals->curtime)
2391 {
2392 CBaseEntity* pBestEntity = NULL;
2393 float fBestDist = MAX_COORD_RANGE;
2394 float fTestDist;
2395
2396 CBaseEntity *pEntity = NULL;
2397
2398 for ( CEntitySphereQuery sphere( GetAbsOrigin(), 1024, 0 ); (pEntity = sphere.GetCurrentEntity()) != NULL; sphere.NextEntity() )
2399 {
2400 if (pEntity == this)
2401 {
2402 continue;
2403 }
2404 CAI_BaseNPC *pNPC = pEntity->MyNPCPointer();
2405 if (pNPC || (pEntity->GetFlags() & FL_CLIENT))
2406 {
2407 fTestDist = (GetAbsOrigin() - pEntity->EyePosition()).Length();
2408 if (fTestDist < fBestDist)
2409 {
2410 if (ValidEyeTarget(pEntity->EyePosition()))
2411 {
2412 fBestDist = fTestDist;
2413 pBestEntity = pEntity;
2414 }
2415 }
2416 }
2417 }
2418 if (pBestEntity)
2419 {
2420 m_flNextEyeLookTime = gpGlobals->curtime + random->RandomInt(1,5);
2421 m_hEyeLookTarget = pBestEntity;
2422 }
2423 }
2424 return m_hEyeLookTarget;
2425}
2426
2427
2428//-----------------------------------------------------------------------------
2429// Purpose: Set direction that the NPC aiming their gun
2430// returns true is the passed Vector is in
2431// the caller's forward aim cone. The dot product is performed
2432// in 2d, making the view cone infinitely tall. By default, the
2433// callers aim cone is assumed to be very narrow
2434//-----------------------------------------------------------------------------
2435
2436bool CAI_BaseNPC::FInAimCone( const Vector &vecSpot )
2437{
2438 Vector los = ( vecSpot - GetAbsOrigin() );
2439
2440 // do this in 2D
2441 los.z = 0;
2442 VectorNormalize( los );
2443
2444 Vector facingDir = BodyDirection2D( );
2445
2446 float flDot = DotProduct( los, facingDir );
2447
2448 if (CapabilitiesGet() & bits_CAP_AIM_GUN)
2449 {
2450 // FIXME: query current animation for ranges
2451 return ( flDot > DOT_30DEGREE );
2452 }
2453
2454 if ( flDot > 0.994 )//!!!BUGBUG - magic number same as FacingIdeal(), what is this?
2455 return true;
2456
2457 return false;
2458}
2459
2460
2461//-----------------------------------------------------------------------------
2462// Purpose: Set direction that the NPC aiming their gun
2463//-----------------------------------------------------------------------------
2464
2465void CAI_BaseNPC::SetAim( const Vector &aimDir )
2466{
2467 QAngle angDir;
2468 VectorAngles( aimDir, angDir );
2469
2470 float curPitch = GetPoseParameter( "aim_pitch" );
2471 float curYaw = GetPoseParameter( "aim_yaw" );
2472
2473 float newPitch;
2474 float newYaw;
2475
2476 if( GetEnemy() )
2477 {
2478 // clamp and dampen movement
2479 newPitch = curPitch + 0.8 * UTIL_AngleDiff( UTIL_ApproachAngle( angDir.x, curPitch, 20 ), curPitch );
2480
2481 float flRelativeYaw = UTIL_AngleDiff( angDir.y, GetAbsAngles().y );
2482 // float flNewTargetYaw = UTIL_ApproachAngle( flRelativeYaw, curYaw, 20 );
2483 // float newYaw = curYaw + 0.8 * UTIL_AngleDiff( flNewTargetYaw, curYaw );
2484 newYaw = curYaw + UTIL_AngleDiff( flRelativeYaw, curYaw );
2485 }
2486 else
2487 {
2488 // Sweep your weapon more slowly if you're not fighting someone
2489 newPitch = curPitch + 0.6 * UTIL_AngleDiff( UTIL_ApproachAngle( angDir.x, curPitch, 20 ), curPitch );
2490
2491 float flRelativeYaw = UTIL_AngleDiff( angDir.y, GetAbsAngles().y );
2492 newYaw = curYaw + 0.6 * UTIL_AngleDiff( flRelativeYaw, curYaw );
2493 }
2494
2495 newPitch = AngleNormalize( newPitch );
2496 newYaw = AngleNormalize( newYaw );
2497
2498 SetPoseParameter( "aim_pitch", newPitch );
2499 SetPoseParameter( "aim_yaw", newYaw );
2500
2501 // Msg("yaw %.0f (%.0f %.0f)\n", newYaw, angDir.y, GetAbsAngles().y );
2502
2503 // Calculate our interaction yaw.
2504 // If we've got a small adjustment off our abs yaw, use that.
2505 // Otherwise, use our abs yaw.
2506 if ( fabs(newYaw) < 20 )
2507 {
2508 m_flInteractionYaw = angDir.y;
2509 }
2510 else
2511 {
2512 m_flInteractionYaw = GetAbsAngles().y;
2513 }
2514}
2515
2516
2517void CAI_BaseNPC::RelaxAim( )
2518{
2519 float curPitch = GetPoseParameter( "aim_pitch" );
2520 float curYaw = GetPoseParameter( "aim_yaw" );
2521
2522 // dampen existing aim
2523 float newPitch = AngleNormalize( UTIL_ApproachAngle( 0, curPitch, 3 ) );
2524 float newYaw = AngleNormalize( UTIL_ApproachAngle( 0, curYaw, 2 ) );
2525
2526 SetPoseParameter( "aim_pitch", newPitch );
2527 SetPoseParameter( "aim_yaw", newYaw );
2528 // DevMsg("relax aim %.0f %0.f\n", newPitch, newYaw );
2529}
2530
2531//-----------------------------------------------------------------------------
2532void CAI_BaseNPC::AimGun()
2533{
2534 if (GetEnemy())
2535 {
2536 Vector vecShootOrigin;
2537
2538 vecShootOrigin = Weapon_ShootPosition();
2539 Vector vecShootDir = GetShootEnemyDir( vecShootOrigin, false );
2540
2541 SetAim( vecShootDir );
2542 }
2543 else
2544 {
2545 RelaxAim( );
2546 }
2547}
2548
2549//-----------------------------------------------------------------------------
2550// Purpose: Set direction that the NPC is looking
2551// Input :
2552// Output :
2553//-----------------------------------------------------------------------------
2554void CAI_BaseNPC::MaintainLookTargets ( float flInterval )
2555{
2556 // --------------------------------------------------------
2557 // Try to look at enemy if I have one
2558 // --------------------------------------------------------
2559 if ((CBaseEntity*)GetEnemy())
2560 {
2561 if ( ValidEyeTarget(GetEnemy()->EyePosition()) )
2562 {
2563 SetHeadDirection(GetEnemy()->EyePosition(),flInterval);
2564 SetViewtarget( GetEnemy()->EyePosition() );
2565 return;
2566 }
2567 }
2568
2569#if 0
2570 // --------------------------------------------------------
2571 // First check if I've been assigned to look at an entity
2572 // --------------------------------------------------------
2573 CBaseEntity *lookTarget = EyeLookTarget();
2574 if (lookTarget && ValidEyeTarget(lookTarget->EyePosition()))
2575 {
2576 SetHeadDirection(lookTarget->EyePosition(),flInterval);
2577 SetViewtarget( lookTarget->EyePosition() );
2578 return;
2579 }
2580#endif
2581
2582 // --------------------------------------------------------
2583 // If I'm moving, look at my target position
2584 // --------------------------------------------------------
2585 if (GetNavigator()->IsGoalActive() && ValidEyeTarget(GetNavigator()->GetCurWaypointPos()))
2586 {
2587 SetHeadDirection(GetNavigator()->GetCurWaypointPos(),flInterval);
2588 SetViewtarget( GetNavigator()->GetCurWaypointPos() );
2589 return;
2590 }
2591
2592
2593 // -------------------------------------
2594 // If I hear a combat sounds look there
2595 // -------------------------------------
2596 if ( HasCondition(COND_HEAR_COMBAT) || HasCondition(COND_HEAR_DANGER) )
2597 {
2598 CSound *pSound = GetBestSound();
2599
2600 if ( pSound && pSound->IsSoundType(SOUND_COMBAT | SOUND_DANGER) )
2601 {
2602 if (ValidEyeTarget( pSound->GetSoundOrigin() ))
2603 {
2604 SetHeadDirection(pSound->GetSoundOrigin(),flInterval);
2605 SetViewtarget( pSound->GetSoundOrigin() );
2606 return;
2607 }
2608 }
2609 }
2610
2611 // -------------------------------------
2612 // Otherwise just look around randomly
2613 // -------------------------------------
2614
2615 // Check that look target position is still valid
2616 if (m_flNextEyeLookTime > gpGlobals->curtime)
2617 {
2618 if (!ValidEyeTarget(m_vEyeLookTarget))
2619 {
2620 // Force choosing of new look target
2621 m_flNextEyeLookTime = 0;
2622 }
2623 }
2624
2625 if (m_flNextEyeLookTime < gpGlobals->curtime)
2626 {
2627 Vector vBodyDir = BodyDirection2D( );
2628
2629 /*
2630 Vector lookSpread = Vector(0.82,0.82,0.22);
2631 float x = random->RandomFloat(-0.5,0.5) + random->RandomFloat(-0.5,0.5);
2632 float y = random->RandomFloat(-0.5,0.5) + random->RandomFloat(-0.5,0.5);
2633 float z = random->RandomFloat(-0.5,0.5) + random->RandomFloat(-0.5,0.5);
2634
2635 QAngle angles;
2636 VectorAngles( vBodyDir, angles );
2637 Vector forward, right, up;
2638 AngleVectors( angles, &forward, &right, &up );
2639
2640 Vector vecDir = vBodyDir + (x * lookSpread.x * forward) + (y * lookSpread.y * right) + (z * lookSpread.z * up);
2641 float lookDist = random->RandomInt(50,2000);
2642 m_vEyeLookTarget = EyePosition() + lookDist*vecDir;
2643 */
2644 m_vEyeLookTarget = EyePosition() + 500*vBodyDir;
2645 m_flNextEyeLookTime = gpGlobals->curtime + 0.5; // random->RandomInt(1,5);
2646 }
2647 SetHeadDirection(m_vEyeLookTarget,flInterval);
2648
2649 // ----------------------------------------------------
2650 // Integrate eye turn in frame rate independent manner
2651 // ----------------------------------------------------
2652 float timeToUse = flInterval;
2653 while (timeToUse > 0)
2654 {
2655 m_vCurEyeTarget = ((1 - m_flEyeIntegRate) * m_vCurEyeTarget + m_flEyeIntegRate * m_vEyeLookTarget);
2656 timeToUse -= 0.1;
2657 }
2658 SetViewtarget( m_vCurEyeTarget );
2659}
2660
2661
2662//-----------------------------------------------------------------------------
2663// Let the motor deal with turning presentation issues
2664//-----------------------------------------------------------------------------
2665
2666void CAI_BaseNPC::MaintainTurnActivity( )
2667{
2668 AI_PROFILE_SCOPE( CAI_BaseNPC_MaintainTurnActivity );
2669 GetMotor()->MaintainTurnActivity( );
2670}
2671
2672
2673//-----------------------------------------------------------------------------
2674// Here's where all motion occurs
2675//-----------------------------------------------------------------------------
2676void CAI_BaseNPC::PerformMovement()
2677{
2678 // don't bother to move if the npc isn't alive
2679 if (!IsAlive())
2680 return;
2681
2682 AI_PROFILE_SCOPE(CAI_BaseNPC_PerformMovement);
2683 g_AIMoveTimer.Start();
2684
2685 float flInterval = ( m_flTimeLastMovement != FLT_MAX ) ? gpGlobals->curtime - m_flTimeLastMovement : 0.1;
2686
2687 m_pNavigator->Move( ROUND_TO_TICKS( flInterval ) );
2688 m_flTimeLastMovement = gpGlobals->curtime;
2689
2690 g_AIMoveTimer.End();
2691
2692}
2693
2694//-----------------------------------------------------------------------------
2695// Updates to npc after movement is completed
2696//-----------------------------------------------------------------------------
2697
2698void CAI_BaseNPC::PostMovement()
2699{
2700 AI_PROFILE_SCOPE( CAI_BaseNPC_PostMovement );
2701
2702 InvalidateBoneCache();
2703
2704 if ( GetModelPtr() && GetModelPtr()->SequencesAvailable() )
2705 {
2706 float flInterval = GetAnimTimeInterval();
2707
2708 if ( CapabilitiesGet() & bits_CAP_AIM_GUN )
2709 {
2710 AI_PROFILE_SCOPE( CAI_BaseNPC_PM_AimGun );
2711 AimGun();
2712 }
2713
2714 // set look targets for npcs with animated faces
2715 if ( CapabilitiesGet() & bits_CAP_ANIMATEDFACE )
2716 {
2717 AI_PROFILE_SCOPE( CAI_BaseNPC_PM_MaintainLookTargets );
2718 MaintainLookTargets(flInterval);
2719 }
2720
2721 }
2722
2723
2724 {
2725 AI_PROFILE_SCOPE( CAI_BaseNPC_PM_MaintainTurnActivity );
2726 // update turning as needed
2727 MaintainTurnActivity( );
2728 }
2729}
2730
2731
2732//-----------------------------------------------------------------------------
2733
2734float g_AINextDisabledMessageTime;
2735extern bool IsInCommentaryMode( void );
2736
2737bool CAI_BaseNPC::PreThink( void )
2738{
2739 // ----------------------------------------------------------
2740 // Skip AI if its been disabled or networks haven't been
2741 // loaded, and put a warning message on the screen
2742 //
2743 // Don't do this if the convar wants it hidden
2744 // ----------------------------------------------------------
2745 if ( (CAI_BaseNPC::m_nDebugBits & bits_debugDisableAI || !g_pAINetworkManager->NetworksLoaded()) )
2746 {
2747 if ( gpGlobals->curtime >= g_AINextDisabledMessageTime && !IsInCommentaryMode() )
2748 {
2749 g_AINextDisabledMessageTime = gpGlobals->curtime + 0.5f;
2750
2751 hudtextparms_s tTextParam;
2752 tTextParam.x = 0.7;
2753 tTextParam.y = 0.65;
2754 tTextParam.effect = 0;
2755 tTextParam.r1 = 255;
2756 tTextParam.g1 = 255;
2757 tTextParam.b1 = 255;
2758 tTextParam.a1 = 255;
2759 tTextParam.r2 = 255;
2760 tTextParam.g2 = 255;
2761 tTextParam.b2 = 255;
2762 tTextParam.a2 = 255;
2763 tTextParam.fadeinTime = 0;
2764 tTextParam.fadeoutTime = 0;
2765 tTextParam.holdTime = 0.6;
2766 tTextParam.fxTime = 0;
2767 tTextParam.channel = 1;
2768 UTIL_HudMessageAll( tTextParam, "A.I. Disabled...\n" );
2769 }
2770 SetActivity( ACT_IDLE );
2771 return false;
2772 }
2773
2774 // --------------------------------------------------------
2775 // If debug stepping
2776 // --------------------------------------------------------
2777 if (CAI_BaseNPC::m_nDebugBits & bits_debugStepAI)
2778 {
2779 if (m_nDebugCurIndex >= CAI_BaseNPC::m_nDebugPauseIndex)
2780 {
2781 if (!GetNavigator()->IsGoalActive())
2782 {
2783 m_flPlaybackRate = 0;
2784 }
2785 return false;
2786 }
2787 else
2788 {
2789 m_flPlaybackRate = 1;
2790 }
2791 }
2792
2793 if ( m_hOpeningDoor.Get() && AIIsDebuggingDoors( this ) )
2794 {
2795 NDebugOverlay::Line( EyePosition(), m_hOpeningDoor->WorldSpaceCenter(), 255, 255, 255, false, .1 );
2796 }
2797
2798 return true;
2799}
2800
2801//-----------------------------------------------------------------------------
2802
2803void CAI_BaseNPC::RunAnimation( void )
2804{
2805 VPROF_BUDGET( "CAI_BaseNPC_RunAnimation", VPROF_BUDGETGROUP_SERVER_ANIM );
2806
2807 if ( !GetModelPtr() )
2808 return;
2809
2810 float flInterval = GetAnimTimeInterval();
2811
2812 StudioFrameAdvance( ); // animate
2813
2814 if ((CAI_BaseNPC::m_nDebugBits & bits_debugStepAI) && !GetNavigator()->IsGoalActive())
2815 {
2816 flInterval = 0;
2817 }
2818
2819 // start or end a fidget
2820 // This needs a better home -- switching animations over time should be encapsulated on a per-activity basis
2821 // perhaps MaintainActivity() or a ShiftAnimationOverTime() or something.
2822 if ( m_NPCState != NPC_STATE_SCRIPT && m_NPCState != NPC_STATE_DEAD && m_Activity == ACT_IDLE && IsActivityFinished() )
2823 {
2824 int iSequence;
2825
2826 // FIXME: this doesn't reissue a translation, so if the idle activity translation changes over time, it'll never get reset
2827 if ( SequenceLoops() )
2828 {
2829 // animation does loop, which means we're playing subtle idle. Might need to fidget.
2830 iSequence = SelectWeightedSequence ( m_translatedActivity );
2831 }
2832 else
2833 {
2834 // animation that just ended doesn't loop! That means we just finished a fidget
2835 // and should return to our heaviest weighted idle (the subtle one)
2836 iSequence = SelectHeaviestSequence ( m_translatedActivity );
2837 }
2838 if ( iSequence != ACTIVITY_NOT_AVAILABLE )
2839 {
2840 ResetSequence( iSequence ); // Set to new anim (if it's there)
2841
2842 //Adrian: Basically everywhere else in the AI code this variable gets set to whatever our sequence is.
2843 //But here it doesn't and not setting it causes any animation set through here to be stomped by the
2844 //ideal sequence before it has a chance of playing out (since there's code that reselects the ideal
2845 //sequence if it doesn't match the current one).
2846 if ( hl2_episodic.GetBool() )
2847 {
2848 m_nIdealSequence = iSequence;
2849 }
2850 }
2851 }
2852
2853 DispatchAnimEvents( this );
2854}
2855
2856//-----------------------------------------------------------------------------
2857
2858void CAI_BaseNPC::PostRun( void )
2859{
2860 AI_PROFILE_SCOPE(CAI_BaseNPC_PostRun);
2861
2862 g_AIPostRunTimer.Start();
2863
2864 if ( !IsMoving() )
2865 {
2866 if ( GetIdealActivity() == ACT_WALK ||
2867 GetIdealActivity() == ACT_RUN ||
2868 GetIdealActivity() == ACT_WALK_AIM ||
2869 GetIdealActivity() == ACT_RUN_AIM )
2870 {
2871 PostRunStopMoving();
2872 }
2873 }
2874
2875 RunAnimation();
2876
2877 // update slave items (weapons)
2878 Weapon_FrameUpdate();
2879
2880 g_AIPostRunTimer.End();
2881}
2882
2883//-----------------------------------------------------------------------------
2884
2885void CAI_BaseNPC::PostRunStopMoving()
2886{
2887 DbgNavMsg1( this, "NPC %s failed to stop properly, slamming activity\n", GetDebugName() );
2888 if ( !GetNavigator()->SetGoalFromStoppingPath() )
2889 SetIdealActivity( GetStoppedActivity() ); // This is to prevent running in place
2890}
2891
2892//-----------------------------------------------------------------------------
2893
2894bool CAI_BaseNPC::ShouldAlwaysThink()
2895{
2896 // @TODO (toml 07-08-03): This needs to be beefed up.
2897 // There are simple cases already seen where an AI can briefly leave
2898 // the PVS while navigating to the player. Perhaps should incorporate a heuristic taking into
2899 // account mode, enemy, last time saw player, player range etc. For example, if enemy is player,
2900 // and player is within 100 feet, and saw the player within the past 15 seconds, keep running...
2901 return HasSpawnFlags(SF_NPC_ALWAYSTHINK);
2902}
2903
2904
2905bool CAI_BaseNPC::ShouldPlayerAvoid( void )
2906{
2907 if ( GetState() == NPC_STATE_SCRIPT )
2908 return true;
2909
2910 if ( IsInAScript() )
2911 return true;
2912
2913 if ( IsInLockedScene() == true )
2914 return true;
2915
2916 if ( HasSpawnFlags( SF_NPC_ALTCOLLISION ) )
2917 return true;
2918
2919 return false;
2920}
2921
2922//-----------------------------------------------------------------------------
2923
2924void CAI_BaseNPC::UpdateEfficiency( bool bInPVS )
2925{
2926 // Sleeping NPCs always dormant
2927 if ( GetSleepState() != AISS_AWAKE )
2928 {
2929 SetEfficiency( AIE_DORMANT );
2930 return;
2931 }
2932
2933 m_bInChoreo = ( GetState() == NPC_STATE_SCRIPT || IsCurSchedule( SCHED_SCENE_GENERIC, false ) );
2934
2935 if ( !ShouldUseEfficiency() )
2936 {
2937 SetEfficiency( AIE_NORMAL );
2938 SetMoveEfficiency( AIME_NORMAL );
2939 return;
2940 }
2941
2942 //---------------------------------
2943
2944 CBasePlayer *pPlayer = AI_GetSinglePlayer();
2945 static Vector vPlayerEyePosition;
2946 static Vector vPlayerForward;
2947 static int iPrevFrame = -1;
2948 if ( gpGlobals->framecount != iPrevFrame )
2949 {
2950 iPrevFrame = gpGlobals->framecount;
2951 if ( pPlayer )
2952 {
2953 pPlayer->EyePositionAndVectors( &vPlayerEyePosition, &vPlayerForward, NULL, NULL );
2954 }
2955 }
2956
2957 Vector vToNPC = GetAbsOrigin() - vPlayerEyePosition;
2958 float playerDist = VectorNormalize( vToNPC );
2959 bool bPlayerFacing;
2960
2961 bool bClientPVSExpanded = UTIL_ClientPVSIsExpanded();
2962
2963 if ( pPlayer )
2964 {
2965 bPlayerFacing = ( bClientPVSExpanded || ( bInPVS && vPlayerForward.Dot( vToNPC ) > 0 ) );
2966 }
2967 else
2968 {
2969 playerDist = 0;
2970 bPlayerFacing = true;
2971 }
2972
2973 //---------------------------------
2974
2975 bool bInVisibilityPVS = ( bClientPVSExpanded && UTIL_FindClientInVisibilityPVS( edict() ) != NULL );
2976
2977 //---------------------------------
2978
2979 if ( ( bInPVS && ( bPlayerFacing || playerDist < 25*12 ) ) || bClientPVSExpanded )
2980 {
2981 SetMoveEfficiency( AIME_NORMAL );
2982 }
2983 else
2984 {
2985 SetMoveEfficiency( AIME_EFFICIENT );
2986 }
2987
2988 //---------------------------------
2989
2990 if ( !IsRetail() && ai_efficiency_override.GetInt() > AIE_NORMAL && ai_efficiency_override.GetInt() <= AIE_DORMANT )
2991 {
2992 SetEfficiency( (AI_Efficiency_t)ai_efficiency_override.GetInt() );
2993 return;
2994 }
2995
2996 //---------------------------------
2997
2998 // Some conditions will always force normal
2999 if ( gpGlobals->curtime - GetLastAttackTime() < .15 )
3000 {
3001 SetEfficiency( AIE_NORMAL );
3002 return;
3003 }
3004
3005 bool bFramerateOk = ( gpGlobals->frametime < ai_frametime_limit.GetFloat() );
3006
3007 if ( m_bForceConditionsGather ||
3008 gpGlobals->curtime - GetLastAttackTime() < .2 ||
3009 gpGlobals->curtime - m_flLastDamageTime < .2 ||
3010 ( GetState() < NPC_STATE_IDLE || GetState() > NPC_STATE_SCRIPT ) ||
3011 ( ( bInPVS || bInVisibilityPVS ) &&
3012 ( ( GetTask() && !TaskIsRunning() ) ||
3013 GetTaskInterrupt() > 0 ||
3014 m_bInChoreo ) ) )
3015 {
3016 SetEfficiency( ( bFramerateOk ) ? AIE_NORMAL : AIE_EFFICIENT );
3017 return;
3018 }
3019
3020 AI_Efficiency_t minEfficiency;
3021
3022 if ( !ShouldDefaultEfficient() )
3023 {
3024 minEfficiency = ( bFramerateOk ) ? AIE_NORMAL : AIE_EFFICIENT;
3025 }
3026 else
3027 {
3028 minEfficiency = ( bFramerateOk ) ? AIE_EFFICIENT : AIE_VERY_EFFICIENT;
3029 }
3030
3031 // Stay normal if there's any chance of a relevant danger sound
3032 bool bPotentialDanger = false;
3033
3034 if ( GetSoundInterests() & SOUND_DANGER )
3035 {
3036 int iSound = CSoundEnt::ActiveList();
3037
3038 while ( iSound != SOUNDLIST_EMPTY )
3039 {
3040 CSound *pCurrentSound = CSoundEnt::SoundPointerForIndex( iSound );
3041
3042 float hearingSensitivity = HearingSensitivity();
3043 Vector vEarPosition = EarPosition();
3044
3045 if ( pCurrentSound && (SOUND_DANGER & pCurrentSound->SoundType()) )
3046 {
3047 float flHearDistanceSq = pCurrentSound->Volume() * hearingSensitivity;
3048 flHearDistanceSq *= flHearDistanceSq;
3049 if ( pCurrentSound->GetSoundOrigin().DistToSqr( vEarPosition ) <= flHearDistanceSq )
3050 {
3051 bPotentialDanger = true;
3052 break;
3053 }
3054 }
3055
3056 iSound = pCurrentSound->NextSound();
3057 }
3058 }
3059
3060 if ( bPotentialDanger )
3061 {
3062 SetEfficiency( minEfficiency );
3063 return;
3064 }
3065
3066 //---------------------------------
3067
3068 if ( !pPlayer )
3069 {
3070 // No heuristic currently for dedicated servers
3071 SetEfficiency( minEfficiency );
3072 return;
3073 }
3074
3075 enum
3076 {
3077 NEAR,
3078 MID,
3079 FAR
3080 };
3081
3082 int range;
3083 if ( bInPVS )
3084 {
3085 if ( playerDist < 15*12 )
3086 {
3087 SetEfficiency( minEfficiency );
3088 return;
3089 }
3090
3091 range = ( playerDist < 50*12 ) ? NEAR :
3092 ( playerDist < 200*12 ) ? MID : FAR;
3093 }
3094 else
3095 {
3096 range = ( playerDist < 25*12 ) ? NEAR :
3097 ( playerDist < 100*12 ) ? MID : FAR;
3098 }
3099
3100 // Efficiency mappings
3101 int state = GetState();
3102 if (state == NPC_STATE_SCRIPT ) // Treat script as alert. Already confirmed not in PVS
3103 state = NPC_STATE_ALERT;
3104
3105 static AI_Efficiency_t mappings[] =
3106 {
3107 // Idle
3108 // In PVS
3109 // Facing
3110 AIE_NORMAL,
3111 AIE_EFFICIENT,
3112 AIE_EFFICIENT,
3113 // Not facing
3114 AIE_EFFICIENT,
3115 AIE_EFFICIENT,
3116 AIE_VERY_EFFICIENT,
3117 // Not in PVS
3118 AIE_VERY_EFFICIENT,
3119 AIE_SUPER_EFFICIENT,
3120 AIE_SUPER_EFFICIENT,
3121 // Alert
3122 // In PVS
3123 // Facing
3124 AIE_NORMAL,
3125 AIE_EFFICIENT,
3126 AIE_EFFICIENT,
3127 // Not facing
3128 AIE_NORMAL,
3129 AIE_EFFICIENT,
3130 AIE_EFFICIENT,
3131 // Not in PVS
3132 AIE_EFFICIENT,
3133 AIE_VERY_EFFICIENT,
3134 AIE_SUPER_EFFICIENT,
3135 // Combat
3136 // In PVS
3137 // Facing
3138 AIE_NORMAL,
3139 AIE_NORMAL,
3140 AIE_EFFICIENT,
3141 // Not facing
3142 AIE_NORMAL,
3143 AIE_EFFICIENT,
3144 AIE_EFFICIENT,
3145 // Not in PVS
3146 AIE_NORMAL,
3147 AIE_EFFICIENT,
3148 AIE_VERY_EFFICIENT,
3149 };
3150
3151 static const int stateBase[] = { 0, 9, 18 };
3152 const int NOT_FACING_OFFSET = 3;
3153 const int NO_PVS_OFFSET = 6;
3154
3155 int iStateOffset = stateBase[state - NPC_STATE_IDLE] ;
3156 int iFacingOffset = (!bInPVS || bPlayerFacing) ? 0 : NOT_FACING_OFFSET;
3157 int iPVSOffset = (bInPVS) ? 0 : NO_PVS_OFFSET;
3158 int iMapping = iStateOffset + iPVSOffset + iFacingOffset + range;
3159
3160 Assert( iMapping < ARRAYSIZE( mappings ) );
3161
3162 AI_Efficiency_t efficiency = mappings[iMapping];
3163
3164 //---------------------------------
3165
3166 AI_Efficiency_t maxEfficiency = AIE_SUPER_EFFICIENT;
3167 if ( bInVisibilityPVS && state >= NPC_STATE_ALERT )
3168 {
3169 maxEfficiency = AIE_EFFICIENT;
3170 }
3171 else if ( bInVisibilityPVS || HasCondition( COND_SEE_PLAYER ) )
3172 {
3173 maxEfficiency = AIE_VERY_EFFICIENT;
3174 }
3175
3176 //---------------------------------
3177
3178 SetEfficiency( clamp( efficiency, minEfficiency, maxEfficiency ) );
3179}
3180
3181
3182//-----------------------------------------------------------------------------
3183
3184void CAI_BaseNPC::UpdateSleepState( bool bInPVS )
3185{
3186 if ( GetSleepState() > AISS_AWAKE )
3187 {
3188 CBasePlayer *pLocalPlayer = AI_GetSinglePlayer();
3189 if ( !pLocalPlayer )
3190 {
3191 if ( gpGlobals->maxClients > 1 )
3192 {
3193 Wake();
3194 }
3195 else
3196 {
3197 Warning( "CAI_BaseNPC::UpdateSleepState called with NULL pLocalPlayer\n" );
3198 }
3199 return;
3200 }
3201
3202 if ( m_flWakeRadius > .1 && !(pLocalPlayer->GetFlags() & FL_NOTARGET) && ( pLocalPlayer->GetAbsOrigin() - GetAbsOrigin() ).LengthSqr() <= Square(m_flWakeRadius) )
3203 Wake();
3204 else if ( GetSleepState() == AISS_WAITING_FOR_PVS )
3205 {
3206 if ( bInPVS )
3207 Wake();
3208 }
3209 else if ( GetSleepState() == AISS_WAITING_FOR_THREAT )
3210 {
3211 if ( HasCondition( COND_LIGHT_DAMAGE ) || HasCondition( COND_HEAVY_DAMAGE ) )
3212 Wake();
3213 else
3214 {
3215 if ( bInPVS )
3216 {
3217 for (int i = 1; i <= gpGlobals->maxClients; i++ )
3218 {
3219 CBasePlayer *pPlayer = UTIL_PlayerByIndex( i );
3220 if ( pPlayer && !(pPlayer->GetFlags() & FL_NOTARGET) && pPlayer->FVisible( this ) )
3221 Wake();
3222 }
3223 }
3224
3225 // Should check for visible danger sounds
3226 if ( (GetSoundInterests() & SOUND_DANGER) && !(HasSpawnFlags(SF_NPC_WAIT_TILL_SEEN)) )
3227 {
3228 int iSound = CSoundEnt::ActiveList();
3229
3230 while ( iSound != SOUNDLIST_EMPTY )
3231 {
3232 CSound *pCurrentSound = CSoundEnt::SoundPointerForIndex( iSound );
3233 Assert( pCurrentSound );
3234
3235 if ( (pCurrentSound->SoundType() & SOUND_DANGER) &&
3236 GetSenses()->CanHearSound( pCurrentSound ) &&
3237 SoundIsVisible( pCurrentSound ))
3238 {
3239 Wake();
3240 break;
3241 }
3242
3243 iSound = pCurrentSound->NextSound();
3244 }
3245 }
3246 }
3247 }
3248 }
3249 else
3250 {
3251 // NPC is awake
3252 // Don't let an NPC sleep if they're running a script!
3253 if( !IsInAScript() && m_NPCState != NPC_STATE_SCRIPT )
3254 {
3255 if( HasSleepFlags(AI_SLEEP_FLAG_AUTO_PVS) )
3256 {
3257 if( !HasCondition(COND_IN_PVS) )
3258 {
3259 SetSleepState( AISS_WAITING_FOR_PVS );
3260 Sleep();
3261 }
3262 }
3263 if( HasSleepFlags(AI_SLEEP_FLAG_AUTO_PVS_AFTER_PVS) )
3264 {
3265 if( HasCondition(COND_IN_PVS) )
3266 {
3267 // OK, we're in the player's PVS. Now switch to regular old AUTO_PVS sleep rules.
3268 AddSleepFlags(AI_SLEEP_FLAG_AUTO_PVS);
3269 RemoveSleepFlags(AI_SLEEP_FLAG_AUTO_PVS_AFTER_PVS);
3270 }
3271 }
3272 }
3273 }
3274}
3275
3276//-----------------------------------------------------------------------------
3277
3278struct AIRebalanceInfo_t
3279{
3280 CAI_BaseNPC * pNPC;
3281 int iNextThinkTick;
3282 bool bInPVS;
3283 float dotPlayer;
3284 float distPlayer;
3285};
3286
3287int __cdecl ThinkRebalanceCompare( const AIRebalanceInfo_t *pLeft, const AIRebalanceInfo_t *pRight )
3288{
3289 int base = pLeft->iNextThinkTick - pRight->iNextThinkTick;
3290 if ( base != 0 )
3291 return base;
3292
3293 if ( !pLeft->bInPVS && !pRight->bInPVS )
3294 return 0;
3295
3296 if ( !pLeft->bInPVS )
3297 return 1;
3298
3299 if ( !pRight->bInPVS )
3300 return -1;
3301
3302 if ( pLeft->dotPlayer < 0 && pRight->dotPlayer < 0 )
3303 return 0;
3304
3305 if ( pLeft->dotPlayer < 0 )
3306 return 1;
3307
3308 if ( pRight->dotPlayer < 0 )
3309 return -1;
3310
3311 const float NEAR_PLAYER = 50*12;
3312
3313 if ( pLeft->distPlayer < NEAR_PLAYER && pRight->distPlayer >= NEAR_PLAYER )
3314 return -1;
3315
3316 if ( pRight->distPlayer < NEAR_PLAYER && pLeft->distPlayer >= NEAR_PLAYER )
3317 return 1;
3318
3319 if ( pLeft->dotPlayer > pRight->dotPlayer )
3320 return -1;
3321
3322 if ( pLeft->dotPlayer < pRight->dotPlayer )
3323 return 1;
3324
3325 return 0;
3326}
3327
3328inline bool CAI_BaseNPC::CanThinkRebalance()
3329{
3330 if ( m_pfnThink != (BASEPTR)&CAI_BaseNPC::CallNPCThink )
3331 {
3332 return false;
3333 }
3334
3335 if ( m_bInChoreo )
3336 {
3337 return false;
3338 }
3339
3340 if ( m_NPCState == NPC_STATE_DEAD )
3341 {
3342 return false;
3343 }
3344
3345 if ( GetSleepState() != AISS_AWAKE )
3346 {
3347 return false;
3348 }
3349
3350 if ( !m_bUsingStandardThinkTime /*&& m_iFrameBlocked == -1 */ )
3351 {
3352 return false;
3353 }
3354
3355 return true;
3356}
3357
3358void CAI_BaseNPC::RebalanceThinks()
3359{
3360 bool bDebugThinkTicks = ai_debug_think_ticks.GetBool();
3361 if ( bDebugThinkTicks )
3362 {
3363 static int iPrevTick;
3364 static int nThinksInTick;
3365 static int nRebalanceableThinksInTick;
3366
3367 if ( gpGlobals->tickcount != iPrevTick )
3368 {
3369 DevMsg( "NPC per tick is %d [%d] (tick %d, frame %d)\n", nRebalanceableThinksInTick, nThinksInTick, iPrevTick, gpGlobals->framecount );
3370 iPrevTick = gpGlobals->tickcount;
3371 nThinksInTick = 0;
3372 nRebalanceableThinksInTick = 0;
3373 }
3374 nThinksInTick++;
3375 if ( CanThinkRebalance() )
3376 nRebalanceableThinksInTick++;
3377 }
3378
3379 if ( ShouldRebalanceThinks() && gpGlobals->tickcount >= gm_iNextThinkRebalanceTick )
3380 {
3381 AI_PROFILE_SCOPE(AI_Think_Rebalance );
3382
3383 static CUtlVector<AIRebalanceInfo_t> rebalanceCandidates( 16, 64 );
3384 gm_iNextThinkRebalanceTick = gpGlobals->tickcount + TIME_TO_TICKS( random->RandomFloat( 3, 5) );
3385
3386 int i;
3387
3388 CBasePlayer *pPlayer = AI_GetSinglePlayer();
3389 Vector vPlayerForward;
3390 Vector vPlayerEyePosition;
3391
3392 vPlayerForward.Init();
3393 vPlayerEyePosition.Init();
3394
3395 if ( pPlayer )
3396 {
3397 pPlayer->EyePositionAndVectors( &vPlayerEyePosition, &vPlayerForward, NULL, NULL );
3398 }
3399
3400 int iTicksPer10Hz = TIME_TO_TICKS( .1 );
3401 int iMinTickRebalance = gpGlobals->tickcount - 1; // -1 needed for alternate ticks
3402 int iMaxTickRebalance = gpGlobals->tickcount + iTicksPer10Hz;
3403
3404 for ( i = 0; i < g_AI_Manager.NumAIs(); i++ )
3405 {
3406 CAI_BaseNPC *pCandidate = g_AI_Manager.AccessAIs()[i];
3407 if ( pCandidate->CanThinkRebalance() &&
3408 ( pCandidate->GetNextThinkTick() >= iMinTickRebalance &&
3409 pCandidate->GetNextThinkTick() < iMaxTickRebalance ) )
3410 {
3411 int iInfo = rebalanceCandidates.AddToTail();
3412
3413 rebalanceCandidates[iInfo].pNPC = pCandidate;
3414 rebalanceCandidates[iInfo].iNextThinkTick = pCandidate->GetNextThinkTick();
3415
3416 if ( pCandidate->IsFlaggedEfficient() )
3417 {
3418 rebalanceCandidates[iInfo].bInPVS = false;
3419 }
3420 else if ( pPlayer )
3421 {
3422 Vector vToCandidate = pCandidate->EyePosition() - vPlayerEyePosition;
3423 rebalanceCandidates[iInfo].bInPVS = ( UTIL_FindClientInPVS( pCandidate->edict() ) != NULL );
3424 rebalanceCandidates[iInfo].distPlayer = VectorNormalize( vToCandidate );
3425 rebalanceCandidates[iInfo].dotPlayer = vPlayerForward.Dot( vToCandidate );
3426 }
3427 else
3428 {
3429 rebalanceCandidates[iInfo].bInPVS = true;
3430 rebalanceCandidates[iInfo].dotPlayer = 1;
3431 rebalanceCandidates[iInfo].distPlayer = 0;
3432 }
3433 }
3434 else if ( bDebugThinkTicks )
3435 DevMsg( " Ignoring %d\n", pCandidate->GetNextThinkTick() );
3436 }
3437
3438 if ( rebalanceCandidates.Count() )
3439 {
3440 rebalanceCandidates.Sort( ThinkRebalanceCompare );
3441
3442 int iMaxThinkersPerTick = ceil( (float)( rebalanceCandidates.Count() + 1 ) / (float)iTicksPer10Hz ); // +1 to account for "this"
3443
3444 int iCurTickDistributing = min( gpGlobals->tickcount, rebalanceCandidates[0].iNextThinkTick );
3445 int iRemainingThinksToDistribute = iMaxThinkersPerTick - 1; // Start with one fewer first time because "this" is
3446 // always gets a slot in the current tick to avoid complications
3447 // in the calculation of "last think"
3448
3449 if ( bDebugThinkTicks )
3450 {
3451 DevMsg( "Rebalance %d!\n", rebalanceCandidates.Count() + 1 );
3452 DevMsg( " Distributing %d\n", iCurTickDistributing );
3453 }
3454
3455 for ( i = 0; i < rebalanceCandidates.Count(); i++ )
3456 {
3457 if ( iRemainingThinksToDistribute == 0 || rebalanceCandidates[i].iNextThinkTick > iCurTickDistributing )
3458 {
3459 if ( rebalanceCandidates[i].iNextThinkTick <= iCurTickDistributing )
3460 {
3461 iCurTickDistributing = iCurTickDistributing + 1;
3462 }
3463 else
3464 {
3465 iCurTickDistributing = rebalanceCandidates[i].iNextThinkTick;
3466 }
3467
3468 if ( bDebugThinkTicks )
3469 DevMsg( " Distributing %d\n", iCurTickDistributing );
3470
3471 iRemainingThinksToDistribute = iMaxThinkersPerTick;
3472 }
3473
3474 if ( rebalanceCandidates[i].pNPC->GetNextThinkTick() != iCurTickDistributing )
3475 {
3476 if ( bDebugThinkTicks )
3477 DevMsg( " Bumping %d to %d\n", rebalanceCandidates[i].pNPC->GetNextThinkTick(), iCurTickDistributing );
3478
3479 rebalanceCandidates[i].pNPC->SetNextThink( TICKS_TO_TIME( iCurTickDistributing ) );
3480 }
3481 else if ( bDebugThinkTicks )
3482 {
3483 DevMsg( " Leaving %d\n", rebalanceCandidates[i].pNPC->GetNextThinkTick() );
3484 }
3485
3486 iRemainingThinksToDistribute--;
3487 }
3488 }
3489
3490 rebalanceCandidates.RemoveAll();
3491
3492 if ( bDebugThinkTicks )
3493 {
3494 DevMsg( "New distribution is:\n");
3495 for ( i = 0; i < g_AI_Manager.NumAIs(); i++ )
3496 {
3497 DevMsg( " %d\n", g_AI_Manager.AccessAIs()[i]->GetNextThinkTick() );
3498 }
3499 }
3500
3501 Assert( GetNextThinkTick() == TICK_NEVER_THINK ); // never change this objects tick
3502 }
3503}
3504
3505static float g_NpcTimeThisFrame;
3506static float g_StartTimeCurThink;
3507
3508bool CAI_BaseNPC::PreNPCThink()
3509{
3510 static int iPrevFrame = -1;
3511 static float frameTimeLimit = FLT_MAX;
3512 static const ConVar *pHostTimescale;
3513
3514 if ( frameTimeLimit == FLT_MAX )
3515 {
3516 pHostTimescale = cvar->FindVar( "host_timescale" );
3517 }
3518
3519 bool bUseThinkLimits = ( !m_bInChoreo && ShouldUseFrameThinkLimits() );
3520
3521#ifdef _DEBUG
3522 const float NPC_THINK_LIMIT = 30.0 / 1000.0;
3523#else
3524 const float NPC_THINK_LIMIT = ( !IsXbox() ) ? (10.0 / 1000.0) : (12.5 / 1000.0);
3525#endif
3526
3527 g_StartTimeCurThink = 0;
3528
3529 if ( bUseThinkLimits && VCRGetMode() == VCR_Disabled )
3530 {
3531 if ( m_iFrameBlocked == gpGlobals->framecount )
3532 {
3533 DbgFrameLimitMsg( "Stalled %d (%d)\n", this, gpGlobals->framecount );
3534 SetNextThink( gpGlobals->curtime );
3535 return false;
3536 }
3537 else if ( gpGlobals->framecount != iPrevFrame )
3538 {
3539 DbgFrameLimitMsg( "--- FRAME: %d (%d)\n", this, gpGlobals->framecount );
3540 float timescale = pHostTimescale->GetFloat();
3541 if ( timescale < 1 )
3542 timescale = 1;
3543
3544 iPrevFrame = gpGlobals->framecount;
3545 frameTimeLimit = NPC_THINK_LIMIT * timescale;
3546 g_NpcTimeThisFrame = 0;
3547 }
3548 else
3549 {
3550 if ( g_NpcTimeThisFrame > NPC_THINK_LIMIT )
3551 {
3552 float timeSinceLastRealThink = gpGlobals->curtime - m_flLastRealThinkTime;
3553 // Don't bump anyone more that a quarter second
3554 if ( timeSinceLastRealThink <= .25 )
3555 {
3556 DbgFrameLimitMsg( "Bumped %d (%d)\n", this, gpGlobals->framecount );
3557 m_iFrameBlocked = gpGlobals->framecount;
3558 SetNextThink( gpGlobals->curtime );
3559 return false;
3560 }
3561 else
3562 {
3563 DbgFrameLimitMsg( "(Over %d )\n", this );
3564 }
3565 }
3566 }
3567
3568 DbgFrameLimitMsg( "Running %d (%d)\n", this, gpGlobals->framecount );
3569 g_StartTimeCurThink = engine->Time();
3570
3571 m_iFrameBlocked = -1;
3572 m_nLastThinkTick = TIME_TO_TICKS( m_flLastRealThinkTime );
3573 }
3574
3575 return true;
3576}
3577
3578void CAI_BaseNPC::PostNPCThink( void )
3579{
3580 if ( g_StartTimeCurThink != 0.0 && VCRGetMode() == VCR_Disabled )
3581 {
3582 g_NpcTimeThisFrame += engine->Time() - g_StartTimeCurThink;
3583 }
3584}
3585
3586void CAI_BaseNPC::CallNPCThink( void )
3587{
3588 RebalanceThinks();
3589
3590 //---------------------------------
3591
3592 m_bUsingStandardThinkTime = false;
3593
3594 //---------------------------------
3595
3596 if ( !PreNPCThink() )
3597 {
3598 return;
3599 }
3600
3601 // reduce cache queries by locking model in memory
3602 MDLCACHE_CRITICAL_SECTION();
3603
3604 this->NPCThink();
3605
3606 m_flLastRealThinkTime = gpGlobals->curtime;
3607
3608 PostNPCThink();
3609}
3610
3611bool NPC_CheckBrushExclude( CBaseEntity *pEntity, CBaseEntity *pBrush )
3612{
3613 CAI_BaseNPC *pNPC = pEntity->MyNPCPointer();
3614
3615 if ( pNPC )
3616 {
3617 return pNPC->GetMoveProbe()->ShouldBrushBeIgnored( pBrush );
3618 }
3619
3620 return false;
3621}
3622
3623class CTraceFilterPlayerAvoidance : public CTraceFilterEntitiesOnly
3624{
3625public:
3626 CTraceFilterPlayerAvoidance( const CBaseEntity *pEntity ) { m_pIgnore = pEntity; }
3627
3628 virtual bool ShouldHitEntity( IHandleEntity *pHandleEntity, int contentsMask )
3629 {
3630 CBaseEntity *pEntity = EntityFromEntityHandle( pHandleEntity );
3631
3632 if ( m_pIgnore == pEntity )
3633 return false;
3634
3635 if ( pEntity->IsPlayer() )
3636 return true;
3637
3638 return false;
3639 }
3640private:
3641
3642 const CBaseEntity *m_pIgnore;
3643};
3644
3645void CAI_BaseNPC::GetPlayerAvoidBounds( Vector *pMins, Vector *pMaxs )
3646{
3647 *pMins = WorldAlignMins();
3648 *pMaxs = WorldAlignMaxs();
3649}
3650
3651ConVar ai_debug_avoidancebounds( "ai_debug_avoidancebounds", "0" );
3652
3653void CAI_BaseNPC::SetPlayerAvoidState( void )
3654{
3655 bool bShouldPlayerAvoid = false;
3656 Vector vNothing;
3657
3658 GetSequenceLinearMotion( GetSequence(), &vNothing );
3659 bool bIsMoving = ( IsMoving() || ( vNothing != vec3_origin ) );
3660
3661 //If we are coming out of a script, check if we are stuck inside the player.
3662 if ( m_bPerformAvoidance || ( ShouldPlayerAvoid() && bIsMoving ) )
3663 {
3664 trace_t trace;
3665 Vector vMins, vMaxs;
3666
3667 GetPlayerAvoidBounds( &vMins, &vMaxs );
3668
3669 CBasePlayer *pLocalPlayer = AI_GetSinglePlayer();
3670
3671 if ( pLocalPlayer )
3672 {
3673 bShouldPlayerAvoid = IsBoxIntersectingBox( GetAbsOrigin() + vMins, GetAbsOrigin() + vMaxs,
3674 pLocalPlayer->GetAbsOrigin() + pLocalPlayer->WorldAlignMins(), pLocalPlayer->GetAbsOrigin() + pLocalPlayer->WorldAlignMaxs() );
3675 }
3676
3677 if ( ai_debug_avoidancebounds.GetBool() )
3678 {
3679 int iRed = ( bShouldPlayerAvoid == true ) ? 255 : 0;
3680
3681 NDebugOverlay::Box( GetAbsOrigin(), vMins, vMaxs, iRed, 0, 255, 64, 0.1 );
3682 }
3683 }
3684
3685 m_bPlayerAvoidState = ShouldPlayerAvoid();
3686 m_bPerformAvoidance = bShouldPlayerAvoid;
3687
3688 if ( GetCollisionGroup() == COLLISION_GROUP_NPC || GetCollisionGroup() == COLLISION_GROUP_NPC_ACTOR )
3689 {
3690 if ( m_bPerformAvoidance == true )
3691 {
3692 SetCollisionGroup( COLLISION_GROUP_NPC_ACTOR );
3693 }
3694 else
3695 {
3696 SetCollisionGroup( COLLISION_GROUP_NPC );
3697 }
3698 }
3699}
3700
3701//-----------------------------------------------------------------------------
3702// Purpose: Enables player avoidance when the player's vphysics shadow penetrates our vphysics shadow. This can
3703// happen when the player is hit by a combine ball, which pushes them into an adjacent npc. Subclasses should
3704// override this if it causes problems, but in general this will solve cases of the player getting stuck in
3705// the NPC from being pushed.
3706//-----------------------------------------------------------------------------
3707void CAI_BaseNPC::PlayerPenetratingVPhysics( void )
3708{
3709 m_bPerformAvoidance = true;
3710}
3711
3712//-----------------------------------------------------------------------------
3713
3714bool CAI_BaseNPC::CheckPVSCondition()
3715{
3716 bool bInPVS = ( UTIL_FindClientInPVS( edict() ) != NULL ) || (UTIL_ClientPVSIsExpanded() && UTIL_FindClientInVisibilityPVS( edict() ));
3717
3718 if ( bInPVS )
3719 SetCondition( COND_IN_PVS );
3720 else
3721 ClearCondition( COND_IN_PVS );
3722
3723 return bInPVS;
3724}
3725
3726
3727//-----------------------------------------------------------------------------
3728// NPC Think - calls out to core AI functions and handles this
3729// npc's specific animation events
3730//
3731
3732void CAI_BaseNPC::NPCThink( void )
3733{
3734 if ( m_bCheckContacts )
3735 {
3736 CheckPhysicsContacts();
3737 }
3738
3739 Assert( !(m_NPCState == NPC_STATE_DEAD && m_lifeState == LIFE_ALIVE) );
3740
3741 //---------------------------------
3742
3743 SetNextThink( TICK_NEVER_THINK );
3744
3745 //---------------------------------
3746
3747 bool bInPVS = CheckPVSCondition();
3748
3749 //---------------------------------
3750
3751 UpdateSleepState( bInPVS );
3752
3753 //---------------------------------
3754 bool bRanDecision = false;
3755
3756 if ( GetEfficiency() < AIE_DORMANT && GetSleepState() == AISS_AWAKE )
3757 {
3758 static CFastTimer timer;
3759 float thinkLimit = ai_show_think_tolerance.GetFloat();
3760
3761 if ( thinkLimit > 0 )
3762 timer.Start();
3763
3764 if ( g_pAINetworkManager && g_pAINetworkManager->IsInitialized() )
3765 {
3766 VPROF_BUDGET( "NPCs", VPROF_BUDGETGROUP_NPCS );
3767
3768 AI_PROFILE_SCOPE_BEGIN_( GetClassScheduleIdSpace()->GetClassName() ); // need to use a string stable from map load to map load
3769
3770 SetPlayerAvoidState();
3771
3772 if ( PreThink() )
3773 {
3774 if ( m_flNextDecisionTime <= gpGlobals->curtime )
3775 {
3776 bRanDecision = true;
3777 m_ScheduleState.bTaskRanAutomovement = false;
3778 m_ScheduleState.bTaskUpdatedYaw = false;
3779 RunAI();
3780 }
3781 else
3782 {
3783 if ( m_ScheduleState.bTaskRanAutomovement )
3784 AutoMovement();
3785 if ( m_ScheduleState.bTaskUpdatedYaw )
3786 GetMotor()->UpdateYaw();
3787 }
3788
3789 PostRun();
3790
3791 PerformMovement();
3792
3793 m_bIsMoving = IsMoving();
3794
3795 PostMovement();
3796
3797 SetSimulationTime( gpGlobals->curtime );
3798 }
3799 else
3800 m_flTimeLastMovement = FLT_MAX;
3801
3802 AI_PROFILE_SCOPE_END();
3803 }
3804
3805 if ( thinkLimit > 0 )
3806 {
3807 timer.End();
3808
3809 float thinkTime = g_AIRunTimer.GetDuration().GetMillisecondsF();
3810
3811 if ( thinkTime > thinkLimit )
3812 {
3813 int color = (int)RemapVal( thinkTime, thinkLimit, thinkLimit * 3, 96.0, 255.0 );
3814 if ( color > 255 )
3815 color = 255;
3816 else if ( color < 96 )
3817 color = 96;
3818
3819 Vector right;
3820 Vector vecPoint;
3821
3822 vecPoint = EyePosition() + Vector( 0, 0, 12 );
3823 GetVectors( NULL, &right, NULL );
3824 NDebugOverlay::Line( vecPoint, vecPoint + Vector( 0, 0, 64 ), color, 0, 0, false , 1.0 );
3825 NDebugOverlay::Line( vecPoint, vecPoint + Vector( 0, 0, 16 ) + right * 16, color, 0, 0, false , 1.0 );
3826 NDebugOverlay::Line( vecPoint, vecPoint + Vector( 0, 0, 16 ) - right * 16, color, 0, 0, false , 1.0 );
3827 }
3828 }
3829 }
3830
3831 m_bUsingStandardThinkTime = ( GetNextThinkTick() == TICK_NEVER_THINK );
3832
3833 UpdateEfficiency( bInPVS );
3834
3835 if ( m_bUsingStandardThinkTime )
3836 {
3837 static const char *ppszEfficiencies[] =
3838 {
3839 "AIE_NORMAL",
3840 "AIE_EFFICIENT",
3841 "AIE_VERY_EFFICIENT",
3842 "AIE_SUPER_EFFICIENT",
3843 "AIE_DORMANT",
3844 };
3845
3846 static const char *ppszMoveEfficiencies[] =
3847 {
3848 "AIME_NORMAL",
3849 "AIME_EFFICIENT",
3850 };
3851
3852 if ( ai_debug_efficiency.GetBool() )
3853 DevMsg( this, "Eff: %s, Move: %s\n", ppszEfficiencies[GetEfficiency()], ppszMoveEfficiencies[GetMoveEfficiency()] );
3854
3855 static float g_DecisionIntervals[] =
3856 {
3857 .1, // AIE_NORMAL
3858 .2, // AIE_EFFICIENT
3859 .4, // AIE_VERY_EFFICIENT
3860 .6, // AIE_SUPER_EFFICIENT
3861 };
3862
3863 if ( bRanDecision )
3864 {
3865 m_flNextDecisionTime = gpGlobals->curtime + g_DecisionIntervals[GetEfficiency()];
3866 }
3867
3868 if ( GetMoveEfficiency() == AIME_NORMAL || GetEfficiency() == AIE_NORMAL )
3869 {
3870 SetNextThink( gpGlobals->curtime + .1 );
3871 }
3872 else
3873 {
3874 SetNextThink( gpGlobals->curtime + .2 );
3875 }
3876 }
3877 else
3878 {
3879 m_flNextDecisionTime = 0;
3880 }
3881}
3882
3883//=========================================================
3884// CAI_BaseNPC - USE - will make a npc angry at whomever
3885// activated it.
3886//=========================================================
3887void CAI_BaseNPC::NPCUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
3888{
3889 return;
3890
3891 // Can't +USE NPCs running scripts
3892 if ( GetState() == NPC_STATE_SCRIPT )
3893 return;
3894
3895 if ( IsInAScript() )
3896 return;
3897
3898 SetIdealState( NPC_STATE_ALERT );
3899}
3900
3901//-----------------------------------------------------------------------------
3902// Purpose: Virtual function that allows us to have any npc ignore a set of
3903// shared conditions.
3904//
3905//-----------------------------------------------------------------------------
3906void CAI_BaseNPC::RemoveIgnoredConditions ( void )
3907{
3908 m_ConditionsPreIgnore = m_Conditions;
3909 m_Conditions.And( m_InverseIgnoreConditions, &m_Conditions );
3910
3911 if ( m_NPCState == NPC_STATE_SCRIPT && m_hCine )
3912 m_hCine->RemoveIgnoredConditions();
3913}
3914
3915//=========================================================
3916// RangeAttack1Conditions
3917//=========================================================
3918int CAI_BaseNPC::RangeAttack1Conditions ( float flDot, float flDist )
3919{
3920 if ( flDist < 64)
3921 {
3922 return COND_TOO_CLOSE_TO_ATTACK;
3923 }
3924 else if (flDist > 784)
3925 {
3926 return COND_TOO_FAR_TO_ATTACK;
3927 }
3928 else if (flDot < 0.5)
3929 {
3930 return COND_NOT_FACING_ATTACK;
3931 }
3932
3933 return COND_CAN_RANGE_ATTACK1;
3934}
3935
3936//=========================================================
3937// RangeAttack2Conditions
3938//=========================================================
3939int CAI_BaseNPC::RangeAttack2Conditions ( float flDot, float flDist )
3940{
3941 if ( flDist < 64)
3942 {
3943 return COND_TOO_CLOSE_TO_ATTACK;
3944 }
3945 else if (flDist > 512)
3946 {
3947 return COND_TOO_FAR_TO_ATTACK;
3948 }
3949 else if (flDot < 0.5)
3950 {
3951 return COND_NOT_FACING_ATTACK;
3952 }
3953
3954 return COND_CAN_RANGE_ATTACK2;
3955}
3956
3957//=========================================================
3958// MeleeAttack1Conditions
3959//=========================================================
3960int CAI_BaseNPC::MeleeAttack1Conditions ( float flDot, float flDist )
3961{
3962 if ( flDist > 64)
3963 {
3964 return COND_TOO_FAR_TO_ATTACK;
3965 }
3966 else if (flDot < 0.7)
3967 {
3968 return 0;
3969 }
3970 else if (GetEnemy() == NULL)
3971 {
3972 return 0;
3973 }
3974
3975 // Decent fix to keep folks from kicking/punching hornets and snarks is to check the onground flag(sjb)
3976 if ( GetEnemy()->GetFlags() & FL_ONGROUND )
3977 {
3978 return COND_CAN_MELEE_ATTACK1;
3979 }
3980 return 0;
3981}
3982
3983//=========================================================
3984// MeleeAttack2Conditions
3985//=========================================================
3986int CAI_BaseNPC::MeleeAttack2Conditions ( float flDot, float flDist )
3987{
3988 if ( flDist > 64)
3989 {
3990 return COND_TOO_FAR_TO_ATTACK;
3991 }
3992 else if (flDot < 0.7)
3993 {
3994 return 0;
3995 }
3996 return COND_CAN_MELEE_ATTACK2;
3997}
3998
3999// Get capability mask
4000int CAI_BaseNPC::CapabilitiesGet( void ) const
4001{
4002 int capability = m_afCapability;
4003 if ( GetActiveWeapon() )
4004 {
4005 capability |= GetActiveWeapon()->CapabilitiesGet();
4006 }
4007 return capability;
4008}
4009
4010// Set capability mask
4011int CAI_BaseNPC::CapabilitiesAdd( int capability )
4012{
4013 m_afCapability |= capability;
4014
4015 return m_afCapability;
4016}
4017
4018// Set capability mask
4019int CAI_BaseNPC::CapabilitiesRemove( int capability )
4020{
4021 m_afCapability &= ~capability;
4022
4023 return m_afCapability;
4024}
4025
4026// Clear capability mask
4027void CAI_BaseNPC::CapabilitiesClear( void )
4028{
4029 m_afCapability = 0;
4030}
4031
4032
4033//=========================================================
4034// ClearAttacks - clear out all attack conditions
4035//=========================================================
4036void CAI_BaseNPC::ClearAttackConditions( )
4037{
4038 // Clear all attack conditions
4039 ClearCondition( COND_CAN_RANGE_ATTACK1 );
4040 ClearCondition( COND_CAN_RANGE_ATTACK2 );
4041 ClearCondition( COND_CAN_MELEE_ATTACK1 );
4042 ClearCondition( COND_CAN_MELEE_ATTACK2 );
4043 ClearCondition( COND_WEAPON_HAS_LOS );
4044 ClearCondition( COND_WEAPON_BLOCKED_BY_FRIEND );
4045 ClearCondition( COND_WEAPON_PLAYER_IN_SPREAD ); // Player in shooting direction
4046 ClearCondition( COND_WEAPON_PLAYER_NEAR_TARGET ); // Player near shooting position
4047 ClearCondition( COND_WEAPON_SIGHT_OCCLUDED );
4048}
4049
4050//=========================================================
4051// GatherAttackConditions - sets all of the bits for attacks that the
4052// npc is capable of carrying out on the passed entity.
4053//=========================================================
4054
4055void CAI_BaseNPC::GatherAttackConditions( CBaseEntity *pTarget, float flDist )
4056{
4057 AI_PROFILE_SCOPE(CAI_BaseNPC_GatherAttackConditions);
4058
4059 Vector vecLOS = ( pTarget->GetAbsOrigin() - GetAbsOrigin() );
4060 vecLOS.z = 0;
4061 VectorNormalize( vecLOS );
4062
4063 Vector vBodyDir = BodyDirection2D( );
4064 float flDot = DotProduct( vecLOS, vBodyDir );
4065
4066 // we know the enemy is in front now. We'll find which attacks the npc is capable of by
4067 // checking for corresponding Activities in the model file, then do the simple checks to validate
4068 // those attack types.
4069
4070 int capability;
4071 Vector targetPos;
4072 bool bWeaponHasLOS;
4073 int condition;
4074
4075 capability = CapabilitiesGet();
4076
4077 // Clear all attack conditions
4078 AI_PROFILE_SCOPE_BEGIN( CAI_BaseNPC_GatherAttackConditions_PrimaryWeaponLOS );
4079
4080 // @TODO (toml 06-15-03): There are simple cases where
4081 // the upper torso of the enemy is visible, and the NPC is at an angle below
4082 // them, but the above test fails because BodyTarget returns the center of
4083 // the target. This needs some better handling/closer evaluation
4084
4085 // Try the eyes first, as likely to succeed (because can see or else wouldn't be here) thus reducing
4086 // the odds of the need for a second trace
4087 ClearAttackConditions();
4088 targetPos = pTarget->EyePosition();
4089 bWeaponHasLOS = WeaponLOSCondition( GetAbsOrigin(), targetPos, true );
4090
4091 AI_PROFILE_SCOPE_END();
4092
4093 if ( !bWeaponHasLOS )
4094 {
4095 AI_PROFILE_SCOPE( CAI_BaseNPC_GatherAttackConditions_SecondaryWeaponLOS );
4096 ClearAttackConditions( );
4097 targetPos = pTarget->BodyTarget( GetAbsOrigin() );
4098 bWeaponHasLOS = WeaponLOSCondition( GetAbsOrigin(), targetPos, true );
4099 }
4100 else
4101 {
4102 SetCondition( COND_WEAPON_HAS_LOS );
4103 }
4104
4105 bool bWeaponIsReady = (GetActiveWeapon() && !IsWeaponStateChanging());
4106
4107 // FIXME: move this out of here
4108 if ( (capability & bits_CAP_WEAPON_RANGE_ATTACK1) && bWeaponIsReady )
4109 {
4110 AI_PROFILE_SCOPE( CAI_BaseNPC_GatherAttackConditions_WeaponRangeAttack1Condition );
4111
4112 condition = GetActiveWeapon()->WeaponRangeAttack1Condition(flDot, flDist);
4113
4114 if ( condition == COND_NOT_FACING_ATTACK && FInAimCone( targetPos ) )
4115 DevMsg( "Warning: COND_NOT_FACING_ATTACK set but FInAimCone is true\n" );
4116
4117 if (condition != COND_CAN_RANGE_ATTACK1 || bWeaponHasLOS)
4118 {
4119 SetCondition(condition);
4120 }
4121 }
4122 else if ( capability & bits_CAP_INNATE_RANGE_ATTACK1 )
4123 {
4124 AI_PROFILE_SCOPE( CAI_BaseNPC_GatherAttackConditions_RangeAttack1Condition );
4125
4126 condition = RangeAttack1Conditions( flDot, flDist );
4127 if (condition != COND_CAN_RANGE_ATTACK1 || bWeaponHasLOS)
4128 {
4129 SetCondition(condition);
4130 }
4131 }
4132
4133 if ( (capability & bits_CAP_WEAPON_RANGE_ATTACK2) && bWeaponIsReady && ( GetActiveWeapon()->CapabilitiesGet() & bits_CAP_WEAPON_RANGE_ATTACK2 ) )
4134 {
4135 AI_PROFILE_SCOPE( CAI_BaseNPC_GatherAttackConditions_WeaponRangeAttack2Condition );
4136
4137 condition = GetActiveWeapon()->WeaponRangeAttack2Condition(flDot, flDist);
4138 if (condition != COND_CAN_RANGE_ATTACK2 || bWeaponHasLOS)
4139 {
4140 SetCondition(condition);
4141 }
4142 }
4143 else if ( capability & bits_CAP_INNATE_RANGE_ATTACK2 )
4144 {
4145 AI_PROFILE_SCOPE( CAI_BaseNPC_GatherAttackConditions_RangeAttack2Condition );
4146
4147 condition = RangeAttack2Conditions( flDot, flDist );
4148 if (condition != COND_CAN_RANGE_ATTACK2 || bWeaponHasLOS)
4149 {
4150 SetCondition(condition);
4151 }
4152 }
4153
4154 if ( (capability & bits_CAP_WEAPON_MELEE_ATTACK1) && bWeaponIsReady)
4155 {
4156 AI_PROFILE_SCOPE( CAI_BaseNPC_GatherAttackConditions_WeaponMeleeAttack1Condition );
4157 SetCondition(GetActiveWeapon()->WeaponMeleeAttack1Condition(flDot, flDist));
4158 }
4159 else if ( capability & bits_CAP_INNATE_MELEE_ATTACK1 )
4160 {
4161 AI_PROFILE_SCOPE( CAI_BaseNPC_GatherAttackConditions_MeleeAttack1Condition );
4162 SetCondition(MeleeAttack1Conditions ( flDot, flDist ));
4163 }
4164
4165 if ( (capability & bits_CAP_WEAPON_MELEE_ATTACK2) && bWeaponIsReady)
4166 {
4167 AI_PROFILE_SCOPE( CAI_BaseNPC_GatherAttackConditions_WeaponMeleeAttack2Condition );
4168 SetCondition(GetActiveWeapon()->WeaponMeleeAttack2Condition(flDot, flDist));
4169 }
4170 else if ( capability & bits_CAP_INNATE_MELEE_ATTACK2 )
4171 {
4172 AI_PROFILE_SCOPE( CAI_BaseNPC_GatherAttackConditions_MeleeAttack2Condition );
4173 SetCondition(MeleeAttack2Conditions ( flDot, flDist ));
4174 }
4175
4176 // -----------------------------------------------------------------
4177 // If any attacks are possible clear attack specific bits
4178 // -----------------------------------------------------------------
4179 if (HasCondition(COND_CAN_RANGE_ATTACK2) ||
4180 HasCondition(COND_CAN_RANGE_ATTACK1) ||
4181 HasCondition(COND_CAN_MELEE_ATTACK2) ||
4182 HasCondition(COND_CAN_MELEE_ATTACK1) )
4183 {
4184 ClearCondition(COND_TOO_CLOSE_TO_ATTACK);
4185 ClearCondition(COND_TOO_FAR_TO_ATTACK);
4186 ClearCondition(COND_WEAPON_BLOCKED_BY_FRIEND);
4187 }
4188}
4189
4190
4191//=========================================================
4192// SetState
4193//=========================================================
4194void CAI_BaseNPC::SetState( NPC_STATE State )
4195{
4196 NPC_STATE OldState;
4197
4198 OldState = m_NPCState;
4199
4200 if ( State != m_NPCState )
4201 {
4202 m_flLastStateChangeTime = gpGlobals->curtime;
4203 }
4204
4205 switch( State )
4206 {
4207 // Drop enemy pointers when going to idle
4208 case NPC_STATE_IDLE:
4209
4210 if ( GetEnemy() != NULL )
4211 {
4212 SetEnemy( NULL ); // not allowed to have an enemy anymore.
4213 DevMsg( 2, "Stripped\n" );
4214 }
4215 break;
4216 }
4217
4218 bool fNotifyChange = false;
4219
4220 if( m_NPCState != State )
4221 {
4222 // Don't notify if we're changing to a state we're already in!
4223 fNotifyChange = true;
4224 }
4225
4226 m_NPCState = State;
4227 SetIdealState( State );
4228
4229 // Notify the character that its state has changed.
4230 if( fNotifyChange )
4231 {
4232 OnStateChange( OldState, m_NPCState );
4233 }
4234}
4235
4236bool CAI_BaseNPC::WokeThisTick() const
4237{
4238 return m_nWakeTick == gpGlobals->tickcount ? true : false;
4239}
4240
4241//-----------------------------------------------------------------------------
4242//-----------------------------------------------------------------------------
4243void CAI_BaseNPC::Wake( bool bFireOutput )
4244{
4245 if ( GetSleepState() != AISS_AWAKE )
4246 {
4247 m_nWakeTick = gpGlobals->tickcount;
4248 SetSleepState( AISS_AWAKE );
4249 RemoveEffects( EF_NODRAW );
4250 if ( bFireOutput )
4251 m_OnWake.FireOutput( this, this );
4252
4253 if ( m_bWakeSquad && GetSquad() )
4254 {
4255 AISquadIter_t iter;
4256 for ( CAI_BaseNPC *pSquadMember = GetSquad()->GetFirstMember( &iter ); pSquadMember; pSquadMember = GetSquad()->GetNextMember( &iter ) )
4257 {
4258 if ( pSquadMember->IsAlive() && pSquadMember != this )
4259 {
4260 pSquadMember->m_bWakeSquad = false;
4261 pSquadMember->Wake();
4262 }
4263 }
4264
4265 }
4266 }
4267}
4268
4269//-----------------------------------------------------------------------------
4270//-----------------------------------------------------------------------------
4271void CAI_BaseNPC::Sleep()
4272{
4273 // Don't render.
4274 AddEffects( EF_NODRAW );
4275
4276 if( GetState() == NPC_STATE_SCRIPT )
4277 {
4278 Warning( "%s put to sleep while in Scripted state!\n");
4279 }
4280
4281 VacateStrategySlot();
4282
4283 // Slam my schedule.
4284 SetSchedule( SCHED_SLEEP );
4285
4286 m_OnSleep.FireOutput( this, this );
4287}
4288
4289//-----------------------------------------------------------------------------
4290// Sets all sensing-related conditions
4291//-----------------------------------------------------------------------------
4292void CAI_BaseNPC::PerformSensing( void )
4293{
4294 GetSenses()->PerformSensing();
4295}
4296
4297
4298//-----------------------------------------------------------------------------
4299
4300void CAI_BaseNPC::ClearSenseConditions( void )
4301{
4302 static int conditionsToClear[] =
4303 {
4304 COND_SEE_HATE,
4305 COND_SEE_DISLIKE,
4306 COND_SEE_ENEMY,
4307 COND_SEE_FEAR,
4308 COND_SEE_NEMESIS,
4309 COND_SEE_PLAYER,
4310 COND_HEAR_DANGER,
4311 COND_HEAR_COMBAT,
4312 COND_HEAR_WORLD,
4313 COND_HEAR_PLAYER,
4314 COND_HEAR_THUMPER,
4315 COND_HEAR_BUGBAIT,
4316 COND_HEAR_PHYSICS_DANGER,
4317 COND_HEAR_MOVE_AWAY,
4318 COND_SMELL,
4319 };
4320
4321 ClearConditions( conditionsToClear, ARRAYSIZE( conditionsToClear ) );
4322}
4323
4324//-----------------------------------------------------------------------------
4325
4326void CAI_BaseNPC::CheckOnGround( void )
4327{
4328 bool bScriptedWait = ( IsCurSchedule( SCHED_WAIT_FOR_SCRIPT ) || ( m_hCine && m_scriptState == CAI_BaseNPC::SCRIPT_WAIT ) );
4329 if ( !bScriptedWait && !HasCondition( COND_FLOATING_OFF_GROUND ) )
4330 {
4331 // parented objects are never floating
4332 if (GetMoveParent() != NULL)
4333 return;
4334
4335 if ( ( GetNavType() == NAV_GROUND ) && ( GetMoveType() != MOVETYPE_VPHYSICS ) && ( GetMoveType() != MOVETYPE_NONE ) )
4336 {
4337 if ( m_CheckOnGroundTimer.Expired() )
4338 {
4339 m_CheckOnGroundTimer.Set(0.5);
4340
4341 // check a shrunk box centered around the foot
4342 Vector maxs = WorldAlignMaxs();
4343 Vector mins = WorldAlignMins();
4344
4345 if ( mins != maxs ) // some NPCs have no hull, so mins == maxs == vec3_origin
4346 {
4347 maxs -= Vector( 0.0f, 0.0f, 0.2f );
4348
4349 Vector vecStart = GetAbsOrigin() + Vector( 0, 0, .1f );
4350 Vector vecDown = GetAbsOrigin();
4351 vecDown.z -= 4.0;
4352
4353 trace_t trace;
4354 m_pMoveProbe->TraceHull( vecStart, vecDown, mins, maxs, MASK_NPCSOLID, &trace );
4355
4356 if (trace.fraction == 1.0)
4357 {
4358 SetCondition( COND_FLOATING_OFF_GROUND );
4359 SetGroundEntity( NULL );
4360 }
4361 else
4362 {
4363 if ( trace.startsolid && trace.m_pEnt->GetMoveType() == MOVETYPE_VPHYSICS &&
4364 trace.m_pEnt->VPhysicsGetObject() && trace.m_pEnt->VPhysicsGetObject()->GetMass() < VPHYSICS_LARGE_OBJECT_MASS )
4365 {
4366 // stuck inside a small physics object?
4367 m_CheckOnGroundTimer.Set(0.1f);
4368 NPCPhysics_CreateSolver( this, trace.m_pEnt, true, 0.25f );
4369 if ( VPhysicsGetObject() )
4370 {
4371 VPhysicsGetObject()->RecheckContactPoints();
4372 }
4373 }
4374 // Check to see if someone changed the ground on us...
4375 if ( trace.m_pEnt && trace.m_pEnt != GetGroundEntity() )
4376 {
4377 SetGroundEntity( trace.m_pEnt );
4378 }
4379 }
4380 }
4381 }
4382 }
4383 }
4384 else
4385 {
4386 // parented objects are never floating
4387 if ( bScriptedWait || GetMoveParent() != NULL || (GetFlags() & FL_ONGROUND ) || GetNavType() != NAV_GROUND )
4388 {
4389 ClearCondition( COND_FLOATING_OFF_GROUND );
4390 }
4391 }
4392
4393}
4394
4395void CAI_BaseNPC::NotifyPushMove()
4396{
4397 // don't recheck ground when I'm being push-moved
4398 m_CheckOnGroundTimer.Set( 0.5f );
4399}
4400
4401//-----------------------------------------------------------------------------
4402// Purpose:
4403//-----------------------------------------------------------------------------
4404bool CAI_BaseNPC::CanFlinch( void )
4405{
4406 if ( IsCurSchedule( SCHED_BIG_FLINCH ) )
4407 return false;
4408
4409 if ( m_flNextFlinchTime >= gpGlobals->curtime )
4410 return false;
4411
4412 return true;
4413}
4414
4415//-----------------------------------------------------------------------------
4416// Purpose:
4417//-----------------------------------------------------------------------------
4418void CAI_BaseNPC::CheckFlinches( void )
4419{
4420 // If we're currently flinching, don't allow gesture flinches to be overlaid
4421 if ( IsCurSchedule( SCHED_BIG_FLINCH ) )
4422 {
4423 ClearCondition( COND_LIGHT_DAMAGE );
4424 ClearCondition( COND_HEAVY_DAMAGE );
4425 }
4426
4427 // If we've taken heavy damage, try to do a full schedule flinch
4428 if ( HasCondition(COND_HEAVY_DAMAGE) )
4429 {
4430 // If we've already flinched recently, gesture flinch instead.
4431 if ( HasMemory(bits_MEMORY_FLINCHED) )
4432 {
4433 // Clear the heavy damage condition so we don't interrupt schedules
4434 // when we play a gesture flinch because we recently did a full flinch.
4435 // Prevents the player from stun-locking enemies, even though they don't full flinch.
4436 ClearCondition( COND_HEAVY_DAMAGE );
4437 }
4438 else if ( !HasInterruptCondition(COND_HEAVY_DAMAGE) )
4439 {
4440 // If we have taken heavy damage, but the current schedule doesn't
4441 // break on that, resort to just playing a gesture flinch.
4442 PlayFlinchGesture();
4443 }
4444
4445 // Otherwise, do nothing. The heavy damage will interrupt our schedule and we'll flinch.
4446 }
4447 else if ( HasCondition( COND_LIGHT_DAMAGE ) )
4448 {
4449 // If we have taken light damage play gesture flinches
4450 PlayFlinchGesture();
4451 }
4452
4453 // If it's been a while since we did a full flinch, forget that we flinched so we'll flinch fully again
4454 if ( HasMemory(bits_MEMORY_FLINCHED) && gpGlobals->curtime > m_flNextFlinchTime )
4455 {
4456 Forget(bits_MEMORY_FLINCHED);
4457 }
4458}
4459
4460//-----------------------------------------------------------------------------
4461
4462void CAI_BaseNPC::GatherConditions( void )
4463{
4464 m_bConditionsGathered = true;
4465 g_AIConditionsTimer.Start();
4466
4467 if ( m_NPCState != NPC_STATE_NONE && m_NPCState != NPC_STATE_DEAD )
4468 {
4469 if ( FacingIdeal() )
4470 Forget( bits_MEMORY_TURNING );
4471
4472 bool bForcedGather = m_bForceConditionsGather;
4473 m_bForceConditionsGather = false;
4474
4475 if ( m_pfnThink != (BASEPTR)&CAI_BaseNPC::CallNPCThink )
4476 {
4477 if ( UTIL_FindClientInPVS( edict() ) != NULL )
4478 SetCondition( COND_IN_PVS );
4479 else
4480 ClearCondition( COND_IN_PVS );
4481 }
4482
4483 // Sample the environment. Do this unconditionally if there is a player in this
4484 // npc's PVS. NPCs in COMBAT state are allowed to simulate when there is no player in
4485 // the same PVS. This is so that any fights in progress will continue even if the player leaves the PVS.
4486 if ( !IsFlaggedEfficient() &&
4487 ( bForcedGather ||
4488 HasCondition( COND_IN_PVS ) ||
4489 ShouldAlwaysThink() ||
4490 m_NPCState == NPC_STATE_COMBAT ) )
4491 {
4492 CheckOnGround();
4493
4494 if ( ShouldPlayIdleSound() )
4495 {
4496 AI_PROFILE_SCOPE(CAI_BaseNPC_IdleSound);
4497 IdleSound();
4498 }
4499
4500 PerformSensing();
4501
4502 GetEnemies()->RefreshMemories();
4503 ChooseEnemy();
4504
4505 // Check to see if there is a better weapon available
4506 if (Weapon_IsBetterAvailable())
4507 {
4508 SetCondition(COND_BETTER_WEAPON_AVAILABLE);
4509 }
4510
4511 if ( GetCurSchedule() &&
4512 ( m_NPCState == NPC_STATE_IDLE || m_NPCState == NPC_STATE_ALERT) &&
4513 GetEnemy() &&
4514 !HasCondition( COND_NEW_ENEMY ) &&
4515 GetCurSchedule()->HasInterrupt( COND_NEW_ENEMY ) )
4516 {
4517 // @Note (toml 05-05-04): There seems to be a case where an NPC can not respond
4518 // to COND_NEW_ENEMY. Only evidence right now is save
4519 // games after the fact, so for now, just patching it up
4520 DevMsg( 2, "Had to force COND_NEW_ENEMY\n" );
4521 SetCondition(COND_NEW_ENEMY);
4522 }
4523 }
4524 else
4525 {
4526 // if not done, can have problems if leave PVS in same frame heard/saw things,
4527 // since only PerformSensing clears conditions
4528 ClearSenseConditions();
4529 }
4530
4531 // do these calculations if npc has an enemy.
4532 if ( GetEnemy() != NULL )
4533 {
4534 if ( !IsFlaggedEfficient() )
4535 {
4536 GatherEnemyConditions( GetEnemy() );
4537 m_flLastEnemyTime = gpGlobals->curtime;
4538 }
4539 else
4540 {
4541 SetEnemy( NULL );
4542 }
4543 }
4544
4545 // do these calculations if npc has a target
4546 if ( GetTarget() != NULL )
4547 {
4548 CheckTarget( GetTarget() );
4549 }
4550
4551 CheckAmmo();
4552
4553 CheckFlinches();
4554
4555 CheckSquad();
4556 }
4557 else
4558 ClearCondition( COND_IN_PVS );
4559
4560 RemoveIgnoredConditions();
4561
4562 g_AIConditionsTimer.End();
4563}
4564
4565//-----------------------------------------------------------------------------
4566// Purpose:
4567//-----------------------------------------------------------------------------
4568void CAI_BaseNPC::PrescheduleThink( void )
4569{
4570#ifdef HL2_EPISODIC
4571 CheckForScriptedNPCInteractions();
4572#endif
4573
4574 // If we use weapons, and our desired weapon state is not the current, fix it
4575 if( (CapabilitiesGet() & bits_CAP_USE_WEAPONS) && (m_iDesiredWeaponState == DESIREDWEAPONSTATE_HOLSTERED || m_iDesiredWeaponState == DESIREDWEAPONSTATE_UNHOLSTERED || m_iDesiredWeaponState == DESIREDWEAPONSTATE_HOLSTERED_DESTROYED ) )
4576 {
4577 if ( IsAlive() && !IsInAScript() )
4578 {
4579 if ( !IsCurSchedule( SCHED_MELEE_ATTACK1, false ) && !IsCurSchedule( SCHED_MELEE_ATTACK2, false ) &&
4580 !IsCurSchedule( SCHED_RANGE_ATTACK1, false ) && !IsCurSchedule( SCHED_RANGE_ATTACK2, false ) )
4581 {
4582 if ( m_iDesiredWeaponState == DESIREDWEAPONSTATE_HOLSTERED || m_iDesiredWeaponState == DESIREDWEAPONSTATE_HOLSTERED_DESTROYED )
4583 {
4584 HolsterWeapon();
4585 }
4586 else if ( m_iDesiredWeaponState == DESIREDWEAPONSTATE_UNHOLSTERED )
4587 {
4588 UnholsterWeapon();
4589 }
4590 }
4591 }
4592 else
4593 {
4594 // Throw away the request
4595 m_iDesiredWeaponState = DESIREDWEAPONSTATE_IGNORE;
4596 }
4597 }
4598}
4599
4600//-----------------------------------------------------------------------------
4601// Main entry point for processing AI
4602//-----------------------------------------------------------------------------
4603
4604void CAI_BaseNPC::RunAI( void )
4605{
4606 AI_PROFILE_SCOPE(CAI_BaseNPC_RunAI);
4607 g_AIRunTimer.Start();
4608
4609 if( ai_debug_squads.GetBool() )
4610 {
4611 if( IsInSquad() && GetSquad() && !CAI_Squad::IsSilentMember(this ) && ( GetSquad()->IsLeader( this ) || GetSquad()->NumMembers() == 1 ) )
4612 {
4613 AISquadIter_t iter;
4614 CAI_Squad *pSquad = GetSquad();
4615
4616 Vector right;
4617 Vector vecPoint;
4618
4619 vecPoint = EyePosition() + Vector( 0, 0, 12 );
4620 GetVectors( NULL, &right, NULL );
4621 NDebugOverlay::Line( vecPoint, vecPoint + Vector( 0, 0, 64 ), 0, 255, 0, false , 0.1 );
4622 NDebugOverlay::Line( vecPoint, vecPoint + Vector( 0, 0, 32 ) + right * 32, 0, 255, 0, false , 0.1 );
4623 NDebugOverlay::Line( vecPoint, vecPoint + Vector( 0, 0, 32 ) - right * 32, 0, 255, 0, false , 0.1 );
4624
4625 for ( CAI_BaseNPC *pSquadMember = pSquad->GetFirstMember( &iter, false ); pSquadMember; pSquadMember = pSquad->GetNextMember( &iter, false ) )
4626 {
4627 if ( pSquadMember != this )
4628 NDebugOverlay::Line( EyePosition(), pSquadMember->EyePosition(), 0,
4629 CAI_Squad::IsSilentMember(pSquadMember) ? 127 : 255, 0, false , 0.1 );
4630 }
4631 }
4632 }
4633
4634 if( ai_debug_loners.GetBool() && !IsInSquad() && AI_IsSinglePlayer() )
4635 {
4636 Vector right;
4637 Vector vecPoint;
4638
4639 vecPoint = EyePosition() + Vector( 0, 0, 12 );
4640
4641 UTIL_GetLocalPlayer()->GetVectors( NULL, &right, NULL );
4642
4643 NDebugOverlay::Line( vecPoint, vecPoint + Vector( 0, 0, 64 ), 255, 0, 0, false , 0.1 );
4644 NDebugOverlay::Line( vecPoint, vecPoint + Vector( 0, 0, 32 ) + right * 32, 255, 0, 0, false , 0.1 );
4645 NDebugOverlay::Line( vecPoint, vecPoint + Vector( 0, 0, 32 ) - right * 32, 255, 0, 0, false , 0.1 );
4646 }
4647
4648#ifdef _DEBUG
4649 m_bSelected = ( (m_debugOverlays & OVERLAY_NPC_SELECTED_BIT) != 0 );
4650#endif
4651
4652 m_bConditionsGathered = false;
4653 m_bSkippedChooseEnemy = false;
4654
4655 if ( g_pDeveloper->GetInt() && !GetNavigator()->IsOnNetwork() )
4656 {
4657 AddTimedOverlay( "NPC w/no reachable nodes!", 5 );
4658 }
4659
4660 AI_PROFILE_SCOPE_BEGIN(CAI_BaseNPC_RunAI_GatherConditions);
4661 GatherConditions();
4662 AI_PROFILE_SCOPE_END();
4663
4664 if ( !m_bConditionsGathered )
4665 m_bConditionsGathered = true; // derived class didn't call to base
4666
4667 TryRestoreHull();
4668
4669 g_AIPrescheduleThinkTimer.Start();
4670
4671 AI_PROFILE_SCOPE_BEGIN(CAI_RunAI_PrescheduleThink);
4672 PrescheduleThink();
4673 AI_PROFILE_SCOPE_END();
4674
4675 g_AIPrescheduleThinkTimer.End();
4676
4677 MaintainSchedule();
4678
4679 PostscheduleThink();
4680
4681 ClearTransientConditions();
4682
4683 g_AIRunTimer.End();
4684}
4685
4686//-----------------------------------------------------------------------------
4687void CAI_BaseNPC::ClearTransientConditions()
4688{
4689 // if the npc didn't use these conditions during the above call to MaintainSchedule()
4690 // we throw them out cause we don't want them sitting around through the lifespan of a schedule
4691 // that doesn't use them.
4692 ClearCondition( COND_LIGHT_DAMAGE );
4693 ClearCondition( COND_HEAVY_DAMAGE );
4694 ClearCondition( COND_PHYSICS_DAMAGE );
4695 ClearCondition( COND_PLAYER_PUSHING );
4696}
4697
4698
4699//-----------------------------------------------------------------------------
4700// Selecting the idle ideal state
4701//-----------------------------------------------------------------------------
4702NPC_STATE CAI_BaseNPC::SelectIdleIdealState()
4703{
4704 // IDLE goes to ALERT upon hearing a sound
4705 // IDLE goes to ALERT upon being injured
4706 // IDLE goes to ALERT upon seeing food
4707 // IDLE goes to COMBAT upon sighting an enemy
4708 if ( HasCondition(COND_NEW_ENEMY) ||
4709 HasCondition(COND_SEE_ENEMY) )
4710 {
4711 // new enemy! This means an idle npc has seen someone it dislikes, or
4712 // that a npc in combat has found a more suitable target to attack
4713 return NPC_STATE_COMBAT;
4714 }
4715
4716 // Set our ideal yaw if we've taken damage
4717 if ( HasCondition(COND_LIGHT_DAMAGE) ||
4718 HasCondition(COND_HEAVY_DAMAGE) ||
4719 (!GetEnemy() && gpGlobals->curtime - GetEnemies()->LastTimeSeen( AI_UNKNOWN_ENEMY ) < TIME_CARE_ABOUT_DAMAGE ) )
4720 {
4721 Vector vecEnemyLKP;
4722
4723 // Fill in where we're trying to look
4724 if ( GetEnemy() )
4725 {
4726 vecEnemyLKP = GetEnemyLKP();
4727 }
4728 else
4729 {
4730 if ( GetEnemies()->Find( AI_UNKNOWN_ENEMY ) )
4731 {
4732 vecEnemyLKP = GetEnemies()->LastKnownPosition( AI_UNKNOWN_ENEMY );
4733 }
4734 else
4735 {
4736 // Don't have an enemy, so face the direction the last attack came from (don't face north)
4737 vecEnemyLKP = WorldSpaceCenter() + ( g_vecAttackDir * 128 );
4738 }
4739 }
4740
4741 // Set the ideal
4742 GetMotor()->SetIdealYawToTarget( vecEnemyLKP );
4743
4744 return NPC_STATE_ALERT;
4745 }
4746
4747 if ( HasCondition(COND_HEAR_DANGER) ||
4748 HasCondition(COND_HEAR_COMBAT) ||
4749 HasCondition(COND_HEAR_WORLD) ||
4750 HasCondition(COND_HEAR_PLAYER) ||
4751 HasCondition(COND_HEAR_THUMPER) ||
4752 HasCondition(COND_HEAR_BULLET_IMPACT) )
4753 {
4754 // Interrupted by a sound. So make our ideal yaw the
4755 // source of the sound!
4756 CSound *pSound;
4757
4758 pSound = GetBestSound();
4759 Assert( pSound != NULL );
4760 if ( pSound )
4761 {
4762 // BRJ 1/7/04: This code used to set the ideal yaw.
4763 // It's really side-effecty to set the yaw here.
4764 // That is now done by the FACE_BESTSOUND schedule.
4765 // Revert this change if it causes problems.
4766 GetMotor()->SetIdealYawToTarget( pSound->GetSoundReactOrigin() );
4767 if ( pSound->IsSoundType( SOUND_COMBAT | SOUND_DANGER | SOUND_BULLET_IMPACT ) )
4768 {
4769 return NPC_STATE_ALERT;
4770 }
4771 }
4772 }
4773
4774 if ( HasInterruptCondition(COND_SMELL) )
4775 {
4776 return NPC_STATE_ALERT;
4777 }
4778
4779 return NPC_STATE_INVALID;
4780}
4781
4782
4783//-----------------------------------------------------------------------------
4784// Selecting the alert ideal state
4785//-----------------------------------------------------------------------------
4786NPC_STATE CAI_BaseNPC::SelectAlertIdealState()
4787{
4788 // ALERT goes to IDLE upon becoming bored
4789 // ALERT goes to COMBAT upon sighting an enemy
4790 if ( HasCondition(COND_NEW_ENEMY) ||
4791 HasCondition(COND_SEE_ENEMY) ||
4792 GetEnemy() != NULL )
4793 {
4794 return NPC_STATE_COMBAT;
4795 }
4796
4797 // Set our ideal yaw if we've taken damage
4798 if ( HasCondition(COND_LIGHT_DAMAGE) ||
4799 HasCondition(COND_HEAVY_DAMAGE) ||
4800 (!GetEnemy() && gpGlobals->curtime - GetEnemies()->LastTimeSeen( AI_UNKNOWN_ENEMY ) < TIME_CARE_ABOUT_DAMAGE ) )
4801 {
4802 Vector vecEnemyLKP;
4803
4804 // Fill in where we're trying to look
4805 if ( GetEnemy() )
4806 {
4807 vecEnemyLKP = GetEnemyLKP();
4808 }
4809 else
4810 {
4811 if ( GetEnemies()->Find( AI_UNKNOWN_ENEMY ) )
4812 {
4813 vecEnemyLKP = GetEnemies()->LastKnownPosition( AI_UNKNOWN_ENEMY );
4814 }
4815 else
4816 {
4817 // Don't have an enemy, so face the direction the last attack came from (don't face north)
4818 vecEnemyLKP = WorldSpaceCenter() + ( g_vecAttackDir * 128 );
4819 }
4820 }
4821
4822 // Set the ideal
4823 GetMotor()->SetIdealYawToTarget( vecEnemyLKP );
4824
4825 return NPC_STATE_ALERT;
4826 }
4827
4828 if ( HasCondition(COND_HEAR_DANGER) ||
4829 HasCondition(COND_HEAR_COMBAT) )
4830 {
4831 CSound *pSound = GetBestSound();
4832 AssertOnce( pSound != NULL );
4833
4834 if ( pSound )
4835 {
4836 GetMotor()->SetIdealYawToTarget( pSound->GetSoundReactOrigin() );
4837 }
4838
4839 return NPC_STATE_ALERT;
4840 }
4841
4842 if ( ShouldGoToIdleState() )
4843 {
4844 return NPC_STATE_IDLE;
4845 }
4846
4847 return NPC_STATE_INVALID;
4848}
4849
4850
4851//-----------------------------------------------------------------------------
4852// Selecting the alert ideal state
4853//-----------------------------------------------------------------------------
4854NPC_STATE CAI_BaseNPC::SelectScriptIdealState()
4855{
4856 if ( HasCondition(COND_TASK_FAILED) ||
4857 HasCondition(COND_LIGHT_DAMAGE) ||
4858 HasCondition(COND_HEAVY_DAMAGE) )
4859 {
4860 ExitScriptedSequence(); // This will set the ideal state
4861 }
4862
4863 if ( m_IdealNPCState == NPC_STATE_IDLE )
4864 {
4865 // Exiting a script. Select the ideal state assuming we were idle now.
4866 m_NPCState = NPC_STATE_IDLE;
4867 NPC_STATE eIdealState = SelectIdealState();
4868 m_NPCState = NPC_STATE_SCRIPT;
4869 return eIdealState;
4870 }
4871
4872 return NPC_STATE_INVALID;
4873}
4874
4875
4876//-----------------------------------------------------------------------------
4877// Purpose: Surveys the Conditions information available and finds the best
4878// new state for a npc.
4879//
4880// NOTICE the CAI_BaseNPC implementation of this function does not care about
4881// private conditions!
4882//
4883// Output : NPC_STATE - the suggested ideal state based on current conditions.
4884//-----------------------------------------------------------------------------
4885NPC_STATE CAI_BaseNPC::SelectIdealState( void )
4886{
4887 // dvs: FIXME: lots of side effecty code in here!! this function should ONLY return an ideal state!
4888
4889 // ---------------------------
4890 // Do some squad stuff first
4891 // ---------------------------
4892 if (m_pSquad)
4893 {
4894 switch( m_NPCState )
4895 {
4896 case NPC_STATE_IDLE:
4897 case NPC_STATE_ALERT:
4898 if ( HasCondition ( COND_NEW_ENEMY ) )
4899 {
4900 m_pSquad->SquadNewEnemy( GetEnemy() );
4901 }
4902 break;
4903 }
4904 }
4905
4906 // ---------------------------
4907 // Set ideal state
4908 // ---------------------------
4909 switch ( m_NPCState )
4910 {
4911 case NPC_STATE_IDLE:
4912 {
4913 NPC_STATE nState = SelectIdleIdealState();
4914 if ( nState != NPC_STATE_INVALID )
4915 return nState;
4916 }
4917 break;
4918
4919 case NPC_STATE_ALERT:
4920 {
4921 NPC_STATE nState = SelectAlertIdealState();
4922 if ( nState != NPC_STATE_INVALID )
4923 return nState;
4924 }
4925 break;
4926
4927 case NPC_STATE_COMBAT:
4928 // COMBAT goes to ALERT upon death of enemy
4929 {
4930 if ( GetEnemy() == NULL )
4931 {
4932 DevWarning( 2, "***Combat state with no enemy!\n" );
4933 return NPC_STATE_ALERT;
4934 }
4935 break;
4936 }
4937 case NPC_STATE_SCRIPT:
4938 {
4939 NPC_STATE nState = SelectScriptIdealState();
4940 if ( nState != NPC_STATE_INVALID )
4941 return nState;
4942 }
4943 break;
4944
4945 case NPC_STATE_DEAD:
4946 return NPC_STATE_DEAD;
4947 }
4948
4949 // The best ideal state is the current ideal state.
4950 return m_IdealNPCState;
4951}
4952
4953//------------------------------------------------------------------------------
4954//------------------------------------------------------------------------------
4955void CAI_BaseNPC::GiveWeapon( string_t iszWeaponName )
4956{
4957 CBaseCombatWeapon *pWeapon = Weapon_Create( STRING(iszWeaponName) );
4958 if ( !pWeapon )
4959 {
4960 Warning( "Couldn't create weapon %s to give NPC %s.\n", STRING(iszWeaponName), STRING(GetEntityName()) );
4961 return;
4962 }
4963
4964 // If I have a weapon already, drop it
4965 if ( GetActiveWeapon() )
4966 {
4967 Weapon_Drop( GetActiveWeapon() );
4968 }
4969
4970 // If I have a name, make my weapon match it with "_weapon" appended
4971 if ( GetEntityName() != NULL_STRING )
4972 {
4973 pWeapon->SetName( AllocPooledString(UTIL_VarArgs("%s_weapon", GetEntityName())) );
4974 }
4975
4976 Weapon_Equip( pWeapon );
4977
4978 // Handle this case
4979 OnGivenWeapon( pWeapon );
4980}
4981
4982//-----------------------------------------------------------------------------
4983// Rather specific function that tells us if an NPC is in the process of
4984// moving to a weapon with the intent to pick it up.
4985//-----------------------------------------------------------------------------
4986bool CAI_BaseNPC::IsMovingToPickupWeapon()
4987{
4988 return IsCurSchedule( SCHED_NEW_WEAPON );
4989}
4990
4991//-----------------------------------------------------------------------------
4992//-----------------------------------------------------------------------------
4993bool CAI_BaseNPC::ShouldLookForBetterWeapon()
4994{
4995 if( m_flNextWeaponSearchTime > gpGlobals->curtime )
4996 return false;
4997
4998 if( !(CapabilitiesGet() & bits_CAP_USE_WEAPONS) )
4999 return false;
5000
5001 // Already armed and currently fighting. Don't try to upgrade.
5002 if( GetActiveWeapon() && m_NPCState == NPC_STATE_COMBAT )
5003 return false;
5004
5005 if( IsMovingToPickupWeapon() )
5006 return false;
5007
5008 if( !IsPlayerAlly() && GetActiveWeapon() )
5009 return false;
5010
5011 if( IsInAScript() )
5012 return false;
5013
5014 return true;
5015}
5016
5017//-----------------------------------------------------------------------------
5018// Purpose: Check if a better weapon is available.
5019// For now check if there is a weapon and I don't have one. In
5020// the future
5021// UNDONE: actually rate the weapons based on there strength
5022// Input :
5023// Output :
5024//-----------------------------------------------------------------------------
5025bool CAI_BaseNPC::Weapon_IsBetterAvailable()
5026{
5027 if( m_iszPendingWeapon != NULL_STRING )
5028 {
5029 // Some weapon is reserved for us.
5030 return true;
5031 }
5032
5033 if( ShouldLookForBetterWeapon() )
5034 {
5035 if( GetActiveWeapon() )
5036 {
5037 m_flNextWeaponSearchTime = gpGlobals->curtime + 2;
5038 }
5039 else
5040 {
5041 if( IsInPlayerSquad() )
5042 {
5043 // Look for a weapon frequently.
5044 m_flNextWeaponSearchTime = gpGlobals->curtime + 1;
5045 }
5046 else
5047 {
5048 m_flNextWeaponSearchTime = gpGlobals->curtime + 2;
5049 }
5050 }
5051
5052 if ( Weapon_FindUsable( WEAPON_SEARCH_DELTA ) )
5053 {
5054 return true;
5055 }
5056 }
5057
5058 return false;
5059}
5060
5061//-----------------------------------------------------------------------------
5062// Purpose: Returns true is weapon has a line of sight. If bSetConditions is
5063// true, also sets LOC conditions
5064// Input :
5065// Output :
5066//-----------------------------------------------------------------------------
5067bool CAI_BaseNPC::WeaponLOSCondition(const Vector &ownerPos, const Vector &targetPos, bool bSetConditions )
5068{
5069#if 0
5070 // @TODO (toml 03-07-04): this code might be better (not tested)
5071 Vector vecLocalRelativePosition;
5072 VectorITransform( npcOwner->Weapon_ShootPosition(), npcOwner->EntityToWorldTransform(), vecLocalRelativePosition );
5073
5074 // Compute desired test transform
5075
5076 // Compute desired x axis
5077 Vector xaxis;
5078 VectorSubtract( targetPos, ownerPos, xaxis );
5079
5080 // FIXME: Insert angle test here?
5081 float flAngle = acos( xaxis.z / xaxis.Length() );
5082
5083 xaxis.z = 0.0f;
5084 float flLength = VectorNormalize( xaxis );
5085 if ( flLength < 1e-3 )
5086 return false;
5087
5088 Vector yaxis( -xaxis.y, xaxis.x, 0.0f );
5089
5090 matrix3x4_t losTestToWorld;
5091 MatrixInitialize( losTestToWorld, ownerPos, xaxis, yaxis, zaxis );
5092
5093 Vector barrelPos;
5094 VectorTransform( vecLocalRelativePosition, losTestToWorld, barrelPos );
5095
5096#endif
5097
5098 bool bHaveLOS;
5099
5100 if (GetActiveWeapon())
5101 {
5102 bHaveLOS = GetActiveWeapon()->WeaponLOSCondition(ownerPos, targetPos, bSetConditions);
5103 }
5104 else if (CapabilitiesGet() & bits_CAP_INNATE_RANGE_ATTACK1)
5105 {
5106 bHaveLOS = InnateWeaponLOSCondition(ownerPos, targetPos, bSetConditions);
5107 }
5108 else
5109 {
5110 if (bSetConditions)
5111 {
5112 SetCondition( COND_NO_WEAPON );
5113 }
5114 bHaveLOS = false;
5115 }
5116 // -------------------------------------------
5117 // Check for friendly fire with the player
5118 // -------------------------------------------
5119 if ( CapabilitiesGet() & ( bits_CAP_NO_HIT_PLAYER | bits_CAP_NO_HIT_SQUADMATES ) )
5120 {
5121 float spread = 0.92;
5122 if ( GetActiveWeapon() )
5123 {
5124 Vector vSpread = GetAttackSpread( GetActiveWeapon() );
5125 if ( vSpread.x > VECTOR_CONE_15DEGREES.x )
5126 spread = TableCos(asin(vSpread.x));
5127 else // too much error because using point not box
5128 spread = 0.99145; // "15 degrees"
5129 }
5130 if (CapabilitiesGet() & bits_CAP_NO_HIT_PLAYER)
5131 {
5132 // Check shoot direction relative to player
5133 if (PlayerInSpread( ownerPos, targetPos, spread, 8*12 ))
5134 {
5135 if (bSetConditions)
5136 {
5137 SetCondition( COND_WEAPON_PLAYER_IN_SPREAD );
5138 }
5139 bHaveLOS = false;
5140 }
5141 /* For grenades etc. check that player is clear?
5142 // Check player position also
5143 if (PlayerInRange( targetPos, 100 ))
5144 {
5145 SetCondition( COND_WEAPON_PLAYER_NEAR_TARGET );
5146 }
5147 */
5148 }
5149
5150 if ( bHaveLOS )
5151 {
5152 if ( (CapabilitiesGet() & bits_CAP_NO_HIT_SQUADMATES) && m_pSquad && GetEnemy() )
5153 {
5154 if ( IsSquadmateInSpread( ownerPos, targetPos, spread, 8*12 ) )
5155 bHaveLOS = false;
5156 }
5157 }
5158 }
5159 return bHaveLOS;
5160}
5161
5162//-----------------------------------------------------------------------------
5163// Purpose: Check the innate weapon LOS for an owner at an arbitrary position
5164// If bSetConditions is true, LOS related conditions will also be set
5165// Input :
5166// Output :
5167//-----------------------------------------------------------------------------
5168bool CAI_BaseNPC::InnateWeaponLOSCondition( const Vector &ownerPos, const Vector &targetPos, bool bSetConditions )
5169{
5170 // --------------------
5171 // Check for occlusion
5172 // --------------------
5173 // Base class version assumes innate weapon position is at eye level
5174 Vector barrelPos = ownerPos + GetViewOffset();
5175 trace_t tr;
5176 AI_TraceLine( barrelPos, targetPos, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr);
5177
5178 if ( tr.fraction == 1.0 )
5179 {
5180 return true;
5181 }
5182
5183 CBaseEntity *pHitEntity = tr.m_pEnt;
5184
5185 // Translate a hit vehicle into its passenger if found
5186 if ( GetEnemy() != NULL )
5187 {
5188 CBaseCombatCharacter *pCCEnemy = GetEnemy()->MyCombatCharacterPointer();
5189 if ( pCCEnemy != NULL && pCCEnemy->IsInAVehicle() )
5190 {
5191 // Ok, player in vehicle, check if vehicle is target we're looking at, fire if it is
5192 // Also, check to see if the owner of the entity is the vehicle, in which case it's valid too.
5193 // This catches vehicles that use bone followers.
5194 CBaseEntity *pVehicleEnt = pCCEnemy->GetVehicleEntity();
5195 if ( pHitEntity == pVehicleEnt || pHitEntity->GetOwnerEntity() == pVehicleEnt )
5196 return true;
5197 }
5198 }
5199
5200 if ( pHitEntity == GetEnemy() )
5201 {
5202 return true;
5203 }
5204 else if ( pHitEntity && pHitEntity->MyCombatCharacterPointer() )
5205 {
5206 if (IRelationType( pHitEntity ) == D_HT)
5207 {
5208 return true;
5209 }
5210 else if (bSetConditions)
5211 {
5212 SetCondition(COND_WEAPON_BLOCKED_BY_FRIEND);
5213 }
5214 }
5215 else if (bSetConditions)
5216 {
5217 SetCondition(COND_WEAPON_SIGHT_OCCLUDED);
5218 SetEnemyOccluder(tr.m_pEnt);
5219 }
5220
5221 return false;
5222}
5223
5224//=========================================================
5225// CanCheckAttacks - prequalifies a npc to do more fine
5226// checking of potential attacks.
5227//=========================================================
5228bool CAI_BaseNPC::FCanCheckAttacks( void )
5229{
5230 // Not allowed to check attacks while climbing or jumping
5231 // Otherwise schedule is interrupted while on ladder/etc
5232 // which is NOT a legal place to attack from
5233 if ( GetNavType() == NAV_CLIMB || GetNavType() == NAV_JUMP )
5234 return false;
5235
5236 if ( HasCondition(COND_SEE_ENEMY) && !HasCondition( COND_ENEMY_TOO_FAR))
5237 {
5238 return true;
5239 }
5240
5241 return false;
5242}
5243
5244//-----------------------------------------------------------------------------
5245// Purpose: Return dist. to enemy (closest of origin/head/feet)
5246// Input :
5247// Output :
5248//-----------------------------------------------------------------------------
5249float CAI_BaseNPC::EnemyDistance( CBaseEntity *pEnemy )
5250{
5251 Vector enemyDelta = pEnemy->WorldSpaceCenter() - WorldSpaceCenter();
5252
5253 // NOTE: We ignore rotation for computing height. Assume it isn't an effect
5254 // we care about, so we simply use OBBSize().z for height.
5255 // Otherwise you'd do this:
5256 // pEnemy->CollisionProp()->WorldSpaceSurroundingBounds( &enemyMins, &enemyMaxs );
5257 // float enemyHeight = enemyMaxs.z - enemyMins.z;
5258
5259 float enemyHeight = pEnemy->CollisionProp()->OBBSize().z;
5260 float myHeight = CollisionProp()->OBBSize().z;
5261
5262 // max distance our centers can be apart with the boxes still overlapping
5263 float flMaxZDist = ( enemyHeight + myHeight ) * 0.5f;
5264
5265 // see if the enemy is closer to my head, feet or in between
5266 if ( enemyDelta.z > flMaxZDist )
5267 {
5268 // enemy feet above my head, compute distance from my head to his feet
5269 enemyDelta.z -= flMaxZDist;
5270 }
5271 else if ( enemyDelta.z < -flMaxZDist )
5272 {
5273 // enemy head below my feet, return distance between my feet and his head
5274 enemyDelta.z += flMaxZDist;
5275 }
5276 else
5277 {
5278 // boxes overlap in Z, no delta
5279 enemyDelta.z = 0;
5280 }
5281
5282 return enemyDelta.Length();
5283}
5284
5285//-----------------------------------------------------------------------------
5286
5287float CAI_BaseNPC::GetReactionDelay( CBaseEntity *pEnemy )
5288{
5289 return ( m_NPCState == NPC_STATE_ALERT || m_NPCState == NPC_STATE_COMBAT ) ?
5290 ai_reaction_delay_alert.GetFloat() :
5291 ai_reaction_delay_idle.GetFloat();
5292}
5293
5294//-----------------------------------------------------------------------------
5295// Purpose: Update information on my enemy
5296// Input :
5297// Output : Returns true is new enemy, false is known enemy
5298//-----------------------------------------------------------------------------
5299bool CAI_BaseNPC::UpdateEnemyMemory( CBaseEntity *pEnemy, const Vector &position, CBaseEntity *pInformer )
5300{
5301 bool firstHand = ( pInformer == NULL || pInformer == this );
5302
5303 AI_PROFILE_SCOPE(CAI_BaseNPC_UpdateEnemyMemory);
5304
5305 if ( GetEnemies() )
5306 {
5307 // If the was eluding me and allow the NPC to play a sound
5308 if (GetEnemies()->HasEludedMe(pEnemy))
5309 {
5310 FoundEnemySound();
5311 }
5312 float reactionDelay = ( !pInformer || pInformer == this ) ? GetReactionDelay( pEnemy ) : 0.0;
5313 bool result = GetEnemies()->UpdateMemory(GetNavigator()->GetNetwork(), pEnemy, position, reactionDelay, firstHand);
5314
5315 if ( !firstHand && pEnemy && result && GetState() == NPC_STATE_IDLE ) // if it's a new potential enemy
5316 ForceDecisionThink();
5317
5318 if ( firstHand && pEnemy && m_pSquad )
5319 {
5320 m_pSquad->UpdateEnemyMemory( this, pEnemy, position );
5321 }
5322 return result;
5323 }
5324 return true;
5325}
5326
5327
5328//-----------------------------------------------------------------------------
5329// Purpose: Remembers the thing my enemy is hiding behind. Called when either
5330// COND_ENEMY_OCCLUDED or COND_WEAPON_SIGHT_OCCLUDED is set.
5331//-----------------------------------------------------------------------------
5332void CAI_BaseNPC::SetEnemyOccluder(CBaseEntity *pBlocker)
5333{
5334 m_hEnemyOccluder = pBlocker;
5335}
5336
5337
5338//-----------------------------------------------------------------------------
5339// Purpose: Gets the thing my enemy is hiding behind (assuming they are hiding).
5340//-----------------------------------------------------------------------------
5341CBaseEntity *CAI_BaseNPC::GetEnemyOccluder(void)
5342{
5343 return m_hEnemyOccluder.Get();
5344}
5345
5346
5347//-----------------------------------------------------------------------------
5348// Purpose: part of the Condition collection process
5349// gets and stores data and conditions pertaining to a npc's
5350// enemy.
5351// @TODO (toml 07-27-03): this should become subservient to the senses. right
5352// now, it yields different result
5353// Input :
5354// Output :
5355//-----------------------------------------------------------------------------
5356void CAI_BaseNPC::GatherEnemyConditions( CBaseEntity *pEnemy )
5357{
5358 AI_PROFILE_SCOPE(CAI_BaseNPC_GatherEnemyConditions);
5359
5360 ClearCondition( COND_ENEMY_FACING_ME );
5361 ClearCondition( COND_BEHIND_ENEMY );
5362
5363 // ---------------------------
5364 // Set visibility conditions
5365 // ---------------------------
5366 if ( HasCondition( COND_NEW_ENEMY ) || GetSenses()->GetTimeLastUpdate( GetEnemy() ) == gpGlobals->curtime )
5367 {
5368 AI_PROFILE_SCOPE_BEGIN(CAI_BaseNPC_GatherEnemyConditions_Visibility);
5369
5370 ClearCondition( COND_HAVE_ENEMY_LOS );
5371 ClearCondition( COND_ENEMY_OCCLUDED );
5372
5373 CBaseEntity *pBlocker = NULL;
5374 SetEnemyOccluder(NULL);
5375
5376 bool bSensesDidSee = GetSenses()->DidSeeEntity( pEnemy );
5377
5378 if ( !bSensesDidSee && ( ( EnemyDistance( pEnemy ) >= GetSenses()->GetDistLook() ) || !FVisible( pEnemy, MASK_OPAQUE, &pBlocker ) ) )
5379 {
5380 // No LOS to enemy
5381 SetEnemyOccluder(pBlocker);
5382 SetCondition( COND_ENEMY_OCCLUDED );
5383 ClearCondition( COND_SEE_ENEMY );
5384
5385 if (HasMemory( bits_MEMORY_HAD_LOS ))
5386 {
5387 AI_PROFILE_SCOPE(CAI_BaseNPC_GatherEnemyConditions_Outputs);
5388 // Send output event
5389 if (GetEnemy()->IsPlayer())
5390 {
5391 m_OnLostPlayerLOS.FireOutput( GetEnemy(), this );
5392 }
5393 m_OnLostEnemyLOS.FireOutput( GetEnemy(), this );
5394 }
5395 Forget( bits_MEMORY_HAD_LOS );
5396 }
5397 else
5398 {
5399 // Have LOS but may not be in view cone
5400 SetCondition( COND_HAVE_ENEMY_LOS );
5401
5402 if ( bSensesDidSee )
5403 {
5404 // Have LOS and in view cone
5405 SetCondition( COND_SEE_ENEMY );
5406 }
5407 else
5408 {
5409 ClearCondition( COND_SEE_ENEMY );
5410 }
5411
5412 if (!HasMemory( bits_MEMORY_HAD_LOS ))
5413 {
5414 AI_PROFILE_SCOPE(CAI_BaseNPC_GatherEnemyConditions_Outputs);
5415 // Send output event
5416 EHANDLE hEnemy;
5417 hEnemy.Set( GetEnemy() );
5418
5419 if (GetEnemy()->IsPlayer())
5420 {
5421 m_OnFoundPlayer.Set(hEnemy, this, this);
5422 m_OnFoundEnemy.Set(hEnemy, this, this);
5423 }
5424 else
5425 {
5426 m_OnFoundEnemy.Set(hEnemy, this, this);
5427 }
5428 }
5429 Remember( bits_MEMORY_HAD_LOS );
5430 }
5431
5432 AI_PROFILE_SCOPE_END();
5433 }
5434
5435 // -------------------
5436 // If enemy is dead
5437 // -------------------
5438 if ( !pEnemy->IsAlive() )
5439 {
5440 SetCondition( COND_ENEMY_DEAD );
5441 ClearCondition( COND_SEE_ENEMY );
5442 ClearCondition( COND_ENEMY_OCCLUDED );
5443 return;
5444 }
5445
5446 float flDistToEnemy = EnemyDistance(pEnemy);
5447
5448 AI_PROFILE_SCOPE_BEGIN(CAI_BaseNPC_GatherEnemyConditions_SeeEnemy);
5449
5450 if ( HasCondition( COND_SEE_ENEMY ) )
5451 {
5452 // Trail the enemy a bit if he's moving
5453 if (pEnemy->GetSmoothedVelocity() != vec3_origin)
5454 {
5455 Vector vTrailPos = pEnemy->GetAbsOrigin() - pEnemy->GetSmoothedVelocity() * random->RandomFloat( -0.05, 0 );
5456 UpdateEnemyMemory(pEnemy,vTrailPos);
5457 }
5458 else
5459 {
5460 UpdateEnemyMemory(pEnemy,pEnemy->GetAbsOrigin());
5461 }
5462
5463 // If it's not an NPC, assume it can't see me
5464 if ( pEnemy->MyCombatCharacterPointer() && pEnemy->MyCombatCharacterPointer()->FInViewCone ( this ) )
5465 {
5466 SetCondition ( COND_ENEMY_FACING_ME );
5467 ClearCondition ( COND_BEHIND_ENEMY );
5468 }
5469 else
5470 {
5471 ClearCondition( COND_ENEMY_FACING_ME );
5472 SetCondition ( COND_BEHIND_ENEMY );
5473 }
5474 }
5475 else if ( (!HasCondition(COND_ENEMY_OCCLUDED) && !HasCondition(COND_SEE_ENEMY)) && ( flDistToEnemy <= 256 ) )
5476 {
5477 // if the enemy is not occluded, and unseen, that means it is behind or beside the npc.
5478 // if the enemy is near enough the npc, we go ahead and let the npc know where the
5479 // enemy is. Send the enemy in as the informer so this knowledge will be regarded as
5480 // secondhand so that the NPC doesn't
5481 UpdateEnemyMemory( pEnemy, pEnemy->GetAbsOrigin(), pEnemy );
5482 }
5483
5484 AI_PROFILE_SCOPE_END();
5485
5486 float tooFar = m_flDistTooFar;
5487 if ( GetActiveWeapon() && HasCondition(COND_SEE_ENEMY) )
5488 {
5489 tooFar = max( m_flDistTooFar, GetActiveWeapon()->m_fMaxRange1 );
5490 }
5491
5492 if ( flDistToEnemy >= tooFar )
5493 {
5494 // enemy is very far away from npc
5495 SetCondition( COND_ENEMY_TOO_FAR );
5496 }
5497 else
5498 {
5499 ClearCondition( COND_ENEMY_TOO_FAR );
5500 }
5501
5502 if ( FCanCheckAttacks() )
5503 {
5504 // This may also call SetEnemyOccluder!
5505 GatherAttackConditions( GetEnemy(), flDistToEnemy );
5506 }
5507 else
5508 {
5509 ClearAttackConditions();
5510 }
5511
5512 // If my enemy has moved significantly, or if the enemy has changed update my path
5513 UpdateEnemyPos();
5514
5515 // If my target entity has moved significantly, update my path
5516 // This is an odd place to put this, but where else should it go?
5517 UpdateTargetPos();
5518
5519 // ----------------------------------------------------------------------------
5520 // Check if enemy is reachable via the node graph unless I'm not on a network
5521 // ----------------------------------------------------------------------------
5522 if (GetNavigator()->IsOnNetwork())
5523 {
5524 // Note that unreachablity times out
5525 if (IsUnreachable(GetEnemy()))
5526 {
5527 SetCondition(COND_ENEMY_UNREACHABLE);
5528 }
5529 }
5530
5531 //-----------------------------------------------------------------------
5532 // If I haven't seen the enemy in a while he may have eluded me
5533 //-----------------------------------------------------------------------
5534 if (gpGlobals->curtime - GetEnemyLastTimeSeen() > 8)
5535 {
5536 //-----------------------------------------------------------------------
5537 // I'm at last known position at enemy isn't in sight then has eluded me
5538 // ----------------------------------------------------------------------
5539 Vector flEnemyLKP = GetEnemyLKP();
5540 if (((flEnemyLKP - GetAbsOrigin()).Length2D() < 48) &&
5541 !HasCondition(COND_SEE_ENEMY))
5542 {
5543 MarkEnemyAsEluded();
5544 }
5545 //-------------------------------------------------------------------
5546 // If enemy isn't reachable, I can see last known position and enemy
5547 // isn't there, then he has eluded me
5548 // ------------------------------------------------------------------
5549 if (!HasCondition(COND_SEE_ENEMY) && HasCondition(COND_ENEMY_UNREACHABLE))
5550 {
5551 if ( !FVisible( flEnemyLKP ) )
5552 {
5553 MarkEnemyAsEluded();
5554 }
5555 }
5556 }
5557}
5558
5559
5560//-----------------------------------------------------------------------------
5561// In the case of goaltype enemy, update the goal position
5562//-----------------------------------------------------------------------------
5563float CAI_BaseNPC::GetGoalRepathTolerance( CBaseEntity *pGoalEnt, GoalType_t type, const Vector &curGoal, const Vector &curTargetPos )
5564{
5565 float distToGoal = ( GetAbsOrigin() - curTargetPos ).Length() - GetNavigator()->GetArrivalDistance();
5566 float distMoved1Sec = GetSmoothedVelocity().Length();
5567 float result = 120; // FIXME: why 120?
5568
5569 if (distMoved1Sec > 0.0)
5570 {
5571 float t = distToGoal / distMoved1Sec;
5572
5573 result = clamp( 120 * t, 0, 120 );
5574 // Msg("t %.2f : d %.0f (%.0f)\n", t, result, distMoved1Sec );
5575 }
5576
5577 if ( !pGoalEnt->IsPlayer() )
5578 result *= 1.20;
5579
5580 return result;
5581}
5582
5583//-----------------------------------------------------------------------------
5584// In the case of goaltype enemy, update the goal position
5585//-----------------------------------------------------------------------------
5586void CAI_BaseNPC::UpdateEnemyPos()
5587{
5588 // Don't perform path recomputations during a climb or a jump
5589 if ( !GetNavigator()->IsInterruptable() )
5590 return;
5591
5592 if ( m_AnyUpdateEnemyPosTimer.Expired() && m_UpdateEnemyPosTimer.Expired() )
5593 {
5594 // FIXME: does GetGoalRepathTolerance() limit re-routing enough to remove this?
5595 // m_UpdateEnemyPosTimer.Set( 0.5, 1.0 );
5596
5597 // If my enemy has moved significantly, or if the enemy has changed update my path
5598 if ( GetNavigator()->GetGoalType() == GOALTYPE_ENEMY )
5599 {
5600 if (m_hEnemy != GetNavigator()->GetGoalTarget())
5601 {
5602 GetNavigator()->SetGoalTarget( m_hEnemy, vec3_origin );
5603 }
5604 else
5605 {
5606 Vector vEnemyLKP = GetEnemyLKP();
5607 TranslateNavGoal( GetEnemy(), vEnemyLKP );
5608 float tolerance = GetGoalRepathTolerance( GetEnemy(), GOALTYPE_ENEMY, GetNavigator()->GetGoalPos(), vEnemyLKP);
5609 if ( (GetNavigator()->GetGoalPos() - vEnemyLKP).Length() > tolerance )
5610 {
5611 // FIXME: when fleeing crowds, won't this severely limit the effectiveness of each individual? Shouldn't this be a mutex that's held for some period so that at least one attacker is effective?
5612 m_AnyUpdateEnemyPosTimer.Set( 0.1 ); // FIXME: what's a reasonable interval?
5613 if ( !GetNavigator()->RefindPathToGoal( false ) )
5614 {
5615 TaskFail( FAIL_NO_ROUTE );
5616 }
5617 }
5618 }
5619 }
5620 }
5621}
5622
5623
5624//-----------------------------------------------------------------------------
5625// In the case of goaltype targetent, update the goal position
5626//-----------------------------------------------------------------------------
5627void CAI_BaseNPC::UpdateTargetPos()
5628{
5629 // BRJ 10/7/02
5630 // FIXME: make this check time based instead of distance based!
5631
5632 // Don't perform path recomputations during a climb or a jump
5633 if ( !GetNavigator()->IsInterruptable() )
5634 return;
5635
5636 // If my target entity has moved significantly, or has changed, update my path
5637 // This is an odd place to put this, but where else should it go?
5638 if ( GetNavigator()->GetGoalType() == GOALTYPE_TARGETENT )
5639 {
5640 if (m_hTargetEnt != GetNavigator()->GetGoalTarget())
5641 {
5642 GetNavigator()->SetGoalTarget( m_hTargetEnt, vec3_origin );
5643 }
5644 else if ( GetNavigator()->GetGoalFlags() & AIN_UPDATE_TARGET_POS )
5645 {
5646 if ( GetTarget() == NULL || (GetNavigator()->GetGoalPos() - GetTarget()->GetAbsOrigin()).Length() > GetGoalRepathTolerance( GetTarget(), GOALTYPE_TARGETENT, GetNavigator()->GetGoalPos(), GetTarget()->GetAbsOrigin()) )
5647 {
5648 if ( !GetNavigator()->RefindPathToGoal( false ) )
5649 {
5650 TaskFail( FAIL_NO_ROUTE );
5651 }
5652 }
5653 }
5654 }
5655}
5656
5657//-----------------------------------------------------------------------------
5658// Purpose: part of the Condition collection process
5659// gets and stores data and conditions pertaining to a npc's
5660// enemy.
5661// Input :
5662// Output :
5663//-----------------------------------------------------------------------------
5664void CAI_BaseNPC::CheckTarget( CBaseEntity *pTarget )
5665{
5666 AI_PROFILE_SCOPE(CAI_Enemies_CheckTarget);
5667
5668 ClearCondition ( COND_HAVE_TARGET_LOS );
5669 ClearCondition ( COND_TARGET_OCCLUDED );
5670
5671 // ---------------------------
5672 // Set visibility conditions
5673 // ---------------------------
5674 if ( ( EnemyDistance( pTarget ) >= GetSenses()->GetDistLook() ) || !FVisible( pTarget ) )
5675 {
5676 // No LOS to target
5677 SetCondition( COND_TARGET_OCCLUDED );
5678 }
5679 else
5680 {
5681 // Have LOS (may not be in view cone)
5682 SetCondition( COND_HAVE_TARGET_LOS );
5683 }
5684
5685 UpdateTargetPos();
5686}
5687
5688//-----------------------------------------------------------------------------
5689// Purpose: Creates a bullseye of limited lifespan at the provided position
5690// Input : vecOrigin - Where to create the bullseye
5691// duration - The lifespan of the bullseye
5692// Output : A BaseNPC pointer to the bullseye
5693//
5694// NOTES : It is the caller's responsibility to set up relationships with
5695// this bullseye!
5696//-----------------------------------------------------------------------------
5697CAI_BaseNPC *CAI_BaseNPC::CreateCustomTarget( const Vector &vecOrigin, float duration )
5698{
5699#ifdef HL2_DLL
5700 CNPC_Bullseye *pTarget = (CNPC_Bullseye*)CreateEntityByName( "npc_bullseye" );
5701
5702 ASSERT( pTarget != NULL );
5703
5704 // Build a nonsolid bullseye and place it in the desired location
5705 // The bullseye must take damage or the SetHealth 0 call will not be able
5706 pTarget->AddSpawnFlags( SF_BULLSEYE_NONSOLID );
5707 pTarget->SetAbsOrigin( vecOrigin );
5708 pTarget->Spawn();
5709
5710 // Set it up to remove itself
5711 variant_t value;
5712 value.SetFloat(0);
5713 g_EventQueue.AddEvent( pTarget, "SetHealth", value, 2.0, this, this );
5714
5715 return pTarget;
5716#else
5717 return NULL;
5718#endif// HL2_DLL
5719}
5720
5721//-----------------------------------------------------------------------------
5722// Purpose:
5723// Input : eNewActivity -
5724// Output : Activity
5725//-----------------------------------------------------------------------------
5726Activity CAI_BaseNPC::NPC_TranslateActivity( Activity eNewActivity )
5727{
5728 Assert( eNewActivity != ACT_INVALID );
5729
5730 if (eNewActivity == ACT_RANGE_ATTACK1)
5731 {
5732 if ( IsCrouching() )
5733 {
5734 eNewActivity = ACT_RANGE_ATTACK1_LOW;
5735 }
5736 }
5737 else if (eNewActivity == ACT_RELOAD)
5738 {
5739 if (IsCrouching())
5740 {
5741 eNewActivity = ACT_RELOAD_LOW;
5742 }
5743 }
5744 else if ( eNewActivity == ACT_IDLE )
5745 {
5746 if ( IsCrouching() )
5747 {
5748 eNewActivity = ACT_CROUCHIDLE;
5749 }
5750 }
5751
5752 if (CapabilitiesGet() & bits_CAP_DUCK)
5753 {
5754 if (eNewActivity == ACT_RELOAD)
5755 {
5756 return GetReloadActivity(GetHintNode());
5757 }
5758 else if ((eNewActivity == ACT_COVER ) ||
5759 (eNewActivity == ACT_IDLE && HasMemory(bits_MEMORY_INCOVER)))
5760 {
5761 Activity nCoverActivity = GetCoverActivity(GetHintNode());
5762 // ---------------------------------------------------------------
5763 // Some NPCs don't have a cover activity defined so just use idle
5764 // ---------------------------------------------------------------
5765 if (SelectWeightedSequence( nCoverActivity ) == ACTIVITY_NOT_AVAILABLE)
5766 {
5767 nCoverActivity = ACT_IDLE;
5768 }
5769
5770 return nCoverActivity;
5771 }
5772 }
5773 return eNewActivity;
5774}
5775
5776
5777//-----------------------------------------------------------------------------
5778
5779Activity CAI_BaseNPC::TranslateActivity( Activity idealActivity, Activity *pIdealWeaponActivity )
5780{
5781 const int MAX_TRIES = 5;
5782 int count = 0;
5783
5784 bool bIdealWeaponRequired = false;
5785 Activity idealWeaponActivity;
5786 Activity baseTranslation;
5787 bool bWeaponRequired = false;
5788 Activity weaponTranslation;
5789 Activity last;
5790 Activity current;
5791
5792 idealWeaponActivity = Weapon_TranslateActivity( idealActivity, &bIdealWeaponRequired );
5793 if ( pIdealWeaponActivity )
5794 *pIdealWeaponActivity = idealWeaponActivity;
5795
5796 baseTranslation = idealActivity;
5797 weaponTranslation = idealActivity;
5798 last = idealActivity;
5799 while ( count++ < MAX_TRIES )
5800 {
5801 current = NPC_TranslateActivity( last );
5802 if ( current != last )
5803 baseTranslation = current;
5804
5805 weaponTranslation = Weapon_TranslateActivity( current, &bWeaponRequired );
5806
5807 if ( weaponTranslation == last )
5808 break;
5809
5810 last = weaponTranslation;
5811 }
5812 AssertMsg( count < MAX_TRIES, "Circular activity translation!" );
5813
5814 if ( last == ACT_SCRIPT_CUSTOM_MOVE )
5815 return ACT_SCRIPT_CUSTOM_MOVE;
5816
5817 if ( HaveSequenceForActivity( weaponTranslation ) )
5818 return weaponTranslation;
5819
5820 if ( bWeaponRequired )
5821 {
5822 // only complain about an activity once
5823 static CUtlVector< Activity > sUniqueActivities;
5824
5825 if (!sUniqueActivities.Find( weaponTranslation))
5826 {
5827 // FIXME: warning
5828 DevWarning( "%s missing activity \"%s\" needed by weapon\"%s\"\n",
5829 GetClassname(), GetActivityName( weaponTranslation ), GetActiveWeapon()->GetClassname() );
5830
5831 sUniqueActivities.AddToTail( weaponTranslation );
5832 }
5833 }
5834
5835 if ( baseTranslation != weaponTranslation && HaveSequenceForActivity( baseTranslation ) )
5836 return baseTranslation;
5837
5838 if ( idealWeaponActivity != baseTranslation && HaveSequenceForActivity( idealWeaponActivity ) )
5839 return idealActivity;
5840
5841 if ( idealActivity != idealWeaponActivity && HaveSequenceForActivity( idealActivity ) )
5842 return idealActivity;
5843
5844 Assert( !HaveSequenceForActivity( idealActivity ) );
5845 if ( idealActivity == ACT_RUN )
5846 {
5847 idealActivity = ACT_WALK;
5848 }
5849 else if ( idealActivity == ACT_WALK )
5850 {
5851 idealActivity = ACT_RUN;
5852 }
5853
5854 return idealActivity;
5855}
5856
5857//-----------------------------------------------------------------------------
5858// Purpose:
5859// Input : NewActivity -
5860// iSequence -
5861// translatedActivity -
5862// weaponActivity -
5863//-----------------------------------------------------------------------------
5864void CAI_BaseNPC::ResolveActivityToSequence(Activity NewActivity, int &iSequence, Activity &translatedActivity, Activity &weaponActivity)
5865{
5866 AI_PROFILE_SCOPE( CAI_BaseNPC_ResolveActivityToSequence );
5867
5868 iSequence = ACTIVITY_NOT_AVAILABLE;
5869
5870 translatedActivity = TranslateActivity( NewActivity, &weaponActivity );
5871
5872 if ( ( NewActivity == ACT_SCRIPT_CUSTOM_MOVE ) )
5873 {
5874 iSequence = GetScriptCustomMoveSequence();
5875 }
5876 else
5877 {
5878 iSequence = SelectWeightedSequence( translatedActivity );
5879
5880 if ( iSequence == ACTIVITY_NOT_AVAILABLE )
5881 {
5882 static CAI_BaseNPC *pLastWarn;
5883 static Activity lastWarnActivity;
5884 static float timeLastWarn;
5885
5886 if ( ( pLastWarn != this && lastWarnActivity != translatedActivity ) || gpGlobals->curtime - timeLastWarn > 5.0 )
5887 {
5888 DevWarning( "%s has no sequence for act:%s\n", GetClassname(), ActivityList_NameForIndex(translatedActivity) );
5889 pLastWarn = this;
5890 lastWarnActivity = translatedActivity;
5891 timeLastWarn = gpGlobals->curtime;
5892 }
5893
5894 if ( translatedActivity == ACT_RUN )
5895 {
5896 translatedActivity = ACT_WALK;
5897 iSequence = SelectWeightedSequence( translatedActivity );
5898 }
5899 }
5900 }
5901
5902 if ( iSequence == ACT_INVALID )
5903 {
5904 // Abject failure. Use sequence zero.
5905 iSequence = 0;
5906 }
5907}
5908
5909
5910//-----------------------------------------------------------------------------
5911// Purpose:
5912// Input : NewActivity -
5913// iSequence -
5914// translatedActivity -
5915// weaponActivity -
5916//-----------------------------------------------------------------------------
5917extern ConVar ai_sequence_debug;
5918
5919void CAI_BaseNPC::SetActivityAndSequence(Activity NewActivity, int iSequence, Activity translatedActivity, Activity weaponActivity)
5920{
5921 m_translatedActivity = translatedActivity;
5922
5923 if (ai_sequence_debug.GetBool() == true && (m_debugOverlays & OVERLAY_NPC_SELECTED_BIT))
5924 {
5925 DevMsg("SetActivityAndSequence : %s: %s:%s -> %s:%s / %s:%s\n", GetClassname(),
5926 GetActivityName(GetActivity()), GetSequenceName(GetSequence()),
5927 GetActivityName(NewActivity), GetSequenceName(iSequence),
5928 GetActivityName(translatedActivity), GetActivityName(weaponActivity) );
5929
5930 }
5931
5932 // Set to the desired anim, or default anim if the desired is not present
5933 if ( iSequence > ACTIVITY_NOT_AVAILABLE )
5934 {
5935 if ( GetSequence() != iSequence || !SequenceLoops() )
5936 {
5937 //
5938 // Don't reset frame between movement phased animations
5939 if (!IsActivityMovementPhased( m_Activity ) ||
5940 !IsActivityMovementPhased( NewActivity ))
5941 {
5942 SetCycle( 0 );
5943 }
5944 }
5945
5946 ResetSequence( iSequence );
5947 Weapon_SetActivity( weaponActivity, SequenceDuration( iSequence ) );
5948 }
5949 else
5950 {
5951 // Not available try to get default anim
5952 ResetSequence( 0 );
5953 }
5954
5955 // Set the view position based on the current activity
5956 SetViewOffset( EyeOffset(m_translatedActivity) );
5957
5958 if (m_Activity != NewActivity)
5959 {
5960 OnChangeActivity(NewActivity);
5961 }
5962
5963 // NOTE: We DO NOT write the translated activity here.
5964 // This is to abstract the activity translation from the AI code.
5965 // As far as the code is concerned, a translation is merely a new set of sequences
5966 // that should be regarded as the activity in question.
5967
5968 // Go ahead and set this so it doesn't keep trying when the anim is not present
5969 m_Activity = NewActivity;
5970
5971 // this cannot be called until m_Activity stores NewActivity!
5972 GetMotor()->RecalculateYawSpeed();
5973}
5974
5975
5976//-----------------------------------------------------------------------------
5977// Purpose: Sets the activity to the desired activity immediately, skipping any
5978// transition sequences.
5979// Input : NewActivity -
5980//-----------------------------------------------------------------------------
5981void CAI_BaseNPC::SetActivity( Activity NewActivity )
5982{
5983 // If I'm already doing the NewActivity I can bail.
5984 // FIXME: Should this be based on the current translated activity and ideal translated activity (calculated below)?
5985 // The old code only cared about the logical activity, not translated.
5986
5987 if (m_Activity == NewActivity)
5988 {
5989 return;
5990 }
5991
5992 // Don't do this if I'm playing a transition, unless it's ACT_RESET.
5993 if ( NewActivity != ACT_RESET && m_Activity == ACT_TRANSITION && m_IdealActivity != ACT_DO_NOT_DISTURB )
5994 {
5995 return;
5996 }
5997
5998 if (ai_sequence_debug.GetBool() == true && (m_debugOverlays & OVERLAY_NPC_SELECTED_BIT))
5999 {
6000 DevMsg("SetActivity : %s: %s -> %s\n", GetClassname(), GetActivityName(GetActivity()), GetActivityName(NewActivity));
6001 }
6002
6003 if ( !GetModelPtr() )
6004 return;
6005
6006 // In case someone calls this with something other than the ideal activity.
6007 m_IdealActivity = NewActivity;
6008
6009 // Resolve to ideals and apply directly, skipping transitions.
6010 ResolveActivityToSequence(m_IdealActivity, m_nIdealSequence, m_IdealTranslatedActivity, m_IdealWeaponActivity);
6011
6012 //DevMsg("%s: SLAM %s -> %s\n", GetClassname(), GetSequenceName(GetSequence()), GetSequenceName(m_nIdealSequence));
6013
6014 SetActivityAndSequence(m_IdealActivity, m_nIdealSequence, m_IdealTranslatedActivity, m_IdealWeaponActivity);
6015}
6016
6017
6018//-----------------------------------------------------------------------------
6019// Purpose: Sets the activity that we would like to transition toward.
6020// Input : NewActivity -
6021//-----------------------------------------------------------------------------
6022void CAI_BaseNPC::SetIdealActivity( Activity NewActivity )
6023{
6024 // ignore if it's an ACT_TRANSITION, it means somewhere we're setting IdealActivity with a bogus intermediate value
6025 if (NewActivity == ACT_TRANSITION)
6026 {
6027 Assert( 0 );
6028 return;
6029 }
6030
6031 if (ai_sequence_debug.GetBool() == true && (m_debugOverlays & OVERLAY_NPC_SELECTED_BIT))
6032 {
6033 DevMsg("SetIdealActivity : %s: %s -> %s\n", GetClassname(), GetActivityName(GetActivity()), GetActivityName(NewActivity));
6034 }
6035
6036
6037 if (NewActivity == ACT_RESET)
6038 {
6039 // They probably meant to call SetActivity(ACT_RESET)... we'll fix it for them.
6040 SetActivity(ACT_RESET);
6041 return;
6042 }
6043
6044 m_IdealActivity = NewActivity;
6045
6046 if( NewActivity == ACT_DO_NOT_DISTURB )
6047 {
6048 // Don't resolve anything! Leave it the way the user has it right now.
6049 return;
6050 }
6051
6052 if ( !GetModelPtr() )
6053 return;
6054
6055 // Perform translation in case we need to change sequences within a single activity,
6056 // such as between a standing idle and a crouching idle.
6057 ResolveActivityToSequence(m_IdealActivity, m_nIdealSequence, m_IdealTranslatedActivity, m_IdealWeaponActivity);
6058}
6059
6060
6061//-----------------------------------------------------------------------------
6062// Purpose: Moves toward the ideal activity through any transition sequences.
6063//-----------------------------------------------------------------------------
6064void CAI_BaseNPC::AdvanceToIdealActivity(void)
6065{
6066 // If there is a transition sequence between the current sequence and the ideal sequence...
6067 int nNextSequence = FindTransitionSequence(GetSequence(), m_nIdealSequence, NULL);
6068 if (nNextSequence != -1)
6069 {
6070 // We found a transition sequence or possibly went straight to
6071 // the ideal sequence.
6072 if (nNextSequence != m_nIdealSequence)
6073 {
6074// DevMsg("%s: TRANSITION %s -> %s -> %s\n", GetClassname(), GetSequenceName(GetSequence()), GetSequenceName(nNextSequence), GetSequenceName(m_nIdealSequence));
6075
6076 Activity eWeaponActivity = ACT_TRANSITION;
6077 Activity eTranslatedActivity = ACT_TRANSITION;
6078
6079 // Figure out if the transition sequence has an associated activity that
6080 // we can use for our weapon. Do activity translation also.
6081 Activity eTransitionActivity = GetSequenceActivity(nNextSequence);
6082 if (eTransitionActivity != ACT_INVALID)
6083 {
6084 int nDiscard;
6085 ResolveActivityToSequence(eTransitionActivity, nDiscard, eTranslatedActivity, eWeaponActivity);
6086 }
6087
6088 // Set activity and sequence to the transition stuff. Set the activity to ACT_TRANSITION
6089 // so we know we're in a transition.
6090 SetActivityAndSequence(ACT_TRANSITION, nNextSequence, eTranslatedActivity, eWeaponActivity);
6091 }
6092 else
6093 {
6094 //DevMsg("%s: IDEAL %s -> %s\n", GetClassname(), GetSequenceName(GetSequence()), GetSequenceName(m_nIdealSequence));
6095
6096 // Set activity and sequence to the ideal stuff that was set up in MaintainActivity.
6097 SetActivityAndSequence(m_IdealActivity, m_nIdealSequence, m_IdealTranslatedActivity, m_IdealWeaponActivity);
6098 }
6099 }
6100 // Else go straight there to the ideal activity.
6101 else
6102 {
6103 //DevMsg("%s: Unable to get from sequence %s to %s!\n", GetClassname(), GetSequenceName(GetSequence()), GetSequenceName(m_nIdealSequence));
6104 SetActivity(m_IdealActivity);
6105 }
6106}
6107
6108
6109//-----------------------------------------------------------------------------
6110// Purpose: Tries to achieve our ideal animation state, playing any transition
6111// sequences that we need to play to get there.
6112//-----------------------------------------------------------------------------
6113void CAI_BaseNPC::MaintainActivity(void)
6114{
6115 AI_PROFILE_SCOPE( CAI_BaseNPC_MaintainActivity );
6116
6117 if ( m_lifeState == LIFE_DEAD )
6118 {
6119 // Don't maintain activities if we're daid.
6120 // Blame Speyrer
6121 return;
6122 }
6123
6124 if ((GetState() == NPC_STATE_SCRIPT))
6125 {
6126 // HACK: finish any transitions we might be playing before we yield control to the script
6127 if (GetActivity() != ACT_TRANSITION)
6128 {
6129 // Our animation state is being controlled by a script.
6130 return;
6131 }
6132 }
6133
6134 if( m_IdealActivity == ACT_DO_NOT_DISTURB || !GetModelPtr() )
6135 {
6136 return;
6137 }
6138
6139 // We may have work to do if we aren't playing our ideal activity OR if we
6140 // aren't playing our ideal sequence.
6141 if ((GetActivity() != m_IdealActivity) || (GetSequence() != m_nIdealSequence))
6142 {
6143 if (ai_sequence_debug.GetBool() == true && (m_debugOverlays & OVERLAY_NPC_SELECTED_BIT))
6144 {
6145 DevMsg("MaintainActivity %s : %s:%s -> %s:%s\n", GetClassname(),
6146 GetActivityName(GetActivity()), GetSequenceName(GetSequence()),
6147 GetActivityName(m_IdealActivity), GetSequenceName(m_nIdealSequence));
6148 }
6149
6150 bool bAdvance = false;
6151
6152 // If we're in a transition activity, see if we are done with the transition.
6153 if (GetActivity() == ACT_TRANSITION)
6154 {
6155 // If the current sequence is finished, try to go to the next one
6156 // closer to our ideal sequence.
6157 if (IsSequenceFinished())
6158 {
6159 bAdvance = true;
6160 }
6161 // Else a transition sequence is in progress, do nothing.
6162 }
6163 // Else get a specific sequence for the activity and try to transition to that.
6164 else
6165 {
6166 // Save off a target sequence and translated activities to apply when we finish
6167 // playing all the transitions and finally arrive at our ideal activity.
6168 ResolveActivityToSequence(m_IdealActivity, m_nIdealSequence, m_IdealTranslatedActivity, m_IdealWeaponActivity);
6169 bAdvance = true;
6170 }
6171
6172 if (bAdvance)
6173 {
6174 // Try to go to the next sequence closer to our ideal sequence.
6175 AdvanceToIdealActivity();
6176 }
6177 }
6178}
6179
6180
6181//-----------------------------------------------------------------------------
6182// Purpose: Returns true if our ideal activity has finished playing.
6183//-----------------------------------------------------------------------------
6184bool CAI_BaseNPC::IsActivityFinished( void )
6185{
6186 return (IsSequenceFinished() && (GetSequence() == m_nIdealSequence));
6187}
6188
6189//-----------------------------------------------------------------------------
6190// Purpose: Checks to see if the activity is one of the standard phase-matched movement activities
6191// Input : activity
6192//-----------------------------------------------------------------------------
6193bool CAI_BaseNPC::IsActivityMovementPhased( Activity activity )
6194{
6195 switch( activity )
6196 {
6197 case ACT_WALK:
6198 case ACT_WALK_AIM:
6199 case ACT_WALK_CROUCH:
6200 case ACT_WALK_CROUCH_AIM:
6201 case ACT_RUN:
6202 case ACT_RUN_AIM:
6203 case ACT_RUN_CROUCH:
6204 case ACT_RUN_CROUCH_AIM:
6205 case ACT_RUN_PROTECTED:
6206 return true;
6207 }
6208 return false;
6209}
6210
6211//-----------------------------------------------------------------------------
6212// Purpose:
6213//-----------------------------------------------------------------------------
6214void CAI_BaseNPC::OnChangeActivity( Activity eNewActivity )
6215{
6216 if ( eNewActivity == ACT_RUN ||
6217 eNewActivity == ACT_RUN_AIM ||
6218 eNewActivity == ACT_WALK )
6219 {
6220 Stand();
6221 }
6222}
6223
6224//=========================================================
6225// SetSequenceByName
6226//=========================================================
6227void CAI_BaseNPC::SetSequenceByName( char *szSequence )
6228{
6229 int iSequence = LookupSequence( szSequence );
6230
6231 if ( iSequence > ACTIVITY_NOT_AVAILABLE )
6232 SetSequenceById( iSequence );
6233 else
6234 {
6235 DevWarning( 2, "%s has no sequence to match request\n", GetClassname(), szSequence );
6236 SetSequence( 0 ); // Set to the reset anim (if it's there)
6237 }
6238}
6239
6240//-----------------------------------------------------------------------------
6241
6242void CAI_BaseNPC::SetSequenceById( int iSequence )
6243{
6244 // Set to the desired anim, or default anim if the desired is not present
6245 if ( iSequence > ACTIVITY_NOT_AVAILABLE )
6246 {
6247 if ( GetSequence() != iSequence || !SequenceLoops() )
6248 {
6249 SetCycle( 0 );
6250 }
6251
6252 ResetSequence( iSequence ); // Set to the reset anim (if it's there)
6253 GetMotor()->RecalculateYawSpeed();
6254 }
6255 else
6256 {
6257 // Not available try to get default anim
6258 DevWarning( 2, "%s invalid sequence requested\n", GetClassname() );
6259 SetSequence( 0 ); // Set to the reset anim (if it's there)
6260 }
6261}
6262
6263//-----------------------------------------------------------------------------
6264// Purpose: Returns the target entity
6265// Input :
6266// Output :
6267//-----------------------------------------------------------------------------
6268CBaseEntity *CAI_BaseNPC::GetNavTargetEntity(void)
6269{
6270 if ( GetNavigator()->GetGoalType() == GOALTYPE_ENEMY )
6271 return m_hEnemy;
6272 else if ( GetNavigator()->GetGoalType() == GOALTYPE_TARGETENT )
6273 return m_hTargetEnt;
6274 return NULL;
6275}
6276
6277
6278//-----------------------------------------------------------------------------
6279// Purpose: returns zero if the caller can jump from
6280// vecStart to vecEnd ignoring collisions with pTarget
6281//
6282// if the throw fails, returns the distance
6283// that can be travelled before an obstacle is hit
6284//-----------------------------------------------------------------------------
6285#include "ai_initutils.h"
6286//#define _THROWDEBUG
6287float CAI_BaseNPC::ThrowLimit( const Vector &vecStart,
6288 const Vector &vecEnd,
6289 float fGravity,
6290 float fArcSize,
6291 const Vector &mins,
6292 const Vector &maxs,
6293 CBaseEntity *pTarget,
6294 Vector *jumpVel,
6295 CBaseEntity **pBlocker)
6296{
6297 // Get my jump velocity
6298 Vector rawJumpVel = CalcThrowVelocity(vecStart, vecEnd, fGravity, fArcSize);
6299 *jumpVel = rawJumpVel;
6300 Vector vecFrom = vecStart;
6301
6302 // Calculate the total time of the jump minus a tiny fraction
6303 float jumpTime = (vecStart - vecEnd).Length2D()/rawJumpVel.Length2D();
6304 float timeStep = jumpTime / 10.0;
6305
6306 Vector gravity = Vector(0,0,fGravity);
6307
6308 // this loop takes single steps to the goal.
6309 for (float flTime = 0 ; flTime < jumpTime-0.1 ; flTime += timeStep )
6310 {
6311 // Calculate my position after the time step (average velocity over this time step)
6312 Vector nextPos = vecFrom + (rawJumpVel - 0.5 * gravity * timeStep) * timeStep;
6313
6314 // If last time step make next position the target position
6315 if ((flTime + timeStep) > jumpTime)
6316 {
6317 nextPos = vecEnd;
6318 }
6319
6320 trace_t tr;
6321 AI_TraceHull( vecFrom, nextPos, mins, maxs, MASK_SOLID, this, COLLISION_GROUP_NONE, &tr );
6322
6323 if (tr.startsolid || tr.fraction < 1.0)
6324 {
6325 CBaseEntity *pEntity = tr.m_pEnt;
6326
6327 // If we hit the target we are good to go!
6328 if (pEntity == pTarget)
6329 {
6330 return 0;
6331 }
6332
6333#ifdef _THROWDEBUG
6334 NDebugOverlay::Line( vecFrom, nextPos, 255, 0, 0, true, 1.0 );
6335#endif
6336 // ----------------------------------------------------------
6337 // If blocked by an npc remember
6338 // ----------------------------------------------------------
6339 *pBlocker = pEntity;
6340
6341 // Return distance sucessfully traveled before block encountered
6342 return ((tr.endpos - vecStart).Length());
6343 }
6344#ifdef _THROWDEBUG
6345 else
6346 {
6347 NDebugOverlay::Line( vecFrom, nextPos, 255, 255, 255, true, 1.0 );
6348 }
6349#endif
6350
6351
6352 rawJumpVel = rawJumpVel - gravity * timeStep;
6353 vecFrom = nextPos;
6354 }
6355 return 0;
6356}
6357
6358
6359
6360//-----------------------------------------------------------------------------
6361// Purpose: Called to initialize or re-initialize the vphysics hull when the size
6362// of the NPC changes
6363//-----------------------------------------------------------------------------
6364void CAI_BaseNPC::SetupVPhysicsHull()
6365{
6366 if ( GetMoveType() == MOVETYPE_VPHYSICS || GetMoveType() == MOVETYPE_NONE )
6367 return;
6368
6369 if ( VPhysicsGetObject() )
6370 {
6371 // Disable collisions to get
6372 VPhysicsGetObject()->EnableCollisions(false);
6373 VPhysicsDestroyObject();
6374 }
6375 VPhysicsInitShadow( true, false );
6376 IPhysicsObject *pPhysObj = VPhysicsGetObject();
6377 if ( pPhysObj )
6378 {
6379 float mass = Studio_GetMass(GetModelPtr());
6380 if ( mass > 0 )
6381 {
6382 pPhysObj->SetMass( mass );
6383 }
6384#if _DEBUG
6385 else
6386 {
6387 DevMsg("Warning: %s has no physical mass\n", STRING(GetModelName()));
6388 }
6389#endif
6390 IPhysicsShadowController *pController = pPhysObj->GetShadowController();
6391 float avgsize = (WorldAlignSize().x + WorldAlignSize().y) * 0.5;
6392 pController->SetTeleportDistance( avgsize * 0.5 );
6393 m_bCheckContacts = true;
6394 }
6395}
6396
6397
6398// Check for problematic physics objects resting on this NPC.
6399// They can screw up his navigation, so attach a controller to
6400// help separate the NPC & physics when you encounter these.
6401ConVar ai_auto_contact_solver( "ai_auto_contact_solver", "1" );
6402void CAI_BaseNPC::CheckPhysicsContacts()
6403{
6404 if ( gpGlobals->frametime <= 0.0f || !ai_auto_contact_solver.GetBool() )
6405 return;
6406
6407 m_bCheckContacts = false;
6408 if ( GetMoveType() == MOVETYPE_STEP && VPhysicsGetObject())
6409 {
6410 IPhysicsObject *pPhysics = VPhysicsGetObject();
6411 IPhysicsFrictionSnapshot *pSnapshot = pPhysics->CreateFrictionSnapshot();
6412 CBaseEntity *pGroundEntity = GetGroundEntity();
6413 float heightCheck = GetAbsOrigin().z + GetHullMaxs().z;
6414 Vector npcVel;
6415 pPhysics->GetVelocity( &npcVel, NULL );
6416 CBaseEntity *pOtherEntity = NULL;
6417 bool createSolver = false;
6418 float solverTime = 0.0f;
6419 while ( pSnapshot->IsValid() )
6420 {
6421 IPhysicsObject *pOther = pSnapshot->GetObject(1);
6422 pOtherEntity = static_cast<CBaseEntity *>(pOther->GetGameData());
6423
6424 if ( pOtherEntity && pGroundEntity != pOtherEntity )
6425 {
6426 float otherMass = PhysGetEntityMass(pOtherEntity);
6427
6428 if ( pOtherEntity->GetMoveType() == MOVETYPE_VPHYSICS && pOther->IsMoveable() &&
6429 otherMass < VPHYSICS_LARGE_OBJECT_MASS )
6430 {
6431 Assert( !NPCPhysics_SolverExists(this, pOtherEntity) );
6432 m_bCheckContacts = true;
6433 Vector vel, point;
6434 pOther->GetVelocity( &vel, NULL );
6435 pSnapshot->GetContactPoint( point );
6436
6437 // compare the relative velocity
6438 vel -= npcVel;
6439
6440 // slow moving object probably won't clear itself.
6441 // Either set ignore, or disable collisions entirely
6442 if ( vel.LengthSqr() < 5.0f*5.0f )
6443 {
6444 float topdist = fabs(point.z-heightCheck);
6445 // 4 seconds to ignore this for nav
6446 solverTime = 4.0f;
6447 if ( topdist < 2.0f )
6448 {
6449 // Resting on my head so disable collisions for a bit
6450 solverTime = 0.5f; // UNDONE: Tune
6451 if ( pOther->GetGameFlags() & FVPHYSICS_PLAYER_HELD )
6452 {
6453 // player is being a monkey
6454 solverTime = 0.25f;
6455 }
6456
6457 //Msg("Dropping %s from %s\n", pOtherEntity->GetClassname(), GetClassname() );
6458 createSolver = true;
6459 break;
6460 }
6461 }
6462 }
6463 }
6464 pSnapshot->NextFrictionData();
6465 }
6466 pPhysics->DestroyFrictionSnapshot( pSnapshot );
6467 if ( createSolver )
6468 {
6469 // turn collisions back on once we've been separated for enough time
6470 NPCPhysics_CreateSolver( this, pOtherEntity, true, solverTime );
6471 pPhysics->RecheckContactPoints();
6472 }
6473 }
6474}
6475
6476void CAI_BaseNPC::StartTouch( CBaseEntity *pOther )
6477{
6478 BaseClass::StartTouch(pOther);
6479
6480 if ( pOther->GetMoveType() == MOVETYPE_VPHYSICS )
6481 {
6482 m_bCheckContacts = true;
6483 }
6484}
6485
6486//-----------------------------------------------------------------------------
6487// Purpose: To be called instead of UTIL_SetSize, so pathfinding hull
6488// and actual hull agree
6489// Input :
6490// Output :
6491//-----------------------------------------------------------------------------
6492void CAI_BaseNPC::SetHullSizeNormal( bool force )
6493{
6494 if ( m_fIsUsingSmallHull || force )
6495 {
6496 UTIL_SetSize(this, GetHullMins(),GetHullMaxs());
6497 m_fIsUsingSmallHull = false;
6498 if ( VPhysicsGetObject() )
6499 {
6500 SetupVPhysicsHull();
6501 }
6502 }
6503}
6504
6505//-----------------------------------------------------------------------------
6506// Purpose: To be called instead of UTIL_SetSize, so pathfinding hull
6507// and actual hull agree
6508// Input :
6509// Output :
6510//-----------------------------------------------------------------------------
6511bool CAI_BaseNPC::SetHullSizeSmall( bool force )
6512{
6513 if ( !m_fIsUsingSmallHull || force )
6514 {
6515 UTIL_SetSize(this, NAI_Hull::SmallMins(GetHullType()),NAI_Hull::SmallMaxs(GetHullType()));
6516 m_fIsUsingSmallHull = true;
6517 if ( VPhysicsGetObject() )
6518 {
6519 SetupVPhysicsHull();
6520 }
6521 }
6522 return true;
6523}
6524
6525//-----------------------------------------------------------------------------
6526// Checks to see that the nav hull is valid for the NPC
6527//-----------------------------------------------------------------------------
6528bool CAI_BaseNPC::IsNavHullValid() const
6529{
6530 Assert( GetSolid() != SOLID_BSP );
6531
6532 Vector hullMin = GetHullMins();
6533 Vector hullMax = GetHullMaxs();
6534 Vector vecMins, vecMaxs;
6535 if ( GetSolid() == SOLID_BBOX )
6536 {
6537 vecMins = WorldAlignMins();
6538 vecMaxs = WorldAlignMaxs();
6539 }
6540 else if ( GetSolid() == SOLID_VPHYSICS )
6541 {
6542 Assert( VPhysicsGetObject() );
6543 const CPhysCollide *pPhysCollide = VPhysicsGetObject()->GetCollide();
6544 physcollision->CollideGetAABB( vecMins, vecMaxs, pPhysCollide, GetAbsOrigin(), GetAbsAngles() );
6545 vecMins -= GetAbsOrigin();
6546 vecMaxs -= GetAbsOrigin();
6547 }
6548 else
6549 {
6550 vecMins = hullMin;
6551 vecMaxs = hullMax;
6552 }
6553
6554 if ( (hullMin.x > vecMins.x) || (hullMax.x < vecMaxs.x) ||
6555 (hullMin.y > vecMins.y) || (hullMax.y < vecMaxs.y) ||
6556 (hullMin.z > vecMins.z) || (hullMax.z < vecMaxs.z) )
6557 {
6558 return false;
6559 }
6560
6561 return true;
6562}
6563
6564
6565//=========================================================
6566// NPCInit - after a npc is spawned, it needs to
6567// be dropped into the world, checked for mobility problems,
6568// and put on the proper path, if any. This function does
6569// all of those things after the npc spawns. Any
6570// initialization that should take place for all npcs
6571// goes here.
6572//=========================================================
6573void CAI_BaseNPC::NPCInit ( void )
6574{
6575 if (!g_pGameRules->FAllowNPCs())
6576 {
6577 UTIL_Remove( this );
6578 return;
6579 }
6580
6581 if( IsWaitingToRappel() )
6582 {
6583 // If this guy's supposed to rappel, keep him from
6584 // falling to the ground when he spawns.
6585 AddFlag( FL_FLY );
6586 }
6587
6588#ifdef _DEBUG
6589 // Make sure that the bounding box is appropriate for the hull size...
6590 // FIXME: We can't test vphysics objects because NPCInit occurs before VPhysics is set up
6591 if ( GetSolid() != SOLID_VPHYSICS && !IsSolidFlagSet(FSOLID_NOT_SOLID) )
6592 {
6593 if ( !IsNavHullValid() )
6594 {
6595 Warning("NPC Entity %s (%d) has a bounding box which extends outside its nav box!\n",
6596 STRING(m_iClassname), entindex() );
6597 }
6598 }
6599#endif
6600
6601 // Set fields common to all npcs
6602 AddFlag( FL_AIMTARGET | FL_NPC );
6603 AddSolidFlags( FSOLID_NOT_STANDABLE );
6604
6605 m_flOriginalYaw = GetAbsAngles().y;
6606
6607 SetBlocksLOS( false );
6608
6609 SetGravity(1.0); // Don't change
6610 m_takedamage = DAMAGE_YES;
6611 GetMotor()->SetIdealYaw( GetLocalAngles().y );
6612 m_iMaxHealth = m_iHealth;
6613 m_lifeState = LIFE_ALIVE;
6614 SetIdealState( NPC_STATE_IDLE );// Assume npc will be idle, until proven otherwise
6615 SetIdealActivity( ACT_IDLE );
6616 SetActivity( ACT_IDLE );
6617
6618#ifdef HL1_DLL
6619 SetDeathPose( ACT_INVALID );
6620#endif
6621
6622 ClearCommandGoal();
6623
6624 ClearSchedule();
6625 GetNavigator()->ClearGoal();
6626 InitBoneControllers( ); // FIX: should be done in Spawn
6627 if ( GetModelPtr() )
6628 {
6629 ResetActivityIndexes();
6630 ResetEventIndexes();
6631 }
6632
6633 SetHintNode( NULL );
6634
6635 m_afMemory = MEMORY_CLEAR;
6636
6637 SetEnemy( NULL );
6638
6639 m_flDistTooFar = 1024.0;
6640 SetDistLook( 2048.0 );
6641
6642 if ( HasSpawnFlags( SF_NPC_LONG_RANGE ) )
6643 {
6644 m_flDistTooFar = 1e9f;
6645 SetDistLook( 6000.0 );
6646 }
6647
6648 // Clear conditions
6649 m_Conditions.ClearAllBits();
6650
6651 // set eye position
6652 SetDefaultEyeOffset();
6653
6654 // Only give weapon of allowed to have one
6655 if (CapabilitiesGet() & bits_CAP_USE_WEAPONS)
6656 { // Does this npc spawn with a weapon
6657 if ( m_spawnEquipment != NULL_STRING && strcmp(STRING(m_spawnEquipment), "0"))
6658 {
6659 CBaseCombatWeapon *pWeapon = Weapon_Create( STRING(m_spawnEquipment) );
6660 if ( pWeapon )
6661 {
6662 // If I have a name, make my weapon match it with "_weapon" appended
6663 if ( GetEntityName() != NULL_STRING )
6664 {
6665 pWeapon->SetName( AllocPooledString(UTIL_VarArgs("%s_weapon", GetEntityName())) );
6666 }
6667 Weapon_Equip( pWeapon );
6668 }
6669 }
6670 }
6671
6672 // Robin: Removed this, since it stomps the weapon's settings, and it's stomped
6673 // by OnUpdateShotRegulator() as soon as they finish firing the first time.
6674 //GetShotRegulator()->SetParameters( 2, 6, 0.3f, 0.8f );
6675
6676 SetUse ( &CAI_BaseNPC::NPCUse );
6677
6678 // NOTE: Can't call NPC Init Think directly... logic changed about
6679 // what time it is when worldspawn happens..
6680
6681 // We must put off the rest of our initialization
6682 // until we're sure everything else has had a chance to spawn. Otherwise
6683 // we may try to reference entities that haven't spawned yet.(sjb)
6684 SetThink( &CAI_BaseNPC::NPCInitThink );
6685 SetNextThink( gpGlobals->curtime + 0.01f );
6686
6687 ForceGatherConditions();
6688
6689 // HACKHACK: set up a pre idle animation
6690 // NOTE: Must do this before CreateVPhysics() so bone followers have the correct initial positions.
6691 if ( HasSpawnFlags( SF_NPC_WAIT_FOR_SCRIPT ) )
6692 {
6693 const char *pStartSequence = CAI_ScriptedSequence::GetSpawnPreIdleSequenceForScript( this );
6694 if ( pStartSequence )
6695 {
6696 SetSequence( LookupSequence( pStartSequence ) );
6697 }
6698 }
6699
6700 CreateVPhysics();
6701
6702 if ( HasSpawnFlags( SF_NPC_START_EFFICIENT ) )
6703 {
6704 SetEfficiency( AIE_EFFICIENT );
6705 }
6706
6707 m_bFadeCorpse = ShouldFadeOnDeath();
6708
6709 m_GiveUpOnDeadEnemyTimer.Set( 0.75, 2.0 );
6710
6711 m_flTimeLastMovement = FLT_MAX;
6712
6713 m_flIgnoreDangerSoundsUntil = 0;
6714
6715 SetDeathPose( ACT_INVALID );
6716 SetDeathPoseFrame( 0 );
6717
6718 m_EnemiesSerialNumber = -1;
6719}
6720
6721//-----------------------------------------------------------------------------
6722
6723bool CAI_BaseNPC::CreateVPhysics()
6724{
6725 if ( IsAlive() && !VPhysicsGetObject() )
6726 {
6727 SetupVPhysicsHull();
6728 }
6729 return true;
6730}
6731
6732
6733//-----------------------------------------------------------------------------
6734// Set up the shot regulator based on the equipped weapon
6735//-----------------------------------------------------------------------------
6736void CAI_BaseNPC::OnUpdateShotRegulator( )
6737{
6738 CBaseCombatWeapon *pWeapon = GetActiveWeapon();
6739 if ( !pWeapon )
6740 return;
6741
6742 // Default values
6743 m_ShotRegulator.SetBurstInterval( pWeapon->GetFireRate(), pWeapon->GetFireRate() );
6744 m_ShotRegulator.SetBurstShotCountRange( pWeapon->GetMinBurst(), pWeapon->GetMaxBurst() );
6745 m_ShotRegulator.SetRestInterval( pWeapon->GetMinRestTime(), pWeapon->GetMaxRestTime() );
6746
6747 // Let the behavior have a whack at it.
6748 if ( GetRunningBehavior() )
6749 {
6750 GetRunningBehavior()->OnUpdateShotRegulator();
6751 }
6752}
6753
6754
6755//-----------------------------------------------------------------------------
6756// Set up the shot regulator based on the equipped weapon
6757//-----------------------------------------------------------------------------
6758void CAI_BaseNPC::OnChangeActiveWeapon( CBaseCombatWeapon *pOldWeapon, CBaseCombatWeapon *pNewWeapon )
6759{
6760 BaseClass::OnChangeActiveWeapon( pOldWeapon, pNewWeapon );
6761
6762 // Shot regulator code
6763 if ( pNewWeapon )
6764 {
6765 OnUpdateShotRegulator();
6766 m_ShotRegulator.Reset( true );
6767 }
6768}
6769
6770//-----------------------------------------------------------------------------
6771// Purpose: Tests to see if NPC can holster their weapon (if animation exists to holster weapon)
6772// Output : true if holster weapon animation exists
6773//-----------------------------------------------------------------------------
6774bool CAI_BaseNPC::CanHolsterWeapon( void )
6775{
6776 int seq = SelectWeightedSequence( ACT_DISARM );
6777 return (seq >= 0);
6778}
6779
6780//-----------------------------------------------------------------------------
6781// Purpose:
6782//-----------------------------------------------------------------------------
6783int CAI_BaseNPC::HolsterWeapon( void )
6784{
6785 if ( IsWeaponHolstered() )
6786 return -1;
6787
6788 int iHolsterGesture = FindGestureLayer( ACT_DISARM );
6789 if ( iHolsterGesture != -1 )
6790 return iHolsterGesture;
6791
6792 int iLayer = AddGesture( ACT_DISARM, true );
6793 //iLayer = AddGesture( ACT_GESTURE_DISARM, true );
6794
6795 if (iLayer != -1)
6796 {
6797 // Prevent firing during the holster / unholster
6798 float flDuration = GetLayerDuration( iLayer );
6799 m_ShotRegulator.FireNoEarlierThan( gpGlobals->curtime + flDuration + 0.5 );
6800
6801 if( m_iDesiredWeaponState == DESIREDWEAPONSTATE_HOLSTERED_DESTROYED )
6802 {
6803 m_iDesiredWeaponState = DESIREDWEAPONSTATE_CHANGING_DESTROY;
6804 }
6805 else
6806 {
6807 m_iDesiredWeaponState = DESIREDWEAPONSTATE_CHANGING;
6808 }
6809
6810 // Make sure we don't try to reload while we're holstering
6811 ClearCondition(COND_LOW_PRIMARY_AMMO);
6812 ClearCondition(COND_NO_PRIMARY_AMMO);
6813 ClearCondition(COND_NO_SECONDARY_AMMO);
6814 }
6815
6816 return iLayer;
6817}
6818
6819//-----------------------------------------------------------------------------
6820// Purpose:
6821//-----------------------------------------------------------------------------
6822int CAI_BaseNPC::UnholsterWeapon( void )
6823{
6824 if ( !IsWeaponHolstered() )
6825 return -1;
6826
6827 int iHolsterGesture = FindGestureLayer( ACT_ARM );
6828 if ( iHolsterGesture != -1 )
6829 return iHolsterGesture;
6830
6831 // Deploy the first weapon you can find
6832 for (int i = 0; i < WeaponCount(); i++)
6833 {
6834 if ( GetWeapon( i ))
6835 {
6836 SetActiveWeapon( GetWeapon(i) );
6837
6838 int iLayer = AddGesture( ACT_ARM, true );
6839 //iLayer = AddGesture( ACT_GESTURE_ARM, true );
6840
6841 if (iLayer != -1)
6842 {
6843 // Prevent firing during the holster / unholster
6844 float flDuration = GetLayerDuration( iLayer );
6845 m_ShotRegulator.FireNoEarlierThan( gpGlobals->curtime + flDuration + 0.5 );
6846
6847 m_iDesiredWeaponState = DESIREDWEAPONSTATE_CHANGING;
6848 }
6849
6850 // Refill the clip
6851 if ( GetActiveWeapon()->UsesClipsForAmmo1() )
6852 {
6853 GetActiveWeapon()->m_iClip1 = GetActiveWeapon()->GetMaxClip1();
6854 }
6855
6856 // Make sure we don't try to reload while we're unholstering
6857 ClearCondition(COND_LOW_PRIMARY_AMMO);
6858 ClearCondition(COND_NO_PRIMARY_AMMO);
6859 ClearCondition(COND_NO_SECONDARY_AMMO);
6860
6861 return iLayer;
6862 }
6863 }
6864
6865 return -1;
6866}
6867
6868//-----------------------------------------------------------------------------
6869// Purpose:
6870//-----------------------------------------------------------------------------
6871void CAI_BaseNPC::InputHolsterWeapon( inputdata_t &inputdata )
6872{
6873 m_iDesiredWeaponState = DESIREDWEAPONSTATE_HOLSTERED;
6874}
6875
6876//-----------------------------------------------------------------------------
6877// Purpose:
6878//-----------------------------------------------------------------------------
6879void CAI_BaseNPC::InputHolsterAndDestroyWeapon( inputdata_t &inputdata )
6880{
6881 m_iDesiredWeaponState = DESIREDWEAPONSTATE_HOLSTERED_DESTROYED;
6882}
6883
6884//-----------------------------------------------------------------------------
6885// Purpose:
6886//-----------------------------------------------------------------------------
6887void CAI_BaseNPC::InputUnholsterWeapon( inputdata_t &inputdata )
6888{
6889 m_iDesiredWeaponState = DESIREDWEAPONSTATE_UNHOLSTERED;
6890}
6891
6892//-----------------------------------------------------------------------------
6893// Purpose:
6894//-----------------------------------------------------------------------------
6895bool CAI_BaseNPC::IsWeaponHolstered( void )
6896{
6897 if( !GetActiveWeapon() )
6898 return true;
6899
6900 if( GetActiveWeapon()->IsEffectActive(EF_NODRAW) )
6901 return true;
6902
6903 return false;
6904}
6905
6906//-----------------------------------------------------------------------------
6907// Purpose:
6908//-----------------------------------------------------------------------------
6909bool CAI_BaseNPC::IsWeaponStateChanging( void )
6910{
6911 return ( m_iDesiredWeaponState == DESIREDWEAPONSTATE_CHANGING || m_iDesiredWeaponState == DESIREDWEAPONSTATE_CHANGING_DESTROY );
6912}
6913
6914//-----------------------------------------------------------------------------
6915// Set up the shot regulator based on the equipped weapon
6916//-----------------------------------------------------------------------------
6917void CAI_BaseNPC::OnRangeAttack1()
6918{
6919 SetLastAttackTime( gpGlobals->curtime );
6920
6921 // Houston, there is a problem!
6922 AssertOnce( GetShotRegulator()->ShouldShoot() );
6923
6924 m_ShotRegulator.OnFiredWeapon();
6925 if ( m_ShotRegulator.IsInRestInterval() )
6926 {
6927 OnUpdateShotRegulator();
6928 }
6929
6930 SetNextAttack( m_ShotRegulator.NextShotTime() );
6931}
6932
6933
6934//-----------------------------------------------------------------------------
6935// Purpose: Initialze the relationship table from the keyvalues
6936// Input :
6937// Output :
6938//-----------------------------------------------------------------------------
6939void CAI_BaseNPC::InitRelationshipTable(void)
6940{
6941 AddRelationship( STRING( m_RelationshipString ), NULL );
6942}
6943
6944
6945//-----------------------------------------------------------------------------
6946// Purpose:
6947//-----------------------------------------------------------------------------
6948void CAI_BaseNPC::AddRelationship( const char *pszRelationship, CBaseEntity *pActivator )
6949{
6950 // Parse the keyvalue data
6951 char parseString[1000];
6952 Q_strncpy(parseString, pszRelationship, sizeof(parseString));
6953
6954 // Look for an entity string
6955 char *entityString = strtok(parseString," ");
6956 while (entityString)
6957 {
6958 // Get the disposition
6959 char *dispositionString = strtok(NULL," ");
6960 Disposition_t disposition = D_NU;
6961 if ( dispositionString )
6962 {
6963 if (!stricmp(dispositionString,"D_HT"))
6964 {
6965 disposition = D_HT;
6966 }
6967 else if (!stricmp(dispositionString,"D_FR"))
6968 {
6969 disposition = D_FR;
6970 }
6971 else if (!stricmp(dispositionString,"D_LI"))
6972 {
6973 disposition = D_LI;
6974 }
6975 else if (!stricmp(dispositionString,"D_NU"))
6976 {
6977 disposition = D_NU;
6978 }
6979 else
6980 {
6981 disposition = D_NU;
6982 Warning( "***ERROR***\nBad relationship type (%s) to unknown entity (%s)!\n", dispositionString,entityString );
6983 Assert( 0 );
6984 return;
6985 }
6986 }
6987 else
6988 {
6989 Warning("Can't parse relationship info (%s)\n", pszRelationship );
6990 Assert(0);
6991 return;
6992 }
6993
6994 // Get the priority
6995 char *priorityString = strtok(NULL," ");
6996 int priority = ( priorityString ) ? atoi(priorityString) : DEF_RELATIONSHIP_PRIORITY;
6997
6998 bool bFoundEntity = false;
6999
7000 // Try to get pointer to an entity of this name
7001 CBaseEntity *entity = gEntList.FindEntityByName( NULL, entityString );
7002 while( entity )
7003 {
7004 // make sure you catch all entities of this name.
7005 bFoundEntity = true;
7006 AddEntityRelationship(entity, disposition, priority );
7007 entity = gEntList.FindEntityByName( entity, entityString );
7008 }
7009
7010 if( !bFoundEntity )
7011 {
7012 // Need special condition for player as we can only have one
7013 if (!stricmp("player", entityString) || !stricmp("!player", entityString))
7014 {
7015 AddClassRelationship( CLASS_PLAYER, disposition, priority );
7016 }
7017 // Otherwise try to create one too see if a valid classname and get class type
7018 else
7019 {
7020 // HACKHACK:
7021 CBaseEntity *pEntity = CanCreateEntityClass( entityString ) ? CreateEntityByName( entityString ) : NULL;
7022 if (pEntity)
7023 {
7024 AddClassRelationship( pEntity->Classify(), disposition, priority );
7025 UTIL_RemoveImmediate(pEntity);
7026 }
7027 else
7028 {
7029 DevWarning( "Couldn't set relationship to unknown entity or class (%s)!\n", entityString );
7030 }
7031 }
7032 }
7033 // Check for another entity in the list
7034 entityString = strtok(NULL," ");
7035 }
7036}
7037
7038//-----------------------------------------------------------------------------
7039//-----------------------------------------------------------------------------
7040void CAI_BaseNPC::AddEntityRelationship( CBaseEntity *pEntity, Disposition_t nDisposition, int nPriority )
7041{
7042#if 0
7043 ForceGatherConditions();
7044#endif
7045 BaseClass::AddEntityRelationship( pEntity, nDisposition, nPriority );
7046}
7047
7048//-----------------------------------------------------------------------------
7049//-----------------------------------------------------------------------------
7050void CAI_BaseNPC::AddClassRelationship( Class_T nClass, Disposition_t nDisposition, int nPriority )
7051{
7052#if 0
7053 ForceGatherConditions();
7054#endif
7055 BaseClass::AddClassRelationship( nClass, nDisposition, nPriority );
7056}
7057
7058//=========================================================
7059// NPCInitThink - Calls StartNPC. Startnpc is
7060// virtual, but this function cannot be
7061//=========================================================
7062void CAI_BaseNPC::NPCInitThink ( void )
7063{
7064 // Initialize the relationship table
7065 InitRelationshipTable();
7066
7067 StartNPC();
7068
7069 PostNPCInit();
7070
7071 if( GetSleepState() == AISS_AUTO_PVS )
7072 {
7073 // This code is a bit wonky, but it makes it easier for level designers to
7074 // select this option in Hammer. So we set a sleep flag to indicate the choice,
7075 // and then set the sleep state to awake (normal)
7076 AddSleepFlags( AI_SLEEP_FLAG_AUTO_PVS );
7077 SetSleepState( AISS_AWAKE );
7078 }
7079
7080 if( GetSleepState() == AISS_AUTO_PVS_AFTER_PVS )
7081 {
7082 AddSleepFlags( AI_SLEEP_FLAG_AUTO_PVS_AFTER_PVS );
7083 SetSleepState( AISS_AWAKE );
7084 }
7085
7086 if ( GetSleepState() > AISS_AWAKE )
7087 {
7088 Sleep();
7089 }
7090
7091 m_flLastRealThinkTime = gpGlobals->curtime;
7092}
7093
7094//=========================================================
7095// StartNPC - final bit of initization before a npc
7096// is turned over to the AI.
7097//=========================================================
7098void CAI_BaseNPC::StartNPC( void )
7099{
7100 // Raise npc off the floor one unit, then drop to floor
7101 if ( (GetMoveType() != MOVETYPE_FLY) && (GetMoveType() != MOVETYPE_FLYGRAVITY) &&
7102 !(CapabilitiesGet() & bits_CAP_MOVE_FLY) &&
7103 !HasSpawnFlags( SF_NPC_FALL_TO_GROUND ) && !IsWaitingToRappel() && !GetMoveParent() )
7104 {
7105 Vector origin = GetLocalOrigin();
7106
7107 if (!GetMoveProbe()->FloorPoint( origin + Vector(0, 0, 0.1), MASK_NPCSOLID, 0, -2048, &origin ))
7108 {
7109 Warning( "NPC %s stuck in wall--level design error at (%.2f %.2f %.2f)\n", GetClassname(), GetAbsOrigin().x, GetAbsOrigin().y, GetAbsOrigin().z );
7110 if ( g_pDeveloper->GetInt() > 1 )
7111 {
7112 m_debugOverlays |= OVERLAY_BBOX_BIT;
7113 }
7114 }
7115
7116 SetLocalOrigin( origin );
7117 }
7118 else
7119 {
7120 SetGroundEntity( NULL );
7121 }
7122
7123 if ( m_target != NULL_STRING )// this npc has a target
7124 {
7125 // Find the npc's initial target entity, stash it
7126 SetGoalEnt( gEntList.FindEntityByName( NULL, m_target ) );
7127
7128 if ( !GetGoalEnt() )
7129 {
7130 Warning( "ReadyNPC()--%s couldn't find target %s\n", GetClassname(), STRING(m_target));
7131 }
7132 else
7133 {
7134 StartTargetHandling( GetGoalEnt() );
7135 }
7136 }
7137
7138 //SetState ( m_IdealNPCState );
7139 //SetActivity ( m_IdealActivity );
7140
7141 InitSquad();
7142
7143 //---------------------------------
7144 //
7145 // Spread think times of simultaneously spawned NPCs so that they don't all happen at the same time
7146 //
7147 // Think distribution based on spawn order is:
7148 //
7149 // Tick offset Think time Spawn order
7150 // 0 0 1
7151 // 1 0.015 13
7152 // 2 0.03 5
7153 // 3 0.045 9
7154 // 4 0.06 18
7155 // 5 0.075 3
7156 // 6 0.09 15
7157 // 7 0.105 11
7158 // 8 0.12 7
7159 // 9 0.135 17
7160 // 10 0.15 2
7161 // 11 0.165 14
7162 // 12 0.18 6
7163 // 13 0.195 19
7164 // 14 0.21 10
7165 // 15 0.225 4
7166 // 16 0.24 16
7167 // 17 0.255 12
7168 // 18 0.27 8
7169 // 19 0.285 20
7170
7171
7172 // If this NPC is spawning late in the game, just push through the rest of the initialization
7173 // start thinking right now. Some spread is added to handle triggered spawns that bring
7174 // a bunch of NPCs into the level
7175 SetThink ( &CAI_BaseNPC::CallNPCThink );
7176
7177 if ( gm_flTimeLastSpawn != gpGlobals->curtime )
7178 {
7179 gm_nSpawnedThisFrame = 0;
7180 gm_flTimeLastSpawn = gpGlobals->curtime;
7181 }
7182
7183 static const float nextThinkTimes[20] =
7184 {
7185 .0, .150, .075, .225, .030, .180, .120, .270, .045, .210, .105, .255, .015, .165, .090, .240, .135, .060, .195, .285
7186 };
7187
7188 SetNextThink( gpGlobals->curtime + nextThinkTimes[gm_nSpawnedThisFrame % 20] );
7189
7190 gm_nSpawnedThisFrame++;
7191
7192 //---------------------------------
7193
7194 m_ScriptArrivalActivity = AIN_DEF_ACTIVITY;
7195 m_strScriptArrivalSequence = NULL_STRING;
7196
7197 if ( HasSpawnFlags(SF_NPC_WAIT_FOR_SCRIPT) )
7198 {
7199 SetState( NPC_STATE_IDLE );
7200 m_Activity = m_IdealActivity;
7201 m_nIdealSequence = GetSequence();
7202 SetSchedule( SCHED_WAIT_FOR_SCRIPT );
7203 }
7204}
7205
7206//-----------------------------------------------------------------------------
7207
7208void CAI_BaseNPC::StartTargetHandling( CBaseEntity *pTargetEnt )
7209{
7210 // set the npc up to walk a path corner path.
7211 // !!!BUGBUG - this is a minor bit of a hack.
7212 // JAYJAY
7213
7214 // NPC will start turning towards his destination
7215 bool bIsFlying = (GetMoveType() == MOVETYPE_FLY) || (GetMoveType() == MOVETYPE_FLYGRAVITY);
7216 AI_NavGoal_t goal( GOALTYPE_PATHCORNER, pTargetEnt->GetAbsOrigin(),
7217 bIsFlying ? ACT_FLY : ACT_WALK,
7218 AIN_DEF_TOLERANCE, AIN_YAW_TO_DEST);
7219
7220 SetState( NPC_STATE_IDLE );
7221 SetSchedule( SCHED_IDLE_WALK );
7222
7223 if ( !GetNavigator()->SetGoal( goal ) )
7224 {
7225 DevWarning( 2, "Can't Create Route!\n" );
7226 }
7227}
7228
7229//-----------------------------------------------------------------------------
7230// Purpose: Connect my memory to the squad's
7231//-----------------------------------------------------------------------------
7232bool CAI_BaseNPC::InitSquad( void )
7233{
7234 // -------------------------------------------------------
7235 // If I form squads add me to a squad
7236 // -------------------------------------------------------
7237 if (!m_pSquad && ( CapabilitiesGet() & bits_CAP_SQUAD ))
7238 {
7239 if ( !m_SquadName )
7240 {
7241 DevMsg(2, "Found %s that isn't in a squad\n",GetClassname());
7242 }
7243 else
7244 {
7245 m_pSquad = g_AI_SquadManager.FindCreateSquad(this, m_SquadName);
7246 }
7247 }
7248
7249 return ( m_pSquad != NULL );
7250}
7251
7252//-----------------------------------------------------------------------------
7253// Purpose: Get the memory for this NPC
7254//-----------------------------------------------------------------------------
7255CAI_Enemies *CAI_BaseNPC::GetEnemies( void )
7256{
7257 return m_pEnemies;
7258}
7259
7260//-----------------------------------------------------------------------------
7261// Purpose: Remove this NPC's memory
7262//-----------------------------------------------------------------------------
7263void CAI_BaseNPC::RemoveMemory( void )
7264{
7265 delete m_pEnemies;
7266}
7267
7268//-----------------------------------------------------------------------------
7269// Purpose:
7270//-----------------------------------------------------------------------------
7271void CAI_BaseNPC::TaskComplete( bool fIgnoreSetFailedCondition )
7272{
7273 EndTaskOverlay();
7274
7275 // Handy thing to use for debugging
7276 //if (IsCurSchedule(SCHED_PUT_HERE) &&
7277 // GetTask()->iTask == TASK_PUT_HERE)
7278 //{
7279 // int put_breakpoint_here = 5;
7280 //}
7281
7282 if ( fIgnoreSetFailedCondition || !HasCondition(COND_TASK_FAILED) )
7283 {
7284 SetTaskStatus( TASKSTATUS_COMPLETE );
7285 }
7286}
7287
7288void CAI_BaseNPC::TaskMovementComplete( void )
7289{
7290 switch( GetTaskStatus() )
7291 {
7292 case TASKSTATUS_NEW:
7293 case TASKSTATUS_RUN_MOVE_AND_TASK:
7294 SetTaskStatus( TASKSTATUS_RUN_TASK );
7295 break;
7296
7297 case TASKSTATUS_RUN_MOVE:
7298 TaskComplete();
7299 break;
7300
7301 case TASKSTATUS_RUN_TASK:
7302 // FIXME: find out how to safely restart movement
7303 //Warning( "Movement completed twice!\n" );
7304 //Assert( 0 );
7305 break;
7306
7307 case TASKSTATUS_COMPLETE:
7308 break;
7309 }
7310
7311 // JAY: Put this back in.
7312 // UNDONE: Figure out how much of the timestep was consumed by movement
7313 // this frame and restart the movement/schedule engine if necessary
7314 if ( m_scriptState != SCRIPT_CUSTOM_MOVE_TO_MARK )
7315 {
7316 SetIdealActivity( GetStoppedActivity() );
7317 }
7318
7319 // Advance past the last node (in case there is some event at this node)
7320 if ( GetNavigator()->IsGoalActive() )
7321 {
7322 GetNavigator()->AdvancePath();
7323 }
7324
7325 // Now clear the path, it's done.
7326 GetNavigator()->ClearGoal();
7327
7328 OnMovementComplete();
7329}
7330
7331
7332int CAI_BaseNPC::TaskIsRunning( void )
7333{
7334 if ( GetTaskStatus() != TASKSTATUS_COMPLETE &&
7335 GetTaskStatus() != TASKSTATUS_RUN_MOVE )
7336 return 1;
7337
7338 return 0;
7339}
7340
7341//-----------------------------------------------------------------------------
7342// Purpose:
7343// Input :
7344// Output :
7345//-----------------------------------------------------------------------------
7346void CAI_BaseNPC::TaskFail( AI_TaskFailureCode_t code )
7347{
7348 EndTaskOverlay();
7349
7350 // Handy tool for debugging
7351 //if (IsCurSchedule(SCHED_PUT_NAME_HERE))
7352 //{
7353 // int put_breakpoint_here = 5;
7354 //}
7355
7356 // If in developer mode save the fail text for debug output
7357 if (g_pDeveloper->GetInt())
7358 {
7359 m_failText = TaskFailureToString( code );
7360
7361 m_interuptSchedule = NULL;
7362 m_failedSchedule = GetCurSchedule();
7363
7364 if (m_debugOverlays & OVERLAY_TASK_TEXT_BIT)
7365 {
7366 DevMsg(this, AIMF_IGNORE_SELECTED, " TaskFail -> %s\n", m_failText );
7367 }
7368
7369 ADD_DEBUG_HISTORY( HISTORY_AI_DECISIONS, UTIL_VarArgs("%s(%d): TaskFail -> %s\n", GetDebugName(), entindex(), m_failText ) );
7370
7371 //AddTimedOverlay( fail_text, 5);
7372 }
7373
7374 m_ScheduleState.taskFailureCode = code;
7375 SetCondition(COND_TASK_FAILED);
7376 Forget( bits_MEMORY_TURNING );
7377}
7378
7379//------------------------------------------------------------------------------
7380// Purpose : Remember that this entity wasn't reachable
7381// Input :
7382// Output :
7383//------------------------------------------------------------------------------
7384void CAI_BaseNPC::RememberUnreachable(CBaseEntity *pEntity)
7385{
7386 if ( pEntity == GetEnemy() )
7387 {
7388 ForceChooseNewEnemy();
7389 }
7390
7391 const float NPC_UNREACHABLE_TIMEOUT = 3;
7392 // Only add to list if not already on it
7393 for (int i=m_UnreachableEnts.Size()-1;i>=0;i--)
7394 {
7395 // If record already exists just update mark time
7396 if (pEntity == m_UnreachableEnts[i].hUnreachableEnt)
7397 {
7398 m_UnreachableEnts[i].fExpireTime = gpGlobals->curtime + NPC_UNREACHABLE_TIMEOUT;
7399 m_UnreachableEnts[i].vLocationWhenUnreachable = pEntity->GetAbsOrigin();
7400 return;
7401 }
7402 }
7403
7404 // Add new unreachabe entity to list
7405 int nNewIndex = m_UnreachableEnts.AddToTail();
7406 m_UnreachableEnts[nNewIndex].hUnreachableEnt = pEntity;
7407 m_UnreachableEnts[nNewIndex].fExpireTime = gpGlobals->curtime + NPC_UNREACHABLE_TIMEOUT;
7408 m_UnreachableEnts[nNewIndex].vLocationWhenUnreachable = pEntity->GetAbsOrigin();
7409}
7410
7411//------------------------------------------------------------------------------
7412// Purpose : Returns true is entity was remembered as unreachable.
7413// After a time delay reachability is checked
7414// Input :
7415// Output :
7416//------------------------------------------------------------------------------
7417bool CAI_BaseNPC::IsUnreachable(CBaseEntity *pEntity)
7418{
7419 float UNREACHABLE_DIST_TOLERANCE_SQ = (120*120);
7420
7421 // Note that it's ok to remove elements while I'm iterating
7422 // as long as I iterate backwards and remove them using FastRemove
7423 for (int i=m_UnreachableEnts.Size()-1;i>=0;i--)
7424 {
7425 // Remove any dead elements
7426 if (m_UnreachableEnts[i].hUnreachableEnt == NULL)
7427 {
7428 m_UnreachableEnts.FastRemove(i);
7429 }
7430 else if (pEntity == m_UnreachableEnts[i].hUnreachableEnt)
7431 {
7432 // Test for reachablility on any elements that have timed out
7433 if ( gpGlobals->curtime > m_UnreachableEnts[i].fExpireTime ||
7434 pEntity->GetAbsOrigin().DistToSqr(m_UnreachableEnts[i].vLocationWhenUnreachable) > UNREACHABLE_DIST_TOLERANCE_SQ)
7435 {
7436 m_UnreachableEnts.FastRemove(i);
7437 return false;
7438 }
7439 return true;
7440 }
7441 }
7442 return false;
7443}
7444
7445bool CAI_BaseNPC::IsValidEnemy( CBaseEntity *pEnemy )
7446{
7447 CAI_BaseNPC *pEnemyNPC = pEnemy->MyNPCPointer();
7448 if ( pEnemyNPC && pEnemyNPC->CanBeAnEnemyOf( this ) == false )
7449 return false;
7450
7451 // Test our enemy filter
7452 if ( m_hEnemyFilter.Get()!= NULL && m_hEnemyFilter->PassesFilter( this, pEnemy ) == false )
7453 return false;
7454
7455 return true;
7456}
7457
7458
7459bool CAI_BaseNPC::CanBeAnEnemyOf( CBaseEntity *pEnemy )
7460{
7461 if ( GetSleepState() > AISS_WAITING_FOR_THREAT )
7462 return false;
7463
7464 return true;
7465}
7466
7467
7468//-----------------------------------------------------------------------------
7469// Purpose: Picks best enemy from my list of enemies
7470// Prefers reachable enemies over enemies that are unreachable,
7471// regardless of priority. For enemies that are both reachable or
7472// unreachable picks by priority. If priority is the same, picks
7473// by distance.
7474// Input :
7475// Output :
7476//-----------------------------------------------------------------------------
7477
7478CBaseEntity *CAI_BaseNPC::BestEnemy( void )
7479{
7480 AI_PROFILE_SCOPE( CAI_BaseNPC_BestEnemy );
7481 // TODO - may want to consider distance, attack types, back turned, etc.
7482
7483 CBaseEntity* pBestEnemy = NULL;
7484 int iBestDistSq = MAX_COORD_RANGE * MAX_COORD_RANGE;// so first visible entity will become the closest.
7485 int iBestPriority = -1000;
7486 bool bBestUnreachable = true; // Forces initial check
7487 ThreeState_t fBestSeen = TRS_NONE;
7488 ThreeState_t fBestVisible = TRS_NONE;
7489 int iDistSq;
7490 bool bUnreachable = false;
7491
7492 AIEnemiesIter_t iter;
7493
7494 DbgEnemyMsg( this, "BestEnemy() {\n" );
7495
7496 for( AI_EnemyInfo_t *pEMemory = GetEnemies()->GetFirst(&iter); pEMemory != NULL; pEMemory = GetEnemies()->GetNext(&iter) )
7497 {
7498 CBaseEntity *pEnemy = pEMemory->hEnemy;
7499
7500 if (!pEnemy || !pEnemy->IsAlive())
7501 {
7502 if ( pEnemy )
7503 DbgEnemyMsg( this, " %s rejected: dead\n", pEnemy->GetDebugName() );
7504 continue;
7505 }
7506
7507 if ( (pEnemy->GetFlags() & FL_NOTARGET) )
7508 {
7509 DbgEnemyMsg( this, " %s rejected: no target\n", pEnemy->GetDebugName() );
7510 continue;
7511 }
7512
7513 // UNDONE: Move relationship checks into IsValidEnemy?
7514 Disposition_t relation = IRelationType( pEnemy );
7515 if ( (relation != D_HT && relation != D_FR) )
7516 {
7517 DbgEnemyMsg( this, " %s rejected: no hate/fear\n", pEnemy->GetDebugName() );
7518 continue;
7519 }
7520
7521 if ( m_flAcceptableTimeSeenEnemy > 0.0 && pEMemory->timeLastSeen < m_flAcceptableTimeSeenEnemy )
7522 {
7523 DbgEnemyMsg( this, " %s rejected: old\n", pEnemy->GetDebugName() );
7524 continue;
7525 }
7526
7527 if ( pEMemory->timeValidEnemy > gpGlobals->curtime )
7528 {
7529 DbgEnemyMsg( this, " %s rejected: not yet valid\n", pEnemy->GetDebugName() );
7530 continue;
7531 }
7532
7533 // Skip enemies that have eluded me to prevent infinite loops
7534 if ( pEMemory->bEludedMe )
7535 {
7536 DbgEnemyMsg( this, " %s rejected: eluded\n", pEnemy->GetDebugName() );
7537 continue;
7538 }
7539
7540 // Skip enemies I fear that I've never seen. (usually seen through an enemy finder)
7541 if ( relation == D_FR && !pEMemory->bUnforgettable && pEMemory->timeFirstSeen == AI_INVALID_TIME )
7542 {
7543 DbgEnemyMsg( this, " %s rejected: feared, but never seen\n", pEnemy->GetDebugName() );
7544 continue;
7545 }
7546
7547 if ( !IsValidEnemy( pEnemy ) )
7548 {
7549 DbgEnemyMsg( this, " %s rejected: not valid\n", pEnemy->GetDebugName() );
7550 continue;
7551 }
7552
7553 // establish the reachability of this enemy
7554 bUnreachable = IsUnreachable(pEnemy);
7555
7556 // If best is reachable and current is unreachable, skip the unreachable enemy regardless of priority
7557 if (!bBestUnreachable && bUnreachable)
7558 {
7559 DbgEnemyMsg( this, " %s rejected: unreachable\n", pEnemy->GetDebugName() );
7560 continue;
7561 }
7562
7563 // If best is unreachable and current is reachable, always pick the current regardless of priority
7564 if (bBestUnreachable && !bUnreachable)
7565 {
7566 DbgEnemyMsg( this, " %s accepted (1)\n", pEnemy->GetDebugName() );
7567 if ( pBestEnemy )
7568 DbgEnemyMsg( this, " (%s displaced)\n", pBestEnemy->GetDebugName() );
7569
7570 iBestPriority = IRelationPriority ( pEnemy );
7571 iBestDistSq = (pEnemy->GetAbsOrigin() - GetAbsOrigin() ).LengthSqr();
7572 pBestEnemy = pEnemy;
7573 bBestUnreachable = bUnreachable;
7574 fBestSeen = TRS_NONE;
7575 fBestVisible = TRS_NONE;
7576 }
7577 // If both are unreachable or both are reachable, chose enemy based on priority and distance
7578 else if ( IRelationPriority( pEnemy ) > iBestPriority )
7579 {
7580 DbgEnemyMsg( this, " %s accepted\n", pEnemy->GetDebugName() );
7581 if ( pBestEnemy )
7582 DbgEnemyMsg( this, " (%s displaced due to priority, %d > %d )\n", pBestEnemy->GetDebugName(), IRelationPriority( pEnemy ), iBestPriority );
7583 // this entity is disliked MORE than the entity that we
7584 // currently think is the best visible enemy. No need to do
7585 // a distance check, just get mad at this one for now.
7586 iBestPriority = IRelationPriority ( pEnemy );
7587 iBestDistSq = ( pEnemy->GetAbsOrigin() - GetAbsOrigin() ).LengthSqr();
7588 pBestEnemy = pEnemy;
7589 bBestUnreachable = bUnreachable;
7590 fBestSeen = TRS_NONE;
7591 fBestVisible = TRS_NONE;
7592 }
7593 else if ( IRelationPriority( pEnemy ) == iBestPriority )
7594 {
7595 // this entity is disliked just as much as the entity that
7596 // we currently think is the best visible enemy, so we only
7597 // get mad at it if it is closer.
7598 iDistSq = ( pEnemy->GetAbsOrigin() - GetAbsOrigin() ).LengthSqr();
7599
7600 bool bAcceptCurrent = false;
7601 bool bCloser = ( iDistSq < iBestDistSq );
7602 ThreeState_t fCurSeen = TRS_NONE;
7603 ThreeState_t fCurVisible = TRS_NONE;
7604
7605 // The following code is constructed in such a verbose manner to
7606 // ensure the expensive calls only occur if absolutely needed
7607
7608 // If current is farther, and best has previously been confirmed as seen or visible, move on
7609 if ( !bCloser)
7610 {
7611 if ( fBestSeen == TRS_TRUE || fBestVisible == TRS_TRUE )
7612 {
7613 DbgEnemyMsg( this, " %s rejected: current is closer and seen\n", pEnemy->GetDebugName() );
7614 continue;
7615 }
7616 }
7617
7618 // If current is closer, and best has previously been confirmed as not seen and not visible, take it
7619 if ( bCloser)
7620 {
7621 if ( fBestSeen == TRS_FALSE && fBestVisible == TRS_FALSE )
7622 {
7623 bAcceptCurrent = true;
7624 }
7625 }
7626
7627 if ( !bAcceptCurrent )
7628 {
7629 // If current is closer, and seen, take it
7630 if ( bCloser )
7631 {
7632 fCurSeen = ( GetSenses()->DidSeeEntity( pEnemy ) ) ? TRS_TRUE : TRS_FALSE;
7633
7634 bAcceptCurrent = ( fCurSeen == TRS_TRUE );
7635 }
7636 }
7637
7638 if ( !bAcceptCurrent )
7639 {
7640 // If current is farther, and best is seen, move on
7641 if ( !bCloser )
7642 {
7643 if ( fBestSeen == TRS_NONE )
7644 {
7645 fBestSeen = ( GetSenses()->DidSeeEntity( pBestEnemy ) ) ? TRS_TRUE : TRS_FALSE;
7646 }
7647
7648 if ( fBestSeen == TRS_TRUE )
7649 {
7650 DbgEnemyMsg( this, " %s rejected: current is closer and seen\n", pEnemy->GetDebugName() );
7651 continue;
7652 }
7653 }
7654
7655 // At this point, need to start performing expensive tests
7656 if ( bCloser && fBestVisible == TRS_NONE )
7657 {
7658 // Perform shortest FVisible
7659 fCurVisible = ( ( EnemyDistance( pEnemy ) < GetSenses()->GetDistLook() ) && FVisible( pEnemy ) ) ? TRS_TRUE : TRS_FALSE;
7660
7661 bAcceptCurrent = ( fCurVisible == TRS_TRUE );
7662 }
7663
7664 // Alas, must do the most expensive comparison
7665 if ( !bAcceptCurrent )
7666 {
7667 if ( fBestSeen == TRS_NONE )
7668 {
7669 fBestSeen = ( GetSenses()->DidSeeEntity( pBestEnemy ) ) ? TRS_TRUE : TRS_FALSE;
7670 }
7671
7672 if ( fBestVisible == TRS_NONE )
7673 {
7674 fBestVisible = ( ( EnemyDistance( pBestEnemy ) < GetSenses()->GetDistLook() ) && FVisible( pBestEnemy ) ) ? TRS_TRUE : TRS_FALSE;
7675 }
7676
7677 if ( fCurSeen == TRS_NONE )
7678 {
7679 fCurSeen = ( GetSenses()->DidSeeEntity( pEnemy ) ) ? TRS_TRUE : TRS_FALSE;
7680 }
7681
7682 if ( fCurVisible == TRS_NONE )
7683 {
7684 fCurVisible = ( ( EnemyDistance( pEnemy ) < GetSenses()->GetDistLook() ) && FVisible( pEnemy ) ) ? TRS_TRUE : TRS_FALSE;
7685 }
7686
7687 bool bBestSeenOrVisible = ( fBestSeen == TRS_TRUE || fBestVisible == TRS_TRUE );
7688 bool bCurSeenOrVisible = ( fCurSeen == TRS_TRUE || fCurVisible == TRS_TRUE );
7689
7690 if ( !bCloser)
7691 {
7692 if ( bBestSeenOrVisible )
7693 {
7694 DbgEnemyMsg( this, " %s rejected: current is closer and seen\n", pEnemy->GetDebugName() );
7695 continue;
7696 }
7697 else if ( !bCurSeenOrVisible )
7698 {
7699 DbgEnemyMsg( this, " %s rejected: current is closer and neither is seen\n", pEnemy->GetDebugName() );
7700 continue;
7701 }
7702 }
7703 else // Closer
7704 {
7705 if ( !bCurSeenOrVisible && bBestSeenOrVisible )
7706 {
7707 DbgEnemyMsg( this, " %s rejected: current is father but seen\n", pEnemy->GetDebugName() );
7708 continue;
7709 }
7710 }
7711 }
7712 }
7713
7714 DbgEnemyMsg( this, " %s accepted\n", pEnemy->GetDebugName() );
7715 if ( pBestEnemy )
7716 DbgEnemyMsg( this, " (%s displaced due to distance/visibility)\n", pBestEnemy->GetDebugName() );
7717 fBestSeen = fCurSeen;
7718 fBestVisible = fCurVisible;
7719 iBestDistSq = iDistSq;
7720 iBestPriority = IRelationPriority ( pEnemy );
7721 pBestEnemy = pEnemy;
7722 bBestUnreachable = bUnreachable;
7723 }
7724 else
7725 DbgEnemyMsg( this, " %s rejected: lower priority\n", pEnemy->GetDebugName() );
7726 }
7727
7728 DbgEnemyMsg( this, "} == %s\n", pBestEnemy->GetDebugName() );
7729
7730 return pBestEnemy;
7731}
7732
7733//-----------------------------------------------------------------------------
7734// Purpose: Given a node returns the appropriate reload activity
7735// Input :
7736// Output :
7737//-----------------------------------------------------------------------------
7738Activity CAI_BaseNPC::GetReloadActivity( CAI_Hint* pHint )
7739{
7740 Activity nReloadActivity = ACT_RELOAD;
7741
7742 if (pHint && GetEnemy()!=NULL)
7743 {
7744 switch (pHint->HintType())
7745 {
7746 case HINT_TACTICAL_COVER_LOW:
7747 case HINT_TACTICAL_COVER_MED:
7748 {
7749 if (SelectWeightedSequence( ACT_RELOAD_LOW ) != ACTIVITY_NOT_AVAILABLE)
7750 {
7751 Vector vEyePos = GetAbsOrigin() + EyeOffset(ACT_RELOAD_LOW);
7752 // Check if this location will block the threat's line of sight to me
7753 trace_t tr;
7754 AI_TraceLOS( vEyePos, GetEnemy()->EyePosition(), this, &tr );
7755 if (tr.fraction != 1.0)
7756 {
7757 nReloadActivity = ACT_RELOAD_LOW;
7758 }
7759 }
7760 break;
7761 }
7762 }
7763 }
7764 return nReloadActivity;
7765}
7766
7767//-----------------------------------------------------------------------------
7768// Purpose: Given a node returns the appropriate cover activity
7769// Input :
7770// Output :
7771//-----------------------------------------------------------------------------
7772Activity CAI_BaseNPC::GetCoverActivity( CAI_Hint *pHint )
7773{
7774 Activity nCoverActivity = ACT_INVALID;
7775
7776 // ---------------------------------------------------------------
7777 // Check if hint node specifies different cover type
7778 // ---------------------------------------------------------------
7779 if (pHint)
7780 {
7781 switch (pHint->HintType())
7782 {
7783 case HINT_TACTICAL_COVER_MED:
7784 {
7785 nCoverActivity = ACT_COVER_MED;
7786 break;
7787 }
7788 case HINT_TACTICAL_COVER_LOW:
7789 {
7790 nCoverActivity = ACT_COVER_LOW;
7791 break;
7792 }
7793 }
7794 }
7795
7796 if ( nCoverActivity == ACT_INVALID )
7797 nCoverActivity = ACT_COVER;
7798
7799 return nCoverActivity;
7800}
7801
7802//=========================================================
7803// CalcIdealYaw - gets a yaw value for the caller that would
7804// face the supplied vector. Value is stuffed into the npc's
7805// ideal_yaw
7806//=========================================================
7807float CAI_BaseNPC::CalcIdealYaw( const Vector &vecTarget )
7808{
7809 Vector vecProjection;
7810
7811 // strafing npc needs to face 90 degrees away from its goal
7812 if ( GetNavigator()->GetMovementActivity() == ACT_STRAFE_LEFT )
7813 {
7814 vecProjection.x = -vecTarget.y;
7815 vecProjection.y = vecTarget.x;
7816
7817 return UTIL_VecToYaw( vecProjection - GetLocalOrigin() );
7818 }
7819 else if ( GetNavigator()->GetMovementActivity() == ACT_STRAFE_RIGHT )
7820 {
7821 vecProjection.x = vecTarget.y;
7822 vecProjection.y = vecTarget.x;
7823
7824 return UTIL_VecToYaw( vecProjection - GetLocalOrigin() );
7825 }
7826 else
7827 {
7828 return UTIL_VecToYaw ( vecTarget - GetLocalOrigin() );
7829 }
7830}
7831
7832//=========================================================
7833// SetEyePosition
7834//
7835// queries the npc's model for $eyeposition and copies
7836// that vector to the npc's m_vDefaultEyeOffset and m_vecViewOffset
7837//
7838//=========================================================
7839void CAI_BaseNPC::SetDefaultEyeOffset ( void )
7840{
7841 if ( GetModelPtr() )
7842 {
7843 GetEyePosition( GetModelPtr(), m_vDefaultEyeOffset );
7844
7845 if ( m_vDefaultEyeOffset == vec3_origin )
7846 {
7847 if ( Classify() != CLASS_NONE )
7848 {
7849 DevMsg( "WARNING: %s(%s) has no eye offset in .qc!\n", GetClassname(), STRING(GetModelName()) );
7850 }
7851 VectorAdd( WorldAlignMins(), WorldAlignMaxs(), m_vDefaultEyeOffset );
7852 m_vDefaultEyeOffset *= 0.75;
7853 }
7854 }
7855 else
7856 m_vDefaultEyeOffset = vec3_origin;
7857
7858 SetViewOffset( m_vDefaultEyeOffset );
7859
7860}
7861
7862//------------------------------------------------------------------------------
7863// Purpose : Returns eye offset for an NPC for the given activity
7864// Input :
7865// Output :
7866//------------------------------------------------------------------------------
7867Vector CAI_BaseNPC::EyeOffset( Activity nActivity )
7868{
7869 if ( CapabilitiesGet() & bits_CAP_DUCK )
7870 {
7871 if ( IsCrouchedActivity( nActivity ) )
7872 return GetCrouchEyeOffset();
7873 }
7874
7875 // if the hint doesn't tell anything, assume current state
7876 if ( IsCrouching() )
7877 return GetCrouchEyeOffset();
7878
7879 return m_vDefaultEyeOffset;
7880}
7881
7882//-----------------------------------------------------------------------------
7883// Purpose:
7884// Output : Vector
7885//-----------------------------------------------------------------------------
7886Vector CAI_BaseNPC::EyePosition( void )
7887{
7888 if ( IsCrouching() )
7889 return GetAbsOrigin() + GetCrouchEyeOffset();
7890
7891 return BaseClass::EyePosition();
7892}
7893
7894//------------------------------------------------------------------------------
7895// Purpose :
7896// Input :
7897// Output :
7898//------------------------------------------------------------------------------
7899void CAI_BaseNPC::HandleAnimEvent( animevent_t *pEvent )
7900{
7901 // UNDONE: Share this code into CBaseAnimating as appropriate?
7902 switch( pEvent->event )
7903 {
7904 case SCRIPT_EVENT_DEAD:
7905 if ( m_NPCState == NPC_STATE_SCRIPT )
7906 {
7907 m_lifeState = LIFE_DYING;
7908 // Kill me now! (and fade out when CineCleanup() is called)
7909#if _DEBUG
7910 DevMsg( 2, "Death event: %s\n", GetClassname() );
7911#endif
7912 m_iHealth = 0;
7913 }
7914#if _DEBUG
7915 else
7916 DevWarning( 2, "INVALID death event:%s\n", GetClassname() );
7917#endif
7918 break;
7919 case SCRIPT_EVENT_NOT_DEAD:
7920 if ( m_NPCState == NPC_STATE_SCRIPT )
7921 {
7922 m_lifeState = LIFE_ALIVE;
7923 // This is for life/death sequences where the player can determine whether a character is dead or alive after the script
7924 m_iHealth = m_iMaxHealth;
7925 }
7926 break;
7927
7928 case SCRIPT_EVENT_SOUND: // Play a named wave file
7929 {
7930 EmitSound( pEvent->options );
7931 }
7932 break;
7933
7934 case SCRIPT_EVENT_SOUND_VOICE:
7935 {
7936 EmitSound( pEvent->options );
7937 }
7938 break;
7939
7940 case SCRIPT_EVENT_SENTENCE_RND1: // Play a named sentence group 33% of the time
7941 if (random->RandomInt(0,2) == 0)
7942 break;
7943 // fall through...
7944 case SCRIPT_EVENT_SENTENCE: // Play a named sentence group
7945 SENTENCEG_PlayRndSz( edict(), pEvent->options, 1.0, SNDLVL_TALKING, 0, 100 );
7946 break;
7947
7948 case SCRIPT_EVENT_FIREEVENT:
7949 {
7950 //
7951 // Fire a script event. The number of the script event to fire is in the options string.
7952 //
7953 if ( m_hCine != NULL )
7954 {
7955 m_hCine->FireScriptEvent( atoi( pEvent->options ) );
7956 }
7957 else
7958 {
7959 // FIXME: look so see if it's playing a vcd and fire those instead
7960 // AssertOnce( 0 );
7961 }
7962 break;
7963 }
7964 case SCRIPT_EVENT_FIRE_INPUT:
7965 {
7966 variant_t emptyVariant;
7967 this->AcceptInput( pEvent->options, this, this, emptyVariant, 0 );
7968 break;
7969 }
7970
7971 case SCRIPT_EVENT_NOINTERRUPT: // Can't be interrupted from now on
7972 if ( m_hCine )
7973 m_hCine->AllowInterrupt( false );
7974 break;
7975
7976 case SCRIPT_EVENT_CANINTERRUPT: // OK to interrupt now
7977 if ( m_hCine )
7978 m_hCine->AllowInterrupt( true );
7979 break;
7980
7981#if 0
7982 case SCRIPT_EVENT_INAIR: // Don't engine->DropToFloor()
7983 case SCRIPT_EVENT_ENDANIMATION: // Set ending animation sequence to
7984 break;
7985#endif
7986 case SCRIPT_EVENT_BODYGROUPON:
7987 case SCRIPT_EVENT_BODYGROUPOFF:
7988 case SCRIPT_EVENT_BODYGROUPTEMP:
7989 DevMsg( "Bodygroup!\n" );
7990 break;
7991
7992 case AE_NPC_ATTACK_BROADCAST:
7993 break;
7994
7995 case NPC_EVENT_BODYDROP_HEAVY:
7996 if ( GetFlags() & FL_ONGROUND )
7997 {
7998 EmitSound( "AI_BaseNPC.BodyDrop_Heavy" );
7999 }
8000 break;
8001
8002 case NPC_EVENT_BODYDROP_LIGHT:
8003 if ( GetFlags() & FL_ONGROUND )
8004 {
8005 EmitSound( "AI_BaseNPC.BodyDrop_Light" );
8006 }
8007 break;
8008
8009 case NPC_EVENT_SWISHSOUND:
8010 {
8011 // NO NPC may use this anim event unless that npc's precache precaches this sound!!!
8012 EmitSound( "AI_BaseNPC.SwishSound" );
8013 break;
8014 }
8015
8016
8017 case NPC_EVENT_180TURN:
8018 {
8019 //DevMsg( "Turned!\n" );
8020 SetIdealActivity( ACT_IDLE );
8021 Forget( bits_MEMORY_TURNING );
8022 SetBoneController( 0, GetLocalAngles().y );
8023 AddEffects( EF_NOINTERP );
8024 break;
8025 }
8026
8027 case NPC_EVENT_ITEM_PICKUP:
8028 {
8029 CBaseEntity *pPickup = NULL;
8030
8031 //
8032 // Figure out what we're supposed to pick up.
8033 //
8034 if ( pEvent->options && strlen( pEvent->options ) > 0 )
8035 {
8036 // Pick up the weapon or item that was specified in the anim event.
8037 pPickup = gEntList.FindEntityGenericNearest( pEvent->options, GetAbsOrigin(), 256, this );
8038 }
8039 else
8040 {
8041 // Pick up the weapon or item that was found earlier and cached in our target pointer.
8042 pPickup = GetTarget();
8043 }
8044
8045 // Make sure we found something to pick up.
8046 if ( !pPickup )
8047 {
8048 TaskFail("Item no longer available!\n");
8049 break;
8050 }
8051
8052 // Make sure the item hasn't moved.
8053 float flDist = ( pPickup->WorldSpaceCenter() - GetAbsOrigin() ).Length2D();
8054 if ( flDist > ITEM_PICKUP_TOLERANCE )
8055 {
8056 TaskFail("Item has moved!\n");
8057 break;
8058 }
8059
8060 CBaseCombatWeapon *pWeapon = dynamic_cast<CBaseCombatWeapon *>( pPickup );
8061 if ( pWeapon )
8062 {
8063 // Picking up a weapon.
8064 CBaseCombatCharacter *pOwner = pWeapon->GetOwner();
8065 if ( pOwner )
8066 {
8067 TaskFail( "Weapon in use by someone else" );
8068 }
8069 else if ( !pWeapon )
8070 {
8071 TaskFail( "Weapon doesn't exist" );
8072 }
8073 else if (!Weapon_CanUse( pWeapon ))
8074 {
8075 TaskFail( "Can't use this weapon type" );
8076 }
8077 else
8078 {
8079 PickupWeapon( pWeapon );
8080 TaskComplete();
8081 break;
8082 }
8083 }
8084 else
8085 {
8086 // Picking up an item.
8087 PickupItem( pPickup );
8088 TaskComplete();
8089 }
8090
8091 break;
8092 }
8093
8094 case NPC_EVENT_WEAPON_SET_SEQUENCE_NUMBER:
8095 {
8096 CBaseCombatWeapon *pWeapon = GetActiveWeapon();
8097 if ((pWeapon) && (pEvent->options))
8098 {
8099 int nSequence = atoi(pEvent->options);
8100 if (nSequence != -1)
8101 {
8102 pWeapon->ResetSequence(nSequence);
8103 }
8104 }
8105 break;
8106 }
8107
8108 case NPC_EVENT_WEAPON_SET_SEQUENCE_NAME:
8109 {
8110 CBaseCombatWeapon *pWeapon = GetActiveWeapon();
8111 if ((pWeapon) && (pEvent->options))
8112 {
8113 int nSequence = pWeapon->LookupSequence(pEvent->options);
8114 if (nSequence != -1)
8115 {
8116 pWeapon->ResetSequence(nSequence);
8117 }
8118 }
8119 break;
8120 }
8121
8122 case NPC_EVENT_WEAPON_SET_ACTIVITY:
8123 {
8124 CBaseCombatWeapon *pWeapon = GetActiveWeapon();
8125 if ((pWeapon) && (pEvent->options))
8126 {
8127 Activity act = (Activity)pWeapon->LookupActivity(pEvent->options);
8128 if (act != ACT_INVALID)
8129 {
8130 // FIXME: where should the duration come from? normally it would come from the current sequence
8131 Weapon_SetActivity(act, 0);
8132 }
8133 }
8134 break;
8135 }
8136
8137 case NPC_EVENT_WEAPON_DROP:
8138 {
8139 //
8140 // Drop our active weapon (or throw it at the specified target entity).
8141 //
8142 CBaseEntity *pTarget = NULL;
8143 if (pEvent->options)
8144 {
8145 pTarget = gEntList.FindEntityGeneric(NULL, pEvent->options, this);
8146 }
8147
8148 if (pTarget)
8149 {
8150 Vector vecTargetPos = pTarget->WorldSpaceCenter();
8151 Weapon_Drop(GetActiveWeapon(), &vecTargetPos);
8152 }
8153 else
8154 {
8155 Weapon_Drop(GetActiveWeapon());
8156 }
8157
8158 break;
8159 }
8160
8161 case EVENT_WEAPON_RELOAD:
8162 {
8163 if ( GetActiveWeapon() )
8164 {
8165 GetActiveWeapon()->WeaponSound( RELOAD_NPC );
8166 GetActiveWeapon()->m_iClip1 = GetActiveWeapon()->GetMaxClip1();
8167 ClearCondition(COND_LOW_PRIMARY_AMMO);
8168 ClearCondition(COND_NO_PRIMARY_AMMO);
8169 ClearCondition(COND_NO_SECONDARY_AMMO);
8170 }
8171 break;
8172 }
8173
8174 case EVENT_WEAPON_RELOAD_SOUND:
8175 {
8176 if ( GetActiveWeapon() )
8177 {
8178 GetActiveWeapon()->WeaponSound( RELOAD_NPC );
8179 }
8180 break;
8181 }
8182
8183 case EVENT_WEAPON_RELOAD_FILL_CLIP:
8184 {
8185 if ( GetActiveWeapon() )
8186 {
8187 GetActiveWeapon()->m_iClip1 = GetActiveWeapon()->GetMaxClip1();
8188 ClearCondition(COND_LOW_PRIMARY_AMMO);
8189 ClearCondition(COND_NO_PRIMARY_AMMO);
8190 ClearCondition(COND_NO_SECONDARY_AMMO);
8191 }
8192 break;
8193 }
8194
8195 case NPC_EVENT_LEFTFOOT:
8196 case NPC_EVENT_RIGHTFOOT:
8197 // For right now, do nothing. All functionality for this lives in individual npcs.
8198 break;
8199
8200 case NPC_EVENT_OPEN_DOOR:
8201 {
8202 CBasePropDoor *pDoor = (CBasePropDoor *)(CBaseEntity *)GetNavigator()->GetPath()->GetCurWaypoint()->GetEHandleData();
8203 if (pDoor != NULL)
8204 {
8205 OpenPropDoorNow( pDoor );
8206 }
8207
8208 break;
8209 }
8210
8211 default:
8212 if ((pEvent->type & AE_TYPE_NEWEVENTSYSTEM) && (pEvent->type & AE_TYPE_SERVER))
8213 {
8214 if (pEvent->event == AE_NPC_HOLSTER)
8215 {
8216 // Cache off the weapon.
8217 CBaseCombatWeapon *pWeapon = GetActiveWeapon();
8218
8219 Assert( pWeapon != NULL );
8220
8221 GetActiveWeapon()->Holster();
8222 SetActiveWeapon( NULL );
8223
8224 //Force the NPC to recalculate it's arrival activity since it'll most likely be wrong now that we don't have a weapon out.
8225 GetNavigator()->SetArrivalSequence( ACT_INVALID );
8226
8227 if ( m_iDesiredWeaponState == DESIREDWEAPONSTATE_CHANGING_DESTROY )
8228 {
8229 // Get rid of it!
8230 UTIL_Remove( pWeapon );
8231 }
8232
8233 if ( m_iDesiredWeaponState != DESIREDWEAPONSTATE_IGNORE )
8234 {
8235 m_iDesiredWeaponState = DESIREDWEAPONSTATE_IGNORE;
8236 m_Activity = ACT_RESET;
8237 }
8238
8239 return;
8240 }
8241 else if (pEvent->event == AE_NPC_DRAW)
8242 {
8243 if (GetActiveWeapon())
8244 {
8245 GetActiveWeapon()->Deploy();
8246
8247 //Force the NPC to recalculate it's arrival activity since it'll most likely be wrong now.
8248 GetNavigator()->SetArrivalSequence( ACT_INVALID );
8249
8250 if ( m_iDesiredWeaponState != DESIREDWEAPONSTATE_IGNORE )
8251 {
8252 m_iDesiredWeaponState = DESIREDWEAPONSTATE_IGNORE;
8253 m_Activity = ACT_RESET;
8254 }
8255 }
8256 return;
8257 }
8258 else if ( pEvent->event == AE_NPC_BODYDROP_HEAVY )
8259 {
8260 if ( GetFlags() & FL_ONGROUND )
8261 {
8262 EmitSound( "AI_BaseNPC.BodyDrop_Heavy" );
8263 }
8264 return;
8265 }
8266 else if ( pEvent->event == AE_NPC_LEFTFOOT || pEvent->event == AE_NPC_RIGHTFOOT )
8267 {
8268 return;
8269 }
8270 else if ( pEvent->event == AE_NPC_RAGDOLL )
8271 {
8272 // Convert to ragdoll immediately
8273 BecomeRagdollOnClient( vec3_origin );
8274 return;
8275 }
8276 else if ( pEvent->event == AE_NPC_ADDGESTURE )
8277 {
8278 Activity act = ( Activity )LookupActivity( pEvent->options );
8279 if (act != ACT_INVALID)
8280 {
8281 act = TranslateActivity( act );
8282 if (act != ACT_INVALID)
8283 {
8284 AddGesture( act );
8285 }
8286 }
8287 return;
8288 }
8289 else if ( pEvent->event == AE_NPC_RESTARTGESTURE )
8290 {
8291 Activity act = ( Activity )LookupActivity( pEvent->options );
8292 if (act != ACT_INVALID)
8293 {
8294 act = TranslateActivity( act );
8295 if (act != ACT_INVALID)
8296 {
8297 RestartGesture( act );
8298 }
8299 }
8300 return;
8301 }
8302 else if ( pEvent->event == AE_NPC_WEAPON_DROP )
8303 {
8304 // Drop our active weapon (or throw it at the specified target entity).
8305 CBaseEntity *pTarget = NULL;
8306 if (pEvent->options)
8307 {
8308 pTarget = gEntList.FindEntityGeneric(NULL, pEvent->options, this);
8309 }
8310
8311 if (pTarget)
8312 {
8313 Vector vecTargetPos = pTarget->WorldSpaceCenter();
8314 Weapon_Drop(GetActiveWeapon(), &vecTargetPos);
8315 }
8316 else
8317 {
8318 Weapon_Drop(GetActiveWeapon());
8319 }
8320 return;
8321 }
8322 else if ( pEvent->event == AE_NPC_WEAPON_SET_ACTIVITY )
8323 {
8324 CBaseCombatWeapon *pWeapon = GetActiveWeapon();
8325 if ((pWeapon) && (pEvent->options))
8326 {
8327 Activity act = (Activity)pWeapon->LookupActivity(pEvent->options);
8328 if (act == ACT_INVALID)
8329 {
8330 // Try and translate it
8331 act = Weapon_TranslateActivity( (Activity)CAI_BaseNPC::GetActivityID(pEvent->options), false );
8332 }
8333
8334 if (act != ACT_INVALID)
8335 {
8336 // FIXME: where should the duration come from? normally it would come from the current sequence
8337 Weapon_SetActivity(act, 0);
8338 }
8339 }
8340 return;
8341 }
8342 else if ( pEvent->event == AE_NPC_SET_INTERACTION_CANTDIE )
8343 {
8344 SetInteractionCantDie( (atoi(pEvent->options) != 0) );
8345 return;
8346 }
8347 else if ( pEvent->event == AE_NPC_HURT_INTERACTION_PARTNER )
8348 {
8349 // If we're currently interacting with an enemy, hurt them/me
8350 if ( m_hInteractionPartner )
8351 {
8352 CAI_BaseNPC *pTarget = NULL;
8353 CAI_BaseNPC *pAttacker = NULL;
8354 if ( pEvent->options )
8355 {
8356 char szEventOptions[128];
8357 Q_strncpy( szEventOptions, pEvent->options, sizeof(szEventOptions) );
8358 char *pszParam = strtok( szEventOptions, " " );
8359 if ( pszParam )
8360 {
8361 if ( !Q_strncmp( pszParam, "ME", 2 ) )
8362 {
8363 pTarget = this;
8364 pAttacker = m_hInteractionPartner;
8365 }
8366 else if ( !Q_strncmp( pszParam, "THEM", 4 ) )
8367 {
8368 pAttacker = this;
8369 pTarget = m_hInteractionPartner;
8370 }
8371
8372 pszParam = strtok(NULL," ");
8373 if ( pAttacker && pTarget && pszParam )
8374 {
8375 int iDamage = atoi( pszParam );
8376 if ( iDamage )
8377 {
8378 // We've got a target, and damage. Now hurt them.
8379 CTakeDamageInfo info;
8380 info.SetDamage( iDamage );
8381 info.SetAttacker( pAttacker );
8382 info.SetInflictor( pAttacker );
8383 info.SetDamageType( DMG_GENERIC | DMG_PREVENT_PHYSICS_FORCE );
8384 pTarget->TakeDamage( info );
8385 return;
8386 }
8387 }
8388 }
8389 }
8390
8391 // Bad data. Explain how to use this anim event.
8392 const char *pName = EventList_NameForIndex( pEvent->event );
8393 DevWarning( 1, "Bad %s format. Should be: { AE_NPC_HURT_INTERACTION_PARTNER <frame number> \"<ME/THEM> <Amount of damage done>\" }\n", pName );
8394 return;
8395 }
8396
8397 DevWarning( "%s received AE_NPC_HURT_INTERACTION_PARTNER anim event, but it's not interacting with anything.\n", GetDebugName() );
8398 return;
8399 }
8400 }
8401
8402 // FIXME: why doesn't this code pass unhandled events down to its parent?
8403 // Came from my weapon?
8404 //Adrian I'll clean this up once the old event system is phased out.
8405 if ( pEvent->pSource != this || ( pEvent->type & AE_TYPE_NEWEVENTSYSTEM && pEvent->type & AE_TYPE_WEAPON ) || (pEvent->event >= EVENT_WEAPON && pEvent->event <= EVENT_WEAPON_LAST) )
8406 {
8407 Weapon_HandleAnimEvent( pEvent );
8408 }
8409 else
8410 {
8411 BaseClass::HandleAnimEvent( pEvent );
8412 }
8413 break;
8414 }
8415}
8416
8417
8418//-----------------------------------------------------------------------------
8419// Purpose: Override base class to add display of routes
8420// Input :
8421// Output : Current text offset from the top
8422//-----------------------------------------------------------------------------
8423void CAI_BaseNPC::DrawDebugGeometryOverlays(void)
8424{
8425 // Handy for debug
8426 //NDebugOverlay::Cross3D(EyePosition(),Vector(-2,-2,-2),Vector(2,2,2),0,255,0,true);
8427
8428 // ------------------------------
8429 // Remove me if requested
8430 // ------------------------------
8431 if (m_debugOverlays & OVERLAY_NPC_ZAP_BIT)
8432 {
8433 VacateStrategySlot();
8434 Weapon_Drop( GetActiveWeapon() );
8435 m_iHealth = 0;
8436 SetThink( &CAI_BaseNPC::SUB_Remove );
8437 }
8438
8439 // ------------------------------
8440 // properly kill an NPC.
8441 // ------------------------------
8442 if (m_debugOverlays & OVERLAY_NPC_KILL_BIT)
8443 {
8444 CTakeDamageInfo info;
8445
8446 info.SetDamage( m_iHealth );
8447 info.SetAttacker( this );
8448 info.SetInflictor( ( AI_IsSinglePlayer() ) ? (CBaseEntity *)AI_GetSinglePlayer() : (CBaseEntity *)this );
8449 info.SetDamageType( DMG_GENERIC );
8450
8451 m_debugOverlays &= ~OVERLAY_NPC_KILL_BIT;
8452 TakeDamage( info );
8453 return;
8454 }
8455
8456
8457 // ------------------------------
8458 // Draw route if requested
8459 // ------------------------------
8460 if ((m_debugOverlays & OVERLAY_NPC_ROUTE_BIT))
8461 {
8462 GetNavigator()->DrawDebugRouteOverlay();
8463 if ( IsMoving() )
8464 {
8465 float yaw = GetMotor()->GetIdealYaw();
8466 Vector vecYaw = UTIL_YawToVector(yaw);
8467 NDebugOverlay::Line(WorldSpaceCenter(),WorldSpaceCenter() + vecYaw * GetHullWidth() * .5,255,255,255,true,0.0);
8468 }
8469 }
8470
8471 if (!(CAI_BaseNPC::m_nDebugBits & bits_debugDisableAI) && (IsCurSchedule(SCHED_FORCED_GO) || IsCurSchedule(SCHED_FORCED_GO_RUN)))
8472 {
8473 NDebugOverlay::Box(m_vecLastPosition, Vector(-5,-5,-5),Vector(5,5,5), 255, 0, 255, 0, 0);
8474 NDebugOverlay::HorzArrow( GetAbsOrigin(), m_vecLastPosition, 16, 255, 0, 255, 64, true, 0 );
8475 }
8476
8477 // ------------------------------
8478 // Draw red box around if selected
8479 // ------------------------------
8480 if ((m_debugOverlays & OVERLAY_NPC_SELECTED_BIT) && !ai_no_select_box.GetBool())
8481 {
8482 NDebugOverlay::EntityBounds(this, 255, 0, 0, 20, 0);
8483 }
8484
8485 // ------------------------------
8486 // Draw nearest node if selected
8487 // ------------------------------
8488 if ((m_debugOverlays & OVERLAY_NPC_NEAREST_BIT))
8489 {
8490 int iNodeID = GetPathfinder()->NearestNodeToNPC();
8491 if (iNodeID != NO_NODE)
8492 {
8493 NDebugOverlay::Box(GetNavigator()->GetNetwork()->AccessNodes()[iNodeID]->GetPosition(GetHullType()), Vector(-10,-10,-10),Vector(10,10,10), 255, 255, 255, 0, 0);
8494 }
8495 }
8496
8497 // ------------------------------
8498 // Draw viewcone if selected
8499 // ------------------------------
8500 if ((m_debugOverlays & OVERLAY_NPC_VIEWCONE_BIT))
8501 {
8502 float flViewRange = acos(m_flFieldOfView);
8503 Vector vEyeDir = EyeDirection2D( );
8504 Vector vLeftDir, vRightDir;
8505 float fSin, fCos;
8506 SinCos( flViewRange, &fSin, &fCos );
8507
8508 vLeftDir.x = vEyeDir.x * fCos - vEyeDir.y * fSin;
8509 vLeftDir.y = vEyeDir.x * fSin + vEyeDir.y * fCos;
8510 vLeftDir.z = vEyeDir.z;
8511 fSin = sin(-flViewRange);
8512 fCos = cos(-flViewRange);
8513 vRightDir.x = vEyeDir.x * fCos - vEyeDir.y * fSin;
8514 vRightDir.y = vEyeDir.x * fSin + vEyeDir.y * fCos;
8515 vRightDir.z = vEyeDir.z;
8516
8517 // Visualize it
8518 NDebugOverlay::VertArrow( EyePosition(), EyePosition() + ( vLeftDir * 200 ), 64, 255, 0, 0, 50, false, 0 );
8519 NDebugOverlay::VertArrow( EyePosition(), EyePosition() + ( vRightDir * 200 ), 64, 255, 0, 0, 50, false, 0 );
8520 NDebugOverlay::VertArrow( EyePosition(), EyePosition() + ( vEyeDir * 100 ), 8, 0, 255, 0, 50, false, 0 );
8521 NDebugOverlay::Box(EyePosition(), -Vector(2,2,2), Vector(2,2,2), 0, 255, 0, 128, 0 );
8522 }
8523
8524 // ----------------------------------------------
8525 // Draw the relationships for this NPC to others
8526 // ----------------------------------------------
8527 if ( m_debugOverlays & OVERLAY_NPC_RELATION_BIT )
8528 {
8529 // Show the relationships to entities around us
8530 int r = 0;
8531 int g = 0;
8532 int b = 0;
8533
8534 int nRelationship;
8535 CAI_BaseNPC **ppAIs = g_AI_Manager.AccessAIs();
8536
8537 // Rate all NPCs
8538 for ( int i = 0; i < g_AI_Manager.NumAIs(); i++ )
8539 {
8540 if ( ppAIs[i] == NULL || ppAIs[i] == this )
8541 continue;
8542
8543 // Get our relation to the target
8544 nRelationship = IRelationType( ppAIs[i] );
8545
8546 // Get the color for the arrow
8547 UTIL_GetDebugColorForRelationship( nRelationship, r, g, b );
8548
8549 // Draw an arrow
8550 NDebugOverlay::HorzArrow( GetAbsOrigin(), ppAIs[i]->GetAbsOrigin(), 16, r, g, b, 64, true, 0.0f );
8551 }
8552
8553 // Also include all players
8554 for ( int i = 1; i <= gpGlobals->maxClients; i++ )
8555 {
8556 CBasePlayer *pPlayer = UTIL_PlayerByIndex( i );
8557 if ( pPlayer == NULL )
8558 continue;
8559
8560 // Get our relation to the target
8561 nRelationship = IRelationType( pPlayer );
8562
8563 // Get the color for the arrow
8564 UTIL_GetDebugColorForRelationship( nRelationship, r, g, b );
8565
8566 // Draw an arrow
8567 NDebugOverlay::HorzArrow( GetAbsOrigin(), pPlayer->GetAbsOrigin(), 16, r, g, b, 64, true, 0.0f );
8568 }
8569 }
8570
8571 // ------------------------------
8572 // Draw enemies if selected
8573 // ------------------------------
8574 if ((m_debugOverlays & OVERLAY_NPC_ENEMIES_BIT))
8575 {
8576 AIEnemiesIter_t iter;
8577 for( AI_EnemyInfo_t *eMemory = GetEnemies()->GetFirst(&iter); eMemory != NULL; eMemory = GetEnemies()->GetNext(&iter) )
8578 {
8579 if (eMemory->hEnemy)
8580 {
8581 CBaseCombatCharacter *npcEnemy = (eMemory->hEnemy)->MyCombatCharacterPointer();
8582 if (npcEnemy)
8583 {
8584 float r,g,b;
8585 char debugText[255];
8586 debugText[0] = NULL;
8587
8588 if (npcEnemy == GetEnemy())
8589 {
8590 Q_strncat(debugText,"Current Enemy", sizeof( debugText ), COPY_ALL_CHARACTERS );
8591 }
8592 else if (npcEnemy == GetTarget())
8593 {
8594 Q_strncat(debugText,"Current Target", sizeof( debugText ), COPY_ALL_CHARACTERS );
8595 }
8596 else
8597 {
8598 Q_strncat(debugText,"Other Memory", sizeof( debugText ), COPY_ALL_CHARACTERS );
8599 }
8600 if (IsUnreachable(npcEnemy))
8601 {
8602 Q_strncat(debugText," (Unreachable)", sizeof( debugText ), COPY_ALL_CHARACTERS );
8603 }
8604 if (eMemory->bEludedMe)
8605 {
8606 Q_strncat(debugText," (Eluded)", sizeof( debugText ), COPY_ALL_CHARACTERS );
8607 }
8608 // Unreachable enemy drawn in green
8609 if (IsUnreachable(npcEnemy))
8610 {
8611 r = 0;
8612 g = 255;
8613 b = 0;
8614 }
8615 // Eluded enemy drawn in blue
8616 else if (eMemory->bEludedMe)
8617 {
8618 r = 0;
8619 g = 0;
8620 b = 255;
8621 }
8622 // Current enemy drawn in red
8623 else if (npcEnemy == GetEnemy())
8624 {
8625 r = 255;
8626 g = 0;
8627 b = 0;
8628 }
8629 // Current traget drawn in magenta
8630 else if (npcEnemy == GetTarget())
8631 {
8632 r = 255;
8633 g = 0;
8634 b = 255;
8635 }
8636 // All other enemies drawn in pink
8637 else
8638 {
8639 r = 255;
8640 g = 100;
8641 b = 100;
8642 }
8643
8644
8645 Vector drawPos = eMemory->vLastKnownLocation;
8646 NDebugOverlay::Text( drawPos, debugText, false, 0.0 );
8647
8648 // If has a line on the player draw cross slightly in front so player can see
8649 if (npcEnemy->IsPlayer() &&
8650 (eMemory->vLastKnownLocation - npcEnemy->GetAbsOrigin()).Length()<10 )
8651 {
8652 Vector vEnemyFacing = npcEnemy->BodyDirection2D( );
8653 Vector eyePos = npcEnemy->EyePosition() + vEnemyFacing*10.0;
8654 Vector upVec = Vector(0,0,2);
8655 Vector sideVec;
8656 CrossProduct( vEnemyFacing, upVec, sideVec);
8657 NDebugOverlay::Line(eyePos+sideVec+upVec, eyePos-sideVec-upVec, r,g,b, false,0);
8658 NDebugOverlay::Line(eyePos+sideVec-upVec, eyePos-sideVec+upVec, r,g,b, false,0);
8659
8660 NDebugOverlay::Text( eyePos, debugText, false, 0.0 );
8661 }
8662 else
8663 {
8664 NDebugOverlay::Cross3D(drawPos,NAI_Hull::Mins(npcEnemy->GetHullType()),NAI_Hull::Maxs(npcEnemy->GetHullType()),r,g,b,false,0);
8665 }
8666 }
8667 }
8668 }
8669 }
8670
8671 // ----------------------------------------------
8672 // Draw line to target and enemy entity if exist
8673 // ----------------------------------------------
8674 if ((m_debugOverlays & OVERLAY_NPC_FOCUS_BIT))
8675 {
8676 if (GetEnemy() != NULL)
8677 {
8678 NDebugOverlay::Line(EyePosition(),GetEnemy()->EyePosition(),255,0,0,true,0.0);
8679 }
8680 if (GetTarget() != NULL)
8681 {
8682 NDebugOverlay::Line(EyePosition(),GetTarget()->EyePosition(),0,0,255,true,0.0);
8683 }
8684 }
8685
8686
8687 GetPathfinder()->DrawDebugGeometryOverlays(m_debugOverlays);
8688
8689 CBaseEntity::DrawDebugGeometryOverlays();
8690}
8691
8692//-----------------------------------------------------------------------------
8693// Purpose: Draw any debug text overlays
8694// Input :
8695// Output : Current text offset from the top
8696//-----------------------------------------------------------------------------
8697int CAI_BaseNPC::DrawDebugTextOverlays(void)
8698{
8699 int text_offset = 0;
8700
8701 // ---------------------
8702 // Print Baseclass text
8703 // ---------------------
8704 text_offset = BaseClass::DrawDebugTextOverlays();
8705
8706 if (m_debugOverlays & OVERLAY_NPC_SQUAD_BIT)
8707 {
8708 // Print health
8709 char tempstr[512];
8710 Q_snprintf(tempstr,sizeof(tempstr),"Health: %i",m_iHealth);
8711 EntityText(text_offset,tempstr,0);
8712 text_offset++;
8713
8714 // Print squad name
8715 Q_strncpy(tempstr,"Squad: ",sizeof(tempstr));
8716 if (m_pSquad)
8717 {
8718 Q_strncat(tempstr,m_pSquad->GetName(),sizeof(tempstr), COPY_ALL_CHARACTERS);
8719
8720 if( m_pSquad->GetLeader() == this )
8721 {
8722 Q_strncat(tempstr," (LEADER)",sizeof(tempstr), COPY_ALL_CHARACTERS);
8723 }
8724
8725 Q_strncat(tempstr,"\n",sizeof(tempstr), COPY_ALL_CHARACTERS);
8726 }
8727 else
8728 {
8729 Q_strncat(tempstr," - \n",sizeof(tempstr), COPY_ALL_CHARACTERS);
8730 }
8731 EntityText(text_offset,tempstr,0);
8732 text_offset++;
8733
8734 // Print enemy name
8735 Q_strncpy(tempstr,"Enemy: ",sizeof(tempstr));
8736 if (GetEnemy())
8737 {
8738 if (GetEnemy()->GetEntityName() != NULL_STRING)
8739 {
8740 Q_strncat(tempstr,STRING(GetEnemy()->GetEntityName()),sizeof(tempstr), COPY_ALL_CHARACTERS);
8741 Q_strncat(tempstr,"\n",sizeof(tempstr), COPY_ALL_CHARACTERS);
8742 }
8743 else
8744 {
8745 Q_strncat(tempstr,STRING(GetEnemy()->m_iClassname),sizeof(tempstr), COPY_ALL_CHARACTERS);
8746 Q_strncat(tempstr,"\n",sizeof(tempstr), COPY_ALL_CHARACTERS);
8747 }
8748 }
8749 else
8750 {
8751 Q_strncat(tempstr," - \n",sizeof(tempstr), COPY_ALL_CHARACTERS);
8752 }
8753 EntityText(text_offset,tempstr,0);
8754 text_offset++;
8755
8756 // Print slot
8757 Q_snprintf(tempstr,sizeof(tempstr),"Slot: %s \n",
8758 SquadSlotName(m_iMySquadSlot));
8759 EntityText(text_offset,tempstr,0);
8760 text_offset++;
8761
8762 }
8763
8764 if (m_debugOverlays & OVERLAY_TEXT_BIT)
8765 {
8766 char tempstr[512];
8767 // --------------
8768 // Print Health
8769 // --------------
8770 Q_snprintf(tempstr,sizeof(tempstr),"Health: %i (DACC:%1.2f)",m_iHealth, GetDamageAccumulator() );
8771 EntityText(text_offset,tempstr,0);
8772 text_offset++;
8773
8774 // --------------
8775 // Print State
8776 // --------------
8777 static const char *pStateNames[] = { "None", "Idle", "Alert", "Combat", "Scripted", "PlayDead", "Dead" };
8778 if ( (int)m_NPCState < ARRAYSIZE(pStateNames) )
8779 {
8780 Q_snprintf(tempstr,sizeof(tempstr),"Stat: %s, ", pStateNames[m_NPCState] );
8781 EntityText(text_offset,tempstr,0);
8782 text_offset++;
8783 }
8784
8785 // -----------------
8786 // Start Scripting?
8787 // -----------------
8788 if( IsInAScript() )
8789 {
8790 Q_snprintf(tempstr,sizeof(tempstr),"STARTSCRIPTING" );
8791 EntityText(text_offset,tempstr,0);
8792 text_offset++;
8793 }
8794
8795 // -----------------
8796 // Print MotionType
8797 // -----------------
8798 int navTypeIndex = (int)GetNavType() + 1;
8799 static const char *pMoveNames[] = { "None", "Ground", "Jump", "Fly", "Climb" };
8800 Assert( navTypeIndex >= 0 && navTypeIndex < ARRAYSIZE(pMoveNames) );
8801 if ( navTypeIndex < ARRAYSIZE(pMoveNames) )
8802 {
8803 Q_snprintf(tempstr,sizeof(tempstr),"Move: %s, ", pMoveNames[navTypeIndex] );
8804 EntityText(text_offset,tempstr,0);
8805 text_offset++;
8806 }
8807
8808 // --------------
8809 // Print Schedule
8810 // --------------
8811 if ( GetCurSchedule() )
8812 {
8813 CAI_BehaviorBase *pBehavior = GetRunningBehavior();
8814 if ( pBehavior )
8815 {
8816 Q_snprintf(tempstr,sizeof(tempstr),"Behv: %s, ", pBehavior->GetName() );
8817 EntityText(text_offset,tempstr,0);
8818 text_offset++;
8819 }
8820
8821 const char *pName = NULL;
8822 pName = GetCurSchedule()->GetName();
8823 if ( !pName )
8824 {
8825 pName = "Unknown";
8826 }
8827 Q_snprintf(tempstr,sizeof(tempstr),"Schd: %s, ", pName );
8828 EntityText(text_offset,tempstr,0);
8829 text_offset++;
8830
8831 if (m_debugOverlays & OVERLAY_NPC_TASK_BIT)
8832 {
8833 for (int i = 0 ; i < GetCurSchedule()->NumTasks(); i++)
8834 {
8835 Q_snprintf(tempstr,sizeof(tempstr),"%s%s%s%s",
8836 ((i==0) ? "Task:":" "),
8837 ((i==GetScheduleCurTaskIndex()) ? "->" :" "),
8838 TaskName(GetCurSchedule()->GetTaskList()[i].iTask),
8839 ((i==GetScheduleCurTaskIndex()) ? "<-" :""));
8840
8841 EntityText(text_offset,tempstr,0);
8842 text_offset++;
8843 }
8844 }
8845 else
8846 {
8847 const Task_t *pTask = GetTask();
8848 if ( pTask )
8849 {
8850 Q_snprintf(tempstr,sizeof(tempstr),"Task: %s (#%d), ", TaskName(pTask->iTask), GetScheduleCurTaskIndex() );
8851 }
8852 else
8853 {
8854 Q_strncpy(tempstr,"Task: None",sizeof(tempstr));
8855 }
8856 EntityText(text_offset,tempstr,0);
8857 text_offset++;
8858 }
8859 }
8860
8861 // --------------
8862 // Print Acitivity
8863 // --------------
8864 if( m_Activity != ACT_INVALID && m_IdealActivity != ACT_INVALID && m_Activity != ACT_RESET)
8865 {
8866 Activity iActivity = TranslateActivity( m_Activity );
8867
8868 Activity iIdealActivity = Weapon_TranslateActivity( m_IdealActivity );
8869 iIdealActivity = NPC_TranslateActivity( iIdealActivity );
8870
8871 const char *pszActivity = GetActivityName( iActivity );
8872 const char *pszIdealActivity = GetActivityName( iIdealActivity );
8873 const char *pszRootActivity = GetActivityName( m_Activity );
8874
8875 Q_snprintf(tempstr,sizeof(tempstr),"Actv: %s (%s) [%s]\n", pszActivity, pszIdealActivity, pszRootActivity );
8876 }
8877 else if (m_Activity == ACT_RESET)
8878 {
8879 Q_strncpy(tempstr,"Actv: RESET",sizeof(tempstr) );
8880 }
8881 else
8882 {
8883 Q_strncpy(tempstr,"Actv: INVALID", sizeof(tempstr) );
8884 }
8885 EntityText(text_offset,tempstr,0);
8886 text_offset++;
8887
8888 //
8889 // Print all the current conditions.
8890 //
8891 if (m_debugOverlays & OVERLAY_NPC_CONDITIONS_BIT)
8892 {
8893 bool bHasConditions = false;
8894 for (int i = 0; i < MAX_CONDITIONS; i++)
8895 {
8896 if (m_Conditions.GetBit(i))
8897 {
8898 Q_snprintf(tempstr, sizeof(tempstr), "Cond: %s\n", ConditionName(AI_RemapToGlobal(i)));
8899 EntityText(text_offset, tempstr, 0);
8900 text_offset++;
8901 bHasConditions = true;
8902 }
8903 }
8904 if (!bHasConditions)
8905 {
8906 Q_snprintf(tempstr,sizeof(tempstr),"(no conditions)",m_iHealth);
8907 EntityText(text_offset,tempstr,0);
8908 text_offset++;
8909 }
8910 }
8911
8912 if ( GetFlags() & FL_FLY )
8913 {
8914 EntityText(text_offset,"HAS FL_FLY",0);
8915 text_offset++;
8916 }
8917
8918 // --------------
8919 // Print Interrupte
8920 // --------------
8921 if (m_interuptSchedule)
8922 {
8923 const char *pName = NULL;
8924 pName = m_interuptSchedule->GetName();
8925 if ( !pName )
8926 {
8927 pName = "Unknown";
8928 }
8929
8930 Q_snprintf(tempstr,sizeof(tempstr),"Intr: %s (%s)\n", pName, m_interruptText );
8931 EntityText(text_offset,tempstr,0);
8932 text_offset++;
8933 }
8934
8935 // --------------
8936 // Print Failure
8937 // --------------
8938 if (m_failedSchedule)
8939 {
8940 const char *pName = NULL;
8941 pName = m_failedSchedule->GetName();
8942 if ( !pName )
8943 {
8944 pName = "Unknown";
8945 }
8946 Q_snprintf(tempstr,sizeof(tempstr),"Fail: %s (%s)\n", pName,m_failText );
8947 EntityText(text_offset,tempstr,0);
8948 text_offset++;
8949 }
8950
8951
8952 // -------------------------------
8953 // Print any important condtions
8954 // -------------------------------
8955 if (HasCondition(COND_ENEMY_TOO_FAR))
8956 {
8957 EntityText(text_offset,"Enemy too far to attack",0);
8958 text_offset++;
8959 }
8960 if ( GetAbsVelocity() != vec3_origin || GetLocalAngularVelocity() != vec3_angle )
8961 {
8962 char tmp[512];
8963 Q_snprintf( tmp, sizeof(tmp), "Vel %.1f %.1f %.1f Ang: %.1f %.1f %.1f\n",
8964 GetAbsVelocity().x, GetAbsVelocity().y, GetAbsVelocity().z,
8965 GetLocalAngularVelocity().x, GetLocalAngularVelocity().y, GetLocalAngularVelocity().z );
8966 EntityText(text_offset,tmp,0);
8967 text_offset++;
8968 }
8969
8970 // -------------------------------
8971 // Print shot accuracy
8972 // -------------------------------
8973 if ( m_LastShootAccuracy != -1 && ai_shot_stats.GetBool() )
8974 {
8975 CFmtStr msg;
8976 EntityText(text_offset,msg.sprintf("Cur Accuracy: %.1f", m_LastShootAccuracy),0);
8977 text_offset++;
8978 if ( m_TotalShots )
8979 {
8980 EntityText(text_offset,msg.sprintf("Act Accuracy: %.1f", ((float)m_TotalHits/(float)m_TotalShots)*100.0),0);
8981 text_offset++;
8982 }
8983
8984 if ( GetActiveWeapon() && GetEnemy() )
8985 {
8986 Vector curSpread = GetAttackSpread(GetActiveWeapon(), GetEnemy());
8987 float curCone = RAD2DEG(asin(curSpread.x)) * 2;
8988 float bias = GetSpreadBias( GetActiveWeapon(), GetEnemy());
8989 EntityText(text_offset,msg.sprintf("Cone %.1f, Bias %.2f", curCone, bias),0);
8990 text_offset++;
8991 }
8992 }
8993
8994 if ( GetGoalEnt() && GetNavigator()->GetGoalType() == GOALTYPE_PATHCORNER )
8995 {
8996 Q_strncpy(tempstr,"Pathcorner/goal ent: ",sizeof(tempstr));
8997 if (GetGoalEnt()->GetEntityName() != NULL_STRING)
8998 {
8999 Q_strncat(tempstr,STRING(GetGoalEnt()->GetEntityName()),sizeof(tempstr), COPY_ALL_CHARACTERS);
9000 }
9001 else
9002 {
9003 Q_strncat(tempstr,STRING(GetGoalEnt()->m_iClassname),sizeof(tempstr), COPY_ALL_CHARACTERS);
9004 }
9005 EntityText(text_offset, tempstr, 0);
9006 text_offset++;
9007 }
9008
9009 if ( VPhysicsGetObject() )
9010 {
9011 vphysics_objectstress_t stressOut;
9012 CalculateObjectStress( VPhysicsGetObject(), this, &stressOut );
9013 Q_snprintf(tempstr, sizeof(tempstr),"Stress: %.2f", stressOut.receivedStress );
9014 EntityText(text_offset, tempstr, 0);
9015 text_offset++;
9016 }
9017 if ( m_pSquad )
9018 {
9019 if( m_pSquad->IsLeader(this) )
9020 {
9021 Q_snprintf(tempstr, sizeof(tempstr),"**Squad Leader**" );
9022 EntityText(text_offset, tempstr, 0);
9023 text_offset++;
9024 }
9025
9026 Q_snprintf(tempstr, sizeof(tempstr), "SquadSlot:%s", GetSquadSlotDebugName( GetMyStrategySlot() ) );
9027 EntityText(text_offset, tempstr, 0);
9028 text_offset++;
9029 }
9030 }
9031 return text_offset;
9032}
9033
9034
9035//-----------------------------------------------------------------------------
9036// Purpose: Displays information in the console about the state of this npc.
9037//-----------------------------------------------------------------------------
9038void CAI_BaseNPC::ReportAIState( void )
9039{
9040 static const char *pStateNames[] = { "None", "Idle", "Alert", "Combat", "Scripted", "PlayDead", "Dead" };
9041
9042 DevMsg( "%s: ", GetClassname() );
9043 if ( (int)m_NPCState < ARRAYSIZE(pStateNames) )
9044 DevMsg( "State: %s, ", pStateNames[m_NPCState] );
9045
9046 if( m_Activity != ACT_INVALID && m_IdealActivity != ACT_INVALID )
9047 {
9048 const char *pszActivity = GetActivityName(m_Activity);
9049 const char *pszIdealActivity = GetActivityName(m_IdealActivity);
9050
9051 DevMsg( "Activity: %s - Ideal Activity: %s\n", pszActivity, pszIdealActivity );
9052 }
9053
9054 if ( GetCurSchedule() )
9055 {
9056 const char *pName = NULL;
9057 pName = GetCurSchedule()->GetName();
9058 if ( !pName )
9059 pName = "Unknown";
9060 DevMsg( "Schedule %s, ", pName );
9061 const Task_t *pTask = GetTask();
9062 if ( pTask )
9063 DevMsg( "Task %d (#%d), ", pTask->iTask, GetScheduleCurTaskIndex() );
9064 }
9065 else
9066 DevMsg( "No Schedule, " );
9067
9068 if ( GetEnemy() != NULL )
9069 {
9070 g_pEffects->Sparks( GetEnemy()->GetAbsOrigin() + Vector( 0, 0, 64 ) );
9071 DevMsg( "\nEnemy is %s", GetEnemy()->GetClassname() );
9072 }
9073 else
9074 DevMsg( "No enemy " );
9075
9076 if ( IsMoving() )
9077 {
9078 DevMsg( " Moving " );
9079 if ( m_flMoveWaitFinished > gpGlobals->curtime )
9080 DevMsg( ": Stopped for %.2f. ", m_flMoveWaitFinished - gpGlobals->curtime );
9081 else if ( m_IdealActivity == GetStoppedActivity() )
9082 DevMsg( ": In stopped anim. " );
9083 }
9084
9085 DevMsg( "Leader." );
9086
9087 DevMsg( "\n" );
9088 DevMsg( "Yaw speed:%3.1f,Health: %3d\n", GetMotor()->GetYawSpeed(), m_iHealth );
9089
9090 if ( GetGroundEntity() )
9091 {
9092 DevMsg( "Groundent:%s\n\n", GetGroundEntity()->GetClassname() );
9093 }
9094 else
9095 {
9096 DevMsg( "Groundent: NULL\n\n" );
9097 }
9098}
9099
9100//-----------------------------------------------------------------------------
9101
9102ConVar ai_report_task_timings_on_limit( "ai_report_task_timings_on_limit", "0", FCVAR_ARCHIVE );
9103ConVar ai_think_limit_label( "ai_think_limit_label", "0", FCVAR_ARCHIVE );
9104
9105void CAI_BaseNPC::ReportOverThinkLimit( float time )
9106{
9107 DevMsg( "%s thinking for %.02fms!!! (%s); r%.2f (c%.2f, pst%.2f, ms%.2f), p-r%.2f, m%.2f\n",
9108 GetDebugName(), time, GetCurSchedule()->GetName(),
9109 g_AIRunTimer.GetDuration().GetMillisecondsF(),
9110 g_AIConditionsTimer.GetDuration().GetMillisecondsF(),
9111 g_AIPrescheduleThinkTimer.GetDuration().GetMillisecondsF(),
9112 g_AIMaintainScheduleTimer.GetDuration().GetMillisecondsF(),
9113 g_AIPostRunTimer.GetDuration().GetMillisecondsF(),
9114 g_AIMoveTimer.GetDuration().GetMillisecondsF() );
9115
9116 if (ai_think_limit_label.GetBool())
9117 {
9118 Vector tmp;
9119 CollisionProp()->NormalizedToWorldSpace( Vector( 0.5f, 0.5f, 1.0f ), &tmp );
9120 tmp.z += 16;
9121
9122 float max = -1;
9123 const char *pszMax = "unknown";
9124
9125 if ( g_AIConditionsTimer.GetDuration().GetMillisecondsF() > max )
9126 {
9127 max = g_AIConditionsTimer.GetDuration().GetMillisecondsF();
9128 pszMax = "Conditions";
9129 }
9130 if ( g_AIPrescheduleThinkTimer.GetDuration().GetMillisecondsF() > max )
9131 {
9132 max = g_AIPrescheduleThinkTimer.GetDuration().GetMillisecondsF();
9133 pszMax = "Pre-think";
9134 }
9135 if ( g_AIMaintainScheduleTimer.GetDuration().GetMillisecondsF() > max )
9136 {
9137 max = g_AIMaintainScheduleTimer.GetDuration().GetMillisecondsF();
9138 pszMax = "Schedule";
9139 }
9140 if ( g_AIPostRunTimer.GetDuration().GetMillisecondsF() > max )
9141 {
9142 max = g_AIPostRunTimer.GetDuration().GetMillisecondsF();
9143 pszMax = "Post-run";
9144 }
9145 if ( g_AIMoveTimer.GetDuration().GetMillisecondsF() > max )
9146 {
9147 max = g_AIMoveTimer.GetDuration().GetMillisecondsF();
9148 pszMax = "Move";
9149 }
9150 NDebugOverlay::Text( tmp, (char*)(const char *)CFmtStr( "Slow %.1f, %s %.1f ", time, pszMax, max ), false, 1 );
9151 }
9152
9153 if ( ai_report_task_timings_on_limit.GetBool() )
9154 DumpTaskTimings();
9155}
9156
9157//-----------------------------------------------------------------------------
9158// Purpose: Returns whether or not this npc can play the scripted sequence or AI
9159// sequence that is trying to possess it. If DisregardState is set, the npc
9160// will be sucked into the script no matter what state it is in. ONLY
9161// Scripted AI ents should allow this.
9162// Input : fDisregardNPCState -
9163// interruptLevel -
9164// eMode - If the function returns true, eMode will be one of the following values:
9165// CAN_PLAY_NOW
9166// CAN_PLAY_ENQUEUED
9167// Output :
9168//-----------------------------------------------------------------------------
9169CanPlaySequence_t CAI_BaseNPC::CanPlaySequence( bool fDisregardNPCState, int interruptLevel )
9170{
9171 CanPlaySequence_t eReturn = CAN_PLAY_NOW;
9172
9173 if ( m_hCine )
9174 {
9175 if ( !m_hCine->CanEnqueueAfter() )
9176 {
9177 // npc is already running one scripted sequence and has an important script to play next
9178 return CANNOT_PLAY;
9179 }
9180
9181 eReturn = CAN_PLAY_ENQUEUED;
9182 }
9183
9184 if ( !IsAlive() )
9185 {
9186 // npc is dead!
9187 return CANNOT_PLAY;
9188 }
9189
9190 if ( fDisregardNPCState )
9191 {
9192 // ok to go, no matter what the npc state. (scripted AI)
9193
9194 // No clue as to how to proced if they're climbing or jumping
9195 // Assert( GetNavType() != NAV_JUMP && GetNavType() != NAV_CLIMB );
9196
9197 return eReturn;
9198 }
9199
9200 if ( m_NPCState == NPC_STATE_NONE || m_NPCState == NPC_STATE_IDLE || m_IdealNPCState == NPC_STATE_IDLE )
9201 {
9202 // ok to go, but only in these states
9203 return eReturn;
9204 }
9205
9206 if ( m_NPCState == NPC_STATE_ALERT && interruptLevel >= SS_INTERRUPT_BY_NAME )
9207 {
9208 return eReturn;
9209 }
9210
9211 // unknown situation
9212 return CANNOT_PLAY;
9213}
9214
9215
9216//-----------------------------------------------------------------------------
9217
9218void CAI_BaseNPC::SetHintGroup( string_t newGroup, bool bHintGroupNavLimiting )
9219{
9220 string_t oldGroup = m_strHintGroup;
9221 m_strHintGroup = newGroup;
9222 m_bHintGroupNavLimiting = bHintGroupNavLimiting;
9223
9224 if ( oldGroup != newGroup )
9225 OnChangeHintGroup( oldGroup, newGroup );
9226
9227}
9228
9229//-----------------------------------------------------------------------------
9230// Purpose:
9231// Input :
9232// Output :
9233//-----------------------------------------------------------------------------
9234Vector CAI_BaseNPC::GetShootEnemyDir( const Vector &shootOrigin, bool bNoisy )
9235{
9236 CBaseEntity *pEnemy = GetEnemy();
9237
9238 if ( pEnemy )
9239 {
9240 Vector vecEnemyLKP = GetEnemyLKP();
9241 Vector retval = (pEnemy->BodyTarget( shootOrigin, bNoisy ) - pEnemy->GetAbsOrigin()) + vecEnemyLKP - shootOrigin;
9242 VectorNormalize( retval );
9243 return retval;
9244 }
9245 else
9246 {
9247 Vector forward;
9248 AngleVectors( GetLocalAngles(), &forward );
9249 return forward;
9250 }
9251}
9252
9253//-----------------------------------------------------------------------------
9254// Simulates many times and reports statistical accuracy.
9255//-----------------------------------------------------------------------------
9256void CAI_BaseNPC::CollectShotStats( const Vector &vecShootOrigin, const Vector &vecShootDir )
9257{
9258#ifdef HL2_DLL
9259 if( ai_shot_stats.GetBool() != 0 && GetEnemy()->IsPlayer() )
9260 {
9261 int iterations = ai_shot_stats_term.GetInt();
9262 int iHits = 0;
9263 Vector testDir = vecShootDir;
9264
9265 CShotManipulator manipulator( testDir );
9266
9267 for( int i = 0 ; i < iterations ; i++ )
9268 {
9269 // Apply appropriate accuracy.
9270 manipulator.ApplySpread( GetAttackSpread( GetActiveWeapon(), GetEnemy() ), GetSpreadBias( GetActiveWeapon(), GetEnemy() ) );
9271 Vector shotDir = manipulator.GetResult();
9272
9273 Vector vecEnd = vecShootOrigin + shotDir * 8192;
9274
9275 trace_t tr;
9276 AI_TraceLine( vecShootOrigin, vecEnd, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr);
9277
9278 if( tr.m_pEnt && tr.m_pEnt == GetEnemy() )
9279 {
9280 iHits++;
9281 }
9282 Vector vecProjectedPosition = GetActualShootPosition( vecShootOrigin );
9283 Vector testDir = vecProjectedPosition - vecShootOrigin;
9284 VectorNormalize( testDir );
9285 manipulator.SetShootDir( testDir );
9286 }
9287
9288 float flHitPercent = ((float)iHits / (float)iterations) * 100.0;
9289 m_LastShootAccuracy = flHitPercent;
9290 //DevMsg("Shots:%d Hits:%d Percentage:%.1f\n", iterations, iHits, flHitPercent);
9291 }
9292 else
9293 {
9294 m_LastShootAccuracy = -1;
9295 }
9296#endif
9297}
9298
9299#ifdef HL2_DLL
9300//-----------------------------------------------------------------------------
9301// Purpose: Return the actual position the NPC wants to fire at when it's trying
9302// to hit it's current enemy.
9303//-----------------------------------------------------------------------------
9304Vector CAI_BaseNPC::GetActualShootPosition( const Vector &shootOrigin )
9305{
9306 // Project the target's location into the future.
9307 Vector vecEnemyLKP = GetEnemyLKP();
9308 Vector vecTargetPosition = (GetEnemy()->BodyTarget( shootOrigin ) - GetEnemy()->GetAbsOrigin()) + vecEnemyLKP;
9309
9310 // lead for some fraction of a second.
9311 return (vecTargetPosition + ( GetEnemy()->GetSmoothedVelocity() * ai_lead_time.GetFloat() ));
9312}
9313
9314//-----------------------------------------------------------------------------
9315// Purpose:
9316//-----------------------------------------------------------------------------
9317float CAI_BaseNPC::GetSpreadBias( CBaseCombatWeapon *pWeapon, CBaseEntity *pTarget )
9318{
9319 float bias = BaseClass::GetSpreadBias( pWeapon, pTarget );
9320 AI_EnemyInfo_t *pEnemyInfo = m_pEnemies->Find( pTarget );
9321 if ( ai_shot_bias.GetFloat() != 1.0 )
9322 bias = ai_shot_bias.GetFloat();
9323 if ( pEnemyInfo )
9324 {
9325 float timeToFocus = ai_spread_pattern_focus_time.GetFloat();
9326 if ( timeToFocus > 0.0 )
9327 {
9328 float timeSinceValidEnemy = gpGlobals->curtime - pEnemyInfo->timeValidEnemy;
9329 if ( timeSinceValidEnemy < 0.0f )
9330 {
9331 timeSinceValidEnemy = 0.0f;
9332 }
9333 float timeSinceReacquire = gpGlobals->curtime - pEnemyInfo->timeLastReacquired;
9334 if ( timeSinceValidEnemy < timeToFocus )
9335 {
9336 float scale = timeSinceValidEnemy / timeToFocus;
9337 Assert( scale >= 0.0 && scale <= 1.0 );
9338 bias *= scale;
9339 }
9340 else if ( timeSinceReacquire < timeToFocus ) // handled seperately as might be tuning seperately
9341 {
9342 float scale = timeSinceReacquire / timeToFocus;
9343 Assert( scale >= 0.0 && scale <= 1.0 );
9344 bias *= scale;
9345 }
9346
9347 }
9348 }
9349 return bias;
9350}
9351
9352//-----------------------------------------------------------------------------
9353Vector CAI_BaseNPC::GetAttackSpread( CBaseCombatWeapon *pWeapon, CBaseEntity *pTarget )
9354{
9355 Vector baseResult = BaseClass::GetAttackSpread( pWeapon, pTarget );
9356
9357 AI_EnemyInfo_t *pEnemyInfo = m_pEnemies->Find( pTarget );
9358 if ( pEnemyInfo )
9359 {
9360 float timeToFocus = ai_spread_cone_focus_time.GetFloat();
9361 if ( timeToFocus > 0.0 )
9362 {
9363 float timeSinceValidEnemy = gpGlobals->curtime - pEnemyInfo->timeValidEnemy;
9364 if ( timeSinceValidEnemy < 0 )
9365 timeSinceValidEnemy = 0;
9366 if ( timeSinceValidEnemy < timeToFocus )
9367 {
9368 float coneMultiplier = ai_spread_defocused_cone_multiplier.GetFloat();
9369 if ( coneMultiplier > 1.0 )
9370 {
9371 float scale = 1.0 - timeSinceValidEnemy / timeToFocus;
9372 Assert( scale >= 0.0 && scale <= 1.0 );
9373 float multiplier = ( (coneMultiplier - 1.0) * scale ) + 1.0;
9374 baseResult *= multiplier;
9375 }
9376 }
9377 }
9378 }
9379 return baseResult;
9380}
9381
9382//-----------------------------------------------------------------------------
9383// Similar to calling GetShootEnemyDir, but returns the exact trajectory to
9384// fire the bullet along, after calculating for target speed, location,
9385// concealment, etc.
9386//
9387// Ultimately, this code results in the shooter aiming his weapon somewhere ahead of
9388// a moving target. Since bullet traces are instant, aiming ahead of a target
9389// will result in more misses than hits. This is designed to provide targets with
9390// a bonus when moving perpendicular to the shooter's line of sight.
9391//
9392// Do not confuse this with leading a target in an effort to more easily hit it.
9393//
9394// This code PENALIZES a target for moving directly along the shooter's line of sight.
9395//
9396// This code REWARDS a target for moving perpendicular to the shooter's line of sight.
9397//-----------------------------------------------------------------------------
9398Vector CAI_BaseNPC::GetActualShootTrajectory( const Vector &shootOrigin )
9399{
9400 if( !GetEnemy() )
9401 return GetShootEnemyDir( shootOrigin );
9402
9403 // If we're above water shooting at a player underwater, bias some of the shots forward of
9404 // the player so that they see the cool bubble trails in the water ahead of them.
9405 if (GetEnemy()->IsPlayer() && (GetWaterLevel() != 3) && (GetEnemy()->GetWaterLevel() == 3))
9406 {
9407#if 1
9408 if (random->RandomInt(0, 4) < 3)
9409 {
9410 Vector vecEnemyForward;
9411 GetEnemy()->GetVectors( &vecEnemyForward, NULL, NULL );
9412 vecEnemyForward.z = 0;
9413
9414 // Lead up to a second ahead of them unless they are moving backwards.
9415 Vector vecEnemyVelocity = GetEnemy()->GetSmoothedVelocity();
9416 VectorNormalize( vecEnemyVelocity );
9417 float flVelocityScale = vecEnemyForward.Dot( vecEnemyVelocity );
9418 if ( flVelocityScale < 0.0f )
9419 {
9420 flVelocityScale = 0.0f;
9421 }
9422
9423 Vector vecAimPos = GetEnemy()->EyePosition() + ( 48.0f * vecEnemyForward ) + (flVelocityScale * GetEnemy()->GetSmoothedVelocity() );
9424 //NDebugOverlay::Cross3D(vecAimPos, Vector(-16,-16,-16), Vector(16,16,16), 255, 255, 0, true, 1.0f );
9425
9426 //vecAimPos.z = UTIL_WaterLevel( vecAimPos, vecAimPos.z, vecAimPos.z + 400.0f );
9427 //NDebugOverlay::Cross3D(vecAimPos, Vector(-32,-32,-32), Vector(32,32,32), 255, 0, 0, true, 1.0f );
9428
9429 Vector vecShotDir = vecAimPos - shootOrigin;
9430 VectorNormalize( vecShotDir );
9431 return vecShotDir;
9432 }
9433#else
9434 if (random->RandomInt(0, 4) < 3)
9435 {
9436 // Aim at a point a few feet in front of the player's eyes
9437 Vector vecEnemyForward;
9438 GetEnemy()->GetVectors( &vecEnemyForward, NULL, NULL );
9439
9440 Vector vecAimPos = GetEnemy()->EyePosition() + (120.0f * vecEnemyForward );
9441
9442 Vector vecShotDir = vecAimPos - shootOrigin;
9443 VectorNormalize( vecShotDir );
9444
9445 CShotManipulator manipulator( vecShotDir );
9446 manipulator.ApplySpread( VECTOR_CONE_10DEGREES, 1 );
9447 vecShotDir = manipulator.GetResult();
9448
9449 return vecShotDir;
9450 }
9451#endif
9452 }
9453
9454 Vector vecProjectedPosition = GetActualShootPosition( shootOrigin );
9455
9456 Vector shotDir = vecProjectedPosition - shootOrigin;
9457 VectorNormalize( shotDir );
9458
9459 CollectShotStats( shootOrigin, shotDir );
9460
9461 // NOW we have a shoot direction. Where a 100% accurate bullet should go.
9462 // Modify it by weapon proficiency.
9463 // construct a manipulator
9464 CShotManipulator manipulator( shotDir );
9465
9466 // Apply appropriate accuracy.
9467 bool bUsePerfectAccuracy = false;
9468 if ( GetEnemy() && GetEnemy()->Classify() == CLASS_BULLSEYE )
9469 {
9470 CNPC_Bullseye *pBullseye = dynamic_cast<CNPC_Bullseye*>(GetEnemy());
9471 if ( pBullseye && pBullseye->UsePerfectAccuracy() )
9472 {
9473 bUsePerfectAccuracy = true;
9474 }
9475 }
9476
9477 if ( !bUsePerfectAccuracy )
9478 {
9479 manipulator.ApplySpread( GetAttackSpread( GetActiveWeapon(), GetEnemy() ), GetSpreadBias( GetActiveWeapon(), GetEnemy() ) );
9480 shotDir = manipulator.GetResult();
9481 }
9482
9483 // Look for an opportunity to make misses hit interesting things.
9484 CBaseCombatCharacter *pEnemy;
9485
9486 pEnemy = GetEnemy()->MyCombatCharacterPointer();
9487
9488 if( pEnemy && pEnemy->ShouldShootMissTarget( this ) )
9489 {
9490 Vector vecEnd = shootOrigin + shotDir * 8192;
9491 trace_t tr;
9492
9493 AI_TraceLine(shootOrigin, vecEnd, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr);
9494
9495 if( tr.fraction != 1.0 && tr.m_pEnt && tr.m_pEnt->m_takedamage != DAMAGE_NO )
9496 {
9497 // Hit something we can harm. Just shoot it.
9498 return manipulator.GetResult();
9499 }
9500
9501 // Find something interesting around the enemy to shoot instead of just missing.
9502 CBaseEntity *pMissTarget = pEnemy->FindMissTarget();
9503
9504 if( pMissTarget )
9505 {
9506 shotDir = pMissTarget->WorldSpaceCenter() - shootOrigin;
9507 VectorNormalize( shotDir );
9508 }
9509 }
9510
9511 return shotDir;
9512}
9513#endif HL2_DLL
9514
9515//-----------------------------------------------------------------------------
9516
9517Vector CAI_BaseNPC::BodyTarget( const Vector &posSrc, bool bNoisy )
9518{
9519 Vector low = WorldSpaceCenter() - ( WorldSpaceCenter() - GetAbsOrigin() ) * .25;
9520 Vector high = EyePosition();
9521 Vector delta = high - low;
9522 Vector result;
9523 if ( bNoisy )
9524 {
9525 // bell curve
9526 float rand1 = random->RandomFloat( 0.0, 0.5 );
9527 float rand2 = random->RandomFloat( 0.0, 0.5 );
9528 result = low + delta * rand1 + delta * rand2;
9529 }
9530 else
9531 result = low + delta * 0.5;
9532
9533 return result;
9534}
9535
9536//-----------------------------------------------------------------------------
9537
9538bool CAI_BaseNPC::ShouldMoveAndShoot()
9539{
9540 return ( ( CapabilitiesGet() & bits_CAP_MOVE_SHOOT ) != 0 );
9541}
9542
9543
9544//=========================================================
9545// FacingIdeal - tells us if a npc is facing its ideal
9546// yaw. Created this function because many spots in the
9547// code were checking the yawdiff against this magic
9548// number. Nicer to have it in one place if we're gonna
9549// be stuck with it.
9550//=========================================================
9551bool CAI_BaseNPC::FacingIdeal( void )
9552{
9553 if ( fabs( GetMotor()->DeltaIdealYaw() ) <= 0.006 )//!!!BUGBUG - no magic numbers!!!
9554 {
9555 return true;
9556 }
9557
9558 return false;
9559}
9560
9561//---------------------------------
9562
9563void CAI_BaseNPC::AddFacingTarget( CBaseEntity *pTarget, float flImportance, float flDuration, float flRamp )
9564{
9565 GetMotor()->AddFacingTarget( pTarget, flImportance, flDuration, flRamp );
9566}
9567
9568void CAI_BaseNPC::AddFacingTarget( const Vector &vecPosition, float flImportance, float flDuration, float flRamp )
9569{
9570 GetMotor()->AddFacingTarget( vecPosition, flImportance, flDuration, flRamp );
9571}
9572
9573void CAI_BaseNPC::AddFacingTarget( CBaseEntity *pTarget, const Vector &vecPosition, float flImportance, float flDuration, float flRamp )
9574{
9575 GetMotor()->AddFacingTarget( pTarget, vecPosition, flImportance, flDuration, flRamp );
9576}
9577
9578float CAI_BaseNPC::GetFacingDirection( Vector &vecDir )
9579{
9580 return (GetMotor()->GetFacingDirection( vecDir ));
9581}
9582
9583
9584//---------------------------------
9585
9586
9587int CAI_BaseNPC::PlaySentence( const char *pszSentence, float delay, float volume, soundlevel_t soundlevel, CBaseEntity *pListener )
9588{
9589 int sentenceIndex = -1;
9590
9591 if ( pszSentence && IsAlive() )
9592 {
9593
9594 if ( pszSentence[0] == '!' )
9595 {
9596 sentenceIndex = SENTENCEG_Lookup( pszSentence );
9597 CPASAttenuationFilter filter( this, soundlevel );
9598 CBaseEntity::EmitSentenceByIndex( filter, entindex(), CHAN_VOICE, sentenceIndex, volume, soundlevel, 0, PITCH_NORM );
9599 }
9600 else
9601 {
9602 sentenceIndex = SENTENCEG_PlayRndSz( edict(), pszSentence, volume, soundlevel, 0, PITCH_NORM );
9603 }
9604 }
9605
9606 return sentenceIndex;
9607}
9608
9609
9610int CAI_BaseNPC::PlayScriptedSentence( const char *pszSentence, float delay, float volume, soundlevel_t soundlevel, bool bConcurrent, CBaseEntity *pListener )
9611{
9612 return PlaySentence( pszSentence, delay, volume, soundlevel, pListener );
9613}
9614
9615
9616void CAI_BaseNPC::SentenceStop( void )
9617{
9618 EmitSound( "AI_BaseNPC.SentenceStop" );
9619}
9620
9621
9622
9623
9624//-----------------------------------------------------------------------------
9625// Purpose: Play a one-shot scene
9626// Input :
9627// Output :
9628//-----------------------------------------------------------------------------
9629float CAI_BaseNPC::PlayScene( const char *pszScene, float flDelay, AI_Response *response )
9630{
9631 return InstancedScriptedScene( this, pszScene, NULL, flDelay, false, response );
9632}
9633
9634//-----------------------------------------------------------------------------
9635// Purpose: Generate a one-shot scene in memory with one track which is to play the named sound on the actor
9636// Input : *soundname -
9637// Output : float
9638//-----------------------------------------------------------------------------
9639float CAI_BaseNPC::PlayAutoGeneratedSoundScene( const char *soundname )
9640{
9641 return InstancedAutoGeneratedSoundScene( this, soundname );
9642}
9643
9644//-----------------------------------------------------------------------------
9645// Purpose:
9646// Input :
9647// Output :
9648//-----------------------------------------------------------------------------
9649CBaseEntity *CAI_BaseNPC::FindNamedEntity( const char *name, IEntityFindFilter *pFilter )
9650{
9651 if ( !stricmp( name, "!player" ))
9652 {
9653 return ( CBaseEntity * )AI_GetSinglePlayer();
9654 }
9655 else if ( !stricmp( name, "!enemy" ) )
9656 {
9657 if (GetEnemy() != NULL)
9658 return GetEnemy();
9659 }
9660 else if ( !stricmp( name, "!self" ) || !stricmp( name, "!target1" ) )
9661 {
9662 return this;
9663 }
9664 else if ( !stricmp( name, "!nearestfriend" ) || !stricmp( name, "!friend" ) )
9665 {
9666 // FIXME: look at CBaseEntity *CNPCSimpleTalker::FindNearestFriend(bool fPlayer)
9667 // punt for now
9668 return ( CBaseEntity * )AI_GetSinglePlayer();
9669 }
9670 else if (!stricmp( name, "self" ))
9671 {
9672 static int selfwarningcount = 0;
9673
9674 // fix the vcd, the reserved names have changed
9675 if ( ++selfwarningcount < 5 )
9676 {
9677 DevMsg( "ERROR: \"self\" is no longer used, use \"!self\" in vcd instead!\n" );
9678 }
9679 return this;
9680 }
9681 else if ( !stricmp( name, "Player" ))
9682 {
9683 static int playerwarningcount = 0;
9684 if ( ++playerwarningcount < 5 )
9685 {
9686 DevMsg( "ERROR: \"player\" is no longer used, use \"!player\" in vcd instead!\n" );
9687 }
9688 return ( CBaseEntity * )AI_GetSinglePlayer();
9689 }
9690 else
9691 {
9692 // search for up to 32 entities with the same name and choose one randomly
9693 CBaseEntity *entityList[ FINDNAMEDENTITY_MAX_ENTITIES ];
9694 CBaseEntity *entity;
9695 int iCount;
9696
9697 entity = NULL;
9698 for( iCount = 0; iCount < FINDNAMEDENTITY_MAX_ENTITIES; iCount++ )
9699 {
9700 entity = gEntList.FindEntityByName( entity, name, NULL, NULL, NULL, pFilter );
9701 if ( !entity )
9702 {
9703 break;
9704 }
9705 entityList[ iCount ] = entity;
9706 }
9707
9708 if ( iCount > 0 )
9709 {
9710 int index = RandomInt( 0, iCount - 1 );
9711 entity = entityList[ index ];
9712 return entity;
9713 }
9714 }
9715
9716 return NULL;
9717}
9718
9719
9720void CAI_BaseNPC::CorpseFallThink( void )
9721{
9722 if ( GetFlags() & FL_ONGROUND )
9723 {
9724 SetThink ( NULL );
9725
9726 SetSequenceBox( );
9727 }
9728 else
9729 {
9730 SetNextThink( gpGlobals->curtime + 0.1f );
9731 }
9732}
9733
9734// Call after animation/pose is set up
9735void CAI_BaseNPC::NPCInitDead( void )
9736{
9737 InitBoneControllers();
9738
9739 RemoveSolidFlags( FSOLID_NOT_SOLID );
9740
9741 // so he'll fall to ground
9742 SetMoveType( MOVETYPE_FLYGRAVITY, MOVECOLLIDE_FLY_BOUNCE );
9743
9744 SetCycle( 0 );
9745 ResetSequenceInfo( );
9746 m_flPlaybackRate = 0;
9747
9748 // Copy health
9749 m_iMaxHealth = m_iHealth;
9750 m_lifeState = LIFE_DEAD;
9751
9752 UTIL_SetSize(this, vec3_origin, vec3_origin );
9753
9754 // Setup health counters, etc.
9755 SetThink( &CAI_BaseNPC::CorpseFallThink );
9756
9757 SetNextThink( gpGlobals->curtime + 0.5f );
9758}
9759
9760//=========================================================
9761// BBoxIsFlat - check to see if the npc's bounding box
9762// is lying flat on a surface (traces from all four corners
9763// are same length.)
9764//=========================================================
9765bool CAI_BaseNPC::BBoxFlat ( void )
9766{
9767 trace_t tr;
9768 Vector vecPoint;
9769 float flXSize, flYSize;
9770 float flLength;
9771 float flLength2;
9772
9773 flXSize = WorldAlignSize().x / 2;
9774 flYSize = WorldAlignSize().y / 2;
9775
9776 vecPoint.x = GetAbsOrigin().x + flXSize;
9777 vecPoint.y = GetAbsOrigin().y + flYSize;
9778 vecPoint.z = GetAbsOrigin().z;
9779
9780 AI_TraceLine ( vecPoint, vecPoint - Vector ( 0, 0, 100 ), MASK_NPCSOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr );
9781 flLength = (vecPoint - tr.endpos).Length();
9782
9783 vecPoint.x = GetAbsOrigin().x - flXSize;
9784 vecPoint.y = GetAbsOrigin().y - flYSize;
9785
9786 AI_TraceLine ( vecPoint, vecPoint - Vector ( 0, 0, 100 ), MASK_NPCSOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr );
9787 flLength2 = (vecPoint - tr.endpos).Length();
9788 if ( flLength2 > flLength )
9789 {
9790 return false;
9791 }
9792 flLength = flLength2;
9793
9794 vecPoint.x = GetAbsOrigin().x - flXSize;
9795 vecPoint.y = GetAbsOrigin().y + flYSize;
9796 AI_TraceLine ( vecPoint, vecPoint - Vector ( 0, 0, 100 ), MASK_NPCSOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr );
9797 flLength2 = (vecPoint - tr.endpos).Length();
9798 if ( flLength2 > flLength )
9799 {
9800 return false;
9801 }
9802 flLength = flLength2;
9803
9804 vecPoint.x = GetAbsOrigin().x + flXSize;
9805 vecPoint.y = GetAbsOrigin().y - flYSize;
9806 AI_TraceLine ( vecPoint, vecPoint - Vector ( 0, 0, 100 ), MASK_NPCSOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr );
9807 flLength2 = (vecPoint - tr.endpos).Length();
9808 if ( flLength2 > flLength )
9809 {
9810 return false;
9811 }
9812 flLength = flLength2;
9813
9814 return true;
9815}
9816
9817
9818//-----------------------------------------------------------------------------
9819// Purpose:
9820// Input : *pEnemy -
9821// bSetCondNewEnemy -
9822//-----------------------------------------------------------------------------
9823void CAI_BaseNPC::SetEnemy( CBaseEntity *pEnemy, bool bSetCondNewEnemy )
9824{
9825 if (m_hEnemy != pEnemy)
9826 {
9827 ClearAttackConditions( );
9828 VacateStrategySlot();
9829 m_GiveUpOnDeadEnemyTimer.Stop();
9830
9831 // If we've just found a new enemy, set the condition
9832 if ( pEnemy && bSetCondNewEnemy )
9833 {
9834 SetCondition( COND_NEW_ENEMY );
9835 }
9836 }
9837
9838 // Assert( (pEnemy == NULL) || (m_NPCState == NPC_STATE_COMBAT) );
9839
9840 m_hEnemy = pEnemy;
9841 m_flTimeEnemyAcquired = gpGlobals->curtime;
9842
9843 m_LastShootAccuracy = -1;
9844 m_TotalShots = 0;
9845 m_TotalHits = 0;
9846
9847 if ( !pEnemy )
9848 ClearCondition( COND_NEW_ENEMY );
9849}
9850
9851const Vector &CAI_BaseNPC::GetEnemyLKP() const
9852{
9853 return (const_cast<CAI_BaseNPC *>(this))->GetEnemies()->LastKnownPosition( GetEnemy() );
9854}
9855
9856float CAI_BaseNPC::GetEnemyLastTimeSeen() const
9857{
9858 return (const_cast<CAI_BaseNPC *>(this))->GetEnemies()->LastTimeSeen( GetEnemy() );
9859}
9860
9861void CAI_BaseNPC::MarkEnemyAsEluded()
9862{
9863 GetEnemies()->MarkAsEluded( GetEnemy() );
9864}
9865
9866void CAI_BaseNPC::ClearEnemyMemory()
9867{
9868 GetEnemies()->ClearMemory( GetEnemy() );
9869}
9870
9871bool CAI_BaseNPC::EnemyHasEludedMe() const
9872{
9873 return (const_cast<CAI_BaseNPC *>(this))->GetEnemies()->HasEludedMe( GetEnemy() );
9874}
9875
9876void CAI_BaseNPC::SetTarget( CBaseEntity *pTarget )
9877{
9878 m_hTargetEnt = pTarget;
9879}
9880
9881
9882//=========================================================
9883// Choose Enemy - tries to find the best suitable enemy for the npc.
9884//=========================================================
9885
9886bool CAI_BaseNPC::ShouldChooseNewEnemy()
9887{
9888 CBaseEntity *pEnemy = GetEnemy();
9889 if ( pEnemy )
9890 {
9891 if ( GetEnemies()->GetSerialNumber() != m_EnemiesSerialNumber )
9892 {
9893 return true;
9894 }
9895
9896 m_EnemiesSerialNumber = GetEnemies()->GetSerialNumber();
9897
9898 if ( EnemyHasEludedMe() || (IRelationType( pEnemy ) != D_HT && IRelationType( pEnemy ) != D_FR) || !IsValidEnemy( pEnemy ) )
9899 {
9900 DbgEnemyMsg( this, "ShouldChooseNewEnemy() --> true (1)\n" );
9901 return true;
9902 }
9903 if ( HasCondition(COND_SEE_HATE) || HasCondition(COND_SEE_DISLIKE) || HasCondition(COND_SEE_NEMESIS) || HasCondition(COND_SEE_FEAR) )
9904 {
9905 DbgEnemyMsg( this, "ShouldChooseNewEnemy() --> true (2)\n" );
9906 return true;
9907 }
9908 if ( !pEnemy->IsAlive() )
9909 {
9910 if ( m_GiveUpOnDeadEnemyTimer.IsRunning() )
9911 {
9912 if ( m_GiveUpOnDeadEnemyTimer.Expired() )
9913 {
9914 DbgEnemyMsg( this, "ShouldChooseNewEnemy() --> true (3)\n" );
9915 return true;
9916 }
9917 }
9918 else
9919 m_GiveUpOnDeadEnemyTimer.Start();
9920 }
9921
9922 AI_EnemyInfo_t *pInfo = GetEnemies()->Find( pEnemy );
9923
9924 if ( m_FailChooseEnemyTimer.Expired() )
9925 {
9926 m_FailChooseEnemyTimer.Set( 1.5 );
9927 if ( HasCondition( COND_TASK_FAILED ) ||
9928 ( pInfo && ( pInfo->timeAtFirstHand == AI_INVALID_TIME || gpGlobals->curtime - pInfo->timeLastSeen > 10 ) ) )
9929 {
9930 return true;
9931 }
9932 }
9933
9934 if ( pInfo && pInfo->timeValidEnemy < gpGlobals->curtime )
9935 {
9936 DbgEnemyMsg( this, "ShouldChooseNewEnemy() --> false\n" );
9937 return false;
9938 }
9939 }
9940
9941 DbgEnemyMsg( this, "ShouldChooseNewEnemy() --> true (4)\n" );
9942 m_EnemiesSerialNumber = GetEnemies()->GetSerialNumber();
9943
9944 return true;
9945}
9946
9947//-------------------------------------
9948
9949bool CAI_BaseNPC::ChooseEnemy( void )
9950{
9951 AI_PROFILE_SCOPE(CAI_Enemies_ChooseEnemy);
9952
9953 DbgEnemyMsg( this, "ChooseEnemy() {\n" );
9954
9955 //---------------------------------
9956 //
9957 // Gather initial conditions
9958 //
9959
9960 CBaseEntity *pInitialEnemy = GetEnemy();
9961 CBaseEntity *pChosenEnemy = pInitialEnemy;
9962
9963 // Use memory bits in case enemy pointer altered outside this function, (e.g., ehandle goes NULL)
9964 bool fHadEnemy = ( HasMemory( bits_MEMORY_HAD_ENEMY | bits_MEMORY_HAD_PLAYER ) );
9965 bool fEnemyWasPlayer = HasMemory( bits_MEMORY_HAD_PLAYER );
9966 bool fEnemyWentNull = ( fHadEnemy && !pInitialEnemy );
9967 bool fEnemyEluded = ( fEnemyWentNull || ( pInitialEnemy && GetEnemies()->HasEludedMe( pInitialEnemy ) ) );
9968
9969 //---------------------------------
9970 //
9971 // Establish suitability of choosing a new enemy
9972 //
9973
9974 bool fHaveCondNewEnemy;
9975 bool fHaveCondLostEnemy;
9976
9977 if ( GetCurSchedule() && !FScheduleDone() )
9978 {
9979 Assert( InterruptFromCondition( COND_NEW_ENEMY ) == COND_NEW_ENEMY && InterruptFromCondition( COND_LOST_ENEMY ) == COND_LOST_ENEMY );
9980 fHaveCondNewEnemy = GetCurSchedule()->HasInterrupt( COND_NEW_ENEMY );
9981 fHaveCondLostEnemy = GetCurSchedule()->HasInterrupt( COND_LOST_ENEMY );
9982
9983 // See if they've been added as a custom interrupt
9984 if ( !fHaveCondNewEnemy )
9985 {
9986 fHaveCondNewEnemy = IsCustomInterruptConditionSet( COND_NEW_ENEMY );
9987 }
9988 if ( !fHaveCondLostEnemy )
9989 {
9990 fHaveCondLostEnemy = IsCustomInterruptConditionSet( COND_LOST_ENEMY );
9991 }
9992 }
9993 else
9994 {
9995 fHaveCondNewEnemy = true; // not having a schedule is the same as being interruptable by any condition
9996 fHaveCondLostEnemy = true;
9997 }
9998
9999 if ( !fEnemyWentNull )
10000 {
10001 if ( !fHaveCondNewEnemy && !( fHaveCondLostEnemy && fEnemyEluded ) )
10002 {
10003 // DO NOT mess with the npc's enemy pointer unless the schedule the npc is currently
10004 // running will be interrupted by COND_NEW_ENEMY or COND_LOST_ENEMY. This will
10005 // eliminate the problem of npcs getting a new enemy while they are in a schedule
10006 // that doesn't care, and then not realizing it by the time they get to a schedule
10007 // that does. I don't feel this is a good permanent fix.
10008 m_bSkippedChooseEnemy = true;
10009
10010 DbgEnemyMsg( this, "Skipped enemy selection due to schedule restriction\n" );
10011 DbgEnemyMsg( this, "}\n" );
10012 return ( pChosenEnemy != NULL );
10013 }
10014 }
10015 else if ( !fHaveCondNewEnemy && !fHaveCondLostEnemy && GetCurSchedule() )
10016 {
10017 DevMsg( 2, "WARNING: AI enemy went NULL but schedule (%s) is not interested\n", GetCurSchedule()->GetName() );
10018 }
10019
10020 m_bSkippedChooseEnemy = false;
10021
10022 //---------------------------------
10023 //
10024 // Select a target
10025 //
10026
10027 if ( ShouldChooseNewEnemy() )
10028 {
10029 pChosenEnemy = BestEnemy();
10030 }
10031
10032 //---------------------------------
10033 //
10034 // React to result of selection
10035 //
10036
10037 bool fChangingEnemy = ( pChosenEnemy != pInitialEnemy );
10038
10039 if ( fChangingEnemy || fEnemyWentNull )
10040 {
10041 DbgEnemyMsg( this, "Enemy changed from %s to %s\n", pInitialEnemy->GetDebugName(), pChosenEnemy->GetDebugName() );
10042 Forget( bits_MEMORY_HAD_ENEMY | bits_MEMORY_HAD_PLAYER );
10043
10044 // Did our old enemy snuff it?
10045 if ( pInitialEnemy && !pInitialEnemy->IsAlive() )
10046 {
10047 SetCondition( COND_ENEMY_DEAD );
10048 }
10049
10050 SetEnemy( pChosenEnemy );
10051
10052 if ( fHadEnemy )
10053 {
10054 // Vacate any strategy slot on old enemy
10055 VacateStrategySlot();
10056
10057 // Force output event for establishing LOS
10058 Forget( bits_MEMORY_HAD_LOS );
10059 // m_flLastAttackTime = 0;
10060 }
10061
10062 if ( !pChosenEnemy )
10063 {
10064 // Don't break on enemies going null if they've been killed
10065 if ( !HasCondition(COND_ENEMY_DEAD) )
10066 {
10067 SetCondition( COND_ENEMY_WENT_NULL );
10068 }
10069
10070 if ( fEnemyEluded )
10071 {
10072 SetCondition( COND_LOST_ENEMY );
10073 LostEnemySound();
10074 }
10075
10076 if ( fEnemyWasPlayer )
10077 {
10078 m_OnLostPlayer.FireOutput( pInitialEnemy, this );
10079 }
10080 m_OnLostEnemy.FireOutput( pInitialEnemy, this);
10081 }
10082 else
10083 {
10084 Remember( ( pChosenEnemy->IsPlayer() ) ? bits_MEMORY_HAD_PLAYER : bits_MEMORY_HAD_ENEMY );
10085 }
10086 }
10087
10088 //---------------------------------
10089
10090 return ( pChosenEnemy != NULL );
10091}
10092
10093
10094//=========================================================
10095void CAI_BaseNPC::PickupWeapon( CBaseCombatWeapon *pWeapon )
10096{
10097 pWeapon->OnPickedUp( this );
10098 Weapon_Equip( pWeapon );
10099 m_iszPendingWeapon = NULL_STRING;
10100}
10101
10102//=========================================================
10103// DropItem - dead npc drops named item
10104//=========================================================
10105CBaseEntity *CAI_BaseNPC::DropItem ( char *pszItemName, Vector vecPos, QAngle vecAng )
10106{
10107 if ( !pszItemName )
10108 {
10109 DevMsg( "DropItem() - No item name!\n" );
10110 return NULL;
10111 }
10112
10113 CBaseEntity *pItem = CBaseEntity::Create( pszItemName, vecPos, vecAng, this );
10114
10115 if ( pItem )
10116 {
10117 if ( g_pGameRules->IsAllowedToSpawn( pItem ) == false )
10118 {
10119 UTIL_Remove( pItem );
10120 return NULL;
10121 }
10122
10123 IPhysicsObject *pPhys = pItem->VPhysicsGetObject();
10124
10125 if ( pPhys )
10126 {
10127 // Add an extra push in a random direction
10128 Vector vel = RandomVector( -64.0f, 64.0f );
10129 AngularImpulse angImp = RandomAngularImpulse( -300.0f, 300.0f );
10130
10131 vel[2] = 0.0f;
10132 pPhys->AddVelocity( &vel, &angImp );
10133 }
10134 else
10135 {
10136 // do we want this behavior to be default?! (sjb)
10137 pItem->ApplyAbsVelocityImpulse( GetAbsVelocity() );
10138 pItem->ApplyLocalAngularVelocityImpulse( AngularImpulse( 0, random->RandomFloat( 0, 100 ), 0 ) );
10139 }
10140
10141 return pItem;
10142 }
10143 else
10144 {
10145 DevMsg( "DropItem() - Didn't create!\n" );
10146 return NULL;
10147 }
10148
10149}
10150
10151bool CAI_BaseNPC::ShouldFadeOnDeath( void )
10152{
10153 if ( g_RagdollLVManager.IsLowViolence() )
10154 {
10155 return true;
10156 }
10157 else
10158 {
10159 // if flagged to fade out
10160 return HasSpawnFlags(SF_NPC_FADE_CORPSE);
10161 }
10162}
10163
10164//-----------------------------------------------------------------------------
10165// Purpose: Indicates whether or not this npc should play an idle sound now.
10166//
10167//
10168// Output : Returns true if yes, false if no.
10169//-----------------------------------------------------------------------------
10170bool CAI_BaseNPC::ShouldPlayIdleSound( void )
10171{
10172 if ( ( m_NPCState == NPC_STATE_IDLE || m_NPCState == NPC_STATE_ALERT ) &&
10173 random->RandomInt(0,99) == 0 && !HasSpawnFlags(SF_NPC_GAG) )
10174 {
10175 return true;
10176 }
10177
10178 return false;
10179}
10180
10181//-----------------------------------------------------------------------------
10182// Purpose: Make a sound that other AI's can hear, to broadcast our presence
10183// Input : volume (radius) of the sound.
10184// Output :
10185//-----------------------------------------------------------------------------
10186void CAI_BaseNPC::MakeAIFootstepSound( float volume, float duration )
10187{
10188 CSoundEnt::InsertSound( SOUND_COMBAT, EyePosition(), volume, duration, this, SOUNDENT_CHANNEL_NPC_FOOTSTEP );
10189}
10190
10191//-----------------------------------------------------------------------------
10192// Purpose:
10193// Input :
10194// Output :
10195//-----------------------------------------------------------------------------
10196bool CAI_BaseNPC::FOkToMakeSound( int soundPriority )
10197{
10198 // ask the squad to filter sounds if I'm in one
10199 if ( m_pSquad )
10200 {
10201 if ( !m_pSquad->FOkToMakeSound( soundPriority ) )
10202 return false;
10203 }
10204 else
10205 {
10206 // otherwise, check my own sound timer
10207 // Am I making uninterruptable sound?
10208 if (gpGlobals->curtime <= m_flSoundWaitTime)
10209 {
10210 if ( soundPriority <= m_nSoundPriority )
10211 return false;
10212 }
10213 }
10214
10215 // no talking outside of combat if gagged.
10216 if ( HasSpawnFlags(SF_NPC_GAG) && ( m_NPCState != NPC_STATE_COMBAT ) )
10217 return false;
10218
10219 return true;
10220}
10221
10222
10223//-----------------------------------------------------------------------------
10224// Purpose:
10225// Input :
10226// Output :
10227//-----------------------------------------------------------------------------
10228void CAI_BaseNPC::JustMadeSound( int soundPriority, float flSoundLength )
10229{
10230 m_flSoundWaitTime = gpGlobals->curtime + flSoundLength + random->RandomFloat(1.5, 2.0);
10231 m_nSoundPriority = soundPriority;
10232
10233 if (m_pSquad)
10234 {
10235 m_pSquad->JustMadeSound( soundPriority, gpGlobals->curtime + flSoundLength + random->RandomFloat(1.5, 2.0) );
10236 }
10237}
10238
10239Activity CAI_BaseNPC::GetStoppedActivity( void )
10240{
10241 if (GetNavigator()->IsGoalActive())
10242 {
10243 Activity activity = GetNavigator()->GetArrivalActivity();
10244
10245 if (activity > ACT_RESET)
10246 {
10247 return activity;
10248 }
10249 }
10250
10251 return ACT_IDLE;
10252}
10253
10254
10255//=========================================================
10256//=========================================================
10257void CAI_BaseNPC::OnScheduleChange ( void )
10258{
10259 EndTaskOverlay();
10260
10261 m_pNavigator->OnScheduleChange();
10262
10263 m_flMoveWaitFinished = 0;
10264
10265 VacateStrategySlot();
10266
10267 // If I still have have a route, clear it
10268 // FIXME: Routes should only be cleared inside of tasks (kenb)
10269 GetNavigator()->ClearGoal();
10270
10271 UnlockBestSound();
10272
10273 // If I locked a hint node clear it
10274 if ( HasMemory(bits_MEMORY_LOCKED_HINT) && GetHintNode() != NULL)
10275 {
10276 float hintDelay = GetHintDelay(GetHintNode()->HintType());
10277 GetHintNode()->Unlock(hintDelay);
10278 SetHintNode( NULL );
10279 }
10280}
10281
10282
10283
10284CBaseCombatCharacter* CAI_BaseNPC::GetEnemyCombatCharacterPointer()
10285{
10286 if ( GetEnemy() == NULL )
10287 return NULL;
10288
10289 return GetEnemy()->MyCombatCharacterPointer();
10290}
10291
10292
10293// Global Savedata for npc
10294//
10295// This should be an exact copy of the var's in the header. Fields
10296// that aren't save/restored are commented out
10297
10298BEGIN_DATADESC( CAI_BaseNPC )
10299
10300 // m_pSchedule (reacquired on restore)
10301 DEFINE_EMBEDDED( m_ScheduleState ),
10302 DEFINE_FIELD( m_IdealSchedule, FIELD_INTEGER ), // handled specially but left in for "virtual" schedules
10303 DEFINE_FIELD( m_failSchedule, FIELD_INTEGER ), // handled specially but left in for "virtual" schedules
10304 DEFINE_FIELD( m_bUsingStandardThinkTime, FIELD_BOOLEAN ),
10305 DEFINE_FIELD( m_flLastRealThinkTime, FIELD_TIME ),
10306 // m_iFrameBlocked (not saved)
10307 // m_bInChoreo (not saved)
10308 // m_bDoPostRestoreRefindPath (not saved)
10309 // gm_flTimeLastSpawn (static)
10310 // gm_nSpawnedThisFrame (static)
10311 // m_Conditions (custom save)
10312 // m_CustomInterruptConditions (custom save)
10313 // m_ConditionsPreIgnore (custom save)
10314 // m_InverseIgnoreConditions (custom save)
10315 DEFINE_FIELD( m_bForceConditionsGather, FIELD_BOOLEAN ),
10316 DEFINE_FIELD( m_bConditionsGathered, FIELD_BOOLEAN ),
10317 DEFINE_FIELD( m_bSkippedChooseEnemy, FIELD_BOOLEAN ),
10318 DEFINE_FIELD( m_NPCState, FIELD_INTEGER ),
10319 DEFINE_FIELD( m_IdealNPCState, FIELD_INTEGER ),
10320 DEFINE_FIELD( m_flLastStateChangeTime, FIELD_TIME ),
10321 DEFINE_FIELD( m_Efficiency, FIELD_INTEGER ),
10322 DEFINE_FIELD( m_MoveEfficiency, FIELD_INTEGER ),
10323 DEFINE_FIELD( m_flNextDecisionTime, FIELD_TIME ),
10324 DEFINE_KEYFIELD( m_SleepState, FIELD_INTEGER, "sleepstate" ),
10325 DEFINE_FIELD( m_SleepFlags, FIELD_INTEGER ),
10326 DEFINE_KEYFIELD( m_flWakeRadius, FIELD_FLOAT, "wakeradius" ),
10327 DEFINE_KEYFIELD( m_bWakeSquad, FIELD_BOOLEAN, "wakesquad" ),
10328 DEFINE_FIELD( m_nWakeTick, FIELD_TICK ),
10329
10330 DEFINE_CUSTOM_FIELD( m_Activity, ActivityDataOps() ),
10331 DEFINE_CUSTOM_FIELD( m_translatedActivity, ActivityDataOps() ),
10332 DEFINE_CUSTOM_FIELD( m_IdealActivity, ActivityDataOps() ),
10333 DEFINE_CUSTOM_FIELD( m_IdealTranslatedActivity, ActivityDataOps() ),
10334 DEFINE_CUSTOM_FIELD( m_IdealWeaponActivity, ActivityDataOps() ),
10335
10336 DEFINE_FIELD( m_nIdealSequence, FIELD_INTEGER ),
10337 DEFINE_EMBEDDEDBYREF( m_pSenses ),
10338 DEFINE_EMBEDDEDBYREF( m_pLockedBestSound ),
10339 DEFINE_FIELD( m_hEnemy, FIELD_EHANDLE ),
10340 DEFINE_FIELD( m_flTimeEnemyAcquired, FIELD_TIME ),
10341 DEFINE_FIELD( m_hTargetEnt, FIELD_EHANDLE ),
10342 DEFINE_EMBEDDED( m_GiveUpOnDeadEnemyTimer ),
10343 DEFINE_EMBEDDED( m_FailChooseEnemyTimer ),
10344 DEFINE_FIELD( m_EnemiesSerialNumber, FIELD_INTEGER ),
10345 DEFINE_FIELD( m_flAcceptableTimeSeenEnemy, FIELD_TIME ),
10346 DEFINE_EMBEDDED( m_UpdateEnemyPosTimer ),
10347 // m_flTimeAnyUpdateEnemyPos (static)
10348 DEFINE_FIELD( m_vecCommandGoal, FIELD_VECTOR ),
10349 DEFINE_EMBEDDED( m_CommandMoveMonitor ),
10350 DEFINE_FIELD( m_flSoundWaitTime, FIELD_TIME ),
10351 DEFINE_FIELD( m_nSoundPriority, FIELD_INTEGER ),
10352 DEFINE_FIELD( m_flIgnoreDangerSoundsUntil, FIELD_TIME ),
10353 DEFINE_FIELD( m_afCapability, FIELD_INTEGER ),
10354 DEFINE_FIELD( m_flMoveWaitFinished, FIELD_TIME ),
10355 DEFINE_FIELD( m_hOpeningDoor, FIELD_EHANDLE ),
10356 DEFINE_EMBEDDEDBYREF( m_pNavigator ),
10357 DEFINE_EMBEDDEDBYREF( m_pLocalNavigator ),
10358 DEFINE_EMBEDDEDBYREF( m_pPathfinder ),
10359 DEFINE_EMBEDDEDBYREF( m_pMoveProbe ),
10360 DEFINE_EMBEDDEDBYREF( m_pMotor ),
10361 DEFINE_UTLVECTOR(m_UnreachableEnts, FIELD_EMBEDDED),
10362 DEFINE_FIELD( m_hInteractionPartner, FIELD_EHANDLE ),
10363 DEFINE_FIELD( m_hLastInteractionTestTarget, FIELD_EHANDLE ),
10364 DEFINE_FIELD( m_hForcedInteractionPartner, FIELD_EHANDLE ),
10365 DEFINE_FIELD( m_vecForcedWorldPosition, FIELD_POSITION_VECTOR ),
10366 DEFINE_FIELD( m_bCannotDieDuringInteraction, FIELD_BOOLEAN ),
10367 DEFINE_FIELD( m_iInteractionState, FIELD_INTEGER ),
10368 DEFINE_FIELD( m_iInteractionPlaying, FIELD_INTEGER ),
10369 DEFINE_UTLVECTOR(m_ScriptedInteractions,FIELD_EMBEDDED),
10370 DEFINE_FIELD( m_flInteractionYaw, FIELD_FLOAT ),
10371 DEFINE_EMBEDDED( m_CheckOnGroundTimer ),
10372 DEFINE_FIELD( m_vDefaultEyeOffset, FIELD_VECTOR ),
10373 DEFINE_FIELD( m_flNextEyeLookTime, FIELD_TIME ),
10374 DEFINE_FIELD( m_flEyeIntegRate, FIELD_FLOAT ),
10375 DEFINE_FIELD( m_vEyeLookTarget, FIELD_POSITION_VECTOR ),
10376 DEFINE_FIELD( m_vCurEyeTarget, FIELD_POSITION_VECTOR ),
10377 DEFINE_FIELD( m_hEyeLookTarget, FIELD_EHANDLE ),
10378 DEFINE_FIELD( m_flHeadYaw, FIELD_FLOAT ),
10379 DEFINE_FIELD( m_flHeadPitch, FIELD_FLOAT ),
10380 DEFINE_FIELD( m_flOriginalYaw, FIELD_FLOAT ),
10381 DEFINE_FIELD( m_bInAScript, FIELD_BOOLEAN ),
10382 DEFINE_FIELD( m_scriptState, FIELD_INTEGER ),
10383 DEFINE_FIELD( m_hCine, FIELD_EHANDLE ),
10384 DEFINE_CUSTOM_FIELD( m_ScriptArrivalActivity, ActivityDataOps() ),
10385 DEFINE_FIELD( m_strScriptArrivalSequence, FIELD_STRING ),
10386 DEFINE_FIELD( m_flSceneTime, FIELD_TIME ),
10387 DEFINE_FIELD( m_iszSceneCustomMoveSeq, FIELD_STRING ),
10388 // m_pEnemies Saved specially in ai_saverestore.cpp
10389 DEFINE_FIELD( m_afMemory, FIELD_INTEGER ),
10390 DEFINE_FIELD( m_hEnemyOccluder, FIELD_EHANDLE ),
10391 DEFINE_FIELD( m_flSumDamage, FIELD_FLOAT ),
10392 DEFINE_FIELD( m_flLastDamageTime, FIELD_TIME ),
10393 DEFINE_FIELD( m_flLastPlayerDamageTime, FIELD_TIME ),
10394 DEFINE_FIELD( m_flLastSawPlayerTime, FIELD_TIME ),
10395 DEFINE_FIELD( m_flLastAttackTime, FIELD_TIME ),
10396 DEFINE_FIELD( m_flLastEnemyTime, FIELD_TIME ),
10397 DEFINE_FIELD( m_flNextWeaponSearchTime, FIELD_TIME ),
10398 DEFINE_FIELD( m_iszPendingWeapon, FIELD_STRING ),
10399 DEFINE_EMBEDDED( m_ShotRegulator ),
10400 DEFINE_FIELD( m_iDesiredWeaponState, FIELD_INTEGER ),
10401 // m_pSquad Saved specially in ai_saverestore.cpp
10402 DEFINE_KEYFIELD(m_SquadName, FIELD_STRING, "squadname" ),
10403 DEFINE_FIELD( m_iMySquadSlot, FIELD_INTEGER ),
10404 DEFINE_KEYFIELD( m_strHintGroup, FIELD_STRING, "hintgroup" ),
10405 DEFINE_KEYFIELD( m_bHintGroupNavLimiting, FIELD_BOOLEAN, "hintlimiting" ),
10406 DEFINE_EMBEDDEDBYREF( m_pTacticalServices ),
10407 DEFINE_FIELD( m_flWaitFinished, FIELD_TIME ),
10408 DEFINE_FIELD( m_flNextFlinchTime, FIELD_TIME ),
10409 DEFINE_FIELD( m_flNextDodgeTime, FIELD_TIME ),
10410 DEFINE_EMBEDDED( m_MoveAndShootOverlay ),
10411 DEFINE_FIELD( m_vecLastPosition, FIELD_POSITION_VECTOR ),
10412 DEFINE_FIELD( m_vSavePosition, FIELD_POSITION_VECTOR ),
10413 DEFINE_FIELD( m_vInterruptSavePosition, FIELD_POSITION_VECTOR ),
10414 DEFINE_FIELD( m_pHintNode, FIELD_EHANDLE),
10415 DEFINE_FIELD( m_cAmmoLoaded, FIELD_INTEGER ),
10416 DEFINE_FIELD( m_flDistTooFar, FIELD_FLOAT ),
10417 DEFINE_FIELD( m_hGoalEnt, FIELD_EHANDLE ),
10418 DEFINE_FIELD( m_flTimeLastMovement, FIELD_TIME ),
10419 DEFINE_KEYFIELD(m_spawnEquipment, FIELD_STRING, "additionalequipment" ),
10420 DEFINE_FIELD( m_fNoDamageDecal, FIELD_BOOLEAN ),
10421 DEFINE_FIELD( m_hStoredPathTarget, FIELD_EHANDLE ),
10422 DEFINE_FIELD( m_vecStoredPathGoal, FIELD_POSITION_VECTOR ),
10423 DEFINE_FIELD( m_nStoredPathType, FIELD_INTEGER ),
10424 DEFINE_FIELD( m_fStoredPathFlags, FIELD_INTEGER ),
10425 DEFINE_FIELD( m_bDidDeathCleanup, FIELD_BOOLEAN ),
10426 DEFINE_FIELD( m_bCrouchDesired, FIELD_BOOLEAN ),
10427 DEFINE_FIELD( m_bForceCrouch, FIELD_BOOLEAN ),
10428 DEFINE_FIELD( m_bIsCrouching, FIELD_BOOLEAN ),
10429 DEFINE_FIELD( m_bPerformAvoidance, FIELD_BOOLEAN ),
10430 DEFINE_FIELD( m_bIsMoving, FIELD_BOOLEAN ),
10431 DEFINE_FIELD( m_bFadeCorpse, FIELD_BOOLEAN ),
10432 DEFINE_FIELD( m_iDeathPose, FIELD_INTEGER ),
10433 DEFINE_FIELD( m_iDeathFrame, FIELD_INTEGER ),
10434 DEFINE_FIELD( m_bCheckContacts, FIELD_BOOLEAN ),
10435 DEFINE_FIELD( m_bSpeedModActive, FIELD_BOOLEAN ),
10436 DEFINE_FIELD( m_iSpeedModRadius, FIELD_INTEGER ),
10437 DEFINE_FIELD( m_iSpeedModSpeed, FIELD_INTEGER ),
10438 DEFINE_FIELD( m_hEnemyFilter, FIELD_EHANDLE ),
10439 DEFINE_KEYFIELD( m_iszEnemyFilterName, FIELD_STRING, "enemyfilter" ),
10440 DEFINE_FIELD( m_bImportanRagdoll, FIELD_BOOLEAN ),
10441
10442 // m_fIsUsingSmallHull TODO -- This needs more consideration than simple save/load
10443 // m_failText DEBUG
10444 // m_interruptText DEBUG
10445 // m_failedSchedule DEBUG
10446 // m_interuptSchedule DEBUG
10447 // m_nDebugCurIndex DEBUG
10448
10449 // m_LastShootAccuracy DEBUG
10450 // m_RecentShotAccuracy DEBUG
10451 // m_TotalShots DEBUG
10452 // m_TotalHits DEBUG
10453 // m_bSelected DEBUG
10454 // m_TimeLastShotMark DEBUG
10455
10456
10457 // Outputs
10458 DEFINE_OUTPUT( m_OnDamaged, "OnDamaged" ),
10459 DEFINE_OUTPUT( m_OnDeath, "OnDeath" ),
10460 DEFINE_OUTPUT( m_OnHalfHealth, "OnHalfHealth" ),
10461 DEFINE_OUTPUT( m_OnFoundEnemy, "OnFoundEnemy" ),
10462 DEFINE_OUTPUT( m_OnLostEnemyLOS, "OnLostEnemyLOS" ),
10463 DEFINE_OUTPUT( m_OnLostEnemy, "OnLostEnemy" ),
10464 DEFINE_OUTPUT( m_OnFoundPlayer, "OnFoundPlayer" ),
10465 DEFINE_OUTPUT( m_OnLostPlayerLOS, "OnLostPlayerLOS" ),
10466 DEFINE_OUTPUT( m_OnLostPlayer, "OnLostPlayer" ),
10467 DEFINE_OUTPUT( m_OnHearWorld, "OnHearWorld" ),
10468 DEFINE_OUTPUT( m_OnHearPlayer, "OnHearPlayer" ),
10469 DEFINE_OUTPUT( m_OnHearCombat, "OnHearCombat" ),
10470 DEFINE_OUTPUT( m_OnDamagedByPlayer, "OnDamagedByPlayer" ),
10471 DEFINE_OUTPUT( m_OnDamagedByPlayerSquad, "OnDamagedByPlayerSquad" ),
10472 DEFINE_OUTPUT( m_OnDenyCommanderUse, "OnDenyCommanderUse" ),
10473 DEFINE_OUTPUT( m_OnRappelTouchdown, "OnRappelTouchdown" ),
10474 DEFINE_OUTPUT( m_OnWake, "OnWake" ),
10475 DEFINE_OUTPUT( m_OnSleep, "OnSleep" ),
10476 DEFINE_OUTPUT( m_OnForcedInteractionAborted, "OnForcedInteractionAborted" ),
10477 DEFINE_OUTPUT( m_OnForcedInteractionFinished, "OnForcedInteractionFinished" ),
10478
10479 // Inputs
10480 DEFINE_INPUTFUNC( FIELD_STRING, "SetRelationship", InputSetRelationship ),
10481 DEFINE_INPUTFUNC( FIELD_INTEGER, "SetHealth", InputSetHealth ),
10482 DEFINE_INPUTFUNC( FIELD_VOID, "BeginRappel", InputBeginRappel ),
10483 DEFINE_INPUTFUNC( FIELD_STRING, "SetSquad", InputSetSquad ),
10484 DEFINE_INPUTFUNC( FIELD_VOID, "Wake", InputWake ),
10485 DEFINE_INPUTFUNC( FIELD_STRING, "ForgetEntity", InputForgetEntity ),
10486 DEFINE_INPUTFUNC( FIELD_FLOAT, "IgnoreDangerSounds", InputIgnoreDangerSounds ),
10487 DEFINE_INPUTFUNC( FIELD_VOID, "Break", InputBreak ),
10488 DEFINE_INPUTFUNC( FIELD_VOID, "StartScripting", InputStartScripting ),
10489 DEFINE_INPUTFUNC( FIELD_VOID, "StopScripting", InputStopScripting ),
10490 DEFINE_INPUTFUNC( FIELD_VOID, "GagEnable", InputGagEnable ),
10491 DEFINE_INPUTFUNC( FIELD_VOID, "GagDisable", InputGagDisable ),
10492 DEFINE_INPUTFUNC( FIELD_VOID, "InsideTransition", InputInsideTransition ),
10493 DEFINE_INPUTFUNC( FIELD_VOID, "OutsideTransition", InputOutsideTransition ),
10494 DEFINE_INPUTFUNC( FIELD_VOID, "ActivateSpeedModifier", InputActivateSpeedModifier ),
10495 DEFINE_INPUTFUNC( FIELD_VOID, "DisableSpeedModifier", InputDisableSpeedModifier ),
10496 DEFINE_INPUTFUNC( FIELD_INTEGER, "SetSpeedModRadius", InputSetSpeedModifierRadius ),
10497 DEFINE_INPUTFUNC( FIELD_INTEGER, "SetSpeedModSpeed", InputSetSpeedModifierSpeed ),
10498 DEFINE_INPUTFUNC( FIELD_VOID, "HolsterWeapon", InputHolsterWeapon ),
10499 DEFINE_INPUTFUNC( FIELD_VOID, "HolsterAndDestroyWeapon", InputHolsterAndDestroyWeapon ),
10500 DEFINE_INPUTFUNC( FIELD_VOID, "UnholsterWeapon", InputUnholsterWeapon ),
10501 DEFINE_INPUTFUNC( FIELD_STRING, "ForceInteractionWithNPC", InputForceInteractionWithNPC ),
10502 DEFINE_INPUTFUNC( FIELD_STRING, "UpdateEnemyMemory", InputUpdateEnemyMemory ),
10503
10504 // Function pointers
10505 DEFINE_USEFUNC( NPCUse ),
10506 DEFINE_THINKFUNC( CallNPCThink ),
10507 DEFINE_THINKFUNC( CorpseFallThink ),
10508 DEFINE_THINKFUNC( NPCInitThink ),
10509
10510END_DATADESC()
10511
10512BEGIN_SIMPLE_DATADESC( AIScheduleState_t )
10513 DEFINE_FIELD( iCurTask, FIELD_INTEGER ),
10514 DEFINE_FIELD( fTaskStatus, FIELD_INTEGER ),
10515 DEFINE_FIELD( timeStarted, FIELD_TIME ),
10516 DEFINE_FIELD( timeCurTaskStarted, FIELD_TIME ),
10517 DEFINE_FIELD( taskFailureCode, FIELD_INTEGER ),
10518 DEFINE_FIELD( iTaskInterrupt, FIELD_INTEGER ),
10519 DEFINE_FIELD( bTaskRanAutomovement, FIELD_BOOLEAN ),
10520 DEFINE_FIELD( bTaskUpdatedYaw, FIELD_BOOLEAN ),
10521END_DATADESC()
10522
10523
10524IMPLEMENT_SERVERCLASS_ST( CAI_BaseNPC, DT_AI_BaseNPC )
10525 SendPropInt( SENDINFO( m_lifeState ), 3, SPROP_UNSIGNED ),
10526 SendPropBool( SENDINFO( m_bPerformAvoidance ) ),
10527 SendPropBool( SENDINFO( m_bIsMoving ) ),
10528 SendPropBool( SENDINFO( m_bFadeCorpse ) ),
10529 SendPropInt( SENDINFO( m_iDeathPose ), ANIMATION_SEQUENCE_BITS ),
10530 SendPropInt( SENDINFO( m_iDeathFrame ), 5 ),
10531 SendPropBool( SENDINFO( m_bSpeedModActive ) ),
10532 SendPropInt( SENDINFO( m_iSpeedModRadius ) ),
10533 SendPropInt( SENDINFO( m_iSpeedModSpeed ) ),
10534 SendPropBool( SENDINFO( m_bImportanRagdoll ) ),
10535END_SEND_TABLE()
10536
10537//-------------------------------------
10538
10539BEGIN_SIMPLE_DATADESC( UnreachableEnt_t )
10540
10541 DEFINE_FIELD( hUnreachableEnt, FIELD_EHANDLE ),
10542 DEFINE_FIELD( fExpireTime, FIELD_TIME ),
10543 DEFINE_FIELD( vLocationWhenUnreachable, FIELD_POSITION_VECTOR ),
10544
10545END_DATADESC()
10546
10547//-------------------------------------
10548
10549BEGIN_SIMPLE_DATADESC( ScriptedNPCInteraction_Phases_t )
10550DEFINE_FIELD( iszSequence, FIELD_STRING ),
10551DEFINE_FIELD( iActivity, FIELD_INTEGER ),
10552END_DATADESC()
10553
10554//-------------------------------------
10555
10556BEGIN_SIMPLE_DATADESC( ScriptedNPCInteraction_t )
10557 DEFINE_FIELD( iszInteractionName, FIELD_STRING ),
10558 DEFINE_FIELD( iFlags, FIELD_INTEGER ),
10559 DEFINE_FIELD( iTriggerMethod, FIELD_INTEGER ),
10560 DEFINE_FIELD( iLoopBreakTriggerMethod, FIELD_INTEGER ),
10561 DEFINE_FIELD( vecRelativeOrigin, FIELD_VECTOR ),
10562 DEFINE_FIELD( angRelativeAngles, FIELD_VECTOR ),
10563 DEFINE_FIELD( vecRelativeVelocity, FIELD_VECTOR ),
10564 DEFINE_FIELD( flDelay, FIELD_FLOAT ),
10565 DEFINE_FIELD( flDistSqr, FIELD_FLOAT ),
10566 DEFINE_FIELD( iszMyWeapon, FIELD_STRING ),
10567 DEFINE_FIELD( iszTheirWeapon, FIELD_STRING ),
10568 DEFINE_EMBEDDED_ARRAY( sPhases, SNPCINT_NUM_PHASES ),
10569 DEFINE_FIELD( matDesiredLocalToWorld, FIELD_VMATRIX ),
10570 DEFINE_FIELD( bValidOnCurrentEnemy, FIELD_BOOLEAN ),
10571 DEFINE_FIELD( flNextAttemptTime, FIELD_TIME ),
10572END_DATADESC()
10573
10574//-------------------------------------
10575
10576void CAI_BaseNPC::PostConstructor( const char *szClassname )
10577{
10578 BaseClass::PostConstructor( szClassname );
10579 CreateComponents();
10580}
10581
10582//-----------------------------------------------------------------------------
10583// Purpose:
10584//-----------------------------------------------------------------------------
10585void CAI_BaseNPC::Activate( void )
10586{
10587 BaseClass::Activate();
10588
10589 if ( GetModelPtr() )
10590 {
10591 ParseScriptedNPCInteractions();
10592 }
10593
10594 // Get a handle to my enemy filter entity if there is one.
10595 if ( m_iszEnemyFilterName != NULL_STRING )
10596 {
10597 CBaseEntity *pFilter = gEntList.FindEntityByName( NULL, m_iszEnemyFilterName );
10598 if ( pFilter != NULL )
10599 {
10600 m_hEnemyFilter = dynamic_cast<CBaseFilter*>(pFilter);
10601 }
10602 }
10603}
10604
10605void CAI_BaseNPC::Precache( void )
10606{
10607 gm_iszPlayerSquad = AllocPooledString( PLAYER_SQUADNAME ); // cache for fast IsPlayerSquad calls
10608
10609 if ( m_spawnEquipment != NULL_STRING && strcmp(STRING(m_spawnEquipment), "0") )
10610 {
10611 UTIL_PrecacheOther( STRING(m_spawnEquipment) );
10612 }
10613
10614 // Make sure schedules are loaded for this NPC type
10615 if (!LoadedSchedules())
10616 {
10617 DevMsg("ERROR: Rejecting spawn of %s as error in NPC's schedules.\n",GetDebugName());
10618 UTIL_Remove(this);
10619 return;
10620 }
10621
10622 PrecacheScriptSound( "AI_BaseNPC.SwishSound" );
10623 PrecacheScriptSound( "AI_BaseNPC.BodyDrop_Heavy" );
10624 PrecacheScriptSound( "AI_BaseNPC.BodyDrop_Light" );
10625 PrecacheScriptSound( "AI_BaseNPC.SentenceStop" );
10626
10627 BaseClass::Precache();
10628}
10629
10630
10631//-----------------------------------------------------------------------------
10632
10633const short AI_EXTENDED_SAVE_HEADER_VERSION = 5;
10634const short AI_EXTENDED_SAVE_HEADER_RESET_VERSION = 3;
10635
10636const short AI_EXTENDED_SAVE_HEADER_FIRST_VERSION_WITH_CONDITIONS = 2;
10637const short AI_EXTENDED_SAVE_HEADER_FIRST_VERSION_WITH_SCHEDULE_ID_FIXUP = 3;
10638const short AI_EXTENDED_SAVE_HEADER_FIRST_VERSION_WITH_SEQUENCE = 4;
10639const short AI_EXTENDED_SAVE_HEADER_FIRST_VERSION_WITH_NAVIGATOR_SAVE = 5;
10640
10641struct AIExtendedSaveHeader_t
10642{
10643 AIExtendedSaveHeader_t()
10644 : version(AI_EXTENDED_SAVE_HEADER_VERSION),
10645 flags(0),
10646 scheduleCrc(0)
10647 {
10648 szSchedule[0] = 0;
10649 szIdealSchedule[0] = 0;
10650 szFailSchedule[0] = 0;
10651 szSequence[0] = 0;
10652 }
10653
10654 short version;
10655 unsigned flags;
10656 char szSchedule[128];
10657 CRC32_t scheduleCrc;
10658 char szIdealSchedule[128];
10659 char szFailSchedule[128];
10660 char szSequence[128];
10661
10662 DECLARE_SIMPLE_DATADESC();
10663};
10664
10665enum AIExtendedSaveHeaderFlags_t
10666{
10667 AIESH_HAD_ENEMY = 0x01,
10668 AIESH_HAD_TARGET = 0x02,
10669 AIESH_HAD_NAVGOAL = 0x04,
10670};
10671
10672//-------------------------------------
10673
10674BEGIN_SIMPLE_DATADESC( AIExtendedSaveHeader_t )
10675 DEFINE_FIELD( version, FIELD_SHORT ),
10676 DEFINE_FIELD( flags, FIELD_INTEGER ),
10677 DEFINE_AUTO_ARRAY( szSchedule, FIELD_CHARACTER ),
10678 DEFINE_FIELD( scheduleCrc, FIELD_INTEGER ),
10679 DEFINE_AUTO_ARRAY( szIdealSchedule, FIELD_CHARACTER ),
10680 DEFINE_AUTO_ARRAY( szFailSchedule, FIELD_CHARACTER ),
10681 DEFINE_AUTO_ARRAY( szSequence, FIELD_CHARACTER ),
10682END_DATADESC()
10683
10684//-------------------------------------
10685
10686int CAI_BaseNPC::Save( ISave &save )
10687{
10688 AIExtendedSaveHeader_t saveHeader;
10689
10690 if ( GetEnemy() )
10691 saveHeader.flags |= AIESH_HAD_ENEMY;
10692 if ( GetTarget() )
10693 saveHeader.flags |= AIESH_HAD_TARGET;
10694 if ( GetNavigator()->IsGoalActive() )
10695 saveHeader.flags |= AIESH_HAD_NAVGOAL;
10696
10697 if ( m_pSchedule )
10698 {
10699 const char *pszSchedule = m_pSchedule->GetName();
10700
10701 Assert( Q_strlen( pszSchedule ) < sizeof( saveHeader.szSchedule ) - 1 );
10702 Q_strncpy( saveHeader.szSchedule, pszSchedule, sizeof( saveHeader.szSchedule ) );
10703
10704 CRC32_Init( &saveHeader.scheduleCrc );
10705 CRC32_ProcessBuffer( &saveHeader.scheduleCrc, (void *)m_pSchedule->GetTaskList(), m_pSchedule->NumTasks() * sizeof(Task_t) );
10706 CRC32_Final( &saveHeader.scheduleCrc );
10707 }
10708 else
10709 {
10710 saveHeader.szSchedule[0] = 0;
10711 saveHeader.scheduleCrc = 0;
10712 }
10713
10714 int idealSchedule = GetGlobalScheduleId( m_IdealSchedule );
10715
10716 if ( idealSchedule != -1 && idealSchedule != AI_RemapToGlobal( SCHED_NONE ) && idealSchedule != AI_RemapToGlobal( SCHED_AISCRIPT ) )
10717 {
10718 CAI_Schedule *pIdealSchedule = GetSchedule( m_IdealSchedule );
10719 if ( pIdealSchedule )
10720 {
10721 const char *pszIdealSchedule = pIdealSchedule->GetName();
10722 Assert( Q_strlen( pszIdealSchedule ) < sizeof( saveHeader.szIdealSchedule ) - 1 );
10723 Q_strncpy( saveHeader.szIdealSchedule, pszIdealSchedule, sizeof( saveHeader.szIdealSchedule ) );
10724 }
10725 }
10726
10727 int failSchedule = GetGlobalScheduleId( m_failSchedule );
10728 if ( failSchedule != -1 && failSchedule != AI_RemapToGlobal( SCHED_NONE ) && failSchedule != AI_RemapToGlobal( SCHED_AISCRIPT ) )
10729 {
10730 CAI_Schedule *pFailSchedule = GetSchedule( m_failSchedule );
10731 if ( pFailSchedule )
10732 {
10733 const char *pszFailSchedule = pFailSchedule->GetName();
10734 Assert( Q_strlen( pszFailSchedule ) < sizeof( saveHeader.szFailSchedule ) - 1 );
10735 Q_strncpy( saveHeader.szFailSchedule, pszFailSchedule, sizeof( saveHeader.szFailSchedule ) );
10736 }
10737 }
10738
10739 if ( GetSequence() != ACT_INVALID && GetModelPtr() )
10740 {
10741 const char *pszSequenceName = GetSequenceName( GetSequence() );
10742 if ( pszSequenceName && *pszSequenceName )
10743 {
10744 Assert( Q_strlen( pszSequenceName ) < sizeof( saveHeader.szSequence ) - 1 );
10745 Q_strncpy( saveHeader.szSequence, pszSequenceName, sizeof(saveHeader.szSequence) );
10746 }
10747 }
10748
10749 save.WriteAll( &saveHeader );
10750
10751 save.StartBlock();
10752 SaveConditions( save, m_Conditions );
10753 SaveConditions( save, m_CustomInterruptConditions );
10754 SaveConditions( save, m_ConditionsPreIgnore );
10755 CAI_ScheduleBits ignoreConditions;
10756 m_InverseIgnoreConditions.Not( &ignoreConditions );
10757 SaveConditions( save, ignoreConditions );
10758 save.EndBlock();
10759
10760 save.StartBlock();
10761 GetNavigator()->Save( save );
10762 save.EndBlock();
10763
10764 return BaseClass::Save(save);
10765}
10766
10767//-------------------------------------
10768
10769void CAI_BaseNPC::DiscardScheduleState()
10770{
10771 // We don't save/restore routes yet
10772 GetNavigator()->ClearGoal();
10773
10774 // We don't save/restore schedules yet
10775 ClearSchedule();
10776
10777 // Reset animation
10778 m_Activity = ACT_RESET;
10779
10780 // If we don't have an enemy, clear conditions like see enemy, etc.
10781 if ( GetEnemy() == NULL )
10782 {
10783 m_Conditions.ClearAllBits();
10784 }
10785
10786 // went across a transition and lost my m_hCine
10787 bool bLostScript = ( m_NPCState == NPC_STATE_SCRIPT && m_hCine == NULL );
10788 if ( bLostScript )
10789 {
10790 // UNDONE: Do something better here?
10791 // for now, just go back to idle and let the AI figure out what to do.
10792 SetState( NPC_STATE_IDLE );
10793 SetIdealState( NPC_STATE_IDLE );
10794 DevMsg(1, "Scripted Sequence stripped on level transition for %s\n", GetDebugName() );
10795 }
10796}
10797
10798//-------------------------------------
10799
10800void CAI_BaseNPC::OnRestore()
10801{
10802 gm_iszPlayerSquad = AllocPooledString( PLAYER_SQUADNAME ); // cache for fast IsPlayerSquad calls
10803
10804 if ( m_bDoPostRestoreRefindPath && CAI_NetworkManager::NetworksLoaded() )
10805 {
10806 CAI_DynamicLink::InitDynamicLinks();
10807 if ( !GetNavigator()->RefindPathToGoal( false ) )
10808 DiscardScheduleState();
10809 }
10810 else
10811 {
10812 GetNavigator()->ClearGoal();
10813 }
10814 BaseClass::OnRestore();
10815 m_bCheckContacts = true;
10816}
10817
10818
10819//-------------------------------------
10820
10821int CAI_BaseNPC::Restore( IRestore &restore )
10822{
10823 AIExtendedSaveHeader_t saveHeader;
10824 restore.ReadAll( &saveHeader );
10825
10826 if ( saveHeader.version >= AI_EXTENDED_SAVE_HEADER_FIRST_VERSION_WITH_SCHEDULE_ID_FIXUP )
10827 {
10828 if ( saveHeader.szIdealSchedule[0] )
10829 {
10830 CAI_Schedule *pIdealSchedule = g_AI_SchedulesManager.GetScheduleByName( saveHeader.szIdealSchedule );
10831 m_IdealSchedule = ( pIdealSchedule ) ? pIdealSchedule->GetId() : SCHED_NONE;
10832 }
10833
10834 if ( saveHeader.szFailSchedule[0] )
10835 {
10836 CAI_Schedule *pFailSchedule = g_AI_SchedulesManager.GetScheduleByName( saveHeader.szFailSchedule );
10837 m_failSchedule = ( pFailSchedule ) ? pFailSchedule->GetId() : SCHED_NONE;
10838 }
10839 }
10840
10841 if ( saveHeader.version >= AI_EXTENDED_SAVE_HEADER_FIRST_VERSION_WITH_CONDITIONS )
10842 {
10843 restore.StartBlock();
10844 RestoreConditions( restore, &m_Conditions );
10845 RestoreConditions( restore, &m_CustomInterruptConditions );
10846 RestoreConditions( restore, &m_ConditionsPreIgnore );
10847 CAI_ScheduleBits ignoreConditions;
10848 RestoreConditions( restore, &ignoreConditions );
10849 ignoreConditions.Not( &m_InverseIgnoreConditions );
10850 restore.EndBlock();
10851 }
10852
10853 if ( saveHeader.version >= AI_EXTENDED_SAVE_HEADER_FIRST_VERSION_WITH_NAVIGATOR_SAVE )
10854 {
10855 restore.StartBlock();
10856 GetNavigator()->Restore( restore );
10857 restore.EndBlock();
10858 }
10859
10860 // do a normal restore
10861 int status = BaseClass::Restore(restore);
10862 if ( !status )
10863 return 0;
10864
10865 bool bLostSequence = false;
10866 if ( saveHeader.version >= AI_EXTENDED_SAVE_HEADER_FIRST_VERSION_WITH_SEQUENCE && saveHeader.szSequence[0] && GetModelPtr() )
10867 {
10868 SetSequence( LookupSequence( saveHeader.szSequence ) );
10869 if ( GetSequence() == ACT_INVALID )
10870 {
10871 DevMsg( this, AIMF_IGNORE_SELECTED, "Discarding missing sequence %s on load.\n", saveHeader.szSequence );
10872 SetSequence( 0 );
10873 bLostSequence = true;
10874 }
10875
10876 Assert( IsValidSequence( GetSequence() ) );
10877 }
10878
10879 bool bLostScript = ( m_NPCState == NPC_STATE_SCRIPT && m_hCine == NULL );
10880 bool bDiscardScheduleState = ( bLostScript ||
10881 bLostSequence ||
10882 saveHeader.szSchedule[0] == 0 ||
10883 saveHeader.version < AI_EXTENDED_SAVE_HEADER_RESET_VERSION ||
10884 ( (saveHeader.flags & AIESH_HAD_ENEMY) && !GetEnemy() ) ||
10885 ( (saveHeader.flags & AIESH_HAD_TARGET) && !GetTarget() ) );
10886
10887 if ( m_ScheduleState.taskFailureCode >= NUM_FAIL_CODES )
10888 m_ScheduleState.taskFailureCode = FAIL_NO_TARGET; // must have been a string, gotta punt
10889
10890 if ( !bDiscardScheduleState )
10891 {
10892 m_pSchedule = g_AI_SchedulesManager.GetScheduleByName( saveHeader.szSchedule );
10893 if ( m_pSchedule )
10894 {
10895 CRC32_t scheduleCrc;
10896 CRC32_Init( &scheduleCrc );
10897 CRC32_ProcessBuffer( &scheduleCrc, (void *)m_pSchedule->GetTaskList(), m_pSchedule->NumTasks() * sizeof(Task_t) );
10898 CRC32_Final( &scheduleCrc );
10899
10900 if ( scheduleCrc != saveHeader.scheduleCrc )
10901 {
10902 m_pSchedule = NULL;
10903 }
10904 }
10905 }
10906
10907 if ( !m_pSchedule )
10908 bDiscardScheduleState = true;
10909
10910 if ( !bDiscardScheduleState )
10911 m_bDoPostRestoreRefindPath = ( ( saveHeader.flags & AIESH_HAD_NAVGOAL) != 0 );
10912 else
10913 {
10914 m_bDoPostRestoreRefindPath = false;
10915 DiscardScheduleState();
10916 }
10917
10918 return status;
10919}
10920
10921//-------------------------------------
10922
10923void CAI_BaseNPC::SaveConditions( ISave &save, const CAI_ScheduleBits &conditions )
10924{
10925 for (int i = 0; i < MAX_CONDITIONS; i++)
10926 {
10927 if (conditions.GetBit(i))
10928 {
10929 const char *pszConditionName = ConditionName(AI_RemapToGlobal(i));
10930 if ( !pszConditionName )
10931 break;
10932 save.WriteString( pszConditionName );
10933 }
10934 }
10935 save.WriteString( "" );
10936}
10937
10938//-------------------------------------
10939
10940void CAI_BaseNPC::RestoreConditions( IRestore &restore, CAI_ScheduleBits *pConditions )
10941{
10942 pConditions->ClearAllBits();
10943 char szCondition[256];
10944 for (;;)
10945 {
10946 restore.ReadString( szCondition, sizeof(szCondition), 0 );
10947 if ( !szCondition[0] )
10948 break;
10949 int iCondition = GetSchedulingSymbols()->ConditionSymbolToId( szCondition );
10950 if ( iCondition != -1 )
10951 pConditions->SetBit( AI_RemapFromGlobal( iCondition ) );
10952 }
10953}
10954
10955//-----------------------------------------------------------------------------
10956//-----------------------------------------------------------------------------
10957bool CAI_BaseNPC::KeyValue( const char *szKeyName, const char *szValue )
10958{
10959 bool bResult = BaseClass::KeyValue( szKeyName, szValue );
10960
10961 if( !bResult )
10962 {
10963 // Defer unhandled Keys to behaviors
10964 CAI_BehaviorBase **ppBehaviors = AccessBehaviors();
10965
10966 for ( int i = 0; i < NumBehaviors(); i++ )
10967 {
10968 if( ppBehaviors[ i ]->KeyValue( szKeyName, szValue ) )
10969 {
10970 return true;
10971 }
10972 }
10973 }
10974
10975 return bResult;
10976}
10977
10978//-----------------------------------------------------------------------------
10979// Purpose: Debug function to make this NPC freeze in place (or unfreeze).
10980//-----------------------------------------------------------------------------
10981void CAI_BaseNPC::ToggleFreeze(void)
10982{
10983 if (!IsCurSchedule(SCHED_NPC_FREEZE))
10984 {
10985 // Freeze them.
10986 SetCondition(COND_NPC_FREEZE);
10987 SetMoveType(MOVETYPE_NONE);
10988 SetGravity(0);
10989 SetLocalAngularVelocity(vec3_angle);
10990 SetAbsVelocity( vec3_origin );
10991 }
10992 else
10993 {
10994 // Unfreeze them.
10995 SetCondition(COND_NPC_UNFREEZE);
10996 m_Activity = ACT_RESET;
10997
10998 // BUGBUG: this might not be the correct movetype!
10999 SetMoveType( MOVETYPE_STEP );
11000
11001 // Doesn't restore gravity to the original value, but who cares?
11002 SetGravity(1);
11003 }
11004}
11005
11006
11007//-----------------------------------------------------------------------------
11008// Purpose: Written by subclasses macro to load schedules
11009// Input :
11010// Output :
11011//-----------------------------------------------------------------------------
11012bool CAI_BaseNPC::LoadSchedules(void)
11013{
11014 return true;
11015}
11016
11017//-----------------------------------------------------------------------------
11018
11019bool CAI_BaseNPC::LoadedSchedules(void)
11020{
11021 return true;
11022}
11023
11024//-----------------------------------------------------------------------------
11025// Purpose: Constructor
11026// Input :
11027// Output :
11028//-----------------------------------------------------------------------------
11029CAI_BaseNPC::CAI_BaseNPC(void)
11030 : m_UnreachableEnts( 0, 4 )
11031{
11032 m_pMotor = NULL;
11033 m_pMoveProbe = NULL;
11034 m_pNavigator = NULL;
11035 m_pSenses = NULL;
11036 m_pPathfinder = NULL;
11037 m_pLocalNavigator = NULL;
11038
11039 m_pSchedule = NULL;
11040 m_IdealSchedule = SCHED_NONE;
11041
11042#ifdef _DEBUG
11043 // necessary since in debug, we initialize vectors to NAN for debugging
11044 m_vecLastPosition.Init();
11045 m_vSavePosition.Init();
11046 m_vEyeLookTarget.Init();
11047 m_vCurEyeTarget.Init();
11048 m_vDefaultEyeOffset.Init();
11049
11050#endif
11051 m_bDidDeathCleanup = false;
11052
11053 m_afCapability = 0; // Make sure this is cleared in the base class
11054
11055 SetHullType(HULL_HUMAN); // Give human hull by default, subclasses should override
11056
11057 m_iMySquadSlot = SQUAD_SLOT_NONE;
11058 m_flSumDamage = 0;
11059 m_flLastDamageTime = 0;
11060 m_flLastAttackTime = 0;
11061 m_flSoundWaitTime = 0;
11062 m_flNextEyeLookTime = 0;
11063 m_flHeadYaw = 0;
11064 m_flHeadPitch = 0;
11065 m_spawnEquipment = NULL_STRING;
11066 m_pEnemies = new CAI_Enemies;
11067 m_flEyeIntegRate = 0.95;
11068 SetTarget( NULL );
11069
11070 m_pSquad = NULL;
11071
11072 m_flMoveWaitFinished = 0;
11073
11074 m_fIsUsingSmallHull = true;
11075
11076 m_bHintGroupNavLimiting = false;
11077
11078 m_fNoDamageDecal = false;
11079
11080 SetInAScript( false );
11081
11082 m_pLockedBestSound = new CSound;
11083 m_pLockedBestSound->m_iType = SOUND_NONE;
11084
11085 // ----------------------------
11086 // Debugging fields
11087 // ----------------------------
11088 m_interruptText = NULL;
11089 m_failText = NULL;
11090 m_failedSchedule = NULL;
11091 m_interuptSchedule = NULL;
11092 m_nDebugPauseIndex = 0;
11093
11094 g_AI_Manager.AddAI( this );
11095
11096 if ( g_AI_Manager.NumAIs() == 1 )
11097 {
11098 m_AnyUpdateEnemyPosTimer.Force();
11099 gm_flTimeLastSpawn = -1;
11100 gm_nSpawnedThisFrame = 0;
11101 gm_iNextThinkRebalanceTick = 0;
11102 }
11103
11104 m_iFrameBlocked = -1;
11105 m_bInChoreo = true; // assume so until call to UpdateEfficiency()
11106
11107 SetCollisionGroup( COLLISION_GROUP_NPC );
11108}
11109
11110//-----------------------------------------------------------------------------
11111// Purpose: Destructor
11112// Input :
11113// Output :
11114//-----------------------------------------------------------------------------
11115CAI_BaseNPC::~CAI_BaseNPC(void)
11116{
11117 g_AI_Manager.RemoveAI( this );
11118
11119 delete m_pLockedBestSound;
11120
11121 RemoveMemory();
11122
11123 delete m_pPathfinder;
11124 delete m_pNavigator;
11125 delete m_pMotor;
11126 delete m_pLocalNavigator;
11127 delete m_pMoveProbe;
11128 delete m_pSenses;
11129 delete m_pTacticalServices;
11130}
11131
11132//-----------------------------------------------------------------------------
11133// Purpose:
11134//-----------------------------------------------------------------------------
11135void CAI_BaseNPC::UpdateOnRemove(void)
11136{
11137 if ( !m_bDidDeathCleanup )
11138 {
11139 if ( m_NPCState == NPC_STATE_DEAD )
11140 DevMsg( "May not have cleaned up on NPC death\n");
11141
11142 CleanupOnDeath( NULL, false );
11143 }
11144
11145 // Chain at end to mimic destructor unwind order
11146 BaseClass::UpdateOnRemove();
11147}
11148
11149//-----------------------------------------------------------------------------
11150
11151bool CAI_BaseNPC::CreateComponents()
11152{
11153 m_pSenses = CreateSenses();
11154 if ( !m_pSenses )
11155 return false;
11156
11157 m_pMotor = CreateMotor();
11158 if ( !m_pMotor )
11159 return false;
11160
11161 m_pLocalNavigator = CreateLocalNavigator();
11162 if ( !m_pLocalNavigator )
11163 return false;
11164
11165 m_pMoveProbe = CreateMoveProbe();
11166 if ( !m_pMoveProbe )
11167 return false;
11168
11169 m_pNavigator = CreateNavigator();
11170 if ( !m_pNavigator )
11171 return false;
11172
11173 m_pPathfinder = CreatePathfinder();
11174 if ( !m_pPathfinder )
11175 return false;
11176
11177 m_pTacticalServices = CreateTacticalServices();
11178 if ( !m_pTacticalServices )
11179 return false;
11180
11181 m_MoveAndShootOverlay.SetOuter( this );
11182
11183 m_pMotor->Init( m_pLocalNavigator );
11184 m_pLocalNavigator->Init( m_pNavigator );
11185 m_pNavigator->Init( g_pBigAINet );
11186 m_pPathfinder->Init( g_pBigAINet );
11187 m_pTacticalServices->Init( g_pBigAINet );
11188
11189 return true;
11190}
11191
11192//-----------------------------------------------------------------------------
11193
11194CAI_Senses *CAI_BaseNPC::CreateSenses()
11195{
11196 CAI_Senses *pSenses = new CAI_Senses;
11197 pSenses->SetOuter( this );
11198 return pSenses;
11199}
11200
11201//-----------------------------------------------------------------------------
11202
11203CAI_Motor *CAI_BaseNPC::CreateMotor()
11204{
11205 return new CAI_Motor( this );
11206}
11207
11208//-----------------------------------------------------------------------------
11209
11210CAI_MoveProbe *CAI_BaseNPC::CreateMoveProbe()
11211{
11212 return new CAI_MoveProbe( this );
11213}
11214
11215//-----------------------------------------------------------------------------
11216
11217CAI_LocalNavigator *CAI_BaseNPC::CreateLocalNavigator()
11218{
11219 return new CAI_LocalNavigator( this );
11220}
11221
11222//-----------------------------------------------------------------------------
11223
11224CAI_TacticalServices *CAI_BaseNPC::CreateTacticalServices()
11225{
11226 return new CAI_TacticalServices( this );
11227}
11228
11229//-----------------------------------------------------------------------------
11230
11231CAI_Navigator *CAI_BaseNPC::CreateNavigator()
11232{
11233 return new CAI_Navigator( this );
11234}
11235
11236//-----------------------------------------------------------------------------
11237
11238CAI_Pathfinder *CAI_BaseNPC::CreatePathfinder()
11239{
11240 return new CAI_Pathfinder( this );
11241}
11242
11243//-----------------------------------------------------------------------------
11244// Purpose:
11245//-----------------------------------------------------------------------------
11246void CAI_BaseNPC::InputSetRelationship( inputdata_t &inputdata )
11247{
11248 AddRelationship( inputdata.value.String(), inputdata.pActivator );
11249}
11250
11251//-----------------------------------------------------------------------------
11252// Purpose:
11253//-----------------------------------------------------------------------------
11254void CAI_BaseNPC::InputSetHealth( inputdata_t &inputdata )
11255{
11256 int iNewHealth = inputdata.value.Int();
11257 int iDelta = abs(GetHealth() - iNewHealth);
11258 if ( iNewHealth > GetHealth() )
11259 {
11260 TakeHealth( iDelta, DMG_GENERIC );
11261 }
11262 else if ( iNewHealth < GetHealth() )
11263 {
11264 TakeDamage( CTakeDamageInfo( this, this, iDelta, DMG_GENERIC ) );
11265 }
11266}
11267
11268//-----------------------------------------------------------------------------
11269// Purpose:
11270//-----------------------------------------------------------------------------
11271void CAI_BaseNPC::InputBeginRappel( inputdata_t &inputdata )
11272{
11273 BeginRappel();
11274}
11275
11276//-----------------------------------------------------------------------------
11277// Purpose:
11278//-----------------------------------------------------------------------------
11279void CAI_BaseNPC::InputSetSquad( inputdata_t &inputdata )
11280{
11281 if ( !( CapabilitiesGet() & bits_CAP_SQUAD ) )
11282 {
11283 Warning("SetSquad Input received for NPC %s, but that NPC can't use squads.\n", GetDebugName() );
11284 return;
11285 }
11286
11287 m_SquadName = inputdata.value.StringID();
11288
11289 // Removing from squad?
11290 if ( m_SquadName == NULL_STRING )
11291 {
11292 if ( m_pSquad )
11293 {
11294 m_pSquad->RemoveFromSquad(this, true);
11295 m_pSquad = NULL;
11296 }
11297 }
11298 else
11299 {
11300 m_pSquad = g_AI_SquadManager.FindCreateSquad(this, m_SquadName);
11301 }
11302}
11303
11304//-----------------------------------------------------------------------------
11305// Purpose:
11306//-----------------------------------------------------------------------------
11307void CAI_BaseNPC::InputWake( inputdata_t &inputdata )
11308{
11309 Wake();
11310
11311 // Check if we have a path to follow. This is normally done in StartNPC,
11312 // but putting the NPC to sleep will cancel it, so we have to do it again.
11313 if ( m_target != NULL_STRING )// this npc has a target
11314 {
11315 // Find the npc's initial target entity, stash it
11316 SetGoalEnt( gEntList.FindEntityByName( NULL, m_target ) );
11317
11318 if ( !GetGoalEnt() )
11319 {
11320 Warning( "ReadyNPC()--%s couldn't find target %s\n", GetClassname(), STRING(m_target));
11321 }
11322 else
11323 {
11324 StartTargetHandling( GetGoalEnt() );
11325 }
11326 }
11327}
11328
11329//-----------------------------------------------------------------------------
11330// Purpose:
11331//-----------------------------------------------------------------------------
11332void CAI_BaseNPC::InputForgetEntity( inputdata_t &inputdata )
11333{
11334 const char *pszEntityToForget = inputdata.value.String();
11335
11336 if ( g_pDeveloper->GetInt() && pszEntityToForget[strlen( pszEntityToForget ) - 1] == '*' )
11337 DevMsg( "InputForgetEntity does not support wildcards\n" );
11338
11339 CBaseEntity *pEntity = gEntList.FindEntityByName( NULL, pszEntityToForget );
11340 if ( pEntity )
11341 {
11342 if ( GetEnemy() == pEntity )
11343 {
11344 SetEnemy( NULL );
11345 SetIdealState( NPC_STATE_ALERT );
11346 }
11347 GetEnemies()->ClearMemory( pEntity );
11348 }
11349}
11350
11351//-----------------------------------------------------------------------------
11352//-----------------------------------------------------------------------------
11353void CAI_BaseNPC::InputIgnoreDangerSounds( inputdata_t &inputdata )
11354{
11355 // Default is 10 seconds.
11356 float flDelay = 10.0f;
11357
11358 if( inputdata.value.Float() > 0.0f )
11359 {
11360 flDelay = inputdata.value.Float();
11361 }
11362
11363 m_flIgnoreDangerSoundsUntil = gpGlobals->curtime + flDelay;
11364}
11365
11366//-----------------------------------------------------------------------------
11367//-----------------------------------------------------------------------------
11368void CAI_BaseNPC::InputUpdateEnemyMemory( inputdata_t &inputdata )
11369{
11370 const char *pszEnemy = inputdata.value.String();
11371 CBaseEntity *pEnemy = gEntList.FindEntityByName( NULL, pszEnemy );
11372
11373 if( pEnemy )
11374 {
11375 UpdateEnemyMemory( pEnemy, pEnemy->GetAbsOrigin(), this );
11376 }
11377}
11378
11379//-----------------------------------------------------------------------------
11380// Purpose:
11381// Input : &inputdata -
11382//-----------------------------------------------------------------------------
11383void CAI_BaseNPC::InputOutsideTransition( inputdata_t &inputdata )
11384{
11385}
11386
11387//-----------------------------------------------------------------------------
11388// Purpose: Called when this NPC transitions to another level with the player
11389// Input : &inputdata -
11390//-----------------------------------------------------------------------------
11391void CAI_BaseNPC::InputInsideTransition( inputdata_t &inputdata )
11392{
11393 CleanupScriptsOnTeleport( true );
11394
11395 // If we're inside a vcd, tell it to stop
11396 if ( IsCurSchedule( SCHED_SCENE_GENERIC, false ) )
11397 {
11398 RemoveActorFromScriptedScenes( this, false );
11399 }
11400}
11401
11402//-----------------------------------------------------------------------------
11403// Purpose:
11404//-----------------------------------------------------------------------------
11405void CAI_BaseNPC::CleanupScriptsOnTeleport( bool bEnrouteAsWell )
11406{
11407 // If I'm running a scripted sequence, I need to clean up
11408 if ( m_NPCState == NPC_STATE_SCRIPT && m_hCine )
11409 {
11410 if ( !bEnrouteAsWell )
11411 {
11412 // Don't cancel scripts when they're teleporting an NPC
11413 // to the script due to CINE_MOVETO_TELEPORT.
11414 if ( m_hCine->IsTeleportingDueToMoveTo() )
11415 return;
11416 }
11417
11418 m_hCine->ScriptEntityCancel( m_hCine, true );
11419 }
11420}
11421
11422//-----------------------------------------------------------------------------
11423//-----------------------------------------------------------------------------
11424bool CAI_BaseNPC::HandleInteraction(int interactionType, void *data, CBaseCombatCharacter* sourceEnt)
11425{
11426#ifdef HL2_DLL
11427 if ( interactionType == g_interactionBarnacleVictimGrab )
11428 {
11429 // Make the victim stop thinking so they're as good as dead without
11430 // shocking the system by destroying the entity.
11431 StopLoopingSounds();
11432 BarnacleDeathSound();
11433 SetThink( NULL );
11434
11435 // Gag the NPC so they won't talk anymore
11436 AddSpawnFlags( SF_NPC_GAG );
11437
11438 // Drop any weapon they're holding
11439 if ( GetActiveWeapon() )
11440 {
11441 Weapon_Drop( GetActiveWeapon() );
11442 }
11443
11444 return true;
11445 }
11446#endif // HL2_DLL
11447
11448 return BaseClass::HandleInteraction( interactionType, data, sourceEnt );
11449}
11450
11451CAI_BaseNPC *CAI_BaseNPC::GetInteractionPartner( void )
11452{
11453 if ( m_hInteractionPartner == NULL )
11454 return NULL;
11455
11456 return m_hInteractionPartner->MyNPCPointer();
11457}
11458
11459//-----------------------------------------------------------------------------
11460// Purpose: Called when exiting a scripted sequence.
11461// Output : Returns true if alive, false if dead.
11462//-----------------------------------------------------------------------------
11463bool CAI_BaseNPC::ExitScriptedSequence( )
11464{
11465 if ( m_lifeState == LIFE_DYING )
11466 {
11467 // is this legal?
11468 // BUGBUG -- This doesn't call Killed()
11469 SetIdealState( NPC_STATE_DEAD );
11470 return false;
11471 }
11472
11473 if (m_hCine)
11474 {
11475 m_hCine->CancelScript( );
11476 }
11477
11478 return true;
11479}
11480
11481
11482bool CAI_BaseNPC::CineCleanup()
11483{
11484 CAI_ScriptedSequence *pOldCine = m_hCine;
11485
11486 bool bDestroyCine = false;
11487 if ( IsRunningDynamicInteraction() )
11488 {
11489 bDestroyCine = true;
11490
11491 // Re-enable physics collisions between me & the other NPC
11492 if ( m_hInteractionPartner )
11493 {
11494 PhysEnableEntityCollisions( this, m_hInteractionPartner );
11495 //Msg("%s(%s) enabled collisions with %s(%s) at %0.2f\n", GetClassName(), GetDebugName(), m_hInteractionPartner->GetClassName(), m_hInteractionPartner->GetDebugName(), gpGlobals->curtime );
11496 }
11497
11498 if ( m_hForcedInteractionPartner )
11499 {
11500 // We've finished a forced interaction. Let the mapmaker know.
11501 m_OnForcedInteractionFinished.FireOutput( this, this );
11502 }
11503
11504 // Clear interaction partner, because we're not running a scripted sequence anymore
11505 m_hInteractionPartner = NULL;
11506 CleanupForcedInteraction();
11507 }
11508
11509 // am I linked to a cinematic?
11510 if (m_hCine)
11511 {
11512 // okay, reset me to what it thought I was before
11513 m_hCine->SetTarget( NULL );
11514 // NOTE that this will have had EF_NODRAW removed in script.dll when it's cached off
11515 SetEffects( m_hCine->m_saved_effects );
11516 }
11517 else
11518 {
11519 // arg, punt
11520 AddSolidFlags( FSOLID_NOT_STANDABLE );
11521 }
11522 m_hCine = NULL;
11523 SetTarget( NULL );
11524 SetGoalEnt( NULL );
11525 if (m_lifeState == LIFE_DYING)
11526 {
11527 // last frame of death animation?
11528 m_iHealth = 0;
11529 AddSolidFlags( FSOLID_NOT_SOLID );
11530 SetState( NPC_STATE_DEAD );
11531 m_lifeState = LIFE_DEAD;
11532 UTIL_SetSize( this, WorldAlignMins(), Vector(WorldAlignMaxs().x, WorldAlignMaxs().y, WorldAlignMins().z + 2) );
11533
11534 if ( pOldCine && pOldCine->HasSpawnFlags( SF_SCRIPT_LEAVECORPSE ) )
11535 {
11536 SetUse( NULL ); // BUGBUG -- This doesn't call Killed()
11537 SetThink( NULL ); // This will probably break some stuff
11538 SetTouch( NULL );
11539 }
11540 else
11541 SUB_StartFadeOut(); // SetThink( SUB_DoNothing );
11542
11543
11544 //Not becoming a ragdoll, so set the NOINTERP flag on.
11545 if ( CanBecomeRagdoll() == false )
11546 {
11547 StopAnimation();
11548 AddEffects( EF_NOINTERP ); // Don't interpolate either, assume the corpse is positioned in its final resting place
11549 }
11550
11551 SetMoveType( MOVETYPE_NONE );
11552 return false;
11553 }
11554
11555 // If we actually played a sequence
11556 if ( pOldCine && pOldCine->m_iszPlay != NULL_STRING && pOldCine->PlayedSequence() )
11557 {
11558 if ( !pOldCine->HasSpawnFlags(SF_SCRIPT_NOSCRIPTMOVEMENT) )
11559 {
11560 // reset position
11561 Vector new_origin;
11562 QAngle new_angle;
11563 GetBonePosition( 0, new_origin, new_angle );
11564
11565 // Figure out how far they have moved
11566 // We can't really solve this problem because we can't query the movement of the origin relative
11567 // to the sequence. We can get the root bone's position as we do here, but there are
11568 // cases where the root bone is in a different relative position to the entity's origin
11569 // before/after the sequence plays. So we are stuck doing this:
11570
11571 // !!!HACKHACK: Float the origin up and drop to floor because some sequences have
11572 // irregular motion that can't be properly accounted for.
11573
11574 // UNDONE: THIS SHOULD ONLY HAPPEN IF WE ACTUALLY PLAYED THE SEQUENCE.
11575 Vector oldOrigin = GetLocalOrigin();
11576
11577 // UNDONE: ugly hack. Don't move NPC if they don't "seem" to move
11578 // this really needs to be done with the AX,AY,etc. flags, but that aren't consistantly
11579 // being set, so animations that really do move won't be caught.
11580 if ((oldOrigin - new_origin).Length2D() < 8.0)
11581 new_origin = oldOrigin;
11582
11583 Vector origin = GetLocalOrigin();
11584
11585 origin.x = new_origin.x;
11586 origin.y = new_origin.y;
11587 origin.z += 1;
11588
11589 if ( GetFlags() & FL_FLY )
11590 {
11591 origin.z = new_origin.z;
11592 SetLocalOrigin( origin );
11593 }
11594 else
11595 {
11596 SetLocalOrigin( origin );
11597
11598 int drop = UTIL_DropToFloor( this, MASK_NPCSOLID );
11599
11600 // Origin in solid? Set to org at the end of the sequence
11601 if ( drop < 0 )
11602 {
11603 SetLocalOrigin( oldOrigin );
11604 }
11605 else if ( drop == 0 ) // Hanging in air?
11606 {
11607 Vector origin = GetLocalOrigin();
11608 origin.z = new_origin.z;
11609 SetLocalOrigin( origin );
11610 SetGroundEntity( NULL );
11611 }
11612 }
11613
11614 origin = GetLocalOrigin();
11615
11616 // teleport if it's a non-trivial distance
11617 if ((oldOrigin - origin).Length() > 8.0)
11618 {
11619 // Call teleport to notify
11620 Teleport( &origin, NULL, NULL );
11621 SetLocalOrigin( origin );
11622 AddEffects( EF_NOINTERP );
11623 }
11624
11625 if ( m_iHealth <= 0 )
11626 {
11627 // Dropping out because he got killed
11628 SetIdealState( NPC_STATE_DEAD );
11629 SetCondition( COND_LIGHT_DAMAGE );
11630 m_lifeState = LIFE_DYING;
11631 }
11632 }
11633
11634 // We should have some animation to put these guys in, but for now it's idle.
11635 // Due to NOINTERP above, there won't be any blending between this anim & the sequence
11636 m_Activity = ACT_RESET;
11637 }
11638
11639 // set them back into a normal state
11640 if ( m_iHealth > 0 )
11641 {
11642 SetIdealState( NPC_STATE_IDLE );
11643 }
11644 else
11645 {
11646 // Dropping out because he got killed
11647 SetIdealState( NPC_STATE_DEAD );
11648 SetCondition( COND_LIGHT_DAMAGE );
11649 }
11650
11651 // SetAnimation( m_NPCState );
11652 CLEARBITS(m_spawnflags, SF_NPC_WAIT_FOR_SCRIPT );
11653
11654 if ( bDestroyCine )
11655 {
11656 UTIL_Remove( pOldCine );
11657 }
11658
11659 return true;
11660}
11661
11662//-----------------------------------------------------------------------------
11663
11664void CAI_BaseNPC::Teleport( const Vector *newPosition, const QAngle *newAngles, const Vector *newVelocity )
11665{
11666 CleanupScriptsOnTeleport( false );
11667
11668 BaseClass::Teleport( newPosition, newAngles, newVelocity );
11669}
11670
11671//-----------------------------------------------------------------------------
11672
11673bool CAI_BaseNPC::FindSpotForNPCInRadius( Vector *pResult, const Vector &vStartPos, CAI_BaseNPC *pNPC, float radius, bool bOutOfPlayerViewcone )
11674{
11675 CBasePlayer *pPlayer = AI_GetSinglePlayer();
11676 QAngle fan;
11677
11678 fan.x = 0;
11679 fan.z = 0;
11680
11681 for( fan.y = 0 ; fan.y < 360 ; fan.y += 18.0 )
11682 {
11683 Vector vecTest;
11684 Vector vecDir;
11685
11686 AngleVectors( fan, &vecDir );
11687
11688 vecTest = vStartPos + vecDir * radius;
11689
11690 if ( bOutOfPlayerViewcone && pPlayer && !pPlayer->FInViewCone( vecTest ) )
11691 continue;
11692
11693 trace_t tr;
11694
11695 UTIL_TraceLine( vecTest, vecTest - Vector( 0, 0, 8192 ), MASK_SHOT, pNPC, COLLISION_GROUP_NONE, &tr );
11696 if( tr.fraction == 1.0 )
11697 {
11698 continue;
11699 }
11700
11701 UTIL_TraceHull( tr.endpos,
11702 tr.endpos + Vector( 0, 0, 10 ),
11703 pNPC->GetHullMins(),
11704 pNPC->GetHullMaxs(),
11705 MASK_NPCSOLID,
11706 pNPC,
11707 COLLISION_GROUP_NONE,
11708 &tr );
11709
11710 if( tr.fraction == 1.0 && pNPC->GetMoveProbe()->CheckStandPosition( tr.endpos, MASK_NPCSOLID ) )
11711 {
11712 *pResult = tr.endpos;
11713 return true;
11714 }
11715 }
11716 return false;
11717}
11718
11719//-----------------------------------------------------------------------------
11720
11721bool CAI_BaseNPC::IsNavigationUrgent()
11722{
11723 // return true if the navigation is for something that can't react well to failure
11724 if ( IsCurSchedule( SCHED_SCRIPTED_WALK, false ) ||
11725 IsCurSchedule( SCHED_SCRIPTED_RUN, false ) ||
11726 IsCurSchedule( SCHED_SCRIPTED_CUSTOM_MOVE, false ) ||
11727 ( IsCurSchedule( SCHED_SCENE_GENERIC, false ) && IsInLockedScene() ) )
11728 {
11729 return true;
11730 }
11731 return false;
11732}
11733
11734//-----------------------------------------------------------------------------
11735
11736bool CAI_BaseNPC::ShouldFailNav( bool bMovementFailed )
11737{
11738 // Robin: Only fail movement. Fail route building so that we'll keep trying to build routes.
11739 // Otherwise, we can can get stuck inside a movement task without a route.
11740 if ( bMovementFailed && IsNavigationUrgent())
11741 {
11742 return false;
11743 }
11744 return true;
11745}
11746
11747Navigation_t CAI_BaseNPC::GetNavType() const
11748{
11749 return m_pNavigator->GetNavType();
11750}
11751
11752void CAI_BaseNPC::SetNavType( Navigation_t navType )
11753{
11754 m_pNavigator->SetNavType( navType );
11755}
11756
11757//-----------------------------------------------------------------------------
11758// NPCs can override this to tweak with how costly particular movements are
11759//-----------------------------------------------------------------------------
11760bool CAI_BaseNPC::MovementCost( int moveType, const Vector &vecStart, const Vector &vecEnd, float *pCost )
11761{
11762 // We have nothing to say on the matter, but derived classes might
11763 return false;
11764}
11765
11766bool CAI_BaseNPC::OverrideMoveFacing( const AILocalMoveGoal_t &move, float flInterval )
11767{
11768 return false;
11769}
11770
11771bool CAI_BaseNPC::OverrideMove( float flInterval )
11772{
11773 return false;
11774}
11775
11776
11777//=========================================================
11778// VecToYaw - turns a directional vector into a yaw value
11779// that points down that vector.
11780//=========================================================
11781float CAI_BaseNPC::VecToYaw( const Vector &vecDir )
11782{
11783 if (vecDir.x == 0 && vecDir.y == 0 && vecDir.z == 0)
11784 return GetLocalAngles().y;
11785
11786 return UTIL_VecToYaw( vecDir );
11787}
11788
11789//-----------------------------------------------------------------------------
11790// Inherited from IAI_MotorMovementServices
11791//-----------------------------------------------------------------------------
11792float CAI_BaseNPC::CalcYawSpeed( void )
11793{
11794 // Negative values are invalud
11795 return -1.0f;
11796}
11797
11798bool CAI_BaseNPC::OnCalcBaseMove( AILocalMoveGoal_t *pMoveGoal,
11799 float distClear,
11800 AIMoveResult_t *pResult )
11801{
11802 if ( pMoveGoal->directTrace.pObstruction )
11803 {
11804 CBasePropDoor *pPropDoor = dynamic_cast<CBasePropDoor *>( pMoveGoal->directTrace.pObstruction );
11805 if ( pPropDoor && OnUpcomingPropDoor( pMoveGoal, pPropDoor, distClear, pResult ) )
11806 {
11807 return true;
11808 }
11809 }
11810
11811 return false;
11812}
11813
11814
11815bool CAI_BaseNPC::OnObstructionPreSteer( AILocalMoveGoal_t *pMoveGoal,
11816 float distClear,
11817 AIMoveResult_t *pResult )
11818{
11819 if ( pMoveGoal->directTrace.pObstruction )
11820 {
11821 CBaseDoor *pDoor = dynamic_cast<CBaseDoor *>( pMoveGoal->directTrace.pObstruction );
11822 if ( pDoor && OnObstructingDoor( pMoveGoal, pDoor, distClear, pResult ) )
11823 {
11824 return true;
11825 }
11826 }
11827
11828 return false;
11829}
11830
11831
11832bool CAI_BaseNPC::OnObstructingDoor( AILocalMoveGoal_t *pMoveGoal,
11833 CBaseDoor *pDoor,
11834 float distClear,
11835 AIMoveResult_t *pResult )
11836{
11837 if ( pMoveGoal->maxDist < distClear )
11838 return false;
11839
11840 // By default, NPCs don't know how to open doors
11841 if ( pDoor->m_toggle_state == TS_AT_BOTTOM || pDoor->m_toggle_state == TS_GOING_DOWN )
11842 {
11843 if ( distClear < 0.1 )
11844 {
11845 *pResult = AIMR_BLOCKED_ENTITY;
11846 }
11847 else
11848 {
11849 pMoveGoal->maxDist = distClear;
11850 *pResult = AIMR_OK;
11851 }
11852
11853 return true;
11854 }
11855
11856 return false;
11857}
11858
11859
11860//-----------------------------------------------------------------------------
11861// Purpose:
11862// Input : pMoveGoal -
11863// pDoor -
11864// distClear -
11865// default -
11866// spawn -
11867// oldorg -
11868// pfPosition -
11869// neworg -
11870// Output : Returns true if movement is solved, false otherwise.
11871//-----------------------------------------------------------------------------
11872
11873bool CAI_BaseNPC::OnUpcomingPropDoor( AILocalMoveGoal_t *pMoveGoal,
11874 CBasePropDoor *pDoor,
11875 float distClear,
11876 AIMoveResult_t *pResult )
11877{
11878 if ( (pMoveGoal->flags & AILMG_TARGET_IS_GOAL) && pMoveGoal->maxDist < distClear )
11879 return false;
11880
11881 if ( pMoveGoal->maxDist + GetHullWidth() * .25 < distClear )
11882 return false;
11883
11884 if (pDoor == m_hOpeningDoor)
11885 {
11886 if ( pDoor->IsNPCOpening( this ) )
11887 {
11888 // We're in the process of opening the door, don't be blocked by it.
11889 pMoveGoal->maxDist = distClear;
11890 *pResult = AIMR_OK;
11891 return true;
11892 }
11893 m_hOpeningDoor = NULL;
11894 }
11895
11896 if ((CapabilitiesGet() & bits_CAP_DOORS_GROUP) && !pDoor->IsDoorLocked() && (pDoor->IsDoorClosed() || pDoor->IsDoorClosing()))
11897 {
11898 AI_Waypoint_t *pOpenDoorRoute = NULL;
11899
11900 opendata_t opendata;
11901 pDoor->GetNPCOpenData(this, opendata);
11902
11903 // dvs: FIXME: local route might not be sufficient
11904 pOpenDoorRoute = GetPathfinder()->BuildLocalRoute(
11905 GetLocalOrigin(),
11906 opendata.vecStandPos,
11907 NULL,
11908 bits_WP_TO_DOOR | bits_WP_DONT_SIMPLIFY,
11909 NO_NODE,
11910 bits_BUILD_GROUND | bits_BUILD_IGNORE_NPCS,
11911 0.0);
11912
11913 if ( pOpenDoorRoute )
11914 {
11915 if ( AIIsDebuggingDoors(this) )
11916 {
11917 NDebugOverlay::Cross3D(opendata.vecStandPos + Vector(0,0,1), 32, 255, 255, 255, false, 1.0 );
11918 Msg( "Opening door!\n" );
11919 }
11920
11921 // Attach the door to the waypoint so we open it when we get there.
11922 // dvs: FIXME: this is kind of bullshit, I need to find the exact waypoint to open the door
11923 // should I just walk the path until I find it?
11924 pOpenDoorRoute->m_hData = pDoor;
11925
11926 GetNavigator()->GetPath()->PrependWaypoints( pOpenDoorRoute );
11927
11928 m_hOpeningDoor = pDoor;
11929 pMoveGoal->maxDist = distClear;
11930 *pResult = AIMR_CHANGE_TYPE;
11931
11932 return true;
11933 }
11934 else
11935 AIDoorDebugMsg( this, "Failed create door route!\n" );
11936 }
11937
11938 return false;
11939}
11940
11941
11942//-----------------------------------------------------------------------------
11943// Purpose: Called by the navigator to initiate the opening of a prop_door
11944// that is in our way.
11945//-----------------------------------------------------------------------------
11946void CAI_BaseNPC::OpenPropDoorBegin( CBasePropDoor *pDoor )
11947{
11948 // dvs: not quite working, disabled for now.
11949 //opendata_t opendata;
11950 //pDoor->GetNPCOpenData(this, opendata);
11951 //
11952 //if (HaveSequenceForActivity(opendata.eActivity))
11953 //{
11954 // SetIdealActivity(opendata.eActivity);
11955 //}
11956 //else
11957 {
11958 // We don't have an appropriate sequence, just open the door magically.
11959 OpenPropDoorNow( pDoor );
11960 }
11961}
11962
11963
11964//-----------------------------------------------------------------------------
11965// Purpose: Called when we are trying to open a prop_door and it's time to start
11966// the door moving. This is called either in response to an anim event
11967// or as a fallback when we don't have an appropriate open activity.
11968//-----------------------------------------------------------------------------
11969void CAI_BaseNPC::OpenPropDoorNow( CBasePropDoor *pDoor )
11970{
11971 // Start the door moving.
11972 pDoor->NPCOpenDoor(this);
11973
11974 // Wait for the door to finish opening before trying to move through the doorway.
11975 m_flMoveWaitFinished = gpGlobals->curtime + pDoor->GetOpenInterval();
11976}
11977
11978
11979//-----------------------------------------------------------------------------
11980// Purpose: Called when the door we were trying to open becomes fully open.
11981// Input : pDoor -
11982//-----------------------------------------------------------------------------
11983void CAI_BaseNPC::OnDoorFullyOpen(CBasePropDoor *pDoor)
11984{
11985 // We're done with the door.
11986 m_hOpeningDoor = NULL;
11987}
11988
11989
11990//-----------------------------------------------------------------------------
11991// Purpose: Called when the door we were trying to open becomes blocked before opening.
11992// Input : pDoor -
11993//-----------------------------------------------------------------------------
11994void CAI_BaseNPC::OnDoorBlocked(CBasePropDoor *pDoor)
11995{
11996 // dvs: FIXME: do something so that we don't loop forever trying to open this door
11997 // not clearing out the door handle will cause the NPC to invalidate the connection
11998 // We're done with the door.
11999 //m_hOpeningDoor = NULL;
12000}
12001
12002
12003//-----------------------------------------------------------------------------
12004// Purpose: Template NPCs are marked as templates by the level designer. They
12005// do not spawn, but their keyvalues are saved for use by a template
12006// spawner.
12007//-----------------------------------------------------------------------------
12008bool CAI_BaseNPC::IsTemplate( void )
12009{
12010 return HasSpawnFlags( SF_NPC_TEMPLATE );
12011}
12012
12013
12014
12015//-----------------------------------------------------------------------------
12016//
12017// Movement code for walking + flying
12018//
12019//-----------------------------------------------------------------------------
12020int CAI_BaseNPC::FlyMove( const Vector& pfPosition, unsigned int mask )
12021{
12022 Vector oldorg, neworg;
12023 trace_t trace;
12024
12025 // try the move
12026 VectorCopy( GetAbsOrigin(), oldorg );
12027 VectorAdd( oldorg, pfPosition, neworg );
12028 UTIL_TraceEntity( this, oldorg, neworg, mask, &trace );
12029 if (trace.fraction == 1)
12030 {
12031 if ( (GetFlags() & FL_SWIM) && enginetrace->GetPointContents(trace.endpos) == CONTENTS_EMPTY )
12032 return false; // swim monster left water
12033
12034 SetAbsOrigin( trace.endpos );
12035 PhysicsTouchTriggers();
12036 return true;
12037 }
12038
12039 return false;
12040}
12041
12042
12043//-----------------------------------------------------------------------------
12044// Purpose:
12045// Input : ent -
12046// Dir - Normalized direction vector for movement.
12047// dist - Distance along 'Dir' to move.
12048// iMode -
12049// Output : Returns nonzero on success, zero on failure.
12050//-----------------------------------------------------------------------------
12051int CAI_BaseNPC::WalkMove( const Vector& vecPosition, unsigned int mask )
12052{
12053 if ( GetFlags() & (FL_FLY | FL_SWIM) )
12054 {
12055 return FlyMove( vecPosition, mask );
12056 }
12057
12058 if ( (GetFlags() & FL_ONGROUND) == 0 )
12059 {
12060 return 0;
12061 }
12062
12063 trace_t trace;
12064 Vector oldorg, neworg, end;
12065 Vector move( vecPosition[0], vecPosition[1], 0.0f );
12066 VectorCopy( GetAbsOrigin(), oldorg );
12067 VectorAdd( oldorg, move, neworg );
12068
12069 // push down from a step height above the wished position
12070 float flStepSize = sv_stepsize.GetFloat();
12071 neworg[2] += flStepSize;
12072 VectorCopy(neworg, end);
12073 end[2] -= flStepSize*2;
12074
12075 UTIL_TraceEntity( this, neworg, end, mask, &trace );
12076 if ( trace.allsolid )
12077 return false;
12078
12079 if (trace.startsolid)
12080 {
12081 neworg[2] -= flStepSize;
12082 UTIL_TraceEntity( this, neworg, end, mask, &trace );
12083 if ( trace.allsolid || trace.startsolid )
12084 return false;
12085 }
12086
12087 if (trace.fraction == 1)
12088 {
12089 // if monster had the ground pulled out, go ahead and fall
12090 if ( GetFlags() & FL_PARTIALGROUND )
12091 {
12092 SetAbsOrigin( oldorg + move );
12093 PhysicsTouchTriggers();
12094 SetGroundEntity( NULL );
12095 return true;
12096 }
12097
12098 return false; // walked off an edge
12099 }
12100
12101 // check point traces down for dangling corners
12102 SetAbsOrigin( trace.endpos );
12103
12104 if (UTIL_CheckBottom( this, NULL, flStepSize ) == 0)
12105 {
12106 if ( GetFlags() & FL_PARTIALGROUND )
12107 {
12108 // entity had floor mostly pulled out from underneath it
12109 // and is trying to correct
12110 PhysicsTouchTriggers();
12111 return true;
12112 }
12113
12114 // Reset to original position
12115 SetAbsOrigin( oldorg );
12116 return false;
12117 }
12118
12119 if ( GetFlags() & FL_PARTIALGROUND )
12120 {
12121 // Con_Printf ("back on ground\n");
12122 RemoveFlag( FL_PARTIALGROUND );
12123 }
12124
12125 // the move is ok
12126 SetGroundEntity( trace.m_pEnt );
12127 PhysicsTouchTriggers();
12128 return true;
12129}
12130
12131//-----------------------------------------------------------------------------
12132
12133static void AIMsgGuts( CAI_BaseNPC *pAI, unsigned flags, const char *pszMsg )
12134{
12135 int len = strlen( pszMsg );
12136 const char *pszFmt2 = NULL;
12137
12138 if ( len && pszMsg[len-1] == '\n' )
12139 {
12140 (const_cast<char *>(pszMsg))[len-1] = 0;
12141 pszFmt2 = "%s (%s: %d/%s) [%d]\n";
12142 }
12143 else
12144 pszFmt2 = "%s (%s: %d/%s) [%d]";
12145
12146 DevMsg( pszFmt2,
12147 pszMsg,
12148 pAI->GetClassname(),
12149 pAI->entindex(),
12150 ( pAI->GetEntityName() == NULL_STRING ) ? "<unnamed>" : STRING(pAI->GetEntityName()),
12151 gpGlobals->tickcount );
12152}
12153
12154void DevMsg( CAI_BaseNPC *pAI, unsigned flags, const char *pszFormat, ... )
12155{
12156 if ( (flags & AIMF_IGNORE_SELECTED) || (pAI->m_debugOverlays & OVERLAY_NPC_SELECTED_BIT) )
12157 {
12158 AIMsgGuts( pAI, flags, CFmtStr( &pszFormat ) );
12159 }
12160}
12161
12162//-----------------------------------------------------------------------------
12163
12164void DevMsg( CAI_BaseNPC *pAI, const char *pszFormat, ... )
12165{
12166 if ( (pAI->m_debugOverlays & OVERLAY_NPC_SELECTED_BIT) )
12167 {
12168 AIMsgGuts( pAI, 0, CFmtStr( &pszFormat ) );
12169 }
12170}
12171
12172//-----------------------------------------------------------------------------
12173
12174bool CAI_BaseNPC::IsPlayerAlly( CBasePlayer *pPlayer )
12175{
12176 if ( pPlayer == NULL )
12177 {
12178 // in multiplayer mode we need a valid pPlayer
12179 // or override this virtual function
12180 if ( !AI_IsSinglePlayer() )
12181 return false;
12182
12183 // NULL means single player mode
12184 pPlayer = UTIL_GetLocalPlayer();
12185 }
12186
12187 return ( !pPlayer || IRelationType( pPlayer ) == D_LI );
12188}
12189
12190//-----------------------------------------------------------------------------
12191
12192void CAI_BaseNPC::SetCommandGoal( const Vector &vecGoal )
12193{
12194 m_vecCommandGoal = vecGoal;
12195 m_CommandMoveMonitor.ClearMark();
12196}
12197
12198//-----------------------------------------------------------------------------
12199
12200void CAI_BaseNPC::ClearCommandGoal()
12201{
12202 m_vecCommandGoal = vec3_invalid;
12203 m_CommandMoveMonitor.ClearMark();
12204}
12205
12206//-----------------------------------------------------------------------------
12207
12208bool CAI_BaseNPC::IsInPlayerSquad() const
12209{
12210 return ( m_pSquad && MAKE_STRING(m_pSquad->GetName()) == GetPlayerSquadName() && !CAI_Squad::IsSilentMember(this) );
12211}
12212
12213
12214//-----------------------------------------------------------------------------
12215
12216bool CAI_BaseNPC::CanBeUsedAsAFriend( void )
12217{
12218 if ( IsCurSchedule(SCHED_FORCED_GO) || IsCurSchedule(SCHED_FORCED_GO_RUN) )
12219 return false;
12220 return true;
12221}
12222
12223//-----------------------------------------------------------------------------
12224
12225Vector CAI_BaseNPC::GetSmoothedVelocity( void )
12226{
12227 if( GetNavType() == NAV_GROUND || GetNavType() == NAV_FLY )
12228 {
12229 return m_pMotor->GetCurVel();
12230 }
12231
12232 return BaseClass::GetSmoothedVelocity();
12233}
12234
12235
12236//-----------------------------------------------------------------------------
12237
12238bool CAI_BaseNPC::IsCoverPosition( const Vector &vecThreat, const Vector &vecPosition )
12239{
12240 trace_t tr;
12241
12242 // By default, we ignore the viewer (me) when determining cover positions
12243 CTraceFilterLOS filter( NULL, COLLISION_GROUP_NONE, this );
12244
12245 // If I'm trying to find cover from the player, and the player is in a vehicle,
12246 // ignore the vehicle for the purpose of determining line of sight.
12247 CBaseEntity *pEnemy = GetEnemy();
12248 if ( pEnemy )
12249 {
12250 // Hack to see if our threat position is our enemy
12251 bool bThreatPosIsEnemy = ( (vecThreat - GetEnemy()->EyePosition()).LengthSqr() < 0.1f );
12252 if ( bThreatPosIsEnemy )
12253 {
12254 CBaseCombatCharacter *pCCEnemy = GetEnemy()->MyCombatCharacterPointer();
12255 if ( pCCEnemy != NULL && pCCEnemy->IsInAVehicle() )
12256 {
12257 // Ignore the vehicle
12258 filter.SetPassEntity( pCCEnemy->GetVehicleEntity() );
12259 }
12260
12261 if ( !filter.GetPassEntity() )
12262 {
12263 filter.SetPassEntity( pEnemy );
12264 }
12265 }
12266 }
12267
12268 AI_TraceLOS( vecThreat, vecPosition, this, &tr, &filter );
12269
12270 if( tr.fraction != 1.0 && hl2_episodic.GetBool() )
12271 {
12272 if( tr.m_pEnt->m_iClassname == m_iClassname )
12273 {
12274 // Don't hide behind buddies!
12275 return false;
12276 }
12277 }
12278
12279 return (tr.fraction != 1.0);
12280}
12281
12282//-----------------------------------------------------------------------------
12283
12284float CAI_BaseNPC::SetWait( float minWait, float maxWait )
12285{
12286 int minThinks = Ceil2Int( minWait * 10 );
12287
12288 if ( maxWait == 0.0 )
12289 {
12290 m_flWaitFinished = gpGlobals->curtime + ( 0.1 * minThinks );
12291 }
12292 else
12293 {
12294 if ( minThinks == 0 ) // random 0..n is almost certain to not return 0
12295 minThinks = 1;
12296 int maxThinks = Ceil2Int( maxWait * 10 );
12297
12298 m_flWaitFinished = gpGlobals->curtime + ( 0.1 * random->RandomInt( minThinks, maxThinks ) );
12299 }
12300 return m_flWaitFinished;
12301}
12302
12303//-----------------------------------------------------------------------------
12304
12305void CAI_BaseNPC::ClearWait()
12306{
12307 m_flWaitFinished = FLT_MAX;
12308}
12309
12310//-----------------------------------------------------------------------------
12311
12312bool CAI_BaseNPC::IsWaitFinished()
12313{
12314 return ( gpGlobals->curtime >= m_flWaitFinished );
12315}
12316
12317//-----------------------------------------------------------------------------
12318
12319bool CAI_BaseNPC::IsWaitSet()
12320{
12321 return ( m_flWaitFinished != FLT_MAX );
12322}
12323
12324void CAI_BaseNPC::TestPlayerPushing( CBaseEntity *pEntity )
12325{
12326 if ( HasSpawnFlags( SF_NPC_NO_PLAYER_PUSHAWAY ) )
12327 return;
12328
12329 // Heuristic for determining if the player is pushing me away
12330 CBasePlayer *pPlayer = ToBasePlayer( pEntity );
12331 if ( pPlayer && !( pPlayer->GetFlags() & FL_NOTARGET ) )
12332 {
12333 if ( (pPlayer->m_nButtons & (IN_FORWARD|IN_BACK|IN_MOVELEFT|IN_MOVERIGHT)) ||
12334 pPlayer->GetAbsVelocity().AsVector2D().LengthSqr() > 50*50 )
12335 {
12336 SetCondition( COND_PLAYER_PUSHING );
12337 Vector vecPush = GetAbsOrigin() - pPlayer->GetAbsOrigin();
12338 VectorNormalize( vecPush );
12339 CascadePlayerPush( vecPush, pPlayer->WorldSpaceCenter() );
12340 }
12341 }
12342}
12343
12344void CAI_BaseNPC::CascadePlayerPush( const Vector &push, const Vector &pushOrigin )
12345{
12346 //
12347 // Try to push any friends that are in the way.
12348 //
12349 float hullWidth = GetHullWidth();
12350 const Vector & origin = GetAbsOrigin();
12351 const Vector2D &origin2D = origin.AsVector2D();
12352
12353 const float MIN_Z_TO_TRANSMIT = GetHullHeight() * 0.5 + 0.1;
12354 const float DIST_REQD_TO_TRANSMIT_PUSH_SQ = Square( hullWidth * 5 + 0.1 );
12355 const float DIST_FROM_PUSH_VECTOR_REQD_SQ = Square( hullWidth + 0.1 );
12356
12357 Vector2D pushTestPoint = vec2_invalid;
12358
12359 for ( int i = 0; i < g_AI_Manager.NumAIs(); i++ )
12360 {
12361 CAI_BaseNPC *pOther = g_AI_Manager.AccessAIs()[i];
12362 if ( pOther != this && pOther->IRelationType(this) == D_LI && !pOther->HasCondition( COND_PLAYER_PUSHING ) )
12363 {
12364 const Vector &friendOrigin = pOther->GetAbsOrigin();
12365 if ( fabsf( friendOrigin.z - origin.z ) < MIN_Z_TO_TRANSMIT &&
12366 ( friendOrigin.AsVector2D() - origin.AsVector2D() ).LengthSqr() < DIST_REQD_TO_TRANSMIT_PUSH_SQ )
12367 {
12368 if ( pushTestPoint == vec2_invalid )
12369 {
12370 pushTestPoint = origin2D - pushOrigin.AsVector2D();
12371 // No normalize, since it wants to just be a big number and we can't be less that a hull away
12372 pushTestPoint *= 2000;
12373 pushTestPoint += origin2D;
12374
12375 }
12376 float t;
12377 float distSq = CalcDistanceSqrToLine2D( friendOrigin.AsVector2D(), origin2D, pushTestPoint, &t );
12378 if ( t > 0 && distSq < DIST_FROM_PUSH_VECTOR_REQD_SQ )
12379 {
12380 pOther->SetCondition( COND_PLAYER_PUSHING );
12381 }
12382 }
12383 }
12384 }
12385}
12386
12387
12388//-----------------------------------------------------------------------------
12389// Break into pieces!
12390//-----------------------------------------------------------------------------
12391void CAI_BaseNPC::Break( CBaseEntity *pBreaker )
12392{
12393 m_takedamage = DAMAGE_NO;
12394
12395 Vector velocity;
12396 AngularImpulse angVelocity;
12397 IPhysicsObject *pPhysics = VPhysicsGetObject();
12398 Vector origin;
12399 QAngle angles;
12400 AddSolidFlags( FSOLID_NOT_SOLID );
12401 if ( pPhysics )
12402 {
12403 pPhysics->GetVelocity( &velocity, &angVelocity );
12404 pPhysics->GetPosition( &origin, &angles );
12405 pPhysics->RecheckCollisionFilter();
12406 }
12407 else
12408 {
12409 velocity = GetAbsVelocity();
12410 QAngleToAngularImpulse( GetLocalAngularVelocity(), angVelocity );
12411 origin = GetAbsOrigin();
12412 angles = GetAbsAngles();
12413 }
12414
12415 breakablepropparams_t params( GetAbsOrigin(), GetAbsAngles(), velocity, angVelocity );
12416 params.impactEnergyScale = m_impactEnergyScale;
12417 params.defCollisionGroup = GetCollisionGroup();
12418 if ( params.defCollisionGroup == COLLISION_GROUP_NONE )
12419 {
12420 // don't automatically make anything COLLISION_GROUP_NONE or it will
12421 // collide with debris being ejected by breaking
12422 params.defCollisionGroup = COLLISION_GROUP_INTERACTIVE;
12423 }
12424
12425 // no damage/damage force? set a burst of 100 for some movement
12426 params.defBurstScale = 100;//pDamageInfo ? 0 : 100;
12427 PropBreakableCreateAll( GetModelIndex(), pPhysics, params, this, -1, false );
12428
12429 UTIL_Remove(this);
12430}
12431
12432
12433//-----------------------------------------------------------------------------
12434// Purpose: Input handler for breaking the breakable immediately.
12435//-----------------------------------------------------------------------------
12436void CAI_BaseNPC::InputBreak( inputdata_t &inputdata )
12437{
12438 Break( inputdata.pActivator );
12439}
12440
12441
12442//-----------------------------------------------------------------------------
12443
12444bool CAI_BaseNPC::FindNearestValidGoalPos( const Vector &vTestPoint, Vector *pResult )
12445{
12446 AIMoveTrace_t moveTrace;
12447 Vector vCandidate = vec3_invalid;
12448 if ( GetNavigator()->CanFitAtPosition( vTestPoint, MASK_SOLID_BRUSHONLY ) )
12449 {
12450 if ( GetMoveProbe()->CheckStandPosition( vTestPoint, MASK_SOLID_BRUSHONLY ) )
12451 {
12452 vCandidate = vTestPoint;
12453 }
12454 }
12455
12456 if ( vCandidate == vec3_invalid )
12457 {
12458 int iNearestNode = GetPathfinder()->NearestNodeToPoint( vTestPoint );
12459 if ( iNearestNode != NO_NODE )
12460 {
12461 GetMoveProbe()->MoveLimit( NAV_GROUND,
12462 g_pBigAINet->GetNodePosition(GetHullType(), iNearestNode ),
12463 vTestPoint,
12464 MASK_SOLID_BRUSHONLY,
12465 NULL,
12466 0,
12467 &moveTrace );
12468 if ( ( moveTrace.vEndPosition - vTestPoint ).Length2DSqr() < Square( GetHullWidth() * 3.0 ) &&
12469 GetMoveProbe()->CheckStandPosition( moveTrace.vEndPosition, MASK_SOLID_BRUSHONLY ) )
12470 {
12471 vCandidate = moveTrace.vEndPosition;
12472 }
12473 }
12474 }
12475
12476 if ( vCandidate != vec3_invalid )
12477 {
12478 AI_Waypoint_t *pPathToPoint = GetPathfinder()->BuildRoute( GetAbsOrigin(), vCandidate, AI_GetSinglePlayer(), 5*12, NAV_NONE, true );
12479 if ( pPathToPoint )
12480 {
12481 GetPathfinder()->UnlockRouteNodes( pPathToPoint );
12482 CAI_Path tempPath;
12483 tempPath.SetWaypoints( pPathToPoint ); // path object will delete waypoints
12484 }
12485 else
12486 vCandidate = vec3_invalid;
12487 }
12488
12489 if ( vCandidate == vec3_invalid )
12490 {
12491 GetMoveProbe()->MoveLimit( NAV_GROUND,
12492 GetAbsOrigin(),
12493 vTestPoint,
12494 MASK_SOLID_BRUSHONLY,
12495 NULL,
12496 0,
12497 &moveTrace );
12498 vCandidate = moveTrace.vEndPosition;
12499 }
12500
12501 if ( vCandidate == vec3_invalid )
12502 return false;
12503
12504 *pResult = vCandidate;
12505 return true;
12506}
12507
12508//---------------------------------------------------------
12509// Pass a direction to get how far an NPC would see if facing
12510// that direction. Pass nothing to get the length of the NPC's
12511// current line of sight.
12512//---------------------------------------------------------
12513float CAI_BaseNPC::LineOfSightDist( const Vector &vecDir, float zEye )
12514{
12515 Vector testDir;
12516 if( vecDir == vec3_invalid )
12517 {
12518 testDir = EyeDirection3D();
12519 }
12520 else
12521 {
12522 testDir = vecDir;
12523 }
12524
12525 if ( zEye == FLT_MAX )
12526 zEye = EyePosition().z;
12527
12528 trace_t tr;
12529 // Need to center trace so don't get erratic results based on orientation
12530 Vector testPos( GetAbsOrigin().x, GetAbsOrigin().y, zEye );
12531 AI_TraceLOS( testPos, testPos + testDir * MAX_COORD_RANGE, this, &tr );
12532 return (tr.startpos - tr.endpos ).Length();
12533}
12534
12535ConVar ai_LOS_mode( "ai_LOS_mode", "0", FCVAR_REPLICATED );
12536
12537//-----------------------------------------------------------------------------
12538// Purpose: Use this to perform AI tracelines that are trying to determine LOS between points.
12539// LOS checks between entities should use FVisible.
12540//-----------------------------------------------------------------------------
12541void AI_TraceLOS( const Vector& vecAbsStart, const Vector& vecAbsEnd, CBaseEntity *pLooker, trace_t *ptr, ITraceFilter *pFilter )
12542{
12543 AI_PROFILE_SCOPE( AI_TraceLOS );
12544
12545 if ( ai_LOS_mode.GetBool() )
12546 {
12547 // Don't use LOS tracefilter
12548 UTIL_TraceLine( vecAbsStart, vecAbsEnd, MASK_OPAQUE, pLooker, COLLISION_GROUP_NONE, ptr );
12549 return;
12550 }
12551
12552 // Use the custom LOS trace filter
12553 CTraceFilterLOS traceFilter( pLooker, COLLISION_GROUP_NONE );
12554 if ( !pFilter )
12555 pFilter = &traceFilter;
12556 AI_TraceLine( vecAbsStart, vecAbsEnd, MASK_OPAQUE_AND_NPCS, pFilter, ptr );
12557}
12558
12559void CAI_BaseNPC::InputSetSpeedModifierRadius( inputdata_t &inputdata )
12560{
12561 m_iSpeedModRadius = inputdata.value.Int();
12562 m_iSpeedModRadius *= m_iSpeedModRadius;
12563}
12564void CAI_BaseNPC::InputSetSpeedModifierSpeed( inputdata_t &inputdata )
12565{
12566 m_iSpeedModSpeed = inputdata.value.Int();
12567}
12568
12569//-----------------------------------------------------------------------------
12570// Purpose:
12571//-----------------------------------------------------------------------------
12572bool CAI_BaseNPC::IsAllowedToDodge( void )
12573{
12574 // Can't do it if I'm not available
12575 if ( m_NPCState != NPC_STATE_IDLE && m_NPCState != NPC_STATE_ALERT && m_NPCState != NPC_STATE_COMBAT )
12576 return false;
12577
12578 return ( m_flNextDodgeTime <= gpGlobals->curtime );
12579}
12580
12581//-----------------------------------------------------------------------------
12582// Purpose:
12583//-----------------------------------------------------------------------------
12584void CAI_BaseNPC::ParseScriptedNPCInteractions( void )
12585{
12586 // Already parsed them?
12587 if ( m_ScriptedInteractions.Count() )
12588 return;
12589
12590 // Parse the model's key values and find any dynamic interactions
12591 KeyValues *modelKeyValues = new KeyValues("");
12592 if ( modelKeyValues->LoadFromBuffer( modelinfo->GetModelName( GetModel() ), modelinfo->GetModelKeyValueText( GetModel() ) ) )
12593 {
12594 // Do we have a dynamic interactions section?
12595 KeyValues *pkvInteractions = modelKeyValues->FindKey("dynamic_interactions");
12596 if ( pkvInteractions )
12597 {
12598 KeyValues *pkvNode = pkvInteractions->GetFirstSubKey();
12599 while ( pkvNode )
12600 {
12601 ScriptedNPCInteraction_t sInteraction;
12602 sInteraction.iszInteractionName = AllocPooledString( pkvNode->GetName() );
12603
12604 // Trigger method
12605 const char *pszTrigger = pkvNode->GetString( "trigger", NULL );
12606 if ( pszTrigger )
12607 {
12608 if ( !Q_strncmp( pszTrigger, "auto_in_combat", 14) )
12609 {
12610 sInteraction.iTriggerMethod = SNPCINT_AUTOMATIC_IN_COMBAT;
12611 }
12612 }
12613
12614 // Loop Break trigger method
12615 pszTrigger = pkvNode->GetString( "loop_break_trigger", NULL );
12616 if ( pszTrigger )
12617 {
12618 char szTrigger[256];
12619 Q_strncpy( szTrigger, pszTrigger, sizeof(szTrigger) );
12620 char *pszParam = strtok( szTrigger, " " );
12621 while (pszParam)
12622 {
12623 if ( !Q_strncmp( pszParam, "on_damage", 9) )
12624 {
12625 sInteraction.iLoopBreakTriggerMethod |= SNPCINT_LOOPBREAK_ON_DAMAGE;
12626 }
12627 if ( !Q_strncmp( pszParam, "on_flashlight_illum", 19) )
12628 {
12629 sInteraction.iLoopBreakTriggerMethod |= SNPCINT_LOOPBREAK_ON_FLASHLIGHT_ILLUM;
12630 }
12631
12632 pszParam = strtok(NULL," ");
12633 }
12634 }
12635
12636 // Origin
12637 const char *pszOrigin = pkvNode->GetString( "origin_relative", "0 0 0" );
12638 UTIL_StringToVector( sInteraction.vecRelativeOrigin.Base(), pszOrigin );
12639
12640 // Angles
12641 const char *pszAngles = pkvNode->GetString( "angles_relative", NULL );
12642 if ( pszAngles )
12643 {
12644 sInteraction.iFlags |= SCNPC_FLAG_TEST_OTHER_ANGLES;
12645 UTIL_StringToVector( sInteraction.angRelativeAngles.Base(), pszAngles );
12646 }
12647
12648 // Velocity
12649 const char *pszVelocity = pkvNode->GetString( "velocity_relative", NULL );
12650 if ( pszVelocity )
12651 {
12652 sInteraction.iFlags |= SCNPC_FLAG_TEST_OTHER_VELOCITY;
12653 UTIL_StringToVector( sInteraction.vecRelativeVelocity.Base(), pszVelocity );
12654 }
12655
12656 // Entry Sequence
12657 const char *pszSequence = pkvNode->GetString( "entry_sequence", NULL );
12658 if ( pszSequence )
12659 {
12660 sInteraction.sPhases[SNPCINT_ENTRY].iszSequence = AllocPooledString( pszSequence );
12661 }
12662 // Entry Activity
12663 const char *pszActivity = pkvNode->GetString( "entry_activity", NULL );
12664 if ( pszActivity )
12665 {
12666 sInteraction.sPhases[SNPCINT_ENTRY].iActivity = GetActivityID( pszActivity );
12667 }
12668
12669 // Sequence
12670 pszSequence = pkvNode->GetString( "sequence", NULL );
12671 if ( pszSequence )
12672 {
12673 sInteraction.sPhases[SNPCINT_SEQUENCE].iszSequence = AllocPooledString( pszSequence );
12674 }
12675 // Activity
12676 pszActivity = pkvNode->GetString( "activity", NULL );
12677 if ( pszActivity )
12678 {
12679 sInteraction.sPhases[SNPCINT_SEQUENCE].iActivity = GetActivityID( pszActivity );
12680 }
12681
12682 // Exit Sequence
12683 pszSequence = pkvNode->GetString( "exit_sequence", NULL );
12684 if ( pszSequence )
12685 {
12686 sInteraction.sPhases[SNPCINT_EXIT].iszSequence = AllocPooledString( pszSequence );
12687 }
12688 // Exit Activity
12689 pszActivity = pkvNode->GetString( "exit_activity", NULL );
12690 if ( pszActivity )
12691 {
12692 sInteraction.sPhases[SNPCINT_EXIT].iActivity = GetActivityID( pszActivity );
12693 }
12694
12695 // Delay
12696 sInteraction.flDelay = pkvNode->GetFloat( "delay", 10.0 );
12697
12698 // Delta
12699 sInteraction.flDistSqr = pkvNode->GetFloat( "origin_max_delta", (DSS_MAX_DIST * DSS_MAX_DIST) );
12700
12701 // Loop?
12702 if ( pkvNode->GetFloat( "loop_in_action", 0 ) )
12703 {
12704 sInteraction.iFlags |= SCNPC_FLAG_LOOP_IN_ACTION;
12705 }
12706
12707 // Needs a weapon?
12708 const char *pszNeedsWeapon = pkvNode->GetString( "needs_weapon", NULL );
12709 if ( pszNeedsWeapon )
12710 {
12711 if ( !Q_strncmp( pszNeedsWeapon, "ME", 2 ) )
12712 {
12713 sInteraction.iFlags |= SCNPC_FLAG_NEEDS_WEAPON_ME;
12714 }
12715 else if ( !Q_strncmp( pszNeedsWeapon, "THEM", 4 ) )
12716 {
12717 sInteraction.iFlags |= SCNPC_FLAG_NEEDS_WEAPON_THEM;
12718 }
12719 else if ( !Q_strncmp( pszNeedsWeapon, "BOTH", 4 ) )
12720 {
12721 sInteraction.iFlags |= SCNPC_FLAG_NEEDS_WEAPON_ME;
12722 sInteraction.iFlags |= SCNPC_FLAG_NEEDS_WEAPON_THEM;
12723 }
12724 }
12725
12726 // Specific weapon types
12727 const char *pszWeaponName = pkvNode->GetString( "weapon_mine", NULL );
12728 if ( pszWeaponName )
12729 {
12730 sInteraction.iFlags |= SCNPC_FLAG_NEEDS_WEAPON_ME;
12731 sInteraction.iszMyWeapon = AllocPooledString( pszWeaponName );
12732 }
12733 pszWeaponName = pkvNode->GetString( "weapon_theirs", NULL );
12734 if ( pszWeaponName )
12735 {
12736 sInteraction.iFlags |= SCNPC_FLAG_NEEDS_WEAPON_THEM;
12737 sInteraction.iszTheirWeapon = AllocPooledString( pszWeaponName );
12738 }
12739
12740 // Add it to the list
12741 AddScriptedNPCInteraction( &sInteraction );
12742
12743 // Move to next interaction
12744 pkvNode = pkvNode->GetNextKey();
12745 }
12746 }
12747 }
12748
12749 modelKeyValues->deleteThis();
12750}
12751
12752//-----------------------------------------------------------------------------
12753// Purpose:
12754//-----------------------------------------------------------------------------
12755void CAI_BaseNPC::AddScriptedNPCInteraction( ScriptedNPCInteraction_t *pInteraction )
12756{
12757 int nNewIndex = m_ScriptedInteractions.AddToTail();
12758
12759 if ( ai_debug_dyninteractions.GetBool() )
12760 {
12761 Msg("%s(%s): Added dynamic interaction: %s\n", GetClassName(), GetDebugName(), STRING(pInteraction->iszInteractionName) );
12762 }
12763
12764 // Copy the interaction over
12765 ScriptedNPCInteraction_t *pNewInt = &(m_ScriptedInteractions[nNewIndex]);
12766 memcpy( pNewInt, pInteraction, sizeof(ScriptedNPCInteraction_t) );
12767
12768 // Calculate the local to world matrix
12769 m_ScriptedInteractions[nNewIndex].matDesiredLocalToWorld.SetupMatrixOrgAngles( pInteraction->vecRelativeOrigin, pInteraction->angRelativeAngles );
12770}
12771
12772//-----------------------------------------------------------------------------
12773// Purpose:
12774//-----------------------------------------------------------------------------
12775const char *CAI_BaseNPC::GetScriptedNPCInteractionSequence( ScriptedNPCInteraction_t *pInteraction, int iPhase )
12776{
12777 if ( pInteraction->sPhases[iPhase].iActivity != ACT_INVALID )
12778 {
12779 int iSequence = SelectWeightedSequence( (Activity)pInteraction->sPhases[iPhase].iActivity );
12780 return GetSequenceName( iSequence );
12781 }
12782
12783 if ( pInteraction->sPhases[iPhase].iszSequence != NULL_STRING )
12784 return STRING(pInteraction->sPhases[iPhase].iszSequence);
12785
12786 return NULL;
12787}
12788
12789//-----------------------------------------------------------------------------
12790// Purpose:
12791//-----------------------------------------------------------------------------
12792void CAI_BaseNPC::StartRunningInteraction( CAI_BaseNPC *pOtherNPC, bool bActive )
12793{
12794 m_hInteractionPartner = pOtherNPC;
12795 if ( bActive )
12796 {
12797 m_iInteractionState = NPCINT_RUNNING_ACTIVE;
12798 }
12799 else
12800 {
12801 m_iInteractionState = NPCINT_RUNNING_PARTNER;
12802 }
12803 m_bCannotDieDuringInteraction = true;
12804
12805 // Force the NPC into an idle schedule so they don't move.
12806 // NOTE: We must set SCHED_IDLE_STAND directly, to prevent derived NPC
12807 // classes from translating the idle stand schedule away to do something bad.
12808 SetSchedule( GetSchedule(SCHED_IDLE_STAND) );
12809
12810 // Prepare the NPC for the script. Setting this allows the scripted sequences
12811 // that we're about to create to immediately grab & use this NPC right away.
12812 // This prevents the NPC from being able to make any schedule decisions
12813 // before the DSS gets underway.
12814 m_scriptState = SCRIPT_PLAYING;
12815}
12816
12817//-----------------------------------------------------------------------------
12818// Purpose:
12819//-----------------------------------------------------------------------------
12820void CAI_BaseNPC::StartScriptedNPCInteraction( CAI_BaseNPC *pOtherNPC, ScriptedNPCInteraction_t *pInteraction, Vector vecOtherOrigin, QAngle angOtherAngles )
12821{
12822 variant_t emptyVariant;
12823
12824 StartRunningInteraction( pOtherNPC, true );
12825 if ( pOtherNPC )
12826 {
12827 pOtherNPC->StartRunningInteraction( this, false );
12828
12829 //Msg("%s(%s) disabled collisions with %s(%s) at %0.2f\n", GetClassName(), GetDebugName(), pOtherNPC->GetClassName(), pOtherNPC->GetDebugName(), gpGlobals->curtime );
12830 PhysDisableEntityCollisions( this, pOtherNPC );
12831 }
12832
12833 // Determine which sequences we're going to use
12834 const char *pszEntrySequence = GetScriptedNPCInteractionSequence( pInteraction, SNPCINT_ENTRY );
12835 const char *pszSequence = GetScriptedNPCInteractionSequence( pInteraction, SNPCINT_SEQUENCE );
12836 const char *pszExitSequence = GetScriptedNPCInteractionSequence( pInteraction, SNPCINT_EXIT );
12837
12838 // Debug
12839 if ( ai_debug_dyninteractions.GetBool() )
12840 {
12841 if ( pOtherNPC )
12842 {
12843 Msg("%s(%s) starting dynamic interaction \"%s\" with %s(%s).\n", GetClassName(), GetDebugName(), STRING(pInteraction->iszInteractionName), pOtherNPC->GetClassName(), pOtherNPC->GetDebugName() );
12844 if ( pszEntrySequence )
12845 {
12846 Msg( " - Entry sequence: %s\n", pszEntrySequence );
12847 }
12848 Msg( " - Core sequence: %s\n", pszSequence );
12849 if ( pszExitSequence )
12850 {
12851 Msg( " - Exit sequence: %s\n", pszExitSequence );
12852 }
12853 }
12854 }
12855
12856 // Create a scripted sequence name that's guaranteed to be unique
12857 char szSSName[256];
12858 if ( pOtherNPC )
12859 {
12860 Q_snprintf( szSSName, sizeof(szSSName), "dss_%s%d%s%d", GetDebugName(), entindex(), pOtherNPC->GetDebugName(), pOtherNPC->entindex() );
12861 }
12862 else
12863 {
12864 Q_snprintf( szSSName, sizeof(szSSName), "dss_%s%d", GetDebugName(), entindex() );
12865 }
12866 string_t iszSSName = AllocPooledString(szSSName);
12867
12868 // Setup next attempt
12869 pInteraction->flNextAttemptTime = gpGlobals->curtime + pInteraction->flDelay + RandomFloat(-2,2);
12870
12871 // Spawn a scripted sequence for this NPC to play the interaction anim
12872 CAI_ScriptedSequence *pMySequence = (CAI_ScriptedSequence*)CreateEntityByName( "scripted_sequence" );
12873 pMySequence->KeyValue( "m_iszEntry", pszEntrySequence );
12874 pMySequence->KeyValue( "m_iszPlay", pszSequence );
12875 pMySequence->KeyValue( "m_iszPostIdle", pszExitSequence );
12876 pMySequence->KeyValue( "m_fMoveTo", "5" );
12877 pMySequence->SetAbsOrigin( GetAbsOrigin() );
12878
12879 QAngle angDesired = GetAbsAngles();
12880 angDesired[YAW] = m_flInteractionYaw;
12881
12882 pMySequence->SetAbsAngles( angDesired );
12883 pMySequence->ForceSetTargetEntity( this, true );
12884 pMySequence->SetName( iszSSName );
12885 pMySequence->AddSpawnFlags( SF_SCRIPT_NOINTERRUPT | SF_SCRIPT_HIGH_PRIORITY | SF_SCRIPT_OVERRIDESTATE );
12886 pMySequence->SetLoopActionSequence( (pInteraction->iFlags & SCNPC_FLAG_LOOP_IN_ACTION) != 0 );
12887 pMySequence->SetSynchPostIdles( true );
12888 if ( ai_debug_dyninteractions.GetBool() )
12889 {
12890 pMySequence->m_debugOverlays |= OVERLAY_TEXT_BIT | OVERLAY_PIVOT_BIT;
12891 }
12892
12893 // Spawn the matching scripted sequence for the other NPC
12894 CAI_ScriptedSequence *pTheirSequence = NULL;
12895 if ( pOtherNPC )
12896 {
12897 pTheirSequence = (CAI_ScriptedSequence*)CreateEntityByName( "scripted_sequence" );
12898 pTheirSequence->KeyValue( "m_iszEntry", pszEntrySequence );
12899 pTheirSequence->KeyValue( "m_iszPlay", pszSequence );
12900 pTheirSequence->KeyValue( "m_iszPostIdle", pszExitSequence );
12901 pTheirSequence->KeyValue( "m_fMoveTo", "5" );
12902 pTheirSequence->SetAbsOrigin( vecOtherOrigin );
12903 pTheirSequence->SetAbsAngles( angOtherAngles );
12904 pTheirSequence->ForceSetTargetEntity( pOtherNPC, true );
12905 pTheirSequence->SetName( iszSSName );
12906 pTheirSequence->AddSpawnFlags( SF_SCRIPT_NOINTERRUPT | SF_SCRIPT_HIGH_PRIORITY | SF_SCRIPT_OVERRIDESTATE );
12907 pTheirSequence->SetLoopActionSequence( (pInteraction->iFlags & SCNPC_FLAG_LOOP_IN_ACTION) != 0 );
12908 pTheirSequence->SetSynchPostIdles( true );
12909 if ( ai_debug_dyninteractions.GetBool() )
12910 {
12911 pTheirSequence->m_debugOverlays |= OVERLAY_TEXT_BIT | OVERLAY_PIVOT_BIT;
12912 }
12913
12914 // Tell their sequence to keep their position relative to me
12915 pTheirSequence->SetupInteractionPosition( this, pInteraction->matDesiredLocalToWorld );
12916 }
12917
12918 // Spawn both sequences at once
12919 pMySequence->Spawn();
12920 if ( pTheirSequence )
12921 {
12922 pTheirSequence->Spawn();
12923 }
12924
12925 // Call activate on both sequences at once
12926 pMySequence->Activate();
12927 if ( pTheirSequence )
12928 {
12929 pTheirSequence->Activate();
12930 }
12931
12932 // Setup the outputs for both sequences. The first kills them both when it finishes
12933 pMySequence->KeyValue( "OnCancelFailedSequence", UTIL_VarArgs("%s,Kill,,0,-1", szSSName ) );
12934 if ( pszExitSequence )
12935 {
12936 pMySequence->KeyValue( "OnPostIdleEndSequence", UTIL_VarArgs("%s,Kill,,0,-1", szSSName ) );
12937 if ( pTheirSequence )
12938 {
12939 pTheirSequence->KeyValue( "OnPostIdleEndSequence", UTIL_VarArgs("%s,Kill,,0,-1", szSSName ) );
12940 }
12941 }
12942 else
12943 {
12944 pMySequence->KeyValue( "OnEndSequence", UTIL_VarArgs("%s,Kill,,0,-1", szSSName ) );
12945 if ( pTheirSequence )
12946 {
12947 pTheirSequence->KeyValue( "OnEndSequence", UTIL_VarArgs("%s,Kill,,0,-1", szSSName ) );
12948 }
12949 }
12950 if ( pTheirSequence )
12951 {
12952 pTheirSequence->KeyValue( "OnCancelFailedSequence", UTIL_VarArgs("%s,Kill,,0,-1", szSSName ) );
12953 }
12954
12955 // Tell both sequences to start
12956 pMySequence->AcceptInput( "BeginSequence", this, this, emptyVariant, 0 );
12957 if ( pTheirSequence )
12958 {
12959 pTheirSequence->AcceptInput( "BeginSequence", this, this, emptyVariant, 0 );
12960 }
12961}
12962
12963//-----------------------------------------------------------------------------
12964// Purpose:
12965//-----------------------------------------------------------------------------
12966bool CAI_BaseNPC::CanRunAScriptedNPCInteraction( bool bForced )
12967{
12968 if ( m_NPCState != NPC_STATE_IDLE && m_NPCState != NPC_STATE_ALERT && m_NPCState != NPC_STATE_COMBAT )
12969 return false;
12970
12971 if ( !IsAlive() )
12972 return false;
12973
12974 if ( IsOnFire() )
12975 return false;
12976
12977 if ( IsCrouching() )
12978 return false;
12979
12980 // Not while running scripted sequences
12981 if ( m_hCine )
12982 return false;
12983
12984 if ( bForced )
12985 {
12986 if ( !m_hForcedInteractionPartner )
12987 return false;
12988 }
12989 else
12990 {
12991 if ( m_hForcedInteractionPartner || m_hInteractionPartner )
12992 return false;
12993 if ( IsInAScript() || !HasCondition(COND_IN_PVS) )
12994 return false;
12995 if ( HasCondition(COND_HEAR_DANGER) || HasCondition(COND_HEAR_MOVE_AWAY) )
12996 return false;
12997
12998 // Default AI prevents interactions while melee attacking, but not ranged attacking
12999 if ( IsCurSchedule( SCHED_MELEE_ATTACK1 ) || IsCurSchedule( SCHED_MELEE_ATTACK2 ) )
13000 return false;
13001 }
13002
13003 return true;
13004}
13005
13006//-----------------------------------------------------------------------------
13007// Purpose:
13008//-----------------------------------------------------------------------------
13009void CAI_BaseNPC::CheckForScriptedNPCInteractions( void )
13010{
13011 // Are we being forced to interact with another NPC? If so, do that
13012 if ( m_hForcedInteractionPartner )
13013 {
13014 CheckForcedNPCInteractions();
13015 return;
13016 }
13017
13018 // Otherwise, see if we can interaction with our enemy
13019 if ( !m_ScriptedInteractions.Count() || !GetEnemy() )
13020 return;
13021
13022 CAI_BaseNPC *pNPC = GetEnemy()->MyNPCPointer();
13023
13024 if( !pNPC )
13025 return;
13026
13027 // Recalculate interaction capability whenever we switch enemies
13028 if ( m_hLastInteractionTestTarget != GetEnemy() )
13029 {
13030 m_hLastInteractionTestTarget = GetEnemy();
13031
13032 CalculateValidEnemyInteractions();
13033 }
13034
13035 // First, make sure I'm in a state where I can do this
13036 if ( !CanRunAScriptedNPCInteraction() )
13037 return;
13038 if ( pNPC && !pNPC->CanRunAScriptedNPCInteraction() )
13039 return;
13040
13041 for ( int i = 0; i < m_ScriptedInteractions.Count(); i++ )
13042 {
13043 ScriptedNPCInteraction_t *pInteraction = &m_ScriptedInteractions[i];
13044
13045 if ( !pInteraction->bValidOnCurrentEnemy )
13046 continue;
13047 if ( pInteraction->flNextAttemptTime > gpGlobals->curtime )
13048 continue;
13049
13050 Vector vecOrigin;
13051 QAngle angAngles;
13052 if ( !InteractionCouldStart( pNPC, pInteraction, vecOrigin, angAngles ) )
13053 continue;
13054
13055 m_iInteractionPlaying = i;
13056 StartScriptedNPCInteraction( pNPC, pInteraction, vecOrigin, angAngles );
13057 }
13058}
13059
13060//-----------------------------------------------------------------------------
13061// Purpose: Calculate all the valid dynamic interactions we can perform with our current enemy
13062//-----------------------------------------------------------------------------
13063void CAI_BaseNPC::CalculateValidEnemyInteractions( void )
13064{
13065 CAI_BaseNPC *pNPC = GetEnemy()->MyNPCPointer();
13066 if ( !pNPC )
13067 return;
13068
13069 bool bDebug = (m_debugOverlays & OVERLAY_NPC_SELECTED_BIT && ai_debug_dyninteractions.GetBool());
13070 if ( bDebug )
13071 {
13072 Msg("%s(%s): Computing valid interactions with %s(%s)\n", GetClassName(), GetDebugName(), pNPC->GetClassName(), pNPC->GetDebugName() );
13073 }
13074
13075 bool bFound = false;
13076 for ( int i = 0; i < m_ScriptedInteractions.Count(); i++ )
13077 {
13078 ScriptedNPCInteraction_t *pInteraction = &m_ScriptedInteractions[i];
13079 pInteraction->bValidOnCurrentEnemy = false;
13080
13081 // If the trigger method of the interaction isn't the one we're after, we're done
13082 if ( pInteraction->iTriggerMethod != SNPCINT_AUTOMATIC_IN_COMBAT )
13083 continue;
13084
13085 if ( !pNPC->GetModelPtr() )
13086 continue;
13087
13088 // If we have a damage filter that prevents us hurting the enemy,
13089 // don't interact with him, since most interactions kill the enemy.
13090 // Create a fake damage info to test it with.
13091 CTakeDamageInfo tempinfo( this, this, vec3_origin, vec3_origin, 1.0, DMG_BULLET );
13092 if ( !pNPC->PassesDamageFilter( tempinfo ) )
13093 continue;
13094
13095 // Check the weapon requirements for the interaction
13096 if ( pInteraction->iFlags & SCNPC_FLAG_NEEDS_WEAPON_ME )
13097 {
13098 if ( !GetActiveWeapon())
13099 continue;
13100
13101 // Check the specific weapon type
13102 if ( pInteraction->iszMyWeapon != NULL_STRING && GetActiveWeapon()->m_iClassname != pInteraction->iszMyWeapon )
13103 continue;
13104 }
13105 if ( pInteraction->iFlags & SCNPC_FLAG_NEEDS_WEAPON_THEM )
13106 {
13107 if ( !pNPC->GetActiveWeapon() )
13108 continue;
13109
13110 // Check the specific weapon type
13111 if ( pInteraction->iszTheirWeapon != NULL_STRING && pNPC->GetActiveWeapon()->m_iClassname != pInteraction->iszTheirWeapon )
13112 continue;
13113 }
13114
13115 // Script needs the other NPC, so make sure they're not dead
13116 if ( !pNPC->IsAlive() )
13117 continue;
13118
13119 // Use sequence? or activity?
13120 if ( pInteraction->sPhases[SNPCINT_SEQUENCE].iActivity != ACT_INVALID )
13121 {
13122 // Resolve the activity to a sequence, and make sure our enemy has it
13123 const char *pszSequence = GetScriptedNPCInteractionSequence( pInteraction, SNPCINT_SEQUENCE );
13124 if ( !pszSequence )
13125 continue;
13126 if ( pNPC->LookupSequence( pszSequence ) == -1 )
13127 continue;
13128 }
13129 else
13130 {
13131 if ( pNPC->LookupSequence( STRING(pInteraction->sPhases[SNPCINT_SEQUENCE].iszSequence) ) == -1 )
13132 continue;
13133 }
13134
13135 pInteraction->bValidOnCurrentEnemy = true;
13136 bFound = true;
13137
13138 if ( bDebug )
13139 {
13140 Msg(" Found: %s\n", STRING(pInteraction->iszInteractionName) );
13141 }
13142 }
13143
13144 if ( bDebug && !bFound )
13145 {
13146 Msg(" No valid interactions found.\n");
13147 }
13148}
13149
13150//-----------------------------------------------------------------------------
13151// Purpose:
13152//-----------------------------------------------------------------------------
13153void CAI_BaseNPC::CheckForcedNPCInteractions( void )
13154{
13155 // If we don't have an interaction, we're waiting for our partner to start it. Do nothing.
13156 if ( m_iInteractionPlaying == NPCINT_NONE )
13157 return;
13158
13159 CAI_BaseNPC *pNPC = m_hForcedInteractionPartner->MyNPCPointer();
13160
13161 // First, make sure both NPCs are able to do this
13162 if ( !CanRunAScriptedNPCInteraction( true ) || !pNPC->CanRunAScriptedNPCInteraction( true ) )
13163 {
13164 // If we were still moving to our target, abort.
13165 if ( m_iInteractionState == NPCINT_MOVING_TO_MARK )
13166 {
13167 if ( m_hForcedInteractionPartner )
13168 {
13169 // We've aborted a forced interaction. Let the mapmaker know.
13170 m_OnForcedInteractionAborted.FireOutput( this, this );
13171 }
13172
13173 CleanupForcedInteraction();
13174 pNPC->CleanupForcedInteraction();
13175 }
13176 return;
13177 }
13178
13179 // Check to see if we can start our interaction. If we can, dance.
13180 ScriptedNPCInteraction_t *pInteraction = &m_ScriptedInteractions[m_iInteractionPlaying];
13181 Vector vecOrigin;
13182 QAngle angAngles;
13183 if ( !InteractionCouldStart( pNPC, pInteraction, vecOrigin, angAngles ) )
13184 return;;
13185
13186 StartScriptedNPCInteraction( pNPC, pInteraction, vecOrigin, angAngles );
13187}
13188
13189//-----------------------------------------------------------------------------
13190// Purpose:
13191//-----------------------------------------------------------------------------
13192bool CAI_BaseNPC::InteractionCouldStart( CAI_BaseNPC *pOtherNPC, ScriptedNPCInteraction_t *pInteraction, Vector &vecOrigin, QAngle &angAngles )
13193{
13194 // Get a matrix that'll convert from my local interaction space to world space
13195 VMatrix matMeToWorld, matLocalToWorld;
13196 QAngle angMyCurrent = GetAbsAngles();
13197 angMyCurrent[YAW] = m_flInteractionYaw;
13198 matMeToWorld.SetupMatrixOrgAngles( GetAbsOrigin(), angMyCurrent );
13199 MatrixMultiply( matMeToWorld, pInteraction->matDesiredLocalToWorld, matLocalToWorld );
13200
13201 // Get the desired NPC position in worldspace
13202 vecOrigin = matLocalToWorld.GetTranslation();
13203 MatrixToAngles( matLocalToWorld, angAngles );
13204
13205 bool bDebug = ai_debug_dyninteractions.GetBool();
13206 if ( bDebug )
13207 {
13208 NDebugOverlay::Axis( vecOrigin, angAngles, 20, true, 0.1 );
13209 }
13210
13211 // Determine whether or not the enemy is on the target
13212 float flDistSqr = (vecOrigin - pOtherNPC->GetAbsOrigin()).LengthSqr();
13213 if ( flDistSqr > pInteraction->flDistSqr )
13214 {
13215 if ( bDebug )
13216 {
13217 if ( m_debugOverlays & OVERLAY_NPC_SELECTED_BIT || pOtherNPC->m_debugOverlays & OVERLAY_NPC_SELECTED_BIT )
13218 {
13219 if ( ai_debug_dyninteractions.GetFloat() == 2 )
13220 {
13221 Msg(" %s distsqr: %0.2f (%0.2f %0.2f %0.2f), desired: (%0.2f %0.2f %0.2f)\n", GetDebugName(), flDistSqr,
13222 pOtherNPC->GetAbsOrigin().x, pOtherNPC->GetAbsOrigin().y, pOtherNPC->GetAbsOrigin().z, vecOrigin.x, vecOrigin.y, vecOrigin.z );
13223 }
13224 }
13225 }
13226 return false;
13227 }
13228
13229 if ( bDebug )
13230 {
13231 Msg("DYNINT: (%s) testing interaction \"%s\"\n", GetDebugName(), STRING(pInteraction->iszInteractionName) );
13232 Msg(" %s is at: %0.2f %0.2f %0.2f\n", GetDebugName(), GetAbsOrigin().x, GetAbsOrigin().y, GetAbsOrigin().z );
13233 Msg(" %s distsqr: %0.2f (%0.2f %0.2f %0.2f), desired: (%0.2f %0.2f %0.2f)\n", GetDebugName(), flDistSqr,
13234 pOtherNPC->GetAbsOrigin().x, pOtherNPC->GetAbsOrigin().y, pOtherNPC->GetAbsOrigin().z, vecOrigin.x, vecOrigin.y, vecOrigin.z );
13235
13236 if ( pOtherNPC )
13237 {
13238 float flOtherSpeed = pOtherNPC->GetSequenceGroundSpeed( pOtherNPC->GetSequence() );
13239 Msg(" %s Speed: %.2f\n", pOtherNPC->GetSequenceName( pOtherNPC->GetSequence() ), flOtherSpeed);
13240 }
13241 }
13242
13243 // Angle check, if we're supposed to
13244 if ( pInteraction->iFlags & SCNPC_FLAG_TEST_OTHER_ANGLES )
13245 {
13246 QAngle angEnemyAngles = pOtherNPC->GetAbsAngles();
13247 bool bMatches = true;
13248 for ( int ang = 0; ang < 3; ang++ )
13249 {
13250 float flAngDiff = AngleDiff( angEnemyAngles[ang], angAngles[ang] );
13251 if ( fabs(flAngDiff) > DSS_MAX_ANGLE_DIFF )
13252 {
13253 bMatches = false;
13254 break;
13255 }
13256 }
13257 if ( !bMatches )
13258 return false;
13259
13260 if ( bDebug )
13261 {
13262 Msg(" %s angle matched: (%0.2f %0.2f %0.2f), desired (%0.2f, %0.2f, %0.2f)\n", GetDebugName(),
13263 anglemod(angEnemyAngles.x), anglemod(angEnemyAngles.y), anglemod(angEnemyAngles.z), anglemod(angAngles.x), anglemod(angAngles.y), anglemod(angAngles.z) );
13264 }
13265 }
13266
13267 // TODO: Velocity check, if we're supposed to
13268 if ( pInteraction->iFlags & SCNPC_FLAG_TEST_OTHER_VELOCITY )
13269 {
13270
13271 }
13272
13273 // Valid so far. Now check to make sure there's nothing in the way.
13274 // This isn't a very good method of checking, but it's cheap and rules out the problems we're seeing so far.
13275 // If we start getting interactions that start a fair distance apart, we're going to need to do more work here.
13276 trace_t tr;
13277 AI_TraceLine( EyePosition(), pOtherNPC->EyePosition(), MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr);
13278 if ( tr.fraction != 1.0 && tr.m_pEnt != pOtherNPC )
13279 {
13280 if ( bDebug )
13281 {
13282 Msg( " %s Interaction was blocked.\n", GetDebugName() );
13283 NDebugOverlay::Line( tr.startpos, tr.endpos, 0,255,0, true, 1.0 );
13284 NDebugOverlay::Line( pOtherNPC->EyePosition(), tr.endpos, 255,0,0, true, 1.0 );
13285 }
13286 return false;
13287 }
13288
13289 if ( bDebug )
13290 {
13291 NDebugOverlay::Line( tr.startpos, tr.endpos, 0,255,0, true, 1.0 );
13292 }
13293
13294 // Do a knee-level trace to find low physics objects
13295 Vector vecMyKnee, vecOtherKnee;
13296 CollisionProp()->NormalizedToWorldSpace( Vector(0,0,0.25f), &vecMyKnee );
13297 pOtherNPC->CollisionProp()->NormalizedToWorldSpace( Vector(0,0,0.25f), &vecOtherKnee );
13298 AI_TraceLine( vecMyKnee, vecOtherKnee, MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr);
13299 if ( tr.fraction != 1.0 && tr.m_pEnt != pOtherNPC )
13300 {
13301 if ( bDebug )
13302 {
13303 Msg( " %s Interaction was blocked.\n", GetDebugName() );
13304 NDebugOverlay::Line( tr.startpos, tr.endpos, 0,255,0, true, 1.0 );
13305 NDebugOverlay::Line( vecOtherKnee, tr.endpos, 255,0,0, true, 1.0 );
13306 }
13307 return false;
13308 }
13309
13310 if ( bDebug )
13311 {
13312 NDebugOverlay::Line( tr.startpos, tr.endpos, 0,255,0, true, 1.0 );
13313 }
13314
13315 // Finally, make sure the NPC can actually fit at the interaction position
13316 // This solves problems with NPCs who are a few units or so above the
13317 // interaction point, and would sink into the ground when playing the anim.
13318 CTraceFilterSkipTwoEntities traceFilter( pOtherNPC, this, COLLISION_GROUP_NONE );
13319 AI_TraceHull( vecOrigin, vecOrigin, pOtherNPC->GetHullMins(), pOtherNPC->GetHullMaxs(), MASK_SOLID, &traceFilter, &tr );
13320 if ( tr.startsolid )
13321 {
13322 if ( bDebug )
13323 {
13324 NDebugOverlay::Box( vecOrigin, pOtherNPC->GetHullMins(), pOtherNPC->GetHullMaxs(), 255,0,0, true, 1.0 );
13325 }
13326 return false;
13327 }
13328
13329 return true;
13330}
13331
13332//-----------------------------------------------------------------------------
13333// Purpose: Return true if this NPC cannot die because it's in an interaction
13334// and the flag has been set by the animation.
13335//-----------------------------------------------------------------------------
13336bool CAI_BaseNPC::HasInteractionCantDie( void )
13337{
13338 return ( m_bCannotDieDuringInteraction && IsRunningDynamicInteraction() );
13339}
13340
13341//-----------------------------------------------------------------------------
13342// Purpose:
13343// Input : &inputdata -
13344//-----------------------------------------------------------------------------
13345void CAI_BaseNPC::InputForceInteractionWithNPC( inputdata_t &inputdata )
13346{
13347 // Get the interaction name & target
13348 char parseString[255];
13349 Q_strncpy(parseString, inputdata.value.String(), sizeof(parseString));
13350
13351 // First, the target's name
13352 char *pszParam = strtok(parseString," ");
13353 if ( !pszParam || !pszParam[0] )
13354 {
13355 Warning("%s(%s) received ForceInteractionWithNPC input with bad parameters: %s\nFormat should be: ForceInteractionWithNPC <target NPC> <interaction name>\n", GetClassname(), GetDebugName(), inputdata.value.String() );
13356 return;
13357 }
13358 // Find the target
13359 CBaseEntity *pTarget = FindNamedEntity( pszParam );
13360 if ( !pTarget )
13361 {
13362 Warning("%s(%s) received ForceInteractionWithNPC input, but couldn't find entity named: %s\n", GetClassname(), GetDebugName(), pszParam );
13363 return;
13364 }
13365 CAI_BaseNPC *pNPC = pTarget->MyNPCPointer();
13366 if ( !pNPC || !pNPC->GetModelPtr() )
13367 {
13368 Warning("%s(%s) received ForceInteractionWithNPC input, but entity named %s cannot run dynamic interactions.\n", GetClassname(), GetDebugName(), pszParam );
13369 return;
13370 }
13371
13372 // Second, the interaction name
13373 pszParam = strtok(NULL," ");
13374 if ( !pszParam || !pszParam[0] )
13375 {
13376 Warning("%s(%s) received ForceInteractionWithNPC input with bad parameters: %s\nFormat should be: ForceInteractionWithNPC <target NPC> <interaction name>\n", GetClassname(), GetDebugName(), inputdata.value.String() );
13377 return;
13378 }
13379
13380 // Find the interaction from the name, and ensure it's one that the target NPC can play
13381 int iInteraction = -1;
13382 for ( int i = 0; i < m_ScriptedInteractions.Count(); i++ )
13383 {
13384 if ( Q_strncmp( pszParam, STRING(m_ScriptedInteractions[i].iszInteractionName), strlen(pszParam) ) )
13385 continue;
13386
13387 // Use sequence? or activity?
13388 if ( m_ScriptedInteractions[i].sPhases[SNPCINT_SEQUENCE].iActivity != ACT_INVALID )
13389 {
13390 if ( !pNPC->HaveSequenceForActivity( (Activity)m_ScriptedInteractions[i].sPhases[SNPCINT_SEQUENCE].iActivity ) )
13391 {
13392 // Other NPC may have all the matching sequences, but just without the activity specified.
13393 // Lets find a single sequence for us, and ensure they have a matching one.
13394 int iMySeq = SelectWeightedSequence( (Activity)m_ScriptedInteractions[i].sPhases[SNPCINT_SEQUENCE].iActivity );
13395 if ( pNPC->LookupSequence( GetSequenceName(iMySeq) ) == -1 )
13396 continue;
13397 }
13398 }
13399 else
13400 {
13401 if ( pNPC->LookupSequence( STRING(m_ScriptedInteractions[i].sPhases[SNPCINT_SEQUENCE].iszSequence) ) == -1 )
13402 continue;
13403 }
13404
13405 iInteraction = i;
13406 break;
13407 }
13408
13409 if ( iInteraction == -1 )
13410 {
13411 Warning("%s(%s) received ForceInteractionWithNPC input, but couldn't find an interaction named %s that entity named %s could run.\n", GetClassname(), GetDebugName(), pszParam, pNPC->GetDebugName() );
13412 return;
13413 }
13414
13415 // Found both pieces of data, lets dance.
13416 StartForcedInteraction( pNPC, iInteraction );
13417 pNPC->StartForcedInteraction( this, NPCINT_NONE );
13418}
13419
13420//-----------------------------------------------------------------------------
13421// Purpose:
13422//-----------------------------------------------------------------------------
13423void CAI_BaseNPC::StartForcedInteraction( CAI_BaseNPC *pNPC, int iInteraction )
13424{
13425 m_hForcedInteractionPartner = pNPC;
13426 ClearSchedule();
13427
13428 m_iInteractionPlaying = iInteraction;
13429 m_iInteractionState = NPCINT_MOVING_TO_MARK;
13430}
13431
13432//-----------------------------------------------------------------------------
13433// Purpose:
13434//-----------------------------------------------------------------------------
13435void CAI_BaseNPC::CleanupForcedInteraction( void )
13436{
13437 m_hForcedInteractionPartner = NULL;
13438 m_iInteractionPlaying = NPCINT_NONE;
13439 m_iInteractionState = NPCINT_NOT_RUNNING;
13440}
13441
13442//-----------------------------------------------------------------------------
13443// Purpose: Calculate a position to move to so that I can interact with my
13444// target NPC.
13445//
13446// FIXME: THIS ONLY WORKS FOR INTERACTIONS THAT REQUIRE THE TARGET
13447// NPC TO BE SOME DISTANCE DIRECTLY IN FRONT OF ME.
13448//-----------------------------------------------------------------------------
13449void CAI_BaseNPC::CalculateForcedInteractionPosition( void )
13450{
13451 if ( m_iInteractionPlaying == NPCINT_NONE )
13452 return;
13453
13454 ScriptedNPCInteraction_t *pInteraction = GetRunningDynamicInteraction();
13455
13456 // Pretend I was facing the target, and extrapolate from that the position I should be at
13457 Vector vecToTarget = m_hForcedInteractionPartner->GetAbsOrigin() - GetAbsOrigin();
13458 VectorNormalize( vecToTarget );
13459 QAngle angToTarget;
13460 VectorAngles( vecToTarget, angToTarget );
13461
13462 // Get the desired position in worldspace, relative to the target
13463 VMatrix matMeToWorld, matLocalToWorld;
13464 matMeToWorld.SetupMatrixOrgAngles( GetAbsOrigin(), angToTarget );
13465 MatrixMultiply( matMeToWorld, pInteraction->matDesiredLocalToWorld, matLocalToWorld );
13466
13467 Vector vecOrigin = GetAbsOrigin() - matLocalToWorld.GetTranslation();
13468 m_vecForcedWorldPosition = m_hForcedInteractionPartner->GetAbsOrigin() + vecOrigin;
13469
13470 //NDebugOverlay::Axis( m_vecForcedWorldPosition, angToTarget, 20, true, 3.0 );
13471}
13472
13473//-----------------------------------------------------------------------------
13474// Purpose:
13475// Input : *pPlayer -
13476//-----------------------------------------------------------------------------
13477void CAI_BaseNPC::PlayerHasIlluminatedNPC( CBasePlayer *pPlayer, float flDot )
13478{
13479#ifdef HL2_DLL
13480 if ( IsActiveDynamicInteraction() )
13481 {
13482 ScriptedNPCInteraction_t *pInteraction = GetRunningDynamicInteraction();
13483 if ( pInteraction->iLoopBreakTriggerMethod & SNPCINT_LOOPBREAK_ON_FLASHLIGHT_ILLUM )
13484 {
13485 // Only do this in alyx darkness mode
13486 if ( HL2GameRules()->IsAlyxInDarknessMode() )
13487 {
13488 // Can only break when we're in the action anim
13489 if ( m_hCine->IsPlayingAction() )
13490 {
13491 m_hCine->StopActionLoop( true );
13492 }
13493 }
13494 }
13495 }
13496#endif
13497}
13498
13499//-----------------------------------------------------------------------------
13500// Purpose:
13501//-----------------------------------------------------------------------------
13502void CAI_BaseNPC::ModifyOrAppendCriteria( AI_CriteriaSet& set )
13503{
13504 BaseClass::ModifyOrAppendCriteria( set );
13505
13506 // Append time since seen player
13507 if ( m_flLastSawPlayerTime )
13508 {
13509 set.AppendCriteria( "timesinceseenplayer", UTIL_VarArgs( "%f", gpGlobals->curtime - m_flLastSawPlayerTime ) );
13510 }
13511 else
13512 {
13513 set.AppendCriteria( "timesinceseenplayer", "-1" );
13514 }
13515
13516 // Append distance to my enemy
13517 if ( GetEnemy() )
13518 {
13519 set.AppendCriteria( "distancetoenemy", UTIL_VarArgs( "%f", EnemyDistance(GetEnemy()) ) );
13520 }
13521 else
13522 {
13523 set.AppendCriteria( "distancetoenemy", "-1" );
13524 }
13525}
13526
13527//-----------------------------------------------------------------------------
13528// If I were crouching at my current location, could I shoot this target?
13529//-----------------------------------------------------------------------------
13530bool CAI_BaseNPC::CouldShootIfCrouching( CBaseEntity *pTarget )
13531{
13532 bool bWasStanding = !IsCrouching();
13533 Crouch();
13534
13535 Vector vecTarget;
13536 if (GetActiveWeapon())
13537 {
13538 vecTarget = pTarget->BodyTarget( GetActiveWeapon()->GetLocalOrigin() );
13539 }
13540 else
13541 {
13542 vecTarget = pTarget->BodyTarget( GetLocalOrigin() );
13543 }
13544
13545 bool bResult = WeaponLOSCondition( GetLocalOrigin(), vecTarget, false );
13546
13547 if ( bWasStanding )
13548 {
13549 Stand();
13550 }
13551
13552 return bResult;
13553}
13554
13555//-----------------------------------------------------------------------------
13556// Purpose:
13557//-----------------------------------------------------------------------------
13558bool CAI_BaseNPC::IsCrouchedActivity( Activity activity )
13559{
13560 Activity realActivity = TranslateActivity(activity);
13561
13562 switch ( realActivity )
13563 {
13564 case ACT_RELOAD_LOW:
13565 case ACT_COVER_LOW:
13566 case ACT_COVER_PISTOL_LOW:
13567 case ACT_COVER_SMG1_LOW:
13568 case ACT_RELOAD_SMG1_LOW:
13569 return true;
13570 }
13571
13572 return false;
13573}
13574
13575//-----------------------------------------------------------------------------
13576// Purpose: Get shoot position of BCC at an arbitrary position
13577//-----------------------------------------------------------------------------
13578Vector CAI_BaseNPC::Weapon_ShootPosition( void )
13579{
13580 Vector right;
13581 GetVectors( NULL, &right, NULL );
13582
13583 bool bStanding = !IsCrouching();
13584 if ( bStanding && (CapabilitiesGet() & bits_CAP_DUCK) )
13585 {
13586 if ( IsCrouchedActivity( GetActivity() ) )
13587 {
13588 bStanding = false;
13589 }
13590 }
13591
13592 if ( !bStanding )
13593 return (GetAbsOrigin() + GetCrouchGunOffset() + right * 8);
13594
13595 return BaseClass::Weapon_ShootPosition();
13596}
13597
13598//-----------------------------------------------------------------------------
13599// Purpose:
13600//-----------------------------------------------------------------------------
13601bool CAI_BaseNPC::ShouldProbeCollideAgainstEntity( CBaseEntity *pEntity )
13602{
13603 if ( pEntity->GetMoveType() == MOVETYPE_VPHYSICS )
13604 {
13605 if ( ai_test_moveprobe_ignoresmall.GetBool() && IsNavigationUrgent() )
13606 {
13607 IPhysicsObject *pPhysics = pEntity->VPhysicsGetObject();
13608
13609 if ( pPhysics->IsMoveable() && pPhysics->GetMass() < 40.0 )
13610 return false;
13611 }
13612 }
13613
13614 return true;
13615}
13616
13617//-----------------------------------------------------------------------------
13618// Purpose:
13619//-----------------------------------------------------------------------------
13620bool CAI_BaseNPC::Crouch( void )
13621{
13622 m_bIsCrouching = true;
13623 return true;
13624}
13625
13626//-----------------------------------------------------------------------------
13627// Purpose:
13628//-----------------------------------------------------------------------------
13629bool CAI_BaseNPC::IsCrouching( void )
13630{
13631 return ( (CapabilitiesGet() & bits_CAP_DUCK) && m_bIsCrouching );
13632}
13633
13634//-----------------------------------------------------------------------------
13635// Purpose:
13636//-----------------------------------------------------------------------------
13637bool CAI_BaseNPC::Stand( void )
13638{
13639 if ( m_bForceCrouch )
13640 return false;
13641
13642 m_bIsCrouching = false;
13643 DesireStand();
13644 return true;
13645}
13646
13647//-----------------------------------------------------------------------------
13648// Purpose:
13649//-----------------------------------------------------------------------------
13650void CAI_BaseNPC::DesireCrouch( void )
13651{
13652 m_bCrouchDesired = true;
13653}
13654
13655bool CAI_BaseNPC::IsInChoreo() const
13656{
13657 return m_bInChoreo;
13658}