mirror of
https://github.com/zkldi/Tachi.git
synced 2026-10-11 00:38:28 +03:00
checkpoint: insanity ascends (35)
127 errors to go
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}) {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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)} >={" "}
|
||||
{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
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user