Guard source gear namespaces and retain recoverable legacy records (#9)
This commit is contained in:
1 parent
ab37006932
commit
e567910c04
23 files changed
+1203
-66
No files matched your search
+40
-1
@@ -4,11 +4,15 @@ import {
|
||||
type ItemQuality,
|
||||
type ItemStats,
|
||||
} from "./itemStats";
|
||||
import { normalizeRomNativeGearStats } from "./romNativeStatsTypes";
|
||||
import type { ContentCategoryId } from "../app/contentCategories";
|
||||
|
||||
export type { ItemQuality, ItemStats } from "./itemStats";
|
||||
|
||||
/** A concrete item owned by a character, distinct from its SQL item template. */
|
||||
export interface InventoryItem {
|
||||
/** Source-game namespace. Missing legacy metadata is normalized with the owning profile. */
|
||||
readonly contentCategory?: ContentCategoryId | null;
|
||||
readonly instanceId: string;
|
||||
readonly itemId: number;
|
||||
readonly itemKey: string;
|
||||
@@ -31,8 +35,37 @@ export interface InventoryItem {
|
||||
|
||||
const ITEM_QUALITIES = new Set<ItemQuality>(["common", "uncommon", "rare", "epic", "legendary"]);
|
||||
|
||||
export interface NormalizeInventoryOptions {
|
||||
/** Saved character/party owner; dungeon and loot-source identities are not owner categories. */
|
||||
readonly ownerCategory?: ContentCategoryId;
|
||||
}
|
||||
|
||||
const CONTENT_CATEGORIES = new Set<ContentCategoryId>(["wow", "rom", "coa"]);
|
||||
|
||||
/** Returns true only for an explicit item/owner namespace match. */
|
||||
export function inventoryItemMatchesCategory(
|
||||
item: Pick<InventoryItem, "contentCategory">,
|
||||
expectedCategory: ContentCategoryId | undefined,
|
||||
): boolean {
|
||||
return expectedCategory !== undefined && item.contentCategory === expectedCategory;
|
||||
}
|
||||
|
||||
/** A RoM item is usable only when it carries nonempty validated native values. */
|
||||
export function inventoryItemUsableForCategory(
|
||||
item: Pick<InventoryItem, "contentCategory" | "stats">,
|
||||
expectedCategory: ContentCategoryId | undefined,
|
||||
): boolean {
|
||||
if (!inventoryItemMatchesCategory(item, expectedCategory)) return false;
|
||||
if (expectedCategory !== "rom") return true;
|
||||
const native = normalizeRomNativeGearStats(item.stats?.romNative);
|
||||
return Boolean(native && Object.keys(native).length > 0);
|
||||
}
|
||||
|
||||
/** Safely migrates persisted profiles created before inventory existed. */
|
||||
export function normalizeInventory(value: unknown): InventoryItem[] {
|
||||
export function normalizeInventory(
|
||||
value: unknown,
|
||||
options: NormalizeInventoryOptions = {},
|
||||
): InventoryItem[] {
|
||||
if (!Array.isArray(value)) return [];
|
||||
const normalized: InventoryItem[] = [];
|
||||
for (const candidate of value) {
|
||||
@@ -53,7 +86,13 @@ export function normalizeInventory(value: unknown): InventoryItem[] {
|
||||
|| !Number.isFinite(item.acquiredAt)
|
||||
) continue;
|
||||
const itemLevel = Math.max(1, Math.min(MAX_PERSISTED_ITEM_LEVEL, Math.trunc(item.itemLevel!)));
|
||||
const contentCategory = CONTENT_CATEGORIES.has(item.contentCategory as ContentCategoryId)
|
||||
? item.contentCategory as ContentCategoryId
|
||||
: item.contentCategory === undefined && options.ownerCategory === "wow"
|
||||
? "wow"
|
||||
: null;
|
||||
normalized.push({
|
||||
contentCategory,
|
||||
instanceId: item.instanceId,
|
||||
itemId: Math.max(1, Math.trunc(item.itemId!)),
|
||||
itemKey: item.itemKey,
|
||||
|
||||
Reference in new issue
Block a user