Team Fortress 2 Source Code as on 22/4/2020
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

4263 lines
124 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose: The downtrodden citizens of City 17.
  4. //
  5. //=============================================================================//
  6. #include "cbase.h"
  7. #include "npc_citizen17.h"
  8. #include "ammodef.h"
  9. #include "globalstate.h"
  10. #include "soundent.h"
  11. #include "BasePropDoor.h"
  12. #include "weapon_rpg.h"
  13. #include "hl2_player.h"
  14. #include "items.h"
  15. #ifdef HL2MP
  16. #include "hl2mp/weapon_crowbar.h"
  17. #else
  18. #include "weapon_crowbar.h"
  19. #endif
  20. #include "eventqueue.h"
  21. #include "ai_squad.h"
  22. #include "ai_pathfinder.h"
  23. #include "ai_route.h"
  24. #include "ai_hint.h"
  25. #include "ai_interactions.h"
  26. #include "ai_looktarget.h"
  27. #include "sceneentity.h"
  28. #include "tier0/icommandline.h"
  29. // memdbgon must be the last include file in a .cpp file!!!
  30. #include "tier0/memdbgon.h"
  31. #define INSIGNIA_MODEL "models/chefhat.mdl"
  32. //-----------------------------------------------------------------------------
  33. //-----------------------------------------------------------------------------
  34. #define CIT_INSPECTED_DELAY_TIME 120 //How often I'm allowed to be inspected
  35. extern ConVar sk_healthkit;
  36. extern ConVar sk_healthvial;
  37. const int MAX_PLAYER_SQUAD = 4;
  38. ConVar sk_citizen_health ( "sk_citizen_health", "0");
  39. ConVar sk_citizen_heal_player ( "sk_citizen_heal_player", "25");
  40. ConVar sk_citizen_heal_player_delay ( "sk_citizen_heal_player_delay", "25");
  41. ConVar sk_citizen_giveammo_player_delay( "sk_citizen_giveammo_player_delay", "10");
  42. ConVar sk_citizen_heal_player_min_pct ( "sk_citizen_heal_player_min_pct", "0.60");
  43. ConVar sk_citizen_heal_player_min_forced( "sk_citizen_heal_player_min_forced", "10.0");
  44. ConVar sk_citizen_heal_ally ( "sk_citizen_heal_ally", "30");
  45. ConVar sk_citizen_heal_ally_delay ( "sk_citizen_heal_ally_delay", "20");
  46. ConVar sk_citizen_heal_ally_min_pct ( "sk_citizen_heal_ally_min_pct", "0.90");
  47. ConVar sk_citizen_player_stare_time ( "sk_citizen_player_stare_time", "1.0" );
  48. ConVar sk_citizen_player_stare_dist ( "sk_citizen_player_stare_dist", "72" );
  49. ConVar sk_citizen_stare_heal_time ( "sk_citizen_stare_heal_time", "5" );
  50. ConVar g_ai_citizen_show_enemy( "g_ai_citizen_show_enemy", "0" );
  51. ConVar npc_citizen_insignia( "npc_citizen_insignia", "0" );
  52. ConVar npc_citizen_squad_marker( "npc_citizen_squad_marker", "0" );
  53. ConVar npc_citizen_explosive_resist( "npc_citizen_explosive_resist", "0" );
  54. ConVar npc_citizen_auto_player_squad( "npc_citizen_auto_player_squad", "1" );
  55. ConVar npc_citizen_auto_player_squad_allow_use( "npc_citizen_auto_player_squad_allow_use", "0" );
  56. ConVar npc_citizen_dont_precache_all( "npc_citizen_dont_precache_all", "0" );
  57. ConVar npc_citizen_medic_emit_sound("npc_citizen_medic_emit_sound", "1" );
  58. #ifdef HL2_EPISODIC
  59. // todo: bake these into pound constants (for now they're not just for tuning purposes)
  60. ConVar npc_citizen_heal_chuck_medkit("npc_citizen_heal_chuck_medkit" , "1" , FCVAR_ARCHIVE, "Set to 1 to use new experimental healthkit-throwing medic.");
  61. ConVar npc_citizen_medic_throw_style( "npc_citizen_medic_throw_style", "1", FCVAR_ARCHIVE, "Set to 0 for a lobbier trajectory" );
  62. ConVar npc_citizen_medic_throw_speed( "npc_citizen_medic_throw_speed", "650" );
  63. ConVar sk_citizen_heal_toss_player_delay("sk_citizen_heal_toss_player_delay", "26", FCVAR_NONE, "how long between throwing healthkits" );
  64. #define MEDIC_THROW_SPEED npc_citizen_medic_throw_speed.GetFloat()
  65. #define USE_EXPERIMENTAL_MEDIC_CODE() (npc_citizen_heal_chuck_medkit.GetBool() && NameMatches("griggs"))
  66. #endif
  67. ConVar player_squad_autosummon_time( "player_squad_autosummon_time", "5" );
  68. ConVar player_squad_autosummon_move_tolerance( "player_squad_autosummon_move_tolerance", "20" );
  69. ConVar player_squad_autosummon_player_tolerance( "player_squad_autosummon_player_tolerance", "10" );
  70. ConVar player_squad_autosummon_time_after_combat( "player_squad_autosummon_time_after_combat", "8" );
  71. ConVar player_squad_autosummon_debug( "player_squad_autosummon_debug", "0" );
  72. #define ShouldAutosquad() (npc_citizen_auto_player_squad.GetBool())
  73. enum SquadSlot_T
  74. {
  75. SQUAD_SLOT_CITIZEN_RPG1 = LAST_SHARED_SQUADSLOT,
  76. SQUAD_SLOT_CITIZEN_RPG2,
  77. };
  78. const float HEAL_MOVE_RANGE = 30*12;
  79. const float HEAL_TARGET_RANGE = 120; // 10 feet
  80. #ifdef HL2_EPISODIC
  81. const float HEAL_TOSS_TARGET_RANGE = 480; // 40 feet when we are throwing medkits
  82. const float HEAL_TARGET_RANGE_Z = 72; // a second check that Gordon isn't too far above us -- 6 feet
  83. #endif
  84. // player must be at least this distance away from an enemy before we fire an RPG at him
  85. const float RPG_SAFE_DISTANCE = CMissile::EXPLOSION_RADIUS + 64.0;
  86. // Animation events
  87. int AE_CITIZEN_GET_PACKAGE;
  88. int AE_CITIZEN_HEAL;
  89. //-------------------------------------
  90. //-------------------------------------
  91. ConVar ai_follow_move_commands( "ai_follow_move_commands", "1" );
  92. ConVar ai_citizen_debug_commander( "ai_citizen_debug_commander", "1" );
  93. #define DebuggingCommanderMode() (ai_citizen_debug_commander.GetBool() && (m_debugOverlays & OVERLAY_NPC_SELECTED_BIT))
  94. //-----------------------------------------------------------------------------
  95. // Citizen expressions for the citizen expression types
  96. //-----------------------------------------------------------------------------
  97. #define STATES_WITH_EXPRESSIONS 3 // Idle, Alert, Combat
  98. #define EXPRESSIONS_PER_STATE 1
  99. char *szExpressionTypes[CIT_EXP_LAST_TYPE] =
  100. {
  101. "Unassigned",
  102. "Scared",
  103. "Normal",
  104. "Angry"
  105. };
  106. struct citizen_expression_list_t
  107. {
  108. char *szExpressions[EXPRESSIONS_PER_STATE];
  109. };
  110. // Scared
  111. citizen_expression_list_t ScaredExpressions[STATES_WITH_EXPRESSIONS] =
  112. {
  113. { { "scenes/Expressions/citizen_scared_idle_01.vcd" } },
  114. { { "scenes/Expressions/citizen_scared_alert_01.vcd" } },
  115. { { "scenes/Expressions/citizen_scared_combat_01.vcd" } },
  116. };
  117. // Normal
  118. citizen_expression_list_t NormalExpressions[STATES_WITH_EXPRESSIONS] =
  119. {
  120. { { "scenes/Expressions/citizen_normal_idle_01.vcd" } },
  121. { { "scenes/Expressions/citizen_normal_alert_01.vcd" } },
  122. { { "scenes/Expressions/citizen_normal_combat_01.vcd" } },
  123. };
  124. // Angry
  125. citizen_expression_list_t AngryExpressions[STATES_WITH_EXPRESSIONS] =
  126. {
  127. { { "scenes/Expressions/citizen_angry_idle_01.vcd" } },
  128. { { "scenes/Expressions/citizen_angry_alert_01.vcd" } },
  129. { { "scenes/Expressions/citizen_angry_combat_01.vcd" } },
  130. };
  131. //-----------------------------------------------------------------------------
  132. //-----------------------------------------------------------------------------
  133. #define COMMAND_POINT_CLASSNAME "info_target_command_point"
  134. class CCommandPoint : public CPointEntity
  135. {
  136. DECLARE_CLASS( CCommandPoint, CPointEntity );
  137. public:
  138. CCommandPoint()
  139. : m_bNotInTransition(false)
  140. {
  141. if ( ++gm_nCommandPoints > 1 )
  142. DevMsg( "WARNING: More than one citizen command point present\n" );
  143. }
  144. ~CCommandPoint()
  145. {
  146. --gm_nCommandPoints;
  147. }
  148. int ObjectCaps()
  149. {
  150. int caps = ( BaseClass::ObjectCaps() | FCAP_NOTIFY_ON_TRANSITION );
  151. if ( m_bNotInTransition )
  152. caps |= FCAP_DONT_SAVE;
  153. return caps;
  154. }
  155. void InputOutsideTransition( inputdata_t &inputdata )
  156. {
  157. if ( !AI_IsSinglePlayer() )
  158. return;
  159. m_bNotInTransition = true;
  160. CAI_Squad *pPlayerAISquad = g_AI_SquadManager.FindSquad(AllocPooledString(PLAYER_SQUADNAME));
  161. if ( pPlayerAISquad )
  162. {
  163. AISquadIter_t iter;
  164. for ( CAI_BaseNPC *pAllyNpc = pPlayerAISquad->GetFirstMember(&iter); pAllyNpc; pAllyNpc = pPlayerAISquad->GetNextMember(&iter) )
  165. {
  166. if ( pAllyNpc->GetCommandGoal() != vec3_invalid )
  167. {
  168. bool bHadGag = pAllyNpc->HasSpawnFlags(SF_NPC_GAG);
  169. pAllyNpc->AddSpawnFlags(SF_NPC_GAG);
  170. pAllyNpc->TargetOrder( UTIL_GetLocalPlayer(), &pAllyNpc, 1 );
  171. if ( !bHadGag )
  172. pAllyNpc->RemoveSpawnFlags(SF_NPC_GAG);
  173. }
  174. }
  175. }
  176. }
  177. DECLARE_DATADESC();
  178. private:
  179. bool m_bNotInTransition; // does not need to be saved. If this is ever not default, the object is not being saved.
  180. static int gm_nCommandPoints;
  181. };
  182. int CCommandPoint::gm_nCommandPoints;
  183. LINK_ENTITY_TO_CLASS( info_target_command_point, CCommandPoint );
  184. BEGIN_DATADESC( CCommandPoint )
  185. // DEFINE_FIELD( m_bNotInTransition, FIELD_BOOLEAN ),
  186. DEFINE_INPUTFUNC( FIELD_VOID, "OutsideTransition", InputOutsideTransition ),
  187. END_DATADESC()
  188. //-----------------------------------------------------------------------------
  189. //-----------------------------------------------------------------------------
  190. class CMattsPipe : public CWeaponCrowbar
  191. {
  192. DECLARE_CLASS( CMattsPipe, CWeaponCrowbar );
  193. const char *GetWorldModel() const { return "models/props_canal/mattpipe.mdl"; }
  194. void SetPickupTouch( void ) { /* do nothing */ }
  195. };
  196. //-----------------------------------------------------------------------------
  197. //-----------------------------------------------------------------------------
  198. //---------------------------------------------------------
  199. // Citizen models
  200. //---------------------------------------------------------
  201. static const char *g_ppszRandomHeads[] =
  202. {
  203. "male_01.mdl",
  204. "male_02.mdl",
  205. "female_01.mdl",
  206. "male_03.mdl",
  207. "female_02.mdl",
  208. "male_04.mdl",
  209. "female_03.mdl",
  210. "male_05.mdl",
  211. "female_04.mdl",
  212. "male_06.mdl",
  213. "female_06.mdl",
  214. "male_07.mdl",
  215. "female_07.mdl",
  216. "male_08.mdl",
  217. "male_09.mdl",
  218. };
  219. static const char *g_ppszModelLocs[] =
  220. {
  221. "Group01",
  222. "Group01",
  223. "Group02",
  224. "Group03%s",
  225. };
  226. #define IsExcludedHead( type, bMedic, iHead) false // see XBox codeline for an implementation
  227. //---------------------------------------------------------
  228. // Citizen activities
  229. //---------------------------------------------------------
  230. int ACT_CIT_HANDSUP;
  231. int ACT_CIT_BLINDED; // Blinded by scanner photo
  232. int ACT_CIT_SHOWARMBAND;
  233. int ACT_CIT_HEAL;
  234. int ACT_CIT_STARTLED; // Startled by sneaky scanner
  235. //---------------------------------------------------------
  236. LINK_ENTITY_TO_CLASS( npc_citizen, CNPC_Citizen );
  237. //---------------------------------------------------------
  238. BEGIN_DATADESC( CNPC_Citizen )
  239. DEFINE_CUSTOM_FIELD( m_nInspectActivity, ActivityDataOps() ),
  240. DEFINE_FIELD( m_flNextFearSoundTime, FIELD_TIME ),
  241. DEFINE_FIELD( m_flStopManhackFlinch, FIELD_TIME ),
  242. DEFINE_FIELD( m_fNextInspectTime, FIELD_TIME ),
  243. DEFINE_FIELD( m_flPlayerHealTime, FIELD_TIME ),
  244. DEFINE_FIELD( m_flNextHealthSearchTime, FIELD_TIME ),
  245. DEFINE_FIELD( m_flAllyHealTime, FIELD_TIME ),
  246. // gm_PlayerSquadEvaluateTimer
  247. // m_AssaultBehavior
  248. // m_FollowBehavior
  249. // m_StandoffBehavior
  250. // m_LeadBehavior
  251. // m_FuncTankBehavior
  252. DEFINE_FIELD( m_flPlayerGiveAmmoTime, FIELD_TIME ),
  253. DEFINE_KEYFIELD( m_iszAmmoSupply, FIELD_STRING, "ammosupply" ),
  254. DEFINE_KEYFIELD( m_iAmmoAmount, FIELD_INTEGER, "ammoamount" ),
  255. DEFINE_FIELD( m_bRPGAvoidPlayer, FIELD_BOOLEAN ),
  256. DEFINE_FIELD( m_bShouldPatrol, FIELD_BOOLEAN ),
  257. DEFINE_FIELD( m_iszOriginalSquad, FIELD_STRING ),
  258. DEFINE_FIELD( m_flTimeJoinedPlayerSquad, FIELD_TIME ),
  259. DEFINE_FIELD( m_bWasInPlayerSquad, FIELD_BOOLEAN ),
  260. DEFINE_FIELD( m_flTimeLastCloseToPlayer, FIELD_TIME ),
  261. DEFINE_EMBEDDED( m_AutoSummonTimer ),
  262. DEFINE_FIELD( m_vAutoSummonAnchor, FIELD_POSITION_VECTOR ),
  263. DEFINE_KEYFIELD( m_Type, FIELD_INTEGER, "citizentype" ),
  264. DEFINE_KEYFIELD( m_ExpressionType, FIELD_INTEGER, "expressiontype" ),
  265. DEFINE_FIELD( m_iHead, FIELD_INTEGER ),
  266. DEFINE_FIELD( m_flTimePlayerStare, FIELD_TIME ),
  267. DEFINE_FIELD( m_flTimeNextHealStare, FIELD_TIME ),
  268. DEFINE_FIELD( m_hSavedFollowGoalEnt, FIELD_EHANDLE ),
  269. DEFINE_KEYFIELD( m_bNotifyNavFailBlocked, FIELD_BOOLEAN, "notifynavfailblocked" ),
  270. DEFINE_KEYFIELD( m_bNeverLeavePlayerSquad, FIELD_BOOLEAN, "neverleaveplayersquad" ),
  271. DEFINE_KEYFIELD( m_iszDenyCommandConcept, FIELD_STRING, "denycommandconcept" ),
  272. DEFINE_OUTPUT( m_OnJoinedPlayerSquad, "OnJoinedPlayerSquad" ),
  273. DEFINE_OUTPUT( m_OnLeftPlayerSquad, "OnLeftPlayerSquad" ),
  274. DEFINE_OUTPUT( m_OnFollowOrder, "OnFollowOrder" ),
  275. DEFINE_OUTPUT( m_OnStationOrder, "OnStationOrder" ),
  276. DEFINE_OUTPUT( m_OnPlayerUse, "OnPlayerUse" ),
  277. DEFINE_OUTPUT( m_OnNavFailBlocked, "OnNavFailBlocked" ),
  278. DEFINE_INPUTFUNC( FIELD_VOID, "RemoveFromPlayerSquad", InputRemoveFromPlayerSquad ),
  279. DEFINE_INPUTFUNC( FIELD_VOID, "StartPatrolling", InputStartPatrolling ),
  280. DEFINE_INPUTFUNC( FIELD_VOID, "StopPatrolling", InputStopPatrolling ),
  281. DEFINE_INPUTFUNC( FIELD_VOID, "SetCommandable", InputSetCommandable ),
  282. DEFINE_INPUTFUNC( FIELD_VOID, "SetMedicOn", InputSetMedicOn ),
  283. DEFINE_INPUTFUNC( FIELD_VOID, "SetMedicOff", InputSetMedicOff ),
  284. DEFINE_INPUTFUNC( FIELD_VOID, "SetAmmoResupplierOn", InputSetAmmoResupplierOn ),
  285. DEFINE_INPUTFUNC( FIELD_VOID, "SetAmmoResupplierOff", InputSetAmmoResupplierOff ),
  286. DEFINE_INPUTFUNC( FIELD_VOID, "SpeakIdleResponse", InputSpeakIdleResponse ),
  287. #if HL2_EPISODIC
  288. DEFINE_INPUTFUNC( FIELD_VOID, "ThrowHealthKit", InputForceHealthKitToss ),
  289. #endif
  290. DEFINE_USEFUNC( CommanderUse ),
  291. DEFINE_USEFUNC( SimpleUse ),
  292. END_DATADESC()
  293. //-----------------------------------------------------------------------------
  294. //-----------------------------------------------------------------------------
  295. CSimpleSimTimer CNPC_Citizen::gm_PlayerSquadEvaluateTimer;
  296. //-----------------------------------------------------------------------------
  297. //-----------------------------------------------------------------------------
  298. bool CNPC_Citizen::CreateBehaviors()
  299. {
  300. BaseClass::CreateBehaviors();
  301. AddBehavior( &m_FuncTankBehavior );
  302. return true;
  303. }
  304. //-----------------------------------------------------------------------------
  305. //-----------------------------------------------------------------------------
  306. void CNPC_Citizen::Precache()
  307. {
  308. SelectModel();
  309. SelectExpressionType();
  310. if ( !npc_citizen_dont_precache_all.GetBool() )
  311. PrecacheAllOfType( m_Type );
  312. else
  313. PrecacheModel( STRING( GetModelName() ) );
  314. if ( NameMatches( "matt" ) )
  315. PrecacheModel( "models/props_canal/mattpipe.mdl" );
  316. PrecacheModel( INSIGNIA_MODEL );
  317. PrecacheScriptSound( "NPC_Citizen.FootstepLeft" );
  318. PrecacheScriptSound( "NPC_Citizen.FootstepRight" );
  319. PrecacheScriptSound( "NPC_Citizen.Die" );
  320. PrecacheInstancedScene( "scenes/Expressions/CitizenIdle.vcd" );
  321. PrecacheInstancedScene( "scenes/Expressions/CitizenAlert_loop.vcd" );
  322. PrecacheInstancedScene( "scenes/Expressions/CitizenCombat_loop.vcd" );
  323. for ( int i = 0; i < STATES_WITH_EXPRESSIONS; i++ )
  324. {
  325. for ( int j = 0; j < ARRAYSIZE(ScaredExpressions[i].szExpressions); j++ )
  326. {
  327. PrecacheInstancedScene( ScaredExpressions[i].szExpressions[j] );
  328. }
  329. for ( int j = 0; j < ARRAYSIZE(NormalExpressions[i].szExpressions); j++ )
  330. {
  331. PrecacheInstancedScene( NormalExpressions[i].szExpressions[j] );
  332. }
  333. for ( int j = 0; j < ARRAYSIZE(AngryExpressions[i].szExpressions); j++ )
  334. {
  335. PrecacheInstancedScene( AngryExpressions[i].szExpressions[j] );
  336. }
  337. }
  338. BaseClass::Precache();
  339. }
  340. //-----------------------------------------------------------------------------
  341. //-----------------------------------------------------------------------------
  342. void CNPC_Citizen::PrecacheAllOfType( CitizenType_t type )
  343. {
  344. if ( m_Type == CT_UNIQUE )
  345. return;
  346. int nHeads = ARRAYSIZE( g_ppszRandomHeads );
  347. int i;
  348. for ( i = 0; i < nHeads; ++i )
  349. {
  350. if ( !IsExcludedHead( type, false, i ) )
  351. {
  352. PrecacheModel( CFmtStr( "models/Humans/%s/%s", (const char *)(CFmtStr(g_ppszModelLocs[m_Type], "")), g_ppszRandomHeads[i] ) );
  353. }
  354. }
  355. if ( m_Type == CT_REBEL )
  356. {
  357. for ( i = 0; i < nHeads; ++i )
  358. {
  359. if ( !IsExcludedHead( type, true, i ) )
  360. {
  361. PrecacheModel( CFmtStr( "models/Humans/%s/%s", (const char *)(CFmtStr(g_ppszModelLocs[m_Type], "m")), g_ppszRandomHeads[i] ) );
  362. }
  363. }
  364. }
  365. }
  366. //-----------------------------------------------------------------------------
  367. //-----------------------------------------------------------------------------
  368. void CNPC_Citizen::Spawn()
  369. {
  370. BaseClass::Spawn();
  371. #ifdef _XBOX
  372. // Always fade the corpse
  373. AddSpawnFlags( SF_NPC_FADE_CORPSE );
  374. #endif // _XBOX
  375. if ( ShouldAutosquad() )
  376. {
  377. if ( m_SquadName == GetPlayerSquadName() )
  378. {
  379. CAI_Squad *pPlayerSquad = g_AI_SquadManager.FindSquad( GetPlayerSquadName() );
  380. if ( pPlayerSquad && pPlayerSquad->NumMembers() >= MAX_PLAYER_SQUAD )
  381. m_SquadName = NULL_STRING;
  382. }
  383. gm_PlayerSquadEvaluateTimer.Force();
  384. }
  385. if ( IsAmmoResupplier() )
  386. m_nSkin = 2;
  387. m_bRPGAvoidPlayer = false;
  388. m_bShouldPatrol = false;
  389. m_iHealth = sk_citizen_health.GetFloat();
  390. // Are we on a train? Used in trainstation to have NPCs on trains.
  391. if ( GetMoveParent() && FClassnameIs( GetMoveParent(), "func_tracktrain" ) )
  392. {
  393. CapabilitiesRemove( bits_CAP_MOVE_GROUND );
  394. SetMoveType( MOVETYPE_NONE );
  395. if ( NameMatches("citizen_train_2") )
  396. {
  397. SetSequenceByName( "d1_t01_TrainRide_Sit_Idle" );
  398. SetIdealActivity( ACT_DO_NOT_DISTURB );
  399. }
  400. else
  401. {
  402. SetSequenceByName( "d1_t01_TrainRide_Stand" );
  403. SetIdealActivity( ACT_DO_NOT_DISTURB );
  404. }
  405. }
  406. m_flStopManhackFlinch = -1;
  407. m_iszIdleExpression = MAKE_STRING("scenes/expressions/citizenidle.vcd");
  408. m_iszAlertExpression = MAKE_STRING("scenes/expressions/citizenalert_loop.vcd");
  409. m_iszCombatExpression = MAKE_STRING("scenes/expressions/citizencombat_loop.vcd");
  410. m_iszOriginalSquad = m_SquadName;
  411. m_flNextHealthSearchTime = gpGlobals->curtime;
  412. CWeaponRPG *pRPG = dynamic_cast<CWeaponRPG*>(GetActiveWeapon());
  413. if ( pRPG )
  414. {
  415. CapabilitiesRemove( bits_CAP_USE_SHOT_REGULATOR );
  416. pRPG->StopGuiding();
  417. }
  418. m_flTimePlayerStare = FLT_MAX;
  419. AddEFlags( EFL_NO_DISSOLVE | EFL_NO_MEGAPHYSCANNON_RAGDOLL | EFL_NO_PHYSCANNON_INTERACTION );
  420. NPCInit();
  421. SetUse( &CNPC_Citizen::CommanderUse );
  422. Assert( !ShouldAutosquad() || !IsInPlayerSquad() );
  423. m_bWasInPlayerSquad = IsInPlayerSquad();
  424. // Use render bounds instead of human hull for guys sitting in chairs, etc.
  425. m_ActBusyBehavior.SetUseRenderBounds( HasSpawnFlags( SF_CITIZEN_USE_RENDER_BOUNDS ) );
  426. }
  427. //-----------------------------------------------------------------------------
  428. //-----------------------------------------------------------------------------
  429. void CNPC_Citizen::PostNPCInit()
  430. {
  431. if ( !gEntList.FindEntityByClassname( NULL, COMMAND_POINT_CLASSNAME ) )
  432. {
  433. CreateEntityByName( COMMAND_POINT_CLASSNAME );
  434. }
  435. if ( IsInPlayerSquad() )
  436. {
  437. if ( m_pSquad->NumMembers() > MAX_PLAYER_SQUAD )
  438. DevMsg( "Error: Spawning citizen in player squad but exceeds squad limit of %d members\n", MAX_PLAYER_SQUAD );
  439. FixupPlayerSquad();
  440. }
  441. else
  442. {
  443. if ( ( m_spawnflags & SF_CITIZEN_FOLLOW ) && AI_IsSinglePlayer() )
  444. {
  445. m_FollowBehavior.SetFollowTarget( UTIL_GetLocalPlayer() );
  446. m_FollowBehavior.SetParameters( AIF_SIMPLE );
  447. }
  448. }
  449. BaseClass::PostNPCInit();
  450. }
  451. //-----------------------------------------------------------------------------
  452. //-----------------------------------------------------------------------------
  453. struct HeadCandidate_t
  454. {
  455. int iHead;
  456. int nHeads;
  457. static int __cdecl Sort( const HeadCandidate_t *pLeft, const HeadCandidate_t *pRight )
  458. {
  459. return ( pLeft->nHeads - pRight->nHeads );
  460. }
  461. };
  462. void CNPC_Citizen::SelectModel()
  463. {
  464. // If making reslists, precache everything!!!
  465. static bool madereslists = false;
  466. if ( CommandLine()->CheckParm("-makereslists") && !madereslists )
  467. {
  468. madereslists = true;
  469. PrecacheAllOfType( CT_DOWNTRODDEN );
  470. PrecacheAllOfType( CT_REFUGEE );
  471. PrecacheAllOfType( CT_REBEL );
  472. }
  473. const char *pszModelName = NULL;
  474. if ( m_Type == CT_DEFAULT )
  475. {
  476. struct CitizenTypeMapping
  477. {
  478. const char *pszMapTag;
  479. CitizenType_t type;
  480. };
  481. static CitizenTypeMapping CitizenTypeMappings[] =
  482. {
  483. { "trainstation", CT_DOWNTRODDEN },
  484. { "canals", CT_REFUGEE },
  485. { "town", CT_REFUGEE },
  486. { "coast", CT_REFUGEE },
  487. { "prison", CT_DOWNTRODDEN },
  488. { "c17", CT_REBEL },
  489. { "citadel", CT_DOWNTRODDEN },
  490. };
  491. char szMapName[256];
  492. Q_strncpy(szMapName, STRING(gpGlobals->mapname), sizeof(szMapName) );
  493. Q_strlower(szMapName);
  494. for ( int i = 0; i < ARRAYSIZE(CitizenTypeMappings); i++ )
  495. {
  496. if ( Q_stristr( szMapName, CitizenTypeMappings[i].pszMapTag ) )
  497. {
  498. m_Type = CitizenTypeMappings[i].type;
  499. break;
  500. }
  501. }
  502. if ( m_Type == CT_DEFAULT )
  503. m_Type = CT_DOWNTRODDEN;
  504. }
  505. if( HasSpawnFlags( SF_CITIZEN_RANDOM_HEAD | SF_CITIZEN_RANDOM_HEAD_MALE | SF_CITIZEN_RANDOM_HEAD_FEMALE ) || GetModelName() == NULL_STRING )
  506. {
  507. Assert( m_iHead == -1 );
  508. char gender = ( HasSpawnFlags( SF_CITIZEN_RANDOM_HEAD_MALE ) ) ? 'm' :
  509. ( HasSpawnFlags( SF_CITIZEN_RANDOM_HEAD_FEMALE ) ) ? 'f' : 0;
  510. RemoveSpawnFlags( SF_CITIZEN_RANDOM_HEAD | SF_CITIZEN_RANDOM_HEAD_MALE | SF_CITIZEN_RANDOM_HEAD_FEMALE );
  511. if( HasSpawnFlags( SF_NPC_START_EFFICIENT ) )
  512. {
  513. SetModelName( AllocPooledString("models/humans/male_cheaple.mdl" ) );
  514. return;
  515. }
  516. else
  517. {
  518. // Count the heads
  519. int headCounts[ARRAYSIZE(g_ppszRandomHeads)] = { 0 };
  520. int i;
  521. for ( i = 0; i < g_AI_Manager.NumAIs(); i++ )
  522. {
  523. CNPC_Citizen *pCitizen = dynamic_cast<CNPC_Citizen *>(g_AI_Manager.AccessAIs()[i]);
  524. if ( pCitizen && pCitizen != this && pCitizen->m_iHead >= 0 && pCitizen->m_iHead < ARRAYSIZE(g_ppszRandomHeads) )
  525. {
  526. headCounts[pCitizen->m_iHead]++;
  527. }
  528. }
  529. // Find all candidates
  530. CUtlVectorFixed<HeadCandidate_t, ARRAYSIZE(g_ppszRandomHeads)> candidates;
  531. for ( i = 0; i < ARRAYSIZE(g_ppszRandomHeads); i++ )
  532. {
  533. if ( !gender || g_ppszRandomHeads[i][0] == gender )
  534. {
  535. if ( !IsExcludedHead( m_Type, IsMedic(), i ) )
  536. {
  537. HeadCandidate_t candidate = { i, headCounts[i] };
  538. candidates.AddToTail( candidate );
  539. }
  540. }
  541. }
  542. Assert( candidates.Count() );
  543. candidates.Sort( &HeadCandidate_t::Sort );
  544. int iSmallestCount = candidates[0].nHeads;
  545. int iLimit;
  546. for ( iLimit = 0; iLimit < candidates.Count(); iLimit++ )
  547. {
  548. if ( candidates[iLimit].nHeads > iSmallestCount )
  549. break;
  550. }
  551. m_iHead = candidates[random->RandomInt( 0, iLimit - 1 )].iHead;
  552. pszModelName = g_ppszRandomHeads[m_iHead];
  553. SetModelName(NULL_STRING);
  554. }
  555. }
  556. Assert( pszModelName || GetModelName() != NULL_STRING );
  557. if ( !pszModelName )
  558. {
  559. if ( GetModelName() == NULL_STRING )
  560. return;
  561. pszModelName = strrchr(STRING(GetModelName()), '/' );
  562. if ( !pszModelName )
  563. pszModelName = STRING(GetModelName());
  564. else
  565. {
  566. pszModelName++;
  567. if ( m_iHead == -1 )
  568. {
  569. for ( int i = 0; i < ARRAYSIZE(g_ppszRandomHeads); i++ )
  570. {
  571. if ( Q_stricmp( g_ppszRandomHeads[i], pszModelName ) == 0 )
  572. {
  573. m_iHead = i;
  574. break;
  575. }
  576. }
  577. }
  578. }
  579. if ( !*pszModelName )
  580. return;
  581. }
  582. // Unique citizen models are left alone
  583. if ( m_Type != CT_UNIQUE )
  584. {
  585. SetModelName( AllocPooledString( CFmtStr( "models/Humans/%s/%s", (const char *)(CFmtStr(g_ppszModelLocs[ m_Type ], ( IsMedic() ) ? "m" : "" )), pszModelName ) ) );
  586. }
  587. }
  588. //-----------------------------------------------------------------------------
  589. // Purpose:
  590. //-----------------------------------------------------------------------------
  591. void CNPC_Citizen::SelectExpressionType()
  592. {
  593. // If we've got a mapmaker assigned type, leave it alone
  594. if ( m_ExpressionType != CIT_EXP_UNASSIGNED )
  595. return;
  596. switch ( m_Type )
  597. {
  598. case CT_DOWNTRODDEN:
  599. m_ExpressionType = (CitizenExpressionTypes_t)RandomInt( CIT_EXP_SCARED, CIT_EXP_NORMAL );
  600. break;
  601. case CT_REFUGEE:
  602. m_ExpressionType = (CitizenExpressionTypes_t)RandomInt( CIT_EXP_SCARED, CIT_EXP_NORMAL );
  603. break;
  604. case CT_REBEL:
  605. m_ExpressionType = (CitizenExpressionTypes_t)RandomInt( CIT_EXP_SCARED, CIT_EXP_ANGRY );
  606. break;
  607. case CT_DEFAULT:
  608. case CT_UNIQUE:
  609. default:
  610. break;
  611. }
  612. }
  613. //-----------------------------------------------------------------------------
  614. //-----------------------------------------------------------------------------
  615. void CNPC_Citizen::FixupMattWeapon()
  616. {
  617. CBaseCombatWeapon *pWeapon = GetActiveWeapon();
  618. if ( pWeapon && pWeapon->ClassMatches( "weapon_crowbar" ) && NameMatches( "matt" ) )
  619. {
  620. Weapon_Drop( pWeapon );
  621. UTIL_Remove( pWeapon );
  622. pWeapon = (CBaseCombatWeapon *)CREATE_UNSAVED_ENTITY( CMattsPipe, "weapon_crowbar" );
  623. pWeapon->SetName( AllocPooledString( "matt_weapon" ) );
  624. DispatchSpawn( pWeapon );
  625. #ifdef DEBUG
  626. extern bool g_bReceivedChainedActivate;
  627. g_bReceivedChainedActivate = false;
  628. #endif
  629. pWeapon->Activate();
  630. Weapon_Equip( pWeapon );
  631. }
  632. }
  633. //-----------------------------------------------------------------------------
  634. //-----------------------------------------------------------------------------
  635. void CNPC_Citizen::Activate()
  636. {
  637. BaseClass::Activate();
  638. FixupMattWeapon();
  639. }
  640. //-----------------------------------------------------------------------------
  641. //-----------------------------------------------------------------------------
  642. void CNPC_Citizen::OnRestore()
  643. {
  644. gm_PlayerSquadEvaluateTimer.Force();
  645. BaseClass::OnRestore();
  646. if ( !gEntList.FindEntityByClassname( NULL, COMMAND_POINT_CLASSNAME ) )
  647. {
  648. CreateEntityByName( COMMAND_POINT_CLASSNAME );
  649. }
  650. }
  651. //-----------------------------------------------------------------------------
  652. //-----------------------------------------------------------------------------
  653. string_t CNPC_Citizen::GetModelName() const
  654. {
  655. string_t iszModelName = BaseClass::GetModelName();
  656. //
  657. // If the model refers to an obsolete model, pretend it was blank
  658. // so that we pick the new default model.
  659. //
  660. if (!Q_strnicmp(STRING(iszModelName), "models/c17_", 11) ||
  661. !Q_strnicmp(STRING(iszModelName), "models/male", 11) ||
  662. !Q_strnicmp(STRING(iszModelName), "models/female", 13) ||
  663. !Q_strnicmp(STRING(iszModelName), "models/citizen", 14))
  664. {
  665. return NULL_STRING;
  666. }
  667. return iszModelName;
  668. }
  669. //-----------------------------------------------------------------------------
  670. // Purpose: Overridden to switch our behavior between passive and rebel. We
  671. // become combative after Gordon becomes a criminal.
  672. //-----------------------------------------------------------------------------
  673. Class_T CNPC_Citizen::Classify()
  674. {
  675. if (GlobalEntity_GetState("gordon_precriminal") == GLOBAL_ON)
  676. return CLASS_CITIZEN_PASSIVE;
  677. if (GlobalEntity_GetState("citizens_passive") == GLOBAL_ON)
  678. return CLASS_CITIZEN_PASSIVE;
  679. return CLASS_PLAYER_ALLY;
  680. }
  681. //-----------------------------------------------------------------------------
  682. //-----------------------------------------------------------------------------
  683. bool CNPC_Citizen::ShouldAlwaysThink()
  684. {
  685. return ( BaseClass::ShouldAlwaysThink() || IsInPlayerSquad() );
  686. }
  687. //-----------------------------------------------------------------------------
  688. //-----------------------------------------------------------------------------
  689. #define CITIZEN_FOLLOWER_DESERT_FUNCTANK_DIST 45.0f*12.0f
  690. bool CNPC_Citizen::ShouldBehaviorSelectSchedule( CAI_BehaviorBase *pBehavior )
  691. {
  692. if( pBehavior == &m_FollowBehavior )
  693. {
  694. // Suppress follow behavior if I have a func_tank and the func tank is near
  695. // what I'm supposed to be following.
  696. if( m_FuncTankBehavior.CanSelectSchedule() )
  697. {
  698. // Is the tank close to the follow target?
  699. Vector vecTank = m_FuncTankBehavior.GetFuncTank()->WorldSpaceCenter();
  700. Vector vecFollowGoal = m_FollowBehavior.GetFollowGoalInfo().position;
  701. float flTankDistSqr = (vecTank - vecFollowGoal).LengthSqr();
  702. float flAllowDist = m_FollowBehavior.GetFollowGoalInfo().followPointTolerance * 2.0f;
  703. float flAllowDistSqr = flAllowDist * flAllowDist;
  704. if( flTankDistSqr < flAllowDistSqr )
  705. {
  706. // Deny follow behavior so the tank can go.
  707. return false;
  708. }
  709. }
  710. }
  711. else if( IsInPlayerSquad() && pBehavior == &m_FuncTankBehavior && m_FuncTankBehavior.IsMounted() )
  712. {
  713. if( m_FollowBehavior.GetFollowTarget() )
  714. {
  715. Vector vecFollowGoal = m_FollowBehavior.GetFollowTarget()->GetAbsOrigin();
  716. if( vecFollowGoal.DistToSqr( GetAbsOrigin() ) > Square(CITIZEN_FOLLOWER_DESERT_FUNCTANK_DIST) )
  717. {
  718. return false;
  719. }
  720. }
  721. }
  722. return BaseClass::ShouldBehaviorSelectSchedule( pBehavior );
  723. }
  724. void CNPC_Citizen::OnChangeRunningBehavior( CAI_BehaviorBase *pOldBehavior, CAI_BehaviorBase *pNewBehavior )
  725. {
  726. if ( pNewBehavior == &m_FuncTankBehavior )
  727. {
  728. m_bReadinessCapable = false;
  729. }
  730. else if ( pOldBehavior == &m_FuncTankBehavior )
  731. {
  732. m_bReadinessCapable = IsReadinessCapable();
  733. }
  734. BaseClass::OnChangeRunningBehavior( pOldBehavior, pNewBehavior );
  735. }
  736. //-----------------------------------------------------------------------------
  737. //-----------------------------------------------------------------------------
  738. void CNPC_Citizen::GatherConditions()
  739. {
  740. BaseClass::GatherConditions();
  741. if( IsInPlayerSquad() && hl2_episodic.GetBool() )
  742. {
  743. // Leave the player squad if someone has made me neutral to player.
  744. if( IRelationType(UTIL_GetLocalPlayer()) == D_NU )
  745. {
  746. RemoveFromPlayerSquad();
  747. }
  748. }
  749. if ( !SpokeConcept( TLK_JOINPLAYER ) && IsRunningScriptedSceneWithSpeech( this, true ) )
  750. {
  751. SetSpokeConcept( TLK_JOINPLAYER, NULL );
  752. for ( int i = 0; i < g_AI_Manager.NumAIs(); i++ )
  753. {
  754. CAI_BaseNPC *pNpc = g_AI_Manager.AccessAIs()[i];
  755. if ( pNpc != this && pNpc->GetClassname() == GetClassname() && pNpc->GetAbsOrigin().DistToSqr( GetAbsOrigin() ) < Square( 15*12 ) && FVisible( pNpc ) )
  756. {
  757. (assert_cast<CNPC_Citizen *>(pNpc))->SetSpokeConcept( TLK_JOINPLAYER, NULL );
  758. }
  759. }
  760. }
  761. if( ShouldLookForHealthItem() )
  762. {
  763. if( FindHealthItem( GetAbsOrigin(), Vector( 240, 240, 240 ) ) )
  764. SetCondition( COND_HEALTH_ITEM_AVAILABLE );
  765. else
  766. ClearCondition( COND_HEALTH_ITEM_AVAILABLE );
  767. m_flNextHealthSearchTime = gpGlobals->curtime + 4.0;
  768. }
  769. // If the player is standing near a medic and can see the medic,
  770. // assume the player is 'staring' and wants health.
  771. if( CanHeal() )
  772. {
  773. CBasePlayer *pPlayer = AI_GetSinglePlayer();
  774. if ( !pPlayer )
  775. {
  776. m_flTimePlayerStare = FLT_MAX;
  777. return;
  778. }
  779. float flDistSqr = ( GetAbsOrigin() - pPlayer->GetAbsOrigin() ).Length2DSqr();
  780. float flStareDist = sk_citizen_player_stare_dist.GetFloat();
  781. float flPlayerDamage = pPlayer->GetMaxHealth() - pPlayer->GetHealth();
  782. if( pPlayer->IsAlive() && flPlayerDamage > 0 && (flDistSqr <= flStareDist * flStareDist) && pPlayer->FInViewCone( this ) && pPlayer->FVisible( this ) )
  783. {
  784. if( m_flTimePlayerStare == FLT_MAX )
  785. {
  786. // Player wasn't looking at me at last think. He started staring now.
  787. m_flTimePlayerStare = gpGlobals->curtime;
  788. }
  789. // Heal if it's been long enough since last time I healed a staring player.
  790. if( gpGlobals->curtime - m_flTimePlayerStare >= sk_citizen_player_stare_time.GetFloat() && gpGlobals->curtime > m_flTimeNextHealStare && !IsCurSchedule( SCHED_CITIZEN_HEAL ) )
  791. {
  792. if ( ShouldHealTarget( pPlayer, true ) )
  793. {
  794. SetCondition( COND_CIT_PLAYERHEALREQUEST );
  795. }
  796. else
  797. {
  798. m_flTimeNextHealStare = gpGlobals->curtime + sk_citizen_stare_heal_time.GetFloat() * .5f;
  799. ClearCondition( COND_CIT_PLAYERHEALREQUEST );
  800. }
  801. }
  802. #ifdef HL2_EPISODIC
  803. // Heal if I'm on an assault. The player hasn't had time to stare at me.
  804. if( m_AssaultBehavior.IsRunning() && IsMoving() )
  805. {
  806. SetCondition( COND_CIT_PLAYERHEALREQUEST );
  807. }
  808. #endif
  809. }
  810. else
  811. {
  812. m_flTimePlayerStare = FLT_MAX;
  813. }
  814. }
  815. }
  816. //-----------------------------------------------------------------------------
  817. //-----------------------------------------------------------------------------
  818. void CNPC_Citizen::PredictPlayerPush()
  819. {
  820. if ( !AI_IsSinglePlayer() )
  821. return;
  822. if ( HasCondition( COND_CIT_PLAYERHEALREQUEST ) )
  823. return;
  824. bool bHadPlayerPush = HasCondition( COND_PLAYER_PUSHING );
  825. BaseClass::PredictPlayerPush();
  826. CBasePlayer *pPlayer = UTIL_GetLocalPlayer();
  827. if ( !bHadPlayerPush && HasCondition( COND_PLAYER_PUSHING ) &&
  828. pPlayer->FInViewCone( this ) && CanHeal() )
  829. {
  830. if ( ShouldHealTarget( pPlayer, true ) )
  831. {
  832. ClearCondition( COND_PLAYER_PUSHING );
  833. SetCondition( COND_CIT_PLAYERHEALREQUEST );
  834. }
  835. }
  836. }
  837. //-----------------------------------------------------------------------------
  838. //-----------------------------------------------------------------------------
  839. void CNPC_Citizen::PrescheduleThink()
  840. {
  841. BaseClass::PrescheduleThink();
  842. UpdatePlayerSquad();
  843. UpdateFollowCommandPoint();
  844. if ( !npc_citizen_insignia.GetBool() && npc_citizen_squad_marker.GetBool() && IsInPlayerSquad() )
  845. {
  846. Vector mins = WorldAlignMins() * .5 + GetAbsOrigin();
  847. Vector maxs = WorldAlignMaxs() * .5 + GetAbsOrigin();
  848. float rMax = 255;
  849. float gMax = 255;
  850. float bMax = 255;
  851. float rMin = 255;
  852. float gMin = 128;
  853. float bMin = 0;
  854. const float TIME_FADE = 1.0;
  855. float timeInSquad = gpGlobals->curtime - m_flTimeJoinedPlayerSquad;
  856. timeInSquad = MIN( TIME_FADE, MAX( timeInSquad, 0 ) );
  857. float fade = ( 1.0 - timeInSquad / TIME_FADE );
  858. float r = rMin + ( rMax - rMin ) * fade;
  859. float g = gMin + ( gMax - gMin ) * fade;
  860. float b = bMin + ( bMax - bMin ) * fade;
  861. // THIS IS A PLACEHOLDER UNTIL WE HAVE A REAL DESIGN & ART -- DO NOT REMOVE
  862. NDebugOverlay::Line( Vector( mins.x, GetAbsOrigin().y, GetAbsOrigin().z+1 ), Vector( maxs.x, GetAbsOrigin().y, GetAbsOrigin().z+1 ), r, g, b, false, .11 );
  863. NDebugOverlay::Line( Vector( GetAbsOrigin().x, mins.y, GetAbsOrigin().z+1 ), Vector( GetAbsOrigin().x, maxs.y, GetAbsOrigin().z+1 ), r, g, b, false, .11 );
  864. }
  865. if( GetEnemy() && g_ai_citizen_show_enemy.GetBool() )
  866. {
  867. NDebugOverlay::Line( EyePosition(), GetEnemy()->EyePosition(), 255, 0, 0, false, .1 );
  868. }
  869. if ( DebuggingCommanderMode() )
  870. {
  871. if ( HaveCommandGoal() )
  872. {
  873. CBaseEntity *pCommandPoint = gEntList.FindEntityByClassname( NULL, COMMAND_POINT_CLASSNAME );
  874. if ( pCommandPoint )
  875. {
  876. NDebugOverlay::Cross3D(pCommandPoint->GetAbsOrigin(), 16, 0, 255, 255, false, 0.1 );
  877. }
  878. }
  879. }
  880. }
  881. //-----------------------------------------------------------------------------
  882. // Purpose: Allows for modification of the interrupt mask for the current schedule.
  883. // In the most cases the base implementation should be called first.
  884. //-----------------------------------------------------------------------------
  885. void CNPC_Citizen::BuildScheduleTestBits()
  886. {
  887. BaseClass::BuildScheduleTestBits();
  888. if ( IsCurSchedule( SCHED_IDLE_STAND ) || IsCurSchedule( SCHED_ALERT_STAND ) )
  889. {
  890. SetCustomInterruptCondition( COND_CIT_START_INSPECTION );
  891. }
  892. if ( IsMedic() && IsCustomInterruptConditionSet( COND_HEAR_MOVE_AWAY ) )
  893. {
  894. if( !IsCurSchedule(SCHED_RELOAD, false) )
  895. {
  896. // Since schedule selection code prioritizes reloading over requests to heal
  897. // the player, we must prevent this condition from breaking the reload schedule.
  898. SetCustomInterruptCondition( COND_CIT_PLAYERHEALREQUEST );
  899. }
  900. SetCustomInterruptCondition( COND_CIT_COMMANDHEAL );
  901. }
  902. if( !IsCurSchedule( SCHED_NEW_WEAPON ) )
  903. {
  904. SetCustomInterruptCondition( COND_RECEIVED_ORDERS );
  905. }
  906. if( GetCurSchedule()->HasInterrupt( COND_IDLE_INTERRUPT ) )
  907. {
  908. SetCustomInterruptCondition( COND_BETTER_WEAPON_AVAILABLE );
  909. }
  910. #ifdef HL2_EPISODIC
  911. if( IsMedic() && m_AssaultBehavior.IsRunning() )
  912. {
  913. if( !IsCurSchedule(SCHED_RELOAD, false) )
  914. {
  915. SetCustomInterruptCondition( COND_CIT_PLAYERHEALREQUEST );
  916. }
  917. SetCustomInterruptCondition( COND_CIT_COMMANDHEAL );
  918. }
  919. #else
  920. if( IsMedic() && m_AssaultBehavior.IsRunning() && !IsMoving() )
  921. {
  922. if( !IsCurSchedule(SCHED_RELOAD, false) )
  923. {
  924. SetCustomInterruptCondition( COND_CIT_PLAYERHEALREQUEST );
  925. }
  926. SetCustomInterruptCondition( COND_CIT_COMMANDHEAL );
  927. }
  928. #endif
  929. }
  930. //-----------------------------------------------------------------------------
  931. //-----------------------------------------------------------------------------
  932. bool CNPC_Citizen::FInViewCone( CBaseEntity *pEntity )
  933. {
  934. #if 0
  935. if ( IsMortar( pEntity ) )
  936. {
  937. // @TODO (toml 11-20-03): do this only if have heard mortar shell recently and it's active
  938. return true;
  939. }
  940. #endif
  941. return BaseClass::FInViewCone( pEntity );
  942. }
  943. //-----------------------------------------------------------------------------
  944. //-----------------------------------------------------------------------------
  945. int CNPC_Citizen::SelectFailSchedule( int failedSchedule, int failedTask, AI_TaskFailureCode_t taskFailCode )
  946. {
  947. switch( failedSchedule )
  948. {
  949. case SCHED_NEW_WEAPON:
  950. // If failed trying to pick up a weapon, try again in one second. This is because other AI code
  951. // has put this off for 10 seconds under the assumption that the citizen would be able to
  952. // pick up the weapon that they found.
  953. m_flNextWeaponSearchTime = gpGlobals->curtime + 1.0f;
  954. break;
  955. case SCHED_ESTABLISH_LINE_OF_FIRE_FALLBACK:
  956. case SCHED_MOVE_TO_WEAPON_RANGE:
  957. if( !IsMortar( GetEnemy() ) )
  958. {
  959. if ( GetActiveWeapon() && ( GetActiveWeapon()->CapabilitiesGet() & bits_CAP_WEAPON_RANGE_ATTACK1 ) && random->RandomInt( 0, 1 ) && HasCondition(COND_SEE_ENEMY) && !HasCondition ( COND_NO_PRIMARY_AMMO ) )
  960. return TranslateSchedule( SCHED_RANGE_ATTACK1 );
  961. return SCHED_STANDOFF;
  962. }
  963. break;
  964. }
  965. return BaseClass::SelectFailSchedule( failedSchedule, failedTask, taskFailCode );
  966. }
  967. //-----------------------------------------------------------------------------
  968. //-----------------------------------------------------------------------------
  969. int CNPC_Citizen::SelectSchedule()
  970. {
  971. // If we can't move, we're on a train, and should be sitting.
  972. if ( GetMoveType() == MOVETYPE_NONE )
  973. {
  974. // For now, we're only ever parented to trains. If you hit this assert, you've parented a citizen
  975. // to something else, and now we need to figure out a better system.
  976. Assert( GetMoveParent() && FClassnameIs( GetMoveParent(), "func_tracktrain" ) );
  977. return SCHED_CITIZEN_SIT_ON_TRAIN;
  978. }
  979. CWeaponRPG *pRPG = dynamic_cast<CWeaponRPG*>(GetActiveWeapon());
  980. if ( pRPG && pRPG->IsGuiding() )
  981. {
  982. DevMsg( "Citizen in select schedule but RPG is guiding?\n");
  983. pRPG->StopGuiding();
  984. }
  985. return BaseClass::SelectSchedule();
  986. }
  987. //-----------------------------------------------------------------------------
  988. //-----------------------------------------------------------------------------
  989. int CNPC_Citizen::SelectSchedulePriorityAction()
  990. {
  991. int schedule = SelectScheduleHeal();
  992. if ( schedule != SCHED_NONE )
  993. return schedule;
  994. schedule = BaseClass::SelectSchedulePriorityAction();
  995. if ( schedule != SCHED_NONE )
  996. return schedule;
  997. schedule = SelectScheduleRetrieveItem();
  998. if ( schedule != SCHED_NONE )
  999. return schedule;
  1000. return SCHED_NONE;
  1001. }
  1002. //-----------------------------------------------------------------------------
  1003. // Determine if citizen should perform heal action.
  1004. //-----------------------------------------------------------------------------
  1005. int CNPC_Citizen::SelectScheduleHeal()
  1006. {
  1007. // episodic medics may toss the healthkits rather than poke you with them
  1008. #if HL2_EPISODIC
  1009. if ( CanHeal() )
  1010. {
  1011. CBaseEntity *pEntity = PlayerInRange( GetLocalOrigin(), HEAL_TOSS_TARGET_RANGE );
  1012. if ( pEntity )
  1013. {
  1014. if ( USE_EXPERIMENTAL_MEDIC_CODE() && IsMedic() )
  1015. {
  1016. // use the new heal toss algorithm
  1017. if ( ShouldHealTossTarget( pEntity, HasCondition( COND_CIT_PLAYERHEALREQUEST ) ) )
  1018. {
  1019. SetTarget( pEntity );
  1020. return SCHED_CITIZEN_HEAL_TOSS;
  1021. }
  1022. }
  1023. else if ( PlayerInRange( GetLocalOrigin(), HEAL_MOVE_RANGE ) )
  1024. {
  1025. // use old mechanism for ammo
  1026. if ( ShouldHealTarget( pEntity, HasCondition( COND_CIT_PLAYERHEALREQUEST ) ) )
  1027. {
  1028. SetTarget( pEntity );
  1029. return SCHED_CITIZEN_HEAL;
  1030. }
  1031. }
  1032. }
  1033. if ( m_pSquad )
  1034. {
  1035. pEntity = NULL;
  1036. float distClosestSq = HEAL_MOVE_RANGE*HEAL_MOVE_RANGE;
  1037. float distCurSq;
  1038. AISquadIter_t iter;
  1039. CAI_BaseNPC *pSquadmate = m_pSquad->GetFirstMember( &iter );
  1040. while ( pSquadmate )
  1041. {
  1042. if ( pSquadmate != this )
  1043. {
  1044. distCurSq = ( GetAbsOrigin() - pSquadmate->GetAbsOrigin() ).LengthSqr();
  1045. if ( distCurSq < distClosestSq && ShouldHealTarget( pSquadmate ) )
  1046. {
  1047. distClosestSq = distCurSq;
  1048. pEntity = pSquadmate;
  1049. }
  1050. }
  1051. pSquadmate = m_pSquad->GetNextMember( &iter );
  1052. }
  1053. if ( pEntity )
  1054. {
  1055. SetTarget( pEntity );
  1056. return SCHED_CITIZEN_HEAL;
  1057. }
  1058. }
  1059. }
  1060. else
  1061. {
  1062. if ( HasCondition( COND_CIT_PLAYERHEALREQUEST ) )
  1063. DevMsg( "Would say: sorry, need to recharge\n" );
  1064. }
  1065. return SCHED_NONE;
  1066. #else
  1067. if ( CanHeal() )
  1068. {
  1069. CBaseEntity *pEntity = PlayerInRange( GetLocalOrigin(), HEAL_MOVE_RANGE );
  1070. if ( pEntity && ShouldHealTarget( pEntity, HasCondition( COND_CIT_PLAYERHEALREQUEST ) ) )
  1071. {
  1072. SetTarget( pEntity );
  1073. return SCHED_CITIZEN_HEAL;
  1074. }
  1075. if ( m_pSquad )
  1076. {
  1077. pEntity = NULL;
  1078. float distClosestSq = HEAL_MOVE_RANGE*HEAL_MOVE_RANGE;
  1079. float distCurSq;
  1080. AISquadIter_t iter;
  1081. CAI_BaseNPC *pSquadmate = m_pSquad->GetFirstMember( &iter );
  1082. while ( pSquadmate )
  1083. {
  1084. if ( pSquadmate != this )
  1085. {
  1086. distCurSq = ( GetAbsOrigin() - pSquadmate->GetAbsOrigin() ).LengthSqr();
  1087. if ( distCurSq < distClosestSq && ShouldHealTarget( pSquadmate ) )
  1088. {
  1089. distClosestSq = distCurSq;
  1090. pEntity = pSquadmate;
  1091. }
  1092. }
  1093. pSquadmate = m_pSquad->GetNextMember( &iter );
  1094. }
  1095. if ( pEntity )
  1096. {
  1097. SetTarget( pEntity );
  1098. return SCHED_CITIZEN_HEAL;
  1099. }
  1100. }
  1101. }
  1102. else
  1103. {
  1104. if ( HasCondition( COND_CIT_PLAYERHEALREQUEST ) )
  1105. DevMsg( "Would say: sorry, need to recharge\n" );
  1106. }
  1107. return SCHED_NONE;
  1108. #endif
  1109. }
  1110. //-----------------------------------------------------------------------------
  1111. //-----------------------------------------------------------------------------
  1112. int CNPC_Citizen::SelectScheduleRetrieveItem()
  1113. {
  1114. if ( HasCondition(COND_BETTER_WEAPON_AVAILABLE) )
  1115. {
  1116. CBaseHLCombatWeapon *pWeapon = dynamic_cast<CBaseHLCombatWeapon *>(Weapon_FindUsable( WEAPON_SEARCH_DELTA ));
  1117. if ( pWeapon )
  1118. {
  1119. m_flNextWeaponSearchTime = gpGlobals->curtime + 10.0;
  1120. // Now lock the weapon for several seconds while we go to pick it up.
  1121. pWeapon->Lock( 10.0, this );
  1122. SetTarget( pWeapon );
  1123. return SCHED_NEW_WEAPON;
  1124. }
  1125. }
  1126. if( HasCondition(COND_HEALTH_ITEM_AVAILABLE) )
  1127. {
  1128. if( !IsInPlayerSquad() )
  1129. {
  1130. // Been kicked out of the player squad since the time I located the health.
  1131. ClearCondition( COND_HEALTH_ITEM_AVAILABLE );
  1132. }
  1133. else
  1134. {
  1135. CBaseEntity *pBase = FindHealthItem(m_FollowBehavior.GetFollowTarget()->GetAbsOrigin(), Vector( 120, 120, 120 ) );
  1136. CItem *pItem = dynamic_cast<CItem *>(pBase);
  1137. if( pItem )
  1138. {
  1139. SetTarget( pItem );
  1140. return SCHED_GET_HEALTHKIT;
  1141. }
  1142. }
  1143. }
  1144. return SCHED_NONE;
  1145. }
  1146. //-----------------------------------------------------------------------------
  1147. //-----------------------------------------------------------------------------
  1148. int CNPC_Citizen::SelectScheduleNonCombat()
  1149. {
  1150. if ( m_NPCState == NPC_STATE_IDLE )
  1151. {
  1152. // Handle being inspected by the scanner
  1153. if ( HasCondition( COND_CIT_START_INSPECTION ) )
  1154. {
  1155. ClearCondition( COND_CIT_START_INSPECTION );
  1156. return SCHED_CITIZEN_PLAY_INSPECT_ACTIVITY;
  1157. }
  1158. }
  1159. ClearCondition( COND_CIT_START_INSPECTION );
  1160. if ( m_bShouldPatrol )
  1161. return SCHED_CITIZEN_PATROL;
  1162. return SCHED_NONE;
  1163. }
  1164. //-----------------------------------------------------------------------------
  1165. //-----------------------------------------------------------------------------
  1166. int CNPC_Citizen::SelectScheduleManhackCombat()
  1167. {
  1168. if ( m_NPCState == NPC_STATE_COMBAT && IsManhackMeleeCombatant() )
  1169. {
  1170. if ( !HasCondition( COND_CAN_MELEE_ATTACK1 ) )
  1171. {
  1172. float distSqEnemy = ( GetEnemy()->GetAbsOrigin() - EyePosition() ).LengthSqr();
  1173. if ( distSqEnemy < 48.0*48.0 &&
  1174. ( ( GetEnemy()->GetAbsOrigin() + GetEnemy()->GetSmoothedVelocity() * .1 ) - EyePosition() ).LengthSqr() < distSqEnemy )
  1175. return SCHED_COWER;
  1176. int iRoll = random->RandomInt( 1, 4 );
  1177. if ( iRoll == 1 )
  1178. return SCHED_BACK_AWAY_FROM_ENEMY;
  1179. else if ( iRoll == 2 )
  1180. return SCHED_CHASE_ENEMY;
  1181. }
  1182. }
  1183. return SCHED_NONE;
  1184. }
  1185. //-----------------------------------------------------------------------------
  1186. //-----------------------------------------------------------------------------
  1187. int CNPC_Citizen::SelectScheduleCombat()
  1188. {
  1189. int schedule = SelectScheduleManhackCombat();
  1190. if ( schedule != SCHED_NONE )
  1191. return schedule;
  1192. return BaseClass::SelectScheduleCombat();
  1193. }
  1194. //-----------------------------------------------------------------------------
  1195. //-----------------------------------------------------------------------------
  1196. bool CNPC_Citizen::ShouldDeferToFollowBehavior()
  1197. {
  1198. #if 0
  1199. if ( HaveCommandGoal() )
  1200. return false;
  1201. #endif
  1202. return BaseClass::ShouldDeferToFollowBehavior();
  1203. }
  1204. //-----------------------------------------------------------------------------
  1205. //-----------------------------------------------------------------------------
  1206. int CNPC_Citizen::TranslateSchedule( int scheduleType )
  1207. {
  1208. CBasePlayer *pLocalPlayer = AI_GetSinglePlayer();
  1209. switch( scheduleType )
  1210. {
  1211. case SCHED_IDLE_STAND:
  1212. case SCHED_ALERT_STAND:
  1213. if( m_NPCState != NPC_STATE_COMBAT && pLocalPlayer && !pLocalPlayer->IsAlive() && CanJoinPlayerSquad() )
  1214. {
  1215. // Player is dead!
  1216. float flDist;
  1217. flDist = ( pLocalPlayer->GetAbsOrigin() - GetAbsOrigin() ).Length();
  1218. if( flDist < 50 * 12 )
  1219. {
  1220. AddSpawnFlags( SF_CITIZEN_NOT_COMMANDABLE );
  1221. return SCHED_CITIZEN_MOURN_PLAYER;
  1222. }
  1223. }
  1224. break;
  1225. case SCHED_ESTABLISH_LINE_OF_FIRE:
  1226. case SCHED_MOVE_TO_WEAPON_RANGE:
  1227. if( !IsMortar( GetEnemy() ) && HaveCommandGoal() )
  1228. {
  1229. if ( GetActiveWeapon() && ( GetActiveWeapon()->CapabilitiesGet() & bits_CAP_WEAPON_RANGE_ATTACK1 ) && random->RandomInt( 0, 1 ) && HasCondition(COND_SEE_ENEMY) && !HasCondition ( COND_NO_PRIMARY_AMMO ) )
  1230. return TranslateSchedule( SCHED_RANGE_ATTACK1 );
  1231. return SCHED_STANDOFF;
  1232. }
  1233. break;
  1234. case SCHED_CHASE_ENEMY:
  1235. if( !IsMortar( GetEnemy() ) && HaveCommandGoal() )
  1236. {
  1237. return SCHED_STANDOFF;
  1238. }
  1239. break;
  1240. case SCHED_RANGE_ATTACK1:
  1241. // If we have an RPG, we use a custom schedule for it
  1242. if ( !IsMortar( GetEnemy() ) && GetActiveWeapon() && FClassnameIs( GetActiveWeapon(), "weapon_rpg" ) )
  1243. {
  1244. if ( GetEnemy() && GetEnemy()->ClassMatches( "npc_strider" ) )
  1245. {
  1246. if (OccupyStrategySlotRange( SQUAD_SLOT_CITIZEN_RPG1, SQUAD_SLOT_CITIZEN_RPG2 ) )
  1247. {
  1248. return SCHED_CITIZEN_STRIDER_RANGE_ATTACK1_RPG;
  1249. }
  1250. else
  1251. {
  1252. return SCHED_STANDOFF;
  1253. }
  1254. }
  1255. else
  1256. {
  1257. CBasePlayer *pPlayer = AI_GetSinglePlayer();
  1258. if ( pPlayer && GetEnemy() && ( ( GetEnemy()->GetAbsOrigin() -
  1259. pPlayer->GetAbsOrigin() ).LengthSqr() < RPG_SAFE_DISTANCE * RPG_SAFE_DISTANCE ) )
  1260. {
  1261. // Don't fire our RPG at an enemy too close to the player
  1262. return SCHED_STANDOFF;
  1263. }
  1264. else
  1265. {
  1266. return SCHED_CITIZEN_RANGE_ATTACK1_RPG;
  1267. }
  1268. }
  1269. }
  1270. break;
  1271. }
  1272. return BaseClass::TranslateSchedule( scheduleType );
  1273. }
  1274. //-----------------------------------------------------------------------------
  1275. //-----------------------------------------------------------------------------
  1276. bool CNPC_Citizen::ShouldAcceptGoal( CAI_BehaviorBase *pBehavior, CAI_GoalEntity *pGoal )
  1277. {
  1278. if ( BaseClass::ShouldAcceptGoal( pBehavior, pGoal ) )
  1279. {
  1280. CAI_FollowBehavior *pFollowBehavior = dynamic_cast<CAI_FollowBehavior *>(pBehavior );
  1281. if ( pFollowBehavior )
  1282. {
  1283. if ( IsInPlayerSquad() )
  1284. {
  1285. m_hSavedFollowGoalEnt = (CAI_FollowGoal *)pGoal;
  1286. return false;
  1287. }
  1288. }
  1289. return true;
  1290. }
  1291. return false;
  1292. }
  1293. //-----------------------------------------------------------------------------
  1294. //-----------------------------------------------------------------------------
  1295. void CNPC_Citizen::OnClearGoal( CAI_BehaviorBase *pBehavior, CAI_GoalEntity *pGoal )
  1296. {
  1297. if ( m_hSavedFollowGoalEnt == pGoal )
  1298. m_hSavedFollowGoalEnt = NULL;
  1299. }
  1300. //-----------------------------------------------------------------------------
  1301. //-----------------------------------------------------------------------------
  1302. void CNPC_Citizen::StartTask( const Task_t *pTask )
  1303. {
  1304. switch( pTask->iTask )
  1305. {
  1306. case TASK_CIT_PLAY_INSPECT_SEQUENCE:
  1307. SetIdealActivity( (Activity) m_nInspectActivity );
  1308. break;
  1309. case TASK_CIT_SIT_ON_TRAIN:
  1310. if ( NameMatches("citizen_train_2") )
  1311. {
  1312. SetSequenceByName( "d1_t01_TrainRide_Sit_Idle" );
  1313. SetIdealActivity( ACT_DO_NOT_DISTURB );
  1314. }
  1315. else
  1316. {
  1317. SetSequenceByName( "d1_t01_TrainRide_Stand" );
  1318. SetIdealActivity( ACT_DO_NOT_DISTURB );
  1319. }
  1320. break;
  1321. case TASK_CIT_LEAVE_TRAIN:
  1322. if ( NameMatches("citizen_train_2") )
  1323. {
  1324. SetSequenceByName( "d1_t01_TrainRide_Sit_Exit" );
  1325. SetIdealActivity( ACT_DO_NOT_DISTURB );
  1326. }
  1327. else
  1328. {
  1329. SetSequenceByName( "d1_t01_TrainRide_Stand_Exit" );
  1330. SetIdealActivity( ACT_DO_NOT_DISTURB );
  1331. }
  1332. break;
  1333. case TASK_CIT_HEAL:
  1334. #if HL2_EPISODIC
  1335. case TASK_CIT_HEAL_TOSS:
  1336. #endif
  1337. if ( IsMedic() )
  1338. {
  1339. if ( GetTarget() && GetTarget()->IsPlayer() && GetTarget()->m_iMaxHealth == GetTarget()->m_iHealth )
  1340. {
  1341. // Doesn't need us anymore
  1342. TaskComplete();
  1343. break;
  1344. }
  1345. Speak( TLK_HEAL );
  1346. }
  1347. else if ( IsAmmoResupplier() )
  1348. {
  1349. Speak( TLK_GIVEAMMO );
  1350. }
  1351. SetIdealActivity( (Activity)ACT_CIT_HEAL );
  1352. break;
  1353. case TASK_CIT_RPG_AUGER:
  1354. m_bRPGAvoidPlayer = false;
  1355. SetWait( 15.0 ); // maximum time auger before giving up
  1356. break;
  1357. case TASK_CIT_SPEAK_MOURNING:
  1358. if ( !IsSpeaking() && CanSpeakAfterMyself() )
  1359. {
  1360. //CAI_AllySpeechManager *pSpeechManager = GetAllySpeechManager();
  1361. //if ( pSpeechManager-> )
  1362. Speak(TLK_PLDEAD);
  1363. }
  1364. TaskComplete();
  1365. break;
  1366. default:
  1367. BaseClass::StartTask( pTask );
  1368. break;
  1369. }
  1370. }
  1371. //-----------------------------------------------------------------------------
  1372. //-----------------------------------------------------------------------------
  1373. void CNPC_Citizen::RunTask( const Task_t *pTask )
  1374. {
  1375. switch( pTask->iTask )
  1376. {
  1377. case TASK_WAIT_FOR_MOVEMENT:
  1378. {
  1379. if ( IsManhackMeleeCombatant() )
  1380. {
  1381. AddFacingTarget( GetEnemy(), 1.0, 0.5 );
  1382. }
  1383. BaseClass::RunTask( pTask );
  1384. break;
  1385. }
  1386. case TASK_MOVE_TO_TARGET_RANGE:
  1387. {
  1388. // If we're moving to heal a target, and the target dies, stop
  1389. if ( IsCurSchedule( SCHED_CITIZEN_HEAL ) && (!GetTarget() || !GetTarget()->IsAlive()) )
  1390. {
  1391. TaskFail(FAIL_NO_TARGET);
  1392. return;
  1393. }
  1394. BaseClass::RunTask( pTask );
  1395. break;
  1396. }
  1397. case TASK_CIT_PLAY_INSPECT_SEQUENCE:
  1398. {
  1399. AutoMovement();
  1400. if ( IsSequenceFinished() )
  1401. {
  1402. TaskComplete();
  1403. }
  1404. break;
  1405. }
  1406. case TASK_CIT_SIT_ON_TRAIN:
  1407. {
  1408. // If we were on a train, but we're not anymore, enable movement
  1409. if ( !GetMoveParent() )
  1410. {
  1411. SetMoveType( MOVETYPE_STEP );
  1412. CapabilitiesAdd( bits_CAP_MOVE_GROUND );
  1413. TaskComplete();
  1414. }
  1415. break;
  1416. }
  1417. case TASK_CIT_LEAVE_TRAIN:
  1418. {
  1419. if ( IsSequenceFinished() )
  1420. {
  1421. SetupVPhysicsHull();
  1422. TaskComplete();
  1423. }
  1424. break;
  1425. }
  1426. case TASK_CIT_HEAL:
  1427. if ( IsSequenceFinished() )
  1428. {
  1429. TaskComplete();
  1430. }
  1431. else if (!GetTarget())
  1432. {
  1433. // Our heal target was killed or deleted somehow.
  1434. TaskFail(FAIL_NO_TARGET);
  1435. }
  1436. else
  1437. {
  1438. if ( ( GetTarget()->GetAbsOrigin() - GetAbsOrigin() ).Length2D() > HEAL_MOVE_RANGE/2 )
  1439. TaskComplete();
  1440. GetMotor()->SetIdealYawToTargetAndUpdate( GetTarget()->GetAbsOrigin() );
  1441. }
  1442. break;
  1443. #if HL2_EPISODIC
  1444. case TASK_CIT_HEAL_TOSS:
  1445. if ( IsSequenceFinished() )
  1446. {
  1447. TaskComplete();
  1448. }
  1449. else if (!GetTarget())
  1450. {
  1451. // Our heal target was killed or deleted somehow.
  1452. TaskFail(FAIL_NO_TARGET);
  1453. }
  1454. else
  1455. {
  1456. GetMotor()->SetIdealYawToTargetAndUpdate( GetTarget()->GetAbsOrigin() );
  1457. }
  1458. break;
  1459. #endif
  1460. case TASK_CIT_RPG_AUGER:
  1461. {
  1462. // Keep augering until the RPG has been destroyed
  1463. CWeaponRPG *pRPG = dynamic_cast<CWeaponRPG*>(GetActiveWeapon());
  1464. if ( !pRPG )
  1465. {
  1466. TaskFail( FAIL_ITEM_NO_FIND );
  1467. return;
  1468. }
  1469. // Has the RPG detonated?
  1470. if ( !pRPG->GetMissile() )
  1471. {
  1472. pRPG->StopGuiding();
  1473. TaskComplete();
  1474. return;
  1475. }
  1476. Vector vecLaserPos = pRPG->GetNPCLaserPosition();
  1477. if ( !m_bRPGAvoidPlayer )
  1478. {
  1479. // Abort if we've lost our enemy
  1480. if ( !GetEnemy() )
  1481. {
  1482. pRPG->StopGuiding();
  1483. TaskFail( FAIL_NO_ENEMY );
  1484. return;
  1485. }
  1486. // Is our enemy occluded?
  1487. if ( HasCondition( COND_ENEMY_OCCLUDED ) )
  1488. {
  1489. // Turn off the laserdot, but don't stop augering
  1490. pRPG->StopGuiding();
  1491. return;
  1492. }
  1493. else if ( pRPG->IsGuiding() == false )
  1494. {
  1495. pRPG->StartGuiding();
  1496. }
  1497. Vector vecEnemyPos = GetEnemy()->BodyTarget(GetAbsOrigin(), false);
  1498. CBasePlayer *pPlayer = AI_GetSinglePlayer();
  1499. if ( pPlayer && ( ( vecEnemyPos - pPlayer->GetAbsOrigin() ).LengthSqr() < RPG_SAFE_DISTANCE * RPG_SAFE_DISTANCE ) )
  1500. {
  1501. m_bRPGAvoidPlayer = true;
  1502. Speak( TLK_WATCHOUT );
  1503. }
  1504. else
  1505. {
  1506. // Pull the laserdot towards the target
  1507. Vector vecToTarget = (vecEnemyPos - vecLaserPos);
  1508. float distToMove = VectorNormalize( vecToTarget );
  1509. if ( distToMove > 90 )
  1510. distToMove = 90;
  1511. vecLaserPos += vecToTarget * distToMove;
  1512. }
  1513. }
  1514. if ( m_bRPGAvoidPlayer )
  1515. {
  1516. // Pull the laserdot up
  1517. vecLaserPos.z += 90;
  1518. }
  1519. if ( IsWaitFinished() )
  1520. {
  1521. pRPG->StopGuiding();
  1522. TaskFail( FAIL_NO_SHOOT );
  1523. return;
  1524. }
  1525. // Add imprecision to avoid obvious robotic perfection stationary targets
  1526. float imprecision = 18*sin(gpGlobals->curtime);
  1527. vecLaserPos.x += imprecision;
  1528. vecLaserPos.y += imprecision;
  1529. vecLaserPos.z += imprecision;
  1530. pRPG->UpdateNPCLaserPosition( vecLaserPos );
  1531. }
  1532. break;
  1533. default:
  1534. BaseClass::RunTask( pTask );
  1535. break;
  1536. }
  1537. }
  1538. //-----------------------------------------------------------------------------
  1539. // Purpose:
  1540. // Input : code -
  1541. //-----------------------------------------------------------------------------
  1542. void CNPC_Citizen::TaskFail( AI_TaskFailureCode_t code )
  1543. {
  1544. // If our heal task has failed, push out the heal time
  1545. if ( IsCurSchedule( SCHED_CITIZEN_HEAL ) )
  1546. {
  1547. m_flPlayerHealTime = gpGlobals->curtime + sk_citizen_heal_ally_delay.GetFloat();
  1548. }
  1549. if( code == FAIL_NO_ROUTE_BLOCKED && m_bNotifyNavFailBlocked )
  1550. {
  1551. m_OnNavFailBlocked.FireOutput( this, this );
  1552. }
  1553. BaseClass::TaskFail( code );
  1554. }
  1555. //-----------------------------------------------------------------------------
  1556. // Purpose: Override base class activiites
  1557. //-----------------------------------------------------------------------------
  1558. Activity CNPC_Citizen::NPC_TranslateActivity( Activity activity )
  1559. {
  1560. if ( activity == ACT_MELEE_ATTACK1 )
  1561. {
  1562. return ACT_MELEE_ATTACK_SWING;
  1563. }
  1564. // !!!HACK - Citizens don't have the required animations for shotguns,
  1565. // so trick them into using the rifle counterparts for now (sjb)
  1566. if ( activity == ACT_RUN_AIM_SHOTGUN )
  1567. return ACT_RUN_AIM_RIFLE;
  1568. if ( activity == ACT_WALK_AIM_SHOTGUN )
  1569. return ACT_WALK_AIM_RIFLE;
  1570. if ( activity == ACT_IDLE_ANGRY_SHOTGUN )
  1571. return ACT_IDLE_ANGRY_SMG1;
  1572. if ( activity == ACT_RANGE_ATTACK_SHOTGUN_LOW )
  1573. return ACT_RANGE_ATTACK_SMG1_LOW;
  1574. return BaseClass::NPC_TranslateActivity( activity );
  1575. }
  1576. //------------------------------------------------------------------------------
  1577. //------------------------------------------------------------------------------
  1578. void CNPC_Citizen::HandleAnimEvent( animevent_t *pEvent )
  1579. {
  1580. if ( pEvent->event == AE_CITIZEN_GET_PACKAGE )
  1581. {
  1582. // Give the citizen a package
  1583. CBaseCombatWeapon *pWeapon = Weapon_Create( "weapon_citizenpackage" );
  1584. if ( pWeapon )
  1585. {
  1586. // If I have a name, make my weapon match it with "_weapon" appended
  1587. if ( GetEntityName() != NULL_STRING )
  1588. {
  1589. pWeapon->SetName( AllocPooledString(UTIL_VarArgs("%s_weapon", STRING(GetEntityName()) )) );
  1590. }
  1591. Weapon_Equip( pWeapon );
  1592. }
  1593. return;
  1594. }
  1595. else if ( pEvent->event == AE_CITIZEN_HEAL )
  1596. {
  1597. // Heal my target (if within range)
  1598. #if HL2_EPISODIC
  1599. if ( USE_EXPERIMENTAL_MEDIC_CODE() && IsMedic() )
  1600. {
  1601. CBaseCombatCharacter *pTarget = dynamic_cast<CBaseCombatCharacter *>( GetTarget() );
  1602. Assert(pTarget);
  1603. if ( pTarget )
  1604. {
  1605. m_flPlayerHealTime = gpGlobals->curtime + sk_citizen_heal_toss_player_delay.GetFloat();;
  1606. TossHealthKit( pTarget, Vector(48.0f, 0.0f, 0.0f) );
  1607. }
  1608. }
  1609. else
  1610. {
  1611. Heal();
  1612. }
  1613. #else
  1614. Heal();
  1615. #endif
  1616. return;
  1617. }
  1618. switch( pEvent->event )
  1619. {
  1620. case NPC_EVENT_LEFTFOOT:
  1621. {
  1622. EmitSound( "NPC_Citizen.FootstepLeft", pEvent->eventtime );
  1623. }
  1624. break;
  1625. case NPC_EVENT_RIGHTFOOT:
  1626. {
  1627. EmitSound( "NPC_Citizen.FootstepRight", pEvent->eventtime );
  1628. }
  1629. break;
  1630. default:
  1631. BaseClass::HandleAnimEvent( pEvent );
  1632. break;
  1633. }
  1634. }
  1635. //------------------------------------------------------------------------------
  1636. //------------------------------------------------------------------------------
  1637. void CNPC_Citizen::PickupItem( CBaseEntity *pItem )
  1638. {
  1639. Assert( pItem != NULL );
  1640. if( FClassnameIs( pItem, "item_healthkit" ) )
  1641. {
  1642. if ( TakeHealth( sk_healthkit.GetFloat(), DMG_GENERIC ) )
  1643. {
  1644. RemoveAllDecals();
  1645. UTIL_Remove( pItem );
  1646. }
  1647. }
  1648. else if( FClassnameIs( pItem, "item_healthvial" ) )
  1649. {
  1650. if ( TakeHealth( sk_healthvial.GetFloat(), DMG_GENERIC ) )
  1651. {
  1652. RemoveAllDecals();
  1653. UTIL_Remove( pItem );
  1654. }
  1655. }
  1656. else
  1657. {
  1658. DevMsg("Citizen doesn't know how to pick up %s!\n", pItem->GetClassname() );
  1659. }
  1660. }
  1661. //-----------------------------------------------------------------------------
  1662. // Purpose:
  1663. //-----------------------------------------------------------------------------
  1664. bool CNPC_Citizen::IgnorePlayerPushing( void )
  1665. {
  1666. // If the NPC's on a func_tank that the player cannot man, ignore player pushing
  1667. if ( m_FuncTankBehavior.IsMounted() )
  1668. {
  1669. CFuncTank *pTank = m_FuncTankBehavior.GetFuncTank();
  1670. if ( pTank && !pTank->IsControllable() )
  1671. return true;
  1672. }
  1673. return false;
  1674. }
  1675. //-----------------------------------------------------------------------------
  1676. // Purpose: Return a random expression for the specified state to play over
  1677. // the state's expression loop.
  1678. //-----------------------------------------------------------------------------
  1679. const char *CNPC_Citizen::SelectRandomExpressionForState( NPC_STATE state )
  1680. {
  1681. // Hacky remap of NPC states to expression states that we care about
  1682. int iExpressionState = 0;
  1683. switch ( state )
  1684. {
  1685. case NPC_STATE_IDLE:
  1686. iExpressionState = 0;
  1687. break;
  1688. case NPC_STATE_ALERT:
  1689. iExpressionState = 1;
  1690. break;
  1691. case NPC_STATE_COMBAT:
  1692. iExpressionState = 2;
  1693. break;
  1694. default:
  1695. // An NPC state we don't have expressions for
  1696. return NULL;
  1697. }
  1698. // Now pick the right one for our expression type
  1699. switch ( m_ExpressionType )
  1700. {
  1701. case CIT_EXP_SCARED:
  1702. {
  1703. int iRandom = RandomInt( 0, ARRAYSIZE(ScaredExpressions[iExpressionState].szExpressions)-1 );
  1704. return ScaredExpressions[iExpressionState].szExpressions[iRandom];
  1705. }
  1706. case CIT_EXP_NORMAL:
  1707. {
  1708. int iRandom = RandomInt( 0, ARRAYSIZE(NormalExpressions[iExpressionState].szExpressions)-1 );
  1709. return NormalExpressions[iExpressionState].szExpressions[iRandom];
  1710. }
  1711. case CIT_EXP_ANGRY:
  1712. {
  1713. int iRandom = RandomInt( 0, ARRAYSIZE(AngryExpressions[iExpressionState].szExpressions)-1 );
  1714. return AngryExpressions[iExpressionState].szExpressions[iRandom];
  1715. }
  1716. default:
  1717. break;
  1718. }
  1719. return NULL;
  1720. }
  1721. //-----------------------------------------------------------------------------
  1722. //-----------------------------------------------------------------------------
  1723. void CNPC_Citizen::SimpleUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
  1724. {
  1725. // Under these conditions, citizens will refuse to go with the player.
  1726. // Robin: NPCs should always respond to +USE even if someone else has the semaphore.
  1727. m_bDontUseSemaphore = true;
  1728. // First, try to speak the +USE concept
  1729. if ( !SelectPlayerUseSpeech() )
  1730. {
  1731. if ( HasSpawnFlags(SF_CITIZEN_NOT_COMMANDABLE) || IRelationType( pActivator ) == D_NU )
  1732. {
  1733. // If I'm denying commander mode because a level designer has made that decision,
  1734. // then fire this output in case they've hooked it to an event.
  1735. m_OnDenyCommanderUse.FireOutput( this, this );
  1736. }
  1737. }
  1738. m_bDontUseSemaphore = false;
  1739. }
  1740. //-----------------------------------------------------------------------------
  1741. //-----------------------------------------------------------------------------
  1742. bool CNPC_Citizen::OnBeginMoveAndShoot()
  1743. {
  1744. if ( BaseClass::OnBeginMoveAndShoot() )
  1745. {
  1746. if( m_iMySquadSlot == SQUAD_SLOT_ATTACK1 || m_iMySquadSlot == SQUAD_SLOT_ATTACK2 )
  1747. return true; // already have the slot I need
  1748. if( m_iMySquadSlot == SQUAD_SLOT_NONE && OccupyStrategySlotRange( SQUAD_SLOT_ATTACK1, SQUAD_SLOT_ATTACK2 ) )
  1749. return true;
  1750. }
  1751. return false;
  1752. }
  1753. //-----------------------------------------------------------------------------
  1754. //-----------------------------------------------------------------------------
  1755. void CNPC_Citizen::OnEndMoveAndShoot()
  1756. {
  1757. VacateStrategySlot();
  1758. }
  1759. //-----------------------------------------------------------------------------
  1760. //-----------------------------------------------------------------------------
  1761. void CNPC_Citizen::LocateEnemySound()
  1762. {
  1763. #if 0
  1764. if ( !GetEnemy() )
  1765. return;
  1766. float flZDiff = GetLocalOrigin().z - GetEnemy()->GetLocalOrigin().z;
  1767. if( flZDiff < -128 )
  1768. {
  1769. EmitSound( "NPC_Citizen.UpThere" );
  1770. }
  1771. else if( flZDiff > 128 )
  1772. {
  1773. EmitSound( "NPC_Citizen.DownThere" );
  1774. }
  1775. else
  1776. {
  1777. EmitSound( "NPC_Citizen.OverHere" );
  1778. }
  1779. #endif
  1780. }
  1781. //-----------------------------------------------------------------------------
  1782. //-----------------------------------------------------------------------------
  1783. bool CNPC_Citizen::IsManhackMeleeCombatant()
  1784. {
  1785. CBaseCombatWeapon *pWeapon = GetActiveWeapon();
  1786. CBaseEntity *pEnemy = GetEnemy();
  1787. return ( pEnemy && pWeapon && pEnemy->Classify() == CLASS_MANHACK && pWeapon->ClassMatches( "weapon_crowbar" ) );
  1788. }
  1789. //-----------------------------------------------------------------------------
  1790. // Purpose: Return the actual position the NPC wants to fire at when it's trying
  1791. // to hit it's current enemy.
  1792. //-----------------------------------------------------------------------------
  1793. Vector CNPC_Citizen::GetActualShootPosition( const Vector &shootOrigin )
  1794. {
  1795. Vector vecTarget = BaseClass::GetActualShootPosition( shootOrigin );
  1796. CWeaponRPG *pRPG = dynamic_cast<CWeaponRPG*>(GetActiveWeapon());
  1797. // If we're firing an RPG at a gunship, aim off to it's side, because we'll auger towards it.
  1798. if ( pRPG && GetEnemy() )
  1799. {
  1800. if ( FClassnameIs( GetEnemy(), "npc_combinegunship" ) )
  1801. {
  1802. Vector vecRight;
  1803. GetVectors( NULL, &vecRight, NULL );
  1804. // Random height
  1805. vecRight.z = 0;
  1806. // Find a clear shot by checking for clear shots around it
  1807. float flShotOffsets[] =
  1808. {
  1809. 512,
  1810. -512,
  1811. 128,
  1812. -128
  1813. };
  1814. for ( int i = 0; i < ARRAYSIZE(flShotOffsets); i++ )
  1815. {
  1816. Vector vecTest = vecTarget + (vecRight * flShotOffsets[i]);
  1817. // Add some random height to it
  1818. vecTest.z += RandomFloat( -512, 512 );
  1819. trace_t tr;
  1820. AI_TraceLine( shootOrigin, vecTest, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr);
  1821. // If we can see the point, it's a clear shot
  1822. if ( tr.fraction == 1.0 && tr.m_pEnt != GetEnemy() )
  1823. {
  1824. pRPG->SetNPCLaserPosition( vecTest );
  1825. return vecTest;
  1826. }
  1827. }
  1828. }
  1829. else
  1830. {
  1831. pRPG->SetNPCLaserPosition( vecTarget );
  1832. }
  1833. }
  1834. return vecTarget;
  1835. }
  1836. //-----------------------------------------------------------------------------
  1837. //-----------------------------------------------------------------------------
  1838. void CNPC_Citizen::OnChangeActiveWeapon( CBaseCombatWeapon *pOldWeapon, CBaseCombatWeapon *pNewWeapon )
  1839. {
  1840. if ( pNewWeapon )
  1841. {
  1842. GetShotRegulator()->SetParameters( pNewWeapon->GetMinBurst(), pNewWeapon->GetMaxBurst(), pNewWeapon->GetMinRestTime(), pNewWeapon->GetMaxRestTime() );
  1843. }
  1844. BaseClass::OnChangeActiveWeapon( pOldWeapon, pNewWeapon );
  1845. }
  1846. //-----------------------------------------------------------------------------
  1847. //-----------------------------------------------------------------------------
  1848. #define SHOTGUN_DEFER_SEARCH_TIME 20.0f
  1849. #define OTHER_DEFER_SEARCH_TIME FLT_MAX
  1850. bool CNPC_Citizen::ShouldLookForBetterWeapon()
  1851. {
  1852. if ( BaseClass::ShouldLookForBetterWeapon() )
  1853. {
  1854. if ( IsInPlayerSquad() && (GetActiveWeapon()&&IsMoving()) && ( m_FollowBehavior.GetFollowTarget() && m_FollowBehavior.GetFollowTarget()->IsPlayer() ) )
  1855. {
  1856. // For citizens in the player squad, you must be unarmed, or standing still (if armed) in order to
  1857. // divert attention to looking for a new weapon.
  1858. return false;
  1859. }
  1860. if ( GetActiveWeapon() && IsMoving() )
  1861. return false;
  1862. if ( GlobalEntity_GetState("gordon_precriminal") == GLOBAL_ON )
  1863. {
  1864. // This stops the NPC looking altogether.
  1865. m_flNextWeaponSearchTime = FLT_MAX;
  1866. return false;
  1867. }
  1868. #ifdef DBGFLAG_ASSERT
  1869. // Cached off to make sure you change this if you ask the code to defer.
  1870. float flOldWeaponSearchTime = m_flNextWeaponSearchTime;
  1871. #endif
  1872. CBaseCombatWeapon *pWeapon = GetActiveWeapon();
  1873. if( pWeapon )
  1874. {
  1875. bool bDefer = false;
  1876. if( FClassnameIs( pWeapon, "weapon_ar2" ) )
  1877. {
  1878. // Content to keep this weapon forever
  1879. m_flNextWeaponSearchTime = OTHER_DEFER_SEARCH_TIME;
  1880. bDefer = true;
  1881. }
  1882. else if( FClassnameIs( pWeapon, "weapon_rpg" ) )
  1883. {
  1884. // Content to keep this weapon forever
  1885. m_flNextWeaponSearchTime = OTHER_DEFER_SEARCH_TIME;
  1886. bDefer = true;
  1887. }
  1888. else if( FClassnameIs( pWeapon, "weapon_shotgun" ) )
  1889. {
  1890. // Shotgunners do not defer their weapon search indefinitely.
  1891. // If more than one citizen in the squad has a shotgun, we force
  1892. // some of them to trade for another weapon.
  1893. if( NumWeaponsInSquad("weapon_shotgun") > 1 )
  1894. {
  1895. // Check for another weapon now. If I don't find one, this code will
  1896. // retry in 2 seconds or so.
  1897. bDefer = false;
  1898. }
  1899. else
  1900. {
  1901. // I'm the only shotgunner in the group right now, so I'll check
  1902. // again in 3 0seconds or so. This code attempts to distribute
  1903. // the desire to reduce shotguns amongst squadmates so that all
  1904. // shotgunners do not discard their weapons when they suddenly realize
  1905. // the squad has too many.
  1906. if( random->RandomInt( 0, 1 ) == 0 )
  1907. {
  1908. m_flNextWeaponSearchTime = gpGlobals->curtime + SHOTGUN_DEFER_SEARCH_TIME;
  1909. }
  1910. else
  1911. {
  1912. m_flNextWeaponSearchTime = gpGlobals->curtime + SHOTGUN_DEFER_SEARCH_TIME + 10.0f;
  1913. }
  1914. bDefer = true;
  1915. }
  1916. }
  1917. if( bDefer )
  1918. {
  1919. // I'm happy with my current weapon. Don't search now.
  1920. // If you ask the code to defer, you must have set m_flNextWeaponSearchTime to when
  1921. // you next want to try to search.
  1922. Assert( m_flNextWeaponSearchTime != flOldWeaponSearchTime );
  1923. return false;
  1924. }
  1925. }
  1926. return true;
  1927. }
  1928. return false;
  1929. }
  1930. //-----------------------------------------------------------------------------
  1931. //-----------------------------------------------------------------------------
  1932. int CNPC_Citizen::OnTakeDamage_Alive( const CTakeDamageInfo &info )
  1933. {
  1934. if( (info.GetDamageType() & DMG_BURN) && (info.GetDamageType() & DMG_DIRECT) )
  1935. {
  1936. #define CITIZEN_SCORCH_RATE 6
  1937. #define CITIZEN_SCORCH_FLOOR 75
  1938. Scorch( CITIZEN_SCORCH_RATE, CITIZEN_SCORCH_FLOOR );
  1939. }
  1940. CTakeDamageInfo newInfo = info;
  1941. if( IsInSquad() && (info.GetDamageType() & DMG_BLAST) && info.GetInflictor() )
  1942. {
  1943. if( npc_citizen_explosive_resist.GetBool() )
  1944. {
  1945. // Blast damage. If this kills a squad member, give the
  1946. // remaining citizens a resistance bonus to this inflictor
  1947. // to try to avoid having the entire squad wiped out by a
  1948. // single explosion.
  1949. if( m_pSquad->IsSquadInflictor( info.GetInflictor() ) )
  1950. {
  1951. newInfo.ScaleDamage( 0.5 );
  1952. }
  1953. else
  1954. {
  1955. // If this blast is going to kill me, designate the inflictor
  1956. // so that the rest of the squad can enjoy a damage resist.
  1957. if( info.GetDamage() >= GetHealth() )
  1958. {
  1959. m_pSquad->SetSquadInflictor( info.GetInflictor() );
  1960. }
  1961. }
  1962. }
  1963. }
  1964. return BaseClass::OnTakeDamage_Alive( newInfo );
  1965. }
  1966. //-----------------------------------------------------------------------------
  1967. //-----------------------------------------------------------------------------
  1968. bool CNPC_Citizen::IsCommandable()
  1969. {
  1970. return ( !HasSpawnFlags(SF_CITIZEN_NOT_COMMANDABLE) && IsInPlayerSquad() );
  1971. }
  1972. //-----------------------------------------------------------------------------
  1973. //-----------------------------------------------------------------------------
  1974. bool CNPC_Citizen::IsPlayerAlly( CBasePlayer *pPlayer )
  1975. {
  1976. if ( Classify() == CLASS_CITIZEN_PASSIVE && GlobalEntity_GetState("gordon_precriminal") == GLOBAL_ON )
  1977. {
  1978. // Robin: Citizens use friendly speech semaphore in trainstation
  1979. return true;
  1980. }
  1981. return BaseClass::IsPlayerAlly( pPlayer );
  1982. }
  1983. //-----------------------------------------------------------------------------
  1984. //-----------------------------------------------------------------------------
  1985. bool CNPC_Citizen::CanJoinPlayerSquad()
  1986. {
  1987. if ( !AI_IsSinglePlayer() )
  1988. return false;
  1989. if ( m_NPCState == NPC_STATE_SCRIPT || m_NPCState == NPC_STATE_PRONE )
  1990. return false;
  1991. if ( HasSpawnFlags(SF_CITIZEN_NOT_COMMANDABLE) )
  1992. return false;
  1993. if ( IsInAScript() )
  1994. return false;
  1995. // Don't bother people who don't want to be bothered
  1996. if ( !CanBeUsedAsAFriend() )
  1997. return false;
  1998. if ( IRelationType( UTIL_GetLocalPlayer() ) != D_LI )
  1999. return false;
  2000. return true;
  2001. }
  2002. //-----------------------------------------------------------------------------
  2003. //-----------------------------------------------------------------------------
  2004. bool CNPC_Citizen::WasInPlayerSquad()
  2005. {
  2006. return m_bWasInPlayerSquad;
  2007. }
  2008. //-----------------------------------------------------------------------------
  2009. //-----------------------------------------------------------------------------
  2010. bool CNPC_Citizen::HaveCommandGoal() const
  2011. {
  2012. if (GetCommandGoal() != vec3_invalid)
  2013. return true;
  2014. return false;
  2015. }
  2016. //-----------------------------------------------------------------------------
  2017. //-----------------------------------------------------------------------------
  2018. bool CNPC_Citizen::IsCommandMoving()
  2019. {
  2020. if ( AI_IsSinglePlayer() && IsInPlayerSquad() )
  2021. {
  2022. if ( m_FollowBehavior.GetFollowTarget() == UTIL_GetLocalPlayer() ||
  2023. IsFollowingCommandPoint() )
  2024. {
  2025. return ( m_FollowBehavior.IsMovingToFollowTarget() );
  2026. }
  2027. }
  2028. return false;
  2029. }
  2030. //-----------------------------------------------------------------------------
  2031. //-----------------------------------------------------------------------------
  2032. bool CNPC_Citizen::ShouldAutoSummon()
  2033. {
  2034. if ( !AI_IsSinglePlayer() || !IsFollowingCommandPoint() || !IsInPlayerSquad() )
  2035. return false;
  2036. CHL2_Player *pPlayer = (CHL2_Player *)UTIL_GetLocalPlayer();
  2037. float distMovedSq = ( pPlayer->GetAbsOrigin() - m_vAutoSummonAnchor ).LengthSqr();
  2038. float moveTolerance = player_squad_autosummon_move_tolerance.GetFloat() * 12;
  2039. const Vector &vCommandGoal = GetCommandGoal();
  2040. if ( distMovedSq < Square(moveTolerance * 10) && (GetAbsOrigin() - vCommandGoal).LengthSqr() > Square(10*12) && IsCommandMoving() )
  2041. {
  2042. m_AutoSummonTimer.Set( player_squad_autosummon_time.GetFloat() );
  2043. if ( player_squad_autosummon_debug.GetBool() )
  2044. DevMsg( "Waiting for arrival before initiating autosummon logic\n");
  2045. }
  2046. else if ( m_AutoSummonTimer.Expired() )
  2047. {
  2048. bool bSetFollow = false;
  2049. bool bTestEnemies = true;
  2050. // Auto summon unconditionally if a significant amount of time has passed
  2051. if ( gpGlobals->curtime - m_AutoSummonTimer.GetNext() > player_squad_autosummon_time.GetFloat() * 2 )
  2052. {
  2053. bSetFollow = true;
  2054. if ( player_squad_autosummon_debug.GetBool() )
  2055. DevMsg( "Auto summoning squad: long time (%f)\n", ( gpGlobals->curtime - m_AutoSummonTimer.GetNext() ) + player_squad_autosummon_time.GetFloat() );
  2056. }
  2057. // Player must move for autosummon
  2058. if ( distMovedSq > Square(12) )
  2059. {
  2060. bool bCommandPointIsVisible = pPlayer->FVisible( vCommandGoal + pPlayer->GetViewOffset() );
  2061. // Auto summon if the player is close by the command point
  2062. if ( !bSetFollow && bCommandPointIsVisible && distMovedSq > Square(24) )
  2063. {
  2064. float closenessTolerance = player_squad_autosummon_player_tolerance.GetFloat() * 12;
  2065. if ( (pPlayer->GetAbsOrigin() - vCommandGoal).LengthSqr() < Square( closenessTolerance ) &&
  2066. ((m_vAutoSummonAnchor - vCommandGoal).LengthSqr() > Square( closenessTolerance )) )
  2067. {
  2068. bSetFollow = true;
  2069. if ( player_squad_autosummon_debug.GetBool() )
  2070. DevMsg( "Auto summoning squad: player close to command point (%f)\n", (GetAbsOrigin() - vCommandGoal).Length() );
  2071. }
  2072. }
  2073. // Auto summon if moved a moderate distance and can't see command point, or moved a great distance
  2074. if ( !bSetFollow )
  2075. {
  2076. if ( distMovedSq > Square( moveTolerance * 2 ) )
  2077. {
  2078. bSetFollow = true;
  2079. bTestEnemies = ( distMovedSq < Square( moveTolerance * 10 ) );
  2080. if ( player_squad_autosummon_debug.GetBool() )
  2081. DevMsg( "Auto summoning squad: player very far from anchor (%f)\n", sqrt(distMovedSq) );
  2082. }
  2083. else if ( distMovedSq > Square( moveTolerance ) )
  2084. {
  2085. if ( !bCommandPointIsVisible )
  2086. {
  2087. bSetFollow = true;
  2088. if ( player_squad_autosummon_debug.GetBool() )
  2089. DevMsg( "Auto summoning squad: player far from anchor (%f)\n", sqrt(distMovedSq) );
  2090. }
  2091. }
  2092. }
  2093. }
  2094. // Auto summon only if there are no readily apparent enemies
  2095. if ( bSetFollow && bTestEnemies )
  2096. {
  2097. for ( int i = 0; i < g_AI_Manager.NumAIs(); i++ )
  2098. {
  2099. CAI_BaseNPC *pNpc = g_AI_Manager.AccessAIs()[i];
  2100. float timeSinceCombatTolerance = player_squad_autosummon_time_after_combat.GetFloat();
  2101. if ( pNpc->IsInPlayerSquad() )
  2102. {
  2103. if ( gpGlobals->curtime - pNpc->GetLastAttackTime() > timeSinceCombatTolerance ||
  2104. gpGlobals->curtime - pNpc->GetLastDamageTime() > timeSinceCombatTolerance )
  2105. continue;
  2106. }
  2107. else if ( pNpc->GetEnemy() )
  2108. {
  2109. CBaseEntity *pNpcEnemy = pNpc->GetEnemy();
  2110. if ( !IsSniper( pNpc ) && ( gpGlobals->curtime - pNpc->GetEnemyLastTimeSeen() ) > timeSinceCombatTolerance )
  2111. continue;
  2112. if ( pNpcEnemy == pPlayer )
  2113. {
  2114. if ( pNpc->CanBeAnEnemyOf( pPlayer ) )
  2115. {
  2116. bSetFollow = false;
  2117. break;
  2118. }
  2119. }
  2120. else if ( pNpcEnemy->IsNPC() && ( pNpcEnemy->MyNPCPointer()->GetSquad() == GetSquad() || pNpcEnemy->Classify() == CLASS_PLAYER_ALLY_VITAL ) )
  2121. {
  2122. if ( pNpc->CanBeAnEnemyOf( this ) )
  2123. {
  2124. bSetFollow = false;
  2125. break;
  2126. }
  2127. }
  2128. }
  2129. }
  2130. if ( !bSetFollow && player_squad_autosummon_debug.GetBool() )
  2131. DevMsg( "Auto summon REVOKED: Combat recent \n");
  2132. }
  2133. return bSetFollow;
  2134. }
  2135. return false;
  2136. }
  2137. //-----------------------------------------------------------------------------
  2138. // Is this entity something that the citizen should interact with (return true)
  2139. // or something that he should try to get close to (return false)
  2140. //-----------------------------------------------------------------------------
  2141. bool CNPC_Citizen::IsValidCommandTarget( CBaseEntity *pTarget )
  2142. {
  2143. return false;
  2144. }
  2145. //-----------------------------------------------------------------------------
  2146. bool CNPC_Citizen::SpeakCommandResponse( AIConcept_t concept, const char *modifiers )
  2147. {
  2148. return SpeakIfAllowed( concept,
  2149. CFmtStr( "numselected:%d,"
  2150. "useradio:%d%s",
  2151. ( GetSquad() ) ? GetSquad()->NumMembers() : 1,
  2152. ShouldSpeakRadio( AI_GetSinglePlayer() ),
  2153. ( modifiers ) ? CFmtStr(",%s", modifiers).operator const char *() : "" ) );
  2154. }
  2155. //-----------------------------------------------------------------------------
  2156. // Purpose: return TRUE if the commander mode should try to give this order
  2157. // to more people. return FALSE otherwise. For instance, we don't
  2158. // try to send all 3 selectedcitizens to pick up the same gun.
  2159. //-----------------------------------------------------------------------------
  2160. bool CNPC_Citizen::TargetOrder( CBaseEntity *pTarget, CAI_BaseNPC **Allies, int numAllies )
  2161. {
  2162. if ( pTarget->IsPlayer() )
  2163. {
  2164. // I'm the target! Toggle follow!
  2165. if( m_FollowBehavior.GetFollowTarget() != pTarget )
  2166. {
  2167. ClearFollowTarget();
  2168. SetCommandGoal( vec3_invalid );
  2169. // Turn follow on!
  2170. m_AssaultBehavior.Disable();
  2171. m_FollowBehavior.SetFollowTarget( pTarget );
  2172. m_FollowBehavior.SetParameters( AIF_SIMPLE );
  2173. SpeakCommandResponse( TLK_STARTFOLLOW );
  2174. m_OnFollowOrder.FireOutput( this, this );
  2175. }
  2176. else if ( m_FollowBehavior.GetFollowTarget() == pTarget )
  2177. {
  2178. // Stop following.
  2179. m_FollowBehavior.SetFollowTarget( NULL );
  2180. SpeakCommandResponse( TLK_STOPFOLLOW );
  2181. }
  2182. }
  2183. return true;
  2184. }
  2185. //-----------------------------------------------------------------------------
  2186. // Purpose: Turn off following before processing a move order.
  2187. //-----------------------------------------------------------------------------
  2188. void CNPC_Citizen::MoveOrder( const Vector &vecDest, CAI_BaseNPC **Allies, int numAllies )
  2189. {
  2190. if ( !AI_IsSinglePlayer() )
  2191. return;
  2192. if( hl2_episodic.GetBool() && m_iszDenyCommandConcept != NULL_STRING )
  2193. {
  2194. SpeakCommandResponse( STRING(m_iszDenyCommandConcept) );
  2195. return;
  2196. }
  2197. CHL2_Player *pPlayer = (CHL2_Player *)UTIL_GetLocalPlayer();
  2198. m_AutoSummonTimer.Set( player_squad_autosummon_time.GetFloat() );
  2199. m_vAutoSummonAnchor = pPlayer->GetAbsOrigin();
  2200. if( m_StandoffBehavior.IsRunning() )
  2201. {
  2202. m_StandoffBehavior.SetStandoffGoalPosition( vecDest );
  2203. }
  2204. // If in assault, cancel and move.
  2205. if( m_AssaultBehavior.HasHitRallyPoint() && !m_AssaultBehavior.HasHitAssaultPoint() )
  2206. {
  2207. m_AssaultBehavior.Disable();
  2208. ClearSchedule( "Moving from rally point to assault point" );
  2209. }
  2210. bool spoke = false;
  2211. CAI_BaseNPC *pClosest = NULL;
  2212. float closestDistSq = FLT_MAX;
  2213. for( int i = 0 ; i < numAllies ; i++ )
  2214. {
  2215. if( Allies[i]->IsInPlayerSquad() )
  2216. {
  2217. Assert( Allies[i]->IsCommandable() );
  2218. float distSq = ( pPlayer->GetAbsOrigin() - Allies[i]->GetAbsOrigin() ).LengthSqr();
  2219. if( distSq < closestDistSq )
  2220. {
  2221. pClosest = Allies[i];
  2222. closestDistSq = distSq;
  2223. }
  2224. }
  2225. }
  2226. if( m_FollowBehavior.GetFollowTarget() && !IsFollowingCommandPoint() )
  2227. {
  2228. ClearFollowTarget();
  2229. #if 0
  2230. if ( ( pPlayer->GetAbsOrigin() - GetAbsOrigin() ).LengthSqr() < Square( 180 ) &&
  2231. ( ( vecDest - pPlayer->GetAbsOrigin() ).LengthSqr() < Square( 120 ) ||
  2232. ( vecDest - GetAbsOrigin() ).LengthSqr() < Square( 120 ) ) )
  2233. {
  2234. if ( pClosest == this )
  2235. SpeakIfAllowed( TLK_STOPFOLLOW );
  2236. spoke = true;
  2237. }
  2238. #endif
  2239. }
  2240. if ( !spoke && pClosest == this )
  2241. {
  2242. float destDistToPlayer = ( vecDest - pPlayer->GetAbsOrigin() ).Length();
  2243. float destDistToClosest = ( vecDest - GetAbsOrigin() ).Length();
  2244. CFmtStr modifiers( "commandpoint_dist_to_player:%.0f,"
  2245. "commandpoint_dist_to_npc:%.0f",
  2246. destDistToPlayer,
  2247. destDistToClosest );
  2248. SpeakCommandResponse( TLK_COMMANDED, modifiers );
  2249. }
  2250. m_OnStationOrder.FireOutput( this, this );
  2251. BaseClass::MoveOrder( vecDest, Allies, numAllies );
  2252. }
  2253. //-----------------------------------------------------------------------------
  2254. //-----------------------------------------------------------------------------
  2255. void CNPC_Citizen::OnMoveOrder()
  2256. {
  2257. SetReadinessLevel( AIRL_STIMULATED, false, false );
  2258. BaseClass::OnMoveOrder();
  2259. }
  2260. //-----------------------------------------------------------------------------
  2261. //-----------------------------------------------------------------------------
  2262. void CNPC_Citizen::CommanderUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
  2263. {
  2264. m_OnPlayerUse.FireOutput( pActivator, pCaller );
  2265. // Under these conditions, citizens will refuse to go with the player.
  2266. // Robin: NPCs should always respond to +USE even if someone else has the semaphore.
  2267. if ( !AI_IsSinglePlayer() || !CanJoinPlayerSquad() )
  2268. {
  2269. SimpleUse( pActivator, pCaller, useType, value );
  2270. return;
  2271. }
  2272. if ( pActivator == UTIL_GetLocalPlayer() )
  2273. {
  2274. // Don't say hi after you've been addressed by the player
  2275. SetSpokeConcept( TLK_HELLO, NULL );
  2276. if ( npc_citizen_auto_player_squad_allow_use.GetBool() )
  2277. {
  2278. if ( !ShouldAutosquad() )
  2279. TogglePlayerSquadState();
  2280. else if ( !IsInPlayerSquad() && npc_citizen_auto_player_squad_allow_use.GetBool() )
  2281. AddToPlayerSquad();
  2282. }
  2283. else if ( GetCurSchedule() && ConditionInterruptsCurSchedule( COND_IDLE_INTERRUPT ) )
  2284. {
  2285. if ( SpeakIfAllowed( TLK_QUESTION, NULL, true ) )
  2286. {
  2287. if ( random->RandomInt( 1, 4 ) < 4 )
  2288. {
  2289. CBaseEntity *pRespondant = FindSpeechTarget( AIST_NPCS );
  2290. if ( pRespondant )
  2291. {
  2292. g_EventQueue.AddEvent( pRespondant, "SpeakIdleResponse", ( GetTimeSpeechComplete() - gpGlobals->curtime ) + .2, this, this );
  2293. }
  2294. }
  2295. }
  2296. }
  2297. }
  2298. }
  2299. //-----------------------------------------------------------------------------
  2300. //-----------------------------------------------------------------------------
  2301. bool CNPC_Citizen::ShouldSpeakRadio( CBaseEntity *pListener )
  2302. {
  2303. if ( !pListener )
  2304. return false;
  2305. const float radioRange = 384 * 384;
  2306. Vector vecDiff;
  2307. vecDiff = WorldSpaceCenter() - pListener->WorldSpaceCenter();
  2308. if( vecDiff.LengthSqr() > radioRange )
  2309. {
  2310. return true;
  2311. }
  2312. return false;
  2313. }
  2314. //-----------------------------------------------------------------------------
  2315. //-----------------------------------------------------------------------------
  2316. void CNPC_Citizen::OnMoveToCommandGoalFailed()
  2317. {
  2318. // Clear the goal.
  2319. SetCommandGoal( vec3_invalid );
  2320. // Announce failure.
  2321. SpeakCommandResponse( TLK_COMMAND_FAILED );
  2322. }
  2323. //-----------------------------------------------------------------------------
  2324. //-----------------------------------------------------------------------------
  2325. void CNPC_Citizen::AddToPlayerSquad()
  2326. {
  2327. Assert( !IsInPlayerSquad() );
  2328. AddToSquad( AllocPooledString(PLAYER_SQUADNAME) );
  2329. m_hSavedFollowGoalEnt = m_FollowBehavior.GetFollowGoal();
  2330. m_FollowBehavior.SetFollowGoalDirect( NULL );
  2331. FixupPlayerSquad();
  2332. SetCondition( COND_PLAYER_ADDED_TO_SQUAD );
  2333. }
  2334. //-----------------------------------------------------------------------------
  2335. //-----------------------------------------------------------------------------
  2336. void CNPC_Citizen::RemoveFromPlayerSquad()
  2337. {
  2338. Assert( IsInPlayerSquad() );
  2339. ClearFollowTarget();
  2340. ClearCommandGoal();
  2341. if ( m_iszOriginalSquad != NULL_STRING && strcmp( STRING( m_iszOriginalSquad ), PLAYER_SQUADNAME ) != 0 )
  2342. AddToSquad( m_iszOriginalSquad );
  2343. else
  2344. RemoveFromSquad();
  2345. if ( m_hSavedFollowGoalEnt )
  2346. m_FollowBehavior.SetFollowGoal( m_hSavedFollowGoalEnt );
  2347. SetCondition( COND_PLAYER_REMOVED_FROM_SQUAD );
  2348. // Don't evaluate the player squad for 2 seconds.
  2349. gm_PlayerSquadEvaluateTimer.Set( 2.0 );
  2350. }
  2351. //-----------------------------------------------------------------------------
  2352. //-----------------------------------------------------------------------------
  2353. void CNPC_Citizen::TogglePlayerSquadState()
  2354. {
  2355. if ( !AI_IsSinglePlayer() )
  2356. return;
  2357. if ( !IsInPlayerSquad() )
  2358. {
  2359. AddToPlayerSquad();
  2360. if ( HaveCommandGoal() )
  2361. {
  2362. SpeakCommandResponse( TLK_COMMANDED );
  2363. }
  2364. else if ( m_FollowBehavior.GetFollowTarget() == UTIL_GetLocalPlayer() )
  2365. {
  2366. SpeakCommandResponse( TLK_STARTFOLLOW );
  2367. }
  2368. }
  2369. else
  2370. {
  2371. SpeakCommandResponse( TLK_STOPFOLLOW );
  2372. RemoveFromPlayerSquad();
  2373. }
  2374. }
  2375. //-----------------------------------------------------------------------------
  2376. //-----------------------------------------------------------------------------
  2377. struct SquadCandidate_t
  2378. {
  2379. CNPC_Citizen *pCitizen;
  2380. bool bIsInSquad;
  2381. float distSq;
  2382. int iSquadIndex;
  2383. };
  2384. void CNPC_Citizen::UpdatePlayerSquad()
  2385. {
  2386. if ( !AI_IsSinglePlayer() )
  2387. return;
  2388. CBasePlayer *pPlayer = UTIL_GetLocalPlayer();
  2389. if ( ( pPlayer->GetAbsOrigin().AsVector2D() - GetAbsOrigin().AsVector2D() ).LengthSqr() < Square(20*12) )
  2390. m_flTimeLastCloseToPlayer = gpGlobals->curtime;
  2391. if ( !gm_PlayerSquadEvaluateTimer.Expired() )
  2392. return;
  2393. gm_PlayerSquadEvaluateTimer.Set( 2.0 );
  2394. // Remove stragglers
  2395. CAI_Squad *pPlayerSquad = g_AI_SquadManager.FindSquad( MAKE_STRING( PLAYER_SQUADNAME ) );
  2396. if ( pPlayerSquad )
  2397. {
  2398. CUtlVectorFixed<CNPC_Citizen *, MAX_PLAYER_SQUAD> squadMembersToRemove;
  2399. AISquadIter_t iter;
  2400. for ( CAI_BaseNPC *pPlayerSquadMember = pPlayerSquad->GetFirstMember(&iter); pPlayerSquadMember; pPlayerSquadMember = pPlayerSquad->GetNextMember(&iter) )
  2401. {
  2402. if ( pPlayerSquadMember->GetClassname() != GetClassname() )
  2403. continue;
  2404. CNPC_Citizen *pCitizen = assert_cast<CNPC_Citizen *>(pPlayerSquadMember);
  2405. if ( !pCitizen->m_bNeverLeavePlayerSquad &&
  2406. pCitizen->m_FollowBehavior.GetFollowTarget() &&
  2407. !pCitizen->m_FollowBehavior.FollowTargetVisible() &&
  2408. pCitizen->m_FollowBehavior.GetNumFailedFollowAttempts() > 0 &&
  2409. gpGlobals->curtime - pCitizen->m_FollowBehavior.GetTimeFailFollowStarted() > 20 &&
  2410. ( fabsf(( pCitizen->m_FollowBehavior.GetFollowTarget()->GetAbsOrigin().z - pCitizen->GetAbsOrigin().z )) > 196 ||
  2411. ( pCitizen->m_FollowBehavior.GetFollowTarget()->GetAbsOrigin().AsVector2D() - pCitizen->GetAbsOrigin().AsVector2D() ).LengthSqr() > Square(50*12) ) )
  2412. {
  2413. if ( DebuggingCommanderMode() )
  2414. {
  2415. DevMsg( "Player follower is lost (%d, %f, %d)\n",
  2416. pCitizen->m_FollowBehavior.GetNumFailedFollowAttempts(),
  2417. gpGlobals->curtime - pCitizen->m_FollowBehavior.GetTimeFailFollowStarted(),
  2418. (int)((pCitizen->m_FollowBehavior.GetFollowTarget()->GetAbsOrigin().AsVector2D() - pCitizen->GetAbsOrigin().AsVector2D() ).Length()) );
  2419. }
  2420. squadMembersToRemove.AddToTail( pCitizen );
  2421. }
  2422. }
  2423. for ( int i = 0; i < squadMembersToRemove.Count(); i++ )
  2424. {
  2425. squadMembersToRemove[i]->RemoveFromPlayerSquad();
  2426. }
  2427. }
  2428. // Autosquadding
  2429. const float JOIN_PLAYER_XY_TOLERANCE_SQ = Square(36*12);
  2430. const float UNCONDITIONAL_JOIN_PLAYER_XY_TOLERANCE_SQ = Square(12*12);
  2431. const float UNCONDITIONAL_JOIN_PLAYER_Z_TOLERANCE = 5*12;
  2432. const float SECOND_TIER_JOIN_DIST_SQ = Square(48*12);
  2433. if ( pPlayer && ShouldAutosquad() && !(pPlayer->GetFlags() & FL_NOTARGET ) && pPlayer->IsAlive() )
  2434. {
  2435. CAI_BaseNPC **ppAIs = g_AI_Manager.AccessAIs();
  2436. CUtlVector<SquadCandidate_t> candidates;
  2437. const Vector &vPlayerPos = pPlayer->GetAbsOrigin();
  2438. bool bFoundNewGuy = false;
  2439. int i;
  2440. for ( i = 0; i < g_AI_Manager.NumAIs(); i++ )
  2441. {
  2442. if ( ppAIs[i]->GetState() == NPC_STATE_DEAD )
  2443. continue;
  2444. if ( ppAIs[i]->GetClassname() != GetClassname() )
  2445. continue;
  2446. CNPC_Citizen *pCitizen = assert_cast<CNPC_Citizen *>(ppAIs[i]);
  2447. int iNew;
  2448. if ( pCitizen->IsInPlayerSquad() )
  2449. {
  2450. iNew = candidates.AddToTail();
  2451. candidates[iNew].pCitizen = pCitizen;
  2452. candidates[iNew].bIsInSquad = true;
  2453. candidates[iNew].distSq = 0;
  2454. candidates[iNew].iSquadIndex = pCitizen->GetSquad()->GetSquadIndex( pCitizen );
  2455. }
  2456. else
  2457. {
  2458. float distSq = (vPlayerPos.AsVector2D() - pCitizen->GetAbsOrigin().AsVector2D()).LengthSqr();
  2459. if ( distSq > JOIN_PLAYER_XY_TOLERANCE_SQ &&
  2460. ( pCitizen->m_flTimeJoinedPlayerSquad == 0 || gpGlobals->curtime - pCitizen->m_flTimeJoinedPlayerSquad > 60.0 ) &&
  2461. ( pCitizen->m_flTimeLastCloseToPlayer == 0 || gpGlobals->curtime - pCitizen->m_flTimeLastCloseToPlayer > 15.0 ) )
  2462. continue;
  2463. if ( !pCitizen->CanJoinPlayerSquad() )
  2464. continue;
  2465. bool bShouldAdd = false;
  2466. if ( pCitizen->HasCondition( COND_SEE_PLAYER ) )
  2467. bShouldAdd = true;
  2468. else
  2469. {
  2470. bool bPlayerVisible = pCitizen->FVisible( pPlayer );
  2471. if ( bPlayerVisible )
  2472. {
  2473. if ( pCitizen->HasCondition( COND_HEAR_PLAYER ) )
  2474. bShouldAdd = true;
  2475. else if ( distSq < UNCONDITIONAL_JOIN_PLAYER_XY_TOLERANCE_SQ && fabsf(vPlayerPos.z - pCitizen->GetAbsOrigin().z) < UNCONDITIONAL_JOIN_PLAYER_Z_TOLERANCE )
  2476. bShouldAdd = true;
  2477. }
  2478. }
  2479. if ( bShouldAdd )
  2480. {
  2481. // @TODO (toml 05-25-04): probably everyone in a squad should be a candidate if one of them sees the player
  2482. AI_Waypoint_t *pPathToPlayer = pCitizen->GetPathfinder()->BuildRoute( pCitizen->GetAbsOrigin(), vPlayerPos, pPlayer, 5*12, NAV_NONE, true );
  2483. GetPathfinder()->UnlockRouteNodes( pPathToPlayer );
  2484. if ( !pPathToPlayer )
  2485. continue;
  2486. CAI_Path tempPath;
  2487. tempPath.SetWaypoints( pPathToPlayer ); // path object will delete waypoints
  2488. iNew = candidates.AddToTail();
  2489. candidates[iNew].pCitizen = pCitizen;
  2490. candidates[iNew].bIsInSquad = false;
  2491. candidates[iNew].distSq = distSq;
  2492. candidates[iNew].iSquadIndex = -1;
  2493. bFoundNewGuy = true;
  2494. }
  2495. }
  2496. }
  2497. if ( bFoundNewGuy )
  2498. {
  2499. // Look for second order guys
  2500. int initialCount = candidates.Count();
  2501. for ( i = 0; i < initialCount; i++ )
  2502. candidates[i].pCitizen->AddSpawnFlags( SF_CITIZEN_NOT_COMMANDABLE ); // Prevents double-add
  2503. for ( i = 0; i < initialCount; i++ )
  2504. {
  2505. if ( candidates[i].iSquadIndex == -1 )
  2506. {
  2507. for ( int j = 0; j < g_AI_Manager.NumAIs(); j++ )
  2508. {
  2509. if ( ppAIs[j]->GetState() == NPC_STATE_DEAD )
  2510. continue;
  2511. if ( ppAIs[j]->GetClassname() != GetClassname() )
  2512. continue;
  2513. if ( ppAIs[j]->HasSpawnFlags( SF_CITIZEN_NOT_COMMANDABLE ) )
  2514. continue;
  2515. CNPC_Citizen *pCitizen = assert_cast<CNPC_Citizen *>(ppAIs[j]);
  2516. float distSq = (vPlayerPos - pCitizen->GetAbsOrigin()).Length2DSqr();
  2517. if ( distSq > JOIN_PLAYER_XY_TOLERANCE_SQ )
  2518. continue;
  2519. distSq = (candidates[i].pCitizen->GetAbsOrigin() - pCitizen->GetAbsOrigin()).Length2DSqr();
  2520. if ( distSq > SECOND_TIER_JOIN_DIST_SQ )
  2521. continue;
  2522. if ( !pCitizen->CanJoinPlayerSquad() )
  2523. continue;
  2524. if ( !pCitizen->FVisible( pPlayer ) )
  2525. continue;
  2526. int iNew = candidates.AddToTail();
  2527. candidates[iNew].pCitizen = pCitizen;
  2528. candidates[iNew].bIsInSquad = false;
  2529. candidates[iNew].distSq = distSq;
  2530. candidates[iNew].iSquadIndex = -1;
  2531. pCitizen->AddSpawnFlags( SF_CITIZEN_NOT_COMMANDABLE ); // Prevents double-add
  2532. }
  2533. }
  2534. }
  2535. for ( i = 0; i < candidates.Count(); i++ )
  2536. candidates[i].pCitizen->RemoveSpawnFlags( SF_CITIZEN_NOT_COMMANDABLE );
  2537. if ( candidates.Count() > MAX_PLAYER_SQUAD )
  2538. {
  2539. candidates.Sort( PlayerSquadCandidateSortFunc );
  2540. for ( i = MAX_PLAYER_SQUAD; i < candidates.Count(); i++ )
  2541. {
  2542. if ( candidates[i].pCitizen->IsInPlayerSquad() )
  2543. {
  2544. candidates[i].pCitizen->RemoveFromPlayerSquad();
  2545. }
  2546. }
  2547. }
  2548. if ( candidates.Count() )
  2549. {
  2550. CNPC_Citizen *pClosest = NULL;
  2551. float closestDistSq = FLT_MAX;
  2552. int nJoined = 0;
  2553. for ( i = 0; i < candidates.Count() && i < MAX_PLAYER_SQUAD; i++ )
  2554. {
  2555. if ( !candidates[i].pCitizen->IsInPlayerSquad() )
  2556. {
  2557. candidates[i].pCitizen->AddToPlayerSquad();
  2558. nJoined++;
  2559. if ( candidates[i].distSq < closestDistSq )
  2560. {
  2561. pClosest = candidates[i].pCitizen;
  2562. closestDistSq = candidates[i].distSq;
  2563. }
  2564. }
  2565. }
  2566. if ( pClosest )
  2567. {
  2568. if ( !pClosest->SpokeConcept( TLK_JOINPLAYER ) )
  2569. {
  2570. pClosest->SpeakCommandResponse( TLK_JOINPLAYER, CFmtStr( "numjoining:%d", nJoined ) );
  2571. }
  2572. else
  2573. {
  2574. pClosest->SpeakCommandResponse( TLK_STARTFOLLOW );
  2575. }
  2576. for ( i = 0; i < candidates.Count() && i < MAX_PLAYER_SQUAD; i++ )
  2577. {
  2578. candidates[i].pCitizen->SetSpokeConcept( TLK_JOINPLAYER, NULL );
  2579. }
  2580. }
  2581. }
  2582. }
  2583. }
  2584. }
  2585. //-----------------------------------------------------------------------------
  2586. //-----------------------------------------------------------------------------
  2587. int CNPC_Citizen::PlayerSquadCandidateSortFunc( const SquadCandidate_t *pLeft, const SquadCandidate_t *pRight )
  2588. {
  2589. // "Bigger" means less approprate
  2590. CNPC_Citizen *pLeftCitizen = pLeft->pCitizen;
  2591. CNPC_Citizen *pRightCitizen = pRight->pCitizen;
  2592. // Medics are better than anyone
  2593. if ( pLeftCitizen->IsMedic() && !pRightCitizen->IsMedic() )
  2594. return -1;
  2595. if ( !pLeftCitizen->IsMedic() && pRightCitizen->IsMedic() )
  2596. return 1;
  2597. CBaseCombatWeapon *pLeftWeapon = pLeftCitizen->GetActiveWeapon();
  2598. CBaseCombatWeapon *pRightWeapon = pRightCitizen->GetActiveWeapon();
  2599. // People with weapons are better than those without
  2600. if ( pLeftWeapon && !pRightWeapon )
  2601. return -1;
  2602. if ( !pLeftWeapon && pRightWeapon )
  2603. return 1;
  2604. // Existing squad members are better than non-members
  2605. if ( pLeft->bIsInSquad && !pRight->bIsInSquad )
  2606. return -1;
  2607. if ( !pLeft->bIsInSquad && pRight->bIsInSquad )
  2608. return 1;
  2609. // New squad members are better than older ones
  2610. if ( pLeft->bIsInSquad && pRight->bIsInSquad )
  2611. return pRight->iSquadIndex - pLeft->iSquadIndex;
  2612. // Finally, just take the closer
  2613. return (int)(pRight->distSq - pLeft->distSq);
  2614. }
  2615. //-----------------------------------------------------------------------------
  2616. //-----------------------------------------------------------------------------
  2617. void CNPC_Citizen::FixupPlayerSquad()
  2618. {
  2619. if ( !AI_IsSinglePlayer() )
  2620. return;
  2621. m_flTimeJoinedPlayerSquad = gpGlobals->curtime;
  2622. m_bWasInPlayerSquad = true;
  2623. if ( m_pSquad->NumMembers() > MAX_PLAYER_SQUAD )
  2624. {
  2625. CAI_BaseNPC *pFirstMember = m_pSquad->GetFirstMember(NULL);
  2626. m_pSquad->RemoveFromSquad( pFirstMember );
  2627. pFirstMember->ClearCommandGoal();
  2628. CNPC_Citizen *pFirstMemberCitizen = dynamic_cast< CNPC_Citizen * >( pFirstMember );
  2629. if ( pFirstMemberCitizen )
  2630. {
  2631. pFirstMemberCitizen->ClearFollowTarget();
  2632. }
  2633. else
  2634. {
  2635. CAI_FollowBehavior *pOldMemberFollowBehavior;
  2636. if ( pFirstMember->GetBehavior( &pOldMemberFollowBehavior ) )
  2637. {
  2638. pOldMemberFollowBehavior->SetFollowTarget( NULL );
  2639. }
  2640. }
  2641. }
  2642. ClearFollowTarget();
  2643. CAI_BaseNPC *pLeader = NULL;
  2644. AISquadIter_t iter;
  2645. for ( CAI_BaseNPC *pAllyNpc = m_pSquad->GetFirstMember(&iter); pAllyNpc; pAllyNpc = m_pSquad->GetNextMember(&iter) )
  2646. {
  2647. if ( pAllyNpc->IsCommandable() )
  2648. {
  2649. pLeader = pAllyNpc;
  2650. break;
  2651. }
  2652. }
  2653. if ( pLeader && pLeader != this )
  2654. {
  2655. const Vector &commandGoal = pLeader->GetCommandGoal();
  2656. if ( commandGoal != vec3_invalid )
  2657. {
  2658. SetCommandGoal( commandGoal );
  2659. SetCondition( COND_RECEIVED_ORDERS );
  2660. OnMoveOrder();
  2661. }
  2662. else
  2663. {
  2664. CAI_FollowBehavior *pLeaderFollowBehavior;
  2665. if ( pLeader->GetBehavior( &pLeaderFollowBehavior ) )
  2666. {
  2667. m_FollowBehavior.SetFollowTarget( pLeaderFollowBehavior->GetFollowTarget() );
  2668. m_FollowBehavior.SetParameters( m_FollowBehavior.GetFormation() );
  2669. }
  2670. }
  2671. }
  2672. else
  2673. {
  2674. m_FollowBehavior.SetFollowTarget( UTIL_GetLocalPlayer() );
  2675. m_FollowBehavior.SetParameters( AIF_SIMPLE );
  2676. }
  2677. }
  2678. //-----------------------------------------------------------------------------
  2679. //-----------------------------------------------------------------------------
  2680. void CNPC_Citizen::ClearFollowTarget()
  2681. {
  2682. m_FollowBehavior.SetFollowTarget( NULL );
  2683. m_FollowBehavior.SetParameters( AIF_SIMPLE );
  2684. }
  2685. //-----------------------------------------------------------------------------
  2686. //-----------------------------------------------------------------------------
  2687. void CNPC_Citizen::UpdateFollowCommandPoint()
  2688. {
  2689. if ( !AI_IsSinglePlayer() )
  2690. return;
  2691. if ( IsInPlayerSquad() )
  2692. {
  2693. if ( HaveCommandGoal() )
  2694. {
  2695. CBaseEntity *pFollowTarget = m_FollowBehavior.GetFollowTarget();
  2696. CBaseEntity *pCommandPoint = gEntList.FindEntityByClassname( NULL, COMMAND_POINT_CLASSNAME );
  2697. if( !pCommandPoint )
  2698. {
  2699. DevMsg("**\nVERY BAD THING\nCommand point vanished! Creating a new one\n**\n");
  2700. pCommandPoint = CreateEntityByName( COMMAND_POINT_CLASSNAME );
  2701. }
  2702. if ( pFollowTarget != pCommandPoint )
  2703. {
  2704. pFollowTarget = pCommandPoint;
  2705. m_FollowBehavior.SetFollowTarget( pFollowTarget );
  2706. m_FollowBehavior.SetParameters( AIF_COMMANDER );
  2707. }
  2708. if ( ( pCommandPoint->GetAbsOrigin() - GetCommandGoal() ).LengthSqr() > 0.01 )
  2709. {
  2710. UTIL_SetOrigin( pCommandPoint, GetCommandGoal(), false );
  2711. }
  2712. }
  2713. else
  2714. {
  2715. if ( IsFollowingCommandPoint() )
  2716. ClearFollowTarget();
  2717. if ( m_FollowBehavior.GetFollowTarget() != UTIL_GetLocalPlayer() )
  2718. {
  2719. DevMsg( "Expected to be following player, but not\n" );
  2720. m_FollowBehavior.SetFollowTarget( UTIL_GetLocalPlayer() );
  2721. m_FollowBehavior.SetParameters( AIF_SIMPLE );
  2722. }
  2723. }
  2724. }
  2725. else if ( IsFollowingCommandPoint() )
  2726. ClearFollowTarget();
  2727. }
  2728. //-----------------------------------------------------------------------------
  2729. //-----------------------------------------------------------------------------
  2730. bool CNPC_Citizen::IsFollowingCommandPoint()
  2731. {
  2732. CBaseEntity *pFollowTarget = m_FollowBehavior.GetFollowTarget();
  2733. if ( pFollowTarget )
  2734. return FClassnameIs( pFollowTarget, COMMAND_POINT_CLASSNAME );
  2735. return false;
  2736. }
  2737. //-----------------------------------------------------------------------------
  2738. //-----------------------------------------------------------------------------
  2739. struct SquadMemberInfo_t
  2740. {
  2741. CNPC_Citizen * pMember;
  2742. bool bSeesPlayer;
  2743. float distSq;
  2744. };
  2745. int __cdecl SquadSortFunc( const SquadMemberInfo_t *pLeft, const SquadMemberInfo_t *pRight )
  2746. {
  2747. if ( pLeft->bSeesPlayer && !pRight->bSeesPlayer )
  2748. {
  2749. return -1;
  2750. }
  2751. if ( !pLeft->bSeesPlayer && pRight->bSeesPlayer )
  2752. {
  2753. return 1;
  2754. }
  2755. return ( pLeft->distSq - pRight->distSq );
  2756. }
  2757. CAI_BaseNPC *CNPC_Citizen::GetSquadCommandRepresentative()
  2758. {
  2759. if ( !AI_IsSinglePlayer() )
  2760. return NULL;
  2761. if ( IsInPlayerSquad() )
  2762. {
  2763. static float lastTime;
  2764. static AIHANDLE hCurrent;
  2765. if ( gpGlobals->curtime - lastTime > 2.0 || !hCurrent || !hCurrent->IsInPlayerSquad() ) // hCurrent will be NULL after level change
  2766. {
  2767. lastTime = gpGlobals->curtime;
  2768. hCurrent = NULL;
  2769. CUtlVectorFixed<SquadMemberInfo_t, MAX_SQUAD_MEMBERS> candidates;
  2770. CBasePlayer *pPlayer = UTIL_GetLocalPlayer();
  2771. if ( pPlayer )
  2772. {
  2773. AISquadIter_t iter;
  2774. for ( CAI_BaseNPC *pAllyNpc = m_pSquad->GetFirstMember(&iter); pAllyNpc; pAllyNpc = m_pSquad->GetNextMember(&iter) )
  2775. {
  2776. if ( pAllyNpc->IsCommandable() && dynamic_cast<CNPC_Citizen *>(pAllyNpc) )
  2777. {
  2778. int i = candidates.AddToTail();
  2779. candidates[i].pMember = (CNPC_Citizen *)(pAllyNpc);
  2780. candidates[i].bSeesPlayer = pAllyNpc->HasCondition( COND_SEE_PLAYER );
  2781. candidates[i].distSq = ( pAllyNpc->GetAbsOrigin() - pPlayer->GetAbsOrigin() ).LengthSqr();
  2782. }
  2783. }
  2784. if ( candidates.Count() > 0 )
  2785. {
  2786. candidates.Sort( SquadSortFunc );
  2787. hCurrent = candidates[0].pMember;
  2788. }
  2789. }
  2790. }
  2791. if ( hCurrent != NULL )
  2792. {
  2793. Assert( dynamic_cast<CNPC_Citizen *>(hCurrent.Get()) && hCurrent->IsInPlayerSquad() );
  2794. return hCurrent;
  2795. }
  2796. }
  2797. return NULL;
  2798. }
  2799. //-----------------------------------------------------------------------------
  2800. //-----------------------------------------------------------------------------
  2801. void CNPC_Citizen::SetSquad( CAI_Squad *pSquad )
  2802. {
  2803. bool bWasInPlayerSquad = IsInPlayerSquad();
  2804. BaseClass::SetSquad( pSquad );
  2805. if( IsInPlayerSquad() && !bWasInPlayerSquad )
  2806. {
  2807. m_OnJoinedPlayerSquad.FireOutput(this, this);
  2808. if ( npc_citizen_insignia.GetBool() )
  2809. AddInsignia();
  2810. }
  2811. else if ( !IsInPlayerSquad() && bWasInPlayerSquad )
  2812. {
  2813. if ( npc_citizen_insignia.GetBool() )
  2814. RemoveInsignia();
  2815. m_OnLeftPlayerSquad.FireOutput(this, this);
  2816. }
  2817. }
  2818. //-----------------------------------------------------------------------------
  2819. // Purpose: This is a generic function (to be implemented by sub-classes) to
  2820. // handle specific interactions between different types of characters
  2821. // (For example the barnacle grabbing an NPC)
  2822. // Input : Constant for the type of interaction
  2823. // Output : true - if sub-class has a response for the interaction
  2824. // false - if sub-class has no response
  2825. //-----------------------------------------------------------------------------
  2826. bool CNPC_Citizen::HandleInteraction(int interactionType, void *data, CBaseCombatCharacter* sourceEnt)
  2827. {
  2828. // TODO: As citizen gets more complex, we will have to only allow
  2829. // these interruptions to happen from certain schedules
  2830. if (interactionType == g_interactionScannerInspect)
  2831. {
  2832. if ( gpGlobals->curtime > m_fNextInspectTime )
  2833. {
  2834. //SetLookTarget(sourceEnt);
  2835. // Don't let anyone else pick me for a couple seconds
  2836. SetNextScannerInspectTime( gpGlobals->curtime + 5.0 );
  2837. return true;
  2838. }
  2839. return false;
  2840. }
  2841. else if (interactionType == g_interactionScannerInspectBegin)
  2842. {
  2843. // Don't inspect me again for a while
  2844. SetNextScannerInspectTime( gpGlobals->curtime + CIT_INSPECTED_DELAY_TIME );
  2845. Vector targetDir = ( sourceEnt->WorldSpaceCenter() - WorldSpaceCenter() );
  2846. VectorNormalize( targetDir );
  2847. // If we're hit from behind, startle
  2848. if ( DotProduct( targetDir, BodyDirection3D() ) < 0 )
  2849. {
  2850. m_nInspectActivity = ACT_CIT_STARTLED;
  2851. }
  2852. else
  2853. {
  2854. // Otherwise we're blinded by the flash
  2855. m_nInspectActivity = ACT_CIT_BLINDED;
  2856. }
  2857. SetCondition( COND_CIT_START_INSPECTION );
  2858. return true;
  2859. }
  2860. else if (interactionType == g_interactionScannerInspectHandsUp)
  2861. {
  2862. m_nInspectActivity = ACT_CIT_HANDSUP;
  2863. SetSchedule(SCHED_CITIZEN_PLAY_INSPECT_ACTIVITY);
  2864. return true;
  2865. }
  2866. else if (interactionType == g_interactionScannerInspectShowArmband)
  2867. {
  2868. m_nInspectActivity = ACT_CIT_SHOWARMBAND;
  2869. SetSchedule(SCHED_CITIZEN_PLAY_INSPECT_ACTIVITY);
  2870. return true;
  2871. }
  2872. else if (interactionType == g_interactionScannerInspectDone)
  2873. {
  2874. SetSchedule(SCHED_IDLE_WANDER);
  2875. return true;
  2876. }
  2877. else if (interactionType == g_interactionHitByPlayerThrownPhysObj )
  2878. {
  2879. if ( IsOkToSpeakInResponseToPlayer() )
  2880. {
  2881. Speak( TLK_PLYR_PHYSATK );
  2882. }
  2883. return true;
  2884. }
  2885. return BaseClass::HandleInteraction( interactionType, data, sourceEnt );
  2886. }
  2887. //-----------------------------------------------------------------------------
  2888. //-----------------------------------------------------------------------------
  2889. bool CNPC_Citizen::FValidateHintType( CAI_Hint *pHint )
  2890. {
  2891. switch( pHint->HintType() )
  2892. {
  2893. case HINT_WORLD_VISUALLY_INTERESTING:
  2894. return true;
  2895. break;
  2896. default:
  2897. break;
  2898. }
  2899. return BaseClass::FValidateHintType( pHint );
  2900. }
  2901. //-----------------------------------------------------------------------------
  2902. //-----------------------------------------------------------------------------
  2903. bool CNPC_Citizen::CanHeal()
  2904. {
  2905. if ( !IsMedic() && !IsAmmoResupplier() )
  2906. return false;
  2907. if( !hl2_episodic.GetBool() )
  2908. {
  2909. // If I'm not armed, my priority should be to arm myself.
  2910. if ( IsMedic() && !GetActiveWeapon() )
  2911. return false;
  2912. }
  2913. if ( IsInAScript() || (m_NPCState == NPC_STATE_SCRIPT) )
  2914. return false;
  2915. return true;
  2916. }
  2917. //-----------------------------------------------------------------------------
  2918. //-----------------------------------------------------------------------------
  2919. bool CNPC_Citizen::ShouldHealTarget( CBaseEntity *pTarget, bool bActiveUse )
  2920. {
  2921. Disposition_t disposition;
  2922. if ( !pTarget && ( ( disposition = IRelationType( pTarget ) ) != D_LI && disposition != D_NU ) )
  2923. return false;
  2924. // Don't heal if I'm in the middle of talking
  2925. if ( IsSpeaking() )
  2926. return false;
  2927. bool bTargetIsPlayer = pTarget->IsPlayer();
  2928. // Don't heal or give ammo to targets in vehicles
  2929. CBaseCombatCharacter *pCCTarget = pTarget->MyCombatCharacterPointer();
  2930. if ( pCCTarget != NULL && pCCTarget->IsInAVehicle() )
  2931. return false;
  2932. if ( IsMedic() )
  2933. {
  2934. Vector toPlayer = ( pTarget->GetAbsOrigin() - GetAbsOrigin() );
  2935. if (( bActiveUse || !HaveCommandGoal() || toPlayer.Length() < HEAL_TARGET_RANGE)
  2936. #ifdef HL2_EPISODIC
  2937. && fabs(toPlayer.z) < HEAL_TARGET_RANGE_Z
  2938. #endif
  2939. )
  2940. {
  2941. if ( pTarget->m_iHealth > 0 )
  2942. {
  2943. if ( bActiveUse )
  2944. {
  2945. // Ignore heal requests if we're going to heal a tiny amount
  2946. float timeFullHeal = m_flPlayerHealTime;
  2947. float timeRecharge = sk_citizen_heal_player_delay.GetFloat();
  2948. float maximumHealAmount = sk_citizen_heal_player.GetFloat();
  2949. float healAmt = ( maximumHealAmount * ( 1.0 - ( timeFullHeal - gpGlobals->curtime ) / timeRecharge ) );
  2950. if ( healAmt > pTarget->m_iMaxHealth - pTarget->m_iHealth )
  2951. healAmt = pTarget->m_iMaxHealth - pTarget->m_iHealth;
  2952. if ( healAmt < sk_citizen_heal_player_min_forced.GetFloat() )
  2953. return false;
  2954. return ( pTarget->m_iMaxHealth > pTarget->m_iHealth );
  2955. }
  2956. // Are we ready to heal again?
  2957. bool bReadyToHeal = ( ( bTargetIsPlayer && m_flPlayerHealTime <= gpGlobals->curtime ) ||
  2958. ( !bTargetIsPlayer && m_flAllyHealTime <= gpGlobals->curtime ) );
  2959. // Only heal if we're ready
  2960. if ( bReadyToHeal )
  2961. {
  2962. int requiredHealth;
  2963. if ( bTargetIsPlayer )
  2964. requiredHealth = pTarget->GetMaxHealth() - sk_citizen_heal_player.GetFloat();
  2965. else
  2966. requiredHealth = pTarget->GetMaxHealth() * sk_citizen_heal_player_min_pct.GetFloat();
  2967. if ( ( pTarget->m_iHealth <= requiredHealth ) && IRelationType( pTarget ) == D_LI )
  2968. return true;
  2969. }
  2970. }
  2971. }
  2972. }
  2973. // Only players need ammo
  2974. if ( IsAmmoResupplier() && bTargetIsPlayer )
  2975. {
  2976. if ( m_flPlayerGiveAmmoTime <= gpGlobals->curtime )
  2977. {
  2978. int iAmmoType = GetAmmoDef()->Index( STRING(m_iszAmmoSupply) );
  2979. if ( iAmmoType == -1 )
  2980. {
  2981. DevMsg("ERROR: Citizen attempting to give unknown ammo type (%s)\n", STRING(m_iszAmmoSupply) );
  2982. }
  2983. else
  2984. {
  2985. // Does the player need the ammo we can give him?
  2986. int iMax = GetAmmoDef()->MaxCarry(iAmmoType);
  2987. int iCount = ((CBasePlayer*)pTarget)->GetAmmoCount(iAmmoType);
  2988. if ( !iCount || ((iMax - iCount) >= m_iAmmoAmount) )
  2989. {
  2990. // Only give the player ammo if he has a weapon that uses it
  2991. if ( ((CBasePlayer*)pTarget)->Weapon_GetWpnForAmmo( iAmmoType ) )
  2992. return true;
  2993. }
  2994. }
  2995. }
  2996. }
  2997. return false;
  2998. }
  2999. #ifdef HL2_EPISODIC
  3000. //-----------------------------------------------------------------------------
  3001. // Determine if the citizen is in a position to be throwing medkits
  3002. //-----------------------------------------------------------------------------
  3003. bool CNPC_Citizen::ShouldHealTossTarget( CBaseEntity *pTarget, bool bActiveUse )
  3004. {
  3005. Disposition_t disposition;
  3006. Assert( IsMedic() );
  3007. if ( !IsMedic() )
  3008. return false;
  3009. if ( !pTarget && ( ( disposition = IRelationType( pTarget ) ) != D_LI && disposition != D_NU ) )
  3010. return false;
  3011. // Don't heal if I'm in the middle of talking
  3012. if ( IsSpeaking() )
  3013. return false;
  3014. bool bTargetIsPlayer = pTarget->IsPlayer();
  3015. // Don't heal or give ammo to targets in vehicles
  3016. CBaseCombatCharacter *pCCTarget = pTarget->MyCombatCharacterPointer();
  3017. if ( pCCTarget != NULL && pCCTarget->IsInAVehicle() )
  3018. return false;
  3019. Vector toPlayer = ( pTarget->GetAbsOrigin() - GetAbsOrigin() );
  3020. if ( bActiveUse || !HaveCommandGoal() || toPlayer.Length() < HEAL_TOSS_TARGET_RANGE )
  3021. {
  3022. if ( pTarget->m_iHealth > 0 )
  3023. {
  3024. if ( bActiveUse )
  3025. {
  3026. // Ignore heal requests if we're going to heal a tiny amount
  3027. float timeFullHeal = m_flPlayerHealTime;
  3028. float timeRecharge = sk_citizen_heal_player_delay.GetFloat();
  3029. float maximumHealAmount = sk_citizen_heal_player.GetFloat();
  3030. float healAmt = ( maximumHealAmount * ( 1.0 - ( timeFullHeal - gpGlobals->curtime ) / timeRecharge ) );
  3031. if ( healAmt > pTarget->m_iMaxHealth - pTarget->m_iHealth )
  3032. healAmt = pTarget->m_iMaxHealth - pTarget->m_iHealth;
  3033. if ( healAmt < sk_citizen_heal_player_min_forced.GetFloat() )
  3034. return false;
  3035. return ( pTarget->m_iMaxHealth > pTarget->m_iHealth );
  3036. }
  3037. // Are we ready to heal again?
  3038. bool bReadyToHeal = ( ( bTargetIsPlayer && m_flPlayerHealTime <= gpGlobals->curtime ) ||
  3039. ( !bTargetIsPlayer && m_flAllyHealTime <= gpGlobals->curtime ) );
  3040. // Only heal if we're ready
  3041. if ( bReadyToHeal )
  3042. {
  3043. int requiredHealth;
  3044. if ( bTargetIsPlayer )
  3045. requiredHealth = pTarget->GetMaxHealth() - sk_citizen_heal_player.GetFloat();
  3046. else
  3047. requiredHealth = pTarget->GetMaxHealth() * sk_citizen_heal_player_min_pct.GetFloat();
  3048. if ( ( pTarget->m_iHealth <= requiredHealth ) && IRelationType( pTarget ) == D_LI )
  3049. return true;
  3050. }
  3051. }
  3052. }
  3053. return false;
  3054. }
  3055. #endif
  3056. //-----------------------------------------------------------------------------
  3057. //-----------------------------------------------------------------------------
  3058. void CNPC_Citizen::Heal()
  3059. {
  3060. if ( !CanHeal() )
  3061. return;
  3062. CBaseEntity *pTarget = GetTarget();
  3063. Vector target = pTarget->GetAbsOrigin() - GetAbsOrigin();
  3064. if ( target.Length() > HEAL_TARGET_RANGE * 2 )
  3065. return;
  3066. // Don't heal a player that's staring at you until a few seconds have passed.
  3067. m_flTimeNextHealStare = gpGlobals->curtime + sk_citizen_stare_heal_time.GetFloat();
  3068. if ( IsMedic() )
  3069. {
  3070. float timeFullHeal;
  3071. float timeRecharge;
  3072. float maximumHealAmount;
  3073. if ( pTarget->IsPlayer() )
  3074. {
  3075. timeFullHeal = m_flPlayerHealTime;
  3076. timeRecharge = sk_citizen_heal_player_delay.GetFloat();
  3077. maximumHealAmount = sk_citizen_heal_player.GetFloat();
  3078. m_flPlayerHealTime = gpGlobals->curtime + timeRecharge;
  3079. }
  3080. else
  3081. {
  3082. timeFullHeal = m_flAllyHealTime;
  3083. timeRecharge = sk_citizen_heal_ally_delay.GetFloat();
  3084. maximumHealAmount = sk_citizen_heal_ally.GetFloat();
  3085. m_flAllyHealTime = gpGlobals->curtime + timeRecharge;
  3086. }
  3087. float healAmt = ( maximumHealAmount * ( 1.0 - ( timeFullHeal - gpGlobals->curtime ) / timeRecharge ) );
  3088. if ( healAmt > maximumHealAmount )
  3089. healAmt = maximumHealAmount;
  3090. else
  3091. healAmt = RoundFloatToInt( healAmt );
  3092. if ( healAmt > 0 )
  3093. {
  3094. if ( pTarget->IsPlayer() && npc_citizen_medic_emit_sound.GetBool() )
  3095. {
  3096. CPASAttenuationFilter filter( pTarget, "HealthKit.Touch" );
  3097. EmitSound( filter, pTarget->entindex(), "HealthKit.Touch" );
  3098. }
  3099. pTarget->TakeHealth( healAmt, DMG_GENERIC );
  3100. pTarget->RemoveAllDecals();
  3101. }
  3102. }
  3103. if ( IsAmmoResupplier() )
  3104. {
  3105. // Non-players don't use ammo
  3106. if ( pTarget->IsPlayer() )
  3107. {
  3108. int iAmmoType = GetAmmoDef()->Index( STRING(m_iszAmmoSupply) );
  3109. if ( iAmmoType == -1 )
  3110. {
  3111. DevMsg("ERROR: Citizen attempting to give unknown ammo type (%s)\n", STRING(m_iszAmmoSupply) );
  3112. }
  3113. else
  3114. {
  3115. ((CBasePlayer*)pTarget)->GiveAmmo( m_iAmmoAmount, iAmmoType, false );
  3116. }
  3117. m_flPlayerGiveAmmoTime = gpGlobals->curtime + sk_citizen_giveammo_player_delay.GetFloat();
  3118. }
  3119. }
  3120. }
  3121. #if HL2_EPISODIC
  3122. //-----------------------------------------------------------------------------
  3123. // Like Heal(), but tosses a healthkit in front of the player rather than just juicing him up.
  3124. //-----------------------------------------------------------------------------
  3125. void CNPC_Citizen::TossHealthKit(CBaseCombatCharacter *pThrowAt, const Vector &offset)
  3126. {
  3127. Assert( pThrowAt );
  3128. Vector forward, right, up;
  3129. GetVectors( &forward, &right, &up );
  3130. Vector medKitOriginPoint = WorldSpaceCenter() + ( forward * 20.0f );
  3131. Vector destinationPoint;
  3132. // this doesn't work without a moveparent: pThrowAt->ComputeAbsPosition( offset, &destinationPoint );
  3133. VectorTransform( offset, pThrowAt->EntityToWorldTransform(), destinationPoint );
  3134. // flatten out any z change due to player looking up/down
  3135. destinationPoint.z = pThrowAt->EyePosition().z;
  3136. Vector tossVelocity;
  3137. if (npc_citizen_medic_throw_style.GetInt() == 0)
  3138. {
  3139. CTraceFilterSkipTwoEntities tracefilter( this, pThrowAt, COLLISION_GROUP_NONE );
  3140. tossVelocity = VecCheckToss( this, &tracefilter, medKitOriginPoint, destinationPoint, 0.233f, 1.0f, false );
  3141. }
  3142. else
  3143. {
  3144. tossVelocity = VecCheckThrow( this, medKitOriginPoint, destinationPoint, MEDIC_THROW_SPEED, 1.0f );
  3145. if (vec3_origin == tossVelocity)
  3146. {
  3147. // if out of range, just throw it as close as I can
  3148. tossVelocity = destinationPoint - medKitOriginPoint;
  3149. // rotate upwards against gravity
  3150. float len = VectorLength(tossVelocity);
  3151. tossVelocity *= (MEDIC_THROW_SPEED / len);
  3152. tossVelocity.z += 0.57735026918962576450914878050196 * MEDIC_THROW_SPEED;
  3153. }
  3154. }
  3155. // create a healthkit and toss it into the world
  3156. CBaseEntity *pHealthKit = CreateEntityByName( "item_healthkit" );
  3157. Assert(pHealthKit);
  3158. if (pHealthKit)
  3159. {
  3160. pHealthKit->SetAbsOrigin( medKitOriginPoint );
  3161. pHealthKit->SetOwnerEntity( this );
  3162. // pHealthKit->SetAbsVelocity( tossVelocity );
  3163. DispatchSpawn( pHealthKit );
  3164. {
  3165. IPhysicsObject *pPhysicsObject = pHealthKit->VPhysicsGetObject();
  3166. Assert( pPhysicsObject );
  3167. if ( pPhysicsObject )
  3168. {
  3169. unsigned int cointoss = random->RandomInt(0,0xFF); // int bits used for bools
  3170. // some random precession
  3171. Vector angDummy(random->RandomFloat(-200,200), random->RandomFloat(-200,200),
  3172. cointoss & 0x01 ? random->RandomFloat(200,600) : -1.0f * random->RandomFloat(200,600));
  3173. pPhysicsObject->SetVelocity( &tossVelocity, &angDummy );
  3174. }
  3175. }
  3176. }
  3177. else
  3178. {
  3179. Warning("Citizen tried to heal but could not spawn item_healthkit!\n");
  3180. }
  3181. }
  3182. //-----------------------------------------------------------------------------
  3183. // cause an immediate call to TossHealthKit with some default numbers
  3184. //-----------------------------------------------------------------------------
  3185. void CNPC_Citizen::InputForceHealthKitToss( inputdata_t &inputdata )
  3186. {
  3187. TossHealthKit( UTIL_GetLocalPlayer(), Vector(48.0f, 0.0f, 0.0f) );
  3188. }
  3189. #endif
  3190. //-----------------------------------------------------------------------------
  3191. //-----------------------------------------------------------------------------
  3192. bool CNPC_Citizen::ShouldLookForHealthItem()
  3193. {
  3194. // Definitely do not take health if not in the player's squad.
  3195. if( !IsInPlayerSquad() )
  3196. return false;
  3197. if( gpGlobals->curtime < m_flNextHealthSearchTime )
  3198. return false;
  3199. // I'm fully healthy.
  3200. if( GetHealth() >= GetMaxHealth() )
  3201. return false;
  3202. // Player is hurt, don't steal his health.
  3203. if( AI_IsSinglePlayer() && UTIL_GetLocalPlayer()->GetHealth() <= UTIL_GetLocalPlayer()->GetHealth() * 0.75f )
  3204. return false;
  3205. // Wait till you're standing still.
  3206. if( IsMoving() )
  3207. return false;
  3208. return true;
  3209. }
  3210. //-----------------------------------------------------------------------------
  3211. //-----------------------------------------------------------------------------
  3212. void CNPC_Citizen::InputStartPatrolling( inputdata_t &inputdata )
  3213. {
  3214. m_bShouldPatrol = true;
  3215. }
  3216. //-----------------------------------------------------------------------------
  3217. //-----------------------------------------------------------------------------
  3218. void CNPC_Citizen::InputStopPatrolling( inputdata_t &inputdata )
  3219. {
  3220. m_bShouldPatrol = false;
  3221. }
  3222. //------------------------------------------------------------------------------
  3223. //------------------------------------------------------------------------------
  3224. void CNPC_Citizen::OnGivenWeapon( CBaseCombatWeapon *pNewWeapon )
  3225. {
  3226. FixupMattWeapon();
  3227. }
  3228. //------------------------------------------------------------------------------
  3229. //------------------------------------------------------------------------------
  3230. void CNPC_Citizen::InputSetCommandable( inputdata_t &inputdata )
  3231. {
  3232. RemoveSpawnFlags( SF_CITIZEN_NOT_COMMANDABLE );
  3233. gm_PlayerSquadEvaluateTimer.Force();
  3234. }
  3235. //-----------------------------------------------------------------------------
  3236. // Purpose:
  3237. // Input : &inputdata -
  3238. //-----------------------------------------------------------------------------
  3239. void CNPC_Citizen::InputSetMedicOn( inputdata_t &inputdata )
  3240. {
  3241. AddSpawnFlags( SF_CITIZEN_MEDIC );
  3242. }
  3243. //-----------------------------------------------------------------------------
  3244. // Purpose:
  3245. // Input : &inputdata -
  3246. //-----------------------------------------------------------------------------
  3247. void CNPC_Citizen::InputSetMedicOff( inputdata_t &inputdata )
  3248. {
  3249. RemoveSpawnFlags( SF_CITIZEN_MEDIC );
  3250. }
  3251. //-----------------------------------------------------------------------------
  3252. // Purpose:
  3253. // Input : &inputdata -
  3254. //-----------------------------------------------------------------------------
  3255. void CNPC_Citizen::InputSetAmmoResupplierOn( inputdata_t &inputdata )
  3256. {
  3257. AddSpawnFlags( SF_CITIZEN_AMMORESUPPLIER );
  3258. }
  3259. //-----------------------------------------------------------------------------
  3260. // Purpose:
  3261. // Input : &inputdata -
  3262. //-----------------------------------------------------------------------------
  3263. void CNPC_Citizen::InputSetAmmoResupplierOff( inputdata_t &inputdata )
  3264. {
  3265. RemoveSpawnFlags( SF_CITIZEN_AMMORESUPPLIER );
  3266. }
  3267. //------------------------------------------------------------------------------
  3268. //------------------------------------------------------------------------------
  3269. void CNPC_Citizen::InputSpeakIdleResponse( inputdata_t &inputdata )
  3270. {
  3271. SpeakIfAllowed( TLK_ANSWER, NULL, true );
  3272. }
  3273. //-----------------------------------------------------------------------------
  3274. //-----------------------------------------------------------------------------
  3275. void CNPC_Citizen::DeathSound( const CTakeDamageInfo &info )
  3276. {
  3277. // Sentences don't play on dead NPCs
  3278. SentenceStop();
  3279. EmitSound( "NPC_Citizen.Die" );
  3280. }
  3281. //------------------------------------------------------------------------------
  3282. //------------------------------------------------------------------------------
  3283. void CNPC_Citizen::FearSound( void )
  3284. {
  3285. }
  3286. //-----------------------------------------------------------------------------
  3287. // Purpose:
  3288. // Output : Returns true on success, false on failure.
  3289. //-----------------------------------------------------------------------------
  3290. bool CNPC_Citizen::UseSemaphore( void )
  3291. {
  3292. // Ignore semaphore if we're told to work outside it
  3293. if ( HasSpawnFlags(SF_CITIZEN_IGNORE_SEMAPHORE) )
  3294. return false;
  3295. return BaseClass::UseSemaphore();
  3296. }
  3297. //-----------------------------------------------------------------------------
  3298. //
  3299. // Schedules
  3300. //
  3301. //-----------------------------------------------------------------------------
  3302. AI_BEGIN_CUSTOM_NPC( npc_citizen, CNPC_Citizen )
  3303. DECLARE_TASK( TASK_CIT_HEAL )
  3304. DECLARE_TASK( TASK_CIT_RPG_AUGER )
  3305. DECLARE_TASK( TASK_CIT_PLAY_INSPECT_SEQUENCE )
  3306. DECLARE_TASK( TASK_CIT_SIT_ON_TRAIN )
  3307. DECLARE_TASK( TASK_CIT_LEAVE_TRAIN )
  3308. DECLARE_TASK( TASK_CIT_SPEAK_MOURNING )
  3309. #if HL2_EPISODIC
  3310. DECLARE_TASK( TASK_CIT_HEAL_TOSS )
  3311. #endif
  3312. DECLARE_ACTIVITY( ACT_CIT_HANDSUP )
  3313. DECLARE_ACTIVITY( ACT_CIT_BLINDED )
  3314. DECLARE_ACTIVITY( ACT_CIT_SHOWARMBAND )
  3315. DECLARE_ACTIVITY( ACT_CIT_HEAL )
  3316. DECLARE_ACTIVITY( ACT_CIT_STARTLED )
  3317. DECLARE_CONDITION( COND_CIT_PLAYERHEALREQUEST )
  3318. DECLARE_CONDITION( COND_CIT_COMMANDHEAL )
  3319. DECLARE_CONDITION( COND_CIT_START_INSPECTION )
  3320. //Events
  3321. DECLARE_ANIMEVENT( AE_CITIZEN_GET_PACKAGE )
  3322. DECLARE_ANIMEVENT( AE_CITIZEN_HEAL )
  3323. //=========================================================
  3324. // > SCHED_SCI_HEAL
  3325. //=========================================================
  3326. DEFINE_SCHEDULE
  3327. (
  3328. SCHED_CITIZEN_HEAL,
  3329. " Tasks"
  3330. " TASK_GET_PATH_TO_TARGET 0"
  3331. " TASK_MOVE_TO_TARGET_RANGE 50"
  3332. " TASK_STOP_MOVING 0"
  3333. " TASK_FACE_IDEAL 0"
  3334. // " TASK_SAY_HEAL 0"
  3335. // " TASK_PLAY_SEQUENCE_FACE_TARGET ACTIVITY:ACT_ARM"
  3336. " TASK_CIT_HEAL 0"
  3337. // " TASK_PLAY_SEQUENCE_FACE_TARGET ACTIVITY:ACT_DISARM"
  3338. " "
  3339. " Interrupts"
  3340. )
  3341. #if HL2_EPISODIC
  3342. //=========================================================
  3343. // > SCHED_CITIZEN_HEAL_TOSS
  3344. // this is for the episodic behavior where the citizen hurls the medkit
  3345. //=========================================================
  3346. DEFINE_SCHEDULE
  3347. (
  3348. SCHED_CITIZEN_HEAL_TOSS,
  3349. " Tasks"
  3350. // " TASK_GET_PATH_TO_TARGET 0"
  3351. // " TASK_MOVE_TO_TARGET_RANGE 50"
  3352. " TASK_STOP_MOVING 0"
  3353. " TASK_FACE_IDEAL 0"
  3354. // " TASK_SAY_HEAL 0"
  3355. // " TASK_PLAY_SEQUENCE_FACE_TARGET ACTIVITY:ACT_ARM"
  3356. " TASK_CIT_HEAL_TOSS 0"
  3357. // " TASK_PLAY_SEQUENCE_FACE_TARGET ACTIVITY:ACT_DISARM"
  3358. " "
  3359. " Interrupts"
  3360. )
  3361. #endif
  3362. //=========================================================
  3363. // > SCHED_CITIZEN_RANGE_ATTACK1_RPG
  3364. //=========================================================
  3365. DEFINE_SCHEDULE
  3366. (
  3367. SCHED_CITIZEN_RANGE_ATTACK1_RPG,
  3368. " Tasks"
  3369. " TASK_STOP_MOVING 0"
  3370. " TASK_FACE_ENEMY 0"
  3371. " TASK_ANNOUNCE_ATTACK 1" // 1 = primary attack
  3372. " TASK_RANGE_ATTACK1 0"
  3373. " TASK_CIT_RPG_AUGER 1"
  3374. ""
  3375. " Interrupts"
  3376. )
  3377. //=========================================================
  3378. // > SCHED_CITIZEN_RANGE_ATTACK1_RPG
  3379. //=========================================================
  3380. DEFINE_SCHEDULE
  3381. (
  3382. SCHED_CITIZEN_STRIDER_RANGE_ATTACK1_RPG,
  3383. " Tasks"
  3384. " TASK_STOP_MOVING 0"
  3385. " TASK_FACE_ENEMY 0"
  3386. " TASK_ANNOUNCE_ATTACK 1" // 1 = primary attack
  3387. " TASK_WAIT 1"
  3388. " TASK_RANGE_ATTACK1 0"
  3389. " TASK_CIT_RPG_AUGER 1"
  3390. " TASK_SET_SCHEDULE SCHEDULE:SCHED_TAKE_COVER_FROM_ENEMY"
  3391. ""
  3392. " Interrupts"
  3393. )
  3394. //=========================================================
  3395. // > SCHED_CITIZEN_PATROL
  3396. //=========================================================
  3397. DEFINE_SCHEDULE
  3398. (
  3399. SCHED_CITIZEN_PATROL,
  3400. " Tasks"
  3401. " TASK_STOP_MOVING 0"
  3402. " TASK_WANDER 901024" // 90 to 1024 units
  3403. " TASK_WALK_PATH 0"
  3404. " TASK_WAIT_FOR_MOVEMENT 0"
  3405. " TASK_STOP_MOVING 0"
  3406. " TASK_WAIT 3"
  3407. " TASK_WAIT_RANDOM 3"
  3408. " TASK_SET_SCHEDULE SCHEDULE:SCHED_CITIZEN_PATROL" // keep doing it
  3409. ""
  3410. " Interrupts"
  3411. " COND_ENEMY_DEAD"
  3412. " COND_LIGHT_DAMAGE"
  3413. " COND_HEAVY_DAMAGE"
  3414. " COND_HEAR_DANGER"
  3415. " COND_NEW_ENEMY"
  3416. )
  3417. DEFINE_SCHEDULE
  3418. (
  3419. SCHED_CITIZEN_MOURN_PLAYER,
  3420. " Tasks"
  3421. " TASK_GET_PATH_TO_PLAYER 0"
  3422. " TASK_RUN_PATH_WITHIN_DIST 180"
  3423. " TASK_WAIT_FOR_MOVEMENT 0"
  3424. " TASK_STOP_MOVING 0"
  3425. " TASK_TARGET_PLAYER 0"
  3426. " TASK_FACE_TARGET 0"
  3427. " TASK_CIT_SPEAK_MOURNING 0"
  3428. " TASK_SUGGEST_STATE STATE:IDLE"
  3429. ""
  3430. " Interrupts"
  3431. " COND_LIGHT_DAMAGE"
  3432. " COND_HEAVY_DAMAGE"
  3433. " COND_HEAR_DANGER"
  3434. " COND_NEW_ENEMY"
  3435. )
  3436. DEFINE_SCHEDULE
  3437. (
  3438. SCHED_CITIZEN_PLAY_INSPECT_ACTIVITY,
  3439. " Tasks"
  3440. " TASK_STOP_MOVING 0"
  3441. " TASK_CIT_PLAY_INSPECT_SEQUENCE 0" // Play the sequence the scanner requires
  3442. " TASK_WAIT 2"
  3443. ""
  3444. " Interrupts"
  3445. " "
  3446. )
  3447. DEFINE_SCHEDULE
  3448. (
  3449. SCHED_CITIZEN_SIT_ON_TRAIN,
  3450. " Tasks"
  3451. " TASK_CIT_SIT_ON_TRAIN 0"
  3452. " TASK_WAIT_RANDOM 1"
  3453. " TASK_CIT_LEAVE_TRAIN 0"
  3454. ""
  3455. " Interrupts"
  3456. )
  3457. AI_END_CUSTOM_NPC()
  3458. //==================================================================================================================
  3459. // CITIZEN PLAYER-RESPONSE SYSTEM
  3460. //
  3461. // NOTE: This system is obsolete, and left here for legacy support.
  3462. // It has been superseded by the ai_eventresponse system.
  3463. //
  3464. //==================================================================================================================
  3465. CHandle<CCitizenResponseSystem> g_pCitizenResponseSystem = NULL;
  3466. CCitizenResponseSystem *GetCitizenResponse()
  3467. {
  3468. return g_pCitizenResponseSystem;
  3469. }
  3470. char *CitizenResponseConcepts[MAX_CITIZEN_RESPONSES] =
  3471. {
  3472. "TLK_CITIZEN_RESPONSE_SHOT_GUNSHIP",
  3473. "TLK_CITIZEN_RESPONSE_KILLED_GUNSHIP",
  3474. "TLK_VITALNPC_DIED",
  3475. };
  3476. LINK_ENTITY_TO_CLASS( ai_citizen_response_system, CCitizenResponseSystem );
  3477. BEGIN_DATADESC( CCitizenResponseSystem )
  3478. DEFINE_ARRAY( m_flResponseAddedTime, FIELD_FLOAT, MAX_CITIZEN_RESPONSES ),
  3479. DEFINE_FIELD( m_flNextResponseTime, FIELD_FLOAT ),
  3480. DEFINE_INPUTFUNC( FIELD_VOID, "ResponseVitalNPC", InputResponseVitalNPC ),
  3481. DEFINE_THINKFUNC( ResponseThink ),
  3482. END_DATADESC()
  3483. //-----------------------------------------------------------------------------
  3484. //-----------------------------------------------------------------------------
  3485. void CCitizenResponseSystem::Spawn()
  3486. {
  3487. if ( g_pCitizenResponseSystem )
  3488. {
  3489. Warning("Multiple citizen response systems in level.\n");
  3490. UTIL_Remove( this );
  3491. return;
  3492. }
  3493. g_pCitizenResponseSystem = this;
  3494. // Invisible, non solid.
  3495. AddSolidFlags( FSOLID_NOT_SOLID );
  3496. AddEffects( EF_NODRAW );
  3497. SetThink( &CCitizenResponseSystem::ResponseThink );
  3498. m_flNextResponseTime = 0;
  3499. }
  3500. //-----------------------------------------------------------------------------
  3501. // Purpose:
  3502. //-----------------------------------------------------------------------------
  3503. void CCitizenResponseSystem::OnRestore()
  3504. {
  3505. BaseClass::OnRestore();
  3506. g_pCitizenResponseSystem = this;
  3507. }
  3508. //-----------------------------------------------------------------------------
  3509. // Purpose:
  3510. //-----------------------------------------------------------------------------
  3511. void CCitizenResponseSystem::AddResponseTrigger( citizenresponses_t iTrigger )
  3512. {
  3513. m_flResponseAddedTime[ iTrigger ] = gpGlobals->curtime;
  3514. SetNextThink( gpGlobals->curtime + 0.1 );
  3515. }
  3516. //-----------------------------------------------------------------------------
  3517. // Purpose:
  3518. //-----------------------------------------------------------------------------
  3519. void CCitizenResponseSystem::InputResponseVitalNPC( inputdata_t &inputdata )
  3520. {
  3521. AddResponseTrigger( CR_VITALNPC_DIED );
  3522. }
  3523. //-----------------------------------------------------------------------------
  3524. // Purpose:
  3525. //-----------------------------------------------------------------------------
  3526. void CCitizenResponseSystem::ResponseThink()
  3527. {
  3528. bool bStayActive = false;
  3529. if ( AI_IsSinglePlayer() )
  3530. {
  3531. for ( int i = 0; i < MAX_CITIZEN_RESPONSES; i++ )
  3532. {
  3533. if ( m_flResponseAddedTime[i] )
  3534. {
  3535. // Should it have expired by now?
  3536. if ( (m_flResponseAddedTime[i] + CITIZEN_RESPONSE_GIVEUP_TIME) < gpGlobals->curtime )
  3537. {
  3538. m_flResponseAddedTime[i] = 0;
  3539. }
  3540. else if ( m_flNextResponseTime < gpGlobals->curtime )
  3541. {
  3542. // Try and find the nearest citizen to the player
  3543. float flNearestDist = (CITIZEN_RESPONSE_DISTANCE * CITIZEN_RESPONSE_DISTANCE);
  3544. CBaseEntity *pNearestCitizen = NULL;
  3545. CBaseEntity *pCitizen = NULL;
  3546. CBasePlayer *pPlayer = UTIL_GetLocalPlayer();
  3547. while ( (pCitizen = gEntList.FindEntityByClassname( pCitizen, "npc_citizen" ) ) != NULL)
  3548. {
  3549. float flDistToPlayer = (pPlayer->WorldSpaceCenter() - pCitizen->WorldSpaceCenter()).LengthSqr();
  3550. if ( flDistToPlayer < flNearestDist )
  3551. {
  3552. flNearestDist = flDistToPlayer;
  3553. pNearestCitizen = pCitizen;
  3554. }
  3555. }
  3556. // Found one?
  3557. if ( pNearestCitizen && ((CNPC_Citizen*)pNearestCitizen)->RespondedTo( CitizenResponseConcepts[i], false, false ) )
  3558. {
  3559. m_flResponseAddedTime[i] = 0;
  3560. m_flNextResponseTime = gpGlobals->curtime + CITIZEN_RESPONSE_REFIRE_TIME;
  3561. // Don't issue multiple responses
  3562. break;
  3563. }
  3564. }
  3565. else
  3566. {
  3567. bStayActive = true;
  3568. }
  3569. }
  3570. }
  3571. }
  3572. // Do we need to keep thinking?
  3573. if ( bStayActive )
  3574. {
  3575. SetNextThink( gpGlobals->curtime + 0.1 );
  3576. }
  3577. }
  3578. void CNPC_Citizen::AddInsignia()
  3579. {
  3580. CBaseEntity *pMark = CreateEntityByName( "squadinsignia" );
  3581. pMark->SetOwnerEntity( this );
  3582. pMark->Spawn();
  3583. }
  3584. void CNPC_Citizen::RemoveInsignia()
  3585. {
  3586. CBaseEntity *pEntity = gEntList.FirstEnt();
  3587. while( pEntity )
  3588. {
  3589. if( pEntity->GetOwnerEntity() == this )
  3590. {
  3591. // Is this my insignia?
  3592. CSquadInsignia *pInsignia = dynamic_cast<CSquadInsignia *>(pEntity);
  3593. if( pInsignia )
  3594. {
  3595. UTIL_Remove( pInsignia );
  3596. return;
  3597. }
  3598. }
  3599. pEntity = gEntList.NextEnt( pEntity );
  3600. }
  3601. }
  3602. //-----------------------------------------------------------------------------
  3603. LINK_ENTITY_TO_CLASS( squadinsignia, CSquadInsignia );
  3604. void CSquadInsignia::Spawn()
  3605. {
  3606. CAI_BaseNPC *pOwner = ( GetOwnerEntity() ) ? GetOwnerEntity()->MyNPCPointer() : NULL;
  3607. if ( pOwner )
  3608. {
  3609. int attachment = pOwner->LookupAttachment( "eyes" );
  3610. if ( attachment )
  3611. {
  3612. SetAbsAngles( GetOwnerEntity()->GetAbsAngles() );
  3613. SetParent( GetOwnerEntity(), attachment );
  3614. Vector vecPosition;
  3615. vecPosition.Init( -2.5, 0, 3.9 );
  3616. SetLocalOrigin( vecPosition );
  3617. }
  3618. }
  3619. SetModel( INSIGNIA_MODEL );
  3620. SetSolid( SOLID_NONE );
  3621. }
  3622. //-----------------------------------------------------------------------------
  3623. // Purpose: Draw any debug text overlays
  3624. // Input :
  3625. // Output : Current text offset from the top
  3626. //-----------------------------------------------------------------------------
  3627. int CNPC_Citizen::DrawDebugTextOverlays( void )
  3628. {
  3629. int text_offset = BaseClass::DrawDebugTextOverlays();
  3630. if (m_debugOverlays & OVERLAY_TEXT_BIT)
  3631. {
  3632. char tempstr[512];
  3633. Q_snprintf(tempstr,sizeof(tempstr),"Expression type: %s", szExpressionTypes[m_ExpressionType]);
  3634. EntityText(text_offset,tempstr,0);
  3635. text_offset++;
  3636. }
  3637. return text_offset;
  3638. }