Enable finite automatic convoy startup and scoped gunner skill

This commit is contained in:
2026-09-20 08:23:19 +02:00
parent 782c163c68
commit 5bd66d76ca
44 changed files with 3694 additions and 48 deletions
@@ -0,0 +1,42 @@
// A one-shot policy driven by the existing HQ tick, never a spawn/replacement clock.
class ERR_ConvoyStartup
{
protected bool m_Enabled;
protected bool m_Finished;
protected float m_Began = -1;
protected float m_Last = -1;
protected float m_Next;
protected string m_State;
void ERR_ConvoyStartup(bool enabled)
{
m_Enabled = enabled;
m_Finished = !enabled;
m_State = "waiting for active round";
if (!enabled) m_State = "disabled for this round";
}
bool Enabled() { return m_Enabled; }
string Status() { return m_State; }
bool Finished() { return m_Finished; }
bool Decide(float now, bool existingTask, bool admitted)
{
if (m_Finished || !ERR_ForceRules.Finite(now) || now < 0 || now < m_Last) return false;
m_Last = now;
if (existingTask) { m_Finished = true; m_State = "existing canonical convoy retained"; return false; }
if (m_Began < 0) { m_Began = now; m_Next = now + 30000; m_State = "startup grace"; }
if (now < m_Next) return false;
m_Next = now + 5000;
if (now - m_Began >= 120000)
{ m_Finished = true; m_State = "startup admission deadline expired"; return false; }
if (!admitted) { m_State = "waiting for existing force admission"; return false; }
// Latch before native road survey/dispatch: refusals and losses never retry.
m_Finished = true; m_State = "single dispatch attempt";
return true;
}
void Complete(bool accepted)
{
if (m_State != "single dispatch attempt") return;
m_State = "dispatch refused; no automatic retry";
if (accepted) m_State = "canonical convoy dispatched";
}
void Stop() { m_Finished = true; m_State = "round stopped"; }
}
@@ -7,7 +7,7 @@ class ERR_EnemyEquipmentRules
if (!Replication.IsServer() || SCR_Global.IsEditMode() || !mode || mode != ERR_GameMode.Get() || !mode.Active()) return false;
if (!group || !actor || !ERR_Actors.CanFight(actor) || ERR_Actors.Player(actor) || ERR_Actors.Group(actor) != group) return false;
SCR_AIGroupUtilityComponent utility = group.GetGroupUtilityComponent();
return utility && utility.IsMilitary() && group.GetPlayerCount(true) == 0 && group.GetFaction() == mode.EnemyFaction();
return utility && utility.IsMilitary() && group.GetPlayerCount(true) == 0 && group.GetFaction() == mode.EnemyFaction() && !ERR_EnemyGunnerSkill.CapturedGroup(mode, group);
}
static EAISkill Floor(EAISkill skill)
{
@@ -22,12 +22,19 @@ modded class SCR_AICombatComponent
protected ERR_GameMode m_ERRSkillMode;
protected SCR_AIGroup m_ERRSkillGroup;
protected EAISkill m_ERRPreviousSkill;
protected EAISkill m_ERRRequestedSkill;
void ERR_BindEnemySkill(ERR_GameMode mode, SCR_AIGroup group)
{
if (!ERR_EnemyEquipmentRules.Eligible(mode, group, GetOwner())) return;
if (!m_ERRSkillMode) m_ERRPreviousSkill = GetAISkill();
if (!m_ERRSkillMode) { m_ERRPreviousSkill = GetAISkill(); m_ERRRequestedSkill = GetAISkill(); }
m_ERRSkillMode = mode; m_ERRSkillGroup = group;
SetAISkill(GetAISkill());
ERR_RefreshEnemySkill();
}
void ERR_RefreshEnemySkill()
{
if (!m_ERRSkillMode || !ERR_EnemyEquipmentRules.Eligible(m_ERRSkillMode, m_ERRSkillGroup, GetOwner())) return;
EAISkill skill = ERR_EnemyGunnerSkill.Profile(m_ERRRequestedSkill, ERR_EnemyGunnerSkill.OriginalGunner(m_ERRSkillMode, m_ERRSkillGroup, GetOwner()));
if (GetAISkill() != skill) super.SetAISkill(skill);
}
void ERR_ReleaseEnemySkill(ERR_GameMode mode)
{
@@ -37,7 +44,11 @@ modded class SCR_AICombatComponent
}
override void SetAISkill(EAISkill skill)
{
if (m_ERRSkillMode && ERR_EnemyEquipmentRules.Eligible(m_ERRSkillMode, m_ERRSkillGroup, GetOwner())) skill = ERR_EnemyEquipmentRules.Floor(skill);
if (m_ERRSkillMode && ERR_EnemyEquipmentRules.Eligible(m_ERRSkillMode, m_ERRSkillGroup, GetOwner()))
{
m_ERRRequestedSkill = skill;
skill = ERR_EnemyGunnerSkill.Profile(skill, ERR_EnemyGunnerSkill.OriginalGunner(m_ERRSkillMode, m_ERRSkillGroup, GetOwner()));
}
super.SetAISkill(skill);
}
override void ResetAISkill()
@@ -203,6 +214,7 @@ class ERR_EnemyEquipment
entry.Combat = SCR_AICombatComponent.Cast(entry.Actor.FindComponent(SCR_AICombatComponent));
if (entry.Combat) entry.Combat.ERR_BindEnemySkill(m_Mode, entry.Group);
}
if (entry.Combat) entry.Combat.ERR_RefreshEnemySkill();
entry.Check(m_Mode);
if (entry.Optics) entry.Optics.Check(m_Mode);
}
@@ -0,0 +1,48 @@
// Scoped skill specialization for original physical convoy turret occupants.
// Owned extension and inspected vanilla API provenance: docs/ai-equipment-migration.md.
class ERR_EnemyGunnerSkill
{
static bool CapturedGroup(ERR_GameMode mode, SCR_AIGroup group)
{
if (!mode || !group) return false;
ERR_HQ hq = ERR_HQ.Cast(mode.Command());
if (!hq) return false;
ERR_ConvoyTask task = hq.ConvoyTask();
if (!task || task.Vehicles.Count() > 6) return false;
foreach (ERR_ConvoyVehicle unit : task.Vehicles)
if (unit && unit.Group == group && ERR_ConvoyRules.Captured(unit.Entity, group)) return true;
return false;
}
static bool OriginalGunner(ERR_GameMode mode, SCR_AIGroup group, IEntity actor)
{
if (!ERR_EnemyEquipmentRules.Eligible(mode, group, actor)) return false;
ERR_HQ hq = ERR_HQ.Cast(mode.Command());
if (!hq) return false;
ERR_ConvoyTask task = hq.ConvoyTask();
if (!task || !task.Ready || task.Stopped || !task.Receipt || !task.Receipt.Consumed || task.Vehicles.Count() > 6 || !mode.Forces()) return false;
foreach (ERR_ConvoyVehicle unit : task.Vehicles)
{
if (!unit || unit.Group != group || !unit.Registered || unit.Released || !unit.Entity || !ERR_Actors.Alive(unit.Entity)) continue;
if (unit.Index < 0 || unit.Index >= task.Receipt.Records.Count() || unit.Members.Count() > 16 || unit.Members.Count() != unit.Seats.Count()) return false;
ERR_ForceRecord record = mode.Forces().FindGroup(group);
if (!record || !record.Physical || record.Eliminated || record.Kind != ERR_ForceKind.CONVOY || record.Id != task.Receipt.Records[unit.Index]) return false;
if (ERR_ConvoyRules.Captured(unit.Entity, group)) return false;
for (int i = 0; i < unit.Members.Count(); i++)
{
if (unit.Members[i] != actor) continue;
BaseCompartmentSlot slot = unit.Seats[i];
return slot && TurretCompartmentSlot.Cast(slot) && slot.GetType() == ECompartmentType.TURRET
&& slot.GetOccupant() == actor && slot.GetVehicle() == unit.Entity
&& ERR_ConvoyRules.PartOf(CompartmentAccessComponent.GetVehicleIn(actor), unit.Entity);
}
return false;
}
return false;
}
static EAISkill Profile(EAISkill requested, bool originalGunner)
{
EAISkill skill = ERR_EnemyEquipmentRules.Floor(requested);
if (originalGunner && skill < EAISkill.CYLON) return EAISkill.CYLON;
return skill;
}
}
+26 -1
View File
@@ -98,6 +98,7 @@ class ERR_HQ : ERR_CommandService
protected ref ERR_HQWatch m_Watch = new ERR_HQWatch();
protected ref ERR_MortarLogistics m_Logistics;
protected ref ERR_InfantryResupply m_Resupply;
protected ref ERR_ConvoyStartup m_ConvoyStartup;
protected ref array<ref ERR_HQUnit> m_Units = {};
protected ref array<ref ERR_HQReport> m_Contacts = {};
protected ref ERR_HQMemory m_Memory = new ERR_HQMemory();
@@ -111,7 +112,7 @@ class ERR_HQ : ERR_CommandService
protected float m_NextNetwork;
protected float m_NextAssignment;
void ERR_HQ(ERR_GameMode mode) { m_Mode = mode; m_Equipment = new ERR_EnemyEquipment(mode); m_ConvoyPool = new ERR_ConvoyPool(mode, 1); m_Convoys = new ERR_ConvoyService(mode, m_ConvoyPool); m_Mortars = new ERR_MortarSystem(this); m_Logistics = new ERR_MortarLogistics(this, mode); m_Resupply = new ERR_InfantryResupply(this, mode); }
void ERR_HQ(ERR_GameMode mode) { m_Mode = mode; m_Equipment = new ERR_EnemyEquipment(mode); m_ConvoyPool = new ERR_ConvoyPool(mode, 1); m_Convoys = new ERR_ConvoyService(mode, m_ConvoyPool); m_Mortars = new ERR_MortarSystem(this); m_Logistics = new ERR_MortarLogistics(this, mode); m_Resupply = new ERR_InfantryResupply(this, mode); m_ConvoyStartup = new ERR_ConvoyStartup(mode.Settings().Get("automaticConvoyEnabled") == 1); }
static ERR_HQ Get()
{
ERR_GameMode mode = ERR_GameMode.Get();
@@ -128,6 +129,7 @@ class ERR_HQ : ERR_CommandService
ERR_HQ hq = Get();
if (hq) hq.Journal().Write(kind, message, messageId);
}
ERR_ConvoyStartup ConvoyStartup() { return m_ConvoyStartup; }
ERR_ConvoyPool ConvoyPool() { return m_ConvoyPool; }
ERR_ConvoyService Convoys() { return m_Convoys; }
ERR_ConvoyTask ConvoyTask() { return m_ConvoyTask; }
@@ -172,6 +174,26 @@ class ERR_HQ : ERR_CommandService
m_ConvoyFailure = "";
return !m_ConvoyTask.Stopped;
}
protected bool ConvoyAdmissionReady()
{
if (m_Units.IsEmpty() || !m_Mode.Forces()) return false;
foreach (ERR_HQUnit unit : m_Units)
if (unit.Group && !m_Mode.Forces().FindGroup(unit.Group)) return false;
array<ref ERR_MortarBattery> batteries = {}; m_Mortars.Batteries(batteries);
foreach (ERR_MortarBattery battery : batteries)
if (battery && !battery.Stopped() && !battery.StartupActivated) return false;
return true;
}
protected void AutomaticConvoy(float now)
{
if (!Replication.IsServer() || !m_Mode || m_Mode != ERR_GameMode.Get() || !m_Mode.Active()) return;
if (!m_ConvoyStartup || m_ConvoyStartup.Finished()) return;
string previous = m_ConvoyStartup.Status();
if (m_ConvoyStartup.Decide(now, m_ConvoyTask != null, ConvoyAdmissionReady()))
m_ConvoyStartup.Complete(StartScenarioConvoy());
if (previous != m_ConvoyStartup.Status())
m_Journal.Write("CONVOY STARTUP", m_ConvoyStartup.Status() + "; " + m_ConvoyFailure);
}
bool StartScenarioConvoy()
{
if (!m_Mode || !m_Mode.Sites()) return false;
@@ -182,6 +204,7 @@ class ERR_HQ : ERR_CommandService
string ConvoyStatus()
{
string detail = m_Convoys.Status();
if (m_ConvoyStartup) detail += "; automatic startup: " + m_ConvoyStartup.Status();
if (!m_ConvoyFailure.IsEmpty()) detail += "; " + m_ConvoyFailure;
return detail;
}
@@ -979,6 +1002,7 @@ class ERR_HQ : ERR_CommandService
m_Memory.Tick(now);
m_Equipment.Tick(now);
m_Convoys.Tick(now);
AutomaticConvoy(now);
m_Bodies.TickBudget(now);
if (now >= m_NextNetwork) { IndexRadios(); m_NextNetwork = now + 5000; }
for (int i = m_Contacts.Count() - 1; i >= 0; i--)
@@ -1022,6 +1046,7 @@ class ERR_HQ : ERR_CommandService
override void Stop()
{
m_ConvoyStartup.Stop();
m_Resupply.Stop();
m_Convoys.Stop();
m_Equipment.Stop();
@@ -40,8 +40,29 @@ class ERR_HQAdminProvider : ERR_AdminProvider
else if (parts.Count() == 3 && parts[2] == "help") replies.Insert(range + ". " + effect);
else replies.Insert("Invalid value or settings could not be saved; previous setting retained. " + range + ". " + effect);
}
// HQ snapshots this saved switch at round creation. Admin changes never
// dispatch, cancel, refund or retry the current round's canonical convoy.
protected void ConvoyAutoSetting(ERR_GameMode mode, array<string> parts, array<string> replies)
{
string key = "automaticConvoyEnabled";
string effect = "Applies next round only; current startup and manual convoy start unchanged. At most one finite initial convoy; losses are not replaced.";
string range = "0 disabled, 1 enabled (default)";
if (parts.Count() == 4 && parts[3] == "show")
{ replies.Insert(key + "=" + mode.Settings().Get(key).ToString() + " saved for next round. " + effect); return; }
int value;
if (parts.Count() == 5 && parts[3] == "set" && ERR_CommandArguments.Integer(parts[4], value) && mode.Settings().Set(key, value))
replies.Insert(key + "=" + value.ToString() + " saved for next round. " + effect);
else if (parts.Count() == 4 && parts[3] == "help") replies.Insert(range + ". " + effect);
else replies.Insert("Invalid value or settings could not be saved; previous setting retained. " + range + ". " + effect);
}
override bool Execute(ERR_GameMode mode, string arguments, array<string> replies)
{
if (arguments == "hq convoy auto" || arguments.IndexOf("hq convoy auto ") == 0)
{
if (!m_HQ) { replies.Insert("HQ unavailable."); return true; }
array<string> parts = {}; arguments.Split(" ", parts, true);
ConvoyAutoSetting(mode, parts, replies); return true;
}
if (arguments.IndexOf("hq stinger ") == 0)
{
if (!m_HQ) { replies.Insert("HQ unavailable."); return true; }
@@ -71,9 +92,9 @@ class ERR_HQAdminProvider : ERR_AdminProvider
{
if (topic != "hq") return false;
replies.Insert("/esc hq show | hq memory | hq log show|next|set on|off | hq convoy show|start");
replies.Insert("/esc hq interval|squads|radius|stinger show|set N");
replies.Insert("/esc hq interval|squads|radius|stinger show|set N | hq convoy auto show|set 0|1|help");
replies.Insert("Stinger: 0 off, 1 every eligible soldier, 2..10000 one in N; applies only before each actor rolls.");
replies.Insert("HQ assigns available physical squads through delivered reports. Convoy start uses the single finite initial deployment; losses are not replaced.");
replies.Insert("Convoy auto defaults to 1; saves apply next round only. Auto and manual start share one finite initial deployment; losses are not replaced.");
return true;
}
}
@@ -1,4 +1,14 @@
#ifdef WORKBENCH
// The explicit manual-dispatch fixture freezes automatic startup off. Other
// fixtures and ordinary Workbench/game sessions keep the saved/default value.
modded class ERR_Settings
{
override int Get(string key)
{
if (key == "automaticConvoyEnabled" && ERR_NativeConvoyProbe.Enabled()) return 0;
return super.Get(key);
}
}
// Failure diagnostics only. Every query is read-only and uses the production seed/volume rules.
class ERR_NativeConvoyRoadSurvey
{
@@ -87,6 +87,8 @@ class ERR_NativeHQMemoryProbe
protected float m_Deadline;
protected bool m_Failed;
protected bool m_ValueChecked;
protected bool m_AutoChecked;
protected float m_AutoReadyAt = -1;
protected int m_RouteRetries;
static bool Enabled()
@@ -167,14 +169,66 @@ class ERR_NativeHQMemoryProbe
}
return removed > 0;
}
protected int AutomaticConvoy(ERR_HQ hq)
{
if (m_AutoChecked) return 1;
if (!hq.ConvoyStartup() || !hq.ConvoyStartup().Enabled())
{ Check(false, "automatic convoy fixture requires enabled round snapshot"); return -1; }
ERR_ConvoyTask task = hq.ConvoyTask();
if (!task)
{
if (hq.ConvoyStartup().Finished()) { Check(false, "automatic convoy refused: " + hq.ConvoyStatus()); return -1; }
return 0;
}
if (task.Stopped) { Check(false, "automatic convoy stopped: " + task.Reason); return -1; }
if (!task.Ready) return 0;
float now = GetGame().GetWorld().GetWorldTime();
if (m_AutoReadyAt < 0) m_AutoReadyAt = now;
if (now - m_AutoReadyAt < 6000) return 0;
if (!Check(hq.ConvoyStartup().Status() == "canonical convoy dispatched" && task.Receipt && task.Receipt.Consumed
&& task.Vehicles.Count() == 6 && hq.Convoys().Count() == 1, "automatic startup owns one canonical six-vehicle task")) return -1;
int crew;
int gunners;
foreach (ERR_ConvoyVehicle vehicle : task.Vehicles)
{
if (!Check(vehicle.Registered && !vehicle.Released && vehicle.CrewValid(m_Mode)
&& vehicle.Members.Count() == ERR_ConvoyPool.CrewCount(vehicle.Index), "automatic convoy exact original seated crew " + vehicle.Index.ToString())) return -1;
crew += vehicle.Members.Count();
for (int member = 0; member < vehicle.Members.Count(); member++)
{
IEntity actor = vehicle.Members[member];
SCR_AICombatComponent combat = SCR_AICombatComponent.Cast(actor.FindComponent(SCR_AICombatComponent));
if (!Check(combat && combat.GetAISkill() >= EAISkill.EXPERT, "original automatic crew has scoped expert floor")) return -1;
BaseCompartmentSlot slot = vehicle.Seats[member];
if (slot && slot.GetType() == ECompartmentType.TURRET)
{
if (!Check(ERR_EnemyGunnerSkill.OriginalGunner(m_Mode, vehicle.Group, actor) && combat.GetAISkill() >= EAISkill.CYLON,
"original native turret gunner has scoped skill100")) return -1;
gunners++;
}
}
}
if (!Check(gunners > 0, "automatic convoy includes confirmed native turret gunners")) return -1;
if (!Check(crew == 19 && task.Receipt.OutstandingBodies == 0 && task.Receipt.OutstandingGroups == 0
&& hq.ConvoyPool().CommittedBodies() <= 60, "automatic nineteen-body convoy respects shared physical budget")) return -1;
if (!Check(!hq.StartScenarioConvoy() && hq.ConvoyTask() == task && hq.Convoys().Count() == 1,
"manual request after automatic startup cannot duplicate the convoy")) return -1;
m_AutoChecked = true; return 1;
}
int Tick(ERR_HQ hq, float now)
{
if (!Enabled() || !hq || !m_Mode || m_Failed) return -1;
if (m_Deadline == 0) m_Deadline = now + 120000;
if (m_Deadline == 0) m_Deadline = now + 150000;
if (now > m_Deadline) { Check(false, "fixture deadline"); return -1; }
if (m_Step == 0)
{
if (!hq.ERR_ProbeSquadsReady()) return 0;
bool automaticWasChecked = m_AutoChecked;
int automatic = AutomaticConvoy(hq);
if (automatic <= 0) return automatic;
// Startup admission and native crew readiness have their own bounded wait.
// Start the original memory/radio deadline once, after automatic proof.
if (!automaticWasChecked) m_Deadline = now + 120000;
if (!m_ValueChecked)
{
if (!Check(hq.ERR_MemoryFixtureRegisteredForces() == 6,
@@ -79,7 +79,8 @@ modded class ERR_GameMode
m_ERRSmokeDeadline = GetWorld().GetWorldTime() + 150000;
if (m_ERRTestConvoy) m_ERRSmokeDeadline = GetWorld().GetWorldTime() + 540000;
if (ERR_NativeConvoyCoverProbe.Enabled()) m_ERRSmokeDeadline = GetWorld().GetWorldTime() + 840000;
if (m_ERRTestHQMemory || m_ERRTestHQBody) m_ERRSmokeDeadline = GetWorld().GetWorldTime() + 240000;
if (m_ERRTestHQMemory) m_ERRSmokeDeadline = GetWorld().GetWorldTime() + 360000;
if (m_ERRTestHQBody) m_ERRSmokeDeadline = GetWorld().GetWorldTime() + 240000;
if (m_ERRTestAircraftControl || m_ERRTestAircraftRoute || m_ERRTestAircraftField) m_ERRSmokeDeadline = GetWorld().GetWorldTime() + 330000;
if (m_ERRTestMortarCourier) m_ERRSmokeDeadline = GetWorld().GetWorldTime() + 450000;
if (m_ERRTestMortarTransfer) m_ERRSmokeDeadline = GetWorld().GetWorldTime() + 330000;
@@ -176,7 +176,12 @@ class ERR_AdminService
if (!mode.Settings()) { replies.Insert("Mission settings are not ready."); return; }
if (arguments == "settings")
{
foreach (string key : ERR_Settings.Keys()) replies.Insert(key + "=" + mode.Settings().Get(key).ToString());
foreach (string key : ERR_Settings.Keys())
{
string line = key + "=" + mode.Settings().Get(key).ToString();
if (key == "automaticConvoyEnabled") line += " (saved for next round; current startup unchanged)";
replies.Insert(line);
}
return;
}
if (Extension(mode, arguments, replies)) return;
@@ -33,10 +33,19 @@ class ERR_EscapeGameOverScreenInfo : SCR_BaseGameOverScreenInfo
modded class SCR_GameOverScreenContentUIComponent
{
protected bool m_ERREscapeContent;
protected ERR_GameMode m_ERRFailureMode;
override void InitContent(SCR_GameOverScreenUIContentData endScreenUIContent)
{
SCR_PlayerController.ERR_OnEscapeResultChanged().Remove(ERR_RefreshEscapeContent);
if (m_ERRFailureMode) m_ERRFailureMode.OnEndReasonChanged().Remove(ERR_RefreshFailureContent);
m_ERRFailureMode = null;
super.InitContent(endScreenUIContent);
if (ERR_FailureGameOverScreenInfo.Cast(endScreenUIContent.m_GameOverInfo))
{
m_ERRFailureMode = ERR_GameMode.Get();
if (m_ERRFailureMode) m_ERRFailureMode.OnEndReasonChanged().Insert(ERR_RefreshFailureContent);
ERR_RefreshFailureContent();
}
m_ERREscapeContent = ERR_EscapeGameOverScreenInfo.Cast(endScreenUIContent.m_GameOverInfo) != null;
if (!m_ERREscapeContent) return;
SCR_PlayerController.ERR_OnEscapeResultChanged().Insert(ERR_RefreshEscapeContent);
@@ -54,10 +63,20 @@ modded class SCR_GameOverScreenContentUIComponent
if (title) title.SetText(info.GetTitle(playerId, null));
if (debrief) debrief.SetText(info.GetDebriefing(playerId, null));
}
// Native end data can open the defeat screen before the mission reason arrives.
protected void ERR_RefreshFailureContent()
{
if (!m_ERRFailureMode || m_ERRFailureMode != ERR_GameMode.Get() || !m_wRoot) return;
ERR_FailureGameOverScreenInfo info = new ERR_FailureGameOverScreenInfo();
TextWidget debrief = TextWidget.Cast(m_wRoot.FindAnyWidget(m_sDebriefingName));
if (debrief) debrief.SetText(info.GetDebriefing(-1, null));
}
override void HandlerDeattached(Widget w)
{
SCR_PlayerController.ERR_OnEscapeResultChanged().Remove(ERR_RefreshEscapeContent);
m_ERREscapeContent = false;
if (m_ERRFailureMode) m_ERRFailureMode.OnEndReasonChanged().Remove(ERR_RefreshFailureContent);
m_ERRFailureMode = null;
super.HandlerDeattached(w);
}
}
+4 -1
View File
@@ -30,7 +30,7 @@ class ERR_GameMode : SCR_BaseGameMode
protected ref array<ERR_LocationComponent> m_Locations = {};
protected ref array<IEntity> m_OwnedEntities = {};
protected ref ERR_ExtractionService m_Extraction;
[RplProp()]
[RplProp(onRplName: "ERR_OnEndReasonReplicated")]
protected string m_EndReason;
protected ref ERR_RadioDefenceService m_RadioDefence;
[RplProp()]
@@ -44,6 +44,7 @@ class ERR_GameMode : SCR_BaseGameMode
protected bool m_Stopped;
protected bool m_NativeEndHandled;
protected float m_NoSurvivorsSince = -1;
protected ref ScriptInvoker m_OnEndReasonChanged = new ScriptInvoker();
protected ref ScriptInvoker m_OnPhaseChanged = new ScriptInvoker();
protected ref ScriptInvoker m_OnTick = new ScriptInvoker();
protected ref ScriptInvoker m_OnStop = new ScriptInvoker();
@@ -71,6 +72,8 @@ class ERR_GameMode : SCR_BaseGameMode
ERR_Objectives Objectives() { return m_Objectives; }
ERR_ExtractionService Extraction() { return m_Extraction; }
string EndReason() { return m_EndReason; }
ScriptInvoker OnEndReasonChanged() { return m_OnEndReasonChanged; }
protected void ERR_OnEndReasonReplicated() { m_OnEndReasonChanged.Invoke(); }
Vehicle ExtractionVehicle()
{
if (!m_Extraction) return null;
+4 -1
View File
@@ -8,11 +8,12 @@ class ERR_Settings
static array<string> Keys()
{
return {"reportIntervalSeconds", "supportSquadLimit", "responseRadiusMetres", "aiIntelChancePercent", "intelRevealChancePercent", "radioDefenceSeconds", "startTimeMinutes", "timeMultiplier", "startDateCode", "weatherProfile", "stingerChanceOneIn"};
return {"reportIntervalSeconds", "supportSquadLimit", "responseRadiusMetres", "aiIntelChancePercent", "intelRevealChancePercent", "radioDefenceSeconds", "startTimeMinutes", "timeMultiplier", "startDateCode", "weatherProfile", "stingerChanceOneIn", "automaticConvoyEnabled"};
}
static bool Valid(string key, int value)
{
if (key == "automaticConvoyEnabled") return value == 0 || value == 1;
if (key == "stingerChanceOneIn") return value >= 0 && value <= 10000;
if (key == "reportIntervalSeconds") return value >= 5 && value <= 60;
if (key == "supportSquadLimit") return value >= 0 && value <= 8;
@@ -45,6 +46,7 @@ class ERR_Settings
static int AddedDefault(string key)
{
if (key == "automaticConvoyEnabled") return 1;
if (key == "stingerChanceOneIn") return 15;
if (key == "startTimeMinutes") return 840;
if (key == "timeMultiplier") return 1;
@@ -66,6 +68,7 @@ class ERR_Settings
m_Values.Set("startDateCode", AddedDefault("startDateCode"));
m_Values.Set("weatherProfile", AddedDefault("weatherProfile"));
m_Values.Set("stingerChanceOneIn", AddedDefault("stingerChanceOneIn"));
m_Values.Set("automaticConvoyEnabled", AddedDefault("automaticConvoyEnabled"));
if (!Replication.IsServer()) { m_ReadOnly = true; return; }
map<string, int> loaded;
if (Read(m_Path, loaded) || Read(m_Path + ".bak", loaded)) m_Values = loaded;