/** * Class Item Data Model * * Defines the data schema for character classes in Vagabond RPG. * Classes are designed as draggable items that can be added to characters. * * Each class provides: * - Key stat recommendation * - Action style (casting skill for casters) * - Preferred combat zone * - Training grants (skills trained) * - Starting equipment pack * - Progression table (level 1-10) * * @extends VagabondItemBase */ import VagabondItemBase from "./base-item.mjs"; export default class ClassData extends VagabondItemBase { /** * Define the schema for class items. * * @returns {Object} The schema definition */ static defineSchema() { const fields = foundry.data.fields; const baseSchema = super.defineSchema(); return { ...baseSchema, // Key stat recommendation for this class keyStat: new fields.StringField({ required: true, initial: "might", }), // Action style / casting skill (for casters) // e.g., "arcana" for Wizard, "mysticism" for Druid, "influence" for Sorcerer actionStyle: new fields.StringField({ required: false, blank: true, }), // Preferred combat zone zone: new fields.StringField({ required: true, initial: "frontline", choices: ["frontline", "midline", "backline"], }), // Weapon proficiencies granted by this class // e.g., ["melee"], ["melee", "ranged"], or [] for none weaponTraining: new fields.ArrayField( new fields.StringField({ choices: ["melee", "ranged"] }), { initial: [] } ), // Skills trained by this class - supports fixed grants and player choices // Each entry is either: {type: "fixed", skills: ["craft"]} or {type: "choice", skills: ["detect", "sneak"], count: 2} // Empty skills array for "choice" means any skill skillTraining: new fields.ArrayField( new fields.SchemaField({ type: new fields.StringField({ required: true, choices: ["fixed", "choice"], initial: "fixed", }), skills: new fields.ArrayField(new fields.StringField(), { initial: [] }), count: new fields.NumberField({ integer: true, initial: 1 }), }), { initial: [] } ), // Legacy field - kept for backwards compatibility, prefer skillTraining trainedSkills: new fields.ArrayField(new fields.StringField(), { initial: [] }), // Starting equipment pack description startingPack: new fields.HTMLField({ required: false, blank: true }), // Is this a spellcasting class? isCaster: new fields.BooleanField({ initial: false }), // Progression table - level-by-level benefits progression: new fields.ArrayField( new fields.SchemaField({ level: new fields.NumberField({ integer: true, min: 1, max: 10 }), mana: new fields.NumberField({ integer: true, initial: 0 }), castingMax: new fields.NumberField({ integer: true, initial: 0 }), spellsKnown: new fields.NumberField({ integer: true, initial: 0 }), features: new fields.ArrayField(new fields.StringField(), { initial: [] }), }), { initial: [] } ), // Class features - detailed definitions features: new fields.ArrayField( new fields.SchemaField({ name: new fields.StringField({ required: true }), level: new fields.NumberField({ integer: true, min: 1, max: 10 }), description: new fields.HTMLField({ required: true }), passive: new fields.BooleanField({ initial: true }), // Active Effect changes this feature applies changes: new fields.ArrayField( new fields.SchemaField({ key: new fields.StringField({ required: true }), mode: new fields.NumberField({ integer: true, initial: 2 }), value: new fields.StringField({ required: true }), }), { initial: [] } ), // Choice features - features that require player selection requiresChoice: new fields.BooleanField({ initial: false }), choiceType: new fields.StringField({ required: false, blank: true }), // "perk", "spell", etc. choiceFilter: new fields.ObjectField({ required: false }), // Filter criteria for choices }), { initial: [] } ), // Resource this class uses (if any) - e.g., "Studied Dice" for Alchemist customResource: new fields.SchemaField({ name: new fields.StringField({ required: false }), max: new fields.StringField({ required: false }), // Can be a formula like "@level" }), }; } /** * Get the features available at a given level. * * @param {number} level - Character level to check * @returns {Array} Array of features available at or before this level */ getFeaturesAtLevel(level) { return this.features.filter((f) => f.level <= level); } /** * Get the progression entry for a given level. * * @param {number} level - Character level to check * @returns {Object|null} Progression data for the level */ getProgressionAtLevel(level) { return this.progression.find((p) => p.level === level) || null; } /** * Get cumulative mana pool at a given level. * * @param {number} level - Character level * @returns {number} Total mana pool */ getManaAtLevel(level) { let totalMana = 0; for (const prog of this.progression) { if (prog.level <= level) { totalMana += prog.mana || 0; } } return totalMana; } /** * Get cumulative casting max at a given level. * Casting max increases are cumulative across levels. * * @param {number} level - Character level * @returns {number} Casting max (max mana per spell) */ getCastingMaxAtLevel(level) { let castingMax = 0; for (const prog of this.progression) { if (prog.level <= level) { castingMax += prog.castingMax || 0; } } return castingMax; } /** * Get chat card data for displaying class information. * * @returns {Object} Chat card data */ getChatData() { const data = super.getChatData(); data.keyStat = this.keyStat; data.zone = this.zone; data.isCaster = this.isCaster; data.weaponTraining = this.weaponTraining; data.skillTraining = this.skillTraining; data.trainedSkills = this.trainedSkills; // Legacy return data; } }