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

200 lines
6.3 KiB
C

// These samples exist only at intake. HQ never revisits their entity loadouts.
class ERR_HQVisualContact
{
IEntity Entity;
vector Position;
float ObservedAt;
bool Escapist;
string Travel = "unknown";
string Weapon = "unknown";
}
class ERR_HQObservation
{
// Require current native line of sight before reading visible equipment.
static bool IsClearSighting(float age, float traceFraction, float exposure)
{
return age >= 0 && age <= 0.5 && traceFraction >= 1 && exposure > 0;
}
static bool CanIdentifyWeapon(float distance, float exposure)
{
return distance >= 0 && distance <= 150 && exposure >= 0.5;
}
static string WeaponCategory(EWeaponType type)
{
switch (type)
{
case EWeaponType.WT_HANDGUN: return "pistol";
case EWeaponType.WT_RIFLE: return "rifle";
case EWeaponType.WT_SNIPERRIFLE: return "sniper rifle";
case EWeaponType.WT_MACHINEGUN: return "machine gun";
case EWeaponType.WT_ROCKETLAUNCHER: return "rocket launcher";
case EWeaponType.WT_GRENADELAUNCHER: return "grenade launcher";
case EWeaponType.WT_FRAGGRENADE: return "grenade";
case EWeaponType.WT_SMOKEGRENADE: return "smoke grenade";
}
return "unidentified weapon";
}
static ERR_HQVisualContact Capture(BaseTarget target, float now)
{
if (!target || target.GetTargetCategory() != ETargetCategory.ENEMY || !IsClearSighting(target.GetTimeSinceSeen(), target.GetTraceFraction(), target.GetExposure()))
return null;
DamageManagerComponent damage = target.GetDamageManagerComponent();
if (damage && damage.IsDestroyed())
return null;
IEntity entity = target.GetTargetEntity();
if (!entity)
return null;
ERR_HQVisualContact seen = new ERR_HQVisualContact();
seen.Entity = entity;
seen.Position = target.GetLastSeenPosition();
seen.ObservedAt = now - target.GetTimeSinceSeen() * 1000;
SCR_ChimeraCharacter character = SCR_ChimeraCharacter.Cast(entity);
if (character)
{
if (GetGame().GetPlayerManager())
seen.Escapist = GetGame().GetPlayerManager().GetPlayerIdFromControlledEntity(entity) > 0;
seen.Travel = "on foot";
if (CompartmentAccessComponent.GetVehicleIn(entity))
seen.Travel = "in vehicle";
// Read only the drawn weapon; never enumerate weapons or inventory.
if (CanIdentifyWeapon(target.GetDistance(), target.GetExposure()))
{
CharacterControllerComponent controller = CharacterControllerComponent.Cast(entity.FindComponent(CharacterControllerComponent));
if (controller)
{
IEntity heldItem = controller.GetCurrentItemInHands();
BaseWeaponComponent weapon;
if (heldItem)
weapon = BaseWeaponComponent.Cast(heldItem.FindComponent(BaseWeaponComponent));
seen.Weapon = "none drawn";
if (weapon)
seen.Weapon = WeaponCategory(weapon.GetWeaponType());
}
}
}
else if (Vehicle.Cast(entity))
{
seen.Travel = "vehicle";
if (target.GetUnitType() == EAIUnitType.UnitType_Aircraft)
seen.Travel = "aircraft";
}
return seen;
}
static void Gather(SCR_AIGroupUtilityComponent utility, float now, out array<ref ERR_HQVisualContact> samples)
{
samples.Clear();
if (!utility)
return;
array<BaseTarget> visible = {};
utility.ERR_HQGetVisualTargets(visible);
foreach (BaseTarget target : visible)
{
ERR_HQVisualContact seen = Capture(target, now);
if (!seen)
continue;
ERR_HQVisualContact existing;
foreach (ERR_HQVisualContact sample : samples)
{
if (sample.Entity == seen.Entity)
{
existing = sample;
break;
}
}
if (!existing)
{
if (samples.Count() < 64)
samples.Insert(seen);
}
else if (seen.ObservedAt > existing.ObservedAt || (seen.ObservedAt == existing.ObservedAt && existing.Weapon == "unknown" && seen.Weapon != "unknown"))
{
existing.Position = seen.Position;
existing.ObservedAt = seen.ObservedAt;
existing.Travel = seen.Travel;
existing.Weapon = seen.Weapon;
}
}
}
static void Describe(ERR_HQReport report, array<ref ERR_HQVisualContact> samples, float now)
{
// New observations must not silently inherit old equipment/travel details.
report.SeenEscapists = -1;
report.TravelMode = "unknown";
report.SeenWeapons = "unknown";
report.DetailsObservedAt = -1;
if (report.Distress)
return;
ERR_HQVisualContact anchor;
foreach (ERR_HQVisualContact sample : samples)
{
if (sample.Entity == report.Target && now - sample.ObservedAt >= 0 && now - sample.ObservedAt <= 500)
{
anchor = sample;
break;
}
}
if (!anchor)
return;
report.DetailsObservedAt = anchor.ObservedAt;
report.TravelMode = anchor.Travel;
report.SeenWeapons = anchor.Weapon;
array<IEntity> counted = {};
array<string> weapons = {};
string travel;
foreach (ERR_HQVisualContact sighting : samples)
{
if (!sighting.Escapist || !sighting.Entity || counted.Contains(sighting.Entity) || now - sighting.ObservedAt < 0 || now - sighting.ObservedAt > 500 || vector.DistanceXZ(anchor.Position, sighting.Position) > 100)
continue;
counted.Insert(sighting.Entity);
if (travel.IsEmpty())
travel = sighting.Travel;
else if (travel != sighting.Travel)
travel = "mixed travel";
if (sighting.Weapon != "unknown" && !weapons.Contains(sighting.Weapon) && weapons.Count() < 4)
weapons.Insert(sighting.Weapon);
}
if (counted.IsEmpty())
return; // A visible vehicle does not reveal its hidden occupants.
report.SeenEscapists = counted.Count();
// A vehicle anchor remains a vehicle report, even with people near it.
if (anchor.Escapist)
report.TravelMode = travel;
report.SeenWeapons = "unknown";
if (!weapons.IsEmpty())
{
report.SeenWeapons = weapons[0];
for (int i = 1; i < weapons.Count(); i++)
report.SeenWeapons += ", " + weapons[i];
}
}
}
modded class SCR_AIGroupUtilityComponent
{
void ERR_HQGetVisualTargets(out array<BaseTarget> targets)
{
targets.Clear();
foreach (SCR_AIInfoComponent info : m_aInfoComponents)
{
if (!info || !info.m_Perception)
continue;
AIAgent observer = AIAgent.Cast(info.GetOwner());
if (!observer || !ERR_Actors.CanFight(observer.GetControlledEntity()))
continue;
array<BaseTarget> own = {};
info.m_Perception.GetTargetsList(own, ETargetCategory.ENEMY);
foreach (BaseTarget target : own)
{
if (target && ERR_HQObservation.IsClearSighting(target.GetTimeSinceSeen(), target.GetTraceFraction(), target.GetExposure()))
targets.Insert(target);
}
}
}
}