Adds the npc-staffing and labor-classifications collections, a shared staffing field factory applied to both structure blueprints (Staffing Defaults group) and game structure instances, blueprint-derived staffing defaults on game structure create, staff-hiring toggles on Game Rules, and their database migrations. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
197 lines
5.2 KiB
TypeScript
197 lines
5.2 KiB
TypeScript
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,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
};
|