/** * npc-card.js * Data model constants and static helper class for NPC Quick Cards. * No instantiation needed — use NPCCardData static methods throughout. */ export const MODULE_ID = "npc-quick-cards"; export const FLAG_KEY = "cardData"; // ── Speech quirk options ─────────────────────────────────────────────────────── export const SPEECH_QUIRKS = [ { value: "never-answers-directly", label: "Never answers directly" }, { value: "speaks-in-questions", label: "Speaks in questions" }, { value: "excessive-use-of-titles", label: "Excessive use of titles" }, { value: "repeats-last-word", label: "Repeats last word" }, { value: "long-pauses", label: "Long pauses" }, { value: "talks-over-people", label: "Talks over people" }, { value: "whisperer", label: "Whisperer" }, { value: "overly-formal", label: "Overly formal" }, { value: "custom", label: "Custom..." }, ]; // ── Personality tag categories ───────────────────────────────────────────────── export const TAG_CATEGORIES = [ { value: "motivation", label: "Motivation (amber)" }, { value: "relationship", label: "Relationship (blue)" }, { value: "knowledge", label: "Knowledge (green)" }, { value: "danger", label: "Danger (red)" }, { value: "other", label: "Other (grey)" }, ]; // ── Static data helpers ──────────────────────────────────────────────────────── export class NPCCardData { /** * Read card data flags from an actor with safe defaults. * Always returns a deep clone so callers can mutate freely. * @param {Actor} actor * @returns {object} */ static getFlags(actor) { return foundry.utils.deepClone( actor.getFlag(MODULE_ID, FLAG_KEY) ?? {} ); } /** * Persist card data flags to an actor. * @param {Actor} actor * @param {object} data * @returns {Promise} */ static async setFlags(actor, data) { return actor.setFlag(MODULE_ID, FLAG_KEY, data); } /** * Return true if this actor has any card data saved. * @param {Actor} actor * @returns {boolean} */ static hasCardData(actor) { const flags = actor.getFlag(MODULE_ID, FLAG_KEY); return flags !== undefined && flags !== null; } /** * Create a fresh, empty card data object. * @returns {object} */ static empty() { return { accent: "", speechQuirk: "", speechQuirkCustom: "", mannerNote: "", tags: [], attitudes: {}, secrets: [], linkedJournalId: null, lastSceneId: null, }; } /** * Generate a short random ID suitable for secret entries. * @returns {string} */ static generateId() { return foundry.utils.randomID(10); } /** * Look up the display label for a speech quirk value. * @param {string} value * @returns {string} */ static quirkLabel(value) { if (!value) return ""; return SPEECH_QUIRKS.find(q => q.value === value)?.label ?? value; } /** * Resolve the effective speech quirk display string for a card's flags. * Falls back to the custom text when quirk === "custom". * @param {object} flags * @returns {string} */ static resolvedQuirk(flags) { if (!flags.speechQuirk) return ""; if (flags.speechQuirk === "custom") return flags.speechQuirkCustom || "Custom"; return NPCCardData.quirkLabel(flags.speechQuirk); } /** * Build an attitudes array (sorted by PC name) suitable for template iteration. * Avoids the Handlebars {{lookup}} helper. * @param {object} attitudesObj - raw attitudes map { actorId: number } * @param {object[]} playerActors - [{ id, name }] * @returns {{ pcId: string, pcName: string, value: number }[]} */ static buildAttitudesArray(attitudesObj, playerActors) { return playerActors.map(pc => ({ pcId: pc.id, pcName: pc.name, value: attitudesObj?.[pc.id] ?? 0, })); } /** * Return a CSS class string describing an attitude value. * @param {number} value -3 to +3 * @returns {string} */ static attitudeClass(value) { if (value >= 2) return "attitude-trusted"; if (value <= -2) return "attitude-hostile"; return "attitude-neutral"; } }