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,
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: [
{
name: "name",
@ -24,6 +48,10 @@ export const Factions: CollectionConfig = {
name: "type",
type: "select",
options: [
{
label: "Player Faction",
value: "playerFaction",
},
{
label: "Major Faction",
value: "majorFaction",
@ -43,11 +71,68 @@ export const Factions: CollectionConfig = {
],
required: true,
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",
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",
type: "checkbox",
@ -70,5 +155,14 @@ export const Factions: CollectionConfig = {
relationTo: "campaigns",
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 {
id: number;
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?: {
root: {
type: string;
@ -1002,6 +1005,30 @@ export interface Faction {
};
[k: string]: unknown;
} | 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.
*/
@ -1011,6 +1038,10 @@ export interface Faction {
*/
startingFaction?: boolean | 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;
createdAt: string;
}
@ -1040,6 +1071,10 @@ export interface Campaign {
} | null;
startDate?: 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;
createdAt: string;
}
@ -1519,6 +1554,10 @@ export interface Mission {
* Hash of the last attendance state rendered to Discord (managed by the bot).
*/
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: {
map: number | Map;
missionType: 'Zeus' | 'PvE' | 'PvP' | 'PvPvE' | 'Training' | 'Special';
@ -2731,6 +2770,7 @@ export interface MissionsSelect<T extends boolean = true> {
operationType?: T;
discordMessageId?: T;
discordAttendanceHash?: T;
coverImage?: T;
classification?:
| T
| {
@ -2859,6 +2899,7 @@ export interface CampaignsSelect<T extends boolean = true> {
description?: T;
startDate?: T;
endDate?: T;
coverImage?: T;
updatedAt?: T;
createdAt?: T;
}
@ -2870,9 +2911,19 @@ export interface FactionsSelect<T extends boolean = true> {
name?: T;
type?: T;
description?: T;
reputation?: T;
relationships?:
| T
| {
targetFaction?: T;
value?: T;
notes?: T;
id?: T;
};
playableFaction?: T;
startingFaction?: T;
associatedCampaigns?: T;
coverImage?: T;
updatedAt?: T;
createdAt?: T;
}