#22601·OpenRA

Campaign progression: persistent state store and Campaign Lua API

Author: penev92Created Sep 3, 2026Updated Sep 3, 2026
LabelsScripting

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:

lua
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)
  • Campaign script 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.