Compare commits
31
Commits
ac285b5c97
...
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 |
@@ -6,6 +6,11 @@ __pycache__/
|
|||||||
.local/
|
.local/
|
||||||
profiles/
|
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.
|
# Preserve exact raw dedicated diagnostic evidence, outside release addons.
|
||||||
!docs/evidence/**/*.log
|
!docs/evidence/**/*.log
|
||||||
!docs/evidence/**/resourceDatabase.rdb
|
!docs/evidence/**/resourceDatabase.rdb
|
||||||
|
|||||||
@@ -11,6 +11,23 @@ server, deployment, Git push status, operational disclaimers or footers. Do not
|
|||||||
backfill old changes unless requested. Internal workflow-only changes need no
|
backfill old changes unless requested. Internal workflow-only changes need no
|
||||||
announcement.
|
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
|
After a confirmed successful Workbench Workshop upload, run
|
||||||
`python3 tools/workshop_changelog.py --published` before committing the changelog.
|
`python3 tools/workshop_changelog.py --published` before committing the changelog.
|
||||||
This reads the local publishing manifest and records the version in the changelog
|
This reads the local publishing manifest and records the version in the changelog
|
||||||
|
|||||||
+165
-1
@@ -1,6 +1,170 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
Latest published Workshop version: **0.0.118**
|
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
|
## 2026-10-21 — Recon drones, body burial and spectator camera
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ 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 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 current working tree passes **326 registered regression programs** after the [5 October validation repairs](docs/evidence/review-audit-2026-10-05/review.json). These offline checks do not establish native gameplay or multiplayer correctness.
|
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 [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.
|
||||||
|
|
||||||
@@ -48,7 +48,9 @@ Register only this project and its external dependencies in Workbench. Remove st
|
|||||||
AI/Intel/Map/Chat project entries if you previously added them. RHS: Status Quo,
|
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,
|
WCS Armaments and WCS BMP-1 (including their dependencies) remain separate,
|
||||||
author-supplied mods; see [external dependencies](docs/external-addons.md).
|
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
|
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.
|
seconds. This removes the corpse and its remaining equipment; loot it first.
|
||||||
@@ -90,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).
|
Random field hospitals and camps, their permanent/mobile guard split, finite supplies and authoring options are documented in [Field sites](docs/field-sites.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).
|
Town population, configurable factory guards and safe tower/roof posts: [town and object garrisons](docs/town-garrisons.md).
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
MenuManager {
|
MenuManager {
|
||||||
MenuPresets {
|
MenuPresets {
|
||||||
|
MenuPreset ERR_PreparationFailure {
|
||||||
|
Layout "{28DD8E27B1CF40C5}UI/layouts/ERR/PreparationFailure.layout"
|
||||||
|
ActionContext "MenuContext"
|
||||||
|
Class "ERR_PreparationFailureMenu"
|
||||||
|
}
|
||||||
MenuPreset ERR_AdminPanel {
|
MenuPreset ERR_AdminPanel {
|
||||||
Layout "{AE761C0439D25FB8}UI/layouts/ERR/AdminMenu.layout"
|
Layout "{AE761C0439D25FB8}UI/layouts/ERR/AdminMenu.layout"
|
||||||
ActionContext "MenuContext"
|
ActionContext "MenuContext"
|
||||||
|
|||||||
@@ -70,12 +70,13 @@ ERR_ScenarioDefinition {
|
|||||||
}
|
}
|
||||||
ERR_SiteDefinition {
|
ERR_SiteDefinition {
|
||||||
Id "everon-extraction-1"
|
Id "everon-extraction-1"
|
||||||
Label "Initial helicopter pickup site"
|
Label "Extraction landing zone"
|
||||||
Kind 2
|
Kind 2
|
||||||
Prefab "{033503110AC34EAC}Prefabs/ERR/Extraction.et"
|
Prefab "{033503110AC34EAC}Prefabs/ERR/Extraction.et"
|
||||||
Position 4800.953 0 11487.412
|
Position 4800.953 0 11487.412
|
||||||
AircraftOffset 0 0 0
|
AircraftOffset 0 0 0
|
||||||
RequireAirfield 1
|
RequireAirfield 0
|
||||||
|
RandomClear 1
|
||||||
Guarded 1
|
Guarded 1
|
||||||
GuardOffset 30 0 0
|
GuardOffset 30 0 0
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ SCR_GameOverScreenConfig : "{651583FFFB7FC765}Configs/GameOverScreen/BaseGameOve
|
|||||||
m_aGameOverScreens {
|
m_aGameOverScreens {
|
||||||
ERR_EscapeGameOverScreenInfo "{6B193DC8DDC63507}" {
|
ERR_EscapeGameOverScreenInfo "{6B193DC8DDC63507}" {
|
||||||
m_GameOverScreenId VICTORY
|
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_OptionalParams SCR_BaseGameOverScreenInfoOptional "{4984A5C1BFEFC04E}" {
|
||||||
m_sTitle "Escape complete"
|
m_sTitle "Escape complete"
|
||||||
m_sAudioOneShot "SOUND_ONVICTORY"
|
m_sAudioOneShot "SOUND_ONVICTORY"
|
||||||
@@ -10,7 +10,7 @@ SCR_GameOverScreenConfig : "{651583FFFB7FC765}Configs/GameOverScreen/BaseGameOve
|
|||||||
}
|
}
|
||||||
ERR_FailureGameOverScreenInfo "{E66F29A7A8AD7438}" {
|
ERR_FailureGameOverScreenInfo "{E66F29A7A8AD7438}" {
|
||||||
m_GameOverScreenId LOOSE
|
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_OptionalParams SCR_BaseGameOverScreenInfoOptional "{3DBF0BDE715945FA}" {
|
||||||
m_sTitle "Escape failed"
|
m_sTitle "Escape failed"
|
||||||
m_sDebriefing "No surviving prisoners can complete the escape, or the extraction helicopter has been lost."
|
m_sDebriefing "No surviving prisoners can complete the escape, or the extraction helicopter has been lost."
|
||||||
|
|||||||
@@ -1,5 +1,16 @@
|
|||||||
ActionManager : "{795184CF9AD764DB}Configs/System/chimeraInputCommon.conf" {
|
ActionManager : "{795184CF9AD764DB}Configs/System/chimeraInputCommon.conf" {
|
||||||
Contexts {
|
Contexts {
|
||||||
|
ActionContext ERR_PassengerBrakeContext {
|
||||||
|
Priority 60
|
||||||
|
Flags 0xa 0
|
||||||
|
Actions {
|
||||||
|
Action ERR_PassengerBrake {
|
||||||
|
InputSource InputSourceValue "{A7B19F416E541030}" {
|
||||||
|
Input "keyboard:KC_SPACE"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
ActionContext ERR_FieldMapContext {
|
ActionContext ERR_FieldMapContext {
|
||||||
Priority 60
|
Priority 60
|
||||||
Flags 0xa 0
|
Flags 0xa 0
|
||||||
|
|||||||
@@ -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 { }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 {}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,6 +11,6 @@ ERR_GameMode : "{0F307326459A1395}Prefabs/MP/Modes/GameMode_Base.et" {
|
|||||||
m_SpawnLogic ERR_SpawnLogic "{79AD722B2EFD595F}" {}
|
m_SpawnLogic ERR_SpawnLogic "{79AD722B2EFD595F}" {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
m_fAutoReloadTime 10
|
m_fAutoReloadTime 120
|
||||||
m_bUseSpawnPreload 1
|
m_bUseSpawnPreload 1
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,6 +78,15 @@ class ERR_Actors
|
|||||||
return position;
|
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)
|
static void Players(out array<IEntity> players, bool conscious = false)
|
||||||
{
|
{
|
||||||
players.Clear();
|
players.Clear();
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ class ERR_AdminMenu : ChimeraMenuBase
|
|||||||
protected bool m_ScrollToEnd;
|
protected bool m_ScrollToEnd;
|
||||||
protected int m_LogCursor = -1;
|
protected int m_LogCursor = -1;
|
||||||
|
|
||||||
|
protected void OpenVehicleAddon(string addon) {}
|
||||||
static ERR_AdminMenu GetOpenMenu()
|
static ERR_AdminMenu GetOpenMenu()
|
||||||
{
|
{
|
||||||
if (!GetGame() || !GetGame().GetMenuManager()) return null;
|
if (!GetGame() || !GetGame().GetMenuManager()) return null;
|
||||||
@@ -46,13 +47,17 @@ class ERR_AdminMenu : ChimeraMenuBase
|
|||||||
m_Controller = SCR_PlayerController.Cast(GetGame().GetPlayerController());
|
m_Controller = SCR_PlayerController.Cast(GetGame().GetPlayerController());
|
||||||
m_Mode = ERR_GameMode.Cast(GetGame().GetGameMode());
|
m_Mode = ERR_GameMode.Cast(GetGame().GetGameMode());
|
||||||
if (!m_Controller) { Close(); return; }
|
if (!m_Controller) { Close(); return; }
|
||||||
array<string> names = {"SitesOn", "SitesOff", "PlaneOn", "PlaneOff", "Close", "PauseJournal", "JournalDetails", "LatestJournal", "KeepWeaponOn", "KeepWeaponOff", "SelfEpiOn", "SelfEpiOff", "IntelSummary", "RerollIntel", "RandomDay_startTimeMinutes", "Default_viewDistanceMetres", "RestartEmptyOn", "RestartEmptyOff", "RecommendedPlane", "RecommendedWeapons", "RecommendedDrones"};
|
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 mainButton : ERR_AdminMenuPages.MainButtons()) names.Insert(mainButton);
|
||||||
foreach (string pageButton : Navigation()) names.Insert(pageButton);
|
foreach (string pageButton : Navigation()) names.Insert(pageButton);
|
||||||
for (int density = 1; density <= 4; density++) names.Insert("GuardEvery_" + density.ToString());
|
|
||||||
for (int policy = 0; policy <= 2; policy++) names.Insert("GuardHQ_" + policy.ToString());
|
|
||||||
foreach (string key : ERR_AdminMenuFields.Keys())
|
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("Apply_" + key);
|
||||||
if (ERR_Settings.Randomizable(key)) names.Insert("Random_" + key);
|
if (ERR_Settings.Randomizable(key)) names.Insert("Random_" + key);
|
||||||
}
|
}
|
||||||
@@ -69,6 +74,7 @@ class ERR_AdminMenu : ChimeraMenuBase
|
|||||||
button.m_OnClicked.Insert(OnButton);
|
button.m_OnClicked.Insert(OnButton);
|
||||||
m_Buttons.Insert(button);
|
m_Buttons.Insert(button);
|
||||||
}
|
}
|
||||||
|
ERR_AdminMenuView.FitPanel(GetRootWidget());
|
||||||
m_Ready = false; m_Age = 0; m_Poll = 0;
|
m_Ready = false; m_Age = 0; m_Poll = 0;
|
||||||
GetGame().GetInputManager().AddActionListener(UIConstants.MENU_ACTION_BACK, EActionTrigger.PRESSED, OnBack);
|
GetGame().GetInputManager().AddActionListener(UIConstants.MENU_ACTION_BACK, EActionTrigger.PRESSED, OnBack);
|
||||||
m_Bound = true;
|
m_Bound = true;
|
||||||
@@ -89,6 +95,7 @@ class ERR_AdminMenu : ChimeraMenuBase
|
|||||||
override void OnMenuUpdate(float tDelta)
|
override void OnMenuUpdate(float tDelta)
|
||||||
{
|
{
|
||||||
super.OnMenuUpdate(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; }
|
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_Age += tDelta; m_Poll += tDelta;
|
||||||
m_ClickDelay = Math.Max(0, m_ClickDelay - tDelta);
|
m_ClickDelay = Math.Max(0, m_ClickDelay - tDelta);
|
||||||
@@ -130,7 +137,9 @@ class ERR_AdminMenu : ChimeraMenuBase
|
|||||||
protected void OnBack()
|
protected void OnBack()
|
||||||
{
|
{
|
||||||
if (!IsFocused()) return;
|
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());
|
SCR_EditBoxComponent edit = SCR_EditBoxComponent.GetEditBoxComponent(key, GetRootWidget());
|
||||||
if (edit && edit.IsInWriteMode()) return;
|
if (edit && edit.IsInWriteMode()) return;
|
||||||
@@ -156,8 +165,13 @@ class ERR_AdminMenu : ChimeraMenuBase
|
|||||||
protected int m_SelfEpiState = -1;
|
protected int m_SelfEpiState = -1;
|
||||||
protected int m_SitesState = -1;
|
protected int m_SitesState = -1;
|
||||||
protected int m_RestartEmptyState = -1;
|
protected int m_RestartEmptyState = -1;
|
||||||
|
protected int m_TelemetryState = -1;
|
||||||
|
protected int m_CrashLootState = -1;
|
||||||
protected void UpdateActions()
|
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(), "SitesOn", "SitesOff", m_SitesState);
|
||||||
ERR_SelectionStyle.Pair(GetRootWidget(), "SelfEpiOn", "SelfEpiOff", m_SelfEpiState);
|
ERR_SelectionStyle.Pair(GetRootWidget(), "SelfEpiOn", "SelfEpiOff", m_SelfEpiState);
|
||||||
ERR_SelectionStyle.Pair(GetRootWidget(), "KeepWeaponOn", "KeepWeaponOff", m_KeepWeaponState);
|
ERR_SelectionStyle.Pair(GetRootWidget(), "KeepWeaponOn", "KeepWeaponOff", m_KeepWeaponState);
|
||||||
@@ -165,17 +179,21 @@ class ERR_AdminMenu : ChimeraMenuBase
|
|||||||
foreach (SCR_ButtonBaseComponent button : m_Buttons)
|
foreach (SCR_ButtonBaseComponent button : m_Buttons)
|
||||||
{
|
{
|
||||||
string name = button.GetRootWidget().GetName();
|
string name = button.GetRootWidget().GetName();
|
||||||
if (name.IndexOf("Apply_") == 0 || name.IndexOf("Random_") == 0 || name.IndexOf("RandomDay_") == 0 || name.IndexOf("Default_") == 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;
|
int prefix = name.IndexOf("_") + 1;
|
||||||
string key = name.Substring(prefix, name.Length() - prefix);
|
string key = name.Substring(prefix, name.Length() - prefix);
|
||||||
button.SetEnabled(m_Ready && m_AvailableFields.Contains(key) && ERR_AdminMenuFields.Available(key, m_HQ, m_Intel));
|
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 == "PlaneOn" || name == "PlaneOff") button.SetEnabled(m_Ready && m_HQ && m_AvailableFields.Contains("planeReconEnabled"));
|
||||||
else if (name.IndexOf("GuardEvery_") == 0) button.SetEnabled(m_Ready && m_HQ && m_AvailableFields.Contains("objectGuardEvery"));
|
|
||||||
else if (name.IndexOf("GuardHQ_") == 0) button.SetEnabled(m_Ready && m_HQ && m_AvailableFields.Contains("objectGuardHQMode"));
|
|
||||||
else if (name == "RerollIntel" || name == "IntelSummary") button.SetEnabled(m_Ready && m_Intel);
|
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 == "LatestJournal") button.SetEnabled(m_Ready && m_HQ);
|
||||||
|
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 == "RestartEmptyOn") button.SetEnabled(m_Ready && m_RestartEmptyState == 0);
|
||||||
else if (name == "RestartEmptyOff") button.SetEnabled(m_Ready && m_RestartEmptyState == 1);
|
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 == "SitesOn") button.SetEnabled(m_Ready && m_SitesState == 0);
|
||||||
@@ -190,7 +208,7 @@ class ERR_AdminMenu : ChimeraMenuBase
|
|||||||
{
|
{
|
||||||
if (!authorized) { Close(); return; }
|
if (!authorized) { Close(); return; }
|
||||||
if (!values) return;
|
if (!values) return;
|
||||||
m_HQ = false; m_Intel = false; m_AvailableFields.Clear(); m_KeepWeaponState = -1; m_SelfEpiState = -1; m_RestartEmptyState = -1;
|
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)
|
foreach (string entry : values)
|
||||||
{
|
{
|
||||||
if (entry == "supports=hq") { m_HQ = true; continue; }
|
if (entry == "supports=hq") { m_HQ = true; continue; }
|
||||||
@@ -198,6 +216,7 @@ class ERR_AdminMenu : ChimeraMenuBase
|
|||||||
int divider = entry.IndexOf("=");
|
int divider = entry.IndexOf("=");
|
||||||
if (divider <= 0) continue;
|
if (divider <= 0) continue;
|
||||||
string key = entry.Substring(0, divider);
|
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 == "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 == "recommendedBikeRuntime") { SetText("RecommendedBikeRuntime", entry.Substring(divider + 1, entry.Length() - divider - 1)); continue; }
|
||||||
if (key.IndexOf("recommended_") == 0)
|
if (key.IndexOf("recommended_") == 0)
|
||||||
@@ -221,12 +240,28 @@ class ERR_AdminMenu : ChimeraMenuBase
|
|||||||
if (ERR_AdminMenuFields.Keys().Contains(resolvedKey)) SetText("Saved_" + resolvedKey, "Saved: Random (using " + entry.Substring(divider + 1, entry.Length() - divider - 1) + ")");
|
if (ERR_AdminMenuFields.Keys().Contains(resolvedKey)) SetText("Saved_" + resolvedKey, "Saved: Random (using " + entry.Substring(divider + 1, entry.Length() - divider - 1) + ")");
|
||||||
continue;
|
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")
|
if (key == "planeReconEnabled")
|
||||||
{
|
{
|
||||||
int planeState = entry.Substring(divider + 1, entry.Length() - divider - 1).ToInt();
|
int planeState = entry.Substring(divider + 1, entry.Length() - divider - 1).ToInt();
|
||||||
ERR_SelectionStyle.Pair(GetRootWidget(), "PlaneOn", "PlaneOff", planeState);
|
ERR_SelectionStyle.Pair(GetRootWidget(), "PlaneOn", "PlaneOff", planeState);
|
||||||
m_AvailableFields.Insert(key); continue;
|
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")
|
if (key == "restartOnEmpty")
|
||||||
{
|
{
|
||||||
m_RestartEmptyState = entry.Substring(divider + 1, entry.Length() - divider - 1).ToInt();
|
m_RestartEmptyState = entry.Substring(divider + 1, entry.Length() - divider - 1).ToInt();
|
||||||
@@ -255,20 +290,18 @@ class ERR_AdminMenu : ChimeraMenuBase
|
|||||||
}
|
}
|
||||||
if (!ERR_AdminMenuFields.Keys().Contains(key)) continue;
|
if (!ERR_AdminMenuFields.Keys().Contains(key)) continue;
|
||||||
string saved = entry.Substring(divider + 1, entry.Length() - divider - 1);
|
string saved = entry.Substring(divider + 1, entry.Length() - divider - 1);
|
||||||
if (ERR_Settings.Randomizable(key)) ERR_SelectionStyle.Choice(SCR_ButtonTextComponent.GetButtonText("Random_" + key, GetRootWidget()), saved == "-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 == "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");
|
if (key == "viewDistanceMetres") ERR_SelectionStyle.Choice(SCR_ButtonTextComponent.GetButtonText("Default_viewDistanceMetres", GetRootWidget()), saved == "0");
|
||||||
if (key == "objectGuardEvery")
|
array<string> choices = ERR_AdminMenuFields.Choices(key);
|
||||||
for (int density = 1; density <= 4; density++)
|
foreach (string choice : choices)
|
||||||
ERR_SelectionStyle.Choice(SCR_ButtonTextComponent.GetButtonText("GuardEvery_" + density.ToString(), GetRootWidget()), saved.ToInt() == density);
|
ERR_SelectionStyle.Choice(SCR_ButtonTextComponent.GetButtonText("Choice_" + key + "_" + choice, GetRootWidget()), choice == ERR_AdminMenuFields.SavedChoice(key, saved));
|
||||||
if (key == "objectGuardHQMode")
|
|
||||||
for (int policy = 0; policy <= 2; policy++)
|
|
||||||
ERR_SelectionStyle.Choice(SCR_ButtonTextComponent.GetButtonText("GuardHQ_" + policy.ToString(), GetRootWidget()), saved.ToInt() == policy);
|
|
||||||
string value = ERR_AdminMenuFields.Display(key, saved);
|
string value = ERR_AdminMenuFields.Display(key, saved);
|
||||||
m_AvailableFields.Insert(key);
|
m_AvailableFields.Insert(key);
|
||||||
if (key == "startMode") SetText("Saved_" + key, ERR_AdminMenuFields.StartSummary(entry.Substring(divider + 1, entry.Length() - divider - 1).ToInt(), m_Mode.StartMode()));
|
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 if (key == "aiDoctrine" || key == "aiShadow") SetText("Saved_" + key, "Next round: " + value);
|
||||||
else SetText("Saved_" + key, "Saved: " + value);
|
else SetText("Saved_" + key, "Saved: " + value);
|
||||||
|
if (!choices.IsEmpty()) continue;
|
||||||
// Existing fields retain all draft text, including a temporarily empty edit.
|
// Existing fields retain all draft text, including a temporarily empty edit.
|
||||||
if (!m_InitializedFields.Contains(key))
|
if (!m_InitializedFields.Contains(key))
|
||||||
{
|
{
|
||||||
@@ -341,6 +374,7 @@ class ERR_AdminMenu : ChimeraMenuBase
|
|||||||
int section = ERR_AdminMenuPages.MainButtons().Find(name);
|
int section = ERR_AdminMenuPages.MainButtons().Find(name);
|
||||||
if (section >= 0) { ShowPage(ERR_AdminMenuPages.MainPage(section)); return; }
|
if (section >= 0) { ShowPage(ERR_AdminMenuPages.MainPage(section)); return; }
|
||||||
if (name == "RecommendedDrones") { ShowPage(ERR_AdminPage.DRONES); 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 == "RecommendedPlane") { ShowPage(ERR_AdminPage.PLANE); return; }
|
||||||
if (name == "RecommendedWeapons") { ShowPage(ERR_AdminPage.ITEMS); return; }
|
if (name == "RecommendedWeapons") { ShowPage(ERR_AdminPage.ITEMS); return; }
|
||||||
int page = Navigation().Find(name);
|
int page = Navigation().Find(name);
|
||||||
@@ -373,16 +407,14 @@ class ERR_AdminMenu : ChimeraMenuBase
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!m_Controller || !m_Ready || m_Age > 6 || m_ClickDelay > 0) return;
|
if (!m_Controller || !m_Ready || m_Age > 6 || m_ClickDelay > 0) return;
|
||||||
if (name.IndexOf("GuardEvery_") == 0 || name.IndexOf("GuardHQ_") == 0)
|
string choiceKey = ERR_AdminMenuFields.ChoiceKey(name);
|
||||||
|
if (!choiceKey.IsEmpty())
|
||||||
{
|
{
|
||||||
string guardKey = "objectGuardEvery";
|
if (!m_AvailableFields.Contains(choiceKey) || !ERR_AdminMenuFields.Available(choiceKey, m_HQ, m_Intel)) return;
|
||||||
if (name.IndexOf("GuardHQ_") == 0) guardKey = "objectGuardHQMode";
|
int choicePrefix = choiceKey.Length() + 8;
|
||||||
if (!m_HQ || !m_AvailableFields.Contains(guardKey)) return;
|
string choice = name.Substring(choicePrefix, name.Length() - choicePrefix);
|
||||||
int guardPrefix = name.IndexOf("_") + 1;
|
m_ClickDelay = 0.3; SetText("Feedback", "Saving selection for next round...");
|
||||||
SCR_EditBoxComponent guardEdit = SCR_EditBoxComponent.GetEditBoxComponent(guardKey, GetRootWidget());
|
m_Controller.ERR_AdminSubmit(ERR_AdminMenuFields.Command(choiceKey, choice), true); return;
|
||||||
if (guardEdit) guardEdit.SetValue(name.Substring(guardPrefix, name.Length() - guardPrefix));
|
|
||||||
SetText("Feedback", "Choice selected. Press Apply to save for next round. Green marks the saved choice.");
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
if (name == "PlaneOn" || name == "PlaneOff")
|
if (name == "PlaneOn" || name == "PlaneOff")
|
||||||
{
|
{
|
||||||
@@ -391,6 +423,19 @@ class ERR_AdminMenu : ChimeraMenuBase
|
|||||||
m_ClickDelay = 0.3; SetText("Feedback", "Saving reconnaissance plane selection for next round...");
|
m_ClickDelay = 0.3; SetText("Feedback", "Saving reconnaissance plane selection for next round...");
|
||||||
m_Controller.ERR_AdminSubmit("mission plane set " + enabled, true); return;
|
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 (name == "RestartEmptyOn" || name == "RestartEmptyOff")
|
||||||
{
|
{
|
||||||
if (m_RestartEmptyState < 0) return;
|
if (m_RestartEmptyState < 0) return;
|
||||||
|
|||||||
@@ -3,7 +3,29 @@ class ERR_AdminMenuFields
|
|||||||
{
|
{
|
||||||
static array<string> Keys()
|
static array<string> Keys()
|
||||||
{
|
{
|
||||||
return {"aiDoctrine", "aiShadow", "automaticEquipmentChancePercent", "sarkaAngerPerCar", "motorbikeCount", "droneReconCount", "droneCooldownSeconds", "planeAltitudeMetres", "planeReplacementSeconds", "planeSectorSeconds", "fieldCampCount", "fieldHospitalCount", "startWeaponCount", "startMagazineCount", "objectGuardCount", "objectGuardEvery", "objectGuardHQMode", "hunterSpawnDistanceMetres", "roadblockCount", "civilianTrafficCount", "emptyRestartHours", "startMode", "radioDefenceSeconds", "aiIntelChancePercent", "intelRevealChancePercent", "startTimeMinutes", "timeMultiplier", "startDateCode", "weatherProfile", "viewDistanceMetres", "reportIntervalSeconds", "supportSquadLimit", "responseRadiusMetres", "convoyCount", "antiAirChancePercent", "antiPersonnelMineChancePercent", "heliTimerMinutes"};
|
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)
|
static bool Available(string key, bool hq, bool intel)
|
||||||
{
|
{
|
||||||
@@ -26,6 +48,7 @@ class ERR_AdminMenuFields
|
|||||||
}
|
}
|
||||||
static string Display(string key, string value)
|
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 == "aiDoctrine") { if (value == "1") return "Coordinated"; return "Current"; }
|
||||||
if (key == "aiShadow") { if (value == "1") return "On"; return "Off"; }
|
if (key == "aiShadow") { if (value == "1") return "On"; return "Off"; }
|
||||||
if (value == "-1" || value == "-2") return ERR_Settings.SelectionText(value.ToInt());
|
if (value == "-1" || value == "-2") return ERR_Settings.SelectionText(value.ToInt());
|
||||||
@@ -60,15 +83,32 @@ class ERR_AdminMenuFields
|
|||||||
else if (key == "objectGuardHQMode") prefix = "mission objecthq";
|
else if (key == "objectGuardHQMode") prefix = "mission objecthq";
|
||||||
else if (key == "objectGuardCount") prefix = "mission objectguards";
|
else if (key == "objectGuardCount") prefix = "mission objectguards";
|
||||||
else if (key == "hunterSpawnDistanceMetres") prefix = "mission hunterdistance";
|
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 == "fieldCampCount") prefix = "mission camps";
|
||||||
else if (key == "fieldHospitalCount") prefix = "mission hospitals";
|
else if (key == "fieldHospitalCount") prefix = "mission hospitals";
|
||||||
else if (key == "roadblockCount") prefix = "mission roadblocks";
|
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 == "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 == "civilianTrafficCount") prefix = "mission traffic";
|
||||||
else if (key == "startMode") prefix = "mission start";
|
else if (key == "startMode") prefix = "mission start";
|
||||||
else if (key == "radioDefenceSeconds") prefix = "mission radio";
|
else if (key == "radioDefenceSeconds") prefix = "mission radio";
|
||||||
else if (key == "aiIntelChancePercent") prefix = "intel carrying";
|
else if (key == "aiIntelChancePercent") prefix = "intel carrying";
|
||||||
else if (key == "intelRevealChancePercent") prefix = "intel reveal";
|
else if (key == "intelRevealChancePercent") prefix = "intel reveal";
|
||||||
|
else if (key == "radioIntelSiteCount") prefix = "intel radiosites";
|
||||||
else if (key == "startTimeMinutes") prefix = "mission time";
|
else if (key == "startTimeMinutes") prefix = "mission time";
|
||||||
else if (key == "timeMultiplier") prefix = "mission speed";
|
else if (key == "timeMultiplier") prefix = "mission speed";
|
||||||
else if (key == "startDateCode") prefix = "mission date";
|
else if (key == "startDateCode") prefix = "mission date";
|
||||||
|
|||||||
@@ -24,23 +24,31 @@ enum ERR_AdminPage
|
|||||||
STARTER = 20,
|
STARTER = 20,
|
||||||
SARKA = 21,
|
SARKA = 21,
|
||||||
DRONES = 22,
|
DRONES = 22,
|
||||||
|
RADIO_INTEL = 23,
|
||||||
|
TELEMETRY = 24,
|
||||||
|
VEHICLES = 25,
|
||||||
|
CHECKPOINTS = 26,
|
||||||
|
VEHICLE_POOL = 27,
|
||||||
|
PACKS = 28,
|
||||||
|
CRASH_SITES = 29,
|
||||||
|
BOATS = 30,
|
||||||
}
|
}
|
||||||
|
|
||||||
class ERR_AdminMenuPages
|
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"}; }
|
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"}; }
|
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_Items", "Main_Mods", "Main_Audio", "Main_Status"}; }
|
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_Items", "Sidebar_Mods", "Sidebar_Audio", "Sidebar_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)
|
static int Group(int page)
|
||||||
{
|
{
|
||||||
array<int> groups = {0, 2, 7, 5, 1, 0, 3, 7, 0, 4, 6, 1, 1, 1, 2, 5, 4, 4, 4, 2, 4, 1, 2};
|
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;
|
if (page < 0 || page >= groups.Count()) return -1;
|
||||||
return groups[page];
|
return groups[page];
|
||||||
}
|
}
|
||||||
static int MainPage(int group)
|
static int MainPage(int group)
|
||||||
{
|
{
|
||||||
array<int> pages = {ERR_AdminPage.MISSION, ERR_AdminPage.FORCES, ERR_AdminPage.HQ, ERR_AdminPage.INTEL, ERR_AdminPage.ITEMS, ERR_AdminPage.MODS, ERR_AdminPage.SPEECH, ERR_AdminPage.STATUS};
|
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;
|
if (group < 0 || group >= pages.Count()) return -1;
|
||||||
return pages[group];
|
return pages[group];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,19 @@
|
|||||||
// Widget presentation only. No settings writes, authorization or transport here.
|
// Widget presentation only. No settings writes, authorization or transport here.
|
||||||
class ERR_AdminMenuView
|
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)
|
static bool ShowPage(Widget root, int page)
|
||||||
{
|
{
|
||||||
int group = ERR_AdminMenuPages.Group(page);
|
int group = ERR_AdminMenuPages.Group(page);
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ class ERR_CommandArguments
|
|||||||
{
|
{
|
||||||
static string CoreSetting(string section, string option)
|
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 == "doctrine") return "aiDoctrine";
|
||||||
if (section == "mission" && option == "aishadow") return "aiShadow";
|
if (section == "mission" && option == "aishadow") return "aiShadow";
|
||||||
if (section == "mission" && option == "sarkapoints") return "sarkaAngerPerCar";
|
if (section == "mission" && option == "sarkapoints") return "sarkaAngerPerCar";
|
||||||
@@ -15,6 +16,16 @@ class ERR_CommandArguments
|
|||||||
if (section == "mission" && option == "dronecooldown") return "droneCooldownSeconds";
|
if (section == "mission" && option == "dronecooldown") return "droneCooldownSeconds";
|
||||||
if (section == "mission" && option == "plane") return "planeReconEnabled";
|
if (section == "mission" && option == "plane") return "planeReconEnabled";
|
||||||
if (section == "mission" && option == "camps") return "fieldCampCount";
|
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 == "hospitals") return "fieldHospitalCount";
|
||||||
if (section == "mission" && option == "startweapons") return "startWeaponCount";
|
if (section == "mission" && option == "startweapons") return "startWeaponCount";
|
||||||
if (section == "mission" && option == "startammo") return "startMagazineCount";
|
if (section == "mission" && option == "startammo") return "startMagazineCount";
|
||||||
@@ -23,6 +34,12 @@ class ERR_CommandArguments
|
|||||||
if (section == "mission" && option == "objectguards") return "objectGuardCount";
|
if (section == "mission" && option == "objectguards") return "objectGuardCount";
|
||||||
if (section == "mission" && option == "hunterdistance") return "hunterSpawnDistanceMetres";
|
if (section == "mission" && option == "hunterdistance") return "hunterSpawnDistanceMetres";
|
||||||
if (section == "mission" && option == "roadblocks") return "roadblockCount";
|
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 == "traffic") return "civilianTrafficCount";
|
||||||
if (section == "mission" && option == "start") return "startMode";
|
if (section == "mission" && option == "start") return "startMode";
|
||||||
if (section == "mission" && option == "selfepi") return "selfEpinephrine";
|
if (section == "mission" && option == "selfepi") return "selfEpinephrine";
|
||||||
@@ -31,11 +48,14 @@ class ERR_CommandArguments
|
|||||||
if (section == "mission" && option == "emptyhours") return "emptyRestartHours";
|
if (section == "mission" && option == "emptyhours") return "emptyRestartHours";
|
||||||
if (section == "mission" && option == "radio") return "radioDefenceSeconds";
|
if (section == "mission" && option == "radio") return "radioDefenceSeconds";
|
||||||
if (section == "intel" && option == "sites") return "showAllSites";
|
if (section == "intel" && option == "sites") return "showAllSites";
|
||||||
|
if (section == "intel" && option == "radiosites") return "radioIntelSiteCount";
|
||||||
if (section == "intel" && option == "reveal") return "intelRevealChancePercent";
|
if (section == "intel" && option == "reveal") return "intelRevealChancePercent";
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
static bool Selection(string key, string text, out int value)
|
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 == "current") { value = 0; return true; }
|
||||||
if (key == "aiDoctrine" && text == "coordinated") { value = 1; 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 == "off") { value = 0; return true; }
|
||||||
@@ -183,28 +203,47 @@ class ERR_AdminService
|
|||||||
if (key == "planeSectorSeconds") return "30-600; default 120. 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 == "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 == "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 == "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 == "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 == "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 == "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 == "hunterSpawnDistanceMetres") return "Opening UAZ and troop-truck patrol placement. Applies next round.";
|
||||||
if (key == "roadblockCount") return "Set 0..30 roadblocks (default 4; 0 disables). Applies next round. Each has four guards; unsuitable sites are skipped.";
|
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 == "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 == "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 == "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 == "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 == "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 == "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")
|
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.";
|
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")
|
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. 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 "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 "";
|
return "";
|
||||||
}
|
}
|
||||||
protected string RoadblockTiming(string key)
|
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 == "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") 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 "";
|
return "";
|
||||||
}
|
}
|
||||||
protected void SettingCommand(ERR_Settings settings, string key, array<string> parts, array<string> replies)
|
protected void SettingCommand(ERR_Settings settings, string key, array<string> parts, array<string> replies)
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ class ERR_AdminSession
|
|||||||
protected int m_Cursor;
|
protected int m_Cursor;
|
||||||
protected string m_ItemAddon;
|
protected string m_ItemAddon;
|
||||||
protected int m_ItemPage;
|
protected int m_ItemPage;
|
||||||
|
protected int m_ItemFilter;
|
||||||
|
protected string m_ItemQuery;
|
||||||
protected float m_LastRequest = -1000;
|
protected float m_LastRequest = -1000;
|
||||||
protected float m_LastSnapshot = -1000;
|
protected float m_LastSnapshot = -1000;
|
||||||
protected float m_LastLog = -1000;
|
protected float m_LastLog = -1000;
|
||||||
@@ -68,7 +70,7 @@ class ERR_AdminSession
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
array<string> replies = {};
|
array<string> replies = {};
|
||||||
if (!StartBackpackCommand(arguments, replies) && !ItemCatalogCommand(arguments, replies) && !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);
|
m_Mode.Admin().Execute(m_Mode, true, arguments, replies);
|
||||||
foreach (string reply : replies) m_Controller.ERR_AdminDeliverReply(m_Token, reply, menu);
|
foreach (string reply : replies) m_Controller.ERR_AdminDeliverReply(m_Token, reply, menu);
|
||||||
if (menu) Snapshot(true);
|
if (menu) Snapshot(true);
|
||||||
@@ -80,19 +82,24 @@ class ERR_AdminSession
|
|||||||
ERR_StartBackpack supplies = GetGame().ERR_StartingSupplies();
|
ERR_StartBackpack supplies = GetGame().ERR_StartingSupplies();
|
||||||
bool saved;
|
bool saved;
|
||||||
if (parts.Count() == 3 && parts[1] == "mode" && (parts[2] == "default" || parts[2] == "custom")) saved = supplies.SetMode(parts[2] == "custom");
|
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[1] == "remove") saved = supplies.Remove(parts[2]);
|
else if ((parts.Count() == 3 || parts.Count() == 4) && parts[1] == "remove")
|
||||||
else if (parts.Count() == 5 && parts[1] == "set")
|
{
|
||||||
|
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);
|
ERR_ServerItemCatalog catalog = GetGame().ERR_ServerItems(); catalog.Start(m_Mode);
|
||||||
int count;
|
int count;
|
||||||
if (catalog.Contains(parts[2], parts[3]) && ERR_CommandArguments.Integer(parts[4], count))
|
if (catalog.Contains(parts[2], parts[3]) && ERR_CommandArguments.Integer(parts[4], count))
|
||||||
{
|
{
|
||||||
ERR_ServerItem item = catalog.Item(parts[3]);
|
ERR_ServerItem item = catalog.Item(parts[3]);
|
||||||
if (item) saved = supplies.Set(item.Prefab, count);
|
string destination = "backpack"; if (parts.Count() == 6) destination = parts[5];
|
||||||
|
if (item) saved = supplies.Set(item.Prefab, count, destination);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (saved) replies.Insert("Starting backpack saved for next round. Custom contents are used only when Custom is selected.");
|
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 item types and 64 items total. Check configuration if read-only.");
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
// Per-session paging: another administrator cannot change this menu's selection.
|
// Per-session paging: another administrator cannot change this menu's selection.
|
||||||
@@ -102,6 +109,22 @@ class ERR_AdminSession
|
|||||||
if (parts.IsEmpty() || parts[0] != "items") return false;
|
if (parts.IsEmpty() || parts[0] != "items") return false;
|
||||||
int page;
|
int page;
|
||||||
ERR_ServerItemCatalog catalog = GetGame().ERR_ServerItems(); catalog.Start(m_Mode);
|
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 (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.");
|
if (GetGame().ERR_ItemSelections().Preset(parts[2], parts[3])) replies.Insert("Preset " + parts[2] + " completed: " + parts[3] + ". Future random equipment uses the saved selection.");
|
||||||
@@ -124,7 +147,7 @@ class ERR_AdminSession
|
|||||||
}
|
}
|
||||||
if (parts.Count() != 4 || parts[1] != "list" || !catalog.Find(parts[2]) || !ERR_CommandArguments.Integer(parts[3], page) || page < 0 || page > 2730)
|
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; }
|
{ replies.Insert("Choose a loaded server mod in Items, then press Show items."); return true; }
|
||||||
m_ItemAddon = parts[2]; m_ItemPage = page;
|
m_ItemAddon = parts[2]; m_ItemPage = page; m_ItemFilter = 0; m_ItemQuery = "";
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
protected bool JournalCommand(string arguments, array<string> replies)
|
protected bool JournalCommand(string arguments, array<string> replies)
|
||||||
@@ -154,11 +177,15 @@ class ERR_AdminSession
|
|||||||
if (key == "convoyCount" || key == "startTimeMinutes" || key == "timeMultiplier" || key == "startDateCode" || key == "weatherProfile") continue;
|
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.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);
|
m_Mode.Admin().Capabilities(values);
|
||||||
ERR_RecommendedAddons.Snapshot(m_Mode, values);
|
ERR_RecommendedAddons.Snapshot(m_Mode, values);
|
||||||
GetGame().ERR_ServerItems().Snapshot(m_Mode, values, m_ItemAddon, m_ItemPage);
|
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);
|
GetGame().ERR_StartingSupplies().Snapshot(values);
|
||||||
m_Mode.Admin().Execute(m_Mode, true, "status", status);
|
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);
|
m_Controller.ERR_AdminDeliverSnapshot(m_Token, true, values, status);
|
||||||
}
|
}
|
||||||
void Log(int cursor, bool latest)
|
void Log(int cursor, bool latest)
|
||||||
@@ -203,4 +230,38 @@ class ERR_AdminSession
|
|||||||
m_Tools.Stop();
|
m_Tools.Stop();
|
||||||
if (m_Mode) m_Mode.OnStop().Remove(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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ class ERR_AirfieldPlacement
|
|||||||
}
|
}
|
||||||
static bool Clear(vector position)
|
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();
|
ERR_AirfieldPlacement survey = new ERR_AirfieldPlacement();
|
||||||
array<vector> authored = {};
|
array<vector> authored = {};
|
||||||
AuthoredAirports(authored);
|
AuthoredAirports(authored);
|
||||||
@@ -49,12 +49,12 @@ class ERR_AirfieldPlacement
|
|||||||
// Same aircraft footprint/vertical sweep, without requiring airport scenery.
|
// Same aircraft footprint/vertical sweep, without requiring airport scenery.
|
||||||
static bool ClearGround(vector position)
|
static bool ClearGround(vector position)
|
||||||
{
|
{
|
||||||
if (!Replication.IsServer() || ChimeraWorldUtils.TryGetWaterSurfaceSimple(GetGame().GetWorld(), position)) return false;
|
if (!Replication.IsServer() || !ERR_Actors.DryGround(position)) return false;
|
||||||
for (int edge = 0; edge < 4; edge++)
|
for (int edge = 0; edge < 4; edge++)
|
||||||
{
|
{
|
||||||
float side = edge * Math.PI / 2;
|
float side = edge * Math.PI / 2;
|
||||||
vector sample = position + Vector(Math.Cos(side) * 14, 0, Math.Sin(side) * 14);
|
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;
|
if (!SCR_WorldTools.TraceCilinderUtil(position + "0 2.5 0", 40, 4, TraceFlags.ENTS)) return false;
|
||||||
TraceBox clearance = new TraceBox();
|
TraceBox clearance = new TraceBox();
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,12 +12,13 @@ modded class ERR_AdminMenu
|
|||||||
override void OnMenuOpen()
|
override void OnMenuOpen()
|
||||||
{
|
{
|
||||||
super.OnMenuOpen();
|
super.OnMenuOpen();
|
||||||
Widget list = GetRootWidget().FindAnyWidget("ModList");
|
Widget list = GetRootWidget().FindAnyWidget("PackList");
|
||||||
foreach (string pack : ERR_ContentRegistry.Ids())
|
foreach (string pack : ERR_ContentRegistry.Ids())
|
||||||
{
|
{
|
||||||
Widget row = GetGame().GetWorkspace().CreateWidgets("{F092D45656A8E02B}UI/layouts/ERR/ContentRow.layout", list);
|
Widget row = GetGame().GetWorkspace().CreateWidgets("{F092D45656A8E02B}UI/layouts/ERR/ContentRow.layout", list);
|
||||||
if (!row) { Close(); return; }
|
if (!row) { Close(); return; }
|
||||||
m_ModRows.Set(pack, row);
|
m_ModRows.Set(pack, row);
|
||||||
|
row.FindAnyWidget("EditItems").SetVisible(false); row.FindAnyWidget("EditVehicles").SetVisible(false);
|
||||||
TextWidget label = TextWidget.Cast(row.FindAnyWidget("PackLabel"));
|
TextWidget label = TextWidget.Cast(row.FindAnyWidget("PackLabel"));
|
||||||
if (label) label.SetText(ERR_ContentRegistry.Label(pack));
|
if (label) label.SetText(ERR_ContentRegistry.Label(pack));
|
||||||
SCR_ButtonTextComponent enable = SCR_ButtonTextComponent.GetButtonText("Enable", row);
|
SCR_ButtonTextComponent enable = SCR_ButtonTextComponent.GetButtonText("Enable", row);
|
||||||
|
|||||||
@@ -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,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.
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -29,7 +29,58 @@ class ERR_DiscoveryService
|
|||||||
protected int m_CleanupCursor;
|
protected int m_CleanupCursor;
|
||||||
protected float m_CleanupNow;
|
protected float m_CleanupNow;
|
||||||
protected bool m_Stopped;
|
protected bool m_Stopped;
|
||||||
void ERR_DiscoveryService(ERR_GameMode mode) { m_Mode = mode; }
|
protected int m_RadioIntelRequired;
|
||||||
|
protected int m_RadioIntelIdentified;
|
||||||
|
protected bool m_RadioIntelFinalized;
|
||||||
|
protected bool m_RadioIntelUnlocked;
|
||||||
|
void ERR_DiscoveryService(ERR_GameMode mode)
|
||||||
|
{
|
||||||
|
m_Mode = mode;
|
||||||
|
// Freeze the saved setting for this round; menu edits affect the next one.
|
||||||
|
m_RadioIntelRequired = mode.Settings().Get("radioIntelSiteCount");
|
||||||
|
m_RadioIntelUnlocked = m_RadioIntelRequired == 0;
|
||||||
|
}
|
||||||
|
protected bool CountsForRadioIntel(ERR_LocationComponent location)
|
||||||
|
{
|
||||||
|
if (!location || !location.Prepared() || location.Kind() == ERR_LocationKind.RADIO || location.Kind() == ERR_LocationKind.PRISON || location.Kind() == ERR_LocationKind.EXTRACTION) return false;
|
||||||
|
ERR_SiteService sites = m_Mode.Sites();
|
||||||
|
ERR_SiteRuntime site;
|
||||||
|
if (sites) site = sites.Find(location.Id());
|
||||||
|
return !site || !site.Definition || !site.Definition.RevealOnEscape;
|
||||||
|
}
|
||||||
|
protected void RefreshRadioIntel()
|
||||||
|
{
|
||||||
|
if (m_Stopped || !m_Mode || !Replication.IsServer()) return;
|
||||||
|
array<ERR_LocationComponent> locations = {};
|
||||||
|
m_Mode.Locations(locations);
|
||||||
|
set<string> available = new set<string>();
|
||||||
|
set<string> identified = new set<string>();
|
||||||
|
foreach (ERR_LocationComponent location : locations)
|
||||||
|
{
|
||||||
|
if (!CountsForRadioIntel(location)) continue;
|
||||||
|
available.Insert(location.Id());
|
||||||
|
if (location.Knowledge() == 2) identified.Insert(location.Id());
|
||||||
|
}
|
||||||
|
m_RadioIntelIdentified = identified.Count();
|
||||||
|
// Background generation can still add eligible sites. Finalize only once
|
||||||
|
// the active world's preparation has finished, including sparse terrains.
|
||||||
|
if (!m_RadioIntelFinalized && m_Mode.Active() && !m_Mode.WorldPreparing())
|
||||||
|
{
|
||||||
|
m_RadioIntelRequired = Math.Min(m_RadioIntelRequired, available.Count());
|
||||||
|
m_RadioIntelFinalized = true;
|
||||||
|
}
|
||||||
|
if (m_RadioIntelIdentified >= m_RadioIntelRequired) m_RadioIntelUnlocked = true;
|
||||||
|
}
|
||||||
|
bool RadioIntelAllowed() { RefreshRadioIntel(); return m_RadioIntelUnlocked; }
|
||||||
|
string RadioIntelStatus()
|
||||||
|
{
|
||||||
|
RefreshRadioIntel();
|
||||||
|
string state = "locked";
|
||||||
|
if (m_RadioIntelUnlocked) state = "unlocked";
|
||||||
|
string progress = string.Format("Radio intel %1: %2 / %3 non-radio sites identified this round.", state, m_RadioIntelIdentified, m_RadioIntelRequired);
|
||||||
|
if (!m_RadioIntelFinalized) progress += " Site preparation is still in progress.";
|
||||||
|
return progress;
|
||||||
|
}
|
||||||
// Shared ledger for recovered and administratively retired documents.
|
// Shared ledger for recovered and administratively retired documents.
|
||||||
bool IsSpent(IEntity folder) { return folder && m_Spent.Contains(folder); }
|
bool IsSpent(IEntity folder) { return folder && m_Spent.Contains(folder); }
|
||||||
bool CanMarkSpent(IEntity folder)
|
bool CanMarkSpent(IEntity folder)
|
||||||
@@ -145,10 +196,22 @@ class ERR_DiscoveryService
|
|||||||
array<ERR_LocationComponent> eligible = {};
|
array<ERR_LocationComponent> eligible = {};
|
||||||
ERR_DocumentComponent document = ERR_DocumentComponent.Cast(folder.FindComponent(ERR_DocumentComponent));
|
ERR_DocumentComponent document = ERR_DocumentComponent.Cast(folder.FindComponent(ERR_DocumentComponent));
|
||||||
string sealedTarget = m_Mode.Evidence().Target(folder);
|
string sealedTarget = m_Mode.Evidence().Target(folder);
|
||||||
|
bool radioAllowed = RadioIntelAllowed();
|
||||||
|
bool redirectRadio;
|
||||||
|
if (!radioAllowed && !document && !sealedTarget.IsEmpty())
|
||||||
|
{
|
||||||
|
foreach (ERR_LocationComponent target : locations)
|
||||||
|
if (target && target.Id() == sealedTarget && target.Kind() == ERR_LocationKind.RADIO) redirectRadio = true;
|
||||||
|
}
|
||||||
foreach (ERR_LocationComponent location : locations)
|
foreach (ERR_LocationComponent location : locations)
|
||||||
{
|
{
|
||||||
if (!location || location.Knowledge() >= 2) continue;
|
if (!location || location.Knowledge() >= 2) continue;
|
||||||
if (!sealedTarget.IsEmpty() && location.Id() != sealedTarget) continue;
|
if (!radioAllowed && location.Kind() == ERR_LocationKind.RADIO)
|
||||||
|
{
|
||||||
|
if (document && location.Id() == document.TargetId()) m_Mode.ERR_RadioIntelWaiting();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!redirectRadio && !sealedTarget.IsEmpty() && location.Id() != sealedTarget) continue;
|
||||||
if (document)
|
if (document)
|
||||||
{
|
{
|
||||||
if (location.Id() == document.TargetId()) eligible.Insert(location);
|
if (location.Id() == document.TargetId()) eligible.Insert(location);
|
||||||
@@ -157,7 +220,7 @@ class ERR_DiscoveryService
|
|||||||
}
|
}
|
||||||
if (eligible.IsEmpty())
|
if (eligible.IsEmpty())
|
||||||
{
|
{
|
||||||
if (!document && !m_Mode.WorldPreparing() && MarkSpent(folder))
|
if (!document && radioAllowed && !m_Mode.WorldPreparing() && MarkSpent(folder))
|
||||||
{
|
{
|
||||||
QueueCleanup(folder, m_CleanupNow);
|
QueueCleanup(folder, m_CleanupNow);
|
||||||
if (!mineRecovered) m_Mode.ERR_IntelExhausted(!sealedTarget.IsEmpty());
|
if (!mineRecovered) m_Mode.ERR_IntelExhausted(!sealedTarget.IsEmpty());
|
||||||
@@ -166,6 +229,7 @@ class ERR_DiscoveryService
|
|||||||
}
|
}
|
||||||
ERR_LocationComponent chosen = eligible.GetRandomElement();
|
ERR_LocationComponent chosen = eligible.GetRandomElement();
|
||||||
if (!chosen.Discover(Math.RandomInt(0, 100), m_Mode.Settings().Runtime("intelRevealChancePercent"))) continue;
|
if (!chosen.Discover(Math.RandomInt(0, 100), m_Mode.Settings().Runtime("intelRevealChancePercent"))) continue;
|
||||||
|
if (redirectRadio) m_Mode.Evidence().Retarget(folder, chosen.Id());
|
||||||
m_Spent.Insert(folder);
|
m_Spent.Insert(folder);
|
||||||
PrintFormat("Reintroduced [intel]: player %1 recovered folder; location=%2 knowledge=%3", GetGame().GetPlayerManager().GetPlayerIdFromControlledEntity(player), chosen.Id(), chosen.Knowledge());
|
PrintFormat("Reintroduced [intel]: player %1 recovered folder; location=%2 knowledge=%3", GetGame().GetPlayerManager().GetPlayerIdFromControlledEntity(player), chosen.Id(), chosen.Knowledge());
|
||||||
|
|
||||||
|
|||||||
@@ -43,9 +43,11 @@ class ERR_ConsumableCombatEpinephrine : SCR_ConsumableMorphine
|
|||||||
{
|
{
|
||||||
SCR_CharacterDamageManagerComponent damage = SCR_CharacterDamageManagerComponent.Cast(target.FindComponent(SCR_CharacterDamageManagerComponent));
|
SCR_CharacterDamageManagerComponent damage = SCR_CharacterDamageManagerComponent.Cast(target.FindComponent(SCR_CharacterDamageManagerComponent));
|
||||||
if (!damage || damage.GetState() == EDamageState.DESTROYED) { failReason = SCR_EConsumableFailReason.UNDAMAGED; return false; }
|
if (!damage || damage.GetState() == EDamageState.DESTROYED) { failReason = SCR_EConsumableFailReason.UNDAMAGED; return false; }
|
||||||
|
CharacterControllerComponent controller = CharacterControllerComponent.Cast(target.FindComponent(CharacterControllerComponent));
|
||||||
|
if (controller && controller.IsUnconscious()) return true;
|
||||||
if (damage.IsBleeding()) return true;
|
if (damage.IsBleeding()) return true;
|
||||||
HitZone blood = damage.GetBloodHitZone(), resilience = damage.GetResilienceHitZone();
|
HitZone blood = damage.GetBloodHitZone(), resilience = damage.GetResilienceHitZone();
|
||||||
if ((blood && blood.GetHealthScaled() < 1) || (resilience && resilience.GetHealthScaled() < 1)) return true;
|
if ((blood && blood.GetHealthScaled() <= blood.GetDamageStateThreshold(ECharacterBloodState.FAINTING)) || (resilience && resilience.GetHealthScaled() < 1)) return true;
|
||||||
array<HitZone> zones = {}; damage.GetPhysicalHitZones(zones);
|
array<HitZone> zones = {}; damage.GetPhysicalHitZones(zones);
|
||||||
foreach (HitZone zone : zones) if (zone && zone.GetHealthScaled() < 1) return true;
|
foreach (HitZone zone : zones) if (zone && zone.GetHealthScaled() < 1) return true;
|
||||||
failReason = SCR_EConsumableFailReason.UNDAMAGED; return false;
|
failReason = SCR_EConsumableFailReason.UNDAMAGED; return false;
|
||||||
|
|||||||
@@ -33,13 +33,22 @@ class ERR_EscapeGameOverScreenInfo : SCR_BaseGameOverScreenInfo
|
|||||||
modded class SCR_GameOverScreenContentUIComponent
|
modded class SCR_GameOverScreenContentUIComponent
|
||||||
{
|
{
|
||||||
protected bool m_ERREscapeContent;
|
protected bool m_ERREscapeContent;
|
||||||
|
protected ERR_GameMode m_ERRStatisticsMode;
|
||||||
protected ERR_GameMode m_ERRFailureMode;
|
protected ERR_GameMode m_ERRFailureMode;
|
||||||
override void InitContent(SCR_GameOverScreenUIContentData endScreenUIContent)
|
override void InitContent(SCR_GameOverScreenUIContentData endScreenUIContent)
|
||||||
{
|
{
|
||||||
SCR_PlayerController.ERR_OnEscapeResultChanged().Remove(ERR_RefreshEscapeContent);
|
SCR_PlayerController.ERR_OnEscapeResultChanged().Remove(ERR_RefreshEscapeContent);
|
||||||
if (m_ERRFailureMode) m_ERRFailureMode.OnEndReasonChanged().Remove(ERR_RefreshFailureContent);
|
if (m_ERRFailureMode) m_ERRFailureMode.OnEndReasonChanged().Remove(ERR_RefreshFailureContent);
|
||||||
m_ERRFailureMode = null;
|
m_ERRFailureMode = null;
|
||||||
|
if (m_ERRStatisticsMode) m_ERRStatisticsMode.ERR_OnStatisticsChanged().Remove(ERR_RefreshStatistics);
|
||||||
|
m_ERRStatisticsMode = null;
|
||||||
super.InitContent(endScreenUIContent);
|
super.InitContent(endScreenUIContent);
|
||||||
|
if (ERR_EscapeGameOverScreenInfo.Cast(endScreenUIContent.m_GameOverInfo) || ERR_FailureGameOverScreenInfo.Cast(endScreenUIContent.m_GameOverInfo))
|
||||||
|
{
|
||||||
|
m_ERRStatisticsMode = ERR_GameMode.Get();
|
||||||
|
if (m_ERRStatisticsMode) m_ERRStatisticsMode.ERR_OnStatisticsChanged().Insert(ERR_RefreshStatistics);
|
||||||
|
ERR_RefreshStatistics();
|
||||||
|
}
|
||||||
if (ERR_FailureGameOverScreenInfo.Cast(endScreenUIContent.m_GameOverInfo))
|
if (ERR_FailureGameOverScreenInfo.Cast(endScreenUIContent.m_GameOverInfo))
|
||||||
{
|
{
|
||||||
m_ERRFailureMode = ERR_GameMode.Get();
|
m_ERRFailureMode = ERR_GameMode.Get();
|
||||||
@@ -71,10 +80,21 @@ modded class SCR_GameOverScreenContentUIComponent
|
|||||||
TextWidget debrief = TextWidget.Cast(m_wRoot.FindAnyWidget(m_sDebriefingName));
|
TextWidget debrief = TextWidget.Cast(m_wRoot.FindAnyWidget(m_sDebriefingName));
|
||||||
if (debrief) debrief.SetText(info.GetDebriefing(-1, null));
|
if (debrief) debrief.SetText(info.GetDebriefing(-1, null));
|
||||||
}
|
}
|
||||||
|
protected void ERR_RefreshStatistics()
|
||||||
|
{
|
||||||
|
if (!m_wRoot || !m_ERRStatisticsMode || m_ERRStatisticsMode != ERR_GameMode.Get()) return;
|
||||||
|
TextWidget text = TextWidget.Cast(m_wRoot.FindAnyWidget("RoundStatistics"));
|
||||||
|
if (!text) return;
|
||||||
|
string summary = m_ERRStatisticsMode.ERR_RoundSummary();
|
||||||
|
if (summary.IsEmpty()) summary = "Waiting for mission statistics...";
|
||||||
|
text.SetText(summary);
|
||||||
|
}
|
||||||
override void HandlerDeattached(Widget w)
|
override void HandlerDeattached(Widget w)
|
||||||
{
|
{
|
||||||
SCR_PlayerController.ERR_OnEscapeResultChanged().Remove(ERR_RefreshEscapeContent);
|
SCR_PlayerController.ERR_OnEscapeResultChanged().Remove(ERR_RefreshEscapeContent);
|
||||||
m_ERREscapeContent = false;
|
m_ERREscapeContent = false;
|
||||||
|
if (m_ERRStatisticsMode) m_ERRStatisticsMode.ERR_OnStatisticsChanged().Remove(ERR_RefreshStatistics);
|
||||||
|
m_ERRStatisticsMode = null;
|
||||||
if (m_ERRFailureMode) m_ERRFailureMode.OnEndReasonChanged().Remove(ERR_RefreshFailureContent);
|
if (m_ERRFailureMode) m_ERRFailureMode.OnEndReasonChanged().Remove(ERR_RefreshFailureContent);
|
||||||
m_ERRFailureMode = null;
|
m_ERRFailureMode = null;
|
||||||
super.HandlerDeattached(w);
|
super.HandlerDeattached(w);
|
||||||
|
|||||||
@@ -8,6 +8,16 @@ class ERR_RescueRules
|
|||||||
if (since < 0 || now < since) return now;
|
if (since < 0 || now < since) return now;
|
||||||
return since;
|
return since;
|
||||||
}
|
}
|
||||||
|
static float AirborneSince(bool airborne, float since, float now)
|
||||||
|
{
|
||||||
|
if (!airborne || now < 0) return -1;
|
||||||
|
if (since < 0 || now < since) return now;
|
||||||
|
return since;
|
||||||
|
}
|
||||||
|
static bool FlightComplete(float since, float now)
|
||||||
|
{
|
||||||
|
return since >= 0 && now >= since && now - since >= 30000;
|
||||||
|
}
|
||||||
static bool CanDepart(float since, float now)
|
static bool CanDepart(float since, float now)
|
||||||
{
|
{
|
||||||
return since >= 0 && now >= since && now - since >= 8000;
|
return since >= 0 && now >= since && now - since >= 8000;
|
||||||
@@ -234,6 +244,7 @@ class ERR_ExtractionService
|
|||||||
protected bool m_Stopped;
|
protected bool m_Stopped;
|
||||||
protected vector m_Center;
|
protected vector m_Center;
|
||||||
protected float m_Radius;
|
protected float m_Radius;
|
||||||
|
protected float m_AirborneSince = -1;
|
||||||
protected ERR_ExtractionState m_State;
|
protected ERR_ExtractionState m_State;
|
||||||
protected ref set<UUID> m_Escapees = new set<UUID>();
|
protected ref set<UUID> m_Escapees = new set<UUID>();
|
||||||
protected int m_ParticipantCount;
|
protected int m_ParticipantCount;
|
||||||
@@ -328,8 +339,12 @@ class ERR_ExtractionService
|
|||||||
if (m_State != ERR_ExtractionState.WAITING || !m_Assigned) return;
|
if (m_State != ERR_ExtractionState.WAITING || !m_Assigned) return;
|
||||||
if (!FleetAlive()) { Resolve(ERR_ExtractionState.LOST); return; }
|
if (!FleetAlive()) { Resolve(ERR_ExtractionState.LOST); return; }
|
||||||
if (m_Stage != 5 || phase != ERR_Phase.EXTRACTION) return;
|
if (m_Stage != 5 || phase != ERR_Phase.EXTRACTION) return;
|
||||||
|
bool airborne = !m_Transports.IsEmpty();
|
||||||
foreach (ERR_RescueFlightComponent departing : m_Transports)
|
foreach (ERR_RescueFlightComponent departing : m_Transports)
|
||||||
if (vector.DistanceXZ(departing.Aircraft().GetOrigin(), m_Pickup) < 1000 || vector.DistanceXZ(departing.Aircraft().GetOrigin(), m_Center) <= m_Radius) return;
|
if (!departing || !departing.AirborneAfterPickup()) airborne = false;
|
||||||
|
float now = GetGame().GetWorld().GetWorldTime();
|
||||||
|
m_AirborneSince = ERR_RescueRules.AirborneSince(airborne, m_AirborneSince, now);
|
||||||
|
if (!ERR_RescueRules.FlightComplete(m_AirborneSince, now)) return;
|
||||||
array<ref ERR_PlayerLife> lives = {};
|
array<ref ERR_PlayerLife> lives = {};
|
||||||
m_Roster.Participants(lives);
|
m_Roster.Participants(lives);
|
||||||
foreach (ERR_PlayerLife life : lives)
|
foreach (ERR_PlayerLife life : lives)
|
||||||
@@ -359,6 +374,13 @@ class ERR_ExtractionService
|
|||||||
}
|
}
|
||||||
void Stop()
|
void Stop()
|
||||||
{
|
{
|
||||||
|
// Capture the final aircraft state before terminal cleanup releases autopilots
|
||||||
|
// and removes the AI groups. Keep the same occupied airframes until reload.
|
||||||
|
if (m_State == ERR_ExtractionState.ESCAPED)
|
||||||
|
{
|
||||||
|
foreach (ERR_RescueFlightComponent safeTransport : m_Transports) if (safeTransport) safeTransport.HoldForResults();
|
||||||
|
foreach (ERR_RescueFlightComponent safeEscort : m_Escorts) if (safeEscort && !safeEscort.Lost()) safeEscort.HoldForResults();
|
||||||
|
}
|
||||||
m_Stopped = true; m_RadioPending = ""; m_PreviousSignals.Clear();
|
m_Stopped = true; m_RadioPending = ""; m_PreviousSignals.Clear();
|
||||||
foreach (ERR_RescueFlightComponent transport : m_Transports) if (transport) transport.Release(false);
|
foreach (ERR_RescueFlightComponent transport : m_Transports) if (transport) transport.Release(false);
|
||||||
foreach (ERR_RescueFlightComponent escort : m_Escorts) if (escort) escort.Release(false);
|
foreach (ERR_RescueFlightComponent escort : m_Escorts) if (escort) escort.Release(false);
|
||||||
|
|||||||
@@ -65,6 +65,9 @@ class ERR_GameMode : SCR_BaseGameMode
|
|||||||
protected ref ERR_Performance m_Performance;
|
protected ref ERR_Performance m_Performance;
|
||||||
ERR_Performance Performance() { return m_Performance; }
|
ERR_Performance Performance() { return m_Performance; }
|
||||||
protected ref ERR_CivilianTraffic m_CivilianTraffic;
|
protected ref ERR_CivilianTraffic m_CivilianTraffic;
|
||||||
|
protected ref ERR_GarageVehicles m_GarageVehicles;
|
||||||
|
protected ref ERR_CrashSites m_CrashSites;
|
||||||
|
protected ref ERR_ShoreBoats m_ShoreBoats;
|
||||||
protected ref ERR_ForceLedger m_Forces = new ERR_ForceLedger();
|
protected ref ERR_ForceLedger m_Forces = new ERR_ForceLedger();
|
||||||
protected ref ERR_ForceSimulation m_ForceSimulation;
|
protected ref ERR_ForceSimulation m_ForceSimulation;
|
||||||
protected ref array<AIAgent> m_ForceAgents = {};
|
protected ref array<AIAgent> m_ForceAgents = {};
|
||||||
@@ -143,6 +146,9 @@ class ERR_GameMode : SCR_BaseGameMode
|
|||||||
ERR_ForceLedger Forces() { return m_Forces; }
|
ERR_ForceLedger Forces() { return m_Forces; }
|
||||||
ERR_CommandService Command() { return m_Command; }
|
ERR_CommandService Command() { return m_Command; }
|
||||||
ERR_CivilianTraffic CivilianTraffic() { return m_CivilianTraffic; }
|
ERR_CivilianTraffic CivilianTraffic() { return m_CivilianTraffic; }
|
||||||
|
ERR_GarageVehicles GarageVehicles() { return m_GarageVehicles; }
|
||||||
|
ERR_CrashSites CrashSites() { return m_CrashSites; }
|
||||||
|
ERR_ShoreBoats ShoreBoats() { return m_ShoreBoats; }
|
||||||
ERR_Objectives Objectives() { return m_Objectives; }
|
ERR_Objectives Objectives() { return m_Objectives; }
|
||||||
ERR_ExtractionService Extraction() { return m_Extraction; }
|
ERR_ExtractionService Extraction() { return m_Extraction; }
|
||||||
string EndReason() { return m_EndReason; }
|
string EndReason() { return m_EndReason; }
|
||||||
@@ -546,6 +552,15 @@ class ERR_GameMode : SCR_BaseGameMode
|
|||||||
m_Command = CreateCommandService();
|
m_Command = CreateCommandService();
|
||||||
m_CivilianTraffic = new ERR_CivilianTraffic(this);
|
m_CivilianTraffic = new ERR_CivilianTraffic(this);
|
||||||
OnStop().Insert(m_CivilianTraffic.Stop);
|
OnStop().Insert(m_CivilianTraffic.Stop);
|
||||||
|
m_GarageVehicles = new ERR_GarageVehicles(this);
|
||||||
|
OnStop().Insert(m_GarageVehicles.Stop);
|
||||||
|
m_GarageVehicles.Start();
|
||||||
|
m_CrashSites = new ERR_CrashSites(this);
|
||||||
|
OnStop().Insert(m_CrashSites.Stop);
|
||||||
|
m_CrashSites.Start();
|
||||||
|
m_ShoreBoats = new ERR_ShoreBoats(this);
|
||||||
|
OnStop().Insert(m_ShoreBoats.Stop);
|
||||||
|
m_ShoreBoats.Start();
|
||||||
GetGame().GetCallqueue().CallLater(ServerTick, 1000, true);
|
GetGame().GetCallqueue().CallLater(ServerTick, 1000, true);
|
||||||
GetGame().GetCallqueue().CallLater(StartScenario, 1500, false);
|
GetGame().GetCallqueue().CallLater(StartScenario, 1500, false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,9 @@
|
|||||||
class ERR_CampTerrain
|
class ERR_CampTerrain
|
||||||
{
|
{
|
||||||
protected ref array<vector> m_Airfields = {};
|
protected ref array<vector> m_Airfields = {};
|
||||||
|
protected string m_Rejection;
|
||||||
|
string Rejection() { return m_Rejection; }
|
||||||
|
protected bool Reject(string reason) { m_Rejection = reason; return false; }
|
||||||
protected bool Airport(IEntity entity)
|
protected bool Airport(IEntity entity)
|
||||||
{
|
{
|
||||||
if (!entity || !entity.GetPrefabData()) return false;
|
if (!entity || !entity.GetPrefabData()) return false;
|
||||||
@@ -27,21 +30,19 @@ class ERR_CampTerrain
|
|||||||
}
|
}
|
||||||
bool Clear(vector center, float heading, float halfSize, float maximumRange, out float lowest, float clearanceHeight = 4)
|
bool Clear(vector center, float heading, float halfSize, float maximumRange, out float lowest, float clearanceHeight = 4)
|
||||||
{
|
{
|
||||||
|
m_Rejection = "";
|
||||||
lowest = float.MAX; float highest = -float.MAX;
|
lowest = float.MAX; float highest = -float.MAX;
|
||||||
for (int x = -1; x <= 1; x++)
|
|
||||||
for (int z = -1; z <= 1; z++)
|
|
||||||
{
|
|
||||||
vector corner = center + Rotate(Vector(x * halfSize, 0, z * halfSize), heading);
|
|
||||||
if (ChimeraWorldUtils.TryGetWaterSurfaceSimple(GetGame().GetWorld(), corner)) return false;
|
|
||||||
}
|
|
||||||
// A 2m grid covers the walls, door approach, furniture and all interior slots.
|
// A 2m grid covers the walls, door approach, furniture and all interior slots.
|
||||||
for (float dx = -halfSize; dx <= halfSize; dx += 2)
|
for (float dx = -halfSize; dx <= halfSize; dx += 2)
|
||||||
for (float dz = -halfSize; dz <= halfSize; dz += 2)
|
for (float dz = -halfSize; dz <= halfSize; dz += 2)
|
||||||
{
|
{
|
||||||
vector sample = center + Rotate(Vector(dx, 0, dz), heading);
|
vector sample = center + Rotate(Vector(dx, 0, dz), heading);
|
||||||
float height = GetGame().GetWorld().GetSurfaceY(sample[0], sample[2]);
|
float height = GetGame().GetWorld().GetSurfaceY(sample[0], sample[2]);
|
||||||
|
if (!ERR_ForceRules.Finite(height)) return Reject("invalid terrain height");
|
||||||
|
sample[1] = height;
|
||||||
|
if (ChimeraWorldUtils.TryGetWaterSurfaceSimple(GetGame().GetWorld(), sample)) return Reject("water");
|
||||||
lowest = Math.Min(lowest, height); highest = Math.Max(highest, height);
|
lowest = Math.Min(lowest, height); highest = Math.Max(highest, height);
|
||||||
if (lowest < 1 || highest - lowest > maximumRange) return false;
|
if (highest - lowest > maximumRange) return Reject("uneven ground");
|
||||||
}
|
}
|
||||||
for (float cx = -halfSize + 1; cx < halfSize; cx += 2)
|
for (float cx = -halfSize + 1; cx < halfSize; cx += 2)
|
||||||
for (float cz = -halfSize + 1; cz < halfSize; cz += 2)
|
for (float cz = -halfSize + 1; cz < halfSize; cz += 2)
|
||||||
@@ -53,33 +54,42 @@ class ERR_CampTerrain
|
|||||||
// Bounding the rotated cell conservatively also protects the exit corridor.
|
// Bounding the rotated cell conservatively also protects the exit corridor.
|
||||||
trace.Mins = "-1.42 0 -1.42"; trace.Maxs = "1.42 0.1 1.42";
|
trace.Mins = "-1.42 0 -1.42"; trace.Maxs = "1.42 0.1 1.42";
|
||||||
trace.Flags = TraceFlags.ENTS | TraceFlags.WORLD;
|
trace.Flags = TraceFlags.ENTS | TraceFlags.WORLD;
|
||||||
if (GetGame().GetWorld().TraceMove(trace, null) < 1) return false;
|
if (GetGame().GetWorld().TraceMove(trace, null) < 1) return Reject("obstructed footprint");
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
bool Site(ERR_SiteDefinition site, vector position, float heading, out float baseHeight)
|
bool Site(ERR_SiteDefinition site, vector position, float heading, out float baseHeight, float airportBuffer = 1500)
|
||||||
{
|
{
|
||||||
|
m_Rejection = "";
|
||||||
bool prison = site.Kind == ERR_LocationKind.PRISON;
|
bool prison = site.Kind == ERR_LocationKind.PRISON;
|
||||||
if (prison)
|
if (prison)
|
||||||
{
|
{
|
||||||
foreach (vector airport : m_Airfields) if (vector.DistanceXZ(position, airport) < 1500) return false;
|
foreach (vector airport : m_Airfields) if (vector.DistanceXZ(position, airport) < airportBuffer) return Reject("airport separation");
|
||||||
}
|
}
|
||||||
|
bool extraction = site.Kind == ERR_LocationKind.EXTRACTION;
|
||||||
|
// Camps must stay off roads; a helicopter pad may use clear paved ground.
|
||||||
|
// Its physical terrain, obstacle and fleet checks still decide acceptance.
|
||||||
|
if (!extraction)
|
||||||
|
{
|
||||||
SCR_AIWorld aiWorld = SCR_AIWorld.Cast(GetGame().GetAIWorld());
|
SCR_AIWorld aiWorld = SCR_AIWorld.Cast(GetGame().GetAIWorld());
|
||||||
if (!aiWorld || !aiWorld.GetRoadNetworkManager()) return false;
|
if (!aiWorld || !aiWorld.GetRoadNetworkManager()) return Reject("road network unavailable");
|
||||||
BaseRoad road; float distance;
|
BaseRoad road; float distance;
|
||||||
aiWorld.GetRoadNetworkManager().GetClosestRoad(position, road, distance, skipNavlinks: true);
|
aiWorld.GetRoadNetworkManager().GetClosestRoad(position, road, distance, skipNavlinks: true);
|
||||||
if (road && distance < 12) return false;
|
if (road && distance < 12) return Reject("road proximity");
|
||||||
if (!Clear(position, heading, 10, 0.6, baseHeight)) return false;
|
}
|
||||||
|
float maximumRange = 0.6;
|
||||||
|
if (extraction) maximumRange = 0.8;
|
||||||
|
if (!Clear(position, heading, 10, maximumRange, baseHeight)) return false;
|
||||||
if (ERR_StartSetup.IsCrash(site))
|
if (ERR_StartSetup.IsCrash(site))
|
||||||
{
|
{
|
||||||
vector offset, center; float halfSize, height;
|
vector offset, center; float halfSize, height;
|
||||||
if (!ERR_StartSetup.Geometry(offset, center, halfSize, height)) return false;
|
if (!ERR_StartSetup.Geometry(offset, center, halfSize, height)) return Reject("crash footprint unavailable");
|
||||||
float wreckFloor;
|
float wreckFloor;
|
||||||
if (!Clear(position + Rotate(center, heading), heading, halfSize, 0.6, wreckFloor, height)) return false;
|
if (!Clear(position + Rotate(center, heading), heading, halfSize, 0.6, wreckFloor, height)) return Reject("wreck area: " + m_Rejection);
|
||||||
}
|
}
|
||||||
float guardHeight;
|
float guardHeight;
|
||||||
if (site.Guarded && !Clear(position + Rotate(site.GuardOffset, heading), heading, 6, 0.6, guardHeight)) return false;
|
if (site.Guarded && !Clear(position + Rotate(site.GuardOffset, heading), heading, 6, 0.6, guardHeight)) return Reject("guard area: " + m_Rejection);
|
||||||
if (site.ResponseCount > 0 && !Clear(position + Rotate(site.ResponseOffset, heading), heading, 6, 0.6, guardHeight)) return false;
|
if (site.ResponseCount > 0 && !Clear(position + Rotate(site.ResponseOffset, heading), heading, 6, 0.6, guardHeight)) return Reject("patrol area: " + m_Rejection);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -90,6 +100,38 @@ class ERR_GeneratedSites
|
|||||||
protected ref array<ERR_SiteDefinition> m_Pending = {};
|
protected ref array<ERR_SiteDefinition> m_Pending = {};
|
||||||
protected vector m_Low, m_High;
|
protected vector m_Low, m_High;
|
||||||
protected int m_Index, m_Attempts, m_Began;
|
protected int m_Index, m_Attempts, m_Began;
|
||||||
|
protected int m_NextDiagnostic;
|
||||||
|
protected ref map<string, int> m_Rejections = new map<string, int>();
|
||||||
|
protected void RecordRejection(string reason)
|
||||||
|
{
|
||||||
|
if (reason.IsEmpty()) reason = "unspecified terrain clearance";
|
||||||
|
int count;
|
||||||
|
m_Rejections.Find(reason, count);
|
||||||
|
m_Rejections.Set(reason, count + 1);
|
||||||
|
}
|
||||||
|
protected string RejectionSummary()
|
||||||
|
{
|
||||||
|
string summary;
|
||||||
|
foreach (string reason, int count : m_Rejections)
|
||||||
|
{
|
||||||
|
if (!summary.IsEmpty()) summary += "; ";
|
||||||
|
summary += reason + "=" + count.ToString();
|
||||||
|
}
|
||||||
|
if (summary.IsEmpty()) return "none recorded";
|
||||||
|
return summary;
|
||||||
|
}
|
||||||
|
protected void ReportSearch(ERR_SiteDefinition site, string stage)
|
||||||
|
{
|
||||||
|
PrintFormat("Reintroduced [placement search]: stage=%1 site=%2 (%3) attempts=%4 elapsedMs=%5 failed/deferred checks: %6", stage, site.Id, site.Label, m_Attempts, System.GetTickCount() - m_Began, RejectionSummary());
|
||||||
|
}
|
||||||
|
protected void ReportProgress(ERR_SiteDefinition site)
|
||||||
|
{
|
||||||
|
int now = System.GetTickCount();
|
||||||
|
if (now - m_Began < 30000 || now < m_NextDiagnostic) return;
|
||||||
|
ReportSearch(site, "still searching");
|
||||||
|
m_NextDiagnostic = now + 30000;
|
||||||
|
}
|
||||||
|
protected void ClearDiagnostics() { m_Rejections.Clear(); m_NextDiagnostic = 0; }
|
||||||
protected bool m_Started;
|
protected bool m_Started;
|
||||||
protected ERR_PrepareResult m_Result = ERR_PrepareResult.PENDING;
|
protected ERR_PrepareResult m_Result = ERR_PrepareResult.PENDING;
|
||||||
protected string m_Error;
|
protected string m_Error;
|
||||||
@@ -144,6 +186,7 @@ class ERR_GeneratedSites
|
|||||||
if (candidate.Kind == ERR_LocationKind.RADIO && other.Kind == ERR_LocationKind.PRISON) minimum = 1000;
|
if (candidate.Kind == ERR_LocationKind.RADIO && other.Kind == ERR_LocationKind.PRISON) minimum = 1000;
|
||||||
if (vector.DistanceXZ(position, other.Position) < minimum) return false;
|
if (vector.DistanceXZ(position, other.Position) < minimum) return false;
|
||||||
if (candidate.Kind == ERR_LocationKind.PRISON && other.Kind == ERR_LocationKind.EXTRACTION && vector.DistanceXZ(position, other.Position) + 100 >= ERR_GameMode.Get().EscapeRadius()) return false;
|
if (candidate.Kind == ERR_LocationKind.PRISON && other.Kind == ERR_LocationKind.EXTRACTION && vector.DistanceXZ(position, other.Position) + 100 >= ERR_GameMode.Get().EscapeRadius()) return false;
|
||||||
|
if (candidate.Kind == ERR_LocationKind.EXTRACTION && other.Kind == ERR_LocationKind.PRISON && vector.DistanceXZ(position, other.Position) + 100 >= ERR_GameMode.Get().EscapeRadius()) return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -169,6 +212,10 @@ class ERR_GeneratedSites
|
|||||||
{
|
{
|
||||||
if (site.Kind != ERR_LocationKind.EXTRACTION) return true;
|
if (site.Kind != ERR_LocationKind.EXTRACTION) return true;
|
||||||
vector primary = ERR_Actors.Ground(position + ERR_CampTerrain.Rotate(site.AircraftOffset, heading));
|
vector primary = ERR_Actors.Ground(position + ERR_CampTerrain.Rotate(site.AircraftOffset, heading));
|
||||||
|
// Validate the primary pad before committing a random candidate, so an
|
||||||
|
// obstructed clearing is retried instead of failing later during site creation.
|
||||||
|
if (site.RequireAirfield) { if (!ERR_AirfieldPlacement.Clear(primary)) return false; }
|
||||||
|
else if (!ERR_AirfieldPlacement.ClearGround(primary)) return false;
|
||||||
return ERR_RescueLanding.Fits(primary);
|
return ERR_RescueLanding.Fits(primary);
|
||||||
}
|
}
|
||||||
ERR_PrepareResult Tick()
|
ERR_PrepareResult Tick()
|
||||||
@@ -180,6 +227,7 @@ class ERR_GeneratedSites
|
|||||||
{
|
{
|
||||||
if (m_StartupOnly && StartupReady()) return ERR_PrepareResult.PENDING;
|
if (m_StartupOnly && StartupReady()) return ERR_PrepareResult.PENDING;
|
||||||
ERR_SiteDefinition site = m_Pending[m_Index];
|
ERR_SiteDefinition site = m_Pending[m_Index];
|
||||||
|
ReportProgress(site);
|
||||||
bool fallback = m_Attempts >= 12000 || System.GetTickCount() - m_Began >= 30000;
|
bool fallback = m_Attempts >= 12000 || System.GetTickCount() - m_Began >= 30000;
|
||||||
vector position = site.Position; float heading = site.Heading;
|
vector position = site.Position; float heading = site.Heading;
|
||||||
if (!fallback)
|
if (!fallback)
|
||||||
@@ -188,22 +236,30 @@ class ERR_GeneratedSites
|
|||||||
heading = Math.RandomFloat(0, 360); m_Attempts++;
|
heading = Math.RandomFloat(0, 360); m_Attempts++;
|
||||||
}
|
}
|
||||||
float floor;
|
float floor;
|
||||||
if (Separated(site, position, fallback) && m_Terrain.Site(site, position, heading, floor) && RescueFits(site, position, heading))
|
bool fits;
|
||||||
|
if (!Separated(site, position, fallback)) RecordRejection("site spacing / escape boundary");
|
||||||
|
else if (!m_Terrain.Site(site, position, heading, floor)) RecordRejection(m_Terrain.Rejection());
|
||||||
|
else if (!RescueFits(site, position, heading)) RecordRejection("aircraft landing clearance / fleet space");
|
||||||
|
else fits = true;
|
||||||
|
if (fits)
|
||||||
{
|
{
|
||||||
position[1] = floor + 0.02;
|
position[1] = floor + 0.02;
|
||||||
site.Position = position; site.Heading = heading; site.PlacementResolved = true;
|
site.Position = position; site.Heading = heading; site.PlacementResolved = true;
|
||||||
PrintFormat("Co10 Escape Reintroduced [generated site]: id=%1 position=%2 heading=%3 attempts=%4 fallback=%5", site.Id, position, heading, m_Attempts, fallback);
|
PrintFormat("Co10 Escape Reintroduced [generated site]: id=%1 position=%2 heading=%3 attempts=%4 fallback=%5", site.Id, position, heading, m_Attempts, fallback);
|
||||||
|
ReportSearch(site, "placed"); ClearDiagnostics();
|
||||||
m_Index++; m_Attempts = 0; m_Began = System.GetTickCount();
|
m_Index++; m_Attempts = 0; m_Began = System.GetTickCount();
|
||||||
}
|
}
|
||||||
else if (fallback)
|
else if (fallback)
|
||||||
{
|
{
|
||||||
|
ReportSearch(site, "exhausted; authored fallback rejected");
|
||||||
if (SkipOptional(site))
|
if (SkipOptional(site))
|
||||||
{
|
{
|
||||||
|
ClearDiagnostics();
|
||||||
m_Index++; m_Attempts = 0; m_Began = System.GetTickCount();
|
m_Index++; m_Attempts = 0; m_Began = System.GetTickCount();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
m_Error = "No safe terrain or valid authored fallback for required site " + site.Id;
|
m_Error = "No safe terrain or valid authored fallback for required site " + site.Id + ". Checks: " + RejectionSummary();
|
||||||
m_Result = ERR_PrepareResult.FAILED; return m_Result;
|
m_Result = ERR_PrepareResult.FAILED; return m_Result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,6 +41,12 @@ class ERR_ItemPreferences
|
|||||||
for (int i = 0; i < 16; i++) if ("0123456789abcdef".IndexOf(value.Substring(i, 1)) < 0) return false;
|
for (int i = 0; i < 16; i++) if ("0123456789abcdef".IndexOf(value.Substring(i, 1)) < 0) return false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
static bool RoleKey(string key)
|
||||||
|
{
|
||||||
|
if (key.Length() != 3 && key.Length() != 18) return false;
|
||||||
|
if (key.Substring(0, 1) != "r" || "012345".IndexOf(key.Substring(1, 1)) < 0) return false;
|
||||||
|
return key.Substring(2, key.Length() - 2) == "*" || Guid(key.Substring(2, key.Length() - 2));
|
||||||
|
}
|
||||||
static bool WeightKey(string key)
|
static bool WeightKey(string key)
|
||||||
{
|
{
|
||||||
return key == "w*" || (key.Length() == 17 && key.Substring(0, 1) == "w" && Guid(key.Substring(1, 16)));
|
return key == "w*" || (key.Length() == 17 && key.Substring(0, 1) == "w" && Guid(key.Substring(1, 16)));
|
||||||
@@ -67,7 +73,7 @@ class ERR_ItemPreferences
|
|||||||
{
|
{
|
||||||
if (parts.Count() != 4 || !Name(parts[0]) || !Guid(parts[1])) return false;
|
if (parts.Count() != 4 || !Name(parts[0]) || !Guid(parts[1])) return false;
|
||||||
if (WeightKey(parts[2])) { if (!WeightValue(parts[3])) return false; }
|
if (WeightKey(parts[2])) { if (!WeightValue(parts[3])) return false; }
|
||||||
else if ((parts[2] != "*" && !Guid(parts[2])) || (parts[3] != "0" && parts[3] != "1")) return false;
|
else if ((parts[2] != "*" && parts[2] != "mod" && parts[2] != "v*" && !RoleKey(parts[2]) && !Guid(parts[2]) && !(parts[2].Length() == 17 && parts[2].StartsWith("v") && Guid(parts[2].Substring(1, 16)))) || (parts[3] != "0" && parts[3] != "1")) return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (names.Count() > 17) return false;
|
if (names.Count() > 17) return false;
|
||||||
@@ -137,13 +143,91 @@ class ERR_ItemPreferences
|
|||||||
key = addon + "|*";
|
key = addon + "|*";
|
||||||
return !m_Rules.Contains(key) || m_Rules.Get(key) == 1;
|
return !m_Rules.Contains(key) || m_Rules.Get(key) == 1;
|
||||||
}
|
}
|
||||||
|
bool ModEnabled(string addon)
|
||||||
|
{
|
||||||
|
string key = addon + "|mod";
|
||||||
|
return !m_Rules.Contains(key) || m_Rules.Get(key) == 1;
|
||||||
|
}
|
||||||
|
bool VehicleEnabled(string addon, string item)
|
||||||
|
{
|
||||||
|
string key = addon + "|v" + item;
|
||||||
|
if (m_Rules.Contains(key)) return m_Rules.Get(key) == 1;
|
||||||
|
key = addon + "|v*";
|
||||||
|
return !m_Rules.Contains(key) || m_Rules.Get(key) == 1;
|
||||||
|
}
|
||||||
|
bool SetSwitch(string addon, string key, bool enabled)
|
||||||
|
{
|
||||||
|
if (!Guid(addon) || (key != "mod" && key != "v*" && !(key.Length() == 17 && key.StartsWith("v") && Guid(key.Substring(1, 16))))) return false;
|
||||||
|
array<string> next = {};
|
||||||
|
foreach (string row : m_Records)
|
||||||
|
{
|
||||||
|
array<string> parts = {}; row.Split("|", parts, false);
|
||||||
|
if (parts.Count() == 4 && parts[0] == "active" && parts[1] == addon && (parts[2] == key || (key == "v*" && parts[2].StartsWith("v")))) continue;
|
||||||
|
next.Insert(row);
|
||||||
|
}
|
||||||
|
string value = "0"; if (enabled) value = "1";
|
||||||
|
next.Insert("active|" + addon + "|" + key + "|" + value);
|
||||||
|
return Save(next);
|
||||||
|
}
|
||||||
|
bool SetVehicleRole(string addon, string item, int role, bool enabled)
|
||||||
|
{
|
||||||
|
if (!Guid(addon) || (item != "*" && !Guid(item)) || role < 0 || role > 5) return false;
|
||||||
|
string prefix = "r" + role.ToString(), key = prefix + item;
|
||||||
|
array<string> next = {};
|
||||||
|
foreach (string row : m_Records)
|
||||||
|
{
|
||||||
|
array<string> parts = {}; row.Split("|", parts, false);
|
||||||
|
if (parts.Count() == 4 && parts[0] == "active" && parts[1] == addon)
|
||||||
|
{
|
||||||
|
if (parts[2] == key || (item == "*" && parts[2].StartsWith(prefix))) continue;
|
||||||
|
if (enabled && (parts[2] == "v" + item || (item == "*" && parts[2].StartsWith("v")))) continue;
|
||||||
|
}
|
||||||
|
next.Insert(row);
|
||||||
|
}
|
||||||
|
string value = "0"; if (enabled) value = "1";
|
||||||
|
next.Insert("active|" + addon + "|" + key + "|" + value);
|
||||||
|
if (enabled) next.Insert("active|" + addon + "|v" + item + "|1");
|
||||||
|
return Save(next);
|
||||||
|
}
|
||||||
|
// -1 means inherit the mission's defaults. Shared-source explicit exclusions win.
|
||||||
|
int VehicleRole(string addon, string item, int role)
|
||||||
|
{
|
||||||
|
string prefix = addon + "|r" + role.ToString();
|
||||||
|
if (m_Rules.Contains(prefix + item)) return m_Rules.Get(prefix + item);
|
||||||
|
if (m_Rules.Contains(prefix + "*")) return m_Rules.Get(prefix + "*");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
bool AllowsVehicleRole(ResourceName prefab, int role, bool defaultEnabled)
|
||||||
|
{
|
||||||
|
if (role < 0 || role > 5 || !AllowsVehicle(prefab)) return false;
|
||||||
|
string item = ERR_TerrainCatalog.Key(prefab);
|
||||||
|
bool enabled = defaultEnabled;
|
||||||
|
array<string> sources = SCR_AddonTool.GetResourceAddons(prefab);
|
||||||
|
foreach (string project : sources)
|
||||||
|
{
|
||||||
|
if (!m_Loaded.Contains(project)) continue;
|
||||||
|
int rule = VehicleRole(m_Loaded.Get(project), item, role);
|
||||||
|
if (rule == 0) return false;
|
||||||
|
if (rule == 1) enabled = true;
|
||||||
|
}
|
||||||
|
return enabled;
|
||||||
|
}
|
||||||
|
bool AllowsVehicle(ResourceName prefab)
|
||||||
|
{
|
||||||
|
string item = ERR_TerrainCatalog.Key(prefab);
|
||||||
|
if (item.IsEmpty()) return false;
|
||||||
|
array<string> sources = SCR_AddonTool.GetResourceAddons(prefab);
|
||||||
|
foreach (string project : sources)
|
||||||
|
if (m_Loaded.Contains(project) && (!ModEnabled(m_Loaded.Get(project)) || !VehicleEnabled(m_Loaded.Get(project), item))) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
bool Allows(ResourceName prefab)
|
bool Allows(ResourceName prefab)
|
||||||
{
|
{
|
||||||
string item = ERR_TerrainCatalog.Key(prefab);
|
string item = ERR_TerrainCatalog.Key(prefab);
|
||||||
if (item.IsEmpty()) return true;
|
if (item.IsEmpty()) return true;
|
||||||
array<string> sources = SCR_AddonTool.GetResourceAddons(prefab);
|
array<string> sources = SCR_AddonTool.GetResourceAddons(prefab);
|
||||||
foreach (string project : sources)
|
foreach (string project : sources)
|
||||||
if (m_Loaded.Contains(project) && !Enabled(m_Loaded.Get(project), item)) return false;
|
if (m_Loaded.Contains(project) && (!ModEnabled(m_Loaded.Get(project)) || !Enabled(m_Loaded.Get(project), item))) return false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
bool Set(string addon, string item, bool enabled)
|
bool Set(string addon, string item, bool enabled)
|
||||||
@@ -153,7 +237,7 @@ class ERR_ItemPreferences
|
|||||||
foreach (string row : m_Records)
|
foreach (string row : m_Records)
|
||||||
{
|
{
|
||||||
array<string> parts = {}; row.Split("|", parts, false);
|
array<string> parts = {}; row.Split("|", parts, false);
|
||||||
if (parts.Count() == 4 && parts[0] == "active" && parts[1] == addon && (item == "*" || parts[2] == item) && !WeightKey(parts[2])) continue;
|
if (parts.Count() == 4 && parts[0] == "active" && parts[1] == addon && (parts[2] == item || (item == "*" && (parts[2] == "*" || Guid(parts[2]))))) continue;
|
||||||
next.Insert(row);
|
next.Insert(row);
|
||||||
}
|
}
|
||||||
string value = "0"; if (enabled) value = "1";
|
string value = "0"; if (enabled) value = "1";
|
||||||
@@ -225,6 +309,6 @@ class ERR_ItemPreferences
|
|||||||
{
|
{
|
||||||
foreach (string row : m_Records) if (row != "active" && row.IndexOf("|") < 0) values.Insert("itemPreset=" + row);
|
foreach (string row : m_Records) if (row != "active" && row.IndexOf("|") < 0) values.Insert("itemPreset=" + row);
|
||||||
if (m_ReadOnly) values.Insert("itemsStorage=Read-only: preference file needs repair");
|
if (m_ReadOnly) values.Insert("itemsStorage=Read-only: preference file needs repair");
|
||||||
else values.Insert("itemsStorage=Auto-saved; applies to future random equipment. Presets include selections and probability weights for all mods.");
|
else values.Insert("itemsStorage=Auto-saved for future spawns. Presets include mod switches, item and vehicle-role choices, and equipment weights.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -131,7 +131,7 @@ modded class ERR_AdminMenu
|
|||||||
{
|
{
|
||||||
GetRootWidget().Update();
|
GetRootWidget().Update();
|
||||||
PrintFormat("ERR MOD MENU: page=%1 rows=%2 saved=%3 active=%4", m_Page, m_ModRows.Count(), m_ModSaved.Count(), m_ModActive.Count());
|
PrintFormat("ERR MOD MENU: page=%1 rows=%2 saved=%3 active=%4", m_Page, m_ModRows.Count(), m_ModSaved.Count(), m_ModActive.Count());
|
||||||
if (m_Page != 3 || m_ModRows.Count() != ERR_ContentRegistry.Ids().Count()) return false;
|
if (m_Page != ERR_AdminPage.PACKS || m_ModRows.Count() != ERR_ContentRegistry.Ids().Count()) return false;
|
||||||
foreach (string pack, Widget row : m_ModRows)
|
foreach (string pack, Widget row : m_ModRows)
|
||||||
{
|
{
|
||||||
TextWidget text = TextWidget.Cast(row.FindAnyWidget("PackState"));
|
TextWidget text = TextWidget.Cast(row.FindAnyWidget("PackState"));
|
||||||
@@ -286,7 +286,7 @@ class ERR_NativeAdminMenuProbe
|
|||||||
Pass("all thirteen setting descriptions fit their native text widgets");
|
Pass("all thirteen setting descriptions fit their native text widgets");
|
||||||
System.MakeScreenshot("$logs:err-admin-open");
|
System.MakeScreenshot("$logs:err-admin-open");
|
||||||
if (!menu.ERR_ProbeNavigation(0)) return Fail("Esc entry did not open Mission page with shared navigation");
|
if (!menu.ERR_ProbeNavigation(0)) return Fail("Esc entry did not open Mission page with shared navigation");
|
||||||
menu.ERR_ProbePage(3);
|
menu.ERR_ProbePage(ERR_AdminPage.PACKS);
|
||||||
if (!menu.ERR_ProbeMods()) return Fail("mod rows missing state or collapsed");
|
if (!menu.ERR_ProbeMods()) return Fail("mod rows missing state or collapsed");
|
||||||
Print("ERR MOD CAPTURE READY");
|
Print("ERR MOD CAPTURE READY");
|
||||||
menu.ERR_ProbePage(0);
|
menu.ERR_ProbePage(0);
|
||||||
@@ -335,7 +335,7 @@ class ERR_NativeAdminMenuProbe
|
|||||||
if (m_Mode.Settings().Get("radioDefenceSeconds") != m_RadioSeconds) return Fail("test setting restoration failed");
|
if (m_Mode.Settings().Get("radioDefenceSeconds") != m_RadioSeconds) return Fail("test setting restoration failed");
|
||||||
Pass("test setting restored without resetting menu drafts");
|
Pass("test setting restored without resetting menu drafts");
|
||||||
System.MakeScreenshot("$logs:err-admin-hq");
|
System.MakeScreenshot("$logs:err-admin-hq");
|
||||||
menu.ERR_ProbePage(3);
|
menu.ERR_ProbePage(ERR_AdminPage.PACKS);
|
||||||
menu.ERR_ProbeMod(!m_RhsSaved);
|
menu.ERR_ProbeMod(!m_RhsSaved);
|
||||||
m_Wait = now + 2000; m_Stage = 11; return 0;
|
m_Wait = now + 2000; m_Stage = 11; return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -168,7 +168,7 @@ modded class ERR_GameMode
|
|||||||
if (!ERR_MortarIntelReady()) return;
|
if (!ERR_MortarIntelReady()) return;
|
||||||
if (!ERR_AirfieldCheck(!Sites().Find("everon-supply-2"), "guarded car-and-crate site absent")) return;
|
if (!ERR_AirfieldCheck(!Sites().Find("everon-supply-2"), "guarded car-and-crate site absent")) return;
|
||||||
ERR_SiteRuntime extraction = Sites().Find("everon-extraction-1");
|
ERR_SiteRuntime extraction = Sites().Find("everon-extraction-1");
|
||||||
if (!ERR_AirfieldCheck(extraction.Definition.RequireAirfield && vector.DistanceXZ(ExtractionVehicle().GetOrigin(), "4800.953 0 11487.412") < 3 && ERR_CasualtyLoadingRules.AircraftReady(ExtractionVehicle()), "production aircraft settled intact on validated Everon airfield pad")) return;
|
if (!ERR_AirfieldCheck(!extraction.Definition.RequireAirfield && extraction.Definition.PlacementResolved && vector.DistanceXZ(ExtractionVehicle().GetOrigin(), extraction.Definition.Position) < 3 && ERR_CasualtyLoadingRules.AircraftReady(ExtractionVehicle()), "production aircraft settled intact on validated generated landing zone")) return;
|
||||||
if (!ERR_AirfieldCheck(!ERR_AirfieldPlacement.Clear(ERR_Actors.Ground("7012 0 7500")), "old off-airfield helicopter position rejected")) return;
|
if (!ERR_AirfieldCheck(!ERR_AirfieldPlacement.Clear(ERR_Actors.Ground("7012 0 7500")), "old off-airfield helicopter position rejected")) return;
|
||||||
if (!ERR_AirfieldCheck(!ERR_AirfieldPlacement.Clear(ExtractionVehicle().GetOrigin()), "occupied airfield pad rejected")) return;
|
if (!ERR_AirfieldCheck(!ERR_AirfieldPlacement.Clear(ExtractionVehicle().GetOrigin()), "occupied airfield pad rejected")) return;
|
||||||
if (!ERR_AirfieldCheck(Sites().Find("everon-medical-1").Definition.RevealOnEscape && Sites().Find("everon-supply-1").Definition.RevealOnEscape, "two reachable intel sites retained")) return;
|
if (!ERR_AirfieldCheck(Sites().Find("everon-medical-1").Definition.RevealOnEscape && Sites().Find("everon-supply-1").Definition.RevealOnEscape, "two reachable intel sites retained")) return;
|
||||||
|
|||||||
@@ -775,6 +775,10 @@ modded class ERR_GameMode
|
|||||||
ERR_SiteRuntime medical = Sites().Find("everon-medical-1");
|
ERR_SiteRuntime medical = Sites().Find("everon-medical-1");
|
||||||
if (!ERR_BaseCheck(medical.Location.Knowledge() == 2 && medical.Location.Label() == "Medical stash" && m_ERRMedicalDiscoveries == 1, "conscious nearby player visually discovers named medical stash once")) return;
|
if (!ERR_BaseCheck(medical.Location.Knowledge() == 2 && medical.Location.Label() == "Medical stash" && m_ERRMedicalDiscoveries == 1, "conscious nearby player visually discovers named medical stash once")) return;
|
||||||
if (!ERR_BaseCheck(!Sites().CanObserve(m_ERRBasePlayer, Sites().Find("everon-radio-1")), "distant medical-site discovery does not reveal the radio")) return;
|
if (!ERR_BaseCheck(!Sites().CanObserve(m_ERRBasePlayer, Sites().Find("everon-radio-1")), "distant medical-site discovery does not reveal the radio")) return;
|
||||||
|
// This sparse historical fixture has two eligible sites. Arrange the
|
||||||
|
// remaining identification before testing document ownership and recovery.
|
||||||
|
Sites().Find("everon-outpost-1").Location.Discover(0, 100);
|
||||||
|
if (!ERR_BaseCheck(Discovery().RadioIntelAllowed(), "identified optional sites unlock the historical radio documents")) return;
|
||||||
m_ERRBaseStep = 1;
|
m_ERRBaseStep = 1;
|
||||||
}
|
}
|
||||||
else if (m_ERRBaseStep == 1 || m_ERRBaseStep == 2)
|
else if (m_ERRBaseStep == 1 || m_ERRBaseStep == 2)
|
||||||
|
|||||||
@@ -3,14 +3,14 @@
|
|||||||
modded class ERR_CampTerrain
|
modded class ERR_CampTerrain
|
||||||
{
|
{
|
||||||
static bool s_ERROptionalSiteRefused;
|
static bool s_ERROptionalSiteRefused;
|
||||||
override bool Site(ERR_SiteDefinition site, vector position, float heading, out float baseHeight)
|
override bool Site(ERR_SiteDefinition site, vector position, float heading, out float baseHeight, float airportBuffer = 1500)
|
||||||
{
|
{
|
||||||
string outcome;
|
string outcome;
|
||||||
if ((site.Id == "everon-camp-5" || site.Id == "everon-outpost-1") && System.GetCLIParam("errSmokeOutcome", outcome) && outcome == "optional-site-skip")
|
if ((site.Id == "everon-camp-5" || site.Id == "everon-outpost-1") && System.GetCLIParam("errSmokeOutcome", outcome) && outcome == "optional-site-skip")
|
||||||
{
|
{
|
||||||
s_ERROptionalSiteRefused = true; return false;
|
s_ERROptionalSiteRefused = true; return false;
|
||||||
}
|
}
|
||||||
return super.Site(site, position, heading, baseHeight);
|
return super.Site(site, position, heading, baseHeight, airportBuffer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
modded class ERR_GameMode
|
modded class ERR_GameMode
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ modded class ERR_GameMode
|
|||||||
float now = GetWorld().GetWorldTime();
|
float now = GetWorld().GetWorldTime();
|
||||||
if (Phase() == ERR_Phase.VICTORY)
|
if (Phase() == ERR_Phase.VICTORY)
|
||||||
{
|
{
|
||||||
ERR_RescueCheck(Extraction().EscapedCount() == 1, "native flight carried the original prisoner across the escape boundary");
|
ERR_RescueCheck(Extraction().EscapedCount() == 1, "native flight carried the original prisoner through thirty seconds of airborne evacuation");
|
||||||
Print("ERR RESCUE COMPLETE"); GetGame().GetCallqueue().Remove(ERR_RescueProbeTick); return;
|
Print("ERR RESCUE COMPLETE"); GetGame().GetCallqueue().Remove(ERR_RescueProbeTick); return;
|
||||||
}
|
}
|
||||||
if (!PreparationError().IsEmpty() || Phase() == ERR_Phase.DEFEAT) { ERR_RescueCheck(false, "mission failed: " + PreparationError()); return; }
|
if (!PreparationError().IsEmpty() || Phase() == ERR_Phase.DEFEAT) { ERR_RescueCheck(false, "mission failed: " + PreparationError()); return; }
|
||||||
|
|||||||
@@ -1,12 +1,22 @@
|
|||||||
#ifdef WORKBENCH
|
#ifdef WORKBENCH
|
||||||
class ERR_RoadblockProbeRules
|
class ERR_RoadblockProbeRules
|
||||||
{
|
{
|
||||||
static int Target()
|
static int SmallTarget()
|
||||||
{
|
{
|
||||||
string value;
|
string value;
|
||||||
if (System.GetCLIParam("errRoadblockProbeCount", value)) return Math.Clamp(value.ToInt(), 1, 30);
|
if (System.GetCLIParam("errRoadblockProbeCount", value)) return Math.Clamp(value.ToInt(), 0, 30);
|
||||||
return 30;
|
return 30;
|
||||||
}
|
}
|
||||||
|
static int LargeTarget()
|
||||||
|
{
|
||||||
|
string value;
|
||||||
|
if (System.GetCLIParam("errLargeRoadblockProbeCount", value)) return Math.Clamp(value.ToInt(), 0, 30);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
static int Target() { return SmallTarget() + LargeTarget(); }
|
||||||
|
static int Guards() { return SmallTarget() * 4 + LargeTarget() * 8; }
|
||||||
|
static int Props() { return SmallTarget() * 8 + LargeTarget() * 14; }
|
||||||
|
|
||||||
}
|
}
|
||||||
modded class ERR_HQ
|
modded class ERR_HQ
|
||||||
{
|
{
|
||||||
@@ -23,7 +33,11 @@ modded class ERR_Roadblocks
|
|||||||
{
|
{
|
||||||
string outcome;
|
string outcome;
|
||||||
if (!m_Begun && System.GetCLIParam("errSmokeOutcome", outcome) && outcome == "roadblocks")
|
if (!m_Begun && System.GetCLIParam("errSmokeOutcome", outcome) && outcome == "roadblocks")
|
||||||
m_Mode.Settings().Set("roadblockCount", ERR_RoadblockProbeRules.Target());
|
{
|
||||||
|
m_Mode.Settings().Set("roadblockCount", ERR_RoadblockProbeRules.SmallTarget());
|
||||||
|
m_Mode.Settings().Set("largeRoadblockCount", ERR_RoadblockProbeRules.LargeTarget());
|
||||||
|
m_Mode.Settings().Set("mainRoadWidthMetres", 6);
|
||||||
|
}
|
||||||
return super.Prepare(definition);
|
return super.Prepare(definition);
|
||||||
}
|
}
|
||||||
protected int m_ERRLayoutPosts;
|
protected int m_ERRLayoutPosts;
|
||||||
@@ -40,7 +54,7 @@ modded class ERR_Roadblocks
|
|||||||
for (int i = 0; i < m_BuildCovers.Count() && valid; i++)
|
for (int i = 0; i < m_BuildCovers.Count() && valid; i++)
|
||||||
{
|
{
|
||||||
ERR_RoadblockProp prop = m_BuildCovers[i];
|
ERR_RoadblockProp prop = m_BuildCovers[i];
|
||||||
IEntity entity = m_BuildSpawned[i + 4];
|
IEntity entity = m_BuildSpawned[i + m_BuildPositions.Count()];
|
||||||
vector low, high; entity.GetBounds(low, high);
|
vector low, high; entity.GetBounds(low, high);
|
||||||
if (m_ERRLayoutPosts == 0) PrintFormat("ERR ROADBLOCK PROP NATIVE: prefab=%1 low=%2 high=%3", prop.Prefab, low, high);
|
if (m_ERRLayoutPosts == 0) PrintFormat("ERR ROADBLOCK PROP NATIVE: prefab=%1 low=%2 high=%3", prop.Prefab, low, high);
|
||||||
if (Math.Max(Math.AbsFloat(low[0]), Math.AbsFloat(high[0])) > prop.HalfWidth
|
if (Math.Max(Math.AbsFloat(low[0]), Math.AbsFloat(high[0])) > prop.HalfWidth
|
||||||
@@ -102,28 +116,29 @@ modded class ERR_Roadblocks
|
|||||||
foreach (SCR_AIGroup group : m_Groups)
|
foreach (SCR_AIGroup group : m_Groups)
|
||||||
{
|
{
|
||||||
ERR_ForceRecord record = m_Mode.Forces().FindGroup(group);
|
ERR_ForceRecord record = m_Mode.Forces().FindGroup(group);
|
||||||
if (!group || !record || record.Kind != ERR_ForceKind.CHECKPOINT || record.InitialStrength != 4 || m_Mode.ForceLiving(group) != 4) return false;
|
if (!group || !record || record.Kind != ERR_ForceKind.CHECKPOINT || (record.InitialStrength != 4 && record.InitialStrength != 8) || m_Mode.ForceLiving(group) != record.InitialStrength) return false;
|
||||||
if (group.GetFaction() != m_Mode.EnemyFaction() || !ERR_HQ.Get().ERR_ProbeRoadblockGroup(group)) return false;
|
if (group.GetFaction() != m_Mode.EnemyFaction() || !ERR_HQ.Get().ERR_ProbeRoadblockGroup(group)) return false;
|
||||||
}
|
}
|
||||||
|
if (m_SmallPlaced != ERR_RoadblockProbeRules.SmallTarget() || m_LargePlaced != ERR_RoadblockProbeRules.LargeTarget()) return false;
|
||||||
int count = m_Positions.Count();
|
int count = m_Positions.Count();
|
||||||
BackgroundStep();
|
BackgroundStep();
|
||||||
PrintFormat("ERR ROADBLOCK BACKGROUND NATIVE: posts=%1 guards=%2 steps=%3 workMs=%4 maxStepMs=%5 attempts=%6", count, count * 4, m_BackgroundSteps, m_BackgroundWork, m_MaxBackgroundStep, m_Attempts);
|
PrintFormat("ERR ROADBLOCK BACKGROUND NATIVE: posts=%1 guards=%2 steps=%3 workMs=%4 maxStepMs=%5 attempts=%6", count, ERR_RoadblockProbeRules.Guards(), m_BackgroundSteps, m_BackgroundWork, m_MaxBackgroundStep, m_Attempts);
|
||||||
return m_Positions.Count() == count && m_BackgroundSteps > 0;
|
return m_Positions.Count() == count && (m_BackgroundSteps > 0 || ERR_RoadblockProbeRules.Target() <= 4);
|
||||||
}
|
}
|
||||||
bool ERR_ProbePreparedRoadblocks()
|
bool ERR_ProbePreparedRoadblocks()
|
||||||
{
|
{
|
||||||
if (!m_Ready || m_Target != 4 || m_Groups.IsEmpty() || m_Groups.Count() > m_Target || m_Groups.Count() != m_Positions.Count()) return false;
|
if (!m_Ready || m_Target != m_SmallTarget + m_LargeTarget || m_Groups.IsEmpty() || m_Groups.Count() > m_Target || m_Groups.Count() != m_Positions.Count()) return false;
|
||||||
int count = m_Groups.Count();
|
int count = m_Groups.Count();
|
||||||
foreach (SCR_AIGroup group : m_Groups)
|
foreach (SCR_AIGroup group : m_Groups)
|
||||||
{
|
{
|
||||||
ERR_ForceRecord record = m_Mode.Forces().FindGroup(group);
|
ERR_ForceRecord record = m_Mode.Forces().FindGroup(group);
|
||||||
if (!record || record.Kind != ERR_ForceKind.CHECKPOINT || record.InitialStrength != 4 || m_Mode.ForceLiving(group) != 4
|
if (!record || record.Kind != ERR_ForceKind.CHECKPOINT || (record.InitialStrength != 4 && record.InitialStrength != 8) || m_Mode.ForceLiving(group) != record.InitialStrength
|
||||||
|| group.GetFaction() != m_Mode.EnemyFaction() || !ERR_HQ.Get().ERR_ProbeRoadblockGroup(group)) return false;
|
|| group.GetFaction() != m_Mode.EnemyFaction() || !ERR_HQ.Get().ERR_ProbeRoadblockGroup(group)) return false;
|
||||||
}
|
}
|
||||||
ERR_ScenarioDefinition definition = ERR_ScenarioDefinition.Cast(SCR_BaseContainerTools.CreateInstanceFromPrefab("{ACA98B44E2173605}Configs/ERR/Tests/LegacySites.conf"));
|
ERR_ScenarioDefinition definition = ERR_ScenarioDefinition.Cast(SCR_BaseContainerTools.CreateInstanceFromPrefab("{ACA98B44E2173605}Configs/ERR/Tests/LegacySites.conf"));
|
||||||
// Repeated preparation cannot replenish or create another squad.
|
// Repeated preparation cannot replenish or create another squad.
|
||||||
if (Prepare(definition) != ERR_PrepareResult.READY || m_Groups.Count() != count) return false;
|
if (Prepare(definition) != ERR_PrepareResult.READY || m_Groups.Count() != count) return false;
|
||||||
PrintFormat("ERR ROADBLOCK NATIVE: posts=%1 soldiers=%2 ledger=checkpoint HQ=available repeatedPrepare=unchanged", count, count * 4);
|
PrintFormat("ERR ROADBLOCK NATIVE: posts=%1 soldiers=%2 ledger=checkpoint HQ=available repeatedPrepare=unchanged", count, m_SmallPlaced * 4 + m_LargePlaced * 8);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -156,13 +171,13 @@ modded class ERR_GameMode
|
|||||||
IEntity player; if (controller) player = controller.GetControlledEntity();
|
IEntity player; if (controller) player = controller.GetControlledEntity();
|
||||||
if (!player || !Players().CurrentActor(player)) return;
|
if (!player || !Players().CurrentActor(player)) return;
|
||||||
DamageManagerComponent.Cast(player.FindComponent(DamageManagerComponent)).EnableDamageHandling(false);
|
DamageManagerComponent.Cast(player.FindComponent(DamageManagerComponent)).EnableDamageHandling(false);
|
||||||
if (!posts.ERR_ProbeBackgroundPending() || posts.ERR_ProbePostCount() >= ERR_RoadblockProbeRules.Target() || !UnlockPrison(player)) { ERR_RoadblockProbeFail("did not enter background search with prison unlocked"); return; }
|
if ((ERR_RoadblockProbeRules.Target() > 4 && (!posts.ERR_ProbeBackgroundPending() || posts.ERR_ProbePostCount() >= ERR_RoadblockProbeRules.Target())) || !UnlockPrison(player)) { ERR_RoadblockProbeFail("did not enter background search with prison unlocked"); return; }
|
||||||
m_ERRRoadblockInitial = posts.ERR_ProbePostCount();
|
m_ERRRoadblockInitial = posts.ERR_ProbePostCount();
|
||||||
PrintFormat("ERR SMOKE PASS: roadblocks prison unlocked with %1/%2 posts; background still pending", m_ERRRoadblockInitial, ERR_RoadblockProbeRules.Target());
|
PrintFormat("ERR SMOKE PASS: roadblocks prison unlocked with %1/%2 posts; placement observation started", m_ERRRoadblockInitial, ERR_RoadblockProbeRules.Target());
|
||||||
}
|
}
|
||||||
if (posts.ERR_ProbeBackgroundPending()) return;
|
if (posts.ERR_ProbeBackgroundPending()) return;
|
||||||
if (!posts.ERR_ProbeCompleteRoadblocks() || posts.ERR_ProbePostCount() <= m_ERRRoadblockInitial) { ERR_RoadblockProbeFail("physical four-guard posts, replicated props, ledger and HQ admission"); return; }
|
if (!posts.ERR_ProbeCompleteRoadblocks() || (ERR_RoadblockProbeRules.Target() > 4 && posts.ERR_ProbePostCount() <= m_ERRRoadblockInitial)) { ERR_RoadblockProbeFail("physical small/large posts, independent quotas, replicated props, ledger and HQ admission"); return; }
|
||||||
PrintFormat("ERR SMOKE PASS: roadblocks %1 physical posts / %2 guards / %3 replicated checkpoint props with distance streaming disabled; verified model footprints and centre lane; background completed after prison unlock, no extra posts on repeat tick", posts.ERR_ProbePostCount(), posts.ERR_ProbePostCount() * 4, posts.ERR_ProbePostCount() * ERR_RoadblockRules.PropCount());
|
PrintFormat("ERR SMOKE PASS: roadblocks %1 physical posts / %2 guards / %3 replicated checkpoint props with distance streaming disabled; verified model footprints and centre lane; placement complete after prison unlock, no extra posts on repeat tick", posts.ERR_ProbePostCount(), ERR_RoadblockProbeRules.Guards(), ERR_RoadblockProbeRules.Props());
|
||||||
ERR_StopRoadblockProbe();
|
ERR_StopRoadblockProbe();
|
||||||
Transition(ERR_Phase.DEFEAT);
|
Transition(ERR_Phase.DEFEAT);
|
||||||
Print("ERR SMOKE COMPLETE: roadblocks background completion on Everon; scripted local fixture");
|
Print("ERR SMOKE COMPLETE: roadblocks background completion on Everon; scripted local fixture");
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ class ERR_ObjectiveText
|
|||||||
ERR_Phase phase = mode.Phase();
|
ERR_Phase phase = mode.Phase();
|
||||||
if (phase == ERR_Phase.PREPARING)
|
if (phase == ERR_Phase.PREPARING)
|
||||||
{
|
{
|
||||||
if (!mode.PreparationError().IsEmpty()) return "The mission could not start. An administrator can use /esc status for the cause. Spawning is disabled until the configuration is corrected and the scenario restarted.";
|
if (!mode.PreparationError().IsEmpty()) return "Reason: " + mode.PreparationError() + "\n\nAn administrator must correct the problem and restart the scenario before players can spawn.";
|
||||||
return "Preparing mission sites and equipment. You will spawn when the mission is ready.";
|
return "Preparing mission sites and equipment. You will spawn when the mission is ready.";
|
||||||
}
|
}
|
||||||
if (phase == ERR_Phase.PRISON)
|
if (phase == ERR_Phase.PRISON)
|
||||||
@@ -52,12 +52,15 @@ class ERR_ObjectiveText
|
|||||||
return "The radio station is identified on your map. Reach its radio and complete the 'Contact extraction HQ' action to start the transmission.";
|
return "The radio station is identified on your map. Reach its radio and complete the 'Contact extraction HQ' action to start the transmission.";
|
||||||
if (location.Knowledge() == 1) roughRadio = true;
|
if (location.Knowledge() == 1) roughRadio = true;
|
||||||
}
|
}
|
||||||
|
// The frozen radio-intel gate is server-owned; conditional guidance remains
|
||||||
|
// correct on clients, with the gate disabled and on sparse terrains.
|
||||||
|
string intelRequirement = "Keep any radio document that cannot yet be deciphered. If radio intel is locked, fully identify more distinct non-radio sites; the starting clue markers do not count. Kept radio documents are read automatically when the requirement is met. ";
|
||||||
// Scan all sites before acknowledging rough intel: an identified radio wins.
|
// Scan all sites before acknowledging rough intel: an identified radio wins.
|
||||||
if (roughRadio)
|
if (roughRadio)
|
||||||
return "Intel recovered: a question mark shows an approximate location, but its type is still unknown. Take a different intelligence folder into your inventory to pinpoint the radio; a folder already recovered cannot provide another clue. Try the other marked clue site if needed. A close approach with a clear view can also identify the station.";
|
return "Intel recovered: a question mark shows an approximate location, but its type is still unknown. Take a different intelligence folder into your inventory to pinpoint the radio; a folder already recovered cannot provide another clue. Try the other marked clue site if needed. A close approach with a clear view can also identify the station. " + intelRequirement;
|
||||||
string startHint;
|
string startHint;
|
||||||
if (mode.StartMode() == 1) startHint = "Search the salvage backpack at the crash site for weapons and a field map. ";
|
if (mode.StartMode() == 1) startHint = "Search the salvage backpack at the crash site for weapons and a field map. ";
|
||||||
return startHint + "Choose either marked clue site. Take its intelligence folders into your own inventory to reveal the station on your map. If the location is not yet identified, take another folder. Each marked site has enough folders; use the other if one is lost. If every folder is lost, a close approach with a clear view can identify the station itself. Nearby medical and guard sites can also be discovered by approaching with a clear view.";
|
return startHint + "Choose either marked clue site and take its intelligence folders into your own inventory. " + intelRequirement + "After radio intel unlocks, a different folder can refine a rough location. Use the other clue site if folders are lost. Even if every folder is lost, a close approach with a clear view can identify the station itself. Nearby medical and guard sites can also be discovered by approaching with a clear view.";
|
||||||
}
|
}
|
||||||
if (phase == ERR_Phase.RADIO_DEFENCE)
|
if (phase == ERR_Phase.RADIO_DEFENCE)
|
||||||
{
|
{
|
||||||
@@ -76,7 +79,7 @@ class ERR_ObjectiveText
|
|||||||
{
|
{
|
||||||
if (mode.PickupStage() == 2 || mode.PickupStage() == 3) return "Smoke accepted. Armed rescue helicopters are approaching. Keep the marked landing point clear and gather everyone nearby.";
|
if (mode.PickupStage() == 2 || mode.PickupStage() == 3) return "Smoke accepted. Armed rescue helicopters are approaching. Keep the marked landing point clear and gather everyone nearby.";
|
||||||
if (mode.PickupStage() == 4) return "Board the rescue transport. It waits for all living prisoners, including unconscious or disconnected survivors. Escorts cover the landing zone. After everyone is aboard for eight seconds, the pilot takes off.";
|
if (mode.PickupStage() == 4) return "Board the rescue transport. It waits for all living prisoners, including unconscious or disconnected survivors. Escorts cover the landing zone. After everyone is aboard for eight seconds, the pilot takes off.";
|
||||||
if (mode.PickupStage() == 5) return "Stay aboard. The rescue pilot and escorts are taking you beyond the escape boundary to safety.";
|
if (mode.PickupStage() == 5) return "Stay aboard. Escape completes after all rescue transports have remained airborne for 30 continuous seconds.";
|
||||||
return Extraction(mode.EscapeRadius());
|
return Extraction(mode.EscapeRadius());
|
||||||
}
|
}
|
||||||
return "";
|
return "";
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ class ERR_ObserverGuidance
|
|||||||
}
|
}
|
||||||
if (!survivorAvailable)
|
if (!survivorAvailable)
|
||||||
return message + " No teammate or body is currently available to follow. Use camera look controls to orbit the last known position and zoom controls to change distance.";
|
return message + " No teammate or body is currently available to follow. Use camera look controls to orbit the last known position and zoom controls to change distance.";
|
||||||
return message + " Press Left / Right arrow to change teammate or body (or /esc spectate next / /esc spectate previous). Press Enter to switch between third-person and the living teammate's camera. Player view waits in third-person if no live camera stream is available. Use camera look controls to orbit and zoom controls to change distance in third-person.";
|
return message + " Press Left / Right arrow to change teammate or body, including your own (or /esc spectate next / /esc spectate previous). Press Enter to switch between third-person and the living teammate's camera. Player view waits in third-person if no live camera stream is available. Use camera look controls to orbit and zoom controls to change distance in third-person.";
|
||||||
}
|
}
|
||||||
static bool Refresh(bool shown, int previousReason, int reason, bool previousAvailable, bool survivorAvailable)
|
static bool Refresh(bool shown, int previousReason, int reason, bool previousAvailable, bool survivorAvailable)
|
||||||
{
|
{
|
||||||
@@ -225,7 +225,7 @@ class ERR_FollowSurvivorCameraComponent : SCR_BaseManualCameraComponent
|
|||||||
{
|
{
|
||||||
target.GetWorldTransform(transform);
|
target.GetWorldTransform(transform);
|
||||||
aim = transform[3] + "0 1.5 0";
|
aim = transform[3] + "0 1.5 0";
|
||||||
if (!ERR_Actors.Alive(target)) aim = transform[3] + "0 0.4 0";
|
if (!ERR_Actors.CanFight(target)) aim = transform[3] + "0 0.4 0";
|
||||||
}
|
}
|
||||||
if (target != m_Target || vehicle != m_Vehicle)
|
if (target != m_Target || vehicle != m_Vehicle)
|
||||||
{
|
{
|
||||||
@@ -242,7 +242,7 @@ class ERR_FollowSurvivorCameraComponent : SCR_BaseManualCameraComponent
|
|||||||
m_Target = null; m_Vehicle = null;
|
m_Target = null; m_Vehicle = null;
|
||||||
m_Distance = Math.Clamp(m_Distance, 2, 12);
|
m_Distance = Math.Clamp(m_Distance, 2, 12);
|
||||||
}
|
}
|
||||||
if ((!m_PlayerView || !target || !ERR_Actors.Alive(target)) && param.isManualInputEnabled)
|
if ((!m_PlayerView || !target || !ERR_Actors.CanFight(target)) && param.isManualInputEnabled)
|
||||||
{
|
{
|
||||||
m_Yaw += GetInputManager().GetActionValue("ManualCameraRotateYaw") * 90 * param.timeSlice;
|
m_Yaw += GetInputManager().GetActionValue("ManualCameraRotateYaw") * 90 * param.timeSlice;
|
||||||
m_Pitch = Math.Clamp(m_Pitch - GetInputManager().GetActionValue("ManualCameraRotatePitch") * 90 * param.timeSlice, 5, 75);
|
m_Pitch = Math.Clamp(m_Pitch - GetInputManager().GetActionValue("ManualCameraRotatePitch") * 90 * param.timeSlice, 5, 75);
|
||||||
@@ -284,6 +284,8 @@ modded class SCR_PlayerController
|
|||||||
protected ERR_ObserverReason m_ERRShownObserverReason;
|
protected ERR_ObserverReason m_ERRShownObserverReason;
|
||||||
protected bool m_ERRShownObserverAvailable;
|
protected bool m_ERRShownObserverAvailable;
|
||||||
protected string m_ERRShownRecoveryStatus;
|
protected string m_ERRShownRecoveryStatus;
|
||||||
|
protected string m_ERRPendingRecoveryStatus;
|
||||||
|
protected int m_ERRRecoveryStatusSince;
|
||||||
protected float m_ERRLastCycle = -1000;
|
protected float m_ERRLastCycle = -1000;
|
||||||
protected IEntity m_ERRRecoveryBody;
|
protected IEntity m_ERRRecoveryBody;
|
||||||
protected float m_ERRRecoveryDeadline = -1;
|
protected float m_ERRRecoveryDeadline = -1;
|
||||||
@@ -364,10 +366,13 @@ modded class SCR_PlayerController
|
|||||||
if (survivors[i] == body || (casualty && !ERR_Actors.CanFight(survivors[i])) || !friendly || !mode.Players() || !mode.Players().CurrentActor(survivors[i]) || SCR_FactionManager.SGetFaction(survivors[i]) != friendly) survivors.RemoveOrdered(i);
|
if (survivors[i] == body || (casualty && !ERR_Actors.CanFight(survivors[i])) || !friendly || !mode.Players() || !mode.Players().CurrentActor(survivors[i]) || SCR_FactionManager.SGetFaction(survivors[i]) != friendly) survivors.RemoveOrdered(i);
|
||||||
// Dead teammates remain selectable alongside survivors, including our own body.
|
// Dead teammates remain selectable alongside survivors, including our own body.
|
||||||
ERR_ObserverTargets.Bodies(mode, survivors);
|
ERR_ObserverTargets.Bodies(mode, survivors);
|
||||||
|
// Lethal damage normally leaves a treatable casualty, not a native corpse.
|
||||||
|
// Keep that player's own canonical body in the cycle without possessing it.
|
||||||
|
if (casualty && !survivors.Contains(body)) survivors.Insert(body);
|
||||||
int index = survivors.Find(m_ERRServerObserved);
|
int index = survivors.Find(m_ERRServerObserved);
|
||||||
if (index < 0)
|
if (index < 0)
|
||||||
{
|
{
|
||||||
if (life && life.Actor) index = survivors.Find(life.Actor);
|
if (!casualty && life && life.Actor) index = survivors.Find(life.Actor);
|
||||||
if (index < 0) index = 0;
|
if (index < 0) index = 0;
|
||||||
}
|
}
|
||||||
else index += direction;
|
else index += direction;
|
||||||
@@ -468,13 +473,24 @@ modded class SCR_PlayerController
|
|||||||
string recovery = mode.RecoveryStatus();
|
string recovery = mode.RecoveryStatus();
|
||||||
string selfEpiHint = ERR_SelfEpinephrineHint();
|
string selfEpiHint = ERR_SelfEpinephrineHint();
|
||||||
if (!selfEpiHint.IsEmpty()) recovery += "\n" + selfEpiHint;
|
if (!selfEpiHint.IsEmpty()) recovery += "\n" + selfEpiHint;
|
||||||
if (ERR_ObserverGuidance.Refresh(m_ERRObserverReady, m_ERRShownObserverReason, m_ERRObserverReason, m_ERRShownObserverAvailable, survivorAvailable) || m_ERRShownRecoveryStatus != recovery)
|
bool contextChanged = ERR_ObserverGuidance.Refresh(m_ERRObserverReady, m_ERRShownObserverReason, m_ERRObserverReason, m_ERRShownObserverAvailable, survivorAvailable);
|
||||||
|
int now = System.GetTickCount();
|
||||||
|
// Medical samples can alternate between improving and stalled. Keep the
|
||||||
|
// accepted hint readable until the new status has remained stable for 3s.
|
||||||
|
// This affects presentation only, never the authoritative defeat timer.
|
||||||
|
if (m_ERRPendingRecoveryStatus != recovery || now < m_ERRRecoveryStatusSince)
|
||||||
|
{
|
||||||
|
m_ERRPendingRecoveryStatus = recovery;
|
||||||
|
m_ERRRecoveryStatusSince = now;
|
||||||
|
}
|
||||||
|
if (!contextChanged && m_ERRShownRecoveryStatus != recovery && now - m_ERRRecoveryStatusSince < 3000) return;
|
||||||
|
if (contextChanged || m_ERRShownRecoveryStatus != recovery)
|
||||||
{
|
{
|
||||||
SCR_HintManagerComponent hints = SCR_HintManagerComponent.GetInstance();
|
SCR_HintManagerComponent hints = SCR_HintManagerComponent.GetInstance();
|
||||||
if (!hints) return; // retry on the existing monitor; do not lose the first hint
|
if (!hints) return; // retry on the existing monitor; do not lose the first hint
|
||||||
string message = ERR_ObserverGuidance.Message(m_ERRObserverReason, survivorAvailable);
|
string message = ERR_ObserverGuidance.Message(m_ERRObserverReason, survivorAvailable);
|
||||||
if (!recovery.IsEmpty()) message += "\n" + recovery;
|
if (!recovery.IsEmpty()) message += "\n" + recovery;
|
||||||
if (!hints.ShowCustom(message, "Spectating", 12)) return;
|
if (!hints.ShowCustom(message, "Spectating", 12, m_ERRObserverReady)) return;
|
||||||
m_ERRObserverReady = true;
|
m_ERRObserverReady = true;
|
||||||
m_ERRShownObserverReason = m_ERRObserverReason;
|
m_ERRShownObserverReason = m_ERRObserverReason;
|
||||||
m_ERRShownObserverAvailable = survivorAvailable;
|
m_ERRShownObserverAvailable = survivorAvailable;
|
||||||
@@ -510,6 +526,8 @@ modded class SCR_PlayerController
|
|||||||
{
|
{
|
||||||
m_ERRObserverReady = false;
|
m_ERRObserverReady = false;
|
||||||
m_ERRShownRecoveryStatus = "";
|
m_ERRShownRecoveryStatus = "";
|
||||||
|
m_ERRPendingRecoveryStatus = "";
|
||||||
|
m_ERRRecoveryStatusSince = 0;
|
||||||
if (!m_ERRObserverCamera) return;
|
if (!m_ERRObserverCamera) return;
|
||||||
if (GetGame().GetPlayerController() == this) m_ERRObserverCamera.TrySwitchToControlledEntityCamera();
|
if (GetGame().GetPlayerController() == this) m_ERRObserverCamera.TrySwitchToControlledEntityCamera();
|
||||||
m_ERRObserverCamera.Terminate();
|
m_ERRObserverCamera.Terminate();
|
||||||
|
|||||||
@@ -0,0 +1,148 @@
|
|||||||
|
// Front-seat passenger assistance is an extra held input, not a parking-brake
|
||||||
|
// toggle. The driver's persistent brake, gears and service brake stay native.
|
||||||
|
class ERR_PassengerHandbrake
|
||||||
|
{
|
||||||
|
static bool Beside(vector delta)
|
||||||
|
{
|
||||||
|
float lateral = Math.AbsFloat(delta[0]);
|
||||||
|
return lateral >= 0.35 && lateral <= 1.7 && Math.AbsFloat(delta[1]) <= 0.5 && Math.AbsFloat(delta[2]) <= 0.65;
|
||||||
|
}
|
||||||
|
static bool FrontSeat(IEntity vehicle, BaseCompartmentSlot slot)
|
||||||
|
{
|
||||||
|
if (!vehicle || !slot || slot.GetType() != ECompartmentType.CARGO) return false;
|
||||||
|
CarControllerComponent car = CarControllerComponent.Cast(vehicle.FindComponent(CarControllerComponent));
|
||||||
|
if (!car) return false;
|
||||||
|
BaseCompartmentSlot pilot = car.GetPilotCompartmentSlot();
|
||||||
|
if (!pilot || pilot == slot || !slot.GetOwner() || slot.GetOwner().GetRootParent() != vehicle.GetRootParent()) return false;
|
||||||
|
PointInfo passengerPoint = slot.GetPassengerPointInfo(), pilotPoint = pilot.GetPassengerPointInfo();
|
||||||
|
if (!passengerPoint || !pilotPoint) return false;
|
||||||
|
vector passenger = vehicle.CoordToLocal(passengerPoint.GetWorldTransformAxis(3));
|
||||||
|
vector driver = vehicle.CoordToLocal(pilotPoint.GetWorldTransformAxis(3));
|
||||||
|
return Beside(passenger - driver);
|
||||||
|
}
|
||||||
|
static IEntity VehicleFor(IEntity actor)
|
||||||
|
{
|
||||||
|
if (!ERR_Actors.CanFight(actor)) return null;
|
||||||
|
CompartmentAccessComponent access = CompartmentAccessComponent.Cast(actor.FindComponent(CompartmentAccessComponent));
|
||||||
|
if (!access) return null;
|
||||||
|
BaseCompartmentSlot slot = access.GetCompartment();
|
||||||
|
IEntity vehicle = CompartmentAccessComponent.GetVehicleIn(actor);
|
||||||
|
if (!slot || slot.GetOccupant() != actor || !FrontSeat(vehicle, slot)) return null;
|
||||||
|
return vehicle;
|
||||||
|
}
|
||||||
|
static bool Requested(IEntity driver)
|
||||||
|
{
|
||||||
|
IEntity vehicle = CompartmentAccessComponent.GetVehicleIn(driver);
|
||||||
|
if (!vehicle) return false;
|
||||||
|
CarControllerComponent car = CarControllerComponent.Cast(vehicle.FindComponent(CarControllerComponent));
|
||||||
|
if (!car || !car.GetPilotCompartmentSlot() || car.GetPilotCompartmentSlot().GetOccupant() != driver) return false;
|
||||||
|
// Native held-handbrake input releases the parking latch. A parked car
|
||||||
|
// already satisfies the passenger's demand, so leave that latch untouched.
|
||||||
|
if (car.GetPersistentHandBrake()) return false;
|
||||||
|
BaseCompartmentManagerComponent manager = BaseCompartmentManagerComponent.Cast(vehicle.FindComponent(BaseCompartmentManagerComponent));
|
||||||
|
if (!manager) return false;
|
||||||
|
array<BaseCompartmentSlot> slots = {};
|
||||||
|
manager.GetCompartments(slots);
|
||||||
|
foreach (BaseCompartmentSlot slot : slots)
|
||||||
|
{
|
||||||
|
IEntity actor = slot.GetOccupant();
|
||||||
|
if (!actor || actor == driver) continue;
|
||||||
|
SCR_CharacterControllerComponent character = SCR_CharacterControllerComponent.Cast(actor.FindComponent(SCR_CharacterControllerComponent));
|
||||||
|
if (character && character.ERR_PassengerBrakeHeld() && VehicleFor(actor) == vehicle) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
modded class SCR_PlayerController
|
||||||
|
{
|
||||||
|
protected SCR_CharacterControllerComponent m_ERRBrakingPassenger;
|
||||||
|
// No caller-supplied body, seat or vehicle: authority derives all three.
|
||||||
|
void ERR_SendPassengerBrake(bool held)
|
||||||
|
{
|
||||||
|
if (this != GetGame().GetPlayerController()) return;
|
||||||
|
if (Replication.IsServer()) ERR_RequestPassengerBrake(held);
|
||||||
|
else Rpc(ERR_RequestPassengerBrake, held);
|
||||||
|
}
|
||||||
|
[RplRpc(RplChannel.Reliable, RplRcver.Server)]
|
||||||
|
protected void ERR_RequestPassengerBrake(bool held)
|
||||||
|
{
|
||||||
|
if (!Replication.IsServer()) return;
|
||||||
|
ERR_GameMode mode = ERR_GameMode.Get();
|
||||||
|
IEntity actor = GetControlledEntity();
|
||||||
|
SCR_CharacterControllerComponent character;
|
||||||
|
if (actor) character = SCR_CharacterControllerComponent.Cast(actor.FindComponent(SCR_CharacterControllerComponent));
|
||||||
|
if (m_ERRBrakingPassenger && (m_ERRBrakingPassenger != character || !held)) m_ERRBrakingPassenger.ERR_SetPassengerBrake(false);
|
||||||
|
m_ERRBrakingPassenger = null;
|
||||||
|
if (!character) return;
|
||||||
|
bool allowed = held && mode && mode.Active() && mode.Players() && mode.Players().CurrentActor(actor) && ERR_PassengerHandbrake.VehicleFor(actor);
|
||||||
|
character.ERR_SetPassengerBrake(allowed);
|
||||||
|
if (allowed) m_ERRBrakingPassenger = character;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
modded class SCR_CharacterControllerComponent
|
||||||
|
{
|
||||||
|
[RplProp()] protected bool m_ERRPassengerBrake;
|
||||||
|
protected int m_ERRPassengerBrakeHeartbeat;
|
||||||
|
protected bool m_ERRPassengerBrakeSent;
|
||||||
|
protected int m_ERRPassengerBrakeNextSend;
|
||||||
|
protected IEntity m_ERRPassengerBrakeHintVehicle;
|
||||||
|
|
||||||
|
bool ERR_PassengerBrakeHeld() { return m_ERRPassengerBrake; }
|
||||||
|
void ERR_SetPassengerBrake(bool held)
|
||||||
|
{
|
||||||
|
if (!Replication.IsServer()) return;
|
||||||
|
m_ERRPassengerBrakeHeartbeat = System.GetTickCount();
|
||||||
|
if (held == m_ERRPassengerBrake) return;
|
||||||
|
m_ERRPassengerBrake = held;
|
||||||
|
Replication.BumpMe();
|
||||||
|
GetGame().GetCallqueue().Remove(ERR_ExpirePassengerBrake);
|
||||||
|
if (held) GetGame().GetCallqueue().CallLater(ERR_ExpirePassengerBrake, 250, true);
|
||||||
|
}
|
||||||
|
protected void ERR_ExpirePassengerBrake()
|
||||||
|
{
|
||||||
|
if (!Replication.IsServer() || !m_ERRPassengerBrake) return;
|
||||||
|
ERR_GameMode mode = ERR_GameMode.Get();
|
||||||
|
int age = System.GetTickCount() - m_ERRPassengerBrakeHeartbeat;
|
||||||
|
if (!mode || !mode.Active() || age < 0 || age > 1000 || !ERR_PassengerHandbrake.VehicleFor(GetOwner())) ERR_SetPassengerBrake(false);
|
||||||
|
}
|
||||||
|
void ~SCR_CharacterControllerComponent()
|
||||||
|
{
|
||||||
|
if (GetGame() && GetGame().GetCallqueue()) GetGame().GetCallqueue().Remove(ERR_ExpirePassengerBrake);
|
||||||
|
}
|
||||||
|
override void OnPrepareControls(IEntity owner, ActionManager am, float dt, bool player)
|
||||||
|
{
|
||||||
|
ERR_GameMode mode = ERR_GameMode.Get();
|
||||||
|
// Feed the actor's native action before it prepares driving controls. The
|
||||||
|
// device/AI refreshes this value each tick; only add a held demand, never
|
||||||
|
// write a release over the driver's own handbrake or parking-brake input.
|
||||||
|
if (am && mode && mode.Active() && !am.GetActionTriggered("CarHandBrakePersistent") && ERR_PassengerHandbrake.Requested(owner)) am.SetActionValue("CarHandBrake", 1);
|
||||||
|
super.OnPrepareControls(owner, am, dt, player);
|
||||||
|
SCR_PlayerController controller = SCR_PlayerController.Cast(GetGame().GetPlayerController());
|
||||||
|
if (!controller || controller.GetControlledEntity() != owner) return;
|
||||||
|
IEntity vehicle;
|
||||||
|
if (mode && mode.Active()) vehicle = ERR_PassengerHandbrake.VehicleFor(owner);
|
||||||
|
bool held;
|
||||||
|
InputManager input = GetGame().GetInputManager();
|
||||||
|
if (vehicle && input && GetGame().GetMenuManager() && !GetGame().GetMenuManager().GetTopMenu() && !ERR_ObserverChatInput.Capture())
|
||||||
|
{
|
||||||
|
input.ActivateContext("ERR_PassengerBrakeContext", 1);
|
||||||
|
held = input.GetActionValue("ERR_PassengerBrake") > 0.5;
|
||||||
|
if (m_ERRPassengerBrakeHintVehicle != vehicle)
|
||||||
|
{
|
||||||
|
SCR_HintManagerComponent hints = SCR_HintManagerComponent.GetInstance();
|
||||||
|
if (hints) hints.ShowCustom("Hold Space to apply the handbrake. Release Space to let go.", "Passenger handbrake", 5);
|
||||||
|
m_ERRPassengerBrakeHintVehicle = vehicle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!vehicle) m_ERRPassengerBrakeHintVehicle = null;
|
||||||
|
int now = System.GetTickCount();
|
||||||
|
if (held != m_ERRPassengerBrakeSent || (held && now >= m_ERRPassengerBrakeNextSend))
|
||||||
|
{
|
||||||
|
controller.ERR_SendPassengerBrake(held);
|
||||||
|
m_ERRPassengerBrakeSent = held;
|
||||||
|
m_ERRPassengerBrakeNextSend = now + 250;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -40,7 +40,9 @@ modded class SCR_PlayerController
|
|||||||
}
|
}
|
||||||
ERR_GameMode mode = ERR_GameMode.Get();
|
ERR_GameMode mode = ERR_GameMode.Get();
|
||||||
if (!mode) return;
|
if (!mode) return;
|
||||||
if (mode.Phase() != ERR_Phase.PREPARING && (ERR_ObserverState() != ERR_ObserverReason.NONE || !ERR_Actors.Alive(GetControlledEntity()))) return;
|
// Local unconsciousness can arrive before the replicated observer reason.
|
||||||
|
// Do not let objective hints compete with casualty/spectator guidance then.
|
||||||
|
if (mode.Phase() != ERR_Phase.PREPARING && (ERR_ObserverState() != ERR_ObserverReason.NONE || !ERR_Actors.CanFight(GetControlledEntity()))) return;
|
||||||
string message = ERR_ObjectiveText.Description(mode, false);
|
string message = ERR_ObjectiveText.Description(mode, false);
|
||||||
if (message.IsEmpty()) return;
|
if (message.IsEmpty()) return;
|
||||||
if (m_ERRGuidePhase == mode.Phase() && m_ERRGuideMessage == message) return;
|
if (m_ERRGuidePhase == mode.Phase() && m_ERRGuideMessage == message) return;
|
||||||
|
|||||||
@@ -43,6 +43,22 @@ modded class SCR_ReconnectComponent
|
|||||||
PrintFormat("Reintroduced [reconnect]: player %1 original body reserved; timeoutSeconds=%2", playerId, seconds);
|
PrintFormat("Reintroduced [reconnect]: player %1 original body reserved; timeoutSeconds=%2", playerId, seconds);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
// Only an actual disconnected account may receive a replacement. Retire an
|
||||||
|
// orphaned living body through native cleanup before admitting a fresh one.
|
||||||
|
protected void PrepareReplacement(int playerId, ERR_GameMode mode, ERR_PlayerLife life)
|
||||||
|
{
|
||||||
|
if (!life.ReconnectPending || !ERR_PlayerLifeRules.CanEnter(false, mode.Phase())) return;
|
||||||
|
if (GetGame().GetPlayerManager().GetPlayerControlledEntity(playerId)) return;
|
||||||
|
if (life.Actor && GetGame().GetPlayerManager().GetPlayerIdFromControlledEntity(life.Actor) > 0) return;
|
||||||
|
if (ERR_Actors.Alive(life.Actor))
|
||||||
|
{
|
||||||
|
SCR_ReconnectData orphan = new SCR_ReconnectData();
|
||||||
|
orphan.m_ReservedEntity = life.Actor;
|
||||||
|
HandleDataExpiery(orphan);
|
||||||
|
}
|
||||||
|
if (mode.Players().AllowReconnectReplacement(playerId, mode.Phase()))
|
||||||
|
PrintFormat("Reintroduced [reconnect]: player %1 has no recoverable reservation; fresh admission allowed", playerId);
|
||||||
|
}
|
||||||
override bool HandlePlayerReconnect(int playerId)
|
override bool HandlePlayerReconnect(int playerId)
|
||||||
{
|
{
|
||||||
ERR_GameMode mode = ERR_GameMode.Get();
|
ERR_GameMode mode = ERR_GameMode.Get();
|
||||||
@@ -54,11 +70,21 @@ modded class SCR_ReconnectComponent
|
|||||||
UUID identity = SCR_PlayerIdentityUtils.GetPlayerIdentityId(playerId);
|
UUID identity = SCR_PlayerIdentityUtils.GetPlayerIdentityId(playerId);
|
||||||
if (!life || !life.Connected || !life.UsedLife || identity.IsNull() || life.AccountId != identity) return false;
|
if (!life || !life.Connected || !life.UsedLife || identity.IsNull() || life.AccountId != identity) return false;
|
||||||
SCR_ReconnectData data;
|
SCR_ReconnectData data;
|
||||||
if (!m_mReconnectData.Find(identity, data)) return false;
|
if (!m_mReconnectData.Find(identity, data))
|
||||||
|
{
|
||||||
|
PrepareReplacement(playerId, mode, life);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
IEntity actor = data.m_ReservedEntity;
|
IEntity actor = data.m_ReservedEntity;
|
||||||
int owner;
|
int owner;
|
||||||
if (actor) owner = GetGame().GetPlayerManager().GetPlayerIdFromControlledEntity(actor);
|
if (actor) owner = GetGame().GetPlayerManager().GetPlayerIdFromControlledEntity(actor);
|
||||||
if (!actor || actor != life.Actor || (owner > 0 && owner != playerId))
|
if (!actor)
|
||||||
|
{
|
||||||
|
m_mReconnectData.Remove(identity); UpdateExpieryCheck();
|
||||||
|
PrepareReplacement(playerId, mode, life);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (actor != life.Actor || (owner > 0 && owner != playerId))
|
||||||
{
|
{
|
||||||
m_mReconnectData.Remove(identity); UpdateExpieryCheck();
|
m_mReconnectData.Remove(identity); UpdateExpieryCheck();
|
||||||
PrintFormat("Reintroduced [reconnect]: player %1 rejected mismatched body reservation", playerId);
|
PrintFormat("Reintroduced [reconnect]: player %1 rejected mismatched body reservation", playerId);
|
||||||
@@ -69,7 +95,7 @@ modded class SCR_ReconnectComponent
|
|||||||
if (IsDataExpired(data, GetGame().GetWorld().GetTimestamp()) || !IsDataRelevant(data))
|
if (IsDataExpired(data, GetGame().GetWorld().GetTimestamp()) || !IsDataRelevant(data))
|
||||||
{
|
{
|
||||||
m_mReconnectData.Remove(identity); HandleDataExpiery(data); UpdateExpieryCheck();
|
m_mReconnectData.Remove(identity); HandleDataExpiery(data); UpdateExpieryCheck();
|
||||||
PrintFormat("Reintroduced [reconnect]: player %1 reservation expired or body lost; no replacement life", playerId);
|
PrepareReplacement(playerId, mode, life);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
bool restored = super.HandlePlayerReconnect(playerId);
|
bool restored = super.HandlePlayerReconnect(playerId);
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ class ERR_PlayerLife
|
|||||||
int ConnectionId;
|
int ConnectionId;
|
||||||
bool Connected;
|
bool Connected;
|
||||||
bool UsedLife;
|
bool UsedLife;
|
||||||
|
bool ReconnectPending;
|
||||||
IEntity Actor;
|
IEntity Actor;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,6 +67,18 @@ class ERR_PlayerRoster
|
|||||||
ERR_PlayerLife life = Get(playerId);
|
ERR_PlayerLife life = Get(playerId);
|
||||||
return life && life.Connected && ERR_PlayerLifeRules.CanEnter(life.UsedLife, phase);
|
return life && life.Connected && ERR_PlayerLifeRules.CanEnter(life.UsedLife, phase);
|
||||||
}
|
}
|
||||||
|
bool AllowReconnectReplacement(int playerId, int phase)
|
||||||
|
{
|
||||||
|
if (!Replication.IsServer()) return false;
|
||||||
|
ERR_PlayerLife life = Get(playerId);
|
||||||
|
if (!life || !life.Connected || !life.UsedLife || !life.ReconnectPending || !ERR_PlayerLifeRules.CanEnter(false, phase)) return false;
|
||||||
|
if (ERR_Actors.Alive(life.Actor)) return false;
|
||||||
|
life.Actor = null;
|
||||||
|
life.UsedLife = false;
|
||||||
|
life.ReconnectPending = false;
|
||||||
|
m_OnChanged.Invoke(playerId);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
void Spawned(int playerId, IEntity actor)
|
void Spawned(int playerId, IEntity actor)
|
||||||
{
|
{
|
||||||
if (!Replication.IsServer() || !actor) return;
|
if (!Replication.IsServer() || !actor) return;
|
||||||
@@ -73,13 +86,18 @@ class ERR_PlayerRoster
|
|||||||
if (!life) return;
|
if (!life) return;
|
||||||
life.Actor = actor;
|
life.Actor = actor;
|
||||||
life.UsedLife = true;
|
life.UsedLife = true;
|
||||||
|
life.ReconnectPending = false;
|
||||||
m_OnChanged.Invoke(playerId);
|
m_OnChanged.Invoke(playerId);
|
||||||
}
|
}
|
||||||
void Disconnect(int playerId)
|
void Disconnect(int playerId)
|
||||||
{
|
{
|
||||||
if (!Replication.IsServer()) return;
|
if (!Replication.IsServer()) return;
|
||||||
ERR_PlayerLife life = Get(playerId);
|
ERR_PlayerLife life = Get(playerId);
|
||||||
if (life) life.Connected = false;
|
if (life)
|
||||||
|
{
|
||||||
|
life.Connected = false;
|
||||||
|
life.ReconnectPending = life.UsedLife;
|
||||||
|
}
|
||||||
m_Connections.Remove(playerId);
|
m_Connections.Remove(playerId);
|
||||||
m_OnChanged.Invoke(playerId);
|
m_OnChanged.Invoke(playerId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,92 @@
|
|||||||
|
modded enum ChimeraMenuPreset
|
||||||
|
{
|
||||||
|
ERR_PreparationFailure
|
||||||
|
}
|
||||||
|
|
||||||
|
// The native loading placeholder is not a menu and has no focus/input lifetime.
|
||||||
|
// Use a real local menu on failure; never grant admission or change server state.
|
||||||
|
class ERR_PreparationFailureMenu : ChimeraMenuBase
|
||||||
|
{
|
||||||
|
protected ERR_GameMode m_Mode;
|
||||||
|
protected SCR_ButtonTextComponent m_ReturnButton, m_PauseButton;
|
||||||
|
protected TextWidget m_Description;
|
||||||
|
protected string m_LastError;
|
||||||
|
protected bool m_Bound, m_Leaving;
|
||||||
|
|
||||||
|
static bool Failed(ERR_GameMode mode)
|
||||||
|
{
|
||||||
|
return mode && mode == ERR_GameMode.Get() && mode.Phase() == ERR_Phase.PREPARING && !mode.PreparationError().IsEmpty();
|
||||||
|
}
|
||||||
|
static bool Show(ERR_GameMode mode)
|
||||||
|
{
|
||||||
|
if (System.IsConsoleApp() || !Failed(mode) || !GetGame() || !GetGame().GetMenuManager()) return false;
|
||||||
|
MenuManager manager = GetGame().GetMenuManager();
|
||||||
|
// Leave existing menus and exit confirmation dialogs in control of focus.
|
||||||
|
if (manager.IsAnyMenuOpen() || manager.IsAnyDialogOpen()) return true;
|
||||||
|
if (manager.OpenMenu(ChimeraMenuPreset.ERR_PreparationFailure)) return true;
|
||||||
|
// A missing custom layout must not trap the player on the loading overlay.
|
||||||
|
GetGame().OpenPauseMenu(false, true);
|
||||||
|
return manager.FindMenuByPreset(ChimeraMenuPreset.PauseMenu) != null;
|
||||||
|
}
|
||||||
|
override void OnMenuOpen()
|
||||||
|
{
|
||||||
|
super.OnMenuOpen();
|
||||||
|
m_Mode = ERR_GameMode.Get();
|
||||||
|
if (!Failed(m_Mode)) { Close(); return; }
|
||||||
|
m_Description = TextWidget.Cast(GetRootWidget().FindAnyWidget("FailureDescription"));
|
||||||
|
m_ReturnButton = SCR_ButtonTextComponent.GetButtonText("ReturnToMainMenu", GetRootWidget());
|
||||||
|
m_PauseButton = SCR_ButtonTextComponent.GetButtonText("OpenPauseMenu", GetRootWidget());
|
||||||
|
if (m_ReturnButton) m_ReturnButton.m_OnClicked.Insert(OnReturn);
|
||||||
|
if (m_PauseButton) m_PauseButton.m_OnClicked.Insert(OnPause);
|
||||||
|
GetGame().GetInputManager().AddActionListener(UIConstants.MENU_ACTION_BACK, EActionTrigger.PRESSED, OnBack);
|
||||||
|
m_Bound = true;
|
||||||
|
Refresh();
|
||||||
|
}
|
||||||
|
protected void Refresh()
|
||||||
|
{
|
||||||
|
string detail = ERR_ObjectiveText.Description(m_Mode, false);
|
||||||
|
if (m_Description && detail != m_LastError) { m_Description.SetText(detail); m_LastError = detail; }
|
||||||
|
}
|
||||||
|
override void OnMenuUpdate(float tDelta)
|
||||||
|
{
|
||||||
|
super.OnMenuUpdate(tDelta);
|
||||||
|
if (!Failed(m_Mode)) { Close(); return; }
|
||||||
|
if (!m_Leaving) Refresh();
|
||||||
|
}
|
||||||
|
protected void OpenPause()
|
||||||
|
{
|
||||||
|
if (!IsFocused() || m_Leaving || !Failed(m_Mode)) return;
|
||||||
|
GetGame().OpenPauseMenu(false, true);
|
||||||
|
}
|
||||||
|
protected void OnBack() { OpenPause(); }
|
||||||
|
protected void OnPause(SCR_ButtonBaseComponent button) { OpenPause(); }
|
||||||
|
protected void OnReturn(SCR_ButtonBaseComponent button)
|
||||||
|
{
|
||||||
|
if (!IsFocused() || m_Leaving || !Failed(m_Mode)) return;
|
||||||
|
m_Leaving = true;
|
||||||
|
if (m_ReturnButton) m_ReturnButton.SetEnabled(false);
|
||||||
|
if (m_PauseButton) m_PauseButton.SetEnabled(false);
|
||||||
|
ChimeraWorld world = GetGame().GetWorld();
|
||||||
|
if (world) world.PauseGameTime(false);
|
||||||
|
// Same local exit transition as vanilla's pause-menu exit. No mission RPC.
|
||||||
|
GameStateTransitions.RequestGameplayEndTransition();
|
||||||
|
}
|
||||||
|
override void OnMenuClose()
|
||||||
|
{
|
||||||
|
if (m_Bound && GetGame() && GetGame().GetInputManager())
|
||||||
|
GetGame().GetInputManager().RemoveActionListener(UIConstants.MENU_ACTION_BACK, EActionTrigger.PRESSED, OnBack);
|
||||||
|
m_Bound = false;
|
||||||
|
if (m_ReturnButton) m_ReturnButton.m_OnClicked.Remove(OnReturn);
|
||||||
|
if (m_PauseButton) m_PauseButton.m_OnClicked.Remove(OnPause);
|
||||||
|
m_ReturnButton = null; m_PauseButton = null; m_Description = null; m_Mode = null;
|
||||||
|
super.OnMenuClose();
|
||||||
|
}
|
||||||
|
#ifdef WORKBENCH
|
||||||
|
static bool Visible(ERR_GameMode mode)
|
||||||
|
{
|
||||||
|
if (!Failed(mode)) return false;
|
||||||
|
ERR_PreparationFailureMenu menu = ERR_PreparationFailureMenu.Cast(GetGame().GetMenuManager().FindMenuByPreset(ChimeraMenuPreset.ERR_PreparationFailure));
|
||||||
|
return menu && menu.m_Mode == mode && menu.GetRootWidget().IsVisible() && menu.m_Description && menu.m_ReturnButton && menu.m_PauseButton && menu.m_Description.GetText() == ERR_ObjectiveText.Description(mode, false);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
@@ -109,6 +109,10 @@ modded class SCR_RespawnSystemComponent
|
|||||||
if (m_ERRPreparationSpinner) m_ERRPreparationSpinner.SetVisible(m_ERRPreparationSpinnerWasVisible);
|
if (m_ERRPreparationSpinner) m_ERRPreparationSpinner.SetVisible(m_ERRPreparationSpinnerWasVisible);
|
||||||
m_ERRPreparationSpinnerHidden = false;
|
m_ERRPreparationSpinnerHidden = false;
|
||||||
}
|
}
|
||||||
|
// The placeholder's high-Z black layer would obscure even the pause menu.
|
||||||
|
// Hide it only after an interactive menu is available; native updates restore
|
||||||
|
// it if preparation recovers. The failed round still admits no players.
|
||||||
|
if (failed && ERR_PreparationFailureMenu.Show(mode)) m_wLoadingPlaceholder.SetVisible(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
override void DestroyLoadingPlaceholder()
|
override void DestroyLoadingPlaceholder()
|
||||||
@@ -130,6 +134,7 @@ modded class SCR_RespawnSystemComponent
|
|||||||
// Inspect the actual production-created widgets, without creating or changing them.
|
// Inspect the actual production-created widgets, without creating or changing them.
|
||||||
bool ERR_ProbePreparationVisible(ERR_GameMode mode, bool failed)
|
bool ERR_ProbePreparationVisible(ERR_GameMode mode, bool failed)
|
||||||
{
|
{
|
||||||
|
if (failed && ERR_PreparationFailureMenu.Visible(mode)) return true;
|
||||||
if (!mode || mode != ERR_GameMode.Get() || m_pGameMode != mode || !m_wLoadingPlaceholder || !m_wLoadingPlaceholder.IsVisible()) return false;
|
if (!mode || mode != ERR_GameMode.Get() || m_pGameMode != mode || !m_wLoadingPlaceholder || !m_wLoadingPlaceholder.IsVisible()) return false;
|
||||||
if (!m_ERRPreparationRoot || m_ERRPreparationParent != m_wLoadingPlaceholder || m_ERRPreparationRoot.GetParent() != m_wLoadingPlaceholder || !m_ERRPreparationRoot.IsVisible()) return false;
|
if (!m_ERRPreparationRoot || m_ERRPreparationParent != m_wLoadingPlaceholder || m_ERRPreparationRoot.GetParent() != m_wLoadingPlaceholder || !m_ERRPreparationRoot.IsVisible()) return false;
|
||||||
if (!ERR_PreparationWidgetVisible(m_ERRPreparationTitle) || !ERR_PreparationWidgetVisible(m_ERRPreparationDescription) || !ERR_PreparationWidgetVisible(m_ERRPreparationRoot)) return false;
|
if (!ERR_PreparationWidgetVisible(m_ERRPreparationTitle) || !ERR_PreparationWidgetVisible(m_ERRPreparationDescription) || !ERR_PreparationWidgetVisible(m_ERRPreparationRoot)) return false;
|
||||||
|
|||||||
@@ -0,0 +1,154 @@
|
|||||||
|
// Immutable snapshots only: never tick native effects or alter a patient's zones.
|
||||||
|
// Seconds: >=0 = predicted wake, -1 = beyond horizon, -2 = unsupported/uncertain.
|
||||||
|
class ERR_RecoveryFlow
|
||||||
|
{
|
||||||
|
bool Passive;
|
||||||
|
float BloodRate;
|
||||||
|
float ResilienceRate;
|
||||||
|
float Delay;
|
||||||
|
float End = 301;
|
||||||
|
}
|
||||||
|
class ERR_RecoveryForecast
|
||||||
|
{
|
||||||
|
static bool KeepsRound(float forecast, bool selfRescue, bool existingRecovery)
|
||||||
|
{
|
||||||
|
return selfRescue || forecast >= 0 || (forecast == -2 && existingRecovery);
|
||||||
|
}
|
||||||
|
static float Horizon() { return 300; }
|
||||||
|
static bool Awake(float blood, float resilience, float bloodThreshold, float resilienceThreshold)
|
||||||
|
{
|
||||||
|
return blood > bloodThreshold && resilience > resilienceThreshold;
|
||||||
|
}
|
||||||
|
static float Overlap(float start, float end, float delay, float expires)
|
||||||
|
{
|
||||||
|
return Math.Max(0, Math.Min(end, expires) - Math.Max(start, delay));
|
||||||
|
}
|
||||||
|
static float Predict(float blood, float resilience, float bloodThreshold, float resilienceThreshold, array<ref ERR_RecoveryFlow> flows)
|
||||||
|
{
|
||||||
|
if (Awake(blood, resilience, bloodThreshold, resilienceThreshold)) return 0;
|
||||||
|
for (int second = 0; second < 300; second++)
|
||||||
|
{
|
||||||
|
// All flow starts/ends partition this second, including finite medicines.
|
||||||
|
float time = second;
|
||||||
|
while (time < second + 1)
|
||||||
|
{
|
||||||
|
float next = second + 1;
|
||||||
|
foreach (ERR_RecoveryFlow flow : flows)
|
||||||
|
{
|
||||||
|
if (flow.Delay > time && flow.Delay < next) next = flow.Delay;
|
||||||
|
if (flow.End > time && flow.End < next) next = flow.End;
|
||||||
|
}
|
||||||
|
float db, dr;
|
||||||
|
foreach (ERR_RecoveryFlow rate : flows)
|
||||||
|
{
|
||||||
|
float duration = Overlap(time, next, rate.Delay, rate.End);
|
||||||
|
db += rate.BloodRate * duration; dr += rate.ResilienceRate * duration;
|
||||||
|
}
|
||||||
|
blood = Math.Clamp(blood + db, 0, 1);
|
||||||
|
resilience = Math.Clamp(resilience + dr, 0, 1);
|
||||||
|
if (Awake(blood, resilience, bloodThreshold, resilienceThreshold)) return next;
|
||||||
|
time = next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
static float Estimate(SCR_CharacterDamageManagerComponent damage)
|
||||||
|
{
|
||||||
|
if (!damage || !damage.ERR_IsRevivablePlayer()) return -2;
|
||||||
|
return damage.ERR_ForecastRecovery();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
modded class SCR_PassiveHitZoneRegenDamageEffect
|
||||||
|
{
|
||||||
|
float ERR_RemainingRegenDelay() { return Math.Max(0, m_fLocalRegenDelayTime - m_fDelayTimer); }
|
||||||
|
}
|
||||||
|
modded class SCR_CharacterDamageManagerComponent
|
||||||
|
{
|
||||||
|
float ERR_ForecastRecovery()
|
||||||
|
{
|
||||||
|
SCR_CharacterBloodHitZone blood = GetBloodHitZone();
|
||||||
|
SCR_CharacterResilienceHitZone resilience = GetResilienceHitZone();
|
||||||
|
if (!blood || !resilience || blood.GetMaxHealth() <= 0 || resilience.GetMaxHealth() <= 0) return -2;
|
||||||
|
if (m_aBeingHealedGroup && !m_aBeingHealedGroup.IsEmpty()) return -2;
|
||||||
|
array<ref ERR_RecoveryFlow> flows = {};
|
||||||
|
array<ref SCR_PersistentDamageEffect> effects = GetAllPersistentEffectsOfType(SCR_PersistentDamageEffect, true);
|
||||||
|
bool bloodChanges, passiveResilience, unknown, pausedNatural;
|
||||||
|
foreach (SCR_PersistentDamageEffect effect : effects)
|
||||||
|
{
|
||||||
|
DotDamageEffect dot = DotDamageEffect.Cast(effect);
|
||||||
|
if (!dot) { unknown = true; continue; }
|
||||||
|
HitZone zone = dot.GetAffectedHitZone();
|
||||||
|
typename kind = effect.Type();
|
||||||
|
bool bleeding = kind == SCR_BleedingDamageEffect;
|
||||||
|
bool passive = kind == SCR_PassiveHitZoneRegenDamageEffect;
|
||||||
|
bool saline = kind == SCR_SalineDamageEffect;
|
||||||
|
bool epi = kind == ERR_EpinephrineEffect;
|
||||||
|
// Physical wound regen and native morphine do not restore consciousness.
|
||||||
|
if (kind == SCR_PhysicalHitZonesRegenDamageEffect || kind == SCR_MorphineDamageEffect) continue;
|
||||||
|
if (passive && !effect.IsActive()) { pausedNatural = true; continue; }
|
||||||
|
if (!effect.IsActive()) { unknown = true; continue; }
|
||||||
|
if (!bleeding && !passive && !saline && !epi) { unknown = true; continue; }
|
||||||
|
if (!bleeding && zone != blood && zone != resilience) continue;
|
||||||
|
ERR_RecoveryFlow flow = new ERR_RecoveryFlow();
|
||||||
|
flow.Passive = passive;
|
||||||
|
float maximum = dot.GetMaxDuration();
|
||||||
|
if (maximum > 0) flow.End = Math.Max(0, maximum - dot.GetCurrentDuration());
|
||||||
|
HitZone target = zone;
|
||||||
|
if (bleeding) target = blood;
|
||||||
|
float rate = -dot.GetDPS() * target.GetBaseDamageMultiplier() * target.GetDamageMultiplier(dot.GetDamageType());
|
||||||
|
if (passive)
|
||||||
|
{
|
||||||
|
flow.Delay = SCR_PassiveHitZoneRegenDamageEffect.Cast(effect).ERR_RemainingRegenDelay();
|
||||||
|
// A terminated blood regen can still be visible until native cleanup.
|
||||||
|
if (IsBleeding()) rate = 0;
|
||||||
|
if (zone == resilience) passiveResilience = true;
|
||||||
|
}
|
||||||
|
if (bleeding)
|
||||||
|
{
|
||||||
|
SCR_CharacterHitZone wound = SCR_CharacterHitZone.Cast(zone);
|
||||||
|
if (!wound) { unknown = true; continue; }
|
||||||
|
if (GetGroupTourniquetted(wound.GetHitZoneGroup())) rate *= GetTourniquetStrengthMultiplier();
|
||||||
|
flow.BloodRate = rate / blood.GetMaxHealth();
|
||||||
|
// Expiring bleeding can create new delayed native regen: do not declare
|
||||||
|
// hopelessness based on a snapshot that cannot yet contain that effect.
|
||||||
|
if (flow.End <= 300) unknown = true;
|
||||||
|
}
|
||||||
|
else if (zone == blood) flow.BloodRate = rate / blood.GetMaxHealth();
|
||||||
|
else flow.ResilienceRate = rate / resilience.GetMaxHealth();
|
||||||
|
if (flow.BloodRate != 0) bloodChanges = true;
|
||||||
|
flows.Insert(flow);
|
||||||
|
}
|
||||||
|
// Native treatment suppresses passive resilience regeneration while the
|
||||||
|
// medicine acts. Do not add both DPS values as simultaneous recovery.
|
||||||
|
bool treatmentOverlap;
|
||||||
|
foreach (ERR_RecoveryFlow regen : flows)
|
||||||
|
{
|
||||||
|
if (!regen.Passive || regen.ResilienceRate <= 0) continue;
|
||||||
|
foreach (ERR_RecoveryFlow medicine : flows)
|
||||||
|
{
|
||||||
|
if (medicine.Passive || medicine.ResilienceRate <= 0) continue;
|
||||||
|
regen.Delay = Math.Max(regen.Delay, medicine.End);
|
||||||
|
treatmentOverlap = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
float bt = blood.GetDamageStateThreshold(ECharacterBloodState.UNCONSCIOUS);
|
||||||
|
float rt = resilience.GetDamageStateThreshold(ECharacterResilienceState.WEAKENED);
|
||||||
|
float predicted = ERR_RecoveryForecast.Predict(blood.GetHealthScaled(), resilience.GetHealthScaled(), bt, rt, flows);
|
||||||
|
if (unknown || (pausedNatural && predicted < 0)) return -2;
|
||||||
|
// If the estimate needs post-treatment passive recovery, the native delay
|
||||||
|
// reset may move that start later. Retain safeguards instead of inventing it.
|
||||||
|
if (treatmentOverlap)
|
||||||
|
foreach (ERR_RecoveryFlow treatment : flows)
|
||||||
|
if (!treatment.Passive && treatment.ResilienceRate > 0 && (predicted < 0 || predicted > treatment.End)) return -2;
|
||||||
|
// Native resilience rates depend on changing blood-state bands. A snapshot's
|
||||||
|
// DPS cannot establish that future rate. Use an optimistic blood-only bound
|
||||||
|
// to prove impossibility; otherwise defer rather than kill a viable casualty.
|
||||||
|
if (bloodChanges && passiveResilience)
|
||||||
|
{
|
||||||
|
float optimistic = ERR_RecoveryForecast.Predict(blood.GetHealthScaled(), 1, bt, rt, flows);
|
||||||
|
if (optimistic < 0) return -1;
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
return predicted;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -42,6 +42,7 @@ class ERR_RecoveryService
|
|||||||
protected float m_NextMedicalLog;
|
protected float m_NextMedicalLog;
|
||||||
protected string m_MedicalDetails;
|
protected string m_MedicalDetails;
|
||||||
protected bool m_DefeatLogged;
|
protected bool m_DefeatLogged;
|
||||||
|
protected float m_NextForecast;
|
||||||
protected ref ScriptInvoker m_OnChanged = new ScriptInvoker();
|
protected ref ScriptInvoker m_OnChanged = new ScriptInvoker();
|
||||||
void ERR_RecoveryService(ERR_GameMode mode) { m_Mode = mode; }
|
void ERR_RecoveryService(ERR_GameMode mode) { m_Mode = mode; }
|
||||||
ScriptInvoker OnChanged() { return m_OnChanged; }
|
ScriptInvoker OnChanged() { return m_OnChanged; }
|
||||||
@@ -55,7 +56,7 @@ class ERR_RecoveryService
|
|||||||
if (due && !m_DefeatLogged)
|
if (due && !m_DefeatLogged)
|
||||||
{
|
{
|
||||||
m_DefeatLogged = true;
|
m_DefeatLogged = true;
|
||||||
ERR_HQ.Event("MEDICAL DEFEAT", "30 seconds without a viable rescuer or recovery; " + m_MedicalDetails);
|
ERR_HQ.Event("MEDICAL DEFEAT", "30 seconds without a viable rescuer, self-injection or recovery within five minutes; " + m_MedicalDetails);
|
||||||
}
|
}
|
||||||
return due;
|
return due;
|
||||||
}
|
}
|
||||||
@@ -70,9 +71,11 @@ class ERR_RecoveryService
|
|||||||
{
|
{
|
||||||
if (!Replication.IsServer() || m_Stopped || !m_Mode || !m_Mode.Active()) return;
|
if (!Replication.IsServer() || m_Stopped || !m_Mode || !m_Mode.Active()) return;
|
||||||
float now = GetGame().GetWorld().GetWorldTime();
|
float now = GetGame().GetWorld().GetWorldTime();
|
||||||
|
if (now >= m_NextForecast - 1000 && now < m_NextForecast) return;
|
||||||
|
m_NextForecast = now + 1000;
|
||||||
bool logMedical = now >= m_NextMedicalLog;
|
bool logMedical = now >= m_NextMedicalLog;
|
||||||
string medicalDetails;
|
string medicalDetails;
|
||||||
int conscious, incapacitated, reserved, reservedRescuers, recovering;
|
int conscious, incapacitated, reserved, reservedRescuers, recovering, selfRescuers, uncertain;
|
||||||
map<IEntity, ref ERR_RecoverySample> nextSamples = new map<IEntity, ref ERR_RecoverySample>();
|
map<IEntity, ref ERR_RecoverySample> nextSamples = new map<IEntity, ref ERR_RecoverySample>();
|
||||||
array<ref ERR_PlayerLife> lives = {};
|
array<ref ERR_PlayerLife> lives = {};
|
||||||
m_Mode.Players().Participants(lives);
|
m_Mode.Players().Participants(lives);
|
||||||
@@ -99,7 +102,12 @@ class ERR_RecoveryService
|
|||||||
bool improving = previous && (sample.Blood > previous.Blood + 0.001 || sample.Resilience > previous.Resilience + 0.001);
|
bool improving = previous && (sample.Blood > previous.Blood + 0.001 || sample.Resilience > previous.Resilience + 0.001);
|
||||||
bool treatment = medical.ERR_TreatmentInProgress();
|
bool treatment = medical.ERR_TreatmentInProgress();
|
||||||
bool natural = medical.ERR_CanRecoverNaturally();
|
bool natural = medical.ERR_CanRecoverNaturally();
|
||||||
if (improving || treatment || natural) recovering++;
|
float forecast = ERR_RecoveryForecast.Estimate(medical);
|
||||||
|
bool selfRescue = life.Connected && controlled == life.Actor && m_Mode.Settings().Get("selfEpinephrine") == 1 && ERR_SelfEpinephrineInventory.FindUsable(life.Actor);
|
||||||
|
if (selfRescue) selfRescuers++;
|
||||||
|
if (forecast == -2) uncertain++;
|
||||||
|
if (ERR_RecoveryForecast.KeepsRound(forecast, selfRescue, improving || treatment || natural)) recovering++;
|
||||||
|
medicalDetails += string.Format("forecastSeconds=%1 selfInjection=%2; ", forecast, selfRescue);
|
||||||
medicalDetails += string.Format("player=%1 connected=%2 blood=%3 resilience=%4 bleeding=%5 improving=%6 treatment=%7 naturalRecovery=%8; ", life.ConnectionId, life.Connected, sample.Blood, sample.Resilience, medical.IsBleeding(), improving, treatment, natural);
|
medicalDetails += string.Format("player=%1 connected=%2 blood=%3 resilience=%4 bleeding=%5 improving=%6 treatment=%7 naturalRecovery=%8; ", life.ConnectionId, life.Connected, sample.Blood, sample.Resilience, medical.IsBleeding(), improving, treatment, natural);
|
||||||
nextSamples.Insert(life.Actor, sample);
|
nextSamples.Insert(life.Actor, sample);
|
||||||
}
|
}
|
||||||
@@ -122,9 +130,11 @@ class ERR_RecoveryService
|
|||||||
{
|
{
|
||||||
message = "No conscious prisoner is currently available to help.";
|
message = "No conscious prisoner is currently available to help.";
|
||||||
if (evacuating) message += " The rescue crew is carrying all living prisoners to safety; evacuation continues.";
|
if (evacuating) message += " The rescue crew is carrying all living prisoners to safety; evacuation continues.";
|
||||||
else if (recovering > 0) message += " Treatment or natural recovery is possible; the round continues.";
|
else if (selfRescuers > 0) message += " Self epinephrine is available. Press Space to use your carried injector; the round continues.";
|
||||||
|
else if (recovering > 0 && uncertain > 0) message += " Recovery timing is uncertain; existing treatment and recovery safeguards remain active.";
|
||||||
|
else if (recovering > 0) message += " A prisoner is expected to wake within five minutes under current medical conditions.";
|
||||||
else if (reservedRescuers > 0) message += " A conscious prisoner's reconnection may provide help.";
|
else if (reservedRescuers > 0) message += " A conscious prisoner's reconnection may provide help.";
|
||||||
else message += " Everyone is unconscious with no treatment or recovery progressing. The mission fails after 30 uninterrupted seconds in this state.";
|
else message += " Nobody is expected to wake within five minutes and no self-injection is available. The mission fails after 30 uninterrupted seconds in this state.";
|
||||||
}
|
}
|
||||||
Publish(message);
|
Publish(message);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,158 @@
|
|||||||
|
// Round-local statistics are always available, independently of telemetry.
|
||||||
|
// Only authoritative participant bodies own records; transient connection IDs
|
||||||
|
// and native winner-ID lists never decide kill credit or escape results.
|
||||||
|
class ERR_RoundPlayerStats
|
||||||
|
{
|
||||||
|
UUID Account;
|
||||||
|
string Name;
|
||||||
|
IEntity Actor;
|
||||||
|
bool Connected;
|
||||||
|
bool Down;
|
||||||
|
int Kills;
|
||||||
|
int FriendlyKills;
|
||||||
|
int Deaths;
|
||||||
|
int Downed;
|
||||||
|
int Recoveries;
|
||||||
|
}
|
||||||
|
class ERR_RoundStatistics
|
||||||
|
{
|
||||||
|
protected ERR_GameMode m_Mode;
|
||||||
|
protected ref array<ref ERR_RoundPlayerStats> m_Players = {};
|
||||||
|
protected bool m_Finished;
|
||||||
|
protected bool m_Truncated;
|
||||||
|
protected int m_StartedAt = -1;
|
||||||
|
void ERR_RoundStatistics(ERR_GameMode mode) { m_Mode = mode; }
|
||||||
|
ERR_RoundPlayerStats Find(UUID account)
|
||||||
|
{
|
||||||
|
foreach (ERR_RoundPlayerStats row : m_Players) if (row.Account == account) return row;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
protected ERR_RoundPlayerStats Body(IEntity actor)
|
||||||
|
{
|
||||||
|
if (!actor) return null;
|
||||||
|
foreach (ERR_RoundPlayerStats row : m_Players) if (row.Actor == actor) return row;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
static string DisplayName(string name)
|
||||||
|
{
|
||||||
|
name.Replace("\n", " "); name.Replace("\r", " "); name.Replace("\t", " ");
|
||||||
|
if (name.Length() > 48) name = name.Substring(0, 48);
|
||||||
|
if (name.IsEmpty()) return "Player";
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
void RosterChanged(int playerId) { Sync(); }
|
||||||
|
void Sync()
|
||||||
|
{
|
||||||
|
if (!Replication.IsServer() || m_Finished || !m_Mode || !m_Mode.Players()) return;
|
||||||
|
array<ref ERR_PlayerLife> lives = {}; m_Mode.Players().Participants(lives);
|
||||||
|
foreach (ERR_PlayerLife life : lives)
|
||||||
|
{
|
||||||
|
ERR_RoundPlayerStats row = Find(life.AccountId);
|
||||||
|
if (!row)
|
||||||
|
{
|
||||||
|
if (m_Players.Count() >= 128) { m_Truncated = true; continue; }
|
||||||
|
row = new ERR_RoundPlayerStats(); row.Account = life.AccountId; m_Players.Insert(row);
|
||||||
|
}
|
||||||
|
row.Connected = life.Connected;
|
||||||
|
if (life.Connected) row.Name = DisplayName(GetGame().GetPlayerManager().GetPlayerName(life.ConnectionId));
|
||||||
|
if (row.Actor != life.Actor) { row.Actor = life.Actor; row.Down = false; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// A consciousness edge is a down/recovery, not a death. Repeated callbacks
|
||||||
|
// cannot count the same incapacitation twice; corpse deletion is not a kill.
|
||||||
|
static void Consciousness(ERR_RoundPlayerStats row, bool conscious)
|
||||||
|
{
|
||||||
|
if (!row) return;
|
||||||
|
if (!conscious && !row.Down) row.Downed++;
|
||||||
|
if (conscious && row.Down) row.Recoveries++;
|
||||||
|
row.Down = !conscious;
|
||||||
|
}
|
||||||
|
void ConsciousnessChanged(IEntity actor, bool conscious)
|
||||||
|
{
|
||||||
|
if (!Replication.IsServer() || m_Finished || !m_Mode || !m_Mode.Active() || !ERR_Actors.Alive(actor)) return;
|
||||||
|
Sync(); Consciousness(Body(actor), conscious);
|
||||||
|
}
|
||||||
|
static void CountDeath(ERR_RoundPlayerStats victim, ERR_RoundPlayerStats killer, bool friendly, bool credit)
|
||||||
|
{
|
||||||
|
if (victim) { victim.Deaths++; victim.Down = false; }
|
||||||
|
if (!credit || !killer || victim == killer) return;
|
||||||
|
if (friendly) killer.FriendlyKills++;
|
||||||
|
else killer.Kills++;
|
||||||
|
}
|
||||||
|
void Killed(IEntity victim, IEntity killerEntity, Instigator instigator)
|
||||||
|
{
|
||||||
|
if (!Replication.IsServer() || m_Finished || !m_Mode || !m_Mode.Active() || !ChimeraCharacter.Cast(victim)) return;
|
||||||
|
SCR_CharacterDamageManagerComponent damage = SCR_CharacterDamageManagerComponent.Cast(victim.FindComponent(SCR_CharacterDamageManagerComponent));
|
||||||
|
if (!damage || !damage.IsDestroyed()) return;
|
||||||
|
Sync();
|
||||||
|
ERR_RoundPlayerStats killed = Body(victim);
|
||||||
|
// Native damage normally provides the original instigator body, including
|
||||||
|
// delayed explosives. Never recover credit from a possibly recycled player ID.
|
||||||
|
IEntity source = killerEntity;
|
||||||
|
if (!ChimeraCharacter.Cast(source) && instigator) source = instigator.GetInstigatorEntity();
|
||||||
|
ERR_RoundPlayerStats killer = Body(source);
|
||||||
|
if (!killed && !killer) return;
|
||||||
|
if (!damage.ERR_ClaimRoundDeath()) return;
|
||||||
|
bool credit = instigator && instigator.GetInstigatorType() != InstigatorType.INSTIGATOR_GM;
|
||||||
|
bool friendly = killed != null;
|
||||||
|
Faction faction = SCR_FactionManager.SGetFaction(victim);
|
||||||
|
if (faction && faction == m_Mode.PlayerFaction()) friendly = true;
|
||||||
|
CountDeath(killed, killer, friendly, credit);
|
||||||
|
}
|
||||||
|
void PhaseChanged(ERR_Phase previous, ERR_Phase current)
|
||||||
|
{
|
||||||
|
if (!Replication.IsServer() || m_Finished) return;
|
||||||
|
if (m_StartedAt < 0 && current >= ERR_Phase.PRISON && current <= ERR_Phase.EXTRACTION) m_StartedAt = System.GetTickCount();
|
||||||
|
if (ERR_MissionRules.Terminal(current)) Finish();
|
||||||
|
}
|
||||||
|
protected string Outcome(ERR_RoundPlayerStats row)
|
||||||
|
{
|
||||||
|
if (m_Mode.Extraction() && m_Mode.Extraction().DidEscape(row.Account)) return "Escaped";
|
||||||
|
if (row.Deaths > 0 && !ERR_Actors.Alive(row.Actor)) return "Dead";
|
||||||
|
if (!row.Connected) return "Disconnected - did not escape";
|
||||||
|
if (row.Down) return "Down - did not escape";
|
||||||
|
return "Did not escape";
|
||||||
|
}
|
||||||
|
void Finish()
|
||||||
|
{
|
||||||
|
if (!Replication.IsServer() || m_Finished || !m_Mode) return;
|
||||||
|
Sync(); m_Finished = true;
|
||||||
|
int seconds;
|
||||||
|
if (m_StartedAt >= 0) seconds = Math.Max(0, (System.GetTickCount() - m_StartedAt) / 1000);
|
||||||
|
int kills, friendly, deaths, downed, recovered, escaped;
|
||||||
|
string rows;
|
||||||
|
foreach (ERR_RoundPlayerStats row : m_Players)
|
||||||
|
{
|
||||||
|
kills += row.Kills; friendly += row.FriendlyKills; deaths += row.Deaths; downed += row.Downed; recovered += row.Recoveries;
|
||||||
|
if (m_Mode.Extraction() && m_Mode.Extraction().DidEscape(row.Account)) escaped++;
|
||||||
|
if (!rows.IsEmpty()) rows += "\n\n";
|
||||||
|
rows += DisplayName(row.Name) + " - " + Outcome(row) + "\n";
|
||||||
|
rows += string.Format("Kills: %1 Friendly kills: %2 Deaths: %3 Downed: %4 Recoveries: %5", row.Kills, row.FriendlyKills, row.Deaths, row.Downed, row.Recoveries);
|
||||||
|
}
|
||||||
|
string summary = string.Format("Duration: %1m %2s Participants: %3 Escaped: %4\n", seconds / 60, Math.Mod(seconds, 60), m_Players.Count(), escaped);
|
||||||
|
summary += string.Format("Total kills: %1 Friendly kills: %2 Deaths: %3 Downed: %4 Recoveries: %5\n\n", kills, friendly, deaths, downed, recovered);
|
||||||
|
summary += "Downed counts unconscious episodes; recoveries include self-recovery. Deaths count fatal losses.\n\n";
|
||||||
|
if (rows.IsEmpty()) rows = "No players participated in this round.";
|
||||||
|
summary += rows;
|
||||||
|
if (m_Truncated) summary += "\n\nOnly the first 128 participants are shown; totals cover those players.";
|
||||||
|
m_Mode.ERR_PublishRoundStatistics(summary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
modded class SCR_CharacterDamageManagerComponent
|
||||||
|
{
|
||||||
|
protected bool m_ERRRoundDeathCounted;
|
||||||
|
bool ERR_ClaimRoundDeath()
|
||||||
|
{
|
||||||
|
if (!Replication.IsServer() || m_ERRRoundDeathCounted) return false;
|
||||||
|
m_ERRRoundDeathCounted = true; return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
modded class SCR_CharacterControllerComponent
|
||||||
|
{
|
||||||
|
override void OnConsciousnessChanged(bool conscious)
|
||||||
|
{
|
||||||
|
super.OnConsciousnessChanged(conscious);
|
||||||
|
ERR_GameMode mode = ERR_GameMode.Get();
|
||||||
|
if (mode && mode.ERR_Statistics()) mode.ERR_Statistics().ConsciousnessChanged(GetOwner(), conscious);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
modded class ERR_GameMode
|
||||||
|
{
|
||||||
|
protected ref ERR_RoundStatistics m_ERRStatistics;
|
||||||
|
[RplProp(onRplName: "ERR_OnRoundStatisticsReplicated")]
|
||||||
|
protected string m_ERRRoundSummary;
|
||||||
|
[RplProp(onRplName: "ERR_OnRoundStatisticsReplicated")]
|
||||||
|
protected string m_ERRCommunitySummary = "Community map records unavailable.";
|
||||||
|
protected ref ScriptInvoker m_ERRStatisticsChanged = new ScriptInvoker();
|
||||||
|
ERR_RoundStatistics ERR_Statistics() { return m_ERRStatistics; }
|
||||||
|
string ERR_RoundSummary()
|
||||||
|
{
|
||||||
|
if (m_ERRRoundSummary.IsEmpty()) return "";
|
||||||
|
return m_ERRCommunitySummary + "\n\n" + m_ERRRoundSummary;
|
||||||
|
}
|
||||||
|
void ERR_PublishCommunityStatistics(string summary)
|
||||||
|
{
|
||||||
|
if (!Replication.IsServer() || summary.Length() > 2048 || m_ERRCommunitySummary == summary) return;
|
||||||
|
m_ERRCommunitySummary = summary;
|
||||||
|
Replication.BumpMe(); ERR_OnRoundStatisticsReplicated();
|
||||||
|
}
|
||||||
|
ScriptInvoker ERR_OnStatisticsChanged() { return m_ERRStatisticsChanged; }
|
||||||
|
protected void ERR_OnRoundStatisticsReplicated() { m_ERRStatisticsChanged.Invoke(); }
|
||||||
|
void ERR_PublishRoundStatistics(string summary)
|
||||||
|
{
|
||||||
|
if (!Replication.IsServer() || !m_ERRRoundSummary.IsEmpty()) return;
|
||||||
|
m_ERRRoundSummary = summary;
|
||||||
|
Replication.BumpMe(); ERR_OnRoundStatisticsReplicated();
|
||||||
|
}
|
||||||
|
protected override void EOnInit(IEntity owner)
|
||||||
|
{
|
||||||
|
super.EOnInit(owner);
|
||||||
|
if (!Replication.IsServer() || SCR_Global.IsEditMode() || !Players()) return;
|
||||||
|
m_ERRStatistics = new ERR_RoundStatistics(this);
|
||||||
|
OnPhaseChanged().Insert(m_ERRStatistics.PhaseChanged);
|
||||||
|
Players().OnChanged().Insert(m_ERRStatistics.RosterChanged);
|
||||||
|
}
|
||||||
|
protected override void OnControllableDestroyed(IEntity entity, IEntity killerEntity, notnull Instigator instigator)
|
||||||
|
{
|
||||||
|
if (m_ERRStatistics) m_ERRStatistics.Killed(entity, killerEntity, instigator);
|
||||||
|
super.OnControllableDestroyed(entity, killerEntity, instigator);
|
||||||
|
}
|
||||||
|
protected override void OnPlayerKilled(int playerId, IEntity playerEntity, IEntity killerEntity, notnull Instigator killer)
|
||||||
|
{
|
||||||
|
if (m_ERRStatistics) m_ERRStatistics.Killed(playerEntity, killerEntity, killer);
|
||||||
|
super.OnPlayerKilled(playerId, playerEntity, killerEntity, killer);
|
||||||
|
}
|
||||||
|
protected override void OnGameModeEnd(SCR_GameModeEndData endData)
|
||||||
|
{
|
||||||
|
// Freeze before native shutdown can detach bodies or remove roster ownership.
|
||||||
|
if (m_ERRStatistics) m_ERRStatistics.Finish();
|
||||||
|
super.OnGameModeEnd(endData);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -51,10 +51,34 @@ modded class SCR_PlayerController
|
|||||||
if (mode && mode.Players()) life = mode.Players().Get(GetPlayerId());
|
if (mode && mode.Players()) life = mode.Players().Get(GetPlayerId());
|
||||||
if (!mode || !ERR_SelfEpinephrineRules.Allowed(mode.Active(), mode.Settings().Get("selfEpinephrine") == 1,
|
if (!mode || !ERR_SelfEpinephrineRules.Allowed(mode.Active(), mode.Settings().Get("selfEpinephrine") == 1,
|
||||||
life && life.Actor == actor && mode.Players().CurrentActor(actor) == life, ERR_Actors.Alive(actor), controller && controller.IsUnconscious())) return "";
|
life && life.Actor == actor && mode.Players().CurrentActor(actor) == life, ERR_Actors.Alive(actor), controller && controller.IsUnconscious())) return "";
|
||||||
|
IEntity injector = ERR_SelfEpinephrineInventory.FindUsable(actor);
|
||||||
|
if (!injector) return "No usable epinephrine injector in your inventory, or treatment is already active.";
|
||||||
|
SCR_ConsumableItemComponent item = SCR_ConsumableItemComponent.Cast(injector.FindComponent(SCR_ConsumableItemComponent));
|
||||||
|
SCR_ConsumableMorphine effect = SCR_ConsumableMorphine.Cast(item.GetConsumableEffect());
|
||||||
|
item.ApplyItemEffect(actor, actor, effect.GetAnimationParameters(injector, actor), injector, effect.GetDeleteOnUse());
|
||||||
|
return "Epinephrine administered. Its normal medical effect is active.";
|
||||||
|
}
|
||||||
|
|
||||||
|
[RplRpc(RplChannel.Reliable, RplRcver.Owner)]
|
||||||
|
protected void ERR_SelfEpinephrineResponse(string message)
|
||||||
|
{
|
||||||
|
if (this != GetGame().GetPlayerController()) return;
|
||||||
|
SCR_HintManagerComponent hints = SCR_HintManagerComponent.GetInstance();
|
||||||
|
if (hints) hints.ShowCustom(message, "Self epinephrine", 6);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shared read-only eligibility: defeat prediction and the actual Space action
|
||||||
|
// must agree, including native duplicate-dose rejection and item preference.
|
||||||
|
class ERR_SelfEpinephrineInventory
|
||||||
|
{
|
||||||
|
static IEntity FindUsable(IEntity actor)
|
||||||
|
{
|
||||||
|
if (!ERR_Actors.Alive(actor) || ERR_Actors.CanFight(actor)) return null;
|
||||||
InventoryStorageManagerComponent inventory = InventoryStorageManagerComponent.Cast(actor.FindComponent(InventoryStorageManagerComponent));
|
InventoryStorageManagerComponent inventory = InventoryStorageManagerComponent.Cast(actor.FindComponent(InventoryStorageManagerComponent));
|
||||||
array<IEntity> items = {};
|
array<IEntity> items = {};
|
||||||
if (inventory) inventory.GetItems(items);
|
if (inventory) inventory.GetItems(items);
|
||||||
if (items.Count() > 512) return "Unable to inspect your medical supplies.";
|
if (items.Count() > 512) return null;
|
||||||
// Prefer the standard injector; preserve each item's ordinary medical effect.
|
// Prefer the standard injector; preserve each item's ordinary medical effect.
|
||||||
for (int kind = 0; kind < 2; kind++)
|
for (int kind = 0; kind < 2; kind++)
|
||||||
foreach (IEntity injector : items)
|
foreach (IEntity injector : items)
|
||||||
@@ -68,18 +92,8 @@ modded class SCR_PlayerController
|
|||||||
if (kind == 1 && !ERR_ConsumableCombatEpinephrine.Cast(effect)) continue;
|
if (kind == 1 && !ERR_ConsumableCombatEpinephrine.Cast(effect)) continue;
|
||||||
SCR_EConsumableFailReason reason;
|
SCR_EConsumableFailReason reason;
|
||||||
if (!effect.CanApplyEffect(actor, actor, reason)) continue;
|
if (!effect.CanApplyEffect(actor, actor, reason)) continue;
|
||||||
// An unconscious player cannot perform the standing injection animation.
|
return injector;
|
||||||
// Use the native effect/consumption path on this exact carried item once.
|
|
||||||
item.ApplyItemEffect(actor, actor, effect.GetAnimationParameters(injector, actor), injector, effect.GetDeleteOnUse());
|
|
||||||
return "Epinephrine administered. Its normal medical effect is active.";
|
|
||||||
}
|
}
|
||||||
return "No usable epinephrine injector in your inventory, or treatment is already active.";
|
return null;
|
||||||
}
|
|
||||||
[RplRpc(RplChannel.Reliable, RplRcver.Owner)]
|
|
||||||
protected void ERR_SelfEpinephrineResponse(string message)
|
|
||||||
{
|
|
||||||
if (this != GetGame().GetPlayerController()) return;
|
|
||||||
SCR_HintManagerComponent hints = SCR_HintManagerComponent.GetInstance();
|
|
||||||
if (hints) hints.ShowCustom(message, "Self epinephrine", 6);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ class ERR_ServerItem
|
|||||||
{
|
{
|
||||||
ResourceName Prefab;
|
ResourceName Prefab;
|
||||||
string Label;
|
string Label;
|
||||||
|
string SearchLabel;
|
||||||
bool Eligible;
|
bool Eligible;
|
||||||
}
|
}
|
||||||
class ERR_ServerAddonItems
|
class ERR_ServerAddonItems
|
||||||
@@ -74,6 +75,7 @@ class ERR_ServerItemCatalog
|
|||||||
if (addon.Label.IsEmpty()) addon.Label = Clean(addon.Project);
|
if (addon.Label.IsEmpty()) addon.Label = Clean(addon.Project);
|
||||||
m_Addons.Insert(addon);
|
m_Addons.Insert(addon);
|
||||||
}
|
}
|
||||||
|
SortAddons(m_Addons);
|
||||||
Queue(manager.GetEntityCatalogOfType(EEntityCatalogType.ITEM, false));
|
Queue(manager.GetEntityCatalogOfType(EEntityCatalogType.ITEM, false));
|
||||||
array<Faction> all = {}; factions.GetFactionsList(all);
|
array<Faction> all = {}; factions.GetFactionsList(all);
|
||||||
foreach (Faction faction : all)
|
foreach (Faction faction : all)
|
||||||
@@ -109,6 +111,7 @@ class ERR_ServerItemCatalog
|
|||||||
item = new ERR_ServerItem(); item.Prefab = prefab; item.Eligible = eligible;
|
item = new ERR_ServerItem(); item.Prefab = prefab; item.Eligible = eligible;
|
||||||
item.Label = Clean(entry.GetEntityName());
|
item.Label = Clean(entry.GetEntityName());
|
||||||
if (item.Label.IsEmpty()) item.Label = Clean(FilePath.StripPath(prefab.GetPath()));
|
if (item.Label.IsEmpty()) item.Label = Clean(FilePath.StripPath(prefab.GetPath()));
|
||||||
|
item.SearchLabel = WidgetManager.Translate(item.Label);
|
||||||
m_Seen.Set(key, item);
|
m_Seen.Set(key, item);
|
||||||
array<string> sources = SCR_AddonTool.GetResourceAddons(prefab);
|
array<string> sources = SCR_AddonTool.GetResourceAddons(prefab);
|
||||||
bool attributed;
|
bool attributed;
|
||||||
@@ -119,16 +122,51 @@ class ERR_ServerItemCatalog
|
|||||||
if (m_Cursor >= m_Pending.Count())
|
if (m_Cursor >= m_Pending.Count())
|
||||||
{
|
{
|
||||||
GetGame().GetCallqueue().Remove(Scan);
|
GetGame().GetCallqueue().Remove(Scan);
|
||||||
|
foreach (ERR_ServerAddonItems sortedAddon : m_Addons) SortItems(sortedAddon.Items);
|
||||||
PrintFormat("Reintroduced [server items]: addons=%1 uniqueItems=%2 unattributed=%3 truncated=%4", m_Addons.Count(), m_Seen.Count(), m_Unattributed, m_Truncated);
|
PrintFormat("Reintroduced [server items]: addons=%1 uniqueItems=%2 unattributed=%3 truncated=%4", m_Addons.Count(), m_Seen.Count(), m_Unattributed, m_Truncated);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
static void SortAddons(array<ref ERR_ServerAddonItems> addons)
|
||||||
|
{
|
||||||
|
array<string> order = {}; map<string, ref ERR_ServerAddonItems> entries = new map<string, ref ERR_ServerAddonItems>();
|
||||||
|
foreach (ERR_ServerAddonItems addon : addons)
|
||||||
|
{
|
||||||
|
string key = addon.Label + "|" + addon.Id; key.ToLower(); order.Insert(key); entries.Set(key, addon);
|
||||||
|
}
|
||||||
|
order.Sort(); addons.Clear();
|
||||||
|
foreach (string key : order) addons.Insert(entries.Get(key));
|
||||||
|
}
|
||||||
|
static void SortItems(array<ref ERR_ServerItem> items)
|
||||||
|
{
|
||||||
|
array<string> order = {}; map<string, ref ERR_ServerItem> entries = new map<string, ref ERR_ServerItem>();
|
||||||
|
foreach (ERR_ServerItem item : items)
|
||||||
|
{
|
||||||
|
string key = item.SearchLabel + "|" + ERR_TerrainCatalog.Key(item.Prefab); key.ToLower(); order.Insert(key); entries.Set(key, item);
|
||||||
|
}
|
||||||
|
order.Sort(); items.Clear();
|
||||||
|
foreach (string key : order) items.Insert(entries.Get(key));
|
||||||
|
}
|
||||||
|
static string Flag(bool value)
|
||||||
|
{
|
||||||
|
if (value) return "1";
|
||||||
|
return "0";
|
||||||
|
}
|
||||||
|
static bool Matches(string label, string path, bool enabled, int filter, string query)
|
||||||
|
{
|
||||||
|
if ((filter == 1 && !enabled) || (filter == 2 && enabled)) return false;
|
||||||
|
if (query.IsEmpty()) return true;
|
||||||
|
string haystack = label + " " + path; haystack.ToLower(); query.ToLower();
|
||||||
|
array<string> words = {}; query.Split(" ", words, true);
|
||||||
|
foreach (string word : words) if (!haystack.Contains(word)) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
static string Share(int tickets, int total)
|
static string Share(int tickets, int total)
|
||||||
{
|
{
|
||||||
if (total <= 0 || tickets <= 0) return "0%";
|
if (total <= 0 || tickets <= 0) return "0%";
|
||||||
float percent = Math.Round(tickets * 10000.0 / total) / 100.0;
|
float percent = Math.Round(tickets * 10000.0 / total) / 100.0;
|
||||||
return percent.ToString() + "%";
|
return percent.ToString() + "%";
|
||||||
}
|
}
|
||||||
void Snapshot(ERR_GameMode mode, array<string> values, string selected, int page)
|
void Snapshot(ERR_GameMode mode, array<string> values, string selected, int page, int filter = 0, string query = "")
|
||||||
{
|
{
|
||||||
Start(mode);
|
Start(mode);
|
||||||
ERR_ItemPreferences preferences = GetGame().ERR_ItemSelections();
|
ERR_ItemPreferences preferences = GetGame().ERR_ItemSelections();
|
||||||
@@ -151,7 +189,7 @@ class ERR_ServerItemCatalog
|
|||||||
foreach (ERR_ServerAddonItems addon : m_Addons)
|
foreach (ERR_ServerAddonItems addon : m_Addons)
|
||||||
{
|
{
|
||||||
string defaultEnabled = "0"; if (preferences.Enabled(addon.Id, "*")) defaultEnabled = "1";
|
string defaultEnabled = "0"; if (preferences.Enabled(addon.Id, "*")) defaultEnabled = "1";
|
||||||
values.Insert("serverMod=" + addon.Id + "|" + addon.Label + "|" + addon.Items.Count().ToString() + "|" + addon.EnabledCount.ToString() + "|" + defaultEnabled);
|
values.Insert("serverMod=" + addon.Id + "|" + addon.Label + "|" + addon.Items.Count().ToString() + "|" + addon.EnabledCount.ToString() + "|" + defaultEnabled + "|" + Flag(preferences.ModEnabled(addon.Id)));
|
||||||
}
|
}
|
||||||
string state = "Catalog complete";
|
string state = "Catalog complete";
|
||||||
if (!m_Started || m_Cursor < m_Pending.Count()) state = "Scanning inventory catalogs";
|
if (!m_Started || m_Cursor < m_Pending.Count()) state = "Scanning inventory catalogs";
|
||||||
@@ -159,24 +197,28 @@ class ERR_ServerItemCatalog
|
|||||||
if (m_Unattributed > 0) state += "; " + m_Unattributed.ToString() + " items could not be attributed";
|
if (m_Unattributed > 0) state += "; " + m_Unattributed.ToString() + " items could not be attributed";
|
||||||
values.Insert("itemsScan=" + state);
|
values.Insert("itemsScan=" + state);
|
||||||
int extraChance = mode.Settings().Runtime("automaticEquipmentChancePercent");
|
int extraChance = mode.Settings().Runtime("automaticEquipmentChancePercent");
|
||||||
values.Insert("weightChance=" + extraChance.ToString() + "% current chance per eligible soldier. The Spawn chances page controls which extra item is chosen; inventory fit can still prevent a grant.");
|
values.Insert("weightChance=" + extraChance.ToString() + "% current chance per eligible soldier. Advanced weights controls which extra item is chosen; inventory fit can still prevent a grant.");
|
||||||
ERR_ServerAddonItems chosen = Find(selected);
|
ERR_ServerAddonItems chosen = Find(selected);
|
||||||
if (!chosen) return;
|
if (!chosen) return;
|
||||||
values.Insert("modWeight=" + chosen.Id + "|" + preferences.WeightSetting(chosen.Id, "*").ToString() + "|" + Share(chosen.TicketWeight, totalWeight));
|
values.Insert("modWeight=" + chosen.Id + "|" + preferences.WeightSetting(chosen.Id, "*").ToString() + "|" + Share(chosen.TicketWeight, totalWeight));
|
||||||
int lastPage = Math.Max(0, (chosen.Items.Count() - 1) / PAGE_SIZE);
|
array<ref ERR_ServerItem> matches = {};
|
||||||
|
foreach (ERR_ServerItem candidate : chosen.Items)
|
||||||
|
if (Matches(candidate.SearchLabel, candidate.Prefab.GetPath(), preferences.Allows(candidate.Prefab) && (!mode.Content() || mode.Content().Allows(candidate.Prefab)), filter, query)) matches.Insert(candidate);
|
||||||
|
int lastPage = Math.Max(0, (matches.Count() - 1) / PAGE_SIZE);
|
||||||
page = Math.Clamp(page, 0, lastPage);
|
page = Math.Clamp(page, 0, lastPage);
|
||||||
values.Insert("itemsPage=" + selected + "|" + page.ToString() + "|" + lastPage.ToString() + "|" + chosen.Items.Count().ToString());
|
values.Insert("itemsPage=" + selected + "|" + page.ToString() + "|" + lastPage.ToString() + "|" + matches.Count().ToString());
|
||||||
int end = Math.Min(chosen.Items.Count(), (page + 1) * PAGE_SIZE);
|
int end = Math.Min(matches.Count(), (page + 1) * PAGE_SIZE);
|
||||||
for (int i = page * PAGE_SIZE; i < end; i++)
|
for (int i = page * PAGE_SIZE; i < end; i++)
|
||||||
{
|
{
|
||||||
ERR_ServerItem item = chosen.Items[i];
|
ERR_ServerItem item = matches[i];
|
||||||
string stateText = "Catalog item; not in automatic equipment pool";
|
string stateText = "Catalog item; not in automatic equipment pool";
|
||||||
if (item.Eligible) stateText = "Eligible for automatic equipment";
|
if (item.Eligible) stateText = "Eligible for automatic equipment";
|
||||||
if (mode.Content() && !mode.Content().Allows(item.Prefab)) stateText = "Blocked by current content-pack selection";
|
if (mode.Content() && !mode.Content().Allows(item.Prefab)) stateText = "Blocked by current content-pack selection";
|
||||||
string key = ERR_TerrainCatalog.Key(item.Prefab);
|
string key = ERR_TerrainCatalog.Key(item.Prefab);
|
||||||
string enabled = "0";
|
string enabled = "0";
|
||||||
if (preferences.Enabled(selected, key)) enabled = "1";
|
if (preferences.Enabled(selected, key)) enabled = "1";
|
||||||
if (!preferences.Allows(item.Prefab)) stateText = "Disabled by item/mod selection";
|
if (!preferences.Allows(item.Prefab)) stateText = "Disabled by a source mod or item selection";
|
||||||
|
if (!preferences.ModEnabled(selected)) stateText = "Mod disabled - item choice is remembered";
|
||||||
string effective = "0";
|
string effective = "0";
|
||||||
if (preferences.Allows(item.Prefab) && (!mode.Content() || mode.Content().Allows(item.Prefab))) effective = "1";
|
if (preferences.Allows(item.Prefab) && (!mode.Content() || mode.Content().Allows(item.Prefab))) effective = "1";
|
||||||
values.Insert("itemWeight=" + key + "|" + preferences.WeightSetting(selected, key).ToString() + "|" + Share(ERR_AutomaticEquipmentCatalog.TicketWeight(item.Prefab), totalWeight));
|
values.Insert("itemWeight=" + key + "|" + preferences.WeightSetting(selected, key).ToString() + "|" + Share(ERR_AutomaticEquipmentCatalog.TicketWeight(item.Prefab), totalWeight));
|
||||||
@@ -189,6 +231,12 @@ modded class ArmaReforgerScripted
|
|||||||
{
|
{
|
||||||
protected ref ERR_ServerItemCatalog m_ERRServerItems;
|
protected ref ERR_ServerItemCatalog m_ERRServerItems;
|
||||||
protected ref ERR_ItemPreferences m_ERRItemSelections;
|
protected ref ERR_ItemPreferences m_ERRItemSelections;
|
||||||
|
protected ref ERR_ServerVehicleCatalog m_ERRServerVehicles;
|
||||||
|
ERR_ServerVehicleCatalog ERR_ServerVehicles()
|
||||||
|
{
|
||||||
|
if (!m_ERRServerVehicles) m_ERRServerVehicles = new ERR_ServerVehicleCatalog();
|
||||||
|
return m_ERRServerVehicles;
|
||||||
|
}
|
||||||
ERR_ItemPreferences ERR_ItemSelections()
|
ERR_ItemPreferences ERR_ItemSelections()
|
||||||
{
|
{
|
||||||
if (!m_ERRItemSelections) m_ERRItemSelections = new ERR_ItemPreferences();
|
if (!m_ERRItemSelections) m_ERRItemSelections = new ERR_ItemPreferences();
|
||||||
@@ -202,7 +250,8 @@ modded class ArmaReforgerScripted
|
|||||||
override protected void OnBeforeWorldCleanup()
|
override protected void OnBeforeWorldCleanup()
|
||||||
{
|
{
|
||||||
if (m_ERRServerItems) m_ERRServerItems.Stop();
|
if (m_ERRServerItems) m_ERRServerItems.Stop();
|
||||||
m_ERRServerItems = null; m_ERRItemSelections = null;
|
if (m_ERRServerVehicles) m_ERRServerVehicles.Stop();
|
||||||
|
m_ERRServerItems = null; m_ERRItemSelections = null; m_ERRServerVehicles = null;
|
||||||
super.OnBeforeWorldCleanup();
|
super.OnBeforeWorldCleanup();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,13 +10,21 @@ modded class ERR_AdminMenu
|
|||||||
protected ref array<string> m_ItemPresets = {};
|
protected ref array<string> m_ItemPresets = {};
|
||||||
protected ref map<string, int> m_ItemSaved = new map<string, int>();
|
protected ref map<string, int> m_ItemSaved = new map<string, int>();
|
||||||
protected ref map<string, int> m_AddonSelectionStates = new map<string, int>();
|
protected ref map<string, int> m_AddonSelectionStates = new map<string, int>();
|
||||||
|
protected ref map<string, int> m_ServerModEnabled = new map<string, int>();
|
||||||
|
protected ref map<string, string> m_ServerModLabels = new map<string, string>();
|
||||||
|
protected ref map<string, int> m_ServerModItemCounts = new map<string, int>();
|
||||||
|
protected ref map<string, int> m_ServerModVehicleCounts = new map<string, int>();
|
||||||
|
protected string m_ServerScanState;
|
||||||
|
protected string m_ItemRowSignature;
|
||||||
|
protected string m_LastItemChoice;
|
||||||
|
protected int m_LastItemFilter = -1;
|
||||||
protected string m_ShownItemAddon;
|
protected string m_ShownItemAddon;
|
||||||
protected int m_ShownItemPage;
|
protected int m_ShownItemPage;
|
||||||
protected int m_LastItemPage;
|
protected int m_LastItemPage;
|
||||||
override void OnMenuOpen()
|
override void OnMenuOpen()
|
||||||
{
|
{
|
||||||
super.OnMenuOpen();
|
super.OnMenuOpen();
|
||||||
foreach (string name : {"ItemsShow", "ItemsPrevious", "ItemsNext", "ItemsEnableAll", "ItemsDisableAll", "ItemsPresetSave", "ItemsPresetLoad", "ItemsPresetDelete"})
|
foreach (string name : {"ItemsShow", "ItemsClear", "ItemsPrevious", "ItemsNext", "ItemsEnableAll", "ItemsDisableAll", "ItemsPresetSave", "ItemsPresetLoad", "ItemsPresetDelete"})
|
||||||
{
|
{
|
||||||
SCR_ButtonTextComponent button = SCR_ButtonTextComponent.GetButtonText(name, GetRootWidget());
|
SCR_ButtonTextComponent button = SCR_ButtonTextComponent.GetButtonText(name, GetRootWidget());
|
||||||
if (!button) { Close(); return; }
|
if (!button) { Close(); return; }
|
||||||
@@ -24,6 +32,12 @@ modded class ERR_AdminMenu
|
|||||||
if (name == "ItemsEnableAll") ERR_SelectionStyle.Action(button, true);
|
if (name == "ItemsEnableAll") ERR_SelectionStyle.Action(button, true);
|
||||||
button.m_OnClicked.Insert(OnItemsButton); m_ItemButtons.Insert(button);
|
button.m_OnClicked.Insert(OnItemsButton); m_ItemButtons.Insert(button);
|
||||||
}
|
}
|
||||||
|
foreach (string filterName : {"ItemsFilter", "ModsFilter"})
|
||||||
|
{
|
||||||
|
SCR_ComboBoxComponent filter = SCR_ComboBoxComponent.GetComboBoxComponent(filterName, GetRootWidget());
|
||||||
|
if (filterName == "ModsFilter") filter.AddItem("With items / vehicles");
|
||||||
|
filter.AddItem("All"); filter.AddItem("Enabled"); filter.AddItem("Disabled"); filter.SetCurrentItem(0, false, false, false);
|
||||||
|
}
|
||||||
ERR_SelectionStyle.Status(GetRootWidget(), "ItemsSelectionState", -1);
|
ERR_SelectionStyle.Status(GetRootWidget(), "ItemsSelectionState", -1);
|
||||||
UpdateActions();
|
UpdateActions();
|
||||||
}
|
}
|
||||||
@@ -31,8 +45,10 @@ modded class ERR_AdminMenu
|
|||||||
{
|
{
|
||||||
foreach (SCR_ButtonTextComponent button : m_ItemButtons) if (button) button.m_OnClicked.Remove(OnItemsButton);
|
foreach (SCR_ButtonTextComponent button : m_ItemButtons) if (button) button.m_OnClicked.Remove(OnItemsButton);
|
||||||
ClearItemRows(); m_ItemPresets.Clear();
|
ClearItemRows(); m_ItemPresets.Clear();
|
||||||
foreach (string addon, Widget row : m_ServerAddonRows) SCR_ButtonTextComponent.GetButtonText("Enable", row).m_OnClicked.Remove(OnLoadedModEdit);
|
foreach (string addon, Widget row : m_ServerAddonRows)
|
||||||
m_AddonSelectionStates.Clear();
|
foreach (string action : {"Enable", "Disable", "EditItems", "EditVehicles"}) SCR_ButtonTextComponent.GetButtonText(action, row).m_OnClicked.Remove(OnLoadedModEdit);
|
||||||
|
m_AddonSelectionStates.Clear(); m_ServerModEnabled.Clear(); m_ServerModLabels.Clear(); m_ServerModItemCounts.Clear(); m_ServerModVehicleCounts.Clear();
|
||||||
|
m_LastItemChoice = ""; m_LastItemFilter = -1; m_ItemRowSignature = "";
|
||||||
m_ItemButtons.Clear(); m_ServerAddonRows.Clear(); m_ServerAddonKeys.Clear(); m_ServerAddonLabels.Clear();
|
m_ItemButtons.Clear(); m_ServerAddonRows.Clear(); m_ServerAddonKeys.Clear(); m_ServerAddonLabels.Clear();
|
||||||
m_ShownItemAddon = ""; m_ShownItemPage = 0; m_LastItemPage = 0;
|
m_ShownItemAddon = ""; m_ShownItemPage = 0; m_LastItemPage = 0;
|
||||||
super.OnMenuClose();
|
super.OnMenuClose();
|
||||||
@@ -49,11 +65,22 @@ modded class ERR_AdminMenu
|
|||||||
bool ready = m_Ready && !m_ServerAddonKeys.IsEmpty();
|
bool ready = m_Ready && !m_ServerAddonKeys.IsEmpty();
|
||||||
SCR_ComboBoxComponent combo = SCR_ComboBoxComponent.GetComboBoxComponent("ItemsMod", GetRootWidget());
|
SCR_ComboBoxComponent combo = SCR_ComboBoxComponent.GetComboBoxComponent("ItemsMod", GetRootWidget());
|
||||||
if (combo) combo.SetEnabled(ready);
|
if (combo) combo.SetEnabled(ready);
|
||||||
foreach (string addon, Widget loadedRow : m_ServerAddonRows) SCR_ButtonTextComponent.GetButtonText("Enable", loadedRow).SetEnabled(ready);
|
foreach (string addon, Widget loadedRow : m_ServerAddonRows)
|
||||||
|
{
|
||||||
|
bool hasItems = m_ServerModItemCounts.Get(addon) > 0;
|
||||||
|
bool hasVehicles = m_ServerModVehicleCounts.Get(addon) > 0;
|
||||||
|
bool selectedMod = m_ServerModEnabled.Get(addon) == 1;
|
||||||
|
SCR_ButtonTextComponent.GetButtonText("Enable", loadedRow).SetEnabled(ready && (hasItems || hasVehicles) && !selectedMod);
|
||||||
|
SCR_ButtonTextComponent.GetButtonText("Disable", loadedRow).SetEnabled(ready && (hasItems || hasVehicles) && selectedMod);
|
||||||
|
SCR_ButtonTextComponent.GetButtonText("EditItems", loadedRow).SetEnabled(ready && hasItems);
|
||||||
|
SCR_ButtonTextComponent.GetButtonText("EditVehicles", loadedRow).SetEnabled(ready && hasVehicles);
|
||||||
|
ERR_SelectionStyle.Pair(loadedRow, "Enable", "Disable", m_ServerModEnabled.Get(addon));
|
||||||
|
}
|
||||||
|
bool rowReady = ItemSelectionReady();
|
||||||
foreach (string key, Widget row : m_ItemRows)
|
foreach (string key, Widget row : m_ItemRows)
|
||||||
{
|
{
|
||||||
SCR_ButtonTextComponent.GetButtonText("Enable", row).SetEnabled(ready && m_ItemSaved.Contains(key) && m_ItemSaved.Get(key) == 0);
|
SCR_ButtonTextComponent.GetButtonText("Enable", row).SetEnabled(rowReady && m_ItemSaved.Contains(key) && m_ItemSaved.Get(key) == 0);
|
||||||
SCR_ButtonTextComponent.GetButtonText("Disable", row).SetEnabled(ready && m_ItemSaved.Contains(key) && m_ItemSaved.Get(key) == 1);
|
SCR_ButtonTextComponent.GetButtonText("Disable", row).SetEnabled(rowReady && m_ItemSaved.Contains(key) && m_ItemSaved.Get(key) == 1);
|
||||||
int selected = -1; if (m_ItemSaved.Contains(key)) selected = m_ItemSaved.Get(key);
|
int selected = -1; if (m_ItemSaved.Contains(key)) selected = m_ItemSaved.Get(key);
|
||||||
ERR_SelectionStyle.Pair(row, "Enable", "Disable", selected);
|
ERR_SelectionStyle.Pair(row, "Enable", "Disable", selected);
|
||||||
}
|
}
|
||||||
@@ -65,7 +92,7 @@ modded class ERR_AdminMenu
|
|||||||
if (button.GetRootWidget().GetName() == "ItemsPrevious") enabled = ready && m_ShownItemPage > 0;
|
if (button.GetRootWidget().GetName() == "ItemsPrevious") enabled = ready && m_ShownItemPage > 0;
|
||||||
if (button.GetRootWidget().GetName() == "ItemsNext") enabled = ready && m_ShownItemPage < m_LastItemPage;
|
if (button.GetRootWidget().GetName() == "ItemsNext") enabled = ready && m_ShownItemPage < m_LastItemPage;
|
||||||
string name = button.GetRootWidget().GetName();
|
string name = button.GetRootWidget().GetName();
|
||||||
if (name == "ItemsEnableAll" || name == "ItemsDisableAll") enabled = ready && !m_ShownItemAddon.IsEmpty();
|
if (name == "ItemsEnableAll" || name == "ItemsDisableAll") enabled = rowReady;
|
||||||
if (name == "ItemsPresetLoad" || name == "ItemsPresetDelete") enabled = ready && !m_ItemPresets.IsEmpty();
|
if (name == "ItemsPresetLoad" || name == "ItemsPresetDelete") enabled = ready && !m_ItemPresets.IsEmpty();
|
||||||
button.SetEnabled(enabled);
|
button.SetEnabled(enabled);
|
||||||
}
|
}
|
||||||
@@ -74,7 +101,7 @@ modded class ERR_AdminMenu
|
|||||||
{
|
{
|
||||||
array<string> keys = {}, labels = {};
|
array<string> keys = {}, labels = {};
|
||||||
array<string> itemRows = {}, presets = {};
|
array<string> itemRows = {}, presets = {};
|
||||||
string scan, shown;
|
string scan, shown, signature;
|
||||||
int page, last, total;
|
int page, last, total;
|
||||||
if (authorized && values)
|
if (authorized && values)
|
||||||
foreach (string entry : values)
|
foreach (string entry : values)
|
||||||
@@ -87,9 +114,11 @@ modded class ERR_AdminMenu
|
|||||||
if (field == "itemPreset") presets.Insert(value);
|
if (field == "itemPreset") presets.Insert(value);
|
||||||
if (field == "itemsPage" && parts.Count() == 4)
|
if (field == "itemsPage" && parts.Count() == 4)
|
||||||
{ shown = parts[0]; page = parts[1].ToInt(); last = parts[2].ToInt(); total = parts[3].ToInt(); }
|
{ shown = parts[0]; page = parts[1].ToInt(); last = parts[2].ToInt(); total = parts[3].ToInt(); }
|
||||||
if (field == "serverItem" && parts.Count() == 6) itemRows.Insert(value);
|
if (field == "serverItem" && parts.Count() == 6) { itemRows.Insert(value); signature += parts[0]; }
|
||||||
if (field != "serverMod" || parts.Count() != 5 || keys.Contains(parts[0])) continue;
|
if (field == "vehicleMod" && parts.Count() == 5) m_ServerModVehicleCounts.Set(parts[0], parts[2].ToInt());
|
||||||
|
if (field != "serverMod" || parts.Count() != 6 || keys.Contains(parts[0])) continue;
|
||||||
keys.Insert(parts[0]); labels.Insert(parts[1]);
|
keys.Insert(parts[0]); labels.Insert(parts[1]);
|
||||||
|
m_ServerModEnabled.Set(parts[0], parts[5].ToInt()); m_ServerModLabels.Set(parts[0], parts[1]); m_ServerModItemCounts.Set(parts[0], parts[2].ToInt());
|
||||||
int aggregate = ERR_SelectionStyle.Aggregate(parts[2].ToInt(), parts[3].ToInt(), parts[4] == "1");
|
int aggregate = ERR_SelectionStyle.Aggregate(parts[2].ToInt(), parts[3].ToInt(), parts[4] == "1");
|
||||||
m_AddonSelectionStates.Set(parts[0], aggregate);
|
m_AddonSelectionStates.Set(parts[0], aggregate);
|
||||||
Widget row = m_ServerAddonRows.Get(parts[0]);
|
Widget row = m_ServerAddonRows.Get(parts[0]);
|
||||||
@@ -98,13 +127,11 @@ modded class ERR_AdminMenu
|
|||||||
row = GetGame().GetWorkspace().CreateWidgets("{F092D45656A8E02B}UI/layouts/ERR/ContentRow.layout", GetRootWidget().FindAnyWidget("ModList"));
|
row = GetGame().GetWorkspace().CreateWidgets("{F092D45656A8E02B}UI/layouts/ERR/ContentRow.layout", GetRootWidget().FindAnyWidget("ModList"));
|
||||||
if (!row) continue;
|
if (!row) continue;
|
||||||
m_ServerAddonRows.Set(parts[0], row);
|
m_ServerAddonRows.Set(parts[0], row);
|
||||||
row.FindAnyWidget("Disable").SetVisible(false);
|
foreach (string action : {"Enable", "Disable", "EditItems", "EditVehicles"}) SCR_ButtonTextComponent.GetButtonText(action, row).m_OnClicked.Insert(OnLoadedModEdit);
|
||||||
SCR_ButtonTextComponent edit = SCR_ButtonTextComponent.GetButtonText("Enable", row);
|
|
||||||
edit.SetText("Edit items"); edit.m_OnClicked.Insert(OnLoadedModEdit);
|
|
||||||
}
|
}
|
||||||
ERR_SelectionStyle.Status(row, "SelectionState", aggregate);
|
ERR_SelectionStyle.Status(row, "SelectionState", parts[5].ToInt());
|
||||||
TextWidget.Cast(row.FindAnyWidget("PackLabel")).SetText(parts[1]);
|
TextWidget.Cast(row.FindAnyWidget("PackLabel")).SetText(parts[1]);
|
||||||
TextWidget.Cast(row.FindAnyWidget("PackState")).SetText("Loaded on server | " + parts[2] + " catalogued items | Edit future random equipment");
|
TextWidget.Cast(row.FindAnyWidget("PackState")).SetText(parts[3] + " / " + parts[2] + " items enabled");
|
||||||
}
|
}
|
||||||
bool changed = keys.Count() != m_ServerAddonKeys.Count();
|
bool changed = keys.Count() != m_ServerAddonKeys.Count();
|
||||||
if (!changed) for (int i = 0; i < keys.Count(); i++) if (keys[i] != m_ServerAddonKeys[i] || labels[i] != m_ServerAddonLabels[i]) changed = true;
|
if (!changed) for (int i = 0; i < keys.Count(); i++) if (keys[i] != m_ServerAddonKeys[i] || labels[i] != m_ServerAddonLabels[i]) changed = true;
|
||||||
@@ -118,14 +145,16 @@ modded class ERR_AdminMenu
|
|||||||
combo.SetCurrentItem(Math.Max(0, keys.Find(draft)), false, false, false);
|
combo.SetCurrentItem(Math.Max(0, keys.Find(draft)), false, false, false);
|
||||||
m_ServerAddonKeys.Copy(keys); m_ServerAddonLabels.Copy(labels);
|
m_ServerAddonKeys.Copy(keys); m_ServerAddonLabels.Copy(labels);
|
||||||
}
|
}
|
||||||
SetText("ItemsScan", scan);
|
m_ServerScanState = scan;
|
||||||
|
SetText("ItemsHelp", "Extra equipment | " + scan);
|
||||||
if (!shown.IsEmpty())
|
if (!shown.IsEmpty())
|
||||||
{
|
{
|
||||||
bool moved = shown != m_ShownItemAddon || page != m_ShownItemPage;
|
bool moved = shown != m_ShownItemAddon || page != m_ShownItemPage || signature != m_ItemRowSignature;
|
||||||
|
m_ItemRowSignature = signature;
|
||||||
m_ShownItemAddon = shown; m_ShownItemPage = page; m_LastItemPage = last;
|
m_ShownItemAddon = shown; m_ShownItemPage = page; m_LastItemPage = last;
|
||||||
int selected = keys.Find(shown); string title = shown;
|
int selected = keys.Find(shown); string title = shown;
|
||||||
if (selected >= 0) title = labels[selected];
|
if (selected >= 0) title = labels[selected];
|
||||||
SetText("ItemsPageState", title + " | " + total.ToString() + " items | Page " + (page + 1).ToString() + "/" + (last + 1).ToString());
|
SetText("ItemsPageState", total.ToString() + " matching items | Page " + (page + 1).ToString() + " of " + (last + 1).ToString());
|
||||||
if (moved)
|
if (moved)
|
||||||
{
|
{
|
||||||
ClearItemRows();
|
ClearItemRows();
|
||||||
@@ -150,11 +179,11 @@ modded class ERR_AdminMenu
|
|||||||
TextWidget.Cast(row.FindAnyWidget("PackLabel")).SetText(WidgetManager.Translate(parts[1]));
|
TextWidget.Cast(row.FindAnyWidget("PackLabel")).SetText(WidgetManager.Translate(parts[1]));
|
||||||
m_ItemSaved.Set(parts[0], parts[4].ToInt());
|
m_ItemSaved.Set(parts[0], parts[4].ToInt());
|
||||||
ERR_SelectionStyle.Status(row, "SelectionState", parts[5].ToInt());
|
ERR_SelectionStyle.Status(row, "SelectionState", parts[5].ToInt());
|
||||||
string selection = "Disabled"; if (parts[4] == "1") selection = "Enabled";
|
TextWidget.Cast(row.FindAnyWidget("PackState")).SetText(parts[3] + " | " + FilePath.StripPath(parts[2]));
|
||||||
TextWidget.Cast(row.FindAnyWidget("PackState")).SetText(selection + " here | " + parts[3] + "\n" + parts[2]);
|
|
||||||
}
|
}
|
||||||
string empty; if (itemRows.IsEmpty()) empty = "No registered inventory items found for this mod.";
|
string empty; if (itemRows.IsEmpty()) empty = "No items match. Clear search or choose All to see more.";
|
||||||
SetText("ItemsText", empty);
|
SetText("ItemsText", empty);
|
||||||
|
GetRootWidget().FindAnyWidget("ItemsText").SetVisible(!empty.IsEmpty());
|
||||||
ScrollLayoutWidget scroll = ScrollLayoutWidget.Cast(GetRootWidget().FindAnyWidget("ItemsScroll"));
|
ScrollLayoutWidget scroll = ScrollLayoutWidget.Cast(GetRootWidget().FindAnyWidget("ItemsScroll"));
|
||||||
if (moved && scroll) scroll.SetSliderPos(0, 0);
|
if (moved && scroll) scroll.SetSliderPos(0, 0);
|
||||||
}
|
}
|
||||||
@@ -175,9 +204,15 @@ modded class ERR_AdminMenu
|
|||||||
{
|
{
|
||||||
if (!button || !m_Ready || !IsFocused() || !m_Controller || m_Age > 6 || m_ClickDelay > 0) return;
|
if (!button || !m_Ready || !IsFocused() || !m_Controller || m_Age > 6 || m_ClickDelay > 0) return;
|
||||||
string name = button.GetRootWidget().GetName();
|
string name = button.GetRootWidget().GetName();
|
||||||
|
if (name == "ItemsClear")
|
||||||
|
{
|
||||||
|
SCR_EditBoxComponent.GetEditBoxComponent("ItemsSearch", GetRootWidget()).SetValue("");
|
||||||
|
SCR_ComboBoxComponent.GetComboBoxComponent("ItemsFilter", GetRootWidget()).SetCurrentItem(0, false, false, false);
|
||||||
|
BrowseItems(0); return;
|
||||||
|
}
|
||||||
if (name == "ItemsEnableAll" || name == "ItemsDisableAll")
|
if (name == "ItemsEnableAll" || name == "ItemsDisableAll")
|
||||||
{
|
{
|
||||||
if (m_ShownItemAddon.IsEmpty()) return;
|
if (!ItemSelectionReady()) return;
|
||||||
string value = "0"; if (name == "ItemsEnableAll") value = "1";
|
string value = "0"; if (name == "ItemsEnableAll") value = "1";
|
||||||
SubmitItems("items set " + m_ShownItemAddon + " * " + value); return;
|
SubmitItems("items set " + m_ShownItemAddon + " * " + value); return;
|
||||||
}
|
}
|
||||||
@@ -206,23 +241,73 @@ modded class ERR_AdminMenu
|
|||||||
if (button.GetRootWidget().GetName() == "ItemsPrevious") page = Math.Max(0, page - 1);
|
if (button.GetRootWidget().GetName() == "ItemsPrevious") page = Math.Max(0, page - 1);
|
||||||
if (button.GetRootWidget().GetName() == "ItemsNext") page = Math.Min(m_LastItemPage, page + 1);
|
if (button.GetRootWidget().GetName() == "ItemsNext") page = Math.Min(m_LastItemPage, page + 1);
|
||||||
}
|
}
|
||||||
SubmitItems("items list " + addon + " " + page.ToString());
|
if (name == "ItemsShow") page = 0;
|
||||||
|
BrowseItems(page);
|
||||||
|
}
|
||||||
|
protected bool ItemSelectionReady()
|
||||||
|
{
|
||||||
|
SCR_ComboBoxComponent combo = SCR_ComboBoxComponent.GetComboBoxComponent("ItemsMod", GetRootWidget());
|
||||||
|
if (!m_Ready || !combo) return false;
|
||||||
|
int index = combo.GetCurrentIndex();
|
||||||
|
return index >= 0 && index < m_ServerAddonKeys.Count() && m_ServerAddonKeys[index] == m_ShownItemAddon;
|
||||||
|
}
|
||||||
|
protected void BrowseItems(int page)
|
||||||
|
{
|
||||||
|
SCR_ComboBoxComponent combo = SCR_ComboBoxComponent.GetComboBoxComponent("ItemsMod", GetRootWidget());
|
||||||
|
int index = combo.GetCurrentIndex(); if (index < 0 || index >= m_ServerAddonKeys.Count()) return;
|
||||||
|
int filter = SCR_ComboBoxComponent.GetComboBoxComponent("ItemsFilter", GetRootWidget()).GetCurrentIndex();
|
||||||
|
string query = SCR_EditBoxComponent.GetEditBoxComponent("ItemsSearch", GetRootWidget()).GetValue().Trim();
|
||||||
|
if (query.Length() > 64) { ReceiveReply("Search can contain up to 64 characters."); return; }
|
||||||
|
m_LastItemChoice = m_ServerAddonKeys[index]; m_LastItemFilter = filter;
|
||||||
|
SubmitItems("items browse " + m_LastItemChoice + " " + page.ToString() + " " + filter.ToString() + " " + query);
|
||||||
|
UpdateActions();
|
||||||
|
}
|
||||||
|
override void OnMenuUpdate(float tDelta)
|
||||||
|
{
|
||||||
|
super.OnMenuUpdate(tDelta);
|
||||||
|
if (!m_Ready || !IsFocused() || m_ClickDelay > 0 || m_Age > 6) return;
|
||||||
|
if (m_Page == ERR_AdminPage.ALLOWED_ITEMS)
|
||||||
|
{
|
||||||
|
SCR_ComboBoxComponent combo = SCR_ComboBoxComponent.GetComboBoxComponent("ItemsMod", GetRootWidget());
|
||||||
|
int index = combo.GetCurrentIndex();
|
||||||
|
int filter = SCR_ComboBoxComponent.GetComboBoxComponent("ItemsFilter", GetRootWidget()).GetCurrentIndex();
|
||||||
|
if (index >= 0 && index < m_ServerAddonKeys.Count() && (m_ServerAddonKeys[index] != m_LastItemChoice || filter != m_LastItemFilter)) BrowseItems(0);
|
||||||
|
}
|
||||||
|
if (m_Page != ERR_AdminPage.ALLOWED_ITEMS) m_LastItemChoice = "";
|
||||||
|
if (m_Page != ERR_AdminPage.MODS) return;
|
||||||
|
string query = SCR_EditBoxComponent.GetEditBoxComponent("ModsSearch", GetRootWidget()).GetValue();
|
||||||
|
int filter = SCR_ComboBoxComponent.GetComboBoxComponent("ModsFilter", GetRootWidget()).GetCurrentIndex();
|
||||||
|
int shown;
|
||||||
|
foreach (string addon, Widget row : m_ServerAddonRows)
|
||||||
|
{
|
||||||
|
bool visible = ERR_ServerItemCatalog.Matches(m_ServerModLabels.Get(addon), addon, m_ServerModEnabled.Get(addon) == 1, Math.Max(0, filter - 1), query);
|
||||||
|
if (filter == 0 && m_ServerModItemCounts.Get(addon) == 0 && m_ServerModVehicleCounts.Get(addon) == 0) visible = false;
|
||||||
|
row.SetVisible(visible); if (visible) shown++;
|
||||||
|
string details = m_ServerModItemCounts.Get(addon).ToString() + " items / " + m_ServerModVehicleCounts.Get(addon).ToString() + " vehicles";
|
||||||
|
if (m_ServerModItemCounts.Get(addon) == 0 && m_ServerModVehicleCounts.Get(addon) == 0) details = "No selectable items or vehicles";
|
||||||
|
TextWidget.Cast(row.FindAnyWidget("PackState")).SetText(details);
|
||||||
|
}
|
||||||
|
SetText("ModsCount", shown.ToString() + " of " + m_ServerAddonRows.Count().ToString() + " loaded mods | " + m_ServerScanState);
|
||||||
}
|
}
|
||||||
protected void OnLoadedModEdit(SCR_ButtonBaseComponent button)
|
protected void OnLoadedModEdit(SCR_ButtonBaseComponent button)
|
||||||
{
|
{
|
||||||
if (!button || !m_Ready || !IsFocused() || !m_Controller || m_Age > 6 || m_ClickDelay > 0) return;
|
if (!button || !m_Ready || !IsFocused() || !m_Controller || m_Age > 6 || m_ClickDelay > 0) return;
|
||||||
foreach (string addon, Widget row : m_ServerAddonRows)
|
foreach (string addon, Widget row : m_ServerAddonRows)
|
||||||
{
|
{
|
||||||
if (button.GetRootWidget() != row.FindAnyWidget("Enable")) continue;
|
if (button.GetRootWidget() == row.FindAnyWidget("Enable")) { SubmitItems("items mod " + addon + " 1"); return; }
|
||||||
SCR_ComboBoxComponent combo = SCR_ComboBoxComponent.GetComboBoxComponent("ItemsMod", GetRootWidget());
|
if (button.GetRootWidget() == row.FindAnyWidget("Disable")) { SubmitItems("items mod " + addon + " 0"); return; }
|
||||||
if (combo) combo.SetCurrentItem(m_ServerAddonKeys.Find(addon), false, false, false);
|
if (button.GetRootWidget() == row.FindAnyWidget("EditVehicles")) { OpenVehicleAddon(addon); return; }
|
||||||
ShowPage(ERR_AdminPage.ALLOWED_ITEMS); SubmitItems("items list " + addon + " 0"); return;
|
if (button.GetRootWidget() != row.FindAnyWidget("EditItems")) continue;
|
||||||
|
SCR_ComboBoxComponent.GetComboBoxComponent("ItemsMod", GetRootWidget()).SetCurrentItem(m_ServerAddonKeys.Find(addon), false, false, false);
|
||||||
|
SCR_EditBoxComponent.GetEditBoxComponent("ItemsSearch", GetRootWidget()).SetValue("");
|
||||||
|
SCR_ComboBoxComponent.GetComboBoxComponent("ItemsFilter", GetRootWidget()).SetCurrentItem(0, false, false, false);
|
||||||
|
ShowPage(ERR_AdminPage.ALLOWED_ITEMS); BrowseItems(0); return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
protected void SubmitItems(string command)
|
protected void SubmitItems(string command)
|
||||||
{
|
{
|
||||||
m_ClickDelay = 0.3;
|
m_ClickDelay = 0.3;
|
||||||
SetText("Feedback", "Saving selection on the server...");
|
SetText("Feedback", "Waiting for server confirmation...");
|
||||||
m_Controller.ERR_AdminSubmit(command, true);
|
m_Controller.ERR_AdminSubmit(command, true);
|
||||||
}
|
}
|
||||||
protected void ClearItemRows()
|
protected void ClearItemRows()
|
||||||
@@ -237,7 +322,7 @@ modded class ERR_AdminMenu
|
|||||||
}
|
}
|
||||||
protected void OnItemRowButton(SCR_ButtonBaseComponent button)
|
protected void OnItemRowButton(SCR_ButtonBaseComponent button)
|
||||||
{
|
{
|
||||||
if (!button || !m_Ready || !IsFocused() || !m_Controller || m_Age > 6 || m_ClickDelay > 0 || m_ShownItemAddon.IsEmpty()) return;
|
if (!button || !m_Ready || !IsFocused() || !m_Controller || m_Age > 6 || m_ClickDelay > 0 || !ItemSelectionReady()) return;
|
||||||
foreach (string key, Widget row : m_ItemRows)
|
foreach (string key, Widget row : m_ItemRows)
|
||||||
{
|
{
|
||||||
string value;
|
string value;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ modded class ERR_AdminMenu
|
|||||||
protected ref map<string, Widget> m_WeightRows = new map<string, Widget>();
|
protected ref map<string, Widget> m_WeightRows = new map<string, Widget>();
|
||||||
protected ref map<string, int> m_WeightSaved = new map<string, int>();
|
protected ref map<string, int> m_WeightSaved = new map<string, int>();
|
||||||
protected ref array<string> m_WeightMods = {};
|
protected ref array<string> m_WeightMods = {};
|
||||||
|
protected string m_WeightRowSignature;
|
||||||
protected string m_WeightAddon;
|
protected string m_WeightAddon;
|
||||||
protected int m_WeightPage = -1;
|
protected int m_WeightPage = -1;
|
||||||
override void OnMenuOpen()
|
override void OnMenuOpen()
|
||||||
@@ -68,7 +69,8 @@ modded class ERR_AdminMenu
|
|||||||
m_WeightMods.Copy(m_ServerAddonKeys);
|
m_WeightMods.Copy(m_ServerAddonKeys);
|
||||||
combo.SetCurrentItem(Math.Max(0, m_ServerAddonKeys.Find(m_ShownItemAddon)), false, false, false);
|
combo.SetCurrentItem(Math.Max(0, m_ServerAddonKeys.Find(m_ShownItemAddon)), false, false, false);
|
||||||
}
|
}
|
||||||
bool moved = m_WeightAddon != m_ShownItemAddon || m_WeightPage != m_ShownItemPage;
|
bool moved = m_WeightAddon != m_ShownItemAddon || m_WeightPage != m_ShownItemPage || m_WeightRowSignature != m_ItemRowSignature;
|
||||||
|
m_WeightRowSignature = m_ItemRowSignature;
|
||||||
if (moved)
|
if (moved)
|
||||||
{
|
{
|
||||||
ClearWeightRows(); m_WeightAddon = m_ShownItemAddon; m_WeightPage = m_ShownItemPage;
|
ClearWeightRows(); m_WeightAddon = m_ShownItemAddon; m_WeightPage = m_ShownItemPage;
|
||||||
|
|||||||
@@ -0,0 +1,265 @@
|
|||||||
|
// Server discovery never instantiates a vehicle. New entries are opt-in per use.
|
||||||
|
class ERR_VehicleEntry
|
||||||
|
{
|
||||||
|
ResourceName Prefab;
|
||||||
|
int Defaults;
|
||||||
|
int Capabilities;
|
||||||
|
}
|
||||||
|
class ERR_ServerVehicleCatalog
|
||||||
|
{
|
||||||
|
protected ref array<ref ERR_ServerAddonItems> m_Addons = {};
|
||||||
|
protected ref map<string, ref ERR_VehicleEntry> m_Entries = new map<string, ref ERR_VehicleEntry>();
|
||||||
|
protected ref map<string, ref ERR_ServerItem> m_Items = new map<string, ref ERR_ServerItem>();
|
||||||
|
protected ref array<ResourceName> m_Pending = {};
|
||||||
|
protected ref set<string> m_Queued = new set<string>();
|
||||||
|
protected ERR_GameMode m_Mode;
|
||||||
|
protected int m_Cursor;
|
||||||
|
protected bool m_Started;
|
||||||
|
protected bool m_Truncated;
|
||||||
|
static string RoleName(int role)
|
||||||
|
{
|
||||||
|
if (role == 5) return "Shore boats";
|
||||||
|
if (role == 0) return "Garages / yards";
|
||||||
|
if (role == 1) return "Civilian traffic";
|
||||||
|
if (role == 2) return "Enemy patrols";
|
||||||
|
if (role == 3) return "Convoy armed escorts";
|
||||||
|
return "Convoy supply trucks";
|
||||||
|
}
|
||||||
|
static string RoleHint(int role)
|
||||||
|
{
|
||||||
|
if (role == 5) return "Shore boats: inspected unarmed Motorboat; coastal water and boarding-space checks apply.";
|
||||||
|
if (role == 0) return "Garages / yards: unarmed vehicles that fit the parking space.";
|
||||||
|
if (role == 1) return "Traffic: vehicles with AI driving support; road size limits apply.";
|
||||||
|
if (role == 2) return "Enemy patrols: driver and crew, up to 15 seats; your enemy faction supplies the soldiers.";
|
||||||
|
if (role == 3) return "Convoy escorts: driver and gunner required. The specialized BMP slot stays unchanged.";
|
||||||
|
return "Convoy supply slot: AI vehicle with cargo storage; normal supply capacity checks apply.";
|
||||||
|
}
|
||||||
|
static bool Candidate(string path)
|
||||||
|
{
|
||||||
|
path.ToLower();
|
||||||
|
return path.EndsWith(".et") && path.Contains("vehicle") && !path.Contains("wreck")
|
||||||
|
&& !path.Contains("_base") && !path.Contains("/parts/") && !path.Contains("/turrets/");
|
||||||
|
}
|
||||||
|
protected void Queue(ResourceName prefab, string path = "")
|
||||||
|
{
|
||||||
|
string key = ERR_TerrainCatalog.Key(prefab);
|
||||||
|
if (key.IsEmpty() || m_Queued.Contains(key)) return;
|
||||||
|
if (m_Pending.Count() >= 8192) { m_Truncated = true; return; }
|
||||||
|
m_Queued.Insert(key); m_Pending.Insert(prefab);
|
||||||
|
}
|
||||||
|
protected void Search(ResourceName prefab, string path = "") { if (Candidate(prefab)) Queue(prefab); }
|
||||||
|
protected void Catalog(SCR_EntityCatalog catalog)
|
||||||
|
{
|
||||||
|
if (!catalog) return;
|
||||||
|
array<SCR_EntityCatalogEntry> entries = {}; catalog.GetEntityList(entries);
|
||||||
|
foreach (SCR_EntityCatalogEntry entry : entries) if (entry && entry.IsEnabled()) Queue(entry.GetPrefab());
|
||||||
|
}
|
||||||
|
void Start()
|
||||||
|
{
|
||||||
|
if (m_Started || !Replication.IsServer()) return;
|
||||||
|
m_Mode = ERR_GameMode.Get();
|
||||||
|
SCR_EntityCatalogManagerComponent manager = SCR_EntityCatalogManagerComponent.GetInstance();
|
||||||
|
if (!m_Mode || !manager || !GetGame().GetFactionManager()) return;
|
||||||
|
m_Started = true;
|
||||||
|
array<string> loaded = {}; GameProject.GetLoadedAddons(loaded);
|
||||||
|
foreach (string guid : loaded)
|
||||||
|
{
|
||||||
|
ERR_ServerAddonItems addon = new ERR_ServerAddonItems(); addon.Id = guid; addon.Id.ToLower();
|
||||||
|
addon.Project = GameProject.GetAddonID(guid); addon.Label = ERR_ServerItemCatalog.Clean(GameProject.GetAddonTitle(guid));
|
||||||
|
if (addon.Label.IsEmpty()) addon.Label = addon.Project;
|
||||||
|
m_Addons.Insert(addon);
|
||||||
|
}
|
||||||
|
ERR_ServerItemCatalog.SortAddons(m_Addons);
|
||||||
|
ERR_ParkedCarCatalog cars = new ERR_ParkedCarCatalog(); cars.Find();
|
||||||
|
foreach (ResourceName parked : cars.Prefabs) Seed(parked, 0);
|
||||||
|
foreach (ResourceName road : cars.RoadPrefabs) Seed(road, 1);
|
||||||
|
Seed(ERR_CivilianTraffic.CarPrefab(2), 1);
|
||||||
|
foreach (ResourceName tractor : cars.Tractors) Seed(tractor, 0);
|
||||||
|
ERR_MotorbikeCatalog bikes = new ERR_MotorbikeCatalog(); bikes.Find();
|
||||||
|
foreach (ResourceName bike : bikes.Prefabs) Seed(bike, 0);
|
||||||
|
ERR_ShoreBoatCatalog boats = new ERR_ShoreBoatCatalog(); boats.Find();
|
||||||
|
foreach (ResourceName boat : boats.Prefabs) Seed(boat, 5);
|
||||||
|
Seed(ERR_HunterFleetRules.VehiclePrefab(0), 2); Seed(ERR_HunterFleetRules.VehiclePrefab(2), 2);
|
||||||
|
Seed(ERR_ConvoyResources.VehiclePrefab(0), 3); Seed(ERR_ConvoyResources.VehiclePrefab(1), 4);
|
||||||
|
Catalog(manager.GetEntityCatalogOfType(EEntityCatalogType.VEHICLE, false));
|
||||||
|
array<Faction> factions = {}; GetGame().GetFactionManager().GetFactionsList(factions);
|
||||||
|
foreach (Faction faction : factions)
|
||||||
|
{
|
||||||
|
SCR_Faction nativeFaction = SCR_Faction.Cast(faction);
|
||||||
|
if (nativeFaction) Catalog(nativeFaction.GetFactionEntityCatalogOfType(EEntityCatalogType.VEHICLE, false));
|
||||||
|
}
|
||||||
|
// Also find complete vehicle prefabs from mods without faction catalogs.
|
||||||
|
foreach (ERR_ServerAddonItems project : m_Addons)
|
||||||
|
{
|
||||||
|
SearchResourcesFilter filter = new SearchResourcesFilter(); filter.fileExtensions = {"et"}; filter.recursive = true;
|
||||||
|
filter.rootPath = "$" + project.Project + ":Prefabs";
|
||||||
|
ResourceDatabase.SearchResources(filter, Search);
|
||||||
|
}
|
||||||
|
GetGame().GetCallqueue().CallLater(Scan, 50, true); m_Mode.OnStop().Insert(Stop);
|
||||||
|
}
|
||||||
|
void Stop()
|
||||||
|
{
|
||||||
|
if (GetGame()) GetGame().GetCallqueue().Remove(Scan);
|
||||||
|
if (m_Mode) m_Mode.OnStop().Remove(Stop);
|
||||||
|
m_Mode = null; m_Started = false; m_Cursor = 0;
|
||||||
|
m_Addons.Clear(); m_Entries.Clear(); m_Items.Clear(); m_Pending.Clear(); m_Queued.Clear();
|
||||||
|
}
|
||||||
|
protected void Seed(ResourceName prefab, int role)
|
||||||
|
{
|
||||||
|
string key = ERR_TerrainCatalog.Key(prefab);
|
||||||
|
ERR_VehicleEntry entry = m_Entries.Get(key);
|
||||||
|
if (!entry) { entry = new ERR_VehicleEntry(); entry.Prefab = prefab; m_Entries.Set(key, entry); }
|
||||||
|
entry.Defaults = entry.Defaults | (1 << role);
|
||||||
|
// Authored defaults remain available while asynchronous discovery completes.
|
||||||
|
entry.Capabilities = entry.Capabilities | (1 << role);
|
||||||
|
Queue(prefab);
|
||||||
|
}
|
||||||
|
protected bool Has(Resource resource, typename component)
|
||||||
|
{
|
||||||
|
IEntitySource source = SCR_BaseContainerTools.FindEntitySource(resource);
|
||||||
|
if (!source) return false;
|
||||||
|
array<IEntityComponentSource> matches = {};
|
||||||
|
SCR_BaseContainerTools.FindComponentSourcesOfClass(source, component, true, matches);
|
||||||
|
return !matches.IsEmpty();
|
||||||
|
}
|
||||||
|
protected void Scan()
|
||||||
|
{
|
||||||
|
if (!m_Mode || m_Mode != ERR_GameMode.Get() || !Replication.IsServer()) { Stop(); return; }
|
||||||
|
int end = Math.Min(m_Cursor + 4, m_Pending.Count());
|
||||||
|
while (m_Cursor < end)
|
||||||
|
{
|
||||||
|
ResourceName prefab = m_Pending[m_Cursor++]; string key = ERR_TerrainCatalog.Key(prefab);
|
||||||
|
Resource resource = Resource.Load(prefab);
|
||||||
|
if (!resource || !resource.IsValid()) continue;
|
||||||
|
bool wheeled = Has(resource, VehicleWheeledSimulation);
|
||||||
|
bool tracked = Has(resource, VehicleTrackedSimulation);
|
||||||
|
if (!wheeled && !tracked) continue;
|
||||||
|
bool ai = (Has(resource, ChimeraAIVehicleControlComponent) || Has(resource, AICarMovementComponent)) && Has(resource, SCR_AIVehicleUsageComponent);
|
||||||
|
ERR_VehicleEntry entry = m_Entries.Get(key);
|
||||||
|
if (!entry) { entry = new ERR_VehicleEntry(); entry.Prefab = prefab; m_Entries.Set(key, entry); }
|
||||||
|
int capabilities;
|
||||||
|
if (wheeled) capabilities = 1;
|
||||||
|
if (ai)
|
||||||
|
{
|
||||||
|
capabilities = capabilities | 12;
|
||||||
|
if (wheeled) capabilities = capabilities | 2;
|
||||||
|
if (Has(resource, InventoryStorageManagerComponent)) capabilities = capabilities | 16;
|
||||||
|
}
|
||||||
|
if (ERR_ShoreBoatCatalog.Candidate(prefab)) capabilities = 32;
|
||||||
|
entry.Capabilities = capabilities;
|
||||||
|
ERR_ServerItem item = new ERR_ServerItem(); item.Prefab = prefab; item.Eligible = true;
|
||||||
|
item.Label = FilePath.StripPath(prefab.GetPath()); item.Label.Replace(".et", ""); item.Label.Replace("_", " "); item.SearchLabel = item.Label;
|
||||||
|
m_Items.Set(key, item);
|
||||||
|
array<string> sources = SCR_AddonTool.GetResourceAddons(prefab);
|
||||||
|
foreach (ERR_ServerAddonItems addon : m_Addons) if (sources.Contains(addon.Project)) addon.Items.Insert(item);
|
||||||
|
}
|
||||||
|
if (m_Cursor >= m_Pending.Count())
|
||||||
|
{
|
||||||
|
GetGame().GetCallqueue().Remove(Scan);
|
||||||
|
foreach (ERR_ServerAddonItems sorted : m_Addons) ERR_ServerItemCatalog.SortItems(sorted.Items);
|
||||||
|
PrintFormat("Reintroduced [server vehicles]: inspected=%1 groundVehicles=%2 truncated=%3", m_Cursor, m_Items.Count(), m_Truncated);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ERR_ServerAddonItems Find(string id)
|
||||||
|
{
|
||||||
|
foreach (ERR_ServerAddonItems addon : m_Addons) if (addon.Id == id) return addon;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
bool Contains(string addonId, string itemId)
|
||||||
|
{
|
||||||
|
ERR_ServerAddonItems addon = Find(addonId); if (!addon) return false;
|
||||||
|
foreach (ERR_ServerItem item : addon.Items) if (ERR_TerrainCatalog.Key(item.Prefab) == itemId) return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
void Reject(ResourceName prefab, int role)
|
||||||
|
{
|
||||||
|
ERR_VehicleEntry entry = m_Entries.Get(ERR_TerrainCatalog.Key(prefab));
|
||||||
|
if (!entry || role < 0 || role > 5) return;
|
||||||
|
entry.Capabilities = entry.Capabilities & ~(1 << role);
|
||||||
|
PrintFormat("Reintroduced [vehicle assignment]: rejected %1 for %2: physical fit, driver, crew seats or storage unavailable; excluded until next round", prefab, RoleName(role));
|
||||||
|
}
|
||||||
|
bool Capable(string key, int role)
|
||||||
|
{
|
||||||
|
ERR_VehicleEntry entry = m_Entries.Get(key);
|
||||||
|
return role >= 0 && role < 6 && entry && (entry.Capabilities & (1 << role)) != 0;
|
||||||
|
}
|
||||||
|
bool Allowed(ResourceName prefab, int role)
|
||||||
|
{
|
||||||
|
Start(); string key = ERR_TerrainCatalog.Key(prefab); ERR_VehicleEntry entry = m_Entries.Get(key);
|
||||||
|
return Capable(key, role) && GetGame().ERR_ItemSelections().AllowsVehicleRole(prefab, role, (entry.Defaults & (1 << role)) != 0);
|
||||||
|
}
|
||||||
|
ResourceName Choose(int role, ResourceName preferred, bool otherDefaults = true)
|
||||||
|
{
|
||||||
|
Start(); array<ResourceName> choices = {};
|
||||||
|
if (!preferred.IsEmpty() && Allowed(preferred, role)) choices.Insert(preferred);
|
||||||
|
foreach (string key, ERR_VehicleEntry entry : m_Entries)
|
||||||
|
if ((entry.Defaults & (1 << role)) == 0 && Allowed(entry.Prefab, role) && !choices.Contains(entry.Prefab)) choices.Insert(entry.Prefab);
|
||||||
|
// If the preferred default was disabled, another enabled default may serve.
|
||||||
|
if (choices.IsEmpty() && otherDefaults)
|
||||||
|
foreach (string key, ERR_VehicleEntry entry : m_Entries)
|
||||||
|
if (Allowed(entry.Prefab, role)) choices.Insert(entry.Prefab);
|
||||||
|
if (choices.IsEmpty()) return "";
|
||||||
|
return choices[Math.RandomInt(0, choices.Count())];
|
||||||
|
}
|
||||||
|
void Snapshot(array<string> values, string selected, int page, int filter, string query, int role = 0)
|
||||||
|
{
|
||||||
|
Start(); ERR_ItemPreferences preferences = GetGame().ERR_ItemSelections();
|
||||||
|
foreach (ERR_ServerAddonItems addon : m_Addons)
|
||||||
|
{
|
||||||
|
if (addon.Items.IsEmpty()) continue;
|
||||||
|
int enabled;
|
||||||
|
foreach (ERR_ServerItem item : addon.Items) if (Allowed(item.Prefab, role)) enabled++;
|
||||||
|
values.Insert("vehicleMod=" + addon.Id + "|" + addon.Label + "|" + addon.Items.Count().ToString() + "|" + enabled.ToString() + "|" + ERR_ServerItemCatalog.Flag(preferences.ModEnabled(addon.Id)));
|
||||||
|
}
|
||||||
|
string scan = "Vehicle discovery complete";
|
||||||
|
if (!m_Started || m_Cursor < m_Pending.Count()) scan = "Discovering vehicles from loaded mods";
|
||||||
|
if (m_Truncated) scan += "; scan limit reached";
|
||||||
|
values.Insert("vehiclesScan=" + scan);
|
||||||
|
ERR_ServerAddonItems chosen = Find(selected); if (!chosen) return;
|
||||||
|
array<ref ERR_ServerItem> matches = {};
|
||||||
|
foreach (ERR_ServerItem candidate : chosen.Items)
|
||||||
|
if (ERR_ServerItemCatalog.Matches(candidate.SearchLabel, candidate.Prefab.GetPath(), Allowed(candidate.Prefab, role), filter, query)) matches.Insert(candidate);
|
||||||
|
int last = Math.Max(0, (matches.Count() - 1) / ERR_ServerItemCatalog.PAGE_SIZE); page = Math.Clamp(page, 0, last);
|
||||||
|
values.Insert("vehiclesPage=" + selected + "|" + page.ToString() + "|" + last.ToString() + "|" + matches.Count().ToString() + "|" + role.ToString());
|
||||||
|
int end = Math.Min(matches.Count(), (page + 1) * ERR_ServerItemCatalog.PAGE_SIZE);
|
||||||
|
for (int i = page * ERR_ServerItemCatalog.PAGE_SIZE; i < end; i++)
|
||||||
|
{
|
||||||
|
ERR_ServerItem item = matches[i]; string key = ERR_TerrainCatalog.Key(item.Prefab); ERR_VehicleEntry entry = m_Entries.Get(key);
|
||||||
|
bool capable = Capable(key, role), effective = Allowed(item.Prefab, role);
|
||||||
|
int rule = preferences.VehicleRole(selected, key, role);
|
||||||
|
bool selectedChoice = rule == 1 || (rule == -1 && (entry.Defaults & (1 << role)) != 0);
|
||||||
|
string use = RoleName(role) + ": not assigned";
|
||||||
|
if (selectedChoice) use = RoleName(role) + ": selected; fit and crew checked at spawn";
|
||||||
|
if (!preferences.ModEnabled(selected)) use = "Mod disabled - assignments remembered";
|
||||||
|
else if (selectedChoice && !effective) use = "Disabled by another source mod or vehicle switch";
|
||||||
|
if (!capable) use = "Unavailable for this use: missing support or failed spawn checks";
|
||||||
|
values.Insert("serverVehicle=" + key + "|" + item.Label + "|" + ERR_ServerItemCatalog.Clean(item.Prefab.GetPath()) + "|" + use + "|" + ERR_ServerItemCatalog.Flag(selectedChoice) + "|" + ERR_ServerItemCatalog.Flag(effective) + "|" + ERR_ServerItemCatalog.Flag(capable));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ERR_VehicleSelection
|
||||||
|
{
|
||||||
|
static bool Allowed(ResourceName prefab) { return !prefab.IsEmpty() && GetGame().ERR_ItemSelections().AllowsVehicle(prefab); }
|
||||||
|
static ResourceName Choose(array<ResourceName> pool, int role = -1)
|
||||||
|
{
|
||||||
|
array<ResourceName> enabled = {};
|
||||||
|
foreach (ResourceName prefab : pool) if ((role < 0 && Allowed(prefab)) || (role >= 0 && RoleAllowed(prefab, role))) enabled.Insert(prefab);
|
||||||
|
if (enabled.IsEmpty()) return "";
|
||||||
|
return enabled[Math.RandomInt(0, enabled.Count())];
|
||||||
|
}
|
||||||
|
static ResourceName ForRole(int role, ResourceName preferred, bool otherDefaults = true) { return GetGame().ERR_ServerVehicles().Choose(role, preferred, otherDefaults); }
|
||||||
|
static bool RoleAllowed(ResourceName prefab, int role) { return !prefab.IsEmpty() && GetGame().ERR_ServerVehicles().Allowed(prefab, role); }
|
||||||
|
static bool PatrolSeats(int drivers, int turrets, int cargo, int total)
|
||||||
|
{
|
||||||
|
return drivers == 1 && turrets >= 0 && cargo >= 0 && turrets + cargo >= 1 && total == drivers + turrets + cargo && total <= 15;
|
||||||
|
}
|
||||||
|
// A mod cannot enlarge the existing 4m x 9m x 3.5m road deployment envelope.
|
||||||
|
static bool RoadFit(Vehicle vehicle)
|
||||||
|
{
|
||||||
|
if (!vehicle || (!vehicle.FindComponent(ChimeraAIVehicleControlComponent) && !vehicle.FindComponent(AICarMovementComponent)) || !vehicle.FindComponent(SCR_AIVehicleUsageComponent)) return false;
|
||||||
|
vector low, high; vehicle.GetBounds(low, high);
|
||||||
|
return ERR_ForceRules.FiniteVector(low) && ERR_ForceRules.FiniteVector(high)
|
||||||
|
&& low[0] >= -2 && high[0] <= 2 && low[2] >= -4.5 && high[2] <= 4.5 && high[1] - low[1] > 0 && high[1] - low[1] <= 3.5;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,195 @@
|
|||||||
|
modded class ERR_AdminMenu
|
||||||
|
{
|
||||||
|
protected ref array<string> m_VehicleMods = {};
|
||||||
|
protected ref array<string> m_VehicleLabels = {};
|
||||||
|
protected ref array<SCR_ButtonTextComponent> m_VehicleButtons = {};
|
||||||
|
protected ref map<string, Widget> m_VehicleRows = new map<string, Widget>();
|
||||||
|
protected ref map<string, int> m_VehicleSaved = new map<string, int>();
|
||||||
|
protected ref map<string, int> m_VehicleStates = new map<string, int>();
|
||||||
|
protected string m_ShownVehicleAddon;
|
||||||
|
protected string m_LastVehicleChoice;
|
||||||
|
protected string m_VehicleRowSignature;
|
||||||
|
protected int m_ShownVehiclePage;
|
||||||
|
protected int m_LastVehiclePage;
|
||||||
|
protected int m_LastVehicleFilter = -1;
|
||||||
|
protected int m_LastVehicleRole = -1;
|
||||||
|
protected int m_ShownVehicleRole;
|
||||||
|
protected ref map<string, int> m_VehicleCapable = new map<string, int>();
|
||||||
|
override void OnMenuOpen()
|
||||||
|
{
|
||||||
|
super.OnMenuOpen();
|
||||||
|
foreach (string name : {"VehiclesShow", "VehiclesClear", "VehiclesPrevious", "VehiclesNext", "VehiclesEnableAll", "VehiclesDisableAll"})
|
||||||
|
{
|
||||||
|
SCR_ButtonTextComponent button = SCR_ButtonTextComponent.GetButtonText(name, GetRootWidget());
|
||||||
|
if (!button) { Close(); return; }
|
||||||
|
button.m_OnClicked.Insert(OnVehiclesButton); m_VehicleButtons.Insert(button);
|
||||||
|
}
|
||||||
|
SCR_ComboBoxComponent filter = SCR_ComboBoxComponent.GetComboBoxComponent("VehiclesFilter", GetRootWidget());
|
||||||
|
filter.AddItem("All"); filter.AddItem("Enabled"); filter.AddItem("Disabled"); filter.SetCurrentItem(0, false, false, false);
|
||||||
|
SCR_ComboBoxComponent roles = SCR_ComboBoxComponent.GetComboBoxComponent("VehiclesRole", GetRootWidget());
|
||||||
|
for (int role = 0; role < 6; role++) roles.AddItem("Use for: " + ERR_ServerVehicleCatalog.RoleName(role));
|
||||||
|
roles.SetCurrentItem(0, false, false, false);
|
||||||
|
UpdateActions();
|
||||||
|
}
|
||||||
|
protected void ClearVehicleRows()
|
||||||
|
{
|
||||||
|
foreach (string key, Widget row : m_VehicleRows)
|
||||||
|
{
|
||||||
|
SCR_ButtonTextComponent.GetButtonText("Enable", row).m_OnClicked.Remove(OnVehicleRowButton);
|
||||||
|
SCR_ButtonTextComponent.GetButtonText("Disable", row).m_OnClicked.Remove(OnVehicleRowButton);
|
||||||
|
row.RemoveFromHierarchy();
|
||||||
|
}
|
||||||
|
m_VehicleRows.Clear(); m_VehicleSaved.Clear(); m_VehicleCapable.Clear();
|
||||||
|
}
|
||||||
|
override void OnMenuClose()
|
||||||
|
{
|
||||||
|
foreach (SCR_ButtonTextComponent button : m_VehicleButtons) button.m_OnClicked.Remove(OnVehiclesButton);
|
||||||
|
m_LastVehicleChoice = ""; m_LastVehicleFilter = -1; m_LastVehicleRole = -1; m_ShownVehicleAddon = ""; m_VehicleRowSignature = "";
|
||||||
|
ClearVehicleRows(); m_VehicleButtons.Clear(); m_VehicleMods.Clear(); m_VehicleLabels.Clear(); m_VehicleStates.Clear();
|
||||||
|
super.OnMenuClose();
|
||||||
|
}
|
||||||
|
protected override void UpdateActions()
|
||||||
|
{
|
||||||
|
super.UpdateActions();
|
||||||
|
bool ready = VehicleSelectionReady();
|
||||||
|
foreach (SCR_ButtonTextComponent button : m_VehicleButtons)
|
||||||
|
{
|
||||||
|
string name = button.GetRootWidget().GetName(); bool enabled = ready;
|
||||||
|
if (name == "VehiclesShow" || name == "VehiclesClear") enabled = m_Ready && !m_VehicleMods.IsEmpty();
|
||||||
|
if (name == "VehiclesPrevious") enabled = ready && m_ShownVehiclePage > 0;
|
||||||
|
if (name == "VehiclesNext") enabled = ready && m_ShownVehiclePage < m_LastVehiclePage;
|
||||||
|
button.SetEnabled(enabled);
|
||||||
|
}
|
||||||
|
foreach (string key, Widget row : m_VehicleRows)
|
||||||
|
{
|
||||||
|
SCR_ButtonTextComponent.GetButtonText("Enable", row).SetEnabled(ready && m_VehicleCapable.Get(key) == 1 && m_VehicleSaved.Get(key) == 0);
|
||||||
|
SCR_ButtonTextComponent.GetButtonText("Disable", row).SetEnabled(ready && m_VehicleSaved.Get(key) == 1);
|
||||||
|
ERR_SelectionStyle.Pair(row, "Enable", "Disable", m_VehicleSaved.Get(key));
|
||||||
|
if (!m_Ready) ERR_SelectionStyle.Status(row, "SelectionState", -1);
|
||||||
|
}
|
||||||
|
int state = -1; if (ready && m_VehicleStates.Contains(m_ShownVehicleAddon)) state = m_VehicleStates.Get(m_ShownVehicleAddon);
|
||||||
|
ERR_SelectionStyle.Status(GetRootWidget(), "VehiclesSelectionState", state);
|
||||||
|
}
|
||||||
|
override void ReceiveSnapshot(bool authorized, array<string> values, array<string> status)
|
||||||
|
{
|
||||||
|
super.ReceiveSnapshot(authorized, values, status);
|
||||||
|
if (!authorized || !values) return;
|
||||||
|
array<string> keys = {}, labels = {}, rows = {}; string shown, signature, scan; int page, last, total, role;
|
||||||
|
foreach (string entry : values)
|
||||||
|
{
|
||||||
|
int divider = entry.IndexOf("="); if (divider < 0) continue;
|
||||||
|
string field = entry.Substring(0, divider), value = entry.Substring(divider + 1, entry.Length() - divider - 1);
|
||||||
|
array<string> parts = {}; value.Split("|", parts, false);
|
||||||
|
if (field == "vehiclesScan") scan = value;
|
||||||
|
if (field == "vehicleMod" && parts.Count() == 5)
|
||||||
|
{
|
||||||
|
keys.Insert(parts[0]); labels.Insert(parts[1]);
|
||||||
|
m_VehicleStates.Set(parts[0], ERR_SelectionStyle.Aggregate(parts[2].ToInt(), parts[3].ToInt(), parts[4] == "1"));
|
||||||
|
}
|
||||||
|
if (field == "vehiclesPage" && parts.Count() == 5) { shown = parts[0]; page = parts[1].ToInt(); last = parts[2].ToInt(); total = parts[3].ToInt(); role = parts[4].ToInt(); }
|
||||||
|
if (field == "serverVehicle" && parts.Count() == 7) { rows.Insert(value); signature += parts[0]; }
|
||||||
|
}
|
||||||
|
SCR_ComboBoxComponent combo = SCR_ComboBoxComponent.GetComboBoxComponent("VehiclesMod", GetRootWidget());
|
||||||
|
if (keys.Count() != m_VehicleMods.Count())
|
||||||
|
{
|
||||||
|
combo.ClearAll(); foreach (string label : labels) combo.AddItem(label);
|
||||||
|
m_VehicleMods.Copy(keys); m_VehicleLabels.Copy(labels); combo.SetCurrentItem(Math.Max(0, keys.Find(m_ShownVehicleAddon)), false, false, false);
|
||||||
|
}
|
||||||
|
if (!shown.IsEmpty())
|
||||||
|
{
|
||||||
|
bool moved = shown != m_ShownVehicleAddon || page != m_ShownVehiclePage || signature != m_VehicleRowSignature || role != m_ShownVehicleRole;
|
||||||
|
SetText("VehiclesHelp", ERR_ServerVehicleCatalog.RoleHint(role));
|
||||||
|
m_ShownVehicleRole = role; m_ShownVehicleAddon = shown; m_ShownVehiclePage = page; m_LastVehiclePage = last; m_VehicleRowSignature = signature;
|
||||||
|
if (moved) ClearVehicleRows();
|
||||||
|
string scanNote; if (scan != "Vehicle discovery complete") scanNote = " (scanning)";
|
||||||
|
SetText("VehiclesPageState", total.ToString() + " matching vehicles | Page " + (page + 1).ToString() + " of " + (last + 1).ToString() + scanNote);
|
||||||
|
foreach (string data : rows)
|
||||||
|
{
|
||||||
|
array<string> parts = {}; data.Split("|", parts, false); Widget row = m_VehicleRows.Get(parts[0]);
|
||||||
|
if (!row)
|
||||||
|
{
|
||||||
|
row = GetGame().GetWorkspace().CreateWidgets("{93A8120CBE7DA469}UI/layouts/ERR/ItemRow.layout", GetRootWidget().FindAnyWidget("VehiclesContent"));
|
||||||
|
if (!row) continue;
|
||||||
|
m_VehicleRows.Set(parts[0], row);
|
||||||
|
SCR_ButtonTextComponent.GetButtonText("Enable", row).m_OnClicked.Insert(OnVehicleRowButton);
|
||||||
|
SCR_ButtonTextComponent.GetButtonText("Disable", row).m_OnClicked.Insert(OnVehicleRowButton);
|
||||||
|
}
|
||||||
|
TextWidget.Cast(row.FindAnyWidget("PackLabel")).SetText(WidgetManager.Translate(parts[1]));
|
||||||
|
TextWidget.Cast(row.FindAnyWidget("PackState")).SetText(parts[3]);
|
||||||
|
m_VehicleCapable.Set(parts[0], parts[6].ToInt()); m_VehicleSaved.Set(parts[0], parts[4].ToInt()); ERR_SelectionStyle.Status(row, "SelectionState", parts[5].ToInt());
|
||||||
|
}
|
||||||
|
string empty; if (rows.IsEmpty()) empty = "No vehicles match. Clear search or choose All to see more.";
|
||||||
|
SetText("VehiclesText", empty);
|
||||||
|
GetRootWidget().FindAnyWidget("VehiclesText").SetVisible(!empty.IsEmpty());
|
||||||
|
if (moved) ScrollLayoutWidget.Cast(GetRootWidget().FindAnyWidget("VehiclesScroll")).SetSliderPos(0, 0);
|
||||||
|
}
|
||||||
|
else if (keys.IsEmpty()) SetText("VehiclesText", "Discovering ground vehicles on this server...");
|
||||||
|
UpdateActions();
|
||||||
|
}
|
||||||
|
protected override void OpenVehicleAddon(string addon)
|
||||||
|
{
|
||||||
|
int index = m_VehicleMods.Find(addon); if (index < 0) return;
|
||||||
|
SCR_ComboBoxComponent.GetComboBoxComponent("VehiclesMod", GetRootWidget()).SetCurrentItem(index, false, false, false);
|
||||||
|
SCR_EditBoxComponent.GetEditBoxComponent("VehiclesSearch", GetRootWidget()).SetValue("");
|
||||||
|
SCR_ComboBoxComponent.GetComboBoxComponent("VehiclesFilter", GetRootWidget()).SetCurrentItem(0, false, false, false);
|
||||||
|
ShowPage(ERR_AdminPage.VEHICLE_POOL); BrowseVehicles(0);
|
||||||
|
}
|
||||||
|
protected bool VehicleSelectionReady()
|
||||||
|
{
|
||||||
|
SCR_ComboBoxComponent combo = SCR_ComboBoxComponent.GetComboBoxComponent("VehiclesMod", GetRootWidget());
|
||||||
|
if (!m_Ready || !combo) return false;
|
||||||
|
int index = combo.GetCurrentIndex();
|
||||||
|
return index >= 0 && index < m_VehicleMods.Count() && m_VehicleMods[index] == m_ShownVehicleAddon && SCR_ComboBoxComponent.GetComboBoxComponent("VehiclesRole", GetRootWidget()).GetCurrentIndex() == m_ShownVehicleRole;
|
||||||
|
}
|
||||||
|
protected void BrowseVehicles(int page)
|
||||||
|
{
|
||||||
|
int index = SCR_ComboBoxComponent.GetComboBoxComponent("VehiclesMod", GetRootWidget()).GetCurrentIndex();
|
||||||
|
if (index < 0 || index >= m_VehicleMods.Count()) return;
|
||||||
|
int filter = SCR_ComboBoxComponent.GetComboBoxComponent("VehiclesFilter", GetRootWidget()).GetCurrentIndex();
|
||||||
|
string query = SCR_EditBoxComponent.GetEditBoxComponent("VehiclesSearch", GetRootWidget()).GetValue().Trim();
|
||||||
|
if (query.Length() > 64) { ReceiveReply("Search can contain up to 64 characters."); return; }
|
||||||
|
m_LastVehicleChoice = m_VehicleMods[index]; m_LastVehicleFilter = filter;
|
||||||
|
m_LastVehicleRole = SCR_ComboBoxComponent.GetComboBoxComponent("VehiclesRole", GetRootWidget()).GetCurrentIndex();
|
||||||
|
SubmitItems("vehicles browse " + m_LastVehicleChoice + " " + page.ToString() + " " + filter.ToString() + " " + m_LastVehicleRole.ToString() + " " + query);
|
||||||
|
UpdateActions();
|
||||||
|
}
|
||||||
|
override void OnMenuUpdate(float tDelta)
|
||||||
|
{
|
||||||
|
super.OnMenuUpdate(tDelta);
|
||||||
|
if (!m_Ready || !IsFocused() || m_ClickDelay > 0 || m_Age > 6 || m_Page != ERR_AdminPage.VEHICLE_POOL) return;
|
||||||
|
int index = SCR_ComboBoxComponent.GetComboBoxComponent("VehiclesMod", GetRootWidget()).GetCurrentIndex();
|
||||||
|
int filter = SCR_ComboBoxComponent.GetComboBoxComponent("VehiclesFilter", GetRootWidget()).GetCurrentIndex();
|
||||||
|
if (index >= 0 && index < m_VehicleMods.Count() && (m_VehicleMods[index] != m_LastVehicleChoice || filter != m_LastVehicleFilter || SCR_ComboBoxComponent.GetComboBoxComponent("VehiclesRole", GetRootWidget()).GetCurrentIndex() != m_LastVehicleRole)) BrowseVehicles(0);
|
||||||
|
}
|
||||||
|
protected void OnVehiclesButton(SCR_ButtonBaseComponent button)
|
||||||
|
{
|
||||||
|
if (!button || !m_Ready || !IsFocused() || !m_Controller || m_Age > 6 || m_ClickDelay > 0) return;
|
||||||
|
string name = button.GetRootWidget().GetName();
|
||||||
|
if (name == "VehiclesEnableAll" || name == "VehiclesDisableAll")
|
||||||
|
{
|
||||||
|
if (!VehicleSelectionReady()) return;
|
||||||
|
string value = "0"; if (name == "VehiclesEnableAll") value = "1";
|
||||||
|
SubmitItems("vehicles role " + m_ShownVehicleAddon + " " + m_ShownVehicleRole.ToString() + " * " + value); return;
|
||||||
|
}
|
||||||
|
if (name == "VehiclesClear")
|
||||||
|
{
|
||||||
|
SCR_EditBoxComponent.GetEditBoxComponent("VehiclesSearch", GetRootWidget()).SetValue("");
|
||||||
|
SCR_ComboBoxComponent.GetComboBoxComponent("VehiclesFilter", GetRootWidget()).SetCurrentItem(0, false, false, false);
|
||||||
|
}
|
||||||
|
int page;
|
||||||
|
if (name == "VehiclesPrevious") page = Math.Max(0, m_ShownVehiclePage - 1);
|
||||||
|
if (name == "VehiclesNext") page = Math.Min(m_LastVehiclePage, m_ShownVehiclePage + 1);
|
||||||
|
BrowseVehicles(page);
|
||||||
|
}
|
||||||
|
protected void OnVehicleRowButton(SCR_ButtonBaseComponent button)
|
||||||
|
{
|
||||||
|
if (!button || !m_Ready || !IsFocused() || !m_Controller || m_Age > 6 || m_ClickDelay > 0 || !VehicleSelectionReady()) return;
|
||||||
|
foreach (string key, Widget row : m_VehicleRows)
|
||||||
|
{
|
||||||
|
string value;
|
||||||
|
if (button.GetRootWidget() == row.FindAnyWidget("Enable")) value = "1";
|
||||||
|
if (button.GetRootWidget() == row.FindAnyWidget("Disable")) value = "0";
|
||||||
|
if (!value.IsEmpty()) { SubmitItems("vehicles role " + m_ShownVehicleAddon + " " + m_ShownVehicleRole.ToString() + " " + key + " " + value); return; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,12 +9,23 @@ class ERR_Settings
|
|||||||
|
|
||||||
static array<string> Keys()
|
static array<string> Keys()
|
||||||
{
|
{
|
||||||
return {"aiDoctrine", "aiShadow", "sarkaAngerPerCar", "motorbikeCount", "planeAltitudeMetres", "planeReplacementSeconds", "planeSectorSeconds", "planeReconEnabled", "droneReconCount", "droneCooldownSeconds", "fieldCampCount", "fieldHospitalCount", "startWeaponCount", "startMagazineCount", "objectGuardCount", "objectGuardEvery", "objectGuardHQMode", "hunterSpawnDistanceMetres", "roadblockCount", "civilianTrafficCount", "startMode", "keepWeaponOnUnconscious", "selfEpinephrine", "restartOnEmpty", "emptyRestartHours", "reportIntervalSeconds", "supportSquadLimit", "responseRadiusMetres", "aiIntelChancePercent", "intelRevealChancePercent", "showAllSites", "radioDefenceSeconds", "startTimeMinutes", "timeMultiplier", "startDateCode", "weatherProfile", "viewDistanceMetres", "antiAirChancePercent", "antiPersonnelMineChancePercent", "automaticEquipmentChancePercent", "automaticConvoyEnabled", "convoyCount", "heliTimerMinutes"};
|
return {"shoreBoatCount", "shoreBoatChancePercent", "shoreBoatCrossingChancePercent", "crashLootMode", "crashSiteCount", "crashHelicopterChancePercent", "crashRifleCount", "crashMagazineCount", "crashAntiAirChancePercent", "crashRocketChancePercent", "telemetryEnabled", "aiDoctrine", "aiShadow", "sarkaAngerPerCar", "motorbikeCount", "planeAltitudeMetres", "planeReplacementSeconds", "planeSectorSeconds", "planeReconEnabled", "droneReconCount", "droneCooldownSeconds", "fieldCampCount", "fieldHospitalCount", "startWeaponCount", "startMagazineCount", "objectGuardCount", "objectGuardEvery", "objectGuardHQMode", "hunterSpawnDistanceMetres", "roadblockCount", "largeRoadblockCount", "mainRoadWidthMetres", "civilianTrafficCount", "garageVehicleCount", "garageChancePercent", "garageRadiusMetres", "garageSpawnIntervalSeconds", "startMode", "keepWeaponOnUnconscious", "selfEpinephrine", "restartOnEmpty", "emptyRestartHours", "reportIntervalSeconds", "supportSquadLimit", "responseRadiusMetres", "aiIntelChancePercent", "intelRevealChancePercent", "radioIntelSiteCount", "showAllSites", "radioDefenceSeconds", "startTimeMinutes", "timeMultiplier", "startDateCode", "weatherProfile", "viewDistanceMetres", "antiAirChancePercent", "antiPersonnelMineChancePercent", "automaticEquipmentChancePercent", "automaticConvoyEnabled", "convoyCount", "heliTimerMinutes"};
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool Valid(string key, int value)
|
static bool Valid(string key, int value)
|
||||||
{
|
{
|
||||||
if (key == "aiDoctrine" || key == "aiShadow") return value == 0 || value == 1;
|
if (key == "shoreBoatCount") return value >= 0 && value <= 16;
|
||||||
|
if (key == "shoreBoatChancePercent") return value >= 0 && value <= 100;
|
||||||
|
if (key == "shoreBoatCrossingChancePercent") return value >= 0 && value <= 100;
|
||||||
|
if (key == "crashLootMode") return value == 0 || value == 1;
|
||||||
|
if (key == "crashSiteCount") return value >= 0 && value <= 12;
|
||||||
|
if (key == "crashHelicopterChancePercent") return value >= 0 && value <= 100;
|
||||||
|
if (key == "crashRifleCount") return value >= 1 && value <= 6;
|
||||||
|
if (key == "crashMagazineCount") return value >= 1 && value <= 8;
|
||||||
|
if (key == "crashAntiAirChancePercent") return value >= 0 && value <= 100;
|
||||||
|
if (key == "crashRocketChancePercent") return value >= 0 && value <= 100;
|
||||||
|
if (key == "telemetryEnabled" || key == "aiDoctrine" || key == "aiShadow") return value == 0 || value == 1;
|
||||||
|
if (key == "radioIntelSiteCount") return value >= 0 && value <= 20;
|
||||||
if (key == "sarkaAngerPerCar") return value >= 0 && value <= 30;
|
if (key == "sarkaAngerPerCar") return value >= 0 && value <= 30;
|
||||||
if (key == "motorbikeCount") return value >= 0 && value <= 6;
|
if (key == "motorbikeCount") return value >= 0 && value <= 6;
|
||||||
if (key == "droneReconCount") return value >= 0 && value <= 3;
|
if (key == "droneReconCount") return value >= 0 && value <= 3;
|
||||||
@@ -32,6 +43,12 @@ class ERR_Settings
|
|||||||
if (key == "objectGuardCount") return value >= 0 && value <= 16;
|
if (key == "objectGuardCount") return value >= 0 && value <= 16;
|
||||||
if (key == "hunterSpawnDistanceMetres") return value >= 800 && value <= 3000;
|
if (key == "hunterSpawnDistanceMetres") return value >= 800 && value <= 3000;
|
||||||
if (key == "roadblockCount") return value >= 0 && value <= 30;
|
if (key == "roadblockCount") return value >= 0 && value <= 30;
|
||||||
|
if (key == "largeRoadblockCount") return value >= 0 && value <= 30;
|
||||||
|
if (key == "mainRoadWidthMetres") return value >= 5 && value <= 12;
|
||||||
|
if (key == "garageChancePercent") return value >= 0 && value <= 100;
|
||||||
|
if (key == "garageRadiusMetres") return value >= 250 && value <= 1500;
|
||||||
|
if (key == "garageSpawnIntervalSeconds") return value >= 5 && value <= 60;
|
||||||
|
if (key == "garageVehicleCount") return value >= 0 && value <= 24;
|
||||||
if (key == "civilianTrafficCount") return value >= 0 && value <= 6;
|
if (key == "civilianTrafficCount") return value >= 0 && value <= 6;
|
||||||
if (key == "startMode") return value == 0 || value == 1;
|
if (key == "startMode") return value == 0 || value == 1;
|
||||||
if (key == "showAllSites") return value == 0 || value == 1;
|
if (key == "showAllSites") return value == 0 || value == 1;
|
||||||
@@ -120,7 +137,18 @@ class ERR_Settings
|
|||||||
|
|
||||||
static int AddedDefault(string key)
|
static int AddedDefault(string key)
|
||||||
{
|
{
|
||||||
|
if (key == "shoreBoatCount") return 6;
|
||||||
|
if (key == "shoreBoatChancePercent") return 20;
|
||||||
|
if (key == "shoreBoatCrossingChancePercent") return 70;
|
||||||
|
if (key == "crashLootMode") return 1;
|
||||||
|
if (key == "crashSiteCount") return 3;
|
||||||
|
if (key == "crashHelicopterChancePercent") return 50;
|
||||||
|
if (key == "crashRifleCount") return 3;
|
||||||
|
if (key == "crashMagazineCount") return 4;
|
||||||
|
if (key == "crashAntiAirChancePercent") return 25;
|
||||||
|
if (key == "crashRocketChancePercent") return 50;
|
||||||
if (key == "aiDoctrine" || key == "aiShadow") return 0;
|
if (key == "aiDoctrine" || key == "aiShadow") return 0;
|
||||||
|
if (key == "radioIntelSiteCount") return 5;
|
||||||
if (key == "sarkaAngerPerCar") return 3;
|
if (key == "sarkaAngerPerCar") return 3;
|
||||||
if (key == "motorbikeCount") return 2;
|
if (key == "motorbikeCount") return 2;
|
||||||
if (key == "droneReconCount") return 0;
|
if (key == "droneReconCount") return 0;
|
||||||
@@ -138,10 +166,16 @@ class ERR_Settings
|
|||||||
if (key == "objectGuardCount") return 8;
|
if (key == "objectGuardCount") return 8;
|
||||||
if (key == "hunterSpawnDistanceMetres") return 1000;
|
if (key == "hunterSpawnDistanceMetres") return 1000;
|
||||||
if (key == "roadblockCount") return 4;
|
if (key == "roadblockCount") return 4;
|
||||||
|
if (key == "largeRoadblockCount") return 4;
|
||||||
|
if (key == "mainRoadWidthMetres") return 6;
|
||||||
if (key == "civilianTrafficCount") return 3;
|
if (key == "civilianTrafficCount") return 3;
|
||||||
|
if (key == "garageChancePercent") return 70;
|
||||||
|
if (key == "garageRadiusMetres") return 1000;
|
||||||
|
if (key == "garageSpawnIntervalSeconds") return 5;
|
||||||
|
if (key == "garageVehicleCount") return 16;
|
||||||
if (key == "showAllSites") return 0;
|
if (key == "showAllSites") return 0;
|
||||||
if (key == "startMode" || key == "selfEpinephrine") return 0;
|
if (key == "startMode" || key == "selfEpinephrine") return 0;
|
||||||
if (key == "keepWeaponOnUnconscious" || key == "restartOnEmpty") return 1;
|
if (key == "telemetryEnabled" || key == "keepWeaponOnUnconscious" || key == "restartOnEmpty") return 1;
|
||||||
if (key == "emptyRestartHours") return 5;
|
if (key == "emptyRestartHours") return 5;
|
||||||
if (key == "heliTimerMinutes") return 15;
|
if (key == "heliTimerMinutes") return 15;
|
||||||
if (key == "convoyCount") return 3;
|
if (key == "convoyCount") return 3;
|
||||||
@@ -158,10 +192,27 @@ class ERR_Settings
|
|||||||
void ERR_Settings(string path = PATH)
|
void ERR_Settings(string path = PATH)
|
||||||
{
|
{
|
||||||
m_Path = path;
|
m_Path = path;
|
||||||
|
m_Values.Set("shoreBoatCount", AddedDefault("shoreBoatCount"));
|
||||||
|
m_Values.Set("shoreBoatChancePercent", AddedDefault("shoreBoatChancePercent"));
|
||||||
|
m_Values.Set("shoreBoatCrossingChancePercent", AddedDefault("shoreBoatCrossingChancePercent"));
|
||||||
|
m_Values.Set("crashLootMode", AddedDefault("crashLootMode"));
|
||||||
|
m_Values.Set("crashSiteCount", AddedDefault("crashSiteCount"));
|
||||||
|
m_Values.Set("crashHelicopterChancePercent", AddedDefault("crashHelicopterChancePercent"));
|
||||||
|
m_Values.Set("crashRifleCount", AddedDefault("crashRifleCount"));
|
||||||
|
m_Values.Set("crashMagazineCount", AddedDefault("crashMagazineCount"));
|
||||||
|
m_Values.Set("crashAntiAirChancePercent", AddedDefault("crashAntiAirChancePercent"));
|
||||||
|
m_Values.Set("crashRocketChancePercent", AddedDefault("crashRocketChancePercent"));
|
||||||
|
m_Values.Set("telemetryEnabled", AddedDefault("telemetryEnabled"));
|
||||||
m_Values.Set("aiDoctrine", AddedDefault("aiDoctrine"));
|
m_Values.Set("aiDoctrine", AddedDefault("aiDoctrine"));
|
||||||
m_Values.Set("aiShadow", AddedDefault("aiShadow"));
|
m_Values.Set("aiShadow", AddedDefault("aiShadow"));
|
||||||
m_Values.Set("sarkaAngerPerCar", AddedDefault("sarkaAngerPerCar"));
|
m_Values.Set("sarkaAngerPerCar", AddedDefault("sarkaAngerPerCar"));
|
||||||
|
m_Values.Set("garageChancePercent", AddedDefault("garageChancePercent"));
|
||||||
|
m_Values.Set("garageRadiusMetres", AddedDefault("garageRadiusMetres"));
|
||||||
|
m_Values.Set("garageSpawnIntervalSeconds", AddedDefault("garageSpawnIntervalSeconds"));
|
||||||
|
m_Values.Set("garageVehicleCount", AddedDefault("garageVehicleCount"));
|
||||||
m_Values.Set("motorbikeCount", AddedDefault("motorbikeCount"));
|
m_Values.Set("motorbikeCount", AddedDefault("motorbikeCount"));
|
||||||
|
m_Values.Set("droneReconCount", AddedDefault("droneReconCount"));
|
||||||
|
m_Values.Set("droneCooldownSeconds", AddedDefault("droneCooldownSeconds"));
|
||||||
m_Values.Set("planeAltitudeMetres", AddedDefault("planeAltitudeMetres"));
|
m_Values.Set("planeAltitudeMetres", AddedDefault("planeAltitudeMetres"));
|
||||||
m_Values.Set("planeReplacementSeconds", AddedDefault("planeReplacementSeconds"));
|
m_Values.Set("planeReplacementSeconds", AddedDefault("planeReplacementSeconds"));
|
||||||
m_Values.Set("planeSectorSeconds", AddedDefault("planeSectorSeconds"));
|
m_Values.Set("planeSectorSeconds", AddedDefault("planeSectorSeconds"));
|
||||||
@@ -175,6 +226,8 @@ class ERR_Settings
|
|||||||
m_Values.Set("objectGuardHQMode", AddedDefault("objectGuardHQMode"));
|
m_Values.Set("objectGuardHQMode", AddedDefault("objectGuardHQMode"));
|
||||||
m_Values.Set("hunterSpawnDistanceMetres", AddedDefault("hunterSpawnDistanceMetres"));
|
m_Values.Set("hunterSpawnDistanceMetres", AddedDefault("hunterSpawnDistanceMetres"));
|
||||||
m_Values.Set("roadblockCount", AddedDefault("roadblockCount"));
|
m_Values.Set("roadblockCount", AddedDefault("roadblockCount"));
|
||||||
|
m_Values.Set("largeRoadblockCount", AddedDefault("largeRoadblockCount"));
|
||||||
|
m_Values.Set("mainRoadWidthMetres", AddedDefault("mainRoadWidthMetres"));
|
||||||
m_Values.Set("civilianTrafficCount", AddedDefault("civilianTrafficCount"));
|
m_Values.Set("civilianTrafficCount", AddedDefault("civilianTrafficCount"));
|
||||||
m_Values.Set("startMode", AddedDefault("startMode"));
|
m_Values.Set("startMode", AddedDefault("startMode"));
|
||||||
m_Values.Set("keepWeaponOnUnconscious", AddedDefault("keepWeaponOnUnconscious"));
|
m_Values.Set("keepWeaponOnUnconscious", AddedDefault("keepWeaponOnUnconscious"));
|
||||||
@@ -186,6 +239,7 @@ class ERR_Settings
|
|||||||
m_Values.Set("responseRadiusMetres", 2000);
|
m_Values.Set("responseRadiusMetres", 2000);
|
||||||
m_Values.Set("aiIntelChancePercent", 60);
|
m_Values.Set("aiIntelChancePercent", 60);
|
||||||
m_Values.Set("intelRevealChancePercent", 15);
|
m_Values.Set("intelRevealChancePercent", 15);
|
||||||
|
m_Values.Set("radioIntelSiteCount", 5);
|
||||||
m_Values.Set("showAllSites", AddedDefault("showAllSites"));
|
m_Values.Set("showAllSites", AddedDefault("showAllSites"));
|
||||||
m_Values.Set("radioDefenceSeconds", 180);
|
m_Values.Set("radioDefenceSeconds", 180);
|
||||||
m_Values.Set("startTimeMinutes", AddedDefault("startTimeMinutes"));
|
m_Values.Set("startTimeMinutes", AddedDefault("startTimeMinutes"));
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
[BaseContainerProps()]
|
[BaseContainerProps()]
|
||||||
class ERR_SiteStock
|
class ERR_SiteStock
|
||||||
{
|
{
|
||||||
|
// Runtime starting-loadout choice; authored objective stock remains in storage.
|
||||||
|
bool OnGround;
|
||||||
[Attribute("", UIWidgets.ResourceNamePicker, "Physical inventory item", "et")]
|
[Attribute("", UIWidgets.ResourceNamePicker, "Physical inventory item", "et")]
|
||||||
ResourceName Prefab;
|
ResourceName Prefab;
|
||||||
[Attribute("1", UIWidgets.EditBox, "Finite initial count")]
|
[Attribute("1", UIWidgets.EditBox, "Finite initial count")]
|
||||||
@@ -59,7 +61,7 @@ class ERR_SiteDefinition
|
|||||||
ResourceName Prefab;
|
ResourceName Prefab;
|
||||||
[Attribute("0 0 0", UIWidgets.Coords)]
|
[Attribute("0 0 0", UIWidgets.Coords)]
|
||||||
vector Position;
|
vector Position;
|
||||||
[Attribute("0", UIWidgets.CheckBox, "Choose dry clear terrain once per round (prison, radio, mortar, hospital, depot or camp)")]
|
[Attribute("0", UIWidgets.CheckBox, "Choose dry clear terrain once per round (prison, extraction, radio, mortar, hospital, depot or camp)")]
|
||||||
bool RandomClear;
|
bool RandomClear;
|
||||||
[Attribute("0", UIWidgets.EditBox, "Composition heading in degrees")]
|
[Attribute("0", UIWidgets.EditBox, "Composition heading in degrees")]
|
||||||
float Heading;
|
float Heading;
|
||||||
@@ -183,7 +185,7 @@ class ERR_ScenarioDefinition
|
|||||||
if (!site || site.Id.IsEmpty() || ids.Contains(site.Id) || site.Prefab.IsEmpty()) return false;
|
if (!site || site.Id.IsEmpty() || ids.Contains(site.Id) || site.Prefab.IsEmpty()) return false;
|
||||||
if (site.RequireAirfield && site.Kind != ERR_LocationKind.EXTRACTION) return false;
|
if (site.RequireAirfield && site.Kind != ERR_LocationKind.EXTRACTION) return false;
|
||||||
if (site.Kind == ERR_LocationKind.RADIO && !site.Guarded && site.Guards.IsEmpty()) return false;
|
if (site.Kind == ERR_LocationKind.RADIO && !site.Guarded && site.Guards.IsEmpty()) return false;
|
||||||
if (site.RandomClear && site.Kind != ERR_LocationKind.PRISON && site.Kind != ERR_LocationKind.RADIO && site.Kind != ERR_LocationKind.MORTAR && site.Kind != ERR_LocationKind.HOSPITAL && site.Kind != ERR_LocationKind.OUTPOST && site.Kind != ERR_LocationKind.DEPOT) return false;
|
if (site.RandomClear && site.Kind != ERR_LocationKind.EXTRACTION && site.Kind != ERR_LocationKind.PRISON && site.Kind != ERR_LocationKind.RADIO && site.Kind != ERR_LocationKind.MORTAR && site.Kind != ERR_LocationKind.HOSPITAL && site.Kind != ERR_LocationKind.OUTPOST && site.Kind != ERR_LocationKind.DEPOT) return false;
|
||||||
if (site.Label.IsEmpty() || site.Label.Length() > 64) return false;
|
if (site.Label.IsEmpty() || site.Label.Length() > 64) return false;
|
||||||
if (!ERR_SiteDiscoveryRules.ValidRadius(site.Kind, site.DiscoverRadius)) return false;
|
if (!ERR_SiteDiscoveryRules.ValidRadius(site.Kind, site.DiscoverRadius)) return false;
|
||||||
bool optionalSite = site.Kind == ERR_LocationKind.DEPOT || site.Kind == ERR_LocationKind.OUTPOST || site.Kind == ERR_LocationKind.HOSPITAL;
|
bool optionalSite = site.Kind == ERR_LocationKind.DEPOT || site.Kind == ERR_LocationKind.OUTPOST || site.Kind == ERR_LocationKind.HOSPITAL;
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ class ERR_StockPlacement
|
|||||||
bool Accepted;
|
bool Accepted;
|
||||||
bool Optional;
|
bool Optional;
|
||||||
bool Skipped;
|
bool Skipped;
|
||||||
|
bool OnGround;
|
||||||
}
|
}
|
||||||
|
|
||||||
class ERR_SiteRuntime
|
class ERR_SiteRuntime
|
||||||
@@ -389,7 +390,7 @@ class ERR_SiteService
|
|||||||
if (only && spec != only) continue;
|
if (only && spec != only) continue;
|
||||||
if (startupOnly && !ERR_GeneratedSites.StartupSite(spec)) continue;
|
if (startupOnly && !ERR_GeneratedSites.StartupSite(spec)) continue;
|
||||||
vector position = ERR_Actors.Ground(spec.Position);
|
vector position = ERR_Actors.Ground(spec.Position);
|
||||||
if (position[1] < 1) return Fail("site below sea level: " + spec.Id);
|
if (!ERR_Actors.DryGround(position)) return Fail("site ground is submerged or invalid: " + spec.Id);
|
||||||
vector rootPosition = position;
|
vector rootPosition = position;
|
||||||
if (spec.PlacementResolved && spec.Kind == ERR_LocationKind.PRISON) rootPosition = spec.Position;
|
if (spec.PlacementResolved && spec.Kind == ERR_LocationKind.PRISON) rootPosition = spec.Position;
|
||||||
rootPosition[1] = rootPosition[1] + spec.RootHeight;
|
rootPosition[1] = rootPosition[1] + spec.RootHeight;
|
||||||
@@ -397,7 +398,7 @@ class ERR_SiteService
|
|||||||
{
|
{
|
||||||
rootPosition = ERR_Actors.Ground(position + ERR_CampTerrain.Rotate(spec.AircraftOffset, spec.Heading));
|
rootPosition = ERR_Actors.Ground(position + ERR_CampTerrain.Rotate(spec.AircraftOffset, spec.Heading));
|
||||||
if (spec.RequireAirfield && !ERR_AirfieldPlacement.Clear(rootPosition)) return Fail("extraction airfield pad is missing or obstructed; no off-airfield fallback");
|
if (spec.RequireAirfield && !ERR_AirfieldPlacement.Clear(rootPosition)) return Fail("extraction airfield pad is missing or obstructed; no off-airfield fallback");
|
||||||
if (definition.TerrainGenerated && !spec.RequireAirfield && !ERR_AirfieldPlacement.ClearGround(rootPosition)) return Fail("generated extraction landing zone is obstructed");
|
if ((definition.TerrainGenerated || spec.RandomClear) && !spec.RequireAirfield && !ERR_AirfieldPlacement.ClearGround(rootPosition)) return Fail("generated extraction landing zone is obstructed");
|
||||||
}
|
}
|
||||||
// Spawn the replicated marker at the final pickup coordinate from the start.
|
// Spawn the replicated marker at the final pickup coordinate from the start.
|
||||||
IEntity root = ERR_Actors.Spawn(spec.Prefab, rootPosition, Vector(spec.Heading, 0, 0));
|
IEntity root = ERR_Actors.Spawn(spec.Prefab, rootPosition, Vector(spec.Heading, 0, 0));
|
||||||
@@ -473,6 +474,7 @@ class ERR_SiteService
|
|||||||
&& site.DepotMines.Count() < 2) site.DepotMines.Insert(item);
|
&& site.DepotMines.Count() < 2) site.DepotMines.Insert(item);
|
||||||
}
|
}
|
||||||
placement.Optional = customStart && stock.Prefab != ERR_Supplies.Map();
|
placement.Optional = customStart && stock.Prefab != ERR_Supplies.Map();
|
||||||
|
placement.OnGround = customStart && stock.OnGround;
|
||||||
placement.Item = item;
|
placement.Item = item;
|
||||||
placement.Inventory = inventory;
|
placement.Inventory = inventory;
|
||||||
m_Placements.Insert(placement);
|
m_Placements.Insert(placement);
|
||||||
@@ -628,6 +630,75 @@ class ERR_SiteService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Optional starting items stay usable when native inventory rejects their
|
||||||
|
// dimensions/volume/type. Keep the exact entity; do not spawn copies or delete it.
|
||||||
|
protected void LeaveStartingItem(ERR_StockPlacement placement)
|
||||||
|
{
|
||||||
|
InventoryItemComponent item = InventoryItemComponent.Cast(placement.Item.FindComponent(InventoryItemComponent));
|
||||||
|
// An accepted asynchronous move or an administrator may already own the item.
|
||||||
|
// Never pull it out of another storage while resolving starter overflow.
|
||||||
|
if (!item || !item.GetParentSlot())
|
||||||
|
{
|
||||||
|
vector origin = placement.Inventory.GetOwner().GetOrigin();
|
||||||
|
array<vector> offsets = {"1.2 0 0", "0 0 1.2", "-1.2 0 0", "0 0 -1.2", "0.9 0 0.9", "-0.9 0 0.9", "-0.9 0 -0.9", "0.9 0 -0.9"};
|
||||||
|
foreach (vector offset : offsets)
|
||||||
|
{
|
||||||
|
vector point = ERR_Actors.Ground(origin + offset);
|
||||||
|
if (!ERR_Actors.DryGround(point) || Math.AbsFloat(point[1] - origin[1]) > 0.5 || !BackpackClearance(point)) continue;
|
||||||
|
// Do not place supplies across a cell wall or inside the crash wreckage.
|
||||||
|
TraceParam access = new TraceParam(); access.Start = origin + "0 0.6 0"; access.End = point + "0 0.6 0";
|
||||||
|
access.Flags = TraceFlags.ENTS | TraceFlags.WORLD; access.Exclude = placement.Inventory.GetOwner();
|
||||||
|
if (GetGame().GetWorld().TraceMove(access, null) < 1) continue;
|
||||||
|
placement.Item.SetOrigin(point + "0 0.2 0");
|
||||||
|
Physics physics = placement.Item.GetPhysics();
|
||||||
|
if (physics && physics.IsDynamic()) physics.SetActive(ActiveState.ACTIVE);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
string resource = "unknown item";
|
||||||
|
if (placement.Item.GetPrefabData()) resource = placement.Item.GetPrefabData().GetPrefabName();
|
||||||
|
if (!placement.OnGround) Print("Reintroduced [starting backpack]: inventory rejected item; retained as starting-area loot: " + resource, LogLevel.WARNING);
|
||||||
|
placement.Skipped = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ERR_PrepareResult PrepareStock()
|
||||||
|
{
|
||||||
|
int pending, accepted;
|
||||||
|
string firstRejected;
|
||||||
|
foreach (ERR_StockPlacement placement : m_Placements)
|
||||||
|
{
|
||||||
|
if (placement.Skipped) continue;
|
||||||
|
if (!placement.Item || !placement.Inventory)
|
||||||
|
{
|
||||||
|
if (placement.Optional) { placement.Skipped = true; Print("Reintroduced [starting backpack]: optional item disappeared before insertion", LogLevel.WARNING); continue; }
|
||||||
|
Fail("stock disappeared during initialization"); return ERR_PrepareResult.FAILED;
|
||||||
|
}
|
||||||
|
if (placement.OnGround) { LeaveStartingItem(placement); continue; }
|
||||||
|
array<IEntity> items = {};
|
||||||
|
placement.Inventory.GetItems(items);
|
||||||
|
if (items.Contains(placement.Item)) continue;
|
||||||
|
bool expired = GetGame().GetWorld().GetWorldTime() > m_Deadline;
|
||||||
|
if (placement.Optional && (expired || (!placement.Accepted && !placement.Inventory.CanInsertItem(placement.Item))))
|
||||||
|
{
|
||||||
|
LeaveStartingItem(placement); continue;
|
||||||
|
}
|
||||||
|
pending++;
|
||||||
|
if (firstRejected.IsEmpty() && placement.Item.GetPrefabData()) firstRejected = placement.Item.GetPrefabData().GetPrefabName();
|
||||||
|
if (placement.Accepted) accepted++;
|
||||||
|
if (!placement.Accepted) placement.Accepted = placement.Inventory.TryInsertItem(placement.Item);
|
||||||
|
}
|
||||||
|
if (pending)
|
||||||
|
{
|
||||||
|
if (GetGame().GetWorld().GetWorldTime() > m_Deadline)
|
||||||
|
{
|
||||||
|
Fail(string.Format("physical stock insertion timed out: pending=%1 accepted=%2 sample=%3", pending, accepted, firstRejected));
|
||||||
|
return ERR_PrepareResult.FAILED;
|
||||||
|
}
|
||||||
|
return ERR_PrepareResult.PENDING;
|
||||||
|
}
|
||||||
|
return ERR_PrepareResult.READY;
|
||||||
|
}
|
||||||
|
|
||||||
ERR_PrepareResult Prepare()
|
ERR_PrepareResult Prepare()
|
||||||
{
|
{
|
||||||
if (!Replication.IsServer() || !m_Begun || m_Stopped) return ERR_PrepareResult.FAILED;
|
if (!Replication.IsServer() || !m_Begun || m_Stopped) return ERR_PrepareResult.FAILED;
|
||||||
@@ -636,7 +707,7 @@ class ERR_SiteService
|
|||||||
ERR_PrepareResult aircraftPlacement = ERR_PrepareResult.READY;
|
ERR_PrepareResult aircraftPlacement = ERR_PrepareResult.READY;
|
||||||
if (m_ExtractionPlacement) aircraftPlacement = m_ExtractionPlacement.Poll(GetGame().GetWorld().GetWorldTime());
|
if (m_ExtractionPlacement) aircraftPlacement = m_ExtractionPlacement.Poll(GetGame().GetWorld().GetWorldTime());
|
||||||
if (aircraftPlacement == ERR_PrepareResult.FAILED) { Fail(m_ExtractionPlacement.FailureReason()); return m_State; }
|
if (aircraftPlacement == ERR_PrepareResult.FAILED) { Fail(m_ExtractionPlacement.FailureReason()); return m_State; }
|
||||||
int pending, accepted, pendingGuards;
|
int pendingGuards;
|
||||||
foreach (ERR_SiteRuntime site : m_Sites)
|
foreach (ERR_SiteRuntime site : m_Sites)
|
||||||
{
|
{
|
||||||
if (site.Definition.Transport && !site.Definition.Transport.Optional && !TransportReady(site))
|
if (site.Definition.Transport && !site.Definition.Transport.Optional && !TransportReady(site))
|
||||||
@@ -654,29 +725,7 @@ class ERR_SiteService
|
|||||||
if (mobile == ERR_PrepareResult.PENDING) pendingGuards++;
|
if (mobile == ERR_PrepareResult.PENDING) pendingGuards++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
string firstRejected;
|
if (PrepareStock() != ERR_PrepareResult.READY) return m_State;
|
||||||
foreach (ERR_StockPlacement placement : m_Placements)
|
|
||||||
{
|
|
||||||
if (placement.Skipped) continue;
|
|
||||||
if (!placement.Item || !placement.Inventory) { Fail("stock disappeared during initialization"); return m_State; }
|
|
||||||
array<IEntity> items = {};
|
|
||||||
placement.Inventory.GetItems(items);
|
|
||||||
if (items.Contains(placement.Item)) continue;
|
|
||||||
if (placement.Optional && GetGame().GetWorld().GetWorldTime() > m_Deadline)
|
|
||||||
{
|
|
||||||
Print("Reintroduced [starting backpack]: item did not fit; omitted " + placement.Item.GetPrefabData().GetPrefabName(), LogLevel.WARNING);
|
|
||||||
ERR_Actors.DeleteSpawned(placement.Item); placement.Skipped = true; continue;
|
|
||||||
}
|
|
||||||
pending++;
|
|
||||||
if (firstRejected.IsEmpty() && placement.Item.GetPrefabData()) firstRejected = placement.Item.GetPrefabData().GetPrefabName();
|
|
||||||
if (placement.Accepted) accepted++;
|
|
||||||
if (!placement.Accepted) placement.Accepted = placement.Inventory.TryInsertItem(placement.Item);
|
|
||||||
}
|
|
||||||
if (pending)
|
|
||||||
{
|
|
||||||
if (GetGame().GetWorld().GetWorldTime() > m_Deadline) Fail(string.Format("physical stock insertion timed out: pending=%1 accepted=%2 sample=%3", pending, accepted, firstRejected));
|
|
||||||
return m_State;
|
|
||||||
}
|
|
||||||
if (pendingGuards > 0 || aircraftPlacement == ERR_PrepareResult.PENDING) return m_State;
|
if (pendingGuards > 0 || aircraftPlacement == ERR_PrepareResult.PENDING) return m_State;
|
||||||
// All required scenery, stock and physical guards now exist. Survey optional
|
// All required scenery, stock and physical guards now exist. Survey optional
|
||||||
// parking once against that real population, then bound only vehicle settling.
|
// parking once against that real population, then bound only vehicle settling.
|
||||||
|
|||||||
@@ -29,13 +29,13 @@ class ERR_SpawnPlacement
|
|||||||
foreach (int id, vector reserved : reservations)
|
foreach (int id, vector reserved : reservations)
|
||||||
if (id != ownReservation && vector.DistanceXZ(position, reserved) < 1.2 && Math.AbsFloat(position[1] - reserved[1]) < 2) return false;
|
if (id != ownReservation && vector.DistanceXZ(position, reserved) < 1.2 && Math.AbsFloat(position[1] - reserved[1]) < 2) return false;
|
||||||
vector ground = ERR_Actors.Ground(position);
|
vector ground = ERR_Actors.Ground(position);
|
||||||
if (ground[1] < 1 || Math.AbsFloat(position[1] - ground[1]) > 0.3) return false;
|
if (!ERR_Actors.DryGround(ground) || Math.AbsFloat(position[1] - ground[1]) > 0.3) return false;
|
||||||
// Feet need supported, reasonably level terrain across the character width.
|
// Feet need supported, reasonably level terrain across the character width.
|
||||||
for (int x = -1; x <= 1; x++)
|
for (int x = -1; x <= 1; x++)
|
||||||
for (int z = -1; z <= 1; z++)
|
for (int z = -1; z <= 1; z++)
|
||||||
{
|
{
|
||||||
vector sample = ERR_Actors.Ground(position + Vector(x * 0.45, 0, z * 0.45));
|
vector sample = ERR_Actors.Ground(position + Vector(x * 0.45, 0, z * 0.45));
|
||||||
if (Math.AbsFloat(sample[1] - ground[1]) > 0.3) return false;
|
if (!ERR_Actors.DryGround(sample) || Math.AbsFloat(sample[1] - ground[1]) > 0.3) return false;
|
||||||
}
|
}
|
||||||
m_Exclude = exclude;
|
m_Exclude = exclude;
|
||||||
m_Candidate = ground; m_Blocked = false;
|
m_Candidate = ground; m_Blocked = false;
|
||||||
|
|||||||
@@ -11,6 +11,11 @@ class ERR_StartBackpack
|
|||||||
if (Read(PATH, loaded) || Read(PATH + ".bak", loaded)) m_Records = loaded;
|
if (Read(PATH, loaded) || Read(PATH + ".bak", loaded)) m_Records = loaded;
|
||||||
else if (ERR_ProfileConfig.Exists(PATH) || ERR_ProfileConfig.Exists(PATH + ".bak")) m_ReadOnly = true;
|
else if (ERR_ProfileConfig.Exists(PATH) || ERR_ProfileConfig.Exists(PATH + ".bak")) m_ReadOnly = true;
|
||||||
}
|
}
|
||||||
|
static string Destination(array<string> parts)
|
||||||
|
{
|
||||||
|
if (parts.Count() == 3) return parts[2];
|
||||||
|
return "backpack";
|
||||||
|
}
|
||||||
static bool Valid(array<string> records)
|
static bool Valid(array<string> records)
|
||||||
{
|
{
|
||||||
if (!records || records.IsEmpty() || records.Count() > 33) return false;
|
if (!records || records.IsEmpty() || records.Count() > 33) return false;
|
||||||
@@ -19,19 +24,21 @@ class ERR_StartBackpack
|
|||||||
for (int i = 1; i < records.Count(); i++)
|
for (int i = 1; i < records.Count(); i++)
|
||||||
{
|
{
|
||||||
array<string> parts = {}; records[i].Split("|", parts, false);
|
array<string> parts = {}; records[i].Split("|", parts, false);
|
||||||
if (parts.Count() != 2 || parts[0].Length() > 512 || !parts[0].EndsWith(".et")) return false;
|
if ((parts.Count() != 2 && parts.Count() != 3) || parts[0].Length() > 512 || !parts[0].EndsWith(".et")) return false;
|
||||||
string key = ERR_TerrainCatalog.Key(parts[0]); int count;
|
string key = ERR_TerrainCatalog.Key(parts[0]); int count;
|
||||||
if (!ERR_ItemPreferences.Guid(key) || seen.Contains(key)) return false;
|
string destination = Destination(parts);
|
||||||
|
if (destination != "backpack" && destination != "ground") return false;
|
||||||
|
if (!ERR_ItemPreferences.Guid(key) || seen.Contains(key + "|" + destination)) return false;
|
||||||
if (!ERR_CommandArguments.Integer(parts[1], count) || count < 1 || count > 48) return false;
|
if (!ERR_CommandArguments.Integer(parts[1], count) || count < 1 || count > 48) return false;
|
||||||
total += count; if (total > 64) return false;
|
total += count; if (total > 64) return false;
|
||||||
seen.Insert(key);
|
seen.Insert(key + "|" + destination);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
static bool Parse(JsonLoadContext json, out array<string> records)
|
static bool Parse(JsonLoadContext json, out array<string> records)
|
||||||
{
|
{
|
||||||
int version; records = {};
|
int version; records = {};
|
||||||
return json.ReadValue("version", version) && version == 1 && json.ReadValue("records", records) && Valid(records);
|
return json.ReadValue("version", version) && (version == 1 || version == 2) && json.ReadValue("records", records) && Valid(records);
|
||||||
}
|
}
|
||||||
protected bool Read(string path, out array<string> records)
|
protected bool Read(string path, out array<string> records)
|
||||||
{
|
{
|
||||||
@@ -42,7 +49,7 @@ class ERR_StartBackpack
|
|||||||
{
|
{
|
||||||
if (!Replication.IsServer() || m_ReadOnly || !Valid(records)) return false;
|
if (!Replication.IsServer() || m_ReadOnly || !Valid(records)) return false;
|
||||||
JsonSaveContext json = new JsonSaveContext(); json.EnableTypeDiscriminator(false);
|
JsonSaveContext json = new JsonSaveContext(); json.EnableTypeDiscriminator(false);
|
||||||
json.WriteValue("version", 1); json.WriteValue("records", records);
|
json.WriteValue("version", 2); json.WriteValue("records", records);
|
||||||
if (!ERR_ProfileConfig.Save(PATH, json)) return false;
|
if (!ERR_ProfileConfig.Save(PATH, json)) return false;
|
||||||
m_Records = records; return true;
|
m_Records = records; return true;
|
||||||
}
|
}
|
||||||
@@ -53,26 +60,26 @@ class ERR_StartBackpack
|
|||||||
next[0] = "default"; if (custom) next[0] = "custom";
|
next[0] = "default"; if (custom) next[0] = "custom";
|
||||||
return Save(next);
|
return Save(next);
|
||||||
}
|
}
|
||||||
bool Set(ResourceName prefab, int quantity)
|
bool Set(ResourceName prefab, int quantity, string destination = "backpack")
|
||||||
{
|
{
|
||||||
if (quantity < 0 || quantity > 48) return false;
|
if (quantity < 0 || quantity > 48 || (destination != "backpack" && destination != "ground")) return false;
|
||||||
string key = ERR_TerrainCatalog.Key(prefab);
|
string key = ERR_TerrainCatalog.Key(prefab);
|
||||||
if (!ERR_ItemPreferences.Guid(key)) return false;
|
if (!ERR_ItemPreferences.Guid(key)) return false;
|
||||||
array<string> next = {m_Records[0]};
|
array<string> next = {m_Records[0]};
|
||||||
for (int i = 1; i < m_Records.Count(); i++)
|
for (int i = 1; i < m_Records.Count(); i++)
|
||||||
{
|
{
|
||||||
array<string> parts = {}; m_Records[i].Split("|", parts, false);
|
array<string> parts = {}; m_Records[i].Split("|", parts, false);
|
||||||
if (ERR_TerrainCatalog.Key(parts[0]) != key) next.Insert(m_Records[i]);
|
if (ERR_TerrainCatalog.Key(parts[0]) != key || Destination(parts) != destination) next.Insert(m_Records[i]);
|
||||||
}
|
}
|
||||||
if (quantity > 0) next.Insert(prefab + "|" + quantity.ToString());
|
if (quantity > 0) next.Insert(prefab + "|" + quantity.ToString() + "|" + destination);
|
||||||
return Save(next);
|
return Save(next);
|
||||||
}
|
}
|
||||||
bool Remove(string key)
|
bool Remove(string key, string destination = "backpack")
|
||||||
{
|
{
|
||||||
for (int i = 1; i < m_Records.Count(); i++)
|
for (int i = 1; i < m_Records.Count(); i++)
|
||||||
{
|
{
|
||||||
array<string> parts = {}; m_Records[i].Split("|", parts, false);
|
array<string> parts = {}; m_Records[i].Split("|", parts, false);
|
||||||
if (ERR_TerrainCatalog.Key(parts[0]) == key) return Set(parts[0], 0);
|
if (ERR_TerrainCatalog.Key(parts[0]) == key && Destination(parts) == destination) return Set(parts[0], 0, destination);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -81,13 +88,13 @@ class ERR_StartBackpack
|
|||||||
for (int i = 1; i < m_Records.Count(); i++)
|
for (int i = 1; i < m_Records.Count(); i++)
|
||||||
{
|
{
|
||||||
array<string> parts = {}; m_Records[i].Split("|", parts, false);
|
array<string> parts = {}; m_Records[i].Split("|", parts, false);
|
||||||
ERR_SiteStock item = new ERR_SiteStock(); item.Prefab = parts[0]; item.Count = parts[1].ToInt(); stock.Insert(item);
|
ERR_SiteStock item = new ERR_SiteStock(); item.Prefab = parts[0]; item.Count = parts[1].ToInt(); item.OnGround = Destination(parts) == "ground"; stock.Insert(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void Snapshot(array<string> values)
|
void Snapshot(array<string> values)
|
||||||
{
|
{
|
||||||
values.Insert("starterMode=" + m_Records[0]);
|
values.Insert("starterMode=" + m_Records[0]);
|
||||||
string state = "Saved for next round. The shared field map is always included. Add matching magazines separately.";
|
string state = "Saved for next round. The shared field map is always included. Oversized items are left nearby. Add matching magazines separately.";
|
||||||
if (m_ReadOnly) state = "Read-only: starting backpack configuration needs repair.";
|
if (m_ReadOnly) state = "Read-only: starting backpack configuration needs repair.";
|
||||||
values.Insert("starterState=" + state);
|
values.Insert("starterState=" + state);
|
||||||
for (int i = 1; i < m_Records.Count(); i++)
|
for (int i = 1; i < m_Records.Count(); i++)
|
||||||
@@ -96,7 +103,7 @@ class ERR_StartBackpack
|
|||||||
string key = ERR_TerrainCatalog.Key(parts[0]);
|
string key = ERR_TerrainCatalog.Key(parts[0]);
|
||||||
ERR_ServerItem item = GetGame().ERR_ServerItems().Item(key);
|
ERR_ServerItem item = GetGame().ERR_ServerItems().Item(key);
|
||||||
string label = FilePath.StripPath(parts[0]); if (item) label = item.Label;
|
string label = FilePath.StripPath(parts[0]); if (item) label = item.Label;
|
||||||
values.Insert("starterItem=" + key + "|" + ERR_ServerItemCatalog.Clean(label) + "|" + parts[1]);
|
values.Insert("starterItem=" + key + "|" + ERR_ServerItemCatalog.Clean(label) + "|" + parts[1] + "|" + Destination(parts));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,9 @@ modded class ERR_AdminMenu
|
|||||||
button.m_OnClicked.Insert(OnStarterButton); m_StarterButtons.Insert(button);
|
button.m_OnClicked.Insert(OnStarterButton); m_StarterButtons.Insert(button);
|
||||||
}
|
}
|
||||||
SCR_EditBoxComponent.GetEditBoxComponent("StarterQuantity", GetRootWidget()).SetValue("1");
|
SCR_EditBoxComponent.GetEditBoxComponent("StarterQuantity", GetRootWidget()).SetValue("1");
|
||||||
|
SCR_ComboBoxComponent destination = SCR_ComboBoxComponent.GetComboBoxComponent("StarterDestination", GetRootWidget());
|
||||||
|
destination.ClearAll(); destination.AddItem("Inside backpack"); destination.AddItem("On ground beside backpack");
|
||||||
|
destination.SetCurrentItem(0, false, false, false);
|
||||||
UpdateActions();
|
UpdateActions();
|
||||||
}
|
}
|
||||||
override void OnMenuClose()
|
override void OnMenuClose()
|
||||||
@@ -69,8 +72,13 @@ modded class ERR_AdminMenu
|
|||||||
if (field == "starterState") SetText("StarterState", value);
|
if (field == "starterState") SetText("StarterState", value);
|
||||||
if (field == "serverItem" && parts.Count() == 6)
|
if (field == "serverItem" && parts.Count() == 6)
|
||||||
{ catalogKeys.Insert(parts[0]); catalogLabels.Insert(parts[1]); catalogSignature += value; }
|
{ catalogKeys.Insert(parts[0]); catalogLabels.Insert(parts[1]); catalogSignature += value; }
|
||||||
if (field == "starterItem" && parts.Count() == 3)
|
if (field == "starterItem" && parts.Count() == 4)
|
||||||
{ savedKeys.Insert(parts[0]); savedLabels.Insert(parts[2] + " x " + WidgetManager.Translate(parts[1])); savedSignature += value; total += parts[2].ToInt(); }
|
{
|
||||||
|
string location = "Backpack"; if (parts[3] == "ground") location = "Ground";
|
||||||
|
savedKeys.Insert(parts[0] + " " + parts[3]);
|
||||||
|
savedLabels.Insert(location + " | " + parts[2] + " x " + WidgetManager.Translate(parts[1]));
|
||||||
|
savedSignature += value; total += parts[2].ToInt();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (catalogSignature != m_StarterCatalogSignature)
|
if (catalogSignature != m_StarterCatalogSignature)
|
||||||
{
|
{
|
||||||
@@ -82,7 +90,7 @@ modded class ERR_AdminMenu
|
|||||||
if (savedSignature != m_StarterSavedSignature)
|
if (savedSignature != m_StarterSavedSignature)
|
||||||
{ StarterCombo("StarterSaved", savedKeys, savedLabels, m_StarterSaved); m_StarterSavedSignature = savedSignature; }
|
{ StarterCombo("StarterSaved", savedKeys, savedLabels, m_StarterSaved); m_StarterSavedSignature = savedSignature; }
|
||||||
string mode = "Default loadout selected"; if (m_StarterMode == 1) mode = "Custom loadout selected";
|
string mode = "Default loadout selected"; if (m_StarterMode == 1) mode = "Custom loadout selected";
|
||||||
SetText("StarterSummary", mode + " | Custom: " + savedKeys.Count().ToString() + "/32 types, " + total.ToString() + "/64 items + field map");
|
SetText("StarterSummary", mode + " | Custom: " + savedKeys.Count().ToString() + "/32 entries, " + total.ToString() + "/64 items + field map");
|
||||||
string page = "Choose a mod, then Show items. Quantity 0 removes an item.";
|
string page = "Choose a mod, then Show items. Quantity 0 removes an item.";
|
||||||
if (!m_StarterAddon.IsEmpty()) page = "Catalog page " + (m_ShownItemPage + 1).ToString() + "/" + (m_LastItemPage + 1).ToString() + " | Select an item above; enter its total desired quantity (0 removes).";
|
if (!m_StarterAddon.IsEmpty()) page = "Catalog page " + (m_ShownItemPage + 1).ToString() + "/" + (m_LastItemPage + 1).ToString() + " | Select an item above; enter its total desired quantity (0 removes).";
|
||||||
SetText("StarterCatalogState", page); UpdateActions();
|
SetText("StarterCatalogState", page); UpdateActions();
|
||||||
@@ -104,7 +112,9 @@ modded class ERR_AdminMenu
|
|||||||
int selected = SCR_ComboBoxComponent.GetComboBoxComponent("StarterCatalogItem", GetRootWidget()).GetCurrentIndex();
|
int selected = SCR_ComboBoxComponent.GetComboBoxComponent("StarterCatalogItem", GetRootWidget()).GetCurrentIndex();
|
||||||
int count; string quantity = SCR_EditBoxComponent.GetEditBoxComponent("StarterQuantity", GetRootWidget()).GetValue().Trim();
|
int count; string quantity = SCR_EditBoxComponent.GetEditBoxComponent("StarterQuantity", GetRootWidget()).GetValue().Trim();
|
||||||
if (!ERR_CommandArguments.Integer(quantity, count) || count < 0 || count > 48) { ReceiveReply("Enter a quantity from 0 to 48; 0 removes the item."); return; }
|
if (!ERR_CommandArguments.Integer(quantity, count) || count < 0 || count > 48) { ReceiveReply("Enter a quantity from 0 to 48; 0 removes the item."); return; }
|
||||||
if (selected >= 0 && selected < m_StarterCatalog.Count() && !m_StarterAddon.IsEmpty()) SubmitStarter("starter set " + m_StarterAddon + " " + m_StarterCatalog[selected] + " " + count.ToString());
|
string destination = "backpack";
|
||||||
|
if (SCR_ComboBoxComponent.GetComboBoxComponent("StarterDestination", GetRootWidget()).GetCurrentIndex() == 1) destination = "ground";
|
||||||
|
if (selected >= 0 && selected < m_StarterCatalog.Count() && !m_StarterAddon.IsEmpty()) SubmitStarter("starter set " + m_StarterAddon + " " + m_StarterCatalog[selected] + " " + count.ToString() + " " + destination);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
int index = SCR_ComboBoxComponent.GetComboBoxComponent("StarterMod", GetRootWidget()).GetCurrentIndex();
|
int index = SCR_ComboBoxComponent.GetComboBoxComponent("StarterMod", GetRootWidget()).GetCurrentIndex();
|
||||||
|
|||||||
@@ -0,0 +1,493 @@
|
|||||||
|
// The mission only produces facts. Aggregation and public leaderboards belong to
|
||||||
|
// the receiver; retries retain event IDs so they cannot count a result twice.
|
||||||
|
[BaseContainerProps()]
|
||||||
|
class ERR_TelemetryConfig
|
||||||
|
{
|
||||||
|
[Attribute("")] string Endpoint;
|
||||||
|
static const string RESOURCE = "{E72B130684FA492C}Configs/ERR/Telemetry.conf";
|
||||||
|
static const string PATH = "$profile:EscapistsTelemetry.json";
|
||||||
|
string Token;
|
||||||
|
string ServerName;
|
||||||
|
static ERR_TelemetryConfig Load()
|
||||||
|
{
|
||||||
|
ERR_TelemetryConfig config = ERR_TelemetryConfig.Cast(SCR_BaseContainerTools.CreateInstanceFromPrefab(RESOURCE));
|
||||||
|
if (!config) config = new ERR_TelemetryConfig();
|
||||||
|
if (FileIO.FileExists(PATH))
|
||||||
|
{
|
||||||
|
JsonLoadContext json = new JsonLoadContext();
|
||||||
|
if (!json.LoadFromFile(PATH)) { config.Endpoint = ""; return config; }
|
||||||
|
json.ReadValue("endpoint", config.Endpoint);
|
||||||
|
json.ReadValue("token", config.Token);
|
||||||
|
json.ReadValue("serverName", config.ServerName);
|
||||||
|
}
|
||||||
|
config.Endpoint.TrimInPlace();
|
||||||
|
if (!config.Endpoint.StartsWith("https://") || !ERR_SpeechRules.Endpoint(config.Endpoint)) config.Endpoint = "";
|
||||||
|
if (config.Token.Length() > 256 || config.ServerName.Length() > 120) config.Endpoint = "";
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ERR_TelemetryPlayer
|
||||||
|
{
|
||||||
|
string id;
|
||||||
|
string name;
|
||||||
|
bool authenticated;
|
||||||
|
bool connected;
|
||||||
|
bool participated;
|
||||||
|
bool escaped;
|
||||||
|
int connectedSeconds;
|
||||||
|
int kills;
|
||||||
|
int friendlyKills;
|
||||||
|
int deaths;
|
||||||
|
int downed;
|
||||||
|
int recoveries;
|
||||||
|
}
|
||||||
|
class ERR_TelemetryMod
|
||||||
|
{
|
||||||
|
string id;
|
||||||
|
string name;
|
||||||
|
string version;
|
||||||
|
}
|
||||||
|
class ERR_TelemetryState
|
||||||
|
{
|
||||||
|
int schemaVersion = 1;
|
||||||
|
string serverId;
|
||||||
|
string endpoint;
|
||||||
|
string previousMissionId;
|
||||||
|
string previousOutcome;
|
||||||
|
// Unsent, prebuilt failure for the last occupied snapshot. Only promoted to
|
||||||
|
// the queue if another instance starts before this mission records its end.
|
||||||
|
string restartFailure;
|
||||||
|
int droppedEvents;
|
||||||
|
ref array<string> pending = {};
|
||||||
|
}
|
||||||
|
class ERR_TelemetryRules
|
||||||
|
{
|
||||||
|
static bool Acknowledged(int status) { return status >= 200 && status < 300; }
|
||||||
|
static int RetrySeconds(int failures)
|
||||||
|
{
|
||||||
|
return Math.Min(300, 5 * Math.Pow(2, Math.Min(failures, 6)));
|
||||||
|
}
|
||||||
|
static string Outcome(int phase, bool emptyRestart, bool preparationFailed)
|
||||||
|
{
|
||||||
|
if (preparationFailed) return "preparation_failed";
|
||||||
|
if (emptyRestart) return "restarted_empty";
|
||||||
|
if (phase == ERR_Phase.VICTORY) return "victory";
|
||||||
|
if (phase == ERR_Phase.DEFEAT) return "defeat";
|
||||||
|
return "interrupted";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ERR_Telemetry
|
||||||
|
{
|
||||||
|
static const string STATE = "$profile:EscapistsTelemetryState.json";
|
||||||
|
protected ERR_GameMode m_Mode;
|
||||||
|
protected ref ERR_TelemetryConfig m_Config;
|
||||||
|
protected ref ERR_TelemetryState m_State;
|
||||||
|
protected ref array<ref ERR_TelemetryPlayer> m_Players = {};
|
||||||
|
protected ref array<ref ERR_TelemetryMod> m_Mods = {};
|
||||||
|
protected ref RestCallback m_Callback;
|
||||||
|
protected RestContext m_Context;
|
||||||
|
protected string m_MissionId;
|
||||||
|
protected string m_InFlight;
|
||||||
|
protected string m_FinalEventId;
|
||||||
|
protected string m_FinalOutcome;
|
||||||
|
protected string m_FinalTerrain;
|
||||||
|
protected bool m_CommunityReady;
|
||||||
|
protected int m_Started;
|
||||||
|
protected int m_LastSample;
|
||||||
|
protected int m_NextHeartbeat;
|
||||||
|
protected int m_NextAttempt;
|
||||||
|
protected int m_Peak;
|
||||||
|
protected int m_Failures;
|
||||||
|
protected int m_RequestStarted;
|
||||||
|
protected int m_Delivered;
|
||||||
|
protected bool m_Collected;
|
||||||
|
protected bool m_Partial;
|
||||||
|
protected bool m_Enabled;
|
||||||
|
protected bool m_Ended;
|
||||||
|
protected bool m_Closed;
|
||||||
|
protected bool m_Dirty;
|
||||||
|
protected bool m_TruncatedPlayers;
|
||||||
|
protected bool m_TruncatedMods;
|
||||||
|
protected bool m_CredentialBlocked;
|
||||||
|
|
||||||
|
void ERR_Telemetry(ERR_GameMode mode)
|
||||||
|
{
|
||||||
|
m_Mode = mode; m_Config = ERR_TelemetryConfig.Load();
|
||||||
|
m_State = new ERR_TelemetryState();
|
||||||
|
// A damaged primary can recover from the last fully written generation.
|
||||||
|
if (!LoadState(STATE) && !LoadState(STATE + ".bak"))
|
||||||
|
{
|
||||||
|
if (FileIO.FileExists(STATE) || FileIO.FileExists(STATE + ".bak"))
|
||||||
|
{
|
||||||
|
Print("Reintroduced [telemetry]: unreadable queue; telemetry suspended, files preserved", LogLevel.ERROR);
|
||||||
|
m_Closed = true; return;
|
||||||
|
}
|
||||||
|
m_State.serverId = UUID.GenV4();
|
||||||
|
}
|
||||||
|
if (m_State.endpoint != m_Config.Endpoint)
|
||||||
|
{
|
||||||
|
// Never redirect previously collected player data to a different receiver.
|
||||||
|
Purge(); m_State.endpoint = m_Config.Endpoint;
|
||||||
|
}
|
||||||
|
array<string> addons = {}; GameProject.GetLoadedAddons(addons);
|
||||||
|
foreach (string addon : addons)
|
||||||
|
{
|
||||||
|
if (GameProject.IsVanillaAddon(addon)) continue;
|
||||||
|
if (m_Mods.Count() >= 128) { m_TruncatedMods = true; break; }
|
||||||
|
ERR_TelemetryMod mod = new ERR_TelemetryMod();
|
||||||
|
mod.id = addon; mod.name = GameProject.GetAddonTitle(addon);
|
||||||
|
BackendApi backend = GetGame().GetBackendApi();
|
||||||
|
if (backend && backend.GetWorkshop())
|
||||||
|
{
|
||||||
|
WorkshopItem item = backend.GetWorkshop().FindItem(addon);
|
||||||
|
if (item && item.GetActiveRevision()) mod.version = item.GetActiveRevision().GetVersion();
|
||||||
|
}
|
||||||
|
m_Mods.Insert(mod);
|
||||||
|
}
|
||||||
|
m_MissionId = UUID.GenV4(); m_Started = System.GetTickCount();
|
||||||
|
Refresh();
|
||||||
|
GetGame().GetCallqueue().CallLater(Pulse, 5000, true);
|
||||||
|
}
|
||||||
|
protected bool ReadState(string path, out ERR_TelemetryState state)
|
||||||
|
{
|
||||||
|
string raw;
|
||||||
|
JsonLoadContext json = new JsonLoadContext();
|
||||||
|
if (!ERR_ProfileConfig.Text(path, raw) || !json.LoadFromString(raw) || !json.ReadValue("state", state) || !state || state.schemaVersion != 1 || !UUID.IsUUID(state.serverId) || !state.pending || state.pending.Count() > 64) return false;
|
||||||
|
foreach (string queuedEvent : state.pending)
|
||||||
|
{
|
||||||
|
JsonLoadContext queued = new JsonLoadContext(); string eventId, missionId, serverId;
|
||||||
|
if (queuedEvent.Length() > 65536 || !queued.LoadFromString(queuedEvent) || !queued.ReadValue("eventId", eventId) || !UUID.IsUUID(eventId) || !queued.ReadValue("missionId", missionId) || !UUID.IsUUID(missionId) || !queued.ReadValue("serverId", serverId) || serverId != state.serverId) return false;
|
||||||
|
}
|
||||||
|
if (!state.restartFailure.IsEmpty())
|
||||||
|
{
|
||||||
|
JsonLoadContext checkpoint = new JsonLoadContext();
|
||||||
|
string checkpointId, checkpointMission, checkpointServer, checkpointType, checkpointOutcome;
|
||||||
|
int checkpointPhase, checkpointPlayers;
|
||||||
|
if (state.restartFailure.Length() > 65536 || !checkpoint.LoadFromString(state.restartFailure)) return false;
|
||||||
|
if (!checkpoint.ReadValue("eventId", checkpointId) || !UUID.IsUUID(checkpointId) || !checkpoint.ReadValue("missionId", checkpointMission) || checkpointMission != state.previousMissionId || !UUID.IsUUID(checkpointMission)) return false;
|
||||||
|
if (!checkpoint.ReadValue("serverId", checkpointServer) || checkpointServer != state.serverId || !checkpoint.ReadValue("type", checkpointType) || checkpointType != "mission_finished") return false;
|
||||||
|
if (!checkpoint.ReadValue("outcome", checkpointOutcome) || checkpointOutcome != "defeat" || !checkpoint.ReadValue("phase", checkpointPhase) || checkpointPhase != ERR_Phase.DEFEAT || !checkpoint.ReadValue("connectedPlayers", checkpointPlayers) || checkpointPlayers <= 0) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
protected bool LoadState(string path)
|
||||||
|
{
|
||||||
|
ERR_TelemetryState state;
|
||||||
|
if (!ReadState(path, state)) return false;
|
||||||
|
m_State = state; return true;
|
||||||
|
}
|
||||||
|
protected bool Save()
|
||||||
|
{
|
||||||
|
JsonSaveContext json = new JsonSaveContext(); json.EnableTypeDiscriminator(false);
|
||||||
|
json.WriteValue("state", m_State);
|
||||||
|
if (!json.SaveToFile(STATE + ".tmp")) return false;
|
||||||
|
string written;
|
||||||
|
if (!ERR_ProfileConfig.Text(STATE + ".tmp", written) || written != json.SaveToString()) return false;
|
||||||
|
ERR_TelemetryState previous;
|
||||||
|
if (ReadState(STATE, previous) && !FileIO.CopyFile(STATE, STATE + ".bak")) return false;
|
||||||
|
if (!FileIO.CopyFile(STATE + ".tmp", STATE)) return false;
|
||||||
|
string confirmed;
|
||||||
|
if (!ERR_ProfileConfig.Text(STATE, confirmed) || confirmed != written) return false;
|
||||||
|
FileIO.DeleteFile(STATE + ".tmp"); m_Dirty = false; return true;
|
||||||
|
}
|
||||||
|
protected void Purge()
|
||||||
|
{
|
||||||
|
m_State.pending.Clear(); m_State.previousMissionId = ""; m_State.previousOutcome = "";
|
||||||
|
m_State.restartFailure = "";
|
||||||
|
m_Players.Clear(); m_InFlight = ""; m_Callback = null;
|
||||||
|
// Do not retain personal data in recovery files after opting out.
|
||||||
|
FileIO.DeleteFile(STATE); FileIO.DeleteFile(STATE + ".bak"); FileIO.DeleteFile(STATE + ".tmp");
|
||||||
|
m_Dirty = true;
|
||||||
|
}
|
||||||
|
protected bool Current() { return GetGame() && m_Mode && ERR_GameMode.Get() == m_Mode; }
|
||||||
|
string Status()
|
||||||
|
{
|
||||||
|
if (m_Closed) return "Telemetry suspended: queue recovery required.";
|
||||||
|
if (m_Mode.Settings().Get("telemetryEnabled") == 0) return "Disabled. Unsent records discarded.";
|
||||||
|
if (m_Config.Endpoint.IsEmpty()) return "Not sending: receiving URL is missing or invalid.";
|
||||||
|
if (m_Dirty) return "Waiting for profile storage; events have not been sent.";
|
||||||
|
if (m_CredentialBlocked) return "Telemetry credential storage needs attention; delivery suspended.";
|
||||||
|
if (m_Failures > 0) return "Delivery unavailable; pending records will retry.";
|
||||||
|
return string.Format("Configured. %1 delivered this session; %2 pending.", m_Delivered, m_State.pending.Count());
|
||||||
|
}
|
||||||
|
bool Enabled() { return m_Enabled && !m_Closed; }
|
||||||
|
void Refresh()
|
||||||
|
{
|
||||||
|
if (m_Closed || !Current() || !Replication.IsServer() || !m_Mode.Settings()) return;
|
||||||
|
bool enabled = m_Mode.Settings().Get("telemetryEnabled") == 1 && !m_Config.Endpoint.IsEmpty();
|
||||||
|
if (!enabled)
|
||||||
|
{
|
||||||
|
if (m_Enabled || !m_State.pending.IsEmpty() || !m_State.previousMissionId.IsEmpty())
|
||||||
|
{
|
||||||
|
m_Enabled = false; Purge(); Save();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (m_Enabled || m_Ended) return;
|
||||||
|
m_Enabled = true;
|
||||||
|
int now = System.GetTickCount(); m_LastSample = now; m_NextHeartbeat = now + 300000;
|
||||||
|
m_Partial = m_Collected || now - m_Started > 1000;
|
||||||
|
m_Peak = 0; m_TruncatedPlayers = false;
|
||||||
|
if (!m_Collected) RecoverRestart();
|
||||||
|
string previous = m_State.previousMissionId;
|
||||||
|
string outcome = m_State.previousOutcome;
|
||||||
|
m_State.previousMissionId = m_MissionId; m_State.previousOutcome = "unfinished";
|
||||||
|
Sample();
|
||||||
|
if (!m_Collected && !previous.IsEmpty()) Event("mission_restarted", "", previous, outcome);
|
||||||
|
if (m_Collected) Event("telemetry_resumed");
|
||||||
|
else Event("mission_started", "", previous, outcome);
|
||||||
|
m_Collected = true;
|
||||||
|
Checkpoint();
|
||||||
|
}
|
||||||
|
protected void RecoverRestart()
|
||||||
|
{
|
||||||
|
if (m_State.previousOutcome == "unfinished" && !m_State.restartFailure.IsEmpty())
|
||||||
|
{
|
||||||
|
Enqueue(m_State.restartFailure, true);
|
||||||
|
m_State.previousOutcome = "defeat";
|
||||||
|
}
|
||||||
|
// Saved together with the new start and recovered result, before delivery.
|
||||||
|
m_State.restartFailure = "";
|
||||||
|
}
|
||||||
|
protected void Checkpoint()
|
||||||
|
{
|
||||||
|
if (!Enabled() || m_Ended || ERR_MissionRules.Terminal(m_Mode.Phase())) return;
|
||||||
|
if (GetGame().GetPlayerManager().GetPlayerCount() > 0)
|
||||||
|
Event("mission_finished", "defeat", "", "", true);
|
||||||
|
else if (!m_State.restartFailure.IsEmpty())
|
||||||
|
{
|
||||||
|
m_State.restartFailure = ""; m_Dirty = true; Save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
protected void Sample()
|
||||||
|
{
|
||||||
|
if (!Enabled() || m_Ended) return;
|
||||||
|
int now = System.GetTickCount();
|
||||||
|
int elapsed = Math.Max(0, (now - m_LastSample) / 1000); m_LastSample = now;
|
||||||
|
foreach (ERR_TelemetryPlayer known : m_Players)
|
||||||
|
{
|
||||||
|
if (known.connected) known.connectedSeconds += elapsed;
|
||||||
|
known.connected = false;
|
||||||
|
}
|
||||||
|
array<int> connections = {}; GetGame().GetPlayerManager().GetPlayers(connections);
|
||||||
|
m_Peak = Math.Max(m_Peak, connections.Count());
|
||||||
|
foreach (int connection : connections)
|
||||||
|
{
|
||||||
|
UUID account = SCR_PlayerIdentityUtils.GetPlayerIdentityId(connection);
|
||||||
|
string key = account;
|
||||||
|
bool authenticated = !account.IsNull();
|
||||||
|
if (!authenticated) key = m_MissionId + ":connection:" + connection.ToString();
|
||||||
|
ERR_TelemetryPlayer player;
|
||||||
|
foreach (ERR_TelemetryPlayer candidate : m_Players) if (candidate.id == key) { player = candidate; break; }
|
||||||
|
if (!player)
|
||||||
|
{
|
||||||
|
if (m_Players.Count() >= 128) { m_TruncatedPlayers = true; continue; }
|
||||||
|
player = new ERR_TelemetryPlayer(); player.id = key; player.authenticated = authenticated; m_Players.Insert(player);
|
||||||
|
}
|
||||||
|
player.name = GetGame().GetPlayerManager().GetPlayerName(connection);
|
||||||
|
if (player.name.Length() > 128) player.name = player.name.Substring(0, 128);
|
||||||
|
player.connected = true;
|
||||||
|
ERR_PlayerLife life = m_Mode.Players().Get(connection);
|
||||||
|
if (life && life.Connected && life.UsedLife) player.participated = true;
|
||||||
|
if (authenticated && m_Mode.Extraction()) player.escaped = m_Mode.Phase() == ERR_Phase.VICTORY && m_Mode.Extraction().DidEscape(account);
|
||||||
|
}
|
||||||
|
// Include disconnected escapees/participants with their final authoritative result.
|
||||||
|
array<ref ERR_PlayerLife> lives = {}; m_Mode.Players().Participants(lives);
|
||||||
|
foreach (ERR_PlayerLife participant : lives)
|
||||||
|
foreach (ERR_TelemetryPlayer tracked : m_Players)
|
||||||
|
if (tracked.authenticated && tracked.id == participant.AccountId)
|
||||||
|
{
|
||||||
|
tracked.participated = true;
|
||||||
|
ERR_RoundPlayerStats stats;
|
||||||
|
if (m_Mode.ERR_Statistics()) stats = m_Mode.ERR_Statistics().Find(participant.AccountId);
|
||||||
|
if (stats)
|
||||||
|
{
|
||||||
|
tracked.kills = stats.Kills; tracked.friendlyKills = stats.FriendlyKills;
|
||||||
|
tracked.deaths = stats.Deaths; tracked.downed = stats.Downed; tracked.recoveries = stats.Recoveries;
|
||||||
|
}
|
||||||
|
if (m_Mode.Extraction()) tracked.escaped = m_Mode.Phase() == ERR_Phase.VICTORY && m_Mode.Extraction().DidEscape(participant.AccountId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void RosterChanged(int playerId) { Sample(); Checkpoint(); }
|
||||||
|
void PhaseChanged(ERR_Phase previous, ERR_Phase current)
|
||||||
|
{
|
||||||
|
if (!Enabled() || m_Ended) return;
|
||||||
|
if (ERR_MissionRules.Terminal(current)) Finish(ERR_TelemetryRules.Outcome(current, false, false));
|
||||||
|
else Event("phase_changed");
|
||||||
|
}
|
||||||
|
void Finish(string outcome)
|
||||||
|
{
|
||||||
|
Refresh();
|
||||||
|
if (!Enabled() || m_Ended) return;
|
||||||
|
if (outcome == "interrupted" && GetGame().GetPlayerManager().GetPlayerCount() > 0) outcome = "defeat";
|
||||||
|
Sample(); m_Ended = true;
|
||||||
|
m_State.previousOutcome = outcome;
|
||||||
|
m_State.restartFailure = "";
|
||||||
|
Event("mission_finished", outcome);
|
||||||
|
}
|
||||||
|
protected string Terrain()
|
||||||
|
{
|
||||||
|
// The loaded root scene identifies the island beneath GM/Conflict scenarios.
|
||||||
|
// Use the same terrain resolver as the mission title, on the reporting server.
|
||||||
|
string world = GetGame().GetWorldFile();
|
||||||
|
if (GetGame().GetWorld() && GetGame().GetWorld().GetNumSubScenes() > 0)
|
||||||
|
{
|
||||||
|
string root = GetGame().GetWorld().GetSubSceneName(0);
|
||||||
|
if (!root.IsEmpty()) world = root;
|
||||||
|
}
|
||||||
|
return ERR_TerrainDisplayName.Resolve(world);
|
||||||
|
}
|
||||||
|
protected void Event(string type, string outcome = "", string previous = "", string previousOutcome = "", bool checkpoint = false)
|
||||||
|
{
|
||||||
|
if (!Enabled()) return;
|
||||||
|
if (!m_Ended) Sample();
|
||||||
|
JsonSaveContext json = new JsonSaveContext(); json.EnableTypeDiscriminator(false);
|
||||||
|
json.WriteValue("schemaVersion", 1);
|
||||||
|
string eventId = UUID.GenV4();
|
||||||
|
json.WriteValue("eventId", eventId); json.WriteValue("missionId", m_MissionId);
|
||||||
|
json.WriteValue("serverId", m_State.serverId); json.WriteValue("serverName", ERR_TelemetryServerName.Resolve(m_Config.ServerName));
|
||||||
|
json.WriteValue("type", type); json.WriteValue("occurredAt", System.GetUnixTime());
|
||||||
|
json.WriteValue("partial", m_Partial);
|
||||||
|
int elapsedSeconds = Math.Max(0, (System.GetTickCount() - m_Started) / 1000);
|
||||||
|
json.WriteValue("elapsedSeconds", elapsedSeconds);
|
||||||
|
int phase = m_Mode.Phase();
|
||||||
|
if (outcome == "defeat") phase = ERR_Phase.DEFEAT;
|
||||||
|
json.WriteValue("phase", phase); json.WriteValue("outcome", outcome);
|
||||||
|
json.WriteValue("previousMissionId", previous); json.WriteValue("previousOutcome", previousOutcome);
|
||||||
|
string terrain = Terrain();
|
||||||
|
json.WriteValue("terrain", terrain);
|
||||||
|
SCR_MissionHeader header = SCR_MissionHeader.Cast(GetGame().GetMissionHeader());
|
||||||
|
string scenario; if (header) scenario = header.m_sName;
|
||||||
|
json.WriteValue("gameVersion", GetGame().GetBuildVersion());
|
||||||
|
json.WriteValue("scenario", scenario); json.WriteValue("mods", m_Mods);
|
||||||
|
json.WriteValue("modsTruncated", m_TruncatedMods); json.WriteValue("playersTruncated", m_TruncatedPlayers);
|
||||||
|
json.WriteValue("connectedPlayers", GetGame().GetPlayerManager().GetPlayerCount());
|
||||||
|
json.WriteValue("peakPlayers", m_Peak); json.WriteValue("players", m_Players);
|
||||||
|
string reason = m_Mode.EndReason();
|
||||||
|
if (outcome == "defeat" && !ERR_MissionRules.Terminal(m_Mode.Phase())) reason = "Mission interrupted by a restart while players were connected.";
|
||||||
|
json.WriteValue("reason", reason); json.WriteValue("preparationError", m_Mode.PreparationError());
|
||||||
|
map<string, int> settings = new map<string, int>();
|
||||||
|
foreach (string key : ERR_Settings.Keys()) settings.Set(key, m_Mode.Settings().Get(key));
|
||||||
|
json.WriteValue("settings", settings); json.WriteValue("droppedEvents", m_State.droppedEvents);
|
||||||
|
string payload = json.SaveToString();
|
||||||
|
if (payload.Length() > 65536) { m_State.droppedEvents++; m_Dirty = true; return; }
|
||||||
|
// This candidate is private profile state, never sent during a live mission.
|
||||||
|
if (checkpoint) { m_State.restartFailure = payload; m_Dirty = true; Save(); return; }
|
||||||
|
Enqueue(payload, type == "mission_finished");
|
||||||
|
if (type == "mission_finished")
|
||||||
|
{
|
||||||
|
m_FinalEventId = eventId; m_FinalOutcome = outcome; m_FinalTerrain = terrain;
|
||||||
|
// Finish can arrive while a previous request is in flight. RemoveItem on its
|
||||||
|
// acknowledgement still removes that exact payload, leaving this final first.
|
||||||
|
if (outcome == "victory" || outcome == "defeat")
|
||||||
|
m_Mode.ERR_PublishCommunityStatistics("This map's community record: waiting for the round result to be recorded...");
|
||||||
|
GetGame().GetCallqueue().CallLater(Pulse, 1, false);
|
||||||
|
}
|
||||||
|
m_Dirty = true;
|
||||||
|
Save();
|
||||||
|
}
|
||||||
|
protected void Enqueue(string payload, bool finalResult)
|
||||||
|
{
|
||||||
|
// Bound disk and memory during long outages. Prefer retaining result records.
|
||||||
|
if (m_State.pending.Count() >= 64)
|
||||||
|
{
|
||||||
|
int remove = -1;
|
||||||
|
for (int i = 0; i < m_State.pending.Count(); i++)
|
||||||
|
{
|
||||||
|
if (m_State.pending[i] == m_InFlight) continue;
|
||||||
|
JsonLoadContext queued = new JsonLoadContext(); string queuedType;
|
||||||
|
if (queued.LoadFromString(m_State.pending[i]) && queued.ReadValue("type", queuedType) && queuedType == "heartbeat") { remove = i; break; }
|
||||||
|
}
|
||||||
|
if (remove < 0) remove = 0;
|
||||||
|
if (remove == 0 && !m_InFlight.IsEmpty()) remove = 1;
|
||||||
|
m_State.pending.Remove(remove); m_State.droppedEvents++;
|
||||||
|
}
|
||||||
|
if (finalResult) m_State.pending.InsertAt(payload, 0);
|
||||||
|
else m_State.pending.Insert(payload);
|
||||||
|
m_Dirty = true;
|
||||||
|
}
|
||||||
|
protected void Pulse()
|
||||||
|
{
|
||||||
|
if (m_Closed || ERR_GameMode.Get() != m_Mode) { Close(); return; }
|
||||||
|
Refresh();
|
||||||
|
if (!Enabled()) return;
|
||||||
|
int now = System.GetTickCount();
|
||||||
|
if (!m_Ended)
|
||||||
|
{
|
||||||
|
Sample();
|
||||||
|
Checkpoint();
|
||||||
|
if (now >= m_NextHeartbeat) { m_NextHeartbeat = now + 300000; Event("heartbeat"); }
|
||||||
|
}
|
||||||
|
if (m_Dirty && !Save()) { CommunityUnavailable(); return; }
|
||||||
|
if (m_Callback && now - m_RequestStarted > 30000) Failed(m_Callback);
|
||||||
|
if (m_Callback || m_State.pending.IsEmpty() || now < m_NextAttempt) return;
|
||||||
|
if (m_Config.Token.IsEmpty())
|
||||||
|
{
|
||||||
|
string token;
|
||||||
|
if (!ERR_TelemetryCredentials.Resolve(m_Config.Endpoint, m_State.serverId, token))
|
||||||
|
{
|
||||||
|
CommunityUnavailable();
|
||||||
|
m_CredentialBlocked = true; m_NextAttempt = now + 30000; return;
|
||||||
|
}
|
||||||
|
m_Config.Token = token; m_CredentialBlocked = false;
|
||||||
|
}
|
||||||
|
m_Context = GetGame().GetRestApi().GetContext(m_Config.Endpoint);
|
||||||
|
if (!m_Context) { CommunityUnavailable(); m_NextAttempt = now + 300000; return; }
|
||||||
|
m_Callback = new RestCallback(); m_Callback.SetOnSuccess(Delivered); m_Callback.SetOnError(Failed);
|
||||||
|
m_InFlight = m_State.pending[0]; m_RequestStarted = now;
|
||||||
|
JsonSaveContext request = new JsonSaveContext(); request.EnableTypeDiscriminator(false);
|
||||||
|
request.WriteValue("token", m_Config.Token);
|
||||||
|
// Embed the event object as JSON rather than double-encoding a JSON string.
|
||||||
|
string wrapper = request.SaveToString();
|
||||||
|
wrapper = wrapper.Substring(0, wrapper.Length() - 1) + ",\"event\":" + m_InFlight + "}";
|
||||||
|
m_Context.SetHeaders("Content-Type,application/json"); m_Context.SetTimeout(15);
|
||||||
|
m_Context.POST(m_Callback, "", wrapper);
|
||||||
|
}
|
||||||
|
protected void Delivered(RestCallback callback)
|
||||||
|
{
|
||||||
|
if (callback != m_Callback || !Enabled() || !Current()) return;
|
||||||
|
if (!ERR_TelemetryRules.Acknowledged(callback.GetHttpCode())) { Failed(callback); return; }
|
||||||
|
UpdateCommunity(callback);
|
||||||
|
m_State.pending.RemoveItem(m_InFlight); m_InFlight = ""; m_Callback = null;
|
||||||
|
m_Failures = 0; m_Delivered++; m_Dirty = true; Save();
|
||||||
|
if (m_Ended) GetGame().GetCallqueue().CallLater(Pulse, 1, false);
|
||||||
|
}
|
||||||
|
protected void CommunityUnavailable()
|
||||||
|
{
|
||||||
|
if (!m_CommunityReady && Current() && m_Ended && (m_FinalOutcome == "victory" || m_FinalOutcome == "defeat"))
|
||||||
|
m_Mode.ERR_PublishCommunityStatistics("Community map records unavailable. This round's local statistics are shown below.");
|
||||||
|
}
|
||||||
|
protected void UpdateCommunity(RestCallback callback)
|
||||||
|
{
|
||||||
|
if (!m_Ended || m_FinalEventId.IsEmpty() || !Current()) return;
|
||||||
|
JsonLoadContext sent = new JsonLoadContext(); string sentEvent;
|
||||||
|
if (!sent.LoadFromString(m_InFlight) || !sent.ReadValue("eventId", sentEvent) || sentEvent != m_FinalEventId) return;
|
||||||
|
if (m_FinalOutcome != "victory" && m_FinalOutcome != "defeat") return;
|
||||||
|
JsonLoadContext reply = new JsonLoadContext();
|
||||||
|
ERR_CommunityStatistics community;
|
||||||
|
if (callback.GetData().Length() > 8192 || !reply.LoadFromString(callback.GetData()) || !reply.ReadValue("community", community) || !community || !community.Matches(m_State.serverId, m_MissionId, m_FinalEventId, m_FinalTerrain, m_FinalOutcome))
|
||||||
|
{
|
||||||
|
CommunityUnavailable(); return;
|
||||||
|
}
|
||||||
|
m_CommunityReady = true;
|
||||||
|
m_Mode.ERR_PublishCommunityStatistics(community.Summary());
|
||||||
|
}
|
||||||
|
protected void Failed(RestCallback callback)
|
||||||
|
{
|
||||||
|
if (callback != m_Callback || !Enabled()) return;
|
||||||
|
m_Callback = null; m_InFlight = ""; m_Failures = Math.Min(100, m_Failures + 1);
|
||||||
|
m_NextAttempt = System.GetTickCount() + 1000 * ERR_TelemetryRules.RetrySeconds(m_Failures);
|
||||||
|
CommunityUnavailable();
|
||||||
|
if (m_Failures == 1) Print("Reintroduced [telemetry]: delivery failed; queued events will retry", LogLevel.WARNING);
|
||||||
|
}
|
||||||
|
void Close()
|
||||||
|
{
|
||||||
|
if (m_Closed) return;
|
||||||
|
if (Current()) Finish("interrupted");
|
||||||
|
m_Closed = true;
|
||||||
|
if (GetGame()) GetGame().GetCallqueue().Remove(Pulse);
|
||||||
|
m_Callback = null; m_Context = null; m_Mode = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
// Each installation claims its random server ID with its own persistent secret.
|
||||||
|
// No receiver administrator secret is bundled with the mod or sent by players.
|
||||||
|
class ERR_TelemetryCredential
|
||||||
|
{
|
||||||
|
string endpoint;
|
||||||
|
string serverId;
|
||||||
|
string token;
|
||||||
|
}
|
||||||
|
class ERR_TelemetryCredentials
|
||||||
|
{
|
||||||
|
static const string PATH = "$profile:EscapistsTelemetryCredentials.json";
|
||||||
|
static bool Valid(ERR_TelemetryCredential entry)
|
||||||
|
{
|
||||||
|
return entry && !entry.endpoint.IsEmpty() && UUID.IsUUID(entry.serverId) && entry.token.Length() >= 32 && entry.token.Length() <= 256;
|
||||||
|
}
|
||||||
|
static bool Resolve(string endpoint, string serverId, out string token)
|
||||||
|
{
|
||||||
|
token = "";
|
||||||
|
ref array<ref ERR_TelemetryCredential> entries = {};
|
||||||
|
if (FileIO.FileExists(PATH))
|
||||||
|
{
|
||||||
|
JsonLoadContext loaded = new JsonLoadContext();
|
||||||
|
// Never silently replace a damaged credential: the receiver already owns its
|
||||||
|
// hash, and a new secret must not take over the same server's history.
|
||||||
|
if (!loaded.LoadFromFile(PATH) || !loaded.ReadValue("credentials", entries) || !entries || entries.Count() > 16) return false;
|
||||||
|
foreach (ERR_TelemetryCredential known : entries)
|
||||||
|
{
|
||||||
|
if (!Valid(known)) return false;
|
||||||
|
if (known.endpoint == endpoint && known.serverId == serverId) token = known.token;
|
||||||
|
}
|
||||||
|
if (!token.IsEmpty()) return true;
|
||||||
|
}
|
||||||
|
if (entries.Count() >= 16) return false;
|
||||||
|
ERR_TelemetryCredential created = new ERR_TelemetryCredential();
|
||||||
|
created.endpoint = endpoint; created.serverId = serverId;
|
||||||
|
string first = UUID.GenV4(); string second = UUID.GenV4();
|
||||||
|
created.token = first + second;
|
||||||
|
entries.Insert(created);
|
||||||
|
JsonSaveContext saved = new JsonSaveContext(); saved.EnableTypeDiscriminator(false);
|
||||||
|
saved.WriteValue("credentials", entries);
|
||||||
|
if (!saved.SaveToFile(PATH + ".tmp")) return false;
|
||||||
|
string written;
|
||||||
|
if (!ERR_ProfileConfig.Text(PATH + ".tmp", written) || written != saved.SaveToString()) return false;
|
||||||
|
if (!FileIO.CopyFile(PATH + ".tmp", PATH)) return false;
|
||||||
|
string confirmed;
|
||||||
|
if (!ERR_ProfileConfig.Text(PATH, confirmed) || confirmed != written) return false;
|
||||||
|
FileIO.DeleteFile(PATH + ".tmp");
|
||||||
|
// Publish to the HTTP sender only after the exact credential is durable.
|
||||||
|
token = created.token;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
// Keep instrumentation outside mission rules: telemetry cannot decide an outcome
|
||||||
|
// or delay vanilla shutdown/restarts. Unsent results survive in the profile queue.
|
||||||
|
modded class ERR_GameMode
|
||||||
|
{
|
||||||
|
protected ref ERR_Telemetry m_ERRTelemetry;
|
||||||
|
protected override void EOnInit(IEntity owner)
|
||||||
|
{
|
||||||
|
super.EOnInit(owner);
|
||||||
|
if (SCR_Global.IsEditMode() || !Replication.IsServer() || !Settings()) return;
|
||||||
|
#ifdef WORKBENCH
|
||||||
|
if (!System.IsCLIParam("errTelemetryTest")) return;
|
||||||
|
#endif
|
||||||
|
m_ERRTelemetry = new ERR_Telemetry(this);
|
||||||
|
Admin().Register(new ERR_TelemetryAdminProvider(m_ERRTelemetry));
|
||||||
|
OnPhaseChanged().Insert(m_ERRTelemetry.PhaseChanged);
|
||||||
|
Players().OnChanged().Insert(m_ERRTelemetry.RosterChanged);
|
||||||
|
}
|
||||||
|
void ERR_RefreshTelemetry() { if (m_ERRTelemetry) m_ERRTelemetry.Refresh(); }
|
||||||
|
protected override void FailPreparation(string detail)
|
||||||
|
{
|
||||||
|
super.FailPreparation(detail);
|
||||||
|
if (m_ERRTelemetry && !PreparationError().IsEmpty()) m_ERRTelemetry.Finish("preparation_failed");
|
||||||
|
}
|
||||||
|
protected override void OnGameModeEnd(SCR_GameModeEndData endData)
|
||||||
|
{
|
||||||
|
// Observe the phase BEFORE vanilla maps native interruption/empty restart to defeat.
|
||||||
|
if (m_ERRTelemetry) m_ERRTelemetry.Finish(ERR_TelemetryRules.Outcome(Phase(), m_EmptyRestartRequested, !PreparationError().IsEmpty()));
|
||||||
|
super.OnGameModeEnd(endData);
|
||||||
|
}
|
||||||
|
override void OnGameEnd()
|
||||||
|
{
|
||||||
|
if (m_ERRTelemetry) m_ERRTelemetry.Close();
|
||||||
|
super.OnGameEnd();
|
||||||
|
}
|
||||||
|
void ~ERR_GameMode() { if (m_ERRTelemetry) m_ERRTelemetry.Close(); }
|
||||||
|
}
|
||||||
|
modded class ERR_AdminService
|
||||||
|
{
|
||||||
|
override void Execute(ERR_GameMode mode, bool authorized, string arguments, array<string> replies)
|
||||||
|
{
|
||||||
|
super.Execute(mode, authorized, arguments, replies);
|
||||||
|
// Settings only change through the existing authenticated, save-before-apply path.
|
||||||
|
if (authorized && mode) mode.ERR_RefreshTelemetry();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ERR_TelemetryAdminProvider : ERR_AdminProvider
|
||||||
|
{
|
||||||
|
protected ERR_Telemetry m_Service;
|
||||||
|
void ERR_TelemetryAdminProvider(ERR_Telemetry service) { m_Service = service; }
|
||||||
|
override string Id() { return "telemetry"; }
|
||||||
|
override void Capabilities(array<string> values)
|
||||||
|
{
|
||||||
|
if (m_Service) values.Insert("telemetryStatus=" + m_Service.Status());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
// Resolve on the reporting server for every new event: lobby metadata can be
|
||||||
|
// unavailable during initial startup. Never infer identity from the scenario.
|
||||||
|
class ERR_TelemetryServerName
|
||||||
|
{
|
||||||
|
static string Resolve(string configured)
|
||||||
|
{
|
||||||
|
string name = configured;
|
||||||
|
name.TrimInPlace();
|
||||||
|
if (name.IsEmpty())
|
||||||
|
{
|
||||||
|
BackendApi backend = GetGame().GetBackendApi();
|
||||||
|
SCR_DSConfig dedicated = new SCR_DSConfig();
|
||||||
|
if (backend && backend.GetRunningDSConfig(dedicated) && dedicated.game)
|
||||||
|
name = dedicated.game.name;
|
||||||
|
name.TrimInPlace();
|
||||||
|
}
|
||||||
|
if (name.IsEmpty())
|
||||||
|
{
|
||||||
|
ServerConfig config = ServerLobbyApi.GetServerConfig();
|
||||||
|
if (config) name = config.GetName();
|
||||||
|
name.TrimInPlace();
|
||||||
|
}
|
||||||
|
if (name.IsEmpty())
|
||||||
|
{
|
||||||
|
ServerInfo info = GetGame().GetServerInfo();
|
||||||
|
if (info) name = info.GetName();
|
||||||
|
name.TrimInPlace();
|
||||||
|
}
|
||||||
|
if (name.Length() > 120) name = name.Substring(0, 120);
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,13 +7,21 @@ class ERR_TerrainSites : ERR_GeneratedSites
|
|||||||
protected vector m_Candidate;
|
protected vector m_Candidate;
|
||||||
protected float m_CandidateHeading;
|
protected float m_CandidateHeading;
|
||||||
protected int m_CandidateBegan;
|
protected int m_CandidateBegan;
|
||||||
protected ref ERR_ReconAirports m_Airports = new ERR_ReconAirports();
|
|
||||||
protected bool m_AirportsReady;
|
|
||||||
protected int m_GeometryRejected, m_NavigationRejected, m_PadRejected;
|
protected int m_GeometryRejected, m_NavigationRejected, m_PadRejected;
|
||||||
|
protected string m_NavigationReason;
|
||||||
|
// Keep the same work slice; difficult terrain gets more slices, not larger stalls.
|
||||||
|
static int SearchAttempts() { return 48000; }
|
||||||
|
static int SearchMilliseconds() { return 120000; }
|
||||||
|
static float PrisonAirportBuffer(vector low, vector high)
|
||||||
|
{
|
||||||
|
// The old 1.5km tactical separation could exclude an entire small island.
|
||||||
|
// Physical ground, scenery and navigation checks still apply independently.
|
||||||
|
return Math.Clamp(Math.Min(high[0] - low[0], high[2] - low[2]) * 0.15, 200, 1500);
|
||||||
|
}
|
||||||
static bool Bounds(vector low, vector high)
|
static bool Bounds(vector low, vector high)
|
||||||
{
|
{
|
||||||
float width = high[0] - low[0], depth = high[2] - low[2];
|
float width = high[0] - low[0], depth = high[2] - low[2];
|
||||||
return width >= 2000 && depth >= 2000 && width <= 100000 && depth <= 100000;
|
return width >= 1000 && depth >= 1000 && width <= 100000 && depth <= 100000;
|
||||||
}
|
}
|
||||||
static float Spacing(vector low, vector high)
|
static float Spacing(vector low, vector high)
|
||||||
{
|
{
|
||||||
@@ -23,12 +31,12 @@ class ERR_TerrainSites : ERR_GeneratedSites
|
|||||||
{
|
{
|
||||||
if (m_Started || !Replication.IsServer() || !definition || !definition.Valid() || !GetGame().GetWorldEntity()) return false;
|
if (m_Started || !Replication.IsServer() || !definition || !definition.Valid() || !GetGame().GetWorldEntity()) return false;
|
||||||
GetGame().GetWorldEntity().GetWorldBounds(m_Low, m_High);
|
GetGame().GetWorldEntity().GetWorldBounds(m_Low, m_High);
|
||||||
if (!Bounds(m_Low, m_High)) { m_Error = "Terrain bounds must span 2-100 km on each axis."; return false; }
|
if (!Bounds(m_Low, m_High)) { m_Error = "Terrain bounds must span 1-100 km on each axis."; return false; }
|
||||||
AIWorld world = GetGame().GetAIWorld();
|
AIWorld world = GetGame().GetAIWorld();
|
||||||
if (!world || !world.GetNavmeshWorldComponent("Soldiers")) { m_Error = "Terrain requires its own Soldiers navigation mesh."; return false; }
|
if (!world || !world.GetNavmeshWorldComponent("Soldiers")) { m_Error = "Terrain requires its own Soldiers navigation mesh."; return false; }
|
||||||
m_Started = true; m_Definition = definition; definition.TerrainGenerated = true;
|
m_Started = true; m_Definition = definition; definition.TerrainGenerated = true;
|
||||||
m_Spacing = Spacing(m_Low, m_High);
|
m_Spacing = Spacing(m_Low, m_High);
|
||||||
// Resolve the terrain's airport first, then keep the prison in escape range.
|
// Resolve a clear landing zone first, then keep the prison in escape range.
|
||||||
array<int> order = {ERR_LocationKind.EXTRACTION, ERR_LocationKind.PRISON, ERR_LocationKind.RADIO, ERR_LocationKind.DEPOT, ERR_LocationKind.HOSPITAL, ERR_LocationKind.MORTAR, ERR_LocationKind.OUTPOST};
|
array<int> order = {ERR_LocationKind.EXTRACTION, ERR_LocationKind.PRISON, ERR_LocationKind.RADIO, ERR_LocationKind.DEPOT, ERR_LocationKind.HOSPITAL, ERR_LocationKind.MORTAR, ERR_LocationKind.OUTPOST};
|
||||||
foreach (int kind : order)
|
foreach (int kind : order)
|
||||||
foreach (ERR_SiteDefinition site : definition.Sites)
|
foreach (ERR_SiteDefinition site : definition.Sites)
|
||||||
@@ -37,7 +45,7 @@ class ERR_TerrainSites : ERR_GeneratedSites
|
|||||||
site.PlacementResolved = false;
|
site.PlacementResolved = false;
|
||||||
// The queue, rather than RandomClear, owns placement including extraction.
|
// The queue, rather than RandomClear, owns placement including extraction.
|
||||||
site.RandomClear = false;
|
site.RandomClear = false;
|
||||||
if (kind == ERR_LocationKind.EXTRACTION) { site.RequireAirfield = true; site.AircraftOffset = vector.Zero; }
|
if (kind == ERR_LocationKind.EXTRACTION) { site.RequireAirfield = false; site.Label = "Extraction landing zone"; site.AircraftOffset = vector.Zero; }
|
||||||
if (kind == ERR_LocationKind.DEPOT) site.Label = "Supply cache";
|
if (kind == ERR_LocationKind.DEPOT) site.Label = "Supply cache";
|
||||||
m_Pending.Insert(site);
|
m_Pending.Insert(site);
|
||||||
}
|
}
|
||||||
@@ -62,89 +70,74 @@ class ERR_TerrainSites : ERR_GeneratedSites
|
|||||||
}
|
}
|
||||||
protected bool Navigable(vector position)
|
protected bool Navigable(vector position)
|
||||||
{
|
{
|
||||||
|
m_NavigationReason = "";
|
||||||
NavmeshWorldComponent mesh = GetGame().GetAIWorld().GetNavmeshWorldComponent("Soldiers");
|
NavmeshWorldComponent mesh = GetGame().GetAIWorld().GetNavmeshWorldComponent("Soldiers");
|
||||||
if (!mesh) return false;
|
if (!mesh) { m_NavigationReason = "Soldiers navmesh unavailable"; return false; }
|
||||||
if (!mesh.IsTileLoaded(position)) { m_WaitingNav = mesh.LoadTileIn(position); return false; }
|
if (!mesh.IsTileLoaded(position))
|
||||||
|
{
|
||||||
|
m_WaitingNav = mesh.LoadTileIn(position);
|
||||||
|
m_NavigationReason = "navmesh tile unavailable";
|
||||||
|
if (m_WaitingNav) m_NavigationReason = "waiting for navmesh tile";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
vector reachable;
|
vector reachable;
|
||||||
return mesh.GetReachablePoint(position, 10, reachable);
|
if (mesh.GetReachablePoint(position, 10, reachable)) return true;
|
||||||
|
m_NavigationReason = "no reachable AI ground";
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
protected bool Survey(ERR_SiteDefinition site, vector position, float heading, out float floor)
|
protected bool Survey(ERR_SiteDefinition site, vector position, float heading, out float floor)
|
||||||
{
|
{
|
||||||
if (!m_Terrain.Site(site, position, heading, floor)) { m_GeometryRejected++; return false; }
|
if (!m_Terrain.Site(site, position, heading, floor, PrisonAirportBuffer(m_Low, m_High))) { m_GeometryRejected++; RecordRejection(m_Terrain.Rejection()); return false; }
|
||||||
vector ground = Vector(position[0], floor, position[2]);
|
vector ground = Vector(position[0], floor, position[2]);
|
||||||
if (!Navigable(ground)) { m_NavigationRejected++; return false; }
|
// Aircraft spawn at the centre's terrain height, not the camp grid's minimum.
|
||||||
if (site.Guarded && !Navigable(ERR_Actors.Ground(position + ERR_CampTerrain.Rotate(site.GuardOffset, heading)))) { m_NavigationRejected++; return false; }
|
// Using the minimum here falsely steepens the uphill edge of a valid pad.
|
||||||
if (site.ResponseCount > 0 && !Navigable(ERR_Actors.Ground(position + ERR_CampTerrain.Rotate(site.ResponseOffset, heading)))) { m_NavigationRejected++; return false; }
|
if (site.Kind == ERR_LocationKind.EXTRACTION)
|
||||||
|
{
|
||||||
|
ground = ERR_Actors.Ground(position);
|
||||||
|
floor = ground[1];
|
||||||
|
}
|
||||||
|
if (!Navigable(ground)) { m_NavigationRejected++; RecordRejection(m_NavigationReason); return false; }
|
||||||
|
if (site.Guarded && !Navigable(ERR_Actors.Ground(position + ERR_CampTerrain.Rotate(site.GuardOffset, heading)))) { m_NavigationRejected++; RecordRejection("guard area: " + m_NavigationReason); return false; }
|
||||||
|
if (site.ResponseCount > 0 && !Navigable(ERR_Actors.Ground(position + ERR_CampTerrain.Rotate(site.ResponseOffset, heading)))) { m_NavigationRejected++; RecordRejection("patrol area: " + m_NavigationReason); return false; }
|
||||||
if (site.Kind != ERR_LocationKind.EXTRACTION) return true;
|
if (site.Kind != ERR_LocationKind.EXTRACTION) return true;
|
||||||
float padFloor;
|
float padFloor;
|
||||||
if (m_Terrain.Clear(ground, heading, 14, 0.8, padFloor))
|
if (m_Terrain.Clear(ground, heading, 14, 0.8, padFloor))
|
||||||
{
|
{
|
||||||
bool primaryClear;
|
bool primaryClear = ERR_AirfieldPlacement.ClearGround(ground);
|
||||||
if (site.RequireAirfield) primaryClear = ERR_AirfieldPlacement.Clear(ground);
|
if (!primaryClear) RecordRejection("primary aircraft clearance");
|
||||||
else primaryClear = ERR_AirfieldPlacement.ClearGround(ground);
|
else if (!ERR_RescueLanding.Fits(ground)) RecordRejection("secondary aircraft pad");
|
||||||
if (primaryClear && ERR_RescueLanding.Fits(ground)) return true;
|
else return true;
|
||||||
}
|
}
|
||||||
|
else RecordRejection("landing zone: " + m_Terrain.Rejection());
|
||||||
m_PadRejected++; return false;
|
m_PadRejected++; return false;
|
||||||
}
|
}
|
||||||
protected vector SamplePosition(ERR_SiteDefinition site)
|
protected vector SamplePosition(ERR_SiteDefinition site)
|
||||||
{
|
{
|
||||||
if (site.Kind != ERR_LocationKind.EXTRACTION || m_Airports.m_Positions.IsEmpty())
|
// Helicopters need a clear pad, not airport infrastructure or an airport-first pass.
|
||||||
return Vector(Math.RandomFloat(m_Low[0] + 60, m_High[0] - 60), 0, Math.RandomFloat(m_Low[2] + 60, m_High[2] - 60));
|
return Vector(Math.RandomFloat(m_Low[0] + 60, m_High[0] - 60), 0, Math.RandomFloat(m_Low[2] + 60, m_High[2] - 60));
|
||||||
vector centre = m_Airports.m_Positions[Math.RandomInt(0, m_Airports.m_Positions.Count())];
|
|
||||||
float angle = Math.RandomFloat(0, Math.PI2), radius = Math.RandomFloat(0, 450);
|
|
||||||
return centre + Vector(Math.Cos(angle) * radius, 0, Math.Sin(angle) * radius);
|
|
||||||
}
|
|
||||||
protected bool PrepareAirports()
|
|
||||||
{
|
|
||||||
if (m_AirportsReady) return true;
|
|
||||||
// The scenario ticks every 250ms. One 2km tile per tick made Chernarus
|
|
||||||
// spend roughly 48 seconds just waiting between airport queries. Batch
|
|
||||||
// inexpensive tiles, but yield after 4ms or eight queries. A native query
|
|
||||||
// itself cannot be interrupted, so check the budget after every call.
|
|
||||||
int started = System.GetTickCount();
|
|
||||||
for (int tile = 0; tile < 8; tile++)
|
|
||||||
{
|
|
||||||
m_Airports.Update();
|
|
||||||
if (m_Airports.Complete() || System.GetTickCount() - started >= 4) break;
|
|
||||||
}
|
|
||||||
if (!m_Airports.Complete()) return false;
|
|
||||||
if (m_Airports.m_Positions.IsEmpty())
|
|
||||||
{
|
|
||||||
// A painted runway or an island without an airport must not require a
|
|
||||||
// connector or administrator coordinates. Survey a helicopter landing zone
|
|
||||||
// with the same ground/navmesh/aircraft checks and finite placement budget.
|
|
||||||
foreach (ERR_SiteDefinition site : m_Pending)
|
|
||||||
if (site.Kind == ERR_LocationKind.EXTRACTION)
|
|
||||||
{
|
|
||||||
site.RequireAirfield = false;
|
|
||||||
site.Label = "Extraction landing zone";
|
|
||||||
}
|
|
||||||
Print("Reintroduced [terrain]: no airport infrastructure found; surveying a clear helicopter extraction landing zone");
|
|
||||||
}
|
|
||||||
PrintFormat("Reintroduced [generation]: airport survey ready in %1 ms; airports=%2", System.GetTickCount() - m_Began, m_Airports.m_Positions.Count());
|
|
||||||
m_AirportsReady = true; m_Began = System.GetTickCount();
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
override ERR_PrepareResult Tick()
|
override ERR_PrepareResult Tick()
|
||||||
{
|
{
|
||||||
if (m_Result != ERR_PrepareResult.PENDING) return m_Result;
|
if (m_Result != ERR_PrepareResult.PENDING) return m_Result;
|
||||||
if (!m_Started || !Replication.IsServer()) return ERR_PrepareResult.FAILED;
|
if (!m_Started || !Replication.IsServer()) return ERR_PrepareResult.FAILED;
|
||||||
if (!PrepareAirports()) return m_Result;
|
|
||||||
int tick = System.GetTickCount();
|
int tick = System.GetTickCount();
|
||||||
while (m_Index < m_Pending.Count())
|
while (m_Index < m_Pending.Count())
|
||||||
{
|
{
|
||||||
if (m_StartupOnly && StartupReady()) return ERR_PrepareResult.PENDING;
|
if (m_StartupOnly && StartupReady()) return ERR_PrepareResult.PENDING;
|
||||||
ERR_SiteDefinition site = m_Pending[m_Index];
|
ERR_SiteDefinition site = m_Pending[m_Index];
|
||||||
if (m_Attempts >= 12000 || System.GetTickCount() - m_Began >= 30000)
|
ReportProgress(site);
|
||||||
|
if (m_Attempts >= SearchAttempts() || System.GetTickCount() - m_Began >= SearchMilliseconds())
|
||||||
{
|
{
|
||||||
PrintFormat("Reintroduced [terrain survey exhausted]: id=%1 attempts=%2 rejected geometry=%3 navigation=%4 aircraft=%5", site.Id, m_Attempts, m_GeometryRejected, m_NavigationRejected, m_PadRejected);
|
PrintFormat("Reintroduced [terrain survey exhausted]: id=%1 attempts=%2 rejected geometry=%3 navigation=%4 aircraft=%5", site.Id, m_Attempts, m_GeometryRejected, m_NavigationRejected, m_PadRejected);
|
||||||
|
ReportSearch(site, "exhausted");
|
||||||
if (!SkipOptional(site))
|
if (!SkipOptional(site))
|
||||||
{
|
{
|
||||||
m_Error = "Terrain has no safe navigable placement for " + site.Label + ". Check this map's Soldiers navmesh, terrain clearance and size; no foreign-map fallback was used.";
|
m_Error = "Terrain has no safe navigable placement for " + site.Label + " (" + site.Id + "). Checks: " + RejectionSummary();
|
||||||
m_Result = ERR_PrepareResult.FAILED; return m_Result;
|
m_Result = ERR_PrepareResult.FAILED; return m_Result;
|
||||||
}
|
}
|
||||||
m_Index++; m_Attempts = 0; m_Began = System.GetTickCount(); m_HaveCandidate = false;
|
m_Index++; m_Attempts = 0; m_Began = System.GetTickCount(); m_HaveCandidate = false;
|
||||||
m_GeometryRejected = 0; m_NavigationRejected = 0; m_PadRejected = 0;
|
m_GeometryRejected = 0; m_NavigationRejected = 0; m_PadRejected = 0;
|
||||||
|
ClearDiagnostics();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -157,11 +150,14 @@ class ERR_TerrainSites : ERR_GeneratedSites
|
|||||||
vector position = m_Candidate;
|
vector position = m_Candidate;
|
||||||
float heading = m_CandidateHeading, floor;
|
float heading = m_CandidateHeading, floor;
|
||||||
m_WaitingNav = false;
|
m_WaitingNav = false;
|
||||||
if (Separated(site, position, false) && Survey(site, position, heading, floor))
|
bool separated = Separated(site, position, false);
|
||||||
|
if (!separated) RecordRejection("site spacing / escape boundary");
|
||||||
|
if (separated && Survey(site, position, heading, floor))
|
||||||
{
|
{
|
||||||
position[1] = floor + 0.02;
|
position[1] = floor + 0.02;
|
||||||
site.Position = position; site.Heading = heading; site.PlacementResolved = true;
|
site.Position = position; site.Heading = heading; site.PlacementResolved = true;
|
||||||
PrintFormat("Reintroduced [terrain site]: id=%1 position=%2 heading=%3 attempts=%4", site.Id, position, heading, m_Attempts);
|
PrintFormat("Reintroduced [terrain site]: id=%1 position=%2 heading=%3 attempts=%4", site.Id, position, heading, m_Attempts);
|
||||||
|
ReportSearch(site, "placed"); ClearDiagnostics();
|
||||||
m_Index++; m_Attempts = 0; m_Began = System.GetTickCount();
|
m_Index++; m_Attempts = 0; m_Began = System.GetTickCount();
|
||||||
m_GeometryRejected = 0; m_NavigationRejected = 0; m_PadRejected = 0;
|
m_GeometryRejected = 0; m_NavigationRejected = 0; m_PadRejected = 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
// World-authored pumps survive a game-mode takeover. Their native registrations
|
||||||
|
// belong to the discarded manager and must move to the replacement on each peer.
|
||||||
|
modded class SCR_SupportStationManagerComponent
|
||||||
|
{
|
||||||
|
void ERR_CollectTerrainStations(array<SCR_BaseSupportStationComponent> stations)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < m_mSupportStations.Count(); i++)
|
||||||
|
{
|
||||||
|
array<SCR_BaseSupportStationComponent> registered = m_mSupportStations.GetElement(i);
|
||||||
|
if (!registered) continue;
|
||||||
|
foreach (SCR_BaseSupportStationComponent station : registered)
|
||||||
|
if (station && !stations.Contains(station)) stations.Insert(station);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
override void OnPostInit(IEntity owner)
|
||||||
|
{
|
||||||
|
super.OnPostInit(owner);
|
||||||
|
if (ERR_TerrainTakeoverMode.Cast(owner) && GetInstance() == this)
|
||||||
|
GetGame().ERR_RestoreTerrainSupportStations(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
modded class SCR_BaseSupportStationComponent
|
||||||
|
{
|
||||||
|
bool ERR_CanRestoreTerrainStation()
|
||||||
|
{
|
||||||
|
IEntity owner = GetOwner();
|
||||||
|
return owner && GetGame() && owner.GetWorld() == GetGame().GetWorld()
|
||||||
|
&& UsesRange() && m_eCurrentDamageState != EDamageState.DESTROYED;
|
||||||
|
}
|
||||||
|
void ERR_TerrainStationRestored(SCR_SupportStationManagerComponent manager) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
modded class SCR_BaseItemSupportStationComponent
|
||||||
|
{
|
||||||
|
override void ERR_TerrainStationRestored(SCR_SupportStationManagerComponent manager)
|
||||||
|
{
|
||||||
|
super.ERR_TerrainStationRestored(manager);
|
||||||
|
// Item cleanup must remove the station from the manager it now belongs to.
|
||||||
|
if (m_ERRItemStationInitialized && UsesRange()) m_ERRItemStationManager = manager;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
modded class ArmaReforgerScripted
|
||||||
|
{
|
||||||
|
protected ref array<SCR_BaseSupportStationComponent> m_ERRTerrainSupportStations = {};
|
||||||
|
void ERR_CaptureTerrainSupportStations(SCR_SupportStationManagerComponent original)
|
||||||
|
{
|
||||||
|
m_ERRTerrainSupportStations.Clear();
|
||||||
|
if (original) original.ERR_CollectTerrainStations(m_ERRTerrainSupportStations);
|
||||||
|
}
|
||||||
|
void ERR_RestoreTerrainSupportStations(SCR_SupportStationManagerComponent manager)
|
||||||
|
{
|
||||||
|
if (!manager) return;
|
||||||
|
int restored;
|
||||||
|
foreach (SCR_BaseSupportStationComponent station : m_ERRTerrainSupportStations)
|
||||||
|
{
|
||||||
|
if (!station || !station.ERR_CanRestoreTerrainStation()) continue;
|
||||||
|
manager.AddSupportStation(station);
|
||||||
|
station.ERR_TerrainStationRestored(manager);
|
||||||
|
restored++;
|
||||||
|
}
|
||||||
|
m_ERRTerrainSupportStations.Clear();
|
||||||
|
PrintFormat("Reintroduced [terrain support]: retained %1 existing support stations", restored);
|
||||||
|
}
|
||||||
|
override protected void OnBeforeWorldCleanup()
|
||||||
|
{
|
||||||
|
m_ERRTerrainSupportStations.Clear();
|
||||||
|
super.OnBeforeWorldCleanup();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -243,6 +243,7 @@ modded class ArmaReforgerScripted
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Print("Reintroduced [terrain takeover]: stopping original scenario; preserving radio and chat managers");
|
Print("Reintroduced [terrain takeover]: stopping original scenario; preserving radio and chat managers");
|
||||||
|
ERR_CaptureTerrainSupportStations(SCR_SupportStationManagerComponent.Cast(m_ERRTerrainOriginal.FindComponent(SCR_SupportStationManagerComponent)));
|
||||||
m_ERRTerrainOriginal.OnGameEnd();
|
m_ERRTerrainOriginal.OnGameEnd();
|
||||||
if (m_DataCollectorComponent && m_DataCollectorComponent.GetOwner() == m_ERRTerrainOriginal) m_DataCollectorComponent = null;
|
if (m_DataCollectorComponent && m_DataCollectorComponent.GetOwner() == m_ERRTerrainOriginal) m_DataCollectorComponent = null;
|
||||||
SCR_EntityCatalogManagerComponent.ERR_ReleaseTerrainCatalog(m_ERRTerrainOriginal);
|
SCR_EntityCatalogManagerComponent.ERR_ReleaseTerrainCatalog(m_ERRTerrainOriginal);
|
||||||
|
|||||||
@@ -102,6 +102,7 @@ modded class ArmaReforgerScripted
|
|||||||
m_ERRClientTerrainTakeover = true;
|
m_ERRClientTerrainTakeover = true;
|
||||||
SCR_BaseGameMode mode = SCR_BaseGameMode.Cast(m_ERRClientOldManagers[0]);
|
SCR_BaseGameMode mode = SCR_BaseGameMode.Cast(m_ERRClientOldManagers[0]);
|
||||||
PrintFormat("Reintroduced [terrain client]: drain retained mode=%1 incoming=%2", mode, incoming);
|
PrintFormat("Reintroduced [terrain client]: drain retained mode=%1 incoming=%2", mode, incoming);
|
||||||
|
ERR_CaptureTerrainSupportStations(m_ERROldSupport);
|
||||||
if (mode) mode.OnGameEnd();
|
if (mode) mode.OnGameEnd();
|
||||||
// Owner entities can already be invalid while native destruction is deferred.
|
// Owner entities can already be invalid while native destruction is deferred.
|
||||||
// Compare the captured component itself, never clear a newer singleton.
|
// Compare the captured component itself, never clear a newer singleton.
|
||||||
|
|||||||
@@ -63,6 +63,18 @@ class ERR_TerrainDisplayName
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Keep the authored Escape header as the source of mission text.
|
||||||
|
class ERR_TerrainMissionText
|
||||||
|
{
|
||||||
|
static bool Load(out string description, out string details)
|
||||||
|
{
|
||||||
|
Resource resource = BaseContainerTools.LoadContainer("{C1E20667DCE324ED}Missions/ERR_Everon.conf");
|
||||||
|
if (!resource || !resource.IsValid()) return false;
|
||||||
|
BaseContainer header = resource.GetResource().ToBaseContainer();
|
||||||
|
return header && header.Get("m_sDescription", description) && header.Get("m_sDetails", details);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Scenario selection still loads the terrain's header. Advertise the configured
|
// Scenario selection still loads the terrain's header. Advertise the configured
|
||||||
// Escape mode through the native DS name callback, then update local metadata
|
// Escape mode through the native DS name callback, then update local metadata
|
||||||
// only when the authority actually starts Escape or its bootstrap reaches a client.
|
// only when the authority actually starts Escape or its bootstrap reaches a client.
|
||||||
@@ -71,6 +83,9 @@ modded class ArmaReforgerScripted
|
|||||||
protected ref SCR_MissionHeader m_ERRPresentedTerrainHeader;
|
protected ref SCR_MissionHeader m_ERRPresentedTerrainHeader;
|
||||||
protected string m_ERROriginalTerrainTitle;
|
protected string m_ERROriginalTerrainTitle;
|
||||||
protected string m_ERROriginalTerrainGameMode;
|
protected string m_ERROriginalTerrainGameMode;
|
||||||
|
protected string m_ERROriginalTerrainDescription, m_ERROriginalTerrainDetails;
|
||||||
|
protected string m_ERRAppliedTerrainDescription, m_ERRAppliedTerrainDetails;
|
||||||
|
protected bool m_ERRTerrainTextApplied;
|
||||||
protected string m_ERRAppliedTerrainTitle;
|
protected string m_ERRAppliedTerrainTitle;
|
||||||
protected string m_ERRTitleWorld;
|
protected string m_ERRTitleWorld;
|
||||||
protected string m_ERRTerrainLabel;
|
protected string m_ERRTerrainLabel;
|
||||||
@@ -131,6 +146,14 @@ modded class ArmaReforgerScripted
|
|||||||
m_ERRAppliedTerrainTitle = ERR_TerrainMissionTitle(header);
|
m_ERRAppliedTerrainTitle = ERR_TerrainMissionTitle(header);
|
||||||
header.m_sName = m_ERRAppliedTerrainTitle;
|
header.m_sName = m_ERRAppliedTerrainTitle;
|
||||||
header.m_sGameMode = "Escape";
|
header.m_sGameMode = "Escape";
|
||||||
|
m_ERROriginalTerrainDescription = header.m_sDescription;
|
||||||
|
m_ERROriginalTerrainDetails = header.m_sDetails;
|
||||||
|
m_ERRTerrainTextApplied = ERR_TerrainMissionText.Load(m_ERRAppliedTerrainDescription, m_ERRAppliedTerrainDetails);
|
||||||
|
if (m_ERRTerrainTextApplied)
|
||||||
|
{
|
||||||
|
header.m_sDescription = m_ERRAppliedTerrainDescription;
|
||||||
|
header.m_sDetails = m_ERRAppliedTerrainDetails;
|
||||||
|
}
|
||||||
PrintFormat("Reintroduced [terrain presentation]: mission=%1; source scenario=%2", header.m_sName, header.GetHeaderResourceName());
|
PrintFormat("Reintroduced [terrain presentation]: mission=%1; source scenario=%2", header.m_sName, header.GetHeaderResourceName());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,17 +168,30 @@ modded class ArmaReforgerScripted
|
|||||||
PrintFormat("Reintroduced [terrain presentation]: refreshed mission=%1; loaded world=%2", title, GetWorldFile());
|
PrintFormat("Reintroduced [terrain presentation]: refreshed mission=%1; loaded world=%2", title, GetWorldFile());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SCR_MissionHeader ERR_TerrainLoadingHeader(SCR_MissionHeader requested)
|
||||||
|
{
|
||||||
|
if (!requested) return null;
|
||||||
|
SCR_MissionHeader header = SCR_MissionHeader.Cast(GetMissionHeader());
|
||||||
|
// A loading screen may hold a copy. Only replace the confirmed scenario;
|
||||||
|
// another incoming map must never inherit the outgoing world's text.
|
||||||
|
if (!header || header != m_ERRPresentedTerrainHeader || m_ERRPresentedTerrainWorld != GetWorld()) return requested;
|
||||||
|
if (requested != header && (requested.GetHeaderResourceName().IsEmpty() || requested.GetHeaderResourceName() != header.GetHeaderResourceName())) return requested;
|
||||||
|
ERR_RefreshTerrainPresentation();
|
||||||
|
return header;
|
||||||
|
}
|
||||||
string ERR_TerrainLoadingTitle(SCR_MissionHeader requested)
|
string ERR_TerrainLoadingTitle(SCR_MissionHeader requested)
|
||||||
{
|
{
|
||||||
if (!requested) return "";
|
SCR_MissionHeader header = ERR_TerrainLoadingHeader(requested);
|
||||||
SCR_MissionHeader header = SCR_MissionHeader.Cast(GetMissionHeader());
|
if (!header) return "";
|
||||||
// The loading screen can hold a separate copy of the same scenario header.
|
|
||||||
// Never replace another incoming scenario's title with the outgoing world.
|
|
||||||
if (!header || header != m_ERRPresentedTerrainHeader || m_ERRPresentedTerrainWorld != GetWorld()) return requested.m_sName;
|
|
||||||
if (requested != header && (requested.GetHeaderResourceName().IsEmpty() || requested.GetHeaderResourceName() != header.GetHeaderResourceName())) return requested.m_sName;
|
|
||||||
ERR_RefreshTerrainPresentation();
|
|
||||||
return header.m_sName;
|
return header.m_sName;
|
||||||
}
|
}
|
||||||
|
string ERR_TerrainLoadingDescription(SCR_MissionHeader requested)
|
||||||
|
{
|
||||||
|
SCR_MissionHeader header = ERR_TerrainLoadingHeader(requested);
|
||||||
|
if (!header) return "";
|
||||||
|
if (!header.m_sDetails.IsEmpty()) return header.m_sDetails;
|
||||||
|
return header.m_sDescription;
|
||||||
|
}
|
||||||
|
|
||||||
override void OnUpdate(BaseWorld world, float timeslice)
|
override void OnUpdate(BaseWorld world, float timeslice)
|
||||||
{
|
{
|
||||||
@@ -173,6 +209,11 @@ modded class ArmaReforgerScripted
|
|||||||
{
|
{
|
||||||
// A later change by another system belongs to that system.
|
// A later change by another system belongs to that system.
|
||||||
if (m_ERRPresentedTerrainHeader.m_sName == m_ERRAppliedTerrainTitle) m_ERRPresentedTerrainHeader.m_sName = m_ERROriginalTerrainTitle;
|
if (m_ERRPresentedTerrainHeader.m_sName == m_ERRAppliedTerrainTitle) m_ERRPresentedTerrainHeader.m_sName = m_ERROriginalTerrainTitle;
|
||||||
|
if (m_ERRTerrainTextApplied)
|
||||||
|
{
|
||||||
|
if (m_ERRPresentedTerrainHeader.m_sDescription == m_ERRAppliedTerrainDescription) m_ERRPresentedTerrainHeader.m_sDescription = m_ERROriginalTerrainDescription;
|
||||||
|
if (m_ERRPresentedTerrainHeader.m_sDetails == m_ERRAppliedTerrainDetails) m_ERRPresentedTerrainHeader.m_sDetails = m_ERROriginalTerrainDetails;
|
||||||
|
}
|
||||||
if (m_ERRPresentedTerrainHeader.m_sGameMode == "Escape") m_ERRPresentedTerrainHeader.m_sGameMode = m_ERROriginalTerrainGameMode;
|
if (m_ERRPresentedTerrainHeader.m_sGameMode == "Escape") m_ERRPresentedTerrainHeader.m_sGameMode = m_ERROriginalTerrainGameMode;
|
||||||
}
|
}
|
||||||
m_ERRPresentedTerrainHeader = null;
|
m_ERRPresentedTerrainHeader = null;
|
||||||
@@ -180,6 +221,9 @@ modded class ArmaReforgerScripted
|
|||||||
m_ERROriginalTerrainTitle = "";
|
m_ERROriginalTerrainTitle = "";
|
||||||
m_ERROriginalTerrainGameMode = "";
|
m_ERROriginalTerrainGameMode = "";
|
||||||
m_ERRAppliedTerrainTitle = "";
|
m_ERRAppliedTerrainTitle = "";
|
||||||
|
m_ERRTerrainTextApplied = false;
|
||||||
|
m_ERROriginalTerrainDescription = ""; m_ERROriginalTerrainDetails = "";
|
||||||
|
m_ERRAppliedTerrainDescription = ""; m_ERRAppliedTerrainDetails = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
override protected void OnBeforeWorldCleanup()
|
override protected void OnBeforeWorldCleanup()
|
||||||
@@ -191,28 +235,38 @@ modded class ArmaReforgerScripted
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Vanilla copies the header title once, before the replicated Escape bootstrap
|
// Vanilla copies the header title once, before the replicated Escape bootstrap
|
||||||
// may arrive. Refresh only the title; do not recreate loading widgets/hints.
|
// may arrive. Refresh text in existing widgets; do not recreate widgets/hints.
|
||||||
modded class SCR_LoadingScreenComponent
|
modded class SCR_LoadingScreenComponent
|
||||||
{
|
{
|
||||||
protected ref SCR_MissionHeader m_ERRLoadingTerrainHeader;
|
protected ref SCR_MissionHeader m_ERRLoadingTerrainHeader;
|
||||||
protected string m_ERRLoadingTerrainTitle;
|
protected string m_ERRLoadingTerrainTitle, m_ERRLoadingTerrainDescription;
|
||||||
protected float m_ERRLoadingTerrainWait;
|
protected float m_ERRLoadingTerrainWait;
|
||||||
|
|
||||||
override void OnMissionDataRetrieved(SCR_MissionHeader header)
|
override void OnMissionDataRetrieved(SCR_MissionHeader header)
|
||||||
{
|
{
|
||||||
super.OnMissionDataRetrieved(header);
|
super.OnMissionDataRetrieved(header);
|
||||||
m_ERRLoadingTerrainHeader = header;
|
m_ERRLoadingTerrainHeader = header;
|
||||||
m_ERRLoadingTerrainTitle = ""; m_ERRLoadingTerrainWait = 0;
|
m_ERRLoadingTerrainTitle = ""; m_ERRLoadingTerrainDescription = ""; m_ERRLoadingTerrainWait = 0;
|
||||||
ERR_RefreshLoadingTerrainTitle();
|
ERR_RefreshLoadingTerrainTitle();
|
||||||
}
|
}
|
||||||
protected void ERR_RefreshLoadingTerrainTitle()
|
protected void ERR_RefreshLoadingTerrainTitle()
|
||||||
{
|
{
|
||||||
if (!m_ERRLoadingTerrainHeader || !m_Widgets || !m_Widgets.m_wTitle || !GetGame()) return;
|
if (!m_ERRLoadingTerrainHeader || !m_Widgets || !GetGame()) return;
|
||||||
string title = GetGame().ERR_TerrainLoadingTitle(m_ERRLoadingTerrainHeader);
|
string title = GetGame().ERR_TerrainLoadingTitle(m_ERRLoadingTerrainHeader);
|
||||||
if (title == m_ERRLoadingTerrainTitle) return;
|
if (m_Widgets.m_wTitle && title != m_ERRLoadingTerrainTitle)
|
||||||
|
{
|
||||||
m_ERRLoadingTerrainTitle = title;
|
m_ERRLoadingTerrainTitle = title;
|
||||||
m_Widgets.m_wTitle.SetText(title);
|
m_Widgets.m_wTitle.SetText(title);
|
||||||
}
|
}
|
||||||
|
// Match vanilla details/description precedence, translation and length limit.
|
||||||
|
string description = WidgetManager.Translate(GetGame().ERR_TerrainLoadingDescription(m_ERRLoadingTerrainHeader));
|
||||||
|
if (description.Length() > MAX_DESCRIPTION_LENGTH) description = description.Substring(0, MAX_DESCRIPTION_LENGTH) + "...";
|
||||||
|
if (m_Widgets.m_wDescription && description != m_ERRLoadingTerrainDescription)
|
||||||
|
{
|
||||||
|
m_ERRLoadingTerrainDescription = description;
|
||||||
|
m_Widgets.m_wDescription.SetText(description);
|
||||||
|
}
|
||||||
|
}
|
||||||
override void Update(float timeSlice, float progress = 0, float minDurationRatio = 0)
|
override void Update(float timeSlice, float progress = 0, float minDurationRatio = 0)
|
||||||
{
|
{
|
||||||
super.Update(timeSlice, progress, minDurationRatio);
|
super.Update(timeSlice, progress, minDurationRatio);
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ modded class SCR_PlayerController
|
|||||||
bool ERR_ProbeOrbitFrame(float yaw, float pitch, float zoom, bool enabled, out vector transform[4])
|
bool ERR_ProbeOrbitFrame(float yaw, float pitch, float zoom, bool enabled, out vector transform[4])
|
||||||
{
|
{
|
||||||
string outcome;
|
string outcome;
|
||||||
if (!System.GetCLIParam("errSmokeOutcome", outcome) || outcome != "unconscious-camera" || this != GetGame().GetPlayerController() || !ERR_ProbeCameraActive() || ERR_ObservedEntity()) return false;
|
if (!System.GetCLIParam("errSmokeOutcome", outcome) || outcome != "unconscious-camera" || this != GetGame().GetPlayerController() || !ERR_ProbeCameraActive() || ERR_ObservedEntity() != GetControlledEntity() || ERR_Actors.CanFight(GetControlledEntity())) return false;
|
||||||
ERR_FollowSurvivorCameraComponent follow = ERR_ProbeFollowComponent();
|
ERR_FollowSurvivorCameraComponent follow = ERR_ProbeFollowComponent();
|
||||||
InputManager input = GetGame().GetInputManager();
|
InputManager input = GetGame().GetInputManager();
|
||||||
if (!follow || !input) return false;
|
if (!follow || !input) return false;
|
||||||
@@ -162,17 +162,30 @@ modded class ERR_GameMode
|
|||||||
foreach (SCR_PersistentDamageEffect effect : effects) m_ERRCameraDamage.TerminateDamageEffect(effect);
|
foreach (SCR_PersistentDamageEffect effect : effects) m_ERRCameraDamage.TerminateDamageEffect(effect);
|
||||||
m_ERRCameraDamage.SetGroupIsBeingHealed(ECharacterHitZoneGroup.RIGHTARM, true);
|
m_ERRCameraDamage.SetGroupIsBeingHealed(ECharacterHitZoneGroup.RIGHTARM, true);
|
||||||
}
|
}
|
||||||
protected bool ERR_CameraNoTargetOrbit(SCR_PlayerController pc)
|
protected bool ERR_CameraOwnBodyOrbit(SCR_PlayerController pc)
|
||||||
{
|
{
|
||||||
vector body = m_ERRCameraBody.GetOrigin();
|
vector body = m_ERRCameraBody.GetOrigin();
|
||||||
|
pc.ERR_UpdateObserverServer(1);
|
||||||
|
pc.ERR_UpdateObserverServer(-1);
|
||||||
|
if (!ERR_CameraCheck(pc.ERR_ObservedEntity() == m_ERRCameraBody, "solo forward/backward cycling retains the actual own body")) return false;
|
||||||
|
ERR_FollowSurvivorCameraComponent follow = pc.ERR_ProbeFollowComponent();
|
||||||
|
follow.Input(true, false, false, true);
|
||||||
|
follow.Input(true, false, false, false);
|
||||||
|
if (!ERR_CameraCheck(follow.IsPlayerView(), "own-body orbit is tested with player-view mode selected")) return false;
|
||||||
vector baseline[4], yaw[4], pitch[4], zoom[4], blocked[4];
|
vector baseline[4], yaw[4], pitch[4], zoom[4], blocked[4];
|
||||||
if (!ERR_CameraCheck(pc.ERR_ProbeOrbitFrame(0, 0, 0, true, baseline), "no-target frame accepts native orbit action values")) return false;
|
if (!ERR_CameraCheck(pc.ERR_ProbeOrbitFrame(0, 0, 0, true, baseline), "own-body frame accepts native orbit action values")) return false;
|
||||||
if (!ERR_CameraCheck(pc.ERR_ProbeOrbitFrame(1, 0, 0, true, yaw) && vector.Distance(baseline[2], yaw[2]) > 0.05, "no-target yaw action changes production camera direction")) return false;
|
if (!ERR_CameraCheck(pc.ERR_ProbeOrbitFrame(1, 0, 0, true, yaw) && vector.Distance(baseline[2], yaw[2]) > 0.05, "own-body yaw action changes production camera direction")) return false;
|
||||||
if (!ERR_CameraCheck(pc.ERR_ProbeOrbitFrame(0, -1, 0, true, pitch) && Math.AbsFloat(yaw[2][1] - pitch[2][1]) > 0.03, "no-target pitch action changes production camera elevation")) return false;
|
if (!ERR_CameraCheck(pc.ERR_ProbeOrbitFrame(0, -1, 0, true, pitch) && Math.AbsFloat(yaw[2][1] - pitch[2][1]) > 0.03, "own-body pitch action changes production camera elevation")) return false;
|
||||||
vector aim = pc.ERR_ObserverAnchor() + "0 1.5 0";
|
vector aim = m_ERRCameraBody.GetOrigin() + "0 0.4 0";
|
||||||
if (!ERR_CameraCheck(pc.ERR_ProbeOrbitFrame(0, 0, -1, true, zoom) && vector.Distance(zoom[3], aim) < vector.Distance(pitch[3], aim) - 0.05, "no-target zoom action moves production camera closer to casualty anchor")) return false;
|
if (!ERR_CameraCheck(pc.ERR_ProbeOrbitFrame(0, 0, -1, true, zoom) && vector.Distance(zoom[3], aim) < vector.Distance(pitch[3], aim) - 0.05, "own-body zoom action moves production camera closer to casualty anchor")) return false;
|
||||||
if (!ERR_CameraCheck(pc.ERR_ProbeOrbitFrame(1, -1, -1, false, blocked) && vector.Distance(zoom[3], blocked[3]) < 0.001 && vector.Distance(zoom[2], blocked[2]) < 0.001, "disabled no-target input preserves camera pose")) return false;
|
if (!ERR_CameraCheck(pc.ERR_ProbeOrbitFrame(1, -1, -1, false, blocked) && vector.Distance(zoom[3], blocked[3]) < 0.001 && vector.Distance(zoom[2], blocked[2]) < 0.001, "disabled own-body input preserves camera pose")) return false;
|
||||||
if (!ERR_CameraCheck(vector.Distance(body, m_ERRCameraBody.GetOrigin()) < 0.001 && pc.GetControlledEntity() == m_ERRCameraBody && !pc.ERR_ObservedEntity(), "no-target orbit leaves casualty and possession unchanged")) return false;
|
if (!ERR_CameraCheck(vector.Distance(body, m_ERRCameraBody.GetOrigin()) < 0.001 && pc.GetControlledEntity() == m_ERRCameraBody && pc.ERR_ObservedEntity() == m_ERRCameraBody, "own-body orbit leaves casualty and possession unchanged")) return false;
|
||||||
|
// The disabled frame starts the normal two-frame input-resume gate.
|
||||||
|
follow.Input(true, false, false, false);
|
||||||
|
follow.Input(true, false, false, false);
|
||||||
|
follow.Input(true, false, false, true);
|
||||||
|
follow.Input(true, false, false, false);
|
||||||
|
if (!ERR_CameraCheck(!follow.IsPlayerView(), "own-body camera returns to ordinary third-person selection")) return false;
|
||||||
Math3D.MatrixCopy(zoom, m_ERRCameraOrbitPose);
|
Math3D.MatrixCopy(zoom, m_ERRCameraOrbitPose);
|
||||||
Print("ERR CAMERA ORBIT: native action/frame regression; physical mouse delivery and multiplayer are not tested");
|
Print("ERR CAMERA ORBIT: native action/frame regression; physical mouse delivery and multiplayer are not tested");
|
||||||
return true;
|
return true;
|
||||||
@@ -192,6 +205,9 @@ modded class ERR_GameMode
|
|||||||
InventoryStorageManagerComponent inventory = InventoryStorageManagerComponent.Cast(m_ERRCameraBody.FindComponent(InventoryStorageManagerComponent));
|
InventoryStorageManagerComponent inventory = InventoryStorageManagerComponent.Cast(m_ERRCameraBody.FindComponent(InventoryStorageManagerComponent));
|
||||||
m_ERRCameraWeapon = ERR_Actors.Spawn("{FA5C25BF66A53DCF}Prefabs/Weapons/Rifles/AK74/Rifle_AK74.et", m_ERRCameraBody.GetOrigin()); Own(m_ERRCameraWeapon);
|
m_ERRCameraWeapon = ERR_Actors.Spawn("{FA5C25BF66A53DCF}Prefabs/Weapons/Rifles/AK74/Rifle_AK74.et", m_ERRCameraBody.GetOrigin()); Own(m_ERRCameraWeapon);
|
||||||
if (!ERR_CameraCheck(m_ERRCameraWeapon && inventory && inventory.TryInsertItem(m_ERRCameraWeapon, EStoragePurpose.PURPOSE_WEAPON_PROXY), "one physical rifle granted to the player")) return;
|
if (!ERR_CameraCheck(m_ERRCameraWeapon && inventory && inventory.TryInsertItem(m_ERRCameraWeapon, EStoragePurpose.PURPOSE_WEAPON_PROXY), "one physical rifle granted to the player")) return;
|
||||||
|
// Keep this deliberately unconscious solo fixture recoverable under the all-down rules.
|
||||||
|
IEntity injector = ERR_Actors.Spawn(ERR_EnemyMedicalCatalog.EPI, m_ERRCameraBody.GetOrigin()); Own(injector);
|
||||||
|
if (!ERR_CameraCheck(injector && inventory.TryInsertItem(injector) && Settings().Set("selfEpinephrine", 1), "carried injector keeps the camera fixture recoverable")) return;
|
||||||
m_ERRCameraStage = -1; m_ERRCameraWait = now + 1500; return;
|
m_ERRCameraStage = -1; m_ERRCameraWait = now + 1500; return;
|
||||||
}
|
}
|
||||||
SCR_CharacterControllerComponent character = SCR_CharacterControllerComponent.Cast(m_ERRCameraBody.FindComponent(SCR_CharacterControllerComponent));
|
SCR_CharacterControllerComponent character = SCR_CharacterControllerComponent.Cast(m_ERRCameraBody.FindComponent(SCR_CharacterControllerComponent));
|
||||||
@@ -208,22 +224,24 @@ modded class ERR_GameMode
|
|||||||
m_ERRCameraBody = pc.GetControlledEntity();
|
m_ERRCameraBody = pc.GetControlledEntity();
|
||||||
m_ERRCameraDamage = SCR_CharacterDamageManagerComponent.Cast(m_ERRCameraBody.FindComponent(SCR_CharacterDamageManagerComponent));
|
m_ERRCameraDamage = SCR_CharacterDamageManagerComponent.Cast(m_ERRCameraBody.FindComponent(SCR_CharacterDamageManagerComponent));
|
||||||
if (!ERR_CameraCheck(m_ERRCameraDamage && m_ERRCameraDamage.ERR_IsRevivablePlayer(), "admitted player retains treatable body")) return;
|
if (!ERR_CameraCheck(m_ERRCameraDamage && m_ERRCameraDamage.ERR_IsRevivablePlayer(), "admitted player retains treatable body")) return;
|
||||||
ERR_CameraKnockout(); m_ERRCameraStage = 1; m_ERRCameraWait = now + 3000; return;
|
// Allow the server medical sample plus the 3s spectator hint settling window.
|
||||||
|
ERR_CameraKnockout(); m_ERRCameraStage = 1; m_ERRCameraWait = now + 6000; return;
|
||||||
}
|
}
|
||||||
if (m_ERRCameraStage == 1)
|
if (m_ERRCameraStage == 1)
|
||||||
{
|
{
|
||||||
if (!ERR_CameraCheck(pc.ERR_ObserverState() == ERR_ObserverReason.UNCONSCIOUS && pc.ERR_ProbeCameraActive(), "native knockout automatically opens the manual third-person camera")) return;
|
if (!ERR_CameraCheck(pc.ERR_ObserverState() == ERR_ObserverReason.UNCONSCIOUS && pc.ERR_ProbeCameraActive(), "native knockout automatically opens the manual third-person camera")) return;
|
||||||
if (!ERR_CameraCheck(pc.GetControlledEntity() == m_ERRCameraBody && ERR_Actors.Alive(m_ERRCameraBody) && !ERR_Actors.CanFight(m_ERRCameraBody), "possession and living unconscious body unchanged")) return;
|
if (!ERR_CameraCheck(pc.GetControlledEntity() == m_ERRCameraBody && ERR_Actors.Alive(m_ERRCameraBody) && !ERR_Actors.CanFight(m_ERRCameraBody), "possession and living unconscious body unchanged")) return;
|
||||||
if (!ERR_CameraCheck(!pc.ERR_ObservedEntity() && vector.DistanceXZ(pc.ERR_ObserverAnchor(), m_ERRCameraBody.GetOrigin()) < 1, "no teammate falls back to the casualty body")) return;
|
if (!ERR_CameraCheck(pc.ERR_ObservedEntity() == m_ERRCameraBody && vector.DistanceXZ(pc.ERR_ObserverAnchor(), m_ERRCameraBody.GetOrigin()) < 1, "own casualty body is a real selected camera target")) return;
|
||||||
if (!ERR_CameraCheck(playerInventory.Contains(m_ERRCameraWeapon), "enabled retention keeps the exact rifle with the unconscious player")) return;
|
if (!ERR_CameraCheck(playerInventory.Contains(m_ERRCameraWeapon), "enabled retention keeps the exact rifle with the unconscious player")) return;
|
||||||
if (!ERR_CameraCheck(!RecoveryStatus().IsEmpty() && RecoveryStatus().Contains("Treatment or natural recovery") && pc.ERR_ProbeAcceptedRecoveryStatus() == RecoveryStatus(), "accepted observer hint contains the current ongoing recovery status")) return;
|
string expectedRecovery = RecoveryStatus() + "\n" + pc.ERR_SelfEpinephrineHint();
|
||||||
if (!ERR_CameraNoTargetOrbit(pc)) return;
|
if (!ERR_CameraCheck(RecoveryStatus().Contains("Self epinephrine is available") && pc.ERR_ProbeAcceptedRecoveryStatus() == expectedRecovery, "accepted observer hint contains the current recovery status and self-injection controls")) return;
|
||||||
|
if (!ERR_CameraOwnBodyOrbit(pc)) return;
|
||||||
m_ERRCameraStage = 11; m_ERRCameraWait = now + 500; return;
|
m_ERRCameraStage = 11; m_ERRCameraWait = now + 500; return;
|
||||||
}
|
}
|
||||||
if (m_ERRCameraStage == 11)
|
if (m_ERRCameraStage == 11)
|
||||||
{
|
{
|
||||||
vector applied[4]; GetGame().GetCameraManager().CurrentCamera().GetWorldTransform(applied);
|
vector applied[4]; GetGame().GetCameraManager().CurrentCamera().GetWorldTransform(applied);
|
||||||
if (!ERR_CameraCheck(pc.ERR_ProbeCameraActive() && vector.Distance(applied[2], m_ERRCameraOrbitPose[2]) < 0.01, "native camera applies and retains no-target orbit direction on subsequent frames")) return;
|
if (!ERR_CameraCheck(pc.ERR_ProbeCameraActive() && vector.Distance(applied[2], m_ERRCameraOrbitPose[2]) < 0.01, "native camera applies and retains own-body orbit direction on subsequent frames")) return;
|
||||||
if (!ERR_CameraOpenChat(pc, false)) return;
|
if (!ERR_CameraOpenChat(pc, false)) return;
|
||||||
m_ERRCameraStage = 111; m_ERRCameraWait = now + 500; return;
|
m_ERRCameraStage = 111; m_ERRCameraWait = now + 500; return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
// Bound native AI horn requests without changing steering, braking or player input.
|
||||||
|
class ERR_AIDriverHornLimit
|
||||||
|
{
|
||||||
|
protected int m_End = -1;
|
||||||
|
protected int m_Next = 0;
|
||||||
|
protected int m_Last = -1;
|
||||||
|
bool Allow(bool requested, int now)
|
||||||
|
{
|
||||||
|
if (m_Last >= 0 && now < m_Last) { m_End = -1; m_Next = now + 30000; }
|
||||||
|
m_Last = now;
|
||||||
|
if (!requested) { m_End = -1; return false; }
|
||||||
|
if (m_End >= 0)
|
||||||
|
{
|
||||||
|
if (now < m_End) return true;
|
||||||
|
m_End = -1;
|
||||||
|
}
|
||||||
|
if (now < m_Next) return false;
|
||||||
|
m_End = now + 500;
|
||||||
|
m_Next = now + 30000;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
modded class SCR_CharacterControllerComponent
|
||||||
|
{
|
||||||
|
protected ref ERR_AIDriverHornLimit m_ERRHornLimit;
|
||||||
|
protected override void OnApplyControls(IEntity owner, float timeSlice)
|
||||||
|
{
|
||||||
|
super.OnApplyControls(owner, timeSlice);
|
||||||
|
if (!Replication.IsServer() || !owner || ERR_Actors.Player(owner)) return;
|
||||||
|
ERR_GameMode mode = ERR_GameMode.Get();
|
||||||
|
if (!mode) return;
|
||||||
|
IEntity vehicle = CompartmentAccessComponent.GetVehicleIn(owner);
|
||||||
|
if (!vehicle || !vehicle.FindComponent(AICarMovementComponent)) return;
|
||||||
|
BaseVehicleControllerComponent controls = BaseVehicleControllerComponent.Cast(vehicle.FindComponent(BaseVehicleControllerComponent));
|
||||||
|
if (!controls || !controls.GetPilotCompartmentSlot() || controls.GetPilotCompartmentSlot().GetOccupant() != owner) return;
|
||||||
|
CharacterInputContext input = GetInputContext();
|
||||||
|
if (!input) return;
|
||||||
|
if (!m_ERRHornLimit) m_ERRHornLimit = new ERR_AIDriverHornLimit();
|
||||||
|
bool permitted = m_ERRHornLimit.Allow(mode.Active() && input.GetVehicleHorn() != 0, System.GetTickCount());
|
||||||
|
if (!permitted) input.SetVehicleHorn(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -82,6 +82,7 @@ class ERR_CivilianTraffic
|
|||||||
{
|
{
|
||||||
protected ERR_GameMode m_Mode;
|
protected ERR_GameMode m_Mode;
|
||||||
protected ref array<ref ERR_CivilianCar> m_Cars = {};
|
protected ref array<ref ERR_CivilianCar> m_Cars = {};
|
||||||
|
protected ref ERR_ParkedCarCatalog m_Catalog = new ERR_ParkedCarCatalog();
|
||||||
protected float m_Next;
|
protected float m_Next;
|
||||||
protected float m_NextSpawn;
|
protected float m_NextSpawn;
|
||||||
protected int m_Serial;
|
protected int m_Serial;
|
||||||
@@ -205,20 +206,31 @@ class ERR_CivilianTraffic
|
|||||||
m_PlayerCursor = m_PlayerCursor % observers.Count();
|
m_PlayerCursor = m_PlayerCursor % observers.Count();
|
||||||
vector centre = observers[m_PlayerCursor++];
|
vector centre = observers[m_PlayerCursor++];
|
||||||
if (!Plan(centre, observers, actors, route, angles)) return false;
|
if (!Plan(centre, observers, actors, route, angles)) return false;
|
||||||
ResourceName prefab = CarPrefab(Math.RandomInt(0, 3));
|
m_Catalog.Find(false);
|
||||||
|
// Keep the transport van in traffic; premium Rally cars stay in parked pools.
|
||||||
|
ResourceName prefab = CarPrefab(2);
|
||||||
|
if (Math.RandomInt(0, 5) != 0 || !ERR_VehicleSelection.RoleAllowed(prefab, 1)) prefab = ERR_VehicleSelection.Choose(m_Catalog.RoadPrefabs, 1);
|
||||||
|
if (prefab.IsEmpty() && ERR_VehicleSelection.RoleAllowed(CarPrefab(2), 1)) prefab = CarPrefab(2);
|
||||||
|
prefab = ERR_VehicleSelection.ForRole(1, prefab);
|
||||||
|
if (prefab.IsEmpty()) return false;
|
||||||
array<ResourceName> required = {prefab, DriverPrefab(), ERR_ConvoyResources.GroupPrefab(), ERR_Resources.MoveWaypoint()};
|
array<ResourceName> required = {prefab, DriverPrefab(), ERR_ConvoyResources.GroupPrefab(), ERR_Resources.MoveWaypoint()};
|
||||||
foreach (ResourceName name : required) { Resource loaded = Resource.Load(name); if (!loaded || !loaded.IsValid()) return false; }
|
foreach (ResourceName name : required) { Resource loaded = Resource.Load(name); if (!loaded || !loaded.IsValid()) return false; }
|
||||||
// Loading can take time: check every observer again immediately before spawn.
|
// Loading can take time: check every observer again immediately before spawn.
|
||||||
if (!Observers(observers, actors) || observers.IsEmpty() || !ERR_CivilianTrafficRules.Far(route[0], observers, 1000) || !Hidden(route[0], actors)) return false;
|
if (!Observers(observers, actors) || observers.IsEmpty() || !ERR_CivilianTrafficRules.Far(route[0], observers, 1000) || !Hidden(route[0], actors)) return false;
|
||||||
ERR_ConvoyPlacement finalPlacement = new ERR_ConvoyPlacement();
|
ERR_ConvoyPlacement finalPlacement = new ERR_ConvoyPlacement();
|
||||||
if (!finalPlacement.Clear(route[0], angles[0])) return false;
|
if (!finalPlacement.Clear(route[0], angles[0]) || !ERR_VehicleSelection.RoleAllowed(prefab, 1)) return false;
|
||||||
ERR_CivilianCar traffic = new ERR_CivilianCar(); traffic.Route.Copy(route);
|
ERR_CivilianCar traffic = new ERR_CivilianCar(); traffic.Route.Copy(route);
|
||||||
traffic.Number = m_Serial + 1; traffic.Prefab = prefab;
|
traffic.Number = m_Serial + 1; traffic.Prefab = prefab;
|
||||||
traffic.Deadline = now + 120000; traffic.ProgressAt = now; traffic.ProgressPosition = route[0];
|
traffic.Deadline = now + 120000; traffic.ProgressAt = now; traffic.ProgressPosition = route[0];
|
||||||
traffic.Car = Vehicle.Cast(ERR_Actors.Spawn(prefab, route[0], angles));
|
traffic.Car = Vehicle.Cast(ERR_Actors.Spawn(prefab, route[0], angles));
|
||||||
|
m_Serial++; // Rejected mod vehicles still consume one of the 64 native creation attempts.
|
||||||
|
if (!ERR_VehicleSelection.RoadFit(traffic.Car))
|
||||||
|
{
|
||||||
|
GetGame().ERR_ServerVehicles().Reject(prefab, 1); ERR_Actors.DeleteSpawned(traffic.Car); return false;
|
||||||
|
}
|
||||||
traffic.Group = SCR_AIGroup.Cast(ERR_Actors.Spawn(ERR_ConvoyResources.GroupPrefab(), route[0]));
|
traffic.Group = SCR_AIGroup.Cast(ERR_Actors.Spawn(ERR_ConvoyResources.GroupPrefab(), route[0]));
|
||||||
// Retain failed partial creations for the same distant-only cleanup policy.
|
// Retain failed partial creations for the same distant-only cleanup policy.
|
||||||
m_Cars.Insert(traffic); m_Serial++; traffic.Protect(true);
|
m_Cars.Insert(traffic); traffic.Protect(true);
|
||||||
Faction civilian = GetGame().GetFactionManager().GetFactionByKey("CIV");
|
Faction civilian = GetGame().GetFactionManager().GetFactionByKey("CIV");
|
||||||
if (traffic.Group) { traffic.Group.SetFaction(civilian); traffic.Group.SetDeleteWhenEmpty(false); }
|
if (traffic.Group) { traffic.Group.SetFaction(civilian); traffic.Group.SetDeleteWhenEmpty(false); }
|
||||||
if (!traffic.Car || !ERR_ConvoyRules.EmptyCrewTemplate(traffic.Group)) { traffic.Finish("creation failed: vehicle or crew group"); return false; }
|
if (!traffic.Car || !ERR_ConvoyRules.EmptyCrewTemplate(traffic.Group)) { traffic.Finish("creation failed: vehicle or crew group"); return false; }
|
||||||
@@ -227,7 +239,7 @@ class ERR_CivilianTraffic
|
|||||||
if (!ERR_ConvoyRules.CollectSeats(traffic.Car, seats)) { traffic.Finish("creation failed: compartments unavailable"); return false; }
|
if (!ERR_ConvoyRules.CollectSeats(traffic.Car, seats)) { traffic.Finish("creation failed: compartments unavailable"); return false; }
|
||||||
foreach (BaseCompartmentSlot seat : seats)
|
foreach (BaseCompartmentSlot seat : seats)
|
||||||
if (seat.GetType() == ECompartmentType.PILOT && seat.IsCompartmentAccessible() && !seat.GetOccupant()) { traffic.Seat = seat; break; }
|
if (seat.GetType() == ECompartmentType.PILOT && seat.IsCompartmentAccessible() && !seat.GetOccupant()) { traffic.Seat = seat; break; }
|
||||||
if (!traffic.Seat) { traffic.Finish("creation failed: no accessible driver seat"); return false; }
|
if (!traffic.Seat) { GetGame().ERR_ServerVehicles().Reject(prefab, 1); traffic.Finish("creation failed: no accessible driver seat"); return false; }
|
||||||
AIGroup group = traffic.Group;
|
AIGroup group = traffic.Group;
|
||||||
traffic.Driver = traffic.Seat.SpawnCharacterInCompartment(DriverPrefab(), group, ERR_ConvoyResources.GroupPrefab());
|
traffic.Driver = traffic.Seat.SpawnCharacterInCompartment(DriverPrefab(), group, ERR_ConvoyResources.GroupPrefab());
|
||||||
traffic.Protect(true);
|
traffic.Protect(true);
|
||||||
|
|||||||
@@ -404,6 +404,16 @@ class ERR_ConvoyService
|
|||||||
ERR_ConvoyTask Dispatch(ERR_ConvoyReceipt receipt, array<vector> positions, array<vector> angles, ERR_ForceRoute route, float now)
|
ERR_ConvoyTask Dispatch(ERR_ConvoyReceipt receipt, array<vector> positions, array<vector> angles, ERR_ForceRoute route, float now)
|
||||||
{
|
{
|
||||||
if (!Current() || !ERR_ForceRules.Finite(now) || now < 0 || !ERR_ConvoyRules.Plan(positions, angles, route) || !ERR_ConvoyResources.Available() || !ERR_ConvoyRules.GroundClear(positions, angles)) return null;
|
if (!Current() || !ERR_ForceRules.Finite(now) || now < 0 || !ERR_ConvoyRules.Plan(positions, angles, route) || !ERR_ConvoyResources.Available() || !ERR_ConvoyRules.GroundClear(positions, angles)) return null;
|
||||||
|
// Choose once before consuming a finite receipt. Empty assigned pools defer dispatch.
|
||||||
|
array<ResourceName> selected = {};
|
||||||
|
for (int slot = 0; slot < 6; slot++)
|
||||||
|
{
|
||||||
|
ResourceName prefab = ERR_ConvoyResources.VehiclePrefab(slot);
|
||||||
|
if (slot == 1) prefab = ERR_VehicleSelection.ForRole(4, prefab);
|
||||||
|
if (slot == 0 || slot == 3 || slot == 5) prefab = ERR_VehicleSelection.ForRole(3, prefab);
|
||||||
|
if (slot != 2 && prefab.IsEmpty()) return null;
|
||||||
|
selected.Insert(prefab);
|
||||||
|
}
|
||||||
// The server role selection owns the memberless group affiliation.
|
// The server role selection owns the memberless group affiliation.
|
||||||
if (!m_Mode.EnemyFaction() || !m_Pool.Consume(receipt)) return null;
|
if (!m_Mode.EnemyFaction() || !m_Pool.Consume(receipt)) return null;
|
||||||
ERR_ConvoyTask task = NewTask(); task.Receipt = receipt; task.Route = route; task.Deadline = now + 30000;
|
ERR_ConvoyTask task = NewTask(); task.Receipt = receipt; task.Route = route; task.Deadline = now + 30000;
|
||||||
@@ -414,10 +424,13 @@ class ERR_ConvoyService
|
|||||||
if (index == 2) { unit.Released = true; continue; }
|
if (index == 2) { unit.Released = true; continue; }
|
||||||
unit.Group = SCR_AIGroup.Cast(ERR_Actors.Spawn(ERR_ConvoyResources.GroupPrefab(), positions[index]));
|
unit.Group = SCR_AIGroup.Cast(ERR_Actors.Spawn(ERR_ConvoyResources.GroupPrefab(), positions[index]));
|
||||||
if (unit.Group) m_Mode.Own(unit.Group);
|
if (unit.Group) m_Mode.Own(unit.Group);
|
||||||
unit.Entity = Vehicle.Cast(ERR_Actors.Spawn(ERR_ConvoyResources.VehiclePrefab(index), positions[index], angles[index]));
|
unit.Entity = Vehicle.Cast(ERR_Actors.Spawn(selected[index], positions[index], angles[index]));
|
||||||
if (unit.Entity) m_Mode.Own(unit.Entity);
|
if (unit.Entity) m_Mode.Own(unit.Entity);
|
||||||
if (!unit.Group || !unit.Entity || unit.Group.GetFaction() != m_Mode.EnemyFaction() || !ERR_ConvoyRules.EmptyCrewTemplate(unit.Group) || !FillCrew(unit))
|
bool assigned = selected[index] != ERR_ConvoyResources.VehiclePrefab(index);
|
||||||
{ task.Stop(m_Mode, "finite convoy startup failed; receipt consumed without retry"); return task; }
|
bool compatible = !assigned || (ERR_VehicleSelection.RoadFit(unit.Entity) && (index != 1 || unit.Entity.FindComponent(InventoryStorageManagerComponent)));
|
||||||
|
int role = 3; if (index == 1) role = 4;
|
||||||
|
if (!compatible || !unit.Group || !unit.Entity || unit.Group.GetFaction() != m_Mode.EnemyFaction() || !ERR_ConvoyRules.EmptyCrewTemplate(unit.Group) || !FillCrew(unit))
|
||||||
|
{ if (assigned) GetGame().ERR_ServerVehicles().Reject(selected[index], role); task.Stop(m_Mode, "finite convoy startup failed; receipt consumed without retry"); return task; }
|
||||||
FactionAffiliationComponent affiliation = FactionAffiliationComponent.Cast(unit.Entity.FindComponent(FactionAffiliationComponent));
|
FactionAffiliationComponent affiliation = FactionAffiliationComponent.Cast(unit.Entity.FindComponent(FactionAffiliationComponent));
|
||||||
if (affiliation) affiliation.SetAffiliatedFaction(m_Mode.EnemyFaction());
|
if (affiliation) affiliation.SetAffiliatedFaction(m_Mode.EnemyFaction());
|
||||||
AICarMovementComponent movement = AICarMovementComponent.Cast(unit.Entity.FindComponent(AICarMovementComponent));
|
AICarMovementComponent movement = AICarMovementComponent.Cast(unit.Entity.FindComponent(AICarMovementComponent));
|
||||||
|
|||||||
@@ -0,0 +1,415 @@
|
|||||||
|
// Nearby garages and residential yards are discovered incrementally. Each callback performs one
|
||||||
|
// survey, maintenance, planning, resource-load or native-spawn step.
|
||||||
|
class ERR_GarageVehicleRules
|
||||||
|
{
|
||||||
|
static bool Garage(string path)
|
||||||
|
{
|
||||||
|
path.ToLower();
|
||||||
|
return path.EndsWith("/garage_e_01.et") || path.EndsWith("/houseaddon_garage_e_01.et");
|
||||||
|
}
|
||||||
|
static bool Envelope(vector low, vector high)
|
||||||
|
{
|
||||||
|
return ERR_ForceRules.FiniteVector(low) && ERR_ForceRules.FiniteVector(high)
|
||||||
|
&& low[0] >= -1.05 && high[0] <= 1.05 && low[2] >= -2.4 && high[2] <= 2.4
|
||||||
|
&& low[1] >= -0.3 && high[1] <= 1.9
|
||||||
|
&& high[0] > low[0] && high[1] > low[1] && high[2] > low[2];
|
||||||
|
}
|
||||||
|
static bool Yard(string path)
|
||||||
|
{
|
||||||
|
path.ToLower();
|
||||||
|
return path.EndsWith("/house_village_e_1i01.et") || path.EndsWith("/house_village_e_1i01_beige.et")
|
||||||
|
|| path.EndsWith("/house_village_e_1i01_v2.et") || path.EndsWith("/house_village_e_1i01_beige_v2.et")
|
||||||
|
|| path.EndsWith("/house_village_e_1i02.et") || path.EndsWith("/house_village_e_1i02_white.et")
|
||||||
|
|| path.EndsWith("/house_village_e_1i03.et") || path.EndsWith("/house_village_e_1i03_yellow.et")
|
||||||
|
|| path.EndsWith("/house_village_e_1i06.et") || path.EndsWith("/house_village_e_1i06_brown.et")
|
||||||
|
|| path.EndsWith("/house_village_e_1i06_white.et");
|
||||||
|
}
|
||||||
|
static bool Farm(string path)
|
||||||
|
{
|
||||||
|
path.ToLower();
|
||||||
|
return path.EndsWith("/barn_e_01.et") || path.EndsWith("/barn_e_01_old.et")
|
||||||
|
|| path.EndsWith("/barn_e_01_white.et") || path.EndsWith("/barn_e_02.et")
|
||||||
|
|| path.EndsWith("/barn_e_03_closed.et") || path.EndsWith("/barn_e_03_open.et")
|
||||||
|
|| path.EndsWith("/cowshed_e_01_short.et") || path.EndsWith("/cowshed_e_01_long.et");
|
||||||
|
}
|
||||||
|
static bool FarmEnvelope(vector low, vector high)
|
||||||
|
{
|
||||||
|
return ERR_ForceRules.FiniteVector(low) && ERR_ForceRules.FiniteVector(high)
|
||||||
|
&& low[0] >= -1.45 && high[0] <= 1.45 && low[2] >= -2.95 && high[2] <= 2.95
|
||||||
|
&& low[1] >= -0.3 && high[1] <= 3.1
|
||||||
|
&& high[0] > low[0] && high[1] > low[1] && high[2] > low[2];
|
||||||
|
}
|
||||||
|
static vector YardOffset(vector low, vector high, int side, float clearance = 3)
|
||||||
|
{
|
||||||
|
vector centre = (low + high) * 0.5; centre[1] = 0;
|
||||||
|
// Separate farm clearance leaves room beside a taller, wider tractor.
|
||||||
|
if (side == 0) centre[0] = low[0] - clearance;
|
||||||
|
else if (side == 1) centre[0] = high[0] + clearance;
|
||||||
|
else if (side == 2) centre[2] = low[2] - clearance;
|
||||||
|
else centre[2] = high[2] + clearance;
|
||||||
|
return centre;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
class ERR_GarageSite
|
||||||
|
{
|
||||||
|
IEntity Garage;
|
||||||
|
bool Consumed;
|
||||||
|
bool Selected;
|
||||||
|
float NextTry;
|
||||||
|
bool Yard;
|
||||||
|
bool Farm;
|
||||||
|
int Side;
|
||||||
|
}
|
||||||
|
class ERR_ParkedVehicle
|
||||||
|
{
|
||||||
|
Vehicle Entity;
|
||||||
|
ref ERR_GarageSite Site;
|
||||||
|
vector Origin;
|
||||||
|
float HiddenSince = -1;
|
||||||
|
}
|
||||||
|
class ERR_GarageVehicles
|
||||||
|
{
|
||||||
|
protected ERR_GameMode m_Mode;
|
||||||
|
protected ref array<ref ERR_GarageSite> m_Sites = {};
|
||||||
|
protected ref array<ref ERR_ParkedVehicle> m_Parked = {};
|
||||||
|
protected ref map<string, bool> m_KnownCells = new map<string, bool>();
|
||||||
|
protected ref ERR_MotorbikeCatalog m_Bikes = new ERR_MotorbikeCatalog();
|
||||||
|
protected ref ERR_ParkedCarCatalog m_Cars = new ERR_ParkedCarCatalog();
|
||||||
|
protected ref array<ResourceName> m_LoadedNames = {};
|
||||||
|
protected ref array<ref Resource> m_LoadedResources = {};
|
||||||
|
protected ref ERR_GarageSite m_Pending;
|
||||||
|
protected vector m_Position, m_Angles;
|
||||||
|
protected ResourceName m_Prefab;
|
||||||
|
protected bool m_PendingBike;
|
||||||
|
protected int m_Stage, m_Work, m_SiteCursor, m_MaintainCursor, m_PlayerCursor;
|
||||||
|
protected int m_Requested, m_Spawned, m_Claimed, m_BikeCandidate;
|
||||||
|
protected int m_GarageChance, m_SpawnRadius, m_SpawnInterval, m_CleanupRadius;
|
||||||
|
protected float m_NextSpawn, m_NextStep;
|
||||||
|
protected bool m_Stopped, m_Started;
|
||||||
|
int Spawned() { return m_Spawned; }
|
||||||
|
void ERR_GarageVehicles(ERR_GameMode mode) { m_Mode = mode; }
|
||||||
|
protected float Now() { return GetGame().GetWorld().GetWorldTime(); }
|
||||||
|
protected bool Running() { return m_Mode && m_Mode == ERR_GameMode.Get() && m_Mode.Active(); }
|
||||||
|
void Start()
|
||||||
|
{
|
||||||
|
if (!Replication.IsServer() || m_Stopped || m_Started) return;
|
||||||
|
m_Started = true; m_Requested = m_Mode.Settings().Get("garageVehicleCount");
|
||||||
|
m_GarageChance = m_Mode.Settings().Get("garageChancePercent");
|
||||||
|
m_SpawnRadius = m_Mode.Settings().Get("garageRadiusMetres");
|
||||||
|
m_SpawnInterval = m_Mode.Settings().Get("garageSpawnIntervalSeconds") * 1000;
|
||||||
|
m_CleanupRadius = Math.Max(1400, m_SpawnRadius + 400);
|
||||||
|
if (m_Requested <= 0 || m_GarageChance <= 0) { Stop(); return; }
|
||||||
|
m_Bikes.Find();
|
||||||
|
m_Cars.Find();
|
||||||
|
GetGame().GetCallqueue().CallLater(Step, 250, true);
|
||||||
|
}
|
||||||
|
protected bool Observers(array<vector> positions, array<IEntity> actors)
|
||||||
|
{
|
||||||
|
positions.Clear(); actors.Clear();
|
||||||
|
array<int> ids = {}; GetGame().GetPlayerManager().GetPlayers(ids);
|
||||||
|
SCR_EditorManagerCore editors = SCR_EditorManagerCore.Cast(SCR_EditorManagerCore.GetInstance(SCR_EditorManagerCore));
|
||||||
|
foreach (int id : ids)
|
||||||
|
{
|
||||||
|
SCR_PlayerController controller = SCR_PlayerController.Cast(GetGame().GetPlayerManager().GetPlayerController(id));
|
||||||
|
if (!controller) return false;
|
||||||
|
SCR_EditorManagerEntity editor; if (editors) editor = editors.GetEditorManager(id);
|
||||||
|
if (editor && editor.IsOpened()) return false;
|
||||||
|
IEntity body = controller.GetControlledEntity();
|
||||||
|
if (body)
|
||||||
|
{
|
||||||
|
if (!ERR_ForceRules.FiniteVector(body.GetOrigin())) return false;
|
||||||
|
positions.Insert(body.GetOrigin()); actors.Insert(body);
|
||||||
|
}
|
||||||
|
if (controller.ERR_ObserverState() != ERR_ObserverReason.NONE)
|
||||||
|
{
|
||||||
|
IEntity watched = controller.ERR_ObservedEntity();
|
||||||
|
if (!watched || !ERR_ForceRules.FiniteVector(watched.GetOrigin())) return false;
|
||||||
|
positions.Insert(watched.GetOrigin()); actors.Insert(watched);
|
||||||
|
}
|
||||||
|
else if (!body) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
protected bool Nearby(vector position, array<vector> observers, float radius)
|
||||||
|
{
|
||||||
|
foreach (vector observer : observers) if (vector.DistanceXZ(position, observer) <= radius) return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Sample both ends/sides and roof, not just the hidden centre of a garage.
|
||||||
|
// Open doors and windows can reveal a bay; no distance alone proves it hidden.
|
||||||
|
protected bool Hidden(vector position, vector angles, array<IEntity> actors, IEntity ignored = null, bool farm = false)
|
||||||
|
{
|
||||||
|
float halfWidth = 1.1, halfLength = 2.5, roofStep = 1.3;
|
||||||
|
if (farm) { halfWidth = 1.5; halfLength = 3; roofStep = 2.5; }
|
||||||
|
vector matrix[4]; Math3D.AnglesToMatrix(angles, matrix);
|
||||||
|
foreach (IEntity actor : actors)
|
||||||
|
{
|
||||||
|
if (!actor) return false;
|
||||||
|
array<IEntity> excluded = {actor};
|
||||||
|
IEntity ride = CompartmentAccessComponent.GetVehicleIn(actor); if (ride) excluded.Insert(ride);
|
||||||
|
for (int height = 0; height < 2; height++)
|
||||||
|
for (int x = -1; x <= 1; x++)
|
||||||
|
for (int z = -1; z <= 1; z++)
|
||||||
|
{
|
||||||
|
TraceParam ray = new TraceParam();
|
||||||
|
ray.Start = actor.GetOrigin() + "0 1.7 0";
|
||||||
|
ray.End = position + matrix[0] * (x * halfWidth) + matrix[2] * (z * halfLength) + Vector(0, 0.5 + height * roofStep, 0);
|
||||||
|
ray.Flags = TraceFlags.WORLD | TraceFlags.ENTS; ray.LayerMask = TRACE_LAYER_CAMERA;
|
||||||
|
ray.ExcludeArray = excluded;
|
||||||
|
float fraction = GetGame().GetWorld().TraceMove(ray, null);
|
||||||
|
if (!ERR_ForceRules.Finite(fraction) || fraction >= 0.995 || (ignored && ERR_ConvoyRules.PartOf(ray.TraceEnt, ignored))) return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
protected bool Filter(IEntity entity) { return entity && SCR_DestructibleBuildingEntity.Cast(entity); }
|
||||||
|
protected bool Remember(IEntity entity)
|
||||||
|
{
|
||||||
|
if (m_Sites.Count() >= 2048) return false;
|
||||||
|
if (!entity.GetPrefabData()) return true;
|
||||||
|
string prefab = entity.GetPrefabData().GetPrefabName();
|
||||||
|
bool farm = !m_Cars.Tractors.IsEmpty() && ERR_GarageVehicleRules.Farm(prefab);
|
||||||
|
bool yard = farm || ERR_GarageVehicleRules.Yard(prefab);
|
||||||
|
if (!yard && !ERR_GarageVehicleRules.Garage(prefab)) return true;
|
||||||
|
foreach (ERR_GarageSite site : m_Sites) if (site.Garage == entity) return true;
|
||||||
|
ERR_GarageSite added = new ERR_GarageSite(); added.Garage = entity;
|
||||||
|
added.Yard = yard; added.Farm = farm; added.Side = Math.RandomInt(0, 4);
|
||||||
|
int chance = m_GarageChance; if (farm) chance = Math.Min(chance, 15);
|
||||||
|
added.Selected = Math.RandomInt(0, 100) < chance;
|
||||||
|
m_Sites.Insert(added);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
protected string CellKey(vector point) { return Math.Floor(point[0] / 500).ToString() + ":" + Math.Floor(point[2] / 500).ToString(); }
|
||||||
|
protected void Discover(array<vector> observers, float now)
|
||||||
|
{
|
||||||
|
if (observers.IsEmpty() || m_Sites.Count() >= 2048 || m_KnownCells.Count() >= 16384) return;
|
||||||
|
// Round-robin observers, nearest unvisited cell first. Each update queries
|
||||||
|
// at most one 500m cell; teammates share already scanned cells.
|
||||||
|
if (m_PlayerCursor >= observers.Count()) m_PlayerCursor = 0;
|
||||||
|
vector observer = observers[m_PlayerCursor++];
|
||||||
|
int cx = Math.Floor(observer[0] / 500), cz = Math.Floor(observer[2] / 500);
|
||||||
|
for (int ring = 0; ring <= Math.Ceil(m_SpawnRadius / 500.0); ring++)
|
||||||
|
for (int x = -ring; x <= ring; x++)
|
||||||
|
for (int z = -ring; z <= ring; z++)
|
||||||
|
{
|
||||||
|
if (Math.Max(Math.AbsInt(x), Math.AbsInt(z)) != ring) continue;
|
||||||
|
vector low = Vector((cx + x) * 500, 0, (cz + z) * 500);
|
||||||
|
string key = CellKey(low);
|
||||||
|
if (m_KnownCells.Contains(key) || !Nearby(low + "250 0 250", observers, m_CleanupRadius)) continue;
|
||||||
|
m_KnownCells.Set(key, true);
|
||||||
|
vector boundsLow, boundsHigh; GetGame().GetWorldEntity().GetWorldBounds(boundsLow, boundsHigh);
|
||||||
|
low[1] = boundsLow[1] - 100; vector high = low + "500 0 500"; high[1] = boundsHigh[1] + 100;
|
||||||
|
GetGame().GetWorld().QueryEntitiesByAABB(low, high, Remember, Filter, EQueryEntitiesFlags.ALL);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
protected bool InUse(ERR_ParkedVehicle parked)
|
||||||
|
{
|
||||||
|
if (!parked.Entity) return false;
|
||||||
|
// Movement also protects a towed, pushed or otherwise used car, even if a
|
||||||
|
// compartment callback was missed. Never reclaim somebody's transport.
|
||||||
|
if (vector.Distance(parked.Entity.GetOrigin(), parked.Origin) > 3) return true;
|
||||||
|
array<BaseCompartmentSlot> seats = {};
|
||||||
|
if (!ERR_ConvoyRules.CollectSeats(parked.Entity, seats)) return true;
|
||||||
|
foreach (BaseCompartmentSlot seat : seats) if (seat.GetOccupant()) return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
protected void Claim(int index)
|
||||||
|
{
|
||||||
|
ERR_ParkedVehicle parked = m_Parked[index];
|
||||||
|
parked.Site.Consumed = true;
|
||||||
|
if (parked.Entity) m_Mode.Own(parked.Entity);
|
||||||
|
m_Claimed++; m_Parked.Remove(index);
|
||||||
|
}
|
||||||
|
void PlayerEntered(IEntity actor, IEntity entered)
|
||||||
|
{
|
||||||
|
if (m_Stopped || !Replication.IsServer() || !ERR_Actors.Player(actor)) return;
|
||||||
|
for (int i = m_Parked.Count() - 1; i >= 0; i--)
|
||||||
|
if (m_Parked[i].Entity && ERR_ConvoyRules.PartOf(entered, m_Parked[i].Entity)) Claim(i);
|
||||||
|
}
|
||||||
|
protected void Maintain(array<vector> observers, array<IEntity> actors, float now, bool known)
|
||||||
|
{
|
||||||
|
if (m_Parked.IsEmpty()) return;
|
||||||
|
if (m_MaintainCursor >= m_Parked.Count()) m_MaintainCursor = 0;
|
||||||
|
int index = m_MaintainCursor++; ERR_ParkedVehicle parked = m_Parked[index];
|
||||||
|
if (!parked.Entity) { parked.Site.Consumed = true; m_Parked.Remove(index); return; }
|
||||||
|
if (InUse(parked)) { Claim(index); return; }
|
||||||
|
if (!known || !ERR_CivilianTrafficRules.Far(parked.Entity.GetOrigin(), observers, m_CleanupRadius)
|
||||||
|
|| !Hidden(parked.Entity.GetOrigin(), parked.Entity.GetYawPitchRoll(), actors, parked.Entity, parked.Site.Farm)) { parked.HiddenSince = -1; return; }
|
||||||
|
if (parked.HiddenSince < 0) parked.HiddenSince = now;
|
||||||
|
if (!ERR_CivilianTrafficRules.CleanupDue(now, parked.HiddenSince)) return;
|
||||||
|
// Recheck occupancy immediately before deletion. Claimed cars are no longer
|
||||||
|
// in this collection and cannot pass through the retirement path.
|
||||||
|
if (InUse(parked)) { Claim(index); return; }
|
||||||
|
SCR_GarbageSystem garbage = SCR_GarbageSystem.GetByEntityWorld(parked.Entity);
|
||||||
|
if (garbage) garbage.UpdateBlacklist(parked.Entity, false);
|
||||||
|
ERR_Actors.DeleteSpawned(parked.Entity); m_Parked.Remove(index);
|
||||||
|
}
|
||||||
|
protected bool Occupied(ERR_GarageSite site)
|
||||||
|
{
|
||||||
|
foreach (ERR_ParkedVehicle parked : m_Parked) if (parked.Site == site) return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
protected bool YardGround(vector position, vector transform[4], bool farm = false)
|
||||||
|
{
|
||||||
|
SCR_AIWorld world = SCR_AIWorld.Cast(GetGame().GetAIWorld());
|
||||||
|
if (!world || !world.GetRoadNetworkManager()) return false;
|
||||||
|
BaseRoad road; float distance;
|
||||||
|
world.GetRoadNetworkManager().GetClosestRoad(position, road, distance, skipNavlinks: true);
|
||||||
|
if (road && (!ERR_ForceRules.Finite(distance) || distance < road.GetWidth() * 0.5 + 4)) return false;
|
||||||
|
float halfWidth = 1.1, halfLength = 2.5;
|
||||||
|
if (farm) { halfWidth = 1.5; halfLength = 3; }
|
||||||
|
float ground = SCR_TerrainHelper.GetTerrainY(position);
|
||||||
|
for (int x = -1; x <= 1; x++)
|
||||||
|
for (int z = -1; z <= 1; z++)
|
||||||
|
{
|
||||||
|
vector sample = position + transform[0] * (x * halfWidth) + transform[2] * (z * halfLength);
|
||||||
|
if (!ERR_Actors.DryGround(sample) || Math.AbsFloat(SCR_TerrainHelper.GetTerrainY(sample) - ground) > 0.18) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
protected void Plan(array<vector> observers, array<IEntity> actors, float now)
|
||||||
|
{
|
||||||
|
if (observers.IsEmpty() || now < m_NextSpawn || m_Parked.Count() >= m_Requested || m_Parked.Count() + m_Claimed >= 128 || m_Sites.IsEmpty()) return;
|
||||||
|
// Bounded planning work, even after a long tour of a large terrain.
|
||||||
|
for (int candidate = 0; candidate < 8; candidate++)
|
||||||
|
{
|
||||||
|
if (m_SiteCursor >= m_Sites.Count()) m_SiteCursor = 0;
|
||||||
|
ERR_GarageSite site = m_Sites[m_SiteCursor++];
|
||||||
|
if (!site.Selected || site.Consumed || !site.Garage || now < site.NextTry || Occupied(site)) continue;
|
||||||
|
vector offset = "0 0 -0.5";
|
||||||
|
vector angles = site.Garage.GetYawPitchRoll();
|
||||||
|
if (site.Yard)
|
||||||
|
{
|
||||||
|
vector low, high; site.Garage.GetBounds(low, high);
|
||||||
|
if (!ERR_ForceRules.FiniteVector(low) || !ERR_ForceRules.FiniteVector(high) || (high - low).Length() > 40) continue;
|
||||||
|
int side = site.Side; site.Side = (site.Side + 1) % 4;
|
||||||
|
float clearance = 3; if (site.Farm) clearance = 4;
|
||||||
|
offset = ERR_GarageVehicleRules.YardOffset(low, high, side, clearance);
|
||||||
|
angles[1] = 0; angles[2] = 0;
|
||||||
|
if (side >= 2) angles[0] = angles[0] + 90;
|
||||||
|
}
|
||||||
|
vector position = site.Garage.CoordToParent(offset);
|
||||||
|
if (!Nearby(position, observers, m_SpawnRadius)) continue;
|
||||||
|
site.NextTry = now + 15000;
|
||||||
|
if (!ERR_CivilianTrafficRules.Far(position, observers, 35) || !ERR_Actors.Alive(site.Garage)) continue;
|
||||||
|
vector transform[4]; Math3D.AnglesToMatrix(angles, transform);
|
||||||
|
if (site.Yard)
|
||||||
|
{
|
||||||
|
position = ERR_Actors.Ground(position) + "0 0.08 0";
|
||||||
|
if (!YardGround(position, transform, site.Farm)) return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
TraceParam floor = new TraceParam(); floor.Start = position + "0 1 0"; floor.End = position - "0 1 0";
|
||||||
|
floor.Flags = TraceFlags.WORLD | TraceFlags.ENTS;
|
||||||
|
float fraction = GetGame().GetWorld().TraceMove(floor, null);
|
||||||
|
if (!ERR_ForceRules.Finite(fraction) || fraction >= 1 || floor.TraceNorm[1] < 0.98) return;
|
||||||
|
position = vector.Lerp(floor.Start, floor.End, fraction) + "0 0.08 0";
|
||||||
|
}
|
||||||
|
if (ChimeraWorldUtils.TryGetWaterSurfaceSimple(GetGame().GetWorld(), position) || !Clear(position, transform, null, site.Farm) || !Hidden(position, angles, actors, null, site.Farm)) return;
|
||||||
|
m_Pending = site; m_Position = position; m_Angles = angles;
|
||||||
|
m_Prefab = ERR_VehicleSelection.Choose(m_Cars.Prefabs, 0);
|
||||||
|
if (site.Farm) m_Prefab = ERR_VehicleSelection.Choose(m_Cars.Tractors, 0);
|
||||||
|
m_PendingBike = !site.Farm && (m_Prefab.IsEmpty() || m_Spawned % 4 == 3) && m_BikeCandidate < m_Bikes.Prefabs.Count();
|
||||||
|
if (m_PendingBike)
|
||||||
|
{
|
||||||
|
if (ERR_VehicleSelection.RoleAllowed(m_Bikes.Prefabs[m_BikeCandidate], 0)) m_Prefab = m_Bikes.Prefabs[m_BikeCandidate];
|
||||||
|
else { m_BikeCandidate++; m_PendingBike = false; }
|
||||||
|
}
|
||||||
|
ResourceName preferred = m_Prefab;
|
||||||
|
m_Prefab = ERR_VehicleSelection.ForRole(0, preferred, false);
|
||||||
|
if (m_Prefab != preferred) m_PendingBike = false;
|
||||||
|
if (m_Prefab.IsEmpty()) { m_Pending = null; return; }
|
||||||
|
m_Stage = 1; return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
protected void Pending(array<vector> observers, array<IEntity> actors)
|
||||||
|
{
|
||||||
|
if (!ERR_VehicleSelection.RoleAllowed(m_Prefab, 0)) { m_Pending = null; return; }
|
||||||
|
if (m_Stage == 1)
|
||||||
|
{
|
||||||
|
if (!m_LoadedNames.Contains(m_Prefab))
|
||||||
|
{
|
||||||
|
Resource resource = Resource.Load(m_Prefab);
|
||||||
|
if (!resource || !resource.IsValid()) { if (m_PendingBike) m_BikeCandidate++; m_Pending = null; return; }
|
||||||
|
m_LoadedNames.Insert(m_Prefab); m_LoadedResources.Insert(resource);
|
||||||
|
}
|
||||||
|
m_Stage = 2; return;
|
||||||
|
}
|
||||||
|
ERR_GarageSite site = m_Pending; m_Pending = null;
|
||||||
|
if (!site.Garage || site.Consumed || !ERR_Actors.Alive(site.Garage) || !Nearby(m_Position, observers, m_SpawnRadius)
|
||||||
|
|| !ERR_CivilianTrafficRules.Far(m_Position, observers, 35) || !Hidden(m_Position, m_Angles, actors, null, site.Farm)) return;
|
||||||
|
vector transform[4]; Math3D.AnglesToMatrix(m_Angles, transform);
|
||||||
|
if (site.Yard && !YardGround(m_Position, transform, site.Farm)) return;
|
||||||
|
if (!Clear(m_Position, transform, null, site.Farm)) return;
|
||||||
|
m_NextSpawn = Now() + m_SpawnInterval;
|
||||||
|
IEntity entity = ERR_Actors.Spawn(m_Prefab, m_Position, m_Angles);
|
||||||
|
Vehicle vehicle = Vehicle.Cast(entity);
|
||||||
|
vector low, high; if (vehicle) vehicle.GetBounds(low, high);
|
||||||
|
array<BaseCompartmentSlot> seats = {}; bool driver; bool armed;
|
||||||
|
if (vehicle && ERR_ConvoyRules.CollectSeats(vehicle, seats))
|
||||||
|
foreach (BaseCompartmentSlot seat : seats)
|
||||||
|
{
|
||||||
|
if (seat.GetType() == ECompartmentType.TURRET) armed = true;
|
||||||
|
if (seat.GetType() == ECompartmentType.PILOT && seat.IsCompartmentAccessible() && !seat.GetOccupant()) driver = true;
|
||||||
|
}
|
||||||
|
bool fits = ERR_GarageVehicleRules.Envelope(low, high);
|
||||||
|
if (site.Farm) fits = ERR_GarageVehicleRules.FarmEnvelope(low, high);
|
||||||
|
if (!driver || armed || !fits || !Clear(m_Position, transform, entity, site.Farm))
|
||||||
|
{
|
||||||
|
if (!driver || armed || !fits) GetGame().ERR_ServerVehicles().Reject(m_Prefab, 0);
|
||||||
|
ERR_Actors.DeleteSpawned(entity); if (m_PendingBike) m_BikeCandidate++; return;
|
||||||
|
}
|
||||||
|
// Empty vehicles start unaffiliated; vanilla assigns the first occupant's
|
||||||
|
// faction. Forcing CIV here can block player entry before that transfer.
|
||||||
|
SCR_FuelManagerComponent fuel = SCR_FuelManagerComponent.Cast(vehicle.FindComponent(SCR_FuelManagerComponent));
|
||||||
|
if (fuel) fuel.SetTotalFuelPercentage(Math.RandomFloat(0.3, 0.8));
|
||||||
|
ERR_ParkedVehicle parked = new ERR_ParkedVehicle(); parked.Entity = vehicle; parked.Site = site; parked.Origin = m_Position;
|
||||||
|
m_Parked.Insert(parked); m_Spawned++;
|
||||||
|
SCR_GarbageSystem garbage = SCR_GarbageSystem.GetByEntityWorld(vehicle);
|
||||||
|
if (garbage) garbage.UpdateBlacklist(vehicle, true);
|
||||||
|
}
|
||||||
|
protected void Step()
|
||||||
|
{
|
||||||
|
if (m_Stopped || !Replication.IsServer() || !Running()) return;
|
||||||
|
float now = Now();
|
||||||
|
if (now < m_NextStep) return;
|
||||||
|
m_NextStep = now + 250; // Do not catch up missed work in one slow frame.
|
||||||
|
array<vector> observers = {}; array<IEntity> actors = {};
|
||||||
|
bool known = Observers(observers, actors);
|
||||||
|
if (!known)
|
||||||
|
{
|
||||||
|
m_Pending = null;
|
||||||
|
// Unknown cameras invalidate every retirement grace period immediately.
|
||||||
|
foreach (ERR_ParkedVehicle parked : m_Parked) parked.HiddenSince = -1;
|
||||||
|
Maintain(observers, actors, now, false); return;
|
||||||
|
}
|
||||||
|
if (m_Pending) { Pending(observers, actors); return; }
|
||||||
|
m_Work = (m_Work + 1) % 3;
|
||||||
|
if (m_Work == 0) Maintain(observers, actors, now, true);
|
||||||
|
else if (m_Work == 1) Discover(observers, now);
|
||||||
|
else Plan(observers, actors, now);
|
||||||
|
}
|
||||||
|
// Filled oriented body volume includes furniture, doors, other vehicles and
|
||||||
|
// players. The garage itself is deliberately NOT excluded from collision.
|
||||||
|
protected bool Clear(vector position, vector transform[4], IEntity ignored = null, bool farm = false)
|
||||||
|
{
|
||||||
|
TraceOBB body = new TraceOBB();
|
||||||
|
body.Start = position; body.End = position + "0 0.01 0";
|
||||||
|
body.Mins = "-1.1 0.12 -2.5"; body.Maxs = "1.1 2 2.5";
|
||||||
|
if (farm) { body.Mins = "-1.5 0.12 -3"; body.Maxs = "1.5 3.2 3"; }
|
||||||
|
for (int i = 0; i < 3; i++) body.Mat[i] = transform[i];
|
||||||
|
body.Flags = TraceFlags.WORLD | TraceFlags.ENTS | TraceFlags.OCEAN;
|
||||||
|
body.Exclude = ignored;
|
||||||
|
float fraction = GetGame().GetWorld().TraceMove(body, null);
|
||||||
|
return ERR_ForceRules.Finite(fraction) && fraction >= 1;
|
||||||
|
}
|
||||||
|
void Stop()
|
||||||
|
{
|
||||||
|
m_Stopped = true;
|
||||||
|
if (GetGame()) GetGame().GetCallqueue().Remove(Step);
|
||||||
|
m_Pending = null; m_Sites.Clear();
|
||||||
|
// No visible deletion at round end. World teardown owns remaining vehicles.
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1203,7 +1203,18 @@ class ERR_HQ : ERR_CommandService
|
|||||||
else unit.CancelPending();
|
else unit.CancelPending();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (m_Watch.ArmedSearchBlocked(unit.ForceSourceId, now) && !(task.OperationId > 0 && task.OperationRole == ERR_HQOperationRole.WITHDRAW)) { LostOrder(unit, now); return; }
|
if (m_Watch.ArmedSearchBlocked(unit.ForceSourceId, now))
|
||||||
|
{
|
||||||
|
bool recoveryOrder = task.OperationId > 0 && task.OperationRole == ERR_HQOperationRole.WITHDRAW;
|
||||||
|
if (m_Coordinated && task.OperationId > 0 && task.OperationRole == ERR_HQOperationRole.RESERVE)
|
||||||
|
{
|
||||||
|
// Pending radio orders exclude this squad from ObserveForces. Its delivered
|
||||||
|
// recovery judgment stays current until the exact queued task is validated below.
|
||||||
|
ERR_HQSquadJudgment squad = m_Operations.Judgment(unit.Id);
|
||||||
|
recoveryOrder = squad && squad.Recovering;
|
||||||
|
}
|
||||||
|
if (!recoveryOrder) { LostOrder(unit, now); return; }
|
||||||
|
}
|
||||||
if (task.Investigation())
|
if (task.Investigation())
|
||||||
{
|
{
|
||||||
if (!WatchTaskValid(task)) { LostOrder(unit, now); return; }
|
if (!WatchTaskValid(task)) { LostOrder(unit, now); return; }
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ class ERR_HunterVehicle : ERR_ConvoyVehicle
|
|||||||
bool DeliveredSearch;
|
bool DeliveredSearch;
|
||||||
float PendingObservedAt = -1;
|
float PendingObservedAt = -1;
|
||||||
bool TroopTransport;
|
bool TroopTransport;
|
||||||
|
bool AssignedVehicle;
|
||||||
bool Motorbike;
|
bool Motorbike;
|
||||||
bool Sarka;
|
bool Sarka;
|
||||||
bool OpeningPatrol;
|
bool OpeningPatrol;
|
||||||
@@ -513,10 +514,16 @@ class ERR_HunterService : ERR_ConvoyService
|
|||||||
{
|
{
|
||||||
if (!ERR_SarkaRules.Seats(drivers.Count(), turrets.Count(), cargo.Count(), all.Count())) return false;
|
if (!ERR_SarkaRules.Seats(drivers.Count(), turrets.Count(), cargo.Count(), all.Count())) return false;
|
||||||
}
|
}
|
||||||
|
else if (hunter.AssignedVehicle)
|
||||||
|
{
|
||||||
|
if (!ERR_VehicleSelection.PatrolSeats(drivers.Count(), turrets.Count(), cargo.Count(), all.Count())) return false;
|
||||||
|
hunter.TroopTransport = turrets.IsEmpty();
|
||||||
|
}
|
||||||
else if (!ERR_HunterFleetRules.Seats(hunter.TroopTransport, drivers.Count(), turrets.Count(), cargo.Count(), all.Count())) return false;
|
else if (!ERR_HunterFleetRules.Seats(hunter.TroopTransport, drivers.Count(), turrets.Count(), cargo.Count(), all.Count())) return false;
|
||||||
unit.Seats.Insert(drivers[0]);
|
unit.Seats.Insert(drivers[0]);
|
||||||
foreach (BaseCompartmentSlot turret : turrets) unit.Seats.Insert(turret);
|
foreach (BaseCompartmentSlot turret : turrets) unit.Seats.Insert(turret);
|
||||||
int medicIndex = unit.Seats.Count();
|
int medicIndex = unit.Seats.Count();
|
||||||
|
if (hunter.AssignedVehicle && cargo.IsEmpty()) medicIndex = unit.Seats.Count() - 1;
|
||||||
foreach (BaseCompartmentSlot passenger : cargo) unit.Seats.Insert(passenger);
|
foreach (BaseCompartmentSlot passenger : cargo) unit.Seats.Insert(passenger);
|
||||||
AIGroup nativeGroup = unit.Group;
|
AIGroup nativeGroup = unit.Group;
|
||||||
for (int i = 0; i < unit.Seats.Count(); i++)
|
for (int i = 0; i < unit.Seats.Count(); i++)
|
||||||
@@ -547,15 +554,18 @@ class ERR_HunterService : ERR_ConvoyService
|
|||||||
unit.OpeningPatrol = Deployed() < 3; unit.PatrolHome = position;
|
unit.OpeningPatrol = Deployed() < 3; unit.PatrolHome = position;
|
||||||
unit.Source = "aggression-uaz-" + (Deployed() + 1).ToString();
|
unit.Source = "aggression-uaz-" + (Deployed() + 1).ToString();
|
||||||
if (unit.TroopTransport) unit.Source = "aggression-truck-" + (Deployed() + 1).ToString();
|
if (unit.TroopTransport) unit.Source = "aggression-truck-" + (Deployed() + 1).ToString();
|
||||||
ResourceName vehiclePrefab = ERR_HunterFleetRules.VehiclePrefab(Deployed());
|
ResourceName originalPrefab = ERR_HunterFleetRules.VehiclePrefab(Deployed());
|
||||||
|
ResourceName vehiclePrefab = ERR_VehicleSelection.ForRole(2, originalPrefab);
|
||||||
|
if (vehiclePrefab.IsEmpty()) { m_NextSpawn = now + 30000; return false; }
|
||||||
|
unit.AssignedVehicle = vehiclePrefab != originalPrefab;
|
||||||
unit.Deadline = now + 120000; unit.LaunchedAt = now; m_Hunters.Insert(unit);
|
unit.Deadline = now + 120000; unit.LaunchedAt = now; m_Hunters.Insert(unit);
|
||||||
CommitReserve(unit, now);
|
CommitReserve(unit, now);
|
||||||
unit.Group = SCR_AIGroup.Cast(ERR_Actors.Spawn(ERR_ConvoyResources.GroupPrefab(), position));
|
unit.Group = SCR_AIGroup.Cast(ERR_Actors.Spawn(ERR_ConvoyResources.GroupPrefab(), position));
|
||||||
unit.Entity = Vehicle.Cast(ERR_Actors.Spawn(vehiclePrefab, position, angles));
|
unit.Entity = Vehicle.Cast(ERR_Actors.Spawn(vehiclePrefab, position, angles));
|
||||||
if (unit.Group) m_Mode.Own(unit.Group);
|
if (unit.Group) m_Mode.Own(unit.Group);
|
||||||
if (unit.Entity) m_Mode.Own(unit.Entity);
|
if (unit.Entity) m_Mode.Own(unit.Entity);
|
||||||
if (!unit.Group || !unit.Entity || !ERR_ConvoyRules.EmptyCrewTemplate(unit.Group) || !FillCrew(unit))
|
if (!unit.Group || !unit.Entity || !ERR_ConvoyRules.EmptyCrewTemplate(unit.Group) || (unit.AssignedVehicle && !ERR_VehicleSelection.RoadFit(unit.Entity)) || !FillCrew(unit))
|
||||||
{ unit.Release(m_Mode); ERR_HQ.Event("HUNTER FAILED", unit.Source + " native vehicle/crew spawn failed; allowance consumed"); return false; }
|
{ GetGame().ERR_ServerVehicles().Reject(vehiclePrefab, 2); unit.Release(m_Mode); ERR_HQ.Event("HUNTER FAILED", unit.Source + " native vehicle/crew spawn failed; allowance consumed"); return false; }
|
||||||
unit.Recovery = new ERR_HunterCrewRecovery(unit);
|
unit.Recovery = new ERR_HunterCrewRecovery(unit);
|
||||||
unit.Recovery.Update(m_Mode, now);
|
unit.Recovery.Update(m_Mode, now);
|
||||||
FactionAffiliationComponent affiliation = FactionAffiliationComponent.Cast(unit.Entity.FindComponent(FactionAffiliationComponent));
|
FactionAffiliationComponent affiliation = FactionAffiliationComponent.Cast(unit.Entity.FindComponent(FactionAffiliationComponent));
|
||||||
@@ -915,6 +925,7 @@ modded class SCR_CompartmentAccessComponent
|
|||||||
if (mode) hq = ERR_HQ.Cast(mode.Command());
|
if (mode) hq = ERR_HQ.Cast(mode.Command());
|
||||||
if (hq && hq.Hunters()) hq.Hunters().PlayerEntered(GetOwner(), targetEntity);
|
if (hq && hq.Hunters()) hq.Hunters().PlayerEntered(GetOwner(), targetEntity);
|
||||||
if (mode && mode.CivilianTraffic()) mode.CivilianTraffic().PlayerEntered(GetOwner(), targetEntity);
|
if (mode && mode.CivilianTraffic()) mode.CivilianTraffic().PlayerEntered(GetOwner(), targetEntity);
|
||||||
|
if (mode && mode.GarageVehicles()) mode.GarageVehicles().PlayerEntered(GetOwner(), targetEntity);
|
||||||
}
|
}
|
||||||
super.OnCompartmentEntered(targetEntity, manager, mgrID, slotID, move);
|
super.OnCompartmentEntered(targetEntity, manager, mgrID, slotID, move);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ class ERR_MortarSystem
|
|||||||
protected bool CreateBattery(vector authoredPosition, int id)
|
protected bool CreateBattery(vector authoredPosition, int id)
|
||||||
{
|
{
|
||||||
vector position = ERR_Actors.Ground(authoredPosition);
|
vector position = ERR_Actors.Ground(authoredPosition);
|
||||||
if (position[1] < 1) { PrintFormat("Reintroduced [mortars]: below land at %1", position); return false; }
|
if (!ERR_Actors.DryGround(position)) { PrintFormat("Reintroduced [mortars]: submerged or invalid ground at %1", position); return false; }
|
||||||
IEntity gun = ERR_Actors.Spawn(ERR_MortarBattery.GUN, position);
|
IEntity gun = ERR_Actors.Spawn(ERR_MortarBattery.GUN, position);
|
||||||
SCR_AIGroup group = SCR_AIGroup.Cast(ERR_Actors.Spawn(MortarGroup(), ERR_Actors.Ground(position + "0 0 4")));
|
SCR_AIGroup group = SCR_AIGroup.Cast(ERR_Actors.Spawn(MortarGroup(), ERR_Actors.Ground(position + "0 0 4")));
|
||||||
if (!gun || !group)
|
if (!gun || !group)
|
||||||
|
|||||||
@@ -62,7 +62,8 @@ class ERR_RecommendedAddons
|
|||||||
static void Snapshot(ERR_GameMode mode, array<string> values)
|
static void Snapshot(ERR_GameMode mode, array<string> values)
|
||||||
{
|
{
|
||||||
array<string> loaded = {}; GameProject.GetLoadedAddons(loaded);
|
array<string> loaded = {}; GameProject.GetLoadedAddons(loaded);
|
||||||
Entry(values, "bikes", loaded, {"6503091A7B264FC3", "61B84088181A8FA4", "629B2BA37EFFD577"}, {"WCS Motorbikes", "Motorbike", "WCS Armaments"}, mode.Settings().Get("motorbikeCount") > 0);
|
Entry(values, "bikes", loaded, {"6503091A7B264FC3", "61B84088181A8FA4", "629B2BA37EFFD577"}, {"WCS Motorbikes", "Motorbike", "WCS Armaments"}, mode.Settings().Get("motorbikeCount") > 0 || (mode.Settings().Get("garageVehicleCount") > 0 && mode.Settings().Get("garageChancePercent") > 0));
|
||||||
|
Entry(values, "boats", loaded, {"610C8532D3756F2D", "696A0CE876246081", "61A6B4C21F6FCF22", "6A7D4E7B69969355"}, {"Motorboat", "Modest Boat Core", "Modest AI Radio", "Modest VehicleCore"}, mode.Settings().Get("shoreBoatCount") > 0 && mode.Settings().Get("shoreBoatCrossingChancePercent") + mode.Settings().Get("shoreBoatChancePercent") > 0);
|
||||||
Entry(values, "plane", loaded, {"69BAEE0D439966FE", "69A8A34027DA65C5"}, {"Cessna 172", "Propeller Flight Core"}, mode.Settings().Get("planeReconEnabled") == 1);
|
Entry(values, "plane", loaded, {"69BAEE0D439966FE", "69A8A34027DA65C5"}, {"Cessna 172", "Propeller Flight Core"}, mode.Settings().Get("planeReconEnabled") == 1);
|
||||||
Entry(values, "weapons", loaded, {"BFC890A6F1603EC0"}, {"Phaseline Marksman Rifles"}, mode.Settings().Get("automaticEquipmentChancePercent") != 0);
|
Entry(values, "weapons", loaded, {"BFC890A6F1603EC0"}, {"Phaseline Marksman Rifles"}, mode.Settings().Get("automaticEquipmentChancePercent") != 0);
|
||||||
ERR_HQ hq = ERR_HQ.Cast(mode.Command());
|
ERR_HQ hq = ERR_HQ.Cast(mode.Command());
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
// Discover only complete civilian cars. Rally is optional and must already be
|
||||||
|
// mounted; do not add a DLC dependency or bypass its normal access checks.
|
||||||
|
class ERR_ParkedCarCatalog
|
||||||
|
{
|
||||||
|
ref array<ResourceName> Prefabs = {};
|
||||||
|
ref array<ResourceName> RoadPrefabs = {};
|
||||||
|
ref array<ResourceName> Tractors = {};
|
||||||
|
protected bool m_Checked;
|
||||||
|
static bool Candidate(string path, bool rally)
|
||||||
|
{
|
||||||
|
path.ToLower();
|
||||||
|
if (!path.EndsWith(".et") || path.Contains("_base") || path.Contains("_fullkit")
|
||||||
|
|| path.Contains("_randomized") || path.Contains("_fia")) return false;
|
||||||
|
if (!path.Contains("/s105/s105_") && !path.Contains("/t601/t601_") && !path.Contains("/v2101/v2101_")) return false;
|
||||||
|
if (rally) return path.Contains("_rally_0");
|
||||||
|
return !path.Contains("_rally");
|
||||||
|
}
|
||||||
|
static bool MovingCandidate(string path)
|
||||||
|
{
|
||||||
|
path.ToLower();
|
||||||
|
// T601 remains usable as a parked car; native AI did not start it in 1.9.0.37.
|
||||||
|
return Candidate(path, false) && !path.Contains("/t601/");
|
||||||
|
}
|
||||||
|
void Find(bool includeRally = true)
|
||||||
|
{
|
||||||
|
if (m_Checked) return;
|
||||||
|
m_Checked = true;
|
||||||
|
Prefabs.Insert(ERR_CivilianTraffic.CarPrefab(0));
|
||||||
|
Prefabs.Insert(ERR_CivilianTraffic.CarPrefab(1));
|
||||||
|
RoadPrefabs.Copy(Prefabs);
|
||||||
|
SearchResourcesFilter filter = new SearchResourcesFilter();
|
||||||
|
filter.fileExtensions = {"et"}; filter.recursive = true;
|
||||||
|
filter.rootPath = "$" + GameProject.GetAddonID("58D0FB3206B6F859") + ":Prefabs/Vehicles/Wheeled";
|
||||||
|
ResourceDatabase.SearchResources(filter, AddRoad);
|
||||||
|
array<string> loaded = {}; GameProject.GetLoadedAddons(loaded);
|
||||||
|
if (!includeRally || !loaded.Contains("6566A2D70498DA6B")) return;
|
||||||
|
filter.rootPath = "$" + GameProject.GetAddonID("6566A2D70498DA6B") + ":Prefabs/Vehicles/Wheeled";
|
||||||
|
ResourceDatabase.SearchResources(filter, AddRally);
|
||||||
|
}
|
||||||
|
protected void AddRoad(ResourceName resource, string path = "")
|
||||||
|
{
|
||||||
|
if (Candidate(resource, false) && Prefabs.Count() < 64 && !Prefabs.Contains(resource))
|
||||||
|
{
|
||||||
|
Prefabs.Insert(resource);
|
||||||
|
if (MovingCandidate(resource)) RoadPrefabs.Insert(resource);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
protected void AddRally(ResourceName resource, string path = "")
|
||||||
|
{
|
||||||
|
if (Candidate(resource, true) && Prefabs.Count() < 64 && !Prefabs.Contains(resource)) Prefabs.Insert(resource);
|
||||||
|
string lower = resource; lower.ToLower();
|
||||||
|
if (lower.EndsWith("/z7211/z7211_red_civ.et") && Tractors.IsEmpty()) Tractors.Insert(resource);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,6 +18,47 @@ class ERR_RescueFlightComponentClass : ERR_ReconFlightComponentClass {}
|
|||||||
class ERR_RescueFlightComponent : ERR_ReconFlightComponent
|
class ERR_RescueFlightComponent : ERR_ReconFlightComponent
|
||||||
{
|
{
|
||||||
protected bool m_Escort, m_Departing, m_LandingConfirmed;
|
protected bool m_Escort, m_Departing, m_LandingConfirmed;
|
||||||
|
[RplProp(onRplName: "ApplyResultsHold")] protected bool m_ResultsHeld;
|
||||||
|
// Contact plus real clearance excludes boarding, engine spin-up, and the
|
||||||
|
// controller's ten-second grounded takeoff hold. Every transport must lift.
|
||||||
|
bool AirborneAfterPickup()
|
||||||
|
{
|
||||||
|
if (!m_Departing || m_Lost || !m_Aircraft) return false;
|
||||||
|
VehicleHelicopterSimulation sim = VehicleHelicopterSimulation.Cast(m_Aircraft.FindComponent(VehicleHelicopterSimulation));
|
||||||
|
if (!sim || sim.HasAnyGroundContact()) return false;
|
||||||
|
float ground = SCR_TerrainHelper.GetTerrainY(m_Aircraft.GetOrigin(), GetGame().GetWorld(), noUnderwater: true);
|
||||||
|
return m_Aircraft.GetOrigin()[1] - ground >= 3;
|
||||||
|
}
|
||||||
|
void HoldForResults()
|
||||||
|
{
|
||||||
|
if (!Replication.IsServer() || m_Lost || !ERR_Actors.Alive(GetOwner()) || m_ResultsHeld) return;
|
||||||
|
m_ResultsHeld = true;
|
||||||
|
ApplyResultsHold();
|
||||||
|
Replication.BumpMe();
|
||||||
|
}
|
||||||
|
protected void ApplyResultsHold()
|
||||||
|
{
|
||||||
|
if (!m_ResultsHeld || !GetOwner()) return;
|
||||||
|
Physics physics = GetOwner().GetPhysics();
|
||||||
|
if (physics)
|
||||||
|
{
|
||||||
|
physics.SetVelocity(vector.Zero);
|
||||||
|
physics.SetAngularVelocity(vector.Zero);
|
||||||
|
physics.ChangeSimulationState(SimulationState.COLLISION);
|
||||||
|
}
|
||||||
|
if (Replication.IsServer())
|
||||||
|
{
|
||||||
|
DamageManagerComponent damage = DamageManagerComponent.Cast(GetOwner().FindComponent(DamageManagerComponent));
|
||||||
|
if (damage) damage.EnableDamageHandling(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
override void Release(bool captured)
|
||||||
|
{
|
||||||
|
// Terminal presentation keeps the engines/rotors; physics is already held.
|
||||||
|
// Ordinary lost/captured/aborted flights retain their original cleanup.
|
||||||
|
super.Release(captured || m_ResultsHeld);
|
||||||
|
}
|
||||||
|
|
||||||
protected float m_BoardingReadySince = -1;
|
protected float m_BoardingReadySince = -1;
|
||||||
protected vector m_Pickup;
|
protected vector m_Pickup;
|
||||||
protected float m_RescueDeadline, m_NextThreat;
|
protected float m_RescueDeadline, m_NextThreat;
|
||||||
@@ -168,6 +209,7 @@ class ERR_RescueFlightComponent : ERR_ReconFlightComponent
|
|||||||
}
|
}
|
||||||
override void EOnSimulate(IEntity owner, float timeSlice)
|
override void EOnSimulate(IEntity owner, float timeSlice)
|
||||||
{
|
{
|
||||||
|
if (m_ResultsHeld) { ApplyResultsHold(); return; }
|
||||||
// Initial airborne crew allocation has a fixed 45-second deadline.
|
// Initial airborne crew allocation has a fixed 45-second deadline.
|
||||||
if (Replication.IsServer() && m_Initialized && m_Phase == ERR_ReconPhase.CREW)
|
if (Replication.IsServer() && m_Initialized && m_Phase == ERR_ReconPhase.CREW)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -4,7 +4,22 @@ class ERR_RoadblockRules
|
|||||||
{
|
{
|
||||||
// Admission never waits longer for a high count. Remaining posts are built
|
// Admission never waits longer for a high count. Remaining posts are built
|
||||||
// by the same safe placement path in background, including after prison unlock.
|
// by the same safe placement path in background, including after prison unlock.
|
||||||
static int PropCount() { return 18; }
|
static int PropCount(bool large = false)
|
||||||
|
{
|
||||||
|
if (large) return 14;
|
||||||
|
return 8;
|
||||||
|
}
|
||||||
|
static int GuardCount(bool large)
|
||||||
|
{
|
||||||
|
if (large) return 8;
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
static bool LargeRoad(float width, float threshold) { return width >= threshold; }
|
||||||
|
static bool Needed(bool large, int smallPlaced, int smallTarget, int largePlaced, int largeTarget)
|
||||||
|
{
|
||||||
|
if (large) return largePlaced < largeTarget;
|
||||||
|
return smallPlaced < smallTarget;
|
||||||
|
}
|
||||||
static int SearchMilliseconds() { return 12000; }
|
static int SearchMilliseconds() { return 12000; }
|
||||||
static int SearchAttempts() { return 300; }
|
static int SearchAttempts() { return 300; }
|
||||||
static bool Road(float width, float length, float heightDifference)
|
static bool Road(float width, float length, float heightDifference)
|
||||||
@@ -51,6 +66,8 @@ class ERR_Roadblocks
|
|||||||
protected int m_Target, m_Attempts, m_Started, m_CandidateStarted, m_Candidates, m_Planned;
|
protected int m_Target, m_Attempts, m_Started, m_CandidateStarted, m_Candidates, m_Planned;
|
||||||
protected vector m_Low, m_High, m_Centre, m_Forward, m_Right;
|
protected vector m_Low, m_High, m_Centre, m_Forward, m_Right;
|
||||||
protected float m_Shoulder;
|
protected float m_Shoulder;
|
||||||
|
protected bool m_Large;
|
||||||
|
protected int m_SmallTarget, m_LargeTarget, m_SmallPlaced, m_LargePlaced, m_MainRoadWidth;
|
||||||
protected SCR_AIGroup m_BuildGroup;
|
protected SCR_AIGroup m_BuildGroup;
|
||||||
protected AIWaypoint m_BuildHold;
|
protected AIWaypoint m_BuildHold;
|
||||||
protected ref array<IEntity> m_BuildSpawned = {};
|
protected ref array<IEntity> m_BuildSpawned = {};
|
||||||
@@ -61,10 +78,7 @@ class ERR_Roadblocks
|
|||||||
protected ref array<ref Resource> m_Loaded = {};
|
protected ref array<ref Resource> m_Loaded = {};
|
||||||
static const ResourceName COVER = "{21D83950DF36F250}Prefabs/Props/Military/Sandbags/Sandbag_01_long_burlap.et";
|
static const ResourceName COVER = "{21D83950DF36F250}Prefabs/Props/Military/Sandbags/Sandbag_01_long_burlap.et";
|
||||||
static const ResourceName BARRIER = "{64C9C1D1C2C1B38D}Prefabs/Props/Infrastructure/Roadblocks/RoadBlock_01.et";
|
static const ResourceName BARRIER = "{64C9C1D1C2C1B38D}Prefabs/Props/Infrastructure/Roadblocks/RoadBlock_01.et";
|
||||||
static const ResourceName OBSTACLE = "{A46D45922F8CAD6C}Prefabs/Props/Military/Fortification/CzechHedgehog_01_painted.et";
|
|
||||||
static const ResourceName SIGN = "{C1977A326F2AA011}Prefabs/Structures/Signs/Military/SignCheckpoint_01_stop.et";
|
static const ResourceName SIGN = "{C1977A326F2AA011}Prefabs/Structures/Signs/Military/SignCheckpoint_01_stop.et";
|
||||||
static const ResourceName SHELTER = "{01AE5FD77A9A4C21}Prefabs/Structures/Military/Camps/TentSmallUS_01/TentSmallUS_01.et";
|
|
||||||
static const ResourceName TABLE = "{53506DCB398C7EBE}Prefabs/Props/Furniture/Table_01.et";
|
|
||||||
void ERR_Roadblocks(ERR_GameMode mode) { m_Mode = mode; m_Mining = new ERR_RoadblockMining(mode); }
|
void ERR_Roadblocks(ERR_GameMode mode) { m_Mode = mode; m_Mining = new ERR_RoadblockMining(mode); }
|
||||||
void MineTick(array<ref ERR_HQUnit> units, array<IEntity> attempted, float now) { if (!m_Stopped) m_Mining.Tick(units, attempted, now); }
|
void MineTick(array<ref ERR_HQUnit> units, array<IEntity> attempted, float now) { if (!m_Stopped) m_Mining.Tick(units, attempted, now); }
|
||||||
bool Contains(SCR_AIGroup group) { return group && m_Groups.Contains(group); }
|
bool Contains(SCR_AIGroup group) { return group && m_Groups.Contains(group); }
|
||||||
@@ -75,7 +89,7 @@ class ERR_Roadblocks
|
|||||||
if (m_Ready && m_Positions.Count() < m_Target) progress = "placement stopped; see server log";
|
if (m_Ready && m_Positions.Count() < m_Target) progress = "placement stopped; see server log";
|
||||||
if (m_Background) progress = "background placement pending";
|
if (m_Background) progress = "background placement pending";
|
||||||
else if (!m_Ready) progress = "initial placement in progress";
|
else if (!m_Ready) progress = "initial placement in progress";
|
||||||
return string.Format("Roadblocks: %1/%2 placed this round; next-round setting %3. Four guards per post, no replacements. %4.", m_Positions.Count(), m_Target, m_Mode.Settings().Get("roadblockCount"), progress);
|
return string.Format("Checkpoints: small %1/%2 (4 guards), large %3/%4 (8 guards). Main-road cutoff %5 m. No replacements. %6.", m_SmallPlaced, m_SmallTarget, m_LargePlaced, m_LargeTarget, m_MainRoadWidth, progress);
|
||||||
}
|
}
|
||||||
protected ERR_PrepareResult Finish(string reason)
|
protected ERR_PrepareResult Finish(string reason)
|
||||||
{
|
{
|
||||||
@@ -93,7 +107,7 @@ class ERR_Roadblocks
|
|||||||
if (profile && profile.IsValid()) source = profile.GetResource().ToEntitySource();
|
if (profile && profile.IsValid()) source = profile.GetResource().ToEntitySource();
|
||||||
if (!source || !source.Get("m_aUnitPrefabSlots", m_Roster) || m_Roster.IsEmpty()) return false;
|
if (!source || !source.Get("m_aUnitPrefabSlots", m_Roster) || m_Roster.IsEmpty()) return false;
|
||||||
// Preload before physical creation. A failed prefab never leaves half a post.
|
// Preload before physical creation. A failed prefab never leaves half a post.
|
||||||
array<ResourceName> resources = {COVER, BARRIER, OBSTACLE, SIGN, SHELTER, TABLE, "{EC9F1AA42D0B7863}Prefabs/ERR/PrisonPatrol.et", ERR_Resources.DefendWaypoint()};
|
array<ResourceName> resources = {COVER, BARRIER, SIGN, "{EC9F1AA42D0B7863}Prefabs/ERR/PrisonPatrol.et", ERR_Resources.DefendWaypoint()};
|
||||||
foreach (ResourceName actor : m_Roster) resources.Insert(actor);
|
foreach (ResourceName actor : m_Roster) resources.Insert(actor);
|
||||||
foreach (ResourceName prefab : resources)
|
foreach (ResourceName prefab : resources)
|
||||||
{
|
{
|
||||||
@@ -122,11 +136,15 @@ class ERR_Roadblocks
|
|||||||
if (i > 0) length += vector.DistanceXZ(points[i - 1], points[i]);
|
if (i > 0) length += vector.DistanceXZ(points[i - 1], points[i]);
|
||||||
}
|
}
|
||||||
if (!ERR_RoadblockRules.Road(road.GetWidth(), length, 0)) return false;
|
if (!ERR_RoadblockRules.Road(road.GetWidth(), length, 0)) return false;
|
||||||
float distance = Math.RandomFloat(20, length - 20);
|
m_Large = ERR_RoadblockRules.LargeRoad(road.GetWidth(), m_MainRoadWidth);
|
||||||
|
if (!ERR_RoadblockRules.Needed(m_Large, m_SmallPlaced, m_SmallTarget, m_LargePlaced, m_LargeTarget)) return false;
|
||||||
|
float margin = 20;
|
||||||
|
if (m_Large) { if (length < 60) return false; margin = 23; }
|
||||||
|
float distance = Math.RandomFloat(margin, length - margin);
|
||||||
vector centre, before, after, beforeDirection, afterDirection;
|
vector centre, before, after, beforeDirection, afterDirection;
|
||||||
if (!ERR_ConvoyRoadPlan.Sample(points, distance, centre, m_Forward)
|
if (!ERR_ConvoyRoadPlan.Sample(points, distance, centre, m_Forward)
|
||||||
|| !ERR_ConvoyRoadPlan.Sample(points, distance - 15, before, beforeDirection)
|
|| !ERR_ConvoyRoadPlan.Sample(points, distance - margin, before, beforeDirection)
|
||||||
|| !ERR_ConvoyRoadPlan.Sample(points, distance + 15, after, afterDirection)) return false;
|
|| !ERR_ConvoyRoadPlan.Sample(points, distance + margin, after, afterDirection)) return false;
|
||||||
if (vector.Dot(m_Forward, beforeDirection) < 0.95 || vector.Dot(m_Forward, afterDirection) < 0.95) return false;
|
if (vector.Dot(m_Forward, beforeDirection) < 0.95 || vector.Dot(m_Forward, afterDirection) < 0.95) return false;
|
||||||
m_Centre = ERR_Actors.Ground(centre);
|
m_Centre = ERR_Actors.Ground(centre);
|
||||||
if (!ERR_RoadblockRules.Road(road.GetWidth(), length, m_Centre[1] - centre[1])) return false;
|
if (!ERR_RoadblockRules.Road(road.GetWidth(), length, m_Centre[1] - centre[1])) return false;
|
||||||
@@ -142,7 +160,7 @@ class ERR_Roadblocks
|
|||||||
m_Right = Vector(m_Forward[2], 0, -m_Forward[0]);
|
m_Right = Vector(m_Forward[2], 0, -m_Forward[0]);
|
||||||
// Protected guard positions sit on the shoulders; approach barriers narrow
|
// Protected guard positions sit on the shoulders; approach barriers narrow
|
||||||
// wider roads while preserving a straight vehicle passage on every width.
|
// wider roads while preserving a straight vehicle passage on every width.
|
||||||
m_Shoulder = Math.Max(road.GetWidth() * 0.5 + 1.5, 4);
|
m_Shoulder = Math.Max(road.GetWidth() * 0.5 + 0.5, 3.2);
|
||||||
m_Candidates++; m_CandidateStarted = System.GetTickCount();
|
m_Candidates++; m_CandidateStarted = System.GetTickCount();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -187,24 +205,30 @@ class ERR_Roadblocks
|
|||||||
protected void Layout(out array<ref ERR_RoadblockProp> covers)
|
protected void Layout(out array<ref ERR_RoadblockProp> covers)
|
||||||
{
|
{
|
||||||
covers.Clear();
|
covers.Clear();
|
||||||
|
if (m_Large)
|
||||||
|
{
|
||||||
|
// Four protected guard bays on each shoulder, with two barriers per approach.
|
||||||
|
// Extend along the road instead of closing the traffic lane.
|
||||||
|
for (int largeSide = -1; largeSide <= 1; largeSide += 2)
|
||||||
|
{
|
||||||
|
for (int bay = 0; bay < 4; bay++)
|
||||||
|
AddProp(covers, COVER, largeSide * m_Shoulder, bay * 6 - 9, m_Right, 1.9, 0.7, 1);
|
||||||
|
AddProp(covers, BARRIER, largeSide * 3.3, largeSide * 14, m_Forward, 1.2, 0.4, 1.3);
|
||||||
|
AddProp(covers, BARRIER, largeSide * 3.3, largeSide * 18, m_Forward, 1.2, 0.4, 1.3);
|
||||||
|
AddProp(covers, SIGN, largeSide * 3, largeSide * 21, m_Forward * (largeSide * 1.0), 0.45, 0.4, 0.8);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
// Footprints enclose the native model bounds, including small placement margins.
|
// Footprints enclose the native model bounds, including small placement margins.
|
||||||
for (int side = -1; side <= 1; side += 2)
|
for (int side = -1; side <= 1; side += 2)
|
||||||
{
|
{
|
||||||
// Two front walls, an approach wall and an outer return form
|
// A short sandbag screen protects each pair without a wide outer bay.
|
||||||
// a sheltered bay for each pair. The rear stays open for guard access.
|
|
||||||
AddProp(covers, COVER, side * m_Shoulder, -2, m_Right, 1.9, 0.7, 1);
|
AddProp(covers, COVER, side * m_Shoulder, -2, m_Right, 1.9, 0.7, 1);
|
||||||
AddProp(covers, COVER, side * m_Shoulder, 2, m_Right, 1.9, 0.7, 1);
|
AddProp(covers, COVER, side * m_Shoulder, 2, m_Right, 1.9, 0.7, 1);
|
||||||
AddProp(covers, COVER, side * (m_Shoulder + 2.8), side * 4.8, m_Forward, 1.9, 0.7, 1);
|
// One barrier and sign per approach preserve a >=4m centre passage.
|
||||||
AddProp(covers, COVER, side * (m_Shoulder + 5.6), side * 2, m_Right, 1.9, 0.7, 1);
|
AddProp(covers, BARRIER, side * 3.3, side * 6, m_Forward, 1.2, 0.4, 1.3);
|
||||||
// Paired stop-line barriers funnel traffic into a >=4m centre passage.
|
AddProp(covers, SIGN, side * 3, side * 8.5, m_Forward * (side * 1.0), 0.45, 0.4, 0.8);
|
||||||
AddProp(covers, BARRIER, side * 3.3, side * 8, m_Forward, 1.2, 0.4, 1.3);
|
|
||||||
AddProp(covers, BARRIER, side * 5.9, side * 8, m_Forward, 1.2, 0.4, 1.3);
|
|
||||||
AddProp(covers, OBSTACLE, side * (m_Shoulder + 4.5), side * 8, m_Forward, 1, 1, 1.6);
|
|
||||||
AddProp(covers, SIGN, side * 3, side * 12, m_Forward * (side * 1.0), 0.45, 0.4, 0.8);
|
|
||||||
}
|
}
|
||||||
// A small inspection/rest area behind one bay, away from the vehicle lane.
|
|
||||||
AddProp(covers, TABLE, m_Shoulder + 3, -4.8, m_Forward, 0.9, 0.7, 1.1);
|
|
||||||
AddProp(covers, SHELTER, m_Shoulder + 3, -8.5, m_Forward, 1.2, 2.4, 1.4);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void AddProp(array<ref ERR_RoadblockProp> covers, ResourceName prefab, float across, float along, vector forward, float halfWidth, float halfDepth, float height)
|
protected void AddProp(array<ref ERR_RoadblockProp> covers, ResourceName prefab, float across, float along, vector forward, float halfWidth, float halfDepth, float height)
|
||||||
@@ -220,7 +244,7 @@ class ERR_Roadblocks
|
|||||||
// Start a transaction; physical pieces are created separately in BuildPost.
|
// Start a transaction; physical pieces are created separately in BuildPost.
|
||||||
protected bool SpawnPost(array<vector> positions, array<ref ERR_RoadblockProp> covers)
|
protected bool SpawnPost(array<vector> positions, array<ref ERR_RoadblockProp> covers)
|
||||||
{
|
{
|
||||||
if (!PlayersClear() || m_Mode.Forces().Count() >= 250 || m_BuildGroup || positions.Count() != 4 || covers.Count() != ERR_RoadblockRules.PropCount()) return false;
|
if (!PlayersClear() || m_Mode.Forces().Count() >= 250 || m_BuildGroup || positions.Count() != ERR_RoadblockRules.GuardCount(m_Large) || covers.Count() != ERR_RoadblockRules.PropCount(m_Large)) return false;
|
||||||
m_BuildGroup = SCR_AIGroup.Cast(ERR_Actors.Spawn("{EC9F1AA42D0B7863}Prefabs/ERR/PrisonPatrol.et", m_Centre));
|
m_BuildGroup = SCR_AIGroup.Cast(ERR_Actors.Spawn("{EC9F1AA42D0B7863}Prefabs/ERR/PrisonPatrol.et", m_Centre));
|
||||||
if (!m_BuildGroup) return false;
|
if (!m_BuildGroup) return false;
|
||||||
m_BuildGroup.SetFaction(m_Mode.EnemyFaction()); m_BuildGroup.SetSpawnImmediately(false); m_BuildGroup.SetDeleteWhenEmpty(false); m_BuildGroup.SetPermanentLOD(0);
|
m_BuildGroup.SetFaction(m_Mode.EnemyFaction()); m_BuildGroup.SetSpawnImmediately(false); m_BuildGroup.SetDeleteWhenEmpty(false); m_BuildGroup.SetPermanentLOD(0);
|
||||||
@@ -236,12 +260,12 @@ class ERR_Roadblocks
|
|||||||
m_BuildPositions.Clear(); m_BuildCovers.Clear();
|
m_BuildPositions.Clear(); m_BuildCovers.Clear();
|
||||||
}
|
}
|
||||||
// A free Game Master camera does not force-stream bare, non-editable props
|
// A free Game Master camera does not force-stream bare, non-editable props
|
||||||
// with the nearby editable guards. Keep this bounded bank (<=540 props) on
|
// with the nearby editable guards. Keep this bounded bank (<=660 props across both quotas) on
|
||||||
// clients, including late joiners, rather than tying it to the player's body.
|
// clients, including late joiners, rather than tying it to the player's body.
|
||||||
// Do not report a completed post if any physical prop lacks a network node.
|
// Do not report a completed post if any physical prop lacks a network node.
|
||||||
protected bool ShareProps()
|
protected bool ShareProps()
|
||||||
{
|
{
|
||||||
if (m_BuildCovers.Count() != ERR_RoadblockRules.PropCount() || m_BuildSpawned.Count() != m_BuildPositions.Count() + m_BuildCovers.Count() + 1) return false;
|
if (m_BuildCovers.Count() != ERR_RoadblockRules.PropCount(m_Large) || m_BuildSpawned.Count() != m_BuildPositions.Count() + m_BuildCovers.Count() + 1) return false;
|
||||||
for (int i = m_BuildPositions.Count(); i < m_BuildPositions.Count() + m_BuildCovers.Count(); i++)
|
for (int i = m_BuildPositions.Count(); i < m_BuildPositions.Count() + m_BuildCovers.Count(); i++)
|
||||||
{
|
{
|
||||||
IEntity entity = m_BuildSpawned[i];
|
IEntity entity = m_BuildSpawned[i];
|
||||||
@@ -294,18 +318,25 @@ class ERR_Roadblocks
|
|||||||
CancelPost(); return;
|
CancelPost(); return;
|
||||||
}
|
}
|
||||||
string id = "roadblock-" + m_Positions.Count().ToString();
|
string id = "roadblock-" + m_Positions.Count().ToString();
|
||||||
if (!m_Mode.Forces().Register(id, ERR_ForceKind.CHECKPOINT, m_Centre, 4, null, m_BuildGroup, GetGame().GetWorld().GetWorldTime()))
|
if (!m_Mode.Forces().Register(id, ERR_ForceKind.CHECKPOINT, m_Centre, m_BuildPositions.Count(), null, m_BuildGroup, GetGame().GetWorld().GetWorldTime()))
|
||||||
{ CancelPost(); return; }
|
{ CancelPost(); return; }
|
||||||
foreach (IEntity ownedEntity : m_BuildSpawned) m_Mode.Own(ownedEntity);
|
foreach (IEntity ownedEntity : m_BuildSpawned) m_Mode.Own(ownedEntity);
|
||||||
m_Mode.Own(m_BuildGroup); m_Groups.Insert(m_BuildGroup); m_Positions.Insert(m_Centre);
|
m_Mode.Own(m_BuildGroup); m_Groups.Insert(m_BuildGroup); m_Positions.Insert(m_Centre);
|
||||||
|
if (m_Large) m_LargePlaced++; else m_SmallPlaced++;
|
||||||
m_BuildGroup.AddWaypoint(m_BuildHold);
|
m_BuildGroup.AddWaypoint(m_BuildHold);
|
||||||
m_Mode.RegisterGroup(m_BuildGroup); m_BuildGroup.ActivateAI();
|
m_Mode.RegisterGroup(m_BuildGroup); m_BuildGroup.ActivateAI();
|
||||||
m_Mining.Register(m_BuildGroup, m_BuildHold, m_Right);
|
m_Mining.Register(m_BuildGroup, m_BuildHold, m_Right);
|
||||||
PrintFormat("Reintroduced [roadblocks]: %1 at %2 guards=4 props=%3 networkedProps=%3 propStreaming=always roadPassage=open HQ=registered", id, m_Centre, m_BuildCovers.Count());
|
PrintFormat("Reintroduced [roadblocks]: %1 at %2 large=%3 guards=%4 props=%5 networkedProps=%5 propStreaming=always roadPassage=open HQ=registered", id, m_Centre, m_Large, m_BuildPositions.Count(), m_BuildCovers.Count());
|
||||||
m_BuildSpawned.Clear(); m_BuildGroup = null; m_BuildHold = null;
|
m_BuildSpawned.Clear(); m_BuildGroup = null; m_BuildHold = null;
|
||||||
m_BuildPositions.Clear(); m_BuildCovers.Clear();
|
m_BuildPositions.Clear(); m_BuildCovers.Clear();
|
||||||
m_NextSearch = System.GetTickCount() + 2000;
|
m_NextSearch = System.GetTickCount() + 2000;
|
||||||
}
|
}
|
||||||
|
protected vector GuardPosition(int side, int row)
|
||||||
|
{
|
||||||
|
float along = side * (row * 2 - 2.5);
|
||||||
|
if (m_Large) along = row * 6 - 9;
|
||||||
|
return ERR_Actors.Ground(m_Centre + m_Right * (side * (m_Shoulder + 1.5)) + m_Forward * along);
|
||||||
|
}
|
||||||
// 0: reject, 1: awaiting native tile, 2: ready.
|
// 0: reject, 1: awaiting native tile, 2: ready.
|
||||||
protected int Plan(out array<vector> positions, out array<ref ERR_RoadblockProp> covers)
|
protected int Plan(out array<vector> positions, out array<ref ERR_RoadblockProp> covers)
|
||||||
{
|
{
|
||||||
@@ -318,9 +349,9 @@ class ERR_Roadblocks
|
|||||||
map<int, vector> reserved = new map<int, vector>();
|
map<int, vector> reserved = new map<int, vector>();
|
||||||
for (int side = -1; side <= 1; side += 2)
|
for (int side = -1; side <= 1; side += 2)
|
||||||
{
|
{
|
||||||
for (int row = -1; row <= 1; row += 2)
|
for (int row = 0; row < ERR_RoadblockRules.GuardCount(m_Large) / 2; row++)
|
||||||
{
|
{
|
||||||
vector point = ERR_Actors.Ground(m_Centre + m_Right * (side * (m_Shoulder + 2.5)) + m_Forward * (side * (row - 1.5)));
|
vector point = GuardPosition(side, row);
|
||||||
if (!mesh.IsTileLoaded(point)) { mesh.LoadTileIn(point); return 1; }
|
if (!mesh.IsTileLoaded(point)) { mesh.LoadTileIn(point); return 1; }
|
||||||
vector reachable;
|
vector reachable;
|
||||||
if (!mesh.GetReachablePoint(point, 1, reachable) || vector.DistanceXZ(point, reachable) > 1 || !m_Placement.Clear(point, reserved)) return 0;
|
if (!mesh.GetReachablePoint(point, 1, reachable) || vector.DistanceXZ(point, reachable) > 1 || !m_Placement.Clear(point, reserved)) return 0;
|
||||||
@@ -335,7 +366,10 @@ class ERR_Roadblocks
|
|||||||
if (m_Ready) return ERR_PrepareResult.READY;
|
if (m_Ready) return ERR_PrepareResult.READY;
|
||||||
if (!m_Begun)
|
if (!m_Begun)
|
||||||
{
|
{
|
||||||
m_Begun = true; m_Started = System.GetTickCount(); m_Target = m_Mode.Settings().Get("roadblockCount");
|
m_Begun = true; m_Started = System.GetTickCount(); m_SmallTarget = m_Mode.Settings().Get("roadblockCount");
|
||||||
|
m_LargeTarget = m_Mode.Settings().Get("largeRoadblockCount");
|
||||||
|
m_MainRoadWidth = m_Mode.Settings().Get("mainRoadWidthMetres");
|
||||||
|
m_Target = m_SmallTarget + m_LargeTarget;
|
||||||
if (m_Target == 0) return Finish("disabled");
|
if (m_Target == 0) return Finish("disabled");
|
||||||
#ifdef WORKBENCH
|
#ifdef WORKBENCH
|
||||||
// The legacy convoy/hq-memory fixtures use the original 60-body allowance and six-squad layout.
|
// The legacy convoy/hq-memory fixtures use the original 60-body allowance and six-squad layout.
|
||||||
|
|||||||
@@ -0,0 +1,180 @@
|
|||||||
|
// Soft integration: use only the inspected unarmed Motorboat when loaded.
|
||||||
|
// No foreign script type, copied asset or mandatory Workshop dependency.
|
||||||
|
class ERR_ShoreBoatCatalog
|
||||||
|
{
|
||||||
|
ref array<ResourceName> Prefabs = {};
|
||||||
|
static bool Candidate(string path)
|
||||||
|
{
|
||||||
|
path.ToLower(); return path.EndsWith("/vehicles/boats/motorboat/motorboat_base.et");
|
||||||
|
}
|
||||||
|
protected void Add(ResourceName resource, string path = "")
|
||||||
|
{
|
||||||
|
if (Candidate(resource) && !Prefabs.Contains(resource)) Prefabs.Insert(resource);
|
||||||
|
}
|
||||||
|
void Find()
|
||||||
|
{
|
||||||
|
Prefabs.Clear(); array<string> loaded = {}; GameProject.GetLoadedAddons(loaded);
|
||||||
|
if (!loaded.Contains("610C8532D3756F2D")) return;
|
||||||
|
SearchResourcesFilter filter = new SearchResourcesFilter(); filter.fileExtensions = {"et"}; filter.recursive = true;
|
||||||
|
filter.rootPath = "$" + GameProject.GetAddonID("610C8532D3756F2D") + ":Prefabs";
|
||||||
|
ResourceDatabase.SearchResources(filter, Add);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
class ERR_ShoreBoats
|
||||||
|
{
|
||||||
|
protected ERR_GameMode m_Mode;
|
||||||
|
protected ref ERR_ShoreMap m_Map = new ERR_ShoreMap();
|
||||||
|
protected ref ERR_ShoreBoatCatalog m_Catalog = new ERR_ShoreBoatCatalog();
|
||||||
|
protected ref array<vector> m_Origins = {};
|
||||||
|
protected bool m_Started, m_Stopped;
|
||||||
|
protected int m_Target, m_Chance, m_CrossingChance, m_Attempts;
|
||||||
|
protected string m_State = "waiting";
|
||||||
|
void ERR_ShoreBoats(ERR_GameMode mode) { m_Mode = mode; }
|
||||||
|
int Count() { return m_Origins.Count(); }
|
||||||
|
string Status() { return string.Format("Shore boats: %1/%2; %3. Used boats remain; no replacements.", Count(), m_Target, m_State); }
|
||||||
|
void Start()
|
||||||
|
{
|
||||||
|
if (m_Started || m_Stopped || !Replication.IsServer() || !m_Mode) return;
|
||||||
|
m_Started = true; m_Target = m_Mode.Settings().Get("shoreBoatCount");
|
||||||
|
m_Chance = m_Mode.Settings().Get("shoreBoatChancePercent"); m_CrossingChance = m_Mode.Settings().Get("shoreBoatCrossingChancePercent");
|
||||||
|
if (m_Target <= 0) { m_State = "disabled"; return; }
|
||||||
|
m_Catalog.Find();
|
||||||
|
if (m_Catalog.Prefabs.IsEmpty()) { m_State = "Motorboat not loaded"; return; }
|
||||||
|
vector low, high; GetGame().GetWorldEntity().GetWorldBounds(low, high);
|
||||||
|
m_Map.Setup(low, high); m_State = "surveying coastlines";
|
||||||
|
GetGame().GetCallqueue().CallLater(Step, 100, true);
|
||||||
|
}
|
||||||
|
protected bool Unobserved(vector position)
|
||||||
|
{
|
||||||
|
array<int> ids = {}; GetGame().GetPlayerManager().GetPlayers(ids);
|
||||||
|
SCR_EditorManagerCore editors = SCR_EditorManagerCore.Cast(SCR_EditorManagerCore.GetInstance(SCR_EditorManagerCore));
|
||||||
|
foreach (int id : ids)
|
||||||
|
{
|
||||||
|
SCR_PlayerController controller = SCR_PlayerController.Cast(GetGame().GetPlayerManager().GetPlayerController(id));
|
||||||
|
if (!controller) return false;
|
||||||
|
SCR_EditorManagerEntity editor; if (editors) editor = editors.GetEditorManager(id);
|
||||||
|
if (editor && editor.IsOpened()) return false;
|
||||||
|
array<IEntity> actors = {}; IEntity body = controller.GetControlledEntity(); if (body) actors.Insert(body);
|
||||||
|
if (controller.ERR_ObserverState() != ERR_ObserverReason.NONE)
|
||||||
|
{
|
||||||
|
IEntity watched = controller.ERR_ObservedEntity(); if (!watched) return false; actors.Insert(watched);
|
||||||
|
}
|
||||||
|
else if (!body) return false;
|
||||||
|
foreach (IEntity actor : actors)
|
||||||
|
{
|
||||||
|
if (!ERR_ForceRules.FiniteVector(actor.GetOrigin()) || vector.DistanceXZ(actor.GetOrigin(), position) < 500) return false;
|
||||||
|
TraceParam ray = new TraceParam(); ray.Start = actor.GetOrigin() + "0 1.7 0"; ray.End = position + "0 2 0";
|
||||||
|
ray.Flags = TraceFlags.WORLD | TraceFlags.ENTS; ray.LayerMask = TRACE_LAYER_CAMERA;
|
||||||
|
array<IEntity> excluded = {actor}; IEntity ride = CompartmentAccessComponent.GetVehicleIn(actor); if (ride) excluded.Insert(ride); ray.ExcludeArray = excluded;
|
||||||
|
float fraction = GetGame().GetWorld().TraceMove(ray, null);
|
||||||
|
if (!ERR_ForceRules.Finite(fraction) || fraction >= 0.995) return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
protected float Depth(vector point)
|
||||||
|
{
|
||||||
|
return GetGame().GetWorld().GetOceanHeight(point[0], point[2]) - SCR_TerrainHelper.GetTerrainY(point);
|
||||||
|
}
|
||||||
|
protected bool Clear(vector position, vector angles, IEntity excluded = null)
|
||||||
|
{
|
||||||
|
vector matrix[4]; Math3D.AnglesToMatrix(angles, matrix);
|
||||||
|
// Hull, boarding space and a short navigable outward departure corridor.
|
||||||
|
for (int z = -7; z <= 21; z += 7)
|
||||||
|
for (int x = -3; x <= 3; x += 3)
|
||||||
|
{
|
||||||
|
vector sample = position + matrix[0] * (x * 1.0) + matrix[2] * (z * 1.0);
|
||||||
|
float depth = Depth(sample); if (!ERR_ForceRules.Finite(depth) || depth < 2) return false;
|
||||||
|
}
|
||||||
|
TraceOBB hull = new TraceOBB(); hull.Start = position; hull.End = position + "0 0.01 0";
|
||||||
|
hull.Mins = "-3 -1.5 -7"; hull.Maxs = "3 4 7";
|
||||||
|
for (int i = 0; i < 3; i++) hull.Mat[i] = matrix[i];
|
||||||
|
hull.Flags = TraceFlags.WORLD | TraceFlags.ENTS; hull.Exclude = excluded;
|
||||||
|
float fraction = GetGame().GetWorld().TraceMove(hull, null);
|
||||||
|
return ERR_ForceRules.Finite(fraction) && fraction >= 1;
|
||||||
|
}
|
||||||
|
protected bool Survey(int edge, out vector position, out vector angles, out bool crossing)
|
||||||
|
{
|
||||||
|
int land = Math.Floor(edge / 4), water = m_Map.Neighbor(land, edge % 4);
|
||||||
|
if (water < 0) return false;
|
||||||
|
vector dry = m_Map.Point(land), wet = m_Map.Point(water);
|
||||||
|
if (Depth(dry) >= -0.5 || Depth(wet) <= 0) return false;
|
||||||
|
vector forward = wet - dry; forward[1] = 0; forward.Normalize(); angles = forward.VectorToAngles();
|
||||||
|
for (int n = 0; n < 10; n++)
|
||||||
|
{
|
||||||
|
vector middle = (dry + wet) * 0.5;
|
||||||
|
if (Depth(middle) > 0) wet = middle; else dry = middle;
|
||||||
|
}
|
||||||
|
// Reject cliffs: a person must be able to approach the shoreline from land.
|
||||||
|
vector approach = dry - forward * 8;
|
||||||
|
if (Depth(approach) < -3 || Depth(approach) > -0.2) return false;
|
||||||
|
crossing = m_Map.FacingIsland(land, wet, forward);
|
||||||
|
for (int offset = 12; offset <= 36; offset += 6)
|
||||||
|
{
|
||||||
|
position = wet + forward * (offset * 1.0); position[1] = GetGame().GetWorld().GetOceanHeight(position[0], position[2]);
|
||||||
|
if (!ERR_ForceRules.FiniteVector(position)) return false;
|
||||||
|
bool nearby;
|
||||||
|
foreach (vector existing : m_Origins) if (vector.DistanceXZ(existing, position) < 700) nearby = true;
|
||||||
|
if (nearby) return false;
|
||||||
|
if (Depth(position) > 8 || !Clear(position, angles)) continue;
|
||||||
|
return Unobserved(position);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
protected bool Spawn(vector position, vector angles)
|
||||||
|
{
|
||||||
|
ResourceName prefab = ERR_VehicleSelection.Choose(m_Catalog.Prefabs, 5);
|
||||||
|
if (prefab.IsEmpty() || !Unobserved(position) || !Clear(position, angles)) return false;
|
||||||
|
IEntity spawned = ERR_Actors.Spawn(prefab, position, angles);
|
||||||
|
Vehicle boat = Vehicle.Cast(spawned);
|
||||||
|
if (!boat) { ERR_Actors.DeleteSpawned(spawned); return false; }
|
||||||
|
VehicleWheeledSimulation simulation = VehicleWheeledSimulation.Cast(boat.FindComponent(VehicleWheeledSimulation));
|
||||||
|
if (!simulation || !simulation.IsValid())
|
||||||
|
{
|
||||||
|
m_State = "boat mod has an incompatible drivetrain; spawning stopped";
|
||||||
|
GetGame().ERR_ServerVehicles().Reject(prefab, 5);
|
||||||
|
ERR_Actors.DeleteSpawned(boat); Stop(); return false;
|
||||||
|
}
|
||||||
|
array<BaseCompartmentSlot> seats = {}; bool driver; bool invalid;
|
||||||
|
if (!ERR_ConvoyRules.CollectSeats(boat, seats)) invalid = true;
|
||||||
|
foreach (BaseCompartmentSlot seat : seats)
|
||||||
|
{
|
||||||
|
if (seat.GetOccupant() || seat.GetType() == ECompartmentType.TURRET) invalid = true;
|
||||||
|
if (seat.GetType() == ECompartmentType.PILOT && seat.IsCompartmentAccessible()) driver = true;
|
||||||
|
}
|
||||||
|
vector low, high; boat.GetBounds(low, high);
|
||||||
|
if (!driver || invalid || !ERR_ForceRules.FiniteVector(low) || !ERR_ForceRules.FiniteVector(high)
|
||||||
|
|| low[0] < -3 || high[0] > 3 || low[2] < -7 || high[2] > 7 || !Clear(position, angles, boat))
|
||||||
|
{ ERR_Actors.DeleteSpawned(boat); return false; }
|
||||||
|
// Empty unaffiliated vehicle: first occupant receives faction ownership.
|
||||||
|
FactionAffiliationComponent affiliation = FactionAffiliationComponent.Cast(boat.FindComponent(FactionAffiliationComponent));
|
||||||
|
if (affiliation) affiliation.SetAffiliatedFaction(null);
|
||||||
|
SCR_FuelManagerComponent fuel = SCR_FuelManagerComponent.Cast(boat.FindComponent(SCR_FuelManagerComponent));
|
||||||
|
if (fuel) fuel.SetTotalFuelPercentage(Math.RandomFloat(0.5, 0.9));
|
||||||
|
SCR_GarbageSystem garbage = SCR_GarbageSystem.GetByEntityWorld(boat);
|
||||||
|
if (garbage) garbage.UpdateBlacklist(boat, true);
|
||||||
|
m_Mode.Own(boat); m_Origins.Insert(position);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
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_Map.Ready()) { m_Map.Step(); return; }
|
||||||
|
if (Count() >= m_Target || m_Map.Edges.IsEmpty() || m_Attempts >= 4096)
|
||||||
|
{ m_State = "survey complete"; Stop(); return; }
|
||||||
|
// Each edge is considered once, in random order. Never reroll a rejected chance.
|
||||||
|
int chosen = Math.RandomInt(0, m_Map.Edges.Count()), edge = m_Map.Edges[chosen];
|
||||||
|
m_Map.Edges.Remove(chosen); m_Attempts++;
|
||||||
|
vector position, angles; bool crossing;
|
||||||
|
if (!Survey(edge, position, angles, crossing)) return;
|
||||||
|
if (Math.RandomInt(0, 100) >= ERR_ShoreMap.Chance(crossing, m_Chance, m_CrossingChance)) return;
|
||||||
|
Spawn(position, angles);
|
||||||
|
}
|
||||||
|
void Stop()
|
||||||
|
{
|
||||||
|
m_Stopped = true;
|
||||||
|
if (GetGame()) GetGame().GetCallqueue().Remove(Step);
|
||||||
|
// Boats are finite round-owned transport; movement, use or destruction never replenishes them.
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
// Bounded coarse land connectivity, followed by precise placement in ERR_ShoreBoats.
|
||||||
|
// A bay on the same landmass must not receive the inter-island probability bonus.
|
||||||
|
class ERR_ShoreMap
|
||||||
|
{
|
||||||
|
vector Low;
|
||||||
|
float Cell;
|
||||||
|
int Width, Height;
|
||||||
|
ref array<int> Parents = {};
|
||||||
|
ref array<int> Edges = {};
|
||||||
|
protected int m_Cursor, m_Phase;
|
||||||
|
void Setup(vector low, vector high)
|
||||||
|
{
|
||||||
|
Low = low; Cell = Math.Max(64, Math.Ceil(Math.Max(high[0] - low[0], high[2] - low[2]) / 256));
|
||||||
|
Width = Math.Clamp(Math.Floor((high[0] - low[0]) / Cell), 0, 256);
|
||||||
|
Height = Math.Clamp(Math.Floor((high[2] - low[2]) / Cell), 0, 256);
|
||||||
|
Parents.Clear(); Edges.Clear(); m_Cursor = 0; m_Phase = 0;
|
||||||
|
}
|
||||||
|
vector Point(int index)
|
||||||
|
{
|
||||||
|
int column = index % Width, row = Math.Floor(index / Width);
|
||||||
|
return Low + Vector((column + 0.5) * Cell, 0, (row + 0.5) * Cell);
|
||||||
|
}
|
||||||
|
int Index(vector point)
|
||||||
|
{
|
||||||
|
int x = Math.Floor((point[0] - Low[0]) / Cell), z = Math.Floor((point[2] - Low[2]) / Cell);
|
||||||
|
if (x < 0 || z < 0 || x >= Width || z >= Height) return -1;
|
||||||
|
return z * Width + x;
|
||||||
|
}
|
||||||
|
int Root(int index)
|
||||||
|
{
|
||||||
|
if (index < 0 || index >= Parents.Count() || Parents[index] < 0) return -1;
|
||||||
|
int root = index;
|
||||||
|
while (Parents[root] != root) root = Parents[root];
|
||||||
|
while (Parents[index] != index) { int next = Parents[index]; Parents[index] = root; index = next; }
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
protected bool Land(vector point)
|
||||||
|
{
|
||||||
|
float ground = SCR_TerrainHelper.GetTerrainY(point), sea = GetGame().GetWorld().GetOceanHeight(point[0], point[2]);
|
||||||
|
point[1] = ground;
|
||||||
|
return ERR_ForceRules.Finite(ground) && ERR_ForceRules.Finite(sea) && ground > sea + 0.5
|
||||||
|
&& !ChimeraWorldUtils.TryGetWaterSurfaceSimple(GetGame().GetWorld(), point);
|
||||||
|
}
|
||||||
|
protected void Join(int a, int b)
|
||||||
|
{
|
||||||
|
int other = Root(b); if (other < 0) return;
|
||||||
|
// Do not connect sampled land across an intervening narrow channel.
|
||||||
|
for (int sample = 1; sample <= 3; sample++) if (!Land(vector.Lerp(Point(a), Point(b), sample * 0.25))) return;
|
||||||
|
Parents[Root(a)] = other;
|
||||||
|
}
|
||||||
|
int Neighbor(int index, int direction)
|
||||||
|
{
|
||||||
|
int x = index % Width, z = Math.Floor(index / Width);
|
||||||
|
if (direction == 0) x++; else if (direction == 1) x--; else if (direction == 2) z++; else z--;
|
||||||
|
if (x < 0 || z < 0 || x >= Width || z >= Height) return -1;
|
||||||
|
return z * Width + x;
|
||||||
|
}
|
||||||
|
bool Ready() { return m_Phase == 2; }
|
||||||
|
void Step()
|
||||||
|
{
|
||||||
|
int began = System.GetTickCount();
|
||||||
|
for (int work = 0; work < 64 && m_Phase < 2; work++)
|
||||||
|
{
|
||||||
|
if (m_Cursor >= Width * Height) { m_Cursor = 0; m_Phase++; continue; }
|
||||||
|
int index = m_Cursor++;
|
||||||
|
if (m_Phase == 0)
|
||||||
|
{
|
||||||
|
if (!Land(Point(index))) Parents.Insert(-1);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Parents.Insert(index);
|
||||||
|
if (index % Width > 0) Join(index, index - 1);
|
||||||
|
if (index >= Width) Join(index, index - Width);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (Root(index) >= 0)
|
||||||
|
for (int direction = 0; direction < 4; direction++)
|
||||||
|
{
|
||||||
|
int next = Neighbor(index, direction);
|
||||||
|
if (next >= 0 && Root(next) < 0 && Edges.Count() < 32768) Edges.Insert(index * 4 + direction);
|
||||||
|
}
|
||||||
|
if (System.GetTickCount() - began >= 4) return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bool FacingIsland(int land, vector water, vector forward)
|
||||||
|
{
|
||||||
|
int own = Root(land); if (own < 0) return false;
|
||||||
|
// Scan an outward fan. Stop at the FIRST land on each ray, including own bays.
|
||||||
|
for (int ray = -1; ray <= 1; ray++)
|
||||||
|
{
|
||||||
|
vector direction = ERR_CampTerrain.Rotate(forward, ray * 30);
|
||||||
|
for (float distance = Cell; distance <= 4000; distance += Cell * 0.5)
|
||||||
|
{
|
||||||
|
int index = Index(water + direction * distance); if (index < 0) break;
|
||||||
|
int other = Root(index); if (other < 0) continue;
|
||||||
|
if (other != own) return true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
static int Chance(bool facing, int ordinary, int crossing)
|
||||||
|
{
|
||||||
|
if (facing && crossing > ordinary) return crossing;
|
||||||
|
return ordinary;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,6 +13,8 @@ class ERR_IntelAdminProvider : ERR_AdminProvider
|
|||||||
if (arguments == "intel show")
|
if (arguments == "intel show")
|
||||||
{
|
{
|
||||||
replies.Insert("aiIntelChancePercent=" + mode.Settings().Get("aiIntelChancePercent").ToString());
|
replies.Insert("aiIntelChancePercent=" + mode.Settings().Get("aiIntelChancePercent").ToString());
|
||||||
|
replies.Insert("radioIntelSiteCount=" + mode.Settings().Get("radioIntelSiteCount").ToString() + " (next round)");
|
||||||
|
if (mode.Discovery()) replies.Insert(mode.Discovery().RadioIntelStatus());
|
||||||
replies.Insert("showAllSites=" + mode.Settings().Get("showAllSites").ToString());
|
replies.Insert("showAllSites=" + mode.Settings().Get("showAllSites").ToString());
|
||||||
replies.Insert("intelRevealChancePercent=" + mode.Settings().Get("intelRevealChancePercent").ToString());
|
replies.Insert("intelRevealChancePercent=" + mode.Settings().Get("intelRevealChancePercent").ToString());
|
||||||
return true;
|
return true;
|
||||||
@@ -31,6 +33,7 @@ class ERR_IntelAdminProvider : ERR_AdminProvider
|
|||||||
override bool Help(string topic, array<string> replies)
|
override bool Help(string topic, array<string> replies)
|
||||||
{
|
{
|
||||||
if (topic != "intel") return false;
|
if (topic != "intel") return false;
|
||||||
|
replies.Insert("/esc intel radiosites show|set N|help (0-20; 0 disables); non-radio sites required before radio intel, next round.");
|
||||||
replies.Insert("/esc intel sites show|set on|off; show anonymous site positions and let intel identify their types.");
|
replies.Insert("/esc intel sites show|set on|off; show anonymous site positions and let intel identify their types.");
|
||||||
replies.Insert("/esc intel show | intel carrying show|set N (0-100; alias: intel chance)");
|
replies.Insert("/esc intel show | intel carrying show|set N (0-100; alias: intel chance)");
|
||||||
replies.Insert("Carrying changes affect newly registered AI. /esc intel reroll applies to eligible living AI; intel reroll show reports progress.");
|
replies.Insert("Carrying changes affect newly registered AI. /esc intel reroll applies to eligible living AI; intel reroll show reports progress.");
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ modded class ERR_GameMode
|
|||||||
protected float m_ERRIntelMessageAt;
|
protected float m_ERRIntelMessageAt;
|
||||||
protected float m_ERRIntelExhaustedAt;
|
protected float m_ERRIntelExhaustedAt;
|
||||||
protected bool m_ERRIntelDuplicateReported, m_ERRIntelExhaustionReported;
|
protected bool m_ERRIntelDuplicateReported, m_ERRIntelExhaustionReported;
|
||||||
|
protected bool m_ERRRadioIntelWaitingReported;
|
||||||
protected bool m_ERRIntelFeedbackStopped;
|
protected bool m_ERRIntelFeedbackStopped;
|
||||||
protected int m_ERRIntelMessageSequence;
|
protected int m_ERRIntelMessageSequence;
|
||||||
protected int m_ERRIntelReceivedSequence;
|
protected int m_ERRIntelReceivedSequence;
|
||||||
@@ -62,6 +63,15 @@ modded class ERR_GameMode
|
|||||||
m_ERRIntelReceivedSequence = sequence;
|
m_ERRIntelReceivedSequence = sequence;
|
||||||
SCR_ChatComponent.RadioProtocolMessage(message);
|
SCR_ChatComponent.RadioProtocolMessage(message);
|
||||||
}
|
}
|
||||||
|
void ERR_RadioIntelWaiting()
|
||||||
|
{
|
||||||
|
if (!Replication.IsServer() || m_ERRIntelFeedbackStopped || !Active() || m_ERRRadioIntelWaitingReported) return;
|
||||||
|
m_ERRRadioIntelWaitingReported = true;
|
||||||
|
string message = "Keep this radio document. Identify more non-radio sites to decipher it; it will be read automatically when ready.";
|
||||||
|
m_ERRIntelMessageSequence++;
|
||||||
|
ERR_ReceiveDiscoveryFeedback(m_ERRIntelMessageSequence, message);
|
||||||
|
Rpc(ERR_ReceiveDiscoveryFeedback, m_ERRIntelMessageSequence, message);
|
||||||
|
}
|
||||||
void ERR_IntelExhausted(bool duplicate = false)
|
void ERR_IntelExhausted(bool duplicate = false)
|
||||||
{
|
{
|
||||||
if (!Replication.IsServer() || m_ERRIntelFeedbackStopped || !Active()) return;
|
if (!Replication.IsServer() || m_ERRIntelFeedbackStopped || !Active()) return;
|
||||||
|
|||||||
@@ -48,16 +48,18 @@ class ERR_IntelEvidence
|
|||||||
}
|
}
|
||||||
// Spread new folders over unfinished sites. Counts persist after recovery or
|
// Spread new folders over unfinished sites. Counts persist after recovery or
|
||||||
// retirement so extra camps/AI do not repeatedly roll the same early targets.
|
// retirement so extra camps/AI do not repeatedly roll the same early targets.
|
||||||
// Existing sealed contents are never rewritten when team knowledge changes.
|
// Only recovery of a progression-locked radio folder can redirect its contents.
|
||||||
protected string NextTarget()
|
protected string NextTarget()
|
||||||
{
|
{
|
||||||
array<ERR_LocationComponent> locations = {};
|
array<ERR_LocationComponent> locations = {};
|
||||||
ERR_GameMode.Get().Locations(locations);
|
ERR_GameMode.Get().Locations(locations);
|
||||||
array<ERR_LocationComponent> candidates = {};
|
array<ERR_LocationComponent> candidates = {};
|
||||||
int least = -1;
|
int least = -1;
|
||||||
|
bool radioAllowed = ERR_GameMode.Get().Discovery() && ERR_GameMode.Get().Discovery().RadioIntelAllowed();
|
||||||
foreach (ERR_LocationComponent location : locations)
|
foreach (ERR_LocationComponent location : locations)
|
||||||
{
|
{
|
||||||
if (!location || location.Knowledge() >= 2 || location.Kind() == ERR_LocationKind.PRISON || location.Kind() == ERR_LocationKind.EXTRACTION) continue;
|
if (!location || location.Knowledge() >= 2 || location.Kind() == ERR_LocationKind.PRISON || location.Kind() == ERR_LocationKind.EXTRACTION) continue;
|
||||||
|
if (!radioAllowed && location.Kind() == ERR_LocationKind.RADIO) continue;
|
||||||
int assigned = 0;
|
int assigned = 0;
|
||||||
m_TargetAssignments.Find(location.Id(), assigned);
|
m_TargetAssignments.Find(location.Id(), assigned);
|
||||||
if (least < 0 || assigned < least) { least = assigned; candidates.Clear(); }
|
if (least < 0 || assigned < least) { least = assigned; candidates.Clear(); }
|
||||||
@@ -99,6 +101,20 @@ class ERR_IntelEvidence
|
|||||||
foreach (ERR_IssuedIntel issued : record.Issued) if (issued.Folder == folder) return issued.SiteId;
|
foreach (ERR_IssuedIntel issued : record.Issued) if (issued.Folder == folder) return issued.SiteId;
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
// Update physical document bookkeeping only. Inspect() has already copied
|
||||||
|
// any queued report into its own Missing list; delivered HQ reports stay intact.
|
||||||
|
void Retarget(IEntity folder, string target)
|
||||||
|
{
|
||||||
|
if (!Replication.IsServer() || m_Stopped || !folder || target.IsEmpty()) return;
|
||||||
|
bool changed;
|
||||||
|
foreach (ERR_BodyEvidence record : m_Roster)
|
||||||
|
foreach (ERR_IssuedIntel issued : record.Issued)
|
||||||
|
if (issued.Folder == folder && issued.SiteId != target) { issued.SiteId = target; changed = true; }
|
||||||
|
if (!changed) return;
|
||||||
|
int assigned;
|
||||||
|
m_TargetAssignments.Find(target, assigned);
|
||||||
|
m_TargetAssignments.Set(target, assigned + 1);
|
||||||
|
}
|
||||||
void Tick(float now)
|
void Tick(float now)
|
||||||
{
|
{
|
||||||
if (m_Stopped) return;
|
if (m_Stopped) return;
|
||||||
|
|||||||
@@ -2,8 +2,16 @@ FrameWidgetClass {
|
|||||||
Name "ERR_AdminRoot" Slot FrameWidgetSlot { Anchor 0 0 1 1 OffsetLeft 0 OffsetTop 0 OffsetRight 0 OffsetBottom 0 }
|
Name "ERR_AdminRoot" Slot FrameWidgetSlot { Anchor 0 0 1 1 OffsetLeft 0 OffsetTop 0 OffsetRight 0 OffsetBottom 0 }
|
||||||
{
|
{
|
||||||
ImageWidgetClass { Name "DimBackground" Slot FrameWidgetSlot { Anchor 0 0 1 1 OffsetLeft 0 OffsetTop 0 OffsetRight 0 OffsetBottom 0 } Color 0 0 0 0.65 }
|
ImageWidgetClass { Name "DimBackground" Slot FrameWidgetSlot { Anchor 0 0 1 1 OffsetLeft 0 OffsetTop 0 OffsetRight 0 OffsetBottom 0 } Color 0 0 0 0.65 }
|
||||||
|
ScaleWidgetClass {
|
||||||
|
Name "PanelScale" "Scale Mode" "User defined"
|
||||||
|
Slot FrameWidgetSlot { Anchor 0 0 1 1 OffsetLeft 0 OffsetTop 0 OffsetRight 0 OffsetBottom 0 }
|
||||||
|
{
|
||||||
|
SizeLayoutWidgetClass {
|
||||||
|
Name "PanelSize" AllowWidthOverride 1 WidthOverride 1220 AllowHeightOverride 1 HeightOverride 760
|
||||||
|
Slot AlignableSlot { HorizontalAlign 1 VerticalAlign 1 }
|
||||||
|
{
|
||||||
FrameWidgetClass {
|
FrameWidgetClass {
|
||||||
Name "Panel" Slot FrameWidgetSlot { Anchor 0.5 0.5 0.5 0.5 PositionX 0 OffsetLeft 0 PositionY 0 OffsetTop 0 SizeX 1220 OffsetRight -1220 SizeY 760 OffsetBottom -760 Alignment 0.5 0.5 }
|
Name "Panel" Slot LayoutSlot { HorizontalAlign 3 VerticalAlign 3 }
|
||||||
{
|
{
|
||||||
ImageWidgetClass { Name "PanelBackground" Slot FrameWidgetSlot { Anchor 0 0 1 1 OffsetLeft 0 OffsetTop 0 OffsetRight 0 OffsetBottom 0 } Color 0.035 0.05 0.065 0.98 }
|
ImageWidgetClass { Name "PanelBackground" Slot FrameWidgetSlot { Anchor 0 0 1 1 OffsetLeft 0 OffsetTop 0 OffsetRight 0 OffsetBottom 0 } Color 0.035 0.05 0.065 0.98 }
|
||||||
TextWidgetClass { Name "Title" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 20 OffsetTop 20 SizeX 990 OffsetRight -1018 SizeY 44 OffsetBottom -64 } Color 0.9 0.92 0.94 1 Text "Co10 Escape Reintroduced" "Font Size" 32 "Min Font Size" 32 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
TextWidgetClass { Name "Title" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 20 OffsetTop 20 SizeX 990 OffsetRight -1018 SizeY 44 OffsetBottom -64 } Color 0.9 0.92 0.94 1 Text "Co10 Escape Reintroduced" "Font Size" 32 "Min Font Size" 32 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
@@ -18,20 +26,30 @@ ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_But
|
|||||||
ImageWidgetClass { "Is Visible" 0 Name "Selected_OpenWorld" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 98 OffsetTop 98 SizeX 180 OffsetRight -180 SizeY 3 OffsetBottom -101 } Color 0.9 0.66 0.25 1 }
|
ImageWidgetClass { "Is Visible" 0 Name "Selected_OpenWorld" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 98 OffsetTop 98 SizeX 180 OffsetRight -180 SizeY 3 OffsetBottom -101 } Color 0.9 0.66 0.25 1 }
|
||||||
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "OpenTerrains" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 108 OffsetTop 108 SizeX 180 OffsetRight -180 SizeY 42 OffsetBottom -150 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Terrains" } } }
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "OpenTerrains" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 108 OffsetTop 108 SizeX 180 OffsetRight -180 SizeY 42 OffsetBottom -150 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Terrains" } } }
|
||||||
ImageWidgetClass { "Is Visible" 0 Name "Selected_OpenTerrains" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 152 OffsetTop 152 SizeX 180 OffsetRight -180 SizeY 3 OffsetBottom -155 } Color 0.9 0.66 0.25 1 }
|
ImageWidgetClass { "Is Visible" 0 Name "Selected_OpenTerrains" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 152 OffsetTop 152 SizeX 180 OffsetRight -180 SizeY 3 OffsetBottom -155 } Color 0.9 0.66 0.25 1 }
|
||||||
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "OpenTelemetry" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 162 OffsetTop 162 SizeX 180 OffsetRight -180 SizeY 42 OffsetBottom -204 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Statistics" } } }
|
||||||
|
ImageWidgetClass { "Is Visible" 0 Name "Selected_OpenTelemetry" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 206 OffsetTop 206 SizeX 180 OffsetRight -180 SizeY 3 OffsetBottom -209 } Color 0.9 0.66 0.25 1 }
|
||||||
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "OpenRecommendations" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 216 OffsetTop 216 SizeX 180 OffsetRight -180 SizeY 42 OffsetBottom -258 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Recommended mods" } } }
|
||||||
|
ImageWidgetClass { "Is Visible" 0 Name "Selected_OpenRecommendations" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 260 OffsetTop 260 SizeX 180 OffsetRight -180 SizeY 3 OffsetBottom -263 } Color 0.9 0.66 0.25 1 }
|
||||||
} }
|
} }
|
||||||
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "Main_Forces" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 174 OffsetLeft 174 PositionY 106 OffsetTop 106 SizeX 138 OffsetRight -312 SizeY 42 OffsetBottom -148 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Forces" } } }
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "Main_Forces" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 174 OffsetLeft 174 PositionY 106 OffsetTop 106 SizeX 138 OffsetRight -312 SizeY 42 OffsetBottom -148 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Forces" } } }
|
||||||
ImageWidgetClass { "Is Visible" 0 Name "Selected_Main_Forces" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 174 OffsetLeft 174 PositionY 150 OffsetTop 150 SizeX 138 OffsetRight -312 SizeY 3 OffsetBottom -153 } Color 0.9 0.66 0.25 1 }
|
ImageWidgetClass { "Is Visible" 0 Name "Selected_Main_Forces" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 174 OffsetLeft 174 PositionY 150 OffsetTop 150 SizeX 138 OffsetRight -312 SizeY 3 OffsetBottom -153 } Color 0.9 0.66 0.25 1 }
|
||||||
FrameWidgetClass { "Is Visible" 0 Name "Sidebar_Forces" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 166 OffsetTop 166 SizeX 180 OffsetRight -208 SizeY 480 OffsetBottom -646 } {
|
FrameWidgetClass { "Is Visible" 0 Name "Sidebar_Forces" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 166 OffsetTop 166 SizeX 180 OffsetRight -208 SizeY 480 OffsetBottom -646 } {
|
||||||
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "OpenForces" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 0 OffsetTop 0 SizeX 180 OffsetRight -180 SizeY 42 OffsetBottom -42 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Patrols" } } }
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "OpenForces" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 0 OffsetTop 0 SizeX 180 OffsetRight -180 SizeY 42 OffsetBottom -42 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Patrols" } } }
|
||||||
ImageWidgetClass { "Is Visible" 0 Name "Selected_OpenForces" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 44 OffsetTop 44 SizeX 180 OffsetRight -180 SizeY 3 OffsetBottom -47 } Color 0.9 0.66 0.25 1 }
|
ImageWidgetClass { "Is Visible" 0 Name "Selected_OpenForces" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 44 OffsetTop 44 SizeX 180 OffsetRight -180 SizeY 3 OffsetBottom -47 } Color 0.9 0.66 0.25 1 }
|
||||||
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "OpenFactions" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 54 OffsetTop 54 SizeX 180 OffsetRight -180 SizeY 42 OffsetBottom -96 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Factions" } } }
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "OpenCheckpoints" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 54 OffsetTop 54 SizeX 180 OffsetRight -180 SizeY 42 OffsetBottom -96 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Checkpoints" } } }
|
||||||
ImageWidgetClass { "Is Visible" 0 Name "Selected_OpenFactions" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 98 OffsetTop 98 SizeX 180 OffsetRight -180 SizeY 3 OffsetBottom -101 } Color 0.9 0.66 0.25 1 }
|
ImageWidgetClass { "Is Visible" 0 Name "Selected_OpenCheckpoints" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 98 OffsetTop 98 SizeX 180 OffsetRight -180 SizeY 3 OffsetBottom -101 } Color 0.9 0.66 0.25 1 }
|
||||||
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "OpenGuards" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 108 OffsetTop 108 SizeX 180 OffsetRight -180 SizeY 42 OffsetBottom -150 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Site guards" } } }
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "OpenFactions" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 108 OffsetTop 108 SizeX 180 OffsetRight -180 SizeY 42 OffsetBottom -150 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Factions" } } }
|
||||||
ImageWidgetClass { "Is Visible" 0 Name "Selected_OpenGuards" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 152 OffsetTop 152 SizeX 180 OffsetRight -180 SizeY 3 OffsetBottom -155 } Color 0.9 0.66 0.25 1 }
|
ImageWidgetClass { "Is Visible" 0 Name "Selected_OpenFactions" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 152 OffsetTop 152 SizeX 180 OffsetRight -180 SizeY 3 OffsetBottom -155 } Color 0.9 0.66 0.25 1 }
|
||||||
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "OpenSites" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 162 OffsetTop 162 SizeX 180 OffsetRight -180 SizeY 42 OffsetBottom -204 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Field sites" } } }
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "OpenGuards" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 162 OffsetTop 162 SizeX 180 OffsetRight -180 SizeY 42 OffsetBottom -204 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Site guards" } } }
|
||||||
ImageWidgetClass { "Is Visible" 0 Name "Selected_OpenSites" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 206 OffsetTop 206 SizeX 180 OffsetRight -180 SizeY 3 OffsetBottom -209 } Color 0.9 0.66 0.25 1 }
|
ImageWidgetClass { "Is Visible" 0 Name "Selected_OpenGuards" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 206 OffsetTop 206 SizeX 180 OffsetRight -180 SizeY 3 OffsetBottom -209 } Color 0.9 0.66 0.25 1 }
|
||||||
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "OpenSarka" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 216 OffsetTop 216 SizeX 180 OffsetRight -180 SizeY 42 OffsetBottom -258 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Šárka hunters" } } }
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "OpenSites" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 216 OffsetTop 216 SizeX 180 OffsetRight -180 SizeY 42 OffsetBottom -258 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Field sites" } } }
|
||||||
ImageWidgetClass { "Is Visible" 0 Name "Selected_OpenSarka" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 260 OffsetTop 260 SizeX 180 OffsetRight -180 SizeY 3 OffsetBottom -263 } Color 0.9 0.66 0.25 1 }
|
ImageWidgetClass { "Is Visible" 0 Name "Selected_OpenSites" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 260 OffsetTop 260 SizeX 180 OffsetRight -180 SizeY 3 OffsetBottom -263 } Color 0.9 0.66 0.25 1 }
|
||||||
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "OpenCrashSites" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 270 OffsetTop 270 SizeX 180 OffsetRight -180 SizeY 42 OffsetBottom -312 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Crash sites" } } }
|
||||||
|
ImageWidgetClass { "Is Visible" 0 Name "Selected_OpenCrashSites" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 314 OffsetTop 314 SizeX 180 OffsetRight -180 SizeY 3 OffsetBottom -317 } Color 0.9 0.66 0.25 1 }
|
||||||
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "OpenSarka" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 324 OffsetTop 324 SizeX 180 OffsetRight -180 SizeY 42 OffsetBottom -366 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Šárka hunters" } } }
|
||||||
|
ImageWidgetClass { "Is Visible" 0 Name "Selected_OpenSarka" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 368 OffsetTop 368 SizeX 180 OffsetRight -180 SizeY 3 OffsetBottom -371 } Color 0.9 0.66 0.25 1 }
|
||||||
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "OpenPacks" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 378 OffsetTop 378 SizeX 180 OffsetRight -180 SizeY 42 OffsetBottom -420 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Enemy packs" } } }
|
||||||
|
ImageWidgetClass { "Is Visible" 0 Name "Selected_OpenPacks" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 422 OffsetTop 422 SizeX 180 OffsetRight -180 SizeY 3 OffsetBottom -425 } Color 0.9 0.66 0.25 1 }
|
||||||
} }
|
} }
|
||||||
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "Main_HQ" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 320 OffsetLeft 320 PositionY 106 OffsetTop 106 SizeX 138 OffsetRight -458 SizeY 42 OffsetBottom -148 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "HQ" } } }
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "Main_HQ" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 320 OffsetLeft 320 PositionY 106 OffsetTop 106 SizeX 138 OffsetRight -458 SizeY 42 OffsetBottom -148 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "HQ" } } }
|
||||||
ImageWidgetClass { "Is Visible" 0 Name "Selected_Main_HQ" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 320 OffsetLeft 320 PositionY 150 OffsetTop 150 SizeX 138 OffsetRight -458 SizeY 3 OffsetBottom -153 } Color 0.9 0.66 0.25 1 }
|
ImageWidgetClass { "Is Visible" 0 Name "Selected_Main_HQ" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 320 OffsetLeft 320 PositionY 150 OffsetTop 150 SizeX 138 OffsetRight -458 SizeY 3 OffsetBottom -153 } Color 0.9 0.66 0.25 1 }
|
||||||
@@ -50,37 +68,39 @@ ImageWidgetClass { "Is Visible" 0 Name "Selected_Main_Intel" Slot FrameWidgetSlo
|
|||||||
FrameWidgetClass { "Is Visible" 0 Name "Sidebar_Intel" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 166 OffsetTop 166 SizeX 180 OffsetRight -208 SizeY 480 OffsetBottom -646 } {
|
FrameWidgetClass { "Is Visible" 0 Name "Sidebar_Intel" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 166 OffsetTop 166 SizeX 180 OffsetRight -208 SizeY 480 OffsetBottom -646 } {
|
||||||
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "OpenIntel" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 0 OffsetTop 0 SizeX 180 OffsetRight -180 SizeY 42 OffsetBottom -42 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Intelligence" } } }
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "OpenIntel" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 0 OffsetTop 0 SizeX 180 OffsetRight -180 SizeY 42 OffsetBottom -42 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Intelligence" } } }
|
||||||
ImageWidgetClass { "Is Visible" 0 Name "Selected_OpenIntel" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 44 OffsetTop 44 SizeX 180 OffsetRight -180 SizeY 3 OffsetBottom -47 } Color 0.9 0.66 0.25 1 }
|
ImageWidgetClass { "Is Visible" 0 Name "Selected_OpenIntel" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 44 OffsetTop 44 SizeX 180 OffsetRight -180 SizeY 3 OffsetBottom -47 } Color 0.9 0.66 0.25 1 }
|
||||||
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "OpenRadioIntel" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 54 OffsetTop 54 SizeX 180 OffsetRight -180 SizeY 42 OffsetBottom -96 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Radio intel" } } }
|
||||||
|
ImageWidgetClass { "Is Visible" 0 Name "Selected_OpenRadioIntel" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 98 OffsetTop 98 SizeX 180 OffsetRight -180 SizeY 3 OffsetBottom -101 } Color 0.9 0.66 0.25 1 }
|
||||||
} }
|
} }
|
||||||
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "Main_Items" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 612 OffsetLeft 612 PositionY 106 OffsetTop 106 SizeX 138 OffsetRight -750 SizeY 42 OffsetBottom -148 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Items" } } }
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "Main_Content" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 612 OffsetLeft 612 PositionY 106 OffsetTop 106 SizeX 138 OffsetRight -750 SizeY 42 OffsetBottom -148 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Content" } } }
|
||||||
ImageWidgetClass { "Is Visible" 0 Name "Selected_Main_Items" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 612 OffsetLeft 612 PositionY 150 OffsetTop 150 SizeX 138 OffsetRight -750 SizeY 3 OffsetBottom -153 } Color 0.9 0.66 0.25 1 }
|
ImageWidgetClass { "Is Visible" 0 Name "Selected_Main_Content" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 612 OffsetLeft 612 PositionY 150 OffsetTop 150 SizeX 138 OffsetRight -750 SizeY 3 OffsetBottom -153 } Color 0.9 0.66 0.25 1 }
|
||||||
FrameWidgetClass { "Is Visible" 0 Name "Sidebar_Items" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 166 OffsetTop 166 SizeX 180 OffsetRight -208 SizeY 480 OffsetBottom -646 } {
|
FrameWidgetClass { "Is Visible" 0 Name "Sidebar_Content" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 166 OffsetTop 166 SizeX 180 OffsetRight -208 SizeY 480 OffsetBottom -646 } {
|
||||||
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "OpenItems" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 0 OffsetTop 0 SizeX 180 OffsetRight -180 SizeY 42 OffsetBottom -42 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Extra equipment" } } }
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "OpenMods" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 0 OffsetTop 0 SizeX 180 OffsetRight -180 SizeY 42 OffsetBottom -42 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "1. Mods" } } }
|
||||||
ImageWidgetClass { "Is Visible" 0 Name "Selected_OpenItems" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 44 OffsetTop 44 SizeX 180 OffsetRight -180 SizeY 3 OffsetBottom -47 } Color 0.9 0.66 0.25 1 }
|
|
||||||
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "OpenAllowedItems" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 54 OffsetTop 54 SizeX 180 OffsetRight -180 SizeY 42 OffsetBottom -96 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Allowed items" } } }
|
|
||||||
ImageWidgetClass { "Is Visible" 0 Name "Selected_OpenAllowedItems" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 98 OffsetTop 98 SizeX 180 OffsetRight -180 SizeY 3 OffsetBottom -101 } Color 0.9 0.66 0.25 1 }
|
|
||||||
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "OpenWeights" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 108 OffsetTop 108 SizeX 180 OffsetRight -180 SizeY 42 OffsetBottom -150 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Spawn chances" } } }
|
|
||||||
ImageWidgetClass { "Is Visible" 0 Name "Selected_OpenWeights" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 152 OffsetTop 152 SizeX 180 OffsetRight -180 SizeY 3 OffsetBottom -155 } Color 0.9 0.66 0.25 1 }
|
|
||||||
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "OpenItemPresets" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 162 OffsetTop 162 SizeX 180 OffsetRight -180 SizeY 42 OffsetBottom -204 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Presets" } } }
|
|
||||||
ImageWidgetClass { "Is Visible" 0 Name "Selected_OpenItemPresets" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 206 OffsetTop 206 SizeX 180 OffsetRight -180 SizeY 3 OffsetBottom -209 } Color 0.9 0.66 0.25 1 }
|
|
||||||
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "OpenStarter" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 216 OffsetTop 216 SizeX 180 OffsetRight -180 SizeY 42 OffsetBottom -258 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Starting backpack" } } }
|
|
||||||
ImageWidgetClass { "Is Visible" 0 Name "Selected_OpenStarter" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 260 OffsetTop 260 SizeX 180 OffsetRight -180 SizeY 3 OffsetBottom -263 } Color 0.9 0.66 0.25 1 }
|
|
||||||
} }
|
|
||||||
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "Main_Mods" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 758 OffsetLeft 758 PositionY 106 OffsetTop 106 SizeX 138 OffsetRight -896 SizeY 42 OffsetBottom -148 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Mods" } } }
|
|
||||||
ImageWidgetClass { "Is Visible" 0 Name "Selected_Main_Mods" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 758 OffsetLeft 758 PositionY 150 OffsetTop 150 SizeX 138 OffsetRight -896 SizeY 3 OffsetBottom -153 } Color 0.9 0.66 0.25 1 }
|
|
||||||
FrameWidgetClass { "Is Visible" 0 Name "Sidebar_Mods" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 166 OffsetTop 166 SizeX 180 OffsetRight -208 SizeY 480 OffsetBottom -646 } {
|
|
||||||
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "OpenMods" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 0 OffsetTop 0 SizeX 180 OffsetRight -180 SizeY 42 OffsetBottom -42 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Installed mods" } } }
|
|
||||||
ImageWidgetClass { "Is Visible" 0 Name "Selected_OpenMods" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 44 OffsetTop 44 SizeX 180 OffsetRight -180 SizeY 3 OffsetBottom -47 } Color 0.9 0.66 0.25 1 }
|
ImageWidgetClass { "Is Visible" 0 Name "Selected_OpenMods" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 44 OffsetTop 44 SizeX 180 OffsetRight -180 SizeY 3 OffsetBottom -47 } Color 0.9 0.66 0.25 1 }
|
||||||
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "OpenRecommendations" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 54 OffsetTop 54 SizeX 180 OffsetRight -180 SizeY 42 OffsetBottom -96 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Recommended mods" } } }
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "OpenAllowedItems" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 54 OffsetTop 54 SizeX 180 OffsetRight -180 SizeY 42 OffsetBottom -96 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "2. Items" } } }
|
||||||
ImageWidgetClass { "Is Visible" 0 Name "Selected_OpenRecommendations" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 98 OffsetTop 98 SizeX 180 OffsetRight -180 SizeY 3 OffsetBottom -101 } Color 0.9 0.66 0.25 1 }
|
ImageWidgetClass { "Is Visible" 0 Name "Selected_OpenAllowedItems" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 98 OffsetTop 98 SizeX 180 OffsetRight -180 SizeY 3 OffsetBottom -101 } Color 0.9 0.66 0.25 1 }
|
||||||
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "OpenVehiclePool" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 108 OffsetTop 108 SizeX 180 OffsetRight -180 SizeY 42 OffsetBottom -150 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "3. Vehicles" } } }
|
||||||
|
ImageWidgetClass { "Is Visible" 0 Name "Selected_OpenVehiclePool" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 152 OffsetTop 152 SizeX 180 OffsetRight -180 SizeY 3 OffsetBottom -155 } Color 0.9 0.66 0.25 1 }
|
||||||
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "OpenVehicles" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 162 OffsetTop 162 SizeX 180 OffsetRight -180 SizeY 42 OffsetBottom -204 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Vehicle spawning" } } }
|
||||||
|
ImageWidgetClass { "Is Visible" 0 Name "Selected_OpenVehicles" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 206 OffsetTop 206 SizeX 180 OffsetRight -180 SizeY 3 OffsetBottom -209 } Color 0.9 0.66 0.25 1 }
|
||||||
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "OpenBoats" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 216 OffsetTop 216 SizeX 180 OffsetRight -180 SizeY 42 OffsetBottom -258 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Shore boats" } } }
|
||||||
|
ImageWidgetClass { "Is Visible" 0 Name "Selected_OpenBoats" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 260 OffsetTop 260 SizeX 180 OffsetRight -180 SizeY 3 OffsetBottom -263 } Color 0.9 0.66 0.25 1 }
|
||||||
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "OpenItems" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 270 OffsetTop 270 SizeX 180 OffsetRight -180 SizeY 42 OffsetBottom -312 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Equipment chance" } } }
|
||||||
|
ImageWidgetClass { "Is Visible" 0 Name "Selected_OpenItems" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 314 OffsetTop 314 SizeX 180 OffsetRight -180 SizeY 3 OffsetBottom -317 } Color 0.9 0.66 0.25 1 }
|
||||||
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "OpenWeights" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 324 OffsetTop 324 SizeX 180 OffsetRight -180 SizeY 42 OffsetBottom -366 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Advanced weights" } } }
|
||||||
|
ImageWidgetClass { "Is Visible" 0 Name "Selected_OpenWeights" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 368 OffsetTop 368 SizeX 180 OffsetRight -180 SizeY 3 OffsetBottom -371 } Color 0.9 0.66 0.25 1 }
|
||||||
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "OpenItemPresets" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 378 OffsetTop 378 SizeX 180 OffsetRight -180 SizeY 42 OffsetBottom -420 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Presets" } } }
|
||||||
|
ImageWidgetClass { "Is Visible" 0 Name "Selected_OpenItemPresets" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 422 OffsetTop 422 SizeX 180 OffsetRight -180 SizeY 3 OffsetBottom -425 } Color 0.9 0.66 0.25 1 }
|
||||||
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "OpenStarter" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 432 OffsetTop 432 SizeX 180 OffsetRight -180 SizeY 42 OffsetBottom -474 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Starting backpack" } } }
|
||||||
|
ImageWidgetClass { "Is Visible" 0 Name "Selected_OpenStarter" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 476 OffsetTop 476 SizeX 180 OffsetRight -180 SizeY 3 OffsetBottom -479 } Color 0.9 0.66 0.25 1 }
|
||||||
} }
|
} }
|
||||||
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "Main_Audio" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 904 OffsetLeft 904 PositionY 106 OffsetTop 106 SizeX 138 OffsetRight -1042 SizeY 42 OffsetBottom -148 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Audio" } } }
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "Main_Audio" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 758 OffsetLeft 758 PositionY 106 OffsetTop 106 SizeX 138 OffsetRight -896 SizeY 42 OffsetBottom -148 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Audio" } } }
|
||||||
ImageWidgetClass { "Is Visible" 0 Name "Selected_Main_Audio" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 904 OffsetLeft 904 PositionY 150 OffsetTop 150 SizeX 138 OffsetRight -1042 SizeY 3 OffsetBottom -153 } Color 0.9 0.66 0.25 1 }
|
ImageWidgetClass { "Is Visible" 0 Name "Selected_Main_Audio" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 758 OffsetLeft 758 PositionY 150 OffsetTop 150 SizeX 138 OffsetRight -896 SizeY 3 OffsetBottom -153 } Color 0.9 0.66 0.25 1 }
|
||||||
FrameWidgetClass { "Is Visible" 0 Name "Sidebar_Audio" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 166 OffsetTop 166 SizeX 180 OffsetRight -208 SizeY 480 OffsetBottom -646 } {
|
FrameWidgetClass { "Is Visible" 0 Name "Sidebar_Audio" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 166 OffsetTop 166 SizeX 180 OffsetRight -208 SizeY 480 OffsetBottom -646 } {
|
||||||
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "OpenTTS" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 0 OffsetTop 0 SizeX 180 OffsetRight -180 SizeY 42 OffsetBottom -42 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Speech and sounds" } } }
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "OpenTTS" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 0 OffsetTop 0 SizeX 180 OffsetRight -180 SizeY 42 OffsetBottom -42 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Speech and sounds" } } }
|
||||||
ImageWidgetClass { "Is Visible" 0 Name "Selected_OpenTTS" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 44 OffsetTop 44 SizeX 180 OffsetRight -180 SizeY 3 OffsetBottom -47 } Color 0.9 0.66 0.25 1 }
|
ImageWidgetClass { "Is Visible" 0 Name "Selected_OpenTTS" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 44 OffsetTop 44 SizeX 180 OffsetRight -180 SizeY 3 OffsetBottom -47 } Color 0.9 0.66 0.25 1 }
|
||||||
} }
|
} }
|
||||||
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "Main_Status" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 1050 OffsetLeft 1050 PositionY 106 OffsetTop 106 SizeX 138 OffsetRight -1188 SizeY 42 OffsetBottom -148 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Status" } } }
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "Main_Status" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 904 OffsetLeft 904 PositionY 106 OffsetTop 106 SizeX 138 OffsetRight -1042 SizeY 42 OffsetBottom -148 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Status" } } }
|
||||||
ImageWidgetClass { "Is Visible" 0 Name "Selected_Main_Status" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 1050 OffsetLeft 1050 PositionY 150 OffsetTop 150 SizeX 138 OffsetRight -1188 SizeY 3 OffsetBottom -153 } Color 0.9 0.66 0.25 1 }
|
ImageWidgetClass { "Is Visible" 0 Name "Selected_Main_Status" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 904 OffsetLeft 904 PositionY 150 OffsetTop 150 SizeX 138 OffsetRight -1042 SizeY 3 OffsetBottom -153 } Color 0.9 0.66 0.25 1 }
|
||||||
FrameWidgetClass { "Is Visible" 0 Name "Sidebar_Status" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 166 OffsetTop 166 SizeX 180 OffsetRight -208 SizeY 480 OffsetBottom -646 } {
|
FrameWidgetClass { "Is Visible" 0 Name "Sidebar_Status" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 166 OffsetTop 166 SizeX 180 OffsetRight -208 SizeY 480 OffsetBottom -646 } {
|
||||||
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "OpenStatus" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 0 OffsetTop 0 SizeX 180 OffsetRight -180 SizeY 42 OffsetBottom -42 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Mission status" } } }
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "OpenStatus" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 0 OffsetTop 0 SizeX 180 OffsetRight -180 SizeY 42 OffsetBottom -42 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Mission status" } } }
|
||||||
ImageWidgetClass { "Is Visible" 0 Name "Selected_OpenStatus" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 44 OffsetTop 44 SizeX 180 OffsetRight -180 SizeY 3 OffsetBottom -47 } Color 0.9 0.66 0.25 1 }
|
ImageWidgetClass { "Is Visible" 0 Name "Selected_OpenStatus" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 44 OffsetTop 44 SizeX 180 OffsetRight -180 SizeY 3 OffsetBottom -47 } Color 0.9 0.66 0.25 1 }
|
||||||
@@ -88,10 +108,10 @@ ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_But
|
|||||||
ImageWidgetClass { "Is Visible" 0 Name "Selected_OpenJournal" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 98 OffsetTop 98 SizeX 180 OffsetRight -180 SizeY 3 OffsetBottom -101 } Color 0.9 0.66 0.25 1 }
|
ImageWidgetClass { "Is Visible" 0 Name "Selected_OpenJournal" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 98 OffsetTop 98 SizeX 180 OffsetRight -180 SizeY 3 OffsetBottom -101 } Color 0.9 0.66 0.25 1 }
|
||||||
} }
|
} }
|
||||||
|
|
||||||
FrameWidgetClass { "Is Visible" 1 Name "SettingsPage" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 220 OffsetLeft 220 PositionY 160 OffsetTop 160 SizeX 1000 OffsetRight -1220 SizeY 486 OffsetBottom -646 } {
|
FrameWidgetClass { "Is Visible" 1 Name "SettingsPage" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 220 OffsetLeft 220 PositionY 160 OffsetTop 160 SizeX 1000 OffsetRight -1220 SizeY 532 OffsetBottom -692 } {
|
||||||
TextWidgetClass { Name "Label_startMode" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 4 OffsetTop 4 SizeX 456 OffsetRight -484 SizeY 22 OffsetBottom -26 } Color 0.9 0.92 0.94 1 Text "Next start (Prison or Crash)" "Font Size" 18 "Min Font Size" 18 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
TextWidgetClass { Name "Label_startMode" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 4 OffsetTop 4 SizeX 456 OffsetRight -484 SizeY 22 OffsetBottom -26 } Color 0.9 0.92 0.94 1 Text "Next start (Prison or Crash)" "Font Size" 18 "Min Font Size" 18 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
ButtonWidgetClass : "{DA43C77A0D1E59F8}UI/layouts/ERR/AdminValue.layout" { Name "startMode" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 28 OffsetTop 28 SizeX 180 OffsetRight -208 SizeY 32 OffsetBottom -60 } components { SCR_EditBoxComponent "{547290FFBD5B33E9}" { m_sLabel "" } } }
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "Choice_startMode_prison" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 28 OffsetTop 28 SizeX 220 OffsetRight -248 SizeY 32 OffsetBottom -60 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Prison" } } }
|
||||||
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "Apply_startMode" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 220 OffsetLeft 220 PositionY 28 OffsetTop 28 SizeX 100 OffsetRight -320 SizeY 32 OffsetBottom -60 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Apply" } } }
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "Choice_startMode_crash" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 264 OffsetLeft 264 PositionY 28 OffsetTop 28 SizeX 220 OffsetRight -484 SizeY 32 OffsetBottom -60 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Crash" } } }
|
||||||
TextWidgetClass { Name "Saved_startMode" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 64 OffsetTop 64 SizeX 456 OffsetRight -484 SizeY 28 OffsetBottom -92 } Color 0.9 0.92 0.94 1 Text "Waiting for server" "Font Size" 14 "Min Font Size" 14 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
TextWidgetClass { Name "Saved_startMode" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 64 OffsetTop 64 SizeX 456 OffsetRight -484 SizeY 28 OffsetBottom -92 } Color 0.9 0.92 0.94 1 Text "Waiting for server" "Font Size" 14 "Min Font Size" 14 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
TextWidgetClass { Name "Help_startMode" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 92 OffsetTop 92 SizeX 456 OffsetRight -484 SizeY 28 OffsetBottom -120 } Color 0.9 0.92 0.94 1 Text "Starting scenario for the next round." "Font Size" 14 "Min Font Size" 14 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
TextWidgetClass { Name "Help_startMode" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 92 OffsetTop 92 SizeX 456 OffsetRight -484 SizeY 28 OffsetBottom -120 } Color 0.9 0.92 0.94 1 Text "Starting scenario for the next round." "Font Size" 14 "Min Font Size" 14 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
TextWidgetClass { Name "Label_radioDefenceSeconds" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 508 OffsetLeft 508 PositionY 4 OffsetTop 4 SizeX 456 OffsetRight -964 SizeY 22 OffsetBottom -26 } Color 0.9 0.92 0.94 1 Text "Radio defence (30-1800 seconds)" "Font Size" 18 "Min Font Size" 18 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
TextWidgetClass { Name "Label_radioDefenceSeconds" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 508 OffsetLeft 508 PositionY 4 OffsetTop 4 SizeX 456 OffsetRight -964 SizeY 22 OffsetBottom -26 } Color 0.9 0.92 0.94 1 Text "Radio defence (30-1800 seconds)" "Font Size" 18 "Min Font Size" 18 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
@@ -117,11 +137,6 @@ TextWidgetClass { Name "Saved_keepWeaponOnUnconscious" Slot FrameWidgetSlot { An
|
|||||||
TextWidgetClass { Name "SelfEpiLabel" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 332 OffsetTop 332 SizeX 240 OffsetRight -268 SizeY 28 OffsetBottom -360 } Color 0.9 0.92 0.94 1 Text "Self epinephrine (Space)" "Font Size" 16 "Min Font Size" 16 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
TextWidgetClass { Name "SelfEpiLabel" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 332 OffsetTop 332 SizeX 240 OffsetRight -268 SizeY 28 OffsetBottom -360 } Color 0.9 0.92 0.94 1 Text "Self epinephrine (Space)" "Font Size" 16 "Min Font Size" 16 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "SelfEpiOn" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 280 OffsetLeft 280 PositionY 332 OffsetTop 332 SizeX 90 OffsetRight -370 SizeY 28 OffsetBottom -360 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Enabled" } } }
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "SelfEpiOn" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 280 OffsetLeft 280 PositionY 332 OffsetTop 332 SizeX 90 OffsetRight -370 SizeY 28 OffsetBottom -360 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Enabled" } } }
|
||||||
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "SelfEpiOff" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 382 OffsetLeft 382 PositionY 332 OffsetTop 332 SizeX 90 OffsetRight -472 SizeY 28 OffsetBottom -360 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Disabled" } } }
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "SelfEpiOff" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 382 OffsetLeft 382 PositionY 332 OffsetTop 332 SizeX 90 OffsetRight -472 SizeY 28 OffsetBottom -360 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Disabled" } } }
|
||||||
TextWidgetClass { Name "Label_civilianTrafficCount" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 508 OffsetLeft 508 PositionY 244 OffsetTop 244 SizeX 456 OffsetRight -964 SizeY 22 OffsetBottom -266 } Color 0.9 0.92 0.94 1 Text "Civilian traffic (0-6 cars; 0 off)" "Font Size" 18 "Min Font Size" 18 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
|
||||||
ButtonWidgetClass : "{DA43C77A0D1E59F8}UI/layouts/ERR/AdminValue.layout" { Name "civilianTrafficCount" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 508 OffsetLeft 508 PositionY 268 OffsetTop 268 SizeX 180 OffsetRight -688 SizeY 32 OffsetBottom -300 } components { SCR_EditBoxComponent "{547290FFBD5B33E9}" { m_sLabel "" } } }
|
|
||||||
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "Apply_civilianTrafficCount" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 700 OffsetLeft 700 PositionY 268 OffsetTop 268 SizeX 100 OffsetRight -800 SizeY 32 OffsetBottom -300 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Apply" } } }
|
|
||||||
TextWidgetClass { Name "Saved_civilianTrafficCount" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 508 OffsetLeft 508 PositionY 304 OffsetTop 304 SizeX 456 OffsetRight -964 SizeY 28 OffsetBottom -332 } Color 0.9 0.92 0.94 1 Text "Waiting for server" "Font Size" 14 "Min Font Size" 14 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
|
||||||
TextWidgetClass { Name "Help_civilianTrafficCount" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 508 OffsetLeft 508 PositionY 332 OffsetTop 332 SizeX 456 OffsetRight -964 SizeY 28 OffsetBottom -360 } Color 0.9 0.92 0.94 1 Text "0 disables new traffic. Player-used cars are kept." "Font Size" 14 "Min Font Size" 14 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
|
||||||
TextWidgetClass { Name "Label_restartOnEmpty" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 364 OffsetTop 364 SizeX 456 OffsetRight -484 SizeY 22 OffsetBottom -386 } Color 0.9 0.92 0.94 1 Text "Restart after last disconnect" "Font Size" 18 "Min Font Size" 18 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
TextWidgetClass { Name "Label_restartOnEmpty" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 364 OffsetTop 364 SizeX 456 OffsetRight -484 SizeY 22 OffsetBottom -386 } Color 0.9 0.92 0.94 1 Text "Restart after last disconnect" "Font Size" 18 "Min Font Size" 18 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "RestartEmptyOn" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 388 OffsetTop 388 SizeX 140 OffsetRight -168 SizeY 32 OffsetBottom -420 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Enabled" } } }
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "RestartEmptyOn" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 388 OffsetTop 388 SizeX 140 OffsetRight -168 SizeY 32 OffsetBottom -420 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Enabled" } } }
|
||||||
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "RestartEmptyOff" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 180 OffsetLeft 180 PositionY 388 OffsetTop 388 SizeX 152 OffsetRight -332 SizeY 32 OffsetBottom -420 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Disabled" } } }
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "RestartEmptyOff" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 180 OffsetLeft 180 PositionY 388 OffsetTop 388 SizeX 152 OffsetRight -332 SizeY 32 OffsetBottom -420 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Disabled" } } }
|
||||||
@@ -133,7 +148,65 @@ ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_But
|
|||||||
TextWidgetClass { Name "Saved_emptyRestartHours" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 508 OffsetLeft 508 PositionY 424 OffsetTop 424 SizeX 456 OffsetRight -964 SizeY 24 OffsetBottom -448 } Color 0.9 0.92 0.94 1 Text "Waiting for server" "Font Size" 14 "Min Font Size" 14 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
TextWidgetClass { Name "Saved_emptyRestartHours" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 508 OffsetLeft 508 PositionY 424 OffsetTop 424 SizeX 456 OffsetRight -964 SizeY 24 OffsetBottom -448 } Color 0.9 0.92 0.94 1 Text "Waiting for server" "Font Size" 14 "Min Font Size" 14 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
TextWidgetClass { Name "Help_emptyRestartHours" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 508 OffsetLeft 508 PositionY 452 OffsetTop 452 SizeX 456 OffsetRight -964 SizeY 28 OffsetBottom -480 } Color 0.9 0.92 0.94 1 Text "0 off; 1-168 real hours empty. Default 5. Applies now." "Font Size" 14 "Min Font Size" 14 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
TextWidgetClass { Name "Help_emptyRestartHours" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 508 OffsetLeft 508 PositionY 452 OffsetTop 452 SizeX 456 OffsetRight -964 SizeY 28 OffsetBottom -480 } Color 0.9 0.92 0.94 1 Text "0 off; 1-168 real hours empty. Default 5. Applies now." "Font Size" 14 "Min Font Size" 14 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
} }
|
} }
|
||||||
FrameWidgetClass { "Is Visible" 0 Name "HQPage" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 220 OffsetLeft 220 PositionY 160 OffsetTop 160 SizeX 1000 OffsetRight -1220 SizeY 486 OffsetBottom -646 } {
|
FrameWidgetClass { "Is Visible" 0 Name "VehiclesPage" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 220 OffsetLeft 220 PositionY 160 OffsetTop 160 SizeX 1000 OffsetRight -1220 SizeY 532 OffsetBottom -692 } {
|
||||||
|
TextWidgetClass { Name "Label_civilianTrafficCount" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 4 OffsetTop 4 SizeX 456 OffsetRight -484 SizeY 22 OffsetBottom -26 } Color 0.9 0.92 0.94 1 Text "Civilian traffic (0-6 cars; 0 off)" "Font Size" 18 "Min Font Size" 18 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
ButtonWidgetClass : "{DA43C77A0D1E59F8}UI/layouts/ERR/AdminValue.layout" { Name "civilianTrafficCount" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 28 OffsetTop 28 SizeX 180 OffsetRight -208 SizeY 32 OffsetBottom -60 } components { SCR_EditBoxComponent "{547290FFBD5B33E9}" { m_sLabel "" } } }
|
||||||
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "Apply_civilianTrafficCount" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 220 OffsetLeft 220 PositionY 28 OffsetTop 28 SizeX 100 OffsetRight -320 SizeY 32 OffsetBottom -60 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Apply" } } }
|
||||||
|
TextWidgetClass { Name "Saved_civilianTrafficCount" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 64 OffsetTop 64 SizeX 456 OffsetRight -484 SizeY 28 OffsetBottom -92 } Color 0.9 0.92 0.94 1 Text "Waiting for server" "Font Size" 14 "Min Font Size" 14 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
TextWidgetClass { Name "Help_civilianTrafficCount" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 92 OffsetTop 92 SizeX 456 OffsetRight -484 SizeY 28 OffsetBottom -120 } Color 0.9 0.92 0.94 1 Text "0 disables new traffic. Player-used cars are kept." "Font Size" 14 "Min Font Size" 14 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
TextWidgetClass { Name "Label_garageVehicleCount" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 508 OffsetLeft 508 PositionY 4 OffsetTop 4 SizeX 456 OffsetRight -964 SizeY 22 OffsetBottom -26 } Color 0.9 0.92 0.94 1 Text "Garage and yard vehicles (0-24; 0 off)" "Font Size" 18 "Min Font Size" 18 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
ButtonWidgetClass : "{DA43C77A0D1E59F8}UI/layouts/ERR/AdminValue.layout" { Name "garageVehicleCount" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 508 OffsetLeft 508 PositionY 28 OffsetTop 28 SizeX 180 OffsetRight -688 SizeY 32 OffsetBottom -60 } components { SCR_EditBoxComponent "{547290FFBD5B33E9}" { m_sLabel "" } } }
|
||||||
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "Apply_garageVehicleCount" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 700 OffsetLeft 700 PositionY 28 OffsetTop 28 SizeX 100 OffsetRight -800 SizeY 32 OffsetBottom -60 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Apply" } } }
|
||||||
|
TextWidgetClass { Name "Saved_garageVehicleCount" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 508 OffsetLeft 508 PositionY 64 OffsetTop 64 SizeX 456 OffsetRight -964 SizeY 28 OffsetBottom -92 } Color 0.9 0.92 0.94 1 Text "Waiting for server" "Font Size" 14 "Min Font Size" 14 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
TextWidgetClass { Name "Help_garageVehicleCount" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 508 OffsetLeft 508 PositionY 92 OffsetTop 92 SizeX 456 OffsetRight -964 SizeY 28 OffsetBottom -120 } Color 0.9 0.92 0.94 1 Text "Default 16 unclaimed vehicles. Set 0 to disable; next round." "Font Size" 14 "Min Font Size" 14 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
TextWidgetClass { Name "Label_garageChancePercent" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 144 OffsetTop 144 SizeX 456 OffsetRight -484 SizeY 22 OffsetBottom -166 } Color 0.9 0.92 0.94 1 Text "Parking-site selection chance (0-100%)" "Font Size" 18 "Min Font Size" 18 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
ButtonWidgetClass : "{DA43C77A0D1E59F8}UI/layouts/ERR/AdminValue.layout" { Name "garageChancePercent" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 168 OffsetTop 168 SizeX 180 OffsetRight -208 SizeY 32 OffsetBottom -200 } components { SCR_EditBoxComponent "{547290FFBD5B33E9}" { m_sLabel "" } } }
|
||||||
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "Apply_garageChancePercent" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 220 OffsetLeft 220 PositionY 168 OffsetTop 168 SizeX 100 OffsetRight -320 SizeY 32 OffsetBottom -200 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Apply" } } }
|
||||||
|
TextWidgetClass { Name "Saved_garageChancePercent" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 204 OffsetTop 204 SizeX 456 OffsetRight -484 SizeY 28 OffsetBottom -232 } Color 0.9 0.92 0.94 1 Text "Waiting for server" "Font Size" 14 "Min Font Size" 14 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
TextWidgetClass { Name "Help_garageChancePercent" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 232 OffsetTop 232 SizeX 456 OffsetRight -484 SizeY 28 OffsetBottom -260 } Color 0.9 0.92 0.94 1 Text "Default 70%; 100 selects every suitable garage. Next round." "Font Size" 14 "Min Font Size" 14 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
TextWidgetClass { Name "Label_garageRadiusMetres" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 508 OffsetLeft 508 PositionY 144 OffsetTop 144 SizeX 456 OffsetRight -964 SizeY 22 OffsetBottom -166 } Color 0.9 0.92 0.94 1 Text "Spawn radius (250-1500 metres)" "Font Size" 18 "Min Font Size" 18 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
ButtonWidgetClass : "{DA43C77A0D1E59F8}UI/layouts/ERR/AdminValue.layout" { Name "garageRadiusMetres" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 508 OffsetLeft 508 PositionY 168 OffsetTop 168 SizeX 180 OffsetRight -688 SizeY 32 OffsetBottom -200 } components { SCR_EditBoxComponent "{547290FFBD5B33E9}" { m_sLabel "" } } }
|
||||||
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "Apply_garageRadiusMetres" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 700 OffsetLeft 700 PositionY 168 OffsetTop 168 SizeX 100 OffsetRight -800 SizeY 32 OffsetBottom -200 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Apply" } } }
|
||||||
|
TextWidgetClass { Name "Saved_garageRadiusMetres" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 508 OffsetLeft 508 PositionY 204 OffsetTop 204 SizeX 456 OffsetRight -964 SizeY 28 OffsetBottom -232 } Color 0.9 0.92 0.94 1 Text "Waiting for server" "Font Size" 14 "Min Font Size" 14 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
TextWidgetClass { Name "Help_garageRadiusMetres" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 508 OffsetLeft 508 PositionY 232 OffsetTop 232 SizeX 456 OffsetRight -964 SizeY 28 OffsetBottom -260 } Color 0.9 0.92 0.94 1 Text "Distance around players; default 1000 m. Next round." "Font Size" 14 "Min Font Size" 14 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
TextWidgetClass { Name "Label_garageSpawnIntervalSeconds" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 284 OffsetTop 284 SizeX 456 OffsetRight -484 SizeY 22 OffsetBottom -306 } Color 0.9 0.92 0.94 1 Text "Time between spawns (5-60 seconds)" "Font Size" 18 "Min Font Size" 18 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
ButtonWidgetClass : "{DA43C77A0D1E59F8}UI/layouts/ERR/AdminValue.layout" { Name "garageSpawnIntervalSeconds" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 308 OffsetTop 308 SizeX 180 OffsetRight -208 SizeY 32 OffsetBottom -340 } components { SCR_EditBoxComponent "{547290FFBD5B33E9}" { m_sLabel "" } } }
|
||||||
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "Apply_garageSpawnIntervalSeconds" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 220 OffsetLeft 220 PositionY 308 OffsetTop 308 SizeX 100 OffsetRight -320 SizeY 32 OffsetBottom -340 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Apply" } } }
|
||||||
|
TextWidgetClass { Name "Saved_garageSpawnIntervalSeconds" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 344 OffsetTop 344 SizeX 456 OffsetRight -484 SizeY 28 OffsetBottom -372 } Color 0.9 0.92 0.94 1 Text "Waiting for server" "Font Size" 14 "Min Font Size" 14 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
TextWidgetClass { Name "Help_garageSpawnIntervalSeconds" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 372 OffsetTop 372 SizeX 456 OffsetRight -484 SizeY 28 OffsetBottom -400 } Color 0.9 0.92 0.94 1 Text "Default 5 s. Increase to spread out spawning. Next round." "Font Size" 14 "Min Font Size" 14 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
TextWidgetClass { Name "GarageSettingsNote" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 508 OffsetLeft 508 PositionY 284 OffsetTop 284 SizeX 456 OffsetRight -964 SizeY 110 OffsetBottom -394 } Color 0.9 0.92 0.94 1 Text "Parking settings apply next round. Used vehicles stay. Garages and clear yards fill while hidden. Rally adds cars and occasional farm tractors." "Font Size" 18 "Min Font Size" 18 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
} }
|
||||||
|
FrameWidgetClass { "Is Visible" 0 Name "BoatsPage" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 220 OffsetLeft 220 PositionY 160 OffsetTop 160 SizeX 1000 OffsetRight -1220 SizeY 532 OffsetBottom -692 } {
|
||||||
|
TextWidgetClass { Name "BoatsHeading" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 4 OffsetTop 4 SizeX 936 OffsetRight -964 SizeY 30 OffsetBottom -34 } Color 0.9 0.92 0.94 1 Text "Shore boats - next round" "Font Size" 22 "Min Font Size" 22 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
TextWidgetClass { Name "BoatsHelp" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 40 OffsetTop 40 SizeX 936 OffsetRight -964 SizeY 50 OffsetBottom -90 } Color 0.9 0.92 0.94 1 Text "Occasional usable boats near accessible shores. Coasts facing another island are favored. Requires Motorboat loaded on the server." "Font Size" 17 "Min Font Size" 17 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
TextWidgetClass { Name "Label_shoreBoatCount" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 108 OffsetTop 108 SizeX 456 OffsetRight -484 SizeY 26 OffsetBottom -134 } Color 0.9 0.92 0.94 1 Text "Shore boats (0-16; 0 off)" "Font Size" 17 "Min Font Size" 17 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
ButtonWidgetClass : "{DA43C77A0D1E59F8}UI/layouts/ERR/AdminValue.layout" { Name "shoreBoatCount" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 136 OffsetTop 136 SizeX 180 OffsetRight -208 SizeY 32 OffsetBottom -168 } components { SCR_EditBoxComponent "{547290FFBD5B33E9}" { m_sLabel "" } } }
|
||||||
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "Apply_shoreBoatCount" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 220 OffsetLeft 220 PositionY 136 OffsetTop 136 SizeX 100 OffsetRight -320 SizeY 32 OffsetBottom -168 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Apply" } } }
|
||||||
|
TextWidgetClass { Name "Saved_shoreBoatCount" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 172 OffsetTop 172 SizeX 456 OffsetRight -484 SizeY 24 OffsetBottom -196 } Color 0.9 0.92 0.94 1 Text "Waiting for server" "Font Size" 14 "Min Font Size" 14 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
TextWidgetClass { Name "Help_shoreBoatCount" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 508 OffsetLeft 508 PositionY 136 OffsetTop 136 SizeX 456 OffsetRight -964 SizeY 60 OffsetBottom -196 } Color 0.9 0.92 0.94 1 Text "Maximum for the round. Used or destroyed boats are not replaced." "Font Size" 17 "Min Font Size" 17 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
TextWidgetClass { Name "Label_shoreBoatChancePercent" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 234 OffsetTop 234 SizeX 456 OffsetRight -484 SizeY 26 OffsetBottom -260 } Color 0.9 0.92 0.94 1 Text "Ordinary coast chance (%)" "Font Size" 17 "Min Font Size" 17 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
ButtonWidgetClass : "{DA43C77A0D1E59F8}UI/layouts/ERR/AdminValue.layout" { Name "shoreBoatChancePercent" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 262 OffsetTop 262 SizeX 180 OffsetRight -208 SizeY 32 OffsetBottom -294 } components { SCR_EditBoxComponent "{547290FFBD5B33E9}" { m_sLabel "" } } }
|
||||||
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "Apply_shoreBoatChancePercent" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 220 OffsetLeft 220 PositionY 262 OffsetTop 262 SizeX 100 OffsetRight -320 SizeY 32 OffsetBottom -294 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Apply" } } }
|
||||||
|
TextWidgetClass { Name "Saved_shoreBoatChancePercent" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 298 OffsetTop 298 SizeX 456 OffsetRight -484 SizeY 24 OffsetBottom -322 } Color 0.9 0.92 0.94 1 Text "Waiting for server" "Font Size" 14 "Min Font Size" 14 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
TextWidgetClass { Name "Help_shoreBoatChancePercent" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 508 OffsetLeft 508 PositionY 262 OffsetTop 262 SizeX 456 OffsetRight -964 SizeY 60 OffsetBottom -322 } Color 0.9 0.92 0.94 1 Text "Chance per surveyed shore candidate; default 20%." "Font Size" 17 "Min Font Size" 17 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
TextWidgetClass { Name "Label_shoreBoatCrossingChancePercent" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 360 OffsetTop 360 SizeX 456 OffsetRight -484 SizeY 26 OffsetBottom -386 } Color 0.9 0.92 0.94 1 Text "Island-facing coast chance (%)" "Font Size" 17 "Min Font Size" 17 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
ButtonWidgetClass : "{DA43C77A0D1E59F8}UI/layouts/ERR/AdminValue.layout" { Name "shoreBoatCrossingChancePercent" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 388 OffsetTop 388 SizeX 180 OffsetRight -208 SizeY 32 OffsetBottom -420 } components { SCR_EditBoxComponent "{547290FFBD5B33E9}" { m_sLabel "" } } }
|
||||||
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "Apply_shoreBoatCrossingChancePercent" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 220 OffsetLeft 220 PositionY 388 OffsetTop 388 SizeX 100 OffsetRight -320 SizeY 32 OffsetBottom -420 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Apply" } } }
|
||||||
|
TextWidgetClass { Name "Saved_shoreBoatCrossingChancePercent" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 424 OffsetTop 424 SizeX 456 OffsetRight -484 SizeY 24 OffsetBottom -448 } Color 0.9 0.92 0.94 1 Text "Waiting for server" "Font Size" 14 "Min Font Size" 14 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
TextWidgetClass { Name "Help_shoreBoatCrossingChancePercent" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 508 OffsetLeft 508 PositionY 388 OffsetTop 388 SizeX 456 OffsetRight -964 SizeY 60 OffsetBottom -448 } Color 0.9 0.92 0.94 1 Text "Default 70%; never lower than the ordinary coast chance." "Font Size" 17 "Min Font Size" 17 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
TextWidgetClass { Name "BoatsFooter" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 486 OffsetTop 486 SizeX 936 OffsetRight -964 SizeY 40 OffsetBottom -526 } Color 0.9 0.92 0.94 1 Text "Default: up to 6 unarmed boats, at least 700 m apart, with fuel. Boat and mod switches are respected. Status shows availability and survey progress." "Font Size" 14 "Min Font Size" 14 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
} }
|
||||||
|
FrameWidgetClass { "Is Visible" 0 Name "TelemetryPage" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 220 OffsetLeft 220 PositionY 160 OffsetTop 160 SizeX 1000 OffsetRight -1220 SizeY 532 OffsetBottom -692 } {
|
||||||
|
TextWidgetClass { Name "TelemetryHeading" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 10 OffsetTop 10 SizeX 936 OffsetRight -964 SizeY 32 OffsetBottom -42 } Color 0.9 0.92 0.94 1 Text "Mission statistics" "Font Size" 18 "Min Font Size" 18 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
TextWidgetClass { Name "TelemetryDescription" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 55 OffsetTop 55 SizeX 936 OffsetRight -964 SizeY 80 OffsetBottom -135 } Color 0.9 0.92 0.94 1 Text "Share mission starts, progress, results, terrain, loaded mods and player counts with the configured statistics service. Enabled by default." "Font Size" 18 "Min Font Size" 18 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "TelemetryOn" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 150 OffsetTop 150 SizeX 140 OffsetRight -168 SizeY 32 OffsetBottom -182 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Enabled" } } }
|
||||||
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "TelemetryOff" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 180 OffsetLeft 180 PositionY 150 OffsetTop 150 SizeX 152 OffsetRight -332 SizeY 32 OffsetBottom -182 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Disabled" } } }
|
||||||
|
TextWidgetClass { Name "Saved_telemetryEnabled" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 200 OffsetTop 200 SizeX 936 OffsetRight -964 SizeY 32 OffsetBottom -232 } Color 0.9 0.92 0.94 1 Text "Waiting for server" "Font Size" 18 "Min Font Size" 18 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
TextWidgetClass { Name "TelemetryStatus" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 232 OffsetTop 232 SizeX 936 OffsetRight -964 SizeY 26 OffsetBottom -258 } Color 0.9 0.92 0.94 1 Text "Delivery status unavailable" "Font Size" 18 "Min Font Size" 18 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
TextWidgetClass { Name "TelemetryPrivacy" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 278 OffsetTop 278 SizeX 936 OffsetRight -964 SizeY 78 OffsetBottom -356 } Color 0.9 0.92 0.94 1 Text "Player names and stable game account IDs are included to count participation and escapes across missions. Connected spectators are distinguished from participants and escapees." "Font Size" 18 "Min Font Size" 18 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
TextWidgetClass { Name "TelemetryDisableHelp" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 360 OffsetTop 360 SizeX 936 OffsetRight -964 SizeY 80 OffsetBottom -440 } Color 0.9 0.92 0.94 1 Text "Disable to stop collection and sending, and discard unsent records. Already delivered statistics remain with the statistics service." "Font Size" 18 "Min Font Size" 18 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
} }
|
||||||
|
FrameWidgetClass { "Is Visible" 0 Name "HQPage" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 220 OffsetLeft 220 PositionY 160 OffsetTop 160 SizeX 1000 OffsetRight -1220 SizeY 532 OffsetBottom -692 } {
|
||||||
TextWidgetClass {
|
TextWidgetClass {
|
||||||
Name "HQHelp" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 6 OffsetTop 6 SizeX 510 OffsetRight -538 SizeY 50 OffsetBottom -56 }
|
Name "HQHelp" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 6 OffsetTop 6 SizeX 510 OffsetRight -538 SizeY 50 OffsetBottom -56 }
|
||||||
Color 0.9 0.92 0.94 1 Text "Enemy reports, ground response and aircraft." "Font Size" 19 "Min Font Size" 19 Wrap 1
|
Color 0.9 0.92 0.94 1 Text "Enemy reports, ground response and aircraft." "Font Size" 19 "Min Font Size" 19 Wrap 1
|
||||||
@@ -312,14 +385,14 @@ TextWidgetClass {
|
|||||||
FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" }
|
FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" }
|
||||||
}
|
}
|
||||||
} }
|
} }
|
||||||
FrameWidgetClass { "Is Visible" 0 Name "JournalPage" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 220 OffsetLeft 220 PositionY 160 OffsetTop 160 SizeX 1000 OffsetRight -1220 SizeY 486 OffsetBottom -646 } {
|
FrameWidgetClass { "Is Visible" 0 Name "JournalPage" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 220 OffsetLeft 220 PositionY 160 OffsetTop 160 SizeX 1000 OffsetRight -1220 SizeY 532 OffsetBottom -692 } {
|
||||||
TextWidgetClass {
|
TextWidgetClass {
|
||||||
Name "JournalHelp" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 6 OffsetTop 6 SizeX 936 OffsetRight -964 SizeY 50 OffsetBottom -56 }
|
Name "JournalHelp" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 6 OffsetTop 6 SizeX 936 OffsetRight -964 SizeY 50 OffsetBottom -56 }
|
||||||
Color 0.9 0.92 0.94 1 Text "Each entry explains what happened. Queued means awaiting radio delivery; received means delivered." "Font Size" 19 "Min Font Size" 19 Wrap 1
|
Color 0.9 0.92 0.94 1 Text "Each entry explains what happened. Queued means awaiting radio delivery; received means delivered." "Font Size" 19 "Min Font Size" 19 Wrap 1
|
||||||
FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" }
|
FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" }
|
||||||
}
|
}
|
||||||
ScrollLayoutWidgetClass {
|
ScrollLayoutWidgetClass {
|
||||||
Name "JournalScroll" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 58 OffsetTop 58 SizeX 936 OffsetRight -964 SizeY 320 OffsetBottom -378 }
|
Name "JournalScroll" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 58 OffsetTop 58 SizeX 936 OffsetRight -964 SizeY 366 OffsetBottom -424 }
|
||||||
components {
|
components {
|
||||||
SCR_GamepadScrollComponent "{5707100AC31E657E}" {
|
SCR_GamepadScrollComponent "{5707100AC31E657E}" {
|
||||||
}
|
}
|
||||||
@@ -343,13 +416,13 @@ ScrollLayoutWidgetClass {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
TextWidgetClass {
|
TextWidgetClass {
|
||||||
Name "JournalState" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 386 OffsetTop 386 SizeX 936 OffsetRight -964 SizeY 42 OffsetBottom -428 }
|
Name "JournalState" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 432 OffsetTop 432 SizeX 936 OffsetRight -964 SizeY 42 OffsetBottom -474 }
|
||||||
Color 0.9 0.92 0.94 1 Text "Waiting for the server..." "Font Size" 17 "Min Font Size" 17 Wrap 1 FontProperties FontProperties {
|
Color 0.9 0.92 0.94 1 Text "Waiting for the server..." "Font Size" 17 "Min Font Size" 17 Wrap 1 FontProperties FontProperties {
|
||||||
Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt"
|
Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" {
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" {
|
||||||
Name "PauseJournal" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 438 OffsetTop 438 SizeX 220 OffsetRight -248 SizeY 42 OffsetBottom -480 }
|
Name "PauseJournal" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 484 OffsetTop 484 SizeX 220 OffsetRight -248 SizeY 42 OffsetBottom -526 }
|
||||||
components {
|
components {
|
||||||
SCR_ButtonTextComponent "{5548F0143AE99095}" {
|
SCR_ButtonTextComponent "{5548F0143AE99095}" {
|
||||||
m_sText "Pause live"
|
m_sText "Pause live"
|
||||||
@@ -357,11 +430,11 @@ ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_But
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" {
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" {
|
||||||
Name "JournalDetails" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 310 OffsetLeft 310 PositionY 438 OffsetTop 438 SizeX 360 OffsetRight -670 SizeY 42 OffsetBottom -480 }
|
Name "JournalDetails" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 310 OffsetLeft 310 PositionY 484 OffsetTop 484 SizeX 360 OffsetRight -670 SizeY 42 OffsetBottom -526 }
|
||||||
components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Show technical details" } }
|
components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Show technical details" } }
|
||||||
}
|
}
|
||||||
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" {
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" {
|
||||||
Name "LatestJournal" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 742 OffsetLeft 742 PositionY 438 OffsetTop 438 SizeX 222 OffsetRight -964 SizeY 42 OffsetBottom -480 }
|
Name "LatestJournal" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 742 OffsetLeft 742 PositionY 484 OffsetTop 484 SizeX 222 OffsetRight -964 SizeY 42 OffsetBottom -526 }
|
||||||
components {
|
components {
|
||||||
SCR_ButtonTextComponent "{5548F0143AE99095}" {
|
SCR_ButtonTextComponent "{5548F0143AE99095}" {
|
||||||
m_sText "Jump to latest"
|
m_sText "Jump to latest"
|
||||||
@@ -369,24 +442,53 @@ ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_But
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} }
|
} }
|
||||||
FrameWidgetClass { "Is Visible" 0 Name "ModsPage" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 220 OffsetLeft 220 PositionY 160 OffsetTop 160 SizeX 1000 OffsetRight -1220 SizeY 486 OffsetBottom -646 } {
|
FrameWidgetClass { "Is Visible" 0 Name "ModsPage" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 220 OffsetLeft 220 PositionY 160 OffsetTop 160 SizeX 1000 OffsetRight -1220 SizeY 532 OffsetBottom -692 } {
|
||||||
|
TextWidgetClass { Name "ModsHelp" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 0 OffsetTop 0 SizeX 936 OffsetRight -964 SizeY 30 OffsetBottom -30 } Color 0.9 0.92 0.94 1 Text "Enable mods first. Their individual item and vehicle choices are remembered when disabled." "Font Size" 17 "Min Font Size" 17 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
TextWidgetClass { Name "ModsScope" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 32 OffsetTop 32 SizeX 936 OffsetRight -964 SizeY 28 OffsetBottom -60 } Color 0.9 0.92 0.94 1 Text "Controls future extra equipment and assigned vehicles. Loaded Workshop scripts stay active." "Font Size" 17 "Min Font Size" 17 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
TextWidgetClass { Name "ModsSearchLabel" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 64 OffsetTop 64 SizeX 430 OffsetRight -458 SizeY 22 OffsetBottom -86 } Color 0.9 0.92 0.94 1 Text "Find a mod" "Font Size" 14 "Min Font Size" 14 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
ButtonWidgetClass : "{DA43C77A0D1E59F8}UI/layouts/ERR/AdminValue.layout" { Name "ModsSearch" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 88 OffsetTop 88 SizeX 540 OffsetRight -568 SizeY 36 OffsetBottom -124 } components { SCR_EditBoxComponent "{547290FFBD5B33E9}" { m_sLabel "" } } }
|
||||||
|
ButtonWidgetClass : "{4B5AE6E64037FFB4}UI/layouts/WidgetLibrary/ComboBox/WLib_ComboBox.layout" { Name "ModsFilter" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 586 OffsetLeft 586 PositionY 88 OffsetTop 88 SizeX 378 OffsetRight -964 SizeY 36 OffsetBottom -124 } components { SCR_ComboBoxComponent "{546B1D464BC32853}" { m_bUseLabel 0 m_sLabel "" m_fSizeWithoutLabel 36 } } }
|
||||||
|
TextWidgetClass { Name "ModsCount" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 130 OffsetTop 130 SizeX 936 OffsetRight -964 SizeY 28 OffsetBottom -158 } Color 0.9 0.92 0.94 1 Text "Waiting for server..." "Font Size" 17 "Min Font Size" 17 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
ScrollLayoutWidgetClass { Name "ModsScroll" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 166 OffsetTop 166 SizeX 936 OffsetRight -964 SizeY 356 OffsetBottom -522 } { VerticalLayoutWidgetClass { Name "ModList" Slot AlignableSlot { HorizontalAlign 3 VerticalAlign 3 Padding 0 0 12 0 } { } } } }
|
||||||
|
} }
|
||||||
|
FrameWidgetClass { "Is Visible" 0 Name "PacksPage" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 220 OffsetLeft 220 PositionY 160 OffsetTop 160 SizeX 1000 OffsetRight -1220 SizeY 532 OffsetBottom -692 } {
|
||||||
TextWidgetClass {
|
TextWidgetClass {
|
||||||
Name "ModsHelp" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 6 OffsetTop 6 SizeX 650 OffsetRight -678 SizeY 50 OffsetBottom -56 }
|
Name "PacksHelp" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 6 OffsetTop 6 SizeX 650 OffsetRight -678 SizeY 50 OffsetBottom -56 }
|
||||||
Color 0.9 0.92 0.94 1 Text "Mission content switches are followed by all server-loaded mods. Workshop mods stay loaded; use Edit items to manage their random equipment." "Font Size" 19 "Min Font Size" 19 Wrap 1
|
Color 0.9 0.92 0.94 1 Text "Choose supported enemy packs and factions for the next round. Item and vehicle choices are under Content." "Font Size" 19 "Min Font Size" 19 Wrap 1
|
||||||
FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" }
|
FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
TextWidgetClass {
|
TextWidgetClass {
|
||||||
Name "ModsScope" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 58 OffsetTop 58 SizeX 936 OffsetRight -964 SizeY 46 OffsetBottom -104 }
|
Name "PacksScope" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 58 OffsetTop 58 SizeX 936 OffsetRight -964 SizeY 46 OffsetBottom -104 }
|
||||||
Color 0.9 0.92 0.94 1 Text "Switches apply only to supported mission content, next round. Use Edit items for random equipment and presets." "Font Size" 19 "Min Font Size" 19 Wrap 1
|
Color 0.9 0.92 0.94 1 Text "Switches apply only to supported mission content, next round. Use Edit items for random equipment and presets." "Font Size" 19 "Min Font Size" 19 Wrap 1
|
||||||
FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" }
|
FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" }
|
||||||
}
|
}
|
||||||
ScrollLayoutWidgetClass { Name "ModsScroll" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 112 OffsetTop 112 SizeX 936 OffsetRight -964 SizeY 362 OffsetBottom -474 } {
|
ScrollLayoutWidgetClass { Name "PacksScroll" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 112 OffsetTop 112 SizeX 936 OffsetRight -964 SizeY 408 OffsetBottom -520 } {
|
||||||
VerticalLayoutWidgetClass { Name "ModList" Slot AlignableSlot { HorizontalAlign 3 VerticalAlign 3 } { } }
|
VerticalLayoutWidgetClass { Name "PackList" Slot AlignableSlot { HorizontalAlign 3 VerticalAlign 3 } { } }
|
||||||
} }
|
} }
|
||||||
} }
|
} }
|
||||||
FrameWidgetClass { "Is Visible" 0 Name "FactionsPage" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 220 OffsetLeft 220 PositionY 160 OffsetTop 160 SizeX 1000 OffsetRight -1220 SizeY 486 OffsetBottom -646 } {
|
FrameWidgetClass { "Is Visible" 0 Name "VehiclePoolPage" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 220 OffsetLeft 220 PositionY 160 OffsetTop 160 SizeX 1000 OffsetRight -1220 SizeY 532 OffsetBottom -692 } {
|
||||||
|
TextWidgetClass { Name "VehiclesHelp" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 0 OffsetTop 0 SizeX 936 OffsetRight -964 SizeY 28 OffsetBottom -28 } Color 0.9 0.92 0.94 1 Text "Choose a mod and a use. New mod vehicles start unassigned; enable the ones you want." "Font Size" 17 "Min Font Size" 17 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
ButtonWidgetClass : "{4B5AE6E64037FFB4}UI/layouts/WidgetLibrary/ComboBox/WLib_ComboBox.layout" { Name "VehiclesMod" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 34 OffsetTop 34 SizeX 370 OffsetRight -398 SizeY 36 OffsetBottom -70 } components { SCR_ComboBoxComponent "{546B1D464BC32853}" { m_bUseLabel 0 m_sLabel "" m_fSizeWithoutLabel 36 } } }
|
||||||
|
ButtonWidgetClass : "{4B5AE6E64037FFB4}UI/layouts/WidgetLibrary/ComboBox/WLib_ComboBox.layout" { Name "VehiclesRole" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 410 OffsetLeft 410 PositionY 34 OffsetTop 34 SizeX 380 OffsetRight -790 SizeY 36 OffsetBottom -70 } components { SCR_ComboBoxComponent "{546B1D464BC32853}" { m_bUseLabel 0 m_sLabel "" m_fSizeWithoutLabel 36 } } }
|
||||||
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "VehiclesSelectionState" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 804 OffsetLeft 804 PositionY 34 OffsetTop 34 SizeX 160 OffsetRight -964 SizeY 36 OffsetBottom -70 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Waiting..." } } }
|
||||||
|
TextWidgetClass { Name "VehiclesSearchLabel" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 77 OffsetTop 77 SizeX 430 OffsetRight -458 SizeY 22 OffsetBottom -99 } Color 0.9 0.92 0.94 1 Text "Search name" "Font Size" 14 "Min Font Size" 14 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
TextWidgetClass { Name "VehiclesFilterLabel" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 470 OffsetLeft 470 PositionY 77 OffsetTop 77 SizeX 220 OffsetRight -690 SizeY 22 OffsetBottom -99 } Color 0.9 0.92 0.94 1 Text "Show" "Font Size" 14 "Min Font Size" 14 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
ButtonWidgetClass : "{DA43C77A0D1E59F8}UI/layouts/ERR/AdminValue.layout" { Name "VehiclesSearch" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 100 OffsetTop 100 SizeX 430 OffsetRight -458 SizeY 36 OffsetBottom -136 } components { SCR_EditBoxComponent "{547290FFBD5B33E9}" { m_sLabel "" } } }
|
||||||
|
ButtonWidgetClass : "{4B5AE6E64037FFB4}UI/layouts/WidgetLibrary/ComboBox/WLib_ComboBox.layout" { Name "VehiclesFilter" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 470 OffsetLeft 470 PositionY 100 OffsetTop 100 SizeX 220 OffsetRight -690 SizeY 36 OffsetBottom -136 } components { SCR_ComboBoxComponent "{546B1D464BC32853}" { m_bUseLabel 0 m_sLabel "" m_fSizeWithoutLabel 36 } } }
|
||||||
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "VehiclesShow" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 704 OffsetLeft 704 PositionY 100 OffsetTop 100 SizeX 120 OffsetRight -824 SizeY 36 OffsetBottom -136 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Search" } } }
|
||||||
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "VehiclesClear" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 838 OffsetLeft 838 PositionY 100 OffsetTop 100 SizeX 126 OffsetRight -964 SizeY 36 OffsetBottom -136 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Clear" } } }
|
||||||
|
TextWidgetClass { Name "VehiclesBulkScope" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 145 OffsetTop 145 SizeX 350 OffsetRight -378 SizeY 28 OffsetBottom -173 } Color 0.9 0.92 0.94 1 Text "This use, whole mod:" "Font Size" 17 "Min Font Size" 17 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "VehiclesEnableAll" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 554 OffsetLeft 554 PositionY 140 OffsetTop 140 SizeX 198 OffsetRight -752 SizeY 36 OffsetBottom -176 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Enable all for use" } } }
|
||||||
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "VehiclesDisableAll" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 766 OffsetLeft 766 PositionY 140 OffsetTop 140 SizeX 198 OffsetRight -964 SizeY 36 OffsetBottom -176 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Disable all for use" } } }
|
||||||
|
ScrollLayoutWidgetClass { Name "VehiclesScroll" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 184 OffsetTop 184 SizeX 936 OffsetRight -964 SizeY 294 OffsetBottom -478 } { VerticalLayoutWidgetClass { Name "VehiclesContent" Slot AlignableSlot { HorizontalAlign 3 VerticalAlign 3 Padding 0 0 12 0 } { TextWidgetClass { Name "VehiclesText" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 0 OffsetTop 0 SizeX 920 OffsetRight -920 SizeY 30 OffsetBottom -30 } Color 0.9 0.92 0.94 1 Text "Select a mod above." "Font Size" 17 "Min Font Size" 17 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
} } } }
|
||||||
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "VehiclesPrevious" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 486 OffsetTop 486 SizeX 120 OffsetRight -148 SizeY 36 OffsetBottom -522 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Previous" } } }
|
||||||
|
TextWidgetClass { Name "VehiclesPageState" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 162 OffsetLeft 162 PositionY 490 OffsetTop 490 SizeX 668 OffsetRight -830 SizeY 32 OffsetBottom -522 } Color 0.9 0.92 0.94 1 Text "Waiting for server..." "Font Size" 17 "Min Font Size" 17 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "VehiclesNext" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 844 OffsetLeft 844 PositionY 486 OffsetTop 486 SizeX 120 OffsetRight -964 SizeY 36 OffsetBottom -522 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Next" } } }
|
||||||
|
} }
|
||||||
|
FrameWidgetClass { "Is Visible" 0 Name "FactionsPage" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 220 OffsetLeft 220 PositionY 160 OffsetTop 160 SizeX 1000 OffsetRight -1220 SizeY 532 OffsetBottom -692 } {
|
||||||
TextWidgetClass {
|
TextWidgetClass {
|
||||||
Name "FactionHelp" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 6 OffsetTop 6 SizeX 936 OffsetRight -964 SizeY 50 OffsetBottom -56 }
|
Name "FactionHelp" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 6 OffsetTop 6 SizeX 936 OffsetRight -964 SizeY 50 OffsetBottom -56 }
|
||||||
Color 0.9 0.92 0.94 1 Text "Choose three different factions, then Apply. Random all saves immediately and picks anew each round." "Font Size" 19 "Min Font Size" 19 Wrap 1
|
Color 0.9 0.92 0.94 1 Text "Choose three different factions, then Apply. Random all saves immediately and picks anew each round." "Font Size" 19 "Min Font Size" 19 Wrap 1
|
||||||
@@ -405,10 +507,10 @@ ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_But
|
|||||||
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "FactionsApply" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 742 OffsetLeft 742 PositionY 418 OffsetTop 418 SizeX 222 OffsetRight -964 SizeY 42 OffsetBottom -460 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Apply factions" } } }
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "FactionsApply" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 742 OffsetLeft 742 PositionY 418 OffsetTop 418 SizeX 222 OffsetRight -964 SizeY 42 OffsetBottom -460 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Apply factions" } } }
|
||||||
|
|
||||||
} }
|
} }
|
||||||
FrameWidgetClass { "Is Visible" 0 Name "WorldPage" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 220 OffsetLeft 220 PositionY 160 OffsetTop 160 SizeX 1000 OffsetRight -1220 SizeY 486 OffsetBottom -646 } {
|
FrameWidgetClass { "Is Visible" 0 Name "WorldPage" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 220 OffsetLeft 220 PositionY 160 OffsetTop 160 SizeX 1000 OffsetRight -1220 SizeY 532 OffsetBottom -692 } {
|
||||||
TextWidgetClass {
|
TextWidgetClass {
|
||||||
Name "WorldHelp" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 6 OffsetTop 6 SizeX 936 OffsetRight -964 SizeY 50 OffsetBottom -56 }
|
Name "WorldHelp" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 6 OffsetTop 6 SizeX 936 OffsetRight -964 SizeY 50 OffsetBottom -56 }
|
||||||
Color 0.9 0.92 0.94 1 Text "Time and weather apply next round. View distance applies now. Press Apply to save." "Font Size" 19 "Min Font Size" 19 Wrap 1
|
Color 0.9 0.92 0.94 1 Text "Time and weather apply next round. View distance applies now. Weather buttons save immediately; use Apply for edited values." "Font Size" 19 "Min Font Size" 19 Wrap 1
|
||||||
FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" }
|
FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" }
|
||||||
}
|
}
|
||||||
TextWidgetClass {
|
TextWidgetClass {
|
||||||
@@ -538,8 +640,8 @@ ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_But
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
TextWidgetClass {
|
TextWidgetClass {
|
||||||
Name "Label_weatherProfile" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 322 OffsetTop 322 SizeX 368 OffsetRight -396 SizeY 28 OffsetBottom -350 }
|
Name "Label_weatherProfile" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 322 OffsetTop 322 SizeX 168 OffsetRight -196 SizeY 28 OffsetBottom -350 }
|
||||||
Color 0.9 0.92 0.94 1 Text "Weather (authored / clear / overcast / rain)" "Font Size" 20 "Min Font Size" 20 Wrap 1 FontProperties FontProperties {
|
Color 0.9 0.92 0.94 1 Text "Weather" "Font Size" 20 "Min Font Size" 20 Wrap 1 FontProperties FontProperties {
|
||||||
Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt"
|
Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -551,34 +653,15 @@ TextWidgetClass {
|
|||||||
}
|
}
|
||||||
TextWidgetClass {
|
TextWidgetClass {
|
||||||
Name "Help_weatherProfile" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 378 OffsetTop 378 SizeX 936 OffsetRight -964 SizeY 20 OffsetBottom -398 }
|
Name "Help_weatherProfile" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 378 OffsetTop 378 SizeX 936 OffsetRight -964 SizeY 20 OffsetBottom -398 }
|
||||||
Color 0.72 0.81 0.87 1 Text "Use authored, clear, overcast or rain. Random chooses one of the three weather types next round." "Font Size" 16 "Min Font Size" 16 Wrap 1
|
Color 0.72 0.81 0.87 1 Text "Map default keeps authored weather. Random chooses clear, overcast or rain each round." "Font Size" 16 "Min Font Size" 16 Wrap 1
|
||||||
FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" }
|
FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" }
|
||||||
}
|
}
|
||||||
|
|
||||||
ButtonWidgetClass : "{DA43C77A0D1E59F8}UI/layouts/ERR/AdminValue.layout" {
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "Choice_weatherProfile_authored" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 208 OffsetLeft 208 PositionY 322 OffsetTop 322 SizeX 140 OffsetRight -348 SizeY 32 OffsetBottom -354 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Map default" } } }
|
||||||
Name "weatherProfile" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 408 OffsetLeft 408 PositionY 322 OffsetTop 322 SizeX 240 OffsetRight -648 SizeY 42 OffsetBottom -364 }
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "Choice_weatherProfile_clear" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 362 OffsetLeft 362 PositionY 322 OffsetTop 322 SizeX 140 OffsetRight -502 SizeY 32 OffsetBottom -354 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Clear" } } }
|
||||||
components {
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "Choice_weatherProfile_overcast" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 516 OffsetLeft 516 PositionY 322 OffsetTop 322 SizeX 140 OffsetRight -656 SizeY 32 OffsetBottom -354 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Overcast" } } }
|
||||||
SCR_EditBoxComponent "{547290FFBD5B33E9}" {
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "Choice_weatherProfile_rain" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 670 OffsetLeft 670 PositionY 322 OffsetTop 322 SizeX 140 OffsetRight -810 SizeY 32 OffsetBottom -354 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Rain" } } }
|
||||||
m_sLabel ""
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "Choice_weatherProfile_random" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 824 OffsetLeft 824 PositionY 322 OffsetTop 322 SizeX 140 OffsetRight -964 SizeY 32 OffsetBottom -354 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Random" } } }
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" {
|
|
||||||
Name "Apply_weatherProfile" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 660 OffsetLeft 660 PositionY 322 OffsetTop 322 SizeX 112 OffsetRight -772 SizeY 42 OffsetBottom -364 }
|
|
||||||
components {
|
|
||||||
SCR_ButtonTextComponent "{5548F0143AE99095}" {
|
|
||||||
m_sText "Apply"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" {
|
|
||||||
Name "Random_weatherProfile" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 784 OffsetLeft 784 PositionY 322 OffsetTop 322 SizeX 180 OffsetRight -964 SizeY 42 OffsetBottom -364 }
|
|
||||||
components {
|
|
||||||
SCR_ButtonTextComponent "{5548F0143AE99095}" {
|
|
||||||
m_sText "Random"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
TextWidgetClass {
|
TextWidgetClass {
|
||||||
Name "Label_viewDistanceMetres" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 406 OffsetTop 406 SizeX 368 OffsetRight -396 SizeY 28 OffsetBottom -434 }
|
Name "Label_viewDistanceMetres" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 406 OffsetTop 406 SizeX 368 OffsetRight -396 SizeY 28 OffsetBottom -434 }
|
||||||
Color 0.9 0.92 0.94 1 Text "View distance (500-10000 m / default)" "Font Size" 20 "Min Font Size" 20 Wrap 1 FontProperties FontProperties {
|
Color 0.9 0.92 0.94 1 Text "View distance (500-10000 m / default)" "Font Size" 20 "Min Font Size" 20 Wrap 1 FontProperties FontProperties {
|
||||||
@@ -630,7 +713,7 @@ ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_But
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} }
|
} }
|
||||||
FrameWidgetClass { "Is Visible" 0 Name "IntelPage" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 220 OffsetLeft 220 PositionY 160 OffsetTop 160 SizeX 1000 OffsetRight -1220 SizeY 486 OffsetBottom -646 } {
|
FrameWidgetClass { "Is Visible" 0 Name "IntelPage" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 220 OffsetLeft 220 PositionY 160 OffsetTop 160 SizeX 1000 OffsetRight -1220 SizeY 532 OffsetBottom -692 } {
|
||||||
TextWidgetClass {
|
TextWidgetClass {
|
||||||
Name "IntelHelp" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 6 OffsetTop 6 SizeX 936 OffsetRight -964 SizeY 40 OffsetBottom -46 }
|
Name "IntelHelp" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 6 OffsetTop 6 SizeX 936 OffsetRight -964 SizeY 40 OffsetBottom -46 }
|
||||||
Color 0.9 0.92 0.94 1 Text "Choose how sites appear on the map and how often soldiers carry intel." "Font Size" 19 "Min Font Size" 19 Wrap 1
|
Color 0.9 0.92 0.94 1 Text "Choose how sites appear on the map and how often soldiers carry intel." "Font Size" 19 "Min Font Size" 19 Wrap 1
|
||||||
@@ -763,14 +846,27 @@ ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_But
|
|||||||
components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Disabled" } }
|
components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Disabled" } }
|
||||||
}
|
}
|
||||||
} }
|
} }
|
||||||
FrameWidgetClass { "Is Visible" 0 Name "StatusPage" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 220 OffsetLeft 220 PositionY 160 OffsetTop 160 SizeX 1000 OffsetRight -1220 SizeY 486 OffsetBottom -646 } {
|
FrameWidgetClass { "Is Visible" 0 Name "RadioIntelPage" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 220 OffsetLeft 220 PositionY 160 OffsetTop 160 SizeX 1000 OffsetRight -1220 SizeY 532 OffsetBottom -692 } {
|
||||||
|
TextWidgetClass { Name "RadioIntelHeading" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 6 OffsetTop 6 SizeX 710 OffsetRight -738 SizeY 32 OffsetBottom -38 } Color 0.9 0.92 0.94 1 Text "Radio intel progression" "Font Size" 22 "Min Font Size" 22 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
|
||||||
|
TextWidgetClass { Name "RadioIntelHelp" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 50 OffsetTop 50 SizeX 936 OffsetRight -964 SizeY 44 OffsetBottom -94 } Color 0.9 0.92 0.94 1 Text "Identify other sites before documents reveal radio stations. Finding and using a radio in person is always allowed." "Font Size" 18 "Min Font Size" 18 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
TextWidgetClass { Name "Label_radioIntelSiteCount" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 112 OffsetTop 112 SizeX 590 OffsetRight -618 SizeY 32 OffsetBottom -144 } Color 0.9 0.92 0.94 1 Text "Sites required before radio intel (0-20)" "Font Size" 20 "Min Font Size" 20 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
ButtonWidgetClass : "{DA43C77A0D1E59F8}UI/layouts/ERR/AdminValue.layout" { Name "radioIntelSiteCount" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 640 OffsetLeft 640 PositionY 108 OffsetTop 108 SizeX 112 OffsetRight -752 SizeY 38 OffsetBottom -146 } components { SCR_EditBoxComponent "{547290FFBD5B33E9}" { m_sLabel "" } } }
|
||||||
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "Apply_radioIntelSiteCount" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 764 OffsetLeft 764 PositionY 108 OffsetTop 108 SizeX 200 OffsetRight -964 SizeY 38 OffsetBottom -146 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Apply" } } }
|
||||||
|
TextWidgetClass { Name "Saved_radioIntelSiteCount" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 640 OffsetLeft 640 PositionY 148 OffsetTop 148 SizeX 324 OffsetRight -964 SizeY 24 OffsetBottom -172 } Color 0.9 0.92 0.94 1 Text "Waiting for server" "Font Size" 16 "Min Font Size" 16 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
TextWidgetClass { Name "Help_radioIntelSiteCount" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 176 OffsetTop 176 SizeX 936 OffsetRight -964 SizeY 26 OffsetBottom -202 } Color 0.9 0.92 0.94 1 Text "Default 5. Set 0 to disable. Apply saves the requirement for the next round." "Font Size" 17 "Min Font Size" 17 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
TextWidgetClass { Name "RadioIntelSettingsHelp" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 424 OffsetTop 424 SizeX 936 OffsetRight -964 SizeY 52 OffsetBottom -476 } Color 0.9 0.92 0.94 1 Text "Only fully identified non-radio sites count. Prison, extraction and automatic starting reveals are excluded. The requirement adjusts to available sites after preparation finishes." "Font Size" 18 "Min Font Size" 18 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
TextWidgetClass { Name "RadioIntelProgress" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 232 OffsetTop 232 SizeX 936 OffsetRight -964 SizeY 64 OffsetBottom -296 } Color 0.9 0.92 0.94 1 Text "Waiting for current-round progress" "Font Size" 18 "Min Font Size" 18 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
TextWidgetClass { Name "RadioIntelDocumentsHelp" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 316 OffsetTop 316 SizeX 936 OffsetRight -964 SizeY 76 OffsetBottom -392 } Color 0.9 0.92 0.94 1 Text "Ordinary folders reveal other sites while radio intel is locked. Keep fixed radio documents: they will be read automatically after unlocking. Question marks and rough clues do not count as identified sites." "Font Size" 18 "Min Font Size" 18 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
} }
|
||||||
|
FrameWidgetClass { "Is Visible" 0 Name "StatusPage" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 220 OffsetLeft 220 PositionY 160 OffsetTop 160 SizeX 1000 OffsetRight -1220 SizeY 532 OffsetBottom -692 } {
|
||||||
TextWidgetClass {
|
TextWidgetClass {
|
||||||
Name "StatusHelp" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 6 OffsetTop 6 SizeX 936 OffsetRight -964 SizeY 50 OffsetBottom -56 }
|
Name "StatusHelp" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 6 OffsetTop 6 SizeX 936 OffsetRight -964 SizeY 50 OffsetBottom -56 }
|
||||||
Color 0.9 0.92 0.94 1 Text "Live mission status. Scroll for more. Mortars: operational / deployed. UAZ allowances include past losses." "Font Size" 19 "Min Font Size" 19 Wrap 1
|
Color 0.9 0.92 0.94 1 Text "Live mission status. Scroll for more. Mortars: operational / deployed. UAZ allowances include past losses." "Font Size" 19 "Min Font Size" 19 Wrap 1
|
||||||
FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" }
|
FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" }
|
||||||
}
|
}
|
||||||
ScrollLayoutWidgetClass {
|
ScrollLayoutWidgetClass {
|
||||||
Name "StatusScroll" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 58 OffsetTop 58 SizeX 936 OffsetRight -964 SizeY 416 OffsetBottom -474 }
|
Name "StatusScroll" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 58 OffsetTop 58 SizeX 936 OffsetRight -964 SizeY 462 OffsetBottom -520 }
|
||||||
components {
|
components {
|
||||||
SCR_GamepadScrollComponent "{5707100AC31E657E}" {
|
SCR_GamepadScrollComponent "{5707100AC31E657E}" {
|
||||||
}
|
}
|
||||||
@@ -794,7 +890,7 @@ ScrollLayoutWidgetClass {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} }
|
} }
|
||||||
FrameWidgetClass { "Is Visible" 0 Name "TerrainsPage" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 220 OffsetLeft 220 PositionY 160 OffsetTop 160 SizeX 1000 OffsetRight -1220 SizeY 486 OffsetBottom -646 } {
|
FrameWidgetClass { "Is Visible" 0 Name "TerrainsPage" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 220 OffsetLeft 220 PositionY 160 OffsetTop 160 SizeX 1000 OffsetRight -1220 SizeY 532 OffsetBottom -692 } {
|
||||||
TextWidgetClass { Name "TerrainHelp" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 6 OffsetTop 6 SizeX 936 OffsetRight -964 SizeY 60 OffsetBottom -66 } Color 0.9 0.92 0.94 1 Text "Choose a terrain scenario from enabled server mods. Saved changes apply after restart; players must rejoin when the map changes." "Font Size" 20 "Min Font Size" 20 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
TextWidgetClass { Name "TerrainHelp" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 6 OffsetTop 6 SizeX 936 OffsetRight -964 SizeY 60 OffsetBottom -66 } Color 0.9 0.92 0.94 1 Text "Choose a terrain scenario from enabled server mods. Saved changes apply after restart; players must rejoin when the map changes." "Font Size" 20 "Min Font Size" 20 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
TextWidgetClass { Name "TerrainCurrent" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 80 OffsetTop 80 SizeX 936 OffsetRight -964 SizeY 50 OffsetBottom -130 } Color 0.9 0.92 0.94 1 Text "Waiting for current terrain..." "Font Size" 20 "Min Font Size" 20 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
TextWidgetClass { Name "TerrainCurrent" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 80 OffsetTop 80 SizeX 936 OffsetRight -964 SizeY 50 OffsetBottom -130 } Color 0.9 0.92 0.94 1 Text "Waiting for current terrain..." "Font Size" 20 "Min Font Size" 20 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
TextWidgetClass { Name "TerrainSaved" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 142 OffsetTop 142 SizeX 936 OffsetRight -964 SizeY 50 OffsetBottom -192 } Color 0.9 0.92 0.94 1 Text "Waiting for saved selection..." "Font Size" 20 "Min Font Size" 20 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
TextWidgetClass { Name "TerrainSaved" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 142 OffsetTop 142 SizeX 936 OffsetRight -964 SizeY 50 OffsetBottom -192 } Color 0.9 0.92 0.94 1 Text "Waiting for saved selection..." "Font Size" 20 "Min Font Size" 20 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
@@ -804,7 +900,7 @@ TextWidgetClass { Name "TerrainWarning" Slot FrameWidgetSlot { Anchor 0 0 0 0 Po
|
|||||||
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "TerrainDefault" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 418 OffsetTop 418 SizeX 260 OffsetRight -288 SizeY 42 OffsetBottom -460 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Use server default" } } }
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "TerrainDefault" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 418 OffsetTop 418 SizeX 260 OffsetRight -288 SizeY 42 OffsetBottom -460 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Use server default" } } }
|
||||||
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "TerrainApply" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 742 OffsetLeft 742 PositionY 418 OffsetTop 418 SizeX 222 OffsetRight -964 SizeY 42 OffsetBottom -460 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Save terrain" } } }
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "TerrainApply" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 742 OffsetLeft 742 PositionY 418 OffsetTop 418 SizeX 222 OffsetRight -964 SizeY 42 OffsetBottom -460 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Save terrain" } } }
|
||||||
} }
|
} }
|
||||||
FrameWidgetClass { "Is Visible" 0 Name "ItemsPage" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 220 OffsetLeft 220 PositionY 160 OffsetTop 160 SizeX 1000 OffsetRight -1220 SizeY 486 OffsetBottom -646 } {
|
FrameWidgetClass { "Is Visible" 0 Name "ItemsPage" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 220 OffsetLeft 220 PositionY 160 OffsetTop 160 SizeX 1000 OffsetRight -1220 SizeY 532 OffsetBottom -692 } {
|
||||||
TextWidgetClass { Name "EquipmentHeading" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 4 OffsetTop 4 SizeX 936 OffsetRight -964 SizeY 30 OffsetBottom -34 } Color 0.9 0.92 0.94 1 Text "Extra equipment for enemy soldiers" "Font Size" 22 "Min Font Size" 22 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
TextWidgetClass { Name "EquipmentHeading" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 4 OffsetTop 4 SizeX 936 OffsetRight -964 SizeY 30 OffsetBottom -34 } Color 0.9 0.92 0.94 1 Text "Extra equipment for enemy soldiers" "Font Size" 22 "Min Font Size" 22 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
TextWidgetClass { Name "EquipmentIntro" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 42 OffsetTop 42 SizeX 936 OffsetRight -964 SizeY 46 OffsetBottom -88 } Color 0.9 0.92 0.94 1 Text "Newly equipped soldiers can receive one extra item from the allowed catalog. First choose how often this happens, then choose which items are more common." "Font Size" 17 "Min Font Size" 17 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
TextWidgetClass { Name "EquipmentIntro" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 42 OffsetTop 42 SizeX 936 OffsetRight -964 SizeY 46 OffsetBottom -88 } Color 0.9 0.92 0.94 1 Text "Newly equipped soldiers can receive one extra item from the allowed catalog. First choose how often this happens, then choose which items are more common." "Font Size" 17 "Min Font Size" 17 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
TextWidgetClass { Name "Label_automaticEquipmentChancePercent" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 102 OffsetTop 102 SizeX 936 OffsetRight -964 SizeY 30 OffsetBottom -132 } Color 0.9 0.92 0.94 1 Text "Soldiers receiving an extra-item attempt (0-100%)" "Font Size" 20 "Min Font Size" 20 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
TextWidgetClass { Name "Label_automaticEquipmentChancePercent" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 102 OffsetTop 102 SizeX 936 OffsetRight -964 SizeY 30 OffsetBottom -132 } Color 0.9 0.92 0.94 1 Text "Soldiers receiving an extra-item attempt (0-100%)" "Font Size" 20 "Min Font Size" 20 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
@@ -818,7 +914,7 @@ TextWidgetClass { Name "EquipmentSteps" Slot FrameWidgetSlot { Anchor 0 0 0 0 Po
|
|||||||
TextWidgetClass { Name "EquipmentTiming" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 374 OffsetTop 374 SizeX 936 OffsetRight -964 SizeY 44 OffsetBottom -418 } Color 0.9 0.92 0.94 1 Text "Apply saves the chance above. Item buttons save immediately. Changes affect future equipment rolls; existing soldiers keep their gear." "Font Size" 17 "Min Font Size" 17 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
TextWidgetClass { Name "EquipmentTiming" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 374 OffsetTop 374 SizeX 936 OffsetRight -964 SizeY 44 OffsetBottom -418 } Color 0.9 0.92 0.94 1 Text "Apply saves the chance above. Item buttons save immediately. Changes affect future equipment rolls; existing soldiers keep their gear." "Font Size" 17 "Min Font Size" 17 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
|
||||||
} }
|
} }
|
||||||
FrameWidgetClass { "Is Visible" 0 Name "TTSPage" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 220 OffsetLeft 220 PositionY 160 OffsetTop 160 SizeX 1000 OffsetRight -1220 SizeY 486 OffsetBottom -646 } {
|
FrameWidgetClass { "Is Visible" 0 Name "TTSPage" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 220 OffsetLeft 220 PositionY 160 OffsetTop 160 SizeX 1000 OffsetRight -1220 SizeY 532 OffsetBottom -692 } {
|
||||||
TextWidgetClass { Name "TTSHelp" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 6 OffsetTop 6 SizeX 936 OffsetRight -964 SizeY 58 OffsetBottom -64 } Color 0.9 0.92 0.94 1 Text "Prerecorded speech works without a service. On / Off applies to everyone. Live TTS below is optional and has its own setting." "Font Size" 18 "Min Font Size" 18 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
TextWidgetClass { Name "TTSHelp" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 6 OffsetTop 6 SizeX 936 OffsetRight -964 SizeY 58 OffsetBottom -64 } Color 0.9 0.92 0.94 1 Text "Prerecorded speech works without a service. On / Off applies to everyone. Live TTS below is optional and has its own setting." "Font Size" 18 "Min Font Size" 18 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "TTSSelectionState" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 74 OffsetTop 74 SizeX 150 OffsetRight -178 SizeY 42 OffsetBottom -116 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Waiting..." } } }
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "TTSSelectionState" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 74 OffsetTop 74 SizeX 150 OffsetRight -178 SizeY 42 OffsetBottom -116 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Waiting..." } } }
|
||||||
TextWidgetClass { Name "TTSStatus" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 198 OffsetLeft 198 PositionY 74 OffsetTop 74 SizeX 766 OffsetRight -964 SizeY 58 OffsetBottom -132 } Color 0.9 0.92 0.94 1 Text "Waiting for the server." "Font Size" 18 "Min Font Size" 18 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
TextWidgetClass { Name "TTSStatus" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 198 OffsetLeft 198 PositionY 74 OffsetTop 74 SizeX 766 OffsetRight -964 SizeY 58 OffsetBottom -132 } Color 0.9 0.92 0.94 1 Text "Waiting for the server." "Font Size" 18 "Min Font Size" 18 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
@@ -836,7 +932,7 @@ ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_But
|
|||||||
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "RecordingsOff" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 758 OffsetLeft 758 PositionY 386 OffsetTop 386 SizeX 180 OffsetRight -938 SizeY 42 OffsetBottom -428 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Off" } } }
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "RecordingsOff" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 758 OffsetLeft 758 PositionY 386 OffsetTop 386 SizeX 180 OffsetRight -938 SizeY 42 OffsetBottom -428 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Off" } } }
|
||||||
TextWidgetClass { Name "TTSServiceHelp" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 440 OffsetTop 440 SizeX 936 OffsetRight -964 SizeY 42 OffsetBottom -482 } Color 0.9 0.92 0.94 1 Text "Prerecorded speech: welcome, soldier dialogue and extraction radio. Text messages stay visible." "Font Size" 17 "Min Font Size" 17 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
TextWidgetClass { Name "TTSServiceHelp" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 440 OffsetTop 440 SizeX 936 OffsetRight -964 SizeY 42 OffsetBottom -482 } Color 0.9 0.92 0.94 1 Text "Prerecorded speech: welcome, soldier dialogue and extraction radio. Text messages stay visible." "Font Size" 17 "Min Font Size" 17 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
} }
|
} }
|
||||||
FrameWidgetClass { "Is Visible" 0 Name "ForcesPage" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 220 OffsetLeft 220 PositionY 160 OffsetTop 160 SizeX 1000 OffsetRight -1220 SizeY 486 OffsetBottom -646 } {
|
FrameWidgetClass { "Is Visible" 0 Name "ForcesPage" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 220 OffsetLeft 220 PositionY 160 OffsetTop 160 SizeX 1000 OffsetRight -1220 SizeY 532 OffsetBottom -692 } {
|
||||||
TextWidgetClass {
|
TextWidgetClass {
|
||||||
Name "ForcesHelp" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 6 OffsetTop 6 SizeX 710 OffsetRight -738 SizeY 50 OffsetBottom -56 }
|
Name "ForcesHelp" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 6 OffsetTop 6 SizeX 710 OffsetRight -738 SizeY 50 OffsetBottom -56 }
|
||||||
Color 0.9 0.92 0.94 1 Text "Enemy patrols, site guards and equipment. Equipment changes apply to newly equipped soldiers." "Font Size" 19 "Min Font Size" 19 Wrap 1
|
Color 0.9 0.92 0.94 1 Text "Enemy patrols, site guards and equipment. Equipment changes apply to newly equipped soldiers." "Font Size" 19 "Min Font Size" 19 Wrap 1
|
||||||
@@ -883,11 +979,6 @@ TextWidgetClass {
|
|||||||
Color 0.72 0.81 0.87 1 Text "0 disables convoys. Lost convoys stay lost. Next round." "Font Size" 14 "Min Font Size" 14 Wrap 1
|
Color 0.72 0.81 0.87 1 Text "0 disables convoys. Lost convoys stay lost. Next round." "Font Size" 14 "Min Font Size" 14 Wrap 1
|
||||||
FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" }
|
FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" }
|
||||||
}
|
}
|
||||||
TextWidgetClass { Name "Label_roadblockCount" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 508 OffsetLeft 508 PositionY 66 OffsetTop 66 SizeX 456 OffsetRight -964 SizeY 26 OffsetBottom -92 } Color 0.9 0.92 0.94 1 Text "Roadblocks (0-30; 0 off)" "Font Size" 20 "Min Font Size" 20 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
|
||||||
ButtonWidgetClass : "{DA43C77A0D1E59F8}UI/layouts/ERR/AdminValue.layout" { Name "roadblockCount" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 508 OffsetLeft 508 PositionY 96 OffsetTop 96 SizeX 130 OffsetRight -638 SizeY 38 OffsetBottom -134 } components { SCR_EditBoxComponent "{547290FFBD5B33E9}" { m_sLabel "" } } }
|
|
||||||
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "Apply_roadblockCount" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 650 OffsetLeft 650 PositionY 96 OffsetTop 96 SizeX 110 OffsetRight -760 SizeY 38 OffsetBottom -134 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Apply" } } }
|
|
||||||
TextWidgetClass { Name "Saved_roadblockCount" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 508 OffsetLeft 508 PositionY 136 OffsetTop 136 SizeX 456 OffsetRight -964 SizeY 22 OffsetBottom -158 } Color 0.9 0.92 0.94 1 Text "Waiting for saved value" "Font Size" 16 "Min Font Size" 16 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
|
||||||
TextWidgetClass { Name "Help_roadblockCount" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 508 OffsetLeft 508 PositionY 160 OffsetTop 160 SizeX 456 OffsetRight -964 SizeY 32 OffsetBottom -192 } Color 0.9 0.92 0.94 1 Text "Four guards per post. Applies next round." "Font Size" 14 "Min Font Size" 14 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
|
||||||
|
|
||||||
|
|
||||||
TextWidgetClass { Name "GuardLinkLabel" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 202 OffsetTop 202 SizeX 456 OffsetRight -484 SizeY 28 OffsetBottom -230 } Color 0.9 0.92 0.94 1 Text "Building guards, camps and hospitals" "Font Size" 18 "Min Font Size" 18 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
TextWidgetClass { Name "GuardLinkLabel" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 202 OffsetTop 202 SizeX 456 OffsetRight -484 SizeY 28 OffsetBottom -230 } Color 0.9 0.92 0.94 1 Text "Building guards, camps and hospitals" "Font Size" 18 "Min Font Size" 18 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
@@ -984,7 +1075,25 @@ TextWidgetClass {
|
|||||||
|
|
||||||
|
|
||||||
} }
|
} }
|
||||||
FrameWidgetClass { "Is Visible" 0 Name "GuardsPage" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 220 OffsetLeft 220 PositionY 160 OffsetTop 160 SizeX 1000 OffsetRight -1220 SizeY 486 OffsetBottom -646 } {
|
FrameWidgetClass { "Is Visible" 0 Name "CheckpointsPage" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 220 OffsetLeft 220 PositionY 160 OffsetTop 160 SizeX 1000 OffsetRight -1220 SizeY 532 OffsetBottom -692 } {
|
||||||
|
TextWidgetClass { Name "CheckpointsHelp" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 6 OffsetTop 6 SizeX 936 OffsetRight -964 SizeY 50 OffsetBottom -56 } Color 0.9 0.92 0.94 1 Text "Set counts separately for small and main-road checkpoints. Changes apply next round. Road width determines the type; unsuitable locations are skipped." "Font Size" 19 "Min Font Size" 19 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
TextWidgetClass { Name "Label_roadblockCount" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 66 OffsetTop 66 SizeX 456 OffsetRight -484 SizeY 26 OffsetBottom -92 } Color 0.9 0.92 0.94 1 Text "Small checkpoints (0-30; 0 off)" "Font Size" 20 "Min Font Size" 20 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
ButtonWidgetClass : "{DA43C77A0D1E59F8}UI/layouts/ERR/AdminValue.layout" { Name "roadblockCount" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 96 OffsetTop 96 SizeX 130 OffsetRight -158 SizeY 38 OffsetBottom -134 } components { SCR_EditBoxComponent "{547290FFBD5B33E9}" { m_sLabel "" } } }
|
||||||
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "Apply_roadblockCount" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 170 OffsetLeft 170 PositionY 96 OffsetTop 96 SizeX 110 OffsetRight -280 SizeY 38 OffsetBottom -134 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Apply" } } }
|
||||||
|
TextWidgetClass { Name "Saved_roadblockCount" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 136 OffsetTop 136 SizeX 456 OffsetRight -484 SizeY 22 OffsetBottom -158 } Color 0.9 0.92 0.94 1 Text "Waiting for saved value" "Font Size" 16 "Min Font Size" 16 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
TextWidgetClass { Name "Help_roadblockCount" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 160 OffsetTop 160 SizeX 456 OffsetRight -484 SizeY 32 OffsetBottom -192 } Color 0.9 0.92 0.94 1 Text "Four guards, compact cover. Below the main-road width." "Font Size" 14 "Min Font Size" 14 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
TextWidgetClass { Name "Label_largeRoadblockCount" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 508 OffsetLeft 508 PositionY 66 OffsetTop 66 SizeX 456 OffsetRight -964 SizeY 26 OffsetBottom -92 } Color 0.9 0.92 0.94 1 Text "Large checkpoints (0-30; 0 off)" "Font Size" 20 "Min Font Size" 20 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
ButtonWidgetClass : "{DA43C77A0D1E59F8}UI/layouts/ERR/AdminValue.layout" { Name "largeRoadblockCount" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 508 OffsetLeft 508 PositionY 96 OffsetTop 96 SizeX 130 OffsetRight -638 SizeY 38 OffsetBottom -134 } components { SCR_EditBoxComponent "{547290FFBD5B33E9}" { m_sLabel "" } } }
|
||||||
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "Apply_largeRoadblockCount" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 650 OffsetLeft 650 PositionY 96 OffsetTop 96 SizeX 110 OffsetRight -760 SizeY 38 OffsetBottom -134 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Apply" } } }
|
||||||
|
TextWidgetClass { Name "Saved_largeRoadblockCount" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 508 OffsetLeft 508 PositionY 136 OffsetTop 136 SizeX 456 OffsetRight -964 SizeY 22 OffsetBottom -158 } Color 0.9 0.92 0.94 1 Text "Waiting for saved value" "Font Size" 16 "Min Font Size" 16 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
TextWidgetClass { Name "Help_largeRoadblockCount" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 508 OffsetLeft 508 PositionY 160 OffsetTop 160 SizeX 456 OffsetRight -964 SizeY 32 OffsetBottom -192 } Color 0.9 0.92 0.94 1 Text "Eight guards, extended cover and approach barriers." "Font Size" 14 "Min Font Size" 14 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
TextWidgetClass { Name "Label_mainRoadWidthMetres" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 202 OffsetTop 202 SizeX 456 OffsetRight -484 SizeY 26 OffsetBottom -228 } Color 0.9 0.92 0.94 1 Text "Main-road minimum width (5-12 m)" "Font Size" 20 "Min Font Size" 20 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
ButtonWidgetClass : "{DA43C77A0D1E59F8}UI/layouts/ERR/AdminValue.layout" { Name "mainRoadWidthMetres" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 232 OffsetTop 232 SizeX 130 OffsetRight -158 SizeY 38 OffsetBottom -270 } components { SCR_EditBoxComponent "{547290FFBD5B33E9}" { m_sLabel "" } } }
|
||||||
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "Apply_mainRoadWidthMetres" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 170 OffsetLeft 170 PositionY 232 OffsetTop 232 SizeX 110 OffsetRight -280 SizeY 38 OffsetBottom -270 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Apply" } } }
|
||||||
|
TextWidgetClass { Name "Saved_mainRoadWidthMetres" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 272 OffsetTop 272 SizeX 456 OffsetRight -484 SizeY 22 OffsetBottom -294 } Color 0.9 0.92 0.94 1 Text "Waiting for saved value" "Font Size" 16 "Min Font Size" 16 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
TextWidgetClass { Name "Help_mainRoadWidthMetres" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 296 OffsetTop 296 SizeX 456 OffsetRight -484 SizeY 32 OffsetBottom -328 } Color 0.9 0.92 0.94 1 Text "Default 6 m. Roads at or above this width use large posts." "Font Size" 14 "Min Font Size" 14 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
} }
|
||||||
|
FrameWidgetClass { "Is Visible" 0 Name "GuardsPage" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 220 OffsetLeft 220 PositionY 160 OffsetTop 160 SizeX 1000 OffsetRight -1220 SizeY 532 OffsetBottom -692 } {
|
||||||
TextWidgetClass { Name "GuardsHeading" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 6 OffsetTop 6 SizeX 710 OffsetRight -738 SizeY 32 OffsetBottom -38 } Color 0.9 0.92 0.94 1 Text "Building guards - changes apply next round" "Font Size" 22 "Min Font Size" 22 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
TextWidgetClass { Name "GuardsHeading" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 6 OffsetTop 6 SizeX 710 OffsetRight -738 SizeY 32 OffsetBottom -38 } Color 0.9 0.92 0.94 1 Text "Building guards - changes apply next round" "Font Size" 22 "Min Font Size" 22 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
|
||||||
TextWidgetClass { Name "GuardsHelp" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 48 OffsetTop 48 SizeX 936 OffsetRight -964 SizeY 44 OffsetBottom -92 } Color 0.9 0.92 0.94 1 Text "Nearby eligible buildings share one site. Fixed guards defend their posts; all guards report contacts." "Font Size" 17 "Min Font Size" 17 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
TextWidgetClass { Name "GuardsHelp" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 48 OffsetTop 48 SizeX 936 OffsetRight -964 SizeY 44 OffsetBottom -92 } Color 0.9 0.92 0.94 1 Text "Nearby eligible buildings share one site. Fixed guards defend their posts; all guards report contacts." "Font Size" 17 "Min Font Size" 17 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
@@ -993,25 +1102,21 @@ ButtonWidgetClass : "{DA43C77A0D1E59F8}UI/layouts/ERR/AdminValue.layout" { Name
|
|||||||
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "Apply_objectGuardCount" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 170 OffsetLeft 170 PositionY 130 OffsetTop 130 SizeX 110 OffsetRight -280 SizeY 38 OffsetBottom -168 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Apply" } } }
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "Apply_objectGuardCount" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 170 OffsetLeft 170 PositionY 130 OffsetTop 130 SizeX 110 OffsetRight -280 SizeY 38 OffsetBottom -168 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Apply" } } }
|
||||||
TextWidgetClass { Name "Saved_objectGuardCount" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 171 OffsetTop 171 SizeX 936 OffsetRight -964 SizeY 22 OffsetBottom -193 } Color 0.9 0.92 0.94 1 Text "Waiting for saved value" "Font Size" 16 "Min Font Size" 16 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
TextWidgetClass { Name "Saved_objectGuardCount" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 171 OffsetTop 171 SizeX 936 OffsetRight -964 SizeY 22 OffsetBottom -193 } Color 0.9 0.92 0.94 1 Text "Waiting for saved value" "Font Size" 16 "Min Font Size" 16 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
TextWidgetClass { Name "Help_objectGuardCount" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 195 OffsetTop 195 SizeX 936 OffsetRight -964 SizeY 26 OffsetBottom -221 } Color 0.9 0.92 0.94 1 Text "Towers and chimneys use suitable platforms. Lost guards are not replaced." "Font Size" 14 "Min Font Size" 14 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
TextWidgetClass { Name "Help_objectGuardCount" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 195 OffsetTop 195 SizeX 936 OffsetRight -964 SizeY 26 OffsetBottom -221 } Color 0.9 0.92 0.94 1 Text "Towers and chimneys use suitable platforms. Lost guards are not replaced." "Font Size" 14 "Min Font Size" 14 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
TextWidgetClass { Name "Label_objectGuardEvery" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 225 OffsetTop 225 SizeX 936 OffsetRight -964 SizeY 26 OffsetBottom -251 } Color 0.9 0.92 0.94 1 Text "Guard every Nth eligible site (1-4)" "Font Size" 20 "Min Font Size" 20 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
TextWidgetClass { Name "Label_objectGuardEvery" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 225 OffsetTop 225 SizeX 936 OffsetRight -964 SizeY 26 OffsetBottom -251 } Color 0.9 0.92 0.94 1 Text "Guard spacing" "Font Size" 20 "Min Font Size" 20 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
ButtonWidgetClass : "{DA43C77A0D1E59F8}UI/layouts/ERR/AdminValue.layout" { Name "objectGuardEvery" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 255 OffsetTop 255 SizeX 130 OffsetRight -158 SizeY 38 OffsetBottom -293 } components { SCR_EditBoxComponent "{547290FFBD5B33E9}" { m_sLabel "" } } }
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "Choice_objectGuardEvery_1" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 255 OffsetTop 255 SizeX 222 OffsetRight -250 SizeY 38 OffsetBottom -293 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Every site" } } }
|
||||||
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "Apply_objectGuardEvery" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 170 OffsetLeft 170 PositionY 255 OffsetTop 255 SizeX 110 OffsetRight -280 SizeY 38 OffsetBottom -293 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Apply" } } }
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "Choice_objectGuardEvery_2" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 266 OffsetLeft 266 PositionY 255 OffsetTop 255 SizeX 222 OffsetRight -488 SizeY 38 OffsetBottom -293 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Every 2nd" } } }
|
||||||
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "Choice_objectGuardEvery_3" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 504 OffsetLeft 504 PositionY 255 OffsetTop 255 SizeX 222 OffsetRight -726 SizeY 38 OffsetBottom -293 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Every 3rd" } } }
|
||||||
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "Choice_objectGuardEvery_4" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 742 OffsetLeft 742 PositionY 255 OffsetTop 255 SizeX 222 OffsetRight -964 SizeY 38 OffsetBottom -293 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Every 4th" } } }
|
||||||
TextWidgetClass { Name "Saved_objectGuardEvery" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 296 OffsetTop 296 SizeX 936 OffsetRight -964 SizeY 22 OffsetBottom -318 } Color 0.9 0.92 0.94 1 Text "Waiting for saved value" "Font Size" 16 "Min Font Size" 16 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
TextWidgetClass { Name "Saved_objectGuardEvery" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 296 OffsetTop 296 SizeX 936 OffsetRight -964 SizeY 22 OffsetBottom -318 } Color 0.9 0.92 0.94 1 Text "Waiting for saved value" "Font Size" 16 "Min Font Size" 16 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
TextWidgetClass { Name "Help_objectGuardEvery" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 320 OffsetTop 320 SizeX 936 OffsetRight -964 SizeY 26 OffsetBottom -346 } Color 0.9 0.92 0.94 1 Text "1 = every site; 2 = every second; 3 = every third; 4 = every fourth." "Font Size" 14 "Min Font Size" 14 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
TextWidgetClass { Name "Help_objectGuardEvery" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 320 OffsetTop 320 SizeX 936 OffsetRight -964 SizeY 26 OffsetBottom -346 } Color 0.9 0.92 0.94 1 Text "1 = every site; 2 = every second; 3 = every third; 4 = every fourth." "Font Size" 14 "Min Font Size" 14 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "GuardEvery_1" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 292 OffsetLeft 292 PositionY 255 OffsetTop 255 SizeX 155 OffsetRight -447 SizeY 38 OffsetBottom -293 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Every site" } } }
|
TextWidgetClass { Name "Label_objectGuardHQMode" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 350 OffsetTop 350 SizeX 936 OffsetRight -964 SizeY 26 OffsetBottom -376 } Color 0.9 0.92 0.94 1 Text "HQ participation" "Font Size" 20 "Min Font Size" 20 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "GuardEvery_2" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 459 OffsetLeft 459 PositionY 255 OffsetTop 255 SizeX 155 OffsetRight -614 SizeY 38 OffsetBottom -293 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Every 2nd" } } }
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "Choice_objectGuardHQMode_0" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 380 OffsetTop 380 SizeX 304 OffsetRight -332 SizeY 38 OffsetBottom -418 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Hold positions" } } }
|
||||||
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "GuardEvery_3" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 626 OffsetLeft 626 PositionY 255 OffsetTop 255 SizeX 155 OffsetRight -781 SizeY 38 OffsetBottom -293 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Every 3rd" } } }
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "Choice_objectGuardHQMode_1" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 344 OffsetLeft 344 PositionY 380 OffsetTop 380 SizeX 304 OffsetRight -648 SizeY 38 OffsetBottom -418 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Half respond to HQ" } } }
|
||||||
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "GuardEvery_4" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 793 OffsetLeft 793 PositionY 255 OffsetTop 255 SizeX 155 OffsetRight -948 SizeY 38 OffsetBottom -293 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Every 4th" } } }
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "Choice_objectGuardHQMode_2" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 660 OffsetLeft 660 PositionY 380 OffsetTop 380 SizeX 304 OffsetRight -964 SizeY 38 OffsetBottom -418 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "All respond to HQ" } } }
|
||||||
TextWidgetClass { Name "Label_objectGuardHQMode" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 350 OffsetTop 350 SizeX 936 OffsetRight -964 SizeY 26 OffsetBottom -376 } Color 0.9 0.92 0.94 1 Text "HQ participation (0-2)" "Font Size" 20 "Min Font Size" 20 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
|
||||||
ButtonWidgetClass : "{DA43C77A0D1E59F8}UI/layouts/ERR/AdminValue.layout" { Name "objectGuardHQMode" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 380 OffsetTop 380 SizeX 130 OffsetRight -158 SizeY 38 OffsetBottom -418 } components { SCR_EditBoxComponent "{547290FFBD5B33E9}" { m_sLabel "" } } }
|
|
||||||
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "Apply_objectGuardHQMode" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 170 OffsetLeft 170 PositionY 380 OffsetTop 380 SizeX 110 OffsetRight -280 SizeY 38 OffsetBottom -418 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Apply" } } }
|
|
||||||
TextWidgetClass { Name "Saved_objectGuardHQMode" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 421 OffsetTop 421 SizeX 936 OffsetRight -964 SizeY 22 OffsetBottom -443 } Color 0.9 0.92 0.94 1 Text "Waiting for saved value" "Font Size" 16 "Min Font Size" 16 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
TextWidgetClass { Name "Saved_objectGuardHQMode" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 421 OffsetTop 421 SizeX 936 OffsetRight -964 SizeY 22 OffsetBottom -443 } Color 0.9 0.92 0.94 1 Text "Waiting for saved value" "Font Size" 16 "Min Font Size" 16 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
TextWidgetClass { Name "Help_objectGuardHQMode" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 445 OffsetTop 445 SizeX 936 OffsetRight -964 SizeY 26 OffsetBottom -471 } Color 0.9 0.92 0.94 1 Text "0 = hold positions; 1 = half hold and half respond; 2 = all can respond." "Font Size" 14 "Min Font Size" 14 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
TextWidgetClass { Name "Help_objectGuardHQMode" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 445 OffsetTop 445 SizeX 936 OffsetRight -964 SizeY 26 OffsetBottom -471 } Color 0.9 0.92 0.94 1 Text "Choose which guards can leave their posts to respond to HQ." "Font Size" 14 "Min Font Size" 14 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "GuardHQ_0" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 292 OffsetLeft 292 PositionY 380 OffsetTop 380 SizeX 210 OffsetRight -502 SizeY 38 OffsetBottom -418 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Hold positions" } } }
|
|
||||||
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "GuardHQ_1" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 514 OffsetLeft 514 PositionY 380 OffsetTop 380 SizeX 210 OffsetRight -724 SizeY 38 OffsetBottom -418 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Half to HQ" } } }
|
|
||||||
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "GuardHQ_2" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 736 OffsetLeft 736 PositionY 380 OffsetTop 380 SizeX 210 OffsetRight -946 SizeY 38 OffsetBottom -418 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "All to HQ" } } }
|
|
||||||
} }
|
} }
|
||||||
FrameWidgetClass { "Is Visible" 0 Name "SitesPage" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 220 OffsetLeft 220 PositionY 160 OffsetTop 160 SizeX 1000 OffsetRight -1220 SizeY 486 OffsetBottom -646 } {
|
FrameWidgetClass { "Is Visible" 0 Name "SitesPage" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 220 OffsetLeft 220 PositionY 160 OffsetTop 160 SizeX 1000 OffsetRight -1220 SizeY 532 OffsetBottom -692 } {
|
||||||
TextWidgetClass { Name "SitesHeading" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 6 OffsetTop 6 SizeX 710 OffsetRight -738 SizeY 32 OffsetBottom -38 } Color 0.9 0.92 0.94 1 Text "Camps and hospitals - next round" "Font Size" 22 "Min Font Size" 22 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
TextWidgetClass { Name "SitesHeading" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 6 OffsetTop 6 SizeX 710 OffsetRight -738 SizeY 32 OffsetBottom -38 } Color 0.9 0.92 0.94 1 Text "Camps and hospitals - next round" "Font Size" 22 "Min Font Size" 22 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
|
||||||
TextWidgetClass { Name "SitesHelp" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 50 OffsetTop 50 SizeX 936 OffsetRight -964 SizeY 40 OffsetBottom -90 } Color 0.9 0.92 0.94 1 Text "More places to discover, raid and find supplies. Unsafe locations can be skipped." "Font Size" 18 "Min Font Size" 18 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
TextWidgetClass { Name "SitesHelp" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 50 OffsetTop 50 SizeX 936 OffsetRight -964 SizeY 40 OffsetBottom -90 } Color 0.9 0.92 0.94 1 Text "More places to discover, raid and find supplies. Unsafe locations can be skipped." "Font Size" 18 "Min Font Size" 18 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
@@ -1027,7 +1132,47 @@ TextWidgetClass { Name "Saved_fieldHospitalCount" Slot FrameWidgetSlot { Anchor
|
|||||||
TextWidgetClass { Name "Help_fieldHospitalCount" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 508 OffsetLeft 508 PositionY 226 OffsetTop 226 SizeX 456 OffsetRight -964 SizeY 54 OffsetBottom -280 } Color 0.9 0.92 0.94 1 Text "Medical supplies and guards. The radio-clue site is retained." "Font Size" 16 "Min Font Size" 16 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
TextWidgetClass { Name "Help_fieldHospitalCount" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 508 OffsetLeft 508 PositionY 226 OffsetTop 226 SizeX 456 OffsetRight -964 SizeY 54 OffsetBottom -280 } Color 0.9 0.92 0.94 1 Text "Medical supplies and guards. The radio-clue site is retained." "Font Size" 16 "Min Font Size" 16 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
TextWidgetClass { Name "SitesObjectives" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 318 OffsetTop 318 SizeX 936 OffsetRight -964 SizeY 90 OffsetBottom -408 } Color 0.9 0.92 0.94 1 Text "The prison, radio, extraction, supply cache and four mortar sites are unchanged. Defaults request 26 sites. More sites share the existing field guard budget. Discover them through intel or exploration." "Font Size" 18 "Min Font Size" 18 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
TextWidgetClass { Name "SitesObjectives" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 318 OffsetTop 318 SizeX 936 OffsetRight -964 SizeY 90 OffsetBottom -408 } Color 0.9 0.92 0.94 1 Text "The prison, radio, extraction, supply cache and four mortar sites are unchanged. Defaults request 26 sites. More sites share the existing field guard budget. Discover them through intel or exploration." "Font Size" 18 "Min Font Size" 18 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
} }
|
} }
|
||||||
FrameWidgetClass { "Is Visible" 0 Name "PlanePage" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 220 OffsetLeft 220 PositionY 160 OffsetTop 160 SizeX 1000 OffsetRight -1220 SizeY 486 OffsetBottom -646 } {
|
FrameWidgetClass { "Is Visible" 0 Name "CrashSitesPage" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 220 OffsetLeft 220 PositionY 160 OffsetTop 160 SizeX 1000 OffsetRight -1220 SizeY 532 OffsetBottom -692 } {
|
||||||
|
TextWidgetClass { Name "CrashHeading" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 4 OffsetTop 4 SizeX 936 OffsetRight -964 SizeY 30 OffsetBottom -34 } Color 0.9 0.92 0.94 1 Text "Crash sites - next round" "Font Size" 22 "Min Font Size" 22 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
TextWidgetClass { Name "CrashHelp" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 40 OffsetTop 40 SizeX 936 OffsetRight -964 SizeY 40 OffsetBottom -80 } Color 0.9 0.92 0.94 1 Text "Default loot is random for each crate: poor pistol supplies, a useful rifle, or a rich haul of scoped guns and launchers. No refills." "Font Size" 17 "Min Font Size" 17 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
TextWidgetClass { Name "Label_crashLootMode" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 82 OffsetTop 82 SizeX 456 OffsetRight -484 SizeY 26 OffsetBottom -108 } Color 0.9 0.92 0.94 1 Text "Crash-site loot" "Font Size" 17 "Min Font Size" 17 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "CrashLootRandom" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 508 OffsetLeft 508 PositionY 82 OffsetTop 82 SizeX 218 OffsetRight -726 SizeY 32 OffsetBottom -114 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Random" } } }
|
||||||
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "CrashLootCustom" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 746 OffsetLeft 746 PositionY 82 OffsetTop 82 SizeX 218 OffsetRight -964 SizeY 32 OffsetBottom -114 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Custom" } } }
|
||||||
|
TextWidgetClass { Name "Saved_crashLootMode" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 508 OffsetLeft 508 PositionY 118 OffsetTop 118 SizeX 456 OffsetRight -964 SizeY 20 OffsetBottom -138 } Color 0.9 0.92 0.94 1 Text "Waiting for server" "Font Size" 14 "Min Font Size" 14 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
TextWidgetClass { Name "Help_crashLootMode" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 112 OffsetTop 112 SizeX 456 OffsetRight -484 SizeY 40 OffsetBottom -152 } Color 0.9 0.92 0.94 1 Text "Random rolls each crate independently. Custom uses the four loot controls below." "Font Size" 14 "Min Font Size" 14 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
TextWidgetClass { Name "Label_crashSiteCount" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 160 OffsetTop 160 SizeX 456 OffsetRight -484 SizeY 22 OffsetBottom -182 } Color 0.9 0.92 0.94 1 Text "Crash sites (0-12)" "Font Size" 17 "Min Font Size" 17 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
ButtonWidgetClass : "{DA43C77A0D1E59F8}UI/layouts/ERR/AdminValue.layout" { Name "crashSiteCount" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 184 OffsetTop 184 SizeX 180 OffsetRight -208 SizeY 32 OffsetBottom -216 } components { SCR_EditBoxComponent "{547290FFBD5B33E9}" { m_sLabel "" } } }
|
||||||
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "Apply_crashSiteCount" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 220 OffsetLeft 220 PositionY 184 OffsetTop 184 SizeX 100 OffsetRight -320 SizeY 32 OffsetBottom -216 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Apply" } } }
|
||||||
|
TextWidgetClass { Name "Saved_crashSiteCount" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 220 OffsetTop 220 SizeX 456 OffsetRight -484 SizeY 20 OffsetBottom -240 } Color 0.9 0.92 0.94 1 Text "Waiting for server" "Font Size" 14 "Min Font Size" 14 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
TextWidgetClass { Name "Help_crashSiteCount" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 242 OffsetTop 242 SizeX 456 OffsetRight -484 SizeY 26 OffsetBottom -268 } Color 0.9 0.92 0.94 1 Text "0 disables sites. Finite wrecks and loot; no refills." "Font Size" 14 "Min Font Size" 14 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
TextWidgetClass { Name "Label_crashHelicopterChancePercent" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 508 OffsetLeft 508 PositionY 160 OffsetTop 160 SizeX 456 OffsetRight -964 SizeY 22 OffsetBottom -182 } Color 0.9 0.92 0.94 1 Text "Helicopter chance (%)" "Font Size" 17 "Min Font Size" 17 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
ButtonWidgetClass : "{DA43C77A0D1E59F8}UI/layouts/ERR/AdminValue.layout" { Name "crashHelicopterChancePercent" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 508 OffsetLeft 508 PositionY 184 OffsetTop 184 SizeX 180 OffsetRight -688 SizeY 32 OffsetBottom -216 } components { SCR_EditBoxComponent "{547290FFBD5B33E9}" { m_sLabel "" } } }
|
||||||
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "Apply_crashHelicopterChancePercent" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 700 OffsetLeft 700 PositionY 184 OffsetTop 184 SizeX 100 OffsetRight -800 SizeY 32 OffsetBottom -216 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Apply" } } }
|
||||||
|
TextWidgetClass { Name "Saved_crashHelicopterChancePercent" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 508 OffsetLeft 508 PositionY 220 OffsetTop 220 SizeX 456 OffsetRight -964 SizeY 20 OffsetBottom -240 } Color 0.9 0.92 0.94 1 Text "Waiting for server" "Font Size" 14 "Min Font Size" 14 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
TextWidgetClass { Name "Help_crashHelicopterChancePercent" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 508 OffsetLeft 508 PositionY 242 OffsetTop 242 SizeX 456 OffsetRight -964 SizeY 26 OffsetBottom -268 } Color 0.9 0.92 0.94 1 Text "0 = armored wrecks; 100 = helicopters." "Font Size" 14 "Min Font Size" 14 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
TextWidgetClass { Name "Label_crashRifleCount" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 270 OffsetTop 270 SizeX 456 OffsetRight -484 SizeY 22 OffsetBottom -292 } Color 0.9 0.92 0.94 1 Text "Custom: scoped rifles per crate (1-6)" "Font Size" 17 "Min Font Size" 17 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
ButtonWidgetClass : "{DA43C77A0D1E59F8}UI/layouts/ERR/AdminValue.layout" { Name "crashRifleCount" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 294 OffsetTop 294 SizeX 180 OffsetRight -208 SizeY 32 OffsetBottom -326 } components { SCR_EditBoxComponent "{547290FFBD5B33E9}" { m_sLabel "" } } }
|
||||||
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "Apply_crashRifleCount" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 220 OffsetLeft 220 PositionY 294 OffsetTop 294 SizeX 100 OffsetRight -320 SizeY 32 OffsetBottom -326 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Apply" } } }
|
||||||
|
TextWidgetClass { Name "Saved_crashRifleCount" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 330 OffsetTop 330 SizeX 456 OffsetRight -484 SizeY 20 OffsetBottom -350 } Color 0.9 0.92 0.94 1 Text "Waiting for server" "Font Size" 14 "Min Font Size" 14 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
TextWidgetClass { Name "Help_crashRifleCount" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 352 OffsetTop 352 SizeX 456 OffsetRight -484 SizeY 26 OffsetBottom -378 } Color 0.9 0.92 0.94 1 Text "Random M21 / SVD rifles with fitted scopes." "Font Size" 14 "Min Font Size" 14 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
TextWidgetClass { Name "Label_crashMagazineCount" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 508 OffsetLeft 508 PositionY 270 OffsetTop 270 SizeX 456 OffsetRight -964 SizeY 22 OffsetBottom -292 } Color 0.9 0.92 0.94 1 Text "Custom: magazines per rifle (1-8)" "Font Size" 17 "Min Font Size" 17 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
ButtonWidgetClass : "{DA43C77A0D1E59F8}UI/layouts/ERR/AdminValue.layout" { Name "crashMagazineCount" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 508 OffsetLeft 508 PositionY 294 OffsetTop 294 SizeX 180 OffsetRight -688 SizeY 32 OffsetBottom -326 } components { SCR_EditBoxComponent "{547290FFBD5B33E9}" { m_sLabel "" } } }
|
||||||
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "Apply_crashMagazineCount" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 700 OffsetLeft 700 PositionY 294 OffsetTop 294 SizeX 100 OffsetRight -800 SizeY 32 OffsetBottom -326 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Apply" } } }
|
||||||
|
TextWidgetClass { Name "Saved_crashMagazineCount" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 508 OffsetLeft 508 PositionY 330 OffsetTop 330 SizeX 456 OffsetRight -964 SizeY 20 OffsetBottom -350 } Color 0.9 0.92 0.94 1 Text "Waiting for server" "Font Size" 14 "Min Font Size" 14 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
TextWidgetClass { Name "Help_crashMagazineCount" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 508 OffsetLeft 508 PositionY 352 OffsetTop 352 SizeX 456 OffsetRight -964 SizeY 26 OffsetBottom -378 } Color 0.9 0.92 0.94 1 Text "Matching ammunition for each selected rifle." "Font Size" 14 "Min Font Size" 14 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
TextWidgetClass { Name "Label_crashAntiAirChancePercent" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 380 OffsetTop 380 SizeX 456 OffsetRight -484 SizeY 22 OffsetBottom -402 } Color 0.9 0.92 0.94 1 Text "Custom: anti-air launcher chance (%)" "Font Size" 17 "Min Font Size" 17 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
ButtonWidgetClass : "{DA43C77A0D1E59F8}UI/layouts/ERR/AdminValue.layout" { Name "crashAntiAirChancePercent" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 404 OffsetTop 404 SizeX 180 OffsetRight -208 SizeY 32 OffsetBottom -436 } components { SCR_EditBoxComponent "{547290FFBD5B33E9}" { m_sLabel "" } } }
|
||||||
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "Apply_crashAntiAirChancePercent" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 220 OffsetLeft 220 PositionY 404 OffsetTop 404 SizeX 100 OffsetRight -320 SizeY 32 OffsetBottom -436 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Apply" } } }
|
||||||
|
TextWidgetClass { Name "Saved_crashAntiAirChancePercent" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 440 OffsetTop 440 SizeX 456 OffsetRight -484 SizeY 20 OffsetBottom -460 } Color 0.9 0.92 0.94 1 Text "Waiting for server" "Font Size" 14 "Min Font Size" 14 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
TextWidgetClass { Name "Help_crashAntiAirChancePercent" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 462 OffsetTop 462 SizeX 456 OffsetRight -484 SizeY 26 OffsetBottom -488 } Color 0.9 0.92 0.94 1 Text "One loaded Stinger, if installed and enabled." "Font Size" 14 "Min Font Size" 14 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
TextWidgetClass { Name "Label_crashRocketChancePercent" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 508 OffsetLeft 508 PositionY 380 OffsetTop 380 SizeX 456 OffsetRight -964 SizeY 22 OffsetBottom -402 } Color 0.9 0.92 0.94 1 Text "Custom: rPG launcher chance (%)" "Font Size" 17 "Min Font Size" 17 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
ButtonWidgetClass : "{DA43C77A0D1E59F8}UI/layouts/ERR/AdminValue.layout" { Name "crashRocketChancePercent" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 508 OffsetLeft 508 PositionY 404 OffsetTop 404 SizeX 180 OffsetRight -688 SizeY 32 OffsetBottom -436 } components { SCR_EditBoxComponent "{547290FFBD5B33E9}" { m_sLabel "" } } }
|
||||||
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "Apply_crashRocketChancePercent" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 700 OffsetLeft 700 PositionY 404 OffsetTop 404 SizeX 100 OffsetRight -800 SizeY 32 OffsetBottom -436 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Apply" } } }
|
||||||
|
TextWidgetClass { Name "Saved_crashRocketChancePercent" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 508 OffsetLeft 508 PositionY 440 OffsetTop 440 SizeX 456 OffsetRight -964 SizeY 20 OffsetBottom -460 } Color 0.9 0.92 0.94 1 Text "Waiting for server" "Font Size" 14 "Min Font Size" 14 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
TextWidgetClass { Name "Help_crashRocketChancePercent" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 508 OffsetLeft 508 PositionY 462 OffsetTop 462 SizeX 456 OffsetRight -964 SizeY 26 OffsetBottom -488 } Color 0.9 0.92 0.94 1 Text "One scoped RPG-7 with two matching rockets." "Font Size" 14 "Min Font Size" 14 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
TextWidgetClass { Name "CrashFooter" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 498 OffsetTop 498 SizeX 936 OffsetRight -964 SizeY 28 OffsetBottom -526 } Color 0.9 0.92 0.94 1 Text "Random ignores custom loot amounts and chances. Content blocks still apply. Default: 3 sites, 50% helicopters." "Font Size" 14 "Min Font Size" 14 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
} }
|
||||||
|
FrameWidgetClass { "Is Visible" 0 Name "PlanePage" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 220 OffsetLeft 220 PositionY 160 OffsetTop 160 SizeX 1000 OffsetRight -1220 SizeY 532 OffsetBottom -692 } {
|
||||||
TextWidgetClass { Name "PlaneHeading" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 6 OffsetTop 6 SizeX 710 OffsetRight -738 SizeY 32 OffsetBottom -38 } Color 0.9 0.92 0.94 1 Text "Cessna patrol - changes apply next round" "Font Size" 22 "Min Font Size" 22 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
TextWidgetClass { Name "PlaneHeading" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 6 OffsetTop 6 SizeX 710 OffsetRight -738 SizeY 32 OffsetBottom -38 } Color 0.9 0.92 0.94 1 Text "Cessna patrol - changes apply next round" "Font Size" 22 "Min Font Size" 22 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
|
||||||
TextWidgetClass { Name "PlaneHelp" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 50 OffsetTop 50 SizeX 936 OffsetRight -964 SizeY 48 OffsetBottom -98 } Color 0.9 0.92 0.94 1 Text "Requires Cessna 172, Propeller Flight Core and a usable airport. Without the addons, no plane spawns." "Font Size" 18 "Min Font Size" 18 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
TextWidgetClass { Name "PlaneHelp" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 50 OffsetTop 50 SizeX 936 OffsetRight -964 SizeY 48 OffsetBottom -98 } Color 0.9 0.92 0.94 1 Text "Requires Cessna 172, Propeller Flight Core and a usable airport. Without the addons, no plane spawns." "Font Size" 18 "Min Font Size" 18 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
@@ -1050,7 +1195,7 @@ ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_But
|
|||||||
TextWidgetClass { Name "Saved_planeSectorSeconds" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 480 OffsetLeft 480 PositionY 410 OffsetTop 410 SizeX 484 OffsetRight -964 SizeY 22 OffsetBottom -432 } Color 0.9 0.92 0.94 1 Text "Waiting for server" "Font Size" 16 "Min Font Size" 16 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
TextWidgetClass { Name "Saved_planeSectorSeconds" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 480 OffsetLeft 480 PositionY 410 OffsetTop 410 SizeX 484 OffsetRight -964 SizeY 22 OffsetBottom -432 } Color 0.9 0.92 0.94 1 Text "Waiting for server" "Font Size" 16 "Min Font Size" 16 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
TextWidgetClass { Name "Help_planeSectorSeconds" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 436 OffsetTop 436 SizeX 936 OffsetRight -964 SizeY 28 OffsetBottom -464 } Color 0.9 0.92 0.94 1 Text "Chooses another area at this interval, or sooner when it reaches its destination." "Font Size" 16 "Min Font Size" 16 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
TextWidgetClass { Name "Help_planeSectorSeconds" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 436 OffsetTop 436 SizeX 936 OffsetRight -964 SizeY 28 OffsetBottom -464 } Color 0.9 0.92 0.94 1 Text "Chooses another area at this interval, or sooner when it reaches its destination." "Font Size" 16 "Min Font Size" 16 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
} }
|
} }
|
||||||
FrameWidgetClass { "Is Visible" 0 Name "DronesPage" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 220 OffsetLeft 220 PositionY 160 OffsetTop 160 SizeX 1000 OffsetRight -1220 SizeY 486 OffsetBottom -646 } {
|
FrameWidgetClass { "Is Visible" 0 Name "DronesPage" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 220 OffsetLeft 220 PositionY 160 OffsetTop 160 SizeX 1000 OffsetRight -1220 SizeY 532 OffsetBottom -692 } {
|
||||||
TextWidgetClass { Name "DronesHeading" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 6 OffsetTop 6 SizeX 710 OffsetRight -738 SizeY 32 OffsetBottom -38 } Color 0.9 0.92 0.94 1 Text "Enemy recon drones - changes apply next round" "Font Size" 22 "Min Font Size" 22 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
TextWidgetClass { Name "DronesHeading" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 6 OffsetTop 6 SizeX 710 OffsetRight -738 SizeY 32 OffsetBottom -38 } Color 0.9 0.92 0.94 1 Text "Enemy recon drones - changes apply next round" "Font Size" 22 "Min Font Size" 22 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
|
||||||
TextWidgetClass { Name "DronesHelp" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 50 OffsetTop 50 SizeX 936 OffsetRight -964 SizeY 48 OffsetBottom -98 } Color 0.9 0.92 0.94 1 Text "Realistic Combat Drones | Workshop: 65AD60E204191D37. Optional; install separately to use reconnaissance drones." "Font Size" 18 "Min Font Size" 18 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
TextWidgetClass { Name "DronesHelp" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 50 OffsetTop 50 SizeX 936 OffsetRight -964 SizeY 48 OffsetBottom -98 } Color 0.9 0.92 0.94 1 Text "Realistic Combat Drones | Workshop: 65AD60E204191D37. Optional; install separately to use reconnaissance drones." "Font Size" 18 "Min Font Size" 18 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
@@ -1067,7 +1212,7 @@ TextWidgetClass { Name "Help_droneCooldownSeconds" Slot FrameWidgetSlot { Anchor
|
|||||||
TextWidgetClass { Name "recommended_drones" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 112 OffsetTop 112 SizeX 936 OffsetRight -964 SizeY 44 OffsetBottom -156 } Color 0.9 0.92 0.94 1 Text "Waiting for server addon list" "Font Size" 18 "Min Font Size" 18 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
TextWidgetClass { Name "recommended_drones" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 112 OffsetTop 112 SizeX 936 OffsetRight -964 SizeY 44 OffsetBottom -156 } Color 0.9 0.92 0.94 1 Text "Waiting for server addon list" "Font Size" 18 "Min Font Size" 18 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
TextWidgetClass { Name "DroneRuntime" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 385 OffsetTop 385 SizeX 936 OffsetRight -964 SizeY 72 OffsetBottom -457 } Color 0.9 0.92 0.94 1 Text "Current round: waiting for server" "Font Size" 18 "Min Font Size" 18 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
TextWidgetClass { Name "DroneRuntime" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 385 OffsetTop 385 SizeX 936 OffsetRight -964 SizeY 72 OffsetBottom -457 } Color 0.9 0.92 0.94 1 Text "Current round: waiting for server" "Font Size" 18 "Min Font Size" 18 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
} }
|
} }
|
||||||
FrameWidgetClass { "Is Visible" 0 Name "RecommendationsPage" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 220 OffsetLeft 220 PositionY 160 OffsetTop 160 SizeX 1000 OffsetRight -1220 SizeY 486 OffsetBottom -646 } {
|
FrameWidgetClass { "Is Visible" 0 Name "RecommendationsPage" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 220 OffsetLeft 220 PositionY 160 OffsetTop 160 SizeX 1000 OffsetRight -1220 SizeY 532 OffsetBottom -692 } {
|
||||||
TextWidgetClass { Name "RecommendedHeading" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 6 OffsetTop 6 SizeX 710 OffsetRight -738 SizeY 32 OffsetBottom -38 } Color 0.9 0.92 0.94 1 Text "Recommended optional addons" "Font Size" 22 "Min Font Size" 22 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
TextWidgetClass { Name "RecommendedHeading" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 6 OffsetTop 6 SizeX 710 OffsetRight -738 SizeY 32 OffsetBottom -38 } Color 0.9 0.92 0.94 1 Text "Recommended optional addons" "Font Size" 22 "Min Font Size" 22 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "RecommendedDrones" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 750 OffsetLeft 750 PositionY 4 OffsetTop 4 SizeX 214 OffsetRight -964 SizeY 38 OffsetBottom -42 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Recon drones" } } }
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "RecommendedDrones" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 750 OffsetLeft 750 PositionY 4 OffsetTop 4 SizeX 214 OffsetRight -964 SizeY 38 OffsetBottom -42 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Recon drones" } } }
|
||||||
|
|
||||||
@@ -1077,20 +1222,23 @@ ButtonWidgetClass : "{DA43C77A0D1E59F8}UI/layouts/ERR/AdminValue.layout" { Name
|
|||||||
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "Apply_motorbikeCount" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 764 OffsetLeft 764 PositionY 80 OffsetTop 80 SizeX 200 OffsetRight -964 SizeY 38 OffsetBottom -118 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Apply" } } }
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "Apply_motorbikeCount" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 764 OffsetLeft 764 PositionY 80 OffsetTop 80 SizeX 200 OffsetRight -964 SizeY 38 OffsetBottom -118 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Apply" } } }
|
||||||
TextWidgetClass { Name "Saved_motorbikeCount" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 640 OffsetLeft 640 PositionY 120 OffsetTop 120 SizeX 324 OffsetRight -964 SizeY 24 OffsetBottom -144 } Color 0.9 0.92 0.94 1 Text "Waiting for server" "Font Size" 16 "Min Font Size" 16 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
TextWidgetClass { Name "Saved_motorbikeCount" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 640 OffsetLeft 640 PositionY 120 OffsetTop 120 SizeX 324 OffsetRight -964 SizeY 24 OffsetBottom -144 } Color 0.9 0.92 0.94 1 Text "Waiting for server" "Font Size" 16 "Min Font Size" 16 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
TextWidgetClass { Name "RecommendedBikeID" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 116 OffsetTop 116 SizeX 580 OffsetRight -608 SizeY 24 OffsetBottom -140 } Color 0.9 0.92 0.94 1 Text "Workshop: 6503091A7B264FC3" "Font Size" 15 "Min Font Size" 15 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
TextWidgetClass { Name "RecommendedBikeID" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 116 OffsetTop 116 SizeX 580 OffsetRight -608 SizeY 24 OffsetBottom -140 } Color 0.9 0.92 0.94 1 Text "Workshop: 6503091A7B264FC3" "Font Size" 15 "Min Font Size" 15 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
TextWidgetClass { Name "recommended_bikes" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 145 OffsetTop 145 SizeX 936 OffsetRight -964 SizeY 25 OffsetBottom -170 } Color 0.9 0.92 0.94 1 Text "Waiting for server addon list" "Font Size" 17 "Min Font Size" 17 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
TextWidgetClass { Name "recommended_bikes" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 137 OffsetTop 137 SizeX 936 OffsetRight -964 SizeY 25 OffsetBottom -162 } Color 0.9 0.92 0.94 1 Text "Waiting for server addon list" "Font Size" 17 "Min Font Size" 17 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
TextWidgetClass { Name "Help_motorbikeCount" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 175 OffsetTop 175 SizeX 936 OffsetRight -964 SizeY 27 OffsetBottom -202 } Color 0.9 0.92 0.94 1 Text "Two special-forces crew per bike, alongside UAZ hunters. Count applies next round; losses stay lost." "Font Size" 17 "Min Font Size" 17 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
TextWidgetClass { Name "Help_motorbikeCount" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 165 OffsetTop 165 SizeX 936 OffsetRight -964 SizeY 22 OffsetBottom -187 } Color 0.9 0.92 0.94 1 Text "Two special-forces crew per bike, alongside UAZ hunters. Count applies next round; losses stay lost." "Font Size" 17 "Min Font Size" 17 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
TextWidgetClass { Name "RecommendedBikeRuntime" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 207 OffsetTop 207 SizeX 936 OffsetRight -964 SizeY 36 OffsetBottom -243 } Color 0.9 0.92 0.94 1 Text "Current round: waiting for server" "Font Size" 16 "Min Font Size" 16 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
TextWidgetClass { Name "RecommendedBikeRuntime" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 188 OffsetTop 188 SizeX 936 OffsetRight -964 SizeY 24 OffsetBottom -212 } Color 0.9 0.92 0.94 1 Text "Current round: waiting for server" "Font Size" 16 "Min Font Size" 16 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
TextWidgetClass { Name "RecommendedPlaneLabel" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 256 OffsetTop 256 SizeX 740 OffsetRight -768 SizeY 28 OffsetBottom -284 } Color 0.9 0.92 0.94 1 Text "Cessna 172 - reconnaissance patrol" "Font Size" 21 "Min Font Size" 21 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
TextWidgetClass { Name "RecommendedPlaneLabel" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 220 OffsetTop 220 SizeX 740 OffsetRight -768 SizeY 28 OffsetBottom -248 } Color 0.9 0.92 0.94 1 Text "Cessna 172 - reconnaissance (requires an airport)" "Font Size" 21 "Min Font Size" 21 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "RecommendedPlane" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 788 OffsetLeft 788 PositionY 252 OffsetTop 252 SizeX 176 OffsetRight -964 SizeY 38 OffsetBottom -290 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Configure Cessna" } } }
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "RecommendedPlane" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 788 OffsetLeft 788 PositionY 216 OffsetTop 216 SizeX 176 OffsetRight -964 SizeY 38 OffsetBottom -254 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Configure Cessna" } } }
|
||||||
TextWidgetClass { Name "RecommendedPlaneID" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 289 OffsetTop 289 SizeX 936 OffsetRight -964 SizeY 24 OffsetBottom -313 } Color 0.9 0.92 0.94 1 Text "Workshop: 69BAEE0D439966FE | Propeller Flight Core: 69A8A34027DA65C5" "Font Size" 15 "Min Font Size" 15 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
TextWidgetClass { Name "RecommendedPlaneID" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 250 OffsetTop 250 SizeX 936 OffsetRight -964 SizeY 24 OffsetBottom -274 } Color 0.9 0.92 0.94 1 Text "Workshop: 69BAEE0D439966FE | Propeller Flight Core: 69A8A34027DA65C5" "Font Size" 15 "Min Font Size" 15 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
TextWidgetClass { Name "recommended_plane" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 316 OffsetTop 316 SizeX 936 OffsetRight -964 SizeY 25 OffsetBottom -341 } Color 0.9 0.92 0.94 1 Text "Waiting for server addon list" "Font Size" 17 "Min Font Size" 17 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
TextWidgetClass { Name "recommended_plane" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 276 OffsetTop 276 SizeX 936 OffsetRight -964 SizeY 25 OffsetBottom -301 } Color 0.9 0.92 0.94 1 Text "Waiting for server addon list" "Font Size" 17 "Min Font Size" 17 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
TextWidgetClass { Name "RecommendedPlaneHelp" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 345 OffsetTop 345 SizeX 936 OffsetRight -964 SizeY 26 OffsetBottom -371 } Color 0.9 0.92 0.94 1 Text "Patrols and reports sightings. Requires a usable airport on the selected terrain." "Font Size" 17 "Min Font Size" 17 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
TextWidgetClass { Name "RecommendedWeaponsLabel" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 320 OffsetTop 320 SizeX 740 OffsetRight -768 SizeY 28 OffsetBottom -348 } Color 0.9 0.92 0.94 1 Text "Phaseline Marksman Rifles - extra weapons" "Font Size" 21 "Min Font Size" 21 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
TextWidgetClass { Name "RecommendedWeaponsLabel" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 385 OffsetTop 385 SizeX 740 OffsetRight -768 SizeY 28 OffsetBottom -413 } Color 0.9 0.92 0.94 1 Text "Phaseline Marksman Rifles - extra weapons" "Font Size" 21 "Min Font Size" 21 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "RecommendedWeapons" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 788 OffsetLeft 788 PositionY 316 OffsetTop 316 SizeX 176 OffsetRight -964 SizeY 38 OffsetBottom -354 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Manage items" } } }
|
||||||
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "RecommendedWeapons" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 788 OffsetLeft 788 PositionY 381 OffsetTop 381 SizeX 176 OffsetRight -964 SizeY 38 OffsetBottom -419 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Manage items" } } }
|
TextWidgetClass { Name "RecommendedWeaponsID" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 351 OffsetTop 351 SizeX 936 OffsetRight -964 SizeY 22 OffsetBottom -373 } Color 0.9 0.92 0.94 1 Text "Workshop: BFC890A6F1603EC0" "Font Size" 15 "Min Font Size" 15 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
TextWidgetClass { Name "RecommendedWeaponsID" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 416 OffsetTop 416 SizeX 936 OffsetRight -964 SizeY 22 OffsetBottom -438 } Color 0.9 0.92 0.94 1 Text "Workshop: BFC890A6F1603EC0" "Font Size" 15 "Min Font Size" 15 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
TextWidgetClass { Name "recommended_weapons" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 376 OffsetTop 376 SizeX 936 OffsetRight -964 SizeY 24 OffsetBottom -400 } Color 0.9 0.92 0.94 1 Text "Waiting for server addon list" "Font Size" 17 "Min Font Size" 17 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
TextWidgetClass { Name "recommended_weapons" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 441 OffsetTop 441 SizeX 936 OffsetRight -964 SizeY 24 OffsetBottom -465 } Color 0.9 0.92 0.94 1 Text "Waiting for server addon list" "Font Size" 17 "Min Font Size" 17 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
TextWidgetClass { Name "RecommendedBoatsLabel" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 412 OffsetTop 412 SizeX 740 OffsetRight -768 SizeY 28 OffsetBottom -440 } Color 0.9 0.92 0.94 1 Text "Motorboat - shoreline transport" "Font Size" 21 "Min Font Size" 21 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "RecommendedBoats" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 788 OffsetLeft 788 PositionY 408 OffsetTop 408 SizeX 176 OffsetRight -964 SizeY 38 OffsetBottom -446 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Shore boats" } } }
|
||||||
|
TextWidgetClass { Name "RecommendedBoatsID" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 445 OffsetTop 445 SizeX 936 OffsetRight -964 SizeY 22 OffsetBottom -467 } Color 0.9 0.92 0.94 1 Text "Workshop: 610C8532D3756F2D | Requires Boat Core, AI Radio and VehicleCore" "Font Size" 15 "Min Font Size" 15 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
TextWidgetClass { Name "recommended_boats" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 470 OffsetTop 470 SizeX 936 OffsetRight -964 SizeY 52 OffsetBottom -522 } Color 0.9 0.92 0.94 1 Text "Waiting for server addon list" "Font Size" 17 "Min Font Size" 17 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
} }
|
} }
|
||||||
FrameWidgetClass { "Is Visible" 0 Name "WeightsPage" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 220 OffsetLeft 220 PositionY 160 OffsetTop 160 SizeX 1000 OffsetRight -1220 SizeY 486 OffsetBottom -646 } {
|
FrameWidgetClass { "Is Visible" 0 Name "WeightsPage" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 220 OffsetLeft 220 PositionY 160 OffsetTop 160 SizeX 1000 OffsetRight -1220 SizeY 532 OffsetBottom -692 } {
|
||||||
TextWidgetClass { Name "WeightsHelp" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 4 OffsetTop 4 SizeX 936 OffsetRight -964 SizeY 48 OffsetBottom -52 } Color 0.9 0.92 0.94 1 Text "Make selected mods or items more common. 1 = normal, 100 = very high, 0 = never. These are relative weights, not percentages." "Font Size" 17 "Min Font Size" 17 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
TextWidgetClass { Name "WeightsHelp" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 4 OffsetTop 4 SizeX 936 OffsetRight -964 SizeY 48 OffsetBottom -52 } Color 0.9 0.92 0.94 1 Text "Make selected mods or items more common. 1 = normal, 100 = very high, 0 = never. These are relative weights, not percentages." "Font Size" 17 "Min Font Size" 17 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
|
||||||
ButtonWidgetClass : "{4B5AE6E64037FFB4}UI/layouts/WidgetLibrary/ComboBox/WLib_ComboBox.layout" { Name "WeightsMod" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 58 OffsetTop 58 SizeX 560 OffsetRight -588 SizeY 42 OffsetBottom -100 } components { SCR_ComboBoxComponent "{546B1D464BC32853}" { m_bUseLabel 0 m_sLabel "" m_fSizeWithoutLabel 42 } } }
|
ButtonWidgetClass : "{4B5AE6E64037FFB4}UI/layouts/WidgetLibrary/ComboBox/WLib_ComboBox.layout" { Name "WeightsMod" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 58 OffsetTop 58 SizeX 560 OffsetRight -588 SizeY 42 OffsetBottom -100 } components { SCR_ComboBoxComponent "{546B1D464BC32853}" { m_bUseLabel 0 m_sLabel "" m_fSizeWithoutLabel 42 } } }
|
||||||
@@ -1105,36 +1253,31 @@ TextWidgetClass { Name "ModWeightSaved" Slot FrameWidgetSlot { Anchor 0 0 0 0 Po
|
|||||||
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "WeightsPrevious" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 194 OffsetTop 194 SizeX 130 OffsetRight -158 SizeY 38 OffsetBottom -232 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Previous" } } }
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "WeightsPrevious" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 194 OffsetTop 194 SizeX 130 OffsetRight -158 SizeY 38 OffsetBottom -232 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Previous" } } }
|
||||||
TextWidgetClass { Name "WeightsPageState" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 174 OffsetLeft 174 PositionY 194 OffsetTop 194 SizeX 642 OffsetRight -816 SizeY 38 OffsetBottom -232 } Color 0.9 0.92 0.94 1 Text "Choose a mod to adjust item probabilities." "Font Size" 17 "Min Font Size" 17 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
TextWidgetClass { Name "WeightsPageState" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 174 OffsetLeft 174 PositionY 194 OffsetTop 194 SizeX 642 OffsetRight -816 SizeY 38 OffsetBottom -232 } Color 0.9 0.92 0.94 1 Text "Choose a mod to adjust item probabilities." "Font Size" 17 "Min Font Size" 17 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "WeightsNext" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 834 OffsetLeft 834 PositionY 194 OffsetTop 194 SizeX 130 OffsetRight -964 SizeY 38 OffsetBottom -232 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Next" } } }
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "WeightsNext" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 834 OffsetLeft 834 PositionY 194 OffsetTop 194 SizeX 130 OffsetRight -964 SizeY 38 OffsetBottom -232 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Next" } } }
|
||||||
ScrollLayoutWidgetClass { Name "WeightsScroll" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 238 OffsetTop 238 SizeX 936 OffsetRight -964 SizeY 148 OffsetBottom -386 } { VerticalLayoutWidgetClass { Name "WeightsContent" Slot AlignableSlot { HorizontalAlign 3 VerticalAlign 3 Padding 8 4 14 4 } { } } } }
|
ScrollLayoutWidgetClass { Name "WeightsScroll" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 238 OffsetTop 238 SizeX 936 OffsetRight -964 SizeY 240 OffsetBottom -478 } { VerticalLayoutWidgetClass { Name "WeightsContent" Slot AlignableSlot { HorizontalAlign 3 VerticalAlign 3 Padding 8 4 14 4 } { } } } }
|
||||||
TextWidgetClass { Name "WeightsFooter" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 392 OffsetTop 392 SizeX 936 OffsetRight -964 SizeY 38 OffsetBottom -430 } Color 0.9 0.92 0.94 1 Text "Shown percentages apply when an extra item is chosen. Mod and item weights multiply. Blocked items remain excluded." "Font Size" 17 "Min Font Size" 17 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
TextWidgetClass { Name "WeightsFooter" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 484 OffsetTop 484 SizeX 936 OffsetRight -964 SizeY 38 OffsetBottom -522 } Color 0.9 0.92 0.94 1 Text "Shown percentages apply when an extra item is chosen. Mod and item weights multiply. Blocked items remain excluded." "Font Size" 17 "Min Font Size" 17 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
} }
|
} }
|
||||||
FrameWidgetClass { "Is Visible" 0 Name "AllowedItemsPage" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 220 OffsetLeft 220 PositionY 160 OffsetTop 160 SizeX 1000 OffsetRight -1220 SizeY 486 OffsetBottom -646 } {
|
FrameWidgetClass { "Is Visible" 0 Name "AllowedItemsPage" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 220 OffsetLeft 220 PositionY 160 OffsetTop 160 SizeX 1000 OffsetRight -1220 SizeY 532 OffsetBottom -692 } {
|
||||||
TextWidgetClass { Name "ItemsHelp" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 4 OffsetTop 4 SizeX 936 OffsetRight -964 SizeY 48 OffsetBottom -52 } Color 0.9 0.92 0.94 1 Text "Choose a mod, then allow or block its items. Allow all / Block all resets individual selections for this mod. Changes affect future extra equipment." "Font Size" 17 "Min Font Size" 17 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
TextWidgetClass { Name "ItemsHelp" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 0 OffsetTop 0 SizeX 936 OffsetRight -964 SizeY 28 OffsetBottom -28 } Color 0.9 0.92 0.94 1 Text "Choose a mod, search by name, then enable or disable individual items." "Font Size" 17 "Min Font Size" 17 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
ButtonWidgetClass : "{4B5AE6E64037FFB4}UI/layouts/WidgetLibrary/ComboBox/WLib_ComboBox.layout" { Name "ItemsMod" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 34 OffsetTop 34 SizeX 540 OffsetRight -568 SizeY 36 OffsetBottom -70 } components { SCR_ComboBoxComponent "{546B1D464BC32853}" { m_bUseLabel 0 m_sLabel "" m_fSizeWithoutLabel 36 } } }
|
||||||
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "ItemsSelectionState" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 804 OffsetLeft 804 PositionY 34 OffsetTop 34 SizeX 160 OffsetRight -964 SizeY 36 OffsetBottom -70 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Waiting..." } } }
|
||||||
|
TextWidgetClass { Name "ItemsSearchLabel" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 77 OffsetTop 77 SizeX 430 OffsetRight -458 SizeY 22 OffsetBottom -99 } Color 0.9 0.92 0.94 1 Text "Search name" "Font Size" 14 "Min Font Size" 14 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
ButtonWidgetClass : "{4B5AE6E64037FFB4}UI/layouts/WidgetLibrary/ComboBox/WLib_ComboBox.layout" { Name "ItemsMod" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 58 OffsetTop 58 SizeX 420 OffsetRight -448 SizeY 42 OffsetBottom -100 } components { SCR_ComboBoxComponent "{546B1D464BC32853}" { m_bUseLabel 0 m_sLabel "" m_fSizeWithoutLabel 42 } } }
|
TextWidgetClass { Name "ItemsFilterLabel" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 470 OffsetLeft 470 PositionY 77 OffsetTop 77 SizeX 220 OffsetRight -690 SizeY 22 OffsetBottom -99 } Color 0.9 0.92 0.94 1 Text "Show" "Font Size" 14 "Min Font Size" 14 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "ItemsShow" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 458 OffsetLeft 458 PositionY 58 OffsetTop 58 SizeX 150 OffsetRight -608 SizeY 42 OffsetBottom -100 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Show items" } } }
|
ButtonWidgetClass : "{DA43C77A0D1E59F8}UI/layouts/ERR/AdminValue.layout" { Name "ItemsSearch" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 100 OffsetTop 100 SizeX 430 OffsetRight -458 SizeY 36 OffsetBottom -136 } components { SCR_EditBoxComponent "{547290FFBD5B33E9}" { m_sLabel "" } } }
|
||||||
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "ItemsEnableAll" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 618 OffsetLeft 618 PositionY 58 OffsetTop 58 SizeX 166 OffsetRight -784 SizeY 42 OffsetBottom -100 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Allow all" } } }
|
ButtonWidgetClass : "{4B5AE6E64037FFB4}UI/layouts/WidgetLibrary/ComboBox/WLib_ComboBox.layout" { Name "ItemsFilter" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 470 OffsetLeft 470 PositionY 100 OffsetTop 100 SizeX 220 OffsetRight -690 SizeY 36 OffsetBottom -136 } components { SCR_ComboBoxComponent "{546B1D464BC32853}" { m_bUseLabel 0 m_sLabel "" m_fSizeWithoutLabel 36 } } }
|
||||||
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "ItemsDisableAll" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 794 OffsetLeft 794 PositionY 58 OffsetTop 58 SizeX 170 OffsetRight -964 SizeY 42 OffsetBottom -100 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Block all" } } }
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "ItemsShow" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 704 OffsetLeft 704 PositionY 100 OffsetTop 100 SizeX 120 OffsetRight -824 SizeY 36 OffsetBottom -136 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Search" } } }
|
||||||
TextWidgetClass { Name "ItemsScan" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 104 OffsetTop 104 SizeX 756 OffsetRight -784 SizeY 24 OffsetBottom -128 } Color 0.9 0.92 0.94 1 Text "Waiting for server..." "Font Size" 17 "Min Font Size" 17 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "ItemsClear" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 838 OffsetLeft 838 PositionY 100 OffsetTop 100 SizeX 126 OffsetRight -964 SizeY 36 OffsetBottom -136 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Clear" } } }
|
||||||
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "ItemsSelectionState" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 804 OffsetLeft 804 PositionY 102 OffsetTop 102 SizeX 160 OffsetRight -964 SizeY 28 OffsetBottom -130 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Waiting..." m_fDisabledOpacity 1 } } }
|
TextWidgetClass { Name "ItemsBulkScope" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 145 OffsetTop 145 SizeX 350 OffsetRight -378 SizeY 28 OffsetBottom -173 } Color 0.9 0.92 0.94 1 Text "Whole selected mod:" "Font Size" 17 "Min Font Size" 17 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "ItemsPrevious" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 132 OffsetTop 132 SizeX 130 OffsetRight -158 SizeY 42 OffsetBottom -174 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Previous" } } }
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "ItemsEnableAll" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 554 OffsetLeft 554 PositionY 140 OffsetTop 140 SizeX 198 OffsetRight -752 SizeY 36 OffsetBottom -176 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Enable all items" } } }
|
||||||
TextWidgetClass { Name "ItemsPageState" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 170 OffsetLeft 170 PositionY 136 OffsetTop 136 SizeX 646 OffsetRight -816 SizeY 40 OffsetBottom -176 } Color 0.9 0.92 0.94 1 Text "Choose a mod and press Show items." "Font Size" 17 "Min Font Size" 17 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "ItemsDisableAll" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 766 OffsetLeft 766 PositionY 140 OffsetTop 140 SizeX 198 OffsetRight -964 SizeY 36 OffsetBottom -176 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Disable all items" } } }
|
||||||
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "ItemsNext" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 834 OffsetLeft 834 PositionY 132 OffsetTop 132 SizeX 130 OffsetRight -964 SizeY 42 OffsetBottom -174 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Next" } } }
|
ScrollLayoutWidgetClass { Name "ItemsScroll" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 184 OffsetTop 184 SizeX 936 OffsetRight -964 SizeY 294 OffsetBottom -478 } { VerticalLayoutWidgetClass { Name "ItemsContent" Slot AlignableSlot { HorizontalAlign 3 VerticalAlign 3 Padding 0 0 12 0 } { TextWidgetClass { Name "ItemsText" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 0 OffsetTop 0 SizeX 920 OffsetRight -920 SizeY 30 OffsetBottom -30 } Color 0.9 0.92 0.94 1 Text "Select a mod above." "Font Size" 17 "Min Font Size" 17 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
ScrollLayoutWidgetClass { Name "ItemsScroll" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 180 OffsetTop 180 SizeX 936 OffsetRight -964 SizeY 246 OffsetBottom -426 } { VerticalLayoutWidgetClass { Name "ItemsContent" Slot AlignableSlot { HorizontalAlign 3 VerticalAlign 3 Padding 8 4 14 4 } { TextWidgetClass { Name "ItemsText" Slot LayoutSlot { HorizontalAlign 3 Padding 0 0 0 8 SizeMode Fill } Text "Choose a mod to edit its inventory items." "Font Size" 18 Wrap 1 } } } } }
|
} } } }
|
||||||
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "ItemsPrevious" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 486 OffsetTop 486 SizeX 120 OffsetRight -148 SizeY 36 OffsetBottom -522 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Previous" } } }
|
||||||
|
TextWidgetClass { Name "ItemsPageState" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 162 OffsetLeft 162 PositionY 490 OffsetTop 490 SizeX 668 OffsetRight -830 SizeY 32 OffsetBottom -522 } Color 0.9 0.92 0.94 1 Text "Waiting for server..." "Font Size" 17 "Min Font Size" 17 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "ItemsNext" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 844 OffsetLeft 844 PositionY 486 OffsetTop 486 SizeX 120 OffsetRight -964 SizeY 36 OffsetBottom -522 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Next" } } }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
} }
|
} }
|
||||||
FrameWidgetClass { "Is Visible" 0 Name "ItemPresetsPage" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 220 OffsetLeft 220 PositionY 160 OffsetTop 160 SizeX 1000 OffsetRight -1220 SizeY 486 OffsetBottom -646 } {
|
FrameWidgetClass { "Is Visible" 0 Name "ItemPresetsPage" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 220 OffsetLeft 220 PositionY 160 OffsetTop 160 SizeX 1000 OffsetRight -1220 SizeY 532 OffsetBottom -692 } {
|
||||||
TextWidgetClass { Name "PresetsHeading" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 4 OffsetTop 4 SizeX 936 OffsetRight -964 SizeY 30 OffsetBottom -34 } Color 0.9 0.92 0.94 1 Text "Save your item selections" "Font Size" 22 "Min Font Size" 22 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
TextWidgetClass { Name "PresetsHeading" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 4 OffsetTop 4 SizeX 936 OffsetRight -964 SizeY 30 OffsetBottom -34 } Color 0.9 0.92 0.94 1 Text "Save your content choices" "Font Size" 22 "Min Font Size" 22 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
TextWidgetClass { Name "PresetsHelp" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 42 OffsetTop 42 SizeX 936 OffsetRight -964 SizeY 46 OffsetBottom -88 } Color 0.9 0.92 0.94 1 Text "A preset includes allowed / blocked items and their weights across all mods. It does not change the overall extra-equipment chance. Missing mods stay inactive." "Font Size" 17 "Min Font Size" 17 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
TextWidgetClass { Name "PresetsHelp" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 42 OffsetTop 42 SizeX 936 OffsetRight -964 SizeY 46 OffsetBottom -88 } Color 0.9 0.92 0.94 1 Text "Presets include mod switches, item and vehicle-role choices, and equipment weights. Spawn counts and overall equipment chance are separate." "Font Size" 17 "Min Font Size" 17 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
TextWidgetClass { Name "ItemsPresetsLabel" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 98 OffsetTop 98 SizeX 936 OffsetRight -964 SizeY 26 OffsetBottom -124 } Color 0.9 0.92 0.94 1 Text "Saved presets" "Font Size" 17 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
TextWidgetClass { Name "ItemsPresetsLabel" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 98 OffsetTop 98 SizeX 936 OffsetRight -964 SizeY 26 OffsetBottom -124 } Color 0.9 0.92 0.94 1 Text "Saved presets" "Font Size" 17 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
ButtonWidgetClass : "{4B5AE6E64037FFB4}UI/layouts/WidgetLibrary/ComboBox/WLib_ComboBox.layout" { Name "ItemsPresets" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 134 OffsetTop 134 SizeX 560 OffsetRight -588 SizeY 42 OffsetBottom -176 } components { SCR_ComboBoxComponent "{546B1D464BC32853}" { m_bUseLabel 0 m_sLabel "" m_fSizeWithoutLabel 42 } } }
|
ButtonWidgetClass : "{4B5AE6E64037FFB4}UI/layouts/WidgetLibrary/ComboBox/WLib_ComboBox.layout" { Name "ItemsPresets" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 134 OffsetTop 134 SizeX 560 OffsetRight -588 SizeY 42 OffsetBottom -176 } components { SCR_ComboBoxComponent "{546B1D464BC32853}" { m_bUseLabel 0 m_sLabel "" m_fSizeWithoutLabel 42 } } }
|
||||||
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "ItemsPresetLoad" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 608 OffsetLeft 608 PositionY 134 OffsetTop 134 SizeX 170 OffsetRight -778 SizeY 42 OffsetBottom -176 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Load" } } }
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "ItemsPresetLoad" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 608 OffsetLeft 608 PositionY 134 OffsetTop 134 SizeX 170 OffsetRight -778 SizeY 42 OffsetBottom -176 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Load" } } }
|
||||||
@@ -1146,23 +1289,23 @@ TextWidgetClass { Name "ItemsStorage" Slot FrameWidgetSlot { Anchor 0 0 0 0 Posi
|
|||||||
TextWidgetClass { Name "PresetNamesHelp" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 304 OffsetTop 304 SizeX 936 OffsetRight -964 SizeY 44 OffsetBottom -348 } Color 0.9 0.92 0.94 1 Text "Names: up to 24 letters, digits, _ or -. Save / replace overwrites a preset with the same name." "Font Size" 17 "Min Font Size" 17 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
TextWidgetClass { Name "PresetNamesHelp" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 304 OffsetTop 304 SizeX 936 OffsetRight -964 SizeY 44 OffsetBottom -348 } Color 0.9 0.92 0.94 1 Text "Names: up to 24 letters, digits, _ or -. Save / replace overwrites a preset with the same name." "Font Size" 17 "Min Font Size" 17 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
|
||||||
} }
|
} }
|
||||||
FrameWidgetClass { "Is Visible" 0 Name "AIPage" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 220 OffsetLeft 220 PositionY 160 OffsetTop 160 SizeX 1000 OffsetRight -1220 SizeY 486 OffsetBottom -646 } {
|
FrameWidgetClass { "Is Visible" 0 Name "AIPage" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 220 OffsetLeft 220 PositionY 160 OffsetTop 160 SizeX 1000 OffsetRight -1220 SizeY 532 OffsetBottom -692 } {
|
||||||
TextWidgetClass { Name "AIHelp" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 6 OffsetTop 6 SizeX 720 OffsetRight -748 SizeY 42 OffsetBottom -48 } Color 0.9 0.92 0.94 1 Text "AI doctrine - changes apply next round." "Font Size" 20 "Min Font Size" 20 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
TextWidgetClass { Name "AIHelp" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 6 OffsetTop 6 SizeX 720 OffsetRight -748 SizeY 42 OffsetBottom -48 } Color 0.9 0.92 0.94 1 Text "AI doctrine - changes apply next round." "Font Size" 20 "Min Font Size" 20 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
|
||||||
TextWidgetClass { Name "Label_aiDoctrine" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 76 OffsetTop 76 SizeX 936 OffsetRight -964 SizeY 28 OffsetBottom -104 } Color 0.9 0.92 0.94 1 Text "Hunting doctrine: Current or Coordinated" "Font Size" 20 "Min Font Size" 20 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
TextWidgetClass { Name "Label_aiDoctrine" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 76 OffsetTop 76 SizeX 936 OffsetRight -964 SizeY 28 OffsetBottom -104 } Color 0.9 0.92 0.94 1 Text "Hunting doctrine: Current or Coordinated" "Font Size" 20 "Min Font Size" 20 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
ButtonWidgetClass : "{DA43C77A0D1E59F8}UI/layouts/ERR/AdminValue.layout" { Name "aiDoctrine" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 108 OffsetTop 108 SizeX 360 OffsetRight -388 SizeY 38 OffsetBottom -146 } components { SCR_EditBoxComponent "{547290FFBD5B33E9}" { m_sLabel "" } } }
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "Choice_aiDoctrine_current" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 108 OffsetTop 108 SizeX 236 OffsetRight -264 SizeY 38 OffsetBottom -146 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Current" } } }
|
||||||
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "Apply_aiDoctrine" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 400 OffsetLeft 400 PositionY 108 OffsetTop 108 SizeX 112 OffsetRight -512 SizeY 38 OffsetBottom -146 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Apply" } } }
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "Choice_aiDoctrine_coordinated" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 276 OffsetLeft 276 PositionY 108 OffsetTop 108 SizeX 236 OffsetRight -512 SizeY 38 OffsetBottom -146 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Coordinated" } } }
|
||||||
TextWidgetClass { Name "Saved_aiDoctrine" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 536 OffsetLeft 536 PositionY 116 OffsetTop 116 SizeX 428 OffsetRight -964 SizeY 26 OffsetBottom -142 } Color 0.9 0.92 0.94 1 Text "Waiting for saved value" "Font Size" 16 "Min Font Size" 16 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
TextWidgetClass { Name "Saved_aiDoctrine" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 536 OffsetLeft 536 PositionY 116 OffsetTop 116 SizeX 428 OffsetRight -964 SizeY 26 OffsetBottom -142 } Color 0.9 0.92 0.94 1 Text "Waiting for saved value" "Font Size" 16 "Min Font Size" 16 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
TextWidgetClass { Name "Help_aiDoctrine" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 152 OffsetTop 152 SizeX 936 OffsetRight -964 SizeY 42 OffsetBottom -194 } Color 0.9 0.92 0.94 1 Text "Current keeps existing behaviour. Coordinated assigns squads to a shared search operation." "Font Size" 16 "Min Font Size" 16 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
TextWidgetClass { Name "Help_aiDoctrine" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 152 OffsetTop 152 SizeX 936 OffsetRight -964 SizeY 42 OffsetBottom -194 } Color 0.9 0.92 0.94 1 Text "Current keeps existing behaviour. Coordinated assigns squads to a shared search operation." "Font Size" 16 "Min Font Size" 16 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
TextWidgetClass { Name "Label_aiShadow" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 206 OffsetTop 206 SizeX 936 OffsetRight -964 SizeY 28 OffsetBottom -234 } Color 0.9 0.92 0.94 1 Text "Proposal logging: On or Off" "Font Size" 20 "Min Font Size" 20 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
TextWidgetClass { Name "Label_aiShadow" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 206 OffsetTop 206 SizeX 936 OffsetRight -964 SizeY 28 OffsetBottom -234 } Color 0.9 0.92 0.94 1 Text "Proposal logging: On or Off" "Font Size" 20 "Min Font Size" 20 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
ButtonWidgetClass : "{DA43C77A0D1E59F8}UI/layouts/ERR/AdminValue.layout" { Name "aiShadow" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 238 OffsetTop 238 SizeX 360 OffsetRight -388 SizeY 38 OffsetBottom -276 } components { SCR_EditBoxComponent "{547290FFBD5B33E9}" { m_sLabel "" } } }
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "Choice_aiShadow_off" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 238 OffsetTop 238 SizeX 236 OffsetRight -264 SizeY 38 OffsetBottom -276 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Off" } } }
|
||||||
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "Apply_aiShadow" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 400 OffsetLeft 400 PositionY 238 OffsetTop 238 SizeX 112 OffsetRight -512 SizeY 38 OffsetBottom -276 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Apply" } } }
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "Choice_aiShadow_on" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 276 OffsetLeft 276 PositionY 238 OffsetTop 238 SizeX 236 OffsetRight -512 SizeY 38 OffsetBottom -276 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "On" } } }
|
||||||
TextWidgetClass { Name "Saved_aiShadow" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 536 OffsetLeft 536 PositionY 246 OffsetTop 246 SizeX 428 OffsetRight -964 SizeY 26 OffsetBottom -272 } Color 0.9 0.92 0.94 1 Text "Waiting for saved value" "Font Size" 16 "Min Font Size" 16 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
TextWidgetClass { Name "Saved_aiShadow" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 536 OffsetLeft 536 PositionY 246 OffsetTop 246 SizeX 428 OffsetRight -964 SizeY 26 OffsetBottom -272 } Color 0.9 0.92 0.94 1 Text "Waiting for saved value" "Font Size" 16 "Min Font Size" 16 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
TextWidgetClass { Name "Help_aiShadow" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 282 OffsetTop 282 SizeX 936 OffsetRight -964 SizeY 42 OffsetBottom -324 } Color 0.9 0.92 0.94 1 Text "On logs coordinated proposals while Current keeps control. No coordinated orders are issued." "Font Size" 16 "Min Font Size" 16 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
TextWidgetClass { Name "Help_aiShadow" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 282 OffsetTop 282 SizeX 936 OffsetRight -964 SizeY 42 OffsetBottom -324 } Color 0.9 0.92 0.94 1 Text "On logs coordinated proposals while Current keeps control. No coordinated orders are issued." "Font Size" 16 "Min Font Size" 16 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
TextWidgetClass { Name "AIChangesHelp" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 350 OffsetTop 350 SizeX 936 OffsetRight -964 SizeY 72 OffsetBottom -422 } Color 0.9 0.92 0.94 1 Text "Current is the default. Proposal logging is for comparing plans before enabling coordinated orders. Neither setting changes the running round." "Font Size" 18 "Min Font Size" 18 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
TextWidgetClass { Name "AIChangesHelp" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 350 OffsetTop 350 SizeX 936 OffsetRight -964 SizeY 72 OffsetBottom -422 } Color 0.9 0.92 0.94 1 Text "Current is the default. Proposal logging is for comparing plans before enabling coordinated orders. Neither setting changes the running round." "Font Size" 18 "Min Font Size" 18 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
} }
|
} }
|
||||||
FrameWidgetClass { "Is Visible" 0 Name "StarterPage" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 220 OffsetLeft 220 PositionY 160 OffsetTop 160 SizeX 1000 OffsetRight -1220 SizeY 486 OffsetBottom -646 } {
|
FrameWidgetClass { "Is Visible" 0 Name "StarterPage" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 220 OffsetLeft 220 PositionY 160 OffsetTop 160 SizeX 1000 OffsetRight -1220 SizeY 532 OffsetBottom -692 } {
|
||||||
TextWidgetClass { Name "StarterHeading" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 5 OffsetTop 5 SizeX 350 OffsetRight -378 SizeY 30 OffsetBottom -35 } Color 0.9 0.92 0.94 1 Text "Shared starting backpack" "Font Size" 17 "Min Font Size" 17 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
TextWidgetClass { Name "StarterHeading" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 5 OffsetTop 5 SizeX 350 OffsetRight -378 SizeY 30 OffsetBottom -35 } Color 0.9 0.92 0.94 1 Text "Shared starting supplies" "Font Size" 17 "Min Font Size" 17 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "StarterDefault" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 410 OffsetLeft 410 PositionY 0 OffsetTop 0 SizeX 260 OffsetRight -670 SizeY 40 OffsetBottom -40 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Default loadout" } } }
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "StarterDefault" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 410 OffsetLeft 410 PositionY 0 OffsetTop 0 SizeX 260 OffsetRight -670 SizeY 40 OffsetBottom -40 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Default loadout" } } }
|
||||||
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "StarterCustom" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 690 OffsetLeft 690 PositionY 0 OffsetTop 0 SizeX 274 OffsetRight -964 SizeY 40 OffsetBottom -40 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Custom loadout" } } }
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "StarterCustom" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 690 OffsetLeft 690 PositionY 0 OffsetTop 0 SizeX 274 OffsetRight -964 SizeY 40 OffsetBottom -40 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Custom loadout" } } }
|
||||||
TextWidgetClass { Name "StarterState" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 46 OffsetTop 46 SizeX 936 OffsetRight -964 SizeY 44 OffsetBottom -90 } Color 0.9 0.92 0.94 1 Text "Waiting for saved loadout..." "Font Size" 17 "Min Font Size" 17 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
TextWidgetClass { Name "StarterState" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 46 OffsetTop 46 SizeX 936 OffsetRight -964 SizeY 44 OffsetBottom -90 } Color 0.9 0.92 0.94 1 Text "Waiting for saved loadout..." "Font Size" 17 "Min Font Size" 17 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
@@ -1175,13 +1318,14 @@ TextWidgetClass { Name "StarterQuantityLabel" Slot FrameWidgetSlot { Anchor 0 0
|
|||||||
ButtonWidgetClass : "{DA43C77A0D1E59F8}UI/layouts/ERR/AdminValue.layout" { Name "StarterQuantity" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 676 OffsetLeft 676 PositionY 144 OffsetTop 144 SizeX 88 OffsetRight -764 SizeY 40 OffsetBottom -184 } components { SCR_EditBoxComponent "{547290FFBD5B33E9}" { m_sLabel "" } } }
|
ButtonWidgetClass : "{DA43C77A0D1E59F8}UI/layouts/ERR/AdminValue.layout" { Name "StarterQuantity" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 676 OffsetLeft 676 PositionY 144 OffsetTop 144 SizeX 88 OffsetRight -764 SizeY 40 OffsetBottom -184 } components { SCR_EditBoxComponent "{547290FFBD5B33E9}" { m_sLabel "" } } }
|
||||||
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "StarterAdd" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 776 OffsetLeft 776 PositionY 144 OffsetTop 144 SizeX 188 OffsetRight -964 SizeY 40 OffsetBottom -184 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Save quantity" } } }
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "StarterAdd" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 776 OffsetLeft 776 PositionY 144 OffsetTop 144 SizeX 188 OffsetRight -964 SizeY 40 OffsetBottom -184 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Save quantity" } } }
|
||||||
TextWidgetClass { Name "StarterCatalogState" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 192 OffsetTop 192 SizeX 936 OffsetRight -964 SizeY 28 OffsetBottom -220 } Color 0.9 0.92 0.94 1 Text "Choose a mod, then an item. Quantity 0 removes an item." "Font Size" 17 "Min Font Size" 17 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
TextWidgetClass { Name "StarterCatalogState" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 192 OffsetTop 192 SizeX 936 OffsetRight -964 SizeY 28 OffsetBottom -220 } Color 0.9 0.92 0.94 1 Text "Choose a mod, then an item. Quantity 0 removes an item." "Font Size" 17 "Min Font Size" 17 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
TextWidgetClass { Name "StarterSavedHeading" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 230 OffsetTop 230 SizeX 936 OffsetRight -964 SizeY 28 OffsetBottom -258 } Color 0.9 0.92 0.94 1 Text "Saved custom contents (select an entry to remove)" "Font Size" 17 "Min Font Size" 17 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
ButtonWidgetClass : "{4B5AE6E64037FFB4}UI/layouts/WidgetLibrary/ComboBox/WLib_ComboBox.layout" { Name "StarterDestination" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 226 OffsetTop 226 SizeX 540 OffsetRight -568 SizeY 40 OffsetBottom -266 } components { SCR_ComboBoxComponent "{546B1D464BC32853}" { m_bUseLabel 0 m_sLabel "" m_fSizeWithoutLabel 40 } } }
|
||||||
ButtonWidgetClass : "{4B5AE6E64037FFB4}UI/layouts/WidgetLibrary/ComboBox/WLib_ComboBox.layout" { Name "StarterSaved" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 266 OffsetTop 266 SizeX 728 OffsetRight -756 SizeY 40 OffsetBottom -306 } components { SCR_ComboBoxComponent "{546B1D464BC32853}" { m_bUseLabel 0 m_sLabel "" m_fSizeWithoutLabel 40 } } }
|
TextWidgetClass { Name "StarterSavedHeading" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 278 OffsetTop 278 SizeX 936 OffsetRight -964 SizeY 28 OffsetBottom -306 } Color 0.9 0.92 0.94 1 Text "Saved custom contents (select an entry to remove)" "Font Size" 17 "Min Font Size" 17 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "StarterRemove" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 776 OffsetLeft 776 PositionY 266 OffsetTop 266 SizeX 188 OffsetRight -964 SizeY 40 OffsetBottom -306 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Remove item" } } }
|
ButtonWidgetClass : "{4B5AE6E64037FFB4}UI/layouts/WidgetLibrary/ComboBox/WLib_ComboBox.layout" { Name "StarterSaved" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 314 OffsetTop 314 SizeX 728 OffsetRight -756 SizeY 40 OffsetBottom -354 } components { SCR_ComboBoxComponent "{546B1D464BC32853}" { m_bUseLabel 0 m_sLabel "" m_fSizeWithoutLabel 40 } } }
|
||||||
TextWidgetClass { Name "StarterSummary" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 314 OffsetTop 314 SizeX 936 OffsetRight -964 SizeY 30 OffsetBottom -344 } Color 0.9 0.92 0.94 1 Text "Custom list is empty." "Font Size" 17 "Min Font Size" 17 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "StarterRemove" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 776 OffsetLeft 776 PositionY 314 OffsetTop 314 SizeX 188 OffsetRight -964 SizeY 40 OffsetBottom -354 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Remove item" } } }
|
||||||
TextWidgetClass { Name "StarterHelp" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 352 OffsetTop 352 SizeX 936 OffsetRight -964 SizeY 72 OffsetBottom -424 } Color 0.9 0.92 0.94 1 Text "Up to 32 item types / 64 items. Changes apply next round, at prison or crash. The backpack has limited space; items that do not fit are omitted. Enemy spawn weights do not affect this loadout." "Font Size" 17 "Min Font Size" 17 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
TextWidgetClass { Name "StarterSummary" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 362 OffsetTop 362 SizeX 936 OffsetRight -964 SizeY 30 OffsetBottom -392 } Color 0.9 0.92 0.94 1 Text "Custom list is empty." "Font Size" 17 "Min Font Size" 17 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
TextWidgetClass { Name "StarterHelp" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 400 OffsetTop 400 SizeX 936 OffsetRight -964 SizeY 72 OffsetBottom -472 } Color 0.9 0.92 0.94 1 Text "Choose where to spawn each quantity before saving: inside the backpack or on the ground beside it. Up to 32 entries / 64 items combined. Applies next round, at prison or crash. Items that do not fit inside are also left nearby." "Font Size" 17 "Min Font Size" 17 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
} }
|
} }
|
||||||
FrameWidgetClass { "Is Visible" 0 Name "SarkaPage" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 220 OffsetLeft 220 PositionY 160 OffsetTop 160 SizeX 1000 OffsetRight -1220 SizeY 486 OffsetBottom -646 } {
|
FrameWidgetClass { "Is Visible" 0 Name "SarkaPage" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 220 OffsetLeft 220 PositionY 160 OffsetTop 160 SizeX 1000 OffsetRight -1220 SizeY 532 OffsetBottom -692 } {
|
||||||
TextWidgetClass { Name "SarkaHeading" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 6 OffsetTop 6 SizeX 710 OffsetRight -738 SizeY 32 OffsetBottom -38 } Color 0.9 0.92 0.94 1 Text "Šárka special-forces hunters" "Font Size" 22 "Min Font Size" 22 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
TextWidgetClass { Name "SarkaHeading" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 6 OffsetTop 6 SizeX 710 OffsetRight -738 SizeY 32 OffsetBottom -38 } Color 0.9 0.92 0.94 1 Text "Šárka special-forces hunters" "Font Size" 22 "Min Font Size" 22 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
|
||||||
TextWidgetClass { Name "SarkaHelp" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 50 OffsetTop 50 SizeX 936 OffsetRight -964 SizeY 44 OffsetBottom -94 } Color 0.9 0.92 0.94 1 Text "Four soldiers per car, including a medic. Anger rises from identified enemy casualties." "Font Size" 18 "Min Font Size" 18 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
TextWidgetClass { Name "SarkaHelp" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 50 OffsetTop 50 SizeX 936 OffsetRight -964 SizeY 44 OffsetBottom -94 } Color 0.9 0.92 0.94 1 Text "Four soldiers per car, including a medic. Anger rises from identified enemy casualties." "Font Size" 18 "Min Font Size" 18 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
@@ -1192,7 +1336,11 @@ TextWidgetClass { Name "Saved_sarkaAngerPerCar" Slot FrameWidgetSlot { Anchor 0
|
|||||||
TextWidgetClass { Name "Help_sarkaAngerPerCar" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 176 OffsetTop 176 SizeX 936 OffsetRight -964 SizeY 26 OffsetBottom -202 } Color 0.9 0.92 0.94 1 Text "Default 3. At 5 points per car: anger 4 = none, 5 = one car, 10 = two." "Font Size" 17 "Min Font Size" 17 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
TextWidgetClass { Name "Help_sarkaAngerPerCar" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 176 OffsetTop 176 SizeX 936 OffsetRight -964 SizeY 26 OffsetBottom -202 } Color 0.9 0.92 0.94 1 Text "Default 3. At 5 points per car: anger 4 = none, 5 = one car, 10 = two." "Font Size" 17 "Min Font Size" 17 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
TextWidgetClass { Name "SarkaSettingsHelp" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 424 OffsetTop 424 SizeX 936 OffsetRight -964 SizeY 52 OffsetBottom -476 } Color 0.9 0.92 0.94 1 Text "Apply saves this setting for new waves. Existing teams remain. Empty sessions do not build up extra waves." "Font Size" 18 "Min Font Size" 18 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
TextWidgetClass { Name "SarkaSettingsHelp" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 424 OffsetTop 424 SizeX 936 OffsetRight -964 SizeY 52 OffsetBottom -476 } Color 0.9 0.92 0.94 1 Text "Apply saves this setting for new waves. Existing teams remain. Empty sessions do not build up extra waves." "Font Size" 18 "Min Font Size" 18 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
} }
|
} }
|
||||||
TextWidgetClass { Name "Feedback" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 248 OffsetLeft 248 PositionY 670 OffsetTop 670 SizeX 936 OffsetRight -1184 SizeY 70 OffsetBottom -740 } Color 0.9 0.92 0.94 1 Text "" "Font Size" 17 "Min Font Size" 17 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
TextWidgetClass { Name "Feedback" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 248 OffsetLeft 248 PositionY 704 OffsetTop 704 SizeX 936 OffsetRight -1184 SizeY 44 OffsetBottom -748 } Color 0.9 0.92 0.94 1 Text "" "Font Size" 17 "Min Font Size" 17 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
SizeLayoutWidgetClass { Name "ContentRow" Slot LayoutSlot { HorizontalAlign 3 Padding 0 0 0 16 } WidthOverride 900 HeightOverride 160 {
|
SizeLayoutWidgetClass { Name "ContentRow" WidthOverride 900 HeightOverride 108 Slot LayoutSlot { HorizontalAlign 3 Padding 0 0 0 8 } {
|
||||||
FrameWidgetClass { Name "ContentRowBody" Slot AlignableSlot { HorizontalAlign 3 VerticalAlign 3 } {
|
FrameWidgetClass { Name "ContentRowBody" Slot AlignableSlot { HorizontalAlign 3 VerticalAlign 3 } {
|
||||||
TextWidgetClass { Name "PackLabel" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 0 OffsetTop 0 SizeX 718 OffsetRight -718 SizeY 32 OffsetBottom -32 } Color 0.9 0.92 0.94 1 Text "Content pack" "Font Size" 26 "Min Font Size" 26 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
TextWidgetClass { Name "PackLabel" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 0 OffsetTop 0 SizeX 530 OffsetRight -530 SizeY 32 OffsetBottom -32 } Color 0.9 0.92 0.94 1 Text "Content" "Font Size" 20 "Min Font Size" 20 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "SelectionState" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 734 OffsetLeft 734 PositionY 0 OffsetTop 0 SizeX 160 OffsetRight -894 SizeY 32 OffsetBottom -32 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Waiting..." m_fDisabledOpacity 1 } } }
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "SelectionState" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 540 OffsetLeft 540 PositionY 0 OffsetTop 0 SizeX 120 OffsetRight -660 SizeY 32 OffsetBottom -32 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Waiting..." } } }
|
||||||
TextWidgetClass { Name "PackState" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 40 OffsetTop 40 SizeX 900 OffsetRight -900 SizeY 30 OffsetBottom -70 } Color 0.9 0.92 0.94 1 Text "Waiting for server settings..." "Font Size" 18 "Min Font Size" 18 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "Enable" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 670 OffsetLeft 670 PositionY 0 OffsetTop 0 SizeX 108 OffsetRight -778 SizeY 32 OffsetBottom -32 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Enable" } } }
|
||||||
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "Enable" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 90 OffsetTop 90 SizeX 180 OffsetRight -180 SizeY 42 OffsetBottom -132 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Enable" } } }
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "Disable" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 788 OffsetLeft 788 PositionY 0 OffsetTop 0 SizeX 108 OffsetRight -896 SizeY 32 OffsetBottom -32 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Disable" } } }
|
||||||
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "Disable" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 196 OffsetLeft 196 PositionY 90 OffsetTop 90 SizeX 180 OffsetRight -376 SizeY 42 OffsetBottom -132 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Disable" } } }
|
TextWidgetClass { Name "PackState" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 38 OffsetTop 38 SizeX 900 OffsetRight -900 SizeY 40 OffsetBottom -78 } Color 0.9 0.92 0.94 1 Text "Waiting for server..." "Font Size" 14 "Min Font Size" 14 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "EditItems" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 670 OffsetLeft 670 PositionY 70 OffsetTop 70 SizeX 108 OffsetRight -778 SizeY 32 OffsetBottom -102 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Items" } } }
|
||||||
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "EditVehicles" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 788 OffsetLeft 788 PositionY 70 OffsetTop 70 SizeX 108 OffsetRight -896 SizeY 32 OffsetBottom -102 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Vehicles" } } }
|
||||||
} }
|
} }
|
||||||
} }
|
} }
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
SizeLayoutWidgetClass { Name "ItemRow" WidthOverride 900 HeightOverride 180 Slot LayoutSlot { HorizontalAlign 3 Padding 0 0 0 16 } {
|
SizeLayoutWidgetClass { Name "ItemRow" WidthOverride 900 HeightOverride 82 Slot LayoutSlot { HorizontalAlign 3 Padding 0 0 0 8 } {
|
||||||
FrameWidgetClass { Name "ContentRowBody" Slot AlignableSlot { HorizontalAlign 3 VerticalAlign 3 } {
|
FrameWidgetClass { Name "ContentRowBody" Slot AlignableSlot { HorizontalAlign 3 VerticalAlign 3 } {
|
||||||
TextWidgetClass { Name "PackLabel" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 0 OffsetTop 0 SizeX 718 OffsetRight -718 SizeY 32 OffsetBottom -32 } Color 0.9 0.92 0.94 1 Text "Content pack" "Font Size" 26 "Min Font Size" 26 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
TextWidgetClass { Name "PackLabel" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 0 OffsetTop 0 SizeX 530 OffsetRight -530 SizeY 32 OffsetBottom -32 } Color 0.9 0.92 0.94 1 Text "Content" "Font Size" 20 "Min Font Size" 20 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "SelectionState" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 734 OffsetLeft 734 PositionY 0 OffsetTop 0 SizeX 160 OffsetRight -894 SizeY 32 OffsetBottom -32 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Waiting..." m_fDisabledOpacity 1 } } }
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "SelectionState" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 540 OffsetLeft 540 PositionY 0 OffsetTop 0 SizeX 120 OffsetRight -660 SizeY 32 OffsetBottom -32 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Waiting..." } } }
|
||||||
TextWidgetClass { Name "PackState" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 36 OffsetTop 36 SizeX 900 OffsetRight -900 SizeY 66 OffsetBottom -102 } Color 0.9 0.92 0.94 1 Text "Waiting for server settings..." "Font Size" 18 "Min Font Size" 18 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "Enable" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 670 OffsetLeft 670 PositionY 0 OffsetTop 0 SizeX 108 OffsetRight -778 SizeY 32 OffsetBottom -32 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Enable" } } }
|
||||||
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "Enable" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 112 OffsetTop 112 SizeX 180 OffsetRight -180 SizeY 42 OffsetBottom -154 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Enable" } } }
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "Disable" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 788 OffsetLeft 788 PositionY 0 OffsetTop 0 SizeX 108 OffsetRight -896 SizeY 32 OffsetBottom -32 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Disable" } } }
|
||||||
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "Disable" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 196 OffsetLeft 196 PositionY 112 OffsetTop 112 SizeX 180 OffsetRight -376 SizeY 42 OffsetBottom -154 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Disable" } } }
|
TextWidgetClass { Name "PackState" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 38 OffsetTop 38 SizeX 900 OffsetRight -900 SizeY 40 OffsetBottom -78 } Color 0.9 0.92 0.94 1 Text "Waiting for server..." "Font Size" 14 "Min Font Size" 14 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
} }
|
} }
|
||||||
} }
|
} }
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
SizeLayoutWidgetClass { Name "ItemWeightRow" WidthOverride 900 HeightOverride 154 Slot LayoutSlot { HorizontalAlign 3 Padding 0 0 0 12 } {
|
SizeLayoutWidgetClass { Name "ItemWeightRow" WidthOverride 900 HeightOverride 104 Slot LayoutSlot { HorizontalAlign 3 Padding 0 0 0 8 } {
|
||||||
FrameWidgetClass { Name "ItemWeightBody" Slot AlignableSlot { HorizontalAlign 3 VerticalAlign 3 } {
|
FrameWidgetClass { Name "ItemWeightBody" Slot AlignableSlot { HorizontalAlign 3 VerticalAlign 3 } {
|
||||||
TextWidgetClass { Name "WeightLabel" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 0 OffsetTop 0 SizeX 900 OffsetRight -900 SizeY 28 OffsetBottom -28 } Color 0.9 0.92 0.94 1 Text "Item" "Font Size" 17 "Min Font Size" 17 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
TextWidgetClass { Name "WeightLabel" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 0 OffsetTop 0 SizeX 400 OffsetRight -400 SizeY 48 OffsetBottom -48 } Color 0.9 0.92 0.94 1 Text "Item" "Font Size" 17 "Min Font Size" 17 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
TextWidgetClass { Name "WeightState" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 34 OffsetTop 34 SizeX 900 OffsetRight -900 SizeY 46 OffsetBottom -80 } Color 0.9 0.92 0.94 1 Text "Waiting for server..." "Font Size" 17 "Min Font Size" 17 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
TextWidgetClass { Name "WeightState" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 62 OffsetTop 62 SizeX 900 OffsetRight -900 SizeY 38 OffsetBottom -100 } Color 0.9 0.92 0.94 1 Text "Waiting for server..." "Font Size" 17 "Min Font Size" 17 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
TextWidgetClass { Name "WeightValueLabel" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 0 OffsetLeft 0 PositionY 96 OffsetTop 96 SizeX 280 OffsetRight -280 SizeY 36 OffsetBottom -132 } Color 0.9 0.92 0.94 1 Text "Item weight (0-100)" "Font Size" 17 "Min Font Size" 17 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
TextWidgetClass { Name "WeightValueLabel" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 420 OffsetLeft 420 PositionY 0 OffsetTop 0 SizeX 110 OffsetRight -530 SizeY 20 OffsetBottom -20 } Color 0.9 0.92 0.94 1 Text "Weight (0-100)" "Font Size" 14 "Min Font Size" 14 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||||
ButtonWidgetClass : "{DA43C77A0D1E59F8}UI/layouts/ERR/AdminValue.layout" { Name "WeightValue" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 300 OffsetLeft 300 PositionY 90 OffsetTop 90 SizeX 110 OffsetRight -410 SizeY 42 OffsetBottom -132 } components { SCR_EditBoxComponent "{547290FFBD5B33E9}" { m_sLabel "" } } }
|
ButtonWidgetClass : "{DA43C77A0D1E59F8}UI/layouts/ERR/AdminValue.layout" { Name "WeightValue" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 420 OffsetLeft 420 PositionY 22 OffsetTop 22 SizeX 100 OffsetRight -520 SizeY 36 OffsetBottom -58 } components { SCR_EditBoxComponent "{547290FFBD5B33E9}" { m_sLabel "" } } }
|
||||||
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "WeightApply" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 430 OffsetLeft 430 PositionY 90 OffsetTop 90 SizeX 160 OffsetRight -590 SizeY 42 OffsetBottom -132 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Apply" } } }
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "WeightApply" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 538 OffsetLeft 538 PositionY 22 OffsetTop 22 SizeX 140 OffsetRight -678 SizeY 36 OffsetBottom -58 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Apply" } } }
|
||||||
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "WeightNormal" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 610 OffsetLeft 610 PositionY 90 OffsetTop 90 SizeX 200 OffsetRight -810 SizeY 42 OffsetBottom -132 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Normal (1)" } } }
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "WeightNormal" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 696 OffsetLeft 696 PositionY 22 OffsetTop 22 SizeX 198 OffsetRight -894 SizeY 36 OffsetBottom -58 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Normal (1)" } } }
|
||||||
} }
|
} }
|
||||||
} }
|
} }
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
FrameWidgetClass {
|
||||||
|
Name "ERR_PreparationFailure"
|
||||||
|
Slot FrameWidgetSlot { Anchor 0 0 1 1 OffsetLeft 0 OffsetTop 0 OffsetRight 0 OffsetBottom 0 }
|
||||||
|
{
|
||||||
|
ImageWidgetClass {
|
||||||
|
Name "Background"
|
||||||
|
Slot FrameWidgetSlot { Anchor 0 0 1 1 OffsetLeft 0 OffsetTop 0 OffsetRight 0 OffsetBottom 0 }
|
||||||
|
Color 0.025 0.035 0.04 1
|
||||||
|
}
|
||||||
|
FrameWidgetClass {
|
||||||
|
Name "FailurePanel"
|
||||||
|
Slot FrameWidgetSlot { Anchor 0.5 0.5 0.5 0.5 OffsetLeft 0 OffsetTop 0 SizeX 960 OffsetRight -960 SizeY 620 OffsetBottom -620 Alignment 0.5 0.5 }
|
||||||
|
{
|
||||||
|
TextWidgetClass {
|
||||||
|
Name "FailureTitle"
|
||||||
|
Slot FrameWidgetSlot { Anchor 0 0 1 0 OffsetLeft 0 OffsetTop 0 SizeY 58 OffsetBottom -58 }
|
||||||
|
Color 1 1 1 1
|
||||||
|
Text "Mission could not start"
|
||||||
|
"Font Size" 32
|
||||||
|
"Min Font Size" 32
|
||||||
|
}
|
||||||
|
TextWidgetClass {
|
||||||
|
Name "FailureDescription"
|
||||||
|
Slot FrameWidgetSlot { Anchor 0 0 1 0 OffsetLeft 0 OffsetTop 80 SizeY 400 OffsetBottom -480 }
|
||||||
|
Color 1 1 1 1
|
||||||
|
"Font Size" 22
|
||||||
|
"Min Font Size" 22
|
||||||
|
Wrap 1
|
||||||
|
}
|
||||||
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" {
|
||||||
|
Name "ReturnToMainMenu"
|
||||||
|
Slot FrameWidgetSlot { Anchor 0 0 0 0 OffsetLeft 0 OffsetTop 520 SizeX 400 OffsetRight -400 SizeY 56 OffsetBottom -576 }
|
||||||
|
components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Return to main menu" } }
|
||||||
|
}
|
||||||
|
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" {
|
||||||
|
Name "OpenPauseMenu"
|
||||||
|
Slot FrameWidgetSlot { Anchor 0 0 0 0 OffsetLeft 430 OffsetTop 520 SizeX 400 OffsetRight -830 SizeY 56 OffsetBottom -576 }
|
||||||
|
components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Pause menu (Esc / Back)" } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
MetaFileClass {
|
||||||
|
Name "{28DD8E27B1CF40C5}UI/layouts/ERR/PreparationFailure.layout"
|
||||||
|
Configurations {
|
||||||
|
LayoutResourceClass PC { }
|
||||||
|
LayoutResourceClass HEADLESS : PC { }
|
||||||
|
LayoutResourceClass XBOX_ONE : PC { }
|
||||||
|
LayoutResourceClass XBOX_SERIES : PC { }
|
||||||
|
LayoutResourceClass PS4 : PC { }
|
||||||
|
LayoutResourceClass PS5 : PC { }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,8 +6,8 @@ FrameWidgetClass {
|
|||||||
OffsetTop 0
|
OffsetTop 0
|
||||||
SizeX 900
|
SizeX 900
|
||||||
OffsetRight -900
|
OffsetRight -900
|
||||||
SizeY 240
|
SizeY 520
|
||||||
OffsetBottom -240
|
OffsetBottom -520
|
||||||
Alignment 0.5 0.5
|
Alignment 0.5 0.5
|
||||||
}
|
}
|
||||||
"Z Order" 10
|
"Z Order" 10
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
SizeLayoutWidgetClass {
|
||||||
|
Name "ERR_RoundResults"
|
||||||
|
AllowWidthOverride 1
|
||||||
|
WidthOverride 1100
|
||||||
|
AllowHeightOverride 1
|
||||||
|
HeightOverride 620
|
||||||
|
Slot OverlayWidgetSlot { HorizontalAlign 1 VerticalAlign 1 }
|
||||||
|
components { SCR_GameOverScreenContentUIComponent "{6E81CD1BC83BE245}" { } }
|
||||||
|
{
|
||||||
|
FrameWidgetClass {
|
||||||
|
Name "ResultFrame"
|
||||||
|
Slot OverlayWidgetSlot { HorizontalAlign 3 VerticalAlign 3 }
|
||||||
|
{
|
||||||
|
TextWidgetClass {
|
||||||
|
Name "GameOver_State"
|
||||||
|
Slot FrameWidgetSlot { Anchor 0 0 1 0 OffsetLeft 20 OffsetTop 0 OffsetRight 20 SizeY 52 OffsetBottom -52 }
|
||||||
|
Color 1 1 1 1 "Font Size" 36 "Min Font Size" 36
|
||||||
|
Text "Round ended"
|
||||||
|
FontProperties FontProperties { Font "{EABA4FE9D014CCEF}UI/Fonts/RobotoCondensed/RobotoCondensed_Bold.fnt" }
|
||||||
|
}
|
||||||
|
TextWidgetClass {
|
||||||
|
Name "GameOver_Description"
|
||||||
|
Slot FrameWidgetSlot { Anchor 0 0 1 0 OffsetLeft 20 OffsetTop 58 OffsetRight 20 SizeY 64 OffsetBottom -122 }
|
||||||
|
Color 0.9 0.92 0.94 1 "Font Size" 22 "Min Font Size" 22 Wrap 1
|
||||||
|
FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" }
|
||||||
|
}
|
||||||
|
TextWidgetClass {
|
||||||
|
Name "StatisticsHeading"
|
||||||
|
Slot FrameWidgetSlot { Anchor 0 0 1 0 OffsetLeft 20 OffsetTop 132 OffsetRight 20 SizeY 36 OffsetBottom -168 }
|
||||||
|
Color 0.9 0.8 0.5 1 "Font Size" 26 "Min Font Size" 26 Text "Mission statistics"
|
||||||
|
FontProperties FontProperties { Font "{EABA4FE9D014CCEF}UI/Fonts/RobotoCondensed/RobotoCondensed_Bold.fnt" }
|
||||||
|
}
|
||||||
|
ScrollLayoutWidgetClass {
|
||||||
|
Name "StatisticsScroll"
|
||||||
|
Slot FrameWidgetSlot { Anchor 0 0 1 1 OffsetLeft 20 OffsetTop 178 OffsetRight 20 OffsetBottom 12 }
|
||||||
|
components { SCR_GamepadScrollComponent "{5707100AC31E657E}" { } }
|
||||||
|
{
|
||||||
|
VerticalLayoutWidgetClass {
|
||||||
|
Name "StatisticsContent"
|
||||||
|
Slot AlignableSlot { HorizontalAlign 3 VerticalAlign 0 Padding 0 0 18 0 }
|
||||||
|
{
|
||||||
|
TextWidgetClass {
|
||||||
|
Name "RoundStatistics"
|
||||||
|
Slot LayoutSlot { HorizontalAlign 3 Padding 0 0 0 8 SizeMode Auto }
|
||||||
|
Clipping Ancestor Color 0.9 0.92 0.94 1
|
||||||
|
Text "Waiting for mission statistics..."
|
||||||
|
"Font Size" 20 "Min Font Size" 20 Wrap 1
|
||||||
|
FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user