Reconcile radio-delivered body and status casualties by original roster identity

This commit is contained in:
2026-09-20 03:09:16 +02:00
parent a2aab17171
commit 9b6596eb3e
44 changed files with 6042 additions and 86 deletions
@@ -3,6 +3,10 @@
class ERR_HQBodyCandidate
{
IEntity Corpse;
ref ERR_HQStatusSampler Roster;
string SourceId;
int Slot = -1;
int Strength;
bool Confirmed;
float ReportAfter;
}
@@ -67,9 +71,9 @@ class ERR_HQBodyObserver
for (int i = m_Groups.Count() - 1; i >= 0; i--)
if (!m_Groups[i].Group) m_Groups.Remove(i);
}
void RegisterGroup(SCR_AIGroup group)
void RegisterGroup(SCR_AIGroup group, ERR_HQStatusSampler sampler)
{
if (!Replication.IsServer() || !OwnedPhysical(group)) return;
if (!Replication.IsServer() || !OwnedPhysical(group) || !sampler || !sampler.OwnsRoster(group)) return;
Prune();
bool known;
foreach (ERR_HQBodyScan scan : m_Groups) if (scan.Group == group) known = true;
@@ -79,17 +83,18 @@ class ERR_HQBodyObserver
ERR_HQBodyScan created = new ERR_HQBodyScan(); created.Group = group;
m_Groups.Insert(created);
}
array<AIAgent> agents = {}; group.GetAgents(agents);
for (int member = 0; member < agents.Count() && member < 32; member++)
// Slot identity comes only from the immutable sampler bind, never current agent order.
for (int member = 0; member < sampler.InitialStrength(); member++)
{
AIAgent agent = agents[member];
if (!agent || !agent.GetControlledEntity()) continue;
IEntity actor = agent.GetControlledEntity();
IEntity actor = sampler.InitialMember(member);
if (!sampler.CanAttribute(member, actor)) continue;
bool found;
foreach (ERR_HQBodyCandidate entry : m_Roster) if (entry.Corpse == actor) found = true;
if (found) continue;
if (m_Roster.Count() >= 128) { Cap(); return; }
ERR_HQBodyCandidate candidate = new ERR_HQBodyCandidate(); candidate.Corpse = actor;
candidate.Roster = sampler; candidate.SourceId = sampler.InitialSourceId();
candidate.Slot = member; candidate.Strength = sampler.InitialStrength();
m_Roster.Insert(candidate);
}
}
@@ -171,6 +176,7 @@ class ERR_HQBodyObserver
if (state.Cursor >= count) state.Cursor = 0;
ERR_HQBodyCandidate candidate = m_Bodies[state.Cursor++];
if (!candidate.Corpse || candidate.Confirmed || now < candidate.ReportAfter || !Dead(candidate.Corpse)) continue;
if (!candidate.Roster || !candidate.Roster.CanAttribute(candidate.Slot, candidate.Corpse)) continue;
for (int member = 0; member < agents.Count() && member < 32; member++)
{
if (m_Traces <= 0) return false;
@@ -181,6 +187,8 @@ class ERR_HQBodyObserver
report = new ERR_HQReport();
report.Target = candidate.Corpse; report.Source = group;
report.Position = position; report.ObservedAt = now; report.TravelMode = "body";
report.CasualtySourceId = candidate.SourceId; report.CasualtySlot = candidate.Slot;
report.CasualtyStrength = candidate.Strength;
// Local throttling only. A lost radio call can be retried after fresh LOS.
candidate.ReportAfter = now + 15000;
return true;
@@ -188,7 +196,7 @@ class ERR_HQBodyObserver
}
return false;
}
// Call only after a radio-delivered body snapshot was accepted by HQ memory.
// Call only after the radio-delivered immutable body attribution was accepted by HQ.
void ConfirmDelivered(IEntity body)
{
if (!Replication.IsServer() || !body) return;