feat(locker): add locker-storages and loadouts collections
This commit is contained in:
parent
9d990d2965
commit
7f4df6597a
6 changed files with 500 additions and 0 deletions
|
|
@ -56,5 +56,25 @@ export const GameRules: GlobalConfig = {
|
|||
"How often the game tick runs, in minutes. This processes shipment movement, fuel consumption, and arrivals.",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "lockerGridWidth",
|
||||
type: "number",
|
||||
defaultValue: 10,
|
||||
min: 4,
|
||||
admin: {
|
||||
description:
|
||||
"Width of each member's personal locker grid, in cells. Applies to newly provisioned lockers.",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "lockerGridHeight",
|
||||
type: "number",
|
||||
defaultValue: 6,
|
||||
min: 4,
|
||||
admin: {
|
||||
description:
|
||||
"Height of each member's personal locker grid, in cells. Applies to newly provisioned lockers.",
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
|
|||
77
src/collections/locker/Loadouts.ts
Normal file
77
src/collections/locker/Loadouts.ts
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
import { CollectionConfig } from "payload";
|
||||
import { isDeveloper } from "@/utils/access-control/isRole";
|
||||
import { LOADOUT_SLOTS } from "@/lib/locker";
|
||||
|
||||
export const Loadouts: CollectionConfig = {
|
||||
slug: "loadouts",
|
||||
admin: {
|
||||
group: "Locker",
|
||||
useAsTitle: "name",
|
||||
defaultColumns: ["name", "ownerUser", "updatedAt"],
|
||||
},
|
||||
access: {
|
||||
read: ({ req }) => {
|
||||
if (!req.user) return false;
|
||||
if (isDeveloper({ req })) return true;
|
||||
return { ownerUser: { equals: req.user.id } };
|
||||
},
|
||||
create: ({ req }) => !!req.user,
|
||||
update: ({ req }) => {
|
||||
if (!req.user) return false;
|
||||
if (isDeveloper({ req })) return true;
|
||||
return { ownerUser: { equals: req.user.id } };
|
||||
},
|
||||
delete: ({ req }) => {
|
||||
if (!req.user) return false;
|
||||
if (isDeveloper({ req })) return true;
|
||||
return { ownerUser: { equals: req.user.id } };
|
||||
},
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: "name",
|
||||
type: "text",
|
||||
required: true,
|
||||
admin: {
|
||||
description: 'Loadout name (e.g., "CQB", "Overwatch", "Resupply").',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "description",
|
||||
type: "textarea",
|
||||
admin: {
|
||||
description: "Optional notes about this loadout.",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ownerUser",
|
||||
type: "relationship",
|
||||
relationTo: "users",
|
||||
required: true,
|
||||
admin: {
|
||||
description: "The operator this loadout belongs to.",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "slots",
|
||||
type: "group",
|
||||
label: "Equipment Slots",
|
||||
admin: {
|
||||
description: "Assign assets from your locker to each equipment slot.",
|
||||
},
|
||||
fields: LOADOUT_SLOTS.map((slot) => ({
|
||||
name: slot.name,
|
||||
type: "relationship",
|
||||
relationTo: "assets",
|
||||
admin: {
|
||||
description: slot.description,
|
||||
},
|
||||
filterOptions: {
|
||||
assetType: {
|
||||
in: slot.assetTypes.join(","),
|
||||
},
|
||||
},
|
||||
})),
|
||||
},
|
||||
],
|
||||
};
|
||||
111
src/collections/locker/LockerStorages.ts
Normal file
111
src/collections/locker/LockerStorages.ts
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
import { CollectionConfig } from "payload";
|
||||
import { isDeveloper } from "@/utils/access-control/isRole";
|
||||
|
||||
export const LockerStorages: CollectionConfig = {
|
||||
slug: "locker-storages",
|
||||
admin: {
|
||||
group: "Locker",
|
||||
useAsTitle: "name",
|
||||
defaultColumns: ["name", "ownerUser", "itemCount"],
|
||||
},
|
||||
access: {
|
||||
read: ({ req }) => {
|
||||
if (!req.user) return false;
|
||||
if (isDeveloper({ req })) return true;
|
||||
return { ownerUser: { equals: req.user.id } };
|
||||
},
|
||||
create: ({ req }) => !!req.user && isDeveloper({ req }),
|
||||
update: ({ req }) => {
|
||||
if (!req.user) return false;
|
||||
if (isDeveloper({ req })) return true;
|
||||
return { ownerUser: { equals: req.user.id } };
|
||||
},
|
||||
delete: isDeveloper,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: "name",
|
||||
type: "text",
|
||||
required: true,
|
||||
defaultValue: "Personal Locker",
|
||||
admin: {
|
||||
description: "Display name for this locker.",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ownerUser",
|
||||
type: "relationship",
|
||||
relationTo: "users",
|
||||
required: true,
|
||||
admin: {
|
||||
description: "The operator who owns this locker.",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "items",
|
||||
type: "array",
|
||||
admin: {
|
||||
description: "Items stored in the locker grid. Position and rotation are managed from the member locker UI.",
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: "asset",
|
||||
type: "relationship",
|
||||
relationTo: "assets",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: "quantity",
|
||||
type: "number",
|
||||
required: true,
|
||||
min: 1,
|
||||
defaultValue: 1,
|
||||
},
|
||||
{
|
||||
name: "gridX",
|
||||
type: "number",
|
||||
required: true,
|
||||
min: 0,
|
||||
defaultValue: 0,
|
||||
admin: {
|
||||
description: "Column position in the locker grid (0-indexed).",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "gridY",
|
||||
type: "number",
|
||||
required: true,
|
||||
min: 0,
|
||||
defaultValue: 0,
|
||||
admin: {
|
||||
description: "Row position in the locker grid (0-indexed).",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "rotated",
|
||||
type: "checkbox",
|
||||
defaultValue: false,
|
||||
admin: {
|
||||
description: "Whether this item is rotated 90 degrees (swaps width and height).",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "itemCount",
|
||||
type: "number",
|
||||
admin: {
|
||||
readOnly: true,
|
||||
description: "Total number of distinct entries currently in this locker.",
|
||||
},
|
||||
hooks: {
|
||||
beforeChange: [
|
||||
({ data }) => {
|
||||
const items = (data as { items?: unknown[] } | undefined)?.items;
|
||||
return Array.isArray(items) ? items.length : 0;
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
@ -489,6 +489,64 @@ export const Assets: CollectionConfig = {
|
|||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "storageDimensions",
|
||||
type: "group",
|
||||
label: "Storage Dimensions",
|
||||
admin: {
|
||||
description:
|
||||
"How this asset occupies space in member lockers and container grids.",
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: "gridWidth",
|
||||
type: "number",
|
||||
required: true,
|
||||
defaultValue: 1,
|
||||
min: 1,
|
||||
admin: {
|
||||
description: "Width of this asset in grid cells.",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "gridHeight",
|
||||
type: "number",
|
||||
required: true,
|
||||
defaultValue: 1,
|
||||
min: 1,
|
||||
admin: {
|
||||
description: "Height of this asset in grid cells.",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "stackable",
|
||||
type: "checkbox",
|
||||
defaultValue: true,
|
||||
admin: {
|
||||
description:
|
||||
"Can multiple units of this asset stack in a single grid cell? If checked, the amount displayed 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 asset be rotated 90 degrees in the grid? When rotated, width and height are swapped.",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "relatedAssets",
|
||||
type: "relationship",
|
||||
|
|
|
|||
|
|
@ -91,6 +91,8 @@ export interface Config {
|
|||
'bank-accounts': BankAccount;
|
||||
'bank-transactions': BankTransaction;
|
||||
'ledger-entries': LedgerEntry;
|
||||
'locker-storages': LockerStorage;
|
||||
loadouts: Loadout;
|
||||
maps: Map;
|
||||
'narrative-events': NarrativeEvent;
|
||||
'mission-files': MissionFile;
|
||||
|
|
@ -133,6 +135,8 @@ export interface Config {
|
|||
'bank-accounts': BankAccountsSelect<false> | BankAccountsSelect<true>;
|
||||
'bank-transactions': BankTransactionsSelect<false> | BankTransactionsSelect<true>;
|
||||
'ledger-entries': LedgerEntriesSelect<false> | LedgerEntriesSelect<true>;
|
||||
'locker-storages': LockerStoragesSelect<false> | LockerStoragesSelect<true>;
|
||||
loadouts: LoadoutsSelect<false> | LoadoutsSelect<true>;
|
||||
maps: MapsSelect<false> | MapsSelect<true>;
|
||||
'narrative-events': NarrativeEventsSelect<false> | NarrativeEventsSelect<true>;
|
||||
'mission-files': MissionFilesSelect<false> | MissionFilesSelect<true>;
|
||||
|
|
@ -764,6 +768,31 @@ export interface Asset {
|
|||
batchSize: number;
|
||||
};
|
||||
};
|
||||
/**
|
||||
* How this asset occupies space in member lockers and container grids.
|
||||
*/
|
||||
storageDimensions: {
|
||||
/**
|
||||
* Width of this asset in grid cells.
|
||||
*/
|
||||
gridWidth: number;
|
||||
/**
|
||||
* Height of this asset in grid cells.
|
||||
*/
|
||||
gridHeight: number;
|
||||
/**
|
||||
* Can multiple units of this asset stack in a single grid cell? If checked, the amount displayed 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 asset be rotated 90 degrees in the grid? When rotated, width and height are swapped.
|
||||
*/
|
||||
rotatable?: boolean | null;
|
||||
};
|
||||
/**
|
||||
* Suggested complements to this asset (for example, if the asset was a Bandage, some related assets could include an IV, blood bag, epinephrine, etc.)
|
||||
*/
|
||||
|
|
@ -1090,6 +1119,8 @@ export interface GameEventLog {
|
|||
| 'bank-accounts'
|
||||
| 'bank-transactions'
|
||||
| 'ledger-entries'
|
||||
| 'locker-storages'
|
||||
| 'loadouts'
|
||||
)
|
||||
| null;
|
||||
targetId?: number | null;
|
||||
|
|
@ -1732,6 +1763,127 @@ export interface LedgerEntry {
|
|||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "locker-storages".
|
||||
*/
|
||||
export interface LockerStorage {
|
||||
id: number;
|
||||
/**
|
||||
* Display name for this locker.
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* The operator who owns this locker.
|
||||
*/
|
||||
ownerUser: number | User;
|
||||
/**
|
||||
* Items stored in the locker grid. Position and rotation are managed from the member locker UI.
|
||||
*/
|
||||
items?:
|
||||
| {
|
||||
asset: number | Asset;
|
||||
quantity: number;
|
||||
/**
|
||||
* Column position in the locker grid (0-indexed).
|
||||
*/
|
||||
gridX: number;
|
||||
/**
|
||||
* Row position in the locker grid (0-indexed).
|
||||
*/
|
||||
gridY: number;
|
||||
/**
|
||||
* Whether this item is rotated 90 degrees (swaps width and height).
|
||||
*/
|
||||
rotated?: boolean | null;
|
||||
id?: string | null;
|
||||
}[]
|
||||
| null;
|
||||
/**
|
||||
* Total number of distinct entries currently in this locker.
|
||||
*/
|
||||
itemCount?: number | null;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "loadouts".
|
||||
*/
|
||||
export interface Loadout {
|
||||
id: number;
|
||||
/**
|
||||
* Loadout name (e.g., "CQB", "Overwatch", "Resupply").
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* Optional notes about this loadout.
|
||||
*/
|
||||
description?: string | null;
|
||||
/**
|
||||
* The operator this loadout belongs to.
|
||||
*/
|
||||
ownerUser: number | User;
|
||||
/**
|
||||
* Assign assets from your locker to each equipment slot.
|
||||
*/
|
||||
slots?: {
|
||||
/**
|
||||
* Primary weapon
|
||||
*/
|
||||
primary?: (number | null) | Asset;
|
||||
/**
|
||||
* Secondary weapon
|
||||
*/
|
||||
secondary?: (number | null) | Asset;
|
||||
/**
|
||||
* Launcher / heavy weapon
|
||||
*/
|
||||
launcher?: (number | null) | Asset;
|
||||
/**
|
||||
* Uniform
|
||||
*/
|
||||
uniform?: (number | null) | Asset;
|
||||
/**
|
||||
* Carrier / plate carrier
|
||||
*/
|
||||
vest?: (number | null) | Asset;
|
||||
/**
|
||||
* Backpack / rucksack
|
||||
*/
|
||||
backpack?: (number | null) | Asset;
|
||||
/**
|
||||
* Helmet
|
||||
*/
|
||||
helmet?: (number | null) | Asset;
|
||||
/**
|
||||
* Facewear / eyewear
|
||||
*/
|
||||
facewear?: (number | null) | Asset;
|
||||
/**
|
||||
* Night vision device
|
||||
*/
|
||||
nvg?: (number | null) | Asset;
|
||||
/**
|
||||
* Radio / comms
|
||||
*/
|
||||
radio?: (number | null) | Asset;
|
||||
/**
|
||||
* Optic attachment
|
||||
*/
|
||||
optic?: (number | null) | Asset;
|
||||
/**
|
||||
* Muzzle attachment
|
||||
*/
|
||||
muzzle?: (number | null) | Asset;
|
||||
/**
|
||||
* Bipod attachment
|
||||
*/
|
||||
bipod?: (number | null) | Asset;
|
||||
};
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "narrative-events".
|
||||
|
|
@ -1893,6 +2045,14 @@ export interface PayloadLockedDocument {
|
|||
relationTo: 'ledger-entries';
|
||||
value: number | LedgerEntry;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'locker-storages';
|
||||
value: number | LockerStorage;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'loadouts';
|
||||
value: number | Loadout;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'maps';
|
||||
value: number | Map;
|
||||
|
|
@ -2473,6 +2633,15 @@ export interface AssetsSelect<T extends boolean = true> {
|
|||
batchSize?: T;
|
||||
};
|
||||
};
|
||||
storageDimensions?:
|
||||
| T
|
||||
| {
|
||||
gridWidth?: T;
|
||||
gridHeight?: T;
|
||||
stackable?: T;
|
||||
maxStackSize?: T;
|
||||
rotatable?: T;
|
||||
};
|
||||
relatedAssets?: T;
|
||||
isLive?: T;
|
||||
notes?: T;
|
||||
|
|
@ -2737,6 +2906,55 @@ export interface LedgerEntriesSelect<T extends boolean = true> {
|
|||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "locker-storages_select".
|
||||
*/
|
||||
export interface LockerStoragesSelect<T extends boolean = true> {
|
||||
name?: T;
|
||||
ownerUser?: T;
|
||||
items?:
|
||||
| T
|
||||
| {
|
||||
asset?: T;
|
||||
quantity?: T;
|
||||
gridX?: T;
|
||||
gridY?: T;
|
||||
rotated?: T;
|
||||
id?: T;
|
||||
};
|
||||
itemCount?: T;
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "loadouts_select".
|
||||
*/
|
||||
export interface LoadoutsSelect<T extends boolean = true> {
|
||||
name?: T;
|
||||
description?: T;
|
||||
ownerUser?: T;
|
||||
slots?:
|
||||
| T
|
||||
| {
|
||||
primary?: T;
|
||||
secondary?: T;
|
||||
launcher?: T;
|
||||
uniform?: T;
|
||||
vest?: T;
|
||||
backpack?: T;
|
||||
helmet?: T;
|
||||
facewear?: T;
|
||||
nvg?: T;
|
||||
radio?: T;
|
||||
optic?: T;
|
||||
muzzle?: T;
|
||||
bipod?: T;
|
||||
};
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "maps_select".
|
||||
|
|
@ -2868,6 +3086,14 @@ export interface GameRule {
|
|||
* How often the game tick runs, in minutes. This processes shipment movement, fuel consumption, and arrivals.
|
||||
*/
|
||||
gameTickIntervalMinutes?: number | null;
|
||||
/**
|
||||
* Width of each member's personal locker grid, in cells. Applies to newly provisioned lockers.
|
||||
*/
|
||||
lockerGridWidth?: number | null;
|
||||
/**
|
||||
* Height of each member's personal locker grid, in cells. Applies to newly provisioned lockers.
|
||||
*/
|
||||
lockerGridHeight?: number | null;
|
||||
updatedAt?: string | null;
|
||||
createdAt?: string | null;
|
||||
}
|
||||
|
|
@ -2881,6 +3107,8 @@ export interface GameRulesSelect<T extends boolean = true> {
|
|||
intelAssignments?: T;
|
||||
proximityThreshold?: T;
|
||||
gameTickIntervalMinutes?: T;
|
||||
lockerGridWidth?: T;
|
||||
lockerGridHeight?: T;
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
globalType?: T;
|
||||
|
|
|
|||
|
|
@ -35,6 +35,8 @@ import { Experience } from "@/collections/users/Experience";
|
|||
import { BankAccounts } from "@/collections/banking/BankAccounts";
|
||||
import { BankTransactions } from "@/collections/banking/BankTransactions";
|
||||
import { LedgerEntries } from "@/collections/banking/LedgerEntries";
|
||||
import { LockerStorages } from "@/collections/locker/LockerStorages";
|
||||
import { Loadouts } from "@/collections/locker/Loadouts";
|
||||
import { nodemailerAdapter } from "@payloadcms/email-nodemailer";
|
||||
import nodemailer from "nodemailer";
|
||||
|
||||
|
|
@ -104,6 +106,10 @@ export default buildConfig({
|
|||
BankTransactions,
|
||||
LedgerEntries,
|
||||
|
||||
// Locker
|
||||
LockerStorages,
|
||||
Loadouts,
|
||||
|
||||
// World
|
||||
Maps,
|
||||
NarrativeEvents,
|
||||
|
|
|
|||
Loading…
Reference in a new issue