Allow off-airport extraction across terrains; record Workshop 0.0.124
continuous-integration/drone/push Build is passing
continuous-integration/drone/push Build is passing
This commit is contained in:
@@ -186,6 +186,7 @@ class ERR_GeneratedSites
|
||||
if (candidate.Kind == ERR_LocationKind.RADIO && other.Kind == ERR_LocationKind.PRISON) minimum = 1000;
|
||||
if (vector.DistanceXZ(position, other.Position) < minimum) return false;
|
||||
if (candidate.Kind == ERR_LocationKind.PRISON && other.Kind == ERR_LocationKind.EXTRACTION && vector.DistanceXZ(position, other.Position) + 100 >= ERR_GameMode.Get().EscapeRadius()) return false;
|
||||
if (candidate.Kind == ERR_LocationKind.EXTRACTION && other.Kind == ERR_LocationKind.PRISON && vector.DistanceXZ(position, other.Position) + 100 >= ERR_GameMode.Get().EscapeRadius()) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -211,6 +212,10 @@ class ERR_GeneratedSites
|
||||
{
|
||||
if (site.Kind != ERR_LocationKind.EXTRACTION) return true;
|
||||
vector primary = ERR_Actors.Ground(position + ERR_CampTerrain.Rotate(site.AircraftOffset, heading));
|
||||
// Validate the primary pad before committing a random candidate, so an
|
||||
// obstructed clearing is retried instead of failing later during site creation.
|
||||
if (site.RequireAirfield) { if (!ERR_AirfieldPlacement.Clear(primary)) return false; }
|
||||
else if (!ERR_AirfieldPlacement.ClearGround(primary)) return false;
|
||||
return ERR_RescueLanding.Fits(primary);
|
||||
}
|
||||
ERR_PrepareResult Tick()
|
||||
@@ -234,7 +239,7 @@ class ERR_GeneratedSites
|
||||
bool fits;
|
||||
if (!Separated(site, position, fallback)) RecordRejection("site spacing / escape boundary");
|
||||
else if (!m_Terrain.Site(site, position, heading, floor)) RecordRejection(m_Terrain.Rejection());
|
||||
else if (!RescueFits(site, position, heading)) RecordRejection("secondary aircraft pad");
|
||||
else if (!RescueFits(site, position, heading)) RecordRejection("aircraft landing clearance / fleet space");
|
||||
else fits = true;
|
||||
if (fits)
|
||||
{
|
||||
|
||||
@@ -168,7 +168,7 @@ modded class ERR_GameMode
|
||||
if (!ERR_MortarIntelReady()) return;
|
||||
if (!ERR_AirfieldCheck(!Sites().Find("everon-supply-2"), "guarded car-and-crate site absent")) return;
|
||||
ERR_SiteRuntime extraction = Sites().Find("everon-extraction-1");
|
||||
if (!ERR_AirfieldCheck(extraction.Definition.RequireAirfield && vector.DistanceXZ(ExtractionVehicle().GetOrigin(), "4800.953 0 11487.412") < 3 && ERR_CasualtyLoadingRules.AircraftReady(ExtractionVehicle()), "production aircraft settled intact on validated Everon airfield pad")) return;
|
||||
if (!ERR_AirfieldCheck(!extraction.Definition.RequireAirfield && extraction.Definition.PlacementResolved && vector.DistanceXZ(ExtractionVehicle().GetOrigin(), extraction.Definition.Position) < 3 && ERR_CasualtyLoadingRules.AircraftReady(ExtractionVehicle()), "production aircraft settled intact on validated generated landing zone")) return;
|
||||
if (!ERR_AirfieldCheck(!ERR_AirfieldPlacement.Clear(ERR_Actors.Ground("7012 0 7500")), "old off-airfield helicopter position rejected")) return;
|
||||
if (!ERR_AirfieldCheck(!ERR_AirfieldPlacement.Clear(ExtractionVehicle().GetOrigin()), "occupied airfield pad rejected")) return;
|
||||
if (!ERR_AirfieldCheck(Sites().Find("everon-medical-1").Definition.RevealOnEscape && Sites().Find("everon-supply-1").Definition.RevealOnEscape, "two reachable intel sites retained")) return;
|
||||
|
||||
@@ -61,7 +61,7 @@ class ERR_SiteDefinition
|
||||
ResourceName Prefab;
|
||||
[Attribute("0 0 0", UIWidgets.Coords)]
|
||||
vector Position;
|
||||
[Attribute("0", UIWidgets.CheckBox, "Choose dry clear terrain once per round (prison, radio, mortar, hospital, depot or camp)")]
|
||||
[Attribute("0", UIWidgets.CheckBox, "Choose dry clear terrain once per round (prison, extraction, radio, mortar, hospital, depot or camp)")]
|
||||
bool RandomClear;
|
||||
[Attribute("0", UIWidgets.EditBox, "Composition heading in degrees")]
|
||||
float Heading;
|
||||
@@ -185,7 +185,7 @@ class ERR_ScenarioDefinition
|
||||
if (!site || site.Id.IsEmpty() || ids.Contains(site.Id) || site.Prefab.IsEmpty()) return false;
|
||||
if (site.RequireAirfield && site.Kind != ERR_LocationKind.EXTRACTION) return false;
|
||||
if (site.Kind == ERR_LocationKind.RADIO && !site.Guarded && site.Guards.IsEmpty()) return false;
|
||||
if (site.RandomClear && site.Kind != ERR_LocationKind.PRISON && site.Kind != ERR_LocationKind.RADIO && site.Kind != ERR_LocationKind.MORTAR && site.Kind != ERR_LocationKind.HOSPITAL && site.Kind != ERR_LocationKind.OUTPOST && site.Kind != ERR_LocationKind.DEPOT) return false;
|
||||
if (site.RandomClear && site.Kind != ERR_LocationKind.EXTRACTION && site.Kind != ERR_LocationKind.PRISON && site.Kind != ERR_LocationKind.RADIO && site.Kind != ERR_LocationKind.MORTAR && site.Kind != ERR_LocationKind.HOSPITAL && site.Kind != ERR_LocationKind.OUTPOST && site.Kind != ERR_LocationKind.DEPOT) return false;
|
||||
if (site.Label.IsEmpty() || site.Label.Length() > 64) return false;
|
||||
if (!ERR_SiteDiscoveryRules.ValidRadius(site.Kind, site.DiscoverRadius)) return false;
|
||||
bool optionalSite = site.Kind == ERR_LocationKind.DEPOT || site.Kind == ERR_LocationKind.OUTPOST || site.Kind == ERR_LocationKind.HOSPITAL;
|
||||
|
||||
@@ -398,7 +398,7 @@ class ERR_SiteService
|
||||
{
|
||||
rootPosition = ERR_Actors.Ground(position + ERR_CampTerrain.Rotate(spec.AircraftOffset, spec.Heading));
|
||||
if (spec.RequireAirfield && !ERR_AirfieldPlacement.Clear(rootPosition)) return Fail("extraction airfield pad is missing or obstructed; no off-airfield fallback");
|
||||
if (definition.TerrainGenerated && !spec.RequireAirfield && !ERR_AirfieldPlacement.ClearGround(rootPosition)) return Fail("generated extraction landing zone is obstructed");
|
||||
if ((definition.TerrainGenerated || spec.RandomClear) && !spec.RequireAirfield && !ERR_AirfieldPlacement.ClearGround(rootPosition)) return Fail("generated extraction landing zone is obstructed");
|
||||
}
|
||||
// Spawn the replicated marker at the final pickup coordinate from the start.
|
||||
IEntity root = ERR_Actors.Spawn(spec.Prefab, rootPosition, Vector(spec.Heading, 0, 0));
|
||||
|
||||
@@ -7,8 +7,6 @@ class ERR_TerrainSites : ERR_GeneratedSites
|
||||
protected vector m_Candidate;
|
||||
protected float m_CandidateHeading;
|
||||
protected int m_CandidateBegan;
|
||||
protected ref ERR_ReconAirports m_Airports = new ERR_ReconAirports();
|
||||
protected bool m_AirportsReady;
|
||||
protected int m_GeometryRejected, m_NavigationRejected, m_PadRejected;
|
||||
protected string m_NavigationReason;
|
||||
// Keep the same work slice; difficult terrain gets more slices, not larger stalls.
|
||||
@@ -38,7 +36,7 @@ class ERR_TerrainSites : ERR_GeneratedSites
|
||||
if (!world || !world.GetNavmeshWorldComponent("Soldiers")) { m_Error = "Terrain requires its own Soldiers navigation mesh."; return false; }
|
||||
m_Started = true; m_Definition = definition; definition.TerrainGenerated = true;
|
||||
m_Spacing = Spacing(m_Low, m_High);
|
||||
// Resolve the terrain's airport first, then keep the prison in escape range.
|
||||
// Resolve a clear landing zone first, then keep the prison in escape range.
|
||||
array<int> order = {ERR_LocationKind.EXTRACTION, ERR_LocationKind.PRISON, ERR_LocationKind.RADIO, ERR_LocationKind.DEPOT, ERR_LocationKind.HOSPITAL, ERR_LocationKind.MORTAR, ERR_LocationKind.OUTPOST};
|
||||
foreach (int kind : order)
|
||||
foreach (ERR_SiteDefinition site : definition.Sites)
|
||||
@@ -47,7 +45,7 @@ class ERR_TerrainSites : ERR_GeneratedSites
|
||||
site.PlacementResolved = false;
|
||||
// The queue, rather than RandomClear, owns placement including extraction.
|
||||
site.RandomClear = false;
|
||||
if (kind == ERR_LocationKind.EXTRACTION) { site.RequireAirfield = true; site.AircraftOffset = vector.Zero; }
|
||||
if (kind == ERR_LocationKind.EXTRACTION) { site.RequireAirfield = false; site.Label = "Extraction landing zone"; site.AircraftOffset = vector.Zero; }
|
||||
if (kind == ERR_LocationKind.DEPOT) site.Label = "Supply cache";
|
||||
m_Pending.Insert(site);
|
||||
}
|
||||
@@ -105,9 +103,7 @@ class ERR_TerrainSites : ERR_GeneratedSites
|
||||
float padFloor;
|
||||
if (m_Terrain.Clear(ground, heading, 14, 0.8, padFloor))
|
||||
{
|
||||
bool primaryClear;
|
||||
if (site.RequireAirfield) primaryClear = ERR_AirfieldPlacement.Clear(ground);
|
||||
else primaryClear = ERR_AirfieldPlacement.ClearGround(ground);
|
||||
bool primaryClear = ERR_AirfieldPlacement.ClearGround(ground);
|
||||
if (!primaryClear) RecordRejection("primary aircraft clearance");
|
||||
else if (!ERR_RescueLanding.Fits(ground)) RecordRejection("secondary aircraft pad");
|
||||
else return true;
|
||||
@@ -117,68 +113,19 @@ class ERR_TerrainSites : ERR_GeneratedSites
|
||||
}
|
||||
protected vector SamplePosition(ERR_SiteDefinition site)
|
||||
{
|
||||
if (site.Kind != ERR_LocationKind.EXTRACTION || !site.RequireAirfield || m_Airports.m_Positions.IsEmpty())
|
||||
return Vector(Math.RandomFloat(m_Low[0] + 60, m_High[0] - 60), 0, Math.RandomFloat(m_Low[2] + 60, m_High[2] - 60));
|
||||
vector centre = m_Airports.m_Positions[Math.RandomInt(0, m_Airports.m_Positions.Count())];
|
||||
float angle = Math.RandomFloat(0, Math.PI2), radius = Math.RandomFloat(0, 450);
|
||||
return centre + Vector(Math.Cos(angle) * radius, 0, Math.Sin(angle) * radius);
|
||||
}
|
||||
protected void ExpandExtractionSearch(ERR_SiteDefinition site)
|
||||
{
|
||||
if (site.Kind != ERR_LocationKind.EXTRACTION || !site.RequireAirfield) return;
|
||||
if (m_Attempts < 12000 && System.GetTickCount() - m_Began < 30000) return;
|
||||
// An airport is a preferred starting area, not proof that a usable pad exists.
|
||||
// Use the remaining budget to find a clear helicopter landing zone elsewhere.
|
||||
// Do not reset the total deadline or carry an airport candidate into this pass.
|
||||
site.RequireAirfield = false;
|
||||
site.Label = "Extraction landing zone";
|
||||
m_HaveCandidate = false;
|
||||
ReportSearch(site, "airport search exhausted; expanding search");
|
||||
PrintFormat("Reintroduced [terrain]: %1 airport search exhausted; surveying off-airport landing zones with full aircraft clearance", site.Id);
|
||||
}
|
||||
protected bool PrepareAirports()
|
||||
{
|
||||
if (m_AirportsReady) return true;
|
||||
// The scenario ticks every 250ms. One 2km tile per tick made Chernarus
|
||||
// spend roughly 48 seconds just waiting between airport queries. Batch
|
||||
// inexpensive tiles, but yield after 4ms or eight queries. A native query
|
||||
// itself cannot be interrupted, so check the budget after every call.
|
||||
int started = System.GetTickCount();
|
||||
for (int tile = 0; tile < 8; tile++)
|
||||
{
|
||||
m_Airports.Update();
|
||||
if (m_Airports.Complete() || System.GetTickCount() - started >= 4) break;
|
||||
}
|
||||
if (!m_Airports.Complete()) return false;
|
||||
if (m_Airports.m_Positions.IsEmpty())
|
||||
{
|
||||
// A painted runway or an island without an airport must not require a
|
||||
// connector or administrator coordinates. Survey a helicopter landing zone
|
||||
// with the same ground/navmesh/aircraft checks and finite placement budget.
|
||||
foreach (ERR_SiteDefinition site : m_Pending)
|
||||
if (site.Kind == ERR_LocationKind.EXTRACTION)
|
||||
{
|
||||
site.RequireAirfield = false;
|
||||
site.Label = "Extraction landing zone";
|
||||
}
|
||||
Print("Reintroduced [terrain]: no airport infrastructure found; surveying a clear helicopter extraction landing zone");
|
||||
}
|
||||
PrintFormat("Reintroduced [generation]: airport survey ready in %1 ms; airports=%2", System.GetTickCount() - m_Began, m_Airports.m_Positions.Count());
|
||||
m_AirportsReady = true; m_Began = System.GetTickCount();
|
||||
return true;
|
||||
// Helicopters need a clear pad, not airport infrastructure or an airport-first pass.
|
||||
return Vector(Math.RandomFloat(m_Low[0] + 60, m_High[0] - 60), 0, Math.RandomFloat(m_Low[2] + 60, m_High[2] - 60));
|
||||
}
|
||||
override ERR_PrepareResult Tick()
|
||||
{
|
||||
if (m_Result != ERR_PrepareResult.PENDING) return m_Result;
|
||||
if (!m_Started || !Replication.IsServer()) return ERR_PrepareResult.FAILED;
|
||||
if (!PrepareAirports()) return m_Result;
|
||||
int tick = System.GetTickCount();
|
||||
while (m_Index < m_Pending.Count())
|
||||
{
|
||||
if (m_StartupOnly && StartupReady()) return ERR_PrepareResult.PENDING;
|
||||
ERR_SiteDefinition site = m_Pending[m_Index];
|
||||
ReportProgress(site);
|
||||
ExpandExtractionSearch(site);
|
||||
if (m_Attempts >= SearchAttempts() || System.GetTickCount() - m_Began >= SearchMilliseconds())
|
||||
{
|
||||
PrintFormat("Reintroduced [terrain survey exhausted]: id=%1 attempts=%2 rejected geometry=%3 navigation=%4 aircraft=%5", site.Id, m_Attempts, m_GeometryRejected, m_NavigationRejected, m_PadRejected);
|
||||
|
||||
Reference in New Issue
Block a user