Release Healer Man 0.1.20

This commit is contained in:
phenom committed 2026-09-06 14:38:59 -04:00
1 parent d9bebb4e81
commit 699fc858c9
22 files changed
+551 -295

No files matched your search

@@ -116,10 +116,12 @@ function reportMarkdown(report) {
dungeon.proceduralModels, "|", dungeon.status, "|",
].join(" "));
return [
"# WoW dungeon content report",
"# WoW dungeon combat and animation report",
"",
"Status: **" + report.status + "**",
"",
"Scope: executable combat populations and creature animation bindings. This report does not certify instance scripts, doors, interactables, escorts, waves, vehicles, or navigation.",
"",
"Coverage: " + report.summary.dungeons + " dungeons, " + report.summary.mobs
+ " mobs, " + report.summary.bosses + " bosses, " + report.summary.attacks
+ " executable attacks, and " + report.summary.specialAttacks + " special attacks/mechanics.",
@@ -177,6 +179,10 @@ for (const dungeon of definitions) {
let specialAttacks = 0;
let nativeModels = 0;
let proceduralModels = 0;
const optionalBossEntityIds = new Set((dungeon.objectiveOrder ?? [])
.filter((objective) => objective.optional)
.map((objective) => dungeon.staticSpawns?.find((spawn) => spawn.id === objective.id)?.entityId)
.filter(Boolean));
if (!entities.length) dungeonIssues.push("has no combat entities.");
if (!bosses.length) dungeonIssues.push("has no bosses.");
@@ -203,7 +209,7 @@ for (const dungeon of definitions) {
attacks += entityAttacks.length;
const specials = entityAttacks.filter((attack) => attack.id !== "basic-attack");
specialAttacks += specials.length;
if (entity.kind === "boss" && !specials.length) {
if (entity.kind === "boss" && !specials.length && !optionalBossEntityIds.has(entityId)) {
const exceptionKey = dungeon.id + ":" + entityId;
if (!basicOnlyBossExceptions.has(exceptionKey)) {
dungeonIssues.push(entityId + " has no boss mechanic beyond its basic attack.");
@@ -297,7 +303,7 @@ const summary = dungeons.reduce((result, dungeon) => ({
const report = {
schemaVersion: 1,
status: issues.length ? "blocked" : "green",
scope: "HealerMan WoW five-player campaign",
scope: "HealerMan WoW five-player combat and animation coverage",
summary,
documentedExceptions,
animationFallbacks,
@@ -317,4 +323,4 @@ console.log(JSON.stringify({
documentedExceptions: report.documentedExceptions,
issues: report.issues,
}, null, 2));
if (issues.length) process.exitCode = 1;
if (issues.length) process.exitCode = 1;
@@ -707,6 +707,18 @@ function smartAiMechanicsFor(entity, boss, spells, worldAi) {
return mechanics;
}
function initializationSpawnChance(entry, worldAi) {
const despawnRows = (worldAi?.smartScriptsByEntry?.[String(entry)] ?? []).filter((row) => (
Number(row.event_type) === 37
&& Number(row.action_type) === 41
&& Number(row.target_type) === 1
));
if (!despawnRows.length) return 1;
return rounded(despawnRows.reduce((chance, row) => (
chance * (1 - Math.max(0, Math.min(100, Number(row.event_chance) || 0)) / 100)
), 1));
}
function uniqueMechanics(mechanics) {
const seenIds = new Set();
const seenSpellIds = new Set();
@@ -1233,6 +1245,7 @@ async function compile() {
const id = encounter.eventOnly
? "mutanus-spawn"
: candidate?.id ?? `encounter-${encounter.encounterId}`;
const spawnChance = initializationSpawnChance(encounter.creatureId, azerothCoreWorldAi);
usedBossSpawnIds.add(id);
return {
id,
@@ -1240,6 +1253,7 @@ async function compile() {
name: encounter.name,
position,
yaw: candidate?.yaw ?? 0,
...(spawnChance < 1 ? { optional: true } : {}),
...(encounter.eventOnly ? { eventOnly: true } : {}),
};
});
@@ -1248,6 +1262,7 @@ async function compile() {
const roamingPacks = [];
for (const spawn of sourceSpawns) {
const entry = Number(spawn.entityId.replace("entry-", ""));
const spawnChance = initializationSpawnChance(entry, azerothCoreWorldAi);
const isBoss = bossEntries.has(entry);
const transformedPosition = mapPosition(spawn.position);
if (!isBoss && spawn.waypoints?.length >= 2) {
@@ -1269,6 +1284,7 @@ async function compile() {
pathId: spawn.pathId,
formationSource: "database-solo",
spawnMask: spawn.spawnMask,
...(spawnChance < 1 ? { spawnChance } : {}),
waypoints,
members: [{
id: "member",
@@ -1286,6 +1302,7 @@ async function compile() {
position: transformedPosition,
yaw: spawn.yaw,
spawnMask: spawn.spawnMask,
...(spawnChance < 1 ? { spawnChance } : {}),
});
}
for (const boss of bossObjectives) {
@@ -1337,10 +1354,11 @@ async function compile() {
...roamingPacks.flatMap((pack) => pack.waypoints),
];
const bounds = finiteBounds(allPoints);
const bosses = bossObjectives.map(({ id, name, position, eventOnly }) => ({
const bosses = bossObjectives.map(({ id, name, position, optional, eventOnly }) => ({
id,
name,
position,
...(optional ? { optional: true } : {}),
...(eventOnly ? { eventOnly: true } : {}),
}));
const databaseKind = recipe.clientBuild === "3.3.5a-Epoch" || draft?.spawns?.length