sendou.ink/app/features/tournament/tournament-utils.server.ts
Kalle 6e987d506f
Some checks are pending
E2E Tests / e2e (push) Waiting to run
Tests and checks on push / run-checks-and-tests (push) Waiting to run
Updates translation progress / update-translation-progress-issue (push) Waiting to run
Tournament layout refresh, improve admin experience (#3152)
2026-06-11 18:31:10 +03:00

137 lines
4.2 KiB
TypeScript

import * as LeaderboardRepository from "~/features/leaderboards/LeaderboardRepository.server";
import type { getServerTournamentManager } from "~/features/tournament-bracket/core/brackets-manager/manager.server";
import * as TournamentOrganizationRepository from "~/features/tournament-organization/TournamentOrganizationRepository.server";
import { logger } from "~/utils/logger";
import { errorToast, errorToastIfFalsy } from "~/utils/remix.server";
import { MATCHES_COUNT_NEEDED_FOR_LEADERBOARD } from "../leaderboards/leaderboards-constants";
import type { Tournament } from "../tournament-bracket/core/Tournament";
export async function requireNotBannedByOrganization({
tournament,
user,
message = "You are banned from events hosted by this organization",
}: {
tournament: Tournament;
user: { id: number };
message?: string;
}) {
if (!tournament.ctx.organization) return;
const isBanned =
await TournamentOrganizationRepository.isUserBannedByOrganization({
organizationId: tournament.ctx.organization.id,
userId: user.id,
});
if (isBanned) {
errorToast(message);
}
}
/**
* Whether the given team name is already used by another team in the tournament.
* Single source of truth for the uniqueness rule shared by the player registration
* ({@link registerTeamFormSchemaServer}) and admin registration
* ({@link adminRegistrationFormSchemaServer}) forms.
*
* @param exceptTournamentTeamId - the team being edited, excluded from the comparison
*/
export function tournamentTeamNameTaken({
tournament,
name,
exceptTournamentTeamId,
}: {
tournament: Tournament;
name: string;
exceptTournamentTeamId?: number;
}) {
return tournament.ctx.teams.some(
(team) => team.name === name && team.id !== exceptTournamentTeamId,
);
}
export async function requireSendouQParticipationIfNeeded({
tournament,
userId,
}: {
tournament: Tournament;
userId: number;
}) {
if (!tournament.ctx.settings.requireSendouQParticipation) return;
const hasEnough = await LeaderboardRepository.userHasEnoughSqMatches(userId);
errorToastIfFalsy(
hasEnough,
`Must have played ${MATCHES_COUNT_NEEDED_FOR_LEADERBOARD} SendouQ matches this season to join`,
);
}
/**
* Ends all unfinished matches involving dropped teams by awarding wins to their opponents.
* If both teams in a match have dropped, a random winner is selected.
*
* @param tournament - The tournament instance
* @param manager - The bracket manager instance used to update matches
* @param droppedTeamId - Optional team ID to filter matches for a specific dropped team.
* If omitted, processes all matches with any dropped team.
*/
export function endDroppedTeamMatches({
tournament,
manager,
droppedTeamId,
}: {
tournament: Tournament;
manager: ReturnType<typeof getServerTournamentManager>;
droppedTeamId?: number;
}) {
const stageData = manager.get.tournamentData(tournament.ctx.id);
const endedMatchIds: number[] = [];
for (const match of stageData.match) {
if (!match.opponent1?.id || !match.opponent2?.id) continue;
if (match.opponent1.result === "win" || match.opponent2.result === "win")
continue;
const team1 = tournament.teamById(match.opponent1.id);
const team2 = tournament.teamById(match.opponent2.id);
const team1Dropped =
team1?.droppedOut || match.opponent1.id === droppedTeamId;
const team2Dropped =
team2?.droppedOut || match.opponent2.id === droppedTeamId;
if (!team1Dropped && !team2Dropped) continue;
const winnerTeamId = (() => {
if (team1Dropped && !team2Dropped) return match.opponent2.id;
if (!team1Dropped && team2Dropped) return match.opponent1.id;
return Math.random() < 0.5 ? match.opponent1.id : match.opponent2.id;
})();
logger.info(
`Ending match with dropped team: Match ID: ${match.id}; Team1 dropped: ${team1Dropped}; Team2 dropped: ${team2Dropped}; Winner: ${winnerTeamId}`,
);
manager.update.match(
{
id: match.id,
opponent1: {
score: match.opponent1.score,
result: winnerTeamId === match.opponent1.id ? "win" : "loss",
},
opponent2: {
score: match.opponent2.score,
result: winnerTeamId === match.opponent2.id ? "win" : "loss",
},
},
true,
);
endedMatchIds.push(match.id);
}
return endedMatchIds;
}