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

+42 -9
View File
@@ -7,12 +7,35 @@ import type { PopulationEntityDefinition } from "./mobPopulation";
import type { ContentCategoryId } from "../app/contentCategories";
import { isBiteSizedDungeonId } from "./dungeonContentSource";
import { resolveBossBalanceOverride } from "./bossBalanceOverrides";
import type { CharacterStatRuleset } from "./characterStatRuleset";
import { clampPlayerLevel } from "./progression";
// Simulator health budgets measured with the five native training builds and
// active player rotations. Keep the legacy RoM/WoW-stat curve independent.
const ROM_NATIVE_HEALTH_BANDS: readonly (readonly [number, number, number])[] = [
// character level, trash scale, boss scale
[1, 0.4, 0.2], [10, 1.0, 0.6], [30, 1.1, 0.85], [60, 1.5, 1.5],
[65, 1.8, 1.9], [66, 2.6, 2.8], [79, 4.5, 4.8], [80, 4.5, 4.8],
];
// Normal training-party point-damage budgets. Native HP/DEF grows faster than
// the shared creature budget; exact source formulas bypass this adjustment.
const ROM_NATIVE_DAMAGE_BANDS: readonly (readonly [number, number])[] = [
[1, 1], [10, 1], [30, 3], [60, 10], [65, 12], [66, 20], [79, 40], [80, 40],
];
// Legacy RoM-character parties still use their compatibility-stat output
// while generated RoM NPCs use native snapshots. Retune only the boss-health
// floor for that mixed mode; explicit native RoM health bands stay independent.
const ROM_LEGACY_BOSS_HEALTH_SCALE: readonly (readonly [level: number, scale: number])[] = [
[1, 1], [7, 1], [8, 1.12], [70, 1.12], [80, 2.3],
];
const WOW_BOSS_HEALTH_FLOOR: readonly (readonly [level: number, health: number])[] = [
// The starter anchors include enough room for authentic presence, imbue,
// disease, and shock rotations to retain the 25/45-second mechanic window.
[2, 1_050],
[4, 1_500],
// Calibrate the starter floors to the tested Ready five-member party while
// retaining the existing 25/45-second mechanic windows.
[2, 1_800],
[4, 2_600],
[9, 7_000],
[21, 11_000],
[41, 23_000],
@@ -74,7 +97,7 @@ function partyBossHealthFloor(level: number, characterSource: ContentCategoryId,
/** Campaign fights retain their full durability floor; testing rooms use a reduced floor. */
export function dungeonEnemyScaling(dungeon: DungeonDefinition, entity: PopulationEntityDefinition,
instanceId: string, characterLevel: number, difficultyId: string, bossDifficulty: number,
characterSource: ContentCategoryId = "wow") {
characterSource: ContentCategoryId = "wow", statRuleset: CharacterStatRuleset = "wow") {
const boss = entity.kind === "boss";
const testing = dungeon.id.startsWith("boss-testing:");
const level = scaledDungeonEnemyLevel(characterLevel, boss);
@@ -90,7 +113,10 @@ export function dungeonEnemyScaling(dungeon: DungeonDefinition, entity: Populati
const multipliers = boss ? bossDifficultyMultipliers(bossDifficulty) : { health: 1, damage: 1 };
const finalBoss = boss && !testing && finalDungeonBoss(dungeon, difficultyId)?.id === instanceId;
// Match durability to the character's game, including when crossing dungeon catalogs.
const partyHealthFloor = partyBossHealthFloor(level, characterSource, testing);
const legacyRomCompatibilityBossScale = boss && characterSource === "rom" && statRuleset === "wow"
? interpolatedFloor(clampPlayerLevel(characterLevel), ROM_LEGACY_BOSS_HEALTH_SCALE)
: 1;
const partyHealthFloor = partyBossHealthFloor(level, characterSource, testing) * legacyRomCompatibilityBossScale;
const biteSizedBoss = boss && !testing && isBiteSizedDungeonId(dungeon.id);
const biteSizedFloorScale = biteSizedBoss
? interpolatedFloor(characterLevel, BITE_SIZED_BOSS_HEALTH_BANDS.map(([levelAnchor, floor]) => [levelAnchor, floor]))
@@ -121,7 +147,14 @@ export function dungeonEnemyScaling(dungeon: DungeonDefinition, entity: Populati
const bossOverride = boss
? resolveBossBalanceOverride(dungeon.id, entity.id, level)
: { healthScale: 1, damageScale: 1 };
return { level, maxHealth: scaledHealth * multipliers.health * bossOverride.healthScale,
healthMultiplier: authoredHealth * multipliers.health * bossOverride.healthScale,
damageMultiplier: authoredDamage * multipliers.damage * testingDamageScale * bossOverride.damageScale };
const nativeHealthScale = characterSource === "rom" && statRuleset === "rom"
? interpolatedFloor(clampPlayerLevel(characterLevel), ROM_NATIVE_HEALTH_BANDS.map(
([anchor, trashScale, bossScale]) => [anchor, boss ? bossScale : trashScale]))
: 1;
const nativeDamageBudgetScale = characterSource === "rom" && statRuleset === "rom"
? interpolatedFloor(clampPlayerLevel(characterLevel), ROM_NATIVE_DAMAGE_BANDS) : 1;
return { level, maxHealth: scaledHealth * multipliers.health * bossOverride.healthScale * nativeHealthScale,
healthMultiplier: authoredHealth * multipliers.health * bossOverride.healthScale * nativeHealthScale,
damageMultiplier: authoredDamage * multipliers.damage * testingDamageScale * bossOverride.damageScale,
nativeDamageBudgetScale };
}