class ERR_MortarSupplyCallback : ScriptedInventoryOperationCallback { ERR_MortarSupplyTransfer Transfer; protected override void OnComplete() { if (Transfer) Transfer.Acknowledge(true); } protected override void OnFailed() { if (Transfer) Transfer.Acknowledge(false); } } // Exact-item native transaction. Callbacks only record outcomes; the HQ tick // authorizes progress after checking actual inventory ownership and the order. class ERR_MortarSupplyTransfer { protected ERR_MortarBattery m_Battery; protected IEntity m_Gun; protected SCR_AIGroup m_Group; protected IEntity m_Donor; protected IEntity m_Gunner; protected IEntity m_Shell; protected InventoryStorageManagerComponent m_Source; protected InventoryStorageManagerComponent m_Destination; protected BaseInventoryStorageComponent m_Storage; protected ref ERR_MortarSupplyCallback m_Callback; protected int m_Order; protected int m_Acknowledgement; protected float m_Deadline; protected bool m_Accepted; protected bool m_Invalidated; protected bool m_Resolved; protected bool m_Ready; void ERR_MortarSupplyTransfer(ERR_MortarBattery battery, IEntity donor, IEntity gunner, IEntity shell, BaseInventoryStorageComponent storage, float now) { m_Battery = battery; m_Gun = battery.Gun; m_Group = battery.Group; m_Donor = donor; m_Gunner = gunner; m_Shell = shell; m_Storage = storage; m_Order = battery.OrderId; m_Deadline = now + 8000; m_Source = InventoryStorageManagerComponent.Cast(donor.FindComponent(InventoryStorageManagerComponent)); m_Destination = InventoryStorageManagerComponent.Cast(gunner.FindComponent(InventoryStorageManagerComponent)); } static bool Authority(bool valid, bool invalidated, int capturedOrder, int currentOrder) { return valid && !invalidated && capturedOrder == currentOrder; } static bool Proven(bool callbackComplete, bool sourceContains, bool destinationContains, bool exactSlot) { return callbackComplete && !sourceContains && destinationContains && exactSlot; } protected bool Contains(InventoryStorageManagerComponent inventory) { if (!inventory || !m_Shell) return false; array items = {}; ERR_Inventory.Items(inventory, items); return items.Contains(m_Shell); } protected bool Valid(float now) { if (!m_Battery || !Authority(m_Battery.SupplyAuthorized(now), m_Invalidated, m_Order, m_Battery.OrderId)) return false; if (m_Battery.Gun != m_Gun || m_Battery.Group != m_Group || !m_Donor || !m_Gunner || m_Donor == m_Gunner || !m_Shell || !m_Source || !m_Destination || !m_Storage) return false; if (!ERR_Actors.CanFight(m_Donor) || !ERR_Actors.CanFight(m_Gunner) || ERR_Actors.Group(m_Donor) != m_Group || ERR_Actors.Group(m_Gunner) != m_Group) return false; Faction faction = m_Group.GetFaction(); if (SCR_FactionManager.SGetFaction(m_Donor) != faction || SCR_FactionManager.SGetFaction(m_Gunner) != faction || vector.Distance(m_Donor.GetOrigin(), m_Gunner.GetOrigin()) > 12) return false; SCR_AIVehicleUsageComponent usage = SCR_AIVehicleUsageComponent.Cast(m_Gun.FindComponent(SCR_AIVehicleUsageComponent)); if (!usage || !usage.GetTurretCompartmentSlot()) return false; IEntity occupant = usage.GetTurretCompartmentSlot().GetOccupant(); return !occupant || occupant == m_Gunner; } void Acknowledge(bool success) { if (m_Acknowledgement != 0) return; if (success) m_Acknowledgement = 1; else m_Acknowledgement = -1; } bool Begin(float now) { if (m_Callback || !Replication.IsServer() || !Valid(now) || !Contains(m_Source) || Contains(m_Destination)) return false; m_Callback = new ERR_MortarSupplyCallback(); m_Callback.Transfer = this; // Do not use callback.GetItem(): the native contract excludes Move operations. m_Accepted = m_Source.TryMoveItemToStorage(m_Shell, m_Storage, -1, m_Callback); if (!m_Accepted) { Acknowledge(false); m_Resolved = true; } return m_Accepted; } void Invalidate() { m_Invalidated = true; m_Ready = false; } bool Unresolved() { return m_Accepted && !m_Resolved; } int CapturedOrder() { return m_Order; } ERR_MortarSupplyState Poll(float now) { if (!Replication.IsServer()) return ERR_MortarSupplyState.FAILED; bool valid = Valid(now); bool sourceContains = Contains(m_Source); bool destinationContains = Contains(m_Destination); bool exactSlot; InventoryItemComponent item; if (m_Shell) item = InventoryItemComponent.Cast(m_Shell.FindComponent(InventoryItemComponent)); if (item && item.GetParentSlot()) exactSlot = item.GetParentSlot().GetStorage() == m_Storage; bool proven = Proven(m_Acknowledgement == 1, sourceContains, destinationContains, exactSlot); if (proven || m_Acknowledgement < 0) m_Resolved = true; // A successful callback without physical destination proof is ambiguous. // Keep it reserved through timeout/cancellation; never start a second move. if (!valid || now >= m_Deadline) { m_Invalidated = true; m_Ready = false; return ERR_MortarSupplyState.FAILED; } if (proven) { m_Ready = true; return ERR_MortarSupplyState.READY; } if (m_Acknowledgement < 0 || !m_Accepted) return ERR_MortarSupplyState.FAILED; return ERR_MortarSupplyState.PENDING; } }