diff --git a/src/collections/game/GameStructures.ts b/src/collections/game/GameStructures.ts index 43c87a8..afacc02 100644 --- a/src/collections/game/GameStructures.ts +++ b/src/collections/game/GameStructures.ts @@ -1,6 +1,5 @@ import { CollectionConfig } from "payload"; import { isDeveloper } from "@/utils/access-control/isRole"; -import { GameStructure } from "@/payload-types"; export const GameStructures: CollectionConfig = { slug: "game-structures", @@ -9,59 +8,11 @@ export const GameStructures: CollectionConfig = { useAsTitle: "name", }, access: { + read: ({ req }) => !!req.user, create: isDeveloper, update: isDeveloper, delete: isDeveloper, }, - hooks: { - beforeValidate: [ - ({ data }) => { - const rows = data?.storedResources; - - if (!Array.isArray(rows) || rows.length === 0) return data; - - type NormalizedRow = { id?: string | null; resource: unknown; amount: number }; - - const byResource = new Map(); - - for (const row of rows) { - if (!row) continue; - - // Normalize relationship value to an ID-like scalar when possible - const rawResource = (row as any).resource; - const resourceId = - rawResource && typeof rawResource === "object" && "id" in rawResource - ? (rawResource as any).id - : rawResource; - - if (resourceId == null) continue; - - const resourceKey = String(resourceId); - const amount = - typeof (row as any).amount === "number" && Number.isFinite((row as any).amount) - ? (row as any).amount - : 0; - - const existing = byResource.get(resourceKey); - - if (existing) { - existing.amount += amount; - // Keep the first row's id to preserve admin UI row identity - } else { - byResource.set(resourceKey, { - id: (row as any).id ?? undefined, - resource: resourceId, - amount, - }); - } - } - - data!.storedResources = Array.from(byResource.values()); - - return data; - }, - ], - }, fields: [ { name: "name", @@ -113,6 +64,33 @@ export const GameStructures: CollectionConfig = { name: "amount", type: "number", required: true, + min: 1, + }, + { + name: "gridX", + type: "number", + required: true, + min: 0, + admin: { + description: "Column position in the inventory grid (0-indexed).", + }, + }, + { + name: "gridY", + type: "number", + required: true, + min: 0, + admin: { + description: "Row position in the inventory grid (0-indexed).", + }, + }, + { + name: "rotated", + type: "checkbox", + defaultValue: false, + admin: { + description: "Whether this item is rotated 90 degrees (swaps width and height).", + }, }, ], }, diff --git a/src/collections/logistics/Resources.ts b/src/collections/logistics/Resources.ts index 8fc1b27..3d3d1f0 100644 --- a/src/collections/logistics/Resources.ts +++ b/src/collections/logistics/Resources.ts @@ -38,6 +38,7 @@ export const Resources: CollectionConfig = { group: "Logistics", }, access: { + read: ({ req }) => !!req.user, update: onlyIfNotPrimaryCurrency, delete: onlyIfNotPrimaryCurrency, }, @@ -76,18 +77,22 @@ export const Resources: CollectionConfig = { hooks: { afterRead: [ async ({ data, req }) => { - const gameRules = await req.payload.findGlobal({ - slug: "game-rules", - depth: 0, - }); + try { + const gameRules = await req.payload.findGlobal({ + slug: "game-rules", + depth: 0, + }); - const primaryCurrencyId = - typeof gameRules.mainCurrency === "object" - ? gameRules.mainCurrency?.id - : gameRules.mainCurrency; + const primaryCurrencyId = + typeof gameRules.mainCurrency === "object" + ? gameRules.mainCurrency?.id + : gameRules.mainCurrency; - if (primaryCurrencyId) { - return data?.id === primaryCurrencyId; + if (primaryCurrencyId) { + return data?.id === primaryCurrencyId; + } + } catch (_) { + // Silently fail — game-rules global may not exist yet } }, ], @@ -281,6 +286,59 @@ export const Resources: CollectionConfig = { "Maximum amount that fits in a single standard cargo slot (if applicable.)", }, }, + { + label: "Grid Dimensions", + type: "group", + fields: [ + { + name: "gridWidth", + type: "number", + required: true, + defaultValue: 1, + min: 1, + admin: { + description: "Width of this resource in grid cells.", + }, + }, + { + name: "gridHeight", + type: "number", + required: true, + defaultValue: 1, + min: 1, + admin: { + description: "Height of this resource in grid cells.", + }, + }, + { + name: "stackable", + type: "checkbox", + defaultValue: true, + admin: { + description: + "Can multiple units of this resource stack in a single grid cell? If checked, the amount displayed on the grid reflects the stack count.", + }, + }, + { + name: "maxStackSize", + type: "number", + admin: { + description: + "Maximum number of units that can exist in a single grid stack. Leave empty for unlimited. Only applies if stackable is checked.", + condition: (_, siblingData) => siblingData?.stackable === true, + }, + }, + { + name: "rotatable", + type: "checkbox", + defaultValue: true, + admin: { + description: + "Can this resource be rotated 90 degrees in the grid? When rotated, width and height are swapped.", + }, + }, + ], + }, ], }, ], diff --git a/src/payload-types.ts b/src/payload-types.ts index 7f73b6d..330afda 100644 --- a/src/payload-types.ts +++ b/src/payload-types.ts @@ -239,6 +239,18 @@ export interface GameStructure { | { resource: number | Resource; amount: number; + /** + * Column position in the inventory grid (0-indexed). + */ + gridX: number; + /** + * Row position in the inventory grid (0-indexed). + */ + gridY: number; + /** + * Whether this item is rotated 90 degrees (swaps width and height). + */ + rotated?: boolean | null; id?: string | null; }[] | null; @@ -516,6 +528,26 @@ export interface Resource { * Maximum amount that fits in a single standard cargo slot (if applicable.) */ stackSize?: number | null; + /** + * Width of this resource in grid cells. + */ + gridWidth: number; + /** + * Height of this resource in grid cells. + */ + gridHeight: number; + /** + * Can multiple units of this resource stack in a single grid cell? If checked, the amount displayed on the grid reflects the stack count. + */ + stackable?: boolean | null; + /** + * Maximum number of units that can exist in a single grid stack. Leave empty for unlimited. Only applies if stackable is checked. + */ + maxStackSize?: number | null; + /** + * Can this resource be rotated 90 degrees in the grid? When rotated, width and height are swapped. + */ + rotatable?: boolean | null; updatedAt: string; createdAt: string; } @@ -1558,6 +1590,9 @@ export interface GameStructuresSelect { | { resource?: T; amount?: T; + gridX?: T; + gridY?: T; + rotated?: T; id?: T; }; updatedAt?: T; @@ -1982,6 +2017,11 @@ export interface ResourcesSelect { unitOfMeasure?: T; massPerUnit?: T; stackSize?: T; + gridWidth?: T; + gridHeight?: T; + stackable?: T; + maxStackSize?: T; + rotatable?: T; updatedAt?: T; createdAt?: T; }