import { NodeIO } from "@gltf-transform/core"; import { BufferAttribute, BufferGeometry } from "three"; import path from "node:path"; import { fileURLToPath } from "node:url"; import { beforeAll, describe, expect, it } from "vitest"; import { buildPartyNavigationGraph, findPartyNavigationPath, type PartyNavigationGraph, type PartyNavigationPosition, } from "./partyNavigation"; import { WAILING_CAVERNS_DEFINITION } from "./dungeons/wailingCaverns"; const PARTY_BOSS_OBJECTIVES = WAILING_CAVERNS_DEFINITION.objectiveOrder; const ENTRANCE: PartyNavigationPosition = [163.49, -73.66, 132.9]; const projectDirectory = fileURLToPath(new URL("../..", import.meta.url)); const navigationPath = path.join( projectDirectory, "src", "assets", "game", "dungeons", "wailing-caverns", "wailing-caverns-navigation.glb", ); let graph: PartyNavigationGraph; beforeAll(async () => { const document = await new NodeIO().read(navigationPath); const primitive = document.getRoot().listMeshes()[0]?.listPrimitives()[0]; const positions = primitive?.getAttribute("POSITION")?.getArray(); const indices = primitive?.getIndices()?.getArray(); if (!positions || !indices) throw new Error("Shipping party navigation asset is not indexed."); const geometry = new BufferGeometry(); geometry.setAttribute("position", new BufferAttribute(new Float32Array(positions), 3)); geometry.setIndex(new BufferAttribute(new Uint32Array(indices), 1)); graph = buildPartyNavigationGraph(geometry); geometry.dispose(); }); describe("shipping Wailing Caverns party navigation", () => { it("routes every approved main-component objective from the previous encounter", () => { let origin: PartyNavigationPosition = ENTRANCE; for (const objective of PARTY_BOSS_OBJECTIVES.slice(0, 5)) { const route = findPartyNavigationPath(graph, origin, objective.position, { maxStartSnapDistance: 3, maxEndSnapDistance: 3, simplifyTolerance: 0.01, }); expect(route, `Missing approved route to ${objective.name}`).not.toBeNull(); expect(route!.length).toBeGreaterThan(1); origin = objective.position; } }); it("rejects the disconnected Serpentis transition instead of authorizing a wall shortcut", () => { const skum = PARTY_BOSS_OBJECTIVES[4]; const serpentis = PARTY_BOSS_OBJECTIVES[5]; expect(findPartyNavigationPath(graph, skum.position, serpentis.position, { maxStartSnapDistance: 3, maxEndSnapDistance: 3, simplifyTolerance: 0.01, })).toBeNull(); }); });