84 lines
3.2 KiB
TypeScript
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;
|
|
}
|