Require physical ammo access and prove finite native infantry transfer

This commit is contained in:
2026-09-20 09:22:52 +02:00
parent 5bd66d76ca
commit d8ec0de765
76 changed files with 14320 additions and 8 deletions
@@ -0,0 +1,62 @@
// Physical reach to authored donor storage contexts; no inventory mutation here.
class ERR_InfantryAmmoAccess
{
protected IEntity m_Donor;
protected IEntity m_Recipient;
// Exact hierarchy exclusion prevents the truck's own cargo shell or recipient
// equipment from occluding an interior-authored anchor. Other entities collide.
protected bool Filter(notnull IEntity entity)
{
return !ERR_ConvoyRules.PartOf(entity, m_Donor) && !ERR_ConvoyRules.PartOf(entity, m_Recipient);
}
static bool Points(Vehicle donor, out array<vector> points)
{
points = {};
if (!donor) return false;
ActionsManagerComponent manager = ActionsManagerComponent.Cast(donor.FindComponent(ActionsManagerComponent));
if (!manager || !manager.IsEnabled()) return false;
array<UserActionContext> contexts = {}; manager.GetContextList(contexts);
if (contexts.IsEmpty() || contexts.Count() > 64) return false;
foreach (UserActionContext context : contexts)
{
if (!context) return false;
array<BaseUserAction> actions = {}; context.GetActionsList(actions);
if (actions.Count() > 16) return false;
bool storage;
foreach (BaseUserAction action : actions)
if (action && action.GetOwner() == donor && SCR_OpenVehicleStorageAction.Cast(action)) storage = true;
if (!storage) continue;
vector matrix[4];
if (!context.GetTransformationWorld(matrix) || !ERR_ForceRules.FiniteVector(matrix[3])) return false;
points.Insert(matrix[3]);
}
return !points.IsEmpty();
}
bool Check(Vehicle donor, IEntity recipient)
{
ChimeraCharacter character = ChimeraCharacter.Cast(recipient);
if (!donor || !character || !GetGame() || !GetGame().GetWorld()) return false;
array<vector> points;
if (!Points(donor, points)) return false;
vector start = character.AimingPosition();
if (!ERR_ForceRules.FiniteVector(start)) return false;
m_Donor = donor; m_Recipient = recipient;
foreach (vector point : points)
{
float distance = vector.Distance(start, point);
if (!(distance >= 0.05 && distance <= 4)) continue;
TraceParam trace = new TraceParam();
trace.Start = start; trace.End = point;
trace.Flags = TraceFlags.WORLD | TraceFlags.ENTS;
trace.LayerMask = TRACE_LAYER_CAMERA;
float fraction = GetGame().GetWorld().TraceMove(trace, Filter);
if (fraction == 1) return true;
}
return false;
}
static bool Clear(Vehicle donor, IEntity recipient)
{
ERR_InfantryAmmoAccess access = new ERR_InfantryAmmoAccess();
return access.Check(donor, recipient);
}
}
@@ -169,6 +169,7 @@ class ERR_InfantryAmmoTransfer
if (!item || !item.GetParentSlot() || !ManagedStorage(m_Source, item.GetParentSlot().GetStorage(), m_Donor)) return Fail("exact source magazine storage does not belong to original donor");
m_Storage = m_Destination.FindStorageForItem(m_Magazine, EStoragePurpose.PURPOSE_DEPOSIT);
if (!ManagedStorage(m_Destination, m_Storage, m_Recipient) || !m_Source.CanMoveItemToStorage(m_Magazine, m_Storage, -1)) return Fail("recipient has no native compatible available storage");
if (!ERR_InfantryAmmoAccess.Clear(m_Donor, m_Recipient)) return Fail("no unobstructed physical reach to an authored donor storage access point");
m_Callback = new ERR_InfantryAmmoCallback(); m_Callback.Transfer = this;
m_Submitted = true;
m_Accepted = m_Source.TryMoveItemToStorage(m_Magazine, m_Storage, -1, m_Callback);