Files
EscapistsReintroduced/addons/ai/Scripts/Game/ERR/ERR_EnemyOptics.c
T

172 lines
7.5 KiB
C

// Owner-authorized startup optics adaptation; provenance belongs in equipment migration docs.
// Native attachment storage owns fitting/replication; no loose optics or replacement grants.
class ERR_EnemyOpticsInspection
{
int Remaining = 128;
bool Complete = true;
bool Visit(IEntity entity, int depth = 0)
{
if (!entity) return false;
if (depth > 4 || Remaining <= 0) { Complete = false; return false; }
Remaining--;
if (entity.FindComponent(SCR_2DOpticsComponent) || entity.FindComponent(SCR_2DPIPSightsComponent) || entity.FindComponent(SCR_PIPSightsComponent)) return true;
IEntity child = entity.GetChildren();
while (child)
{
if (Visit(child, depth + 1)) return true;
if (!Complete) return false;
child = child.GetSibling();
}
return false;
}
bool Inspect(BaseWeaponComponent weapon)
{
if (!weapon) { Complete = false; return false; }
if (Visit(weapon.GetOwner())) return true;
BaseSightsComponent sights = weapon.GetAttachedSights();
if (sights && Visit(sights.GetOwner())) return true;
array<AttachmentSlotComponent> slots = {}; weapon.GetAttachments(slots);
if (slots.Count() > 16) { Complete = false; return false; }
foreach (AttachmentSlotComponent slot : slots)
{
if (slot && Visit(slot.GetAttachedEntity())) return true;
if (!Complete) return false;
}
return false;
}
}
class ERR_EnemyOpticsJob
{
IEntity Rifle;
bool Submitted;
bool Terminal;
bool Confirmed;
int Attempt = -1;
ref array<int> Rejected = {};
ref ERR_EnemyEquipmentCallback Operation;
string Reason;
}
class ERR_EnemyOptics
{
protected IEntity m_Actor;
protected SCR_AIGroup m_Group;
protected ref array<ref ERR_EnemyOpticsJob> m_Jobs = {};
protected int m_Checks;
protected bool m_Captured;
protected bool m_Stopped;
void ERR_EnemyOptics(IEntity actor, SCR_AIGroup group) { m_Actor = actor; m_Group = group; }
static bool EligibleWeapon(EWeaponType type)
{
return type == EWeaponType.WT_SNIPERRIFLE || type == EWeaponType.WT_RIFLE || type == EWeaponType.WT_MACHINEGUN;
}
static void Candidates(out array<ResourceName> result)
{
result = {
"{C850A33226B8F9C1}Prefabs/Weapons/Attachments/Optics/Optic_PSO1/Optic_PSO1.et",
"{D2018EDB1BBF4C88}Prefabs/Weapons/Attachments/Optics/Optic_ARTII/Optic_ARTII.et",
"{A1D5833FF10C6503}Prefabs/Weapons/Attachments/Optics/Optic_PSO1/Optic_PSO1M2.et",
"{695AAA77F84EB238}Prefabs/Weapons/Attachments/Optics/1p21/Optic_1P21.et",
"{333A794F0B55C019}Prefabs/Weapons/Attachments/Optics/m8541/Optic_M8541.et",
"{BD496EE1B40DC510}Prefabs/Weapons/Attachments/Optics/Optic_4x20/Optic_4x20.et",
"{8D4BAA235F7EDBEA}Prefabs/Weapons/Attachments/Optics/1p78/Optic_1P78.et"
};
}
protected void Finish(ERR_EnemyOpticsJob job, string reason)
{
if (job.Terminal) return;
job.Terminal = true; job.Reason = reason;
ERR_HQ.Event("OPTICS", reason);
}
protected void Fit(ERR_GameMode mode, ERR_EnemyOpticsJob job, InventoryStorageManagerComponent inventory)
{
if (job.Terminal) return;
// Re-read the canonical actor and native weapon roster before every grant.
if (!ERR_EnemyEquipmentRules.Eligible(mode, m_Group, m_Actor)) { Finish(job, "startup optics cancelled: actor ownership changed"); return; }
BaseWeaponManagerComponent manager = BaseWeaponManagerComponent.Cast(m_Actor.FindComponent(BaseWeaponManagerComponent));
array<IEntity> current = {}; if (manager) manager.GetWeaponsList(current);
if (!job.Rifle || !current.Contains(job.Rifle)) { Finish(job, "startup rifle left original actor; no replacement"); return; }
BaseWeaponComponent weapon = BaseWeaponComponent.Cast(job.Rifle.FindComponent(BaseWeaponComponent));
if (!weapon || !EligibleWeapon(weapon.GetWeaponType())) { Finish(job, "original weapon no longer eligible"); return; }
ERR_EnemyOpticsInspection inspection = new ERR_EnemyOpticsInspection();
if (inspection.Inspect(weapon)) { job.Confirmed = true; Finish(job, "physical magnified optic preserved/confirmed"); return; }
// Callback success is terminal even if someone removes the optic before our next observation.
if (job.Submitted && job.Operation && job.Operation.Finished && job.Operation.Succeeded) { Finish(job, "native optic insertion completed; no replenishment"); return; }
if (m_Checks >= 5)
{
if (job.Submitted && (!job.Operation || !job.Operation.Finished)) Finish(job, "startup optics deadline; unresolved insertion remains quarantined");
else Finish(job, "startup optics deadline; compatible magnified optic not confirmed");
return;
}
if (job.Submitted)
{
if (!job.Operation || !job.Operation.Finished) return;
job.Rejected.Insert(job.Attempt); job.Submitted = false;
}
if (!inspection.Complete) { job.Reason = "existing optics inspection incomplete; no grant"; return; }
WeaponAttachmentsStorageComponent storage = WeaponAttachmentsStorageComponent.Cast(job.Rifle.FindComponent(WeaponAttachmentsStorageComponent));
if (!storage || storage.GetSlotsCount() > 16) return;
array<ResourceName> candidates = {}; Candidates(candidates);
array<int> remaining = {0, 1, 2, 3, 4, 5, 6};
while (!remaining.IsEmpty())
{
int draw = Math.RandomInt(0, remaining.Count());
int index = remaining[draw]; remaining.Remove(draw);
ResourceName prefab = candidates[index];
for (int slotIndex = 0; slotIndex < storage.GetSlotsCount(); slotIndex++)
{
InventoryStorageSlot slot = storage.GetSlot(slotIndex);
int attempt = index * 16 + slotIndex;
if (!slot || slot.GetAttachedEntity() || job.Rejected.Contains(attempt)) continue;
if (!inventory.CanInsertResourceInStorage(prefab, storage, slotIndex)) continue;
job.Attempt = attempt; job.Operation = new ERR_EnemyEquipmentCallback(); job.Submitted = true;
bool accepted = inventory.TrySpawnPrefabToStorage(prefab, storage, slotIndex, EStoragePurpose.PURPOSE_ANY, job.Operation);
// A synchronous success is authoritative even if the return value is inconsistent.
if (!accepted && !(job.Operation.Finished && job.Operation.Succeeded))
{ job.Rejected.Insert(attempt); job.Submitted = false; }
return; // At most one native submission per original rifle per startup check.
}
}
}
bool Check(ERR_GameMode mode)
{
if (m_Stopped) return true;
if (!ERR_EnemyEquipmentRules.Eligible(mode, m_Group, m_Actor)) { Stop(); return true; }
m_Checks++;
BaseWeaponManagerComponent manager = BaseWeaponManagerComponent.Cast(m_Actor.FindComponent(BaseWeaponManagerComponent));
InventoryStorageManagerComponent inventory = InventoryStorageManagerComponent.Cast(m_Actor.FindComponent(InventoryStorageManagerComponent));
if (!manager || !inventory) { if (m_Checks >= 5) Stop(); return m_Stopped; }
if (!m_Captured)
{
array<IEntity> current = {}; manager.GetWeaponsList(current);
if (current.Count() > 8) { Stop(); return true; }
array<IEntity> seen = {};
foreach (IEntity rifle : current)
{
if (!rifle || rifle == m_Actor || seen.Contains(rifle)) continue;
seen.Insert(rifle);
BaseWeaponComponent weapon = BaseWeaponComponent.Cast(rifle.FindComponent(BaseWeaponComponent));
if (!weapon || !EligibleWeapon(weapon.GetWeaponType())) continue;
ERR_EnemyOpticsJob job = new ERR_EnemyOpticsJob(); job.Rifle = rifle;
m_Jobs.Insert(job);
}
m_Captured = !m_Jobs.IsEmpty();
}
bool complete = m_Captured;
foreach (ERR_EnemyOpticsJob job : m_Jobs)
{
Fit(mode, job, inventory);
if (!job.Terminal) complete = false;
}
if (complete || m_Checks >= 5) Stop();
return m_Stopped;
}
void Stop()
{
m_Stopped = true;
foreach (ERR_EnemyOpticsJob job : m_Jobs) job.Terminal = true;
// Keep exact-weapon tombstones/callbacks. Accepted native inserts cannot be cancelled here.
}
}