StatsCore
Stat-tracking library for REPO mods: define a stat, two-way multiplayer sync, career persistence, query-by-day, and a built-in pack of player stats.
Screenshots 1 screenshots
About this mod Author’s original text
StatsCore
A stat-tracking library for R.E.P.O. mods. Define a stat once, then read and write it through a typed handle. StatsCore handles the parts every scorekeeping mod gets wrong: multiplayer sync, lifetime resets that follow the game's real scene flow, on-disk persistence, and querying history by day. It also ships a built-in pack of player stats any mod can read without writing a single tracker.
A note from Vippy
After way too long of a break, I'm finally back and working on these again:D Every one of my mods just got a full pass: bugs fixed, reports read, a few things I'd always meant to do. New builds and fixes land in Vippy's Discord before they hit, so come hang out. Thanks for sticking around.
StatsCore is a library. On its own it quietly records the built-in pack; its real job is to be a dependency for other mods. A drinking-game mod, an end-of-run scoreboard, a "reward the unluckiest Semibot" mod: all of them just call into StatsCore instead of reinventing the bookkeeping.
A screen to browse your stats in-game is coming in a later release. This one is the engine underneath it.
Install
Drop StatsCore.dll into BepInEx/plugins. Depends only on BepInEx. If you use a mod manager, add it as a dependency of your mod and the manager handles the rest.
Quick start
using StatsCore;
// Define once (e.g. in your plugin's Awake). Key convention: "author.modname.stat".
var drinks = Stats.Define("vippy.pourdecisions.drinks", StatScope.PerPlayer, StatLifetime.Career).Describe("Drinks", "Sips taken this career.", category: "Party").TrackDaily();
// Write. For Career this always lands locally; for synced lifetimes only the host's write lands.
drinks.Add(steamID, 1);
// Read.
int total = drinks.Get(steamID);
int today = drinks.GetToday(steamID);
int thisWeek = drinks.GetLastDays(steamID, 7);
Another mod can read your stat by its key without referencing your assembly:
int n = Stats.Get("vippy.pourdecisions.drinks")?.Get(steamID)?? 0;
Scopes and lifetimes
A stat has a scope (who it's tracked for) and a lifetime (when it resets and whether it persists).
| StatScope | meaning |
|---|---|
| PerPlayer | one value per steamID |
| Team | a single shared value (use GetTeam / SetTeam / AddTeam) |
| StatLifetime | resets | synced? | persists? |
|---|---|---|---|
| Level | every scene / level transition | yes (host-authoritative) | no |
| Run | when a new run starts (back at the lobby menu) | yes (host-authoritative) | no |
| Session | never until the game closes | yes (host-authoritative) | no |
| Career | never | no (local to each machine) | yes, to disk |
The Run reset correctly distinguishes the truck "lobby" between levels (Run stats survive) from the real lobby menu that starts a fresh run (Run stats clear). That distinction is the thing most mods get wrong.
The sync model
Synced stats (Level, Run, Session) flow one of two ways. Career stats stay local to each machine.
Host-authoritative (default)
The host owns the value. Call Add/Set from host-gated code; it replicates to every client running StatsCore, and late joiners receive a full snapshot the moment they enter the room. A write on a client is dropped with a debug log, so gate your writes:
if (Stats.IsHost) myStat.Add(steamID, 1); // true on the host, or in singleplayer/offline
Client-authoritative (push)
When the client is the one that knows the value, mark the stat ClientAuthoritative(). Each client owns its own player's slot and pushes changes to everyone, the host included. No host gate, and no client can write another player's slot (spoofed writes are rejected on receipt). This is the clean way to feed a host-side aggregate from per-client counters:
// every client, defined the same way
var sips = Stats.Define("vippy.pourdecisions.drinks", StatScope.PerPlayer, StatLifetime.Session).ClientAuthoritative();
sips.Add(localSteamID, 1); // your own slot; lands on every machine
int crewTotal = sips.Sum(); // the host (and everyone) sees the whole c
Install & use Install: BepInEx plugin
- The archive from playbuff is the mod itself — usually a BepInEx plugin (a .dll or a plugin folder once unzipped).
- Recommended: use a community mod manager (such as r2modman) — pick this game, import the mod, and the manager sets up BepInEx and its dependencies for you.
- Manual route: install the BepInEx loader in the game folder, then drop the unzipped .dll or plugin folder into BepInEx/plugins/.
- Launch the game — the BepInEx console logs every plugin it loads; seeing this mod in the log means it is installed.
Mods with listed dependencies need those installed too, or they will not load. Options are usually changed in-game or in the plugin's config file.
Install these first, or the mod will not work: