Compare commits
59
Commits
a951a96edc
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
47cd4fa2bc | ||
|
|
e421dcfc15 | ||
|
|
161100cf08 | ||
|
|
a7452f8be6 | ||
|
|
8b1c3cf88a | ||
|
|
460d9808e5 | ||
|
|
518a1a5744 | ||
|
|
55c0ec775a | ||
|
|
9caed3f12d | ||
|
|
9418b6950a | ||
|
|
828bb0a1a7 | ||
|
|
4f02ad64b4 | ||
|
|
6ba3036de6 | ||
|
|
b8dfa9184e | ||
|
|
0e1e7790f9 | ||
|
|
2c2158702d | ||
|
|
d21adb90d5 | ||
|
|
6738eda748 | ||
|
|
2dcbe3107d | ||
|
|
3ec882dc4e | ||
|
|
08e5cbf5ea | ||
|
|
681d5e5eae | ||
|
|
213c31dfb2 | ||
|
|
5453159867 | ||
|
|
c6ee764c6c | ||
|
|
970dd338f5 | ||
|
|
95d21b06ff | ||
|
|
9233320fd1 | ||
|
|
6397fe89b6 | ||
|
|
3768ce7ed8 | ||
|
|
253dde90cd | ||
|
|
ac285b5c97 | ||
|
|
f41d8ba2f9 | ||
|
|
87d85ff02e | ||
|
|
ae2aedd131 | ||
|
|
cc322572b4 | ||
|
|
372fca7a68 | ||
|
|
82b52ae1fc | ||
|
|
d5464f7438 | ||
|
|
0e71ef1fa7 | ||
|
|
38fd8f7dd2 | ||
|
|
8f2e1f210d | ||
|
|
7a7ac59edf | ||
|
|
96f471ef4a | ||
|
|
f15c915e54 | ||
|
|
a66aca35bf | ||
|
|
ae9aaafaae | ||
|
|
009b2fb591 | ||
|
|
07c4d70de5 | ||
|
|
a2106cfa26 | ||
|
|
0a7102cd51 | ||
|
|
3b920e0715 | ||
|
|
7b13231f29 | ||
|
|
fda19a5200 | ||
|
|
01994ed344 | ||
|
|
e4ca37dfc1 | ||
|
|
0048527fe2 | ||
|
|
bf85f62dca | ||
|
|
6a667d8deb |
+31
@@ -0,0 +1,31 @@
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: discord-changelog
|
||||
|
||||
clone:
|
||||
depth: 0
|
||||
|
||||
steps:
|
||||
- name: test-changelog
|
||||
image: python:3.13-alpine
|
||||
commands:
|
||||
- apk add --no-cache git
|
||||
- python tools/build_admin_menu.py --check
|
||||
- python -m unittest discover -s tests -p test_admin_menu_sources.py
|
||||
- python -m unittest discover -s tests -p test_discord_changelog_ci.py
|
||||
- python -m unittest discover -s tests -p test_workshop_changelog.py
|
||||
|
||||
- name: notify-discord
|
||||
image: python:3.13-alpine
|
||||
environment:
|
||||
DISCORD_WEBHOOK_URL:
|
||||
from_secret: discord_webhook_url
|
||||
commands:
|
||||
- apk add --no-cache git
|
||||
- python tools/discord_changelog_ci.py
|
||||
|
||||
trigger:
|
||||
branch:
|
||||
- main
|
||||
event:
|
||||
- push
|
||||
@@ -6,6 +6,11 @@ __pycache__/
|
||||
.local/
|
||||
profiles/
|
||||
|
||||
# Retired RHS 1.9 overrides: keep archived/generated upstream source out of Git.
|
||||
# These files must be absent from the active addon and future Workshop packages.
|
||||
/addons/core/Scripts/Game/Editor/Components/EditableEntity/RHS_CAS_EditableEntityComponent.c
|
||||
/addons/core/Scripts/Game/Vehicle/RHS_DestructionTireComponent.c
|
||||
|
||||
# Preserve exact raw dedicated diagnostic evidence, outside release addons.
|
||||
!docs/evidence/**/*.log
|
||||
!docs/evidence/**/resourceDatabase.rdb
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
# Push changelogs
|
||||
|
||||
Drone is the sole Discord changelog sender. Its first delivery was verified in
|
||||
build #1 on 2026-10-17, and the user confirmed receipt and disabled local sending.
|
||||
Do not run a local/manual Discord sender after pushes or as an automatic fallback.
|
||||
`tools/discord_changelog.py` is disabled and cannot send messages.
|
||||
|
||||
For player-facing changes, maintain the newest entry in `CHANGELOG.md` before
|
||||
pushing to `origin/main`. Keep it concise and describe only fixes/features, without
|
||||
server, deployment, Git push status, operational disclaimers or footers. Do not
|
||||
backfill old changes unless requested. Internal workflow-only changes need no
|
||||
announcement.
|
||||
|
||||
Before every push to `origin/main`, fetch the latest **published** Workshop version
|
||||
before preparing the commit. The user should not need to supply a version number.
|
||||
On this workstation use the live in-game catalogue (the website can be cached):
|
||||
|
||||
```
|
||||
python3 tools/workshop_changelog.py --sync --native-compat .local/rhs-19-work/compat
|
||||
```
|
||||
|
||||
This runs a bounded, silent query in a disposable game profile and updates only
|
||||
the changelog version preamble. It does not download or publish an addon. Never
|
||||
terminate an existing editor to acquire the private prefix. On other machines,
|
||||
`--sync` without `--native-compat` checks the official Workshop page, verifies the
|
||||
addon identity and published/approved version, and rejects a downgrade. If a
|
||||
lookup fails or returns stale data, resolve it through a fresh Workshop query;
|
||||
do not guess, use a local bundle as publication proof, or ask the user to enter
|
||||
the version manually. Include the synchronized preamble in the requested push.
|
||||
|
||||
After a confirmed successful Workbench Workshop upload, run
|
||||
`python3 tools/workshop_changelog.py --published` before committing the changelog.
|
||||
This reads the local publishing manifest and records the version in the changelog
|
||||
preamble. Compilation/bundling alone is not confirmation of publication; check
|
||||
the successful upload result first. With no flag the helper only displays the
|
||||
bundled version. Drone includes the recorded version with new changelog entries;
|
||||
a version-only edit does not announce an old entry again.
|
||||
|
||||
The `.drone.yml` pipeline posts the latest changed entry using Drone's
|
||||
`discord_webhook_url` repository secret. See `docs/discord-changelog-ci.md`.
|
||||
Check the Drone result and notification log when available; a successful Git push
|
||||
alone does not prove delivery. Report delivery failures honestly. Inspect Discord
|
||||
before rerunning an uncertain delivery or rebuilding the same push, since builds
|
||||
do not share delivery state and retries can produce duplicates.
|
||||
|
||||
The saved local webhook in `.local/discord-webhook.url` is no longer used for
|
||||
post-push notifications. Never print it, commit it, or put it into changelogs or
|
||||
command-line arguments. Do not configure an additional Discord push webhook
|
||||
alongside Drone.
|
||||
+217
@@ -0,0 +1,217 @@
|
||||
# Changelog
|
||||
|
||||
Latest published Workshop version: **0.0.149**
|
||||
|
||||
## 2026-11-07 — Clearer setting choices
|
||||
|
||||
- Replaced typed loot, starting scenario, AI and weather modes with labelled selection buttons.
|
||||
- Guard spacing and HQ participation buttons now save directly, without redundant input fields or Apply.
|
||||
- Selected choices highlight after server confirmation.
|
||||
|
||||
## 2026-11-07 — Interrupted mission results
|
||||
|
||||
- Restarts with players connected now count as failed missions, including recovery after a forced stop. Empty restarts remain excluded.
|
||||
|
||||
## 2026-11-07 — Shore boats
|
||||
|
||||
- Added optional Motorboat spawns along accessible shores, with better chances on coasts facing another island.
|
||||
- Added boat limits and coastal spawn chances under Content → Shore boats.
|
||||
- Added Motorboat to Recommended addons with dependency checks and a settings shortcut.
|
||||
|
||||
## 2026-11-06 — Crash-site salvage
|
||||
|
||||
- Added configurable smoking helicopter and armored wreck sites with finite loot crates.
|
||||
- Crash-site loot defaults to random quality, from poor pistol supplies to rich scoped-rifle and AA/RPG finds; Custom mode keeps manual loot controls.
|
||||
- Added crash-site count, wreck mix and loot settings under Forces → Crash sites.
|
||||
|
||||
## 2026-11-06 — More room in admin lists
|
||||
|
||||
- Expanded scrolling lists into unused menu space and compacted item weight controls to show more settings at once.
|
||||
|
||||
## 2026-11-06 — Clearer content controls
|
||||
|
||||
- Unified mod, item and vehicle controls under Content, with searchable lists and clear enabled/disabled states.
|
||||
- Added mod switches that remember individual choices.
|
||||
- Removed the obsolete RHS 1.9 script overrides so updated RHS scripts are used directly.
|
||||
- Discover vehicles from installed mods and assign them to garages, yards, traffic, enemy patrols and convoy escort or supply roles.
|
||||
|
||||
## 2026-11-06 — Rural traffic and menu scaling
|
||||
|
||||
- Added V2101 variants to civilian traffic and occasional DLC tractors beside farm buildings.
|
||||
- Kept the mission administration panel within the screen at larger UI scales.
|
||||
|
||||
## 2026-11-06 — More parked cars
|
||||
|
||||
- Added T601 and V2101 cars and optional Rally variants to garage vehicle spawns.
|
||||
- Added vehicle parking in suitable residential yards, sharing the garage vehicle limit.
|
||||
|
||||
## 2026-11-05 — RHS compatibility
|
||||
|
||||
- Included the RHS 1.9 script compatibility fixes in Escape, without an extra compatibility addon.
|
||||
- Fixed recovering squads losing their queued reserve orders.
|
||||
- Clarified radio-intel requirements and the airborne extraction objective.
|
||||
- Prevented combat epinephrine use when it would provide no treatment benefit.
|
||||
|
||||
## 2026-11-05 — Garage vehicle access
|
||||
|
||||
- Fixed garage vehicles refusing player entry because of civilian faction hostility.
|
||||
|
||||
## 2026-11-03 — Spectator body camera and player names
|
||||
|
||||
- Spectator cycling now includes your own unconscious body, with ground-level framing and orbit controls.
|
||||
- The statistics dashboard now shows player names alongside their account IDs.
|
||||
|
||||
## 2026-11-01 — Dedicated-server names
|
||||
|
||||
- Server-name telemetry now reads the running dedicated-server configuration before checking lobby metadata.
|
||||
|
||||
## 2026-11-01 — Statistics dashboard
|
||||
|
||||
- Added a private statistics webpage with server win/loss records, last received reports, mission details, player totals and event history, with manual refresh.
|
||||
- Mission telemetry automatically reports server names and keeps dashboard labels updated when servers are renamed.
|
||||
|
||||
## 2026-11-01 — Checkpoints and reconnects
|
||||
|
||||
- Added larger checkpoints with eight guards, extra sandbags and longer barrier approaches on wide roads.
|
||||
- Added separate small/large checkpoint counts and a road-width cutoff under Forces → Checkpoints.
|
||||
- Fixed saving mission settings on fresh profiles.
|
||||
- Returning players receive a fresh soldier when their reserved body is gone, until extraction begins.
|
||||
|
||||
## 2026-10-31 — Terrain mission text
|
||||
|
||||
- Terrain takeover now shows Escape’s mission description on the loading screen instead of the original terrain scenario’s text.
|
||||
|
||||
## 2026-10-31 — Extraction finish
|
||||
|
||||
- Escape statistics open after 30 seconds of airborne evacuation instead of a long flight across the escape boundary.
|
||||
- Rescue helicopters remain safely in place while the results screen is open.
|
||||
|
||||
## 2026-10-31 — Recovery forecast
|
||||
|
||||
- All-down rounds now check whether anyone can wake within five minutes before starting the defeat countdown, while preserving usable self-epinephrine opportunities.
|
||||
|
||||
## 2026-10-30 — Passenger handbrake
|
||||
|
||||
- The front passenger can hold Space to apply the handbrake and release it to let go.
|
||||
|
||||
## 2026-10-30 — Roadblocks, fuel pumps and garage vehicles
|
||||
|
||||
- Added civilian cars and optional motorbikes to garages near exploring players, with gradual spawning, retention of used vehicles, and menu controls for count, chance, radius and spawn delay.
|
||||
- Reduced roadside checkpoint width and length to fit more locations while retaining four guards and a clear vehicle passage.
|
||||
- Fixed existing petrol-station pumps losing their refuelling service when Escape starts on a terrain scenario.
|
||||
|
||||
## 2026-10-30 — Community statistics delivery
|
||||
|
||||
- Fixed rejected mission reports so wins, defeats and queued results can update community statistics.
|
||||
- Game servers now report the loaded island name in statistics instead of the Game Master scenario path.
|
||||
|
||||
## 2026-10-30 — Quieter AI traffic
|
||||
|
||||
- Limited AI drivers to short horn beeps with a 30-second cooldown to reduce repeated honking.
|
||||
|
||||
## 2026-10-30 — Results reading time and spectator hints
|
||||
|
||||
- Kept mission results open for two minutes before automatic restart.
|
||||
- Prevented objective hints from interrupting unconscious spectators and stabilized recovery messages to reduce repeated switching.
|
||||
|
||||
## 2026-10-30 — Results panel visibility
|
||||
|
||||
- Fixed the blank end-of-mission panel so victory, failure and mission statistics are visible before restart.
|
||||
|
||||
## 2026-10-30 — Community mission statistics
|
||||
|
||||
- Mission statistics now connect automatically on community servers without manual registration.
|
||||
- Server administrators can disable reporting under Mission → Statistics.
|
||||
- Victory and failure screens show the current map's wins, failures and win rate on this server and across all reporting servers.
|
||||
|
||||
## 2026-10-30 — Mission results
|
||||
|
||||
- Added an after-mission summary with duration, kills, friendly kills, deaths, times downed and recoveries for each participant.
|
||||
- Results show who escaped and retain the records of players who disconnected.
|
||||
|
||||
## 2026-10-28 — Extraction landing zones
|
||||
|
||||
- Extraction can use suitable clear ground anywhere on the terrain, including original Everon, without prioritizing airports.
|
||||
- Landing zones retain terrain, obstacle, aircraft-space and escape-boundary checks.
|
||||
|
||||
## 2026-10-28 — Starting supplies and mission statistics
|
||||
|
||||
- Oversized starting weapons and other items remain available beside the backpack instead of delaying preparation and being removed.
|
||||
- Choose whether each starting item spawns inside the backpack or on the ground beside it, with separate quantities for each destination.
|
||||
|
||||
- Added optional mission statistics reporting for starts, restarts, terrain, mods, player participation and mission results.
|
||||
- Statistics are enabled by default and can be disabled under Mission → Statistics; player escape records distinguish actual escapees from spectators.
|
||||
|
||||
## 2026-10-25 — Startup error screen
|
||||
|
||||
- Failed mission starts now show an interactive error screen with a Return to main menu button.
|
||||
- Esc opens the normal pause menu from the error screen, so leaving no longer requires closing Arma.
|
||||
|
||||
## 2026-10-25 — Small-terrain startup
|
||||
|
||||
- Fixed low-lying dry terrain being rejected during site placement, player spawning and mortar setup; water checks now use the actual ground surface.
|
||||
- Helicopter landing sites can use clear ground near roads and use the landing-pad flatness limit instead of the stricter camp limit.
|
||||
- Fixed aircraft clearance checks using the wrong ground height during extraction placement.
|
||||
|
||||
## 2026-10-24 — Placement on smaller terrains
|
||||
|
||||
- Improved mission placement on small maps with shorter airport separation for prisons and support for terrain bounds down to 1 km.
|
||||
- Extraction searches can find a clear landing zone elsewhere when an airport has no suitable space.
|
||||
- Difficult terrain searches try more positions for longer before giving up.
|
||||
- Startup failures show their cause directly on the loading screen, including placement rejection reasons.
|
||||
|
||||
## 2026-10-21 — Radio intel progression
|
||||
|
||||
- Radio intel unlocks after five distinct non-radio sites are fully identified; finding and using radios in person remains unrestricted.
|
||||
- Configure the requirement under Intel → Radio intel, with live progress and a zero option to disable it for the next round.
|
||||
- Early ordinary folders reveal other sites; fixed radio documents stay available and are read automatically after unlocking.
|
||||
|
||||
## 2026-10-21 — Recon drones, body burial and spectator camera
|
||||
|
||||
- Players holding a shovel can bury dead bodies and their remaining equipment.
|
||||
- Added optional enemy reconnaissance drones using Realistic Combat Drones, responding to recent HQ sightings.
|
||||
- Configure the active drone limit and launch cooldown under HQ → Recon drones; disabled by default.
|
||||
- Buffered the full first-person spectator camera position to reduce shaking from remote character movement corrections.
|
||||
|
||||
## 2026-10-20 — Optional site map hints
|
||||
|
||||
- Added an Intel menu option to show sites as question marks until intel identifies their types.
|
||||
|
||||
## 2026-10-20 — Safe intel cleanup
|
||||
|
||||
- Removing used intel folders preserves enemies' ability to discover stolen documents.
|
||||
- Used ordinary folders are cleaned up even when no new locations remain to reveal.
|
||||
- Failed inventory cleanup retries safely without granting extra intel or deleting objective documents.
|
||||
|
||||
## 2026-10-20 — Hunter recovery and intel cleanup
|
||||
|
||||
- Hunters with a blocked driver seat or failed reboarding can abandon the vehicle and continue on foot.
|
||||
- Hunters remember failed approach positions and pause before attempting another coordinated manoeuvre.
|
||||
- Ordinary intel folders are removed after revealing a location; objective documents are retained.
|
||||
|
||||
## 2026-10-17 — Fewer Šárka hunters
|
||||
|
||||
- Set how many anger points are needed per Šárka in the mission menu. Higher values mean fewer cars.
|
||||
- Default: one car per three anger points, rounded down. Zero disables new waves.
|
||||
|
||||
## 2026-10-17 — Mission status readability
|
||||
|
||||
- Fixed clipped status and journal text so long reports can be scrolled.
|
||||
- Fixed duplicated Šárka wave details and restored the hunting-patrol summary.
|
||||
|
||||
## 2026-10-17 — Mission menu navigation
|
||||
|
||||
- Reorganized the mission menu with main tabs across the top and related submenus on the left.
|
||||
|
||||
## 2026-10-17 — Configurable Šárka hunters
|
||||
|
||||
- Added a mission-menu setting for the number of Šárka special-forces cars per anger point.
|
||||
- Set cars per anger point to zero to disable new waves. Deployed teams remain.
|
||||
|
||||
## 2026-10-17 — Discord changelog test
|
||||
|
||||
- Test notification for automatic mission changelogs.
|
||||
|
||||
## 2026-10-17 — Changelog notifications
|
||||
|
||||
- Added automatic Discord changelogs for new mission updates.
|
||||
@@ -6,7 +6,9 @@ An independently authored replacement for the abandoned Escapists base mission,
|
||||
|
||||
The current base supplies prison spawning, two independent scavenging/clue routes, finite medical and outpost stock, an optional ground vehicle, physical radio documents, named discovery markers, native objectives, a separate radio-defence service, capacity-checked helicopter extraction, identity-based partial results, early helicopter-loss defeat, saved mission time/speed, defeat/restart, identity-based lives and local spectator following. Authored site definitions and an injected vanilla faction/asset catalog own the initial physical population. Core provides administration and startup diagnostics; the AI, Intel, Chat and Map modules extend its interfaces inside one addon. HQ coordinates enemy responses through physical radios and available forces.
|
||||
|
||||
The [local development playtest handoff](docs/playtest-build.md) contains the earlier `5e74269` checkpoint: five native addon packages, matching source and separate manual launch scripts. It does not contain the newer HQ memory or RHS/WCS/BMP integration. Two scripted Core escape/restart rounds pass616 assertions; packaged Core and full-companion startup both pass. Full human completion and multiplayer remain to be tested.
|
||||
The complete repository validator passes at the [5 November integration checkpoint](docs/evidence/integration-check-2026-11-05/report.json), bound to 1,229 unchanged input files. These offline checks do not establish native gameplay or multiplayer correctness.
|
||||
|
||||
The [4 October development playtest handoff](.build/playtest-2026-10-04-ui-bmp/README.md) includes the inventory callback and BMP ammunition-accounting corrections. Its 439 authored files match 378 PAK payloads and three runtime files. At that archived checkpoint, code/tests/tools bound to 309 passing regression programs; exact-source normal escape and ordinary packaged startup complete with retained engine errors. It remains an **unqualified development candidate**. Existing missions and prior packages are preserved. See [current status](docs/current-status.md) for exact scope and limits.
|
||||
|
||||
The [AI migration checkpoint](docs/ai-migration-progress.md) adds delivered-report memory, bounded trajectory prediction, physical infantry pursuit, status checks, body observations and finite equipment. Native memory and actual-sighting response fixtures pass their assertions, with four retained upstream localization errors. See [current development status](docs/current-status.md) for source-bound checkpoints. The earlier five-addon checkpoint shows the [real prisoner view and starting objective](docs/evidence/game-2026-09-19-125906-integrated-prison/ordinary-prison-view.png). Native tests cover scripted escape/restart, finite mortar support, physical radio delivery, shared-map ownership, and the reusable aircraft controller's vertical flight, airborne cancellation and an isolated 20 m fixed route. Archived five-addon packages pass ordinary Chat startup without loose successor source and compile/prepare the empty mission on an isolated Linux server. The latter retains four installed-asset errors reproduced by a vanilla control.
|
||||
|
||||
@@ -18,7 +20,12 @@ Later ordinary input checks demonstrate walking, clean inventory interaction and
|
||||
|
||||
Open **`addons/core/addon.gproj`**, titled **Co10 Escape Reintroduced**. This is the
|
||||
single project to develop, package and eventually publish as one Workshop mod.
|
||||
Use **`Missions/ERR_Everon.conf`** (`{C1E20667DCE324ED}`) for the full scenario.
|
||||
Select **Co10 Escape Reintroduced — Automatic terrain**,
|
||||
**`Missions/ERR_Everon.conf`** (`{C1E20667DCE324ED}`), as the universal starting
|
||||
scenario. It opens Escape on Everon by default; a saved choice in the admin
|
||||
**Terrains** menu loads another installed terrain after restart. The existing
|
||||
resource ID is unchanged. The public browser may keep the generic Automatic
|
||||
terrain title after switching maps; it no longer labels this entry as Everon.
|
||||
The Core directory and addon GUID `2528466DE220D91C` are retained for continuity.
|
||||
|
||||
Scripts are organized under `addons/core/Scripts/Game/ERR/`:
|
||||
@@ -32,16 +39,41 @@ Scripts are organized under `addons/core/Scripts/Game/ERR/`:
|
||||
The prefixes preserve Enforce script extension order. All modules load together.
|
||||
Resources retain their existing paths and GUIDs within the combined project.
|
||||
|
||||
Menu appearance and navigation are maintained in [ui/admin_menu](ui/admin_menu/README.md):
|
||||
one theme, a navigation manifest, and separate page layouts. Run
|
||||
`python3 tools/build_admin_menu.py` after changing those sources; the generated
|
||||
native layouts remain in the addon for Workbench and publishing.
|
||||
|
||||
Register only this project and its external dependencies in Workbench. Remove stale
|
||||
AI/Intel/Map/Chat project entries if you previously added them. RHS: Status Quo,
|
||||
WCS Armaments and WCS BMP-1 (including their dependencies) remain separate,
|
||||
author-supplied mods; see [external dependencies](docs/external-addons.md).
|
||||
Their assets are referenced, never redistributed.
|
||||
Their assets are referenced. Use updated RHS Status Quo directly. The old RHS 1.9
|
||||
script overrides and standalone compatibility addon are retired; do not generate
|
||||
or load them. See [RHS compatibility retirement](compatibility/rhs-1.9/README.md).
|
||||
|
||||
With a shovel equipped, look at a dead body and hold **Bury body** for eight
|
||||
seconds. This removes the corpse and its remaining equipment; loot it first.
|
||||
Living and unconscious characters cannot be buried. Existing HQ casualty reports
|
||||
remain valid after burial.
|
||||
|
||||
Optional [enemy reconnaissance drones](docs/drone-reconnaissance.md) use Realistic
|
||||
Combat Drones. Configure their limit and cooldown in **HQ → Recon drones**;
|
||||
they are disabled by default.
|
||||
|
||||
See the [single-mod migration and current commands](docs/single-mod.md).
|
||||
Earlier five-addon build reports below are historical evidence, not validation of
|
||||
the merged project. Nothing has been published to Workshop.
|
||||
|
||||
[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).
|
||||
|
||||
## Validation
|
||||
@@ -60,4 +92,8 @@ The [development source bundle tool](docs/development-source-bundle.md) previews
|
||||
|
||||
Random field hospitals and camps, their permanent/mobile guard split, finite supplies and authoring options are documented in [Field sites](docs/field-sites.md).
|
||||
|
||||
Town population and its 700-metre spawning rule: [town garrisons](docs/town-garrisons.md).
|
||||
Occasional transport boats and island-facing coast preferences: [Shore boats](docs/shore-boats.md).
|
||||
|
||||
Configurable smoking wrecks and finite weapon crates are documented in [Crash-site salvage](docs/crash-sites.md).
|
||||
|
||||
Town population, configurable factory guards and safe tower/roof posts: [town and object garrisons](docs/town-garrisons.md).
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
MatPBRBasic {
|
||||
Color 0.48 0.018 0.012 1
|
||||
MetalnessScale 0
|
||||
RoughnessScale 0.75
|
||||
BCRMap "{A64A8C63E0FBAE87}system/textures/defWhite.edds"
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
MetaFileClass {
|
||||
Name "{F6575DAA717C6693}Assets/Items/ERR_Atlas/AtlasCover.emat"
|
||||
Configurations {
|
||||
ResourceClass PC {}
|
||||
ResourceClass HEADLESS : PC {}
|
||||
ResourceClass XBOX_ONE : PC {}
|
||||
ResourceClass XBOX_SERIES : PC {}
|
||||
ResourceClass PS4 : PC {}
|
||||
ResourceClass PS5 : PC {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
MatPBRBasic {
|
||||
Color 0.73 0.70 0.62 1
|
||||
MetalnessScale 0
|
||||
RoughnessScale 0.75
|
||||
BCRMap "{A64A8C63E0FBAE87}system/textures/defWhite.edds"
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
MetaFileClass {
|
||||
Name "{C029F1CCABDA2C3E}Assets/Items/ERR_Atlas/AtlasPaper.emat"
|
||||
Configurations {
|
||||
ResourceClass PC {}
|
||||
ResourceClass HEADLESS : PC {}
|
||||
ResourceClass XBOX_ONE : PC {}
|
||||
ResourceClass XBOX_SERIES : PC {}
|
||||
ResourceClass PS4 : PC {}
|
||||
ResourceClass PS5 : PC {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
MatPBRBasic {
|
||||
Color 0.882 0.847 0.792 1
|
||||
AlphaTest 0.5
|
||||
AllowUserAlphaBias 1
|
||||
BCRMap "$rendertarget"
|
||||
OpacityMap "{A64A8C63E0FBAE87}system/textures/defWhite.edds" "{A64A8C63E0FBAE87}system/textures/defWhite.edds" anim( 0 )
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
MetaFileClass {
|
||||
Name "{CE2D59FD9280F47D}Assets/Items/ERR_Atlas/AtlasPrint.emat"
|
||||
Configurations {
|
||||
ResourceClass PC {}
|
||||
ResourceClass HEADLESS : PC {}
|
||||
ResourceClass XBOX_ONE : PC {}
|
||||
ResourceClass XBOX_SERIES : PC {}
|
||||
ResourceClass PS4 : PC {}
|
||||
ResourceClass PS5 : PC {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
AnimSrcGraphFile {
|
||||
Sheets {
|
||||
AnimSrcGraphSheet Atlas {
|
||||
Nodes {
|
||||
AnimSrcNodeStateMachine ERRAtlasControl {
|
||||
states {
|
||||
AnimSrcNodeState Idle { Child "MasterControl" IsExit 1 }
|
||||
AnimSrcNodeState Turn { Child "ERRAtlasHandIK" TimeStorage "Real Time" }
|
||||
AnimSrcNodeState TurnBack { Child "ERRAtlasBackHandIK" TimeStorage "Real Time" }
|
||||
}
|
||||
transitions {
|
||||
AnimSrcNodeTransition { FromState "Idle" ToState "Turn" Duration "0.25" Condition "IsCommand(CMD_HealSelf) && GetCommandI(CMD_HealSelf) == 1" }
|
||||
AnimSrcNodeTransition { FromState "Idle" ToState "TurnBack" Duration "0.25" Condition "IsCommand(CMD_HealSelf) && GetCommandI(CMD_HealSelf) == 2" }
|
||||
AnimSrcNodeTransition { FromState "TurnBack" ToState "Idle" Duration "0.25" Condition "GetStateTime() > 0.95 || GetCommandI(CMD_HealSelf) == -1" }
|
||||
AnimSrcNodeTransition { FromState "Turn" ToState "Idle" Duration "0.25" Condition "GetStateTime() > 0.95 || GetCommandI(CMD_HealSelf) == -1" }
|
||||
}
|
||||
}
|
||||
AnimSrcNodeIK2Target ERRAtlasHandTarget {
|
||||
Child "MasterControl"
|
||||
Bones {
|
||||
AnimSrcNodeIK2BoneTarget { IkTarget "ERRAtlasRightHand" Bone "LeftHandProp" }
|
||||
}
|
||||
}
|
||||
AnimSrcNodeProcTransform ERRAtlasHandOffset {
|
||||
Child "ERRAtlasHandTarget"
|
||||
Expression "1"
|
||||
IkTargets {
|
||||
AnimSrcNodeProcTrIkTargetItem { Operation Translate Axis X Amount "-0.068 + 0.21*cos(3.141593*curve_s(clamp((GetUpperRTime()-0.25)/0.65,0,1)))" Space Local IkTarget "ERRAtlasRightHand" }
|
||||
AnimSrcNodeProcTrIkTargetItem { Operation Translate Axis Y Amount "0.02 + 0.055*sin(3.141593*curve_s(clamp((GetUpperRTime()-0.25)/0.65,0,1)))" Space Local IkTarget "ERRAtlasRightHand" }
|
||||
AnimSrcNodeProcTrIkTargetItem { Operation Translate Axis Z Amount "-0.065" Space Local IkTarget "ERRAtlasRightHand" }
|
||||
}
|
||||
}
|
||||
AnimSrcNodeIK2 ERRAtlasHandIK {
|
||||
Tags { "TagLItemAction" }
|
||||
Child "ERRAtlasHandOffset"
|
||||
Weight "1"
|
||||
Chains {
|
||||
AnimSrcIkBinding { IkTarget "ERRAtlasRightHand" IkChain "ERRAtlasRightArm" }
|
||||
}
|
||||
Solver AnimSrcNodeIK2TwoBoneSolver { SnapRotation 0 AllowStretching 0 }
|
||||
Version 1
|
||||
}
|
||||
AnimSrcNodeProcTransform ERRAtlasBackHandOffset {
|
||||
Child "ERRAtlasHandTarget"
|
||||
Expression "1"
|
||||
IkTargets {
|
||||
AnimSrcNodeProcTrIkTargetItem { Operation Translate Axis X Amount "-0.068 + 0.21*cos(3.141593*(1-curve_s(clamp((GetUpperRTime()-0.25)/0.65,0,1))))" Space Local IkTarget "ERRAtlasRightHand" }
|
||||
AnimSrcNodeProcTrIkTargetItem { Operation Translate Axis Y Amount "0.02 + 0.055*sin(3.141593*(1-curve_s(clamp((GetUpperRTime()-0.25)/0.65,0,1))))" Space Local IkTarget "ERRAtlasRightHand" }
|
||||
AnimSrcNodeProcTrIkTargetItem { Operation Translate Axis Z Amount "-0.065" Space Local IkTarget "ERRAtlasRightHand" }
|
||||
}
|
||||
}
|
||||
AnimSrcNodeIK2 ERRAtlasBackHandIK {
|
||||
Tags { "TagLItemAction" }
|
||||
Child "ERRAtlasBackHandOffset"
|
||||
Weight "1"
|
||||
Chains {
|
||||
AnimSrcIkBinding { IkTarget "ERRAtlasRightHand" IkChain "ERRAtlasRightArm" }
|
||||
}
|
||||
Solver AnimSrcNodeIK2TwoBoneSolver { SnapRotation 0 AllowStretching 0 }
|
||||
Version 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
MetaFileClass {
|
||||
Name "{B428122ACA3850BC}Assets/Items/ERR_Atlas/AtlasTurn.agf"
|
||||
Configurations { ResourceClass PC {} ResourceClass HEADLESS : PC {} ResourceClass XBOX_ONE : PC {} ResourceClass XBOX_SERIES : PC {} ResourceClass PS4 : PC {} ResourceClass PS5 : PC {} }
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
AnimSrcGraph {
|
||||
AnimSetTemplate "{91953B0B3D746630}Assets/Items/Equipment/workspaces/paper_map.ast"
|
||||
ControlTemplate AnimSrcGCT "{55567D08724BC61D}" {
|
||||
Variables {
|
||||
AnimSrcGCTVarFloat AimY {
|
||||
MinValue -90
|
||||
MaxValue 90
|
||||
}
|
||||
AnimSrcGCTVarInt Stance {
|
||||
DefaultValue 0
|
||||
MaxValue 2
|
||||
}
|
||||
AnimSrcGCTVarBool Low {
|
||||
DefaultValue 0
|
||||
}
|
||||
AnimSrcGCTVarFloat AimX {
|
||||
MinValue -90
|
||||
MaxValue 90
|
||||
}
|
||||
AnimSrcGCTVarBool Temp {
|
||||
}
|
||||
AnimSrcGCTVarInt WeaponType {
|
||||
MaxValue 20
|
||||
}
|
||||
AnimSrcGCTVarBool FreeLook {
|
||||
}
|
||||
}
|
||||
Commands { AnimSrcGCTCmd CMD_HealSelf { } }
|
||||
IkChains {
|
||||
AnimSrcGCTIkChain ERRAtlasRightArm {
|
||||
Joints { "rightarm" "rightarmtwist" "rightforearm" "rightforearmtwist" "righthand" }
|
||||
MiddleJoint "rightforearm"
|
||||
ChainAxis "+y"
|
||||
}
|
||||
AnimSrcGCTIkChain Head {
|
||||
Joints {
|
||||
"neck2"
|
||||
"neck3"
|
||||
"head"
|
||||
}
|
||||
MiddleJoint "neck3"
|
||||
ChainAxis "+y"
|
||||
}
|
||||
AnimSrcGCTIkChain LeftArm {
|
||||
Joints {
|
||||
"leftarm"
|
||||
"leftarmtwist"
|
||||
"leftforearm"
|
||||
"leftforearmtwist"
|
||||
"lefthand"
|
||||
}
|
||||
MiddleJoint "leftforearm"
|
||||
ChainAxis "+y"
|
||||
}
|
||||
}
|
||||
GlobalTags {
|
||||
"ITEM"
|
||||
"GADGET"
|
||||
"MAP"
|
||||
"PaperMap01"
|
||||
}
|
||||
}
|
||||
Debug AnimSrcGD "{55567D08724BC783}" {
|
||||
}
|
||||
GraphFilesResourceNames {
|
||||
"{B428122ACA3850BC}Assets/Items/ERR_Atlas/AtlasTurn.agf"
|
||||
"{6F87679E2512527D}Assets/Items/Equipment/workspaces/map.agf"
|
||||
}
|
||||
DefaultRunNode "ERRAtlasControl"
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
MetaFileClass {
|
||||
Name "{4959464B088CCCA4}Assets/Items/ERR_Atlas/AtlasTurn.agr"
|
||||
Configurations { ResourceClass PC {} ResourceClass HEADLESS : PC {} ResourceClass XBOX_ONE : PC {} ResourceClass XBOX_SERIES : PC {} ResourceClass PS4 : PC {} ResourceClass PS5 : PC {} }
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
MatPBRBasic {
|
||||
Color 0.8 0.8 0.78 1
|
||||
MetalnessScale 0.65
|
||||
RoughnessScale 0.75
|
||||
BCRMap "{A64A8C63E0FBAE87}system/textures/defWhite.edds"
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
MetaFileClass {
|
||||
Name "{993AED2484A6829B}Assets/Items/ERR_Atlas/AtlasWire.emat"
|
||||
Configurations {
|
||||
ResourceClass PC {}
|
||||
ResourceClass HEADLESS : PC {}
|
||||
ResourceClass XBOX_ONE : PC {}
|
||||
ResourceClass XBOX_SERIES : PC {}
|
||||
ResourceClass PS4 : PC {}
|
||||
ResourceClass PS5 : PC {}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -0,0 +1,85 @@
|
||||
MetaFileClass {
|
||||
Name "{51ABB9E2E838FC9E}Assets/Items/ERR_Atlas/ERR_Atlas.xob"
|
||||
Configurations {
|
||||
FBXResourceClass PC {
|
||||
MaterialAssigns {
|
||||
MaterialAssignClass "{6A9727841D4273B0}" {
|
||||
SourceMaterial "AtlasPrint"
|
||||
AssignedMaterial "{CE2D59FD9280F47D}Assets/Items/ERR_Atlas/AtlasPrint.emat"
|
||||
}
|
||||
MaterialAssignClass "{6A9727841D427046}" {
|
||||
SourceMaterial "AtlasPaper"
|
||||
AssignedMaterial "{C029F1CCABDA2C3E}Assets/Items/ERR_Atlas/AtlasPaper.emat"
|
||||
}
|
||||
MaterialAssignClass "{6A9727841D427075}" {
|
||||
SourceMaterial "AtlasCover"
|
||||
AssignedMaterial "{F6575DAA717C6693}Assets/Items/ERR_Atlas/AtlasCover.emat"
|
||||
}
|
||||
MaterialAssignClass "{6A9727841D427065}" {
|
||||
SourceMaterial "AtlasWire"
|
||||
AssignedMaterial "{993AED2484A6829B}Assets/Items/ERR_Atlas/AtlasWire.emat"
|
||||
}
|
||||
}
|
||||
MeshParams {
|
||||
MeshParam BackCover {
|
||||
}
|
||||
MeshParam FrontCover {
|
||||
}
|
||||
MeshParam PageBlock {
|
||||
}
|
||||
MeshParam PageEdge_00 {
|
||||
}
|
||||
MeshParam PageEdge_01 {
|
||||
}
|
||||
MeshParam PageEdge_02 {
|
||||
}
|
||||
MeshParam PageEdge_03 {
|
||||
}
|
||||
MeshParam PageEdge_04 {
|
||||
}
|
||||
MeshParam PageEdge_05 {
|
||||
}
|
||||
MeshParam PageEdge_06 {
|
||||
}
|
||||
MeshParam PageEdge_07 {
|
||||
}
|
||||
MeshParam PageEdge_08 {
|
||||
}
|
||||
MeshParam PageEdge_09 {
|
||||
}
|
||||
MeshParam PageEdge_10 {
|
||||
}
|
||||
MeshParam PageEdge_11 {
|
||||
}
|
||||
MeshParam PrintedSheet {
|
||||
}
|
||||
MeshParam SpiralWire {
|
||||
}
|
||||
}
|
||||
GeometryParams {
|
||||
GeometryParam UBX_Atlas {
|
||||
LayerPreset "ItemFireView"
|
||||
SurfaceProperties {
|
||||
"{AC1A1CDEE58FEE27}Common/Materials/Game/paper.gamemat"
|
||||
}
|
||||
Mass 0.35
|
||||
}
|
||||
}
|
||||
Common TXOCommonClass "{6A9727841D4270C8}" : "{0877E7C4BB2B2C9A}Configs/System/ResourceTypes/PC/MeshObjectCommon.conf" {
|
||||
}
|
||||
ApplicationVendor "Blender Foundation"
|
||||
ApplicationName "Blender (stable FBX IO)"
|
||||
ApplicationVersion "4.5.3 LTS"
|
||||
}
|
||||
FBXResourceClass HEADLESS : PC {
|
||||
}
|
||||
FBXResourceClass XBOX_ONE : PC {
|
||||
}
|
||||
FBXResourceClass XBOX_SERIES : PC {
|
||||
}
|
||||
FBXResourceClass PS4 : PC {
|
||||
}
|
||||
FBXResourceClass PS5 : PC {
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,374 @@
|
||||
$object "" {
|
||||
#version 1.0
|
||||
#tag MergeMeshes "1"
|
||||
#tag Coords "XZY"
|
||||
#tag Scale "1.000000"
|
||||
#tag SrcFile_AppVendor "Blender Foundation"
|
||||
#tag SrcFile_AppName "Blender (stable FBX IO)"
|
||||
#tag SrcFile_AppVersion "4.5.3 LTS"
|
||||
$materials {
|
||||
$material "AtlasPaper" {
|
||||
#UVset 0
|
||||
}
|
||||
$material "AtlasPrint" {
|
||||
#UVset 0
|
||||
}
|
||||
}
|
||||
$lod 0 {
|
||||
#lodFactor 0.5
|
||||
$mesh "LeafBack" {
|
||||
#texCoords 1
|
||||
$faces {
|
||||
0 4 0 1 2 3
|
||||
0 4 4 5 6 7
|
||||
0 4 8 9 10 11
|
||||
0 4 12 13 14 15
|
||||
0 4 16 17 18 19
|
||||
0 4 20 21 22 23
|
||||
0 4 24 25 26 27
|
||||
0 4 28 29 30 31
|
||||
0 4 32 33 34 35
|
||||
0 4 36 37 38 39
|
||||
0 4 40 41 42 43
|
||||
0 4 44 45 46 47
|
||||
0 4 48 49 50 51
|
||||
0 4 52 53 54 55
|
||||
0 4 56 57 58 59
|
||||
0 4 60 61 62 63
|
||||
}
|
||||
$faceVerts {
|
||||
0 0 0
|
||||
1 0 1
|
||||
3 0 2
|
||||
2 0 3
|
||||
2 1 3
|
||||
3 1 2
|
||||
5 1 4
|
||||
4 1 5
|
||||
4 2 5
|
||||
5 2 4
|
||||
7 2 6
|
||||
6 2 7
|
||||
6 3 7
|
||||
7 3 6
|
||||
9 3 8
|
||||
8 3 9
|
||||
8 4 9
|
||||
9 4 8
|
||||
11 4 10
|
||||
10 4 11
|
||||
10 5 11
|
||||
11 5 10
|
||||
13 5 12
|
||||
12 5 13
|
||||
12 6 13
|
||||
13 6 12
|
||||
15 6 14
|
||||
14 6 15
|
||||
14 7 15
|
||||
15 7 14
|
||||
17 7 16
|
||||
16 7 17
|
||||
16 8 17
|
||||
17 8 16
|
||||
19 8 18
|
||||
18 8 19
|
||||
18 9 19
|
||||
19 9 18
|
||||
21 9 20
|
||||
20 9 21
|
||||
20 10 21
|
||||
21 10 20
|
||||
23 10 22
|
||||
22 10 23
|
||||
22 11 23
|
||||
23 11 22
|
||||
25 11 24
|
||||
24 11 25
|
||||
24 12 25
|
||||
25 12 24
|
||||
27 12 26
|
||||
26 12 27
|
||||
26 13 27
|
||||
27 13 26
|
||||
29 13 28
|
||||
28 13 29
|
||||
28 14 29
|
||||
29 14 28
|
||||
31 14 30
|
||||
30 14 31
|
||||
30 15 31
|
||||
31 15 30
|
||||
33 15 32
|
||||
32 15 33
|
||||
}
|
||||
$verts {
|
||||
0 -0 0.1485 0
|
||||
0 0 -0.1485 0
|
||||
0.011937 0.00078 0.1485 0
|
||||
0.011937 0.00078 -0.1485 0
|
||||
0.023875 0.001531 0.1485 0
|
||||
0.023875 0.001531 -0.1485 0
|
||||
0.035813 0.002222 0.1485 0
|
||||
0.035813 0.002222 -0.1485 0
|
||||
0.04775 0.002828 0.1485 0
|
||||
0.04775 0.002828 -0.1485 0
|
||||
0.059687 0.003326 0.1485 0
|
||||
0.059687 0.003326 -0.1485 0
|
||||
0.071625 0.003696 0.1485 0
|
||||
0.071625 0.003696 -0.1485 0
|
||||
0.083563 0.003923 0.1485 0
|
||||
0.083563 0.003923 -0.1485 0
|
||||
0.0955 0.004 0.1485 0
|
||||
0.0955 0.004 -0.1485 0
|
||||
0.107437 0.003923 0.1485 0
|
||||
0.107437 0.003923 -0.1485 0
|
||||
0.119375 0.003696 0.1485 0
|
||||
0.119375 0.003696 -0.1485 0
|
||||
0.131313 0.003326 0.1485 0
|
||||
0.131313 0.003326 -0.1485 0
|
||||
0.14325 0.002828 0.1485 0
|
||||
0.14325 0.002828 -0.1485 0
|
||||
0.155188 0.002222 0.1485 0
|
||||
0.155188 0.002222 -0.1485 0
|
||||
0.167125 0.001531 0.1485 0
|
||||
0.167125 0.001531 -0.1485 0
|
||||
0.179063 0.00078 0.1485 0
|
||||
0.179063 0.00078 -0.1485 0
|
||||
0.191 -0 0.1485 0
|
||||
0.191 0 -0.1485 0
|
||||
}
|
||||
$texCoords {
|
||||
0.10753 0
|
||||
0.10753 1
|
||||
0.16331 1
|
||||
0.16331 0
|
||||
0.21909 1
|
||||
0.21909 0
|
||||
0.27487 1
|
||||
0.27487 0
|
||||
0.33065 1
|
||||
0.33065 0
|
||||
0.38642 1
|
||||
0.38642 0
|
||||
0.4422 1
|
||||
0.4422 0
|
||||
0.49798 1
|
||||
0.49798 0
|
||||
0.55376 1
|
||||
0.55376 0
|
||||
0.60954 1
|
||||
0.60954 0
|
||||
0.66532 1
|
||||
0.66532 0
|
||||
0.7211 1
|
||||
0.7211 0
|
||||
0.77688 1
|
||||
0.77688 0
|
||||
0.83266 1
|
||||
0.83266 0
|
||||
0.88844 1
|
||||
0.88844 0
|
||||
0.94422 1
|
||||
0.94422 0
|
||||
1 1
|
||||
1 0
|
||||
}
|
||||
$normals {
|
||||
0.0652 -0.9979 -0
|
||||
0.0627 -0.998 -0
|
||||
0.0578 -0.9983 -0
|
||||
0.0507 -0.9987 -0
|
||||
0.0416 -0.9991 -0
|
||||
0.0309 -0.9995 -0
|
||||
0.0191 -0.9998 -0
|
||||
0.0064 -1 -0
|
||||
-0.0064 -1 -0
|
||||
-0.0191 -0.9998 -0
|
||||
-0.0309 -0.9995 -0
|
||||
-0.0416 -0.9991 -0
|
||||
-0.0507 -0.9987 -0
|
||||
-0.0578 -0.9983 -0
|
||||
-0.0627 -0.998 -0
|
||||
-0.0652 -0.9979 -0
|
||||
}
|
||||
}
|
||||
$mesh "TurningLeaf" {
|
||||
#texCoords 1
|
||||
$faces {
|
||||
1 4 0 1 2 3
|
||||
1 4 4 5 6 7
|
||||
1 4 8 9 10 11
|
||||
1 4 12 13 14 15
|
||||
1 4 16 17 18 19
|
||||
1 4 20 21 22 23
|
||||
1 4 24 25 26 27
|
||||
1 4 28 29 30 31
|
||||
1 4 32 33 34 35
|
||||
1 4 36 37 38 39
|
||||
1 4 40 41 42 43
|
||||
1 4 44 45 46 47
|
||||
1 4 48 49 50 51
|
||||
1 4 52 53 54 55
|
||||
1 4 56 57 58 59
|
||||
1 4 60 61 62 63
|
||||
}
|
||||
$faceVerts {
|
||||
2 0 0
|
||||
3 0 1
|
||||
1 0 2
|
||||
0 0 3
|
||||
4 1 4
|
||||
5 1 5
|
||||
3 1 1
|
||||
2 1 0
|
||||
6 2 6
|
||||
7 2 7
|
||||
5 2 5
|
||||
4 2 4
|
||||
8 3 8
|
||||
9 3 9
|
||||
7 3 7
|
||||
6 3 6
|
||||
10 4 10
|
||||
11 4 11
|
||||
9 4 9
|
||||
8 4 8
|
||||
12 5 12
|
||||
13 5 13
|
||||
11 5 11
|
||||
10 5 10
|
||||
14 6 14
|
||||
15 6 15
|
||||
13 6 13
|
||||
12 6 12
|
||||
16 7 16
|
||||
17 7 17
|
||||
15 7 15
|
||||
14 7 14
|
||||
18 8 18
|
||||
19 8 19
|
||||
17 8 17
|
||||
16 8 16
|
||||
20 9 20
|
||||
21 9 21
|
||||
19 9 19
|
||||
18 9 18
|
||||
22 10 22
|
||||
23 10 23
|
||||
21 10 21
|
||||
20 10 20
|
||||
24 11 24
|
||||
25 11 25
|
||||
23 11 23
|
||||
22 11 22
|
||||
26 12 26
|
||||
27 12 27
|
||||
25 12 25
|
||||
24 12 24
|
||||
28 13 28
|
||||
29 13 29
|
||||
27 13 27
|
||||
26 13 26
|
||||
30 14 30
|
||||
31 14 31
|
||||
29 14 29
|
||||
28 14 28
|
||||
32 15 32
|
||||
33 15 33
|
||||
31 15 31
|
||||
30 15 30
|
||||
}
|
||||
$verts {
|
||||
0 -0 0.1485 0
|
||||
0 0 -0.1485 0
|
||||
0.011937 0.00078 0.1485 0
|
||||
0.011937 0.00078 -0.1485 0
|
||||
0.023875 0.001531 0.1485 0
|
||||
0.023875 0.001531 -0.1485 0
|
||||
0.035813 0.002222 0.1485 0
|
||||
0.035813 0.002222 -0.1485 0
|
||||
0.04775 0.002828 0.1485 0
|
||||
0.04775 0.002828 -0.1485 0
|
||||
0.059687 0.003326 0.1485 0
|
||||
0.059687 0.003326 -0.1485 0
|
||||
0.071625 0.003696 0.1485 0
|
||||
0.071625 0.003696 -0.1485 0
|
||||
0.083563 0.003923 0.1485 0
|
||||
0.083563 0.003923 -0.1485 0
|
||||
0.0955 0.004 0.1485 0
|
||||
0.0955 0.004 -0.1485 0
|
||||
0.107437 0.003923 0.1485 0
|
||||
0.107437 0.003923 -0.1485 0
|
||||
0.119375 0.003696 0.1485 0
|
||||
0.119375 0.003696 -0.1485 0
|
||||
0.131313 0.003326 0.1485 0
|
||||
0.131313 0.003326 -0.1485 0
|
||||
0.14325 0.002828 0.1485 0
|
||||
0.14325 0.002828 -0.1485 0
|
||||
0.155188 0.002222 0.1485 0
|
||||
0.155188 0.002222 -0.1485 0
|
||||
0.167125 0.001531 0.1485 0
|
||||
0.167125 0.001531 -0.1485 0
|
||||
0.179063 0.00078 0.1485 0
|
||||
0.179063 0.00078 -0.1485 0
|
||||
0.191 -0 0.1485 0
|
||||
0.191 0 -0.1485 0
|
||||
}
|
||||
$texCoords {
|
||||
0.16331 0
|
||||
0.16331 1
|
||||
0.10753 1
|
||||
0.10753 0
|
||||
0.21909 0
|
||||
0.21909 1
|
||||
0.27487 0
|
||||
0.27487 1
|
||||
0.33065 0
|
||||
0.33065 1
|
||||
0.38642 0
|
||||
0.38642 1
|
||||
0.4422 0
|
||||
0.4422 1
|
||||
0.49798 0
|
||||
0.49798 1
|
||||
0.55376 0
|
||||
0.55376 1
|
||||
0.60954 0
|
||||
0.60954 1
|
||||
0.66532 0
|
||||
0.66532 1
|
||||
0.7211 0
|
||||
0.7211 1
|
||||
0.77688 0
|
||||
0.77688 1
|
||||
0.83266 0
|
||||
0.83266 1
|
||||
0.88844 0
|
||||
0.88844 1
|
||||
0.94422 0
|
||||
0.94422 1
|
||||
1 0
|
||||
1 1
|
||||
}
|
||||
$normals {
|
||||
-0.0652 0.9979 0
|
||||
-0.0627 0.998 0
|
||||
-0.0578 0.9983 0
|
||||
-0.0507 0.9987 0
|
||||
-0.0416 0.9991 0
|
||||
-0.0309 0.9995 0
|
||||
-0.0191 0.9998 0
|
||||
-0.0064 1 0
|
||||
0.0064 1 0
|
||||
0.0191 0.9998 0
|
||||
0.0309 0.9995 0
|
||||
0.0416 0.9991 0
|
||||
0.0507 0.9987 0
|
||||
0.0578 0.9983 0
|
||||
0.0627 0.998 0
|
||||
0.0652 0.9979 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,38 @@
|
||||
MetaFileClass {
|
||||
Name "{76D738EEB0CA431F}Assets/Items/ERR_Atlas/ERR_AtlasLeaf.xob"
|
||||
Configurations {
|
||||
FBXResourceClass PC {
|
||||
MaterialAssigns {
|
||||
MaterialAssignClass "{6A9727841D4273B0}" {
|
||||
SourceMaterial "AtlasPrint"
|
||||
AssignedMaterial "{CE2D59FD9280F47D}Assets/Items/ERR_Atlas/AtlasPrint.emat"
|
||||
}
|
||||
MaterialAssignClass "{6A9727841D427046}" {
|
||||
SourceMaterial "AtlasPaper"
|
||||
AssignedMaterial "{C029F1CCABDA2C3E}Assets/Items/ERR_Atlas/AtlasPaper.emat"
|
||||
}
|
||||
}
|
||||
MeshParams {
|
||||
MeshParam LeafBack {
|
||||
}
|
||||
MeshParam TurningLeaf {
|
||||
}
|
||||
}
|
||||
Common TXOCommonClass "{6A9727841D4270C8}" : "{0877E7C4BB2B2C9A}Configs/System/ResourceTypes/PC/MeshObjectCommon.conf" {
|
||||
}
|
||||
ApplicationVendor "Blender Foundation"
|
||||
ApplicationName "Blender (stable FBX IO)"
|
||||
ApplicationVersion "4.5.3 LTS"
|
||||
}
|
||||
FBXResourceClass HEADLESS : PC {
|
||||
}
|
||||
FBXResourceClass XBOX_ONE : PC {
|
||||
}
|
||||
FBXResourceClass XBOX_SERIES : PC {
|
||||
}
|
||||
FBXResourceClass PS4 : PC {
|
||||
}
|
||||
FBXResourceClass PS5 : PC {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,10 @@
|
||||
MenuManager {
|
||||
MenuPresets {
|
||||
MenuPreset ERR_PreparationFailure {
|
||||
Layout "{28DD8E27B1CF40C5}UI/layouts/ERR/PreparationFailure.layout"
|
||||
ActionContext "MenuContext"
|
||||
Class "ERR_PreparationFailureMenu"
|
||||
}
|
||||
MenuPreset ERR_AdminPanel {
|
||||
Layout "{AE761C0439D25FB8}UI/layouts/ERR/AdminMenu.layout"
|
||||
ActionContext "MenuContext"
|
||||
|
||||
@@ -30,12 +30,22 @@ ERR_ScenarioDefinition {
|
||||
GuardOffset 18 0 0
|
||||
Container "{40986810D0B1F4AE}Prefabs/ERR/SupplyCache.et"
|
||||
ContainerOffset 0 0 0
|
||||
Transport ERR_SiteTransport {
|
||||
Optional 1
|
||||
Prefab "{FA65287C19DE304B}Prefabs/ERR/Vehicles/SupplyUAZ.et"
|
||||
Offset 0 0 -12
|
||||
Heading 180
|
||||
ClearanceRadius 4
|
||||
MaximumTerrainRange 1
|
||||
}
|
||||
Stock {
|
||||
ERR_SiteStock { Prefab "{FA5C25BF66A53DCF}Prefabs/Weapons/Rifles/AK74/Rifle_AK74.et" Count 8 }
|
||||
ERR_SiteStock { Prefab "{BBB50A815A2F916B}Prefabs/Weapons/Magazines/Magazine_545x39_AK_30rnd_Ball.et" Count 16 }
|
||||
ERR_SiteStock { Prefab "{C3F1FA1E2EC2B345}Prefabs/Items/Medicine/FieldDressing_01/FieldDressing_USSR_01.et" Count 12 }
|
||||
ERR_SiteStock { Prefab "{13772C903CB5E4F7}Prefabs/Items/Equipment/Maps/Map_Paper_01/PaperMap_01_folded.et" Count 1 }
|
||||
ERR_SiteStock { Prefab "{554159A6740C3F3E}Prefabs/ERR/ObjectiveDocument.et" Count 2 TargetId "everon-radio-1" }
|
||||
ERR_SiteStock { Prefab "{C09C7C97EFA34909}Prefabs/ERR/Items/EmergencyRepairKit.et" Count 1 }
|
||||
ERR_SiteStock { Prefab "{12D5AD21E383B768}Prefabs/Items/Fuel/Jerrycan_01/Jerrycan_01_item.et" Count 2 }
|
||||
}
|
||||
RevealOnEscape 1
|
||||
}
|
||||
@@ -60,12 +70,13 @@ ERR_ScenarioDefinition {
|
||||
}
|
||||
ERR_SiteDefinition {
|
||||
Id "everon-extraction-1"
|
||||
Label "Initial helicopter pickup site"
|
||||
Label "Extraction landing zone"
|
||||
Kind 2
|
||||
Prefab "{033503110AC34EAC}Prefabs/ERR/Extraction.et"
|
||||
Position 4800.953 0 11487.412
|
||||
AircraftOffset 0 0 0
|
||||
RequireAirfield 1
|
||||
RequireAirfield 0
|
||||
RandomClear 1
|
||||
Guarded 1
|
||||
GuardOffset 30 0 0
|
||||
}
|
||||
@@ -121,6 +132,7 @@ ERR_ScenarioDefinition {
|
||||
ERR_SiteProp { Prefab "{CFFEB23FBEA00322}Prefabs/Props/Military/Sandbags/Sandbag_01_short_burlap.et" Offset 5 0 -7 }
|
||||
}
|
||||
Stock {
|
||||
ERR_SiteStock { Prefab "{B05A816C0BF50802}Prefabs/Weapons/Explosives/Mine_PMN4/Mine_PMN4.et" Count 2 }
|
||||
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 }
|
||||
@@ -244,6 +256,7 @@ ERR_ScenarioDefinition {
|
||||
ERR_SiteProp { Prefab "{CFFEB23FBEA00322}Prefabs/Props/Military/Sandbags/Sandbag_01_short_burlap.et" Offset 5 0 -7 }
|
||||
}
|
||||
Stock {
|
||||
ERR_SiteStock { Prefab "{B05A816C0BF50802}Prefabs/Weapons/Explosives/Mine_PMN4/Mine_PMN4.et" Count 2 }
|
||||
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 }
|
||||
@@ -273,6 +286,7 @@ ERR_ScenarioDefinition {
|
||||
ERR_SiteProp { Prefab "{CFFEB23FBEA00322}Prefabs/Props/Military/Sandbags/Sandbag_01_short_burlap.et" Offset 5 0 -7 }
|
||||
}
|
||||
Stock {
|
||||
ERR_SiteStock { Prefab "{B05A816C0BF50802}Prefabs/Weapons/Explosives/Mine_PMN4/Mine_PMN4.et" Count 2 }
|
||||
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 }
|
||||
@@ -302,6 +316,7 @@ ERR_ScenarioDefinition {
|
||||
ERR_SiteProp { Prefab "{CFFEB23FBEA00322}Prefabs/Props/Military/Sandbags/Sandbag_01_short_burlap.et" Offset 5 0 -7 }
|
||||
}
|
||||
Stock {
|
||||
ERR_SiteStock { Prefab "{B05A816C0BF50802}Prefabs/Weapons/Explosives/Mine_PMN4/Mine_PMN4.et" Count 2 }
|
||||
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 }
|
||||
@@ -331,6 +346,7 @@ ERR_ScenarioDefinition {
|
||||
ERR_SiteProp { Prefab "{CFFEB23FBEA00322}Prefabs/Props/Military/Sandbags/Sandbag_01_short_burlap.et" Offset 5 0 -7 }
|
||||
}
|
||||
Stock {
|
||||
ERR_SiteStock { Prefab "{B05A816C0BF50802}Prefabs/Weapons/Explosives/Mine_PMN4/Mine_PMN4.et" Count 2 }
|
||||
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 }
|
||||
|
||||
@@ -2,7 +2,7 @@ SCR_GameOverScreenConfig : "{651583FFFB7FC765}Configs/GameOverScreen/BaseGameOve
|
||||
m_aGameOverScreens {
|
||||
ERR_EscapeGameOverScreenInfo "{6B193DC8DDC63507}" {
|
||||
m_GameOverScreenId VICTORY
|
||||
m_GameOverContentLayout "{C4507D07F3C54831}UI/layouts/HUD/GameOver/EndScreen/EndScreenContent_NoImage.layout"
|
||||
m_GameOverContentLayout "{C63B4D1E09A572F8}UI/layouts/ERR/RoundResults.layout"
|
||||
m_OptionalParams SCR_BaseGameOverScreenInfoOptional "{4984A5C1BFEFC04E}" {
|
||||
m_sTitle "Escape complete"
|
||||
m_sAudioOneShot "SOUND_ONVICTORY"
|
||||
@@ -10,7 +10,7 @@ SCR_GameOverScreenConfig : "{651583FFFB7FC765}Configs/GameOverScreen/BaseGameOve
|
||||
}
|
||||
ERR_FailureGameOverScreenInfo "{E66F29A7A8AD7438}" {
|
||||
m_GameOverScreenId LOOSE
|
||||
m_GameOverContentLayout "{C4507D07F3C54831}UI/layouts/HUD/GameOver/EndScreen/EndScreenContent_NoImage.layout"
|
||||
m_GameOverContentLayout "{C63B4D1E09A572F8}UI/layouts/ERR/RoundResults.layout"
|
||||
m_OptionalParams SCR_BaseGameOverScreenInfoOptional "{3DBF0BDE715945FA}" {
|
||||
m_sTitle "Escape failed"
|
||||
m_sDebriefing "No surviving prisoners can complete the escape, or the extraction helicopter has been lost."
|
||||
|
||||
@@ -1,7 +1,111 @@
|
||||
ActionManager : "{795184CF9AD764DB}Configs/System/chimeraInputCommon.conf" {
|
||||
Contexts {
|
||||
ActionContext ERR_PassengerBrakeContext {
|
||||
Priority 60
|
||||
Flags 0xa 0
|
||||
Actions {
|
||||
Action ERR_PassengerBrake {
|
||||
InputSource InputSourceValue "{A7B19F416E541030}" {
|
||||
Input "keyboard:KC_SPACE"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ActionContext ERR_FieldMapContext {
|
||||
Priority 60
|
||||
Flags 0xa 0
|
||||
Actions {
|
||||
Action ERR_AtlasDigit0 {
|
||||
InputSource InputSourceValue "{A7B19F416E541020}" {
|
||||
Input "keyboard:KC_NUMPAD0"
|
||||
}
|
||||
}
|
||||
Action ERR_AtlasDigit1 {
|
||||
InputSource InputSourceValue "{A7B19F416E541021}" {
|
||||
Input "keyboard:KC_NUMPAD1"
|
||||
}
|
||||
}
|
||||
Action ERR_AtlasDigit2 {
|
||||
InputSource InputSourceValue "{A7B19F416E541022}" {
|
||||
Input "keyboard:KC_NUMPAD2"
|
||||
}
|
||||
}
|
||||
Action ERR_AtlasDigit3 {
|
||||
InputSource InputSourceValue "{A7B19F416E541023}" {
|
||||
Input "keyboard:KC_NUMPAD3"
|
||||
}
|
||||
}
|
||||
Action ERR_AtlasDigit4 {
|
||||
InputSource InputSourceValue "{A7B19F416E541024}" {
|
||||
Input "keyboard:KC_NUMPAD4"
|
||||
}
|
||||
}
|
||||
Action ERR_AtlasDigit5 {
|
||||
InputSource InputSourceValue "{A7B19F416E541025}" {
|
||||
Input "keyboard:KC_NUMPAD5"
|
||||
}
|
||||
}
|
||||
Action ERR_AtlasDigit6 {
|
||||
InputSource InputSourceValue "{A7B19F416E541026}" {
|
||||
Input "keyboard:KC_NUMPAD6"
|
||||
}
|
||||
}
|
||||
Action ERR_AtlasDigit7 {
|
||||
InputSource InputSourceValue "{A7B19F416E541027}" {
|
||||
Input "keyboard:KC_NUMPAD7"
|
||||
}
|
||||
}
|
||||
Action ERR_AtlasDigit8 {
|
||||
InputSource InputSourceValue "{A7B19F416E541028}" {
|
||||
Input "keyboard:KC_NUMPAD8"
|
||||
}
|
||||
}
|
||||
Action ERR_AtlasDigit9 {
|
||||
InputSource InputSourceValue "{A7B19F416E541029}" {
|
||||
Input "keyboard:KC_NUMPAD9"
|
||||
}
|
||||
}
|
||||
Action ERR_AtlasEnter {
|
||||
InputSource InputSourceSum "{A7B19F416E54102B}" {
|
||||
Sources {
|
||||
InputSourceValue "{A7B19F416E54102A}" {
|
||||
Input "keyboard:KC_NUMPADENTER"
|
||||
}
|
||||
InputSourceValue "{A7B19F416E54102C}" {
|
||||
Input "keyboard:KC_RETURN"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Action ERR_FieldMapLeft {
|
||||
InputSource InputSourceValue "{A7B19F416E541010}" {
|
||||
Input "keyboard:KC_LEFT"
|
||||
}
|
||||
}
|
||||
Action ERR_FieldMapRight {
|
||||
InputSource InputSourceValue "{A7B19F416E541011}" {
|
||||
Input "keyboard:KC_RIGHT"
|
||||
}
|
||||
}
|
||||
Action ERR_FieldMapUp {
|
||||
InputSource InputSourceValue "{A7B19F416E541012}" {
|
||||
Input "keyboard:KC_UP"
|
||||
}
|
||||
}
|
||||
Action ERR_FieldMapDown {
|
||||
InputSource InputSourceValue "{A7B19F416E541013}" {
|
||||
Input "keyboard:KC_DOWN"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ActionContext ERR_ObserverContext : "{C538807EEE48BED8}Configs/System/ActionContext/ManualCameraContext.conf" {
|
||||
Actions {
|
||||
Action ERR_SelfEpinephrine {
|
||||
InputSource InputSourceValue "{A7B19F416E541020}" {
|
||||
Input "keyboard:KC_SPACE"
|
||||
}
|
||||
}
|
||||
Action ERR_ObserverPrevious {
|
||||
InputSource InputSourceValue "{A7B19F416E541001}" {
|
||||
Input "keyboard:KC_LEFT"
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
ERR_TelemetryConfig {
|
||||
Endpoint "https://co10escape-telemetry.zacekjakub.workers.dev/api/mission-events"
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
MetaFileClass {
|
||||
Name "{E72B130684FA492C}Configs/ERR/Telemetry.conf"
|
||||
Configurations {
|
||||
CONFResourceClass PC { }
|
||||
CONFResourceClass HEADLESS : PC { }
|
||||
CONFResourceClass XBOX_ONE : PC { }
|
||||
CONFResourceClass XBOX_SERIES : PC { }
|
||||
CONFResourceClass PS4 : PC { }
|
||||
CONFResourceClass PS5 : PC { }
|
||||
}
|
||||
}
|
||||
@@ -3,5 +3,11 @@ MatPBRBasic {
|
||||
AlphaTest 0.5
|
||||
AllowUserAlphaBias 1
|
||||
BCRMap "$rendertarget"
|
||||
GlobalMapsUVTransform MatUVTransform {
|
||||
TilingU 1.5
|
||||
TilingV 2
|
||||
Rotation 270
|
||||
OffsetU 1
|
||||
}
|
||||
OpacityMap "{A64A8C63E0FBAE87}system/textures/defWhite.edds" "{A64A8C63E0FBAE87}system/textures/defWhite.edds" anim( 0 )
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
SCR_MissionHeader {
|
||||
World "{B6C516F86302C679}Worlds/ERR_Everon.ent"
|
||||
SystemsConfig "{C50579A2EC48E8EF}Configs/Systems/MissionSystems.conf"
|
||||
m_sName "Co10 Escape Reintroduced — Everon"
|
||||
m_sName "Co10 Escape Reintroduced — Automatic terrain"
|
||||
m_sAuthor "Jakub Jim Žáček"
|
||||
m_sDescription "Independent escape prototype. Development scenario; runtime validation in progress."
|
||||
m_sDescription "Escape from captivity, find equipment and intelligence, and arrange helicopter extraction while enemy forces hunt you."
|
||||
m_sDetails "Universal Escape starting scenario. Starts on Everon unless an administrator has saved another terrain in the Escape mission menu. Add terrain mods to the server, select their scenario in the Terrains page, save and restart. No terrain connector is required. The server browser may keep this Automatic terrain title while the selected map is running."
|
||||
m_sGameMode "Escape"
|
||||
m_iPlayerCount 12
|
||||
m_eSaveTypes 0
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
SCR_MissionHeader {
|
||||
World "{B6C516F86302C679}Worlds/ERR_Everon.ent"
|
||||
SystemsConfig "{C50579A2EC48E8EF}Configs/Systems/MissionSystems.conf"
|
||||
m_sName "Co10 Escape Reintroduced — Everon"
|
||||
m_sAuthor "Jakub Jim Žáček"
|
||||
m_sDescription "Legacy diagnostic header for the integrated Co10 Escape Reintroduced mod."
|
||||
m_sGameMode "Escape"
|
||||
m_iPlayerCount 12
|
||||
m_eSaveTypes 0
|
||||
m_bShowInScenarioMenu 0
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
MetaFileClass {
|
||||
Name "{AD62322D5213C440}Missions/ERR_Everon_Base.conf"
|
||||
Configurations {
|
||||
ResourceClass PC {
|
||||
}
|
||||
ResourceClass HEADLESS : PC {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
SCR_ChimeraCharacter {
|
||||
ID "520EC961A090B1EE"
|
||||
components {
|
||||
ActionsManagerComponent "{520EA1D2F659CE02}" {
|
||||
additionalActions {
|
||||
ERR_BuryBodyAction "{A62CB789905D4011}" {
|
||||
ParentContextList { "Chest" "LowerTorso" }
|
||||
UIInfo SCR_ActionUIInfo "{A62CB789905D4012}" {
|
||||
Name "Bury body"
|
||||
Description "Bury the body and its remaining equipment. Loot anything you want to keep first."
|
||||
}
|
||||
Duration 8
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
MetaFileClass {
|
||||
Name "{37578B1666981FCE}Prefabs/Characters/Core/Character_Base.et"
|
||||
Configurations {
|
||||
EntityTemplateResourceClass PC {}
|
||||
EntityTemplateResourceClass HEADLESS : PC {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
GenericEntity : "{0D9A5DCF89AE7AA9}Prefabs/Items/Medicine/MorphineInjection_01/MorphineInjection_01.et" {
|
||||
ID "59DAB68B83510861"
|
||||
components {
|
||||
InventoryItemComponent "{5222EB4D0C73006B}" {
|
||||
Attributes SCR_ItemAttributeCollection "{5222EB4D0A2B466B}" {
|
||||
ItemDisplayName SCR_InventoryUIInfo "{5222EB4D07D865FA}" {
|
||||
Name "Combat epinephrine (strong)"
|
||||
Description "Stronger field injector carried by special troops and medics. Stops bleeding, restores consciousness, boosts dangerously low blood and partially heals injuries. Cannot revive the dead."
|
||||
m_aItemHints {}
|
||||
}
|
||||
}
|
||||
}
|
||||
SCR_ConsumableItemComponent "{59E68C74C5CEE74F}" {
|
||||
m_ConsumableEffect ERR_ConsumableCombatEpinephrine "{59E68C74C1F3910E}" {
|
||||
m_fItemRegenerationSpeed 0
|
||||
m_fItemRegenerationDuration 0
|
||||
m_aDamageEffectsToLoad {
|
||||
ERR_CombatEpinephrineEffect "{6122D60861B541E7}" {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
MetaFileClass {
|
||||
Name "{792634D9359AED21}Prefabs/ERR/CombatEpinephrine.et"
|
||||
Configurations {
|
||||
EntityTemplateResourceClass PC {}
|
||||
EntityTemplateResourceClass HEADLESS : PC {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
GenericEntity : "{B90E5414AB7C325B}Prefabs/Props/Wrecks/BTR70_Wreck/BTR70_wreck_static.et" {
|
||||
ID "9EA3A65E039FDAC0"
|
||||
components {
|
||||
Hierarchy "{BD920DE465D04435}" {}
|
||||
RplComponent "{7E4852E4567E49D6}" { Streamable Disabled }
|
||||
}
|
||||
coords 0 0 0
|
||||
angles 0 0 0
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
MetaFileClass {
|
||||
Name "{56C7BD958478A96D}Prefabs/ERR/CrashArmorWreck.et"
|
||||
Configurations {
|
||||
ResourceClass PC {}
|
||||
ResourceClass HEADLESS : PC {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
GenericEntity : "{EBBC859CCD94D96D}Prefabs/Props/EmptyStorages/USSR_AmmoBox_empty.et" {
|
||||
ID "A2DD3ADFFC0219D9"
|
||||
components {
|
||||
RplComponent "{6530845A09302A0A}" { Streamable Disabled }
|
||||
MeshObject "{6530845A09309E37}" {
|
||||
Object "{819ED3266EE5113B}Assets/Props/Military/AmmoBox/EquipmentBoxWooden_01/EquipmentBoxWooden_Weapon_01.xob"
|
||||
Materials {
|
||||
MaterialAssignClass "{6530845A093091BB}" {
|
||||
SourceMaterial "EquipmentBoxWooden_Weapon_01"
|
||||
AssignedMaterial "{7D2E831AFA5CF50D}Assets/Props/Military/AmmoBox/EquipmentBoxWooden_01/Data/EquipmentBoxWooden_Weapon_01_USSR.emat"
|
||||
}
|
||||
MaterialAssignClass "{6530845A093091B2}" {
|
||||
SourceMaterial "EquipmentBoxWooden_Weapon_01_Decal"
|
||||
}
|
||||
}
|
||||
}
|
||||
SCR_UniversalInventoryStorageComponent "{6530845A09302DE0}" {
|
||||
MaxCumulativeVolume 1000000
|
||||
MaxItemSize 200 100 100
|
||||
m_fMaxWeight 400
|
||||
Attributes SCR_ItemAttributeCollection "{6530845A09302DFF}" {
|
||||
ItemDisplayName SCR_InventoryUIInfo "{6530845A09302D03}" {
|
||||
Name "Crash-site weapons"
|
||||
Description "Salvaged weapons and ammunition. This crate does not refill."
|
||||
}
|
||||
}
|
||||
}
|
||||
InventoryStorageManagerComponent "{18A1D42A468F584F}" {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
MetaFileClass {
|
||||
Name "{CD7E33406E1ACA1D}Prefabs/ERR/CrashLootCrate.et"
|
||||
Configurations {
|
||||
ResourceClass PC {}
|
||||
ResourceClass HEADLESS : PC {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
ReplicatedParticleEffectEntity {
|
||||
ID "3B6ACA9A82472A27"
|
||||
components {
|
||||
Hierarchy "{666ECA1B7BACEF74}" {}
|
||||
RplComponent "{7E4852E4567E49D6}" { Streamable Disabled }
|
||||
}
|
||||
UseFrameEvent 1
|
||||
EffectPath "{6EE3295AC363E562}Particles/Enviroment/Smoke_Smokestack_Big.ptc"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
MetaFileClass {
|
||||
Name "{2608501780FEE1EA}Prefabs/ERR/CrashLootSmoke.et"
|
||||
Configurations {
|
||||
ResourceClass PC {}
|
||||
ResourceClass HEADLESS : PC {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
GenericEntity {
|
||||
ID "46B3FAD3A89A4EA3"
|
||||
components {
|
||||
ERR_LocationComponent "{606DF66235E34CA9}" { m_Kind 0 }
|
||||
RplComponent "{B6AB8FE43FE143B4}" { Streamable Disabled }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
MetaFileClass {
|
||||
Name "{174211DDE2F6422B}Prefabs/ERR/CrashSite.et"
|
||||
Configurations {
|
||||
ResourceClass PC {}
|
||||
ResourceClass HEADLESS : PC {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
GenericEntity : "{74DCE1AEC6832E18}Prefabs/Props/Wrecks/UH1H_wreck/UH1H_wreck_static.et" {
|
||||
ID "BDBAE020F10A41DF"
|
||||
components {
|
||||
Hierarchy "{BD920DE465D04435}" {}
|
||||
RplComponent "{7E4852E4567E49D6}" { Streamable Disabled }
|
||||
}
|
||||
coords 0 0 0
|
||||
angles 0 0 0
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
MetaFileClass {
|
||||
Name "{486DDD0B811047EE}Prefabs/ERR/CrashWreck.et"
|
||||
Configurations {
|
||||
ResourceClass PC {}
|
||||
ResourceClass HEADLESS : PC {}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,9 @@ SCR_FactionManager : "{A242612865F2A48E}Prefabs/MP/Managers/Factions/FactionMana
|
||||
ID "9D8B772E349A5ED5"
|
||||
m_bCanChangeFactionsPlayable 1
|
||||
Factions {
|
||||
SCR_Faction : "{3FA20B01D950D31F}Configs/Factions/CIV.conf" {
|
||||
m_bIsPlayable 0
|
||||
}
|
||||
SCR_Faction : "{8053DB656DD18B14}Configs/Factions/FIA.conf" {
|
||||
m_aFriendlyFactionsIds { }
|
||||
}
|
||||
|
||||
@@ -11,5 +11,6 @@ ERR_GameMode : "{0F307326459A1395}Prefabs/MP/Modes/GameMode_Base.et" {
|
||||
m_SpawnLogic ERR_SpawnLogic "{79AD722B2EFD595F}" {}
|
||||
}
|
||||
}
|
||||
m_fAutoReloadTime 120
|
||||
m_bUseSpawnPreload 1
|
||||
}
|
||||
|
||||
@@ -91,6 +91,14 @@ GenericEntity {
|
||||
coords 0 0 -6
|
||||
angles 0 0 0
|
||||
}
|
||||
StaticModelEntity : "{CAE1588B6265A2AB}Prefabs/Structures/Walls/BarbedWire/BarbedTape_01/BarbedTape_01_Short_noStake.et" {
|
||||
ID "F23719AD8B46C015"
|
||||
components {
|
||||
Hierarchy "{B86B0198EF40D762}" {}
|
||||
}
|
||||
coords 0 2.95 -6
|
||||
angles 0 90 0
|
||||
}
|
||||
DecalEntity : "{4AD6EB80A8B1FA3A}Prefabs/Decals/Dirt/Decal_Dirt_Patch_03.et" {
|
||||
ID "FCFBA935CC13E016"
|
||||
components {
|
||||
|
||||
@@ -6,4 +6,14 @@ GenericEntity {
|
||||
RigidBody "{4F20A90EDBD625A2}" { ModelGeometry 1 Static 1 }
|
||||
RplComponent "{4F20A90EDBD625A3}" {}
|
||||
}
|
||||
{
|
||||
StaticModelEntity : "{46904AF5D473FB75}Prefabs/Structures/Walls/BarbedWire/BarbedTape_01/BarbedTape_01_Long_noStake.et" {
|
||||
ID "A32066F0B56D0EA9"
|
||||
components {
|
||||
Hierarchy "{F93ED27DEB1E2490}" {}
|
||||
}
|
||||
coords 1.5 2.95 0
|
||||
angles 0 90 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
Vehicle : "{90B3A9E658D47B30}Prefabs/ERR/RescueEscortWest.et" {
|
||||
ID "90B3A9E658D47B71"
|
||||
components {
|
||||
MeshObject "{51DAA09FEFBFC0E7}" {
|
||||
Materials {
|
||||
MaterialAssignClass "{6168D22F500C3196}" {
|
||||
SourceMaterial "UH1H_Text_Decals_Ext_30EC8A12786836F9"
|
||||
AssignedMaterial "{E244CE4CE64A92D8}Assets/Vehicles/Helicopters/UH1H/Data/UH1H_Text_Decals_Ext_CIV.emat"
|
||||
}
|
||||
MaterialAssignClass "{6168D22F500C31B3}" {
|
||||
SourceMaterial "UH_1H_Body01_6224CA051369DE45"
|
||||
AssignedMaterial "{FFDE0DE516E4816F}Assets/Vehicles/Helicopters/UH1H/Data/UH_1H_Body01_CIV.emat"
|
||||
}
|
||||
}
|
||||
}
|
||||
SCR_VehicleFactionAffiliationComponent "{5882CBD9AC741CEC}" {
|
||||
"faction affiliation" "RHS_ION"
|
||||
}
|
||||
SlotManagerComponent "{55BCE45E438E4CFF}" {
|
||||
Slots {
|
||||
RegisteringComponentSlotInfo USArmyDecal {
|
||||
Enabled 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
MetaFileClass {
|
||||
Name "{90B3A9E658D47B70}Prefabs/ERR/RescueEscortION.et"
|
||||
Configurations {
|
||||
EntityTemplateResourceClass PC {}
|
||||
EntityTemplateResourceClass HEADLESS {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
Vehicle : "{DDDD9B51F1234DF3}Prefabs/Vehicles/Helicopters/UH1H/UH1H_armed.et" {
|
||||
ID "90B3A9E658D47B31"
|
||||
components {
|
||||
ERR_RescueFlightComponent "{90B3A9E658D47B32}" {
|
||||
}
|
||||
VehicleHelicopterSimulation "{51FAEE57F7DA270E}" {
|
||||
Simulation Helicopter "{51FAED9EF37A2D29}" {
|
||||
Engine HelicopterEngine "{51FAED9EF37A2D2B}" {
|
||||
StartUpTime 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
MetaFileClass {
|
||||
Name "{90B3A9E658D47B30}Prefabs/ERR/RescueEscortWest.et"
|
||||
Configurations {
|
||||
EntityTemplateResourceClass PC {}
|
||||
EntityTemplateResourceClass HEADLESS {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
Vehicle : "{7BD282AF716ED639}Prefabs/Vehicles/Helicopters/Mi8MT/Mi8MT_armed.et" {
|
||||
ID "90B3A9E658D47B11"
|
||||
components {
|
||||
ERR_RescueFlightComponent "{90B3A9E658D47B12}" {
|
||||
}
|
||||
VehicleHelicopterSimulation "{51FAEE57F7DA270E}" {
|
||||
Simulation Helicopter "{51FAED9EF37A2D29}" {
|
||||
Engine HelicopterEngine "{51FAED9EF37A2D2B}" {
|
||||
StartUpTime 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
MetaFileClass {
|
||||
Name "{90B3A9E658D47B10}Prefabs/ERR/RescueHelicopter.et"
|
||||
Configurations {
|
||||
EntityTemplateResourceClass PC {}
|
||||
EntityTemplateResourceClass HEADLESS {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
Vehicle : "{5BF04078A1EC66D8}Prefabs/Vehicles/Helicopters/Mi8MT/Mi8MTT-A_Black_no_pylons.et" {
|
||||
ID "90B3A9E658D47B51"
|
||||
components {
|
||||
ERR_RescueFlightComponent "{90B3A9E658D47B52}" {
|
||||
}
|
||||
VehicleHelicopterSimulation "{51FAEE57F7DA270E}" {
|
||||
Simulation Helicopter "{51FAED9EF37A2D29}" {
|
||||
Engine HelicopterEngine "{51FAED9EF37A2D2B}" {
|
||||
StartUpTime 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
MetaFileClass {
|
||||
Name "{90B3A9E658D47B50}Prefabs/ERR/RescueHelicopterAFRF.et"
|
||||
Configurations {
|
||||
EntityTemplateResourceClass PC {}
|
||||
EntityTemplateResourceClass HEADLESS {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
Vehicle : "{90B3A9E658D47B10}Prefabs/ERR/RescueHelicopter.et" {
|
||||
ID "90B3A9E658D47B41"
|
||||
components {
|
||||
MeshObject "{51DAA09FEFBFC0E7}" {
|
||||
Materials {
|
||||
MaterialAssignClass "{6168E1F20FDD15C6}" {
|
||||
SourceMaterial "Flag_decals_01_0418C5226701B4E1"
|
||||
AssignedMaterial "{35D747E785C4D90F}Assets/Vehicles/Helicopters/Mi8/Data/MI8_Flag_decals_CIV_01.emat"
|
||||
}
|
||||
}
|
||||
}
|
||||
SCR_VehicleFactionAffiliationComponent "{5882CBD9AC741CEC}" {
|
||||
"faction affiliation" "FIA"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
MetaFileClass {
|
||||
Name "{90B3A9E658D47B40}Prefabs/ERR/RescueHelicopterFIA.et"
|
||||
Configurations {
|
||||
EntityTemplateResourceClass PC {}
|
||||
EntityTemplateResourceClass HEADLESS {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
Vehicle : "{90B3A9E658D47B20}Prefabs/ERR/RescueHelicopterWest.et" {
|
||||
ID "90B3A9E658D47B61"
|
||||
components {
|
||||
MeshObject "{51DAA09FEFBFC0E7}" {
|
||||
Materials {
|
||||
MaterialAssignClass "{6168D22F500C3196}" {
|
||||
SourceMaterial "UH1H_Text_Decals_Ext_30EC8A12786836F9"
|
||||
AssignedMaterial "{E244CE4CE64A92D8}Assets/Vehicles/Helicopters/UH1H/Data/UH1H_Text_Decals_Ext_CIV.emat"
|
||||
}
|
||||
MaterialAssignClass "{6168D22F500C31B3}" {
|
||||
SourceMaterial "UH_1H_Body01_6224CA051369DE45"
|
||||
AssignedMaterial "{FFDE0DE516E4816F}Assets/Vehicles/Helicopters/UH1H/Data/UH_1H_Body01_CIV.emat"
|
||||
}
|
||||
}
|
||||
}
|
||||
SCR_VehicleFactionAffiliationComponent "{5882CBD9AC741CEC}" {
|
||||
"faction affiliation" "RHS_ION"
|
||||
}
|
||||
SlotManagerComponent "{55BCE45E438E4CFF}" {
|
||||
Slots {
|
||||
RegisteringComponentSlotInfo USArmyDecal {
|
||||
Enabled 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
MetaFileClass {
|
||||
Name "{90B3A9E658D47B60}Prefabs/ERR/RescueHelicopterION.et"
|
||||
Configurations {
|
||||
EntityTemplateResourceClass PC {}
|
||||
EntityTemplateResourceClass HEADLESS {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
Vehicle : "{70BAEEFC2D3FEE64}Prefabs/Vehicles/Helicopters/UH1H/UH1H.et" {
|
||||
ID "90B3A9E658D47B21"
|
||||
components {
|
||||
ERR_RescueFlightComponent "{90B3A9E658D47B22}" {
|
||||
}
|
||||
VehicleHelicopterSimulation "{51FAEE57F7DA270E}" {
|
||||
Simulation Helicopter "{51FAED9EF37A2D29}" {
|
||||
Engine HelicopterEngine "{51FAED9EF37A2D2B}" {
|
||||
StartUpTime 0
|
||||
}
|
||||
}
|
||||
}
|
||||
SlotManagerComponent "{55BCE45E438E4CFF}" {
|
||||
Slots {
|
||||
RegisteringComponentSlotInfo Seat_Gunners {
|
||||
Prefab "{67030528FA4FA725}Prefabs/Vehicles/Helicopters/UH1H/VehParts/Seats/VehPart_UH1H_seats_gunners.et"
|
||||
}
|
||||
RegisteringComponentSlotInfo Seat_Cargo {
|
||||
Prefab "{205599C7074F1E02}Prefabs/Vehicles/Helicopters/UH1H/VehParts/Seats/VehPart_UH1H_seats_cargo.et"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
MetaFileClass {
|
||||
Name "{90B3A9E658D47B20}Prefabs/ERR/RescueHelicopterWest.et"
|
||||
Configurations {
|
||||
EntityTemplateResourceClass PC {}
|
||||
EntityTemplateResourceClass HEADLESS {}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
SCR_ManualCamera {
|
||||
ERR_ObserverCamera {
|
||||
ID "634904BCEDA1A285"
|
||||
m_bInputEnabled 1
|
||||
m_sActionContext "ERR_ObserverContext"
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
ERR_GameMode : "{978E045E53EF36C8}Prefabs/ERR/GameMode.et" {
|
||||
ID "D12F4DF2625CA3AC"
|
||||
components {
|
||||
Hierarchy "{3DC8FB32DE3E1EBD}" {}
|
||||
ERR_ScenarioComponent "{B78E910E88DE30D2}" {
|
||||
m_TerrainIndependent 1
|
||||
}
|
||||
}
|
||||
{
|
||||
PerceptionManager Perception : "{028DAEAD63E056BE}Prefabs/World/Game/PerceptionManager.et" {
|
||||
ID "1CD3AA95056E2D65"
|
||||
}
|
||||
SCR_FactionManager Factions : "{5414041A15DC4B54}Prefabs/ERR/FactionManager.et" {
|
||||
ID "9FE86AC6892BF013"
|
||||
}
|
||||
SCR_LoadoutManager Loadouts : "{97EF4C1A41F63DDB}Prefabs/MP/Managers/Loadouts/LoadoutManager_USxUSSR.et" {
|
||||
ID "588AD55F7655278B"
|
||||
}
|
||||
RadioManagerEntity RadioManager : "{B8E09FAB91C4ECCD}Prefabs/Systems/Radio/RadioManager.et" {
|
||||
ID "5F87F7A144BDDE95"
|
||||
}
|
||||
ScriptedChatEntity Chat : "{F69BC912AC8236F9}Prefabs/MP/ScriptedChatEntity.et" {
|
||||
ID "85ED17B0836DB4A2"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
MetaFileClass {
|
||||
Name "{8FB1559AE3F29722}Prefabs/ERR/TerrainConnector.et"
|
||||
Configurations {
|
||||
EntityTemplateResourceClass PC {}
|
||||
EntityTemplateResourceClass HEADLESS : PC {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
ERR_TerrainTakeoverMode : "{978E045E53EF36C8}Prefabs/ERR/GameMode.et" {
|
||||
ID "F39C0AB761E582D4"
|
||||
components {
|
||||
Hierarchy "{3DC8FB32DE3E1EBD}" {}
|
||||
ERR_ScenarioComponent "{B78E910E88DE30D2}" {
|
||||
m_TerrainIndependent 1
|
||||
}
|
||||
}
|
||||
{
|
||||
PerceptionManager Perception : "{028DAEAD63E056BE}Prefabs/World/Game/PerceptionManager.et" {
|
||||
ID "1CD3AA95056E2D65"
|
||||
}
|
||||
SCR_FactionManager Factions : "{5414041A15DC4B54}Prefabs/ERR/FactionManager.et" {
|
||||
ID "9FE86AC6892BF013"
|
||||
}
|
||||
SCR_LoadoutManager Loadouts : "{97EF4C1A41F63DDB}Prefabs/MP/Managers/Loadouts/LoadoutManager_USxUSSR.et" {
|
||||
ID "588AD55F7655278B"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
MetaFileClass {
|
||||
Name "{9D182739ABEF405C}Prefabs/ERR/TerrainTakeover.et"
|
||||
Configurations { ResourceClass PC {} ResourceClass HEADLESS : PC {} }
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
ERR_TerrainTakeoverMode : "{9D182739ABEF405C}Prefabs/ERR/TerrainTakeover.et" {
|
||||
ID "CE902374A658BDF1"
|
||||
{
|
||||
ScriptedChatEntity Chat : "{F69BC912AC8236F9}Prefabs/MP/ScriptedChatEntity.et" {
|
||||
ID "85ED17B0836DB4A2"
|
||||
components { RplComponent "{CA456708D69D73BA}" { Streamable Disabled } }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
MetaFileClass {
|
||||
Name "{DA8E364BC095712F}Prefabs/ERR/TerrainTakeoverWithChat.et"
|
||||
Configurations { ResourceClass PC {} ResourceClass HEADLESS : PC {} }
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
GenericEntity {
|
||||
ID "E83CA269A0D647B1"
|
||||
components {
|
||||
MeshObject { Object "{76D738EEB0CA431F}Assets/Items/ERR_Atlas/ERR_AtlasLeaf.xob" InheritVisibility 1 }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
MetaFileClass {
|
||||
Name "{385C5228D90F456B}Prefabs/Items/Equipment/ERRM_AtlasLeaf.et"
|
||||
Configurations { EntityPrefabResourceClass PC {} EntityPrefabResourceClass HEADLESS : PC {} EntityPrefabResourceClass XBOX_ONE : PC {} EntityPrefabResourceClass XBOX_SERIES : PC {} EntityPrefabResourceClass PS4 : PC {} EntityPrefabResourceClass PS5 : PC {} }
|
||||
}
|
||||
@@ -4,8 +4,8 @@ GameEntity : "{C95E11C60810F432}Prefabs/Items/Core/Item_Base.et" {
|
||||
InventoryItemComponent "{5222EB4D0C73006B}" {
|
||||
Attributes SCR_ItemAttributeCollection "{5222EB4D0A2B466B}" {
|
||||
ItemDisplayName UIInfo "{7236A6DB18BD574C}" {
|
||||
Name "Shared Field Map"
|
||||
Description "A field map whose visible surface can be read by nearby players."
|
||||
Name "OCRAM OLOP Autoatlas"
|
||||
Description "Terrain atlas with an overview and numbered detail pages. Arrow keys turn sheets; numpad page number + Enter jumps directly; 0 + Enter returns to the index. Double-tap M opens the interactive map. No player marker."
|
||||
}
|
||||
ItemPhysAttributes ItemPhysicalAttributes PhysicalAttributes {
|
||||
Weight 0.1
|
||||
@@ -19,8 +19,9 @@ GameEntity : "{C95E11C60810F432}Prefabs/Items/Core/Item_Base.et" {
|
||||
ItemAnimationAttributes ItemAnimationAttributes "{4AC999738D39BA9A}" {
|
||||
AnimationAttachments {
|
||||
AnimationAttachmentInfo "{1D66E5B861D2F00A}" {
|
||||
AnimGraph "{83675D0B0AB1B2C3}Assets/Items/Equipment/workspaces/paper_map.agr"
|
||||
AnimGraph "{4959464B088CCCA4}Assets/Items/ERR_Atlas/AtlasTurn.agr"
|
||||
AnimInstance "{56794534F6B8C3D7}Assets/Items/Equipment/workspaces/player_map.asi"
|
||||
StartNode "ERRAtlasControl"
|
||||
BindingName "Gadget"
|
||||
}
|
||||
}
|
||||
@@ -46,13 +47,7 @@ GameEntity : "{C95E11C60810F432}Prefabs/Items/Core/Item_Base.et" {
|
||||
}
|
||||
}
|
||||
MeshObject "{E66421D681D64AB7}" {
|
||||
Object "{E443663E5241622A}Assets/Items/Equipment/Maps/Map_Paper_01/Map_Paper_01_folded.xob"
|
||||
Materials {
|
||||
MaterialAssignClass "{667CE655A40B0E50}" {
|
||||
SourceMaterial "01__Default"
|
||||
AssignedMaterial "{03D6DE777C8E411C}Materials/ERRM_MapRenderTarget.emat"
|
||||
}
|
||||
}
|
||||
Object "{51ABB9E2E838FC9E}Assets/Items/ERR_Atlas/ERR_Atlas.xob"
|
||||
InheritVisibility 1
|
||||
}
|
||||
RigidBody "{B29D8B091D657415}" {
|
||||
|
||||
@@ -78,6 +78,15 @@ class ERR_Actors
|
||||
return position;
|
||||
}
|
||||
|
||||
// Map altitude is not water depth. Query the terrain surface itself, without
|
||||
// Ground's 10cm spawn lift, so even shallow water remains unsuitable.
|
||||
static bool DryGround(vector position)
|
||||
{
|
||||
position[1] = GetGame().GetWorld().GetSurfaceY(position[0], position[2]);
|
||||
if (!ERR_ForceRules.Finite(position[1])) return false;
|
||||
return !ChimeraWorldUtils.TryGetWaterSurfaceSimple(GetGame().GetWorld(), position);
|
||||
}
|
||||
|
||||
static void Players(out array<IEntity> players, bool conscious = false)
|
||||
{
|
||||
players.Clear();
|
||||
|
||||
@@ -3,60 +3,6 @@ modded enum ChimeraMenuPreset
|
||||
ERR_AdminPanel
|
||||
}
|
||||
|
||||
// Presentation rules only: all mutations still use the authenticated command service.
|
||||
class ERR_AdminMenuFields
|
||||
{
|
||||
static array<string> Keys()
|
||||
{
|
||||
return {"radioDefenceSeconds", "aiIntelChancePercent", "intelRevealChancePercent", "startTimeMinutes", "timeMultiplier", "startDateCode", "weatherProfile", "reportIntervalSeconds", "supportSquadLimit", "responseRadiusMetres", "convoyCount", "antiAirChancePercent", "antiPersonnelMineChancePercent"};
|
||||
}
|
||||
static bool Available(string key, bool hq, bool intel)
|
||||
{
|
||||
if (key == "aiIntelChancePercent") return intel;
|
||||
if (key == "antiAirChancePercent" || key == "antiPersonnelMineChancePercent" || key == "convoyCount" || key == "reportIntervalSeconds" || key == "supportSquadLimit" || key == "responseRadiusMetres") return hq;
|
||||
return Keys().Contains(key);
|
||||
}
|
||||
// A short journal has no older off-screen lines, even if its slider is zero.
|
||||
static bool FollowJournal(bool empty, float slider, float height, float viewHeight, bool latest)
|
||||
{
|
||||
return latest || empty || slider >= 0.98 || height <= viewHeight;
|
||||
}
|
||||
// TextWidget interprets hash-prefixed tokens as localization IDs. Keep the
|
||||
// server audit and retained entries intact; render their numeric IDs literally.
|
||||
static string JournalText(string line, bool details = false)
|
||||
{
|
||||
return ERR_JournalText.Menu(line, details);
|
||||
}
|
||||
static string Display(string key, string value)
|
||||
{
|
||||
if (value == "-1" || value == "-2") return ERR_Settings.SelectionText(value.ToInt());
|
||||
if (key == "startTimeMinutes") return ERR_TimeService.Clock(value.ToInt());
|
||||
if (key == "startDateCode") return ERR_EnvironmentService.Date(value.ToInt());
|
||||
if (key == "weatherProfile") return ERR_EnvironmentService.ProfileName(value.ToInt());
|
||||
return value;
|
||||
}
|
||||
static string Command(string key, string value)
|
||||
{
|
||||
string prefix;
|
||||
if (key == "radioDefenceSeconds") prefix = "mission radio";
|
||||
else if (key == "aiIntelChancePercent") prefix = "intel carrying";
|
||||
else if (key == "intelRevealChancePercent") prefix = "intel reveal";
|
||||
else if (key == "startTimeMinutes") prefix = "mission time";
|
||||
else if (key == "timeMultiplier") prefix = "mission speed";
|
||||
else if (key == "startDateCode") prefix = "mission date";
|
||||
else if (key == "weatherProfile") prefix = "mission weather";
|
||||
else if (key == "reportIntervalSeconds") prefix = "hq interval";
|
||||
else if (key == "supportSquadLimit") prefix = "hq squads";
|
||||
else if (key == "responseRadiusMetres") prefix = "hq radius";
|
||||
else if (key == "antiAirChancePercent") prefix = "hq aa";
|
||||
else if (key == "antiPersonnelMineChancePercent") prefix = "hq mines";
|
||||
else if (key == "convoyCount") prefix = "hq convoy count";
|
||||
value = value.Trim();
|
||||
if (prefix.IsEmpty() || value.IsEmpty() || value.Length() > 32) return "";
|
||||
return prefix + " set " + value;
|
||||
}
|
||||
}
|
||||
|
||||
// Own mission dialog. ChimeraMenuBase owns focus, cursor and input-context lifetime.
|
||||
class ERR_AdminMenu : ChimeraMenuBase
|
||||
{
|
||||
@@ -83,6 +29,7 @@ class ERR_AdminMenu : ChimeraMenuBase
|
||||
protected bool m_ScrollToEnd;
|
||||
protected int m_LogCursor = -1;
|
||||
|
||||
protected void OpenVehicleAddon(string addon) {}
|
||||
static ERR_AdminMenu GetOpenMenu()
|
||||
{
|
||||
if (!GetGame() || !GetGame().GetMenuManager()) return null;
|
||||
@@ -100,12 +47,19 @@ class ERR_AdminMenu : ChimeraMenuBase
|
||||
m_Controller = SCR_PlayerController.Cast(GetGame().GetPlayerController());
|
||||
m_Mode = ERR_GameMode.Cast(GetGame().GetGameMode());
|
||||
if (!m_Controller) { Close(); return; }
|
||||
array<string> names = {"Close", "OpenSettings", "OpenWorld", "OpenFactions", "OpenHQ", "OpenJournal", "PauseJournal", "JournalDetails", "LatestJournal", "KeepWeaponOn", "KeepWeaponOff", "IntelSummary", "RerollIntel", "OpenMods", "OpenIntel", "OpenStatus"};
|
||||
names.Insert("RandomDay_startTimeMinutes");
|
||||
array<string> names = {"CrashLootRandom", "CrashLootCustom", "TelemetryOn", "TelemetryOff","SitesOn", "SitesOff", "PlaneOn", "PlaneOff", "Close", "PauseJournal", "JournalDetails", "LatestJournal", "KeepWeaponOn", "KeepWeaponOff", "SelfEpiOn", "SelfEpiOff", "IntelSummary", "RerollIntel", "RandomDay_startTimeMinutes", "Default_viewDistanceMetres", "RestartEmptyOn", "RestartEmptyOff", "RecommendedBoats", "RecommendedPlane", "RecommendedWeapons", "RecommendedDrones"};
|
||||
foreach (string mainButton : ERR_AdminMenuPages.MainButtons()) names.Insert(mainButton);
|
||||
foreach (string pageButton : Navigation()) names.Insert(pageButton);
|
||||
foreach (string key : ERR_AdminMenuFields.Keys())
|
||||
{
|
||||
array<string> choices = ERR_AdminMenuFields.Choices(key);
|
||||
if (!choices.IsEmpty())
|
||||
{
|
||||
foreach (string choice : choices) names.Insert("Choice_" + key + "_" + choice);
|
||||
continue;
|
||||
}
|
||||
names.Insert("Apply_" + key);
|
||||
names.Insert("Random_" + key);
|
||||
if (ERR_Settings.Randomizable(key)) names.Insert("Random_" + key);
|
||||
}
|
||||
foreach (string name : names)
|
||||
{
|
||||
@@ -120,10 +74,11 @@ class ERR_AdminMenu : ChimeraMenuBase
|
||||
button.m_OnClicked.Insert(OnButton);
|
||||
m_Buttons.Insert(button);
|
||||
}
|
||||
ERR_AdminMenuView.FitPanel(GetRootWidget());
|
||||
m_Ready = false; m_Age = 0; m_Poll = 0;
|
||||
GetGame().GetInputManager().AddActionListener(UIConstants.MENU_ACTION_BACK, EActionTrigger.PRESSED, OnBack);
|
||||
m_Bound = true;
|
||||
ShowPage(0);
|
||||
ShowPage(ERR_AdminPage.MISSION);
|
||||
UpdateActions();
|
||||
RequestSnapshot();
|
||||
}
|
||||
@@ -140,6 +95,7 @@ class ERR_AdminMenu : ChimeraMenuBase
|
||||
override void OnMenuUpdate(float tDelta)
|
||||
{
|
||||
super.OnMenuUpdate(tDelta);
|
||||
ERR_AdminMenuView.FitPanel(GetRootWidget());
|
||||
if (!m_Controller || m_Controller != GetGame().GetPlayerController() || !m_Mode || m_Mode != GetGame().GetGameMode() || ERR_MissionRules.Terminal(m_Mode.Phase())) { Close(); return; }
|
||||
m_Age += tDelta; m_Poll += tDelta;
|
||||
m_ClickDelay = Math.Max(0, m_ClickDelay - tDelta);
|
||||
@@ -150,7 +106,7 @@ class ERR_AdminMenu : ChimeraMenuBase
|
||||
UpdateActions();
|
||||
}
|
||||
if (IsFocused() && m_Poll >= 2) RequestSnapshot();
|
||||
if (m_Page != 2) return;
|
||||
if (m_Page != ERR_AdminPage.JOURNAL) return;
|
||||
m_LogPoll += tDelta;
|
||||
if (m_LogPending && m_LogPoll > 5) m_LogPending = false;
|
||||
if (IsFocused() && m_Ready && m_HQ && !m_LogPaused && !m_LogPending && m_LogPoll >= 1)
|
||||
@@ -181,12 +137,14 @@ class ERR_AdminMenu : ChimeraMenuBase
|
||||
protected void OnBack()
|
||||
{
|
||||
if (!IsFocused()) return;
|
||||
foreach (string key : ERR_AdminMenuFields.Keys())
|
||||
array<string> editFields = ERR_AdminMenuFields.Keys();
|
||||
editFields.InsertAll({"ModsSearch", "ItemsSearch", "VehiclesSearch", "ItemsPresetName"});
|
||||
foreach (string key : editFields)
|
||||
{
|
||||
SCR_EditBoxComponent edit = SCR_EditBoxComponent.GetEditBoxComponent(key, GetRootWidget());
|
||||
if (edit && edit.IsInWriteMode()) return;
|
||||
}
|
||||
if (m_Page != 0) ShowPage(0);
|
||||
if (m_Page != ERR_AdminPage.MISSION) ShowPage(ERR_AdminPage.MISSION);
|
||||
else Close();
|
||||
}
|
||||
protected void SetText(string name, string value)
|
||||
@@ -194,43 +152,63 @@ class ERR_AdminMenu : ChimeraMenuBase
|
||||
TextWidget widget = TextWidget.Cast(GetRootWidget().FindAnyWidget(name));
|
||||
if (widget) widget.SetText(value);
|
||||
}
|
||||
// Stable page order is also used by the shared navigation bar and native probe.
|
||||
static array<string> Pages() { return {"SettingsPage", "HQPage", "JournalPage", "ModsPage", "FactionsPage", "WorldPage", "IntelPage", "StatusPage"}; }
|
||||
static array<string> Navigation() { return {"OpenSettings", "OpenHQ", "OpenJournal", "OpenMods", "OpenFactions", "OpenWorld", "OpenIntel", "OpenStatus"}; }
|
||||
// Stable IDs are generated from the navigation manifest; extensions use named pages.
|
||||
static array<string> Pages() { return ERR_AdminMenuPages.Pages(); }
|
||||
static array<string> Navigation() { return ERR_AdminMenuPages.Navigation(); }
|
||||
protected void ShowPage(int page)
|
||||
{
|
||||
array<string> pages = Pages();
|
||||
array<string> navigation = Navigation();
|
||||
if (page < 0 || page >= pages.Count()) return;
|
||||
if (!ERR_AdminMenuView.ShowPage(GetRootWidget(), page)) return;
|
||||
m_Page = page;
|
||||
for (int i = 0; i < pages.Count(); i++)
|
||||
{
|
||||
GetRootWidget().FindAnyWidget(pages[i]).SetVisible(i == page);
|
||||
GetRootWidget().FindAnyWidget("Selected_" + navigation[i]).SetVisible(i == page);
|
||||
}
|
||||
if (page == 2) m_LogPoll = 1;
|
||||
if (page == ERR_AdminPage.JOURNAL) m_LogPoll = 1;
|
||||
}
|
||||
protected int m_KeepWeaponState = -1;
|
||||
protected int m_SelfEpiState = -1;
|
||||
protected int m_SitesState = -1;
|
||||
protected int m_RestartEmptyState = -1;
|
||||
protected int m_TelemetryState = -1;
|
||||
protected int m_CrashLootState = -1;
|
||||
protected void UpdateActions()
|
||||
{
|
||||
ERR_SelectionStyle.Choice(SCR_ButtonTextComponent.GetButtonText("CrashLootRandom", GetRootWidget()), m_CrashLootState == 1);
|
||||
ERR_SelectionStyle.Choice(SCR_ButtonTextComponent.GetButtonText("CrashLootCustom", GetRootWidget()), m_CrashLootState == 0);
|
||||
ERR_SelectionStyle.Pair(GetRootWidget(), "TelemetryOn", "TelemetryOff", m_TelemetryState);
|
||||
ERR_SelectionStyle.Pair(GetRootWidget(), "SitesOn", "SitesOff", m_SitesState);
|
||||
ERR_SelectionStyle.Pair(GetRootWidget(), "SelfEpiOn", "SelfEpiOff", m_SelfEpiState);
|
||||
ERR_SelectionStyle.Pair(GetRootWidget(), "KeepWeaponOn", "KeepWeaponOff", m_KeepWeaponState);
|
||||
ERR_SelectionStyle.Pair(GetRootWidget(), "RestartEmptyOn", "RestartEmptyOff", m_RestartEmptyState);
|
||||
foreach (SCR_ButtonBaseComponent button : m_Buttons)
|
||||
{
|
||||
string name = button.GetRootWidget().GetName();
|
||||
if (name.IndexOf("Apply_") == 0 || name.IndexOf("Random_") == 0 || name.IndexOf("RandomDay_") == 0)
|
||||
string choiceKey = ERR_AdminMenuFields.ChoiceKey(name);
|
||||
if (!choiceKey.IsEmpty()) button.SetEnabled(m_Ready && m_AvailableFields.Contains(choiceKey) && ERR_AdminMenuFields.Available(choiceKey, m_HQ, m_Intel));
|
||||
else if (name.IndexOf("Apply_") == 0 || name.IndexOf("Random_") == 0 || name.IndexOf("RandomDay_") == 0 || name.IndexOf("Default_") == 0)
|
||||
{
|
||||
int prefix = name.IndexOf("_") + 1;
|
||||
string key = name.Substring(prefix, name.Length() - prefix);
|
||||
button.SetEnabled(m_Ready && m_AvailableFields.Contains(key) && ERR_AdminMenuFields.Available(key, m_HQ, m_Intel));
|
||||
}
|
||||
else if (name == "PlaneOn" || name == "PlaneOff") button.SetEnabled(m_Ready && m_HQ && m_AvailableFields.Contains("planeReconEnabled"));
|
||||
else if (name == "RerollIntel" || name == "IntelSummary") button.SetEnabled(m_Ready && m_Intel);
|
||||
else if (name == "LatestJournal") button.SetEnabled(m_Ready && m_HQ);
|
||||
else if (name == "KeepWeaponOn" || name == "KeepWeaponOff") button.SetEnabled(m_Ready);
|
||||
else if (name == "CrashLootRandom") button.SetEnabled(m_Ready && m_CrashLootState == 0);
|
||||
else if (name == "CrashLootCustom") button.SetEnabled(m_Ready && m_CrashLootState == 1);
|
||||
else if (name == "TelemetryOn") button.SetEnabled(m_Ready && m_TelemetryState == 0);
|
||||
else if (name == "TelemetryOff") button.SetEnabled(m_Ready && m_TelemetryState == 1);
|
||||
else if (name == "RestartEmptyOn") button.SetEnabled(m_Ready && m_RestartEmptyState == 0);
|
||||
else if (name == "RestartEmptyOff") button.SetEnabled(m_Ready && m_RestartEmptyState == 1);
|
||||
else if (name == "SitesOn") button.SetEnabled(m_Ready && m_SitesState == 0);
|
||||
else if (name == "SitesOff") button.SetEnabled(m_Ready && m_SitesState == 1);
|
||||
else if (name == "SelfEpiOn") button.SetEnabled(m_Ready && m_SelfEpiState == 0);
|
||||
else if (name == "SelfEpiOff") button.SetEnabled(m_Ready && m_SelfEpiState == 1);
|
||||
else if (name == "KeepWeaponOn") button.SetEnabled(m_Ready && m_KeepWeaponState == 0);
|
||||
else if (name == "KeepWeaponOff") button.SetEnabled(m_Ready && m_KeepWeaponState == 1);
|
||||
}
|
||||
}
|
||||
void ReceiveSnapshot(bool authorized, array<string> values, array<string> status)
|
||||
{
|
||||
if (!authorized) { Close(); return; }
|
||||
if (!values) return;
|
||||
m_HQ = false; m_Intel = false; m_AvailableFields.Clear();
|
||||
m_HQ = false; m_Intel = false; m_AvailableFields.Clear(); m_KeepWeaponState = -1; m_SelfEpiState = -1; m_RestartEmptyState = -1; m_TelemetryState = -1; m_CrashLootState = -1;
|
||||
foreach (string entry : values)
|
||||
{
|
||||
if (entry == "supports=hq") { m_HQ = true; continue; }
|
||||
@@ -238,23 +216,92 @@ class ERR_AdminMenu : ChimeraMenuBase
|
||||
int divider = entry.IndexOf("=");
|
||||
if (divider <= 0) continue;
|
||||
string key = entry.Substring(0, divider);
|
||||
if (key == "radioIntelProgress") { SetText("RadioIntelProgress", entry.Substring(divider + 1, entry.Length() - divider - 1)); continue; }
|
||||
if (key == "droneRuntime") { SetText("DroneRuntime", entry.Substring(divider + 1, entry.Length() - divider - 1)); continue; }
|
||||
if (key == "recommendedBikeRuntime") { SetText("RecommendedBikeRuntime", entry.Substring(divider + 1, entry.Length() - divider - 1)); continue; }
|
||||
if (key.IndexOf("recommended_") == 0)
|
||||
{
|
||||
SetText(key, entry.Substring(divider + 1, entry.Length() - divider - 1)); continue;
|
||||
}
|
||||
if (key.IndexOf("recommendedReady_") == 0)
|
||||
{
|
||||
string addonKey = key.Substring(17, key.Length() - 17);
|
||||
Widget state = GetRootWidget().FindAnyWidget("recommended_" + addonKey);
|
||||
if (state)
|
||||
{
|
||||
if (entry.Substring(divider + 1, entry.Length() - divider - 1) == "1") state.SetColor(Color.FromInt(ERR_MenuTheme.READY_TEXT));
|
||||
else state.SetColor(Color.FromInt(ERR_MenuTheme.UNAVAILABLE_TEXT));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (key.IndexOf("resolved_") == 0)
|
||||
{
|
||||
string resolvedKey = key.Substring(9, key.Length() - 9);
|
||||
if (ERR_AdminMenuFields.Keys().Contains(resolvedKey)) SetText("Saved_" + resolvedKey, "Saved: Random (using " + entry.Substring(divider + 1, entry.Length() - divider - 1) + ")");
|
||||
continue;
|
||||
}
|
||||
if (key == "crashLootMode")
|
||||
{
|
||||
string lootMode = entry.Substring(divider + 1, entry.Length() - divider - 1);
|
||||
if (lootMode == "0" || lootMode == "1") m_CrashLootState = lootMode.ToInt();
|
||||
SetText("Saved_crashLootMode", "Next round: " + ERR_AdminMenuFields.Display(key, lootMode));
|
||||
continue;
|
||||
}
|
||||
if (key == "planeReconEnabled")
|
||||
{
|
||||
int planeState = entry.Substring(divider + 1, entry.Length() - divider - 1).ToInt();
|
||||
ERR_SelectionStyle.Pair(GetRootWidget(), "PlaneOn", "PlaneOff", planeState);
|
||||
m_AvailableFields.Insert(key); continue;
|
||||
}
|
||||
if (key == "telemetryStatus") { SetText("TelemetryStatus", entry.Substring(divider + 1, entry.Length() - divider - 1)); continue; }
|
||||
if (key == "telemetryEnabled")
|
||||
{
|
||||
m_TelemetryState = entry.Substring(divider + 1, entry.Length() - divider - 1).ToInt();
|
||||
string telemetryState = "Disabled";
|
||||
if (m_TelemetryState == 1) telemetryState = "Enabled";
|
||||
SetText("Saved_telemetryEnabled", "Saved: " + telemetryState);
|
||||
continue;
|
||||
}
|
||||
if (key == "restartOnEmpty")
|
||||
{
|
||||
m_RestartEmptyState = entry.Substring(divider + 1, entry.Length() - divider - 1).ToInt();
|
||||
string restartState = "Disabled";
|
||||
if (m_RestartEmptyState == 1) restartState = "Enabled";
|
||||
SetText("Saved_restartOnEmpty", "Saved: " + restartState);
|
||||
continue;
|
||||
}
|
||||
if (key == "showAllSites")
|
||||
{
|
||||
m_SitesState = entry.Substring(divider + 1, entry.Length() - divider - 1).ToInt();
|
||||
continue;
|
||||
}
|
||||
if (key == "selfEpinephrine")
|
||||
{
|
||||
m_SelfEpiState = entry.Substring(divider + 1, entry.Length() - divider - 1).ToInt();
|
||||
continue;
|
||||
}
|
||||
if (key == "keepWeaponOnUnconscious")
|
||||
{
|
||||
m_KeepWeaponState = entry.Substring(divider + 1, entry.Length() - divider - 1).ToInt();
|
||||
string retention = "Disabled: held guns drop on unconsciousness.";
|
||||
if (entry.Substring(divider + 1, entry.Length() - divider - 1) == "1") retention = "Enabled: retain your gun and equip it after revival.";
|
||||
SetText("Saved_keepWeaponOnUnconscious", retention);
|
||||
continue;
|
||||
}
|
||||
if (!ERR_AdminMenuFields.Keys().Contains(key)) continue;
|
||||
string value = ERR_AdminMenuFields.Display(key, entry.Substring(divider + 1, entry.Length() - divider - 1));
|
||||
string saved = entry.Substring(divider + 1, entry.Length() - divider - 1);
|
||||
if (ERR_Settings.Randomizable(key) && ERR_AdminMenuFields.Choices(key).IsEmpty()) ERR_SelectionStyle.Choice(SCR_ButtonTextComponent.GetButtonText("Random_" + key, GetRootWidget()), saved == "-1");
|
||||
if (key == "startTimeMinutes") ERR_SelectionStyle.Choice(SCR_ButtonTextComponent.GetButtonText("RandomDay_startTimeMinutes", GetRootWidget()), saved == "-2");
|
||||
if (key == "viewDistanceMetres") ERR_SelectionStyle.Choice(SCR_ButtonTextComponent.GetButtonText("Default_viewDistanceMetres", GetRootWidget()), saved == "0");
|
||||
array<string> choices = ERR_AdminMenuFields.Choices(key);
|
||||
foreach (string choice : choices)
|
||||
ERR_SelectionStyle.Choice(SCR_ButtonTextComponent.GetButtonText("Choice_" + key + "_" + choice, GetRootWidget()), choice == ERR_AdminMenuFields.SavedChoice(key, saved));
|
||||
string value = ERR_AdminMenuFields.Display(key, saved);
|
||||
m_AvailableFields.Insert(key);
|
||||
SetText("Saved_" + key, "Saved: " + value);
|
||||
if (key == "startMode") SetText("Saved_" + key, ERR_AdminMenuFields.StartSummary(entry.Substring(divider + 1, entry.Length() - divider - 1).ToInt(), m_Mode.StartMode()));
|
||||
else if (key == "aiDoctrine" || key == "aiShadow") SetText("Saved_" + key, "Next round: " + value);
|
||||
else SetText("Saved_" + key, "Saved: " + value);
|
||||
if (!choices.IsEmpty()) continue;
|
||||
// Existing fields retain all draft text, including a temporarily empty edit.
|
||||
if (!m_InitializedFields.Contains(key))
|
||||
{
|
||||
@@ -272,7 +319,7 @@ class ERR_AdminMenu : ChimeraMenuBase
|
||||
}
|
||||
if (summary.IsEmpty()) summary = "No mission status is available yet.";
|
||||
SetText("Status", summary);
|
||||
string connection = "Connected to server. Editing a value does not save it: press Apply. Unsaved edits are lost on close.";
|
||||
string connection = "Connected to server. Use Apply for edited values. Selection buttons save immediately. Unsaved edits are lost on close.";
|
||||
if (!m_HQ || !m_Intel) connection = "Connected. Controls for companion systems absent from this mission are disabled.";
|
||||
SetText("Connection", connection);
|
||||
m_Ready = true; m_Age = 0; UpdateActions();
|
||||
@@ -324,6 +371,12 @@ class ERR_AdminMenu : ChimeraMenuBase
|
||||
if (!button || !IsFocused()) return;
|
||||
string name = button.GetRootWidget().GetName();
|
||||
if (name == "Close") { Close(); return; }
|
||||
int section = ERR_AdminMenuPages.MainButtons().Find(name);
|
||||
if (section >= 0) { ShowPage(ERR_AdminMenuPages.MainPage(section)); return; }
|
||||
if (name == "RecommendedDrones") { ShowPage(ERR_AdminPage.DRONES); return; }
|
||||
if (name == "RecommendedBoats") { ShowPage(ERR_AdminPage.BOATS); return; }
|
||||
if (name == "RecommendedPlane") { ShowPage(ERR_AdminPage.PLANE); return; }
|
||||
if (name == "RecommendedWeapons") { ShowPage(ERR_AdminPage.ITEMS); return; }
|
||||
int page = Navigation().Find(name);
|
||||
if (page >= 0) { ShowPage(page); return; }
|
||||
if (name == "JournalDetails")
|
||||
@@ -354,6 +407,56 @@ class ERR_AdminMenu : ChimeraMenuBase
|
||||
return;
|
||||
}
|
||||
if (!m_Controller || !m_Ready || m_Age > 6 || m_ClickDelay > 0) return;
|
||||
string choiceKey = ERR_AdminMenuFields.ChoiceKey(name);
|
||||
if (!choiceKey.IsEmpty())
|
||||
{
|
||||
if (!m_AvailableFields.Contains(choiceKey) || !ERR_AdminMenuFields.Available(choiceKey, m_HQ, m_Intel)) return;
|
||||
int choicePrefix = choiceKey.Length() + 8;
|
||||
string choice = name.Substring(choicePrefix, name.Length() - choicePrefix);
|
||||
m_ClickDelay = 0.3; SetText("Feedback", "Saving selection for next round...");
|
||||
m_Controller.ERR_AdminSubmit(ERR_AdminMenuFields.Command(choiceKey, choice), true); return;
|
||||
}
|
||||
if (name == "PlaneOn" || name == "PlaneOff")
|
||||
{
|
||||
if (!m_HQ || !m_AvailableFields.Contains("planeReconEnabled")) return;
|
||||
string enabled = "0"; if (name == "PlaneOn") enabled = "1";
|
||||
m_ClickDelay = 0.3; SetText("Feedback", "Saving reconnaissance plane selection for next round...");
|
||||
m_Controller.ERR_AdminSubmit("mission plane set " + enabled, true); return;
|
||||
}
|
||||
if (name == "CrashLootRandom" || name == "CrashLootCustom")
|
||||
{
|
||||
if (m_CrashLootState < 0) return;
|
||||
string lootMode = "custom"; if (name == "CrashLootRandom") lootMode = "random";
|
||||
m_ClickDelay = 0.3; SetText("Feedback", "Saving crash-site loot mode for next round...");
|
||||
m_Controller.ERR_AdminSubmit("mission crashloot set " + lootMode, true); return;
|
||||
}
|
||||
if (name == "TelemetryOn" || name == "TelemetryOff")
|
||||
{
|
||||
if (m_TelemetryState < 0) return;
|
||||
string enabled = "0"; if (name == "TelemetryOn") enabled = "1";
|
||||
m_Controller.ERR_AdminSubmit("mission telemetry set " + enabled, true); return;
|
||||
}
|
||||
if (name == "RestartEmptyOn" || name == "RestartEmptyOff")
|
||||
{
|
||||
if (m_RestartEmptyState < 0) return;
|
||||
string restartEnabled = "0"; if (name == "RestartEmptyOn") restartEnabled = "1";
|
||||
m_ClickDelay = 0.3; SetText("Feedback", "Saving restart after last disconnect...");
|
||||
m_Controller.ERR_AdminSubmit("mission restartempty set " + restartEnabled, true); return;
|
||||
}
|
||||
if (name == "SitesOn" || name == "SitesOff")
|
||||
{
|
||||
if (m_SitesState < 0) return;
|
||||
string sitesEnabled = "0"; if (name == "SitesOn") sitesEnabled = "1";
|
||||
m_ClickDelay = 0.3; SetText("Feedback", "Saving site map visibility...");
|
||||
m_Controller.ERR_AdminSubmit("intel sites set " + sitesEnabled, true); return;
|
||||
}
|
||||
if (name == "SelfEpiOn" || name == "SelfEpiOff")
|
||||
{
|
||||
if (m_SelfEpiState < 0) return;
|
||||
m_ClickDelay = 0.3; SetText("Feedback", "Saving self epinephrine permission...");
|
||||
string selfEpi = "0"; if (name == "SelfEpiOn") selfEpi = "1";
|
||||
m_Controller.ERR_AdminSubmit("mission selfepi set " + selfEpi, true); return;
|
||||
}
|
||||
if (name == "KeepWeaponOn" || name == "KeepWeaponOff")
|
||||
{
|
||||
string retain = "0"; if (name == "KeepWeaponOn") retain = "1";
|
||||
@@ -370,7 +473,7 @@ class ERR_AdminMenu : ChimeraMenuBase
|
||||
if (pause) pause.SetText("Pause live");
|
||||
return;
|
||||
}
|
||||
if (name.IndexOf("Random_") == 0 || name.IndexOf("RandomDay_") == 0)
|
||||
if (name.IndexOf("Random_") == 0 || name.IndexOf("RandomDay_") == 0 || name == "Default_viewDistanceMetres")
|
||||
{
|
||||
int prefix = name.IndexOf("_") + 1;
|
||||
string key = name.Substring(prefix, name.Length() - prefix);
|
||||
@@ -379,6 +482,7 @@ class ERR_AdminMenu : ChimeraMenuBase
|
||||
if (edit)
|
||||
{
|
||||
string choice = "Random";
|
||||
if (name == "Default_viewDistanceMetres") choice = "default";
|
||||
if (name.IndexOf("RandomDay_") == 0) choice = "Random day";
|
||||
edit.SetValue(choice);
|
||||
SetText("Feedback", choice + " selected. Press Apply to save.");
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
// Presentation rules only: all mutations still use the authenticated command service.
|
||||
class ERR_AdminMenuFields
|
||||
{
|
||||
static array<string> Keys()
|
||||
{
|
||||
return {"shoreBoatCount", "shoreBoatChancePercent", "shoreBoatCrossingChancePercent", "crashSiteCount", "crashHelicopterChancePercent", "crashRifleCount", "crashMagazineCount", "crashAntiAirChancePercent", "crashRocketChancePercent", "aiDoctrine", "aiShadow", "automaticEquipmentChancePercent", "sarkaAngerPerCar", "motorbikeCount", "droneReconCount", "droneCooldownSeconds", "planeAltitudeMetres", "planeReplacementSeconds", "planeSectorSeconds", "fieldCampCount", "fieldHospitalCount", "startWeaponCount", "startMagazineCount", "objectGuardCount", "objectGuardEvery", "objectGuardHQMode", "hunterSpawnDistanceMetres", "roadblockCount", "largeRoadblockCount", "mainRoadWidthMetres", "civilianTrafficCount", "garageVehicleCount", "garageChancePercent", "garageRadiusMetres", "garageSpawnIntervalSeconds", "emptyRestartHours", "startMode", "radioDefenceSeconds", "aiIntelChancePercent", "intelRevealChancePercent", "radioIntelSiteCount", "startTimeMinutes", "timeMultiplier", "startDateCode", "weatherProfile", "viewDistanceMetres", "reportIntervalSeconds", "supportSquadLimit", "responseRadiusMetres", "convoyCount", "antiAirChancePercent", "antiPersonnelMineChancePercent", "heliTimerMinutes"};
|
||||
}
|
||||
// Only bounded named modes belong here; quantities and free-form values stay edits.
|
||||
static array<string> Choices(string key)
|
||||
{
|
||||
if (key == "startMode") return {"prison", "crash"};
|
||||
if (key == "aiDoctrine") return {"current", "coordinated"};
|
||||
if (key == "aiShadow") return {"off", "on"};
|
||||
if (key == "objectGuardEvery") return {"1", "2", "3", "4"};
|
||||
if (key == "objectGuardHQMode") return {"0", "1", "2"};
|
||||
if (key == "weatherProfile") return {"authored", "clear", "overcast", "rain", "random"};
|
||||
return {};
|
||||
}
|
||||
static string ChoiceKey(string button)
|
||||
{
|
||||
if (!button.StartsWith("Choice_")) return "";
|
||||
array<string> parts = {}; button.Split("_", parts, false);
|
||||
if (parts.Count() != 3 || !Choices(parts[1]).Contains(parts[2])) return "";
|
||||
return parts[1];
|
||||
}
|
||||
static string SavedChoice(string key, string saved)
|
||||
{
|
||||
string choice = Display(key, saved); choice.ToLower(); return choice;
|
||||
}
|
||||
static bool Available(string key, bool hq, bool intel)
|
||||
{
|
||||
if (key == "aiDoctrine" || key == "aiShadow") return hq;
|
||||
if (key == "sarkaAngerPerCar" || key == "droneReconCount" || key == "droneCooldownSeconds") return hq;
|
||||
if (key == "aiIntelChancePercent") return intel;
|
||||
if (key == "automaticEquipmentChancePercent" || key == "motorbikeCount" || key == "planeAltitudeMetres" || key == "planeReplacementSeconds" || key == "planeSectorSeconds" || key == "planeReconEnabled" || key == "objectGuardCount" || key == "objectGuardEvery" || key == "objectGuardHQMode" || key == "hunterSpawnDistanceMetres" || key == "heliTimerMinutes" || key == "antiAirChancePercent" || key == "antiPersonnelMineChancePercent" || key == "convoyCount" || key == "reportIntervalSeconds" || key == "supportSquadLimit" || key == "responseRadiusMetres") return hq;
|
||||
return Keys().Contains(key);
|
||||
}
|
||||
// A short journal has no older off-screen lines, even if its slider is zero.
|
||||
static bool FollowJournal(bool empty, float slider, float height, float viewHeight, bool latest)
|
||||
{
|
||||
return latest || empty || slider >= 0.98 || height <= viewHeight;
|
||||
}
|
||||
// TextWidget interprets hash-prefixed tokens as localization IDs. Keep the
|
||||
// server audit and retained entries intact; render their numeric IDs literally.
|
||||
static string JournalText(string line, bool details = false)
|
||||
{
|
||||
return ERR_JournalText.Menu(line, details);
|
||||
}
|
||||
static string Display(string key, string value)
|
||||
{
|
||||
if (key == "crashLootMode") { if (value == "1") return "Random"; return "Custom"; }
|
||||
if (key == "aiDoctrine") { if (value == "1") return "Coordinated"; return "Current"; }
|
||||
if (key == "aiShadow") { if (value == "1") return "On"; return "Off"; }
|
||||
if (value == "-1" || value == "-2") return ERR_Settings.SelectionText(value.ToInt());
|
||||
if (key == "startMode") { if (value == "1") return "Crash"; return "Prison"; }
|
||||
if (key == "startTimeMinutes") return ERR_TimeService.Clock(value.ToInt());
|
||||
if (key == "startDateCode") return ERR_EnvironmentService.Date(value.ToInt());
|
||||
if (key == "weatherProfile") return ERR_EnvironmentService.ProfileName(value.ToInt());
|
||||
if (key == "viewDistanceMetres" && value == "0") return "default";
|
||||
return value;
|
||||
}
|
||||
static string StartSummary(int saved, int current)
|
||||
{
|
||||
return "Next start: " + Display("startMode", saved.ToString()) + " | Current round: " + Display("startMode", current.ToString());
|
||||
}
|
||||
static string Command(string key, string value)
|
||||
{
|
||||
string prefix;
|
||||
if (key == "aiDoctrine") prefix = "mission doctrine";
|
||||
else if (key == "aiShadow") prefix = "mission aishadow";
|
||||
else if (key == "automaticEquipmentChancePercent") prefix = "hq equipment";
|
||||
else if (key == "sarkaAngerPerCar") prefix = "mission sarkapoints";
|
||||
else if (key == "motorbikeCount") prefix = "mission motorbikes";
|
||||
else if (key == "droneReconCount") prefix = "mission drones";
|
||||
else if (key == "droneCooldownSeconds") prefix = "mission dronecooldown";
|
||||
else if (key == "planeReconEnabled") prefix = "mission plane";
|
||||
else if (key == "planeAltitudeMetres") prefix = "mission planealtitude";
|
||||
else if (key == "planeReplacementSeconds") prefix = "mission planereplacement";
|
||||
else if (key == "planeSectorSeconds") prefix = "mission planesector";
|
||||
else if (key == "startWeaponCount") prefix = "mission startweapons";
|
||||
else if (key == "startMagazineCount") prefix = "mission startammo";
|
||||
else if (key == "objectGuardEvery") prefix = "mission objectevery";
|
||||
else if (key == "objectGuardHQMode") prefix = "mission objecthq";
|
||||
else if (key == "objectGuardCount") prefix = "mission objectguards";
|
||||
else if (key == "hunterSpawnDistanceMetres") prefix = "mission hunterdistance";
|
||||
else if (key == "crashLootMode") prefix = "mission crashloot";
|
||||
else if (key == "crashSiteCount") prefix = "mission crashsites";
|
||||
else if (key == "crashHelicopterChancePercent") prefix = "mission crashhelis";
|
||||
else if (key == "crashRifleCount") prefix = "mission crashrifles";
|
||||
else if (key == "crashMagazineCount") prefix = "mission crashammo";
|
||||
else if (key == "crashAntiAirChancePercent") prefix = "mission crashaa";
|
||||
else if (key == "crashRocketChancePercent") prefix = "mission crashrockets";
|
||||
else if (key == "shoreBoatCount") prefix = "mission shoreboats";
|
||||
else if (key == "shoreBoatChancePercent") prefix = "mission boatchance";
|
||||
else if (key == "shoreBoatCrossingChancePercent") prefix = "mission boatcrossings";
|
||||
else if (key == "fieldCampCount") prefix = "mission camps";
|
||||
else if (key == "fieldHospitalCount") prefix = "mission hospitals";
|
||||
else if (key == "roadblockCount") prefix = "mission roadblocks";
|
||||
else if (key == "largeRoadblockCount") prefix = "mission largeroadblocks";
|
||||
else if (key == "mainRoadWidthMetres") prefix = "mission mainroadwidth";
|
||||
else if (key == "emptyRestartHours") prefix = "mission emptyhours";
|
||||
else if (key == "garageChancePercent") prefix = "mission garagechance";
|
||||
else if (key == "garageRadiusMetres") prefix = "mission garageradius";
|
||||
else if (key == "garageSpawnIntervalSeconds") prefix = "mission garageinterval";
|
||||
else if (key == "garageVehicleCount") prefix = "mission garagevehicles";
|
||||
else if (key == "civilianTrafficCount") prefix = "mission traffic";
|
||||
else if (key == "startMode") prefix = "mission start";
|
||||
else if (key == "radioDefenceSeconds") prefix = "mission radio";
|
||||
else if (key == "aiIntelChancePercent") prefix = "intel carrying";
|
||||
else if (key == "intelRevealChancePercent") prefix = "intel reveal";
|
||||
else if (key == "radioIntelSiteCount") prefix = "intel radiosites";
|
||||
else if (key == "startTimeMinutes") prefix = "mission time";
|
||||
else if (key == "timeMultiplier") prefix = "mission speed";
|
||||
else if (key == "startDateCode") prefix = "mission date";
|
||||
else if (key == "weatherProfile") prefix = "mission weather";
|
||||
else if (key == "viewDistanceMetres") prefix = "mission viewdistance";
|
||||
else if (key == "reportIntervalSeconds") prefix = "hq interval";
|
||||
else if (key == "supportSquadLimit") prefix = "hq squads";
|
||||
else if (key == "responseRadiusMetres") prefix = "hq radius";
|
||||
else if (key == "antiAirChancePercent") prefix = "hq aa";
|
||||
else if (key == "antiPersonnelMineChancePercent") prefix = "hq mines";
|
||||
else if (key == "heliTimerMinutes") prefix = "hq heli timer";
|
||||
else if (key == "convoyCount") prefix = "hq convoy count";
|
||||
value = value.Trim();
|
||||
if (prefix.IsEmpty() || value.IsEmpty() || value.Length() > 32) return "";
|
||||
return prefix + " set " + value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
// Generated by tools/build_admin_menu.py; edit ui/admin_menu/navigation.json.
|
||||
enum ERR_AdminPage
|
||||
{
|
||||
MISSION = 0,
|
||||
HQ = 1,
|
||||
JOURNAL = 2,
|
||||
MODS = 3,
|
||||
FACTIONS = 4,
|
||||
WORLD = 5,
|
||||
INTEL = 6,
|
||||
STATUS = 7,
|
||||
TERRAINS = 8,
|
||||
ITEMS = 9,
|
||||
SPEECH = 10,
|
||||
FORCES = 11,
|
||||
GUARDS = 12,
|
||||
SITES = 13,
|
||||
PLANE = 14,
|
||||
RECOMMENDATIONS = 15,
|
||||
WEIGHTS = 16,
|
||||
ALLOWED_ITEMS = 17,
|
||||
ITEM_PRESETS = 18,
|
||||
AI = 19,
|
||||
STARTER = 20,
|
||||
SARKA = 21,
|
||||
DRONES = 22,
|
||||
RADIO_INTEL = 23,
|
||||
TELEMETRY = 24,
|
||||
VEHICLES = 25,
|
||||
CHECKPOINTS = 26,
|
||||
VEHICLE_POOL = 27,
|
||||
PACKS = 28,
|
||||
CRASH_SITES = 29,
|
||||
BOATS = 30,
|
||||
}
|
||||
|
||||
class ERR_AdminMenuPages
|
||||
{
|
||||
static array<string> Pages() { return {"SettingsPage", "HQPage", "JournalPage", "ModsPage", "FactionsPage", "WorldPage", "IntelPage", "StatusPage", "TerrainsPage", "ItemsPage", "TTSPage", "ForcesPage", "GuardsPage", "SitesPage", "PlanePage", "RecommendationsPage", "WeightsPage", "AllowedItemsPage", "ItemPresetsPage", "AIPage", "StarterPage", "SarkaPage", "DronesPage", "RadioIntelPage", "TelemetryPage", "VehiclesPage", "CheckpointsPage", "VehiclePoolPage", "PacksPage", "CrashSitesPage", "BoatsPage"}; }
|
||||
static array<string> Navigation() { return {"OpenSettings", "OpenHQ", "OpenJournal", "OpenMods", "OpenFactions", "OpenWorld", "OpenIntel", "OpenStatus", "OpenTerrains", "OpenItems", "OpenTTS", "OpenForces", "OpenGuards", "OpenSites", "OpenPlane", "OpenRecommendations", "OpenWeights", "OpenAllowedItems", "OpenItemPresets", "OpenAI", "OpenStarter", "OpenSarka", "OpenDrones", "OpenRadioIntel", "OpenTelemetry", "OpenVehicles", "OpenCheckpoints", "OpenVehiclePool", "OpenPacks", "OpenCrashSites", "OpenBoats"}; }
|
||||
static array<string> MainButtons() { return {"Main_Mission", "Main_Forces", "Main_HQ", "Main_Intel", "Main_Content", "Main_Audio", "Main_Status"}; }
|
||||
static array<string> Sidebars() { return {"Sidebar_Mission", "Sidebar_Forces", "Sidebar_HQ", "Sidebar_Intel", "Sidebar_Content", "Sidebar_Audio", "Sidebar_Status"}; }
|
||||
static int Group(int page)
|
||||
{
|
||||
array<int> groups = {0, 2, 6, 4, 1, 0, 3, 6, 0, 4, 5, 1, 1, 1, 2, 0, 4, 4, 4, 2, 4, 1, 2, 3, 0, 4, 1, 4, 1, 1, 4};
|
||||
if (page < 0 || page >= groups.Count()) return -1;
|
||||
return groups[page];
|
||||
}
|
||||
static int MainPage(int group)
|
||||
{
|
||||
array<int> pages = {ERR_AdminPage.MISSION, ERR_AdminPage.FORCES, ERR_AdminPage.HQ, ERR_AdminPage.INTEL, ERR_AdminPage.MODS, ERR_AdminPage.SPEECH, ERR_AdminPage.STATUS};
|
||||
if (group < 0 || group >= pages.Count()) return -1;
|
||||
return pages[group];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
// Widget presentation only. No settings writes, authorization or transport here.
|
||||
class ERR_AdminMenuView
|
||||
{
|
||||
// Preserve the requested UI scale until the fixed panel would leave the viewport.
|
||||
static void FitPanel(Widget root)
|
||||
{
|
||||
if (!root || !GetGame() || !GetGame().GetWorkspace()) return;
|
||||
ScaleWidget scale = ScaleWidget.Cast(root.FindAnyWidget("PanelScale"));
|
||||
if (!scale) return;
|
||||
float width, height; root.GetScreenSize(width, height);
|
||||
float panelWidth = GetGame().GetWorkspace().DPIScale(1220);
|
||||
float panelHeight = GetGame().GetWorkspace().DPIScale(760);
|
||||
if (width <= 0 || height <= 0 || panelWidth <= 0 || panelHeight <= 0) return;
|
||||
float fit = Math.Min(1, Math.Min(width * 0.96 / panelWidth, height * 0.96 / panelHeight));
|
||||
if (Math.AbsFloat(scale.GetScale() - fit) > 0.001) scale.SetScale(fit);
|
||||
}
|
||||
static bool ShowPage(Widget root, int page)
|
||||
{
|
||||
int group = ERR_AdminMenuPages.Group(page);
|
||||
if (!root || group < 0) return false;
|
||||
array<string> pages = ERR_AdminMenuPages.Pages();
|
||||
array<string> navigation = ERR_AdminMenuPages.Navigation();
|
||||
for (int i = 0; i < pages.Count(); i++)
|
||||
{
|
||||
root.FindAnyWidget(pages[i]).SetVisible(i == page);
|
||||
root.FindAnyWidget("Selected_" + navigation[i]).SetVisible(i == page);
|
||||
ERR_SelectionStyle.Choice(SCR_ButtonTextComponent.GetButtonText(navigation[i], root), i == page);
|
||||
}
|
||||
array<string> buttons = ERR_AdminMenuPages.MainButtons();
|
||||
array<string> sidebars = ERR_AdminMenuPages.Sidebars();
|
||||
for (int section = 0; section < buttons.Count(); section++)
|
||||
{
|
||||
root.FindAnyWidget(sidebars[section]).SetVisible(section == group);
|
||||
root.FindAnyWidget("Selected_" + buttons[section]).SetVisible(section == group);
|
||||
ERR_SelectionStyle.Choice(SCR_ButtonTextComponent.GetButtonText(buttons[section], root), section == group);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -4,18 +4,70 @@ class ERR_CommandArguments
|
||||
{
|
||||
static string CoreSetting(string section, string option)
|
||||
{
|
||||
if (section == "mission" && option == "telemetry") return "telemetryEnabled";
|
||||
if (section == "mission" && option == "doctrine") return "aiDoctrine";
|
||||
if (section == "mission" && option == "aishadow") return "aiShadow";
|
||||
if (section == "mission" && option == "sarkapoints") return "sarkaAngerPerCar";
|
||||
if (section == "mission" && option == "motorbikes") return "motorbikeCount";
|
||||
if (section == "mission" && option == "planealtitude") return "planeAltitudeMetres";
|
||||
if (section == "mission" && option == "planereplacement") return "planeReplacementSeconds";
|
||||
if (section == "mission" && option == "planesector") return "planeSectorSeconds";
|
||||
if (section == "mission" && option == "drones") return "droneReconCount";
|
||||
if (section == "mission" && option == "dronecooldown") return "droneCooldownSeconds";
|
||||
if (section == "mission" && option == "plane") return "planeReconEnabled";
|
||||
if (section == "mission" && option == "camps") return "fieldCampCount";
|
||||
if (section == "mission" && option == "crashloot") return "crashLootMode";
|
||||
if (section == "mission" && option == "crashsites") return "crashSiteCount";
|
||||
if (section == "mission" && option == "crashhelis") return "crashHelicopterChancePercent";
|
||||
if (section == "mission" && option == "crashrifles") return "crashRifleCount";
|
||||
if (section == "mission" && option == "crashammo") return "crashMagazineCount";
|
||||
if (section == "mission" && option == "crashaa") return "crashAntiAirChancePercent";
|
||||
if (section == "mission" && option == "crashrockets") return "crashRocketChancePercent";
|
||||
if (section == "mission" && option == "shoreboats") return "shoreBoatCount";
|
||||
if (section == "mission" && option == "boatchance") return "shoreBoatChancePercent";
|
||||
if (section == "mission" && option == "boatcrossings") return "shoreBoatCrossingChancePercent";
|
||||
if (section == "mission" && option == "hospitals") return "fieldHospitalCount";
|
||||
if (section == "mission" && option == "startweapons") return "startWeaponCount";
|
||||
if (section == "mission" && option == "startammo") return "startMagazineCount";
|
||||
if (section == "mission" && option == "objectevery") return "objectGuardEvery";
|
||||
if (section == "mission" && option == "objecthq") return "objectGuardHQMode";
|
||||
if (section == "mission" && option == "objectguards") return "objectGuardCount";
|
||||
if (section == "mission" && option == "hunterdistance") return "hunterSpawnDistanceMetres";
|
||||
if (section == "mission" && option == "roadblocks") return "roadblockCount";
|
||||
if (section == "mission" && option == "largeroadblocks") return "largeRoadblockCount";
|
||||
if (section == "mission" && option == "mainroadwidth") return "mainRoadWidthMetres";
|
||||
if (section == "mission" && option == "garagechance") return "garageChancePercent";
|
||||
if (section == "mission" && option == "garageradius") return "garageRadiusMetres";
|
||||
if (section == "mission" && option == "garageinterval") return "garageSpawnIntervalSeconds";
|
||||
if (section == "mission" && option == "garagevehicles") return "garageVehicleCount";
|
||||
if (section == "mission" && option == "traffic") return "civilianTrafficCount";
|
||||
if (section == "mission" && option == "start") return "startMode";
|
||||
if (section == "mission" && option == "selfepi") return "selfEpinephrine";
|
||||
if (section == "mission" && option == "keepweapon") return "keepWeaponOnUnconscious";
|
||||
if (section == "mission" && option == "restartempty") return "restartOnEmpty";
|
||||
if (section == "mission" && option == "emptyhours") return "emptyRestartHours";
|
||||
if (section == "mission" && option == "radio") return "radioDefenceSeconds";
|
||||
if (section == "intel" && option == "sites") return "showAllSites";
|
||||
if (section == "intel" && option == "radiosites") return "radioIntelSiteCount";
|
||||
if (section == "intel" && option == "reveal") return "intelRevealChancePercent";
|
||||
return "";
|
||||
}
|
||||
static bool Selection(string key, string text, out int value)
|
||||
{
|
||||
if (key == "crashLootMode" && text == "random") { value = 1; return true; }
|
||||
if (key == "crashLootMode" && text == "custom") { value = 0; return true; }
|
||||
if (key == "aiDoctrine" && text == "current") { value = 0; return true; }
|
||||
if (key == "aiDoctrine" && text == "coordinated") { value = 1; return true; }
|
||||
if ((key == "aiShadow" || key == "restartOnEmpty" || key == "showAllSites") && text == "off") { value = 0; return true; }
|
||||
if ((key == "aiShadow" || key == "restartOnEmpty" || key == "showAllSites") && text == "on") { value = 1; return true; }
|
||||
if (key == "startMode" && text == "prison") { value = 0; return true; }
|
||||
if (key == "startMode" && text == "crash") { value = 1; return true; }
|
||||
if (text == "random" && ERR_Settings.Randomizable(key)) { value = -1; return true; }
|
||||
if ((text == "random-day" || text == "random day") && key == "startTimeMinutes") { value = -2; return true; }
|
||||
if (key == "startTimeMinutes") return ERR_TimeService.ParseClock(text, value);
|
||||
if (key == "startDateCode") return ERR_EnvironmentService.ParseDate(text, value);
|
||||
if (key == "weatherProfile") return ERR_EnvironmentService.ParseProfile(text, value);
|
||||
if (key == "viewDistanceMetres" && text == "default") { value = 0; return true; }
|
||||
return Integer(text, value);
|
||||
}
|
||||
static bool Integer(string text, out int value)
|
||||
@@ -81,6 +133,37 @@ class ERR_AdminService
|
||||
}
|
||||
return handled;
|
||||
}
|
||||
protected bool ViewDistanceCommand(ERR_GameMode mode, array<string> parts, array<string> replies)
|
||||
{
|
||||
if (parts.Count() < 2 || parts[0] != "mission" || parts[1] != "viewdistance") return false;
|
||||
if (parts.Count() == 3 && parts[2] == "show")
|
||||
{
|
||||
replies.Insert("Mission view distance: " + ERR_AdminMenuFields.Display("viewDistanceMetres", mode.Settings().Get("viewDistanceMetres").ToString()) + ". Server limit: " + GetGame().GetViewDistanceServerLimit().ToString() + " m. Platform limits also apply.");
|
||||
return true;
|
||||
}
|
||||
int value;
|
||||
if (parts.Count() == 4 && parts[2] == "set" && ERR_CommandArguments.Selection("viewDistanceMetres", parts[3], value) && mode.Settings().Set("viewDistanceMetres", value))
|
||||
{
|
||||
mode.PublishViewDistance();
|
||||
replies.Insert("Saved mission view distance: " + ERR_AdminMenuFields.Display("viewDistanceMetres", value.ToString()) + ". Applies now when players close menus, and to later joins; server/platform limits apply.");
|
||||
}
|
||||
else replies.Insert("Use /esc mission viewdistance show|set 500..10000|default. Invalid value or save failure leaves the previous setting unchanged.");
|
||||
return true;
|
||||
}
|
||||
protected bool StartCommand(ERR_GameMode mode, array<string> parts, array<string> replies)
|
||||
{
|
||||
if (parts.Count() < 2 || parts[0] != "mission" || parts[1] != "start") return false;
|
||||
if (parts.Count() == 3 && parts[2] == "show")
|
||||
{
|
||||
replies.Insert(ERR_AdminMenuFields.StartSummary(mode.Settings().Get("startMode"), mode.StartMode()));
|
||||
return true;
|
||||
}
|
||||
int value;
|
||||
if (parts.Count() == 4 && parts[2] == "set" && ERR_CommandArguments.Selection("startMode", parts[3], value) && mode.Settings().Set("startMode", value))
|
||||
replies.Insert(ERR_AdminMenuFields.StartSummary(value, mode.StartMode()) + ". Saved for next round; current start and players are unchanged.");
|
||||
else replies.Insert("Use /esc mission start show|set prison|crash (or 0|1). No random start. Invalid value or save failure leaves the previous selection unchanged.");
|
||||
return true;
|
||||
}
|
||||
protected bool TimeCommand(ERR_GameMode mode, array<string> parts, array<string> replies)
|
||||
{
|
||||
if (parts.Count() < 2 || parts[0] != "mission" || (parts[1] != "time" && parts[1] != "speed")) return false;
|
||||
@@ -107,21 +190,75 @@ class ERR_AdminService
|
||||
}
|
||||
protected string SettingHelp(string key)
|
||||
{
|
||||
if (key == "aiDoctrine") return "Current (0) or Coordinated (1); default Current. Saved for next round.";
|
||||
if (key == "aiShadow") return "On (1) records coordinated proposals without issuing their orders; Off (0) uses the selected doctrine. Saved for next round.";
|
||||
if (key == "startWeaponCount") return "0..12 pistols in the shared prison/crash supply backpack; default 5. Applies next round.";
|
||||
if (key == "startMagazineCount") return "0..48 spare pistol magazines in the shared prison/crash supply backpack; default 8. Applies next round.";
|
||||
if (key == "sarkaAngerPerCar") return "1-30 anger points per car; 0 disables new waves. Default 3. Whole thresholds only: 5 means one car at 5 anger, two at 10. Four special-forces crew per car. Applies to new waves; existing teams remain.";
|
||||
if (key == "motorbikeCount") return "0-6; default 2; 0 disabled. Optional WCS Motorbikes, two response specialists per bike. Applies next round; losses are not replaced.";
|
||||
if (key == "droneReconCount") return "0-3; default 0. Applies next round.";
|
||||
if (key == "droneCooldownSeconds") return "60-1800; default 300. Applies next round.";
|
||||
if (key == "planeAltitudeMetres") return "150-500; default 250. Applies next round.";
|
||||
if (key == "planeReplacementSeconds") return "30-1800; default 60. Applies next round.";
|
||||
if (key == "planeSectorSeconds") return "30-600; default 120. Applies next round.";
|
||||
if (key == "planeReconEnabled") return "0 disabled, 1 enabled if Cessna 172 and Propeller Flight Core are loaded. Applies next round. A clear airport departure area is required.";
|
||||
if (key == "fieldCampCount") return "1..16 field camps; default 12. Applies next round. Counts are requested: unsafe optional sites may be omitted.";
|
||||
if (key == "crashLootMode") return "Random (1, default): each crate rolls independently from poor pistol supplies to rich scoped-rifle/launcher loot. Custom (0): use configured rifle, ammo and launcher values. Next round.";
|
||||
if (key == "crashSiteCount") return "0..12; default 3. 0 disables sites. Finite wrecks and loot; no refills. Applies next round.";
|
||||
if (key == "crashHelicopterChancePercent") return "0..100; default 50. 0 = armored wrecks; 100 = helicopters. Applies next round.";
|
||||
if (key == "crashRifleCount") return "1..6; default 3. Random M21 / SVD rifles with fitted scopes. Custom loot mode only; next round.";
|
||||
if (key == "crashMagazineCount") return "1..8; default 4. Matching ammunition for each selected rifle. Custom loot mode only; next round.";
|
||||
if (key == "crashAntiAirChancePercent") return "0..100; default 25. One loaded Stinger, if installed and enabled. Custom loot mode only; next round.";
|
||||
if (key == "crashRocketChancePercent") return "0..100; default 50. One scoped RPG-7 with two matching rockets. Custom loot mode only; next round.";
|
||||
if (key == "shoreBoatCount") return "0..16; default 6. Maximum for the round. Used or destroyed boats are not replaced. Requires loaded Motorboat. Next round.";
|
||||
if (key == "shoreBoatChancePercent") return "0..100; default 20. Chance per surveyed shore candidate; default 20%. Requires loaded Motorboat. Next round.";
|
||||
if (key == "shoreBoatCrossingChancePercent") return "0..100; default 70. Default 70%; never lower than the ordinary coast chance. Requires loaded Motorboat. Next round.";
|
||||
if (key == "fieldHospitalCount") return "1..8 field hospitals; default 6. Applies next round. The required radio-clue hospital is retained.";
|
||||
if (key == "objectGuardEvery") return "Guard every 1st, 2nd, 3rd or 4th eligible site; default every third. Nearby buildings share one site. Next round.";
|
||||
if (key == "objectGuardHQMode") return "0: all hold their posts; 1: half hold, half respond to HQ; 2: all may respond to HQ. All report contacts. Next round.";
|
||||
if (key == "objectGuardCount") return "0..16 soldiers per factory/industrial, military or infrastructure site; default 8, zero off. Next round. HQ participation is configured separately; all report. Towers and chimneys use suitable platforms. Lost guards are not replaced.";
|
||||
if (key == "hunterSpawnDistanceMetres") return "Opening UAZ and troop-truck patrol placement. Applies next round.";
|
||||
if (key == "largeRoadblockCount") return "Set 0..30 main-road checkpoints (default 4; 0 disables large posts). Eight guards per post. Applies next round; unsuitable sites are skipped.";
|
||||
if (key == "mainRoadWidthMetres") return "Set main-road minimum width to 5..12 metres (default 6). Wider roads use large checkpoints; narrower roads use small ones. Applies next round.";
|
||||
if (key == "roadblockCount") return "Set 0..30 small-road checkpoints (default 4; 0 disables small posts). Four guards per post. Applies next round; unsuitable sites are skipped.";
|
||||
if (key == "telemetryEnabled") return "Default on. Sends mission results and player identities to the configured statistics service. Disabling clears unsent events immediately.";
|
||||
if (key == "restartOnEmpty") return "On (1) restarts after the last disconnect; Off (0) preserves the round for reconnects. Default On. Applies immediately; normal round endings still apply.";
|
||||
if (key == "emptyRestartHours") return "0 disables idle restarts; 1..168 real hours continuously empty. Default 5. Applies immediately, measured from mission start or the last disconnect. Connected players, including spectators, prevent idle restarts.";
|
||||
if (key == "garageChancePercent") return "Parking-site selection chance (0-100%). Default 70%; applies to suitable garages and residential yards. Next round.";
|
||||
if (key == "garageRadiusMetres") return "Spawn radius (250-1500 metres). Distance around players; default 1000 m. Next round.";
|
||||
if (key == "garageSpawnIntervalSeconds") return "Time between spawns (5-60 seconds). Default 5 s. Increase to spread out spawning. Next round.";
|
||||
if (key == "garageVehicleCount") return "0..24 unclaimed cars in garages and residential yards; default 16. Used vehicles stay. Optional Rally cars, farm tractors and bikes. Next round.";
|
||||
if (key == "civilianTrafficCount") return "Set 0..6 civilian cars (default 3). Zero disables new traffic. Unclaimed cars retire only far from all players and out of sight; player-entered cars are preserved.";
|
||||
if (key == "showAllSites") return "0 disabled (default), 1 enabled. Show prepared sites at their positions as question marks; recovered intel identifies their types. Applies immediately to everyone.";
|
||||
if (key == "selfEpinephrine") return "0 disabled (default), 1 enabled. Unconscious players can press Space to use a carried epinephrine injector. Applies immediately; consumes the item and keeps its normal medical effect.";
|
||||
if (key == "keepWeaponOnUnconscious") return "Set 1 to retain the held firearm during unconsciousness and equip it after recovery; 0 keeps normal dropping. Applies to the next knockout.";
|
||||
if (key == "antiAirChancePercent" || key == "antiPersonnelMineChancePercent")
|
||||
return "Set 0..100 percent or Random. Applies to newly equipped enemy soldiers; existing loadouts are not rerolled or replenished.";
|
||||
if (key == "radioIntelSiteCount") return "Sites required before radio intel: 0..20, default 5; 0 disables. Fully identify distinct non-radio sites; starting reveals do not count. Applies next round. Finding and using radios in person remains unrestricted.";
|
||||
if (key == "intelRevealChancePercent")
|
||||
return "Use /esc intel reveal set N (0-100) or random. This is the chance that a first document identifies its location exactly; otherwise it gives a rough location. A second document for that location always identifies it. Changes affect newly recovered documents immediately; known locations and spent documents remain unchanged.";
|
||||
return "Use /esc intel reveal set N (0-100) or random. This is the chance that a first document identifies its location exactly; otherwise it gives a rough location. A second document for that location always identifies it. With all sites shown, the first document always identifies the type. Changes affect newly recovered documents immediately; known locations and spent documents remain unchanged.";
|
||||
return "";
|
||||
}
|
||||
protected string RoadblockTiming(string key)
|
||||
{
|
||||
if (key == "shoreBoatCount" || key == "shoreBoatChancePercent" || key == "shoreBoatCrossingChancePercent" || key == "crashLootMode" || key == "crashSiteCount" || key == "crashHelicopterChancePercent" || key == "crashRifleCount" || key == "crashMagazineCount" || key == "crashAntiAirChancePercent" || key == "crashRocketChancePercent" || key == "radioIntelSiteCount" || key == "garageVehicleCount" || key == "garageChancePercent" || key == "garageRadiusMetres" || key == "garageSpawnIntervalSeconds") return " for next round";
|
||||
if (key == "aiDoctrine" || key == "aiShadow" || key == "droneReconCount" || key == "droneCooldownSeconds") return " for next round";
|
||||
if (key == "motorbikeCount" || key == "planeAltitudeMetres" || key == "planeReplacementSeconds" || key == "planeSectorSeconds" || key == "planeReconEnabled" || key == "fieldCampCount" || key == "fieldHospitalCount" || key == "startWeaponCount" || key == "startMagazineCount" || key == "objectGuardCount" || key == "objectGuardEvery" || key == "objectGuardHQMode" || key == "hunterSpawnDistanceMetres" || key == "roadblockCount" || key == "largeRoadblockCount" || key == "mainRoadWidthMetres") return " for next round";
|
||||
return "";
|
||||
}
|
||||
protected void SettingCommand(ERR_Settings settings, string key, array<string> parts, array<string> replies)
|
||||
{
|
||||
if (parts.Count() == 3 && parts[2] == "show") { replies.Insert(key + "=" + ERR_Settings.SelectionText(settings.Get(key))); return; }
|
||||
if (parts.Count() == 3 && parts[2] == "show")
|
||||
{
|
||||
string displayed = ERR_Settings.SelectionText(settings.Get(key));
|
||||
if (key == "aiDoctrine" || key == "aiShadow") displayed = ERR_AdminMenuFields.Display(key, settings.Get(key).ToString()) + " (next round)";
|
||||
replies.Insert(key + "=" + displayed); return;
|
||||
}
|
||||
string help = SettingHelp(key);
|
||||
if (parts.Count() == 3 && parts[2] == "help" && !help.IsEmpty()) { replies.Insert(help); return; }
|
||||
int value;
|
||||
if (parts.Count() == 4 && parts[2] == "set" && ERR_CommandArguments.Selection(key, parts[3], value) && settings.Set(key, value))
|
||||
replies.Insert(key + "=" + ERR_Settings.SelectionText(value) + " saved");
|
||||
replies.Insert(key + "=" + ERR_Settings.SelectionText(value) + " saved" + RoadblockTiming(key));
|
||||
else
|
||||
{
|
||||
string failure = "Invalid value or settings could not be saved; previous setting retained.";
|
||||
@@ -136,12 +273,19 @@ class ERR_AdminService
|
||||
if (topic == "time" || topic == "mission")
|
||||
{
|
||||
replies.Insert("/esc mission radio show|set N|random | mission abandon");
|
||||
replies.Insert("/esc mission selfepi show|set 0|1; allow unconscious self-injection with Space. Disabled by default.");
|
||||
replies.Insert("/esc mission restartempty show|set on|off | mission emptyhours show|set 0..168; applies now, zero hours disables idle restarts.");
|
||||
replies.Insert("/esc mission doctrine show|set current|coordinated | mission aishadow show|set off|on; next round only.");
|
||||
replies.Insert("/esc mission start show|set prison|crash: save the next round start; current round unchanged.");
|
||||
replies.Insert("/esc mission startweapons show|set 0..12 | mission startammo show|set 0..48; shared backpack, next round.");
|
||||
replies.Insert("/esc mission viewdistance show|set 500..10000|default; applies immediately after closing menus.");
|
||||
replies.Insert("/esc time show|set HH:MM|random|random-day | mission speed show|set N|random; saved changes apply next round.");
|
||||
replies.Insert("/esc mission date show|set YYYY-MM-DD|authored|random | mission weather show|set authored|clear|overcast|rain|random");
|
||||
return;
|
||||
}
|
||||
if (topic == "intel")
|
||||
{
|
||||
replies.Insert("/esc intel sites show|set on|off; show anonymous site positions and let intel identify their types.");
|
||||
replies.Insert("/esc intel reveal show|set N|help (0-100); affects newly recovered documents immediately.");
|
||||
replies.Insert("AI carrying and reroll commands require the Intel addon.");
|
||||
return;
|
||||
@@ -153,7 +297,8 @@ class ERR_AdminService
|
||||
}
|
||||
replies.Insert("/esc menu | status | settings | help mission|time|intel|hq|cheat|god|killais");
|
||||
replies.Insert("/esc mission abandon | mission radio show|set N|random | time show|set HH:MM|random|random-day");
|
||||
replies.Insert("/esc mission date|weather|speed show|set VALUE | intel reveal show|set N|help");
|
||||
replies.Insert("/esc mission date|weather|speed show|set VALUE | mission viewdistance show|set 500..10000|default | intel reveal show|set N|help");
|
||||
replies.Insert("/esc mission start show|set prison|crash; next round only.");
|
||||
replies.Insert("/esc spectate next|previous; optional systems add their own command topics.");
|
||||
}
|
||||
protected bool Unavailable(array<string> parts, array<string> replies)
|
||||
@@ -193,6 +338,8 @@ class ERR_AdminService
|
||||
foreach (string key : ERR_Settings.Keys())
|
||||
{
|
||||
string line = key + "=" + mode.Settings().Get(key).ToString();
|
||||
if (key == "startMode") line = ERR_AdminMenuFields.StartSummary(mode.Settings().Get(key), mode.StartMode());
|
||||
if (key == "aiDoctrine" || key == "aiShadow") line = key + "=" + ERR_AdminMenuFields.Display(key, mode.Settings().Get(key).ToString()) + " (next round)";
|
||||
if (key == "automaticConvoyEnabled") line += " (saved for next round; current startup unchanged)";
|
||||
replies.Insert(line);
|
||||
}
|
||||
@@ -219,13 +366,16 @@ class ERR_AdminService
|
||||
return;
|
||||
}
|
||||
if (ERR_EnvironmentCommands.Execute(mode.Settings(), mode.Environment(), parts, replies)) return;
|
||||
if (ViewDistanceCommand(mode, parts, replies)) return;
|
||||
if (TimeCommand(mode, parts, replies)) return;
|
||||
if (StartCommand(mode, parts, replies)) return;
|
||||
if (parts.Count() == 3 || parts.Count() == 4)
|
||||
{
|
||||
string key = Setting(parts[0], parts[1]);
|
||||
if (!key.IsEmpty())
|
||||
{
|
||||
SettingCommand(mode.Settings(), key, parts, replies);
|
||||
if (key == "showAllSites") mode.PublishSiteVisibility();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,10 @@ class ERR_AdminSession
|
||||
protected bool m_Stopped;
|
||||
protected bool m_Live;
|
||||
protected int m_Cursor;
|
||||
protected string m_ItemAddon;
|
||||
protected int m_ItemPage;
|
||||
protected int m_ItemFilter;
|
||||
protected string m_ItemQuery;
|
||||
protected float m_LastRequest = -1000;
|
||||
protected float m_LastSnapshot = -1000;
|
||||
protected float m_LastLog = -1000;
|
||||
@@ -66,11 +70,86 @@ class ERR_AdminSession
|
||||
return;
|
||||
}
|
||||
array<string> replies = {};
|
||||
if (!JournalCommand(arguments, replies) && !m_Tools.Execute(m_Mode, m_Controller, arguments, replies))
|
||||
if (!StartBackpackCommand(arguments, replies) && !ItemCatalogCommand(arguments, replies) && !VehicleCatalogCommand(arguments, replies) && !JournalCommand(arguments, replies) && !m_Tools.Execute(m_Mode, m_Controller, arguments, replies))
|
||||
m_Mode.Admin().Execute(m_Mode, true, arguments, replies);
|
||||
foreach (string reply : replies) m_Controller.ERR_AdminDeliverReply(m_Token, reply, menu);
|
||||
if (menu) Snapshot(true);
|
||||
}
|
||||
protected bool StartBackpackCommand(string arguments, array<string> replies)
|
||||
{
|
||||
array<string> parts = {}; arguments.Split(" ", parts, true);
|
||||
if (parts.IsEmpty() || parts[0] != "starter") return false;
|
||||
ERR_StartBackpack supplies = GetGame().ERR_StartingSupplies();
|
||||
bool saved;
|
||||
if (parts.Count() == 3 && parts[1] == "mode" && (parts[2] == "default" || parts[2] == "custom")) saved = supplies.SetMode(parts[2] == "custom");
|
||||
else if ((parts.Count() == 3 || parts.Count() == 4) && parts[1] == "remove")
|
||||
{
|
||||
string removeDestination = "backpack"; if (parts.Count() == 4) removeDestination = parts[3];
|
||||
saved = supplies.Remove(parts[2], removeDestination);
|
||||
}
|
||||
else if ((parts.Count() == 5 || parts.Count() == 6) && parts[1] == "set")
|
||||
{
|
||||
ERR_ServerItemCatalog catalog = GetGame().ERR_ServerItems(); catalog.Start(m_Mode);
|
||||
int count;
|
||||
if (catalog.Contains(parts[2], parts[3]) && ERR_CommandArguments.Integer(parts[4], count))
|
||||
{
|
||||
ERR_ServerItem item = catalog.Item(parts[3]);
|
||||
string destination = "backpack"; if (parts.Count() == 6) destination = parts[5];
|
||||
if (item) saved = supplies.Set(item.Prefab, count, destination);
|
||||
}
|
||||
}
|
||||
if (saved) replies.Insert("Starting supplies saved for next round. Custom contents are used only when Custom is selected.");
|
||||
else replies.Insert("Not saved. Choose a catalog item and quantity 0-48; maximum 32 entries and 64 items across both destinations. Check configuration if read-only.");
|
||||
return true;
|
||||
}
|
||||
// Per-session paging: another administrator cannot change this menu's selection.
|
||||
protected bool ItemCatalogCommand(string arguments, array<string> replies)
|
||||
{
|
||||
array<string> parts = {}; arguments.Split(" ", parts, true);
|
||||
if (parts.IsEmpty() || parts[0] != "items") return false;
|
||||
int page;
|
||||
ERR_ServerItemCatalog catalog = GetGame().ERR_ServerItems(); catalog.Start(m_Mode);
|
||||
if (parts.Count() == 4 && parts[1] == "mod" && catalog.Find(parts[2]) && (parts[3] == "0" || parts[3] == "1"))
|
||||
{
|
||||
if (GetGame().ERR_ItemSelections().SetSwitch(parts[2], "mod", parts[3] == "1")) replies.Insert("Mod selection saved for future random equipment and civilian vehicles. Individual choices are remembered.");
|
||||
else replies.Insert("Could not save mod selection. Previous choices retained.");
|
||||
return true;
|
||||
}
|
||||
if (parts.Count() >= 5 && parts[1] == "browse" && catalog.Find(parts[2]))
|
||||
{
|
||||
int filter;
|
||||
if (!ERR_CommandArguments.Integer(parts[3], page) || page < 0 || page > 2730 || !ERR_CommandArguments.Integer(parts[4], filter) || filter < 0 || filter > 2) return true;
|
||||
string query;
|
||||
for (int q = 5; q < parts.Count(); q++) { if (!query.IsEmpty()) query += " "; query += parts[q]; }
|
||||
if (query.Length() > 64) return true;
|
||||
m_ItemAddon = parts[2]; m_ItemPage = page; m_ItemFilter = filter; m_ItemQuery = query;
|
||||
return true;
|
||||
}
|
||||
if (parts.Count() == 4 && parts[1] == "preset")
|
||||
{
|
||||
if (GetGame().ERR_ItemSelections().Preset(parts[2], parts[3])) replies.Insert("Preset " + parts[2] + " completed: " + parts[3] + ". Future random equipment uses the saved selection.");
|
||||
else replies.Insert("Preset not saved/applied. Use an existing name to load/delete, or save a name of 1-24 letters, digits, _ or -. Maximum 16 presets. Previous selection retained.");
|
||||
return true;
|
||||
}
|
||||
if (parts.Count() == 5 && parts[1] == "set" && catalog.Find(parts[2]) && (parts[3] == "*" || catalog.Contains(parts[2], parts[3])) && (parts[4] == "0" || parts[4] == "1"))
|
||||
{
|
||||
if (GetGame().ERR_ItemSelections().Set(parts[2], parts[3], parts[4] == "1")) replies.Insert("Item selection saved. Applies to future random equipment; existing inventories remain unchanged.");
|
||||
else replies.Insert("Could not save item selection. Previous selection retained.");
|
||||
return true;
|
||||
}
|
||||
if (parts.Count() == 5 && parts[1] == "weight" && catalog.Find(parts[2]) && (parts[3] == "*" || catalog.Contains(parts[2], parts[3])))
|
||||
{
|
||||
int weight;
|
||||
if (!ERR_CommandArguments.Integer(parts[4], weight) || weight < 0 || weight > 100) replies.Insert("Relative weight must be 0-100; 1 is normal, 0 excludes from extra equipment.");
|
||||
else if (GetGame().ERR_ItemSelections().SetWeight(parts[2], parts[3], weight)) replies.Insert("Probability weight saved. Applies to future extra-equipment rolls; disabled items remain disabled.");
|
||||
else replies.Insert("Could not save weight. Previous settings retained.");
|
||||
return true;
|
||||
}
|
||||
if (parts.Count() != 4 || parts[1] != "list" || !catalog.Find(parts[2]) || !ERR_CommandArguments.Integer(parts[3], page) || page < 0 || page > 2730)
|
||||
{ replies.Insert("Choose a loaded server mod in Items, then press Show items."); return true; }
|
||||
m_ItemAddon = parts[2]; m_ItemPage = page; m_ItemFilter = 0; m_ItemQuery = "";
|
||||
return true;
|
||||
}
|
||||
protected bool JournalCommand(string arguments, array<string> replies)
|
||||
{
|
||||
if (arguments != "hq log" && arguments != "hq log show" && arguments != "hq log next" && arguments != "hq log set on" && arguments != "hq log set off") return false;
|
||||
@@ -98,8 +177,15 @@ class ERR_AdminSession
|
||||
if (key == "convoyCount" || key == "startTimeMinutes" || key == "timeMultiplier" || key == "startDateCode" || key == "weatherProfile") continue;
|
||||
if (m_Mode.Settings().Get(key) == -1) values.Insert("resolved_" + key + "=" + m_Mode.Settings().Runtime(key).ToString());
|
||||
}
|
||||
if (m_Mode.Discovery()) values.Insert("radioIntelProgress=" + m_Mode.Discovery().RadioIntelStatus());
|
||||
m_Mode.Admin().Capabilities(values);
|
||||
ERR_RecommendedAddons.Snapshot(m_Mode, values);
|
||||
GetGame().ERR_ServerItems().Snapshot(m_Mode, values, m_ItemAddon, m_ItemPage, m_ItemFilter, m_ItemQuery);
|
||||
GetGame().ERR_ServerVehicles().Snapshot(values, m_VehicleAddon, m_VehiclePage, m_VehicleFilter, m_VehicleQuery, m_VehicleRole);
|
||||
GetGame().ERR_StartingSupplies().Snapshot(values);
|
||||
m_Mode.Admin().Execute(m_Mode, true, "status", status);
|
||||
if (m_Mode.CrashSites()) status.Insert(m_Mode.CrashSites().Status());
|
||||
if (m_Mode.ShoreBoats()) status.Insert(m_Mode.ShoreBoats().Status());
|
||||
m_Controller.ERR_AdminDeliverSnapshot(m_Token, true, values, status);
|
||||
}
|
||||
void Log(int cursor, bool latest)
|
||||
@@ -144,4 +230,38 @@ class ERR_AdminSession
|
||||
m_Tools.Stop();
|
||||
if (m_Mode) m_Mode.OnStop().Remove(Stop);
|
||||
}
|
||||
|
||||
protected string m_VehicleAddon;
|
||||
protected int m_VehiclePage;
|
||||
protected int m_VehicleFilter;
|
||||
protected int m_VehicleRole;
|
||||
protected string m_VehicleQuery;
|
||||
protected bool VehicleCatalogCommand(string arguments, array<string> replies)
|
||||
{
|
||||
array<string> parts = {}; arguments.Split(" ", parts, true);
|
||||
if (parts.IsEmpty() || parts[0] != "vehicles") return false;
|
||||
ERR_ServerVehicleCatalog catalog = GetGame().ERR_ServerVehicles(); catalog.Start();
|
||||
if (parts.Count() == 6 && parts[1] == "role" && catalog.Find(parts[2]))
|
||||
{
|
||||
int role;
|
||||
if (!ERR_CommandArguments.Integer(parts[3], role) || role < 0 || role > 5 || (parts[5] != "0" && parts[5] != "1")) return true;
|
||||
if (parts[4] != "*" && (!catalog.Contains(parts[2], parts[4]) || (parts[5] == "1" && !catalog.Capable(parts[4], role)))) return true;
|
||||
if (GetGame().ERR_ItemSelections().SetVehicleRole(parts[2], parts[4], role, parts[5] == "1")) replies.Insert("Vehicle assignment saved for future spawns. Existing vehicles stay.");
|
||||
else replies.Insert("Could not save assignment. Previous choices retained.");
|
||||
return true;
|
||||
}
|
||||
if (parts.Count() >= 6 && parts[1] == "browse" && catalog.Find(parts[2]))
|
||||
{
|
||||
int page, filter, role;
|
||||
if (!ERR_CommandArguments.Integer(parts[3], page) || page < 0 || page > 2730 || !ERR_CommandArguments.Integer(parts[4], filter) || filter < 0 || filter > 2 || !ERR_CommandArguments.Integer(parts[5], role) || role < 0 || role > 5) return true;
|
||||
string query;
|
||||
for (int q = 6; q < parts.Count(); q++) { if (!query.IsEmpty()) query += " "; query += parts[q]; }
|
||||
if (query.Length() > 64) return true;
|
||||
m_VehicleAddon = parts[2]; m_VehiclePage = page; m_VehicleFilter = filter; m_VehicleQuery = query; m_VehicleRole = role;
|
||||
replies.Insert("Vehicle list updated. Assignments apply to future spawns.");
|
||||
return true;
|
||||
}
|
||||
replies.Insert("Choose a mod in Content / Vehicles, then choose a use, then assign vehicles.");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,24 +3,58 @@
|
||||
class ERR_AirfieldPlacement
|
||||
{
|
||||
protected bool m_NearAirport;
|
||||
// Optional terrain/adapter anchors; positions are centres, not trusted pads.
|
||||
// Numbered names allow several airports without matching unrelated map labels.
|
||||
static void AuthoredAirports(array<vector> positions)
|
||||
{
|
||||
if (!GetGame().GetWorldEntity()) return;
|
||||
vector low, high;
|
||||
GetGame().GetWorldEntity().GetWorldBounds(low, high);
|
||||
for (int index = -1; index < 16; index++)
|
||||
{
|
||||
string name = "ERR_ReconAirport";
|
||||
if (index >= 0) name = "ERR_Airfield_" + index.ToString();
|
||||
IEntity marker = GetGame().GetWorld().FindEntityByName(name);
|
||||
if (!marker) continue;
|
||||
vector position = ERR_Actors.Ground(marker.GetOrigin());
|
||||
if (position[0] < low[0] || position[0] > high[0] || position[2] < low[2] || position[2] > high[2]) continue;
|
||||
if (ChimeraWorldUtils.TryGetWaterSurfaceSimple(GetGame().GetWorld(), position)) continue;
|
||||
positions.Insert(position);
|
||||
}
|
||||
}
|
||||
static bool AirportPrefab(string path)
|
||||
{
|
||||
path.ToLower();
|
||||
return path.Contains("prefabs/structures/") && (path.Contains("/runway") || path.Contains("/lightrunway_") || path.Contains("/aircontroltower") || path.Contains("/airportcontroltower") || path.Contains("/controltower"));
|
||||
}
|
||||
protected bool Airport(IEntity entity)
|
||||
{
|
||||
if (!entity || !entity.GetPrefabData()) return true;
|
||||
string path = entity.GetPrefabData().GetPrefabName(); path.ToLower();
|
||||
if (path.Contains("/runway") || path.Contains("/lightrunway_") || path.Contains("/aircontroltower") || path.Contains("/airportcontroltower") || path.Contains("/locations/eden/airport.et")) m_NearAirport = true;
|
||||
if (AirportPrefab(path) || path.Contains("/locations/eden/airport.et")) m_NearAirport = true;
|
||||
return !m_NearAirport;
|
||||
}
|
||||
static bool Clear(vector position)
|
||||
{
|
||||
if (!Replication.IsServer() || position[1] < 2 || ChimeraWorldUtils.TryGetWaterSurfaceSimple(GetGame().GetWorld(), position)) return false;
|
||||
if (!Replication.IsServer() || !ERR_Actors.DryGround(position)) return false;
|
||||
ERR_AirfieldPlacement survey = new ERR_AirfieldPlacement();
|
||||
GetGame().GetWorld().QueryEntitiesBySphere(position, 600, survey.Airport, null, EQueryEntitiesFlags.ALL);
|
||||
array<vector> authored = {};
|
||||
AuthoredAirports(authored);
|
||||
foreach (vector centre : authored)
|
||||
if (vector.DistanceXZ(centre, position) <= 600) survey.m_NearAirport = true;
|
||||
if (!survey.m_NearAirport) GetGame().GetWorld().QueryEntitiesBySphere(position, 600, survey.Airport, null, EQueryEntitiesFlags.ALL);
|
||||
if (!survey.m_NearAirport) return false;
|
||||
return ClearGround(position);
|
||||
}
|
||||
// Same aircraft footprint/vertical sweep, without requiring airport scenery.
|
||||
static bool ClearGround(vector position)
|
||||
{
|
||||
if (!Replication.IsServer() || !ERR_Actors.DryGround(position)) return false;
|
||||
for (int edge = 0; edge < 4; edge++)
|
||||
{
|
||||
float side = edge * Math.PI / 2;
|
||||
vector sample = position + Vector(Math.Cos(side) * 14, 0, Math.Sin(side) * 14);
|
||||
if (Math.AbsFloat(SCR_TerrainHelper.GetTerrainY(sample) - position[1]) > 0.8) return false;
|
||||
if (!ERR_Actors.DryGround(sample) || Math.AbsFloat(SCR_TerrainHelper.GetTerrainY(sample) - position[1]) > 0.8) return false;
|
||||
}
|
||||
if (!SCR_WorldTools.TraceCilinderUtil(position + "0 2.5 0", 40, 4, TraceFlags.ENTS)) return false;
|
||||
TraceBox clearance = new TraceBox();
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
// Generated by tools/build_bundled_speech.py from docs/bundled-speech.json.
|
||||
// Only these packaged graphs are accepted as local speech sources.
|
||||
class ERR_BakedSpeech
|
||||
{
|
||||
static array<string> Sources() { return {"baked:welcome", "baked:soldier_1001", "baked:soldier_1002", "baked:soldier_1003", "baked:soldier_1004", "baked:soldier_1005", "baked:soldier_1100", "baked:soldier_1101", "baked:soldier_1102", "baked:soldier_1103", "baked:soldier_1104", "baked:soldier_1105", "baked:soldier_1200", "baked:soldier_1201", "baked:soldier_1202", "baked:soldier_1203", "baked:soldier_1204", "baked:soldier_1205", "baked:soldier_clear", "baked:soldier_moving", "baked:soldier_covering", "baked:soldier_reloading", "baked:soldier_under_fire", "baked:soldier_no_ammo", "baked:soldier_engaging", "baked:soldier_target_down", "baked:soldier_target_lost", "baked:radio_01", "baked:radio_02", "baked:radio_03", "baked:radio_04", "baked:radio_05", "baked:radio_06", "baked:radio_07", "baked:radio_08", "baked:radio_09", "baked:radio_10"}; }
|
||||
static string Welcome() { return "baked:welcome"; }
|
||||
static string WelcomeText() { return "Welcome to Escape Reintroduced. Work together, stay alert, and find your way off the island. Good luck."; }
|
||||
static ResourceName Graph(string source)
|
||||
{
|
||||
switch (source)
|
||||
{
|
||||
case "baked:welcome": return "{3B55F836F9B6D20C}Sounds/ERR/Speech/welcome.acp";
|
||||
case "baked:soldier_1001": return "{3DF753B659A184FB}Sounds/ERR/Speech/soldier_1001.acp";
|
||||
case "baked:soldier_1002": return "{B1B78A9E50D41FD5}Sounds/ERR/Speech/soldier_1002.acp";
|
||||
case "baked:soldier_1003": return "{FEE47650B627484E}Sounds/ERR/Speech/soldier_1003.acp";
|
||||
case "baked:soldier_1004": return "{2FA4DC16F80F04FE}Sounds/ERR/Speech/soldier_1004.acp";
|
||||
case "baked:soldier_1005": return "{5F3C22AA392DAE36}Sounds/ERR/Speech/soldier_1005.acp";
|
||||
case "baked:soldier_1100": return "{AD2DC2C52E226AA7}Sounds/ERR/Speech/soldier_1100.acp";
|
||||
case "baked:soldier_1101": return "{AE2E3AA19209018D}Sounds/ERR/Speech/soldier_1101.acp";
|
||||
case "baked:soldier_1102": return "{74617A1D01426F95}Sounds/ERR/Speech/soldier_1102.acp";
|
||||
case "baked:soldier_1103": return "{6B5037B1DEEB3FCB}Sounds/ERR/Speech/soldier_1103.acp";
|
||||
case "baked:soldier_1104": return "{0C8AE411366B20EB}Sounds/ERR/Speech/soldier_1104.acp";
|
||||
case "baked:soldier_1105": return "{3E1B0B6406E16734}Sounds/ERR/Speech/soldier_1105.acp";
|
||||
case "baked:soldier_1200": return "{86FD331E3D1EB134}Sounds/ERR/Speech/soldier_1200.acp";
|
||||
case "baked:soldier_1201": return "{A840837CC23E4A32}Sounds/ERR/Speech/soldier_1201.acp";
|
||||
case "baked:soldier_1202": return "{3AF9ED4087FE442C}Sounds/ERR/Speech/soldier_1202.acp";
|
||||
case "baked:soldier_1203": return "{97397F4FD27C4371}Sounds/ERR/Speech/soldier_1203.acp";
|
||||
case "baked:soldier_1204": return "{EB394FAC323A0CA0}Sounds/ERR/Speech/soldier_1204.acp";
|
||||
case "baked:soldier_1205": return "{69750949590AECF1}Sounds/ERR/Speech/soldier_1205.acp";
|
||||
case "baked:soldier_clear": return "{ECF3257A4BFF2F53}Sounds/ERR/Speech/soldier_clear.acp";
|
||||
case "baked:soldier_moving": return "{BAB1942B5DE85A52}Sounds/ERR/Speech/soldier_moving.acp";
|
||||
case "baked:soldier_covering": return "{78DCA194DA126513}Sounds/ERR/Speech/soldier_covering.acp";
|
||||
case "baked:soldier_reloading": return "{C51337F1692C21A7}Sounds/ERR/Speech/soldier_reloading.acp";
|
||||
case "baked:soldier_under_fire": return "{8A05B1A08D5F23CC}Sounds/ERR/Speech/soldier_under_fire.acp";
|
||||
case "baked:soldier_no_ammo": return "{D363E6B37331E416}Sounds/ERR/Speech/soldier_no_ammo.acp";
|
||||
case "baked:soldier_engaging": return "{A700E14970CCA3E2}Sounds/ERR/Speech/soldier_engaging.acp";
|
||||
case "baked:soldier_target_down": return "{388F2800E5683289}Sounds/ERR/Speech/soldier_target_down.acp";
|
||||
case "baked:soldier_target_lost": return "{DA42EA7B286B20BD}Sounds/ERR/Speech/soldier_target_lost.acp";
|
||||
case "baked:radio_01": return "{29F6A1A8A691488D}Sounds/ERR/Speech/radio_01.acp";
|
||||
case "baked:radio_02": return "{0B1147F331AC4EDA}Sounds/ERR/Speech/radio_02.acp";
|
||||
case "baked:radio_03": return "{BC0C902F48008D93}Sounds/ERR/Speech/radio_03.acp";
|
||||
case "baked:radio_04": return "{E9EB0183C1ED1906}Sounds/ERR/Speech/radio_04.acp";
|
||||
case "baked:radio_05": return "{4995CB641455140B}Sounds/ERR/Speech/radio_05.acp";
|
||||
case "baked:radio_06": return "{E4473DD1140515F9}Sounds/ERR/Speech/radio_06.acp";
|
||||
case "baked:radio_07": return "{3D3AA49576443D5D}Sounds/ERR/Speech/radio_07.acp";
|
||||
case "baked:radio_08": return "{207C5D973D7EBE55}Sounds/ERR/Speech/radio_08.acp";
|
||||
case "baked:radio_09": return "{DCAF67D5B901D45F}Sounds/ERR/Speech/radio_09.acp";
|
||||
case "baked:radio_10": return "{8CC6CCBA6BDF1568}Sounds/ERR/Speech/radio_10.acp";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
static string Soldier(int kind)
|
||||
{
|
||||
switch (kind)
|
||||
{
|
||||
case 1001: return "baked:soldier_1001";
|
||||
case 1002: return "baked:soldier_1002";
|
||||
case 1003: return "baked:soldier_1003";
|
||||
case 1004: return "baked:soldier_1004";
|
||||
case 1005: return "baked:soldier_1005";
|
||||
case 1100: return "baked:soldier_1100";
|
||||
case 1101: return "baked:soldier_1101";
|
||||
case 1102: return "baked:soldier_1102";
|
||||
case 1103: return "baked:soldier_1103";
|
||||
case 1104: return "baked:soldier_1104";
|
||||
case 1105: return "baked:soldier_1105";
|
||||
case 1200: return "baked:soldier_1200";
|
||||
case 1201: return "baked:soldier_1201";
|
||||
case 1202: return "baked:soldier_1202";
|
||||
case 1203: return "baked:soldier_1203";
|
||||
case 1204: return "baked:soldier_1204";
|
||||
case 1205: return "baked:soldier_1205";
|
||||
case ECommunicationType.REPORT_CLEAR: return "baked:soldier_clear";
|
||||
case ECommunicationType.REPORT_MOVING: return "baked:soldier_moving";
|
||||
case ECommunicationType.REPORT_COVERING: return "baked:soldier_covering";
|
||||
case ECommunicationType.REPORT_RELOADING: return "baked:soldier_reloading";
|
||||
case ECommunicationType.REPORT_UNDER_FIRE: return "baked:soldier_under_fire";
|
||||
case ECommunicationType.REPORT_NO_AMMO: return "baked:soldier_no_ammo";
|
||||
case ECommunicationType.REPORT_ENGAGING: return "baked:soldier_engaging";
|
||||
case ECommunicationType.REPORT_TARGET_DOWN: return "baked:soldier_target_down";
|
||||
case ECommunicationType.REPORT_TARGET_LOST: return "baked:soldier_target_lost";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
static string Radio(string text)
|
||||
{
|
||||
switch (text)
|
||||
{
|
||||
case "Escapists, this is rescue command. We are preparing your escape route and assembling transports with armed escort helicopters. Hold the radio position until preparations are complete. Keep your smoke grenades ready and stand by for pickup coordinates.": return "baked:radio_01";
|
||||
case "Your escape route is ready. The exact pickup point is marked on your map. Gather your team there and throw a smoke grenade within 25 metres of the mark. We will send the transport and armed escorts when we see your smoke.": return "baked:radio_02";
|
||||
case "Rescue command cannot establish a safe approach. Extraction has failed.": return "baked:radio_03";
|
||||
case "Rescue command could not assemble the required transport capacity. Extraction has failed.": return "baked:radio_04";
|
||||
case "Signal received. Launching the rescue flight. Keep the landing point clear.": return "baked:radio_05";
|
||||
case "A rescue transport is down or unable to continue. Extraction has failed.": return "baked:radio_06";
|
||||
case "Rescue transports are on the ground. Everyone aboard, including your wounded. Use either rescue transport if there are two. Escorts are covering the pickup. We leave when all surviving prisoners are inside.": return "baked:radio_07";
|
||||
case "All survivors aboard. Lifting now. Stay inside the transport; escorts are taking you to safety.": return "baked:radio_08";
|
||||
case "Smoke confirmed. Rescue transport and armed cover are inbound. Clear the marked landing point. Bring your wounded; we wait for all surviving prisoners to board.": return "baked:radio_09";
|
||||
case "Smoke confirmed. Two rescue transports and armed cover are inbound. The helicopters will land separately near the marker. Use either transport and bring your wounded; both wait until everyone is aboard.": return "baked:radio_10";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
// The native action supplies held progress/cancellation. Authority rechecks the
|
||||
// current player, shovel and corpse immediately before replicated deletion.
|
||||
class ERR_Burial
|
||||
{
|
||||
static bool Dead(IEntity entity)
|
||||
{
|
||||
ChimeraCharacter character = ChimeraCharacter.Cast(entity);
|
||||
return character && character.GetCharacterController()
|
||||
&& character.GetCharacterController().GetLifeState() == ECharacterLifeState.DEAD;
|
||||
}
|
||||
static bool ShovelHeld(IEntity user)
|
||||
{
|
||||
ChimeraCharacter character = ChimeraCharacter.Cast(user);
|
||||
if (!character || !character.GetCharacterController()) return false;
|
||||
CharacterControllerComponent controller = character.GetCharacterController();
|
||||
// Gadgets occupy the left-hand slot; GetCurrentItemInHands alone only finds
|
||||
// weapons. Inventory ownership and a selected quickslot are not sufficient.
|
||||
IEntity item = controller.GetAttachedGadgetAtLeftHandSlot();
|
||||
if (!item) item = controller.GetCurrentItemInHands();
|
||||
return item && item.FindComponent(SCR_CampaignBuildingGadgetToolComponent) != null;
|
||||
}
|
||||
static bool Reach(float distance)
|
||||
{
|
||||
return distance >= 0 && distance <= 2.5;
|
||||
}
|
||||
static bool Clear(IEntity user, IEntity body)
|
||||
{
|
||||
TraceParam trace = new TraceParam();
|
||||
trace.Start = user.GetOrigin() + Vector(0, 1.2, 0);
|
||||
trace.End = body.GetOrigin() + Vector(0, 0.25, 0);
|
||||
trace.Exclude = user; trace.Flags = TraceFlags.ENTS | TraceFlags.WORLD;
|
||||
if (GetGame().GetWorld().TraceMove(trace, null) >= 1) return true;
|
||||
IEntity hit = trace.TraceEnt;
|
||||
for (int depth = 0; hit && depth < 16; depth++)
|
||||
{
|
||||
if (hit == body) return true;
|
||||
hit = hit.GetParent();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
static string Rejection(IEntity user, IEntity body)
|
||||
{
|
||||
ERR_GameMode mode = ERR_GameMode.Get();
|
||||
if (!mode || !mode.Active()) return "Mission is not active";
|
||||
if (!ERR_Actors.Player(user) || !ERR_Actors.CanFight(user)) return "You must be alive and conscious";
|
||||
if (user == body || !Dead(body)) return "Only dead bodies can be buried";
|
||||
if (!ShovelHeld(user)) return "Take a shovel into your hands";
|
||||
if (CompartmentAccessComponent.GetVehicleIn(user) || CompartmentAccessComponent.GetVehicleIn(body)) return "Move yourself and the body out of the vehicle";
|
||||
if (!Reach(vector.Distance(user.GetOrigin(), body.GetOrigin()))) return "Move closer to the body";
|
||||
if (!Clear(user, body)) return "The body is obstructed";
|
||||
return string.Empty;
|
||||
}
|
||||
static bool Permitted(IEntity user, IEntity body)
|
||||
{
|
||||
if (!Replication.IsServer()) return false;
|
||||
ERR_GameMode mode = ERR_GameMode.Get();
|
||||
return mode && mode.Players() && mode.Players().CurrentActor(user) && Rejection(user, body).IsEmpty();
|
||||
}
|
||||
static void Complete(IEntity user, IEntity body)
|
||||
{
|
||||
if (!Permitted(user, body)) return;
|
||||
// Corpses and their remaining inventory disappear on every peer. Already
|
||||
// queued HQ casualty reports retain their immutable attribution and position.
|
||||
SCR_EntityHelper.DeleteEntityAndChildren(body);
|
||||
}
|
||||
}
|
||||
|
||||
class ERR_BuryBodyAction : ScriptedUserAction
|
||||
{
|
||||
override bool HasLocalEffectOnlyScript() { return false; }
|
||||
override bool CheckOnServerFirstScript() { return true; }
|
||||
override bool CanBroadcastScript() { return false; }
|
||||
override bool CanBeShownScript(IEntity user)
|
||||
{
|
||||
ERR_GameMode mode = ERR_GameMode.Get();
|
||||
return mode && mode.Active() && user != GetOwner() && ERR_Burial.Dead(GetOwner()) && ERR_Burial.ShovelHeld(user);
|
||||
}
|
||||
override bool CanBePerformedScript(IEntity user)
|
||||
{
|
||||
string reason = ERR_Burial.Rejection(user, GetOwner());
|
||||
SetCannotPerformReason(reason);
|
||||
return reason.IsEmpty();
|
||||
}
|
||||
override void PerformAction(IEntity pOwnerEntity, IEntity pUserEntity)
|
||||
{
|
||||
if (pOwnerEntity != GetOwner() || !ERR_Burial.Permitted(pUserEntity, pOwnerEntity)) return;
|
||||
// Let the native action finish before destroying its owning manager. The
|
||||
// deferred callback revalidates, so duplicate completions are harmless.
|
||||
GetGame().GetCallqueue().CallLater(ERR_Burial.Complete, 1, false, pUserEntity, pOwnerEntity);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
// Conservative fallback for a sleeping, engine-off Mi8 whose native simulation
|
||||
// has not yet populated contact flags. Reads physical support; never wakes it.
|
||||
// Conservative fallback for audited sleeping, engine-off rescue aircraft whose
|
||||
// native simulation has not populated contact flags. Reads support; never wakes it.
|
||||
class ERR_CasualtyGroundSupport
|
||||
{
|
||||
static bool SupportHit(float fraction, float normalY, bool selfHit, bool dynamicHit)
|
||||
@@ -59,14 +59,37 @@ class ERR_CasualtyGroundSupport
|
||||
}
|
||||
return supported;
|
||||
}
|
||||
static int LandingGearKind(string prefab)
|
||||
{
|
||||
if (prefab.Contains("/Mi8MT/") || prefab.EndsWith("Prefabs/ERR/RescueHelicopter.et") || prefab.EndsWith("Prefabs/ERR/RescueHelicopterFIA.et") || prefab.EndsWith("Prefabs/ERR/RescueHelicopterAFRF.et")) return 1;
|
||||
if (prefab.Contains("/UH1H/") || prefab.EndsWith("Prefabs/ERR/RescueHelicopterWest.et") || prefab.EndsWith("Prefabs/ERR/RescueHelicopterION.et")) return 2;
|
||||
return 0;
|
||||
}
|
||||
static bool SkidsSupported(Vehicle aircraft, bool diagnostics = false)
|
||||
{
|
||||
vector leftFront, rightFront, leftRear, rightRear;
|
||||
if (!WheelCenter(aircraft, "v_ground_contact_L01", leftFront, diagnostics) || !WheelCenter(aircraft, "v_ground_contact_R01", rightFront, diagnostics) || !WheelCenter(aircraft, "v_ground_contact_L02", leftRear, diagnostics) || !WheelCenter(aircraft, "v_ground_contact_R02", rightRear, diagnostics)) return false;
|
||||
// Installed UH1H_base.et defines these four skid pivots without wheel radii.
|
||||
// Both skids and both ends need direct static support. Missing/collapsed
|
||||
// pivots and a helicopter suspended above the ground fail closed.
|
||||
if (vector.DistanceXZ(leftFront, rightFront) < 1 || vector.DistanceXZ(leftRear, rightRear) < 1 || vector.DistanceXZ(leftFront, leftRear) < 1 || vector.DistanceXZ(rightFront, rightRear) < 1) return false;
|
||||
bool a = WheelSupported(aircraft, leftFront, 0, diagnostics);
|
||||
bool b = WheelSupported(aircraft, rightFront, 0, diagnostics);
|
||||
bool c = WheelSupported(aircraft, leftRear, 0, diagnostics);
|
||||
bool d = WheelSupported(aircraft, rightRear, 0, diagnostics);
|
||||
return a && b && c && d;
|
||||
}
|
||||
static bool Parked(Vehicle aircraft, bool diagnostics = false)
|
||||
{
|
||||
if (!aircraft || !aircraft.GetPrefabData() || !aircraft.GetPrefabData().GetPrefabName().Contains("/Mi8MT/")) return false;
|
||||
if (!aircraft || !aircraft.GetPrefabData()) return false;
|
||||
int kind = LandingGearKind(aircraft.GetPrefabData().GetPrefabName());
|
||||
if (!kind) return false;
|
||||
Physics physics = aircraft.GetPhysics();
|
||||
HelicopterControllerComponent controller = HelicopterControllerComponent.Cast(aircraft.FindComponent(HelicopterControllerComponent));
|
||||
if (!physics || !controller || !controller.GetSimulation()) return false;
|
||||
if (diagnostics) PrintFormat("ERR PARKED SUPPORT: physicsAwake=%1 engineOn=%2 nativeContact=%3", physics.IsActive(), controller.GetSimulation().EngineIsOn(), controller.GetSimulation().HasAnyGroundContact());
|
||||
if (physics.IsActive() || controller.GetSimulation().EngineIsOn()) return false;
|
||||
if (kind == 2) return SkidsSupported(aircraft, diagnostics);
|
||||
vector front, left, right;
|
||||
if (!WheelCenter(aircraft, "v_wheel_F", front, diagnostics) || !WheelCenter(aircraft, "v_wheel_bL", left, diagnostics) || !WheelCenter(aircraft, "v_wheel_bR", right, diagnostics)) return false;
|
||||
// Fail closed if native pivot resolution collapses to an owner origin or
|
||||
|
||||
@@ -4,7 +4,7 @@ class ERR_CasualtyFeedback
|
||||
static bool Visible(ERR_GameMode mode, IEntity caller, IEntity casualty)
|
||||
{
|
||||
if (!mode || !mode.Active() || mode.Phase() != ERR_Phase.EXTRACTION || !caller || caller == casualty || !ERR_CasualtyLoadingRules.Down(casualty)) return false;
|
||||
Vehicle aircraft = mode.CasualtyLoadingAircraft();
|
||||
Vehicle aircraft = mode.CasualtyLoadingAircraftFor(caller, casualty);
|
||||
Faction faction = mode.PlayerFaction();
|
||||
return aircraft && faction && SCR_FactionManager.SGetFaction(caller) == faction && SCR_FactionManager.SGetFaction(casualty) == faction && ERR_CasualtyLoadingRules.Ranges(vector.Distance(caller.GetOrigin(), casualty.GetOrigin()), vector.Distance(caller.GetOrigin(), aircraft.GetOrigin()), vector.Distance(casualty.GetOrigin(), aircraft.GetOrigin()));
|
||||
}
|
||||
@@ -14,7 +14,7 @@ class ERR_CasualtyFeedback
|
||||
if (!caller || caller == casualty || !ERR_Actors.CanFight(caller)) return "A living, conscious rescuer is required";
|
||||
if (CompartmentAccessComponent.GetVehicleIn(caller)) return "Leave the helicopter before loading a casualty";
|
||||
if (!ERR_CasualtyLoadingRules.GroundSettled(casualty)) return "Place the living, unconscious casualty on stable ground";
|
||||
Vehicle aircraft = mode.CasualtyLoadingAircraft();
|
||||
Vehicle aircraft = mode.CasualtyLoadingAircraftFor(caller, casualty);
|
||||
if (!aircraft || !ERR_CasualtyLoadingRules.Ranges(vector.Distance(caller.GetOrigin(), casualty.GetOrigin()), vector.Distance(caller.GetOrigin(), aircraft.GetOrigin()), vector.Distance(casualty.GetOrigin(), aircraft.GetOrigin()))) return "Stay beside the casualty and within twelve metres of the extraction helicopter";
|
||||
if (!ERR_CasualtyLoadingRules.AircraftReady(aircraft)) return "The helicopter must be upright, grounded and stationary";
|
||||
if (!ERR_CasualtyLoadingRules.Nearby(mode, caller, casualty)) return "Keep clear access to a fellow prisoner's body";
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
// Round-owned server service; only the aircraft reference is replicated for action visibility.
|
||||
// Round-owned server service; only registered transport references are replicated.
|
||||
modded class ERR_GameMode
|
||||
{
|
||||
protected ref ERR_CasualtyLoadingService m_ERRCasualtyLoading;
|
||||
[RplProp()]
|
||||
protected RplId m_ERRCasualtyAircraftId = RplId.Invalid();
|
||||
[RplProp()]
|
||||
protected RplId m_ERRCasualtySecondAircraftId = RplId.Invalid();
|
||||
ERR_CasualtyLoadingService CasualtyLoading() { return m_ERRCasualtyLoading; }
|
||||
Vehicle CasualtyLoadingAircraft()
|
||||
{
|
||||
@@ -12,17 +14,61 @@ modded class ERR_GameMode
|
||||
if (!component) return null;
|
||||
return Vehicle.Cast(component.GetEntity());
|
||||
}
|
||||
int CasualtyLoadingAircrafts(out array<Vehicle> aircrafts)
|
||||
{
|
||||
aircrafts.Clear();
|
||||
if (Replication.IsServer())
|
||||
{
|
||||
if (Extraction()) return Extraction().Vehicles(aircrafts);
|
||||
return 0;
|
||||
}
|
||||
Vehicle first = CasualtyLoadingAircraft();
|
||||
if (first) aircrafts.Insert(first);
|
||||
Vehicle second = Vehicle.Cast(ERR_CasualtyLoadingRules.Entity(m_ERRCasualtySecondAircraftId));
|
||||
if (second && second != first) aircrafts.Insert(second);
|
||||
return aircrafts.Count();
|
||||
}
|
||||
bool CasualtyLoadingTransport(Vehicle aircraft)
|
||||
{
|
||||
if (!aircraft) return false;
|
||||
if (Replication.IsServer()) return Extraction() && Extraction().IsTransport(aircraft);
|
||||
return aircraft == CasualtyLoadingAircraft() || (m_ERRCasualtySecondAircraftId != RplId.Invalid() && ERR_CasualtyLoadingRules.EntityId(aircraft) == m_ERRCasualtySecondAircraftId);
|
||||
}
|
||||
// Prefer a usable nearby transport; retain the nearest ineligible aircraft for
|
||||
// a useful denial reason. An existing casualty reservation wins over distance.
|
||||
Vehicle CasualtyLoadingAircraftFor(IEntity caller, IEntity casualty)
|
||||
{
|
||||
if (!caller || !casualty) return null;
|
||||
array<Vehicle> aircrafts = {}; CasualtyLoadingAircrafts(aircrafts);
|
||||
Vehicle nearest, available;
|
||||
float nearestDistance = float.MAX, availableDistance = float.MAX;
|
||||
foreach (Vehicle aircraft : aircrafts)
|
||||
{
|
||||
if (!ERR_Actors.Alive(aircraft)) continue;
|
||||
float distance = vector.Distance(casualty.GetOrigin(), aircraft.GetOrigin());
|
||||
if (distance < nearestDistance) { nearest = aircraft; nearestDistance = distance; }
|
||||
if (!ERR_CasualtyLoadingRules.Ranges(vector.Distance(caller.GetOrigin(), casualty.GetOrigin()), vector.Distance(caller.GetOrigin(), aircraft.GetOrigin()), distance) || !ERR_CasualtyLoadingRules.AircraftReady(aircraft)) continue;
|
||||
BaseCompartmentManagerComponent manager = BaseCompartmentManagerComponent.Cast(aircraft.FindComponent(BaseCompartmentManagerComponent));
|
||||
if (!manager) continue;
|
||||
array<BaseCompartmentSlot> seats = {}; manager.GetCompartments(seats);
|
||||
foreach (BaseCompartmentSlot seat : seats)
|
||||
{
|
||||
if (!ERR_CasualtyLoadingRules.TransportBench(seat, aircraft) || seat.GetOccupant() || !seat.IsCompartmentAccessible() || seat.IsGetInLockedFor(casualty)) continue;
|
||||
if (seat.IsReservedBy(casualty)) return aircraft;
|
||||
if (!seat.IsReserved() && distance < availableDistance) { available = aircraft; availableDistance = distance; }
|
||||
}
|
||||
}
|
||||
if (available) return available;
|
||||
return nearest;
|
||||
}
|
||||
protected void ERR_UpdateCasualtyAircraft(float now)
|
||||
{
|
||||
Vehicle aircraft = ExtractionVehicle();
|
||||
if (!aircraft)
|
||||
{
|
||||
if (m_ERRCasualtyAircraftId != RplId.Invalid()) { m_ERRCasualtyAircraftId = RplId.Invalid(); Replication.BumpMe(); }
|
||||
return;
|
||||
}
|
||||
RplComponent component = RplComponent.Cast(aircraft.FindComponent(RplComponent));
|
||||
if (!component || m_ERRCasualtyAircraftId == component.Id()) return;
|
||||
m_ERRCasualtyAircraftId = component.Id();
|
||||
array<Vehicle> aircrafts = {}; CasualtyLoadingAircrafts(aircrafts);
|
||||
RplId first = RplId.Invalid(), second = RplId.Invalid();
|
||||
if (!aircrafts.IsEmpty()) first = ERR_CasualtyLoadingRules.EntityId(aircrafts[0]);
|
||||
if (aircrafts.Count() > 1) second = ERR_CasualtyLoadingRules.EntityId(aircrafts[1]);
|
||||
if (first == m_ERRCasualtyAircraftId && second == m_ERRCasualtySecondAircraftId) return;
|
||||
m_ERRCasualtyAircraftId = first; m_ERRCasualtySecondAircraftId = second;
|
||||
Replication.BumpMe();
|
||||
}
|
||||
protected void ERR_StopCasualtyLoading()
|
||||
|
||||
@@ -52,7 +52,7 @@ modded class SCR_PlayerController
|
||||
IEntity caller = ERR_CasualtyLoadingRules.Entity(callerId);
|
||||
Vehicle vehicle = Vehicle.Cast(ERR_CasualtyLoadingRules.Entity(vehicleId));
|
||||
IEntity owner = ERR_CasualtyLoadingRules.Entity(ownerId);
|
||||
if (lease && mode && ERR_CasualtyLoadingRules.EntityId(mode) == modeId && ERR_CasualtyLoadingRules.EntityId(actor) == actorId && vehicle == mode.CasualtyLoadingAircraft() && ERR_CasualtyLoadingRules.Nearby(mode, caller, actor) && owner)
|
||||
if (lease && mode && ERR_CasualtyLoadingRules.EntityId(mode) == modeId && ERR_CasualtyLoadingRules.EntityId(actor) == actorId && mode.CasualtyLoadingTransport(vehicle) && ERR_CasualtyLoadingRules.NearbyAircraft(mode, caller, actor, vehicle) && owner)
|
||||
{
|
||||
BaseCompartmentManagerComponent manager = BaseCompartmentManagerComponent.Cast(owner.FindComponent(BaseCompartmentManagerComponent));
|
||||
SCR_CompartmentAccessComponent access = SCR_CompartmentAccessComponent.Cast(actor.FindComponent(SCR_CompartmentAccessComponent));
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// First release supports only the registered Mi8 extraction aircraft's physical
|
||||
// child transport benches. Nothing changes health, ownership, or seat classes.
|
||||
// Only registered rescue transports and audited native child benches are eligible.
|
||||
// Nothing changes health, ownership, or seat classes.
|
||||
class ERR_CasualtyLoadingRules
|
||||
{
|
||||
static int RoundAction(int phase)
|
||||
@@ -86,20 +86,30 @@ class ERR_CasualtyLoadingRules
|
||||
return false;
|
||||
}
|
||||
static bool Nearby(ERR_GameMode mode, IEntity caller, IEntity casualty)
|
||||
{
|
||||
if (!mode) return false;
|
||||
return NearbyAircraft(mode, caller, casualty, mode.CasualtyLoadingAircraftFor(caller, casualty));
|
||||
}
|
||||
static bool NearbyAircraft(ERR_GameMode mode, IEntity caller, IEntity casualty, Vehicle aircraft)
|
||||
{
|
||||
if (!mode || !mode.Active() || mode.Phase() != ERR_Phase.EXTRACTION || !caller || caller == casualty) return false;
|
||||
CharacterControllerComponent rescuer = CharacterControllerComponent.Cast(caller.FindComponent(CharacterControllerComponent));
|
||||
if (!ERR_Actors.CanFight(caller) || !rescuer || rescuer.GetLifeState() != ECharacterLifeState.ALIVE || !GroundSettled(casualty) || CompartmentAccessComponent.GetVehicleIn(caller)) return false;
|
||||
Vehicle aircraft = mode.CasualtyLoadingAircraft();
|
||||
if (!aircraft || !Ranges(vector.Distance(caller.GetOrigin(), casualty.GetOrigin()), vector.Distance(caller.GetOrigin(), aircraft.GetOrigin()), vector.Distance(casualty.GetOrigin(), aircraft.GetOrigin())) || !AircraftReady(aircraft)) return false;
|
||||
if (!mode.CasualtyLoadingTransport(aircraft) || !Ranges(vector.Distance(caller.GetOrigin(), casualty.GetOrigin()), vector.Distance(caller.GetOrigin(), aircraft.GetOrigin()), vector.Distance(casualty.GetOrigin(), aircraft.GetOrigin())) || !AircraftReady(aircraft)) return false;
|
||||
Faction faction = mode.PlayerFaction();
|
||||
return faction && SCR_FactionManager.SGetFaction(caller) == faction && SCR_FactionManager.SGetFaction(casualty) == faction && LineOfSight(caller, casualty);
|
||||
}
|
||||
static bool TransportBenchPrefab(string prefab)
|
||||
{
|
||||
// Exact authored bench owners, including the two unarmed Huey side seats.
|
||||
// Aircraft-root pilot seats, turret owners and patient slots stay excluded.
|
||||
return prefab.EndsWith("Prefabs/Vehicles/Helicopters/Mi8MT/VehParts/Seats/VehPart_MI8_seats_cargo.et") || prefab.EndsWith("Prefabs/Vehicles/Helicopters/Mi8MT/VehParts/Seats/VehPart_MI8_CIV_seats_cargo.et") || prefab.EndsWith("Prefabs/Vehicles/Helicopters/UH1H/VehParts/Seats/VehPart_UH1H_seats_cargo.et") || prefab.EndsWith("Prefabs/Vehicles/Helicopters/UH1H/VehParts/Seats/VehPart_UH1H_seats_cargo_openDoor.et") || prefab.EndsWith("Prefabs/Vehicles/Helicopters/UH1H/VehParts/Seats/VehPart_UH1H_seats_gunners.et") || prefab.EndsWith("Prefabs/Vehicles/Helicopters/UH1H/VehParts/Seats/VehPart_UH1H_CIV_seats_cargo.et") || prefab.EndsWith("Prefabs/Vehicles/Helicopters/UH1H/VehParts/Seats/VehPart_UH1H_CIV_seats_gunners.et");
|
||||
}
|
||||
static bool TransportBench(BaseCompartmentSlot seat, Vehicle aircraft)
|
||||
{
|
||||
if (!seat || !aircraft || seat.GetType() != ECompartmentType.CARGO || SCR_PatientCompartmentSlot.Cast(seat)) return false;
|
||||
IEntity owner = seat.GetOwner();
|
||||
if (!owner || owner == aircraft || !owner.GetPrefabData() || !owner.GetPrefabData().GetPrefabName().Contains("VehPart_MI8_seats")) return false;
|
||||
if (!owner || owner == aircraft || !owner.GetPrefabData() || !TransportBenchPrefab(owner.GetPrefabData().GetPrefabName())) return false;
|
||||
while (owner)
|
||||
{
|
||||
if (owner == aircraft) return true;
|
||||
@@ -203,7 +213,7 @@ class ERR_CasualtyLoadingService
|
||||
{
|
||||
if (!Replication.IsServer() || m_Stopped || !ERR_CasualtyLoadingRules.Nearby(m_Mode, caller, casualty)) return false;
|
||||
if (!CurrentLife(caller) || !CurrentLife(casualty) || !Settled(casualty, System.GetTickCount()) || PendingActor(caller, casualty)) return false;
|
||||
return AvailableSeat(casualty, m_Mode.ExtractionVehicle()) != null;
|
||||
return AvailableSeat(casualty, m_Mode.CasualtyLoadingAircraftFor(caller, casualty)) != null;
|
||||
}
|
||||
// Reasons never replace CanLoad/Begin authority or reserve a seat.
|
||||
string RejectionReason(IEntity caller, IEntity casualty)
|
||||
@@ -214,7 +224,7 @@ class ERR_CasualtyLoadingService
|
||||
if (!CurrentLife(caller) || !CurrentLife(casualty)) return "Both prisoners must still be connected to this mission";
|
||||
if (!Settled(casualty, System.GetTickCount())) return "Let the casualty rest on the ground for five seconds";
|
||||
if (PendingActor(caller, casualty)) return "A casualty loading request is already in progress";
|
||||
if (!AvailableSeat(casualty, m_Mode.ExtractionVehicle())) return "No accessible transport seat is available";
|
||||
if (!AvailableSeat(casualty, m_Mode.CasualtyLoadingAircraftFor(caller, casualty))) return "No accessible transport seat is available";
|
||||
return string.Empty;
|
||||
}
|
||||
protected void NotifyCaller(IEntity caller, string message)
|
||||
@@ -238,10 +248,11 @@ class ERR_CasualtyLoadingService
|
||||
if (!CanLoad(caller, casualty) || m_NextNonce >= 2147483646) return false;
|
||||
ERR_PlayerLife callerLife = CurrentLife(caller);
|
||||
ERR_PlayerLife casualtyLife = CurrentLife(casualty);
|
||||
Vehicle aircraft = m_Mode.CasualtyLoadingAircraftFor(caller, casualty);
|
||||
SCR_PlayerController controller = SCR_PlayerController.Cast(GetGame().GetPlayerManager().GetPlayerController(casualtyLife.ConnectionId));
|
||||
if (!controller || ERR_CasualtyLoadingRules.EntityId(m_Mode) == RplId.Invalid() || ERR_CasualtyLoadingRules.EntityId(caller) == RplId.Invalid() || ERR_CasualtyLoadingRules.EntityId(casualty) == RplId.Invalid() || ERR_CasualtyLoadingRules.EntityId(m_Mode.ExtractionVehicle()) == RplId.Invalid()) return false;
|
||||
if (!controller || ERR_CasualtyLoadingRules.EntityId(m_Mode) == RplId.Invalid() || ERR_CasualtyLoadingRules.EntityId(caller) == RplId.Invalid() || ERR_CasualtyLoadingRules.EntityId(casualty) == RplId.Invalid() || ERR_CasualtyLoadingRules.EntityId(aircraft) == RplId.Invalid()) return false;
|
||||
ERR_CasualtyLoadTicket ticket = new ERR_CasualtyLoadTicket();
|
||||
ticket.Caller = caller; ticket.Casualty = casualty; ticket.Aircraft = m_Mode.ExtractionVehicle();
|
||||
ticket.Caller = caller; ticket.Casualty = casualty; ticket.Aircraft = aircraft;
|
||||
ticket.Seat = AvailableSeat(casualty, ticket.Aircraft);
|
||||
if (!ticket.Seat || ERR_CasualtyLoadingRules.EntityId(ticket.Seat.GetOwner()) == RplId.Invalid()) return false;
|
||||
ticket.CallerAccount = callerLife.AccountId; ticket.CasualtyAccount = casualtyLife.AccountId;
|
||||
@@ -269,7 +280,7 @@ class ERR_CasualtyLoadingService
|
||||
ERR_CasualtyLoadTicket ticket = Ticket(controller, nonce);
|
||||
if (!ticket || ticket.Committed) return;
|
||||
int now = System.GetTickCount();
|
||||
if (!ValidTicket(ticket) || !ERR_CasualtyLoadingRules.Within(ticket.Started, now, 5000) || !Settled(ticket.Casualty, now) || !ERR_CasualtyLoadingRules.Nearby(m_Mode, ticket.Caller, ticket.Casualty) || ticket.Seat.GetOccupant() || !ticket.Seat.IsCompartmentAccessible() || ticket.Seat.IsGetInLockedFor(ticket.Casualty) || !ticket.Seat.IsReservedBy(ticket.Casualty)) { Resolve(ticket, false); return; }
|
||||
if (!ValidTicket(ticket) || !ERR_CasualtyLoadingRules.Within(ticket.Started, now, 5000) || !Settled(ticket.Casualty, now) || !ERR_CasualtyLoadingRules.NearbyAircraft(m_Mode, ticket.Caller, ticket.Casualty, ticket.Aircraft) || ticket.Seat.GetOccupant() || !ticket.Seat.IsCompartmentAccessible() || ticket.Seat.IsGetInLockedFor(ticket.Casualty) || !ticket.Seat.IsReservedBy(ticket.Casualty)) { Resolve(ticket, false); return; }
|
||||
ticket.Committed = true; ticket.CommittedAt = now;
|
||||
// Final authority/physical validation is the commit boundary. No retries.
|
||||
controller.ERR_CommitCasualtyLoad(ERR_CasualtyLoadingRules.EntityId(m_Mode), nonce, ERR_CasualtyLoadingRules.EntityId(ticket.Caller), ERR_CasualtyLoadingRules.EntityId(ticket.Casualty), ERR_CasualtyLoadingRules.EntityId(ticket.Aircraft), ERR_CasualtyLoadingRules.EntityId(ticket.Seat.GetOwner()), ticket.Seat.GetCompartmentSlotID());
|
||||
@@ -288,11 +299,11 @@ class ERR_CasualtyLoadingService
|
||||
{
|
||||
// Death cannot revoke an in-flight commit. Keep the vacant seat reserved
|
||||
// while the captured body exists; its owner may still see the prior state.
|
||||
return m_Mode && m_Mode.Active() && m_Mode.Phase() == ERR_Phase.EXTRACTION && ticket.Aircraft == m_Mode.ExtractionVehicle() && ERR_Actors.Alive(ticket.Aircraft) && ticket.Casualty != null && ERR_CasualtyLoadingRules.TransportBench(ticket.Seat, ticket.Aircraft);
|
||||
return m_Mode && m_Mode.Active() && m_Mode.Phase() == ERR_Phase.EXTRACTION && m_Mode.CasualtyLoadingTransport(ticket.Aircraft) && ERR_Actors.Alive(ticket.Aircraft) && ticket.Casualty != null && ERR_CasualtyLoadingRules.TransportBench(ticket.Seat, ticket.Aircraft);
|
||||
}
|
||||
protected bool ValidTicket(ERR_CasualtyLoadTicket ticket)
|
||||
{
|
||||
return m_Mode && m_Mode.Active() && m_Mode.Phase() == ERR_Phase.EXTRACTION && ticket.Aircraft == m_Mode.ExtractionVehicle() && ERR_Actors.Alive(ticket.Aircraft) && ERR_Actors.Alive(ticket.Casualty) && SameLife(ticket.Caller, ticket.CallerAccount, ticket.CallerConnection) && SameLife(ticket.Casualty, ticket.CasualtyAccount, ticket.CasualtyConnection) && ERR_CasualtyLoadingRules.TransportBench(ticket.Seat, ticket.Aircraft);
|
||||
return m_Mode && m_Mode.Active() && m_Mode.Phase() == ERR_Phase.EXTRACTION && m_Mode.CasualtyLoadingTransport(ticket.Aircraft) && ERR_Actors.Alive(ticket.Aircraft) && ERR_Actors.Alive(ticket.Casualty) && SameLife(ticket.Caller, ticket.CallerAccount, ticket.CallerConnection) && SameLife(ticket.Casualty, ticket.CasualtyAccount, ticket.CasualtyConnection) && ERR_CasualtyLoadingRules.TransportBench(ticket.Seat, ticket.Aircraft);
|
||||
}
|
||||
protected void NotifyResolution(ERR_CasualtyLoadTicket ticket, bool success)
|
||||
{
|
||||
@@ -353,7 +364,7 @@ class ERR_CasualtyLoadingService
|
||||
// leaves room for replication. Timeouts do not issue compensating teleports.
|
||||
if (!ERR_CasualtyLoadingRules.Within(ticket.CommittedAt, now, 8000)) Resolve(ticket, false);
|
||||
}
|
||||
else if (!ERR_CasualtyLoadingRules.Within(ticket.Started, now, 5000) || !ERR_CasualtyLoadingRules.Nearby(m_Mode, ticket.Caller, ticket.Casualty)) Resolve(ticket, false);
|
||||
else if (!ERR_CasualtyLoadingRules.Within(ticket.Started, now, 5000) || !ERR_CasualtyLoadingRules.NearbyAircraft(m_Mode, ticket.Caller, ticket.Casualty, ticket.Aircraft)) Resolve(ticket, false);
|
||||
}
|
||||
}
|
||||
void RosterChanged(int connection) { if (GetGame()) Tick(GetGame().GetWorld().GetWorldTime()); }
|
||||
|
||||
@@ -49,6 +49,7 @@ class ERR_ChatClient
|
||||
if (m_Command) m_Command.Remove(OnCommand);
|
||||
m_Command = null;
|
||||
ERR_AdminMenu menu = ERR_AdminMenu.GetOpenMenu();
|
||||
if (menu) menu.Close();
|
||||
// A retained former controller must not close the current owner's panel.
|
||||
if (menu && GetGame() && m_Controller == GetGame().GetPlayerController()) menu.Close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
class ERR_CommunityMapRecord
|
||||
{
|
||||
int wins = -1;
|
||||
int losses = -1;
|
||||
int completed = -1;
|
||||
int partialMissions = -1;
|
||||
bool Valid()
|
||||
{
|
||||
return wins >= 0 && wins <= 1000000000 && losses >= 0 && losses <= 1000000000 && completed == wins + losses && completed > 0 && partialMissions >= 0 && partialMissions <= completed;
|
||||
}
|
||||
string Line(string label)
|
||||
{
|
||||
int rate = Math.Round(100.0 * wins / completed);
|
||||
return string.Format("%1: %2 wins | %3 failures | ", label, wins, losses) + rate.ToString() + "% win rate";
|
||||
}
|
||||
}
|
||||
class ERR_CommunityStatistics
|
||||
{
|
||||
int schemaVersion;
|
||||
string terrain;
|
||||
string serverId;
|
||||
string missionId;
|
||||
string eventId;
|
||||
string outcome;
|
||||
ref ERR_CommunityMapRecord server;
|
||||
ref ERR_CommunityMapRecord allServers;
|
||||
bool Matches(string expectedServer, string expectedMission, string expectedEvent, string expectedTerrain, string expectedOutcome)
|
||||
{
|
||||
if (schemaVersion != 1 || serverId != expectedServer || missionId != expectedMission || eventId != expectedEvent || terrain != expectedTerrain || outcome != expectedOutcome) return false;
|
||||
if (outcome != "victory" && outcome != "defeat") return false;
|
||||
if (!server || !allServers || !server.Valid() || !allServers.Valid()) return false;
|
||||
if (server.wins > allServers.wins || server.losses > allServers.losses) return false;
|
||||
if (outcome == "victory" && server.wins == 0) return false;
|
||||
if (outcome == "defeat" && server.losses == 0) return false;
|
||||
return true;
|
||||
}
|
||||
string Summary()
|
||||
{
|
||||
string text = "This map's record (including this round)\n";
|
||||
text += server.Line("This server") + "\n" + allServers.Line("All reporting servers");
|
||||
if (allServers.partialMissions > 0) text += "\nIncludes rounds with partial statistics.";
|
||||
return text;
|
||||
}
|
||||
}
|
||||
@@ -7,22 +7,25 @@ modded class ERR_AdminMenu
|
||||
{
|
||||
Open();
|
||||
ERR_AdminMenu menu = GetOpenMenu();
|
||||
if (menu) menu.ShowPage(3);
|
||||
if (menu) menu.ShowPage(ERR_AdminPage.MODS);
|
||||
}
|
||||
override void OnMenuOpen()
|
||||
{
|
||||
super.OnMenuOpen();
|
||||
Widget list = GetRootWidget().FindAnyWidget("ModList");
|
||||
Widget list = GetRootWidget().FindAnyWidget("PackList");
|
||||
foreach (string pack : ERR_ContentRegistry.Ids())
|
||||
{
|
||||
Widget row = GetGame().GetWorkspace().CreateWidgets("{F092D45656A8E02B}UI/layouts/ERR/ContentRow.layout", list);
|
||||
if (!row) { Close(); return; }
|
||||
m_ModRows.Set(pack, row);
|
||||
row.FindAnyWidget("EditItems").SetVisible(false); row.FindAnyWidget("EditVehicles").SetVisible(false);
|
||||
TextWidget label = TextWidget.Cast(row.FindAnyWidget("PackLabel"));
|
||||
if (label) label.SetText(ERR_ContentRegistry.Label(pack));
|
||||
SCR_ButtonTextComponent enable = SCR_ButtonTextComponent.GetButtonText("Enable", row);
|
||||
SCR_ButtonTextComponent disable = SCR_ButtonTextComponent.GetButtonText("Disable", row);
|
||||
if (!enable || !disable) { Close(); return; }
|
||||
ERR_SelectionStyle.Action(enable, true); ERR_SelectionStyle.Action(disable, false);
|
||||
ERR_SelectionStyle.Status(row, "SelectionState", -1);
|
||||
enable.m_OnClicked.Insert(OnModButton); disable.m_OnClicked.Insert(OnModButton);
|
||||
}
|
||||
UpdateActions();
|
||||
@@ -49,7 +52,11 @@ modded class ERR_AdminMenu
|
||||
SCR_ButtonTextComponent disable = SCR_ButtonTextComponent.GetButtonText("Disable", row);
|
||||
if (enable) enable.SetEnabled(ready && m_ModSaved.Get(pack) == 0);
|
||||
if (disable) disable.SetEnabled(ready && m_ModSaved.Get(pack) == 1);
|
||||
int selected = -1; if (m_ModSaved.Contains(pack)) selected = m_ModSaved.Get(pack);
|
||||
ERR_SelectionStyle.Pair(row, "Enable", "Disable", selected);
|
||||
TextWidget state = TextWidget.Cast(row.FindAnyWidget("PackState"));
|
||||
int badge = -1; if (ready) badge = m_ModActive.Get(pack);
|
||||
ERR_SelectionStyle.Status(row, "SelectionState", badge);
|
||||
if (!state) continue;
|
||||
if (!ready) state.SetText("Waiting for server settings...");
|
||||
else state.SetText("Current round: " + ERR_ContentRegistry.State(m_ModActive.Get(pack) == 1) + " | Saved preference: " + ERR_ContentRegistry.State(m_ModSaved.Get(pack) == 1));
|
||||
@@ -103,6 +110,24 @@ modded class PauseMenuUI
|
||||
m_ERRModsButton = SCR_ButtonTextComponent.Cast(widget.FindHandler(SCR_ButtonTextComponent));
|
||||
if (m_ERRModsButton) m_ERRModsButton.m_OnClicked.Insert(ERR_OpenMods);
|
||||
}
|
||||
// Vanilla disables this button while limited, but does not enable it again
|
||||
// when an admin mode arrives asynchronously. Refresh from native permissions.
|
||||
protected void ERR_RefreshGameMasterButton()
|
||||
{
|
||||
if (!ERR_GameMode.Get() || !m_EditorUnlimitedOpenButton) return;
|
||||
SCR_EditorManagerEntity editor = SCR_EditorManagerEntity.GetInstance();
|
||||
m_EditorUnlimitedOpenButton.SetEnabled(editor && !editor.IsLimited() && editor.CanOpen());
|
||||
}
|
||||
protected override void OnEditorModeChanged(SCR_EditorModeEntity modeEntity)
|
||||
{
|
||||
super.OnEditorModeChanged(modeEntity);
|
||||
ERR_RefreshGameMasterButton();
|
||||
}
|
||||
override void OnMenuUpdate(float tDelta)
|
||||
{
|
||||
super.OnMenuUpdate(tDelta);
|
||||
ERR_RefreshGameMasterButton();
|
||||
}
|
||||
protected void ERR_OpenMods()
|
||||
{
|
||||
// Unpause local simulation before requesting an authenticated server snapshot.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Separate, config-driven preferences. Missing newly installed options use their
|
||||
// Content preferences in the shared profile. Newly installed options use their
|
||||
// authored defaults. Failed writes preserve both previous state and backup.
|
||||
class ERR_ContentSettings
|
||||
{
|
||||
@@ -20,7 +20,7 @@ class ERR_ContentSettings
|
||||
if (!Replication.IsServer()) { m_ReadOnly = true; return; }
|
||||
map<string, int> loaded;
|
||||
if (Read(m_Path, loaded) || Read(m_Path + ".bak", loaded)) m_Values = loaded;
|
||||
else if (FileIO.FileExists(m_Path) || FileIO.FileExists(m_Path + ".bak"))
|
||||
else if (ERR_ProfileConfig.Exists(m_Path) || ERR_ProfileConfig.Exists(m_Path + ".bak"))
|
||||
{
|
||||
m_ReadOnly = true;
|
||||
Print("Co10 Escape Reintroduced: invalid content preferences; authored defaults active, existing files preserved", LogLevel.ERROR);
|
||||
@@ -30,7 +30,10 @@ class ERR_ContentSettings
|
||||
protected bool Read(string path, out map<string, int> values)
|
||||
{
|
||||
JsonLoadContext json = new JsonLoadContext();
|
||||
if (!json.LoadFromFile(path)) return false;
|
||||
return ERR_ProfileConfig.Load(path, json) && Parse(json, values);
|
||||
}
|
||||
static bool Parse(JsonLoadContext json, out map<string, int> values)
|
||||
{
|
||||
values = new map<string, int>();
|
||||
foreach (ERR_ContentOption option : ERR_ContentRegistry.Definition().Options)
|
||||
{
|
||||
@@ -65,6 +68,11 @@ class ERR_ContentSettings
|
||||
if (option == id) saved = value;
|
||||
json.WriteValue(option, saved);
|
||||
}
|
||||
if (m_Path == PATH)
|
||||
{
|
||||
if (!ERR_ProfileConfig.Save(PATH, json)) return false;
|
||||
m_Values.Set(id, value); return true;
|
||||
}
|
||||
map<string, int> checked;
|
||||
if (!json.SaveToFile(m_Path + ".tmp") || !Read(m_Path + ".tmp", checked) || !Matches(checked, id, value)) return false;
|
||||
map<string, int> previous;
|
||||
@@ -72,7 +80,7 @@ class ERR_ContentSettings
|
||||
if (hadPrevious && !FileIO.CopyFile(m_Path, m_Path + ".bak")) return false;
|
||||
if (!FileIO.CopyFile(m_Path + ".tmp", m_Path) || !Read(m_Path, checked) || !Matches(checked, id, value))
|
||||
{
|
||||
if (FileIO.FileExists(m_Path + ".bak")) FileIO.CopyFile(m_Path + ".bak", m_Path);
|
||||
if (ERR_ProfileConfig.Exists(m_Path + ".bak")) FileIO.CopyFile(m_Path + ".bak", m_Path);
|
||||
else if (!hadPrevious) FileIO.DeleteFile(m_Path);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
// Curated compatible weapon/ammunition pairs. No dependency is added for AA.
|
||||
class ERR_CrashLoot
|
||||
{
|
||||
static ResourceName Rifle(bool west)
|
||||
{
|
||||
if (west) return "{81EB948E6414BD6F}Prefabs/Weapons/Rifles/M14/Rifle_M21_ARTII.et";
|
||||
return "{6415B7923DE28C1B}Prefabs/Weapons/Rifles/SVD/Rifle_SVD_PSO.et";
|
||||
}
|
||||
static ResourceName Magazine(bool west)
|
||||
{
|
||||
if (west) return "{627255315038152A}Prefabs/Weapons/Magazines/Magazine_762x51_M14_20rnd_SpecialBall.et";
|
||||
return "{9CCB46C6EE632C1A}Prefabs/Weapons/Magazines/Magazine_762x54_SVD_10rnd_Sniper.et";
|
||||
}
|
||||
static ResourceName Scope(bool west)
|
||||
{
|
||||
if (west) return "{D2018EDB1BBF4C88}Prefabs/Weapons/Attachments/Optics/Optic_ARTII/Optic_ARTII.et";
|
||||
return "{C850A33226B8F9C1}Prefabs/Weapons/Attachments/Optics/Optic_PSO1/Optic_PSO1.et";
|
||||
}
|
||||
static ResourceName RPG() { return "{E8A55396050E1762}Prefabs/Weapons/Launchers/RPG7/Launcher_RPG7_PGO7.et"; }
|
||||
static ResourceName Rocket() { return "{609E216CBF8D0B68}Prefabs/Weapons/Ammo/Ammo_Rocket_PG7VL.et"; }
|
||||
static bool Allowed(ResourceName prefab)
|
||||
{
|
||||
if (!GetGame().ERR_ItemSelections().Allows(prefab)) return false;
|
||||
Resource resource = Resource.Load(prefab);
|
||||
return resource && resource.IsValid();
|
||||
}
|
||||
static bool RifleAllowed(bool west) { return Allowed(Rifle(west)) && Allowed(Magazine(west)) && Allowed(Scope(west)); }
|
||||
// Roll quality once per site, then quantities within that quality. Never reroll on opening.
|
||||
static void RandomPlan(array<ResourceName> items)
|
||||
{
|
||||
QualityPlan(Math.RandomInt(0, 100), items);
|
||||
}
|
||||
static void QualityPlan(int quality, array<ResourceName> items)
|
||||
{
|
||||
items.Clear();
|
||||
if (quality < 30)
|
||||
{
|
||||
// Poor salvage can be only a few pistol magazines, without a weapon.
|
||||
if (!Allowed(ERR_Supplies.Magazine())) return;
|
||||
if (Math.RandomInt(0, 2) == 1 && Allowed(ERR_Supplies.Pistol())) items.Insert(ERR_Supplies.Pistol());
|
||||
int magazines = Math.RandomInt(1, 4);
|
||||
for (int i = 0; i < magazines; i++) items.Insert(ERR_Supplies.Magazine());
|
||||
return;
|
||||
}
|
||||
if (quality < 65) { Plan(1, Math.RandomInt(1, 4), 0, 0, items); return; }
|
||||
if (quality < 90) { Plan(Math.RandomInt(2, 4), Math.RandomInt(2, 6), 15, 35, items); return; }
|
||||
Plan(Math.RandomInt(4, 7), Math.RandomInt(4, 9), 50, 75, items);
|
||||
}
|
||||
static void Plan(int rifles, int magazines, int aaChance, int rocketChance, array<ResourceName> items)
|
||||
{
|
||||
items.Clear();
|
||||
bool west = RifleAllowed(true), east = RifleAllowed(false);
|
||||
for (int i = 0; i < rifles && (west || east); i++)
|
||||
{
|
||||
bool useWest = west && (!east || Math.RandomInt(0, 2) == 0);
|
||||
items.Insert(Rifle(useWest));
|
||||
for (int j = 0; j < magazines; j++) items.Insert(Magazine(useWest));
|
||||
}
|
||||
if (Math.RandomInt(0, 100) < rocketChance && Allowed(RPG()) && Allowed(Rocket())
|
||||
&& Allowed("{E5E9DBBF3BFB88C6}Prefabs/Weapons/Attachments/Optics/Optic_PGO7/Optic_PGO7V3.et"))
|
||||
{
|
||||
items.Insert(RPG()); items.Insert(Rocket()); items.Insert(Rocket());
|
||||
}
|
||||
// The inspected WCS Stinger is disposable and already contains one missile.
|
||||
if (Math.RandomInt(0, 100) < aaChance && Allowed(ERR_EnemyLoadoutCatalog.FIM)
|
||||
&& Allowed("{517333DE6AADE624}Prefabs/Weapons/Ammo/WCS/Ammo_Missile_FIM92.et")) items.Insert(ERR_EnemyLoadoutCatalog.FIM);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// Owned static aftermath; no aircraft, crew, flight or inventory systems.
|
||||
class ERR_CrashSiteResources
|
||||
{
|
||||
static ResourceName SitePrefab() { return "{174211DDE2F6422B}Prefabs/ERR/CrashSite.et"; }
|
||||
static ResourceName WreckPrefab() { return "{486DDD0B811047EE}Prefabs/ERR/CrashWreck.et"; }
|
||||
static bool Bounds(out vector minimum, out vector maximum)
|
||||
{
|
||||
// Exact owned static wrapper measured in WORKBENCH on 2026-10-03.
|
||||
// Native bounds: (-1.43719, 0.512451, -8.79651) to (1.4325, 3.98193, 3.94385).
|
||||
// Expand by 1mm then round outward to millimetres; terrain survey adds its own clearance.
|
||||
minimum = "-1.439 0.511 -8.798";
|
||||
maximum = "1.434 3.983 3.945";
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
class ERR_CrashSite
|
||||
{
|
||||
vector Position;
|
||||
IEntity Wreck;
|
||||
IEntity Smoke;
|
||||
IEntity Crate;
|
||||
ref array<IEntity> Stock = {};
|
||||
bool Ready;
|
||||
}
|
||||
|
||||
// Finite server-owned scenery. Small bounded steps never block mission preparation.
|
||||
class ERR_CrashSites
|
||||
{
|
||||
protected ERR_GameMode m_Mode;
|
||||
protected ref array<ref ERR_CrashSite> m_Sites = {};
|
||||
protected ref ERR_CampTerrain m_Terrain = new ERR_CampTerrain();
|
||||
protected ref array<ResourceName> m_Loot = {};
|
||||
protected ERR_CrashSite m_Pending;
|
||||
protected InventoryStorageManagerComponent m_Inventory;
|
||||
protected IEntity m_Item;
|
||||
protected bool m_Accepted, m_ItemIssued, m_Started, m_Stopped;
|
||||
protected int m_Target, m_Consumed, m_Attempts, m_HeliChance, m_LootMode, m_Rifles, m_Magazines, m_AA, m_Rockets;
|
||||
protected int m_ItemIndex, m_ItemTicks, m_Skipped;
|
||||
void ERR_CrashSites(ERR_GameMode mode) { m_Mode = mode; }
|
||||
int Count() { return m_Sites.Count(); }
|
||||
ERR_CrashSite At(int index) { return m_Sites[index]; }
|
||||
string Status()
|
||||
{
|
||||
return string.Format("Crash sites: %1/%2 created; %3 unsuitable slots skipped. Finite loot; no refills.", Count(), m_Target, m_Skipped);
|
||||
}
|
||||
void Start()
|
||||
{
|
||||
if (m_Started || m_Stopped || !Replication.IsServer() || !m_Mode) return;
|
||||
m_Started = true;
|
||||
ERR_Settings settings = m_Mode.Settings();
|
||||
m_LootMode = settings.Get("crashLootMode");
|
||||
m_Target = settings.Get("crashSiteCount"); m_HeliChance = settings.Get("crashHelicopterChancePercent");
|
||||
m_Rifles = settings.Get("crashRifleCount"); m_Magazines = settings.Get("crashMagazineCount");
|
||||
m_AA = settings.Get("crashAntiAirChancePercent"); m_Rockets = settings.Get("crashRocketChancePercent");
|
||||
if (m_Target > 0) GetGame().GetCallqueue().CallLater(Step, 250, true);
|
||||
}
|
||||
protected bool Ready()
|
||||
{
|
||||
if (!m_Mode.Sites() || !GetGame().GetWorldEntity()) return false;
|
||||
ERR_ScenarioComponent scenario = ERR_ScenarioComponent.Cast(m_Mode.FindComponent(ERR_ScenarioComponent));
|
||||
return scenario && !scenario.BackgroundPending();
|
||||
}
|
||||
protected bool Separated(vector point)
|
||||
{
|
||||
foreach (ERR_CrashSite site : m_Sites) if (vector.DistanceXZ(site.Position, point) < 600) return false;
|
||||
if (m_Mode.Sites())
|
||||
for (int i = 0; i < m_Mode.Sites().Count(); i++)
|
||||
{
|
||||
ERR_SiteRuntime other = m_Mode.Sites().At(i);
|
||||
if (other && other.Definition && vector.DistanceXZ(other.Definition.Position, point) < 250) return false;
|
||||
}
|
||||
array<int> players = {}; GetGame().GetPlayerManager().GetPlayers(players);
|
||||
foreach (int id : players)
|
||||
{
|
||||
IEntity actor = GetGame().GetPlayerManager().GetPlayerControlledEntity(id);
|
||||
if (actor && vector.DistanceXZ(actor.GetOrigin(), point) < 400) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
protected bool Survey(out vector point, out float heading)
|
||||
{
|
||||
vector low, high; GetGame().GetWorldEntity().GetWorldBounds(low, high);
|
||||
point = Vector(Math.RandomFloat(low[0], high[0]), 0, Math.RandomFloat(low[2], high[2]));
|
||||
heading = Math.RandomFloat(0, 360);
|
||||
if (!Separated(point)) return false;
|
||||
SCR_AIWorld ai = SCR_AIWorld.Cast(GetGame().GetAIWorld());
|
||||
if (!ai || !ai.GetRoadNetworkManager()) return false;
|
||||
BaseRoad road; float distance;
|
||||
ai.GetRoadNetworkManager().GetClosestRoad(point, road, distance, skipNavlinks: true);
|
||||
if (road && distance < 20) return false;
|
||||
float floor;
|
||||
// Includes UH-1 tail (-8.8m), armor, smoke and the crate at x=6m plus access.
|
||||
if (!m_Terrain.Clear(point, heading, 11, 0.6, floor, 5)) return false;
|
||||
point = ERR_Actors.Ground(point);
|
||||
return true;
|
||||
}
|
||||
protected IEntity Spawn(ResourceName resource, vector position, vector angles)
|
||||
{
|
||||
IEntity entity = ERR_Actors.Spawn(resource, position, angles);
|
||||
if (entity) m_Mode.Own(entity);
|
||||
return entity;
|
||||
}
|
||||
protected bool Create(vector point, float heading)
|
||||
{
|
||||
if (m_LootMode == 1) ERR_CrashLoot.RandomPlan(m_Loot);
|
||||
else ERR_CrashLoot.Plan(m_Rifles, m_Magazines, m_AA, m_Rockets, m_Loot);
|
||||
if (m_Loot.IsEmpty() && m_LootMode == 0) return false;
|
||||
ERR_CrashSite site = new ERR_CrashSite(); site.Position = point;
|
||||
ResourceName wreck = "{56C7BD958478A96D}Prefabs/ERR/CrashArmorWreck.et";
|
||||
if (Math.RandomInt(0, 100) < m_HeliChance) wreck = ERR_CrashSiteResources.WreckPrefab();
|
||||
vector wreckPoint = point;
|
||||
if (wreck == ERR_CrashSiteResources.WreckPrefab())
|
||||
{
|
||||
vector minimum, maximum;
|
||||
if (ERR_CrashSiteResources.Bounds(minimum, maximum)) wreckPoint[1] = wreckPoint[1] - minimum[1];
|
||||
}
|
||||
site.Wreck = Spawn(wreck, wreckPoint, Vector(heading, 0, 0));
|
||||
vector cratePoint = ERR_Actors.Ground(point + ERR_CampTerrain.Rotate("6 0 0", heading));
|
||||
site.Crate = Spawn("{CD7E33406E1ACA1D}Prefabs/ERR/CrashLootCrate.et", cratePoint + "0 0.1 0", Vector(heading, 0, 0));
|
||||
site.Smoke = Spawn("{2608501780FEE1EA}Prefabs/ERR/CrashLootSmoke.et", point + "0 1 0", "0 0 0");
|
||||
if (site.Crate) m_Inventory = InventoryStorageManagerComponent.Cast(site.Crate.FindComponent(InventoryStorageManagerComponent));
|
||||
BaseInventoryStorageComponent storage;
|
||||
if (site.Crate) storage = BaseInventoryStorageComponent.Cast(site.Crate.FindComponent(BaseInventoryStorageComponent));
|
||||
if (!site.Wreck || !site.Crate || !site.Smoke || !m_Inventory || !storage)
|
||||
{
|
||||
ERR_Actors.DeleteSpawned(site.Smoke); ERR_Actors.DeleteSpawned(site.Crate); ERR_Actors.DeleteSpawned(site.Wreck);
|
||||
m_Inventory = null; return false;
|
||||
}
|
||||
ParticleEffectEntity smoke = ParticleEffectEntity.Cast(site.Smoke);
|
||||
if (smoke) { smoke.SetDeleteWhenStopped(false); smoke.Play(); }
|
||||
m_Inventory.AddStorage(storage);
|
||||
m_Sites.Insert(site); m_Pending = site; m_ItemIndex = 0;
|
||||
SCR_AIWorld ai = SCR_AIWorld.Cast(GetGame().GetAIWorld());
|
||||
if (ai) ai.RequestNavmeshRebuildEntity(site.Wreck);
|
||||
return true;
|
||||
}
|
||||
protected void Fill()
|
||||
{
|
||||
if (!m_Pending.Crate || !m_Inventory) { Finish(); return; }
|
||||
if (m_Item)
|
||||
{
|
||||
array<IEntity> items = {}; m_Inventory.GetItems(items);
|
||||
InventoryItemComponent itemComponent = InventoryItemComponent.Cast(m_Item.FindComponent(InventoryItemComponent));
|
||||
// A player may take the item between insertion and this confirmation tick.
|
||||
// Any attached inventory slot consumes the grant; never steal it back.
|
||||
if (items.Contains(m_Item) || (itemComponent && itemComponent.GetParentSlot()))
|
||||
{
|
||||
m_Pending.Stock.Insert(m_Item); m_Item = null; m_ItemIssued = false; m_ItemIndex++; m_ItemTicks = 0; m_Accepted = false;
|
||||
return;
|
||||
}
|
||||
m_ItemTicks++;
|
||||
if (m_ItemTicks > 12)
|
||||
{
|
||||
Print("Reintroduced [crash sites]: inventory rejected " + m_Loot[m_ItemIndex], LogLevel.WARNING);
|
||||
ERR_Actors.DeleteSpawned(m_Item); m_Item = null; m_ItemIssued = false; m_ItemIndex++; m_ItemTicks = 0; m_Accepted = false; return;
|
||||
}
|
||||
if (!m_Accepted) m_Accepted = m_Inventory.TryInsertItem(m_Item);
|
||||
return;
|
||||
}
|
||||
// A destroyed/consumed entity is still a spent grant, not a spawn retry.
|
||||
if (m_ItemIssued) { m_ItemIssued = false; m_ItemIndex++; m_ItemTicks = 0; m_Accepted = false; return; }
|
||||
if (m_ItemIndex >= m_Loot.Count()) { Finish(); return; }
|
||||
ResourceName resource = m_Loot[m_ItemIndex];
|
||||
if (!ERR_CrashLoot.Allowed(resource)) { m_ItemIndex++; return; }
|
||||
m_ItemIssued = true;
|
||||
m_Item = Spawn(resource, m_Pending.Crate.GetOrigin() + "0 0.4 0", "0 0 0");
|
||||
if (!m_Item) { m_ItemIssued = false; m_ItemIndex++; }
|
||||
}
|
||||
protected void Finish()
|
||||
{
|
||||
if (m_Item)
|
||||
{
|
||||
InventoryItemComponent itemComponent = InventoryItemComponent.Cast(m_Item.FindComponent(InventoryItemComponent));
|
||||
if (!itemComponent || !itemComponent.GetParentSlot()) ERR_Actors.DeleteSpawned(m_Item);
|
||||
}
|
||||
m_Item = null; m_ItemIssued = false;
|
||||
if (m_Pending) m_Pending.Ready = true;
|
||||
m_Pending = null; m_Inventory = null; m_Loot.Clear();
|
||||
}
|
||||
protected void Step()
|
||||
{
|
||||
if (m_Stopped || !Replication.IsServer() || !m_Mode || m_Mode != ERR_GameMode.Get() || ERR_MissionRules.Terminal(m_Mode.Phase())) { Stop(); return; }
|
||||
if (!m_Mode.Active()) return;
|
||||
if (m_Pending) { Fill(); return; }
|
||||
if (m_Consumed >= m_Target) { Stop(); return; }
|
||||
if (!Ready()) return;
|
||||
int began = System.GetTickCount();
|
||||
for (int sample = 0; sample < 8; sample++)
|
||||
{
|
||||
vector point; float heading;
|
||||
m_Attempts++;
|
||||
if (Survey(point, heading))
|
||||
{
|
||||
m_Consumed++; m_Attempts = 0;
|
||||
if (!Create(point, heading)) m_Skipped++;
|
||||
return;
|
||||
}
|
||||
if (m_Attempts >= 2000)
|
||||
{
|
||||
m_Consumed++; m_Skipped++; m_Attempts = 0;
|
||||
Print("Reintroduced [crash sites]: skipped slot after bounded terrain search", LogLevel.WARNING);
|
||||
return;
|
||||
}
|
||||
if (System.GetTickCount() - began >= 4) return;
|
||||
}
|
||||
}
|
||||
void Stop()
|
||||
{
|
||||
if (m_Stopped) return;
|
||||
m_Stopped = true;
|
||||
if (GetGame()) GetGame().GetCallqueue().Remove(Step);
|
||||
Finish();
|
||||
// All scenery and issued stock remain in the round's owned-entity cleanup.
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,19 @@
|
||||
// A first clue identifies a stable search square, not the exact objective.
|
||||
// Stable grid centers prevent repeated updates from triangulating the site.
|
||||
// Normal clues use stable search squares to prevent triangulating the site.
|
||||
// The optional anonymous-site mode intentionally exposes positions, never types.
|
||||
class ERR_DiscoveryMapRules
|
||||
{
|
||||
// -1 hidden, 0 anonymous exact position, 1 approximate clue, 2 identified.
|
||||
static int DisplayKnowledge(int knowledge, bool prepared, bool prison, bool showAll)
|
||||
{
|
||||
if (prison || !prepared) return -1;
|
||||
if (knowledge >= 2) return 2;
|
||||
if (showAll) return 0;
|
||||
if (knowledge == 1) return 1;
|
||||
return -1;
|
||||
}
|
||||
static int Coordinate(float coordinate, int knowledge)
|
||||
{
|
||||
if (knowledge >= 2) return Math.Round(coordinate);
|
||||
if (knowledge == 0 || knowledge >= 2) return Math.Round(coordinate);
|
||||
return Math.Floor(coordinate / 500.0) * 500 + 250;
|
||||
}
|
||||
}
|
||||
@@ -15,6 +24,7 @@ class ERR_DiscoveryMapRules
|
||||
class ERR_DiscoveryMarker : SCR_MapMarkerBase
|
||||
{
|
||||
protected string m_Icon = "supplies";
|
||||
protected ref ERR_ObjectiveMapPresentation m_ERRPresentation;
|
||||
static string Icon(ERR_LocationKind kind, string id)
|
||||
{
|
||||
if (kind == ERR_LocationKind.EXTRACTION) return "helicopters";
|
||||
@@ -22,6 +32,7 @@ class ERR_DiscoveryMarker : SCR_MapMarkerBase
|
||||
if (kind == ERR_LocationKind.MORTAR) return "artilleryFireMission";
|
||||
if (kind == ERR_LocationKind.OUTPOST) return "building";
|
||||
if (kind == ERR_LocationKind.HOSPITAL || id.Contains("medical")) return "medical";
|
||||
if (kind == ERR_LocationKind.PRISON) return "prison";
|
||||
return "supplies";
|
||||
}
|
||||
void SetLocationIcon(ERR_LocationKind kind, string id, int knowledge = 2)
|
||||
@@ -29,27 +40,37 @@ class ERR_DiscoveryMarker : SCR_MapMarkerBase
|
||||
m_Icon = "mark-question";
|
||||
if (knowledge >= 2) m_Icon = Icon(kind, id);
|
||||
ApplyIcon();
|
||||
RefreshPresentation();
|
||||
}
|
||||
protected void ApplyIcon()
|
||||
{
|
||||
SCR_MapMarkerWidgetComponent widget = GetMarkerComponent();
|
||||
if (!GetRootWidget() || !widget) return;
|
||||
if (m_Icon == "mark-question")
|
||||
{
|
||||
widget.SetImage("{E23427CAC80DA8B7}UI/Textures/Icons/icons_mapMarkersUI.imageset", m_Icon);
|
||||
widget.SetGlowImage("{67B3A6DC2D712B52}UI/Textures/Icons/icons_mapMarkersUI-glow.imageset", m_Icon);
|
||||
}
|
||||
else
|
||||
{
|
||||
widget.SetImage("{3262679C50EF4F01}UI/Textures/Icons/icons_wrapperUI.imageset", m_Icon);
|
||||
widget.SetGlowImage("{00FE3DBDFD15227B}UI/Textures/Icons/icons_wrapperUI-glow.imageset", m_Icon);
|
||||
}
|
||||
widget.SetColor(Color.FromSRGBA(35, 45, 50, 255));
|
||||
widget.SetImage("{E0248EBF90751BA5}UI/Textures/ERR/SiteMarkers.imageset", m_Icon);
|
||||
widget.SetGlowImage("{E0248EBF90751BA5}UI/Textures/ERR/SiteMarkers.imageset", m_Icon);
|
||||
// Preserve the atlas colours instead of tinting the violet disc dark grey.
|
||||
widget.SetColor(Color.FromSRGBA(255, 255, 255, 255));
|
||||
}
|
||||
// Production authored objectives use a separate map UI element. They never
|
||||
// enter the player-annotation registry or change its content restrictions.
|
||||
void StartPresentation()
|
||||
{
|
||||
if (!m_ERRPresentation) m_ERRPresentation = new ERR_ObjectiveMapPresentation();
|
||||
RefreshPresentation();
|
||||
m_ERRPresentation.Start();
|
||||
}
|
||||
void RefreshPresentation()
|
||||
{
|
||||
if (!m_ERRPresentation) return;
|
||||
int point[2];
|
||||
GetWorldPos(point);
|
||||
m_ERRPresentation.SetData(Vector(point[0], 0, point[1]), GetCustomText(), m_Icon);
|
||||
}
|
||||
void ERR_DiscoveryMarker() { SetCanBeRemovedByOwner(false); }
|
||||
void SetDiscoveryPosition(vector position, int knowledge)
|
||||
{
|
||||
SetWorldPos(ERR_DiscoveryMapRules.Coordinate(position[0], knowledge), ERR_DiscoveryMapRules.Coordinate(position[2], knowledge));
|
||||
RefreshPresentation();
|
||||
}
|
||||
override void OnCreateMarker(bool skipProfanityFilter = false)
|
||||
{
|
||||
@@ -61,6 +82,8 @@ class ERR_DiscoveryMarker : SCR_MapMarkerBase
|
||||
}
|
||||
override void OnDelete()
|
||||
{
|
||||
if (m_ERRPresentation) m_ERRPresentation.Stop();
|
||||
m_ERRPresentation = null;
|
||||
SCR_MapEntity.GetOnMapClose().Remove(OnMapClosed);
|
||||
SCR_MapEntity.GetOnLayerChanged().Remove(OnMapLayerChanged);
|
||||
super.OnDelete();
|
||||
@@ -70,6 +93,7 @@ class ERR_DiscoveryMarker : SCR_MapMarkerBase
|
||||
override void SetCustomText(string text)
|
||||
{
|
||||
super.SetCustomText(text);
|
||||
RefreshPresentation();
|
||||
// Native SetCustomText updates data only; an already-open map needs its
|
||||
// existing widget updated too. Closed maps use the saved text on creation.
|
||||
SCR_MapMarkerWidgetComponent widget = GetMarkerComponent();
|
||||
@@ -81,6 +105,10 @@ class ERR_DiscoveryMarker : SCR_MapMarkerBase
|
||||
// must also leave the disabled list or map movement can resurrect it later.
|
||||
modded class SCR_MapMarkerManagerComponent
|
||||
{
|
||||
void ERR_InsertDiscoveryMarker(ERR_DiscoveryMarker marker)
|
||||
{
|
||||
if (marker) marker.StartPresentation();
|
||||
}
|
||||
void ERR_RemoveDiscoveryMarker(ERR_DiscoveryMarker marker)
|
||||
{
|
||||
if (!marker || marker.GetMarkerID() != -1) return;
|
||||
@@ -90,12 +118,12 @@ modded class SCR_MapMarkerManagerComponent
|
||||
}
|
||||
}
|
||||
|
||||
// Read replicated discovery locally. A server-wide marker list never leaks
|
||||
// locations which have not been recovered through gameplay.
|
||||
// Read replicated discovery and the administrator-selected visibility locally.
|
||||
// Anonymous markers never expose a site label or type before discovery.
|
||||
modded class ERR_LocationComponent
|
||||
{
|
||||
protected ref ERR_DiscoveryMarker m_ERRMarker;
|
||||
protected int m_ERRShown;
|
||||
protected int m_ERRShown = -1;
|
||||
protected string m_ERRShownLabel;
|
||||
|
||||
protected override void EOnInit(IEntity owner)
|
||||
@@ -107,23 +135,28 @@ modded class ERR_LocationComponent
|
||||
protected void ERR_UpdateMarker()
|
||||
{
|
||||
if (!GetGame().GetPlayerController()) return;
|
||||
if (Kind() == ERR_LocationKind.PRISON || Knowledge() < 1)
|
||||
ERR_GameMode mode = ERR_GameMode.Get();
|
||||
bool showAll = mode && mode.ShowAllSites();
|
||||
int shown = ERR_DiscoveryMapRules.DisplayKnowledge(Knowledge(), Prepared(), Kind() == ERR_LocationKind.PRISON, showAll);
|
||||
if (shown < 0)
|
||||
{
|
||||
SCR_MapMarkerManagerComponent oldManager = SCR_MapMarkerManagerComponent.GetInstance();
|
||||
if (m_ERRMarker && oldManager) oldManager.ERR_RemoveDiscoveryMarker(m_ERRMarker);
|
||||
else if (m_ERRMarker) m_ERRMarker.OnDelete();
|
||||
m_ERRMarker = null; m_ERRShown = 0; m_ERRShownLabel = "";
|
||||
m_ERRMarker = null; m_ERRShown = -1; m_ERRShownLabel = "";
|
||||
return;
|
||||
}
|
||||
if (Knowledge() == m_ERRShown && Label() == m_ERRShownLabel) return;
|
||||
if (m_ERRMarker) m_ERRMarker.RefreshPresentation();
|
||||
if (m_ERRMarker && shown == m_ERRShown && Label() == m_ERRShownLabel) return;
|
||||
SCR_MapMarkerManagerComponent manager = SCR_MapMarkerManagerComponent.GetInstance();
|
||||
if (!manager || !manager.GetMarkerConfig()) return;
|
||||
string label = "Unknown location (500 m area)";
|
||||
if (Knowledge() == 2)
|
||||
if (shown == 0) label = "Unknown site";
|
||||
if (shown == 2)
|
||||
{
|
||||
if (!Label().IsEmpty()) label = Label();
|
||||
else if (Kind() == ERR_LocationKind.RADIO) label = "Radio station";
|
||||
else if (Kind() == ERR_LocationKind.EXTRACTION) label = "Initial helicopter pickup site";
|
||||
else if (Kind() == ERR_LocationKind.EXTRACTION) label = "Extraction pickup - throw smoke here";
|
||||
else if (Kind() == ERR_LocationKind.DEPOT) label = "Supply cache";
|
||||
else if (Kind() == ERR_LocationKind.MORTAR) label = "Mortar site";
|
||||
else label = Id();
|
||||
@@ -132,20 +165,18 @@ modded class ERR_LocationComponent
|
||||
{
|
||||
m_ERRMarker = new ERR_DiscoveryMarker();
|
||||
m_ERRMarker.SetType(SCR_EMapMarkerType.PLACED_CUSTOM);
|
||||
m_ERRMarker.SetDiscoveryPosition(Position(), Knowledge());
|
||||
m_ERRMarker.SetDiscoveryPosition(Position(), shown);
|
||||
m_ERRMarker.SetMarkerConfigID(0);
|
||||
m_ERRMarker.SetLocationIcon(Kind(), Id(), Knowledge());
|
||||
m_ERRMarker.SetLocationIcon(Kind(), Id(), shown);
|
||||
m_ERRMarker.SetCustomText(label);
|
||||
manager.InsertStaticMarker(m_ERRMarker, true);
|
||||
manager.ERR_InsertDiscoveryMarker(m_ERRMarker);
|
||||
}
|
||||
m_ERRMarker.SetDiscoveryPosition(Position(), Knowledge());
|
||||
m_ERRMarker.SetLocationIcon(Kind(), Id(), Knowledge());
|
||||
m_ERRMarker.SetDiscoveryPosition(Position(), shown);
|
||||
m_ERRMarker.SetLocationIcon(Kind(), Id(), shown);
|
||||
m_ERRMarker.SetCustomText(label);
|
||||
// Reactivate a previously culled rough marker if the exact point moves
|
||||
// into the open map's view. Native update performs normal visibility checks.
|
||||
SCR_MapEntity mapEntity = SCR_MapEntity.GetMapInstance();
|
||||
if (mapEntity && mapEntity.IsOpen()) manager.SetStaticMarkerDisabled(m_ERRMarker, false);
|
||||
m_ERRShown = Knowledge();
|
||||
// The objective presentation performs its own normal viewport culling.
|
||||
m_ERRMarker.RefreshPresentation();
|
||||
m_ERRShown = shown;
|
||||
m_ERRShownLabel = Label();
|
||||
}
|
||||
protected override void OnDelete(IEntity owner)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user