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

194 lines
8.6 KiB
C

// Detached counts of compatible small-arms magazines/chambers and separately
// supported launcher magazines. Unknown inventories are never reported empty.
class ERR_HQAmmoSnapshot
{
bool Complete;
bool LauncherComplete;
int Members;
int SmallArmsMuzzles;
int SmallArmsRounds;
int SmallArmsMagazines;
int SmallArmsChambers;
int LauncherRounds;
int LauncherMagazines;
int LauncherChambers;
bool KnownSmallArmsEmpty()
{
return Complete && Members > 0 && SmallArmsMuzzles > 0 && SmallArmsRounds == 0 && SmallArmsChambers == 0;
}
ERR_HQAmmoSnapshot Snapshot()
{
ERR_HQAmmoSnapshot copy = new ERR_HQAmmoSnapshot();
copy.Complete = Complete; copy.LauncherComplete = LauncherComplete; copy.Members = Members;
copy.SmallArmsMuzzles = SmallArmsMuzzles; copy.SmallArmsRounds = SmallArmsRounds;
copy.SmallArmsMagazines = SmallArmsMagazines; copy.SmallArmsChambers = SmallArmsChambers;
copy.LauncherRounds = LauncherRounds; copy.LauncherMagazines = LauncherMagazines;
copy.LauncherChambers = LauncherChambers;
return copy;
}
bool Valid(int living)
{
if (Members < 0 || Members > living || living > 32 || (Complete && Members != living)) return false;
if (SmallArmsMuzzles < 0 || SmallArmsMuzzles > 1024) return false;
if (SmallArmsRounds < 0 || SmallArmsRounds > 4096000) return false;
if (SmallArmsMagazines < 0 || SmallArmsMagazines > 4096) return false;
if (SmallArmsChambers < 0 || SmallArmsChambers > 4096) return false;
if (LauncherRounds < 0 || LauncherRounds > 4096000) return false;
if (LauncherMagazines < 0 || LauncherMagazines > 4096) return false;
return LauncherChambers >= 0 && LauncherChambers <= 4096;
}
}
class ERR_HQAmmoSampler
{
protected static int Category(BaseWeaponComponent weapon, BaseMuzzleComponent muzzle)
{
if (!weapon || !muzzle) return 0;
EWeaponType kind = weapon.GetWeaponType();
EMuzzleType muzzleKind = muzzle.GetMuzzleType();
if (muzzleKind == EMuzzleType.MT_RPGMuzzle || muzzleKind == EMuzzleType.MT_UGLMuzzle
|| kind == EWeaponType.WT_ROCKETLAUNCHER || kind == EWeaponType.WT_GRENADELAUNCHER) return 2;
if (kind == EWeaponType.WT_RIFLE || kind == EWeaponType.WT_SNIPERRIFLE
|| kind == EWeaponType.WT_MACHINEGUN || kind == EWeaponType.WT_HANDGUN) return 1;
return 0;
}
protected static bool Compatible(BaseMagazineComponent magazine, array<BaseMuzzleComponent> muzzles)
{
if (!magazine || !magazine.GetMagazineWell()) return false;
foreach (BaseMuzzleComponent muzzle : muzzles)
if (muzzle.GetMagazineWell() && muzzle.GetMagazineWell().Type() == magazine.GetMagazineWell().Type()) return true;
return false;
}
// Native magazine search permits derived wells, while the reload UI uses exact
// types. Keep exact counts, but never infer empty from uncertain derived stock.
protected static bool InheritedCandidate(BaseMagazineComponent magazine, array<BaseMuzzleComponent> muzzles)
{
if (!magazine || !magazine.GetMagazineWell()) return false;
foreach (BaseMuzzleComponent muzzle : muzzles)
{
if (!muzzle.GetMagazineWell()) continue;
if (magazine.GetMagazineWell().Type() == muzzle.GetMagazineWell().Type()) continue;
if (magazine.GetMagazineWell().IsInherited(muzzle.GetMagazineWell().Type())) return true;
}
return false;
}
// Caller must enumerate the immutable own roster. No group discovery, position,
// spawning, ammunition mutation, vehicle weapons or virtual inventory work.
static void Inspect(IEntity actor, SCR_AIGroup group, ERR_HQAmmoSnapshot result)
{
if (!result) return;
if (!Replication.IsServer() || !group || group.IsSlave() || group.GetPlayerCount(true) > 0
|| !ERR_Actors.Alive(actor) || ERR_Actors.Player(actor) || ERR_Actors.Group(actor) != group)
{ result.Complete = false; result.LauncherComplete = false; return; }
if (result.Members >= 32) { result.Complete = false; result.LauncherComplete = false; return; }
result.Members++;
InventoryStorageManagerComponent inventory;
inventory = InventoryStorageManagerComponent.Cast(actor.FindComponent(InventoryStorageManagerComponent));
BaseWeaponManagerComponent manager;
manager = BaseWeaponManagerComponent.Cast(actor.FindComponent(BaseWeaponManagerComponent));
if (!inventory || !manager) { result.Complete = false; result.LauncherComplete = false; return; }
array<IEntity> raw = {}; inventory.GetItems(raw);
array<IEntity> weapons = {}; manager.GetWeaponsList(weapons);
if (raw.Count() > 128 || weapons.Count() > 8)
{ result.Complete = false; result.LauncherComplete = false; return; }
array<IEntity> items = {};
foreach (IEntity item : raw)
{
if (!item) { result.Complete = false; result.LauncherComplete = false; continue; }
if (!items.Contains(item)) items.Insert(item);
}
array<BaseMuzzleComponent> small = {};
array<BaseMuzzleComponent> launchers = {};
array<IEntity> seenWeapons = {};
foreach (IEntity weaponEntity : weapons)
{
if (!weaponEntity) { result.Complete = false; result.LauncherComplete = false; continue; }
if (seenWeapons.Contains(weaponEntity)) continue;
seenWeapons.Insert(weaponEntity);
BaseWeaponComponent weapon = BaseWeaponComponent.Cast(weaponEntity.FindComponent(BaseWeaponComponent));
if (!weapon) { result.Complete = false; result.LauncherComplete = false; continue; }
// Throwables have no relevant magazine well; smoke/grenades do not pretend to be rifle ammunition.
if (weapon.GetWeaponType() == EWeaponType.WT_FRAGGRENADE
|| weapon.GetWeaponType() == EWeaponType.WT_SMOKEGRENADE) continue;
bool launcherWeapon = weapon.GetWeaponType() == EWeaponType.WT_ROCKETLAUNCHER
|| weapon.GetWeaponType() == EWeaponType.WT_GRENADELAUNCHER;
array<BaseMuzzleComponent> muzzles = {}; weapon.GetMuzzlesList(muzzles);
if (muzzles.IsEmpty() || muzzles.Count() > 4)
{
if (!launcherWeapon) result.Complete = false;
result.LauncherComplete = false;
continue;
}
foreach (BaseMuzzleComponent muzzle : muzzles)
{
if (!muzzle)
{
if (!launcherWeapon) result.Complete = false;
result.LauncherComplete = false;
continue;
}
if (small.Contains(muzzle) || launchers.Contains(muzzle)) continue;
int category = Category(weapon, muzzle);
if (category == 0) { result.Complete = false; result.LauncherComplete = false; continue; }
if (category == 1) { small.Insert(muzzle); result.SmallArmsMuzzles++; }
else launchers.Insert(muzzle);
if (!muzzle.GetMagazineWell())
{
if (category == 1) result.Complete = false;
else result.LauncherComplete = false; // loose launcher projectiles are not compatible-magazine counts
}
int barrels = muzzle.GetBarrelsCount();
if (barrels < 1 || barrels > 4)
{
if (category == 1) result.Complete = false;
else result.LauncherComplete = false;
continue;
}
for (int barrel = 0; barrel < barrels; barrel++)
{
if (!muzzle.IsBarrelChambered(barrel)) continue;
if (category == 1) result.SmallArmsChambers++;
else result.LauncherChambers++;
}
BaseMagazineComponent loaded = muzzle.GetMagazine();
if (loaded && (!loaded.GetOwner() || !loaded.GetMagazineWell()))
{
if (category == 1) result.Complete = false;
else result.LauncherComplete = false;
}
if (loaded && loaded.GetOwner() && !items.Contains(loaded.GetOwner())) items.Insert(loaded.GetOwner());
}
}
// Physical identity deduplicates loaded/carried enumeration and shared compatible weapon wells.
if (items.Count() > 128) { result.Complete = false; result.LauncherComplete = false; return; }
foreach (IEntity item : items)
{
BaseMagazineComponent magazine = BaseMagazineComponent.Cast(item.FindComponent(BaseMagazineComponent));
if (!magazine) continue;
if (!magazine.GetMagazineWell())
{
// Stock reload and magazine-search predicates reject a candidate with no well.
// It cannot replenish our fully inspected well-bearing small arms. A loaded
// no-well small-arms magazine was already rejected in its muzzle above.
result.LauncherComplete = false;
continue;
}
if (InheritedCandidate(magazine, small)) result.Complete = false;
if (InheritedCandidate(magazine, launchers)) result.LauncherComplete = false;
bool rifle = Compatible(magazine, small);
bool launcher = Compatible(magazine, launchers);
if (!rifle && !launcher) continue;
int rounds = magazine.GetAmmoCount();
int capacity = magazine.GetMaxAmmoCount();
if ((rifle && launcher) || rounds < 0 || capacity < 1 || capacity > 1000 || rounds > capacity)
{
if (rifle) result.Complete = false;
if (launcher) result.LauncherComplete = false;
continue;
}
if (rifle) { result.SmallArmsRounds += rounds; result.SmallArmsMagazines++; }
else { result.LauncherRounds += rounds; result.LauncherMagazines++; }
}
}
}