Expand independent escape routes and finite site discovery
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
ERR_ScenarioDefinition {
|
||||
MinimumRadioSources 2
|
||||
Sites {
|
||||
ERR_SiteDefinition {
|
||||
Id "everon-prison-1"
|
||||
Label "Prison"
|
||||
Kind 0
|
||||
Prefab "{BE6B8D56349038A6}Prefabs/ERR/Prison.et"
|
||||
Position 6090 0 7160
|
||||
@@ -17,6 +19,7 @@ ERR_ScenarioDefinition {
|
||||
}
|
||||
ERR_SiteDefinition {
|
||||
Id "everon-supply-1"
|
||||
Label "Southern supply cache"
|
||||
Kind 4
|
||||
Prefab "{7C3D16C17C24D351}Prefabs/ERR/SupplySite.et"
|
||||
Position 6150 0 7050
|
||||
@@ -34,6 +37,7 @@ ERR_ScenarioDefinition {
|
||||
}
|
||||
ERR_SiteDefinition {
|
||||
Id "everon-radio-1"
|
||||
Label "Radio station"
|
||||
Kind 1
|
||||
Prefab "{F7E7E3D6ACF0C860}Prefabs/ERR/Radio.et"
|
||||
Position 6500 0 7200
|
||||
@@ -42,11 +46,71 @@ ERR_ScenarioDefinition {
|
||||
}
|
||||
ERR_SiteDefinition {
|
||||
Id "everon-extraction-1"
|
||||
Label "Extraction helicopter"
|
||||
Kind 2
|
||||
Prefab "{033503110AC34EAC}Prefabs/ERR/Extraction.et"
|
||||
Position 7000 0 7500
|
||||
Guarded 1
|
||||
GuardOffset 30 0 0
|
||||
}
|
||||
ERR_SiteDefinition {
|
||||
Id "everon-supply-2"
|
||||
Label "Western supply cache"
|
||||
Kind 4
|
||||
Prefab "{7C3D16C17C24D351}Prefabs/ERR/SupplySite.et"
|
||||
Position 5960 0 7220
|
||||
Guarded 1
|
||||
GuardOffset 16 0 0
|
||||
Container "{40986810D0B1F4AE}Prefabs/ERR/SupplyCache.et"
|
||||
DiscoverRadius 25
|
||||
RevealOnEscape 1
|
||||
Stock {
|
||||
ERR_SiteStock { Prefab "{FA5C25BF66A53DCF}Prefabs/Weapons/Rifles/AK74/Rifle_AK74.et" Count 4 }
|
||||
ERR_SiteStock { Prefab "{BBB50A815A2F916B}Prefabs/Weapons/Magazines/Magazine_545x39_AK_30rnd_Ball.et" Count 8 }
|
||||
ERR_SiteStock { Prefab "{C3F1FA1E2EC2B345}Prefabs/Items/Medicine/FieldDressing_01/FieldDressing_USSR_01.et" Count 6 }
|
||||
ERR_SiteStock { Prefab "{554159A6740C3F3E}Prefabs/ERR/ObjectiveDocument.et" Count 2 TargetId "everon-radio-1" }
|
||||
}
|
||||
}
|
||||
ERR_SiteDefinition {
|
||||
Id "everon-medical-1"
|
||||
Label "Medical stash"
|
||||
Kind 4
|
||||
Prefab "{7C3D16C17C24D351}Prefabs/ERR/SupplySite.et"
|
||||
Position 6340 0 7400
|
||||
Guarded 0
|
||||
GuardOffset 16 0 0
|
||||
Container "{40986810D0B1F4AE}Prefabs/ERR/SupplyCache.et"
|
||||
DiscoverRadius 25
|
||||
RevealOnEscape 0
|
||||
Stock {
|
||||
ERR_SiteStock { Prefab "{C3F1FA1E2EC2B345}Prefabs/Items/Medicine/FieldDressing_01/FieldDressing_USSR_01.et" Count 12 }
|
||||
ERR_SiteStock { Prefab "{527D7C5D2E476BDC}Prefabs/Items/Medicine/SalineBag_01/SalineBag_USSR_01.et" Count 6 }
|
||||
ERR_SiteStock { Prefab "{0D9A5DCF89AE7AA9}Prefabs/Items/Medicine/MorphineInjection_01/MorphineInjection_01.et" Count 6 }
|
||||
ERR_SiteStock { Prefab "{13772C903CB5E4F7}Prefabs/Items/Equipment/Maps/Map_Paper_01/PaperMap_01_folded.et" Count 3 }
|
||||
}
|
||||
}
|
||||
ERR_SiteDefinition {
|
||||
Id "everon-outpost-1"
|
||||
Label "Eastern guard outpost"
|
||||
Kind 3
|
||||
Prefab "{7C3D16C17C24D351}Prefabs/ERR/SupplySite.et"
|
||||
Position 6740 0 7300
|
||||
Guarded 1
|
||||
GuardOffset 16 0 0
|
||||
Container "{40986810D0B1F4AE}Prefabs/ERR/SupplyCache.et"
|
||||
DiscoverRadius 35
|
||||
RevealOnEscape 0
|
||||
Stock {
|
||||
ERR_SiteStock { Prefab "{FA5C25BF66A53DCF}Prefabs/Weapons/Rifles/AK74/Rifle_AK74.et" Count 2 }
|
||||
ERR_SiteStock { Prefab "{BBB50A815A2F916B}Prefabs/Weapons/Magazines/Magazine_545x39_AK_30rnd_Ball.et" Count 10 }
|
||||
ERR_SiteStock { Prefab "{243948B23D90BECB}Prefabs/Items/Equipment/Binoculars/Binoculars_B8/Binoculars_B8.et" Count 2 }
|
||||
ERR_SiteStock { Prefab "{C3F1FA1E2EC2B345}Prefabs/Items/Medicine/FieldDressing_01/FieldDressing_USSR_01.et" Count 4 }
|
||||
}
|
||||
Props {
|
||||
ERR_SiteProp { Prefab "{4B6AC1D8701C35A1}Prefabs/ERR/FieldCover.et" Offset 0 0 -4 }
|
||||
ERR_SiteProp { Prefab "{4B6AC1D8701C35A1}Prefabs/ERR/FieldCover.et" Offset -4 0 0 Angles 90 0 0 }
|
||||
ERR_SiteProp { Prefab "{4B6AC1D8701C35A1}Prefabs/ERR/FieldCover.et" Offset 4 0 0 Angles 90 0 0 }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
GenericEntity {
|
||||
components {
|
||||
MeshObject "{A557F6DC012E5101}" {
|
||||
Object "{268DBDABB3C94AD7}Assets/Props/Military/Sandbags/Sandbag_01_short.xob"
|
||||
Materials {
|
||||
MaterialAssignClass "{A557F6DC012E5102}" { SourceMaterial "Sandbags_01_Short" AssignedMaterial "{31529E064AA7C917}Assets/Props/Military/Sandbags/Data/Sandbags_01_short_Burlap.emat" }
|
||||
MaterialAssignClass "{A557F6DC012E5103}" { SourceMaterial "Sandbags_01_MLOD" AssignedMaterial "{2169E8EB73E1C127}Assets/Props/Military/Sandbags/Data/Sandbags_01_Burlap_MLOD.emat" }
|
||||
}
|
||||
}
|
||||
RigidBody "{A557F6DC012E5104}" { ModelGeometry 1 Static 1 }
|
||||
RplComponent "{A557F6DC012E5105}" {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
MetaFileClass {
|
||||
Name "{4B6AC1D8701C35A1}Prefabs/ERR/FieldCover.et"
|
||||
Configurations {
|
||||
ResourceClass PC {}
|
||||
ResourceClass HEADLESS : PC {}
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ modded class ERR_LocationComponent
|
||||
{
|
||||
protected ref SCR_MapMarkerBase m_ERRMarker;
|
||||
protected int m_ERRShown;
|
||||
protected string m_ERRShownLabel;
|
||||
|
||||
protected override void EOnInit(IEntity owner)
|
||||
{
|
||||
@@ -13,7 +14,7 @@ modded class ERR_LocationComponent
|
||||
}
|
||||
protected void ERR_UpdateMarker()
|
||||
{
|
||||
if (!GetGame().GetPlayerController() || Knowledge() <= 0 || Knowledge() == m_ERRShown) return;
|
||||
if (!GetGame().GetPlayerController() || Knowledge() <= 0 || (Knowledge() == m_ERRShown && Label() == m_ERRShownLabel)) return;
|
||||
SCR_MapMarkerManagerComponent manager = SCR_MapMarkerManagerComponent.GetInstance();
|
||||
if (!manager || !manager.GetMarkerConfig()) return;
|
||||
if (!m_ERRMarker)
|
||||
@@ -28,7 +29,8 @@ modded class ERR_LocationComponent
|
||||
string label = "Unknown location";
|
||||
if (Knowledge() == 2)
|
||||
{
|
||||
if (Kind() == ERR_LocationKind.RADIO) label = "Radio station";
|
||||
if (!Label().IsEmpty()) label = Label();
|
||||
else if (Kind() == ERR_LocationKind.RADIO) label = "Radio station";
|
||||
else if (Kind() == ERR_LocationKind.EXTRACTION) label = "Extraction helicopter";
|
||||
else if (Kind() == ERR_LocationKind.DEPOT) label = "Supply cache";
|
||||
else if (Kind() == ERR_LocationKind.PRISON) label = "Prison";
|
||||
@@ -36,6 +38,7 @@ modded class ERR_LocationComponent
|
||||
}
|
||||
m_ERRMarker.SetCustomText(label);
|
||||
m_ERRShown = Knowledge();
|
||||
m_ERRShownLabel = Label();
|
||||
}
|
||||
protected override void OnDelete(IEntity owner)
|
||||
{
|
||||
|
||||
@@ -34,6 +34,7 @@ class ERR_GameMode : SCR_BaseGameMode
|
||||
protected ref ScriptInvoker m_OnPhaseChanged = new ScriptInvoker();
|
||||
protected ref ScriptInvoker m_OnTick = new ScriptInvoker();
|
||||
protected ref ScriptInvoker m_OnStop = new ScriptInvoker();
|
||||
protected ref ScriptInvoker m_OnLocationDiscovered = new ScriptInvoker();
|
||||
protected ref ScriptInvoker m_OnGroup = new ScriptInvoker();
|
||||
|
||||
static ERR_GameMode Get()
|
||||
@@ -60,6 +61,12 @@ class ERR_GameMode : SCR_BaseGameMode
|
||||
ScriptInvoker OnTick() { return m_OnTick; }
|
||||
ScriptInvoker OnStop() { return m_OnStop; }
|
||||
ScriptInvoker OnGroup() { return m_OnGroup; }
|
||||
ScriptInvoker OnLocationDiscovered() { return m_OnLocationDiscovered; }
|
||||
void LocationDiscovered(ERR_LocationComponent location, int previous, int current)
|
||||
{
|
||||
if (!Replication.IsServer() || !m_Locations.Contains(location) || current <= previous) return;
|
||||
m_OnLocationDiscovered.Invoke(location, previous, current);
|
||||
}
|
||||
ERR_FactionCatalog Factions() { return m_Factions; }
|
||||
Faction PlayerFaction()
|
||||
{
|
||||
|
||||
@@ -6,23 +6,27 @@ class ERR_LocationComponent : ScriptComponent
|
||||
{
|
||||
[RplProp(onRplName: "IdentityChanged"), Attribute("", UIWidgets.EditBox, "Stable scenario location ID")]
|
||||
protected string m_Id;
|
||||
[RplProp()]
|
||||
protected string m_Label;
|
||||
[RplProp(), Attribute("0", UIWidgets.ComboBox, "Location kind", enums: ParamEnumArray.FromEnum(ERR_LocationKind))]
|
||||
protected ERR_LocationKind m_Kind;
|
||||
[RplProp()]
|
||||
protected int m_Knowledge;
|
||||
|
||||
string Id() { return m_Id; }
|
||||
string Label() { return m_Label; }
|
||||
ERR_LocationKind Kind() { return m_Kind; }
|
||||
int Knowledge() { return m_Knowledge; }
|
||||
vector Position() { return GetOwner().GetOrigin(); }
|
||||
|
||||
bool Configure(string id, ERR_LocationKind kind)
|
||||
bool Configure(string id, ERR_LocationKind kind, string label = "")
|
||||
{
|
||||
if (!Replication.IsServer() || id.IsEmpty()) return false;
|
||||
ERR_GameMode mode = ERR_GameMode.Get();
|
||||
if (!mode || mode.Phase() != ERR_Phase.PREPARING) return false;
|
||||
mode.UnregisterLocation(this);
|
||||
m_Id = id;
|
||||
m_Label = label;
|
||||
m_Kind = kind;
|
||||
mode.RegisterLocation(this);
|
||||
Replication.BumpMe();
|
||||
@@ -38,8 +42,11 @@ class ERR_LocationComponent : ScriptComponent
|
||||
bool Discover(int roll, int chance)
|
||||
{
|
||||
if (!Replication.IsServer() || m_Knowledge >= 2) return false;
|
||||
int previous = m_Knowledge;
|
||||
m_Knowledge = ERR_MissionRules.Discovery(m_Knowledge, roll, chance);
|
||||
Replication.BumpMe();
|
||||
ERR_GameMode mode = ERR_GameMode.Get();
|
||||
if (mode) mode.LocationDiscovered(this, previous, m_Knowledge);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,10 @@ modded class ERR_GameMode
|
||||
{
|
||||
protected int m_ERRBaseStep;
|
||||
protected bool m_ERRStartupFailure;
|
||||
protected bool m_ERRCacheLoss;
|
||||
protected string m_ERRActiveCacheId = "everon-supply-1";
|
||||
protected int m_ERRMedicalDiscoveries;
|
||||
protected IEntity m_ERRSightBlocker;
|
||||
protected float m_ERRBaseDeadline;
|
||||
protected float m_ERRBaseWait;
|
||||
protected IEntity m_ERRBasePlayer;
|
||||
@@ -17,8 +21,11 @@ modded class ERR_GameMode
|
||||
if (SCR_Global.IsEditMode() || !Replication.IsServer() || !System.GetCLIParam("errBaseSmoke", enabled) || enabled != "1") return;
|
||||
string outcome;
|
||||
m_ERRStartupFailure = System.GetCLIParam("errSmokeOutcome", outcome) && outcome == "startup-failure";
|
||||
m_ERRCacheLoss = outcome == "cache-loss";
|
||||
if (m_ERRCacheLoss) m_ERRActiveCacheId = "everon-supply-2";
|
||||
OnLocationDiscovered().Insert(ERR_BaseDiscovered);
|
||||
if (m_ERRStartupFailure && m_Factions) m_Factions.EnemyFactionKey = m_Factions.PlayerFactionKey;
|
||||
m_ERRBaseDeadline = GetWorld().GetWorldTime() + 150000;
|
||||
m_ERRBaseDeadline = GetWorld().GetWorldTime() + 210000;
|
||||
OnPhaseChanged().Insert(ERR_BaseResult);
|
||||
GetGame().GetCallqueue().CallLater(ERR_BaseTick, 500, true);
|
||||
Print("ERR BASE SMOKE: core-only scripted escape; not manual or multiplayer acceptance");
|
||||
@@ -59,6 +66,37 @@ modded class ERR_GameMode
|
||||
}
|
||||
return null;
|
||||
}
|
||||
protected bool ERR_BaseCover()
|
||||
{
|
||||
int count;
|
||||
foreach (IEntity entity : m_OwnedEntities)
|
||||
{
|
||||
if (!entity || !entity.GetPrefabData() || entity.GetPrefabData().GetPrefabName() != "{4B6AC1D8701C35A1}Prefabs/ERR/FieldCover.et") continue;
|
||||
vector transform[4];
|
||||
entity.GetWorldTransform(transform);
|
||||
vector up = transform[1];
|
||||
if (!ERR_BaseCheck(up[1] > 0.99, "outpost barrier is upright")) return false;
|
||||
vector low, high;
|
||||
entity.GetWorldBounds(low, high);
|
||||
vector center = (low + high) * 0.5;
|
||||
vector across = "0 0 2";
|
||||
if (high[0] - low[0] < high[2] - low[2]) across = "2 0 0";
|
||||
TraceParam trace = new TraceParam();
|
||||
trace.Start = center + across;
|
||||
trace.End = center - across;
|
||||
trace.Flags = TraceFlags.ENTS;
|
||||
trace.LayerMask = TRACE_LAYER_CAMERA;
|
||||
float fraction = GetWorld().TraceMove(trace, null);
|
||||
PrintFormat("ERR COVER GEOMETRY: low=%1 high=%2 trace=%3", low, high, fraction);
|
||||
if (!ERR_BaseCheck(fraction < 1 && trace.TraceEnt == entity, "outpost barrier has physical collision")) return false;
|
||||
count++;
|
||||
}
|
||||
return ERR_BaseCheck(count == 3, "exactly three authored outpost barriers exist");
|
||||
}
|
||||
protected void ERR_BaseDiscovered(ERR_LocationComponent location, int previous, int current)
|
||||
{
|
||||
if (location.Id() == "everon-medical-1" && previous == 0 && current == 2) m_ERRMedicalDiscoveries++;
|
||||
}
|
||||
protected void ERR_BaseResult(ERR_Phase previous, ERR_Phase current)
|
||||
{
|
||||
if (!ERR_MissionRules.Terminal(current)) return;
|
||||
@@ -92,15 +130,25 @@ 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() || !ERR_NativeContractsProbe.Run(this, m_ERRBasePlayer)) return;
|
||||
string survey;
|
||||
if (System.GetCLIParam("errSmokeOutcome", survey) && survey == "site-survey")
|
||||
{
|
||||
ERR_NativeSiteSurvey probe = new ERR_NativeSiteSurvey();
|
||||
probe.Run();
|
||||
GetGame().GetCallqueue().Remove(ERR_BaseTick);
|
||||
return;
|
||||
}
|
||||
if (!ERR_NativeDefinitionProbe.Run() || !ERR_NativeContractsProbe.Run(this, m_ERRBasePlayer) || !ERR_BaseCover()) 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");
|
||||
if (!ERR_BaseCheck(Sites().Count() == 4 && cache && cache.Container && vector.DistanceXZ(cache.Container.GetOrigin(), PrisonPosition()) > 100, "authored supply cache is a separate scavenging location")) return;
|
||||
ERR_SiteRuntime cache = Sites().Find(m_ERRActiveCacheId);
|
||||
if (!ERR_BaseCheck(Sites().Count() == 7 && cache && cache.Container && vector.DistanceXZ(cache.Container.GetOrigin(), PrisonPosition()) > 100, "authored supply cache is a separate scavenging location")) return;
|
||||
array<IEntity> stock = {};
|
||||
InventoryStorageManagerComponent.Cast(cache.Container.FindComponent(InventoryStorageManagerComponent)).GetItems(stock);
|
||||
PrintFormat("ERR BASE STOCK: cache items=%1", stock.Count());
|
||||
if (!ERR_BaseCheck(stock.Count() >= 38, "cache holds finite physical equipment and objective documents")) return;
|
||||
int expectedStock;
|
||||
foreach (ERR_SiteStock entry : cache.Definition.Stock) expectedStock += entry.Count;
|
||||
if (!ERR_BaseCheck(stock.Count() >= expectedStock, "cache holds finite physical equipment and objective documents")) return;
|
||||
if (!ERR_BaseCheck(Command() == null, "no companion HQ or support controller loaded")) return;
|
||||
if (!ERR_BaseCheck(vector.DistanceXZ(m_ERRBasePlayer.GetOrigin(), PrisonPosition()) < 5, "prisoner spawned in authored prison")) return;
|
||||
foreach (ERR_LocationComponent location : m_Locations)
|
||||
@@ -110,7 +158,13 @@ modded class ERR_GameMode
|
||||
{
|
||||
if (!entity) continue;
|
||||
if (!lock) lock = ERR_BaseLock(entity);
|
||||
if (entity.GetPrefabData() && entity.GetPrefabData().GetPrefabName() == ERR_Resources.ObjectiveDocument()) m_ERRBaseDocuments.Insert(entity);
|
||||
}
|
||||
foreach (IEntity item : stock)
|
||||
if (item && item.GetPrefabData() && item.GetPrefabData().GetPrefabName() == ERR_Resources.ObjectiveDocument()) m_ERRBaseDocuments.Insert(item);
|
||||
if (m_ERRCacheLoss)
|
||||
{
|
||||
ERR_Actors.DeleteSpawned(Sites().Find("everon-supply-1").Container);
|
||||
if (!ERR_BaseCheck(!Sites().Find("everon-supply-1").Container, "primary cache lost before any clues are recovered")) return;
|
||||
}
|
||||
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");
|
||||
@@ -120,13 +174,57 @@ modded class ERR_GameMode
|
||||
SCR_Task prisonTask = SCR_TaskSystem.GetTaskFromTaskID(ERR_Objectives.Id(ERR_Phase.PRISON), false);
|
||||
if (!ERR_BaseCheck(prisonTask && prisonTask.GetTaskState() == SCR_ETaskState.COMPLETED, "previous native objective completes on phase change")) return;
|
||||
if (!ERR_BaseCheck(Objectives().Current().GetTaskUIVisibility() == SCR_ETaskUIVisibility.LIST_ONLY, "search objective does not expose a hidden site")) return;
|
||||
if (!ERR_BaseCheck(Sites().Find("everon-supply-1").Location.Knowledge() == 2 && Sites().Find("everon-supply-2").Location.Knowledge() == 2, "both named supply routes revealed after escape")) return;
|
||||
ERR_SiteRuntime medical = Sites().Find("everon-medical-1");
|
||||
if (!ERR_BaseCheck(medical.Location.Knowledge() == 0 && Sites().Find("everon-outpost-1").Location.Knowledge() == 0, "optional sites remain hidden until approached")) return;
|
||||
ERR_BaseMove(medical.Location.Position() + "0 0 8");
|
||||
CharacterControllerComponent.Cast(m_ERRBasePlayer.FindComponent(CharacterControllerComponent)).SetUnconscious(true);
|
||||
m_ERRBaseStep = 10;
|
||||
m_ERRBaseWait = now + 6000;
|
||||
}
|
||||
else if (m_ERRBaseStep == 10)
|
||||
{
|
||||
ERR_SiteRuntime medical = Sites().Find("everon-medical-1");
|
||||
if (!ERR_BaseCheck(medical.Location.Knowledge() == 0 && !Sites().CanObserve(m_ERRBasePlayer, medical), "unconscious player cannot discover optional sites")) return;
|
||||
CharacterControllerComponent.Cast(m_ERRBasePlayer.FindComponent(CharacterControllerComponent)).SetUnconscious(false);
|
||||
ERR_BaseMove(medical.Location.Position() + "0 0 65");
|
||||
m_ERRBaseStep = 11;
|
||||
m_ERRBaseWait = now + 6000;
|
||||
}
|
||||
else if (m_ERRBaseStep == 11)
|
||||
{
|
||||
ERR_SiteRuntime medical = Sites().Find("everon-medical-1");
|
||||
if (!ERR_BaseCheck(medical.Location.Knowledge() == 0 && !Sites().CanObserve(m_ERRBasePlayer, medical), "distant player cannot discover optional sites")) return;
|
||||
ERR_BaseMove(medical.Location.Position() + "0 0 8");
|
||||
m_ERRSightBlocker = ERR_Actors.Spawn("{A67420C1FB91856E}Prefabs/ERR/PrisonWall.et", ERR_Actors.Ground(medical.Location.Position() + "-1.5 0 4"));
|
||||
if (!ERR_BaseCheck(m_ERRSightBlocker != null, "isolated visibility obstacle created")) return;
|
||||
Own(m_ERRSightBlocker);
|
||||
vector low, high;
|
||||
m_ERRSightBlocker.GetWorldBounds(low, high);
|
||||
PrintFormat("ERR VISIBILITY OBSTACLE: low=%1 high=%2 observer=%3 target=%4", low, high, m_ERRBasePlayer.GetOrigin(), medical.Location.Position());
|
||||
m_ERRBaseStep = 115;
|
||||
m_ERRBaseWait = now + 6000;
|
||||
}
|
||||
else if (m_ERRBaseStep == 115)
|
||||
{
|
||||
ERR_SiteRuntime medical = Sites().Find("everon-medical-1");
|
||||
if (!ERR_BaseCheck(medical.Location.Knowledge() == 0 && !Sites().CanObserve(m_ERRBasePlayer, medical), "physical wall prevents nearby site discovery")) return;
|
||||
ERR_Actors.DeleteSpawned(m_ERRSightBlocker);
|
||||
m_ERRBaseStep = 12;
|
||||
m_ERRBaseWait = now + 6000;
|
||||
}
|
||||
else if (m_ERRBaseStep == 12)
|
||||
{
|
||||
ERR_SiteRuntime medical = Sites().Find("everon-medical-1");
|
||||
if (!ERR_BaseCheck(medical.Location.Knowledge() == 2 && medical.Location.Label() == "Medical stash" && m_ERRMedicalDiscoveries == 1, "conscious nearby player visually discovers named medical stash once")) return;
|
||||
if (!ERR_BaseCheck(!Sites().CanObserve(m_ERRBasePlayer, Sites().Find("everon-radio-1")), "proximity cannot bypass radio documents")) return;
|
||||
m_ERRBaseStep = 1;
|
||||
}
|
||||
else if (m_ERRBaseStep == 1 || m_ERRBaseStep == 2)
|
||||
{
|
||||
ERR_BaseMove(Sites().Find("everon-supply-1").Container.GetOrigin() + "0 0 2");
|
||||
ERR_BaseMove(Sites().Find(m_ERRActiveCacheId).Container.GetOrigin() + "0 0 2");
|
||||
SCR_InventoryStorageManagerComponent inventory = SCR_InventoryStorageManagerComponent.Cast(m_ERRBasePlayer.FindComponent(SCR_InventoryStorageManagerComponent));
|
||||
InventoryStorageManagerComponent source = InventoryStorageManagerComponent.Cast(Sites().Find("everon-supply-1").Container.FindComponent(InventoryStorageManagerComponent));
|
||||
InventoryStorageManagerComponent source = InventoryStorageManagerComponent.Cast(Sites().Find(m_ERRActiveCacheId).Container.FindComponent(InventoryStorageManagerComponent));
|
||||
BaseInventoryStorageComponent destination = inventory.FindStorageForItem(m_ERRBaseDocuments[m_ERRBaseStep - 1]);
|
||||
if (!ERR_BaseCheck(destination && source.TryMoveItemToStorage(m_ERRBaseDocuments[m_ERRBaseStep - 1], destination), "physical clue recovered")) return;
|
||||
m_ERRBaseStep++;
|
||||
@@ -135,10 +233,12 @@ modded class ERR_GameMode
|
||||
else if (m_ERRBaseStep == 3)
|
||||
{
|
||||
if (!ERR_BaseCheck(Objectives().Current().GetTaskDescription().Contains("identify the radio station on your map. Reach"), "objective guidance reacts to discovered radio")) return;
|
||||
ERR_SiteRuntime cache = Sites().Find("everon-supply-1");
|
||||
ERR_SiteRuntime cache = Sites().Find(m_ERRActiveCacheId);
|
||||
array<IEntity> stock = {};
|
||||
InventoryStorageManagerComponent.Cast(cache.Container.FindComponent(InventoryStorageManagerComponent)).GetItems(stock);
|
||||
foreach (IEntity item : stock) if (item) SCR_EntityHelper.DeleteEntityAndChildren(item);
|
||||
if (!ERR_BaseCheck(m_ERRMedicalDiscoveries == 1, "discovery ticks do not repeat the location event")) return;
|
||||
if (m_ERRCacheLoss && !ERR_BaseCheck(!Sites().Find("everon-supply-1").Container, "lost primary cache stays absent while alternate documents identify the radio")) return;
|
||||
if (!ERR_BaseCheck(m_ERRBaseRadio.Knowledge() == 2, "documents identify radio without Intel addon")) return;
|
||||
ERR_BaseMove(m_ERRBaseRadio.Position() + "2 0 0");
|
||||
m_ERRBaseStep = 30;
|
||||
@@ -152,7 +252,7 @@ modded class ERR_GameMode
|
||||
}
|
||||
else if (m_ERRBaseStep == 31)
|
||||
{
|
||||
ERR_SiteRuntime cache = Sites().Find("everon-supply-1");
|
||||
ERR_SiteRuntime cache = Sites().Find(m_ERRActiveCacheId);
|
||||
array<IEntity> stock = {};
|
||||
InventoryStorageManagerComponent.Cast(cache.Container.FindComponent(InventoryStorageManagerComponent)).GetItems(stock);
|
||||
if (!ERR_BaseCheck(stock.IsEmpty() && Sites().Prepare() == ERR_PrepareResult.READY, "depleted stock stays empty when preparation is queried again")) return;
|
||||
@@ -184,7 +284,7 @@ modded class ERR_GameMode
|
||||
}
|
||||
else if (m_ERRBaseStep == 32)
|
||||
{
|
||||
if (!ERR_BaseCheck(!Sites().Find("everon-supply-1").Container, "lost supply cache is not replaced")) return;
|
||||
if (!ERR_BaseCheck(!Sites().Find(m_ERRActiveCacheId).Container, "lost supply cache is not replaced")) return;
|
||||
if (!ERR_BaseCheck(!DefenceOccupied() && DefenceSecondsRemaining() == 30 && Objectives().Current().GetTaskDescription().Contains("interrupted"), "objective reports interruption and reset")) return;
|
||||
ERR_BaseMove(m_ERRBaseRadio.Position() + "2 0 0");
|
||||
m_ERRBaseStep = 4;
|
||||
|
||||
@@ -30,6 +30,27 @@ class ERR_NativeDefinitionProbe
|
||||
cache.RevealOnEscape = false;
|
||||
if (!Check(!definition.Valid(), "unguaranteed clue route rejected")) return false;
|
||||
cache.RevealOnEscape = true;
|
||||
ERR_SiteDefinition alternate = definition.Find("everon-supply-2");
|
||||
vector alternatePosition = alternate.Position;
|
||||
alternate.Position = cache.Position + "10 0 0";
|
||||
if (!Check(!definition.Valid(), "radio sources sharing one footprint rejected")) return false;
|
||||
alternate.Position = alternatePosition;
|
||||
string label = alternate.Label;
|
||||
alternate.Label = "";
|
||||
if (!Check(!definition.Valid(), "unnamed location rejected")) return false;
|
||||
alternate.Label = label;
|
||||
ERR_SiteDefinition radio = definition.Find("everon-radio-1");
|
||||
radio.DiscoverRadius = 25;
|
||||
if (!Check(!definition.Valid(), "proximity discovery cannot reveal progression sites")) return false;
|
||||
radio.DiscoverRadius = 0;
|
||||
radio.RevealOnEscape = true;
|
||||
if (!Check(!definition.Valid(), "escape cannot reveal the radio directly")) return false;
|
||||
radio.RevealOnEscape = false;
|
||||
ERR_SiteProp prop = definition.Find("everon-outpost-1").Props[0];
|
||||
vector offset = prop.Offset;
|
||||
prop.Offset = "100 0 0";
|
||||
if (!Check(!definition.Valid(), "unbounded scenery offset rejected")) return false;
|
||||
prop.Offset = offset;
|
||||
return Check(definition.Valid(), "valid configuration restored after isolated mutations");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
#ifdef WORKBENCH
|
||||
// Read-only spatial survey of new authored site candidates. No world edits.
|
||||
class ERR_NativeSiteSurvey
|
||||
{
|
||||
protected bool m_Blocked;
|
||||
protected string m_Obstacle;
|
||||
protected bool Obstacle(IEntity entity)
|
||||
{
|
||||
if (entity && entity.GetVObject())
|
||||
{
|
||||
m_Blocked = true;
|
||||
m_Obstacle = entity.ToString();
|
||||
if (entity.GetPrefabData()) m_Obstacle = entity.GetPrefabData().GetPrefabName();
|
||||
}
|
||||
return !m_Blocked;
|
||||
}
|
||||
void Area(string name, vector center)
|
||||
{
|
||||
int found, slopes, blocked;
|
||||
float best = 10000;
|
||||
for (int x = -80; x <= 80; x += 20)
|
||||
for (int z = -80; z <= 80; z += 20)
|
||||
{
|
||||
vector position = ERR_Actors.Ground(center + Vector(x, 0, z));
|
||||
float low = 10000;
|
||||
float high = -10000;
|
||||
for (int dx = -2; dx <= 2; dx += 2)
|
||||
for (int dz = -2; dz <= 2; dz += 2)
|
||||
{
|
||||
float height = GetGame().GetWorld().GetSurfaceY(position[0] + dx, position[2] + dz);
|
||||
low = Math.Min(low, height);
|
||||
high = Math.Max(high, height);
|
||||
}
|
||||
best = Math.Min(best, high-low);
|
||||
if (low < 1 || high - low > 1.2) { slopes++; continue; }
|
||||
m_Blocked = false;
|
||||
GetGame().GetWorld().QueryEntitiesByAABB(Vector(position[0]-1.3, low+0.1, position[2]-1.3), Vector(position[0]+1.3, high+2, position[2]+1.3), Obstacle);
|
||||
if (m_Blocked)
|
||||
{
|
||||
blocked++;
|
||||
if (blocked <= 2) PrintFormat("ERR SITE OBSTACLE: area=%1 position=%2 object=%3", name, position, m_Obstacle);
|
||||
continue;
|
||||
}
|
||||
found++;
|
||||
PrintFormat("ERR SITE CANDIDATE: area=%1 position=%2 elevationRange=%3", name, position, high-low);
|
||||
}
|
||||
PrintFormat("ERR SITE SURVEY COUNT: area=%1 clear=%2 slopes=%3 blocked=%4 bestSlope=%5", name, found, slopes, blocked, best);
|
||||
}
|
||||
void Run()
|
||||
{
|
||||
Area("west-supplies", "5900 0 7200");
|
||||
Area("medical", "6320 0 7440");
|
||||
Area("outpost", "6740 0 7320");
|
||||
Print("ERR SMOKE PASS: candidate survey completed without authoring world changes");
|
||||
Print("ERR SMOKE COMPLETE: read-only site survey");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -51,7 +51,7 @@ class ERR_Objectives
|
||||
foreach (ERR_LocationComponent location : locations)
|
||||
if (location.Kind() == ERR_LocationKind.RADIO && location.Knowledge() == 2)
|
||||
return "The recovered documents identify the radio station on your map. Reach the station and tune its radio to start the transmission.";
|
||||
return "Search the supply cache marked on your map. Its finite stock contains weapons, ammunition, medical supplies and radio documents. Carry the documents to identify the radio station.";
|
||||
return "Choose either marked supply cache. Each has equipment and enough radio documents to identify the station. If one cache is lost, use the other. Nearby medical and guard sites can be discovered by approaching with a clear view.";
|
||||
}
|
||||
if (phase == ERR_Phase.RADIO_DEFENCE)
|
||||
{
|
||||
|
||||
@@ -37,7 +37,7 @@ modded class SCR_PlayerController
|
||||
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.";
|
||||
message = "Two supply caches are marked on your map. Either contains enough radio documents to identify the station; use the other if one is lost. Search nearby medical and guard sites for additional finite supplies.";
|
||||
else if (mode.Phase() == ERR_Phase.RADIO_DEFENCE)
|
||||
message = "Keep a conscious survivor within 60 metres of the radio until the transmission finishes. Leaving the area resets the defence timer.";
|
||||
else if (mode.Phase() == ERR_Phase.EXTRACTION)
|
||||
|
||||
@@ -37,6 +37,7 @@ class ERR_ScenarioComponent : ScriptComponent
|
||||
}
|
||||
mode.OnPhaseChanged().Insert(m_Sites.PhaseChanged);
|
||||
mode.OnStop().Insert(m_Sites.Stop);
|
||||
mode.OnTick().Insert(m_Sites.Tick);
|
||||
GetGame().GetCallqueue().CallLater(ActivateSites, 2000, false);
|
||||
m_Finished = true;
|
||||
return ERR_PrepareResult.READY;
|
||||
|
||||
@@ -10,11 +10,24 @@ class ERR_SiteStock
|
||||
string TargetId;
|
||||
}
|
||||
|
||||
[BaseContainerProps()]
|
||||
class ERR_SiteProp
|
||||
{
|
||||
[Attribute("", UIWidgets.ResourceNamePicker, "Finite initial scenery", "et")]
|
||||
ResourceName Prefab;
|
||||
[Attribute("0 0 0", UIWidgets.Coords)] vector Offset;
|
||||
[Attribute("0 0 0", UIWidgets.Coords, "Yaw, pitch, roll in degrees")] vector Angles;
|
||||
}
|
||||
|
||||
[BaseContainerProps()]
|
||||
class ERR_SiteDefinition
|
||||
{
|
||||
[Attribute()]
|
||||
string Id;
|
||||
[Attribute()] string Label;
|
||||
[Attribute("0", UIWidgets.EditBox, "Conscious visual discovery radius for optional sites") ]
|
||||
float DiscoverRadius;
|
||||
[Attribute()] ref array<ref ERR_SiteProp> Props;
|
||||
[Attribute("0", UIWidgets.ComboBox, "Location kind", enums: ParamEnumArray.FromEnum(ERR_LocationKind))]
|
||||
ERR_LocationKind Kind;
|
||||
[Attribute("", UIWidgets.ResourceNamePicker, "Successor site composition", "et")]
|
||||
@@ -42,6 +55,8 @@ class ERR_ScenarioDefinition
|
||||
{
|
||||
[Attribute()]
|
||||
ref array<ref ERR_SiteDefinition> Sites;
|
||||
[Attribute("2", UIWidgets.EditBox, "Independent accessible caches containing enough radio documents") ]
|
||||
int MinimumRadioSources;
|
||||
|
||||
ERR_SiteDefinition Find(string id)
|
||||
{
|
||||
@@ -54,16 +69,30 @@ class ERR_ScenarioDefinition
|
||||
{
|
||||
if (!Sites || Sites.IsEmpty() || Sites.Count() > 32) return false;
|
||||
set<string> ids = new set<string>();
|
||||
int prisons, radios, extractions, groups, items, documents, maps;
|
||||
int prisons, radios, extractions, groups, items, sources, maps, props;
|
||||
array<vector> cluePositions = {};
|
||||
if (MinimumRadioSources < 1 || MinimumRadioSources > 8) return false;
|
||||
foreach (ERR_SiteDefinition site : Sites)
|
||||
{
|
||||
if (!site || site.Id.IsEmpty() || ids.Contains(site.Id) || site.Prefab.IsEmpty()) return false;
|
||||
if (site.Label.IsEmpty() || site.Label.Length() > 64) return false;
|
||||
if (site.DiscoverRadius < 0 || site.DiscoverRadius > 60) return false;
|
||||
bool optionalSite = site.Kind == ERR_LocationKind.DEPOT || site.Kind == ERR_LocationKind.OUTPOST;
|
||||
if (!optionalSite && (site.DiscoverRadius > 0 || site.RevealOnEscape)) return false;
|
||||
if (site.Props)
|
||||
{
|
||||
if (site.Props.Count() > 12) return false;
|
||||
props += site.Props.Count();
|
||||
foreach (ERR_SiteProp prop : site.Props)
|
||||
if (!prop || prop.Prefab.IsEmpty() || prop.Offset.Length() > 40) return false;
|
||||
}
|
||||
if (site.Kind < ERR_LocationKind.PRISON || site.Kind > ERR_LocationKind.DEPOT) return false;
|
||||
ids.Insert(site.Id);
|
||||
if (site.Kind == ERR_LocationKind.PRISON) prisons++;
|
||||
if (site.Kind == ERR_LocationKind.RADIO) radios++;
|
||||
if (site.Kind == ERR_LocationKind.EXTRACTION) extractions++;
|
||||
if (site.Guarded || !site.Guards.IsEmpty()) groups++;
|
||||
int documents = 0;
|
||||
if (!site.Stock) continue;
|
||||
foreach (ERR_SiteStock stock : site.Stock)
|
||||
{
|
||||
@@ -81,7 +110,14 @@ class ERR_ScenarioDefinition
|
||||
if (!target || target.Kind != ERR_LocationKind.RADIO || stock.Prefab != ERR_Resources.ObjectiveDocument()) return false;
|
||||
}
|
||||
}
|
||||
if (documents >= 2)
|
||||
{
|
||||
foreach (vector position : cluePositions)
|
||||
if (vector.DistanceXZ(position, site.Position) < 100) return false;
|
||||
cluePositions.Insert(site.Position);
|
||||
sources++;
|
||||
}
|
||||
}
|
||||
return prisons == 1 && radios == 1 && extractions == 1 && groups <= 8 && items <= 256 && documents >= 2 && maps >= 1;
|
||||
return prisons == 1 && radios == 1 && extractions == 1 && groups <= 8 && items <= 256 && props <= 64 && sources >= MinimumRadioSources && maps >= 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ class ERR_SiteService
|
||||
protected bool m_Activated;
|
||||
protected bool m_Obstructed;
|
||||
protected string m_FailureReason;
|
||||
protected float m_NextDiscovery;
|
||||
string FailureReason() { return m_FailureReason; }
|
||||
|
||||
void ERR_SiteService(ERR_GameMode mode) { m_Mode = mode; }
|
||||
@@ -82,7 +83,7 @@ class ERR_SiteService
|
||||
ERR_SiteRuntime site = new ERR_SiteRuntime();
|
||||
site.Definition = spec;
|
||||
site.Location = ERR_LocationComponent.Cast(root.FindComponent(ERR_LocationComponent));
|
||||
if (!site.Location || !site.Location.Configure(spec.Id, spec.Kind)) return Fail("location identity: " + spec.Id);
|
||||
if (!site.Location || !site.Location.Configure(spec.Id, spec.Kind, spec.Label)) return Fail("location identity: " + spec.Id);
|
||||
m_Sites.Insert(site);
|
||||
if (spec.Kind == ERR_LocationKind.PRISON) m_Mode.ConfigureStart(position);
|
||||
if (spec.Kind == ERR_LocationKind.EXTRACTION)
|
||||
@@ -126,6 +127,18 @@ class ERR_SiteService
|
||||
}
|
||||
}
|
||||
}
|
||||
if (spec.Props)
|
||||
{
|
||||
foreach (ERR_SiteProp prop : spec.Props)
|
||||
{
|
||||
vector propPosition = ERR_Actors.Ground(position + prop.Offset);
|
||||
if (!CacheClearance(propPosition)) return Fail("obstructed scenery position: " + spec.Id);
|
||||
propPosition[1] = propPosition[1] + prop.Offset[1];
|
||||
IEntity scenery = ERR_Actors.Spawn(prop.Prefab, propPosition, prop.Angles);
|
||||
if (!scenery) return Fail("scenery prefab: " + spec.Id);
|
||||
m_Entities.Insert(scenery);
|
||||
}
|
||||
}
|
||||
if (spec.Guarded || !spec.Guards.IsEmpty())
|
||||
{
|
||||
vector guardPosition = position + spec.GuardOffset;
|
||||
@@ -195,6 +208,38 @@ class ERR_SiteService
|
||||
foreach (ERR_SiteRuntime site : m_Sites)
|
||||
if (site.Definition.RevealOnEscape && site.Location) site.Location.Discover(0, 100);
|
||||
}
|
||||
bool CanObserve(IEntity player, ERR_SiteRuntime site)
|
||||
{
|
||||
if (!Replication.IsServer() || m_Stopped || !m_Mode.Active() || m_Mode.Phase() < ERR_Phase.RADIO_SEARCH) return false;
|
||||
if (!site || !site.Location || site.Definition.DiscoverRadius <= 0 || !ERR_Actors.CanFight(player)) return false;
|
||||
vector position = site.Location.Position();
|
||||
vector observer = player.GetOrigin();
|
||||
if (vector.DistanceXZ(observer, position) > site.Definition.DiscoverRadius || Math.AbsFloat(observer[1] - position[1]) > 8) return false;
|
||||
TraceParam trace = new TraceParam();
|
||||
trace.Start = player.GetOrigin() + "0 1.5 0";
|
||||
trace.End = position + "0 1.3 0";
|
||||
trace.Flags = TraceFlags.WORLD | TraceFlags.ENTS;
|
||||
trace.LayerMask = TRACE_LAYER_CAMERA;
|
||||
trace.Exclude = player;
|
||||
return GetGame().GetWorld().TraceMove(trace, null) >= 1;
|
||||
}
|
||||
void Tick(float now)
|
||||
{
|
||||
if (!Replication.IsServer() || m_Stopped || now < m_NextDiscovery) return;
|
||||
m_NextDiscovery = now + 5000;
|
||||
array<IEntity> players = {};
|
||||
ERR_Actors.Players(players, true);
|
||||
foreach (ERR_SiteRuntime site : m_Sites)
|
||||
{
|
||||
if (!site.Location || site.Location.Knowledge() >= 2) continue;
|
||||
foreach (IEntity player : players)
|
||||
{
|
||||
if (!CanObserve(player, site)) continue;
|
||||
site.Location.Discover(0, 100);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
void Rollback()
|
||||
{
|
||||
// Children/items are listed after parents; delete them first.
|
||||
|
||||
Reference in New Issue
Block a user