import type { CollectionConfig } from "payload"; import { requirePermission } from "@/utils/access-control/hasPermission"; import { emitGameEvent } from "@/utils/event-log/emit"; import { staffingDefaultsFromBlueprint } from "@/lib/base/staffingDefaults"; import { createStaffingFields } from "./staffingFields"; export const GameStructures: CollectionConfig = { slug: "game-structures", admin: { group: "Game", useAsTitle: "name", }, access: { read: ({ req }) => !!req.user, create: requirePermission("game-structures:create"), update: requirePermission("game-structures:update"), delete: requirePermission("game-structures:delete"), }, hooks: { beforeChange: [ async ({ data, operation, req }) => { if (operation !== "create" || !data?.type) return data; const blueprintId = typeof data.type === "object" ? data.type.id : data.type; const blueprint = await req.payload.findByID({ collection: "structures", id: blueprintId, depth: 1, overrideAccess: true, }); const defaults = staffingDefaultsFromBlueprint(blueprint); return { ...data, ...(data.laborRequirements === undefined ? { laborRequirements: defaults.laborRequirements } : {}), ...(data.staffPositions === undefined ? { staffPositions: defaults.staffPositions } : {}), ...(data.maintenanceCost === undefined ? { maintenanceCost: defaults.maintenanceCost } : {}), }; }, ], afterChange: [ async ({ doc, previousDoc, req }) => { const d = doc as any; const prev = previousDoc as any; if (!prev) return; if (!req.user) return; const oldStored = JSON.stringify(prev.storedResources ?? []); const newStored = JSON.stringify(d.storedResources ?? []); const oldVoid = JSON.stringify(prev.voidStorage ?? []); const newVoid = JSON.stringify(d.voidStorage ?? []); if (oldStored === newStored && oldVoid === newVoid) return; await emitGameEvent(req.payload, { type: "storage:withdraw", message: `Admin updated storage in "${d.name}"`, actor: req.user.id, structure: d.id, targetCollection: "game-structures", targetId: d.id, data: { previousStored: prev.storedResources, newStored: d.storedResources, previousVoid: prev.voidStorage, newVoid: d.voidStorage, }, }); }, ], }, fields: [ { name: "name", type: "text", label: "Display Name", admin: { description: "The name of the structure as displayed in-game and on the frontend.", }, required: true, }, { name: "type", type: "relationship", relationTo: "structures", required: true, admin: { description: "The type of structure this is.", }, }, { name: "faction", type: "relationship", relationTo: "factions", admin: { description: "The faction that owns this structure.", }, }, ...createStaffingFields("instance"), { label: "Geolocation", type: "group", fields: [ { name: "coordinates", type: "point", }, ], }, { name: "storedResources", type: "array", fields: [ { name: "resource", type: "relationship", relationTo: "resources", required: true, }, { 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).", }, }, ], }, { name: "voidStorage", type: "array", admin: { description: "Items that no longer fit in the grid after a resize, stored here until retrieved.", }, fields: [ { name: "resource", type: "relationship", relationTo: "resources", required: true, }, { name: "amount", type: "number", required: true, min: 1, }, { name: "gridX", type: "number", required: true, min: 0, }, { name: "gridY", type: "number", required: true, min: 0, }, { name: "rotated", type: "checkbox", defaultValue: false, }, ], }, ], };