From ce885d8eaed4e299cb7ea262662ac0dd7bae21bb Mon Sep 17 00:00:00 2001 From: Dave Date: Mon, 13 Oct 2025 22:44:31 +0200 Subject: [PATCH] Added AWQ Articles --- .../anatomy-of-a-player-character.mdoc | 86 +++++ content/meta/index.mdoc | 2 +- keystatic.config.ts | 4 + src/keystatic/collections/awq/article.ts | 14 + src/keystatic/collections/awq/index.ts | 7 + src/keystatic/fields/article.ts | 7 +- src/keystatic/fields/awq/general.ts | 304 ++++++++++++++++++ src/keystatic/fields/general.ts | 21 ++ src/keystatic/fields/meta.ts | 1 - 9 files changed, 443 insertions(+), 3 deletions(-) create mode 100644 content/awq/articles/anatomy-of-a-player-character.mdoc create mode 100644 src/keystatic/collections/awq/article.ts create mode 100644 src/keystatic/collections/awq/index.ts create mode 100644 src/keystatic/fields/awq/general.ts create mode 100644 src/keystatic/fields/general.ts diff --git a/content/awq/articles/anatomy-of-a-player-character.mdoc b/content/awq/articles/anatomy-of-a-player-character.mdoc new file mode 100644 index 0000000..93b9a1d --- /dev/null +++ b/content/awq/articles/anatomy-of-a-player-character.mdoc @@ -0,0 +1,86 @@ +--- +title: Anatomy of a Player Character +path: awq +cover: + showInHeader: false +meta: + publicationDate: 2025-10-08T08:43:00.000Z + status: draft + isFeatured: false + tags: [] +seo: + noIndex: false +--- +Every model shares some common abilities + +## Main Characteristics +Characteristics define the innate abilities of a model, and each characteristic has two skills associated with it +- **Weapon Skill (WS):** Aptitude in landing and avoiding blows in close combat; governs _Melee_ and _Defence_ +- **Ballistic Skill (BS):** Capability of Hand-Eye-Coordination; governs _Skirmish_ and _Evasion_ +- **Strength (S):** Used for brute force, stamina and might; governs _Brawn_ and _Toil_ +- **Toughness (T):** Resistance to physical Trauma; governs _Consume Alcohol_ and _Endurance_ +- **Initiative(I):** Reflects speed of thought and perception; governs _Perception_ and _Outdoor Survival_ +- **Dexterity (Dex):** Affinity for performing fine and delicate manual tasks; governs _Skullduggery_ and _Tradecraft_ +- **Agility (Ag):** Physical coordination and natural athleticism; governs _Stealth_ and _Athletics_ +- **Intelligence (Int):** Power of thought, analysis, and understanding; governs _Intuition_ and _Education_ +- **Willpower (WP):** General strength if mind; governs _Cool_ and _Animal Handling_ +- **Fellowship (Fel):** Ability to get on with people; governs _Leadership_ and _Charm_ + +## Secondary Characteristics +- **Corruption Threshold (CT):[^*]** Ability to withstand _Mutation_ +- **Insanity Threshold (IT):[^*]** Ability to withstand _Insanity_ +- **Wounds (W):** Number of wounds a model can endure before _out of play_ +- **Movement (M):** Indicator of how far a model can move under normal conditionsA +- **Fate Points (FP):[^*]** Can be used to avoid certain death and dark fates +- **Luck Points (LP):[^*]** Used for Re-rolls +- **Attacks (A):** Indicator of the numbers of attacks a character can make in a single round +- **Magic (Mag):** Denotes the Model's Wizard Level + +## Skills +Used in _Checks_ +- **Melee (WS):** Used to make _Melee Attack_ actions +- **Defense (WS):** Used to defend against a model's _Melee Attack_ action +- **Shooting (BS):** Used to make _Ranged Attack_ actions with _Missile_ weapons +- **Throwing (BS):** Used to make _Ranged Attack_ actions with _Throwing_ weapons +- **Brawn (S):** Used for immediate feats of strength +- **Toil (T):** Used for prolonged manual labor +- **Consume Alcohol (T):** Used for resisting short-term hazards like alcohol and poison +- **Endurance (T):** Used to endure hardship, withstand deprivation, and survive harsh environments +- **Perception (I):** Used to notice things +- **Dodge (I):** Used to evade attacks and immediate hazards, e.g. Traps +- **Stealth (Ag):** Used for moving quietly and concealing +- **Athletics (Ag):** Used for running, jumping, climbing, and swimming +- **Streetwise (Dex):** Used for picking locks or pockets, disarming traps and other feats of Sleight of Hand +- **Crafting (Dex):** Used when crafting trappings +- **Intuition (Int):** Used for detecting subterfuge and determining value of objects +- **Education (Int):** Used for recalling relevant information +- **Cool (WP):** Used to remain calm under stress, resisting fear, and psychological coercion +- **Animal Handling (WP):** Used to charm, train, and care for animals +- **Leadership (Fel):** Used for intimidation, command, and coercing obedience; often resisted by _Discipline_ +- **Charm (Fel):** Used for deceiving, blathering, haggling, gossiping; usually resisted by _Intuition_ + +## Traits +Inherent abilities often based on Species, examples include Flier (for flying Monsters), Dark Vision, Mutation, and so on + +## Lore[^*] +Define what a character knows and specialist skills and come in the following categories +- _Academic:_ Represent various academic fields like Accountancy, Anatomy, History and Law +- _Cultural:_ Represent the knowledge of social groups and language +- _Enemy:_ Represent the knowledge of adversaries and how to effectively combat them +- _Environment:_ Represent the knowledge of surviving in various hazardous environment +- _Magic Lores:_ Represent the knowledge of the various forms of magic and enables casting spells from them+ +- _Specialist Weapon Groups:_ Represent training with special weapons like Polearms, Two-Handed Swords, Blackpowder Guns +- _Trade Lores:_ Represent the knowledge and ability to create certain trappings or work in certain fields, example Blacksmith, Weaver, Engineer, Artist +- _Vehicle Lores:_ Represent knowledge in operating vehicles and mounts + +## Talents[^*] +Represent certain knacks, tricks and innate abilities a character has, examples would be Ambidextrous, Aetheric Attunement, Menacing or Acute Senses + +## Maneuvers[^*] +Represent special combat actions and tactics a character can employ during combat, examples include Formation Fighting, Disarm, and Shield Bash + +## Careers[^*] +Represent building blocks of a character, that provide certain skills, lores, talents, maneuvers, etc. + + +[^*]: Only applicable for _Player Characters_ diff --git a/content/meta/index.mdoc b/content/meta/index.mdoc index 3dd24e9..520e12a 100644 --- a/content/meta/index.mdoc +++ b/content/meta/index.mdoc @@ -10,7 +10,7 @@ cover: meta: publicationDate: 2025-10-01T10:30:00.000Z status: draft - isFeatured: true + isFeatured: false tags: - how-and-what author: dave-damage diff --git a/keystatic.config.ts b/keystatic.config.ts index efc569c..5b31b40 100644 --- a/keystatic.config.ts +++ b/keystatic.config.ts @@ -5,6 +5,9 @@ import AuthorsCollection from '@/keystatic/collections/taxonomy/authors'; import TagsCollection from '@/keystatic/collections/taxonomy/tags'; import MetaPostsCollection from '@/keystatic/collections/meta/article'; +import AWQColletions from '@/keystatic/collections/awq'; +import awqCollections from '@/keystatic/collections/awq'; + export default config({ storage: { kind: 'local', @@ -14,5 +17,6 @@ export default config({ authors: AuthorsCollection, tags: TagsCollection, meta_posts: MetaPostsCollection, + ...awqCollections, }, }); diff --git a/src/keystatic/collections/awq/article.ts b/src/keystatic/collections/awq/article.ts new file mode 100644 index 0000000..ee45cf7 --- /dev/null +++ b/src/keystatic/collections/awq/article.ts @@ -0,0 +1,14 @@ +import { collection } from '@keystatic/core'; + +import { createArticleField } from '@/keystatic/fields/article'; + +export default collection({ + label: 'AWQ - Posts', + slugField: 'title', + path: 'content/awq/articles/*', + format: { contentField: 'content' }, + entryLayout: 'content', + schema: { + ...createArticleField('awq/articles', 'awq'), + }, +}); diff --git a/src/keystatic/collections/awq/index.ts b/src/keystatic/collections/awq/index.ts new file mode 100644 index 0000000..2f972ea --- /dev/null +++ b/src/keystatic/collections/awq/index.ts @@ -0,0 +1,7 @@ +import posts from '@/keystatic/collections/awq/article'; + +const awqCollections = { + awq_posts: posts, +}; + +export default awqCollections; diff --git a/src/keystatic/fields/article.ts b/src/keystatic/fields/article.ts index 17ce3d3..20b6b4b 100644 --- a/src/keystatic/fields/article.ts +++ b/src/keystatic/fields/article.ts @@ -1,12 +1,17 @@ import { fields } from '@keystatic/core'; import { createContentField } from '@/keystatic/fields/content'; +import { createPathField } from '@/keystatic/fields/general'; import { createSEOField } from '@/keystatic/fields/seo'; import { createMetaField } from '@/keystatic/fields/meta'; -export const createArticleField = (imageSubfolder: string) => ({ +export const createArticleField = ( + imageSubfolder: string, + defaultPath: string = '' +) => ({ title: fields.slug({ name: { label: 'Title' } }), summary: fields.text({ label: 'Summary', multiline: true }), + path: createPathField(defaultPath), cover: fields.object({ src: fields.image({ label: 'Cover Image', diff --git a/src/keystatic/fields/awq/general.ts b/src/keystatic/fields/awq/general.ts new file mode 100644 index 0000000..7107f58 --- /dev/null +++ b/src/keystatic/fields/awq/general.ts @@ -0,0 +1,304 @@ +import { type ComponentSchema, fields } from '@keystatic/core'; +import { createOperatorField } from '@/keystatic/fields/general'; + +export const createCharacteristicReferenceField = (): ComponentSchema => + fields.select({ + label: 'Characteristic', + options: [ + { label: 'Weapon Skill', value: 'ws' }, + { label: 'Ballistic Skill', value: 'bs' }, + { label: 'Strength', value: 's' }, + { label: 'Toughness', value: 't' }, + { label: 'Initiative', value: 'i' }, + { label: 'Dexterity', value: 'dex' }, + { label: 'Agility', value: 'ag' }, + { label: 'Intelligence', value: 'int' }, + { label: 'Willpower', value: 'wp' }, + { label: 'Fellowship', value: 'fel' }, + ], + defaultValue: 'ws', + }); + +export const createSkillReferenceField = (): ComponentSchema => + fields.select({ + label: 'Skill', + options: [ + { + label: 'Melee', + value: 'melee', + }, + { + label: 'Defence', + value: 'defence', + }, + { + label: 'Shooting', + value: 'shooting', + }, + { + label: 'Throwing', + value: 'throwing', + }, + { + label: 'Brawn', + value: 'brawn', + }, + { + label: 'Toil', + value: 'toil', + }, + { + label: 'Consume Alcohol', + value: 'consume-alcohol', + }, + { + label: 'Endurance', + value: 'endurance', + }, + { + label: 'Perception', + value: 'perception', + }, + { + label: 'Dodge', + value: 'dodge', + }, + { + label: 'Skullduggery', + value: 'skullduggery', + }, + { + label: 'Tradecraft', + value: 'tradecraft', + }, + { + label: 'Stealth', + value: 'stealth', + }, + { + label: 'Athletics', + value: 'athletics', + }, + { + label: 'Intuition', + value: 'intuition', + }, + { + label: 'Education', + value: 'education', + }, + { + label: 'Discipline', + value: 'discipline', + }, + { + label: 'Animal Handling', + value: 'animal-handling', + }, + { + label: 'Leadership', + value: 'leadership', + }, + { + label: 'Charm', + value: 'charm', + }, + ], + defaultValue: 'melee', + }); + +export const createSizeReferenceField = (): ComponentSchema => + fields.select({ + label: 'Size', + options: [ + { + label: 'Tiny', + value: 'tiny', + }, + { + label: 'Little', + value: 'little', + }, + { + label: 'Small', + value: 'small', + }, + { + label: 'Average', + value: 'average', + }, + { + label: 'Large', + value: 'large', + }, + { + label: 'Enormous', + value: 'enormous', + }, + { + label: 'Monstrous', + value: 'monstrous', + }, + ], + defaultValue: 'average', + }); + +export const createPsychologyReferenceField = (): ComponentSchema => + fields.object({ + type: fields.select({ + label: 'Psychology Type', + defaultValue: 'animosity', + options: [ + { label: 'Animosity', value: 'animosity' }, + { label: 'Fear', value: 'fear' }, + { label: 'Frenzy', value: 'frenzy' }, + { label: 'Hatred', value: 'hatred' }, + { label: 'Prejudice', value: 'prejudice' }, + { label: 'Terror', value: 'terror' }, + ], + }), + target: fields.text({ + label: 'Target', + description: 'e.g. Elves, Undead, Chaos', + }), + rating: fields.number({ + label: 'Rating', + description: 'Fear/Terror Only', + }), + }); + +export const createRequirementsField = () => + fields.blocks( + { + characteristic: { + label: 'Characteristic', + schema: fields.object({ + characteristic: createCharacteristicReferenceField(), + operator: createOperatorField(), + value: fields.number({ label: 'Value', defaultValue: 3 }), + }), + }, + skill: { + label: 'Skill', + schema: fields.object({ + skill: createSkillReferenceField(), + operator: createOperatorField(), + value: fields.number({ label: 'Value', defaultValue: 3 }), + }), + }, + status: { + label: 'Status', + schema: fields.multiselect({ + label: 'Status Tier', + options: [ + { + value: 'copper', + label: 'Copper', + }, + { + value: 'silver', + label: 'Silver', + }, + { + value: 'gold', + label: 'gold', + }, + ], + defaultValue: ['copper'], + }), + }, + talent: { + label: 'Talent', + schema: fields.relationship({ + label: 'Talent', + collection: 'awq_talents', + }), + }, + lore: { + label: 'Lore', + schema: fields.relationship({ + label: 'Lore', + collection: 'awq_lores', + }), + }, + }, + { label: 'Requirements' } + ); + +export const createTraitsField = (): ComponentSchema => + fields.array( + fields.object({ + name: fields.text({ + label: 'Trait name', + }), + description: fields.text({ + label: 'Description', + multiline: true, + }), + grants: fields.blocks( + { + talent: { + label: 'Talent', + schema: fields.relationship({ + label: 'Talent', + collection: 'awq_talents', + }), + }, + talent_options: { + label: 'Talent Options', + schema: fields.object({ + choices: fields.number({ + label: 'Number to choose', + defaultValue: 1, + }), + options: fields.array( + fields.relationship({ + label: 'Talent Options', + collection: 'awq_talents', + }) + ), + }), + }, + talent_random: { + label: 'Random Talent', + schema: fields.object({ + rolls: fields.number({ + label: 'Rolls', + defaultValue: 1, + }), + table: fields.array( + fields.object({ + roll: fields.object({ + min: fields.number({ label: 'Min' }), + max: fields.number({ label: 'Max' }), + }), + talent: fields.relationship({ + label: 'Talent', + collection: 'awq_talents', + }), + }) + ), + }), + }, + psychology: { + label: 'Psychology', + schema: createPsychologyReferenceField(), + }, + custom: { + label: 'Custom Effect', + schema: fields.text({ + label: 'Custom Effect', + multiline: true, + }), + }, + }, + { + label: 'Trait Grants', + } + ), + }), + { + label: 'Traits', + itemLabel: (props) => props.fields.name.value || 'Unnamed Trait', + } + ); diff --git a/src/keystatic/fields/general.ts b/src/keystatic/fields/general.ts new file mode 100644 index 0000000..8614532 --- /dev/null +++ b/src/keystatic/fields/general.ts @@ -0,0 +1,21 @@ +import { ComponentSchema, fields } from '@keystatic/core'; + +export const createOperatorField = (): ComponentSchema => + fields.select({ + label: 'Operator', + options: [ + { label: '>=', value: 'gte' }, + { label: '>', value: 'gt' }, + { label: '=', value: 'eq' }, + { label: '<', value: 'lt' }, + { label: '<=', value: 'lte' }, + ], + defaultValue: 'eq', + }); + +export const createPathField = (defaultValue: string): ComponentSchema => + fields.text({ + label: 'Path', + description: 'Path on the website', + defaultValue: defaultValue, + }); diff --git a/src/keystatic/fields/meta.ts b/src/keystatic/fields/meta.ts index 968b2ee..37d1e99 100644 --- a/src/keystatic/fields/meta.ts +++ b/src/keystatic/fields/meta.ts @@ -39,6 +39,5 @@ export const createMetaField = (): ComponentSchema => }, { label: 'Meta Information', - layout: [4, 4, 4, 12, 12, 12], } );