Arma Reforger Explorer 1.7.0.54
Arma Reforger Code Explorer by Zeroy - Thanks to MisterOutofTime
Loading...
Searching...
No Matches
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 //---------------------------------------------------------------------------
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
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 {
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 {
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);
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)
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
135 {
136 vector centerPos = 0.5*(s.m_vBBMin + s.m_vBBMax);
137
138 bool playerControlled = m_Utility.m_Owner.IsSlave();
139
140 // Disregard if combat mode doesn't allow it
141 if (m_Utility.GetCombatModeActual() == EAIGroupCombatMode.HOLD_FIRE)
142 return EAITargetClusterState.NONE;
143
144 if (s.GetTimeSinceLastNewInformation() > s.m_fMaxAge_s)
145 {
146 return EAITargetClusterState.LOST;
147 }
148 else if (!m_Utility.IsPositionAllowed(centerPos))
149 {
150 // We are not allowed to go there
151
152 if (s.m_iCountIdentified > 0)
153 {
154 // For now we don't want to use DefendFromClusterActivity, as it only corresponds to defend waypoint and collides with its functionality.
155 return EAITargetClusterState.NONE;
156 //return EAITargetClusterState.DEFENDING; // We can only defend against this
157 }
158 }
159 else
160 {
161 // We are allowed to go there
162
163 // Don't investigate if controlled by player
164
165 if (s.m_iCountIdentified > 0)
166 return EAITargetClusterState.ATTACKING;
167 else if ( ((s.m_iCountLost + s.m_iCountDetected + s.m_iCountDestroyed) > 0) &&
168 !playerControlled)
169 return EAITargetClusterState.INVESTIGATING;
170 }
171
172 return EAITargetClusterState.NONE;
173 }
174
178
180 {
181 // Calculate max cluster age threshold
182 s.m_fMaxAge_s = CalculateMaxAgeThreshold_s(s, newState);
183 }
184
186 {
187 #ifdef AI_DEBUG
188 AddDebugMessage(string.Format("%1: SwitchToState: %2", s, typename.EnumToString(EAITargetClusterState, newState)));
189 #endif
190
191 s.m_ePrevState = s.m_eState;
192 s.m_eState = newState;
193 m_OnClusterStateChanged.Invoke(s, s.m_ePrevState, s.m_eState);
194 }
195
197 {
198 #ifdef AI_DEBUG
199 AddDebugMessage(string.Format("%1: AssignActivity: %2", s, activity));
200 #endif
201
202 // Ensure a composite parallel activity for sub activities
204 if (!parallelAction)
205 {
206 parallelAction = new SCR_AIClustersCompositeAction();
207 m_Utility.AddAction(parallelAction);
208 }
209 parallelAction.AddAction(activity);
210
211 s.m_Activity = activity;
212 }
213
215 {
216 #ifdef AI_DEBUG
217 AddDebugMessage(string.Format("%1: UnassignActivity: %2", s, s.m_Activity));
218 #endif
219
220 if (s.m_Activity)
221 {
222 EAIActionState activityState = s.m_Activity.GetActionState();
223 if (activityState != EAIActionState.FAILED && activityState != EAIActionState.COMPLETED)
224 s.m_Activity.Complete();
225 }
226
227 s.m_Activity = null;
228 }
229
231 {
232 switch (eState)
233 {
234 // Return true if there is an activity of proper class
235 case EAITargetClusterState.INVESTIGATING:
236 return SCR_AIInvestigateClusterActivity.Cast(s.m_Activity) != null;
237 case EAITargetClusterState.ATTACKING:
238 return SCR_AIAttackClusterActivity.Cast(s.m_Activity) != null;
239 case EAITargetClusterState.DEFENDING:
240 return SCR_AIDefendFromClusterActivity.Cast(s.m_Activity) != null;
241 }
242
243 return false;
244 }
245
247 {
248 TFireteamLockRefArray ftsMain;
250
251 // ---- Try to allocate new fireteams, or reuse existing
252 array<SCR_AIGroupFireteam> newFireteams = {};
253 switch (estate)
254 {
255 case EAITargetClusterState.INVESTIGATING:
256 case EAITargetClusterState.ATTACKING:
257 {
258 //-----------------------------------
259 // Main fireteams
260
261 // Reuse previous main fireteams if provided
262 if (!inFtsMain.IsEmpty())
263 ftsMain = SCR_AIGroupFireteamLock.CopyLockArray(inFtsMain); // Reuse old fireteams
264 else
265 ftsMain = {};
266
267 // Reuse previous aux fireteams if provided
268 if (!inFtsAux.IsEmpty())
269 ftsAux = SCR_AIGroupFireteamLock.CopyLockArray(inFtsAux); // Reuse old fireteams
270 else
271 ftsAux = {};
272
273 // Allocate even more fireteams if needed
274 AllocateMoreFireteams(s, ftsMain, ftsAux);
275
276 // If by now we have nothing, it's pointless
277 if (ftsMain.IsEmpty())
278 return null;
279
280
281 //-----------------------------------
282 // Aux fireteams
283
284
285
286 // Try to ensure at least one aux. fireteam
287 // If we have many main fireteams, distribute some to aux fireteams
288 if (ftsAux.IsEmpty())
289 {
290 if (ftsMain.Count() > 1)
291 {
292 SCR_AIGroupFireteamLock ftLock = ftsMain[ftsMain.Count()-1];
293 ftsMain.Remove(ftsMain.Count()-1);
294 ftsAux.Insert(ftLock);
295 }
296 else if (m_Utility.m_FireteamMgr.FindFreeFireteams(newFireteams, 1, SCR_AIGroupFireteam))
297 {
298 SCR_AIGroupFireteamLock.TryLockFireteams(newFireteams, ftsAux, true);
299 }
300 }
301 else if(ftsAux.Count() > 1)
302 {
303 // So far we don't want more than 1 aux fireteam to keep AI more engaged
304 while (ftsAux.Count() > 1)
305 {
306 SCR_AIGroupFireteamLock ftLock = ftsAux[ftsAux.Count()-1];
307 ftsMain.Insert(ftLock);
308 ftsAux.Remove(ftsAux.Count()-1);
309 }
310 }
311
312 break;
313 }
314
315 case EAITargetClusterState.DEFENDING:
316 {
317 if (!inFtsMain.IsEmpty() || !inFtsAux.IsEmpty())
318 {
319 // Here we need only one array of fireteams
320 // Combine all previous main and aux fireteams into one array
321 ftsMain = {};
322 foreach (auto ft : inFtsMain)
323 ftsMain.Insert(ft);
324 foreach (auto ft : inFtsAux)
325 ftsAux.Insert(ft);
326
327 ftsAux = {};
328 }
329 else if (m_Utility.m_FireteamMgr.FindFreeFireteams(newFireteams, 1, SCR_AIGroupFireteam))
330 {
331 ftsMain = {};
332 SCR_AIGroupFireteamLock.TryLockFireteams(newFireteams, ftsMain, true);
333 ftsAux = {};
334 }
335 else
336 {
337 // Failed to find any fireteams
338 return null;
339 }
340
341 break;
342 }
343
344 default:
345 {
346 // Should not be possible to call this function for those states
347 return null;
348 }
349 }
350
351
352
353 // ---- Create activity
354 // At this point we know we have found fireteams
355 SCR_AIActivityBase activity = null;
356 switch (estate)
357 {
358 case EAITargetClusterState.INVESTIGATING:
359 {
360 activity = new SCR_AIInvestigateClusterActivity(m_Utility, null, s, ftsMain, ftsAux);
361 //m_Utility.AddAction(activity);
362 //return activity;
363 break;
364 }
365 case EAITargetClusterState.ATTACKING:
366 {
367 activity = new SCR_AIAttackClusterActivity(m_Utility, null, s, ftsMain, ftsAux);
368 //m_Utility.AddAction(activity);
369 //return activity;
370 break;
371 }
372 case EAITargetClusterState.DEFENDING:
373 {
374 activity = new SCR_AIDefendFromClusterActivity(m_Utility, null, s, ftsMain);
375 //m_Utility.AddAction(activity);
376 //return activity;
377 break;
378 }
379 }
380
381 return activity;
382 }
383
385 {
386 // We slightly overestimate amount of enemies to allocate even more people
387 float fEnemies = 1.3 * (float)s.m_iCountDetected + 1.3*s.m_iCountIdentified + 1.3*s.m_iCountLost + 0.5*s.m_iCountDestroyed;
388
389 int nEnemies = Math.Ceil(fEnemies);
390
391 // Count soldiers from what we have so far
392 int nSoldiersAllocated = 0;
393 foreach (SCR_AIGroupFireteamLock ftLock : inOutFtLocksMain)
394 nSoldiersAllocated += ftLock.GetFireteam().GetMemberCount();
395 foreach (SCR_AIGroupFireteamLock ftLock : ftLocksAux)
396 nSoldiersAllocated += ftLock.GetFireteam().GetMemberCount();
397
398 // Allocate fireteams
399 array<SCR_AIGroupFireteam> freeFireteams = {};
400 m_Utility.m_FireteamMgr.GetFreeFireteams(freeFireteams, SCR_AIGroupFireteam);
401 while (nSoldiersAllocated < nEnemies && !freeFireteams.IsEmpty())
402 {
403 SCR_AIGroupFireteam newFireteam = freeFireteams[0];
404 SCR_AIGroupFireteamLock newFtLock = newFireteam.TryLock();
405 inOutFtLocksMain.Insert(newFtLock);
406 freeFireteams.Remove(0);
407
408 nSoldiersAllocated += newFireteam.GetMemberCount();
409 }
410 }
411
413 void GetAssignedFireteams(notnull SCR_AIFireteamsActivity fromActivity, notnull TFireteamLockRefArray outMainFireteams, notnull TFireteamLockRefArray outAuxFireteams)
414 {
415 outMainFireteams.Clear();
416 outAuxFireteams.Clear();
417
418 if (SCR_AIInvestigateClusterActivity.Cast(fromActivity))
419 {
420 SCR_AIInvestigateClusterActivity.Cast(fromActivity).GetSpecificFireteams(outMainFireteams, outAuxFireteams);
421 return;
422 }
423 else if (SCR_AIAttackClusterActivity.Cast(fromActivity))
424 {
425 SCR_AIAttackClusterActivity.Cast(fromActivity).GetSpecificFireteams(outMainFireteams, outAuxFireteams);
426 return;
427 }
428 else if (SCR_AIDefendFromClusterActivity.Cast(fromActivity))
429 {
430 SCR_AIDefendFromClusterActivity.Cast(fromActivity).GetAssignedFireteams(outMainFireteams);
431 return;
432 }
433 return;
434 }
435
438 {
439 int countAlive = s.m_iCountLost + s.m_iCountDetected + s.m_iCountIdentified;
440
441 if (countAlive > 0)
442 {
443 // If some targets are still alive
444 return MAX_CLUSTER_AGE_S;
445 }
446 else
447 {
448 // Everything is destroyed, investigation time is lower
449 vector ourPos = m_Utility.m_Owner.GetCenterOfMass();
450 vector tgtPos = 0.5 * (s.m_vBBMin + s.m_vBBMax);
451 float distance = vector.DistanceXZ(ourPos, tgtPos);
452 float tgtCount = s.m_iCountDestroyed + s.m_iCountIdentified;
453
454 const float movementSpeed = 2.0; // Speed in m/s
455 float duration_s = distance / movementSpeed + 15.0 * tgtCount;
456 duration_s = Math.Max(20.0, duration_s);
457
458 return duration_s;
459 }
460 }
461
466
467 #ifdef AI_DEBUG
468 void AddDebugMessage(string str)
469 {
470 SCR_AIInfoBaseComponent infoComp = m_Utility.m_GroupInfo;
471 infoComp.AddDebugMessage(string.Format("%1: %2", this, str), msgType: EAIDebugMsgType.CLUSTER);
472 }
473 #endif
474}
void SCR_AIActivityBase(SCR_AIGroupUtilityComponent utility, AIWaypoint relatedWaypoint)
enum SCR_EAIActivityCause m_Utility
EAIDebugMsgType
array< ref SCR_AIGroupFireteamLock > TFireteamLockRefArray
func SCR_AITargetClusterStateChanged
void SCR_AITargetClusterState(SCR_AIGroupTargetCluster cluster)
float distance
Definition Math.c:13
void GetSpecificFireteams(notnull TFireteamLockRefArray fireteamsAttack, notnull TFireteamLockRefArray fireteamsCover)
SCR_AIGroupFireteamLock TryLock()
static void TryLockFireteams(notnull array< SCR_AIGroupFireteam > fireteams, notnull TFireteamLockRefArray locks, bool clearLockArray)
Tries to lock all fireteams, returns a new array of locks for those fireteams it was able to lock.
static TFireteamLockRefArray CopyLockArray(TFireteamLockRefArray other)
Creates a copy of locks array.
bool HasActivityForState(SCR_AITargetClusterState s, EAITargetClusterState eState)
void AllocateMoreFireteams(SCR_AITargetClusterState s, notnull TFireteamLockRefArray inOutFtLocksMain, notnull TFireteamLockRefArray ftLocksAux)
void UnassignActivity(SCR_AITargetClusterState s)
ref ScriptInvokerBase< SCR_AITargetClusterStateChanged > m_OnClusterStateChanged
void SCR_AIGroupTargetClusterProcessor(SCR_AIGroupUtilityComponent utility)
void UpdateCluster(SCR_AIGroupTargetCluster c, SCR_AITargetClusterState s, float deltaTime_ms)
void AssignActivity(notnull SCR_AITargetClusterState s, notnull SCR_AIActivityBase activity)
void OnEnterState(SCR_AITargetClusterState s, EAITargetClusterState newState)
SCR_AIActivityBase TryCreateActivityForState(SCR_AITargetClusterState s, EAITargetClusterState estate, notnull TFireteamLockRefArray inFtsMain, notnull TFireteamLockRefArray inFtsAux)
float CalculateMaxAgeThreshold_s(SCR_AITargetClusterState s, EAITargetClusterState newState)
Calculates how long we should investigate this target cluster.
void SwitchToState(SCR_AITargetClusterState s, EAITargetClusterState newState)
void OnLeaveState(SCR_AITargetClusterState s, EAITargetClusterState oldState)
EAITargetClusterState EvaluateNewDesiredState(SCR_AITargetClusterState s)
void GetAssignedFireteams(notnull SCR_AIFireteamsActivity fromActivity, notnull TFireteamLockRefArray outMainFireteams, notnull TFireteamLockRefArray outAuxFireteams)
Returns arrays with fireteams already assigned to this cluster, if there are any. Otherwise returns e...
void GetSpecificFireteams(notnull TFireteamLockRefArray fireteamsInvestigate, notnull TFireteamLockRefArray fireteamsCover)
Definition float.c:13
EAIActionState