Integrate radio-delivered convoy cover and conservative squad ammunition reports
This commit is contained in:
@@ -0,0 +1,193 @@
|
||||
// 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++; }
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user