Arma Reforger Explorer  1.1.0.42
Arma Reforger Code Explorer by Zeroy - Thanks to MisterOutofTime
SCR_AIGroupTargetClusterProcessor.c
Go to the documentation of this file.
1 // Processes group's target clusters, performs decisions about what to do with them
2 
5 
7 {
8  protected const float INITIAL_DECISION_DELAY_MS = 5000.0;
9 
10  // When we don't receive new information from cluster for more than this amount of seconds,
11  // We switch to 'LOST' state.
12  protected const float MAX_CLUSTER_AGE_S = 120.0;
13 
14  protected SCR_AIGroupUtilityComponent m_Utility; // Owner utility component of this
15 
16  ref ScriptInvokerBase<SCR_AITargetClusterStateChanged> m_OnClusterStateChanged = new ScriptInvokerBase<SCR_AITargetClusterStateChanged>();
17 
18  //---------------------------------------------------------------------------
19  void UpdateCluster(SCR_AIGroupTargetCluster c, SCR_AITargetClusterState s, float deltaTime_ms)
20  {
21  s.m_fTimer_ms += deltaTime_ms;
22 
23  if (s.m_eState != s.m_ePrevState)
24  {
25  OnLeaveState(s, s.m_ePrevState);
26  OnEnterState(s, s.m_eState);
27  }
28 
29  EAITargetClusterState newDesiredState = EvaluateNewDesiredState(s);
30 
31  switch (s.m_eState)
32  {
33  case EAITargetClusterState.NONE:
34  {
35  if (newDesiredState == s.m_eState)
36  {
37  // Do nothing
38  if (s.m_Activity)
39  {
40  UnassignActivity(s);
41  }
42  }
43  else
44  {
45  if (s.m_fTimer_ms > INITIAL_DECISION_DELAY_MS)
46  {
47  SwitchToState(s, newDesiredState);
48  }
49  }
50  break;
51  }
52 
53 
54  case EAITargetClusterState.LOST:
55  {
56  if (newDesiredState == s.m_eState)
57  {
58  // Do nothing
59  if (s.m_Activity)
60  {
61  UnassignActivity(s);
62  }
63  }
64  else
65  {
66  SwitchToState(s, newDesiredState);
67  }
68  break;
69  }
70 
71  case EAITargetClusterState.ATTACKING:
72  case EAITargetClusterState.INVESTIGATING:
73  case EAITargetClusterState.DEFENDING:
74  {
75  if (newDesiredState == s.m_eState)
76  {
77  // We want to remain in this state
78 
79  if (!HasActivityForState(s, newDesiredState))
80  {
81  TFireteamLockRefArray mainFireteams = {};
82  TFireteamLockRefArray auxFireteams = {};
83 
84  if (s.m_Activity)
85  {
86  // There is already activity from previous state
87  // Complete it, but try to reallocate existing fireteams
88 
89  GetAssignedFireteams(SCR_AIFireteamsClusterActivity.Cast(s.m_Activity), mainFireteams, auxFireteams);
90  UnassignActivity(s);
91  }
92 
93  // Try to create a new activity
94  SCR_AIActivityBase activity = TryCreateActivityForState(s, newDesiredState, mainFireteams, auxFireteams);
95  if (activity)
96  {
97  AssignActivity(s, activity);
98  }
99  }
100  else
101  {
102  // There is an activity for this state already,
103  // If it is complete or failed, unassign it
104  if (s.m_Activity)
105  {
106  EAIActionState activityState = s.m_Activity.GetActionState();
107  if (activityState == EAIActionState.COMPLETED || activityState == EAIActionState.FAILED)
108  UnassignActivity(s);
109  }
110  }
111  }
112  else
113  {
114  // We want to switch to a different state
115  SwitchToState(s, newDesiredState);
116  }
117 
118  break;
119  }
120 
121  case EAITargetClusterState.UNINITIALIZED:
122  {
123  if (newDesiredState != s.m_eState)
124  {
125  SwitchToState(s, newDesiredState);
126  }
127  break;
128  }
129  }
130  }
131 
132  //--------------------------------------------------------------------------------
133  // Evaluates new desired state - what to do with this target cluster
134  EAITargetClusterState EvaluateNewDesiredState(SCR_AITargetClusterState s)
135  {
136  vector centerPos = 0.5*(s.m_vBBMin + s.m_vBBMax);
137 
138  bool playerControlled = m_Utility.m_Owner.IsSlave();
139 
140  if (s.GetTimeSinceLastNewInformation() > s.m_fMaxAge_s)
141  {
142  return EAITargetClusterState.LOST;
143  }
144  else if (!m_Utility.IsPositionAllowed(centerPos))
145  {
146  // We are not allowed to go there
147 
148  if (s.m_iCountIdentified > 0)
149  {
150  // For now we don't want to use DefendFromClusterActivity, as it only corresponds to defend waypoint and collides with its functionality.
151  return EAITargetClusterState.NONE;
152  //return EAITargetClusterState.DEFENDING; // We can only defend against this
153  }
154  }
155  else
156  {
157  // We are allowed to go there
158 
159  // Don't investigate if controlled by player
160 
161  if (s.m_iCountIdentified > 0)
162  return EAITargetClusterState.ATTACKING;
163  else if ( ((s.m_iCountLost + s.m_iCountDetected + s.m_iCountDestroyed) > 0) &&
164  !playerControlled)
165  return EAITargetClusterState.INVESTIGATING;
166  }
167 
168  return EAITargetClusterState.NONE;
169  }
170 
171  void OnLeaveState(SCR_AITargetClusterState s, EAITargetClusterState oldState)
172  {
173  }
174 
175  void OnEnterState(SCR_AITargetClusterState s, EAITargetClusterState newState)
176  {
177  // Calculate max cluster age threshold
178  s.m_fMaxAge_s = CalculateMaxAgeThreshold_s(s, newState);
179  }
180 
181  void SwitchToState(SCR_AITargetClusterState s, EAITargetClusterState newState)
182  {
183  #ifdef AI_DEBUG
184  AddDebugMessage(string.Format("%1: SwitchToState: %2", s, typename.EnumToString(EAITargetClusterState, newState)));
185  #endif
186 
187  s.m_ePrevState = s.m_eState;
188  s.m_eState = newState;
189  m_OnClusterStateChanged.Invoke(s, s.m_ePrevState, s.m_eState);
190  }
191 
192  void AssignActivity(notnull SCR_AITargetClusterState s, notnull SCR_AIActivityBase activity)
193  {
194  #ifdef AI_DEBUG
195  AddDebugMessage(string.Format("%1: AssignActivity: %2", s, activity));
196  #endif
197 
198  // Ensure a composite parallel activity for sub activities
199  SCR_AIClustersCompositeAction parallelAction = SCR_AIClustersCompositeAction.Cast(m_Utility.FindActionOfType(SCR_AIClustersCompositeAction));
200  if (!parallelAction)
201  {
202  parallelAction = new SCR_AIClustersCompositeAction();
203  m_Utility.AddAction(parallelAction);
204  }
205  parallelAction.AddAction(activity);
206 
207  s.m_Activity = activity;
208  }
209 
210  void UnassignActivity(SCR_AITargetClusterState s)
211  {
212  #ifdef AI_DEBUG
213  AddDebugMessage(string.Format("%1: UnassignActivity: %2", s, s.m_Activity));
214  #endif
215 
216  if (s.m_Activity)
217  {
218  EAIActionState activityState = s.m_Activity.GetActionState();
219  if (activityState != EAIActionState.FAILED && activityState != EAIActionState.COMPLETED)
220  s.m_Activity.Complete();
221  }
222 
223  s.m_Activity = null;
224  }
225 
226  bool HasActivityForState(SCR_AITargetClusterState s, EAITargetClusterState eState)
227  {
228  switch (eState)
229  {
230  // Return true if there is an activity of proper class
231  case EAITargetClusterState.INVESTIGATING:
232  return SCR_AIInvestigateClusterActivity.Cast(s.m_Activity) != null;
233  case EAITargetClusterState.ATTACKING:
234  return SCR_AIAttackClusterActivity.Cast(s.m_Activity) != null;
235  case EAITargetClusterState.DEFENDING:
236  return SCR_AIDefendFromClusterActivity.Cast(s.m_Activity) != null;
237  }
238 
239  return false;
240  }
241 
242  SCR_AIActivityBase TryCreateActivityForState(SCR_AITargetClusterState s, EAITargetClusterState estate, notnull TFireteamLockRefArray inFtsMain, notnull TFireteamLockRefArray inFtsAux)
243  {
244  TFireteamLockRefArray ftsMain;
245  TFireteamLockRefArray ftsAux;
246 
247  // ---- Try to allocate new fireteams, or reuse existing
248  array<SCR_AIGroupFireteam> newFireteams = {};
249  switch (estate)
250  {
251  case EAITargetClusterState.INVESTIGATING:
252  case EAITargetClusterState.ATTACKING:
253  {
254  //-----------------------------------
255  // Main fireteams
256 
257  // Reuse previous main fireteams if provided
258  if (!inFtsMain.IsEmpty())
259  ftsMain = SCR_AIGroupFireteamLock.CopyLockArray(inFtsMain); // Reuse old fireteams
260  else
261  ftsMain = {};
262 
263  // Reuse previous aux fireteams if provided
264  if (!inFtsAux.IsEmpty())
265  ftsAux = SCR_AIGroupFireteamLock.CopyLockArray(inFtsAux); // Reuse old fireteams
266  else
267  ftsAux = {};
268 
269  // Allocate even more fireteams if needed
270  AllocateMoreFireteams(s, ftsMain, ftsAux);
271 
272  // If by now we have nothing, it's pointless
273  if (ftsMain.IsEmpty())
274  return null;
275 
276 
277  //-----------------------------------
278  // Aux fireteams
279 
280 
281 
282  // Try to ensure at least one aux. fireteam
283  // If we have many main fireteams, distribute some to aux fireteams
284  if (ftsAux.IsEmpty())
285  {
286  if (ftsMain.Count() > 1)
287  {
288  SCR_AIGroupFireteamLock ftLock = ftsMain[ftsMain.Count()-1];
289  ftsMain.Remove(ftsMain.Count()-1);
290  ftsAux.Insert(ftLock);
291  }
292  else if (m_Utility.m_FireteamMgr.FindFreeFireteams(newFireteams, 1))
293  {
294  SCR_AIGroupFireteamLock.TryLockFireteams(newFireteams, ftsAux, true);
295  }
296  }
297  else if(ftsAux.Count() > 1)
298  {
299  // So far we don't want more than 1 aux fireteam to keep AI more engaged
300  while (ftsAux.Count() > 1)
301  {
302  SCR_AIGroupFireteamLock ftLock = ftsAux[ftsAux.Count()-1];
303  ftsMain.Insert(ftLock);
304  ftsAux.Remove(ftsAux.Count()-1);
305  }
306  }
307 
308  break;
309  }
310 
311  case EAITargetClusterState.DEFENDING:
312  {
313  if (!inFtsMain.IsEmpty() || !inFtsAux.IsEmpty())
314  {
315  // Here we need only one array of fireteams
316  // Combine all previous main and aux fireteams into one array
317  ftsMain = {};
318  foreach (auto ft : inFtsMain)
319  ftsMain.Insert(ft);
320  foreach (auto ft : inFtsAux)
321  ftsAux.Insert(ft);
322 
323  ftsAux = {};
324  }
325  else if (m_Utility.m_FireteamMgr.FindFreeFireteams(newFireteams, 1))
326  {
327  ftsMain = {};
328  SCR_AIGroupFireteamLock.TryLockFireteams(newFireteams, ftsMain, true);
329  ftsAux = {};
330  }
331  else
332  {
333  // Failed to find any fireteams
334  return null;
335  }
336 
337  break;
338  }
339 
340  default:
341  {
342  // Should not be possible to call this function for those states
343  return null;
344  }
345  }
346 
347 
348 
349  // ---- Create activity
350  // At this point we know we have found fireteams
351  SCR_AIActivityBase activity = null;
352  switch (estate)
353  {
354  case EAITargetClusterState.INVESTIGATING:
355  {
356  activity = new SCR_AIInvestigateClusterActivity(m_Utility, null, s, ftsMain, ftsAux);
357  //m_Utility.AddAction(activity);
358  //return activity;
359  break;
360  }
361  case EAITargetClusterState.ATTACKING:
362  {
363  activity = new SCR_AIAttackClusterActivity(m_Utility, null, s, ftsMain, ftsAux);
364  //m_Utility.AddAction(activity);
365  //return activity;
366  break;
367  }
368  case EAITargetClusterState.DEFENDING:
369  {
370  activity = new SCR_AIDefendFromClusterActivity(m_Utility, null, s, ftsMain);
371  //m_Utility.AddAction(activity);
372  //return activity;
373  break;
374  }
375  }
376 
377  return activity;
378  }
379 
380  void AllocateMoreFireteams(SCR_AITargetClusterState s, notnull TFireteamLockRefArray inOutFtLocksMain, notnull TFireteamLockRefArray ftLocksAux)
381  {
382  // We slightly overestimate amount of enemies to allocate even more people
383  float fEnemies = 1.3 * (float)s.m_iCountDetected + 1.3*s.m_iCountIdentified + 1.3*s.m_iCountLost + 0.5*s.m_iCountDestroyed;
384 
385  int nEnemies = Math.Ceil(fEnemies);
386 
387  // Count soldiers from what we have so far
388  int nSoldiersAllocated = 0;
389  foreach (SCR_AIGroupFireteamLock ftLock : inOutFtLocksMain)
390  nSoldiersAllocated += ftLock.GetFireteam().GetMemberCount();
391  foreach (SCR_AIGroupFireteamLock ftLock : ftLocksAux)
392  nSoldiersAllocated += ftLock.GetFireteam().GetMemberCount();
393 
394  // Allocate fireteams
395  array<SCR_AIGroupFireteam> freeFireteams = {};
396  m_Utility.m_FireteamMgr.GetFreeFireteams(freeFireteams);
397  while (nSoldiersAllocated < nEnemies && !freeFireteams.IsEmpty())
398  {
399  SCR_AIGroupFireteam newFireteam = freeFireteams[0];
400  SCR_AIGroupFireteamLock newFtLock = newFireteam.TryLock();
401  inOutFtLocksMain.Insert(newFtLock);
402  freeFireteams.Remove(0);
403 
404  nSoldiersAllocated += newFireteam.GetMemberCount();
405  }
406  }
407 
409  void GetAssignedFireteams(notnull SCR_AIFireteamsActivity fromActivity, notnull TFireteamLockRefArray outMainFireteams, notnull TFireteamLockRefArray outAuxFireteams)
410  {
411  outMainFireteams.Clear();
412  outAuxFireteams.Clear();
413 
414  if (SCR_AIInvestigateClusterActivity.Cast(fromActivity))
415  {
416  SCR_AIInvestigateClusterActivity.Cast(fromActivity).GetSpecificFireteams(outMainFireteams, outAuxFireteams);
417  return;
418  }
419  else if (SCR_AIAttackClusterActivity.Cast(fromActivity))
420  {
421  SCR_AIAttackClusterActivity.Cast(fromActivity).GetSpecificFireteams(outMainFireteams, outAuxFireteams);
422  return;
423  }
424  else if (SCR_AIDefendFromClusterActivity.Cast(fromActivity))
425  {
426  SCR_AIDefendFromClusterActivity.Cast(fromActivity).GetAssignedFireteams(outMainFireteams);
427  return;
428  }
429  return;
430  }
431 
433  float CalculateMaxAgeThreshold_s(SCR_AITargetClusterState s, EAITargetClusterState newState)
434  {
435  int countAlive = s.m_iCountLost + s.m_iCountDetected + s.m_iCountIdentified;
436 
437  if (countAlive > 0)
438  {
439  // If some targets are still alive
440  return MAX_CLUSTER_AGE_S;
441  }
442  else
443  {
444  // Everything is destroyed, investigation time is lower
445  vector ourPos = m_Utility.m_Owner.GetCenterOfMass();
446  vector tgtPos = 0.5 * (s.m_vBBMin + s.m_vBBMax);
447  float distance = vector.DistanceXZ(ourPos, tgtPos);
448  float tgtCount = s.m_iCountDestroyed + s.m_iCountIdentified;
449 
450  const float movementSpeed = 2.0; // Speed in m/s
451  float duration_s = distance / movementSpeed + 15.0 * tgtCount;
452  duration_s = Math.Max(20.0, duration_s);
453 
454  return duration_s;
455  }
456  }
457 
458  void SCR_AIGroupTargetClusterProcessor(SCR_AIGroupUtilityComponent utility)
459  {
460  m_Utility = utility;
461  }
462 
463  #ifdef AI_DEBUG
464  void AddDebugMessage(string str)
465  {
466  SCR_AIInfoBaseComponent infoComp = m_Utility.m_GroupInfo;
467  infoComp.AddDebugMessage(string.Format("%1: %2", this, str), msgType: EAIDebugMsgType.CLUSTER);
468  }
469  #endif
470 }
SCR_AIDefendFromClusterActivity
Definition: SCR_AIDefendFromClusterActivity.c:1
EAIDebugMsgType
EAIDebugMsgType
Definition: SCR_AIDebugMessage.c:1
SCR_AIFireteamsActivity
Definition: SCR_AIFireteamsActivity.c:6
SCR_AIGroupTargetClusterProcessor
Definition: SCR_AIGroupTargetClusterProcessor.c:6
func
func
Definition: SCR_AIThreatSystem.c:5
SCR_AIActivityBase
Definition: SCR_AIActivity.c:1
SCR_AIClustersCompositeAction
Definition: SCR_AIClustersCompositeAction.c:4
SCR_AIGroupFireteam
Definition: SCR_AIGroupFireteam.c:1
SCR_AIFireteamsClusterActivity
Definition: SCR_AIFireteamsClusterActivity.c:6
distance
float distance
Definition: SCR_DestructibleTreeV2.c:29
SCR_AIAttackClusterActivity
Definition: SCR_AIAttackClusterActivity.c:1
SCR_AIGroupTargetCluster
Definition: SCR_AIGroupTargetCluster.c:41
TFireteamLockRefArray
array< ref SCR_AIGroupFireteamLock > TFireteamLockRefArray
Definition: SCR_AIGroupFireteamLock.c:6
SCR_AITargetClusterStateChanged
func SCR_AITargetClusterStateChanged
Definition: SCR_AIGroupTargetClusterProcessor.c:4
SCR_AIGroupFireteamLock
Definition: SCR_AIGroupFireteamLock.c:9
SCR_AIInvestigateClusterActivity
Definition: SCR_AIInvestigateClusterActivity.c:1
SCR_AITargetClusterState
void SCR_AITargetClusterState(SCR_AIGroupTargetCluster cluster)
Definition: SCR_AITargetClusterState.c:38
EAITargetClusterState
EAITargetClusterState
Definition: SCR_AITargetClusterState.c:6
EAIActionState
EAIActionState
Definition: EAIActionState.c:12