199 lines
8.7 KiB
TypeScript
199 lines
8.7 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import type { InventoryItem } from "./lootTypes";
|
|
import {
|
|
PLAYER_EQUIPMENT_OWNER_ID,
|
|
classCanEquipItem,
|
|
equipInventoryItem,
|
|
equipmentItemLevel,
|
|
equipmentStats,
|
|
activeEquipmentStatsForClass,
|
|
normalizeEquipment,
|
|
unequipInventorySlot,
|
|
} from "./equipment";
|
|
import { EMPTY_ITEM_STATS, type ItemStats } from "./itemStats";
|
|
import { deriveRomNativeStats } from "./romNativeStats";
|
|
|
|
function stats(overrides: Partial<ItemStats>): ItemStats {
|
|
return { ...EMPTY_ITEM_STATS, ...overrides };
|
|
}
|
|
|
|
function item(
|
|
instanceId: string,
|
|
inventorySlot: string,
|
|
overrides: Partial<InventoryItem> = {},
|
|
): InventoryItem {
|
|
return {
|
|
instanceId,
|
|
itemId: 1,
|
|
itemKey: instanceId,
|
|
name: instanceId,
|
|
contentCategory: "wow",
|
|
quality: "uncommon",
|
|
inventorySlot,
|
|
armorType: null,
|
|
weaponType: null,
|
|
itemLevel: 12,
|
|
sourceItemLevel: 20,
|
|
stats: stats({ spellPower: 3, stamina: 2 }),
|
|
quantity: 1,
|
|
sourceBossKey: "kresh",
|
|
sourceBossName: "Kresh",
|
|
acquiredAt: 1,
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
describe("equipment assignments", () => {
|
|
it("moves a unique item between the player and companions", () => {
|
|
const cloak = item("cloak", "back");
|
|
const player = equipInventoryItem({}, [cloak], PLAYER_EQUIPMENT_OWNER_ID, "priest", cloak.instanceId);
|
|
expect(player.ok).toBe(true);
|
|
expect(player.assignments.player?.back).toBe("cloak");
|
|
|
|
const companion = equipInventoryItem(player.assignments, [cloak], "party-damage-1-mage", "mage", cloak.instanceId);
|
|
expect(companion.ok).toBe(true);
|
|
expect(companion.assignments.player).toBeUndefined();
|
|
expect(companion.assignments["party-damage-1-mage"]?.back).toBe("cloak");
|
|
});
|
|
|
|
it("enforces armor, shield, and weapon compatibility", () => {
|
|
const leather = item("leather", "chest", { armorType: "leather" });
|
|
const shield = item("shield", "off-hand", { armorType: "shield" });
|
|
const bow = item("bow", "ranged", { weaponType: "bow" });
|
|
expect(classCanEquipItem("priest", leather)).toBe(false);
|
|
expect(classCanEquipItem("warrior", leather)).toBe(true);
|
|
expect(classCanEquipItem("mage", shield)).toBe(false);
|
|
expect(classCanEquipItem("shaman", shield)).toBe(true);
|
|
expect(classCanEquipItem("hunter", bow)).toBe(true);
|
|
expect(classCanEquipItem("warlock", bow)).toBe(false);
|
|
});
|
|
|
|
it("rejects foreign, unresolved, and non-native RoM gear before changing assignments", () => {
|
|
const wowDagger = item("wow-dagger", "one-hand", { weaponType: "dagger" });
|
|
const nativeRomDagger = item("rom-dagger", "one-hand", {
|
|
contentCategory: "rom",
|
|
weaponType: "dagger",
|
|
stats: stats({ strength: 12, romNative: { strength: 12 } }),
|
|
});
|
|
const unresolvedRomDagger = item("unresolved-rom-dagger", "one-hand", {
|
|
contentCategory: null,
|
|
weaponType: "dagger",
|
|
stats: stats({ strength: 12 }),
|
|
});
|
|
const foreign = equipInventoryItem({ player: { back: "kept" } }, [wowDagger], "player", "rom-priest", wowDagger.instanceId);
|
|
expect(foreign).toEqual({
|
|
ok: false,
|
|
assignments: { player: { back: "kept" } },
|
|
reason: "That item belongs to a different game category.",
|
|
});
|
|
expect(equipInventoryItem({}, [nativeRomDagger], "player", "rom-priest", nativeRomDagger.instanceId).ok).toBe(true);
|
|
expect(equipInventoryItem({}, [unresolvedRomDagger], "player", "rom-priest", unresolvedRomDagger.instanceId).ok).toBe(false);
|
|
expect(normalizeEquipment({ player: { "main-hand": wowDagger.instanceId } }, [wowDagger], "rom")).toEqual({});
|
|
|
|
const forgedCategory = equipInventoryItem(
|
|
{ player: { back: "kept" } },
|
|
[wowDagger],
|
|
"player",
|
|
"rom-priest",
|
|
wowDagger.instanceId,
|
|
{ ownerCategory: "wow" },
|
|
);
|
|
expect(forgedCategory.ok).toBe(false);
|
|
expect(forgedCategory.assignments).toEqual({ player: { back: "kept" } });
|
|
expect(classCanEquipItem("rom-priest", wowDagger, { ownerCategory: "wow" })).toBe(false);
|
|
});
|
|
|
|
it("does not apply foreign or unresolved items in active equipment stat reads", () => {
|
|
const romItem = item("rom-native", "back", {
|
|
contentCategory: "rom",
|
|
stats: stats({ stamina: 20, romNative: { stamina: 20 } }),
|
|
});
|
|
const wowItem = item("wow-foreign", "head", { stats: stats({ stamina: 90 }) });
|
|
const assignments = { player: { back: romItem.instanceId, head: wowItem.instanceId } };
|
|
expect(activeEquipmentStatsForClass(assignments, [romItem, wowItem], "player", "rom-priest", "rom"))
|
|
.toMatchObject({ stamina: 20 });
|
|
expect(activeEquipmentStatsForClass(assignments, [romItem, wowItem], "player", "rom-priest", "coa"))
|
|
.toMatchObject({ stamina: 0 });
|
|
expect(activeEquipmentStatsForClass(assignments, [romItem, wowItem], "player", "rom-priest", "wow"))
|
|
.toEqual(EMPTY_ITEM_STATS);
|
|
});
|
|
|
|
it("handles two-handed conflicts, unequipping, normalization, and item level", () => {
|
|
const staff = item("staff", "two-hand", { weaponType: "staff", itemLevel: 18 });
|
|
const shield = item("shield", "off-hand", { armorType: "shield", itemLevel: 14 });
|
|
const main = equipInventoryItem({}, [staff, shield], "player", "warrior", staff.instanceId);
|
|
const offhand = equipInventoryItem(main.assignments, [staff, shield], "player", "warrior", shield.instanceId);
|
|
expect(offhand.assignments.player?.["main-hand"]).toBeUndefined();
|
|
expect(offhand.assignments.player?.["off-hand"]).toBe("shield");
|
|
expect(equipmentItemLevel(offhand.assignments, [staff, shield], "player")).toBe(14);
|
|
|
|
const unequipped = unequipInventorySlot(offhand.assignments, "player", "off-hand");
|
|
expect(unequipped.player).toBeUndefined();
|
|
expect(normalizeEquipment({ player: { back: "missing", "off-hand": "shield" } }, [shield]))
|
|
.toEqual({ player: { "off-hand": "shield" } });
|
|
});
|
|
|
|
it("totals only the items worn by the selected owner", () => {
|
|
const cloak = item("cloak", "back", { stats: stats({ spellPower: 3, stamina: 4, armor: 9 }) });
|
|
const staff = item("staff", "two-hand", {
|
|
weaponType: "staff",
|
|
stats: stats({ strength: 8, stamina: 2 }),
|
|
});
|
|
const assignments = {
|
|
player: { back: cloak.instanceId },
|
|
companion: { "main-hand": staff.instanceId },
|
|
};
|
|
|
|
expect(equipmentStats(assignments, [cloak, staff], "player"))
|
|
.toMatchObject({ spellPower: 3, stamina: 4, armor: 9 });
|
|
expect(equipmentStats(assignments, [cloak, staff], "companion"))
|
|
.toMatchObject({ strength: 8, stamina: 2, armor: 0 });
|
|
});
|
|
|
|
it("bridges foreign direct stats per item while native payloads take precedence", () => {
|
|
const sourceNative = item("native", "head", {
|
|
stats: stats({ strength: 40, health: 99, mana: 99, romNative: { strength: 3, health: 5, mana: 5 } }),
|
|
});
|
|
const foreign = item("foreign", "back", {
|
|
stats: stats({ strength: 2, health: 10, mana: 10, spirit: 700, attackPower: 500, armor: 100, spellPower: 600 }),
|
|
});
|
|
const inventory = [sourceNative, foreign];
|
|
const assignments = { player: { head: sourceNative.instanceId, back: foreign.instanceId } };
|
|
const equipped = equipmentStats(assignments, inventory, "player");
|
|
expect(equipped).toMatchObject({ strength: 42, health: 109, mana: 109 });
|
|
expect(equipped.romNative).toEqual({ strength: 5, health: 15, mana: 15 });
|
|
|
|
const withoutGear = deriveRomNativeStats({ classId: "rom-warrior", level: 1 });
|
|
const withGear = deriveRomNativeStats({ classId: "rom-warrior", level: 1, gear: equipped.romNative });
|
|
expect(withGear.attributes.strength - withoutGear.attributes.strength).toBe(5);
|
|
expect(withGear.physicalAttack - withoutGear.physicalAttack).toBe(10);
|
|
expect(withGear.health.max - withoutGear.health.max).toBeCloseTo(16);
|
|
expect(withGear.attributes.wisdom).toBe(withoutGear.attributes.wisdom);
|
|
const mageWithoutGear = deriveRomNativeStats({ classId: "rom-mage", level: 1 });
|
|
const mageWithGear = deriveRomNativeStats({ classId: "rom-mage", level: 1, gear: equipped.romNative });
|
|
expect(mageWithGear.mana.max - mageWithoutGear.mana.max).toBe(15);
|
|
|
|
const unequipped = equipmentStats({}, inventory, "player");
|
|
expect(unequipped.romNative).toBeUndefined();
|
|
expect(deriveRomNativeStats({ classId: "rom-warrior", level: 1, gear: unequipped.romNative }).health.max)
|
|
.toBe(withoutGear.health.max);
|
|
});
|
|
|
|
it("keeps CoA-tagged direct stats out of the RoM native projection", () => {
|
|
const coaGear = item("coa-gear", "chest", {
|
|
contentCategory: "coa",
|
|
armorType: "plate",
|
|
stats: stats({ stamina: 7, strength: 3 }),
|
|
});
|
|
const active = activeEquipmentStatsForClass(
|
|
{ player: { chest: coaGear.instanceId } },
|
|
[coaGear],
|
|
"player",
|
|
"guardian",
|
|
"coa",
|
|
);
|
|
expect(active).toMatchObject({ stamina: 7, strength: 3 });
|
|
expect(active.romNative).toBeUndefined();
|
|
});
|
|
});
|