Integrate delivered HQ memory, finite physical convoy and RHS WCS dependencies
This commit is contained in:
@@ -18,8 +18,10 @@ class ERR_Actors
|
||||
{
|
||||
if (!actor) return null;
|
||||
AIControlComponent control = AIControlComponent.Cast(actor.FindComponent(AIControlComponent));
|
||||
if (!control || !control.GetAIAgent()) return null;
|
||||
return SCR_AIGroup.Cast(control.GetAIAgent().GetParentGroup());
|
||||
// Seated characters may delegate their inherited agent to a vehicle.
|
||||
// Native group membership belongs to the character's controlled agent.
|
||||
if (!control || !control.GetControlAIAgent()) return null;
|
||||
return SCR_AIGroup.Cast(control.GetControlAIAgent().GetParentGroup());
|
||||
}
|
||||
|
||||
static bool Player(IEntity actor)
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
enum ERR_ForceKind { GARRISON, PATROL, CHECKPOINT, CONVOY }
|
||||
enum ERR_ForceDuty { HOLD, PATROL_ROUTE, MOVE_TO, SEARCH, RETURN }
|
||||
|
||||
// Owned authored polyline. Seal on registration so simulation never observes a
|
||||
// partially edited route. Native waypoints and terrain queries belong elsewhere.
|
||||
class ERR_ForceRoute
|
||||
{
|
||||
protected ref array<vector> m_Points = {};
|
||||
protected bool m_Loop;
|
||||
protected bool m_Sealed;
|
||||
protected float m_Length;
|
||||
void ERR_ForceRoute(bool loop = false) { m_Loop = loop; }
|
||||
int Count() { return m_Points.Count(); }
|
||||
vector At(int index)
|
||||
{
|
||||
if (index < 0 || index >= m_Points.Count()) return "0 0 0";
|
||||
return m_Points[index];
|
||||
}
|
||||
bool Looped() { return m_Loop; }
|
||||
float Length() { return m_Length; }
|
||||
bool Add(vector point)
|
||||
{
|
||||
if (m_Sealed || m_Points.Count() >= 64 || !ERR_ForceRules.FiniteVector(point)) return false;
|
||||
m_Points.Insert(point);
|
||||
return true;
|
||||
}
|
||||
bool Seal()
|
||||
{
|
||||
if (m_Sealed) return true;
|
||||
if (m_Points.IsEmpty() || (m_Loop && m_Points.Count() < 2)) return false;
|
||||
float length;
|
||||
for (int i = 1; i < m_Points.Count(); i++)
|
||||
length += vector.DistanceXZ(m_Points[i - 1], m_Points[i]);
|
||||
if (m_Loop) length += vector.DistanceXZ(m_Points[m_Points.Count() - 1], m_Points[0]);
|
||||
if (!ERR_ForceRules.Finite(length) || (m_Loop && length <= 0.01)) return false;
|
||||
m_Length = length;
|
||||
m_Sealed = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class ERR_ForceRecord
|
||||
{
|
||||
int Id;
|
||||
string SourceId;
|
||||
ERR_ForceKind Kind;
|
||||
vector Position;
|
||||
float Heading;
|
||||
vector HomePosition;
|
||||
ERR_ForceDuty HomeDuty;
|
||||
ref ERR_ForceRoute Route;
|
||||
int RouteIndex;
|
||||
float RouteProgress;
|
||||
bool RouteFinished;
|
||||
int InitialStrength;
|
||||
int Strength;
|
||||
int Casualties;
|
||||
ERR_ForceDuty Duty;
|
||||
vector DutyTarget;
|
||||
int Importance = 1;
|
||||
int RadioCapability;
|
||||
SCR_AIGroup Group;
|
||||
// A dormant group handle may remain for native member reuse. This explicit
|
||||
// state, never handle presence, gates arithmetic and later materialisation.
|
||||
bool Physical;
|
||||
bool Pinned;
|
||||
bool Eliminated;
|
||||
float LastSimulatedAt;
|
||||
float StateSince;
|
||||
}
|
||||
|
||||
class ERR_ForceLedger
|
||||
{
|
||||
protected ref array<ref ERR_ForceRecord> m_Records = {};
|
||||
protected ref map<int, ERR_ForceRecord> m_ById = new map<int, ERR_ForceRecord>();
|
||||
protected ref map<string, int> m_Sources = new map<string, int>();
|
||||
protected ref map<SCR_AIGroup, int> m_Groups = new map<SCR_AIGroup, int>();
|
||||
protected int m_NextId;
|
||||
protected bool m_CapLogged;
|
||||
int Count() { return m_Records.Count(); }
|
||||
ERR_ForceRecord Find(int id) { return m_ById.Get(id); }
|
||||
ERR_ForceRecord FindGroup(SCR_AIGroup group)
|
||||
{
|
||||
if (!group || !m_Groups.Contains(group)) return null;
|
||||
return Find(m_Groups.Get(group));
|
||||
}
|
||||
ERR_ForceRecord At(int index)
|
||||
{
|
||||
if (index < 0 || index >= m_Records.Count()) return null;
|
||||
return m_Records[index];
|
||||
}
|
||||
ERR_ForceRecord Register(string sourceId, ERR_ForceKind kind, vector position, int strength,
|
||||
ERR_ForceRoute route = null, SCR_AIGroup group = null, float now = 0)
|
||||
{
|
||||
if (!Replication.IsServer() || sourceId.IsEmpty() || sourceId.Length() > 96
|
||||
|| !ERR_ForceRules.FiniteVector(position) || !ERR_ForceRules.Finite(now) || now < 0
|
||||
|| strength < 1 || strength > 32 || kind < ERR_ForceKind.GARRISON || kind > ERR_ForceKind.CONVOY) return null;
|
||||
if (m_Sources.Contains(sourceId))
|
||||
{
|
||||
ERR_ForceRecord known = Find(m_Sources.Get(sourceId));
|
||||
if (known && known.Kind == kind && known.Group == group) return known;
|
||||
return null;
|
||||
}
|
||||
if (group && FindGroup(group)) return null;
|
||||
// Retired source IDs also occupy the finite pool: re-registering a removed
|
||||
// formation cannot replace its casualties with a fresh full-strength unit.
|
||||
if (m_Records.Count() >= 128 || m_Sources.Count() >= 128)
|
||||
{
|
||||
if (!m_CapLogged) Print("Rereintroduced [ledger]: 128-formation registration cap reached.");
|
||||
m_CapLogged = true;
|
||||
return null;
|
||||
}
|
||||
if (route && !route.Seal()) return null;
|
||||
ERR_ForceRecord record = new ERR_ForceRecord();
|
||||
record.Id = ++m_NextId; record.SourceId = sourceId; record.Kind = kind;
|
||||
record.Position = position; record.HomePosition = position; record.DutyTarget = position;
|
||||
record.Route = route; record.InitialStrength = strength; record.Strength = strength;
|
||||
record.Group = group; record.Physical = group != null;
|
||||
record.LastSimulatedAt = now; record.StateSince = now;
|
||||
if (route) record.Duty = ERR_ForceDuty.PATROL_ROUTE;
|
||||
else record.Duty = ERR_ForceDuty.HOLD;
|
||||
record.HomeDuty = record.Duty;
|
||||
m_Records.Insert(record); m_ById.Set(record.Id, record); m_Sources.Set(sourceId, record.Id);
|
||||
if (group) m_Groups.Set(group, record.Id);
|
||||
return record;
|
||||
}
|
||||
bool Unregister(int id)
|
||||
{
|
||||
if (!Replication.IsServer()) return false;
|
||||
ERR_ForceRecord record = Find(id);
|
||||
if (!record) return false;
|
||||
if (record.Group) m_Groups.Remove(record.Group);
|
||||
m_ById.Remove(id); m_Records.RemoveItem(record);
|
||||
return true;
|
||||
}
|
||||
bool Eliminate(int id, float now)
|
||||
{
|
||||
if (!Replication.IsServer() || !ERR_ForceRules.Finite(now) || now < 0) return false;
|
||||
ERR_ForceRecord record = Find(id);
|
||||
if (!record || now < record.LastSimulatedAt) return false;
|
||||
if (record.Eliminated) return true;
|
||||
record.Strength = 0; record.Casualties = record.InitialStrength;
|
||||
record.Eliminated = true; record.Physical = false; record.Pinned = false;
|
||||
record.Duty = ERR_ForceDuty.HOLD; record.RouteFinished = true;
|
||||
record.LastSimulatedAt = now; record.StateSince = now;
|
||||
return true;
|
||||
}
|
||||
bool RecordPhysicalState(int id, vector position, float heading, int living, float now, bool physical = true)
|
||||
{
|
||||
if (!Replication.IsServer() || !ERR_ForceRules.FiniteVector(position)
|
||||
|| !ERR_ForceRules.Finite(heading) || !ERR_ForceRules.Finite(now)) return false;
|
||||
ERR_ForceRecord record = Find(id);
|
||||
if (!record || record.Eliminated || now < record.LastSimulatedAt || living < 0 || living > record.Strength)
|
||||
return false;
|
||||
record.Position = position; record.Heading = heading;
|
||||
if (living == 0) return Eliminate(id, now);
|
||||
record.Strength = living; record.Casualties = record.InitialStrength - living;
|
||||
if (record.Physical != physical) record.StateSince = now;
|
||||
record.Physical = physical; record.LastSimulatedAt = now;
|
||||
return true;
|
||||
}
|
||||
string Status()
|
||||
{
|
||||
int physical, dormant, eliminated, living, casualties;
|
||||
foreach (ERR_ForceRecord record : m_Records)
|
||||
{
|
||||
if (record.Eliminated) eliminated++;
|
||||
else if (record.Physical) physical++;
|
||||
else dormant++;
|
||||
living += record.Strength; casualties += record.Casualties;
|
||||
}
|
||||
return string.Format("Forces: %1 records, %2 physical, %3 dormant, %4 eliminated; %5 living, %6 casualties",
|
||||
Count(), physical, dormant, eliminated, living, casualties);
|
||||
}
|
||||
void Clear()
|
||||
{
|
||||
m_Records.Clear(); m_ById.Clear(); m_Sources.Clear(); m_Groups.Clear();
|
||||
m_NextId = 0; m_CapLogged = false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
class ERR_ForceRules
|
||||
{
|
||||
static bool Finite(float value) { return value == value && value > -1e20 && value < 1e20; }
|
||||
static bool FiniteVector(vector value)
|
||||
{
|
||||
return Finite(value[0]) && Finite(value[1]) && Finite(value[2]);
|
||||
}
|
||||
static float Speed(ERR_ForceKind kind, ERR_ForceDuty duty)
|
||||
{
|
||||
if (kind < ERR_ForceKind.GARRISON || kind > ERR_ForceKind.CONVOY
|
||||
|| duty <= ERR_ForceDuty.HOLD || duty > ERR_ForceDuty.RETURN) return 0;
|
||||
if (kind == ERR_ForceKind.CONVOY) return 11;
|
||||
return 1.4;
|
||||
}
|
||||
static int Advance(int index, int count, bool loop, bool arrived)
|
||||
{
|
||||
if (count < 1 || count > 64 || index < 0 || index > count) return -1;
|
||||
if (!arrived || index == count) return index;
|
||||
if (index + 1 < count) return index + 1;
|
||||
if (loop) return 0;
|
||||
return count; // terminal sentinel: a non-looping route finishes once
|
||||
}
|
||||
static vector Step(vector from, vector to, float speed, float seconds, out bool arrived)
|
||||
{
|
||||
arrived = false;
|
||||
if (!FiniteVector(from) || !FiniteVector(to) || !Finite(speed) || !Finite(seconds)
|
||||
|| speed < 0 || seconds < 0) return from;
|
||||
float distance = vector.DistanceXZ(from, to);
|
||||
if (distance <= 0.001) { arrived = true; return to; }
|
||||
float travel = speed * seconds;
|
||||
if (!Finite(travel)) return from;
|
||||
if (travel >= distance) { arrived = true; return to; }
|
||||
// Interpolate authored endpoint elevations, never query terrain/navmesh.
|
||||
return from + (to - from) * (travel / distance);
|
||||
}
|
||||
static bool Due(int id, float now, float lastSimulatedAt, float interval)
|
||||
{
|
||||
if (id < 0 || !Finite(now) || !Finite(lastSimulatedAt) || !Finite(interval)
|
||||
|| now < 0 || interval < 1000 || interval > 60000) return false;
|
||||
int phaseIndex = id % 8;
|
||||
float phase = phaseIndex * interval / 8;
|
||||
if (lastSimulatedAt < 0) return now >= phase;
|
||||
if (now - lastSimulatedAt < 1000) return false;
|
||||
return Math.Floor((now - phase) / interval) > Math.Floor((lastSimulatedAt - phase) / interval);
|
||||
}
|
||||
static float Heading(vector from, vector to, float previous)
|
||||
{
|
||||
vector delta = to - from;
|
||||
if (vector.DistanceXZ(from, to) <= 0.001) return previous;
|
||||
float heading = Math.Atan2(delta[0], delta[2]) * Math.RAD2DEG;
|
||||
if (heading < 0) heading += 360;
|
||||
return heading;
|
||||
}
|
||||
}
|
||||
|
||||
// Called by the existing slow mission tick. Dormant arithmetic never consults
|
||||
// an entity, observer, terrain or navmesh and never allocates per-record work.
|
||||
class ERR_ForceSimulation
|
||||
{
|
||||
protected ERR_ForceLedger m_Ledger;
|
||||
void ERR_ForceSimulation(ERR_ForceLedger ledger) { m_Ledger = ledger; }
|
||||
protected void Route(ERR_ForceRecord record, float distance)
|
||||
{
|
||||
ERR_ForceRoute route = record.Route;
|
||||
if (!route || record.RouteFinished || record.RouteIndex < 0 || record.RouteIndex >= route.Count()) return;
|
||||
int visited;
|
||||
while (visited <= route.Count())
|
||||
{
|
||||
vector target = route.At(record.RouteIndex);
|
||||
float leg = vector.DistanceXZ(record.Position, target);
|
||||
record.Heading = ERR_ForceRules.Heading(record.Position, target, record.Heading);
|
||||
if (leg > distance)
|
||||
{
|
||||
bool arrived;
|
||||
record.Position = ERR_ForceRules.Step(record.Position, target, 1, distance, arrived);
|
||||
record.RouteProgress += distance;
|
||||
return;
|
||||
}
|
||||
record.Position = target;
|
||||
distance -= leg;
|
||||
record.RouteProgress = 0;
|
||||
record.RouteIndex = ERR_ForceRules.Advance(record.RouteIndex, route.Count(), route.Looped(), true);
|
||||
if (record.RouteIndex == route.Count())
|
||||
{
|
||||
record.RouteFinished = true;
|
||||
record.Duty = ERR_ForceDuty.HOLD;
|
||||
return;
|
||||
}
|
||||
// At an authored vertex a whole lap returns to this exact state. Skip whole
|
||||
// laps arithmetically, then visit at most one remaining lap (64 points).
|
||||
if (route.Looped() && route.Length() > 0 && distance >= route.Length())
|
||||
distance -= Math.Floor(distance / route.Length()) * route.Length();
|
||||
if (distance <= 0) return;
|
||||
visited++;
|
||||
}
|
||||
}
|
||||
void Tick(float now)
|
||||
{
|
||||
if (!Replication.IsServer() || !m_Ledger) return;
|
||||
for (int i = 0; i < m_Ledger.Count(); i++)
|
||||
{
|
||||
ERR_ForceRecord record = m_Ledger.At(i);
|
||||
if (!record || record.Physical || record.Eliminated || record.Strength <= 0
|
||||
|| !ERR_ForceRules.Due(record.Id, now, record.LastSimulatedAt, 2000)) continue;
|
||||
float seconds = (now - record.LastSimulatedAt) / 1000;
|
||||
record.LastSimulatedAt = now;
|
||||
float speed = ERR_ForceRules.Speed(record.Kind, record.Duty);
|
||||
if (speed <= 0 || seconds <= 0 || !ERR_ForceRules.Finite(speed * seconds)) continue;
|
||||
if (record.Duty == ERR_ForceDuty.PATROL_ROUTE) { Route(record, speed * seconds); continue; }
|
||||
record.Heading = ERR_ForceRules.Heading(record.Position, record.DutyTarget, record.Heading);
|
||||
bool arrived;
|
||||
record.Position = ERR_ForceRules.Step(record.Position, record.DutyTarget, speed, seconds, arrived);
|
||||
if (arrived) record.Duty = ERR_ForceDuty.HOLD;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,11 @@ class ERR_GameMode : SCR_BaseGameMode
|
||||
protected ref ERR_PlayerRoster m_Roster = new ERR_PlayerRoster();
|
||||
protected ref ERR_AdminService m_Admin = new ERR_AdminService();
|
||||
protected ref ERR_CommandService m_Command;
|
||||
protected ref ERR_ForceLedger m_Forces = new ERR_ForceLedger();
|
||||
protected ref ERR_ForceSimulation m_ForceSimulation;
|
||||
protected ref array<AIAgent> m_ForceAgents = {};
|
||||
protected int m_ForceSerial;
|
||||
protected float m_NextForceSnapshot;
|
||||
protected ref array<ERR_LocationComponent> m_Locations = {};
|
||||
protected ref array<IEntity> m_OwnedEntities = {};
|
||||
protected ref ERR_ExtractionService m_Extraction;
|
||||
@@ -61,6 +66,7 @@ class ERR_GameMode : SCR_BaseGameMode
|
||||
ERR_RecoveryService Recovery() { return m_Recovery; }
|
||||
ERR_PrisonEscapeService PrisonEscape() { return m_PrisonEscape; }
|
||||
string RecoveryStatus() { return m_RecoveryStatus; }
|
||||
ERR_ForceLedger Forces() { return m_Forces; }
|
||||
ERR_CommandService Command() { return m_Command; }
|
||||
ERR_Objectives Objectives() { return m_Objectives; }
|
||||
ERR_ExtractionService Extraction() { return m_Extraction; }
|
||||
@@ -206,10 +212,45 @@ class ERR_GameMode : SCR_BaseGameMode
|
||||
{
|
||||
if (!Replication.IsServer() || !group) return;
|
||||
Own(group);
|
||||
if (group.GetFaction() == EnemyFaction() && !m_Forces.FindGroup(group))
|
||||
{
|
||||
int strength = ForceLiving(group);
|
||||
if (strength > 0)
|
||||
{
|
||||
m_ForceSerial++;
|
||||
ERR_ForceRecord record = m_Forces.Register("formation-" + m_ForceSerial.ToString(), ERR_ForceKind.GARRISON, group.GetOrigin(), strength, null, group, GetGame().GetWorld().GetWorldTime());
|
||||
if (!record) { Print("Rereintroduced: force ledger refused physical formation", LogLevel.ERROR); return; }
|
||||
}
|
||||
}
|
||||
if (m_Command) m_Command.RegisterGroup(group);
|
||||
m_OnGroup.Invoke(group);
|
||||
}
|
||||
|
||||
int ForceLiving(SCR_AIGroup group)
|
||||
{
|
||||
m_ForceAgents.Clear();
|
||||
if (!group) return 0;
|
||||
group.GetAgents(m_ForceAgents);
|
||||
int living;
|
||||
foreach (AIAgent agent : m_ForceAgents)
|
||||
if (agent && ERR_Actors.Alive(agent.GetControlledEntity())) living++;
|
||||
return living;
|
||||
}
|
||||
protected void TickForces(float now)
|
||||
{
|
||||
if (now >= m_NextForceSnapshot)
|
||||
{
|
||||
m_NextForceSnapshot = now + 2000;
|
||||
for (int i = 0; i < m_Forces.Count(); i++)
|
||||
{
|
||||
ERR_ForceRecord record = m_Forces.At(i);
|
||||
if (!record || !record.Physical || record.Eliminated) continue;
|
||||
if (!record.Group) { m_Forces.Eliminate(record.Id, now); continue; }
|
||||
m_Forces.RecordPhysicalState(record.Id, record.Group.GetOrigin(), record.Group.GetAngles()[0], ForceLiving(record.Group), now);
|
||||
}
|
||||
}
|
||||
if (m_ForceSimulation) m_ForceSimulation.Tick(now);
|
||||
}
|
||||
bool Transition(ERR_Phase phase)
|
||||
{
|
||||
if (!Replication.IsServer() || !m_PreparationError.IsEmpty() || !ERR_MissionRules.CanTransition(m_Phase, phase)) return false;
|
||||
@@ -330,6 +371,7 @@ class ERR_GameMode : SCR_BaseGameMode
|
||||
OnPhaseChanged().Insert(m_Objectives.PhaseChanged);
|
||||
OnTick().Insert(m_Objectives.Tick);
|
||||
OnStop().Insert(m_Objectives.Stop);
|
||||
m_ForceSimulation = new ERR_ForceSimulation(m_Forces);
|
||||
m_Command = CreateCommandService();
|
||||
GetGame().GetCallqueue().CallLater(ServerTick, 1000, true);
|
||||
GetGame().GetCallqueue().CallLater(StartScenario, 1500, false);
|
||||
@@ -406,6 +448,7 @@ class ERR_GameMode : SCR_BaseGameMode
|
||||
if (m_Command) m_Command.Maintain(now);
|
||||
// Maintenance can finish or stop the round; do not resume tactical work.
|
||||
if (!Active()) return;
|
||||
TickForces(now);
|
||||
if (GetGame().GetPlayerManager().GetPlayerCount() == 0)
|
||||
{
|
||||
m_NoSurvivorsSince = -1;
|
||||
@@ -469,6 +512,8 @@ class ERR_GameMode : SCR_BaseGameMode
|
||||
GetGame().GetCallqueue().Remove(BindLocalObserver);
|
||||
if (m_Command) m_Command.Stop();
|
||||
m_OnStop.Invoke();
|
||||
m_Forces.Clear();
|
||||
m_ForceAgents.Clear();
|
||||
if (Replication.IsServer())
|
||||
{
|
||||
array<int> connected = {};
|
||||
|
||||
@@ -8,11 +8,12 @@ class ERR_Settings
|
||||
|
||||
static array<string> Keys()
|
||||
{
|
||||
return {"reportIntervalSeconds", "supportSquadLimit", "responseRadiusMetres", "aiIntelChancePercent", "intelRevealChancePercent", "radioDefenceSeconds", "startTimeMinutes", "timeMultiplier", "startDateCode", "weatherProfile"};
|
||||
return {"reportIntervalSeconds", "supportSquadLimit", "responseRadiusMetres", "aiIntelChancePercent", "intelRevealChancePercent", "radioDefenceSeconds", "startTimeMinutes", "timeMultiplier", "startDateCode", "weatherProfile", "stingerChanceOneIn"};
|
||||
}
|
||||
|
||||
static bool Valid(string key, int value)
|
||||
{
|
||||
if (key == "stingerChanceOneIn") return value >= 0 && value <= 10000;
|
||||
if (key == "reportIntervalSeconds") return value >= 5 && value <= 60;
|
||||
if (key == "supportSquadLimit") return value >= 0 && value <= 8;
|
||||
if (key == "responseRadiusMetres") return value >= 500 && value <= 5000;
|
||||
@@ -44,6 +45,7 @@ class ERR_Settings
|
||||
|
||||
static int AddedDefault(string key)
|
||||
{
|
||||
if (key == "stingerChanceOneIn") return 15;
|
||||
if (key == "startTimeMinutes") return 840;
|
||||
if (key == "timeMultiplier") return 1;
|
||||
if (key == "startDateCode" || key == "weatherProfile") return 0;
|
||||
@@ -63,6 +65,7 @@ class ERR_Settings
|
||||
m_Values.Set("timeMultiplier", AddedDefault("timeMultiplier"));
|
||||
m_Values.Set("startDateCode", AddedDefault("startDateCode"));
|
||||
m_Values.Set("weatherProfile", AddedDefault("weatherProfile"));
|
||||
m_Values.Set("stingerChanceOneIn", AddedDefault("stingerChanceOneIn"));
|
||||
if (!Replication.IsServer()) { m_ReadOnly = true; return; }
|
||||
map<string, int> loaded;
|
||||
if (Read(m_Path, loaded) || Read(m_Path + ".bak", loaded)) m_Values = loaded;
|
||||
|
||||
@@ -51,6 +51,14 @@ class ERR_SiteService
|
||||
if (site.Transport) count++;
|
||||
return count;
|
||||
}
|
||||
// Own physical group identity only; command providers do not inspect players.
|
||||
ERR_SiteRuntime FindGroup(SCR_AIGroup group)
|
||||
{
|
||||
if (!group) return null;
|
||||
foreach (ERR_SiteRuntime site : m_Sites)
|
||||
if (site.Group == group) return site;
|
||||
return null;
|
||||
}
|
||||
ERR_SiteRuntime Find(string id)
|
||||
{
|
||||
foreach (ERR_SiteRuntime site : m_Sites)
|
||||
@@ -421,7 +429,20 @@ class ERR_SiteService
|
||||
foreach (ERR_SiteRuntime site : m_Sites)
|
||||
{
|
||||
if (site.Definition.Kind == ERR_LocationKind.PRISON && site.Location) site.Location.Discover(0, 100);
|
||||
if (site.Group) m_Mode.RegisterGroup(site.Group);
|
||||
if (site.Group)
|
||||
{
|
||||
ERR_ForceRoute route;
|
||||
ERR_ForceKind kind = ERR_ForceKind.GARRISON;
|
||||
if (site.Definition.Patrol && !site.Definition.Patrol.IsEmpty())
|
||||
{
|
||||
route = new ERR_ForceRoute(true);
|
||||
vector anchor = site.Definition.Position + site.Definition.GuardOffset;
|
||||
foreach (ERR_SitePatrolPoint point : site.Definition.Patrol) route.Add(anchor + point.Offset);
|
||||
kind = ERR_ForceKind.PATROL;
|
||||
}
|
||||
m_Mode.Forces().Register(site.Definition.Id, kind, site.Group.GetOrigin(), m_Mode.ForceLiving(site.Group), route, site.Group, GetGame().GetWorld().GetWorldTime());
|
||||
m_Mode.RegisterGroup(site.Group);
|
||||
}
|
||||
}
|
||||
}
|
||||
void PhaseChanged(ERR_Phase previous, ERR_Phase current)
|
||||
|
||||
Reference in New Issue
Block a user