Arma Reforger Explorer 1.7.0.54
Arma Reforger Code Explorer by Zeroy - Thanks to MisterOutofTime
Loading...
Searching...
No Matches
SCR_AICombatMoveLogic_Attack.c
Go to the documentation of this file.
1/*
2This node analyzes combat move state and issues new requests for combat move tree.
3*/
4
8{
9 protected SCR_AICombatMoveState m_State;
10 protected SCR_AIUtilityComponent m_Utility;
11 protected SCR_AICombatComponent m_CombatComp;
13 protected CharacterControllerComponent m_CharacterController;
14
15 // Values updated on each update, to avoid passing them through calls
16 protected EAIThreatState m_eThreatState;
19 protected float m_fTargetDist;
20 protected float m_fWeaponMinDist;
21 protected bool m_bCloseRangeCombat; // True if we consider it's a close range fight
22 protected bool m_bVeryLongRangeCombat; // True if we consider it's a very long range fight
23
24 protected float m_fNextUpdate_ms;
25 [Attribute("500")]
26 protected float m_fUpdateInterval_ms;
27
28 // Half-angle of cover query sector when directed cover search is used
29 protected const float COVER_QUERY_SECTOR_ANGLE_RAD = 0.3 * Math.PI;
30
31 //--------------------------------------------------------------------------------------------
32 // These methods must be overriden in derived classes.
33 protected bool OnUpdate(AIAgent owner, float dt); // This gets called before the rest of the logic. Here you should read data from ports, return true on success.
35 protected bool ResolveFailMoveIfNoCover(); // Gets called from PushRequestMove, this should return value of request.m_bFailIfNoCover
36 protected float ResolveStoppedWaitTime(bool inCover, EAIThreatState threat, EWeaponType weaponType);
38 protected float GetTargetDistance();
39 protected bool MoveToNextPosCondition();
40 protected vector GetAvoidStraightPathDir() { return vector.Zero; }; // Must return vector for straight path avoidance (flanking)
41 //--------------------------------------------------------------------------------------------
42
43
44 //--------------------------------------------------------------------------------------------
45 protected override void OnInit(AIAgent owner)
46 {
47 m_Utility = SCR_AIUtilityComponent.Cast(owner.FindComponent(SCR_AIUtilityComponent));
48 if (m_Utility)
49 m_State = m_Utility.m_CombatMoveState;
50
51 m_MyEntity = owner.GetControlledEntity();
52
53 if (m_MyEntity)
54 {
55 m_CharacterController = CharacterControllerComponent.Cast(m_MyEntity.FindComponent(CharacterControllerComponent));
56 m_CombatComp = SCR_AICombatComponent.Cast(m_MyEntity.FindComponent(SCR_AICombatComponent));
57 }
58 }
59
60 protected override ENodeResult EOnTaskSimulate(AIAgent owner, float dt)
61 {
62 float currentTime_ms = GetGame().GetWorld().GetWorldTime();
63 if (currentTime_ms < m_fNextUpdate_ms)
64 return ENodeResult.RUNNING;
65 m_fNextUpdate_ms = currentTime_ms + m_fUpdateInterval_ms;
66
67 if (!OnUpdate(owner, dt))
68 return ENodeResult.FAIL;
69
71 return ENodeResult.FAIL;
72
73 // Don't run combat movement logic if CombatMove BT is not used now (like in turret)
74 SCR_AIBehaviorBase executedBehavior = SCR_AIBehaviorBase.Cast(m_Utility.GetExecutedAction());
75 if (executedBehavior && !executedBehavior.m_bUseCombatMove)
76 return ENodeResult.RUNNING;
77
78 // Update cached variables
81 m_bVeryLongRangeCombat = m_fTargetDist > SCR_AICombatMoveUtils.VERY_LONG_RANGE_COMBAT_DIST;
82 m_eThreatState = m_Utility.m_ThreatSystem.GetState();
83 m_eStance = m_CharacterController.GetStance();
84 m_fWeaponMinDist = m_CombatComp.GetSelectedWeaponMinDist();
85 m_eWeaponType = m_CombatComp.GetSelectedWeaponType();
86
87
88 /*
89 //------------------------------------------------------------------------------------
90 Combat movement logic
91
92 Conditions represent states inside which we want to remain.
93
94 Conditions are organized based on their priority, highest first.
95
96 Within each state there can be extra logic which decides if it's worth to
97 send a new request, because even though we have selected a state, we should avoid
98 spamming same request over and over.
99
100 Conditions for states mostly depend on Combat Move State and its timers.
101
102 It is important to write logic in such a way that it doesn't depend on state
103 of this node. In this case the state flow also doesn't depend on it, and AI
104 does movement is more fluent when switching to a new behavior which also utilizes
105 combat movement, including attacking a different target.
106 */
107
109 {
111 }
112 else if (MoveFromTargetCondition())
113 {
114 // Too close to target
115 // Step away
118 }
120 {
121 // Current cover has been compromised, it's not directed at enemy any more
122 // Find a new cover nearby
124 }
125 else if (m_State.m_bInCover && m_CharacterController.IsReloading())
126 {
127 // We're reloading and can't do much else now
128 // Hide in cover
129 if (m_State.m_bExposedInCover)
130 m_State.ApplyRequestChangeStanceInCover(false);
131 }
132 else if (m_State.m_bInCover && !m_State.m_bExposedInCover)
133 {
134 // We're in cover but we are still hiding in it, unhide
135 m_State.ApplyRequestChangeStanceInCover(true);
136 }
137 else if (FFAvoidanceCondition())
138 {
141 }
142 else if (MoveToNextPosCondition())
143 {
144 // We've waited here too long, move to next place
146 }
147 else if (!m_State.IsExecutingRequest() && !m_State.m_bInCover)
148 {
149 // We are stopped and not in cover, manage our stance
151 if (newStance > m_eStance)
152 {
153 // Only let stance go down, no need to get back up
154 m_State.ApplyRequestChangeStanceOutsideCover(newStance);
155 }
156 }
157
158 return ENodeResult.RUNNING;
159 }
160
161
162 //--------------------------------------------------------------------------------------------
163 // Friendly fire avoidance
164
165 // Friendly fire avoidance condition
166 protected bool FFAvoidanceCondition()
167 {
168 // Should we deal with friendly fire avoidance?
169
170 // Ignore if we don't want to aim at all
171 if (!m_State.m_bAimAtTarget)
172 return false;
173
174 // True only when not in cover
175 if (m_State.m_bInCover)
176 return false;
177
178 // True when not moving to cover
179 if (m_State.IsMovingToCover())
180 return false;
181
182 // True when friendly is in aim
183 return m_Utility.m_CombatComponent.IsFriendlyInAim();
184 }
185
186 // Friendly fire avoidance condition for pushing new request
188 {
189 if (!m_State.IsExecutingRequest())
190 return true;
191
192 // Still executing ...
193 // Send new request only if we are executing NOT side-step
195 if (!rq)
196 return true;
197
198 return rq.m_eReason != SCR_EAICombatMoveReason.FF_AVOIDANCE;
199 }
200
201 protected void PushRequestFFAvoidance()
202 {
204
205 rq.m_eReason = SCR_EAICombatMoveReason.FF_AVOIDANCE;
206
207 // If prev. request was FF avoidance too, keep direction.
208 // Otherwise choose a new direction.
210 if (prevRequest && prevRequest.m_eReason == SCR_EAICombatMoveReason.FF_AVOIDANCE)
211 {
212 rq.m_eDirection = prevRequest.m_eDirection;
213 }
214 else
215 {
216 if (Math.RandomIntInclusive(0, 1) == 1)
217 rq.m_eDirection = SCR_EAICombatMoveDirection.RIGHT;
218 else
219 rq.m_eDirection = SCR_EAICombatMoveDirection.LEFT;
220 }
221
222 rq.m_eStanceMoving = m_CharacterController.GetStance(); // Don't change stance
223 rq.m_eStanceEnd = rq.m_eStanceMoving;
224 rq.m_vMovePos = ResolveRequestTargetPos();
225 rq.m_eMovementType = EMovementType.WALK;
226 rq.m_fMoveDuration_s = 1.0;
227 rq.m_bAimAtTarget = SCR_AICombatMoveUtils.IsAimingAndMovementPossible(rq.m_eStanceMoving, rq.m_eMovementType);
228 rq.m_bAimAtTargetEnd = true;
229
230 m_State.ApplyNewRequest(rq);
231 }
232
233
234 //--------------------------------------------------------------------------------------------
235 // Movement
236
237 protected void PushRequestMove()
238 {
240
241 rq.m_eReason = SCR_EAICombatMoveReason.STANDARD;
242
243 // Common values
244 rq.m_vTargetPos = ResolveRequestTargetPos();
245 ResolveMoveRequestMovePosAndDir(rq.m_vTargetPos, rq.m_vMovePos, rq.m_vAvoidStraightPathDir, rq.m_eDirection, rq.m_fCoverSearchSectorHalfAngleRad);
246 rq.m_bTryFindCover = true;
247 rq.m_bUseCoverSearchDirectivity = true;
248 rq.m_bCheckCoverVisibility = true;
249
250 float coverSearchDistMin = 0;
251 float coverSearchDistMax = 30;
252 float moveDurationMax = 6;
254 {
255 // Close range combat
256
257 switch (m_eThreatState)
258 {
259 case EAIThreatState.THREATENED:
260 {
261 rq.m_eStanceMoving = ECharacterStance.CROUCH;
262 rq.m_eStanceEnd = ECharacterStance.CROUCH;
263 coverSearchDistMin = 2.0;
264 coverSearchDistMax = 10.0;
265 moveDurationMax = 2;
266 break;
267 }
268 default:
269 {
270 rq.m_eStanceMoving = ECharacterStance.STAND;
271 rq.m_eStanceEnd = ECharacterStance.CROUCH;
272 coverSearchDistMin = 5.0;
273 coverSearchDistMax = 15.0;
274 moveDurationMax = 3;
275 break;
276 }
277 }
278
279 rq.m_eMovementType = EMovementType.RUN;
280 rq.m_bAimAtTarget = SCR_AICombatMoveUtils.IsAimingAndMovementPossible(rq.m_eStanceMoving, rq.m_eMovementType) &&
282 rq.m_bAimAtTargetEnd = true;
283 }
284 else
285 {
286 // Long range combat
287
288 switch (m_eThreatState)
289 {
290 case EAIThreatState.THREATENED:
291 {
292 coverSearchDistMin = 2.0;
293 coverSearchDistMax = 20.0;
294 moveDurationMax = 2.5;
295 rq.m_eStanceMoving = ECharacterStance.CROUCH;
296 rq.m_eStanceEnd = ECharacterStance.PRONE;
297 break;
298 }
299 default:
300 {
301 coverSearchDistMin = 10.0;
302 coverSearchDistMax = 30.0;
303 moveDurationMax = 4; // Shouldn't be so large because we are sprinting and can't shoot
304 rq.m_eStanceMoving = ECharacterStance.CROUCH;
305 rq.m_eStanceEnd = ECharacterStance.CROUCH;
306 break;
307 }
308 }
309
310 rq.m_eMovementType = EMovementType.SPRINT;
311 rq.m_bAimAtTarget = false; // Can't aim at tgt while sprinting
312 rq.m_bAimAtTargetEnd = true;
313 }
314
315 rq.m_bFailIfNoCover = ResolveFailMoveIfNoCover();
316
317 // If we are not in cover, min cover search distance is overridden to 0, we should find any cover ASAP
318 if (!m_State.m_bInCover)
319 coverSearchDistMin = 0;
320
321 rq.m_fCoverSearchDistMin = coverSearchDistMin;
322 rq.m_fCoverSearchDistMax = coverSearchDistMax;
323 rq.m_fMoveDuration_s = Math.RandomFloat(0.5, 1.0) * moveDurationMax;
324
325 // Subscribe to events
326 // We will pronounce voice lines once we start or end moving
328 rq.GetOnCompleted().Insert(OnMovementCompleted);
329
330 m_State.ApplyNewRequest(rq);
331 }
332
333 protected static void OnMovementStarted(SCR_AIUtilityComponent utility, SCR_AICombatMoveRequest_Move rq, vector pos, bool destinationIsCover)
334 {
335 if (!utility.m_CommsHandler.CanBypass())
336 {
337 SCR_AITalkRequest talkRq = new SCR_AITalkRequest(ECommunicationType.REPORT_MOVING, null, vector.Zero, 0, false, false, SCR_EAITalkRequestPreset.IRRELEVANT_IMMEDIATE);
338 utility.m_CommsHandler.AddRequest(talkRq);
339 }
340 }
341
342 protected static void OnMovementCompleted(SCR_AIUtilityComponent utility, SCR_AICombatMoveRequestBase rq)
343 {
344 if (!utility.m_CommsHandler.CanBypass())
345 {
346 SCR_AITalkRequest talkRq = new SCR_AITalkRequest(ECommunicationType.REPORT_COVERING, null, vector.Zero, 0, false, false, SCR_EAITalkRequestPreset.IRRELEVANT_IMMEDIATE);
347 utility.m_CommsHandler.AddRequest(talkRq);
348 }
349 }
350
351 // Resolves which move pos and dir. we should use for _MOVE_ request
352 // By now rq.m_vTargetPos must be already calculated!
353 protected void ResolveMoveRequestMovePosAndDir(vector targetPos, out vector outMovePos, out vector outAvoidStraightPathDir, out SCR_EAICombatMoveDirection outDirection, out float outCoverSearchSectorHalfAngleRad)
354 {
355 AIWaypoint wp = null;
356 AIAgent agent = m_Utility.GetAIAgent();
357 AIGroup group = agent.GetParentGroup();
358 if (group)
359 wp = group.GetCurrentWaypoint();
360
361 vector movePos;
362 SCR_EAICombatMoveDirection eDirection;
363 float coverSearchSectorHalfAngleRad;
364 vector avoidStraightPathDir;
365
366 if (!wp || SCR_EntityWaypoint.Cast(wp))
367 {
368 // No waypoint, or it's an entity-associated waypoint, like Follow waypoint.
369 // Therefore use standard movement logic.
370 // Otherwise they will want to run towards position where the waypoint is placed, which makes no sense.
371 movePos = targetPos;
372 eDirection = SCR_EAICombatMoveDirection.CUSTOM_POS; // Move to target
373 avoidStraightPathDir = GetAvoidStraightPathDir(); // Use flanking
374
375 if (IsFirstExecution())
376 coverSearchSectorHalfAngleRad = Math.PI; // Full circle, on first run we just want any cover if possible
377 else
378 coverSearchSectorHalfAngleRad = COVER_QUERY_SECTOR_ANGLE_RAD;
379 }
380 else
381 {
382 vector wpPos = wp.GetOrigin();
383 float wpRadius = wp.GetCompletionRadius();
384 bool tgtInWaypoint = vector.DistanceXZ(wpPos, targetPos) < wpRadius;
385 float myDistToWp = vector.DistanceXZ(wpPos, m_MyEntity.GetOrigin());
386
387 if (myDistToWp > wpRadius)
388 {
389 // We are outside WP, move towards center
390 movePos = wpPos;
391 eDirection = SCR_EAICombatMoveDirection.CUSTOM_POS;
392 coverSearchSectorHalfAngleRad = COVER_QUERY_SECTOR_ANGLE_RAD;
393 avoidStraightPathDir = vector.Zero; // Go straight
394 }
395 else if (myDistToWp > 0.5 * wpRadius)
396 {
397 // We are between 50% and 100% of wp radius
398
399 if (tgtInWaypoint)
400 {
401 // Towards target
402 movePos = targetPos;
403 eDirection = SCR_EAICombatMoveDirection.CUSTOM_POS;
404 avoidStraightPathDir = GetAvoidStraightPathDir(); // Use flanking
405 coverSearchSectorHalfAngleRad = COVER_QUERY_SECTOR_ANGLE_RAD;
406 }
407 else
408 {
409 // Move around current pos.
410 movePos = targetPos;
411 eDirection = SCR_EAICombatMoveDirection.ANYWHERE;
412 avoidStraightPathDir = vector.Zero;
413 coverSearchSectorHalfAngleRad = -1.0;
414 }
415 }
416 else
417 {
418 // We are within 50% radius of wp,
419 // Move towards tgt, regardless where tgt is
420 movePos = targetPos;
421 eDirection = SCR_EAICombatMoveDirection.CUSTOM_POS;
422 avoidStraightPathDir = GetAvoidStraightPathDir();
423
424 coverSearchSectorHalfAngleRad = COVER_QUERY_SECTOR_ANGLE_RAD;
425 }
426 }
427
428 outMovePos = movePos;
429 outDirection = eDirection;
430 outAvoidStraightPathDir = avoidStraightPathDir;
431 outCoverSearchSectorHalfAngleRad = coverSearchSectorHalfAngleRad;
432 }
433
434 //--------------------------------------------------------------------------------------------
435 // Hide in cover when suppressed
436
438 {
439 return m_State.m_bInCover && m_eThreatState == EAIThreatState.THREATENED;
440 }
441
442 protected void SuppressedInCoverLogic()
443 {
444 // We're pinned in cover and can't do much else now
445 // Alternate hiding in cover and unhiding
446
447 // How long to wait here? Depends on timer value from previous request.
448
449 float waitTime_s;
450 SCR_AICombatMoveRequestBase rq = m_State.GetRequest();
451 if (SCR_AICombatMoveRequest_ChangeStanceInCover.Cast(rq) && rq.m_eReason == SCR_EAICombatMoveReason.SUPPRESSED_IN_COVER)
452 waitTime_s = rq.m_f_UserTimer_s;
453 else
454 {
455 if (m_State.m_bExposedInCover)
456 waitTime_s = 1.7;
457 else
458 waitTime_s = 3.0;
459 }
460
461 if (m_State.m_bExposedInCover && m_State.m_fTimerRequest_s > waitTime_s)
462 {
463 float newWaitTime = Math.RandomFloat(3.5, 7.0); // Hide in cover for this time
464 PushRequestChangeStanceInCover(false, SCR_EAICombatMoveReason.SUPPRESSED_IN_COVER, newWaitTime);
465 }
466 else if (!m_State.m_bExposedInCover && m_State.m_fTimerRequest_s > waitTime_s)
467 {
468 float newWaitTime = Math.RandomFloat(1.7, 2.5); // Expose out of cover for this time
469 PushRequestChangeStanceInCover(true, SCR_EAICombatMoveReason.SUPPRESSED_IN_COVER, newWaitTime);
470 }
471 }
472
473 protected void PushRequestChangeStanceInCover(bool exposed, SCR_EAICombatMoveReason reason, float waitTime)
474 {
476
477 rq.m_bExposedInCover = exposed;
478 rq.m_bAimAtTarget = true;
479 rq.m_bAimAtTargetEnd = true;
480 rq.m_f_UserTimer_s = waitTime;
481 rq.m_eReason = reason;
482
483 m_State.ApplyNewRequest(rq);
484 }
485
486 //--------------------------------------------------------------------------------------------
487 // Cover not useful any more
488 // It means that it doesn't provide cover in direction of enemy
489
491 {
492 if (!m_State.m_bInCover || !m_State.IsAssignedCoverValid())
493 return false;
494
495 vector tgtPos = GetTargetPosition();
496
497 float cosAngle = m_State.GetAssignedCover().CosAngleToThreat(tgtPos);
498 return (cosAngle < 0.5); // cos 60 deg = 0.5
499 }
500
502 {
504
505 rq.m_eReason = SCR_EAICombatMoveReason.STANDARD;
506
507 rq.m_vTargetPos = ResolveRequestTargetPos();
508 rq.m_vMovePos = rq.m_vTargetPos;
509 rq.m_bTryFindCover = true;
510 rq.m_bUseCoverSearchDirectivity = true;
511 rq.m_bCheckCoverVisibility = true;
512 rq.m_bFailIfNoCover = true;
513 rq.m_eStanceMoving = ECharacterStance.CROUCH;
514 rq.m_eStanceEnd = ECharacterStance.CROUCH;
515 rq.m_eMovementType = EMovementType.RUN;
516 rq.m_eDirection = SCR_EAICombatMoveDirection.BACKWARD; // Move back from target
517 rq.m_fCoverSearchSectorHalfAngleRad = -1.0;
518 rq.m_bAimAtTarget = SCR_AICombatMoveUtils.IsAimingAndMovementPossible(rq.m_eStanceMoving, rq.m_eMovementType);
519 rq.m_bAimAtTargetEnd = true;
520
521 rq.m_fCoverSearchDistMin = 0;
522 rq.m_fCoverSearchDistMax = 20;
523
524 m_State.ApplyNewRequest(rq);
525 }
526
527
528 //--------------------------------------------------------------------------------------------
529 // Moving away from target if we are too close
530 // This is meant for very stepping backwards a very short distance
531
532 protected bool MoveFromTargetCondition()
533 {
534 float weaponMinDist = Math.Max(3.0, m_fWeaponMinDist);
535
536 return m_fTargetDist < weaponMinDist;
537 }
538
540 {
541 if (!m_State.IsExecutingRequest())
542 return true;
543
544 // Still executing ...
545 // Send new request only if we are executing NOT side-step
547 if (!rq)
548 return true;
549
550 return rq.m_eReason != SCR_EAICombatMoveReason.MOVE_FROM_TARGET;
551 }
552
554 {
556
557 rq.m_eReason = SCR_EAICombatMoveReason.MOVE_FROM_TARGET;
558
559 rq.m_vMovePos = ResolveRequestTargetPos();
560 rq.m_eMovementType = EMovementType.RUN;
561 rq.m_eStanceMoving = ECharacterStance.STAND;
562 rq.m_eStanceEnd = ECharacterStance.STAND;
563 rq.m_eDirection = SCR_EAICombatMoveDirection.BACKWARD;
564 rq.m_fMoveDuration_s = 2.0;
565 rq.m_bAimAtTarget = SCR_AICombatMoveUtils.IsAimingAndMovementPossible(rq.m_eStanceMoving, rq.m_eMovementType);
566 rq.m_bAimAtTargetEnd = true;
567
568 m_State.ApplyNewRequest(rq);
569 }
570
571 //--------------------------------------------------------------------------------------------
572 // Returns stance when stopped outside cover
573 protected static ECharacterStance ResolveStanceOutsideCover(bool closeRange, EAIThreatState threat)
574 {
575 if (closeRange)
576 {
577 // Close range combat
578 return ECharacterStance.CROUCH;
579 }
580 else
581 {
582 // Long range combat
583 switch (threat)
584 {
585 case EAIThreatState.THREATENED:
586 return ECharacterStance.PRONE;
587 default:
588 return ECharacterStance.CROUCH;
589 }
590 }
591 return ECharacterStance.STAND;
592 }
593
594 //--------------------------------------------------------------------------------------------
595 // Returns 'optimal' distance
596 // If we are between weaponMinDist and 'optimal' dist, we don't need to move closer to tgt
597 protected static float ResolveOptimalDistance(float weaponMinDist)
598 {
599 return Math.Max(weaponMinDist + 5.0, 15.0);
600 }
601
602 //--------------------------------------------------------------------------------------------
603 // Returns true if we are allowed to aim and move with that weapon
604 protected static bool IsAimingAndMovingAllowedForWeapon(EWeaponType weaponType)
605 {
606 switch (weaponType)
607 {
608 // Most common case is fast
609 case EWeaponType.WT_RIFLE:
610 return true;
611
612 case EWeaponType.WT_ROCKETLAUNCHER:
613 case EWeaponType.WT_GRENADELAUNCHER:
614 return false;
615
616 // Everything else
617 default:
618 return true;
619 }
620
621 return true;
622 }
623
624 //--------------------------------------------------------------------------------------------
625 // Returns true if it's first of combat movement logic. Doesn't mean first execution of this node.
626 protected bool IsFirstExecution()
627 {
628 return !m_State.GetRequest();
629 }
630
631 //--------------------------------------------------------------------------------------------
632 static override bool VisibleInPalette() { return true; }
633}
634
636class SCR_AICombatMoveLogic_Attack : SCR_AICombatMoveLogicBase
637{
638 // Inputs
639 protected static const string PORT_BASE_TARGET = "BaseTarget";
640 protected static const string PORT_AVOID_STRAIGHT_PATH_DIR = "AvoidStraightPathDir";
641
644
645 //--------------------------------------------------------------------------------------------
646 protected override bool OnUpdate(AIAgent owner, float dt)
647 {
649 GetVariableIn(PORT_AVOID_STRAIGHT_PATH_DIR, m_vAvoidStraightPathDir);
650
651 if (!m_Target || !m_Target.GetTargetEntity())
652 return false;
653
654 return true;
655 }
656
657 //--------------------------------------------------------------------------------------------
658 protected override float GetTargetDistance()
659 {
660 return m_Target.GetDistance();
661 }
662
663 //--------------------------------------------------------------------------------------------
664 protected override vector GetTargetPosition()
665 {
666 return m_Target.GetLastSeenPosition();
667 }
668
669 protected override vector GetAvoidStraightPathDir()
670 {
672 }
673
674 //--------------------------------------------------------------------------------------------
675 protected override vector ResolveRequestTargetPos()
676 {
677 IEntity tgtEntity = m_Target.GetTargetEntity();
678 if (tgtEntity && m_CombatComp.IsTargetVisible(m_Target))
679 {
680 ChimeraCharacter character = ChimeraCharacter.Cast(tgtEntity);
681 if (character)
682 {
683 vector eyePos = character.EyePosition();
684 return eyePos;
685 }
686
687 // It's a vehicle
688 vector pos = tgtEntity.GetOrigin();
689 pos = pos + Vector(0, 2.0, 0);
690 return pos;
691 }
692
693 // Target is either not visible or tgtEntity is null, use last seen position
694 vector lastSeenPos = m_Target.GetLastSeenPosition();
695 lastSeenPos = lastSeenPos + Vector(0, 1.8, 0);
696 return lastSeenPos;
697 }
698
699 //--------------------------------------------------------------------------------------------
700 protected override bool ResolveFailMoveIfNoCover()
701 {
702 if (m_bCloseRangeCombat)
703 {
704 return false;
705 }
706 else
707 {
708 // Long range combat
709 if (IsFirstExecution())
710 return true; // On first run we want to move to cover, or stay where we are if there is no cover, and shoot.
711 else
712 return m_State.m_bInCover; // Don't leave cover if there is no next cover
713 }
714 }
715
716 //--------------------------------------------------------------------------------------------
717 protected override float ResolveStoppedWaitTime(bool inCover, EAIThreatState threat, EWeaponType weaponType)
718 {
719 float waitTime;
720
721 if (inCover)
722 {
723 // In cover
724 switch (threat)
725 {
726 case EAIThreatState.THREATENED:
727 waitTime = 20.0; // Stay in cover for a long time, until we are not suppressed any more
728 break;
729 default:
730 waitTime = 5.0;
731 }
732 }
733 else
734 {
735 // Not in cover
736 switch (threat)
737 {
738 case EAIThreatState.THREATENED:
739 waitTime = 6.0;
740 break;
741 default:
742 waitTime = 3.0;
743 break;
744 }
745 }
746
747 // When using those weapons we want to move much less
748 bool longWaitTime = false;
749 switch (weaponType)
750 {
751 case EWeaponType.WT_MACHINEGUN:
752 case EWeaponType.WT_ROCKETLAUNCHER:
753 case EWeaponType.WT_GRENADELAUNCHER:
754 case EWeaponType.WT_SNIPERRIFLE:
755 longWaitTime = true;
756 }
757
758 // Note: it's important to let bots enough time to aim at very long range
759 // Stop time should be more than just a few seconds.
760 if (m_bVeryLongRangeCombat)
761 waitTime *= 2.0;
762
763 return waitTime;
764 }
765
766 //--------------------------------------------------------------------------------------------
767 protected override bool MoveToNextPosCondition()
768 {
769 // Don't get any more closer
770 // Except we should still move closer if we haven't seen target for a long time
771 float optimalDist = ResolveOptimalDistance(m_fWeaponMinDist);
772 if (m_fTargetDist < optimalDist && m_Target.GetTimeSinceSeen() < 15)
773 return false;
774
775 if (m_State.IsExecutingRequest())
776 return false;
777
778 // If it's first run, ignore timers, only if:
779 // - If we are not in cover.
780 if (IsFirstExecution() && !m_State.m_bInCover)
781 return true;
782
783 // If we should keep formation, don't move too far away
784 if (m_Utility.ShouldKeepFormation() || m_CombatComp.GetCombatMode() == EAIGroupCombatMode.HOLD_FIRE)
785 {
786 // In this case we move only once
787 if (!IsFirstExecution())
788 return false;
789 }
790
791 float stoppedWaitTime = ResolveStoppedWaitTime(m_State.m_bInCover, m_eThreatState, m_eWeaponType);
792 return m_State.m_fTimerStopped_s > stoppedWaitTime;
793 }
794
795 //--------------------------------------------------------------------------------------------
796 protected static ref TStringArray s_aVarsIn = {
798 PORT_AVOID_STRAIGHT_PATH_DIR
799 };
800 override TStringArray GetVariablesIn() { return s_aVarsIn; }
801}
802
805{
806 // Inputs
807 protected static const string PORT_SUPPRESSION_VOLUME = "SuppressionVolume";
808 protected static const string PORT_VISIBLE = "Visible";
809 protected static const string PORT_TIME_LAST_SEEN = "TimeLastSeen_ms";
810
811 // Variables updated from input ports
813 protected bool m_bTargetVisible = false;
814 protected float m_fTargetLastSeenTime_ms = 0; // World time
815
816 protected bool m_bGoodVision;
817
818 protected static const float TIME_SINCE_GOOD_VISIBILITY_MIN_MS = 10000.0;
819
820 //--------------------------------------------------------------------------------------------
821 protected override bool OnUpdate(AIAgent owner, float dt)
822 {
824
826 return false;
827
829 return false;
830
832 return false;
833
834 // Update m_bGoodVision
835 // The timer criteria is to exclude occlusion due to us hiding in cover
836 float timeSinceLastSeen_ms = GetGame().GetWorld().GetWorldTime() - m_fTargetLastSeenTime_ms;
838
839 return true;
840 }
841
842 //--------------------------------------------------------------------------------------------
843 protected override float GetTargetDistance()
844 {
845 return vector.Distance(m_MyEntity.GetOrigin(), m_SuppressionVolume.GetCenterPosition());
846 }
847
848 //--------------------------------------------------------------------------------------------
849 protected override vector GetTargetPosition()
850 {
851 return m_SuppressionVolume.GetCenterPosition();
852 }
853
854 //--------------------------------------------------------------------------------------------
855 protected override vector ResolveRequestTargetPos()
856 {
857 return m_SuppressionVolume.GetCenterPosition();
858 }
859
860 //--------------------------------------------------------------------------------------------
861 protected override bool ResolveFailMoveIfNoCover()
862 {
863 // Don't move out of cover if we already have good vision from current cover
864 if (m_bGoodVision)
865 return true;
866
867 return false; // We're allowed to move anywhere, including to coverless position. But our own suppression criteria still apply and run above this.
868 }
869
870 //--------------------------------------------------------------------------------------------
871 protected override float ResolveStoppedWaitTime(bool inCover, EAIThreatState threat, EWeaponType weaponType)
872 {
873 return 1.0;
874 }
875
876 //--------------------------------------------------------------------------------------------
877 protected override bool MoveToNextPosCondition()
878 {
879 if (m_State.IsExecutingRequest())
880 return false;
881
882 if (m_bGoodVision && m_State.m_bInCover)
883 {
884 // We have good vision and we are in cover, just stay here
885 return false;
886 }
887
888 // If vision is bad, move out until we have good visibility
889 // Here we operate with visibility of the suppression volume, still concept is same as during normal attack.
890 // Most important thing is to exit area with poor vision of target, but beyond that we don't need to move.
891 if (!m_bGoodVision || (m_bGoodVision && !m_State.m_bInCover) || (m_fTargetLastSeenTime_ms == 0))
892 {
893 // If it's first run, ignore timers, only if:
894 // - If we are not in cover.
895 if (IsFirstExecution() && !m_State.m_bInCover)
896 return true;
897
898 float stoppedWaitTime = ResolveStoppedWaitTime(m_State.m_bInCover, m_eThreatState, m_eWeaponType);
899 return m_State.m_fTimerStopped_s > stoppedWaitTime;
900 }
901
902 return false;
903 }
904
905 //--------------------------------------------------------------------------------------------
911 override TStringArray GetVariablesIn() { return s_aVarsIn; }
912}
ArmaReforgerScripted GetGame()
Definition game.c:1398
enum SCR_EAIActivityCause m_Utility
void SCR_AIBehaviorBase(SCR_AIUtilityComponent utility, SCR_AIActivityBase groupActivity)
override TStringArray GetVariablesIn()
vector m_vAvoidStraightPathDir
SCR_AICombatMoveLogicBase PORT_BASE_TARGET
Combat movement node for attack behavior, which is aimed at BaseTarget.
EAIThreatState m_eThreatState
void SCR_AICombatMoveRequest_Move()
class SCR_AIPolar m_Target
ECommunicationType
ref TStringArray s_aVarsIn
void SCR_AITalkRequest(ECommunicationType type, IEntity entity, vector pos, int enumSignal, bool transmitIfNoReceivers, bool transmitIfPassenger, SCR_EAITalkRequestPreset preset)
override void OnUpdate()
EWeaponType m_eWeaponType
Definition SendOrder.c:1
proto external vector GetOrigin()
Definition Math.c:13
proto bool GetVariableIn(string name, out void val)
Combat move logic when doing suppressive fire.
override bool OnUpdate(AIAgent owner, float dt)
override float ResolveStoppedWaitTime(bool inCover, EAIThreatState threat, EWeaponType weaponType)
static bool IsAimingAndMovingAllowedForWeapon(EWeaponType weaponType)
bool OnUpdate(AIAgent owner, float dt)
override void OnInit(AIAgent owner)
CharacterControllerComponent m_CharacterController
static void OnMovementCompleted(SCR_AIUtilityComponent utility, SCR_AICombatMoveRequestBase rq)
float ResolveStoppedWaitTime(bool inCover, EAIThreatState threat, EWeaponType weaponType)
override ENodeResult EOnTaskSimulate(AIAgent owner, float dt)
void ResolveMoveRequestMovePosAndDir(vector targetPos, out vector outMovePos, out vector outAvoidStraightPathDir, out SCR_EAICombatMoveDirection outDirection, out float outCoverSearchSectorHalfAngleRad)
static float ResolveOptimalDistance(float weaponMinDist)
static ECharacterStance ResolveStanceOutsideCover(bool closeRange, EAIThreatState threat)
static void OnMovementStarted(SCR_AIUtilityComponent utility, SCR_AICombatMoveRequest_Move rq, vector pos, bool destinationIsCover)
void PushRequestChangeStanceInCover(bool exposed, SCR_EAICombatMoveReason reason, float waitTime)
ScriptInvokerBase< SCR_AICombatMoveRequest_Move_MovementEvent > GetOnMovementStarted(bool createInvoker=true)
ENodeResult
Definition ENodeResult.c:13
ECharacterStance
EMovementType
SCR_FieldOfViewSettings Attribute
array< string > TStringArray
Definition Types.c:385
proto native vector Vector(float x, float y, float z)
EWeaponType
Definition EWeaponType.c:13