|
VoltMod
C++23 framework for CS2 server plugins
|
Settings are a default-initialized struct that mirrors the JSON file, loaded through VoltMod::Options. Include <VoltMod/App/Config.hpp> in the plugin's own Config.hpp; it carries the configuration types and the JSON layer, which <VoltMod/Api.hpp> deliberately does not.
Each public member name is its JSON key, so nothing has to be registered. A missing key keeps the member's C++ initializer. JSONC comments and unknown keys are accepted; a missing file, a parse error or a wrong value type fails the load and names the offending key with its line and column.
Reflection reads member names off the type, so the struct needs external linkage: declare it at namespace scope, not inside a function or an anonymous namespace.
Document each key with a comment beside it in the shipped settings.jsonc; that file is what an operator reads.
LoadConfig runs a required Configuration load check that reads addons/voltmod/plugins/<plugin>/configs/settings.jsonc, then loads the plugin's translations and applies plugin.locale when the settings struct embeds VoltMod::StandardPluginSettings, and returns the config. When the settings step fails the defaults stand, and the framework refuses the plugin before Load runs, so members below Config must not act outside the plugin in their constructors: a server convar or a published service waits for Load.
A config type with a builder is passed in, and LoadConfigOptions changes either half:
It calls your config type's LoadSettings(path) when it has one, otherwise Options::Load(path).
When settings need checking, clamping or values derived from them, name a second type for what the plugin publishes and the function that builds it. Options runs the builder on a local copy and publishes the result in one move, so a reload that fails leaves the previous snapshot in place and no caller ever sees a half-validated value.
Without the wrapper, Get() returns the snapshot itself, so options.Get().Values is the settings. Wrapping it keeps plugin.locale reachable and gives the rest of the plugin named accessors.
VoltMod/App/Config/Validation.hpp has the common helpers. BuildSnapshot takes the raw settings by value, so each one works on a local copy:
Options remembers the file it loaded, and Reload() parses it again, rebuilds the snapshot and swaps it in. A failure returns the error and changes nothing, so a command can report the offending key and keep serving the settings already in memory.
DatabaseConfig uses lowercase field names so a JSON section maps straight onto it:
Database has the field list and one JSON example per driver.
Player-facing text lives in per-language files under translations/ (en.json, ru.json, ...), flat key to string with {token} placeholders:
The player's language lives in the host, so one plugin's language setting reaches every other plugin's text; the host clears it when the slot changes hands. On connect the framework sets it from the player's Steam client language (cl_language) unless a plugin already has; a language no plugin translates falls back to the server's. A key nothing carries is returned as itself. Command replies (Caller::Ok/Fail/Say), Flow validation errors and Messages::SendKey all resolve through this service in the addressed player's language. The framework reserves a few keys for its own error replies; see Commands.