Files
healer-man/scripts/coa-source-policy.mjs
T

100 lines
4.1 KiB
JavaScript

import fs from "node:fs";
import path from "node:path";
const dbcDirectory = (root, patch, fileName) => path.join(root, patch, "DBFilesClient", fileName);
function firstExisting(candidates, exists) {
return candidates.find((candidate) => exists(candidate)) ?? candidates.at(-1);
}
/** Resolve independent source paths for classic and CoA data in the combined importer. */
export function resolveCoaSourcePaths({
extractedClientRoot,
legacyExtractedRoot,
classicSpellOverride,
exists = fs.existsSync,
}) {
const area52Spell = dbcDirectory(path.join(extractedClientRoot, "area-52"), "patch-D", "Spell.dbc");
const globalSpell = dbcDirectory(extractedClientRoot, "patch-T", "Spell.dbc");
const legacySpell = dbcDirectory(legacyExtractedRoot, "patch-T", "Spell.dbc");
const area52SkillLineAbility = dbcDirectory(
path.join(extractedClientRoot, "area-52"),
"patch-D",
"SkillLineAbility.dbc",
);
const globalSkillLineAbility = dbcDirectory(extractedClientRoot, "patch-M", "SkillLineAbility.dbc");
const legacySkillLineAbility = dbcDirectory(legacyExtractedRoot, "patch-M", "SkillLineAbility.dbc");
const classicSpellDbcPath = classicSpellOverride
? path.resolve(classicSpellOverride)
: firstExisting([area52Spell, globalSpell, legacySpell], exists);
const classicSiblingSkillLineAbility = path.join(path.dirname(classicSpellDbcPath), "SkillLineAbility.dbc");
return {
classicSpellDbcPath,
classicSkillLineAbilityDbcPath: exists(classicSiblingSkillLineAbility)
? classicSiblingSkillLineAbility
: firstExisting([area52SkillLineAbility, globalSkillLineAbility, legacySkillLineAbility], exists),
coaSpellDbcPath: firstExisting([globalSpell, legacySpell], exists),
coaSkillLineAbilityDbcPath: firstExisting([globalSkillLineAbility, legacySkillLineAbility], exists),
};
}
/** Collect DBC IDs whose classic metadata must remain available beside CoA metadata. */
export function collectRealmSpellIds({
classicClassIds,
coaClassIds,
classSpellRows,
classicRankCandidates,
coaRankCandidates,
coaEntrySpellIds,
}) {
const classicIds = new Set();
const coaIds = new Set(coaEntrySpellIds ?? []);
const classicClasses = new Set(classicClassIds);
const coaClasses = new Set(coaClassIds);
for (const row of classSpellRows) {
if (classicClasses.has(row.classId)) classicIds.add(row.id);
if (coaClasses.has(row.classId)) coaIds.add(row.id);
}
for (const candidate of classicRankCandidates) classicIds.add(candidate.id);
for (const candidate of coaRankCandidates) coaIds.add(candidate.id);
return { classicIds, coaIds };
}
/** Return IDs that need a separate CoA wrapper to protect classic metadata. */
export function sharedRealmSpellIds(classicIds, coaIds) {
return [...coaIds].filter((spellId) => classicIds.has(spellId)).sort((left, right) => left - right);
}
/** Include linked base-client payloads, including effect 168's miscValue spell. */
export function expandCoaSpellIds(buffer, initialIds) {
if (buffer.length < 20 || buffer.toString("ascii", 0, 4) !== "WDBC" || buffer.readUInt32LE(8) < 234) throw new Error("CoA Spell.dbc does not expose the 234-field WDBC schema");
const recordCount = buffer.readUInt32LE(4);
const recordSize = buffer.readUInt32LE(12);
if (recordSize < 234 * 4 || 20 + recordCount * recordSize > buffer.length) throw new Error("CoA Spell.dbc has invalid record lengths");
const rows = new Map();
for (let index = 0; index < recordCount; index += 1) {
const base = 20 + index * recordSize;
const id = buffer.readUInt32LE(base);
if (!rows.has(id)) rows.set(id, base);
}
const ids = new Set(initialIds);
const queue = [...ids];
for (let cursor = 0; cursor < queue.length; cursor += 1) {
const base = rows.get(queue[cursor]);
if (base === undefined) continue;
for (let index = 0; index < 3; index += 1) {
const childId = buffer.readUInt32LE(base + (116 + index) * 4)
|| (buffer.readUInt32LE(base + (71 + index) * 4) === 168 ? buffer.readUInt32LE(base + (110 + index) * 4) : 0);
if (childId > 0 && !ids.has(childId)) {
ids.add(childId);
queue.push(childId);
}
}
}
return ids;
}