Campaign progression: persistent state store and Campaign Lua API
Part 1 of #22600. First milestone: missions can persist arbitrary campaign- and mission-scoped data from Lua and read it back — the foundation every later milestone consumes. This is the "global (per-campaign) data store that maps can save and query arbitrary key-value pairs to via a Lua API" @pchote described in #7213 back in 2014.
API
A new Lua global, available to all scripted worlds:
Campaign.ReadCampaignData(campaignId) -- → table (empty if nothing saved yet)
Campaign.ReadMissionData(campaignId, missionId) -- → table
Campaign.WriteCampaignData(campaignId, dataTable)
Campaign.WriteMissionData(campaignId, missionId, dataTable)Storage: <SupportDir>/Campaigns/<modId>/<campaignId> containing campaign.json and missions/<missionId>.json - JSON PICKED FOR VERSATILITY, BUT THERE WAS AN ARGUMENT FOR YAML. Tables may nest tables/strings/numbers/booleans; functions and userdata are rejected with a script error.
Semantics
- Writes replace the whole document; reads of missing data return an empty table.
- Writes are ignored while watching a replay. The data is local to the player's machine — using it to drive game logic in multiplayer would desync (a harder guard can come with milestone 2).
- Cross-campaign reads are intentional (they enable C&C3-style unlocks).
Work items
- Lua table ⇄ JSON conversion (nested tables, array/object mapping, clear script errors for unstorable values)
-
Campaignscript global wiring the two together, with proper API documentation strings
Implementation lives entirely in OpenRA.Mods.Common — no engine changes. JSON via Newtonsoft.Json (already shipped).
Out of scope
Any UI, any schema conventions (milestone 2 defines the standard keys), bundles, achievements.
Source: OpenRA/OpenRA