Configure anger points per Sarka instead of cars per point
continuous-integration/drone/push Build is passing

This commit is contained in:
2026-10-17 18:58:35 +02:00
parent 82b52ae1fc
commit 372fca7a68
15 changed files with 125 additions and 59 deletions
+5
View File
@@ -2,6 +2,11 @@
Latest published Workshop version: **0.0.112** Latest published Workshop version: **0.0.112**
## 2026-10-17 — Fewer Šárka hunters
- Set how many anger points are needed per Šárka in the mission menu. Higher values mean fewer cars.
- Default: one car per three anger points, rounded down. Zero disables new waves.
## 2026-10-17 — Mission status readability ## 2026-10-17 — Mission status readability
- Fixed clipped status and journal text so long reports can be scrolled. - Fixed clipped status and journal text so long reports can be scrolled.
@@ -3,12 +3,12 @@ class ERR_AdminMenuFields
{ {
static array<string> Keys() static array<string> Keys()
{ {
return {"aiDoctrine", "aiShadow", "automaticEquipmentChancePercent", "sarkaCarsPerAnger", "motorbikeCount", "planeAltitudeMetres", "planeReplacementSeconds", "planeSectorSeconds", "fieldCampCount", "fieldHospitalCount", "startWeaponCount", "startMagazineCount", "objectGuardCount", "objectGuardEvery", "objectGuardHQMode", "hunterSpawnDistanceMetres", "roadblockCount", "civilianTrafficCount", "emptyRestartHours", "startMode", "radioDefenceSeconds", "aiIntelChancePercent", "intelRevealChancePercent", "startTimeMinutes", "timeMultiplier", "startDateCode", "weatherProfile", "viewDistanceMetres", "reportIntervalSeconds", "supportSquadLimit", "responseRadiusMetres", "convoyCount", "antiAirChancePercent", "antiPersonnelMineChancePercent", "heliTimerMinutes"}; return {"aiDoctrine", "aiShadow", "automaticEquipmentChancePercent", "sarkaAngerPerCar", "motorbikeCount", "planeAltitudeMetres", "planeReplacementSeconds", "planeSectorSeconds", "fieldCampCount", "fieldHospitalCount", "startWeaponCount", "startMagazineCount", "objectGuardCount", "objectGuardEvery", "objectGuardHQMode", "hunterSpawnDistanceMetres", "roadblockCount", "civilianTrafficCount", "emptyRestartHours", "startMode", "radioDefenceSeconds", "aiIntelChancePercent", "intelRevealChancePercent", "startTimeMinutes", "timeMultiplier", "startDateCode", "weatherProfile", "viewDistanceMetres", "reportIntervalSeconds", "supportSquadLimit", "responseRadiusMetres", "convoyCount", "antiAirChancePercent", "antiPersonnelMineChancePercent", "heliTimerMinutes"};
} }
static bool Available(string key, bool hq, bool intel) static bool Available(string key, bool hq, bool intel)
{ {
if (key == "aiDoctrine" || key == "aiShadow") return hq; if (key == "aiDoctrine" || key == "aiShadow") return hq;
if (key == "sarkaCarsPerAnger") return hq; if (key == "sarkaAngerPerCar") return hq;
if (key == "aiIntelChancePercent") return intel; if (key == "aiIntelChancePercent") return intel;
if (key == "automaticEquipmentChancePercent" || key == "motorbikeCount" || key == "planeAltitudeMetres" || key == "planeReplacementSeconds" || key == "planeSectorSeconds" || key == "planeReconEnabled" || key == "objectGuardCount" || key == "objectGuardEvery" || key == "objectGuardHQMode" || key == "hunterSpawnDistanceMetres" || key == "heliTimerMinutes" || key == "antiAirChancePercent" || key == "antiPersonnelMineChancePercent" || key == "convoyCount" || key == "reportIntervalSeconds" || key == "supportSquadLimit" || key == "responseRadiusMetres") return hq; if (key == "automaticEquipmentChancePercent" || key == "motorbikeCount" || key == "planeAltitudeMetres" || key == "planeReplacementSeconds" || key == "planeSectorSeconds" || key == "planeReconEnabled" || key == "objectGuardCount" || key == "objectGuardEvery" || key == "objectGuardHQMode" || key == "hunterSpawnDistanceMetres" || key == "heliTimerMinutes" || key == "antiAirChancePercent" || key == "antiPersonnelMineChancePercent" || key == "convoyCount" || key == "reportIntervalSeconds" || key == "supportSquadLimit" || key == "responseRadiusMetres") return hq;
return Keys().Contains(key); return Keys().Contains(key);
@@ -46,7 +46,7 @@ class ERR_AdminMenuFields
if (key == "aiDoctrine") prefix = "mission doctrine"; if (key == "aiDoctrine") prefix = "mission doctrine";
else if (key == "aiShadow") prefix = "mission aishadow"; else if (key == "aiShadow") prefix = "mission aishadow";
else if (key == "automaticEquipmentChancePercent") prefix = "hq equipment"; else if (key == "automaticEquipmentChancePercent") prefix = "hq equipment";
else if (key == "sarkaCarsPerAnger") prefix = "mission sarkacars"; else if (key == "sarkaAngerPerCar") prefix = "mission sarkapoints";
else if (key == "motorbikeCount") prefix = "mission motorbikes"; else if (key == "motorbikeCount") prefix = "mission motorbikes";
else if (key == "planeReconEnabled") prefix = "mission plane"; else if (key == "planeReconEnabled") prefix = "mission plane";
else if (key == "planeAltitudeMetres") prefix = "mission planealtitude"; else if (key == "planeAltitudeMetres") prefix = "mission planealtitude";
@@ -6,7 +6,7 @@ class ERR_CommandArguments
{ {
if (section == "mission" && option == "doctrine") return "aiDoctrine"; if (section == "mission" && option == "doctrine") return "aiDoctrine";
if (section == "mission" && option == "aishadow") return "aiShadow"; if (section == "mission" && option == "aishadow") return "aiShadow";
if (section == "mission" && option == "sarkacars") return "sarkaCarsPerAnger"; if (section == "mission" && option == "sarkapoints") return "sarkaAngerPerCar";
if (section == "mission" && option == "motorbikes") return "motorbikeCount"; if (section == "mission" && option == "motorbikes") return "motorbikeCount";
if (section == "mission" && option == "planealtitude") return "planeAltitudeMetres"; if (section == "mission" && option == "planealtitude") return "planeAltitudeMetres";
if (section == "mission" && option == "planereplacement") return "planeReplacementSeconds"; if (section == "mission" && option == "planereplacement") return "planeReplacementSeconds";
@@ -171,7 +171,7 @@ class ERR_AdminService
if (key == "aiShadow") return "On (1) records coordinated proposals without issuing their orders; Off (0) uses the selected doctrine. Saved for next round."; if (key == "aiShadow") return "On (1) records coordinated proposals without issuing their orders; Off (0) uses the selected doctrine. Saved for next round.";
if (key == "startWeaponCount") return "0..12 pistols in the shared prison/crash supply backpack; default 5. Applies next round."; if (key == "startWeaponCount") return "0..12 pistols in the shared prison/crash supply backpack; default 5. Applies next round.";
if (key == "startMagazineCount") return "0..48 spare pistol magazines in the shared prison/crash supply backpack; default 8. Applies next round."; if (key == "startMagazineCount") return "0..48 spare pistol magazines in the shared prison/crash supply backpack; default 8. Applies next round.";
if (key == "sarkaCarsPerAnger") return "0-5 cars per anger point; 0 disables new waves. Default 1; maximum 30 cars per wave, four special-forces crew per car. Applies to new waves; existing teams remain."; if (key == "sarkaAngerPerCar") return "1-30 anger points per car; 0 disables new waves. Default 3. Whole thresholds only: 5 means one car at 5 anger, two at 10. Four special-forces crew per car. Applies to new waves; existing teams remain.";
if (key == "motorbikeCount") return "0-6; default 2; 0 disabled. Optional WCS Motorbikes, two response specialists per bike. Applies next round; losses are not replaced."; if (key == "motorbikeCount") return "0-6; default 2; 0 disabled. Optional WCS Motorbikes, two response specialists per bike. Applies next round; losses are not replaced.";
if (key == "planeAltitudeMetres") return "150-500; default 250. Applies next round."; if (key == "planeAltitudeMetres") return "150-500; default 250. Applies next round.";
if (key == "planeReplacementSeconds") return "30-1800; default 60. Applies next round."; if (key == "planeReplacementSeconds") return "30-1800; default 60. Applies next round.";
@@ -208,7 +208,7 @@ modded class ERR_AdminMenu
if (!text) return false; if (!text) return false;
string report = text.GetText(); string report = text.GetText();
array<string> parts = {}; report.Split("Sarka waves:", parts, false); array<string> parts = {}; report.Split("Sarka waves:", parts, false);
return report.Contains("HQ hunting patrols:") && parts.Count() == 2 && report.Contains("cars per anger point"); return report.Contains("HQ hunting patrols:") && parts.Count() == 2 && report.Contains("One car per");
} }
bool ERR_ProbeJournalVisible() bool ERR_ProbeJournalVisible()
{ {
@@ -1,20 +1,20 @@
#ifdef WORKBENCH #ifdef WORKBENCH
modded class ERR_AdminMenu modded class ERR_AdminMenu
{ {
bool ERR_SarkaProbeReady() { return m_Ready && m_AvailableFields.Contains("sarkaCarsPerAnger"); } bool ERR_SarkaProbeReady() { return m_Ready && m_AvailableFields.Contains("sarkaAngerPerCar"); }
bool ERR_SarkaProbeSet(int cars) bool ERR_SarkaProbeSet(int points)
{ {
ShowPage(Navigation().Find("OpenSarka")); ShowPage(Navigation().Find("OpenSarka"));
SCR_EditBoxComponent edit = SCR_EditBoxComponent.GetEditBoxComponent("sarkaCarsPerAnger", GetRootWidget()); SCR_EditBoxComponent edit = SCR_EditBoxComponent.GetEditBoxComponent("sarkaAngerPerCar", GetRootWidget());
SCR_ButtonTextComponent button = SCR_ButtonTextComponent.GetButtonText("Apply_sarkaCarsPerAnger", GetRootWidget()); SCR_ButtonTextComponent button = SCR_ButtonTextComponent.GetButtonText("Apply_sarkaAngerPerCar", GetRootWidget());
if (!edit || !button || !button.IsEnabled()) return false; if (!edit || !button || !button.IsEnabled()) return false;
edit.SetValue(cars.ToString()); OnButton(button); return true; edit.SetValue(points.ToString()); OnButton(button); return true;
} }
} }
modded class ERR_HunterService modded class ERR_HunterService
{ {
void ERR_ProbeSarkaDue(float now) { m_SarkaWaves.NextWave = now; } void ERR_ProbeSarkaDue(float now) { m_SarkaWaves.NextWave = now; }
bool ERR_ProbeSarkaDisabled() { return m_SarkaWaves.CarsPerAnger == 0 && m_SarkaWaves.Pending == 0; } bool ERR_ProbeSarkaDisabled() { return m_SarkaWaves.AngerPerCar == 0 && m_SarkaWaves.Pending == 0; }
void ERR_ProbeSarkas(out array<ERR_HunterVehicle> cars) void ERR_ProbeSarkas(out array<ERR_HunterVehicle> cars)
{ {
cars.Clear(); cars.Clear();
@@ -25,11 +25,11 @@ modded class ERR_HQ
{ {
void ERR_ProbeSarkaBodies(float now) void ERR_ProbeSarkaBodies(float now)
{ {
for (int slot = 0; slot < 6; slot++) for (int slot = 0; slot < 24; slot++)
{ {
ERR_HQReport report = new ERR_HQReport(); ERR_HQReport report = new ERR_HQReport();
report.TravelMode = "body"; report.MessageId = 200000 + slot; report.TravelMode = "body"; report.MessageId = 200000 + slot;
report.CasualtySourceId = "fixture-sarka-bodies"; report.CasualtyStrength = 6; report.CasualtySlot = slot; report.CasualtySourceId = "fixture-sarka-bodies"; report.CasualtyStrength = 24; report.CasualtySlot = slot;
report.ObservedAt = now; report.Position = "5000 50 5000"; report.ObservedAt = now; report.Position = "5000 50 5000";
Accept(report, now); Accept(report, now);
} }
@@ -66,7 +66,7 @@ modded class ERR_GameMode
float now = GetWorld().GetWorldTime(); float now = GetWorld().GetWorldTime();
if (m_ERRSarkaStage == 0) if (m_ERRSarkaStage == 0)
{ {
if (!ERR_SarkaProbeCheck(Settings().Get("sarkaCarsPerAnger") == 1, "default one car per anger point")) return; if (!ERR_SarkaProbeCheck(Settings().Get("sarkaAngerPerCar") == 3, "default three anger points per car")) return;
ERR_AdminMenu.Open(); m_ERRSarkaStage = 9; return; ERR_AdminMenu.Open(); m_ERRSarkaStage = 9; return;
} }
if (m_ERRSarkaStage == 9) if (m_ERRSarkaStage == 9)
@@ -100,19 +100,19 @@ modded class ERR_GameMode
{ {
ERR_AdminMenu menu = ERR_AdminMenu.GetOpenMenu(); ERR_AdminMenu menu = ERR_AdminMenu.GetOpenMenu();
if (!menu || !menu.ERR_SarkaProbeReady()) return; if (!menu || !menu.ERR_SarkaProbeReady()) return;
if (!ERR_SarkaProbeCheck(menu.ERR_SarkaProbeSet(2), "real menu submits two cars per anger point")) return; if (!ERR_SarkaProbeCheck(menu.ERR_SarkaProbeSet(2), "real menu submits two anger points per car")) return;
m_ERRSarkaStage = 11; return; m_ERRSarkaStage = 11; return;
} }
if (m_ERRSarkaStage == 11) if (m_ERRSarkaStage == 11)
{ {
if (Settings().Get("sarkaCarsPerAnger") != 2) return; if (Settings().Get("sarkaAngerPerCar") != 2) return;
ERR_AdminMenu menu = ERR_AdminMenu.GetOpenMenu(); if (!menu || !menu.ERR_SarkaProbeReady()) return; ERR_AdminMenu menu = ERR_AdminMenu.GetOpenMenu(); if (!menu || !menu.ERR_SarkaProbeReady()) return;
System.MakeScreenshot("$logs:err-sarka-settings"); menu.Close(); System.MakeScreenshot("$logs:err-sarka-settings"); menu.Close();
ERR_Settings reloaded = new ERR_Settings(); ERR_Settings reloaded = new ERR_Settings();
if (!ERR_SarkaProbeCheck(reloaded.Get("sarkaCarsPerAnger") == 2, "menu owner RPC persists multiplier")) return; if (!ERR_SarkaProbeCheck(reloaded.Get("sarkaAngerPerCar") == 2, "menu owner RPC persists anger threshold")) return;
if (!ERR_SarkaProbeCheck(UnlockPrison(player) || Phase() >= ERR_Phase.RADIO_SEARCH, "prison released")) return; if (!ERR_SarkaProbeCheck(UnlockPrison(player) || Phase() >= ERR_Phase.RADIO_SEARCH, "prison released")) return;
hq.ERR_ProbeSarkaBodies(now); hq.ERR_ProbeSarkaBodies(now);
if (!ERR_SarkaProbeCheck(hq.Watch().Aggression() == 2, "six delivered bodies produce two anger points")) return; if (!ERR_SarkaProbeCheck(hq.Watch().Aggression() == 8, "24 delivered bodies produce eight anger points")) return;
// Accelerate only the opt-in wave deadline; production placement/admission run unchanged. // Accelerate only the opt-in wave deadline; production placement/admission run unchanged.
hq.Hunters().ERR_ProbeSarkaDue(now); m_ERRSarkaStage = 1; hq.Hunters().ERR_ProbeSarkaDue(now); m_ERRSarkaStage = 1;
} }
@@ -127,16 +127,16 @@ modded class ERR_GameMode
} }
if (m_ERRSarkaStage == 21) if (m_ERRSarkaStage == 21)
{ {
if (Settings().Get("sarkaCarsPerAnger") != 0 || !hq.Hunters().ERR_ProbeSarkaDisabled()) return; if (Settings().Get("sarkaAngerPerCar") != 0 || !hq.Hunters().ERR_ProbeSarkaDisabled()) return;
foreach (ERR_HunterVehicle retained : cars) foreach (ERR_HunterVehicle retained : cars)
if (!ERR_SarkaProbeCheck(retained.Entity && retained.Members.Count() == 4, "disabling retains deployed cars and teams")) return; if (!ERR_SarkaProbeCheck(retained.Entity && retained.Members.Count() == 4, "disabling retains deployed cars and teams")) return;
ERR_AdminMenu menu = ERR_AdminMenu.GetOpenMenu(); if (menu) menu.Close(); ERR_AdminMenu menu = ERR_AdminMenu.GetOpenMenu(); if (menu) menu.Close();
GetGame().GetCallqueue().Remove(ERR_SarkaProbeTick); GetGame().GetCallqueue().Remove(ERR_SarkaProbeTick);
Print("ERR SMOKE COMPLETE: sarka menu save, multiplier, four SF cars, radio, driving and disable"); return; Print("ERR SMOKE COMPLETE: sarka menu save, anger threshold, four SF cars, radio, driving and disable"); return;
} }
if (m_ERRSarkaStage == 1) if (m_ERRSarkaStage == 1)
{ {
if (!ERR_SarkaProbeCheck(cars.Count() == 4, "two cars per anger point produce four physical cars")) return; if (!ERR_SarkaProbeCheck(cars.Count() == 4, "eight anger at two points per car produces four physical cars")) return;
array<vector> positions = {player.GetOrigin()}; array<vector> positions = {player.GetOrigin()};
foreach (ERR_HunterVehicle car : cars) foreach (ERR_HunterVehicle car : cars)
{ {
@@ -9,13 +9,13 @@ class ERR_Settings
static array<string> Keys() static array<string> Keys()
{ {
return {"aiDoctrine", "aiShadow", "sarkaCarsPerAnger", "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", "radioDefenceSeconds", "startTimeMinutes", "timeMultiplier", "startDateCode", "weatherProfile", "viewDistanceMetres", "antiAirChancePercent", "antiPersonnelMineChancePercent", "automaticEquipmentChancePercent", "automaticConvoyEnabled", "convoyCount", "heliTimerMinutes"};
} }
static bool Valid(string key, int value) static bool Valid(string key, int value)
{ {
if (key == "aiDoctrine" || key == "aiShadow") return value == 0 || value == 1; if (key == "aiDoctrine" || key == "aiShadow") return value == 0 || value == 1;
if (key == "sarkaCarsPerAnger") return value >= 0 && value <= 5; if (key == "sarkaAngerPerCar") return value >= 0 && value <= 30;
if (key == "motorbikeCount") return value >= 0 && value <= 6; if (key == "motorbikeCount") return value >= 0 && value <= 6;
if (key == "planeAltitudeMetres") return value >= 150 && value <= 500; if (key == "planeAltitudeMetres") return value >= 150 && value <= 500;
if (key == "planeReplacementSeconds") return value >= 30 && value <= 1800; if (key == "planeReplacementSeconds") return value >= 30 && value <= 1800;
@@ -118,7 +118,7 @@ class ERR_Settings
static int AddedDefault(string key) static int AddedDefault(string key)
{ {
if (key == "aiDoctrine" || key == "aiShadow") return 0; if (key == "aiDoctrine" || key == "aiShadow") return 0;
if (key == "sarkaCarsPerAnger") return 1; if (key == "sarkaAngerPerCar") return 3;
if (key == "motorbikeCount") return 2; if (key == "motorbikeCount") return 2;
if (key == "planeAltitudeMetres") return 250; if (key == "planeAltitudeMetres") return 250;
if (key == "planeReplacementSeconds") return 60; if (key == "planeReplacementSeconds") return 60;
@@ -154,7 +154,7 @@ class ERR_Settings
m_Path = path; m_Path = path;
m_Values.Set("aiDoctrine", AddedDefault("aiDoctrine")); m_Values.Set("aiDoctrine", AddedDefault("aiDoctrine"));
m_Values.Set("aiShadow", AddedDefault("aiShadow")); m_Values.Set("aiShadow", AddedDefault("aiShadow"));
m_Values.Set("sarkaCarsPerAnger", AddedDefault("sarkaCarsPerAnger")); m_Values.Set("sarkaAngerPerCar", AddedDefault("sarkaAngerPerCar"));
m_Values.Set("motorbikeCount", AddedDefault("motorbikeCount")); m_Values.Set("motorbikeCount", AddedDefault("motorbikeCount"));
m_Values.Set("planeAltitudeMetres", AddedDefault("planeAltitudeMetres")); m_Values.Set("planeAltitudeMetres", AddedDefault("planeAltitudeMetres"));
m_Values.Set("planeReplacementSeconds", AddedDefault("planeReplacementSeconds")); m_Values.Set("planeReplacementSeconds", AddedDefault("planeReplacementSeconds"));
@@ -214,6 +214,19 @@ class ERR_Settings
values = new map<string, int>(); values = new map<string, int>();
foreach (string key : Keys()) foreach (string key : Keys())
{ {
// The former multiplier cannot express a lower frequency. Keep disabled
// saves disabled; migrate enabled saves to the new three-points-per-car default.
// An explicit new setting always takes precedence over the legacy key.
if (key == "sarkaAngerPerCar" && !json.DoesKeyExist(key) && json.DoesKeyExist("sarkaCarsPerAnger"))
{
float oldCars;
if (!json.ReadValue("sarkaCarsPerAnger", oldCars)) return false;
int oldMultiplier = Math.Round(oldCars);
if (oldCars != oldMultiplier || oldMultiplier < 0 || oldMultiplier > 5) return false;
int points = AddedDefault(key);
if (oldMultiplier == 0) points = 0;
values.Set(key, points); continue;
}
// Migrate the former one-in-N setting to the nearest integer percent. // Migrate the former one-in-N setting to the nearest integer percent.
if (key == "antiAirChancePercent" && !json.DoesKeyExist(key) && json.DoesKeyExist("stingerChanceOneIn")) if (key == "antiAirChancePercent" && !json.DoesKeyExist(key) && json.DoesKeyExist("stingerChanceOneIn"))
{ {
@@ -8,22 +8,23 @@ class ERR_SarkaWaves
int Target; int Target;
int Wave; int Wave;
int Spawned; int Spawned;
int CarsPerAnger = 1; int AngerPerCar = 3;
void Configure(int cars) void Configure(int points)
{ {
if (CarsPerAnger == cars) return; if (AngerPerCar == points) return;
CarsPerAnger = cars; AngerPerCar = points;
if (cars == 0) Pause(); if (points == 0) Pause();
} }
void Pause() { NextWave = -1; Pending = 0; Target = 0; } void Pause() { NextWave = -1; Pending = 0; Target = 0; }
bool Due(float now, int aggression, bool playing) bool Due(float now, int aggression, bool playing)
{ {
if (!playing || CarsPerAnger == 0) { Pause(); return false; } if (!playing || AngerPerCar == 0) { Pause(); return false; }
if (NextWave < 0) { NextWave = now + 600000; return false; } if (NextWave < 0) { NextWave = now + 600000; return false; }
if (now >= NextWave) if (now >= NextWave)
{ {
NextWave = now + 600000; NextAttempt = now; NextWave = now + 600000; NextAttempt = now;
Target = Math.Clamp(Math.Clamp(aggression, 0, 30) * CarsPerAnger, 0, 30); Pending = Target; Wave++; // Whole thresholds only: five points per car means zero at four, one at five.
Target = Math.Clamp(aggression, 0, 30) / AngerPerCar; Pending = Target; Wave++;
} }
if (Pending == 0 || now < NextAttempt) return false; if (Pending == 0 || now < NextAttempt) return false;
NextAttempt = now + 5000; NextAttempt = now + 5000;
@@ -141,15 +141,17 @@ class ERR_HunterService : ERR_ConvoyService
protected string m_SarkaStatus = "waiting for first wave after prison escape"; protected string m_SarkaStatus = "waiting for first wave after prison escape";
void ConfigureSarka() void ConfigureSarka()
{ {
int cars = m_Mode.Settings().Get("sarkaCarsPerAnger"); int points = m_Mode.Settings().Get("sarkaAngerPerCar");
bool changed = cars != m_SarkaWaves.CarsPerAnger; bool changed = points != m_SarkaWaves.AngerPerCar;
m_SarkaWaves.Configure(cars); m_SarkaWaves.Configure(points);
if (changed) m_SarkaStatus = "saved car count applies to the next wave"; if (changed) m_SarkaStatus = "saved anger threshold applies to the next wave";
if (cars == 0) m_SarkaStatus = "new waves disabled; existing teams remain"; if (points == 0) m_SarkaStatus = "new waves disabled; existing teams remain";
} }
string SarkaStatus() string SarkaStatus()
{ {
return string.Format("Sarka waves: %1 deployed, %2/%3 pending in wave %4; %5. %6 cars per anger point (30 per wave maximum).", m_SarkaWaves.Spawned, m_SarkaWaves.Pending, m_SarkaWaves.Target, m_SarkaWaves.Wave, m_SarkaStatus, m_SarkaWaves.CarsPerAnger); string ratio = "New waves disabled.";
if (m_SarkaWaves.AngerPerCar > 0) ratio = string.Format("One car per %1 anger points (rounded down).", m_SarkaWaves.AngerPerCar);
return string.Format("Sarka waves: %1 deployed, %2/%3 pending in wave %4; %5. %6", m_SarkaWaves.Spawned, m_SarkaWaves.Pending, m_SarkaWaves.Target, m_SarkaWaves.Wave, m_SarkaStatus, ratio);
} }
// HQ maintenance runs even while tactical ticks pause in an empty round. // HQ maintenance runs even while tactical ticks pause in an empty round.
void PauseSarkaIfEmpty() void PauseSarkaIfEmpty()
+5 -5
View File
@@ -1149,11 +1149,11 @@ FrameWidgetClass { "Is Visible" 0 Name "SarkaPage" Slot FrameWidgetSlot { Anchor
TextWidgetClass { Name "SarkaHeading" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 6 OffsetTop 6 SizeX 710 OffsetRight -738 SizeY 32 OffsetBottom -38 } Color 0.9 0.92 0.94 1 Text "Šárka special-forces hunters" "Font Size" 22 "Min Font Size" 22 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } } TextWidgetClass { Name "SarkaHeading" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 6 OffsetTop 6 SizeX 710 OffsetRight -738 SizeY 32 OffsetBottom -38 } Color 0.9 0.92 0.94 1 Text "Šárka special-forces hunters" "Font Size" 22 "Min Font Size" 22 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
TextWidgetClass { Name "SarkaHelp" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 50 OffsetTop 50 SizeX 936 OffsetRight -964 SizeY 44 OffsetBottom -94 } Color 0.9 0.92 0.94 1 Text "Four soldiers per car, including a medic. Anger rises from identified enemy casualties." "Font Size" 18 "Min Font Size" 18 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } } TextWidgetClass { Name "SarkaHelp" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 50 OffsetTop 50 SizeX 936 OffsetRight -964 SizeY 44 OffsetBottom -94 } Color 0.9 0.92 0.94 1 Text "Four soldiers per car, including a medic. Anger rises from identified enemy casualties." "Font Size" 18 "Min Font Size" 18 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
TextWidgetClass { Name "Label_sarkaCarsPerAnger" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 112 OffsetTop 112 SizeX 590 OffsetRight -618 SizeY 32 OffsetBottom -144 } Color 0.9 0.92 0.94 1 Text "Šárkas per anger point (0-5; 0 disables waves)" "Font Size" 20 "Min Font Size" 20 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } } TextWidgetClass { Name "Label_sarkaAngerPerCar" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 112 OffsetTop 112 SizeX 590 OffsetRight -618 SizeY 32 OffsetBottom -144 } Color 0.9 0.92 0.94 1 Text "Anger points per Šárka (1-30; 0 disables waves)" "Font Size" 20 "Min Font Size" 20 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
ButtonWidgetClass : "{DA43C77A0D1E59F8}UI/layouts/ERR/AdminValue.layout" { Name "sarkaCarsPerAnger" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 640 OffsetLeft 640 PositionY 108 OffsetTop 108 SizeX 112 OffsetRight -752 SizeY 38 OffsetBottom -146 } components { SCR_EditBoxComponent "{547290FFBD5B33E9}" { m_sLabel "" } } } ButtonWidgetClass : "{DA43C77A0D1E59F8}UI/layouts/ERR/AdminValue.layout" { Name "sarkaAngerPerCar" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 640 OffsetLeft 640 PositionY 108 OffsetTop 108 SizeX 112 OffsetRight -752 SizeY 38 OffsetBottom -146 } components { SCR_EditBoxComponent "{547290FFBD5B33E9}" { m_sLabel "" } } }
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "Apply_sarkaCarsPerAnger" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 764 OffsetLeft 764 PositionY 108 OffsetTop 108 SizeX 200 OffsetRight -964 SizeY 38 OffsetBottom -146 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Apply" } } } ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "Apply_sarkaAngerPerCar" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 764 OffsetLeft 764 PositionY 108 OffsetTop 108 SizeX 200 OffsetRight -964 SizeY 38 OffsetBottom -146 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Apply" } } }
TextWidgetClass { Name "Saved_sarkaCarsPerAnger" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 640 OffsetLeft 640 PositionY 148 OffsetTop 148 SizeX 324 OffsetRight -964 SizeY 24 OffsetBottom -172 } Color 0.9 0.92 0.94 1 Text "Waiting for server" "Font Size" 16 "Min Font Size" 16 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } } TextWidgetClass { Name "Saved_sarkaAngerPerCar" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 640 OffsetLeft 640 PositionY 148 OffsetTop 148 SizeX 324 OffsetRight -964 SizeY 24 OffsetBottom -172 } Color 0.9 0.92 0.94 1 Text "Waiting for server" "Font Size" 16 "Min Font Size" 16 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
TextWidgetClass { Name "Help_sarkaCarsPerAnger" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 176 OffsetTop 176 SizeX 936 OffsetRight -964 SizeY 26 OffsetBottom -202 } Color 0.9 0.92 0.94 1 Text "Default 1; up to 30 cars per wave. Existing teams remain when disabled." "Font Size" 17 "Min Font Size" 17 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } } TextWidgetClass { Name "Help_sarkaAngerPerCar" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 176 OffsetTop 176 SizeX 936 OffsetRight -964 SizeY 26 OffsetBottom -202 } Color 0.9 0.92 0.94 1 Text "Default 3. At 5 points per car: anger 4 = none, 5 = one car, 10 = two." "Font Size" 17 "Min Font Size" 17 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
TextWidgetClass { Name "SarkaSettingsHelp" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 424 OffsetTop 424 SizeX 936 OffsetRight -964 SizeY 52 OffsetBottom -476 } Color 0.9 0.92 0.94 1 Text "Apply saves this setting for new waves. Existing teams remain. Empty sessions do not build up extra waves." "Font Size" 18 "Min Font Size" 18 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } } TextWidgetClass { Name "SarkaSettingsHelp" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 424 OffsetTop 424 SizeX 936 OffsetRight -964 SizeY 52 OffsetBottom -476 } Color 0.9 0.92 0.94 1 Text "Apply saves this setting for new waves. Existing teams remain. Empty sessions do not build up extra waves." "Font Size" 18 "Min Font Size" 18 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
} } } }
TextWidgetClass { Name "Feedback" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 248 OffsetLeft 248 PositionY 670 OffsetTop 670 SizeX 936 OffsetRight -1184 SizeY 70 OffsetBottom -740 } Color 0.9 0.92 0.94 1 Text "" "Font Size" 17 "Min Font Size" 17 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } } TextWidgetClass { Name "Feedback" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 248 OffsetLeft 248 PositionY 670 OffsetTop 670 SizeX 936 OffsetRight -1184 SizeY 70 OffsetBottom -740 } Color 0.9 0.92 0.94 1 Text "" "Font Size" 17 "Min Font Size" 17 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
+15
View File
@@ -63,6 +63,21 @@ requested totals instead of preventing mission startup. The admin Status page
also shows these totals. This is an interim checkpoint system, independent of the also shows these totals. This is an interim checkpoint system, independent of the
existing HQ-directed road interceptions. existing HQ-directed road interceptions.
## Šárka special-forces hunters
Under **Forces → Šárka hunters**, set **Anger points per Šárka** from 1 to 30.
The default is 3. Each wave requests the current anger divided by this value,
rounded down: a value of 5 gives no car below 5 anger, one at 5–9, and two at
10–14. Zero disables new waves; already deployed cars and crews remain.
Each car carries four special-forces soldiers, including a medic. Changes apply
to the next wave; the existing interval and placement rules remain unchanged.
The saved key is `mission.sarkaAngerPerCar` in `$profile:Escapists.json`.
The command is `/esc mission sarkapoints set N` (also `show` and `help`).
Older `sarkaCarsPerAnger` settings migrate to the new default of 3 when enabled;
an old zero remains disabled. An explicit new key takes precedence. The old
`sarkacars` command is retired to avoid silently reversing its meaning.
## Mission restarts ## Mission restarts
The admin menu **Mission** page includes two independent settings: The admin menu **Mission** page includes two independent settings:
+1 -1
View File
@@ -55,7 +55,7 @@ assert 'Apply_planeReconEnabled' not in names # Saved-state toggle pair, not a
assert len(names) == len(set(names)), 'Menu widget names must be unique' assert len(names) == len(set(names)), 'Menu widget names must be unique'
for key in keys: for key in keys:
assert all(name in names for name in (key, 'Apply_' + key, 'Saved_' + key, 'Help_' + key)) assert all(name in names for name in (key, 'Apply_' + key, 'Saved_' + key, 'Help_' + key))
if key not in ('sarkaCarsPerAnger','emptyRestartHours','aiDoctrine','aiShadow','motorbikeCount','planeAltitudeMetres','planeReplacementSeconds','planeSectorSeconds','fieldCampCount','fieldHospitalCount','startMode','civilianTrafficCount','roadblockCount','hunterSpawnDistanceMetres','objectGuardCount','objectGuardEvery','objectGuardHQMode','startWeaponCount','startMagazineCount'): assert ('Default_' if key == 'viewDistanceMetres' else 'Random_') + key in names if key not in ('sarkaAngerPerCar','emptyRestartHours','aiDoctrine','aiShadow','motorbikeCount','planeAltitudeMetres','planeReplacementSeconds','planeSectorSeconds','fieldCampCount','fieldHospitalCount','startMode','civilianTrafficCount','roadblockCount','hunterSpawnDistanceMetres','objectGuardCount','objectGuardEvery','objectGuardHQMode','startWeaponCount','startMagazineCount'): assert ('Default_' if key == 'viewDistanceMetres' else 'Random_') + key in names
else: assert 'Random_startMode' not in names else: assert 'Random_startMode' not in names
for name in ('Close', 'SettingsPage', 'HQPage', 'JournalPage', 'JournalText', 'JournalContent', 'JournalScroll', 'PauseJournal', 'LatestJournal', 'Status'): for name in ('Close', 'SettingsPage', 'HQPage', 'JournalPage', 'JournalText', 'JournalContent', 'JournalScroll', 'PauseJournal', 'LatestJournal', 'Status'):
assert name in names assert name in names
+3 -3
View File
@@ -21,9 +21,9 @@ int main(){
assert(!Valid("emptyRestartHours",-1)&&!Valid("emptyRestartHours",169)); assert(!Valid("emptyRestartHours",-1)&&!Valid("emptyRestartHours",169));
for(std::string key:{"aiDoctrine","aiShadow"}){ assert(Valid(key,0)&&Valid(key,1)); assert(!Valid(key,-1)&&!Valid(key,2)); } for(std::string key:{"aiDoctrine","aiShadow"}){ assert(Valid(key,0)&&Valid(key,1)); assert(!Valid(key,-1)&&!Valid(key,2)); }
assert(CoreSetting("mission","motorbikes")=="motorbikeCount"); assert(CoreSetting("mission","motorbikes")=="motorbikeCount");
assert(CoreSetting("mission","sarkacars")=="sarkaCarsPerAnger"); assert(CoreSetting("mission","sarkapoints")=="sarkaAngerPerCar");
assert(Valid("sarkaCarsPerAnger",0)&&Valid("sarkaCarsPerAnger",1)&&Valid("sarkaCarsPerAnger",5)); assert(Valid("sarkaAngerPerCar",0)&&Valid("sarkaAngerPerCar",1)&&Valid("sarkaAngerPerCar",30));
assert(!Valid("sarkaCarsPerAnger",-1)&&!Valid("sarkaCarsPerAnger",6)); assert(!Valid("sarkaAngerPerCar",-1)&&!Valid("sarkaAngerPerCar",31));
assert(Valid("motorbikeCount",0)&&Valid("motorbikeCount",2)&&Valid("motorbikeCount",6)); assert(Valid("motorbikeCount",0)&&Valid("motorbikeCount",2)&&Valid("motorbikeCount",6));
assert(!Valid("motorbikeCount",-1)&&!Valid("motorbikeCount",7)); assert(!Valid("motorbikeCount",-1)&&!Valid("motorbikeCount",7));
assert(CoreSetting("mission","planealtitude")=="planeAltitudeMetres"); assert(CoreSetting("mission","planealtitude")=="planeAltitudeMetres");
+18
View File
@@ -47,6 +47,24 @@ int main(){
for(float bad:{-1.0f,10001.0f,0.5f}){files["legacy"]=files["old"];files["legacy"]["stingerChanceOneIn"]=bad;assert(!s.Read("legacy",loaded));} for(float bad:{-1.0f,10001.0f,0.5f}){files["legacy"]=files["old"];files["legacy"]["stingerChanceOneIn"]=bad;assert(!s.Read("legacy",loaded));}
files["new"]=files["old"];files["new"]["stingerChanceOneIn"]=15;files["new"]["antiAirChancePercent"]=100; files["new"]=files["old"];files["new"]["stingerChanceOneIn"]=15;files["new"]["antiAirChancePercent"]=100;
assert(s.Read("new",loaded)&&loaded->Get("antiAirChancePercent")==100&&loaded->Get("antiPersonnelMineChancePercent")==60); assert(s.Read("new",loaded)&&loaded->Get("antiAirChancePercent")==100&&loaded->Get("antiPersonnelMineChancePercent")==60);
assert(Settings::AddedDefault("sarkaAngerPerCar")==3);
for(int old=0;old<=5;++old){
files["sarka"]=files["old"];files["sarka"]["sarkaCarsPerAnger"]=old;
assert(s.Read("sarka",loaded)&&loaded->Get("sarkaAngerPerCar")== (old==0?0:3));
}
for(float bad:{-1.0f,6.0f,0.5f}){
files["sarka"]=files["old"];files["sarka"]["sarkaCarsPerAnger"]=bad;
assert(!s.Read("sarka",loaded));
}
for(int points:{0,1,3,5,30}){
files["sarka"]=files["old"];files["sarka"]["sarkaCarsPerAnger"]=0;
files["sarka"]["sarkaAngerPerCar"]=points;
assert(s.Read("sarka",loaded)&&loaded->Get("sarkaAngerPerCar")==points);
}
for(float bad:{-1.0f,31.0f,0.5f}){
files["sarka"]=files["old"];files["sarka"]["sarkaAngerPerCar"]=bad;
assert(!s.Read("sarka",loaded));
}
assert(!s.Read("missing",loaded)); assert(!s.Read("missing",loaded));
} }
''') ''')
+18 -6
View File
@@ -21,6 +21,8 @@ struct Waves
struct Rules {'''+band+seats+r'''}; struct Rules {'''+band+seats+r'''};
int main(){ int main(){
Waves w{}; Waves w{};
assert(w.AngerPerCar==3);
w.Configure(1); // Preserve the timing/retry checks at the densest allowed rate.
assert(!w.Due(100,3,true)&&w.NextWave==600100); assert(!w.Due(100,3,true)&&w.NextWave==600100);
assert(!w.Due(600099,3,true)); assert(!w.Due(600099,3,true));
assert(w.Due(600100,3,true)&&w.Pending==3&&w.Wave==1); assert(w.Due(600100,3,true)&&w.Pending==3&&w.Wave==1);
@@ -41,16 +43,26 @@ int main(){
Waves configured{}; Waves configured{};
configured.Configure(2); configured.Configure(2);
assert(!configured.Due(0,3,true)); assert(!configured.Due(0,3,true));
assert(configured.Due(600000,3,true)&&configured.Target==6); assert(configured.Due(600000,5,true)&&configured.Target==2);
configured.Commit();assert(configured.Pending==5&&configured.Spawned==1); configured.Commit();assert(configured.Pending==1&&configured.Spawned==1);
configured.Configure(5); // Already sampled wave and deadline stay unchanged. configured.Configure(5); // Already sampled wave and deadline stay unchanged.
assert(configured.Pending==5&&configured.NextWave==1200000); assert(configured.Pending==1&&configured.NextWave==1200000);
assert(configured.Due(1200000,30,true)&&configured.Target==30); // Keep the formation cap. assert(configured.Due(1200000,30,true)&&configured.Target==6);
configured.Configure(0);assert(configured.Pending==0&&configured.Target==0); configured.Configure(0);assert(configured.Pending==0&&configured.Target==0);
assert(!configured.Due(9999999,30,true)&&configured.Spawned==1); // No waves; retain deployed count. assert(!configured.Due(9999999,30,true)&&configured.Spawned==1); // No waves; retain deployed count.
configured.Configure(2);assert(!configured.Due(10000000,3,true)); configured.Configure(2);assert(!configured.Due(10000000,3,true));
assert(!configured.Due(10599999,3,true)); // Re-enable waits normally, no backlog. assert(!configured.Due(10599999,3,true)); // Re-enable waits normally, no backlog.
assert(configured.Due(10600000,3,true)&&configured.Target==6); assert(configured.Due(10600000,3,true)&&configured.Target==1);
// All supported divisors, exact thresholds, remainders and clamped anger.
for(int points=1;points<=30;++points){
for(int anger=-1;anger<=31;++anger){
Waves threshold{};threshold.Configure(points);
assert(!threshold.Due(0,anger,true));
int expected=std::clamp(anger,0,30)/points;
assert(threshold.Due(600000,anger,true)==(expected>0));
assert(threshold.Target==expected&&threshold.Pending==expected);
}
}
Array<vector> players{{0,10,0}}; Array<vector> players{{0,10,0}};
assert(!Rules::InBand({999,0,0},players)); assert(!Rules::InBand({999,0,0},players));
assert(Rules::InBand({1000,900,0},players)&&Rules::InBand({1250,0,0},players)); assert(Rules::InBand({1000,900,0},players)&&Rules::InBand({1250,0,0},players));
@@ -61,4 +73,4 @@ int main(){
assert(Rules::Seats(1,0,3,4)&&!Rules::Seats(1,1,2,4)&&!Rules::Seats(1,0,4,5)); assert(Rules::Seats(1,0,3,4)&&!Rules::Seats(1,1,2,4)&&!Rules::Seats(1,0,4,5));
} }
''') ''')
print('PASS: configurable anger multiplier, disable/re-enable, wave cap, staged retries, empty-round reset, no backlog, four seats and nearest-player road distance') print('PASS: anger per car, all thresholds and remainders, disable/re-enable, wave cap, staged retries, empty-round reset, no backlog, four seats and nearest-player road distance')
+5 -5
View File
@@ -2,10 +2,10 @@ FrameWidgetClass { "Is Visible" {{VISIBLE_SarkaPage}} Name "SarkaPage" Slot Fram
TextWidgetClass { Name "SarkaHeading" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 6 OffsetTop 6 SizeX 710 OffsetRight -738 SizeY 32 OffsetBottom -38 } Color {{COLOR_TEXT}} Text "Šárka special-forces hunters" "Font Size" {{SIZE_HEADING}} "Min Font Size" {{SIZE_HEADING}} Wrap 1 FontProperties FontProperties { Font "{{FONT}}" } } TextWidgetClass { Name "SarkaHeading" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 6 OffsetTop 6 SizeX 710 OffsetRight -738 SizeY 32 OffsetBottom -38 } Color {{COLOR_TEXT}} Text "Šárka special-forces hunters" "Font Size" {{SIZE_HEADING}} "Min Font Size" {{SIZE_HEADING}} Wrap 1 FontProperties FontProperties { Font "{{FONT}}" } }
TextWidgetClass { Name "SarkaHelp" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 50 OffsetTop 50 SizeX 936 OffsetRight -964 SizeY 44 OffsetBottom -94 } Color {{COLOR_TEXT}} Text "Four soldiers per car, including a medic. Anger rises from identified enemy casualties." "Font Size" {{SIZE_BODY}} "Min Font Size" {{SIZE_BODY}} Wrap 1 FontProperties FontProperties { Font "{{FONT}}" } } TextWidgetClass { Name "SarkaHelp" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 50 OffsetTop 50 SizeX 936 OffsetRight -964 SizeY 44 OffsetBottom -94 } Color {{COLOR_TEXT}} Text "Four soldiers per car, including a medic. Anger rises from identified enemy casualties." "Font Size" {{SIZE_BODY}} "Min Font Size" {{SIZE_BODY}} Wrap 1 FontProperties FontProperties { Font "{{FONT}}" } }
TextWidgetClass { Name "Label_sarkaCarsPerAnger" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 112 OffsetTop 112 SizeX 590 OffsetRight -618 SizeY 32 OffsetBottom -144 } Color {{COLOR_TEXT}} Text "Šárkas per anger point (0-5; 0 disables waves)" "Font Size" {{SIZE_LABEL}} "Min Font Size" {{SIZE_LABEL}} Wrap 1 FontProperties FontProperties { Font "{{FONT}}" } } TextWidgetClass { Name "Label_sarkaAngerPerCar" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 112 OffsetTop 112 SizeX 590 OffsetRight -618 SizeY 32 OffsetBottom -144 } Color {{COLOR_TEXT}} Text "Anger points per Šárka (1-30; 0 disables waves)" "Font Size" {{SIZE_LABEL}} "Min Font Size" {{SIZE_LABEL}} Wrap 1 FontProperties FontProperties { Font "{{FONT}}" } }
ButtonWidgetClass : "{DA43C77A0D1E59F8}UI/layouts/ERR/AdminValue.layout" { Name "sarkaCarsPerAnger" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 640 OffsetLeft 640 PositionY 108 OffsetTop 108 SizeX 112 OffsetRight -752 SizeY 38 OffsetBottom -146 } components { SCR_EditBoxComponent "{547290FFBD5B33E9}" { m_sLabel "" } } } ButtonWidgetClass : "{DA43C77A0D1E59F8}UI/layouts/ERR/AdminValue.layout" { Name "sarkaAngerPerCar" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 640 OffsetLeft 640 PositionY 108 OffsetTop 108 SizeX 112 OffsetRight -752 SizeY 38 OffsetBottom -146 } components { SCR_EditBoxComponent "{547290FFBD5B33E9}" { m_sLabel "" } } }
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "Apply_sarkaCarsPerAnger" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 764 OffsetLeft 764 PositionY 108 OffsetTop 108 SizeX 200 OffsetRight -964 SizeY 38 OffsetBottom -146 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Apply" } } } ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "Apply_sarkaAngerPerCar" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 764 OffsetLeft 764 PositionY 108 OffsetTop 108 SizeX 200 OffsetRight -964 SizeY 38 OffsetBottom -146 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Apply" } } }
TextWidgetClass { Name "Saved_sarkaCarsPerAnger" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 640 OffsetLeft 640 PositionY 148 OffsetTop 148 SizeX 324 OffsetRight -964 SizeY 24 OffsetBottom -172 } Color {{COLOR_TEXT}} Text "Waiting for server" "Font Size" {{SIZE_CAPTION}} "Min Font Size" {{SIZE_CAPTION}} Wrap 1 FontProperties FontProperties { Font "{{FONT}}" } } TextWidgetClass { Name "Saved_sarkaAngerPerCar" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 640 OffsetLeft 640 PositionY 148 OffsetTop 148 SizeX 324 OffsetRight -964 SizeY 24 OffsetBottom -172 } Color {{COLOR_TEXT}} Text "Waiting for server" "Font Size" {{SIZE_CAPTION}} "Min Font Size" {{SIZE_CAPTION}} Wrap 1 FontProperties FontProperties { Font "{{FONT}}" } }
TextWidgetClass { Name "Help_sarkaCarsPerAnger" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 176 OffsetTop 176 SizeX 936 OffsetRight -964 SizeY 26 OffsetBottom -202 } Color {{COLOR_TEXT}} Text "Default 1; up to 30 cars per wave. Existing teams remain when disabled." "Font Size" {{SIZE_HELP}} "Min Font Size" {{SIZE_HELP}} Wrap 1 FontProperties FontProperties { Font "{{FONT}}" } } TextWidgetClass { Name "Help_sarkaAngerPerCar" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 176 OffsetTop 176 SizeX 936 OffsetRight -964 SizeY 26 OffsetBottom -202 } Color {{COLOR_TEXT}} Text "Default 3. At 5 points per car: anger 4 = none, 5 = one car, 10 = two." "Font Size" {{SIZE_HELP}} "Min Font Size" {{SIZE_HELP}} Wrap 1 FontProperties FontProperties { Font "{{FONT}}" } }
TextWidgetClass { Name "SarkaSettingsHelp" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 424 OffsetTop 424 SizeX 936 OffsetRight -964 SizeY 52 OffsetBottom -476 } Color {{COLOR_TEXT}} Text "Apply saves this setting for new waves. Existing teams remain. Empty sessions do not build up extra waves." "Font Size" {{SIZE_BODY}} "Min Font Size" {{SIZE_BODY}} Wrap 1 FontProperties FontProperties { Font "{{FONT}}" } } TextWidgetClass { Name "SarkaSettingsHelp" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 424 OffsetTop 424 SizeX 936 OffsetRight -964 SizeY 52 OffsetBottom -476 } Color {{COLOR_TEXT}} Text "Apply saves this setting for new waves. Existing teams remain. Empty sessions do not build up extra waves." "Font Size" {{SIZE_BODY}} "Min Font Size" {{SIZE_BODY}} Wrap 1 FontProperties FontProperties { Font "{{FONT}}" } }
} } } }