Decouple base configuration and administration from companions
This commit is contained in:
@@ -5,7 +5,7 @@ ERR_ScenarioDefinition {
|
||||
Kind 0
|
||||
Prefab "{BE6B8D56349038A6}Prefabs/ERR/Prison.et"
|
||||
Position 6090 0 7160
|
||||
Guards "{D5A4D0D3AE1A3E26}Prefabs/ERR/Patrol.et"
|
||||
Guarded 1
|
||||
GuardOffset 35 0 0
|
||||
Container "{40986810D0B1F4AE}Prefabs/ERR/SupplyCache.et"
|
||||
ContainerOffset -4 0 0
|
||||
@@ -20,7 +20,7 @@ ERR_ScenarioDefinition {
|
||||
Kind 4
|
||||
Prefab "{7C3D16C17C24D351}Prefabs/ERR/SupplySite.et"
|
||||
Position 6150 0 7050
|
||||
Guards "{D5A4D0D3AE1A3E26}Prefabs/ERR/Patrol.et"
|
||||
Guarded 1
|
||||
GuardOffset 18 0 0
|
||||
Container "{40986810D0B1F4AE}Prefabs/ERR/SupplyCache.et"
|
||||
ContainerOffset 0 0 0
|
||||
@@ -37,7 +37,7 @@ ERR_ScenarioDefinition {
|
||||
Kind 1
|
||||
Prefab "{F7E7E3D6ACF0C860}Prefabs/ERR/Radio.et"
|
||||
Position 6500 0 7200
|
||||
Guards "{D5A4D0D3AE1A3E26}Prefabs/ERR/Patrol.et"
|
||||
Guarded 1
|
||||
GuardOffset 30 0 0
|
||||
}
|
||||
ERR_SiteDefinition {
|
||||
@@ -45,7 +45,7 @@ ERR_ScenarioDefinition {
|
||||
Kind 2
|
||||
Prefab "{033503110AC34EAC}Prefabs/ERR/Extraction.et"
|
||||
Position 7000 0 7500
|
||||
Guards "{D5A4D0D3AE1A3E26}Prefabs/ERR/Patrol.et"
|
||||
Guarded 1
|
||||
GuardOffset 30 0 0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
ERR_FactionCatalog {
|
||||
PlayerFactionKey "US"
|
||||
EnemyFactionKey "USSR"
|
||||
PrisonerPrefab "{2F912ED6E399FF47}Prefabs/Characters/Factions/BLUFOR/US_Army/Character_US_Unarmed.et"
|
||||
GuardGroupPrefab "{D5A4D0D3AE1A3E26}Prefabs/ERR/Patrol.et"
|
||||
ExtractionPrefab "{DF5CCB7C0FF049F4}Prefabs/Vehicles/Helicopters/Mi8MT/Mi8MT_unarmed_transport.et"
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
MetaFileClass {
|
||||
Name "{BE50B97B8E2B36E6}Configs/ERR/VanillaFactions.conf"
|
||||
Configurations {
|
||||
ResourceClass PC {
|
||||
}
|
||||
ResourceClass HEADLESS : PC {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
// Transport-independent administration. Only the owning controller supplies
|
||||
// authorization, derived on the server from its authenticated player ID.
|
||||
class ERR_CommandArguments
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
class ERR_AdminService
|
||||
{
|
||||
protected string Setting(string section, string option)
|
||||
{
|
||||
if (section == "mission" && option == "radio") return "radioDefenceSeconds";
|
||||
return "";
|
||||
}
|
||||
protected bool Extension(ERR_GameMode mode, string arguments, array<string> replies) { return false; }
|
||||
protected string Help()
|
||||
{
|
||||
return "/esc status | settings | mission radio show|set N | spectate next|previous";
|
||||
}
|
||||
void Execute(ERR_GameMode mode, bool authorized, string arguments, array<string> replies)
|
||||
{
|
||||
replies.Clear();
|
||||
if (!Replication.IsServer() || !mode) return;
|
||||
if (!authorized) { replies.Insert("Administrator access required."); return; }
|
||||
if (arguments.Length() > 160) { replies.Insert("Command is too long."); return; }
|
||||
arguments = arguments.Trim(); arguments.ToLower();
|
||||
if (arguments == "status") { replies.Insert(mode.Status()); Extension(mode, arguments, replies); return; }
|
||||
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());
|
||||
return;
|
||||
}
|
||||
if (Extension(mode, arguments, replies)) return;
|
||||
array<string> parts = {};
|
||||
arguments.Split(" ", parts, true);
|
||||
if (parts.Count() == 3 || parts.Count() == 4)
|
||||
{
|
||||
string key = Setting(parts[0], parts[1]);
|
||||
if (!key.IsEmpty())
|
||||
{
|
||||
if (parts.Count() == 3 && parts[2] == "show") { replies.Insert(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))
|
||||
replies.Insert(key + "=" + value.ToString() + " saved");
|
||||
else replies.Insert("Invalid value or settings could not be saved; previous setting retained.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
replies.Insert(Help());
|
||||
}
|
||||
}
|
||||
@@ -9,10 +9,10 @@ class ERR_DiscoveryService
|
||||
{
|
||||
folders.Clear();
|
||||
SCR_PrefabNamePredicate predicate = new SCR_PrefabNamePredicate();
|
||||
predicate.prefabName = ERR_FactionCatalog.IntelFolder();
|
||||
predicate.prefabName = ERR_Resources.IntelFolder();
|
||||
inventory.FindItems(folders, predicate, EStoragePurpose.PURPOSE_ANY);
|
||||
array<IEntity> objectives = {};
|
||||
predicate.prefabName = ERR_FactionCatalog.ObjectiveDocument();
|
||||
predicate.prefabName = ERR_Resources.ObjectiveDocument();
|
||||
inventory.FindItems(objectives, predicate, EStoragePurpose.PURPOSE_ANY);
|
||||
foreach (IEntity objective : objectives)
|
||||
if (!folders.Contains(objective)) folders.Insert(objective);
|
||||
|
||||
@@ -1,11 +1,42 @@
|
||||
// Vanilla-only catalog. Optional factions will supply separate catalogs.
|
||||
// One authored faction/asset contract, supplied by the game mode resource.
|
||||
[BaseContainerProps()]
|
||||
class ERR_FactionCatalog
|
||||
{
|
||||
static ResourceName ObjectiveDocument() { return "{554159A6740C3F3E}Prefabs/ERR/ObjectiveDocument.et"; }
|
||||
static ResourceName Prisoner() { return "{2F912ED6E399FF47}Prefabs/Characters/Factions/BLUFOR/US_Army/Character_US_Unarmed.et"; }
|
||||
static ResourceName Patrol() { return "{D5A4D0D3AE1A3E26}Prefabs/ERR/Patrol.et"; }
|
||||
static ResourceName Helicopter() { return "{DF5CCB7C0FF049F4}Prefabs/Vehicles/Helicopters/Mi8MT/Mi8MT_unarmed_transport.et"; }
|
||||
static ResourceName SearchWaypoint() { return "{B3E7B8DC2BAB8ACC}Prefabs/AI/Waypoints/AIWaypoint_SearchAndDestroy.et"; }
|
||||
static ResourceName DefendWaypoint() { return "{93291E72AC23930F}Prefabs/AI/Waypoints/AIWaypoint_Defend.et"; }
|
||||
static ResourceName IntelFolder() { return "{6D56FED1E55A8F84}Prefabs/Items/Misc/IntelligenceFolder_E_01/IntelligenceFolder_E_01.et"; }
|
||||
[Attribute()] string PlayerFactionKey;
|
||||
[Attribute()] string EnemyFactionKey;
|
||||
[Attribute("", UIWidgets.ResourceNamePicker, "Unarmed prisoner character", "et")]
|
||||
ResourceName PrisonerPrefab;
|
||||
[Attribute("", UIWidgets.ResourceNamePicker, "Initial physical guard squad", "et")]
|
||||
ResourceName GuardGroupPrefab;
|
||||
[Attribute("", UIWidgets.ResourceNamePicker, "Extraction helicopter", "et")]
|
||||
ResourceName ExtractionPrefab;
|
||||
|
||||
bool Valid()
|
||||
{
|
||||
return !PlayerFactionKey.IsEmpty() && !EnemyFactionKey.IsEmpty() && PlayerFactionKey != EnemyFactionKey && !PrisonerPrefab.IsEmpty() && !GuardGroupPrefab.IsEmpty() && !ExtractionPrefab.IsEmpty();
|
||||
}
|
||||
Faction PlayerFaction()
|
||||
{
|
||||
if (!GetGame().GetFactionManager()) return null;
|
||||
return GetGame().GetFactionManager().GetFactionByKey(PlayerFactionKey);
|
||||
}
|
||||
Faction EnemyFaction()
|
||||
{
|
||||
if (!GetGame().GetFactionManager()) return null;
|
||||
return GetGame().GetFactionManager().GetFactionByKey(EnemyFactionKey);
|
||||
}
|
||||
bool WorldReady()
|
||||
{
|
||||
if (!Valid()) return false;
|
||||
Faction player = PlayerFaction();
|
||||
Faction enemy = EnemyFaction();
|
||||
if (!player || !enemy || !player.IsFactionEnemy(enemy) || !enemy.IsFactionEnemy(player)) return false;
|
||||
array<ResourceName> required = {PrisonerPrefab, GuardGroupPrefab, ExtractionPrefab};
|
||||
foreach (ResourceName prefab : required)
|
||||
{
|
||||
Resource resource = Resource.Load(prefab);
|
||||
if (!resource || !resource.IsValid()) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,10 +11,16 @@ class ERR_GameMode : SCR_BaseGameMode
|
||||
protected ERR_Phase m_Phase;
|
||||
[RplProp()]
|
||||
protected float m_DefenceSince = -1;
|
||||
[Attribute("{BE50B97B8E2B36E6}Configs/ERR/VanillaFactions.conf", UIWidgets.ResourceNamePicker, "Faction and asset catalog", "conf")]
|
||||
protected ResourceName m_FactionDefinition;
|
||||
[RplProp()]
|
||||
protected string m_PreparationError;
|
||||
protected ref ERR_FactionCatalog m_Factions;
|
||||
protected ref ERR_Settings m_Settings;
|
||||
protected ref ERR_DiscoveryService m_Discovery;
|
||||
protected ref ERR_Objectives m_Objectives;
|
||||
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 array<ERR_LocationComponent> m_Locations = {};
|
||||
protected ref array<IEntity> m_OwnedEntities = {};
|
||||
@@ -37,6 +43,7 @@ class ERR_GameMode : SCR_BaseGameMode
|
||||
}
|
||||
ERR_Phase Phase() { return m_Phase; }
|
||||
ERR_PlayerRoster Players() { return m_Roster; }
|
||||
ERR_AdminService Admin() { return m_Admin; }
|
||||
ERR_Settings Settings() { return m_Settings; }
|
||||
ERR_CommandService Command() { return m_Command; }
|
||||
ERR_Objectives Objectives() { return m_Objectives; }
|
||||
@@ -53,7 +60,39 @@ class ERR_GameMode : SCR_BaseGameMode
|
||||
ScriptInvoker OnTick() { return m_OnTick; }
|
||||
ScriptInvoker OnStop() { return m_OnStop; }
|
||||
ScriptInvoker OnGroup() { return m_OnGroup; }
|
||||
Faction EnemyFaction() { return GetGame().GetFactionManager().GetFactionByKey("USSR"); }
|
||||
ERR_FactionCatalog Factions() { return m_Factions; }
|
||||
Faction PlayerFaction()
|
||||
{
|
||||
if (!m_Factions) return null;
|
||||
return m_Factions.PlayerFaction();
|
||||
}
|
||||
Faction EnemyFaction()
|
||||
{
|
||||
if (!m_Factions) return null;
|
||||
return m_Factions.EnemyFaction();
|
||||
}
|
||||
string PreparationError() { return m_PreparationError; }
|
||||
string Status()
|
||||
{
|
||||
if (!m_PreparationError.IsEmpty()) return "Mission startup failed: " + m_PreparationError;
|
||||
if (m_Phase == ERR_Phase.PREPARING) return "Preparing mission sites and physical supplies.";
|
||||
string phase;
|
||||
if (m_Phase == ERR_Phase.PRISON) phase = "Escape the prison";
|
||||
else if (m_Phase == ERR_Phase.RADIO_SEARCH) phase = "Locate the radio station";
|
||||
else if (m_Phase == ERR_Phase.RADIO_DEFENCE) phase = "Defend the radio transmission";
|
||||
else if (m_Phase == ERR_Phase.EXTRACTION) phase = "Helicopter extraction";
|
||||
else if (m_Phase == ERR_Phase.VICTORY) phase = "Escape complete";
|
||||
else phase = "Mission lost";
|
||||
return phase;
|
||||
}
|
||||
protected void FailPreparation(string detail)
|
||||
{
|
||||
if (!Replication.IsServer() || m_Phase != ERR_Phase.PREPARING || !m_PreparationError.IsEmpty()) return;
|
||||
m_PreparationError = detail;
|
||||
Replication.BumpMe();
|
||||
Print("Rereintroduced: scenario preparation failed; spawning remains disabled: " + detail, LogLevel.ERROR);
|
||||
StopServices();
|
||||
}
|
||||
vector PrisonPosition() { return ERR_Actors.Ground(m_PrisonPosition); }
|
||||
bool Active() { return m_Phase >= ERR_Phase.PRISON && !ERR_MissionRules.Terminal(m_Phase); }
|
||||
protected ERR_CommandService CreateCommandService() { return null; }
|
||||
@@ -98,7 +137,7 @@ class ERR_GameMode : SCR_BaseGameMode
|
||||
|
||||
bool Transition(ERR_Phase phase)
|
||||
{
|
||||
if (!Replication.IsServer() || !ERR_MissionRules.CanTransition(m_Phase, phase)) return false;
|
||||
if (!Replication.IsServer() || !m_PreparationError.IsEmpty() || !ERR_MissionRules.CanTransition(m_Phase, phase)) return false;
|
||||
ERR_Phase previous = m_Phase;
|
||||
m_Phase = phase;
|
||||
Replication.BumpMe();
|
||||
@@ -146,6 +185,7 @@ class ERR_GameMode : SCR_BaseGameMode
|
||||
{
|
||||
super.EOnInit(owner);
|
||||
if (SCR_Global.IsEditMode()) return;
|
||||
m_Factions = ERR_FactionCatalog.Cast(SCR_BaseContainerTools.CreateInstanceFromPrefab(m_FactionDefinition));
|
||||
if (!System.IsConsoleApp()) GetGame().GetCallqueue().CallLater(BindLocalObserver, 250, true);
|
||||
if (!Replication.IsServer()) return;
|
||||
m_Settings = new ERR_Settings();
|
||||
@@ -171,7 +211,8 @@ class ERR_GameMode : SCR_BaseGameMode
|
||||
|
||||
protected void StartScenario()
|
||||
{
|
||||
if (m_Phase != ERR_Phase.PREPARING) return;
|
||||
if (m_Phase != ERR_Phase.PREPARING || !m_PreparationError.IsEmpty()) return;
|
||||
if (!m_Factions || !m_Factions.WorldReady()) { FailPreparation("The configured player and enemy factions or required assets are missing, invalid or not hostile."); return; }
|
||||
// Scenario content is created through a separate authoring component.
|
||||
ERR_ScenarioComponent scenario = ERR_ScenarioComponent.Cast(FindComponent(ERR_ScenarioComponent));
|
||||
ERR_PrepareResult prepared = ERR_PrepareResult.FAILED;
|
||||
@@ -183,7 +224,9 @@ class ERR_GameMode : SCR_BaseGameMode
|
||||
}
|
||||
if (prepared == ERR_PrepareResult.FAILED)
|
||||
{
|
||||
Print("Rereintroduced: scenario preparation failed; spawning remains disabled", LogLevel.ERROR);
|
||||
string detail = "Scenario component is missing.";
|
||||
if (scenario) detail = scenario.FailureReason();
|
||||
FailPreparation(detail);
|
||||
return;
|
||||
}
|
||||
Transition(ERR_Phase.PRISON);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
modded class ERR_GameMode
|
||||
{
|
||||
protected int m_ERRBaseStep;
|
||||
protected bool m_ERRStartupFailure;
|
||||
protected float m_ERRBaseDeadline;
|
||||
protected float m_ERRBaseWait;
|
||||
protected IEntity m_ERRBasePlayer;
|
||||
@@ -14,6 +15,9 @@ modded class ERR_GameMode
|
||||
super.EOnInit(owner);
|
||||
string enabled;
|
||||
if (SCR_Global.IsEditMode() || !Replication.IsServer() || !System.GetCLIParam("errBaseSmoke", enabled) || enabled != "1") return;
|
||||
string outcome;
|
||||
m_ERRStartupFailure = System.GetCLIParam("errSmokeOutcome", outcome) && outcome == "startup-failure";
|
||||
if (m_ERRStartupFailure && m_Factions) m_Factions.EnemyFactionKey = m_Factions.PlayerFactionKey;
|
||||
m_ERRBaseDeadline = GetWorld().GetWorldTime() + 150000;
|
||||
OnPhaseChanged().Insert(ERR_BaseResult);
|
||||
GetGame().GetCallqueue().CallLater(ERR_BaseTick, 500, true);
|
||||
@@ -66,6 +70,21 @@ modded class ERR_GameMode
|
||||
{
|
||||
float now = GetWorld().GetWorldTime();
|
||||
if (now > m_ERRBaseDeadline) { ERR_BaseCheck(false, "timeout at " + m_ERRBaseStep.ToString()); return; }
|
||||
if (m_ERRStartupFailure)
|
||||
{
|
||||
if (PreparationError().IsEmpty()) return;
|
||||
if (!ERR_BaseCheck(Phase() == ERR_Phase.PREPARING && !Active() && m_Stopped, "invalid faction setup stops services without starting the mission")) return;
|
||||
array<IEntity> players = {};
|
||||
ERR_Actors.Players(players);
|
||||
if (!ERR_BaseCheck(players.IsEmpty() && !Sites() && m_OwnedEntities.IsEmpty(), "failed setup spawns no players, sites or supplies")) return;
|
||||
array<string> replies = {};
|
||||
Admin().Execute(this, true, "status", replies);
|
||||
if (!ERR_BaseCheck(replies.Count() == 1 && replies[0].Contains("configured player and enemy factions"), "administrator receives retained startup failure cause")) return;
|
||||
if (!ERR_BaseCheck(!Transition(ERR_Phase.PRISON) && !Transition(ERR_Phase.EXTRACTION), "failed preparation cannot be advanced into gameplay")) return;
|
||||
GetGame().GetCallqueue().Remove(ERR_BaseTick);
|
||||
Print("ERR SMOKE COMPLETE: expected startup-failure");
|
||||
return;
|
||||
}
|
||||
if (now < m_ERRBaseWait) return;
|
||||
if (m_ERRBaseStep == 0)
|
||||
{
|
||||
@@ -73,7 +92,7 @@ modded class ERR_GameMode
|
||||
ERR_Actors.Players(players, true);
|
||||
if (players.Count() != 1 || Phase() != ERR_Phase.PRISON) return;
|
||||
m_ERRBasePlayer = players[0];
|
||||
if (!ERR_NativeDefinitionProbe.Run()) return;
|
||||
if (!ERR_NativeDefinitionProbe.Run() || !ERR_NativeContractsProbe.Run(this, m_ERRBasePlayer)) return;
|
||||
DamageManagerComponent.Cast(m_ERRBasePlayer.FindComponent(DamageManagerComponent)).EnableDamageHandling(false);
|
||||
if (!ERR_BaseCheck(Objectives() && Objectives().Current() && Objectives().Current().GetTaskName() == "Escape the prison", "native prison objective exists")) return;
|
||||
ERR_SiteRuntime cache = Sites().Find("everon-supply-1");
|
||||
@@ -91,7 +110,7 @@ modded class ERR_GameMode
|
||||
{
|
||||
if (!entity) continue;
|
||||
if (!lock) lock = ERR_BaseLock(entity);
|
||||
if (entity.GetPrefabData() && entity.GetPrefabData().GetPrefabName() == ERR_FactionCatalog.ObjectiveDocument()) m_ERRBaseDocuments.Insert(entity);
|
||||
if (entity.GetPrefabData() && entity.GetPrefabData().GetPrefabName() == ERR_Resources.ObjectiveDocument()) m_ERRBaseDocuments.Insert(entity);
|
||||
}
|
||||
if (!ERR_BaseCheck(lock && m_ERRBaseRadio && m_ERRBaseDocuments.Count() == 2 && ERR_Actors.Alive(m_ExtractionVehicle), "gate, clues, radio and helicopter created by core")) return;
|
||||
ERR_BaseMove(lock.GetOwner().GetOrigin() + "1 0 1");
|
||||
@@ -139,6 +158,26 @@ modded class ERR_GameMode
|
||||
if (!ERR_BaseCheck(stock.IsEmpty() && Sites().Prepare() == ERR_PrepareResult.READY, "depleted stock stays empty when preparation is queried again")) return;
|
||||
ERR_Actors.DeleteSpawned(cache.Container);
|
||||
if (!ERR_BaseCheck(DefenceOccupied() && DefenceSecondsRemaining() > 0 && DefenceSecondsRemaining() < 30 && Objectives().Current().GetTaskDescription().Contains("seconds remaining"), "native objective reports advancing radio progress")) return;
|
||||
CharacterControllerComponent.Cast(m_ERRBasePlayer.FindComponent(CharacterControllerComponent)).SetUnconscious(true);
|
||||
m_ERRBaseStep = 310;
|
||||
m_ERRBaseWait = now + 35000;
|
||||
}
|
||||
else if (m_ERRBaseStep == 310)
|
||||
{
|
||||
CharacterControllerComponent controller = CharacterControllerComponent.Cast(m_ERRBasePlayer.FindComponent(CharacterControllerComponent));
|
||||
if (!ERR_BaseCheck(controller.IsUnconscious() && ERR_Actors.Alive(m_ERRBasePlayer) && Players().HasSurvivor() && Phase() == ERR_Phase.RADIO_DEFENCE, "unconscious survivor does not trigger defeat after the full grace period")) return;
|
||||
if (!ERR_BaseCheck(!DefenceOccupied() && DefenceSecondsRemaining() == 30, "unconscious radio defender cannot advance transmission")) return;
|
||||
SCR_PlayerController playerController = SCR_PlayerController.Cast(GetGame().GetPlayerController());
|
||||
if (!ERR_BaseCheck(playerController.GetControlledEntity() == m_ERRBasePlayer && playerController.ERR_ObserverState() == ERR_ObserverReason.NONE && !playerController.ERR_HasObserverCamera(), "unconscious player retains their body and camera")) return;
|
||||
controller.SetUnconscious(false);
|
||||
m_ERRBaseStep = 311;
|
||||
m_ERRBaseWait = now + 6000;
|
||||
}
|
||||
else if (m_ERRBaseStep == 311)
|
||||
{
|
||||
if (!ERR_BaseCheck(ERR_Actors.CanFight(m_ERRBasePlayer) && DefenceOccupied() && DefenceSecondsRemaining() < 30, "regaining consciousness resumes transmission from zero")) return;
|
||||
int playerId = GetGame().GetPlayerManager().GetPlayerIdFromControlledEntity(m_ERRBasePlayer);
|
||||
if (!ERR_BaseCheck(!Players().CanEnter(playerId, Phase()), "regaining consciousness does not grant another life")) return;
|
||||
ERR_BaseMove(m_ERRBaseRadio.Position() + "90 0 0");
|
||||
m_ERRBaseStep = 32;
|
||||
m_ERRBaseWait = now + 2500;
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
#ifdef WORKBENCH
|
||||
// Exercises native service dispatch, not remote RPC ownership or rendered UI.
|
||||
class ERR_NativeContractsProbe
|
||||
{
|
||||
protected static bool Check(bool condition, string detail)
|
||||
{
|
||||
if (condition) { Print("ERR SMOKE PASS: base contracts " + detail); return true; }
|
||||
Print("ERR SMOKE FAIL: base contracts " + detail, LogLevel.ERROR);
|
||||
return false;
|
||||
}
|
||||
static bool Run(ERR_GameMode mode, IEntity player)
|
||||
{
|
||||
if (!Check(mode.Factions() && mode.Factions().WorldReady() && mode.PreparationError().IsEmpty(), "catalog resolves hostile vanilla factions and successful preparation")) return false;
|
||||
if (!Check(SCR_FactionManager.SGetFaction(player) == mode.PlayerFaction() && player.GetPrefabData().GetPrefabName() == mode.Factions().PrisonerPrefab, "physical prisoner matches injected faction and resource")) return false;
|
||||
ERR_SiteRuntime site = mode.Sites().Find("everon-supply-1");
|
||||
if (!Check(site && site.Group && site.Group.GetFaction() == mode.EnemyFaction() && site.Group.GetPrefabData().GetPrefabName() == mode.Factions().GuardGroupPrefab, "physical guard squad comes from injected catalog")) return false;
|
||||
ERR_FactionCatalog isolated = ERR_FactionCatalog.Cast(SCR_BaseContainerTools.CreateInstanceFromPrefab("{BE50B97B8E2B36E6}Configs/ERR/VanillaFactions.conf"));
|
||||
if (!isolated) return false;
|
||||
string enemyKey = isolated.EnemyFactionKey;
|
||||
isolated.EnemyFactionKey = isolated.PlayerFactionKey;
|
||||
if (!Check(!isolated.Valid() && !isolated.WorldReady(), "same-side factions rejected")) return false;
|
||||
isolated.EnemyFactionKey = "ERR_MISSING_FACTION";
|
||||
if (!Check(!isolated.WorldReady(), "unknown world faction rejected")) return false;
|
||||
isolated.EnemyFactionKey = enemyKey;
|
||||
ResourceName prisoner = isolated.PrisonerPrefab;
|
||||
isolated.PrisonerPrefab = "";
|
||||
if (!Check(!isolated.Valid(), "missing prisoner resource rejected")) return false;
|
||||
isolated.PrisonerPrefab = prisoner;
|
||||
if (!Check(isolated.WorldReady(), "isolated catalog restored without mutating live configuration")) return false;
|
||||
|
||||
array<string> replies = {};
|
||||
int original = mode.Settings().Get("radioDefenceSeconds");
|
||||
mode.Admin().Execute(mode, false, "mission radio set 60", replies);
|
||||
if (!Check(mode.Settings().Get("radioDefenceSeconds") == original && replies.Count() == 1 && replies[0] == "Administrator access required.", "unauthorized settings request rejected")) return false;
|
||||
mode.Admin().Execute(mode, true, "mission radio set 60junk", replies);
|
||||
if (!Check(mode.Settings().Get("radioDefenceSeconds") == original && replies[0].Contains("Invalid"), "partially numeric input rejected without mutation")) return false;
|
||||
mode.Admin().Execute(mode, true, "mission radio set 0", replies);
|
||||
if (!Check(mode.Settings().Get("radioDefenceSeconds") == original && replies[0].Contains("Invalid"), "out-of-range duration rejected without mutation")) return false;
|
||||
mode.Admin().Execute(mode, true, " MISSION RADIO SET 60 ", replies);
|
||||
if (!Check(mode.Settings().Get("radioDefenceSeconds") == 60 && replies[0] == "radioDefenceSeconds=60 saved", "normalized base command saves validated duration")) return false;
|
||||
mode.Admin().Execute(mode, true, "mission radio show", replies);
|
||||
if (!Check(replies.Count() == 1 && replies[0] == "radioDefenceSeconds=60", "base command reads saved duration")) return false;
|
||||
mode.Admin().Execute(mode, true, "settings", replies);
|
||||
if (!Check(replies.Count() == ERR_Settings.Keys().Count(), "base settings listing available")) return false;
|
||||
mode.Admin().Execute(mode, true, "status", replies);
|
||||
if (!Check(!replies.IsEmpty() && replies[0] == "Escape the prison", "base mission status available before companions")) return false;
|
||||
mode.Admin().Execute(mode, true, "mission radio set " + original.ToString(), replies);
|
||||
if (!Check(mode.Settings().Get("radioDefenceSeconds") == original, "test setting restored")) return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -118,7 +118,7 @@ modded class SCR_PlayerController
|
||||
}
|
||||
array<IEntity> survivors = {};
|
||||
ERR_Actors.Players(survivors);
|
||||
Faction friendly = GetGame().GetFactionManager().GetFactionByKey("US");
|
||||
Faction friendly = mode.PlayerFaction();
|
||||
for (int i = survivors.Count() - 1; i >= 0; i--)
|
||||
if (SCR_FactionManager.SGetFaction(survivors[i]) != friendly) survivors.RemoveOrdered(i);
|
||||
int index = survivors.Find(m_ERRServerObserved);
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
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) return;
|
||||
float now = GetGame().GetWorld().GetWorldTime();
|
||||
if (arguments.Length() > 160 || now - m_ERRLastRequest < 250) return;
|
||||
m_ERRLastRequest = now;
|
||||
arguments = arguments.Trim(); arguments.ToLower();
|
||||
array<string> replies = {};
|
||||
mode.Admin().Execute(mode, SCR_Global.IsAdmin(GetPlayerId()), arguments, replies);
|
||||
foreach (string reply : replies) ERR_Reply(reply);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -3,10 +3,20 @@ modded class SCR_PlayerController
|
||||
{
|
||||
protected int m_ERRGuidePhase = -1;
|
||||
protected bool m_ERRGuideStarted;
|
||||
protected string m_ERRGuideError;
|
||||
|
||||
override void OnControlledEntityChanged(IEntity from, IEntity to)
|
||||
{
|
||||
super.OnControlledEntityChanged(from, to);
|
||||
ERR_StartGuide();
|
||||
}
|
||||
protected override void ERR_OnObserverMonitorStarted()
|
||||
{
|
||||
super.ERR_OnObserverMonitorStarted();
|
||||
ERR_StartGuide();
|
||||
}
|
||||
protected void ERR_StartGuide()
|
||||
{
|
||||
if (this != GetGame().GetPlayerController() || !ERR_GameMode.Get() || m_ERRGuideStarted) return;
|
||||
m_ERRGuideStarted = true;
|
||||
GetGame().GetCallqueue().CallLater(ERR_UpdateGuide, 1000, true);
|
||||
@@ -14,11 +24,17 @@ modded class SCR_PlayerController
|
||||
protected void ERR_UpdateGuide()
|
||||
{
|
||||
ERR_GameMode mode = ERR_GameMode.Get();
|
||||
if (!mode || !GetControlledEntity() || m_ERRGuidePhase == mode.Phase()) return;
|
||||
if (!mode || (m_ERRGuidePhase == mode.Phase() && m_ERRGuideError == mode.PreparationError())) return;
|
||||
SCR_HintManagerComponent hints = SCR_HintManagerComponent.GetInstance();
|
||||
if (!hints) return;
|
||||
string message;
|
||||
if (mode.Phase() == ERR_Phase.PRISON)
|
||||
if (mode.Phase() == ERR_Phase.PREPARING)
|
||||
{
|
||||
message = "Preparing mission sites and equipment. You will spawn when the mission is ready.";
|
||||
if (!mode.PreparationError().IsEmpty()) message = "The mission could not start. An administrator can use /esc status for the cause. Spawning is disabled until the configuration is corrected and the scenario restarted.";
|
||||
}
|
||||
else if (!GetControlledEntity()) return;
|
||||
else if (mode.Phase() == ERR_Phase.PRISON)
|
||||
message = "Search the supply box inside the prison for weapons and a field map. Pick the gate lock to begin your escape.";
|
||||
else if (mode.Phase() == ERR_Phase.RADIO_SEARCH)
|
||||
message = "The nearby supply cache is marked on your map. Scavenge its equipment and carry the radio documents to identify the radio station. Enemy soldiers may carry more intel.";
|
||||
@@ -28,6 +44,7 @@ modded class SCR_PlayerController
|
||||
message = "The extraction helicopter is marked on your map. Board it and fly at least 9 kilometres from the prison. Everyone aboard escapes; players left behind do not.";
|
||||
if (!message.IsEmpty()) hints.ShowCustom(message, "Escape Everon", 15);
|
||||
m_ERRGuidePhase = mode.Phase();
|
||||
m_ERRGuideError = mode.PreparationError();
|
||||
}
|
||||
void ~SCR_PlayerController()
|
||||
{
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
// Shared mission resources are independent of the selected factions.
|
||||
class ERR_Resources
|
||||
{
|
||||
static ResourceName ObjectiveDocument() { return "{554159A6740C3F3E}Prefabs/ERR/ObjectiveDocument.et"; }
|
||||
static ResourceName SearchWaypoint() { return "{B3E7B8DC2BAB8ACC}Prefabs/AI/Waypoints/AIWaypoint_SearchAndDestroy.et"; }
|
||||
static ResourceName DefendWaypoint() { return "{93291E72AC23930F}Prefabs/AI/Waypoints/AIWaypoint_Defend.et"; }
|
||||
static ResourceName IntelFolder() { return "{6D56FED1E55A8F84}Prefabs/Items/Misc/IntelligenceFolder_E_01/IntelligenceFolder_E_01.et"; }
|
||||
}
|
||||
@@ -10,6 +10,11 @@ class ERR_ScenarioComponent : ScriptComponent
|
||||
protected bool m_Failed;
|
||||
|
||||
ERR_SiteService Sites() { return m_Sites; }
|
||||
string FailureReason()
|
||||
{
|
||||
if (m_Sites && !m_Sites.FailureReason().IsEmpty()) return m_Sites.FailureReason();
|
||||
return "Scenario or additional force preparation failed.";
|
||||
}
|
||||
ERR_PrepareResult Prepare()
|
||||
{
|
||||
ERR_GameMode mode = ERR_GameMode.Get();
|
||||
|
||||
@@ -23,6 +23,8 @@ class ERR_SiteDefinition
|
||||
vector Position;
|
||||
[Attribute("", UIWidgets.ResourceNamePicker, "Optional physical guard group", "et")]
|
||||
ResourceName Guards;
|
||||
[Attribute("0", UIWidgets.CheckBox, "Populate a finite squad from the faction catalog") ]
|
||||
bool Guarded;
|
||||
[Attribute("20 0 0", UIWidgets.Coords)]
|
||||
vector GuardOffset;
|
||||
[Attribute("", UIWidgets.ResourceNamePicker, "Finite inventory container", "et")]
|
||||
@@ -61,13 +63,13 @@ class ERR_ScenarioDefinition
|
||||
if (site.Kind == ERR_LocationKind.PRISON) prisons++;
|
||||
if (site.Kind == ERR_LocationKind.RADIO) radios++;
|
||||
if (site.Kind == ERR_LocationKind.EXTRACTION) extractions++;
|
||||
if (!site.Guards.IsEmpty()) groups++;
|
||||
if (site.Guarded || !site.Guards.IsEmpty()) groups++;
|
||||
if (!site.Stock) continue;
|
||||
foreach (ERR_SiteStock stock : site.Stock)
|
||||
{
|
||||
if (!stock || stock.Prefab.IsEmpty() || stock.Count < 1 || stock.Count > 64 || site.Container.IsEmpty()) return false;
|
||||
items += stock.Count;
|
||||
if (stock.Prefab == ERR_FactionCatalog.ObjectiveDocument())
|
||||
if (stock.Prefab == ERR_Resources.ObjectiveDocument())
|
||||
{
|
||||
if (stock.TargetId.IsEmpty()) return false;
|
||||
if (site.RevealOnEscape || site.Kind == ERR_LocationKind.PRISON) documents += stock.Count;
|
||||
@@ -76,7 +78,7 @@ class ERR_ScenarioDefinition
|
||||
if (!stock.TargetId.IsEmpty())
|
||||
{
|
||||
ERR_SiteDefinition target = Find(stock.TargetId);
|
||||
if (!target || target.Kind != ERR_LocationKind.RADIO || stock.Prefab != ERR_FactionCatalog.ObjectiveDocument()) return false;
|
||||
if (!target || target.Kind != ERR_LocationKind.RADIO || stock.Prefab != ERR_Resources.ObjectiveDocument()) return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,8 @@ class ERR_SiteService
|
||||
protected bool m_Stopped;
|
||||
protected bool m_Activated;
|
||||
protected bool m_Obstructed;
|
||||
protected string m_FailureReason;
|
||||
string FailureReason() { return m_FailureReason; }
|
||||
|
||||
void ERR_SiteService(ERR_GameMode mode) { m_Mode = mode; }
|
||||
int Count() { return m_Sites.Count(); }
|
||||
@@ -58,6 +60,7 @@ class ERR_SiteService
|
||||
}
|
||||
protected bool Fail(string reason)
|
||||
{
|
||||
m_FailureReason = reason;
|
||||
Print("Rereintroduced [sites]: preparation failed: " + reason, LogLevel.ERROR);
|
||||
Rollback();
|
||||
m_State = ERR_PrepareResult.FAILED;
|
||||
@@ -84,7 +87,7 @@ class ERR_SiteService
|
||||
if (spec.Kind == ERR_LocationKind.PRISON) m_Mode.ConfigureStart(position);
|
||||
if (spec.Kind == ERR_LocationKind.EXTRACTION)
|
||||
{
|
||||
Vehicle helicopter = Vehicle.Cast(Spawn(ERR_FactionCatalog.Helicopter(), position + "12 0 0"));
|
||||
Vehicle helicopter = Vehicle.Cast(Spawn(m_Mode.Factions().ExtractionPrefab, position + "12 0 0"));
|
||||
if (!helicopter) return Fail("extraction vehicle");
|
||||
m_Mode.SetExtractionVehicle(helicopter);
|
||||
}
|
||||
@@ -123,11 +126,13 @@ class ERR_SiteService
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!spec.Guards.IsEmpty())
|
||||
if (spec.Guarded || !spec.Guards.IsEmpty())
|
||||
{
|
||||
vector guardPosition = position + spec.GuardOffset;
|
||||
site.Group = SCR_AIGroup.Cast(Spawn(spec.Guards, guardPosition));
|
||||
AIWaypoint waypoint = AIWaypoint.Cast(Spawn(ERR_FactionCatalog.DefendWaypoint(), guardPosition));
|
||||
ResourceName groupPrefab = spec.Guards;
|
||||
if (groupPrefab.IsEmpty()) groupPrefab = m_Mode.Factions().GuardGroupPrefab;
|
||||
site.Group = SCR_AIGroup.Cast(Spawn(groupPrefab, guardPosition));
|
||||
AIWaypoint waypoint = AIWaypoint.Cast(Spawn(ERR_Resources.DefendWaypoint(), guardPosition));
|
||||
if (!site.Group || !waypoint) return Fail("guard population: " + spec.Id);
|
||||
site.Group.AddWaypoint(waypoint);
|
||||
}
|
||||
@@ -141,6 +146,15 @@ class ERR_SiteService
|
||||
if (!Replication.IsServer() || !m_Begun || m_Stopped) return ERR_PrepareResult.FAILED;
|
||||
if (m_State != ERR_PrepareResult.PENDING) return m_State;
|
||||
int pending, accepted;
|
||||
foreach (ERR_SiteRuntime site : m_Sites)
|
||||
{
|
||||
if (!site.Group) continue;
|
||||
if (site.Group.GetFaction() != m_Mode.EnemyFaction())
|
||||
{
|
||||
Fail("guard group faction disagrees with catalog: " + site.Definition.Id);
|
||||
return m_State;
|
||||
}
|
||||
}
|
||||
string firstRejected;
|
||||
foreach (ERR_StockPlacement placement : m_Placements)
|
||||
{
|
||||
|
||||
@@ -43,10 +43,13 @@ class ERR_SpawnLogic : SCR_SpawnLogic
|
||||
if (!controller || !mode || !m_Identities.Contains(playerId) || !m_Attempts.Contains(playerId) || m_Pending.Contains(playerId)) return;
|
||||
if (controller.GetControlledEntity()) return;
|
||||
if (ERR_MissionRules.Terminal(mode.Phase())) return;
|
||||
if (!mode.PreparationError().IsEmpty()) { Observe(playerId, ERR_ObserverReason.SPAWN_UNAVAILABLE); return; }
|
||||
// Preparing sites is not a failed spawn attempt. Every audited arrival waits
|
||||
// for the same server preparation result before consuming its retry budget.
|
||||
if (!mode.Active()) { RetryLater(playerId); return; }
|
||||
int attempts = m_Attempts.Get(playerId);
|
||||
if (attempts >= 30) { Observe(playerId, ERR_ObserverReason.SPAWN_UNAVAILABLE); return; }
|
||||
m_Attempts.Set(playerId, attempts + 1);
|
||||
if (!mode.Active()) { RetryLater(playerId); return; }
|
||||
if (!mode.Players().CanEnter(playerId, mode.Phase()))
|
||||
{
|
||||
ERR_ObserverReason reason = ERR_ObserverReason.SPENT_LIFE;
|
||||
@@ -57,11 +60,11 @@ class ERR_SpawnLogic : SCR_SpawnLogic
|
||||
}
|
||||
SCR_PlayerFactionAffiliationComponent affiliation = GetPlayerFactionComponent_S(playerId);
|
||||
SCR_RespawnComponent respawn = GetPlayerRespawnComponent_S(playerId);
|
||||
Faction faction = GetGame().GetFactionManager().GetFactionByKey("US");
|
||||
Faction faction = mode.PlayerFaction();
|
||||
if (!affiliation || !respawn || !faction) { RetryLater(playerId); return; }
|
||||
affiliation.RequestFaction(faction);
|
||||
vector position = SpawnPosition(mode, playerId);
|
||||
SCR_FreeSpawnData request = new SCR_FreeSpawnData(ERR_FactionCatalog.Prisoner(), position);
|
||||
SCR_FreeSpawnData request = new SCR_FreeSpawnData(mode.Factions().PrisonerPrefab, position);
|
||||
if (!respawn.CanSpawn(request)) { RetryLater(playerId); return; }
|
||||
m_AuthorizedPositions.Set(playerId, position);
|
||||
m_Pending.Insert(playerId);
|
||||
@@ -176,7 +179,7 @@ class ERR_SpawnLogic : SCR_SpawnLogic
|
||||
{
|
||||
ERR_GameMode mode = ERR_GameMode.Get();
|
||||
if (!Replication.IsServer() || !mode || !data || !mode.Players().CanEnter(playerId, mode.Phase())) return false;
|
||||
if (!m_Pending.Contains(playerId) || !m_AuthorizedPositions.Contains(playerId) || data.GetPrefab() != ERR_FactionCatalog.Prisoner()) return false;
|
||||
if (!m_Pending.Contains(playerId) || !m_AuthorizedPositions.Contains(playerId) || data.GetPrefab() != mode.Factions().PrisonerPrefab) return false;
|
||||
return vector.Distance(data.GetPosition(), m_AuthorizedPositions.Get(playerId)) < 0.1;
|
||||
}
|
||||
void ~ERR_SpawnLogic()
|
||||
|
||||
Reference in New Issue
Block a user