feat: let a custom game mode define its own team size - #406
Open
LCrew wants to merge 3 commits into
Open
Conversation
A match's player count came from match_options.type alone, hard-coded in four places that had to be kept in sync. A custom game mode could change the plugins and cvars a match runs under but not its shape, so a 3v3 retakes mode still demanded a full 5v5 lobby. game_modes gains players_per_team and allow_short_handed_start. A new resolver, get_match_options_min_players(mo), coalesces the size a match actually launched at, then the mode's, then the type's -- and every gate that gated on the type now reads it instead: match_min/max_players_per_lineup, check_match_has_min_players, is_match_lineup_ready, the match_lineup_players removal trigger, and draft_games capacity. A mode may also permit starting a draft lobby before both sides are full. The new startDraftGame action narrows the lobby to the players who turned up *before* flipping it to Filled -- doing it the other way round would leave a draft stuck in CreatingMatch that beginDraft then refuses to act on, with every player in it locked out of joining anything else. Uneven sides are allowed; Captains still requires an even pool, because get_draft_game_pattern builds its pick order off capacity / 2. match_options.min_players_per_lineup records the size a short-handed match launched at, so editing the mode later cannot invalidate a match already running, and the game server is told the same number. All three columns are nullable, so with no custom mode configured every one of those resolves to exactly what the match type gave before. The feature is inert until an admin sets players_per_team, and clearing it back to NULL is a kill switch that needs no deploy. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G8LmkD3i1HyGjbcxAZ5Crz
min_players_per_lineup records the SMALLER side, because the gates apply one number to both lineups -- a 1v2 has to record 1 or the short side never clears check_match_has_min_players. The game server was doubling it, so a 1v2 asked for 2 players and could go live while the third was still connecting. One number cannot express an uneven start, so record the total too. expected_players counts starters off draft_game_players rather than the match lineups: buildTeams also seats waitlisted backups in the substitute slots, and a backup is not someone warmup should wait for. Only written when a lobby actually starts short-handed. An even start leaves it null and the server keeps doubling the per-lineup figure. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G8LmkD3i1HyGjbcxAZ5Crz
applyMigrations tracks versions, not content: `if (!applied.has(version))`. Appending the column to 1886000000000 was therefore a silent no-op on any database that had already run it -- while the metadata in the same build still granted permission on it. Hasura then failed to track match_options, dropped the objects that depended on it, and `matches_bool_exp` lost its `options` relationship: the draft room stopped loading. Its own version applies everywhere -- in order on a fresh install, and on its own for a database already carrying 1886000000000. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G8LmkD3i1HyGjbcxAZ5Crz
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
A match's player count comes from
match_options.typealone, and that fact is hard-coded in four places that have to be kept in sync:get_match_type_min_players()tbi_draft_gamesExpectedPlayers(api)GetExpectedPlayerCount()(plugin)A custom game mode can already change the plugins and cvars a match runs under, but not its shape — so a 3v3 retakes mode still demands a full 5v5 lobby, and the draft room's Start button stays locked until 10 people are seated.
What
game_modesgainsplayers_per_teamandallow_short_handed_start.One new resolver,
get_match_options_min_players(mo), coalesces the size a match actually launched at → the mode's → the type's. Every gate that keyed on the type now reads it instead:match_min/max_players_per_lineup,check_match_has_min_players,is_match_lineup_ready, thematch_lineup_playersremoval trigger, anddraft_gamescapacity.A mode may also permit starting a draft lobby before both sides are full. The new
startDraftGameaction narrows the lobby to the players who turned up before flipping it toFilled— the other order leaves a draft stuck inCreatingMatchthatbeginDraftthen refuses to act on, with every player in it locked out of joining anything else. Authorization is unchanged:isOrganizerOrHostmatchesis_draft_game_organizerexactly.Two snapshots, because one number can't describe an uneven start
min_players_per_lineupexpected_playersBoth count starters only, read off
draft_game_playersrather than the match lineups —buildTeamsalso seats waitlisted backups in substitute slots, and a backup isn't someone warmup should wait for.Captainsstill requires an even pool, becauseget_draft_game_patternbuilds its pick order offcapacity / 2.Zero change by default
Every column is nullable. With no custom mode configured, each path resolves to exactly what the type gave before:
players_per_team = NULLplayers_per_team = 3Inert until an admin sets
players_per_team; clearing it back toNULLis a kill switch needing no deploy.Tested
players_per_team = 1produces a 1v1 lobby, sends 2 players, and the game server goes to[0/2]instead of[0/4].Notes for review
generated/covers the first three columns but notexpected_players— worth ayarn codegenagainst a Hasura carrying the migrations. Related: thecurrent-matchselection inmatches.controller.tsturns out not to be type-checked at all (a deliberately bogus field compiles), so column names there are only validated at runtime. Might be worth a look independently of this PR.hasura/functionsandtriggersre-apply whenever a file's SHA changes and are allCREATE OR REPLACE, so reverting the code restores the old definitions on the next boot. The migrations are additive and safe to leave.