From 6c8b091796729c07628f2da2d6319c760877df08 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Wed, 5 Jan 2022 08:50:02 +0000 Subject: [PATCH] Jubeat support. --- client/package.json | 2 +- client/pnpm-lock.yaml | 8 +- .../folder/FolderScoreDistributionChart.tsx | 168 +++++++++--------- .../tables/cells/DifficultyCell.tsx | 2 +- .../tables/cells/JubeatJudgementCell.tsx | 50 ++++++ .../game-core-cells/JubeatCoreCells.tsx | 25 +++ .../components/tables/pbs/JubeatPBTable.tsx | 109 ++++++++++++ client/src/components/tables/pbs/PBTable.tsx | 3 + .../components/tables/pbs/WaccaPBTable.tsx | 2 +- .../tables/scores/JubeatScoreTable.tsx | 95 ++++++++++ .../components/tables/scores/ScoreTable.tsx | 3 + client/src/lib/config.ts | 2 +- client/src/util/grade-deltas.ts | 4 + client/src/util/misc.ts | 6 +- client/src/util/scales.ts | 1 + client/src/util/tierlist.ts | 1 + 16 files changed, 388 insertions(+), 93 deletions(-) create mode 100644 client/src/components/tables/cells/JubeatJudgementCell.tsx create mode 100644 client/src/components/tables/game-core-cells/JubeatCoreCells.tsx create mode 100644 client/src/components/tables/pbs/JubeatPBTable.tsx create mode 100644 client/src/components/tables/scores/JubeatScoreTable.tsx diff --git a/client/package.json b/client/package.json index 002a0c896..de2e2b72f 100644 --- a/client/package.json +++ b/client/package.json @@ -83,7 +83,7 @@ "resolve": "1.12.0", "resolve-url-loader": "3.1.0", "socicon": "3.0.5", - "tachi-common": "0.4.12" + "tachi-common": "0.4.21" }, "devDependencies": { "@types/humanize-duration": "3.25.1", diff --git a/client/pnpm-lock.yaml b/client/pnpm-lock.yaml index ea687e517..ed6afe5fb 100644 --- a/client/pnpm-lock.yaml +++ b/client/pnpm-lock.yaml @@ -85,7 +85,7 @@ specifiers: serve: 11.2.0 socicon: 3.0.5 style-loader: 1.0.0 - tachi-common: 0.4.12 + tachi-common: 0.4.21 terser-webpack-plugin: 1.4.1 ts-pnp: 1.1.4 typescript: 4.3.5 @@ -158,7 +158,7 @@ dependencies: resolve: 1.12.0 resolve-url-loader: 3.1.0 socicon: 3.0.5 - tachi-common: 0.4.12 + tachi-common: 0.4.21 devDependencies: '@types/humanize-duration': 3.25.1 @@ -11277,8 +11277,8 @@ packages: strip-ansi: 6.0.0 dev: true - /tachi-common/0.4.12: - resolution: {integrity: sha512-PsmVP5oJNnCvx3Omc6s6TVSheHmmiG/MJJAtUtcdeG8fwx38ulevVBiB2lSLTUldAh+HMtRvg5m0Qf/ts1QlwQ==} + /tachi-common/0.4.21: + resolution: {integrity: sha512-Rlworn5QevIuTbZD2qWrsAFSE2dUhcrJQ2iQfo5PtH8WZ95YbsBeOo0D1WfLUBH9YqASQuS3O7lZcwZ0Tz3wvg==} dependencies: monk: 7.3.4 transitivePeerDependencies: diff --git a/client/src/components/game/folder/FolderScoreDistributionChart.tsx b/client/src/components/game/folder/FolderScoreDistributionChart.tsx index 6df7a4003..0c36b438e 100644 --- a/client/src/components/game/folder/FolderScoreDistributionChart.tsx +++ b/client/src/components/game/folder/FolderScoreDistributionChart.tsx @@ -59,99 +59,99 @@ export default function FolderScoreDistributionChart({ ); } -function ScoreDistributionChart({ game, folderDataset, playtype, view }: Props) { - if (view === "grade") { - return ; - } +// function ScoreDistributionChart({ game, folderDataset, playtype, view }: Props) { +// if (view === "grade") { +// return ; +// } - return <>LAMP TODO; -} +// return <>LAMP TODO; +// } -function ScoreBarChart({ game, playtype, folderDataset }: Omit) { - const dataMap = CreateChartIDMap(folderDataset); - const gptConfig = GetGamePTConfig(game, playtype); +// function ScoreBarChart({ game, playtype, folderDataset }: Omit) { +// 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(