feat(collections): add grid fields to game-structures and resources schemas
GameStructures: - Add gridX, gridY, rotated fields to storedResources rows - Remove beforeValidate hook that merged duplicate resources - Add read access (was missing) - Add min: 1 constraint on amount Resources: - Add gridWidth, gridHeight, stackable, maxStackSize, rotatable fields - Add read access (was missing) - Wrap game-rules afterRead hook in try/catch to handle missing global
This commit is contained in:
parent
275cb49a92
commit
159755af8d
3 changed files with 136 additions and 60 deletions
|
|
@ -1,6 +1,5 @@
|
||||||
import { CollectionConfig } from "payload";
|
import { CollectionConfig } from "payload";
|
||||||
import { isDeveloper } from "@/utils/access-control/isRole";
|
import { isDeveloper } from "@/utils/access-control/isRole";
|
||||||
import { GameStructure } from "@/payload-types";
|
|
||||||
|
|
||||||
export const GameStructures: CollectionConfig = {
|
export const GameStructures: CollectionConfig = {
|
||||||
slug: "game-structures",
|
slug: "game-structures",
|
||||||
|
|
@ -9,59 +8,11 @@ export const GameStructures: CollectionConfig = {
|
||||||
useAsTitle: "name",
|
useAsTitle: "name",
|
||||||
},
|
},
|
||||||
access: {
|
access: {
|
||||||
|
read: ({ req }) => !!req.user,
|
||||||
create: isDeveloper,
|
create: isDeveloper,
|
||||||
update: isDeveloper,
|
update: isDeveloper,
|
||||||
delete: 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<string, NormalizedRow>();
|
|
||||||
|
|
||||||
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: [
|
fields: [
|
||||||
{
|
{
|
||||||
name: "name",
|
name: "name",
|
||||||
|
|
@ -113,6 +64,33 @@ export const GameStructures: CollectionConfig = {
|
||||||
name: "amount",
|
name: "amount",
|
||||||
type: "number",
|
type: "number",
|
||||||
required: true,
|
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).",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@ export const Resources: CollectionConfig = {
|
||||||
group: "Logistics",
|
group: "Logistics",
|
||||||
},
|
},
|
||||||
access: {
|
access: {
|
||||||
|
read: ({ req }) => !!req.user,
|
||||||
update: onlyIfNotPrimaryCurrency,
|
update: onlyIfNotPrimaryCurrency,
|
||||||
delete: onlyIfNotPrimaryCurrency,
|
delete: onlyIfNotPrimaryCurrency,
|
||||||
},
|
},
|
||||||
|
|
@ -76,6 +77,7 @@ export const Resources: CollectionConfig = {
|
||||||
hooks: {
|
hooks: {
|
||||||
afterRead: [
|
afterRead: [
|
||||||
async ({ data, req }) => {
|
async ({ data, req }) => {
|
||||||
|
try {
|
||||||
const gameRules = await req.payload.findGlobal({
|
const gameRules = await req.payload.findGlobal({
|
||||||
slug: "game-rules",
|
slug: "game-rules",
|
||||||
depth: 0,
|
depth: 0,
|
||||||
|
|
@ -89,6 +91,9 @@ export const Resources: CollectionConfig = {
|
||||||
if (primaryCurrencyId) {
|
if (primaryCurrencyId) {
|
||||||
return data?.id === 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.)",
|
"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.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -239,6 +239,18 @@ export interface GameStructure {
|
||||||
| {
|
| {
|
||||||
resource: number | Resource;
|
resource: number | Resource;
|
||||||
amount: number;
|
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;
|
id?: string | null;
|
||||||
}[]
|
}[]
|
||||||
| null;
|
| null;
|
||||||
|
|
@ -516,6 +528,26 @@ export interface Resource {
|
||||||
* Maximum amount that fits in a single standard cargo slot (if applicable.)
|
* Maximum amount that fits in a single standard cargo slot (if applicable.)
|
||||||
*/
|
*/
|
||||||
stackSize?: number | null;
|
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;
|
updatedAt: string;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
}
|
}
|
||||||
|
|
@ -1558,6 +1590,9 @@ export interface GameStructuresSelect<T extends boolean = true> {
|
||||||
| {
|
| {
|
||||||
resource?: T;
|
resource?: T;
|
||||||
amount?: T;
|
amount?: T;
|
||||||
|
gridX?: T;
|
||||||
|
gridY?: T;
|
||||||
|
rotated?: T;
|
||||||
id?: T;
|
id?: T;
|
||||||
};
|
};
|
||||||
updatedAt?: T;
|
updatedAt?: T;
|
||||||
|
|
@ -1982,6 +2017,11 @@ export interface ResourcesSelect<T extends boolean = true> {
|
||||||
unitOfMeasure?: T;
|
unitOfMeasure?: T;
|
||||||
massPerUnit?: T;
|
massPerUnit?: T;
|
||||||
stackSize?: T;
|
stackSize?: T;
|
||||||
|
gridWidth?: T;
|
||||||
|
gridHeight?: T;
|
||||||
|
stackable?: T;
|
||||||
|
maxStackSize?: T;
|
||||||
|
rotatable?: T;
|
||||||
updatedAt?: T;
|
updatedAt?: T;
|
||||||
createdAt?: T;
|
createdAt?: T;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue