Arma Reforger Explorer 1.7.0.54
Arma Reforger Code Explorer by Zeroy - Thanks to MisterOutofTime
Loading...
Searching...
No Matches
SCR_InteractionHandlerComponent.c
Go to the documentation of this file.
4
7enum SCR_NearbyContextDisplayMode
8{
9 DISABLED = 0,
12 ON_FREELOOK = 3
13}
14
20class SCR_InteractionHandlerComponent : InteractionHandlerComponent
21{
23 protected SCR_BaseInteractionDisplay m_pDisplay;
24
25 [Attribute("3", UIWidgets.ComboBox, "Display mode", "", ParamEnumArray.FromEnum(SCR_NearbyContextDisplayMode), category: "Nearby Context Properties")]
26 protected SCR_NearbyContextDisplayMode m_eDisplayMode;
27
28 [Attribute("1", UIWidgets.Slider, "Distance in percentage the raycast needs to travel until the context counts as visible", "0 1 0.01", category: "Nearby Context Properties")]
29 protected float m_fRaycastThreshold;
30
31 [Attribute("", UIWidgets.EditBox, "Action to listen for when SCR_NearbyContextDisplayMode is set to ON_INPUT_ACTION", category: "Nearby Context Properties")]
32 protected string m_sActionName;
33
34 [Attribute("", UIWidgets.EditBox, "Context to activate when SCR_NearbyContextDisplayMode is set to ON_INPUT_ACTION. Mustn't be empty to be activated.", category: "Nearby Context Properties")]
35 protected string m_sActionContext;
36
38 protected UserActionContext m_pLastContext;
39
41 protected BaseUserAction m_pLastUserAction;
42
44 protected int m_iSelectedActionIndex;
45
46 protected bool m_bIsPerforming;
47 protected bool m_bPerformAction;
48 protected bool m_bLastInput;
49 protected float m_fSelectAction;
50 protected float m_fCurrentProgress;
51
53 protected ref array<IEntity> m_aCollectedEntities = {};
55 protected ref array<IEntity> m_aCollectedNearbyEntities = {};
56
57 protected IEntity m_ControlledEntity;
58
59 //------------------------------------------------------------------------------------------------
61 protected void RegisterActionListeners()
62 {
63 InputManager pInputManager = GetGame().GetInputManager();
64 if (!pInputManager)
65 return;
66
67 m_bPerformAction = false;
68 m_fSelectAction = 0;
69 pInputManager.AddActionListener("PerformAction", EActionTrigger.DOWN, ActionPerform);
70 pInputManager.AddActionListener("PerformAction", EActionTrigger.UP, ActionPerform);
71 pInputManager.AddActionListener("SelectAction", EActionTrigger.VALUE, ActionScroll);
72 }
73
74 //------------------------------------------------------------------------------------------------
75 protected void RemoveActionListeners()
76 {
77 InputManager pInputManager = GetGame().GetInputManager();
78 if (!pInputManager)
79 return;
80
81 m_bPerformAction = false;
82 m_fSelectAction = 0;
83 pInputManager.RemoveActionListener("PerformAction", EActionTrigger.DOWN, ActionPerform);
84 pInputManager.RemoveActionListener("PerformAction", EActionTrigger.UP, ActionPerform);
85 pInputManager.RemoveActionListener("SelectAction", EActionTrigger.VALUE, ActionScroll);
86 }
87
88 //------------------------------------------------------------------------------------------------
90 void ActionPerform(float value, EActionTrigger reason)
91 {
92 m_bPerformAction = reason == EActionTrigger.DOWN;
93 }
94
95 //------------------------------------------------------------------------------------------------
96 void ActionScroll(float value, EActionTrigger reason)
97 {
98 if (value == 0)
99 return;
100
101 m_fSelectAction = value;
102 }
103
104 //------------------------------------------------------------------------------------------------
108 override protected void OnControlledEntityChanged(IEntity from, IEntity to)
109 {
110 PlayerController controller = GetGame().GetPlayerController();
111 if (!controller)
112 return;
113
114 if (controller.FindComponent(SCR_InteractionHandlerComponent) != this)
115 return;
116
117 if (from)//Was an owner of the entity that contians this component but that is changing now
119
120 if (to && to == SCR_PlayerController.GetLocalControlledEntity())//Became an owner of the entity that has this component
121 RegisterActionListeners();
122 }
123
124 //------------------------------------------------------------------------------------------------
125 override void OnInit(IEntity owner)
126 {
127 DiagMenu.RegisterBool(SCR_DebugMenuID.DEBUGUI_INTERACTION_SKIP_DURATION, "", "Skip action duration", "User Actions");
128 }
129
130 //------------------------------------------------------------------------------------------------
131 protected SCR_BaseInteractionDisplay FindDisplay(IEntity owner)
132 {
133 PlayerController playerController = PlayerController.Cast(owner);
134 if (!playerController)
135 {
136 Print("InteractionHandler must be attached to a PlayerController!", LogLevel.ERROR);
137 return null;
138 }
139
140 HUDManagerComponent hudManager = HUDManagerComponent.Cast(playerController.FindComponent(HUDManagerComponent));
141 array<BaseInfoDisplay> displayInfos = {};
142 int count = hudManager.GetInfoDisplays(displayInfos);
143 for (int i = 0; i < count; i++)
144 {
145 SCR_BaseInteractionDisplay current = SCR_BaseInteractionDisplay.Cast(displayInfos[i]);
146 if (current)
147 return current;
148 }
149
150 Print("InteractionDisplay not found! InteractionDisplay must be stored in HUDManagerComponent of a PlayerController!", LogLevel.WARNING);
151 return null;
152 }
153
154 //------------------------------------------------------------------------------------------------
163 protected void DoProcessInteraction(
164 ChimeraCharacter user,
165 UserActionContext context,
166 BaseUserAction action,
167 bool canPerform,
168 bool performInput,
169 float timeSlice,
170 SCR_BaseInteractionDisplay display)
171 {
172 if (action)
173 action.SetActiveContext(context);
174
175 // Can action be performed?
176 bool isOk = action && canPerform && action == m_pLastUserAction;
177 // We want to perform and action is OK
178 if (performInput && isOk)
179 {
180 // We want to be performing, but we're not yet.
181 // Start the action and dispatch events.
182 if (!m_bIsPerforming && !m_bLastInput)
183 {
184 if (!GetCanInteractScript(user))
185 return;
186
187 // UI
188 if (display)
189 display.OnActionStart(user, action);
190
191 // Start the action. Calls action.OnActionStart
192 user.DoStartObjectAction(action);
193
194 // Set state
195 m_bIsPerforming = true;
196 m_fCurrentProgress = 0.0;
197 }
198 // We want to perform and we already started performing,
199 // update continuous handler state until we're finished
200 else if (m_bIsPerforming)
201 {
202 if (DiagMenu.GetValue(SCR_DebugMenuID.DEBUGUI_INTERACTION_SKIP_DURATION))
203 {
204 timeSlice += Math.AbsFloat(action.GetActionDuration());
205
206 SCR_ScriptedUserAction scrAction = SCR_ScriptedUserAction.Cast(action);
207 if (scrAction)
208 timeSlice += scrAction.GetLoopActionHoldDuration();
209 }
210
211 // Update elapsed time
212 m_fCurrentProgress = action.GetActionProgress(m_fCurrentProgress, timeSlice);
213
214 // Tick action
215 if (action.ShouldPerformPerFrame())
216 user.DoPerformContinuousObjectAction(action, timeSlice);
217
218 // Get action duration
219 float duration = action.GetActionDuration();
220
221 // Update UI
222 if (display)
223 display.OnActionProgress(user, action, m_fCurrentProgress, Math.AbsFloat(duration));
224
225 // We are finished, dispatch events and reset state
226 // TODO: Why are some actions set with negative duration? Why does using an abs duration check here causes those actions to break? Why Is this not using a proper event system from the actions themselves?!
227 if (m_fCurrentProgress >= duration && duration >= 0)
228 {
229 // Update UI
230 if (display)
231 display.OnActionFinish(user, action, ActionFinishReason.FINISHED);
232
233 // Finally perform action
234 if (!action.ShouldPerformPerFrame())
235 user.DoPerformObjectAction(action);
236
237 // Reset state
238 m_fCurrentProgress = 0.0;
239 m_bIsPerforming = false;
240 }
241 }
242 }
243 else
244 {
245 // Input was released, we were performing previously,
246 // stop performing and dispatch necessary events.
247 if (m_bIsPerforming)
248 {
249 // Update UI
250 if (display)
251 display.OnActionFinish(user, action, ActionFinishReason.INTERRUPTED);
252
253 // Cancel the action. Calls action.OnActionCanceled
254 user.DoCancelObjectAction(action);
255
256 // Reset state
257 m_bIsPerforming = false;
258 m_fCurrentProgress = 0.0;
259 }
260 }
261 }
262
263 //------------------------------------------------------------------------------------------------
264 protected override bool GetCanInteractScript(IEntity controlledEntity)
265 {
266 ChimeraCharacter character = ChimeraCharacter.Cast(controlledEntity);
267 if (!character)
268 return false;
269
270 // No interactions when menu is open
271 MenuManager menuManager = GetGame().GetMenuManager();
272 if (menuManager && menuManager.IsAnyMenuOpen())
273 return false;
274
275 SCR_CharacterControllerComponent characterController = SCR_CharacterControllerComponent.Cast(character.GetCharacterController());
276 if (characterController && !characterController.CanInteract())
277 return false;
278
279 return true;
280 }
281
282 //------------------------------------------------------------------------------------------------
283 protected override bool GetIsInteractionAvailableScript()
284 {
285 return IsContextAvailable();
286 }
287
288 //------------------------------------------------------------------------------------------------
289 protected override BaseUserAction GetSelectedActionScript()
290 {
291 return m_pLastUserAction;
292 }
293
294 //------------------------------------------------------------------------------------------------
295 protected override bool DoIntersectCheck(IEntity controlledEntity)
296 {
297 if (!controlledEntity)
298 return false;
299
300 ChimeraCharacter character = ChimeraCharacter.Cast(controlledEntity);
301 if (!character)
302 return false;
303
304 if (character.IsInVehicle())
305 return true;
306
307 if (character.GetCharacterController().GetInspect())
308 return true;
309
310 return false;
311 }
312
313 //------------------------------------------------------------------------------------------------
314 protected override void OnContextChanged(UserActionContext previousContext, UserActionContext newContext)
315 {
316 // Changed, so hide previous
317 if (!newContext || newContext != GetCurrentContext())
318 {
319 m_iSelectedActionIndex = 0;
320 if (m_pDisplay)
321 m_pDisplay.HideDisplay();
322 }
323
324 // Changed, so show new
325 if (m_pDisplay && newContext)
326 m_pDisplay.ShowDisplay();
327 }
328
329 //------------------------------------------------------------------------------------------------
330 protected override event bool CanContextChange(UserActionContext currentContext, UserActionContext newContext)
331 {
332 // Setting null context might be desirable in certain cases when state should be cleared, see below:
333 if (!newContext)
334 {
335 // Allow clearing if no entity is controlled, to prevent leaking of contexts
336 if (!m_ControlledEntity)
337 return true;
338
339 // Allow clearing of context if controlled entity is destroyed, at this point interaction should begone
340 DamageManagerComponent dmg = DamageManagerComponent.Cast(m_ControlledEntity.FindComponent(DamageManagerComponent));
341 if (dmg && dmg.IsDestroyed())
342 return true;
343
344 // Otherwise continue with the usual
345 }
346
347 // Check whether we are still in range
348 if (currentContext && m_ControlledEntity)
349 {
350 // We will leave a small error threshold
351 const float threshold = 1.1;
352 // Global action visibility range in meters
353 float visRange = currentContext.GetVisibilityRange(GetVisibilityRange());
354 // Maximum sq distance we can interact at
355 float maxSqDistance = (visRange * visRange) * 1.1;
356 // Sq distance to controlled entity
357 float sqDistance = vector.DistanceSq(currentContext.GetOrigin(), m_ControlledEntity.GetOrigin());
358
359 if (sqDistance > maxSqDistance)
360 {
361 // We are out of range, context can change safely
362 return true;
363 }
364 }
365
366 // Suppress context changing when we are interacting with one already
367 if (currentContext && m_bIsPerforming)
368 {
369 // TODO: Validate distance to ctx
370 return false;
371 }
372
373 return true;
374 }
375
376 protected override event bool IsPerformingAction()
377 {
378 return m_bIsPerforming;
379 }
380
381 //------------------------------------------------------------------------------------------------
384 protected bool IsFreelookEnabled(ChimeraCharacter character)
385 {
386 if (!character)
387 return false;
388
389 CharacterControllerComponent controller = character.GetCharacterController();
390 if (!controller)
391 return false;
392
393 // Inspection is priority
394 if (controller.GetInspect())
395 return true;
396
397 CompartmentAccessComponent compartmentAccess = character.GetCompartmentAccessComponent();
398 // Hide blips when in TPP while in the vehicle
399 if (compartmentAccess && compartmentAccess.IsInCompartment() && controller.IsInThirdPersonView())
400 return false;
401
402 // When forced, avoid displaying in certain cases
403 // Suppress display when getting in our out
404 if (compartmentAccess)
405 {
406 if (compartmentAccess.IsGettingIn() || compartmentAccess.IsGettingOut())
407 return false;
408 }
409
410 // Supress display when in a vehicle while in 3rd person
411 if (character.IsInVehicle())
412 {
413 if (controller.IsInThirdPersonView())
414 return false;
415 }
416
417 // Supress display when falling
418 if (controller.IsFalling())
419 return false;
420
421 // Or climbing
422 if (controller.IsClimbing())
423 return false;
424
425 // Or unconscious
426 if (controller.GetLifeState() != ECharacterLifeState.ALIVE)
427 return false;
428
429 if (controller.GetFreeLookInput())
430 return true;
431
432 return false;
433 }
434
435 //------------------------------------------------------------------------------------------------
436 protected bool ShouldBeEnabled(SCR_NearbyContextDisplayMode displayMode, ChimeraCharacter character, bool playerCameraOnly = true)
437 {
438 // Disallow when character is none
439 if (!character)
440 return false;
441
442 // Disallow out of player camera when true
443 if (playerCameraOnly)
444 {
445 CameraManager cameraManager = GetGame().GetCameraManager();
446 if (cameraManager && !PlayerCamera.Cast(cameraManager.CurrentCamera()))
447 return false;
448 }
449
450#ifdef NEARBY_INTERACTIONS_CONTEXT_DEBUG
451 // If debug mode is active show them always to make debugging easier
452 return true;
453#endif
454
455 // Handle different display mode cases
456 switch (displayMode)
457 {
458 // Always off
459 case SCR_NearbyContextDisplayMode.DISABLED:
460 return false;
461
462 // Always on
463 case SCR_NearbyContextDisplayMode.ALWAYS_ON:
464 return true;
465
466 // On action
467 case SCR_NearbyContextDisplayMode.ON_INPUT_ACTION:
468 {
469 if (m_sActionName.IsEmpty())
470 return false;
471
472 InputManager inputManager = GetGame().GetInputManager();
473 if (!m_sActionContext.IsEmpty())
474 inputManager.ActivateContext(m_sActionContext);
475
476 return inputManager.GetActionValue(m_sActionName) > 0;
477 }
478
479 // When in freelook
480 case SCR_NearbyContextDisplayMode.ON_FREELOOK:
481 return IsFreelookEnabled(character);
482 }
483
484 // Nope, sorry.
485 return false;
486 }
487
488 //------------------------------------------------------------------------------------------------
489 void GetOverrideListReferencePoint(IEntity owner, out vector referencePoint)
490 {
491 CameraManager cameraManager = GetGame().GetCameraManager();
492 if (cameraManager)
493 {
494 CameraBase camera = cameraManager.CurrentCamera();
495 vector rayDir = camera.GetWorldTransformAxis(2);
496 vector rayStart = camera.GetOrigin();
497 referencePoint = rayStart + rayDir;
498
499 // Inspection correction
500 ChimeraCharacter character = ChimeraCharacter.Cast(m_ControlledEntity);
501 if (character)
502 {
503 // During inspection (of a weapon)
504 CharacterControllerComponent controller = character.GetCharacterController();
505 if (controller.GetInspect() && controller.GetInspectCurrentWeapon())
506 {
507 // Assume that while in inspection, weapon is tilted and its
508 // left side is pointed towards the player camera
509 IEntity inspectedEntity = controller.GetInspectEntity();
510
511 vector origin = inspectedEntity.GetOrigin();
512 vector normal = -inspectedEntity.GetWorldTransformAxis(0);
513
514 referencePoint = SCR_Math3D.IntersectPlane(rayStart, rayDir, origin, normal);
515 // Shape.CreateSphere(COLOR_RED, ShapeFlags.ONCE, referencePoint, 0.01);
516 }
517 }
518 }
519 else
520 {
521 referencePoint = vector.Zero;
522 }
523 }
524
525 //------------------------------------------------------------------------------------------------
526 override array<IEntity> GetManualNearbyOverrideList(IEntity owner, out vector referencePoint)
527 {
528 GetOverrideListReferencePoint(owner, referencePoint);
529 return m_aCollectedNearbyEntities;
530 }
531
532 //------------------------------------------------------------------------------------------------
533 override array<IEntity> GetManualOverrideList(IEntity owner, out vector referencePoint)
534 {
535 GetOverrideListReferencePoint(owner, referencePoint);
536 return m_aCollectedEntities;
537 }
538
539 //------------------------------------------------------------------------------------------------
542 protected void HandleOverride(notnull ChimeraCharacter character)
543 {
544 m_aCollectedNearbyEntities.Clear();
545 m_aCollectedEntities.Clear();
546
547 const bool bInspection = HandleInspection(character);
548 const bool bInVehicle = HandleVehicle(character);
549
552
553 if (bInspection || bInVehicle)
554 {
556 if (bInspection)
558 }
559 }
560
561 //------------------------------------------------------------------------------------------------
565 protected bool HandleVehicle(notnull ChimeraCharacter character)
566 {
567 if (!character.IsInVehicle())
568 return false;
569
570 CompartmentAccessComponent compartmentAccess = character.GetCompartmentAccessComponent();
571 // Check if player is inside a Vehicle
572 if (!compartmentAccess || !compartmentAccess.IsInCompartment())
573 return false;
574
575 BaseCompartmentSlot compartment = compartmentAccess.GetCompartment();
576 if (!compartment)
577 return false;
578
579 // Get the vehicle the player is in. (Can be the turret of a vehicle, thats why we use GetRootParent().)
580 IEntity vehicle = compartment.GetOwner().GetRootParent();
581
582 m_aCollectedNearbyEntities.Insert(vehicle);
583
584 BaseCompartmentManagerComponent compartmentManager = BaseCompartmentManagerComponent.Cast(vehicle.FindComponent(BaseCompartmentManagerComponent));
585 if (!compartmentManager)
586 return true;
587
588 array<BaseCompartmentSlot> compartments = {};
589 compartmentManager.GetCompartments(compartments);
590
591 foreach (BaseCompartmentSlot comp : compartments)
592 {
593 IEntity compOwner = comp.GetOwner();
594 if (compOwner && !m_aCollectedNearbyEntities.Contains(compOwner))
595 m_aCollectedNearbyEntities.Insert(compOwner);
596
597 IEntity compOccupant = comp.GetOwner();
598 if (compOccupant && !m_aCollectedNearbyEntities.Contains(compOccupant))
599 m_aCollectedNearbyEntities.Insert(compOccupant);
600 }
601
602 return true;
603 }
604
605 //------------------------------------------------------------------------------------------------
609 protected bool HandleInspection(notnull ChimeraCharacter character)
610 {
611 if (!character.GetCharacterController().GetInspect())
612 return false;
613
614 // Weapon is the priority if inspected, including all attachements
615 CharacterControllerComponent ctrlComp = character.GetCharacterController();
616 if (ctrlComp.GetInspectCurrentWeapon())
617 {
618 // Insert all items we can be interested in
619 BaseWeaponManagerComponent weaponManager = BaseWeaponManagerComponent.Cast(character.FindComponent(BaseWeaponManagerComponent));
620 if (!weaponManager)
621 return false;
622
623 BaseWeaponComponent weapon = weaponManager.GetCurrentWeapon();
624 if (!weapon)
625 return false;
626
627 m_aCollectedNearbyEntities.Insert(weapon.GetOwner());
628 m_aCollectedEntities.Insert(weapon.GetOwner());
629
630 array<AttachmentSlotComponent> attachments = {};
631 weapon.GetAttachments(attachments);
632
633 foreach (AttachmentSlotComponent attachment : attachments)
634 {
635 IEntity attachedEntity = attachment.GetAttachedEntity();
636 if (attachedEntity)
637 {
638 m_aCollectedNearbyEntities.Insert(attachedEntity);
639 m_aCollectedEntities.Insert(attachedEntity);
640 }
641 }
642
643 BaseMagazineComponent magazineComp = weapon.GetCurrentMagazine();
644 if (magazineComp)
645 {
646 m_aCollectedNearbyEntities.Insert(magazineComp.GetOwner());
647 m_aCollectedEntities.Insert(magazineComp.GetOwner());
648 }
649
650 return true;
651 }
652 else
653 {
654 // Whatever else is inspected kicks in
655 IEntity inspectedItem = ctrlComp.GetInspectEntity();
656 if (inspectedItem)
657 {
658 m_aCollectedNearbyEntities.Insert(inspectedItem);
659 m_aCollectedEntities.Insert(inspectedItem);
660 return true;
661 }
662 }
663
664 return false;
665 }
666
667 //------------------------------------------------------------------------------------------------
668 protected override void OnPostFrame(IEntity owner, IEntity controlledEntity, float timeSlice)
669 {
670 // TODO@AS: Add a reliable init method and get rid of this monstrosity
671 if (!m_pDisplay)
672 m_pDisplay = FindDisplay(owner);
673
674 m_ControlledEntity = controlledEntity;
675 // Make sure we have a valid character
676 ChimeraCharacter character = ChimeraCharacter.Cast(controlledEntity);
677
678 // Nearby context collection?
679 bool enableNearbyCollection = ShouldBeEnabled(m_eDisplayMode, character, true);
680 SetNearbyCollectionEnabled(enableNearbyCollection);
681
682 // Make sure we have a valid character
683 if (!character || character.IsInVehicleADS())
684 {
685 m_bPerformAction = false;
686 return;
687 }
688
689 ChimeraWorld world = ChimeraWorld.CastFrom(character.GetWorld());
690 if (!world || world.IsGameTimePaused())
691 return; // dont tick user actions when everything is paused
692
693 HandleOverride(character);
694
695 UserActionContext currentContext = GetCurrentContext();
696 if (currentContext)
697 {
698 array<BaseUserAction> actions = {};
699 array<bool> canPerform = {};
700 int count = GetFilteredActions(actions, canPerform);
701 if (count > 0)
702 GetGame().GetInputManager().ActivateContext("ActionMenuContext", 250);
703
704 foreach (BaseUserAction action : actions)
705 {
706 if (m_pDisplay)
707 m_pDisplay.OnActionProgress(character, action, action.GetActionProgress(0, 0), action.GetActionDuration());
708 }
709
710 AggregateActions(actions, canPerform);
711
712 // First of all, prior to doing any destructive changes,
713 // find the previous selected action (if any) and
714 // update the index, in case it has been shuffled.
715 if (m_pLastUserAction)
716 {
717 BaseUserAction action;
718 for (int i = 0, ac = actions.Count(); i < ac; i++)
719 {
720 action = actions[i];
721 if (action && action == m_pLastUserAction)
722 {
723 m_iSelectedActionIndex = i;
724 break;
725 }
726 }
727 }
728
729 // Update selection
730 int iScrollAmount = 0;
731 int prevActionIndex = m_iSelectedActionIndex;
732 // But only if player is not performing an action already
733 if (!m_bIsPerforming)
734 {
735 if (Math.AbsFloat(m_fSelectAction) > 0.5)
736 iScrollAmount = Math.Clamp(m_fSelectAction, -1.0, 1.0);
737
738 if (iScrollAmount != 0)
739 m_iSelectedActionIndex = m_iSelectedActionIndex - iScrollAmount;
740 }
741
742 // Make sure that selected action is always within bounds
743 int actionsCount = actions.Count();
744 m_iSelectedActionIndex = Math.Clamp(m_iSelectedActionIndex, 0, actionsCount - 1);
745
746 BaseUserAction selectedAction = null;
747 bool canPerformSelectedAction = false;
748
749 if (m_bIsPerforming)
750 {
751 selectedAction = m_pLastUserAction;
752 if (actions.Count() > prevActionIndex && actions[prevActionIndex] == m_pLastUserAction)
753 canPerformSelectedAction = canPerform[prevActionIndex];
754 else if (m_pLastUserAction)
755 canPerformSelectedAction = m_pLastUserAction.CanBeShown(character) && m_pLastUserAction.CanBePerformed(character);
756
757 SCR_CharacterControllerComponent controller = SCR_CharacterControllerComponent.Cast(character.GetCharacterController());
758 canPerformSelectedAction = canPerformSelectedAction && controller && controller.GetLifeState() == ECharacterLifeState.ALIVE;
759 }
760 else if (actionsCount > 0)
761 {
762 selectedAction = actions[m_iSelectedActionIndex];
763 canPerformSelectedAction = canPerform[m_iSelectedActionIndex];
764 }
765
766 // Process interaction
767 if (selectedAction)
768 canPerformSelectedAction = canPerformSelectedAction && selectedAction.CanBePerformed(character); // need to call this because the data from GetFilteredActions might not be up to date
769
770 DoProcessInteraction(character, currentContext, selectedAction, canPerformSelectedAction, m_bPerformAction, timeSlice, m_pDisplay);
771 m_pLastUserAction = selectedAction;
772 SetSelectedAction(selectedAction);
773
774 // Pass data to display
775 if (m_pDisplay)
776 {
777 ActionsTuple pData = new ActionsTuple();
778 bool canInteract = GetCanInteractScript(character);
779
780 if (canInteract || m_bIsPerforming)
781 {
782 pData.param1 = actions;
783 pData.param2 = canPerform;
784 }
785 else
786 {
787 pData.Init();
788 }
789
790 ActionDisplayData pDisplayData = new ActionDisplayData();
791 pDisplayData.pUser = controlledEntity;
792 pDisplayData.pActionsData = pData;
793 pDisplayData.pSelectedAction = selectedAction;
794 pDisplayData.pCurrentContext = currentContext;
795
796 m_pDisplay.SetDisplayData(pDisplayData);
797 }
798 }
799 // We don't have a context, but we possibly had, thus reset
800 // our current action and make sure to dispatch events.
801 else if (m_pLastContext != currentContext)
802 {
803 // We had valid action
804 if (m_pLastUserAction)
805 {
806 // And we were performing it
807 if (m_bIsPerforming)
808 {
809 // Reset state
810 m_bIsPerforming = false;
811 m_fCurrentProgress = 0.0;
812
813 // Interruption event
814 character.DoCancelObjectAction(m_pLastUserAction);
815 }
816
817 // Reset state
818 m_pLastUserAction = null;
819 SetSelectedAction(m_pLastUserAction);
820 }
821 }
822
823 // Store last input
824 m_bLastInput = m_bPerformAction;
825
826 // Reset cached inputs to ensure that those actions will not be triggered on next frame
827 m_fSelectAction = 0;
828 if (!m_bIsPerforming)
829 m_bPerformAction = false;
830
831 // Update last context
832 m_pLastContext = currentContext;
833 }
834
835 protected ref array<BaseUserAction> m_ActionsBuffer = {};
836 protected ref array<bool> m_PerformBuffer = {};
837 protected ref map<string, ref array<int>> m_IndicesBuffer = new map<string, ref array<int>>();
838
843 protected void AggregateActions(array<BaseUserAction> actionsList, array<bool> canPerformList)
844 {
845 m_ActionsBuffer.Copy(actionsList);
846 m_PerformBuffer.Copy(canPerformList);
847 m_IndicesBuffer.Clear();
848 actionsList.Clear();
849 canPerformList.Clear();
850
851 // First pass, filter&gather
852 for (int i = 0, count = m_ActionsBuffer.Count(); i < count; i++)
853 {
854 BaseUserAction action = m_ActionsBuffer[i];
855 if (!action)
856 continue;
857
858 // For non-aggregated actions, skip this process
859 bool canPerform = m_PerformBuffer[i];
860 if (!action.CanAggregate())
861 continue;
862
863 // Group actions of same name for aggregation
864 string actionName = action.GetActionName();
865 if (!m_IndicesBuffer.Contains(actionName))
866 m_IndicesBuffer.Insert(actionName, {});
867
868 m_IndicesBuffer[actionName].Insert(i);
869 }
870
871 // Second pass, resolve&output
872 for (int i = 0, count = m_ActionsBuffer.Count(); i < count; i++)
873 {
874 BaseUserAction action = m_ActionsBuffer[i];
875 if (!action)
876 continue;
877
878 // For non-aggregated actions, append the action straight away
879 bool canPerform = m_PerformBuffer[i];
880 if (!action.CanAggregate())
881 {
882 actionsList.Insert(action);
883 canPerformList.Insert(canPerform);
884 continue;
885 }
886
887 // For aggregated actions, find group of given actions
888 string actionName = action.GetActionName();
889 // If group was sorted, it will be removed from the map,
890 // and no longer present, therefore we can ommit rechecking
891 if (!m_IndicesBuffer.Contains(actionName))
892 continue;
893
894 // If not resolved yet, resolve by finding available action
895 int availableIndex = m_IndicesBuffer[actionName][0]; // By default the first action
896 foreach (int index : m_IndicesBuffer[actionName])
897 {
898 // First performable hit
899 if (m_PerformBuffer[index])
900 {
901 availableIndex = index;
902 break;
903 }
904 }
905
906 BaseUserAction aggregatedAction = m_ActionsBuffer[availableIndex];
907 bool aggregatedState = m_PerformBuffer[availableIndex];
908 actionsList.Insert(aggregatedAction);
909 canPerformList.Insert(aggregatedState);
910 // And remove the action from the group map
911 m_IndicesBuffer.Remove(actionName);
912 }
913 }
914
915 //------------------------------------------------------------------------------------------------
916 float GetRaycastThreshold()
917 {
918 return m_fRaycastThreshold;
919 }
920
921 //------------------------------------------------------------------------------------------------
930 static bool CanBeShownInVehicle(notnull ChimeraCharacter character, notnull BaseUserAction userAction, bool pilotOnly = false, bool pilotUncapableOverride = false, bool interiorOnly = false, array<int> definedCompartmentsOnly = null, array<int> excludeDefinedCompartments = null)
931 {
932 // See if character is in "this" (owner) vehicle
933 CompartmentAccessComponent compartmentAccess = character.GetCompartmentAccessComponent();
934 if (!compartmentAccess)
935 return false;
936
937 Vehicle vehicle = Vehicle.Cast(userAction.GetOwner().GetRootParent());
938 if (!vehicle)
939 return false;
940
941 // Check interior only condition
942 // Character is in compartment
943 // that belongs to owner of this action
944 BaseCompartmentSlot slot = compartmentAccess.GetCompartment();
945 IEntity slotRootParent;
946 if (slot)
947 slotRootParent = slot.GetOwner().GetRootParent();
948
949 if (interiorOnly && (!slotRootParent || slotRootParent != vehicle))
950 return false;
951
952 // Check pilot only condition
953 if (pilotOnly && !PilotCompartmentSlot.Cast(slot))
954 {
955 ChimeraCharacter pilot = ChimeraCharacter.Cast(vehicle.GetPilot());
956 if (!pilotUncapableOverride && pilot != character)
957 return false;
958
959 if (pilot && pilot != character)
960 {
961 CharacterControllerComponent controller = pilot.GetCharacterController();
962 if (controller && controller.GetLifeState() == ECharacterLifeState.ALIVE)
963 return false;
964 }
965 }
966
967 int compartmentSection = -1;
968 if (slot)
969 compartmentSection = slot.GetCompartmentSection();
970
971 if (definedCompartmentsOnly && !definedCompartmentsOnly.IsEmpty() && (!definedCompartmentsOnly.Contains(compartmentSection) || vehicle != slotRootParent))
972 return false;
973
974 if (excludeDefinedCompartments && !excludeDefinedCompartments.IsEmpty() && (excludeDefinedCompartments.Contains(compartmentSection) || vehicle != slotRootParent))
975 return false;
976
977 return true;
978 }
979}
SCR_DebugMenuID
This enum contains all IDs for DiagMenu entries added in script.
Definition DebugMenuID.c:4
ArmaReforgerScripted GetGame()
Definition game.c:1398
void ActionPerform(SCR_BaseEditorAction action, vector cursorWorldPosition, int flags)
ActionFinishReason
Reason for why an action ended. Used in SCR_BaseInteractionDisplay and derived classes.
SCR_DestructionSynchronizationComponentClass ScriptComponentClass int index
void RemoveActionListeners()
SCR_InteractionHandlerComponentClass ALWAYS_ON
Nearby display will always be on (when possible).
SCR_InteractionHandlerComponentClass ON_INPUT_ACTION
Nearby display will show on provided input action.
string m_sActionName
enum EVehicleType IEntity
float m_fCurrentProgress
int GetCompartmentSection()
Switching seats is allowed only between compartments with matching area.
proto external void SetActiveContext(UserActionContext context)
Setter for m_pActiveContext.
proto external bool CanBeShown(IEntity user)
Can this action be shown in the UI for the user?
proto external string GetActionName()
proto external bool CanBePerformed(IEntity user)
Can this action be performed by the user?
proto external bool CanAggregate()
Whether action can be aggregated by name, this is a temp workaround for localization.
proto external Managed FindComponent(typename typeName)
proto external vector GetOrigin()
proto external vector GetWorldTransformAxis(int axis)
See IEntity::GetTransformAxis.
bool DoIntersectCheck(IEntity controlledEntity)
event bool CanContextChange(UserActionContext currentContext, UserActionContext newContext)
bool GetCanInteractScript(IEntity controlledEntity)
proto external bool IsAnyMenuOpen()
enum EPhysicsLayerPresets Vehicle
Definition gameLib.c:24
ECharacterLifeState
proto void Print(void var, LogLevel level=LogLevel.NORMAL)
Prints content of variable to console/log.
LogLevel
Enum with severity of the logging message.
Definition LogLevel.c:14
SCR_FieldOfViewSettings Attribute
@ DISABLED
General event switch.
Definition EntityEvent.c:35
EActionTrigger
void OnControlledEntityChanged(IEntity from, IEntity to)
Runs every time the controlled entity has been changed.
proto external void SetManualCollectionOverride(bool enabled)
If set to true, we expect a list of entities to be provided from the user instead.
void OnContextChanged(UserActionContext previousContext, UserActionContext newContext)
bool IsPerformingAction()
array< IEntity > GetManualNearbyOverrideList(IEntity owner, out vector referencePoint)
void OnPostFrame(IEntity owner, IEntity controlledEntity, float timeSlice)
proto external void SetNearbyCollectionEnabled(bool enabled)
ExtBaseInteractionHandlerComponentClass BaseInteractionHandlerComponentClass SetSelectedAction(BaseUserAction action)
array< IEntity > GetManualOverrideList(IEntity owner, out vector referencePoint)
proto external int GetFilteredActions(out notnull array< BaseUserAction > outActions, out notnull array< bool > outCanBePerformed)
proto external void SetManualNearbyCollectionOverride(bool enabled)
If set to true, we expect a list of nearby entities to be provided from the user instead.
proto external float GetVisibilityRange()
Returns the global actions visibility range value defined by attribute in this component.
proto external UserActionContext GetCurrentContext()
Returns currently gathered (active-preferred) context or null if none.
proto external bool IsContextAvailable()
Returns true when there is a gathered context available.