import type { Structure } from "@/payload-types"; type ResourceRef = { relationTo: "resources"; value: number | { id: number }; }; export type StorageRulesSource = Pick< Structure, "allowedStorage" | "prohibitedStorage" | "restrictToAllowed" >; export type StorageRuleViolation = "prohibited" | "not_whitelisted" | "over_cap"; export interface StorageRuleCheck { allowed: boolean; violation?: StorageRuleViolation; cap?: number; currentStored?: number; } function isResourceRef(item: unknown): item is ResourceRef { if (!item || typeof item !== "object") return false; const ref = item as { relationTo?: string }; return ref.relationTo === "resources"; } function resolveResourceId(value: number | { id: number }): number { return typeof value === "object" && value !== null ? value.id : value; } /** * Whether this resource is on the structure's prohibited list. */ export function isResourceProhibited(rules: StorageRulesSource, resourceId: number): boolean { return (rules.prohibitedStorage ?? []).some((rule) => { if (!isResourceRef(rule.item)) return false; return resolveResourceId(rule.item.value) === resourceId; }); } /** * Whether this resource passes the whitelist gate. When `restrictToAllowed` is * enabled, only resources listed in `allowedStorage` pass. Otherwise everything * passes (prohibited list still applies separately). */ export function isResourceWhitelisted(rules: StorageRulesSource, resourceId: number): boolean { if (!rules.restrictToAllowed) return true; return (rules.allowedStorage ?? []).some((rule) => { if (!isResourceRef(rule.item)) return false; return resolveResourceId(rule.item.value) === resourceId; }); } /** * The per-item quantity cap defined in `allowedStorage`, or null if none set. */ export function getResourceStorageCap( rules: StorageRulesSource, resourceId: number, ): number | null { const entry = (rules.allowedStorage ?? []).find((rule) => { if (!isResourceRef(rule.item)) return false; return resolveResourceId(rule.item.value) === resourceId; }); return entry?.amount ?? null; } /** * Validates a deposit/transfer of `amount` of `resourceId` into a structure, * given how much of that resource is currently stored (grid + void). * * Checks, in order: prohibited list, whitelist gate, per-item quantity cap. */ export function checkStorageDeposit( rules: StorageRulesSource, resourceId: number, amount: number, currentStored: number, ): StorageRuleCheck { if (isResourceProhibited(rules, resourceId)) { return { allowed: false, violation: "prohibited" }; } if (!isResourceWhitelisted(rules, resourceId)) { return { allowed: false, violation: "not_whitelisted" }; } const cap = getResourceStorageCap(rules, resourceId); if (cap != null && currentStored + amount > cap) { return { allowed: false, violation: "over_cap", cap, currentStored }; } return { allowed: true, cap: cap ?? undefined }; } /** * Human-readable message describing why a deposit/transfer was rejected. */ export function storageViolationMessage(resourceName: string, check: StorageRuleCheck): string { switch (check.violation) { case "prohibited": return `This resource is prohibited from storage at this structure.`; case "not_whitelisted": return `This structure only accepts items on its allowed storage list: "${resourceName}" is not permitted.`; case "over_cap": { const available = Math.max(0, (check.cap ?? 0) - (check.currentStored ?? 0)); return available <= 0 ? `${resourceName} is at its storage allowance here (${check.cap?.toLocaleString()}). No more can be stored.` : `${resourceName} is limited to ${check.cap?.toLocaleString()} units here. You can add at most ${available.toLocaleString()} more.`; } default: return `This resource cannot be stored here.`; } }