Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
235 lines
13 KiB
C
235 lines
13 KiB
C
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 a carrier, or replaces an original designated engineer's empty stock.
|
|
// 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 bool m_EmptyReplacement;
|
|
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, bool emptyReplacement = false)
|
|
{
|
|
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; m_EmptyReplacement = emptyReplacement;
|
|
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 int CarriedCount(IEntity actor, SCR_AIGroup group, out IEntity found, IEntity exclude = null)
|
|
{
|
|
found = null;
|
|
if (!Replication.IsServer() || !group || group.IsSlave() || group.GetPlayerCount(true) > 0) return -1;
|
|
if (!ERR_Actors.CanFight(actor) || ERR_Actors.Player(actor) || ERR_Actors.Group(actor) != group
|
|
|| CompartmentAccessComponent.GetVehicleIn(actor)) return -1;
|
|
InventoryStorageManagerComponent inventory = InventoryStorageManagerComponent.Cast(actor.FindComponent(InventoryStorageManagerComponent));
|
|
if (!inventory || inventory.GetOwner() != actor) return -1;
|
|
array<IEntity> items = {}; inventory.GetItems(items);
|
|
if (items.Count() > 128) return -1;
|
|
array<IEntity> seen = {}; int count;
|
|
foreach (IEntity item : items)
|
|
{
|
|
if (!item || !item.GetPrefabData()) return -1;
|
|
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 -1;
|
|
if (!item.FindComponent(SCR_AntiPersonnelMineCollisionHandlerComponent)
|
|
|| !item.FindComponent(SCR_PressureTriggerComponent) || !item.FindComponent(DamageManagerComponent)) return -1;
|
|
if (!ReadyMine(item)) continue;
|
|
count++;
|
|
found = item;
|
|
}
|
|
return count;
|
|
}
|
|
static IEntity Carrier(IEntity actor, SCR_AIGroup group, IEntity exclude = null)
|
|
{
|
|
IEntity found;
|
|
if (CarriedCount(actor, group, found, exclude) != 1) return null;
|
|
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 (!ReadyMine(m_Mine)) return false;
|
|
IEntity exclude;
|
|
if (m_Submitted) exclude = m_Mine;
|
|
if (m_EmptyReplacement)
|
|
{
|
|
ERR_HQ hq = ERR_HQ.Cast(m_Mode.Command());
|
|
if (!hq || !hq.MineSupplyReady(m_RecipientGroup, m_Recipient, true)) return false;
|
|
IEntity found;
|
|
if (CarriedCount(m_Recipient, m_RecipientGroup, found, exclude) != 0) return false;
|
|
}
|
|
else if (!m_CarriedMine || 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");
|
|
}
|
|
// Native callbacks may synchronously stop this leaf or revoke its authority.
|
|
if (m_State != ERR_InfantryAmmoTransferState.PENDING) return m_State;
|
|
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;
|
|
}
|
|
}
|