Add configurable anonymous site markers and record Workshop 117

This commit is contained in:
2026-10-20 18:35:22 +02:00
parent 87d85ff02e
commit f41d8ba2f9
17 changed files with 265 additions and 70 deletions
+5 -1
View File
@@ -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
@@ -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<string> names = {"PlaneOn", "PlaneOff", "Close", "PauseJournal", "JournalDetails", "LatestJournal", "KeepWeaponOn", "KeepWeaponOff", "SelfEpiOn", "SelfEpiOff", "IntelSummary", "RerollIntel", "RandomDay_startTimeMinutes", "Default_viewDistanceMetres", "RestartEmptyOn", "RestartEmptyOff", "RecommendedPlane", "RecommendedWeapons"};
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"};
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;
@@ -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;
}
}
@@ -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)
@@ -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();
@@ -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;
}
@@ -9,7 +9,7 @@ class ERR_Settings
static array<string> 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"));
@@ -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<string> 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.");
+37 -21
View File
@@ -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 {
+1 -1
View File
@@ -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<string> Keys('))
settings = re.findall(r'"([A-Za-z]+)"', body(BASE / 'addons/core/Scripts/Game/ERR/00_Core/ERR_Settings.c', 'static array<string> 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.
+3
View File
@@ -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");
+2 -2
View File
@@ -26,7 +26,7 @@ template<class T> struct Array:std::vector<T>{void Insert(T v){this->push_back(v
template<class T> struct Set:std::set<T>{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<string>&,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<IEntity> m_Spent;
};
int main(){
Admin admin;Mode mode;Array<string> 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");
+1 -1
View File
@@ -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})
+105
View File
@@ -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 <cassert>
#include <cmath>
#include <string>
#include <memory>
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<string> 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')
+1 -1
View File
File diff suppressed because one or more lines are too long
+2 -1
View File
@@ -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)]:
+37 -21
View File
@@ -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" } }
}
} }