Files
zkldi_Tachi/database-seeds/scripts/rerunners/usc/add-usc-tables.ts
T

216 lines
7.5 KiB
TypeScript

import { Command } from "commander";
import sqlite3 from "better-sqlite3";
import { parse } from "csv-parse/sync";
import fs from "fs";
const { CreateChartID, ReadCollection, WriteCollection } = require("../../util");
// The tables are at https://docs.google.com/spreadsheets/u/1/d/e/2PACX-1vRH1beDh3I76qS-UUppW4_GibEj9bBDgcpO1XM4SrMylnSZYjyPjPYcMuQHaMrB-JGpIdsZtb-0wzmp/pubhtml.
// Download as csv using these links:
// us1-14: https://docs.google.com/spreadsheets/u/1/d/e/2PACX-1vRH1beDh3I76qS-UUppW4_GibEj9bBDgcpO1XM4SrMylnSZYjyPjPYcMuQHaMrB-JGpIdsZtb-0wzmp/pub?output=csv&gid=1782673801
// us15-18+: https://docs.google.com/spreadsheets/u/1/d/e/2PACX-1vRH1beDh3I76qS-UUppW4_GibEj9bBDgcpO1XM4SrMylnSZYjyPjPYcMuQHaMrB-JGpIdsZtb-0wzmp/pub?output=csv&gid=1134382369
// os: https://docs.google.com/spreadsheets/u/1/d/e/2PACX-1vRH1beDh3I76qS-UUppW4_GibEj9bBDgcpO1XM4SrMylnSZYjyPjPYcMuQHaMrB-JGpIdsZtb-0wzmp/pub?output=csv&gid=144278610
// us0*: https://docs.google.com/spreadsheets/u/1/d/e/2PACX-1vRH1beDh3I76qS-UUppW4_GibEj9bBDgcpO1XM4SrMylnSZYjyPjPYcMuQHaMrB-JGpIdsZtb-0wzmp/pub?output=csv&gid=1456079294
// You must also download the "packages" sheet, which maps song pack names to directories. Pass this using the '-p' option:
// https://docs.google.com/spreadsheets/u/1/d/e/2PACX-1vRH1beDh3I76qS-UUppW4_GibEj9bBDgcpO1XM4SrMylnSZYjyPjPYcMuQHaMrB-JGpIdsZtb-0wzmp/pub?output=csv&gid=899058659
const DIFF_MAP = {
Light: "NOV",
Challenge: "ADV",
Extended: "EXH",
Infinite: "INF",
};
// Imagine if sdvx/ksm players could just copy and paste properly...
function fixBrokenData(tier, title, diff, level, packDir) {
const tierDiffLevel = `${tier} ${diff} ${level}`;
if (title === "FALSE" && ["us3 NOV 3", "us9 ADV 9", "us13 EXH 13"].includes(tierDiffLevel)) {
return ["False", diff, level, `\\${packDir}\\`];
}
if (title === "Agitatus" && ["us4 NOV 4", "us11 ADV 11"].includes(tierDiffLevel)) {
// This is actually a typo in the chart, only for NOV and ADV.
return [" Agitatus", diff, level, `\\${packDir}\\`];
}
if (title === "Amnèhilesie" && ["us6 NOV 6", "us12 ADV 12"].includes(tierDiffLevel)) {
return ["Amnéhilesie", diff, level, `\\${packDir}\\`];
}
if (
title === 'White Wail -the "X-carols"-' &&
["us9 NOV 9", "us16 ADV 16", "os3 EXH 19", "os10 INF 20"].includes(tierDiffLevel)
) {
return ['White Wail -the "X Carols"-', diff, level, `\\${packDir}\\`];
}
if (title === "Re:building ZERO" && tierDiffLevel === "us16 EXH 16") {
return [title, diff, "17", `\\${packDir}\\`];
}
if (title === "Our School Festival!" && tierDiffLevel === "us18 INF 18") {
// The INF has a weird title, the other diffs are all fine.
return ["Our School festival", diff, level, `\\${packDir}\\`];
}
// We must distinguish between the finale and the non-finale version manually.
// Why the fuck they set it up like this I will never know.
if (title === "#curtaincall(finale)" && tierDiffLevel === "os5 INF 20") {
return [
"#curtaincall",
diff,
level,
"B4UT&KUOC K-shoot mania Package 2016\\03_BOSS_B4UT\\04_#curtaincall_finale\\",
];
}
if (title === "#curtaincall" && tierDiffLevel === "os9* INF 20") {
return [
"#curtaincall",
diff,
level,
"B4UT&KUOC K-shoot mania Package 2016\\03_BOSS_B4UT\\03_#curtaincall\\",
];
}
if (title === "双極のバラキエル" && tierDiffLevel === "os6 INF 20") {
return [title, diff, "19", `\\${packDir}\\`];
}
if (
(title === "Modern Times Paradox" && tierDiffLevel === "us6 NOV 6") ||
(title === "Modern Times Paradox[B.B.K. Collabo]" && tierDiffLevel === "os8* INF 20")
) {
// These charts are corrupted in USC.
return null;
}
return [title, diff, level, `\\${packDir}\\`];
}
function tableFolder(tierString: string): { table: string; level: string } {
const match = tierString.match(/^(us|os)(\d{1,2}\+?\*?)$/);
return {
table: match[1],
level: match[2],
};
}
const program = new Command();
program.requiredOption("-d, --db <maps.db>");
program.requiredOption("-p, --packages <パッケージ集.csv>");
program.requiredOption("-c, --charts [chartlists.csv...]");
program.parse(process.argv);
const options = program.opts();
const directories: Record<string, string[]> = {};
const packageData = parse(fs.readFileSync(options.packages), { from: 1 });
let currentPack = "";
for (let i = 0; i < packageData.length; i++) {
const row = packageData[i];
// first column is this weird star thing we ignore
if (row[1] !== "") {
currentPack = row[1];
directories[currentPack] = [];
}
const directory = row[2];
directories[currentPack].push(directory);
}
const db = sqlite3(options.db);
// We could use proper queries to be more efficient,
// but the db isn't really large enough for it to matter...
const dbRows = db.prepare("SELECT * FROM Charts").all();
const songs = ReadCollection("songs-usc.json");
const charts = ReadCollection("charts-usc.json");
let maxSongId = Math.max(...songs.map((s) => s.id));
const folderIdToSongId = {};
for (const tableFile of options.charts) {
const tableData = parse(fs.readFileSync(tableFile), { from: 2 });
for (const chartData of tableData) {
const [tier, origTitle, ksmDiff, origLevel, pack, _notes] = chartData;
const origDiff = DIFF_MAP[ksmDiff];
const packDir = directories[pack];
const fixedData = fixBrokenData(tier, origTitle, origDiff, origLevel, packDir);
if (fixedData === null) {
continue;
}
const [title, difficulty, level, pathMatch] = fixedData;
let dbRowMatches = dbRows.filter(
(row) =>
row.title === title &&
row.diff_shortname === difficulty &&
row.level === Number(level) &&
row.path.includes(pathMatch)
);
if (dbRowMatches.length === 0) {
console.log(
`Couldn't find matching row for ${title} (${difficulty} ${level}) in ${tier}. (From package ${pack})`
);
continue;
} else if (dbRowMatches.length !== 1) {
if (
dbRowMatches.length === 2 &&
dbRowMatches.filter((row) => row.path.endsWith("data.ksh")).length === 1
) {
// These files are an old version of the chart I believe.
dbRowMatches = dbRowMatches.filter((row) => !row.path.endsWith("data.ksh"));
} else {
console.log(dbRowMatches);
//throw new Error(`Wrong number of matching rows for ${title} (${difficulty} ${level}) in ${tier}`);
}
}
const dbRow = dbRowMatches[0];
const matchingCharts = charts.filter((chart) => chart.hashSHA1 === dbRow.hash);
if (matchingCharts.length === 2) {
for (const chart of matchingCharts) {
chart.data.tableFolders = [tableFolder(tier)];
}
} else if (matchingCharts.length === 0) {
if (!folderIdToSongId[dbRow.folderid]) {
const songID = maxSongId + 1;
maxSongId = songID;
// May need to change to forward slash depending on OS.
const splitPath = dbRow.path.split("\\");
const folderName = splitPath[splitPath.length - 2];
songs.push({
id: songID,
title,
altTitles: [],
artist: dbRow.artist,
data: {},
searchTerms: [folderName],
});
folderIdToSongId[dbRow.folderid] = songID;
}
const songID = folderIdToSongId[dbRow.folderid];
for (const playtype of ["Controller", "Keyboard"]) {
charts.push({
chartID: CreateChartID(),
data: {
effector: dbRow.effector,
hashSHA1: dbRow.hash,
isOfficial: false,
tableFolders: [tableFolder(tier)],
},
difficulty,
isPrimary: true,
level,
// So it doesn't give VF. (Consider changing this.)
levelNum: 0,
playtype,
rgcID: null,
songID,
tierlistInfo: {},
versions: [],
});
}
}
}
}
WriteCollection("songs-usc.json", songs);
WriteCollection("charts-usc.json", charts);