Jubeat support.

This commit is contained in:
zkldi
2022-02-09 02:56:33 +00:00
parent 819217d364
commit 6c8b091796
16 changed files with 388 additions and 93 deletions
+1 -1
View File
@@ -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",
+4 -4
View File
@@ -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:
@@ -59,99 +59,99 @@ export default function FolderScoreDistributionChart({
);
}
function ScoreDistributionChart({ game, folderDataset, playtype, view }: Props) {
if (view === "grade") {
return <ScoreBarChart game={game} playtype={playtype} folderDataset={folderDataset} />;
}
// function ScoreDistributionChart({ game, folderDataset, playtype, view }: Props) {
// if (view === "grade") {
// return <ScoreBarChart game={game} playtype={playtype} folderDataset={folderDataset} />;
// }
return <>LAMP TODO</>;
}
// return <>LAMP TODO</>;
// }
function ScoreBarChart({ game, playtype, folderDataset }: Omit<Props, "view">) {
const dataMap = CreateChartIDMap(folderDataset);
const gptConfig = GetGamePTConfig(game, playtype);
// 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 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 (
<div style={{ height: 400 }}>
<ResponsiveBar
indexBy="chartID"
tooltip={d => (
<BarChartTooltip
point={d}
renderFn={d => {
const data = dataMap.get(d.indexValue as string)!;
// 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;
// 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);
// 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}
/>
</div>
);
}
// return gptConfig.grades[nearest];
// },
// }}
// axisBottom={null}
// {...DEFAULT_BAR_PROPS}
// labelSkipWidth={40}
// />
// </div>
// );
// }
@@ -45,7 +45,7 @@ export default function DifficultyCell({
<span className={!alwaysShort ? "d-lg-none" : ""}>
{FormatDifficultyShort(chart, game)}
</span>
{chart.levelNum.toString() !== chart.level && (
{chart.levelNum.toString() !== chart.level && !chart.level.endsWith(".0") && (
<Muted>{chart.levelNum.toFixed(2)}</Muted>
)}
{"isHot" in chart.data && chart.data.isHot && (
@@ -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 (
<td>
<strong>
<span style={{ color: COLOUR_SET.gold }}>0</span>-
<span style={{ color: COLOUR_SET.blue }}>0</span>-
<span style={{ color: COLOUR_SET.purple }}>0</span>-
<span style={{ color: COLOUR_SET.red }}>0</span>
</strong>
</td>
);
}
// 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 <td>No Data.</td>;
}
return (
<td>
<strong>
<span style={{ color: COLOUR_SET.gold }}>{judgements.great}</span>-
<span style={{ color: COLOUR_SET.blue }}>{judgements.good}</span>-
<span style={{ color: COLOUR_SET.purple }}>{judgements.poor}</span>-
<span style={{ color: COLOUR_SET.red }}>{judgements.miss}</span>
</strong>
</td>
);
}
@@ -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 (
<>
<MillionsScoreCell score={sc} />
<JubeatJudgementCell score={sc} />
<LampCell score={sc} />
<td>
{!IsNullish(sc.calculatedData.jubility)
? sc.calculatedData.jubility!.toFixed(2)
: "N/A"}
</td>
</>
);
}
@@ -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<PBDataset<"jubeat:Single">[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 (
<TachiTable
dataset={dataset}
headers={headers}
entryName="PBs"
searchFunctions={CreateDefaultPBSearchParams("jubeat", playtype)}
defaultSortMode={indexCol ? "#" : undefined}
rowFunction={pb => (
<Row
pb={pb}
key={`${pb.chartID}:${pb.userID}`}
indexCol={indexCol}
showPlaycount={showPlaycount}
showChart={showChart}
showUser={showUser}
/>
)}
/>
);
}
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 (
<DropdownRow
dropdown={
<GenericPBDropdown
chart={pb.__related.chart}
userID={pb.userID}
game={pb.game}
playtype={pb.playtype}
scoreState={scoreState}
/>
}
>
{indexCol && <IndexCell index={pb.__related.index} />}
<PBLeadingRows {...{ showUser, showChart, pb, scoreState }} />
<JubeatCoreCells sc={pb} />
<RankingCell rankingData={pb.rankingData} />
<TimestampCell time={pb.timeAchieved} />
{showPlaycount && <td>{pb.__playcount ?? 0}</td>}
</DropdownRow>
);
}
@@ -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 <WACCAPBTable {...props} />;
} else if (game === "popn") {
return <PopnPBTable {...props} />;
} else if (game === "jubeat") {
return <JubeatPBTable {...props} />;
}
return <GenericPBTable {...props} game={game} playtype={playtype} />;
@@ -26,7 +26,7 @@ export default function WACCAPBTable({
showPlaycount?: boolean;
showUser?: boolean;
showChart?: boolean;
playtype: "9B";
playtype: "Single";
}) {
const headers: Header<PBDataset<"wacca:Single">[0]>[] = [
...GetPBLeadingHeaders(showUser, showChart, [
@@ -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<ScoreDataset<"jubeat:Single">[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 (
<TachiTable
dataset={dataset}
pageLen={pageLen}
headers={headers}
entryName="Scores"
searchFunctions={CreateDefaultScoreSearchParams("jubeat", "Single")}
rowFunction={sc => <Row key={sc.scoreID} sc={sc} reqUser={reqUser} userCol={userCol} />}
/>
);
}
function Row({
sc,
userCol,
}: {
sc: ScoreDataset<"jubeat:Single">[0];
reqUser: PublicUserDocument;
userCol: boolean;
}) {
const scoreState = useScoreState(sc);
return (
<DropdownRow
dropdown={
<GenericScoreDropdown
chart={sc.__related.chart}
user={sc.__related.user}
game={sc.game}
thisScore={sc}
playtype={sc.playtype}
scoreState={scoreState}
/>
}
>
{userCol && <UserCell game={sc.game} playtype={sc.playtype} user={sc.__related.user} />}
<DifficultyCell chart={sc.__related.chart} game="jubeat" />
<IndicatorsCell highlight={scoreState.highlight} />
<TitleCell
song={sc.__related.song}
comment={sc.comment}
chart={sc.__related.chart}
game="jubeat"
/>
<JubeatCoreCells sc={sc} />
<TimestampCell time={sc.timeAchieved} service={sc.service} />
</DropdownRow>
);
}
@@ -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 <GenericScoreTable {...props} game={game} playtype={playtype} showScore={false} />;
} else if (game === "museca") {
return <MusecaScoreTable {...props} />;
} else if (game === "jubeat") {
return <JubeatScoreTable {...props} />;
}
return <GenericScoreTable {...props} game={game} playtype={playtype} />;
+1 -1
View File
@@ -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",
+4
View File
@@ -9,6 +9,10 @@ export function AbsoluteGradeDelta<I extends IDStrings = IDStrings>(
) {
const gptConfig = GetGamePTConfig(game, playtype);
if (!gptConfig.gradeBoundaries) {
return 0;
}
const maxScore = Math.round(score * (100 / percent));
const gradeIndex =
+5 -1
View File
@@ -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]) {
+1
View File
@@ -12,6 +12,7 @@ const scales: Record<Game, number> = {
usc: 5,
wacca: 5,
popn: 4,
jubeat: 5,
};
export function GetGradeChartExpScale(game: Game) {
+1
View File
@@ -36,6 +36,7 @@ const ScaleNameAchievedFns: ScaleAchievedFns = {
"usc:Keyboard": {},
"wacca:Single": {},
"popn:9B": {},
"jubeat:Single": {},
};
export function GetScaleAchievedFn(