Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
361 lines
31 KiB
Plaintext
361 lines
31 KiB
Plaintext
--- /dev/null
|
|
+++ b/addons/core/Scripts/Game/ERR/10_AI/ERR_InfantryMineTransfer.c
|
|
@@ -0,0 +1,217 @@
|
|
+class ERR_InfantryMineCallback : ScriptedInventoryOperationCallback
|
|
+{
|
|
+ ERR_InfantryMineTransfer Transfer;
|
|
+ protected override void OnComplete() { if (Transfer) Transfer.Acknowledge(true); }
|
|
+ protected override void OnAllComplete() { if (Transfer) Transfer.Acknowledge(true); }
|
|
+ protected override void OnFailed() { if (Transfer) Transfer.Acknowledge(false); }
|
|
+ protected override void OnAllFailed() { if (Transfer) Transfer.Acknowledge(false); }
|
|
+}
|
|
+
|
|
+// One finite original PMN4 tops up an existing original carrier from one to two.
|
|
+// The coordinator owns radio authorization and the shared truck/recipient claim.
|
|
+class ERR_InfantryMineTransfer
|
|
+{
|
|
+ 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_Mine;
|
|
+ protected IEntity m_CarriedMine;
|
|
+ protected IEntity m_Driver;
|
|
+ protected ref ERR_ForceRecord m_DonorRecord;
|
|
+ protected ref ERR_ForceRecord m_RecipientRecord;
|
|
+ protected ref ERR_HQStatusSampler m_Roster;
|
|
+ protected int m_Slot;
|
|
+ protected int m_Strength;
|
|
+ protected InventoryStorageManagerComponent m_Source;
|
|
+ protected InventoryStorageManagerComponent m_Destination;
|
|
+ protected InventoryStorageSlot m_FromSlot;
|
|
+ protected BaseInventoryStorageComponent m_FromStorage;
|
|
+ protected BaseInventoryStorageComponent m_Storage;
|
|
+ protected ref ERR_FiniteMagazineReservation m_Reservation;
|
|
+ protected ref ERR_InfantryMineCallback m_Callback;
|
|
+ protected bool m_Submitted;
|
|
+ protected int m_Acknowledgement;
|
|
+ protected float m_Started = -1;
|
|
+ protected float m_LastTick = -1;
|
|
+ protected ERR_InfantryAmmoTransferState m_State = ERR_InfantryAmmoTransferState.PENDING;
|
|
+ protected string m_Reason;
|
|
+
|
|
+ void ERR_InfantryMineTransfer(ERR_GameMode mode, Vehicle donorVehicle, SCR_AIGroup donorGroup, IEntity recipientActor, SCR_AIGroup recipientGroup, IEntity exactMine, ERR_FiniteMagazineReservation reservation, ERR_HQStatusSampler originalRoster, int originalSlot)
|
|
+ {
|
|
+ m_Mode = mode; m_Donor = donorVehicle; m_DonorGroup = donorGroup;
|
|
+ m_Recipient = recipientActor; m_RecipientGroup = recipientGroup; m_Mine = exactMine;
|
|
+ m_Reservation = reservation; m_Roster = originalRoster; m_Slot = originalSlot;
|
|
+ if (!mode || !mode.Forces() || !donorVehicle || !recipientActor || !originalRoster) return;
|
|
+ m_DonorRecord = mode.Forces().FindGroup(donorGroup); m_RecipientRecord = mode.Forces().FindGroup(recipientGroup);
|
|
+ m_Strength = originalRoster.InitialStrength();
|
|
+ m_Source = InventoryStorageManagerComponent.Cast(donorVehicle.FindComponent(InventoryStorageManagerComponent));
|
|
+ m_Destination = InventoryStorageManagerComponent.Cast(recipientActor.FindComponent(InventoryStorageManagerComponent));
|
|
+ SCR_AIVehicleUsageComponent usage = SCR_AIVehicleUsageComponent.Cast(donorVehicle.FindComponent(SCR_AIVehicleUsageComponent));
|
|
+ if (usage && usage.GetPilotCompartmentSlot()) m_Driver = usage.GetPilotCompartmentSlot().GetOccupant();
|
|
+ m_CarriedMine = Carrier(recipientActor, recipientGroup);
|
|
+ if (!exactMine) return;
|
|
+ InventoryItemComponent item = InventoryItemComponent.Cast(exactMine.FindComponent(InventoryItemComponent));
|
|
+ if (item) m_FromSlot = item.GetParentSlot();
|
|
+ if (m_FromSlot) m_FromStorage = m_FromSlot.GetStorage();
|
|
+ }
|
|
+ static bool ReadyMine(IEntity item)
|
|
+ {
|
|
+ if (!ERR_Actors.Alive(item) || !item.GetPrefabData() || item.GetPrefabData().GetPrefabName() != ERR_EnemyLoadoutCatalog.MINE) return false;
|
|
+ if (!item.FindComponent(SCR_AntiPersonnelMineCollisionHandlerComponent)) return false;
|
|
+ SCR_PressureTriggerComponent trigger = SCR_PressureTriggerComponent.Cast(item.FindComponent(SCR_PressureTriggerComponent));
|
|
+ return trigger && !trigger.IsActivated() && !trigger.IsActivatedByDefault();
|
|
+ }
|
|
+ // Exclusion is used only after submission: the incoming item may already be
|
|
+ // carried, but the exact pre-existing one must still prove carrier entitlement.
|
|
+ static IEntity Carrier(IEntity actor, SCR_AIGroup group, IEntity exclude = null)
|
|
+ {
|
|
+ if (!Replication.IsServer() || !group || group.IsSlave() || group.GetPlayerCount(true) > 0) return null;
|
|
+ if (!ERR_Actors.CanFight(actor) || ERR_Actors.Player(actor) || ERR_Actors.Group(actor) != group
|
|
+ || CompartmentAccessComponent.GetVehicleIn(actor)) return null;
|
|
+ InventoryStorageManagerComponent inventory = InventoryStorageManagerComponent.Cast(actor.FindComponent(InventoryStorageManagerComponent));
|
|
+ if (!inventory || inventory.GetOwner() != actor) return null;
|
|
+ array<IEntity> items = {}; inventory.GetItems(items);
|
|
+ if (items.Count() > 128) return null;
|
|
+ array<IEntity> seen = {}; IEntity found;
|
|
+ foreach (IEntity item : items)
|
|
+ {
|
|
+ if (!item || !item.GetPrefabData()) return null;
|
|
+ if (seen.Contains(item)) continue;
|
|
+ seen.Insert(item);
|
|
+ if (item == exclude || item.GetPrefabData().GetPrefabName() != ERR_EnemyLoadoutCatalog.MINE) continue;
|
|
+ InventoryItemComponent carried = InventoryItemComponent.Cast(item.FindComponent(InventoryItemComponent));
|
|
+ if (!carried || !carried.GetParentSlot() || carried.GetParentSlot().GetAttachedEntity() != item
|
|
+ || !ERR_InfantryAmmoTransfer.StoredUnder(item, actor)) return null;
|
|
+ if (!item.FindComponent(SCR_AntiPersonnelMineCollisionHandlerComponent)
|
|
+ || !item.FindComponent(SCR_PressureTriggerComponent) || !item.FindComponent(DamageManagerComponent)) return null;
|
|
+ if (!ReadyMine(item)) continue;
|
|
+ if (found) return null;
|
|
+ found = item;
|
|
+ }
|
|
+ return found;
|
|
+ }
|
|
+ bool Submitted() { return m_Submitted; }
|
|
+ string Reason() { return m_Reason; }
|
|
+ void Acknowledge(bool success)
|
|
+ {
|
|
+ if (!m_Submitted) return;
|
|
+ if (!success) m_Acknowledgement = -1;
|
|
+ else if (m_Acknowledgement == 0) m_Acknowledgement = 1;
|
|
+ }
|
|
+ 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("mine delivery authority stopped; submitted movement remains spent and observable"); }
|
|
+ protected bool ManagedStorage(InventoryStorageManagerComponent inventory, BaseInventoryStorageComponent storage, IEntity owner)
|
|
+ {
|
|
+ if (!inventory || !storage || !owner || inventory.GetOwner() != owner) return false;
|
|
+ if (!ERR_InfantryAmmoTransfer.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_Mine) return -1;
|
|
+ array<IEntity> items = {}; inventory.GetItems(items, EStoragePurpose.PURPOSE_DEPOSIT);
|
|
+ if (items.Count() > 128) return -1;
|
|
+ if (items.Contains(m_Mine)) return 1;
|
|
+ return 0;
|
|
+ }
|
|
+ protected bool InSlot(InventoryStorageSlot slot)
|
|
+ {
|
|
+ if (!m_Mine || !slot || slot.GetAttachedEntity() != m_Mine) return false;
|
|
+ InventoryItemComponent item = InventoryItemComponent.Cast(m_Mine.FindComponent(InventoryItemComponent));
|
|
+ return item && item.GetParentSlot() == slot;
|
|
+ }
|
|
+ protected bool OriginalProof()
|
|
+ {
|
|
+ return m_FromSlot && m_FromSlot.GetStorage() == m_FromStorage
|
|
+ && ManagedStorage(m_Source, m_FromStorage, m_Donor) && InSlot(m_FromSlot)
|
|
+ && m_Destination && m_Destination.GetOwner() == m_Recipient
|
|
+ && Ownership(m_Source) == 1 && Ownership(m_Destination) == 0;
|
|
+ }
|
|
+ protected bool PhysicalProof()
|
|
+ {
|
|
+ if (!m_Mine || !m_Source || !m_Donor || m_Source.GetOwner() != m_Donor) return false;
|
|
+ if (!ManagedStorage(m_Destination, m_Storage, m_Recipient)
|
|
+ || Ownership(m_Source) != 0 || Ownership(m_Destination) != 1) return false;
|
|
+ InventoryItemComponent item = InventoryItemComponent.Cast(m_Mine.FindComponent(InventoryItemComponent));
|
|
+ return item && item.GetParentSlot() && item.GetParentSlot().GetStorage() == m_Storage && InSlot(item.GetParentSlot());
|
|
+ }
|
|
+ bool Unresolved()
|
|
+ {
|
|
+ if (!m_Submitted || !m_Mine) return false;
|
|
+ if (m_Acknowledgement == 1 && PhysicalProof()) return false;
|
|
+ if (m_Acknowledgement < 0 && OriginalProof()) return false;
|
|
+ return true;
|
|
+ }
|
|
+ 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 (!m_Roster || !m_Roster.OwnsRoster(m_RecipientGroup) || m_Strength < 1 || m_Strength > 32) return false;
|
|
+ if (m_Roster.InitialStrength() != m_Strength || m_Roster.InitialMember(m_Slot) != m_Recipient
|
|
+ || !m_Roster.CanAttribute(m_Slot, m_Recipient, false)) return false;
|
|
+ if (!ERR_EnemyEquipmentRules.Eligible(m_Mode, m_RecipientGroup, m_Recipient) || !ERR_Actors.CanFight(m_Recipient)
|
|
+ || CompartmentAccessComponent.GetVehicleIn(m_Recipient)) return false;
|
|
+ if (m_DonorGroup.IsSlave() || m_RecipientGroup.IsSlave() || m_DonorGroup.GetPlayerCount(true) > 0) return false;
|
|
+ if (!m_Mode.EnemyFaction() || m_DonorGroup.GetFaction() != m_Mode.EnemyFaction()
|
|
+ || SCR_FactionManager.SGetFaction(m_Donor) != m_Mode.EnemyFaction()
|
|
+ || SCR_FactionManager.SGetFaction(m_Recipient) != m_Mode.EnemyFaction()) return false;
|
|
+ if (!m_DonorRecord || !m_RecipientRecord || m_Mode.Forces().FindGroup(m_DonorGroup) != m_DonorRecord
|
|
+ || m_Mode.Forces().FindGroup(m_RecipientGroup) != m_RecipientRecord) return false;
|
|
+ if (m_DonorRecord.Group != m_DonorGroup || !m_DonorRecord.Physical || m_DonorRecord.Eliminated) return false;
|
|
+ if (m_RecipientRecord.Group != m_RecipientGroup || !m_RecipientRecord.Physical || m_RecipientRecord.Eliminated) 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() || !m_Driver || usage.GetPilotCompartmentSlot().GetOccupant() != m_Driver) return false;
|
|
+ if (!ERR_EnemyEquipmentRules.Eligible(m_Mode, m_DonorGroup, m_Driver)
|
|
+ || SCR_FactionManager.SGetFaction(m_Driver) != m_Mode.EnemyFaction()
|
|
+ || !ERR_ConvoyRules.PartOf(CompartmentAccessComponent.GetVehicleIn(m_Driver), m_Donor)) return false;
|
|
+ if (!m_Source || !m_Destination || m_Source == m_Destination) return false;
|
|
+ if (m_Donor.FindComponent(InventoryStorageManagerComponent) != m_Source
|
|
+ || m_Recipient.FindComponent(InventoryStorageManagerComponent) != m_Destination) return false;
|
|
+ if (!m_CarriedMine || !ReadyMine(m_Mine)) return false;
|
|
+ IEntity exclude;
|
|
+ if (m_Submitted) exclude = m_Mine;
|
|
+ if (Carrier(m_Recipient, m_RecipientGroup, exclude) != m_CarriedMine) return false;
|
|
+ Physics physics = m_Donor.GetPhysics();
|
|
+ if (!physics) return false;
|
|
+ float speed = physics.GetVelocity().Length(); float angular = physics.GetAngularVelocity().Length();
|
|
+ return speed >= 0 && speed <= 0.25 && angular >= 0 && angular <= 0.1;
|
|
+ }
|
|
+ ERR_InfantryAmmoTransferState Tick(float now)
|
|
+ {
|
|
+ if (m_State != ERR_InfantryAmmoTransferState.PENDING) return m_State;
|
|
+ if (!ERR_ForceRules.Finite(now) || now < 0 || now < m_LastTick) return Fail("invalid mine move clock");
|
|
+ if (m_Started < 0) m_Started = now;
|
|
+ if (now - m_Started >= 8000) return Fail("native mine move deadline expired");
|
|
+ m_LastTick = now;
|
|
+ if (!Context() || !ERR_InfantryAmmoAccess.Clear(m_Donor, m_Recipient)) return Fail("original mine carrier, truck, stock or physical access lost");
|
|
+ if (!m_Submitted)
|
|
+ {
|
|
+ if (!m_Reservation || !OriginalProof()) return Fail("exact original mine is not exclusive truck stock");
|
|
+ m_Storage = m_Destination.FindStorageForItem(m_Mine, EStoragePurpose.PURPOSE_DEPOSIT);
|
|
+ if (!ManagedStorage(m_Destination, m_Storage, m_Recipient)
|
|
+ || !m_Source.CanMoveItemToStorage(m_Mine, m_Storage, -1)) return Fail("original mine carrier lacks native capacity");
|
|
+ m_Callback = new ERR_InfantryMineCallback(); m_Callback.Transfer = this;
|
|
+ if (!m_Reservation.Commit() || !m_Reservation.CommittedFor(m_Mine)) return Fail("shared finite mine reservation unavailable");
|
|
+ m_Submitted = true;
|
|
+ bool accepted = m_Source.TryMoveItemToStorage(m_Mine, m_Storage, -1, m_Callback);
|
|
+ if (!accepted && m_Acknowledgement != 1) return Fail("native mine move refused; exact identity remains spent");
|
|
+ }
|
|
+ if (m_Acknowledgement < 0) return Fail("native mine move failed; unproved custody remains quarantined");
|
|
+ if (m_Acknowledgement == 1 && PhysicalProof())
|
|
+ {
|
|
+ m_State = ERR_InfantryAmmoTransferState.SUCCEEDED;
|
|
+ m_Reason = "callback and exact carried storage confirm one finite PMN4 transferred";
|
|
+ }
|
|
+ return m_State;
|
|
+ }
|
|
+}
|
|
--- /dev/null
|
|
+++ b/tests/test_infantry_mine_transfer.py
|
|
@@ -0,0 +1,137 @@
|
|
+"""Actual finite mine transaction/callback and shared reservation; native boundaries doubled."""
|
|
+from pathlib import Path
|
|
+import os,re
|
|
+from policy_harness import ROOT,body,run
|
|
+P=Path(os.environ.get('ERR_MINE_TRANSFER_SOURCE',ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_InfantryMineTransfer.c'))
|
|
+s=P.read_text()
|
|
+for forbidden in ('GetItem(', 'TrySpawn', 'SetAmmoCount', 'ActivateTrigger(', 'DeleteEntity', 'SetOrigin'):
|
|
+ assert forbidden not in s
|
|
+assert s.count('TryMoveItemToStorage(')==1
|
|
+assert s.index('m_Reservation.Commit()')<s.index('m_Submitted = true;')<s.index('TryMoveItemToStorage(')
|
|
+reservation=(ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_FiniteMagazineReservation.c').read_text()
|
|
+stored=body(ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_InfantryAmmoTransfer.c',' static bool StoredUnder(')
|
|
+def cpp(s):
|
|
+ s=re.sub(r'//[^\n]*','',s).replace('protected ','').replace('override ','').replace('ref ','').replace('class ','struct ').replace('foreach (','for (').replace('array<','Array<').replace('null','nullptr')
|
|
+ s=s.replace('void ERR_InfantryMineTransfer(', 'ERR_InfantryMineTransfer(').replace('void ERR_FiniteMagazineReservation(', 'ERR_FiniteMagazineReservation(')
|
|
+ for typ in ('InventoryStorageManagerComponent','InventoryItemComponent','SCR_AIVehicleUsageComponent','SCR_PressureTriggerComponent'):
|
|
+ s=re.sub(typ+r'\.Cast\((\w+)\.FindComponent\('+typ+r'\)\)',r'\1.'+{'InventoryStorageManagerComponent':'inventory','InventoryItemComponent':'item','SCR_AIVehicleUsageComponent':'usage','SCR_PressureTriggerComponent':'trigger'}[typ],s)
|
|
+ for typ,field in [('InventoryStorageManagerComponent','inventory'),('SCR_AntiPersonnelMineCollisionHandlerComponent','collision'),('SCR_PressureTriggerComponent','trigger'),('DamageManagerComponent','damage')]:
|
|
+ s=re.sub(r'(\w+)\.FindComponent\('+typ+r'\)',r'\1.'+field,s)
|
|
+ for typ in ('ERR_GameMode','ERR_ForceRecord','ERR_HQStatusSampler','InventoryStorageManagerComponent','InventoryItemComponent','InventoryStorageSlot','BaseInventoryStorageComponent','SCR_AIVehicleUsageComponent','SCR_PressureTriggerComponent','SCR_AIGroupUtilityComponent','Physics','ERR_InfantryMineTransfer','ERR_InfantryMineCallback','ERR_FiniteMagazineReservation'):
|
|
+ s=re.sub(r'\b'+typ+r' (?=\w)',typ+'* ',s)
|
|
+ s=s.replace('Array<BaseInventoryStorageComponent>','Array<BaseInventoryStorageComponent*>').replace('Array<IEntity> attempted','Array<IEntity>* attempted')
|
|
+ for field in ('m_Original','m_Spent'):
|
|
+ s=s.replace('Array<IEntity> '+field,'Array<IEntity>* '+field).replace(field+'.',field+'->')
|
|
+ s=s.replace('Array<IEntity> original, Array<IEntity> spent','Array<IEntity>* original, Array<IEntity>* spent')
|
|
+ for ns in ('Replication','SCR_Global','ERR_GameMode','ERR_Actors','ERR_EnemyEquipmentRules','ERR_ConvoyRules','SCR_FactionManager','CompartmentAccessComponent','ERR_InfantryAmmoTransfer','ERR_InfantryAmmoAccess','ERR_ForceRules','ERR_EnemyLoadoutCatalog','ERR_InfantryAmmoTransferState','EStoragePurpose'):
|
|
+ s=s.replace(ns+'.',ns+'::')
|
|
+ for var in ('m_Mine','exactMine','mode','donorVehicle','recipientActor','originalRoster','usage','item','actor','group','inventory','carried','trigger','m_Mode','m_Donor','m_DonorGroup','m_Recipient','m_RecipientGroup','m_Roster','m_DonorRecord','m_RecipientRecord','m_Source','m_Destination','m_Reservation','m_Callback','m_FromSlot','m_FromStorage','m_Storage','slot','storage','utility','physics','Transfer','entity'):
|
|
+ s=re.sub(r'\b'+var+r'\.',var+'->',s)
|
|
+ for call in ('Forces()','GetPilotCompartmentSlot()','GetPrefabData()','GetParentSlot()'):
|
|
+ s=s.replace(call+'.',call+'->')
|
|
+ s=s.replace('m_VehicleMgr.','m_VehicleMgr->')
|
|
+ s=re.sub(r'\b(bool|int|IEntity) (\w+);',r'\1 \2{};',s)
|
|
+ s=re.sub(r'^( \w+\*) (\w+);',r'\1 \2=nullptr;',s,flags=re.M)
|
|
+ return s.replace('\n}\n','\n};\n')
|
|
+run(r'''
|
|
+#include <cassert>
|
|
+#include <vector>
|
|
+#include <string>
|
|
+#include <algorithm>
|
|
+#include <cmath>
|
|
+#include <functional>
|
|
+using string=std::string;
|
|
+template<class T>struct Array:std::vector<T>{using std::vector<T>::vector;int Count(){return this->size();}void Insert(T v){this->push_back(v);}bool Contains(T v){return std::find(this->begin(),this->end(),v)!=this->end();}void RemoveItem(T v){auto i=std::find(this->begin(),this->end(),v);if(i!=this->end())this->erase(i);}};
|
|
+struct Entity;struct Group;using IEntity=Entity*;using Vehicle=Entity*;using SCR_AIGroup=Group*;
|
|
+struct Vector{float n=0;float Length(){return n;}};
|
|
+struct Physics{Vector speed,angular;Vector GetVelocity(){return speed;}Vector GetAngularVelocity(){return angular;}};
|
|
+struct BaseInventoryStorageComponent{IEntity owner=nullptr;IEntity GetOwner(){return owner;}};
|
|
+struct InventoryStorageSlot{IEntity attached=nullptr;BaseInventoryStorageComponent*storage=nullptr;IEntity GetAttachedEntity(){return attached;}BaseInventoryStorageComponent*GetStorage(){return storage;}};
|
|
+struct InventoryItemComponent{InventoryStorageSlot*slot=nullptr;InventoryStorageSlot*GetParentSlot(){return slot;}};
|
|
+struct SCR_PressureTriggerComponent{bool active=false,initial=false;bool IsActivated(){return active;}bool IsActivatedByDefault(){return initial;}};
|
|
+struct Prefab{string name="PMN4";string GetPrefabName(){return name;}};
|
|
+struct Seat{IEntity occupant=nullptr;IEntity GetOccupant(){return occupant;}};
|
|
+struct SCR_AIVehicleUsageComponent{Seat*seat=nullptr;Seat*GetPilotCompartmentSlot(){return seat;}};
|
|
+struct InventoryStorageManagerComponent;
|
|
+struct Entity{InventoryStorageManagerComponent*inventory=nullptr;InventoryItemComponent*item=nullptr;SCR_PressureTriggerComponent*trigger=nullptr;SCR_AIVehicleUsageComponent*usage=nullptr;Prefab*prefab=nullptr;Physics*physics=nullptr;IEntity vehicle=nullptr,parent=nullptr;SCR_AIGroup group=nullptr;int faction=1;bool alive=true,fit=true,player=false,collision=true,damage=true;
|
|
+ Prefab*GetPrefabData(){return prefab;}Physics*GetPhysics(){return physics;}IEntity GetParent(){return parent;}};
|
|
+struct VehicleManager{bool found=true;bool FindVehicle(IEntity){return found;}};
|
|
+struct SCR_AIGroupUtilityComponent{bool military=true;VehicleManager*m_VehicleMgr=nullptr;bool IsMilitary(){return military;}};
|
|
+struct Group{bool slave=false;int players=0,faction=1;SCR_AIGroupUtilityComponent*utility=nullptr;bool IsSlave(){return slave;}int GetPlayerCount(bool){return players;}int GetFaction(){return faction;}SCR_AIGroupUtilityComponent*GetGroupUtilityComponent(){return utility;}};
|
|
+struct ERR_ForceRecord{SCR_AIGroup Group=nullptr;bool Physical=true,Eliminated=false;};
|
|
+struct Ledger{SCR_AIGroup donor=nullptr,recipient=nullptr;ERR_ForceRecord*d=nullptr,*r=nullptr;ERR_ForceRecord*FindGroup(SCR_AIGroup g){return g==donor?d:g==recipient?r:nullptr;}};
|
|
+struct ERR_GameMode{inline static ERR_GameMode*current=nullptr;bool active=true;Ledger*ledger=nullptr;static ERR_GameMode*Get(){return current;}bool Active(){return active;}Ledger*Forces(){return ledger;}int EnemyFaction(){return 1;}};
|
|
+struct ERR_HQStatusSampler{SCR_AIGroup group=nullptr;IEntity actor=nullptr;bool valid=true;int strength=1;bool OwnsRoster(SCR_AIGroup g){return g==group;}int InitialStrength(){return strength;}IEntity InitialMember(int i){return i==0?actor:nullptr;}bool CanAttribute(int i,IEntity a,bool){return valid&&i==0&&a==actor;}};
|
|
+struct Replication{inline static bool server=true;static bool IsServer(){return server;}};
|
|
+struct SCR_Global{inline static bool edit=false;static bool IsEditMode(){return edit;}};
|
|
+struct ERR_Actors{static bool Alive(IEntity e){return e&&e->damage&&e->alive;}static bool CanFight(IEntity e){return Alive(e)&&e->fit;}static bool Player(IEntity e){return e&&e->player;}static SCR_AIGroup Group(IEntity e){return e?e->group:nullptr;}};
|
|
+struct ERR_EnemyEquipmentRules{static bool Eligible(ERR_GameMode*,SCR_AIGroup g,IEntity e){return g&&!g->slave&&!g->players&&g->faction==1&&ERR_Actors::CanFight(e)&&!e->player&&e->group==g;}};
|
|
+struct SCR_FactionManager{static int SGetFaction(IEntity e){return e?e->faction:0;}};
|
|
+struct CompartmentAccessComponent{static IEntity GetVehicleIn(IEntity e){return e?e->vehicle:nullptr;}};
|
|
+struct ERR_ConvoyRules{inline static bool captured=false;static bool Captured(IEntity,SCR_AIGroup){return captured;}static bool PartOf(IEntity e,IEntity root){return e&&e==root;}};
|
|
+struct ERR_InfantryAmmoAccess{inline static bool clear=true;static bool Clear(IEntity,IEntity){return clear;}};
|
|
+struct ERR_ForceRules{static bool Finite(float n){return std::isfinite(n);}};
|
|
+struct ERR_EnemyLoadoutCatalog{inline static string MINE="PMN4";};
|
|
+struct EStoragePurpose{static const int PURPOSE_DEPOSIT=1;};
|
|
+enum ERR_InfantryAmmoTransferState{FAILED=-1,PENDING,SUCCEEDED};
|
|
+struct ScriptedInventoryOperationCallback{virtual ~ScriptedInventoryOperationCallback()=default;virtual void OnComplete(){}virtual void OnAllComplete(){}virtual void OnFailed(){}virtual void OnAllFailed(){}};
|
|
+struct InventoryStorageManagerComponent{IEntity owner=nullptr;Array<IEntity>items;Array<BaseInventoryStorageComponent*>storages;BaseInventoryStorageComponent*destination=nullptr;bool capacity=true,accept=true;int calls=0;ScriptedInventoryOperationCallback*last=nullptr;std::function<void()>effect;
|
|
+ IEntity GetOwner(){return owner;}void GetItems(Array<IEntity>&a,int=0){a=items;}void GetStorages(Array<BaseInventoryStorageComponent*>&a,int){a=storages;}BaseInventoryStorageComponent*FindStorageForItem(IEntity,int){return destination;}
|
|
+ bool CanMoveItemToStorage(IEntity,BaseInventoryStorageComponent*,int){return capacity;}
|
|
+ bool TryMoveItemToStorage(IEntity,BaseInventoryStorageComponent*,int,ScriptedInventoryOperationCallback*cb){calls++;last=cb;if(effect)effect();return accept;}};
|
|
+struct ERR_InfantryAmmoTransfer{
|
|
+'''+cpp(stored)+r'''
|
|
+};
|
|
+'''+cpp(reservation)+r'''
|
|
+struct ERR_InfantryMineTransfer;
|
|
+struct ERR_InfantryMineCallback : ScriptedInventoryOperationCallback{ERR_InfantryMineTransfer*Transfer=nullptr;void OnComplete();void OnAllComplete();void OnFailed();void OnAllFailed();};
|
|
+'''+cpp(s[s.index('class ERR_InfantryMineTransfer\n'):])+r'''
|
|
+'''+''.join(cpp(body(P,'protected override void '+method+'(')).replace('void '+method+'(', 'void ERR_InfantryMineCallback::'+method+'(') for method in ('OnComplete','OnAllComplete','OnFailed','OnAllFailed'))+r'''
|
|
+struct Fixture{ERR_GameMode mode;Ledger ledger;Group donorGroup,recipientGroup;ERR_ForceRecord donorRecord,recipientRecord;Entity truck,driver,actor,incoming,original,extra;InventoryStorageManagerComponent source,destination;BaseInventoryStorageComponent sourceStorage,destStorage;InventoryStorageSlot from,to,ownSlot,extraSlot;InventoryItemComponent inItem,ownItem,extraItem;Prefab prefab;SCR_PressureTriggerComponent inTrigger,ownTrigger,extraTrigger;SCR_AIVehicleUsageComponent usage;Seat seat;Physics physics;VehicleManager vehicleMgr;SCR_AIGroupUtilityComponent utility;ERR_HQStatusSampler roster;Array<IEntity>stock,spent;ERR_FiniteMagazineReservation reservation;
|
|
+ Fixture():reservation(&stock,&spent,&incoming){Replication::server=true;SCR_Global::edit=false;ERR_ConvoyRules::captured=false;ERR_InfantryAmmoAccess::clear=true;ERR_GameMode::current=&mode;mode.ledger=&ledger;
|
|
+ ledger.donor=&donorGroup;ledger.recipient=&recipientGroup;ledger.d=&donorRecord;ledger.r=&recipientRecord;donorRecord.Group=&donorGroup;recipientRecord.Group=&recipientGroup;
|
|
+ truck.inventory=&source;actor.inventory=&destination;truck.physics=&physics;truck.usage=&usage;usage.seat=&seat;seat.occupant=&driver;driver.vehicle=&truck;driver.group=&donorGroup;actor.group=&recipientGroup;donorGroup.utility=&utility;utility.m_VehicleMgr=&vehicleMgr;
|
|
+ source.owner=&truck;destination.owner=&actor;sourceStorage.owner=&truck;destStorage.owner=&actor;source.storages={&sourceStorage};destination.storages={&destStorage};destination.destination=&destStorage;
|
|
+ from.storage=&sourceStorage;from.attached=&incoming;to.storage=&destStorage;ownSlot.storage=&destStorage;ownSlot.attached=&original;extraSlot.storage=&destStorage;extraSlot.attached=&extra;
|
|
+ incoming.prefab=&prefab;incoming.trigger=&inTrigger;incoming.item=&inItem;inItem.slot=&from;original.prefab=&prefab;original.trigger=&ownTrigger;original.item=&ownItem;ownItem.slot=&ownSlot;extra.prefab=&prefab;extra.trigger=&extraTrigger;extra.item=&extraItem;extraItem.slot=&extraSlot;
|
|
+ source.items={&incoming};destination.items={&original};roster.group=&recipientGroup;roster.actor=&actor;stock={&incoming};assert(reservation.Reserve());}
|
|
+ ERR_InfantryMineTransfer Job(){return ERR_InfantryMineTransfer(&mode,&truck,&donorGroup,&actor,&recipientGroup,&incoming,&reservation,&roster,0);}
|
|
+ void Move(){source.items.clear();destination.items.Insert(&incoming);from.attached=nullptr;to.attached=&incoming;inItem.slot=&to;}
|
|
+};
|
|
+int main(){
|
|
+ {Fixture f;auto t=f.Job();assert(t.Tick(0)==PENDING&&t.Submitted()&&t.Unresolved()&&f.source.calls==1);assert(f.reservation.CommittedFor(&f.incoming)&&!f.reservation.ReleaseUnsubmitted());
|
|
+ f.source.last->OnComplete();assert(t.Tick(1)==PENDING&&t.Unresolved());f.Move();assert(t.Tick(2)==SUCCEEDED&&!t.Unresolved());assert(f.destination.items.Count()==2);t.Tick(3);assert(f.source.calls==1);}
|
|
+ // Partially constructed contexts never bind later or reach native mutation.
|
|
+ for(int k=0;k<10;k++){Fixture f;
|
|
+ if(k==4)f.mode.ledger=nullptr;if(k==5)f.ledger.d=nullptr;if(k==6)f.ledger.r=nullptr;if(k==7)f.seat.occupant=nullptr;
|
|
+ ERR_InfantryMineTransfer t(k==0?nullptr:&f.mode,k==1?nullptr:&f.truck,&f.donorGroup,k==2?nullptr:&f.actor,&f.recipientGroup,k==8?nullptr:&f.incoming,&f.reservation,k==3?nullptr:&f.roster,k==9?1:0);
|
|
+ assert(t.Tick(0)==FAILED&&!t.Submitted()&&f.source.calls==0);}
|
|
+ // No entitlement for zero/two carriers, uncertain inventory, or replaced original mine.
|
|
+ for(int k=0;k<8;k++){Fixture f;if(k==0)f.destination.items.clear();if(k==1)f.destination.items.Insert(&f.extra);if(k==2)f.ownTrigger.active=true;if(k==3)f.ownTrigger.initial=true;if(k==4)f.ownItem.slot=nullptr;if(k==5)f.ownSlot.attached=&f.extra;if(k==6)f.destination.items.Insert(nullptr);if(k==7)f.destination.items.resize(129,&f.original);
|
|
+ auto t=f.Job();assert(t.Tick(0)==FAILED&&!t.Submitted()&&f.source.calls==0&&f.reservation.ReleaseUnsubmitted());}
|
|
+ {Fixture f;auto t=f.Job();f.destination.items={&f.extra};assert(t.Tick(0)==FAILED&&f.source.calls==0);}
|
|
+ {Fixture f;f.extra.damage=false;f.destination.items.Insert(&f.extra);assert(!ERR_InfantryMineTransfer::Carrier(&f.actor,&f.recipientGroup));auto t=f.Job();assert(t.Tick(0)==FAILED&&f.source.calls==0);}
|
|
+ // Duplicates in native enumeration are one item, not a second entitlement.
|
|
+ {Fixture f;f.destination.items.Insert(&f.original);assert(ERR_InfantryMineTransfer::Carrier(&f.actor,&f.recipientGroup)==&f.original);}
|
|
+ // Atomic shared claim before native request, including synchronous callback and false return.
|
|
+ {Fixture f;f.source.accept=false;f.source.effect=[&]{assert(f.reservation.CommittedFor(&f.incoming)&&!f.reservation.ReleaseUnsubmitted());f.Move();f.source.last->OnComplete();};auto t=f.Job();assert(t.Tick(0)==SUCCEEDED&&!t.Unresolved()&&f.source.calls==1);}
|
|
+ {Fixture f;ERR_FiniteMagazineReservation rival(&f.stock,&f.spent,&f.incoming);assert(!rival.Reserve());auto t=f.Job();assert(t.Tick(0)==PENDING&&f.spent.Count()==1);}
|
|
+ {Fixture f;f.reservation.ReleaseUnsubmitted();auto t=f.Job();assert(t.Tick(0)==FAILED&&!t.Submitted()&&f.source.calls==0);}
|
|
+ {Fixture f;f.source.accept=false;auto t=f.Job();assert(t.Tick(0)==FAILED&&t.Submitted()&&t.Unresolved());f.source.last->OnFailed();assert(!t.Unresolved()&&!f.reservation.ReleaseUnsubmitted());t.Tick(1);assert(f.source.calls==1);}
|
|
+ // Failure cannot release partial movement; late success is not permission to retry.
|
|
+ {Fixture f;auto t=f.Job();t.Tick(0);f.inItem.slot=&f.to;f.source.last->OnFailed();assert(t.Tick(1)==FAILED&&t.Unresolved());f.source.last->OnComplete();assert(t.Unresolved()&&f.source.calls==1);}
|
|
+ {Fixture f;auto t=f.Job();t.Tick(0);t.Stop();f.mode.active=false;f.Move();f.source.last->OnComplete();assert(!t.Unresolved()&&t.Tick(1)==FAILED&&f.source.calls==1);}
|
|
+ {Fixture f;auto t=f.Job();t.Tick(0);assert(t.Tick(8000)==FAILED&&t.Unresolved());f.Move();f.source.last->OnComplete();assert(!t.Unresolved()&&t.Tick(8001)==FAILED&&f.source.calls==1);}
|
|
+ {Fixture f;auto t=f.Job();t.Tick(10);assert(t.Tick(9)==FAILED&&t.Unresolved()&&f.source.calls==1);}
|
|
+ // Only deletion, not damage/capture, settles an unknown one-item movement.
|
|
+ {Fixture f;auto t=f.Job();t.Tick(0);t.m_Mine=nullptr;f.reservation.m_Item=nullptr;f.stock[0]=nullptr;f.spent[0]=nullptr;assert(t.Tick(1)==FAILED&&!t.Unresolved());f.source.last->OnComplete();assert(t.Tick(2)==FAILED&&!f.reservation.ReleaseUnsubmitted()&&f.spent.Count()==1);}
|
|
+ for(int k=0;k<3;k++){Fixture f;auto t=f.Job();t.Tick(0);if(k==0)f.incoming.alive=false;if(k==1)f.actor.player=true;if(k==2){ERR_ConvoyRules::captured=true;}assert(t.Tick(1)==FAILED&&t.Unresolved()&&f.source.calls==1);}
|
|
+ // Original actor/ledger/driver/managers/capacity/reach/stock all remain required.
|
|
+ for(int k=0;k<25;k++){Fixture f;auto t=f.Job();ERR_ForceRecord replacement;InventoryStorageManagerComponent changed;
|
|
+ switch(k){case 0:Replication::server=false;break;case 1:SCR_Global::edit=true;break;case 2:f.mode.active=false;break;case 3:ERR_GameMode::current=nullptr;break;case 4:f.roster.valid=false;break;case 5:f.roster.actor=&f.extra;break;case 6:f.roster.strength=2;break;case 7:f.actor.fit=false;break;case 8:f.actor.player=true;break;case 9:f.actor.vehicle=&f.truck;break;case 10:f.ledger.r=&replacement;break;case 11:f.donorRecord.Eliminated=true;break;case 12:f.vehicleMgr.found=false;break;case 13:f.seat.occupant=&f.extra;break;case 14:f.truck.inventory=&changed;break;case 15:f.actor.inventory=&changed;break;case 16:f.physics.speed.n=1;break;case 17:f.physics.angular.n=1;break;case 18:ERR_InfantryAmmoAccess::clear=false;break;case 19:f.source.capacity=false;break;case 20:f.destination.destination=nullptr;break;case 21:f.inTrigger.active=true;break;case 22:f.from.attached=&f.extra;break;case 23:f.source.items.clear();break;case 24:f.destination.items.Insert(&f.incoming);break;}
|
|
+ assert(t.Tick(0)==FAILED&&!t.Submitted()&&f.source.calls==0);}
|
|
+ // Ownership lost after submission remains quarantined until exact physical proof.
|
|
+ {Fixture f;auto t=f.Job();t.Tick(0);f.roster.valid=false;assert(t.Tick(1)==FAILED&&t.Unresolved());f.Move();f.source.last->OnComplete();assert(!t.Unresolved()&&t.Tick(2)==FAILED);}
|
|
+}
|
|
+'''.replace(';if(', ';\n if('))
|
|
+print('PASS: actual finite PMN4 one-to-two entitlement, pinned original ownership, shared commit, physical custody, callback quarantine/deletion and no retry')
|