feat: gitadora frontend

This commit is contained in:
zkldi
2022-12-20 14:36:27 +00:00
parent 4d1714e2c2
commit e63cab2159
22 changed files with 187 additions and 94 deletions
+2 -2
View File
@@ -8,7 +8,7 @@ import type {
integer,
ScoreDocument,
SongDocument,
UGSRatingsLookup,
ProfileRatingLookup,
UserGameStats,
} from "tachi-common";
@@ -50,7 +50,7 @@ export interface UGPTStats<I extends IDStrings = IDStrings> {
firstScore: ScoreDocument;
mostRecentScore: ScoreDocument;
totalScores: integer;
rankingData: Record<UGSRatingsLookup[I], { ranking: integer; outOf: integer }>;
rankingData: Record<ProfileRatingLookup[I], { ranking: integer; outOf: integer }>;
}
export interface ChartQueryReturns {
@@ -19,7 +19,7 @@ import {
IDStrings,
integer,
UserDocument,
UGSRatingsLookup,
ProfileRatingLookup,
UserGameStats,
} from "tachi-common";
import { GameClassSets } from "tachi-common/game-classes";
@@ -84,8 +84,8 @@ function LeaderboardsPageContent({
}: {
reqUser: UserDocument;
data: LeaderboardsData;
alg: UGSRatingsLookup[IDStrings];
setAlg: SetState<UGSRatingsLookup[IDStrings]>;
alg: ProfileRatingLookup[IDStrings];
setAlg: SetState<ProfileRatingLookup[IDStrings]>;
} & GamePT) {
const { stats, leaderboard } = data;
@@ -21,6 +21,7 @@ export default function DefaultSongChartInfoFormat({
<h5>({FormatDifficulty(chart, game)})</h5>
<h6>
<DisplayLevelNum
game={game}
level={chart.level}
levelNum={chart.levelNum}
prefix="Internal Level: "
@@ -3,6 +3,7 @@ import Divider from "components/util/Divider";
import Loading from "components/util/Loading";
import React from "react";
import { ImportStates } from "types/import";
import ManageImport from "components/tables/dropdowns/components/ManageImport";
import ImportInfo from "./ImportInfo";
export default function ImportStateRenderer({ state: s }: { state: ImportStates }) {
@@ -35,6 +36,11 @@ export default function ImportStateRenderer({ state: s }: { state: ImportStates
Your import was successful!
<Divider />
<ImportInfo importID={s.import.importID} />
<Divider />
<div>
<h4>Messed up?</h4>
<ManageImport importDoc={s.import} />
</div>
</>
) : (
<>
@@ -54,6 +54,7 @@ export default function DifficultyCell({
style={{
backgroundColor: ChangeOpacity(gptConfig.difficultyColours[chart.difficulty]!, 0.2),
minWidth: "80px",
maxWidth: "100px",
}}
>
{!alwaysShort && (
@@ -62,7 +63,7 @@ export default function DifficultyCell({
<div className={!alwaysShort ? "d-lg-none" : ""}>
{FormatDifficultyShort(chart, game)}
</div>
<DisplayLevelNum level={chart.level} levelNum={chart.levelNum} />
<DisplayLevelNum game={game} level={chart.level} levelNum={chart.levelNum} />
{"isHot" in chart.data && chart.data.isHot && (
<QuickTooltip tooltipContent="This chart is from the latest version of the game!">
<div>
@@ -88,10 +89,12 @@ export function DisplayLevelNum({
level,
levelNum,
prefix,
game,
}: {
levelNum: number;
level: string;
prefix?: string;
game: Game;
}) {
// Don't display levelnum if its identical to the level, the decimal places in the
// level end with .0, or the levelNum itself is 0.
@@ -99,6 +102,11 @@ export function DisplayLevelNum({
return null;
}
// or if it's gitadora
if (game === "gitadora") {
return null;
}
return (
<Muted>
{prefix}
@@ -0,0 +1,35 @@
import { IsNullish } from "util/misc";
import React from "react";
import { COLOUR_SET, PBScoreDocument, ScoreDocument } from "tachi-common";
export default function GitadoraJudgementCell({
score,
}: {
score:
| ScoreDocument<"gitadora:Dora" | "gitadora:Gita">
| PBScoreDocument<"gitadora:Dora" | "gitadora:Gita">;
}) {
const judgements = score.scoreData.judgements;
if (
IsNullish(judgements.perfect) ||
IsNullish(judgements.great) ||
IsNullish(judgements.good) ||
IsNullish(judgements.ok) ||
IsNullish(judgements.miss)
) {
return <td>No Data.</td>;
}
return (
<td>
<strong>
<span style={{ color: COLOUR_SET.gold }}>{judgements.perfect}</span>-
<span style={{ color: COLOUR_SET.green }}>{judgements.great}</span>-
<span style={{ color: COLOUR_SET.blue }}>{judgements.good}</span>-
<span style={{ color: COLOUR_SET.purple }}>{judgements.ok}</span>-
<span style={{ color: COLOUR_SET.red }}>{judgements.miss}</span>
</strong>
</td>
);
}
@@ -0,0 +1,25 @@
import React from "react";
import { PBScoreDocument, ScoreDocument } from "tachi-common";
import GitadoraJudgementCell from "../cells/GitadoraJudgementCell";
import LampCell from "../cells/LampCell";
import RatingCell from "../cells/RatingCell";
import ScoreCell from "../cells/ScoreCell";
export default function GitadoraCoreCells({
sc,
rating,
}: {
sc:
| ScoreDocument<"gitadora:Gita" | "gitadora:Dora">
| PBScoreDocument<"gitadora:Gita" | "gitadora:Dora">;
rating: keyof ScoreDocument["calculatedData"];
}) {
return (
<>
<ScoreCell showScore={false} score={sc} />
<GitadoraJudgementCell score={sc} />
<LampCell score={sc} />
<RatingCell score={sc} rating={rating} />
</>
);
}
@@ -2,9 +2,6 @@ import React from "react";
import { PBScoreDocument, ScoreDocument } from "tachi-common";
import ITGJudgementCell from "../cells/ITGJudgementCell";
import LampCell from "../cells/LampCell";
import MillionsScoreCell from "../cells/MillionsScoreCell";
import PopnJudgementCell from "../cells/PopnJudgementCell";
import PopnLampCell from "../cells/PopnLampCell";
import RatingCell from "../cells/RatingCell";
import ScoreCell from "../cells/ScoreCell";
@@ -18,6 +18,7 @@ import PMSCoreCells from "./PMSCoreCells";
import PopnCoreCells from "./PopnCoreCells";
import SDVXScoreCoreCells from "./SDVXCoreCells";
import WACCACoreCells from "./WACCACoreCells";
import GitadoraCoreCells from "./GitadoraCoreCells";
export default function ScoreCoreCells({
game,
@@ -32,40 +33,42 @@ export default function ScoreCoreCells({
}) {
const sc = score as any; // lazy hack
if (game === "iidx") {
return (
<IIDXCoreCells
rating={rating as ScoreCalculatedDataLookup["iidx:SP" | "iidx:DP"]}
chart={chart as ChartDocument<"iidx:SP" | "iidx:DP">}
sc={sc}
/>
);
} else if (game === "bms") {
return <BMSCoreCells sc={sc} rating={rating} />;
} else if (game === "sdvx" || game === "usc") {
return (
<SDVXScoreCoreCells
sc={sc}
chart={chart as ChartDocument<"sdvx:Single" | "usc:Controller" | "usc:Keyboard">}
/>
);
} else if (game === "museca") {
return <MusecaCoreCells sc={sc} rating={rating} />;
} else if (game === "wacca") {
return <WACCACoreCells sc={sc} rating={rating} />;
} else if (game === "popn") {
return <PopnCoreCells sc={sc} rating={rating} />;
} else if (game === "jubeat") {
return <JubeatCoreCells sc={sc} rating={rating} />;
} else if (game === "chunithm") {
return <CHUNITHMCoreCells sc={sc} rating={rating} />;
} else if (game === "gitadora") {
return <GenericCoreCells sc={sc} rating={rating} />;
} else if (game === "pms") {
return <PMSCoreCells sc={sc} rating={rating} />;
} else if (game === "itg") {
return <ITGCoreCells sc={sc} rating={rating} />;
switch (game) {
case "iidx":
return (
<IIDXCoreCells
rating={rating as ScoreCalculatedDataLookup["iidx:SP" | "iidx:DP"]}
chart={chart as ChartDocument<"iidx:SP" | "iidx:DP">}
sc={sc}
/>
);
case "bms":
return <BMSCoreCells sc={sc} rating={rating} />;
case "sdvx":
case "usc":
return (
<SDVXScoreCoreCells
sc={sc}
chart={
chart as ChartDocument<"sdvx:Single" | "usc:Controller" | "usc:Keyboard">
}
/>
);
case "museca":
return <MusecaCoreCells sc={sc} rating={rating} />;
case "wacca":
return <WACCACoreCells sc={sc} rating={rating} />;
case "popn":
return <PopnCoreCells sc={sc} rating={rating} />;
case "jubeat":
return <JubeatCoreCells sc={sc} rating={rating} />;
case "chunithm":
return <CHUNITHMCoreCells sc={sc} rating={rating} />;
case "gitadora":
return <GitadoraCoreCells sc={sc} rating={rating} />;
case "pms":
return <PMSCoreCells sc={sc} rating={rating} />;
case "itg":
return <ITGCoreCells sc={sc} rating={rating} />;
}
return <GenericCoreCells sc={sc} rating={rating} />;
}
@@ -2,7 +2,7 @@ import { UppercaseFirst } from "util/misc";
import { useProfileRatingAlg } from "components/util/useScoreRatingAlg";
import React from "react";
import { Link } from "react-router-dom";
import { IDStrings, integer, UGSRatingsLookup } from "tachi-common";
import { IDStrings, integer, ProfileRatingLookup } from "tachi-common";
import { GamePT } from "types/react";
export default function RankingData({
@@ -11,7 +11,7 @@ export default function RankingData({
userID,
playtype,
}: {
rankingData: Record<UGSRatingsLookup[IDStrings], { ranking: number; outOf: integer }>;
rankingData: Record<ProfileRatingLookup[IDStrings], { ranking: number; outOf: integer }>;
userID: integer;
} & GamePT) {
const alg = useProfileRatingAlg(game, playtype);
@@ -19,7 +19,7 @@ export default function RankingData({
const extendData = [];
for (const k in rankingData) {
const key = k as UGSRatingsLookup[IDStrings];
const key = k as ProfileRatingLookup[IDStrings];
if (key !== alg) {
extendData.push(
@@ -8,7 +8,7 @@ import {
GetGamePTConfig,
IDStrings,
ScoreCalculatedDataLookup,
UGSRatingsLookup,
ProfileRatingLookup,
UserGameStats,
} from "tachi-common";
import { GameClassSets } from "tachi-common/game-classes";
@@ -16,7 +16,7 @@ import { GameClassSets } from "tachi-common/game-classes";
export default function UGPTRatingsTable({ ugs }: { ugs: UserGameStats }) {
const gptConfig = GetGamePTConfig(ugs.game, ugs.playtype);
const ratings = Object.entries(ugs.ratings) as [UGSRatingsLookup[IDStrings], number][];
const ratings = Object.entries(ugs.ratings) as [ProfileRatingLookup[IDStrings], number][];
return (
<MiniTable className="table-sm text-center" headers={["Player Stats"]} colSpan={2}>
@@ -4,7 +4,7 @@ import {
IDStrings,
ScoreCalculatedDataLookup,
SessionCalculatedDataLookup,
UGSRatingsLookup,
ProfileRatingLookup,
} from "tachi-common";
import { Playtype } from "tachi-common";
import useLUGPTSettings from "./useLUGPTSettings";
@@ -42,14 +42,14 @@ export function useSessionRatingAlg<I extends IDStrings = IDStrings>(
export function useProfileRatingAlg<I extends IDStrings = IDStrings>(
game: Game,
playtype: Playtype
): UGSRatingsLookup[I] {
): ProfileRatingLookup[I] {
const { settings } = useLUGPTSettings();
if (!settings?.preferences.preferredProfileAlg) {
const gptConfig = GetGamePTConfig(game, playtype);
return gptConfig.defaultProfileRatingAlg as UGSRatingsLookup[I];
return gptConfig.defaultProfileRatingAlg as ProfileRatingLookup[I];
}
return settings.preferences.preferredProfileAlg as UGSRatingsLookup[I];
return settings.preferences.preferredProfileAlg as ProfileRatingLookup[I];
}
+3 -3
View File
@@ -22,7 +22,7 @@ import {
ShowcaseStatFolder,
SongDocument,
TableDocument,
UGSRatingsLookup,
ProfileRatingLookup,
UserGameStats,
UserGameStatsSnapshot,
QuestDocument,
@@ -36,7 +36,7 @@ export interface UGPTStatsReturn<I extends IDStrings = IDStrings> {
mostRecentScore: ScoreDocument<I>;
totalScores: number;
rankingData: Record<
UGSRatingsLookup[I],
ProfileRatingLookup[I],
{
ranking: integer;
outOf: integer;
@@ -92,7 +92,7 @@ export interface UGPTChartPBComposition<I extends IDStrings = IDStrings> {
}
export type UGSWithRankingData<I extends IDStrings = IDStrings> = UserGameStats<I> & {
__rankingData: Record<UGSRatingsLookup[I], { outOf: number; ranking: number }>;
__rankingData: Record<ProfileRatingLookup[I], { outOf: number; ranking: number }>;
};
export interface SongChartsSearch<I extends IDStrings = IDStrings> {
+20
View File
@@ -16,6 +16,7 @@ import {
QuestDocument,
QuestSubscriptionDocument,
SessionCalculatedDataLookup,
ProfileRatingLookup,
} from "tachi-common";
import fjsh from "fast-json-stable-hash";
@@ -74,6 +75,25 @@ export function FormatGPTRating(
return value.toFixed(2);
}
export function FormatGPTProfileRating(
game: Game,
playtype: Playtype,
key: ProfileRatingLookup[IDStrings],
value: number | null
) {
if (value === null) {
return "No Data.";
}
const gptConfig = GetGamePTConfig(game, playtype);
if (gptConfig.profileRatingAlgFormatters[key]) {
return gptConfig.profileRatingAlgFormatters[key]!(value);
}
return value.toFixed(2);
}
export function FormatGPTSessionRating(
game: Game,
playtype: Playtype,
+14 -28
View File
@@ -16,23 +16,23 @@ import {
WaccaColours,
WaccaStageUps,
} from "../game-classes";
import { FormatInt, FormatSieglindeBMS, FormatSieglindePMS } from "../utils/util";
import { FormatSieglindeBMS, FormatSieglindePMS } from "../utils/util";
import type { GPTTierlists } from "..";
import type { ClassInfo, GameClassSets } from "../game-classes";
import type { ESDJudgementFormat } from "../lib/esd";
import type {
Difficulties,
Game,
GPTSupportedVersions,
Game,
Grades,
IDStrings,
JudgementLookup,
Lamps,
Playtype,
Playtypes,
ProfileRatingLookup,
ScoreCalculatedDataLookup,
SessionCalculatedDataLookup,
UGSRatingsLookup,
} from "../types";
export interface GameConfig<G extends Game = Game> {
@@ -49,21 +49,21 @@ interface BaseGamePTConfig<I extends IDStrings> {
defaultScoreRatingAlg: ScoreCalculatedDataLookup[I];
defaultSessionRatingAlg: SessionCalculatedDataLookup[I];
defaultProfileRatingAlg: UGSRatingsLookup[I];
defaultProfileRatingAlg: ProfileRatingLookup[I];
scoreRatingAlgs: Array<ScoreCalculatedDataLookup[I]>;
sessionRatingAlgs: Array<SessionCalculatedDataLookup[I]>;
profileRatingAlgs: Array<UGSRatingsLookup[I]>;
profileRatingAlgs: Array<ProfileRatingLookup[I]>;
scoreRatingAlgDescriptions: Record<ScoreCalculatedDataLookup[I], string>;
sessionRatingAlgDescriptions: Record<SessionCalculatedDataLookup[I], string>;
profileRatingAlgDescriptions: Record<UGSRatingsLookup[I], string>;
profileRatingAlgDescriptions: Record<ProfileRatingLookup[I], string>;
scoreRatingAlgFormatters: Partial<Record<ScoreCalculatedDataLookup[I], (v: number) => string>>;
sessionRatingAlgFormatters: Partial<
Record<SessionCalculatedDataLookup[I], (v: number) => string>
>;
profileRatingAlgFormatters: Partial<Record<UGSRatingsLookup[I], (v: number) => string>>;
profileRatingAlgFormatters: Partial<Record<ProfileRatingLookup[I], (v: number) => string>>;
difficulties: Array<Difficulties[I]>;
shortDifficulties: Partial<Record<Difficulties[I], string>>;
@@ -1291,16 +1291,9 @@ const GAME_PT_CONFIGS: GamePTConfigs = {
skill: "The average of your best 10 skill ratings this session.",
},
scoreRatingAlgFormatters: {
skill: FormatInt,
},
profileRatingAlgFormatters: {
skill: FormatInt,
naiveSkill: FormatInt,
},
sessionRatingAlgFormatters: {
skill: FormatInt,
},
scoreRatingAlgFormatters: {},
profileRatingAlgFormatters: {},
sessionRatingAlgFormatters: {},
difficulties: [
"BASIC",
@@ -1330,7 +1323,7 @@ const GAME_PT_CONFIGS: GamePTConfigs = {
MASTER: COLOUR_SET.purple,
"BASS BASIC": COLOUR_SET.vibrantBlue,
"BASS ADVANCED": COLOUR_SET.vibrantOrange,
"BASS EXTREME": "todo", // colourset vibrant red
"BASS EXTREME": COLOUR_SET.vibrantRed,
"BASS MASTER": COLOUR_SET.vibrantPurple,
},
@@ -1397,16 +1390,9 @@ const GAME_PT_CONFIGS: GamePTConfigs = {
skill: "The average of your best 10 skill ratings this session.",
},
scoreRatingAlgFormatters: {
skill: FormatInt,
},
profileRatingAlgFormatters: {
skill: FormatInt,
naiveSkill: FormatInt,
},
sessionRatingAlgFormatters: {
skill: FormatInt,
},
scoreRatingAlgFormatters: {},
profileRatingAlgFormatters: {},
sessionRatingAlgFormatters: {},
difficulties: ["BASIC", "ADVANCED", "EXTREME", "MASTER"],
shortDifficulties: {
+1 -1
View File
@@ -80,7 +80,7 @@ export const GitadoraColours: Array<ClassInfo> = [
mouseoverCSS("GOLD", "金", "Gold", "gold"),
// @todo #11 Come up with CSS for Gitadora's rainbow icon.
mouseoverCSS("RAINBOW", "虹", "Rainbow", "todo"),
mouseoverCSS("RAINBOW", "虹", "Rainbow", ""),
];
export const BMSGenocideDans: Array<ClassInfo> = [
+4 -4
View File
@@ -600,7 +600,7 @@ export interface UserDocument extends MongoDBDocument {
authLevel: UserAuthLevels;
}
export interface UGSRatingsLookup {
export interface ProfileRatingLookup {
"iidx:SP": "BPI" | "ktLampRating";
"iidx:DP": "BPI" | "ktLampRating";
"popn:9B": "naiveClassPoints";
@@ -624,7 +624,7 @@ export interface UserGameStats<I extends IDStrings = IDStrings> extends MongoDBD
userID: integer;
game: IDStringToGame[I];
playtype: IDStringToPlaytype[I];
ratings: Partial<Record<UGSRatingsLookup[I], number | null>>;
ratings: Partial<Record<ProfileRatingLookup[I], number | null>>;
classes: Partial<GameClasses<I>>;
}
@@ -1359,7 +1359,7 @@ export interface UGPTSettings<I extends IDStrings = IDStrings> extends MongoDBDo
preferences: {
preferredScoreAlg: ScoreCalculatedDataLookup[I] | null;
preferredSessionAlg: SessionCalculatedDataLookup[I] | null;
preferredProfileAlg: UGSRatingsLookup[I] | null;
preferredProfileAlg: ProfileRatingLookup[I] | null;
stats: Array<ShowcaseStatDetails>;
scoreBucket: "grade" | "lamp" | null;
defaultTable: string | null;
@@ -1372,7 +1372,7 @@ export interface UGPTSettings<I extends IDStrings = IDStrings> extends MongoDBDo
export interface UserGameStatsSnapshot<I extends IDStrings = IDStrings>
extends MongoDBDocument,
UserGameStats<I> {
rankings: Record<UGSRatingsLookup[I], { ranking: integer | null; outOf: integer }>;
rankings: Record<ProfileRatingLookup[I], { ranking: integer | null; outOf: integer }>;
playcount: integer;
timestamp: integer;
}
+10 -1
View File
@@ -4,7 +4,7 @@ import type { Grades, integer, PBScoreDocument } from "../types";
import type { PrudenceError } from "prudence";
export function FormatInt(v: number): string {
return v.toString();
return Math.floor(v).toFixed(0);
}
export function FormatDifficulty(chart: ChartDocument, game: Game): string {
@@ -22,6 +22,15 @@ export function FormatDifficulty(chart: ChartDocument, game: Game): string {
return `${itgChart.data.difficultyTag} ${itgChart.level}`;
}
if (game === "gitadora") {
const gptConfig = GetGamePTConfig(game, chart.playtype);
const shortDiff = gptConfig.shortDifficulties[chart.difficulty] ?? chart.difficulty;
// gitadora should always use short diffs. they just look better.
return `${shortDiff} ${chart.level}`;
}
const gameConfig = GetGameConfig(game);
if (gameConfig.validPlaytypes.length > 1) {
+1 -1
View File
@@ -111,7 +111,7 @@ const staticIndexes: Partial<Record<Databases, Array<Index>>> = {
"charts-sdvx": [index({ "data.inGameID": 1, difficulty: 1 })],
"charts-museca": [index({ "data.inGameID": 1, difficulty: 1 }, UNIQUE)],
"charts-chunithm": [index({ "data.inGameID": 1, difficulty: 1 }, UNIQUE)],
"charts-gitadora": [index({ "data.inGameID": 1, difficulty: 1 }, UNIQUE)],
"charts-gitadora": [index({ "data.inGameID": 1, difficulty: 1, playtype: 1 }, UNIQUE)],
"charts-wacca": [index({ isHot: 1 })],
"charts-usc": [index({ "data.hashSHA1": 1, playtype: 1 }, UNIQUE)],
"charts-jubeat": [index({ "data.inGameID": 1, difficulty: 1 }, UNIQUE)],
@@ -91,6 +91,9 @@ export function GenericCalculatePercent(game: Game, score: number, chart?: Chart
case "jubeat":
throw new Error(`Cannot calculate percent for jubeat. Music Rate must be provided.`);
// gitadora's score is just a percent
// this is a hack: we need a refactor for this.
case "gitadora":
case "itg":
return score;
@@ -362,7 +362,7 @@ export async function ResolveMatchTypeToKTData(
);
}
const identifier = data.identifier;
const identifier = Number(data.identifier);
const difficulty = AssertStrAsDifficulty(data.difficulty, game, context.playtype);
+2 -2
View File
@@ -10,7 +10,7 @@ import type {
integer,
Playtype,
UserDocument,
UGSRatingsLookup,
ProfileRatingLookup,
UserGameStats,
} from "tachi-common";
@@ -192,7 +192,7 @@ export async function GetAllRankings(stats: UserGameStats) {
export async function GetUsersRankingAndOutOf(
stats: UserGameStats,
alg?: UGSRatingsLookup[IDStrings]
alg?: ProfileRatingLookup[IDStrings]
) {
const gptConfig = GetGamePTConfig(stats.game, stats.playtype);