Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
549 lines
33 KiB
Diff
549 lines
33 KiB
Diff
--- before/README.md
|
||
+++ after/README.md
|
||
@@ -42,14 +42,14 @@
|
||
Earlier five-addon build reports below are historical evidence, not validation of
|
||
the merged project. Nothing has been published to Workshop.
|
||
|
||
-An experimental [server-owner terrain setup](docs/terrain-takeover.md) takes over
|
||
-an existing island scenario without terrain-author changes. Local server/client
|
||
-manager and restart checks pass; authenticated spawning and published server-config
|
||
-validation are still required before retiring connector deployments.
|
||
-Terrain authors can also include the [universal Escape connector](docs/terrain-adapters.md)
|
||
-in a scenario inside their terrain addon. It supplies the shared game mode and
|
||
-managers; the terrain supplies navigation and any airport hints. A ready
|
||
-[Nogova integration](docs/nogova-publishing.md) is provided.
|
||
+[Automatic terrain takeover](docs/terrain-takeover.md) starts Escape when an admin
|
||
+loads Escape alongside a terrain and selects that terrain's existing scenario.
|
||
+No connector, custom startup flag or airport coordinates are required. The map
|
||
+must provide usable navigation and safe placement space. Published authenticated
|
||
+multiplayer validation remains outstanding; see the linked test scope.
|
||
+Terrain authors can optionally use the [shared connector prefab](docs/terrain-adapters.md)
|
||
+for an authored Escape scenario; [Nogova setup](docs/nogova-publishing.md) describes
|
||
+both routes.
|
||
|
||
See the [player route playtest guide](docs/player-route.md), [player lifecycle](docs/player-lifecycle.md), [manual gameplay gaps](docs/base-gameplay-gaps.md), [release qualification](docs/release-qualification.md), [performance comparison tooling](docs/performance-comparison.md), [configuration and commands](docs/configuration.md), [site authoring](docs/site-authoring.md), [architecture](docs/architecture.md) and [migration priorities](docs/base-replacement.md).
|
||
|
||
--- before/addons/core/Scripts/Game/ERR/00_Core/ERR_SiteService.c
|
||
+++ after/addons/core/Scripts/Game/ERR/00_Core/ERR_SiteService.c
|
||
@@ -376,6 +376,7 @@
|
||
{
|
||
vector aircraftPosition = ERR_Actors.Ground(position + ERR_CampTerrain.Rotate(spec.AircraftOffset, spec.Heading));
|
||
if (spec.RequireAirfield && !ERR_AirfieldPlacement.Clear(aircraftPosition)) return Fail("extraction airfield pad is missing or obstructed; no off-airfield fallback");
|
||
+ if (definition.TerrainGenerated && !spec.RequireAirfield && !ERR_AirfieldPlacement.ClearGround(aircraftPosition)) return Fail("generated extraction landing zone is obstructed");
|
||
Vehicle helicopter = Vehicle.Cast(Spawn(m_Mode.Factions().ExtractionPrefab, aircraftPosition));
|
||
if (!helicopter) return Fail("extraction vehicle");
|
||
if (!m_Mode.SetExtractionVehicle(helicopter)) return Fail("extraction vehicle registration");
|
||
--- before/addons/core/Scripts/Game/ERR/00_Core/ERR_TerrainSites.c
|
||
+++ after/addons/core/Scripts/Game/ERR/00_Core/ERR_TerrainSites.c
|
||
@@ -1,4 +1,4 @@
|
||
-// Opt-in terrain adapter: all positions come from this world's native geometry.
|
||
+// Automatic terrain placement: all positions come from this world's native geometry.
|
||
// The original Everon authored/fallback contract remains in ERR_GeneratedSites.
|
||
class ERR_TerrainSites : ERR_GeneratedSites
|
||
{
|
||
@@ -77,12 +77,16 @@
|
||
if (site.ResponseCount > 0 && !Navigable(ERR_Actors.Ground(position + ERR_CampTerrain.Rotate(site.ResponseOffset, heading)))) { m_NavigationRejected++; return false; }
|
||
if (site.Kind != ERR_LocationKind.EXTRACTION) return true;
|
||
float padFloor;
|
||
- if (m_Terrain.Clear(ground, heading, 14, 0.8, padFloor) && ERR_AirfieldPlacement.Clear(ground)) return true;
|
||
+ if (m_Terrain.Clear(ground, heading, 14, 0.8, padFloor))
|
||
+ {
|
||
+ if (site.RequireAirfield && ERR_AirfieldPlacement.Clear(ground)) return true;
|
||
+ if (!site.RequireAirfield && ERR_AirfieldPlacement.ClearGround(ground)) return true;
|
||
+ }
|
||
m_PadRejected++; return false;
|
||
}
|
||
protected vector SamplePosition(ERR_SiteDefinition site)
|
||
{
|
||
- if (site.Kind != ERR_LocationKind.EXTRACTION)
|
||
+ if (site.Kind != ERR_LocationKind.EXTRACTION || 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);
|
||
@@ -95,9 +99,16 @@
|
||
if (!m_Airports.Complete()) return false;
|
||
if (m_Airports.m_Positions.IsEmpty())
|
||
{
|
||
- m_Error = "No airfield found on this terrain. For terrain takeover, configure m_sERR_Airfields with airport X Z coordinates. Authored scenarios can use ERR_Airfield_0 through _15 markers.";
|
||
- m_Result = ERR_PrepareResult.FAILED;
|
||
- return false;
|
||
+ // 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");
|
||
}
|
||
m_AirportsReady = true; m_Began = System.GetTickCount();
|
||
return true;
|
||
--- before/addons/core/Scripts/Game/ERR/00_Core/ERR_TerrainTakeover.c
|
||
+++ after/addons/core/Scripts/Game/ERR/00_Core/ERR_TerrainTakeover.c
|
||
@@ -1,5 +1,5 @@
|
||
-// Experimental opt-in for an existing island scenario. Native multiplayer
|
||
-// replication must be qualified before recommending this route on live servers.
|
||
+// Loading Escape starts it on the selected island scenario automatically.
|
||
+// Existing authored Escape modes and editor worlds are never replaced.
|
||
enum ERR_TerrainTakeoverStage
|
||
{
|
||
INACTIVE,
|
||
@@ -180,6 +180,9 @@
|
||
m_ERRTerrainOriginal = SCR_BaseGameMode.Cast(GetGameMode());
|
||
if (ERR_PreflightTerrainTakeover())
|
||
{
|
||
+ // Escape owns a fresh round; do not save the replaced scenario's state.
|
||
+ SaveGameManager saves = GetSaveGameManager();
|
||
+ if (saves) saves.SetSavingAllowed(false);
|
||
m_ERRTerrainStage = ERR_TerrainTakeoverStage.SETTLING;
|
||
PrintFormat("Reintroduced [terrain takeover]: enabled; configured airports=%1", m_ERRTerrainAirfields.Count());
|
||
}
|
||
--- before/addons/core/Scripts/Game/ERR/00_Core/ERR_TerrainTakeoverSettings.c
|
||
+++ after/addons/core/Scripts/Game/ERR/00_Core/ERR_TerrainTakeoverSettings.c
|
||
@@ -2,7 +2,7 @@
|
||
// added to Escape, and no world resource is changed by these settings.
|
||
modded class SCR_MissionHeader
|
||
{
|
||
- [Attribute("0", desc: "Experimental: replace this scenario's game mode with Escape. Use a plain terrain exploration scenario.")]
|
||
+ [Attribute("1", desc: "Automatically start Escape on the selected terrain scenario. Set false to keep its original game mode.")]
|
||
bool m_bERR_EnableEscape;
|
||
[Attribute("", desc: "Optional airport centres: X Z pairs separated by semicolons, for example 1525 5075. Up to 16 centres.")]
|
||
string m_sERR_Airfields;
|
||
@@ -49,19 +49,19 @@
|
||
static bool Enabled()
|
||
{
|
||
if (!GetGame() || SCR_Global.IsEditMode()) return false;
|
||
+ // Loading Escape is the opt-in. Explicit CLI values override the header,
|
||
+ // including 0 so a host can temporarily keep the original scenario.
|
||
+ string enabled;
|
||
+ if (System.GetCLIParam("errTerrainTakeover", enabled)) return enabled == "1";
|
||
SCR_MissionHeader header = SCR_MissionHeader.Cast(GetGame().GetMissionHeader());
|
||
- if (header && header.m_bERR_EnableEscape) return true;
|
||
- // Also support owner-controlled CLI launches (including local-mod servers
|
||
- // and Workbench play, which may not apply mission-header JSON overrides).
|
||
- string enabled;
|
||
- return System.GetCLIParam("errTerrainTakeover", enabled) && enabled == "1";
|
||
+ return !header || header.m_bERR_EnableEscape;
|
||
}
|
||
static string Airfields()
|
||
{
|
||
+ string centres;
|
||
+ if (System.GetCLIParam("errAirfields", centres)) return centres;
|
||
SCR_MissionHeader header = SCR_MissionHeader.Cast(GetGame().GetMissionHeader());
|
||
- if (header && header.m_bERR_EnableEscape) return header.m_sERR_Airfields;
|
||
- string centres;
|
||
- System.GetCLIParam("errAirfields", centres);
|
||
- return centres;
|
||
+ if (header) return header.m_sERR_Airfields;
|
||
+ return "";
|
||
}
|
||
}
|
||
--- before/docs/nogova-publishing.md
|
||
+++ after/docs/nogova-publishing.md
|
||
@@ -1,14 +1,25 @@
|
||
# Publish Escape on Nogova
|
||
|
||
-The instructions below describe the existing authored integration inside **Nogova
|
||
-Reforger**, referencing the shared prefab in **Co10 Escape Reintroduced**.
|
||
-The requested replacement is [server-owner terrain takeover](terrain-takeover.md),
|
||
-which needs no Nogova edits or Escape dependency in the terrain. That replacement
|
||
-has passed local server/client manager and connected-restart checks. It remains
|
||
-experimental pending authenticated player spawning and published server-config
|
||
-validation. For that route, publish the updated **Co10 Escape Reintroduced** core;
|
||
-no new Nogova connector or Escape dependency in Nogova is needed. Do not remove a working
|
||
-separate connector until the deployment has moved off its mission header.
|
||
+## Server-owner setup (no connector)
|
||
+
|
||
+Publish the updated **Co10 Escape Reintroduced** core. Server owners add Escape
|
||
+and **Nogova Reforger** to their mods and select Nogova's exploration scenario:
|
||
+
|
||
+```text
|
||
+{469880DFE1B48F95}Missions/Nogova_TerrainTest.conf
|
||
+```
|
||
+
|
||
+Escape automatically takes over. There are no required custom header fields,
|
||
+AMP settings, airport coordinates or changes to Nogova. When no physical airport
|
||
+is detected it surveys a clear helicopter landing zone. See
|
||
+[automatic terrain takeover](terrain-takeover.md) for requirements and test scope.
|
||
+Do not remove a connector while the server still selects its `ERR_nogova.conf`
|
||
+header; switch to the existing Nogova scenario first.
|
||
+
|
||
+## Optional authored integration
|
||
+
|
||
+The remaining instructions describe the older authored integration inside Nogova.
|
||
+Use these only when deliberately providing a separate Escape scenario.
|
||
|
||
1. Publish the updated **Co10 Escape Reintroduced** core containing
|
||
`Prefabs/ERR/TerrainConnector.et` and the terrain-independent runtime. The
|
||
@@ -38,7 +49,9 @@
|
||
"scenarioId": "{681921DC9921F915}Missions/ERR_nogova.conf"
|
||
```
|
||
|
||
-Do not use `Worlds/Nogova/Nogova.ent` or the exploration scenario for Escape.
|
||
+For this authored route, use `ERR_nogova.conf`. The automatic route above uses
|
||
+Nogova’s exploration scenario. Neither route accepts `Worlds/Nogova/Nogova.ent`
|
||
+as a mission header.
|
||
The scenario subscene references the shared Escape connector and supplies a mainland
|
||
airport-centre marker at `[1525, 0, 5075]`. The aircraft is placed only after actual
|
||
terrain, navigation, clearance and physical-settling checks.
|
||
--- before/docs/terrain-adapters.md
|
||
+++ after/docs/terrain-adapters.md
|
||
@@ -1,9 +1,9 @@
|
||
# Universal terrain connector
|
||
|
||
-For the requested server-owner-only workflow, see the new
|
||
-[experimental terrain takeover](terrain-takeover.md). It uses an island's
|
||
-existing scenario and requires no terrain-author changes. Keep the connector
|
||
-workflow below for current deployments until multiplayer takeover is qualified.
|
||
+Server owners normally use [automatic terrain takeover](terrain-takeover.md):
|
||
+load Escape and the terrain, select the terrain's existing scenario, and start.
|
||
+No connector or terrain-author changes are required. The authored connector below
|
||
+remains an optional way to provide a dedicated Escape scenario.
|
||
|
||
The reusable connector ships in **Co10 Escape Reintroduced**:
|
||
|
||
@@ -49,9 +49,10 @@
|
||
edit the terrain project or its dependency list. The ready Nogova export is in
|
||
`terrain-integrations/nogova/`; see [Nogova publishing](nogova-publishing.md).
|
||
|
||
-Escape now has an opt-in terrain-independent placement path. It uses the loaded
|
||
+Escape has a terrain-independent placement path. It uses the loaded
|
||
world's bounds and navigation to place the prison, radio, caches and finite enemy
|
||
-sites. Extraction is surveyed on an airfield. The existing Everon scenario keeps
|
||
+sites. Extraction is surveyed on an airfield, or on a clear helicopter landing zone
|
||
+when no airport is detected. The existing Everon scenario keeps
|
||
its authored extraction and fallback rules.
|
||
|
||
Reforger still needs a scenario subscene and mission header on each terrain.
|
||
--- before/docs/terrain-takeover.md
|
||
+++ after/docs/terrain-takeover.md
|
||
@@ -1,77 +1,81 @@
|
||
-# Server-owner terrain setup — experimental
|
||
+# Automatic Escape on added terrains
|
||
|
||
-The opt-in takeover implementation is now in Escape's core source. It loads the
|
||
-scenario selected by the server owner, stops its original game mode, waits for
|
||
-the old managers to unregister, and starts Escape on that same world. Escape
|
||
-does not depend on Nogova or any other island. The island's source, navigation,
|
||
-dependencies and published scenarios are not modified.
|
||
+Add **Co10 Escape Reintroduced** and the desired terrain to the server's mods,
|
||
+select an existing scenario supplied by that terrain, and start the server.
|
||
+Escape automatically replaces the scenario's game mode and creates its prison,
|
||
+objectives, enemy sites and extraction on the loaded terrain. No AMP-specific
|
||
+settings, enable flag, custom mission-header fields, connector addon or
|
||
+terrain-author changes are required.
|
||
|
||
-**Do not replace a working connector deployment yet.** Local dedicated-server
|
||
-joining now passes the client manager checks. Authenticated player spawning and
|
||
-published Workshop/config delivery still need validation. This is development
|
||
-functionality, disabled by default; it does not qualify arbitrary scenario types.
|
||
-
|
||
-## Server-owner startup options
|
||
-
|
||
-The owner can enable takeover through these server startup arguments:
|
||
+For Nogova, select its existing exploration scenario:
|
||
|
||
```text
|
||
--errTerrainTakeover 1 -errAirfields "1525 5075"
|
||
+{469880DFE1B48F95}Missions/Nogova_TerrainTest.conf
|
||
```
|
||
|
||
-Use the airport coordinates appropriate to the selected island. Omit
|
||
-`-errAirfields` for automatic airport discovery. Only the exact enable value `1`
|
||
-activates the CLI option. This route has started Escape on Nogova in the real
|
||
-Linux dedicated-server engine with a separate Windows client, using a private
|
||
-local-mod launch. The engine's local-mod launch uses `-server`
|
||
-and `-addons`; it rejects combining `-addons` with `-config`. See Bohemia's
|
||
-[startup parameter reference](https://community.bistudio.com/wiki/Arma_Reforger:Startup_Parameters).
|
||
+In a standard server configuration this is the value of `game.scenarioId`.
|
||
+Select a mission header (`Missions/*.conf`), not the terrain's world resource.
|
||
+Installing multiple terrains does not choose one: the selected scenario still
|
||
+controls which world the engine loads. Escape has no dependency on any custom
|
||
+terrain. Publish the updated Escape core once; server owners then update it
|
||
+alongside their chosen terrain. Workshop 0.0.45 predates this automatic default.
|
||
|
||
-## Intended mission-header configuration
|
||
+Before publishing the updated core, reopen/rescan the project in Workbench so
|
||
+`Prefabs/ERR/TerrainTakeover.et` and its metadata enter the resource index. An old
|
||
+cache can include the script but still fail to register the replicated prefab.
|
||
+This is a publishing check, not a server-admin setup requirement.
|
||
|
||
-Keep Escape and the desired terrain in the server's mod list. Select a plain
|
||
-exploration scenario already supplied by the terrain. Merge the following fields
|
||
-into the existing `game` object; keep the other server settings and mods:
|
||
+## Placement and requirements
|
||
|
||
-```json
|
||
-{
|
||
- "scenarioId": "{469880DFE1B48F95}Missions/Nogova_TerrainTest.conf",
|
||
- "gameProperties": {
|
||
- "missionHeader": {
|
||
- "m_bERR_EnableEscape": true,
|
||
- "m_sERR_Airfields": "1525 5075",
|
||
- "m_eSaveTypes": 0
|
||
- }
|
||
- }
|
||
-}
|
||
-```
|
||
+Escape prefers detected airfields. If there are no airport markers, standard
|
||
+runways or control towers, it surveys a clear helicopter landing zone instead.
|
||
+Nogova's painted runway therefore needs no administrator coordinates. Every
|
||
+candidate still passes terrain, navigation, slope and aircraft-clearance checks;
|
||
+a missing airport does not allow an unsafe spawn. Searches have finite time and
|
||
+attempt budgets and never fall back to coordinates from another map.
|
||
|
||
-The example selects Nogova's original exploration scenario, **not**
|
||
-`Missions/ERR_nogova.conf`. Server configuration uses Reforger's
|
||
-[mission-header overrides](https://community.bistudio.com/wiki/Arma_Reforger:Server_Config).
|
||
-The custom fields above are supplied by this version of Escape and will not work
|
||
-in an older published build. This document is not a claim that the configuration
|
||
-has been validated on RKS 2.
|
||
+The selected scenario must supply `SCR_BaseGameMode`, a usable `Soldiers` navmesh,
|
||
+world bounds spanning 2–100 km on both axes, and enough clear navigable ground.
|
||
+Navigation remains the terrain author's responsibility. A plain exploration
|
||
+scenario is the recommended starting point. Arbitrary scripted scenarios can
|
||
+retain their own tasks and entities; saved campaigns and every third-party game
|
||
+mode are not qualified. Escape disables further saving when taking over; it does
|
||
+not delete existing saves. Existing authored Escape scenarios and editor worlds
|
||
+are left on their normal startup path.
|
||
|
||
-`m_sERR_Airfields` contains X/Z airport-centre pairs separated by semicolons,
|
||
-for example `1525 5075; 6000 9000`. Omit it or leave it empty for automatic runway
|
||
-and control-tower detection. Nogova needs the hint because its runway is painted
|
||
-into the terrain. Height and aircraft clearance are surveyed in the game; a hint
|
||
-does not bypass placement checks. There can be at most 16 configured centres,
|
||
-and they must be on dry land within the loaded terrain. Existing authored airport
|
||
-markers are retained; configured centres use unused numbered marker slots.
|
||
+## Optional overrides
|
||
|
||
-Use a new session with persistence disabled (`m_eSaveTypes: 0`). Taking over a
|
||
-saved Conflict campaign or another scripted mission is not supported. The map
|
||
-must supply its own `Soldiers` navigation and enough suitable terrain for Escape.
|
||
-Other scenario types can keep their own tasks, scripts and entities even after
|
||
-the game mode is replaced; compatibility must be tested individually.
|
||
+To keep the original scenario while Escape remains loaded, pass
|
||
+`-errTerrainTakeover 0`, or set the mission-header field
|
||
+`m_bERR_EnableEscape` to `false`, then restart. The CLI override takes precedence;
|
||
+only its exact value `1` enables takeover when explicitly supplied. With no
|
||
+overrides, takeover is enabled.
|
||
|
||
-To use the original terrain scenario again, remove the enable override (or set it
|
||
-to `false`) and restart the scenario. Existing authored Escape scenarios are never
|
||
-replaced, even when the setting is enabled.
|
||
+Optional airport-centre hints use `-errAirfields "1525 5075"` or the header field
|
||
+`m_sERR_Airfields`. Use X/Z pairs separated by semicolons, up to 16 centres.
|
||
+Hints must be on dry land inside the terrain and do not bypass clearance checks.
|
||
+An explicitly supplied CLI hint takes precedence over the header. Empty hints
|
||
+use automatic discovery and the landing-zone fallback.
|
||
|
||
-## Implementation and validation
|
||
+## Validation of the automatic default
|
||
+
|
||
+Production-method regressions exercise default activation, editor/authority
|
||
+protection, opt-out precedence, airport discovery and bounded placement. Native
|
||
+Nogova server/client evidence is recorded in
|
||
+[evidence/automatic-terrain-takeover-2026-10-02](evidence/automatic-terrain-takeover-2026-10-02/).
|
||
+The no-flags Nogova test completed two takeovers and two prison phases across a
|
||
+connected restart, with all 16 sites and a supported, fueled helicopter in each
|
||
+round. Client manager checks passed with no client script errors. The evidence
|
||
+report retains server authentication refusals and installed-resource diagnostics;
|
||
+it is not a clean-release verdict. A freshly built package also reached the
|
||
+prison phase on Nogova without flags, loose Escape source, test probes or script
|
||
+errors. Its 351 payload files matched the authored source. Packaging's strict
|
||
+resource/shutdown failures remain recorded separately.
|
||
+Authenticated prisoner spawning, late joining and reconnecting still require a
|
||
+published-build multiplayer check; offline backend-disabled tests cannot prove
|
||
+account admission. Publication and live-server changes remain with the owner.
|
||
+
|
||
+## Handover behavior and earlier validation
|
||
|
||
- Invalid airport syntax, terrain bounds, navigation or missing bootstrap assets
|
||
are rejected before the original mode is stopped.
|
||
@@ -114,10 +118,13 @@
|
||
Repository checks passed. See the
|
||
[feature test report](evidence/terrain-takeover-feature-2026-10-01/report.json).
|
||
|
||
+Earlier opt-in builds required enable flags and airport hints. The following
|
||
+results describe those builds, before the automatic default and landing-zone fallback.
|
||
+
|
||
The command-line listen-server route did not apply either custom mission-header
|
||
overrides or the standard `m_eSaveTypes` override. Its diagnostic read the original
|
||
defaults and left Nogova's mode running. Consequently the example server JSON
|
||
-above still needs testing through the actual dedicated-server route. A read-only
|
||
+used in those tests still needs testing through the actual dedicated-server route. A read-only
|
||
copy of the RKS 2 engine executables was subsequently authorized and completed.
|
||
The dedicated JSON route could not complete addon/backend initialization with
|
||
external networking disabled. The explicit CLI route did load Nogova, generate
|
||
@@ -148,15 +155,3 @@
|
||
[the existing integration instructions](terrain-adapters.md). Do not delete them
|
||
from a deployment that still selects their mission header.
|
||
|
||
-## Publishing this candidate
|
||
-
|
||
-Publish the updated **Co10 Escape Reintroduced** core, including both
|
||
-`TerrainTakeover.et` and its `.meta` file. No new Nogova connector or terrain
|
||
-dependency is needed for this route. Update the terrain only when its own assets
|
||
-or navigation need an update. The owner then selects Nogova's existing exploration
|
||
-scenario and explicitly enables takeover as described above.
|
||
-
|
||
-After publishing, first verify on the test server that an authenticated player
|
||
-receives a prisoner, another player can join later, and both can reconnect and
|
||
-survive a scenario restart. Only then retire the separate connector from that
|
||
-deployment. Publication and RKS 2 configuration changes remain with the owner.
|
||
--- before/tests/test_terrain_sites.py
|
||
+++ after/tests/test_terrain_sites.py
|
||
@@ -3,7 +3,7 @@
|
||
base = ROOT/'addons/core/Scripts/Game/ERR/00_Core'
|
||
s = body(base/'ERR_GeneratedSites.c', 'protected bool SkipOptional(') + '\n' + body(base/'ERR_TerrainSites.c', 'override ERR_PrepareResult Tick(') + '\n' + body(base/'ERR_TerrainSites.c', 'protected bool PrepareAirports(')
|
||
for before, after in [('override ', ''), ('protected ', ''), ('ref ERR_SiteDefinition retained','Site* retained'),
|
||
- ('ERR_SiteDefinition site','Site* site'), ('site.', 'site->'), ('retained.','retained->'),
|
||
+ ('ERR_SiteDefinition site','Site* site'), ('foreach (', 'for ('), ('site.', 'site->'), ('retained.','retained->'),
|
||
('m_Definition.', 'm_Definition->'), ('m_Airports.', 'm_Airports->'), ('System.GetTickCount','Clock::Now'),
|
||
('Replication.IsServer','Server'), ('Math.RandomFloat','RandomFloat'),
|
||
('ERR_PrepareResult.','Result::'), ('ERR_LocationKind.','Kind::')]:
|
||
@@ -20,8 +20,9 @@
|
||
namespace Clock{int now=0;int Now(){return now++;}}
|
||
bool Server(){return true;}int randomCalls=0;float RandomFloat(float a,float b){randomCalls++;return (a+b)/2;}
|
||
template<class...T>void PrintFormat(T...){}
|
||
+void Print(string){}
|
||
namespace Kind{enum{PRISON,RADIO,EXTRACTION,OUTPOST,DEPOT,MORTAR,HOSPITAL};}
|
||
-struct Site{int Kind=Kind::PRISON;string Label="Test site",Id="test";vector Position=Vector(11,0,22);float Heading=0;bool PlacementResolved=false;};
|
||
+struct Site{int Kind=Kind::PRISON;string Label="Test site",Id="test";vector Position=Vector(11,0,22);float Heading=0;bool PlacementResolved=false,RequireAirfield=true;};
|
||
template<class T>struct Array:std::vector<T>{using std::vector<T>::operator=;bool IsEmpty(){return this->empty();}int Count(){return this->size();}int Find(T v){auto i=std::find(this->begin(),this->end(),v);return i==this->end()?-1:i-this->begin();}void Remove(int i){this->erase(this->begin()+i);}void InsertAt(T v,int i){this->insert(this->begin()+i,v);}};
|
||
struct Definition{Array<Site*> Sites;Site* required=nullptr;bool valid=true;bool Valid(){return valid&&(!required||Sites.Find(required)>=0);}};
|
||
struct Airports{Array<vector> m_Positions;Airports(){m_Positions.push_back(Vector(0,0,0));}bool complete=true;int updates=0;void Update(){updates++;}bool Complete(){return complete;}};
|
||
@@ -42,9 +43,11 @@
|
||
int main(){
|
||
{Scheduler s;s.m_Started=false;assert(s.Tick()==Result::FAILED);}
|
||
{Scheduler s;assert(s.Tick()==Result::READY);}
|
||
- // Airport discovery yields without spending the placement budget, then fails clearly when none exist.
|
||
- {Clock::now=0;Scheduler s;s.m_AirportsReady=false;s.airports.complete=false;assert(s.Tick()==Result::PENDING&&s.surveys==0);s.airports.complete=true;s.airports.m_Positions.clear();assert(s.Tick()==Result::FAILED&&!s.m_Error.empty()&&s.surveys==0);}
|
||
+ // Airport discovery yields without spending placement budget, then enables bounded clear-ground extraction.
|
||
+ {Clock::now=0;Scheduler s;s.m_AirportsReady=false;s.airports.complete=false;assert(s.Tick()==Result::PENDING&&s.surveys==0);s.airports.complete=true;s.airports.m_Positions.clear();Site extraction;extraction.Kind=Kind::EXTRACTION;s.m_Pending={&extraction};while(s.Tick()==Result::PENDING){}assert(s.m_Result==Result::READY&&!extraction.RequireAirfield&&extraction.PlacementResolved&&extraction.Label=="Extraction landing zone");}
|
||
{Clock::now=40000;Scheduler s;s.m_AirportsReady=false;Site a;s.m_Pending={&a};while(s.Tick()==Result::PENDING){}assert(s.m_Result==Result::READY&&a.PlacementResolved&&s.airports.updates==1);}
|
||
+ // Recognized airports retain their airfield requirement.
|
||
+ {Clock::now=0;Scheduler s;s.m_AirportsReady=false;Site extraction;extraction.Kind=Kind::EXTRACTION;s.m_Pending={&extraction};while(s.Tick()==Result::PENDING){}assert(s.m_Result==Result::READY&&extraction.RequireAirfield);}
|
||
// Off-origin bounds are sampled; authored Everon positions cannot be retained.
|
||
{Clock::now=0;Scheduler s;Site a,b;s.m_Pending={&a,&b};s.definition.Sites={&a,&b};while(s.Tick()==Result::PENDING){}assert(s.m_Result==Result::READY&&a.PlacementResolved&&b.PlacementResolved);assert(a.Position[0]==5000&&a.Position[2]==-1000&&a.Position[1]>42);int calls=s.surveys;assert(s.Tick()==Result::READY&&s.surveys==calls);}
|
||
// Pending navigation keeps the SAME candidate and yields before spinning or resampling.
|
||
@@ -93,3 +96,69 @@
|
||
}
|
||
''')
|
||
print('PASS: actual navigation readiness supports static meshes, yields only for loadable streaming tiles and refuses unreachable positions')
|
||
+
|
||
+# Execute sampling and clearance selection: an empty airport list must never be
|
||
+# indexed, and generic helicopter pads retain geometry/nav/physical validation.
|
||
+placement = body(base/'ERR_TerrainSites.c', 'protected vector SamplePosition(') + '\n' + body(base/'ERR_TerrainSites.c', 'protected bool Survey(')
|
||
+for before, after in [
|
||
+ ('protected ', ''), ('ERR_SiteDefinition site', 'Site* site'), ('site.', 'site->'),
|
||
+ ('out float floor', 'float& floor'), ('m_Airports.', 'm_Airports->'),
|
||
+ ('Math.', 'Math::'), ('ERR_LocationKind.', 'Kind::'),
|
||
+ ('ERR_Actors.', 'ERR_Actors::'), ('ERR_CampTerrain.', 'ERR_CampTerrain::'),
|
||
+ ('ERR_AirfieldPlacement.', 'ERR_AirfieldPlacement::')]:
|
||
+ placement = placement.replace(before, after)
|
||
+run(r'''
|
||
+#include <cassert>
|
||
+#include <cmath>
|
||
+#include <vector>
|
||
+struct vector {float a[3];float& operator[](int i){return a[i];}};
|
||
+vector Vector(float x,float y,float z){return {{x,y,z}};}
|
||
+vector operator+(vector a,vector b){return Vector(a[0]+b[0],a[1]+b[1],a[2]+b[2]);}
|
||
+namespace Kind {enum {PRISON,EXTRACTION};}
|
||
+struct Site {int Kind=Kind::EXTRACTION,ResponseCount=0;bool RequireAirfield=false,Guarded=false;vector GuardOffset{},ResponseOffset{};};
|
||
+namespace Math {
|
||
+ int indexed=0;const float PI2=6.283185;
|
||
+ float RandomFloat(float a,float b){return (a+b)/2;}
|
||
+ int RandomInt(int a,int b){assert(b>a);indexed++;return a;}
|
||
+ float Cos(float a){return std::cos(a);}float Sin(float a){return std::sin(a);}
|
||
+}
|
||
+namespace ERR_Actors {vector Ground(vector p){return p;}}
|
||
+namespace ERR_CampTerrain {vector Rotate(vector p,float){return p;}}
|
||
+namespace ERR_AirfieldPlacement {
|
||
+ bool airfield=false,ground=true;int airportChecks=0,groundChecks=0;
|
||
+ bool Clear(vector){airportChecks++;return airfield;}
|
||
+ bool ClearGround(vector){groundChecks++;return ground;}
|
||
+}
|
||
+template<class T>struct Array:std::vector<T>{bool IsEmpty(){return this->empty();}int Count(){return this->size();}};
|
||
+struct Airports {Array<vector> m_Positions;};
|
||
+struct Terrain {
|
||
+ bool geometry=true,flat=true;
|
||
+ bool Site(::Site*,vector,float,float& floor){floor=42;return geometry;}
|
||
+ bool Clear(vector,float,float,float,float&){return flat;}
|
||
+};
|
||
+struct Placement {
|
||
+ Airports airports;Airports* m_Airports=&airports;Terrain m_Terrain;
|
||
+ vector m_Low=Vector(1000,0,-5000),m_High=Vector(9000,0,3000);
|
||
+ int m_GeometryRejected=0,m_NavigationRejected=0,m_PadRejected=0;
|
||
+ bool nav=true;bool Navigable(vector){return nav;}
|
||
+''' + placement + r'''
|
||
+};
|
||
+int main(){
|
||
+ Placement s;Site site;float floor=0;
|
||
+ auto p=s.SamplePosition(&site);assert(p[0]==5000&&p[2]==-1000&&Math::indexed==0);
|
||
+ s.airports.m_Positions.push_back(Vector(2000,0,3000));
|
||
+ p=s.SamplePosition(&site);assert(Math::indexed==1&&std::abs(p[0]-1775)<.01&&std::abs(p[2]-3000)<.01);
|
||
+ site.Kind=Kind::PRISON;p=s.SamplePosition(&site);assert(p[0]==5000&&p[2]==-1000&&Math::indexed==1);
|
||
+ site.Kind=Kind::EXTRACTION;
|
||
+ assert(s.Survey(&site,p,0,floor)&&floor==42);assert(ERR_AirfieldPlacement::groundChecks==1&&ERR_AirfieldPlacement::airportChecks==0);
|
||
+ ERR_AirfieldPlacement::ground=false;assert(!s.Survey(&site,p,0,floor)&&s.m_PadRejected==1);
|
||
+ site.RequireAirfield=true;ERR_AirfieldPlacement::ground=true;
|
||
+ assert(!s.Survey(&site,p,0,floor));assert(ERR_AirfieldPlacement::airportChecks==1&&ERR_AirfieldPlacement::groundChecks==2);
|
||
+ ERR_AirfieldPlacement::airfield=true;assert(s.Survey(&site,p,0,floor));
|
||
+ site.RequireAirfield=false;s.m_Terrain.flat=false;assert(!s.Survey(&site,p,0,floor));
|
||
+ s.m_Terrain.flat=true;s.nav=false;assert(!s.Survey(&site,p,0,floor)&&s.m_NavigationRejected==1);
|
||
+ s.nav=true;s.m_Terrain.geometry=false;assert(!s.Survey(&site,p,0,floor)&&s.m_GeometryRejected==1);
|
||
+ assert(ERR_AirfieldPlacement::groundChecks==2&&ERR_AirfieldPlacement::airportChecks==2);
|
||
+}
|
||
+''')
|
||
+print('PASS: real sampling handles absent airports and extraction clearance preserves geometry/navigation/flatness while selecting the correct aircraft survey')
|
||
--- before/tests/test_terrain_takeover.py
|
||
+++ after/tests/test_terrain_takeover.py
|
||
@@ -92,7 +92,7 @@
|
||
#include <map>
|
||
using string=std::string;
|
||
struct SCR_MissionHeader {
|
||
- bool m_bERR_EnableEscape=false; string m_sERR_Airfields;
|
||
+ bool m_bERR_EnableEscape=true; string m_sERR_Airfields;
|
||
static SCR_MissionHeader* Cast(SCR_MissionHeader* header){return header;}
|
||
} header;
|
||
struct Game {SCR_MissionHeader* current=&header;SCR_MissionHeader* GetMissionHeader(){return current;}} game;
|
||
@@ -105,7 +105,8 @@
|
||
''' + settings_cpp(body(settings, 'static bool Enabled(')) +
|
||
settings_cpp(body(settings, 'static string Airfields(')) + r'''
|
||
int main(){
|
||
- assert(!Enabled());
|
||
+ assert(Enabled()&&Airfields().empty()); // Installing Escape requires no header or CLI flags.
|
||
+ header.m_bERR_EnableEscape=false;assert(!Enabled());
|
||
for(string bad:{"0","true","yes","01",""}){System::cli["errTerrainTakeover"]=bad;assert(!Enabled());}
|
||
System::cli["errTerrainTakeover"]="1";System::cli["errAirfields"]="1525 5075";
|
||
assert(Enabled()&&Airfields()=="1525 5075");
|
||
@@ -113,13 +114,19 @@
|
||
currentGame=nullptr;assert(!Enabled());currentGame=&game;
|
||
game.current=nullptr;assert(Enabled());game.current=&header;
|
||
header.m_bERR_EnableEscape=true;header.m_sERR_Airfields="10 20";
|
||
- System::cli["errTerrainTakeover"]="0";
|
||
- assert(Enabled()&&Airfields()=="10 20");
|
||
- header.m_sERR_Airfields="";assert(Airfields().empty()); // Header-selected automatic discovery wins.
|
||
- header.m_bERR_EnableEscape=false;System::cli.clear();assert(!Enabled());
|
||
-}
|
||
-''')
|
||
-print('PASS: explicit CLI opt-in, disabled/editor guards and mission-header airport precedence')
|
||
+ System::cli["errTerrainTakeover"]="0";assert(!Enabled()); // Explicit opt-out wins over the default header.
|
||
+ System::cli.erase("errTerrainTakeover");assert(Enabled());
|
||
+ assert(Airfields()=="1525 5075"); // Explicit CLI coordinates win.
|
||
+ System::cli.erase("errAirfields");assert(Airfields()=="10 20");
|
||
+ header.m_sERR_Airfields="";assert(Airfields().empty());
|
||
+ game.current=nullptr;System::cli.clear();assert(Enabled()&&Airfields().empty());
|
||
+}
|
||
+''')
|
||
+assert '[Attribute("1", desc: "Automatically start Escape' in settings.read_text()
|
||
+startup = body(takeover, 'protected override void OnWorldPostProcess(')
|
||
+assert '!ERR_GameMode.Get() && Replication.IsServer()' in startup
|
||
+assert startup.index('ERR_PreflightTerrainTakeover()') < startup.index('saves.SetSavingAllowed(false)')
|
||
+print('PASS: automatic default, explicit header/CLI opt-out, editor/authored-mode guards, optional airport override precedence and fresh-round saving guard')
|
||
|
||
client = core/'ERR_TerrainTakeoverClient.c'
|
||
managers = core/'ERR_TerrainTakeoverManagers.c'
|