mirror of
https://github.com/zkldi/Tachi.git
synced 2026-10-03 12:28:13 +03:00
checkpoint: CLIENT BUILDS
now db migration and we just start piling on the bug fixes
This commit is contained in:
@@ -9,16 +9,15 @@ import Muted from "components/util/Muted";
|
||||
import SelectButton from "components/util/SelectButton";
|
||||
import { UserSettingsContext } from "context/UserSettingsContext";
|
||||
import { useFormik } from "formik";
|
||||
import { TachiConfig } from "lib/config";
|
||||
import React, { useContext, useState } from "react";
|
||||
import { Alert, Button, Form } from "react-bootstrap";
|
||||
import { UserDocument, UserSettings } from "tachi-common";
|
||||
import { UserDocument, UserSettingsDocument } from "tachi-common";
|
||||
|
||||
interface Props {
|
||||
reqUser: UserDocument;
|
||||
}
|
||||
|
||||
export default function UserSettingsPage({ reqUser }: Props) {
|
||||
export default function UserSettingsDocumentPage({ reqUser }: Props) {
|
||||
useSetSubheader(
|
||||
["Users", reqUser.username, "Settings"],
|
||||
[reqUser],
|
||||
@@ -82,7 +81,7 @@ function AccountSettings({ reqUser }: { reqUser: UserDocument }) {
|
||||
confPass: "",
|
||||
},
|
||||
onSubmit: async (values) => {
|
||||
const r = await APIFetchV1<UserSettings>(
|
||||
const r = await APIFetchV1<UserSettingsDocument>(
|
||||
`/users/${reqUser.id}/change-password`,
|
||||
{
|
||||
method: "POST",
|
||||
@@ -174,7 +173,7 @@ function PreferencesForm({ reqUser }: { reqUser: UserDocument }) {
|
||||
deletableScores: settings?.preferences.deletableScores ?? false,
|
||||
},
|
||||
onSubmit: async (values) => {
|
||||
const res = await APIFetchV1<UserSettings>(
|
||||
const res = await APIFetchV1<UserSettingsDocument>(
|
||||
`/users/${reqUser.id}/settings`,
|
||||
{
|
||||
method: "PATCH",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { APIFetchV1, UnsuccessfulAPIFetchResponse } from "util/api";
|
||||
import { ChangeOpacity } from "util/color-opacity";
|
||||
import { UppercaseFirst } from "util/misc";
|
||||
import { FormatGPTProfileRating, IsNotNullish, UppercaseFirst } from "util/misc";
|
||||
import ClassBadge from "components/game/ClassBadge";
|
||||
import useSetSubheader from "components/layout/header/useSetSubheader";
|
||||
import Card from "components/layout/page/Card";
|
||||
@@ -21,8 +21,8 @@ import {
|
||||
UserDocument,
|
||||
ProfileRatingAlgorithms,
|
||||
UserGameStats,
|
||||
Classes,
|
||||
} from "tachi-common";
|
||||
import { GameClassSets } from "tachi-common/game-classes";
|
||||
import { GPTLeaderboard, UGPTLeaderboardAdjacent } from "types/api-returns";
|
||||
import { GamePT, SetState, UGPT } from "types/react";
|
||||
|
||||
@@ -129,24 +129,25 @@ function LeaderboardsPageContent({
|
||||
</GentleLink>
|
||||
</td>
|
||||
<td>
|
||||
{s.ratings[alg]
|
||||
? gptConfig.profileRatingAlgFormatters[alg]
|
||||
? gptConfig.profileRatingAlgFormatters[alg]!(s.ratings[alg]!)
|
||||
: s.ratings[alg]!.toFixed(2)
|
||||
{IsNotNullish(s.ratings[alg])
|
||||
? FormatGPTProfileRating(game, playtype, alg, s.ratings[alg]!)
|
||||
: "No Data."}
|
||||
</td>
|
||||
{/* temp */}
|
||||
<td>
|
||||
{Object.entries(s.classes).length
|
||||
? Object.entries(s.classes).map(([k, v]) => (
|
||||
<ClassBadge
|
||||
key={`${k}:${v}`}
|
||||
classValue={v}
|
||||
classSet={k as GameClassSets[GPTString]}
|
||||
game={game}
|
||||
playtype={playtype}
|
||||
/>
|
||||
))
|
||||
? Object.entries(s.classes).map(
|
||||
([k, v]) =>
|
||||
v && (
|
||||
<ClassBadge
|
||||
key={`${k}:${v}`}
|
||||
classValue={v}
|
||||
classSet={k as Classes[GPTString]}
|
||||
game={game}
|
||||
playtype={playtype}
|
||||
/>
|
||||
)
|
||||
)
|
||||
: "No Classes"}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -318,15 +318,6 @@ function PreferencesForm({ reqUser, game, playtype }: UGPT) {
|
||||
<Col xs={12} lg={3} className="my-auto">
|
||||
Expected Profile VF6{" "}
|
||||
{((formik.values.gameSpecific.vf6Target ?? 0) * 50).toFixed(2)}
|
||||
<br />
|
||||
<ClassBadge
|
||||
game="sdvx"
|
||||
playtype="Single"
|
||||
classSet="vfClass"
|
||||
classValue={ToVolforceClass(
|
||||
(formik.values.gameSpecific.vf6Target ?? 0) * 50
|
||||
)}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
@@ -502,67 +493,6 @@ function RenderCurrentStats({
|
||||
);
|
||||
}
|
||||
|
||||
// Straight up stolen from tachi-server.
|
||||
enum SDVXVFClasses {
|
||||
SIENNA_I,
|
||||
SIENNA_II,
|
||||
SIENNA_III,
|
||||
SIENNA_IV,
|
||||
COBALT_I,
|
||||
COBALT_II,
|
||||
COBALT_III,
|
||||
COBALT_IV,
|
||||
DANDELION_I,
|
||||
DANDELION_II,
|
||||
DANDELION_III,
|
||||
DANDELION_IV,
|
||||
CYAN_I,
|
||||
CYAN_II,
|
||||
CYAN_III,
|
||||
CYAN_IV,
|
||||
SCARLET_I,
|
||||
SCARLET_II,
|
||||
SCARLET_III,
|
||||
SCARLET_IV,
|
||||
CORAL_I,
|
||||
CORAL_II,
|
||||
CORAL_III,
|
||||
CORAL_IV,
|
||||
ARGENTO_I,
|
||||
ARGENTO_II,
|
||||
ARGENTO_III,
|
||||
ARGENTO_IV,
|
||||
ELDORA_I,
|
||||
ELDORA_II,
|
||||
ELDORA_III,
|
||||
ELDORA_IV,
|
||||
CRIMSON_I,
|
||||
CRIMSON_II,
|
||||
CRIMSON_III,
|
||||
CRIMSON_IV,
|
||||
IMPERIAL_I,
|
||||
IMPERIAL_II,
|
||||
IMPERIAL_III,
|
||||
IMPERIAL_IV,
|
||||
}
|
||||
|
||||
function ToVolforceClass(vf: number) {
|
||||
if (vf >= 24) {
|
||||
return SDVXVFClasses.IMPERIAL_IV;
|
||||
} else if (vf >= 20) {
|
||||
// imperial i -> iv has gaps of 1
|
||||
return SDVXVFClasses.IMPERIAL_I + Math.floor(vf - 20);
|
||||
} else if (vf >= 14) {
|
||||
// cyan i -> crimson iv has gaps of 0.25
|
||||
return SDVXVFClasses.CYAN_I + Math.floor(4 * (vf - 14));
|
||||
} else if (vf >= 10) {
|
||||
// cobalt i -> dandelion iv have gaps of 0.5
|
||||
return SDVXVFClasses.COBALT_I + Math.floor(2 * (vf - 10));
|
||||
}
|
||||
|
||||
return Math.floor(vf / 2.5);
|
||||
}
|
||||
|
||||
function ManageAccount({ reqUser, game, playtype }: UGPT) {
|
||||
const [password, setPassword] = useState("");
|
||||
const [deleting, setDeleting] = useState(false);
|
||||
|
||||
+111
-108
@@ -178,11 +178,11 @@ function FolderCompare({
|
||||
<Col xs={12}>
|
||||
<Divider />
|
||||
</Col>
|
||||
<CompareCard
|
||||
{/* <CompareCard
|
||||
dataset={dataset}
|
||||
baseUsername={reqUser.username}
|
||||
otherUsername={withUser.username}
|
||||
/>
|
||||
/> */}
|
||||
<Col xs={12}>
|
||||
<Divider />
|
||||
<Form.Check
|
||||
@@ -238,120 +238,123 @@ function UserCard({ username, game, playtype }: { username: string } & GamePT) {
|
||||
);
|
||||
}
|
||||
|
||||
function CompareCard({
|
||||
dataset,
|
||||
baseUsername,
|
||||
otherUsername,
|
||||
}: {
|
||||
dataset: ComparePBsDataset;
|
||||
// temp disabled. needs to be migrated into conf somehow
|
||||
// as a declaration of "these are the metrics that should appear in the rival compare card".
|
||||
|
||||
baseUsername: string;
|
||||
otherUsername: string;
|
||||
}) {
|
||||
// this code sucks
|
||||
let scoreWin = 0;
|
||||
let scoreDraw = 0;
|
||||
let scoreLoss = 0;
|
||||
let lampWin = 0;
|
||||
let lampDraw = 0;
|
||||
let lampLoss = 0;
|
||||
// function CompareCard({
|
||||
// dataset,
|
||||
// baseUsername,
|
||||
// otherUsername,
|
||||
// }: {
|
||||
// dataset: ComparePBsDataset;
|
||||
|
||||
for (const d of dataset) {
|
||||
if (!d.compare && !d.base) {
|
||||
scoreDraw++;
|
||||
lampDraw++;
|
||||
continue;
|
||||
}
|
||||
// baseUsername: string;
|
||||
// otherUsername: string;
|
||||
// }) {
|
||||
// // this code sucks
|
||||
// let scoreWin = 0;
|
||||
// let scoreDraw = 0;
|
||||
// let scoreLoss = 0;
|
||||
// let lampWin = 0;
|
||||
// let lampDraw = 0;
|
||||
// let lampLoss = 0;
|
||||
|
||||
if (!d.compare) {
|
||||
scoreWin++;
|
||||
lampWin++;
|
||||
continue;
|
||||
}
|
||||
// for (const d of dataset) {
|
||||
// if (!d.compare && !d.base) {
|
||||
// scoreDraw++;
|
||||
// lampDraw++;
|
||||
// continue;
|
||||
// }
|
||||
|
||||
if (!d.base) {
|
||||
scoreLoss++;
|
||||
lampLoss++;
|
||||
continue;
|
||||
}
|
||||
// if (!d.compare) {
|
||||
// scoreWin++;
|
||||
// lampWin++;
|
||||
// continue;
|
||||
// }
|
||||
|
||||
if (d.base.scoreData.percent > d.compare.scoreData.percent) {
|
||||
scoreWin++;
|
||||
} else if (d.base.scoreData.percent < d.compare.scoreData.percent) {
|
||||
scoreLoss++;
|
||||
} else {
|
||||
scoreDraw++;
|
||||
}
|
||||
// if (!d.base) {
|
||||
// scoreLoss++;
|
||||
// lampLoss++;
|
||||
// continue;
|
||||
// }
|
||||
|
||||
if (d.base.scoreData.lampIndex > d.compare.scoreData.lampIndex) {
|
||||
lampWin++;
|
||||
} else if (d.base.scoreData.lampIndex < d.compare.scoreData.lampIndex) {
|
||||
lampLoss++;
|
||||
} else {
|
||||
lampDraw++;
|
||||
}
|
||||
}
|
||||
// if (d.base.scoreData.percent > d.compare.scoreData.percent) {
|
||||
// scoreWin++;
|
||||
// } else if (d.base.scoreData.percent < d.compare.scoreData.percent) {
|
||||
// scoreLoss++;
|
||||
// } else {
|
||||
// scoreDraw++;
|
||||
// }
|
||||
|
||||
return (
|
||||
<Col xs={12}>
|
||||
<Card>
|
||||
<Row>
|
||||
<Col xs={12} lg={6}>
|
||||
<MiniTable headers={["Score Win/Loss"]} colSpan={23}>
|
||||
<tr>
|
||||
<td>{baseUsername} Win</td>
|
||||
// if (d.base.scoreData.lampIndex > d.compare.scoreData.lampIndex) {
|
||||
// lampWin++;
|
||||
// } else if (d.base.scoreData.lampIndex < d.compare.scoreData.lampIndex) {
|
||||
// lampLoss++;
|
||||
// } else {
|
||||
// lampDraw++;
|
||||
// }
|
||||
// }
|
||||
|
||||
<td style={{ color: COLOUR_SET.green }}>{scoreWin}</td>
|
||||
// return (
|
||||
// <Col xs={12}>
|
||||
// <Card>
|
||||
// <Row>
|
||||
// <Col xs={12} lg={6}>
|
||||
// <MiniTable headers={["Score Win/Loss"]} colSpan={23}>
|
||||
// <tr>
|
||||
// <td>{baseUsername} Win</td>
|
||||
|
||||
<td style={{ color: COLOUR_SET.green }}>
|
||||
{((100 * scoreWin) / dataset.length).toFixed(2)}%
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Draw</td>
|
||||
<td style={{ color: COLOUR_SET.gold }}>{scoreDraw}</td>
|
||||
<td style={{ color: COLOUR_SET.gold }}>
|
||||
{((100 * scoreDraw) / dataset.length).toFixed(2)}%
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{otherUsername} Win</td>
|
||||
<td style={{ color: COLOUR_SET.red }}>{scoreLoss}</td>
|
||||
<td style={{ color: COLOUR_SET.red }}>
|
||||
{((100 * scoreLoss) / dataset.length).toFixed(2)}%
|
||||
</td>
|
||||
</tr>
|
||||
</MiniTable>
|
||||
</Col>
|
||||
<Col xs={12} lg={6}>
|
||||
<MiniTable headers={["Lamp Win/Loss"]} colSpan={23}>
|
||||
<tr>
|
||||
<td>{baseUsername} Win</td>
|
||||
// <td style={{ color: COLOUR_SET.green }}>{scoreWin}</td>
|
||||
|
||||
<td style={{ color: COLOUR_SET.green }}>{lampWin}</td>
|
||||
// <td style={{ color: COLOUR_SET.green }}>
|
||||
// {((100 * scoreWin) / dataset.length).toFixed(2)}%
|
||||
// </td>
|
||||
// </tr>
|
||||
// <tr>
|
||||
// <td>Draw</td>
|
||||
// <td style={{ color: COLOUR_SET.gold }}>{scoreDraw}</td>
|
||||
// <td style={{ color: COLOUR_SET.gold }}>
|
||||
// {((100 * scoreDraw) / dataset.length).toFixed(2)}%
|
||||
// </td>
|
||||
// </tr>
|
||||
// <tr>
|
||||
// <td>{otherUsername} Win</td>
|
||||
// <td style={{ color: COLOUR_SET.red }}>{scoreLoss}</td>
|
||||
// <td style={{ color: COLOUR_SET.red }}>
|
||||
// {((100 * scoreLoss) / dataset.length).toFixed(2)}%
|
||||
// </td>
|
||||
// </tr>
|
||||
// </MiniTable>
|
||||
// </Col>
|
||||
// <Col xs={12} lg={6}>
|
||||
// <MiniTable headers={["Lamp Win/Loss"]} colSpan={23}>
|
||||
// <tr>
|
||||
// <td>{baseUsername} Win</td>
|
||||
|
||||
<td style={{ color: COLOUR_SET.green }}>
|
||||
{((100 * lampWin) / dataset.length).toFixed(2)}%
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Draw</td>
|
||||
<td style={{ color: COLOUR_SET.gold }}>{lampDraw}</td>
|
||||
<td style={{ color: COLOUR_SET.gold }}>
|
||||
{((100 * lampDraw) / dataset.length).toFixed(2)}%
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{otherUsername} Win</td>
|
||||
<td style={{ color: COLOUR_SET.red }}>{lampLoss}</td>
|
||||
<td style={{ color: COLOUR_SET.red }}>
|
||||
{((100 * lampLoss) / dataset.length).toFixed(2)}%
|
||||
</td>
|
||||
</tr>
|
||||
</MiniTable>
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
</Col>
|
||||
);
|
||||
}
|
||||
// <td style={{ color: COLOUR_SET.green }}>{lampWin}</td>
|
||||
|
||||
// <td style={{ color: COLOUR_SET.green }}>
|
||||
// {((100 * lampWin) / dataset.length).toFixed(2)}%
|
||||
// </td>
|
||||
// </tr>
|
||||
// <tr>
|
||||
// <td>Draw</td>
|
||||
// <td style={{ color: COLOUR_SET.gold }}>{lampDraw}</td>
|
||||
// <td style={{ color: COLOUR_SET.gold }}>
|
||||
// {((100 * lampDraw) / dataset.length).toFixed(2)}%
|
||||
// </td>
|
||||
// </tr>
|
||||
// <tr>
|
||||
// <td>{otherUsername} Win</td>
|
||||
// <td style={{ color: COLOUR_SET.red }}>{lampLoss}</td>
|
||||
// <td style={{ color: COLOUR_SET.red }}>
|
||||
// {((100 * lampLoss) / dataset.length).toFixed(2)}%
|
||||
// </td>
|
||||
// </tr>
|
||||
// </MiniTable>
|
||||
// </Col>
|
||||
// </Row>
|
||||
// </Card>
|
||||
// </Col>
|
||||
// );
|
||||
// }
|
||||
|
||||
+15
-20
@@ -7,15 +7,17 @@ import Divider from "components/util/Divider";
|
||||
import Icon from "components/util/Icon";
|
||||
import LinkButton from "components/util/LinkButton";
|
||||
import Loading from "components/util/Loading";
|
||||
import useApiQuery from "components/util/query/useApiQuery";
|
||||
import SelectButton from "components/util/SelectButton";
|
||||
import useApiQuery from "components/util/query/useApiQuery";
|
||||
import { useBucket } from "components/util/useBucket";
|
||||
import useUGPTBase from "components/util/useUGPTBase";
|
||||
import { UserContext } from "context/UserContext";
|
||||
import { GPT_CLIENT_IMPLEMENTATIONS } from "lib/game-implementations";
|
||||
import React, { useContext, useMemo, useState } from "react";
|
||||
import { FolderDocument, GetGamePTConfig, UserDocument } from "tachi-common";
|
||||
import { FolderDocument, GetGPTString, GetGamePTConfig, GetScoreMetricConf } from "tachi-common";
|
||||
import { ConfEnumScoreMetric } from "tachi-common/types/metrics";
|
||||
import { FolderStatsInfo, UGPTFolderSearch } from "types/api-returns";
|
||||
import { GamePT, UGPT } from "types/react";
|
||||
import { UGPT } from "types/react";
|
||||
|
||||
export default function FoldersSearch({ reqUser, game, playtype }: UGPT) {
|
||||
const [search, setSearch] = useState("");
|
||||
@@ -82,34 +84,27 @@ export function FolderInfoComponent({
|
||||
folder,
|
||||
}: UGPT & { folder: FolderDocument; folderStats: FolderStatsInfo }) {
|
||||
const gptConfig = GetGamePTConfig(game, playtype);
|
||||
const gptImpl = GPT_CLIENT_IMPLEMENTATIONS[GetGPTString(game, playtype)];
|
||||
|
||||
const preferredDefaultEnum = useBucket(game, playtype);
|
||||
|
||||
const [elements, setElements] = useState<"grade" | "lamp">(preferredDefaultEnum);
|
||||
const [metric, setMetric] = useState<string>(preferredDefaultEnum);
|
||||
|
||||
const base = useUGPTBase({ reqUser, game, playtype });
|
||||
|
||||
const dataset = useMemo(() => {
|
||||
if (elements === "grade") {
|
||||
return (
|
||||
<DistributionTable
|
||||
colours={gptConfig.gradeColours}
|
||||
keys={Reverse(gptConfig.grades)}
|
||||
values={folderStats.grades}
|
||||
max={folderStats.chartCount}
|
||||
/>
|
||||
);
|
||||
}
|
||||
const conf = GetScoreMetricConf(gptConfig, metric) as ConfEnumScoreMetric<string>;
|
||||
|
||||
return (
|
||||
<DistributionTable
|
||||
colours={gptConfig.lampColours}
|
||||
keys={Reverse(gptConfig.lamps)}
|
||||
values={folderStats.lamps}
|
||||
// @ts-expect-error hack yeah sorry
|
||||
colours={gptImpl.enumColours[metric]}
|
||||
keys={Reverse(conf.values)}
|
||||
values={folderStats.stats[metric]}
|
||||
max={folderStats.chartCount}
|
||||
/>
|
||||
);
|
||||
}, [elements]);
|
||||
}, [metric]);
|
||||
|
||||
const { user } = useContext(UserContext);
|
||||
|
||||
@@ -139,11 +134,11 @@ export function FolderInfoComponent({
|
||||
<div className="row text-center">
|
||||
<div className="col-12">
|
||||
<div className="btn-group">
|
||||
<SelectButton value={elements} setValue={setElements} id="grade">
|
||||
<SelectButton value={metric} setValue={setMetric} id="grade">
|
||||
<Icon type="sort-alpha-up" />
|
||||
Grades
|
||||
</SelectButton>
|
||||
<SelectButton value={elements} setValue={setElements} id="lamp">
|
||||
<SelectButton value={metric} setValue={setMetric} id="lamp">
|
||||
<Icon type="lightbulb" />
|
||||
Lamps
|
||||
</SelectButton>
|
||||
|
||||
+24
-20
@@ -24,12 +24,16 @@ import {
|
||||
FolderDocument,
|
||||
Game,
|
||||
GamePTConfig,
|
||||
GetGPTString,
|
||||
GetGamePTConfig,
|
||||
GetScoreMetricConf,
|
||||
Playtype,
|
||||
TableDocument,
|
||||
UserDocument,
|
||||
} from "tachi-common";
|
||||
import { FolderStatsInfo, UGPTTableReturns } from "types/api-returns";
|
||||
import { GPT_CLIENT_IMPLEMENTATIONS } from "lib/game-implementations";
|
||||
import { ConfEnumScoreMetric } from "tachi-common/types/metrics";
|
||||
|
||||
interface Props {
|
||||
reqUser: UserDocument;
|
||||
@@ -248,17 +252,14 @@ function TableBarChart({
|
||||
dataMap: Map<string, UGPTFolderStats>;
|
||||
}) {
|
||||
const gptConfig = GetGamePTConfig(table.game, table.playtype);
|
||||
const gptImpl = GPT_CLIENT_IMPLEMENTATIONS[GetGPTString(table.game, table.playtype)];
|
||||
|
||||
const bucket = useBucket(table.game, table.playtype);
|
||||
|
||||
const [mode, setMode] = useState<"grades" | "lamps">(bucket === "grade" ? "grades" : "lamps");
|
||||
const [enumMetric, setEnumMetric] = useState<string>(bucket);
|
||||
|
||||
const colours = useMemo(() => {
|
||||
if (mode === "grades") {
|
||||
return gptConfig.gradeColours;
|
||||
}
|
||||
|
||||
return gptConfig.lampColours;
|
||||
}, [mode]);
|
||||
// @ts-expect-error hack!
|
||||
const colours = useMemo(() => gptImpl.enumColours[enumMetric], [enumMetric]);
|
||||
|
||||
const dataset = useMemo(() => {
|
||||
const arr = [];
|
||||
@@ -275,11 +276,12 @@ function TableBarChart({
|
||||
};
|
||||
|
||||
// this is a series of stupid hacks and i dont really care.
|
||||
for (const key in data.stats[mode]) {
|
||||
// @ts-expect-error hack!
|
||||
for (const key in data.stats[enumMetric]) {
|
||||
// @ts-expect-error stupid key stuff
|
||||
realData[key] = (100 * data.stats[mode][key]) / data.stats.chartCount;
|
||||
realData[key] = (100 * data.stats[enumMetric][key]) / data.stats.chartCount;
|
||||
// @ts-expect-error stupid key stuff
|
||||
realData[`${key}-count`] = data.stats[mode][key];
|
||||
realData[`${key}-count`] = data.stats[enumMetric][key];
|
||||
}
|
||||
|
||||
arr.push({
|
||||
@@ -292,18 +294,18 @@ function TableBarChart({
|
||||
}
|
||||
|
||||
return arr.reverse();
|
||||
}, [dataMap, mode]);
|
||||
}, [dataMap, enumMetric]);
|
||||
|
||||
return (
|
||||
<Card header="Overview">
|
||||
<div className="row">
|
||||
<div className="col-12 d-flex justify-content-center">
|
||||
<div className="btn-group">
|
||||
<SelectButton value={mode} setValue={setMode} id="grades">
|
||||
<SelectButton value={enumMetric} setValue={setEnumMetric} id="grades">
|
||||
<Icon type="sort-alpha-up" />
|
||||
Grades
|
||||
</SelectButton>
|
||||
<SelectButton value={mode} setValue={setMode} id="lamps">
|
||||
<SelectButton value={enumMetric} setValue={setEnumMetric} id="lamps">
|
||||
<Icon type="lightbulb" />
|
||||
Lamps
|
||||
</SelectButton>
|
||||
@@ -322,11 +324,11 @@ function TableBarChart({
|
||||
{/* This hack is necessary because otherwise
|
||||
nivo gets caught in a render loop
|
||||
and dies */}
|
||||
{mode === "grades" ? (
|
||||
{enumMetric === "grades" ? (
|
||||
<OverviewBarChart
|
||||
key="grade"
|
||||
gptConfig={gptConfig}
|
||||
mode={mode}
|
||||
mode={enumMetric}
|
||||
dataset={dataset}
|
||||
colours={colours}
|
||||
/>
|
||||
@@ -334,7 +336,7 @@ function TableBarChart({
|
||||
<OverviewBarChart
|
||||
key="lamp"
|
||||
gptConfig={gptConfig}
|
||||
mode={mode}
|
||||
mode={enumMetric}
|
||||
dataset={dataset}
|
||||
colours={colours}
|
||||
/>
|
||||
@@ -348,12 +350,12 @@ function TableBarChart({
|
||||
|
||||
function OverviewBarChart({
|
||||
gptConfig,
|
||||
mode,
|
||||
mode: metric,
|
||||
dataset,
|
||||
colours,
|
||||
}: {
|
||||
gptConfig: GamePTConfig;
|
||||
mode: "grades" | "lamps";
|
||||
mode: string;
|
||||
dataset: any;
|
||||
colours: any;
|
||||
}) {
|
||||
@@ -367,6 +369,8 @@ function OverviewBarChart({
|
||||
[dataset]
|
||||
);
|
||||
|
||||
const conf = GetScoreMetricConf(gptConfig, metric) as ConfEnumScoreMetric<string>;
|
||||
|
||||
return (
|
||||
<ResponsiveBar
|
||||
indexBy="folder"
|
||||
@@ -388,7 +392,7 @@ function OverviewBarChart({
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
keys={Reverse(gptConfig[mode])}
|
||||
keys={Reverse(conf.values)}
|
||||
colors={(k) => ChangeOpacity(colours[k.id], 0.5)}
|
||||
borderColor={(k) => colours[k.data.id]}
|
||||
borderWidth={1}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { ToCDNURL } from "util/api";
|
||||
import { IsSupportedGame, IsSupportedPlaytype } from "util/asserts";
|
||||
import { ChangeOpacity } from "util/color-opacity";
|
||||
import { CreateChartLink } from "util/data";
|
||||
import { NumericSOV } from "util/sorts";
|
||||
import { NumericSOV, StrSOV } from "util/sorts";
|
||||
import PlaytypeSelect from "app/pages/dashboard/games/_game/PlaytypeSelect";
|
||||
import GPTChartPage from "app/pages/dashboard/games/_game/_playtype/GPTChartPage";
|
||||
import GPTDevInfo from "app/pages/dashboard/games/_game/_playtype/GPTDevInfo";
|
||||
@@ -30,6 +30,7 @@ import {
|
||||
ChartDocument,
|
||||
FormatDifficulty,
|
||||
Game,
|
||||
GetGPTString,
|
||||
GetGameConfig,
|
||||
GetGamePTConfig,
|
||||
SongDocument,
|
||||
@@ -43,6 +44,8 @@ import QuestlinePage from "components/game/targets/QuestlinePage";
|
||||
import QuestsPage from "components/game/targets/QuestsPage";
|
||||
import ChartRedirector from "app/pages/dashboard/games/_game/_playtype/ChartRedirector";
|
||||
import QuestPage from "components/game/targets/QuestPage";
|
||||
import { GPT_CLIENT_IMPLEMENTATIONS } from "lib/game-implementations";
|
||||
import { DifficultyConfig, FixedDifficulties } from "tachi-common/types/game-config-utils";
|
||||
|
||||
export default function GameRoutes() {
|
||||
const { game } = useParams<{ game: string }>();
|
||||
@@ -265,18 +268,16 @@ function SongInfoHeader({
|
||||
SongsReturn) {
|
||||
const gptConfig = GetGamePTConfig(game, playtype);
|
||||
|
||||
// accidentally O(n^2) but this is a short list so who cares
|
||||
const sortedCharts = charts
|
||||
.slice(0)
|
||||
.sort(
|
||||
NumericSOV((x) =>
|
||||
gptConfig.difficulties.indexOf(
|
||||
game === "itg"
|
||||
? (x as ChartDocument<"itg:Stamina">).data.difficultyTag
|
||||
: x.difficulty
|
||||
)
|
||||
)
|
||||
);
|
||||
const sortedCharts = charts.slice(0).sort(
|
||||
gptConfig.difficulties.type === "DYNAMIC"
|
||||
? StrSOV((x) => x.difficulty)
|
||||
: NumericSOV((x) =>
|
||||
// can TS *really* not infer this?
|
||||
(gptConfig.difficulties as FixedDifficulties<string>).order.indexOf(
|
||||
x.difficulty
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
const [ImageCell, setImageCell] = useState<JSX.Element | null>(null);
|
||||
|
||||
@@ -293,7 +294,7 @@ function SongInfoHeader({
|
||||
/>
|
||||
);
|
||||
} else if (game === "itg") {
|
||||
const banner = (song as SongDocument<"itg">).data.banner;
|
||||
const banner = (song as SongDocument<"itg">).data.originalPack;
|
||||
|
||||
if (banner) {
|
||||
setImageCell(
|
||||
@@ -385,12 +386,9 @@ function DifficultyButton({
|
||||
setActiveChart,
|
||||
activeChart,
|
||||
}: Props & { chart: ChartDocument }) {
|
||||
const gptConfig = GetGamePTConfig(game, playtype);
|
||||
const gptImpl = GPT_CLIENT_IMPLEMENTATIONS[GetGPTString(game, playtype)];
|
||||
|
||||
const diffTag =
|
||||
game === "itg"
|
||||
? (chart as ChartDocument<"itg:Stamina">).data.difficultyTag
|
||||
: chart.difficulty;
|
||||
const diffTag = chart.difficulty;
|
||||
|
||||
return (
|
||||
<LinkButton
|
||||
@@ -399,9 +397,11 @@ function DifficultyButton({
|
||||
key={chart.chartID}
|
||||
to={CreateChartLink(chart, game)}
|
||||
style={{
|
||||
backgroundColor: gptConfig.difficultyColours[diffTag]
|
||||
// @ts-expect-error hack!
|
||||
backgroundColor: gptImpl.difficultyColours[diffTag]
|
||||
? ChangeOpacity(
|
||||
gptConfig.difficultyColours[diffTag]!,
|
||||
// @ts-expect-error hack!
|
||||
gptImpl.difficultyColours[diffTag],
|
||||
activeChart?.chartID === chart.chartID ? 0.4 : 0.2
|
||||
)
|
||||
: undefined,
|
||||
|
||||
@@ -4,19 +4,19 @@ import { COLOUR_SET } from "tachi-common";
|
||||
import ChartTooltip from "./ChartTooltip";
|
||||
|
||||
const yAxes = {
|
||||
EASY: [22, 60, 80, 100],
|
||||
NORMAL: [22, 60, 80, 100],
|
||||
HARD: [30, 50, 100],
|
||||
Easy: [22, 60, 80, 100],
|
||||
Normal: [22, 60, 80, 100],
|
||||
Hard: [30, 50, 100],
|
||||
DAN_GAUGE: [30, 50, 100],
|
||||
EX_HARD: [30, 50, 100],
|
||||
EXHard: [30, 50, 100],
|
||||
};
|
||||
|
||||
const colours = {
|
||||
DAN_GAUGE: COLOUR_SET.gray,
|
||||
EASY: [COLOUR_SET.green, COLOUR_SET.vibrantRed],
|
||||
NORMAL: [COLOUR_SET.blue, COLOUR_SET.vibrantRed],
|
||||
HARD: COLOUR_SET.vibrantRed,
|
||||
EX_HARD: COLOUR_SET.gold,
|
||||
Easy: [COLOUR_SET.green, COLOUR_SET.vibrantRed],
|
||||
Normal: [COLOUR_SET.blue, COLOUR_SET.vibrantRed],
|
||||
Hard: COLOUR_SET.vibrantRed,
|
||||
EXHard: COLOUR_SET.gold,
|
||||
};
|
||||
|
||||
export default function IIDXLampChart({
|
||||
@@ -34,11 +34,11 @@ export default function IIDXLampChart({
|
||||
height?: number | string;
|
||||
data: Serie[];
|
||||
usePercentXAxis?: boolean;
|
||||
type: "DAN_GAUGE" | "EASY" | "NORMAL" | "HARD" | "EX_HARD";
|
||||
type: "DAN_GAUGE" | "Normal" | "Easy" | "Hard" | "EXHard";
|
||||
} & ResponsiveLine["props"]) {
|
||||
let realData = [];
|
||||
|
||||
if (type === "HARD" || type === "EX_HARD" || type === "DAN_GAUGE") {
|
||||
if (type === "Hard" || type === "EXHard" || type === "DAN_GAUGE") {
|
||||
realData = data;
|
||||
} else {
|
||||
const failSet = [];
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { UppercaseFirst } from "util/misc";
|
||||
import Card from "components/layout/page/Card";
|
||||
import Divider from "components/util/Divider";
|
||||
import Icon from "components/util/Icon";
|
||||
import SelectButton from "components/util/SelectButton";
|
||||
import { useBucket } from "components/util/useBucket";
|
||||
import React, { useState } from "react";
|
||||
import { UserDocument } from "tachi-common";
|
||||
import { GetGamePTConfig, GetScoreEnumConfs, UserDocument } from "tachi-common";
|
||||
import { GamePT } from "types/react";
|
||||
import { FolderDataset } from "types/tables";
|
||||
import FolderMinimap from "./FolderMinimap";
|
||||
@@ -22,21 +23,23 @@ export default function FolderInfoHeader({
|
||||
folderTitle: string;
|
||||
} & GamePT) {
|
||||
const preferredDefaultEnum = useBucket(game, playtype);
|
||||
const [view, setView] = useState<"grade" | "lamp">(preferredDefaultEnum);
|
||||
const [enumMetric, setEnumMetric] = useState<string>(preferredDefaultEnum);
|
||||
const [breakdownInfo, setBreakdownInfo] = useState<"graph" | "switchboard">("switchboard");
|
||||
|
||||
const gptConfig = GetGamePTConfig(game, playtype);
|
||||
|
||||
const enumConf = GetScoreEnumConfs(gptConfig);
|
||||
|
||||
return (
|
||||
<Card header={`${reqUser.username}'s ${folderTitle} Breakdown`}>
|
||||
<div className="col-12 d-flex justify-content-center">
|
||||
<div className="btn-group">
|
||||
<SelectButton value={view} setValue={setView} id="grade">
|
||||
<Icon type="chart-area" />
|
||||
Score Info
|
||||
</SelectButton>
|
||||
<SelectButton value={view} setValue={setView} id="lamp">
|
||||
<Icon type="lightbulb" />
|
||||
Lamp Info
|
||||
</SelectButton>
|
||||
{Object.keys(enumConf).map((k) => (
|
||||
<SelectButton value={enumMetric} setValue={setEnumMetric} id={k}>
|
||||
<Icon type="chart-area" />
|
||||
{UppercaseFirst(k)} Info
|
||||
</SelectButton>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-12 mt-4 d-flex justify-content-center">
|
||||
@@ -58,7 +61,7 @@ export default function FolderInfoHeader({
|
||||
game={game}
|
||||
folderDataset={folderDataset}
|
||||
playtype={playtype}
|
||||
view={view}
|
||||
view={enumMetric}
|
||||
/>
|
||||
) : (
|
||||
<FolderMinimap
|
||||
@@ -66,7 +69,7 @@ export default function FolderInfoHeader({
|
||||
game={game}
|
||||
playtype={playtype}
|
||||
folderDataset={folderDataset}
|
||||
enumMetric={view}
|
||||
enumMetric={enumMetric}
|
||||
/>
|
||||
)}
|
||||
</Card>
|
||||
|
||||
@@ -1,150 +1,45 @@
|
||||
import { CountElements, Reverse } from "util/misc";
|
||||
import React, { useMemo } from "react";
|
||||
import { GetGamePTConfig } from "tachi-common";
|
||||
import { GetGPTString, GetGamePTConfig, GetScoreMetricConf } from "tachi-common";
|
||||
import { GamePT } from "types/react";
|
||||
import { FolderDataset } from "types/tables";
|
||||
import { ConfEnumScoreMetric } from "tachi-common/types/metrics";
|
||||
import { GPT_CLIENT_IMPLEMENTATIONS } from "lib/game-implementations";
|
||||
import FolderDistributionTable from "./FolderDistributionTable";
|
||||
|
||||
type Props = {
|
||||
folderDataset: FolderDataset;
|
||||
view: "grade" | "lamp";
|
||||
view: string;
|
||||
} & GamePT;
|
||||
|
||||
export default function FolderScoreDistributionChart({
|
||||
game,
|
||||
playtype,
|
||||
folderDataset,
|
||||
view,
|
||||
view: metric,
|
||||
}: Props) {
|
||||
const gptConfig = GetGamePTConfig(game, playtype);
|
||||
const conf = GetScoreMetricConf(gptConfig, metric) as ConfEnumScoreMetric<string>;
|
||||
|
||||
const values = useMemo(() => {
|
||||
if (view === "grade") {
|
||||
return CountElements(folderDataset, (x) => x.__related.pb?.scoreData.grade ?? null);
|
||||
}
|
||||
const gptImpl = GPT_CLIENT_IMPLEMENTATIONS[GetGPTString(game, playtype)];
|
||||
|
||||
return CountElements(folderDataset, (x) => x.__related.pb?.scoreData.lamp ?? null);
|
||||
}, [folderDataset, view]);
|
||||
const values = useMemo(
|
||||
// @ts-expect-error hack
|
||||
() => CountElements(folderDataset, (x) => x.__related.pb?.scoreData[metric] ?? null),
|
||||
[folderDataset, metric]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="row">
|
||||
<div className="col-12 col-lg-6 offset-lg-3">
|
||||
{view === "grade" ? (
|
||||
<FolderDistributionTable
|
||||
colours={gptConfig.gradeColours}
|
||||
keys={Reverse(gptConfig.grades)}
|
||||
max={folderDataset.length}
|
||||
values={values}
|
||||
/>
|
||||
) : (
|
||||
<FolderDistributionTable
|
||||
colours={gptConfig.lampColours}
|
||||
keys={Reverse(gptConfig.lamps)}
|
||||
max={folderDataset.length}
|
||||
values={values}
|
||||
/>
|
||||
)}
|
||||
<FolderDistributionTable
|
||||
// @ts-expect-error this will always work
|
||||
colours={gptImpl.enumColours[metric]}
|
||||
keys={Reverse(conf.values)}
|
||||
max={folderDataset.length}
|
||||
values={values}
|
||||
/>
|
||||
</div>
|
||||
{/* <div className="col-12 col-lg-8">
|
||||
<ScoreDistributionChart {...{ game, playtype, folderDataset, view }} />
|
||||
</div> */}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// function ScoreDistributionChart({ game, folderDataset, playtype, view }: Props) {
|
||||
// if (view === "grade") {
|
||||
// return <ScoreBarChart game={game} playtype={playtype} folderDataset={folderDataset} />;
|
||||
// }
|
||||
|
||||
// return <>LAMP TODO</>;
|
||||
// }
|
||||
|
||||
// function ScoreBarChart({ game, playtype, folderDataset }: Omit<Props, "view">) {
|
||||
// const dataMap = CreateChartIDMap(folderDataset);
|
||||
// const gptConfig = GetGamePTConfig(game, playtype);
|
||||
|
||||
// const expScale = GetGradeChartExpScale(game);
|
||||
// const expFn = ComposeExpFn(expScale);
|
||||
// const invExpFn = ComposeInverseExpFn(expScale);
|
||||
|
||||
// const dataset = [];
|
||||
|
||||
// for (const data of folderDataset) {
|
||||
// const value = data.__related.pb ? expFn(data.__related.pb.scoreData.percent) : 0;
|
||||
// dataset.push({
|
||||
// chartID: data.chartID,
|
||||
// expValue: expFn(value),
|
||||
// value,
|
||||
// grade: data.__related.pb?.scoreData.grade,
|
||||
// });
|
||||
// }
|
||||
|
||||
// dataset.sort(NumericSOV(x => x.expValue));
|
||||
|
||||
// return (
|
||||
// <div style={{ height: 400 }}>
|
||||
// <ResponsiveBar
|
||||
// indexBy="chartID"
|
||||
// tooltip={d => (
|
||||
// <BarChartTooltip
|
||||
// point={d}
|
||||
// renderFn={d => {
|
||||
// const data = dataMap.get(d.indexValue as string)!;
|
||||
|
||||
// return (
|
||||
// <div className="w-100 text-center">
|
||||
// {data.__related.song.title}
|
||||
// <br />
|
||||
// {data.__related.pb?.scoreData.percent.toFixed(2)}%
|
||||
// </div>
|
||||
// );
|
||||
// }}
|
||||
// />
|
||||
// )}
|
||||
// key={"value"}
|
||||
// colors={k => {
|
||||
// if (!k.data.grade) {
|
||||
// return "black";
|
||||
// }
|
||||
// // @ts-expect-error temp
|
||||
// return ChangeOpacity(gptConfig.gradeColours[k.data.grade], 0.5);
|
||||
// }}
|
||||
// // @ts-expect-error temp
|
||||
// borderColor={k => gptConfig.gradeColours[k.data.grade]}
|
||||
// borderWidth={1}
|
||||
// padding={0.2}
|
||||
// // @ts-expect-error temp
|
||||
// data={dataset}
|
||||
// minValue={0}
|
||||
// maxValue={expFn(100)}
|
||||
// margin={{ left: 50, top: 20, bottom: 20 }}
|
||||
// valueFormat={e => `${invExpFn(e).toFixed(2)}%`}
|
||||
// axisLeft={{
|
||||
// tickValues: gptConfig.gradeBoundaries.map(e => (e === 0 ? 0 : expFn(e))),
|
||||
// format: x => {
|
||||
// let nearest;
|
||||
|
||||
// const lgv = invExpFn(x);
|
||||
|
||||
// for (const [i, gradeBnd] of gptConfig.gradeBoundaries.entries()) {
|
||||
// if (Math.abs(gradeBnd - lgv) < 0.00005) {
|
||||
// nearest = i;
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
|
||||
// if (nearest === undefined) {
|
||||
// return null;
|
||||
// }
|
||||
|
||||
// return gptConfig.grades[nearest];
|
||||
// },
|
||||
// }}
|
||||
// axisBottom={null}
|
||||
// {...DEFAULT_BAR_PROPS}
|
||||
// labelSkipWidth={40}
|
||||
// />
|
||||
// </div>
|
||||
// );
|
||||
// }
|
||||
|
||||
@@ -24,11 +24,16 @@ import {
|
||||
Game,
|
||||
GamePTConfig,
|
||||
GetGamePTConfig,
|
||||
GetGPTString,
|
||||
GetScoreMetricConf,
|
||||
GPTString,
|
||||
integer,
|
||||
SongDocument,
|
||||
TableDocument,
|
||||
} from "tachi-common";
|
||||
import { SessionFolderRaises, SessionReturns } from "types/api-returns";
|
||||
import { ConfEnumScoreMetric } from "tachi-common/types/metrics";
|
||||
import { GPT_CLIENT_IMPLEMENTATIONS } from "lib/game-implementations";
|
||||
|
||||
export default function SessionFolderRaiseBreakdown({
|
||||
sessionData,
|
||||
@@ -182,7 +187,7 @@ export default function SessionFolderRaiseBreakdown({
|
||||
chartMap={chartMap}
|
||||
game={game}
|
||||
folderRaiseInfo={folderRaiseInfo}
|
||||
gptConfig={gptConfig}
|
||||
gptString={GetGPTString(game, playtype)}
|
||||
/>
|
||||
))}
|
||||
</MiniTable>
|
||||
@@ -205,41 +210,39 @@ export default function SessionFolderRaiseBreakdown({
|
||||
);
|
||||
}
|
||||
|
||||
const SortRaisesNicely = (gptConfig: GamePTConfig, preferredScoreBucket: "grade" | "lamp") =>
|
||||
NumericSOV<SessionFolderRaises>((x) => {
|
||||
const baseValue =
|
||||
x.type === "grade"
|
||||
? gptConfig.grades.indexOf(x.value)
|
||||
: gptConfig.lamps.indexOf(x.value);
|
||||
const SortRaisesNicely = (gptConfig: GamePTConfig, preferredEnum: string) => {
|
||||
const conf = GetScoreMetricConf(gptConfig, preferredEnum) as ConfEnumScoreMetric<string>;
|
||||
|
||||
return NumericSOV<SessionFolderRaises>((x) => {
|
||||
const baseValue = conf.values.indexOf(x.value);
|
||||
|
||||
// if this is what the user prefers to see, push it to the top
|
||||
if (x.type === preferredScoreBucket) {
|
||||
if (x.type === preferredEnum) {
|
||||
return baseValue + 1_000_000;
|
||||
}
|
||||
|
||||
return baseValue;
|
||||
}, true);
|
||||
};
|
||||
|
||||
function FolderRaiseRender({
|
||||
folderRaiseInfo,
|
||||
game,
|
||||
songMap,
|
||||
chartMap,
|
||||
gptConfig,
|
||||
gptString,
|
||||
}: {
|
||||
folderRaiseInfo: SessionFolderRaises;
|
||||
chartMap: Map<string, ChartDocument>;
|
||||
songMap: Map<integer, SongDocument>;
|
||||
game: Game;
|
||||
gptConfig: GamePTConfig;
|
||||
gptString: GPTString;
|
||||
}) {
|
||||
let colour;
|
||||
|
||||
if (folderRaiseInfo.type === "grade") {
|
||||
colour = gptConfig.gradeColours[folderRaiseInfo.value];
|
||||
} else {
|
||||
colour = gptConfig.lampColours[folderRaiseInfo.value];
|
||||
}
|
||||
const colour =
|
||||
// @ts-expect-error lazy
|
||||
GPT_CLIENT_IMPLEMENTATIONS[gptString].enumColours[folderRaiseInfo.type][
|
||||
folderRaiseInfo.value
|
||||
];
|
||||
|
||||
const newTotal = folderRaiseInfo.previousCount + folderRaiseInfo.raisedCharts.length;
|
||||
|
||||
|
||||
@@ -154,15 +154,7 @@ function FormatAverage(score: integer, playtype: "SP" | "DP", notecount: integer
|
||||
score={score}
|
||||
/>
|
||||
),
|
||||
delta: (
|
||||
<DeltaCell
|
||||
game="iidx"
|
||||
playtype={playtype}
|
||||
score={score}
|
||||
percent={percent}
|
||||
grade={grade}
|
||||
/>
|
||||
),
|
||||
delta: <DeltaCell value={percent} grade={grade} gradeBoundaries={IIDXLIKE_GBOUNDARIES} />,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,29 +1,24 @@
|
||||
import { GenericFormatGradeDelta } from "util/grade-deltas";
|
||||
import React from "react";
|
||||
import { Game, Grades, GPTString, Playtypes } from "tachi-common";
|
||||
import { GetGradeDeltas, GradeBoundary } from "tachi-common";
|
||||
|
||||
export default function DeltaCell({
|
||||
game,
|
||||
playtype,
|
||||
score,
|
||||
percent,
|
||||
value,
|
||||
grade,
|
||||
gradeBoundaries,
|
||||
}: {
|
||||
game: Game;
|
||||
playtype: Playtypes[Game];
|
||||
score: number;
|
||||
percent: number;
|
||||
grade: Grades[GPTString];
|
||||
value: number;
|
||||
grade: string;
|
||||
gradeBoundaries: Array<GradeBoundary<string>>;
|
||||
}) {
|
||||
if (score === 0) {
|
||||
if (value === 0) {
|
||||
return <td>N/A</td>;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line prefer-const
|
||||
let { lower, upper, closer } = GenericFormatGradeDelta(game, playtype, score, percent, grade);
|
||||
let { lower, upper, closer } = GetGradeDeltas(gradeBoundaries, grade, value);
|
||||
|
||||
// (max-)+20 is a stupid statistic. hard override it.
|
||||
if ((game === "iidx" || game === "bms" || game === "pms") && lower.startsWith("(MAX-)+")) {
|
||||
if (lower.startsWith("(MAX-)+")) {
|
||||
closer = "upper";
|
||||
}
|
||||
|
||||
|
||||
@@ -8,8 +8,10 @@ import {
|
||||
FormatDifficulty,
|
||||
FormatDifficultyShort,
|
||||
Game,
|
||||
GetGPTString,
|
||||
GetGamePTConfig,
|
||||
} from "tachi-common";
|
||||
import { GPT_CLIENT_IMPLEMENTATIONS } from "lib/game-implementations";
|
||||
import BMSOrPMSDifficultyCell from "./BMSOrPMSDifficultyCell";
|
||||
import RatingSystemPart from "./RatingSystemPart";
|
||||
import USCDifficultyCell from "./USCDifficultyCell";
|
||||
@@ -49,10 +51,13 @@ export default function DifficultyCell({
|
||||
return <ITGDifficultyCell chart={chart as ChartDocument<"itg:Stamina">} />;
|
||||
}
|
||||
|
||||
const gptImpl = GPT_CLIENT_IMPLEMENTATIONS[GetGPTString(game, chart.playtype)];
|
||||
|
||||
return (
|
||||
<td
|
||||
style={{
|
||||
backgroundColor: ChangeOpacity(gptConfig.difficultyColours[chart.difficulty]!, 0.2),
|
||||
// @ts-expect-error yawn
|
||||
backgroundColor: ChangeOpacity(gptImpl.difficultyColours[chart.difficulty]!, 0.2),
|
||||
minWidth: "80px",
|
||||
maxWidth: "100px",
|
||||
}}
|
||||
@@ -72,7 +77,7 @@ export default function DifficultyCell({
|
||||
</div>
|
||||
</QuickTooltip>
|
||||
)}
|
||||
{!noTierlist && Object.keys(chart.tierlistInfo).length > 0 && (
|
||||
{!noTierlist && gptImpl.ratingSystems.length > 0 && (
|
||||
<RatingSystemPart chart={chart} game={game} />
|
||||
)}
|
||||
{!chart.isPrimary && (
|
||||
|
||||
@@ -2,18 +2,25 @@ import { ChangeOpacity } from "util/color-opacity";
|
||||
import QuickTooltip from "components/layout/misc/QuickTooltip";
|
||||
import Muted from "components/util/Muted";
|
||||
import React from "react";
|
||||
import { ChartDocument, COLOUR_SET, GetGamePTConfig } from "tachi-common";
|
||||
import { ChartDocument, COLOUR_SET } from "tachi-common";
|
||||
import MiniTable from "../components/MiniTable";
|
||||
import RatingSystemPart from "./RatingSystemPart";
|
||||
|
||||
export default function ITGDifficultyCell({ chart }: { chart: ChartDocument<"itg:Stamina"> }) {
|
||||
const gptConfig = GetGamePTConfig("itg", "Stamina");
|
||||
const COLOUR_LOOKUP = {
|
||||
Beginner: COLOUR_SET.paleBlue,
|
||||
Easy: COLOUR_SET.green,
|
||||
Medium: COLOUR_SET.vibrantYellow,
|
||||
Hard: COLOUR_SET.red,
|
||||
Expert: COLOUR_SET.pink,
|
||||
Edit: COLOUR_SET.gray,
|
||||
};
|
||||
|
||||
export default function ITGDifficultyCell({ chart }: { chart: ChartDocument<"itg:Stamina"> }) {
|
||||
return (
|
||||
<td
|
||||
style={{
|
||||
backgroundColor: ChangeOpacity(
|
||||
gptConfig.difficultyColours[chart.data.difficultyTag] ?? COLOUR_SET.gray,
|
||||
COLOUR_LOOKUP[chart.data.difficultyTag] ?? COLOUR_SET.gray,
|
||||
0.2
|
||||
),
|
||||
minWidth: "80px",
|
||||
@@ -40,8 +47,8 @@ export default function ITGDifficultyCell({ chart }: { chart: ChartDocument<"itg
|
||||
>
|
||||
<div>
|
||||
<span>
|
||||
{chart.data.difficultyTag}: {chart.level} [{chart.data.displayBPM.toFixed()}
|
||||
]
|
||||
{chart.data.difficultyTag}: {chart.level} [
|
||||
{chart.data.primaryBPM?.toFixed() ?? "???"}]
|
||||
</span>
|
||||
<br />
|
||||
<Muted>{chart.data.charter}</Muted>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { FormatMillions } from "util/misc";
|
||||
import React from "react";
|
||||
import { COLOUR_SET, Game, PBScoreDocument, Playtype } from "tachi-common";
|
||||
import { ConfScoreMetric } from "tachi-common/types/metrics";
|
||||
@@ -34,41 +33,6 @@ export default function PBCompareCell({
|
||||
status = cmp(base.scoreData[metric], compare.scoreData[metric]);
|
||||
}
|
||||
|
||||
let scoreRenderFn = (k: number) => k.toString();
|
||||
let shouldShowPercent = false;
|
||||
let shouldPrioritisePercent = false;
|
||||
|
||||
switch (game) {
|
||||
case "chunithm":
|
||||
case "gitadora":
|
||||
case "usc":
|
||||
case "sdvx":
|
||||
case "popn":
|
||||
case "wacca":
|
||||
case "museca":
|
||||
scoreRenderFn = FormatMillions;
|
||||
shouldShowPercent = false;
|
||||
break;
|
||||
case "jubeat":
|
||||
scoreRenderFn = FormatMillions;
|
||||
shouldShowPercent = true;
|
||||
shouldPrioritisePercent = true;
|
||||
break;
|
||||
case "bms":
|
||||
case "iidx":
|
||||
scoreRenderFn = (k: number) => k.toString();
|
||||
shouldShowPercent = true;
|
||||
break;
|
||||
}
|
||||
|
||||
const fmtCmpPercent = fmtNum(
|
||||
(base?.scoreData.percent ?? 0) - (compare?.scoreData.percent ?? 0),
|
||||
2,
|
||||
"%"
|
||||
);
|
||||
const cmpScore = (base?.scoreData.score ?? 0) - (compare?.scoreData.score ?? 0);
|
||||
const cmpLamp = (base?.scoreData.lampIndex ?? 0) - (compare?.scoreData.lampIndex ?? 0);
|
||||
|
||||
return (
|
||||
<td
|
||||
style={{
|
||||
@@ -83,33 +47,18 @@ export default function PBCompareCell({
|
||||
}}
|
||||
>
|
||||
{metricConf.type === "ENUM" ? (
|
||||
<></>
|
||||
// @ts-expect-error this access is fine
|
||||
<>{base.scoreData[metric]}</>
|
||||
) : (
|
||||
<>
|
||||
{shouldPrioritisePercent ? (
|
||||
<>
|
||||
<strong>{fmtCmpPercent}</strong>
|
||||
{shouldShowPercent && (
|
||||
<>
|
||||
<br />
|
||||
<small>
|
||||
({(cmpScore > 0 ? "+" : "") + scoreRenderFn(cmpScore)})
|
||||
</small>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<strong>{(cmpScore > 0 ? "+" : "") + scoreRenderFn(cmpScore)}</strong>
|
||||
{shouldShowPercent && (
|
||||
<>
|
||||
<br />
|
||||
<small>({fmtCmpPercent})</small>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
metricConf.type === "DECIMAL" ||
|
||||
(metricConf.type === "INTEGER" && (
|
||||
<strong>
|
||||
{/* @ts-expect-error this is fine */}
|
||||
{(base.scoreData[metric] > 0 ? "+" : "") +
|
||||
// @ts-expect-error this is fine
|
||||
metricConf.formatter(base.scoreData[metric])}
|
||||
</strong>
|
||||
))
|
||||
)}
|
||||
</td>
|
||||
);
|
||||
|
||||
@@ -41,7 +41,6 @@ export default function RatingSystemPart({ chart, game }: { chart: ChartDocument
|
||||
<td>{e}</td>
|
||||
<td>
|
||||
{strV} <Muted>({numV.toFixed(2)})</Muted>
|
||||
{/* @ts-expect-error utterly silly types */}
|
||||
{e.idvDifference(chart) && (
|
||||
<>
|
||||
<br />
|
||||
|
||||
@@ -9,7 +9,7 @@ export default function ScoreCell({
|
||||
percent,
|
||||
scoreRenderFn,
|
||||
}: {
|
||||
score: integer;
|
||||
score?: integer;
|
||||
percent: number;
|
||||
grade: string;
|
||||
colour: string;
|
||||
@@ -26,8 +26,14 @@ export default function ScoreCell({
|
||||
<br />
|
||||
{`${percent.toFixed(2)}%`}
|
||||
|
||||
<br />
|
||||
<small className="text-muted">[{scoreRenderFn ? scoreRenderFn(score) : score}]</small>
|
||||
{score !== undefined && (
|
||||
<>
|
||||
<br />
|
||||
<small className="text-muted">
|
||||
[{scoreRenderFn ? scoreRenderFn(score) : score}]
|
||||
</small>
|
||||
</>
|
||||
)}
|
||||
</td>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ export default function TitleCell({
|
||||
`/misc/popn/banners/${(chart as any).data.inGameID}.png`
|
||||
)})`;
|
||||
} else if (game === "itg") {
|
||||
const banner = (song as SongDocument<"itg">).data.banner;
|
||||
const banner = (song as SongDocument<"itg">).data.originalPack;
|
||||
|
||||
if (banner) {
|
||||
backgroundImage = `url(${ToCDNURL(
|
||||
|
||||
@@ -2,8 +2,10 @@ import { ChangeOpacity } from "util/color-opacity";
|
||||
import { FormatTables } from "util/misc";
|
||||
import QuickTooltip from "components/layout/misc/QuickTooltip";
|
||||
import Icon from "components/util/Icon";
|
||||
import { GPT_CLIENT_IMPLEMENTATIONS } from "lib/game-implementations";
|
||||
import { GPTClientImplementation } from "lib/types";
|
||||
import React from "react";
|
||||
import { ChartDocument, COLOUR_SET, GetGamePTConfig } from "tachi-common";
|
||||
import { COLOUR_SET, ChartDocument } from "tachi-common";
|
||||
import RatingSystemPart from "./RatingSystemPart";
|
||||
|
||||
export default function USCDifficultyCell({
|
||||
@@ -15,10 +17,12 @@ export default function USCDifficultyCell({
|
||||
? `${chart.difficulty} ${chart.level}`
|
||||
: FormatTables(chart.data.tableFolders);
|
||||
|
||||
const gptConfig = GetGamePTConfig("usc", chart.playtype);
|
||||
const gptImpl = GPT_CLIENT_IMPLEMENTATIONS[
|
||||
`usc:${chart.playtype}`
|
||||
] as GPTClientImplementation<"usc:Controller">;
|
||||
|
||||
const bgColour = ChangeOpacity(
|
||||
chart.data.isOfficial ? gptConfig.difficultyColours[chart.difficulty]! : COLOUR_SET.teal,
|
||||
chart.data.isOfficial ? gptImpl.difficultyColours[chart.difficulty]! : COLOUR_SET.teal,
|
||||
0.2
|
||||
);
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import React, { useEffect, useState } from "react";
|
||||
import { Nav } from "react-bootstrap";
|
||||
import { ChartDocument, PBScoreDocument, ScoreDocument } from "tachi-common";
|
||||
|
||||
type LampTypes = "NORMAL" | "EASY" | "HARD" | "EX_HARD";
|
||||
type LampTypes = "Normal" | "Easy" | "Hard" | "EXHard";
|
||||
|
||||
export function BMSGraphsComponent({
|
||||
score,
|
||||
@@ -34,34 +34,34 @@ export function BMSGraphsComponent({
|
||||
<div className="col-12 d-flex justify-content-center">
|
||||
<Nav variant="pills">
|
||||
<SelectNav
|
||||
id="EASY"
|
||||
id="Easy"
|
||||
value={lamp}
|
||||
setValue={setLamp}
|
||||
disabled={shouldDisable("EASY")}
|
||||
disabled={shouldDisable("Easy")}
|
||||
>
|
||||
Easy
|
||||
</SelectNav>
|
||||
<SelectNav
|
||||
id="NORMAL"
|
||||
id="Normal"
|
||||
value={lamp}
|
||||
setValue={setLamp}
|
||||
disabled={shouldDisable("NORMAL")}
|
||||
disabled={shouldDisable("Normal")}
|
||||
>
|
||||
Groove
|
||||
</SelectNav>
|
||||
<SelectNav
|
||||
id="HARD"
|
||||
id="Hard"
|
||||
value={lamp}
|
||||
setValue={setLamp}
|
||||
disabled={shouldDisable("HARD")}
|
||||
disabled={shouldDisable("Hard")}
|
||||
>
|
||||
Hard
|
||||
</SelectNav>
|
||||
<SelectNav
|
||||
id="EX_HARD"
|
||||
id="EXHard"
|
||||
value={lamp}
|
||||
setValue={setLamp}
|
||||
disabled={shouldDisable("EX_HARD")}
|
||||
disabled={shouldDisable("EXHard")}
|
||||
>
|
||||
Ex Hard
|
||||
</SelectNav>
|
||||
@@ -108,32 +108,34 @@ function LampToKey(
|
||||
if (IsScore(score) && score.scoreMeta.gauge) {
|
||||
switch (score.scoreMeta.gauge) {
|
||||
case "EASY":
|
||||
return "Easy";
|
||||
case "NORMAL":
|
||||
return "Normal";
|
||||
case "HARD":
|
||||
return score.scoreMeta.gauge;
|
||||
return "Hard";
|
||||
case "EX-HARD":
|
||||
return "EX_HARD";
|
||||
return "EXHard";
|
||||
}
|
||||
}
|
||||
|
||||
if (lamp === "CLEAR") {
|
||||
return "NORMAL";
|
||||
return "Normal";
|
||||
} else if (lamp === "EASY CLEAR") {
|
||||
return "EASY";
|
||||
return "Easy";
|
||||
} else if (lamp === "HARD CLEAR") {
|
||||
return "HARD";
|
||||
return "Hard";
|
||||
} else if (lamp === "EX HARD CLEAR") {
|
||||
return "EX_HARD";
|
||||
return "EXHard";
|
||||
} else if (lamp === "FULL COMBO") {
|
||||
// @hack - attempt to guess what gauge they used?
|
||||
if ((score.scoreData.optional.gaugeHistory?.[0] ?? 0) > 22) {
|
||||
return "EX_HARD";
|
||||
return "EXHard";
|
||||
}
|
||||
return "NORMAL";
|
||||
return "Normal";
|
||||
} else if (lamp === "NO PLAY") {
|
||||
// dan gauge looks like this
|
||||
return "HARD";
|
||||
return "Hard";
|
||||
}
|
||||
|
||||
return "NORMAL";
|
||||
return "Normal";
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ import { PBScoreDocument, ScoreDocument } from "tachi-common";
|
||||
// );
|
||||
// }
|
||||
|
||||
type LampTypes = "DAN_GAUGE" | "NORMAL" | "EASY" | "HARD" | "EX_HARD";
|
||||
type LampTypes = "DAN_GAUGE" | "Normal" | "Easy" | "Hard" | "EXHard";
|
||||
|
||||
export function IIDXGraphsComponent({
|
||||
score,
|
||||
@@ -43,7 +43,12 @@ export function IIDXGraphsComponent({
|
||||
|
||||
let gaugeStatus: "none" | "single" | "gsm" = "none";
|
||||
|
||||
if (score.scoreData.optional.gsm) {
|
||||
if (
|
||||
score.scoreData.optional.gsmEXHard &&
|
||||
score.scoreData.optional.gsmHard &&
|
||||
score.scoreData.optional.gsmNormal &&
|
||||
score.scoreData.optional.gsmEasy
|
||||
) {
|
||||
gaugeStatus = "gsm";
|
||||
} else if (score.scoreData.optional.gaugeHistory) {
|
||||
gaugeStatus = "single";
|
||||
@@ -78,34 +83,34 @@ export function IIDXGraphsComponent({
|
||||
</SelectNav>
|
||||
)}
|
||||
<SelectNav
|
||||
id="EASY"
|
||||
id="Easy"
|
||||
value={lamp}
|
||||
setValue={setLamp}
|
||||
disabled={shouldDisable("EASY")}
|
||||
disabled={shouldDisable("Easy")}
|
||||
>
|
||||
Easy
|
||||
</SelectNav>
|
||||
<SelectNav
|
||||
id="NORMAL"
|
||||
id="Normal"
|
||||
value={lamp}
|
||||
setValue={setLamp}
|
||||
disabled={shouldDisable("NORMAL")}
|
||||
disabled={shouldDisable("Normal")}
|
||||
>
|
||||
Normal
|
||||
</SelectNav>
|
||||
<SelectNav
|
||||
id="HARD"
|
||||
id="Hard"
|
||||
value={lamp}
|
||||
setValue={setLamp}
|
||||
disabled={shouldDisable("HARD")}
|
||||
disabled={shouldDisable("Hard")}
|
||||
>
|
||||
Hard
|
||||
</SelectNav>
|
||||
<SelectNav
|
||||
id="EX_HARD"
|
||||
id="EXHard"
|
||||
value={lamp}
|
||||
setValue={setLamp}
|
||||
disabled={shouldDisable("EX_HARD")}
|
||||
disabled={shouldDisable("EXHard")}
|
||||
>
|
||||
Ex Hard
|
||||
</SelectNav>
|
||||
@@ -113,7 +118,7 @@ export function IIDXGraphsComponent({
|
||||
</div>
|
||||
<div className="col-12">
|
||||
{gaugeStatus === "gsm" && lamp !== "DAN_GAUGE" ? (
|
||||
<GraphComponent type={lamp} values={score.scoreData.optional.gsm![lamp]} />
|
||||
<GraphComponent type={lamp} values={score.scoreData.optional[`gsm${lamp}`]!} />
|
||||
) : gaugeStatus === "single" ? (
|
||||
<GraphComponent type={lamp} values={score.scoreData.optional.gaugeHistory!} />
|
||||
) : (
|
||||
@@ -157,31 +162,34 @@ function LampToKey(
|
||||
if (IsScore(score) && score.scoreMeta.gauge) {
|
||||
switch (score.scoreMeta.gauge) {
|
||||
case "EASY":
|
||||
return "Easy";
|
||||
case "NORMAL":
|
||||
return "Normal";
|
||||
case "HARD":
|
||||
return score.scoreMeta.gauge;
|
||||
return "Hard";
|
||||
case "ASSISTED EASY":
|
||||
return "EASY";
|
||||
return "Easy";
|
||||
case "EX-HARD":
|
||||
return "EX_HARD";
|
||||
return "EXHard";
|
||||
}
|
||||
}
|
||||
|
||||
if (lamp === "CLEAR") {
|
||||
return "NORMAL";
|
||||
return "Normal";
|
||||
} else if (lamp === "EASY CLEAR") {
|
||||
return "EASY";
|
||||
return "Easy";
|
||||
} else if (lamp === "HARD CLEAR") {
|
||||
return "HARD";
|
||||
return "Hard";
|
||||
} else if (lamp === "EX HARD CLEAR") {
|
||||
return "EX_HARD";
|
||||
return "EXHard";
|
||||
} else if (lamp === "FULL COMBO") {
|
||||
// @hack - attempt to guess what gauge they used?
|
||||
// this could be hard or easy, we actually legitimately do not know in this scenario
|
||||
if ((score.scoreData.optional.gaugeHistory?.[0] ?? 0) > 22) {
|
||||
return "EX_HARD";
|
||||
return "EXHard";
|
||||
}
|
||||
return "NORMAL";
|
||||
return "Normal";
|
||||
}
|
||||
|
||||
return "NORMAL";
|
||||
return "Normal";
|
||||
}
|
||||
|
||||
@@ -26,14 +26,14 @@ export function ITGGraphsComponent({
|
||||
</Nav>
|
||||
</div>
|
||||
<div className="col-12">
|
||||
{chart.data.breakdown.npsPerMeasure ? (
|
||||
{chart.data.npsPerMeasure ? (
|
||||
<ITGDensityChart
|
||||
height="200px"
|
||||
mobileHeight="175px"
|
||||
data={[
|
||||
{
|
||||
id: "chart",
|
||||
data: chart.data.breakdown.npsPerMeasure.map((e, i) => ({
|
||||
data: chart.data.npsPerMeasure.map((e, i) => ({
|
||||
x: i,
|
||||
y: e,
|
||||
})),
|
||||
|
||||
@@ -7,7 +7,7 @@ import useUGPTBase from "components/util/useUGPTBase";
|
||||
import { UserContext } from "context/UserContext";
|
||||
import React, { useContext } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { ChartDocument, Game } from "tachi-common";
|
||||
import { ChartDocument, Game, GetGamePTConfig } from "tachi-common";
|
||||
import { ChartRivalsReturn } from "types/api-returns";
|
||||
import { RivalChartDataset } from "types/tables";
|
||||
import { Col } from "react-bootstrap";
|
||||
@@ -45,6 +45,8 @@ export default function RivalCompare({ chart, game }: { chart: ChartDocument; ga
|
||||
);
|
||||
}
|
||||
|
||||
const gptConfig = GetGamePTConfig(game, playtype);
|
||||
|
||||
const rivalDataset: RivalChartDataset = [...data.rivals, currentUser]
|
||||
.map((u) => ({
|
||||
...u,
|
||||
@@ -52,7 +54,10 @@ export default function RivalCompare({ chart, game }: { chart: ChartDocument; ga
|
||||
pb: data.pbs.find((p) => p.userID === u.id) ?? null,
|
||||
},
|
||||
}))
|
||||
.sort(NumericSOV((x) => x.__related.pb?.scoreData.percent ?? -Infinity, true))
|
||||
.sort(
|
||||
// @ts-expect-error this access is obviously legal
|
||||
NumericSOV((x) => x.__related.pb?.scoreData[gptConfig.defaultMetric] ?? -Infinity, true)
|
||||
)
|
||||
.map((e, index) => ({
|
||||
...e,
|
||||
__related: {
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
import React from "react";
|
||||
import { GPTString, PBScoreDocument, ScoreRatingAlgorithms, ScoreDocument } from "tachi-common";
|
||||
import {
|
||||
GPTString,
|
||||
PBScoreDocument,
|
||||
ScoreRatingAlgorithms,
|
||||
ScoreDocument,
|
||||
IIDXLIKE_GBOUNDARIES,
|
||||
GetGPTString,
|
||||
} from "tachi-common";
|
||||
import { GPT_CLIENT_IMPLEMENTATIONS } from "lib/game-implementations";
|
||||
import BMSOrPMSLampCell from "../cells/BMSOrPMSLampCell";
|
||||
import DeltaCell from "../cells/DeltaCell";
|
||||
import RatingCell from "../cells/RatingCell";
|
||||
@@ -14,12 +22,19 @@ export default function BMSCoreCells({
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
<ScoreCell score={sc} />
|
||||
<DeltaCell
|
||||
game="bms"
|
||||
playtype={sc.playtype}
|
||||
score={sc.scoreData.score}
|
||||
<ScoreCell
|
||||
colour={
|
||||
// @ts-expect-error lazy
|
||||
GPT_CLIENT_IMPLEMENTATIONS[GetGPTString(sc.game, sc.playtype)].enumColours
|
||||
.grade[sc.scoreData.grade]
|
||||
}
|
||||
grade={sc.scoreData.grade}
|
||||
percent={sc.scoreData.percent}
|
||||
score={sc.scoreData.score}
|
||||
/>
|
||||
<DeltaCell
|
||||
gradeBoundaries={IIDXLIKE_GBOUNDARIES}
|
||||
value={sc.scoreData.percent}
|
||||
grade={sc.scoreData.grade}
|
||||
/>
|
||||
<BMSOrPMSLampCell score={sc} />
|
||||
|
||||
@@ -17,7 +17,11 @@ export default function GitadoraCoreCells({
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
<ScoreCell showScore={false} score={sc} />
|
||||
<ScoreCell
|
||||
colour={GetEnumColour(sc, "grade")}
|
||||
grade={sc.scoreData.grade}
|
||||
percent={sc.scoreData.percent}
|
||||
/>
|
||||
<GitadoraJudgementCell score={sc} />
|
||||
<LampCell lamp={sc.scoreData.lamp} colour={GetEnumColour(sc, "lamp")} />
|
||||
<RatingCell score={sc} rating={rating} />
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
import React from "react";
|
||||
import { ChartDocument, PBScoreDocument, ScoreRatingAlgorithms, ScoreDocument } from "tachi-common";
|
||||
import {
|
||||
ChartDocument,
|
||||
PBScoreDocument,
|
||||
ScoreRatingAlgorithms,
|
||||
ScoreDocument,
|
||||
IIDXLIKE_GBOUNDARIES,
|
||||
GetGPTString,
|
||||
} from "tachi-common";
|
||||
import { GPT_CLIENT_IMPLEMENTATIONS, GetEnumColour } from "lib/game-implementations";
|
||||
import BPICell from "../cells/BPICell";
|
||||
import DeltaCell from "../cells/DeltaCell";
|
||||
import IIDXLampCell from "../cells/IIDXLampCell";
|
||||
@@ -17,12 +25,15 @@ export default function IIDXCoreCells({
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
<ScoreCell score={sc} />
|
||||
<DeltaCell
|
||||
game="iidx"
|
||||
playtype={sc.playtype}
|
||||
score={sc.scoreData.score}
|
||||
<ScoreCell
|
||||
colour={GetEnumColour(sc, "grade")}
|
||||
grade={sc.scoreData.grade}
|
||||
percent={sc.scoreData.percent}
|
||||
score={sc.scoreData.score}
|
||||
/>
|
||||
<DeltaCell
|
||||
gradeBoundaries={IIDXLIKE_GBOUNDARIES}
|
||||
value={sc.scoreData.percent}
|
||||
grade={sc.scoreData.grade}
|
||||
/>
|
||||
<IIDXLampCell sc={sc} chart={chart} />
|
||||
|
||||
@@ -15,7 +15,11 @@ export default function ITGCoreCells({
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
<ScoreCell showScore={false} score={sc} />
|
||||
<ScoreCell
|
||||
colour={GetEnumColour(sc, "grade")}
|
||||
grade={sc.scoreData.grade}
|
||||
percent={sc.scoreData.scorePercent}
|
||||
/>
|
||||
<ITGJudgementCell score={sc} />
|
||||
<LampCell lamp={sc.scoreData.lamp} colour={GetEnumColour(sc, "lamp")} />
|
||||
<RatingCell score={sc} rating={rating} />
|
||||
|
||||
@@ -15,7 +15,11 @@ export default function MaimaiDXCoreCells({
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
<ScoreCell showScore={false} score={sc} />
|
||||
<ScoreCell
|
||||
colour={GetEnumColour(sc, "grade")}
|
||||
grade={sc.scoreData.grade}
|
||||
percent={sc.scoreData.percent}
|
||||
/>
|
||||
<MaimaiDXJudgementCell score={sc} />
|
||||
<LampCell lamp={sc.scoreData.lamp} colour={GetEnumColour(sc, "lamp")} />
|
||||
<RatingCell score={sc} rating={rating} />
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
import React from "react";
|
||||
import { GPTString, PBScoreDocument, ScoreRatingAlgorithms, ScoreDocument } from "tachi-common";
|
||||
import {
|
||||
GPTString,
|
||||
PBScoreDocument,
|
||||
ScoreRatingAlgorithms,
|
||||
ScoreDocument,
|
||||
IIDXLIKE_GBOUNDARIES,
|
||||
GetGPTString,
|
||||
} from "tachi-common";
|
||||
import { GPT_CLIENT_IMPLEMENTATIONS, GetEnumColour } from "lib/game-implementations";
|
||||
import BMSOrPMSLampCell from "../cells/BMSOrPMSLampCell";
|
||||
import DeltaCell from "../cells/DeltaCell";
|
||||
import RatingCell from "../cells/RatingCell";
|
||||
@@ -16,12 +24,15 @@ export default function PMSCoreCells({
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
<ScoreCell score={sc} />
|
||||
<DeltaCell
|
||||
game="pms"
|
||||
playtype={sc.playtype}
|
||||
score={sc.scoreData.score}
|
||||
<ScoreCell
|
||||
colour={GetEnumColour(sc, "grade")}
|
||||
grade={sc.scoreData.grade}
|
||||
percent={sc.scoreData.percent}
|
||||
score={sc.scoreData.score}
|
||||
/>
|
||||
<DeltaCell
|
||||
gradeBoundaries={IIDXLIKE_GBOUNDARIES}
|
||||
value={sc.scoreData.percent}
|
||||
grade={sc.scoreData.grade}
|
||||
/>
|
||||
<BMSOrPMSLampCell score={sc} />
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
import IIDXLampCell from "components/tables/cells/IIDXLampCell";
|
||||
import JubeatScoreCell from "components/tables/cells/JubeatScoreCell";
|
||||
import LampCell from "components/tables/cells/LampCell";
|
||||
import MillionsScoreCell from "components/tables/cells/MillionsScoreCell";
|
||||
import PopnLampCell from "components/tables/cells/PopnLampCell";
|
||||
import ScoreCell from "components/tables/cells/ScoreCell";
|
||||
import SDVXLampCell from "components/tables/cells/SDVXLampCell";
|
||||
import React from "react";
|
||||
import { ChartDocument, Game, PBScoreDocument, ScoreDocument } from "tachi-common";
|
||||
import BMSOrPMSLampCell from "../cells/BMSOrPMSLampCell";
|
||||
import BMSCoreCells from "./BMSCoreCells";
|
||||
|
||||
export default function ShortScoreCoreCells({
|
||||
game,
|
||||
score,
|
||||
chart,
|
||||
}: {
|
||||
score: ScoreDocument | PBScoreDocument;
|
||||
chart: ChartDocument;
|
||||
game: Game;
|
||||
}) {
|
||||
const sc = score as any; // lazy hack
|
||||
|
||||
if (game === "iidx") {
|
||||
return (
|
||||
<>
|
||||
<ScoreCell score={sc} />
|
||||
<IIDXLampCell sc={sc} chart={chart as ChartDocument<"iidx:SP" | "iidx:DP">} />
|
||||
</>
|
||||
);
|
||||
} else if (game === "bms" || game === "pms") {
|
||||
return (
|
||||
<>
|
||||
<ScoreCell score={sc} />
|
||||
<BMSOrPMSLampCell score={sc} />
|
||||
</>
|
||||
);
|
||||
} else if (
|
||||
game === "sdvx" ||
|
||||
game === "usc" ||
|
||||
game === "museca" ||
|
||||
game === "wacca" ||
|
||||
game === "chunithm" ||
|
||||
game === "gitadora"
|
||||
) {
|
||||
return (
|
||||
<>
|
||||
<MillionsScoreCell score={sc} />
|
||||
<SDVXLampCell score={sc} />
|
||||
</>
|
||||
);
|
||||
} else if (game === "jubeat") {
|
||||
return (
|
||||
<>
|
||||
<JubeatScoreCell sc={sc} />
|
||||
<LampCell score={sc} />
|
||||
</>
|
||||
);
|
||||
} else if (game === "popn") {
|
||||
return (
|
||||
<>
|
||||
<MillionsScoreCell score={sc} />
|
||||
<PopnLampCell score={sc} />
|
||||
</>
|
||||
);
|
||||
} else if (game === "itg") {
|
||||
return (
|
||||
<>
|
||||
<ScoreCell showScore={false} score={sc} />
|
||||
<LampCell score={sc} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return <>No cells defined for {game}. Report this!</>;
|
||||
}
|
||||
@@ -8,8 +8,8 @@ import PBCompareCell from "../cells/PBCompareCell";
|
||||
import TitleCell from "../cells/TitleCell";
|
||||
import SelectableCompareType from "../components/SelectableCompareType";
|
||||
import TachiTable, { Header, ZTableTHProps } from "../components/TachiTable";
|
||||
import ShortScoreCoreCells from "../game-core-cells/ShortScoreCoreCells";
|
||||
import ChartHeader from "../headers/ChartHeader";
|
||||
import ScoreCoreCells from "../game-core-cells/ScoreCoreCells";
|
||||
|
||||
export default function ComparePBsTable({
|
||||
dataset,
|
||||
@@ -103,7 +103,7 @@ function Row({ data, game, metric }: { data: ComparePBsDataset[0]; game: Game; m
|
||||
<DifficultyCell chart={data.chart} game={game} alwaysShort />
|
||||
<TitleCell game={game} song={data.song} chart={data.chart} />
|
||||
{data.base ? (
|
||||
<ShortScoreCoreCells score={data.base} game={game} chart={data.chart} />
|
||||
<ScoreCoreCells score={data.base} game={game} chart={data.chart} />
|
||||
) : (
|
||||
<td colSpan={2}>Not Played</td>
|
||||
)}
|
||||
@@ -116,7 +116,7 @@ function Row({ data, game, metric }: { data: ComparePBsDataset[0]; game: Game; m
|
||||
metric={metric}
|
||||
/>
|
||||
{data.compare ? (
|
||||
<ShortScoreCoreCells score={data.compare} game={game} chart={data.chart} />
|
||||
<ScoreCoreCells score={data.compare} game={game} chart={data.chart} />
|
||||
) : (
|
||||
<td colSpan={2}>Not Played</td>
|
||||
)}
|
||||
|
||||
@@ -10,12 +10,14 @@ import {
|
||||
FormatChart,
|
||||
Game,
|
||||
GetGamePTConfig,
|
||||
GetScoreMetricConf,
|
||||
GoalDocument,
|
||||
Playtype,
|
||||
SongDocument,
|
||||
} from "tachi-common";
|
||||
import { GamePT, SetState, UGPT } from "types/react";
|
||||
import CheckEdit from "components/util/CheckEdit";
|
||||
import { ConfEnumScoreMetric } from "tachi-common/types/metrics";
|
||||
|
||||
export default function SetNewGoalModal({
|
||||
show,
|
||||
@@ -32,14 +34,15 @@ export default function SetNewGoalModal({
|
||||
preData: { chart: ChartDocument; song: SongDocument } | FolderDocument;
|
||||
} & UGPT) {
|
||||
const gptConfig = GetGamePTConfig(game, playtype);
|
||||
const conf = GetScoreMetricConf(
|
||||
gptConfig,
|
||||
gptConfig.preferredDefaultEnum
|
||||
) as ConfEnumScoreMetric<string>;
|
||||
|
||||
const [criteria, setCriteria] = useState<GoalDocument["criteria"]>({
|
||||
mode: "single",
|
||||
key: gptConfig.preferredDefaultEnum === "grade" ? "scoreData.gradeIndex" : "scoreData.lampIndex",
|
||||
value:
|
||||
gptConfig.preferredDefaultEnum === "grade"
|
||||
? gptConfig.grades.indexOf(gptConfig.clearGrade)
|
||||
: gptConfig.lamps.indexOf(gptConfig.clearLamp),
|
||||
key: gptConfig.preferredDefaultEnum,
|
||||
value: conf.values.indexOf(conf.minimumRelevantValue),
|
||||
});
|
||||
|
||||
const charts = useMemo<GoalDocument["charts"]>(
|
||||
@@ -283,20 +286,18 @@ function CriteriaValuePicker({
|
||||
onChange: (value: GoalDocument["criteria"]["value"]) => void;
|
||||
} & GamePT) {
|
||||
const gptConfig = GetGamePTConfig(game, playtype);
|
||||
const conf = GetScoreMetricConf(gptConfig, criteria.key);
|
||||
|
||||
switch (criteria.key) {
|
||||
case "scoreData.score":
|
||||
return (
|
||||
<Form.Control
|
||||
style={{ display: "inline", width: "unset" }}
|
||||
className="mx-2"
|
||||
onChange={(e) => onChange(Number(e.target.value))}
|
||||
type="number"
|
||||
min={0}
|
||||
value={criteria.value}
|
||||
/>
|
||||
);
|
||||
case "scoreData.percent":
|
||||
if (!conf) {
|
||||
return <>ENOCONF {criteria.key}</>;
|
||||
}
|
||||
|
||||
switch (conf.type) {
|
||||
case "NULLABLE_GRAPH":
|
||||
case "GRAPH":
|
||||
return <>Cannot set goals for graph metrics.</>;
|
||||
case "DECIMAL":
|
||||
case "INTEGER":
|
||||
return (
|
||||
<Form.Control
|
||||
style={{ display: "inline", width: "unset" }}
|
||||
@@ -305,36 +306,22 @@ function CriteriaValuePicker({
|
||||
type="number"
|
||||
value={criteria.value}
|
||||
min={0}
|
||||
max={gptConfig.percentMax}
|
||||
/>
|
||||
);
|
||||
case "scoreData.gradeIndex":
|
||||
case "ENUM":
|
||||
return (
|
||||
<Select
|
||||
inline
|
||||
value={criteria.value.toString()}
|
||||
setValue={(v) => onChange(Number(v))}
|
||||
>
|
||||
{gptConfig.grades.map((e, i) => (
|
||||
<option key={i} value={i}>
|
||||
{e}
|
||||
</option>
|
||||
))}
|
||||
</Select>
|
||||
);
|
||||
|
||||
case "scoreData.lampIndex":
|
||||
return (
|
||||
<Select
|
||||
inline
|
||||
value={criteria.value.toString()}
|
||||
setValue={(v) => onChange(Number(v))}
|
||||
>
|
||||
{gptConfig.lamps.map((e, i) => (
|
||||
<option key={i} value={i}>
|
||||
{e}
|
||||
</option>
|
||||
))}
|
||||
{conf.values
|
||||
.slice(conf.values.indexOf(conf.minimumRelevantValue))
|
||||
.map((e, i) => (
|
||||
<option key={i} value={i}>
|
||||
{e}
|
||||
</option>
|
||||
))}
|
||||
</Select>
|
||||
);
|
||||
}
|
||||
@@ -342,12 +329,16 @@ function CriteriaValuePicker({
|
||||
|
||||
function getBaseKeyValue(game: Game, playtype: Playtype, key: GoalDocument["criteria"]["key"]) {
|
||||
const gptConfig = GetGamePTConfig(game, playtype);
|
||||
const conf = GetScoreMetricConf(gptConfig, key);
|
||||
|
||||
switch (key) {
|
||||
case "scoreData.gradeIndex":
|
||||
return gptConfig.grades.indexOf(gptConfig.clearGrade);
|
||||
case "scoreData.lampIndex":
|
||||
return gptConfig.lamps.indexOf(gptConfig.clearLamp);
|
||||
if (!conf) {
|
||||
// SHOULD NEVER HAPPEN!
|
||||
return 0;
|
||||
}
|
||||
|
||||
switch (conf.type) {
|
||||
case "ENUM":
|
||||
return conf.values.indexOf(conf.minimumRelevantValue);
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { APIFetchV1 } from "util/api";
|
||||
import { CreateSongMap } from "util/data";
|
||||
import { UppercaseFirst } from "util/misc";
|
||||
import { useFormik } from "formik";
|
||||
import React, { ChangeEventHandler, useContext, useEffect, useState } from "react";
|
||||
import { Button, Form, Modal } from "react-bootstrap";
|
||||
@@ -11,6 +12,8 @@ import {
|
||||
UserDocument,
|
||||
ShowcaseStatDetails,
|
||||
Playtype,
|
||||
GetScoreMetricConf,
|
||||
GetScoreMetrics,
|
||||
} from "tachi-common";
|
||||
import { GamePT, SetState } from "types/react";
|
||||
import { SongChartsSearch } from "types/api-returns";
|
||||
@@ -48,13 +51,13 @@ export default function UGPTStatCreator({
|
||||
if (values.mode === "chart") {
|
||||
stat = {
|
||||
mode: "chart",
|
||||
metric: values.property as ShowcaseStatDetails["metric"],
|
||||
metric: values.metric as ShowcaseStatDetails["metric"],
|
||||
chartID: values.chartID ?? "",
|
||||
};
|
||||
} else if (values.mode === "folder") {
|
||||
stat = {
|
||||
mode: "folder",
|
||||
metric: values.property as "lamp" | "score" | "percent" | "grade",
|
||||
metric: values.metric as "lamp" | "score" | "percent" | "grade",
|
||||
folderID: values.folderID ?? "",
|
||||
gte,
|
||||
};
|
||||
@@ -68,7 +71,7 @@ export default function UGPTStatCreator({
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (formik.values.mode === "folder" && formik.values.property === "playcount") {
|
||||
if (formik.values.mode === "folder" && formik.values.metric === "playcount") {
|
||||
formik.setValues({ ...formik.values, metric: "lamp" });
|
||||
}
|
||||
}, [formik.values.mode]);
|
||||
@@ -143,6 +146,8 @@ export default function UGPTStatCreator({
|
||||
})();
|
||||
}, [chartSearch, requesterHasPlayed]);
|
||||
|
||||
const gptConfig = GetGamePTConfig(game, playtype);
|
||||
|
||||
return (
|
||||
<Modal show={show} onHide={() => setShow(false)}>
|
||||
<Modal.Header closeButton>
|
||||
@@ -167,14 +172,15 @@ export default function UGPTStatCreator({
|
||||
<Form.Label>Property</Form.Label>
|
||||
<Form.Control
|
||||
as="select"
|
||||
id="property"
|
||||
value={formik.values.property}
|
||||
id="metric"
|
||||
value={formik.values.metric}
|
||||
onChange={formik.handleChange}
|
||||
>
|
||||
<option value="lamp">Lamp</option>
|
||||
<option value="grade">Grade</option>
|
||||
<option value="percent">Percent</option>
|
||||
<option value="score">Score</option>
|
||||
{GetScoreMetrics(gptConfig, ["DECIMAL", "INTEGER", "ENUM"]).map((e) => (
|
||||
<option key={e} value={e}>
|
||||
{UppercaseFirst(e)}
|
||||
</option>
|
||||
))}
|
||||
{formik.values.mode === "chart" && (
|
||||
<option value="playcount">Playcount</option>
|
||||
)}
|
||||
@@ -222,7 +228,7 @@ export default function UGPTStatCreator({
|
||||
{...{
|
||||
game,
|
||||
playtype,
|
||||
property: formik.values.property,
|
||||
metric: formik.values.metric,
|
||||
}}
|
||||
/>
|
||||
</Form.Group>
|
||||
@@ -268,52 +274,39 @@ export default function UGPTStatCreator({
|
||||
}
|
||||
|
||||
function FolderGTESelect({
|
||||
property,
|
||||
metric,
|
||||
game,
|
||||
playtype,
|
||||
value,
|
||||
onChange,
|
||||
}: {
|
||||
property: string;
|
||||
metric: string;
|
||||
value: number;
|
||||
onChange: ChangeEventHandler<HTMLSelectElement | HTMLInputElement>;
|
||||
} & GamePT) {
|
||||
const gptConfig = GetGamePTConfig(game, playtype);
|
||||
|
||||
const props = { value, onChange };
|
||||
if (property === "grade") {
|
||||
return (
|
||||
<select className="form-control" {...props}>
|
||||
{gptConfig.grades.map((e, i) => (
|
||||
<option key={i} value={i}>
|
||||
{e}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
);
|
||||
} else if (property === "lamp" || property === "playcount") {
|
||||
return (
|
||||
<select className="form-control" {...props}>
|
||||
{gptConfig.lamps.map((e, i) => (
|
||||
<option key={i} value={i}>
|
||||
{e}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
);
|
||||
} else if (property === "percent") {
|
||||
return (
|
||||
<input
|
||||
className="form-control"
|
||||
type="number"
|
||||
min={0}
|
||||
max={gptConfig.percentMax}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
} else if (property === "score") {
|
||||
return <input className="form-control" type="number" min={0} {...props} />;
|
||||
|
||||
const conf = GetScoreMetricConf(gptConfig, metric);
|
||||
|
||||
if (!conf) {
|
||||
return <>error: no conf? what?</>;
|
||||
}
|
||||
|
||||
throw new Error(`Invalid property ${property}.`);
|
||||
if (conf.type === "ENUM") {
|
||||
return (
|
||||
<select className="form-control" {...props}>
|
||||
{conf.values.map((e, i) => (
|
||||
<option key={i} value={i}>
|
||||
{e}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
);
|
||||
} else if (conf.type === "GRAPH" || conf.type === "NULLABLE_GRAPH") {
|
||||
return <>can't set stats for graphs. how'd you get here?</>;
|
||||
}
|
||||
|
||||
return <input className="form-control" type="number" min={0} {...props} />;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { GITADORA_DORA_IMPL, GITADORA_GITA_IMPL } from "./games/gitadora";
|
||||
import { SDVX_IMPL, USC_IMPL } from "./games/sdvx-usc";
|
||||
import { GPTClientImplementation } from "./types";
|
||||
import { IIDX_DP_IMPL, IIDX_SP_IMPL } from "./games/iidx";
|
||||
import { BMS_PMS_IMPL } from "./games/bms-pms";
|
||||
import { BMS_14K_IMPL, BMS_7K_IMPL, PMS_IMPL } from "./games/bms-pms";
|
||||
import { bg, bgc } from "./games/_util";
|
||||
|
||||
type GPTClientImplementations = {
|
||||
@@ -397,10 +397,10 @@ export const GPT_CLIENT_IMPLEMENTATIONS: GPTClientImplementations = {
|
||||
},
|
||||
"gitadora:Dora": GITADORA_DORA_IMPL,
|
||||
"gitadora:Gita": GITADORA_GITA_IMPL,
|
||||
"bms:14K": BMS_PMS_IMPL,
|
||||
"bms:7K": BMS_PMS_IMPL,
|
||||
"pms:Controller": BMS_PMS_IMPL,
|
||||
"pms:Keyboard": BMS_PMS_IMPL,
|
||||
"bms:14K": BMS_14K_IMPL,
|
||||
"bms:7K": BMS_7K_IMPL,
|
||||
"pms:Controller": PMS_IMPL,
|
||||
"pms:Keyboard": PMS_IMPL,
|
||||
"sdvx:Single": SDVX_IMPL,
|
||||
"usc:Controller": USC_IMPL,
|
||||
"usc:Keyboard": USC_IMPL,
|
||||
|
||||
@@ -3,9 +3,9 @@ import { NumericSOV } from "util/sorts";
|
||||
import { GPTClientImplementation } from "lib/types";
|
||||
import { COLOUR_SET, GPTStrings } from "tachi-common";
|
||||
import { FormatSieglindeBMS } from "tachi-common/config/game-support/bms";
|
||||
import { CreateRatingSys } from "./_util";
|
||||
import { CreateRatingSys, bg } from "./_util";
|
||||
|
||||
const BMS_PMS_IMPL: GPTClientImplementation<GPTStrings["bms" | "pms"]> = {
|
||||
const BASE_IMPL: GPTClientImplementation<GPTStrings["bms" | "pms"]> = {
|
||||
difficultyColours: {
|
||||
CHART: COLOUR_SET.gray, // lol
|
||||
},
|
||||
@@ -55,4 +55,137 @@ const BMS_PMS_IMPL: GPTClientImplementation<GPTStrings["bms" | "pms"]> = {
|
||||
],
|
||||
};
|
||||
|
||||
export const;
|
||||
export const BMS_7K_IMPL: GPTClientImplementation<"bms:7K"> = {
|
||||
...BASE_IMPL,
|
||||
classColours: {
|
||||
genocideDan: {
|
||||
NORMAL_1: bg("lightblue"),
|
||||
NORMAL_2: bg("lightblue"),
|
||||
NORMAL_3: bg("lightblue"),
|
||||
NORMAL_4: bg("lightblue"),
|
||||
NORMAL_5: bg("lightblue"),
|
||||
NORMAL_6: bg("lightblue"),
|
||||
NORMAL_7: bg("lightblue"),
|
||||
NORMAL_8: bg("lightblue"),
|
||||
NORMAL_9: bg("lightred"),
|
||||
NORMAL_10: bg("lightred"),
|
||||
INSANE_1: "info",
|
||||
INSANE_2: "info",
|
||||
INSANE_3: "info",
|
||||
INSANE_4: "info",
|
||||
INSANE_5: "info",
|
||||
INSANE_6: "info",
|
||||
INSANE_7: "info",
|
||||
INSANE_8: "info",
|
||||
INSANE_9: bg("red"),
|
||||
INSANE_10: bg("red"),
|
||||
INSANE_KAIDEN: bg("teal"),
|
||||
OVERJOY: bg("purple"),
|
||||
},
|
||||
lnDan: {
|
||||
DAN_1: bg("lightblue"),
|
||||
DAN_2: bg("lightblue"),
|
||||
DAN_3: bg("lightblue"),
|
||||
DAN_4: bg("lightblue"),
|
||||
DAN_5: bg("lightblue"),
|
||||
DAN_6: bg("lightblue"),
|
||||
DAN_7: bg("lightblue"),
|
||||
DAN_8: bg("lightblue"),
|
||||
DAN_9: bg("lightred"),
|
||||
DAN_10: bg("lightred"),
|
||||
KAIDEN: bg("teal"),
|
||||
OVERJOY: bg("purple"),
|
||||
UDON: bg("gold"),
|
||||
},
|
||||
scratchDan: {
|
||||
KYU_7: bg("green"),
|
||||
KYU_6: bg("green"),
|
||||
KYU_5: bg("green"),
|
||||
KYU_4: bg("green"),
|
||||
KYU_3: bg("green"),
|
||||
KYU_2: bg("green"),
|
||||
KYU_1: bg("green"),
|
||||
DAN_1: "info",
|
||||
DAN_2: "info",
|
||||
DAN_3: "info",
|
||||
DAN_4: "info",
|
||||
DAN_5: "info",
|
||||
DAN_6: "info",
|
||||
DAN_7: "info",
|
||||
DAN_8: "info",
|
||||
DAN_9: "danger",
|
||||
DAN_10: "danger",
|
||||
KAIDEN: "warning",
|
||||
},
|
||||
stslDan: {
|
||||
SL0: null,
|
||||
SL1: null,
|
||||
SL2: null,
|
||||
SL3: null,
|
||||
SL4: null,
|
||||
SL5: null,
|
||||
SL6: null,
|
||||
SL7: null,
|
||||
SL8: null,
|
||||
SL9: null,
|
||||
SL10: null,
|
||||
SL11: null,
|
||||
SL12: null,
|
||||
ST0: null,
|
||||
ST1: null,
|
||||
ST2: null,
|
||||
ST3: null,
|
||||
ST4: null,
|
||||
ST5: null,
|
||||
ST6: null,
|
||||
ST7: null,
|
||||
ST8: null,
|
||||
ST9: null,
|
||||
ST10: null,
|
||||
ST11: null,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const BMS_14K_IMPL: GPTClientImplementation<"bms:14K"> = {
|
||||
...BASE_IMPL,
|
||||
classColours: {
|
||||
genocideDan: BMS_7K_IMPL.classColours.genocideDan,
|
||||
stslDan: {
|
||||
SL0: null,
|
||||
SL1: null,
|
||||
SL2: null,
|
||||
SL3: null,
|
||||
SL4: null,
|
||||
SL5: null,
|
||||
SL6: null,
|
||||
SL7: null,
|
||||
SL8: null,
|
||||
SL9: null,
|
||||
SL10: null,
|
||||
SL11: null,
|
||||
SL12: null,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const PMS_IMPL: GPTClientImplementation<GPTStrings["pms"]> = {
|
||||
...BASE_IMPL,
|
||||
classColours: {
|
||||
dan: {
|
||||
INSANE_1: "info",
|
||||
INSANE_2: "info",
|
||||
INSANE_3: "info",
|
||||
INSANE_4: "info",
|
||||
INSANE_5: "info",
|
||||
INSANE_6: "info",
|
||||
INSANE_7: "info",
|
||||
INSANE_8: "info",
|
||||
INSANE_9: bg("red"),
|
||||
INSANE_10: bg("red"),
|
||||
INSANE_KAIDEN: bg("teal"),
|
||||
OVERJOY: bg("purple"),
|
||||
UNDEFINED: "warning",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { NumericSOV } from "util/sorts";
|
||||
import { GPTClientImplementation } from "lib/types";
|
||||
import { COLOUR_SET, GPTString, GPTStrings } from "tachi-common";
|
||||
import { COLOUR_SET, GPTStrings } from "tachi-common";
|
||||
import { bg, bgc } from "./_util";
|
||||
|
||||
const GITADORA_ENUM_COLOURS: GPTClientImplementation<
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { NumericSOV } from "util/sorts";
|
||||
import { GPTClientImplementation } from "lib/types";
|
||||
import { COLOUR_SET, GPTStrings } from "tachi-common";
|
||||
import { CreateRatingSys } from "./_util";
|
||||
import { CreateRatingSys, bg, bgc } from "./_util";
|
||||
|
||||
const SDVXLIKE_ENUM_COLOURS: GPTClientImplementation<GPTStrings["usc" | "sdvx"]>["enumColours"] = {
|
||||
grade: {
|
||||
@@ -39,6 +39,64 @@ export const SDVX_IMPL: GPTClientImplementation<"sdvx:Single"> = {
|
||||
XCD: COLOUR_SET.blue,
|
||||
MXM: COLOUR_SET.white,
|
||||
},
|
||||
classColours: {
|
||||
dan: {
|
||||
DAN_1: bg(COLOUR_SET.red),
|
||||
DAN_2: bg(COLOUR_SET.paleBlue),
|
||||
DAN_3: bgc("gold", "black"),
|
||||
DAN_4: bg("gray"),
|
||||
DAN_5: bgc(COLOUR_SET.teal, "black"),
|
||||
DAN_6: bg("blue"),
|
||||
DAN_7: bgc(COLOUR_SET.vibrantPink, "black"),
|
||||
DAN_8: bgc("pink", "black"),
|
||||
DAN_9: bgc("white", "black"),
|
||||
DAN_10: "warning",
|
||||
DAN_11: "danger",
|
||||
INF: bgc("purple", "gold"),
|
||||
},
|
||||
vfClass: {
|
||||
SIENNA_I: bg(COLOUR_SET.red),
|
||||
SIENNA_II: bg(COLOUR_SET.red),
|
||||
SIENNA_III: bg(COLOUR_SET.red),
|
||||
SIENNA_IV: bg(COLOUR_SET.red),
|
||||
COBALT_I: bg(COLOUR_SET.paleBlue),
|
||||
COBALT_II: bg(COLOUR_SET.paleBlue),
|
||||
COBALT_III: bg(COLOUR_SET.paleBlue),
|
||||
COBALT_IV: bg(COLOUR_SET.paleBlue),
|
||||
DANDELION_I: bgc(COLOUR_SET.gold, "black"),
|
||||
DANDELION_II: bgc(COLOUR_SET.gold, "black"),
|
||||
DANDELION_III: bgc(COLOUR_SET.gold, "black"),
|
||||
DANDELION_IV: bgc(COLOUR_SET.gold, "black"),
|
||||
CYAN_I: bgc(COLOUR_SET.teal, "black"),
|
||||
CYAN_II: bgc(COLOUR_SET.teal, "black"),
|
||||
CYAN_III: bgc(COLOUR_SET.teal, "black"),
|
||||
CYAN_IV: bgc(COLOUR_SET.teal, "black"),
|
||||
SCARLET_I: bgc(COLOUR_SET.vibrantPink, "black"),
|
||||
SCARLET_II: bgc(COLOUR_SET.vibrantPink, "black"),
|
||||
SCARLET_III: bgc(COLOUR_SET.vibrantPink, "black"),
|
||||
SCARLET_IV: bgc(COLOUR_SET.vibrantPink, "black"),
|
||||
CORAL_I: bgc(COLOUR_SET.pink, "black"),
|
||||
CORAL_II: bgc(COLOUR_SET.pink, "black"),
|
||||
CORAL_III: bgc(COLOUR_SET.pink, "black"),
|
||||
CORAL_IV: bgc(COLOUR_SET.pink, "black"),
|
||||
ARGENTO_I: bgc(COLOUR_SET.white, "black"),
|
||||
ARGENTO_II: bgc(COLOUR_SET.white, "black"),
|
||||
ARGENTO_III: bgc(COLOUR_SET.white, "black"),
|
||||
ARGENTO_IV: bgc(COLOUR_SET.white, "black"),
|
||||
ELDORA_I: "warning",
|
||||
ELDORA_II: "warning",
|
||||
ELDORA_III: "warning",
|
||||
ELDORA_IV: "warning",
|
||||
CRIMSON_I: bg(COLOUR_SET.vibrantRed),
|
||||
CRIMSON_II: bg(COLOUR_SET.vibrantRed),
|
||||
CRIMSON_III: bg(COLOUR_SET.vibrantRed),
|
||||
CRIMSON_IV: bg(COLOUR_SET.vibrantRed),
|
||||
IMPERIAL_I: bg(COLOUR_SET.vibrantPurple),
|
||||
IMPERIAL_II: bg(COLOUR_SET.vibrantPurple),
|
||||
IMPERIAL_III: bg(COLOUR_SET.vibrantPurple),
|
||||
IMPERIAL_IV: bg(COLOUR_SET.vibrantPurple),
|
||||
},
|
||||
},
|
||||
ratingSystems: [
|
||||
CreateRatingSys(
|
||||
"Tierlist",
|
||||
@@ -68,4 +126,7 @@ export const USC_IMPL: GPTClientImplementation<GPTStrings["usc"]> = {
|
||||
["Near - Miss", "Nr. Ms.", NumericSOV((x) => x?.scoreData.score)],
|
||||
["Lamp", "Lamp", NumericSOV((x) => x?.scoreData.enumIndexes.lamp)],
|
||||
],
|
||||
classColours: {
|
||||
vfClass: SDVX_IMPL.classColours.vfClass,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,87 +0,0 @@
|
||||
import { Game, GetGamePTConfig, Grades, GPTString, integer, Playtypes } from "tachi-common";
|
||||
|
||||
export function AbsoluteGradeDelta<GPT extends GPTString = GPTString>(
|
||||
game: Game,
|
||||
playtype: Playtypes[Game],
|
||||
score: number,
|
||||
percent: number,
|
||||
gradeOrIndex: Grades[GPT] | integer
|
||||
) {
|
||||
const gptConfig = GetGamePTConfig(game, playtype);
|
||||
|
||||
if (!gptConfig.gradeBoundaries) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const maxScore = Math.round(score * (100 / percent));
|
||||
|
||||
const gradeIndex =
|
||||
typeof gradeOrIndex === "number" ? gradeOrIndex : gptConfig.grades.indexOf(gradeOrIndex);
|
||||
|
||||
const gradeScore = Math.ceil((gptConfig.gradeBoundaries[gradeIndex] * maxScore) / 100);
|
||||
|
||||
return score - gradeScore;
|
||||
}
|
||||
|
||||
export function RelativeGradeDelta<GPT extends GPTString = GPTString>(
|
||||
game: Game,
|
||||
playtype: Playtypes[Game],
|
||||
score: number,
|
||||
percent: number,
|
||||
grade: Grades[GPT],
|
||||
relativeIndex: integer
|
||||
) {
|
||||
const gptConfig = GetGamePTConfig(game, playtype);
|
||||
|
||||
const nextGradeIndex = gptConfig.grades.indexOf(grade) + relativeIndex;
|
||||
|
||||
if (nextGradeIndex < 0 || nextGradeIndex >= gptConfig.grades.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
grade: gptConfig.grades[nextGradeIndex],
|
||||
delta: AbsoluteGradeDelta(game, playtype, score, percent, nextGradeIndex),
|
||||
};
|
||||
}
|
||||
|
||||
function WrapGrade(grade: string) {
|
||||
if (grade.endsWith("-") || grade.endsWith("+")) {
|
||||
return `(${grade})`;
|
||||
}
|
||||
|
||||
return grade;
|
||||
}
|
||||
|
||||
export function GenericFormatGradeDelta<GPT extends GPTString = GPTString>(
|
||||
game: Game,
|
||||
playtype: Playtypes[Game],
|
||||
score: number,
|
||||
percent: number,
|
||||
grade: Grades[GPT],
|
||||
formatNumFn: (n: number) => number = (s) => s
|
||||
): {
|
||||
lower: string;
|
||||
upper?: string;
|
||||
closer: "lower" | "upper";
|
||||
} {
|
||||
const upper = RelativeGradeDelta(game, playtype, score, percent, grade, 1);
|
||||
const lower = AbsoluteGradeDelta(game, playtype, score, percent, grade);
|
||||
|
||||
const formatLower = `${WrapGrade(grade)}+${formatNumFn(lower)}`;
|
||||
|
||||
if (!upper) {
|
||||
return {
|
||||
lower: formatLower,
|
||||
closer: "lower",
|
||||
};
|
||||
}
|
||||
|
||||
const formatUpper = `${WrapGrade(upper.grade)}${formatNumFn(upper.delta)}`;
|
||||
|
||||
return {
|
||||
lower: formatLower,
|
||||
upper: formatUpper,
|
||||
closer: upper.delta + lower < 0 ? "lower" : "upper",
|
||||
};
|
||||
}
|
||||
@@ -128,7 +128,7 @@ export function EscapeStringRegexp(string: string) {
|
||||
return string.replace(/[|\\{}()[\]^$+*?.]/gu, "\\$&").replace(/-/gu, "\\x2d");
|
||||
}
|
||||
|
||||
export function Reverse<T>(arr: T[]): T[] {
|
||||
export function Reverse<T>(arr: readonly T[]): T[] {
|
||||
return arr.slice(0).reverse();
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ export function HumanFriendlyStrToEnumIndex(
|
||||
|
||||
const conf = GetScoreMetricConf(gptConfig, enumMetric);
|
||||
|
||||
if (conf.type !== "ENUM") {
|
||||
if (conf?.type !== "ENUM") {
|
||||
return () => 0; // wut
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user