Release Healer Man 0.1.44

This commit is contained in:
phenom committed 2026-09-14 22:12:49 -04:00
1 parent ccc01396cd
commit 756ee4f64a
24 files changed
+575 -46

No files matched your search

+45 -8
View File
@@ -11,6 +11,7 @@ import { advanceDungeonPartyCombat, partyCombatRangeBand, resetPartyRuntimeClock
import { subscribeCombatEvents } from "./combatEvents";
import { bossDifficultyMultipliers } from "./dungeonDifficulty";
import { abilitiesForClass } from "./abilityCatalog";
import type { ContentCategoryId } from "../app/contentCategories";
const dungeon = requireDungeonDefinition("ragefire-chasm");
const final = finalDungeonBoss(dungeon)!;
@@ -21,15 +22,16 @@ afterAll(() => {
writeFileSync("artifacts/boss-difficulty/ragefire-balance.json", `${JSON.stringify({ samples }, null, 2)}\n`);
});
function fight(bossId: string, level: number, difficulty: number, seed: string, mechanics = false, playerDamage = false) {
function fight(bossId: string, level: number, difficulty: number, seed: string, mechanics = false, playerDamage = false,
characterSource: ContentCategoryId = "rom", encounterDungeon = dungeon) {
clearMobRuntimeRegistry(); clearPartyRuntimeRegistry(); resetPartyRuntimeClock();
const entity = dungeon.entities[dungeon.staticSpawns.find((spawn) => spawn.id === bossId)!.entityId];
const scaling = dungeonEnemyScaling(dungeon, entity, bossId, level, "normal", difficulty);
const entity = encounterDungeon.entities[encounterDungeon.staticSpawns.find((spawn) => spawn.id === bossId)!.entityId];
const scaling = dungeonEnemyScaling(encounterDungeon, entity, bossId, level, "normal", difficulty, characterSource);
const combat = useCombatStore.getState();
const classId = playerDamage ? "rom-mage" : "rom-priest";
combat.initializeCharacter({ classId, raceId: "rom-human", level });
const classId = characterSource === "wow" ? (playerDamage ? "mage" : "priest") : (playerDamage ? "rom-mage" : "rom-priest");
combat.initializeCharacter({ classId, raceId: characterSource === "wow" ? "human" : "rom-human", level });
combat.setPlayerPosition([-18, 0, 0]);
usePartyStore.getState().initializeParty(seed, level, useCombatStore.getState().health, playerDamage ? "damage" : "healer", 5, "rom");
usePartyStore.getState().initializeParty(seed, level, useCombatStore.getState().health, playerDamage ? "damage" : "healer", 5, characterSource);
combat.synchronizeActors();
combat.registerMob(bossId, { name: entity.name, boss: true, serverEntry: entity.combat?.serverEntry,
...scaling, attacks: entity.combat?.attacks, xpReward: 0 });
@@ -40,7 +42,8 @@ function fight(bossId: string, level: number, difficulty: number, seed: string,
}
let firstDamage: number | undefined;
let incomingDamage = 0; let healing = 0;
const heal = abilitiesForClass("rom-priest").find((ability) => ability.name === "Urgent Heal")!;
const heal = abilitiesForClass(classId).find((ability) => ability.target === "friendly"
&& ability.effects.some((effect) => effect.kind === "heal") && (ability.unlockLevel ?? 1) <= level)!;
const damage = abilitiesForClass(classId).find((ability) => ability.target === "hostile"
&& ability.effects.some((effect) => effect.kind === "damage"))!;
combat.selectMob(bossId);
@@ -59,7 +62,8 @@ function fight(bossId: string, level: number, difficulty: number, seed: string,
const injured = usePartyStore.getState().members.filter((member) => member.health > 0)
.sort((left, right) => left.health / left.maxHealth - right.health / right.maxHealth)[0];
if (injured && injured.health < injured.maxHealth * .8 && !useCombatStore.getState().activeCast) {
usePartyStore.getState().selectMember(injured.id); combat.castAbility(heal.id, undefined, now);
usePartyStore.getState().selectMember(injured.id);
combat.castAbility(heal.id, { targetActorId: injured.id, now });
}
}
const mob = useCombatStore.getState().mobs[bossId];
@@ -72,6 +76,39 @@ function fight(bossId: string, level: number, difficulty: number, seed: string,
}
describe("dungeon boss durability and difficulty", () => {
const stockades = requireDungeonDefinition("stormwind-stockade");
const stockadesBoss = stockades.bosses[0];
const stockadesEntity = stockades.entities[stockades.staticSpawns.find((entry) => entry.id === stockadesBoss.id)!.entityId];
it("gives each level-three Stockades party its source-calibrated health minimum", () => {
const easy = dungeonEnemyScaling(stockades, stockadesEntity, stockadesBoss.id, 3, "normal", 1, "wow");
expect(easy).toMatchObject({ level: 4, maxHealth: 1300 });
expect(dungeonEnemyScaling(stockades, stockadesEntity, stockadesBoss.id, 3, "normal", 10, "wow").maxHealth)
.toBe(1300 * bossDifficultyMultipliers(10).health);
// The dungeon catalog must not choose the companion power curve.
expect(dungeonEnemyScaling(stockades, stockadesEntity, stockadesBoss.id, 3, "normal", 1, "rom").maxHealth).toBeCloseTo(7074.8);
expect(dungeonEnemyScaling(stockades, stockadesEntity, stockadesBoss.id, 3, "normal", 1, "coa").maxHealth).toBe(10766);
});
for (const level of [1, 3, 8, 20, 40, 80]) {
it(`preserves WoW boss mechanic windows in Stockades at level ${level}`, () => {
for (const seed of ["wow-health-1", "wow-health-2", "wow-health-3"]) {
const ordinary = fight(stockadesBoss.id, level, 1, seed, false, false, "wow", stockades);
const last = fight(finalDungeonBoss(stockades)!.id, level, 1, seed, false, false, "wow", stockades);
samples.push({ level, seed, boss: ordinary, final: last });
expect(ordinary.seconds ?? 240, JSON.stringify({ level, seed, ordinary, last })).toBeGreaterThanOrEqual(25);
expect(last.seconds ?? 240, JSON.stringify({ level, seed, ordinary, last })).toBeGreaterThanOrEqual(45);
}
});
}
it("allows real Stockades boss mechanics during a level-three WoW Priest party fight", () => {
const result = fight(stockadesBoss.id, 3, 1, "wow-health-1", true, false, "wow", stockades);
expect(result.seconds, JSON.stringify(result)).not.toBeNull();
expect(result.seconds!).toBeLessThanOrEqual(180);
expect(result.attacks).toBeGreaterThan(0);
expect(result.incomingDamage).toBeGreaterThan(0);
});
it("increases boss health and damage at every slider step without changing trash", () => {
const spawn = dungeon.staticSpawns.find((entry) => entry.id === final.id)!;
const entity = dungeon.entities[spawn.entityId];