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

211 lines
7.4 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);