Use direct buttons for fixed mission setting choices; record Workshop 0.0.149
continuous-integration/drone/push Build is passing

This commit is contained in:
2026-11-07 02:16:12 +01:00
parent e421dcfc15
commit 47cd4fa2bc
10 changed files with 156 additions and 129 deletions
+28 -2
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) == {"telemetryEnabled", "automaticConvoyEnabled", "keepWeaponOnUnconscious", "selfEpinephrine", "planeReconEnabled", "restartOnEmpty", "showAllSites"}
assert len(keys) == len(set(keys)) and set(settings) - set(keys) == {"crashLootMode", "telemetryEnabled", "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.
@@ -48,12 +48,21 @@ else:
names = re.findall(r'\bName "([^"]+)"', layout.read_text())
assert 'RandomDay_startTimeMinutes' in names
assert 'PlaneOn' in names and 'PlaneOff' in names
assert 'CrashLootRandom' in names and 'CrashLootCustom' in names
assert 'crashLootMode' not in names and 'Apply_crashLootMode' not in names
assert all(n in names for n in ('OpenPlane','Sidebar_HQ','PlanePage','Selected_OpenPlane'))
for signature in ('static array<string> Pages(', 'static array<string> Navigation('):
for n in re.findall(r'"([A-Za-z]+)"', body(navigation, signature)): assert n in names
assert 'Apply_planeReconEnabled' not in names # Saved-state toggle pair, not a numeric edit.
assert len(names) == len(set(names)), 'Menu widget names must be unique'
choice_sets = {key: re.findall(r'"([^"]+)"', values) for key, values in re.findall(r'if \(key == "([^"]+)"\) return \{([^}]+)\}', body(fields, 'static array<string> Choices('))}
assert set(choice_sets) == {'startMode', 'aiDoctrine', 'aiShadow', 'objectGuardEvery', 'objectGuardHQMode', 'weatherProfile'}
for key in keys:
if key in choice_sets:
assert key not in names and 'Apply_' + key not in names
assert all('Choice_' + key + '_' + choice in names for choice in choice_sets[key])
assert all(prefix + key in names for prefix in ('Saved_', 'Help_'))
continue
assert all(name in names for name in (key, 'Apply_' + key, 'Saved_' + key, 'Help_' + key))
if key not in ('shoreBoatCount','shoreBoatChancePercent','shoreBoatCrossingChancePercent','crashLootMode','crashSiteCount','crashHelicopterChancePercent','crashRifleCount','crashMagazineCount','crashAntiAirChancePercent','crashRocketChancePercent','radioIntelSiteCount','droneReconCount','droneCooldownSeconds','sarkaAngerPerCar','emptyRestartHours','aiDoctrine','aiShadow','motorbikeCount','planeAltitudeMetres','planeReplacementSeconds','planeSectorSeconds','fieldCampCount','fieldHospitalCount','startMode','garageVehicleCount','garageChancePercent','garageRadiusMetres','garageSpawnIntervalSeconds','civilianTrafficCount','roadblockCount','largeRoadblockCount','mainRoadWidthMetres','hunterSpawnDistanceMetres','objectGuardCount','objectGuardEvery','objectGuardHQMode','startWeaponCount','startMagazineCount'): assert ('Default_' if key == 'viewDistanceMetres' else 'Random_') + key in names
else: assert 'Random_startMode' not in names
@@ -66,14 +75,19 @@ menu_format = body(ROOT/'addons/core/Scripts/Game/ERR/00_Core/ERR_JournalText.c'
follow = body(fields, 'static bool FollowJournal(')
command = body(fields, 'static string Command(')
available = body(fields, 'static bool Available(').replace('Keys().Contains(key)', 'Known(key)')
choices = body(fields, 'static array<string> Choices(').replace('array<string>', 'Array<string>')
choice_key = body(fields, 'static string ChoiceKey(').replace('array<string>', 'Array<string>')
known = ' || '.join('key == "' + key + '"' for key in keys)
run(r'''
#include <cassert>
#include <string>
#include <vector>
#include <algorithm>
struct string : std::string {
using std::string::string;
string(std::string s):std::string(s){}
bool StartsWith(string prefix){return rfind(prefix,0)==0;}
template<class T> void Split(string separator,T& result,bool skipEmpty){assert(!skipEmpty);size_t start=0,end;while((end=find(separator,start))!=npos){result.push_back(substr(start,end-start));start=end+separator.size();}result.push_back(substr(start));}
bool IsEmpty(){return empty();}
int Length(){return size();}
int IndexOf(string s){auto i=find(s);return i==npos?-1:int(i);}
@@ -81,13 +95,25 @@ struct string : std::string {
void Replace(string a,string b){size_t i=0;while((i=find(a,i))!=npos){replace(i,a.size(),b);i+=b.size();}}
string Trim(){auto a=find_first_not_of(" \t\r\n");if(a==npos)return "";auto b=find_last_not_of(" \t\r\n");return substr(a,b-a+1);}
};
template<class T> struct Array:std::vector<T>{using std::vector<T>::vector;int Count(){return this->size();}bool Contains(T v){return std::find(this->begin(),this->end(),v)!=this->end();}};
struct ERR_JournalText {
''' + menu_format + r'''
};
struct Fields {
static bool Known(string key) {return ''' + known + ';}\n' + command + '\n' + available + '\n' + follow + '\n' + journal + r'''
static bool Known(string key) {return ''' + known + ';}\n' + choices + '\n' + choice_key + '\n' + command + '\n' + available + '\n' + follow + '\n' + journal + r'''
};
int main() {
assert(Fields::ChoiceKey("Choice_startMode_prison")=="startMode");
assert(Fields::ChoiceKey("Choice_startMode_crash")=="startMode");
assert(Fields::ChoiceKey("Choice_aiDoctrine_coordinated")=="aiDoctrine");
assert(Fields::ChoiceKey("Choice_aiShadow_off")=="aiShadow");
for(string choice:{"authored","clear","overcast","rain","random"})
assert(Fields::ChoiceKey(string("Choice_weatherProfile_")+choice)=="weatherProfile");
assert(Fields::ChoiceKey("Choice_objectGuardEvery_4")=="objectGuardEvery");
assert(Fields::ChoiceKey("Choice_objectGuardHQMode_2")=="objectGuardHQMode");
for(string invalid:{"Apply_weatherProfile","Choice_weatherProfile_4","Choice_objectGuardEvery_0","Choice_objectGuardHQMode_3","Choice_startMode_random","Choice_aiShadow_on_extra","Choice_"}) assert(Fields::ChoiceKey(invalid).empty());
assert(Fields::Choices("crashRifleCount").empty());
assert(Fields::Choices("startDateCode").empty());
string raw="#24 t=7s M3 observe guard";
assert(Fields::JournalText(raw)=="No. 24 t=7s M3 observe guard");
assert(raw=="#24 t=7s M3 observe guard");