) {
+// const dataMap = CreateChartIDMap(folderDataset);
+// const gptConfig = GetGamePTConfig(game, playtype);
- const expScale = GetGradeChartExpScale(game);
- const expFn = ComposeExpFn(expScale);
- const invExpFn = ComposeInverseExpFn(expScale);
+// const expScale = GetGradeChartExpScale(game);
+// const expFn = ComposeExpFn(expScale);
+// const invExpFn = ComposeInverseExpFn(expScale);
- const dataset = [];
+// 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,
- });
- }
+// 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));
+// dataset.sort(NumericSOV(x => x.expValue));
- return (
-
-
(
- {
- const data = dataMap.get(d.indexValue as string)!;
+// return (
+//
+//
(
+// {
+// const data = dataMap.get(d.indexValue as string)!;
- 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;
+// 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;
- const lgv = invExpFn(x);
+// 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}
- labelSkipWidth={40}
- />
-
- );
-}
+// return gptConfig.grades[nearest];
+// },
+// }}
+// axisBottom={null}
+// {...DEFAULT_BAR_PROPS}
+// labelSkipWidth={40}
+// />
+//
+// );
+// }
diff --git a/client/src/components/tables/cells/DifficultyCell.tsx b/client/src/components/tables/cells/DifficultyCell.tsx
index f95b7ca22..0cae68b0a 100644
--- a/client/src/components/tables/cells/DifficultyCell.tsx
+++ b/client/src/components/tables/cells/DifficultyCell.tsx
@@ -45,7 +45,7 @@ export default function DifficultyCell({
{FormatDifficultyShort(chart, game)}
- {chart.levelNum.toString() !== chart.level && (
+ {chart.levelNum.toString() !== chart.level && !chart.level.endsWith(".0") && (
{chart.levelNum.toFixed(2)}
)}
{"isHot" in chart.data && chart.data.isHot && (
diff --git a/client/src/components/tables/cells/JubeatJudgementCell.tsx b/client/src/components/tables/cells/JubeatJudgementCell.tsx
new file mode 100644
index 000000000..3b2c28a9f
--- /dev/null
+++ b/client/src/components/tables/cells/JubeatJudgementCell.tsx
@@ -0,0 +1,50 @@
+import React from "react";
+import { COLOUR_SET, PBScoreDocument, ScoreDocument } from "tachi-common";
+import { IsNullish } from "util/misc";
+
+export default function JubeatJudgementCell({
+ score,
+}: {
+ score: ScoreDocument<"jubeat:Single"> | PBScoreDocument<"jubeat:Single">;
+}) {
+ // even if we dont have judgement data, we know what they got.
+ if (score.scoreData.lamp === "EXCELLENT") {
+ return (
+
+
+ 0-
+ 0-
+ 0-
+ 0
+
+ |
+ );
+ }
+
+ // safety
+ if (score.scoreData.lamp === "FULL COMBO") {
+ score.scoreData.judgements.miss = 0;
+ }
+
+ const judgements = score.scoreData.judgements;
+
+ if (
+ IsNullish(judgements.miss) ||
+ IsNullish(judgements.great) ||
+ IsNullish(judgements.good) ||
+ IsNullish(judgements.poor)
+ ) {
+ return No Data. | ;
+ }
+
+ return (
+
+
+ {judgements.great}-
+ {judgements.good}-
+ {judgements.poor}-
+ {judgements.miss}
+
+ |
+ );
+}
diff --git a/client/src/components/tables/game-core-cells/JubeatCoreCells.tsx b/client/src/components/tables/game-core-cells/JubeatCoreCells.tsx
new file mode 100644
index 000000000..b5c0e4bc1
--- /dev/null
+++ b/client/src/components/tables/game-core-cells/JubeatCoreCells.tsx
@@ -0,0 +1,25 @@
+import React from "react";
+import { PBScoreDocument, ScoreDocument } from "tachi-common";
+import { IsNullish } from "util/misc";
+import LampCell from "../cells/LampCell";
+import MillionsScoreCell from "../cells/MillionsScoreCell";
+import JubeatJudgementCell from "../cells/JubeatJudgementCell";
+
+export default function JubeatCoreCells({
+ sc,
+}: {
+ sc: ScoreDocument<"jubeat:Single"> | PBScoreDocument<"jubeat:Single">;
+}) {
+ return (
+ <>
+
+
+
+
+ {!IsNullish(sc.calculatedData.jubility)
+ ? sc.calculatedData.jubility!.toFixed(2)
+ : "N/A"}
+ |
+ >
+ );
+}
diff --git a/client/src/components/tables/pbs/JubeatPBTable.tsx b/client/src/components/tables/pbs/JubeatPBTable.tsx
new file mode 100644
index 000000000..1df8645d5
--- /dev/null
+++ b/client/src/components/tables/pbs/JubeatPBTable.tsx
@@ -0,0 +1,109 @@
+import React from "react";
+import { PBDataset } from "types/tables";
+import { NumericSOV } from "util/sorts";
+import { CreateDefaultPBSearchParams } from "util/tables/create-search";
+import { GetPBLeadingHeaders } from "util/tables/get-pb-leaders";
+import IndexCell from "../cells/IndexCell";
+import RankingCell from "../cells/RankingCell";
+import TimestampCell from "../cells/TimestampCell";
+import DropdownRow from "../components/DropdownRow";
+import TachiTable, { Header } from "../components/TachiTable";
+import { usePBState } from "../components/UseScoreState";
+import GenericPBDropdown from "../dropdowns/GenericPBDropdown";
+import JubeatCoreCells from "../game-core-cells/JubeatCoreCells";
+import PBLeadingRows from "./PBLeadingRows";
+
+export default function JubeatPBTable({
+ dataset,
+ indexCol = true,
+ showPlaycount = false,
+ showUser = false,
+ showChart = true,
+ playtype,
+}: {
+ dataset: PBDataset<"jubeat:Single">;
+ indexCol?: boolean;
+ showPlaycount?: boolean;
+ showUser?: boolean;
+ showChart?: boolean;
+ playtype: "Single";
+}) {
+ const headers: Header[0]>[] = [
+ ...GetPBLeadingHeaders(showUser, showChart, [
+ "Chart",
+ "Chart",
+ NumericSOV(x => x.__related.chart.levelNum),
+ ]),
+ ["Score", "Score", NumericSOV(x => x.scoreData.percent)],
+ ["Judgements", "Judgements", NumericSOV(x => x.scoreData.percent)],
+ ["Lamp", "Lamp", NumericSOV(x => x.scoreData.lampIndex)],
+ ["Jubility", "Jub.", NumericSOV(x => x.calculatedData.jubility ?? 0)],
+ ["Site Ranking", "Site Rank", NumericSOV(x => x.rankingData.rank)],
+ ["Last Raised", "Last Raised", NumericSOV(x => x.timeAchieved ?? 0)],
+ ];
+
+ if (showPlaycount) {
+ headers.push(["Playcount", "Plays", NumericSOV(x => x.__playcount ?? 0)]);
+ }
+
+ if (indexCol) {
+ headers.unshift(["#", "#", NumericSOV(x => x.__related.index)]);
+ }
+
+ return (
+ (
+
+ )}
+ />
+ );
+}
+
+function Row({
+ pb,
+ indexCol,
+ showPlaycount,
+ showChart,
+ showUser,
+}: {
+ pb: PBDataset<"jubeat:Single">[0];
+ indexCol: boolean;
+ showPlaycount: boolean;
+ showChart: boolean;
+ showUser: boolean;
+}) {
+ const scoreState = usePBState(pb);
+
+ return (
+
+ }
+ >
+ {indexCol && }
+
+
+
+
+ {showPlaycount && {pb.__playcount ?? 0} | }
+
+ );
+}
diff --git a/client/src/components/tables/pbs/PBTable.tsx b/client/src/components/tables/pbs/PBTable.tsx
index d32c46d6c..b0e403ae1 100644
--- a/client/src/components/tables/pbs/PBTable.tsx
+++ b/client/src/components/tables/pbs/PBTable.tsx
@@ -5,6 +5,7 @@ import { PBDataset } from "types/tables";
import BMSPBTable from "./BMSPBTable";
import GenericPBTable from "./GenericPBTable";
import IIDXPBTable from "./IIDXPBTable";
+import JubeatPBTable from "./JubeatPBTable";
import MusecaPBTable from "./MusecaPBTable";
import PopnPBTable from "./PopnPBTable";
import SDVXLikePBTable from "./SDVXLikePBTable";
@@ -56,6 +57,8 @@ export default function PBTable({
return ;
} else if (game === "popn") {
return ;
+ } else if (game === "jubeat") {
+ return ;
}
return ;
diff --git a/client/src/components/tables/pbs/WaccaPBTable.tsx b/client/src/components/tables/pbs/WaccaPBTable.tsx
index 17e9794d4..1d9d7d30e 100644
--- a/client/src/components/tables/pbs/WaccaPBTable.tsx
+++ b/client/src/components/tables/pbs/WaccaPBTable.tsx
@@ -26,7 +26,7 @@ export default function WACCAPBTable({
showPlaycount?: boolean;
showUser?: boolean;
showChart?: boolean;
- playtype: "9B";
+ playtype: "Single";
}) {
const headers: Header[0]>[] = [
...GetPBLeadingHeaders(showUser, showChart, [
diff --git a/client/src/components/tables/scores/JubeatScoreTable.tsx b/client/src/components/tables/scores/JubeatScoreTable.tsx
new file mode 100644
index 000000000..8812ba118
--- /dev/null
+++ b/client/src/components/tables/scores/JubeatScoreTable.tsx
@@ -0,0 +1,95 @@
+import React from "react";
+import { integer, Playtypes, PublicUserDocument } from "tachi-common";
+import { ScoreDataset } from "types/tables";
+import { NumericSOV, StrSOV } from "util/sorts";
+import { CreateDefaultScoreSearchParams } from "util/tables/create-search";
+import DifficultyCell from "../cells/DifficultyCell";
+import IndicatorsCell from "../cells/IndicatorsCell";
+import TimestampCell from "../cells/TimestampCell";
+import TitleCell from "../cells/TitleCell";
+import UserCell from "../cells/UserCell";
+import DropdownRow from "../components/DropdownRow";
+import TachiTable, { Header } from "../components/TachiTable";
+import { useScoreState } from "../components/UseScoreState";
+import GenericScoreDropdown from "../dropdowns/GenericScoreDropdown";
+import JubeatCoreCells from "../game-core-cells/JubeatCoreCells";
+import WACCACoreCells from "../game-core-cells/WACCACoreCells";
+import IndicatorHeader from "../headers/IndicatorHeader";
+
+export default function JubeatScoreTable({
+ reqUser,
+ dataset,
+ pageLen,
+ userCol = false,
+}: {
+ reqUser: PublicUserDocument;
+ dataset: ScoreDataset<"jubeat:Single">;
+ pageLen?: integer;
+ playtype: Playtypes["jubeat"];
+ userCol: boolean;
+}) {
+ const headers: Header[0]>[] = [
+ ["Chart", "Chart", NumericSOV(x => x.__related.chart.levelNum)],
+ IndicatorHeader,
+ ["Song", "Song", StrSOV(x => x.__related.song.title)],
+ ["Score", "Score", NumericSOV(x => x.scoreData.percent)],
+ ["Judgements", "Judgements", NumericSOV(x => x.scoreData.percent)],
+ ["Lamp", "Lamp", NumericSOV(x => x.scoreData.lampIndex)],
+ ["Jubility", "Jub.", NumericSOV(x => x.calculatedData.jubility ?? 0)],
+
+ ["Timestamp", "Timestamp", NumericSOV(x => x.timeAchieved ?? 0)],
+ ];
+
+ if (userCol) {
+ headers.unshift(["User", "User", StrSOV(x => x.__related.user.username)]);
+ }
+
+ return (
+
}
+ />
+ );
+}
+
+function Row({
+ sc,
+ userCol,
+}: {
+ sc: ScoreDataset<"jubeat:Single">[0];
+ reqUser: PublicUserDocument;
+ userCol: boolean;
+}) {
+ const scoreState = useScoreState(sc);
+
+ return (
+
+ }
+ >
+ {userCol && }
+
+
+
+
+
+
+ );
+}
diff --git a/client/src/components/tables/scores/ScoreTable.tsx b/client/src/components/tables/scores/ScoreTable.tsx
index e7b94fe29..edae8b179 100644
--- a/client/src/components/tables/scores/ScoreTable.tsx
+++ b/client/src/components/tables/scores/ScoreTable.tsx
@@ -5,6 +5,7 @@ import { ScoreDataset } from "types/tables";
import BMSScoreTable from "./BMSScoreTable";
import GenericScoreTable from "./GenericScoreTable";
import IIDXScoreTable from "./IIDXScoreTable";
+import JubeatScoreTable from "./JubeatScoreTable";
import MusecaScoreTable from "./MusecaScoreTable";
import SDVXLikeScoreTable from "./SDVXLikeScoreTable";
@@ -36,6 +37,8 @@ export default function ScoreTable({
return ;
} else if (game === "museca") {
return ;
+ } else if (game === "jubeat") {
+ return ;
}
return ;
diff --git a/client/src/lib/config.ts b/client/src/lib/config.ts
index 6c446361d..119850d00 100644
--- a/client/src/lib/config.ts
+++ b/client/src/lib/config.ts
@@ -21,7 +21,7 @@ if (mode === "ktchi") {
type: "ktchi",
// TODO: #78 This should inherit in some way from the server config. Can we reflect
// this out somehow?
- supportedGames: ["iidx", "chunithm", "museca", "sdvx", "wacca", "popn"],
+ supportedGames: ["iidx", "chunithm", "museca", "sdvx", "wacca", "popn", "jubeat"],
importTypes: [
"api/arc-iidx",
"api/arc-sdvx",
diff --git a/client/src/util/grade-deltas.ts b/client/src/util/grade-deltas.ts
index b8ccd6eed..908eda44e 100644
--- a/client/src/util/grade-deltas.ts
+++ b/client/src/util/grade-deltas.ts
@@ -9,6 +9,10 @@ export function AbsoluteGradeDelta(
) {
const gptConfig = GetGamePTConfig(game, playtype);
+ if (!gptConfig.gradeBoundaries) {
+ return 0;
+ }
+
const maxScore = Math.round(score * (100 / percent));
const gradeIndex =
diff --git a/client/src/util/misc.ts b/client/src/util/misc.ts
index 4b94fdb99..954bea2f3 100644
--- a/client/src/util/misc.ts
+++ b/client/src/util/misc.ts
@@ -52,8 +52,12 @@ export function FormatGPTRating(
game: Game,
playtype: Playtype,
key: ScoreCalculatedDataLookup[IDStrings],
- value: number
+ value: number | null
) {
+ if (value === null) {
+ return "No Data.";
+ }
+
const gptConfig = GetGamePTConfig(game, playtype);
if (gptConfig.scoreRatingAlgFormatters[key]) {
diff --git a/client/src/util/scales.ts b/client/src/util/scales.ts
index 98411ba5f..5e51aa55b 100644
--- a/client/src/util/scales.ts
+++ b/client/src/util/scales.ts
@@ -12,6 +12,7 @@ const scales: Record = {
usc: 5,
wacca: 5,
popn: 4,
+ jubeat: 5,
};
export function GetGradeChartExpScale(game: Game) {
diff --git a/client/src/util/tierlist.ts b/client/src/util/tierlist.ts
index c1d6d6641..ab87d58b9 100644
--- a/client/src/util/tierlist.ts
+++ b/client/src/util/tierlist.ts
@@ -36,6 +36,7 @@ const ScaleNameAchievedFns: ScaleAchievedFns = {
"usc:Keyboard": {},
"wacca:Single": {},
"popn:9B": {},
+ "jubeat:Single": {},
};
export function GetScaleAchievedFn(