Integrate delivered HQ memory, finite physical convoy and RHS WCS dependencies
This commit is contained in:
@@ -0,0 +1,203 @@
|
||||
// Adapted from the user-owned AI body's visual discovery rules only.
|
||||
// Candidate casualty identities are local perception work, never delivered HQ knowledge.
|
||||
class ERR_HQBodyCandidate
|
||||
{
|
||||
IEntity Corpse;
|
||||
bool Confirmed;
|
||||
float ReportAfter;
|
||||
}
|
||||
|
||||
class ERR_HQBodyScan
|
||||
{
|
||||
SCR_AIGroup Group;
|
||||
float NextScan;
|
||||
float LastAsked = -1;
|
||||
int Cursor;
|
||||
}
|
||||
|
||||
class ERR_HQBodyRules
|
||||
{
|
||||
static bool InView(float distance, float facing)
|
||||
{
|
||||
return ERR_HQMemoryRules.Finite(distance) && ERR_HQMemoryRules.Finite(facing)
|
||||
&& distance >= 0 && distance <= 60 && (distance <= 12 || facing >= 0.5);
|
||||
}
|
||||
static bool Clear(float fraction, bool hitBody)
|
||||
{
|
||||
return ERR_HQMemoryRules.Finite(fraction) && fraction >= 0 && (fraction >= 1 || hitBody);
|
||||
}
|
||||
}
|
||||
|
||||
class ERR_HQBodyObserver
|
||||
{
|
||||
protected ref array<ref ERR_HQBodyCandidate> m_Roster = {};
|
||||
protected ref array<ref ERR_HQBodyCandidate> m_Bodies = {};
|
||||
protected ref array<ref ERR_HQBodyScan> m_Groups = {};
|
||||
protected float m_NextBudget;
|
||||
protected float m_Now = -1;
|
||||
protected int m_Traces;
|
||||
protected int m_RosterCursor;
|
||||
protected bool m_CapLogged;
|
||||
|
||||
protected bool OwnedPhysical(SCR_AIGroup group)
|
||||
{
|
||||
ERR_GameMode mode = ERR_GameMode.Get();
|
||||
if (!mode || !group || group.GetFaction() != mode.EnemyFaction()) return false;
|
||||
ERR_ForceRecord force = mode.Forces().FindGroup(group);
|
||||
return force && force.Physical && !force.Eliminated;
|
||||
}
|
||||
protected bool Dead(IEntity actor)
|
||||
{
|
||||
ChimeraCharacter character = ChimeraCharacter.Cast(actor);
|
||||
return character && character.GetCharacterController()
|
||||
&& character.GetCharacterController().GetLifeState() == ECharacterLifeState.DEAD;
|
||||
}
|
||||
protected void Cap()
|
||||
{
|
||||
if (m_CapLogged) return;
|
||||
m_CapLogged = true;
|
||||
Print("Rereintroduced [body observer]: finite local registry limit reached; no discovery work added.");
|
||||
}
|
||||
protected void Prune()
|
||||
{
|
||||
for (int i = m_Bodies.Count() - 1; i >= 0; i--)
|
||||
if (!m_Bodies[i].Corpse || m_Bodies[i].Confirmed || !Dead(m_Bodies[i].Corpse)) m_Bodies.Remove(i);
|
||||
for (int i = m_Roster.Count() - 1; i >= 0; i--)
|
||||
if (!m_Roster[i].Corpse) m_Roster.Remove(i);
|
||||
for (int i = m_Groups.Count() - 1; i >= 0; i--)
|
||||
if (!m_Groups[i].Group) m_Groups.Remove(i);
|
||||
}
|
||||
void RegisterGroup(SCR_AIGroup group)
|
||||
{
|
||||
if (!Replication.IsServer() || !OwnedPhysical(group)) return;
|
||||
Prune();
|
||||
bool known;
|
||||
foreach (ERR_HQBodyScan scan : m_Groups) if (scan.Group == group) known = true;
|
||||
if (!known)
|
||||
{
|
||||
if (m_Groups.Count() >= 128) { Cap(); return; }
|
||||
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++)
|
||||
{
|
||||
AIAgent agent = agents[member];
|
||||
if (!agent || !agent.GetControlledEntity()) continue;
|
||||
IEntity actor = agent.GetControlledEntity();
|
||||
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;
|
||||
m_Roster.Insert(candidate);
|
||||
}
|
||||
}
|
||||
void TickBudget(float now)
|
||||
{
|
||||
if (!Replication.IsServer() || !ERR_HQMemoryRules.Finite(now) || now < 0 || now < m_Now) return;
|
||||
m_Now = now;
|
||||
if (now < m_NextBudget) return;
|
||||
m_NextBudget = now + 5000;
|
||||
m_Traces = 12;
|
||||
Prune();
|
||||
// No position reads, messages or callbacks here: death only makes a local LOS candidate.
|
||||
// Rotate the finite candidate window so sixteen remote corpses cannot hide later losses.
|
||||
m_Bodies.Clear();
|
||||
int total = m_Roster.Count();
|
||||
for (int visited = 0; visited < total; visited++)
|
||||
{
|
||||
if (m_Bodies.Count() >= 16) { Cap(); break; }
|
||||
if (m_RosterCursor >= total) m_RosterCursor = 0;
|
||||
ERR_HQBodyCandidate candidate = m_Roster[m_RosterCursor++];
|
||||
if (!candidate.Corpse || candidate.Confirmed || m_Bodies.Contains(candidate) || !Dead(candidate.Corpse)) continue;
|
||||
m_Bodies.Insert(candidate);
|
||||
}
|
||||
}
|
||||
protected bool Visible(IEntity observer, IEntity body, out vector observedPosition)
|
||||
{
|
||||
if (m_Traces <= 0 || !observer || !body || !ERR_Actors.CanFight(observer)
|
||||
|| CompartmentAccessComponent.GetVehicleIn(observer)) return false;
|
||||
vector bodyPosition = body.GetOrigin();
|
||||
vector observerPosition = observer.GetOrigin();
|
||||
vector delta = bodyPosition - observerPosition;
|
||||
float distance = delta.Length();
|
||||
delta[1] = 0;
|
||||
vector transform[4]; observer.GetWorldTransform(transform);
|
||||
vector forward = transform[2]; forward[1] = 0;
|
||||
float facing;
|
||||
if (delta.Length() > 0.01 && forward.Length() > 0.01)
|
||||
facing = vector.Dot(forward.Normalized(), delta.Normalized());
|
||||
if (!ERR_HQBodyRules.InView(distance, facing)) return false;
|
||||
TraceParam trace = new TraceParam();
|
||||
trace.Start = observerPosition + "0 1.5 0";
|
||||
trace.End = bodyPosition + "0 0.3 0";
|
||||
trace.Exclude = observer;
|
||||
trace.Flags = TraceFlags.ENTS | TraceFlags.WORLD;
|
||||
m_Traces--;
|
||||
float fraction = GetGame().GetWorld().TraceMove(trace, null);
|
||||
if (!ERR_HQBodyRules.Clear(fraction, trace.TraceEnt == body)) return false;
|
||||
observedPosition = bodyPosition;
|
||||
return true;
|
||||
}
|
||||
protected ERR_HQBodyScan OldestDue(float now)
|
||||
{
|
||||
ERR_HQBodyScan selected;
|
||||
foreach (ERR_HQBodyScan scan : m_Groups)
|
||||
{
|
||||
// Recently requesting groups share turns; inactive callers cannot reserve work forever.
|
||||
if (!scan.Group || now < scan.NextScan || scan.LastAsked < 0 || now - scan.LastAsked > 5000) continue;
|
||||
if (!OwnedPhysical(scan.Group) || scan.Group.GetPlayerCount(true) > 0) continue;
|
||||
if (!selected || scan.NextScan < selected.NextScan) selected = scan;
|
||||
}
|
||||
return selected;
|
||||
}
|
||||
bool Observe(SCR_AIGroup group, float now, out ERR_HQReport report)
|
||||
{
|
||||
report = null;
|
||||
if (!Replication.IsServer() || !ERR_HQMemoryRules.Finite(now) || now < m_Now || now < 0) return false;
|
||||
TickBudget(now);
|
||||
if (!OwnedPhysical(group) || group.GetPlayerCount(true) > 0 || m_Bodies.IsEmpty()) return false;
|
||||
ERR_HQBodyScan state;
|
||||
foreach (ERR_HQBodyScan scan : m_Groups) if (scan.Group == group) state = scan;
|
||||
if (!state) return false;
|
||||
state.LastAsked = now;
|
||||
if (now < state.NextScan || m_Traces <= 0 || OldestDue(now) != state) return false;
|
||||
state.NextScan = now + 15000;
|
||||
array<AIAgent> agents = {}; group.GetAgents(agents);
|
||||
int count = m_Bodies.Count();
|
||||
for (int visited = 0; visited < count; visited++)
|
||||
{
|
||||
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;
|
||||
for (int member = 0; member < agents.Count() && member < 32; member++)
|
||||
{
|
||||
if (m_Traces <= 0) return false;
|
||||
AIAgent agent = agents[member];
|
||||
if (!agent) continue;
|
||||
vector position;
|
||||
if (!Visible(agent.GetControlledEntity(), candidate.Corpse, position)) continue;
|
||||
report = new ERR_HQReport();
|
||||
report.Target = candidate.Corpse; report.Source = group;
|
||||
report.Position = position; report.ObservedAt = now; report.TravelMode = "body";
|
||||
// Local throttling only. A lost radio call can be retried after fresh LOS.
|
||||
candidate.ReportAfter = now + 15000;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// Call only after a radio-delivered body snapshot was accepted by HQ memory.
|
||||
void ConfirmDelivered(IEntity body)
|
||||
{
|
||||
if (!Replication.IsServer() || !body) return;
|
||||
foreach (ERR_HQBodyCandidate candidate : m_Roster)
|
||||
if (candidate.Corpse == body) candidate.Confirmed = true;
|
||||
}
|
||||
void Stop()
|
||||
{
|
||||
m_Roster.Clear(); m_Bodies.Clear(); m_Groups.Clear();
|
||||
m_Traces = 0; m_Now = -1; m_NextBudget = 0; m_RosterCursor = 0; m_CapLogged = false;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user