From f41d8ba2f94cff44b8d449d9d28b4da3a708e851 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=BD=C3=A1=C4=8Dek?= Date: Tue, 20 Oct 2026 18:35:22 +0200 Subject: [PATCH] Add configurable anonymous site markers and record Workshop 117 --- CHANGELOG.md | 6 +- .../Scripts/Game/ERR/00_Core/ERR_AdminMenu.c | 18 ++- .../Game/ERR/00_Core/ERR_AdminService.c | 10 +- .../Game/ERR/00_Core/ERR_DiscoveryMap.c | 43 ++++--- .../Scripts/Game/ERR/00_Core/ERR_GameMode.c | 11 ++ .../Scripts/Game/ERR/00_Core/ERR_Location.c | 3 +- .../Scripts/Game/ERR/00_Core/ERR_Settings.c | 5 +- .../ERR/20_Intel/ERR_IntelAdminProvider.c | 2 + addons/core/UI/layouts/ERR/AdminMenu.layout | 58 ++++++---- tests/test_admin_menu.py | 2 +- tests/test_admin_setting_policy.py | 3 + tests/test_intel_admin.py | 4 +- tests/test_settings_migration.py | 2 +- tests/test_site_map_visibility.py | 105 ++++++++++++++++++ tools/check.py | 2 +- tools/import_settings.py | 3 +- ui/admin_menu/pages/IntelPage.layout.in | 58 ++++++---- 17 files changed, 265 insertions(+), 70 deletions(-) create mode 100644 tests/test_site_map_visibility.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 0466336..4e0c190 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ # Changelog -Latest published Workshop version: **0.0.115** +Latest published Workshop version: **0.0.117** + +## 2026-10-20 — Optional site map hints + +- Added an Intel menu option to show sites as question marks until intel identifies their types. ## 2026-10-20 — Safe intel cleanup diff --git a/addons/core/Scripts/Game/ERR/00_Core/ERR_AdminMenu.c b/addons/core/Scripts/Game/ERR/00_Core/ERR_AdminMenu.c index c6c97cc..955dab2 100644 --- a/addons/core/Scripts/Game/ERR/00_Core/ERR_AdminMenu.c +++ b/addons/core/Scripts/Game/ERR/00_Core/ERR_AdminMenu.c @@ -46,7 +46,7 @@ class ERR_AdminMenu : ChimeraMenuBase m_Controller = SCR_PlayerController.Cast(GetGame().GetPlayerController()); m_Mode = ERR_GameMode.Cast(GetGame().GetGameMode()); if (!m_Controller) { Close(); return; } - array names = {"PlaneOn", "PlaneOff", "Close", "PauseJournal", "JournalDetails", "LatestJournal", "KeepWeaponOn", "KeepWeaponOff", "SelfEpiOn", "SelfEpiOff", "IntelSummary", "RerollIntel", "RandomDay_startTimeMinutes", "Default_viewDistanceMetres", "RestartEmptyOn", "RestartEmptyOff", "RecommendedPlane", "RecommendedWeapons"}; + array names = {"SitesOn", "SitesOff", "PlaneOn", "PlaneOff", "Close", "PauseJournal", "JournalDetails", "LatestJournal", "KeepWeaponOn", "KeepWeaponOff", "SelfEpiOn", "SelfEpiOff", "IntelSummary", "RerollIntel", "RandomDay_startTimeMinutes", "Default_viewDistanceMetres", "RestartEmptyOn", "RestartEmptyOff", "RecommendedPlane", "RecommendedWeapons"}; foreach (string mainButton : ERR_AdminMenuPages.MainButtons()) names.Insert(mainButton); foreach (string pageButton : Navigation()) names.Insert(pageButton); for (int density = 1; density <= 4; density++) names.Insert("GuardEvery_" + density.ToString()); @@ -154,9 +154,11 @@ class ERR_AdminMenu : ChimeraMenuBase } protected int m_KeepWeaponState = -1; protected int m_SelfEpiState = -1; + protected int m_SitesState = -1; protected int m_RestartEmptyState = -1; protected void UpdateActions() { + ERR_SelectionStyle.Pair(GetRootWidget(), "SitesOn", "SitesOff", m_SitesState); ERR_SelectionStyle.Pair(GetRootWidget(), "SelfEpiOn", "SelfEpiOff", m_SelfEpiState); ERR_SelectionStyle.Pair(GetRootWidget(), "KeepWeaponOn", "KeepWeaponOff", m_KeepWeaponState); ERR_SelectionStyle.Pair(GetRootWidget(), "RestartEmptyOn", "RestartEmptyOff", m_RestartEmptyState); @@ -176,6 +178,8 @@ class ERR_AdminMenu : ChimeraMenuBase else if (name == "LatestJournal") button.SetEnabled(m_Ready && m_HQ); else if (name == "RestartEmptyOn") button.SetEnabled(m_Ready && m_RestartEmptyState == 0); else if (name == "RestartEmptyOff") button.SetEnabled(m_Ready && m_RestartEmptyState == 1); + else if (name == "SitesOn") button.SetEnabled(m_Ready && m_SitesState == 0); + else if (name == "SitesOff") button.SetEnabled(m_Ready && m_SitesState == 1); else if (name == "SelfEpiOn") button.SetEnabled(m_Ready && m_SelfEpiState == 0); else if (name == "SelfEpiOff") button.SetEnabled(m_Ready && m_SelfEpiState == 1); else if (name == "KeepWeaponOn") button.SetEnabled(m_Ready && m_KeepWeaponState == 0); @@ -230,6 +234,11 @@ class ERR_AdminMenu : ChimeraMenuBase SetText("Saved_restartOnEmpty", "Saved: " + restartState); continue; } + if (key == "showAllSites") + { + m_SitesState = entry.Substring(divider + 1, entry.Length() - divider - 1).ToInt(); + continue; + } if (key == "selfEpinephrine") { m_SelfEpiState = entry.Substring(divider + 1, entry.Length() - divider - 1).ToInt(); @@ -387,6 +396,13 @@ class ERR_AdminMenu : ChimeraMenuBase m_ClickDelay = 0.3; SetText("Feedback", "Saving restart after last disconnect..."); m_Controller.ERR_AdminSubmit("mission restartempty set " + restartEnabled, true); return; } + if (name == "SitesOn" || name == "SitesOff") + { + if (m_SitesState < 0) return; + string sitesEnabled = "0"; if (name == "SitesOn") sitesEnabled = "1"; + m_ClickDelay = 0.3; SetText("Feedback", "Saving site map visibility..."); + m_Controller.ERR_AdminSubmit("intel sites set " + sitesEnabled, true); return; + } if (name == "SelfEpiOn" || name == "SelfEpiOff") { if (m_SelfEpiState < 0) return; diff --git a/addons/core/Scripts/Game/ERR/00_Core/ERR_AdminService.c b/addons/core/Scripts/Game/ERR/00_Core/ERR_AdminService.c index 8340b8c..fcbae7a 100644 --- a/addons/core/Scripts/Game/ERR/00_Core/ERR_AdminService.c +++ b/addons/core/Scripts/Game/ERR/00_Core/ERR_AdminService.c @@ -28,6 +28,7 @@ class ERR_CommandArguments if (section == "mission" && option == "restartempty") return "restartOnEmpty"; if (section == "mission" && option == "emptyhours") return "emptyRestartHours"; if (section == "mission" && option == "radio") return "radioDefenceSeconds"; + if (section == "intel" && option == "sites") return "showAllSites"; if (section == "intel" && option == "reveal") return "intelRevealChancePercent"; return ""; } @@ -35,8 +36,8 @@ class ERR_CommandArguments { if (key == "aiDoctrine" && text == "current") { value = 0; return true; } if (key == "aiDoctrine" && text == "coordinated") { value = 1; return true; } - if ((key == "aiShadow" || key == "restartOnEmpty") && text == "off") { value = 0; return true; } - if ((key == "aiShadow" || key == "restartOnEmpty") && text == "on") { value = 1; return true; } + if ((key == "aiShadow" || key == "restartOnEmpty" || key == "showAllSites") && text == "off") { value = 0; return true; } + if ((key == "aiShadow" || key == "restartOnEmpty" || key == "showAllSites") && text == "on") { value = 1; return true; } if (key == "startMode" && text == "prison") { value = 0; return true; } if (key == "startMode" && text == "crash") { value = 1; return true; } if (text == "random" && ERR_Settings.Randomizable(key)) { value = -1; return true; } @@ -187,12 +188,13 @@ class ERR_AdminService 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 == "civilianTrafficCount") return "Set 0..6 civilian cars (default 3). Zero disables new traffic. Unclaimed cars retire only far from all players and out of sight; player-entered cars are preserved."; + if (key == "showAllSites") return "0 disabled (default), 1 enabled. Show prepared sites at their positions as question marks; recovered intel identifies their types. Applies immediately to everyone."; if (key == "selfEpinephrine") return "0 disabled (default), 1 enabled. Unconscious players can press Space to use a carried epinephrine injector. Applies immediately; consumes the item and keeps its normal medical effect."; if (key == "keepWeaponOnUnconscious") return "Set 1 to retain the held firearm during unconsciousness and equip it after recovery; 0 keeps normal dropping. Applies to the next knockout."; if (key == "antiAirChancePercent" || key == "antiPersonnelMineChancePercent") return "Set 0..100 percent or Random. Applies to newly equipped enemy soldiers; existing loadouts are not rerolled or replenished."; if (key == "intelRevealChancePercent") - return "Use /esc intel reveal set N (0-100) or random. This is the chance that a first document identifies its location exactly; otherwise it gives a rough location. A second document for that location always identifies it. Changes affect newly recovered documents immediately; known locations and spent documents remain unchanged."; + return "Use /esc intel reveal set N (0-100) or random. This is the chance that a first document identifies its location exactly; otherwise it gives a rough location. A second document for that location always identifies it. With all sites shown, the first document always identifies the type. Changes affect newly recovered documents immediately; known locations and spent documents remain unchanged."; return ""; } protected string RoadblockTiming(string key) @@ -240,6 +242,7 @@ class ERR_AdminService } if (topic == "intel") { + replies.Insert("/esc intel sites show|set on|off; show anonymous site positions and let intel identify their types."); replies.Insert("/esc intel reveal show|set N|help (0-100); affects newly recovered documents immediately."); replies.Insert("AI carrying and reroll commands require the Intel addon."); return; @@ -329,6 +332,7 @@ class ERR_AdminService if (!key.IsEmpty()) { SettingCommand(mode.Settings(), key, parts, replies); + if (key == "showAllSites") mode.PublishSiteVisibility(); return; } } diff --git a/addons/core/Scripts/Game/ERR/00_Core/ERR_DiscoveryMap.c b/addons/core/Scripts/Game/ERR/00_Core/ERR_DiscoveryMap.c index b6731fa..b2a9cc6 100644 --- a/addons/core/Scripts/Game/ERR/00_Core/ERR_DiscoveryMap.c +++ b/addons/core/Scripts/Game/ERR/00_Core/ERR_DiscoveryMap.c @@ -1,10 +1,19 @@ -// A first clue identifies a stable search square, not the exact objective. -// Stable grid centers prevent repeated updates from triangulating the site. +// Normal clues use stable search squares to prevent triangulating the site. +// The optional anonymous-site mode intentionally exposes positions, never types. class ERR_DiscoveryMapRules { + // -1 hidden, 0 anonymous exact position, 1 approximate clue, 2 identified. + static int DisplayKnowledge(int knowledge, bool prepared, bool prison, bool showAll) + { + if (prison || !prepared) return -1; + if (knowledge >= 2) return 2; + if (showAll) return 0; + if (knowledge == 1) return 1; + return -1; + } static int Coordinate(float coordinate, int knowledge) { - if (knowledge >= 2) return Math.Round(coordinate); + if (knowledge == 0 || knowledge >= 2) return Math.Round(coordinate); return Math.Floor(coordinate / 500.0) * 500 + 250; } } @@ -109,12 +118,12 @@ modded class SCR_MapMarkerManagerComponent } } -// Read replicated discovery locally. A server-wide marker list never leaks -// locations which have not been recovered through gameplay. +// Read replicated discovery and the administrator-selected visibility locally. +// Anonymous markers never expose a site label or type before discovery. modded class ERR_LocationComponent { protected ref ERR_DiscoveryMarker m_ERRMarker; - protected int m_ERRShown; + protected int m_ERRShown = -1; protected string m_ERRShownLabel; protected override void EOnInit(IEntity owner) @@ -126,20 +135,24 @@ modded class ERR_LocationComponent protected void ERR_UpdateMarker() { if (!GetGame().GetPlayerController()) return; - if (Kind() == ERR_LocationKind.PRISON || Knowledge() < 1) + ERR_GameMode mode = ERR_GameMode.Get(); + bool showAll = mode && mode.ShowAllSites(); + int shown = ERR_DiscoveryMapRules.DisplayKnowledge(Knowledge(), Prepared(), Kind() == ERR_LocationKind.PRISON, showAll); + if (shown < 0) { SCR_MapMarkerManagerComponent oldManager = SCR_MapMarkerManagerComponent.GetInstance(); if (m_ERRMarker && oldManager) oldManager.ERR_RemoveDiscoveryMarker(m_ERRMarker); else if (m_ERRMarker) m_ERRMarker.OnDelete(); - m_ERRMarker = null; m_ERRShown = 0; m_ERRShownLabel = ""; + m_ERRMarker = null; m_ERRShown = -1; m_ERRShownLabel = ""; return; } if (m_ERRMarker) m_ERRMarker.RefreshPresentation(); - if (Knowledge() == m_ERRShown && Label() == m_ERRShownLabel) return; + if (m_ERRMarker && shown == m_ERRShown && Label() == m_ERRShownLabel) return; SCR_MapMarkerManagerComponent manager = SCR_MapMarkerManagerComponent.GetInstance(); if (!manager || !manager.GetMarkerConfig()) return; string label = "Unknown location (500 m area)"; - if (Knowledge() == 2) + if (shown == 0) label = "Unknown site"; + if (shown == 2) { if (!Label().IsEmpty()) label = Label(); else if (Kind() == ERR_LocationKind.RADIO) label = "Radio station"; @@ -152,18 +165,18 @@ modded class ERR_LocationComponent { m_ERRMarker = new ERR_DiscoveryMarker(); m_ERRMarker.SetType(SCR_EMapMarkerType.PLACED_CUSTOM); - m_ERRMarker.SetDiscoveryPosition(Position(), Knowledge()); + m_ERRMarker.SetDiscoveryPosition(Position(), shown); m_ERRMarker.SetMarkerConfigID(0); - m_ERRMarker.SetLocationIcon(Kind(), Id(), Knowledge()); + m_ERRMarker.SetLocationIcon(Kind(), Id(), shown); m_ERRMarker.SetCustomText(label); manager.ERR_InsertDiscoveryMarker(m_ERRMarker); } - m_ERRMarker.SetDiscoveryPosition(Position(), Knowledge()); - m_ERRMarker.SetLocationIcon(Kind(), Id(), Knowledge()); + m_ERRMarker.SetDiscoveryPosition(Position(), shown); + m_ERRMarker.SetLocationIcon(Kind(), Id(), shown); m_ERRMarker.SetCustomText(label); // The objective presentation performs its own normal viewport culling. m_ERRMarker.RefreshPresentation(); - m_ERRShown = Knowledge(); + m_ERRShown = shown; m_ERRShownLabel = Label(); } protected override void OnDelete(IEntity owner) diff --git a/addons/core/Scripts/Game/ERR/00_Core/ERR_GameMode.c b/addons/core/Scripts/Game/ERR/00_Core/ERR_GameMode.c index ce739c4..8568b1b 100644 --- a/addons/core/Scripts/Game/ERR/00_Core/ERR_GameMode.c +++ b/addons/core/Scripts/Game/ERR/00_Core/ERR_GameMode.c @@ -26,6 +26,16 @@ class ERR_GameMode : SCR_BaseGameMode m_SelectedPlayerFaction = player; m_SelectedEnemyFaction = enemy; Replication.BumpMe(); } protected ref ERR_Settings m_Settings; + [RplProp()] protected bool m_ShowAllSites; + bool ShowAllSites() { return m_ShowAllSites; } + void PublishSiteVisibility() + { + if (!Replication.IsServer() || !m_Settings || m_Stopped || m_NativeEndHandled) return; + bool enabled = m_Settings.Get("showAllSites") == 1; + if (m_ShowAllSites == enabled) return; + m_ShowAllSites = enabled; + Replication.BumpMe(); + } [RplProp()] protected int m_StartMode; protected bool m_StartModeFrozen; protected bool m_StartPrepared; @@ -498,6 +508,7 @@ class ERR_GameMode : SCR_BaseGameMode if (!Replication.IsServer()) return; m_EmptySince = System.GetTickCount(); m_Settings = new ERR_Settings(SettingsPath()); + PublishSiteVisibility(); if (!System.IsCLIParam("errNoPerf")) m_Performance = new ERR_Performance(); PublishViewDistance(); m_ContentSettings = new ERR_ContentSettings(); diff --git a/addons/core/Scripts/Game/ERR/00_Core/ERR_Location.c b/addons/core/Scripts/Game/ERR/00_Core/ERR_Location.c index 82e4a7e..e919105 100644 --- a/addons/core/Scripts/Game/ERR/00_Core/ERR_Location.c +++ b/addons/core/Scripts/Game/ERR/00_Core/ERR_Location.c @@ -50,10 +50,11 @@ class ERR_LocationComponent : ScriptComponent bool Discover(int roll, int chance) { if (!Replication.IsServer() || !m_Prepared || m_Knowledge >= 2) return false; + ERR_GameMode mode = ERR_GameMode.Get(); + if (mode && mode.ShowAllSites()) chance = 100; int previous = m_Knowledge; m_Knowledge = ERR_MissionRules.Discovery(m_Knowledge, roll, chance); Replication.BumpMe(); - ERR_GameMode mode = ERR_GameMode.Get(); if (mode) mode.LocationDiscovered(this, previous, m_Knowledge); return true; } diff --git a/addons/core/Scripts/Game/ERR/00_Core/ERR_Settings.c b/addons/core/Scripts/Game/ERR/00_Core/ERR_Settings.c index 602bd7c..5da208c 100644 --- a/addons/core/Scripts/Game/ERR/00_Core/ERR_Settings.c +++ b/addons/core/Scripts/Game/ERR/00_Core/ERR_Settings.c @@ -9,7 +9,7 @@ class ERR_Settings static array Keys() { - return {"aiDoctrine", "aiShadow", "sarkaAngerPerCar", "motorbikeCount", "planeAltitudeMetres", "planeReplacementSeconds", "planeSectorSeconds", "planeReconEnabled", "fieldCampCount", "fieldHospitalCount", "startWeaponCount", "startMagazineCount", "objectGuardCount", "objectGuardEvery", "objectGuardHQMode", "hunterSpawnDistanceMetres", "roadblockCount", "civilianTrafficCount", "startMode", "keepWeaponOnUnconscious", "selfEpinephrine", "restartOnEmpty", "emptyRestartHours", "reportIntervalSeconds", "supportSquadLimit", "responseRadiusMetres", "aiIntelChancePercent", "intelRevealChancePercent", "radioDefenceSeconds", "startTimeMinutes", "timeMultiplier", "startDateCode", "weatherProfile", "viewDistanceMetres", "antiAirChancePercent", "antiPersonnelMineChancePercent", "automaticEquipmentChancePercent", "automaticConvoyEnabled", "convoyCount", "heliTimerMinutes"}; + return {"aiDoctrine", "aiShadow", "sarkaAngerPerCar", "motorbikeCount", "planeAltitudeMetres", "planeReplacementSeconds", "planeSectorSeconds", "planeReconEnabled", "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"}; } static bool Valid(string key, int value) @@ -32,6 +32,7 @@ class ERR_Settings if (key == "roadblockCount") return value >= 0 && value <= 30; if (key == "civilianTrafficCount") return value >= 0 && value <= 6; if (key == "startMode") return value == 0 || value == 1; + if (key == "showAllSites") return value == 0 || value == 1; if (key == "selfEpinephrine" || key == "keepWeaponOnUnconscious" || key == "restartOnEmpty") return value == 0 || value == 1; if (key == "emptyRestartHours") return value >= 0 && value <= 168; if (key == "heliTimerMinutes") return value >= 1 && value <= 1440; @@ -134,6 +135,7 @@ class ERR_Settings if (key == "hunterSpawnDistanceMetres") return 1000; if (key == "roadblockCount") return 4; if (key == "civilianTrafficCount") return 3; + if (key == "showAllSites") return 0; if (key == "startMode" || key == "selfEpinephrine") return 0; if (key == "keepWeaponOnUnconscious" || key == "restartOnEmpty") return 1; if (key == "emptyRestartHours") return 5; @@ -180,6 +182,7 @@ class ERR_Settings m_Values.Set("responseRadiusMetres", 2000); m_Values.Set("aiIntelChancePercent", 60); m_Values.Set("intelRevealChancePercent", 15); + m_Values.Set("showAllSites", AddedDefault("showAllSites")); m_Values.Set("radioDefenceSeconds", 180); m_Values.Set("startTimeMinutes", AddedDefault("startTimeMinutes")); m_Values.Set("timeMultiplier", AddedDefault("timeMultiplier")); diff --git a/addons/core/Scripts/Game/ERR/20_Intel/ERR_IntelAdminProvider.c b/addons/core/Scripts/Game/ERR/20_Intel/ERR_IntelAdminProvider.c index 8f5dc22..da71b19 100644 --- a/addons/core/Scripts/Game/ERR/20_Intel/ERR_IntelAdminProvider.c +++ b/addons/core/Scripts/Game/ERR/20_Intel/ERR_IntelAdminProvider.c @@ -13,6 +13,7 @@ class ERR_IntelAdminProvider : ERR_AdminProvider if (arguments == "intel show") { replies.Insert("aiIntelChancePercent=" + mode.Settings().Get("aiIntelChancePercent").ToString()); + replies.Insert("showAllSites=" + mode.Settings().Get("showAllSites").ToString()); replies.Insert("intelRevealChancePercent=" + mode.Settings().Get("intelRevealChancePercent").ToString()); return true; } @@ -30,6 +31,7 @@ class ERR_IntelAdminProvider : ERR_AdminProvider override bool Help(string topic, array replies) { if (topic != "intel") return false; + 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("Carrying changes affect newly registered AI. /esc intel reroll applies to eligible living AI; intel reroll show reports progress."); replies.Insert("/esc intel reveal show|set N|help (0-100); newly recovered documents change, known locations and spent documents remain unchanged."); diff --git a/addons/core/UI/layouts/ERR/AdminMenu.layout b/addons/core/UI/layouts/ERR/AdminMenu.layout index 8c561b1..441d4ed 100644 --- a/addons/core/UI/layouts/ERR/AdminMenu.layout +++ b/addons/core/UI/layouts/ERR/AdminMenu.layout @@ -630,30 +630,30 @@ 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 } { TextWidgetClass { - Name "IntelHelp" 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 "Control how often soldiers carry intel and whether the first clue reveals an exact location." "Font Size" 19 "Min Font Size" 19 Wrap 1 + 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 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } } TextWidgetClass { - Name "Label_aiIntelChancePercent" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 80 OffsetTop 80 SizeX 368 OffsetRight -396 SizeY 28 OffsetBottom -108 } + Name "Label_aiIntelChancePercent" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 150 OffsetTop 150 SizeX 368 OffsetRight -396 SizeY 28 OffsetBottom -178 } Color 0.9 0.92 0.94 1 Text "AI carrying intel (0-100%)" "Font Size" 20 "Min Font Size" 20 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } } TextWidgetClass { - Name "Saved_aiIntelChancePercent" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 112 OffsetTop 112 SizeX 936 OffsetRight -964 SizeY 24 OffsetBottom -136 } + Name "Saved_aiIntelChancePercent" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 194 OffsetTop 194 SizeX 936 OffsetRight -964 SizeY 20 OffsetBottom -214 } 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_aiIntelChancePercent" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 136 OffsetTop 136 SizeX 936 OffsetRight -964 SizeY 20 OffsetBottom -156 } + Name "Help_aiIntelChancePercent" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 218 OffsetTop 218 SizeX 936 OffsetRight -964 SizeY 32 OffsetBottom -250 } Color 0.72 0.81 0.87 1 Text "Chance for each new enemy soldier to carry intel. Reassign carried intel updates existing soldiers." "Font Size" 16 "Min Font Size" 16 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } } ButtonWidgetClass : "{DA43C77A0D1E59F8}UI/layouts/ERR/AdminValue.layout" { - Name "aiIntelChancePercent" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 408 OffsetLeft 408 PositionY 80 OffsetTop 80 SizeX 240 OffsetRight -648 SizeY 42 OffsetBottom -122 } + Name "aiIntelChancePercent" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 408 OffsetLeft 408 PositionY 150 OffsetTop 150 SizeX 240 OffsetRight -648 SizeY 42 OffsetBottom -192 } components { SCR_EditBoxComponent "{547290FFBD5B33E9}" { m_sLabel "" @@ -661,7 +661,7 @@ ButtonWidgetClass : "{DA43C77A0D1E59F8}UI/layouts/ERR/AdminValue.layout" { } } ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { - Name "Apply_aiIntelChancePercent" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 660 OffsetLeft 660 PositionY 80 OffsetTop 80 SizeX 112 OffsetRight -772 SizeY 42 OffsetBottom -122 } + Name "Apply_aiIntelChancePercent" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 660 OffsetLeft 660 PositionY 150 OffsetTop 150 SizeX 112 OffsetRight -772 SizeY 42 OffsetBottom -192 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Apply" @@ -669,7 +669,7 @@ ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_But } } ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { - Name "Random_aiIntelChancePercent" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 784 OffsetLeft 784 PositionY 80 OffsetTop 80 SizeX 180 OffsetRight -964 SizeY 42 OffsetBottom -122 } + Name "Random_aiIntelChancePercent" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 784 OffsetLeft 784 PositionY 150 OffsetTop 150 SizeX 180 OffsetRight -964 SizeY 42 OffsetBottom -192 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Random" @@ -677,25 +677,25 @@ ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_But } } TextWidgetClass { - Name "Label_intelRevealChancePercent" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 168 OffsetTop 168 SizeX 368 OffsetRight -396 SizeY 28 OffsetBottom -196 } + Name "Label_intelRevealChancePercent" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 252 OffsetTop 252 SizeX 368 OffsetRight -396 SizeY 28 OffsetBottom -280 } Color 0.9 0.92 0.94 1 Text "First-document exact reveal (0-100%)" "Font Size" 20 "Min Font Size" 20 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } } TextWidgetClass { - Name "Saved_intelRevealChancePercent" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 200 OffsetTop 200 SizeX 936 OffsetRight -964 SizeY 24 OffsetBottom -224 } + Name "Saved_intelRevealChancePercent" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 296 OffsetTop 296 SizeX 936 OffsetRight -964 SizeY 20 OffsetBottom -316 } 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_intelRevealChancePercent" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 224 OffsetTop 224 SizeX 936 OffsetRight -964 SizeY 20 OffsetBottom -244 } - Color 0.72 0.81 0.87 1 Text "First clue: chance to reveal the exact site. Otherwise a question mark shows an approximate area." "Font Size" 16 "Min Font Size" 16 Wrap 1 + Name "Help_intelRevealChancePercent" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 320 OffsetTop 320 SizeX 936 OffsetRight -964 SizeY 36 OffsetBottom -356 } + Color 0.72 0.81 0.87 1 Text "With all sites shown, intel always identifies the type. Otherwise this chance controls exact versus approximate clues." "Font Size" 16 "Min Font Size" 16 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } } ButtonWidgetClass : "{DA43C77A0D1E59F8}UI/layouts/ERR/AdminValue.layout" { - Name "intelRevealChancePercent" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 408 OffsetLeft 408 PositionY 168 OffsetTop 168 SizeX 240 OffsetRight -648 SizeY 42 OffsetBottom -210 } + Name "intelRevealChancePercent" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 408 OffsetLeft 408 PositionY 252 OffsetTop 252 SizeX 240 OffsetRight -648 SizeY 42 OffsetBottom -294 } components { SCR_EditBoxComponent "{547290FFBD5B33E9}" { m_sLabel "" @@ -703,7 +703,7 @@ ButtonWidgetClass : "{DA43C77A0D1E59F8}UI/layouts/ERR/AdminValue.layout" { } } ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { - Name "Apply_intelRevealChancePercent" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 660 OffsetLeft 660 PositionY 168 OffsetTop 168 SizeX 112 OffsetRight -772 SizeY 42 OffsetBottom -210 } + Name "Apply_intelRevealChancePercent" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 660 OffsetLeft 660 PositionY 252 OffsetTop 252 SizeX 112 OffsetRight -772 SizeY 42 OffsetBottom -294 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Apply" @@ -711,7 +711,7 @@ ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_But } } ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { - Name "Random_intelRevealChancePercent" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 784 OffsetLeft 784 PositionY 168 OffsetTop 168 SizeX 180 OffsetRight -964 SizeY 42 OffsetBottom -210 } + Name "Random_intelRevealChancePercent" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 784 OffsetLeft 784 PositionY 252 OffsetTop 252 SizeX 180 OffsetRight -964 SizeY 42 OffsetBottom -294 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Random" @@ -719,7 +719,7 @@ ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_But } } ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { - Name "IntelSummary" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 276 OffsetTop 276 SizeX 260 OffsetRight -288 SizeY 42 OffsetBottom -318 } + Name "IntelSummary" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 364 OffsetTop 364 SizeX 260 OffsetRight -288 SizeY 42 OffsetBottom -406 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Intel summary" @@ -727,7 +727,7 @@ ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_But } } ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { - Name "RerollIntel" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 306 OffsetLeft 306 PositionY 276 OffsetTop 276 SizeX 260 OffsetRight -566 SizeY 42 OffsetBottom -318 } + Name "RerollIntel" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 306 OffsetLeft 306 PositionY 364 OffsetTop 364 SizeX 260 OffsetRight -566 SizeY 42 OffsetBottom -406 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Reassign carried intel" @@ -735,15 +735,31 @@ ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_But } } TextWidgetClass { - Name "IntelRerollHelp" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 336 OffsetTop 336 SizeX 936 OffsetRight -964 SizeY 52 OffsetBottom -388 } - Color 0.9 0.92 0.94 1 Text "Reassign carried intel uses the saved chance on existing AI. Intel already collected stays revealed." "Font Size" 19 "Min Font Size" 19 Wrap 1 + Name "IntelRerollHelp" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 420 OffsetTop 420 SizeX 936 OffsetRight -964 SizeY 26 OffsetBottom -446 } + Color 0.9 0.92 0.94 1 Text "Reassign carried intel updates existing AI. Collected intel stays revealed." "Font Size" 16 "Min Font Size" 16 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } } TextWidgetClass { - Name "EvidenceHelp" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 402 OffsetTop 402 SizeX 936 OffsetRight -964 SizeY 60 OffsetBottom -462 } - Color 0.9 0.92 0.94 1 Text "Enemy bodies stay until a recovery team searches them and reports to HQ. Cleanup is then enabled with a five-minute delay." "Font Size" 19 "Min Font Size" 19 Wrap 1 + Name "EvidenceHelp" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 454 OffsetTop 454 SizeX 936 OffsetRight -964 SizeY 28 OffsetBottom -482 } + Color 0.9 0.92 0.94 1 Text "Enemy bodies remain until searched and reported, then become eligible for cleanup after five minutes." "Font Size" 16 "Min Font Size" 16 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } } +TextWidgetClass { + Name "Label_showAllSites" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 58 OffsetTop 58 SizeX 550 OffsetRight -578 SizeY 32 OffsetBottom -90 } + Color 0.9 0.92 0.94 1 Text "Show all sites as question marks" "Font Size" 20 "Min Font Size" 20 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } +} +TextWidgetClass { + Name "Help_showAllSites" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 102 OffsetTop 102 SizeX 936 OffsetRight -964 SizeY 40 OffsetBottom -142 } + Color 0.72 0.81 0.87 1 Text "Show prepared site positions without names or types. Intel identifies each site. Applies now for everyone; disabled by default." "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 "SitesOn" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 660 OffsetLeft 660 PositionY 54 OffsetTop 54 SizeX 146 OffsetRight -806 SizeY 42 OffsetBottom -96 } + components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Enabled" } } +} +ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { + Name "SitesOff" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 818 OffsetLeft 818 PositionY 54 OffsetTop 54 SizeX 146 OffsetRight -964 SizeY 42 OffsetBottom -96 } + 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 } { TextWidgetClass { diff --git a/tests/test_admin_menu.py b/tests/test_admin_menu.py index a613944..d45e1b2 100644 --- a/tests/test_admin_menu.py +++ b/tests/test_admin_menu.py @@ -12,7 +12,7 @@ navigation = source.with_name('ERR_AdminMenuPages.c') layout = BASE / 'addons/core/UI/layouts/ERR/AdminMenu.layout' keys = re.findall(r'"([A-Za-z]+)"', body(fields, 'static array Keys(')) settings = re.findall(r'"([A-Za-z]+)"', body(BASE / 'addons/core/Scripts/Game/ERR/00_Core/ERR_Settings.c', 'static array Keys(')) -assert len(keys) == len(set(keys)) and set(settings) - set(keys) == {"automaticConvoyEnabled", "keepWeaponOnUnconscious", "selfEpinephrine", "planeReconEnabled", "restartOnEmpty"} +assert len(keys) == len(set(keys)) and set(settings) - set(keys) == {"automaticConvoyEnabled", "keepWeaponOnUnconscious", "selfEpinephrine", "planeReconEnabled", "restartOnEmpty", "showAllSites"} assert set(keys) <= set(settings) # Every menu field must have a server setting. # Native layouts place child widget declarations inside an anonymous Children # block. A missing brace can preserve most widget names yet prevent menu load. diff --git a/tests/test_admin_setting_policy.py b/tests/test_admin_setting_policy.py index ccd5cce..a11f478 100644 --- a/tests/test_admin_setting_policy.py +++ b/tests/test_admin_setting_policy.py @@ -45,6 +45,9 @@ int main(){ assert(Valid("fieldHospitalCount",1)&&Valid("fieldHospitalCount",6)&&Valid("fieldHospitalCount",8)); assert(!Valid("fieldHospitalCount",0)&&!Valid("fieldHospitalCount",9)); + assert(CoreSetting("intel","sites")=="showAllSites"); + assert(Valid("showAllSites",0)&&Valid("showAllSites",1)); + assert(!Valid("showAllSites",-1)&&!Valid("showAllSites",2)); assert(CoreSetting("intel","reveal")=="intelRevealChancePercent"); assert(CoreSetting("mission","radio")=="radioDefenceSeconds"); assert(CoreSetting("mission","objectguards")=="objectGuardCount"); diff --git a/tests/test_intel_admin.py b/tests/test_intel_admin.py index f4fd0a3..3dff782 100644 --- a/tests/test_intel_admin.py +++ b/tests/test_intel_admin.py @@ -26,7 +26,7 @@ template struct Array:std::vector{void Insert(T v){this->push_back(v template struct Set:std::set{bool Contains(T v){return this->count(v);}void Insert(T v){this->insert(v);}int Count(){return this->size();}}; namespace Replication { bool server=true; bool IsServer(){return server;} } struct Number {int n;string ToString(){return std::to_string(n);}}; -struct Settings {Number Get(string key){return {key=="aiIntelChancePercent"?60:15};}}; +struct Settings {Number Get(string key){return {key=="aiIntelChancePercent"?60:(key=="showAllSites"?0:15)};}}; struct Intel {int starts=0;bool canStart=true;bool BeginReroll(){starts++;return canStart;}string RerollStatus(){return "batch status";}}; struct Journal {int Sequence(){return 0;}void Read(int,Array&,int&) {}}; struct HQ {struct Journal journal;static HQ* Cast(HQ* h){return h;}string Status(){return "hq";}struct Journal* Journal(){return &journal;}}; @@ -40,7 +40,7 @@ struct Ledger {Set m_Spent; }; int main(){ Admin admin;Mode mode;Array replies;Intel intel;mode.intel=&intel; - assert(admin.Extension(&mode,"intel show",replies));assert(replies.size()==2&&replies[0]=="aiIntelChancePercent=60"&&replies[1]=="intelRevealChancePercent=15"&&intel.starts==0); + assert(admin.Extension(&mode,"intel show",replies));assert(replies.size()==3&&replies[0]=="aiIntelChancePercent=60"&&replies[1]=="showAllSites=0"&&replies[2]=="intelRevealChancePercent=15"&&intel.starts==0); replies.clear();assert(admin.Extension(&mode,"intel reroll show",replies));assert(intel.starts==0&&replies.size()==1&&replies[0]=="batch status"); replies.clear();assert(admin.Extension(&mode,"intel reroll",replies));assert(intel.starts==1&&replies.size()==1); intel.canStart=false;replies.clear();assert(admin.Extension(&mode,"intel reroll",replies));assert(intel.starts==2&&replies.size()==2&&replies[1]=="batch status"); diff --git a/tests/test_settings_migration.py b/tests/test_settings_migration.py index e6966d2..905ecaf 100644 --- a/tests/test_settings_migration.py +++ b/tests/test_settings_migration.py @@ -5,7 +5,7 @@ from policy_harness import ROOT,body,run spec=importlib.util.spec_from_file_location('import_settings',BASE/'tools/import_settings.py') m=importlib.util.module_from_spec(spec);spec.loader.exec_module(m) result,deferred=m.merge({'configVersion':1,'reportIntervalSeconds':20.0,'supportSquadLimit':2,'responseRadiusMetres':1500,'convoyCount':3},{'aiIntelChancePercent':0,'intelRevealChancePercent':100}) -assert result==dict(aiDoctrine=0,aiShadow=0,planeAltitudeMetres=250,planeReplacementSeconds=60,planeSectorSeconds=120,planeReconEnabled=1,roadblockCount=4,civilianTrafficCount=3,startMode=0,keepWeaponOnUnconscious=1,selfEpinephrine=0,restartOnEmpty=1,emptyRestartHours=5,reportIntervalSeconds=20,supportSquadLimit=2,responseRadiusMetres=1500,aiIntelChancePercent=0,intelRevealChancePercent=100,radioDefenceSeconds=180,startTimeMinutes=840,timeMultiplier=1,startDateCode=0,weatherProfile=0,viewDistanceMetres=0,automaticEquipmentChancePercent=25,antiAirChancePercent=7,antiPersonnelMineChancePercent=60,automaticConvoyEnabled=1,convoyCount=3,heliTimerMinutes=15) +assert result==dict(aiDoctrine=0,aiShadow=0,planeAltitudeMetres=250,planeReplacementSeconds=60,planeSectorSeconds=120,planeReconEnabled=1,roadblockCount=4,civilianTrafficCount=3,startMode=0,keepWeaponOnUnconscious=1,selfEpinephrine=0,restartOnEmpty=1,emptyRestartHours=5,reportIntervalSeconds=20,supportSquadLimit=2,responseRadiusMetres=1500,aiIntelChancePercent=0,intelRevealChancePercent=100,showAllSites=0,radioDefenceSeconds=180,startTimeMinutes=840,timeMultiplier=1,startDateCode=0,weatherProfile=0,viewDistanceMetres=0,automaticEquipmentChancePercent=25,antiAirChancePercent=7,antiPersonnelMineChancePercent=60,automaticConvoyEnabled=1,convoyCount=3,heliTimerMinutes=15) assert deferred==[] for count in (0,1,3,20): values,notes=m.merge(ai={'convoyCount':count}) diff --git a/tests/test_site_map_visibility.py b/tests/test_site_map_visibility.py new file mode 100644 index 0000000..967695d --- /dev/null +++ b/tests/test_site_map_visibility.py @@ -0,0 +1,105 @@ +"""Run production marker transitions and discovery with engine boundary doubles.""" +from policy_harness import ROOT, body, run +base = ROOT/'addons/core/Scripts/Game/ERR/00_Core' +map_source = base/'ERR_DiscoveryMap.c' +rules = '\n'.join(body(map_source, s) for s in ('static int DisplayKnowledge(', 'static int Coordinate(')).replace('Math.Round','std::round').replace('Math.Floor','std::floor') +update = body(map_source, 'protected void ERR_UpdateMarker(').replace('protected ', '') +discover = body(base/'ERR_Location.c', 'bool Discover(') +icon = body(map_source, 'void SetLocationIcon(') +for name in ('ERR_GameMode', 'SCR_MapMarkerManagerComponent', 'ERR_DiscoveryMapRules', 'ERR_LocationKind', 'SCR_EMapMarkerType', 'Replication', 'ERR_MissionRules'): + update = update.replace(name+'.', name+'::') + discover = discover.replace(name+'.', name+'::') +for typename in ('ERR_GameMode', 'SCR_MapMarkerManagerComponent'): + update = update.replace(typename+' ', typename+'* ') +discover = discover.replace('ERR_GameMode mode', 'ERR_GameMode* mode') +for name in ('mode', 'manager', 'oldManager', 'm_ERRMarker'): + update = update.replace(name+'.',name+'->') +discover = discover.replace('mode.', 'mode->') +update = update.replace('GetGame().','GetGame()->').replace('null','nullptr') +mission = body(base/'ERR_MissionRules.c', 'static int Discovery(') +run(r''' +#include +#include +#include +#include +struct string:std::string{using std::string::string;string(std::string s):std::string(s){}bool IsEmpty(){return empty();}}; +namespace ERR_LocationKind {enum Kind{PRISON,RADIO,EXTRACTION,OUTPOST,DEPOT,MORTAR,HOSPITAL};} +namespace SCR_EMapMarkerType {constexpr int PLACED_CUSTOM=0;} +struct Game{bool controller=true;bool GetPlayerController(){return controller;}}game; +Game* GetGame(){return &game;} +struct Replication{static inline bool server=true;static bool IsServer(){return server;}static void BumpMe(){}}; +struct ERR_GameMode{bool enabled=false;int events=0;static ERR_GameMode* Get();bool ShowAllSites(){return enabled;}void LocationDiscovered(void*,int,int){events++;}}mode; +ERR_GameMode* ERR_GameMode::Get(){return &mode;} +struct ERR_MissionRules {'''+mission+r'''}; +struct ERR_DiscoveryMapRules {'''+rules+r'''}; +struct ERR_DiscoveryMarker{ + string label,m_Icon;int x=0,refreshes=0;bool removed=false; + static string Icon(int kind,string){return kind==ERR_LocationKind::RADIO?"radio":"supplies";} + void ApplyIcon(){}void SetType(int){}void SetMarkerConfigID(int){} + void SetCustomText(string v){label=v;}void RefreshPresentation(){refreshes++;} + void SetDiscoveryPosition(float p,int knowledge){x=ERR_DiscoveryMapRules::Coordinate(p,knowledge);} + void OnDelete(){removed=true;} +'''+icon.replace('ERR_LocationKind kind','int kind')+r''' +}; +struct SCR_MapMarkerManagerComponent{ + static inline bool ready=true;int inserts=0,removes=0; + static SCR_MapMarkerManagerComponent* GetInstance(); + bool GetMarkerConfig(){return ready;} + void ERR_InsertDiscoveryMarker(ERR_DiscoveryMarker*){inserts++;} + void ERR_RemoveDiscoveryMarker(ERR_DiscoveryMarker* marker){removes++;delete marker;} +}manager; +SCR_MapMarkerManagerComponent* SCR_MapMarkerManagerComponent::GetInstance(){return &manager;} +struct Location{ + ERR_DiscoveryMarker* m_ERRMarker=nullptr;int m_ERRShown=-1,m_Knowledge=0;string m_ERRShownLabel; + bool m_Prepared=true;int kind=ERR_LocationKind::RADIO; + int Knowledge(){return m_Knowledge;}bool Prepared(){return m_Prepared;}int Kind(){return kind;} + string Label(){return "Secret radio station";}string Id(){return "secret-radio-id";}float Position(){return 6123.25f;} +'''+update+'\n'+discover+r''' + ~Location(){delete m_ERRMarker;} +}; +int main(){ + Location site;site.ERR_UpdateMarker();assert(!site.m_ERRMarker&&site.m_Knowledge==0); + mode.enabled=true;site.ERR_UpdateMarker();assert(site.m_ERRMarker); + assert(site.m_ERRMarker->label=="Unknown site"&&site.m_ERRMarker->m_Icon=="mark-question"&&site.m_ERRMarker->x==6123); + assert(site.m_Knowledge==0&&mode.events==0); // Visibility never grants discovery or objective progress. + auto* first=site.m_ERRMarker;site.ERR_UpdateMarker();assert(site.m_ERRMarker==first&&manager.inserts==1); + mode.enabled=false;site.ERR_UpdateMarker();assert(!site.m_ERRMarker&&manager.removes==1); + mode.enabled=true;site.ERR_UpdateMarker();assert(site.m_ERRMarker&&manager.inserts==2); + assert(site.Discover(99,0)); // An actual clue identifies even when normal exact chance is zero. + site.ERR_UpdateMarker();assert(site.Knowledge()==2&&mode.events==1); + assert(site.m_ERRMarker->label=="Secret radio station"&&site.m_ERRMarker->m_Icon=="radio"); + mode.enabled=false;site.ERR_UpdateMarker();assert(site.m_ERRMarker&&site.m_ERRMarker->x==6123); + assert(!site.Discover(0,100)&&mode.events==1); + Location rough;assert(rough.Discover(99,0)&&rough.Knowledge()==1);rough.ERR_UpdateMarker(); + assert(rough.m_ERRMarker->label=="Unknown location (500 m area)"&&rough.m_ERRMarker->x==6250); + mode.enabled=true;rough.ERR_UpdateMarker();assert(rough.Knowledge()==1&&rough.m_ERRMarker->x==6123&&rough.m_ERRMarker->m_Icon=="mark-question"); + mode.enabled=false;rough.ERR_UpdateMarker();assert(rough.m_ERRMarker->x==6250); + mode.enabled=true;assert(rough.Discover(99,0)&&rough.Knowledge()==2); + Location pending;pending.m_Prepared=false;pending.ERR_UpdateMarker();assert(!pending.m_ERRMarker&&!pending.Discover(0,100)); + pending.m_Prepared=true;pending.ERR_UpdateMarker();assert(pending.m_ERRMarker); + pending.m_Prepared=false;pending.ERR_UpdateMarker();assert(!pending.m_ERRMarker); + Location prison;prison.kind=ERR_LocationKind::PRISON;prison.ERR_UpdateMarker();assert(!prison.m_ERRMarker); + Location late;manager.ready=false;late.ERR_UpdateMarker();assert(!late.m_ERRMarker); + manager.ready=true;late.ERR_UpdateMarker();assert(late.m_ERRMarker&&late.m_ERRMarker->m_Icon=="mark-question"); + Replication::server=false;assert(!late.Discover(0,100)&&late.Knowledge()==0); + for(int kind=ERR_LocationKind::RADIO;kind<=ERR_LocationKind::HOSPITAL;kind++){ + Location other;other.kind=kind;other.ERR_UpdateMarker();assert(other.m_ERRMarker&&other.m_ERRMarker->label=="Unknown site"&&other.m_ERRMarker->m_Icon=="mark-question"); + } +} +''') +# Persisted authority and runtime presentation wiring (including late-join replication). +settings=base/'ERR_Settings.c' +assert '"showAllSites"' in body(settings,'static array Keys(') +assert 'if (key == "showAllSites") return 0;' in body(settings,'static int AddedDefault(') +assert 'showAllSites' not in body(settings,'static bool Randomizable(') +mode_source=(base/'ERR_GameMode.c').read_text() +assert '[RplProp()] protected bool m_ShowAllSites;' in mode_source +assert 'PublishSiteVisibility();' in body(base/'ERR_GameMode.c','protected override void EOnInit(') +publish=body(base/'ERR_GameMode.c','void PublishSiteVisibility(') +assert '!Replication.IsServer()' in publish and 'Replication.BumpMe()' in publish +admin=body(base/'ERR_AdminService.c','void Execute(') +assert admin.index('!authorized') < admin.index('mode.PublishSiteVisibility()') +assert admin.index('SettingCommand(mode.Settings(), key, parts, replies);') < admin.index('mode.PublishSiteVisibility()') +click=body(base/'ERR_AdminMenu.c','protected void OnButton(') +assert click.index('!m_Ready') < click.index('"intel sites set "') +print('PASS: hidden/anonymous/rough/identified markers, toggle transitions, prepared sites, no identity leakage, intel discovery and replicated authority wiring') diff --git a/tools/check.py b/tools/check.py index d2dd377..4a13b49 100644 --- a/tools/check.py +++ b/tools/check.py @@ -129,7 +129,7 @@ def check(): assert set(mapped)==set(inventory['script_paths']), 'Original inventory has untracked or unknown entries' print(f'PASS: {len(mapped)} original script entries tracked across {len(feature_ids)} unfinished responsibilities') print(f'PASS: {len(ids)} isolated addons, acyclic dependencies, {len(resources)} unique resources, exact vanilla/authorized external references') - for name in ['test_sarka_hunters.py', 'test_optional_motorbikes.py', 'test_server_memory_sampler.py', 'test_roadblocks.py', 'test_roadblock_background.py', 'test_roadblock_layout.py', 'test_roadblock_mining.py', 'test_crash_start.py', 'test_start_settings.py', 'test_start_backpack.py', 'test_intercept_vehicle_footprint.py', 'test_bmp_intercept_geometry.py', 'test_native_random_radius_trace.py', 'test_native_random_radius_runner.py', 'test_native_supply_travel.py', 'test_native_supply_travel_hook.py', 'test_native_supply_travel_runner.py', 'test_native_mounted_intercept.py', 'test_hq_mounted_dispatch.py', 'test_hq_mounted_intercept.py', 'test_hq_mounted_intercept_pair.py', 'test_convoy_intercept_pair.py', 'test_native_maintenance_refuel.py', 'test_mine_intel.py', 'test_mine_intel_discovery_integration.py', 'test_infantry_combat.py', 'test_enemy_medicine.py', 'test_hunter_combat.py', 'test_hunter_stuck.py', 'test_hq_local_awareness.py', 'test_unconscious_controls.py', 'test_self_epinephrine.py', 'test_town_garrisons.py', 'test_field_sites.py', 'test_gameplay_session_regressions.py', 'test_gameplay_review_fixes.py', 'test_server_log_regressions.py', 'test_mortar_load_ack.py', 'test_mortar_spotting.py', 'test_hunter_profiles.py', 'test_hunter_lifecycle.py', 'test_hunter_capture.py', 'test_hunter_opening.py', 'test_hunter_search_memory.py', 'test_hunter_reserve.py', 'test_hunter_sector_search.py', 'test_enemy_awareness.py', 'test_intel_body_inspection.py', 'test_body_recovery_lifecycle.py', 'test_radio_document_defence.py', 'test_air_support_policy.py', 'test_air_logistics_orders.py', 'test_air_recon.py', 'test_scouts.py', 'test_mortar_station.py', 'test_random_settings.py', 'test_debug_ai.py', 'test_generated_sites.py', 'test_terrain_sites.py', 'test_terrain_adapter.py', 'test_terrain_airfields.py','test_terrain_takeover.py', 'test_terrain_presentation.py', 'test_terrain_selection.py', 'test_faction_selection.py', 'test_faction_menu.py', 'test_content_policy.py', 'test_hq_road_intercept.py', 'test_hq_delivered_task.py', 'test_hq_local_handoff.py', 'test_hq_local_handoff_intake.py', 'test_native_local_handoff_probe.py', 'test_infantry_resupply_waypoints.py', 'test_infantry_placement_settle.py', 'test_native_infantry_fixture_lifecycle.py', 'test_native_infantry_cargo.py', 'test_native_infantry_setup.py', 'test_infantry_ammo_access.py', 'test_infantry_resupply_planning.py', 'test_infantry_supply_pool.py', 'test_hq_multiconvoy_supply.py', 'test_enemy_gunner_skill.py', 'test_native_memory_deadlines.py', 'test_convoy_auto_settings.py', 'test_convoy_startup.py', 'test_failure_result_refresh.py', 'test_infantry_resupply_coordinator.py', 'test_infantry_mine_coordinator.py', 'test_infantry_mine_transfer.py', 'test_infantry_ammo_transfer.py', 'test_finite_magazine_reservation.py', 'test_mounted_ammo_transfer.py', 'test_mounted_ammo_pickup.py', 'test_mounted_resupply.py', 'test_mounted_supply_integration.py', 'test_hq_ammo_reporting.py', 'test_enemy_radio.py', 'test_native_maintenance_stock.py', 'test_packed_external_game.py', 'test_hq_radio_node.py', 'test_vehicle_maintenance_stock.py', 'test_optional_transport.py', 'test_vehicle_entry_diagnostics.py', 'test_vehicle_security_native_probe.py', 'test_vehicle_security.py', 'test_vehicle_alarm.py', 'test_vehicle_alarm_perception.py', 'test_native_convoy_mine_stock.py', 'test_native_mine_stock_runner.py', 'test_native_mounted_ammo_probe.py', 'test_native_mounted_swap_probe.py', 'test_native_mounted_courier_probe.py', 'test_native_alarm_hearing.py', 'test_native_convoy_cover_probe.py', 'test_enemy_optics.py', 'test_convoy_combat.py', 'test_ai_stinger_admin.py', 'test_hq_ammo_dispatch.py', 'test_hq_convoy_combat.py', 'test_hq_ammo_sampler.py', 'test_hq_mine_sampler.py', 'test_hq_mounted_ammo_sampler.py', 'test_hq_mounted_binding.py', 'test_hq_vehicle_seats.py', 'test_native_hq_body_probe.py', 'test_hq_delivered_casualties.py', 'test_external_profile_review.py', 'test_hq_uplink_fairness.py', 'test_actor_group.py', 'test_native_package_external.py', 'test_native_convoy_probe.py', 'test_convoy_placement.py', 'test_convoy_road_plan.py', 'test_hq_convoy_dispatch.py', 'test_convoy_admission.py', 'test_convoy_dispatch.py', 'test_convoy_lifecycle.py', 'test_convoy_survivors.py', 'test_convoy_budget.py','test_supply_convoys.py', 'test_supply_budget_admission.py', 'test_mounted_initial_stock.py', 'test_convoy_mine_stock.py', 'test_convoy_column.py', 'test_hq_body_observer.py', 'test_hq_status_sampler.py', 'test_hq_watch.py', 'test_hq_assignment_lifecycle.py', 'test_force_ledger.py', 'test_enemy_equipment.py', 'test_enemy_loadout.py', 'test_automatic_equipment.py', 'test_engineer_mine.py', 'test_engineer_mine_site.py', 'test_engineer_mine_hq.py', 'test_engineer_mine_posts.py', 'test_hq_doctrine.py', 'test_hq_coverage.py', 'test_hq_hypotheses.py', 'test_hq_local_coverage.py', 'test_hq_operations.py', 'test_hq_squad_judgment.py', 'test_hq_tactical_ground.py', 'test_hq_operation_speech.py', 'test_hq_hunter_operations.py', 'test_hq_coordinated.py', 'test_hq_execution.py', 'test_ai_pursuit_replay.py', 'test_native_coordinated_runner.py', 'test_hq_task_reservations.py', 'test_hq_pursuit.py', 'test_hq_memory.py', 'test_external_addons.py', 'test_admin_tick_delivery.py', 'test_project_schema.py', 'test_admin_session.py', 'test_admin_providers.py', 'test_admin_menu.py', 'test_server_item_catalog.py', 'test_item_preferences.py', 'test_journal_readability.py', 'test_admin_player_tools.py', 'test_intel_admin.py', 'test_intel_reroll.py', 'test_intel_discovery_feedback.py', 'test_hq_report_selection.py', 'test_casualty_feedback.py', 'test_initial_admission_exhaustion.py', 'test_prison_held_action_proof.py', 'test_prison_held_action_evidence.py', 'test_prison_held_input_adapter.py', 'test_prison_held_runner.py', 'test_prison_coarse_facing.py', 'test_aircraft_field_probe.py', 'test_aircraft_field_runner.py', 'test_aircraft_field_rules.py', 'test_aircraft_field_envelope.py', 'test_aircraft_field_flight.py', 'test_command_maintenance.py', 'test_aircraft_service.py', 'test_aircraft_service_cancel_runner.py', 'test_prison_action_selection_proof.py', 'test_prison_action_selection_evidence.py', 'test_ordinary_gate_choices.py', 'test_aircraft_control_bridge.py', 'test_prison_alignment_proof.py', 'test_prison_alignment_evidence.py', 'test_aircraft_route_demand.py', 'test_prison_approach_proof.py', 'test_prison_approach_evidence.py', 'test_ordinary_gate_evidence.py', 'test_ordinary_gate_observer.py', 'test_ordinary_look_observer.py', 'test_ordinary_look_evidence.py', 'test_relative_look_runner.py', 'test_radio_waiting_text.py', 'test_inventory_control_runner.py', 'test_inventory_layout.py', 'test_headless_input_proof.py', 'test_headless_input_exercise_runner.py', 'test_private_prefix_cleanup.py', 'test_runner_private_cleanup.py', 'test_ordinary_input_runner.py', 'test_ordinary_input_observer.py', 'test_aircraft_route_envelope.py', 'test_aircraft_route_policy.py', 'test_aircraft_route_probe.py', 'test_rough_intel_feedback.py', 'test_headless_display_probe.py', 'test_headless_display_runner.py', 'test_preparation_status.py', 'test_hint_delivery.py', 'test_native_end_lifecycle.py', 'test_native_end_runner.py', 'test_escape_result_wording.py', 'test_native_stats.py', 'test_aircraft_translation.py', 'test_spawn_retry_budget.py', 'test_spawn_preload_timeout.py', 'test_action_feedback.py', 'test_native_aircraft_control.py', 'test_aircraft_flight_policy.py', 'test_aircraft_flight_envelope.py', 'test_ordinary_screenshot.py', 'test_participant_selection.py', 'test_native_shared_map_ownership.py', 'test_shared_map_ownership.py', 'test_intel_registration.py', 'test_mortar_activity_completion.py', 'test_packed_chat_game.py', 'test_native_mortar_refresh.py', 'test_archive_packed.py', 'test_mortar_refresh.py', 'test_packed_core_game.py', 'test_pak_inventory.py', 'test_native_mortar_salvo.py', 'test_spectator_guidance.py', 'test_spectator_recovery.py', 'test_native_package.py', 'test_item_station_teardown.py', 'test_source_bundle.py', 'test_native_flight_footprint.py', 'test_native_flight_input_diagnostics.py', 'test_mortar_logistics.py', 'test_mortar_courier_storage.py','test_powered_flight_pad.py','test_prison_escape_policy.py','test_powered_flight_policy.py','test_flight_action_runner.py','test_native_local_admission_probe.py','test_mortar_report_ledger.py','test_dedicated_native.py','test_native_mortar_transfer.py','test_mortar_aim_renewal.py','test_dedicated_preflight.py','test_mortar_transactions.py','test_native_mortar_probe.py','test_diagnostic_no_splash.py','test_casualty_incapacitation.py','test_casualty_remove_probe.py','test_extraction_placement_policy.py','test_guard_cancellation_policy.py','test_casualty_ground_policy.py','test_casualty_reservation_death.py','test_casualty_loading_policy.py','test_initial_guard_policy.py','test_headless_game_runner.py','test_vanilla_game_control.py','test_archive_game_control.py','test_site_discovery_policy.py','test_faction_catalog_scope.py','test_mission_rules.py','test_admin_setting_policy.py', 'test_player_admission_policy.py','test_progression_identity.py','test_discovery_map_position.py','test_local_admission.py','test_prison_placement_policy.py', 'test_recovery_policy.py', 'test_native_inventory_hitzone.py','test_inventory_hitzone_compat.py','test_player_medical.py','test_time_policy.py','test_environment_policy.py','test_view_distance.py','test_environment_restart_runner.py', 'test_inventory_enumeration_policy.py','test_radio_defence_policy.py','test_radio_feedback_policy.py','test_performance_comparison.py','test_release_check.py','test_settings_migration.py','hq_network_regression.py','helicopter_availability_regression.py','mortar_salvo_regression.py']: + for name in ['test_sarka_hunters.py', 'test_optional_motorbikes.py', 'test_server_memory_sampler.py', 'test_roadblocks.py', 'test_roadblock_background.py', 'test_roadblock_layout.py', 'test_roadblock_mining.py', 'test_crash_start.py', 'test_start_settings.py', 'test_start_backpack.py', 'test_intercept_vehicle_footprint.py', 'test_bmp_intercept_geometry.py', 'test_native_random_radius_trace.py', 'test_native_random_radius_runner.py', 'test_native_supply_travel.py', 'test_native_supply_travel_hook.py', 'test_native_supply_travel_runner.py', 'test_native_mounted_intercept.py', 'test_hq_mounted_dispatch.py', 'test_hq_mounted_intercept.py', 'test_hq_mounted_intercept_pair.py', 'test_convoy_intercept_pair.py', 'test_native_maintenance_refuel.py', 'test_mine_intel.py', 'test_mine_intel_discovery_integration.py', 'test_infantry_combat.py', 'test_enemy_medicine.py', 'test_hunter_combat.py', 'test_hunter_stuck.py', 'test_hq_local_awareness.py', 'test_unconscious_controls.py', 'test_self_epinephrine.py', 'test_town_garrisons.py', 'test_field_sites.py', 'test_gameplay_session_regressions.py', 'test_gameplay_review_fixes.py', 'test_server_log_regressions.py', 'test_mortar_load_ack.py', 'test_mortar_spotting.py', 'test_hunter_profiles.py', 'test_hunter_lifecycle.py', 'test_hunter_capture.py', 'test_hunter_opening.py', 'test_hunter_search_memory.py', 'test_hunter_reserve.py', 'test_hunter_sector_search.py', 'test_enemy_awareness.py', 'test_intel_body_inspection.py', 'test_body_recovery_lifecycle.py', 'test_radio_document_defence.py', 'test_air_support_policy.py', 'test_air_logistics_orders.py', 'test_air_recon.py', 'test_scouts.py', 'test_mortar_station.py', 'test_random_settings.py', 'test_debug_ai.py', 'test_generated_sites.py', 'test_terrain_sites.py', 'test_terrain_adapter.py', 'test_terrain_airfields.py','test_terrain_takeover.py', 'test_terrain_presentation.py', 'test_terrain_selection.py', 'test_faction_selection.py', 'test_faction_menu.py', 'test_content_policy.py', 'test_hq_road_intercept.py', 'test_hq_delivered_task.py', 'test_hq_local_handoff.py', 'test_hq_local_handoff_intake.py', 'test_native_local_handoff_probe.py', 'test_infantry_resupply_waypoints.py', 'test_infantry_placement_settle.py', 'test_native_infantry_fixture_lifecycle.py', 'test_native_infantry_cargo.py', 'test_native_infantry_setup.py', 'test_infantry_ammo_access.py', 'test_infantry_resupply_planning.py', 'test_infantry_supply_pool.py', 'test_hq_multiconvoy_supply.py', 'test_enemy_gunner_skill.py', 'test_native_memory_deadlines.py', 'test_convoy_auto_settings.py', 'test_convoy_startup.py', 'test_failure_result_refresh.py', 'test_infantry_resupply_coordinator.py', 'test_infantry_mine_coordinator.py', 'test_infantry_mine_transfer.py', 'test_infantry_ammo_transfer.py', 'test_finite_magazine_reservation.py', 'test_mounted_ammo_transfer.py', 'test_mounted_ammo_pickup.py', 'test_mounted_resupply.py', 'test_mounted_supply_integration.py', 'test_hq_ammo_reporting.py', 'test_enemy_radio.py', 'test_native_maintenance_stock.py', 'test_packed_external_game.py', 'test_hq_radio_node.py', 'test_vehicle_maintenance_stock.py', 'test_optional_transport.py', 'test_vehicle_entry_diagnostics.py', 'test_vehicle_security_native_probe.py', 'test_vehicle_security.py', 'test_vehicle_alarm.py', 'test_vehicle_alarm_perception.py', 'test_native_convoy_mine_stock.py', 'test_native_mine_stock_runner.py', 'test_native_mounted_ammo_probe.py', 'test_native_mounted_swap_probe.py', 'test_native_mounted_courier_probe.py', 'test_native_alarm_hearing.py', 'test_native_convoy_cover_probe.py', 'test_enemy_optics.py', 'test_convoy_combat.py', 'test_ai_stinger_admin.py', 'test_hq_ammo_dispatch.py', 'test_hq_convoy_combat.py', 'test_hq_ammo_sampler.py', 'test_hq_mine_sampler.py', 'test_hq_mounted_ammo_sampler.py', 'test_hq_mounted_binding.py', 'test_hq_vehicle_seats.py', 'test_native_hq_body_probe.py', 'test_hq_delivered_casualties.py', 'test_external_profile_review.py', 'test_hq_uplink_fairness.py', 'test_actor_group.py', 'test_native_package_external.py', 'test_native_convoy_probe.py', 'test_convoy_placement.py', 'test_convoy_road_plan.py', 'test_hq_convoy_dispatch.py', 'test_convoy_admission.py', 'test_convoy_dispatch.py', 'test_convoy_lifecycle.py', 'test_convoy_survivors.py', 'test_convoy_budget.py','test_supply_convoys.py', 'test_supply_budget_admission.py', 'test_mounted_initial_stock.py', 'test_convoy_mine_stock.py', 'test_convoy_column.py', 'test_hq_body_observer.py', 'test_hq_status_sampler.py', 'test_hq_watch.py', 'test_hq_assignment_lifecycle.py', 'test_force_ledger.py', 'test_enemy_equipment.py', 'test_enemy_loadout.py', 'test_automatic_equipment.py', 'test_engineer_mine.py', 'test_engineer_mine_site.py', 'test_engineer_mine_hq.py', 'test_engineer_mine_posts.py', 'test_hq_doctrine.py', 'test_hq_coverage.py', 'test_hq_hypotheses.py', 'test_hq_local_coverage.py', 'test_hq_operations.py', 'test_hq_squad_judgment.py', 'test_hq_tactical_ground.py', 'test_hq_operation_speech.py', 'test_hq_hunter_operations.py', 'test_hq_coordinated.py', 'test_hq_execution.py', 'test_ai_pursuit_replay.py', 'test_native_coordinated_runner.py', 'test_hq_task_reservations.py', 'test_hq_pursuit.py', 'test_hq_memory.py', 'test_external_addons.py', 'test_admin_tick_delivery.py', 'test_project_schema.py', 'test_admin_session.py', 'test_admin_providers.py', 'test_admin_menu.py', 'test_server_item_catalog.py', 'test_item_preferences.py', 'test_journal_readability.py', 'test_admin_player_tools.py', 'test_intel_admin.py', 'test_intel_reroll.py', 'test_intel_discovery_feedback.py', 'test_hq_report_selection.py', 'test_casualty_feedback.py', 'test_initial_admission_exhaustion.py', 'test_prison_held_action_proof.py', 'test_prison_held_action_evidence.py', 'test_prison_held_input_adapter.py', 'test_prison_held_runner.py', 'test_prison_coarse_facing.py', 'test_aircraft_field_probe.py', 'test_aircraft_field_runner.py', 'test_aircraft_field_rules.py', 'test_aircraft_field_envelope.py', 'test_aircraft_field_flight.py', 'test_command_maintenance.py', 'test_aircraft_service.py', 'test_aircraft_service_cancel_runner.py', 'test_prison_action_selection_proof.py', 'test_prison_action_selection_evidence.py', 'test_ordinary_gate_choices.py', 'test_aircraft_control_bridge.py', 'test_prison_alignment_proof.py', 'test_prison_alignment_evidence.py', 'test_aircraft_route_demand.py', 'test_prison_approach_proof.py', 'test_prison_approach_evidence.py', 'test_ordinary_gate_evidence.py', 'test_ordinary_gate_observer.py', 'test_ordinary_look_observer.py', 'test_ordinary_look_evidence.py', 'test_relative_look_runner.py', 'test_radio_waiting_text.py', 'test_inventory_control_runner.py', 'test_inventory_layout.py', 'test_headless_input_proof.py', 'test_headless_input_exercise_runner.py', 'test_private_prefix_cleanup.py', 'test_runner_private_cleanup.py', 'test_ordinary_input_runner.py', 'test_ordinary_input_observer.py', 'test_aircraft_route_envelope.py', 'test_aircraft_route_policy.py', 'test_aircraft_route_probe.py', 'test_rough_intel_feedback.py', 'test_headless_display_probe.py', 'test_headless_display_runner.py', 'test_preparation_status.py', 'test_hint_delivery.py', 'test_native_end_lifecycle.py', 'test_native_end_runner.py', 'test_escape_result_wording.py', 'test_native_stats.py', 'test_aircraft_translation.py', 'test_spawn_retry_budget.py', 'test_spawn_preload_timeout.py', 'test_action_feedback.py', 'test_native_aircraft_control.py', 'test_aircraft_flight_policy.py', 'test_aircraft_flight_envelope.py', 'test_ordinary_screenshot.py', 'test_participant_selection.py', 'test_native_shared_map_ownership.py', 'test_shared_map_ownership.py', 'test_intel_registration.py', 'test_mortar_activity_completion.py', 'test_packed_chat_game.py', 'test_native_mortar_refresh.py', 'test_archive_packed.py', 'test_mortar_refresh.py', 'test_packed_core_game.py', 'test_pak_inventory.py', 'test_native_mortar_salvo.py', 'test_spectator_guidance.py', 'test_spectator_recovery.py', 'test_native_package.py', 'test_item_station_teardown.py', 'test_source_bundle.py', 'test_native_flight_footprint.py', 'test_native_flight_input_diagnostics.py', 'test_mortar_logistics.py', 'test_mortar_courier_storage.py','test_powered_flight_pad.py','test_prison_escape_policy.py','test_powered_flight_policy.py','test_flight_action_runner.py','test_native_local_admission_probe.py','test_mortar_report_ledger.py','test_dedicated_native.py','test_native_mortar_transfer.py','test_mortar_aim_renewal.py','test_dedicated_preflight.py','test_mortar_transactions.py','test_native_mortar_probe.py','test_diagnostic_no_splash.py','test_casualty_incapacitation.py','test_casualty_remove_probe.py','test_extraction_placement_policy.py','test_guard_cancellation_policy.py','test_casualty_ground_policy.py','test_casualty_reservation_death.py','test_casualty_loading_policy.py','test_initial_guard_policy.py','test_headless_game_runner.py','test_vanilla_game_control.py','test_archive_game_control.py','test_site_discovery_policy.py','test_faction_catalog_scope.py','test_mission_rules.py','test_admin_setting_policy.py', 'test_player_admission_policy.py','test_progression_identity.py','test_discovery_map_position.py','test_site_map_visibility.py','test_local_admission.py','test_prison_placement_policy.py', 'test_recovery_policy.py', 'test_native_inventory_hitzone.py','test_inventory_hitzone_compat.py','test_player_medical.py','test_time_policy.py','test_environment_policy.py','test_view_distance.py','test_environment_restart_runner.py', 'test_inventory_enumeration_policy.py','test_radio_defence_policy.py','test_radio_feedback_policy.py','test_performance_comparison.py','test_release_check.py','test_settings_migration.py','hq_network_regression.py','helicopter_availability_regression.py','mortar_salvo_regression.py']: subprocess.run([sys.executable,str(ROOT/'tests'/name)],check=True) print('All repository checks passed. Native compilation and multiplayer tests are separate requirements.') if __name__=='__main__':check() diff --git a/tools/import_settings.py b/tools/import_settings.py index 2b5cf6f..7f0f91d 100644 --- a/tools/import_settings.py +++ b/tools/import_settings.py @@ -21,6 +21,7 @@ SCHEMA={ 'responseRadiusMetres':(2000,500,5000), 'aiIntelChancePercent':(60,0,100), 'intelRevealChancePercent':(15,0,100), + 'showAllSites':(0,0,1), 'radioDefenceSeconds':(180,30,1800), 'startTimeMinutes':(840,0,1439), 'timeMultiplier':(1,1,24), @@ -35,7 +36,7 @@ SCHEMA={ 'heliTimerMinutes':(15,1,1440), } AI_KEYS=('aiDoctrine','aiShadow','planeAltitudeMetres','planeReplacementSeconds','planeSectorSeconds','planeReconEnabled','reportIntervalSeconds','supportSquadLimit','responseRadiusMetres','automaticEquipmentChancePercent','antiAirChancePercent','antiPersonnelMineChancePercent','automaticConvoyEnabled','heliTimerMinutes') -INTEL_KEYS=('aiIntelChancePercent','intelRevealChancePercent') +INTEL_KEYS=('aiIntelChancePercent','intelRevealChancePercent','showAllSites') def merge(ai=None,intel=None): result={key:spec[0] for key,spec in SCHEMA.items()};deferred=[] for label,data,keys in [('ai',ai,AI_KEYS),('intel',intel,INTEL_KEYS)]: diff --git a/ui/admin_menu/pages/IntelPage.layout.in b/ui/admin_menu/pages/IntelPage.layout.in index d0fef71..471e7d9 100644 --- a/ui/admin_menu/pages/IntelPage.layout.in +++ b/ui/admin_menu/pages/IntelPage.layout.in @@ -1,29 +1,29 @@ FrameWidgetClass { "Is Visible" {{VISIBLE_IntelPage}} 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 } { TextWidgetClass { - Name "IntelHelp" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 6 OffsetTop 6 SizeX 936 OffsetRight -964 SizeY 50 OffsetBottom -56 } - Color {{COLOR_TEXT}} Text "Control how often soldiers carry intel and whether the first clue reveals an exact location." "Font Size" {{SIZE_INTRO}} "Min Font Size" {{SIZE_INTRO}} Wrap 1 + 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 {{COLOR_TEXT}} Text "Choose how sites appear on the map and how often soldiers carry intel." "Font Size" {{SIZE_INTRO}} "Min Font Size" {{SIZE_INTRO}} Wrap 1 FontProperties FontProperties { Font "{{FONT}}" } } TextWidgetClass { - Name "Label_aiIntelChancePercent" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 80 OffsetTop 80 SizeX 368 OffsetRight -396 SizeY 28 OffsetBottom -108 } + Name "Label_aiIntelChancePercent" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 150 OffsetTop 150 SizeX 368 OffsetRight -396 SizeY 28 OffsetBottom -178 } Color {{COLOR_TEXT}} Text "AI carrying intel (0-100%)" "Font Size" {{SIZE_LABEL}} "Min Font Size" {{SIZE_LABEL}} Wrap 1 FontProperties FontProperties { Font "{{FONT}}" } } TextWidgetClass { - Name "Saved_aiIntelChancePercent" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 112 OffsetTop 112 SizeX 936 OffsetRight -964 SizeY 24 OffsetBottom -136 } + Name "Saved_aiIntelChancePercent" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 194 OffsetTop 194 SizeX 936 OffsetRight -964 SizeY 20 OffsetBottom -214 } Color {{COLOR_TEXT}} Text "Waiting for saved value" "Font Size" {{SIZE_CAPTION}} "Min Font Size" {{SIZE_CAPTION}} Wrap 1 FontProperties FontProperties { Font "{{FONT}}" } } TextWidgetClass { - Name "Help_aiIntelChancePercent" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 136 OffsetTop 136 SizeX 936 OffsetRight -964 SizeY 20 OffsetBottom -156 } + Name "Help_aiIntelChancePercent" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 218 OffsetTop 218 SizeX 936 OffsetRight -964 SizeY 32 OffsetBottom -250 } Color {{COLOR_MUTED}} Text "Chance for each new enemy soldier to carry intel. Reassign carried intel updates existing soldiers." "Font Size" {{SIZE_CAPTION}} "Min Font Size" {{SIZE_CAPTION}} Wrap 1 FontProperties FontProperties { Font "{{FONT}}" } } ButtonWidgetClass : "{DA43C77A0D1E59F8}UI/layouts/ERR/AdminValue.layout" { - Name "aiIntelChancePercent" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 408 OffsetLeft 408 PositionY 80 OffsetTop 80 SizeX 240 OffsetRight -648 SizeY 42 OffsetBottom -122 } + Name "aiIntelChancePercent" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 408 OffsetLeft 408 PositionY 150 OffsetTop 150 SizeX 240 OffsetRight -648 SizeY 42 OffsetBottom -192 } components { SCR_EditBoxComponent "{547290FFBD5B33E9}" { m_sLabel "" @@ -31,7 +31,7 @@ ButtonWidgetClass : "{DA43C77A0D1E59F8}UI/layouts/ERR/AdminValue.layout" { } } ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { - Name "Apply_aiIntelChancePercent" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 660 OffsetLeft 660 PositionY 80 OffsetTop 80 SizeX 112 OffsetRight -772 SizeY 42 OffsetBottom -122 } + Name "Apply_aiIntelChancePercent" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 660 OffsetLeft 660 PositionY 150 OffsetTop 150 SizeX 112 OffsetRight -772 SizeY 42 OffsetBottom -192 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Apply" @@ -39,7 +39,7 @@ ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_But } } ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { - Name "Random_aiIntelChancePercent" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 784 OffsetLeft 784 PositionY 80 OffsetTop 80 SizeX 180 OffsetRight -964 SizeY 42 OffsetBottom -122 } + Name "Random_aiIntelChancePercent" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 784 OffsetLeft 784 PositionY 150 OffsetTop 150 SizeX 180 OffsetRight -964 SizeY 42 OffsetBottom -192 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Random" @@ -47,25 +47,25 @@ ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_But } } TextWidgetClass { - Name "Label_intelRevealChancePercent" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 168 OffsetTop 168 SizeX 368 OffsetRight -396 SizeY 28 OffsetBottom -196 } + Name "Label_intelRevealChancePercent" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 252 OffsetTop 252 SizeX 368 OffsetRight -396 SizeY 28 OffsetBottom -280 } Color {{COLOR_TEXT}} Text "First-document exact reveal (0-100%)" "Font Size" {{SIZE_LABEL}} "Min Font Size" {{SIZE_LABEL}} Wrap 1 FontProperties FontProperties { Font "{{FONT}}" } } TextWidgetClass { - Name "Saved_intelRevealChancePercent" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 200 OffsetTop 200 SizeX 936 OffsetRight -964 SizeY 24 OffsetBottom -224 } + Name "Saved_intelRevealChancePercent" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 296 OffsetTop 296 SizeX 936 OffsetRight -964 SizeY 20 OffsetBottom -316 } Color {{COLOR_TEXT}} Text "Waiting for saved value" "Font Size" {{SIZE_CAPTION}} "Min Font Size" {{SIZE_CAPTION}} Wrap 1 FontProperties FontProperties { Font "{{FONT}}" } } TextWidgetClass { - Name "Help_intelRevealChancePercent" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 224 OffsetTop 224 SizeX 936 OffsetRight -964 SizeY 20 OffsetBottom -244 } - Color {{COLOR_MUTED}} Text "First clue: chance to reveal the exact site. Otherwise a question mark shows an approximate area." "Font Size" {{SIZE_CAPTION}} "Min Font Size" {{SIZE_CAPTION}} Wrap 1 + Name "Help_intelRevealChancePercent" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 320 OffsetTop 320 SizeX 936 OffsetRight -964 SizeY 36 OffsetBottom -356 } + Color {{COLOR_MUTED}} Text "With all sites shown, intel always identifies the type. Otherwise this chance controls exact versus approximate clues." "Font Size" {{SIZE_CAPTION}} "Min Font Size" {{SIZE_CAPTION}} Wrap 1 FontProperties FontProperties { Font "{{FONT}}" } } ButtonWidgetClass : "{DA43C77A0D1E59F8}UI/layouts/ERR/AdminValue.layout" { - Name "intelRevealChancePercent" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 408 OffsetLeft 408 PositionY 168 OffsetTop 168 SizeX 240 OffsetRight -648 SizeY 42 OffsetBottom -210 } + Name "intelRevealChancePercent" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 408 OffsetLeft 408 PositionY 252 OffsetTop 252 SizeX 240 OffsetRight -648 SizeY 42 OffsetBottom -294 } components { SCR_EditBoxComponent "{547290FFBD5B33E9}" { m_sLabel "" @@ -73,7 +73,7 @@ ButtonWidgetClass : "{DA43C77A0D1E59F8}UI/layouts/ERR/AdminValue.layout" { } } ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { - Name "Apply_intelRevealChancePercent" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 660 OffsetLeft 660 PositionY 168 OffsetTop 168 SizeX 112 OffsetRight -772 SizeY 42 OffsetBottom -210 } + Name "Apply_intelRevealChancePercent" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 660 OffsetLeft 660 PositionY 252 OffsetTop 252 SizeX 112 OffsetRight -772 SizeY 42 OffsetBottom -294 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Apply" @@ -81,7 +81,7 @@ ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_But } } ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { - Name "Random_intelRevealChancePercent" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 784 OffsetLeft 784 PositionY 168 OffsetTop 168 SizeX 180 OffsetRight -964 SizeY 42 OffsetBottom -210 } + Name "Random_intelRevealChancePercent" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 784 OffsetLeft 784 PositionY 252 OffsetTop 252 SizeX 180 OffsetRight -964 SizeY 42 OffsetBottom -294 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Random" @@ -89,7 +89,7 @@ ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_But } } ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { - Name "IntelSummary" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 276 OffsetTop 276 SizeX 260 OffsetRight -288 SizeY 42 OffsetBottom -318 } + Name "IntelSummary" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 364 OffsetTop 364 SizeX 260 OffsetRight -288 SizeY 42 OffsetBottom -406 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Intel summary" @@ -97,7 +97,7 @@ ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_But } } ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { - Name "RerollIntel" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 306 OffsetLeft 306 PositionY 276 OffsetTop 276 SizeX 260 OffsetRight -566 SizeY 42 OffsetBottom -318 } + Name "RerollIntel" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 306 OffsetLeft 306 PositionY 364 OffsetTop 364 SizeX 260 OffsetRight -566 SizeY 42 OffsetBottom -406 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Reassign carried intel" @@ -105,13 +105,29 @@ ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_But } } TextWidgetClass { - Name "IntelRerollHelp" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 336 OffsetTop 336 SizeX 936 OffsetRight -964 SizeY 52 OffsetBottom -388 } - Color {{COLOR_TEXT}} Text "Reassign carried intel uses the saved chance on existing AI. Intel already collected stays revealed." "Font Size" {{SIZE_INTRO}} "Min Font Size" {{SIZE_INTRO}} Wrap 1 + Name "IntelRerollHelp" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 420 OffsetTop 420 SizeX 936 OffsetRight -964 SizeY 26 OffsetBottom -446 } + Color {{COLOR_TEXT}} Text "Reassign carried intel updates existing AI. Collected intel stays revealed." "Font Size" {{SIZE_CAPTION}} "Min Font Size" {{SIZE_CAPTION}} Wrap 1 FontProperties FontProperties { Font "{{FONT}}" } } TextWidgetClass { - Name "EvidenceHelp" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 402 OffsetTop 402 SizeX 936 OffsetRight -964 SizeY 60 OffsetBottom -462 } - Color {{COLOR_TEXT}} Text "Enemy bodies stay until a recovery team searches them and reports to HQ. Cleanup is then enabled with a five-minute delay." "Font Size" {{SIZE_INTRO}} "Min Font Size" {{SIZE_INTRO}} Wrap 1 + Name "EvidenceHelp" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 454 OffsetTop 454 SizeX 936 OffsetRight -964 SizeY 28 OffsetBottom -482 } + Color {{COLOR_TEXT}} Text "Enemy bodies remain until searched and reported, then become eligible for cleanup after five minutes." "Font Size" {{SIZE_CAPTION}} "Min Font Size" {{SIZE_CAPTION}} Wrap 1 FontProperties FontProperties { Font "{{FONT}}" } } +TextWidgetClass { + Name "Label_showAllSites" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 58 OffsetTop 58 SizeX 550 OffsetRight -578 SizeY 32 OffsetBottom -90 } + Color {{COLOR_TEXT}} Text "Show all sites as question marks" "Font Size" {{SIZE_LABEL}} "Min Font Size" {{SIZE_LABEL}} Wrap 1 FontProperties FontProperties { Font "{{FONT}}" } +} +TextWidgetClass { + Name "Help_showAllSites" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 102 OffsetTop 102 SizeX 936 OffsetRight -964 SizeY 40 OffsetBottom -142 } + Color {{COLOR_MUTED}} Text "Show prepared site positions without names or types. Intel identifies each site. Applies now for everyone; disabled by default." "Font Size" {{SIZE_CAPTION}} "Min Font Size" {{SIZE_CAPTION}} Wrap 1 FontProperties FontProperties { Font "{{FONT}}" } +} +ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { + Name "SitesOn" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 660 OffsetLeft 660 PositionY 54 OffsetTop 54 SizeX 146 OffsetRight -806 SizeY 42 OffsetBottom -96 } + components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Enabled" } } +} +ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { + Name "SitesOff" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 818 OffsetLeft 818 PositionY 54 OffsetTop 54 SizeX 146 OffsetRight -964 SizeY 42 OffsetBottom -96 } + components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Disabled" } } +} } }