Skip to content

bug: surrender winner, workshop cvar overrides, knife timeout resolution - #169

Merged
lukepolo merged 2 commits into
mainfrom
fork-triage-bugs
Aug 12, 2026
Merged

bug: surrender winner, workshop cvar overrides, knife timeout resolution#169
lukepolo merged 2 commits into
mainfrom
fork-triage-bugs

Conversation

@lukepolo

Copy link
Copy Markdown
Contributor

Every change here is applied to both the CounterStrikeSharp and Swiftly plugins.

Pairs with the API and web branches of the same name.

Surrender awarded the win to the wrong team

The headline fix.

Surrender() resolved the winning lineup by comparing lineup.name against "CT" and "TERRORIST". lineup.name is the team's display name — "Theft's Team" — so the comparison was never true. Every surrender fell through to the lineup_2 branch and handed the win to whichever team happened to be lineup 2, regardless of who actually forfeited.

Two changes:

  • The lineup is resolved by the side it is currently playing, via TeamUtility.GetLineupSide(matchData, currentMap, lineupId, roundsPlayed), so side swaps are accounted for.
  • Surrender() now takes the team that wins, not the team giving up. The vote site passes the opposite team in. This reads backwards at the call site if you are not expecting it, so it is called out in a comment where it matters.

The winning lineup id is also passed through to UpdateMapStatus(eMapStatus.Surrendered, lineup_id). Without it the API recorded the map as surrendered with no winner at all.

Workshop maps blocking convars

Workshop maps refuse a set of convars outright with DISALLOWED WORKSHOP CONVAR, which silently left matches running with wrong timeout, overtime and pause behaviour.

MatchManager now carries a WorkshopBlockedCvars table and re-applies them after map load:

convar value
mp_team_timeout_time 31
mp_team_timeout_max 3
mp_overtime_limit 0
mp_halftime_pausematch 0
mp_competitive_endofmatch_extra_time 155
sv_pausable 1

Per-match overrides are honored — if the match config sets one of these, that value wins.

Pause, timeout and resume

  • PauseMatch dropped the pause when a tactical timeout was active. mp_pause_match does not take during a native timeout. It now retries, up to 20 attempts, while IsTimeoutActive().
  • .resume is refused while a team is empty, so a match cannot be resumed into an empty server.
  • Auto-resume when the match is paused and every expected player has reconnected.

Knife round ending on the timer

If the knife round ran out the clock, RoundEnd fired with eWinReason.TimeRanOut and no winner was recorded — the knife round just hung.

ResolveTimedOutKnifeWinner(reported) now resolves it from the reported winner when the round ends that way.

Also: mp_friendlyfire 0 for the knife round. A blanket toggle is fine here because knife-round damage can only ever come from a knife, unlike the live match which needs per-source filtering to keep grenade friendly fire working. The match-type cfg re-exec in StartLive restores it to 1.

Match vs map conclusion

GameEnd announced the same thing whether a map had finished or the whole match had. It now keys the announcement on whether another map is queued.

Entities

FiveStackMatch gains is_tournament_match, is_draft_match and cancels_at, all of which the API already sends on current-match/:serverId.


Rebased onto current main — the branch was previously based on an older main and would otherwise have reverted metamod 14101407 and SwiftlyS2 1.4.41.4.3.

Surrender resolution was keyed off lineup.name compared against "CT" /
"TERRORIST", which never matched, so every surrender fell through to
lineup_2 and handed the win to whichever team happened to be lineup 2.
It now resolves the lineup by the side it is currently playing, side
swaps included, and passes the winning lineup id through to
UpdateMapStatus so the API records a winner instead of a surrendered
map with no winner at all.

Also in this change:

- Re-apply the timeout, overtime, halftime and pause cvars that workshop
  maps block, with per-match overrides honored
- Retry PauseMatch while a tactical timeout is active instead of
  dropping the pause
- Refuse .resume while a team is empty
- Resolve the knife round winner when the round ends on TimeRanOut
- Auto-resume once every expected player has reconnected
- Announce map conclusion vs match conclusion based on whether another
  map is queued
- Disable friendly fire for the knife round

Applied to both the CounterStrikeSharp and Swiftly plugins.
@lukepolo
lukepolo merged commit b597111 into main Aug 12, 2026
2 checks passed
@lukepolo
lukepolo deleted the fork-triage-bugs branch August 12, 2026 19:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant