Winners-stay 1v1 / NvN arena plugin for CS2 — a from-scratch ModSharp port of K4-Arenas. No CounterStrikeSharp runtime.
Arenas discovers spawn points on map load and clusters them into paired arenas, then runs a winners-stay queue: every round it drains each arena's result into ranked winner/loser queues, folds in players waiting in queue plus any pending duel challenges, re-pairs everyone across the arenas by their common round-type preference, gives each side a loadout, and isolates arenas from each other (no cross-arena damage or flash). Players persist per-weapon and per-round-type preferences, can go AFK, and can challenge each other to a duel. There is no ELO/points/rank persistence — the only "ranking" is the transient winners-stay ordering, recomputed each round. Special round-types (Headshot-only, No-crosshair, Nades, One-tap) ship as a separate addon that registers through the public API.
Port of KitsuneLab-Development/K4-Arenas + Letaryat/K4-Arenas-Special-Rounds, rebuilt in ModSharp house idioms.
Copy the build output into your ModSharp install (<sharp> = your sharp directory):
| From | To |
|---|---|
.build/modules/Arenas.Core/ |
<sharp>/modules/Arenas.Core/ |
.build/shared/Arenas.Shared/ |
<sharp>/shared/Arenas.Shared/ |
.assets/locales/arenas.json |
<sharp>/locales/arenas.json |
.assets/configs/arenas.cfg |
<sharp>/../csgo/cfg/arenas/arenas.cfg (auto-created on first run) |
Optional special rounds:
| From | To |
|---|---|
.build/modules/Arenas.SpecialRounds/ |
<sharp>/modules/Arenas.SpecialRounds/ |
Arenas.SpecialRounds/.assets/locales/arenas.specialrounds.json |
<sharp>/locales/arenas.specialrounds.json |
Restart the server (or change map) to load. Config arenas.json is auto-generated in <sharp>/configs/arenas/ on first run.
Uses the ModSharp first-party modules (ship with ModSharp): CommandCenter (chat commands), MenuManager (preference menus), LocalizerManager (all user-facing text), ClientPreferences (persist weapon/round prefs per SteamID), TargetingManager (resolve challenge target by name). Each is resolved optionally — the plugin degrades gracefully (menus/commands/persistence simply become unavailable) if a module is absent.
External plugins:
| Plugin | Required? | Why |
|---|---|---|
| Arenas.SpecialRounds | ⚪ Optional | Adds the Headshot-only / No-crosshair / Nades / One-tap round types. Registers through IArenasShared; the base game works without it. |
LevelRanks (if installed) runs alongside Arenas out of the box, awarding its own global points from kills — Arenas needs zero integration.
All commands are open (no permission). Chat triggers: !, /, or .; console prefix ms_. Aliases are configurable in arenas.json.
| Command | Aliases | Description | Permission |
|---|---|---|---|
!queue |
— | Show your position in the waiting queue. | — |
!guns |
!gunpref, !weaponpref |
Open the weapon-preference menu (per category). | — |
!rounds |
!roundpref |
Open the round-type-preference toggle menu. | — |
!afk |
— | Toggle AFK (move to spectator, re-queue at the tail). | — |
!challenge <name> |
!duel <name> |
Challenge another player in an arena to a 1v1 duel. | — |
!caccept |
!capprove |
Accept a pending challenge. | — |
!cdecline |
!cdeny |
Decline a pending challenge. | — |
!arenas |
— | Print current arena ladder assignments in chat (admin debug). | admin-permission |
configs/arenas/arenas.json (auto-generated on first run). Key sections:
| Setting | Default | Meaning |
|---|---|---|
command-settings.*-commands |
see above | Command name/alias lists. |
command-settings.center-menu-mode |
true |
Prefer center-HUD menus over chat menus. |
command-settings.center-announce-mode |
true |
Show round/arena announcements as center-HUD text (falls back to chat). |
command-settings.freeze-in-center-menu |
true |
Freeze players while a center-HUD preference menu is open. |
command-settings.admin-permission |
"@css/generic" |
AdminManager permission flag required for the !arenas admin command. |
compatibility-settings.force-arena-clantags |
false |
Reflect the arena tag (ARENA n / WAITING / AFK / CHALLENGE) into the scoreboard clan tag. |
compatibility-settings.disable-clantags |
false |
Never touch clan tags. |
compatibility-settings.block-damage-of-not-opponent |
false |
Block damage between players in different arenas. |
compatibility-settings.block-flash-of-not-opponent |
false |
Cancel cross-arena flash blinds. |
compatibility-settings.give-knife-by-default |
true |
Give a knife in every loadout. |
compatibility-settings.prevent-draw-rounds |
true |
Force a winner instead of a draw when teams tie. |
allowed-weapon-prefs.* |
all true |
Which weapon categories appear in the guns menu. |
default-weapon-settings.default-* |
null |
Optional fixed weapon per category (overrides preference). |
default-weapon-settings.default-round |
Arenas_Round_Rifle |
Fallback round type when two players share none. |
round-settings |
[] |
Custom round-type list; empty = the 12 built-ins. |
Economy/round scoring (mp_maxmoney, mp_teamcashawards, mp_maxrounds, …) is neutralised automatically via arenas.cfg and direct convar sets — arenas drive round ends themselves.
ArenaFinder clusters info_player_* spawns into arena pairs by median enemy-distance. Each round_prestart the ladder is rebuilt (winners up, losers down), accepted duel challenges are seated first, then the queue is re-paired across arenas by common round-type preference. Cross-arena isolation blocks damage at a PlayerDispatchTraceAttack pre-hook and cancels flash at player_blind. Preferences persist as IClientPreference cookies keyed by SteamID (weapon classnames + enabled round names) — no database required. See docs/PORT_PLAN.md for the full architecture.
Other plugins add custom round types + query arena state through IArenasShared (resolve in OnAllModulesLoaded):
var api = sharpModuleManager
.GetOptionalSharpModuleInterface<IArenasShared>(IArenasShared.Identity)?.Instance;
int id = api.RegisterRoundType("My_Round_Key", teamSize: 1, enabledByDefault: false, OnStart, OnEnd);
// ... api.GetArenaPlacement(steamId), api.FindOpponents(steamId), api.IsAfk(steamId), ...
api.UnregisterRoundType(id); // in ShutdownArenas.SpecialRounds is a reference consumer — see its source for the HeadshotOnly / NoCrosshair / Nades / OneTap implementations.
dotnet build -c ReleaseOutputs:
.build/modules/Arenas.Core/Arenas.dll.build/shared/Arenas.Shared/Arenas.Shared.dll.build/modules/Arenas.SpecialRounds/Arenas.SpecialRounds.dll.build/modules/Arenas.Vip/Arenas.Vip.dll
Arenas ships no ranking of its own — pair it with the house LevelRank plugin, which persists
points (MySQL) and scores by kills/deaths. On an arena server set the round win/loss scores to 0
in levelrank.jsonc (RoundWins / RoundLosses → 0): global round win/loss is meaningless when
teams are re-paired every round, whereas per-kill scoring already ranks players correctly. Arenas
does not read LevelRank scores (pairing is a winner-ladder, not skill-seeded), so no wiring is needed
— just run both. Note: if LevelRank's scoreboard module is on, it and Arenas both write m_iScore;
prefer disabling LevelRank's scoreboard spoof on arena servers so the arena ladder display wins.
Core is VIP-agnostic (never admin flags): VIP goes through the IArenasVipProvider contract,
whose default says nobody is VIP. Deploy the optional Arenas.Vip module (bridges the house
Vip.Shared IVipShared) on servers that run the Vip plugin — Core adopts it automatically via the
module registry. VIP-less servers just omit the module.
Port of KitsuneLab-Development/K4-Arenas and Letaryat/K4-Arenas-Special-Rounds. Ranking via LevelRank; VIP via the house Vip plugin.
Made with ❤️ by yappershq
⭐ Star this repo if you find it useful!