Skip to content

Commit

Permalink
feat: auto-close games with too many substitute requests
Browse files Browse the repository at this point in the history
  • Loading branch information
garrappachc committed Dec 13, 2024
1 parent 2f85d55 commit 55d8a41
Show file tree
Hide file tree
Showing 6 changed files with 84 additions and 2 deletions.
9 changes: 9 additions & 0 deletions src/database/models/configuration-entry.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,15 @@ export const configurationSchema = z.discriminatedUnion('key', [
})
.describe('Apply cooldown before players can join the queue after a game ends'),
}),
z.object({
key: z.literal('games.auto_force_end_threshold'),
value: z
.number()
.default(4)
.describe(
'Number of active substitute requests that make the game be automatically force-ended',
),
}),
z.object({
key: z.literal('players.etf2l_account_required'),
value: z.boolean().default(false),
Expand Down
1 change: 1 addition & 0 deletions src/database/models/game-event.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export interface GameCreated {
export enum GameEndedReason {
matchEnded = 'match ended',
interrupted = 'interrupted',
tooManySubstituteRequests = 'too many substitute requests',
}

export interface GameEnded {
Expand Down
9 changes: 7 additions & 2 deletions src/games/force-end.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import { GameEndedReason, GameEventType } from '../database/models/game-event.model'
import { SlotStatus } from '../database/models/game-slot.model'
import { GameState, type GameNumber } from '../database/models/game.model'
import type { Bot } from '../shared/types/bot'
import type { SteamId64 } from '../shared/types/steam-id-64'
import { update } from './update'

export async function forceEnd(gameNumber: GameNumber, actor?: SteamId64) {
export async function forceEnd(
gameNumber: GameNumber,
actor?: SteamId64 | Bot,
reason = GameEndedReason.interrupted,
) {
await update(
{
number: gameNumber,
Expand All @@ -18,7 +23,7 @@ export async function forceEnd(gameNumber: GameNumber, actor?: SteamId64) {
events: {
at: new Date(),
event: GameEventType.gameEnded,
reason: GameEndedReason.interrupted,
reason,
...(actor && { actor }),
},
},
Expand Down
53 changes: 53 additions & 0 deletions src/games/plugins/auto-close-games.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import fp from 'fastify-plugin'
import { events } from '../../events'
import { safe } from '../../utils/safe'
import { GameState, type GameModel } from '../../database/models/game.model'
import { configuration } from '../../configuration'
import { SlotStatus } from '../../database/models/game-slot.model'
import { debounce } from 'lodash-es'
import { logger } from '../../logger'
import { forceEnd } from '../force-end'
import { GameEndedReason } from '../../database/models/game-event.model'

export default fp(
async () => {
const maybeCloseGame = debounce(async (game: GameModel) => {
if (
![
GameState.created,
GameState.configuring,
GameState.launching,
GameState.started,
].includes(game.state)
) {
return
}

const threshold = await configuration.get('games.auto_force_end_threshold')
if (threshold <= 0) {
return
}

const subRequests = game.slots.filter(
slot => slot.status === SlotStatus.waitingForSubstitute,
).length
if (subRequests >= threshold) {
logger.info(
{ game, subRequestCount: subRequests, threshold },
`game #${game.number} has too many substitute requests; the game will be force-ended`,
)
await forceEnd(game.number, 'bot', GameEndedReason.tooManySubstituteRequests)
}
}, 100)

events.on(
'game:substituteRequested',
safe(async ({ game }) => {
await maybeCloseGame(game)
}),
)
},
{
name: 'auto close games with too many subsitute requests',
},
)
2 changes: 2 additions & 0 deletions src/games/views/html/game-event-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,8 @@ async function GameEventInfo(props: { event: GameEventModel; game: GameModel })
</a>
</span>
)
case GameEndedReason.tooManySubstituteRequests:
return <span>Game interrupted (too many substitute requests)</span>
default:
return <span>Game ended</span>
}
Expand Down
12 changes: 12 additions & 0 deletions tests/20-game/07-auto-close-games.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { expect, launchGame as test } from '../fixtures/launch-game'

test('auto-closes games with too many substitute requests', async ({ gameNumber, users }) => {
const page = await users.getAdmin().gamePage(gameNumber)
await page.requestSubstitute('Mayflower')
await page.requestSubstitute('Polemic')
await page.requestSubstitute('Shadowhunter')
await page.requestSubstitute('MoonMan')

await expect.poll(() => page.isLive()).toBe(false)
await expect(page.gameEvent('Game interrupted (too many substitute requests)')).toBeVisible()
})

0 comments on commit 55d8a41

Please sign in to comment.