Update Healer Man server

This commit is contained in:
phenom committed 2026-09-20 14:13:38 -04:00
1 parent daa417ec17
commit 8b9c95b24f
3 files changed
+48 -5

No files matched your search

+3 -2
View File
@@ -34,8 +34,9 @@ describe("bite-sized Stormwind Stockades", () => {
expect(biteSized.objectiveOrder).toEqual(biteSized.bosses); expect(biteSized.objectiveOrder).toEqual(biteSized.bosses);
expect(biteSized.staticSpawns.filter((spawn) => spawn.id === bossId)).toHaveLength(1); expect(biteSized.staticSpawns.filter((spawn) => spawn.id === bossId)).toHaveLength(1);
expect(biteSized.staticSpawns.every((spawn) => Math.abs(spawn.position[2]) <= 15)).toBe(true); expect(biteSized.staticSpawns.every((spawn) => Math.abs(spawn.position[2]) <= 15)).toBe(true);
expect(biteSized.navigationLinks.map((link) => link.id)).toEqual(["entrance-to-central-stairs"]); expect(biteSized.navigationLinks).toEqual(source.navigationLinks);
expect(biteSized.traversalRamps?.map((ramp) => ramp.id)).toEqual(["entrance-stair-ramp"]); expect(biteSized.traversalRamps).toEqual(source.traversalRamps);
expect(biteSized.roamingPacks).toEqual([]);
}); });
it("seals both side wings with visible movement and sight blockers", () => { it("seals both side wings with visible movement and sight blockers", () => {
+5 -3
View File
@@ -133,8 +133,10 @@ export function createBiteSizedStockadesDefinition(
roamingPacks: [], roamingPacks: [],
bosses: [boss], bosses: [boss],
objectiveOrder: [boss], objectiveOrder: [boss],
navigationLinks: source.navigationLinks.filter((link) => link.id === "entrance-to-central-stairs"), // Keep the complete reviewed Stockades navigation graph connected. The
traversalRamps: source.traversalRamps?.filter((ramp) => ramp.id === "entrance-stair-ramp"), // physical branch walls, not navmesh pruning, close the unused wings.
navigationLinks: source.navigationLinks,
traversalRamps: source.traversalRamps,
encounters: [{ encounters: [{
id: `${BITE_SIZED_STOCKADES_DUNGEON_ID}:boss-progression`, id: `${BITE_SIZED_STOCKADES_DUNGEON_ID}:boss-progression`,
dungeonId: BITE_SIZED_STOCKADES_DUNGEON_ID, dungeonId: BITE_SIZED_STOCKADES_DUNGEON_ID,
@@ -148,7 +150,7 @@ export function createBiteSizedStockadesDefinition(
reviewedBehavior: "A single seeded Stockades boss completes the bite-sized run in the first boss room.", reviewedBehavior: "A single seeded Stockades boss completes the bite-sized run in the first boss room.",
adaptations: [ adaptations: [
"Each seeded run retains the selected source boss objective ID so saves, loot, guides, and leaderboards report the correct variant.", "Each seeded run retains the selected source boss objective ID so saves, loot, guides, and leaderboards report the correct variant.",
"The two side-wing stairs are permanently sealed and omitted from party navigation.", "The side-wing stairs retain their navigation bridges so the central route stays on the retained navmesh component; physical walls seal both unused wings.",
], ],
remainingGaps: [], remainingGaps: [],
}, },
+40
View File
@@ -29,7 +29,9 @@ const STOCKADES_NAVIGATION = "public/assets/game/manastorm/34-stormwindjail/navi
const TANK_RANGE = Object.freeze({ minimum: 0, preferred: 2.9, maximum: 3.15 }); const TANK_RANGE = Object.freeze({ minimum: 0, preferred: 2.9, maximum: 3.15 });
const stockades = requireDungeonDefinition("stormwind-stockade"); const stockades = requireDungeonDefinition("stormwind-stockade");
const biteSizedStockades = requireDungeonDefinition("bite-sized-stormwind-stockade");
let graph: PartyNavigationGraph; let graph: PartyNavigationGraph;
let biteSizedGraph: PartyNavigationGraph;
let sourceCollision: Group; let sourceCollision: Group;
beforeAll(async () => { beforeAll(async () => {
@@ -64,6 +66,10 @@ beforeAll(async () => {
graph = preparePartyNavigationGraph( graph = preparePartyNavigationGraph(
"/test/stockades-navigation.glb", scene, stockades.navigationLinks, stockades.traversalRamps, "/test/stockades-navigation.glb", scene, stockades.navigationLinks, stockades.traversalRamps,
); );
biteSizedGraph = preparePartyNavigationGraph(
"/test/bite-sized-stockades-navigation.glb", scene,
biteSizedStockades.navigationLinks, biteSizedStockades.traversalRamps,
);
geometry.dispose(); geometry.dispose();
const collisionDocument = await new NodeIO() const collisionDocument = await new NodeIO()
@@ -256,6 +262,40 @@ describe("Stormwind Stockade party navigation", () => {
expect(graph.triangleNodes?.length).toBe(graph.triangles?.length); expect(graph.triangleNodes?.length).toBe(graph.triangles?.length);
}); });
it("keeps the bite-sized entrance and boss room on the retained navigation surface", () => {
const world = collisionWorld();
try {
for (const wall of biteSizedStockades.serverGameObjects.filter((object) => object.id.startsWith("bite-sized-"))) {
world.createCollider(RAPIER.ColliderDesc.cuboid(...wall.halfExtents)
.setTranslation(...wall.position)
.setCollisionGroups(DUNGEON_PHYSICAL_COLLISION_GROUPS));
}
world.step();
const entrance = findNearestPartyNavigationSurface(
biteSizedGraph,
biteSizedStockades.entrance.footPosition,
6,
);
const boss = findNearestPartyNavigationSurface(
biteSizedGraph,
biteSizedStockades.bosses[0].position,
15,
);
expect(entrance).not.toBeNull();
expect(boss).not.toBeNull();
expect(findPartyNavigationPath(
biteSizedGraph,
entrance!.position,
boss!.position,
{
maxStartSnapDistance: 0.1,
maxEndSnapDistance: 0.1,
routeSegmentAllowed: createPartyRouteSegmentValidator(RAPIER, world),
},
)).not.toBeNull();
} finally { world.free(); }
});
it("projects every entrance formation slot onto the retained walkable surface", () => { it("projects every entrance formation slot onto the retained walkable surface", () => {
const slots = derivePartySpawnSlots({ const slots = derivePartySpawnSlots({
footPosition: stockades.entrance.footPosition, footPosition: stockades.entrance.footPosition,