Harden base progression and cleanup; validate finite physical resupply
This commit is contained in:
@@ -0,0 +1,347 @@
|
||||
class ERR_MortarSupplyStatus
|
||||
{
|
||||
SCR_AIGroup Group;
|
||||
IEntity Gun;
|
||||
vector Position;
|
||||
float ObservedAt;
|
||||
int MessageId;
|
||||
int Shells;
|
||||
}
|
||||
class ERR_MortarLogisticsRules
|
||||
{
|
||||
static bool Fresh(float observedAt, float now) { return observedAt >= 0 && now >= observedAt && now - observedAt <= 180000; }
|
||||
static bool Need(int shells) { return shells >= 0 && shells <= 6; }
|
||||
static bool NewRequest(int message, int consumed, bool fresh) { return fresh && message > 0 && message > consumed; }
|
||||
static bool Near(float distance) { return distance >= 0 && distance <= 12; }
|
||||
static bool ActiveTrip(bool server, bool stopped, int trip, int current, bool fresh, bool ownershipValid) { return server && !stopped && trip > 0 && trip == current && fresh && ownershipValid; }
|
||||
}
|
||||
// One finite western-cache courier, owned and ticked exclusively by HQ.
|
||||
class ERR_MortarLogistics
|
||||
{
|
||||
protected ERR_HQ m_HQ;
|
||||
protected ERR_GameMode m_Mode;
|
||||
protected ERR_SiteRuntime m_Site;
|
||||
protected IEntity m_Cache;
|
||||
protected SCR_AIGroup m_Group;
|
||||
protected ERR_MortarBattery m_Battery;
|
||||
protected IEntity m_OriginalGun;
|
||||
protected SCR_AIGroup m_OriginalBatteryGroup;
|
||||
protected ref array<IEntity> m_BatteryMembers = {};
|
||||
protected ref array<IEntity> m_OriginalMembers = {};
|
||||
protected ref array<IEntity> m_Stock = {};
|
||||
protected ref array<IEntity> m_Cargo = {};
|
||||
protected ref array<IEntity> m_AttemptedPickup = {};
|
||||
protected ref array<IEntity> m_AttemptedUnload = {};
|
||||
protected ref array<ref ERR_MortarInitialItem> m_Initial = {};
|
||||
protected ref ERR_MortarSupplyStatus m_Report;
|
||||
protected ref ERR_HQRoute m_Order;
|
||||
protected ref ERR_MortarCourierTransfer m_Move;
|
||||
protected IEntity m_Courier;
|
||||
protected AIWaypoint m_Waypoint;
|
||||
protected AIWaypoint m_HoldWaypoint;
|
||||
protected float m_InitialDeadline;
|
||||
protected float m_NextReport;
|
||||
protected float m_LastDeliveryAt = -1;
|
||||
protected int m_ConsumedMessage;
|
||||
protected int m_Trip;
|
||||
protected int m_State; // 0 initial, 1 idle, 2 downlink, 3 pickup, 4 travel, 5 unload
|
||||
protected int m_Delivered;
|
||||
protected int m_CompletedTrips;
|
||||
protected bool m_Started;
|
||||
protected bool m_Stopped;
|
||||
protected string m_Reason;
|
||||
static const int INITIAL_SHELLS = 12;
|
||||
static const int TRIP_SHELLS = 6;
|
||||
void ERR_MortarLogistics(ERR_HQ hq, ERR_GameMode mode) { m_HQ = hq; m_Mode = mode; }
|
||||
int State() { return m_State; }
|
||||
int Trip() { return m_Trip; }
|
||||
int Delivered() { return m_Delivered; }
|
||||
int CompletedTrips() { return m_CompletedTrips; }
|
||||
IEntity Cache() { return m_Cache; }
|
||||
SCR_AIGroup Group() { return m_Group; }
|
||||
IEntity Courier() { return m_Courier; }
|
||||
ERR_MortarBattery Battery() { return m_Battery; }
|
||||
ERR_MortarSupplyStatus Report() { return m_Report; }
|
||||
bool Stopped() { return m_Stopped; }
|
||||
void InitialStock(out array<IEntity> items) { items.Copy(m_Stock); }
|
||||
protected void Audit(string kind, string reason)
|
||||
{
|
||||
int message; if (m_Report) message = m_Report.MessageId;
|
||||
m_Reason = reason;
|
||||
m_HQ.Journal().Write("COURIER " + kind, string.Format("trip=%1 state=%2 delivered=%3; %4", m_Trip, m_State, m_Delivered, reason), message);
|
||||
}
|
||||
protected InventoryStorageManagerComponent Inventory(IEntity entity)
|
||||
{
|
||||
if (!entity) return null;
|
||||
return InventoryStorageManagerComponent.Cast(entity.FindComponent(InventoryStorageManagerComponent));
|
||||
}
|
||||
protected bool Contains(IEntity owner, IEntity shell)
|
||||
{
|
||||
InventoryStorageManagerComponent inventory = Inventory(owner);
|
||||
if (!inventory || !shell) return false;
|
||||
array<IEntity> items = {}; ERR_Inventory.Items(inventory, items); return items.Contains(shell);
|
||||
}
|
||||
protected void Shells(IEntity owner, out array<IEntity> shells)
|
||||
{
|
||||
shells.Clear(); InventoryStorageManagerComponent inventory = Inventory(owner); if (!inventory) return;
|
||||
array<IEntity> items = {}; ERR_Inventory.Items(inventory, items);
|
||||
foreach (IEntity item : items)
|
||||
if (item && item.GetPrefabData() && item.GetPrefabData().GetPrefabName() == ERR_MortarBattery.SHELL) shells.Insert(item);
|
||||
}
|
||||
bool Prepare()
|
||||
{
|
||||
if (!Replication.IsServer() || m_Started || m_Stopped) return false;
|
||||
m_Started = true;
|
||||
ERR_ScenarioComponent scenario = ERR_ScenarioComponent.Cast(m_Mode.FindComponent(ERR_ScenarioComponent));
|
||||
if (!scenario || !scenario.Sites()) return false;
|
||||
m_Site = scenario.Sites().Find("everon-supply-2");
|
||||
if (!m_Site || !m_Site.Group || !m_Site.PatrolCycle || !scenario.Sites().Owns(m_Site.Container)) return false;
|
||||
m_Cache = m_Site.Container; m_Group = m_Site.Group;
|
||||
array<ref ERR_MortarBattery> batteries = {}; m_HQ.Mortars().Batteries(batteries);
|
||||
if (batteries.Count() != 1) return false;
|
||||
m_Battery = batteries[0]; m_OriginalGun = m_Battery.Gun; m_OriginalBatteryGroup = m_Battery.Group;
|
||||
InventoryStorageManagerComponent inventory = Inventory(m_Cache);
|
||||
if (!inventory || !inventory.CanInsertResource(ERR_MortarBattery.SHELL, EStoragePurpose.PURPOSE_DEPOSIT)) return false;
|
||||
array<IEntity> existing = {}; Shells(m_Cache, existing); if (!existing.IsEmpty()) return false;
|
||||
array<AIAgent> agents = {}; m_Group.GetAgents(agents);
|
||||
foreach (AIAgent agent : agents)
|
||||
{
|
||||
if (!agent || !ERR_Actors.CanFight(agent.GetControlledEntity())) return false;
|
||||
m_OriginalMembers.Insert(agent.GetControlledEntity());
|
||||
}
|
||||
if (m_OriginalMembers.Count() != m_Site.InitialGuardCount) return false;
|
||||
m_InitialDeadline = GetGame().GetWorld().GetWorldTime() + 15000;
|
||||
// SiteService owns this existing cache and its contents through rollback/world teardown.
|
||||
// This bounded startup allocation is never reopened, including partial failure/theft.
|
||||
for (int i = 0; i < INITIAL_SHELLS; i++)
|
||||
{
|
||||
IEntity shell = ERR_Actors.Spawn(ERR_MortarBattery.SHELL, m_Cache.GetOrigin());
|
||||
if (!shell) return false;
|
||||
// Keep the exact created identity; counts cannot prove an external item was
|
||||
// not substituted while an asynchronous insertion was pending.
|
||||
m_Mode.Own(shell); m_Stock.Insert(shell);
|
||||
ERR_MortarInitialItem operation = new ERR_MortarInitialItem(); m_Initial.Insert(operation);
|
||||
if (!inventory.TryInsertItem(shell, EStoragePurpose.PURPOSE_DEPOSIT, operation)) { operation.Rejected(); return false; }
|
||||
}
|
||||
return true;
|
||||
}
|
||||
protected bool OriginalGroup()
|
||||
{
|
||||
if (!m_Site || m_Site.Group != m_Group || m_Site.Container != m_Cache || !m_Group || !m_Cache || m_Group.GetPlayerCount(true) > 0 || m_Group.GetFaction() != m_Mode.EnemyFaction()) return false;
|
||||
array<AIAgent> agents = {}; m_Group.GetAgents(agents); bool living;
|
||||
foreach (AIAgent agent : agents)
|
||||
{
|
||||
if (!agent || !agent.GetControlledEntity()) continue;
|
||||
IEntity actor = agent.GetControlledEntity();
|
||||
if (!m_OriginalMembers.Contains(actor) || ERR_Actors.Player(actor) || ERR_Actors.Group(actor) != m_Group || SCR_FactionManager.SGetFaction(actor) != m_Mode.EnemyFaction()) return false;
|
||||
if (ERR_Actors.CanFight(actor)) living = true;
|
||||
}
|
||||
return living;
|
||||
}
|
||||
protected bool BindBatteryMembers()
|
||||
{
|
||||
if (!m_Battery || !m_Battery.StartupActivated || m_Battery.Group != m_OriginalBatteryGroup || m_Battery.Gun != m_OriginalGun) return false;
|
||||
if (!m_BatteryMembers.IsEmpty()) return true;
|
||||
array<AIAgent> agents = {}; m_Battery.Group.GetAgents(agents);
|
||||
foreach (AIAgent agent : agents)
|
||||
if (agent && agent.GetControlledEntity() && !m_BatteryMembers.Contains(agent.GetControlledEntity())) m_BatteryMembers.Insert(agent.GetControlledEntity());
|
||||
return !m_BatteryMembers.IsEmpty();
|
||||
}
|
||||
bool TargetAvailable()
|
||||
{
|
||||
if (!m_Battery || m_Battery.Gun != m_OriginalGun || m_Battery.Group != m_OriginalBatteryGroup || !m_OriginalGun || !m_Battery.StartupActivated || m_Battery.Stopped() || !m_Battery.Operational() || m_HQ.Mortars().FindGun(m_OriginalGun) != m_Battery || m_BatteryMembers.IsEmpty()) return false;
|
||||
if (SCR_FactionManager.SGetFaction(m_OriginalGun) != m_Mode.EnemyFaction()) return false;
|
||||
SCR_AIVehicleUsageComponent usage = SCR_AIVehicleUsageComponent.Cast(m_OriginalGun.FindComponent(SCR_AIVehicleUsageComponent));
|
||||
if (!usage || !usage.GetTurretCompartmentSlot()) return false;
|
||||
IEntity occupant = usage.GetTurretCompartmentSlot().GetOccupant();
|
||||
if (!occupant) return true;
|
||||
return m_BatteryMembers.Contains(occupant) && ERR_Actors.CanFight(occupant) && !ERR_Actors.Player(occupant) && ERR_Actors.Group(occupant) == m_OriginalBatteryGroup && SCR_FactionManager.SGetFaction(occupant) == m_Mode.EnemyFaction();
|
||||
}
|
||||
bool CanSample(ERR_MortarBattery battery) { return battery == m_Battery && TargetAvailable(); }
|
||||
protected bool Bearer(IEntity actor)
|
||||
{
|
||||
return actor && m_OriginalMembers.Contains(actor) && ERR_Actors.Group(actor) == m_Group && ERR_Actors.CanFight(actor) && !ERR_Actors.Player(actor) && !CompartmentAccessComponent.GetVehicleIn(actor) && SCR_FactionManager.SGetFaction(actor) == m_Mode.EnemyFaction() && ERR_HQRadio.Capability(actor) != 2;
|
||||
}
|
||||
bool AcceptsStatus(ERR_MortarSupplyStatus report, float now)
|
||||
{
|
||||
return !m_Stopped && m_State == 1 && report && m_Battery && report.Group == m_Battery.Group && report.Gun == m_Battery.Gun && ERR_MortarLogisticsRules.Need(report.Shells) && ERR_MortarLogisticsRules.NewRequest(report.MessageId, m_ConsumedMessage, ERR_MortarLogisticsRules.Fresh(report.ObservedAt, now));
|
||||
}
|
||||
void Receive(ERR_MortarSupplyStatus report, float now)
|
||||
{
|
||||
if (!AcceptsStatus(report, now)) return;
|
||||
m_Report = report;
|
||||
Audit("RECEIVED", "immutable low-ammunition status delivered by native physical radio route");
|
||||
}
|
||||
protected bool Authorized(float now)
|
||||
{
|
||||
if (!m_Mode || !m_Mode.Active() || !m_Report || !m_Battery) return false;
|
||||
return ERR_MortarLogisticsRules.ActiveTrip(Replication.IsServer(), m_Stopped, m_Trip, m_HQ.LogisticsToken(m_Group), ERR_MortarLogisticsRules.Fresh(m_Report.ObservedAt, now), OriginalGroup()) && TargetAvailable() && m_Report.Gun == m_Battery.Gun && m_Report.Group == m_Battery.Group && vector.Distance(m_Report.Position, m_Battery.Gun.GetOrigin()) <= 5;
|
||||
}
|
||||
bool TransferAllowed(int trip, IEntity shell, IEntity from, IEntity to, float now)
|
||||
{
|
||||
if (trip != m_Trip || !Authorized(now) || !shell || !m_Stock.Contains(shell) || !Bearer(m_Courier)) return false;
|
||||
if (m_State == 3) return from == m_Cache && to == m_Courier && ERR_MortarLogisticsRules.Near(vector.Distance(from.GetOrigin(), to.GetOrigin()));
|
||||
if (m_State != 5 || from != m_Courier || !m_Cargo.Contains(shell) || !ERR_Actors.CanFight(to) || ERR_Actors.Player(to) || to == m_Battery.RadioOperator || !m_BatteryMembers.Contains(to) || ERR_Actors.Group(to) != m_Battery.Group || SCR_FactionManager.SGetFaction(to) != m_Mode.EnemyFaction()) return false;
|
||||
return !CompartmentAccessComponent.GetVehicleIn(to) && ERR_MortarLogisticsRules.Near(vector.Distance(from.GetOrigin(), to.GetOrigin()));
|
||||
}
|
||||
protected void ClearWaypoint()
|
||||
{
|
||||
if (m_Waypoint) { if (m_Group) m_Group.RemoveWaypoint(m_Waypoint); ERR_Actors.DeleteSpawned(m_Waypoint); }
|
||||
m_Waypoint = null;
|
||||
if (m_HoldWaypoint) { if (m_Group) m_Group.RemoveWaypoint(m_HoldWaypoint); ERR_Actors.DeleteSpawned(m_HoldWaypoint); }
|
||||
m_HoldWaypoint = null;
|
||||
}
|
||||
protected bool MoveTo(vector position)
|
||||
{
|
||||
ClearWaypoint();
|
||||
m_Waypoint = AIWaypoint.Cast(ERR_Actors.Spawn(ERR_Resources.MoveWaypoint(), ERR_Actors.Ground(position)));
|
||||
if (!m_Waypoint) return false;
|
||||
// A completed Move alone would let the original patrol resume during the
|
||||
// asynchronous item operation. This owned, non-completing native Defend
|
||||
// keeps that queue below us until release; its original entries are untouched.
|
||||
m_HoldWaypoint = AIWaypoint.Cast(ERR_Actors.Spawn(ERR_Resources.DefendWaypoint(), ERR_Actors.Ground(position)));
|
||||
if (!m_HoldWaypoint) { ClearWaypoint(); return false; }
|
||||
m_HoldWaypoint.SetCompletionRadius(8);
|
||||
m_Group.AddWaypointAt(m_HoldWaypoint, 0); m_Group.AddWaypointAt(m_Waypoint, 0); m_Group.ActivateAI(); return true;
|
||||
}
|
||||
protected void Finish(string reason)
|
||||
{
|
||||
Audit("END", reason);
|
||||
if (m_Move) m_Move.Invalidate();
|
||||
ClearWaypoint(); m_HQ.ReleaseLogistics(m_Group, m_Trip);
|
||||
m_Order = null; m_Report = null; m_Courier = null; m_Cargo.Clear(); m_State = 1;
|
||||
// Native requests cannot be canceled. A pending move remains held until its
|
||||
// physical outcome resolves; callbacks cannot authorize a later trip.
|
||||
m_NextReport = GetGame().GetWorld().GetWorldTime() + 60000;
|
||||
}
|
||||
protected bool BeginMove(IEntity shell, IEntity from, IEntity to, float now)
|
||||
{
|
||||
InventoryStorageManagerComponent destination = Inventory(to);
|
||||
BaseInventoryStorageComponent storage; if (destination) storage = destination.FindStorageForItem(shell, EStoragePurpose.PURPOSE_DEPOSIT);
|
||||
if (!storage) return false;
|
||||
m_Move = new ERR_MortarCourierTransfer(this, m_Trip, shell, from, to, storage, now);
|
||||
return m_Move.Begin(now);
|
||||
}
|
||||
protected bool PollMove(float now)
|
||||
{
|
||||
if (!m_Move) return true;
|
||||
int result = m_Move.Poll(now);
|
||||
if (result == 0) return false;
|
||||
if (result < 0) { Finish("native item operation rejected, expired or lost authority; physical items retained"); return false; }
|
||||
IEntity shell = m_Move.Shell(); m_Move = null;
|
||||
if (m_State == 3) { m_Cargo.Insert(shell); Audit("PICKUP", "exact depot shell confirmed in original bearer inventory"); }
|
||||
else if (m_State == 5) { m_Cargo.RemoveItem(shell); m_Delivered++; m_LastDeliveryAt = now; Audit("DELIVERED", "exact original shell confirmed in living mortar crew inventory"); }
|
||||
return true;
|
||||
}
|
||||
protected void Pickup(float now)
|
||||
{
|
||||
if (!m_Courier)
|
||||
{
|
||||
foreach (IEntity candidate : m_OriginalMembers)
|
||||
if (Bearer(candidate) && Inventory(candidate) && Inventory(candidate).CanInsertResource(ERR_MortarBattery.SHELL, EStoragePurpose.PURPOSE_DEPOSIT) && ERR_MortarLogisticsRules.Near(vector.Distance(candidate.GetOrigin(), m_Cache.GetOrigin()))) { m_Courier = candidate; break; }
|
||||
if (!m_Courier) return;
|
||||
}
|
||||
if (!Bearer(m_Courier)) { Finish("original bearer lost or captured"); return; }
|
||||
if (!PollMove(now)) return;
|
||||
if (m_Cargo.Count() < TRIP_SHELLS)
|
||||
{
|
||||
foreach (IEntity shell : m_Stock)
|
||||
{
|
||||
if (!shell || !Contains(m_Cache, shell) || m_AttemptedPickup.Contains(shell)) continue;
|
||||
InventoryStorageManagerComponent destination = Inventory(m_Courier);
|
||||
if (!destination || !destination.FindStorageForItem(shell, EStoragePurpose.PURPOSE_DEPOSIT)) break;
|
||||
m_AttemptedPickup.Insert(shell);
|
||||
if (!BeginMove(shell, m_Cache, m_Courier, now)) Finish("native pickup rejected; no second request for that shell");
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (m_Cargo.IsEmpty()) { Finish("finite cache empty or bearer native storage full"); return; }
|
||||
m_State = 4;
|
||||
if (!MoveTo(m_Report.Position)) { Finish("native delivery waypoint unavailable"); return; }
|
||||
Audit("OUTBOUND", "original patrol walking with verified finite cargo to reported battery position");
|
||||
}
|
||||
protected void Unload(float now)
|
||||
{
|
||||
if (!PollMove(now)) return;
|
||||
if (m_Cargo.IsEmpty()) { m_CompletedTrips++; Finish("finite delivery complete; original patrol restored"); return; }
|
||||
array<AIAgent> agents = {}; m_Battery.Group.GetAgents(agents);
|
||||
foreach (IEntity shell : m_Cargo)
|
||||
{
|
||||
if (!shell || !Contains(m_Courier, shell)) { Finish("carried shell lost or stolen; no replacement"); return; }
|
||||
if (m_AttemptedUnload.Contains(shell)) { Finish("previous unload unresolved or failed; no retry"); return; }
|
||||
foreach (AIAgent agent : agents)
|
||||
{
|
||||
if (!agent) continue;
|
||||
IEntity recipient = agent.GetControlledEntity();
|
||||
if (!TransferAllowed(m_Trip, shell, m_Courier, recipient, now)) continue;
|
||||
InventoryStorageManagerComponent destination = Inventory(recipient);
|
||||
if (!destination || !destination.FindStorageForItem(shell, EStoragePurpose.PURPOSE_DEPOSIT)) continue;
|
||||
m_AttemptedUnload.Insert(shell);
|
||||
if (!BeginMove(shell, m_Courier, recipient, now)) Finish("native recipient move rejected; cargo remains physical");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
void Tick(float now)
|
||||
{
|
||||
if (m_Stopped || !m_Started || !Replication.IsServer() || !m_Mode.Active()) return;
|
||||
if (m_State == 0)
|
||||
{
|
||||
bool failed; foreach (ERR_MortarInitialItem operation : m_Initial) { if (!operation.Finished) { if (now < m_InitialDeadline) return; failed = true; } else if (!operation.Succeeded) failed = true; }
|
||||
if (!OriginalGroup()) failed = true;
|
||||
array<IEntity> shells = {}; Shells(m_Cache, shells);
|
||||
if (shells.Count() != INITIAL_SHELLS) { if (now < m_InitialDeadline && !failed) return; failed = true; }
|
||||
foreach (IEntity originalShell : m_Stock) if (!originalShell || !shells.Contains(originalShell)) failed = true;
|
||||
if (m_Stock.Count() != INITIAL_SHELLS) failed = true;
|
||||
if (failed) { Audit("STARTUP FAILED", "one-time native stock allocation incomplete; no refill"); Stop(); return; }
|
||||
m_State = 1; Audit("READY", "12 distinct original HE shells physically confirmed in existing western cache");
|
||||
}
|
||||
if (m_State == 1)
|
||||
{
|
||||
if (m_Move) { m_Move.Poll(now); if (m_Move.Unresolved()) return; m_Move = null; }
|
||||
if (!OriginalGroup() || !BindBatteryMembers() || !TargetAvailable()) return;
|
||||
bool available; foreach (IEntity stocked : m_Stock) if (Contains(m_Cache, stocked) && !m_AttemptedPickup.Contains(stocked)) available = true;
|
||||
if (!available) return;
|
||||
if (now >= m_NextReport && ERR_MortarLogisticsRules.Need(m_Battery.CountShells())) { if (m_HQ.QueueSupplyStatus(m_Battery, now)) m_NextReport = now + 60000; }
|
||||
if (!m_Report || !ERR_MortarLogisticsRules.NewRequest(m_Report.MessageId, m_ConsumedMessage, ERR_MortarLogisticsRules.Fresh(m_Report.ObservedAt, now))) return;
|
||||
int nextTrip = m_Trip + 1;
|
||||
if (!m_HQ.ReserveLogistics(m_Group, nextTrip)) return;
|
||||
m_Trip = nextTrip; m_ConsumedMessage = m_Report.MessageId;
|
||||
m_Order = m_HQ.Downlink(m_Group, "MORTAR SUPPLY", "collect finite western-cache HE; walk to reported battery", m_Report.MessageId);
|
||||
m_State = 2;
|
||||
if (!m_Order) { Finish("no native courier downlink"); return; }
|
||||
Audit("QUEUED", "original patrol reserved pending physical radio command delivery");
|
||||
}
|
||||
if (!Authorized(now)) { Finish("supply report expired or original ownership/gun/group changed"); return; }
|
||||
if (m_Courier && !Bearer(m_Courier)) { Finish("original bearer unavailable; surviving cargo retained"); return; }
|
||||
if (m_State == 2)
|
||||
{
|
||||
int delivered = m_Order.Advance(now, m_HQ.Journal());
|
||||
if (delivered == 0) return;
|
||||
if (delivered < 0) { Finish("courier command interrupted"); return; }
|
||||
m_Order = null; m_State = 3;
|
||||
if (!MoveTo(m_Cache.GetOrigin())) { Finish("native pickup waypoint unavailable"); return; }
|
||||
Audit("DISPATCH", "same original patrol receives delivered order and walks to finite cache");
|
||||
}
|
||||
if (m_State == 3) { Pickup(now); return; }
|
||||
if (m_State == 4)
|
||||
{
|
||||
if (!ERR_MortarLogisticsRules.Near(vector.Distance(m_Courier.GetOrigin(), m_Report.Position))) return;
|
||||
m_State = 5; Audit("ARRIVED", "physical bearer reached reported gun area; exact recipient checks still required");
|
||||
}
|
||||
if (m_State == 5) Unload(now);
|
||||
}
|
||||
void Stop()
|
||||
{
|
||||
if (m_Stopped) return;
|
||||
m_Stopped = true;
|
||||
if (m_Move) m_Move.Invalidate();
|
||||
ClearWaypoint(); m_HQ.ReleaseLogistics(m_Group, m_Trip); m_Order = null; m_Report = null;
|
||||
}
|
||||
#ifdef WORKBENCH
|
||||
ERR_HQRoute ProbeOrder() { return m_Order; }
|
||||
int ProbeSourceMessage() { return m_ConsumedMessage; }
|
||||
float ProbeLastDelivery() { return m_LastDeliveryAt; }
|
||||
#endif
|
||||
string Status() { return string.Format("courier state=%1 finite initial=%2 delivered=%3 stopped=%4 (%5)", m_State, m_Stock.Count(), m_Delivered, m_Stopped, m_Reason); }
|
||||
}
|
||||
Reference in New Issue
Block a user