sendou.ink/app/features/tournament/tournament-utils.server.ts
2026-04-24 19:41:42 +03:00

115 lines
3.5 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);
}
}
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;
}