checkpoint: insanity ascends (35)

127 errors to go
This commit is contained in:
zkldi
2023-01-19 16:39:42 +00:00
parent 2b0e65f0c8
commit 5cbb2d5f8a
131 changed files with 2106 additions and 1886 deletions
+22 -10
View File
@@ -18,7 +18,13 @@ import { UserContext } from "context/UserContext";
import React, { useContext, useEffect, useState } from "react";
import { Button, Col, Row } from "react-bootstrap";
import { Link } from "react-router-dom";
import { FormatChart, FormatGame, GetGamePTConfig, UserDocument } from "tachi-common";
import {
FormatChart,
FormatGame,
GetGamePTConfig,
GetScoreEnumConfs,
UserDocument,
} from "tachi-common";
import { ActivityReturn, RecordActivityReturn, SessionReturns } from "types/api-returns";
import { UGPT } from "types/react";
import { ScoreDataset } from "types/tables";
@@ -556,15 +562,21 @@ function SessionShower({ sessionID }: { sessionID: string }) {
return false;
}
if (e.isNewScore) {
return (
score.scoreData.lampIndex > gptConfig.lamps.indexOf(gptConfig.clearLamp) ||
score.scoreData.gradeIndex > gptConfig.grades.indexOf(gptConfig.clearGrade)
);
} else if (e.gradeDelta > 0) {
return score.scoreData.gradeIndex > gptConfig.grades.indexOf(gptConfig.clearGrade);
} else if (e.lampDelta > 0) {
return score.scoreData.lampIndex > gptConfig.lamps.indexOf(gptConfig.clearLamp);
const enumMetrics = GetScoreEnumConfs(gptConfig);
// for all enum metrics, check if this score beats the minimum relevant enum
// and is a raise.
for (const [metric, conf] of Object.entries(enumMetrics)) {
if (!e.isNewScore && e.deltas[metric] <= 0) {
continue;
}
if (
// @ts-expect-error its gonna exist buddy
score.scoreData.enumIndexes[metric] > conf.values.indexOf(conf.minimumRelevantValue)
) {
return true;
}
}
return false;
+31 -17
View File
@@ -1,59 +1,73 @@
import { UppercaseFirst } from "util/misc";
import QuickTooltip from "components/layout/misc/QuickTooltip";
import { GPT_CLIENT_IMPLEMENTATIONS } from "lib/game-implementations";
import React from "react";
import { Badge } from "react-bootstrap";
import { GetGamePTConfig, IDStrings, integer } from "tachi-common";
import { GameClassSets } from "tachi-common/game-classes";
import { Classes, GPTString, GetGPTString, GetGamePTConfig } from "tachi-common";
import { GamePT } from "types/react";
export default function ClassBadge<I extends IDStrings = IDStrings>({
export default function ClassBadge<GPT extends GPTString = GPTString>({
game,
playtype,
classSet,
classValue,
showSetOnHover = true,
}: {
classSet: GameClassSets[I];
classValue: integer;
classSet: Classes[GPT];
classValue: string;
showSetOnHover?: boolean;
} & GamePT) {
const gptConfig = GetGamePTConfig(game, playtype);
const classStyle =
// @ts-expect-error hepl i'm trapped in a type factory
GPT_CLIENT_IMPLEMENTATIONS[GetGPTString(game, playtype)].classColours[classSet][classValue];
const data = gptConfig.classHumanisedFormat[classSet][classValue];
const data = GetGamePTConfig(game, playtype).classes[classSet].values.find(
(e) => e.id === classValue
);
if (!data) {
throw new Error(`Unknown class value ${classSet}:${classValue}`);
return (
<>
{classSet} {classValue} (messed up!)
</>
);
}
let badgeComponent;
if (data.variant) {
if (classStyle === null) {
badgeComponent = (
<Badge className="mx-2" variant={data.variant}>
<Badge
className="mx-2"
style={{
backgroundColor: "#131313",
}}
>
{data.display}
</Badge>
);
} else if (data.css) {
} else if (typeof classStyle === "string") {
badgeComponent = (
<Badge className="mx-2" style={data.css}>
<Badge className="mx-2" variant={classStyle}>
{data.display}
</Badge>
);
} else {
badgeComponent = (
<Badge className="mx-2" variant="secondary">
<Badge className="mx-2" style={classStyle}>
{data.display}
</Badge>
);
}
if (data.mouseover && showSetOnHover) {
if (data.hoverText && showSetOnHover) {
return (
<QuickTooltip tooltipContent={`${UppercaseFirst(classSet)}: ${data.mouseover}`}>
<QuickTooltip tooltipContent={`${UppercaseFirst(classSet)}: ${data.hoverText}`}>
{badgeComponent}
</QuickTooltip>
);
} else if (data.mouseover) {
return <QuickTooltip tooltipContent={data.mouseover}>{badgeComponent}</QuickTooltip>;
} else if (data.hoverText) {
return <QuickTooltip tooltipContent={data.hoverText}>{badgeComponent}</QuickTooltip>;
} else if (showSetOnHover) {
<QuickTooltip tooltipContent={UppercaseFirst(classSet)}>{badgeComponent}</QuickTooltip>;
}
@@ -38,9 +38,9 @@ export default function ScoreLeaderboard({
const [alg, setAlg] = useState(defaultAlg);
const SelectComponent =
gptConfig.scoreRatingAlgs.length > 1 ? (
Object.keys(gptConfig.scoreRatingAlgs).length > 1 ? (
<Form.Control as="select" value={alg} onChange={(e) => setAlg(e.target.value as any)}>
{gptConfig.scoreRatingAlgs.map((e) => (
{Object.keys(gptConfig.scoreRatingAlgs).map((e) => (
<option key={e} value={e}>
{UppercaseFirst(e)}
</option>
@@ -1,4 +1,5 @@
import QuickTooltip from "components/layout/misc/QuickTooltip";
import RatingSystemPart from "components/tables/cells/RatingSystemPart";
import MiniTable from "components/tables/components/MiniTable";
import ApiError from "components/util/ApiError";
import ExternalLink from "components/util/ExternalLink";
@@ -6,12 +7,13 @@ import Icon from "components/util/Icon";
import Loading from "components/util/Loading";
import Muted from "components/util/Muted";
import useApiQuery from "components/util/query/useApiQuery";
import { UserContext } from "context/UserContext";
import { AllLUGPTStatsContext } from "context/AllLUGPTStatsContext";
import { UserContext } from "context/UserContext";
import { GPT_CLIENT_IMPLEMENTATIONS } from "lib/game-implementations";
import React, { useContext } from "react";
import { Col, Row } from "react-bootstrap";
import { Link } from "react-router-dom";
import { ChartDocument, FolderDocument, Game, GetGamePTConfig } from "tachi-common";
import { ChartDocument, FolderDocument, Game, GetGPTString } from "tachi-common";
import { GamePT } from "types/react";
export default function ChartInfoFormat({
@@ -19,9 +21,9 @@ export default function ChartInfoFormat({
game,
playtype,
}: { chart: ChartDocument } & GamePT) {
const gptConfig = GetGamePTConfig(game, playtype);
const gptImpl = GPT_CLIENT_IMPLEMENTATIONS[GetGPTString(game, playtype)];
const withTierlists = gptConfig.tierlists.filter((e) => chart.tierlistInfo[e]);
const ratingSystems = gptImpl.ratingSystems;
const { data, error } = useApiQuery<FolderDocument[]>(
`/games/${game}/${playtype}/charts/${chart.chartID}/folders`
@@ -71,27 +73,44 @@ export default function ChartInfoFormat({
<ChartInfoMiddle chart={chart} game={game} />
</Col>
<Col xs={12} lg={3}>
{withTierlists.length !== 0 ? (
<MiniTable headers={["Tierlist Info"]} colSpan={2}>
{withTierlists.map((e) => (
<tr key={e}>
<td>{e}</td>
<td>
{chart.tierlistInfo[e]!.text}{" "}
<Muted>({chart.tierlistInfo[e]!.value.toFixed(2)})</Muted>
{chart.tierlistInfo[e]!.individualDifference && (
<>
<br />
<QuickTooltip tooltipContent="Individual Difference - The difficulty of this varies massively between people!">
<span>
<Icon type="balance-scale-left" />
</span>
</QuickTooltip>
</>
)}
</td>
</tr>
))}
<RatingSystemPart chart={chart} game={game} />
{ratingSystems.length !== 0 ? (
<MiniTable headers={["Ratings"]} colSpan={2}>
{ratingSystems.map((e) => {
// @ts-expect-error bad types
const strV = e.toString(chart);
// @ts-expect-error bad types
const numV = e.toNumber(chart);
if (
strV === null ||
strV === undefined ||
numV === null ||
numV === undefined
) {
return null;
}
return (
<tr key={e.name}>
<td>{e}</td>
<td>
{strV} <Muted>({numV.toFixed(2)})</Muted>
{/* @ts-expect-error utterly silly types */}
{e.idvDifference(chart) && (
<>
<br />
<QuickTooltip tooltipContent="Individual Difference - The difficulty of this varies massively between people!">
<span>
<Icon type="balance-scale-left" />
</span>
</QuickTooltip>
</>
)}
</td>
</tr>
);
})}
</MiniTable>
) : (
<Muted>No tierlist info.</Muted>
@@ -21,8 +21,8 @@ export default function FolderInfoHeader({
folderDataset: FolderDataset;
folderTitle: string;
} & GamePT) {
const scoreBucket = useBucket(game, playtype);
const [view, setView] = useState<"grade" | "lamp">(scoreBucket);
const preferredDefaultEnum = useBucket(game, playtype);
const [view, setView] = useState<"grade" | "lamp">(preferredDefaultEnum);
const [breakdownInfo, setBreakdownInfo] = useState<"graph" | "switchboard">("switchboard");
return (
@@ -66,7 +66,7 @@ export default function FolderInfoHeader({
game={game}
playtype={playtype}
folderDataset={folderDataset}
view={view}
enumMetric={view}
/>
)}
</Card>
@@ -1,34 +1,35 @@
import { ChangeOpacity } from "util/color-opacity";
import { ONE_WEEK } from "util/constants/time";
import { CreateChartLink } from "util/data";
import { NumericSOV } from "util/sorts";
import { ONE_WEEK } from "util/constants/time";
import QuickTooltip from "components/layout/misc/QuickTooltip";
import ScoreCoreCells from "components/tables/game-core-cells/ScoreCoreCells";
import ApiError from "components/util/ApiError";
import Divider from "components/util/Divider";
import Loading from "components/util/Loading";
import useApiQuery from "components/util/query/useApiQuery";
import React, { useEffect, useMemo, useState } from "react";
import { Form } from "react-bootstrap";
import { GPT_CLIENT_IMPLEMENTATIONS } from "lib/game-implementations";
import React, { useMemo } from "react";
import { Link } from "react-router-dom";
import {
ChartDocument,
FormatChart,
Game,
GamePTConfig,
GetGPTString,
GetGamePTConfig,
GetScoreMetricConf,
IIDX_LAMPS,
UserDocument,
SessionDocument,
SessionScoreInfo,
UserDocument,
} from "tachi-common";
import { GamePT } from "types/react";
import { FolderDataset } from "types/tables";
import { SessionReturns } from "types/api-returns";
type Props = {
folderDataset: FolderDataset;
reqUser: UserDocument;
view: "grade" | "lamp";
enumMetric: string;
} & GamePT;
export default function FolderMinimap(props: Props) {
@@ -61,7 +62,7 @@ export default function FolderMinimap(props: Props) {
<FolderMinimapMain {...props} recentSession={session} />
</div>
<div className="d-block d-lg-none">
Sadly, Switchboard view doesn't work very well on mobile at the moment.
Sadly, Switchboard view doesn't work on mobile at the moment.
</div>
</>
);
@@ -71,40 +72,32 @@ function FolderMinimapMain({
game,
playtype,
folderDataset,
view,
enumMetric,
recentSession,
}: Props & {
recentSession: { session: SessionDocument; scoreInfo: Array<SessionScoreInfo> } | null;
}) {
const gptConfig = GetGamePTConfig(game, playtype);
const [sortAlg, setSortAlg] = useState(view === "grade" ? "score" : "lamp");
useEffect(() => {
if (view === "grade") {
setSortAlg("score");
} else {
setSortAlg("lamp");
}
}, [view]);
const getterFn = useMemo<(f: FolderDataset[0]) => number | undefined>(() => {
if (sortAlg === "score") {
return (c) => c.__related.pb?.scoreData.percent;
} else if (sortAlg === "lamp") {
return (c) => c.__related.pb?.scoreData.lampIndex;
} else if (sortAlg.startsWith("tierlist:")) {
const v = sortAlg.split("tierlist:")[1];
const conf = GetScoreMetricConf(gptConfig, enumMetric);
return (c) => c.tierlistInfo[v as keyof ChartDocument["tierlistInfo"]]?.value;
if (!conf) {
return () => 0; // wut
}
return () => 0;
}, [sortAlg]);
if (conf.type === "ENUM") {
// @ts-expect-error insane dynamic access
return (c) => c.__related.pb?.scoreData.enumIndexes[enumMetric];
}
// @ts-expect-error insane dynamic access
return (c) => c.__related.pb?.scoreData[enumMetric];
}, [enumMetric]);
const sortedDataset = useMemo(
() => folderDataset.slice(0).sort(NumericSOV((a) => getterFn(a) ?? -Infinity, true)),
[getterFn, folderDataset, sortAlg]
[getterFn, folderDataset, enumMetric]
);
// For the switchboard chart, display a raise icon on stuff the user has recently played.
@@ -114,7 +107,19 @@ function FolderMinimapMain({
}
return recentSession.scoreInfo
.filter((e) => e.isNewScore || e.gradeDelta > 0 || e.lampDelta > 0)
.filter((e) => {
if (e.isNewScore) {
return true;
}
for (const v of Object.values(e.deltas)) {
if (v >= 0) {
return true;
}
}
return false;
})
.map((e) => e.scoreID);
}, [recentSession]);
@@ -127,37 +132,20 @@ function FolderMinimapMain({
key={d.chartID}
data={d}
gptConfig={gptConfig}
view={view}
enumMetric={enumMetric}
game={game}
wasRecent={
(d.__related.pb &&
recentlyTouched.some(
(scoreID) =>
d.__related.pb!.composedFrom.scorePB === scoreID ||
d.__related.pb!.composedFrom.lampPB === scoreID
recentlyTouched.some((scoreID) =>
d.__related.pb!.composedFrom.find(
(e) => e.scoreID === scoreID
)
)) ??
false
}
/>
))}
</div>
<Divider />
<Form.Group>
<Form.Label>Sorting switchboard on:</Form.Label>
<Form.Control
as="select"
value={sortAlg}
onChange={(e) => setSortAlg(e.target.value)}
>
{view === "grade" && <option value="score">Your Score</option>}
{view === "lamp" && <option value="lamp">Your Lamp</option>}
{gptConfig.tierlists.map((e) => (
<option key={`tierlist:${e}`} value={`tierlist:${e}`}>
Tierlist: {e}
</option>
))}
</Form.Control>
</Form.Group>
</div>
</div>
);
@@ -166,16 +154,18 @@ function FolderMinimapMain({
function MinimapElement({
data,
gptConfig,
view,
enumMetric,
game,
wasRecent,
}: {
data: FolderDataset[0];
view: "grade" | "lamp";
enumMetric: string;
gptConfig: GamePTConfig;
game: Game;
wasRecent: boolean;
}) {
const gptImpl = GPT_CLIENT_IMPLEMENTATIONS[GetGPTString(game, data.playtype)];
let icon = "level-up-alt";
// if this user recently cleared mare nectaris
@@ -183,7 +173,8 @@ function MinimapElement({
if (
data.chartID === "924bf011fdd8334b609b02e382123f9f5440d16d" &&
data.__related.pb &&
data.__related.pb?.scoreData.lampIndex >= IIDX_LAMPS.EASY_CLEAR
// @ts-expect-error the above chartID guarantees mare nectaris
data.__related.pb?.scoreData.enumIndexes.lamp >= IIDX_LAMPS.EASY_CLEAR
) {
icon = "hand-middle-finger";
}
@@ -194,8 +185,8 @@ function MinimapElement({
}
// @ts-expect-error unhinged dynamic access (i dont care)
return gptConfig[`${view}Colours`][data.__related.pb.scoreData[view]];
}, [data.__related.pb, gptConfig, view]);
return gptImpl.enumColours[enumMetric][data.__related.pb.scoreData[enumMetric]];
}, [data.__related.pb, gptConfig, enumMetric]);
return (
<QuickTooltip
@@ -209,17 +200,10 @@ function MinimapElement({
<br />
</>
)}
<span>{data.__related.pb?.scoreData.lamp ?? "Not Played"}</span>
{data.__related.pb && (
<>
<br />
{data.__related.pb.scoreData.grade}
<br />
<span>
{data.__related.pb.scoreData.score} (
{data.__related.pb.scoreData.percent.toFixed(2)}%)
</span>
</>
{data.__related.pb ? (
<ScoreCoreCells chart={data} game={game} score={data.__related.pb} />
) : (
<span>Not Played</span>
)}
</div>
}
@@ -12,7 +12,7 @@ export default function SongChartInfoFormat({
chart: ChartDocument | null;
game: Game;
}) {
// lazy casting hack to avoid IDStrings
// lazy casting hack to avoid GPTString
const props = { song: tempSong, chart: tempChart, game } as any;
const { song, chart } = props;
+3 -3
View File
@@ -1,4 +1,4 @@
import { Game, IDStrings, Playtype } from "tachi-common";
import { Game, GPTString, Playtype } from "tachi-common";
import { GPTUtility } from "types/ugpt";
import { BMSCustomTablesTool } from "./tools/BMSCustomTablesTool";
import { IIDXEamusementExportTool } from "./tools/IIDXEamusementExportTool";
@@ -7,7 +7,7 @@ import { JubilityBreakdownInsight } from "./insights/JubilityBreakdownInsight";
import { BMSSieglindeInfoTool } from "./tools/BMSSieglindeInfoTool";
// What utils does each game support?
const GPT_UTILS: Record<IDStrings, Array<GPTUtility>> = {
const GPT_UTILS: Record<GPTString, Array<GPTUtility>> = {
"bms:7K": [BMSCustomTablesTool, BMSSieglindeInfoTool],
"bms:14K": [BMSCustomTablesTool, BMSSieglindeInfoTool],
"chunithm:Single": [],
@@ -29,7 +29,7 @@ const GPT_UTILS: Record<IDStrings, Array<GPTUtility>> = {
};
export function GetGPTUtils(game: Game, playtype: Playtype) {
const idString = `${game}:${playtype}` as IDStrings;
const idString = `${game}:${playtype}` as GPTString;
return GPT_UTILS[idString];
}
@@ -12,9 +12,9 @@ import { TachiConfig } from "lib/config";
import React from "react";
import { Col, Row } from "react-bootstrap";
import { BMS_TABLES, ChartDocument, SongDocument } from "tachi-common";
import { FormatSieglindeBMS } from "tachi-common/config/internal-utils";
import { UGPT } from "types/react";
import { GPTUtility } from "types/ugpt";
import { FormatSieglindeBMS } from "tachi-common/config/game-support/bms";
type DatasetElement = ChartDocument<"bms:7K" | "bms:14K"> & { __related: { song: SongDocument } };
@@ -76,20 +76,20 @@ function Component({ game, playtype, reqUser }: UGPT) {
[
"EASY CLEAR Sieglinde",
"EC sgl.",
NumericSOV((x) => x.tierlistInfo["sgl-EC"]?.value ?? -Infinity),
NumericSOV((x) => x.data.sglEC ?? -Infinity),
],
[
"HARD CLEAR Sieglinde",
"EC sgl.",
NumericSOV((x) => x.tierlistInfo["sgl-HC"]?.value ?? -Infinity),
NumericSOV((x) => x.data.sglHC ?? -Infinity),
],
]}
rowFunction={(d) => (
<tr>
<BMSOrPMSDifficultyCell chart={d} game="bms" />
<TitleCell song={d.__related.song} game="bms" chart={d} />
<td>{FormatSieglindeBMS(d.tierlistInfo["sgl-EC"]?.value ?? 0)}</td>
<td>{FormatSieglindeBMS(d.tierlistInfo["sgl-HC"]?.value ?? 0)}</td>
<td>{FormatSieglindeBMS(d.data.sglEC ?? 0)}</td>
<td>{FormatSieglindeBMS(d.data.sglHC ?? 0)}</td>
</tr>
)}
defaultSortMode="EASY CLEAR Sieglinde"
@@ -115,7 +115,8 @@ export default function SessionFolderRaiseBreakdown({
const chartMap = CreateChartMap(sessionData.charts);
const songMap = CreateSongMap(sessionData.songs);
const preferredScoreBucket = settings?.preferences.scoreBucket ?? gptConfig.scoreBucket;
const preferredScoreBucket =
settings?.preferences.preferredDefaultEnum ?? gptConfig.preferredDefaultEnum;
if (selectedTable === "LOADING") {
return <Loading />;
@@ -5,7 +5,7 @@ import ScoreTable from "components/tables/scores/ScoreTable";
import Divider from "components/util/Divider";
import React, { useState } from "react";
import { Col, Row } from "react-bootstrap";
import { GetGamePTConfig, ScoreDocument, SessionDocument } from "tachi-common";
import { AnySessionRatingAlg, GetGamePTConfig, ScoreDocument, SessionDocument } from "tachi-common";
import { SessionReturns } from "types/api-returns";
import { ScoreDataset } from "types/tables";
import { SetState } from "types/react";
@@ -73,15 +73,15 @@ function RatingsOverview({ session }: { session: SessionDocument }) {
<Divider />
<Card header="Ratings">
<div className="d-flex text-center" style={{ justifyContent: "space-evenly" }}>
{gptConfig.sessionRatingAlgs.map((e) => (
{Object.keys(gptConfig.sessionRatingAlgs).map((e) => (
<div key={e}>
<div className="display-4">{UppercaseFirst(e)}</div>
<div style={{ fontSize: "1.2rem" }}>
{FormatSessionRating(
session.game,
session.playtype,
e,
session.calculatedData[e]
e as AnySessionRatingAlg,
session.calculatedData[e as AnySessionRatingAlg]
)}
</div>
</div>
@@ -1,35 +1,38 @@
import { APIFetchV1 } from "util/api";
import { ChangeOpacity } from "util/color-opacity";
import { CreateChartMap, CreateScoreIDMap, CreateSongMap } from "util/data";
import { PartialArrayRecordAssign } from "util/misc";
import { PartialArrayRecordAssign, UppercaseFirst } from "util/misc";
import DifficultyCell from "components/tables/cells/DifficultyCell";
import LampCell from "components/tables/cells/LampCell";
import ScoreCell from "components/tables/cells/ScoreCell";
import TitleCell from "components/tables/cells/TitleCell";
import MiniTable from "components/tables/components/MiniTable";
import { CommentModal, ModifyScore } from "components/tables/dropdowns/components/ScoreEditButtons";
import ScoreCoreCells from "components/tables/game-core-cells/ScoreCoreCells";
import Divider from "components/util/Divider";
import Icon from "components/util/Icon";
import Loading from "components/util/Loading";
import SelectButton from "components/util/SelectButton";
import { UserContext } from "context/UserContext";
import deepmerge from "deepmerge";
import { GPT_CLIENT_IMPLEMENTATIONS } from "lib/game-implementations";
import { GPTClientImplementation } from "lib/types";
import React, { useContext, useEffect, useMemo, useState } from "react";
import { useQuery } from "react-query";
import {
ChartDocument,
GPTString,
Game,
GamePTConfig,
GetGPTString,
GetGamePTConfig,
Grades,
IDStrings,
integer,
Lamps,
GetScoreMetricConf,
GetScoreMetrics,
ScoreDocument,
SessionScoreInfo,
SongDocument,
TableDocument,
integer,
} from "tachi-common";
import { ConfEnumScoreMetric } from "tachi-common/types/metrics";
import { SessionReturns } from "types/api-returns";
type SetScores = (scores: ScoreDocument[]) => void;
@@ -57,7 +60,10 @@ export default function SessionRaiseBreakdown({
return res.body;
});
const [view, setView] = useState<"lamps" | "both" | "grades">("both");
// null -> view all in small format
// string -> view this specific metric.
const [view, setView] = useState<string | null>(null);
if (error) {
return <>{(error as Error).message}</>;
@@ -75,17 +81,17 @@ export default function SessionRaiseBreakdown({
<div className="col-12 col-lg-6 offset-lg-3">
<div className="d-none d-lg-flex justify-content-center">
<div className="btn-group">
<SelectButton value={view} setValue={setView} id="lamps">
<SelectButton value={view} setValue={setView} id="lamp">
<Icon type="lightbulb" />
Lamps Only
</SelectButton>
<SelectButton value={view} setValue={setView} id="both">
<SelectButton value={view} setValue={setView} id={null}>
<Icon type="bolt" />
Both
All
</SelectButton>
<SelectButton value={view} setValue={setView} id="grades">
<SelectButton value={view} setValue={setView} id="grade">
<Icon type="sort-alpha-up" />
Grades Only
</SelectButton>
@@ -115,122 +121,103 @@ function SessionScoreStatBreakdown({
}: {
sessionData: SessionReturns;
setScores?: SetScores;
view: "lamps" | "both" | "grades";
view: string | null;
}) {
const songMap = CreateSongMap(sessionData.songs);
const chartMap = CreateChartMap(sessionData.charts);
const scoreMap = CreateScoreIDMap(sessionData.scores);
const gptConfig = GetGamePTConfig(sessionData.session.game, sessionData.session.playtype);
const [newLamps, newGrades] = useMemo(() => {
const newLamps: Partial<
Record<Lamps[IDStrings], { score: ScoreDocument; scoreInfo: SessionScoreInfo }[]>
> = {};
const newGrades: Partial<
Record<Grades[IDStrings], { score: ScoreDocument; scoreInfo: SessionScoreInfo }[]>
const enumMetrics = GetScoreMetrics(gptConfig, "ENUM");
const newEnums = useMemo(() => {
const newEnums: Record<
string,
Record<string, { score: ScoreDocument; scoreInfo: SessionScoreInfo }[]>
> = {};
for (const scoreInfo of sessionData.scoreInfo) {
const score = scoreMap.get(scoreInfo.scoreID);
for (const metric of enumMetrics) {
newEnums[metric] = {};
if (!score) {
console.error(
`Session score info contains scoreID ${scoreInfo.scoreID}, but no score exists?`
);
continue;
}
for (const scoreInfo of sessionData.scoreInfo) {
const score = scoreMap.get(scoreInfo.scoreID);
if (scoreInfo.isNewScore) {
PartialArrayRecordAssign(newLamps, score.scoreData.lamp, { score, scoreInfo });
PartialArrayRecordAssign(newGrades, score.scoreData.grade, { score, scoreInfo });
} else {
if (scoreInfo.lampDelta > 0) {
PartialArrayRecordAssign(newLamps, score.scoreData.lamp, { score, scoreInfo });
if (!score) {
console.error(
`Session score info contains scoreID ${scoreInfo.scoreID}, but no score exists?`
);
continue;
}
if (scoreInfo.gradeDelta > 0) {
PartialArrayRecordAssign(newGrades, score.scoreData.grade, {
score,
scoreInfo,
});
if (scoreInfo.isNewScore || scoreInfo.deltas[metric] > 0) {
PartialArrayRecordAssign(
newEnums[metric],
// @ts-expect-error yeah this is fine pls
score.scoreData[metric] as string,
{
score,
scoreInfo,
}
);
}
}
}
return [newLamps, newGrades];
return newEnums;
}, [view]);
const gptConfig = GetGamePTConfig(sessionData.session.game, sessionData.session.playtype);
const gptImpl =
GPT_CLIENT_IMPLEMENTATIONS[
GetGPTString(sessionData.session.game, sessionData.session.playtype)
];
return (
<>
{view === "both" ? (
<>
<div className="col-12 col-lg-6">
<MiniTable
headers={["Lamp", "New Lamps"]}
colSpan={[1, 2]}
className="table-sm"
>
<ElementStatTable
scores={sessionData.scores}
setScores={setScores}
chartMap={chartMap}
songMap={songMap}
counts={newLamps}
game={sessionData.session.game}
type="lamp"
gptConfig={gptConfig}
/>
</MiniTable>
</div>
<div className="col-12 col-lg-6">
<MiniTable
headers={["Grade", "New Grades"]}
colSpan={[1, 2]}
className="table-sm"
>
<ElementStatTable
scores={sessionData.scores}
setScores={setScores}
chartMap={chartMap}
songMap={songMap}
counts={newGrades}
game={sessionData.session.game}
type="grade"
gptConfig={gptConfig}
/>
</MiniTable>
</div>
</>
) : view === "grades" ? (
<div className="col-12">
<MiniTable headers={["Grade", "New Grades"]} colSpan={[1, 100]}>
<ElementStatTable
scores={sessionData.scores}
setScores={setScores}
fullSize
chartMap={chartMap}
songMap={songMap}
counts={newGrades}
game={sessionData.session.game}
type="grade"
gptConfig={gptConfig}
/>
</MiniTable>
{view === null ? (
<div className="d-flex w-100">
{enumMetrics.map((metric) => (
<div style={{ flex: 1 }}>
<MiniTable
headers={[
`${UppercaseFirst(metric)}s`,
`New ${UppercaseFirst(metric)}s`,
]}
colSpan={[1, 100]}
>
<ElementStatTable
scores={sessionData.scores}
setScores={setScores}
fullSize
chartMap={chartMap}
songMap={songMap}
counts={newEnums[metric]!}
game={sessionData.session.game}
metric={metric}
gptConfig={gptConfig}
gptImpl={gptImpl}
/>
</MiniTable>
</div>
))}
</div>
) : (
<div className="col-12">
<MiniTable headers={["Lamps", "New Lamps"]} colSpan={[1, 100]}>
<MiniTable
headers={[`${UppercaseFirst(view)}s`, `New ${UppercaseFirst(view)}s`]}
colSpan={[1, 100]}
>
<ElementStatTable
scores={sessionData.scores}
setScores={setScores}
fullSize
chartMap={chartMap}
songMap={songMap}
counts={newLamps}
counts={newEnums[view]!}
game={sessionData.session.game}
type="lamp"
gptConfig={gptConfig}
gptImpl={gptImpl}
metric={view}
/>
</MiniTable>
</div>
@@ -240,7 +227,7 @@ function SessionScoreStatBreakdown({
}
function ElementStatTable({
type,
metric: metric,
counts,
gptConfig,
songMap,
@@ -249,27 +236,26 @@ function ElementStatTable({
fullSize = false,
scores,
setScores,
gptImpl,
}: {
type: "lamp" | "grade";
metric: string;
setScores?: SetScores;
scores: ScoreDocument[];
counts: Record<string, { score: ScoreDocument; scoreInfo: SessionScoreInfo }[]>;
gptConfig: GamePTConfig;
gptImpl: GPTClientImplementation<any>;
songMap: Map<integer, SongDocument<Game>>;
chartMap: Map<string, ChartDocument<IDStrings>>;
chartMap: Map<string, ChartDocument<GPTString>>;
game: Game;
fullSize?: boolean;
}) {
const tableContents = useMemo(() => {
// relements.. haha
const relevantElements =
type === "lamp"
? gptConfig.lamps.slice(gptConfig.lamps.indexOf(gptConfig.clearLamp) - 1).reverse()
: gptConfig.grades
.slice(gptConfig.grades.indexOf(gptConfig.clearGrade) - 1)
.reverse();
const conf = GetScoreMetricConf(gptConfig, metric) as ConfEnumScoreMetric<string>;
const colours = type === "lamp" ? gptConfig.lampColours : gptConfig.gradeColours;
// relements.. haha
const relevantElements = conf.values.slice(conf.values.indexOf(conf.minimumRelevantValue));
const colours = gptImpl.enumColours[metric];
const tableContents = [];
for (const element of relevantElements) {
@@ -283,7 +269,6 @@ function ElementStatTable({
<tr key={element} className="breakdown-hover-row">
<td
style={{
// @ts-expect-error this is a hack due to the funky type of colours and element.
backgroundColor: ChangeOpacity(colours[element], 0.1),
}}
rowSpan={counts[element]!.length}
@@ -298,7 +283,7 @@ function ElementStatTable({
fullSize,
game,
gptConfig,
type,
metric: metric,
scores,
setScores,
}}
@@ -317,7 +302,7 @@ function ElementStatTable({
fullSize,
game,
gptConfig,
type,
metric: metric,
scores,
setScores,
}}
@@ -328,7 +313,7 @@ function ElementStatTable({
}
return tableContents;
}, [type, counts, fullSize, game, scores]);
}, [metric, counts, fullSize, game, scores]);
if (tableContents.length === 0) {
return (
@@ -349,7 +334,7 @@ function BreakdownChartContents({
chartMap,
fullSize,
gptConfig,
type,
metric,
scores,
setScores,
}: {
@@ -360,7 +345,7 @@ function BreakdownChartContents({
songMap: Map<integer, SongDocument>;
chartMap: Map<string, ChartDocument>;
gptConfig: GamePTConfig;
type: "lamp" | "grade";
metric: string;
scores: Array<ScoreDocument>;
setScores?: SetScores;
}) {
@@ -425,25 +410,24 @@ function BreakdownChartContents({
let preScoreCell = <td>No Play</td>;
if (!scoreInfo.isNewScore) {
const oldGradeIndex = score.scoreData.gradeIndex - scoreInfo.gradeDelta;
const oldLampIndex = score.scoreData.lampIndex - scoreInfo.lampDelta;
const newScoreData = score.scoreData;
for (const [k, d] of Object.entries(scoreInfo.deltas)) {
// @ts-expect-error ugh
if (typeof score.scoreData[k] === "string") {
// @ts-expect-error ugh
newScoreData.enumIndexes[k] = score.scoreData.enumIndexes[k] - d;
} else {
// @ts-expect-error ugh
newScoreData[k] = score.scoreData[k] - d;
}
}
const mockScore = deepmerge(score, {
scoreData: {
score: score.scoreData.score - scoreInfo.scoreDelta,
percent: score.scoreData.percent - scoreInfo.percentDelta,
grade: gptConfig.grades[oldGradeIndex],
gradeIndex: oldGradeIndex,
lamp: gptConfig.lamps[oldLampIndex],
lampIndex: oldLampIndex,
},
scoreData: newScoreData,
}) as ScoreDocument;
if (type === "grade") {
preScoreCell = <ScoreCell score={mockScore} />;
} else {
preScoreCell = <LampCell score={mockScore} />;
}
preScoreCell = <ScoreCoreCells chart={chart} game={game} score={mockScore} />;
}
if (score) {
@@ -453,11 +437,7 @@ function BreakdownChartContents({
<DifficultyCell chart={chart} game={game} />
{preScoreCell}
<td>⟶</td>
{type === "grade" ? (
<ScoreCell {...{ score, game, playtype: score.playtype }} />
) : (
<LampCell score={score} />
)}
<ScoreCoreCells chart={chart} game={game} score={score} />
</>
);
}
@@ -5,8 +5,8 @@ import Divider from "components/util/Divider";
import Muted from "components/util/Muted";
import useLUGPTSettings from "components/util/useLUGPTSettings";
import React from "react";
import { BMS_TABLES, ChartDocument, COLOUR_SET, UGPTSettings } from "tachi-common";
import TierlistInfoPart from "./TierlistInfoPart";
import { BMS_TABLES, ChartDocument, COLOUR_SET, UGPTSettingsDocument } from "tachi-common";
import RatingSystemPart from "./RatingSystemPart";
export default function BMSOrPMSDifficultyCell({
chart,
@@ -15,7 +15,9 @@ export default function BMSOrPMSDifficultyCell({
chart: ChartDocument<"bms:7K" | "bms:14K" | "pms:Controller" | "pms:Keyboard">;
game: "bms" | "pms";
}) {
const { settings } = useLUGPTSettings() as { settings: UGPTSettings<"bms:7K" | "bms:14K"> };
const { settings } = useLUGPTSettings() as {
settings: UGPTSettingsDocument<"bms:7K" | "bms:14K">;
};
const hasLevel = chart.data.tableFolders.length > 0;
@@ -31,10 +33,10 @@ export default function BMSOrPMSDifficultyCell({
if (settings?.preferences.gameSpecific.displayTables) {
filteredTables = tables.filter(
(e) => !settings.preferences.gameSpecific.displayTables.includes(e.table)
(e) => !settings.preferences.gameSpecific.displayTables!.includes(e.table)
);
tables = tables.filter((e) =>
settings.preferences.gameSpecific.displayTables.includes(e.table)
settings.preferences.gameSpecific.displayTables!.includes(e.table)
);
}
@@ -81,7 +83,7 @@ export default function BMSOrPMSDifficultyCell({
<span>{levelText}</span>
)}
<TierlistInfoPart chart={chart} game={game} />
<RatingSystemPart chart={chart} game={game} />
</td>
);
}
@@ -1,7 +1,8 @@
import { ChangeOpacity } from "util/color-opacity";
import { IsNotNullish } from "util/misc";
import { GetEnumColour } from "lib/game-implementations";
import React from "react";
import { GetGamePTConfig, PBScoreDocument, ScoreDocument } from "tachi-common";
import { PBScoreDocument, ScoreDocument } from "tachi-common";
export default function BMSOrPMSLampCell({
score,
@@ -10,19 +11,17 @@ export default function BMSOrPMSLampCell({
| ScoreDocument<"bms:7K" | "bms:14K" | "pms:Keyboard" | "pms:Controller">
| PBScoreDocument<"bms:7K" | "bms:14K" | "pms:Keyboard" | "pms:Controller">;
}) {
const gptConfig = GetGamePTConfig(score.game, score.playtype);
return (
<td
style={{
backgroundColor: ChangeOpacity(gptConfig.lampColours[score.scoreData.lamp], 0.2),
backgroundColor: ChangeOpacity(GetEnumColour(score, "lamp"), 0.2),
}}
>
<strong>{score.scoreData.lamp}</strong>
{IsNotNullish(score.scoreData.hitMeta.bp) && (
{IsNotNullish(score.scoreData.optional.bp) && (
<>
<br />
<small>[BP: {score.scoreData.hitMeta.bp}]</small>
<small>[BP: {score.scoreData.optional.bp}]</small>
</>
)}
</td>
+15 -14
View File
@@ -1,4 +1,4 @@
import { GetGradeFromPercent, IsNullish } from "util/misc";
import { IsNullish } from "util/misc";
import QuickTooltip from "components/layout/misc/QuickTooltip";
import Divider from "components/util/Divider";
import Muted from "components/util/Muted";
@@ -6,7 +6,15 @@ import useLUGPTSettings from "components/util/useLUGPTSettings";
import { UserContext } from "context/UserContext";
import React, { useContext } from "react";
import { PoyashiBPI } from "rg-stats";
import { ChartDocument, integer, PBScoreDocument, ScoreDocument } from "tachi-common";
import {
ChartDocument,
GetGrade,
IIDXLIKE_GBOUNDARIES,
integer,
PBScoreDocument,
ScoreDocument,
} from "tachi-common";
import { GPT_CLIENT_IMPLEMENTATIONS } from "lib/game-implementations";
import MiniTable from "../components/MiniTable";
import DeltaCell from "./DeltaCell";
import ScoreCell from "./ScoreCell";
@@ -135,22 +143,15 @@ export default function BPICell({
function FormatAverage(score: integer, playtype: "SP" | "DP", notecount: integer) {
const percent = (100 * score) / (notecount * 2);
const grade = GetGradeFromPercent("iidx", playtype, percent);
const grade = GetGrade(IIDXLIKE_GBOUNDARIES, percent);
return {
score: (
<ScoreCell
score={
{
game: "iidx",
playtype,
scoreData: {
grade,
percent,
score,
},
} as any
}
colour={GPT_CLIENT_IMPLEMENTATIONS[`iidx:${playtype}`].enumColours.grade[grade]}
grade={grade}
percent={percent}
score={score}
/>
),
delta: (
@@ -1,6 +1,6 @@
import { GenericFormatGradeDelta } from "util/grade-deltas";
import React from "react";
import { Game, Grades, IDStrings, Playtypes } from "tachi-common";
import { Game, Grades, GPTString, Playtypes } from "tachi-common";
export default function DeltaCell({
game,
@@ -13,7 +13,7 @@ export default function DeltaCell({
playtype: Playtypes[Game];
score: number;
percent: number;
grade: Grades[IDStrings];
grade: Grades[GPTString];
}) {
if (score === 0) {
return <td>N/A</td>;
@@ -11,7 +11,7 @@ import {
GetGamePTConfig,
} from "tachi-common";
import BMSOrPMSDifficultyCell from "./BMSOrPMSDifficultyCell";
import TierlistInfoPart from "./TierlistInfoPart";
import RatingSystemPart from "./RatingSystemPart";
import USCDifficultyCell from "./USCDifficultyCell";
import ITGDifficultyCell from "./ITGDifficultyCell";
@@ -73,7 +73,7 @@ export default function DifficultyCell({
</QuickTooltip>
)}
{!noTierlist && Object.keys(chart.tierlistInfo).length > 0 && (
<TierlistInfoPart chart={chart} game={game} />
<RatingSystemPart chart={chart} game={game} />
)}
{!chart.isPrimary && (
<QuickTooltip tooltipContent="This chart is an alternate, old chart.">
@@ -1,8 +1,9 @@
import { IsScore } from "util/asserts";
import { ChangeOpacity } from "util/color-opacity";
import { IsNotNullish } from "util/misc";
import { ChartDocument, GetGamePTConfig, PBScoreDocument, ScoreDocument } from "tachi-common";
import { GetEnumColour } from "lib/game-implementations";
import React from "react";
import { ChartDocument, GetGPTString, PBScoreDocument, ScoreDocument } from "tachi-common";
export default function IIDXLampCell({
sc,
@@ -11,33 +12,36 @@ export default function IIDXLampCell({
sc: ScoreDocument<"iidx:SP" | "iidx:DP"> | PBScoreDocument<"iidx:SP" | "iidx:DP">;
chart: ChartDocument<"iidx:SP" | "iidx:DP">;
}) {
const gptConfig = GetGamePTConfig("iidx", sc.playtype);
let gaugeText = null;
if (sc.scoreData.hitMeta.gsm) {
if (
sc.scoreData.optional.gsmEXHard &&
sc.scoreData.optional.gsmHard &&
sc.scoreData.optional.gsmNormal &&
sc.scoreData.optional.gsmEasy
) {
gaugeText = `EC: ${
sc.scoreData.hitMeta.gsm.EASY[sc.scoreData.hitMeta.gsm.EASY.length - 1]
}%, NC: ${sc.scoreData.hitMeta.gsm.NORMAL[sc.scoreData.hitMeta.gsm.NORMAL.length - 1]}%`;
sc.scoreData.optional.gsmEasy[sc.scoreData.optional.gsmEasy.length - 1]
}%, NC: ${sc.scoreData.optional.gsmNormal[sc.scoreData.optional.gsmNormal.length - 1]}%`;
} else if (IsScore(sc)) {
if (sc.scoreMeta.gauge === "EASY") {
gaugeText = `EC: ${sc.scoreData.hitMeta.gauge}%`;
gaugeText = `EC: ${sc.scoreData.optional.gauge}%`;
} else if (sc.scoreMeta.gauge === "NORMAL") {
gaugeText = `NC: ${sc.scoreData.hitMeta.gauge}%`;
gaugeText = `NC: ${sc.scoreData.optional.gauge}%`;
}
}
let bpText;
if (IsNotNullish(sc.scoreData.hitMeta.bp)) {
bpText = `[BP: ${sc.scoreData.hitMeta.bp}]`;
if (IsNotNullish(sc.scoreData.optional.bp)) {
bpText = `[BP: ${sc.scoreData.optional.bp}]`;
}
let cbrkCount;
let cbrkText;
if (sc.scoreData.hitMeta.comboBreak) {
cbrkCount = sc.scoreData.hitMeta.comboBreak;
if (sc.scoreData.optional.comboBreak) {
cbrkCount = sc.scoreData.optional.comboBreak;
} else if (
IsNotNullish(sc.scoreData.judgements.pgreat) &&
IsNotNullish(sc.scoreData.judgements.great) &&
@@ -57,7 +61,7 @@ export default function IIDXLampCell({
return (
<td
style={{
backgroundColor: ChangeOpacity(gptConfig.lampColours[sc.scoreData.lamp], 0.2),
backgroundColor: ChangeOpacity(GetEnumColour(sc, "lamp"), 0.2),
}}
>
<strong>{sc.scoreData.lamp}</strong>
@@ -4,7 +4,7 @@ import Muted from "components/util/Muted";
import React from "react";
import { ChartDocument, COLOUR_SET, GetGamePTConfig } from "tachi-common";
import MiniTable from "../components/MiniTable";
import TierlistInfoPart from "./TierlistInfoPart";
import RatingSystemPart from "./RatingSystemPart";
export default function ITGDifficultyCell({ chart }: { chart: ChartDocument<"itg:Stamina"> }) {
const gptConfig = GetGamePTConfig("itg", "Stamina");
@@ -45,7 +45,7 @@ export default function ITGDifficultyCell({ chart }: { chart: ChartDocument<"itg
</span>
<br />
<Muted>{chart.data.charter}</Muted>
<TierlistInfoPart chart={chart} game="itg" />
<RatingSystemPart chart={chart} game="itg" />
</div>
</QuickTooltip>
</td>
@@ -1,20 +1,28 @@
import { ChangeOpacity } from "util/color-opacity";
import { FormatMillions } from "util/misc";
import { GPT_CLIENT_IMPLEMENTATIONS } from "lib/game-implementations";
import React from "react";
import { GetGamePTConfig, PBScoreDocument, ScoreDocument } from "tachi-common";
export default function JubeatScoreCell({ sc }: { sc: ScoreDocument | PBScoreDocument }) {
const gptConfig = GetGamePTConfig(sc.game, sc.playtype);
import { PBScoreDocument, ScoreDocument } from "tachi-common";
export default function JubeatScoreCell({
sc,
}: {
sc: ScoreDocument<"jubeat:Single"> | PBScoreDocument<"jubeat:Single">;
}) {
return (
<td
style={{
backgroundColor: ChangeOpacity(gptConfig.gradeColours[sc.scoreData.grade], 0.2),
backgroundColor: ChangeOpacity(
GPT_CLIENT_IMPLEMENTATIONS["jubeat:Single"].enumColours.grade[
sc.scoreData.grade
],
0.2
),
}}
>
<strong>{sc.scoreData.grade}</strong>
<br />
<b>{sc.scoreData.percent.toFixed(2)}%</b>
<b>{sc.scoreData.musicRate.toFixed(2)}%</b>
<br />
{FormatMillions(sc.scoreData.score)}
</td>
@@ -1,17 +1,14 @@
import { ChangeOpacity } from "util/color-opacity";
import React from "react";
import { GetGamePTConfig, PBScoreDocument, ScoreDocument } from "tachi-common";
export default function LampCell({ score }: { score: ScoreDocument | PBScoreDocument }) {
const gptConfig = GetGamePTConfig(score.game, score.playtype);
export default function LampCell({ lamp, colour }: { lamp: string; colour: string }) {
return (
<td
style={{
backgroundColor: ChangeOpacity(gptConfig.lampColours[score.scoreData.lamp], 0.2),
backgroundColor: ChangeOpacity(colour, 0.2),
}}
>
<strong>{score.scoreData.lamp}</strong>
<strong>{lamp}</strong>
</td>
);
}
@@ -1,20 +1,26 @@
import { ChangeOpacity } from "util/color-opacity";
import { FormatMillions } from "util/misc";
import React from "react";
import { GetGamePTConfig, PBScoreDocument, ScoreDocument } from "tachi-common";
export default function MillionsScoreCell({ score }: { score: PBScoreDocument | ScoreDocument }) {
const gptConfig = GetGamePTConfig(score.game, score.playtype);
import { GetGamePTConfig, PBScoreDocument, ScoreDocument, integer } from "tachi-common";
export default function MillionsScoreCell({
score,
colour,
grade,
}: {
score: integer;
grade: string;
colour: string;
}) {
return (
<td
style={{
backgroundColor: ChangeOpacity(gptConfig.gradeColours[score.scoreData.grade], 0.2),
backgroundColor: ChangeOpacity(colour, 0.2),
}}
>
<strong>{score.scoreData.grade}</strong>
<strong>{grade}</strong>
<br />
{FormatMillions(score.scoreData.score)}
{FormatMillions(score)}
</td>
);
}
@@ -1,22 +1,21 @@
import { FormatMillions } from "util/misc";
import React from "react";
import { COLOUR_SET, Game, PBScoreDocument, Playtype } from "tachi-common";
import { ESDCompare } from "tachi-common/lib/esd";
import Muted from "components/util/Muted";
import { ConfScoreMetric } from "tachi-common/types/metrics";
export default function PBCompareCell({
base,
compare,
game,
compareType,
shouldESD,
metricConf,
metric,
}: {
base: PBScoreDocument | null;
compare: PBScoreDocument | null;
game: Game;
playtype: Playtype;
compareType: "score" | "lamp";
shouldESD: boolean;
metricConf: ConfScoreMetric;
metric: string;
}) {
let status: "win" | "draw" | "lose";
@@ -27,10 +26,12 @@ export default function PBCompareCell({
status = "lose";
} else if (!compare) {
status = "win";
} else if (compareType === "score") {
status = cmp(base.scoreData.score, compare.scoreData.score);
} else if (metricConf.type === "ENUM") {
// @ts-expect-error yeah this index will work sorry
status = cmp(base.scoreData.enumIndexes[metric], compare.scoreData.enumIndexes[metric]);
} else {
status = cmp(base.scoreData.lampIndex, compare.scoreData.lampIndex);
// @ts-expect-error yeah this index will work sorry
status = cmp(base.scoreData[metric], compare.scoreData[metric]);
}
let scoreRenderFn = (k: number) => k.toString();
@@ -68,14 +69,6 @@ export default function PBCompareCell({
const cmpScore = (base?.scoreData.score ?? 0) - (compare?.scoreData.score ?? 0);
const cmpLamp = (base?.scoreData.lampIndex ?? 0) - (compare?.scoreData.lampIndex ?? 0);
let esdDiff;
if (shouldESD) {
if (base?.scoreData.esd && compare?.scoreData.esd) {
esdDiff = ESDCompare(compare.scoreData.esd, base.scoreData.esd);
}
}
return (
<td
style={{
@@ -89,7 +82,9 @@ export default function PBCompareCell({
borderRight: "1px black double",
}}
>
{compareType === "score" ? (
{metricConf.type === "ENUM" ? (
<></>
) : (
<>
{shouldPrioritisePercent ? (
<>
@@ -114,22 +109,6 @@ export default function PBCompareCell({
)}
</>
)}
{esdDiff !== undefined && (
<>
<br />
{esdDiff.toFixed(2) === "0.00" ? (
<Muted>Negligable Difference</Muted>
) : (
<Muted>
Relative: {(esdDiff > 0 ? "+" : "") + esdDiff.toFixed(2)}
</Muted>
)}
</>
)}
</>
) : (
<>
<strong>{(cmpLamp > 0 ? "+" : "") + cmpLamp}</strong>
</>
)}
</td>
@@ -1,31 +1,31 @@
import { ToCDNURL } from "util/api";
import { ChangeOpacity } from "util/color-opacity";
import { GPT_CLIENT_IMPLEMENTATIONS } from "lib/game-implementations";
import React from "react";
import { GetGamePTConfig, PBScoreDocument, ScoreDocument } from "tachi-common";
import { PBScoreDocument, ScoreDocument } from "tachi-common";
export default function PopnLampCell({
score,
}: {
score: ScoreDocument<"popn:9B"> | PBScoreDocument<"popn:9B">;
}) {
const gptConfig = GetGamePTConfig(score.game, score.playtype);
return (
<td
style={{
backgroundColor: ChangeOpacity(gptConfig.lampColours[score.scoreData.lamp], 0.2),
backgroundColor: ChangeOpacity(
GPT_CLIENT_IMPLEMENTATIONS["popn:9B"].enumColours.lamp[score.scoreData.lamp],
0.2
),
}}
>
<strong>{score.scoreData.lamp}</strong>
<br />
{score.scoreData.hitMeta.specificClearType && (
<img
style={{
maxWidth: "32px",
}}
src={ToCDNURL(`/misc/popn/${score.scoreData.hitMeta.specificClearType}.png`)}
/>
)}
<img
style={{
maxWidth: "32px",
}}
src={ToCDNURL(`/misc/popn/${score.scoreData.clearMedal}.png`)}
/>
</td>
);
}
@@ -0,0 +1,81 @@
import QuickTooltip from "components/layout/misc/QuickTooltip";
import Icon from "components/util/Icon";
import Muted from "components/util/Muted";
import { GPT_CLIENT_IMPLEMENTATIONS } from "lib/game-implementations";
import React from "react";
import { ChartDocument, Game, GetGPTString } from "tachi-common";
import { GPTRatingSystem } from "lib/types";
import MiniTable from "../components/MiniTable";
export default function RatingSystemPart({ chart, game }: { chart: ChartDocument; game: Game }) {
const ratingSystems: Array<GPTRatingSystem<any>> =
GPT_CLIENT_IMPLEMENTATIONS[GetGPTString(game, chart.playtype)].ratingSystems;
// don't render anything if this game has no other rating systems defined
// for this chart
if (ratingSystems.filter((e) => typeof e.toNumber(chart) === "number").length === 0) {
return null;
}
return (
<>
<div className="d-none d-lg-block">
<QuickTooltip
tooltipContent={
<MiniTable headers={["Ratings"]} colSpan={2}>
{ratingSystems.map((e) => {
const strV = e.toString(chart);
const numV = e.toNumber(chart);
if (
strV === null ||
strV === undefined ||
numV === null ||
numV === undefined
) {
return null;
}
return (
<tr key={e.name}>
<td>{e}</td>
<td>
{strV} <Muted>({numV.toFixed(2)})</Muted>
{/* @ts-expect-error utterly silly types */}
{e.idvDifference(chart) && (
<>
<br />
<QuickTooltip tooltipContent="Individual Difference - The difficulty of this varies massively between people!">
<span>
<Icon type="balance-scale-left" />
</span>
</QuickTooltip>
</>
)}
</td>
</tr>
);
})}
</MiniTable>
}
>
<div>
<Muted>
{ratingSystems.map((r) => r.toString(chart as any)).join(" / ")}
</Muted>
</div>
</QuickTooltip>
</div>
<div className="d-block d-lg-none">
<Muted>
{ratingSystems.map((r) => (
<React.Fragment key={r.name}>
{r.toString(chart as any)}
<br />
</React.Fragment>
))}
</Muted>
</div>
</>
);
}
@@ -1,6 +1,7 @@
import { ChangeOpacity } from "util/color-opacity";
import { GetEnumColour } from "lib/game-implementations";
import React from "react";
import { GetGamePTConfig, PBScoreDocument, ScoreDocument } from "tachi-common";
import { PBScoreDocument, ScoreDocument } from "tachi-common";
// Lamp cell, but if the lamp is FAILED, display it slightly differently.
export default function SDVXLampCell({
@@ -10,12 +11,10 @@ export default function SDVXLampCell({
| ScoreDocument<"sdvx:Single" | "usc:Controller" | "usc:Keyboard">
| PBScoreDocument<"sdvx:Single" | "usc:Controller" | "usc:Keyboard">;
}) {
const gptConfig = GetGamePTConfig(score.game, score.playtype);
return (
<td
style={{
backgroundColor: ChangeOpacity(gptConfig.lampColours[score.scoreData.lamp], 0.2),
backgroundColor: ChangeOpacity(GetEnumColour(score, "lamp"), 0.2),
}}
>
<strong>{score.scoreData.lamp === "FAILED" ? "PLAYED" : score.scoreData.lamp}</strong>
@@ -1,39 +1,33 @@
import { ChangeOpacity } from "util/color-opacity";
import React from "react";
import { GetGamePTConfig, PBScoreDocument, ScoreDocument } from "tachi-common";
import { integer } from "tachi-common";
export default function ScoreCell({
score,
showScore = true,
colour,
grade,
percent,
scoreRenderFn,
}: {
score: PBScoreDocument | ScoreDocument;
score: integer;
percent: number;
grade: string;
colour: string;
showScore?: boolean;
scoreRenderFn?: (s: number) => string;
}) {
const gptConfig = GetGamePTConfig(score.game, score.playtype);
return (
<td
style={{
backgroundColor: ChangeOpacity(gptConfig.gradeColours[score.scoreData.grade], 0.2),
backgroundColor: ChangeOpacity(colour, 0.2),
}}
>
<strong>{score.scoreData.grade}</strong>
<strong>{grade}</strong>
<br />
{`${score.scoreData.percent.toFixed(2)}%`}
{showScore && (
<>
<br />
<small className="text-muted">
[
{scoreRenderFn
? scoreRenderFn(score.scoreData.score)
: score.scoreData.score}
]
</small>
</>
)}
{`${percent.toFixed(2)}%`}
<br />
<small className="text-muted">[{scoreRenderFn ? scoreRenderFn(score) : score}]</small>
</td>
);
}
@@ -1,12 +0,0 @@
import { FormatMillions } from "util/misc";
import React from "react";
import { Game, PBScoreDocument, ScoreDocument, Playtype } from "tachi-common";
import ScoreCell from "./ScoreCell";
export default function ScoreCellLarge(props: {
game: Game;
playtype: Playtype;
score: ScoreDocument | PBScoreDocument;
}) {
return <ScoreCell {...props} scoreRenderFn={FormatMillions} />;
}
@@ -1,65 +0,0 @@
import QuickTooltip from "components/layout/misc/QuickTooltip";
import Icon from "components/util/Icon";
import Muted from "components/util/Muted";
import React from "react";
import { ChartDocument, Game, GetGamePTConfig } from "tachi-common";
import MiniTable from "../components/MiniTable";
// I hate the fact object.keys doesnt infer types.
type K = keyof ChartDocument["tierlistInfo"];
export default function TierlistInfoPart({ chart, game }: { chart: ChartDocument; game: Game }) {
const keys = GetGamePTConfig(game, chart.playtype).tierlists.filter(
(e) => chart.tierlistInfo[e]
);
return (
<>
<div className="d-none d-lg-block">
<QuickTooltip
tooltipContent={
<MiniTable className="table-sm">
<thead>
<tr>
<th>Tierlist</th>
<th>Value</th>
</tr>
</thead>
<tbody>
{keys.map((k) => (
<tr key={k}>
<td>{k}</td>
<td>
{chart.tierlistInfo[k]!.text} (
{chart.tierlistInfo[k]!.value.toFixed(2)})
{chart.tierlistInfo[k]!.individualDifference && (
<>
<br />
<Icon type="balance-scale-left" />
</>
)}
</td>
</tr>
))}
</tbody>
</MiniTable>
}
>
<div>
<Muted>{keys.map((k) => chart.tierlistInfo[k]!.text).join(" / ")}</Muted>
</div>
</QuickTooltip>
</div>
<div className="d-block d-lg-none">
<Muted>
{keys.map((k) => (
<React.Fragment key={k}>
{chart.tierlistInfo[k]!.text}
<br />
</React.Fragment>
))}
</Muted>
</div>
</>
);
}
@@ -4,7 +4,7 @@ import QuickTooltip from "components/layout/misc/QuickTooltip";
import Icon from "components/util/Icon";
import React from "react";
import { ChartDocument, COLOUR_SET, GetGamePTConfig } from "tachi-common";
import TierlistInfoPart from "./TierlistInfoPart";
import RatingSystemPart from "./RatingSystemPart";
export default function USCDifficultyCell({
chart,
@@ -29,7 +29,7 @@ export default function USCDifficultyCell({
}}
>
<span>{levelText}</span>
<TierlistInfoPart chart={chart} game="usc" />
<RatingSystemPart chart={chart} game="usc" />
{!chart.isPrimary && (
<QuickTooltip tooltipContent="This chart is an alternate, old chart.">
<div>
+18 -7
View File
@@ -3,9 +3,15 @@ import useLUGPTSettings from "components/util/useLUGPTSettings";
import { UserContext } from "context/UserContext";
import React, { useContext } from "react";
import { Volforce } from "rg-stats";
import { ChartDocument, GetGamePTConfig, PBScoreDocument, ScoreDocument } from "tachi-common";
import {
ChartDocument,
GetGPTString,
GetSpecificGPTConfig,
PBScoreDocument,
ScoreDocument,
} from "tachi-common";
type VF6IDStrings = "sdvx:Single" | "usc:Keyboard" | "usc:Controller";
type VF6GPTString = "sdvx:Single" | "usc:Keyboard" | "usc:Controller";
const SHORT_LAMPS = {
CLEAR: "CLR",
@@ -17,11 +23,11 @@ export default function VF6Cell({
score,
chart,
}: {
score: ScoreDocument<VF6IDStrings> | PBScoreDocument<VF6IDStrings>;
chart: ChartDocument<VF6IDStrings>;
score: ScoreDocument<VF6GPTString> | PBScoreDocument<VF6GPTString>;
chart: ChartDocument<VF6GPTString>;
}) {
const { user } = useContext(UserContext);
const { settings } = useLUGPTSettings<VF6IDStrings>();
const { settings } = useLUGPTSettings<VF6GPTString>();
if (IsNullish(score.calculatedData.VF6)) {
return <td>N/A</td>;
@@ -29,13 +35,18 @@ export default function VF6Cell({
const vf6Target = settings?.preferences.gameSpecific.vf6Target;
const gptConfig = GetGamePTConfig(score.game, score.playtype);
const gptConfig = GetSpecificGPTConfig<VF6GPTString>(
GetGPTString(score.game, score.playtype) as VF6GPTString
);
const targets: Record<string, number | null> = {};
if (vf6Target && score.userID === user?.id) {
for (const lamp of ["CLEAR", "EXCESSIVE CLEAR", "ULTIMATE CHAIN"] as const) {
if (score.scoreData.lampIndex <= gptConfig.lamps.indexOf(lamp)) {
if (
score.scoreData.enumIndexes.lamp <=
gptConfig.providedMetrics.lamp.values.indexOf(lamp)
) {
const expectedScore = InverseVF6(vf6Target, lamp, chart.levelNum);
if (expectedScore === null) {
@@ -3,20 +3,20 @@ import { SetState } from "types/react";
import { ZTableTHProps } from "./TachiTable";
export default function SelectableCompareType({
compareType,
setCompareType,
metric,
setMetric,
changeSort,
currentSortMode,
reverseSort,
}: {
compareType: "score" | "lamp";
setCompareType: SetState<"score" | "lamp">;
metric: string;
setMetric: SetState<string>;
} & ZTableTHProps) {
return (
<th>
<select
onChange={(v) => setCompareType(v.target.value as "score" | "lamp")}
value={compareType}
onChange={(v) => setMetric(v.target.value)}
value={metric}
style={{
backgroundColor: "#131313",
border: "none",
@@ -1,24 +1,13 @@
import { UppercaseFirst } from "util/misc";
import React from "react";
import {
Game,
GetGamePTConfig,
IDStrings,
Playtypes,
ScoreCalculatedDataLookup,
} from "tachi-common";
import { Game, GetGamePTConfig, GPTString, Playtypes, ScoreRatingAlgorithms } from "tachi-common";
import { SetState } from "types/react";
import { ZTableTHProps } from "./TachiTable";
// type AllRatings<I extends IDStrings> =
// | ScoreCalculatedDataLookup[I]
// | SessionCalculatedDataLookup[I]
// | UGSRatingsLookup[I];
// hack to get everything to work
type AllRatings<I> = any;
export default function SelectableRating<I extends IDStrings>({
export default function SelectableRating<GPT extends GPTString>({
game,
playtype,
rating,
@@ -30,8 +19,8 @@ export default function SelectableRating<I extends IDStrings>({
}: {
game: Game;
playtype: Playtypes[Game];
rating: AllRatings<I>;
setRating: SetState<AllRatings<I>>;
rating: AllRatings<GPT>;
setRating: SetState<AllRatings<GPT>>;
mode?: "score" | "session" | "profile";
} & ZTableTHProps) {
const gptConfig = GetGamePTConfig(game, playtype);
@@ -48,7 +37,7 @@ export default function SelectableRating<I extends IDStrings>({
return (
<th>
<select
onChange={(v) => setRating(v.target.value as AllRatings<I>)}
onChange={(v) => setRating(v.target.value as AllRatings<GPT>)}
value={rating}
style={{
backgroundColor: "#131313",
@@ -59,7 +48,7 @@ export default function SelectableRating<I extends IDStrings>({
textAlign: "center",
}}
>
{gptConfig[key].map((s) => (
{Object.keys(gptConfig[key]).map((s) => (
<option key={s} value={s}>
{UppercaseFirst(s)}
</option>
@@ -9,7 +9,7 @@ import { UserContext } from "context/UserContext";
import React, { useContext, useMemo, useReducer, useState } from "react";
import {
ChartDocument,
IDStrings,
GPTString,
integer,
PBScoreDocument,
ScoreDocument,
@@ -27,14 +27,14 @@ import RivalCompare from "./components/RivalCompare";
import TargetInfo from "./components/TargetInfo";
import { GPTDropdownSettings } from "./GPTDropdownSettings";
export interface ScoreDropdownProps<I extends IDStrings = IDStrings> {
score: ScoreDocument<I> | PBScoreDocument<I>;
export interface ScoreDropdownProps {
score: ScoreDocument | PBScoreDocument;
scoreState: ScoreState;
pbData: UGPTChartPBComposition<I>;
chart: ChartDocument<I>;
pbData: UGPTChartPBComposition;
chart: ChartDocument;
}
export default function PBDropdown<I extends IDStrings = IDStrings>({
export default function PBDropdown({
game,
playtype,
chart,
@@ -47,7 +47,7 @@ export default function PBDropdown<I extends IDStrings = IDStrings>({
chart: ChartDocument;
song: SongDocument;
scoreState: ScoreState;
defaultView?: "pb" | "scorePB" | "lampPB" | "history" | "debug" | "rivals" | "targets";
defaultView?: "pb" | `otherPB::${string}` | "history" | "debug" | "rivals" | "targets";
} & GamePT) {
const { user: currentUser } = useContext(UserContext);
@@ -56,11 +56,11 @@ export default function PBDropdown<I extends IDStrings = IDStrings>({
const [view, setView] = useState(defaultView);
const { data, error } = useApiQuery<UGPTChartPBComposition<I>>(
const { data, error } = useApiQuery<UGPTChartPBComposition>(
`/users/${userID}/games/${game}/${playtype}/pbs/${chart.chartID}?getComposition=true`
);
const { error: histError, data: histData } = useApiQuery<ScoreDocument<I>[]>(
const { error: histError, data: histData } = useApiQuery<ScoreDocument[]>(
`/users/${userID}/games/${game}/${playtype}/scores/${chart.chartID}`
);
@@ -76,32 +76,28 @@ export default function PBDropdown<I extends IDStrings = IDStrings>({
currentUser === null
);
const currentScoreDoc: ScoreDocument<I> | PBScoreDocument<I> | null = useMemo(() => {
const currentScoreDoc: ScoreDocument | PBScoreDocument | null = useMemo(() => {
if (!data) {
// dont worry about this null, it never gets below the rquery checks
return null;
}
if (view === "pb") {
if (data.pb.composedFrom.lampPB === data.pb.composedFrom.scorePB) {
if (data.pb.composedFrom.length === 1) {
// scores have more information than PBs.
// In this case, the PB is only composed of one score,
// so we should default to this instead.
return data.scores.filter((e) => e.scoreID === data.pb.composedFrom.lampPB)[0];
return data.scores.find((e) => e.scoreID === data.pb.composedFrom[0]!.scoreID)!;
}
return data.pb;
} else if (view.startsWith("otherPB::")) {
const scoreID = view.split("otherPB::")[1];
return data.scores.filter((e) => e.scoreID === scoreID)[0];
}
const idMap = {
scorePB: data.pb.composedFrom.scorePB,
lampPB: data.pb.composedFrom.lampPB,
...Object.fromEntries(
(data.pb.composedFrom.other ?? []).map((e) => [e.name, e.scoreID])
),
};
// @ts-expect-error awful
return data.scores.filter((e) => e.scoreID === idMap[view])[0];
return null;
}, [view, data]);
if (error) {
@@ -116,8 +112,7 @@ export default function PBDropdown<I extends IDStrings = IDStrings>({
);
}
// @TODO this doesn't support alternate PBs.
const isComposedFromSingleScore = data.pb.composedFrom.lampPB === data.pb.composedFrom.scorePB;
const isComposedFromSingleScore = data.pb.composedFrom.length === 1;
let body;
@@ -174,14 +169,16 @@ export default function PBDropdown<I extends IDStrings = IDStrings>({
</SelectButton>
{!isComposedFromSingleScore && (
<>
<SelectButton setValue={setView} value={view} id="scorePB">
<Icon type="star-half-alt" />
Best Score
</SelectButton>
<SelectButton setValue={setView} value={view} id="lampPB">
<Icon type="lightbulb" />
Best Lamp
</SelectButton>
{data.pb.composedFrom.map((e) => (
<SelectButton
setValue={setView}
value={view}
id={`otherPB::${e.scoreID}`}
>
<Icon type="star-half-alt" />
{e.name}
</SelectButton>
))}
</>
)}
<SelectButton setValue={setView} value={view} id="history">
@@ -9,7 +9,7 @@ import { UserSettingsContext } from "context/UserSettingsContext";
import React, { useContext, useReducer, useState } from "react";
import {
ChartDocument,
IDStrings,
GPTString,
PBScoreDocument,
ScoreDocument,
SongDocument,
@@ -32,12 +32,12 @@ export interface ScoreState {
setHighlight: SetState<boolean>;
}
export interface ScoreDropdownProps<I extends IDStrings = IDStrings> {
score: ScoreDocument<I> | PBScoreDocument<I>;
export interface ScoreDropdownProps {
score: ScoreDocument | PBScoreDocument;
scoreState: ScoreState;
}
export default function ScoreDropdown<I extends IDStrings = IDStrings>({
export default function ScoreDropdown({
game,
playtype,
user,
@@ -65,11 +65,11 @@ export default function ScoreDropdown<I extends IDStrings = IDStrings>({
const { user: currentUser } = useContext(UserContext);
const { settings } = useLUGPTSettings();
const { data, error } = useApiQuery<UGPTChartPBComposition<I>>(
const { data, error } = useApiQuery<UGPTChartPBComposition>(
`/users/${user.id}/games/${game}/${playtype}/pbs/${chart.chartID}?getComposition=true`
);
const { error: histError, data: histData } = useApiQuery<ScoreDocument<I>[]>(
const { error: histError, data: histData } = useApiQuery<ScoreDocument[]>(
`/users/${user.id}/games/${game}/${playtype}/scores/${chart.chartID}`
);
@@ -13,7 +13,7 @@ export function BMSGraphsComponent({
score: ScoreDocument<"bms:7K" | "bms:14K"> | PBScoreDocument<"bms:7K" | "bms:14K">;
chart: ChartDocument<"bms:7K" | "bms:14K">;
}) {
const gaugeHistory = score.scoreData.hitMeta.gaugeHistory;
const gaugeHistory = score.scoreData.optional.gaugeHistory;
const [lamp, setLamp] = useState<LampTypes>(LampToKey(score));
@@ -126,7 +126,7 @@ function LampToKey(
return "EX_HARD";
} else if (lamp === "FULL COMBO") {
// @hack - attempt to guess what gauge they used?
if ((score.scoreData.hitMeta.gaugeHistory?.[0] ?? 0) > 22) {
if ((score.scoreData.optional.gaugeHistory?.[0] ?? 0) > 22) {
return "EX_HARD";
}
return "NORMAL";
@@ -43,9 +43,9 @@ export function IIDXGraphsComponent({
let gaugeStatus: "none" | "single" | "gsm" = "none";
if (score.scoreData.hitMeta.gsm) {
if (score.scoreData.optional.gsm) {
gaugeStatus = "gsm";
} else if (score.scoreData.hitMeta.gaugeHistory) {
} else if (score.scoreData.optional.gaugeHistory) {
gaugeStatus = "single";
}
@@ -113,9 +113,9 @@ export function IIDXGraphsComponent({
</div>
<div className="col-12">
{gaugeStatus === "gsm" && lamp !== "DAN_GAUGE" ? (
<GraphComponent type={lamp} values={score.scoreData.hitMeta.gsm![lamp]} />
<GraphComponent type={lamp} values={score.scoreData.optional.gsm![lamp]} />
) : gaugeStatus === "single" ? (
<GraphComponent type={lamp} values={score.scoreData.hitMeta.gaugeHistory!} />
<GraphComponent type={lamp} values={score.scoreData.optional.gaugeHistory!} />
) : (
<div
className="d-flex align-items-center justify-content-center"
@@ -177,7 +177,7 @@ function LampToKey(
return "EX_HARD";
} else if (lamp === "FULL COMBO") {
// @hack - attempt to guess what gauge they used?
if ((score.scoreData.hitMeta.gaugeHistory?.[0] ?? 0) > 22) {
if ((score.scoreData.optional.gaugeHistory?.[0] ?? 0) > 22) {
return "EX_HARD";
}
return "NORMAL";
@@ -9,18 +9,21 @@ export default function JudgementTable({ score }: { score: ScoreDocument | PBSco
return (
<MiniTable className="text-center table-sm" headers={["Judgements"]} colSpan={2}>
{gptConfig.judgements.map((j) => (
{gptConfig.orderedJudgements.map((j) => (
<tr key={j}>
<td>{j.toUpperCase()}</td>
{/* @ts-expect-error fine access */}
<td>{score.scoreData.judgements[j] ?? "No Data."}</td>
</tr>
))}
<tr>
<td style={{ backgroundColor: ChangeOpacity(COLOUR_SET.red, 0.2) }}>
Slow: {score.scoreData.hitMeta.slow ?? "Unknown"}
{/* @ts-expect-error it might exist */}
Slow: {score.scoreData.optional.slow ?? "Unknown"}
</td>
<td style={{ backgroundColor: ChangeOpacity(COLOUR_SET.paleBlue, 0.2) }}>
Fast: {score.scoreData.hitMeta.fast ?? "Unknown"}
{/* @ts-expect-error it might exist */}
Fast: {score.scoreData.optional.fast ?? "Unknown"}
</td>
</tr>
</MiniTable>
@@ -1,21 +1,21 @@
import React from "react";
import { ScoreDocument, PBScoreDocument, IDStrings, ChartDocument } from "tachi-common";
import { ScoreDocument, PBScoreDocument, GPTString, ChartDocument } from "tachi-common";
import { UGPTChartPBComposition } from "types/api-returns";
import { ScoreState } from "../ScoreDropdown";
export default function PBCompare<I extends IDStrings>({
export default function PBCompare({
data,
scoreState,
DocComponent,
}: {
data: UGPTChartPBComposition<I>;
data: UGPTChartPBComposition;
scoreState: ScoreState;
DocComponent: (props: {
score: ScoreDocument<I> | PBScoreDocument<I>;
score: ScoreDocument | PBScoreDocument;
scoreState: ScoreState;
pbData: UGPTChartPBComposition;
forceScoreData: boolean;
chart: ChartDocument<I>;
chart: ChartDocument;
}) => JSX.Element;
}) {
return (
@@ -4,7 +4,7 @@ import Muted from "components/util/Muted";
import usePreferredRanking from "components/util/usePreferredRanking";
import useScoreRatingAlg from "components/util/useScoreRatingAlg";
import React, { useState } from "react";
import { Game, IDStrings, ScoreCalculatedDataLookup, Playtype } from "tachi-common";
import { Game, GPTString, ScoreRatingAlgorithms, Playtype } from "tachi-common";
import { FolderDataset } from "types/tables";
import DifficultyCell from "../cells/DifficultyCell";
import IndicatorsCell from "../cells/IndicatorsCell";
@@ -21,12 +21,12 @@ import { GetGPTCoreHeaders } from "../headers/GameHeaders";
import { EmptyHeader, FolderIndicatorHeader } from "../headers/IndicatorHeader";
import { CreateRankingHeader } from "../headers/RankingHeader";
export default function FolderTable<I extends IDStrings = IDStrings>({
export default function FolderTable({
dataset,
game,
playtype,
}: {
dataset: FolderDataset<I>;
dataset: FolderDataset;
game: Game;
playtype: Playtype;
}) {
@@ -78,15 +78,15 @@ export default function FolderTable<I extends IDStrings = IDStrings>({
);
}
function Row<I extends IDStrings = IDStrings>({
function Row({
data,
rating,
game,
rankingViewMode,
}: {
data: FolderDataset<I>[0];
data: FolderDataset[0];
game: Game;
rating: ScoreCalculatedDataLookup[I];
rating: ScoreRatingAlgorithms[GPTString];
rankingViewMode: RankingViewMode;
}) {
const score = data.__related.pb;
@@ -1,5 +1,5 @@
import React from "react";
import { IDStrings, PBScoreDocument, ScoreCalculatedDataLookup, ScoreDocument } from "tachi-common";
import { GPTString, PBScoreDocument, ScoreRatingAlgorithms, ScoreDocument } from "tachi-common";
import BMSOrPMSLampCell from "../cells/BMSOrPMSLampCell";
import DeltaCell from "../cells/DeltaCell";
import RatingCell from "../cells/RatingCell";
@@ -10,7 +10,7 @@ export default function BMSCoreCells({
rating,
}: {
sc: PBScoreDocument<"bms:7K" | "bms:14K"> | ScoreDocument<"bms:7K" | "bms:14K">;
rating: ScoreCalculatedDataLookup[IDStrings];
rating: ScoreRatingAlgorithms[GPTString];
}) {
return (
<>
@@ -1,5 +1,6 @@
import React from "react";
import { PBScoreDocument, ScoreDocument } from "tachi-common";
import { GetEnumColour } from "lib/game-implementations";
import CHUNITHMJudgementCell from "../cells/CHUNITHMJudgementCell";
import LampCell from "../cells/LampCell";
import MillionsScoreCell from "../cells/MillionsScoreCell";
@@ -14,9 +15,13 @@ export default function CHUNITHMCoreCells({
}) {
return (
<>
<MillionsScoreCell score={sc} />
<MillionsScoreCell
score={sc.scoreData.score}
grade={sc.scoreData.grade}
colour={GetEnumColour(sc, "grade")}
/>
<CHUNITHMJudgementCell score={sc} />
<LampCell score={sc} />
<LampCell lamp={sc.scoreData.lamp} colour={GetEnumColour(sc, "lamp")} />
<RatingCell score={sc} rating={rating} />
</>
);
@@ -1,5 +1,6 @@
import React from "react";
import { PBScoreDocument, ScoreDocument } from "tachi-common";
import { GetEnumColour } from "lib/game-implementations";
import GitadoraJudgementCell from "../cells/GitadoraJudgementCell";
import LampCell from "../cells/LampCell";
import RatingCell from "../cells/RatingCell";
@@ -18,7 +19,7 @@ export default function GitadoraCoreCells({
<>
<ScoreCell showScore={false} score={sc} />
<GitadoraJudgementCell score={sc} />
<LampCell score={sc} />
<LampCell lamp={sc.scoreData.lamp} colour={GetEnumColour(sc, "lamp")} />
<RatingCell score={sc} rating={rating} />
</>
);
@@ -1,10 +1,5 @@
import React from "react";
import {
ChartDocument,
PBScoreDocument,
ScoreCalculatedDataLookup,
ScoreDocument,
} from "tachi-common";
import { ChartDocument, PBScoreDocument, ScoreRatingAlgorithms, ScoreDocument } from "tachi-common";
import BPICell from "../cells/BPICell";
import DeltaCell from "../cells/DeltaCell";
import IIDXLampCell from "../cells/IIDXLampCell";
@@ -17,7 +12,7 @@ export default function IIDXCoreCells({
chart,
}: {
sc: PBScoreDocument<"iidx:SP" | "iidx:DP"> | ScoreDocument<"iidx:SP" | "iidx:DP">;
rating: ScoreCalculatedDataLookup["iidx:SP" | "iidx:DP"];
rating: ScoreRatingAlgorithms["iidx:SP" | "iidx:DP"];
chart: ChartDocument<"iidx:SP" | "iidx:DP">;
}) {
return (
@@ -1,5 +1,6 @@
import React from "react";
import { PBScoreDocument, ScoreDocument } from "tachi-common";
import { GetEnumColour } from "lib/game-implementations";
import ITGJudgementCell from "../cells/ITGJudgementCell";
import LampCell from "../cells/LampCell";
import RatingCell from "../cells/RatingCell";
@@ -16,7 +17,7 @@ export default function ITGCoreCells({
<>
<ScoreCell showScore={false} score={sc} />
<ITGJudgementCell score={sc} />
<LampCell score={sc} />
<LampCell lamp={sc.scoreData.lamp} colour={GetEnumColour(sc, "lamp")} />
<RatingCell score={sc} rating={rating} />
</>
);
@@ -1,5 +1,6 @@
import React from "react";
import { PBScoreDocument, ScoreDocument } from "tachi-common";
import { GetEnumColour } from "lib/game-implementations";
import JubeatJudgementCell from "../cells/JubeatJudgementCell";
import JubeatScoreCell from "../cells/JubeatScoreCell";
import JubilityCell from "../cells/JubilityCell";
@@ -7,7 +8,6 @@ import LampCell from "../cells/LampCell";
export default function JubeatCoreCells({
sc,
rating,
}: {
sc: ScoreDocument<"jubeat:Single"> | PBScoreDocument<"jubeat:Single">;
rating: keyof ScoreDocument["calculatedData"];
@@ -16,7 +16,7 @@ export default function JubeatCoreCells({
<>
<JubeatScoreCell sc={sc} />
<JubeatJudgementCell score={sc} />
<LampCell score={sc} />
<LampCell lamp={sc.scoreData.lamp} colour={GetEnumColour(sc, "lamp")} />
<JubilityCell score={sc} />
</>
);
@@ -1,5 +1,6 @@
import React from "react";
import { PBScoreDocument, ScoreDocument } from "tachi-common";
import { GetEnumColour } from "lib/game-implementations";
import LampCell from "../cells/LampCell";
import RatingCell from "../cells/RatingCell";
import ScoreCell from "../cells/ScoreCell";
@@ -16,7 +17,7 @@ export default function MaimaiDXCoreCells({
<>
<ScoreCell showScore={false} score={sc} />
<MaimaiDXJudgementCell score={sc} />
<LampCell score={sc} />
<LampCell lamp={sc.scoreData.lamp} colour={GetEnumColour(sc, "lamp")} />
<RatingCell score={sc} rating={rating} />
</>
);
@@ -1,5 +1,6 @@
import React from "react";
import { IDStrings, PBScoreDocument, ScoreCalculatedDataLookup, ScoreDocument } from "tachi-common";
import { GPTString, PBScoreDocument, ScoreRatingAlgorithms, ScoreDocument } from "tachi-common";
import { GetEnumColour } from "lib/game-implementations";
import LampCell from "../cells/LampCell";
import MillionsScoreCell from "../cells/MillionsScoreCell";
import MusecaJudgementCell from "../cells/MusecaJudgementCell";
@@ -10,13 +11,17 @@ export default function MusecaCoreCells({
rating,
}: {
sc: ScoreDocument<"museca:Single"> | PBScoreDocument<"museca:Single">;
rating: ScoreCalculatedDataLookup[IDStrings];
rating: ScoreRatingAlgorithms[GPTString];
}) {
return (
<>
<MillionsScoreCell score={sc} />
<MillionsScoreCell
score={sc.scoreData.score}
grade={sc.scoreData.grade}
colour={GetEnumColour(sc, "grade")}
/>
<MusecaJudgementCell score={sc} />
<LampCell score={sc} />
<LampCell lamp={sc.scoreData.lamp} colour={GetEnumColour(sc, "lamp")} />
<RatingCell score={sc} rating={rating} />
</>
);
@@ -1,5 +1,5 @@
import React from "react";
import { IDStrings, PBScoreDocument, ScoreCalculatedDataLookup, ScoreDocument } from "tachi-common";
import { GPTString, PBScoreDocument, ScoreRatingAlgorithms, ScoreDocument } from "tachi-common";
import BMSOrPMSLampCell from "../cells/BMSOrPMSLampCell";
import DeltaCell from "../cells/DeltaCell";
import RatingCell from "../cells/RatingCell";
@@ -12,7 +12,7 @@ export default function PMSCoreCells({
sc:
| PBScoreDocument<"pms:Controller" | "pms:Keyboard">
| ScoreDocument<"pms:Controller" | "pms:Keyboard">;
rating: ScoreCalculatedDataLookup[IDStrings];
rating: ScoreRatingAlgorithms[GPTString];
}) {
return (
<>
@@ -1,5 +1,6 @@
import React from "react";
import { PBScoreDocument, ScoreDocument } from "tachi-common";
import { GetEnumColour } from "lib/game-implementations";
import MillionsScoreCell from "../cells/MillionsScoreCell";
import PopnJudgementCell from "../cells/PopnJudgementCell";
import PopnLampCell from "../cells/PopnLampCell";
@@ -14,7 +15,11 @@ export default function PopnCoreCells({
}) {
return (
<>
<MillionsScoreCell score={sc} />
<MillionsScoreCell
score={sc.scoreData.score}
grade={sc.scoreData.grade}
colour={GetEnumColour(sc, "grade")}
/>
<PopnJudgementCell score={sc} />
<PopnLampCell score={sc} />
<RatingCell score={sc} rating={rating} />
@@ -1,5 +1,6 @@
import React from "react";
import { ChartDocument, PBScoreDocument, ScoreDocument } from "tachi-common";
import { GetEnumColour } from "lib/game-implementations";
import MillionsScoreCell from "../cells/MillionsScoreCell";
import SDVXJudgementCell from "../cells/SDVXJudgementCell";
import SDVXLampCell from "../cells/SDVXLampCell";
@@ -14,7 +15,11 @@ export default function SDVXScoreCoreCells({
}) {
return (
<>
<MillionsScoreCell score={sc} />
<MillionsScoreCell
score={sc.scoreData.score}
grade={sc.scoreData.grade}
colour={GetEnumColour(sc, "grade")}
/>
<SDVXJudgementCell score={sc} />
<SDVXLampCell score={sc} />
<VF6Cell score={sc} chart={chart} />
@@ -2,11 +2,13 @@ import React from "react";
import {
ChartDocument,
Game,
IDStrings,
GPTString,
PBScoreDocument,
ScoreCalculatedDataLookup,
ScoreRatingAlgorithms,
ScoreDocument,
AnyScoreRatingAlg,
} from "tachi-common";
import useScoreRatingAlg from "components/util/useScoreRatingAlg";
import BMSCoreCells from "./BMSCoreCells";
import CHUNITHMCoreCells from "./CHUNITHMCoreCells";
import IIDXCoreCells from "./IIDXCoreCells";
@@ -28,22 +30,28 @@ export default function ScoreCoreCells({
}: {
score: ScoreDocument | PBScoreDocument;
chart: ChartDocument;
rating: ScoreCalculatedDataLookup[IDStrings];
rating?: AnyScoreRatingAlg;
game: Game;
}): React.ReactChild {
}): JSX.Element {
const [defaultRating] = useScoreRatingAlg(game, chart.playtype);
// fallback to this users preferred rating if none provided.
// @ts-expect-error whateverr
const rt: AnyScoreRatingAlg = rating ?? defaultRating;
const sc = score as any; // lazy hack
switch (game) {
case "iidx":
return (
<IIDXCoreCells
rating={rating as ScoreCalculatedDataLookup["iidx:SP" | "iidx:DP"]}
rating={rt as ScoreRatingAlgorithms["iidx:SP" | "iidx:DP"]}
chart={chart as ChartDocument<"iidx:SP" | "iidx:DP">}
sc={sc}
/>
);
case "bms":
return <BMSCoreCells sc={sc} rating={rating} />;
return <BMSCoreCells sc={sc} rating={rt} />;
case "sdvx":
case "usc":
return (
@@ -55,22 +63,22 @@ export default function ScoreCoreCells({
/>
);
case "museca":
return <MusecaCoreCells sc={sc} rating={rating} />;
return <MusecaCoreCells sc={sc} rating={rt} />;
case "wacca":
return <WACCACoreCells sc={sc} rating={rating} />;
return <WACCACoreCells sc={sc} rating={rt} />;
case "popn":
return <PopnCoreCells sc={sc} rating={rating} />;
return <PopnCoreCells sc={sc} rating={rt} />;
case "jubeat":
return <JubeatCoreCells sc={sc} rating={rating} />;
return <JubeatCoreCells sc={sc} rating={rt} />;
case "chunithm":
return <CHUNITHMCoreCells sc={sc} rating={rating} />;
return <CHUNITHMCoreCells sc={sc} rating={rt} />;
case "gitadora":
return <GitadoraCoreCells sc={sc} rating={rating} />;
return <GitadoraCoreCells sc={sc} rating={rt} />;
case "pms":
return <PMSCoreCells sc={sc} rating={rating} />;
return <PMSCoreCells sc={sc} rating={rt} />;
case "itg":
return <ITGCoreCells sc={sc} rating={rating} />;
return <ITGCoreCells sc={sc} rating={rt} />;
case "maimaidx":
return <MaimaiDXCoreCells sc={sc} rating={rating} />;
return <MaimaiDXCoreCells sc={sc} rating={rt} />;
}
}
@@ -1,5 +1,6 @@
import React from "react";
import { PBScoreDocument, ScoreDocument } from "tachi-common";
import { GetEnumColour } from "lib/game-implementations";
import LampCell from "../cells/LampCell";
import MillionsScoreCell from "../cells/MillionsScoreCell";
import RatingCell from "../cells/RatingCell";
@@ -14,9 +15,13 @@ export default function WACCACoreCells({
}) {
return (
<>
<MillionsScoreCell score={sc} />
<MillionsScoreCell
score={sc.scoreData.score}
grade={sc.scoreData.grade}
colour={GetEnumColour(sc, "grade")}
/>
<WaccaJudgementCell score={sc} />
<LampCell score={sc} />
<LampCell lamp={sc.scoreData.lamp} colour={GetEnumColour(sc, "lamp")} />
<RatingCell score={sc} rating={rating} />
</>
);
@@ -1,47 +1,37 @@
import { IsNotNullish } from "util/misc";
import { NumericSOV } from "util/sorts";
import { ChartDocument, Game } from "tachi-common";
import { IsNullish } from "util/misc";
import { GPT_CLIENT_IMPLEMENTATIONS } from "lib/game-implementations";
import { GPTRatingSystem } from "lib/types";
import { ChartDocument, Game, GetGPTString } from "tachi-common";
import { Header } from "../components/TachiTable";
function CascadingTierlistValue(
chart: ChartDocument,
...tiers: (keyof ChartDocument["tierlistInfo"])[]
) {
for (const tier of tiers) {
if (IsNotNullish(chart.tierlistInfo[tier])) {
return chart.tierlistInfo[tier]!.value;
export function CascadingRatingValue(game: Game, chartA: ChartDocument, chartB: ChartDocument) {
const gptImpl = GPT_CLIENT_IMPLEMENTATIONS[GetGPTString(game, chartA.playtype)];
if (chartA.levelNum !== chartB.levelNum) {
return chartA.levelNum - chartB.levelNum;
}
for (const rating of gptImpl.ratingSystems as GPTRatingSystem<any>[]) {
const a = rating.toNumber(chartA);
const b = rating.toNumber(chartB);
if (a !== b) {
if (IsNullish(a)) {
return -Infinity;
} else if (IsNullish(b)) {
return Infinity;
}
return a! - b!;
}
}
return chart.levelNum;
// these things are equal on all counts
return 0;
}
export default function ChartHeader<D>(
game: Game,
chartGetter: (k: D) => ChartDocument
): Header<D> {
let sortFn: (d: D) => number = (k) => chartGetter(k).levelNum;
if (game === "iidx") {
sortFn = (k) => CascadingTierlistValue(chartGetter(k), "kt-HC", "kt-NC");
} else if (game === "bms" || game === "pms") {
sortFn = (k) => CascadingTierlistValue(chartGetter(k), "sgl-EC", "sgl-HC");
} else if (game === "itg") {
return [
"Chart",
"Chart",
(a, b) => {
const ac = chartGetter(a) as ChartDocument<"itg:Stamina">;
const bc = chartGetter(b) as ChartDocument<"itg:Stamina">;
if (ac.levelNum === bc.levelNum) {
return ac.data.displayBPM - bc.data.displayBPM;
}
return ac.levelNum - bc.levelNum;
},
];
}
return ["Chart", "Chart", NumericSOV(sortFn)];
return ["Chart", "Chart", (a, b) => CascadingRatingValue(game, chartGetter(a), chartGetter(b))];
}
@@ -4,11 +4,13 @@ import React from "react";
import {
Game,
GetGamePTConfig,
IDStrings,
GPTString,
PBScoreDocument,
ScoreCalculatedDataLookup,
ScoreRatingAlgorithms,
ScoreDocument,
Playtype,
AnyScoreRatingAlg,
GetGPTString,
} from "tachi-common";
import { SetState } from "types/react";
import {
@@ -18,6 +20,7 @@ import {
RivalChartDataset,
ScoreDataset,
} from "types/tables";
import { GPT_CLIENT_IMPLEMENTATIONS } from "lib/game-implementations";
import SelectableRating from "../components/SelectableRating";
import { Header, ZTableTHProps } from "../components/TachiTable";
@@ -26,28 +29,16 @@ export function GetGPTCoreHeaders<
>(
game: Game,
playtype: Playtype,
rating: ScoreCalculatedDataLookup[IDStrings],
setRating: SetState<ScoreCalculatedDataLookup[IDStrings]>,
rating: ScoreRatingAlgorithms[GPTString],
setRating: SetState<ScoreRatingAlgorithms[GPTString]>,
kMapToScoreOrPB: (k: Dataset[0]) => PBScoreDocument | ScoreDocument | null
): Header<Dataset[0]>[] {
const ScoreHeader: Header<Dataset[0]> = [
"Score",
"Score",
NumericSOV((x) => kMapToScoreOrPB(x)?.scoreData.percent ?? -Infinity),
];
const LampHeader: Header<Dataset[0]> = [
"Lamp",
"Lamp",
NumericSOV((x) => kMapToScoreOrPB(x)?.scoreData.lampIndex ?? -Infinity),
];
const gptConfig = GetGamePTConfig(game, playtype);
let RatingHeader: Header<Dataset[0]>;
const gptConfig = GetGamePTConfig(game, playtype);
if (gptConfig.scoreRatingAlgs.length === 1) {
const alg = gptConfig.scoreRatingAlgs[0];
if (Object.keys(gptConfig.scoreRatingAlgs).length === 1) {
const alg = Object.keys(gptConfig.scoreRatingAlgs)[0] as AnyScoreRatingAlg;
RatingHeader = [
UppercaseFirst(alg),
@@ -72,108 +63,41 @@ export function GetGPTCoreHeaders<
];
}
switch (game) {
case "sdvx":
case "museca":
case "usc":
return [
ScoreHeader,
[
"Near - Miss",
"Nr. Ms.",
NumericSOV((x) => kMapToScoreOrPB(x)?.scoreData.percent ?? -Infinity),
],
LampHeader,
RatingHeader,
];
case "iidx":
case "bms":
case "pms":
return [
ScoreHeader,
[
"Deltas",
"Deltas",
NumericSOV((x) => kMapToScoreOrPB(x)?.scoreData.percent ?? -Infinity),
],
LampHeader,
RatingHeader,
];
case "gitadora":
case "jubeat":
case "wacca":
case "chunithm":
case "maimaidx":
case "itg":
return [
ScoreHeader,
[
"Judgements",
"Hits",
NumericSOV((x) => kMapToScoreOrPB(x)?.scoreData.percent ?? -Infinity),
],
LampHeader,
RatingHeader,
];
case "popn":
return [
ScoreHeader,
[
"Judgements",
"Hits",
NumericSOV((x) => kMapToScoreOrPB(x)?.scoreData.percent ?? -Infinity),
],
[
"Lamp",
"Lamp",
(a, b) => {
const aSc = kMapToScoreOrPB(a) as
| ScoreDocument<"popn:9B">
| PBScoreDocument<"popn:9B">;
const bSc = kMapToScoreOrPB(b) as
| ScoreDocument<"popn:9B">
| PBScoreDocument<"popn:9B">;
const implHeaders = GPT_CLIENT_IMPLEMENTATIONS[GetGPTString(game, playtype)].scoreHeaders;
if (!aSc) {
return -Infinity;
}
const outHeaders: Array<Header<Dataset[0]>> = [];
if (aSc && !bSc) {
return Infinity;
}
for (const header of implHeaders) {
if (header[2]) {
// replace the sort function with one that correctly resolves
// pbs
outHeaders.push([
header[0],
header[1],
(a, b) => {
const pbA = kMapToScoreOrPB(a);
const pbB = kMapToScoreOrPB(b);
if (aSc.scoreData.lampIndex === bSc.scoreData.lampIndex) {
if (
!aSc.scoreData.hitMeta.specificClearType ||
!bSc.scoreData.hitMeta.specificClearType
) {
return -Infinity;
}
if (!pbA) {
return -Infinity;
}
if (!pbB) {
return Infinity;
}
return (
popnClearTypeToInt[aSc.scoreData.hitMeta.specificClearType] -
popnClearTypeToInt[bSc.scoreData.hitMeta.specificClearType]
);
}
return aSc.scoreData.lampIndex - bSc.scoreData.lampIndex;
},
],
RatingHeader,
];
// typescript is NOT happy with what i've done here
// LOL, who cares.
// safety is for chumps
return (header[2] as any)!(pbA, pbB);
},
]);
} else {
outHeaders.push(header as Header<Dataset[0]>);
}
}
}
const popnClearTypeToInt = {
failedCircle: 0,
failedDiamond: 1,
failedStar: 2,
easyClear: 3,
clearCircle: 4,
clearDiamond: 5,
clearStar: 6,
fullComboCircle: 7,
fullComboDiamond: 8,
fullComboStar: 9,
perfect: 10,
} as const;
// all games have a rating header after their impl header.
outHeaders.push(RatingHeader);
return outHeaders;
}
@@ -4,9 +4,9 @@ import React, { useState } from "react";
import {
ChartDocument,
Game,
IDStrings,
GPTString,
integer,
ScoreCalculatedDataLookup,
ScoreRatingAlgorithms,
ScoreDocument,
Playtype,
} from "tachi-common";
@@ -67,7 +67,7 @@ function Row({
game,
}: {
sc: ScoreDocument;
rating: ScoreCalculatedDataLookup[IDStrings];
rating: ScoreRatingAlgorithms[GPTString];
game: Game;
chart: ChartDocument;
}) {
+8 -8
View File
@@ -3,7 +3,7 @@ import { CreateDefaultPBSearchParams } from "util/tables/create-search";
import { GetPBLeadingHeaders } from "util/tables/get-pb-leaders";
import useScoreRatingAlg from "components/util/useScoreRatingAlg";
import React, { useState } from "react";
import { Game, IDStrings, ScoreCalculatedDataLookup, Playtype } from "tachi-common";
import { Game, GPTString, ScoreRatingAlgorithms, Playtype, AnyScoreRatingAlg } from "tachi-common";
import { PBDataset } from "types/tables";
import DropdownIndicatorCell from "../cells/DropdownIndicatorCell";
import IndexCell from "../cells/IndexCell";
@@ -20,7 +20,7 @@ import { EmptyHeader } from "../headers/IndicatorHeader";
import { CreateRankingHeader } from "../headers/RankingHeader";
import PBLeadingRows from "./PBLeadingRows";
export default function PBTable<I extends IDStrings = IDStrings>({
export default function PBTable({
dataset,
game,
indexCol = true,
@@ -31,14 +31,14 @@ export default function PBTable<I extends IDStrings = IDStrings>({
alg,
defaultRankingViewMode,
}: {
dataset: PBDataset<I>;
dataset: PBDataset;
indexCol?: boolean;
showPlaycount?: boolean;
showUser?: boolean;
showChart?: boolean;
playtype: Playtype;
game: Game;
alg?: ScoreCalculatedDataLookup[I];
alg?: AnyScoreRatingAlg;
defaultRankingViewMode?: RankingViewMode | null;
}) {
const defaultRating = useScoreRatingAlg(game, playtype);
@@ -48,7 +48,7 @@ export default function PBTable<I extends IDStrings = IDStrings>({
defaultRankingViewMode ?? "global"
);
const headers: Header<PBDataset<I>[0]>[] = [
const headers: Header<PBDataset[0]>[] = [
...GetPBLeadingHeaders(
showUser,
showChart,
@@ -98,7 +98,7 @@ export default function PBTable<I extends IDStrings = IDStrings>({
);
}
function Row<I extends IDStrings = IDStrings>({
function Row({
pb,
indexCol,
showPlaycount,
@@ -108,7 +108,7 @@ function Row<I extends IDStrings = IDStrings>({
rating,
rankingViewMode,
}: {
pb: PBDataset<I>[0];
pb: PBDataset[0];
indexCol: boolean;
showPlaycount: boolean;
showChart: boolean;
@@ -116,7 +116,7 @@ function Row<I extends IDStrings = IDStrings>({
game: Game;
playtype: Playtype;
// ts bug?
rating: any; // ScoreCalculatedDataLookup[I];
rating: any; // ScoreRatingAlgorithms[I];
rankingViewMode: RankingViewMode;
}) {
const scoreState = usePBState(pb);
@@ -1,8 +1,7 @@
import { NumericSOV, StrSOV } from "util/sorts";
import { CreatePBCompareSearchParams } from "util/tables/create-search";
import React, { useState } from "react";
import { Game, IDStrings, Playtype } from "tachi-common";
import { ESDCompare } from "tachi-common/lib/esd";
import React, { useEffect, useState } from "react";
import { Game, GetGamePTConfig, GetScoreMetricConf, Playtype } from "tachi-common";
import { ComparePBsDataset } from "types/tables";
import DifficultyCell from "../cells/DifficultyCell";
import PBCompareCell from "../cells/PBCompareCell";
@@ -12,22 +11,26 @@ import TachiTable, { Header, ZTableTHProps } from "../components/TachiTable";
import ShortScoreCoreCells from "../game-core-cells/ShortScoreCoreCells";
import ChartHeader from "../headers/ChartHeader";
export default function ComparePBsTable<I extends IDStrings = IDStrings>({
export default function ComparePBsTable({
dataset,
game,
playtype,
baseUser,
compareUser,
shouldESD,
}: {
dataset: ComparePBsDataset<I>;
dataset: ComparePBsDataset;
game: Game;
playtype: Playtype;
baseUser: string;
compareUser: string;
shouldESD: boolean;
}) {
const [compareType, setCompareType] = useState<"score" | "lamp">("score");
const gptConfig = GetGamePTConfig(game, playtype);
const [metric, setMetric] = useState<string>(gptConfig.defaultMetric);
useEffect(() => {
setMetric(gptConfig.defaultMetric);
}, [gptConfig]);
const headers: Header<ComparePBsDataset[0]>[] = [
ChartHeader(game, (d) => d.chart),
@@ -45,26 +48,33 @@ export default function ComparePBsTable<I extends IDStrings = IDStrings>({
return -Infinity;
}
if (compareType === "score") {
if (shouldESD && x.base.scoreData.esd && x.compare.scoreData.esd) {
// esd is inverted, whoops
const e = ESDCompare(x.compare.scoreData.esd, x.base.scoreData.esd);
const conf = GetScoreMetricConf(gptConfig, metric);
if (e !== 0) {
return e;
}
}
return x.base.scoreData.percent - x.compare.scoreData.percent;
if (!conf) {
return 0; // wut
}
return x.base.scoreData.lampIndex - x.compare.scoreData.lampIndex;
if (conf.type === "ENUM") {
return (
// @ts-expect-error this will work
x.base.scoreData.enumIndexes[metric] -
// @ts-expect-error this will work
x.compare.scoreData.enumIndexes[metric]
);
}
return (
// @ts-expect-error this will work
x.base.scoreData[metric] -
// @ts-expect-error this will work
x.compare.scoreData[metric]
);
}),
(thProps: ZTableTHProps) => (
<SelectableCompareType
key={compareType}
compareType={compareType}
setCompareType={setCompareType}
key={metric}
metric={metric}
setMetric={setMetric}
{...thProps}
/>
),
@@ -74,32 +84,20 @@ export default function ComparePBsTable<I extends IDStrings = IDStrings>({
return (
<TachiTable
// very funny way of getting the table to re-render when shouldESD is changed
key={`${shouldESD}`}
dataset={dataset}
headers={headers}
entryName="Charts"
defaultReverseSort
defaultSortMode="Vs."
searchFunctions={CreatePBCompareSearchParams(game, playtype)}
rowFunction={(data) => (
<Row data={data} game={game} compareType={compareType} shouldESD={shouldESD} />
)}
rowFunction={(data) => <Row data={data} game={game} metric={metric} />}
/>
);
}
function Row<I extends IDStrings = IDStrings>({
data,
game,
compareType,
shouldESD,
}: {
data: ComparePBsDataset<I>[0];
game: Game;
compareType: "score" | "lamp";
shouldESD: boolean;
}) {
function Row({ data, game, metric }: { data: ComparePBsDataset[0]; game: Game; metric: string }) {
const metricConf = GetScoreMetricConf(GetGamePTConfig(game, data.chart.playtype), metric)!;
return (
<tr>
<DifficultyCell chart={data.chart} game={game} alwaysShort />
@@ -110,12 +108,12 @@ function Row<I extends IDStrings = IDStrings>({
<td colSpan={2}>Not Played</td>
)}
<PBCompareCell
shouldESD={shouldESD}
base={data.base}
compare={data.compare}
game={game}
playtype={data.chart.playtype}
compareType={compareType}
metricConf={metricConf}
metric={metric}
/>
{data.compare ? (
<ShortScoreCoreCells score={data.compare} game={game} chart={data.chart} />
@@ -1,7 +1,13 @@
import { NumericSOV, StrSOV } from "util/sorts";
import useScoreRatingAlg from "components/util/useScoreRatingAlg";
import React, { useState } from "react";
import { ChartDocument, Game, IDStrings, ScoreCalculatedDataLookup } from "tachi-common";
import {
AnyScoreRatingAlg,
ChartDocument,
Game,
GPTString,
ScoreRatingAlgorithms,
} from "tachi-common";
import { RivalChartDataset } from "types/tables";
import IndexCell from "../cells/IndexCell";
import RankingCell, { RankingViewMode } from "../cells/RankingCell";
@@ -15,12 +21,12 @@ import ScoreCoreCells from "../game-core-cells/ScoreCoreCells";
import { GetGPTCoreHeaders } from "../headers/GameHeaders";
import { CreateRankingHeader } from "../headers/RankingHeader";
export default function RivalChartTable<I extends IDStrings = IDStrings>({
export default function RivalChartTable({
dataset,
game,
chart,
}: {
dataset: RivalChartDataset<I>;
dataset: RivalChartDataset;
game: Game;
chart: ChartDocument;
}) {
@@ -70,16 +76,16 @@ export default function RivalChartTable<I extends IDStrings = IDStrings>({
);
}
function Row<I extends IDStrings = IDStrings>({
function Row({
data,
rating,
game,
chart,
rankingViewMode,
}: {
data: RivalChartDataset<I>[0];
data: RivalChartDataset[0];
game: Game;
rating: ScoreCalculatedDataLookup[I];
rating: AnyScoreRatingAlg;
chart: ChartDocument;
rankingViewMode: RankingViewMode;
}) {
@@ -2,9 +2,10 @@ import { NumericSOV, StrSOV } from "util/sorts";
import { CreateDefaultScoreSearchParams } from "util/tables/create-search";
import useScoreRatingAlg from "components/util/useScoreRatingAlg";
import React, { useState } from "react";
import { Game, IDStrings, integer, ScoreCalculatedDataLookup, Playtype } from "tachi-common";
import { AnyScoreRatingAlg, Game, Playtype, integer } from "tachi-common";
import { ScoreDataset } from "types/tables";
import DifficultyCell from "../cells/DifficultyCell";
import DropdownIndicatorCell from "../cells/DropdownIndicatorCell";
import IndicatorsCell from "../cells/IndicatorsCell";
import TimestampCell from "../cells/TimestampCell";
import TitleCell from "../cells/TitleCell";
@@ -17,9 +18,8 @@ import ScoreCoreCells from "../game-core-cells/ScoreCoreCells";
import ChartHeader from "../headers/ChartHeader";
import { GetGPTCoreHeaders } from "../headers/GameHeaders";
import IndicatorHeader, { EmptyHeader } from "../headers/IndicatorHeader";
import DropdownIndicatorCell from "../cells/DropdownIndicatorCell";
export default function ScoreTable<I extends IDStrings = IDStrings>({
export default function ScoreTable({
dataset,
pageLen,
playtype,
@@ -28,18 +28,18 @@ export default function ScoreTable<I extends IDStrings = IDStrings>({
alg,
noTopDisplayStr,
}: {
dataset: ScoreDataset<I>;
dataset: ScoreDataset;
pageLen?: integer;
playtype: Playtype;
userCol?: boolean;
game: Game;
alg?: ScoreCalculatedDataLookup[I];
alg?: AnyScoreRatingAlg;
noTopDisplayStr?: boolean;
}) {
const defaultRating = useScoreRatingAlg(game, playtype);
const [rating, setRating] = useState(alg ?? defaultRating);
const headers: Header<ScoreDataset<I>[0]>[] = [
const headers: Header<ScoreDataset[0]>[] = [
ChartHeader(game, (k) => k.__related.chart),
IndicatorHeader,
["Song", "Song", StrSOV((x) => x.__related.song.title)],
@@ -74,15 +74,15 @@ export default function ScoreTable<I extends IDStrings = IDStrings>({
);
}
function Row<I extends IDStrings = IDStrings>({
function Row({
sc,
rating,
playtype,
userCol,
game,
}: {
sc: ScoreDataset<I>[0];
rating: ScoreCalculatedDataLookup[I];
sc: ScoreDataset[0];
rating: AnyScoreRatingAlg;
game: Game;
playtype: Playtype;
userCol: boolean;
@@ -1,7 +1,7 @@
import { StrSOV } from "util/sorts";
import { SearchFunctions } from "util/ztable/search";
import React from "react";
import { FormatChart } from "tachi-common";
import { FormatChart, GetBMSCourseIndex } from "tachi-common";
import { BMSCourseWithRelated, CellsRenderFN } from "types/seeds";
import { Header } from "../components/TachiTable";
@@ -16,7 +16,7 @@ export const SeedsBMSCourseLookupHeaders: Header<BMSCourseWithRelated>[] = [
const b2 = `${b.playtype} ${b.set}`;
if (a2 === b2) {
return a.value - b.value;
return GetBMSCourseIndex(a) - GetBMSCourseIndex(b);
}
return a2.localeCompare(b2);
@@ -8,10 +8,10 @@ import { Link } from "react-router-dom";
import {
Game,
GetGamePTConfig,
IDStrings,
GPTString,
integer,
UserDocument,
SessionCalculatedDataLookup,
SessionRatingAlgorithms,
SessionDocument,
Playtype,
SessionScoreInfo,
@@ -41,7 +41,7 @@ export default function GenericSessionTable({
const defaultRating = useSessionRatingAlg(game, playtype);
const [alg, setAlg] = useState<SessionCalculatedDataLookup[IDStrings]>(defaultRating);
const [alg, setAlg] = useState<SessionRatingAlgorithms[GPTString]>(defaultRating);
const headers: Header<SessionDataset[0]>[] = [
["Name", "Name", StrSOV((x) => x.name)],
@@ -51,7 +51,7 @@ export default function GenericSessionTable({
["Timestamp", "Timestamp", NumericSOV((x) => x.timeStarted)],
];
if (gptConfig.sessionRatingAlgs.length > 1) {
if (Object.keys(gptConfig.sessionRatingAlgs).length > 1) {
headers[2] = [
"Rating",
"Rating",
@@ -108,7 +108,7 @@ function Row({
{
data: SessionDataset[0];
// reqUser: PublicUserDocument;
ratingAlg: SessionCalculatedDataLookup[IDStrings];
ratingAlg: SessionRatingAlgorithms[GPTString];
reqUser: UserDocument;
indexCol?: boolean;
}) {
@@ -6,11 +6,18 @@ import Divider from "components/util/Divider";
import Select from "components/util/Select";
import React, { useEffect, useState } from "react";
import { Button, Col, Form, InputGroup, Modal, Row } from "react-bootstrap";
import { FolderDocument, FormatChart, GetGamePTConfig, GoalDocument } from "tachi-common";
import {
FolderDocument,
FormatChart,
GetGamePTConfig,
GetScoreMetricConf,
GoalDocument,
} from "tachi-common";
import { SongChartsSearch } from "types/api-returns";
import { GamePT, SetState } from "types/react";
import { RawQuestGoal } from "types/tachi";
import { OptionsOrGroups, GroupBase } from "react-select";
import { ConfEnumScoreMetric } from "tachi-common/types/metrics";
import { RenderGoalCriteriaPicker } from "./SetNewGoalModal";
export default function AddNewGoalForQuestModal({
@@ -30,14 +37,16 @@ export default function AddNewGoalForQuestModal({
} & GamePT) {
const gptConfig = GetGamePTConfig(game, playtype);
const enumConf = GetScoreMetricConf(
gptConfig,
gptConfig.preferredDefaultEnum
) as ConfEnumScoreMetric<string>;
const [criteria, setCriteria] = useState<GoalDocument["criteria"]>(
initialState?.goal.criteria ?? {
mode: "single",
key: gptConfig.scoreBucket === "grade" ? "scoreData.gradeIndex" : "scoreData.lampIndex",
value:
gptConfig.scoreBucket === "grade"
? gptConfig.grades.indexOf(gptConfig.clearGrade)
: gptConfig.lamps.indexOf(gptConfig.clearLamp),
key: gptConfig.preferredDefaultEnum,
value: enumConf.values.indexOf(enumConf.minimumRelevantValue),
}
);
@@ -3,7 +3,7 @@ import Select from "components/util/Select";
import { TachiConfig } from "lib/config";
import React, { useState } from "react";
import { Button, Col, Modal, Row } from "react-bootstrap";
import { FormatGame, Game, GetGameConfig, IDStrings, Playtype } from "tachi-common";
import { FormatGame, Game, GetGameConfig, GPTString, Playtype } from "tachi-common";
import { SetState } from "types/react";
import { RawQuestDocument } from "types/tachi";
@@ -16,7 +16,7 @@ export default function AddNewQuestModal({
setShow: SetState<boolean>;
onCreate: (rawQuest: RawQuestDocument) => void;
}) {
const [gpt, setGPT] = useState<IDStrings | null>(null);
const [gpt, setGPT] = useState<GPTString | null>(null);
return (
<Modal size="xl" show={show} onHide={() => setShow(false)}>
@@ -35,9 +35,9 @@ export default function SetNewGoalModal({
const [criteria, setCriteria] = useState<GoalDocument["criteria"]>({
mode: "single",
key: gptConfig.scoreBucket === "grade" ? "scoreData.gradeIndex" : "scoreData.lampIndex",
key: gptConfig.preferredDefaultEnum === "grade" ? "scoreData.gradeIndex" : "scoreData.lampIndex",
value:
gptConfig.scoreBucket === "grade"
gptConfig.preferredDefaultEnum === "grade"
? gptConfig.grades.indexOf(gptConfig.clearGrade)
: gptConfig.lamps.indexOf(gptConfig.clearLamp),
});
@@ -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, ProfileRatingLookup } from "tachi-common";
import { GPTString, integer, ProfileRatingAlgorithms } from "tachi-common";
import { GamePT } from "types/react";
export default function RankingData({
@@ -11,7 +11,7 @@ export default function RankingData({
userID,
playtype,
}: {
rankingData: Record<ProfileRatingLookup[IDStrings], { ranking: number; outOf: integer }>;
rankingData: Record<ProfileRatingAlgorithms[GPTString], { 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 ProfileRatingLookup[IDStrings];
const key = k as ProfileRatingAlgorithms[GPTString];
if (key !== alg) {
extendData.push(
+18 -41
View File
@@ -24,6 +24,7 @@ import {
ShowcaseStatDetails,
SongDocument,
Playtype,
GetScoreMetricConf,
} from "tachi-common";
import { UGPTPreferenceStatsReturn } from "types/api-returns";
import { GamePT, UGPT } from "types/react";
@@ -259,14 +260,14 @@ function StatDelta({
v1,
v2,
mode,
property,
metric: property,
game,
playtype,
}: {
v1: number;
v2?: number;
mode: "folder" | "chart";
property: UGPTPreferenceStatsReturn["stat"]["metric"];
metric: string;
game: Game;
playtype: Playtype;
}) {
@@ -276,11 +277,8 @@ function StatDelta({
v2 = 0;
}
let d: string | number = v2 - v1;
const formattedV2 = FormatValue(game, playtype, mode, property, v2);
if (property === "percent") {
d = `${d.toFixed(2)}%`;
}
const d = FormatValue(game, playtype, mode, property, v2 - v1);
let colour;
if (v2 === v1) {
@@ -310,7 +308,7 @@ export function FormatValue(
game: Game,
playtype: Playtype,
mode: "folder" | "chart",
prop: "grade" | "lamp" | "score" | "percent" | "playcount",
metric: string,
value: number
) {
if (mode === "folder") {
@@ -318,42 +316,21 @@ export function FormatValue(
}
const gptConfig = GetGamePTConfig(game, playtype);
const conf = GetScoreMetricConf(gptConfig, metric);
if (prop === "percent") {
return `${value.toFixed(2)}%`;
} else if (prop === "grade") {
return gptConfig.grades[value];
} else if (prop === "lamp") {
return gptConfig.lamps[value];
if (!conf) {
return "UNKNOWN METRIC";
}
if (conf.type === "ENUM") {
return conf.values[value];
} else if (conf.type === "DECIMAL" || conf.type === "INTEGER") {
return conf.formatter(value);
}
return value;
}
export function FormatPropertyGTE(
game: Game,
playtype: Playtype,
prop: "grade" | "lamp" | "score" | "percent" | "playcount",
gte: number
) {
const gptConfig = GetGamePTConfig(game, playtype);
if (gte === null) {
return "NO DATA";
}
if (prop === "grade") {
return gptConfig.grades[gte];
} else if (prop === "lamp") {
return gptConfig.lamps[gte];
} else if (prop === "score" || prop === "playcount") {
return gte;
}
// else if (prop === "percent") {
return gte.toFixed(2);
// }
}
export function GetStatName(
stat: ShowcaseStatDetails,
game: Game,
@@ -398,14 +375,14 @@ export function StatDisplay({
</Link>
<h4>
{UppercaseFirst(stat.metric)}:{" "}
{FormatPropertyGTE(game, playtype, stat.metric, result.value)}
{FormatValue(game, playtype, stat.mode, stat.metric, result.value)}
</h4>
{user && user.id !== reqUser.id && (
<StatDelta
v1={statData.result.value}
v2={compareData?.result.value}
mode={stat.mode}
property={stat.metric}
metric={stat.metric}
game={game}
playtype={playtype}
/>
@@ -432,7 +409,7 @@ export function StatDisplay({
</Link>
<h5>
{UppercaseFirst(stat.metric)} &gt;={" "}
{FormatPropertyGTE(game, playtype, stat.metric, stat.gte)}
{FormatValue(game, playtype, stat.mode, stat.metric, stat.gte)}
</h5>
<h4>
{result.value}
@@ -447,7 +424,7 @@ export function StatDisplay({
v1={statData.result.value}
v2={compareData?.result.value}
mode={stat.mode}
property={stat.metric}
metric={stat.metric}
game={game}
playtype={playtype}
/>
@@ -1,4 +1,4 @@
import { FormatGPTProfileRating, FormatGPTRating, UppercaseFirst } from "util/misc";
import { FormatGPTProfileRating, UppercaseFirst } from "util/misc";
import ClassBadge from "components/game/ClassBadge";
import QuickTooltip from "components/layout/misc/QuickTooltip";
import MiniTable from "components/tables/components/MiniTable";
@@ -6,22 +6,21 @@ import Divider from "components/util/Divider";
import React from "react";
import {
GetGamePTConfig,
IDStrings,
ScoreCalculatedDataLookup,
ProfileRatingLookup,
GPTString,
ProfileRatingAlgorithms,
UserGameStats,
Classes,
} from "tachi-common";
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 [ProfileRatingLookup[IDStrings], number][];
const ratings = Object.entries(ugs.ratings) as [ProfileRatingAlgorithms[GPTString], number][];
return (
<MiniTable className="table-sm text-center" headers={["Player Stats"]} colSpan={2}>
<>
{(Object.keys(gptConfig.classHumanisedFormat) as GameClassSets[IDStrings][])
{(Object.keys(gptConfig.classes) as Classes[GPTString][])
.filter((k) => ugs.classes[k] !== undefined)
.map((k) => (
<tr key={k}>
@@ -44,15 +43,13 @@ export default function UGPTRatingsTable({ ugs }: { ugs: UserGameStats }) {
<QuickTooltip
tooltipContent={
<div>
{gptConfig.profileRatingAlgDescriptions[k]}
{k in gptConfig.scoreRatingAlgDescriptions &&
gptConfig.idString !== "itg:Stamina" && (
<>
<Divider />({UppercaseFirst(k)}:{" "}
{/* @ts-expect-error I know better. */}
{gptConfig.scoreRatingAlgDescriptions[k]})
</>
)}
{gptConfig.profileRatingAlgs[k].description}
{k in gptConfig.scoreRatingAlgs && (
<>
<Divider />({UppercaseFirst(k)}:{" "}
{gptConfig.scoreRatingAlgs[k].description})
</>
)}
</div>
}
wide
+3 -3
View File
@@ -4,11 +4,11 @@ import useLUGPTSettings from "./useLUGPTSettings";
export function useBucket(game: Game, playtype: Playtype) {
const { settings } = useLUGPTSettings();
if (!settings?.preferences.scoreBucket) {
if (!settings?.preferences.preferredDefaultEnum) {
const gptConfig = GetGamePTConfig(game, playtype);
return gptConfig.scoreBucket;
return gptConfig.preferredDefaultEnum;
}
return settings.preferences.scoreBucket;
return settings.preferences.preferredDefaultEnum;
}
@@ -1,13 +1,13 @@
import { UGPTContext } from "context/UGPTContext";
import { useContext } from "react";
import { IDStrings, UGPTSettings } from "tachi-common";
import { GPTString, UGPTSettingsDocument } from "tachi-common";
export default function useLUGPTSettings<I extends IDStrings>() {
export default function useLUGPTSettings<GPT extends GPTString>() {
const { loggedInData, setLoggedInData } = useContext(UGPTContext);
const settings = (loggedInData?.settings ?? null) as UGPTSettings<I> | null;
const settings = (loggedInData?.settings ?? null) as UGPTSettingsDocument<GPT> | null;
const setSettings = (newSettings: UGPTSettings<I>) => {
const setSettings = (newSettings: UGPTSettingsDocument<GPT>) => {
if (!loggedInData) {
throw new Error(`Tried to set settings while nobody was logged in?`);
}
@@ -1,8 +1,8 @@
import { UGPTSettings } from "tachi-common";
import { UGPTSettingsDocument } from "tachi-common";
import useLUGPTSettings from "./useLUGPTSettings";
export default function usePreferredRanking():
| UGPTSettings["preferences"]["preferredRanking"]
| UGPTSettingsDocument["preferences"]["preferredRanking"]
| null {
const { settings } = useLUGPTSettings();
@@ -1,55 +1,55 @@
import {
Game,
GetGamePTConfig,
IDStrings,
ScoreCalculatedDataLookup,
SessionCalculatedDataLookup,
ProfileRatingLookup,
GPTString,
Playtype,
ProfileRatingAlgorithms,
ScoreRatingAlgorithms,
SessionRatingAlgorithms,
} from "tachi-common";
import useLUGPTSettings from "./useLUGPTSettings";
export default function useScoreRatingAlg<I extends IDStrings = IDStrings>(
export default function useScoreRatingAlg<GPT extends GPTString = GPTString>(
game: Game,
playtype: Playtype
): ScoreCalculatedDataLookup[I] {
): ScoreRatingAlgorithms[GPT] {
const { settings } = useLUGPTSettings();
if (!settings?.preferences.preferredScoreAlg) {
const gptConfig = GetGamePTConfig(game, playtype);
return gptConfig.defaultScoreRatingAlg as ScoreCalculatedDataLookup[I];
return gptConfig.defaultScoreRatingAlg as ScoreRatingAlgorithms[GPT];
}
return settings.preferences.preferredScoreAlg as ScoreCalculatedDataLookup[I];
return settings.preferences.preferredScoreAlg as ScoreRatingAlgorithms[GPT];
}
export function useSessionRatingAlg<I extends IDStrings = IDStrings>(
export function useSessionRatingAlg<GPT extends GPTString = GPTString>(
game: Game,
playtype: Playtype
): SessionCalculatedDataLookup[I] {
): SessionRatingAlgorithms[GPT] {
const { settings } = useLUGPTSettings();
if (!settings?.preferences.preferredSessionAlg) {
const gptConfig = GetGamePTConfig(game, playtype);
return gptConfig.defaultSessionRatingAlg as SessionCalculatedDataLookup[I];
return gptConfig.defaultSessionRatingAlg as SessionRatingAlgorithms[GPT];
}
return settings.preferences.preferredSessionAlg as SessionCalculatedDataLookup[I];
return settings.preferences.preferredSessionAlg as SessionRatingAlgorithms[GPT];
}
export function useProfileRatingAlg<I extends IDStrings = IDStrings>(
export function useProfileRatingAlg<GPT extends GPTString = GPTString>(
game: Game,
playtype: Playtype
): ProfileRatingLookup[I] {
): ProfileRatingAlgorithms[GPT] {
const { settings } = useLUGPTSettings();
if (!settings?.preferences.preferredProfileAlg) {
const gptConfig = GetGamePTConfig(game, playtype);
return gptConfig.defaultProfileRatingAlg as ProfileRatingLookup[I];
return gptConfig.defaultProfileRatingAlg as ProfileRatingAlgorithms[GPT];
}
return settings.preferences.preferredProfileAlg as ProfileRatingLookup[I];
return settings.preferences.preferredProfileAlg as ProfileRatingAlgorithms[GPT];
}