Integrate delivered HQ memory, finite physical convoy and RHS WCS dependencies

This commit is contained in:
2026-09-20 02:55:30 +02:00
parent e7b4141f69
commit a2aab17171
230 changed files with 37976 additions and 139 deletions
@@ -0,0 +1,100 @@
// Local evidence only. HQ must receive the resulting immutable status through
// the normal radio route; sampling never writes the delivered watch roster.
class ERR_HQStatusSampler
{
protected SCR_AIGroup m_Group;
protected Faction m_Faction;
protected string m_SourceId;
protected int m_Strength;
protected int m_Deaths;
protected bool m_Bound;
protected float m_LastSampleAt = -1;
protected ref array<IEntity> m_Members = {};
protected ref array<bool> m_Dead = {};
protected ref array<bool> m_Released = {};
protected ref array<AIAgent> m_Agents = {};
bool Bound() { return m_Bound; }
protected bool Eligible()
{
return Replication.IsServer() && m_Bound && m_Group && m_Faction
&& m_Group.GetPlayerCount(true) == 0 && !m_Group.IsSlave() && m_Group.GetFaction() == m_Faction;
}
bool Bind(SCR_AIGroup group, string sourceId, int initialStrength, Faction enemyFaction)
{
if (!Replication.IsServer()) return false;
if (m_Bound) return Eligible() && group == m_Group && sourceId == m_SourceId
&& initialStrength == m_Strength && enemyFaction == m_Faction;
if (!Replication.IsServer() || !group || !enemyFaction || sourceId.IsEmpty() || sourceId.Length() > 96
|| initialStrength < 1 || initialStrength > 32 || group.GetPlayerCount(true) > 0
|| group.IsSlave() || group.GetFaction() != enemyFaction) return false;
m_Agents.Clear(); m_Members.Clear();
group.GetAgents(m_Agents);
if (m_Agents.Count() != initialStrength) { m_Agents.Clear(); return false; }
foreach (AIAgent agent : m_Agents)
{
if (!agent) { m_Members.Clear(); return false; }
IEntity actor = agent.GetControlledEntity();
if (!ERR_Actors.Alive(actor) || ERR_Actors.Player(actor) || ERR_Actors.Group(actor) != group
|| SCR_FactionManager.SGetFaction(actor) != enemyFaction || m_Members.Contains(actor))
{ m_Members.Clear(); return false; }
m_Members.Insert(actor);
}
m_Group = group; m_SourceId = sourceId; m_Strength = initialStrength; m_Faction = enemyFaction;
for (int i = 0; i < initialStrength; i++) { m_Dead.Insert(false); m_Released.Insert(false); }
m_Agents.Clear();
m_Bound = true;
return true;
}
// Call from the existing physical slow tick, not just each 180-second radio
// cycle: after native corpse deletion, a null reference supplies no evidence.
bool ObserveDeaths()
{
if (!Eligible()) return false;
for (int i = 0; i < m_Members.Count(); i++)
{
if (m_Dead[i] || m_Released[i]) continue;
IEntity actor = m_Members[i];
if (!actor) continue;
if (ERR_Actors.Player(actor) || SCR_FactionManager.SGetFaction(actor) != m_Faction)
{ m_Released[i] = true; continue; }
DamageManagerComponent damage = DamageManagerComponent.Cast(actor.FindComponent(DamageManagerComponent));
if (!damage) continue;
if (damage.IsDestroyed()) { m_Dead[i] = true; m_Deaths++; continue; }
// A living member who left this group is no longer ours to account for.
// Native death may detach a corpse, so group identity is checked after death.
if (ERR_Actors.Group(actor) != m_Group) m_Released[i] = true;
}
return true;
}
ERR_HQStatusReport Sample(float now, int messageId, IEntity caller)
{
if (!ERR_HQPursuitRules.Finite(now) || now < 0 || now < m_LastSampleAt || messageId <= 0
|| !ObserveDeaths()) return null;
int callerIndex = m_Members.Find(caller);
if (!caller || callerIndex < 0 || m_Dead[callerIndex] || m_Released[callerIndex]
|| ERR_Actors.Player(caller) || ERR_Actors.Group(caller) != m_Group
|| SCR_FactionManager.SGetFaction(caller) != m_Faction || ERR_HQRadio.Capability(caller) <= 0) return null;
vector position = m_Group.GetOrigin();
if (!ERR_HQPursuitRules.PositionValid(position)) return null;
ERR_HQStatusReport report = new ERR_HQStatusReport();
report.SourceId = m_SourceId; report.MessageId = messageId; report.Position = position;
report.Strength = m_Strength; report.ConfirmedLosses = m_Deaths; report.ObservedAt = now;
for (int i = 0; i < m_Members.Count(); i++)
{
if (m_Dead[i] || m_Released[i]) continue;
IEntity actor = m_Members[i];
if (!ERR_Actors.Alive(actor) || ERR_Actors.Player(actor) || ERR_Actors.Group(actor) != m_Group
|| SCR_FactionManager.SGetFaction(actor) != m_Faction) continue;
report.Count++;
if (ERR_Actors.CanFight(actor)) report.Fit++;
}
m_LastSampleAt = now;
return report;
}
void Clear()
{
m_Group = null; m_Faction = null; m_SourceId = ""; m_Strength = 0; m_Deaths = 0;
m_Bound = false; m_LastSampleAt = -1;
m_Members.Clear(); m_Dead.Clear(); m_Released.Clear(); m_Agents.Clear();
}
}