115 lines
3.8 KiB
TypeScript
115 lines
3.8 KiB
TypeScript
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.`;
|
|
}
|
|
}
|