YappersHQ.Progression.Shared 1.0.1

dotnet add package YappersHQ.Progression.Shared --version 1.0.1
                    
NuGet\Install-Package YappersHQ.Progression.Shared -Version 1.0.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="YappersHQ.Progression.Shared" Version="1.0.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="YappersHQ.Progression.Shared" Version="1.0.1" />
                    
Directory.Packages.props
<PackageReference Include="YappersHQ.Progression.Shared" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add YappersHQ.Progression.Shared --version 1.0.1
                    
#r "nuget: YappersHQ.Progression.Shared, 1.0.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package YappersHQ.Progression.Shared@1.0.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=YappersHQ.Progression.Shared&version=1.0.1
                    
Install as a Cake Addin
#tool nuget:?package=YappersHQ.Progression.Shared&version=1.0.1
                    
Install as a Cake Tool

<div align="center"> <h1><strong>Progression</strong></h1> <p>Missions, achievements and a season pass for CS2 — one counting engine, driven by whatever your gamemodes emit.</p> </div>

<p align="center"> <a href="https://github.com/Kxnrl/modsharp-public"><img src="https://img.shields.io/badge/framework-ModSharp-5865F2?logo=github" alt="ModSharp"></a> <img src="https://img.shields.io/badge/game-CS2-orange" alt="CS2"> </p>


Players earn progress by playing. Objectives are defined as an event plus a filter — "10 AK headshots", "kills in Long A", "3 bomb plants" — so a new mission is authored, not coded. Daily, weekly, monthly, all-time achievements and a seasonal pass are all the same machinery with different reset rules.

🧩 How it fits together

Piece Responsibility
Progression.Core Pulls definitions, matches events locally, batches progress, runs reward commands. Owns buffering, retry and idempotency
Progression.Shared IProgressionShared (what gamemodes reference) and IProgressionStore (what storage implements)
Progression.Database Storage: MySQL. Install it and you need no backend at all
Progression.Ui !missions menu and completion announcements. Delete it if you want a different UI
Progression.Events.CS2 Standard CS2 events. The first satellite, not part of Core

Core contains no game knowledge. It does not know what a kill is. Every event source is a satellite — including the CS2 one — so adding a gamemode needs no change to Core. Deleting Progression.Events.CS2 leaves Core building and running.

🔌 Emitting from your gamemode

Reference YappersHQ.Progression.Shared with ExcludeAssets="runtime", resolve in OnAllModulesLoaded, and declare what you can emit:

var progression = moduleManager
    .GetOptionalSharpModuleInterface<IProgressionShared>(IProgressionShared.Identity)?.Instance;

progression?.RegisterEvent("duels.arena_won",
[
    new ProgField("arena",  ProgFieldKind.Int),
    new ProgField("weapon", ProgFieldKind.String),
]);

// later, when it happens
progression?.Emit("duels.arena_won", steamId, new Dictionary<string, object?>
{
    ["arena"]  = arenaId,
    ["weapon"] = weaponName,
});

Missions filtering on duels.arena_won can then be authored on the website with no code change — that is the entire point of the split.

Event keys are permanent. They are stored in mission definitions that live in a database for months, so namespace them by owner and never derive one from a runtime id.

Deploying it for the first time: docs/DEPLOY.md — ordered steps, each with a way to tell whether it worked, because several failures here are silent by construction (a wrong catalogue is empty, not an error).

💾 Storage — pick one

Progression stores nothing itself. Choose how it persists:

Option What you run When
Progression.Database A MySQL server You just want it to work. Owns its schema, migrates on load
Your own store One class You already have a database, or want Postgres/Redis/an existing API
Built-in HTTP client A web service You have a website and want authoring UI and a shared fleet

With Progression.Database, docs/example-missions.sql gives you eight working missions to start from — run it after the first boot, since objectives are only served for events a server has reported it can emit.

Writing your own is one project implementing IProgressionStore, registered in PostInit. Core resolves it in OnAllModulesLoaded and prefers it over HTTP.

You do not implement delivery. Buffering, idempotency keys, disk spill and retry stay in Core — a store is asked to write deltas and say what completed, never to re-derive exactly-once semantics. What it must guarantee is in docs/BACKEND_PROTOCOL.md.

⚙️ Configuration

Copy the examples in .assets/configs/ to <sharp>/configs/ and edit.

progression.json — Core:

Key Default Meaning
backend-url "" Backend base URL. Ignored when a store module is installed
server-key "" Sent as Authorization: Bearer. Not shared between servers
flush-seconds 10 How often buffered progress is sent
catalog-refresh-seconds 900 Backstop for definition changes
state-refresh-seconds 60 How often the in-game mission list refreshes
min-players 5 Below this many real players (bots excluded), nothing is tracked
track-during-warmup false Whether warmup counts. Off by default — warmup is unlimited respawns and no stakes
spill-max-age-days 7 How old an undelivered delta may be on disk before it's discarded instead of replayed. 0 = never discard
stat-fields {} Which event fields to count as stat breakdowns, per event key. Empty = totals only
stat-flush-seconds 300 How often counted statistics are sent. 0 disables statistics

progression-database.json — only if using Progression.Database:

Key Default Meaning
connection-string "" MySQL. Needs CREATE TABLE on first run
gamemode "" Scopes which objectives this server is offered. Empty = all
timezone "UTC" Zone daily/weekly boundaries are measured in

progression-ui.json — only if using Progression.Ui:

Key Default Meaning
spawn-hint false Tell a player, shortly after joining, that they have missions waiting. Off by default — it's the only unprompted message this plugin sends
spawn-hint-delay-seconds 20 How long after joining to say it. Not zero — a line printed on load screen arrives unread

🔧 How it works

Servers match filters locally and post only progress deltas — posting raw events would turn every kill on every server into an HTTP call. Gameplay never blocks on the network: emissions land in a buffer, a timer flushes, and a backend outage spills to disk and replays rather than losing progress.

Rewards are decided by the backend but must run on a server, so pending grants ride back in the response to the flush the server already made. No RCON, no inbound connection, no port to open.

Every delta carries an idempotency key generated at enqueue time, so replaying a batch that partially landed is safe rather than merely unlikely to double-count.

⌨️ Commands

Command What it does
!missions · !mission · !progress · !daily Your current missions and progress

Uses MenuManager when present and falls back to a chat listing when not. Mission titles resolve as locale keys, so a mission set can ship translations; a plain literal title still works.

📦 Build

dotnet build -c Release

Outputs .build/modules/ and .build/shared/Progression.Shared/.

To verify the MySQL store against a throwaway database (it writes rows — never point it at production):

dotnet run --project tools/StoreSelfTest -- "Server=127.0.0.1;User ID=u;Password=p;Database=throwaway"

29 checks covering idempotent replay, compare-and-swap progress, single-claim completion, grant leasing and period boundaries.

<div align="center">

Made with ❤️ by yappershq

</div>

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.1 105 8/10/2026
1.0.0 110 8/3/2026