Pop'n Music support

Fixes #69
This commit is contained in:
zkldi
2021-12-24 03:52:56 +00:00
parent 36602a7f9e
commit 736fb1f0ee
13 changed files with 163 additions and 157 deletions
+1 -1
View File
@@ -82,7 +82,7 @@
"rimraf": "3.0.2",
"safe-json-stringify": "1.2.0",
"seq-logging": "^1.1.1",
"tachi-common": "0.4.7",
"tachi-common": "0.4.11",
"ts-node": "10.0.0",
"tsconfig-paths": "3.10.1",
"typescript": "4.3.4",
+4 -4
View File
@@ -63,7 +63,7 @@ specifiers:
safe-json-stringify: 1.2.0
seq-logging: ^1.1.1
supertest: 6.1.3
tachi-common: 0.4.7
tachi-common: 0.4.11
tap: 15.0.9
ts-node: 10.0.0
tsconfig-paths: 3.10.1
@@ -107,7 +107,7 @@ dependencies:
rimraf: 3.0.2
safe-json-stringify: 1.2.0
seq-logging: 1.1.1
tachi-common: 0.4.7
tachi-common: 0.4.11
ts-node: 10.0.0_83f53b0a0c5616d3fa00ed4e30b9ce1b
tsconfig-paths: 3.10.1
typescript: 4.3.4
@@ -5386,8 +5386,8 @@ packages:
strip-ansi: 6.0.0
dev: true
/tachi-common/0.4.7:
resolution: {integrity: sha512-tlntA6tOJSG7l2GNx7byktIma/2upSr+s1tTS1c/t+6IUCED9scsCTwDcqaRSkjzz+fvx3SMg79jyyKIu0Wt9Q==}
/tachi-common/0.4.11:
resolution: {integrity: sha512-jbo432IgMJUXTza7xrDNxBF2dqjGjZuNRRdkfWJI051QbusP2+JZ4+lilKxwpErZnIiZlB07wu/G5iUscFHPVA==}
dependencies:
monk: 7.3.4
transitivePeerDependencies:
+1
View File
@@ -85,6 +85,7 @@ const staticIndexes: Partial<Record<Databases, Index[]>> = {
counters: [index({ counterName: 1 }, UNIQUE)],
"class-achievements": [index({ game: 1, playtype: 1, timeAchieved: 1 })],
"api-clients": [index({ clientID: 1 }, UNIQUE)],
"charts-popn": [index({ "data.hashSHA256": 1 }, UNIQUE)],
};
const indexes: Partial<Record<Databases, Index[]>> = staticIndexes;
-139
View File
@@ -1,139 +0,0 @@
/* eslint-disable no-await-in-loop */
import db from "external/mongo/db";
import CreateLogCtx from "lib/logger/logger";
import fetch from "node-fetch";
import { ChartDocument, Difficulties, IIDXBPIData } from "tachi-common";
import { RecalcGameProfiles } from "utils/calculations/recalc-game-profiles";
import { RecalcAllScores } from "utils/calculations/recalc-scores";
import { RecalcSessions } from "utils/calculations/recalc-sessions";
import { FindChartWithPTDFVersion } from "utils/queries/charts";
import { FindSongOnTitle } from "utils/queries/songs";
const logger = CreateLogCtx(__filename);
const difficultyResolve: Record<string, [string, string]> = {
3: ["SP", "HYPER"],
4: ["SP", "ANOTHER"],
8: ["DP", "HYPER"],
9: ["DP", "ANOTHER"],
10: ["SP", "LEGGENDARIA"],
11: ["DP", "LEGGENDARIA"],
};
async function UpdatePoyashiData() {
logger.info("Fetching BPI Poyashi data from proxy...");
const rj = await fetch("https://proxy.poyashi.me/?type=bpi").then((r) => r.json());
logger.info("Fetched data.");
const data = rj;
const realData: IIDXBPIData[] = [];
for (const d of data.body) {
const res = difficultyResolve[d.difficulty];
if (!res) {
throw new Error(`Unknown difficulty ${d.difficulty}`);
}
const [playtype, diff] = res;
const tachiSong = await FindSongOnTitle("iidx", d.title);
if (!tachiSong) {
logger.warn(`Cannot find song ${d.title}?`);
continue;
}
const tachiChart = (await FindChartWithPTDFVersion(
"iidx",
tachiSong.id,
playtype as "SP" | "DP",
diff as Difficulties["iidx:DP" | "iidx:SP"],
"29"
)) as ChartDocument<"iidx:SP" | "iidx:DP">;
if (!tachiChart) {
logger.warn(
`Cannot find chart ${tachiSong.title} (${tachiSong.id}) ${playtype}, ${diff}?`
);
continue;
}
const kavg = Number(d.avg);
if (kavg < 0) {
logger.warn(
`${tachiSong.title} (${playtype} ${diff}). Invalid kavg ${d.avg}, Skipping.`
);
continue;
}
if (d.removed) {
logger.info(`Skipping removed chart ${tachiSong.title}.`);
continue;
}
realData.push({
coef: d.coef === -1 ? null : d.coef,
kavg: Number(d.avg),
wr: Number(d.wr),
chartID: tachiChart.chartID,
});
}
const updatedCharts = [];
for (const newData of realData) {
const oldData = await db["iidx-bpi-data"].findOne({
chartID: newData.chartID,
});
if (!oldData) {
updatedCharts.push(newData.chartID);
await db["iidx-bpi-data"].insert({
chartID: newData.chartID,
coef: newData.coef,
kavg: newData.kavg,
wr: newData.wr,
});
} else if (
oldData.wr !== newData.wr ||
oldData.kavg !== newData.kavg ||
oldData.coef !== newData.coef
) {
updatedCharts.push(newData.chartID);
await db["iidx-bpi-data"].update(
{
chartID: newData.chartID,
},
{
$set: {
wr: newData.wr,
kavg: newData.kavg,
coef: newData.coef,
},
}
);
}
}
if (updatedCharts.length === 0) {
logger.info(`Nothing has changed. Not updating any BPI data.`);
return;
}
logger.info(`Triggering IIDX Recalc for ${updatedCharts.length} charts.`);
await RecalcAllScores({ game: "iidx", chartID: { $in: updatedCharts } });
// @optimisable -- can be filtered to just scores that were recalced.
await RecalcSessions({ game: "iidx" });
await RecalcGameProfiles({ game: "iidx" });
logger.info(`Done.`);
}
if (require.main === module) {
UpdatePoyashiData().then(() => process.exit(0));
}
@@ -158,6 +158,58 @@ t.test("#ResolveMatchTypeToKTData", (t) => {
t.end();
});
t.test("Should resolve for the popn chartHash if the matchType is popnChartHash", async (t) => {
const chartHash = "2c26d666fa7c907e85115dbb279c267c14a263d47b2d46a93f99eae49d779119";
const popnContext: BatchManualContext = deepmerge(context, {
game: "popn",
playtype: "9B",
});
const res = await ResolveMatchTypeToKTData(
deepmerge(baseBatchManualScore, {
matchType: "popnChartHash",
identifier: chartHash,
}),
popnContext,
importType,
logger
);
t.hasStrict(
res,
{
song: { id: 1 },
chart: {
songID: 1,
data: { hashSHA256: chartHash },
playtype: "9B",
},
},
"Should return the right song and chart."
);
t.end();
});
t.test("Should reject if popnChartHash is used while game is not popn", async (t) => {
const chartHash = "2c26d666fa7c907e85115dbb279c267c14a263d47b2d46a93f99eae49d779119";
t.rejects(() =>
ResolveMatchTypeToKTData(
deepmerge(baseBatchManualScore, {
matchType: "popnChartHash",
identifier: chartHash,
}),
context,
importType,
logger
)
);
t.end();
});
t.test("Should resolve for the usc chartHash if the matchType is uscChartHash", async (t) => {
const chartHash = "USC_CHART_HASH";
@@ -141,6 +141,35 @@ export async function ResolveMatchTypeToKTData(
throw new InternalFailure(`DDR songID ${chart.songID} has charts but no parent song.`);
}
return { song, chart };
} else if (data.matchType === "popnChartHash") {
if (game !== "popn") {
throw new InvalidScoreFailure(`Cannot use popnChartHash lookup on ${game}.`);
}
const chart = await db.charts.popn.findOne({
playtype: context.playtype,
"data.hashSHA256": data.identifier,
});
if (!chart) {
throw new KTDataNotFoundFailure(
`Cannot find chart for popnChartHash ${data.identifier} (${context.playtype}).`,
importType,
data,
context
);
}
const song = await FindSongOnID(game, chart.songID);
if (!song) {
logger.severe(`Pop'n songID ${chart.songID} has charts but no parent song.`);
throw new InternalFailure(
`Pop'n songID ${chart.songID} has charts but no parent song.`
);
}
return { song, chart };
} else if (data.matchType === "tachiSongID") {
const songID = AssertStrAsPositiveInt(
@@ -71,6 +71,16 @@ const PR_ScoreMeta = (game: Game, playtype: Playtypes[Game]): PrudenceSchema =>
return {
inSkillAnalyser: "*?boolean",
};
} else if (game === "wacca") {
return { mirror: "*?boolean" };
} else if (game === "popn") {
return {
hiSpeed: optNull(p.isPositive),
hidden: optNull(p.isInteger),
sudden: optNull(p.isInteger),
random: optNull(p.isIn("NONRAN", "MIRROR", "RANDOM", "S-RANDOM")),
gauge: optNull(p.isIn("NORMAL", "EASY", "HARD", "DANGER")),
};
}
return {};
@@ -91,7 +101,7 @@ const PR_HitMeta = (game: Game): PrudenceSchema => {
EX_HARD: [p.nullable(p.isBoundedInteger(0, 100))],
}),
};
} else if (/* game === "popn" || */ game === "sdvx") {
} else if (game === "sdvx") {
return {
gauge: optNull(p.isBoundedInteger(0, 100)),
};
@@ -112,6 +122,26 @@ const PR_HitMeta = (game: Game): PrudenceSchema => {
lpg: optNull(p.isPositiveInteger),
epg: optNull(p.isPositiveInteger),
};
} else if (game === "popn") {
return {
gauge: optNull(p.isBoundedInteger(0, 100)),
specificClearType: optNull(
p.isIn(
"failedUnknown",
"failedCircle",
"failedDiamond",
"failedStar",
"easyClear",
"clearCircle",
"clearDiamond",
"clearStar",
"fullComboCircle",
"fullComboDiamond",
"fullComboStar",
"perfect"
)
),
};
}
return {};
@@ -165,8 +195,6 @@ const PR_BatchManualScore = (game: Game, playtype: Playtypes[Game]): PrudenceSch
deepmerge(BaseValidHitMeta, PR_HitMeta(game)) as unknown as ValidSchemaValue
),
scoreMeta: optNull(PR_ScoreMeta(game, playtype)),
// scoreMeta: @todo #74
// more game specific props, maybe?
};
};
@@ -13,7 +13,10 @@
"notecount": 786,
"arcChartID": "CYjwAuz7Yq9",
"2dxtraSet": null,
"hashSHA256": null
"hashSHA256": null,
"bpiCoefficient": null,
"kaidenAverage": null,
"worldRecord": null
},
"isPrimary": true,
"versions": [
@@ -0,0 +1,19 @@
[
{
"songID": 1,
"chartID": "de5344935963bf24a38a5d5693dd4dd3ddacf8d6",
"difficulty": "Easy",
"playtype": "9B",
"level": "1",
"levelNum": 1,
"rgcID": null,
"isPrimary": true,
"data": {
"hashSHA256": "2c26d666fa7c907e85115dbb279c267c14a263d47b2d46a93f99eae49d779119"
},
"tierlistInfo": {},
"versions": [
"peace"
]
}
]
@@ -1,9 +0,0 @@
[
{
"coef": null,
"kavg": 1300,
"wr": 1400,
"chartID": "c2311194e3897ddb5745b1760d2c0141f933e683",
"kesd": 13
}
]
@@ -0,0 +1,13 @@
[
{
"id": 1,
"title": "I REALLY WANT TO HURT YOU",
"artist": "SUGI & REO",
"searchTerms": [],
"altTitles": [],
"data": {
"displayVersion": null,
"genre": "ポップス"
}
}
]
+6
View File
@@ -43,6 +43,12 @@ function GetAndCache(filename: string, fileLoc: string) {
collection = db[filename];
}
if (!collection) {
throw new Error(
`Panicked when trying to get collection for ${filename}. Does this collection exist?`
);
}
if (CACHE[filename]) {
return { data: CACHE[filename], collection };
}
+3
View File
@@ -186,6 +186,9 @@ export const Testing511SPA: ChartDocument<"iidx:SP"> = {
arcChartID: "CYjwAuz7Yq9",
"2dxtraSet": null,
hashSHA256: null,
bpiCoefficient: null,
kaidenAverage: null,
worldRecord: null,
},
tierlistInfo: {},
isPrimary: true,