Skip to content

Repository files navigation

Duels

1v1 duels gamemode for CS2 — queue up, get seated in an arena, fight, climb the ladder.

ModSharp CS2 License Stars


Everyone who joins goes into a queue and gets paired into an arena. Win your duel and you move up, lose it and you move down — which arena you are in is your rank, so there is no ranking plugin to run and no database to host. Each pairing rolls a round type both players have enabled, and weapon preferences persist between sessions.

Arenas can be placed by hand on any map with an in-game editor, so you are not limited to maps built with paired arena spawns — and ready-made arenas ship for all nine active-duty maps, so it plays out of the box on dust2, mirage, inferno, nuke, ancient, anubis, overpass, train and vertigo.

🚀 Install

Copy into your ModSharp install (<sharp> = your sharp directory):

From To
.build/modules/Duels.Core/ <sharp>/modules/Duels.Core/
.build/shared/Duels.Shared/ <sharp>/shared/Duels.Shared/
.assets/locales/duels.json <sharp>/locales/duels.json
.assets/configs/duels/maps/ <sharp>/configs/duels/maps/

Restart the server (or change map) to load. configs/duels/duels.json is written with defaults on first run, and duels.cfg installs itself into csgo/cfg/duels/ and is exec'd on every map load.

⚠️ Never place a second copy of Duels.Shared.dll in the module folder — it must resolve from shared/, or its types get a separate assembly-load-context identity and API lookups silently return null.

🧩 Dependencies

Uses the ModSharp first-party modules (they ship with ModSharp): CommandCenter (every command), MenuManager (the weapon/round preference menus), ClientPreferences (persisting those preferences), LocalizerManager (all player-facing text), AdminManager (gating the two admin commands), TargetingManager (resolving a player by partial name for challenges).

Every one is resolved optionally — the plugin loads and runs without any of them, losing only the feature each powers.

⌨️ Commands

Command Aliases Description Permission
!queue Your current position in the waiting queue.
!guns gunpref, weaponpref Weapon preference menu, per category.
!rounds roundpref Enable/disable round types for yourself (at least one must stay on).
!afk Toggle AFK — leaves the queue and moves you to spectate.
!challenge <name> duel Challenge a specific player to a direct duel.
!caccept capprove Accept a pending challenge.
!cdecline cdeny Decline a pending challenge.
!duels List every arena with its round type and occupants. @duels/admin
!arenaedit <sub> Place arenas by hand — see below. @duels/admin

Command names and aliases are configurable.

Placing arenas

Stand where a player should spawn, run add twice — once per side — then save.

Subcommand Effect
add Store your current position and facing as a spawn. Two calls make one arena.
cancel Discard a half-placed arena.
list List placed arenas with coordinates.
del <n> Remove arena n.
clear Empty the working set.
save Write to disk and hot-reload the live arena list.
revert Discard unsaved edits and re-read the file.
tp <n> Teleport to arena n to check placement.

Edits stay in memory until save, so a misplaced spawn costs nothing.

⚙️ Configuration

configs/duels/duels.json (auto-generated on first run):

Setting Default Meaning
round-timer-seconds 30 Hard cap on a live round, timed from freeze-end. 0 disables it.
round-settings [] Empty uses the ten built-in round types; non-empty replaces them entirely.
force-arena-clantags true Show each player's arena in their clan tag.
disable-clantags false Suppress clan tag handling altogether.
block-damage-of-not-opponent false Block damage between players in different arenas.
block-flash-of-not-opponent false Block flash between players in different arenas.
block-sound-of-not-opponent true Strip players in other arenas from a player's sound recipients.
arena-bounds.enabled true Keep players inside their own arena.
arena-bounds.radius-scale 1.6 Multiplier on half the spawn separation. Higher = roomier.
arena-bounds.padding 220 Flat units added on top, so tight arenas aren't claustrophobic.
arena-visibility.enabled true Hide players in other arenas, per-viewer.
arena-visibility.hide-weapons true Hide their weapons too (else guns float in mid-air).
arena-visibility.transmit-channel 3 Dedicated channel so it composes with other transmit plugins.
arena-visibility.refresh-seconds 0.5 Full re-push interval; also reconciles mid-round weapon changes.
give-knife-by-default true Always include a knife.
prevent-draw-rounds true Resolve draws rather than leaving a round unwon.
scoreboard-arena-grouping true Group the scoreboard by arena rank.
pistol-armor-balance true Helmet off, kevlar only for cheap pistols, on pistol rounds.
allowed-weapon-prefs all true Per-category toggles: rifle, sniper, smg, lmg, shotgun, pistol.
default-weapon-settings Fallback weapon per category, plus default-round.

