CS2 servers
MatchZy integration
What FLICKED needs from the CS2 side, checked against MatchZy's source and verified on a real server rather than written from memory.
MatchZy is MIT licensed and does the hard part of running a CS2 match: knife rounds, pauses, demos, the veto flow. FLICKED would not have a working match without it. The quirks recorded here are where a plugin serving everybody meets a backend that wants one particular thing. They are written down so FLICKED handles them correctly, not as complaints.
How a match starts#
MatchZy loads a match from JSON, from a local file or over HTTP. matchzy_loadmatch_url does a
GET and accepts one optional custom header, so FLICKED can require a token on its config
endpoint rather than leaving it open.
matchzy_loadmatch <filepath> # relative to csgo/
matchzy_loadmatch_url <url> [header name] [header value]So the flow is: claim a server from the pool, then send it over RCON:
css_plugins reload MatchZy
matchzy_loadmatch_url "https://api.example/api/matches/27/config" "X-Match-Token" "<token>"The config FLICKED serves#
GET /api/matches/{id}/config, authenticated by the per-match X-Match-Token. It is not
public, because it lists every player's Steam ID.
{
"matchid": 27,
"team1": {
"name": "Team A",
"players": { "76561198xxxxxxxxxx": "PlayerNickname" }
},
"team2": { "name": "Team B", "players": { } },
"num_maps": 1,
"maplist": ["de_mirage"],
"map_sides": ["knife"],
"players_per_team": 5,
"cvars": { "hostname": "FLICKED #27" }
}- Players are keyed by Steam64 ID, the only identifier MatchZy accepts. FLICKED already
stores
Player.SteamId. matchidis always sent, so events can be tied to a FLICKED match.- One map per match for now:
num_maps: 1, with the voted map inmaplist.map_sides: ["knife"]gives a knife round. - The config carries its own reporting settings, so the server knows where to send events
and which token to attach without anything in
server.cfg.
How a match reports back#
Events are POSTed as JSON to /api/matches/events, with the configured header
attached. The endpoint accepts either the server's own X-Server-Token or the per-match
X-Match-Token the config handed out, and checks that the server reporting is the one the match was
given to.
matchzy_remote_log_url <url>
matchzy_remote_log_header_key <name>
matchzy_remote_log_header_value <token>The events#
Every payload has an event field, and every match-scoped one has matchid.
| event | When | Beyond event and matchid |
|---|---|---|
series_start | Match config loaded | team1, team2, num_maps |
going_live | Knife done, match starts | map_number |
round_end | Every round | round_number, reason, winner, team1, team2 with player stats |
map_result | Map finished | winner, team1, team2. players is always empty |
series_end | Match over | winner, series scores. No per-player stats and no map score |
map_picked / map_vetoed / side_picked | Veto, if used | team, map_name, side |
player_disconnect | A player leaves | player |
demo_upload_ended | Demo uploaded | filename, success |
Team objects in round and map events carry score and a players array where each player has
steamid, name and a stats object: kills, deaths,
assists, damage, headshot_kills, rounds_played, utility figures,
multi-kills and clutches.
What FLICKED does with them#
| Event | Effect |
|---|---|
going_live | Match becomes Live; the server moves to Hosting and its lease extends to 90 minutes |
round_end | Overwrites each player's figures with the cumulative totals so far |
map_result | Records the score, and stats if it ever carries any |
series_end | Finishes the match exactly once, applies ratings, releases the server to Idle |
Quirks FLICKED handles#
map_result sends no player stats#
Observed on FLICKED's own server on 24 September 2026, at the end of a real match:
"team1":{"series_score":1,"score":13,"score_ct":0,"score_t":0,"players":[],"name":"Team A"}The players array is empty, although the documentation says it carries every player's stats. It is
plugin bug #405, which also reports that
round_end populates players correctly. So FLICKED takes player stats from
round_end. The figures are cumulative, so the last round's numbers are the match totals.
map_result still applies stats when it has any, so a fixed plugin needs no change.
Note also that score_ct and score_t are 0 even though score is right.
Only score is trustworthy.
One match per process#
isMatchSetup is set when a config loads and never set back, so every later
matchzy_loadmatch_url is refused with "A match is already setup". css_endmatch does not
clear it. FLICKED sends css_plugins reload MatchZy before each match, which builds a fresh plugin
instance.
Refusals are invisible over RCON#
MatchZy logs a refusal to the server console, so the RCON reply looks like success. FLICKED assumes nothing from an empty reply, and waits for the map to actually change before anyone is given the address.
Smaller things#
matchidarrives as a number in events, although the config accepts a string. FLICKED reads it leniently, or every event would be rejected as a type error.css_endmatchdoes not sendseries_end. The admin command resets to warmup; the event only fires when a match ends naturally. Ending a match by hand is not a way to test the reporting path.winneris{ "side": "...", "team": "..." }.
A FLICKED plugin, later#
The roadmap's plan is to fork MatchZy rather than start again: keep what works, and change the parts that only matter to FLICKED, such as per-match admin control from the dashboard, moderation while a match is live, and stats shaped the way FLICKED stores them. It would carry MatchZy's copyright notice and credit, as the licence requires. Nothing starts until the current arrangement is genuinely the limiting factor. See the roadmap.