194 lines
8.6 KiB
C
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++; }
|
|
}
|
|
}
|
|
}
|