Update Healer Man server
This commit is contained in:
1 parent
3e30af4c98
commit
97ee0c13f9
25 files changed
+576
-179
No files matched your search
@@ -3,20 +3,32 @@ import {
|
||||
RANDOMIZED_BITE_SIZED_STOCKADES_DUNGEON_ID,
|
||||
STORMWIND_STOCKADE_DUNGEON_ID,
|
||||
} from "./dungeonContentSource";
|
||||
import { DUNGEON_REGISTRY, dungeonDefinitionById } from "./dungeonRegistry";
|
||||
import { activateDungeonSession } from "./dungeonSession";
|
||||
import { BITE_SIZED_STOCKADES_BOSS_CANDIDATES } from "./dungeons/biteSizedStockades";
|
||||
import {
|
||||
createRandomizedBiteSizedStockadesDefinition,
|
||||
randomizedBiteSizedCreatureCatalog,
|
||||
} from "./dungeons/randomizedBiteSizedStockades";
|
||||
DUNGEON_REGISTRY,
|
||||
RANDOMIZED_BITE_SIZED_CREATURE_CATALOG,
|
||||
dungeonDefinitionById,
|
||||
} from "./dungeonRegistry";
|
||||
import { activateDungeonSession } from "./dungeonSession";
|
||||
import { createBiteSizedStockadesDefinition } from "./dungeons/biteSizedStockades";
|
||||
import { createRandomizedBiteSizedStockadesDefinition } from "./dungeons/randomizedBiteSizedStockades";
|
||||
|
||||
const source = DUNGEON_REGISTRY[STORMWIND_STOCKADE_DUNGEON_ID];
|
||||
const catalog = RANDOMIZED_BITE_SIZED_CREATURE_CATALOG;
|
||||
|
||||
function create(seed: string) {
|
||||
return createRandomizedBiteSizedStockadesDefinition(source, seed, catalog);
|
||||
}
|
||||
|
||||
function bossEntityServerEntry(definition: ReturnType<typeof create>): number | undefined {
|
||||
const boss = definition.bosses[0];
|
||||
const spawn = definition.staticSpawns.find((candidate) => candidate.id === boss.id);
|
||||
return spawn ? definition.entities[spawn.entityId]?.combat?.serverEntry : undefined;
|
||||
}
|
||||
|
||||
describe("Randomized Bite-Sized Stockades", () => {
|
||||
it("keeps source geometry and both existing Stockades definitions immutable", () => {
|
||||
const originalSpawns = source.staticSpawns.map((spawn) => ({ ...spawn }));
|
||||
const randomized = createRandomizedBiteSizedStockadesDefinition(source, "random-layout-a");
|
||||
const randomized = create("random-layout-a");
|
||||
|
||||
expect(randomized.id).toBe(RANDOMIZED_BITE_SIZED_STOCKADES_DUNGEON_ID);
|
||||
expect(randomized.navigationLinks).toEqual(source.navigationLinks);
|
||||
@@ -26,34 +38,85 @@ describe("Randomized Bite-Sized Stockades", () => {
|
||||
expect(source.staticSpawns).toEqual(originalSpawns);
|
||||
});
|
||||
|
||||
it("rerolls every ordinary slot from the complete placed Stockades mob pool", () => {
|
||||
const catalog = randomizedBiteSizedCreatureCatalog(source);
|
||||
const mobIds = catalog.filter((candidate) => candidate.kind === "mob").map((candidate) => candidate.entityId);
|
||||
const observed = new Set<string>();
|
||||
for (let index = 0; index < 2_000; index += 1) {
|
||||
const definition = createRandomizedBiteSizedStockadesDefinition(source, `mob-roll-${index}`);
|
||||
const bossId = definition.bosses[0].id;
|
||||
for (const spawn of definition.staticSpawns.filter((candidate) => candidate.id !== bossId)) {
|
||||
expect(mobIds).toContain(spawn.entityId);
|
||||
observed.add(spawn.entityId);
|
||||
}
|
||||
}
|
||||
expect([...observed].sort()).toEqual([...mobIds].sort());
|
||||
it("uses the complete model-backed cross-dungeon catalog regardless of installed packs", () => {
|
||||
const mobs = catalog.filter((candidate) => candidate.kind === "mob");
|
||||
const bosses = catalog.filter((candidate) => candidate.kind === "boss");
|
||||
|
||||
expect(mobs.length).toBeGreaterThan(500);
|
||||
expect(bosses.length).toBeGreaterThan(100);
|
||||
expect(new Set(catalog.map((candidate) => candidate.sourceDungeonId)).size).toBeGreaterThan(50);
|
||||
expect(catalog.some((candidate) => candidate.definition.visual.model?.url.startsWith("/assets/creatures/wailing-caverns/"))).toBe(true);
|
||||
expect(catalog.some((candidate) => candidate.definition.visual.model?.url.startsWith("/assets/game/creatures/shared/"))).toBe(true);
|
||||
expect(catalog.some((candidate) => candidate.definition.visual.model?.url.startsWith("/assets/game/dungeons/")
|
||||
&& candidate.sourceDungeonId !== STORMWIND_STOCKADE_DUNGEON_ID)).toBe(true);
|
||||
expect(catalog.some((candidate) => candidate.sourceDungeonId === STORMWIND_STOCKADE_DUNGEON_ID)).toBe(true);
|
||||
expect(catalog.every((candidate) => /^(wow|rom):(mob|boss):[1-9][0-9]*$/.test(candidate.key))).toBe(true);
|
||||
});
|
||||
|
||||
it("reconstructs the same party lineup from a run id and can select every boss", () => {
|
||||
const first = createRandomizedBiteSizedStockadesDefinition(source, "shared-run");
|
||||
const second = createRandomizedBiteSizedStockadesDefinition(source, "shared-run");
|
||||
it("replaces every corridor spawn and deals distinct mobs without replacement", () => {
|
||||
const randomized = create("every-slot-is-random");
|
||||
const bossId = randomized.bosses[0].id;
|
||||
const trash = randomized.staticSpawns.filter((spawn) => spawn.id !== bossId);
|
||||
const selectedIds = trash.map((spawn) => spawn.entityId);
|
||||
const base = createBiteSizedStockadesDefinition(source, "every-slot-is-random:objective-slot");
|
||||
|
||||
expect(trash).toHaveLength(12);
|
||||
expect(new Set(selectedIds).size).toBe(trash.length);
|
||||
expect(selectedIds.every((entityId) => /^randomized:(wow|rom):mob:/.test(entityId))).toBe(true);
|
||||
for (const spawn of trash) {
|
||||
const entity = randomized.entities[spawn.entityId];
|
||||
const originalSpawn = base.staticSpawns.find((candidate) => candidate.id === spawn.id)!;
|
||||
const original = base.entities[originalSpawn.entityId];
|
||||
expect(entity.kind).toBe("mob");
|
||||
expect(entity.combat?.serverEntry).not.toBe(original.combat?.serverEntry);
|
||||
expect(entity.combat).toMatchObject({
|
||||
healthMultiplier: 1,
|
||||
damageMultiplier: 1,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it("uses independent deterministic trash and boss rolls across run ids", () => {
|
||||
const first = create("shared-run");
|
||||
const second = create("shared-run");
|
||||
expect(second.staticSpawns).toEqual(first.staticSpawns);
|
||||
expect(second.bosses).toEqual(first.bosses);
|
||||
|
||||
const bosses = new Set<string>();
|
||||
for (let index = 0; index < 1_000; index += 1) {
|
||||
bosses.add(createRandomizedBiteSizedStockadesDefinition(source, `boss-roll-${index}`).bosses[0].id);
|
||||
const bossEntries = new Set(catalog.filter((candidate) => candidate.kind === "boss").map((candidate) => candidate.serverEntry));
|
||||
const observedBossEntries = new Set<number>();
|
||||
const observedTrashLineups = new Set<string>();
|
||||
for (let index = 0; index < 2_000; index += 1) {
|
||||
const definition = create(`roll-${index}`);
|
||||
const entry = bossEntityServerEntry(definition);
|
||||
if (entry) observedBossEntries.add(entry);
|
||||
const bossId = definition.bosses[0].id;
|
||||
observedTrashLineups.add(definition.staticSpawns
|
||||
.filter((spawn) => spawn.id !== bossId)
|
||||
.map((spawn) => spawn.entityId)
|
||||
.join("|"));
|
||||
}
|
||||
expect([...bosses].sort()).toEqual(BITE_SIZED_STOCKADES_BOSS_CANDIDATES.map((candidate) => candidate.sourceSpawnId).sort());
|
||||
expect([...observedBossEntries].every((entry) => bossEntries.has(entry))).toBe(true);
|
||||
expect(observedBossEntries.size).toBeGreaterThan(100);
|
||||
expect(observedTrashLineups.size).toBeGreaterThan(1_900);
|
||||
|
||||
expect(activateDungeonSession(RANDOMIZED_BITE_SIZED_STOCKADES_DUNGEON_ID, undefined, { runId: "shared-run" })).toBe(true);
|
||||
expect(dungeonDefinitionById(RANDOMIZED_BITE_SIZED_STOCKADES_DUNGEON_ID)?.staticSpawns).toEqual(first.staticSpawns);
|
||||
});
|
||||
|
||||
it("keeps the randomized boss on the location's stable completion objective", () => {
|
||||
const randomized = create("random-boss-objective");
|
||||
const boss = randomized.bosses[0];
|
||||
const spawn = randomized.staticSpawns.find((candidate) => candidate.id === boss.id)!;
|
||||
const entity = randomized.entities[spawn.entityId];
|
||||
const base = createBiteSizedStockadesDefinition(source, "random-boss-objective:objective-slot");
|
||||
const originalSpawn = base.staticSpawns.find((candidate) => candidate.id === boss.id)!;
|
||||
const originalBoss = base.entities[originalSpawn.entityId];
|
||||
|
||||
expect(entity.kind).toBe("boss");
|
||||
expect(boss.name).toBe(entity.name);
|
||||
expect(catalog.some((candidate) => candidate.kind === "boss" && candidate.entityId === spawn.entityId)).toBe(true);
|
||||
expect(entity.combat?.serverEntry).not.toBe(originalBoss.combat?.serverEntry);
|
||||
expect(randomized.encounters?.[0].rules[0].trigger).toEqual({ kind: "entities-dead", entityIds: [boss.id] });
|
||||
expect(entity.combat).toMatchObject({ healthMultiplier: 1, damageMultiplier: 1 });
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user