Release Healer Man 0.1.9
This commit is contained in:
1 parent
c245a199b6
commit
d060296ade
74 files changed
+5901
-334
No files matched your search
@@ -1,18 +1,26 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { Group, Object3D } from "three";
|
||||
import { Euler, Group, Object3D, Vector3 } from "three";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { CLASSES, type ClassId } from "../app/characterCatalog";
|
||||
import {
|
||||
STARTER_WEAPON_LOADOUTS,
|
||||
approximateStarterWeaponGripRotation,
|
||||
avatarWeaponSocketRotation,
|
||||
characterWeaponStance,
|
||||
estimateAvatarWeaponScale,
|
||||
findAvatarHandBone,
|
||||
findAvatarHandBones,
|
||||
findAvatarShieldBone,
|
||||
sampleStarterWeaponStrike,
|
||||
starterWeaponItemsForAvailableHands,
|
||||
starterWeaponLoadout,
|
||||
starterWeaponModelRotation,
|
||||
starterWeaponStance,
|
||||
} from "./starterWeapons";
|
||||
import type { AvatarManifestEntry } from "./characterManifest";
|
||||
import { resolveAvatarAttachmentPoint } from "./attachmentPoints";
|
||||
import { equipmentAttachmentHand } from "./equipmentAttachments";
|
||||
|
||||
interface RawGlbNode {
|
||||
@@ -93,6 +101,89 @@ describe("starter weapon loadouts", () => {
|
||||
expect(equipmentAttachmentHand("shield")).toBe("left");
|
||||
expect(equipmentAttachmentHand("shoulderLeft")).toBeNull();
|
||||
});
|
||||
|
||||
it("selects the native ready stance that matches each loadout", () => {
|
||||
expect(starterWeaponStance("warrior")).toBe("one-handed");
|
||||
expect(starterWeaponStance("rogue")).toBe("one-handed");
|
||||
expect(starterWeaponStance("hunter")).toBe("bow");
|
||||
expect(starterWeaponStance("priest")).toBe("two-handed-left");
|
||||
expect(starterWeaponStance("death-knight")).toBe("two-handed");
|
||||
expect(starterWeaponStance("rom-priest")).toBe("two-handed-left");
|
||||
});
|
||||
|
||||
it("lets visible equipped weapons override the class starter stance", () => {
|
||||
expect(characterWeaponStance("priest", [
|
||||
{ inventorySlot: "one-hand", itemId: 6472, weaponType: "dagger" },
|
||||
], [6472])).toBe("one-handed");
|
||||
expect(characterWeaponStance("warrior", [
|
||||
{ inventorySlot: "two-hand", itemId: 6631, weaponType: "staff" },
|
||||
], [6631])).toBe("two-handed-left");
|
||||
expect(characterWeaponStance("warrior", [
|
||||
{ inventorySlot: "ranged", itemId: 6469, weaponType: "bow" },
|
||||
], [6469])).toBe("bow");
|
||||
expect(characterWeaponStance("priest", [
|
||||
{ inventorySlot: "one-hand", itemId: 9999, weaponType: "dagger" },
|
||||
], [])).toBe("two-handed-left");
|
||||
});
|
||||
|
||||
it("normalizes procedural equipment to the native WoW item axes", () => {
|
||||
const rotated = (
|
||||
kind: Parameters<typeof starterWeaponModelRotation>[0],
|
||||
direction: readonly [number, number, number],
|
||||
) => new Vector3(...direction).applyEuler(new Euler(...starterWeaponModelRotation(kind)));
|
||||
|
||||
expect(rotated("sword", [0, -1, 0]).distanceTo(new Vector3(1, 0, 0)))
|
||||
.toBeLessThan(1e-6);
|
||||
expect(rotated("dagger", [0, -1, 0]).distanceTo(new Vector3(1, 0, 0)))
|
||||
.toBeLessThan(1e-6);
|
||||
expect(rotated("mace", [0, -1, 0]).distanceTo(new Vector3(1, 0, 0)))
|
||||
.toBeLessThan(1e-6);
|
||||
expect(rotated("staff", [0, 1, 0]).distanceTo(new Vector3(1, 0, 0)))
|
||||
.toBeLessThan(1e-6);
|
||||
expect(rotated("bow", [0, 1, 0]).distanceTo(new Vector3(1, 0, 0)))
|
||||
.toBeLessThan(1e-6);
|
||||
expect(rotated("bow", [0, 0, -1]).distanceTo(new Vector3(0, 1, 0)))
|
||||
.toBeLessThan(1e-6);
|
||||
expect(starterWeaponModelRotation("shield")).toEqual([0, 0, 0]);
|
||||
|
||||
const fallbackDirection = (
|
||||
kind: Parameters<typeof starterWeaponModelRotation>[0],
|
||||
direction: readonly [number, number, number],
|
||||
) => rotated(kind, direction).applyEuler(
|
||||
new Euler(...approximateStarterWeaponGripRotation(kind)),
|
||||
);
|
||||
expect(fallbackDirection("sword", [0, -1, 0]).distanceTo(new Vector3(0, -1, 0)))
|
||||
.toBeLessThan(1e-6);
|
||||
expect(fallbackDirection("staff", [0, 1, 0]).distanceTo(new Vector3(0, 1, 0)))
|
||||
.toBeLessThan(1e-6);
|
||||
expect(fallbackDirection("bow", [0, 1, 0]).distanceTo(new Vector3(0, 1, 0)))
|
||||
.toBeLessThan(1e-6);
|
||||
});
|
||||
|
||||
it("adapts canonical weapons to RuneWaker prop-socket axes", () => {
|
||||
const entry = {
|
||||
rendererStrategy: { kind: "runewaker-modular" },
|
||||
} as AvatarManifestEntry;
|
||||
const socketDirection = (
|
||||
kind: Parameters<typeof avatarWeaponSocketRotation>[1],
|
||||
stance: Parameters<typeof avatarWeaponSocketRotation>[2],
|
||||
) => new Vector3(1, 0, 0).applyEuler(
|
||||
new Euler(...avatarWeaponSocketRotation(entry, kind, stance)),
|
||||
);
|
||||
|
||||
expect(socketDirection("sword", "one-handed").distanceTo(new Vector3(0, 0, 1)))
|
||||
.toBeLessThan(1e-6);
|
||||
expect(socketDirection("bow", "bow").distanceTo(new Vector3(0, 0, 1)))
|
||||
.toBeLessThan(1e-6);
|
||||
expect(socketDirection("staff", "two-handed-left").distanceTo(new Vector3(0, 1, 0)))
|
||||
.toBeLessThan(1e-6);
|
||||
expect(socketDirection("sword", "two-handed").distanceTo(new Vector3(0, 1, 0)))
|
||||
.toBeLessThan(1e-6);
|
||||
expect(socketDirection("shield", "one-handed").distanceTo(new Vector3(1, 0, 0)))
|
||||
.toBeLessThan(1e-6);
|
||||
expect(avatarWeaponSocketRotation(undefined, "sword", "one-handed"))
|
||||
.toEqual([0, 0, 0]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("avatar hand-bone discovery", () => {
|
||||
@@ -123,6 +214,37 @@ describe("avatar hand-bone discovery", () => {
|
||||
expect(findAvatarHandBone(root, "right")).toBeNull();
|
||||
});
|
||||
|
||||
it("prioritizes explicit RuneWaker equipment locators over generic hand bones", () => {
|
||||
const root = new Group();
|
||||
const genericLeft = new Group();
|
||||
genericLeft.name = "Bip01 L Hand";
|
||||
const genericRight = new Group();
|
||||
genericRight.name = "Bip01 R Hand";
|
||||
const leftPivot = new Group();
|
||||
leftPivot.name = "p_left_hand";
|
||||
const rightPivot = new Group();
|
||||
rightPivot.name = "p_right_hand";
|
||||
const shieldPivot = new Group();
|
||||
shieldPivot.name = "p_hand_shield";
|
||||
root.add(genericLeft, genericRight, leftPivot, rightPivot, shieldPivot);
|
||||
|
||||
expect(findAvatarHandBone(root, "left")).toBe(leftPivot);
|
||||
expect(findAvatarHandBone(root, "right")).toBe(rightPivot);
|
||||
expect(findAvatarShieldBone(root)).toBe(shieldPivot);
|
||||
});
|
||||
|
||||
it("cancels inherited locator scale for portal children", () => {
|
||||
const root = new Group();
|
||||
const scaledRig = new Group();
|
||||
scaledRig.scale.setScalar(0.1);
|
||||
const pivot = new Group();
|
||||
root.add(scaledRig);
|
||||
scaledRig.add(pivot);
|
||||
|
||||
const transform = resolveAvatarAttachmentPoint(root, undefined, pivot);
|
||||
expect(transform?.scale.toArray()).toEqual([10, 10, 10]);
|
||||
});
|
||||
|
||||
it("estimates equipment scale in avatar-local space and ignores parent transforms", () => {
|
||||
const actor = new Group();
|
||||
actor.position.set(20, 8, -12);
|
||||
@@ -143,7 +265,7 @@ describe("avatar hand-bone discovery", () => {
|
||||
});
|
||||
|
||||
it("resolves both hands in every shipped race/gender character rig", () => {
|
||||
const manifestPaths = ["wow335a-v1", "ascension-v1"].map((version) => path.join(
|
||||
const manifestPaths = ["wow335a-v1", "ascension-v1", "runewaker-v1"].map((version) => path.join(
|
||||
projectDirectory,
|
||||
"public",
|
||||
"assets",
|
||||
@@ -154,7 +276,7 @@ describe("avatar hand-bone discovery", () => {
|
||||
const entries = manifestPaths.flatMap((manifestPath) => (
|
||||
Object.values((JSON.parse(readFileSync(manifestPath, "utf8")) as CharacterManifest).entries)
|
||||
));
|
||||
expect(entries).toHaveLength(45);
|
||||
expect(entries).toHaveLength(50);
|
||||
const resolvedLeftNames = new Set<string>();
|
||||
const resolvedRightNames = new Set<string>();
|
||||
const unresolved: string[] = [];
|
||||
@@ -172,6 +294,8 @@ describe("avatar hand-bone discovery", () => {
|
||||
// accidentally hardcodes one id cannot satisfy this diversity check.
|
||||
expect(resolvedLeftNames.size).toBeGreaterThan(8);
|
||||
expect(resolvedRightNames.size).toBeGreaterThan(8);
|
||||
expect(resolvedLeftNames).toContain("p_left_hand");
|
||||
expect(resolvedRightNames).toContain("p_right_hand");
|
||||
}, 20_000);
|
||||
});
|
||||
|
||||
|
||||
Reference in new issue
Block a user