Decouple base configuration and administration from companions

This commit is contained in:
2026-09-19 03:15:33 +02:00
parent 35daefa860
commit ce19ec68df
96 changed files with 9200 additions and 171 deletions
+35 -114
View File
@@ -1,116 +1,37 @@
class ERR_CommandArguments
// Companion-specific commands extend Core's authenticated transport.
modded class ERR_AdminService
{
static bool Integer(string text, out int value)
{
if (text.IsEmpty() || text.Length() > 6) return false;
for (int i = 0; i < text.Length(); i++)
if ("0123456789".IndexOf(text.Substring(i, 1)) < 0) return false;
value = text.ToInt();
return true;
}
static string Setting(string section, string option)
{
if (section == "hq")
{
if (option == "interval") return "reportIntervalSeconds";
if (option == "squads") return "supportSquadLimit";
if (option == "radius") return "responseRadiusMetres";
}
if (section == "intel")
{
if (option == "carrying") return "aiIntelChancePercent";
if (option == "reveal") return "intelRevealChancePercent";
}
return "";
}
}
modded class SCR_PlayerController
{
protected ChatCommandInvoker m_ERRCommand;
protected float m_ERRLastRequest = -1000;
protected int m_ERRRegisterAttempts;
override void OnControlledEntityChanged(IEntity from, IEntity to)
{
super.OnControlledEntityChanged(from, to);
if (this == GetGame().GetPlayerController() && ERR_GameMode.Get()) ERR_Register();
}
protected void ERR_Register()
{
if (m_ERRCommand || m_ERRRegisterAttempts++ > 40) return;
SCR_ChatPanelManager manager = SCR_ChatPanelManager.GetInstance();
if (!manager) { GetGame().GetCallqueue().CallLater(ERR_Register, 250, false); return; }
m_ERRCommand = manager.GetCommandInvoker("esc");
if (m_ERRCommand) m_ERRCommand.Insert(ERR_OnCommand);
}
protected override void ERR_OnObserverMonitorStarted()
{
super.ERR_OnObserverMonitorStarted();
ERR_Register();
}
protected void ERR_OnCommand(SCR_ChatPanel panel, string arguments)
{
string localCommand = arguments.Trim(); localCommand.ToLower();
if (localCommand == "spectate next" || localCommand == "spectate previous")
{
int direction = 1;
if (localCommand == "spectate previous") direction = -1;
ERR_CycleObserver(direction);
return;
}
Rpc(ERR_Request, arguments);
}
[RplRpc(RplChannel.Reliable, RplRcver.Server)]
protected void ERR_Request(string arguments)
{
ERR_GameMode mode = ERR_GameMode.Get();
if (!Replication.IsServer() || !mode || !SCR_Global.IsAdmin(GetPlayerId())) return;
float now = GetGame().GetWorld().GetWorldTime();
if (arguments.Length() > 160 || now - m_ERRLastRequest < 250) return;
m_ERRLastRequest = now;
arguments = arguments.Trim(); arguments.ToLower();
ERR_HQ hq = ERR_HQ.Cast(mode.Command());
if (arguments == "hq show" || arguments == "status")
{
string status = "HQ unavailable";
if (hq) status = hq.Status();
ERR_Reply(string.Format("Mission phase=%1; %2", mode.Phase(), status)); return;
}
if (arguments == "hq log")
{
if (!hq) { ERR_Reply("HQ unavailable"); return; }
array<string> lines = {}; int cursor;
hq.Journal().Read(Math.Max(0, hq.Journal().Sequence() - 8), lines, cursor);
foreach (string line : lines) ERR_Reply(line);
return;
}
array<string> parts = {};
arguments.Split(" ", parts, true);
if (parts.Count() == 3 || parts.Count() == 4)
{
string key = ERR_CommandArguments.Setting(parts[0], parts[1]);
if (!key.IsEmpty())
{
if (parts.Count() == 3 && parts[2] == "show") { ERR_Reply(key + "=" + mode.Settings().Get(key).ToString()); return; }
int value;
if (parts.Count() == 4 && parts[2] == "set" && ERR_CommandArguments.Integer(parts[3], value) && mode.Settings().Set(key, value))
ERR_Reply(key + "=" + value.ToString() + " saved");
else ERR_Reply("Invalid value or settings could not be saved; previous setting retained.");
return;
}
}
ERR_Reply("/esc status | hq show | hq log | hq interval|squads|radius show|set N | intel carrying|reveal show|set N");
}
protected void ERR_Reply(string text) { Rpc(ERR_Response, text); }
[RplRpc(RplChannel.Reliable, RplRcver.Owner)]
protected void ERR_Response(string text) { SCR_ChatComponent.RadioProtocolMessage(text); }
void ~SCR_PlayerController()
{
if (GetGame()) GetGame().GetCallqueue().Remove(ERR_Register);
if (m_ERRCommand) m_ERRCommand.Remove(ERR_OnCommand);
}
protected override string Setting(string section, string option)
{
if (section == "hq")
{
if (option == "interval") return "reportIntervalSeconds";
if (option == "squads") return "supportSquadLimit";
if (option == "radius") return "responseRadiusMetres";
}
if (section == "intel")
{
if (option == "carrying") return "aiIntelChancePercent";
if (option == "reveal") return "intelRevealChancePercent";
}
return super.Setting(section, option);
}
protected override bool Extension(ERR_GameMode mode, string arguments, array<string> replies)
{
if (arguments != "status" && arguments != "hq show" && arguments != "hq log") return super.Extension(mode, arguments, replies);
ERR_HQ hq = ERR_HQ.Cast(mode.Command());
if (!hq) { replies.Insert("HQ unavailable"); return true; }
if (arguments == "hq show" || arguments == "status") replies.Insert(hq.Status());
else
{
int cursor;
hq.Journal().Read(Math.Max(0, hq.Journal().Sequence() - 8), replies, cursor);
if (replies.IsEmpty()) replies.Insert("HQ journal is empty.");
}
return true;
}
protected override string Help()
{
return super.Help() + " | hq show|log | hq interval|squads|radius show|set N | intel carrying|reveal show|set N";
}
}
+19 -2
View File
@@ -104,6 +104,23 @@ modded class ERR_GameMode
if (!ERR_Check(roster.Bind(101, second) && roster.CanEnter(101, ERR_Phase.RADIO_DEFENCE) && !roster.CanEnter(101, ERR_Phase.EXTRACTION), "recycled connection receives independent life and extraction admission policy")) return false;
return true;
}
protected bool ERR_CheckCompanionCommands()
{
array<string> replies = {};
array<string> commands = {"hq interval show", "hq squads show", "hq radius show", "intel carrying show", "intel reveal show"};
array<string> keys = {"reportIntervalSeconds", "supportSquadLimit", "responseRadiusMetres", "aiIntelChancePercent", "intelRevealChancePercent"};
for (int i = 0; i < commands.Count(); i++)
{
Admin().Execute(this, true, commands[i], replies);
if (!ERR_Check(replies.Count() == 1 && replies[0] == keys[i] + "=" + Settings().Get(keys[i]).ToString(), "companion command retained: " + commands[i])) return false;
}
Admin().Execute(this, false, "hq log", replies);
if (!ERR_Check(replies.Count() == 1 && replies[0] == "Administrator access required.", "companion journal is behind Core admin authorization")) return false;
Admin().Execute(this, true, "hq show", replies);
ERR_HQ hq = ERR_HQ.Cast(Command());
if (!ERR_Check(hq && replies.Count() == 1 && replies[0] == hq.Status(), "companion HQ status uses the shared command service")) return false;
return true;
}
protected void ERR_PrisonScreenshot() { System.MakeScreenshot("$logs:err-prison"); }
protected void ERR_EndScreenshot() { System.MakeScreenshot("$logs:err-result"); }
protected void ERR_SmokeTick()
@@ -118,7 +135,7 @@ modded class ERR_GameMode
ERR_Actors.Players(players, true);
if (players.Count() != 1 || Phase() != ERR_Phase.PRISON) return;
m_ERRSmokePlayer = players[0];
if (!ERR_CheckRoster()) return;
if (!ERR_CheckRoster() || !ERR_NativeContractsProbe.Run(this, m_ERRSmokePlayer) || !ERR_CheckCompanionCommands()) return;
if (m_ERRTestMortar)
{
DamageManagerComponent.Cast(m_ERRSmokePlayer.FindComponent(DamageManagerComponent)).EnableDamageHandling(false);
@@ -176,7 +193,7 @@ modded class ERR_GameMode
if (!ERR_Check(Phase() == ERR_Phase.RADIO_SEARCH, "lock action advances mission")) return;
foreach (IEntity tracked : m_OwnedEntities)
{
if (tracked && tracked.GetPrefabData() && tracked.GetPrefabData().GetPrefabName() == ERR_FactionCatalog.ObjectiveDocument()) m_ERRCacheFolders.Insert(tracked);
if (tracked && tracked.GetPrefabData() && tracked.GetPrefabData().GetPrefabName() == ERR_Resources.ObjectiveDocument()) m_ERRCacheFolders.Insert(tracked);
}
if (!ERR_Check(m_ERRCacheFolders.Count() == 2, "two guaranteed physical cache documents")) return;
if (!ERR_Check(m_Settings.Set("aiIntelChancePercent", 0), "test does not rely on random intel")) return;