Files
healer-man/src/game/lootTypes.ts
T
2026-09-05 15:33:06 -04:00

84 lines
3.2 KiB
TypeScript

import {
normalizeItemStats,
MAX_PERSISTED_ITEM_LEVEL,
type ItemQuality,
type ItemStats,
} from "./itemStats";
export type { ItemQuality, ItemStats } from "./itemStats";
/** A concrete item owned by a character, distinct from its SQL item template. */
export interface InventoryItem {
readonly instanceId: string;
readonly itemId: number;
readonly itemKey: string;
readonly name: string;
readonly quality: ItemQuality;
readonly inventorySlot: string;
readonly armorType: string | null;
readonly weaponType: string | null;
/** Scaled at boss death from the killing player's level. */
readonly itemLevel: number;
/** Original 3.3.5a item level retained for provenance and future balancing. */
readonly sourceItemLevel: number;
/** Concrete scaled attributes captured when the boss drops this item. */
readonly stats: ItemStats;
readonly quantity: number;
readonly sourceBossKey: string;
readonly sourceBossName: string;
readonly acquiredAt: number;
}
const ITEM_QUALITIES = new Set<ItemQuality>(["common", "uncommon", "rare", "epic", "legendary"]);
/** Safely migrates persisted profiles created before inventory existed. */
export function normalizeInventory(value: unknown): InventoryItem[] {
if (!Array.isArray(value)) return [];
const normalized: InventoryItem[] = [];
for (const candidate of value) {
if (!candidate || typeof candidate !== "object") continue;
const item = candidate as Partial<InventoryItem>;
if (
typeof item.instanceId !== "string"
|| !Number.isFinite(item.itemId)
|| typeof item.itemKey !== "string"
|| typeof item.name !== "string"
|| !ITEM_QUALITIES.has(item.quality as ItemQuality)
|| typeof item.inventorySlot !== "string"
|| !Number.isFinite(item.itemLevel)
|| !Number.isFinite(item.sourceItemLevel)
|| !Number.isFinite(item.quantity)
|| typeof item.sourceBossKey !== "string"
|| typeof item.sourceBossName !== "string"
|| !Number.isFinite(item.acquiredAt)
) continue;
const itemLevel = Math.max(1, Math.min(MAX_PERSISTED_ITEM_LEVEL, Math.trunc(item.itemLevel!)));
normalized.push({
instanceId: item.instanceId,
itemId: Math.max(1, Math.trunc(item.itemId!)),
itemKey: item.itemKey,
name: item.name,
quality: item.quality as ItemQuality,
inventorySlot: item.inventorySlot,
armorType: typeof item.armorType === "string" ? item.armorType : null,
weaponType: typeof item.weaponType === "string" ? item.weaponType : null,
itemLevel,
sourceItemLevel: Math.max(1, Math.trunc(item.sourceItemLevel!)),
stats: normalizeItemStats(item.stats, {
itemId: Math.max(1, Math.trunc(item.itemId!)),
quality: item.quality as ItemQuality,
inventorySlot: item.inventorySlot,
armorType: typeof item.armorType === "string" ? item.armorType : null,
weaponType: typeof item.weaponType === "string" ? item.weaponType : null,
itemLevel,
sourceItemLevel: Math.max(1, Math.trunc(item.sourceItemLevel!)),
}),
quantity: Math.max(1, Math.trunc(item.quantity!)),
sourceBossKey: item.sourceBossKey,
sourceBossName: item.sourceBossName,
acquiredAt: Math.max(0, Math.trunc(item.acquiredAt!)),
});
}
return normalized;
}