[maimaidx] Update seeds to 2025/12/05 (#1402)

This commit is contained in:
beerpsi
2025-12-06 19:09:03 +00:00
committed by GitHub
parent 3c432678e7
commit 0b7797be0b
5 changed files with 332 additions and 27 deletions
+192
View File
@@ -11041,6 +11041,70 @@
"prismplus"
]
},
{
"chartID": "455372326d56ff36c9a817ad9e3db862a5f6c2f0",
"data": {
"displayVersion": "maimaiでらっくす PRiSM PLUS",
"inGameID": 10187
},
"difficulty": "DX Advanced",
"isPrimary": true,
"level": "7+",
"levelNum": 7.8,
"playtype": "Single",
"songID": 95,
"versions": [
"prismplus"
]
},
{
"chartID": "591ea8f3a3f15a5f75c5a7cbb33719150b6a3828",
"data": {
"displayVersion": "maimaiでらっくす PRiSM PLUS",
"inGameID": 10187
},
"difficulty": "DX Basic",
"isPrimary": true,
"level": "4",
"levelNum": 4,
"playtype": "Single",
"songID": 95,
"versions": [
"prismplus"
]
},
{
"chartID": "d598fd3a830ed70bdf76b681857296330cd88b3d",
"data": {
"displayVersion": "maimaiでらっくす PRiSM PLUS",
"inGameID": 10187
},
"difficulty": "DX Expert",
"isPrimary": true,
"level": "10+",
"levelNum": 10.9,
"playtype": "Single",
"songID": 95,
"versions": [
"prismplus"
]
},
{
"chartID": "283fbbe68113da289059a81c7635a2f7d0dc6db9",
"data": {
"displayVersion": "maimaiでらっくす PRiSM PLUS",
"inGameID": 10187
},
"difficulty": "DX Master",
"isPrimary": true,
"level": "13",
"levelNum": 13.3,
"playtype": "Single",
"songID": 95,
"versions": [
"prismplus"
]
},
{
"chartID": "dbf525efab45d1c9147363da9836749f8a9402e5",
"data": {
@@ -150633,5 +150697,133 @@
"versions": [
"prismplus"
]
},
{
"chartID": "798bddcca4ef4fc2a0403ba0a9bd120fa45bde4e",
"data": {
"displayVersion": "maimaiでらっくす PRiSM PLUS",
"inGameID": 11864
},
"difficulty": "DX Advanced",
"isPrimary": true,
"level": "6",
"levelNum": 6.5,
"playtype": "Single",
"songID": 1527,
"versions": [
"prismplus"
]
},
{
"chartID": "1fa9ffa83add19468a408f727351b5fbff5d2704",
"data": {
"displayVersion": "maimaiでらっくす PRiSM PLUS",
"inGameID": 11864
},
"difficulty": "DX Basic",
"isPrimary": true,
"level": "2",
"levelNum": 2,
"playtype": "Single",
"songID": 1527,
"versions": [
"prismplus"
]
},
{
"chartID": "3199545db1e72b754ef838d4993011ad9f0ee282",
"data": {
"displayVersion": "maimaiでらっくす PRiSM PLUS",
"inGameID": 11864
},
"difficulty": "DX Expert",
"isPrimary": true,
"level": "9+",
"levelNum": 9.9,
"playtype": "Single",
"songID": 1527,
"versions": [
"prismplus"
]
},
{
"chartID": "134e15e7fdd0544b5b2f4928642e7572cc7c1804",
"data": {
"displayVersion": "maimaiでらっくす PRiSM PLUS",
"inGameID": 11864
},
"difficulty": "DX Master",
"isPrimary": true,
"level": "12+",
"levelNum": 12.9,
"playtype": "Single",
"songID": 1527,
"versions": [
"prismplus"
]
},
{
"chartID": "0bd5f4cb68729b5b9dbac0ef91623ffcd341059b",
"data": {
"displayVersion": "maimaiでらっくす PRiSM PLUS",
"inGameID": 11866
},
"difficulty": "DX Advanced",
"isPrimary": true,
"level": "6",
"levelNum": 6,
"playtype": "Single",
"songID": 1528,
"versions": [
"prismplus"
]
},
{
"chartID": "78f2c32e5fa920b8d1da1c4e6fce0030e049b00f",
"data": {
"displayVersion": "maimaiでらっくす PRiSM PLUS",
"inGameID": 11866
},
"difficulty": "DX Basic",
"isPrimary": true,
"level": "2",
"levelNum": 2,
"playtype": "Single",
"songID": 1528,
"versions": [
"prismplus"
]
},
{
"chartID": "59a97f033d5d208b48b6972075b457201e7c7f22",
"data": {
"displayVersion": "maimaiでらっくす PRiSM PLUS",
"inGameID": 11866
},
"difficulty": "DX Expert",
"isPrimary": true,
"level": "9",
"levelNum": 9.2,
"playtype": "Single",
"songID": 1528,
"versions": [
"prismplus"
]
},
{
"chartID": "7d697ad800d479111da3a709a45e8e9264df8ac4",
"data": {
"displayVersion": "maimaiでらっくす PRiSM PLUS",
"inGameID": 11866
},
"difficulty": "DX Master",
"isPrimary": true,
"level": "13",
"levelNum": 13.3,
"playtype": "Single",
"songID": 1528,
"versions": [
"prismplus"
]
}
]
+28
View File
@@ -19553,5 +19553,33 @@
"id": 1526,
"searchTerms": [],
"title": "Destined Marionette"
},
{
"altTitles": [],
"artist": "みきとP",
"data": {
"duration": 159.6,
"genre": "niconico&ボーカロイド"
},
"id": 1527,
"searchTerms": [
"shoujo rei",
"ghost girl"
],
"title": "少女レイ"
},
{
"altTitles": [],
"artist": "R Sound Design",
"data": {
"duration": 163.022,
"genre": "niconico&ボーカロイド"
},
"id": 1528,
"searchTerms": [
"imperial girl",
"teikoku shoujou"
],
"title": "帝国少女"
}
]
@@ -25,3 +25,25 @@ ts-node merge-options.ts --version <VERSION> --input /path/to/maimai/App/Package
4. Manually add search terms in `seeds/collections/songs-maimaidx.json`. At a minimum, songs with
Japanese titles should be romanized.
5. In the root project folder, run `just seeds test`.
## Adding a new version
1. Add a new version in `common/src/config/game-support/maimai-dx.ts`.
- Also, add an Omnimix variant for the previous version.
2. Create tables and folders for the new version (and the omnimix version).
See `seeds/scripts/rerunners/maimaidx/add-maimaidx-table-and-folders.js`. You will have
to adjust the constants for the version you're adding.
3. Deactivate old tables using `seeds/scripts/rerunners/toggle-table-inactive.js`:
```
node toggle-table-inactive.js -t maimaidx-Single-<VERSION>-levels
```
Keep tables for the current and last (aka n-1) versions.
4. Switch the new table to the default one by editing `seeds/collections/tables.json`.
Change to `"default": true` for the current version, and `"default": false` for all
other tables of the game.
5. Verify you've done everything correctly by running `just seeds test` in the root project
folder.
@@ -0,0 +1,23 @@
# List of charts to ignore when parsing game data.
# Each line is a regex, and these regexes are matched against the strings:
# - S<song ID>
# - C<song ID>-<difficulty>
# Empty lines, or lines starting with # are ignored.
# Ignore all UTAGEs (ID >=100000) since they're unsupported on Kamaitachi.
S1\d{5}$
# Ignore Xaleid◆scopiX (2): event chart used to implement the KALEIDXSCOPE event,
# not normally playable.
S11879$
S11856$
S11857$
S11858$
S11859$
S11861$
S11874$
S11875$
S11876$
S11877$
S11878$
@@ -155,32 +155,37 @@ for (const chart of existingCharts) {
songTitleArtistMap.set(`${song.title}-${song.artist}`, song.id);
}
const parser = new XMLParser({
numberParseOptions: {
hex: false,
leadingZeros: false,
// do not coerce any number-like strings to numbers, since song titles
// may also be numbers. we coerce anything we know to be a number later.
skipLike: /.*/u,
},
});
const blacklist = readFileSync(path.join(__dirname, "blacklist.txt"), "utf-8")
.split("\n")
.filter((e) => !e.startsWith("#") && e.trim() !== "")
.map((e) => new RegExp(e, "u"));
const newSongs: Array<SongDocument<"maimaidx">> = [];
const newCharts: Array<ChartDocument<"maimaidx:Single">> = [];
const songIDGenerator = GetFreshSongIDGenerator("maimaidx");
for (const optionsDir of options.input) {
for (const option of readdirSync(optionsDir)) {
if (!option.match(/^[A-Z]\d{3}$/u)) {
continue;
function isInBlacklist(str: string) {
for (const regex of blacklist) {
if (regex.exec(str)) {
return true;
}
}
const optionDir = path.join(optionsDir, option);
const musicsDir = path.join(optionDir, "music");
const soundDataDir = path.join(optionDir, "SoundData");
return false;
}
// Read all audio files ahead of time so the correct file is used, in case of
// audio fixes
if (options.vgmsBinary) {
for (const optionsDir of options.input) {
for (const option of readdirSync(optionsDir)) {
if (!option.match(/^[A-Z]\d{3}$/u)) {
continue;
}
const optionDir = path.join(optionsDir, option);
const soundDataDir = path.join(optionDir, "SoundData");
if (!existsSync(soundDataDir)) {
continue;
}
if (options.vgmsBinary && existsSync(soundDataDir)) {
for (const cueFileName of readdirSync(soundDataDir)) {
if (!cueFileName.match(/music\d+\.awb$/u)) {
continue;
@@ -211,6 +216,32 @@ for (const optionsDir of options.input) {
}
}
}
}
}
const parser = new XMLParser({
numberParseOptions: {
hex: false,
leadingZeros: false,
// do not coerce any number-like strings to numbers, since song titles
// may also be numbers. we coerce anything we know to be a number later.
skipLike: /.*/u,
},
});
const newSongs: Array<SongDocument<"maimaidx">> = [];
const newCharts: Array<ChartDocument<"maimaidx:Single">> = [];
const songIDGenerator = GetFreshSongIDGenerator("maimaidx");
for (const optionsDir of options.input) {
for (const option of readdirSync(optionsDir)) {
if (!option.match(/^[A-Z]\d{3}$/u)) {
continue;
}
const optionDir = path.join(optionsDir, option);
const musicsDir = path.join(optionDir, "music");
if (!existsSync(musicsDir)) {
logger.warn(`Option at ${optionDir} does not have a "music" folder.`);
@@ -241,10 +272,10 @@ for (const optionsDir of options.input) {
const musicData = data.MusicData;
const inGameID = Number(musicData.name.id);
if (inGameID >= 100000 || inGameID === 11879) {
// Ignore UTAGE charts, which are not supported by Tachi.
// Ignore Xaleid◆scopiX (2): event chart used to implement the KALEIDXSCOPE event,
// not normally playable.
if (isInBlacklist(`S${inGameID}`)) {
logger.verbose(
`Ignored ${musicData.artistName.str} - ${musicData.name.str} (inGameID ${inGameID}) as it is in the blacklist.`
);
continue;
}
@@ -322,7 +353,9 @@ for (const optionsDir of options.input) {
songTitleArtistMap.set(`${songDoc.title}-${songDoc.artist}`, tachiSongID);
songMap.set(tachiSongID, songDoc);
logger.info(`Added new song ${songDoc.artist} - ${songDoc.title}.`);
logger.info(
`Added new song ${songDoc.artist} - ${songDoc.title} (inGameID ${inGameID}, tachiSongID ${tachiSongID}).`
);
} else {
const songDoc = songMap.get(tachiSongID)!;
@@ -347,6 +380,13 @@ for (const optionsDir of options.input) {
);
}
if (isInBlacklist(`C${inGameID}-${difficultyName}`)) {
logger.verbose(
`Ignoring ${musicData.artistName.str} - ${musicData.name.str} [${difficultyName}] (inGameID ${inGameID}) as it is in the blacklist.`
);
continue;
}
if (inGameID > 10000) {
difficultyName = `DX ${difficultyName}`;
}