Capture current mission, AI, map and administration development
Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
This commit is contained in:
+232
@@ -0,0 +1,232 @@
|
||||
class ERR_MountedPickupCallback : ScriptedInventoryOperationCallback
|
||||
{
|
||||
ERR_MountedAmmoPickup 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); }
|
||||
}
|
||||
|
||||
// First courier leg: one exact finite truck magazine to an original crew worker.
|
||||
// The caller owns delivered HQ authorization, movement and an exclusive worker/
|
||||
// muzzle claim. This leaf neither requires a matching personal rifle nor loads
|
||||
// the gun. Submitted uncertainty must retain this operation and its claim.
|
||||
class ERR_MountedAmmoPickup
|
||||
{
|
||||
protected ERR_GameMode m_Mode;
|
||||
protected ref ERR_ConvoyTask m_Task;
|
||||
protected ref ERR_ConvoyReceipt m_Receipt;
|
||||
protected ref ERR_ConvoyVehicle m_Donor;
|
||||
protected ref ERR_ConvoyVehicle m_Target;
|
||||
protected Vehicle m_Truck;
|
||||
protected Vehicle m_Root;
|
||||
protected SCR_AIGroup m_DonorGroup;
|
||||
protected SCR_AIGroup m_TargetGroup;
|
||||
protected ref ERR_ForceRecord m_DonorRecord;
|
||||
protected ref ERR_ForceRecord m_TargetRecord;
|
||||
protected ref ERR_HQStatusSampler m_Roster;
|
||||
protected int m_Index;
|
||||
protected int m_WorkerSlot;
|
||||
protected IEntity m_Worker;
|
||||
protected IEntity m_Driver;
|
||||
protected IEntity m_TargetDriver;
|
||||
protected BaseWeaponComponent m_Weapon;
|
||||
protected WeaponSlotComponent m_Mount;
|
||||
protected InventoryStorageSlot m_WeaponSlot;
|
||||
protected BaseMuzzleComponent m_Muzzle;
|
||||
protected IEntity m_Loaded;
|
||||
protected IEntity m_Incoming;
|
||||
protected BaseMagazineComponent m_OldMagazine;
|
||||
protected BaseMagazineComponent m_NewMagazine;
|
||||
protected InventoryStorageSlot m_FromSlot;
|
||||
protected InventoryStorageSlot m_ToSlot;
|
||||
protected BaseInventoryStorageComponent m_FromStorage;
|
||||
protected BaseInventoryStorageComponent m_Destination;
|
||||
protected BaseInventoryStorageComponent m_ToStorage;
|
||||
protected InventoryStorageManagerComponent m_Carried;
|
||||
protected InventoryStorageManagerComponent m_TruckInventory;
|
||||
protected ref ERR_FiniteMagazineReservation m_Reservation;
|
||||
protected ref ERR_MountedPickupCallback m_Callback;
|
||||
protected bool m_Submitted;
|
||||
protected int m_Acknowledgement;
|
||||
protected float m_Started = -1;
|
||||
protected float m_LastTick = -1;
|
||||
protected ERR_MountedAmmoTransferState m_State = ERR_MountedAmmoTransferState.PENDING;
|
||||
protected string m_Reason;
|
||||
void ERR_MountedAmmoPickup(ERR_GameMode mode, ERR_ConvoyTask task, int vehicleIndex, ERR_HQStatusSampler originalRoster, int workerSlot, BaseWeaponComponent originalWeapon, BaseMuzzleComponent originalMuzzle, IEntity originalLoaded, IEntity exactIncoming, ERR_FiniteMagazineReservation pickupReservation)
|
||||
{
|
||||
m_Mode = mode; m_Task = task; m_Index = vehicleIndex; m_Roster = originalRoster; m_WorkerSlot = workerSlot;
|
||||
m_Weapon = originalWeapon; m_Muzzle = originalMuzzle; m_Loaded = originalLoaded; m_Incoming = exactIncoming; m_Reservation = pickupReservation;
|
||||
if (!mode || !mode.Forces() || !task || task.Vehicles.Count() != 6 || vehicleIndex < 0 || vehicleIndex >= 6 || vehicleIndex == 1 || !originalRoster) return;
|
||||
m_Receipt = task.Receipt; m_Donor = task.Vehicles[1]; m_Target = task.Vehicles[vehicleIndex];
|
||||
if (!m_Donor || !m_Target) return;
|
||||
m_Truck = m_Donor.Entity; m_Root = m_Target.Entity; m_DonorGroup = m_Donor.Group; m_TargetGroup = m_Target.Group;
|
||||
m_Driver = m_Donor.Driver; m_TargetDriver = m_Target.Driver;
|
||||
if (!m_Truck || !m_Root || !m_DonorGroup || !m_TargetGroup) return;
|
||||
m_DonorRecord = mode.Forces().FindGroup(m_DonorGroup); m_TargetRecord = mode.Forces().FindGroup(m_TargetGroup);
|
||||
m_Worker = originalRoster.InitialMember(workerSlot);
|
||||
if (!m_Worker || !m_Weapon || !m_Weapon.GetOwner() || !m_Loaded || !m_Incoming || m_Loaded == m_Incoming) return;
|
||||
InventoryItemComponent gunItem = InventoryItemComponent.Cast(m_Weapon.GetOwner().FindComponent(InventoryItemComponent));
|
||||
InventoryItemComponent loadedItem = InventoryItemComponent.Cast(m_Loaded.FindComponent(InventoryItemComponent));
|
||||
InventoryItemComponent incomingItem = InventoryItemComponent.Cast(m_Incoming.FindComponent(InventoryItemComponent));
|
||||
if (!gunItem || !loadedItem || !incomingItem) return;
|
||||
m_WeaponSlot = gunItem.GetParentSlot(); m_ToSlot = loadedItem.GetParentSlot(); m_FromSlot = incomingItem.GetParentSlot();
|
||||
if (!m_WeaponSlot || !m_ToSlot || !m_FromSlot) return;
|
||||
m_Mount = WeaponSlotComponent.Cast(m_WeaponSlot.GetParentContainer());
|
||||
m_FromStorage = m_FromSlot.GetStorage(); m_ToStorage = m_ToSlot.GetStorage();
|
||||
m_OldMagazine = BaseMagazineComponent.Cast(m_Loaded.FindComponent(BaseMagazineComponent));
|
||||
m_NewMagazine = BaseMagazineComponent.Cast(m_Incoming.FindComponent(BaseMagazineComponent));
|
||||
m_Carried = InventoryStorageManagerComponent.Cast(m_Worker.FindComponent(InventoryStorageManagerComponent));
|
||||
m_TruckInventory = InventoryStorageManagerComponent.Cast(m_Truck.FindComponent(InventoryStorageManagerComponent));
|
||||
}
|
||||
bool Submitted() { return m_Submitted; }
|
||||
string Reason() { return m_Reason; }
|
||||
// A failure remains sticky; success notification alone proves neither item.
|
||||
void Acknowledge(bool success)
|
||||
{
|
||||
if (!m_Submitted) return;
|
||||
if (!success) m_Acknowledgement = -1;
|
||||
else if (m_Acknowledgement == 0) m_Acknowledgement = 1;
|
||||
}
|
||||
protected ERR_MountedAmmoTransferState Fail(string reason)
|
||||
{
|
||||
if (m_State == ERR_MountedAmmoTransferState.PENDING) { m_State = ERR_MountedAmmoTransferState.FAILED; m_Reason = reason; }
|
||||
return m_State;
|
||||
}
|
||||
void Stop() { Fail("courier authority stopped; submitted pickup cannot be cancelled or refunded"); }
|
||||
protected static bool Stationary(Vehicle vehicle)
|
||||
{
|
||||
if (!vehicle || !ERR_Actors.Alive(vehicle) || !vehicle.GetPhysics()) return false;
|
||||
float speed = vehicle.GetPhysics().GetVelocity().Length();
|
||||
float angular = vehicle.GetPhysics().GetAngularVelocity().Length();
|
||||
return speed >= 0 && speed <= 0.25 && angular >= 0 && angular <= 0.1;
|
||||
}
|
||||
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_Task || !m_Task.Ready || m_Task.Stopped || !m_Receipt || m_Task.Receipt != m_Receipt || !m_Receipt.Consumed
|
||||
|| m_Task.Vehicles.Count() != 6 || m_Receipt.Records.Count() < 6 || m_Index < 0 || m_Index >= 6 || m_Index == 1) return false;
|
||||
if (!m_Donor || !m_Target || m_Task.Vehicles[1] != m_Donor || m_Task.Vehicles[m_Index] != m_Target
|
||||
|| m_Donor.Index != 1 || m_Target.Index != m_Index || m_Donor.Entity != m_Truck || m_Target.Entity != m_Root
|
||||
|| m_Donor.Group != m_DonorGroup || m_Target.Group != m_TargetGroup) return false;
|
||||
if (m_Donor.Released || m_Target.Released
|
||||
|| !m_Donor.Registered || !m_Target.Registered
|
||||
|| !m_Donor.CrewValid(m_Mode) || !m_Target.CrewValid(m_Mode)) return false;
|
||||
if (!m_Driver || m_Donor.Driver != m_Driver || !m_TargetDriver || m_Target.Driver != m_TargetDriver
|
||||
|| m_Worker == m_TargetDriver) return false;
|
||||
if (!m_DonorRecord || !m_TargetRecord || m_Mode.Forces().FindGroup(m_DonorGroup) != m_DonorRecord
|
||||
|| m_Mode.Forces().FindGroup(m_TargetGroup) != m_TargetRecord || m_DonorRecord.Id != m_Receipt.Records[1]
|
||||
|| m_TargetRecord.Id != m_Receipt.Records[m_Index]) return false;
|
||||
ERR_HQ hq = ERR_HQ.Cast(m_Mode.Command());
|
||||
if (!hq || !hq.ValidateBoundVehicle(m_DonorGroup, m_Truck) || !hq.ValidateBoundVehicle(m_TargetGroup, m_Root)) return false;
|
||||
if (!m_Roster || !m_Roster.OwnsRoster(m_TargetGroup) || !m_Roster.ValidateBoundVehicle(m_Root)
|
||||
|| !m_Roster.CanAttribute(m_WorkerSlot, m_Worker, false) || !m_Target.Members.Contains(m_Worker)
|
||||
|| !ERR_EnemyEquipmentRules.Eligible(m_Mode, m_TargetGroup, m_Worker) || !ERR_Actors.CanFight(m_Worker)
|
||||
|| CompartmentAccessComponent.GetVehicleIn(m_Worker)) return false;
|
||||
if (!m_Reservation || !Stationary(m_Truck) || !Stationary(m_Root)) return false;
|
||||
if (m_Submitted && !m_Reservation.CommittedFor(m_Incoming)) return false;
|
||||
if (InventoryStorageManagerComponent.Cast(m_Worker.FindComponent(InventoryStorageManagerComponent)) != m_Carried
|
||||
|| InventoryStorageManagerComponent.Cast(m_Truck.FindComponent(InventoryStorageManagerComponent)) != m_TruckInventory) return false;
|
||||
return TargetShape() && InSlot(m_Loaded, m_ToSlot) && m_Muzzle.GetMagazine() == m_OldMagazine;
|
||||
}
|
||||
protected bool TargetShape()
|
||||
{
|
||||
if (!m_Root || !m_Weapon || !m_Weapon.GetOwner() || !m_Mount || !m_WeaponSlot || !m_Muzzle || !m_ToSlot || !m_ToStorage) return false;
|
||||
IEntity gun = m_Weapon.GetOwner();
|
||||
InventoryItemComponent item = InventoryItemComponent.Cast(gun.FindComponent(InventoryItemComponent));
|
||||
if (!item || item.GetParentSlot() != m_WeaponSlot || m_Mount.GetSlotInfo() != m_WeaponSlot || m_Mount.GetWeaponEntity() != gun
|
||||
|| m_WeaponSlot.GetAttachedEntity() != gun || m_WeaponSlot.GetParentContainer() != m_Mount
|
||||
|| !ERR_ConvoyRules.PartOf(m_Mount.GetOwner(), m_Root) || !ERR_ConvoyRules.PartOf(m_Muzzle.GetOwner(), m_Root)
|
||||
|| m_ToSlot.GetParentContainer() != m_Muzzle || m_ToSlot.GetStorage() != m_ToStorage || m_ToStorage.GetOwner() != gun
|
||||
|| m_Muzzle.GetMuzzleType() != EMuzzleType.MT_BaseMuzzle) return false;
|
||||
array<BaseMuzzleComponent> muzzles = {}; m_Weapon.GetMuzzlesList(muzzles);
|
||||
return muzzles.Count() <= 8 && muzzles.Contains(m_Muzzle);
|
||||
}
|
||||
protected bool MagazineValid(BaseMagazineComponent magazine)
|
||||
{
|
||||
if (!magazine || !m_Muzzle || !magazine.GetMagazineWell() || !m_Muzzle.GetMagazineWell()
|
||||
|| !magazine.GetMagazineWell().IsInherited(m_Muzzle.GetMagazineWell().Type())) return false;
|
||||
int capacity = magazine.GetMaxAmmoCount(); int rounds = magazine.GetAmmoCount();
|
||||
return capacity > 0 && capacity <= 100000 && rounds >= 0 && rounds <= capacity;
|
||||
}
|
||||
protected bool ManagedStorage(InventoryStorageManagerComponent inventory, BaseInventoryStorageComponent storage, IEntity owner)
|
||||
{
|
||||
if (!inventory || !storage || inventory.GetOwner() != owner || !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 Owned(InventoryStorageManagerComponent inventory, IEntity item)
|
||||
{
|
||||
if (!inventory || !item) return -1;
|
||||
array<IEntity> items = {}; inventory.GetItems(items, EStoragePurpose.PURPOSE_DEPOSIT);
|
||||
if (items.Count() > 128) return -1;
|
||||
if (items.Contains(item)) return 1;
|
||||
return 0;
|
||||
}
|
||||
protected bool InSlot(IEntity entity, InventoryStorageSlot slot)
|
||||
{
|
||||
if (!entity || !slot || slot.GetAttachedEntity() != entity) return false;
|
||||
InventoryItemComponent item = InventoryItemComponent.Cast(entity.FindComponent(InventoryItemComponent));
|
||||
return item && item.GetParentSlot() == slot;
|
||||
}
|
||||
protected bool OriginalProof()
|
||||
{
|
||||
return m_Truck && m_Worker && m_FromSlot && m_FromSlot.GetStorage() == m_FromStorage
|
||||
&& ManagedStorage(m_TruckInventory, m_FromStorage, m_Truck)
|
||||
&& m_Carried && m_Carried.GetOwner() == m_Worker && InSlot(m_Incoming, m_FromSlot)
|
||||
&& Owned(m_TruckInventory, m_Incoming) == 1 && Owned(m_Carried, m_Incoming) == 0;
|
||||
}
|
||||
protected bool PhysicalProof()
|
||||
{
|
||||
if (!m_Truck || !m_TruckInventory || m_TruckInventory.GetOwner() != m_Truck
|
||||
|| !ManagedStorage(m_Carried, m_Destination, m_Worker)
|
||||
|| Owned(m_TruckInventory, m_Incoming) != 0 || Owned(m_Carried, m_Incoming) != 1 || !m_Incoming) return false;
|
||||
InventoryItemComponent item = InventoryItemComponent.Cast(m_Incoming.FindComponent(InventoryItemComponent));
|
||||
return item && item.GetParentSlot() && item.GetParentSlot().GetStorage() == m_Destination
|
||||
&& item.GetParentSlot().GetAttachedEntity() == m_Incoming;
|
||||
}
|
||||
bool Unresolved()
|
||||
{
|
||||
if (!m_Submitted) return false;
|
||||
// A deleted exact item cannot complete this one-item move. Its committed
|
||||
// reservation stays spent; capture or damage alone does not settle custody.
|
||||
if (!m_Incoming) return false;
|
||||
if (m_Acknowledgement == 1 && PhysicalProof()) return false;
|
||||
if (m_Acknowledgement < 0 && OriginalProof()) return false;
|
||||
// False native return without a definitive callback is also quarantined.
|
||||
return true;
|
||||
}
|
||||
ERR_MountedAmmoTransferState Tick(float now)
|
||||
{
|
||||
if (m_State != ERR_MountedAmmoTransferState.PENDING) return m_State;
|
||||
if (!ERR_ForceRules.Finite(now) || now < 0) return Fail("invalid pickup clock");
|
||||
if (m_Started < 0) m_Started = now;
|
||||
if (now < m_LastTick || now - m_Started >= 8000) return Fail("native pickup deadline expired");
|
||||
m_LastTick = now;
|
||||
if (!Context() || !ERR_InfantryAmmoAccess.Clear(m_Truck, m_Worker)) return Fail("original convoy, worker, bound mount or physical source access lost");
|
||||
if (!m_Submitted)
|
||||
{
|
||||
if (!OriginalProof() || !m_OldMagazine || !m_NewMagazine || m_OldMagazine.GetOwner() != m_Loaded
|
||||
|| m_NewMagazine.GetOwner() != m_Incoming || !MagazineValid(m_OldMagazine) || !MagazineValid(m_NewMagazine)
|
||||
|| m_NewMagazine.IsUsed() || m_NewMagazine.GetAmmoCount() <= m_OldMagazine.GetAmmoCount()
|
||||
|| m_OldMagazine.GetAmmoCount() > m_OldMagazine.GetMaxAmmoCount() * 0.25 || m_FromSlot.IsLocked() || m_ToSlot.IsLocked())
|
||||
return Fail("exact finite stock or original low mounted magazine unavailable");
|
||||
m_Destination = m_Carried.FindStorageForItem(m_Incoming, EStoragePurpose.PURPOSE_DEPOSIT);
|
||||
if (!ManagedStorage(m_Carried, m_Destination, m_Worker)
|
||||
|| !m_TruckInventory.CanMoveItemToStorage(m_Incoming, m_Destination, -1)) return Fail("original worker has no compatible native storage capacity");
|
||||
m_Callback = new ERR_MountedPickupCallback(); m_Callback.Transfer = this;
|
||||
// Commit before entering native code: callbacks may run synchronously.
|
||||
if (!m_Reservation.Commit() || !m_Reservation.CommittedFor(m_Incoming)) return Fail("exact finite pickup reservation unavailable");
|
||||
m_Submitted = true;
|
||||
bool accepted = m_TruckInventory.TryMoveItemToStorage(m_Incoming, m_Destination, -1, m_Callback);
|
||||
if (!accepted && m_Acknowledgement != 1) return Fail("native pickup refused; exact stock remains spent");
|
||||
}
|
||||
if (m_Acknowledgement < 0) return Fail("native pickup failed; unproved movement remains quarantined");
|
||||
if (m_Acknowledgement == 1 && PhysicalProof())
|
||||
{
|
||||
m_State = ERR_MountedAmmoTransferState.SUCCEEDED;
|
||||
m_Reason = "callback and exact worker storage prove one finite physical magazine pickup";
|
||||
}
|
||||
return m_State;
|
||||
}
|
||||
}
|
||||
+243
@@ -0,0 +1,243 @@
|
||||
enum ERR_MountedAmmoTransferState { FAILED = -1, PENDING, SUCCEEDED }
|
||||
|
||||
class ERR_MountedAmmoCallback : ScriptedInventoryOperationCallback
|
||||
{
|
||||
ERR_MountedAmmoTransfer 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); }
|
||||
}
|
||||
|
||||
// Second courier leg only. The caller retains the original gun/muzzle/loaded
|
||||
// identities before dispatch, the original worker roster, and the committed
|
||||
// finite pickup reservation. Fresh delivered demand and exclusive HQ orders
|
||||
// remain caller responsibilities. This class never picks up from a remote truck.
|
||||
class ERR_MountedAmmoTransfer
|
||||
{
|
||||
protected ERR_GameMode m_Mode;
|
||||
protected ERR_ConvoyTask m_Task;
|
||||
protected ERR_ConvoyReceipt m_Receipt;
|
||||
protected ERR_ConvoyVehicle m_Donor;
|
||||
protected ERR_ConvoyVehicle m_Target;
|
||||
protected Vehicle m_Truck;
|
||||
protected Vehicle m_Root;
|
||||
protected SCR_AIGroup m_DonorGroup;
|
||||
protected SCR_AIGroup m_TargetGroup;
|
||||
protected ERR_ForceRecord m_DonorRecord;
|
||||
protected ERR_ForceRecord m_TargetRecord;
|
||||
protected ERR_HQStatusSampler m_Roster;
|
||||
protected int m_Index;
|
||||
protected int m_WorkerSlot;
|
||||
protected IEntity m_Worker;
|
||||
protected BaseWeaponComponent m_Weapon;
|
||||
protected WeaponSlotComponent m_Mount;
|
||||
protected InventoryStorageSlot m_WeaponSlot;
|
||||
protected BaseMuzzleComponent m_Muzzle;
|
||||
protected IEntity m_Loaded;
|
||||
protected IEntity m_Incoming;
|
||||
protected BaseMagazineComponent m_OldMagazine;
|
||||
protected BaseMagazineComponent m_NewMagazine;
|
||||
protected InventoryStorageSlot m_FromSlot;
|
||||
protected InventoryStorageSlot m_ToSlot;
|
||||
protected BaseInventoryStorageComponent m_FromStorage;
|
||||
protected BaseInventoryStorageComponent m_ToStorage;
|
||||
protected InventoryStorageManagerComponent m_Carried;
|
||||
protected InventoryStorageManagerComponent m_TruckInventory;
|
||||
protected ref ERR_FiniteMagazineReservation m_Reservation;
|
||||
protected ref ERR_MountedAmmoCallback m_Callback;
|
||||
protected bool m_Submitted;
|
||||
protected int m_Acknowledgement;
|
||||
protected float m_Started = -1;
|
||||
protected float m_LastTick = -1;
|
||||
protected ERR_MountedAmmoTransferState m_State = ERR_MountedAmmoTransferState.PENDING;
|
||||
protected string m_Reason;
|
||||
void ERR_MountedAmmoTransfer(ERR_GameMode mode, ERR_ConvoyTask task, int vehicleIndex, ERR_HQStatusSampler originalRoster, int workerSlot, BaseWeaponComponent originalWeapon, BaseMuzzleComponent originalMuzzle, IEntity originalLoaded, IEntity carriedIncoming, ERR_FiniteMagazineReservation committedPickup)
|
||||
{
|
||||
m_Mode = mode; m_Task = task; m_Index = vehicleIndex; m_Roster = originalRoster; m_WorkerSlot = workerSlot;
|
||||
m_Weapon = originalWeapon; m_Muzzle = originalMuzzle; m_Loaded = originalLoaded; m_Incoming = carriedIncoming; m_Reservation = committedPickup;
|
||||
if (!mode || !mode.Forces() || !task || task.Vehicles.Count() != 6 || vehicleIndex < 0 || vehicleIndex >= 6 || vehicleIndex == 1 || !originalRoster) return;
|
||||
m_Receipt = task.Receipt; m_Donor = task.Vehicles[1]; m_Target = task.Vehicles[vehicleIndex];
|
||||
if (!m_Donor || !m_Target) return;
|
||||
m_Truck = m_Donor.Entity; m_Root = m_Target.Entity; m_DonorGroup = m_Donor.Group; m_TargetGroup = m_Target.Group;
|
||||
if (!m_Truck || !m_Root || !m_DonorGroup || !m_TargetGroup) return;
|
||||
m_DonorRecord = mode.Forces().FindGroup(m_DonorGroup); m_TargetRecord = mode.Forces().FindGroup(m_TargetGroup);
|
||||
m_Worker = originalRoster.InitialMember(workerSlot);
|
||||
if (!m_Worker || !m_Weapon || !m_Weapon.GetOwner() || !m_Loaded || !m_Incoming || m_Loaded == m_Incoming) return;
|
||||
InventoryItemComponent gunItem = InventoryItemComponent.Cast(m_Weapon.GetOwner().FindComponent(InventoryItemComponent));
|
||||
InventoryItemComponent loadedItem = InventoryItemComponent.Cast(m_Loaded.FindComponent(InventoryItemComponent));
|
||||
InventoryItemComponent incomingItem = InventoryItemComponent.Cast(m_Incoming.FindComponent(InventoryItemComponent));
|
||||
if (!gunItem || !loadedItem || !incomingItem) return;
|
||||
m_WeaponSlot = gunItem.GetParentSlot(); m_ToSlot = loadedItem.GetParentSlot(); m_FromSlot = incomingItem.GetParentSlot();
|
||||
if (!m_WeaponSlot || !m_ToSlot || !m_FromSlot) return;
|
||||
m_Mount = WeaponSlotComponent.Cast(m_WeaponSlot.GetParentContainer());
|
||||
m_FromStorage = m_FromSlot.GetStorage(); m_ToStorage = m_ToSlot.GetStorage();
|
||||
m_OldMagazine = BaseMagazineComponent.Cast(m_Loaded.FindComponent(BaseMagazineComponent));
|
||||
m_NewMagazine = BaseMagazineComponent.Cast(m_Incoming.FindComponent(BaseMagazineComponent));
|
||||
m_Carried = InventoryStorageManagerComponent.Cast(m_Worker.FindComponent(InventoryStorageManagerComponent));
|
||||
m_TruckInventory = InventoryStorageManagerComponent.Cast(m_Truck.FindComponent(InventoryStorageManagerComponent));
|
||||
}
|
||||
bool Submitted() { return m_Submitted; }
|
||||
bool InReach() { return Reach(); }
|
||||
string Reason() { return m_Reason; }
|
||||
// A failure remains sticky; success notification alone proves neither item.
|
||||
void Acknowledge(bool success)
|
||||
{
|
||||
if (!m_Submitted) return;
|
||||
if (!success) m_Acknowledgement = -1;
|
||||
else if (m_Acknowledgement == 0) m_Acknowledgement = 1;
|
||||
}
|
||||
protected ERR_MountedAmmoTransferState Fail(string reason)
|
||||
{
|
||||
if (m_State == ERR_MountedAmmoTransferState.PENDING) { m_State = ERR_MountedAmmoTransferState.FAILED; m_Reason = reason; }
|
||||
return m_State;
|
||||
}
|
||||
void Stop() { Fail("courier authority stopped; submitted swaps cannot be cancelled or refunded"); }
|
||||
protected static bool Stationary(Vehicle vehicle)
|
||||
{
|
||||
if (!vehicle || !ERR_Actors.Alive(vehicle) || !vehicle.GetPhysics()) return false;
|
||||
float speed = vehicle.GetPhysics().GetVelocity().Length();
|
||||
float angular = vehicle.GetPhysics().GetAngularVelocity().Length();
|
||||
return speed >= 0 && speed <= 0.25 && angular >= 0 && angular <= 0.1;
|
||||
}
|
||||
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_Task || !m_Task.Ready || m_Task.Stopped || !m_Receipt || m_Task.Receipt != m_Receipt || !m_Receipt.Consumed
|
||||
|| m_Task.Vehicles.Count() != 6 || m_Receipt.Records.Count() < 6 || m_Index < 0 || m_Index >= 6 || m_Index == 1) return false;
|
||||
if (!m_Donor || !m_Target || m_Task.Vehicles[1] != m_Donor || m_Task.Vehicles[m_Index] != m_Target
|
||||
|| m_Donor.Index != 1 || m_Target.Index != m_Index || m_Donor.Entity != m_Truck || m_Target.Entity != m_Root
|
||||
|| m_Donor.Group != m_DonorGroup || m_Target.Group != m_TargetGroup) return false;
|
||||
if (m_Donor.Released || m_Target.Released
|
||||
|| !m_Donor.Registered || !m_Target.Registered || !m_Donor.CrewValid(m_Mode) || !m_Target.CrewValid(m_Mode)) return false;
|
||||
if (!m_DonorRecord || !m_TargetRecord || m_Mode.Forces().FindGroup(m_DonorGroup) != m_DonorRecord
|
||||
|| m_Mode.Forces().FindGroup(m_TargetGroup) != m_TargetRecord || m_DonorRecord.Id != m_Receipt.Records[1]
|
||||
|| m_TargetRecord.Id != m_Receipt.Records[m_Index]) return false;
|
||||
ERR_HQ hq = ERR_HQ.Cast(m_Mode.Command());
|
||||
if (!hq || !hq.ValidateBoundVehicle(m_DonorGroup, m_Truck) || !hq.ValidateBoundVehicle(m_TargetGroup, m_Root)) return false;
|
||||
if (!m_Roster || !m_Roster.OwnsRoster(m_TargetGroup) || !m_Roster.ValidateBoundVehicle(m_Root)
|
||||
|| !m_Roster.CanAttribute(m_WorkerSlot, m_Worker, false) || !m_Target.Members.Contains(m_Worker)
|
||||
|| !ERR_EnemyEquipmentRules.Eligible(m_Mode, m_TargetGroup, m_Worker) || !ERR_Actors.CanFight(m_Worker)
|
||||
|| CompartmentAccessComponent.GetVehicleIn(m_Worker)) return false;
|
||||
if (!m_Reservation || !m_Reservation.CommittedFor(m_Incoming) || !Stationary(m_Truck) || !Stationary(m_Root)) return false;
|
||||
if (InventoryStorageManagerComponent.Cast(m_Worker.FindComponent(InventoryStorageManagerComponent)) != m_Carried
|
||||
|| InventoryStorageManagerComponent.Cast(m_Truck.FindComponent(InventoryStorageManagerComponent)) != m_TruckInventory) return false;
|
||||
return TargetShape();
|
||||
}
|
||||
protected bool TargetShape()
|
||||
{
|
||||
if (!m_Root || !m_Weapon || !m_Weapon.GetOwner() || !m_Mount || !m_WeaponSlot || !m_Muzzle || !m_ToSlot || !m_ToStorage) return false;
|
||||
IEntity gun = m_Weapon.GetOwner();
|
||||
InventoryItemComponent item = InventoryItemComponent.Cast(gun.FindComponent(InventoryItemComponent));
|
||||
if (!item || item.GetParentSlot() != m_WeaponSlot || m_Mount.GetSlotInfo() != m_WeaponSlot || m_Mount.GetWeaponEntity() != gun
|
||||
|| m_WeaponSlot.GetAttachedEntity() != gun || m_WeaponSlot.GetParentContainer() != m_Mount
|
||||
|| !ERR_ConvoyRules.PartOf(m_Mount.GetOwner(), m_Root) || !ERR_ConvoyRules.PartOf(m_Muzzle.GetOwner(), m_Root)
|
||||
|| m_ToSlot.GetParentContainer() != m_Muzzle || m_ToSlot.GetStorage() != m_ToStorage || m_ToStorage.GetOwner() != gun
|
||||
|| m_Muzzle.GetMuzzleType() != EMuzzleType.MT_BaseMuzzle) return false;
|
||||
array<BaseMuzzleComponent> muzzles = {}; m_Weapon.GetMuzzlesList(muzzles);
|
||||
return muzzles.Count() <= 8 && muzzles.Contains(m_Muzzle);
|
||||
}
|
||||
protected bool MagazineValid(BaseMagazineComponent magazine)
|
||||
{
|
||||
if (!magazine || !m_Muzzle || !magazine.GetMagazineWell() || !m_Muzzle.GetMagazineWell()
|
||||
|| !magazine.GetMagazineWell().IsInherited(m_Muzzle.GetMagazineWell().Type())) return false;
|
||||
int capacity = magazine.GetMaxAmmoCount(); int rounds = magazine.GetAmmoCount();
|
||||
return capacity > 0 && capacity <= 100000 && rounds >= 0 && rounds <= capacity;
|
||||
}
|
||||
protected bool CarriedStorage()
|
||||
{
|
||||
if (!m_Carried || !m_Worker || m_Carried.GetOwner() != m_Worker || !m_FromStorage
|
||||
|| !m_FromSlot || m_FromSlot.GetStorage() != m_FromStorage || !ERR_InfantryAmmoTransfer.StoredUnder(m_FromStorage.GetOwner(), m_Worker)) return false;
|
||||
array<BaseInventoryStorageComponent> storages = {}; m_Carried.GetStorages(storages, EStoragePurpose.PURPOSE_DEPOSIT);
|
||||
return storages.Count() <= 64 && storages.Contains(m_FromStorage);
|
||||
}
|
||||
protected int Owned(InventoryStorageManagerComponent inventory, IEntity item)
|
||||
{
|
||||
if (!inventory || !item) return -1;
|
||||
array<IEntity> items = {}; inventory.GetItems(items, EStoragePurpose.PURPOSE_DEPOSIT);
|
||||
if (items.Count() > 128) return -1;
|
||||
if (items.Contains(item)) return 1;
|
||||
return 0;
|
||||
}
|
||||
protected bool InSlot(IEntity entity, InventoryStorageSlot slot)
|
||||
{
|
||||
if (!entity || !slot || slot.GetAttachedEntity() != entity) return false;
|
||||
InventoryItemComponent item = InventoryItemComponent.Cast(entity.FindComponent(InventoryItemComponent));
|
||||
return item && item.GetParentSlot() == slot;
|
||||
}
|
||||
// Exact two-way proof remains observable after Stop, loss or mission end.
|
||||
protected bool OriginalProof()
|
||||
{
|
||||
return TargetShape() && CarriedStorage() && m_Muzzle.GetMagazine() == m_OldMagazine
|
||||
&& InSlot(m_Incoming, m_FromSlot) && InSlot(m_Loaded, m_ToSlot)
|
||||
&& Owned(m_Carried, m_Incoming) == 1 && Owned(m_Carried, m_Loaded) == 0;
|
||||
}
|
||||
protected bool SwappedProof()
|
||||
{
|
||||
return TargetShape() && CarriedStorage() && m_Muzzle.GetMagazine() == m_NewMagazine
|
||||
&& InSlot(m_Incoming, m_ToSlot) && InSlot(m_Loaded, m_FromSlot)
|
||||
&& Owned(m_Carried, m_Incoming) == 0 && Owned(m_Carried, m_Loaded) == 1;
|
||||
}
|
||||
bool Unresolved()
|
||||
{
|
||||
if (!m_Submitted) return false;
|
||||
// Both exchange identities must be deleted before loss alone settles this
|
||||
// two-item operation. Either surviving item may still be in flight.
|
||||
if (!m_Incoming && !m_Loaded) return false;
|
||||
if (m_Acknowledgement == 1 && SwappedProof()) return false;
|
||||
if (m_Acknowledgement < 0 && OriginalProof()) return false;
|
||||
// Even a false submission result without callback cannot authorize another
|
||||
// swap of these slots. Keep both identities and the target claim quarantined.
|
||||
return true;
|
||||
}
|
||||
protected bool AccessFilter(notnull IEntity entity)
|
||||
{
|
||||
return !ERR_ConvoyRules.PartOf(entity, m_Worker) && !ERR_ConvoyRules.PartOf(entity, m_Weapon.GetOwner());
|
||||
}
|
||||
protected bool Reach()
|
||||
{
|
||||
ChimeraCharacter character = ChimeraCharacter.Cast(m_Worker);
|
||||
if (!character || !m_ToSlot || !m_Weapon || !GetGame() || !GetGame().GetWorld()) return false;
|
||||
vector matrix[4]; m_ToSlot.GetWorldTransform(matrix);
|
||||
vector start = character.AimingPosition(); vector point = matrix[3];
|
||||
if (!ERR_ForceRules.FiniteVector(start) || !ERR_ForceRules.FiniteVector(point)) return false;
|
||||
float distance = vector.Distance(start, point);
|
||||
if (!(distance >= 0.05 && distance <= 4)) return false;
|
||||
TraceParam trace = new TraceParam(); trace.Start = start; trace.End = point;
|
||||
trace.Flags = TraceFlags.WORLD | TraceFlags.ENTS; trace.LayerMask = TRACE_LAYER_CAMERA;
|
||||
// The original vehicle hull remains an occluder. This is reach to the exact
|
||||
// authored magazine slot, not a support-station action or a vehicle-center test.
|
||||
return GetGame().GetWorld().TraceMove(trace, AccessFilter) == 1;
|
||||
}
|
||||
ERR_MountedAmmoTransferState Tick(float now)
|
||||
{
|
||||
if (m_State != ERR_MountedAmmoTransferState.PENDING) return m_State;
|
||||
if (!ERR_ForceRules.Finite(now) || now < 0) return Fail("invalid swap clock");
|
||||
if (m_Started < 0) m_Started = now;
|
||||
if (now < m_LastTick || now - m_Started >= 8000) return Fail("mounted swap deadline expired");
|
||||
m_LastTick = now;
|
||||
if (!Context() || !Reach()) return Fail("original convoy, worker, bound mount or unobstructed physical reach lost");
|
||||
if (!m_Submitted)
|
||||
{
|
||||
if (!OriginalProof() || !m_OldMagazine || !m_NewMagazine || m_OldMagazine.GetOwner() != m_Loaded || m_NewMagazine.GetOwner() != m_Incoming
|
||||
|| !MagazineValid(m_OldMagazine) || !MagazineValid(m_NewMagazine) || m_NewMagazine.IsUsed()
|
||||
|| m_NewMagazine.GetAmmoCount() <= m_OldMagazine.GetAmmoCount() || m_OldMagazine.GetAmmoCount() > m_OldMagazine.GetMaxAmmoCount() * 0.25
|
||||
|| m_FromSlot == m_ToSlot || m_FromSlot.IsLocked() || m_ToSlot.IsLocked()
|
||||
|| !m_TruckInventory || m_TruckInventory.GetOwner() != m_Truck || Owned(m_TruckInventory, m_Incoming) != 0 || Owned(m_TruckInventory, m_Loaded) != 0)
|
||||
return Fail("exact carried finite stock or original low mounted magazine unavailable");
|
||||
if (!m_Carried.CanSwapItemStorages(m_Incoming, m_Loaded)) return Fail("native slot swap preflight refused");
|
||||
if (!m_Reservation.ClaimMountedSwap(m_Incoming)) return Fail("committed pickup already claimed by another mounted swap");
|
||||
m_Callback = new ERR_MountedAmmoCallback(); m_Callback.Transfer = this;
|
||||
m_Submitted = true;
|
||||
bool accepted = m_Carried.TrySwapItemStorages(m_Incoming, m_Loaded, m_Callback);
|
||||
if (!accepted && m_Acknowledgement != 1) return Fail("native swap refused; exact committed identity will not be retried");
|
||||
}
|
||||
if (m_Acknowledgement < 0) return Fail("native swap failure; partial or unproved movement remains quarantined");
|
||||
if (m_Acknowledgement == 1 && SwappedProof())
|
||||
{
|
||||
m_State = ERR_MountedAmmoTransferState.SUCCEEDED;
|
||||
m_Reason = "callback plus exact mounted and carried slots confirm one two-way physical magazine exchange";
|
||||
}
|
||||
return m_State;
|
||||
}
|
||||
}
|
||||
+174
@@ -0,0 +1,174 @@
|
||||
"""Execute the exact original truck-to-courier pickup; no engine coverage claim."""
|
||||
import os,re
|
||||
from pathlib import Path
|
||||
from policy_harness import ROOT,run
|
||||
from test_finite_magazine_reservation import translated_reservation
|
||||
p=Path(os.environ.get('ERR_MOUNTED_PICKUP_SOURCE',ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_MountedAmmoPickup.c'))
|
||||
s=p.read_text()
|
||||
for forbidden in ('TrySpawn','SetAmmoCount','SetOrigin','SetTransform','DeleteEntity','TrySwap','TryInsert','TryRemove','PerformAction'):
|
||||
assert forbidden not in s,forbidden
|
||||
assert s.count('TryMoveItemToStorage(')==1
|
||||
assert s.index('m_Reservation.Commit()') < s.index('TryMoveItemToStorage(')
|
||||
code=s[s.index('class ERR_MountedAmmoPickup\n'):]
|
||||
code=re.sub(r'//[^\n]*','',code).replace('protected ','').replace('notnull ','').replace('ref ','').replace('class ','struct ')
|
||||
code=code.replace('void ERR_MountedAmmoPickup(','ERR_MountedAmmoPickup(')
|
||||
code=code.replace('array<','Array<').replace('null','nullptr')
|
||||
# Only engine component lookup/casts are doubled; all transaction methods execute.
|
||||
cast_types={'InventoryItemComponent':'item','BaseMagazineComponent':'magazine','InventoryStorageManagerComponent':'inventory'}
|
||||
for kind,field in cast_types.items():
|
||||
code=re.sub(kind+r'\.Cast\(([^;\n]*?)\.FindComponent\('+kind+r'\)\)',lambda m:m[1]+'.'+field,code)
|
||||
code=code.replace('WeaponSlotComponent.Cast(m_WeaponSlot.GetParentContainer())','dynamic_cast<Mount*>(m_WeaponSlot.GetParentContainer())')
|
||||
code=code.replace('ERR_HQ.Cast(m_Mode.Command())','m_Mode.Command()').replace('ChimeraCharacter.Cast(m_Worker)','m_Worker')
|
||||
for name in ('ERR_GameMode','ERR_MountedAmmoTransferState','ERR_ForceRules','ERR_Actors','ERR_ConvoyRules','ERR_EnemyEquipmentRules','ERR_InfantryAmmoTransfer','Replication','SCR_Global','CompartmentAccessComponent','EMuzzleType','EStoragePurpose','vector','TraceFlags','ERR_InfantryAmmoAccess'):
|
||||
code=code.replace(name+'.',name+'::')
|
||||
for name in ('m_Mode','mode','task','originalRoster','m_Task','m_Receipt','m_Donor','m_Target','m_Truck','m_Root','m_DonorRecord','m_TargetRecord','m_Roster','m_Weapon','m_WeaponSlot','m_Muzzle','m_Loaded','m_Incoming','m_Mount','m_FromStorage','m_ToStorage','m_OldMagazine','m_NewMagazine','m_Carried','m_TruckInventory','m_Reservation','m_Callback','m_Worker','gunItem','loadedItem','incomingItem','gun','item','hq','vehicle','entity','slot','inventory','magazine','character','trace','m_FromSlot','m_ToSlot','m_Destination','owner','storage'):
|
||||
code=re.sub(r'\b'+name+r'\.',name+'->',code)
|
||||
code=code.replace(').',' )->')
|
||||
# Physics/vector return values, unlike components, are not pointers.
|
||||
code=code.replace('GetVelocity( )->Length()', 'GetVelocity().Length()').replace('GetAngularVelocity( )->Length()', 'GetAngularVelocity().Length()')
|
||||
code=code.replace('ERR_FiniteMagazineReservation m_Reservation','ERR_FiniteMagazineReservation* m_Reservation').replace('ERR_FiniteMagazineReservation pickupReservation','ERR_FiniteMagazineReservation* pickupReservation')
|
||||
code=code.replace('ERR_MountedPickupCallback m_Callback','ERR_MountedPickupCallback* m_Callback')
|
||||
code=code.replace('TraceParam trace = new TraceParam()', 'TraceParam* trace = new TraceParam()')
|
||||
code=code.replace('TraceMove(trace, AccessFilter)', 'TraceMove(trace, [this](IEntity e){return AccessFilter(e);})')
|
||||
code=re.sub(r'\b(bool|int|IEntity|Vehicle|ERR_GameMode|ERR_ConvoyTask|ERR_ConvoyReceipt|ERR_ConvoyVehicle|SCR_AIGroup|ERR_ForceRecord|ERR_HQStatusSampler|BaseWeaponComponent|WeaponSlotComponent|InventoryStorageSlot|BaseMuzzleComponent|BaseMagazineComponent|BaseInventoryStorageComponent|InventoryStorageManagerComponent|ERR_FiniteMagazineReservation\*|ERR_MountedPickupCallback\*) (\w+);',r'\1 \2{};',code)
|
||||
code=code.replace('\n}\n','\n};\n')
|
||||
callbacks=[]
|
||||
for name in ('OnComplete','OnAllComplete','OnFailed','OnAllFailed'):
|
||||
m=re.search('protected override void '+name+r'\(\) (\{[^\n]+\})',s)
|
||||
callbacks.append('void ERR_MountedPickupCallback::'+name+'() '+m[1].replace('Transfer.','Transfer->'))
|
||||
pre=r'''
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
using string=std::string;
|
||||
template<class T>struct Array:std::vector<T>{using std::vector<T>::vector;int Count(){return this->size();}bool Contains(T x){return std::find(this->begin(),this->end(),x)!=this->end();}void Insert(T x){this->push_back(x);}void RemoveItem(T x){this->erase(std::remove(this->begin(),this->end(),x),this->end());}};
|
||||
struct vector{float x=0,y=0,z=0;float Length(){return std::sqrt(x*x+y*y+z*z);}static float Distance(vector a,vector b){return vector{a.x-b.x,a.y-b.y,a.z-b.z}.Length();}};
|
||||
struct Entity;struct Group;struct Mode;struct Task;struct Receipt;struct ConvoyVehicle;struct Record;struct Roster;struct Weapon;struct Mount;struct Slot;struct Muzzle;struct Magazine;struct Storage;struct Inventory;struct HQ;
|
||||
using IEntity=Entity*;using Vehicle=Entity*;using ChimeraCharacter=Entity*;using SCR_AIGroup=Group*;using ERR_GameMode=Mode*;using ERR_ConvoyTask=Task*;using ERR_ConvoyReceipt=Receipt*;using ERR_ConvoyVehicle=ConvoyVehicle*;using ERR_ForceRecord=Record*;using ERR_HQStatusSampler=Roster*;using BaseWeaponComponent=Weapon*;using WeaponSlotComponent=Mount*;using InventoryStorageSlot=Slot*;using BaseMuzzleComponent=Muzzle*;using BaseMagazineComponent=Magazine*;using BaseInventoryStorageComponent=Storage*;using InventoryStorageManagerComponent=Inventory*;using ERR_HQ=HQ*;
|
||||
struct Component{virtual ~Component()=default;};
|
||||
struct Item{Slot*slot=nullptr;Slot*GetParentSlot(){return slot;}};using InventoryItemComponent=Item*;
|
||||
struct Physics{vector velocity,angular;vector GetVelocity(){return velocity;}vector GetAngularVelocity(){return angular;}};
|
||||
struct Entity{Entity*parent=nullptr;bool alive=true,eligible=true,awake=true;IEntity seated=nullptr;Item*item=nullptr;Magazine*magazine=nullptr;Inventory*inventory=nullptr;Physics*physics=nullptr;vector position;Physics*GetPhysics(){return physics;}vector GetOrigin(){return position;}vector AimingPosition(){return position;}};
|
||||
struct Slot{Storage*storage=nullptr;Component*container=nullptr;IEntity attached=nullptr;bool locked=false;vector point{1,0,0};Storage*GetStorage(){return storage;}Component*GetParentContainer(){return container;}IEntity GetAttachedEntity(){return attached;}bool IsLocked(){return locked;}void GetWorldTransform(vector mat[4]){mat[3]=point;}};
|
||||
struct Storage{IEntity owner=nullptr;IEntity GetOwner(){return owner;}};
|
||||
struct Well{int type=1;int Type(){return type;}bool IsInherited(int t){return t==type;}};
|
||||
struct Magazine{IEntity owner=nullptr;Well*well=nullptr;int rounds=100,capacity=100;bool used=false;IEntity GetOwner(){return owner;}Well*GetMagazineWell(){return well;}int GetAmmoCount(){return rounds;}int GetMaxAmmoCount(){return capacity;}bool IsUsed(){return used;}};
|
||||
struct Muzzle:Component{IEntity owner=nullptr;Magazine*mag=nullptr;Well*well=nullptr;int kind=0;IEntity GetOwner(){return owner;}Magazine*GetMagazine(){return mag;}Well*GetMagazineWell(){return well;}int GetMuzzleType(){return kind;}};
|
||||
struct Weapon{IEntity owner=nullptr;Array<Muzzle*>muzzles;IEntity GetOwner(){return owner;}void GetMuzzlesList(Array<Muzzle*>&a){a=muzzles;}};
|
||||
struct Mount:Component{IEntity owner=nullptr;IEntity gun=nullptr;Slot*slot=nullptr;IEntity GetOwner(){return owner;}IEntity GetWeaponEntity(){return gun;}Slot*GetSlotInfo(){return slot;}};
|
||||
struct Group{};
|
||||
struct Record{string Id;};
|
||||
struct Ledger{SCR_AIGroup donor=nullptr,target=nullptr;Record*d=nullptr,*t=nullptr;Record*FindGroup(SCR_AIGroup g){return g==donor?d:g==target?t:nullptr;}};
|
||||
struct HQ{bool valid=true;bool ValidateBoundVehicle(SCR_AIGroup g,IEntity e){return valid&&g&&e;}};
|
||||
struct Mode{inline static Mode*current=nullptr;bool active=true;Ledger*ledger=nullptr;HQ*hq=nullptr;static Mode*Get(){return current;}bool Active(){return active;}Ledger*Forces(){return ledger;}HQ*Command(){return hq;}};
|
||||
struct Receipt{bool Consumed=true;Array<string>Records;};
|
||||
struct ConvoyVehicle{int Index=0;IEntity Entity=nullptr;SCR_AIGroup Group=nullptr;bool Released=false,Registered=true,crew=true,Arrived=false;IEntity Driver=nullptr;Array<IEntity>Members;bool CrewValid(ERR_GameMode){return crew;}};
|
||||
struct Task{bool Ready=true,Stopped=false;ERR_ConvoyReceipt Receipt=nullptr;Array<ERR_ConvoyVehicle>Vehicles;};
|
||||
struct Roster{SCR_AIGroup group=nullptr;IEntity root=nullptr,worker=nullptr;bool valid=true;IEntity InitialMember(int slot){return slot==1?worker:nullptr;}bool OwnsRoster(SCR_AIGroup g){return valid&&g==group;}bool ValidateBoundVehicle(IEntity e){return valid&&e==root;}bool CanAttribute(int slot,IEntity e,bool observe){assert(!observe);return valid&&slot==1&&e==worker;}};
|
||||
struct Replication{inline static bool server=true;static bool IsServer(){return server;}};struct SCR_Global{inline static bool editor=false;static bool IsEditMode(){return editor;}};
|
||||
struct ERR_Actors{static bool Alive(IEntity e){return e&&e->alive;}static bool CanFight(IEntity e){return e&&e->alive&&e->awake;}};
|
||||
struct ERR_ConvoyRules{static bool PartOf(IEntity e,IEntity root){for(int i=0;e&&i<32;i++,e=e->parent)if(e==root)return true;return false;}};
|
||||
struct ERR_InfantryAmmoTransfer{static bool StoredUnder(IEntity e,IEntity root){return ERR_ConvoyRules::PartOf(e,root);}};
|
||||
struct ERR_EnemyEquipmentRules{static bool Eligible(ERR_GameMode m,SCR_AIGroup g,IEntity e){return m&&g&&e&&e->eligible;}};
|
||||
struct CompartmentAccessComponent{static IEntity GetVehicleIn(IEntity e){return e?e->seated:nullptr;}};
|
||||
struct ERR_ForceRules{static bool Finite(float x){return std::isfinite(x);}static bool FiniteVector(vector v){return Finite(v.x)&&Finite(v.y)&&Finite(v.z);}};
|
||||
struct EMuzzleType{static const int MT_BaseMuzzle=0;};struct EStoragePurpose{static const int PURPOSE_DEPOSIT=1;};
|
||||
struct TraceParam{vector Start,End;int Flags=0,LayerMask=0;};struct TraceFlags{static const int WORLD=1,ENTS=2;};const int TRACE_LAYER_CAMERA=4;
|
||||
struct World{float fraction=1;Array<IEntity>blockers;int calls=0;float TraceMove(TraceParam*t,std::function<bool(IEntity)>filter){assert(t->Flags==3&&t->LayerMask==4);calls++;for(auto*e:blockers)if(filter(e))return 0;return fraction;}};
|
||||
struct Game{World*world=nullptr;World*GetWorld(){return world;}};Game*game=nullptr;Game*GetGame(){return game;}
|
||||
struct ScriptedInventoryOperationCallback{virtual void OnComplete(){}virtual void OnAllComplete(){}virtual void OnFailed(){}virtual void OnAllFailed(){}virtual ~ScriptedInventoryOperationCallback()=default;};
|
||||
struct ERR_MountedAmmoPickup;
|
||||
struct ERR_MountedPickupCallback:ScriptedInventoryOperationCallback{ERR_MountedAmmoPickup*Transfer=nullptr;void OnComplete()override;void OnAllComplete()override;void OnFailed()override;void OnAllFailed()override;};
|
||||
struct Inventory{IEntity owner=nullptr;Array<IEntity>items;Array<Storage*>storages;Storage*selected=nullptr;bool allow=true,accept=true;int queries=0,calls=0,ack=0;ScriptedInventoryOperationCallback*last=nullptr;std::function<void()>effect;
|
||||
IEntity GetOwner(){return owner;}void GetItems(Array<IEntity>&a,int p){assert(p==1);a=items;}void GetStorages(Array<Storage*>&a,int p){assert(p==1);a=storages;}
|
||||
Storage*FindStorageForItem(IEntity,int p){assert(p==1);return selected;}
|
||||
bool CanMoveItemToStorage(IEntity a,Storage*b,int slot){assert(a&&b&&slot==-1);queries++;return allow;}
|
||||
bool TryMoveItemToStorage(IEntity a,Storage*b,int slot,ScriptedInventoryOperationCallback*cb){assert(a&&b&&slot==-1);calls++;last=cb;if(effect)effect();if(ack==1)cb->OnComplete();if(ack==-1)cb->OnFailed();return accept;}
|
||||
bool CanSwapItemStorages(IEntity a,IEntity b){assert(a&&b&&a!=b);queries++;return allow;}
|
||||
bool TrySwapItemStorages(IEntity a,IEntity b,ScriptedInventoryOperationCallback*cb){assert(a&&b&&a!=b);calls++;last=cb;if(effect)effect();if(ack==1)cb->OnComplete();if(ack==-1)cb->OnFailed();return accept;}};
|
||||
enum ERR_MountedAmmoTransferState{FAILED=-1,PENDING,SUCCEEDED};
|
||||
struct ERR_InfantryAmmoAccess{inline static bool clear=true;static bool Clear(Vehicle v,IEntity e){return v&&e&&clear;}};
|
||||
'''
|
||||
code=code.replace('ERR_GameMode::Get()', 'Mode::Get()')
|
||||
main=r'''
|
||||
struct Fixture{
|
||||
Mode mode;Ledger ledger;HQ hq;Group dg,tg;Record dr{"donor"},tr{"target"};Receipt receipt;Task task;ConvoyVehicle donor,target;Roster roster;
|
||||
Entity truck,root,worker,driver,targetDriver,gun,loaded,incoming,foreign;Physics dp,tp;Item gunItem,oldItem,newItem;Slot weaponSlot,fromSlot,toSlot,carrySlot;Storage truckStorage,carryStorage,gunStorage;Inventory carried,truckInventory;
|
||||
Well well;Weapon weapon;Mount mount;Muzzle muzzle;Magazine oldMag,newMag;Array<IEntity>stock,spent;ERR_FiniteMagazineReservation reservation;
|
||||
Fixture():reservation(&stock,&spent,&incoming){Mode::current=&mode;mode.ledger=&ledger;mode.hq=&hq;ledger.donor=&dg;ledger.target=&tg;ledger.d=&dr;ledger.t=&tr;
|
||||
receipt.Records={"target","donor","2","3","4","5"};task.Receipt=&receipt;task.Vehicles.resize(6,nullptr);task.Vehicles[0]=⌖task.Vehicles[1]=&donor;
|
||||
donor.Index=1;donor.Entity=&truck;donor.Group=&dg;donor.Driver=&driver;target.Entity=&root;target.Group=&tg;target.Driver=&targetDriver;target.Members={&worker,&targetDriver};truck.physics=&dp;root.physics=&tp;
|
||||
roster.group=&tg;roster.root=&root;roster.worker=&worker;gun.parent=&root;loaded.parent=&gun;gun.item=&gunItem;loaded.item=&oldItem;incoming.item=&newItem;
|
||||
gunItem.slot=&weaponSlot;oldItem.slot=&toSlot;newItem.slot=&fromSlot;weaponSlot.attached=&gun;weaponSlot.container=&mount;mount.slot=&weaponSlot;mount.gun=&gun;mount.owner=&root;
|
||||
weapon.owner=&gun;weapon.muzzles={&muzzle};muzzle.owner=&gun;muzzle.mag=&oldMag;muzzle.well=&well;oldMag.owner=&loaded;oldMag.rounds=25;oldMag.used=true;newMag.owner=&incoming;oldMag.well=&well;newMag.well=&well;
|
||||
loaded.magazine=&oldMag;incoming.magazine=&newMag;toSlot.attached=&loaded;toSlot.container=&muzzle;toSlot.storage=&gunStorage;gunStorage.owner=&gun;
|
||||
fromSlot.attached=&incoming;fromSlot.storage=&truckStorage;truckStorage.owner=&truck;carrySlot.storage=&carryStorage;carryStorage.owner=&worker;
|
||||
carried.owner=&worker;carried.storages={&carryStorage};carried.selected=&carryStorage;worker.inventory=&carried;
|
||||
truckInventory.owner=&truck;truckInventory.items={&incoming};truckInventory.storages={&truckStorage};truck.inventory=&truckInventory;stock={&incoming};assert(reservation.Reserve());ERR_InfantryAmmoAccess::clear=true;}
|
||||
ERR_MountedAmmoPickup Job(){return ERR_MountedAmmoPickup(&mode,&task,0,&roster,1,&weapon,&muzzle,&loaded,&incoming,&reservation);}
|
||||
void Move(){fromSlot.attached=nullptr;carrySlot.attached=&incoming;newItem.slot=&carrySlot;truckInventory.items.clear();carried.items={&incoming};}
|
||||
};
|
||||
int main(){
|
||||
// No personal firearm exists in the fixture: compatibility is the pinned mounted muzzle.
|
||||
{Fixture f;auto t=f.Job();assert(t.Tick(0)==PENDING&&t.Submitted()&&t.Unresolved());assert(f.truckInventory.calls==1&&f.reservation.CommittedFor(&f.incoming));
|
||||
f.truckInventory.last->OnComplete();assert(t.Tick(1)==PENDING&&t.Unresolved());f.Move();assert(t.Tick(2)==SUCCEEDED&&!t.Unresolved());
|
||||
assert(t.Tick(9000)==SUCCEEDED&&f.truckInventory.calls==1&&f.spent.Count()==1&&f.oldMag.rounds==25&&f.newMag.rounds==100);}
|
||||
{Fixture f;auto t=f.Job();t.Tick(0);f.Move();assert(t.Tick(1)==PENDING);f.truckInventory.last->OnAllComplete();assert(t.Tick(2)==SUCCEEDED);}
|
||||
// Authored cargo access can be reachable beyond four metres from the vehicle origin.
|
||||
{Fixture f;f.worker.position={-1,0,5};auto t=f.Job();assert(t.Tick(0)==PENDING&&t.Submitted());}
|
||||
// A valid original convoy remains serviceable at a stationary route endpoint.
|
||||
{Fixture f;f.donor.Arrived=true;f.target.Arrived=true;auto t=f.Job();assert(t.Tick(0)==PENDING&&t.Submitted());}
|
||||
// The shared reservation is irrevocable before entering a synchronously-calling engine.
|
||||
{Fixture f;f.truckInventory.accept=false;f.truckInventory.ack=1;f.truckInventory.effect=[&]{assert(f.reservation.CommittedFor(&f.incoming));assert(!f.reservation.ReleaseUnsubmitted());f.Move();};auto t=f.Job();assert(t.Tick(0)==SUCCEEDED&&!t.Unresolved());}
|
||||
{Fixture f;auto a=f.Job();auto b=f.Job();assert(a.Tick(0)==PENDING&&b.Tick(0)==FAILED&&!b.Submitted()&&f.truckInventory.calls==1);}
|
||||
{Fixture f;f.truckInventory.accept=false;auto t=f.Job();assert(t.Tick(0)==FAILED&&t.Submitted()&&t.Unresolved()&&!f.reservation.ReleaseUnsubmitted());f.truckInventory.last->OnFailed();assert(!t.Unresolved());assert(t.Tick(1)==FAILED&&f.truckInventory.calls==1);}
|
||||
// Failure beats success; partial movement remains quarantined even after failure callback.
|
||||
{Fixture f;auto t=f.Job();t.Tick(0);f.truckInventory.last->OnComplete();f.truckInventory.last->OnFailed();assert(t.Tick(1)==FAILED&&!t.Unresolved());f.truckInventory.last->OnAllComplete();assert(t.Tick(2)==FAILED);}
|
||||
{Fixture f;auto t=f.Job();t.Tick(0);f.newItem.slot=&f.carrySlot;f.truckInventory.last->OnFailed();assert(t.Tick(1)==FAILED&&t.Unresolved());}
|
||||
{Fixture f;auto t=f.Job();t.Stop();assert(t.Tick(0)==FAILED&&!t.Submitted()&&f.truckInventory.calls==0&&f.reservation.ReleaseUnsubmitted());}
|
||||
{Fixture f;auto t=f.Job();t.Tick(0);t.Stop();f.mode.active=false;f.Move();f.truckInventory.last->OnComplete();assert(!t.Unresolved()&&t.Tick(1)==FAILED&&f.truckInventory.calls==1);}
|
||||
{Fixture f;auto t=f.Job();t.Tick(0);assert(t.Tick(8000)==FAILED&&t.Unresolved()&&f.truckInventory.calls==1);}
|
||||
{Fixture f;auto t=f.Job();t.Tick(10);assert(t.Tick(9)==FAILED&&t.Unresolved()&&f.truckInventory.calls==1);}
|
||||
// Native deletion clears the weak exact identity: no item can arrive later.
|
||||
// Retain the spent slot, do not count delivery, and never retry on late callbacks.
|
||||
{Fixture f;auto t=f.Job();assert(t.Tick(0)==PENDING&&t.Unresolved());
|
||||
t.m_Incoming=nullptr;f.reservation.m_Item=nullptr;f.stock[0]=nullptr;f.spent[0]=nullptr;
|
||||
f.fromSlot.attached=nullptr;f.truckInventory.items.clear();
|
||||
assert(t.Tick(1)==FAILED&&!t.Unresolved()&&f.spent.Count()==1&&!f.reservation.ReleaseUnsubmitted());
|
||||
f.truckInventory.last->OnComplete();f.truckInventory.last->OnFailed();
|
||||
assert(t.Tick(2)==FAILED&&!t.Unresolved()&&f.truckInventory.calls==1&&f.spent.Count()==1);}
|
||||
// Damage, worker loss, source capture and a moved-but-existing item remain uncertain.
|
||||
for(int k=0;k<4;k++){Fixture f;auto t=f.Job();assert(t.Tick(0)==PENDING);
|
||||
if(k==0){f.incoming.alive=false;}if(k==1){t.m_Worker=nullptr;}
|
||||
if(k==2){f.donor.Released=true;}if(k==3){f.truckInventory.items.clear();f.newItem.slot=nullptr;}
|
||||
t.Stop();assert(t.Unresolved()&&t.Tick(1)==FAILED&&f.truckInventory.calls==1);}
|
||||
// Original identity, carried capacity and source access losses fail before any submission.
|
||||
for(int k=0;k<43;k++){Fixture f;auto t=f.Job();
|
||||
switch(k){case 0:f.task.Stopped=true;break;case 1:f.task.Ready=false;break;case 2:f.donor.Released=true;break;case 3:f.target.Released=true;break;
|
||||
case 4:f.receipt.Consumed=false;break;case 5:f.roster.valid=false;break;case 6:f.worker.seated=&f.root;break;case 7:f.worker.awake=false;break;
|
||||
case 8:f.worker.eligible=false;break;case 9:f.hq.valid=false;break;case 10:f.dp.velocity.x=.3;break;case 11:f.tp.angular.x=.2;break;
|
||||
case 12:f.target.crew=false;break;case 13:f.mount.gun=&f.foreign;break;case 14:f.muzzle.mag=nullptr;break;case 15:f.fromSlot.locked=true;break;
|
||||
case 16:f.oldItem.slot=nullptr;break;case 17:f.newMag.used=true;break;case 18:f.oldMag.rounds=26;break;case 19:f.newMag.rounds=25;break;
|
||||
case 20:f.newMag.capacity=0;break;case 21:f.oldMag.rounds=-1;break;case 22:f.newMag.rounds=101;break;case 23:f.truckInventory.allow=false;break;
|
||||
case 24:f.truckInventory.items.clear();break;case 25:f.truckInventory.items.resize(129,&f.foreign);break;case 26:f.carried.storages.resize(65,&f.gunStorage);break;
|
||||
case 27:f.gunStorage.owner=&f.foreign;break;case 28:f.fromSlot.storage=&f.gunStorage;break;case 29:f.task.Vehicles[0]=&f.donor;break;
|
||||
case 30:f.worker.inventory=&f.truckInventory;break;case 31:f.weapon.muzzles.resize(9,&f.muzzle);break;case 32:f.ledger.t=&f.dr;break;case 33:f.target.Members.clear();break;
|
||||
case 34:f.carried.selected=nullptr;break;case 35:ERR_InfantryAmmoAccess::clear=false;break;case 36:f.donor.Registered=false;break;
|
||||
case 37:f.donor.Driver=&f.foreign;break;case 38:f.target.Driver=&f.foreign;break;case 39:f.target.Registered=false;break;
|
||||
case 40:f.carried.items={&f.incoming};break;case 41:f.carried.selected=&f.gunStorage;break;case 42:f.toSlot.locked=true;break;}
|
||||
assert(t.Tick(0)==FAILED&&!t.Submitted()&&f.truckInventory.calls==0&&f.spent.Count()==1&&f.reservation.ReleaseUnsubmitted());}
|
||||
// Loss while pending is terminal; a late exact receipt settles quarantine without resuming.
|
||||
for(int k=0;k<5;k++){Fixture f;auto t=f.Job();assert(t.Tick(0)==PENDING);switch(k){case 0:f.task.Stopped=true;break;case 1:f.roster.valid=false;break;case 2:f.mode.active=false;break;case 3:f.target.Driver=&f.foreign;break;case 4:f.muzzle.mag=nullptr;break;}
|
||||
assert(t.Tick(1)==FAILED&&t.Unresolved());f.Move();f.truckInventory.last->OnComplete();assert(!t.Unresolved()&&t.Tick(2)==FAILED&&f.truckInventory.calls==1);}
|
||||
// Invalid construction cannot reach inventory mutation.
|
||||
for(int k=0;k<9;k++){Fixture f;switch(k){case 0:f.donor.Entity=nullptr;break;case 1:f.target.Entity=nullptr;break;case 2:f.task.Vehicles[1]=nullptr;break;case 3:f.task.Vehicles[0]=nullptr;break;
|
||||
case 4:f.roster.worker=nullptr;break;case 5:f.gun.item=nullptr;break;case 6:f.incoming.item=nullptr;break;case 7:f.loaded.item=nullptr;break;case 8:f.target.Driver=&f.worker;break;}
|
||||
auto t=f.Job();assert(t.Tick(0)==FAILED&&!t.Submitted()&&f.truckInventory.calls==0);}
|
||||
}
|
||||
'''
|
||||
run(pre+translated_reservation()+code+'\n'+'\n'.join(callbacks)+main)
|
||||
print('PASS: actual mounted pickup, exact finite ownership, pre-submission commit, physical custody, bounded callback and quarantine lifecycle; no native coverage claim')
|
||||
+184
@@ -0,0 +1,184 @@
|
||||
"""Execute the proposed exact carried-to-mounted swap; no engine coverage claim."""
|
||||
import os,re
|
||||
from pathlib import Path
|
||||
from policy_harness import ROOT,run
|
||||
from test_finite_magazine_reservation import translated_reservation
|
||||
p=Path(os.environ.get('ERR_MOUNTED_TRANSFER_SOURCE',ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_MountedAmmoTransfer.c'))
|
||||
s=p.read_text()
|
||||
for forbidden in ('TrySpawn','SetAmmoCount','SetOrigin','SetTransform','DeleteEntity','TryMove','TryInsert','TryRemove','PerformAction'):
|
||||
assert forbidden not in s,forbidden
|
||||
assert s.count('TrySwapItemStorages(')==1
|
||||
code=s[s.index('class ERR_MountedAmmoTransfer\n'):]
|
||||
code=re.sub(r'//[^\n]*','',code).replace('protected ','').replace('notnull ','').replace('ref ','').replace('class ','struct ')
|
||||
code=code.replace('void ERR_MountedAmmoTransfer(','ERR_MountedAmmoTransfer(')
|
||||
code=code.replace('array<','Array<').replace('null','nullptr')
|
||||
# Only engine component lookup/casts are doubled; all transaction methods execute.
|
||||
cast_types={'InventoryItemComponent':'item','BaseMagazineComponent':'magazine','InventoryStorageManagerComponent':'inventory'}
|
||||
for kind,field in cast_types.items():
|
||||
code=re.sub(kind+r'\.Cast\(([^;\n]*?)\.FindComponent\('+kind+r'\)\)',lambda m:m[1]+'.'+field,code)
|
||||
code=code.replace('WeaponSlotComponent.Cast(m_WeaponSlot.GetParentContainer())','dynamic_cast<Mount*>(m_WeaponSlot.GetParentContainer())')
|
||||
code=code.replace('ERR_HQ.Cast(m_Mode.Command())','m_Mode.Command()').replace('ChimeraCharacter.Cast(m_Worker)','m_Worker')
|
||||
for name in ('ERR_GameMode','ERR_MountedAmmoTransferState','ERR_ForceRules','ERR_Actors','ERR_ConvoyRules','ERR_EnemyEquipmentRules','ERR_InfantryAmmoTransfer','Replication','SCR_Global','CompartmentAccessComponent','EMuzzleType','EStoragePurpose','vector','TraceFlags'):
|
||||
code=code.replace(name+'.',name+'::')
|
||||
for name in ('m_Mode','mode','task','originalRoster','m_Task','m_Receipt','m_Donor','m_Target','m_Truck','m_Root','m_DonorRecord','m_TargetRecord','m_Roster','m_Weapon','m_WeaponSlot','m_Muzzle','m_Loaded','m_Incoming','m_Mount','m_FromStorage','m_ToStorage','m_OldMagazine','m_NewMagazine','m_Carried','m_TruckInventory','m_Reservation','m_Callback','m_Worker','gunItem','loadedItem','incomingItem','gun','item','hq','vehicle','entity','slot','inventory','magazine','character','trace','m_FromSlot','m_ToSlot'):
|
||||
code=re.sub(r'\b'+name+r'\.',name+'->',code)
|
||||
code=code.replace(').',' )->')
|
||||
# Physics/vector return values, unlike components, are not pointers.
|
||||
code=code.replace('GetVelocity( )->Length()', 'GetVelocity().Length()').replace('GetAngularVelocity( )->Length()', 'GetAngularVelocity().Length()')
|
||||
code=code.replace('ERR_FiniteMagazineReservation m_Reservation','ERR_FiniteMagazineReservation* m_Reservation').replace('ERR_FiniteMagazineReservation committedPickup','ERR_FiniteMagazineReservation* committedPickup')
|
||||
code=code.replace('ERR_MountedAmmoCallback m_Callback','ERR_MountedAmmoCallback* m_Callback')
|
||||
code=code.replace('TraceParam trace = new TraceParam()', 'TraceParam* trace = new TraceParam()')
|
||||
code=code.replace('TraceMove(trace, AccessFilter)', 'TraceMove(trace, [this](IEntity e){return AccessFilter(e);})')
|
||||
code=re.sub(r'\b(bool|int|IEntity|Vehicle|ERR_GameMode|ERR_ConvoyTask|ERR_ConvoyReceipt|ERR_ConvoyVehicle|SCR_AIGroup|ERR_ForceRecord|ERR_HQStatusSampler|BaseWeaponComponent|WeaponSlotComponent|InventoryStorageSlot|BaseMuzzleComponent|BaseMagazineComponent|BaseInventoryStorageComponent|InventoryStorageManagerComponent|ERR_FiniteMagazineReservation\*|ERR_MountedAmmoCallback\*) (\w+);',r'\1 \2{};',code)
|
||||
code=code.replace('\n}\n','\n};\n')
|
||||
callbacks=[]
|
||||
for name in ('OnComplete','OnAllComplete','OnFailed','OnAllFailed'):
|
||||
m=re.search('protected override void '+name+r'\(\) (\{[^\n]+\})',s)
|
||||
callbacks.append('void ERR_MountedAmmoCallback::'+name+'() '+m[1].replace('Transfer.','Transfer->'))
|
||||
pre=r'''
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
using string=std::string;
|
||||
template<class T>struct Array:std::vector<T>{using std::vector<T>::vector;int Count(){return this->size();}bool Contains(T x){return std::find(this->begin(),this->end(),x)!=this->end();}void Insert(T x){this->push_back(x);}void RemoveItem(T x){this->erase(std::remove(this->begin(),this->end(),x),this->end());}};
|
||||
struct vector{float x=0,y=0,z=0;float Length(){return std::sqrt(x*x+y*y+z*z);}static float Distance(vector a,vector b){return vector{a.x-b.x,a.y-b.y,a.z-b.z}.Length();}};
|
||||
struct Entity;struct Group;struct Mode;struct Task;struct Receipt;struct ConvoyVehicle;struct Record;struct Roster;struct Weapon;struct Mount;struct Slot;struct Muzzle;struct Magazine;struct Storage;struct Inventory;struct HQ;
|
||||
using IEntity=Entity*;using Vehicle=Entity*;using ChimeraCharacter=Entity*;using SCR_AIGroup=Group*;using ERR_GameMode=Mode*;using ERR_ConvoyTask=Task*;using ERR_ConvoyReceipt=Receipt*;using ERR_ConvoyVehicle=ConvoyVehicle*;using ERR_ForceRecord=Record*;using ERR_HQStatusSampler=Roster*;using BaseWeaponComponent=Weapon*;using WeaponSlotComponent=Mount*;using InventoryStorageSlot=Slot*;using BaseMuzzleComponent=Muzzle*;using BaseMagazineComponent=Magazine*;using BaseInventoryStorageComponent=Storage*;using InventoryStorageManagerComponent=Inventory*;using ERR_HQ=HQ*;
|
||||
struct Component{virtual ~Component()=default;};
|
||||
struct Item{Slot*slot=nullptr;Slot*GetParentSlot(){return slot;}};using InventoryItemComponent=Item*;
|
||||
struct Physics{vector velocity,angular;vector GetVelocity(){return velocity;}vector GetAngularVelocity(){return angular;}};
|
||||
struct Entity{Entity*parent=nullptr;bool alive=true,eligible=true,awake=true;IEntity seated=nullptr;Item*item=nullptr;Magazine*magazine=nullptr;Inventory*inventory=nullptr;Physics*physics=nullptr;vector position;Physics*GetPhysics(){return physics;}vector AimingPosition(){return position;}};
|
||||
struct Slot{Storage*storage=nullptr;Component*container=nullptr;IEntity attached=nullptr;bool locked=false;vector point{1,0,0};Storage*GetStorage(){return storage;}Component*GetParentContainer(){return container;}IEntity GetAttachedEntity(){return attached;}bool IsLocked(){return locked;}void GetWorldTransform(vector mat[4]){mat[3]=point;}};
|
||||
struct Storage{IEntity owner=nullptr;IEntity GetOwner(){return owner;}};
|
||||
struct Well{int type=1;int Type(){return type;}bool IsInherited(int t){return t==type;}};
|
||||
struct Magazine{IEntity owner=nullptr;Well*well=nullptr;int rounds=100,capacity=100;bool used=false;IEntity GetOwner(){return owner;}Well*GetMagazineWell(){return well;}int GetAmmoCount(){return rounds;}int GetMaxAmmoCount(){return capacity;}bool IsUsed(){return used;}};
|
||||
struct Muzzle:Component{IEntity owner=nullptr;Magazine*mag=nullptr;Well*well=nullptr;int kind=0;IEntity GetOwner(){return owner;}Magazine*GetMagazine(){return mag;}Well*GetMagazineWell(){return well;}int GetMuzzleType(){return kind;}};
|
||||
struct Weapon{IEntity owner=nullptr;Array<Muzzle*>muzzles;IEntity GetOwner(){return owner;}void GetMuzzlesList(Array<Muzzle*>&a){a=muzzles;}};
|
||||
struct Mount:Component{IEntity owner=nullptr;IEntity gun=nullptr;Slot*slot=nullptr;IEntity GetOwner(){return owner;}IEntity GetWeaponEntity(){return gun;}Slot*GetSlotInfo(){return slot;}};
|
||||
struct Group{};
|
||||
struct Record{string Id;};
|
||||
struct Ledger{SCR_AIGroup donor=nullptr,target=nullptr;Record*d=nullptr,*t=nullptr;Record*FindGroup(SCR_AIGroup g){return g==donor?d:g==target?t:nullptr;}};
|
||||
struct HQ{bool valid=true;bool ValidateBoundVehicle(SCR_AIGroup g,IEntity e){return valid&&g&&e;}};
|
||||
struct Mode{inline static Mode*current=nullptr;bool active=true;Ledger*ledger=nullptr;HQ*hq=nullptr;static Mode*Get(){return current;}bool Active(){return active;}Ledger*Forces(){return ledger;}HQ*Command(){return hq;}};
|
||||
struct Receipt{bool Consumed=true;Array<string>Records;};
|
||||
struct ConvoyVehicle{int Index=0;IEntity Entity=nullptr;SCR_AIGroup Group=nullptr;bool Released=false,Registered=true,crew=true;Array<IEntity>Members;bool CrewValid(ERR_GameMode){return crew;}};
|
||||
struct Task{bool Ready=true,Stopped=false;ERR_ConvoyReceipt Receipt=nullptr;Array<ERR_ConvoyVehicle>Vehicles;};
|
||||
struct Roster{SCR_AIGroup group=nullptr;IEntity root=nullptr,worker=nullptr;bool valid=true;IEntity InitialMember(int slot){return slot==1?worker:nullptr;}bool OwnsRoster(SCR_AIGroup g){return valid&&g==group;}bool ValidateBoundVehicle(IEntity e){return valid&&e==root;}bool CanAttribute(int slot,IEntity e,bool observe){assert(!observe);return valid&&slot==1&&e==worker;}};
|
||||
struct Replication{inline static bool server=true;static bool IsServer(){return server;}};struct SCR_Global{inline static bool editor=false;static bool IsEditMode(){return editor;}};
|
||||
struct ERR_Actors{static bool Alive(IEntity e){return e&&e->alive;}static bool CanFight(IEntity e){return e&&e->alive&&e->awake;}};
|
||||
struct ERR_ConvoyRules{static bool PartOf(IEntity e,IEntity root){for(int i=0;e&&i<32;i++,e=e->parent)if(e==root)return true;return false;}};
|
||||
struct ERR_InfantryAmmoTransfer{static bool StoredUnder(IEntity e,IEntity root){return ERR_ConvoyRules::PartOf(e,root);}};
|
||||
struct ERR_EnemyEquipmentRules{static bool Eligible(ERR_GameMode m,SCR_AIGroup g,IEntity e){return m&&g&&e&&e->eligible;}};
|
||||
struct CompartmentAccessComponent{static IEntity GetVehicleIn(IEntity e){return e?e->seated:nullptr;}};
|
||||
struct ERR_ForceRules{static bool Finite(float x){return std::isfinite(x);}static bool FiniteVector(vector v){return Finite(v.x)&&Finite(v.y)&&Finite(v.z);}};
|
||||
struct EMuzzleType{static const int MT_BaseMuzzle=0;};struct EStoragePurpose{static const int PURPOSE_DEPOSIT=1;};
|
||||
struct TraceParam{vector Start,End;int Flags=0,LayerMask=0;};struct TraceFlags{static const int WORLD=1,ENTS=2;};const int TRACE_LAYER_CAMERA=4;
|
||||
struct World{float fraction=1;Array<IEntity>blockers;int calls=0;float TraceMove(TraceParam*t,std::function<bool(IEntity)>filter){assert(t->Flags==3&&t->LayerMask==4);calls++;for(auto*e:blockers)if(filter(e))return 0;return fraction;}};
|
||||
struct Game{World*world=nullptr;World*GetWorld(){return world;}};Game*game=nullptr;Game*GetGame(){return game;}
|
||||
struct ScriptedInventoryOperationCallback{virtual void OnComplete(){}virtual void OnAllComplete(){}virtual void OnFailed(){}virtual void OnAllFailed(){}virtual ~ScriptedInventoryOperationCallback()=default;};
|
||||
struct ERR_MountedAmmoTransfer;
|
||||
struct ERR_MountedAmmoCallback:ScriptedInventoryOperationCallback{ERR_MountedAmmoTransfer*Transfer=nullptr;void OnComplete()override;void OnAllComplete()override;void OnFailed()override;void OnAllFailed()override;};
|
||||
struct Inventory{IEntity owner=nullptr;Array<IEntity>items;Array<Storage*>storages;bool allow=true,accept=true;int queries=0,calls=0,ack=0;ScriptedInventoryOperationCallback*last=nullptr;std::function<void()>effect;
|
||||
IEntity GetOwner(){return owner;}void GetItems(Array<IEntity>&a,int p){assert(p==1);a=items;}void GetStorages(Array<Storage*>&a,int p){assert(p==1);a=storages;}
|
||||
bool CanSwapItemStorages(IEntity a,IEntity b){assert(a&&b&&a!=b);queries++;return allow;}
|
||||
bool TrySwapItemStorages(IEntity a,IEntity b,ScriptedInventoryOperationCallback*cb){assert(a&&b&&a!=b);calls++;last=cb;if(effect)effect();if(ack==1)cb->OnComplete();if(ack==-1)cb->OnFailed();return accept;}};
|
||||
enum ERR_MountedAmmoTransferState{FAILED=-1,PENDING,SUCCEEDED};
|
||||
'''
|
||||
# ERR_GameMode aliases are pointer types; the static call uses the doubled class.
|
||||
code=code.replace('ERR_GameMode::Get()', 'Mode::Get()')
|
||||
main=r'''
|
||||
struct Fixture{
|
||||
Mode mode;Ledger ledger;HQ hq;Group dg,tg;Record dr{"donor"},tr{"target"};Receipt receipt;Task task;ConvoyVehicle donor,target;Roster roster;
|
||||
Entity truck,root,worker,gun,loaded,incoming,foreign;Physics dp,tp;Item gunItem,oldItem,newItem;Slot weaponSlot,fromSlot,toSlot;Storage carryStorage,gunStorage;Inventory carried,truckInventory;
|
||||
Well well;Weapon weapon;Mount mount;Muzzle muzzle;Magazine oldMag,newMag;Array<IEntity>stock,spent;ERR_FiniteMagazineReservation reservation;World world;Game game_;
|
||||
Fixture():reservation(&stock,&spent,&incoming){Mode::current=&mode;mode.ledger=&ledger;mode.hq=&hq;ledger.donor=&dg;ledger.target=&tg;ledger.d=&dr;ledger.t=&tr;
|
||||
receipt.Records={"target","donor","2","3","4","5"};task.Receipt=&receipt;task.Vehicles.resize(6,nullptr);task.Vehicles[0]=⌖task.Vehicles[1]=&donor;
|
||||
donor.Index=1;donor.Entity=&truck;donor.Group=&dg;target.Entity=&root;target.Group=&tg;target.Members={&worker};truck.physics=&dp;root.physics=&tp;
|
||||
roster.group=&tg;roster.root=&root;roster.worker=&worker;gun.parent=&root;loaded.parent=&gun;gun.item=&gunItem;loaded.item=&oldItem;incoming.item=&newItem;
|
||||
gunItem.slot=&weaponSlot;oldItem.slot=&toSlot;newItem.slot=&fromSlot;weaponSlot.attached=&gun;weaponSlot.container=&mount;mount.slot=&weaponSlot;mount.gun=&gun;mount.owner=&root;
|
||||
weapon.owner=&gun;weapon.muzzles={&muzzle};muzzle.owner=&gun;muzzle.mag=&oldMag;muzzle.well=&well;oldMag.owner=&loaded;oldMag.rounds=25;oldMag.used=true;newMag.owner=&incoming;oldMag.well=&well;newMag.well=&well;
|
||||
loaded.magazine=&oldMag;incoming.magazine=&newMag;toSlot.attached=&loaded;toSlot.container=&muzzle;toSlot.storage=&gunStorage;gunStorage.owner=&gun;
|
||||
fromSlot.attached=&incoming;fromSlot.storage=&carryStorage;carryStorage.owner=&worker;carried.owner=&worker;carried.items={&incoming};carried.storages={&carryStorage};worker.inventory=&carried;
|
||||
truckInventory.owner=&truck;truck.inventory=&truckInventory;stock={&incoming};assert(reservation.Reserve()&&reservation.Commit());game_.world=&world;game=&game_;}
|
||||
ERR_MountedAmmoTransfer Job(){return ERR_MountedAmmoTransfer(&mode,&task,0,&roster,1,&weapon,&muzzle,&loaded,&incoming,&reservation);}
|
||||
void Swap(){fromSlot.attached=&loaded;toSlot.attached=&incoming;oldItem.slot=&fromSlot;newItem.slot=&toSlot;muzzle.mag=&newMag;carried.items={&loaded};}
|
||||
};
|
||||
int main(){
|
||||
{Fixture f;auto t=f.Job();assert(t.Tick(0)==PENDING&&t.Submitted()&&t.Unresolved());assert(f.carried.calls==1);
|
||||
f.carried.last->OnComplete();assert(t.Tick(1)==PENDING&&t.Unresolved());f.Swap();assert(t.Tick(2)==SUCCEEDED&&!t.Unresolved());
|
||||
assert(t.Tick(9000)==SUCCEEDED&&f.carried.calls==1&&f.spent.Count()==1&&f.oldMag.rounds==25&&f.newMag.rounds==100);}
|
||||
{Fixture f;auto t=f.Job();assert(t.Tick(0)==PENDING);f.Swap();assert(t.Tick(1)==PENDING);f.carried.last->OnAllComplete();assert(t.Tick(2)==SUCCEEDED);}
|
||||
// Inline success plus false native return is still accepted when exact proof agrees.
|
||||
{Fixture f;f.carried.accept=false;f.carried.ack=1;f.carried.effect=[&]{f.Swap();};auto t=f.Job();assert(t.Tick(0)==SUCCEEDED&&!t.Unresolved()&&f.carried.calls==1);}
|
||||
// Two transactions sharing a committed pickup can never submit two native swaps.
|
||||
{Fixture f;auto a=f.Job();auto b=f.Job();assert(a.Tick(0)==PENDING&&b.Tick(0)==FAILED&&!b.Submitted()&&f.carried.calls==1);}
|
||||
// No callback, even on false native return, is conservatively quarantined.
|
||||
{Fixture f;f.carried.accept=false;auto t=f.Job();assert(t.Tick(0)==FAILED&&t.Submitted()&&t.Unresolved());f.carried.last->OnFailed();assert(!t.Unresolved());assert(t.Tick(1)==FAILED&&f.carried.calls==1);}
|
||||
{Fixture f;auto t=f.Job();assert(t.Tick(0)==PENDING);f.carried.last->OnComplete();f.carried.last->OnFailed();assert(t.Tick(1)==FAILED&&!t.Unresolved());f.carried.last->OnAllComplete();assert(t.Tick(2)==FAILED);}
|
||||
// A partial native failure cannot free target slots or either magazine identity.
|
||||
{Fixture f;auto t=f.Job();t.Tick(0);f.newItem.slot=&f.toSlot;f.toSlot.attached=&f.incoming;f.carried.last->OnFailed();assert(t.Tick(1)==FAILED&&t.Unresolved());}
|
||||
// Stop never launches/replays; late exact completion resolves quarantine only.
|
||||
{Fixture f;auto t=f.Job();t.Stop();assert(t.Tick(0)==FAILED&&!t.Submitted()&&f.carried.calls==0&&f.spent.Count()==1);}
|
||||
{Fixture f;auto t=f.Job();t.Tick(0);t.Stop();f.mode.active=false;f.Swap();f.carried.last->OnComplete();assert(!t.Unresolved()&&t.Tick(1)==FAILED&&f.carried.calls==1);}
|
||||
{Fixture f;auto t=f.Job();t.Tick(0);assert(t.Tick(8000)==FAILED&&t.Unresolved()&&f.carried.calls==1);}
|
||||
{Fixture f;auto t=f.Job();t.Tick(10);assert(t.Tick(9)==FAILED&&f.carried.calls==1);}
|
||||
// A swap retains quarantine while either exact exchange identity survives.
|
||||
for(int side=0;side<2;side++){Fixture f;auto t=f.Job();assert(t.Tick(0)==PENDING&&t.Unresolved());
|
||||
if(side==0)t.m_Incoming=nullptr;else t.m_Loaded=nullptr;
|
||||
t.Stop();assert(t.Unresolved()&&t.Tick(1)==FAILED&&f.carried.calls==1);
|
||||
f.carried.last->OnComplete();assert(t.Unresolved());
|
||||
t.m_Incoming=nullptr;t.m_Loaded=nullptr;f.reservation.m_Item=nullptr;f.stock[0]=nullptr;f.spent[0]=nullptr;
|
||||
assert(!t.Unresolved()&&!f.reservation.ReleaseUnsubmitted()&&f.spent.Count()==1);
|
||||
f.carried.last->OnFailed();assert(t.Tick(2)==FAILED&&!t.Unresolved()&&f.carried.calls==1);}
|
||||
{Fixture f;auto t=f.Job();assert(t.Tick(0)==PENDING);f.incoming.alive=false;f.loaded.alive=false;
|
||||
t.Stop();assert(t.Unresolved()&&f.carried.calls==1);}
|
||||
// Physical target changes, outgoing capacity/content guards, source inventory bounds.
|
||||
for(int k=0;k<34;k++){Fixture f;auto t=f.Job();
|
||||
switch(k){case 0:f.task.Stopped=true;break;case 1:f.task.Ready=false;break;case 2:f.donor.Released=true;break;case 3:f.target.Released=true;break;
|
||||
case 4:f.receipt.Consumed=false;break;case 5:f.roster.valid=false;break;case 6:f.worker.seated=&f.root;break;case 7:f.worker.awake=false;break;
|
||||
case 8:f.worker.eligible=false;break;case 9:f.hq.valid=false;break;case 10:f.dp.velocity.x=0.3;break;case 11:f.tp.angular.x=0.2;break;
|
||||
case 12:f.target.crew=false;break;case 13:f.mount.gun=&f.foreign;break;case 14:f.muzzle.mag=nullptr;break;case 15:f.fromSlot.locked=true;break;
|
||||
case 16:f.toSlot.locked=true;break;case 17:f.newMag.used=true;break;case 18:f.oldMag.rounds=26;break;case 19:f.newMag.rounds=25;break;
|
||||
case 20:f.newMag.capacity=0;break;case 21:f.oldMag.rounds=-1;break;case 22:f.newMag.rounds=101;break;case 23:f.carried.allow=false;break;
|
||||
case 24:f.truckInventory.items={&f.incoming};break;case 25:f.carried.items.resize(129,&f.foreign);break;case 26:f.carried.storages.resize(65,&f.gunStorage);break;
|
||||
case 27:f.gunStorage.owner=&f.foreign;break;case 28:f.fromSlot.storage=&f.gunStorage;break;case 29:f.task.Vehicles[0]=&f.donor;break;
|
||||
case 30:f.worker.inventory=&f.truckInventory;break;case 31:f.weapon.muzzles.resize(9,&f.muzzle);break;case 32:f.ledger.t=&f.dr;break;case 33:f.target.Members.clear();break;}
|
||||
assert(t.Tick(0)==FAILED&&!t.Submitted()&&f.carried.calls==0&&f.spent.Count()==1);}
|
||||
// Partial constructor state fails closed before native inventory access.
|
||||
for(int k=0;k<9;k++){Fixture f;
|
||||
switch(k){case 0:f.donor.Entity=nullptr;break;case 1:f.target.Entity=nullptr;break;case 2:f.donor.Group=nullptr;break;
|
||||
case 3:f.target.Group=nullptr;break;case 4:f.task.Vehicles[1]=nullptr;break;case 5:f.task.Vehicles[0]=nullptr;break;
|
||||
case 6:f.roster.worker=nullptr;break;case 7:f.incoming.item=nullptr;break;case 8:f.gun.item=nullptr;break;}
|
||||
auto t=f.Job();assert(t.Tick(0)==FAILED&&!t.Submitted()&&f.carried.calls==0);}
|
||||
// A released handle cannot authorize even when another handle reclaims its exact item.
|
||||
{Fixture f;Array<IEntity> stock{&f.incoming},spent;ERR_FiniteMagazineReservation released(&stock,&spent,&f.incoming);
|
||||
assert(released.Reserve()&&released.ReleaseUnsubmitted());ERR_FiniteMagazineReservation other(&stock,&spent,&f.incoming);
|
||||
assert(other.Reserve()&&other.Commit());auto t=ERR_MountedAmmoTransfer(&f.mode,&f.task,0,&f.roster,1,&f.weapon,&f.muzzle,&f.loaded,&f.incoming,&released);
|
||||
assert(t.Tick(0)==FAILED&&!t.Submitted()&&f.carried.calls==0);}
|
||||
// Truck is stationary but need not be beside the mount during the carried leg.
|
||||
{Fixture f;f.truck.position={35,0,0};auto t=f.Job();assert(t.Tick(0)==PENDING&&f.carried.calls==1);}
|
||||
// Context loss after accepted operation is terminal while exact late proof can settle quarantine.
|
||||
for(int k=0;k<5;k++){Fixture f;auto t=f.Job();t.Tick(0);switch(k){case 0:f.target.Released=true;break;case 1:f.donor.Released=true;break;
|
||||
case 2:f.roster.valid=false;break;case 3:f.worker.awake=false;break;case 4:f.task.Stopped=true;break;}
|
||||
assert(t.Tick(1)==FAILED&&t.Unresolved());f.Swap();f.carried.last->OnComplete();assert(!t.Unresolved()&&t.Tick(2)==FAILED&&f.carried.calls==1);}
|
||||
// Existing reservation has already spent the pickup. Released/foreign/duplicate handles fail.
|
||||
{Fixture f;assert(f.reservation.CommittedFor(&f.incoming)&&!f.reservation.CommittedFor(&f.loaded));assert(!f.reservation.ReleaseUnsubmitted());}
|
||||
{Fixture f;Array<IEntity> stock{&f.loaded},spent;ERR_FiniteMagazineReservation r(&stock,&spent,&f.loaded);assert(r.Reserve()&&r.ReleaseUnsubmitted());
|
||||
ERR_FiniteMagazineReservation next(&stock,&spent,&f.loaded);assert(next.Reserve()&&next.Commit());assert(!r.CommittedFor(&f.loaded)&&!r.ClaimMountedSwap(&f.loaded));}
|
||||
// Authored slot reach executes real distance/trace logic, retaining vehicle hull occlusion.
|
||||
for(int k=0;k<7;k++){Fixture f;auto t=f.Job();switch(k){case 0:f.toSlot.point.x=4.01;break;case 1:f.toSlot.point.x=0.01;break;
|
||||
case 2:f.toSlot.point.x=std::numeric_limits<float>::quiet_NaN();break;case 3:f.worker.position.x=std::numeric_limits<float>::infinity();break;
|
||||
case 4:f.world.blockers={&f.root};break;case 5:f.world.blockers={&f.foreign};break;case 6:f.world.fraction=0.5;break;}
|
||||
assert(t.Tick(0)==FAILED&&!t.Submitted());}
|
||||
{Fixture f;f.world.blockers={&f.worker,&f.gun,&f.loaded};f.toSlot.point.x=4;auto t=f.Job();assert(t.Tick(0)==PENDING&&f.world.calls==1);}
|
||||
// Invalid clock/server/editor guards never consume the second-leg claim.
|
||||
for(int k=0;k<4;k++){Fixture f;auto t=f.Job();if(k==0)Replication::server=false;if(k==1)SCR_Global::editor=true;
|
||||
assert(t.Tick(k==2?-1:k==3?std::numeric_limits<float>::quiet_NaN():0)==FAILED&&!t.Submitted());Replication::server=true;SCR_Global::editor=false;}
|
||||
}
|
||||
'''
|
||||
run(pre+translated_reservation()+code+'\n'+'\n'.join(callbacks)+main)
|
||||
print('PASS: actual carried-mounted swap, shared committed claim, physical slot reach, exact two-way proof, uncertainty and terminal cleanup')
|
||||
Reference in New Issue
Block a user