17 const float TARGET_LOST_THRESHOLD_S = 45.0;
21 const float TARGET_FORGET_THRESHOLD_S = 150.0;
24 protected SCR_AIGroupUtilityComponent m_Utility;
26 protected ref ScriptInvokerBase<SCR_AIOnTargetClusterStateDeleted> Event_OnTargetClusterStateDeleted =
new ScriptInvokerBase<SCR_AIOnTargetClusterStateDeleted>();
27 protected ref ScriptInvokerBase<SCR_AIGroupPerceptionOnEnemyDetected> Event_OnEnemyDetected;
28 protected ref ScriptInvokerBase<SCR_AIGroupPerceptionOnEnemyDetectedFiltered> Event_OnEnemyDetectedFiltered;
29 protected ref ScriptInvokerBase<SCR_AIGroupPerceptionOnNoEnemy> Event_OnNoEnemy;
31 ref array<IEntity> m_aTargetEntities =
new array<IEntity>;
32 ref array<ref SCR_AITargetInfo> m_aTargets =
new array<ref SCR_AITargetInfo>;
33 ref array<ref SCR_AIGroupTargetCluster> m_aTargetClusters = {};
44 ScriptInvokerBase<SCR_AIOnTargetClusterStateDeleted> GetOnTargetClusterDeleted()
46 return Event_OnTargetClusterStateDeleted;
50 ScriptInvokerBase<SCR_AIGroupPerceptionOnEnemyDetected> GetOnEnemyDetected()
52 if (!Event_OnEnemyDetected)
53 Event_OnEnemyDetected =
new ScriptInvokerBase<SCR_AIGroupPerceptionOnEnemyDetected>();
55 return Event_OnEnemyDetected;
61 ScriptInvokerBase<SCR_AIGroupPerceptionOnEnemyDetectedFiltered> GetOnEnemyDetectedFiltered()
63 if (!Event_OnEnemyDetectedFiltered)
64 Event_OnEnemyDetectedFiltered =
new ScriptInvokerBase<SCR_AIGroupPerceptionOnEnemyDetectedFiltered>();
66 return Event_OnEnemyDetectedFiltered;
70 ScriptInvokerBase<SCR_AIGroupPerceptionOnNoEnemy> GetOnNoEnemy()
73 Event_OnNoEnemy =
new ScriptInvokerBase<SCR_AIGroupPerceptionOnNoEnemy>();
75 return Event_OnNoEnemy;
79 protected void RemoveTarget(IEntity enemy)
84 int index = m_aTargetEntities.Find(enemy);
88 m_aTargetEntities.Remove(
index);
89 m_aTargets.Remove(
index);
90 if (m_aTargetEntities.IsEmpty() && Event_OnNoEnemy)
91 Event_OnNoEnemy.Invoke(
m_Group);
96 protected void RemoveTarget(
int id)
98 m_aTargetEntities.Remove(
id);
99 m_aTargets.Remove(
id);
101 if (m_aTargetEntities.IsEmpty() && Event_OnNoEnemy)
102 Event_OnNoEnemy.Invoke(
m_Group);
107 SCR_AITargetInfo AddOrUpdateTarget(notnull
BaseTarget target, out
bool outNewTarget)
109 IEntity enemy = target.GetTargetEntity();
113 outNewTarget =
false;
117 int id = m_aTargetEntities.Find(enemy);
120 SCR_AITargetInfo oldTargetInfo = m_aTargets[id];
126 outNewTarget =
false;
127 return oldTargetInfo;
135 newTimestamp = target.GetTimeLastDetected();
137 newTimestamp = target.GetTimeLastDetected();
139 if (oldTargetInfo.m_fTimestamp < newTimestamp)
146 oldTargetInfo.UpdateFromBaseTarget(target);
150 outNewTarget =
false;
151 return oldTargetInfo;
157 if (target.IsDisarmed())
159 outNewTarget =
false;
163 SCR_AITargetInfo targetInfo =
new SCR_AITargetInfo();
164 targetInfo.InitFromBaseTarget(target);
166 m_aTargetEntities.Insert(enemy);
167 m_aTargets.Insert(targetInfo);
169 if (Event_OnEnemyDetected)
171 Event_OnEnemyDetected.Invoke(
m_Group, targetInfo);
179 void AddOrUpdateGunshot(notnull IEntity shooter, vector worldPos,
float timestamp)
181 int id = m_aTargetEntities.Find(shooter);
185 SCR_AITargetInfo oldTargetInfo = m_aTargets[id];
187 (timestamp > oldTargetInfo.m_fTimestamp))
189 oldTargetInfo.UpdateFromGunshot(worldPos, timestamp);
195 SCR_AITargetInfo targetInfo =
new SCR_AITargetInfo();
196 targetInfo.InitFromGunshot(shooter, worldPos, timestamp);
198 m_aTargets.Insert(targetInfo);
199 m_aTargetEntities.Insert(shooter);
206 UpdateTargetsFromMembers();
212 protected void UpdateTargetsFromMembers()
214 IEntity leaderEntity =
m_Group.GetLeaderEntity();
221 array<AIAgent> agents = {};
224 array<BaseTarget> targets = {};
227 SCR_AITargetInfo targetInfo;
228 bool invokedEvent =
false;
230 foreach (SCR_AIInfoComponent infoComp : m_Utility.m_aInfoComponents)
232 PerceptionComponent perception = infoComp.m_Perception;
238 targetInfo = AddOrUpdateTarget(baseTarget, targetIsNew);
240 if (targetIsNew && !invokedEvent)
242 if (Event_OnEnemyDetectedFiltered)
244 AIAgent reporter = AIAgent.Cast(infoComp.GetOwner());
245 Event_OnEnemyDetectedFiltered.Invoke(
m_Group, targetInfo, reporter);
255 targetInfo = AddOrUpdateTarget(baseTarget, targetIsNew);
257 if (targetIsNew && !invokedEvent)
259 if (Event_OnEnemyDetectedFiltered)
261 AIAgent reporter = AIAgent.Cast(infoComp.GetOwner());
262 Event_OnEnemyDetectedFiltered.Invoke(
m_Group, targetInfo, reporter);
271 protected void MaintainTargets()
274 PerceptionManager pm =
GetGame().GetPerceptionManager();
276 timeNow = pm.GetTime();
278 for (
int i = m_aTargets.Count()-1; i >= 0; i--)
280 SCR_AITargetInfo tgtInfo = m_aTargets[i];
282 if (timeNow - tgtInfo.m_fTimestamp > TARGET_FORGET_THRESHOLD_S)
288 if (tgtInfo.m_DamageManager && tgtInfo.m_DamageManager.IsDestroyed())
290 if (tgtInfo.m_Perceivable && !tgtInfo.m_Perceivable.IsDisarmed())
295 if (tgtInfo.m_DamageManager && tgtInfo.m_DamageManager.IsDestroyed())
297 else if (!tgtInfo.m_Entity)
299 else if (timeNow - tgtInfo.m_fTimestamp > TARGET_LOST_THRESHOLD_S)
301 else if (tgtInfo.m_Perceivable && tgtInfo.m_Perceivable.IsDisarmed())
308 protected void ClusterTargets()
310 vector centerPos =
m_Group.GetCenterOfMass();
312 if (centerPos == vector.Zero)
315 float minAngularDist = Math.DEG2RAD * 30.0;
316 array<ref SCR_AIGroupTargetCluster> newClusters = {};
317 GenerateClusters(m_aTargets, newClusters, centerPos, minAngularDist);
320 array<ref array<int>> association = {};
321 association.Resize(m_aTargetClusters.Count());
322 for (
int oldClusterId = 0; oldClusterId < m_aTargetClusters.Count(); oldClusterId++)
324 array<int> row =
new array<int>();
325 row.Resize(newClusters.Count());
326 association[oldClusterId] = row;
328 for (
int newClusterId = 0; newClusterId < newClusters.Count(); newClusterId++)
330 int associationValue = GetClusterAssociation(m_aTargetClusters[oldClusterId], newClusters[newClusterId]);
331 row[newClusterId] = associationValue;
337 array<int> newClusterStateIds = {};
338 newClusterStateIds.Resize(newClusters.Count());
339 for (
int i = 0; i < newClusters.Count(); i++)
340 newClusterStateIds[i] = -1;
342 for (
int oldClusterId = 0; oldClusterId < m_aTargetClusters.Count(); oldClusterId++)
345 int maxAssociation = 0;
346 int newClusterIdMaxAssociation = -1;
348 array<int> row = association[oldClusterId];
349 for (
int i = 0; i < row.Count(); i++)
351 if (row[i] > maxAssociation)
353 maxAssociation = row[i];
354 newClusterIdMaxAssociation = i;
358 bool transferClusterState =
false;
360 if (newClusterIdMaxAssociation != -1)
365 int stateTransferedFrom = newClusterStateIds[newClusterIdMaxAssociation];
366 if (stateTransferedFrom != -1)
371 if (association[stateTransferedFrom][newClusterIdMaxAssociation] < association[oldClusterId][newClusterIdMaxAssociation])
373 DeleteClusterState(newClusters[newClusterIdMaxAssociation]);
374 transferClusterState =
true;
379 transferClusterState =
true;
383 if (transferClusterState)
385 TransferClusterState(m_aTargetClusters[oldClusterId], newClusters[newClusterIdMaxAssociation]);
386 newClusterStateIds[newClusterIdMaxAssociation] = oldClusterId;
391 DeleteClusterState(m_aTargetClusters[oldClusterId]);
402 c.m_State.ProcessTargets();
405 m_aTargetClusters = newClusters;
411 void GenerateClusters(array<ref SCR_AITargetInfo> targets, array<ref SCR_AIGroupTargetCluster> outClusters, vector centerPos,
float minAngularDist)
416 if (targets.IsEmpty())
419 int nTargets = targets.Count();
423 array<ref SCR_AITargetInfoPolar> targetsPolar = {};
424 targetsPolar.Resize(nTargets);
426 for (
int i = 0; i < nTargets; i++)
428 SCR_AITargetInfo target = targets[i];
429 vector vdir = target.m_vWorldPos - centerPos;
431 float dist = vector.DistanceXZ(target.m_vWorldPos, centerPos);
433 SCR_AITargetInfoPolar targetPolar =
new SCR_AITargetInfoPolar();
434 targetPolar.m_Target = target;
435 targetPolar.m_fAngle = angle;
436 targetPolar.m_fDistance = dist;
438 targetsPolar[i] = targetPolar;
445 targetsPolar.Sort(
false);
450 currentCluster.m_fAngleMin = targetsPolar[0].m_fAngle;
451 currentCluster.m_fAngleMax = currentCluster.m_fAngleMin;
453 outClusters.Insert(currentCluster);
455 for (
int i = 1; i < nTargets; i++)
457 SCR_AITargetInfoPolar target = targetsPolar[i];
459 float angle = target.m_fAngle;
461 if (angle - currentCluster.m_fAngleMax < minAngularDist)
464 currentCluster.AddTarget(target.m_Target, angle, target.m_fDistance);
465 currentCluster.m_fAngleMax = angle;
471 currentCluster.AddTarget(target.m_Target, target.m_fAngle, target.m_fDistance);
472 currentCluster.m_fAngleMin = target.m_fAngle;
473 currentCluster.m_fAngleMax = currentCluster.m_fAngleMin;
474 outClusters.Insert(currentCluster);
480 if (outClusters.Count() > 1)
485 float distLastToFirst = Math.PI2 - last.m_fAngleMax + first.m_fAngleMin;
486 if (distLastToFirst < minAngularDist)
489 first.AddCluster(last);
490 outClusters.RemoveOrdered(outClusters.Count()-1);
501 int intersection = 0;
502 array<SCR_AITargetInfo> bTargets = b.m_aTargets;
503 foreach (SCR_AITargetInfo aTarget : a.m_aTargets)
506 bTargets.Find(aTarget) != -1)
515 Event_OnTargetClusterStateDeleted.Invoke(c.m_State);
522 newCluster.MoveStateFrom(oldCluster);
537 void DiagPrintClusterAssociation(array<ref array<int>> association)
539 Print(
"Cluster association:");
540 for (
int oldClusterId = 0; oldClusterId < association.Count(); oldClusterId++)
542 array<int> row = association[oldClusterId];
544 string s =
string.Format(
"%1: ", oldClusterId);
545 for (
int newClusterId = 0; newClusterId < row.Count(); newClusterId++)
546 s = s +
string.Format(
"%1\t", row[newClusterId].ToString());
554 void DiagDrawClusters()
556 PerceptionManager pm =
GetGame().GetPerceptionManager();
557 float timeNow_s = pm.GetTime();
561 foreach (SCR_AITargetInfo targetInfo : cluster.m_aTargets)
563 vector mrkPos = targetInfo.m_vWorldPos + Vector(0, 3, 0);
565 string s =
string.Empty;
567 float timeSinceTimestamp_s = timeNow_s - targetInfo.m_fTimestamp;
568 s = s +
string.Format(
"C: %1, time-timestamp: %2\n", clusterId.ToString(), timeSinceTimestamp_s.ToString(5, 1));
570 s = s +
string.Format(
"Category: %1",
typename.EnumToString(
EAITargetInfoCategory, targetInfo.m_eCategory));
572 DebugTextWorldSpace.Create(
GetGame().GetWorld(), s, DebugTextFlags.ONCE | DebugTextFlags.CENTER | DebugTextFlags.FACE_CAMERA,
573 mrkPos[0], mrkPos[1], mrkPos[2],
574 color: 0xFFFFFFFF, bgColor: 0xFF000000,
579 vector clusterCenter = cluster.m_State.GetCenterPosition();
582 string s =
string.Empty;
584 s = s +
string.Format(
"Cluster: %1\n", clusterId);
587 s = s +
string.Format(
"State: %1, Time since Info: %2, Max age: %3",
589 cluster.m_State.GetTimeSinceLastNewInformation().ToString(5,1),
590 cluster.m_State.m_fMaxAge_s.ToString(5, 1));
593 DebugTextWorldSpace.Create(
GetGame().GetWorld(), s, DebugTextFlags.ONCE | DebugTextFlags.CENTER | DebugTextFlags.FACE_CAMERA,
594 clusterCenter[0], clusterCenter[1] + 10.0, clusterCenter[2],
595 color: 0xFFFFFFFF, bgColor: 0xFF000000,