105 lines
4.6 KiB
TypeScript
105 lines
4.6 KiB
TypeScript
/**
|
|
* Generates src/game/drlg/drlg-ids.ts from D2MOO's DataTbls/LevelsIds.h (the LvlPrest, LvlSub and
|
|
* LvlTypes enums), so the DRLG port uses exactly the ids D2MOO compiles against.
|
|
*
|
|
* D2MOO is read from D2MOO_SRC (default ~/tmp/D2MOO) and must be at the pinned commit.
|
|
*
|
|
* Usage: npx tsx scripts/gen-drlg-ids.ts
|
|
*/
|
|
import { execFileSync } from 'node:child_process'
|
|
import { readFileSync, writeFileSync } from 'node:fs'
|
|
import { homedir } from 'node:os'
|
|
import { join, resolve } from 'node:path'
|
|
|
|
const PINNED_COMMIT = '5596f5cb6c5251a0a07c6637d26458b06099d516'
|
|
const d2mooSrc = resolve(process.env.D2MOO_SRC ?? join(homedir(), 'tmp', 'D2MOO'))
|
|
const commit = execFileSync('git', ['-C', d2mooSrc, 'rev-parse', 'HEAD'], { encoding: 'utf8' }).trim()
|
|
if (commit !== PINNED_COMMIT) {
|
|
throw new Error(`${d2mooSrc} is at ${commit}, expected ${PINNED_COMMIT}`)
|
|
}
|
|
|
|
const header = readFileSync(join(d2mooSrc, 'source/D2Common/include/DataTbls/LevelsIds.h'), 'latin1')
|
|
|
|
function parseEnum(name: string): [string, number][] {
|
|
const match = new RegExp(`enum\\s+${name}\\s*\\{([\\s\\S]*?)\\};`).exec(header)
|
|
if (!match) throw new Error(`enum ${name} not found`)
|
|
const body = match[1]!
|
|
if (/^\s*#/m.test(body)) throw new Error(`enum ${name} has preprocessor directives; handle them explicitly`)
|
|
const entries: [string, number][] = []
|
|
let value = -1
|
|
for (const raw of body.replace(/\/\*[\s\S]*?\*\//g, '').split(',')) {
|
|
const item = raw.replace(/\/\/.*$/gm, '').trim()
|
|
if (!item) continue
|
|
const eq = /^(\w+)\s*=\s*(.+)$/.exec(item)
|
|
if (eq) {
|
|
const literal = eq[2]!.trim()
|
|
if (!/^(0x[0-9a-fA-F]+|\d+)$/.test(literal)) throw new Error(`${name}: unsupported initialiser ${item}`)
|
|
value = Number(literal)
|
|
entries.push([eq[1]!, value])
|
|
} else {
|
|
if (!/^\w+$/.test(item)) throw new Error(`${name}: unsupported entry ${item}`)
|
|
value += 1
|
|
entries.push([item, value])
|
|
}
|
|
}
|
|
return entries
|
|
}
|
|
|
|
const sections: [string, string][] = [
|
|
['D2C_LvlPrestIds', 'LvlPrest.txt row ids (DataTbls/LevelsIds.h D2C_LvlPrestIds).'],
|
|
['D2C_LvlSubIds', 'LvlSub.txt types (DataTbls/LevelsIds.h D2C_LvlSubIds).'],
|
|
['D2C_LvlTypes', 'LvlTypes.txt ids (DataTbls/LevelsIds.h D2C_LvlTypes).'],
|
|
]
|
|
|
|
let out = `/**
|
|
* GENERATED by scripts/gen-drlg-ids.ts from D2MOO source/D2Common/include/DataTbls/LevelsIds.h
|
|
* (commit ${PINNED_COMMIT}, MIT License, Copyright (c) 2020-2025 The Phrozen Keep community).
|
|
* Do not edit by hand.
|
|
*/
|
|
`
|
|
for (const [name, doc] of sections) {
|
|
const entries = parseEnum(name)
|
|
out += `\n// ${doc} ${entries.length} entries.\n`
|
|
for (const [id, value] of entries) out += `export const ${id} = ${value}\n`
|
|
}
|
|
|
|
/** Values of `static const <type> <name>[..]... = { ... };` in a D2MOO source file, flattened. */
|
|
function extractArray(relPath: string, name: string, expectedLength: number): number[] {
|
|
const source = readFileSync(join(d2mooSrc, relPath), 'latin1')
|
|
const match = new RegExp(`static\\s+const\\s+\\w+\\s+${name}\\s*(?:\\[[^\\]]*\\])+\\s*=\\s*\\{([\\s\\S]*?)\\};`).exec(source)
|
|
if (!match) throw new Error(`${relPath}: array ${name} not found`)
|
|
const values = match[1]!
|
|
.replace(/\/\/.*$/gm, '')
|
|
.replace(/[{}]/g, ',')
|
|
.split(',')
|
|
.map(s => s.trim())
|
|
.filter(s => s.length > 0)
|
|
.map(s => {
|
|
if (!/^(0x[0-9a-fA-F]+|\d+)$/.test(s)) throw new Error(`${relPath}: ${name}: unsupported literal ${s}`)
|
|
return Number(s)
|
|
})
|
|
if (values.length !== expectedLength) {
|
|
throw new Error(`${relPath}: ${name} has ${values.length} values, expected ${expectedLength}`)
|
|
}
|
|
return values
|
|
}
|
|
|
|
const arrays: [string, string, string, number, string][] = [
|
|
['source/D2Common/src/Drlg/DrlgPreset.cpp', 'dword_6FDE1180', 'gObjPresetToObjectId', 5 * 150,
|
|
'DRLGPRESET_GetObjectIndexFromObjPreset (D2Common.0x6FD859E0): [nAct * 150 + nUnitId].'],
|
|
['source/D2Common/src/Drlg/DrlgPreset.cpp', 'nTileTypeMappingTable', 'gTileTypeMappingTable', 42,
|
|
'DRLGPRESET_MapTileType (D2Common.0x6FD88850): DS1 version < 7 tile types.'],
|
|
['source/D2Common/src/Drlg/DrlgOutdoors.cpp', 'byte_6FDCF958', 'byte_6FDCF958', 256,
|
|
'DRLG_OUTDOORS_GenerateDirtPath (D2Common.0x6FD7EFE0): dirt-path neighbourhood -> tile sequence.'],
|
|
]
|
|
for (const [relPath, name, exportName, length, doc] of arrays) {
|
|
const values = extractArray(relPath, name, length)
|
|
out += `\n// ${doc}\n// ${relPath}: ${name}\nexport const ${exportName}: readonly number[] = [\n`
|
|
for (let i = 0; i < values.length; i += 25) out += ` ${values.slice(i, i + 25).join(', ')},\n`
|
|
out += ']\n'
|
|
}
|
|
|
|
const target = resolve('src/game/drlg/drlg-ids.ts')
|
|
writeFileSync(target, out)
|
|
console.log(`wrote ${target}`)
|