zombiebase.c 30 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150
  1. class ZombieBase extends DayZInfected
  2. {
  3. const float TARGET_CONE_ANGLE_CHASE = 20;
  4. const float TARGET_CONE_ANGLE_FIGHT = 30;
  5. const float ORIENTATION_SYNC_THRESHOLD = 30; //threshold for local heading/orientation sync
  6. const float SHOCK_TO_STUN_MULTIPLIER = 2.82;
  7. //! server / singleplayer properties
  8. protected int m_StanceVariation = 0;
  9. protected int m_LastMindState = -1;
  10. protected float m_LastMovementSpeed = -1;
  11. protected bool m_KnuckleLand = false;
  12. protected float m_KnuckleOutTimer = 0;
  13. protected int m_MindState = -1;
  14. protected int m_OrientationLocal = -1; //local 'companion' value for sync checking
  15. protected int m_OrientationSynced = -1;
  16. protected float m_OrientationTimer;
  17. protected float m_MovementSpeed = -1;
  18. protected vector m_DefaultHitPosition;
  19. protected float m_DeltaTime;
  20. protected AbstractWave m_LastSoundVoiceAW;
  21. protected ref InfectedSoundEventHandler m_InfectedSoundEventHandler;
  22. protected ref array<Object> m_AllTargetObjects;
  23. protected ref array<typename>m_TargetableObjects;
  24. //static ref map<int,ref array<string>> m_FinisherSelectionMap; //! which selections in the FireGeometry trigger which finisher on hit (when applicable)
  25. protected bool m_IsCrawling; //'DayZInfectedCommandCrawl' is transition to crawl only, 'DayZInfectedCommandMove' used after that, hence this VARIABLE_WET
  26. protected bool m_FinisherInProgress = false; //is this object being backstabbed?
  27. protected ref ArrowManagerBase m_ArrowManager;
  28. //-------------------------------------------------------------
  29. void ZombieBase()
  30. {
  31. Init();
  32. }
  33. void Init()
  34. {
  35. SetEventMask(EntityEvent.INIT);
  36. m_IsCrawling = false;
  37. RegisterNetSyncVariableInt("m_MindState", -1, 4);
  38. RegisterNetSyncVariableInt("m_OrientationSynced", 0, 359);
  39. RegisterNetSyncVariableFloat("m_MovementSpeed", -1, 3);
  40. RegisterNetSyncVariableBool("m_IsCrawling");
  41. //! sets default hit position and cache it here (mainly for impact particles)
  42. m_DefaultHitPosition = SetDefaultHitPosition(GetDayZInfectedType().GetDefaultHitPositionComponent());
  43. //! client only
  44. if ( !GetGame().IsDedicatedServer() )
  45. {
  46. m_LastSoundVoiceAW = null;
  47. m_InfectedSoundEventHandler = new InfectedSoundEventHandler(this);
  48. }
  49. m_AllTargetObjects = new array<Object>;
  50. m_TargetableObjects = new array<typename>;
  51. m_TargetableObjects.Insert(PlayerBase);
  52. m_TargetableObjects.Insert(AnimalBase);
  53. m_OrientationTimer = 0;
  54. m_ArrowManager = new ArrowManagerBase(this);
  55. }
  56. //! synced variable(s) handler
  57. override void OnVariablesSynchronized()
  58. {
  59. DebugSound("[Infected @ " + this + "][OnVariablesSynchronized]");
  60. HandleSoundEvents();
  61. if ( m_OrientationLocal != m_OrientationSynced )
  62. {
  63. m_OrientationLocal = m_OrientationSynced;
  64. }
  65. }
  66. //-------------------------------------------------------------
  67. override void EOnInit(IEntity other, int extra)
  68. {
  69. if ( !GetGame().IsMultiplayer() || GetGame().IsServer() )
  70. {
  71. m_StanceVariation = Math.RandomInt(0, 4);
  72. DayZInfectedCommandMove moveCommand = GetCommand_Move();
  73. moveCommand.SetStanceVariation(m_StanceVariation);
  74. }
  75. }
  76. override bool IsZombie()
  77. {
  78. return true;
  79. }
  80. override bool IsDanger()
  81. {
  82. return true;
  83. }
  84. override bool IsZombieMilitary()
  85. {
  86. return false;
  87. }
  88. bool IsMale()
  89. {
  90. return true;
  91. }
  92. override bool CanBeBackstabbed()
  93. {
  94. return true;
  95. }
  96. //-------------------------------------------------------------
  97. override AnimBootsType GetBootsType()
  98. {
  99. return AnimBootsType.Boots;
  100. }
  101. override bool CanBeSkinned()
  102. {
  103. return false;
  104. }
  105. //-------------------------------------------------------------
  106. override bool IsHealthVisible()
  107. {
  108. return false;
  109. }
  110. //-------------------------------------------------------------
  111. override bool IsRefresherSignalingViable()
  112. {
  113. return false;
  114. }
  115. override bool IsSelfAdjustingTemperature()
  116. {
  117. return IsAlive();
  118. }
  119. //! returns hit component for attacking AI
  120. override string GetHitComponentForAI()
  121. {
  122. return GetDayZInfectedType().GetHitComponentForAI();
  123. }
  124. //! returns default hit component (fallback)
  125. override string GetDefaultHitComponent()
  126. {
  127. return GetDayZInfectedType().GetDefaultHitComponent();
  128. }
  129. override vector GetDefaultHitPosition()
  130. {
  131. return m_DefaultHitPosition;
  132. }
  133. protected vector SetDefaultHitPosition(string pSelection)
  134. {
  135. return GetSelectionPositionMS(pSelection);
  136. }
  137. //! returns suitable hit components for finisher attacks; DEPRECATED
  138. override array<string> GetSuitableFinisherHitComponents()
  139. {
  140. return GetDayZInfectedType().GetSuitableFinisherHitComponents();
  141. }
  142. int GetMindStateSynced()
  143. {
  144. return m_MindState;
  145. }
  146. //! returns rounded zombie yaw for sync purposes
  147. int GetOrientationSynced()
  148. {
  149. return m_OrientationSynced;
  150. }
  151. //-------------------------------------------------------------
  152. //!
  153. //! CommandHandler
  154. //!
  155. void CommandHandler(float pDt, int pCurrentCommandID, bool pCurrentCommandFinished)
  156. {
  157. m_DeltaTime = pDt;
  158. //! for mods
  159. if ( ModCommandHandlerBefore(pDt, pCurrentCommandID, pCurrentCommandFinished) )
  160. {
  161. return;
  162. }
  163. //! handle death
  164. if ( pCurrentCommandID != DayZInfectedConstants.COMMANDID_DEATH )
  165. {
  166. if ( HandleDeath(pCurrentCommandID) )
  167. return;
  168. }
  169. else if (!pCurrentCommandFinished)
  170. {
  171. return;
  172. }
  173. //! movement handler (just for sync now)
  174. HandleMove(pCurrentCommandID);
  175. HandleOrientation(pDt,pCurrentCommandID);
  176. //! handle finished commands
  177. if (pCurrentCommandFinished)
  178. {
  179. //! default behaviour after finish is to start move
  180. DayZInfectedCommandMove moveCommand = StartCommand_Move();
  181. moveCommand.SetStanceVariation(m_StanceVariation);
  182. return;
  183. }
  184. //! for mods
  185. if ( ModCommandHandlerInside(pDt, pCurrentCommandID, pCurrentCommandFinished) )
  186. {
  187. return;
  188. }
  189. //! crawl transition
  190. if ( HandleCrawlTransition(pCurrentCommandID) )
  191. {
  192. return;
  193. }
  194. //! damage hits
  195. if ( HandleDamageHit(pCurrentCommandID) )
  196. {
  197. return;
  198. }
  199. DayZInfectedInputController inputController = GetInputController();
  200. if ( inputController )
  201. {
  202. if ( HandleVault(pCurrentCommandID, inputController, pDt) )
  203. {
  204. return;
  205. }
  206. if ( HandleMindStateChange(pCurrentCommandID, inputController, pDt) )
  207. {
  208. return;
  209. }
  210. if ( FightLogic(pCurrentCommandID, inputController, pDt) )
  211. {
  212. return;
  213. }
  214. }
  215. //!
  216. if ( ModCommandHandlerAfter(pDt, pCurrentCommandID, pCurrentCommandFinished) )
  217. {
  218. return;
  219. }
  220. }
  221. //-------------------------------------------------------------
  222. //!
  223. //! CommandHandlerDebug
  224. //!
  225. void CommandHandlerDebug(float pDt, int pCurrentCommandID, bool pCurrentCommandFinished)
  226. {
  227. if ( GetPluginManager() )
  228. {
  229. PluginDayZInfectedDebug infectedDebug = PluginDayZInfectedDebug.Cast(GetPluginManager().GetPluginByType(PluginDayZInfectedDebug));
  230. if ( infectedDebug )
  231. infectedDebug.CommandHandler(this);
  232. }
  233. }
  234. //-------------------------------------------------------------
  235. //!
  236. //! HandleMove
  237. //!
  238. void HandleMove(int pCurrentCommandID)
  239. {
  240. DayZInfectedInputController ic = GetInputController();
  241. m_MovementSpeed = ic.GetMovementSpeed();
  242. if (Math.AbsFloat(m_LastMovementSpeed - m_MovementSpeed) >= 0.9 && m_LastMovementSpeed != m_MovementSpeed)
  243. {
  244. SetSynchDirty();
  245. }
  246. m_LastMovementSpeed = m_MovementSpeed;
  247. }
  248. //-------------------------------------------------------------
  249. //!
  250. //! HandleOrientation
  251. //!
  252. void HandleOrientation(float pDt, int pCurrentCommandID)
  253. {
  254. m_OrientationTimer += pDt;
  255. int yaw = Math.Round(GetOrientation()[0]);
  256. yaw = Math.NormalizeAngle(yaw);
  257. //atan2(sin(x-y), cos(x-y))
  258. float angleSourceRad = m_OrientationSynced * Math.DEG2RAD;
  259. float angleTargetRad = yaw * Math.DEG2RAD;
  260. float angleDiffRad = Math.Atan2(Math.Sin(angleTargetRad - angleSourceRad), Math.Cos(angleSourceRad - angleTargetRad));
  261. angleDiffRad *= Math.RAD2DEG;
  262. angleDiffRad = Math.Round(angleDiffRad);
  263. if (m_OrientationTimer >= 2.0 || m_OrientationSynced == -1 || Math.AbsInt(angleDiffRad) > ORIENTATION_SYNC_THRESHOLD)
  264. {
  265. m_OrientationTimer = 0.0;
  266. if (m_OrientationSynced == -1 || Math.AbsInt(angleDiffRad) > 5)
  267. {
  268. m_OrientationSynced = yaw;
  269. SetSynchDirty();
  270. }
  271. }
  272. }
  273. //-------------------------------------------------------------
  274. //!
  275. //! HandleDeath
  276. //!
  277. float m_DamageHitDirection = 0;
  278. int m_DeathType = 0;
  279. bool HandleDeath(int pCurrentCommandID)
  280. {
  281. if ( !IsAlive() || m_FinisherInProgress )
  282. {
  283. StartCommand_Death(m_DeathType, m_DamageHitDirection);
  284. m_MovementSpeed = -1;
  285. m_MindState = -1;
  286. SetSynchDirty();
  287. return true;
  288. }
  289. return false;
  290. }
  291. bool EvaluateDeathAnimationEx(EntityAI pSource, ZombieHitData data, out int pAnimType, out float pAnimHitDir)
  292. {
  293. bool ret = EvaluateDeathAnimation(pSource,data.m_DamageZone,data.m_AmmoType,pAnimType,pAnimHitDir);
  294. return ret;
  295. }
  296. bool EvaluateDeathAnimation(EntityAI pSource, string pComponent, string pAmmoType, out int pAnimType, out float pAnimHitDir)
  297. {
  298. //!
  299. bool doPhxImpulse = GetGame().ConfigGetInt("cfgAmmo " + pAmmoType + " doPhxImpulse") > 0;
  300. //! anim type
  301. pAnimType = doPhxImpulse;
  302. //! direction
  303. pAnimHitDir = ComputeHitDirectionAngle(pSource);
  304. //! add some impulse if needed
  305. if ( doPhxImpulse )
  306. {
  307. vector impulse = 80 * m_TransportHitVelocity;
  308. impulse[1] = 80 * 1.5;
  309. //Print("Impulse: " + impulse.ToString());
  310. dBodyApplyImpulse(this, impulse);
  311. }
  312. return true;
  313. }
  314. //-------------------------------------------------------------
  315. //!
  316. //! HandleVault
  317. //!
  318. int m_ActiveVaultType = -1;
  319. int GetVaultType(float height)
  320. {
  321. if ( height <= 0.6 )
  322. return 0;
  323. else if ( height <= 1.1 )
  324. return 1;
  325. else if ( height <= 1.6 )
  326. return 2;
  327. else
  328. return 3;
  329. }
  330. bool HandleVault(int pCurrentCommandID, DayZInfectedInputController pInputController, float pDt)
  331. {
  332. if ( pCurrentCommandID == DayZInfectedConstants.COMMANDID_VAULT )
  333. {
  334. DayZInfectedCommandVault vaultCmd = GetCommand_Vault();
  335. if ( vaultCmd && vaultCmd.WasLand() )
  336. {
  337. m_KnuckleOutTimer = 0;
  338. m_KnuckleLand = true;
  339. }
  340. if ( m_KnuckleLand )
  341. {
  342. m_KnuckleOutTimer += pDt;
  343. if ( m_KnuckleOutTimer > 2.0 )
  344. StartCommand_Vault(-1);
  345. }
  346. return true;
  347. }
  348. if ( pInputController.IsVault() )
  349. {
  350. float vaultHeight = pInputController.GetVaultHeight();
  351. int vaultType = GetVaultType(vaultHeight);
  352. m_KnuckleLand = false;
  353. StartCommand_Vault(vaultType);
  354. return true;
  355. }
  356. return false;
  357. }
  358. //-------------------------------------------------------------
  359. //!
  360. //! Mind state change
  361. //!
  362. bool HandleMindStateChange(int pCurrentCommandID, DayZInfectedInputController pInputController, float pDt)
  363. {
  364. DayZInfectedCommandMove moveCommand = GetCommand_Move();
  365. m_MindState = pInputController.GetMindState();
  366. if ( m_LastMindState != m_MindState )
  367. {
  368. switch ( m_MindState )
  369. {
  370. case DayZInfectedConstants.MINDSTATE_CALM:
  371. if ( moveCommand && !moveCommand.IsTurning() )
  372. moveCommand.SetIdleState(0);
  373. break;
  374. case DayZInfectedConstants.MINDSTATE_DISTURBED:
  375. if ( moveCommand && !moveCommand.IsTurning() )
  376. moveCommand.SetIdleState(1);
  377. break;
  378. case DayZInfectedConstants.MINDSTATE_CHASE:
  379. if ( moveCommand && !moveCommand.IsTurning() && (m_LastMindState < DayZInfectedConstants.MINDSTATE_CHASE) )
  380. moveCommand.SetIdleState(2);
  381. break;
  382. }
  383. m_LastMindState = m_MindState;
  384. m_AttackCooldownTime = 0.0;
  385. SetSynchDirty();
  386. }
  387. return false;
  388. }
  389. //-------------------------------------------------------------
  390. //!
  391. //! Sound (client only)
  392. //!
  393. protected void HandleSoundEvents()
  394. {
  395. //! no sound handler - bail out
  396. if ( !m_InfectedSoundEventHandler )
  397. {
  398. return;
  399. }
  400. //! infected is dead
  401. if ( !IsAlive() )
  402. {
  403. //! stop all sounds
  404. m_InfectedSoundEventHandler.Stop();
  405. return;
  406. }
  407. switch ( m_MindState )
  408. {
  409. case DayZInfectedConstants.MINDSTATE_CALM:
  410. m_InfectedSoundEventHandler.PlayRequest(EInfectedSoundEventID.MINDSTATE_CALM_MOVE);
  411. break;
  412. case DayZInfectedConstants.MINDSTATE_ALERTED:
  413. m_InfectedSoundEventHandler.PlayRequest(EInfectedSoundEventID.MINDSTATE_ALERTED_MOVE);
  414. break;
  415. case DayZInfectedConstants.MINDSTATE_DISTURBED:
  416. m_InfectedSoundEventHandler.PlayRequest(EInfectedSoundEventID.MINDSTATE_DISTURBED_IDLE);
  417. break
  418. case DayZInfectedConstants.MINDSTATE_CHASE:
  419. m_InfectedSoundEventHandler.PlayRequest(EInfectedSoundEventID.MINDSTATE_CHASE_MOVE);
  420. break;
  421. default:
  422. m_InfectedSoundEventHandler.Stop();
  423. break;
  424. }
  425. DebugSound("[Infected @ " + this + "][MindState]" + typename.EnumToString(DayZInfectedConstants, m_MindState));
  426. DebugSound("[Infected @ " + this + "][SoundEventID]" + typename.EnumToString(EInfectedSoundEventID, m_InfectedSoundEventHandler.GetCurrentStateEventID()));
  427. }
  428. AbstractWave ProcessVoiceFX(string pSoundSetName)
  429. {
  430. SoundParams soundParams;
  431. SoundObjectBuilder soundObjectBuilder;
  432. SoundObject soundObject;
  433. if (!GetGame().IsDedicatedServer())
  434. {
  435. soundParams = new SoundParams( pSoundSetName );
  436. if ( !soundParams.IsValid() )
  437. {
  438. //SoundError("Invalid sound set.");
  439. return null;
  440. }
  441. soundObjectBuilder = new SoundObjectBuilder( soundParams );
  442. soundObject = soundObjectBuilder.BuildSoundObject();
  443. AttenuateSoundIfNecessary(soundObject);
  444. return PlaySound(soundObject, soundObjectBuilder);
  445. }
  446. return null;
  447. }
  448. override void OnSoundVoiceEvent(int event_id, string event_user_string)
  449. {
  450. //super.OnSoundVoiceEvent(event_id, event_user_string);
  451. AnimSoundVoiceEvent voice_event = GetCreatureAIType().GetSoundVoiceEvent(event_id);
  452. if (voice_event != null)
  453. {
  454. //! stop state sound when playing anim SoundVoice
  455. if (m_InfectedSoundEventHandler) // && m_InfectedSoundEventHandler.IsPlaying())
  456. {
  457. m_InfectedSoundEventHandler.Stop();
  458. DebugSound("[Infected @ " + this + "][SoundEvent] InfectedSoundEventHandler - stop all");
  459. }
  460. //! stop playing of old SoundVoice from anim (if any)
  461. if (m_LastSoundVoiceAW != null)
  462. {
  463. DebugSound("[Infected @ " + this + "][AnimVoiceEvent] Stopping LastAW");
  464. m_LastSoundVoiceAW.Stop();
  465. }
  466. //! play new SoundVoice from anim
  467. ProcessSoundVoiceEvent(voice_event, m_LastSoundVoiceAW);
  468. HandleSoundEvents();
  469. }
  470. }
  471. protected void ProcessSoundVoiceEvent(AnimSoundVoiceEvent sound_event, out AbstractWave aw)
  472. {
  473. if (!GetGame().IsDedicatedServer())
  474. {
  475. SoundObjectBuilder objectBuilder = sound_event.GetSoundBuilder();
  476. if (NULL != objectBuilder)
  477. {
  478. objectBuilder.AddEnvSoundVariables(GetPosition());
  479. SoundObject soundObject = objectBuilder.BuildSoundObject();
  480. AttenuateSoundIfNecessary(soundObject);
  481. aw = PlaySound(soundObject, objectBuilder);
  482. }
  483. }
  484. if (GetGame().IsServer())
  485. {
  486. if (sound_event.m_NoiseParams != NULL)
  487. GetGame().GetNoiseSystem().AddNoise(this, sound_event.m_NoiseParams, NoiseAIEvaluate.GetNoiseReduction(GetGame().GetWeather()));
  488. }
  489. }
  490. //-------------------------------------------------------------
  491. //!
  492. //! Combat
  493. //!
  494. EntityAI m_ActualTarget = null;
  495. float m_AttackCooldownTime = 0;
  496. DayZInfectedAttackType m_ActualAttackType = null;
  497. bool FightLogic(int pCurrentCommandID, DayZInfectedInputController pInputController, float pDt)
  498. {
  499. if (pCurrentCommandID == DayZInfectedConstants.COMMANDID_MOVE)
  500. {
  501. // we attack only in chase & fight state
  502. int mindState = pInputController.GetMindState();
  503. if (mindState == DayZInfectedConstants.MINDSTATE_CHASE)
  504. {
  505. return ChaseAttackLogic(pCurrentCommandID, pInputController, pDt);
  506. }
  507. else if (mindState == DayZInfectedConstants.MINDSTATE_FIGHT)
  508. {
  509. return FightAttackLogic(pCurrentCommandID, pInputController, pDt);
  510. }
  511. }
  512. else if (pCurrentCommandID == DayZInfectedConstants.COMMANDID_ATTACK)
  513. {
  514. DayZInfectedCommandAttack attackCommand = GetCommand_Attack();
  515. if (attackCommand && attackCommand.WasHit())
  516. {
  517. if (m_ActualTarget != null)
  518. {
  519. if (m_ActualTarget.GetMeleeTargetType() == EMeleeTargetType.NONALIGNABLE)
  520. return false;
  521. bool playerInBlockStance = false;
  522. vector targetPos = m_ActualTarget.GetPosition();
  523. vector hitPosWS = targetPos;
  524. vector zombiePos = GetPosition();
  525. PlayerBase playerTarget = PlayerBase.Cast(m_ActualTarget);
  526. if (playerTarget)
  527. {
  528. playerInBlockStance = playerTarget.GetMeleeFightLogic() && playerTarget.GetMeleeFightLogic().IsInBlock();
  529. }
  530. if (vector.DistanceSq(targetPos, zombiePos) <= m_ActualAttackType.m_Distance * m_ActualAttackType.m_Distance)
  531. {
  532. //! player is in block stance and facing the infected
  533. if (playerInBlockStance && (Math.RAD2DEG * Math.AbsFloat(Math3D.AngleFromPosition(targetPos, MiscGameplayFunctions.GetHeadingVector(playerTarget), zombiePos))) <= GameConstants.AI_MAX_BLOCKABLE_ANGLE)
  534. {
  535. //! infected is playing heavy attack - decrease the dmg to light
  536. if (m_ActualAttackType.m_IsHeavy == 1)
  537. {
  538. hitPosWS = m_ActualTarget.ModelToWorld(m_ActualTarget.GetDefaultHitPosition()); //! override hit pos by pos defined in type
  539. DamageSystem.CloseCombatDamageName(this, m_ActualTarget, m_ActualTarget.GetHitComponentForAI(), "MeleeZombie", hitPosWS);
  540. }
  541. else
  542. {
  543. //! infected is playing light attack - do not send damage, play animation instead
  544. hitPosWS = m_ActualTarget.ModelToWorld(m_ActualTarget.GetDefaultHitPosition());
  545. DamageSystem.CloseCombatDamageName(this, m_ActualTarget, m_ActualTarget.GetHitComponentForAI(), "Dummy_Light", hitPosWS);
  546. }
  547. }
  548. else
  549. {
  550. hitPosWS = m_ActualTarget.ModelToWorld(m_ActualTarget.GetDefaultHitPosition()); //! override hit pos by pos defined in type
  551. DamageSystem.CloseCombatDamageName(this, m_ActualTarget, m_ActualTarget.GetHitComponentForAI(), m_ActualAttackType.m_AmmoType, hitPosWS);
  552. }
  553. }
  554. }
  555. }
  556. return true;
  557. }
  558. return false;
  559. }
  560. bool ChaseAttackLogic(int pCurrentCommandID, DayZInfectedInputController pInputController, float pDt)
  561. {
  562. // always update target - it can be destroyed
  563. m_ActualTarget = pInputController.GetTargetEntity();
  564. //! do not attack players in vehicle - hotfix
  565. PlayerBase pb = PlayerBase.Cast(m_ActualTarget);
  566. if ( pb && pb.GetCommand_Vehicle() )
  567. {
  568. return false;
  569. }
  570. if ( m_ActualTarget == NULL )
  571. return false;
  572. vector targetPos = m_ActualTarget.GetPosition();
  573. if ( !CanAttackToPosition(targetPos) )
  574. return false;
  575. float targetDist = vector.Distance(targetPos, this.GetPosition());
  576. int pitch = GetAttackPitch(m_ActualTarget);
  577. m_ActualAttackType = GetDayZInfectedType().ChooseAttack(DayZInfectedAttackGroupType.CHASE, targetDist, pitch);
  578. if (m_ActualAttackType)
  579. {
  580. Object target = DayZPlayerUtils.GetMeleeTarget(this.GetPosition(), this.GetDirection(), TARGET_CONE_ANGLE_CHASE, m_ActualAttackType.m_Distance, -1.0, 2.0, this, m_TargetableObjects, m_AllTargetObjects);
  581. //! target is outside the targeting cone; skip attack
  582. if (m_ActualTarget != target)
  583. {
  584. m_AllTargetObjects.Clear();
  585. return false;
  586. }
  587. StartCommand_Attack(m_ActualTarget, m_ActualAttackType.m_Type, m_ActualAttackType.m_Subtype);
  588. m_AttackCooldownTime = m_ActualAttackType.m_Cooldown;
  589. return true;
  590. }
  591. return false;
  592. }
  593. bool FightAttackLogic(int pCurrentCommandID, DayZInfectedInputController pInputController, float pDt)
  594. {
  595. // always update target - it can be destroyed
  596. m_ActualTarget = pInputController.GetTargetEntity();
  597. //! do not attack players in vehicle - hotfix
  598. PlayerBase pb = PlayerBase.Cast(m_ActualTarget);
  599. if (pb && pb.GetCommand_Vehicle())
  600. return false;
  601. if (m_AttackCooldownTime > 0)
  602. {
  603. m_AttackCooldownTime -= pDt * GameConstants.AI_ATTACKSPEED;
  604. return false;
  605. }
  606. if (m_ActualTarget == null)
  607. return false;
  608. vector targetPos = m_ActualTarget.GetPosition();
  609. float targetDist = vector.Distance(targetPos, this.GetPosition());
  610. int pitch = GetAttackPitch(m_ActualTarget);
  611. if (!CanAttackToPosition(targetPos))
  612. return false;
  613. m_ActualAttackType = GetDayZInfectedType().ChooseAttack(DayZInfectedAttackGroupType.FIGHT, targetDist, pitch);
  614. if (m_ActualAttackType)
  615. {
  616. Object target = DayZPlayerUtils.GetMeleeTarget(this.GetPosition(), this.GetDirection(), TARGET_CONE_ANGLE_FIGHT, m_ActualAttackType.m_Distance, -1.0, 2.0, this, m_TargetableObjects, m_AllTargetObjects);
  617. //! target is outside the targeting cone; skip attack
  618. if (m_AllTargetObjects.Count() > 0 && m_AllTargetObjects[0] != m_ActualTarget)
  619. {
  620. m_AllTargetObjects.Clear();
  621. return false;
  622. }
  623. StartCommand_Attack(m_ActualTarget, m_ActualAttackType.m_Type, m_ActualAttackType.m_Subtype);
  624. m_AttackCooldownTime = m_ActualAttackType.m_Cooldown;
  625. return true;
  626. }
  627. return false;
  628. }
  629. int GetAttackPitch(EntityAI target)
  630. {
  631. vector attackRefPos;
  632. attackRefPos = target.GetDefaultHitPosition();
  633. //! no default hit pos fallback
  634. if ( attackRefPos != vector.Zero )
  635. {
  636. attackRefPos = target.ModelToWorld(attackRefPos);
  637. }
  638. else
  639. {
  640. attackRefPos = target.GetPosition();
  641. }
  642. // Now we have only erect stance, we need to get head position later too
  643. float headPosY = GetPosition()[1];
  644. headPosY += 1.8;
  645. float diff = Math.AbsFloat(attackRefPos[1] - headPosY);
  646. if ( diff < 0.3 )
  647. return 0;
  648. if ( headPosY > attackRefPos[1] )
  649. return -1;
  650. else
  651. return 1;
  652. }
  653. //-------------------------------------------------------------
  654. //!
  655. //! Crawl transition
  656. //!
  657. int m_CrawlTransition = -1;
  658. bool HandleCrawlTransition(int pCurrentCommandID)
  659. {
  660. if ( m_CrawlTransition != -1 )
  661. {
  662. StartCommand_Crawl(m_CrawlTransition);
  663. m_CrawlTransition = -1;
  664. m_IsCrawling = true;
  665. SetSynchDirty();
  666. return true;
  667. }
  668. return pCurrentCommandID == DayZInfectedConstants.COMMANDID_CRAWL;
  669. }
  670. bool EvaluateCrawlTransitionAnimation(EntityAI pSource, string pComponent, string pAmmoType, out int pAnimType)
  671. {
  672. pAnimType = -1;
  673. if ( pComponent == "LeftLeg" && GetHealth(pComponent, "Health") == 0 )
  674. pAnimType = 0;
  675. else if ( pComponent == "RightLeg" && GetHealth(pComponent, "Health") == 0 )
  676. pAnimType = 2;
  677. if ( pAnimType != -1 )
  678. {
  679. vector targetDirection = GetDirection();
  680. vector toSourceDirection = (pSource.GetPosition() - GetPosition());
  681. targetDirection[1] = 0;
  682. toSourceDirection[1] = 0;
  683. targetDirection.Normalize();
  684. toSourceDirection.Normalize();
  685. float cosFi = vector.Dot(targetDirection, toSourceDirection);
  686. if ( cosFi >= 0 ) // front
  687. pAnimType++;
  688. }
  689. return pAnimType != -1;
  690. }
  691. //-------------------------------------------------------------
  692. //!
  693. //! Damage hits
  694. //!
  695. bool m_DamageHitToProcess = false;
  696. bool m_DamageHitHeavy = false;
  697. int m_DamageHitType = 0;
  698. float m_ShockDamage = 0;
  699. const float HIT_INTERVAL_MIN = 0.3; // Minimum time in seconds before a COMMANDID_HIT to COMMANDID_HIT transition is allowed
  700. float m_HitElapsedTime = HIT_INTERVAL_MIN;
  701. bool HandleDamageHit(int pCurrentCommandID)
  702. {
  703. if ( pCurrentCommandID == DayZInfectedConstants.COMMANDID_HIT )
  704. {
  705. // Throttle hit command up to a fixed rate
  706. if ( m_HitElapsedTime < HIT_INTERVAL_MIN )
  707. {
  708. m_HitElapsedTime += m_DeltaTime;
  709. m_DamageHitToProcess = false;
  710. m_ShockDamage = 0;
  711. return false;
  712. }
  713. }
  714. if ( m_DamageHitToProcess )
  715. {
  716. int randNum = Math.RandomIntInclusive(0, 100);
  717. float stunChange = SHOCK_TO_STUN_MULTIPLIER * m_ShockDamage;
  718. if ( m_DamageHitHeavy || randNum <= stunChange || ( m_MindState == DayZInfectedConstants.MINDSTATE_CALM || m_MindState == DayZInfectedConstants.MINDSTATE_DISTURBED ) )
  719. {
  720. StartCommand_Hit(m_DamageHitHeavy, m_DamageHitType, m_DamageHitDirection);
  721. m_HitElapsedTime = 0;
  722. }
  723. m_DamageHitToProcess = false;
  724. m_ShockDamage = 0;
  725. m_HeavyHitOverride = false;
  726. return true;
  727. }
  728. return false;
  729. }
  730. //! selects animation type and direction based on damage system data
  731. bool EvaluateDamageHitAnimation(EntityAI pSource, string pComponent, string pAmmoType, out bool pHeavyHit, out int pAnimType, out float pAnimHitDir)
  732. {
  733. int invertHitDir = 0; //Used to flip the heavy hit animation direction
  734. //! heavy hit
  735. pHeavyHit = ((GetGame().ConfigGetInt("cfgAmmo " + pAmmoType + " hitAnimation") > 0) || m_HeavyHitOverride);
  736. invertHitDir = GetGame().ConfigGetInt("cfgAmmo " + pAmmoType + " invertHitDir");
  737. //! anim type
  738. pAnimType = 0; // belly
  739. if ( !pHeavyHit )
  740. {
  741. if ( pComponent == "Torso" ) // body
  742. pAnimType = 1;
  743. else if ( pComponent == "Head" ) // head
  744. pAnimType = 2;
  745. }
  746. //! direction
  747. //pAnimHitDir = ComputeHitDirectionAngle(pSource);
  748. pAnimHitDir = ComputeHitDirectionAngleEx(pSource, invertHitDir);
  749. //! shock GetDamage
  750. //m_ShockDamage = GetGame().ConfigGetFloat( "CfgAmmo " + pAmmoType + " DamageApplied " + "Shock " + "damage");
  751. return true;
  752. }
  753. float ComputeHitDirectionAngle(EntityAI pSource)
  754. {
  755. vector targetDirection = GetDirection();
  756. vector toSourceDirection = (pSource.GetPosition() - GetPosition());
  757. targetDirection[1] = 0;
  758. toSourceDirection[1] = 0;
  759. targetDirection.Normalize();
  760. toSourceDirection.Normalize();
  761. float cosFi = vector.Dot(targetDirection, toSourceDirection);
  762. vector cross = targetDirection * toSourceDirection;
  763. float dirAngle = Math.Acos(cosFi) * Math.RAD2DEG;
  764. if ( cross[1] < 0 )
  765. dirAngle = -dirAngle;
  766. return dirAngle;
  767. }
  768. float ComputeHitDirectionAngleEx(EntityAI pSource, int invertHitDir = 0)
  769. {
  770. vector targetDirection = GetDirection();
  771. vector toSourceDirection = (pSource.GetPosition() - GetPosition());
  772. targetDirection[1] = 0;
  773. toSourceDirection[1] = 0;
  774. targetDirection.Normalize();
  775. toSourceDirection.Normalize();
  776. float cosFi = vector.Dot(targetDirection, toSourceDirection);
  777. vector cross = targetDirection * toSourceDirection;
  778. float dirAngle = Math.Acos(cosFi) * Math.RAD2DEG;
  779. // We will invert direction of the hit
  780. if ( invertHitDir > 0 )
  781. dirAngle -= 180;
  782. if ( cross[1] < 0 )
  783. dirAngle = -dirAngle;
  784. return dirAngle;
  785. }
  786. //-------------------------------------------------------------
  787. //!
  788. //! Events from damage system
  789. //!
  790. override void EEHitBy(TotalDamageResult damageResult, int damageType, EntityAI source, int component, string dmgZone, string ammo, vector modelPos, float speedCoef)
  791. {
  792. super.EEHitBy(damageResult, damageType, source, component, dmgZone, ammo, modelPos, speedCoef);
  793. m_TransportHitRegistered = false;
  794. if ( !IsAlive() )
  795. {
  796. ZombieHitData data = new ZombieHitData;
  797. data.m_Component = component;
  798. data.m_DamageZone = dmgZone;
  799. data.m_AmmoType = ammo;
  800. EvaluateDeathAnimationEx(source, data, m_DeathType, m_DamageHitDirection);
  801. }
  802. else
  803. {
  804. int crawlTransitionType = -1;
  805. if ( EvaluateCrawlTransitionAnimation(source, dmgZone, ammo, crawlTransitionType) )
  806. {
  807. m_CrawlTransition = crawlTransitionType;
  808. return;
  809. }
  810. if ( EvaluateDamageHitAnimation(source, dmgZone, ammo, m_DamageHitHeavy, m_DamageHitType, m_DamageHitDirection) )
  811. {
  812. if ( dmgZone )
  813. m_ShockDamage = damageResult.GetDamage( dmgZone, "Shock" );
  814. m_DamageHitToProcess = true;
  815. return;
  816. }
  817. }
  818. }
  819. override void EEHitByRemote(int damageType, EntityAI source, int component, string dmgZone, string ammo, vector modelPos)
  820. {
  821. super.EEHitByRemote(damageType, source, component, dmgZone, ammo, modelPos);
  822. }
  823. //! sound debug messages
  824. protected void DebugSound(string s)
  825. {
  826. //Print(s);
  827. }
  828. //-------------------------------------------------------------
  829. //!
  830. //! Phx contact event
  831. //!
  832. override protected void EOnContact(IEntity other, Contact extra)
  833. {
  834. if ( !IsAlive() )
  835. return;
  836. Transport transport = Transport.Cast(other);
  837. if ( transport )
  838. {
  839. if ( GetGame().IsServer() )
  840. {
  841. RegisterTransportHit(transport);
  842. }
  843. }
  844. }
  845. override bool CanReceiveAttachment(EntityAI attachment, int slotId)
  846. {
  847. if ( !IsAlive() )
  848. {
  849. return false;
  850. }
  851. return super.CanReceiveAttachment(attachment, slotId);
  852. }
  853. override vector GetCenter()
  854. {
  855. return GetBonePositionWS( GetBoneIndexByName( "spine3" ) );
  856. }
  857. //! returns true if backstab is in progress; used for suspending of AI targeting and other useful things besides
  858. override bool IsBeingBackstabbed()
  859. {
  860. return m_FinisherInProgress;
  861. }
  862. override void SetBeingBackstabbed(int backstabType)
  863. {
  864. // disable AI simulation
  865. GetAIAgent().SetKeepInIdle(true);
  866. // select death animation
  867. switch (backstabType)
  868. {
  869. case EMeleeHitType.FINISHER_LIVERSTAB:
  870. m_DeathType = DayZInfectedDeathAnims.ANIM_DEATH_BACKSTAB;
  871. break;
  872. case EMeleeHitType.FINISHER_NECKSTAB:
  873. m_DeathType = DayZInfectedDeathAnims.ANIM_DEATH_NECKSTAB;
  874. break;
  875. default:
  876. m_DeathType = DayZInfectedDeathAnims.ANIM_DEATH_DEFAULT;
  877. }
  878. // set flag - death command will be executed
  879. m_FinisherInProgress = true;
  880. }
  881. //! returns true if crawling; 'DayZInfectedCommandCrawl' is only for the transition, after that it remains 'DayZInfectedCommandMove' (unreliable)
  882. bool IsCrawling()
  883. {
  884. return m_IsCrawling;
  885. }
  886. // called from command death when stealth attack wan't successful
  887. void OnRecoverFromDeath()
  888. {
  889. // enable AI simulation again
  890. GetAIAgent().SetKeepInIdle(false);
  891. // reset flag
  892. m_FinisherInProgress = false;
  893. }
  894. override void AddArrow(Object arrow, int componentIndex, vector closeBonePosWS, vector closeBoneRotWS)
  895. {
  896. CachedObjectsArrays.ARRAY_STRING.Clear();
  897. GetActionComponentNameList(componentIndex, CachedObjectsArrays.ARRAY_STRING, "fire");
  898. int pivot = -1;
  899. for (int i = 0; i < CachedObjectsArrays.ARRAY_STRING.Count() && pivot == -1; i++)
  900. {
  901. pivot = GetBoneIndexByName(CachedObjectsArrays.ARRAY_STRING.Get(i));
  902. }
  903. vector parentTransMat[4];
  904. vector arrowTransMat[4];
  905. if (pivot == -1)
  906. {
  907. GetTransformWS(parentTransMat);
  908. }
  909. else
  910. {
  911. vector rotMatrix[3];
  912. Math3D.YawPitchRollMatrix(closeBoneRotWS * Math.RAD2DEG,rotMatrix);
  913. parentTransMat[0] = rotMatrix[0];
  914. parentTransMat[1] = rotMatrix[1];
  915. parentTransMat[2] = rotMatrix[2];
  916. parentTransMat[3] = closeBonePosWS;
  917. }
  918. arrow.GetTransform(arrowTransMat);
  919. Math3D.MatrixInvMultiply4(parentTransMat, arrowTransMat, arrowTransMat);
  920. // orthogonalize matrix - parent might be skewed
  921. Math3D.MatrixOrthogonalize4(arrowTransMat);
  922. arrow.SetTransform(arrowTransMat);
  923. AddChild(arrow, pivot);
  924. }
  925. override bool IsManagingArrows()
  926. {
  927. return true;
  928. }
  929. override ArrowManagerBase GetArrowManager()
  930. {
  931. return m_ArrowManager;
  932. }
  933. }
  934. //! an extendable data container
  935. class ZombieHitData
  936. {
  937. int m_Component;
  938. string m_DamageZone;
  939. string m_AmmoType;
  940. }