Update Healer Man server

This commit is contained in:
phenom committed 2026-10-04 13:13:35 -04:00
1 parent 39458d5de6
commit 6cc1b9217f
203 files changed
+133929 -31271

No files matched your search

@@ -41,6 +41,20 @@ const epochDungeonSourceFile = path.join(
*/
export const DEFAULT_AZEROTHCORE_COMMIT = "4d9d1d4b5723e819c5c390f6ca1177283dbfb8e3";
export const PINNED_CREATURE_IMMUNITIES_SOURCE = Object.freeze({
repository: "https://github.com/azerothcore/azerothcore-wotlk",
commit: DEFAULT_AZEROTHCORE_COMMIT,
path: "data/sql/base/db_world/creature_immunities.sql",
url: `https://raw.githubusercontent.com/azerothcore/azerothcore-wotlk/${DEFAULT_AZEROTHCORE_COMMIT}/data/sql/base/db_world/creature_immunities.sql`,
sha256: "aee81a1d8b99986f8498a2b4c51b64eaf4b9095a0d1a12bbf3705d9588a90a13",
fixtureRoot: path.join(
projectRoot,
"dungeon-pipeline",
"source-fixtures",
`azerothcore-${DEFAULT_AZEROTHCORE_COMMIT}`,
),
});
const SOURCE_TABLES = Object.freeze([
"creature",
"creature_template",
@@ -303,16 +317,37 @@ function bitMaskNames(maskValue, names, bitOffset = 0) {
));
}
function creatureTemplateReference(row) {
export function creatureTemplateReference(row, creatureImmunitiesById = new Map()) {
const entry = numeric(firstValue(row, ["entry", "CreatureID", "creatureId"]));
const mechanicImmuneMask = numeric(firstValue(
row,
["mechanic_immune_mask", "mechanicImmuneMask", "CreatureImmunitiesId"],
));
const spellSchoolImmuneMask = numeric(firstValue(
const legacyMechanicImmuneMask = firstValue(row, ["mechanic_immune_mask", "mechanicImmuneMask"]);
const creatureImmunitiesId = firstValue(row, ["CreatureImmunitiesId", "creatureImmunitiesId"]);
let mechanicImmuneMask = numeric(legacyMechanicImmuneMask);
let spellSchoolImmuneMask = numeric(firstValue(
row,
["spell_school_immune_mask", "spellSchoolImmuneMask"],
));
if (legacyMechanicImmuneMask === undefined && creatureImmunitiesId !== undefined) {
const immunityId = numeric(creatureImmunitiesId);
if (immunityId !== 0) {
const immunity = creatureImmunitiesById.get(immunityId);
if (!immunity) {
throw new Error(
`Creature ${entry} references missing creature_immunities.ID ${immunityId}.`,
);
}
const sourceMechanicsMask = numeric(firstValue(immunity, ["MechanicsMask", "mechanicsMask"]));
if (!Number.isSafeInteger(sourceMechanicsMask) || sourceMechanicsMask < 0) {
throw new Error(
`creature_immunities.ID ${immunityId} has an unsupported MechanicsMask ${sourceMechanicsMask}.`,
);
}
// CreatureImmunities.Mechanic stores bit N at the mechanic enum value N.
// The legacy creature_template mask stores bit N-1. Normalize back to
// that established runtime representation before decoding names below.
mechanicImmuneMask = Math.floor(sourceMechanicsMask / 2);
spellSchoolImmuneMask = numeric(firstValue(immunity, ["SchoolMask", "schoolMask"]));
}
}
return {
entry,
name: String(firstValue(row, ["name", "Name"]) ?? `Creature ${entry}`),
@@ -589,7 +624,13 @@ function parseColumns(sql, tableName) {
return columns;
}
async function parseTable(sourceRoot, tableName, visitor) {
export function creatureTemplateUsesImmunitiesReference(sql) {
return parseColumns(sql, "creature_template").some((column) => (
column === "CreatureImmunitiesId" || column === "creatureImmunitiesId"
));
}
export async function parseTable(sourceRoot, tableName, visitor) {
const file = path.join(sourceRoot, `${tableName}.sql`);
const buffer = await readFile(file);
const sql = buffer.toString("utf8");
@@ -627,6 +668,27 @@ async function fetchSources(sourceRoot, commit) {
sha256: sha256(buffer),
});
}
const creatureTemplateSql = (await readFile(path.join(sourceRoot, "creature_template.sql"))).toString("utf8");
if (creatureTemplateUsesImmunitiesReference(creatureTemplateSql)) {
const tableName = "creature_immunities";
const url = `${base}/${tableName}.sql`;
const response = await fetch(url, {
headers: { "User-Agent": "healer-man-dungeon-pipeline" },
});
if (!response.ok) {
throw new Error(`${tableName}: ${response.status} ${response.statusText} while fetching ${url}`);
}
const buffer = Buffer.from(await response.arrayBuffer());
const file = path.join(sourceRoot, `${tableName}.sql`);
await writeFile(file, buffer);
reports.push({
table: tableName,
url,
file: path.relative(projectRoot, file).replace(/\\/g, "/"),
byteLength: buffer.length,
sha256: sha256(buffer),
});
}
await writeJson(path.join(sourceRoot, "source.lock.json"), {
schemaVersion: 1,
repository: "https://github.com/azerothcore/azerothcore-wotlk",
@@ -857,6 +919,42 @@ export async function importAzerothCoreWorld({
}
}));
const creatureImmunitiesById = new Map();
const creatureTemplateSql = (await readFile(
path.join(resolvedSourceRoot, "creature_template.sql"),
)).toString("utf8");
if (creatureTemplateUsesImmunitiesReference(creatureTemplateSql)) {
let creatureImmunitiesSourceRoot = resolvedSourceRoot;
try {
await readFile(path.join(creatureImmunitiesSourceRoot, "creature_immunities.sql"));
} catch (error) {
if (error?.code !== "ENOENT" || commit !== PINNED_CREATURE_IMMUNITIES_SOURCE.commit) throw error;
creatureImmunitiesSourceRoot = PINNED_CREATURE_IMMUNITIES_SOURCE.fixtureRoot;
}
const creatureImmunitiesReport = await parseTable(
creatureImmunitiesSourceRoot,
"creature_immunities",
(row) => {
const id = numeric(firstValue(row, ["ID", "id"]));
if (creatureImmunitiesById.has(id)) {
throw new Error(`creature_immunities contains duplicate ID ${id}.`);
}
creatureImmunitiesById.set(id, row);
},
);
if (commit === PINNED_CREATURE_IMMUNITIES_SOURCE.commit
&& creatureImmunitiesReport.sha256 !== PINNED_CREATURE_IMMUNITIES_SOURCE.sha256) {
throw new Error(
`creature_immunities.sql does not match pinned ${commit} SHA-256 `
+ `${PINNED_CREATURE_IMMUNITIES_SOURCE.sha256}.`,
);
}
tableReports.push({
...creatureImmunitiesReport,
url: `https://raw.githubusercontent.com/azerothcore/azerothcore-wotlk/${commit}/data/sql/base/db_world/creature_immunities.sql`,
});
}
const templateByEntry = new Map();
tableReports.push(await parseTable(resolvedSourceRoot, "creature_template", (row) => {
const entry = numeric(firstValue(row, ["entry", "CreatureID", "creatureId"]));
@@ -1137,7 +1235,7 @@ export async function importAzerothCoreWorld({
const normalizedTemplates = Object.fromEntries(
[...templateByEntry.entries()]
.sort(([left], [right]) => left - right)
.map(([entry, row]) => [entry, creatureTemplateReference(row)]),
.map(([entry, row]) => [entry, creatureTemplateReference(row, creatureImmunitiesById)]),
);
const normalizedCreatureClassStats = Object.fromEntries(
creatureClassLevelStats
@@ -1170,9 +1268,10 @@ export async function importAzerothCoreWorld({
}
}
const sourceFiles = tableReports.map(({ file, columns, rows, byteLength, sha256: hash }) => ({
const sourceFiles = tableReports.map(({ file, columns, rows, byteLength, sha256: hash, url }) => ({
table: path.basename(file, ".sql"),
file: path.basename(file),
...(url ? { url } : {}),
columns,
rows,
byteLength,