101 lines
4.5 KiB
C
101 lines
4.5 KiB
C
// 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();
|
|
}
|
|
}
|