// 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 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 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 raw = {}; inventory.GetItems(raw); array weapons = {}; manager.GetWeaponsList(weapons); if (raw.Count() > 128 || weapons.Count() > 8) { result.Complete = false; result.LauncherComplete = false; return; } array items = {}; foreach (IEntity item : raw) { if (!item) { result.Complete = false; result.LauncherComplete = false; continue; } if (!items.Contains(item)) items.Insert(item); } array small = {}; array launchers = {}; array 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 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++; } } } }