Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
313 lines
17 KiB
Plaintext
313 lines
17 KiB
Plaintext
#ifdef WORKBENCH
|
|
class ERR_NativeMaintenanceMove : ScriptedInventoryOperationCallback
|
|
{
|
|
int Result;
|
|
protected void Finish(int result) { if (!Result) Result = result; }
|
|
protected override void OnComplete() { Finish(1); }
|
|
protected override void OnAllComplete() { Finish(1); }
|
|
protected override void OnFailed() { Finish(-1); }
|
|
protected override void OnAllFailed() { Finish(-1); }
|
|
}
|
|
|
|
// Opt-in fixture only. One existing Southern can, original UAZ, native action.
|
|
// Direct action execution proves transfer/custody, not held input or animation.
|
|
// The outer fixture owns its explicitly recorded player approach; no movement here.
|
|
class ERR_NativeMaintenanceProbe
|
|
{
|
|
protected ERR_GameMode m_Mode;
|
|
protected IEntity m_Player;
|
|
protected ERR_SiteRuntime m_Site;
|
|
protected IEntity m_Container;
|
|
protected Vehicle m_Vehicle;
|
|
protected IEntity m_Can;
|
|
protected IEntity m_Spare;
|
|
protected InventoryStorageManagerComponent m_Source;
|
|
protected InventoryStorageManagerComponent m_Inventory;
|
|
protected BaseInventoryStorageComponent m_Destination;
|
|
protected SCR_FuelManagerComponent m_Fuel;
|
|
protected SCR_FuelManagerComponent m_CanFuel;
|
|
protected SCR_FuelManagerComponent m_SpareFuel;
|
|
protected SCR_FuelNode m_Tank;
|
|
protected ref array<BaseFuelNode> m_Nodes = {};
|
|
protected ref array<float> m_InitialNodes = {};
|
|
protected SCR_RefuelAtSupportStationAction m_Action;
|
|
protected SCR_FuelSupportStationComponent m_Station;
|
|
protected SCR_GadgetManagerComponent m_Gadgets;
|
|
protected ref ERR_NativeMaintenanceMove m_Move;
|
|
protected int m_State;
|
|
protected int m_Calls;
|
|
protected float m_Deadline = -1;
|
|
protected float m_Next;
|
|
protected float m_Interval;
|
|
protected float m_InitialCan;
|
|
protected float m_InitialSpare;
|
|
protected float m_BaseTank;
|
|
protected float m_LastCan;
|
|
protected float m_LastTank;
|
|
protected bool m_ActionStarted;
|
|
protected bool m_Interrupted;
|
|
protected bool m_Stopped;
|
|
void ERR_NativeMaintenanceProbe(ERR_GameMode mode, IEntity player) { m_Mode = mode; m_Player = player; }
|
|
static bool Enabled()
|
|
{
|
|
string enabled, smoke, outcome;
|
|
return System.GetCLIParam("errMaintenanceRefuel", enabled) && enabled == "1"
|
|
&& System.GetCLIParam("errSmoke", smoke) && smoke == "1"
|
|
&& System.GetCLIParam("errSmokeOutcome", outcome) && outcome == "normal-escape-route";
|
|
}
|
|
static bool WithinDeadline(float started, float now)
|
|
{
|
|
return started >= 0 && now >= started && now - started <= 120000;
|
|
}
|
|
static bool FuelConserved(float initialCan, float can, float baseTank, float tank, float capacity)
|
|
{
|
|
return initialCan > 0 && initialCan <= 5.001 && can >= 0 && can <= initialCan
|
|
&& baseTank >= 0 && tank >= baseTank && tank <= capacity
|
|
&& Math.AbsFloat((initialCan - can) - (tank - baseTank)) <= 0.01;
|
|
}
|
|
bool NeedsApproach() { return !m_Stopped && m_State == 3; }
|
|
vector ApproachPosition()
|
|
{
|
|
if (!m_Action || !m_Vehicle) return vector.Zero;
|
|
vector point = m_Action.GetWorldPositionAction();
|
|
vector direction = point - m_Vehicle.GetOrigin(); direction[1] = 0;
|
|
if (direction.LengthSq() < 0.01) return vector.Zero;
|
|
direction.Normalize();
|
|
return ERR_Actors.Ground(point + direction);
|
|
}
|
|
protected bool Check(bool condition, string detail)
|
|
{
|
|
if (condition) { Print("ERR SMOKE PASS: maintenance " + detail); return true; }
|
|
Print("ERR SMOKE FAIL: maintenance " + detail, LogLevel.ERROR);
|
|
m_State = -1; Stop(); return false;
|
|
}
|
|
protected bool Contains(InventoryStorageManagerComponent inventory, IEntity exact)
|
|
{
|
|
if (!inventory || !exact) return false;
|
|
array<IEntity> items = {}; ERR_Inventory.Items(inventory, items);
|
|
return items.Count() <= 128 && items.Contains(exact);
|
|
}
|
|
protected bool Context()
|
|
{
|
|
if (!Replication.IsServer() || !m_Mode || m_Mode != ERR_GameMode.Get() || !m_Mode.Active()) return false;
|
|
if (!m_Player || !m_Mode.Players() || !m_Mode.Players().CurrentActor(m_Player) || !ERR_Actors.CanFight(m_Player)) return false;
|
|
CompartmentAccessComponent access = CompartmentAccessComponent.Cast(m_Player.FindComponent(CompartmentAccessComponent));
|
|
if (!access || access.IsInCompartment()) return false;
|
|
if (!m_Mode.Sites() || !m_Site || m_Mode.Sites().Find("everon-supply-1") != m_Site) return false;
|
|
if (!m_Container || m_Site.Container != m_Container || !m_Mode.Sites().Owns(m_Container)) return false;
|
|
if (m_Site.Transport != m_Vehicle || m_Site.TransportEntity != m_Vehicle || !ERR_Actors.Alive(m_Vehicle)) return false;
|
|
if (!m_Mode.Sites().Owns(m_Vehicle) || !m_Source || m_Source.GetOwner() != m_Container) return false;
|
|
if (!m_Inventory || m_Inventory.GetOwner() != m_Player || !m_Can || !m_Spare) return false;
|
|
if (!m_Mode.Sites().Owns(m_Can) || !m_Mode.Sites().Owns(m_Spare) || !m_CanFuel || !m_SpareFuel) return false;
|
|
if (!m_Fuel || m_Fuel.GetOwner() != m_Vehicle || !m_Tank || !m_Action || m_Action.GetOwner() != m_Vehicle) return false;
|
|
if (m_CanFuel.GetOwner() != m_Can || m_SpareFuel.GetOwner() != m_Spare || !m_Station || m_Station.GetOwner() != m_Can) return false;
|
|
if (!Contains(m_Source, m_Spare) || !(Math.AbsFloat(m_SpareFuel.GetTotalFuel() - m_InitialSpare) <= 0.001)) return false;
|
|
CarControllerComponent controller = CarControllerComponent.Cast(m_Vehicle.FindComponent(CarControllerComponent));
|
|
Physics physics = m_Vehicle.GetPhysics();
|
|
if (!controller || controller.IsEngineOn() || !controller.GetPersistentHandBrake()) return false;
|
|
if (!physics || physics.GetVelocity().LengthSq() > 0.01) return false;
|
|
BaseCompartmentManagerComponent manager = BaseCompartmentManagerComponent.Cast(m_Vehicle.FindComponent(BaseCompartmentManagerComponent));
|
|
array<BaseCompartmentSlot> seats = {}; if (manager) manager.GetCompartments(seats);
|
|
if (!manager || seats.IsEmpty() || seats.Count() > 64) return false;
|
|
foreach (BaseCompartmentSlot seat : seats) if (!seat || seat.GetOccupant()) return false;
|
|
return true;
|
|
}
|
|
protected bool Carried()
|
|
{
|
|
if (!Contains(m_Inventory, m_Can) || Contains(m_Source, m_Can)) return false;
|
|
return ERR_InfantryAmmoTransfer.StoredUnder(m_Can, m_Player);
|
|
}
|
|
protected bool Held()
|
|
{
|
|
return Carried() && m_Gadgets && m_Gadgets.GetHeldGadget() == m_Can
|
|
&& SCR_SupportStationGadgetComponent.GetHeldSupportStation(ESupportStationType.FUEL, m_Player) == m_Station;
|
|
}
|
|
protected bool AtAction()
|
|
{
|
|
return m_Action && m_Action.GetOwner() == m_Vehicle && vector.Distance(m_Player.GetOrigin(), m_Action.GetWorldPositionAction()) <= 3;
|
|
}
|
|
protected bool ActionAllowed()
|
|
{
|
|
bool shown = m_Action.CanBeShownScript(m_Player);
|
|
bool allowed = shown && m_Action.CanBePerformedScript(m_Player);
|
|
if (!allowed)
|
|
{
|
|
ESupportStationReasonInvalid reason;
|
|
m_Action.CanPerform(reason);
|
|
PrintFormat("ERR MAINTENANCE REFUSAL: shown=%1 allowed=%2 nativeReason=%3", shown, allowed, reason);
|
|
}
|
|
return allowed;
|
|
}
|
|
protected bool Conservation()
|
|
{
|
|
array<BaseFuelNode> nodes = {}; m_Fuel.GetFuelNodesList(nodes);
|
|
if (nodes.Count() != m_Nodes.Count()) return false;
|
|
for (int i = 0; i < nodes.Count(); i++)
|
|
{
|
|
if (!nodes[i] || nodes[i] != m_Nodes[i]) return false;
|
|
if (nodes[i] != m_Tank && !(Math.AbsFloat(nodes[i].GetFuel() - m_InitialNodes[i]) <= 0.001)) return false;
|
|
}
|
|
float can = m_CanFuel.GetTotalFuel(), tank = m_Tank.GetFuel();
|
|
return FuelConserved(m_InitialCan, can, m_BaseTank, tank, m_Tank.GetMaxFuel());
|
|
}
|
|
protected void CancelAction()
|
|
{
|
|
if (!m_ActionStarted) return;
|
|
m_ActionStarted = false;
|
|
// Do not cancel another holder's gadget or a replacement round's action.
|
|
if (m_Mode && m_Mode == ERR_GameMode.Get() && m_Action && m_Action.GetOwner() == m_Vehicle && m_Player && m_Mode.Players() && m_Mode.Players().CurrentActor(m_Player)
|
|
&& m_Gadgets && m_Gadgets.GetHeldGadget() == m_Can) m_Action.OnActionCanceled(m_Vehicle, m_Player);
|
|
}
|
|
void Stop()
|
|
{
|
|
if (m_Stopped) return;
|
|
m_Stopped = true; CancelAction();
|
|
if (m_Mode && m_Mode == ERR_GameMode.Get() && m_Mode.Players() && m_Mode.Players().CurrentActor(m_Player)
|
|
&& m_Gadgets && m_Gadgets.GetHeldGadget() == m_Can && m_Can) m_Gadgets.SetGadgetMode(m_Can, EGadgetMode.IN_STORAGE);
|
|
}
|
|
protected bool Select()
|
|
{
|
|
if (!m_Mode || !m_Mode.Sites() || !m_Player) return false;
|
|
m_Site = m_Mode.Sites().Find("everon-supply-1");
|
|
if (!m_Site || !m_Site.Container || !m_Site.Transport || m_Site.TransportOmitted) return false;
|
|
m_Container = m_Site.Container; m_Vehicle = m_Site.Transport;
|
|
m_Source = InventoryStorageManagerComponent.Cast(m_Container.FindComponent(InventoryStorageManagerComponent));
|
|
m_Inventory = InventoryStorageManagerComponent.Cast(m_Player.FindComponent(InventoryStorageManagerComponent));
|
|
m_Fuel = SCR_FuelManagerComponent.Cast(m_Vehicle.FindComponent(SCR_FuelManagerComponent));
|
|
m_Gadgets = SCR_GadgetManagerComponent.GetGadgetManager(m_Player);
|
|
if (!m_Source || !m_Inventory || !m_Fuel || !m_Gadgets) return false;
|
|
array<IEntity> items = {}; ERR_Inventory.Items(m_Source, items);
|
|
if (items.Count() > 128) return false;
|
|
int cans;
|
|
foreach (IEntity item : items)
|
|
{
|
|
if (!item || !item.GetPrefabData() || item.GetPrefabData().GetPrefabName() != "{12D5AD21E383B768}Prefabs/Items/Fuel/Jerrycan_01/Jerrycan_01_item.et") continue;
|
|
cans++; if (!m_Can) m_Can = item; else m_Spare = item;
|
|
}
|
|
if (cans != 2 || m_Can == m_Spare) return false;
|
|
m_CanFuel = SCR_FuelManagerComponent.Cast(m_Can.FindComponent(SCR_FuelManagerComponent));
|
|
m_SpareFuel = SCR_FuelManagerComponent.Cast(m_Spare.FindComponent(SCR_FuelManagerComponent));
|
|
m_Station = SCR_FuelSupportStationComponent.Cast(m_Can.FindComponent(SCR_FuelSupportStationComponent));
|
|
if (!m_CanFuel || !m_SpareFuel || !m_Station) return false;
|
|
m_InitialCan = m_CanFuel.GetTotalFuel(); m_InitialSpare = m_SpareFuel.GetTotalFuel();
|
|
if (!(Math.AbsFloat(m_InitialCan - 5) <= 0.001) || !(Math.AbsFloat(m_InitialSpare - 5) <= 0.001)) return false;
|
|
m_Fuel.GetFuelNodesList(m_Nodes);
|
|
if (m_Nodes.IsEmpty() || m_Nodes.Count() > 8) return false;
|
|
foreach (BaseFuelNode node : m_Nodes) { if (!SCR_FuelNode.Cast(node)) return false; m_InitialNodes.Insert(node.GetFuel()); }
|
|
BaseActionsManagerComponent manager = BaseActionsManagerComponent.Cast(m_Vehicle.FindComponent(BaseActionsManagerComponent));
|
|
array<BaseUserAction> actions = {}; if (manager) manager.GetActionsList(actions);
|
|
if (actions.Count() > 64) return false;
|
|
foreach (BaseUserAction candidate : actions)
|
|
{
|
|
SCR_RefuelAtSupportStationAction action = SCR_RefuelAtSupportStationAction.Cast(candidate);
|
|
if (!action || action.GetOwner() != m_Vehicle) continue;
|
|
foreach (BaseFuelNode baseNode : m_Nodes)
|
|
{
|
|
SCR_FuelNode tank = SCR_FuelNode.Cast(baseNode);
|
|
if (!tank || !tank.CanReceiveFuel() || tank.GetFuel() < 5 || !action.CheckIfFuelNodeIsValid(tank)) continue;
|
|
// Refuse an action addressing several non-full tanks: prove one exact target.
|
|
int addressed;
|
|
foreach (BaseFuelNode other : m_Nodes)
|
|
{
|
|
SCR_FuelNode otherTank = SCR_FuelNode.Cast(other);
|
|
if (!otherTank) return false;
|
|
if (otherTank == tank || (action.CheckIfFuelNodeIsValid(otherTank) && otherTank.GetFuel() < otherTank.GetMaxFuel())) addressed++;
|
|
}
|
|
if (addressed != 1) continue;
|
|
m_Action = action; m_Tank = tank; break;
|
|
}
|
|
if (m_Action) break;
|
|
}
|
|
if (!m_Action || !m_Tank) return false;
|
|
m_Interval = Math.AbsFloat(m_Action.GetActionDuration()) * 1000;
|
|
return m_Interval >= 100 && m_Interval <= 5000 && Context();
|
|
}
|
|
int Tick(float now)
|
|
{
|
|
if (m_State == 8) return 1;
|
|
if (m_Stopped || m_State < 0) return -1;
|
|
if (!Enabled() || !Replication.IsServer() || !m_Mode || m_Mode != ERR_GameMode.Get() || !m_Mode.Active()) { Stop(); return -1; }
|
|
if (m_Deadline < 0) m_Deadline = now + 120000;
|
|
if (!WithinDeadline(m_Deadline - 120000, now)) { Check(false, "fixed refuel proof deadline"); return -1; }
|
|
if (m_State == 0)
|
|
{
|
|
if (!Check(Select(), "read-only selection pins two original five-litre cans and exact original parked UAZ action/tank")) return -1;
|
|
if (!Check(vector.Distance(m_Player.GetOrigin(), m_Container.GetOrigin()) <= 4 && Contains(m_Source, m_Can) && !Contains(m_Inventory, m_Can) && ERR_InfantryAmmoTransfer.StoredUnder(m_Can, m_Container), "original can is exclusively at the nearby cache")) return -1;
|
|
m_Destination = m_Inventory.FindStorageForItem(m_Can, EStoragePurpose.PURPOSE_DEPOSIT);
|
|
if (!Check(m_Destination && ERR_InfantryAmmoTransfer.StoredUnder(m_Destination.GetOwner(), m_Player)
|
|
&& m_Source.CanMoveItemToStorage(m_Can, m_Destination), "native destination has capacity for original can")) return -1;
|
|
m_Move = new ERR_NativeMaintenanceMove(); m_State = 1;
|
|
bool accepted = m_Source.TryMoveItemToStorage(m_Can, m_Destination, -1, m_Move);
|
|
PrintFormat("ERR MAINTENANCE MOVE: accepted=%1 callback=%2; one submission only", accepted, m_Move.Result);
|
|
return 0;
|
|
}
|
|
if (!Context()) { Check(false, "original mission, player, empty stationary UAZ or untouched spare can lost"); return -1; }
|
|
if (m_State == 1)
|
|
{
|
|
if (!m_Move.Result) return 0;
|
|
if (!Check(m_Move.Result == 1, "original can inventory callback succeeded")) return -1;
|
|
if (!Carried()) return 0;
|
|
InventoryItemComponent item = InventoryItemComponent.Cast(m_Can.FindComponent(InventoryItemComponent));
|
|
if (!Check(item && item.GetParentSlot() && item.GetParentSlot().GetStorage() == m_Destination
|
|
&& item.GetParentSlot().GetAttachedEntity() == m_Can, "callback and exact destination slot agree")) return -1;
|
|
m_State = 2; m_Gadgets.SetGadgetMode(m_Can, EGadgetMode.IN_HAND); return 0;
|
|
}
|
|
if (m_State == 2)
|
|
{
|
|
if (!Held()) return 0;
|
|
if (!Check(Math.AbsFloat(m_CanFuel.GetTotalFuel() - 5) <= 0.001, "equipping original can did not refill or consume it")) return -1;
|
|
m_State = 3; return 0;
|
|
}
|
|
if (!Held()) { Check(false, "exact original can is no longer the held native fuel support station"); return -1; }
|
|
if (m_State == 3)
|
|
{
|
|
if (!AtAction()) return 0;
|
|
m_BaseTank = m_Tank.GetFuel() - 5;
|
|
if (!Check(m_BaseTank >= 0 && m_Action.CheckIfFuelNodeIsValid(m_Tank), "one original tank permits five-litre fixture depletion")) return -1;
|
|
PrintFormat("ERR MAINTENANCE ARRANGE: original tank before=%1 after=%2; can unchanged=%3", m_Tank.GetFuel(), m_BaseTank, m_CanFuel.GetTotalFuel());
|
|
m_Tank.SetFuel(m_BaseTank);
|
|
if (!Check(Conservation(), "one-time tank depletion recorded; both original cans remain unchanged")) return -1;
|
|
if (!Check(ActionAllowed(), "existing native refuel action accepts actual held can and parked UAZ")) return -1;
|
|
m_Action.OnActionStart(m_Player); m_ActionStarted = true;
|
|
m_State = 4; m_Next = now + m_Interval; return 0;
|
|
}
|
|
if (!AtAction() || !Conservation()) { Check(false, "physical action reach or finite fuel conservation lost"); return -1; }
|
|
if (now < m_Next) return 0;
|
|
if (m_State == 5)
|
|
{
|
|
if (!Check(Math.AbsFloat(m_CanFuel.GetTotalFuel() - m_LastCan) <= 0.001 && Math.AbsFloat(m_Tank.GetFuel() - m_LastTank) <= 0.001, "cancelled action transfers no fuel while interrupted")) return -1;
|
|
m_Action.OnActionStart(m_Player); m_ActionStarted = true; m_State = 4; m_Next = now + m_Interval; return 0;
|
|
}
|
|
if (m_State == 6)
|
|
{
|
|
if (!Check(Math.AbsFloat(m_CanFuel.GetTotalFuel() - m_LastCan) <= 0.001 && Math.AbsFloat(m_Tank.GetFuel() - m_LastTank) <= 0.001 && Contains(m_Inventory, m_Can), "empty original can persists and refused replay adds no fuel")) return -1;
|
|
m_State = 8; Stop(); return 1;
|
|
}
|
|
float beforeCan = m_CanFuel.GetTotalFuel(), beforeTank = m_Tank.GetFuel();
|
|
if (beforeCan <= 0.001)
|
|
{
|
|
if (!Check(m_Interrupted, "at least one partial native transfer was interrupted and resumed")) return -1;
|
|
CancelAction();
|
|
if (!Check(!m_Station.IsValid(m_Vehicle, m_Player, m_Action, m_Action.GetWorldPositionAction(), m_EmptyReason, m_EmptySupplies), "empty original can is natively ineligible")) return -1;
|
|
if (!Check(!m_Action.CanBePerformedScript(m_Player), "native action refuses empty held can")) return -1;
|
|
m_Action.PerformAction(m_Vehicle, m_Player);
|
|
m_LastCan = beforeCan; m_LastTank = beforeTank; m_State = 6; m_Next = now + 1500; return 0;
|
|
}
|
|
if (!Check(++m_Calls <= 128 && ActionAllowed(), "bounded native action remains eligible before transfer")) return -1;
|
|
m_Action.PerformAction(m_Vehicle, m_Player);
|
|
float afterCan = m_CanFuel.GetTotalFuel(), afterTank = m_Tank.GetFuel();
|
|
if (!Check(afterCan < beforeCan && afterTank > beforeTank && Conservation(), "native transfer consumes original can fuel and adds the same amount to the original tank")) return -1;
|
|
PrintFormat("ERR MAINTENANCE TRANSFER: call=%1 can=%2 tank=%3 consumed=%4 gained=%5", m_Calls, afterCan, afterTank, m_InitialCan - afterCan, afterTank - m_BaseTank);
|
|
m_Next = now + m_Interval;
|
|
if (!m_Interrupted && afterCan > 0.001)
|
|
{
|
|
m_Interrupted = true; CancelAction(); m_LastCan = afterCan; m_LastTank = afterTank;
|
|
m_State = 5; m_Next = now + 1500;
|
|
}
|
|
return 0;
|
|
}
|
|
protected ESupportStationReasonInvalid m_EmptyReason;
|
|
protected int m_EmptySupplies;
|
|
}
|
|
#endif
|