diff --git a/client/package.json b/client/package.json
index 3a8ed93c7..f9f7002bf 100644
--- a/client/package.json
+++ b/client/package.json
@@ -75,7 +75,7 @@
"resolve": "1.12.0",
"resolve-url-loader": "3.1.0",
"socicon": "3.0.5",
- "tachi-common": "0.2.12"
+ "tachi-common": "0.2.15"
},
"devDependencies": {
"@types/humanize-duration": "3.25.1",
diff --git a/client/pnpm-lock.yaml b/client/pnpm-lock.yaml
index 4f279ba86..bc8896253 100644
--- a/client/pnpm-lock.yaml
+++ b/client/pnpm-lock.yaml
@@ -77,7 +77,7 @@ specifiers:
serve: 11.2.0
socicon: 3.0.5
style-loader: 1.0.0
- tachi-common: 0.2.12
+ tachi-common: 0.2.15
terser-webpack-plugin: 1.4.1
ts-pnp: 1.1.4
typescript: 4.3.5
@@ -142,7 +142,7 @@ dependencies:
resolve: 1.12.0
resolve-url-loader: 3.1.0
socicon: 3.0.5
- tachi-common: 0.2.12
+ tachi-common: 0.2.15
devDependencies:
'@types/humanize-duration': 3.25.1
@@ -11178,8 +11178,8 @@ packages:
strip-ansi: 6.0.0
dev: true
- /tachi-common/0.2.12:
- resolution: {integrity: sha512-xwbQUZefLUs1sus3nrrys84qJyRMWLl/Mb4UzFYd74n0ZmOejrxpwIb18ILWww4J/IxQurPd4Id+DCP12SEW1w==}
+ /tachi-common/0.2.15:
+ resolution: {integrity: sha512-p7nt7pupl2JWNb20K7n18YOBtxUYeoyDsE6YbPVVyPvY1oj74a2YxALpmajGV2pbVAkdywrJS94In2sQUGzJtw==}
dependencies:
monk: 7.3.4
transitivePeerDependencies:
diff --git a/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/SpecificFolderPage.tsx b/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/SpecificFolderPage.tsx
index d7a7cb229..049d72ac7 100644
--- a/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/SpecificFolderPage.tsx
+++ b/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/SpecificFolderPage.tsx
@@ -1,22 +1,38 @@
import { ResponsiveBar } from "@nivo/bar";
import { BarChartTooltip } from "components/charts/ChartTooltip";
+import QuickTooltip from "components/layout/misc/QuickTooltip";
import Card from "components/layout/page/Card";
import FolderTable from "components/tables/folders/FolderTable";
import ApiError from "components/util/ApiError";
import Divider from "components/util/Divider";
+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 React, { useMemo } from "react";
+import SelectButton from "components/util/SelectButton";
+import { useFormik } from "formik";
+import React, { useMemo, useState } from "react";
+import { Col, Form, Row } from "react-bootstrap";
import { useParams } from "react-router-dom";
-import { Game, GetGamePTConfig, PublicUserDocument } from "tachi-common";
+import {
+ ChartTierlistInfo,
+ FormatDifficultyShort,
+ Game,
+ GetGamePTConfig,
+ GPTTierlists,
+ IDStrings,
+ PublicUserDocument,
+} from "tachi-common";
import { UGPTFolderReturns } from "types/api-returns";
import { FolderDataset } from "types/tables";
import { Playtype } from "types/tachi";
import { DEFAULT_BAR_PROPS } from "util/charts";
import { ChangeOpacity } from "util/color-opacity";
import { CreateChartIDMap, CreateSongMap } from "util/data";
-import { ComposeExpFn, ComposeInverseExpFn } from "util/misc";
+import { ComposeExpFn, ComposeInverseExpFn, IsNullish, NO_OP } from "util/misc";
+import { GetGradeChartExpScale } from "util/scales";
import { NumericSOV, StrSOV } from "util/sorts";
+import { GetScaleAchievedFn } from "util/tierlist";
interface Props {
reqUser: PublicUserDocument;
@@ -31,6 +47,10 @@ export default function SpecificFolderPage({ reqUser, game, playtype }: Props) {
`/users/${reqUser.id}/games/${game}/${playtype}/folders/${folderID}`
);
+ const [mode, setMode] = useState<"normal" | "ladder">("normal");
+
+ const gptConfig = GetGamePTConfig(game, playtype);
+
const folderDataset = useMemo(() => {
if (!data) {
return null;
@@ -56,6 +76,22 @@ export default function SpecificFolderPage({ reqUser, game, playtype }: Props) {
return folderDataset;
}, [data]);
+ const folderInfoHeader = useMemo(() => {
+ if (!folderDataset || !data) {
+ return ;
+ }
+
+ return (
+
+ );
+ }, [folderDataset]);
+
if (isLoading || !data || !folderDataset) {
return ;
}
@@ -69,35 +105,326 @@ export default function SpecificFolderPage({ reqUser, game, playtype }: Props) {
+ {folderInfoHeader}
+
+
+
+
+ Normal View
+
+ {gptConfig.tierlists.length && (
+
+
+ Tierlist View
+
+ )}
+
-
+ {mode === "normal" ? (
+
+ ) : (
+
+ )}
);
}
-function FolderInfoHeader({
+type InfoProps = Props & {
+ folderDataset: FolderDataset;
+ data: UGPTFolderReturns;
+};
+
+function TierlistBreakdown({ game, folderDataset, playtype, reqUser }: InfoProps) {
+ const gptConfig = GetGamePTConfig(game, playtype);
+
+ const formik = useFormik({
+ initialValues: {
+ __hideAchieved: false,
+ ...Object.fromEntries(gptConfig.tierlists.map(e => [e, true])),
+ } as { __hideAchieved: boolean } & Partial>,
+ onSubmit: NO_OP,
+ });
+
+ const tierlistInfo = FolderDatasetToTierlistInfo(folderDataset, game, playtype, formik.values);
+
+ const dataMap = CreateChartIDMap(folderDataset);
+
+ return (
+
+
+
+ Here you can select what tierlist blocks to show!
+
+ {gptConfig.tierlists.length > 1 &&
+ gptConfig.tierlists.map(e => (
+ <>
+
+ {gptConfig.tierlistDescriptions[e]}
+ >
+ ))}
+
+
+ Hide achieved elements of the tierlist.
+
+
+
+
+
+
+
+
+
+ );
+}
+
+function TierlistInfoLadder({
+ tierlistInfo,
+ dataMap,
game,
playtype,
- reqUser,
- folderDataset,
- data,
-}: Props & { folderDataset: FolderDataset; data: UGPTFolderReturns }) {
+}: {
+ tierlistInfo: TierlistInfo[];
+ dataMap: Map;
+ game: Game;
+ playtype: Playtype;
+}) {
+ const buckets: TierlistInfo[][] = useMemo(() => {
+ const buckets: TierlistInfo[][] = [];
+ let currentBucket: TierlistInfo[] = [];
+
+ let lastValue;
+ for (const tl of tierlistInfo) {
+ if (lastValue && tl.data.value !== lastValue) {
+ buckets.push(currentBucket);
+ currentBucket = [tl];
+ } else {
+ currentBucket.push(tl);
+ }
+ lastValue = tl.data.value;
+ }
+
+ if (currentBucket.length) {
+ buckets.push(currentBucket);
+ }
+
+ return buckets;
+ }, tierlistInfo);
+
+ const gptConfig = GetGamePTConfig(game, playtype);
+
+ for (const bucket of buckets) {
+ bucket.sort(NumericSOV(x => gptConfig.tierlists.indexOf(x.key), true));
+ }
+
+ if (tierlistInfo.length === 0) {
+ return Got no tierlist data to show you!
;
+ }
+
+ return (
+
+ {buckets.map((bucket, i) => (
+
+
+ {bucket[0].data.value}
+
+
+ {bucket.map((tierlistInfo, i) => {
+ const data = dataMap.get(tierlistInfo.chartID)!;
+
+ const lastKey = bucket[i - 1];
+
+ let statusClass;
+
+ switch (tierlistInfo.achieved) {
+ case AchievedStatuses.ACHIEVED:
+ statusClass = "achieved";
+ break;
+ case AchievedStatuses.FAILED:
+ statusClass = "unachieved";
+ break;
+ case AchievedStatuses.NOT_PLAYED:
+ case AchievedStatuses.SCORE_BASED:
+ statusClass = "";
+ }
+
+ return (
+ <>
+ {lastKey && lastKey.key !== tierlistInfo.key && (
+
+ )}
+
+ {data.__related.song.title} {FormatDifficultyShort(data, game)}
+
+ {tierlistInfo.key}{" "}
+ {tierlistInfo.data.individualDifference && (
+ <>
+
+
+
+
+
+
+
+
+
+ >
+ )}
+
+
+ {tierlistInfo.achieved === AchievedStatuses.NOT_PLAYED
+ ? "Not Played"
+ : tierlistInfo.achieved === AchievedStatuses.SCORE_BASED
+ ? `You have: ${data.__related.pb!.scoreData.grade}`
+ : `You have: ${data.__related.pb!.scoreData.lamp}`}
+
+
+ >
+ );
+ })}
+
+ ))}
+
+ );
+}
+
+interface TierlistInfo {
+ chartID: string;
+ key: GPTTierlists[IDStrings];
+ data: ChartTierlistInfo;
+ achieved: AchievedStatuses;
+}
+
+enum AchievedStatuses {
+ NOT_PLAYED,
+ FAILED,
+ ACHIEVED,
+ SCORE_BASED,
+}
+
+function FolderDatasetToTierlistInfo(
+ folderDataset: FolderDataset,
+ game: Game,
+ playtype: Playtype,
+ options: Partial> & { __hideAchieved: boolean }
+) {
+ const tierlistInfo: TierlistInfo[] = [];
+
+ const tierlistKeys: GPTTierlists[IDStrings][] = [];
+
+ for (const k in options) {
+ const key = k as GPTTierlists[IDStrings];
+ if (options[key]) {
+ tierlistKeys.push(key);
+ }
+ }
+
+ for (const data of folderDataset) {
+ for (const key of tierlistKeys) {
+ if (IsNullish(data.tierlistInfo[key])) {
+ continue;
+ }
+
+ let achieved: AchievedStatuses;
+
+ if (!data.__related.pb) {
+ achieved = AchievedStatuses.NOT_PLAYED;
+ } else {
+ const fn = GetScaleAchievedFn(game, playtype, key);
+
+ if (fn) {
+ achieved = fn(data.__related.pb)
+ ? AchievedStatuses.ACHIEVED
+ : AchievedStatuses.FAILED;
+ } else {
+ achieved = AchievedStatuses.SCORE_BASED;
+ }
+ }
+
+ if (options.__hideAchieved && achieved === AchievedStatuses.ACHIEVED) {
+ continue;
+ }
+
+ tierlistInfo.push({
+ chartID: data.chartID,
+ key,
+ data: data.tierlistInfo[key]!,
+ achieved,
+ });
+ }
+ }
+
+ return tierlistInfo.sort(NumericSOV(x => x.data.value, true));
+}
+
+function FolderInfoHeader({ game, playtype, reqUser, folderDataset, data }: InfoProps) {
+ return (
+
+
+
+ );
+}
+
+function ScoreDistributionChart({ game, playtype, reqUser, folderDataset, data }: InfoProps) {
+ const dataMap = CreateChartIDMap(folderDataset);
+ const gptConfig = GetGamePTConfig(game, playtype);
+
const dataset = [];
- const expScale = 1;
+ const expScale = GetGradeChartExpScale(game);
const expFn = ComposeExpFn(expScale);
const invExpFn = ComposeInverseExpFn(expScale);
@@ -114,70 +441,69 @@ function FolderInfoHeader({
dataset.sort(NumericSOV(x => x.expValue));
- const dataMap = CreateChartIDMap(folderDataset);
-
- const gptConfig = GetGamePTConfig(game, playtype);
-
return (
-
-
-
(
- {
- const data = dataMap.get(d.indexValue as string)!;
+
+
(
+ {
+ const data = dataMap.get(d.indexValue as string)!;
- return (
-
- {data.__related.song.title}
-
- {data.__related.pb?.scoreData.percent.toFixed(2)}%
-
- );
- }}
- />
- )}
- key={"value"}
+ return (
+
+ {data.__related.song.title}
+
+ {data.__related.pb?.scoreData.percent.toFixed(2)}%
+
+ );
+ }}
+ />
+ )}
+ 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;
- colors={k => ChangeOpacity(gptConfig.gradeColours[k.data.grade], 0.5)}
- // @ts-expect-error temp
+ const lgv = invExpFn(x);
- 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 }}
- 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;
- }
+ for (const [i, gradeBnd] of gptConfig.gradeBoundaries.entries()) {
+ if (Math.abs(gradeBnd - lgv) < 0.00005) {
+ nearest = i;
+ break;
}
+ }
- if (nearest === undefined) {
- return null;
- }
+ if (nearest === undefined) {
+ return null;
+ }
- return gptConfig.grades[nearest];
- },
- }}
- axisBottom={null}
- {...DEFAULT_BAR_PROPS}
- />
-
-
+ return gptConfig.grades[nearest];
+ },
+ }}
+ axisBottom={null}
+ {...DEFAULT_BAR_PROPS}
+ labelSkipWidth={40}
+ />
+
);
}
diff --git a/client/src/components/layout/header/SearchBar.tsx b/client/src/components/layout/header/SearchBar.tsx
index 95bea41bf..8cd1817e7 100644
--- a/client/src/components/layout/header/SearchBar.tsx
+++ b/client/src/components/layout/header/SearchBar.tsx
@@ -68,7 +68,9 @@ function GPTSearchResult({
playtype
)} profile, if they have one.`}
>
-
+
+
+
diff --git a/client/src/components/tables/cells/BMSDifficultyCell.tsx b/client/src/components/tables/cells/BMSDifficultyCell.tsx
index 988028bdb..d115dc4a8 100644
--- a/client/src/components/tables/cells/BMSDifficultyCell.tsx
+++ b/client/src/components/tables/cells/BMSDifficultyCell.tsx
@@ -21,7 +21,7 @@ export default function BMSDifficultyCell({
}}
>
{levelText}
-
+
{!chart.isPrimary && (
diff --git a/client/src/components/tables/cells/DifficultyCell.tsx b/client/src/components/tables/cells/DifficultyCell.tsx
index f68e7390e..c122323b7 100644
--- a/client/src/components/tables/cells/DifficultyCell.tsx
+++ b/client/src/components/tables/cells/DifficultyCell.tsx
@@ -30,16 +30,14 @@ export default function DifficultyCell({
}}
>
{!alwaysShort && (
- <>
-
{FormatDifficulty(chart, game)}
- {Object.keys(chart.tierlistInfo).length > 0 && (
-
- )}
- >
+
{FormatDifficulty(chart, game)}
)}
{FormatDifficultyShort(chart, game)}
+ {Object.keys(chart.tierlistInfo).length > 0 && (
+
+ )}
{!chart.isPrimary && (
diff --git a/client/src/components/tables/cells/TierlistInfoPart.tsx b/client/src/components/tables/cells/TierlistInfoPart.tsx
index 4689bd1a6..c44d9bc17 100644
--- a/client/src/components/tables/cells/TierlistInfoPart.tsx
+++ b/client/src/components/tables/cells/TierlistInfoPart.tsx
@@ -2,47 +2,61 @@ 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 } from "tachi-common";
+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 }: { chart: ChartDocument }) {
- const keys = Object.keys(chart.tierlistInfo) as K[];
+export default function TierlistInfoPart({ chart, game }: { chart: ChartDocument; game: Game }) {
+ const keys = GetGamePTConfig(game, chart.playtype).tierlists.filter(e => chart.tierlistInfo[e]);
return (
-
-
-
- | Tierlist |
- Value |
-
-
-
- {keys.map(k => (
-
- | {k} |
-
- {chart.tierlistInfo[k]!.text} ({chart.tierlistInfo[k]!.value})
- {chart.tierlistInfo[k]!.individualDifference && (
- <>
-
-
- >
- )}
- |
-
- ))}
-
-
- }
- >
-
-
{keys.map(k => chart.tierlistInfo[k]!.text).join(" / ")}
+ <>
+
+
+
+
+ | Tierlist |
+ Value |
+
+
+
+ {keys.map(k => (
+
+ | {k} |
+
+ {chart.tierlistInfo[k]!.text} (
+ {chart.tierlistInfo[k]!.value})
+ {chart.tierlistInfo[k]!.individualDifference && (
+ <>
+
+
+ >
+ )}
+ |
+
+ ))}
+
+
+ }
+ >
+
+ {keys.map(k => chart.tierlistInfo[k]!.text).join(" / ")}
+
+
-
+
+
+ {keys.map(k => (
+ <>
+ {chart.tierlistInfo[k]!.text}
+ >
+ ))}
+
+
+ >
);
}
diff --git a/client/src/components/tables/components/FilterDirectivesIndicator.tsx b/client/src/components/tables/components/FilterDirectivesIndicator.tsx
index aba5df0a4..3a3dc5de9 100644
--- a/client/src/components/tables/components/FilterDirectivesIndicator.tsx
+++ b/client/src/components/tables/components/FilterDirectivesIndicator.tsx
@@ -56,7 +56,10 @@ export default function FilterDirectivesIndicator
({
component={
<>
You can use{" "}
-
+
Filter Directives
{" "}
inside this filter box.
@@ -79,7 +82,10 @@ export default function FilterDirectivesIndicator({
This is a slightly advanced feature for{" "}
power users. You can read about it{" "}
- here.
+
+ here
+
+ .
The quick explanation is that you can use directives like{" "}
Title:conflict Score:>=100 to perform advanced filters!
diff --git a/client/src/components/tables/folders/BMSFolderTable.tsx b/client/src/components/tables/folders/BMSFolderTable.tsx
index 6366b6179..8915eb053 100644
--- a/client/src/components/tables/folders/BMSFolderTable.tsx
+++ b/client/src/components/tables/folders/BMSFolderTable.tsx
@@ -31,7 +31,7 @@ export default function BMSFolderTable({
const headers: Header[0]>[] = [
[
"Chart",
- "Ch.",
+ "Chart",
NumericSOV(
x =>
x.tierlistInfo["sgl-EC"]?.value ?? x.tierlistInfo["sgl-HC"]?.value ?? x.levelNum
diff --git a/client/src/components/tables/folders/GenericFolderTable.tsx b/client/src/components/tables/folders/GenericFolderTable.tsx
index 9aa80d8ef..231db1d03 100644
--- a/client/src/components/tables/folders/GenericFolderTable.tsx
+++ b/client/src/components/tables/folders/GenericFolderTable.tsx
@@ -35,7 +35,7 @@ export default function GenericFolderTable({
const gptConfig = GetGamePTConfig(game, playtype);
const headers: Header[] = [
- ["Chart", "Ch.", NumericSOV(x => x.levelNum)],
+ ["Chart", "Chart", NumericSOV(x => x.levelNum)],
FolderIndicatorHeader,
["Song", "Song", StrSOV(x => x.__related.song.title)],
["Score", "Score", NumericSOV(x => x.__related.pb?.scoreData.percent ?? -Infinity)],
diff --git a/client/src/components/tables/folders/IIDXFolderTable.tsx b/client/src/components/tables/folders/IIDXFolderTable.tsx
index c5dc9bd76..a109dfb56 100644
--- a/client/src/components/tables/folders/IIDXFolderTable.tsx
+++ b/client/src/components/tables/folders/IIDXFolderTable.tsx
@@ -39,7 +39,7 @@ export default function IIDXFolderTable({
const headers: Header[0]>[] = [
[
"Chart",
- "Ch.",
+ "Chart",
NumericSOV(
x => x.tierlistInfo["kt-NC"]?.value ?? x.tierlistInfo["kt-HC"]?.value ?? x.levelNum
),
diff --git a/client/src/components/tables/folders/MusecaFolderTable.tsx b/client/src/components/tables/folders/MusecaFolderTable.tsx
index 9dfaafed6..e64ed2624 100644
--- a/client/src/components/tables/folders/MusecaFolderTable.tsx
+++ b/client/src/components/tables/folders/MusecaFolderTable.tsx
@@ -31,7 +31,7 @@ export default function MusecaFolderTable({
playtype: Playtype;
}) {
const headers: Header[0]>[] = [
- ["Chart", "Ch.", NumericSOV(x => x.levelNum)],
+ ["Chart", "Chart", NumericSOV(x => x.levelNum)],
FolderIndicatorHeader,
["Song", "Song", StrSOV(x => x.__related.song.title)],
["Score", "Score", NumericSOV(x => x.__related.pb?.scoreData.percent ?? -Infinity)],
diff --git a/client/src/components/tables/folders/SDVXFolderTable.tsx b/client/src/components/tables/folders/SDVXFolderTable.tsx
index 7c1765691..e4c8d458b 100644
--- a/client/src/components/tables/folders/SDVXFolderTable.tsx
+++ b/client/src/components/tables/folders/SDVXFolderTable.tsx
@@ -30,7 +30,7 @@ export default function SDVXFolderTable({
playtype: Playtype;
}) {
const headers: Header[0]>[] = [
- ["Chart", "Ch.", NumericSOV(x => x.levelNum)],
+ ["Chart", "Chart", NumericSOV(x => x.levelNum)],
FolderIndicatorHeader,
["Song", "Song", StrSOV(x => x.__related.song.title)],
["Score", "Score", NumericSOV(x => x.__related.pb?.scoreData.percent ?? -Infinity)],
diff --git a/client/src/components/tables/pbs/BMSPBTable.tsx b/client/src/components/tables/pbs/BMSPBTable.tsx
index 094bb5608..813c7e28f 100644
--- a/client/src/components/tables/pbs/BMSPBTable.tsx
+++ b/client/src/components/tables/pbs/BMSPBTable.tsx
@@ -48,7 +48,7 @@ export default function BMSPBTable({
const headers: Header[0]>[] = [
[
"Chart",
- "Ch.",
+ "Chart",
NumericSOV(
x =>
x.__related.chart.tierlistInfo["sgl-EC"]?.value ??
diff --git a/client/src/components/tables/pbs/GenericPBTable.tsx b/client/src/components/tables/pbs/GenericPBTable.tsx
index 522dfe368..a9cae2516 100644
--- a/client/src/components/tables/pbs/GenericPBTable.tsx
+++ b/client/src/components/tables/pbs/GenericPBTable.tsx
@@ -40,7 +40,7 @@ export default function GenericPBTable({
const gptConfig = GetGamePTConfig(game, playtype);
const headers: Header[] = [
- ["Chart", "Ch.", NumericSOV(x => x.__related.chart.levelNum)],
+ ["Chart", "Chart", NumericSOV(x => x.__related.chart.levelNum)],
IndicatorHeader,
["Song", "Song", StrSOV(x => x.__related.song.title)],
["Score", "Score", NumericSOV(x => x.scoreData.percent)],
diff --git a/client/src/components/tables/pbs/IIDXPBTable.tsx b/client/src/components/tables/pbs/IIDXPBTable.tsx
index fcf856faa..6f04da17c 100644
--- a/client/src/components/tables/pbs/IIDXPBTable.tsx
+++ b/client/src/components/tables/pbs/IIDXPBTable.tsx
@@ -48,7 +48,7 @@ export default function IIDXPBTable({
const headers: Header[0]>[] = [
[
"Chart",
- "Ch.",
+ "Chart",
NumericSOV(
x =>
x.__related.chart.tierlistInfo["kt-NC"]?.value ??
diff --git a/client/src/components/tables/pbs/MusecaPBTable.tsx b/client/src/components/tables/pbs/MusecaPBTable.tsx
index 19f64330b..0e7fb6af9 100644
--- a/client/src/components/tables/pbs/MusecaPBTable.tsx
+++ b/client/src/components/tables/pbs/MusecaPBTable.tsx
@@ -34,7 +34,7 @@ export default function MusecaPBTable({
playtype: "Single";
}) {
const headers: Header[0]>[] = [
- ["Chart", "Ch.", NumericSOV(x => x.__related.chart.levelNum)],
+ ["Chart", "Chart", NumericSOV(x => x.__related.chart.levelNum)],
IndicatorHeader,
["Song", "Song", StrSOV(x => x.__related.song.title)],
["Score", "Score", NumericSOV(x => x.scoreData.percent)],
diff --git a/client/src/components/tables/pbs/SDVXPBTable.tsx b/client/src/components/tables/pbs/SDVXPBTable.tsx
index b54ec7307..aad0faa92 100644
--- a/client/src/components/tables/pbs/SDVXPBTable.tsx
+++ b/client/src/components/tables/pbs/SDVXPBTable.tsx
@@ -33,7 +33,7 @@ export default function SDVXPBTable({
playtype: "7K" | "14K";
}) {
const headers: Header[0]>[] = [
- ["Chart", "Ch.", NumericSOV(x => x.__related.chart.levelNum)],
+ ["Chart", "Chart", NumericSOV(x => x.__related.chart.levelNum)],
IndicatorHeader,
["Song", "Song", StrSOV(x => x.__related.song.title)],
["Score", "Score", NumericSOV(x => x.scoreData.percent)],
diff --git a/client/src/components/tables/scores/BMSScoreTable.tsx b/client/src/components/tables/scores/BMSScoreTable.tsx
index 1d2099298..eae13e668 100644
--- a/client/src/components/tables/scores/BMSScoreTable.tsx
+++ b/client/src/components/tables/scores/BMSScoreTable.tsx
@@ -34,7 +34,7 @@ export default function BMSScoreTable({
[
[
"Chart",
- "Ch.",
+ "Chart",
NumericSOV(
x =>
x.__related.chart.tierlistInfo["sgl-EC"]?.value ??
diff --git a/client/src/components/tables/scores/GenericScoreTable.tsx b/client/src/components/tables/scores/GenericScoreTable.tsx
index c6f25635c..af1eee4eb 100644
--- a/client/src/components/tables/scores/GenericScoreTable.tsx
+++ b/client/src/components/tables/scores/GenericScoreTable.tsx
@@ -36,7 +36,7 @@ export default function GenericScoreTable({
dataset={dataset}
pageLen={pageLen}
headers={[
- ["Chart", "Ch.", NumericSOV(x => x.__related.chart.levelNum)],
+ ["Chart", "Chart", NumericSOV(x => x.__related.chart.levelNum)],
IndicatorHeader,
["Song", "Song", StrSOV(x => x.__related.song.title)],
["Score", "Score", NumericSOV(x => x.scoreData.percent)],
diff --git a/client/src/components/tables/scores/IIDXScoreTable.tsx b/client/src/components/tables/scores/IIDXScoreTable.tsx
index 09e009d29..e36aee557 100644
--- a/client/src/components/tables/scores/IIDXScoreTable.tsx
+++ b/client/src/components/tables/scores/IIDXScoreTable.tsx
@@ -42,7 +42,7 @@ export default function IIDXScoreTable({
[
[
"Chart",
- "Ch.",
+ "Chart",
NumericSOV(
x =>
x.__related.chart.tierlistInfo["kt-NC"]?.value ??
diff --git a/client/src/components/tables/scores/MusecaScoreTable.tsx b/client/src/components/tables/scores/MusecaScoreTable.tsx
index 0b6b70e8f..4cd7a98e0 100644
--- a/client/src/components/tables/scores/MusecaScoreTable.tsx
+++ b/client/src/components/tables/scores/MusecaScoreTable.tsx
@@ -30,7 +30,7 @@ export default function MusecaScoreTable({
pageLen={pageLen}
headers={
[
- ["Chart", "Ch.", NumericSOV(x => x.__related.chart.levelNum)],
+ ["Chart", "Chart", NumericSOV(x => x.__related.chart.levelNum)],
IndicatorHeader,
["Song", "Song", StrSOV(x => x.__related.song.title)],
["Score", "Score", NumericSOV(x => x.scoreData.percent)],
diff --git a/client/src/components/tables/scores/SDVXScoreTable.tsx b/client/src/components/tables/scores/SDVXScoreTable.tsx
index 300a72119..8dd8a6abf 100644
--- a/client/src/components/tables/scores/SDVXScoreTable.tsx
+++ b/client/src/components/tables/scores/SDVXScoreTable.tsx
@@ -30,7 +30,7 @@ export default function SDVXScoreTable({
pageLen={pageLen}
headers={
[
- ["Chart", "Ch.", NumericSOV(x => x.__related.chart.levelNum)],
+ ["Chart", "Chart", NumericSOV(x => x.__related.chart.levelNum)],
IndicatorHeader,
["Song", "Song", StrSOV(x => x.__related.song.title)],
["Score", "Score", NumericSOV(x => x.scoreData.percent)],
diff --git a/client/src/components/user/UserHeader.tsx b/client/src/components/user/UserHeader.tsx
index f11112937..78f004429 100644
--- a/client/src/components/user/UserHeader.tsx
+++ b/client/src/components/user/UserHeader.tsx
@@ -64,7 +64,7 @@ export function UserHeaderBody({ reqUser }: { reqUser: PublicUserDocument }) {
href="https://steamcommunity.com/id/"
mode="steam"
/>
-
+
= {
+ iidx: 2,
+ bms: 2,
+ chunithm: 1,
+ ddr: 1,
+ gitadora: 1,
+ maimai: 1,
+ museca: 5,
+ sdvx: 5,
+ usc: 5,
+};
+
+export function GetGradeChartExpScale(game: Game) {
+ return scales[game];
+}
diff --git a/client/src/util/splashes.ts b/client/src/util/splashes.ts
index e893c9f08..e957f3d7f 100644
--- a/client/src/util/splashes.ts
+++ b/client/src/util/splashes.ts
@@ -63,6 +63,7 @@ export const loggedInSplashes = [
"Play ULTRAKILL.",
"A Cutting-Edge Score Tracker (heh).",
"Winnipeg is a warm, nice place to live.",
+ "I accidentally the score tracker.",
];
// Stuff that's bad for the kids, you know?
@@ -70,4 +71,5 @@ export const contentiousSplashes = [
"Formerly Chucks",
"Node.js Is Bad Ass Rock Star Tech",
"i'm obligated to ask for you to seek immediate psychological attention",
+ "Is IIDX still a good game or are you all basically stupid?",
];
diff --git a/client/src/util/tierlist.ts b/client/src/util/tierlist.ts
new file mode 100644
index 000000000..595296daf
--- /dev/null
+++ b/client/src/util/tierlist.ts
@@ -0,0 +1,51 @@
+import { Game, GPTTierlists, IDStrings, IIDX_LAMPS, ScoreDocument } from "tachi-common";
+import { Playtype } from "types/tachi";
+
+type ScaleAchievedFns = {
+ [I in IDStrings]: {
+ [K in GPTTierlists[I]]: ((k: ScoreDocument) => boolean) | null;
+ };
+};
+
+const ScaleNameAchievedFns: ScaleAchievedFns = {
+ "bms:14K": {
+ "sgl-EC": k => k.scoreData.lampIndex >= IIDX_LAMPS.EASY_CLEAR,
+ "sgl-HC": k => k.scoreData.lampIndex >= IIDX_LAMPS.HARD_CLEAR,
+ },
+ "bms:7K": {
+ "sgl-EC": k => k.scoreData.lampIndex >= IIDX_LAMPS.EASY_CLEAR,
+ "sgl-HC": k => k.scoreData.lampIndex >= IIDX_LAMPS.HARD_CLEAR,
+ },
+ "chunithm:Single": {},
+ "ddr:DP": {},
+ "ddr:SP": {},
+ "gitadora:Dora": {},
+ "gitadora:Gita": {},
+ "iidx:DP": {},
+ "iidx:SP": {
+ "kt-NC": k => k.scoreData.lampIndex >= IIDX_LAMPS.CLEAR,
+ "kt-HC": k => k.scoreData.lampIndex >= IIDX_LAMPS.HARD_CLEAR,
+ "kt-EXHC": k => k.scoreData.lampIndex >= IIDX_LAMPS.EX_HARD_CLEAR,
+ },
+ "maimai:Single": {},
+ "museca:Single": {
+ "tachi-score": null,
+ },
+ "sdvx:Single": {},
+ "usc:Single": {},
+};
+
+export function GetScaleAchievedFn(
+ game: Game,
+ playtype: Playtype,
+ tierlist: GPTTierlists[IDStrings]
+) {
+ const scl = ScaleNameAchievedFns[`${game}:${playtype}` as IDStrings];
+
+ if (!scl) {
+ throw new Error(`Invalid game + pt combination ${game}:${playtype}. Can't find scale.`);
+ }
+
+ // @ts-expect-error hack haha
+ return scl[tierlist];
+}