sendou.ink/app/features/sendouq/core/summarizer.server.ts
Kalle e7bbb565be
SendouQ (#1455)
* Tables

* Clocks

* Maplist preference selector

* Fix SSR

* Nav icon

* RankedOrScrim

* Map pool

* Create group

* Redirect logic

* Persist map pool

* Advance from preparing page

* Rename query

* Fix merge

* Fix migration order

* Seed groups

* Find looking groups SQL

* Renders something

* More UI work

* Back to 30min

* Likes/dislikes

* Always return own group

* Fix like order

* 3 tc/rm/cb -> 2

* Show only 3 weapons

* Pass group size

* Handle both liked and liked by same group

* Fix SQL

* Group preference frontend work

* Morphing

* Styling

* Don't show group controls if not manager

* Give/remove manager

* Leave group

* Leave with confirm

* Delete likes when morphing groups

* Clocks consistency

* Remove bad invariant

* Persist settings to local storage

* Fix initial value flashing

* Fix never resolving loading indicator

* REFRESH_GROUP

* Flip animations

* Tweaks

* Auto refresh logic

* Groups of 4 seed

* Reduce throwing

* Load full groups initial

* Create match

* Match UI initial

* Score reporter initial

* Push footer down on match page

* Score reporter knows when set ended

* Score reporting untested

* Show score after report

* Align better

* Look again with same group functionality

* More migrations

* Team on match page

* Show confirmer before reporting score

* Report weapons

* Report weapos again by admin + skill changing

* Handle no tiebreaker given to MapPool

* Remove unranked

* Remove support for "team id skill"

* no-wrap -> nowrap

* Preparing page work

* Use common GroupCard component

* Add some metas

* MemberAdder in looking page

* Fix GroupCard actions

* Fix SZ only map list including other modes

* Add season info

* Prompt login

* Joining team

* Manage group on preparing page

* Manage group on preparing page

* Seed past matches

* Add to seed

* No map list preference when full group + fix expiry

* Fix skill matchesCount calculation

* Tiers initial work

* Some progress on tiers

* Tiering logic

* MMR in group cards

* Name to challenge

* Team MMR

* Big team rank icons

* Adjust todos

* Match score report with confirm

* Allow regular members to report score

* Handle reporting weapons edge cases

* Add tier images

* Improve GroupCard spacing

* Refactor looking page

* Looking mobile UI

* Calculate skill only for current season

* Divide groups visually when reporting weapons

* Fix match page weapons sorting

* Add cache to user skills+tier calculation

* Admin report match score

* Initial leaderboard

* Cached leaderboard

* Weapon category lb's

* Populate SkillTeamUser in SendouQ

* Team leaderboard filtered down

* Add TODOs

* Seasons initlal

* Season weapons initial

* Weapons stylized

* Show rest weapons as +

* Hide peak if same as current

* Load matches SQL initial

* Season matches UI initial

* Take user id in account

* Add weapons

* Paginated matches

* Fix pages count logic

* Scroll top on data change

* Day headers for matches

* Link from user page to user seasons page

* Summarize maps + ui initial

* Map stats

* Player info tabs

* MMR chart

* Chart adjustments

* Handle basing team MMR on player MMR

* Set initial MMR

* Add info about discord to match page

* Season support to tournaments

* Get tournament skills as well for the graph

* WIP

* New team rating logic + misc other

* tiered -> tiered.server

* Update season starting time

* TODOs

* Add rules page

* Hide elements correctly when off-season

* Fix crash when only one player with skill

* How-to video

* Fix StartRank showing when not logged in

* Make user leaderboard the default

* Make Skill season non-nullable

* Add suggested pass to match

* Add rule

* identifierToUserIds helper

* Fix tiers not showing
2023-08-12 22:42:54 +03:00

151 lines
3.7 KiB
TypeScript

import type { MapResult, PlayerResult } from "~/db/types";
import type { MatchById } from "../queries/findMatchById.server";
import { previousOrCurrentSeason } from "~/features/mmr/season";
import invariant from "tiny-invariant";
import { winnersArrayToWinner } from "../q-utils";
export function summarizeMaps({
match,
winners,
members,
}: {
match: MatchById;
winners: ("ALPHA" | "BRAVO")[];
members: { id: number; groupId: number }[];
}) {
const season = previousOrCurrentSeason(new Date())?.nth;
invariant(typeof season === "number", "No ranked season for skills");
const result: Array<MapResult> = [];
const playedMaps = match.mapList.slice(0, winners.length);
for (const [i, map] of playedMaps.entries()) {
const winnerSide = winners[i];
const winnerGroupId =
winnerSide === "ALPHA" ? match.alphaGroupId : match.bravoGroupId;
const winnerPlayers = members.filter((p) => p.groupId === winnerGroupId);
const loserPlayers = members.filter((p) => p.groupId !== winnerGroupId);
for (const winner of winnerPlayers) {
result.push({
userId: winner.id,
wins: 1,
losses: 0,
mode: map.mode,
stageId: map.stageId,
season,
});
}
for (const loser of loserPlayers) {
result.push({
userId: loser.id,
wins: 0,
losses: 1,
mode: map.mode,
stageId: map.stageId,
season,
});
}
}
return result;
}
export function summarizePlayerResults({
match,
winners,
members,
}: {
match: MatchById;
winners: ("ALPHA" | "BRAVO")[];
members: { id: number; groupId: number }[];
}) {
const season = previousOrCurrentSeason(new Date())?.nth;
invariant(typeof season === "number", "No ranked season for skills");
const result: Array<PlayerResult> = [];
const addMapResult = ({
outcome,
type,
ownerUserId,
otherUserId,
}: {
outcome: "win" | "loss";
type: "MATE" | "ENEMY";
ownerUserId: number;
otherUserId: number;
}) => {
const existing = result.find(
(r) => r.ownerUserId === ownerUserId && r.otherUserId === otherUserId
);
if (existing) {
if (outcome === "win") {
existing.mapWins++;
} else existing.mapLosses++;
} else {
result.push({
ownerUserId,
otherUserId,
type,
mapWins: outcome === "win" ? 1 : 0,
mapLosses: outcome === "win" ? 0 : 1,
season,
setLosses: 0,
setWins: 0,
});
}
};
for (const winner of winners) {
for (const member of members) {
for (const member2 of members) {
if (member.id === member2.id) continue;
const type = member.groupId === member2.groupId ? "MATE" : "ENEMY";
const won =
winner === "ALPHA"
? member.groupId === match.alphaGroupId
: member.groupId === match.bravoGroupId;
addMapResult({
ownerUserId: member.id,
otherUserId: member2.id,
type,
outcome: won ? "win" : "loss",
});
}
}
}
const winner = winnersArrayToWinner(winners);
for (const member of members) {
for (const member2 of members) {
if (member.id === member2.id) continue;
const type = member.groupId === member2.groupId ? "MATE" : "ENEMY";
const won =
winner === "ALPHA"
? member.groupId === match.alphaGroupId
: member.groupId === match.bravoGroupId;
result.push({
ownerUserId: member.id,
otherUserId: member2.id,
type,
mapWins: 0,
mapLosses: 0,
season,
setWins: won ? 1 : 0,
setLosses: won ? 0 : 1,
});
}
}
return result;
}