mirror of
https://github.com/zkldi/Tachi.git
synced 2026-10-12 09:18:25 +03:00
feat: Ongeki support (#1013)
* feat: initial ongeki support * feat: initial ongeki seeds * chore: link to a basic score exporter * fix: unhardcode the colSpan of score dropdowns Fixes ongeki and doesn't break chunithm * fix: make bellCount optional to match judgements and damage * docs: add a page for Ongeki * feat: ongeki seeder * feat: implement isHot Also restore Arcaea to test.conf.json5 (it was a mistake) * fix: add isHot to the seeder * fix: proper handling of re:masters Also change non-full-bell from an empty string to "NONE", for clarity * docs: document the re:master fix * feat: update rg-stats to 0.5.5 enables ongeki calcs * chore: lockfile * fix: get rid of scales as they're unused * fix: update name->NAME * minrelevantvalue should be one of the values? * fix: add property that must be there? * fix: jesus christ the tests are loud * better docs on mrv * fix: remove platdelta, make platscore optional * fix: grade B tests oh my god * fix: rating check * fix: pb merge check * fix: pb merge check #2 * fix: use nowrap in all lamp cells * fix: tidy up platcell * fix: all of these MUST be part of scoreID * fix: remove unused code * feat: remove note counts & isHot * chore: bump rg-stats --------- Co-authored-by: zkldi <20380519+zkldi@users.noreply.github.com>
This commit is contained in:
+1
-1
@@ -67,7 +67,7 @@
|
||||
"react-router-bootstrap": "^0.25.0",
|
||||
"react-router-dom": "5.1.2",
|
||||
"react-select": "^5.6.1",
|
||||
"rg-stats": "0.5.4",
|
||||
"rg-stats": "0.5.6",
|
||||
"sync-fetch": "^0.3.1",
|
||||
"tachi-common": "workspace:../common",
|
||||
"vite-plugin-html": "^3.2.0"
|
||||
|
||||
@@ -341,7 +341,22 @@ function ImportInfoDisplayer({ game }: { game: Game }) {
|
||||
key="SL-ITG"
|
||||
/>
|
||||
);
|
||||
} // else if (game === "jubeat") {
|
||||
} else if (game === 'ongeki') {
|
||||
Content.unshift(
|
||||
<ImportInfoCard
|
||||
name="ARTEMiS Exporter"
|
||||
href="ongeki-artemis-exporter"
|
||||
desc="Export your scores from an ARTEMiS instance."
|
||||
moreInfo={
|
||||
<>
|
||||
This is a script that exports scores from an ARTEMiS instance. <br />
|
||||
Note: You will need direct access to the server instance.
|
||||
</>
|
||||
}
|
||||
key="ARTEMiS Exporter"
|
||||
/>
|
||||
);
|
||||
}// else if (game === "jubeat") {
|
||||
// Content.unshift(
|
||||
// <ImportTypeInfoCard key="api/cg-dev-jubeat" importType="api/cg-dev-jubeat" />,
|
||||
// <ImportTypeInfoCard key="api/cg-prod-jubeat" importType="api/cg-prod-jubeat" />
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import useSetSubheader from "components/layout/header/useSetSubheader";
|
||||
import Divider from "components/util/Divider";
|
||||
import ExternalLink from "components/util/ExternalLink";
|
||||
import { TachiConfig } from "lib/config";
|
||||
import React from "react";
|
||||
|
||||
export default function OngekiArtemisExport() {
|
||||
useSetSubheader(["Import Scores", "O.N.G.E.K.I. ARTEMiS Exporter"]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1 className="text-center mb-4">What is the ARTEMiS Exporter?</h1>
|
||||
<div>
|
||||
The Artemis Exporter is a script that will export your O.N.G.E.K.I. scores from an
|
||||
ARTEMiS instance to a BATCH-MANUAL JSON for import to {TachiConfig.NAME}. You will
|
||||
need direct access to the server instance.
|
||||
</div>
|
||||
<Divider />
|
||||
<h1 className="text-center my-4">Setup Instructions</h1>
|
||||
Instructions are available on{" "}
|
||||
<ExternalLink href="https://gist.github.com/nyairobi/ffdf9e674f31987b1ffbd38d31b55f6c">
|
||||
the GitHub gist
|
||||
</ExternalLink>
|
||||
.
|
||||
</div>
|
||||
);
|
||||
}
|
||||
+14
-8
@@ -1,5 +1,5 @@
|
||||
import { APIFetchV1 } from "util/api";
|
||||
import { Reverse } from "util/misc";
|
||||
import { Reverse, UppercaseFirst } from "util/misc";
|
||||
import DistributionTable from "components/game/folder/FolderDistributionTable";
|
||||
import Card from "components/layout/page/Card";
|
||||
import DebounceSearch from "components/util/DebounceSearch";
|
||||
@@ -14,7 +14,13 @@ 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, GetGPTString, GetGamePTConfig, GetScoreMetricConf } from "tachi-common";
|
||||
import {
|
||||
FolderDocument,
|
||||
GetGPTString,
|
||||
GetGamePTConfig,
|
||||
GetScoreMetricConf,
|
||||
GetScoreMetrics,
|
||||
} from "tachi-common";
|
||||
import { ConfEnumScoreMetric } from "tachi-common/types/metrics";
|
||||
import { FolderStatsInfo, UGPTFolderSearch } from "types/api-returns";
|
||||
import { UGPT } from "types/react";
|
||||
@@ -130,12 +136,12 @@ export function FolderInfoComponent({
|
||||
<div className="row text-center">
|
||||
<div className="col-12">
|
||||
<div className="btn-group">
|
||||
<SelectButton value={metric} setValue={setMetric} id="grade">
|
||||
<Icon type="sort-alpha-up" /> Grades
|
||||
</SelectButton>
|
||||
<SelectButton value={metric} setValue={setMetric} id="lamp">
|
||||
<Icon type="lightbulb" /> Lamps
|
||||
</SelectButton>
|
||||
{GetScoreMetrics(gptConfig, "ENUM").map((e) => (
|
||||
<SelectButton value={metric} setValue={setMetric} id={e}>
|
||||
{/* @ts-expect-error this access is legal zzz */}
|
||||
<Icon type={gptImpl.enumIcons[e]} /> {UppercaseFirst(e)}s
|
||||
</SelectButton>
|
||||
))}
|
||||
</div>
|
||||
<Divider />
|
||||
{dataset}
|
||||
|
||||
@@ -29,6 +29,7 @@ import ChunithmSiteImportPage from "app/pages/dashboard/import/ChunithmSiteImpor
|
||||
import SaekawaPage from "app/pages/dashboard/import/SaekawaPage";
|
||||
import AquaArtemisExport from "app/pages/dashboard/import/AquaArtemisExportPage";
|
||||
import ChunithmMYTExport from "app/pages/dashboard/import/ChunithmMYTExportPage";
|
||||
import OngekiArtemisExport from "app/pages/dashboard/import/OngekiArtemisExportPage";
|
||||
|
||||
export default function ImportRoutes() {
|
||||
const { user } = useContext(UserContext);
|
||||
@@ -224,6 +225,10 @@ export default function ImportRoutes() {
|
||||
<Route exact path="/import/kt-maimaidx-site-importer">
|
||||
<MaimaiDXSiteImportPage />
|
||||
</Route>
|
||||
|
||||
<Route exact path="/import/ongeki-artemis-exporter">
|
||||
<OngekiArtemisExport />
|
||||
</Route>
|
||||
</>
|
||||
)}
|
||||
</Route>
|
||||
|
||||
@@ -28,6 +28,7 @@ const GPT_UTILS: Record<GPTString, Array<GPTUtility>> = {
|
||||
"wacca:Single": [],
|
||||
"maimaidx:Single": [],
|
||||
"maimai:Single": [],
|
||||
"ongeki:Single": [],
|
||||
};
|
||||
|
||||
export function GetGPTUtils(game: Game, playtype: Playtype) {
|
||||
|
||||
@@ -15,6 +15,7 @@ export default function BMSOrPMSLampCell({
|
||||
<td
|
||||
style={{
|
||||
backgroundColor: ChangeOpacity(GetEnumColour(score, "lamp"), 0.2),
|
||||
whiteSpace: "nowrap",
|
||||
}}
|
||||
>
|
||||
<strong>{score.scoreData.lamp}</strong>
|
||||
|
||||
@@ -3,7 +3,7 @@ import { ChangeOpacity } from "util/color-opacity";
|
||||
import { IsNotNullish } from "util/misc";
|
||||
import { GetEnumColour } from "lib/game-implementations";
|
||||
import React from "react";
|
||||
import { ChartDocument, GetGPTString, PBScoreDocument, ScoreDocument } from "tachi-common";
|
||||
import { ChartDocument, PBScoreDocument, ScoreDocument } from "tachi-common";
|
||||
|
||||
export default function IIDXLampCell({
|
||||
sc,
|
||||
@@ -62,6 +62,7 @@ export default function IIDXLampCell({
|
||||
<td
|
||||
style={{
|
||||
backgroundColor: ChangeOpacity(GetEnumColour(sc, "lamp"), 0.2),
|
||||
whiteSpace: "nowrap",
|
||||
}}
|
||||
>
|
||||
<strong>{sc.scoreData.lamp}</strong>
|
||||
|
||||
@@ -6,6 +6,7 @@ export default function LampCell({ lamp, colour }: { lamp: string; colour: strin
|
||||
<td
|
||||
style={{
|
||||
backgroundColor: ChangeOpacity(colour, 0.2),
|
||||
whiteSpace: "nowrap",
|
||||
}}
|
||||
>
|
||||
<strong>{lamp}</strong>
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import { IsNullish } from "util/misc";
|
||||
import React from "react";
|
||||
import { COLOUR_SET, PBScoreDocument, ScoreDocument } from "tachi-common";
|
||||
|
||||
export default function OngekiJudgementCell({
|
||||
score,
|
||||
}: {
|
||||
score: ScoreDocument<"ongeki:Single"> | PBScoreDocument<"ongeki:Single">;
|
||||
}) {
|
||||
const judgements = score.scoreData.judgements;
|
||||
|
||||
if (
|
||||
IsNullish(judgements.miss) ||
|
||||
IsNullish(judgements.hit) ||
|
||||
IsNullish(judgements.break) ||
|
||||
IsNullish(judgements.cbreak)
|
||||
) {
|
||||
return <td>No Data.</td>;
|
||||
}
|
||||
|
||||
return (
|
||||
<td>
|
||||
<strong>
|
||||
<div>
|
||||
<span style={{ color: COLOUR_SET.gold }}>{judgements.cbreak}</span>-
|
||||
<span style={{ color: COLOUR_SET.orange }}>{judgements.break}</span>-
|
||||
<span style={{ color: COLOUR_SET.vibrantBlue }}>{judgements.hit}</span>-
|
||||
<span style={{ color: COLOUR_SET.gray }}>{judgements.miss}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span style={{ color: COLOUR_SET.vibrantYellow }}>
|
||||
{score.scoreData.optional.bellCount ?? "?"}/
|
||||
{score.scoreData.optional.totalBellCount ?? "?"}
|
||||
</span>
|
||||
<span style={{ color: COLOUR_SET.red, marginLeft: "0.5em" }}>
|
||||
{score.scoreData.optional.damage ?? "?"}
|
||||
</span>
|
||||
</div>
|
||||
</strong>
|
||||
</td>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import { ChangeOpacity } from "util/color-opacity";
|
||||
import React from "react";
|
||||
|
||||
export default function OngekiLampCell({
|
||||
lamp1,
|
||||
lamp2,
|
||||
colour,
|
||||
}: {
|
||||
lamp1: string;
|
||||
lamp2: string;
|
||||
colour: string;
|
||||
}) {
|
||||
let content = <div>{lamp1}</div>;
|
||||
|
||||
if (lamp2 !== "NONE") {
|
||||
if (lamp1 === "CLEAR") {
|
||||
content = <div>{lamp2}</div>;
|
||||
} else {
|
||||
content = (
|
||||
<span>
|
||||
<div>{lamp1}</div>
|
||||
<div>{lamp2}</div>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<td
|
||||
style={{
|
||||
backgroundColor: ChangeOpacity(colour, 0.2),
|
||||
whiteSpace: "nowrap",
|
||||
}}
|
||||
>
|
||||
<strong>{content}</strong>
|
||||
</td>
|
||||
);
|
||||
}
|
||||
@@ -16,6 +16,7 @@ export default function PopnLampCell({
|
||||
GPT_CLIENT_IMPLEMENTATIONS["popn:9B"].enumColours.lamp[score.scoreData.lamp],
|
||||
0.2
|
||||
),
|
||||
whiteSpace: "nowrap",
|
||||
}}
|
||||
>
|
||||
<strong>{score.scoreData.lamp}</strong>
|
||||
|
||||
@@ -15,6 +15,7 @@ export default function SDVXLampCell({
|
||||
<td
|
||||
style={{
|
||||
backgroundColor: ChangeOpacity(GetEnumColour(score, "lamp"), 0.2),
|
||||
whiteSpace: "nowrap",
|
||||
}}
|
||||
>
|
||||
<strong>{score.scoreData.lamp === "FAILED" ? "PLAYED" : score.scoreData.lamp}</strong>
|
||||
|
||||
@@ -4,10 +4,11 @@ import TimestampCell from "components/tables/cells/TimestampCell";
|
||||
import ScoreCoreCells from "components/tables/game-core-cells/ScoreCoreCells";
|
||||
import useScoreRatingAlg from "components/util/useScoreRatingAlg";
|
||||
import React, { useContext, useEffect, useState } from "react";
|
||||
import { ChartDocument, PBScoreDocument, ScoreDocument } from "tachi-common";
|
||||
import { ChartDocument, GetGPTString, PBScoreDocument, ScoreDocument } from "tachi-common";
|
||||
import { UGPTChartPBComposition } from "types/api-returns";
|
||||
import { SetState } from "types/react";
|
||||
import { UserContext } from "context/UserContext";
|
||||
import { GPT_CLIENT_IMPLEMENTATIONS } from "lib/game-implementations";
|
||||
import CommentContainer from "./CommentContainer";
|
||||
import JudgementTable from "./JudgementTable";
|
||||
import PBNote from "./PBNote";
|
||||
@@ -22,13 +23,14 @@ export function ScoreInfo({
|
||||
chart: ChartDocument;
|
||||
}) {
|
||||
const rating = useScoreRatingAlg(score.game, score.playtype);
|
||||
const gptImpl = GPT_CLIENT_IMPLEMENTATIONS[GetGPTString(score.game, chart.playtype)];
|
||||
|
||||
return (
|
||||
<div className="col-12">
|
||||
<table className="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<td colSpan={3}>Score Info</td>
|
||||
<td colSpan={gptImpl.scoreHeaders.length}>Score Info</td>
|
||||
<td>{UppercaseFirst(rating)}</td>
|
||||
<td>Timestamp</td>
|
||||
</tr>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { NumericSOV } from "util/sorts";
|
||||
import { ChangeOpacity } from "util/color-opacity";
|
||||
import { FormatMillions } from "util/misc";
|
||||
import { COLOUR_SET, GPTString, GetGPTString, PBScoreDocument, ScoreDocument } from "tachi-common";
|
||||
import CHUNITHMJudgementCell from "components/tables/cells/CHUNITHMJudgementCell";
|
||||
import ITGJudgementCell from "components/tables/cells/ITGJudgementCell";
|
||||
@@ -16,7 +17,9 @@ import PopnLampCell from "components/tables/cells/PopnLampCell";
|
||||
import RatingCell from "components/tables/cells/RatingCell";
|
||||
import ScoreCell from "components/tables/cells/ScoreCell";
|
||||
import WaccaJudgementCell from "components/tables/cells/WACCAJudgementCell";
|
||||
import OngekiJudgementCell from "components/tables/cells/OngekiJudgementCell";
|
||||
import React from "react";
|
||||
import OngekiLampCell from "components/tables/cells/OngekiLampCell";
|
||||
import { CreateRatingSys, bgc } from "./games/_util";
|
||||
import { BMS_14K_IMPL, BMS_7K_IMPL, PMS_IMPL } from "./games/bms-pms";
|
||||
import { IIDX_DP_IMPL, IIDX_SP_IMPL } from "./games/iidx";
|
||||
@@ -111,7 +114,7 @@ export const GPT_CLIENT_IMPLEMENTATIONS: GPTClientImplementations = {
|
||||
["Judgements", "Hits", NumericSOV((x) => x.scoreData.score)],
|
||||
["Lamp", "Lamp", NumericSOV((x) => x.scoreData.enumIndexes.lamp)],
|
||||
],
|
||||
scoreCoreCells: ({ sc, chart }) => (
|
||||
scoreCoreCells: ({ sc }) => (
|
||||
<>
|
||||
<MillionsScoreCell
|
||||
score={sc.scoreData.score}
|
||||
@@ -173,7 +176,7 @@ export const GPT_CLIENT_IMPLEMENTATIONS: GPTClientImplementations = {
|
||||
["Judgements", "Hits", NumericSOV((x) => x?.scoreData.musicRate ?? -Infinity)],
|
||||
["Lamp", "Lamp", NumericSOV((x) => x?.scoreData.enumIndexes.lamp ?? -Infinity)],
|
||||
],
|
||||
scoreCoreCells: ({ sc, chart }) => (
|
||||
scoreCoreCells: ({ sc }) => (
|
||||
<>
|
||||
<JubeatScoreCell sc={sc} />
|
||||
<JubeatJudgementCell score={sc} />
|
||||
@@ -458,7 +461,7 @@ export const GPT_CLIENT_IMPLEMENTATIONS: GPTClientImplementations = {
|
||||
["Near - Miss", "Nr. Ms.", NumericSOV((x) => x?.scoreData.score)],
|
||||
["Lamp", "Lamp", NumericSOV((x) => x?.scoreData.enumIndexes.lamp)],
|
||||
],
|
||||
scoreCoreCells: ({ sc, chart }) => (
|
||||
scoreCoreCells: ({ sc }) => (
|
||||
<>
|
||||
<MillionsScoreCell
|
||||
score={sc.scoreData.score}
|
||||
@@ -664,7 +667,7 @@ export const GPT_CLIENT_IMPLEMENTATIONS: GPTClientImplementations = {
|
||||
"How fast are the streams in this chart?",
|
||||
(c) => c.data.streamBPM,
|
||||
(c) => c.data.streamBPM?.toString(),
|
||||
(c) => undefined,
|
||||
() => undefined,
|
||||
(s) => [
|
||||
s.scoreData.lamp === "FAILED"
|
||||
? `Failed ${s.scoreData.survivedPercent.toFixed(2)}%`
|
||||
@@ -678,7 +681,7 @@ export const GPT_CLIENT_IMPLEMENTATIONS: GPTClientImplementations = {
|
||||
["Judgements", "Hits", NumericSOV((x) => x.scoreData.scorePercent)],
|
||||
["Lamp", "Lamp", NumericSOV((x) => x.scoreData.enumIndexes.lamp)],
|
||||
],
|
||||
scoreCoreCells: ({ sc, chart }) => (
|
||||
scoreCoreCells: ({ sc }) => (
|
||||
<>
|
||||
<ScoreCell
|
||||
colour={GetEnumColour(sc, "grade")}
|
||||
@@ -717,6 +720,110 @@ export const GPT_CLIENT_IMPLEMENTATIONS: GPTClientImplementations = {
|
||||
</>
|
||||
),
|
||||
},
|
||||
"ongeki:Single": {
|
||||
enumIcons: {
|
||||
grade: "sort-alpha-up",
|
||||
noteLamp: "lightbulb",
|
||||
bellLamp: "lightbulb",
|
||||
},
|
||||
classColours: {
|
||||
colour: {
|
||||
BLUE: bgc("var(--bs-info)", "var(--bs-light)"),
|
||||
GREEN: bgc("green", "var(--bs-light)"),
|
||||
ORANGE: bgc("orange", "var(--bs-dark)"),
|
||||
RED: bgc("red", "var(--bs-light)"),
|
||||
PURPLE: bgc("purple", "var(--bs-light)"),
|
||||
COPPER: bgc("sienna", "var(--bs-light)"),
|
||||
SILVER: bgc("gray", "var(--bs-light)"),
|
||||
GOLD: bgc("var(--bs-warning)", "var(--bs-dark)"),
|
||||
PLATINUM: bgc("silver", "var(--bs-dark)"),
|
||||
RAINBOW: {
|
||||
background:
|
||||
"linear-gradient(-45deg, #f0788a, #f48fb1, #9174c2, #79bcf2, #70a173, #f7ff99, #faca7d, #ff9d80, #f0788a)",
|
||||
color: "var(--bs-dark)",
|
||||
},
|
||||
},
|
||||
},
|
||||
enumColours: {
|
||||
grade: {
|
||||
D: COLOUR_SET.red,
|
||||
C: COLOUR_SET.purple,
|
||||
B: COLOUR_SET.paleBlue,
|
||||
BB: COLOUR_SET.blue,
|
||||
BBB: COLOUR_SET.vibrantBlue,
|
||||
A: COLOUR_SET.paleGreen,
|
||||
AA: COLOUR_SET.green,
|
||||
AAA: COLOUR_SET.vibrantGreen,
|
||||
S: COLOUR_SET.orange,
|
||||
SS: COLOUR_SET.vibrantYellow,
|
||||
SSS: COLOUR_SET.teal,
|
||||
"SSS+": COLOUR_SET.white,
|
||||
},
|
||||
noteLamp: {
|
||||
LOSS: COLOUR_SET.red,
|
||||
CLEAR: COLOUR_SET.paleGreen,
|
||||
"FULL COMBO": COLOUR_SET.paleBlue,
|
||||
"ALL BREAK": COLOUR_SET.gold,
|
||||
},
|
||||
bellLamp: {
|
||||
NONE: COLOUR_SET.red,
|
||||
"FULL BELL": COLOUR_SET.gold,
|
||||
},
|
||||
},
|
||||
difficultyColours: {
|
||||
BASIC: COLOUR_SET.blue,
|
||||
ADVANCED: COLOUR_SET.orange,
|
||||
EXPERT: COLOUR_SET.red,
|
||||
MASTER: COLOUR_SET.purple,
|
||||
LUNATIC: COLOUR_SET.vibrantRed,
|
||||
},
|
||||
ratingSystems: [],
|
||||
scoreHeaders: [
|
||||
[
|
||||
"Score",
|
||||
"Score",
|
||||
NumericSOV(
|
||||
(x) => x.scoreData.score * 10000 + (x.scoreData.optional.platScore ?? 0)
|
||||
),
|
||||
],
|
||||
["Judgements", "Hits", NumericSOV((x) => x.scoreData.score)],
|
||||
[
|
||||
"Lamp",
|
||||
"Lamp",
|
||||
NumericSOV(
|
||||
(x) =>
|
||||
(x.scoreData.enumIndexes.noteLamp << 8) + x.scoreData.enumIndexes.bellLamp
|
||||
),
|
||||
],
|
||||
],
|
||||
scoreCoreCells: ({ sc, chart }) => (
|
||||
<>
|
||||
<td
|
||||
style={{
|
||||
backgroundColor: ChangeOpacity(GetEnumColour(sc, "grade"), 0.2),
|
||||
}}
|
||||
>
|
||||
<strong>{sc.scoreData.grade}</strong>
|
||||
<br />
|
||||
{FormatMillions(sc.scoreData.score)}
|
||||
{typeof sc.scoreData.optional.platScore === "number" &&
|
||||
(chart.difficulty === "MASTER" || chart.difficulty === "LUNATIC") && (
|
||||
<>
|
||||
<br />
|
||||
[Plat: {sc.scoreData.optional.platScore}]
|
||||
</>
|
||||
)}
|
||||
</td>
|
||||
<OngekiJudgementCell score={sc} />
|
||||
<OngekiLampCell
|
||||
lamp1={sc.scoreData.noteLamp}
|
||||
lamp2={sc.scoreData.bellLamp}
|
||||
colour={GetEnumColour(sc, "noteLamp")}
|
||||
/>
|
||||
</>
|
||||
),
|
||||
ratingCell: ({ sc, rating }) => <RatingCell score={sc} rating={rating} />,
|
||||
},
|
||||
"arcaea:Touch": ARCAEA_TOUCH_IMPL,
|
||||
"gitadora:Dora": GITADORA_DORA_IMPL,
|
||||
"gitadora:Gita": GITADORA_GITA_IMPL,
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
import { Game } from "tachi-common";
|
||||
|
||||
const scales: Record<Game, number> = {
|
||||
iidx: 2,
|
||||
bms: 2,
|
||||
pms: 2,
|
||||
chunithm: 5,
|
||||
gitadora: 1,
|
||||
maimaidx: 1,
|
||||
maimai: 1,
|
||||
museca: 5,
|
||||
sdvx: 5,
|
||||
usc: 5,
|
||||
wacca: 5,
|
||||
popn: 4,
|
||||
jubeat: 5,
|
||||
itg: 5,
|
||||
arcaea: 1,
|
||||
};
|
||||
|
||||
export function GetGradeChartExpScale(game: Game) {
|
||||
return scales[game];
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import { JUBEAT_CONF, JUBEAT_SINGLE_CONF } from "./game-support/jubeat";
|
||||
import { MAIMAI_CONF, MAIMAI_SINGLE_CONF } from "./game-support/maimai";
|
||||
import { MAIMAI_DX_CONF, MAIMAI_DX_SINGLE_CONF } from "./game-support/maimai-dx";
|
||||
import { MUSECA_CONF, MUSECA_SINGLE_CONF } from "./game-support/museca";
|
||||
import { ONGEKI_CONF, ONGEKI_SINGLE_CONF } from "./game-support/ongeki";
|
||||
import { PMS_CONF, PMS_CONTROLLER_CONF, PMS_KEYBOARD_CONF } from "./game-support/pms";
|
||||
import { POPN_9B_CONF, POPN_CONF } from "./game-support/popn";
|
||||
import { SDVX_CONF, SDVX_SINGLE_CONF } from "./game-support/sdvx";
|
||||
@@ -48,6 +49,7 @@ export const GAME_CONFIGS = {
|
||||
pms: PMS_CONF,
|
||||
itg: ITG_CONF,
|
||||
arcaea: ARCAEA_CONF,
|
||||
ongeki: ONGEKI_CONF,
|
||||
} as const satisfies Record<string, INTERNAL_GAME_CONFIG>;
|
||||
|
||||
/**
|
||||
@@ -95,6 +97,7 @@ export const GAME_PT_CONFIGS = {
|
||||
"usc:Keyboard": USC_KEYBOARD_CONF,
|
||||
"itg:Stamina": ITG_STAMINA_CONF,
|
||||
"arcaea:Touch": ARCAEA_TOUCH_CONF,
|
||||
"ongeki:Single": ONGEKI_SINGLE_CONF,
|
||||
} as const satisfies Record<GPTString, INTERNAL_GAME_PT_CONFIG>;
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
import { FAST_SLOW_MAXCOMBO } from "./_common";
|
||||
import { FmtNum } from "../../utils/util";
|
||||
import { ClassValue } from "../config-utils";
|
||||
import { p } from "prudence";
|
||||
import { z } from "zod";
|
||||
import type { INTERNAL_GAME_CONFIG, INTERNAL_GAME_PT_CONFIG } from "../../types/internals";
|
||||
|
||||
export const ONGEKI_CONF = {
|
||||
name: "O.N.G.E.K.I.",
|
||||
playtypes: ["Single"],
|
||||
songData: z.strictObject({
|
||||
genre: z.string(),
|
||||
}),
|
||||
} as const satisfies INTERNAL_GAME_CONFIG;
|
||||
|
||||
export const OngekiColours = [
|
||||
ClassValue("BLUE", "青", "Blue: 0 - 1.99 Rating"),
|
||||
ClassValue("GREEN", "緑", "Green: 2 - 3.99 Rating"),
|
||||
ClassValue("ORANGE", "橙", "Orange: 4 - 6.99 Rating"),
|
||||
ClassValue("RED", "赤", "Red: 7 - 9.99 Rating"),
|
||||
ClassValue("PURPLE", "紫", "Purple: 10 - 11.99 Rating"),
|
||||
ClassValue("COPPER", "銅", "Copper: 12 - 12.99 Rating"),
|
||||
ClassValue("SILVER", "銀", "Silver: 13 - 13.99 Rating"),
|
||||
ClassValue("GOLD", "金", "Gold: 14.00 - 14.49 Rating"),
|
||||
ClassValue("PLATINUM", "鉑", "Platinum: 14.50 - 14.99 Rating"),
|
||||
ClassValue("RAINBOW", "虹", "Rainbow: >=15 Rating"),
|
||||
];
|
||||
|
||||
export const ONGEKI_SINGLE_CONF = {
|
||||
providedMetrics: {
|
||||
score: {
|
||||
type: "INTEGER",
|
||||
validate: p.isBetween(0, 1_010_000),
|
||||
formatter: FmtNum,
|
||||
description:
|
||||
"Known in-game as 'Technical Score'. It ranges between 0 and 1,010,000, where notes are worth 950,000, and bells 60,000.",
|
||||
},
|
||||
noteLamp: {
|
||||
type: "ENUM",
|
||||
values: ["LOSS", "CLEAR", "FULL COMBO", "ALL BREAK"],
|
||||
minimumRelevantValue: "CLEAR",
|
||||
description: "The first lamp. A clear is either a draw or a win.",
|
||||
},
|
||||
bellLamp: {
|
||||
type: "ENUM",
|
||||
values: ["NONE", "FULL BELL"],
|
||||
minimumRelevantValue: "NONE",
|
||||
description:
|
||||
"The second lamp that tracks whether all bells in the chart have been collected.",
|
||||
},
|
||||
},
|
||||
|
||||
derivedMetrics: {
|
||||
grade: {
|
||||
type: "ENUM",
|
||||
values: ["D", "C", "B", "BB", "BBB", "A", "AA", "AAA", "S", "SS", "SSS", "SSS+"],
|
||||
minimumRelevantValue: "A",
|
||||
description: "The grade this score was.",
|
||||
},
|
||||
},
|
||||
|
||||
defaultMetric: "score",
|
||||
preferredDefaultEnum: "grade",
|
||||
|
||||
optionalMetrics: {
|
||||
...FAST_SLOW_MAXCOMBO,
|
||||
damage: {
|
||||
type: "INTEGER",
|
||||
chartDependentMax: true,
|
||||
formatter: FmtNum,
|
||||
description: "The number of damage ticks received.",
|
||||
partOfScoreID: true,
|
||||
},
|
||||
bellCount: {
|
||||
type: "INTEGER",
|
||||
chartDependentMax: true,
|
||||
formatter: FmtNum,
|
||||
description: "The number of bells collected.",
|
||||
partOfScoreID: true,
|
||||
},
|
||||
totalBellCount: {
|
||||
type: "INTEGER",
|
||||
chartDependentMax: true,
|
||||
formatter: FmtNum,
|
||||
description: "The total number of bells.",
|
||||
},
|
||||
platScore: {
|
||||
type: "INTEGER",
|
||||
chartDependentMax: true,
|
||||
formatter: FmtNum,
|
||||
description: "The Platinum Score value. Only exists in MASTER and LUNATIC charts.",
|
||||
partOfScoreID: true,
|
||||
},
|
||||
},
|
||||
|
||||
scoreRatingAlgs: {
|
||||
rating: {
|
||||
description:
|
||||
"The rating value of this score. This is identical to the system used in game.",
|
||||
},
|
||||
},
|
||||
sessionRatingAlgs: {
|
||||
naiveRating: { description: "The average of your best 10 ratings this session." },
|
||||
},
|
||||
profileRatingAlgs: {
|
||||
naiveRating: {
|
||||
description: "The average of your best 45 scores.",
|
||||
},
|
||||
},
|
||||
|
||||
defaultScoreRatingAlg: "rating",
|
||||
defaultSessionRatingAlg: "naiveRating",
|
||||
defaultProfileRatingAlg: "naiveRating",
|
||||
|
||||
difficulties: {
|
||||
type: "FIXED",
|
||||
order: ["BASIC", "ADVANCED", "EXPERT", "MASTER", "LUNATIC"],
|
||||
shorthand: {
|
||||
BASIC: "BAS",
|
||||
ADVANCED: "ADV",
|
||||
EXPERT: "EXP",
|
||||
MASTER: "MAS",
|
||||
LUNATIC: "LUN",
|
||||
},
|
||||
default: "MASTER",
|
||||
},
|
||||
|
||||
classes: {
|
||||
colour: {
|
||||
type: "DERIVED",
|
||||
values: OngekiColours,
|
||||
},
|
||||
},
|
||||
|
||||
orderedJudgements: ["cbreak", "break", "hit", "miss"],
|
||||
|
||||
versions: {
|
||||
brightMemory2: "bright MEMORY Act.II",
|
||||
brightMemory2Omni: "bright MEMORY Act.II Omnimix",
|
||||
brightMemory3: "bright MEMORY Act.III",
|
||||
brightMemory3Omni: "bright MEMORY Act.III Omnimix",
|
||||
},
|
||||
|
||||
chartData: z.strictObject({
|
||||
displayVersion: z.string(),
|
||||
isUnranked: z.boolean().optional(),
|
||||
isReMaster: z.boolean().optional(),
|
||||
}),
|
||||
|
||||
preferences: z.strictObject({}),
|
||||
|
||||
scoreMeta: z.strictObject({}),
|
||||
|
||||
supportedMatchTypes: ["songTitle", "tachiSongID"],
|
||||
} as const satisfies INTERNAL_GAME_PT_CONFIG;
|
||||
@@ -556,3 +556,43 @@ export enum ARCAEA_COURSE_BANNERS {
|
||||
PHASE_10 = 9,
|
||||
PHASE_11 = 10,
|
||||
}
|
||||
|
||||
export enum ONGEKI_NOTE_LAMPS {
|
||||
LOSS = 0,
|
||||
CLEAR = 1,
|
||||
FULL_COMBO = 2,
|
||||
ALL_BREAK = 3,
|
||||
}
|
||||
|
||||
export enum ONGEKI_BELL_LAMPS {
|
||||
NONE = 0,
|
||||
FULL_BELL = 1,
|
||||
}
|
||||
|
||||
export enum ONGEKI_GRADES {
|
||||
D = 0,
|
||||
C = 1,
|
||||
B = 2,
|
||||
BB = 3,
|
||||
BBB = 4,
|
||||
A = 5,
|
||||
AA = 6,
|
||||
AAA = 7,
|
||||
S = 8,
|
||||
SS = 9,
|
||||
SSS = 10,
|
||||
SSS_PLUS = 11,
|
||||
}
|
||||
|
||||
export enum ONGEKI_COLOURS {
|
||||
BLUE = 0,
|
||||
GREEN = 1,
|
||||
ORANGE = 2,
|
||||
RED = 3,
|
||||
PURPLE = 4,
|
||||
COPPER = 5,
|
||||
SILVER = 6,
|
||||
GOLD = 7,
|
||||
PLATINUM = 8,
|
||||
RAINBOW = 9,
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import type { integer } from "../types";
|
||||
import type { GPTStrings } from "../types/game-config";
|
||||
import type { GetEnumValue } from "../types/metrics";
|
||||
|
||||
@@ -207,3 +206,18 @@ export const ARCAEA_GBOUNDARIES = MakeGradeBoundaries<GetEnumValue<"arcaea:Touch
|
||||
EX: 9_800_000,
|
||||
"EX+": 9_900_000,
|
||||
});
|
||||
|
||||
export const ONGEKI_GBOUNDARIES = MakeGradeBoundaries<GetEnumValue<"ongeki:Single", "grade">>({
|
||||
D: 0,
|
||||
C: 500_000,
|
||||
B: 700_000,
|
||||
BB: 750_000,
|
||||
BBB: 800_000,
|
||||
A: 850_000,
|
||||
AA: 900_000,
|
||||
AAA: 940_000,
|
||||
S: 970_000,
|
||||
SS: 990_000,
|
||||
SSS: 1_000_000,
|
||||
"SSS+": 1_007_500,
|
||||
});
|
||||
|
||||
@@ -82,6 +82,10 @@ export interface ConfEnumScoreMetric<V extends string> {
|
||||
type: "ENUM";
|
||||
values: ReadonlyArray<V>;
|
||||
|
||||
/**
|
||||
* The minimum value that users should care about. Used to prevent UI-issues like saying
|
||||
* "You got 5 new fails/D ranks today!" etc.
|
||||
*/
|
||||
minimumRelevantValue: V;
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -3932,6 +3932,218 @@
|
||||
"tableID": "museca-Single-1.5-levels",
|
||||
"title": "MÚSECA (1 + 1/2)"
|
||||
},
|
||||
{
|
||||
"default": false,
|
||||
"description": "Levels for O.N.G.E.K.I. in bright MEMORY Act.2 Omnimix.",
|
||||
"folders": [
|
||||
"F9d846efcecb94b8379983e966007b5ebb19a060840dabfd959b3325b5ca871c2",
|
||||
"F272a16ebce16a4e751a0b6baf081be9dc8bb3c3e006edb55f6865f7ec158d9f7",
|
||||
"Fa37c407703c21ebf12ce798182e2a664954d30dee1c9eb8e3e6c4ea2e7bef731",
|
||||
"F13740536865b6c573ee1585270a10093936cad43e35ee8e0e6fec74c92a67ec9",
|
||||
"Fb8d0753c792ab6c654f1d00304ed5ec44f883b6f24e2b4b0f1d5643e8a134afd",
|
||||
"F2071665eb9e5ae130766d4a1ea52e54d8429820fca1a53b631f504d2f2c21f3d",
|
||||
"F0f49415e52dfc6a8a7f26c6f46640fcbf9c7234e0fc90c79063c921ab18df7d4",
|
||||
"Ffaeb5a16a8aa1977a61aac60e5b162e94436f99848d687939bae7debf69bb5ff",
|
||||
"F7bef76d81570df63374a69330f2b36802ab1ae64c69aa2343c2062017bd6b2e1",
|
||||
"Fa57648770688d3e7dbfcae723fbb81b1992c8b5aa858ccc304075b39efe33faf",
|
||||
"Fc4deabf9610a80f9a9c5761ab37464c817f98a8b787d5e2d21690ef56cfbce34",
|
||||
"Fad38dc6d28b0963e8937e1902159ca56a0fb0391b63b10b62b0cc59d1027be39",
|
||||
"Fff292ed9235a780a3ca7587060a77e546f5094bd14643ad7c7e6e057fb3ee4e2",
|
||||
"F35d2721aeffcd77d5350bc62601e4eeaa154cfe89ca1aa47b86f22f6da999503",
|
||||
"F9ebe2cd32f49220392c2605c01d90b02597a56c1673fddaea32036b0798b3283",
|
||||
"F8856f8ceb286ccdd6226e8e439553e6e2488a7777bdb252392dbca9889e94da2",
|
||||
"F4d40825a38bae2fc95457d90378a60f08ee56a5db94c5f3799ee71ae75e1404a",
|
||||
"Fdf848c21a5e32cc6db1a56f338bc665b5d9a8fc8182cda51141f88d8ec4dba6b",
|
||||
"Fb4d6948fb690aa0a8572b9e9db05d8c9755778c4f28d7cdb2393b1875e2d946e",
|
||||
"F5ab75ac4a2433e6afaf65462e1b36e4588fd13e1f4238b3418eb62ce00061b3d",
|
||||
"F81f69854d7965c855719ec99bfa16f6b86fc53ce1deddd9b871ea9c233778a47",
|
||||
"F1129428c65176fd73f322afe157f9a62e7d88df7c409ae8e047962af6225dc44",
|
||||
"F45b9bb20da3100ddd68cf0e9744e7c343900a68509763c2e9cb0a937cf94db26",
|
||||
"F8ac34ecb186a405c0b70968d276687b72e23e85c43ea5fe80eadfc1e3649fc46",
|
||||
"F76f935699b091e1de36f52b8572ab882a3c339def09e8e1f8fdae1166fa43cd9"
|
||||
],
|
||||
"game": "ongeki",
|
||||
"inactive": false,
|
||||
"playtype": "Single",
|
||||
"tableID": "ongeki-Single-brightMemory2Omni-levels",
|
||||
"title": "O.N.G.E.K.I. (bright MEMORY Act.2 Omnimix)"
|
||||
},
|
||||
{
|
||||
"default": false,
|
||||
"description": "Difficulties for O.N.G.E.K.I. in bright MEMORY Act.2 Omnimix.",
|
||||
"folders": [
|
||||
"Fa6dfd0428218be473c5d1f305cd333cce405d77c9e8e6df7edb9775c2cbc2c05",
|
||||
"F36fcfc0fcef6d056963e01643ded484e491bc95c04b10addfaece815d67347cd",
|
||||
"F7e323d1a7496ca5adcec5c501fe9b5ff7bceec3da3cc7a137d70cdbc9abf80d9",
|
||||
"Fc9a0662ab06e11e68f4700446f9ab0c7ed9a01442b0acaf812114234461e3ea9",
|
||||
"F6c7768ca1c35243b0fd34b0db875e6f1c5b890c5b1f86be888d28ce91bce92eb",
|
||||
"Fe38407fda4f0e77274d6ae67614e51a3a44202a600f0749b3ccb2745da241928"
|
||||
],
|
||||
"game": "ongeki",
|
||||
"inactive": false,
|
||||
"playtype": "Single",
|
||||
"tableID": "ongeki-Single-brightMemory2Omni-difficulties",
|
||||
"title": "O.N.G.E.K.I. (bright MEMORY Act.2 Omnimix) (Difficulties)"
|
||||
},
|
||||
{
|
||||
"default": true,
|
||||
"description": "Levels for O.N.G.E.K.I. in bright MEMORY Act.2.",
|
||||
"folders": [
|
||||
"Ffd9cf2a106b24860953efb32318714ff2c221a74b79fd57e5182a389f44cb1dc",
|
||||
"Fb1bbc8ce3fe6208ab25c385398a2e27fad6c6f5fa040d621ed066ce83633bbed",
|
||||
"F0e20686c106aab0a897f52cd3eaf3a5139ef03ec1dedb9eb3282574be03969c8",
|
||||
"F51a06f5da5e38a853c37126de751551954f6f931ca82e747663f86449b0ff205",
|
||||
"F583db98ee120691cdf78e20117cf94aa86a3fa657326cff4a37a38bfdcbe651b",
|
||||
"Ff098d916c6b9a5302cd884c5f7f95902fdd032a5d5c0659f46bf0a740133b580",
|
||||
"Ff170ebb1b4e6435bd2ea60de9e0eb3ec115ae799ec4d0dc0fc0cfdb4bb784864",
|
||||
"Fb74568edd41f9fa68307baef37d7e952286b6727c5c0539dd2b0c652b8542682",
|
||||
"Fe17b5de32044eb9075c5a828afcd74f646e41ee1a46e04fbdf2bd4a23709ebb5",
|
||||
"F406cce9133fb1693c6bd9cc8285b142cbba8178be681b57eed40670f7e7fd396",
|
||||
"F71d69895bfd9a6fab8e0a6a067be68dc7e5c6b1d370a415d8380a4549bc5e25a",
|
||||
"Fcedc5b2197356f5059b68811257932212753fa3db373cdb130c0e858eca2d75c",
|
||||
"Fac27c3384aba808c30ea1e5255c31fd4438130ac3ac59f053c36f3b4e949ed27",
|
||||
"Faaa32dc865025f5d188905c008d48c4a79e8c0ad7dad6284542d51923005520b",
|
||||
"Fae137e8a9092a18bd058a831f2f02febc752ddedea54b8b24750d74115d7e654",
|
||||
"F20fb32ddaec6609c127e1ffabdaa28a36707f50fc52636463beeba9e733ce771",
|
||||
"F7da3a8dbba05d8bfc2963e23ee890f6f3276ce77f0bcf39ed821d3d6134edf2a",
|
||||
"Fb71c7dc0fa7cc24714575a03770c3506dca63e7a46f56afe78acb6b10482500a",
|
||||
"F572f54b15e0742ff686b237a055ade44fcafc565f1a728d4741d51a1d70a0885",
|
||||
"Faef2411d9d27e6893ba721b3e437b2a5e69cad70eae5d87194f618701230b139",
|
||||
"Fab85a25c2f20412d242995d441d840323442d989256c6b8c97e4a0d8c03821d2",
|
||||
"F1c5ccbba8da3bde18b6c9b2c345949bae4ca9aba14adc21ec5dd4122f7d8e3bf",
|
||||
"Fc726778bfc50de7e77b246d86b25d10d48025c2eb0d8de7b0f77df5a9a114502",
|
||||
"Faaf526ea4c67d1456a091d19ffd7c9fb1a504da98dd33f4fa4687d37aa64c407",
|
||||
"F290de9ba414d8ed6d8ea5889fcc981d1845e8edb78ce16ee203e51b5698eb15d"
|
||||
],
|
||||
"game": "ongeki",
|
||||
"inactive": false,
|
||||
"playtype": "Single",
|
||||
"tableID": "ongeki-Single-brightMemory2-levels",
|
||||
"title": "O.N.G.E.K.I. (bright MEMORY Act.2)"
|
||||
},
|
||||
{
|
||||
"default": false,
|
||||
"description": "Difficulties for O.N.G.E.K.I. in bright MEMORY Act.2.",
|
||||
"folders": [
|
||||
"Fc6eb78670ec730d94de7e2b422f9db8d5c8e096e225a0769efc9818cdbf76e9c",
|
||||
"Fbcb6bb96b104a5229321b0fd292418f20e6881c5a9304e610dff985e17231bfa",
|
||||
"Ff12aac666f9f280d4a60025984c64609f671d992e13c9009d90a4ff8bde0662f",
|
||||
"F0c4c33417aae13c49ff36e548d4d2b8a481718491a3272377465800de046af02",
|
||||
"Fe5cfcd9c118e8eca4b3397019947e0287cfe09f296d381e0528af02724ac60fe",
|
||||
"F8c55bc4cbe3654d5a5a38073b7c4cc40966a5c33fc8d4087ccd7e34ad3ca10da"
|
||||
],
|
||||
"game": "ongeki",
|
||||
"inactive": false,
|
||||
"playtype": "Single",
|
||||
"tableID": "ongeki-Single-brightMemory2-difficulties",
|
||||
"title": "O.N.G.E.K.I. (bright MEMORY Act.2) (Difficulties)"
|
||||
},
|
||||
{
|
||||
"default": false,
|
||||
"description": "Levels for O.N.G.E.K.I. in bright MEMORY Act.3 Omnimix.",
|
||||
"folders": [
|
||||
"F276de07bc9ce42088c76a4c65dcf962241c5b509533cb035558f9369565ab938",
|
||||
"Fdb1ec28d31c8618ee2b3dd739e3140e9e3f7e2095f316b691c66af6a876a72c5",
|
||||
"F5ab7e27e7f8b8812df8819acb5332b3a0c326f9b958d75bbf096cff7d1d5ceb3",
|
||||
"F0ecbb82f7553a35e9e5289637f37c5be1d3dc6afc92c9a329ddce1758f040060",
|
||||
"F57cd134859f56236e8bf8546ff0b785dfbe62e1b812f9d314135a3154ff7fb03",
|
||||
"Ff9d5dd4f1d9b5de66b11bffaf8ff3cae8f03db1a7c6555113a57cae957f870cb",
|
||||
"Fde0e368cd05bfffc1ad239e229de4e8ae7bdcce74723590ffff149f89758d920",
|
||||
"Faf59d0fe492d2272d0d3a67e9853e1d1c36bcad7e3e9bf565856980a66ec65df",
|
||||
"F912cb7870f729765b2714e3f0ae7d7f3b45b4d37fa871deb98f135906a263f26",
|
||||
"Ff47f3a0a4c8d41b28f33ec39ef2f2a0f0ee92ef022607629dc77b68da5245e9f",
|
||||
"F910f2ee1b8f1710cf39032a65c4f9abefe243612b63afdb1342e372362fb89e8",
|
||||
"F4062d8cceb274e9d167b4f51e396219f229b9cf459ded3779a354929aa0d6e32",
|
||||
"F082e8b4dc8831a1fd5c1819d88125d8460695130f59616188fa13dbc45cf4263",
|
||||
"F10663b393c3337602e8064f924c3f9ec8ae4db5309aa772492bcbb7d8b690adf",
|
||||
"F0a6fb91e9726baaa8b37995f5bb50613d08674017117fd86732a2743ec9cdba2",
|
||||
"F640450d271c7477fdeb75f80b4091debfcb4543c623f380f5561a17f9aaf74d9",
|
||||
"F14881345781cc7f04e92dc26b28c6e888e1ce0dc35dc60de79aea41d384fff3e",
|
||||
"F22918c2040d224a851cf20ad6d20f6b01f8f882b0bb7e5e6a924951902ae139c",
|
||||
"Ff2f03e6934cb5304eb6692341b0fcf479f6234c3eac2d20e2d3c14dec226fab0",
|
||||
"Ff1d306ffa2d62ba67e000e8acfdc2ab745fe79edee27f4b8207342cea022cc9d",
|
||||
"F48746c903310d676745370e1de478a1acfdf431769af3268e2495d1089f31ce0",
|
||||
"Fa5ff25ddff12ecece13aa3b7995b0daeb9c9af3e6c2a484eb15a02e4a977096a",
|
||||
"F81d2d29ad4378b2c0018e7edf60597913bd8fa0120a2b9bcdeff07a73e83be61",
|
||||
"F4a8669b7afd6fc7de1d901495c0f5ec8d59038d60020466d8dbf8c962fbea019",
|
||||
"Fb25a317f41d1e3125750a24d489b3949165adc7fc6a0462bcbf0d79e5314ed52"
|
||||
],
|
||||
"game": "ongeki",
|
||||
"inactive": false,
|
||||
"playtype": "Single",
|
||||
"tableID": "ongeki-Single-brightMemory3Omni-levels",
|
||||
"title": "O.N.G.E.K.I. (bright MEMORY Act.3 Omnimix)"
|
||||
},
|
||||
{
|
||||
"default": false,
|
||||
"description": "Difficulties for O.N.G.E.K.I. in bright MEMORY Act.3 Omnimix.",
|
||||
"folders": [
|
||||
"F8a5a5d7424c21898a445961d185812c95e53f668863aed7a1e6e693e76ff7c11",
|
||||
"F5abeae4a2952c9d0d4df7ae03f71634fcb1b02076841c35a5820aa88f8d7551f",
|
||||
"F2454301003bbd29013d4626a05a16680a38d9d54a8dd265b5b3dfceb7a994c51",
|
||||
"F86c6409ff8b9ace572714e3361d456f346708b45eaf1652fd5d86e81b2111989",
|
||||
"F32de08ef7420094b8b1c9719858afb6d5176bc97f17b60827c11ce2a3a5af83b",
|
||||
"F20d1e739fd77625944c1b7985e9f06a8e81baa3d9b29cbcdf3b2eb3689d79189"
|
||||
],
|
||||
"game": "ongeki",
|
||||
"inactive": false,
|
||||
"playtype": "Single",
|
||||
"tableID": "ongeki-Single-brightMemory3Omni-difficulties",
|
||||
"title": "O.N.G.E.K.I. (bright MEMORY Act.3 Omnimix) (Difficulties)"
|
||||
},
|
||||
{
|
||||
"default": false,
|
||||
"description": "Levels for O.N.G.E.K.I. in bright MEMORY Act.3.",
|
||||
"folders": [
|
||||
"Fad5e5eb714f0e8d039f533564c92cb95990ca1f5e864ff8cb4766032283e2de2",
|
||||
"F3cddb7852bcd011e080ecbf8636d9b00240d2f4d9c1859933fefc949e2776b0e",
|
||||
"F397987bca64f279caecdebbb8a2a61673d2a93b39463687ebe6423c05260712f",
|
||||
"Fd3889e704d3acfaf6bb2ae6a742a313484ee4b3ecb62dd762448ef6b1871b7ed",
|
||||
"F0efbcbb1855f01aa38df74843fcccfdd4ddcd25a761ce3deaf855ce5d091dd2f",
|
||||
"F90ff22aeff14e65c5fe05d10cd195e781a54e491b46b9c5cc800b2da610f1017",
|
||||
"Fe0f86791caa1f387010c773ecabc1d9f5a9089529605d6c78dfef8f5a6a9de8f",
|
||||
"Fbc73d737f1a345fac411dcd40a3d1a8cef44d4a58c6a9c6cf906b728bb3e5fbb",
|
||||
"F68790dcd063be0e97a4be28792f0df2b18404e5766e96262b4ad6602c2906d1f",
|
||||
"F32051d649f84cab8153ccbf0d470e59d8db3ddf597e0626079503a40474c4590",
|
||||
"Fbb86790e40e74657fdb8c09fc4bc0734d2ff1324d0947fac7c0765b475282e2e",
|
||||
"Fa0e81b5ea91c4d7dfe5cea5737219d3c0f951c2d946244af2f5a0a8a8364f6b9",
|
||||
"F5b62e48240db08753dd1231943d9b74153f36a9efb5654c6eec3e769a51cf764",
|
||||
"Faa63073e3a3316175b6f87aa8eb6ac87f54284aa19d10671479811adf1776d56",
|
||||
"Fbec6b88a363b3f928a1eafe16c3ad2328b31c98bb8187b6736dba6e6db985806",
|
||||
"F60644ce31e027d968a123f6fe62e3af7c5de74049703dbb04429417a54cbc60f",
|
||||
"F93b8b04bced6a044e569b2a76c3aaec19ea1e3cbba5d30a5f1d7e4bda76f727e",
|
||||
"F305c511b904f21ff3741127b35fbd6f90e27b7cbeda76445423be58609671aad",
|
||||
"Feaec120e8f6f1814a3f7500d7d0658310a6facf0ebafde55aadee9dbe530e6c8",
|
||||
"F7d37163f40263b6fb60f41632b13fbdb472d0814f683e8ef8430ccdaf2a8b8cf",
|
||||
"F6bef8e774b33e452a515158e5d830dba5a29a535a969b3b3a20a105c79b51646",
|
||||
"Fab36c11cc2fc91225bddc1eb2608fdcd0d229975d714045e6da22453cfbcce90",
|
||||
"Fc4be7c884f942a7233b31261828f155290f86d3f7d9e333964004ffd7d6cae22",
|
||||
"F611bba57563b06bf96e3a110a7ad542817ebb5b46049c6a7ef29c4fe3c61259b",
|
||||
"F798dd21ed158a1bbd9113e1fe46672e708e233281ac77596be4d6431a5cd5005"
|
||||
],
|
||||
"game": "ongeki",
|
||||
"inactive": false,
|
||||
"playtype": "Single",
|
||||
"tableID": "ongeki-Single-brightMemory3-levels",
|
||||
"title": "O.N.G.E.K.I. (bright MEMORY Act.3)"
|
||||
},
|
||||
{
|
||||
"default": false,
|
||||
"description": "Difficulties for O.N.G.E.K.I. in bright MEMORY Act.3.",
|
||||
"folders": [
|
||||
"F73070f7c2edc7e037775d9965a151dfb6d55df53a68211df7805784d063427b6",
|
||||
"F21adc25f54a6ce919f75a18f6575569b0869a4762f776f9a844945222c1b8ecd",
|
||||
"F222a2f1516fd944711e27feb6de3c2502cc21324a1cc5d1af6c6a562d7bf78bf",
|
||||
"Fc783b12bc69fe0f7ee6ccecfa14a329e25aa8e7274c8696ee9d9f9dc61f11992",
|
||||
"F095c535be64825c713bebc26d34a0ac8449d487bee1fed3f7df37c16f30b37e4",
|
||||
"Fddd5da8078803f869db6dc4e92220c3291a1163f25f375d32525160aed09dc8e"
|
||||
],
|
||||
"game": "ongeki",
|
||||
"inactive": false,
|
||||
"playtype": "Single",
|
||||
"tableID": "ongeki-Single-brightMemory3-difficulties",
|
||||
"title": "O.N.G.E.K.I. (bright MEMORY Act.3) (Difficulties)"
|
||||
},
|
||||
{
|
||||
"default": true,
|
||||
"description": "The Insane PMS Table. This table starts around pop'n level 46.",
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
"monk": "^7.3.4",
|
||||
"node-fetch": "2.6.7",
|
||||
"prudence": "0.10.0",
|
||||
"cheerio": "^1.0.0-rc.12",
|
||||
"tachi-common": "workspace:../../common"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,176 @@
|
||||
import { Command } from "commander";
|
||||
import { CreateChartID, ReadCollection, WriteCollection } from "../../util";
|
||||
import { ChartDocument, Difficulties, SongDocument } from "tachi-common";
|
||||
import * as cheerio from "cheerio";
|
||||
|
||||
const SXGA_DATA_URL = "https://ongeki.sxga.jp/assets/json/music/music.json".replace("x", "e");
|
||||
const CURRENT_VERSION = "brightMemory3";
|
||||
const CURRENT_VERSION_PRETTY = "bright MEMORY Act.3";
|
||||
|
||||
type OngekiChart = ChartDocument<"ongeki:Single">;
|
||||
type OngekiSong = SongDocument<"ongeki">;
|
||||
|
||||
interface SxgaEntry {
|
||||
date: string;
|
||||
title: string;
|
||||
artist: string;
|
||||
category: string;
|
||||
lunatic: "" | "1";
|
||||
}
|
||||
|
||||
// Wikiwiki really sucks
|
||||
const unnormalizeTitle = (title: string): string =>
|
||||
title
|
||||
.replace("...", "…")
|
||||
.replace(/:/gu, ":")
|
||||
.replace(/\(/gu, "(")
|
||||
.replace(/\)/gu, ")")
|
||||
.replace(/!/gu, "!")
|
||||
.replace(/\?/gu, "?")
|
||||
.replace(/~/gu, "~");
|
||||
|
||||
const scrapeWikiwiki = async (
|
||||
charts: OngekiChart[],
|
||||
songID: number,
|
||||
title: string,
|
||||
lunatic: boolean,
|
||||
unnormalized = false
|
||||
) => {
|
||||
const res = await fetch(`https://wikiwiki.jp/gameongeki/${title.replace(/ /gu, "%20")}`);
|
||||
const $ = cheerio.load(await res.text());
|
||||
const tables = $("#content tbody").toArray();
|
||||
if (tables.length < 2) {
|
||||
if (unnormalized) {
|
||||
throw new Error(`${title}: Invalid wikipage: expected 2 tables, got ${tables.length}`);
|
||||
} else {
|
||||
return scrapeWikiwiki(charts, songID, unnormalizeTitle(title), lunatic, true);
|
||||
}
|
||||
}
|
||||
const diffTable = tables![1];
|
||||
const diffRows = $(diffTable).find("tr").toArray();
|
||||
|
||||
const diffsToParse: Difficulties["ongeki:Single"][] = lunatic
|
||||
? ["LUNATIC"]
|
||||
: ["BASIC", "ADVANCED", "EXPERT", "MASTER"];
|
||||
for (const diff of diffsToParse) {
|
||||
const row = diffRows.find((row) => $(row.firstChild!).html() === diff);
|
||||
|
||||
const level = $(row?.children[1]).html();
|
||||
const noteCountRaw = $(row?.children[2]).html();
|
||||
const bellCountRaw = $(row?.children[3]).html();
|
||||
const internalLevelRaw = $(row?.children[4]).html();
|
||||
|
||||
let noteCount: number | undefined;
|
||||
let bellCount: number | undefined;
|
||||
let internalLevel: number;
|
||||
|
||||
if (!level) {
|
||||
throw new Error(`${title} ${diff}: Invalid wikipage: ${diff} has no level`);
|
||||
}
|
||||
|
||||
if (!noteCountRaw) {
|
||||
if (diff === "MASTER" || diff === "LUNATIC") {
|
||||
// This value is necessary for platinum delta calculation
|
||||
throw new Error(`${title} ${diff}: unknown note count`);
|
||||
} else {
|
||||
console.log(`warn: ${title} ${diff}: unknown note count`);
|
||||
}
|
||||
} else {
|
||||
noteCount = parseInt(noteCountRaw, 10);
|
||||
}
|
||||
if (!bellCountRaw) {
|
||||
console.log(`warn: ${title} ${diff}: unknown bell count`);
|
||||
} else {
|
||||
bellCount = parseInt(bellCountRaw, 10);
|
||||
}
|
||||
if (!internalLevelRaw) {
|
||||
console.log(`warn: ${title} ${diff}: unknown internal level (will be deduced)`);
|
||||
internalLevel = parseInt(level, 10);
|
||||
if (level.endsWith("+")) {
|
||||
internalLevel += 0.7;
|
||||
}
|
||||
} else {
|
||||
internalLevel = parseFloat(internalLevelRaw);
|
||||
}
|
||||
|
||||
const newChart: OngekiChart = {
|
||||
difficulty: diff,
|
||||
chartID: CreateChartID(),
|
||||
songID,
|
||||
level,
|
||||
levelNum: internalLevel,
|
||||
isPrimary: true,
|
||||
playtype: "Single",
|
||||
data: {
|
||||
displayVersion: CURRENT_VERSION_PRETTY,
|
||||
totalBellCount: bellCount,
|
||||
totalNoteCount: noteCount,
|
||||
isHot: true,
|
||||
},
|
||||
versions: [CURRENT_VERSION, `${CURRENT_VERSION}Omni`],
|
||||
};
|
||||
|
||||
charts.push(newChart);
|
||||
}
|
||||
};
|
||||
|
||||
const main = async ({ date }) => {
|
||||
const charts: OngekiChart[] = ReadCollection("charts-ongeki.json");
|
||||
const songs: OngekiSong[] = ReadCollection("songs-ongeki.json");
|
||||
|
||||
const sxgaEntriesRaw = await fetch(SXGA_DATA_URL);
|
||||
const sxgaEntries: SxgaEntry[] = (await sxgaEntriesRaw.json()).filter(
|
||||
(o: SxgaEntry) => o.date >= date
|
||||
);
|
||||
for (const entry of sxgaEntries) {
|
||||
const queriedDiff = entry.lunatic ? "LUNATIC" : "MASTER";
|
||||
const song = songs.find((song) => song.title === entry.title);
|
||||
|
||||
console.log(`${entry.title} ${queriedDiff}`);
|
||||
|
||||
if (song) {
|
||||
const chart = charts.find(
|
||||
(chart) => chart.songID === song.id && chart.difficulty === queriedDiff
|
||||
);
|
||||
if (chart) {
|
||||
console.log(`\tskipping`);
|
||||
} else {
|
||||
console.log(`\t\x1b[34mnew chart\x1b[0m`);
|
||||
|
||||
scrapeWikiwiki(charts, song.id, entry.title, entry.lunatic ? true : false);
|
||||
}
|
||||
} else {
|
||||
console.log(`\t\x1b[31mnew song\x1b[0m`);
|
||||
|
||||
songs.push({
|
||||
title: entry.title,
|
||||
artist: entry.artist,
|
||||
id: songs[songs.length - 1]!.id + 1,
|
||||
searchTerms: [],
|
||||
altTitles: [],
|
||||
data: {
|
||||
genre: entry.category,
|
||||
},
|
||||
});
|
||||
|
||||
scrapeWikiwiki(
|
||||
charts,
|
||||
songs[songs.length - 1]!.id,
|
||||
entry.title,
|
||||
entry.lunatic ? true : false
|
||||
);
|
||||
}
|
||||
|
||||
console.log("");
|
||||
}
|
||||
|
||||
WriteCollection("songs-ongeki.json", songs);
|
||||
WriteCollection("charts-ongeki.json", charts);
|
||||
};
|
||||
|
||||
new Command()
|
||||
.name("Fetch Ongeki")
|
||||
.description("Fetch new Ongeki charts.")
|
||||
.requiredOption("--date <date>", "Start date as YYYYMMDD, e.g. 20231228")
|
||||
.action(main)
|
||||
.parse();
|
||||
@@ -0,0 +1,95 @@
|
||||
# O.N.G.E.K.I. Support
|
||||
|
||||
This game has the internal GPTString of `ongeki:Single`.
|
||||
|
||||
!!! note
|
||||
For information on what each section means, please see [Common Config](../common-config/index.md).
|
||||
|
||||
## Metrics
|
||||
|
||||
For more information on what metrics are and how they work, see [TODO]!
|
||||
|
||||
### Provided Metrics
|
||||
|
||||
| Metric Name | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `score` | Integer | Known in-game as 'Technical Score'. It ranges between 0 and 1,010,000, where notes are worth 950,000, and bells 60,000. |
|
||||
| `noteLamp` | "LOSS", "CLEAR", "FULL COMBO", "ALL BREAK" | The first lamp. A clear is either a draw or a win. |
|
||||
| `bellLamp` | "FULL BELL" | The second lamp that tracks whether all bells in the chart have been collected. |
|
||||
|
||||
### Derived Metrics
|
||||
|
||||
| Metric Name | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `grade` | "D", "C", "B", "BB", "BBB", "A", "AA", "AAA", "S", "SS", "SSS", "SSS+" | The grade this score was. |
|
||||
|
||||
### Optional Metrics
|
||||
|
||||
| Metric Name | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `fast` | Integer | The number of mistakes in this score that were a result of hitting early. |
|
||||
| `slow` | Integer | The number of mistakes in this score that were a result of hitting late. |
|
||||
| `maxCombo` | Integer | The largest combo in this score. |
|
||||
| `bellCount` | Integer | The numer of bells collected. |
|
||||
| `damage` | Integer | The number of damage ticks received. |
|
||||
| `platScore` | Integer | The Platinum Score value. Only exists in MASTER and LUNATIC charts. |
|
||||
|
||||
## Judgements
|
||||
|
||||
The folowing judgements are defined:
|
||||
|
||||
- `cbreak`
|
||||
- `break`
|
||||
- `hit`
|
||||
- `miss`
|
||||
|
||||
Note: `pbreak`s are not tracked separately but can be deduced from `platScore`.
|
||||
|
||||
## Rating Algorithms
|
||||
|
||||
### Score Rating Algorithms
|
||||
|
||||
| Name | Description |
|
||||
| :: | :: |
|
||||
| `rating` | The rating value of this score. This is identical to the system used in game. |
|
||||
|
||||
### Session Rating Algorithms
|
||||
|
||||
| Name | Description |
|
||||
| :: | :: |
|
||||
| `naiveRating` | The average of your best 10 ratings this session. |
|
||||
|
||||
### Profile Rating Algorithms
|
||||
|
||||
| Name | Description |
|
||||
| :: | :: |
|
||||
| `naiveRating` | The average of your best 45 ratings. |
|
||||
|
||||
## Difficulties
|
||||
|
||||
- `BASIC`
|
||||
- `ADVANCED`
|
||||
- `EXPERT`
|
||||
- `MASTER`
|
||||
- `LUNATIC`
|
||||
|
||||
Note: since bright MEMORY Act.3, "crazy" charts and "Re:MASTER" charts are in separate folders, but both ought to be tracked as LUNATIC.
|
||||
|
||||
## Classes
|
||||
|
||||
| Name | Type | Values |
|
||||
| :: | :: | :: |
|
||||
| `colour` | DERIVED | BLUE, GREEN, ORANGE, RED, PURPLE, COPPER, SILVER, GOLD, PLATINUM, RAINBOW |
|
||||
|
||||
## Versions
|
||||
|
||||
| ID | Pretty Name |
|
||||
| :: | :: |
|
||||
| `brightMemory2` | bright MEMORY Act.2 |
|
||||
| `brightMemory3` | bright MEMORY Act.3 |
|
||||
|
||||
## Supported Match Types
|
||||
|
||||
- `songTitle`
|
||||
- `tachiSongID`
|
||||
|
||||
@@ -71,6 +71,7 @@ nav:
|
||||
- "game-support/games/pms-Controller.md"
|
||||
- "game-support/games/pms-Keyboard.md"
|
||||
- "game-support/games/itg-Stamina.md"
|
||||
- "game-support/games/ongeki-Single.md"
|
||||
|
||||
- Wiki:
|
||||
- "wiki/index.md"
|
||||
|
||||
Generated
+93
-6
@@ -295,8 +295,8 @@ importers:
|
||||
specifier: ^5.6.1
|
||||
version: 5.6.1(@babel/core@7.1.0)(@types/react@17.0.13)(react-dom@17.0.2)(react@17.0.2)
|
||||
rg-stats:
|
||||
specifier: 0.5.4
|
||||
version: 0.5.4
|
||||
specifier: 0.5.6
|
||||
version: 0.5.6
|
||||
sync-fetch:
|
||||
specifier: ^0.3.1
|
||||
version: 0.3.1
|
||||
@@ -386,6 +386,9 @@ importers:
|
||||
chalk:
|
||||
specifier: '4'
|
||||
version: 4.1.2
|
||||
cheerio:
|
||||
specifier: ^1.0.0-rc.12
|
||||
version: 1.0.0-rc.12
|
||||
commander:
|
||||
specifier: ^8.2.0
|
||||
version: 8.3.0
|
||||
@@ -656,8 +659,8 @@ importers:
|
||||
specifier: 3.1.2
|
||||
version: 3.1.2
|
||||
rg-stats:
|
||||
specifier: 0.5.4
|
||||
version: 0.5.4
|
||||
specifier: 0.5.6
|
||||
version: 0.5.6
|
||||
rimraf:
|
||||
specifier: 3.0.2
|
||||
version: 3.0.2
|
||||
@@ -4649,6 +4652,30 @@ packages:
|
||||
resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==}
|
||||
dev: false
|
||||
|
||||
/cheerio-select@2.1.0:
|
||||
resolution: {integrity: sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==}
|
||||
dependencies:
|
||||
boolbase: 1.0.0
|
||||
css-select: 5.1.0
|
||||
css-what: 6.1.0
|
||||
domelementtype: 2.3.0
|
||||
domhandler: 5.0.3
|
||||
domutils: 3.1.0
|
||||
dev: false
|
||||
|
||||
/cheerio@1.0.0-rc.12:
|
||||
resolution: {integrity: sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==}
|
||||
engines: {node: '>= 6'}
|
||||
dependencies:
|
||||
cheerio-select: 2.1.0
|
||||
dom-serializer: 2.0.0
|
||||
domhandler: 5.0.3
|
||||
domutils: 3.1.0
|
||||
htmlparser2: 8.0.2
|
||||
parse5: 7.1.2
|
||||
parse5-htmlparser2-tree-adapter: 7.0.0
|
||||
dev: false
|
||||
|
||||
/chokidar@3.5.3:
|
||||
resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==}
|
||||
engines: {node: '>= 8.10.0'}
|
||||
@@ -5031,6 +5058,16 @@ packages:
|
||||
nth-check: 2.1.1
|
||||
dev: false
|
||||
|
||||
/css-select@5.1.0:
|
||||
resolution: {integrity: sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==}
|
||||
dependencies:
|
||||
boolbase: 1.0.0
|
||||
css-what: 6.1.0
|
||||
domhandler: 5.0.3
|
||||
domutils: 3.1.0
|
||||
nth-check: 2.1.1
|
||||
dev: false
|
||||
|
||||
/css-what@6.1.0:
|
||||
resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==}
|
||||
engines: {node: '>= 6'}
|
||||
@@ -5377,6 +5414,14 @@ packages:
|
||||
entities: 2.2.0
|
||||
dev: false
|
||||
|
||||
/dom-serializer@2.0.0:
|
||||
resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==}
|
||||
dependencies:
|
||||
domelementtype: 2.3.0
|
||||
domhandler: 5.0.3
|
||||
entities: 4.5.0
|
||||
dev: false
|
||||
|
||||
/domelementtype@2.3.0:
|
||||
resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==}
|
||||
dev: false
|
||||
@@ -5388,6 +5433,13 @@ packages:
|
||||
domelementtype: 2.3.0
|
||||
dev: false
|
||||
|
||||
/domhandler@5.0.3:
|
||||
resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==}
|
||||
engines: {node: '>= 4'}
|
||||
dependencies:
|
||||
domelementtype: 2.3.0
|
||||
dev: false
|
||||
|
||||
/domutils@2.8.0:
|
||||
resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==}
|
||||
dependencies:
|
||||
@@ -5396,6 +5448,14 @@ packages:
|
||||
domhandler: 4.3.1
|
||||
dev: false
|
||||
|
||||
/domutils@3.1.0:
|
||||
resolution: {integrity: sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==}
|
||||
dependencies:
|
||||
dom-serializer: 2.0.0
|
||||
domelementtype: 2.3.0
|
||||
domhandler: 5.0.3
|
||||
dev: false
|
||||
|
||||
/dot-case@3.0.4:
|
||||
resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==}
|
||||
dependencies:
|
||||
@@ -5523,6 +5583,11 @@ packages:
|
||||
resolution: {integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==}
|
||||
dev: false
|
||||
|
||||
/entities@4.5.0:
|
||||
resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
|
||||
engines: {node: '>=0.12'}
|
||||
dev: false
|
||||
|
||||
/env-paths@2.2.1:
|
||||
resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==}
|
||||
engines: {node: '>=6'}
|
||||
@@ -7047,6 +7112,15 @@ packages:
|
||||
terser: 5.16.1
|
||||
dev: false
|
||||
|
||||
/htmlparser2@8.0.2:
|
||||
resolution: {integrity: sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==}
|
||||
dependencies:
|
||||
domelementtype: 2.3.0
|
||||
domhandler: 5.0.3
|
||||
domutils: 3.1.0
|
||||
entities: 4.5.0
|
||||
dev: false
|
||||
|
||||
/http-cache-semantics@4.1.1:
|
||||
resolution: {integrity: sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==}
|
||||
dev: true
|
||||
@@ -9215,6 +9289,19 @@ packages:
|
||||
json-parse-even-better-errors: 2.3.1
|
||||
lines-and-columns: 1.2.4
|
||||
|
||||
/parse5-htmlparser2-tree-adapter@7.0.0:
|
||||
resolution: {integrity: sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==}
|
||||
dependencies:
|
||||
domhandler: 5.0.3
|
||||
parse5: 7.1.2
|
||||
dev: false
|
||||
|
||||
/parse5@7.1.2:
|
||||
resolution: {integrity: sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==}
|
||||
dependencies:
|
||||
entities: 4.5.0
|
||||
dev: false
|
||||
|
||||
/parseurl@1.3.3:
|
||||
resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==}
|
||||
engines: {node: '>= 0.8'}
|
||||
@@ -10084,8 +10171,8 @@ packages:
|
||||
resolution: {integrity: sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==}
|
||||
dev: false
|
||||
|
||||
/rg-stats@0.5.4:
|
||||
resolution: {integrity: sha512-eatjAvoh7sXH1lvRhUlPLmSMkr1qUwHmZKroZp3HsLDafgxyqQB/UklkXWp7/+7X35CB/tPr/psMDuiT1ybZ6w==}
|
||||
/rg-stats@0.5.6:
|
||||
resolution: {integrity: sha512-4YG942ztdaXd27KUABxiQAA3aJuLvLcvjdLfoxKqn89P7967bfyG5Z5OWaAg9Sr8pvhsABA4bHZuBAKtSW90xA==}
|
||||
dev: false
|
||||
|
||||
/rimraf@2.4.5:
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ test-env:
|
||||
- MONGO_URL=127.0.0.1
|
||||
- REDIS_URL=127.0.0.1
|
||||
- TCHIS_CONF_LOCATION=test.conf.json5
|
||||
- LOG_LEVEL=verbose
|
||||
- LOG_LEVEL=info
|
||||
# On a local machine, these tests run in under 2-3 minutes.
|
||||
# On github actions, these tests can take an inordinate amount of time.
|
||||
# The timeout is set to 10 minutes, which is ridiculous.
|
||||
|
||||
+1
-1
@@ -93,7 +93,7 @@
|
||||
"prudence": "0.10.0",
|
||||
"rate-limit-redis": "2.1.0",
|
||||
"redis": "3.1.2",
|
||||
"rg-stats": "0.5.4",
|
||||
"rg-stats": "0.5.6",
|
||||
"rimraf": "3.0.2",
|
||||
"safe-json-stringify": "1.2.0",
|
||||
"semver": "^7.3.7",
|
||||
|
||||
@@ -8,6 +8,7 @@ import { JUBEAT_IMPL } from "./games/jubeat";
|
||||
import { MAIMAI_IMPL } from "./games/maimai";
|
||||
import { MAIMAIDX_IMPL } from "./games/maimaidx";
|
||||
import { MUSECA_IMPL } from "./games/museca";
|
||||
import { ONGEKI_IMPL } from "./games/ongeki";
|
||||
import { POPN_9B_IMPL } from "./games/popn";
|
||||
import { SDVX_IMPL } from "./games/sdvx";
|
||||
import { USC_CONTROLLER_IMPL, USC_KEYBOARD_IMPL } from "./games/usc";
|
||||
@@ -43,4 +44,5 @@ export const GPT_SERVER_IMPLEMENTATIONS: GPTImplementations = {
|
||||
"usc:Keyboard": USC_KEYBOARD_IMPL,
|
||||
"sdvx:Single": SDVX_IMPL,
|
||||
"arcaea:Touch": ARCAEA_IMPL,
|
||||
"ongeki:Single": ONGEKI_IMPL,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
import { ONGEKI_IMPL } from "./ongeki";
|
||||
import db from "external/mongo/db";
|
||||
import CreateLogCtx from "lib/logger/logger";
|
||||
import { CreatePBDoc } from "lib/score-import/framework/pb/create-pb-doc";
|
||||
import { ONGEKI_BELL_LAMPS, ONGEKI_GRADES, ONGEKI_NOTE_LAMPS } from "tachi-common";
|
||||
import t from "tap";
|
||||
import { dmf, mkMockPB, mkMockScore } from "test-utils/misc";
|
||||
import ResetDBState from "test-utils/resets";
|
||||
import { TestingOngekiChart } from "test-utils/test-data";
|
||||
import type { ProvidedMetrics, ScoreData } from "tachi-common";
|
||||
|
||||
const baseMetrics: ProvidedMetrics["ongeki:Single"] = {
|
||||
noteLamp: "CLEAR",
|
||||
bellLamp: "FULL BELL",
|
||||
score: 1_001_500,
|
||||
};
|
||||
|
||||
const scoreData: ScoreData<"ongeki:Single"> = {
|
||||
noteLamp: "CLEAR",
|
||||
bellLamp: "FULL BELL",
|
||||
score: 1_001_500,
|
||||
grade: "SSS",
|
||||
judgements: {},
|
||||
optional: { enumIndexes: {}, bellCount: 100, platScore: 1000 },
|
||||
enumIndexes: {
|
||||
grade: ONGEKI_GRADES.SSS,
|
||||
noteLamp: ONGEKI_NOTE_LAMPS.CLEAR,
|
||||
bellLamp: ONGEKI_BELL_LAMPS.FULL_BELL,
|
||||
},
|
||||
};
|
||||
|
||||
const mockScore = mkMockScore("ongeki", "Single", TestingOngekiChart, scoreData);
|
||||
const mockPB = mkMockPB("ongeki", "Single", TestingOngekiChart, scoreData);
|
||||
|
||||
const logger = CreateLogCtx(__filename);
|
||||
|
||||
t.test("ONGEKI Implementation", (t) => {
|
||||
t.test("Grade Deriver", (t) => {
|
||||
const f = (score: number, expected: any) =>
|
||||
t.equal(
|
||||
ONGEKI_IMPL.derivers.grade(dmf(baseMetrics, { score }), TestingOngekiChart),
|
||||
expected,
|
||||
`A score of ${score.toLocaleString()} should result in grade=${expected}.`
|
||||
);
|
||||
|
||||
f(0, "D");
|
||||
f(500_000, "C");
|
||||
f(700_000, "B");
|
||||
f(750_000, "BB");
|
||||
f(800_000, "BBB");
|
||||
f(850_000, "A");
|
||||
f(900_000, "AA");
|
||||
f(940_000, "AAA");
|
||||
f(970_000, "S");
|
||||
f(990_000, "SS");
|
||||
f(1_000_000, "SSS");
|
||||
f(1_007_500, "SSS+");
|
||||
f(1_010_000, "SSS+");
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
t.test("Rating Calc", (t) => {
|
||||
t.equal(
|
||||
ONGEKI_IMPL.scoreCalcs.rating(scoreData, TestingOngekiChart),
|
||||
12.1,
|
||||
"Basic rating check"
|
||||
);
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
t.todo("Session Calcs");
|
||||
t.todo("Profile Calcs");
|
||||
|
||||
t.test("Colour Deriver", (t) => {
|
||||
const f = (v: number | null, expected: any) =>
|
||||
t.equal(
|
||||
ONGEKI_IMPL.classDerivers.colour({ naiveRating: v }),
|
||||
expected,
|
||||
`A naiveRating of ${v} should result in ${expected}.`
|
||||
);
|
||||
|
||||
f(null, null);
|
||||
f(0, "BLUE");
|
||||
f(2, "GREEN");
|
||||
f(4, "ORANGE");
|
||||
f(7, "RED");
|
||||
f(10, "PURPLE");
|
||||
f(12, "COPPER");
|
||||
f(13, "SILVER");
|
||||
f(14, "GOLD");
|
||||
f(14.5, "PLATINUM");
|
||||
f(15, "RAINBOW");
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
t.test("Goal Formatters", (t) => {
|
||||
t.test("Criteria", (t) => {
|
||||
t.equal(
|
||||
ONGEKI_IMPL.goalCriteriaFormatters.score(1_008_182),
|
||||
"Get a score of 1,008,182 on"
|
||||
);
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
t.test("Progress", (t) => {
|
||||
const f = (
|
||||
k: keyof typeof ONGEKI_IMPL.goalProgressFormatters,
|
||||
modifant: Partial<ScoreData<"ongeki:Single">>,
|
||||
goalValue: any,
|
||||
expected: any
|
||||
) =>
|
||||
t.equal(
|
||||
ONGEKI_IMPL.goalProgressFormatters[k](
|
||||
dmf(mockPB, {
|
||||
scoreData: modifant,
|
||||
}),
|
||||
goalValue
|
||||
),
|
||||
expected
|
||||
);
|
||||
|
||||
f("grade", { grade: "S", score: 987_342 }, ONGEKI_GRADES.S, "SS-2.7K");
|
||||
f("score", { score: 982_123 }, 1_000_000, "982,123");
|
||||
f("noteLamp", { noteLamp: "CLEAR" }, ONGEKI_NOTE_LAMPS.CLEAR, "CLEAR");
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
t.test("Out Of", (t) => {
|
||||
t.equal(ONGEKI_IMPL.goalOutOfFormatters.score(1_001_003), "1,001,003");
|
||||
t.equal(ONGEKI_IMPL.goalOutOfFormatters.score(983_132), "983,132");
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
t.test("PB Merging", (t) => {
|
||||
t.beforeEach(ResetDBState);
|
||||
|
||||
t.test("Should join best lamp", async (t) => {
|
||||
await db.scores.insert(mockScore);
|
||||
await db.scores.insert(
|
||||
dmf(mockScore, {
|
||||
scoreID: "bestLamp",
|
||||
scoreData: {
|
||||
score: 0,
|
||||
noteLamp: "FULL COMBO",
|
||||
enumIndexes: { noteLamp: ONGEKI_NOTE_LAMPS.FULL_COMBO },
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
t.hasStrict(await CreatePBDoc("ongeki:Single", 1, TestingOngekiChart, logger), {
|
||||
composedFrom: [
|
||||
{ name: "Best Score" },
|
||||
{ name: "Best Note Lamp", scoreID: "bestLamp" },
|
||||
],
|
||||
scoreData: {
|
||||
score: mockScore.scoreData.score,
|
||||
noteLamp: "FULL COMBO",
|
||||
bellLamp: "FULL BELL",
|
||||
enumIndexes: {
|
||||
noteLamp: ONGEKI_NOTE_LAMPS.FULL_COMBO,
|
||||
bellLamp: ONGEKI_BELL_LAMPS.FULL_BELL,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
t.end();
|
||||
});
|
||||
@@ -0,0 +1,145 @@
|
||||
/* eslint-disable no-confusing-arrow */
|
||||
import { GoalFmtScore, GoalOutOfFmtScore, GradeGoalFormatter } from "./_common";
|
||||
import { CreatePBMergeFor } from "game-implementations/utils/pb-merge";
|
||||
import { ProfileAvgBestN } from "game-implementations/utils/profile-calc";
|
||||
import { SessionAvgBest10For } from "game-implementations/utils/session-calc";
|
||||
import { ONGEKIRating } from "rg-stats";
|
||||
import { ONGEKI_GBOUNDARIES, FmtNum, GetGrade } from "tachi-common";
|
||||
import { IsNullish } from "utils/misc";
|
||||
import type { GPTServerImplementation } from "game-implementations/types";
|
||||
import type { Difficulties } from "tachi-common";
|
||||
|
||||
export const OngekiPlatDiff = (diff: Difficulties["ongeki:Single"]) =>
|
||||
diff === "MASTER" || diff === "LUNATIC";
|
||||
|
||||
export const ONGEKI_IMPL: GPTServerImplementation<"ongeki:Single"> = {
|
||||
chartSpecificValidators: {
|
||||
platScore: (platScore, chart) => {
|
||||
if (!OngekiPlatDiff(chart.difficulty)) {
|
||||
// We don't care about other difficulties
|
||||
return true;
|
||||
}
|
||||
|
||||
if (platScore < 0) {
|
||||
return `Platinum Score must be non-negative. Got ${platScore}`;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
bellCount: (bellCount) => {
|
||||
if (bellCount < 0) {
|
||||
return `Bell Count must be non-negative. Got ${bellCount}`;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
totalBellCount: (bellCount) => {
|
||||
if (bellCount < 0) {
|
||||
return `Total bell Count must be non-negative. Got ${bellCount}`;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
damage: (damage) => {
|
||||
if (damage < 0) {
|
||||
return `Damage must be non-negative. Got ${damage}`;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
},
|
||||
derivers: {
|
||||
grade: ({ score }) => GetGrade(ONGEKI_GBOUNDARIES, score),
|
||||
},
|
||||
scoreCalcs: {
|
||||
rating: (scoreData, chart) =>
|
||||
chart.data.isUnranked || chart.levelNum === 0.0
|
||||
? 0
|
||||
: ONGEKIRating.calculate(scoreData.score, chart.levelNum),
|
||||
},
|
||||
sessionCalcs: { naiveRating: SessionAvgBest10For("rating") },
|
||||
profileCalcs: {
|
||||
naiveRating: ProfileAvgBestN("rating", 45),
|
||||
},
|
||||
classDerivers: {
|
||||
colour: (ratings) => {
|
||||
const rating = ratings.naiveRating;
|
||||
|
||||
if (IsNullish(rating)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (rating >= 15) {
|
||||
return "RAINBOW";
|
||||
} else if (rating >= 14.5) {
|
||||
return "PLATINUM";
|
||||
} else if (rating >= 14) {
|
||||
return "GOLD";
|
||||
} else if (rating >= 13) {
|
||||
return "SILVER";
|
||||
} else if (rating >= 12) {
|
||||
return "COPPER";
|
||||
} else if (rating >= 10) {
|
||||
return "PURPLE";
|
||||
} else if (rating >= 7) {
|
||||
return "RED";
|
||||
} else if (rating >= 4) {
|
||||
return "ORANGE";
|
||||
} else if (rating >= 2) {
|
||||
return "GREEN";
|
||||
}
|
||||
|
||||
return "BLUE";
|
||||
},
|
||||
},
|
||||
goalCriteriaFormatters: {
|
||||
score: GoalFmtScore,
|
||||
},
|
||||
goalProgressFormatters: {
|
||||
grade: (pb, gradeIndex) =>
|
||||
GradeGoalFormatter(
|
||||
ONGEKI_GBOUNDARIES,
|
||||
pb.scoreData.grade,
|
||||
pb.scoreData.score,
|
||||
ONGEKI_GBOUNDARIES[gradeIndex]?.name ?? "D"
|
||||
),
|
||||
noteLamp: (pb) => pb.scoreData.noteLamp,
|
||||
bellLamp: (pb) => pb.scoreData.bellLamp,
|
||||
score: (pb) => FmtNum(pb.scoreData.score),
|
||||
},
|
||||
goalOutOfFormatters: {
|
||||
score: GoalOutOfFmtScore,
|
||||
},
|
||||
pbMergeFunctions: [
|
||||
CreatePBMergeFor("largest", "optional.platScore", "Best Platinum Score", (base, score) => {
|
||||
base.scoreData.optional.platScore = score.scoreData.optional.platScore;
|
||||
}),
|
||||
CreatePBMergeFor("largest", "enumIndexes.noteLamp", "Best Note Lamp", (base, score) => {
|
||||
base.scoreData.noteLamp = score.scoreData.noteLamp;
|
||||
}),
|
||||
CreatePBMergeFor("largest", "enumIndexes.bellLamp", "Best Bell Lamp", (base, score) => {
|
||||
base.scoreData.bellLamp = score.scoreData.bellLamp;
|
||||
}),
|
||||
],
|
||||
defaultMergeRefName: "Best Score",
|
||||
scoreValidators: [
|
||||
(s) => {
|
||||
let { hit, miss } = s.scoreData.judgements;
|
||||
|
||||
hit ??= 0;
|
||||
miss ??= 0;
|
||||
|
||||
if (s.scoreData.noteLamp === "ALL BREAK") {
|
||||
if (hit + miss > 0) {
|
||||
return "Cannot have an ALL BREAK if not all hits were justice or better.";
|
||||
}
|
||||
}
|
||||
|
||||
if (s.scoreData.noteLamp === "FULL COMBO") {
|
||||
if (miss > 0) {
|
||||
return "Cannot have a FULL COMBO if the score has misses.";
|
||||
}
|
||||
}
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -35,6 +35,7 @@ function perToScore(goal: GoalDocument) {
|
||||
return v * 10_000_000;
|
||||
case "wacca":
|
||||
case "chunithm":
|
||||
case "ongeki":
|
||||
case "museca":
|
||||
return v * 1_000_000;
|
||||
case "popn":
|
||||
|
||||
@@ -82,6 +82,7 @@ const scoreMovers: ScoreMovers = {
|
||||
"pms:Controller": BMS_MV,
|
||||
"pms:Keyboard": BMS_MV,
|
||||
"chunithm:Single": NEUTRAL_MV,
|
||||
"ongeki:Single": NEUTRAL_MV,
|
||||
"usc:Controller": NEUTRAL_MV,
|
||||
"usc:Keyboard": NEUTRAL_MV,
|
||||
"sdvx:Single": NEUTRAL_MV,
|
||||
|
||||
@@ -1268,3 +1268,17 @@ export const TestingArcaeaSheriruthFTR: ChartDocument<"arcaea:Touch"> = {
|
||||
songID: 19,
|
||||
versions: ["mobile"],
|
||||
};
|
||||
|
||||
export const TestingOngekiChart: ChartDocument<"ongeki:Single"> = {
|
||||
chartID: "213796bdb6150f80ba6412ce69df1249e16c0cb0",
|
||||
data: {
|
||||
displayVersion: "bright MEMORY Act.3",
|
||||
},
|
||||
difficulty: "MASTER",
|
||||
isPrimary: true,
|
||||
level: "10",
|
||||
levelNum: 10.5,
|
||||
playtype: "Single",
|
||||
songID: 19,
|
||||
versions: ["brightMemory3", "brightMemory3Omni"],
|
||||
};
|
||||
|
||||
@@ -50,6 +50,7 @@
|
||||
"maimai",
|
||||
"maimaidx",
|
||||
"arcaea",
|
||||
"ongeki",
|
||||
],
|
||||
IMPORT_TYPES: [
|
||||
"file/eamusement-iidx-csv",
|
||||
|
||||
Reference in New Issue
Block a user