Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SendouQ match page show & save powers + plus tier status #1519

Merged
merged 9 commits into from
Oct 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions app/db/seed/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1732,6 +1732,7 @@ function playedMatches() {

invariant(groupAlpha !== 0 && groupBravo !== 0, "groups not created");

// @ts-expect-error creating without memento on purpose
const match = createMatch({
alphaGroupId: groupAlpha,
bravoGroupId: groupBravo,
Expand Down Expand Up @@ -1772,10 +1773,12 @@ function playedMatches() {
const winner = winnersArrayToWinner(winners);
const finishedMatch = findMatchById(match.id)!;

const newSkills = calculateMatchSkills({
const { newSkills, differences } = calculateMatchSkills({
groupMatchId: match.id,
winner: winner === "ALPHA" ? groupAlphaMembers : groupBravoMembers,
loser: winner === "ALPHA" ? groupBravoMembers : groupAlphaMembers,
loserGroupId: winner === "ALPHA" ? groupBravo : groupAlpha,
winnerGroupId: winner === "ALPHA" ? groupAlpha : groupBravo,
});
const members = [
...groupForMatch(match.alphaGroupId)!.members.map((m) => ({
Expand All @@ -1794,7 +1797,12 @@ function playedMatches() {
Math.random() > 0.5 ? groupAlphaMembers[0] : groupBravoMembers[0],
winners,
});
addSkills(newSkills);
addSkills({
skills: newSkills,
differences,
groupMatchId: match.id,
oldMatchMemento: { users: {}, groups: {} },
});
setGroupAsInactive(groupAlpha);
setGroupAsInactive(groupBravo);
addMapResults(summarizeMaps({ match: finishedMatch, members, winners }));
Expand Down
39 changes: 39 additions & 0 deletions app/db/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type {
StageId,
} from "~/modules/in-game-lists";
import type allTags from "../routes/calendar/tags.json";
import type { TieredSkill } from "~/features/mmr/tiered.server";

export interface User {
id: number;
Expand Down Expand Up @@ -543,6 +544,43 @@ export interface GroupLike {
createdAt: number;
}

type CalculatingSkill = {
calculated: false;
matchesCount: number;
matchesCountNeeded: number;
/** Freshly calculated skill */
newSp?: number;
};
export type UserSkillDifference =
| {
calculated: true;
spDiff: number;
}
| CalculatingSkill;
export type GroupSkillDifference =
| {
calculated: true;
oldSp: number;
newSp: number;
}
| CalculatingSkill;
export type ParsedMemento = {
users: Record<
User["id"],
{
plusTier?: PlusTier["tier"];
skill?: TieredSkill;
skillDifference?: UserSkillDifference;
}
>;
groups: Record<
Group["id"],
{
tier?: TieredSkill["tier"];
skillDifference?: GroupSkillDifference;
}
>;
};
export interface GroupMatch {
id: number;
alphaGroupId: number;
Expand All @@ -551,6 +589,7 @@ export interface GroupMatch {
reportedAt: number | null;
reportedByUserId: number | null;
chatCode: string | null;
memento: string | null;
}

export interface GroupMatchMap {
Expand Down
12 changes: 6 additions & 6 deletions app/features/mmr/mmr-utils.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ export function queryCurrentUserRating({
const skill = findCurrentSkillByUserId({ userId, season: season ?? null });

if (!skill) {
return rating();
return { rating: rating(), matchesCount: 0 };
}

return rating(skill);
return { rating: rating(skill), matchesCount: skill.matchesCount };
}

export function queryCurrentTeamRating({
Expand All @@ -31,9 +31,9 @@ export function queryCurrentTeamRating({
season,
});

if (!skill) return rating();
if (!skill) return { rating: rating(), matchesCount: 0 };

return rating(skill);
return { rating: rating(skill), matchesCount: skill.matchesCount };
}

export function queryTeamPlayerRatingAverage({
Expand All @@ -43,8 +43,8 @@ export function queryTeamPlayerRatingAverage({
identifier: string;
season: number;
}) {
const playerRatings = identifierToUserIds(identifier).map((userId) =>
queryCurrentUserRating({ userId, season }),
const playerRatings = identifierToUserIds(identifier).map(
(userId) => queryCurrentUserRating({ userId, season }).rating,
);

if (playerRatings.length === 0) return rating();
Expand Down
8 changes: 6 additions & 2 deletions app/features/mmr/queries/findCurrentSkillByUserId.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import type { Skill } from "~/db/types";
const stm = sql.prepare(/* sql */ `
select
"mu",
"sigma"
"sigma",
"matchesCount"
from
"Skill"
where
Expand All @@ -24,5 +25,8 @@ export function findCurrentSkillByUserId({
userId: number;
season: number;
}) {
return stm.get({ userId, season }) as Pick<Skill, "mu" | "sigma"> | null;
return stm.get({ userId, season }) as Pick<
Skill,
"mu" | "sigma" | "matchesCount"
> | null;
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import type { Skill } from "~/db/types";
const stm = sql.prepare(/* sql */ `
select
"mu",
"sigma"
"sigma",
"matchesCount"
from
"Skill"
where
Expand All @@ -24,5 +25,8 @@ export function findCurrentTeamSkillByIdentifier({
identifier: string;
season: number;
}) {
return stm.get({ identifier, season }) as Pick<Skill, "mu" | "sigma"> | null;
return stm.get({ identifier, season }) as Pick<
Skill,
"mu" | "sigma" | "matchesCount"
> | null;
}
123 changes: 115 additions & 8 deletions app/features/sendouq/components/GroupCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { SubmitButton } from "~/components/SubmitButton";
import { MicrophoneIcon } from "~/components/icons/Microphone";
import { SpeakerIcon } from "~/components/icons/Speaker";
import { SpeakerXIcon } from "~/components/icons/SpeakerX";
import type { Group, GroupMember as GroupMemberType } from "~/db/types";
import type { GroupMember as GroupMemberType, ParsedMemento } from "~/db/types";
import { ordinalToRoundedSp } from "~/features/mmr/mmr-utils";
import type { TieredSkill } from "~/features/mmr/tiered.server";
import { useTranslation } from "~/hooks/useTranslation";
Expand All @@ -27,18 +27,25 @@ export function GroupCard({
ownRole,
ownGroup = false,
isExpired = false,
displayOnly = false,
hideVc = false,
hideWeapons = false,
}: {
group: LookingGroup;
action?: "LIKE" | "UNLIKE" | "GROUP_UP" | "MATCH_UP";
mapListPreference?: Group["mapListPreference"];
ownRole?: GroupMemberType["role"];
ownGroup?: boolean;
isExpired?: boolean;
displayOnly?: boolean;
hideVc?: boolean;
hideWeapons?: boolean;
}) {
const fetcher = useFetcher();

return (
<section className="q__group">
<section
className={clsx("q__group", { "q__group__display-only": displayOnly })}
>
<div
className={clsx("stack md", {
"horizontal justify-center": !group.members,
Expand All @@ -50,6 +57,9 @@ export function GroupCard({
member={member}
showActions={ownGroup && ownRole === "OWNER"}
key={member.discordId}
displayOnly={displayOnly}
hideVc={hideVc}
hideWeapons={hideWeapons}
/>
);
})}
Expand All @@ -63,7 +73,7 @@ export function GroupCard({
})
: null}
</div>
{group.tier ? (
{group.tier && !displayOnly ? (
<div className="stack xs text-lighter font-bold items-center justify-center text-xs">
<TierImage tier={group.tier} width={100} />
<div>
Expand All @@ -77,6 +87,16 @@ export function GroupCard({
</div>
</div>
) : null}
{group.tier && displayOnly ? (
<div className="q__group__display-group-tier">
<TierImage tier={group.tier} width={38} />
{group.tier.name}
{group.tier.isPlus ? "+" : ""}
</div>
) : null}
{group.skillDifference ? (
<GroupSkillDifference skillDifference={group.skillDifference} />
) : null}
{action &&
(ownRole === "OWNER" || ownRole === "MANAGER") &&
!isExpired ? (
Expand Down Expand Up @@ -119,9 +139,15 @@ export function GroupCard({
function GroupMember({
member,
showActions,
displayOnly,
hideVc,
hideWeapons,
}: {
member: NonNullable<LookingGroup["members"]>[number];
showActions: boolean;
displayOnly?: boolean;
hideVc?: boolean;
hideWeapons?: boolean;
}) {
return (
<div className="stack xxs">
Expand All @@ -135,13 +161,15 @@ function GroupMember({
<span className="q__group-member__name">{member.discordName}</span>
</Link>
<div className="ml-auto stack horizontal sm items-center">
{showActions ? <MemberRoleManager member={member} /> : null}
{showActions || displayOnly ? (
<MemberRoleManager member={member} displayOnly={displayOnly} />
) : null}
{member.skill ? <TierInfo skill={member.skill} /> : null}
</div>
</div>
<div className="stack horizontal justify-between">
<div className="stack horizontal xxs">
{member.vc ? (
{member.vc && !hideVc ? (
<div className="q__group-member__extra-info">
<VoiceChatInfo member={member} />
</div>
Expand All @@ -153,7 +181,7 @@ function GroupMember({
</div>
) : null}
</div>
{member.weapons ? (
{member.weapons && member.weapons.length > 0 && !hideWeapons ? (
<div className="q__group-member__extra-info">
{member.weapons?.map((weapon) => {
return (
Expand All @@ -167,20 +195,99 @@ function GroupMember({
})}
</div>
) : null}
{member.skillDifference ? (
<MemberSkillDifference skillDifference={member.skillDifference} />
) : null}
</div>
</div>
);
}

function GroupSkillDifference({
skillDifference,
}: {
skillDifference: NonNullable<
ParsedMemento["groups"][number]["skillDifference"]
>;
}) {
if (skillDifference.calculated) {
return (
<div className="text-center font-semi-bold">
Team SP {skillDifference.oldSp} ➜ {skillDifference.newSp}
</div>
);
}

if (skillDifference.newSp) {
return (
<div className="text-center font-semi-bold">
Team SP calculated: {skillDifference.newSp}
</div>
);
}

return (
<div className="text-center font-semi-bold">
Team SP calculating... ({skillDifference.matchesCount}/
{skillDifference.matchesCountNeeded})
</div>
);
}

function MemberSkillDifference({
skillDifference,
}: {
skillDifference: NonNullable<
ParsedMemento["users"][number]["skillDifference"]
>;
}) {
if (skillDifference.calculated) {
if (skillDifference.spDiff === 0) return null;

const symbol =
skillDifference.spDiff > 0 ? (
<span className="text-success">▲</span>
) : (
<span className="text-warning">▼</span>
);
return (
<div className="q__group-member__extra-info">
{symbol}
{Math.abs(skillDifference.spDiff)}SP
</div>
);
}

if (skillDifference.matchesCount === skillDifference.matchesCountNeeded) {
return (
<div className="q__group-member__extra-info">
<span className="text-lighter">Calculated:</span>{" "}
{skillDifference.newSp ? <>{skillDifference.newSp}SP</> : null}
</div>
);
}

return (
<div className="q__group-member__extra-info">
<span className="text-lighter">Calculating...</span> (
{skillDifference.matchesCount}/{skillDifference.matchesCountNeeded})
</div>
);
}

function MemberRoleManager({
member,
displayOnly,
}: {
member: NonNullable<LookingGroup["members"]>[number];
displayOnly?: boolean;
}) {
const fetcher = useFetcher();
const { t } = useTranslation(["q"]);
const Icon = member.role === "OWNER" ? StarFilledIcon : StarIcon;

if (displayOnly && member.role !== "OWNER") return null;

return (
<Popover
buttonChildren={
Expand All @@ -193,7 +300,7 @@ function MemberRoleManager({
>
<div className="stack md items-center">
<div>{t(`q:roles.${member.role}`)}</div>
{member.role !== "OWNER" ? (
{member.role !== "OWNER" && !displayOnly ? (
<fetcher.Form method="post" action={SENDOUQ_LOOKING_PAGE}>
<input type="hidden" name="userId" value={member.id} />
{member.role === "REGULAR" ? (
Expand Down
Loading