Update Healer Man server
This commit is contained in:
1 parent
fdb6739f8d
commit
f90ba7d989
41 files changed
+1671
-118
No files matched your search
@@ -1,6 +1,4 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { CLASSES } from "../../app/characterCatalog";
|
||||
import { partyRolesForClass } from "../partyRoles";
|
||||
import { runtimeNormalizeTalentRanks } from "../talentRuntimeCatalog";
|
||||
import {
|
||||
loadCanonicalBenchmarkBuilds,
|
||||
@@ -9,16 +7,15 @@ import {
|
||||
} from "./benchmarkBuilds";
|
||||
|
||||
describe("canonical benchmark builds", () => {
|
||||
it("covers every declared class-role pair exactly once with legal ranks", async () => {
|
||||
it("defaults to the five Ready class/spec builds with legal ranks", async () => {
|
||||
const builds = await loadCanonicalBenchmarkBuilds();
|
||||
const expectedPairs = CLASSES.flatMap((definition) => (
|
||||
partyRolesForClass(definition.id).map((role) => `${definition.id}:${role}`)
|
||||
));
|
||||
const actualPairs = builds.map((build) => `${build.classId}:${build.role}`);
|
||||
|
||||
expect(builds).toHaveLength(77);
|
||||
expect(new Set(actualPairs).size).toBe(builds.length);
|
||||
expect(new Set(actualPairs)).toEqual(new Set(expectedPairs));
|
||||
expect(builds.map((build) => `${build.classId}:${build.orientation}:${build.role}`)).toEqual([
|
||||
"warrior:Protection:tank",
|
||||
"hunter:Marksmanship:damage",
|
||||
"rogue:Assassination:damage",
|
||||
"priest:Discipline:healer",
|
||||
"mage:Fire:damage",
|
||||
]);
|
||||
for (const build of builds) {
|
||||
expect(build.level).toBe(60);
|
||||
expect(build.gearPresetId).toBe("virtual-heirloom");
|
||||
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
} from "../partyTalentRuntime";
|
||||
import { clampPlayerLevel } from "../progression";
|
||||
import {
|
||||
romGearProfile,
|
||||
specializationsForClass,
|
||||
type SpecializationDefinition,
|
||||
} from "../specializationCatalog";
|
||||
@@ -45,6 +46,7 @@ import {
|
||||
} from "../talentUiCatalog";
|
||||
import type { BenchmarkBuildDefinition } from "./benchmarkTypes";
|
||||
import {
|
||||
READY_COMPANION_SPECIALIZATION_IDS,
|
||||
readinessStatusFor,
|
||||
type CompanionSpecReadinessStatus,
|
||||
} from "../companionSpecReadiness";
|
||||
@@ -59,6 +61,7 @@ const EMPTY_TALENT_RANKS: RuntimeTalentRanks = Object.freeze({});
|
||||
const BASE_CLASS_ID_SET: ReadonlySet<ClassId> = new Set(BASE_CLASS_IDS);
|
||||
const COA_CLASS_ID_SET: ReadonlySet<ClassId> = new Set(COA_CLASS_IDS);
|
||||
const ROM_CLASS_ID_SET: ReadonlySet<ClassId> = new Set(ROM_CLASS_IDS);
|
||||
const READY_SPECIALIZATION_ID_SET: ReadonlySet<string> = new Set(READY_COMPANION_SPECIALIZATION_IDS);
|
||||
|
||||
const WOW_ORIENTATION_BY_ROLE: Readonly<
|
||||
Record<BaseClassId, Readonly<Partial<Record<PartyRole, string>>>>
|
||||
@@ -66,8 +69,8 @@ const WOW_ORIENTATION_BY_ROLE: Readonly<
|
||||
warrior: { tank: "Protection", damage: "Fury" },
|
||||
paladin: { tank: "Protection", healer: "Holy", damage: "Retribution" },
|
||||
hunter: { damage: "Marksmanship" },
|
||||
rogue: { damage: "Combat" },
|
||||
priest: { healer: "Holy", damage: "Shadow" },
|
||||
rogue: { damage: "Assassination" },
|
||||
priest: { healer: "Discipline", damage: "Shadow" },
|
||||
"death-knight": { tank: "Blood", damage: "Frost" },
|
||||
shaman: { healer: "Restoration", damage: "Elemental" },
|
||||
mage: { damage: "Fire" },
|
||||
@@ -555,14 +558,19 @@ export function selectableBenchmarkBuildCatalog(
|
||||
.map((definition) => definition.id)
|
||||
.filter((classId) => !requested || requested.has(classId))
|
||||
.flatMap((classId): readonly SelectableBenchmarkBuildCatalogEntry[] => {
|
||||
if (isRomClassId(classId)) return partyRolesForClass(classId).map((role) => ({
|
||||
id: benchmarkBuildId(classId, role, safeLevel),
|
||||
source: contentCategoryForClass(classId),
|
||||
classId,
|
||||
role,
|
||||
orientation: "Primary",
|
||||
level: safeLevel,
|
||||
}));
|
||||
if (isRomClassId(classId)) return partyRolesForClass(classId).map((role) => {
|
||||
const specialization = romGearProfile(classId, role);
|
||||
return {
|
||||
id: benchmarkBuildId(classId, role, safeLevel),
|
||||
source: contentCategoryForClass(classId),
|
||||
classId,
|
||||
role,
|
||||
orientation: "Primary",
|
||||
specializationId: specialization.id,
|
||||
readinessStatus: readinessStatusFor(specialization.id),
|
||||
level: safeLevel,
|
||||
};
|
||||
});
|
||||
return specializationsForClass(classId).flatMap((specialization) => {
|
||||
const profiles = isBaseClassId(classId)
|
||||
? runtimePartyTalentProfiles(classId, specialization.displayName)
|
||||
@@ -583,8 +591,8 @@ export function selectableBenchmarkBuildCatalog(
|
||||
classId,
|
||||
role: specialization.role,
|
||||
orientation: specialization.displayName,
|
||||
specializationId: isBaseClassId(classId) ? specialization.id : undefined,
|
||||
readinessStatus: isBaseClassId(classId) ? readinessStatusFor(specialization.id) : undefined,
|
||||
specializationId: specialization.id,
|
||||
readinessStatus: readinessStatusFor(specialization.id),
|
||||
level: safeLevel,
|
||||
characterName: talentProfile?.characterName,
|
||||
talentBuildId: talentProfile?.id,
|
||||
@@ -663,19 +671,25 @@ export async function loadSelectableBenchmarkBuilds(
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates one stable, legal benchmark build for every declared class-role pair.
|
||||
* A filter changes only membership; output always follows the canonical CLASSES
|
||||
* and partyRolesForClass declaration order.
|
||||
* Creates the five Ready benchmark builds by default. Supplying explicit class
|
||||
* IDs remains a targeted diagnostic and returns those classes' declared roles.
|
||||
* Output follows the canonical CLASSES and partyRolesForClass declaration order.
|
||||
*/
|
||||
export async function loadCanonicalBenchmarkBuilds(
|
||||
level = CANONICAL_BENCHMARK_LEVEL,
|
||||
classIds?: readonly ClassId[],
|
||||
): Promise<BenchmarkBuildDefinition[]> {
|
||||
const safeLevel = clampPlayerLevel(level);
|
||||
const readyOnly = classIds === undefined;
|
||||
const requested = classIds ? new Set(classIds) : null;
|
||||
const selectedClassIds = CLASSES
|
||||
.map((definition) => definition.id)
|
||||
.filter((classId) => !requested || requested.has(classId));
|
||||
.filter((classId) => {
|
||||
if (requested) return requested.has(classId);
|
||||
return isBaseClassId(classId) && specializationsForClass(classId).some((specialization) => (
|
||||
READY_SPECIALIZATION_ID_SET.has(specialization.id)
|
||||
));
|
||||
});
|
||||
const selectedCoaClassIds = selectedClassIds.filter(isCoaClassId);
|
||||
|
||||
await preloadAbilityCatalogsForClasses(selectedCoaClassIds);
|
||||
@@ -690,6 +704,13 @@ export async function loadCanonicalBenchmarkBuilds(
|
||||
for (const classId of selectedClassIds) {
|
||||
for (const role of partyRolesForClass(classId)) {
|
||||
if (isBaseClassId(classId)) {
|
||||
if (readyOnly) {
|
||||
const orientation = WOW_ORIENTATION_BY_ROLE[classId][role];
|
||||
const specialization = specializationsForClass(classId).find((candidate) => (
|
||||
candidate.displayName === orientation
|
||||
));
|
||||
if (!specialization || !READY_SPECIALIZATION_ID_SET.has(specialization.id)) continue;
|
||||
}
|
||||
builds.push(wowBuild(classId, role, safeLevel));
|
||||
} else if (isCoaClassId(classId)) {
|
||||
const catalog = coaCatalogs.get(classId);
|
||||
|
||||
Reference in new issue
Block a user