1
0
Fork 0

feat(factions): add player-faction type, reputation, relationships, and cover image

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
Jason Fraley 2026-08-12 16:21:03 -04:00
parent 00919ceeb3
commit b14e04c487
2 changed files with 146 additions and 1 deletions

View file

@ -13,6 +13,30 @@ export const Factions: CollectionConfig = {
delete: isDeveloper, delete: isDeveloper,
read: ({ req }) => !!req.user, read: ({ req }) => !!req.user,
}, },
hooks: {
beforeValidate: [
async ({ data, operation, originalDoc, req }) => {
if (data?.type !== "playerFaction") return data;
const payload = req.payload;
const existing = await payload.find({
collection: "factions",
where: { type: { equals: "playerFaction" } },
limit: 1,
depth: 0,
overrideAccess: true,
});
const conflict = existing.docs.find((doc) => doc.id !== originalDoc?.id);
if (conflict) {
throw new Error(
`Only one faction can be the Player Faction at a time. "${conflict.name}" is already the Player Faction.`,
);
}
return data;
},
],
},
fields: [ fields: [
{ {
name: "name", name: "name",
@ -24,6 +48,10 @@ export const Factions: CollectionConfig = {
name: "type", name: "type",
type: "select", type: "select",
options: [ options: [
{
label: "Player Faction",
value: "playerFaction",
},
{ {
label: "Major Faction", label: "Major Faction",
value: "majorFaction", value: "majorFaction",
@ -43,11 +71,68 @@ export const Factions: CollectionConfig = {
], ],
required: true, required: true,
defaultValue: "minorFaction", defaultValue: "minorFaction",
admin: {
description:
"Player Faction = the faction our unit is currently playing as. Only one faction can carry this type at a time.",
},
}, },
{ {
name: "description", name: "description",
type: "richText", type: "richText",
}, },
{
name: "reputation",
type: "number",
admin: {
description:
"This faction's standing in the world, from -100 (reviled) to 100 (revered). 0 is neutral. Adjusted manually by admins/GMs as campaign events unfold.",
},
defaultValue: 0,
min: -100,
max: 100,
},
{
name: "relationships",
type: "array",
labels: {
singular: "Relationship",
plural: "Relationships",
},
admin: {
description:
"Pairwise standing between this faction and another faction, from -100 (hostile) to 100 (allied). 0 is neutral. One entry per counterpart faction.",
},
fields: [
{
name: "targetFaction",
type: "relationship",
relationTo: "factions",
required: true,
admin: {
description: "The other faction in this pairwise relationship.",
},
},
{
name: "value",
type: "number",
required: true,
defaultValue: 0,
min: -100,
max: 100,
admin: {
description:
"Standing from -100 (hostile) to 100 (allied). 0 is neutral.",
},
},
{
name: "notes",
type: "textarea",
admin: {
description: "Optional context for why this relationship stands where it does.",
},
},
],
},
{ {
name: "playableFaction", name: "playableFaction",
type: "checkbox", type: "checkbox",
@ -70,5 +155,14 @@ export const Factions: CollectionConfig = {
relationTo: "campaigns", relationTo: "campaigns",
hasMany: true, hasMany: true,
}, },
{
name: "coverImage",
type: "upload",
relationTo: "media",
admin: {
description:
"Background image for the faction detail page hero. Renders translucent with a top-to-bottom fade to black. Recommended aspect ratio: 16:9 or wider.",
},
},
], ],
}; };

View file

@ -986,7 +986,10 @@ export interface Vehicle {
export interface Faction { export interface Faction {
id: number; id: number;
name: string; name: string;
type: 'majorFaction' | 'minorFaction' | 'corporation' | 'special'; /**
* Player Faction = the faction our unit is currently playing as. Only one faction can carry this type at a time.
*/
type: 'playerFaction' | 'majorFaction' | 'minorFaction' | 'corporation' | 'special';
description?: { description?: {
root: { root: {
type: string; type: string;
@ -1002,6 +1005,30 @@ export interface Faction {
}; };
[k: string]: unknown; [k: string]: unknown;
} | null; } | null;
/**
* This faction's standing in the world, from -100 (reviled) to 100 (revered). 0 is neutral. Adjusted manually by admins/GMs as campaign events unfold.
*/
reputation?: number | null;
/**
* Pairwise standing between this faction and another faction, from -100 (hostile) to 100 (allied). 0 is neutral. One entry per counterpart faction.
*/
relationships?:
| {
/**
* The other faction in this pairwise relationship.
*/
targetFaction: number | Faction;
/**
* Standing from -100 (hostile) to 100 (allied). 0 is neutral.
*/
value: number;
/**
* Optional context for why this relationship stands where it does.
*/
notes?: string | null;
id?: string | null;
}[]
| null;
/** /**
* Whether or not this faction is playable. Some factions may be specifically intended to be unplayable based on narrative requirements. * Whether or not this faction is playable. Some factions may be specifically intended to be unplayable based on narrative requirements.
*/ */
@ -1011,6 +1038,10 @@ export interface Faction {
*/ */
startingFaction?: boolean | null; startingFaction?: boolean | null;
associatedCampaigns?: (number | Campaign)[] | null; associatedCampaigns?: (number | Campaign)[] | null;
/**
* Background image for the faction detail page hero. Renders translucent with a top-to-bottom fade to black. Recommended aspect ratio: 16:9 or wider.
*/
coverImage?: (number | null) | Media;
updatedAt: string; updatedAt: string;
createdAt: string; createdAt: string;
} }
@ -1040,6 +1071,10 @@ export interface Campaign {
} | null; } | null;
startDate?: string | null; startDate?: string | null;
endDate?: string | null; endDate?: string | null;
/**
* Background image for the campaign selector card on the campaigns page. Renders translucent with hover effects. Recommended aspect ratio: 16:9 or square.
*/
coverImage?: (number | null) | Media;
updatedAt: string; updatedAt: string;
createdAt: string; createdAt: string;
} }
@ -1519,6 +1554,10 @@ export interface Mission {
* Hash of the last attendance state rendered to Discord (managed by the bot). * Hash of the last attendance state rendered to Discord (managed by the bot).
*/ */
discordAttendanceHash?: string | null; discordAttendanceHash?: string | null;
/**
* Background image for the mission page hero header. Renders translucent with a top-to-bottom fade to black. Recommended aspect ratio: 16:9 or wider.
*/
coverImage?: (number | null) | Media;
classification: { classification: {
map: number | Map; map: number | Map;
missionType: 'Zeus' | 'PvE' | 'PvP' | 'PvPvE' | 'Training' | 'Special'; missionType: 'Zeus' | 'PvE' | 'PvP' | 'PvPvE' | 'Training' | 'Special';
@ -2731,6 +2770,7 @@ export interface MissionsSelect<T extends boolean = true> {
operationType?: T; operationType?: T;
discordMessageId?: T; discordMessageId?: T;
discordAttendanceHash?: T; discordAttendanceHash?: T;
coverImage?: T;
classification?: classification?:
| T | T
| { | {
@ -2859,6 +2899,7 @@ export interface CampaignsSelect<T extends boolean = true> {
description?: T; description?: T;
startDate?: T; startDate?: T;
endDate?: T; endDate?: T;
coverImage?: T;
updatedAt?: T; updatedAt?: T;
createdAt?: T; createdAt?: T;
} }
@ -2870,9 +2911,19 @@ export interface FactionsSelect<T extends boolean = true> {
name?: T; name?: T;
type?: T; type?: T;
description?: T; description?: T;
reputation?: T;
relationships?:
| T
| {
targetFaction?: T;
value?: T;
notes?: T;
id?: T;
};
playableFaction?: T; playableFaction?: T;
startingFaction?: T; startingFaction?: T;
associatedCampaigns?: T; associatedCampaigns?: T;
coverImage?: T;
updatedAt?: T; updatedAt?: T;
createdAt?: T; createdAt?: T;
} }