Round types — ten built-ins, all 1v1: Rifle · Sniper · Shotgun · Pistol · Scout · AWP · Deagle · SMG · LMG · Knife. Rifle/Sniper/Shotgun/SMG/LMG use each player's preferred weapon in that category; Scout, AWP and Deagle force a specific one. A pairing only rolls a type both players have enabled.

Custom arenasconfigs/duels/maps/<mapname>.json, written by arenaedit save and read on every map load. When a file exists it replaces spawn-clustering for that map.

24 arenas per map ship for the nine active-duty maps, derived from OpenPrefirePrac's practice profiles (MIT — see THIRD-PARTY-NOTICES.md) by tools/prefire_to_arenas.py. Its bot positions are placed to be shot at from the player position, which implies line of sight, so pairing them gives duels across real competitive angles. They are a starting set: trim what you don't like with arenaedit list / tp <n> / del <n>, then save.

[
  {
    "Id": 0,
    "Spawn1": { "posX": -7994.4, "posY": -3898.1, "posZ": -192.3, "angleX": 8.9, "angleY": -41.9, "angleZ": 0 },
    "Spawn2": { "posX": -7261.5, "posY": -4598.2, "posZ": -395.9, "angleX": -15.1, "angleY": 133.2, "angleZ": 0 }
  }
]

🔧 How it works

Players sit in a queue and are drained into arenas at round start, one duel per arena. When a duel ends the winner moves toward arena 1 and the loser moves the other way, so standing is positional rather than a stored score. Round ends are driven by the plugin rather than CS round-win conditions, which is why the shipped duels.cfg neutralises team money, score and round limits.

Arena geometry comes from a hand-placed spawn file when one exists, otherwise from clustering the map's info_player_terrorist/counterterrorist entities into pairs. Hand-placed spawns also carry a facing angle, so both players start looking at each other.

Players are kept in their own arena by a boundary derived from its two spawns — a cylinder centred between them — enforced with a velocity nudge rather than a teleport. Nothing to draw or maintain: every arena is bounded automatically. Sounds from other arenas are stripped per-recipient, because with two dozen arenas on one map, hearing every other duel makes sound cues worthless.

Players in other arenas are hidden per-viewer. The pawn itself cannot be transmit-hooked, so the player's controller is hooked instead — the engine then clears the pawn's transmit bit for the denied receiver while leaving the controller intact, so scoreboard and team data still work. Because the denial is per-receiver, you stay fully visible to your own opponent and disappear for everyone else. Held weapons are hidden with their owner, otherwise guns float where an invisible player stands.

🧩 Public API

Other plugins consume IDuelsShared (resolve in OnAllModulesLoaded) for arena placement lookups, AFK state, and RegisterRoundType to add their own round behaviour:

var api = sharpModuleManager
    .GetOptionalSharpModuleInterface<IDuelsShared>(IDuelsShared.Identity)?.Instance;

Reference Duels.Shared with ExcludeAssets="runtime" and never ship your own copy of the DLL.

📦 Build

dotnet build -c Release

Outputs .build/modules/Duels.Core/Duels.dll and .build/shared/Duels.Shared/Duels.Shared.dll.

🙏 Credits

Derived from yappershq/Arenas, our ModSharp port of K4ryuu/K4-Arenas (GPL-3.0) — the original arena gamemode and the source of the ladder mechanic. Licensed under the same terms as that upstream, see LICENSE.

Feature set informed by Letaryat/CS2_Poor_Duels and zhw1nq/zDuels (CounterStrikeSharp). No code is taken from either — neither declares a license — but the custom-spawn file format is deliberately compatible, so spawn files in that shape load unmodified. Detail in docs/PROVENANCE.md.


Made with ❤️ by yappershq

⭐ Star this repo if you find it useful!

About

1v1 duels gamemode for CS2 — queue-based arena ladder with an in-game arena editor.

Topics

Resources

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages