Enable finite automatic convoy startup and scoped gunner skill
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user