#ifdef WORKBENCH // Mechanism-only fixture: radio delivery and automatic artillery scheduling are // explicitly bypassed. All ammunition and inventory operations remain physical. modded class ERR_MortarSystem { protected ERR_NativeMortarTransferProbe m_ERRTransferFixture; bool ERR_HoldForTransfer(ERR_NativeMortarTransferProbe owner) { if (!ERR_NativeMortarTransferProbe.Enabled() || m_Stopped || m_ERRTransferFixture || !owner) return false; m_ERRTransferFixture = owner; return true; } void ERR_ReleaseTransferHold(ERR_NativeMortarTransferProbe owner) { if (m_ERRTransferFixture == owner) m_ERRTransferFixture = null; } override void Tick(float now) { if (m_ERRTransferFixture) return; super.Tick(now); } ERR_MortarBattery ERR_TransferBattery() { if (m_Batteries.Count() != 1) return null; return m_Batteries[0]; } } modded class ERR_MortarBattery { ERR_MortarSupplyTransfer ERR_TransferForProbe() { return m_Transfer; } int ERR_TransferAttempts() { return m_AttemptedShells.Count(); } } modded class ERR_MortarSupplyTransfer { int ERR_CallbackState() { return m_Acknowledgement; } IEntity ERR_TransferredShell() { return m_Shell; } IEntity ERR_TransferRecipient() { return m_Gunner; } bool ERR_RequestAccepted() { return m_Accepted; } } class ERR_NativeMortarMoveCallback : ScriptedInventoryOperationCallback { int Result; protected override void OnComplete() { if (Result == 0) Result = 1; } protected override void OnFailed() { if (Result == 0) Result = -1; } } class ERR_NativeMortarShellOwner { IEntity Shell; IEntity OriginalOwner; } class ERR_NativeMortarTransferProbe { protected ERR_GameMode m_Mode; protected ERR_MortarSystem m_System; protected ERR_MortarBattery m_Battery; protected IEntity m_Gunner; protected BaseCompartmentSlot m_Seat; protected ref array m_Crew = {}; protected ref array m_Boxes = {}; protected ref array m_Stores = {}; protected ref array m_Original = {}; protected ref ERR_NativeMortarMoveCallback m_MoveCallback; protected IEntity m_MoveShell; protected BaseInventoryStorageComponent m_MoveDestination; protected float m_MoveDeadline; protected ref ERR_MortarSupplyTransfer m_ObservedTransfer; protected IEntity m_FirstShell; protected IEntity m_SecondShell; protected int m_AttemptsAtStart; protected int m_ShotsAtStart; protected int m_CallbackAtCancel; protected int m_CancelOrder; protected int m_Step; protected float m_Deadline; protected float m_StepDeadline; protected float m_Wait; protected bool m_Held; protected bool m_Stopped; protected bool m_Complete; static const ResourceName STORE = "{EBBC859CCD94D96D}Prefabs/Props/EmptyStorages/USSR_AmmoBox_empty.et"; static bool Enabled() { string enabled, outcome; return Replication.IsServer() && System.GetCLIParam("errSmoke", enabled) && enabled == "1" && System.GetCLIParam("errSmokeOutcome", outcome) && outcome == "mortar-transfer"; } void ERR_NativeMortarTransferProbe(ERR_GameMode mode) { m_Mode = mode; m_Deadline = GetGame().GetWorld().GetWorldTime() + 180000; } protected bool Check(bool condition, string detail) { if (condition) { Print("ERR SMOKE PASS: mortar transfer " + detail); return true; } Print("ERR SMOKE FAIL: mortar transfer " + detail, LogLevel.ERROR); Stop(); return false; } protected bool IsShell(IEntity item) { return item && item.GetPrefabData() && item.GetPrefabData().GetPrefabName() == ERR_MortarBattery.SHELL; } protected InventoryStorageManagerComponent Inventory(IEntity actor) { if (!actor) return null; return InventoryStorageManagerComponent.Cast(actor.FindComponent(InventoryStorageManagerComponent)); } protected bool Has(IEntity actor, IEntity item) { array items = {}; ERR_Inventory.Items(Inventory(actor), items); return item && items.Contains(item); } protected IEntity CrewOwner(IEntity item) { foreach (IEntity actor : m_Crew) if (Has(actor, item)) return actor; return null; } protected IEntity OriginalOwner(IEntity item) { foreach (ERR_NativeMortarShellOwner entry : m_Original) if (entry.Shell == item) return entry.OriginalOwner; return null; } protected bool InStore(IEntity item) { foreach (BaseInventoryStorageComponent storage : m_Stores) if (storage && storage.Contains(item)) return true; return false; } static bool Conservation(int originals, int actual, bool allOriginal, bool singleOwner) { return originals == 27 && actual == originals && allOriginal && singleOwner; } protected bool Conserved() { array actual = {}; foreach (IEntity actor : m_Crew) { array items = {}; ERR_Inventory.Items(Inventory(actor), items); foreach (IEntity item : items) if (IsShell(item) && !actual.Contains(item)) actual.Insert(item); } foreach (BaseInventoryStorageComponent storage : m_Stores) { if (!storage) return false; array items = {}; storage.GetAll(items); foreach (IEntity item : items) if (IsShell(item) && !actual.Contains(item)) actual.Insert(item); } bool allOriginal = true; bool singleOwner = true; foreach (ERR_NativeMortarShellOwner entry : m_Original) { if (!entry.Shell || !actual.Contains(entry.Shell)) allOriginal = false; int owners; foreach (IEntity actor : m_Crew) if (Has(actor, entry.Shell)) owners++; foreach (BaseInventoryStorageComponent storage : m_Stores) if (storage && storage.Contains(entry.Shell)) owners++; if (owners != 1) singleOwner = false; } return Conservation(m_Original.Count(), actual.Count(), allOriginal, singleOwner); } protected IEntity FirstGunnerShell() { array items = {}; ERR_Inventory.Items(Inventory(m_Gunner), items); foreach (IEntity item : items) if (IsShell(item)) return item; return null; } protected bool BeginMove(IEntity shell, BaseInventoryStorageComponent destination, float now) { if (!shell || !destination || m_MoveCallback) return false; IEntity current = CrewOwner(shell); // A vanilla empty box has storage but no inventory manager. The original // recipient manager performs its explicit pickup from the external storage. if (!current) current = OriginalOwner(shell); InventoryStorageManagerComponent manager = Inventory(current); if (!manager) return false; m_MoveShell = shell; m_MoveDestination = destination; m_MoveCallback = new ERR_NativeMortarMoveCallback(); m_MoveDeadline = now + 8000; if (!manager.TryMoveItemToStorage(shell, destination, -1, m_MoveCallback)) return false; return true; } protected int MoveResult(float now) { if (!m_MoveCallback) return 1; if (m_MoveCallback.Result < 0 || now >= m_MoveDeadline) return -1; if (m_MoveCallback.Result == 0 || !m_MoveShell || !m_MoveDestination || !m_MoveDestination.Contains(m_MoveShell)) return 0; InventoryItemComponent item = InventoryItemComponent.Cast(m_MoveShell.FindComponent(InventoryItemComponent)); if (!item || !item.GetParentSlot() || item.GetParentSlot().GetStorage() != m_MoveDestination) return 0; m_MoveCallback = null; m_MoveShell = null; m_MoveDestination = null; return 1; } protected bool Stash(IEntity shell, float now) { foreach (BaseInventoryStorageComponent storage : m_Stores) if (storage && storage.GetEstimatedCountFitForItem(shell) > 0) return BeginMove(shell, storage, now); return false; } protected void StageOrder(float now) { ERR_HQReport report = new ERR_HQReport(); report.Target = GetGame().GetPlayerController().GetControlledEntity(); report.Source = m_Battery.Group; report.Position = m_Battery.Gun.GetOrigin() + "700 0 0"; report.ObservedAt = now; report.DetailsObservedAt = now; report.TravelMode = "on foot"; report.MessageId = 900001 + m_Battery.OrderId; m_Battery.BeginRequest(report, now); m_Battery.PendingSupplyReport = report.Snapshot(); PrintFormat("ERR MORTAR TRANSFER FIXTURE: synthetic supply order=%1; radio bypassed; scheduler held; no artillery activity authorized", m_Battery.OrderId); } protected bool NoFire() { return m_Battery && m_Battery.TotalShots == m_ShotsAtStart && !m_Battery.Activity && !m_Battery.RadioOrder && !m_Battery.Released; } void Stop() { if (m_Stopped) return; m_Stopped = true; if (m_Battery) m_Battery.Cancel("native transfer fixture stopped"); if (m_System) { m_System.Stop(); // do not resume automatic fire while test items may be stored if (m_Held) m_System.ERR_ReleaseTransferHold(this); } m_Held = false; // Never delete a box containing borrowed shells or an outstanding operation. // Failure preserves it for diagnosis/normal round-owned cleanup. if (!m_MoveCallback) { foreach (IEntity box : m_Boxes) { if (!box) continue; BaseInventoryStorageComponent storage = BaseInventoryStorageComponent.Cast(box.FindComponent(BaseInventoryStorageComponent)); array items = {}; if (storage) storage.GetAll(items); if (storage && items.IsEmpty()) ERR_Actors.DeleteSpawned(box); } } } int Tick(float now) { if (m_Complete) return 1; if (m_Stopped) return -1; if (!Enabled() || now > m_Deadline) { Check(false, "enabled server fixture and bounded 180-second deadline"); return -1; } if (now < m_Wait) return 0; if (m_MoveCallback) { int moved = MoveResult(now); if (moved < 0) { Check(false, "fixture inventory move completed with exact destination before deadline; borrowed shells retained"); return -1; } if (moved == 0) return 0; if (!Check(Conserved(), "all original 27 shell identities conserved after fixture move")) return -1; } if (m_Step == 0) { m_System = ERR_MortarSystem.Get(); if (!m_System) return 0; m_Battery = m_System.ERR_TransferBattery(); if (!m_Battery || !m_Battery.StartupActivated) return 0; if (!Check(m_Battery.Operational() && m_Battery.CountShells() == 27 && !m_Battery.Activity && !m_Battery.RadioOrder && !m_Battery.CommandOpen, "actual initialized idle battery has finite 27 shells")) return -1; if (!Check(m_System.ERR_HoldForTransfer(this), "opt-in mechanism fixture holds automatic mortar scheduler")) return -1; m_Held = true; m_AttemptsAtStart = m_Battery.ERR_TransferAttempts(); m_ShotsAtStart = m_Battery.TotalShots; array agents = {}; m_Battery.Group.GetAgents(agents); foreach (AIAgent agent : agents) { if (!agent || !ERR_Actors.CanFight(agent.GetControlledEntity())) continue; IEntity actor = agent.GetControlledEntity(); if (m_Crew.Contains(actor)) continue; m_Crew.Insert(actor); array items = {}; ERR_Inventory.Items(Inventory(actor), items); foreach (IEntity item : items) { if (!IsShell(item)) continue; ERR_NativeMortarShellOwner entry = new ERR_NativeMortarShellOwner(); entry.Shell = item; entry.OriginalOwner = actor; m_Original.Insert(entry); } if (!m_Gunner && actor != m_Battery.RadioOperator) m_Gunner = actor; } if (!Check(m_Crew.Count() == 4 && m_Original.Count() == 27 && m_Gunner != null, "four real crew and original shell-owner identities captured")) return -1; SCR_AIVehicleUsageComponent usage = SCR_AIVehicleUsageComponent.Cast(m_Battery.Gun.FindComponent(SCR_AIVehicleUsageComponent)); if (usage) m_Seat = usage.GetTurretCompartmentSlot(); if (m_Seat && m_Seat.GetOccupant()) { IEntity occupant = m_Seat.GetOccupant(); if (!Check(m_Crew.Contains(occupant) && occupant != m_Battery.RadioOperator && ERR_Actors.CanFight(occupant), "existing native seat occupant is original fighting gunner")) return -1; m_Gunner = occupant; } else { CompartmentAccessComponent access = CompartmentAccessComponent.Cast(m_Gunner.FindComponent(CompartmentAccessComponent)); if (!Check(m_Seat && access && access.GetInVehicle(m_Battery.Gun, m_Seat, true, -1, ECloseDoorAfterActions.INVALID, false), "native seating request selects actual original crew gunner")) return -1; } if (!Check(m_Battery.InventoryShells(Inventory(m_Gunner)) == 9, "selected actual gunner owns its original nine physical shells")) return -1; for (int i = 0; i < 2; i++) { IEntity box = ERR_Actors.Spawn(STORE, ERR_Actors.Ground(m_Battery.Gun.GetOrigin() + Vector(3 + i * 2, 0, 3))); if (!Check(box != null, "fixture-owned empty vanilla storage created")) return -1; m_Boxes.Insert(box); m_Mode.Own(box); BaseInventoryStorageComponent storage = BaseInventoryStorageComponent.Cast(box.FindComponent(BaseInventoryStorageComponent)); if (!Check(storage != null, "real vanilla storage component available without attaching it to gunner inventory")) return -1; m_Stores.Insert(storage); } m_Step = 1; m_StepDeadline = now + 8000; m_Wait = now + 500; return 0; } if (!NoFire()) { Check(false, "no artillery activity, committed load or physical projectile during inventory mechanism test"); return -1; } if (m_Step == 1) { if (m_Seat.GetOccupant() != m_Gunner) { if (now >= m_StepDeadline) { Check(false, "actual original gunner occupies native mortar seat"); return -1; } return 0; } if (!Check(Conserved(), "27 original shells have exactly one observed owner each")) return -1; m_Step = 2; } if (m_Step == 2 || m_Step == 5) { IEntity shell = FirstGunnerShell(); if (shell) { if (!Check(Stash(shell, now), "one existing gunner shell move to vanilla store accepted; no shell minted")) return -1; return 0; } if (!Check(m_Battery.InventoryShells(Inventory(m_Gunner)) == 0 && Conserved(), "actual gunner empty while all 27 original shells remain in crew or fixture storage")) return -1; StageOrder(now); ERR_MortarSupplyState started = m_Battery.SupplyState(now); m_ObservedTransfer = m_Battery.ERR_TransferForProbe(); if (!Check(started == ERR_MortarSupplyState.PENDING && m_ObservedTransfer && m_ObservedTransfer.ERR_RequestAccepted() && m_ObservedTransfer.ERR_TransferRecipient() == m_Gunner && m_ObservedTransfer.CapturedOrder() == m_Battery.OrderId, "production exact-shell request accepted once with captured current order and real gunner")) return -1; if (m_Step == 2) { m_FirstShell = m_ObservedTransfer.ERR_TransferredShell(); m_Step = 3; } else { m_SecondShell = m_ObservedTransfer.ERR_TransferredShell(); m_CallbackAtCancel = m_ObservedTransfer.ERR_CallbackState(); m_CancelOrder = m_Battery.OrderId; m_Battery.Cancel("native fixture cancels authorization immediately after accepted inventory request"); if (!Check(m_SecondShell && m_SecondShell != m_FirstShell && !m_Battery.CommandOpen && m_Battery.SupplyState(now) == ERR_MortarSupplyState.FAILED, "canceled current order cannot report supply READY or issue another request")) return -1; PrintFormat("ERR MORTAR TRANSFER CANCEL: callbackBeforeCancel=%1 order=%2; accepted native move is not retracted", m_CallbackAtCancel, m_CancelOrder); m_Step = 6; } m_StepDeadline = now + 8000; m_Wait = now + 250; return 0; } if (m_Step == 3) { ERR_MortarSupplyState state = m_Battery.SupplyState(now); if (state == ERR_MortarSupplyState.FAILED || now >= m_StepDeadline) { Check(false, "production transfer completes within its bounded deadline"); return -1; } if (state == ERR_MortarSupplyState.PENDING) return 0; if (!Check(m_ObservedTransfer.ERR_CallbackState() == 1 && Has(m_Gunner, m_FirstShell) && !Has(OriginalOwner(m_FirstShell), m_FirstShell) && Conserved() && m_Battery.ERR_TransferAttempts() == m_AttemptsAtStart + 1, "successful native callback and exact donor-to-gunner ownership, one attempt, original 27 conserved")) return -1; if (!Check(m_Battery.SupplyState(now) == ERR_MortarSupplyState.READY && m_Battery.ERR_TransferAttempts() == m_AttemptsAtStart + 1, "repeated supply check reuses actual inventory without another move")) return -1; m_Battery.Cancel("native success subcase finished; no artillery activity requested"); m_Step = 5; return 0; } if (m_Step == 6) { if (m_Battery.SupplyState(now) != ERR_MortarSupplyState.FAILED || m_Battery.OrderId != m_CancelOrder || m_Battery.ERR_TransferAttempts() != m_AttemptsAtStart + 2) { Check(false, "canceled order/generation remains failed with exactly two total move attempts"); return -1; } if (m_ObservedTransfer.ERR_CallbackState() == 0 || m_ObservedTransfer.Unresolved()) { if (now >= m_StepDeadline) { Check(false, "accepted canceled operation reached definite native outcome; no ambiguous retry"); return -1; } return 0; } if (!Check(m_ObservedTransfer.ERR_CallbackState() == 1 && Has(m_Gunner, m_SecondShell) && Conserved(), "canceled accepted native move has definite physical outcome with exactly two attempts and original 27 conserved")) return -1; if (m_CallbackAtCancel == 0) Print("ERR SMOKE PASS: mortar transfer actual native completion observed after authorization cancellation"); else Print("ERR MORTAR TRANSFER LIMIT: native callback completed synchronously before cancellation; late callback timing not exercised"); m_Step = 7; return 0; } if (m_Step == 7) { // Return foreign shells first, leaving room for each original nine-round kit. IEntity misplaced; foreach (ERR_NativeMortarShellOwner entry : m_Original) if (Has(m_Gunner, entry.Shell) && entry.OriginalOwner != m_Gunner) { misplaced = entry.Shell; break; } if (!misplaced) foreach (ERR_NativeMortarShellOwner entry : m_Original) if (InStore(entry.Shell)) { misplaced = entry.Shell; break; } if (misplaced) { InventoryStorageManagerComponent recipient = Inventory(OriginalOwner(misplaced)); BaseInventoryStorageComponent destination; if (recipient) destination = recipient.FindStorageForItem(misplaced, EStoragePurpose.PURPOSE_DEPOSIT); if (!Check(destination && BeginMove(misplaced, destination, now), "one borrowed shell return to its original crew owner accepted")) return -1; return 0; } foreach (ERR_NativeMortarShellOwner entry : m_Original) if (!Check(Has(entry.OriginalOwner, entry.Shell), "original shell identity returned to original crew inventory")) return -1; if (!Check(Conserved() && m_Battery.CountShells() == 27 && NoFire(), "all original ammunition restored; no minted/deleted shell or shot")) return -1; foreach (BaseInventoryStorageComponent storage : m_Stores) { array items = {}; storage.GetAll(items); if (!Check(items.IsEmpty(), "borrowed-ammunition fixture storage empty before deletion")) return -1; } Stop(); m_Complete = true; return 1; } return 0; } } #endif