189 lines
11 KiB
C
189 lines
11 KiB
C
enum ERR_InfantryAmmoTransferState { FAILED = -1, PENDING, SUCCEEDED }
|
|
|
|
class ERR_InfantryAmmoCallback : ScriptedInventoryOperationCallback
|
|
{
|
|
ERR_InfantryAmmoTransfer Transfer;
|
|
protected override void OnComplete() { if (Transfer) Transfer.Acknowledge(true); }
|
|
protected override void OnFailed() { if (Transfer) Transfer.Acknowledge(false); }
|
|
protected override void OnAllComplete() { if (Transfer) Transfer.Acknowledge(true); }
|
|
protected override void OnAllFailed() { if (Transfer) Transfer.Acknowledge(false); }
|
|
}
|
|
|
|
// One exact physical magazine movement. The caller owns HQ authorization and
|
|
// the spent-item reservation; this leaf never selects tasks or retries a move.
|
|
class ERR_InfantryAmmoTransfer
|
|
{
|
|
protected ERR_GameMode m_Mode;
|
|
protected Vehicle m_Donor;
|
|
protected SCR_AIGroup m_DonorGroup;
|
|
protected IEntity m_Recipient;
|
|
protected SCR_AIGroup m_RecipientGroup;
|
|
protected IEntity m_Magazine;
|
|
protected IEntity m_Driver;
|
|
protected ERR_ForceRecord m_DonorRecord;
|
|
protected ERR_ForceRecord m_RecipientRecord;
|
|
protected InventoryStorageManagerComponent m_Source;
|
|
protected InventoryStorageManagerComponent m_Destination;
|
|
protected BaseInventoryStorageComponent m_Storage;
|
|
protected ref ERR_InfantryAmmoCallback m_Callback;
|
|
protected bool m_Submitted;
|
|
protected bool m_Accepted;
|
|
protected int m_Acknowledgement;
|
|
protected float m_Started = -1;
|
|
protected ERR_InfantryAmmoTransferState m_State = ERR_InfantryAmmoTransferState.PENDING;
|
|
protected string m_Reason;
|
|
void ERR_InfantryAmmoTransfer(ERR_GameMode mode, Vehicle donorVehicle, SCR_AIGroup donorGroup, IEntity recipientActor, SCR_AIGroup recipientGroup, IEntity exactMagazine)
|
|
{
|
|
m_Mode = mode; m_Donor = donorVehicle; m_DonorGroup = donorGroup;
|
|
m_Recipient = recipientActor; m_RecipientGroup = recipientGroup; m_Magazine = exactMagazine;
|
|
}
|
|
static bool Compatible(IEntity actor, IEntity exactMagazine)
|
|
{
|
|
if (!actor || !exactMagazine) return false;
|
|
BaseMagazineComponent magazine = BaseMagazineComponent.Cast(exactMagazine.FindComponent(BaseMagazineComponent));
|
|
BaseWeaponManagerComponent manager = BaseWeaponManagerComponent.Cast(actor.FindComponent(BaseWeaponManagerComponent));
|
|
if (!magazine || !manager || !magazine.GetMagazineWell() || magazine.GetAmmoCount() <= 0 || magazine.GetMaxAmmoCount() <= 0 || magazine.GetAmmoCount() > magazine.GetMaxAmmoCount()) return false;
|
|
array<IEntity> weapons = {}; manager.GetWeaponsList(weapons);
|
|
if (weapons.IsEmpty() || weapons.Count() > 8) return false;
|
|
foreach (IEntity rifle : weapons)
|
|
{
|
|
if (!rifle) continue;
|
|
BaseWeaponComponent weapon = BaseWeaponComponent.Cast(rifle.FindComponent(BaseWeaponComponent));
|
|
if (!weapon || weapon.GetOwner() != rifle) continue;
|
|
EWeaponType kind = weapon.GetWeaponType();
|
|
if (kind != EWeaponType.WT_RIFLE && kind != EWeaponType.WT_SNIPERRIFLE && kind != EWeaponType.WT_MACHINEGUN && kind != EWeaponType.WT_HANDGUN) continue;
|
|
array<BaseMuzzleComponent> muzzles = {}; weapon.GetMuzzlesList(muzzles);
|
|
if (muzzles.Count() > 4) continue;
|
|
foreach (BaseMuzzleComponent muzzle : muzzles)
|
|
{
|
|
if (!muzzle || !muzzle.GetMagazineWell()) continue;
|
|
EMuzzleType muzzleKind = muzzle.GetMuzzleType();
|
|
if (muzzleKind != EMuzzleType.MT_BaseMuzzle) continue;
|
|
// Same direction as the stock magazine inventory predicate: a magazine's
|
|
// derived well must inherit the actual carried small-arms muzzle's well.
|
|
if (magazine.GetMagazineWell().IsInherited(muzzle.GetMagazineWell().Type())) return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
static bool StoredUnder(IEntity entity, IEntity root)
|
|
{
|
|
for (int depth = 0; entity && depth < 16; depth++)
|
|
{
|
|
if (entity == root) return true;
|
|
InventoryItemComponent item = InventoryItemComponent.Cast(entity.FindComponent(InventoryItemComponent));
|
|
if (item && item.GetParentSlot())
|
|
{
|
|
BaseInventoryStorageComponent storage = item.GetParentSlot().GetStorage();
|
|
if (!storage) return false;
|
|
entity = storage.GetOwner();
|
|
}
|
|
else entity = entity.GetParent();
|
|
}
|
|
return false;
|
|
}
|
|
protected bool ManagedStorage(InventoryStorageManagerComponent inventory, BaseInventoryStorageComponent storage, IEntity owner)
|
|
{
|
|
if (!inventory || !storage || inventory.GetOwner() != owner || !StoredUnder(storage.GetOwner(), owner)) return false;
|
|
array<BaseInventoryStorageComponent> storages = {};
|
|
inventory.GetStorages(storages, EStoragePurpose.PURPOSE_DEPOSIT);
|
|
return storages.Count() <= 64 && storages.Contains(storage);
|
|
}
|
|
protected int Ownership(InventoryStorageManagerComponent inventory)
|
|
{
|
|
if (!inventory || !m_Magazine) return -1;
|
|
array<IEntity> items = {}; inventory.GetItems(items, EStoragePurpose.PURPOSE_DEPOSIT);
|
|
if (items.Count() > 512) return -1;
|
|
if (items.Contains(m_Magazine)) return 1;
|
|
return 0;
|
|
}
|
|
protected bool PhysicalProof()
|
|
{
|
|
if (!m_Magazine || m_Acknowledgement != 1 || Ownership(m_Source) != 0 || Ownership(m_Destination) != 1 || !ManagedStorage(m_Destination, m_Storage, m_Recipient)) return false;
|
|
InventoryItemComponent item = InventoryItemComponent.Cast(m_Magazine.FindComponent(InventoryItemComponent));
|
|
return item && item.GetParentSlot() && item.GetParentSlot().GetStorage() == m_Storage;
|
|
}
|
|
void Acknowledge(bool success)
|
|
{
|
|
if (m_Acknowledgement != 0) return;
|
|
if (success) m_Acknowledgement = 1;
|
|
else m_Acknowledgement = -1;
|
|
}
|
|
// This read-only observation works even after Stop; the caller must retain
|
|
// ambiguous accepted operations and cannot interpret failure as cancellation.
|
|
bool Unresolved()
|
|
{
|
|
if (!m_Accepted || !m_Magazine || m_Acknowledgement < 0) return false;
|
|
return !PhysicalProof();
|
|
}
|
|
bool Submitted() { return m_Submitted; }
|
|
string Reason() { return m_Reason; }
|
|
protected ERR_InfantryAmmoTransferState Fail(string reason)
|
|
{
|
|
if (m_State == ERR_InfantryAmmoTransferState.PENDING) { m_State = ERR_InfantryAmmoTransferState.FAILED; m_Reason = reason; }
|
|
return m_State;
|
|
}
|
|
void Stop() { Fail("HQ authorization stopped; an accepted native move cannot be cancelled"); }
|
|
protected bool Context()
|
|
{
|
|
if (!Replication.IsServer() || SCR_Global.IsEditMode() || !m_Mode || m_Mode != ERR_GameMode.Get() || !m_Mode.Active() || !m_Mode.Forces()) return false;
|
|
if (!m_Donor || !ERR_Actors.Alive(m_Donor) || !m_DonorGroup || !m_RecipientGroup || m_DonorGroup == m_RecipientGroup) return false;
|
|
if (!ERR_EnemyEquipmentRules.Eligible(m_Mode, m_RecipientGroup, m_Recipient) || CompartmentAccessComponent.GetVehicleIn(m_Recipient)) return false;
|
|
if (m_DonorGroup.IsSlave() || m_RecipientGroup.IsSlave() || m_DonorGroup.GetPlayerCount(true) > 0 || m_DonorGroup.GetFaction() != m_Mode.EnemyFaction()) return false;
|
|
if (SCR_FactionManager.SGetFaction(m_Donor) != m_Mode.EnemyFaction() || SCR_FactionManager.SGetFaction(m_Recipient) != m_Mode.EnemyFaction()) return false;
|
|
ERR_ForceRecord donorRecord = m_Mode.Forces().FindGroup(m_DonorGroup);
|
|
ERR_ForceRecord recipientRecord = m_Mode.Forces().FindGroup(m_RecipientGroup);
|
|
if (!donorRecord || !recipientRecord || donorRecord.Group != m_DonorGroup || recipientRecord.Group != m_RecipientGroup || !donorRecord.Physical || !recipientRecord.Physical || donorRecord.Eliminated || recipientRecord.Eliminated) return false;
|
|
if (m_DonorRecord && (m_DonorRecord != donorRecord || m_RecipientRecord != recipientRecord)) return false;
|
|
SCR_AIGroupUtilityComponent utility = m_DonorGroup.GetGroupUtilityComponent();
|
|
if (!utility || !utility.IsMilitary() || !utility.m_VehicleMgr || !utility.m_VehicleMgr.FindVehicle(m_Donor)) return false;
|
|
if (ERR_ConvoyRules.Captured(m_Donor, m_DonorGroup)) return false;
|
|
SCR_AIVehicleUsageComponent usage = SCR_AIVehicleUsageComponent.Cast(m_Donor.FindComponent(SCR_AIVehicleUsageComponent));
|
|
if (!usage || !usage.GetPilotCompartmentSlot()) return false;
|
|
IEntity driver = usage.GetPilotCompartmentSlot().GetOccupant();
|
|
if (!ERR_EnemyEquipmentRules.Eligible(m_Mode, m_DonorGroup, driver) || !ERR_ConvoyRules.PartOf(CompartmentAccessComponent.GetVehicleIn(driver), m_Donor)) return false;
|
|
if (SCR_FactionManager.SGetFaction(driver) != m_Mode.EnemyFaction() || (m_Driver && driver != m_Driver)) return false;
|
|
Physics physics = m_Donor.GetPhysics();
|
|
if (!physics) return false;
|
|
float speed = physics.GetVelocity().Length();
|
|
float angular = physics.GetAngularVelocity().Length();
|
|
float distance = vector.Distance(m_Donor.GetOrigin(), m_Recipient.GetOrigin());
|
|
if (!(speed >= 0 && speed <= 0.25 && angular >= 0 && angular <= 0.1 && distance >= 0 && distance <= 4)) return false;
|
|
if (!Compatible(m_Recipient, m_Magazine)) return false;
|
|
m_Driver = driver; m_DonorRecord = donorRecord; m_RecipientRecord = recipientRecord;
|
|
return true;
|
|
}
|
|
ERR_InfantryAmmoTransferState Tick(float now)
|
|
{
|
|
if (m_State != ERR_InfantryAmmoTransferState.PENDING) return m_State;
|
|
if (!ERR_ForceRules.Finite(now) || now < 0) return Fail("invalid transaction clock");
|
|
if (m_Started < 0) m_Started = now;
|
|
if (now < m_Started || now - m_Started >= 8000) return Fail("native magazine move deadline expired");
|
|
if (!Context()) return Fail("original owned groups, vehicle, driver, recipient, proximity or compatible weapon unavailable");
|
|
if (!m_Submitted)
|
|
{
|
|
m_Source = InventoryStorageManagerComponent.Cast(m_Donor.FindComponent(InventoryStorageManagerComponent));
|
|
m_Destination = InventoryStorageManagerComponent.Cast(m_Recipient.FindComponent(InventoryStorageManagerComponent));
|
|
if (!m_Source || !m_Destination || m_Source == m_Destination || Ownership(m_Source) != 1 || Ownership(m_Destination) != 0) return Fail("exact magazine is not exclusive donor stock");
|
|
InventoryItemComponent item = InventoryItemComponent.Cast(m_Magazine.FindComponent(InventoryItemComponent));
|
|
if (!item || !item.GetParentSlot() || !ManagedStorage(m_Source, item.GetParentSlot().GetStorage(), m_Donor)) return Fail("exact source magazine storage does not belong to original donor");
|
|
m_Storage = m_Destination.FindStorageForItem(m_Magazine, EStoragePurpose.PURPOSE_DEPOSIT);
|
|
if (!ManagedStorage(m_Destination, m_Storage, m_Recipient) || !m_Source.CanMoveItemToStorage(m_Magazine, m_Storage, -1)) return Fail("recipient has no native compatible available storage");
|
|
if (!ERR_InfantryAmmoAccess.Clear(m_Donor, m_Recipient)) return Fail("no unobstructed physical reach to an authored donor storage access point");
|
|
m_Callback = new ERR_InfantryAmmoCallback(); m_Callback.Transfer = this;
|
|
m_Submitted = true;
|
|
m_Accepted = m_Source.TryMoveItemToStorage(m_Magazine, m_Storage, -1, m_Callback);
|
|
if (m_Acknowledgement == 1) m_Accepted = true;
|
|
if (!m_Accepted) return Fail("native magazine move rejected; exact item will not be retried");
|
|
}
|
|
if (PhysicalProof())
|
|
{
|
|
m_State = ERR_InfantryAmmoTransferState.SUCCEEDED;
|
|
m_Reason = "native callback and exact destination storage confirm one physical magazine transferred";
|
|
return m_State;
|
|
}
|
|
if (m_Acknowledgement < 0) return Fail("native magazine move failed");
|
|
return ERR_InfantryAmmoTransferState.PENDING;
|
|
}
|
|
}
|