Files
npc-quick-cards/scripts/npc-card.js
T

140 lines
4.6 KiB
JavaScript

/**
* 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";
}
}